@sinequa/atomic-angular 2.7.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.
@@ -2,7 +2,7 @@ import * as i0 from "@angular/core";
2
2
  import { ChangeDetectionStrategy, ChangeDetectorRef, Component, DOCUMENT, DestroyRef, Directive, ElementRef, EnvironmentInjector, HostBinding, HostListener, Inject, Injectable, InjectionToken, Injector, LOCALE_ID, Optional, Pipe, Service, TemplateRef, ViewEncapsulation, afterNextRender, afterRenderEffect, assertInInjectionContext, booleanAttribute, computed, contentChild, contentChildren, effect, inject, input, isDevMode, linkedSignal, makeEnvironmentProviders, model, numberAttribute, output, provideEnvironmentInitializer, resource, runInInjectionContext, signal, untracked, viewChild, viewChildren } from "@angular/core";
3
3
  import { BehaviorSubject, EMPTY, Subject, Subscription, catchError, debounceTime, filter, firstValueFrom, from, fromEvent, map, of, scan, shareReplay, switchMap, tap, throwError } from "rxjs";
4
4
  import { TranslocoPipe, TranslocoService, provideTranslocoScope } from "@jsverse/transloco";
5
- import { ArrowDownIcon, ArrowDownZaIcon, ArrowLeftIcon, ArrowRightFromBracketIcon, ArrowRightIcon, ArrowUpAzIcon, ArrowUpIcon, ArrowUpRightFromSquareIcon, ArrowsMaximizeIcon, AutocompleteComponent, AvatarComponent, AvatarFallbackComponent, AvatarImageComponent, BadgeComponent, BarsIcon, BellIcon, BookmarkIcon, BreakpointObserverService, ButtonComponent, CalendarDayIcon, CheckIcon, CheckboxComponent, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ChevronsLeftIcon, ChevronsRightIcon, CircleCheckIcon, CommentIcon, CommentsIcon, DIALOG_REF, DesktopIcon, DialogBodyComponent, DialogComponent, DialogContentComponent, DialogDescriptionComponent, DialogFooterComponent, DialogHeaderComponent, DialogTitleComponent, DropdownDirective, EditIcon, EllipsisIcon, EllipsisVerticalIcon, EyeIcon, EyeSlashIcon, FileHTMLIcon, FileIcon, FileOutputIcon, FileSpreadsheetIcon, FileTextIcon, FilterIcon, FilterXIcon, FlagEnglishIcon, FlagFrenchIcon, FlagGermanIcon, FlashlightIcon, FolderIcon, FolderOpenIcon, FrownIcon, GearIcon, GitForkIcon, GlobeIcon, HighlighterIcon, HistoryIcon, HomeIcon, ImageIcon, InboxIcon, InfoCircleIcon, InputComponent, InputControlDirective, InputIconRightDirective, KbdComponent, KbdGroupComponent, KeyIcon, KeyboardIcon, LanguageIcon, LightbulbIcon, LightbulbSlashIcon, LinkComponent, ListComponent, ListFilterIcon, ListItemActionComponent, ListItemAvatarDirective, ListItemComponent, ListItemLeftDirective, ListItemTextComponent, LoadingCircleIcon, LockIcon, MagnifyingGlassIcon, MagnifyingGlassMinusIcon, MagnifyingGlassPlusIcon, MenuComponent, MenuContentComponent, MenuItemComponent, MenuTriggerDirective, MoonIcon, PaletteIcon, PaperclipIcon, PenToSquareIcon, PlusIcon, PopoverDirective, PresentationIcon, QuestionCircleIcon, RadioComponent, RobotIcon, SaveIcon, SearchBarComponent, SearchInputComponent, SelectComponent, SelectTriggerDirective, SendIcon, SheetBodyComponent, SheetComponent, SheetHeaderComponent, SidebarInsetDirective, SlashIcon, SlidersIcon, SparklesIcon, SpinnerIcon, SquareCheckIcon, SquareIcon, SquareMinusIcon, StarIcon, SunBrightIcon, SwitchComponent, SyncIcon, TabComponent, TabListComponent, TabPanelComponent, TabsComponent, TagComponent, TagIcon, TagInputComponent, TagsIcon, TextareaComponent, TextareaControlDirective, ThumbsDownIcon, ThumbsUpIcon, ToggleComponent, ToggleGroupComponent, ToggleItemComponent, ToggleLeftIcon, ToggleRightIcon, TooltipDirective, TrashCanIcon, TrashIcon, TreeComponent, TreeItemComponent, TreeItemExpanderComponent, TreeItemGroupComponent, TriangleAlertIcon, UndoIcon, UserIcon, UserSecretIcon, VideoIcon, XMarkIcon, createCallable, injectCallRef, isApplePlatform } from "@sinequa/galactik";
5
+ import { ArrowDownIcon, ArrowDownZaIcon, ArrowLeftIcon, ArrowRightFromBracketIcon, ArrowRightIcon, ArrowUpAzIcon, ArrowUpIcon, ArrowUpRightFromSquareIcon, ArrowsMaximizeIcon, AutocompleteComponent, AvatarComponent, AvatarFallbackComponent, AvatarImageComponent, BadgeComponent, BarsIcon, BellIcon, BellSlashIcon, BookmarkIcon, BreakpointObserverService, ButtonComponent, CalendarDayIcon, CheckIcon, CheckboxComponent, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ChevronsLeftIcon, ChevronsRightIcon, CircleCheckIcon, CommentIcon, CommentsIcon, DIALOG_REF, DesktopIcon, DialogBodyComponent, DialogComponent, DialogContentComponent, DialogDescriptionComponent, DialogFooterComponent, DialogHeaderComponent, DialogTitleComponent, DropdownDirective, EditIcon, EllipsisIcon, EllipsisVerticalIcon, EyeIcon, EyeSlashIcon, FileHTMLIcon, FileIcon, FileOutputIcon, FileSpreadsheetIcon, FileTextIcon, FilterIcon, FilterXIcon, FlagEnglishIcon, FlagFrenchIcon, FlagGermanIcon, FlashlightIcon, FolderIcon, FolderOpenIcon, FrownIcon, GearIcon, GitForkIcon, GlobeIcon, GripVerticalIcon, HighlighterIcon, HistoryIcon, HomeIcon, ImageIcon, InboxIcon, InfoCircleIcon, InputComponent, InputControlDirective, InputIconRightDirective, KbdComponent, KbdGroupComponent, KeyIcon, KeyboardIcon, LanguageIcon, LightbulbIcon, LightbulbSlashIcon, LinkComponent, ListComponent, ListFilterIcon, ListItemActionComponent, ListItemAvatarDirective, ListItemComponent, ListItemLeftDirective, ListItemTextComponent, LoadingCircleIcon, LockIcon, MagnifyingGlassIcon, MagnifyingGlassMinusIcon, MagnifyingGlassPlusIcon, MenuComponent, MenuContentComponent, MenuItemComponent, MenuTriggerDirective, MoonIcon, PaletteIcon, PaperclipIcon, PenToSquareIcon, PlusIcon, PopoverDirective, PresentationIcon, QuestionCircleIcon, RadioComponent, RobotIcon, SaveIcon, SearchBarComponent, SearchIcon, SearchInputComponent, SelectComponent, SelectTriggerDirective, SendIcon, SheetBodyComponent, SheetComponent, SheetHeaderComponent, SidebarInsetDirective, SlashIcon, SlidersIcon, SortableDirective, SortableItemDirective, SparklesIcon, SpinnerIcon, SquareCheckIcon, SquareIcon, SquareMinusIcon, StarIcon, SunBrightIcon, SwitchComponent, SyncIcon, TabComponent, TabListComponent, TabPanelComponent, TabsComponent, TagComponent, TagIcon, TagInputComponent, TagsIcon, TextareaComponent, TextareaControlDirective, ThumbsDownIcon, ThumbsUpIcon, ToggleComponent, ToggleGroupComponent, ToggleItemComponent, ToggleLeftIcon, ToggleRightIcon, TooltipDirective, TrashCanIcon, TrashIcon, TreeComponent, TreeItemComponent, TreeItemExpanderComponent, TreeItemGroupComponent, TriangleAlertIcon, TruncatedDirective, UndoIcon, UserIcon, UserSecretIcon, VideoIcon, XMarkIcon, createCallable, injectCallRef, isApplePlatform } from "@sinequa/galactik";
6
6
  import { withDevtools } from "@angular-architects/ngrx-toolkit";
7
7
  import { getState, patchState, signalStore, signalStoreFeature, withComputed, withMethods, withState } from "@ngrx/signals";
8
8
  import { ActivatedRoute, NavigationEnd, Router, RouterLink, RouterModule } from "@angular/router";
@@ -18,8 +18,6 @@ import { FormField, FormRoot, applyEach, form, readonly, required, schema, submi
18
18
  import { injectVirtualizer } from "@tanstack/angular-virtual";
19
19
  import { catchError as catchError$1, filter as filter$2, map as map$1, switchMap as switchMap$1 } from "rxjs/operators";
20
20
  import { Tab } from "@angular/aria/tabs";
21
- import * as i1 from "@angular/cdk/drag-drop";
22
- import { DragDropModule } from "@angular/cdk/drag-drop";
23
21
  import { A11yModule } from "@angular/cdk/a11y";
24
22
  import { Calendar, months } from "vanilla-calendar-pro";
25
23
  import { getDate, getDateString } from "vanilla-calendar-pro/utils";
@@ -1374,9 +1372,8 @@ function withAlertsFeatures() {
1374
1372
  });
1375
1373
  },
