@sinequa/atomic-angular 2.8.0 → 2.9.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.
@@ -16263,25 +16263,124 @@ function alertSummary(lang, alert) {
16263
16263
  if (alert.frequency !== Alert.Frequency.Immediate && days !== Alert.Days.None && (days & Alert.Days.AllDays) !== Alert.Days.AllDays) parts.push({ text: formatDays(days, lang) });
16264
16264
  return parts;
16265
16265
  }
16266
- const ALREADY_NOTIFIED = [
16267
- 400,
16268
- 403,
16269
- 500,
16270
- 503
16271
- ];
16266
+ function querySummary(query) {
16267
+ if (!query) return [];
16268
+ const parts = [];
16269
+ if (query.text) parts.push({ text: `“${query.text}”` });
16270
+ if (query.tab) parts.push({
16271
+ key: "alerts.queryTab",
16272
+ params: { tab: query.tab }
16273
+ });
16274
+ const filters = Array.isArray(query.filters) ? query.filters.length : 0;
16275
+ if (filters === 1) parts.push({ key: "alerts.queryFiltersOne" });
16276
+ else if (filters > 1) parts.push({
16277
+ key: "alerts.queryFiltersMany",
16278
+ params: { count: filters }
16279
+ });
16280
+ return parts;
16281
+ }
16272
16282
  function saveErrorDetail(error) {
16273
16283
  const { errorMessage, message } = error ?? {};
16274
16284
  return errorMessage ?? message;
16275
16285
  }
