@sinequa/atomic-angular 2.7.0 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,70 @@ 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
+ const ALREADY_NOTIFIED = [
16267
+ 400,
16268
+ 403,
16269
+ 500,
16270
+ 503
16271
+ ];
16272
+ function saveErrorDetail(error) {
16273
+ const { errorMessage, message } = error ?? {};
16274
+ return errorMessage ?? message;
16275
+ }
16276
+ function notifySaveFailed(transloco, error) {
16277
+ const { status } = error ?? {};
16278
+ if (status !== void 0 && ALREADY_NOTIFIED.includes(status)) return;
16279
+ notify.error(transloco.translate("alerts.saveFailed"), {
16280
+ description: saveErrorDetail(error),
16281
+ closeButton: true,
16282
+ duration: 5e3
16283
+ });
16284
+ }
16099
16285
  const WEEKDAYS = [
16100
16286
  {
16101
16287
  key: "monday",
@@ -16170,6 +16356,7 @@ var AlertDialog = class AlertDialog {
16170
16356
  alert = signal(void 0, ...ngDevMode ? [{ debugName: "alert" }] : /* istanbul ignore next */ []);
16171
16357
  canUpdateQuery = signal(false, ...ngDevMode ? [{ debugName: "canUpdateQuery" }] : /* istanbul ignore next */ []);
16172
16358
  updateStatus = signal("idle", ...ngDevMode ? [{ debugName: "updateStatus" }] : /* istanbul ignore next */ []);
16359
+ saveError = signal(void 0, ...ngDevMode ? [{ debugName: "saveError" }] : /* istanbul ignore next */ []);
16173
16360
  alertModel = signal(emptyAlertModel(), ...ngDevMode ? [{ debugName: "alertModel" }] : /* istanbul ignore next */ []);
16174
16361
  alertForm = form(this.alertModel, alertSchema, {
16175
16362
  name: "alert",
@@ -16198,8 +16385,14 @@ var AlertDialog = class AlertDialog {
16198
16385
  this.dialog()?.open();
16199
16386
  }
16200
16387
  async persist() {
16201
- if (this.alert()) await this.update();
16202
- else await this.create();
16388
+ this.saveError.set(void 0);
16389
+ try {
16390
+ if (this.alert()) await this.update();
16391
+ else await this.create();
16392
+ } catch (error) {
16393
+ this.saveError.set({ detail: saveErrorDetail(error) });
16394
+ return;
16395
+ }
16203
16396
  this.dialog()?.close("dialog-confirm");
16204
16397
  }
16205
16398
  async create() {
@@ -16218,19 +16411,21 @@ var AlertDialog = class AlertDialog {
16218
16411
  combine: true,
16219
16412
  respectTabSelection: false
16220
16413
  };
16221
- this.userSettingsStore.createAlert(alert);
16414
+ await this.userSettingsStore.createAlert(alert);
16222
16415
  }
16223
16416
  async update() {
16224
16417
  const alert = this.alert();
16225
16418
  const index = this.index();
16226
16419
  if (!alert || index === void 0) return;
16227
16420
  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);
16421
+ await this.userSettingsStore.updateAlert({
16422
+ ...alert,
16423
+ name: model.name.trim(),
16424
+ frequency: model.frequency ?? Alert.Frequency.Daily,
16425
+ days: flagsFromWeekdays(model.days),
16426
+ times: model.time,
16427
+ active: model.active
16428
+ }, index);
16234
16429
  }
16235
16430
  showError(field) {
16236
16431
  return fieldShowsError(field);
@@ -16242,9 +16437,12 @@ var AlertDialog = class AlertDialog {
16242
16437
  const alert = this.alert();
16243
16438
  const index = this.index();
16244
16439
  if (!alert || index === void 0) return;
16440
+ this.saveError.set(void 0);
16245
16441
  this.updateStatus.set("updating");
16246
- alert.query = this.queryParamsStore.getQuery();
16247
- this.userSettingsStore.updateAlert(alert, index).then(() => {
16442
+ this.userSettingsStore.updateAlert({
16443
+ ...alert,
16444
+ query: this.queryParamsStore.getQuery()
16445
+ }, index).then(() => {
16248
16446
  setTimeout(() => {
16249
16447
  this.updateStatus.set("updated");
16250
16448
  }, 500);
@@ -16252,6 +16450,9 @@ var AlertDialog = class AlertDialog {
16252
16450
  setTimeout(() => {
16253
16451
  this.updateStatus.set("idle");
16254
16452
  }, 2e3);
16453
+ }).catch((error) => {
16454
+ this.updateStatus.set("idle");
16455
+ this.saveError.set({ detail: saveErrorDetail(error) });
16255
16456
  });
16256
16457
  }
16257
16458
  execute() {
@@ -16404,6 +16605,25 @@ var AlertDialog = class AlertDialog {
16404
16605
  </DialogBody>
16405
16606
 
16406
16607
  <DialogFooter class="flex-col">
16608
+ <!-- Shown here and not as a toast: the dialog is modal, so it is in the top layer, above any toast. -->
16609
+ @if (saveError(); as failure) {
16610
+ <div class="save-error w-full">
16611
+ <div class="min-h-0 overflow-hidden">
16612
+ <div
16613
+ id="alertSaveError"
16614
+ role="alert"
16615
+ class="flex w-full items-start gap-(--spacing-sm) rounded-(--radius-md) border border-(--stroke-error-base) bg-(--bg-error-lightest) p-(--spacing-md) text-(--font-error-base)">
16616
+ <TriangleAlert class="mt-0.5 size-4 shrink-0" width="16" height="16" aria-hidden="true" />
16617
+ <div class="flex min-w-0 flex-col gap-(--spacing-2xs)">
16618
+ <p class="m-0 font-medium">{{ 'alerts.saveFailed' | transloco }}</p>
16619
+ @if (failure.detail) {
16620
+ <p class="m-0 text-(length:--size-sm) wrap-anywhere opacity-80">{{ failure.detail }}</p>
16621
+ }
16622
+ </div>
16623
+ </div>
16624
+ </div>
16625
+ </div>
16626
+ }
16407
16627
  @if (alert() || canUpdateQuery()) {
16408
16628
  <div class="flex w-full flex-col gap-2">
16409
16629
  @if (alert()) {
@@ -16450,7 +16670,7 @@ var AlertDialog = class AlertDialog {
16450
16670
  </dialog>
16451
16671
  `,
16452
16672
  isInline: true,
16453
- styles: [".weekdays-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(100px,1fr))}\n"],
16673
+ styles: [".weekdays-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(100px,1fr))}.save-error{display:grid;animation:save-error-in .22s ease-out}.save-error>div>div{animation:save-error-card-in .22s ease-out}@keyframes save-error-in{0%{grid-template-rows:0fr}to{grid-template-rows:1fr}}@keyframes save-error-card-in{0%{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:none}}@media(prefers-reduced-motion:reduce){.save-error,.save-error>div>div{animation:none}}\n"],
16454
16674
  dependencies: [
16455
16675
  {
16456
16676
  kind: "directive",
@@ -16610,6 +16830,12 @@ var AlertDialog = class AlertDialog {
16610
16830
  selector: "circle-check, circle-check-icon, CircleCheck, CircleCheckIcon",
16611
16831
  inputs: ["class"]
16612
16832
  },
16833
+ {
16834
+ kind: "component",
16835
+ type: TriangleAlertIcon,
16836
+ selector: "triangle-alert, triangle-alert-icon, TriangleAlert, TriangleAlertIcon",
16837
+ inputs: ["class"]
16838
+ },
16613
16839
  {
16614
16840
  kind: "pipe",
16615
16841
  type: TranslocoPipe,
@@ -16646,7 +16872,8 @@ i0.ɵɵngDeclareClassMetadata({
16646
16872
  ChevronDownIcon,
16647
16873
  CheckIcon,
16648
16874
  LoadingCircleIcon,
16649
- CircleCheckIcon
16875
+ CircleCheckIcon,
16876
+ TriangleAlertIcon
16650
16877
  ],
16651
16878
  providers: [provideTranslocoScope("alerts")],
16652
16879
  template: `
@@ -16761,6 +16988,25 @@ i0.ɵɵngDeclareClassMetadata({
16761
16988
  </DialogBody>
16762
16989
 
16763
16990
  <DialogFooter class="flex-col">
16991
+ <!-- Shown here and not as a toast: the dialog is modal, so it is in the top layer, above any toast. -->
16992
+ @if (saveError(); as failure) {
16993
+ <div class="save-error w-full">
16994
+ <div class="min-h-0 overflow-hidden">
16995
+ <div
16996
+ id="alertSaveError"
16997
+ role="alert"
16998
+ class="flex w-full items-start gap-(--spacing-sm) rounded-(--radius-md) border border-(--stroke-error-base) bg-(--bg-error-lightest) p-(--spacing-md) text-(--font-error-base)">
16999
+ <TriangleAlert class="mt-0.5 size-4 shrink-0" width="16" height="16" aria-hidden="true" />
17000
+ <div class="flex min-w-0 flex-col gap-(--spacing-2xs)">
17001
+ <p class="m-0 font-medium">{{ 'alerts.saveFailed' | transloco }}</p>
17002
+ @if (failure.detail) {
17003
+ <p class="m-0 text-(length:--size-sm) wrap-anywhere opacity-80">{{ failure.detail }}</p>
17004
+ }
17005
+ </div>
17006
+ </div>
17007
+ </div>
17008
+ </div>
17009
+ }
16764
17010
  @if (alert() || canUpdateQuery()) {
16765
17011
  <div class="flex w-full flex-col gap-2">
16766
17012
  @if (alert()) {
@@ -16806,7 +17052,7 @@ i0.ɵɵngDeclareClassMetadata({
16806
17052
  </DialogContent>
16807
17053
  </dialog>
16808
17054
  `,
16809
- styles: [".weekdays-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(100px,1fr))}\n"]
17055
+ styles: [".weekdays-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(100px,1fr))}.save-error{display:grid;animation:save-error-in .22s ease-out}.save-error>div>div{animation:save-error-card-in .22s ease-out}@keyframes save-error-in{0%{grid-template-rows:0fr}to{grid-template-rows:1fr}}@keyframes save-error-card-in{0%{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:none}}@media(prefers-reduced-motion:reduce){.save-error,.save-error>div>div{animation:none}}\n"]
16810
17056
  }]
16811
17057
  }],
16812
17058
  ctorParameters: () => [],
@@ -16816,6 +17062,7 @@ i0.ɵɵngDeclareClassMetadata({
16816
17062
  }] }
16817
17063
  });
16818
17064
  const AlertForm = createCallable(AlertDialog);
17065
+ const UNDO_DELAY = 6e3;
16819
17066
  var AlertsComponent = class AlertsComponent {
16820
17067
  floating = inject(PopoverDirective, {
16821
17068
  skipSelf: true,
@@ -16823,17 +17070,42 @@ var AlertsComponent = class AlertsComponent {
16823
17070
  });
16824
17071
  userSettingsStore = inject(UserSettingsStore);
16825
17072
  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 = [];
17073
+ transloco = inject(TranslocoService);
17074
+ lang = toSignal(this.transloco.langChanges$);
16829
17075
  query;
17076
+ alerts = computed(() => this.userSettingsStore.alerts() ?? [], ...ngDevMode ? [{ debugName: "alerts" }] : /* istanbul ignore next */ []);
17077
+ order = linkedSignal(() => [...this.alerts()], ...ngDevMode ? [{ debugName: "order" }] : /* istanbul ignore next */ []);
17078
+ hidden = signal(/* @__PURE__ */ new Set(), ...ngDevMode ? [{ debugName: "hidden" }] : /* istanbul ignore next */ []);
17079
+ pendingDeletion = signal(void 0, ...ngDevMode ? [{ debugName: "pendingDeletion" }] : /* istanbul ignore next */ []);
17080
+ undoTimer;
17081
+ visible = computed(() => {
17082
+ const hidden = this.hidden();
17083
+ return this.order().filter((alert) => !hidden.has(alert));
17084
+ }, ...ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []);
17085
+ summaries = computed(() => {
17086
+ const lang = this.lang() ?? this.transloco.getActiveLang();
17087
+ return new Map(this.order().map((alert) => [alert, alertSummary(lang, alert)]));
17088
+ }, ...ngDevMode ? [{ debugName: "summaries" }] : /* istanbul ignore next */ []);
17089
+ summaryOf = (alert) => this.summaries().get(alert) ?? [];
17090
+ announceMove = (alert, position, total) => this.transloco.translate("alerts.moved", {
17091
+ name: alert.name,
17092
+ position,
17093
+ total
17094
+ });
16830
17095
  constructor() {
16831
17096
  this.query = buildQuery();
16832
- effect(() => {
16833
- this.tmpAlerts = this.alerts()?.map((a) => Object.assign({}, a));
16834
- });
17097
+ inject(DestroyRef).onDestroy(() => this.commitDeletion());
17098
+ }
17099
+ onRowClick(event, alert) {
17100
+ if (event.target.closest("[data-row-action]")) return;
17101
+ this.edit(alert);
17102
+ }
17103
+ onRowEnter(event, alert) {
17104
+ if (event.target === event.currentTarget) this.edit(alert);
16835
17105
  }
16836
- onClick(index) {
17106
+ edit(alert) {
17107
+ const index = this.alerts().indexOf(alert);
17108
+ if (index < 0) return;
16837
17109
  this.floating?.close();
16838
17110
  AlertForm.call(index, { injector: this.injector });
16839
17111
  }
@@ -16841,19 +17113,73 @@ var AlertsComponent = class AlertsComponent {
16841
17113
  this.floating?.close();
16842
17114
  AlertForm.call(void 0, { injector: this.injector });
16843
17115
  }
16844
- deleteAlert(event, index) {
16845
- event.stopPropagation();
16846
- this.userSettingsStore.deleteAlert(index);
17116
+ pendingActive = signal(/* @__PURE__ */ new Map(), ...ngDevMode ? [{ debugName: "pendingActive" }] : /* istanbul ignore next */ []);
17117
+ isActive(alert) {
17118
+ return this.pendingActive().get(alert) ?? alert.active;
17119
+ }
17120
+ async toggle(alert, active) {
17121
+ const index = this.alerts().indexOf(alert);
17122
+ if (index < 0) return;
17123
+ this.pendingActive.update((pending) => new Map(pending).set(alert, active));
17124
+ try {
17125
+ await this.userSettingsStore.updateAlert({
17126
+ ...alert,
17127
+ active
17128
+ }, index);
17129
+ } catch (error) {
17130
+ notifySaveFailed(this.transloco, error);
17131
+ } finally {
17132
+ this.pendingActive.update((pending) => {
17133
+ const next = new Map(pending);
17134
+ next.delete(alert);
17135
+ return next;
17136
+ });
17137
+ }
17138
+ }
17139
+ deleteAlert(alert) {
17140
+ this.commitDeletion();
17141
+ this.hidden.update((hidden) => new Set(hidden).add(alert));
17142
+ this.pendingDeletion.set(alert);
17143
+ this.undoTimer = setTimeout(() => this.commitDeletion(), UNDO_DELAY);
17144
+ }
17145
+ undoDeletion() {
17146
+ clearTimeout(this.undoTimer);
17147
+ const alert = this.pendingDeletion();
17148
+ if (!alert) return;
17149
+ this.pendingDeletion.set(void 0);
17150
+ this.unhide(alert);
17151
+ }
17152
+ commitDeletion() {
17153
+ clearTimeout(this.undoTimer);
17154
+ const alert = this.pendingDeletion();
17155
+ if (!alert) return;
17156
+ this.pendingDeletion.set(void 0);
17157
+ const index = this.alerts().indexOf(alert);
17158
+ if (index < 0) {
17159
+ this.unhide(alert);
17160
+ return;
17161
+ }
17162
+ this.userSettingsStore.deleteAlert(index).catch((error) => notifySaveFailed(this.transloco, error)).finally(() => this.unhide(alert));
16847
17163
  }
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);
17164
+ unhide(alert) {
17165
+ this.hidden.update((hidden) => {
17166
+ const next = new Set(hidden);
17167
+ next.delete(alert);
17168
+ return next;
17169
+ });
16853
17170
  }
16854
- dropped(drop) {
16855
- if (drop.currentIndex === drop.previousIndex) return;
16856
- this.tmpAlerts.splice(drop.currentIndex, 0, this.tmpAlerts.splice(drop.previousIndex, 1)[0]);
17171
+ async saveOrder(shown) {
17172
+ const hidden = this.hidden();
17173
+ const previous = this.order();
17174
+ const next = [...shown, ...previous.filter((alert) => hidden.has(alert))];
17175
+ if (next.every((alert, index) => alert === previous[index])) return;
17176
+ this.order.set(next);
17177
+ try {
17178
+ await this.userSettingsStore.updateAlerts(next);
17179
+ } catch (error) {
17180
+ this.order.set(previous);
17181
+ notifySaveFailed(this.transloco, error);
17182
+ }
16857
17183
  }
16858
17184
  static ɵfac = i0.ɵɵngDeclareFactory({
16859
17185
  minVersion: "12.0.0",
@@ -16871,7 +17197,7 @@ var AlertsComponent = class AlertsComponent {
16871
17197
  selector: "Alerts",
16872
17198
  providers: [provideTranslocoScope("alerts")],
16873
17199
  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",
17200
+ template: "<div class=\"flex min-w-80 flex-col gap-(--spacing-3xs) p-2\">\n <div class=\"flex items-center gap-(--spacing-2xs) ps-(--spacing-2xs)\">\n @if (floating) {\n <!-- Not an <h2>: a page's prose styles put a rule and margins on every h2, even inside a popover. -->\n <span\n role=\"heading\"\n aria-level=\"2\"\n class=\"flex-1 text-xl leading-(--line-height-4) font-bold\">\n {{ \"alerts.label\" | transloco }}\n @if (visible().length) {\n <span\n class=\"text-(length:--size-sm) font-normal text-(--font-neutral-muted)\"\n >· {{ visible().length }}</span\n >\n }\n </span>\n } @else {\n <span class=\"flex-1\"></span>\n }\n <!-- With no alert, \"Create\" is the call to action of the empty state, below. -->\n @if (visible().length) {\n <button size=\"md\" (click)=\"createAlert()\">\n <plus-icon />\n {{ \"alerts.create\" | transloco }}\n </button>\n }\n </div>\n\n <ul\n role=\"list\"\n class=\"m-0 flex list-none flex-col p-0\"\n [attr.aria-label]=\"'alerts.label' | transloco\"\n [sortable]=\"visible()\"\n #sortable=\"sortable\"\n [sortableAnnounce]=\"announceMove\"\n (reorder)=\"saveOrder($event)\">\n @for (alert of sortable.items(); track alert.name) {\n @let name = alert.name;\n <li\n [sortableItem]=\"alert\"\n class=\"group relative flex min-h-12 w-full cursor-pointer items-center gap-(--spacing-3xs) rounded-(--radius-sm) py-(--spacing-3xs) ps-(--spacing-3xs) pe-(--spacing-2xs) text-(--font-neutral-base) select-none hover:bg-(--bg-primary-lighter) focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-(--stroke-focus) active:bg-(--bg-primary-light) data-dragging:bg-(--bg-primary-lighter) data-dragging:outline-2 data-dragging:-outline-offset-2 data-dragging:outline-(--stroke-primary-base) data-dragging:outline-dashed data-dragging:*:invisible\"\n tabindex=\"0\"\n [attr.data-inactive]=\"alert.active ? null : ''\"\n (click)=\"onRowClick($event, alert)\"\n (keydown.enter)=\"onRowEnter($event, alert)\">\n <!-- Handle: shown when the row is hovered or focused, only if there is something to reorder. -->\n <grip-vertical-icon\n class=\"shrink-0 cursor-grab text-(--font-neutral-muted) opacity-0 group-focus-within:opacity-100 group-hover:opacity-100 pointer-coarse:hidden\"\n [class.invisible]=\"visible().length < 2\" />\n @if (alert.active) {\n <bell-icon class=\"shrink-0\" />\n } @else {\n <bell-slash-icon class=\"shrink-0 text-(--font-neutral-muted)\" />\n }\n <span class=\"flex min-w-0 flex-1 flex-col\">\n <span\n class=\"line-clamp-1 text-(length:--size-base) group-data-inactive:text-(--font-neutral-muted)\"\n >{{ name }}</span\n >\n @if (summaryOf(alert).length) {\n <span\n class=\"line-clamp-1 text-(length:--size-sm) text-(--font-neutral-muted)\">\n @for (\n part of summaryOf(alert);\n track $index;\n let first = $first\n ) {\n {{ first ? \"\" : \" · \"\n }}{{ part.key ? (part.key | transloco) : part.text }}\n }\n </span>\n }\n </span>\n <span data-row-action class=\"flex shrink-0 items-center\">\n <Switch\n [checked]=\"isActive(alert)\"\n [aria-label]=\"'alerts.alertActiveFor' | transloco: { name }\"\n (checkedChange)=\"toggle(alert, $event)\" />\n </span>\n <span data-row-action class=\"flex shrink-0 items-center\">\n <button\n variant=\"tertiary\"\n size=\"sm\"\n [iconOnly]=\"true\"\n class=\"text-(--font-error-muted) opacity-0 group-focus-within:opacity-100 group-hover:opacity-100 hover:bg-(--bg-error-lightest) hover:text-(--font-error-base) focus-visible:opacity-100 pointer-coarse:opacity-100\"\n [attr.title]=\"'alerts.deleteAlertNamed' | transloco: { name }\"\n [attr.aria-label]=\"'alerts.deleteAlertNamed' | transloco: { name }\"\n (click)=\"deleteAlert(alert)\">\n <TrashIcon />\n </button>\n </span>\n </li>\n } @empty {\n <li class=\"list-none\">\n <ListPanelEmpty color=\"orange\">\n <bell-icon listPanelEmptyIcon />\n {{ \"alerts.noAlerts\" | transloco }}\n <p listPanelEmptyHint>{{ \"alerts.noAlertsHint\" | transloco }}</p>\n <button listPanelEmptyAction (click)=\"createAlert()\">\n <plus-icon />\n {{ \"alerts.createAlert\" | transloco }}\n </button>\n </ListPanelEmpty>\n </li>\n }\n </ul>\n\n @if (visible().length > 1) {\n <p\n class=\"m-0 ps-(--spacing-2xs) pt-(--spacing-3xs) text-(length:--size-sm) text-(--font-neutral-muted)\">\n {{ \"alerts.reorderHint\" | transloco }}\n </p>\n }\n\n <!-- The regions exist before their text does, which is what makes a screen reader announce the text. -->\n <div role=\"status\">\n @if (pendingDeletion(); as pending) {\n <div\n class=\"mt-(--spacing-2xs) flex items-center gap-(--spacing-2xs) rounded-(--radius-sm) bg-(--bg-neutral-base) py-(--spacing-3xs) ps-(--spacing-xs) pe-(--spacing-3xs) text-(length:--size-sm) text-(--font-neutral-base)\">\n <span class=\"min-w-0 flex-1\">{{\n \"alerts.deleted\" | transloco: { name: pending.name }\n }}</span>\n <button variant=\"tertiary\" size=\"sm\" (click)=\"undoDeletion()\">\n {{ \"alerts.undo\" | transloco }}\n </button>\n </div>\n }\n </div>\n</div>\n",
16875
17201
  dependencies: [
16876
17202
  {
16877
17203
  kind: "directive",
@@ -16886,63 +17212,39 @@ var AlertsComponent = class AlertsComponent {
16886
17212
  ]
16887
17213
  },
16888
17214
  {
16889
- kind: "ngmodule",
16890
- type: DragDropModule
17215
+ kind: "component",
17216
+ type: SwitchComponent,
17217
+ selector: "switch, Switch",
17218
+ inputs: [
17219
+ "checked",
17220
+ "disabled",
17221
+ "size",
17222
+ "class",
17223
+ "aria-label",
17224
+ "aria-labelledby",
17225
+ "presentational",
17226
+ "compact",
17227
+ "tabIndex"
17228
+ ],
17229
+ outputs: ["checkedChange"]
16891
17230
  },
16892
17231
  {
16893
17232
  kind: "directive",
16894
- type: i1.CdkDropList,
16895
- selector: "[cdkDropList], cdk-drop-list",
17233
+ type: SortableDirective,
17234
+ selector: "[sortable]",
16896
17235
  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"
17236
+ "sortable",
17237
+ "sortableDisabled",
17238
+ "sortableAnnounce"
16910
17239
  ],
16911
- outputs: [
16912
- "cdkDropListDropped",
16913
- "cdkDropListEntered",
16914
- "cdkDropListExited",
16915
- "cdkDropListSorted"
16916
- ],
16917
- exportAs: ["cdkDropList"]
17240
+ outputs: ["reorder"],
17241
+ exportAs: ["sortable"]
16918
17242
  },
16919
17243
  {
16920
17244
  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"]
17245
+ type: SortableItemDirective,
17246
+ selector: "[sortableItem]",
17247
+ inputs: ["sortableItem", "sortableItemDisabled"]
16946
17248
  },
16947
17249
  {
16948
17250
  kind: "component",
@@ -16950,6 +17252,24 @@ var AlertsComponent = class AlertsComponent {
16950
17252
  selector: "bell, bell-icon, Bell, BellIcon",
16951
17253
  inputs: ["class"]
16952
17254
  },
17255
+ {
17256
+ kind: "component",
17257
+ type: BellSlashIcon,
17258
+ selector: "bell-slash, bell-slash-icon, BellSlash, BellSlashIcon",
17259
+ inputs: ["class"]
17260
+ },
17261
+ {
17262
+ kind: "component",
17263
+ type: GripVerticalIcon,
17264
+ selector: "grip-vertical, grip-vertical-icon, GripVertical, GripVerticalIcon",
17265
+ inputs: ["class"]
17266
+ },
17267
+ {
17268
+ kind: "component",
17269
+ type: PlusIcon,
17270
+ selector: "plus, plus-icon, Plus, PlusIcon",
17271
+ inputs: ["class"]
17272
+ },
16953
17273
  {
16954
17274
  kind: "component",
16955
17275
  type: TrashIcon,
@@ -16958,9 +17278,9 @@ var AlertsComponent = class AlertsComponent {
16958
17278
  },
16959
17279
  {
16960
17280
  kind: "component",
16961
- type: BarsIcon,
16962
- selector: "bars, bars-icon, Bars, BarsIcon",
16963
- inputs: ["class"]
17281
+ type: ListPanelEmptyComponent,
17282
+ selector: "list-panel-empty, ListPanelEmpty",
17283
+ inputs: ["color"]
16964
17284
  },
16965
17285
  {
16966
17286
  kind: "pipe",
@@ -16982,13 +17302,18 @@ i0.ɵɵngDeclareClassMetadata({
16982
17302
  imports: [
16983
17303
  TranslocoPipe,
16984
17304
  ButtonComponent,
16985
- DragDropModule,
17305
+ SwitchComponent,
17306
+ SortableDirective,
17307
+ SortableItemDirective,
16986
17308
  BellIcon,
17309
+ BellSlashIcon,
17310
+ GripVerticalIcon,
17311
+ PlusIcon,
16987
17312
  TrashIcon,
16988
- BarsIcon
17313
+ ListPanelEmptyComponent
16989
17314
  ],
16990
17315
  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"
17316
+ template: "<div class=\"flex min-w-80 flex-col gap-(--spacing-3xs) p-2\">\n <div class=\"flex items-center gap-(--spacing-2xs) ps-(--spacing-2xs)\">\n @if (floating) {\n <!-- Not an <h2>: a page's prose styles put a rule and margins on every h2, even inside a popover. -->\n <span\n role=\"heading\"\n aria-level=\"2\"\n class=\"flex-1 text-xl leading-(--line-height-4) font-bold\">\n {{ \"alerts.label\" | transloco }}\n @if (visible().length) {\n <span\n class=\"text-(length:--size-sm) font-normal text-(--font-neutral-muted)\"\n >· {{ visible().length }}</span\n >\n }\n </span>\n } @else {\n <span class=\"flex-1\"></span>\n }\n <!-- With no alert, \"Create\" is the call to action of the empty state, below. -->\n @if (visible().length) {\n <button size=\"md\" (click)=\"createAlert()\">\n <plus-icon />\n {{ \"alerts.create\" | transloco }}\n </button>\n }\n </div>\n\n <ul\n role=\"list\"\n class=\"m-0 flex list-none flex-col p-0\"\n [attr.aria-label]=\"'alerts.label' | transloco\"\n [sortable]=\"visible()\"\n #sortable=\"sortable\"\n [sortableAnnounce]=\"announceMove\"\n (reorder)=\"saveOrder($event)\">\n @for (alert of sortable.items(); track alert.name) {\n @let name = alert.name;\n <li\n [sortableItem]=\"alert\"\n class=\"group relative flex min-h-12 w-full cursor-pointer items-center gap-(--spacing-3xs) rounded-(--radius-sm) py-(--spacing-3xs) ps-(--spacing-3xs) pe-(--spacing-2xs) text-(--font-neutral-base) select-none hover:bg-(--bg-primary-lighter) focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-(--stroke-focus) active:bg-(--bg-primary-light) data-dragging:bg-(--bg-primary-lighter) data-dragging:outline-2 data-dragging:-outline-offset-2 data-dragging:outline-(--stroke-primary-base) data-dragging:outline-dashed data-dragging:*:invisible\"\n tabindex=\"0\"\n [attr.data-inactive]=\"alert.active ? null : ''\"\n (click)=\"onRowClick($event, alert)\"\n (keydown.enter)=\"onRowEnter($event, alert)\">\n <!-- Handle: shown when the row is hovered or focused, only if there is something to reorder. -->\n <grip-vertical-icon\n class=\"shrink-0 cursor-grab text-(--font-neutral-muted) opacity-0 group-focus-within:opacity-100 group-hover:opacity-100 pointer-coarse:hidden\"\n [class.invisible]=\"visible().length < 2\" />\n @if (alert.active) {\n <bell-icon class=\"shrink-0\" />\n } @else {\n <bell-slash-icon class=\"shrink-0 text-(--font-neutral-muted)\" />\n }\n <span class=\"flex min-w-0 flex-1 flex-col\">\n <span\n class=\"line-clamp-1 text-(length:--size-base) group-data-inactive:text-(--font-neutral-muted)\"\n >{{ name }}</span\n >\n @if (summaryOf(alert).length) {\n <span\n class=\"line-clamp-1 text-(length:--size-sm) text-(--font-neutral-muted)\">\n @for (\n part of summaryOf(alert);\n track $index;\n let first = $first\n ) {\n {{ first ? \"\" : \" · \"\n }}{{ part.key ? (part.key | transloco) : part.text }}\n }\n </span>\n }\n </span>\n <span data-row-action class=\"flex shrink-0 items-center\">\n <Switch\n [checked]=\"isActive(alert)\"\n [aria-label]=\"'alerts.alertActiveFor' | transloco: { name }\"\n (checkedChange)=\"toggle(alert, $event)\" />\n </span>\n <span data-row-action class=\"flex shrink-0 items-center\">\n <button\n variant=\"tertiary\"\n size=\"sm\"\n [iconOnly]=\"true\"\n class=\"text-(--font-error-muted) opacity-0 group-focus-within:opacity-100 group-hover:opacity-100 hover:bg-(--bg-error-lightest) hover:text-(--font-error-base) focus-visible:opacity-100 pointer-coarse:opacity-100\"\n [attr.title]=\"'alerts.deleteAlertNamed' | transloco: { name }\"\n [attr.aria-label]=\"'alerts.deleteAlertNamed' | transloco: { name }\"\n (click)=\"deleteAlert(alert)\">\n <TrashIcon />\n </button>\n </span>\n </li>\n } @empty {\n <li class=\"list-none\">\n <ListPanelEmpty color=\"orange\">\n <bell-icon listPanelEmptyIcon />\n {{ \"alerts.noAlerts\" | transloco }}\n <p listPanelEmptyHint>{{ \"alerts.noAlertsHint\" | transloco }}</p>\n <button listPanelEmptyAction (click)=\"createAlert()\">\n <plus-icon />\n {{ \"alerts.createAlert\" | transloco }}\n </button>\n </ListPanelEmpty>\n </li>\n }\n </ul>\n\n @if (visible().length > 1) {\n <p\n class=\"m-0 ps-(--spacing-2xs) pt-(--spacing-3xs) text-(length:--size-sm) text-(--font-neutral-muted)\">\n {{ \"alerts.reorderHint\" | transloco }}\n </p>\n }\n\n <!-- The regions exist before their text does, which is what makes a screen reader announce the text. -->\n <div role=\"status\">\n @if (pendingDeletion(); as pending) {\n <div\n class=\"mt-(--spacing-2xs) flex items-center gap-(--spacing-2xs) rounded-(--radius-sm) bg-(--bg-neutral-base) py-(--spacing-3xs) ps-(--spacing-xs) pe-(--spacing-3xs) text-(length:--size-sm) text-(--font-neutral-base)\">\n <span class=\"min-w-0 flex-1\">{{\n \"alerts.deleted\" | transloco: { name: pending.name }\n }}</span>\n <button variant=\"tertiary\" size=\"sm\" (click)=\"undoDeletion()\">\n {{ \"alerts.undo\" | transloco }}\n </button>\n </div>\n }\n </div>\n</div>\n"
16992
17317
  }]
16993
17318
  }],
16994
17319
  ctorParameters: () => []
@@ -19105,9 +19430,9 @@ var ListPanelComponent = class ListPanelComponent {
19105
19430
  }
19106
19431
  </ListItem>
19107
19432
  } @empty {
19108
- <span class="py-4 text-center text-neutral-500">
19433
+ <div class="py-(--spacing-xs) text-center text-(--font-neutral-base)">
19109
19434
  <ng-content />
19110
- </span>
19435
+ </div>
19111
19436
  }
19112
19437
  </List>
19113
19438
  `,
@@ -19212,9 +19537,9 @@ i0.ɵɵngDeclareClassMetadata({
19212
19537
  }
19213
19538
  </ListItem>
19214
19539
  } @empty {
19215
- <span class="py-4 text-center text-neutral-500">
19540
+ <div class="py-(--spacing-xs) text-center text-(--font-neutral-base)">
19216
19541
  <ng-content />
19217
- </span>
19542
+ </div>
19218
19543
  }
19219
19544
  </List>
19220
19545
  `,
@@ -19357,7 +19682,7 @@ var ListPanelFooterComponent = class ListPanelFooterComponent {
19357
19682
  }
19358
19683
  },
19359
19684
  outputs: { loadMore: "loadMore" },
19360
- host: { properties: { "class": "cn('flex flex-col px-2 mt-2 py-(--spacing-xs)', class())" } },
19685
+ host: { properties: { "class": "cn('flex flex-col gap-(--spacing-xs) px-2 mt-2 py-(--spacing-xs)', class())" } },
19361
19686
  providers: [provideTranslocoScope("list-panel")],
19362
19687
  ngImport: i0,
19363
19688
  template: `
@@ -19509,7 +19834,7 @@ i0.ɵɵngDeclareClassMetadata({
19509
19834
  </button>
19510
19835
  </div>
19511
19836
  `,
19512
- host: { "[class]": "cn('flex flex-col px-2 mt-2 py-(--spacing-xs)', class())" }
19837
+ host: { "[class]": "cn('flex flex-col gap-(--spacing-xs) px-2 mt-2 py-(--spacing-xs)', class())" }
19513
19838
  }]
19514
19839
  }],
19515
19840
  propDecorators: {
@@ -19588,8 +19913,8 @@ var ListPanelHeaderComponent = class ListPanelHeaderComponent {
19588
19913
  template: `
19589
19914
  @if (floating()) {
19590
19915
  <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)" />
19916
+ <!-- Not an <h2>: a page's prose styles put a rule and margins on every h2, even inside a popover. -->
19917
+ <span role="heading" aria-level="2" class="block text-xl leading-(--line-height-4) font-bold">{{ label() }}</span>
19593
19918
  </div>
19594
19919
  }
19595
19920
  `,
@@ -19608,8 +19933,8 @@ i0.ɵɵngDeclareClassMetadata({
19608
19933
  template: `
19609
19934
  @if (floating()) {
19610
19935
  <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)" />
19936
+ <!-- Not an <h2>: a page's prose styles put a rule and margins on every h2, even inside a popover. -->
19937
+ <span role="heading" aria-level="2" class="block text-xl leading-(--line-height-4) font-bold">{{ label() }}</span>
19613
19938
  </div>
19614
19939
  }
19615
19940
  `
@@ -19733,7 +20058,7 @@ var BookmarksComponent = class BookmarksComponent {
19733
20058
  } },
19734
20059
  providers: [provideTranslocoScope("bookmarks")],
19735
20060
  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",
20061
+ 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
20062
  dependencies: [
19738
20063
  {
19739
20064
  kind: "component",
@@ -19756,6 +20081,12 @@ var BookmarksComponent = class BookmarksComponent {
19756
20081
  ],
19757
20082
  outputs: ["itemClick", "delete"]
19758
20083
  },
20084
+ {
20085
+ kind: "component",
20086
+ type: ListPanelEmptyComponent,
20087
+ selector: "list-panel-empty, ListPanelEmpty",
20088
+ inputs: ["color"]
20089
+ },
19759
20090
  {
19760
20091
  kind: "component",
19761
20092
  type: ListPanelFooterComponent,
@@ -19815,6 +20146,39 @@ var BookmarksComponent = class BookmarksComponent {
19815
20146
  "size"
19816
20147
  ]
19817
20148
  },
20149
+ {
20150
+ kind: "directive",
20151
+ type: TooltipDirective,
20152
+ selector: "[tooltip]",
20153
+ inputs: [
20154
+ "tooltip",
20155
+ "tooltipPosition",
20156
+ "tooltipEvent",
20157
+ "showDelay",
20158
+ "hideDelay",
20159
+ "life",
20160
+ "tooltipOffset",
20161
+ "autoHide",
20162
+ "hideOnEscape",
20163
+ "escape",
20164
+ "tooltipDisabled",
20165
+ "tooltipArrow",
20166
+ "tooltipStyleClass",
20167
+ "tooltipOptions",
20168
+ "tooltip-position",
20169
+ "tooltip-delay",
20170
+ "tooltip-duration",
20171
+ "tooltip-offset"
20172
+ ],
20173
+ exportAs: ["tooltip"]
20174
+ },
20175
+ {
20176
+ kind: "directive",
20177
+ type: TruncatedDirective,
20178
+ selector: "[truncated]",
20179
+ inputs: ["truncated"],
20180
+ exportAs: ["truncated"]
20181
+ },
19818
20182
  {
19819
20183
  kind: "pipe",
19820
20184
  type: TranslocoPipe,
@@ -19836,6 +20200,7 @@ i0.ɵɵngDeclareClassMetadata({
19836
20200
  TranslocoPipe,
19837
20201
  ListPanelHeaderComponent,
19838
20202
  ListPanelComponent,
20203
+ ListPanelEmptyComponent,
19839
20204
  ListPanelFooterComponent,
19840
20205
  ListPanelItemDirective,
19841
20206
  BookmarkIcon,
@@ -19843,10 +20208,12 @@ i0.ɵɵngDeclareClassMetadata({
19843
20208
  FolderIcon,
19844
20209
  ListItemLeftDirective,
19845
20210
  ListItemTextComponent,
19846
- TagComponent
20211
+ TagComponent,
20212
+ TooltipDirective,
20213
+ TruncatedDirective
19847
20214
  ],
19848
20215
  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"
20216
+ 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
20217
  }]
19851
20218
  }],
19852
20219
  ctorParameters: () => [],
@@ -20102,7 +20469,7 @@ var CollectionsComponent = class CollectionsComponent {
20102
20469
  } },
20103
20470
  providers: [provideTranslocoScope("collections")],
20104
20471
  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",
20472
+ 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
20473
  dependencies: [
20107
20474
  {
20108
20475
  kind: "component",
@@ -20125,6 +20492,12 @@ var CollectionsComponent = class CollectionsComponent {
20125
20492
  ],
20126
20493
  outputs: ["itemClick", "delete"]
20127
20494
  },
20495
+ {
20496
+ kind: "component",
20497
+ type: ListPanelEmptyComponent,
20498
+ selector: "list-panel-empty, ListPanelEmpty",
20499
+ inputs: ["color"]
20500
+ },
20128
20501
  {
20129
20502
  kind: "component",
20130
20503
  type: ListPanelFooterComponent,
@@ -20182,6 +20555,7 @@ i0.ɵɵngDeclareClassMetadata({
20182
20555
  TranslocoPipe,
20183
20556
  ListPanelHeaderComponent,
20184
20557
  ListPanelComponent,
20558
+ ListPanelEmptyComponent,
20185
20559
  ListPanelFooterComponent,
20186
20560
  ListPanelItemDirective,
20187
20561
  InboxIcon,
@@ -20189,7 +20563,7 @@ i0.ɵɵngDeclareClassMetadata({
20189
20563
  ListItemTextComponent
20190
20564
  ],
20191
20565
  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"
20566
+ 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
20567
  }]
20194
20568
  }],
20195
20569
  ctorParameters: () => [],
@@ -30670,6 +31044,14 @@ function navigateToSearch(router, search) {
30670
31044
  if (search.queryParams?.filters && search.queryParams?.filters?.length > 0) queryParams.f = JSON.stringify(search.queryParams?.filters);
30671
31045
  router.navigate([search.queryParams?.path], { queryParams });
30672
31046
  }
31047
+ function searchItemSubtext(transloco, search) {
31048
+ const parts = [];
31049
+ const filterCount = search.filterCount || 0;
31050
+ if (filterCount > 0) parts.push(transloco.translate("searches.filterCount", { count: filterCount }));
31051
+ if (search.date) parts.push(getRelativeDate(transloco.getActiveLang(), search.date));
31052
+ const text = parts.join(" · ");
31053
+ return text ? text.charAt(0).toUpperCase() + text.slice(1) : void 0;
31054
+ }
30673
31055
  const RECENT_SEARCHES_OPTIONS = {
30674
31056
  itemsPerPage: 10,
30675
31057
  showLoadMore: true,
@@ -30686,7 +31068,7 @@ var RecentSearchesComponent = class RecentSearchesComponent {
30686
31068
  userSettingsStore = inject(UserSettingsStore);
30687
31069
  transloco = inject(TranslocoService);
30688
31070
  router = inject(Router);
30689
- getRelativeDate = getRelativeDate;
31071
+ subtextOf = (search) => searchItemSubtext(this.transloco, search);
30690
31072
  urlOf = (search) => search.url;
30691
31073
  labelOf = (search) => search.display || search.label || search.url;
30692
31074
  range = signal(10, ...ngDevMode ? [{ debugName: "range" }] : /* istanbul ignore next */ []);
@@ -30759,24 +31141,15 @@ var RecentSearchesComponent = class RecentSearchesComponent {
30759
31141
  <ng-template listPanelItem let-search>
30760
31142
  <ListItemLeft>
30761
31143
  <history-icon />
30762
- <ListItemText>{{ search.display || search.label }}</ListItemText>
31144
+ <ListItemText [subtext]="subtextOf(search)">{{ search.display || search.label }}</ListItemText>
30763
31145
  </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
31146
  </ng-template>
30778
31147
 
30779
- {{ "searches.recent.noRecentSearches" | transloco }}
31148
+ <ListPanelEmpty color="blue">
31149
+ <history-icon listPanelEmptyIcon />
31150
+ {{ "searches.recent.noRecentSearches" | transloco }}
31151
+ <p listPanelEmptyHint>{{ "searches.recent.noRecentSearchesHint" | transloco }}</p>
31152
+ </ListPanelEmpty>
30780
31153
  </ListPanel>
30781
31154
 
30782
31155
  @if (paginatedRecentSearches().length > 0) {
@@ -30794,36 +31167,6 @@ var RecentSearchesComponent = class RecentSearchesComponent {
30794
31167
  selector: "history, history-icon, History, HistoryIcon",
30795
31168
  inputs: ["class"]
30796
31169
  },
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
31170
  {
30828
31171
  kind: "directive",
30829
31172
  type: ListItemLeftDirective,
@@ -30863,6 +31206,12 @@ var RecentSearchesComponent = class RecentSearchesComponent {
30863
31206
  ],
30864
31207
  outputs: ["itemClick", "delete"]
30865
31208
  },
31209
+ {
31210
+ kind: "component",
31211
+ type: ListPanelEmptyComponent,
31212
+ selector: "list-panel-empty, ListPanelEmpty",
31213
+ inputs: ["color"]
31214
+ },
30866
31215
  {
30867
31216
  kind: "component",
30868
31217
  type: ListPanelFooterComponent,
@@ -30896,14 +31245,12 @@ i0.ɵɵngDeclareClassMetadata({
30896
31245
  imports: [
30897
31246
  TranslocoPipe,
30898
31247
  HistoryIcon,
30899
- FilterIcon,
30900
- BadgeComponent,
30901
- TagComponent,
30902
31248
  ListItemLeftDirective,
30903
31249
  ListItemTextComponent,
30904
31250
  ListPanelItemDirective,
30905
31251
  ListPanelHeaderComponent,
30906
31252
  ListPanelComponent,
31253
+ ListPanelEmptyComponent,
30907
31254
  ListPanelFooterComponent
30908
31255
  ],
30909
31256
  template: `
@@ -30920,24 +31267,15 @@ i0.ɵɵngDeclareClassMetadata({
30920
31267
  <ng-template listPanelItem let-search>
30921
31268
  <ListItemLeft>
30922
31269
  <history-icon />
30923
- <ListItemText>{{ search.display || search.label }}</ListItemText>
31270
+ <ListItemText [subtext]="subtextOf(search)">{{ search.display || search.label }}</ListItemText>
30924
31271
  </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
31272
  </ng-template>
30939
31273
 
30940
- {{ "searches.recent.noRecentSearches" | transloco }}
31274
+ <ListPanelEmpty color="blue">
31275
+ <history-icon listPanelEmptyIcon />
31276
+ {{ "searches.recent.noRecentSearches" | transloco }}
31277
+ <p listPanelEmptyHint>{{ "searches.recent.noRecentSearchesHint" | transloco }}</p>
31278
+ </ListPanelEmpty>
30941
31279
  </ListPanel>
30942
31280
 
30943
31281
  @if (paginatedRecentSearches().length > 0) {
@@ -31409,7 +31747,7 @@ var SavedSearchesComponent = class SavedSearchesComponent {
31409
31747
  userSettingsStore = inject(UserSettingsStore);
31410
31748
  transloco = inject(TranslocoService);
31411
31749
  router = inject(Router);
31412
- getRelativeDate = getRelativeDate;
31750
+ subtextOf = (search) => searchItemSubtext(this.transloco, search);
31413
31751
  urlOf = (search) => search.url;
31414
31752
  labelOf = (search) => search.display || search.label || search.url;
31415
31753
  range = signal(10, ...ngDevMode ? [{ debugName: "range" }] : /* istanbul ignore next */ []);
@@ -31482,24 +31820,15 @@ var SavedSearchesComponent = class SavedSearchesComponent {
31482
31820
  <ng-template listPanelItem let-search>
31483
31821
  <ListItemLeft>
31484
31822
  <star-icon [solid]="true" />
31485
- <ListItemText>{{ search.display || search.label }}</ListItemText>
31823
+ <ListItemText [subtext]="subtextOf(search)">{{ search.display || search.label }}</ListItemText>
31486
31824
  </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
31825
  </ng-template>
31501
31826
 
31502
- {{ 'searches.saved.noSavedSearches' | transloco }}
31827
+ <ListPanelEmpty color="yellow">
31828
+ <star-icon listPanelEmptyIcon [solid]="true" />
31829
+ {{ 'searches.saved.noSavedSearches' | transloco }}
31830
+ <p listPanelEmptyHint>{{ 'searches.saved.noSavedSearchesHint' | transloco }}</p>
31831
+ </ListPanelEmpty>
31503
31832
  </ListPanel>
31504
31833
 
31505
31834
  @if(paginatedSearches().length > 0) {
@@ -31514,36 +31843,6 @@ var SavedSearchesComponent = class SavedSearchesComponent {
31514
31843
  selector: "star, star-icon, Star, StarIcon",
31515
31844
  inputs: ["class", "solid"]
31516
31845
  },
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
31846
  {
31548
31847
  kind: "directive",
31549
31848
  type: ListItemLeftDirective,
@@ -31583,6 +31882,12 @@ var SavedSearchesComponent = class SavedSearchesComponent {
31583
31882
  ],
31584
31883
  outputs: ["itemClick", "delete"]
31585
31884
  },
31885
+ {
31886
+ kind: "component",
31887
+ type: ListPanelEmptyComponent,
31888
+ selector: "list-panel-empty, ListPanelEmpty",
31889
+ inputs: ["color"]
31890
+ },
31586
31891
  {
31587
31892
  kind: "component",
31588
31893
  type: ListPanelFooterComponent,
@@ -31616,14 +31921,12 @@ i0.ɵɵngDeclareClassMetadata({
31616
31921
  imports: [
31617
31922
  TranslocoPipe,
31618
31923
  StarIcon,
31619
- FilterIcon,
31620
- BadgeComponent,
31621
- TagComponent,
31622
31924
  ListItemLeftDirective,
31623
31925
  ListItemTextComponent,
31624
31926
  ListPanelItemDirective,
31625
31927
  ListPanelHeaderComponent,
31626
31928
  ListPanelComponent,
31929
+ ListPanelEmptyComponent,
31627
31930
  ListPanelFooterComponent
31628
31931
  ],
31629
31932
  template: `
@@ -31640,24 +31943,15 @@ i0.ɵɵngDeclareClassMetadata({
31640
31943
  <ng-template listPanelItem let-search>
31641
31944
  <ListItemLeft>
31642
31945
  <star-icon [solid]="true" />
31643
- <ListItemText>{{ search.display || search.label }}</ListItemText>
31946
+ <ListItemText [subtext]="subtextOf(search)">{{ search.display || search.label }}</ListItemText>
31644
31947
  </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
31948
  </ng-template>
31659
31949
 
31660
- {{ 'searches.saved.noSavedSearches' | transloco }}
31950
+ <ListPanelEmpty color="yellow">
31951
+ <star-icon listPanelEmptyIcon [solid]="true" />
31952
+ {{ 'searches.saved.noSavedSearches' | transloco }}
31953
+ <p listPanelEmptyHint>{{ 'searches.saved.noSavedSearchesHint' | transloco }}</p>
31954
+ </ListPanelEmpty>
31661
31955
  </ListPanel>
31662
31956
 
31663
31957
  @if(paginatedSearches().length > 0) {