1376
1374
  async updateAlert(alert, index) {
1377
- const alerts = store.alerts();
1378
- if (index < 0 || index >= alerts.length) return;
1379
- alerts.splice(index, 1, alert);
1375
+ if (index < 0 || index >= store.alerts().length) return;
1376
+ const alerts = store.alerts().with(index, alert);
1380
1377
  await patch({ alerts }, {
1381
1378
  type: "Alert_Update",
1382
1379
  detail: { alert: alert.name }
@@ -1390,8 +1387,8 @@ function withAlertsFeatures() {
1390
1387
  },
1391
1388
  async updateAlerts(alerts) {
1392
1389
  await patch({ alerts }, {
1393
- type: "Alert_Update",
1394
- detail: { alert: alert.name }
1390
+ type: "Alerts_Update",
1391
+ detail: { alerts: alerts.map((a) => a.name).join(", ") }
1395
1392
  });
1396
1393
  patchState(store, (state) => {
1397
1394
  return {
@@ -5745,6 +5742,86 @@ i0.ɵɵngDeclareClassMetadata({
5745
5742
  }]
5746
5743
  }] }
5747
5744
  });
5745
+ const COLOR_CLASSES = {
5746
+ yellow: "bg-(--bg-yellow-base-alt) text-(--font-yellow-muted)",
5747
+ blue: "bg-(--bg-info-base-alt) text-(--font-info-muted)",
5748
+ pink: "bg-(--bg-accent-base-alt) text-(--font-accent-muted)",
5749
+ indigo: "bg-(--bg-indigo-base-alt) text-(--font-indigo-muted)",
5750
+ green: "bg-(--bg-success-base-alt) text-(--font-success-muted)",
5751
+ orange: "bg-(--bg-warning-base-alt) text-(--font-warning-muted)"
5752
+ };
5753
+ var ListPanelEmptyComponent = class ListPanelEmptyComponent {
5754
+ color = input("blue", ...ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []);
5755
+ tileClasses = computed(() => `inline-flex size-12 shrink-0 items-center justify-center rounded-(--radius-md) [&_svg]:size-6 [&_svg]:opacity-80 ${COLOR_CLASSES[this.color()]}`, ...ngDevMode ? [{ debugName: "tileClasses" }] : /* istanbul ignore next */ []);
5756
+ static ɵfac = i0.ɵɵngDeclareFactory({
5757
+ minVersion: "12.0.0",
5758
+ version: "22.2.1",
5759
+ ngImport: i0,
5760
+ type: ListPanelEmptyComponent,
5761
+ deps: [],
5762
+ target: i0.ɵɵFactoryTarget.Component
5763
+ });
5764
+ static ɵcmp = i0.ɵɵngDeclareComponent({
5765
+ minVersion: "17.1.0",
5766
+ version: "22.2.1",
5767
+ type: ListPanelEmptyComponent,
5768
+ isStandalone: true,
5769
+ selector: "list-panel-empty, ListPanelEmpty",
5770
+ inputs: { color: {
5771
+ classPropertyName: "color",
5772
+ publicName: "color",
5773
+ isSignal: true,
5774
+ isRequired: false,
5775
+ transformFunction: null
5776
+ } },
5777
+ host: { classAttribute: "flex flex-col items-center gap-(--spacing-xs) py-(--spacing-xs) text-center [&>[listPanelEmptyHint]]:m-0 [&>[listPanelEmptyHint]]:-mt-(--spacing-2xs) [&>[listPanelEmptyHint]]:max-w-prose [&>[listPanelEmptyHint]]:text-(length:--size-sm) [&>[listPanelEmptyHint]]:leading-(--line-height-2) [&>[listPanelEmptyHint]]:text-(--font-neutral-muted)" },
5778
+ ngImport: i0,
5779
+ template: `
5780
+ <span [class]="tileClasses()" aria-hidden="true">
5781
+ <ng-content select="[listPanelEmptyIcon]" />
5782
+ </span>
5783
+ <p class="m-0 text-(length:--size-base) leading-(--line-height-3) font-semibold text-(--font-neutral-base)">
5784
+ <ng-content />
5785
+ </p>
5786
+ <ng-content select="[listPanelEmptyHint]" />
5787
+ <ng-content select="[listPanelEmptyAction]" />
5788
+ `,
5789
+ isInline: true,
5790
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
5791
+ });
5792
+ };
5793
+ i0.ɵɵngDeclareClassMetadata({
5794
+ minVersion: "12.0.0",
5795
+ version: "22.2.1",
5796
+ ngImport: i0,
5797
+ type: ListPanelEmptyComponent,
5798
+ decorators: [{
5799
+ type: Component,
5800
+ args: [{
5801
+ selector: "list-panel-empty, ListPanelEmpty",
5802
+ changeDetection: ChangeDetectionStrategy.OnPush,
5803
+ host: { class: "flex flex-col items-center gap-(--spacing-xs) py-(--spacing-xs) text-center [&>[listPanelEmptyHint]]:m-0 [&>[listPanelEmptyHint]]:-mt-(--spacing-2xs) [&>[listPanelEmptyHint]]:max-w-prose [&>[listPanelEmptyHint]]:text-(length:--size-sm) [&>[listPanelEmptyHint]]:leading-(--line-height-2) [&>[listPanelEmptyHint]]:text-(--font-neutral-muted)" },
5804
+ template: `
5805
+ <span [class]="tileClasses()" aria-hidden="true">
5806
+ <ng-content select="[listPanelEmptyIcon]" />
5807
+ </span>
5808
+ <p class="m-0 text-(length:--size-base) leading-(--line-height-3) font-semibold text-(--font-neutral-base)">
5809
+ <ng-content />
5810
+ </p>
5811
+ <ng-content select="[listPanelEmptyHint]" />
5812
+ <ng-content select="[listPanelEmptyAction]" />
5813
+ `
5814
+ }]
5815
+ }],
5816
+ propDecorators: { color: [{
5817
+ type: i0.Input,
5818
+ args: [{
5819
+ isSignal: true,
5820
+ alias: "color",
5821
+ required: false
5822
+ }]
5823
+ }] }
5824
+ });
5748
5825
  var CollectionsDialog = class CollectionsDialog {
5749
5826
  call = injectCallRef();
5750
5827
  createInputElement = viewChild("createInput", ...ngDevMode ? [{ debugName: "createInputElement" }] : /* istanbul ignore next */ []);
@@ -5920,9 +5997,21 @@ var CollectionsDialog = class CollectionsDialog {
5920
5997
  }
5921
5998
  </div>
5922
5999
  } @else {
5923
- <p class="py-4 text-center text-neutral-500">
5924
- {{ (searchText().trim() ? 'collections.noMatch' : 'collections.noCollections') | transloco }}
5925
- </p>
6000
+ <!-- One ListPanelEmpty per branch: an @if holding the icon and the text would be projected as a
6001
+ whole into the default slot, and the icon would not reach the tile. -->
6002
+ @if (searchText().trim()) {
6003
+ <ListPanelEmpty color="indigo">
6004
+ <search-icon listPanelEmptyIcon />
6005
+ {{ 'collections.noMatch' | transloco }}
6006
+ <p listPanelEmptyHint>{{ 'collections.noMatchHint' | transloco }}</p>
6007
+ </ListPanelEmpty>
6008
+ } @else {
6009
+ <ListPanelEmpty color="indigo">
6010
+ <inbox-icon listPanelEmptyIcon />
6011
+ {{ 'collections.noCollections' | transloco }}
6012
+ <p listPanelEmptyHint>{{ 'collections.noCollectionsDialogHint' | transloco }}</p>
6013
+ </ListPanelEmpty>
6014
+ }
5926
6015
  }
5927
6016
  </div>
5928
6017
  } @else {
@@ -6059,6 +6148,24 @@ var CollectionsDialog = class CollectionsDialog {
6059
6148
  ],
6060
6149
  outputs: ["checkedChange"]
6061
6150
  },
6151
+ {
6152
+ kind: "component",
6153
+ type: ListPanelEmptyComponent,
6154
+ selector: "list-panel-empty, ListPanelEmpty",
6155
+ inputs: ["color"]
6156
+ },
6157
+ {
6158
+ kind: "component",
6159
+ type: InboxIcon,
6160
+ selector: "inbox, inbox-icon, Inbox, InboxIcon",
6161
+ inputs: ["class"]
6162
+ },
6163
+ {
6164
+ kind: "component",
6165
+ type: SearchIcon,
6166
+ selector: "search, search-icon, Search, SearchIcon",
6167
+ inputs: ["class"]
6168
+ },
6062
6169
  {
6063
6170
  kind: "pipe",
6064
6171
  type: TranslocoPipe,
@@ -6088,7 +6195,10 @@ i0.ɵɵngDeclareClassMetadata({
6088
6195
  DialogFooterComponent,
6089
6196
  InputComponent,
6090
6197
  InputControlDirective,
6091
- CheckboxComponent
6198
+ CheckboxComponent,
6199
+ ListPanelEmptyComponent,
6200
+ InboxIcon,
6201
+ SearchIcon
6092
6202
  ],
6093
6203
  providers: [provideTranslocoScope("collections")],
6094
6204
  template: `
@@ -6137,9 +6247,21 @@ i0.ɵɵngDeclareClassMetadata({
6137
6247
  }
6138
6248
  </div>
6139
6249
  } @else {
6140
- <p class="py-4 text-center text-neutral-500">
6141
- {{ (searchText().trim() ? 'collections.noMatch' : 'collections.noCollections') | transloco }}
6142
- </p>
6250
+ <!-- One ListPanelEmpty per branch: an @if holding the icon and the text would be projected as a
6251
+ whole into the default slot, and the icon would not reach the tile. -->
6252
+ @if (searchText().trim()) {
6253
+ <ListPanelEmpty color="indigo">
6254
+ <search-icon listPanelEmptyIcon />
6255
+ {{ 'collections.noMatch' | transloco }}
6256
+ <p listPanelEmptyHint>{{ 'collections.noMatchHint' | transloco }}</p>
6257
+ </ListPanelEmpty>
6258
+ } @else {
6259
+ <ListPanelEmpty color="indigo">
6260
+ <inbox-icon listPanelEmptyIcon />
6261
+ {{ 'collections.noCollections' | transloco }}
6262
+ <p listPanelEmptyHint>{{ 'collections.noCollectionsDialogHint' | transloco }}</p>
6263
+ </ListPanelEmpty>
6264
+ }
6143
6265
  }
6144
6266
  </div>
6145
6267
  } @else {
@@ -16096,6 +16218,169 @@ var Alert;
16096
16218
  })(Alert.Days || (Alert.Days = {}));
16097
16219
  })(Alert || (Alert = {}));
16098
16220
  const COMPONENTS_FOR_DOCUMENT_TYPE = new InjectionToken("COMPONENTS_FOR_DOCUMENT_TYPE");
16221
+ const WEEKDAY_FLAGS = [
16222
+ Alert.Days.Monday,
16223
+ Alert.Days.Tuesday,
16224
+ Alert.Days.Wednesday,
16225
+ Alert.Days.Thursday,
16226
+ Alert.Days.Friday,
16227
+ Alert.Days.Saturday,
16228
+ Alert.Days.Sunday
16229
+ ];
16230
+ const TIMED_FREQUENCIES = [
16231
+ Alert.Frequency.Daily,
16232
+ Alert.Frequency.Weekly,
16233
+ Alert.Frequency.Monthly
16234
+ ];
16235
+ function weekdayNames(lang) {
16236
+ const format = new Intl.DateTimeFormat(lang, { weekday: "short" });
16237
+ return WEEKDAY_FLAGS.map((_, offset) => format.format(new Date(2024, 0, 1 + offset)));
16238
+ }
16239
+ function formatDays(days, lang) {
16240
+ const names = weekdayNames(lang);
16241
+ const selected = WEEKDAY_FLAGS.map((flag) => (days & flag) !== 0);
16242
+ const parts = [];
16243
+ let start = 0;
16244
+ while (start < selected.length) {
16245
+ if (!selected[start]) {
16246
+ start++;
16247
+ continue;
16248
+ }
16249
+ let end = start;
16250
+ while (end + 1 < selected.length && selected[end + 1]) end++;
16251
+ if (end - start >= 2) parts.push(`${names[start]}–${names[end]}`);
16252
+ else for (let i = start; i <= end; i++) parts.push(names[i]);
16253
+ start = end + 1;
16254
+ }
16255
+ return parts.join(", ");
16256
+ }
16257
+ function alertSummary(lang, alert) {
16258
+ const parts = [];
16259
+ if (!alert.active) parts.push({ key: "alerts.paused" });
16260
+ if (alert.frequency !== void 0 && Alert.Frequency[alert.frequency] !== void 0) parts.push({ key: `alerts.frequency.${Alert.Frequency[alert.frequency]}` });
16261
+ if (alert.times && TIMED_FREQUENCIES.includes(alert.frequency)) parts.push({ text: alert.times });
16262
+ const days = alert.days ?? Alert.Days.None;
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
+ return parts;
16265
+ }
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
+ }
16282
+ function saveErrorDetail(error) {
16283
+ const { errorMessage, message } = error ?? {};
16284
+ return errorMessage ?? message;
16285
+ }
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
16295
+ });
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
+ });
16099
16384
  const WEEKDAYS = [
16100
16385
  {
16101
16386
  key: "monday",
@@ -16141,6 +16426,15 @@ function emptyAlertModel() {
16141
16426
  active: true
16142
16427
  };
16143
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
+ }
16144
16438
  const alertSchema = schema((p) => {
16145
16439
  required(p.name);
16146
16440
  validate(p.name, ({ value }) => value().length > 0 && !value().trim() ? { kind: "required" } : void 0);
@@ -16148,13 +16442,17 @@ const alertSchema = schema((p) => {
16148
16442
  required(p.time);
16149
16443
  validate(p.days, ({ value, valueOf }) => valueOf(p.frequency) === Alert.Frequency.Immediate || Object.values(value()).some(Boolean) ? void 0 : { kind: "noDaySelected" });
16150
16444
  });
16151
- var AlertDialog = class AlertDialog {
16152
- call = injectCallRef();
16445
+ let nextFormId = 0;
16446
+ var AlertFormComponent = class AlertFormComponent {
16153
16447
  queryService = inject(QueryService);
16154
16448
  queryParamsStore = inject(QueryParamsStore);
16155
16449
  userSettingsStore = inject(UserSettingsStore);
16156
16450
  transloco = inject(TranslocoService);
16157
- 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++}`;
16158
16456
  fieldClass = FORM_FIELD_CLASS;
16159
16457
  labelClass = FORM_LABEL_CLASS;
16160
16458
  errorClass = FORM_ERROR_CLASS;
@@ -16164,43 +16462,48 @@ var AlertDialog = class AlertDialog {
16164
16462
  Alert.Frequency.Immediate
16165
16463
  ];
16166
16464
  frequency = Alert.Frequency;
16465
+ Days = Alert.Days;
16466
+ querySummary = querySummary;
16167
16467
  frequencyLabel = (value) => this.transloco.translate(`alerts.frequency.${Alert.Frequency[value]}`);
16168
16468
  weekdays = WEEKDAYS;
16169
- index = signal(void 0, ...ngDevMode ? [{ debugName: "index" }] : /* istanbul ignore next */ []);
16170
- alert = signal(void 0, ...ngDevMode ? [{ debugName: "alert" }] : /* istanbul ignore next */ []);
16171
- 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 */ []);
16172
16479
  updateStatus = signal("idle", ...ngDevMode ? [{ debugName: "updateStatus" }] : /* istanbul ignore next */ []);
16173
- alertModel = signal(emptyAlertModel(), ...ngDevMode ? [{ debugName: "alertModel" }] : /* istanbul ignore next */ []);
16480
+ saveError = signal(void 0, ...ngDevMode ? [{ debugName: "saveError" }] : /* istanbul ignore next */ []);
16481
+ alertModel = linkedSignal(() => {
16482
+ const alert = this.alert();
16483
+ return alert ? modelOf(alert) : emptyAlertModel();
16484
+ }, ...ngDevMode ? [{ debugName: "alertModel" }] : /* istanbul ignore next */ []);
16174
16485
  alertForm = form(this.alertModel, alertSchema, {
16175
16486
  name: "alert",
16176
16487
  submission: { action: async () => this.persist() }
16177
16488
  });
16489
+ saving = computed(() => this.alertForm().submitting(), ...ngDevMode ? [{ debugName: "saving" }] : /* istanbul ignore next */ []);
16178
16490
  showDaysError = computed(() => fieldShowsError(this.alertForm.days), ...ngDevMode ? [{ debugName: "showDaysError" }] : /* istanbul ignore next */ []);
16179
- constructor() {
16180
- afterNextRender(() => this.init(this.call.props()));
16181
- }
16182
- async init(index) {
16183
- this.index.set(index);
16184
- const alert = index !== void 0 ? this.userSettingsStore.alerts()[index] : void 0;
16185
- this.alert.set(alert);
16186
- if (alert) {
16187
- this.alertModel.set({
16188
- name: alert.name,
16189
- frequency: alert.frequency,
16190
- days: weekdaysFromFlags(alert.days),
16191
- time: alert.times,
16192
- active: alert.active
16193
- });
16194
- const q = this.queryParamsStore.getQuery();
16195
- const response = await firstValueFrom(this.queryService.search(q, false));
16196
- this.canUpdateQuery.set(response.records?.length > 0);
16197
- } else this.alertModel.set(emptyAlertModel());
16198
- this.dialog()?.open();
16491
+ setDays(mask) {
16492
+ this.alertModel.update((model) => ({
16493
+ ...model,
16494
+ days: weekdaysFromFlags(mask)
16495
+ }));
16199
16496
  }
16200
16497
  async persist() {
16201
- if (this.alert()) await this.update();
16202
- else await this.create();
16203
- this.dialog()?.close("dialog-confirm");
16498
+ this.saveError.set(void 0);
16499
+ try {
16500
+ if (this.alert()) await this.update();
16501
+ else await this.create();
16502
+ } catch (error) {
16503
+ this.saveError.set({ detail: saveErrorDetail(error) });
16504
+ return;
16505
+ }
16506
+ this.saved.emit();
16204
16507
  }
16205
16508
  async create() {
16206
16509
  const model = this.alertModel();
@@ -16218,19 +16521,21 @@ var AlertDialog = class AlertDialog {
16218
16521
  combine: true,
16219
16522
  respectTabSelection: false
16220
16523
  };
16221
- this.userSettingsStore.createAlert(alert);
16524
+ await this.userSettingsStore.createAlert(alert);
16222
16525
  }
16223
16526
  async update() {
16224
16527
  const alert = this.alert();
16225
16528
  const index = this.index();
16226
16529
  if (!alert || index === void 0) return;
16227
16530
  const model = this.alertModel();
16228
- alert.name = model.name.trim();
16229
- alert.frequency = model.frequency ?? Alert.Frequency.Daily;
16230
- alert.days = flagsFromWeekdays(model.days);
16231
- alert.times = model.time;
16232
- alert.active = model.active;
16233
- this.userSettingsStore.updateAlert(alert, index);
16531
+ await this.userSettingsStore.updateAlert({
16532
+ ...alert,
16533
+ name: model.name.trim(),
16534
+ frequency: model.frequency ?? Alert.Frequency.Daily,
16535
+ days: flagsFromWeekdays(model.days),
16536
+ times: model.time,
16537
+ active: model.active
16538
+ }, index);
16234
16539
  }
16235
16540
  showError(field) {
16236
16541
  return fieldShowsError(field);
@@ -16242,9 +16547,12 @@ var AlertDialog = class AlertDialog {
16242
16547
  const alert = this.alert();
16243
16548
  const index = this.index();
16244
16549
  if (!alert || index === void 0) return;
16550
+ this.saveError.set(void 0);
16245
16551
  this.updateStatus.set("updating");
16246
- alert.query = this.queryParamsStore.getQuery();
16247
- this.userSettingsStore.updateAlert(alert, index).then(() => {
16552
+ this.userSettingsStore.updateAlert({
16553
+ ...alert,
16554
+ query: this.queryParamsStore.getQuery()
16555
+ }, index).then(() => {
16248
16556
  setTimeout(() => {
16249
16557
  this.updateStatus.set("updated");
16250
16558
  }, 500);
@@ -16252,6 +16560,9 @@ var AlertDialog = class AlertDialog {
16252
16560
  setTimeout(() => {
16253
16561
  this.updateStatus.set("idle");
16254
16562
  }, 2e3);
16563
+ }).catch((error) => {
16564
+ this.updateStatus.set("idle");
16565
+ this.saveError.set({ detail: saveErrorDetail(error) });
16255
16566
  });
16256
16567
  }
16257
16568
  execute() {
@@ -16267,190 +16578,197 @@ var AlertDialog = class AlertDialog {
16267
16578
  filters,
16268
16579
  name: q.name
16269
16580
  });
16270
- this.dialog()?.close();
16581
+ this.executed.emit();
16271
16582
  }
16272
16583
  static ɵfac = i0.ɵɵngDeclareFactory({
16273
16584
  minVersion: "12.0.0",
16274
16585
  version: "22.2.1",
16275
16586
  ngImport: i0,
16276
- type: AlertDialog,
16587
+ type: AlertFormComponent,
16277
16588
  deps: [],
16278
16589
  target: i0.ɵɵFactoryTarget.Component
16279
16590
  });
16280
16591
  static ɵcmp = i0.ɵɵngDeclareComponent({
16281
16592
  minVersion: "17.0.0",
16282
16593
  version: "22.2.1",
16283
- type: AlertDialog,
16594
+ type: AlertFormComponent,
16284
16595
  isStandalone: true,
16285
- 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" },
16286
16609
  providers: [provideTranslocoScope("alerts")],
16287
- viewQueries: [{
16288
- propertyName: "dialog",
16289
- first: true,
16290
- predicate: DialogComponent,
16291
- descendants: true,
16292
- isSignal: true
16293
- }],
16294
16610
  ngImport: i0,
16295
16611
  template: `
16296
- <dialog #dialog (closed)="call.end($event)">
16297
- <DialogContent size="md">
16298
- <DialogHeader [closeLabel]="'alerts.close' | transloco">
16299
- <DialogTitle>{{ 'alerts.createAlert' | transloco }}</DialogTitle>
16300
- </DialogHeader>
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>
16301
16639
 
16302
- <!--
16303
- [formRoot] poses novalidate, intercepts submit, calls preventDefault() and triggers submit()
16304
- on the field tree: Enter submits natively, and the action only runs on a valid form.
16305
- class="contents" keeps the DialogContent flex column intact — the body/footer slots are
16306
- styled by descendant selectors, which a display: contents wrapper does not break.
16307
- -->
16308
- <form [formRoot]="alertForm" class="contents" [attr.aria-label]="'alerts.createAlert' | transloco">
16309
- <DialogBody class="gap-(--spacing-md)">
16310
- <div [class]="fieldClass">
16311
- <label for="alertName" [class]="labelClass">{{ 'alerts.alertName' | transloco }}</label>
16312
- <input-group [scheme]="showError(alertForm.name) ? 'error' : 'default'">
16313
- <input
16314
- input-control
16315
- id="alertName"
16316
- type="text"
16317
- autocomplete="off"
16318
- spellcheck="false"
16319
- [formField]="alertForm.name"
16320
- [attr.placeholder]="'alerts.placeholder' | transloco"
16321
- [attr.aria-invalid]="showError(alertForm.name) ? 'true' : null"
16322
- [attr.aria-describedby]="showError(alertForm.name) ? 'alertName-error' : null" />
16323
- </input-group>
16324
- @if (showError(alertForm.name)) {
16325
- <p id="alertName-error" role="alert" [class]="errorClass">{{ errorKey(alertForm.name) | transloco }}</p>
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 }}
16651
+ }
16652
+ </span>
16653
+ <chevron-down-icon class="select-chevron" />
16654
+ </button>
16655
+
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" />
16326
16662
  }
16327
- </div>
16663
+ </ng-template>
16664
+ </SelectMenu>
16665
+ </div>
16328
16666
 
16329
- @let selectedFrequency = alertForm.frequency().value();
16330
- <div [class]="fieldClass">
16331
- <label [class]="labelClass" for="alertFrequency">{{ 'alerts.alertFrequency' | transloco }}</label>
16332
- <SelectMenu
16333
- class="contents"
16334
- [options]="frequencies"
16335
- [formField]="alertForm.frequency"
16336
- [label]="frequencyLabel"
16337
- size="md">
16338
- <button selectTrigger type="button" id="alertFrequency" class="select-trigger">
16339
- <span class="select-trigger-label" [class.select-placeholder]="selectedFrequency === null">
16340
- @if (selectedFrequency === null) {
16341
- {{ 'alerts.selectFrequency' | transloco }}
16342
- } @else {
16343
- {{ 'alerts.frequency.' + frequency[selectedFrequency] | transloco }}
16344
- }
16345
- </span>
16346
- <chevron-down-icon class="select-chevron" />
16347
- </button>
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>
16348
16685
 
16349
- <ng-template #selectOption let-frequencyValue let-selected="selected">
16350
- <span class="select-option-body">
16351
- <span class="select-option-label">{{ 'alerts.frequency.' + frequency[frequencyValue] | transloco }}</span>
16352
- </span>
16353
- @if (selected) {
16354
- <check-icon class="select-option-check" />
16355
- }
16356
- </ng-template>
16357
- </SelectMenu>
16358
- </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>
16713
+ }
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
+ }
16359
16722
 
16360
- <!-- A group of related checkboxes is a fieldset: its legend is what names the group for
16361
- assistive technologies. It used to be a <p>, which names nothing. -->
16362
- <fieldset
16363
- class="m-0 border-0 p-0"
16364
- [attr.aria-invalid]="showDaysError() ? 'true' : null"
16365
- [attr.aria-describedby]="showDaysError() ? 'alertDays-error' : null">
16366
- <legend [class]="labelClass + ' mb-(--spacing-2xs)'">{{ 'alerts.repeatOn' | transloco }}</legend>
16367
- <div class="weekdays-grid gap-1.5 px-2.5 py-0">
16368
- @for (day of weekdays; track day.key) {
16369
- <div>
16370
- <checkbox [formField]="alertForm.days[day.key]">
16371
- {{ 'alerts.weekdays.' + day.key | transloco }}
16372
- </checkbox>
16373
- </div>
16374
- }
16375
- </div>
16376
- @if (showDaysError()) {
16377
- <p id="alertDays-error" role="alert" [class]="errorClass + ' mt-(--spacing-2xs)'">
16378
- {{ errorKey(alertForm.days) | transloco }}
16379
- </p>
16380
- }
16381
- </fieldset>
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>
16382
16730
 
16383
- <div [class]="fieldClass">
16384
- <label [class]="labelClass" for="alertTimes">{{ 'alerts.alertHours' | transloco }}</label>
16385
- <input-group [scheme]="showError(alertForm.time) ? 'error' : 'default'">
16386
- <input
16387
- input-control
16388
- type="time"
16389
- id="alertTimes"
16390
- autocomplete="off"
16391
- spellcheck="false"
16392
- [formField]="alertForm.time"
16393
- [attr.aria-invalid]="showError(alertForm.time) ? 'true' : null"
16394
- [attr.aria-describedby]="showError(alertForm.time) ? 'alertTimes-error' : null" />
16395
- </input-group>
16396
- @if (showError(alertForm.time)) {
16397
- <p id="alertTimes-error" role="alert" [class]="errorClass">{{ errorKey(alertForm.time) | transloco }}</p>
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 }}
16398
16738
  }
16399
- </div>
16400
-
16401
- <div>
16402
- <checkbox [formField]="alertForm.active">{{ 'alerts.alertActive' | transloco }}</checkbox>
16403
- </div>
16404
- </DialogBody>
16405
-
16406
- <DialogFooter class="flex-col">
16407
- @if (alert() || canUpdateQuery()) {
16408
- <div class="flex w-full flex-col gap-2">
16409
- @if (alert()) {
16410
- <button type="button" variant="secondary" (click)="execute()" [disabled]="!canUpdateQuery()">
16411
- {{ 'alerts.execute' | transloco }}
16412
- </button>
16413
- }
16414
- @if (canUpdateQuery()) {
16415
- <button type="button" [disabled]="updateStatus() !== 'idle'" variant="tertiary" (click)="updateQuery()">
16416
- @switch (updateStatus()) {
16417
- @case ('updating') {
16418
- <LoadingCircle class="size-4 animate-spin" width="16" height="16" />
16419
- {{ 'alerts.updateQuery' | transloco }}
16420
- }
16421
- @case ('updated') {
16422
- <CircleCheck width="16" height="16" />
16423
- {{ 'alerts.queryUpdated' | transloco }}
16424
- }
16425
- @default {
16426
- {{ 'alerts.updateQuery' | transloco }}
16427
- }
16428
- }
16429
- </button>
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
+ }
16430
16759
  }
16431
- </div>
16432
- }
16433
- <div class="ml-auto flex justify-end gap-2">
16434
- <button type="button" variant="secondary" (click)="dialog.cancel()">
16435
- {{ 'cancel' | transloco }}
16436
- </button>
16437
- <!--
16438
- Deliberately not disabled on an invalid form: a disabled submit button never fires
16439
- submit(), which is what marks every field as touched and reveals the messages. The
16440
- weekday group has no other way to become touched — the galactik Checkbox emits no
16441
- touch output — so the "pick a day" error would be unreachable.
16442
- -->
16443
- <button type="submit">
16444
- {{ 'confirm' | transloco }}
16445
16760
  </button>
16446
- </div>
16447
- </DialogFooter>
16448
- </form>
16449
- </DialogContent>
16450
- </dialog>
16761
+ }
16762
+ </div>
16763
+ </section>
16764
+ }
16765
+
16766
+ @if (saveError(); as failure) {
16767
+ <alert-save-error [detail]="failure.detail" />
16768
+ }
16769
+ </form>
16451
16770
  `,
16452
16771
  isInline: true,
16453
- styles: [".weekdays-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(100px,1fr))}\n"],
16454
16772
  dependencies: [
16455
16773
  {
16456
16774
  kind: "directive",
@@ -16477,62 +16795,6 @@ var AlertDialog = class AlertDialog {
16477
16795
  "size"
16478
16796
  ]
16479
16797
  },
16480
- {
16481
- kind: "component",
16482
- type: CheckboxComponent,
16483
- selector: "checkbox, Checkbox",
16484
- inputs: [
16485
- "checked",
16486
- "disabled",
16487
- "indeterminate",
16488
- "hint",
16489
- "class",
16490
- "aria-label"
16491
- ],
16492
- outputs: ["checkedChange"]
16493
- },
16494
- {
16495
- kind: "component",
16496
- type: DialogComponent,
16497
- selector: "dialog, [dialog]",
16498
- inputs: ["closeLabel"],
16499
- outputs: ["closed"],
16500
- exportAs: ["dialog"]
16501
- },
16502
- {
16503
- kind: "component",
16504
- type: DialogHeaderComponent,
16505
- selector: "DialogHeader",
16506
- inputs: ["closeLabel", "class"]
16507
- },
16508
- {
16509
- kind: "directive",
16510
- type: DialogTitleComponent,
16511
- selector: "DialogTitle",
16512
- inputs: ["id", "class"]
16513
- },
16514
- {
16515
- kind: "directive",
16516
- type: DialogContentComponent,
16517
- selector: "DialogContent",
16518
- inputs: [
16519
- "size",
16520
- "labelledby",
16521
- "class"
16522
- ]
16523
- },
16524
- {
16525
- kind: "directive",
16526
- type: DialogBodyComponent,
16527
- selector: "DialogBody",
16528
- inputs: ["class"]
16529
- },
16530
- {
16531
- kind: "directive",
16532
- type: DialogFooterComponent,
16533
- selector: "DialogFooter",
16534
- inputs: ["class"]
16535
- },
16536
16798
  {
16537
16799
  kind: "directive",
16538
16800
  type: InputComponent,
@@ -16586,6 +16848,23 @@ var AlertDialog = class AlertDialog {
16586
16848
  selector: "[selectTrigger]",
16587
16849
  exportAs: ["selectTrigger"]
16588
16850
  },
16851
+ {
16852
+ kind: "component",
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
+ },
16589
16868
  {
16590
16869
  kind: "component",
16591
16870
  type: ChevronDownIcon,
@@ -16610,6 +16889,12 @@ var AlertDialog = class AlertDialog {
16610
16889
  selector: "circle-check, circle-check-icon, CircleCheck, CircleCheckIcon",
16611
16890
  inputs: ["class"]
16612
16891
  },
16892
+ {
16893
+ kind: "component",
16894
+ type: AlertSaveErrorComponent,
16895
+ selector: "alert-save-error",
16896
+ inputs: ["detail"]
16897
+ },
16613
16898
  {
16614
16899
  kind: "pipe",
16615
16900
  type: TranslocoPipe,
@@ -16622,191 +16907,388 @@ i0.ɵɵngDeclareClassMetadata({
16622
16907
  minVersion: "12.0.0",
16623
16908
  version: "22.2.1",
16624
16909
  ngImport: i0,
16625
- type: AlertDialog,
16910
+ type: AlertFormComponent,
16626
16911
  decorators: [{
16627
16912
  type: Component,
16628
16913
  args: [{
16629
- selector: "alert-dialog, alertdialog, AlertDialog",
16914
+ selector: "alert-form, alertform, AlertFormComponent",
16630
16915
  imports: [
16631
16916
  FormRoot,
16632
16917
  FormField,
16633
16918
  TranslocoPipe,
16634
16919
  ButtonComponent,
16635
- CheckboxComponent,
16636
- DialogComponent,
16637
- DialogHeaderComponent,
16638
- DialogTitleComponent,
16639
- DialogContentComponent,
16640
- DialogBodyComponent,
16641
- DialogFooterComponent,
16642
16920
  InputComponent,
16643
16921
  InputControlDirective,
16644
16922
  SelectComponent,
16645
16923
  SelectTriggerDirective,
16924
+ SwitchComponent,
16646
16925
  ChevronDownIcon,
16647
16926
  CheckIcon,
16648
16927
  LoadingCircleIcon,
16649
- CircleCheckIcon
16928
+ CircleCheckIcon,
16929
+ AlertSaveErrorComponent
16650
16930
  ],
16651
16931
  providers: [provideTranslocoScope("alerts")],
16932
+ host: { class: "block @container" },
16652
16933
  template: `
16653
- <dialog #dialog (closed)="call.end($event)">
16654
- <DialogContent size="md">
16655
- <DialogHeader [closeLabel]="'alerts.close' | transloco">
16656
- <DialogTitle>{{ 'alerts.createAlert' | transloco }}</DialogTitle>
16657
- </DialogHeader>
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>
16658
16961
 
16659
- <!--
16660
- [formRoot] poses novalidate, intercepts submit, calls preventDefault() and triggers submit()
16661
- on the field tree: Enter submits natively, and the action only runs on a valid form.
16662
- class="contents" keeps the DialogContent flex column intact — the body/footer slots are
16663
- styled by descendant selectors, which a display: contents wrapper does not break.
16664
- -->
16665
- <form [formRoot]="alertForm" class="contents" [attr.aria-label]="'alerts.createAlert' | transloco">
16666
- <DialogBody class="gap-(--spacing-md)">
16667
- <div [class]="fieldClass">
16668
- <label for="alertName" [class]="labelClass">{{ 'alerts.alertName' | transloco }}</label>
16669
- <input-group [scheme]="showError(alertForm.name) ? 'error' : 'default'">
16670
- <input
16671
- input-control
16672
- id="alertName"
16673
- type="text"
16674
- autocomplete="off"
16675
- spellcheck="false"
16676
- [formField]="alertForm.name"
16677
- [attr.placeholder]="'alerts.placeholder' | transloco"
16678
- [attr.aria-invalid]="showError(alertForm.name) ? 'true' : null"
16679
- [attr.aria-describedby]="showError(alertForm.name) ? 'alertName-error' : null" />
16680
- </input-group>
16681
- @if (showError(alertForm.name)) {
16682
- <p id="alertName-error" role="alert" [class]="errorClass">{{ errorKey(alertForm.name) | transloco }}</p>
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" />
16683
16984
  }
16684
- </div>
16985
+ </ng-template>
16986
+ </SelectMenu>
16987
+ </div>
16685
16988
 
16686
- @let selectedFrequency = alertForm.frequency().value();
16687
- <div [class]="fieldClass">
16688
- <label [class]="labelClass" for="alertFrequency">{{ 'alerts.alertFrequency' | transloco }}</label>
16689
- <SelectMenu
16690
- class="contents"
16691
- [options]="frequencies"
16692
- [formField]="alertForm.frequency"
16693
- [label]="frequencyLabel"
16694
- size="md">
16695
- <button selectTrigger type="button" id="alertFrequency" class="select-trigger">
16696
- <span class="select-trigger-label" [class.select-placeholder]="selectedFrequency === null">
16697
- @if (selectedFrequency === null) {
16698
- {{ 'alerts.selectFrequency' | transloco }}
16699
- } @else {
16700
- {{ 'alerts.frequency.' + frequency[selectedFrequency] | transloco }}
16701
- }
16702
- </span>
16703
- <chevron-down-icon class="select-chevron" />
16704
- </button>
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>
16705
17007
 
16706
- <ng-template #selectOption let-frequencyValue let-selected="selected">
16707
- <span class="select-option-body">
16708
- <span class="select-option-label">{{ 'alerts.frequency.' + frequency[frequencyValue] | transloco }}</span>
16709
- </span>
16710
- @if (selected) {
16711
- <check-icon class="select-option-check" />
16712
- }
16713
- </ng-template>
16714
- </SelectMenu>
16715
- </div>
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
+ }
16716
17044
 
16717
- <!-- A group of related checkboxes is a fieldset: its legend is what names the group for
16718
- assistive technologies. It used to be a <p>, which names nothing. -->
16719
- <fieldset
16720
- class="m-0 border-0 p-0"
16721
- [attr.aria-invalid]="showDaysError() ? 'true' : null"
16722
- [attr.aria-describedby]="showDaysError() ? 'alertDays-error' : null">
16723
- <legend [class]="labelClass + ' mb-(--spacing-2xs)'">{{ 'alerts.repeatOn' | transloco }}</legend>
16724
- <div class="weekdays-grid gap-1.5 px-2.5 py-0">
16725
- @for (day of weekdays; track day.key) {
16726
- <div>
16727
- <checkbox [formField]="alertForm.days[day.key]">
16728
- {{ 'alerts.weekdays.' + day.key | transloco }}
16729
- </checkbox>
16730
- </div>
16731
- }
16732
- </div>
16733
- @if (showDaysError()) {
16734
- <p id="alertDays-error" role="alert" [class]="errorClass + ' mt-(--spacing-2xs)'">
16735
- {{ errorKey(alertForm.days) | transloco }}
16736
- </p>
16737
- }
16738
- </fieldset>
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>
16739
17052
 
16740
- <div [class]="fieldClass">
16741
- <label [class]="labelClass" for="alertTimes">{{ 'alerts.alertHours' | transloco }}</label>
16742
- <input-group [scheme]="showError(alertForm.time) ? 'error' : 'default'">
16743
- <input
16744
- input-control
16745
- type="time"
16746
- id="alertTimes"
16747
- autocomplete="off"
16748
- spellcheck="false"
16749
- [formField]="alertForm.time"
16750
- [attr.aria-invalid]="showError(alertForm.time) ? 'true' : null"
16751
- [attr.aria-describedby]="showError(alertForm.time) ? 'alertTimes-error' : null" />
16752
- </input-group>
16753
- @if (showError(alertForm.time)) {
16754
- <p id="alertTimes-error" role="alert" [class]="errorClass">{{ errorKey(alertForm.time) | transloco }}</p>
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 }}
16755
17060
  }
16756
- </div>
16757
-
16758
- <div>
16759
- <checkbox [formField]="alertForm.active">{{ 'alerts.alertActive' | transloco }}</checkbox>
16760
- </div>
16761
- </DialogBody>
16762
-
16763
- <DialogFooter class="flex-col">
16764
- @if (alert() || canUpdateQuery()) {
16765
- <div class="flex w-full flex-col gap-2">
16766
- @if (alert()) {
16767
- <button type="button" variant="secondary" (click)="execute()" [disabled]="!canUpdateQuery()">
16768
- {{ 'alerts.execute' | transloco }}
16769
- </button>
16770
- }
16771
- @if (canUpdateQuery()) {
16772
- <button type="button" [disabled]="updateStatus() !== 'idle'" variant="tertiary" (click)="updateQuery()">
16773
- @switch (updateStatus()) {
16774
- @case ('updating') {
16775
- <LoadingCircle class="size-4 animate-spin" width="16" height="16" />
16776
- {{ 'alerts.updateQuery' | transloco }}
16777
- }
16778
- @case ('updated') {
16779
- <CircleCheck width="16" height="16" />
16780
- {{ 'alerts.queryUpdated' | transloco }}
16781
- }
16782
- @default {
16783
- {{ 'alerts.updateQuery' | transloco }}
16784
- }
16785
- }
16786
- </button>
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
+ }
16787
17081
  }
16788
- </div>
16789
- }
16790
- <div class="ml-auto flex justify-end gap-2">
16791
- <button type="button" variant="secondary" (click)="dialog.cancel()">
16792
- {{ 'cancel' | transloco }}
16793
- </button>
16794
- <!--
16795
- Deliberately not disabled on an invalid form: a disabled submit button never fires
16796
- submit(), which is what marks every field as touched and reveals the messages. The
16797
- weekday group has no other way to become touched — the galactik Checkbox emits no
16798
- touch output — so the "pick a day" error would be unreachable.
16799
- -->
16800
- <button type="submit">
16801
- {{ 'confirm' | transloco }}
16802
17082
  </button>
16803
- </div>
16804
- </DialogFooter>
16805
- </form>
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>
16806
17169
  </DialogContent>
16807
17170
  </dialog>
16808
17171
  `,
16809
- styles: [".weekdays-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(100px,1fr))}\n"]
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"]
17199
+ },
17200
+ {
17201
+ kind: "directive",
17202
+ type: DialogTitleComponent,
17203
+ selector: "DialogTitle",
17204
+ inputs: ["id", "class"]
17205
+ },
17206
+ {
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",
17220
+ inputs: ["class"]
17221
+ },
17222
+ {
17223
+ kind: "directive",
17224
+ type: DialogFooterComponent,
17225
+ selector: "DialogFooter",
17226
+ inputs: ["class"]
17227
+ },
17228
+ {
17229
+ kind: "component",
17230
+ type: AlertFormComponent,
17231
+ selector: "alert-form, alertform, AlertFormComponent",
17232
+ inputs: ["index"],
17233
+ outputs: ["saved", "executed"]
17234
+ },
17235
+ {
17236
+ kind: "pipe",
17237
+ type: TranslocoPipe,
17238
+ name: "transloco"
17239
+ }
17240
+ ]
17241
+ });
17242
+ };
17243
+ i0.ɵɵngDeclareClassMetadata({
17244
+ minVersion: "12.0.0",
17245
+ version: "22.2.1",
17246
+ ngImport: i0,
17247
+ type: AlertDialog,
17248
+ decorators: [{
17249
+ type: Component,
17250
+ args: [{
17251
+ selector: "alert-dialog, alertdialog, AlertDialog",
17252
+ imports: [
17253
+ TranslocoPipe,
17254
+ ButtonComponent,
17255
+ DialogComponent,
17256
+ DialogHeaderComponent,
17257
+ DialogTitleComponent,
17258
+ DialogContentComponent,
17259
+ DialogBodyComponent,
17260
+ DialogFooterComponent,
17261
+ AlertFormComponent
17262
+ ],
17263
+ providers: [provideTranslocoScope("alerts")],
17264
+ template: `
17265
+ <dialog #dialog (closed)="call.end($event)">
17266
+ <DialogContent size="md">
17267
+ <DialogHeader [closeLabel]="'alerts.close' | transloco">
17268
+ <DialogTitle>{{ (index() === undefined ? 'alerts.createAlert' : 'alerts.editAlert') | transloco }}</DialogTitle>
17269
+ </DialogHeader>
17270
+
17271
+ <DialogBody>
17272
+ <alert-form #form [index]="index()" (saved)="dialog.close('dialog-confirm')" (executed)="dialog.close()" />
17273
+ </DialogBody>
17274
+
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>
17289
+ </DialogContent>
17290
+ </dialog>
17291
+ `
16810
17292
  }]
16811
17293
  }],
16812
17294
  ctorParameters: () => [],
@@ -16816,6 +17298,7 @@ i0.ɵɵngDeclareClassMetadata({
16816
17298
  }] }
16817
17299
  });
16818
17300
  const AlertForm = createCallable(AlertDialog);
17301
+ const UNDO_DELAY = 6e3;
16819
17302
  var AlertsComponent = class AlertsComponent {
16820
17303
  floating = inject(PopoverDirective, {
16821
17304
  skipSelf: true,
@@ -16823,37 +17306,133 @@ var AlertsComponent = class AlertsComponent {
16823
17306
  });
16824
17307
  userSettingsStore = inject(UserSettingsStore);
16825
17308
  injector = inject(Injector);
16826
- reordering = signal(false, ...ngDevMode ? [{ debugName: "reordering" }] : /* istanbul ignore next */ []);
16827
- alerts = computed(() => this.userSettingsStore.alerts(), ...ngDevMode ? [{ debugName: "alerts" }] : /* istanbul ignore next */ []);
16828
- tmpAlerts = [];
17309
+ transloco = inject(TranslocoService);
17310
+ lang = toSignal(this.transloco.langChanges$);
16829
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 */ []);
17319
+ alerts = computed(() => this.userSettingsStore.alerts() ?? [], ...ngDevMode ? [{ debugName: "alerts" }] : /* istanbul ignore next */ []);
17320
+ order = linkedSignal(() => [...this.alerts()], ...ngDevMode ? [{ debugName: "order" }] : /* istanbul ignore next */ []);
17321
+ hidden = signal(/* @__PURE__ */ new Set(), ...ngDevMode ? [{ debugName: "hidden" }] : /* istanbul ignore next */ []);
17322
+ pendingDeletion = signal(void 0, ...ngDevMode ? [{ debugName: "pendingDeletion" }] : /* istanbul ignore next */ []);
17323
+ undoTimer;
17324
+ visible = computed(() => {
17325
+ const hidden = this.hidden();
17326
+ return this.order().filter((alert) => !hidden.has(alert));
17327
+ }, ...ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []);
17328
+ summaries = computed(() => {
17329
+ const lang = this.lang() ?? this.transloco.getActiveLang();
17330
+ return new Map(this.order().map((alert) => [alert, alertSummary(lang, alert)]));
17331
+ }, ...ngDevMode ? [{ debugName: "summaries" }] : /* istanbul ignore next */ []);
17332
+ summaryOf = (alert) => this.summaries().get(alert) ?? [];
17333
+ announceMove = (alert, position, total) => this.transloco.translate("alerts.moved", {
17334
+ name: alert.name,
17335
+ position,
17336
+ total
17337
+ });
16830
17338
  constructor() {
16831
17339
  this.query = buildQuery();
16832
- effect(() => {
16833
- this.tmpAlerts = this.alerts()?.map((a) => Object.assign({}, a));
16834
- });
17340
+ inject(DestroyRef).onDestroy(() => this.commitDeletion());
17341
+ }
17342
+ onRowClick(event, alert) {
17343
+ if (event.target.closest("[data-row-action]")) return;
17344
+ this.edit(alert);
16835
17345
  }
16836
- onClick(index) {
17346
+ onRowEnter(event, alert) {
17347
+ if (event.target === event.currentTarget) this.edit(alert);
17348
+ }
17349
+ edit(alert) {
17350
+ const index = this.alerts().indexOf(alert);
17351
+ if (index < 0) return;
16837
17352
  this.floating?.close();
17353
+ if (!this.dialog()) {
17354
+ this.editRequested.emit(index);
17355
+ return;
17356
+ }
16838
17357
  AlertForm.call(index, { injector: this.injector });
16839
17358
  }
16840
17359
  createAlert() {
16841
17360
  this.floating?.close();
17361
+ if (!this.dialog()) {
17362
+ this.createRequested.emit();
17363
+ return;
17364
+ }
16842
17365
  AlertForm.call(void 0, { injector: this.injector });
16843
17366
  }
16844
- deleteAlert(event, index) {
16845
- event.stopPropagation();
16846
- this.userSettingsStore.deleteAlert(index);
17367
+ pendingActive = signal(/* @__PURE__ */ new Map(), ...ngDevMode ? [{ debugName: "pendingActive" }] : /* istanbul ignore next */ []);
17368
+ isActive(alert) {
17369
+ return this.pendingActive().get(alert) ?? alert.active;
16847
17370
  }
16848
- async reorder() {
16849
- if (this.reordering()) {
16850
- await this.userSettingsStore.updateAlerts(this.tmpAlerts);
16851
- this.reordering.set(false);
16852
- } else this.reordering.set(true);
17371
+ async toggle(alert, active) {
17372
+ const index = this.alerts().indexOf(alert);
17373
+ if (index < 0) return;
17374
+ this.saveError.set(void 0);
17375
+ this.pendingActive.update((pending) => new Map(pending).set(alert, active));
17376
+ try {
17377
+ await this.userSettingsStore.updateAlert({
17378
+ ...alert,
17379
+ active
17380
+ }, index);
17381
+ } catch (error) {
17382
+ this.saveError.set({ detail: saveErrorDetail(error) });
17383
+ } finally {
17384
+ this.pendingActive.update((pending) => {
17385
+ const next = new Map(pending);
17386
+ next.delete(alert);
17387
+ return next;
17388
+ });
17389
+ }
17390
+ }
17391
+ deleteAlert(alert) {
17392
+ this.commitDeletion();
17393
+ this.hidden.update((hidden) => new Set(hidden).add(alert));
17394
+ this.pendingDeletion.set(alert);
17395
+ this.undoTimer = setTimeout(() => this.commitDeletion(), UNDO_DELAY);
17396
+ }
17397
+ undoDeletion() {
17398
+ clearTimeout(this.undoTimer);
17399
+ const alert = this.pendingDeletion();
17400
+ if (!alert) return;
17401
+ this.pendingDeletion.set(void 0);
17402
+ this.unhide(alert);
16853
17403
  }
16854
- dropped(drop) {
16855
- if (drop.currentIndex === drop.previousIndex) return;
16856
- this.tmpAlerts.splice(drop.currentIndex, 0, this.tmpAlerts.splice(drop.previousIndex, 1)[0]);
17404
+ commitDeletion() {
17405
+ clearTimeout(this.undoTimer);
17406
+ const alert = this.pendingDeletion();
17407
+ if (!alert) return;
17408
+ this.pendingDeletion.set(void 0);
17409
+ const index = this.alerts().indexOf(alert);
17410
+ if (index < 0) {
17411
+ this.unhide(alert);
17412
+ return;
17413
+ }
17414
+ this.userSettingsStore.deleteAlert(index).catch((error) => this.saveError.set({ detail: saveErrorDetail(error) })).finally(() => this.unhide(alert));
17415
+ }
17416
+ unhide(alert) {
17417
+ this.hidden.update((hidden) => {
17418
+ const next = new Set(hidden);
17419
+ next.delete(alert);
17420
+ return next;
17421
+ });
17422
+ }
17423
+ async saveOrder(shown) {
17424
+ const hidden = this.hidden();
17425
+ const previous = this.order();
17426
+ const next = [...shown, ...previous.filter((alert) => hidden.has(alert))];
17427
+ if (next.every((alert, index) => alert === previous[index])) return;
17428
+ this.saveError.set(void 0);
17429
+ this.order.set(next);
17430
+ try {
17431
+ await this.userSettingsStore.updateAlerts(next);
17432
+ } catch (error) {
17433
+ this.order.set(previous);
17434
+ this.saveError.set({ detail: saveErrorDetail(error) });
17435
+ }
16857
17436
  }
16858
17437
  static ɵfac = i0.ɵɵngDeclareFactory({
16859
17438
  minVersion: "12.0.0",
@@ -16869,9 +17448,20 @@ var AlertsComponent = class AlertsComponent {
16869
17448
  type: AlertsComponent,
16870
17449
  isStandalone: true,
16871
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
+ },
16872
17462
  providers: [provideTranslocoScope("alerts")],
16873
17463
  ngImport: i0,
16874
- template: "<ul\n role=\"list\"\n class=\"flex min-w-80 flex-col p-2\"\n cdkDropList\n [cdkDropListData]=\"tmpAlerts\"\n [cdkDropListDisabled]=\"!reordering()\"\n (cdkDropListDropped)=\"dropped($event)\">\n @if (floating) {\n <label class=\"text-xl font-bold\">{{ \"alerts.label\" | transloco }}</label>\n <hr\n class=\"my-(--spacing-3xs) h-px w-full shrink-0 border-0 bg-(--stroke-neutral-disabled)\" />\n }\n @for (alert of tmpAlerts; track $index) {\n <li\n class=\"group box-border flex h-10 w-full cursor-pointer items-center gap-(--spacing-2xs) rounded-(--radius-sm) bg-transparent px-(--spacing-2xs) py-(--spacing-3xs) 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)\"\n tabindex=\"0\"\n cdkDrag\n (click)=\"onClick($index)\"\n (keydown.enter)=\"onClick($index)\">\n <bell-icon class=\"shrink-0\" />\n <p class=\"line-clamp-1\">{{ alert.name }}</p>\n @if (!reordering()) {\n @let openBookmark = \"bookmarks.openBookmark\" | transloco;\n <button\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n title=\"{{ openBookmark }}\"\n class=\"text-destructive ms-auto group-hover:visible\"\n [attr.title]=\"openBookmark\"\n [attr.aria-label]=\"openBookmark\"\n (click)=\"deleteAlert($event, $index)\">\n <TrashIcon />\n </button>\n } @else {\n <bars-icon class=\"ms-auto\" />\n }\n </li>\n } @empty {\n <span class=\"py-4 text-center text-neutral-500\">\n {{ \"alerts.noAlerts\" | transloco }}\n </span>\n }\n</ul>\n<div class=\"flex flex-col gap-2 p-2\">\n <button\n variant=\"secondary\"\n class=\"w-full\"\n tabindex=\"0\"\n [attr.title]=\"'alerts.manageAlerts' | transloco\"\n [disabled]=\"!alerts().length\"\n (click)=\"reorder()\">\n {{ (reordering() ? \"save\" : \"alerts.manageAlerts\") | transloco }}\n </button>\n <button\n variant=\"secondary\"\n class=\"w-full\"\n [attr.title]=\"'alerts.createAlert' | transloco\"\n (click)=\"createAlert()\">\n {{ \"alerts.createAlert\" | transloco }}\n </button>\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",
16875
17465
  dependencies: [
16876
17466
  {
16877
17467
  kind: "directive",
@@ -16886,63 +17476,39 @@ var AlertsComponent = class AlertsComponent {
16886
17476
  ]
16887
17477
  },
16888
17478
  {
16889
- kind: "ngmodule",
16890
- type: DragDropModule
17479
+ kind: "component",
17480
+ type: SwitchComponent,
17481
+ selector: "switch, Switch",
17482
+ inputs: [
17483
+ "checked",
17484
+ "disabled",
17485
+ "size",
17486
+ "class",
17487
+ "aria-label",
17488
+ "aria-labelledby",
17489
+ "presentational",
17490
+ "compact",
17491
+ "tabIndex"
17492
+ ],
17493
+ outputs: ["checkedChange"]
16891
17494
  },
16892
17495
  {
16893
17496
  kind: "directive",
16894
- type: i1.CdkDropList,
16895
- selector: "[cdkDropList], cdk-drop-list",
17497
+ type: SortableDirective,
17498
+ selector: "[sortable]",
16896
17499
  inputs: [
16897
- "cdkDropListConnectedTo",
16898
- "cdkDropListData",
16899
- "cdkDropListOrientation",
16900
- "id",
16901
- "cdkDropListLockAxis",
16902
- "cdkDropListDisabled",
16903
- "cdkDropListSortingDisabled",
16904
- "cdkDropListEnterPredicate",
16905
- "cdkDropListSortPredicate",
16906
- "cdkDropListAutoScrollDisabled",
16907
- "cdkDropListAutoScrollStep",
16908
- "cdkDropListElementContainer",
16909
- "cdkDropListHasAnchor"
17500
+ "sortable",
17501
+ "sortableDisabled",
17502
+ "sortableAnnounce"
16910
17503
  ],
16911
- outputs: [
16912
- "cdkDropListDropped",
16913
- "cdkDropListEntered",
16914
- "cdkDropListExited",
16915
- "cdkDropListSorted"
16916
- ],
16917
- exportAs: ["cdkDropList"]
17504
+ outputs: ["reorder"],
17505
+ exportAs: ["sortable"]
16918
17506
  },
16919
17507
  {
16920
17508
  kind: "directive",
16921
- type: i1.CdkDrag,
16922
- selector: "[cdkDrag]",
16923
- inputs: [
16924
- "cdkDragData",
16925
- "cdkDragLockAxis",
16926
- "cdkDragRootElement",
16927
- "cdkDragBoundary",
16928
- "cdkDragStartDelay",
16929
- "cdkDragFreeDragPosition",
16930
- "cdkDragDisabled",
16931
- "cdkDragConstrainPosition",
16932
- "cdkDragPreviewClass",
16933
- "cdkDragPreviewContainer",
16934
- "cdkDragScale"
16935
- ],
16936
- outputs: [
16937
- "cdkDragStarted",
16938
- "cdkDragReleased",
16939
- "cdkDragEnded",
16940
- "cdkDragEntered",
16941
- "cdkDragExited",
16942
- "cdkDragDropped",
16943
- "cdkDragMoved"
16944
- ],
16945
- exportAs: ["cdkDrag"]
17509
+ type: SortableItemDirective,
17510
+ selector: "[sortableItem]",
17511
+ inputs: ["sortableItem", "sortableItemDisabled"]
16946
17512
  },
16947
17513
  {
16948
17514
  kind: "component",
@@ -16950,6 +17516,24 @@ var AlertsComponent = class AlertsComponent {
16950
17516
  selector: "bell, bell-icon, Bell, BellIcon",
16951
17517
  inputs: ["class"]
16952
17518
  },
17519
+ {
17520
+ kind: "component",
17521
+ type: BellSlashIcon,
17522
+ selector: "bell-slash, bell-slash-icon, BellSlash, BellSlashIcon",
17523
+ inputs: ["class"]
17524
+ },
17525
+ {
17526
+ kind: "component",
17527
+ type: GripVerticalIcon,
17528
+ selector: "grip-vertical, grip-vertical-icon, GripVertical, GripVerticalIcon",
17529
+ inputs: ["class"]
17530
+ },
17531
+ {
17532
+ kind: "component",
17533
+ type: PlusIcon,
17534
+ selector: "plus, plus-icon, Plus, PlusIcon",
17535
+ inputs: ["class"]
17536
+ },
16953
17537
  {
16954
17538
  kind: "component",
16955
17539
  type: TrashIcon,
@@ -16958,9 +17542,15 @@ var AlertsComponent = class AlertsComponent {
16958
17542
  },
16959
17543
  {
16960
17544
  kind: "component",
16961
- type: BarsIcon,
16962
- selector: "bars, bars-icon, Bars, BarsIcon",
16963
- inputs: ["class"]
17545
+ type: ListPanelEmptyComponent,
17546
+ selector: "list-panel-empty, ListPanelEmpty",
17547
+ inputs: ["color"]
17548
+ },
17549
+ {
17550
+ kind: "component",
17551
+ type: AlertSaveErrorComponent,
17552
+ selector: "alert-save-error",
17553
+ inputs: ["detail"]
16964
17554
  },
16965
17555
  {
16966
17556
  kind: "pipe",
@@ -16982,16 +17572,40 @@ i0.ɵɵngDeclareClassMetadata({
16982
17572
  imports: [
16983
17573
  TranslocoPipe,
16984
17574
  ButtonComponent,
16985
- DragDropModule,
17575
+ SwitchComponent,
17576
+ SortableDirective,
17577
+ SortableItemDirective,
16986
17578
  BellIcon,
17579
+ BellSlashIcon,
17580
+ GripVerticalIcon,
17581
+ PlusIcon,
16987
17582
  TrashIcon,
16988
- BarsIcon
17583
+ ListPanelEmptyComponent,
17584
+ AlertSaveErrorComponent
16989
17585
  ],
16990
17586
  providers: [provideTranslocoScope("alerts")],
16991
- template: "<ul\n role=\"list\"\n class=\"flex min-w-80 flex-col p-2\"\n cdkDropList\n [cdkDropListData]=\"tmpAlerts\"\n [cdkDropListDisabled]=\"!reordering()\"\n (cdkDropListDropped)=\"dropped($event)\">\n @if (floating) {\n <label class=\"text-xl font-bold\">{{ \"alerts.label\" | transloco }}</label>\n <hr\n class=\"my-(--spacing-3xs) h-px w-full shrink-0 border-0 bg-(--stroke-neutral-disabled)\" />\n }\n @for (alert of tmpAlerts; track $index) {\n <li\n class=\"group box-border flex h-10 w-full cursor-pointer items-center gap-(--spacing-2xs) rounded-(--radius-sm) bg-transparent px-(--spacing-2xs) py-(--spacing-3xs) 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)\"\n tabindex=\"0\"\n cdkDrag\n (click)=\"onClick($index)\"\n (keydown.enter)=\"onClick($index)\">\n <bell-icon class=\"shrink-0\" />\n <p class=\"line-clamp-1\">{{ alert.name }}</p>\n @if (!reordering()) {\n @let openBookmark = \"bookmarks.openBookmark\" | transloco;\n <button\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n title=\"{{ openBookmark }}\"\n class=\"text-destructive ms-auto group-hover:visible\"\n [attr.title]=\"openBookmark\"\n [attr.aria-label]=\"openBookmark\"\n (click)=\"deleteAlert($event, $index)\">\n <TrashIcon />\n </button>\n } @else {\n <bars-icon class=\"ms-auto\" />\n }\n </li>\n } @empty {\n <span class=\"py-4 text-center text-neutral-500\">\n {{ \"alerts.noAlerts\" | transloco }}\n </span>\n }\n</ul>\n<div class=\"flex flex-col gap-2 p-2\">\n <button\n variant=\"secondary\"\n class=\"w-full\"\n tabindex=\"0\"\n [attr.title]=\"'alerts.manageAlerts' | transloco\"\n [disabled]=\"!alerts().length\"\n (click)=\"reorder()\">\n {{ (reordering() ? \"save\" : \"alerts.manageAlerts\") | transloco }}\n </button>\n <button\n variant=\"secondary\"\n class=\"w-full\"\n [attr.title]=\"'alerts.createAlert' | transloco\"\n (click)=\"createAlert()\">\n {{ \"alerts.createAlert\" | transloco }}\n </button>\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"
16992
17588
  }]
16993
17589
  }],
16994
- 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
+ }
16995
17609
  });
16996
17610
  const passwordChangeSchema = schema((p) => {
16997
17611
  required(p.currentPassword);
@@ -19105,9 +19719,9 @@ var ListPanelComponent = class ListPanelComponent {
19105
19719
  }
19106
19720
  </ListItem>
19107
19721
  } @empty {
19108
- <span class="py-4 text-center text-neutral-500">
19722
+ <div class="py-(--spacing-xs) text-center text-(--font-neutral-base)">
19109
19723
  <ng-content />
19110
- </span>
19724
+ </div>
19111
19725
  }
19112
19726
  </List>
19113
19727
  `,
@@ -19212,9 +19826,9 @@ i0.ɵɵngDeclareClassMetadata({
19212
19826
  }
19213
19827
  </ListItem>
19214
19828
  } @empty {
19215
- <span class="py-4 text-center text-neutral-500">
19829
+ <div class="py-(--spacing-xs) text-center text-(--font-neutral-base)">
19216
19830
  <ng-content />
19217
- </span>
19831
+ </div>
19218
19832
  }
19219
19833
  </List>
19220
19834
  `,
@@ -19357,7 +19971,7 @@ var ListPanelFooterComponent = class ListPanelFooterComponent {
19357
19971
  }
19358
19972
  },
19359
19973
  outputs: { loadMore: "loadMore" },
19360
- host: { properties: { "class": "cn('flex flex-col px-2 mt-2 py-(--spacing-xs)', class())" } },
19974
+ host: { properties: { "class": "cn('flex flex-col gap-(--spacing-xs) px-2 mt-2 py-(--spacing-xs)', class())" } },
19361
19975
  providers: [provideTranslocoScope("list-panel")],
19362
19976
  ngImport: i0,
19363
19977
  template: `
@@ -19509,7 +20123,7 @@ i0.ɵɵngDeclareClassMetadata({
19509
20123
  </button>
19510
20124
  </div>
19511
20125
  `,
19512
- host: { "[class]": "cn('flex flex-col px-2 mt-2 py-(--spacing-xs)', class())" }
20126
+ host: { "[class]": "cn('flex flex-col gap-(--spacing-xs) px-2 mt-2 py-(--spacing-xs)', class())" }
19513
20127
  }]
19514
20128
  }],
19515
20129
  propDecorators: {
@@ -19588,8 +20202,8 @@ var ListPanelHeaderComponent = class ListPanelHeaderComponent {
19588
20202
  template: `
19589
20203
  @if (floating()) {
19590
20204
  <div class="p-2">
19591
- <label class="text-xl font-bold">{{ label() }}</label>
19592
- <hr class="my-(--spacing-3xs) h-px w-full shrink-0 border-0 bg-(--stroke-neutral-disabled)" />
20205
+ <!-- Not an <h2>: a page's prose styles put a rule and margins on every h2, even inside a popover. -->
20206
+ <span role="heading" aria-level="2" class="block text-xl leading-(--line-height-4) font-bold">{{ label() }}</span>
19593
20207
  </div>
19594
20208
  }
19595
20209
  `,
@@ -19608,8 +20222,8 @@ i0.ɵɵngDeclareClassMetadata({
19608
20222
  template: `
19609
20223
  @if (floating()) {
19610
20224
  <div class="p-2">
19611
- <label class="text-xl font-bold">{{ label() }}</label>
19612
- <hr class="my-(--spacing-3xs) h-px w-full shrink-0 border-0 bg-(--stroke-neutral-disabled)" />
20225
+ <!-- Not an <h2>: a page's prose styles put a rule and margins on every h2, even inside a popover. -->
20226
+ <span role="heading" aria-level="2" class="block text-xl leading-(--line-height-4) font-bold">{{ label() }}</span>
19613
20227
  </div>
19614
20228
  }
19615
20229
  `
@@ -19733,7 +20347,7 @@ var BookmarksComponent = class BookmarksComponent {
19733
20347
  } },
19734
20348
  providers: [provideTranslocoScope("bookmarks")],
19735
20349
  ngImport: i0,
19736
- template: "<ListPanelHeader\n [label]=\"'bookmarks.label' | transloco\"\n [floating]=\"!!floating\" />\n\n<ListPanel\n #panel\n [items]=\"paginatedBookmarks()\"\n [getValue]=\"idOf\"\n [getLabel]=\"labelOf\"\n deleteButtonTitle=\"bookmarks.removeBookmark\"\n [ariaLabel]=\"'bookmarks.label' | transloco\"\n (itemClick)=\"onClick($event)\"\n (delete)=\"onDelete($event)\">\n <ng-template listPanelItem let-bookmark>\n <ListItemLeft>\n <BookmarkIcon solid class=\"shrink-0\" />\n <ListItemText>{{ bookmark.label }}</ListItemText>\n </ListItemLeft>\n\n @if (bookmark.author) {\n <Tag variant=\"primary\" scheme=\"sage\" size=\"xs\" class=\"gap-1\">\n <UserIcon class=\"size-3\" />\n <span class=\"min-w-0 truncate\">{{ bookmark.author }}</span>\n </Tag>\n }\n @if (bookmark.parentFolder) {\n <Tag variant=\"primary\" scheme=\"sage\" size=\"xs\" class=\"gap-1\">\n <FolderIcon class=\"size-3\" />\n <span class=\"min-w-0 truncate\">{{ bookmark.parentFolder }}</span>\n </Tag>\n }\n </ng-template>\n\n {{ \"bookmarks.noBookmarks\" | transloco }}\n</ListPanel>\n\n@if (paginatedBookmarks().length > 0) {\n <ListPanelFooter\n [navigationHint]=\"panel.keyboardFocused()\"\n [hasMore]=\"hasMore()\"\n [config]=\"config\"\n (loadMore)=\"loadMore()\" />\n}\n",
20350
+ template: "<ListPanelHeader\n [label]=\"'bookmarks.label' | transloco\"\n [floating]=\"!!floating\" />\n\n<ListPanel\n #panel\n [items]=\"paginatedBookmarks()\"\n [getValue]=\"idOf\"\n [getLabel]=\"labelOf\"\n deleteButtonTitle=\"bookmarks.removeBookmark\"\n [ariaLabel]=\"'bookmarks.label' | transloco\"\n (itemClick)=\"onClick($event)\"\n (delete)=\"onDelete($event)\">\n <ng-template listPanelItem let-bookmark>\n <ListItemLeft>\n <BookmarkIcon solid class=\"shrink-0\" />\n <div class=\"flex min-w-0 flex-1 flex-col gap-(--spacing-3xs)\">\n <ListItemText\n truncated=\".list-item-text\"\n #labelCut=\"truncated\"\n [tooltip]=\"bookmark.label\"\n [tooltipDisabled]=\"!labelCut.isTruncated()\"\n tooltipPosition=\"top\"\n class=\"[&_.list-item-text]:line-clamp-2 in-focus-visible:[&_.list-item-text]:line-clamp-none\">\n {{ bookmark.label }}\n </ListItemText>\n\n @if (bookmark.author || bookmark.parentFolder) {\n <div class=\"flex min-w-0 flex-wrap gap-(--spacing-3xs)\">\n @if (bookmark.author) {\n <Tag\n variant=\"primary\"\n scheme=\"sage\"\n size=\"xs\"\n class=\"max-w-full gap-1\">\n <UserIcon class=\"size-3 shrink-0\" />\n <span\n truncated\n #authorCut=\"truncated\"\n [tooltip]=\"bookmark.author\"\n [tooltipDisabled]=\"!authorCut.isTruncated()\"\n tooltipPosition=\"top\"\n class=\"min-w-0 truncate in-focus-visible:break-words in-focus-visible:whitespace-normal\">\n {{ bookmark.author }}\n </span>\n </Tag>\n }\n @if (bookmark.parentFolder) {\n <Tag\n variant=\"primary\"\n scheme=\"sage\"\n size=\"xs\"\n class=\"max-w-full gap-1\">\n <FolderIcon class=\"size-3 shrink-0\" />\n <span\n truncated\n #folderCut=\"truncated\"\n [tooltip]=\"bookmark.parentFolder\"\n [tooltipDisabled]=\"!folderCut.isTruncated()\"\n tooltipPosition=\"top\"\n class=\"min-w-0 truncate in-focus-visible:break-words in-focus-visible:whitespace-normal\">\n {{ bookmark.parentFolder }}\n </span>\n </Tag>\n }\n </div>\n }\n </div>\n </ListItemLeft>\n </ng-template>\n\n <ListPanelEmpty color=\"pink\">\n <BookmarkIcon listPanelEmptyIcon solid />\n {{ \"bookmarks.noBookmarks\" | transloco }}\n <p listPanelEmptyHint>{{ \"bookmarks.noBookmarksHint\" | transloco }}</p>\n </ListPanelEmpty>\n</ListPanel>\n\n@if (paginatedBookmarks().length > 0) {\n <ListPanelFooter\n [navigationHint]=\"panel.keyboardFocused()\"\n [hasMore]=\"hasMore()\"\n [config]=\"config\"\n (loadMore)=\"loadMore()\" />\n}\n",
19737
20351
  dependencies: [
19738
20352
  {
19739
20353
  kind: "component",
@@ -19756,6 +20370,12 @@ var BookmarksComponent = class BookmarksComponent {
19756
20370
  ],
19757
20371
  outputs: ["itemClick", "delete"]
19758
20372
  },
20373
+ {
20374
+ kind: "component",
20375
+ type: ListPanelEmptyComponent,
20376
+ selector: "list-panel-empty, ListPanelEmpty",
20377
+ inputs: ["color"]
20378
+ },
19759
20379
  {
19760
20380
  kind: "component",
19761
20381
  type: ListPanelFooterComponent,
@@ -19815,6 +20435,39 @@ var BookmarksComponent = class BookmarksComponent {
19815
20435
  "size"
19816
20436
  ]
19817
20437
  },
20438
+ {
20439
+ kind: "directive",
20440
+ type: TooltipDirective,
20441
+ selector: "[tooltip]",
20442
+ inputs: [
20443
+ "tooltip",
20444
+ "tooltipPosition",
20445
+ "tooltipEvent",
20446
+ "showDelay",
20447
+ "hideDelay",
20448
+ "life",
20449
+ "tooltipOffset",
20450
+ "autoHide",
20451
+ "hideOnEscape",
20452
+ "escape",
20453
+ "tooltipDisabled",
20454
+ "tooltipArrow",
20455
+ "tooltipStyleClass",
20456
+ "tooltipOptions",
20457
+ "tooltip-position",
20458
+ "tooltip-delay",
20459
+ "tooltip-duration",
20460
+ "tooltip-offset"
20461
+ ],
20462
+ exportAs: ["tooltip"]
20463
+ },
20464
+ {
20465
+ kind: "directive",
20466
+ type: TruncatedDirective,
20467
+ selector: "[truncated]",
20468
+ inputs: ["truncated"],
20469
+ exportAs: ["truncated"]
20470
+ },
19818
20471
  {
19819
20472
  kind: "pipe",
19820
20473
  type: TranslocoPipe,
@@ -19836,6 +20489,7 @@ i0.ɵɵngDeclareClassMetadata({
19836
20489
  TranslocoPipe,
19837
20490
  ListPanelHeaderComponent,
19838
20491
  ListPanelComponent,
20492
+ ListPanelEmptyComponent,
19839
20493
  ListPanelFooterComponent,
19840
20494
  ListPanelItemDirective,
19841
20495
  BookmarkIcon,
@@ -19843,10 +20497,12 @@ i0.ɵɵngDeclareClassMetadata({
19843
20497
  FolderIcon,
19844
20498
  ListItemLeftDirective,
19845
20499
  ListItemTextComponent,
19846
- TagComponent
20500
+ TagComponent,
20501
+ TooltipDirective,
20502
+ TruncatedDirective
19847
20503
  ],
19848
20504
  providers: [provideTranslocoScope("bookmarks")],
19849
- template: "<ListPanelHeader\n [label]=\"'bookmarks.label' | transloco\"\n [floating]=\"!!floating\" />\n\n<ListPanel\n #panel\n [items]=\"paginatedBookmarks()\"\n [getValue]=\"idOf\"\n [getLabel]=\"labelOf\"\n deleteButtonTitle=\"bookmarks.removeBookmark\"\n [ariaLabel]=\"'bookmarks.label' | transloco\"\n (itemClick)=\"onClick($event)\"\n (delete)=\"onDelete($event)\">\n <ng-template listPanelItem let-bookmark>\n <ListItemLeft>\n <BookmarkIcon solid class=\"shrink-0\" />\n <ListItemText>{{ bookmark.label }}</ListItemText>\n </ListItemLeft>\n\n @if (bookmark.author) {\n <Tag variant=\"primary\" scheme=\"sage\" size=\"xs\" class=\"gap-1\">\n <UserIcon class=\"size-3\" />\n <span class=\"min-w-0 truncate\">{{ bookmark.author }}</span>\n </Tag>\n }\n @if (bookmark.parentFolder) {\n <Tag variant=\"primary\" scheme=\"sage\" size=\"xs\" class=\"gap-1\">\n <FolderIcon class=\"size-3\" />\n <span class=\"min-w-0 truncate\">{{ bookmark.parentFolder }}</span>\n </Tag>\n }\n </ng-template>\n\n {{ \"bookmarks.noBookmarks\" | transloco }}\n</ListPanel>\n\n@if (paginatedBookmarks().length > 0) {\n <ListPanelFooter\n [navigationHint]=\"panel.keyboardFocused()\"\n [hasMore]=\"hasMore()\"\n [config]=\"config\"\n (loadMore)=\"loadMore()\" />\n}\n"
20505
+ template: "<ListPanelHeader\n [label]=\"'bookmarks.label' | transloco\"\n [floating]=\"!!floating\" />\n\n<ListPanel\n #panel\n [items]=\"paginatedBookmarks()\"\n [getValue]=\"idOf\"\n [getLabel]=\"labelOf\"\n deleteButtonTitle=\"bookmarks.removeBookmark\"\n [ariaLabel]=\"'bookmarks.label' | transloco\"\n (itemClick)=\"onClick($event)\"\n (delete)=\"onDelete($event)\">\n <ng-template listPanelItem let-bookmark>\n <ListItemLeft>\n <BookmarkIcon solid class=\"shrink-0\" />\n <div class=\"flex min-w-0 flex-1 flex-col gap-(--spacing-3xs)\">\n <ListItemText\n truncated=\".list-item-text\"\n #labelCut=\"truncated\"\n [tooltip]=\"bookmark.label\"\n [tooltipDisabled]=\"!labelCut.isTruncated()\"\n tooltipPosition=\"top\"\n class=\"[&_.list-item-text]:line-clamp-2 in-focus-visible:[&_.list-item-text]:line-clamp-none\">\n {{ bookmark.label }}\n </ListItemText>\n\n @if (bookmark.author || bookmark.parentFolder) {\n <div class=\"flex min-w-0 flex-wrap gap-(--spacing-3xs)\">\n @if (bookmark.author) {\n <Tag\n variant=\"primary\"\n scheme=\"sage\"\n size=\"xs\"\n class=\"max-w-full gap-1\">\n <UserIcon class=\"size-3 shrink-0\" />\n <span\n truncated\n #authorCut=\"truncated\"\n [tooltip]=\"bookmark.author\"\n [tooltipDisabled]=\"!authorCut.isTruncated()\"\n tooltipPosition=\"top\"\n class=\"min-w-0 truncate in-focus-visible:break-words in-focus-visible:whitespace-normal\">\n {{ bookmark.author }}\n </span>\n </Tag>\n }\n @if (bookmark.parentFolder) {\n <Tag\n variant=\"primary\"\n scheme=\"sage\"\n size=\"xs\"\n class=\"max-w-full gap-1\">\n <FolderIcon class=\"size-3 shrink-0\" />\n <span\n truncated\n #folderCut=\"truncated\"\n [tooltip]=\"bookmark.parentFolder\"\n [tooltipDisabled]=\"!folderCut.isTruncated()\"\n tooltipPosition=\"top\"\n class=\"min-w-0 truncate in-focus-visible:break-words in-focus-visible:whitespace-normal\">\n {{ bookmark.parentFolder }}\n </span>\n </Tag>\n }\n </div>\n }\n </div>\n </ListItemLeft>\n </ng-template>\n\n <ListPanelEmpty color=\"pink\">\n <BookmarkIcon listPanelEmptyIcon solid />\n {{ \"bookmarks.noBookmarks\" | transloco }}\n <p listPanelEmptyHint>{{ \"bookmarks.noBookmarksHint\" | transloco }}</p>\n </ListPanelEmpty>\n</ListPanel>\n\n@if (paginatedBookmarks().length > 0) {\n <ListPanelFooter\n [navigationHint]=\"panel.keyboardFocused()\"\n [hasMore]=\"hasMore()\"\n [config]=\"config\"\n (loadMore)=\"loadMore()\" />\n}\n"
19850
20506
  }]
19851
20507
  }],
19852
20508
  ctorParameters: () => [],
@@ -20102,7 +20758,7 @@ var CollectionsComponent = class CollectionsComponent {
20102
20758
  } },
20103
20759
  providers: [provideTranslocoScope("collections")],
20104
20760
  ngImport: i0,
20105
- template: "<ListPanelHeader\n [label]=\"'collections.label' | transloco\"\n [floating]=\"!!floating\" />\n\n<ListPanel\n #panel\n [items]=\"paginatedCollections()\"\n [getValue]=\"nameOf\"\n [getLabel]=\"nameOf\"\n deleteButtonTitle=\"collections.deleteCollection\"\n [ariaLabel]=\"'collections.label' | transloco\"\n (itemClick)=\"onClick($event)\"\n (delete)=\"onDelete($event)\">\n <ng-template listPanelItem let-collection>\n <ListItemLeft>\n <inbox-icon />\n <ListItemText>{{ collection.name }}</ListItemText>\n </ListItemLeft>\n </ng-template>\n\n {{ \"collections.noCollections\" | transloco }}\n</ListPanel>\n\n@if (paginatedCollections().length > 0) {\n <ListPanelFooter\n [navigationHint]=\"panel.keyboardFocused()\"\n [hasMore]=\"hasMore()\"\n [config]=\"config\"\n (loadMore)=\"loadMore()\" />\n}\n",
20761
+ template: "<ListPanelHeader\n [label]=\"'collections.label' | transloco\"\n [floating]=\"!!floating\" />\n\n<ListPanel\n #panel\n [items]=\"paginatedCollections()\"\n [getValue]=\"nameOf\"\n [getLabel]=\"nameOf\"\n deleteButtonTitle=\"collections.deleteCollection\"\n [ariaLabel]=\"'collections.label' | transloco\"\n (itemClick)=\"onClick($event)\"\n (delete)=\"onDelete($event)\">\n <ng-template listPanelItem let-collection>\n <ListItemLeft>\n <inbox-icon />\n <ListItemText>{{ collection.name }}</ListItemText>\n </ListItemLeft>\n </ng-template>\n\n <ListPanelEmpty color=\"indigo\">\n <inbox-icon listPanelEmptyIcon />\n {{ \"collections.noCollections\" | transloco }}\n <p listPanelEmptyHint>{{ \"collections.noCollectionsHint\" | transloco }}</p>\n </ListPanelEmpty>\n</ListPanel>\n\n@if (paginatedCollections().length > 0) {\n <ListPanelFooter\n [navigationHint]=\"panel.keyboardFocused()\"\n [hasMore]=\"hasMore()\"\n [config]=\"config\"\n (loadMore)=\"loadMore()\" />\n}\n",
20106
20762
  dependencies: [
20107
20763
  {
20108
20764
  kind: "component",
@@ -20125,6 +20781,12 @@ var CollectionsComponent = class CollectionsComponent {
20125
20781
  ],
20126
20782
  outputs: ["itemClick", "delete"]
20127
20783
  },
20784
+ {
20785
+ kind: "component",
20786
+ type: ListPanelEmptyComponent,
20787
+ selector: "list-panel-empty, ListPanelEmpty",
20788
+ inputs: ["color"]
20789
+ },
20128
20790
  {
20129
20791
  kind: "component",
20130
20792
  type: ListPanelFooterComponent,
@@ -20182,6 +20844,7 @@ i0.ɵɵngDeclareClassMetadata({
20182
20844
  TranslocoPipe,
20183
20845
  ListPanelHeaderComponent,
20184
20846
  ListPanelComponent,
20847
+ ListPanelEmptyComponent,
20185
20848
  ListPanelFooterComponent,
20186
20849
  ListPanelItemDirective,
20187
20850
  InboxIcon,
@@ -20189,7 +20852,7 @@ i0.ɵɵngDeclareClassMetadata({
20189
20852
  ListItemTextComponent
20190
20853
  ],
20191
20854
  providers: [provideTranslocoScope("collections")],
20192
- template: "<ListPanelHeader\n [label]=\"'collections.label' | transloco\"\n [floating]=\"!!floating\" />\n\n<ListPanel\n #panel\n [items]=\"paginatedCollections()\"\n [getValue]=\"nameOf\"\n [getLabel]=\"nameOf\"\n deleteButtonTitle=\"collections.deleteCollection\"\n [ariaLabel]=\"'collections.label' | transloco\"\n (itemClick)=\"onClick($event)\"\n (delete)=\"onDelete($event)\">\n <ng-template listPanelItem let-collection>\n <ListItemLeft>\n <inbox-icon />\n <ListItemText>{{ collection.name }}</ListItemText>\n </ListItemLeft>\n </ng-template>\n\n {{ \"collections.noCollections\" | transloco }}\n</ListPanel>\n\n@if (paginatedCollections().length > 0) {\n <ListPanelFooter\n [navigationHint]=\"panel.keyboardFocused()\"\n [hasMore]=\"hasMore()\"\n [config]=\"config\"\n (loadMore)=\"loadMore()\" />\n}\n"
20855
+ template: "<ListPanelHeader\n [label]=\"'collections.label' | transloco\"\n [floating]=\"!!floating\" />\n\n<ListPanel\n #panel\n [items]=\"paginatedCollections()\"\n [getValue]=\"nameOf\"\n [getLabel]=\"nameOf\"\n deleteButtonTitle=\"collections.deleteCollection\"\n [ariaLabel]=\"'collections.label' | transloco\"\n (itemClick)=\"onClick($event)\"\n (delete)=\"onDelete($event)\">\n <ng-template listPanelItem let-collection>\n <ListItemLeft>\n <inbox-icon />\n <ListItemText>{{ collection.name }}</ListItemText>\n </ListItemLeft>\n </ng-template>\n\n <ListPanelEmpty color=\"indigo\">\n <inbox-icon listPanelEmptyIcon />\n {{ \"collections.noCollections\" | transloco }}\n <p listPanelEmptyHint>{{ \"collections.noCollectionsHint\" | transloco }}</p>\n </ListPanelEmpty>\n</ListPanel>\n\n@if (paginatedCollections().length > 0) {\n <ListPanelFooter\n [navigationHint]=\"panel.keyboardFocused()\"\n [hasMore]=\"hasMore()\"\n [config]=\"config\"\n (loadMore)=\"loadMore()\" />\n}\n"
20193
20856
  }]
20194
20857
  }],
20195
20858
  ctorParameters: () => [],
@@ -30670,6 +31333,14 @@ function navigateToSearch(router, search) {
30670
31333
  if (search.queryParams?.filters && search.queryParams?.filters?.length > 0) queryParams.f = JSON.stringify(search.queryParams?.filters);
30671
31334
  router.navigate([search.queryParams?.path], { queryParams });
30672
31335
  }
31336
+ function searchItemSubtext(transloco, search) {
31337
+ const parts = [];
31338
+ const filterCount = search.filterCount || 0;
31339
+ if (filterCount > 0) parts.push(transloco.translate("searches.filterCount", { count: filterCount }));
31340
+ if (search.date) parts.push(getRelativeDate(transloco.getActiveLang(), search.date));
31341
+ const text = parts.join(" · ");
31342
+ return text ? text.charAt(0).toUpperCase() + text.slice(1) : void 0;
31343
+ }
30673
31344
  const RECENT_SEARCHES_OPTIONS = {
30674
31345
  itemsPerPage: 10,
30675
31346
  showLoadMore: true,
@@ -30686,7 +31357,7 @@ var RecentSearchesComponent = class RecentSearchesComponent {
30686
31357
  userSettingsStore = inject(UserSettingsStore);
30687
31358
  transloco = inject(TranslocoService);
30688
31359
  router = inject(Router);
30689
- getRelativeDate = getRelativeDate;
31360
+ subtextOf = (search) => searchItemSubtext(this.transloco, search);
30690
31361
  urlOf = (search) => search.url;
30691
31362
  labelOf = (search) => search.display || search.label || search.url;
30692
31363
  range = signal(10, ...ngDevMode ? [{ debugName: "range" }] : /* istanbul ignore next */ []);
@@ -30759,24 +31430,15 @@ var RecentSearchesComponent = class RecentSearchesComponent {
30759
31430
  <ng-template listPanelItem let-search>
30760
31431
  <ListItemLeft>
30761
31432
  <history-icon />
30762
- <ListItemText>{{ search.display || search.label }}</ListItemText>
31433
+ <ListItemText [subtext]="subtextOf(search)">{{ search.display || search.label }}</ListItemText>
30763
31434
  </ListItemLeft>
30764
-
30765
- @let filterCount = search.filterCount || 0;
30766
- @if (filterCount > 0) {
30767
- <Badge variant="number" fill="secondary" scheme="grey" size="xs" class="flex items-center gap-1" aria-hidden="true">
30768
- <FilterIcon />
30769
- {{ 'searches.filterCount' | transloco: { count: filterCount } }}
30770
- </Badge>
30771
- }
30772
-
30773
- @let date = search.date;
30774
- @if (date) {
30775
- <Tag variant="primary" scheme="sage" size="xs" class="ms-auto shrink-0 first-letter:capitalize">{{ getRelativeDate(transloco.getActiveLang(), date) }}</Tag>
30776
- }
30777
31435
  </ng-template>
30778
31436
 
30779
- {{ "searches.recent.noRecentSearches" | transloco }}
31437
+ <ListPanelEmpty color="blue">
31438
+ <history-icon listPanelEmptyIcon />
31439
+ {{ "searches.recent.noRecentSearches" | transloco }}
31440
+ <p listPanelEmptyHint>{{ "searches.recent.noRecentSearchesHint" | transloco }}</p>
31441
+ </ListPanelEmpty>
30780
31442
  </ListPanel>
30781
31443
 
30782
31444
  @if (paginatedRecentSearches().length > 0) {
@@ -30794,36 +31456,6 @@ var RecentSearchesComponent = class RecentSearchesComponent {
30794
31456
  selector: "history, history-icon, History, HistoryIcon",
30795
31457
  inputs: ["class"]
30796
31458
  },
30797
- {
30798
- kind: "component",
30799
- type: FilterIcon,
30800
- selector: "filter, filter-icon, Filter, FilterIcon",
30801
- inputs: ["class"]
30802
- },
30803
- {
30804
- kind: "directive",
30805
- type: BadgeComponent,
30806
- selector: "badge, Badge",
30807
- inputs: [
30808
- "class",
30809
- "variant",
30810
- "fill",
30811
- "scheme",
30812
- "size",
30813
- "aria-label"
30814
- ]
30815
- },
30816
- {
30817
- kind: "directive",
30818
- type: TagComponent,
30819
- selector: "tag, Tag",
30820
- inputs: [
30821
- "class",
30822
- "variant",
30823
- "scheme",
30824
- "size"
30825
- ]
30826
- },
30827
31459
  {
30828
31460
  kind: "directive",
30829
31461
  type: ListItemLeftDirective,
@@ -30863,6 +31495,12 @@ var RecentSearchesComponent = class RecentSearchesComponent {
30863
31495
  ],
30864
31496
  outputs: ["itemClick", "delete"]
30865
31497
  },
31498
+ {
31499
+ kind: "component",
31500
+ type: ListPanelEmptyComponent,
31501
+ selector: "list-panel-empty, ListPanelEmpty",
31502
+ inputs: ["color"]
31503
+ },
30866
31504
  {
30867
31505
  kind: "component",
30868
31506
  type: ListPanelFooterComponent,
@@ -30896,14 +31534,12 @@ i0.ɵɵngDeclareClassMetadata({
30896
31534
  imports: [
30897
31535
  TranslocoPipe,
30898
31536
  HistoryIcon,
30899
- FilterIcon,
30900
- BadgeComponent,
30901
- TagComponent,
30902
31537
  ListItemLeftDirective,
30903
31538
  ListItemTextComponent,
30904
31539
  ListPanelItemDirective,
30905
31540
  ListPanelHeaderComponent,
30906
31541
  ListPanelComponent,
31542
+ ListPanelEmptyComponent,
30907
31543
  ListPanelFooterComponent
30908
31544
  ],
30909
31545
  template: `
@@ -30920,24 +31556,15 @@ i0.ɵɵngDeclareClassMetadata({
30920
31556
  <ng-template listPanelItem let-search>
30921
31557
  <ListItemLeft>
30922
31558
  <history-icon />
30923
- <ListItemText>{{ search.display || search.label }}</ListItemText>
31559
+ <ListItemText [subtext]="subtextOf(search)">{{ search.display || search.label }}</ListItemText>
30924
31560
  </ListItemLeft>
30925
-
30926
- @let filterCount = search.filterCount || 0;
30927
- @if (filterCount > 0) {
30928
- <Badge variant="number" fill="secondary" scheme="grey" size="xs" class="flex items-center gap-1" aria-hidden="true">
30929
- <FilterIcon />
30930
- {{ 'searches.filterCount' | transloco: { count: filterCount } }}
30931
- </Badge>
30932
- }
30933
-
30934
- @let date = search.date;
30935
- @if (date) {
30936
- <Tag variant="primary" scheme="sage" size="xs" class="ms-auto shrink-0 first-letter:capitalize">{{ getRelativeDate(transloco.getActiveLang(), date) }}</Tag>
30937
- }
30938
31561
  </ng-template>
30939
31562
 
30940
- {{ "searches.recent.noRecentSearches" | transloco }}
31563
+ <ListPanelEmpty color="blue">
31564
+ <history-icon listPanelEmptyIcon />
31565
+ {{ "searches.recent.noRecentSearches" | transloco }}
31566
+ <p listPanelEmptyHint>{{ "searches.recent.noRecentSearchesHint" | transloco }}</p>
31567
+ </ListPanelEmpty>
30941
31568
  </ListPanel>
30942
31569
 
30943
31570
  @if (paginatedRecentSearches().length > 0) {
@@ -31409,7 +32036,7 @@ var SavedSearchesComponent = class SavedSearchesComponent {
31409
32036
  userSettingsStore = inject(UserSettingsStore);
31410
32037
  transloco = inject(TranslocoService);
31411
32038
  router = inject(Router);
31412
- getRelativeDate = getRelativeDate;
32039
+ subtextOf = (search) => searchItemSubtext(this.transloco, search);
31413
32040
  urlOf = (search) => search.url;
31414
32041
  labelOf = (search) => search.display || search.label || search.url;
31415
32042
  range = signal(10, ...ngDevMode ? [{ debugName: "range" }] : /* istanbul ignore next */ []);
@@ -31482,24 +32109,15 @@ var SavedSearchesComponent = class SavedSearchesComponent {
31482
32109
  <ng-template listPanelItem let-search>
31483
32110
  <ListItemLeft>
31484
32111
  <star-icon [solid]="true" />
31485
- <ListItemText>{{ search.display || search.label }}</ListItemText>
32112
+ <ListItemText [subtext]="subtextOf(search)">{{ search.display || search.label }}</ListItemText>
31486
32113
  </ListItemLeft>
31487
-
31488
- @let filterCount = search.filterCount || 0;
31489
- @if (filterCount > 0) {
31490
- <Badge variant="number" fill="secondary" scheme="grey" size="xs" class="flex items-center gap-1" aria-hidden="true">
31491
- <FilterIcon />
31492
- {{ 'searches.filterCount' | transloco: { count: filterCount } }}
31493
- </Badge>
31494
- }
31495
-
31496
- @let date = search.date;
31497
- @if (date) {
31498
- <Tag variant="primary" scheme="sage" size="xs" class="ms-auto shrink-0 first-letter:capitalize">{{ getRelativeDate(transloco.getActiveLang(), date) }}</Tag>
31499
- }
31500
32114
  </ng-template>
31501
32115
 
31502
- {{ 'searches.saved.noSavedSearches' | transloco }}
32116
+ <ListPanelEmpty color="yellow">
32117
+ <star-icon listPanelEmptyIcon [solid]="true" />
32118
+ {{ 'searches.saved.noSavedSearches' | transloco }}
32119
+ <p listPanelEmptyHint>{{ 'searches.saved.noSavedSearchesHint' | transloco }}</p>
32120
+ </ListPanelEmpty>
31503
32121
  </ListPanel>
31504
32122
 
31505
32123
  @if(paginatedSearches().length > 0) {
@@ -31514,36 +32132,6 @@ var SavedSearchesComponent = class SavedSearchesComponent {
31514
32132
  selector: "star, star-icon, Star, StarIcon",
31515
32133
  inputs: ["class", "solid"]
31516
32134
  },
31517
- {
31518
- kind: "component",
31519
- type: FilterIcon,
31520
- selector: "filter, filter-icon, Filter, FilterIcon",
31521
- inputs: ["class"]
31522
- },
31523
- {
31524
- kind: "directive",
31525
- type: BadgeComponent,
31526
- selector: "badge, Badge",
31527
- inputs: [
31528
- "class",
31529
- "variant",
31530
- "fill",
31531
- "scheme",
31532
- "size",
31533
- "aria-label"
31534
- ]
31535
- },
31536
- {
31537
- kind: "directive",
31538
- type: TagComponent,
31539
- selector: "tag, Tag",
31540
- inputs: [
31541
- "class",
31542
- "variant",
31543
- "scheme",
31544
- "size"
31545
- ]
31546
- },
31547
32135
  {
31548
32136
  kind: "directive",
31549
32137
  type: ListItemLeftDirective,
@@ -31583,6 +32171,12 @@ var SavedSearchesComponent = class SavedSearchesComponent {
31583
32171
  ],
31584
32172
  outputs: ["itemClick", "delete"]
31585
32173
  },
32174
+ {
32175
+ kind: "component",
32176
+ type: ListPanelEmptyComponent,
32177
+ selector: "list-panel-empty, ListPanelEmpty",
32178
+ inputs: ["color"]
32179
+ },
31586
32180
  {
31587
32181
  kind: "component",
31588
32182
  type: ListPanelFooterComponent,
@@ -31616,14 +32210,12 @@ i0.ɵɵngDeclareClassMetadata({
31616
32210
  imports: [
31617
32211
  TranslocoPipe,
31618
32212
  StarIcon,
31619
- FilterIcon,
31620
- BadgeComponent,
31621
- TagComponent,
31622
32213
  ListItemLeftDirective,
31623
32214
  ListItemTextComponent,
31624
32215
  ListPanelItemDirective,
31625
32216
  ListPanelHeaderComponent,
31626
32217
  ListPanelComponent,
32218
+ ListPanelEmptyComponent,
31627
32219
  ListPanelFooterComponent
31628
32220
  ],
31629
32221
  template: `
@@ -31640,24 +32232,15 @@ i0.ɵɵngDeclareClassMetadata({
31640
32232
  <ng-template listPanelItem let-search>
31641
32233
  <ListItemLeft>
31642
32234
  <star-icon [solid]="true" />
31643
- <ListItemText>{{ search.display || search.label }}</ListItemText>
32235
+ <ListItemText [subtext]="subtextOf(search)">{{ search.display || search.label }}</ListItemText>
31644
32236
  </ListItemLeft>
31645
-
31646
- @let filterCount = search.filterCount || 0;
31647
- @if (filterCount > 0) {
31648
- <Badge variant="number" fill="secondary" scheme="grey" size="xs" class="flex items-center gap-1" aria-hidden="true">
31649
- <FilterIcon />
31650
- {{ 'searches.filterCount' | transloco: { count: filterCount } }}
31651
- </Badge>
31652
- }
31653
-
31654
- @let date = search.date;
31655
- @if (date) {
31656
- <Tag variant="primary" scheme="sage" size="xs" class="ms-auto shrink-0 first-letter:capitalize">{{ getRelativeDate(transloco.getActiveLang(), date) }}</Tag>
31657
- }
31658
32237
  </ng-template>
31659
32238
 
31660
- {{ 'searches.saved.noSavedSearches' | transloco }}
32239
+ <ListPanelEmpty color="yellow">
32240
+ <star-icon listPanelEmptyIcon [solid]="true" />
32241
+ {{ 'searches.saved.noSavedSearches' | transloco }}
32242
+ <p listPanelEmptyHint>{{ 'searches.saved.noSavedSearchesHint' | transloco }}</p>
32243
+ </ListPanelEmpty>
31661
32244
  </ListPanel>
31662
32245
 
31663
32246
  @if(paginatedSearches().length > 0) {
@@ -32543,6 +33126,147 @@ i0.ɵɵngDeclareClassMetadata({
32543
33126
  }]
32544
33127
  }] }
32545
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
+ });
32546
33270
  const THEMES$1 = [
32547
33271
  "light",
32548
33272
  "dark",
@@ -32879,6 +33603,10 @@ var SettingsDialogComponent = class SettingsDialogComponent {
32879
33603
  id: "appearance",
32880
33604
  labelKey: "settings.appearance.nav"
32881
33605
  },
33606
+ {
33607
+ id: "alerts",
33608
+ labelKey: "settings.alerts.nav"
33609
+ },
32882
33610
  {
32883
33611
  id: "shortcuts",
32884
33612
  labelKey: "settings.shortcuts.nav"
@@ -32959,6 +33687,9 @@ var SettingsDialogComponent = class SettingsDialogComponent {
32959
33687
  @case ("appearance") {
32960
33688
  <palette-icon aria-hidden="true" />
32961
33689
  }
33690
+ @case ("alerts") {
33691
+ <bell-icon aria-hidden="true" />
33692
+ }
32962
33693
  @case ("shortcuts") {
32963
33694
  <keyboard-icon aria-hidden="true" />
32964
33695
  }
@@ -32977,6 +33708,9 @@ var SettingsDialogComponent = class SettingsDialogComponent {
32977
33708
  @case ("appearance") {
32978
33709
  <settings-appearance-panel />
32979
33710
  }
33711
+ @case ("alerts") {
33712
+ <settings-alerts-panel (closeRequested)="dialog.close()" />
33713
+ }
32980
33714
  @case ("shortcuts") {
32981
33715
  <settings-shortcuts-panel />
32982
33716
  }
@@ -33030,6 +33764,12 @@ var SettingsDialogComponent = class SettingsDialogComponent {
33030
33764
  selector: "palette, palette-icon, Palette, PaletteIcon",
33031
33765
  inputs: ["class"]
33032
33766
  },
33767
+ {
33768
+ kind: "component",
33769
+ type: BellIcon,
33770
+ selector: "bell, bell-icon, Bell, BellIcon",
33771
+ inputs: ["class"]
33772
+ },
33033
33773
  {
33034
33774
  kind: "component",
33035
33775
  type: KeyboardIcon,
@@ -33059,6 +33799,12 @@ var SettingsDialogComponent = class SettingsDialogComponent {
33059
33799
  type: SettingsAppearancePanelComponent,
33060
33800
  selector: "settings-appearance-panel"
33061
33801
  },
33802
+ {
33803
+ kind: "component",
33804
+ type: SettingsAlertsPanelComponent,
33805
+ selector: "settings-alerts-panel",
33806
+ outputs: ["closeRequested"]
33807
+ },
33062
33808
  {
33063
33809
  kind: "component",
33064
33810
  type: SettingsShortcutsPanelComponent,
@@ -33088,11 +33834,13 @@ i0.ɵɵngDeclareClassMetadata({
33088
33834
  DialogContentComponent,
33089
33835
  UserIcon,
33090
33836
  PaletteIcon,
33837
+ BellIcon,
33091
33838
  KeyboardIcon,
33092
33839
  XMarkIcon,
33093
33840
  SidebarInsetDirective,
33094
33841
  SettingsAccountPanelComponent,
33095
33842
  SettingsAppearancePanelComponent,
33843
+ SettingsAlertsPanelComponent,
33096
33844
  SettingsShortcutsPanelComponent
33097
33845
  ],
33098
33846
  providers: [provideTranslocoScope("settings")],
@@ -33129,6 +33877,9 @@ i0.ɵɵngDeclareClassMetadata({
33129
33877
  @case ("appearance") {
33130
33878
  <palette-icon aria-hidden="true" />
33131
33879
  }
33880
+ @case ("alerts") {
33881
+ <bell-icon aria-hidden="true" />
33882
+ }
33132
33883
  @case ("shortcuts") {
33133
33884
  <keyboard-icon aria-hidden="true" />
33134
33885
  }
@@ -33147,6 +33898,9 @@ i0.ɵɵngDeclareClassMetadata({
33147
33898
  @case ("appearance") {
33148
33899
  <settings-appearance-panel />
33149
33900
  }
33901
+ @case ("alerts") {
33902
+ <settings-alerts-panel (closeRequested)="dialog.close()" />
33903
+ }
33150
33904
  @case ("shortcuts") {
33151
33905
  <settings-shortcuts-panel />
33152
33906
  }
@@ -33639,6 +34393,6 @@ function provideSessionExpiry(options = {}) {
33639
34393
  const queryNameResolver = () => {
33640
34394
  return inject(AppStore).getDefaultQuery()?.name || "";
33641
34395
  };
33642
- 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 };
33643
34397
 
33644
34398
  //# sourceMappingURL=sinequa-atomic-angular.mjs.map