16276
- function notifySaveFailed(transloco, error) {
16277
- const { status } = error ?? {};
16278
- if (status !== void 0 && ALREADY_NOTIFIED.includes(status)) return;
16279
- notify.error(transloco.translate("alerts.saveFailed"), {
16280
- description: saveErrorDetail(error),
16281
- closeButton: true,
16282
- duration: 5e3
16286
+ var AlertSaveErrorComponent = class AlertSaveErrorComponent {
16287
+ detail = input(void 0, ...ngDevMode ? [{ debugName: "detail" }] : /* istanbul ignore next */ []);
16288
+ static ɵfac = i0.ɵɵngDeclareFactory({
16289
+ minVersion: "12.0.0",
16290
+ version: "22.2.1",
16291
+ ngImport: i0,
16292
+ type: AlertSaveErrorComponent,
16293
+ deps: [],
16294
+ target: i0.ɵɵFactoryTarget.Component
16283
16295
  });
16284
- }
16296
+ static ɵcmp = i0.ɵɵngDeclareComponent({
16297
+ minVersion: "17.0.0",
16298
+ version: "22.2.1",
16299
+ type: AlertSaveErrorComponent,
16300
+ isStandalone: true,
16301
+ selector: "alert-save-error",
16302
+ inputs: { detail: {
16303
+ classPropertyName: "detail",
16304
+ publicName: "detail",
16305
+ isSignal: true,
16306
+ isRequired: false,
16307
+ transformFunction: null
16308
+ } },
16309
+ host: {
16310
+ attributes: { "role": "alert" },
16311
+ classAttribute: "save-error"
16312
+ },
16313
+ providers: [provideTranslocoScope("alerts")],
16314
+ ngImport: i0,
16315
+ template: `
16316
+ <div class="min-h-0 overflow-hidden">
16317
+ <div
16318
+ class="flex w-full items-start gap-(--spacing-xs) rounded-(--radius-md) border border-(--stroke-error-base) bg-(--bg-error-lightest) px-(--spacing-sm) py-(--spacing-xs) text-(--font-error-base)">
16319
+ <TriangleAlert class="mt-px size-3.5 shrink-0" width="14" height="14" aria-hidden="true" />
16320
+ <div class="flex min-w-0 flex-col">
16321
+ <p class="m-0 text-(length:--size-sm) font-medium">{{ "alerts.saveFailed" | transloco }}</p>
16322
+ @if (detail()) {
16323
+ <p class="m-0 text-(length:--size-xs) wrap-anywhere opacity-80">{{ detail() }}</p>
16324
+ }
16325
+ </div>
16326
+ </div>
16327
+ </div>
16328
+ `,
16329
+ isInline: true,
16330
+ styles: [":host{display:grid;animation:save-error-in .22s ease-out}:host>div>div{animation:save-error-card-in .22s ease-out}@keyframes save-error-in{0%{grid-template-rows:0fr}to{grid-template-rows:1fr}}@keyframes save-error-card-in{0%{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:none}}@media(prefers-reduced-motion:reduce){:host,:host>div>div{animation:none}}\n"],
16331
+ dependencies: [{
16332
+ kind: "component",
16333
+ type: TriangleAlertIcon,
16334
+ selector: "triangle-alert, triangle-alert-icon, TriangleAlert, TriangleAlertIcon",
16335
+ inputs: ["class"]
16336
+ }, {
16337
+ kind: "pipe",
16338
+ type: TranslocoPipe,
16339
+ name: "transloco"
16340
+ }]
16341
+ });
16342
+ };
16343
+ i0.ɵɵngDeclareClassMetadata({
16344
+ minVersion: "12.0.0",
16345
+ version: "22.2.1",
16346
+ ngImport: i0,
16347
+ type: AlertSaveErrorComponent,
16348
+ decorators: [{
16349
+ type: Component,
16350
+ args: [{
16351
+ selector: "alert-save-error",
16352
+ imports: [TranslocoPipe, TriangleAlertIcon],
16353
+ providers: [provideTranslocoScope("alerts")],
16354
+ host: {
16355
+ class: "save-error",
16356
+ role: "alert"
16357
+ },
16358
+ template: `
16359
+ <div class="min-h-0 overflow-hidden">
16360
+ <div
16361
+ class="flex w-full items-start gap-(--spacing-xs) rounded-(--radius-md) border border-(--stroke-error-base) bg-(--bg-error-lightest) px-(--spacing-sm) py-(--spacing-xs) text-(--font-error-base)">
16362
+ <TriangleAlert class="mt-px size-3.5 shrink-0" width="14" height="14" aria-hidden="true" />
16363
+ <div class="flex min-w-0 flex-col">
16364
+ <p class="m-0 text-(length:--size-sm) font-medium">{{ "alerts.saveFailed" | transloco }}</p>
16365
+ @if (detail()) {
16366
+ <p class="m-0 text-(length:--size-xs) wrap-anywhere opacity-80">{{ detail() }}</p>
16367
+ }
16368
+ </div>
16369
+ </div>
16370
+ </div>
16371
+ `,
16372
+ styles: [":host{display:grid;animation:save-error-in .22s ease-out}:host>div>div{animation:save-error-card-in .22s ease-out}@keyframes save-error-in{0%{grid-template-rows:0fr}to{grid-template-rows:1fr}}@keyframes save-error-card-in{0%{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:none}}@media(prefers-reduced-motion:reduce){:host,:host>div>div{animation:none}}\n"]
16373
+ }]
16374
+ }],
16375
+ propDecorators: { detail: [{
16376
+ type: i0.Input,
16377
+ args: [{
16378
+ isSignal: true,
16379
+ alias: "detail",
16380
+ required: false
16381
+ }]
16382
+ }] }
16383
+ });
16285
16384
  const WEEKDAYS = [
16286
16385
  {
16287
16386
  key: "monday",
@@ -16327,6 +16426,15 @@ function emptyAlertModel() {
16327
16426
  active: true
16328
16427
  };
16329
16428
  }
16429
+ function modelOf(alert) {
16430
+ return {
16431
+ name: alert.name,
16432
+ frequency: alert.frequency,
16433
+ days: weekdaysFromFlags(alert.days),
16434
+ time: alert.times,
16435
+ active: alert.active
16436
+ };
16437
+ }
16330
16438
  const alertSchema = schema((p) => {
16331
16439
  required(p.name);
16332
16440
  validate(p.name, ({ value }) => value().length > 0 && !value().trim() ? { kind: "required" } : void 0);
@@ -16334,13 +16442,17 @@ const alertSchema = schema((p) => {
16334
16442
  required(p.time);
16335
16443
  validate(p.days, ({ value, valueOf }) => valueOf(p.frequency) === Alert.Frequency.Immediate || Object.values(value()).some(Boolean) ? void 0 : { kind: "noDaySelected" });
16336
16444
  });
16337
- var AlertDialog = class AlertDialog {
16338
- call = injectCallRef();
16445
+ let nextFormId = 0;
16446
+ var AlertFormComponent = class AlertFormComponent {
16339
16447
  queryService = inject(QueryService);
16340
16448
  queryParamsStore = inject(QueryParamsStore);
16341
16449
  userSettingsStore = inject(UserSettingsStore);
16342
16450
  transloco = inject(TranslocoService);
16343
- dialog = viewChild(DialogComponent, ...ngDevMode ? [{ debugName: "dialog" }] : /* istanbul ignore next */ []);
16451
+ lang = toSignal(this.transloco.langChanges$);
16452
+ index = input(void 0, ...ngDevMode ? [{ debugName: "index" }] : /* istanbul ignore next */ []);
16453
+ saved = output();
16454
+ executed = output();
16455
+ formId = `alert-form-${nextFormId++}`;
16344
16456
  fieldClass = FORM_FIELD_CLASS;
16345
16457
  labelClass = FORM_LABEL_CLASS;
16346
16458
  errorClass = FORM_ERROR_CLASS;
@@ -16350,39 +16462,37 @@ var AlertDialog = class AlertDialog {
16350
16462
  Alert.Frequency.Immediate
16351
16463
  ];
16352
16464
  frequency = Alert.Frequency;
16465
+ Days = Alert.Days;
16466
+ querySummary = querySummary;
16353
16467
  frequencyLabel = (value) => this.transloco.translate(`alerts.frequency.${Alert.Frequency[value]}`);
16354
16468
  weekdays = WEEKDAYS;
16355
- index = signal(void 0, ...ngDevMode ? [{ debugName: "index" }] : /* istanbul ignore next */ []);
16356
- alert = signal(void 0, ...ngDevMode ? [{ debugName: "alert" }] : /* istanbul ignore next */ []);
16357
- canUpdateQuery = signal(false, ...ngDevMode ? [{ debugName: "canUpdateQuery" }] : /* istanbul ignore next */ []);
16469
+ dayNames = computed(() => weekdayNames(this.lang() ?? this.transloco.getActiveLang()), ...ngDevMode ? [{ debugName: "dayNames" }] : /* istanbul ignore next */ []);
16470
+ alert = computed(() => {
16471
+ const index = this.index();
16472
+ return index === void 0 ? void 0 : this.userSettingsStore.alerts()[index];
16473
+ }, ...ngDevMode ? [{ debugName: "alert" }] : /* istanbul ignore next */ []);
16474
+ currentSearch = rxResource({
16475
+ params: () => this.alert() ? true : void 0,
16476
+ stream: () => this.queryService.search(this.queryParamsStore.getQuery(), false).pipe(map((response) => response.records?.length > 0))
16477
+ });
16478
+ canUpdateQuery = computed(() => this.currentSearch.value() === true, ...ngDevMode ? [{ debugName: "canUpdateQuery" }] : /* istanbul ignore next */ []);
16358
16479
  updateStatus = signal("idle", ...ngDevMode ? [{ debugName: "updateStatus" }] : /* istanbul ignore next */ []);
16359
16480
  saveError = signal(void 0, ...ngDevMode ? [{ debugName: "saveError" }] : /* istanbul ignore next */ []);
16360
- alertModel = signal(emptyAlertModel(), ...ngDevMode ? [{ debugName: "alertModel" }] : /* istanbul ignore next */ []);
16481
+ alertModel = linkedSignal(() => {
16482
+ const alert = this.alert();
16483
+ return alert ? modelOf(alert) : emptyAlertModel();
16484
+ }, ...ngDevMode ? [{ debugName: "alertModel" }] : /* istanbul ignore next */ []);
16361
16485
  alertForm = form(this.alertModel, alertSchema, {
16362
16486
  name: "alert",
16363
16487
  submission: { action: async () => this.persist() }
16364
16488
  });
16489
+ saving = computed(() => this.alertForm().submitting(), ...ngDevMode ? [{ debugName: "saving" }] : /* istanbul ignore next */ []);
16365
16490
  showDaysError = computed(() => fieldShowsError(this.alertForm.days), ...ngDevMode ? [{ debugName: "showDaysError" }] : /* istanbul ignore next */ []);
16366
- constructor() {
16367
- afterNextRender(() => this.init(this.call.props()));
16368
- }
16369
- async init(index) {
16370
- this.index.set(index);
16371
- const alert = index !== void 0 ? this.userSettingsStore.alerts()[index] : void 0;
16372
- this.alert.set(alert);
16373
- if (alert) {
16374
- this.alertModel.set({
16375
- name: alert.name,
16376
- frequency: alert.frequency,
16377
- days: weekdaysFromFlags(alert.days),
16378
- time: alert.times,
16379
- active: alert.active
16380
- });
16381
- const q = this.queryParamsStore.getQuery();
16382
- const response = await firstValueFrom(this.queryService.search(q, false));
16383
- this.canUpdateQuery.set(response.records?.length > 0);
16384
- } else this.alertModel.set(emptyAlertModel());
16385
- this.dialog()?.open();
16491
+ setDays(mask) {
16492
+ this.alertModel.update((model) => ({
16493
+ ...model,
16494
+ days: weekdaysFromFlags(mask)
16495
+ }));
16386
16496
  }
16387
16497
  async persist() {
16388
16498
  this.saveError.set(void 0);
@@ -16393,7 +16503,7 @@ var AlertDialog = class AlertDialog {
16393
16503
  this.saveError.set({ detail: saveErrorDetail(error) });
16394
16504
  return;
16395
16505
  }
16396
- this.dialog()?.close("dialog-confirm");
16506
+ this.saved.emit();
16397
16507
  }
16398
16508
  async create() {
16399
16509
  const model = this.alertModel();
@@ -16468,209 +16578,197 @@ var AlertDialog = class AlertDialog {
16468
16578
  filters,
16469
16579
  name: q.name
16470
16580
  });
16471
- this.dialog()?.close();
16581
+ this.executed.emit();
16472
16582
  }
16473
16583
  static ɵfac = i0.ɵɵngDeclareFactory({
16474
16584
  minVersion: "12.0.0",
16475
16585
  version: "22.2.1",
16476
16586
  ngImport: i0,
16477
- type: AlertDialog,
16587
+ type: AlertFormComponent,
16478
16588
  deps: [],
16479
16589
  target: i0.ɵɵFactoryTarget.Component
16480
16590
  });
16481
16591
  static ɵcmp = i0.ɵɵngDeclareComponent({
16482
16592
  minVersion: "17.0.0",
16483
16593
  version: "22.2.1",
16484
- type: AlertDialog,
16594
+ type: AlertFormComponent,
16485
16595
  isStandalone: true,
16486
- selector: "alert-dialog, alertdialog, AlertDialog",
16596
+ selector: "alert-form, alertform, AlertFormComponent",
16597
+ inputs: { index: {
16598
+ classPropertyName: "index",
16599
+ publicName: "index",
16600
+ isSignal: true,
16601
+ isRequired: false,
16602
+ transformFunction: null
16603
+ } },
16604
+ outputs: {
16605
+ saved: "saved",
16606
+ executed: "executed"
16607
+ },
16608
+ host: { classAttribute: "block @container" },
16487
16609
  providers: [provideTranslocoScope("alerts")],
16488
- viewQueries: [{
16489
- propertyName: "dialog",
16490
- first: true,
16491
- predicate: DialogComponent,
16492
- descendants: true,
16493
- isSignal: true
16494
- }],
16495
16610
  ngImport: i0,
16496
16611
  template: `
16497
- <dialog #dialog (closed)="call.end($event)">
16498
- <DialogContent size="md">
16499
- <DialogHeader [closeLabel]="'alerts.close' | transloco">
16500
- <DialogTitle>{{ 'alerts.createAlert' | transloco }}</DialogTitle>
16501
- </DialogHeader>
16502
-
16503
- <!--
16504
- [formRoot] poses novalidate, intercepts submit, calls preventDefault() and triggers submit()
16505
- on the field tree: Enter submits natively, and the action only runs on a valid form.
16506
- class="contents" keeps the DialogContent flex column intact — the body/footer slots are
16507
- styled by descendant selectors, which a display: contents wrapper does not break.
16508
- -->
16509
- <form [formRoot]="alertForm" class="contents" [attr.aria-label]="'alerts.createAlert' | transloco">
16510
- <DialogBody class="gap-(--spacing-md)">
16511
- <div [class]="fieldClass">
16512
- <label for="alertName" [class]="labelClass">{{ 'alerts.alertName' | transloco }}</label>
16513
- <input-group [scheme]="showError(alertForm.name) ? 'error' : 'default'">
16514
- <input
16515
- input-control
16516
- id="alertName"
16517
- type="text"
16518
- autocomplete="off"
16519
- spellcheck="false"
16520
- [formField]="alertForm.name"
16521
- [attr.placeholder]="'alerts.placeholder' | transloco"
16522
- [attr.aria-invalid]="showError(alertForm.name) ? 'true' : null"
16523
- [attr.aria-describedby]="showError(alertForm.name) ? 'alertName-error' : null" />
16524
- </input-group>
16525
- @if (showError(alertForm.name)) {
16526
- <p id="alertName-error" role="alert" [class]="errorClass">{{ errorKey(alertForm.name) | transloco }}</p>
16527
- }
16528
- </div>
16529
-
16530
- @let selectedFrequency = alertForm.frequency().value();
16531
- <div [class]="fieldClass">
16532
- <label [class]="labelClass" for="alertFrequency">{{ 'alerts.alertFrequency' | transloco }}</label>
16533
- <SelectMenu
16534
- class="contents"
16535
- [options]="frequencies"
16536
- [formField]="alertForm.frequency"
16537
- [label]="frequencyLabel"
16538
- size="md">
16539
- <button selectTrigger type="button" id="alertFrequency" class="select-trigger">
16540
- <span class="select-trigger-label" [class.select-placeholder]="selectedFrequency === null">
16541
- @if (selectedFrequency === null) {
16542
- {{ 'alerts.selectFrequency' | transloco }}
16543
- } @else {
16544
- {{ 'alerts.frequency.' + frequency[selectedFrequency] | transloco }}
16545
- }
16546
- </span>
16547
- <chevron-down-icon class="select-chevron" />
16548
- </button>
16549
-
16550
- <ng-template #selectOption let-frequencyValue let-selected="selected">
16551
- <span class="select-option-body">
16552
- <span class="select-option-label">{{ 'alerts.frequency.' + frequency[frequencyValue] | transloco }}</span>
16553
- </span>
16554
- @if (selected) {
16555
- <check-icon class="select-option-check" />
16556
- }
16557
- </ng-template>
16558
- </SelectMenu>
16559
- </div>
16612
+ <!--
16613
+ [formRoot] poses novalidate, intercepts submit, calls preventDefault() and triggers submit()
16614
+ on the field tree: Enter submits natively, and the action only runs on a valid form.
16615
+ -->
16616
+ <form
16617
+ [formRoot]="alertForm"
16618
+ [id]="formId"
16619
+ class="flex flex-col gap-(--spacing-lg)"
16620
+ [attr.aria-label]="(alert() ? 'alerts.editAlert' : 'alerts.createAlert') | transloco">
16621
+ <div [class]="fieldClass">
16622
+ <label for="alertName" [class]="labelClass">{{ 'alerts.alertName' | transloco }}</label>
16623
+ <input-group [scheme]="showError(alertForm.name) ? 'error' : 'default'">
16624
+ <input
16625
+ input-control
16626
+ id="alertName"
16627
+ type="text"
16628
+ autocomplete="off"
16629
+ spellcheck="false"
16630
+ [formField]="alertForm.name"
16631
+ [attr.placeholder]="'alerts.placeholder' | transloco"
16632
+ [attr.aria-invalid]="showError(alertForm.name) ? 'true' : null"
16633
+ [attr.aria-describedby]="showError(alertForm.name) ? 'alertName-error' : null" />
16634
+ </input-group>
16635
+ @if (showError(alertForm.name)) {
16636
+ <p id="alertName-error" role="alert" [class]="errorClass">{{ errorKey(alertForm.name) | transloco }}</p>
16637
+ }
16638
+ </div>
16560
16639
 
16561
- <!-- A group of related checkboxes is a fieldset: its legend is what names the group for
16562
- assistive technologies. It used to be a <p>, which names nothing. -->
16563
- <fieldset
16564
- class="m-0 border-0 p-0"
16565
- [attr.aria-invalid]="showDaysError() ? 'true' : null"
16566
- [attr.aria-describedby]="showDaysError() ? 'alertDays-error' : null">
16567
- <legend [class]="labelClass + ' mb-(--spacing-2xs)'">{{ 'alerts.repeatOn' | transloco }}</legend>
16568
- <div class="weekdays-grid gap-1.5 px-2.5 py-0">
16569
- @for (day of weekdays; track day.key) {
16570
- <div>
16571
- <checkbox [formField]="alertForm.days[day.key]">
16572
- {{ 'alerts.weekdays.' + day.key | transloco }}
16573
- </checkbox>
16574
- </div>
16640
+ <div class="grid gap-(--spacing-md) @sm:grid-cols-2">
16641
+ @let selectedFrequency = alertForm.frequency().value();
16642
+ <div [class]="fieldClass">
16643
+ <label [class]="labelClass" for="alertFrequency">{{ 'alerts.alertFrequency' | transloco }}</label>
16644
+ <SelectMenu class="contents" [options]="frequencies" [formField]="alertForm.frequency" [label]="frequencyLabel" size="md">
16645
+ <button selectTrigger type="button" id="alertFrequency" class="select-trigger">
16646
+ <span class="select-trigger-label" [class.select-placeholder]="selectedFrequency === null">
16647
+ @if (selectedFrequency === null) {
16648
+ {{ 'alerts.selectFrequency' | transloco }}
16649
+ } @else {
16650
+ {{ 'alerts.frequency.' + frequency[selectedFrequency] | transloco }}
16575
16651
  }
16576
- </div>
16577
- @if (showDaysError()) {
16578
- <p id="alertDays-error" role="alert" [class]="errorClass + ' mt-(--spacing-2xs)'">
16579
- {{ errorKey(alertForm.days) | transloco }}
16580
- </p>
16581
- }
16582
- </fieldset>
16652
+ </span>
16653
+ <chevron-down-icon class="select-chevron" />
16654
+ </button>
16583
16655
 
16584
- <div [class]="fieldClass">
16585
- <label [class]="labelClass" for="alertTimes">{{ 'alerts.alertHours' | transloco }}</label>
16586
- <input-group [scheme]="showError(alertForm.time) ? 'error' : 'default'">
16587
- <input
16588
- input-control
16589
- type="time"
16590
- id="alertTimes"
16591
- autocomplete="off"
16592
- spellcheck="false"
16593
- [formField]="alertForm.time"
16594
- [attr.aria-invalid]="showError(alertForm.time) ? 'true' : null"
16595
- [attr.aria-describedby]="showError(alertForm.time) ? 'alertTimes-error' : null" />
16596
- </input-group>
16597
- @if (showError(alertForm.time)) {
16598
- <p id="alertTimes-error" role="alert" [class]="errorClass">{{ errorKey(alertForm.time) | transloco }}</p>
16656
+ <ng-template #selectOption let-frequencyValue let-selected="selected">
16657
+ <span class="select-option-body">
16658
+ <span class="select-option-label">{{ 'alerts.frequency.' + frequency[frequencyValue] | transloco }}</span>
16659
+ </span>
16660
+ @if (selected) {
16661
+ <check-icon class="select-option-check" />
16599
16662
  }
16600
- </div>
16663
+ </ng-template>
16664
+ </SelectMenu>
16665
+ </div>
16601
16666
 
16602
- <div>
16603
- <checkbox [formField]="alertForm.active">{{ 'alerts.alertActive' | transloco }}</checkbox>
16604
- </div>
16605
- </DialogBody>
16667
+ <div [class]="fieldClass">
16668
+ <label [class]="labelClass" for="alertTimes">{{ 'alerts.alertHours' | transloco }}</label>
16669
+ <input-group [scheme]="showError(alertForm.time) ? 'error' : 'default'">
16670
+ <input
16671
+ input-control
16672
+ type="time"
16673
+ id="alertTimes"
16674
+ autocomplete="off"
16675
+ spellcheck="false"
16676
+ [formField]="alertForm.time"
16677
+ [attr.aria-invalid]="showError(alertForm.time) ? 'true' : null"
16678
+ [attr.aria-describedby]="showError(alertForm.time) ? 'alertTimes-error' : null" />
16679
+ </input-group>
16680
+ @if (showError(alertForm.time)) {
16681
+ <p id="alertTimes-error" role="alert" [class]="errorClass">{{ errorKey(alertForm.time) | transloco }}</p>
16682
+ }
16683
+ </div>
16684
+ </div>
16606
16685
 
16607
- <DialogFooter class="flex-col">
16608
- <!-- Shown here and not as a toast: the dialog is modal, so it is in the top layer, above any toast. -->
16609
- @if (saveError(); as failure) {
16610
- <div class="save-error w-full">
16611
- <div class="min-h-0 overflow-hidden">
16612
- <div
16613
- id="alertSaveError"
16614
- role="alert"
16615
- class="flex w-full items-start gap-(--spacing-sm) rounded-(--radius-md) border border-(--stroke-error-base) bg-(--bg-error-lightest) p-(--spacing-md) text-(--font-error-base)">
16616
- <TriangleAlert class="mt-0.5 size-4 shrink-0" width="16" height="16" aria-hidden="true" />
16617
- <div class="flex min-w-0 flex-col gap-(--spacing-2xs)">
16618
- <p class="m-0 font-medium">{{ 'alerts.saveFailed' | transloco }}</p>
16619
- @if (failure.detail) {
16620
- <p class="m-0 text-(length:--size-sm) wrap-anywhere opacity-80">{{ failure.detail }}</p>
16621
- }
16622
- </div>
16623
- </div>
16624
- </div>
16625
- </div>
16686
+ <!--
16687
+ An immediate alert does not repeat: the weekdays carry no meaning for it, so they are not offered
16688
+ (the schema exempts it too).
16689
+ A group of related checkboxes is a fieldset: its legend is what names the group for assistive
16690
+ technologies. Each day is a native checkbox dressed as a toggle chip — the chip is its label, and
16691
+ the name read out is the full weekday, the short one being decoration.
16692
+ -->
16693
+ @if (selectedFrequency !== frequency.Immediate) {
16694
+ <fieldset
16695
+ class="m-0 flex flex-col gap-(--spacing-xs) border-0 p-0"
16696
+ [attr.aria-invalid]="showDaysError() ? 'true' : null"
16697
+ [attr.aria-describedby]="showDaysError() ? 'alertDays-error' : null">
16698
+ <div class="flex items-baseline justify-between gap-(--spacing-md)">
16699
+ <legend [class]="labelClass + ' p-0'">{{ 'alerts.repeatOn' | transloco }}</legend>
16700
+ <span class="flex gap-(--spacing-xs)">
16701
+ <button type="button" variant="tertiary" size="sm" (click)="setDays(Days.WeekDays)">{{ 'alerts.weekdaysPreset' | transloco }}</button>
16702
+ <button type="button" variant="tertiary" size="sm" (click)="setDays(Days.AllDays)">{{ 'alerts.everyDay' | transloco }}</button>
16703
+ </span>
16704
+ </div>
16705
+ <div class="grid grid-cols-7 gap-1.5">
16706
+ @for (day of weekdays; track day.key; let i = $index) {
16707
+ <label
16708
+ class="relative flex h-10 cursor-pointer items-center justify-center rounded-(--radius-md) border border-(--layer-page-edge) bg-(--bg-neutral-white) text-(length:--size-sm) font-medium text-(--font-neutral-muted) transition-colors select-none hover:bg-(--bg-primary-lighter) has-checked:border-(--stroke-primary-base) has-checked:bg-(--bg-primary-lighter) has-checked:text-(--font-primary-base) has-focus-visible:outline-2 has-focus-visible:outline-(--stroke-focus) motion-reduce:transition-none">
16709
+ <input type="checkbox" class="absolute inset-0 m-0 size-full cursor-pointer opacity-0" [formField]="alertForm.days[day.key]" />
16710
+ <span aria-hidden="true">{{ dayNames()[i] }}</span>
16711
+ <span class="sr-only">{{ 'alerts.weekdays.' + day.key | transloco }}</span>
16712
+ </label>
16626
16713
  }
16627
- @if (alert() || canUpdateQuery()) {
16628
- <div class="flex w-full flex-col gap-2">
16629
- @if (alert()) {
16630
- <button type="button" variant="secondary" (click)="execute()" [disabled]="!canUpdateQuery()">
16631
- {{ 'alerts.execute' | transloco }}
16632
- </button>
16633
- }
16634
- @if (canUpdateQuery()) {
16635
- <button type="button" [disabled]="updateStatus() !== 'idle'" variant="tertiary" (click)="updateQuery()">
16636
- @switch (updateStatus()) {
16637
- @case ('updating') {
16638
- <LoadingCircle class="size-4 animate-spin" width="16" height="16" />
16639
- {{ 'alerts.updateQuery' | transloco }}
16640
- }
16641
- @case ('updated') {
16642
- <CircleCheck width="16" height="16" />
16643
- {{ 'alerts.queryUpdated' | transloco }}
16644
- }
16645
- @default {
16646
- {{ 'alerts.updateQuery' | transloco }}
16647
- }
16648
- }
16649
- </button>
16714
+ </div>
16715
+ @if (showDaysError()) {
16716
+ <p id="alertDays-error" role="alert" [class]="errorClass">
16717
+ {{ errorKey(alertForm.days) | transloco }}
16718
+ </p>
16719
+ }
16720
+ </fieldset>
16721
+ }
16722
+
16723
+ <div class="flex items-center justify-between gap-(--spacing-md) rounded-(--radius-md) border border-(--layer-page-edge) px-(--spacing-md) py-(--spacing-sm)">
16724
+ <div id="alertActiveLabel" class="flex min-w-0 flex-col">
16725
+ <span class="text-(length:--size-base) font-medium">{{ 'alerts.alertActive' | transloco }}</span>
16726
+ <span class="text-(length:--size-sm) text-(--font-neutral-muted)">{{ 'alerts.activeHint' | transloco }}</span>
16727
+ </div>
16728
+ <Switch [formField]="alertForm.active" aria-labelledby="alertActiveLabel" />
16729
+ </div>
16730
+
16731
+ @if (alert(); as current) {
16732
+ <section aria-labelledby="alertQueryTitle" class="flex flex-col gap-(--spacing-sm) rounded-(--radius-md) bg-(--layer-page-on-layer) p-(--spacing-md)">
16733
+ <div class="flex min-w-0 flex-col gap-(--spacing-2xs)">
16734
+ <h3 id="alertQueryTitle" class="m-0 text-(length:--size-sm) font-semibold">{{ 'alerts.queryWatched' | transloco }}</h3>
16735
+ <p class="m-0 text-(length:--size-sm) wrap-anywhere text-(--font-neutral-muted)">
16736
+ @for (part of querySummary(current); track $index; let first = $first) {
16737
+ {{ first ? "" : " · " }}{{ part.key ? (part.key | transloco: part.params) : part.text }}
16738
+ }
16739
+ </p>
16740
+ </div>
16741
+ <div class="flex flex-wrap gap-(--spacing-xs)">
16742
+ <button type="button" variant="secondary" size="sm" (click)="execute()" [disabled]="!canUpdateQuery()">
16743
+ {{ 'alerts.execute' | transloco }}
16744
+ </button>
16745
+ @if (canUpdateQuery()) {
16746
+ <button type="button" variant="tertiary" size="sm" [disabled]="updateStatus() !== 'idle'" (click)="updateQuery()">
16747
+ @switch (updateStatus()) {
16748
+ @case ('updating') {
16749
+ <LoadingCircle class="size-4 animate-spin" width="16" height="16" />
16750
+ {{ 'alerts.updateQuery' | transloco }}
16751
+ }
16752
+ @case ('updated') {
16753
+ <CircleCheck width="16" height="16" />
16754
+ {{ 'alerts.queryUpdated' | transloco }}
16755
+ }
16756
+ @default {
16757
+ {{ 'alerts.updateQuery' | transloco }}
16758
+ }
16650
16759
  }
16651
- </div>
16652
- }
16653
- <div class="ml-auto flex justify-end gap-2">
16654
- <button type="button" variant="secondary" (click)="dialog.cancel()">
16655
- {{ 'cancel' | transloco }}
16656
16760
  </button>
16657
- <!--
16658
- Deliberately not disabled on an invalid form: a disabled submit button never fires
16659
- submit(), which is what marks every field as touched and reveals the messages. The
16660
- weekday group has no other way to become touched — the galactik Checkbox emits no
16661
- touch output — so the "pick a day" error would be unreachable.
16662
- -->
16663
- <button type="submit">
16664
- {{ 'confirm' | transloco }}
16665
- </button>
16666
- </div>
16667
- </DialogFooter>
16668
- </form>
16669
- </DialogContent>
16670
- </dialog>
16761
+ }
16762
+ </div>
16763
+ </section>
16764
+ }
16765
+
16766
+ @if (saveError(); as failure) {
16767
+ <alert-save-error [detail]="failure.detail" />
16768
+ }
16769
+ </form>
16671
16770
  `,
16672
16771
  isInline: true,
16673
- styles: [".weekdays-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(100px,1fr))}.save-error{display:grid;animation:save-error-in .22s ease-out}.save-error>div>div{animation:save-error-card-in .22s ease-out}@keyframes save-error-in{0%{grid-template-rows:0fr}to{grid-template-rows:1fr}}@keyframes save-error-card-in{0%{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:none}}@media(prefers-reduced-motion:reduce){.save-error,.save-error>div>div{animation:none}}\n"],
16674
16772
  dependencies: [
16675
16773
  {
16676
16774
  kind: "directive",
@@ -16697,62 +16795,6 @@ var AlertDialog = class AlertDialog {
16697
16795
  "size"
16698
16796
  ]
16699
16797
  },
16700
- {
16701
- kind: "component",
16702
- type: CheckboxComponent,
16703
- selector: "checkbox, Checkbox",
16704
- inputs: [
16705
- "checked",
16706
- "disabled",
16707
- "indeterminate",
16708
- "hint",
16709
- "class",
16710
- "aria-label"
16711
- ],
16712
- outputs: ["checkedChange"]
16713
- },
16714
- {
16715
- kind: "component",
16716
- type: DialogComponent,
16717
- selector: "dialog, [dialog]",
16718
- inputs: ["closeLabel"],
16719
- outputs: ["closed"],
16720
- exportAs: ["dialog"]
16721
- },
16722
- {
16723
- kind: "component",
16724
- type: DialogHeaderComponent,
16725
- selector: "DialogHeader",
16726
- inputs: ["closeLabel", "class"]
16727
- },
16728
- {
16729
- kind: "directive",
16730
- type: DialogTitleComponent,
16731
- selector: "DialogTitle",
16732
- inputs: ["id", "class"]
16733
- },
16734
- {
16735
- kind: "directive",
16736
- type: DialogContentComponent,
16737
- selector: "DialogContent",
16738
- inputs: [
16739
- "size",
16740
- "labelledby",
16741
- "class"
16742
- ]
16743
- },
16744
- {
16745
- kind: "directive",
16746
- type: DialogBodyComponent,
16747
- selector: "DialogBody",
16748
- inputs: ["class"]
16749
- },
16750
- {
16751
- kind: "directive",
16752
- type: DialogFooterComponent,
16753
- selector: "DialogFooter",
16754
- inputs: ["class"]
16755
- },
16756
16798
  {
16757
16799
  kind: "directive",
16758
16800
  type: InputComponent,
@@ -16808,33 +16850,387 @@ var AlertDialog = class AlertDialog {
16808
16850
  },
16809
16851
  {
16810
16852
  kind: "component",
16811
- type: ChevronDownIcon,
16812
- selector: "chevron-down, chevron-down-icon, ChevronDown, ChevronDownIcon",
16813
- inputs: ["class"]
16853
+ type: SwitchComponent,
16854
+ selector: "switch, Switch",
16855
+ inputs: [
16856
+ "checked",
16857
+ "disabled",
16858
+ "size",
16859
+ "class",
16860
+ "aria-label",
16861
+ "aria-labelledby",
16862
+ "presentational",
16863
+ "compact",
16864
+ "tabIndex"
16865
+ ],
16866
+ outputs: ["checkedChange"]
16867
+ },
16868
+ {
16869
+ kind: "component",
16870
+ type: ChevronDownIcon,
16871
+ selector: "chevron-down, chevron-down-icon, ChevronDown, ChevronDownIcon",
16872
+ inputs: ["class"]
16873
+ },
16874
+ {
16875
+ kind: "component",
16876
+ type: CheckIcon,
16877
+ selector: "check, check-icon, Check, CheckIcon",
16878
+ inputs: ["class"]
16879
+ },
16880
+ {
16881
+ kind: "component",
16882
+ type: LoadingCircleIcon,
16883
+ selector: "loading-circle, loading-circle-icon, LoadingCircle, LoadingCircleIcon",
16884
+ inputs: ["class"]
16885
+ },
16886
+ {
16887
+ kind: "component",
16888
+ type: CircleCheckIcon,
16889
+ selector: "circle-check, circle-check-icon, CircleCheck, CircleCheckIcon",
16890
+ inputs: ["class"]
16891
+ },
16892
+ {
16893
+ kind: "component",
16894
+ type: AlertSaveErrorComponent,
16895
+ selector: "alert-save-error",
16896
+ inputs: ["detail"]
16897
+ },
16898
+ {
16899
+ kind: "pipe",
16900
+ type: TranslocoPipe,
16901
+ name: "transloco"
16902
+ }
16903
+ ]
16904
+ });
16905
+ };
16906
+ i0.ɵɵngDeclareClassMetadata({
16907
+ minVersion: "12.0.0",
16908
+ version: "22.2.1",
16909
+ ngImport: i0,
16910
+ type: AlertFormComponent,
16911
+ decorators: [{
16912
+ type: Component,
16913
+ args: [{
16914
+ selector: "alert-form, alertform, AlertFormComponent",
16915
+ imports: [
16916
+ FormRoot,
16917
+ FormField,
16918
+ TranslocoPipe,
16919
+ ButtonComponent,
16920
+ InputComponent,
16921
+ InputControlDirective,
16922
+ SelectComponent,
16923
+ SelectTriggerDirective,
16924
+ SwitchComponent,
16925
+ ChevronDownIcon,
16926
+ CheckIcon,
16927
+ LoadingCircleIcon,
16928
+ CircleCheckIcon,
16929
+ AlertSaveErrorComponent
16930
+ ],
16931
+ providers: [provideTranslocoScope("alerts")],
16932
+ host: { class: "block @container" },
16933
+ template: `
16934
+ <!--
16935
+ [formRoot] poses novalidate, intercepts submit, calls preventDefault() and triggers submit()
16936
+ on the field tree: Enter submits natively, and the action only runs on a valid form.
16937
+ -->
16938
+ <form
16939
+ [formRoot]="alertForm"
16940
+ [id]="formId"
16941
+ class="flex flex-col gap-(--spacing-lg)"
16942
+ [attr.aria-label]="(alert() ? 'alerts.editAlert' : 'alerts.createAlert') | transloco">
16943
+ <div [class]="fieldClass">
16944
+ <label for="alertName" [class]="labelClass">{{ 'alerts.alertName' | transloco }}</label>
16945
+ <input-group [scheme]="showError(alertForm.name) ? 'error' : 'default'">
16946
+ <input
16947
+ input-control
16948
+ id="alertName"
16949
+ type="text"
16950
+ autocomplete="off"
16951
+ spellcheck="false"
16952
+ [formField]="alertForm.name"
16953
+ [attr.placeholder]="'alerts.placeholder' | transloco"
16954
+ [attr.aria-invalid]="showError(alertForm.name) ? 'true' : null"
16955
+ [attr.aria-describedby]="showError(alertForm.name) ? 'alertName-error' : null" />
16956
+ </input-group>
16957
+ @if (showError(alertForm.name)) {
16958
+ <p id="alertName-error" role="alert" [class]="errorClass">{{ errorKey(alertForm.name) | transloco }}</p>
16959
+ }
16960
+ </div>
16961
+
16962
+ <div class="grid gap-(--spacing-md) @sm:grid-cols-2">
16963
+ @let selectedFrequency = alertForm.frequency().value();
16964
+ <div [class]="fieldClass">
16965
+ <label [class]="labelClass" for="alertFrequency">{{ 'alerts.alertFrequency' | transloco }}</label>
16966
+ <SelectMenu class="contents" [options]="frequencies" [formField]="alertForm.frequency" [label]="frequencyLabel" size="md">
16967
+ <button selectTrigger type="button" id="alertFrequency" class="select-trigger">
16968
+ <span class="select-trigger-label" [class.select-placeholder]="selectedFrequency === null">
16969
+ @if (selectedFrequency === null) {
16970
+ {{ 'alerts.selectFrequency' | transloco }}
16971
+ } @else {
16972
+ {{ 'alerts.frequency.' + frequency[selectedFrequency] | transloco }}
16973
+ }
16974
+ </span>
16975
+ <chevron-down-icon class="select-chevron" />
16976
+ </button>
16977
+
16978
+ <ng-template #selectOption let-frequencyValue let-selected="selected">
16979
+ <span class="select-option-body">
16980
+ <span class="select-option-label">{{ 'alerts.frequency.' + frequency[frequencyValue] | transloco }}</span>
16981
+ </span>
16982
+ @if (selected) {
16983
+ <check-icon class="select-option-check" />
16984
+ }
16985
+ </ng-template>
16986
+ </SelectMenu>
16987
+ </div>
16988
+
16989
+ <div [class]="fieldClass">
16990
+ <label [class]="labelClass" for="alertTimes">{{ 'alerts.alertHours' | transloco }}</label>
16991
+ <input-group [scheme]="showError(alertForm.time) ? 'error' : 'default'">
16992
+ <input
16993
+ input-control
16994
+ type="time"
16995
+ id="alertTimes"
16996
+ autocomplete="off"
16997
+ spellcheck="false"
16998
+ [formField]="alertForm.time"
16999
+ [attr.aria-invalid]="showError(alertForm.time) ? 'true' : null"
17000
+ [attr.aria-describedby]="showError(alertForm.time) ? 'alertTimes-error' : null" />
17001
+ </input-group>
17002
+ @if (showError(alertForm.time)) {
17003
+ <p id="alertTimes-error" role="alert" [class]="errorClass">{{ errorKey(alertForm.time) | transloco }}</p>
17004
+ }
17005
+ </div>
17006
+ </div>
17007
+
17008
+ <!--
17009
+ An immediate alert does not repeat: the weekdays carry no meaning for it, so they are not offered
17010
+ (the schema exempts it too).
17011
+ A group of related checkboxes is a fieldset: its legend is what names the group for assistive
17012
+ technologies. Each day is a native checkbox dressed as a toggle chip — the chip is its label, and
17013
+ the name read out is the full weekday, the short one being decoration.
17014
+ -->
17015
+ @if (selectedFrequency !== frequency.Immediate) {
17016
+ <fieldset
17017
+ class="m-0 flex flex-col gap-(--spacing-xs) border-0 p-0"
17018
+ [attr.aria-invalid]="showDaysError() ? 'true' : null"
17019
+ [attr.aria-describedby]="showDaysError() ? 'alertDays-error' : null">
17020
+ <div class="flex items-baseline justify-between gap-(--spacing-md)">
17021
+ <legend [class]="labelClass + ' p-0'">{{ 'alerts.repeatOn' | transloco }}</legend>
17022
+ <span class="flex gap-(--spacing-xs)">
17023
+ <button type="button" variant="tertiary" size="sm" (click)="setDays(Days.WeekDays)">{{ 'alerts.weekdaysPreset' | transloco }}</button>
17024
+ <button type="button" variant="tertiary" size="sm" (click)="setDays(Days.AllDays)">{{ 'alerts.everyDay' | transloco }}</button>
17025
+ </span>
17026
+ </div>
17027
+ <div class="grid grid-cols-7 gap-1.5">
17028
+ @for (day of weekdays; track day.key; let i = $index) {
17029
+ <label
17030
+ class="relative flex h-10 cursor-pointer items-center justify-center rounded-(--radius-md) border border-(--layer-page-edge) bg-(--bg-neutral-white) text-(length:--size-sm) font-medium text-(--font-neutral-muted) transition-colors select-none hover:bg-(--bg-primary-lighter) has-checked:border-(--stroke-primary-base) has-checked:bg-(--bg-primary-lighter) has-checked:text-(--font-primary-base) has-focus-visible:outline-2 has-focus-visible:outline-(--stroke-focus) motion-reduce:transition-none">
17031
+ <input type="checkbox" class="absolute inset-0 m-0 size-full cursor-pointer opacity-0" [formField]="alertForm.days[day.key]" />
17032
+ <span aria-hidden="true">{{ dayNames()[i] }}</span>
17033
+ <span class="sr-only">{{ 'alerts.weekdays.' + day.key | transloco }}</span>
17034
+ </label>
17035
+ }
17036
+ </div>
17037
+ @if (showDaysError()) {
17038
+ <p id="alertDays-error" role="alert" [class]="errorClass">
17039
+ {{ errorKey(alertForm.days) | transloco }}
17040
+ </p>
17041
+ }
17042
+ </fieldset>
17043
+ }
17044
+
17045
+ <div class="flex items-center justify-between gap-(--spacing-md) rounded-(--radius-md) border border-(--layer-page-edge) px-(--spacing-md) py-(--spacing-sm)">
17046
+ <div id="alertActiveLabel" class="flex min-w-0 flex-col">
17047
+ <span class="text-(length:--size-base) font-medium">{{ 'alerts.alertActive' | transloco }}</span>
17048
+ <span class="text-(length:--size-sm) text-(--font-neutral-muted)">{{ 'alerts.activeHint' | transloco }}</span>
17049
+ </div>
17050
+ <Switch [formField]="alertForm.active" aria-labelledby="alertActiveLabel" />
17051
+ </div>
17052
+
17053
+ @if (alert(); as current) {
17054
+ <section aria-labelledby="alertQueryTitle" class="flex flex-col gap-(--spacing-sm) rounded-(--radius-md) bg-(--layer-page-on-layer) p-(--spacing-md)">
17055
+ <div class="flex min-w-0 flex-col gap-(--spacing-2xs)">
17056
+ <h3 id="alertQueryTitle" class="m-0 text-(length:--size-sm) font-semibold">{{ 'alerts.queryWatched' | transloco }}</h3>
17057
+ <p class="m-0 text-(length:--size-sm) wrap-anywhere text-(--font-neutral-muted)">
17058
+ @for (part of querySummary(current); track $index; let first = $first) {
17059
+ {{ first ? "" : " · " }}{{ part.key ? (part.key | transloco: part.params) : part.text }}
17060
+ }
17061
+ </p>
17062
+ </div>
17063
+ <div class="flex flex-wrap gap-(--spacing-xs)">
17064
+ <button type="button" variant="secondary" size="sm" (click)="execute()" [disabled]="!canUpdateQuery()">
17065
+ {{ 'alerts.execute' | transloco }}
17066
+ </button>
17067
+ @if (canUpdateQuery()) {
17068
+ <button type="button" variant="tertiary" size="sm" [disabled]="updateStatus() !== 'idle'" (click)="updateQuery()">
17069
+ @switch (updateStatus()) {
17070
+ @case ('updating') {
17071
+ <LoadingCircle class="size-4 animate-spin" width="16" height="16" />
17072
+ {{ 'alerts.updateQuery' | transloco }}
17073
+ }
17074
+ @case ('updated') {
17075
+ <CircleCheck width="16" height="16" />
17076
+ {{ 'alerts.queryUpdated' | transloco }}
17077
+ }
17078
+ @default {
17079
+ {{ 'alerts.updateQuery' | transloco }}
17080
+ }
17081
+ }
17082
+ </button>
17083
+ }
17084
+ </div>
17085
+ </section>
17086
+ }
17087
+
17088
+ @if (saveError(); as failure) {
17089
+ <alert-save-error [detail]="failure.detail" />
17090
+ }
17091
+ </form>
17092
+ `
17093
+ }]
17094
+ }],
17095
+ propDecorators: {
17096
+ index: [{
17097
+ type: i0.Input,
17098
+ args: [{
17099
+ isSignal: true,
17100
+ alias: "index",
17101
+ required: false
17102
+ }]
17103
+ }],
17104
+ saved: [{
17105
+ type: i0.Output,
17106
+ args: ["saved"]
17107
+ }],
17108
+ executed: [{
17109
+ type: i0.Output,
17110
+ args: ["executed"]
17111
+ }]
17112
+ }
17113
+ });
17114
+ var AlertDialog = class AlertDialog {
17115
+ call = injectCallRef();
17116
+ dialog = viewChild(DialogComponent, ...ngDevMode ? [{ debugName: "dialog" }] : /* istanbul ignore next */ []);
17117
+ index = this.call.props;
17118
+ constructor() {
17119
+ afterNextRender(() => this.dialog()?.open());
17120
+ }
17121
+ static ɵfac = i0.ɵɵngDeclareFactory({
17122
+ minVersion: "12.0.0",
17123
+ version: "22.2.1",
17124
+ ngImport: i0,
17125
+ type: AlertDialog,
17126
+ deps: [],
17127
+ target: i0.ɵɵFactoryTarget.Component
17128
+ });
17129
+ static ɵcmp = i0.ɵɵngDeclareComponent({
17130
+ minVersion: "17.2.0",
17131
+ version: "22.2.1",
17132
+ type: AlertDialog,
17133
+ isStandalone: true,
17134
+ selector: "alert-dialog, alertdialog, AlertDialog",
17135
+ providers: [provideTranslocoScope("alerts")],
17136
+ viewQueries: [{
17137
+ propertyName: "dialog",
17138
+ first: true,
17139
+ predicate: DialogComponent,
17140
+ descendants: true,
17141
+ isSignal: true
17142
+ }],
17143
+ ngImport: i0,
17144
+ template: `
17145
+ <dialog #dialog (closed)="call.end($event)">
17146
+ <DialogContent size="md">
17147
+ <DialogHeader [closeLabel]="'alerts.close' | transloco">
17148
+ <DialogTitle>{{ (index() === undefined ? 'alerts.createAlert' : 'alerts.editAlert') | transloco }}</DialogTitle>
17149
+ </DialogHeader>
17150
+
17151
+ <DialogBody>
17152
+ <alert-form #form [index]="index()" (saved)="dialog.close('dialog-confirm')" (executed)="dialog.close()" />
17153
+ </DialogBody>
17154
+
17155
+ <DialogFooter>
17156
+ <button type="button" variant="secondary" (click)="dialog.cancel()">
17157
+ {{ 'cancel' | transloco }}
17158
+ </button>
17159
+ <!--
17160
+ Deliberately not disabled on an invalid form: a disabled submit button never fires
17161
+ submit(), which is what marks every field as touched and reveals the messages. The
17162
+ weekday group has no other way to become touched — it emits no touch event — so the
17163
+ "pick a day" error would be unreachable. It does wait for a save in progress.
17164
+ -->
17165
+ <button type="submit" [attr.form]="form.formId" [disabled]="form.saving()">
17166
+ {{ 'confirm' | transloco }}
17167
+ </button>
17168
+ </DialogFooter>
17169
+ </DialogContent>
17170
+ </dialog>
17171
+ `,
17172
+ isInline: true,
17173
+ dependencies: [
17174
+ {
17175
+ kind: "directive",
17176
+ type: ButtonComponent,
17177
+ selector: "button",
17178
+ inputs: [
17179
+ "class",
17180
+ "variant",
17181
+ "scheme",
17182
+ "iconOnly",
17183
+ "size"
17184
+ ]
17185
+ },
17186
+ {
17187
+ kind: "component",
17188
+ type: DialogComponent,
17189
+ selector: "dialog, [dialog]",
17190
+ inputs: ["closeLabel"],
17191
+ outputs: ["closed"],
17192
+ exportAs: ["dialog"]
17193
+ },
17194
+ {
17195
+ kind: "component",
17196
+ type: DialogHeaderComponent,
17197
+ selector: "DialogHeader",
17198
+ inputs: ["closeLabel", "class"]
16814
17199
  },
16815
17200
  {
16816
- kind: "component",
16817
- type: CheckIcon,
16818
- selector: "check, check-icon, Check, CheckIcon",
16819
- inputs: ["class"]
17201
+ kind: "directive",
17202
+ type: DialogTitleComponent,
17203
+ selector: "DialogTitle",
17204
+ inputs: ["id", "class"]
16820
17205
  },
16821
17206
  {
16822
- kind: "component",
16823
- type: LoadingCircleIcon,
16824
- selector: "loading-circle, loading-circle-icon, LoadingCircle, LoadingCircleIcon",
17207
+ kind: "directive",
17208
+ type: DialogContentComponent,
17209
+ selector: "DialogContent",
17210
+ inputs: [
17211
+ "size",
17212
+ "labelledby",
17213
+ "class"
17214
+ ]
17215
+ },
17216
+ {
17217
+ kind: "directive",
17218
+ type: DialogBodyComponent,
17219
+ selector: "DialogBody",
16825
17220
  inputs: ["class"]
16826
17221
  },
16827
17222
  {
16828
- kind: "component",
16829
- type: CircleCheckIcon,
16830
- selector: "circle-check, circle-check-icon, CircleCheck, CircleCheckIcon",
17223
+ kind: "directive",
17224
+ type: DialogFooterComponent,
17225
+ selector: "DialogFooter",
16831
17226
  inputs: ["class"]
16832
17227
  },
16833
17228
  {
16834
17229
  kind: "component",
16835
- type: TriangleAlertIcon,
16836
- selector: "triangle-alert, triangle-alert-icon, TriangleAlert, TriangleAlertIcon",
16837
- inputs: ["class"]
17230
+ type: AlertFormComponent,
17231
+ selector: "alert-form, alertform, AlertFormComponent",
17232
+ inputs: ["index"],
17233
+ outputs: ["saved", "executed"]
16838
17234
  },
16839
17235
  {
16840
17236
  kind: "pipe",
@@ -16854,205 +17250,45 @@ i0.ɵɵngDeclareClassMetadata({
16854
17250
  args: [{
16855
17251
  selector: "alert-dialog, alertdialog, AlertDialog",
16856
17252
  imports: [
16857
- FormRoot,
16858
- FormField,
16859
17253
  TranslocoPipe,
16860
17254
  ButtonComponent,
16861
- CheckboxComponent,
16862
17255
  DialogComponent,
16863
17256
  DialogHeaderComponent,
16864
17257
  DialogTitleComponent,
16865
17258
  DialogContentComponent,
16866
17259
  DialogBodyComponent,
16867
17260
  DialogFooterComponent,
16868
- InputComponent,
16869
- InputControlDirective,
16870
- SelectComponent,
16871
- SelectTriggerDirective,
16872
- ChevronDownIcon,
16873
- CheckIcon,
16874
- LoadingCircleIcon,
16875
- CircleCheckIcon,
16876
- TriangleAlertIcon
17261
+ AlertFormComponent
16877
17262
  ],
16878
17263
  providers: [provideTranslocoScope("alerts")],
16879
17264
  template: `
16880
17265
  <dialog #dialog (closed)="call.end($event)">
16881
17266
  <DialogContent size="md">
16882
17267
  <DialogHeader [closeLabel]="'alerts.close' | transloco">
16883
- <DialogTitle>{{ 'alerts.createAlert' | transloco }}</DialogTitle>
17268
+ <DialogTitle>{{ (index() === undefined ? 'alerts.createAlert' : 'alerts.editAlert') | transloco }}</DialogTitle>
16884
17269
  </DialogHeader>
16885
17270
 
16886
- <!--
16887
- [formRoot] poses novalidate, intercepts submit, calls preventDefault() and triggers submit()
16888
- on the field tree: Enter submits natively, and the action only runs on a valid form.
16889
- class="contents" keeps the DialogContent flex column intact — the body/footer slots are
16890
- styled by descendant selectors, which a display: contents wrapper does not break.
16891
- -->
16892
- <form [formRoot]="alertForm" class="contents" [attr.aria-label]="'alerts.createAlert' | transloco">
16893
- <DialogBody class="gap-(--spacing-md)">
16894
- <div [class]="fieldClass">
16895
- <label for="alertName" [class]="labelClass">{{ 'alerts.alertName' | transloco }}</label>
16896
- <input-group [scheme]="showError(alertForm.name) ? 'error' : 'default'">
16897
- <input
16898
- input-control
16899
- id="alertName"
16900
- type="text"
16901
- autocomplete="off"
16902
- spellcheck="false"
16903
- [formField]="alertForm.name"
16904
- [attr.placeholder]="'alerts.placeholder' | transloco"
16905
- [attr.aria-invalid]="showError(alertForm.name) ? 'true' : null"
16906
- [attr.aria-describedby]="showError(alertForm.name) ? 'alertName-error' : null" />
16907
- </input-group>
16908
- @if (showError(alertForm.name)) {
16909
- <p id="alertName-error" role="alert" [class]="errorClass">{{ errorKey(alertForm.name) | transloco }}</p>
16910
- }
16911
- </div>
16912
-
16913
- @let selectedFrequency = alertForm.frequency().value();
16914
- <div [class]="fieldClass">
16915
- <label [class]="labelClass" for="alertFrequency">{{ 'alerts.alertFrequency' | transloco }}</label>
16916
- <SelectMenu
16917
- class="contents"
16918
- [options]="frequencies"
16919
- [formField]="alertForm.frequency"
16920
- [label]="frequencyLabel"
16921
- size="md">
16922
- <button selectTrigger type="button" id="alertFrequency" class="select-trigger">
16923
- <span class="select-trigger-label" [class.select-placeholder]="selectedFrequency === null">
16924
- @if (selectedFrequency === null) {
16925
- {{ 'alerts.selectFrequency' | transloco }}
16926
- } @else {
16927
- {{ 'alerts.frequency.' + frequency[selectedFrequency] | transloco }}
16928
- }
16929
- </span>
16930
- <chevron-down-icon class="select-chevron" />
16931
- </button>
16932
-
16933
- <ng-template #selectOption let-frequencyValue let-selected="selected">
16934
- <span class="select-option-body">
16935
- <span class="select-option-label">{{ 'alerts.frequency.' + frequency[frequencyValue] | transloco }}</span>
16936
- </span>
16937
- @if (selected) {
16938
- <check-icon class="select-option-check" />
16939
- }
16940
- </ng-template>
16941
- </SelectMenu>
16942
- </div>
16943
-
16944
- <!-- A group of related checkboxes is a fieldset: its legend is what names the group for
16945
- assistive technologies. It used to be a <p>, which names nothing. -->
16946
- <fieldset
16947
- class="m-0 border-0 p-0"
16948
- [attr.aria-invalid]="showDaysError() ? 'true' : null"
16949
- [attr.aria-describedby]="showDaysError() ? 'alertDays-error' : null">
16950
- <legend [class]="labelClass + ' mb-(--spacing-2xs)'">{{ 'alerts.repeatOn' | transloco }}</legend>
16951
- <div class="weekdays-grid gap-1.5 px-2.5 py-0">
16952
- @for (day of weekdays; track day.key) {
16953
- <div>
16954
- <checkbox [formField]="alertForm.days[day.key]">
16955
- {{ 'alerts.weekdays.' + day.key | transloco }}
16956
- </checkbox>
16957
- </div>
16958
- }
16959
- </div>
16960
- @if (showDaysError()) {
16961
- <p id="alertDays-error" role="alert" [class]="errorClass + ' mt-(--spacing-2xs)'">
16962
- {{ errorKey(alertForm.days) | transloco }}
16963
- </p>
16964
- }
16965
- </fieldset>
16966
-
16967
- <div [class]="fieldClass">
16968
- <label [class]="labelClass" for="alertTimes">{{ 'alerts.alertHours' | transloco }}</label>
16969
- <input-group [scheme]="showError(alertForm.time) ? 'error' : 'default'">
16970
- <input
16971
- input-control
16972
- type="time"
16973
- id="alertTimes"
16974
- autocomplete="off"
16975
- spellcheck="false"
16976
- [formField]="alertForm.time"
16977
- [attr.aria-invalid]="showError(alertForm.time) ? 'true' : null"
16978
- [attr.aria-describedby]="showError(alertForm.time) ? 'alertTimes-error' : null" />
16979
- </input-group>
16980
- @if (showError(alertForm.time)) {
16981
- <p id="alertTimes-error" role="alert" [class]="errorClass">{{ errorKey(alertForm.time) | transloco }}</p>
16982
- }
16983
- </div>
16984
-
16985
- <div>
16986
- <checkbox [formField]="alertForm.active">{{ 'alerts.alertActive' | transloco }}</checkbox>
16987
- </div>
16988
- </DialogBody>
17271
+ <DialogBody>
17272
+ <alert-form #form [index]="index()" (saved)="dialog.close('dialog-confirm')" (executed)="dialog.close()" />
17273
+ </DialogBody>
16989
17274
 
16990
- <DialogFooter class="flex-col">
16991
- <!-- Shown here and not as a toast: the dialog is modal, so it is in the top layer, above any toast. -->
16992
- @if (saveError(); as failure) {
16993
- <div class="save-error w-full">
16994
- <div class="min-h-0 overflow-hidden">
16995
- <div
16996
- id="alertSaveError"
16997
- role="alert"
16998
- class="flex w-full items-start gap-(--spacing-sm) rounded-(--radius-md) border border-(--stroke-error-base) bg-(--bg-error-lightest) p-(--spacing-md) text-(--font-error-base)">
16999
- <TriangleAlert class="mt-0.5 size-4 shrink-0" width="16" height="16" aria-hidden="true" />
17000
- <div class="flex min-w-0 flex-col gap-(--spacing-2xs)">
17001
- <p class="m-0 font-medium">{{ 'alerts.saveFailed' | transloco }}</p>
17002
- @if (failure.detail) {
17003
- <p class="m-0 text-(length:--size-sm) wrap-anywhere opacity-80">{{ failure.detail }}</p>
17004
- }
17005
- </div>
17006
- </div>
17007
- </div>
17008
- </div>
17009
- }
17010
- @if (alert() || canUpdateQuery()) {
17011
- <div class="flex w-full flex-col gap-2">
17012
- @if (alert()) {
17013
- <button type="button" variant="secondary" (click)="execute()" [disabled]="!canUpdateQuery()">
17014
- {{ 'alerts.execute' | transloco }}
17015
- </button>
17016
- }
17017
- @if (canUpdateQuery()) {
17018
- <button type="button" [disabled]="updateStatus() !== 'idle'" variant="tertiary" (click)="updateQuery()">
17019
- @switch (updateStatus()) {
17020
- @case ('updating') {
17021
- <LoadingCircle class="size-4 animate-spin" width="16" height="16" />
17022
- {{ 'alerts.updateQuery' | transloco }}
17023
- }
17024
- @case ('updated') {
17025
- <CircleCheck width="16" height="16" />
17026
- {{ 'alerts.queryUpdated' | transloco }}
17027
- }
17028
- @default {
17029
- {{ 'alerts.updateQuery' | transloco }}
17030
- }
17031
- }
17032
- </button>
17033
- }
17034
- </div>
17035
- }
17036
- <div class="ml-auto flex justify-end gap-2">
17037
- <button type="button" variant="secondary" (click)="dialog.cancel()">
17038
- {{ 'cancel' | transloco }}
17039
- </button>
17040
- <!--
17041
- Deliberately not disabled on an invalid form: a disabled submit button never fires
17042
- submit(), which is what marks every field as touched and reveals the messages. The
17043
- weekday group has no other way to become touched — the galactik Checkbox emits no
17044
- touch output — so the "pick a day" error would be unreachable.
17045
- -->
17046
- <button type="submit">
17047
- {{ 'confirm' | transloco }}
17048
- </button>
17049
- </div>
17050
- </DialogFooter>
17051
- </form>
17275
+ <DialogFooter>
17276
+ <button type="button" variant="secondary" (click)="dialog.cancel()">
17277
+ {{ 'cancel' | transloco }}
17278
+ </button>
17279
+ <!--
17280
+ Deliberately not disabled on an invalid form: a disabled submit button never fires
17281
+ submit(), which is what marks every field as touched and reveals the messages. The
17282
+ weekday group has no other way to become touched — it emits no touch event — so the
17283
+ "pick a day" error would be unreachable. It does wait for a save in progress.
17284
+ -->
17285
+ <button type="submit" [attr.form]="form.formId" [disabled]="form.saving()">
17286
+ {{ 'confirm' | transloco }}
17287
+ </button>
17288
+ </DialogFooter>
17052
17289
  </DialogContent>
17053
17290
  </dialog>
17054
- `,
17055
- styles: [".weekdays-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(100px,1fr))}.save-error{display:grid;animation:save-error-in .22s ease-out}.save-error>div>div{animation:save-error-card-in .22s ease-out}@keyframes save-error-in{0%{grid-template-rows:0fr}to{grid-template-rows:1fr}}@keyframes save-error-card-in{0%{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:none}}@media(prefers-reduced-motion:reduce){.save-error,.save-error>div>div{animation:none}}\n"]
17291
+ `
17056
17292
  }]
17057
17293
  }],
17058
17294
  ctorParameters: () => [],
@@ -17073,6 +17309,13 @@ var AlertsComponent = class AlertsComponent {
17073
17309
  transloco = inject(TranslocoService);
17074
17310
  lang = toSignal(this.transloco.langChanges$);
17075
17311
  query;
17312
+ dialog = input(true, {
17313
+ ...ngDevMode ? { debugName: "dialog" } : /* istanbul ignore next */ {},
17314
+ transform: booleanAttribute
17315
+ });
17316
+ editRequested = output();
17317
+ createRequested = output();
17318
+ saveError = signal(void 0, ...ngDevMode ? [{ debugName: "saveError" }] : /* istanbul ignore next */ []);
17076
17319
  alerts = computed(() => this.userSettingsStore.alerts() ?? [], ...ngDevMode ? [{ debugName: "alerts" }] : /* istanbul ignore next */ []);
17077
17320
  order = linkedSignal(() => [...this.alerts()], ...ngDevMode ? [{ debugName: "order" }] : /* istanbul ignore next */ []);
17078
17321
  hidden = signal(/* @__PURE__ */ new Set(), ...ngDevMode ? [{ debugName: "hidden" }] : /* istanbul ignore next */ []);
@@ -17107,10 +17350,18 @@ var AlertsComponent = class AlertsComponent {
17107
17350
  const index = this.alerts().indexOf(alert);
17108
17351
  if (index < 0) return;
17109
17352
  this.floating?.close();
17353
+ if (!this.dialog()) {
17354
+ this.editRequested.emit(index);
17355
+ return;
17356
+ }
17110
17357
  AlertForm.call(index, { injector: this.injector });
17111
17358
  }
17112
17359
  createAlert() {
17113
17360
  this.floating?.close();
17361
+ if (!this.dialog()) {
17362
+ this.createRequested.emit();
17363
+ return;
17364
+ }
17114
17365
  AlertForm.call(void 0, { injector: this.injector });
17115
17366
  }
17116
17367
  pendingActive = signal(/* @__PURE__ */ new Map(), ...ngDevMode ? [{ debugName: "pendingActive" }] : /* istanbul ignore next */ []);
@@ -17120,6 +17371,7 @@ var AlertsComponent = class AlertsComponent {
17120
17371
  async toggle(alert, active) {
17121
17372
  const index = this.alerts().indexOf(alert);
17122
17373
  if (index < 0) return;
17374
+ this.saveError.set(void 0);
17123
17375
  this.pendingActive.update((pending) => new Map(pending).set(alert, active));
17124
17376
  try {
17125
17377
  await this.userSettingsStore.updateAlert({
@@ -17127,7 +17379,7 @@ var AlertsComponent = class AlertsComponent {
17127
17379
  active
17128
17380
  }, index);
17129
17381
  } catch (error) {
17130
- notifySaveFailed(this.transloco, error);
17382
+ this.saveError.set({ detail: saveErrorDetail(error) });
17131
17383
  } finally {
17132
17384
  this.pendingActive.update((pending) => {
17133
17385
  const next = new Map(pending);
@@ -17159,7 +17411,7 @@ var AlertsComponent = class AlertsComponent {
17159
17411
  this.unhide(alert);
17160
17412
  return;
17161
17413
  }
17162
- this.userSettingsStore.deleteAlert(index).catch((error) => notifySaveFailed(this.transloco, error)).finally(() => this.unhide(alert));
17414
+ this.userSettingsStore.deleteAlert(index).catch((error) => this.saveError.set({ detail: saveErrorDetail(error) })).finally(() => this.unhide(alert));
17163
17415
  }
17164
17416
  unhide(alert) {
17165
17417
  this.hidden.update((hidden) => {
@@ -17173,12 +17425,13 @@ var AlertsComponent = class AlertsComponent {
17173
17425
  const previous = this.order();
17174
17426
  const next = [...shown, ...previous.filter((alert) => hidden.has(alert))];
17175
17427
  if (next.every((alert, index) => alert === previous[index])) return;
17428
+ this.saveError.set(void 0);
17176
17429
  this.order.set(next);
17177
17430
  try {
17178
17431
  await this.userSettingsStore.updateAlerts(next);
17179
17432
  } catch (error) {
17180
17433
  this.order.set(previous);
17181
- notifySaveFailed(this.transloco, error);
17434
+ this.saveError.set({ detail: saveErrorDetail(error) });
17182
17435
  }
17183
17436
  }
17184
17437
  static ɵfac = i0.ɵɵngDeclareFactory({
@@ -17195,9 +17448,20 @@ var AlertsComponent = class AlertsComponent {
17195
17448
  type: AlertsComponent,
17196
17449
  isStandalone: true,
17197
17450
  selector: "Alerts",
17451
+ inputs: { dialog: {
17452
+ classPropertyName: "dialog",
17453
+ publicName: "dialog",
17454
+ isSignal: true,
17455
+ isRequired: false,
17456
+ transformFunction: null
17457
+ } },
17458
+ outputs: {
17459
+ editRequested: "editRequested",
17460
+ createRequested: "createRequested"
17461
+ },
17198
17462
  providers: [provideTranslocoScope("alerts")],
17199
17463
  ngImport: i0,
17200
- template: "<div class=\"flex min-w-80 flex-col gap-(--spacing-3xs) p-2\">\n <div class=\"flex items-center gap-(--spacing-2xs) ps-(--spacing-2xs)\">\n @if (floating) {\n <!-- Not an <h2>: a page's prose styles put a rule and margins on every h2, even inside a popover. -->\n <span\n role=\"heading\"\n aria-level=\"2\"\n class=\"flex-1 text-xl leading-(--line-height-4) font-bold\">\n {{ \"alerts.label\" | transloco }}\n @if (visible().length) {\n <span\n class=\"text-(length:--size-sm) font-normal text-(--font-neutral-muted)\"\n >· {{ visible().length }}</span\n >\n }\n </span>\n } @else {\n <span class=\"flex-1\"></span>\n }\n <!-- With no alert, \"Create\" is the call to action of the empty state, below. -->\n @if (visible().length) {\n <button size=\"md\" (click)=\"createAlert()\">\n <plus-icon />\n {{ \"alerts.create\" | transloco }}\n </button>\n }\n </div>\n\n <ul\n role=\"list\"\n class=\"m-0 flex list-none flex-col p-0\"\n [attr.aria-label]=\"'alerts.label' | transloco\"\n [sortable]=\"visible()\"\n #sortable=\"sortable\"\n [sortableAnnounce]=\"announceMove\"\n (reorder)=\"saveOrder($event)\">\n @for (alert of sortable.items(); track alert.name) {\n @let name = alert.name;\n <li\n [sortableItem]=\"alert\"\n class=\"group relative flex min-h-12 w-full cursor-pointer items-center gap-(--spacing-3xs) rounded-(--radius-sm) py-(--spacing-3xs) ps-(--spacing-3xs) pe-(--spacing-2xs) text-(--font-neutral-base) select-none hover:bg-(--bg-primary-lighter) focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-(--stroke-focus) active:bg-(--bg-primary-light) data-dragging:bg-(--bg-primary-lighter) data-dragging:outline-2 data-dragging:-outline-offset-2 data-dragging:outline-(--stroke-primary-base) data-dragging:outline-dashed data-dragging:*:invisible\"\n tabindex=\"0\"\n [attr.data-inactive]=\"alert.active ? null : ''\"\n (click)=\"onRowClick($event, alert)\"\n (keydown.enter)=\"onRowEnter($event, alert)\">\n <!-- Handle: shown when the row is hovered or focused, only if there is something to reorder. -->\n <grip-vertical-icon\n class=\"shrink-0 cursor-grab text-(--font-neutral-muted) opacity-0 group-focus-within:opacity-100 group-hover:opacity-100 pointer-coarse:hidden\"\n [class.invisible]=\"visible().length < 2\" />\n @if (alert.active) {\n <bell-icon class=\"shrink-0\" />\n } @else {\n <bell-slash-icon class=\"shrink-0 text-(--font-neutral-muted)\" />\n }\n <span class=\"flex min-w-0 flex-1 flex-col\">\n <span\n class=\"line-clamp-1 text-(length:--size-base) group-data-inactive:text-(--font-neutral-muted)\"\n >{{ name }}</span\n >\n @if (summaryOf(alert).length) {\n <span\n class=\"line-clamp-1 text-(length:--size-sm) text-(--font-neutral-muted)\">\n @for (\n part of summaryOf(alert);\n track $index;\n let first = $first\n ) {\n {{ first ? \"\" : \" · \"\n }}{{ part.key ? (part.key | transloco) : part.text }}\n }\n </span>\n }\n </span>\n <span data-row-action class=\"flex shrink-0 items-center\">\n <Switch\n [checked]=\"isActive(alert)\"\n [aria-label]=\"'alerts.alertActiveFor' | transloco: { name }\"\n (checkedChange)=\"toggle(alert, $event)\" />\n </span>\n <span data-row-action class=\"flex shrink-0 items-center\">\n <button\n variant=\"tertiary\"\n size=\"sm\"\n [iconOnly]=\"true\"\n class=\"text-(--font-error-muted) opacity-0 group-focus-within:opacity-100 group-hover:opacity-100 hover:bg-(--bg-error-lightest) hover:text-(--font-error-base) focus-visible:opacity-100 pointer-coarse:opacity-100\"\n [attr.title]=\"'alerts.deleteAlertNamed' | transloco: { name }\"\n [attr.aria-label]=\"'alerts.deleteAlertNamed' | transloco: { name }\"\n (click)=\"deleteAlert(alert)\">\n <TrashIcon />\n </button>\n </span>\n </li>\n } @empty {\n <li class=\"list-none\">\n <ListPanelEmpty color=\"orange\">\n <bell-icon listPanelEmptyIcon />\n {{ \"alerts.noAlerts\" | transloco }}\n <p listPanelEmptyHint>{{ \"alerts.noAlertsHint\" | transloco }}</p>\n <button listPanelEmptyAction (click)=\"createAlert()\">\n <plus-icon />\n {{ \"alerts.createAlert\" | transloco }}\n </button>\n </ListPanelEmpty>\n </li>\n }\n </ul>\n\n @if (visible().length > 1) {\n <p\n class=\"m-0 ps-(--spacing-2xs) pt-(--spacing-3xs) text-(length:--size-sm) text-(--font-neutral-muted)\">\n {{ \"alerts.reorderHint\" | transloco }}\n </p>\n }\n\n <!-- The regions exist before their text does, which is what makes a screen reader announce the text. -->\n <div role=\"status\">\n @if (pendingDeletion(); as pending) {\n <div\n class=\"mt-(--spacing-2xs) flex items-center gap-(--spacing-2xs) rounded-(--radius-sm) bg-(--bg-neutral-base) py-(--spacing-3xs) ps-(--spacing-xs) pe-(--spacing-3xs) text-(length:--size-sm) text-(--font-neutral-base)\">\n <span class=\"min-w-0 flex-1\">{{\n \"alerts.deleted\" | transloco: { name: pending.name }\n }}</span>\n <button variant=\"tertiary\" size=\"sm\" (click)=\"undoDeletion()\">\n {{ \"alerts.undo\" | transloco }}\n </button>\n </div>\n }\n </div>\n</div>\n",
17464
+ template: "<div class=\"flex min-w-80 flex-col gap-(--spacing-3xs) p-2\">\n <div class=\"flex items-center gap-(--spacing-2xs) ps-(--spacing-2xs)\">\n @if (floating) {\n <!-- Not an <h2>: a page's prose styles put a rule and margins on every h2, even inside a popover. -->\n <span\n role=\"heading\"\n aria-level=\"2\"\n class=\"flex-1 text-xl leading-(--line-height-4) font-bold\">\n {{ \"alerts.label\" | transloco }}\n @if (visible().length) {\n <span\n class=\"text-(length:--size-sm) font-normal text-(--font-neutral-muted)\"\n >· {{ visible().length }}</span\n >\n }\n </span>\n } @else {\n <span class=\"flex-1\"></span>\n }\n <!-- With no alert, \"Create\" is the call to action of the empty state, below. -->\n @if (visible().length) {\n <button size=\"md\" (click)=\"createAlert()\">\n <plus-icon />\n {{ \"alerts.create\" | transloco }}\n </button>\n }\n </div>\n\n @if (saveError(); as failure) {\n <alert-save-error class=\"mb-(--spacing-2xs)\" [detail]=\"failure.detail\" />\n }\n\n <ul\n role=\"list\"\n class=\"m-0 flex list-none flex-col p-0\"\n [attr.aria-label]=\"'alerts.label' | transloco\"\n [sortable]=\"visible()\"\n #sortable=\"sortable\"\n [sortableAnnounce]=\"announceMove\"\n (reorder)=\"saveOrder($event)\">\n @for (alert of sortable.items(); track alert.name) {\n @let name = alert.name;\n <li\n [sortableItem]=\"alert\"\n class=\"group relative flex min-h-12 w-full cursor-pointer items-center gap-(--spacing-3xs) rounded-(--radius-sm) py-(--spacing-3xs) ps-(--spacing-3xs) pe-(--spacing-2xs) text-(--font-neutral-base) select-none hover:bg-(--bg-primary-lighter) focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-(--stroke-focus) active:bg-(--bg-primary-light) data-dragging:bg-(--bg-primary-lighter) data-dragging:outline-2 data-dragging:-outline-offset-2 data-dragging:outline-(--stroke-primary-base) data-dragging:outline-dashed data-dragging:*:invisible\"\n tabindex=\"0\"\n [attr.data-inactive]=\"alert.active ? null : ''\"\n (click)=\"onRowClick($event, alert)\"\n (keydown.enter)=\"onRowEnter($event, alert)\">\n <!-- Handle: shown when the row is hovered or focused, only if there is something to reorder. -->\n <grip-vertical-icon\n class=\"shrink-0 cursor-grab text-(--font-neutral-muted) opacity-0 group-focus-within:opacity-100 group-hover:opacity-100 pointer-coarse:hidden\"\n [class.invisible]=\"visible().length < 2\" />\n @if (alert.active) {\n <bell-icon class=\"shrink-0\" />\n } @else {\n <bell-slash-icon class=\"shrink-0 text-(--font-neutral-muted)\" />\n }\n <span class=\"flex min-w-0 flex-1 flex-col\">\n <span\n class=\"line-clamp-1 text-(length:--size-base) group-data-inactive:text-(--font-neutral-muted)\"\n >{{ name }}</span\n >\n @if (summaryOf(alert).length) {\n <span\n class=\"line-clamp-1 text-(length:--size-sm) text-(--font-neutral-muted)\">\n @for (\n part of summaryOf(alert);\n track $index;\n let first = $first\n ) {\n {{ first ? \"\" : \" · \"\n }}{{ part.key ? (part.key | transloco) : part.text }}\n }\n </span>\n }\n </span>\n <span data-row-action class=\"flex shrink-0 items-center\">\n <Switch\n [checked]=\"isActive(alert)\"\n [aria-label]=\"'alerts.alertActiveFor' | transloco: { name }\"\n (checkedChange)=\"toggle(alert, $event)\" />\n </span>\n <span data-row-action class=\"flex shrink-0 items-center\">\n <button\n variant=\"tertiary\"\n size=\"sm\"\n [iconOnly]=\"true\"\n class=\"text-(--font-error-muted) opacity-0 group-focus-within:opacity-100 group-hover:opacity-100 hover:bg-(--bg-error-lightest) hover:text-(--font-error-base) focus-visible:opacity-100 pointer-coarse:opacity-100\"\n [attr.title]=\"'alerts.deleteAlertNamed' | transloco: { name }\"\n [attr.aria-label]=\"'alerts.deleteAlertNamed' | transloco: { name }\"\n (click)=\"deleteAlert(alert)\">\n <TrashIcon />\n </button>\n </span>\n </li>\n } @empty {\n <li class=\"list-none\">\n <ListPanelEmpty color=\"orange\">\n <bell-icon listPanelEmptyIcon />\n {{ \"alerts.noAlerts\" | transloco }}\n <p listPanelEmptyHint>{{ \"alerts.noAlertsHint\" | transloco }}</p>\n <button listPanelEmptyAction (click)=\"createAlert()\">\n <plus-icon />\n {{ \"alerts.createAlert\" | transloco }}\n </button>\n </ListPanelEmpty>\n </li>\n }\n </ul>\n\n @if (visible().length > 1) {\n <p\n class=\"m-0 ps-(--spacing-2xs) pt-(--spacing-3xs) text-(length:--size-sm) text-(--font-neutral-muted)\">\n {{ \"alerts.reorderHint\" | transloco }}\n </p>\n }\n\n <!-- The regions exist before their text does, which is what makes a screen reader announce the text. -->\n <div role=\"status\">\n @if (pendingDeletion(); as pending) {\n <div\n class=\"mt-(--spacing-2xs) flex items-center gap-(--spacing-2xs) rounded-(--radius-sm) bg-(--bg-neutral-base) py-(--spacing-3xs) ps-(--spacing-xs) pe-(--spacing-3xs) text-(length:--size-sm) text-(--font-neutral-base)\">\n <span class=\"min-w-0 flex-1\">{{\n \"alerts.deleted\" | transloco: { name: pending.name }\n }}</span>\n <button variant=\"tertiary\" size=\"sm\" (click)=\"undoDeletion()\">\n {{ \"alerts.undo\" | transloco }}\n </button>\n </div>\n }\n </div>\n</div>\n",
17201
17465
  dependencies: [
17202
17466
  {
17203
17467
  kind: "directive",
@@ -17282,6 +17546,12 @@ var AlertsComponent = class AlertsComponent {
17282
17546
  selector: "list-panel-empty, ListPanelEmpty",
17283
17547
  inputs: ["color"]
17284
17548
  },
17549
+ {
17550
+ kind: "component",
17551
+ type: AlertSaveErrorComponent,
17552
+ selector: "alert-save-error",
17553
+ inputs: ["detail"]
17554
+ },
17285
17555
  {
17286
17556
  kind: "pipe",
17287
17557
  type: TranslocoPipe,
@@ -17310,13 +17580,32 @@ i0.ɵɵngDeclareClassMetadata({
17310
17580
  GripVerticalIcon,
17311
17581
  PlusIcon,
17312
17582
  TrashIcon,
17313
- ListPanelEmptyComponent
17583
+ ListPanelEmptyComponent,
17584
+ AlertSaveErrorComponent
17314
17585
  ],
17315
17586
  providers: [provideTranslocoScope("alerts")],
17316
- template: "<div class=\"flex min-w-80 flex-col gap-(--spacing-3xs) p-2\">\n <div class=\"flex items-center gap-(--spacing-2xs) ps-(--spacing-2xs)\">\n @if (floating) {\n <!-- Not an <h2>: a page's prose styles put a rule and margins on every h2, even inside a popover. -->\n <span\n role=\"heading\"\n aria-level=\"2\"\n class=\"flex-1 text-xl leading-(--line-height-4) font-bold\">\n {{ \"alerts.label\" | transloco }}\n @if (visible().length) {\n <span\n class=\"text-(length:--size-sm) font-normal text-(--font-neutral-muted)\"\n >· {{ visible().length }}</span\n >\n }\n </span>\n } @else {\n <span class=\"flex-1\"></span>\n }\n <!-- With no alert, \"Create\" is the call to action of the empty state, below. -->\n @if (visible().length) {\n <button size=\"md\" (click)=\"createAlert()\">\n <plus-icon />\n {{ \"alerts.create\" | transloco }}\n </button>\n }\n </div>\n\n <ul\n role=\"list\"\n class=\"m-0 flex list-none flex-col p-0\"\n [attr.aria-label]=\"'alerts.label' | transloco\"\n [sortable]=\"visible()\"\n #sortable=\"sortable\"\n [sortableAnnounce]=\"announceMove\"\n (reorder)=\"saveOrder($event)\">\n @for (alert of sortable.items(); track alert.name) {\n @let name = alert.name;\n <li\n [sortableItem]=\"alert\"\n class=\"group relative flex min-h-12 w-full cursor-pointer items-center gap-(--spacing-3xs) rounded-(--radius-sm) py-(--spacing-3xs) ps-(--spacing-3xs) pe-(--spacing-2xs) text-(--font-neutral-base) select-none hover:bg-(--bg-primary-lighter) focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-(--stroke-focus) active:bg-(--bg-primary-light) data-dragging:bg-(--bg-primary-lighter) data-dragging:outline-2 data-dragging:-outline-offset-2 data-dragging:outline-(--stroke-primary-base) data-dragging:outline-dashed data-dragging:*:invisible\"\n tabindex=\"0\"\n [attr.data-inactive]=\"alert.active ? null : ''\"\n (click)=\"onRowClick($event, alert)\"\n (keydown.enter)=\"onRowEnter($event, alert)\">\n <!-- Handle: shown when the row is hovered or focused, only if there is something to reorder. -->\n <grip-vertical-icon\n class=\"shrink-0 cursor-grab text-(--font-neutral-muted) opacity-0 group-focus-within:opacity-100 group-hover:opacity-100 pointer-coarse:hidden\"\n [class.invisible]=\"visible().length < 2\" />\n @if (alert.active) {\n <bell-icon class=\"shrink-0\" />\n } @else {\n <bell-slash-icon class=\"shrink-0 text-(--font-neutral-muted)\" />\n }\n <span class=\"flex min-w-0 flex-1 flex-col\">\n <span\n class=\"line-clamp-1 text-(length:--size-base) group-data-inactive:text-(--font-neutral-muted)\"\n >{{ name }}</span\n >\n @if (summaryOf(alert).length) {\n <span\n class=\"line-clamp-1 text-(length:--size-sm) text-(--font-neutral-muted)\">\n @for (\n part of summaryOf(alert);\n track $index;\n let first = $first\n ) {\n {{ first ? \"\" : \" · \"\n }}{{ part.key ? (part.key | transloco) : part.text }}\n }\n </span>\n }\n </span>\n <span data-row-action class=\"flex shrink-0 items-center\">\n <Switch\n [checked]=\"isActive(alert)\"\n [aria-label]=\"'alerts.alertActiveFor' | transloco: { name }\"\n (checkedChange)=\"toggle(alert, $event)\" />\n </span>\n <span data-row-action class=\"flex shrink-0 items-center\">\n <button\n variant=\"tertiary\"\n size=\"sm\"\n [iconOnly]=\"true\"\n class=\"text-(--font-error-muted) opacity-0 group-focus-within:opacity-100 group-hover:opacity-100 hover:bg-(--bg-error-lightest) hover:text-(--font-error-base) focus-visible:opacity-100 pointer-coarse:opacity-100\"\n [attr.title]=\"'alerts.deleteAlertNamed' | transloco: { name }\"\n [attr.aria-label]=\"'alerts.deleteAlertNamed' | transloco: { name }\"\n (click)=\"deleteAlert(alert)\">\n <TrashIcon />\n </button>\n </span>\n </li>\n } @empty {\n <li class=\"list-none\">\n <ListPanelEmpty color=\"orange\">\n <bell-icon listPanelEmptyIcon />\n {{ \"alerts.noAlerts\" | transloco }}\n <p listPanelEmptyHint>{{ \"alerts.noAlertsHint\" | transloco }}</p>\n <button listPanelEmptyAction (click)=\"createAlert()\">\n <plus-icon />\n {{ \"alerts.createAlert\" | transloco }}\n </button>\n </ListPanelEmpty>\n </li>\n }\n </ul>\n\n @if (visible().length > 1) {\n <p\n class=\"m-0 ps-(--spacing-2xs) pt-(--spacing-3xs) text-(length:--size-sm) text-(--font-neutral-muted)\">\n {{ \"alerts.reorderHint\" | transloco }}\n </p>\n }\n\n <!-- The regions exist before their text does, which is what makes a screen reader announce the text. -->\n <div role=\"status\">\n @if (pendingDeletion(); as pending) {\n <div\n class=\"mt-(--spacing-2xs) flex items-center gap-(--spacing-2xs) rounded-(--radius-sm) bg-(--bg-neutral-base) py-(--spacing-3xs) ps-(--spacing-xs) pe-(--spacing-3xs) text-(length:--size-sm) text-(--font-neutral-base)\">\n <span class=\"min-w-0 flex-1\">{{\n \"alerts.deleted\" | transloco: { name: pending.name }\n }}</span>\n <button variant=\"tertiary\" size=\"sm\" (click)=\"undoDeletion()\">\n {{ \"alerts.undo\" | transloco }}\n </button>\n </div>\n }\n </div>\n</div>\n"
17587
+ template: "<div class=\"flex min-w-80 flex-col gap-(--spacing-3xs) p-2\">\n <div class=\"flex items-center gap-(--spacing-2xs) ps-(--spacing-2xs)\">\n @if (floating) {\n <!-- Not an <h2>: a page's prose styles put a rule and margins on every h2, even inside a popover. -->\n <span\n role=\"heading\"\n aria-level=\"2\"\n class=\"flex-1 text-xl leading-(--line-height-4) font-bold\">\n {{ \"alerts.label\" | transloco }}\n @if (visible().length) {\n <span\n class=\"text-(length:--size-sm) font-normal text-(--font-neutral-muted)\"\n >· {{ visible().length }}</span\n >\n }\n </span>\n } @else {\n <span class=\"flex-1\"></span>\n }\n <!-- With no alert, \"Create\" is the call to action of the empty state, below. -->\n @if (visible().length) {\n <button size=\"md\" (click)=\"createAlert()\">\n <plus-icon />\n {{ \"alerts.create\" | transloco }}\n </button>\n }\n </div>\n\n @if (saveError(); as failure) {\n <alert-save-error class=\"mb-(--spacing-2xs)\" [detail]=\"failure.detail\" />\n }\n\n <ul\n role=\"list\"\n class=\"m-0 flex list-none flex-col p-0\"\n [attr.aria-label]=\"'alerts.label' | transloco\"\n [sortable]=\"visible()\"\n #sortable=\"sortable\"\n [sortableAnnounce]=\"announceMove\"\n (reorder)=\"saveOrder($event)\">\n @for (alert of sortable.items(); track alert.name) {\n @let name = alert.name;\n <li\n [sortableItem]=\"alert\"\n class=\"group relative flex min-h-12 w-full cursor-pointer items-center gap-(--spacing-3xs) rounded-(--radius-sm) py-(--spacing-3xs) ps-(--spacing-3xs) pe-(--spacing-2xs) text-(--font-neutral-base) select-none hover:bg-(--bg-primary-lighter) focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-(--stroke-focus) active:bg-(--bg-primary-light) data-dragging:bg-(--bg-primary-lighter) data-dragging:outline-2 data-dragging:-outline-offset-2 data-dragging:outline-(--stroke-primary-base) data-dragging:outline-dashed data-dragging:*:invisible\"\n tabindex=\"0\"\n [attr.data-inactive]=\"alert.active ? null : ''\"\n (click)=\"onRowClick($event, alert)\"\n (keydown.enter)=\"onRowEnter($event, alert)\">\n <!-- Handle: shown when the row is hovered or focused, only if there is something to reorder. -->\n <grip-vertical-icon\n class=\"shrink-0 cursor-grab text-(--font-neutral-muted) opacity-0 group-focus-within:opacity-100 group-hover:opacity-100 pointer-coarse:hidden\"\n [class.invisible]=\"visible().length < 2\" />\n @if (alert.active) {\n <bell-icon class=\"shrink-0\" />\n } @else {\n <bell-slash-icon class=\"shrink-0 text-(--font-neutral-muted)\" />\n }\n <span class=\"flex min-w-0 flex-1 flex-col\">\n <span\n class=\"line-clamp-1 text-(length:--size-base) group-data-inactive:text-(--font-neutral-muted)\"\n >{{ name }}</span\n >\n @if (summaryOf(alert).length) {\n <span\n class=\"line-clamp-1 text-(length:--size-sm) text-(--font-neutral-muted)\">\n @for (\n part of summaryOf(alert);\n track $index;\n let first = $first\n ) {\n {{ first ? \"\" : \" · \"\n }}{{ part.key ? (part.key | transloco) : part.text }}\n }\n </span>\n }\n </span>\n <span data-row-action class=\"flex shrink-0 items-center\">\n <Switch\n [checked]=\"isActive(alert)\"\n [aria-label]=\"'alerts.alertActiveFor' | transloco: { name }\"\n (checkedChange)=\"toggle(alert, $event)\" />\n </span>\n <span data-row-action class=\"flex shrink-0 items-center\">\n <button\n variant=\"tertiary\"\n size=\"sm\"\n [iconOnly]=\"true\"\n class=\"text-(--font-error-muted) opacity-0 group-focus-within:opacity-100 group-hover:opacity-100 hover:bg-(--bg-error-lightest) hover:text-(--font-error-base) focus-visible:opacity-100 pointer-coarse:opacity-100\"\n [attr.title]=\"'alerts.deleteAlertNamed' | transloco: { name }\"\n [attr.aria-label]=\"'alerts.deleteAlertNamed' | transloco: { name }\"\n (click)=\"deleteAlert(alert)\">\n <TrashIcon />\n </button>\n </span>\n </li>\n } @empty {\n <li class=\"list-none\">\n <ListPanelEmpty color=\"orange\">\n <bell-icon listPanelEmptyIcon />\n {{ \"alerts.noAlerts\" | transloco }}\n <p listPanelEmptyHint>{{ \"alerts.noAlertsHint\" | transloco }}</p>\n <button listPanelEmptyAction (click)=\"createAlert()\">\n <plus-icon />\n {{ \"alerts.createAlert\" | transloco }}\n </button>\n </ListPanelEmpty>\n </li>\n }\n </ul>\n\n @if (visible().length > 1) {\n <p\n class=\"m-0 ps-(--spacing-2xs) pt-(--spacing-3xs) text-(length:--size-sm) text-(--font-neutral-muted)\">\n {{ \"alerts.reorderHint\" | transloco }}\n </p>\n }\n\n <!-- The regions exist before their text does, which is what makes a screen reader announce the text. -->\n <div role=\"status\">\n @if (pendingDeletion(); as pending) {\n <div\n class=\"mt-(--spacing-2xs) flex items-center gap-(--spacing-2xs) rounded-(--radius-sm) bg-(--bg-neutral-base) py-(--spacing-3xs) ps-(--spacing-xs) pe-(--spacing-3xs) text-(length:--size-sm) text-(--font-neutral-base)\">\n <span class=\"min-w-0 flex-1\">{{\n \"alerts.deleted\" | transloco: { name: pending.name }\n }}</span>\n <button variant=\"tertiary\" size=\"sm\" (click)=\"undoDeletion()\">\n {{ \"alerts.undo\" | transloco }}\n </button>\n </div>\n }\n </div>\n</div>\n"
17317
17588
  }]
17318
17589
  }],
17319
- ctorParameters: () => []
17590
+ ctorParameters: () => [],
17591
+ propDecorators: {
17592
+ dialog: [{
17593
+ type: i0.Input,
17594
+ args: [{
17595
+ isSignal: true,
17596
+ alias: "dialog",
17597
+ required: false
17598
+ }]
17599
+ }],
17600
+ editRequested: [{
17601
+ type: i0.Output,
17602
+ args: ["editRequested"]
17603
+ }],
17604
+ createRequested: [{
17605
+ type: i0.Output,
17606
+ args: ["createRequested"]
17607
+ }]
17608
+ }
17320
17609
  });
17321
17610
  const passwordChangeSchema = schema((p) => {
17322
17611
  required(p.currentPassword);
@@ -32837,6 +33126,147 @@ i0.ɵɵngDeclareClassMetadata({
32837
33126
  }]
32838
33127
  }] }
32839
33128
  });
33129
+ var SettingsAlertsPanelComponent = class SettingsAlertsPanelComponent {
33130
+ closeRequested = output();
33131
+ form = signal(void 0, ...ngDevMode ? [{ debugName: "form" }] : /* istanbul ignore next */ []);
33132
+ edit(index) {
33133
+ this.form.set({ index });
33134
+ }
33135
+ create() {
33136
+ this.form.set({ index: void 0 });
33137
+ }
33138
+ back() {
33139
+ this.form.set(void 0);
33140
+ }
33141
+ static ɵfac = i0.ɵɵngDeclareFactory({
33142
+ minVersion: "12.0.0",
33143
+ version: "22.2.1",
33144
+ ngImport: i0,
33145
+ type: SettingsAlertsPanelComponent,
33146
+ deps: [],
33147
+ target: i0.ɵɵFactoryTarget.Component
33148
+ });
33149
+ static ɵcmp = i0.ɵɵngDeclareComponent({
33150
+ minVersion: "17.0.0",
33151
+ version: "22.2.1",
33152
+ type: SettingsAlertsPanelComponent,
33153
+ isStandalone: true,
33154
+ selector: "settings-alerts-panel",
33155
+ outputs: { closeRequested: "closeRequested" },
33156
+ providers: [provideTranslocoScope("settings"), provideTranslocoScope("alerts")],
33157
+ ngImport: i0,
33158
+ template: `
33159
+ @if (form(); as current) {
33160
+ <button type="button" variant="tertiary" size="sm" class="-ms-(--spacing-xs) mb-(--spacing-xs)" (click)="back()">
33161
+ <chevron-left-icon aria-hidden="true" />
33162
+ {{ "settings.alerts.back" | transloco }}
33163
+ </button>
33164
+ <h2 class="mb-(--spacing-md) text-(length:--size-2xl) font-semibold">
33165
+ {{ (current.index === undefined ? "alerts.createAlert" : "alerts.editAlert") | transloco }}
33166
+ </h2>
33167
+
33168
+ <alert-form #alertForm [index]="current.index" (saved)="back()" (executed)="closeRequested.emit()" />
33169
+
33170
+ <div class="mt-(--spacing-lg) flex justify-end gap-(--spacing-xs)">
33171
+ <button type="button" variant="secondary" (click)="back()">{{ "cancel" | transloco }}</button>
33172
+ <button type="submit" [attr.form]="alertForm.formId" [disabled]="alertForm.saving()">{{ "confirm" | transloco }}</button>
33173
+ </div>
33174
+ } @else {
33175
+ <h2 class="text-(length:--size-2xl) font-semibold">{{ "settings.alerts.title" | transloco }}</h2>
33176
+ <p class="text-(--font-primary-muted)">{{ "settings.alerts.description" | transloco }}</p>
33177
+
33178
+ <Alerts class="mt-(--spacing-md) block" [dialog]="false" (editRequested)="edit($event)" (createRequested)="create()" />
33179
+ }
33180
+ `,
33181
+ isInline: true,
33182
+ dependencies: [
33183
+ {
33184
+ kind: "directive",
33185
+ type: ButtonComponent,
33186
+ selector: "button",
33187
+ inputs: [
33188
+ "class",
33189
+ "variant",
33190
+ "scheme",
33191
+ "iconOnly",
33192
+ "size"
33193
+ ]
33194
+ },
33195
+ {
33196
+ kind: "component",
33197
+ type: ChevronLeftIcon,
33198
+ selector: "chevron-left, chevron-left-icon, ChevronLeft, ChevronLeftIcon",
33199
+ inputs: ["class"]
33200
+ },
33201
+ {
33202
+ kind: "component",
33203
+ type: AlertsComponent,
33204
+ selector: "Alerts",
33205
+ inputs: ["dialog"],
33206
+ outputs: ["editRequested", "createRequested"]
33207
+ },
33208
+ {
33209
+ kind: "component",
33210
+ type: AlertFormComponent,
33211
+ selector: "alert-form, alertform, AlertFormComponent",
33212
+ inputs: ["index"],
33213
+ outputs: ["saved", "executed"]
33214
+ },
33215
+ {
33216
+ kind: "pipe",
33217
+ type: TranslocoPipe,
33218
+ name: "transloco"
33219
+ }
33220
+ ]
33221
+ });
33222
+ };
33223
+ i0.ɵɵngDeclareClassMetadata({
33224
+ minVersion: "12.0.0",
33225
+ version: "22.2.1",
33226
+ ngImport: i0,
33227
+ type: SettingsAlertsPanelComponent,
33228
+ decorators: [{
33229
+ type: Component,
33230
+ args: [{
33231
+ selector: "settings-alerts-panel",
33232
+ imports: [
33233
+ TranslocoPipe,
33234
+ ButtonComponent,
33235
+ ChevronLeftIcon,
33236
+ AlertsComponent,
33237
+ AlertFormComponent
33238
+ ],
33239
+ providers: [provideTranslocoScope("settings"), provideTranslocoScope("alerts")],
33240
+ template: `
33241
+ @if (form(); as current) {
33242
+ <button type="button" variant="tertiary" size="sm" class="-ms-(--spacing-xs) mb-(--spacing-xs)" (click)="back()">
33243
+ <chevron-left-icon aria-hidden="true" />
33244
+ {{ "settings.alerts.back" | transloco }}
33245
+ </button>
33246
+ <h2 class="mb-(--spacing-md) text-(length:--size-2xl) font-semibold">
33247
+ {{ (current.index === undefined ? "alerts.createAlert" : "alerts.editAlert") | transloco }}
33248
+ </h2>
33249
+
33250
+ <alert-form #alertForm [index]="current.index" (saved)="back()" (executed)="closeRequested.emit()" />
33251
+
33252
+ <div class="mt-(--spacing-lg) flex justify-end gap-(--spacing-xs)">
33253
+ <button type="button" variant="secondary" (click)="back()">{{ "cancel" | transloco }}</button>
33254
+ <button type="submit" [attr.form]="alertForm.formId" [disabled]="alertForm.saving()">{{ "confirm" | transloco }}</button>
33255
+ </div>
33256
+ } @else {
33257
+ <h2 class="text-(length:--size-2xl) font-semibold">{{ "settings.alerts.title" | transloco }}</h2>
33258
+ <p class="text-(--font-primary-muted)">{{ "settings.alerts.description" | transloco }}</p>
33259
+
33260
+ <Alerts class="mt-(--spacing-md) block" [dialog]="false" (editRequested)="edit($event)" (createRequested)="create()" />
33261
+ }
33262
+ `
33263
+ }]
33264
+ }],
33265
+ propDecorators: { closeRequested: [{
33266
+ type: i0.Output,
33267
+ args: ["closeRequested"]
33268
+ }] }
33269
+ });
32840
33270
  const THEMES$1 = [
32841
33271
  "light",
32842
33272
  "dark",
@@ -33173,6 +33603,10 @@ var SettingsDialogComponent = class SettingsDialogComponent {
33173
33603
  id: "appearance",
33174
33604
  labelKey: "settings.appearance.nav"
33175
33605
  },
33606
+ {
33607
+ id: "alerts",
33608
+ labelKey: "settings.alerts.nav"
33609
+ },
33176
33610
  {
33177
33611
  id: "shortcuts",
33178
33612
  labelKey: "settings.shortcuts.nav"
@@ -33253,6 +33687,9 @@ var SettingsDialogComponent = class SettingsDialogComponent {
33253
33687
  @case ("appearance") {
33254
33688
  <palette-icon aria-hidden="true" />
33255
33689
  }
33690
+ @case ("alerts") {
33691
+ <bell-icon aria-hidden="true" />
33692
+ }
33256
33693
  @case ("shortcuts") {
33257
33694
  <keyboard-icon aria-hidden="true" />
33258
33695
  }
@@ -33271,6 +33708,9 @@ var SettingsDialogComponent = class SettingsDialogComponent {
33271
33708
  @case ("appearance") {
33272
33709
  <settings-appearance-panel />
33273
33710
  }
33711
+ @case ("alerts") {
33712
+ <settings-alerts-panel (closeRequested)="dialog.close()" />
33713
+ }
33274
33714
  @case ("shortcuts") {
33275
33715
  <settings-shortcuts-panel />
33276
33716
  }
@@ -33324,6 +33764,12 @@ var SettingsDialogComponent = class SettingsDialogComponent {
33324
33764
  selector: "palette, palette-icon, Palette, PaletteIcon",
33325
33765
  inputs: ["class"]
33326
33766
  },
33767
+ {
33768
+ kind: "component",
33769
+ type: BellIcon,
33770
+ selector: "bell, bell-icon, Bell, BellIcon",
33771
+ inputs: ["class"]
33772
+ },
33327
33773
  {
33328
33774
  kind: "component",
33329
33775
  type: KeyboardIcon,
@@ -33353,6 +33799,12 @@ var SettingsDialogComponent = class SettingsDialogComponent {
33353
33799
  type: SettingsAppearancePanelComponent,
33354
33800
  selector: "settings-appearance-panel"
33355
33801
  },
33802
+ {
33803
+ kind: "component",
33804
+ type: SettingsAlertsPanelComponent,
33805
+ selector: "settings-alerts-panel",
33806
+ outputs: ["closeRequested"]
33807
+ },
33356
33808
  {
33357
33809
  kind: "component",
33358
33810
  type: SettingsShortcutsPanelComponent,
@@ -33382,11 +33834,13 @@ i0.ɵɵngDeclareClassMetadata({
33382
33834
  DialogContentComponent,
33383
33835
  UserIcon,
33384
33836
  PaletteIcon,
33837
+ BellIcon,
33385
33838
  KeyboardIcon,
33386
33839
  XMarkIcon,
33387
33840
  SidebarInsetDirective,
33388
33841
  SettingsAccountPanelComponent,
33389
33842
  SettingsAppearancePanelComponent,
33843
+ SettingsAlertsPanelComponent,
33390
33844
  SettingsShortcutsPanelComponent
33391
33845
  ],
33392
33846
  providers: [provideTranslocoScope("settings")],
@@ -33423,6 +33877,9 @@ i0.ɵɵngDeclareClassMetadata({
33423
33877
  @case ("appearance") {
33424
33878
  <palette-icon aria-hidden="true" />
33425
33879
  }
33880
+ @case ("alerts") {
33881
+ <bell-icon aria-hidden="true" />
33882
+ }
33426
33883
  @case ("shortcuts") {
33427
33884
  <keyboard-icon aria-hidden="true" />
33428
33885
  }
@@ -33441,6 +33898,9 @@ i0.ɵɵngDeclareClassMetadata({
33441
33898
  @case ("appearance") {
33442
33899
  <settings-appearance-panel />
33443
33900
  }
33901
+ @case ("alerts") {
33902
+ <settings-alerts-panel (closeRequested)="dialog.close()" />
33903
+ }
33444
33904
  @case ("shortcuts") {
33445
33905
  <settings-shortcuts-panel />
33446
33906
  }
@@ -33933,6 +34393,6 @@ function provideSessionExpiry(options = {}) {
33933
34393
  const queryNameResolver = () => {
33934
34394
  return inject(AppStore).getDefaultQuery()?.name || "";
33935
34395
  };
33936
- export { AGGREGATIONS_NAMES, AGGREGATIONS_NAMES_PRESET_DEFAULT, APP_FEATURES, ATOMIC_CLIENT, ATOMIC_SCOPED_PROVIDERS, AddToCollection, AdvancedFiltersBodyComponent, AdvancedFiltersComponent, AdvancedSearch, AdvancedSearchTabs, AggregationComponent, AggregationDateComponent, AggregationDateRange, AggregationItemDirective, AggregationListComponent, AggregationPanelComponent, AggregationTreeComponent, AggregationsService, AggregationsStore, Alert, AlertDialog, AlertForm, AlertsComponent, AppService, AppStore, ApplicationService, ApplicationStore, AppliedFiltersComponent, ArticleEntities, ArticleEntitiesTabPanel, ArticleExtracts, ArticleExtractsTabPanel, ArticleHighlights, ArticleLabels, ArticleLabelsTabPanel, ArticleMatchLocations, ArticleMatchLocationsTabPanel, ArticleSimilarDocuments, ArticleSimilarDocumentsTabPanel, AsideFiltersComponent, AuditFeedbackType, AuditService, AuthGuard, AuthPageComponent, AutocompleteService, BOOKMARKS_CONFIG, BOOKMARKS_OPTIONS, BackdropComponent, BackdropService, BookmarkButtonComponent, BookmarksComponent, COLLECTIONS_CONFIG, COLLECTIONS_OPTIONS, COMPONENTS_FOR_DOCUMENT_TYPE, ChangePasswordComponent, ChildMarkerDirective, CollectionsComponent, CollectionsDialog, ConverterSelectComponent, DeleteCollection, DeleteCollectionDialog, DidYouMeanComponent, DocumentLocatorComponent, EditLabels, ErrorComponent, Export, ExportDialog, ExportService, FILTERS_BREAKPOINT, FILTER_DATE_ALLOW_CUSTOM_RANGE, FORM_ALERT_CLASS, FORM_BARE_CLASS, FORM_ERROR_CLASS, FORM_FIELD_CLASS, FORM_FOOTER_CLASS, FORM_LABEL_CLASS, FORM_SURFACE_CLASS, FaIconComponent, FeatureFlags, FeatureFlagsDialogComponent, Feedback, FeedbackDialogComponent, FileSizePipe, FilterButtonComponent, FiltersBarComponent, FloatingToolbarComponent, ForgotPasswordComponent, HIGHLIGHTS, HeatmapComponent, HighlightWordPipe, InfinityScrollDirective, InlineWorker, JsonMethodPluginService, KeyboardShortcutsDialog, KeyboardShortcutsDialogComponent, KeyboardShortcutsListComponent, KeyboardShortcutsService, LabelService, LabelsEditDialog, ListPanelItemDirective, LoadingComponent, MarkdownPipe, MarkdownRendererService, MetadataComponent, MissingTermsComponent, MoreButtonComponent, MoreComponent, MultiSelectLabelsComponent, MultiSelectionToolbarComponent, NON_SEARCHABLE_COLUMNS, NON_SEARCHABLE_DEFAULTS, NavbarTabsComponent, NavigationService, NoResultComponent, OpenArticleOnCtrlEnterDirective, OperatorPipe, OverflowItemDirective, OverflowManagerDirective, OverflowStopDirective, OverrideUser, OverrideUserDialogComponent, PREVIEW_ASSISTANT_COMPONENT, PREVIEW_CONFIG, PagerComponent, PreviewActionsComponent, PreviewContentComponent, PreviewHeaderComponent, PreviewNavigator, PreviewService, PrincipalService, PrincipalStore, QueryParamsStore, QueryService, RECENT_SEARCHES_CONFIG, RECENT_SEARCHES_OPTIONS, ROUTE_COMPONENTS, RecentSearchesComponent, ResetUserSettings, ResetUserSettingsDialogComponent, SAVED_SEARCHES_CONFIG, SAVED_SEARCHES_OPTIONS, STARTUP_DATA_SOURCE, SavedSearch, SavedSearchDialog, SavedSearchPopover, SavedSearchesComponent, SavedSearchesService, SearchFeedbackComponent, SearchInputFooter, SearchService, SelectArticleDirective, SelectionService, SelectionStore, Settings, SettingsAccountPanelComponent, SettingsAppearancePanelComponent, SettingsDialogComponent, SettingsShortcutsPanelComponent, ShortcutDirective, ShowBookmarkDirective, SignInComponent, SignedOutComponent, SortSelectorComponent, SourceComponent, SourceIconPipe, SponsoredResultsComponent, SyslangPipe, THEMES, TagCloudComponent, TextChunkService, ThemeProviderDirective, ThemeSelectorComponent, ThemeStore, ThemeToggleComponent, TranslocoDateImpurePipe, UserOverrideService, UserProfileFormComponent, UserProfileService, UserSettingsPersistence, UserSettingsStore, WEIGHT_STEPS, WordCloudComponent, ZoomControlsComponent, __resetSignInMutexForTests, activateShortcutTarget, applyThemeToNativeElement, atomicClientProvider, auditInterceptorFn, authInterceptorFn, bodyInterceptorFn, bootstrapApp, buildHeatmap, buildQuery, buildTagCloud, candidatesFor, canonicalize, debouncedSignal, errorInterceptorFn, fieldErrorKey, fieldShowsError, filterKey, filterLeaves, filterValues, findAggregation, flagsFromWeekdays, focusWhenReady, galactikPrimitive, galactikSemantic, getCurrentPath, getCurrentQueryName, getQueryNameFromRoute, injectAdvancedFilters, injectAtomicClient, injectRouteNavigation, injectUserSettingsPatch, isAuthRoute, isSignInInFlight, itemLabel, layoutWordCloud, legacyFilterToFilter, makeWeightScale, multiSelectionToolbarVariants, paletteColor, processCssVars, provideAtomicClient, provideAtomicNotifications, provideAtomicScope, provideDocumentLang, provideKeyboardShortcuts, provideSessionExpiry, provideStartupAggregation, quantileScale, queryNameResolver, rebuildWithout, seriesPalette, signIn, themeColorNameToCssVariable, themeColorsToCssVariables, timelinePalette, toAriaKeyshortcuts, toDisplayParts, toPercentages, toSeries, toastInterceptorFn, weekdaysFromFlags, withAggregationsFeatures, withAlertsFeatures, withAlpha, withAppFeatures, withApplicationFeatures, withAssistantFeatures, withBasketsFeatures, withBookmarkFeatures, withBootstrapApp, withExtractsFeatures, withFetch, withMultiSelectionFeatures, withQueryParamsFeatures, withRecentSearchesFeatures, withSavedSearchesFeatures, withSelectionFeatures, withThemeBodyHook, withThemes, withThemesFeatures, withUserSettingsFeatures };
34396
+ export { AGGREGATIONS_NAMES, AGGREGATIONS_NAMES_PRESET_DEFAULT, APP_FEATURES, ATOMIC_CLIENT, ATOMIC_SCOPED_PROVIDERS, AddToCollection, AdvancedFiltersBodyComponent, AdvancedFiltersComponent, AdvancedSearch, AdvancedSearchTabs, AggregationComponent, AggregationDateComponent, AggregationDateRange, AggregationItemDirective, AggregationListComponent, AggregationPanelComponent, AggregationTreeComponent, AggregationsService, AggregationsStore, Alert, AlertDialog, AlertForm, AlertFormComponent, AlertsComponent, AppService, AppStore, ApplicationService, ApplicationStore, AppliedFiltersComponent, ArticleEntities, ArticleEntitiesTabPanel, ArticleExtracts, ArticleExtractsTabPanel, ArticleHighlights, ArticleLabels, ArticleLabelsTabPanel, ArticleMatchLocations, ArticleMatchLocationsTabPanel, ArticleSimilarDocuments, ArticleSimilarDocumentsTabPanel, AsideFiltersComponent, AuditFeedbackType, AuditService, AuthGuard, AuthPageComponent, AutocompleteService, BOOKMARKS_CONFIG, BOOKMARKS_OPTIONS, BackdropComponent, BackdropService, BookmarkButtonComponent, BookmarksComponent, COLLECTIONS_CONFIG, COLLECTIONS_OPTIONS, COMPONENTS_FOR_DOCUMENT_TYPE, ChangePasswordComponent, ChildMarkerDirective, CollectionsComponent, CollectionsDialog, ConverterSelectComponent, DeleteCollection, DeleteCollectionDialog, DidYouMeanComponent, DocumentLocatorComponent, EditLabels, ErrorComponent, Export, ExportDialog, ExportService, FILTERS_BREAKPOINT, FILTER_DATE_ALLOW_CUSTOM_RANGE, FORM_ALERT_CLASS, FORM_BARE_CLASS, FORM_ERROR_CLASS, FORM_FIELD_CLASS, FORM_FOOTER_CLASS, FORM_LABEL_CLASS, FORM_SURFACE_CLASS, FaIconComponent, FeatureFlags, FeatureFlagsDialogComponent, Feedback, FeedbackDialogComponent, FileSizePipe, FilterButtonComponent, FiltersBarComponent, FloatingToolbarComponent, ForgotPasswordComponent, HIGHLIGHTS, HeatmapComponent, HighlightWordPipe, InfinityScrollDirective, InlineWorker, JsonMethodPluginService, KeyboardShortcutsDialog, KeyboardShortcutsDialogComponent, KeyboardShortcutsListComponent, KeyboardShortcutsService, LabelService, LabelsEditDialog, ListPanelItemDirective, LoadingComponent, MarkdownPipe, MarkdownRendererService, MetadataComponent, MissingTermsComponent, MoreButtonComponent, MoreComponent, MultiSelectLabelsComponent, MultiSelectionToolbarComponent, NON_SEARCHABLE_COLUMNS, NON_SEARCHABLE_DEFAULTS, NavbarTabsComponent, NavigationService, NoResultComponent, OpenArticleOnCtrlEnterDirective, OperatorPipe, OverflowItemDirective, OverflowManagerDirective, OverflowStopDirective, OverrideUser, OverrideUserDialogComponent, PREVIEW_ASSISTANT_COMPONENT, PREVIEW_CONFIG, PagerComponent, PreviewActionsComponent, PreviewContentComponent, PreviewHeaderComponent, PreviewNavigator, PreviewService, PrincipalService, PrincipalStore, QueryParamsStore, QueryService, RECENT_SEARCHES_CONFIG, RECENT_SEARCHES_OPTIONS, ROUTE_COMPONENTS, RecentSearchesComponent, ResetUserSettings, ResetUserSettingsDialogComponent, SAVED_SEARCHES_CONFIG, SAVED_SEARCHES_OPTIONS, STARTUP_DATA_SOURCE, SavedSearch, SavedSearchDialog, SavedSearchPopover, SavedSearchesComponent, SavedSearchesService, SearchFeedbackComponent, SearchInputFooter, SearchService, SelectArticleDirective, SelectionService, SelectionStore, Settings, SettingsAccountPanelComponent, SettingsAlertsPanelComponent, SettingsAppearancePanelComponent, SettingsDialogComponent, SettingsShortcutsPanelComponent, ShortcutDirective, ShowBookmarkDirective, SignInComponent, SignedOutComponent, SortSelectorComponent, SourceComponent, SourceIconPipe, SponsoredResultsComponent, SyslangPipe, THEMES, TagCloudComponent, TextChunkService, ThemeProviderDirective, ThemeSelectorComponent, ThemeStore, ThemeToggleComponent, TranslocoDateImpurePipe, UserOverrideService, UserProfileFormComponent, UserProfileService, UserSettingsPersistence, UserSettingsStore, WEIGHT_STEPS, WordCloudComponent, ZoomControlsComponent, __resetSignInMutexForTests, activateShortcutTarget, applyThemeToNativeElement, atomicClientProvider, auditInterceptorFn, authInterceptorFn, bodyInterceptorFn, bootstrapApp, buildHeatmap, buildQuery, buildTagCloud, candidatesFor, canonicalize, debouncedSignal, errorInterceptorFn, fieldErrorKey, fieldShowsError, filterKey, filterLeaves, filterValues, findAggregation, flagsFromWeekdays, focusWhenReady, galactikPrimitive, galactikSemantic, getCurrentPath, getCurrentQueryName, getQueryNameFromRoute, injectAdvancedFilters, injectAtomicClient, injectRouteNavigation, injectUserSettingsPatch, isAuthRoute, isSignInInFlight, itemLabel, layoutWordCloud, legacyFilterToFilter, makeWeightScale, multiSelectionToolbarVariants, paletteColor, processCssVars, provideAtomicClient, provideAtomicNotifications, provideAtomicScope, provideDocumentLang, provideKeyboardShortcuts, provideSessionExpiry, provideStartupAggregation, quantileScale, queryNameResolver, rebuildWithout, seriesPalette, signIn, themeColorNameToCssVariable, themeColorsToCssVariables, timelinePalette, toAriaKeyshortcuts, toDisplayParts, toPercentages, toSeries, toastInterceptorFn, weekdaysFromFlags, withAggregationsFeatures, withAlertsFeatures, withAlpha, withAppFeatures, withApplicationFeatures, withAssistantFeatures, withBasketsFeatures, withBookmarkFeatures, withBootstrapApp, withExtractsFeatures, withFetch, withMultiSelectionFeatures, withQueryParamsFeatures, withRecentSearchesFeatures, withSavedSearchesFeatures, withSelectionFeatures, withThemeBodyHook, withThemes, withThemesFeatures, withUserSettingsFeatures };
33937
34397
 
33938
34398
  //# sourceMappingURL=sinequa-atomic-angular.mjs.map