@sinequa/atomic-angular 2.10.0 → 2.11.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.
- package/features/filters/i18n/de.json +20 -1
- package/features/filters/i18n/en.json +20 -1
- package/features/filters/i18n/fr.json +20 -1
- package/features/quick-filters/i18n/de.json +5 -1
- package/features/quick-filters/i18n/en.json +5 -1
- package/features/quick-filters/i18n/fr.json +5 -1
- package/fesm2022/sinequa-atomic-angular.mjs +1773 -695
- package/fesm2022/sinequa-atomic-angular.mjs.map +1 -1
- package/package.json +1 -1
- package/types/sinequa-atomic-angular-document-workspaces.d.ts +4 -4
- package/types/sinequa-atomic-angular.d.ts +326 -91
|
@@ -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, BellSlashIcon, BookmarkIcon, BreakpointObserverService, BuildingIcon, ButtonComponent, CalendarDayIcon, CheckIcon, CheckboxComponent, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ChevronsLeftIcon, ChevronsRightIcon, CircleCheckIcon, ClockIcon, CommentIcon, CommentsIcon, DIALOG_REF, DesktopIcon, DialogBodyComponent, DialogComponent, DialogContentComponent, DialogDescriptionComponent, DialogFooterComponent, DialogHeaderComponent, DialogTitleComponent, DropdownDirective, EditIcon, EllipsisIcon, EllipsisVerticalIcon, EyeIcon, EyeSlashIcon, FileHTMLIcon, FileIcon, FileOutputIcon, FileSpreadsheetIcon, FileTextIcon, 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, ListItemActionComponent, ListItemAvatarDirective, ListItemComponent, ListItemIconDirective, ListItemLeftDirective, ListItemTextComponent, LoadingCircleIcon, LockIcon, MagnifyingGlassIcon, MagnifyingGlassMinusIcon, MagnifyingGlassPlusIcon, MapPinIcon, MenuComponent, MenuContentComponent, MenuItemComponent, MenuTriggerDirective, MoonIcon, PaletteIcon, PaperclipIcon, PenToSquareIcon, PlusIcon, PopoverDirective, PopoverResizeHandleComponent, PresentationIcon, QuestionCircleIcon, QuickFilterIcon, RadioComponent, RobotIcon, SaveIcon, SearchBarComponent, SearchBarSuggestionsComponent, 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, injectFloatingPanel, 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, BuildingIcon, ButtonComponent, CalendarDayIcon, CheckIcon, CheckboxComponent, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ChevronsLeftIcon, ChevronsRightIcon, CircleCheckIcon, ClockIcon, CommentIcon, CommentsIcon, DIALOG_REF, DesktopIcon, DialogBodyComponent, DialogComponent, DialogContentComponent, DialogDescriptionComponent, DialogFooterComponent, DialogHeaderComponent, DialogTitleComponent, DropdownDirective, EditIcon, EllipsisIcon, EllipsisVerticalIcon, EyeIcon, EyeSlashIcon, FileHTMLIcon, FileIcon, FileOutputIcon, FileSpreadsheetIcon, FileTextIcon, FilterIcon, 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, ListItemActionComponent, ListItemAvatarDirective, ListItemComponent, ListItemIconDirective, ListItemLeftDirective, ListItemTextComponent, LoadingCircleIcon, LockIcon, MagnifyingGlassIcon, MagnifyingGlassMinusIcon, MagnifyingGlassPlusIcon, MapPinIcon, MenuComponent, MenuContentComponent, MenuItemComponent, MenuTriggerDirective, MoonIcon, PaletteIcon, PaperclipIcon, PenToSquareIcon, PlusIcon, PopoverDirective, PopoverResizeHandleComponent, PresentationIcon, QuestionCircleIcon, QuickFilterIcon, RadioComponent, RobotIcon, SaveIcon, SearchBarComponent, SearchBarSuggestionsComponent, 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, injectFloatingPanel, isApplePlatform } from "@sinequa/galactik";
|
|
6
6
|
import { withDevtools } from "@ngrx-toolkit/core";
|
|
7
7
|
import { getState, patchState, signalStore, signalStoreFeature, type, withComputed, withMethods, withState } from "@ngrx/signals";
|
|
8
8
|
import { ActivatedRoute, NavigationEnd, Router, RouterLink, RouterModule } from "@angular/router";
|
|
@@ -5521,6 +5521,39 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
5521
5521
|
args: [{ name: "fileSize" }]
|
|
5522
5522
|
}]
|
|
5523
5523
|
});
|
|
5524
|
+
var LocalizedNumberPipe = class LocalizedNumberPipe {
|
|
5525
|
+
transloco = inject(TranslocoService);
|
|
5526
|
+
lang = toSignal(this.transloco.langChanges$, { initialValue: this.transloco.getActiveLang() });
|
|
5527
|
+
transform(value) {
|
|
5528
|
+
return new Intl.NumberFormat(this.lang()).format(value ?? 0);
|
|
5529
|
+
}
|
|
5530
|
+
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
5531
|
+
minVersion: "12.0.0",
|
|
5532
|
+
version: "22.2.1",
|
|
5533
|
+
ngImport: i0,
|
|
5534
|
+
type: LocalizedNumberPipe,
|
|
5535
|
+
deps: [],
|
|
5536
|
+
target: i0.ɵɵFactoryTarget.Pipe
|
|
5537
|
+
});
|
|
5538
|
+
static ɵpipe = i0.ɵɵngDeclarePipe({
|
|
5539
|
+
minVersion: "14.0.0",
|
|
5540
|
+
version: "22.2.1",
|
|
5541
|
+
ngImport: i0,
|
|
5542
|
+
type: LocalizedNumberPipe,
|
|
5543
|
+
isStandalone: true,
|
|
5544
|
+
name: "localizedNumber"
|
|
5545
|
+
});
|
|
5546
|
+
};
|
|
5547
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
5548
|
+
minVersion: "12.0.0",
|
|
5549
|
+
version: "22.2.1",
|
|
5550
|
+
ngImport: i0,
|
|
5551
|
+
type: LocalizedNumberPipe,
|
|
5552
|
+
decorators: [{
|
|
5553
|
+
type: Pipe,
|
|
5554
|
+
args: [{ name: "localizedNumber" }]
|
|
5555
|
+
}]
|
|
5556
|
+
});
|
|
5524
5557
|
var MetadataComponent = class MetadataComponent {
|
|
5525
5558
|
click = output();
|
|
5526
5559
|
class = input(...ngDevMode ? [void 0, { debugName: "class" }] : /* istanbul ignore next */ []);
|
|
@@ -8218,6 +8251,8 @@ var NavbarTabsComponent = class NavbarTabsComponent {
|
|
|
8218
8251
|
appStore = inject(AppStore);
|
|
8219
8252
|
class = input(...ngDevMode ? [void 0, { debugName: "class" }] : /* istanbul ignore next */ []);
|
|
8220
8253
|
ariaLabel = input(...ngDevMode ? [void 0, { debugName: "ariaLabel" }] : /* istanbul ignore next */ []);
|
|
8254
|
+
variant = input("primary", ...ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []);
|
|
8255
|
+
isPill = computed(() => this.variant() === "primary", ...ngDevMode ? [{ debugName: "isPill" }] : /* istanbul ignore next */ []);
|
|
8221
8256
|
showCount = input(true, {
|
|
8222
8257
|
...ngDevMode ? { debugName: "showCount" } : /* istanbul ignore next */ {},
|
|
8223
8258
|
transform: booleanAttribute
|
|
@@ -8325,6 +8360,13 @@ var NavbarTabsComponent = class NavbarTabsComponent {
|
|
|
8325
8360
|
isRequired: false,
|
|
8326
8361
|
transformFunction: null
|
|
8327
8362
|
},
|
|
8363
|
+
variant: {
|
|
8364
|
+
classPropertyName: "variant",
|
|
8365
|
+
publicName: "variant",
|
|
8366
|
+
isSignal: true,
|
|
8367
|
+
isRequired: false,
|
|
8368
|
+
transformFunction: null
|
|
8369
|
+
},
|
|
8328
8370
|
showCount: {
|
|
8329
8371
|
classPropertyName: "showCount",
|
|
8330
8372
|
publicName: "showCount",
|
|
@@ -8382,10 +8424,11 @@ var NavbarTabsComponent = class NavbarTabsComponent {
|
|
|
8382
8424
|
WHICH list it landed in — and a page may hold more than one. The default is translated
|
|
8383
8425
|
so every consumer gets a named tab list without passing anything.
|
|
8384
8426
|
(No backticks in this comment: the template is a template literal.) -->
|
|
8385
|
-
<TabList class="w-full" [attr.aria-label]="ariaLabel() ?? ('navbarTabs.tabs' | transloco)" [(selectedTab)]="selectedTab" (selectedTabChange)="onSelectTab($event)" selectionMode="explicit" [softDisabled]="false">
|
|
8427
|
+
<TabList class="w-full" [variant]="variant()" [attr.aria-label]="ariaLabel() ?? ('navbarTabs.tabs' | transloco)" [(selectedTab)]="selectedTab" (selectedTabChange)="onSelectTab($event)" selectionMode="explicit" [softDisabled]="false">
|
|
8386
8428
|
@for (tab of nav.tabs(); track $index) {
|
|
8387
8429
|
<Tab
|
|
8388
|
-
[
|
|
8430
|
+
[variant]="variant()"
|
|
8431
|
+
[class]="cn('group', !noTruncate() && 'basis-50', !isPill() && 'h-11 px-(--spacing-xs) text-(length:--size-base) font-semibold')"
|
|
8389
8432
|
[value]="tab.path"
|
|
8390
8433
|
overflowItem
|
|
8391
8434
|
[noTruncate]="noTruncate()"
|
|
@@ -8416,315 +8459,11 @@ var NavbarTabsComponent = class NavbarTabsComponent {
|
|
|
8416
8459
|
</div>
|
|
8417
8460
|
</Tab>
|
|
8418
8461
|
}
|
|
8419
|
-
<!--
|
|
8420
|
-
|
|
8421
|
-
|
|
8422
|
-
— so the list keeps ONE tab stop and the arrows reach it like any other tab.
|
|
8423
|
-
|
|
8424
|
-
It stays a real <button> because that is what menuTrigger needs: it wires the menu
|
|
8425
|
-
through the native popovertarget attribute, which only works on a button.
|
|
8426
|
-
|
|
8427
|
-
ms-auto pins it to the right end of the flex tab list. overflowStop is found by content
|
|
8428
|
-
query, so the measurement is unaffected by where it sits. Always mounted so the stop
|
|
8429
|
-
position is stable; hidden when every tab fits.
|
|
8430
|
-
(No backticks in this comment: the template is a template literal.) -->
|
|
8431
|
-
<button
|
|
8432
|
-
ngTab
|
|
8433
|
-
[value]="MORE_TABS_VALUE"
|
|
8434
|
-
[menuTrigger]="moreTabsMenu"
|
|
8435
|
-
variant="tertiary" [iconOnly]="true"
|
|
8436
|
-
[attr.aria-label]="'navbarTabs.moreTabs' | transloco" [tooltip]="'navbarTabs.moreTabs' | transloco" overflowStop
|
|
8437
|
-
[disabled]="moreTabs().length === 0"
|
|
8438
|
-
class="ms-auto shrink-0"
|
|
8439
|
-
[class.invisible]="moreTabs().length === 0">
|
|
8440
|
-
<EllipsisIcon orientation="vertical"></EllipsisIcon>
|
|
8441
|
-
<span class="sr-only">{{ "navbarTabs.moreTabs" | transloco }}</span>
|
|
8442
|
-
</button>
|
|
8443
|
-
</TabList>
|
|
8444
|
-
</Tabs>
|
|
8462
|
+
<!-- Pill strip: ms-auto pins the trigger to the right end of the strip. Underline variants: no pin, the trigger
|
|
8463
|
+
follows the last visible tab, which is where the eye expects the rest of the list to be. The overflow
|
|
8464
|
+
manager reads only its size, never its position, so both placements measure the same.
|
|
8445
8465
|
|
|
8446
|
-
|
|
8447
|
-
<MenuContent class="z-500 w-fit">
|
|
8448
|
-
@for (tab of moreTabs(); track $index) {
|
|
8449
|
-
<MenuItem [value]="tab" [disabled]="isTabDisabled(tab)">
|
|
8450
|
-
<a class="inline-flex items-center gap-1 whitespace-nowrap first-letter:capitalize"
|
|
8451
|
-
[routerLink]="isTabDisabled(tab) ? null : [tab.routerLink]"
|
|
8452
|
-
[queryParams]="getQueryParams(tab)"
|
|
8453
|
-
[queryParamsHandling]="getQueryParamsHandling()"
|
|
8454
|
-
[attr.aria-selected]="nav.currentPath() === tab.path"
|
|
8455
|
-
[attr.aria-label]="tab.display | syslang | transloco"
|
|
8456
|
-
[attr.aria-disabled]="isTabDisabled(tab) ? true : null"
|
|
8457
|
-
[attr.tabindex]="isTabDisabled(tab) ? -1 : null"
|
|
8458
|
-
(click)="onOverflowItemClick($event, tab)"
|
|
8459
|
-
>
|
|
8460
|
-
@if (tab.icon) {
|
|
8461
|
-
<FaIcon [faClass]="tab.icon"/>
|
|
8462
|
-
}
|
|
8463
|
-
{{ tab.display | syslang | transloco }}
|
|
8464
|
-
</a>
|
|
8465
|
-
</MenuItem>
|
|
8466
|
-
}
|
|
8467
|
-
</MenuContent>
|
|
8468
|
-
</Menu>
|
|
8469
|
-
</div>
|
|
8470
|
-
}
|
|
8471
|
-
`,
|
|
8472
|
-
isInline: true,
|
|
8473
|
-
dependencies: [
|
|
8474
|
-
{
|
|
8475
|
-
kind: "directive",
|
|
8476
|
-
type: RouterLink,
|
|
8477
|
-
selector: "[routerLink]",
|
|
8478
|
-
inputs: [
|
|
8479
|
-
"target",
|
|
8480
|
-
"queryParams",
|
|
8481
|
-
"fragment",
|
|
8482
|
-
"queryParamsHandling",
|
|
8483
|
-
"state",
|
|
8484
|
-
"info",
|
|
8485
|
-
"relativeTo",
|
|
8486
|
-
"preserveFragment",
|
|
8487
|
-
"skipLocationChange",
|
|
8488
|
-
"replaceUrl",
|
|
8489
|
-
"browserUrl",
|
|
8490
|
-
"routerLink"
|
|
8491
|
-
]
|
|
8492
|
-
},
|
|
8493
|
-
{
|
|
8494
|
-
kind: "directive",
|
|
8495
|
-
type: ButtonComponent,
|
|
8496
|
-
selector: "button",
|
|
8497
|
-
inputs: [
|
|
8498
|
-
"class",
|
|
8499
|
-
"variant",
|
|
8500
|
-
"scheme",
|
|
8501
|
-
"iconOnly",
|
|
8502
|
-
"size"
|
|
8503
|
-
]
|
|
8504
|
-
},
|
|
8505
|
-
{
|
|
8506
|
-
kind: "directive",
|
|
8507
|
-
type: TabsComponent,
|
|
8508
|
-
selector: "Tabs",
|
|
8509
|
-
inputs: ["class"]
|
|
8510
|
-
},
|
|
8511
|
-
{
|
|
8512
|
-
kind: "directive",
|
|
8513
|
-
type: TabListComponent,
|
|
8514
|
-
selector: "TabList",
|
|
8515
|
-
inputs: ["class", "variant"]
|
|
8516
|
-
},
|
|
8517
|
-
{
|
|
8518
|
-
kind: "directive",
|
|
8519
|
-
type: TabComponent,
|
|
8520
|
-
selector: "Tab",
|
|
8521
|
-
inputs: [
|
|
8522
|
-
"class",
|
|
8523
|
-
"variant",
|
|
8524
|
-
"size",
|
|
8525
|
-
"noTruncate"
|
|
8526
|
-
]
|
|
8527
|
-
},
|
|
8528
|
-
{
|
|
8529
|
-
kind: "directive",
|
|
8530
|
-
type: Tab,
|
|
8531
|
-
selector: "[ngTab]",
|
|
8532
|
-
inputs: [
|
|
8533
|
-
"id",
|
|
8534
|
-
"disabled",
|
|
8535
|
-
"value"
|
|
8536
|
-
],
|
|
8537
|
-
exportAs: ["ngTab"]
|
|
8538
|
-
},
|
|
8539
|
-
{
|
|
8540
|
-
kind: "directive",
|
|
8541
|
-
type: OverflowManagerDirective,
|
|
8542
|
-
selector: "[overflowManager]",
|
|
8543
|
-
inputs: [
|
|
8544
|
-
"target",
|
|
8545
|
-
"margin",
|
|
8546
|
-
"direction",
|
|
8547
|
-
"reserveStop"
|
|
8548
|
-
],
|
|
8549
|
-
outputs: ["count"]
|
|
8550
|
-
},
|
|
8551
|
-
{
|
|
8552
|
-
kind: "directive",
|
|
8553
|
-
type: OverflowItemDirective,
|
|
8554
|
-
selector: "[overflowItem]"
|
|
8555
|
-
},
|
|
8556
|
-
{
|
|
8557
|
-
kind: "directive",
|
|
8558
|
-
type: OverflowStopDirective,
|
|
8559
|
-
selector: "[overflowStop]"
|
|
8560
|
-
},
|
|
8561
|
-
{
|
|
8562
|
-
kind: "directive",
|
|
8563
|
-
type: BadgeComponent,
|
|
8564
|
-
selector: "badge, Badge",
|
|
8565
|
-
inputs: [
|
|
8566
|
-
"class",
|
|
8567
|
-
"variant",
|
|
8568
|
-
"fill",
|
|
8569
|
-
"scheme",
|
|
8570
|
-
"size",
|
|
8571
|
-
"aria-label"
|
|
8572
|
-
]
|
|
8573
|
-
},
|
|
8574
|
-
{
|
|
8575
|
-
kind: "component",
|
|
8576
|
-
type: EllipsisIcon,
|
|
8577
|
-
selector: "ellipsis-icon, EllipsisIcon, ellipsisicon",
|
|
8578
|
-
inputs: ["class", "orientation"]
|
|
8579
|
-
},
|
|
8580
|
-
{
|
|
8581
|
-
kind: "directive",
|
|
8582
|
-
type: TooltipDirective,
|
|
8583
|
-
selector: "[tooltip]",
|
|
8584
|
-
inputs: [
|
|
8585
|
-
"tooltip",
|
|
8586
|
-
"tooltipPosition",
|
|
8587
|
-
"tooltipEvent",
|
|
8588
|
-
"showDelay",
|
|
8589
|
-
"hideDelay",
|
|
8590
|
-
"life",
|
|
8591
|
-
"tooltipOffset",
|
|
8592
|
-
"autoHide",
|
|
8593
|
-
"hideOnEscape",
|
|
8594
|
-
"escape",
|
|
8595
|
-
"tooltipDisabled",
|
|
8596
|
-
"tooltipArrow",
|
|
8597
|
-
"tooltipStyleClass",
|
|
8598
|
-
"tooltipOptions",
|
|
8599
|
-
"tooltip-position",
|
|
8600
|
-
"tooltip-delay",
|
|
8601
|
-
"tooltip-duration",
|
|
8602
|
-
"tooltip-offset"
|
|
8603
|
-
],
|
|
8604
|
-
exportAs: ["tooltip"]
|
|
8605
|
-
},
|
|
8606
|
-
{
|
|
8607
|
-
kind: "component",
|
|
8608
|
-
type: FaIconComponent,
|
|
8609
|
-
selector: "fa-icon, FaIcon",
|
|
8610
|
-
inputs: ["faClass", "class"]
|
|
8611
|
-
},
|
|
8612
|
-
{
|
|
8613
|
-
kind: "directive",
|
|
8614
|
-
type: MenuTriggerDirective,
|
|
8615
|
-
selector: "[menuTrigger]",
|
|
8616
|
-
inputs: ["menuTrigger"],
|
|
8617
|
-
exportAs: ["menuTrigger"]
|
|
8618
|
-
},
|
|
8619
|
-
{
|
|
8620
|
-
kind: "directive",
|
|
8621
|
-
type: MenuComponent,
|
|
8622
|
-
selector: "Menu,menu",
|
|
8623
|
-
inputs: ["class"],
|
|
8624
|
-
exportAs: ["menu"]
|
|
8625
|
-
},
|
|
8626
|
-
{
|
|
8627
|
-
kind: "component",
|
|
8628
|
-
type: MenuContentComponent,
|
|
8629
|
-
selector: "menu-content,MenuContent,menucontent",
|
|
8630
|
-
inputs: ["class"]
|
|
8631
|
-
},
|
|
8632
|
-
{
|
|
8633
|
-
kind: "directive",
|
|
8634
|
-
type: MenuItemComponent,
|
|
8635
|
-
selector: "menu-item,MenuItem,menuitem",
|
|
8636
|
-
inputs: ["class", "checked"],
|
|
8637
|
-
exportAs: ["menuitem"]
|
|
8638
|
-
},
|
|
8639
|
-
{
|
|
8640
|
-
kind: "pipe",
|
|
8641
|
-
type: TranslocoPipe,
|
|
8642
|
-
name: "transloco"
|
|
8643
|
-
},
|
|
8644
|
-
{
|
|
8645
|
-
kind: "pipe",
|
|
8646
|
-
type: SyslangPipe,
|
|
8647
|
-
name: "syslang"
|
|
8648
|
-
}
|
|
8649
|
-
]
|
|
8650
|
-
});
|
|
8651
|
-
};
|
|
8652
|
-
i0.ɵɵngDeclareClassMetadata({
|
|
8653
|
-
minVersion: "12.0.0",
|
|
8654
|
-
version: "22.2.1",
|
|
8655
|
-
ngImport: i0,
|
|
8656
|
-
type: NavbarTabsComponent,
|
|
8657
|
-
decorators: [{
|
|
8658
|
-
type: Component,
|
|
8659
|
-
args: [{
|
|
8660
|
-
selector: "navbar-tabs",
|
|
8661
|
-
imports: [
|
|
8662
|
-
RouterLink,
|
|
8663
|
-
TranslocoPipe,
|
|
8664
|
-
SyslangPipe,
|
|
8665
|
-
ButtonComponent,
|
|
8666
|
-
TabsComponent,
|
|
8667
|
-
TabListComponent,
|
|
8668
|
-
TabComponent,
|
|
8669
|
-
Tab,
|
|
8670
|
-
OverflowManagerDirective,
|
|
8671
|
-
OverflowItemDirective,
|
|
8672
|
-
OverflowStopDirective,
|
|
8673
|
-
BadgeComponent,
|
|
8674
|
-
EllipsisIcon,
|
|
8675
|
-
TooltipDirective,
|
|
8676
|
-
FaIconComponent,
|
|
8677
|
-
MenuTriggerDirective,
|
|
8678
|
-
MenuComponent,
|
|
8679
|
-
MenuContentComponent,
|
|
8680
|
-
MenuItemComponent
|
|
8681
|
-
],
|
|
8682
|
-
providers: [provideTranslocoScope("navbar-tabs")],
|
|
8683
|
-
template: `
|
|
8684
|
-
<!-- do not display the tabs if there are no tabs -->
|
|
8685
|
-
@if (nav.tabs().length > 0) {
|
|
8686
|
-
<div overflowManager (count)="visibleTabCount.set($event)" class="flex items-end rounded-[inherit] bg-inherit">
|
|
8687
|
-
<Tabs class="flex-1 min-w-0 overflow-hidden">
|
|
8688
|
-
<!-- The tab list needs a name of its own: the tablist role announces itself and its item
|
|
8689
|
-
count, but without an accessible name a screen reader has nothing to tell the user
|
|
8690
|
-
WHICH list it landed in — and a page may hold more than one. The default is translated
|
|
8691
|
-
so every consumer gets a named tab list without passing anything.
|
|
8692
|
-
(No backticks in this comment: the template is a template literal.) -->
|
|
8693
|
-
<TabList class="w-full" [attr.aria-label]="ariaLabel() ?? ('navbarTabs.tabs' | transloco)" [(selectedTab)]="selectedTab" (selectedTabChange)="onSelectTab($event)" selectionMode="explicit" [softDisabled]="false">
|
|
8694
|
-
@for (tab of nav.tabs(); track $index) {
|
|
8695
|
-
<Tab
|
|
8696
|
-
[class]="cn('group', !noTruncate() && 'basis-50')"
|
|
8697
|
-
[value]="tab.path"
|
|
8698
|
-
overflowItem
|
|
8699
|
-
[noTruncate]="noTruncate()"
|
|
8700
|
-
[style.--tab-min-width]="tabMinWidth(!!tab.icon)"
|
|
8701
|
-
[disabled]="isTabDisabled(tab) || !isTabVisible($index)"
|
|
8702
|
-
>
|
|
8703
|
-
<div [class]="cn('flex items-center content-start w-full gap-1', !noTruncate() && '@container overflow-hidden min-w-0')">
|
|
8704
|
-
@if (tab.icon) {
|
|
8705
|
-
<!-- the icon never shrinks: the label truncates first -->
|
|
8706
|
-
<FaIcon [faClass]="tab.icon" class="shrink-0" aria-hidden="true" />
|
|
8707
|
-
}
|
|
8708
|
-
<!-- when the tab gets too narrow to show anything meaningful next to
|
|
8709
|
-
the icon, hide the label entirely instead of a clipped sliver.
|
|
8710
|
-
w-0 + invisible (not display: none) keeps the label's line box,
|
|
8711
|
-
so the tab height doesn't collapse to the icon's height -->
|
|
8712
|
-
<span [class]="cn(!noTruncate() && 'truncate', !noTruncate() && tab.icon && '@max-[3rem]:w-0 @max-[3rem]:invisible')">{{ tab.display | syslang | transloco }}</span>
|
|
8713
|
-
<!-- Show count badge only if count is > 0 -->
|
|
8714
|
-
@if((tab.count ?? 0) > 0) {
|
|
8715
|
-
<Badge
|
|
8716
|
-
variant="number"
|
|
8717
|
-
fill="secondary"
|
|
8718
|
-
scheme="sage"
|
|
8719
|
-
size="xs"
|
|
8720
|
-
[aria-label]="tab.count + ' ' + (tab.count === 1 ? ('navbarTabs.result' | transloco) : ('navbarTabs.results' | transloco))">
|
|
8721
|
-
{{ tab.count }}
|
|
8722
|
-
</Badge>
|
|
8723
|
-
}
|
|
8724
|
-
</div>
|
|
8725
|
-
</Tab>
|
|
8726
|
-
}
|
|
8727
|
-
<!-- The overflow trigger belongs INSIDE the tab list, and it is a tab: a tablist may only
|
|
8466
|
+
The overflow trigger belongs INSIDE the tab list, and it is a tab: a tablist may only
|
|
8728
8467
|
contain tab elements, so a plain button here would break the announced tab count and
|
|
8729
8468
|
the arrow-key navigation. ngTab gives it the role and enrols it in the roving tabindex
|
|
8730
8469
|
— so the list keeps ONE tab stop and the arrows reach it like any other tab.
|
|
@@ -8743,9 +8482,322 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
8743
8482
|
variant="tertiary" [iconOnly]="true"
|
|
8744
8483
|
[attr.aria-label]="'navbarTabs.moreTabs' | transloco" [tooltip]="'navbarTabs.moreTabs' | transloco" overflowStop
|
|
8745
8484
|
[disabled]="moreTabs().length === 0"
|
|
8746
|
-
class="ms-auto
|
|
8485
|
+
[class]="cn('shrink-0', isPill() ? 'ms-auto' : 'self-center')"
|
|
8747
8486
|
[class.invisible]="moreTabs().length === 0">
|
|
8748
|
-
<EllipsisIcon orientation="vertical"></EllipsisIcon>
|
|
8487
|
+
<EllipsisIcon [orientation]="isPill() ? 'vertical' : 'horizontal'"></EllipsisIcon>
|
|
8488
|
+
<span class="sr-only">{{ "navbarTabs.moreTabs" | transloco }}</span>
|
|
8489
|
+
</button>
|
|
8490
|
+
</TabList>
|
|
8491
|
+
</Tabs>
|
|
8492
|
+
|
|
8493
|
+
<Menu #moreTabsMenu="menu" placement="bottom-end">
|
|
8494
|
+
<MenuContent class="z-500 w-fit">
|
|
8495
|
+
@for (tab of moreTabs(); track $index) {
|
|
8496
|
+
<MenuItem [value]="tab" [disabled]="isTabDisabled(tab)">
|
|
8497
|
+
<a class="inline-flex items-center gap-1 whitespace-nowrap first-letter:capitalize"
|
|
8498
|
+
[routerLink]="isTabDisabled(tab) ? null : [tab.routerLink]"
|
|
8499
|
+
[queryParams]="getQueryParams(tab)"
|
|
8500
|
+
[queryParamsHandling]="getQueryParamsHandling()"
|
|
8501
|
+
[attr.aria-selected]="nav.currentPath() === tab.path"
|
|
8502
|
+
[attr.aria-label]="tab.display | syslang | transloco"
|
|
8503
|
+
[attr.aria-disabled]="isTabDisabled(tab) ? true : null"
|
|
8504
|
+
[attr.tabindex]="isTabDisabled(tab) ? -1 : null"
|
|
8505
|
+
(click)="onOverflowItemClick($event, tab)"
|
|
8506
|
+
>
|
|
8507
|
+
@if (tab.icon) {
|
|
8508
|
+
<FaIcon [faClass]="tab.icon"/>
|
|
8509
|
+
}
|
|
8510
|
+
{{ tab.display | syslang | transloco }}
|
|
8511
|
+
</a>
|
|
8512
|
+
</MenuItem>
|
|
8513
|
+
}
|
|
8514
|
+
</MenuContent>
|
|
8515
|
+
</Menu>
|
|
8516
|
+
</div>
|
|
8517
|
+
}
|
|
8518
|
+
`,
|
|
8519
|
+
isInline: true,
|
|
8520
|
+
dependencies: [
|
|
8521
|
+
{
|
|
8522
|
+
kind: "directive",
|
|
8523
|
+
type: RouterLink,
|
|
8524
|
+
selector: "[routerLink]",
|
|
8525
|
+
inputs: [
|
|
8526
|
+
"target",
|
|
8527
|
+
"queryParams",
|
|
8528
|
+
"fragment",
|
|
8529
|
+
"queryParamsHandling",
|
|
8530
|
+
"state",
|
|
8531
|
+
"info",
|
|
8532
|
+
"relativeTo",
|
|
8533
|
+
"preserveFragment",
|
|
8534
|
+
"skipLocationChange",
|
|
8535
|
+
"replaceUrl",
|
|
8536
|
+
"browserUrl",
|
|
8537
|
+
"routerLink"
|
|
8538
|
+
]
|
|
8539
|
+
},
|
|
8540
|
+
{
|
|
8541
|
+
kind: "directive",
|
|
8542
|
+
type: ButtonComponent,
|
|
8543
|
+
selector: "button",
|
|
8544
|
+
inputs: [
|
|
8545
|
+
"class",
|
|
8546
|
+
"variant",
|
|
8547
|
+
"scheme",
|
|
8548
|
+
"iconOnly",
|
|
8549
|
+
"size"
|
|
8550
|
+
]
|
|
8551
|
+
},
|
|
8552
|
+
{
|
|
8553
|
+
kind: "directive",
|
|
8554
|
+
type: TabsComponent,
|
|
8555
|
+
selector: "Tabs",
|
|
8556
|
+
inputs: ["class"]
|
|
8557
|
+
},
|
|
8558
|
+
{
|
|
8559
|
+
kind: "directive",
|
|
8560
|
+
type: TabListComponent,
|
|
8561
|
+
selector: "TabList",
|
|
8562
|
+
inputs: ["class", "variant"]
|
|
8563
|
+
},
|
|
8564
|
+
{
|
|
8565
|
+
kind: "directive",
|
|
8566
|
+
type: TabComponent,
|
|
8567
|
+
selector: "Tab",
|
|
8568
|
+
inputs: [
|
|
8569
|
+
"class",
|
|
8570
|
+
"variant",
|
|
8571
|
+
"size",
|
|
8572
|
+
"noTruncate"
|
|
8573
|
+
]
|
|
8574
|
+
},
|
|
8575
|
+
{
|
|
8576
|
+
kind: "directive",
|
|
8577
|
+
type: Tab,
|
|
8578
|
+
selector: "[ngTab]",
|
|
8579
|
+
inputs: [
|
|
8580
|
+
"id",
|
|
8581
|
+
"disabled",
|
|
8582
|
+
"value"
|
|
8583
|
+
],
|
|
8584
|
+
exportAs: ["ngTab"]
|
|
8585
|
+
},
|
|
8586
|
+
{
|
|
8587
|
+
kind: "directive",
|
|
8588
|
+
type: OverflowManagerDirective,
|
|
8589
|
+
selector: "[overflowManager]",
|
|
8590
|
+
inputs: [
|
|
8591
|
+
"target",
|
|
8592
|
+
"margin",
|
|
8593
|
+
"direction",
|
|
8594
|
+
"reserveStop"
|
|
8595
|
+
],
|
|
8596
|
+
outputs: ["count"]
|
|
8597
|
+
},
|
|
8598
|
+
{
|
|
8599
|
+
kind: "directive",
|
|
8600
|
+
type: OverflowItemDirective,
|
|
8601
|
+
selector: "[overflowItem]"
|
|
8602
|
+
},
|
|
8603
|
+
{
|
|
8604
|
+
kind: "directive",
|
|
8605
|
+
type: OverflowStopDirective,
|
|
8606
|
+
selector: "[overflowStop]"
|
|
8607
|
+
},
|
|
8608
|
+
{
|
|
8609
|
+
kind: "directive",
|
|
8610
|
+
type: BadgeComponent,
|
|
8611
|
+
selector: "badge, Badge",
|
|
8612
|
+
inputs: [
|
|
8613
|
+
"class",
|
|
8614
|
+
"variant",
|
|
8615
|
+
"fill",
|
|
8616
|
+
"scheme",
|
|
8617
|
+
"size",
|
|
8618
|
+
"aria-label"
|
|
8619
|
+
]
|
|
8620
|
+
},
|
|
8621
|
+
{
|
|
8622
|
+
kind: "component",
|
|
8623
|
+
type: EllipsisIcon,
|
|
8624
|
+
selector: "ellipsis-icon, EllipsisIcon, ellipsisicon",
|
|
8625
|
+
inputs: ["class", "orientation"]
|
|
8626
|
+
},
|
|
8627
|
+
{
|
|
8628
|
+
kind: "directive",
|
|
8629
|
+
type: TooltipDirective,
|
|
8630
|
+
selector: "[tooltip]",
|
|
8631
|
+
inputs: [
|
|
8632
|
+
"tooltip",
|
|
8633
|
+
"tooltipPosition",
|
|
8634
|
+
"tooltipEvent",
|
|
8635
|
+
"showDelay",
|
|
8636
|
+
"hideDelay",
|
|
8637
|
+
"life",
|
|
8638
|
+
"tooltipOffset",
|
|
8639
|
+
"autoHide",
|
|
8640
|
+
"hideOnEscape",
|
|
8641
|
+
"escape",
|
|
8642
|
+
"tooltipDisabled",
|
|
8643
|
+
"tooltipArrow",
|
|
8644
|
+
"tooltipStyleClass",
|
|
8645
|
+
"tooltipOptions",
|
|
8646
|
+
"tooltip-position",
|
|
8647
|
+
"tooltip-delay",
|
|
8648
|
+
"tooltip-duration",
|
|
8649
|
+
"tooltip-offset"
|
|
8650
|
+
],
|
|
8651
|
+
exportAs: ["tooltip"]
|
|
8652
|
+
},
|
|
8653
|
+
{
|
|
8654
|
+
kind: "component",
|
|
8655
|
+
type: FaIconComponent,
|
|
8656
|
+
selector: "fa-icon, FaIcon",
|
|
8657
|
+
inputs: ["faClass", "class"]
|
|
8658
|
+
},
|
|
8659
|
+
{
|
|
8660
|
+
kind: "directive",
|
|
8661
|
+
type: MenuTriggerDirective,
|
|
8662
|
+
selector: "[menuTrigger]",
|
|
8663
|
+
inputs: ["menuTrigger"],
|
|
8664
|
+
exportAs: ["menuTrigger"]
|
|
8665
|
+
},
|
|
8666
|
+
{
|
|
8667
|
+
kind: "directive",
|
|
8668
|
+
type: MenuComponent,
|
|
8669
|
+
selector: "Menu,menu",
|
|
8670
|
+
inputs: ["class"],
|
|
8671
|
+
exportAs: ["menu"]
|
|
8672
|
+
},
|
|
8673
|
+
{
|
|
8674
|
+
kind: "component",
|
|
8675
|
+
type: MenuContentComponent,
|
|
8676
|
+
selector: "menu-content,MenuContent,menucontent",
|
|
8677
|
+
inputs: ["class"]
|
|
8678
|
+
},
|
|
8679
|
+
{
|
|
8680
|
+
kind: "directive",
|
|
8681
|
+
type: MenuItemComponent,
|
|
8682
|
+
selector: "menu-item,MenuItem,menuitem",
|
|
8683
|
+
inputs: ["class", "checked"],
|
|
8684
|
+
exportAs: ["menuitem"]
|
|
8685
|
+
},
|
|
8686
|
+
{
|
|
8687
|
+
kind: "pipe",
|
|
8688
|
+
type: TranslocoPipe,
|
|
8689
|
+
name: "transloco"
|
|
8690
|
+
},
|
|
8691
|
+
{
|
|
8692
|
+
kind: "pipe",
|
|
8693
|
+
type: SyslangPipe,
|
|
8694
|
+
name: "syslang"
|
|
8695
|
+
}
|
|
8696
|
+
]
|
|
8697
|
+
});
|
|
8698
|
+
};
|
|
8699
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
8700
|
+
minVersion: "12.0.0",
|
|
8701
|
+
version: "22.2.1",
|
|
8702
|
+
ngImport: i0,
|
|
8703
|
+
type: NavbarTabsComponent,
|
|
8704
|
+
decorators: [{
|
|
8705
|
+
type: Component,
|
|
8706
|
+
args: [{
|
|
8707
|
+
selector: "navbar-tabs",
|
|
8708
|
+
imports: [
|
|
8709
|
+
RouterLink,
|
|
8710
|
+
TranslocoPipe,
|
|
8711
|
+
SyslangPipe,
|
|
8712
|
+
ButtonComponent,
|
|
8713
|
+
TabsComponent,
|
|
8714
|
+
TabListComponent,
|
|
8715
|
+
TabComponent,
|
|
8716
|
+
Tab,
|
|
8717
|
+
OverflowManagerDirective,
|
|
8718
|
+
OverflowItemDirective,
|
|
8719
|
+
OverflowStopDirective,
|
|
8720
|
+
BadgeComponent,
|
|
8721
|
+
EllipsisIcon,
|
|
8722
|
+
TooltipDirective,
|
|
8723
|
+
FaIconComponent,
|
|
8724
|
+
MenuTriggerDirective,
|
|
8725
|
+
MenuComponent,
|
|
8726
|
+
MenuContentComponent,
|
|
8727
|
+
MenuItemComponent
|
|
8728
|
+
],
|
|
8729
|
+
providers: [provideTranslocoScope("navbar-tabs")],
|
|
8730
|
+
template: `
|
|
8731
|
+
<!-- do not display the tabs if there are no tabs -->
|
|
8732
|
+
@if (nav.tabs().length > 0) {
|
|
8733
|
+
<div overflowManager (count)="visibleTabCount.set($event)" class="flex items-end rounded-[inherit] bg-inherit">
|
|
8734
|
+
<Tabs class="flex-1 min-w-0 overflow-hidden">
|
|
8735
|
+
<!-- The tab list needs a name of its own: the tablist role announces itself and its item
|
|
8736
|
+
count, but without an accessible name a screen reader has nothing to tell the user
|
|
8737
|
+
WHICH list it landed in — and a page may hold more than one. The default is translated
|
|
8738
|
+
so every consumer gets a named tab list without passing anything.
|
|
8739
|
+
(No backticks in this comment: the template is a template literal.) -->
|
|
8740
|
+
<TabList class="w-full" [variant]="variant()" [attr.aria-label]="ariaLabel() ?? ('navbarTabs.tabs' | transloco)" [(selectedTab)]="selectedTab" (selectedTabChange)="onSelectTab($event)" selectionMode="explicit" [softDisabled]="false">
|
|
8741
|
+
@for (tab of nav.tabs(); track $index) {
|
|
8742
|
+
<Tab
|
|
8743
|
+
[variant]="variant()"
|
|
8744
|
+
[class]="cn('group', !noTruncate() && 'basis-50', !isPill() && 'h-11 px-(--spacing-xs) text-(length:--size-base) font-semibold')"
|
|
8745
|
+
[value]="tab.path"
|
|
8746
|
+
overflowItem
|
|
8747
|
+
[noTruncate]="noTruncate()"
|
|
8748
|
+
[style.--tab-min-width]="tabMinWidth(!!tab.icon)"
|
|
8749
|
+
[disabled]="isTabDisabled(tab) || !isTabVisible($index)"
|
|
8750
|
+
>
|
|
8751
|
+
<div [class]="cn('flex items-center content-start w-full gap-1', !noTruncate() && '@container overflow-hidden min-w-0')">
|
|
8752
|
+
@if (tab.icon) {
|
|
8753
|
+
<!-- the icon never shrinks: the label truncates first -->
|
|
8754
|
+
<FaIcon [faClass]="tab.icon" class="shrink-0" aria-hidden="true" />
|
|
8755
|
+
}
|
|
8756
|
+
<!-- when the tab gets too narrow to show anything meaningful next to
|
|
8757
|
+
the icon, hide the label entirely instead of a clipped sliver.
|
|
8758
|
+
w-0 + invisible (not display: none) keeps the label's line box,
|
|
8759
|
+
so the tab height doesn't collapse to the icon's height -->
|
|
8760
|
+
<span [class]="cn(!noTruncate() && 'truncate', !noTruncate() && tab.icon && '@max-[3rem]:w-0 @max-[3rem]:invisible')">{{ tab.display | syslang | transloco }}</span>
|
|
8761
|
+
<!-- Show count badge only if count is > 0 -->
|
|
8762
|
+
@if((tab.count ?? 0) > 0) {
|
|
8763
|
+
<Badge
|
|
8764
|
+
variant="number"
|
|
8765
|
+
fill="secondary"
|
|
8766
|
+
scheme="sage"
|
|
8767
|
+
size="xs"
|
|
8768
|
+
[aria-label]="tab.count + ' ' + (tab.count === 1 ? ('navbarTabs.result' | transloco) : ('navbarTabs.results' | transloco))">
|
|
8769
|
+
{{ tab.count }}
|
|
8770
|
+
</Badge>
|
|
8771
|
+
}
|
|
8772
|
+
</div>
|
|
8773
|
+
</Tab>
|
|
8774
|
+
}
|
|
8775
|
+
<!-- Pill strip: ms-auto pins the trigger to the right end of the strip. Underline variants: no pin, the trigger
|
|
8776
|
+
follows the last visible tab, which is where the eye expects the rest of the list to be. The overflow
|
|
8777
|
+
manager reads only its size, never its position, so both placements measure the same.
|
|
8778
|
+
|
|
8779
|
+
The overflow trigger belongs INSIDE the tab list, and it is a tab: a tablist may only
|
|
8780
|
+
contain tab elements, so a plain button here would break the announced tab count and
|
|
8781
|
+
the arrow-key navigation. ngTab gives it the role and enrols it in the roving tabindex
|
|
8782
|
+
— so the list keeps ONE tab stop and the arrows reach it like any other tab.
|
|
8783
|
+
|
|
8784
|
+
It stays a real <button> because that is what menuTrigger needs: it wires the menu
|
|
8785
|
+
through the native popovertarget attribute, which only works on a button.
|
|
8786
|
+
|
|
8787
|
+
ms-auto pins it to the right end of the flex tab list. overflowStop is found by content
|
|
8788
|
+
query, so the measurement is unaffected by where it sits. Always mounted so the stop
|
|
8789
|
+
position is stable; hidden when every tab fits.
|
|
8790
|
+
(No backticks in this comment: the template is a template literal.) -->
|
|
8791
|
+
<button
|
|
8792
|
+
ngTab
|
|
8793
|
+
[value]="MORE_TABS_VALUE"
|
|
8794
|
+
[menuTrigger]="moreTabsMenu"
|
|
8795
|
+
variant="tertiary" [iconOnly]="true"
|
|
8796
|
+
[attr.aria-label]="'navbarTabs.moreTabs' | transloco" [tooltip]="'navbarTabs.moreTabs' | transloco" overflowStop
|
|
8797
|
+
[disabled]="moreTabs().length === 0"
|
|
8798
|
+
[class]="cn('shrink-0', isPill() ? 'ms-auto' : 'self-center')"
|
|
8799
|
+
[class.invisible]="moreTabs().length === 0">
|
|
8800
|
+
<EllipsisIcon [orientation]="isPill() ? 'vertical' : 'horizontal'"></EllipsisIcon>
|
|
8749
8801
|
<span class="sr-only">{{ "navbarTabs.moreTabs" | transloco }}</span>
|
|
8750
8802
|
</button>
|
|
8751
8803
|
</TabList>
|
|
@@ -8802,6 +8854,14 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
8802
8854
|
required: false
|
|
8803
8855
|
}]
|
|
8804
8856
|
}],
|
|
8857
|
+
variant: [{
|
|
8858
|
+
type: i0.Input,
|
|
8859
|
+
args: [{
|
|
8860
|
+
isSignal: true,
|
|
8861
|
+
alias: "variant",
|
|
8862
|
+
required: false
|
|
8863
|
+
}]
|
|
8864
|
+
}],
|
|
8805
8865
|
showCount: [{
|
|
8806
8866
|
type: i0.Input,
|
|
8807
8867
|
args: [{
|
|
@@ -24533,8 +24593,8 @@ var AggregationDateComponent = class AggregationDateComponent {
|
|
|
24533
24593
|
host: { properties: { "class": "cn(\"@container flex h-[inherit] max-h-[inherit] flex-col\", class(), isEmpty() && \"hidden\")" } },
|
|
24534
24594
|
providers: [provideTranslocoScope("filters")],
|
|
24535
24595
|
ngImport: i0,
|
|
24536
|
-
template: "<!-- `<details>`/`<summary>` stay native (open/close, the `name`-based exclusive accordion). A facet is a\n disclosure, not a grid row: see `aggregation-panel.html` for the why (same pattern, duplicated here\n since this facet type doesn't go through `AggregationPanelComponent`). The `<summary>` itself is\n `tabindex=\"-1\"`: the header's chevron is the keyboard stop, see the chevron below. Clear and apply are not in the\n header: they are in the body, in a bar at the top of the facet. -->\n<ng-template #content>\n <!-- content wrapper -->\n <!-- RGAA 11.5 / WCAG 1.3.1: the radios below all share one `name`, so they are one set\n and have to be exposed as one. `role=\"group\"` here rather than `role=\"radiogroup\"`\n on the list: a radiogroup owns its radios directly, and the <li> in between would be\n invalid owned children — the list would also stop announcing how many options it\n holds. The name is the aggregation's own title, the words the header already shows;\n with no title the group stays unnamed rather than carrying an empty label. -->\n @let groupName = aggregation()?.display;\n <form\n class=\"flex min-h-0 flex-1 flex-col\"\n (keydown.enter)=\"onOptionEnter($event)\">\n <!-- The same bar as for the list and the tree, in a popover and in the filters drawer alike: clear (a button with a text, there but disabled when there is nothing\n to clear) and, while a choice is pending, apply, without an icon. There is no search field here to put them under:\n they sit at the top of the facet. -->\n <div class=\"flex shrink-0 items-center justify-end gap-1 pt-1 pl-1\">\n <button\n type=\"button\"\n variant=\"tertiary\"\n size=\"sm\"\n class=\"shrink-0\"\n [disabled]=\"!hasFilters() && !selection()\"\n [attr.aria-label]=\"'filters.clearFilters' | transloco\"\n (click)=\"clear()\">\n {{ \"filters.clearAll\" | transloco }}\n </button>\n @if (\n (!isControlled() || emitOn() === \"apply\") &&\n selection() &&\n validSelection()\n ) {\n <button\n type=\"button\"\n variant=\"accent\"\n size=\"sm\"\n class=\"shrink-0 overflow-hidden\"\n (click)=\"apply()\">\n <span class=\"truncate\">{{ \"filters.apply\" | transloco }}</span>\n </button>\n }\n </div>\n <div\n class=\"flex min-h-0 flex-1 flex-col\"\n role=\"group\"\n [attr.aria-label]=\"groupName ? (groupName | syslang | transloco) : null\">\n <ul\n class=\"flex min-h-0 flex-1 snap-y snap-start scrollbar-thin flex-col gap-0.5 overflow-auto pt-2\"\n role=\"list\">\n @for (option of dateOptions(); track option.display) {\n <li\n role=\"listitem\"\n [attr.aria-label]=\"\n maskedLabel(option) ?? (option.display | syslang | transloco)\n \"\n [class]=\"\n cn(\n itemStateClass,\n 'flex cursor-pointer items-center p-2 text-sm text-(--font-neutral-base) select-none',\n dateModel().option === option.display &&\n 'bg-(--bg-primary-lighter)',\n option.hidden && 'hidden',\n option.disabled && 'pointer-events-none opacity-40'\n )\n \"\n [attr.aria-hidden]=\"option.disabled\">\n <radio\n name=\"date-filter\"\n class=\"grow\"\n [checked]=\"dateModel().option === option.display\"\n [disabled]=\"option.disabled\"\n (checkedChange)=\"selectOption(option.display)\">\n {{\n maskedLabel(option) ?? (option.display | syslang | transloco)\n }}\n </radio>\n </li>\n }\n\n @if (allowCustomRange) {\n <li\n role=\"listitem\"\n aria-label=\"open date range picker\"\n [class]=\"\n cn(\n itemStateClass,\n 'flex cursor-pointer items-center gap-(--spacing-xs) p-2 text-sm text-(--font-neutral-base) select-none',\n dateModel().option === 'custom-range' &&\n 'bg-(--bg-primary-lighter)'\n )\n \">\n <radio\n name=\"date-filter\"\n class=\"grow items-center\"\n [checked]=\"dateModel().option === 'custom-range'\"\n (checkedChange)=\"selectOption('custom-range')\">\n <div class=\"flex flex-wrap items-center gap-x-1 gap-y-1\">\n <div class=\"flex items-center gap-1\">\n <label\n [attr.for]=\"startId\"\n class=\"radio-label min-w-8 truncate\"\n >{{ \"filters.from\" | transloco }}</label\n >\n <input-group\n class=\"h-8 max-w-[18ch] min-w-[18ch] has-[[readonly]]:border-(--stroke-primary-muted)\">\n <input\n input-control\n [id]=\"startId\"\n name=\"start\"\n type=\"text\"\n readonly\n [value]=\"customRangeFrom()\"\n (click)=\"selectAndOpenDialog()\"\n (keydown.enter)=\"onRangeInputEnter($event)\" />\n </input-group>\n </div>\n <div class=\"flex items-center gap-1\">\n <label\n [attr.for]=\"endId\"\n class=\"radio-label min-w-8 truncate text-right\"\n >{{ \"filters.to\" | transloco }}</label\n >\n <input-group\n class=\"h-8 max-w-[18ch] min-w-[18ch] has-[[readonly]]:border-(--stroke-primary-muted)\">\n <input\n input-control\n [id]=\"endId\"\n name=\"end\"\n type=\"text\"\n readonly\n [value]=\"customRangeTo()\"\n (click)=\"selectAndOpenDialog()\"\n (keydown.enter)=\"onRangeInputEnter($event)\" />\n </input-group>\n </div>\n </div>\n </radio>\n </li>\n }\n </ul>\n </div>\n </form>\n</ng-template>\n\n@if (headless()) {\n <div class=\"flex min-h-0 flex-1 flex-col\">\n <ng-container [ngTemplateOutlet]=\"content\" />\n </div>\n} @else {\n <details\n [attr.open]=\"expanded()\"\n [attr.name]=\"id()\"\n class=\"group flex min-h-0 flex-1 flex-col space-y-2 details-content:flex details-content:min-h-0 details-content:flex-
|
|
24537
|
-
styles: [":host{min-width:200px}ul[role=list]{scrollbar-width:thin}\n"],
|
|
24596
|
+
template: "<!-- `<details>`/`<summary>` stay native (open/close, the `name`-based exclusive accordion). A facet is a\n disclosure, not a grid row: see `aggregation-panel.html` for the why (same pattern, duplicated here\n since this facet type doesn't go through `AggregationPanelComponent`). The `<summary>` itself is\n `tabindex=\"-1\"`: the header's chevron is the keyboard stop, see the chevron below. Clear and apply are not in the\n header: they are in the body, in a bar at the top of the facet. -->\n<ng-template #content>\n <!-- content wrapper -->\n <!-- RGAA 11.5 / WCAG 1.3.1: the radios below all share one `name`, so they are one set\n and have to be exposed as one. `role=\"group\"` here rather than `role=\"radiogroup\"`\n on the list: a radiogroup owns its radios directly, and the <li> in between would be\n invalid owned children — the list would also stop announcing how many options it\n holds. The name is the aggregation's own title, the words the header already shows;\n with no title the group stays unnamed rather than carrying an empty label. -->\n @let groupName = aggregation()?.display;\n <form\n class=\"flex min-h-0 flex-1 flex-col\"\n (keydown.enter)=\"onOptionEnter($event)\">\n <!-- The same bar as for the list and the tree, in a popover and in the filters drawer alike: clear (a button with a text, there but disabled when there is nothing\n to clear) and, while a choice is pending, apply, without an icon. There is no search field here to put them under:\n they sit at the top of the facet. -->\n <div class=\"flex shrink-0 items-center justify-end gap-1 pt-1 pl-1\">\n <button\n type=\"button\"\n variant=\"tertiary\"\n size=\"sm\"\n class=\"shrink-0\"\n [disabled]=\"!hasFilters() && !selection()\"\n [attr.aria-label]=\"'filters.clearFilters' | transloco\"\n (click)=\"clear()\">\n {{ \"filters.clearAll\" | transloco }}\n </button>\n @if (\n (!isControlled() || emitOn() === \"apply\") &&\n selection() &&\n validSelection()\n ) {\n <button\n type=\"button\"\n variant=\"accent\"\n size=\"sm\"\n class=\"shrink-0 overflow-hidden\"\n (click)=\"apply()\">\n <span class=\"truncate\">{{ \"filters.apply\" | transloco }}</span>\n </button>\n }\n </div>\n <div\n class=\"flex min-h-0 flex-1 flex-col\"\n role=\"group\"\n [attr.aria-label]=\"groupName ? (groupName | syslang | transloco) : null\">\n <ul\n class=\"flex min-h-0 flex-1 snap-y snap-start scrollbar-thin flex-col gap-0.5 overflow-auto pt-2\"\n role=\"list\">\n @for (option of dateOptions(); track option.display) {\n <li\n role=\"listitem\"\n [attr.aria-label]=\"\n maskedLabel(option) ?? (option.display | syslang | transloco)\n \"\n [class]=\"\n cn(\n itemStateClass,\n 'flex cursor-pointer items-center p-2 text-sm text-(--font-neutral-base) select-none',\n dateModel().option === option.display &&\n 'bg-(--bg-primary-lighter)',\n option.hidden && 'hidden',\n option.disabled && 'pointer-events-none opacity-40'\n )\n \"\n [attr.aria-hidden]=\"option.disabled\">\n <radio\n name=\"date-filter\"\n class=\"grow\"\n [checked]=\"dateModel().option === option.display\"\n [disabled]=\"option.disabled\"\n (checkedChange)=\"selectOption(option.display)\">\n {{\n maskedLabel(option) ?? (option.display | syslang | transloco)\n }}\n </radio>\n </li>\n }\n\n @if (allowCustomRange) {\n <li\n role=\"listitem\"\n aria-label=\"open date range picker\"\n [class]=\"\n cn(\n itemStateClass,\n 'flex cursor-pointer items-center gap-(--spacing-xs) p-2 text-sm text-(--font-neutral-base) select-none',\n dateModel().option === 'custom-range' &&\n 'bg-(--bg-primary-lighter)'\n )\n \">\n <radio\n name=\"date-filter\"\n class=\"grow items-center\"\n [checked]=\"dateModel().option === 'custom-range'\"\n (checkedChange)=\"selectOption('custom-range')\">\n <div class=\"flex flex-wrap items-center gap-x-1 gap-y-1\">\n <div class=\"flex items-center gap-1\">\n <label\n [attr.for]=\"startId\"\n class=\"radio-label min-w-8 truncate\"\n >{{ \"filters.from\" | transloco }}</label\n >\n <input-group\n class=\"h-8 max-w-[18ch] min-w-[18ch] has-[[readonly]]:border-(--stroke-primary-muted)\">\n <input\n input-control\n [id]=\"startId\"\n name=\"start\"\n type=\"text\"\n readonly\n [value]=\"customRangeFrom()\"\n (click)=\"selectAndOpenDialog()\"\n (keydown.enter)=\"onRangeInputEnter($event)\" />\n </input-group>\n </div>\n <div class=\"flex items-center gap-1\">\n <label\n [attr.for]=\"endId\"\n class=\"radio-label min-w-8 truncate text-right\"\n >{{ \"filters.to\" | transloco }}</label\n >\n <input-group\n class=\"h-8 max-w-[18ch] min-w-[18ch] has-[[readonly]]:border-(--stroke-primary-muted)\">\n <input\n input-control\n [id]=\"endId\"\n name=\"end\"\n type=\"text\"\n readonly\n [value]=\"customRangeTo()\"\n (click)=\"selectAndOpenDialog()\"\n (keydown.enter)=\"onRangeInputEnter($event)\" />\n </input-group>\n </div>\n </div>\n </radio>\n </li>\n }\n </ul>\n </div>\n </form>\n</ng-template>\n\n@if (headless()) {\n <div class=\"flex min-h-0 flex-1 flex-col\">\n <ng-container [ngTemplateOutlet]=\"content\" />\n </div>\n} @else {\n <details\n [attr.open]=\"expanded()\"\n [attr.name]=\"id()\"\n class=\"group flex min-h-0 flex-1 flex-col space-y-2 details-content:flex details-content:min-h-0 details-content:flex-col\">\n <!-- scroll-mb-3/scroll-mt-16: see aggregation-panel-row.html for the full rationale\n (bottom margin against the container's edge, much bigger top margin to also clear\n <More>'s sticky facet-name search box when navigating ArrowUp). -->\n <summary\n tabindex=\"-1\"\n [class.cursor-pointer]=\"collapsible()\"\n [class]=\"rowStateClass\"\n class=\"m-0 flex h-8 w-full shrink-0 scroll-mt-16 scroll-mb-3 items-center gap-1 pl-1 font-semibold select-none\"\n (click)=\"onHeaderClick($event)\">\n <ng-content select=\"label\">\n @let icon = aggregation()?.icon;\n @if (icon) {\n <FaIcon [faClass]=\"icon\" class=\"mr-1 shrink-0\" />\n }\n <span class=\"grow\">{{\n aggregation()?.display | syslang | transloco\n }}</span>\n </ng-content>\n\n @if (collapsible()) {\n <!-- The `<summary>` is out of the tab order (`tabindex=\"-1\"`), so this button is the keyboard way to\n collapse the facet. Its click bubbles to the `<summary>`, whose handler does the toggle, and it\n exposes `aria-expanded`. -->\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n size=\"sm\"\n [aria-label]=\"'filters.toggle' | transloco\"\n [attr.aria-expanded]=\"!isCollapsed()\"\n [tooltip]=\"'filters.toggle' | transloco\"\n class=\"shrink-0 cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\n <chevron-right class=\"shrink-0\" />\n </button>\n }\n </summary>\n <ng-container [ngTemplateOutlet]=\"content\" />\n </details>\n}\n",
|
|
24597
|
+
styles: [":host{interpolate-size:allow-keywords}details::details-content{flex:1 1 auto;block-size:0;overflow:clip;overflow-clip-margin:.25rem;transition:block-size .2s cubic-bezier(.32,.72,0,1),content-visibility .2s allow-discrete}details[open]::details-content{block-size:auto}@media(prefers-reduced-motion:reduce){details::details-content{transition:none}}\n", ":host{min-width:200px}ul[role=list]{scrollbar-width:thin}\n"],
|
|
24538
24598
|
dependencies: [
|
|
24539
24599
|
{
|
|
24540
24600
|
kind: "directive",
|
|
@@ -24665,8 +24725,8 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
24665
24725
|
TooltipDirective
|
|
24666
24726
|
],
|
|
24667
24727
|
host: { "[class]": "cn(\"@container flex h-[inherit] max-h-[inherit] flex-col\", class(), isEmpty() && \"hidden\")" },
|
|
24668
|
-
template: "<!-- `<details>`/`<summary>` stay native (open/close, the `name`-based exclusive accordion). A facet is a\n disclosure, not a grid row: see `aggregation-panel.html` for the why (same pattern, duplicated here\n since this facet type doesn't go through `AggregationPanelComponent`). The `<summary>` itself is\n `tabindex=\"-1\"`: the header's chevron is the keyboard stop, see the chevron below. Clear and apply are not in the\n header: they are in the body, in a bar at the top of the facet. -->\n<ng-template #content>\n <!-- content wrapper -->\n <!-- RGAA 11.5 / WCAG 1.3.1: the radios below all share one `name`, so they are one set\n and have to be exposed as one. `role=\"group\"` here rather than `role=\"radiogroup\"`\n on the list: a radiogroup owns its radios directly, and the <li> in between would be\n invalid owned children — the list would also stop announcing how many options it\n holds. The name is the aggregation's own title, the words the header already shows;\n with no title the group stays unnamed rather than carrying an empty label. -->\n @let groupName = aggregation()?.display;\n <form\n class=\"flex min-h-0 flex-1 flex-col\"\n (keydown.enter)=\"onOptionEnter($event)\">\n <!-- The same bar as for the list and the tree, in a popover and in the filters drawer alike: clear (a button with a text, there but disabled when there is nothing\n to clear) and, while a choice is pending, apply, without an icon. There is no search field here to put them under:\n they sit at the top of the facet. -->\n <div class=\"flex shrink-0 items-center justify-end gap-1 pt-1 pl-1\">\n <button\n type=\"button\"\n variant=\"tertiary\"\n size=\"sm\"\n class=\"shrink-0\"\n [disabled]=\"!hasFilters() && !selection()\"\n [attr.aria-label]=\"'filters.clearFilters' | transloco\"\n (click)=\"clear()\">\n {{ \"filters.clearAll\" | transloco }}\n </button>\n @if (\n (!isControlled() || emitOn() === \"apply\") &&\n selection() &&\n validSelection()\n ) {\n <button\n type=\"button\"\n variant=\"accent\"\n size=\"sm\"\n class=\"shrink-0 overflow-hidden\"\n (click)=\"apply()\">\n <span class=\"truncate\">{{ \"filters.apply\" | transloco }}</span>\n </button>\n }\n </div>\n <div\n class=\"flex min-h-0 flex-1 flex-col\"\n role=\"group\"\n [attr.aria-label]=\"groupName ? (groupName | syslang | transloco) : null\">\n <ul\n class=\"flex min-h-0 flex-1 snap-y snap-start scrollbar-thin flex-col gap-0.5 overflow-auto pt-2\"\n role=\"list\">\n @for (option of dateOptions(); track option.display) {\n <li\n role=\"listitem\"\n [attr.aria-label]=\"\n maskedLabel(option) ?? (option.display | syslang | transloco)\n \"\n [class]=\"\n cn(\n itemStateClass,\n 'flex cursor-pointer items-center p-2 text-sm text-(--font-neutral-base) select-none',\n dateModel().option === option.display &&\n 'bg-(--bg-primary-lighter)',\n option.hidden && 'hidden',\n option.disabled && 'pointer-events-none opacity-40'\n )\n \"\n [attr.aria-hidden]=\"option.disabled\">\n <radio\n name=\"date-filter\"\n class=\"grow\"\n [checked]=\"dateModel().option === option.display\"\n [disabled]=\"option.disabled\"\n (checkedChange)=\"selectOption(option.display)\">\n {{\n maskedLabel(option) ?? (option.display | syslang | transloco)\n }}\n </radio>\n </li>\n }\n\n @if (allowCustomRange) {\n <li\n role=\"listitem\"\n aria-label=\"open date range picker\"\n [class]=\"\n cn(\n itemStateClass,\n 'flex cursor-pointer items-center gap-(--spacing-xs) p-2 text-sm text-(--font-neutral-base) select-none',\n dateModel().option === 'custom-range' &&\n 'bg-(--bg-primary-lighter)'\n )\n \">\n <radio\n name=\"date-filter\"\n class=\"grow items-center\"\n [checked]=\"dateModel().option === 'custom-range'\"\n (checkedChange)=\"selectOption('custom-range')\">\n <div class=\"flex flex-wrap items-center gap-x-1 gap-y-1\">\n <div class=\"flex items-center gap-1\">\n <label\n [attr.for]=\"startId\"\n class=\"radio-label min-w-8 truncate\"\n >{{ \"filters.from\" | transloco }}</label\n >\n <input-group\n class=\"h-8 max-w-[18ch] min-w-[18ch] has-[[readonly]]:border-(--stroke-primary-muted)\">\n <input\n input-control\n [id]=\"startId\"\n name=\"start\"\n type=\"text\"\n readonly\n [value]=\"customRangeFrom()\"\n (click)=\"selectAndOpenDialog()\"\n (keydown.enter)=\"onRangeInputEnter($event)\" />\n </input-group>\n </div>\n <div class=\"flex items-center gap-1\">\n <label\n [attr.for]=\"endId\"\n class=\"radio-label min-w-8 truncate text-right\"\n >{{ \"filters.to\" | transloco }}</label\n >\n <input-group\n class=\"h-8 max-w-[18ch] min-w-[18ch] has-[[readonly]]:border-(--stroke-primary-muted)\">\n <input\n input-control\n [id]=\"endId\"\n name=\"end\"\n type=\"text\"\n readonly\n [value]=\"customRangeTo()\"\n (click)=\"selectAndOpenDialog()\"\n (keydown.enter)=\"onRangeInputEnter($event)\" />\n </input-group>\n </div>\n </div>\n </radio>\n </li>\n }\n </ul>\n </div>\n </form>\n</ng-template>\n\n@if (headless()) {\n <div class=\"flex min-h-0 flex-1 flex-col\">\n <ng-container [ngTemplateOutlet]=\"content\" />\n </div>\n} @else {\n <details\n [attr.open]=\"expanded()\"\n [attr.name]=\"id()\"\n class=\"group flex min-h-0 flex-1 flex-col space-y-2 details-content:flex details-content:min-h-0 details-content:flex-
|
|
24669
|
-
styles: [":host{min-width:200px}ul[role=list]{scrollbar-width:thin}\n"]
|
|
24728
|
+
template: "<!-- `<details>`/`<summary>` stay native (open/close, the `name`-based exclusive accordion). A facet is a\n disclosure, not a grid row: see `aggregation-panel.html` for the why (same pattern, duplicated here\n since this facet type doesn't go through `AggregationPanelComponent`). The `<summary>` itself is\n `tabindex=\"-1\"`: the header's chevron is the keyboard stop, see the chevron below. Clear and apply are not in the\n header: they are in the body, in a bar at the top of the facet. -->\n<ng-template #content>\n <!-- content wrapper -->\n <!-- RGAA 11.5 / WCAG 1.3.1: the radios below all share one `name`, so they are one set\n and have to be exposed as one. `role=\"group\"` here rather than `role=\"radiogroup\"`\n on the list: a radiogroup owns its radios directly, and the <li> in between would be\n invalid owned children — the list would also stop announcing how many options it\n holds. The name is the aggregation's own title, the words the header already shows;\n with no title the group stays unnamed rather than carrying an empty label. -->\n @let groupName = aggregation()?.display;\n <form\n class=\"flex min-h-0 flex-1 flex-col\"\n (keydown.enter)=\"onOptionEnter($event)\">\n <!-- The same bar as for the list and the tree, in a popover and in the filters drawer alike: clear (a button with a text, there but disabled when there is nothing\n to clear) and, while a choice is pending, apply, without an icon. There is no search field here to put them under:\n they sit at the top of the facet. -->\n <div class=\"flex shrink-0 items-center justify-end gap-1 pt-1 pl-1\">\n <button\n type=\"button\"\n variant=\"tertiary\"\n size=\"sm\"\n class=\"shrink-0\"\n [disabled]=\"!hasFilters() && !selection()\"\n [attr.aria-label]=\"'filters.clearFilters' | transloco\"\n (click)=\"clear()\">\n {{ \"filters.clearAll\" | transloco }}\n </button>\n @if (\n (!isControlled() || emitOn() === \"apply\") &&\n selection() &&\n validSelection()\n ) {\n <button\n type=\"button\"\n variant=\"accent\"\n size=\"sm\"\n class=\"shrink-0 overflow-hidden\"\n (click)=\"apply()\">\n <span class=\"truncate\">{{ \"filters.apply\" | transloco }}</span>\n </button>\n }\n </div>\n <div\n class=\"flex min-h-0 flex-1 flex-col\"\n role=\"group\"\n [attr.aria-label]=\"groupName ? (groupName | syslang | transloco) : null\">\n <ul\n class=\"flex min-h-0 flex-1 snap-y snap-start scrollbar-thin flex-col gap-0.5 overflow-auto pt-2\"\n role=\"list\">\n @for (option of dateOptions(); track option.display) {\n <li\n role=\"listitem\"\n [attr.aria-label]=\"\n maskedLabel(option) ?? (option.display | syslang | transloco)\n \"\n [class]=\"\n cn(\n itemStateClass,\n 'flex cursor-pointer items-center p-2 text-sm text-(--font-neutral-base) select-none',\n dateModel().option === option.display &&\n 'bg-(--bg-primary-lighter)',\n option.hidden && 'hidden',\n option.disabled && 'pointer-events-none opacity-40'\n )\n \"\n [attr.aria-hidden]=\"option.disabled\">\n <radio\n name=\"date-filter\"\n class=\"grow\"\n [checked]=\"dateModel().option === option.display\"\n [disabled]=\"option.disabled\"\n (checkedChange)=\"selectOption(option.display)\">\n {{\n maskedLabel(option) ?? (option.display | syslang | transloco)\n }}\n </radio>\n </li>\n }\n\n @if (allowCustomRange) {\n <li\n role=\"listitem\"\n aria-label=\"open date range picker\"\n [class]=\"\n cn(\n itemStateClass,\n 'flex cursor-pointer items-center gap-(--spacing-xs) p-2 text-sm text-(--font-neutral-base) select-none',\n dateModel().option === 'custom-range' &&\n 'bg-(--bg-primary-lighter)'\n )\n \">\n <radio\n name=\"date-filter\"\n class=\"grow items-center\"\n [checked]=\"dateModel().option === 'custom-range'\"\n (checkedChange)=\"selectOption('custom-range')\">\n <div class=\"flex flex-wrap items-center gap-x-1 gap-y-1\">\n <div class=\"flex items-center gap-1\">\n <label\n [attr.for]=\"startId\"\n class=\"radio-label min-w-8 truncate\"\n >{{ \"filters.from\" | transloco }}</label\n >\n <input-group\n class=\"h-8 max-w-[18ch] min-w-[18ch] has-[[readonly]]:border-(--stroke-primary-muted)\">\n <input\n input-control\n [id]=\"startId\"\n name=\"start\"\n type=\"text\"\n readonly\n [value]=\"customRangeFrom()\"\n (click)=\"selectAndOpenDialog()\"\n (keydown.enter)=\"onRangeInputEnter($event)\" />\n </input-group>\n </div>\n <div class=\"flex items-center gap-1\">\n <label\n [attr.for]=\"endId\"\n class=\"radio-label min-w-8 truncate text-right\"\n >{{ \"filters.to\" | transloco }}</label\n >\n <input-group\n class=\"h-8 max-w-[18ch] min-w-[18ch] has-[[readonly]]:border-(--stroke-primary-muted)\">\n <input\n input-control\n [id]=\"endId\"\n name=\"end\"\n type=\"text\"\n readonly\n [value]=\"customRangeTo()\"\n (click)=\"selectAndOpenDialog()\"\n (keydown.enter)=\"onRangeInputEnter($event)\" />\n </input-group>\n </div>\n </div>\n </radio>\n </li>\n }\n </ul>\n </div>\n </form>\n</ng-template>\n\n@if (headless()) {\n <div class=\"flex min-h-0 flex-1 flex-col\">\n <ng-container [ngTemplateOutlet]=\"content\" />\n </div>\n} @else {\n <details\n [attr.open]=\"expanded()\"\n [attr.name]=\"id()\"\n class=\"group flex min-h-0 flex-1 flex-col space-y-2 details-content:flex details-content:min-h-0 details-content:flex-col\">\n <!-- scroll-mb-3/scroll-mt-16: see aggregation-panel-row.html for the full rationale\n (bottom margin against the container's edge, much bigger top margin to also clear\n <More>'s sticky facet-name search box when navigating ArrowUp). -->\n <summary\n tabindex=\"-1\"\n [class.cursor-pointer]=\"collapsible()\"\n [class]=\"rowStateClass\"\n class=\"m-0 flex h-8 w-full shrink-0 scroll-mt-16 scroll-mb-3 items-center gap-1 pl-1 font-semibold select-none\"\n (click)=\"onHeaderClick($event)\">\n <ng-content select=\"label\">\n @let icon = aggregation()?.icon;\n @if (icon) {\n <FaIcon [faClass]=\"icon\" class=\"mr-1 shrink-0\" />\n }\n <span class=\"grow\">{{\n aggregation()?.display | syslang | transloco\n }}</span>\n </ng-content>\n\n @if (collapsible()) {\n <!-- The `<summary>` is out of the tab order (`tabindex=\"-1\"`), so this button is the keyboard way to\n collapse the facet. Its click bubbles to the `<summary>`, whose handler does the toggle, and it\n exposes `aria-expanded`. -->\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n size=\"sm\"\n [aria-label]=\"'filters.toggle' | transloco\"\n [attr.aria-expanded]=\"!isCollapsed()\"\n [tooltip]=\"'filters.toggle' | transloco\"\n class=\"shrink-0 cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\n <chevron-right class=\"shrink-0\" />\n </button>\n }\n </summary>\n <ng-container [ngTemplateOutlet]=\"content\" />\n </details>\n}\n",
|
|
24729
|
+
styles: [":host{interpolate-size:allow-keywords}details::details-content{flex:1 1 auto;block-size:0;overflow:clip;overflow-clip-margin:.25rem;transition:block-size .2s cubic-bezier(.32,.72,0,1),content-visibility .2s allow-discrete}details[open]::details-content{block-size:auto}@media(prefers-reduced-motion:reduce){details::details-content{transition:none}}\n", ":host{min-width:200px}ul[role=list]{scrollbar-width:thin}\n"]
|
|
24670
24730
|
}]
|
|
24671
24731
|
}],
|
|
24672
24732
|
ctorParameters: () => [],
|
|
@@ -24869,12 +24929,30 @@ var AggregationPanelComponent = class AggregationPanelComponent {
|
|
|
24869
24929
|
isCollapsed = linkedSignal(() => this.collapsed(), ...ngDevMode ? [{ debugName: "isCollapsed" }] : /* istanbul ignore next */ []);
|
|
24870
24930
|
expanded = computed(() => this.isCollapsed() ? null : "", ...ngDevMode ? [{ debugName: "expanded" }] : /* istanbul ignore next */ []);
|
|
24871
24931
|
searchBox = viewChild(SearchInputComponent, ...ngDevMode ? [{ debugName: "searchBox" }] : /* istanbul ignore next */ []);
|
|
24932
|
+
searchToggle = viewChild("searchToggle", ...ngDevMode ? [{ debugName: "searchToggle" }] : /* istanbul ignore next */ []);
|
|
24933
|
+
injector = inject(Injector);
|
|
24872
24934
|
isInPopover = !!inject(PopoverDirective, { optional: true });
|
|
24873
|
-
|
|
24874
|
-
|
|
24875
|
-
|
|
24876
|
-
|
|
24877
|
-
|
|
24935
|
+
static nextId = 0;
|
|
24936
|
+
searchId = `aggregation-search-${AggregationPanelComponent.nextId++}`;
|
|
24937
|
+
searchRequested = signal(false, ...ngDevMode ? [{ debugName: "searchRequested" }] : /* istanbul ignore next */ []);
|
|
24938
|
+
canSearch = computed(() => !!this.aggregation()?.searchable && this.itemsLength() > 0, ...ngDevMode ? [{ debugName: "canSearch" }] : /* istanbul ignore next */ []);
|
|
24939
|
+
showSearch = computed(() => this.canSearch() && (this.searchRequested() || this.searchText() !== ""), ...ngDevMode ? [{ debugName: "showSearch" }] : /* istanbul ignore next */ []);
|
|
24940
|
+
toggleSearch(event) {
|
|
24941
|
+
event?.stopPropagation();
|
|
24942
|
+
event?.preventDefault();
|
|
24943
|
+
if (this.showSearch()) {
|
|
24944
|
+
this.searchText.set("");
|
|
24945
|
+
this.searchRequested.set(false);
|
|
24946
|
+
afterNextRender(() => this.searchToggle()?.nativeElement.focus(), { injector: this.injector });
|
|
24947
|
+
return;
|
|
24948
|
+
}
|
|
24949
|
+
this.searchRequested.set(true);
|
|
24950
|
+
afterNextRender(() => this.searchBox()?.searchInput().nativeElement.focus(), { injector: this.injector });
|
|
24951
|
+
}
|
|
24952
|
+
onSearchEscape(event) {
|
|
24953
|
+
if (!this.showSearch()) return;
|
|
24954
|
+
event.stopPropagation();
|
|
24955
|
+
this.toggleSearch();
|
|
24878
24956
|
}
|
|
24879
24957
|
onHeaderClick(event) {
|
|
24880
24958
|
if (!this.isDate() && this.isEmpty()) {
|
|
@@ -25039,9 +25117,16 @@ var AggregationPanelComponent = class AggregationPanelComponent {
|
|
|
25039
25117
|
predicate: SearchInputComponent,
|
|
25040
25118
|
descendants: true,
|
|
25041
25119
|
isSignal: true
|
|
25120
|
+
}, {
|
|
25121
|
+
propertyName: "searchToggle",
|
|
25122
|
+
first: true,
|
|
25123
|
+
predicate: ["searchToggle"],
|
|
25124
|
+
descendants: true,
|
|
25125
|
+
isSignal: true
|
|
25042
25126
|
}],
|
|
25043
25127
|
ngImport: i0,
|
|
25044
|
-
template: "<!-- `<details>`/`<summary>` stay native (open/close, the `name`-based exclusive accordion): a facet is\n a disclosure, not a grid row. They used to sit in an `@angular/aria` grid so ArrowUp/ArrowDown could\n jump from one facet header to the next, but a grid without headers reads as a layout table\n (RGAA 5.3). The `<summary>` itself is `tabindex=\"-1\"`: Tab walks the header's chevron, never the header row, and the\n chevron is the keyboard way to collapse. Select all, clear and apply are not in the header: they are in the body, in a\n bar under the search field. Label, header actions (the count and the chevron) and body are `<ng-template>`s: `<ng-content select=\"…\">` can only be\n declared once, and the body is projected content. -->\n\n<ng-template #labelSlot>\n <ng-content select=\"label\">\n @let icon = aggregation()?.icon;\n @if (icon) {\n <FaIcon [faClass]=\"icon\" class=\"mr-1 shrink-0\" />\n }\n <span class=\"grow truncate\">{{\n aggregation()?.display | syslang | transloco\n }}</span>\n </ng-content>\n</ng-template>\n\n<ng-template #headerActionsSlot>\n @if (showFiltersCount() && filtersCount() > 0) {\n <Badge variant=\"number\" fill=\"primary\" scheme=\"sage\" size=\"xs\" class=\"ml-1\">\n {{ filtersCount() }}\n </Badge>\n }\n @if (collapsible()) {\n <!-- The `<summary>` is out of the tab order (`tabindex=\"-1\"`), so this button is the keyboard way to\n collapse the facet. Its click bubbles to the `<summary>`, whose handler does the toggle, and it\n exposes `aria-expanded`. -->\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n size=\"sm\"\n [attr.aria-expanded]=\"!isCollapsed()\"\n [tooltip]=\"'filters.toggle' | transloco\"\n class=\"shrink-0 cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\n <chevron-right class=\"shrink-0\" />\n <span class=\"sr-only\">{{ \"filters.toggle\" | transloco }}</span>\n </button>\n }\n</ng-template>\n\n<ng-template #bodySlot>\n <div class=\"flex min-h-0 flex-1 flex-col\">\n @if (
|
|
25128
|
+
template: "<!-- `<details>`/`<summary>` stay native (open/close, the `name`-based exclusive accordion): a facet is\n a disclosure, not a grid row. They used to sit in an `@angular/aria` grid so ArrowUp/ArrowDown could\n jump from one facet header to the next, but a grid without headers reads as a layout table\n (RGAA 5.3). The `<summary>` itself is `tabindex=\"-1\"`: Tab walks the header's chevron, never the header row, and the\n chevron is the keyboard way to collapse. Select all, clear and apply are not in the header: they are in the body, in a\n bar under the search field. Label, header actions (the count and the chevron) and body are `<ng-template>`s: `<ng-content select=\"…\">` can only be\n declared once, and the body is projected content. -->\n\n<ng-template #searchToggleSlot>\n <button\n #searchToggle\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n size=\"sm\"\n class=\"shrink-0 cursor-pointer\"\n [attr.aria-expanded]=\"showSearch()\"\n [attr.aria-controls]=\"searchId\"\n [tooltip]=\"\n 'filters.searchInFacet'\n | transloco: { facet: (aggregation()?.display | syslang | transloco) }\n \"\n (click)=\"toggleSearch($event)\">\n <search-icon class=\"shrink-0\" />\n <span class=\"sr-only\">{{\n \"filters.searchInFacet\"\n | transloco: { facet: (aggregation()?.display | syslang | transloco) }\n }}</span>\n </button>\n</ng-template>\n\n<ng-template #labelSlot>\n <ng-content select=\"label\">\n @let icon = aggregation()?.icon;\n @if (icon) {\n <FaIcon [faClass]=\"icon\" class=\"mr-1 shrink-0\" />\n }\n <span class=\"grow truncate\">{{\n aggregation()?.display | syslang | transloco\n }}</span>\n </ng-content>\n</ng-template>\n\n<ng-template #headerActionsSlot>\n @if (showFiltersCount() && filtersCount() > 0) {\n <Badge variant=\"number\" fill=\"primary\" scheme=\"sage\" size=\"xs\" class=\"ml-1\">\n {{ filtersCount() }}\n </Badge>\n }\n @if (canSearch() && !isCollapsed()) {\n <!-- The magnifier opens the search field of the facet (and closes it): the field is not on screen until then. It is\n in the header, next to the chevron, and only while the facet is open — there is nothing to search in a closed\n one. Interactive content in a summary does not toggle the details by itself, and the click is stopped anyway. -->\n <ng-container [ngTemplateOutlet]=\"searchToggleSlot\" />\n }\n @if (collapsible()) {\n <!-- The `<summary>` is out of the tab order (`tabindex=\"-1\"`), so this button is the keyboard way to\n collapse the facet. Its click bubbles to the `<summary>`, whose handler does the toggle, and it\n exposes `aria-expanded`. -->\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n size=\"sm\"\n [attr.aria-expanded]=\"!isCollapsed()\"\n [tooltip]=\"'filters.toggle' | transloco\"\n class=\"shrink-0 cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\n <chevron-right class=\"shrink-0\" />\n <span class=\"sr-only\">{{ \"filters.toggle\" | transloco }}</span>\n </button>\n }\n</ng-template>\n\n<ng-template #bodySlot>\n <div class=\"flex min-h-0 flex-1 flex-col\">\n @if (canSearch()) {\n <!-- The field is always in the DOM and folded to nothing until it is asked for: a grid row that goes from 0fr to 1fr is\n the one height animation that needs no measuring. `inert` takes the folded field out of the tab order and of the\n accessibility tree. The margins live in the inner box: a padding on the folded element itself would keep it\n from reaching zero.\n\n No `hotkey`: this field is one of many facet search boxes on the page, so it must not advertise a global shortcut.\n A placeholder is not a label (RGAA 11.1): the name says WHICH facet this box filters — the one thing that tells a\n dozen identical fields apart. SearchInput hands its class to its inner field, so a margin on it would count twice,\n which is one more reason for the padding on the box around it. -->\n <div\n [id]=\"searchId\"\n class=\"grid shrink-0 transition-[grid-template-rows,opacity] duration-200 ease-[cubic-bezier(0.32,0.72,0,1)] motion-reduce:transition-none\"\n [class]=\"\n showSearch()\n ? 'grid-rows-[1fr] opacity-100'\n : 'grid-rows-[0fr] opacity-0'\n \"\n [inert]=\"!showSearch()\">\n <!-- `-mx-1 px-1`: the box clips what is outside it, and the focus ring of the field is drawn outside the field,\n so it needs a few pixels of room on each side — taken back by the negative margin so nothing moves. -->\n <div class=\"-mx-1 min-h-0 overflow-hidden px-1\">\n <div [class]=\"headless() ? 'pt-0.5 pb-1' : 'pt-2 pb-1'\">\n <SearchInput\n [placeholder]=\"'search' | transloco\"\n [clearLabel]=\"'filters.clearSearch' | transloco\"\n [aria-label]=\"\n 'filters.searchInFacet'\n | transloco\n : { facet: (aggregation()?.display | syslang | transloco) }\n \"\n [(value)]=\"searchText\"\n (keydown.escape)=\"onSearchEscape($event)\">\n <ng-content select=\"[search-addon]\" />\n </SearchInput>\n </div>\n </div>\n </div>\n }\n\n @if (itemsLength() > 0 || hasFilters() || selection()) {\n <!-- The bar under the search field, in a popover and in the filters drawer alike: select all (the count of selected items in its tooltip), then clear and apply. Clear is a button\n with a text, not an icon, and stays in place (disabled) when there is nothing to clear. Apply only shows once\n something is pending, as it always did, and sits at the same level. The checkbox keeps its own name: the text\n beside it is a count, not the label of what it does. -->\n <div class=\"mb-1 flex shrink-0 items-center gap-1 pl-1.5\">\n <!-- How many are selected is in the tooltip of the checkbox, not written beside it: that room is what clear and apply need.\n Screen readers keep it as text (the tooltip only shows on hover and focus, and describes). -->\n <span\n class=\"inline-flex shrink-0\"\n [tooltip]=\"\n 'filters.selectedCount' | transloco: { count: selectedCount() }\n \"\n tooltipPosition=\"bottom\">\n <checkbox\n [checked]=\"isAllSelected()\"\n [indeterminate]=\"selectedCount() > 0 && !isAllSelected()\"\n [aria-label]=\"'filters.selectAllFilters' | transloco\"\n (checkedChange)=\"\n $event ? allSelected.emit() : allUnselected.emit()\n \" />\n </span>\n <span class=\"sr-only\" aria-live=\"polite\">{{\n \"filters.selectedCount\" | transloco: { count: selectedCount() }\n }}</span>\n <span class=\"flex-1\" aria-hidden=\"true\"></span>\n @if (headless() && canSearch()) {\n <ng-container [ngTemplateOutlet]=\"searchToggleSlot\" />\n }\n <button\n type=\"button\"\n variant=\"tertiary\"\n size=\"sm\"\n class=\"shrink-0\"\n [disabled]=\"!hasFilters() && selectedCount() === 0\"\n [attr.aria-label]=\"'filters.clearFilters' | transloco\"\n (click)=\"cleared.emit()\">\n {{ \"filters.clearAll\" | transloco }}\n </button>\n @if (selection()) {\n <!-- No icon: the room is short, and the word says it. -->\n <button\n type=\"button\"\n variant=\"accent\"\n size=\"sm\"\n class=\"shrink-0 overflow-hidden\"\n (click)=\"applied.emit()\">\n <span class=\"truncate\">{{ \"filters.apply\" | transloco }}</span>\n </button>\n }\n </div>\n }\n\n <ng-content />\n\n @if (hasMore() && searchedItemsLength() === 0) {\n <button\n type=\"button\"\n class=\"mt-1 flex w-full shrink-0 justify-center\"\n [attr.aria-label]=\"'loadMore' | transloco\"\n (click)=\"loadedMore.emit()\">\n {{ \"loadMore\" | transloco }}\n </button>\n }\n </div>\n</ng-template>\n\n@if (headless()) {\n <div class=\"flex min-h-0 flex-1 flex-col p-0.5\">\n <ng-container [ngTemplateOutlet]=\"bodySlot\" />\n </div>\n} @else {\n <details\n [attr.open]=\"expanded()\"\n [attr.name]=\"id()\"\n class=\"group flex min-h-0 flex-1 flex-col p-0.5 details-content:flex details-content:min-h-0 details-content:flex-col\"\n (toggle)=\"onToggle($event)\">\n <summary\n tabindex=\"-1\"\n [class.cursor-pointer]=\"collapsible() && !isEmpty()\"\n [class]=\"\n (isEmpty() ? 'text-(--font-neutral-muted) ' : '') + rowStateClass\n \"\n class=\"m-0 flex h-8 w-full shrink-0 scroll-mt-16 scroll-mb-3 items-center gap-1 pl-1 font-semibold select-none\"\n (click)=\"onHeaderClick($event)\">\n <ng-container [ngTemplateOutlet]=\"labelSlot\" />\n <ng-container [ngTemplateOutlet]=\"headerActionsSlot\" />\n </summary>\n <ng-container [ngTemplateOutlet]=\"bodySlot\" />\n </details>\n}\n",
|
|
25129
|
+
styles: [":host{interpolate-size:allow-keywords}details::details-content{flex:1 1 auto;block-size:0;overflow:clip;overflow-clip-margin:.25rem;transition:block-size .2s cubic-bezier(.32,.72,0,1),content-visibility .2s allow-discrete}details[open]::details-content{block-size:auto}@media(prefers-reduced-motion:reduce){details::details-content{transition:none}}\n"],
|
|
25045
25130
|
dependencies: [
|
|
25046
25131
|
{
|
|
25047
25132
|
kind: "directive",
|
|
@@ -25084,6 +25169,12 @@ var AggregationPanelComponent = class AggregationPanelComponent {
|
|
|
25084
25169
|
selector: "chevron-right, chevron-right-icon, ChevronRight, ChevronRightIcon",
|
|
25085
25170
|
inputs: ["class"]
|
|
25086
25171
|
},
|
|
25172
|
+
{
|
|
25173
|
+
kind: "component",
|
|
25174
|
+
type: SearchIcon,
|
|
25175
|
+
selector: "search, search-icon, Search, SearchIcon",
|
|
25176
|
+
inputs: ["class"]
|
|
25177
|
+
},
|
|
25087
25178
|
{
|
|
25088
25179
|
kind: "component",
|
|
25089
25180
|
type: SearchInputComponent,
|
|
@@ -25164,6 +25255,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
25164
25255
|
TranslocoPipe,
|
|
25165
25256
|
BadgeComponent,
|
|
25166
25257
|
ChevronRightIcon,
|
|
25258
|
+
SearchIcon,
|
|
25167
25259
|
SearchInputComponent,
|
|
25168
25260
|
FaIconComponent,
|
|
25169
25261
|
CheckboxComponent,
|
|
@@ -25171,10 +25263,10 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
25171
25263
|
],
|
|
25172
25264
|
providers: [provideTranslocoScope("filters")],
|
|
25173
25265
|
host: { class: "flex min-h-0 flex-1 flex-col" },
|
|
25174
|
-
template: "<!-- `<details>`/`<summary>` stay native (open/close, the `name`-based exclusive accordion): a facet is\n a disclosure, not a grid row. They used to sit in an `@angular/aria` grid so ArrowUp/ArrowDown could\n jump from one facet header to the next, but a grid without headers reads as a layout table\n (RGAA 5.3). The `<summary>` itself is `tabindex=\"-1\"`: Tab walks the header's chevron, never the header row, and the\n chevron is the keyboard way to collapse. Select all, clear and apply are not in the header: they are in the body, in a\n bar under the search field. Label, header actions (the count and the chevron) and body are `<ng-template>`s: `<ng-content select=\"…\">` can only be\n declared once, and the body is projected content. -->\n\n<ng-template #labelSlot>\n <ng-content select=\"label\">\n @let icon = aggregation()?.icon;\n @if (icon) {\n <FaIcon [faClass]=\"icon\" class=\"mr-1 shrink-0\" />\n }\n <span class=\"grow truncate\">{{\n aggregation()?.display | syslang | transloco\n }}</span>\n </ng-content>\n</ng-template>\n\n<ng-template #headerActionsSlot>\n @if (showFiltersCount() && filtersCount() > 0) {\n <Badge variant=\"number\" fill=\"primary\" scheme=\"sage\" size=\"xs\" class=\"ml-1\">\n {{ filtersCount() }}\n </Badge>\n }\n @if (collapsible()) {\n <!-- The `<summary>` is out of the tab order (`tabindex=\"-1\"`), so this button is the keyboard way to\n collapse the facet. Its click bubbles to the `<summary>`, whose handler does the toggle, and it\n exposes `aria-expanded`. -->\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n size=\"sm\"\n [attr.aria-expanded]=\"!isCollapsed()\"\n [tooltip]=\"'filters.toggle' | transloco\"\n class=\"shrink-0 cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\n <chevron-right class=\"shrink-0\" />\n <span class=\"sr-only\">{{ \"filters.toggle\" | transloco }}</span>\n </button>\n }\n</ng-template>\n\n<ng-template #bodySlot>\n <div class=\"flex min-h-0 flex-1 flex-col\">\n @if (
|
|
25266
|
+
template: "<!-- `<details>`/`<summary>` stay native (open/close, the `name`-based exclusive accordion): a facet is\n a disclosure, not a grid row. They used to sit in an `@angular/aria` grid so ArrowUp/ArrowDown could\n jump from one facet header to the next, but a grid without headers reads as a layout table\n (RGAA 5.3). The `<summary>` itself is `tabindex=\"-1\"`: Tab walks the header's chevron, never the header row, and the\n chevron is the keyboard way to collapse. Select all, clear and apply are not in the header: they are in the body, in a\n bar under the search field. Label, header actions (the count and the chevron) and body are `<ng-template>`s: `<ng-content select=\"…\">` can only be\n declared once, and the body is projected content. -->\n\n<ng-template #searchToggleSlot>\n <button\n #searchToggle\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n size=\"sm\"\n class=\"shrink-0 cursor-pointer\"\n [attr.aria-expanded]=\"showSearch()\"\n [attr.aria-controls]=\"searchId\"\n [tooltip]=\"\n 'filters.searchInFacet'\n | transloco: { facet: (aggregation()?.display | syslang | transloco) }\n \"\n (click)=\"toggleSearch($event)\">\n <search-icon class=\"shrink-0\" />\n <span class=\"sr-only\">{{\n \"filters.searchInFacet\"\n | transloco: { facet: (aggregation()?.display | syslang | transloco) }\n }}</span>\n </button>\n</ng-template>\n\n<ng-template #labelSlot>\n <ng-content select=\"label\">\n @let icon = aggregation()?.icon;\n @if (icon) {\n <FaIcon [faClass]=\"icon\" class=\"mr-1 shrink-0\" />\n }\n <span class=\"grow truncate\">{{\n aggregation()?.display | syslang | transloco\n }}</span>\n </ng-content>\n</ng-template>\n\n<ng-template #headerActionsSlot>\n @if (showFiltersCount() && filtersCount() > 0) {\n <Badge variant=\"number\" fill=\"primary\" scheme=\"sage\" size=\"xs\" class=\"ml-1\">\n {{ filtersCount() }}\n </Badge>\n }\n @if (canSearch() && !isCollapsed()) {\n <!-- The magnifier opens the search field of the facet (and closes it): the field is not on screen until then. It is\n in the header, next to the chevron, and only while the facet is open — there is nothing to search in a closed\n one. Interactive content in a summary does not toggle the details by itself, and the click is stopped anyway. -->\n <ng-container [ngTemplateOutlet]=\"searchToggleSlot\" />\n }\n @if (collapsible()) {\n <!-- The `<summary>` is out of the tab order (`tabindex=\"-1\"`), so this button is the keyboard way to\n collapse the facet. Its click bubbles to the `<summary>`, whose handler does the toggle, and it\n exposes `aria-expanded`. -->\n <button\n type=\"button\"\n variant=\"tertiary\"\n [iconOnly]=\"true\"\n size=\"sm\"\n [attr.aria-expanded]=\"!isCollapsed()\"\n [tooltip]=\"'filters.toggle' | transloco\"\n class=\"shrink-0 cursor-pointer [&_svg]:transition-transform [&_svg]:duration-150 group-open:[&_svg]:rotate-90\">\n <chevron-right class=\"shrink-0\" />\n <span class=\"sr-only\">{{ \"filters.toggle\" | transloco }}</span>\n </button>\n }\n</ng-template>\n\n<ng-template #bodySlot>\n <div class=\"flex min-h-0 flex-1 flex-col\">\n @if (canSearch()) {\n <!-- The field is always in the DOM and folded to nothing until it is asked for: a grid row that goes from 0fr to 1fr is\n the one height animation that needs no measuring. `inert` takes the folded field out of the tab order and of the\n accessibility tree. The margins live in the inner box: a padding on the folded element itself would keep it\n from reaching zero.\n\n No `hotkey`: this field is one of many facet search boxes on the page, so it must not advertise a global shortcut.\n A placeholder is not a label (RGAA 11.1): the name says WHICH facet this box filters — the one thing that tells a\n dozen identical fields apart. SearchInput hands its class to its inner field, so a margin on it would count twice,\n which is one more reason for the padding on the box around it. -->\n <div\n [id]=\"searchId\"\n class=\"grid shrink-0 transition-[grid-template-rows,opacity] duration-200 ease-[cubic-bezier(0.32,0.72,0,1)] motion-reduce:transition-none\"\n [class]=\"\n showSearch()\n ? 'grid-rows-[1fr] opacity-100'\n : 'grid-rows-[0fr] opacity-0'\n \"\n [inert]=\"!showSearch()\">\n <!-- `-mx-1 px-1`: the box clips what is outside it, and the focus ring of the field is drawn outside the field,\n so it needs a few pixels of room on each side — taken back by the negative margin so nothing moves. -->\n <div class=\"-mx-1 min-h-0 overflow-hidden px-1\">\n <div [class]=\"headless() ? 'pt-0.5 pb-1' : 'pt-2 pb-1'\">\n <SearchInput\n [placeholder]=\"'search' | transloco\"\n [clearLabel]=\"'filters.clearSearch' | transloco\"\n [aria-label]=\"\n 'filters.searchInFacet'\n | transloco\n : { facet: (aggregation()?.display | syslang | transloco) }\n \"\n [(value)]=\"searchText\"\n (keydown.escape)=\"onSearchEscape($event)\">\n <ng-content select=\"[search-addon]\" />\n </SearchInput>\n </div>\n </div>\n </div>\n }\n\n @if (itemsLength() > 0 || hasFilters() || selection()) {\n <!-- The bar under the search field, in a popover and in the filters drawer alike: select all (the count of selected items in its tooltip), then clear and apply. Clear is a button\n with a text, not an icon, and stays in place (disabled) when there is nothing to clear. Apply only shows once\n something is pending, as it always did, and sits at the same level. The checkbox keeps its own name: the text\n beside it is a count, not the label of what it does. -->\n <div class=\"mb-1 flex shrink-0 items-center gap-1 pl-1.5\">\n <!-- How many are selected is in the tooltip of the checkbox, not written beside it: that room is what clear and apply need.\n Screen readers keep it as text (the tooltip only shows on hover and focus, and describes). -->\n <span\n class=\"inline-flex shrink-0\"\n [tooltip]=\"\n 'filters.selectedCount' | transloco: { count: selectedCount() }\n \"\n tooltipPosition=\"bottom\">\n <checkbox\n [checked]=\"isAllSelected()\"\n [indeterminate]=\"selectedCount() > 0 && !isAllSelected()\"\n [aria-label]=\"'filters.selectAllFilters' | transloco\"\n (checkedChange)=\"\n $event ? allSelected.emit() : allUnselected.emit()\n \" />\n </span>\n <span class=\"sr-only\" aria-live=\"polite\">{{\n \"filters.selectedCount\" | transloco: { count: selectedCount() }\n }}</span>\n <span class=\"flex-1\" aria-hidden=\"true\"></span>\n @if (headless() && canSearch()) {\n <ng-container [ngTemplateOutlet]=\"searchToggleSlot\" />\n }\n <button\n type=\"button\"\n variant=\"tertiary\"\n size=\"sm\"\n class=\"shrink-0\"\n [disabled]=\"!hasFilters() && selectedCount() === 0\"\n [attr.aria-label]=\"'filters.clearFilters' | transloco\"\n (click)=\"cleared.emit()\">\n {{ \"filters.clearAll\" | transloco }}\n </button>\n @if (selection()) {\n <!-- No icon: the room is short, and the word says it. -->\n <button\n type=\"button\"\n variant=\"accent\"\n size=\"sm\"\n class=\"shrink-0 overflow-hidden\"\n (click)=\"applied.emit()\">\n <span class=\"truncate\">{{ \"filters.apply\" | transloco }}</span>\n </button>\n }\n </div>\n }\n\n <ng-content />\n\n @if (hasMore() && searchedItemsLength() === 0) {\n <button\n type=\"button\"\n class=\"mt-1 flex w-full shrink-0 justify-center\"\n [attr.aria-label]=\"'loadMore' | transloco\"\n (click)=\"loadedMore.emit()\">\n {{ \"loadMore\" | transloco }}\n </button>\n }\n </div>\n</ng-template>\n\n@if (headless()) {\n <div class=\"flex min-h-0 flex-1 flex-col p-0.5\">\n <ng-container [ngTemplateOutlet]=\"bodySlot\" />\n </div>\n} @else {\n <details\n [attr.open]=\"expanded()\"\n [attr.name]=\"id()\"\n class=\"group flex min-h-0 flex-1 flex-col p-0.5 details-content:flex details-content:min-h-0 details-content:flex-col\"\n (toggle)=\"onToggle($event)\">\n <summary\n tabindex=\"-1\"\n [class.cursor-pointer]=\"collapsible() && !isEmpty()\"\n [class]=\"\n (isEmpty() ? 'text-(--font-neutral-muted) ' : '') + rowStateClass\n \"\n class=\"m-0 flex h-8 w-full shrink-0 scroll-mt-16 scroll-mb-3 items-center gap-1 pl-1 font-semibold select-none\"\n (click)=\"onHeaderClick($event)\">\n <ng-container [ngTemplateOutlet]=\"labelSlot\" />\n <ng-container [ngTemplateOutlet]=\"headerActionsSlot\" />\n </summary>\n <ng-container [ngTemplateOutlet]=\"bodySlot\" />\n </details>\n}\n",
|
|
25267
|
+
styles: [":host{interpolate-size:allow-keywords}details::details-content{flex:1 1 auto;block-size:0;overflow:clip;overflow-clip-margin:.25rem;transition:block-size .2s cubic-bezier(.32,.72,0,1),content-visibility .2s allow-discrete}details[open]::details-content{block-size:auto}@media(prefers-reduced-motion:reduce){details::details-content{transition:none}}\n"]
|
|
25175
25268
|
}]
|
|
25176
25269
|
}],
|
|
25177
|
-
ctorParameters: () => [],
|
|
25178
25270
|
propDecorators: {
|
|
25179
25271
|
id: [{
|
|
25180
25272
|
type: i0.Input,
|
|
@@ -25338,6 +25430,10 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
25338
25430
|
searchBox: [{
|
|
25339
25431
|
type: i0.ViewChild,
|
|
25340
25432
|
args: [i0.forwardRef(() => SearchInputComponent), { isSignal: true }]
|
|
25433
|
+
}],
|
|
25434
|
+
searchToggle: [{
|
|
25435
|
+
type: i0.ViewChild,
|
|
25436
|
+
args: ["searchToggle", { isSignal: true }]
|
|
25341
25437
|
}]
|
|
25342
25438
|
}
|
|
25343
25439
|
});
|
|
@@ -26038,7 +26134,7 @@ var AggregationTreeComponent = class AggregationTreeComponent {
|
|
|
26038
26134
|
isSignal: true
|
|
26039
26135
|
}],
|
|
26040
26136
|
ngImport: i0,
|
|
26041
|
-
template: "@if (!aggregation()?.isTree) {\n <div role=\"alert\" class=\"p-2 text-sm text-red-500\">\n <triangle-alert-icon class=\"mr-1 shrink-0\" />\n The aggregationTree component does not support list aggregations. Please use\n the <Aggregation /> component instead.\n </div>\n}\n<aggregation-panel\n [id]=\"id()\"\n [collapsible]=\"collapsible()\"\n [collapsed]=\"collapsed()\"\n [isEmpty]=\"isEmpty()\"\n [isDate]=\"isDate()\"\n [aggregation]=\"aggregation()\"\n [showFiltersCount]=\"showFiltersCount()\"\n [filtersCount]=\"filtersCount()\"\n [hasFilters]=\"hasFilters()\"\n [selection]=\"selection()\"\n [isAllSelected]=\"isAllSelected()\"\n [headless]=\"headless()\"\n [selectedCount]=\"selected().length\"\n [(searchText)]=\"searchText\"\n [itemsLength]=\"items().length\"\n [hasMore]=\"aggregation()?.$hasMore ?? false\"\n [searchedItemsLength]=\"searchedItems().length\"\n (cleared)=\"clear()\"\n (applied)=\"apply()\"\n (allSelected)=\"selectAll()\"\n (allUnselected)=\"unselectAll()\"\n (loadedMore)=\"loadMore()\">\n <!-- Relay the host's `label` projection into the panel's `label` slot. When the\n host projects nothing, the local <ng-content> falls back to the icon + display\n (mirrors AggregationPanel's own fallback), so the header is never empty. -->\n <ng-container ngProjectAs=\"label\">\n <ng-content select=\"label\">\n @let labelIcon = aggregation()?.icon;\n @if (labelIcon) {\n <FaIcon [faClass]=\"labelIcon\" class=\"mr-1 shrink-0\" />\n }\n <span class=\"grow truncate\">{{\n aggregation()?.display | syslang | transloco\n }}</span>\n </ng-content>\n </ng-container>\n <div class=\"min-h-0 w-full flex-1 scrollbar-thin overflow-auto pb-3\">\n <ul\n ngTree\n #tree=\"ngTree\"\n [multi]=\"true\"\n [(value)]=\"selected\"\n (valueChange)=\"onSelectionChange($event)\"\n /* Translated like the flat list's own label (aggregation-list.html): this was the raw\n `display`, so the accessible name of a whole facet came out in the source language.\n The pipes apply to the whole nullish chain, `|` being the loosest operator Angular\n parses — no parentheses needed. */\n [attr.aria-label]=\"aggregation()?.display ?? name() | syslang | transloco\"\n class=\"basic-tree m-1\">\n <ng-template\n [ngTemplateOutlet]=\"treeNodes\"\n [ngTemplateOutletContext]=\"{ nodes: nodes(), parent: tree }\" />\n </ul>\n </div>\n</aggregation-panel>\n\n<ng-template #treeNodes let-nodes=\"nodes\" let-parent=\"parent\">\n @for (node of nodes; track node.$path) {\n @let label = node.display ?? node.value ?? \"\";\n <li\n ngTreeItem\n #treeItem=\"ngTreeItem\"\n [parent]=\"parent\"\n [value]=\"node.$path\"\n [label]=\"label\"\n [disabled]=\"node.disabled\"\n [(expanded)]=\"node.$opened\"\n (expandedChange)=\"onExpandedChange(node, $event)\"\n [attr.aria-label]=\"label\"\n [attr.aria-describedby]=\"\n nodeDescription(node) ? descriptionId(node.$path) : null\n \"\n [class]=\"cn('m-0 block w-full p-0', itemStateClass)\"\n (keydown.enter)=\"onTreeItemEnterKey(node, $event)\"\n (keydown.space)=\"onTreeItemSpaceKey(node, $event)\">\n <!-- 2. Ce div prend tout l'espace et intercepte tous les clics de la ligne. `ps-0.5`: the checkbox of a list option sits\n 2px further in than a tree's (the option has its own padding), so the checkboxes of the two facets, and the select-all of\n the bar of a popover, are on the same vertical line. -->\n <div\n class=\"flex w-full cursor-pointer items-center gap-2 py-0.5 ps-0.5 text-sm leading-6\"\n (click)=\"onTreeItemRowClick(node, $event)\"\n (mousedown)=\"$event.stopPropagation()\"\n (pointerdown)=\"$event.stopPropagation()\">\n @if (showCheckbox()) {\n <!-- `inert` keeps the native input out of the tab order and out of hit-testing:\n a role=\"treeitem\" admits no focusable descendant, and the click must reach\n the row handler above. Selection is announced by the <li>'s aria-selected. -->\n <!-- --spacing-xs: 0 kills the gap the checkbox keeps between its box and its\n (here empty) text block. Set as a custom property rather than a utility\n class: the CVA already sets gap-(--spacing-xs), and two same-specificity\n classes would be resolved by stylesheet order, not by us. -->\n <span\n inert\n aria-hidden=\"true\"\n class=\"inline-flex shrink-0\"\n style=\"--spacing-xs: 0\">\n <checkbox\n [checked]=\"treeItem.selected() || node.$selectedVisually || false\"\n [disabled]=\"node.disabled\"\n [class]=\"\n cn(\n 'pointer-events-none',\n (treeItem.selected() || node.$selectedVisually) &&\n 'selected-icon'\n )\n \" />\n </span>\n }\n <!-- Depth indent sits on the chevron, after the checkbox: the checkboxes of every\n level stay flush left (aligned with a flat list's), and since this adds no extra\n flex child, the step between consecutive levels is exactly one indent. -->\n @let depthIndent =\n \"calc(var(--agg-tree-indent, 0.5rem) * \" +\n (treeItem.level() - 1) +\n \")\";\n @if (node.hasChildren) {\n <!-- Expand/collapse affordance for the mouse only, hence a <span> and not a <button>:\n the keyboard already expands with ArrowRight / ArrowLeft, handled by ngTreeItem, and\n a role=\"treeitem\" admits no focusable descendant — so this was a <button> nobody\n could reach, already carrying aria-hidden and tabindex=\"-1\" to say so. Every audit\n reads it as a button with no accessible name (RGAA 7.1, 11.9). Same shape as the leaf\n branch below. -->\n <span\n aria-hidden=\"true\"\n (click)=\"toggleTreeNode(treeItem, $event)\"\n [style.marginInlineStart]=\"depthIndent\"\n class=\"inline-flex cursor-pointer items-center justify-center\">\n <ChevronRightIcon\n [class]=\"\n cn('expand-icon shrink-0', node.$opened && 'rotate-90')\n \" />\n <FolderIcon class=\"shrink-0\" />\n </span>\n } @else {\n <span\n aria-hidden=\"true\"\n [style.marginInlineStart]=\"depthIndent\"\n class=\"inline-flex items-center justify-center\">\n <!-- Reserves the chevron's own width (`.expand-icon` is a styling hook with\n no rule of its own), so a leaf lines up with its parent nodes. -->\n <span class=\"expand-icon w-4 shrink-0\"></span>\n <FileIcon class=\"size-4 shrink-0\" />\n </span>\n }\n @if (resolvedItemTemplate(); as tpl) {\n <span\n class=\"grow truncate select-none\"\n (click)=\"onTreeItemTextClick(node, $event)\">\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{\n $implicit: node,\n field: aggregation()?.column,\n selected: treeItem.selected() ?? false,\n count: node.count,\n name: label,\n searchText: searchText(),\n level: node.$level ?? 0,\n expanded: node.$opened ?? false,\n hasChildren: node.hasChildren ?? false\n }\" />\n </span>\n } @else {\n <!-- ce span est essentiel pour le style -->\n @let appliesOnLabelClick = quickFilter() || applyOnSelect();\n <span\n [class]=\"\n cn(\n 'grow truncate select-none',\n appliesOnLabelClick && 'hover:underline'\n )\n \"\n [title]=\"\n appliesOnLabelClick\n ? ((isTreeItemFiltered(node)\n ? 'filters.removeFilter'\n : 'filters.addFilter'\n ) | transloco) +\n ': ' +\n label\n : label\n \"\n (click)=\"onTreeItemTextClick(node, $event)\">\n @for (\n chunk of label | highlightWord: searchText() : 10;\n track $index\n ) {\n <span [class]=\"{ 'font-bold': chunk.match }\" aria-hidden=\"true\">{{\n chunk.text\n }}</span>\n }\n </span>\n @if (showCount() && node.count > 0) {\n <span class=\"ml-auto px-1 text-xs\" aria-hidden=\"true\">{{\n node.count\n }}</span>\n }\n }\n @let description = nodeDescription(node);\n @if (description) {\n <!-- `hidden`, not `sr-only`: read through aria-describedby only. A visually-hidden\n span would also be counted in the treeitem's name (computed from its content). -->\n <span hidden [id]=\"descriptionId(node.$path)\">{{ description }}</span>\n }\n </div>\n </li>\n @if (node.hasChildren) {\n <!-- No indentation here: it would shift the checkbox too. Depth is applied\n inside the row, after the checkbox, so every checkbox stays flush left. -->\n <!-- The <ul> sits INSIDE the deferred content, not around it: `ngTreeItemGroup` only\n instantiates its view when the node opens, so a <ul> declared outside stayed in the DOM,\n empty, for every collapsed node — a structural tag with nothing to structure (RGAA 8.9).\n The @if cannot move onto the <ul> either: the directive registers itself with its tree\n item on init and has to exist for as long as the item does. -->\n <ng-template\n ngTreeItemGroup\n [ownedBy]=\"treeItem\"\n #group=\"ngTreeItemGroup\">\n <ul role=\"group\">\n <ng-template\n [ngTemplateOutlet]=\"treeNodes\"\n [ngTemplateOutletContext]=\"{ nodes: node.items, parent: group }\" />\n </ul>\n </ng-template>\n }\n }\n</ng-template>\n",
|
|
26137
|
+
template: "@if (!aggregation()?.isTree) {\n <div role=\"alert\" class=\"p-2 text-sm text-red-500\">\n <triangle-alert-icon class=\"mr-1 shrink-0\" />\n The aggregationTree component does not support list aggregations. Please use\n the <Aggregation /> component instead.\n </div>\n}\n<aggregation-panel\n [id]=\"id()\"\n [collapsible]=\"collapsible()\"\n [collapsed]=\"collapsed()\"\n [isEmpty]=\"isEmpty()\"\n [isDate]=\"isDate()\"\n [aggregation]=\"aggregation()\"\n [showFiltersCount]=\"showFiltersCount()\"\n [filtersCount]=\"filtersCount()\"\n [hasFilters]=\"hasFilters()\"\n [selection]=\"selection()\"\n [isAllSelected]=\"isAllSelected()\"\n [headless]=\"headless()\"\n [selectedCount]=\"selected().length\"\n [(searchText)]=\"searchText\"\n [itemsLength]=\"items().length\"\n [hasMore]=\"aggregation()?.$hasMore ?? false\"\n [searchedItemsLength]=\"searchedItems().length\"\n (cleared)=\"clear()\"\n (applied)=\"apply()\"\n (allSelected)=\"selectAll()\"\n (allUnselected)=\"unselectAll()\"\n (loadedMore)=\"loadMore()\">\n <!-- Relay the host's `label` projection into the panel's `label` slot. When the\n host projects nothing, the local <ng-content> falls back to the icon + display\n (mirrors AggregationPanel's own fallback), so the header is never empty. -->\n <ng-container ngProjectAs=\"label\">\n <ng-content select=\"label\">\n @let labelIcon = aggregation()?.icon;\n @if (labelIcon) {\n <FaIcon [faClass]=\"labelIcon\" class=\"mr-1 shrink-0\" />\n }\n <span class=\"grow truncate\">{{\n aggregation()?.display | syslang | transloco\n }}</span>\n </ng-content>\n </ng-container>\n <div class=\"min-h-0 w-full flex-1 scrollbar-thin overflow-auto pb-3\">\n <ul\n ngTree\n #tree=\"ngTree\"\n [multi]=\"true\"\n [(value)]=\"selected\"\n (valueChange)=\"onSelectionChange($event)\"\n /* Translated like the flat list's own label (aggregation-list.html): this was the raw\n `display`, so the accessible name of a whole facet came out in the source language.\n The pipes apply to the whole nullish chain, `|` being the loosest operator Angular\n parses — no parentheses needed. */\n [attr.aria-label]=\"aggregation()?.display ?? name() | syslang | transloco\"\n class=\"basic-tree m-1\">\n <ng-template\n [ngTemplateOutlet]=\"treeNodes\"\n [ngTemplateOutletContext]=\"{ nodes: nodes(), parent: tree }\" />\n </ul>\n </div>\n</aggregation-panel>\n\n<ng-template #treeNodes let-nodes=\"nodes\" let-parent=\"parent\">\n @for (node of nodes; track node.$path) {\n @let label = node.display ?? node.value ?? \"\";\n <li\n ngTreeItem\n #treeItem=\"ngTreeItem\"\n [parent]=\"parent\"\n [value]=\"node.$path\"\n [label]=\"label\"\n [disabled]=\"node.disabled\"\n [(expanded)]=\"node.$opened\"\n (expandedChange)=\"onExpandedChange(node, $event)\"\n [attr.aria-label]=\"label\"\n [attr.aria-describedby]=\"\n nodeDescription(node) ? descriptionId(node.$path) : null\n \"\n [class]=\"cn('m-0 block w-full p-0', itemStateClass)\"\n (keydown.enter)=\"onTreeItemEnterKey(node, $event)\"\n (keydown.space)=\"onTreeItemSpaceKey(node, $event)\">\n <!-- 2. Ce div prend tout l'espace et intercepte tous les clics de la ligne. `ps-0.5`: the checkbox of a list option sits\n 2px further in than a tree's (the option has its own padding), so the checkboxes of the two facets, and the select-all of\n the bar of a popover, are on the same vertical line. -->\n <div\n class=\"flex w-full cursor-pointer items-center gap-2 py-0.5 ps-0.5 text-sm leading-6\"\n (click)=\"onTreeItemRowClick(node, $event)\"\n (mousedown)=\"$event.stopPropagation()\"\n (pointerdown)=\"$event.stopPropagation()\">\n @if (showCheckbox()) {\n <!-- `inert` keeps the native input out of the tab order and out of hit-testing:\n a role=\"treeitem\" admits no focusable descendant, and the click must reach\n the row handler above. Selection is announced by the <li>'s aria-selected. -->\n <!-- --spacing-xs: 0 kills the gap the checkbox keeps between its box and its\n (here empty) text block. Set as a custom property rather than a utility\n class: the CVA already sets gap-(--spacing-xs), and two same-specificity\n classes would be resolved by stylesheet order, not by us. -->\n <span\n inert\n aria-hidden=\"true\"\n class=\"inline-flex shrink-0\"\n style=\"--spacing-xs: 0\">\n <checkbox\n [checked]=\"treeItem.selected() || node.$selectedVisually || false\"\n [disabled]=\"node.disabled\"\n [class]=\"\n cn(\n 'pointer-events-none',\n (treeItem.selected() || node.$selectedVisually) &&\n 'selected-icon'\n )\n \" />\n </span>\n }\n <!-- Depth indent sits on the chevron, after the checkbox: the checkboxes of every\n level stay flush left (aligned with a flat list's), and since this adds no extra\n flex child, the step between consecutive levels is exactly one indent. -->\n @let depthIndent =\n \"calc(var(--agg-tree-indent, 0.5rem) * \" +\n (treeItem.level() - 1) +\n \")\";\n @if (node.hasChildren) {\n <!-- Expand/collapse affordance for the mouse only, hence a <span> and not a <button>:\n the keyboard already expands with ArrowRight / ArrowLeft, handled by ngTreeItem, and\n a role=\"treeitem\" admits no focusable descendant — so this was a <button> nobody\n could reach, already carrying aria-hidden and tabindex=\"-1\" to say so. Every audit\n reads it as a button with no accessible name (RGAA 7.1, 11.9). Same shape as the leaf\n branch below. -->\n <span\n aria-hidden=\"true\"\n (click)=\"toggleTreeNode(treeItem, $event)\"\n [style.marginInlineStart]=\"depthIndent\"\n class=\"inline-flex cursor-pointer items-center justify-center\">\n <ChevronRightIcon\n [class]=\"\n cn('expand-icon shrink-0', node.$opened && 'rotate-90')\n \" />\n <FolderIcon class=\"shrink-0\" />\n </span>\n } @else {\n <span\n aria-hidden=\"true\"\n [style.marginInlineStart]=\"depthIndent\"\n class=\"inline-flex items-center justify-center\">\n <!-- Reserves the chevron's own width (`.expand-icon` is a styling hook with\n no rule of its own), so a leaf lines up with its parent nodes. -->\n <span class=\"expand-icon w-4 shrink-0\"></span>\n <FileIcon class=\"size-4 shrink-0\" />\n </span>\n }\n @if (resolvedItemTemplate(); as tpl) {\n <!-- Cut off, the label shows whole in a tooltip; a label that fits needs none. -->\n <span\n class=\"grow truncate select-none\"\n truncated\n #cutCustom=\"truncated\"\n [tooltip]=\"label\"\n [tooltipDisabled]=\"!cutCustom.isTruncated()\"\n (click)=\"onTreeItemTextClick(node, $event)\">\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{\n $implicit: node,\n field: aggregation()?.column,\n selected: treeItem.selected() ?? false,\n count: node.count,\n name: label,\n searchText: searchText(),\n level: node.$level ?? 0,\n expanded: node.$opened ?? false,\n hasChildren: node.hasChildren ?? false\n }\" />\n </span>\n } @else {\n <!-- ce span est essentiel pour le style -->\n @let appliesOnLabelClick = quickFilter() || applyOnSelect();\n <!-- Cut off, the label shows whole in a tooltip (`truncated` measures it); a label that fits has none. -->\n <span\n [class]=\"\n cn(\n 'grow truncate select-none',\n appliesOnLabelClick && 'hover:underline'\n )\n \"\n truncated\n #cut=\"truncated\"\n [tooltip]=\"label\"\n [tooltipDisabled]=\"!cut.isTruncated()\"\n (click)=\"onTreeItemTextClick(node, $event)\">\n @for (\n chunk of label | highlightWord: searchText() : 10;\n track $index\n ) {\n <span [class]=\"{ 'font-bold': chunk.match }\" aria-hidden=\"true\">{{\n chunk.text\n }}</span>\n }\n </span>\n @if (showCount() && node.count > 0) {\n <span class=\"ml-auto px-1 text-xs\" aria-hidden=\"true\">{{\n node.count\n }}</span>\n }\n }\n @let description = nodeDescription(node);\n @if (description) {\n <!-- `hidden`, not `sr-only`: read through aria-describedby only. A visually-hidden\n span would also be counted in the treeitem's name (computed from its content). -->\n <span hidden [id]=\"descriptionId(node.$path)\">{{ description }}</span>\n }\n </div>\n </li>\n @if (node.hasChildren) {\n <!-- No indentation here: it would shift the checkbox too. Depth is applied\n inside the row, after the checkbox, so every checkbox stays flush left. -->\n <!-- The <ul> sits INSIDE the deferred content, not around it: `ngTreeItemGroup` only\n instantiates its view when the node opens, so a <ul> declared outside stayed in the DOM,\n empty, for every collapsed node — a structural tag with nothing to structure (RGAA 8.9).\n The @if cannot move onto the <ul> either: the directive registers itself with its tree\n item on init and has to exist for as long as the item does. -->\n <ng-template\n ngTreeItemGroup\n [ownedBy]=\"treeItem\"\n #group=\"ngTreeItemGroup\">\n <ul role=\"group\">\n <ng-template\n [ngTemplateOutlet]=\"treeNodes\"\n [ngTemplateOutletContext]=\"{ nodes: node.items, parent: group }\" />\n </ul>\n </ng-template>\n }\n }\n</ng-template>\n",
|
|
26042
26138
|
dependencies: [
|
|
26043
26139
|
{
|
|
26044
26140
|
kind: "directive",
|
|
@@ -26088,12 +26184,45 @@ var AggregationTreeComponent = class AggregationTreeComponent {
|
|
|
26088
26184
|
selector: "folder, folder-icon, Folder, FolderIcon",
|
|
26089
26185
|
inputs: ["class"]
|
|
26090
26186
|
},
|
|
26187
|
+
{
|
|
26188
|
+
kind: "directive",
|
|
26189
|
+
type: TooltipDirective,
|
|
26190
|
+
selector: "[tooltip]",
|
|
26191
|
+
inputs: [
|
|
26192
|
+
"tooltip",
|
|
26193
|
+
"tooltipPosition",
|
|
26194
|
+
"tooltipEvent",
|
|
26195
|
+
"showDelay",
|
|
26196
|
+
"hideDelay",
|
|
26197
|
+
"life",
|
|
26198
|
+
"tooltipOffset",
|
|
26199
|
+
"autoHide",
|
|
26200
|
+
"hideOnEscape",
|
|
26201
|
+
"escape",
|
|
26202
|
+
"tooltipDisabled",
|
|
26203
|
+
"tooltipArrow",
|
|
26204
|
+
"tooltipStyleClass",
|
|
26205
|
+
"tooltipOptions",
|
|
26206
|
+
"tooltip-position",
|
|
26207
|
+
"tooltip-delay",
|
|
26208
|
+
"tooltip-duration",
|
|
26209
|
+
"tooltip-offset"
|
|
26210
|
+
],
|
|
26211
|
+
exportAs: ["tooltip"]
|
|
26212
|
+
},
|
|
26091
26213
|
{
|
|
26092
26214
|
kind: "component",
|
|
26093
26215
|
type: TriangleAlertIcon,
|
|
26094
26216
|
selector: "triangle-alert, triangle-alert-icon, TriangleAlert, TriangleAlertIcon",
|
|
26095
26217
|
inputs: ["class"]
|
|
26096
26218
|
},
|
|
26219
|
+
{
|
|
26220
|
+
kind: "directive",
|
|
26221
|
+
type: TruncatedDirective,
|
|
26222
|
+
selector: "[truncated]",
|
|
26223
|
+
inputs: ["truncated"],
|
|
26224
|
+
exportAs: ["truncated"]
|
|
26225
|
+
},
|
|
26097
26226
|
{
|
|
26098
26227
|
kind: "directive",
|
|
26099
26228
|
type: Tree,
|
|
@@ -26205,7 +26334,9 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
26205
26334
|
FaIconComponent,
|
|
26206
26335
|
FileIcon,
|
|
26207
26336
|
FolderIcon,
|
|
26337
|
+
TooltipDirective,
|
|
26208
26338
|
TriangleAlertIcon,
|
|
26339
|
+
TruncatedDirective,
|
|
26209
26340
|
HighlightWordPipe,
|
|
26210
26341
|
SyslangPipe,
|
|
26211
26342
|
TranslocoPipe,
|
|
@@ -26215,7 +26346,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
26215
26346
|
AggregationPanelComponent
|
|
26216
26347
|
],
|
|
26217
26348
|
host: { "[class]": "cn(\"flex h-[inherit] max-h-[inherit] flex-col\", class(), (isMissing() || (hideWhenEmpty() && isHidden())) && \"hidden\")" },
|
|
26218
|
-
template: "@if (!aggregation()?.isTree) {\n <div role=\"alert\" class=\"p-2 text-sm text-red-500\">\n <triangle-alert-icon class=\"mr-1 shrink-0\" />\n The aggregationTree component does not support list aggregations. Please use\n the <Aggregation /> component instead.\n </div>\n}\n<aggregation-panel\n [id]=\"id()\"\n [collapsible]=\"collapsible()\"\n [collapsed]=\"collapsed()\"\n [isEmpty]=\"isEmpty()\"\n [isDate]=\"isDate()\"\n [aggregation]=\"aggregation()\"\n [showFiltersCount]=\"showFiltersCount()\"\n [filtersCount]=\"filtersCount()\"\n [hasFilters]=\"hasFilters()\"\n [selection]=\"selection()\"\n [isAllSelected]=\"isAllSelected()\"\n [headless]=\"headless()\"\n [selectedCount]=\"selected().length\"\n [(searchText)]=\"searchText\"\n [itemsLength]=\"items().length\"\n [hasMore]=\"aggregation()?.$hasMore ?? false\"\n [searchedItemsLength]=\"searchedItems().length\"\n (cleared)=\"clear()\"\n (applied)=\"apply()\"\n (allSelected)=\"selectAll()\"\n (allUnselected)=\"unselectAll()\"\n (loadedMore)=\"loadMore()\">\n <!-- Relay the host's `label` projection into the panel's `label` slot. When the\n host projects nothing, the local <ng-content> falls back to the icon + display\n (mirrors AggregationPanel's own fallback), so the header is never empty. -->\n <ng-container ngProjectAs=\"label\">\n <ng-content select=\"label\">\n @let labelIcon = aggregation()?.icon;\n @if (labelIcon) {\n <FaIcon [faClass]=\"labelIcon\" class=\"mr-1 shrink-0\" />\n }\n <span class=\"grow truncate\">{{\n aggregation()?.display | syslang | transloco\n }}</span>\n </ng-content>\n </ng-container>\n <div class=\"min-h-0 w-full flex-1 scrollbar-thin overflow-auto pb-3\">\n <ul\n ngTree\n #tree=\"ngTree\"\n [multi]=\"true\"\n [(value)]=\"selected\"\n (valueChange)=\"onSelectionChange($event)\"\n /* Translated like the flat list's own label (aggregation-list.html): this was the raw\n `display`, so the accessible name of a whole facet came out in the source language.\n The pipes apply to the whole nullish chain, `|` being the loosest operator Angular\n parses — no parentheses needed. */\n [attr.aria-label]=\"aggregation()?.display ?? name() | syslang | transloco\"\n class=\"basic-tree m-1\">\n <ng-template\n [ngTemplateOutlet]=\"treeNodes\"\n [ngTemplateOutletContext]=\"{ nodes: nodes(), parent: tree }\" />\n </ul>\n </div>\n</aggregation-panel>\n\n<ng-template #treeNodes let-nodes=\"nodes\" let-parent=\"parent\">\n @for (node of nodes; track node.$path) {\n @let label = node.display ?? node.value ?? \"\";\n <li\n ngTreeItem\n #treeItem=\"ngTreeItem\"\n [parent]=\"parent\"\n [value]=\"node.$path\"\n [label]=\"label\"\n [disabled]=\"node.disabled\"\n [(expanded)]=\"node.$opened\"\n (expandedChange)=\"onExpandedChange(node, $event)\"\n [attr.aria-label]=\"label\"\n [attr.aria-describedby]=\"\n nodeDescription(node) ? descriptionId(node.$path) : null\n \"\n [class]=\"cn('m-0 block w-full p-0', itemStateClass)\"\n (keydown.enter)=\"onTreeItemEnterKey(node, $event)\"\n (keydown.space)=\"onTreeItemSpaceKey(node, $event)\">\n <!-- 2. Ce div prend tout l'espace et intercepte tous les clics de la ligne. `ps-0.5`: the checkbox of a list option sits\n 2px further in than a tree's (the option has its own padding), so the checkboxes of the two facets, and the select-all of\n the bar of a popover, are on the same vertical line. -->\n <div\n class=\"flex w-full cursor-pointer items-center gap-2 py-0.5 ps-0.5 text-sm leading-6\"\n (click)=\"onTreeItemRowClick(node, $event)\"\n (mousedown)=\"$event.stopPropagation()\"\n (pointerdown)=\"$event.stopPropagation()\">\n @if (showCheckbox()) {\n <!-- `inert` keeps the native input out of the tab order and out of hit-testing:\n a role=\"treeitem\" admits no focusable descendant, and the click must reach\n the row handler above. Selection is announced by the <li>'s aria-selected. -->\n <!-- --spacing-xs: 0 kills the gap the checkbox keeps between its box and its\n (here empty) text block. Set as a custom property rather than a utility\n class: the CVA already sets gap-(--spacing-xs), and two same-specificity\n classes would be resolved by stylesheet order, not by us. -->\n <span\n inert\n aria-hidden=\"true\"\n class=\"inline-flex shrink-0\"\n style=\"--spacing-xs: 0\">\n <checkbox\n [checked]=\"treeItem.selected() || node.$selectedVisually || false\"\n [disabled]=\"node.disabled\"\n [class]=\"\n cn(\n 'pointer-events-none',\n (treeItem.selected() || node.$selectedVisually) &&\n 'selected-icon'\n )\n \" />\n </span>\n }\n <!-- Depth indent sits on the chevron, after the checkbox: the checkboxes of every\n level stay flush left (aligned with a flat list's), and since this adds no extra\n flex child, the step between consecutive levels is exactly one indent. -->\n @let depthIndent =\n \"calc(var(--agg-tree-indent, 0.5rem) * \" +\n (treeItem.level() - 1) +\n \")\";\n @if (node.hasChildren) {\n <!-- Expand/collapse affordance for the mouse only, hence a <span> and not a <button>:\n the keyboard already expands with ArrowRight / ArrowLeft, handled by ngTreeItem, and\n a role=\"treeitem\" admits no focusable descendant — so this was a <button> nobody\n could reach, already carrying aria-hidden and tabindex=\"-1\" to say so. Every audit\n reads it as a button with no accessible name (RGAA 7.1, 11.9). Same shape as the leaf\n branch below. -->\n <span\n aria-hidden=\"true\"\n (click)=\"toggleTreeNode(treeItem, $event)\"\n [style.marginInlineStart]=\"depthIndent\"\n class=\"inline-flex cursor-pointer items-center justify-center\">\n <ChevronRightIcon\n [class]=\"\n cn('expand-icon shrink-0', node.$opened && 'rotate-90')\n \" />\n <FolderIcon class=\"shrink-0\" />\n </span>\n } @else {\n <span\n aria-hidden=\"true\"\n [style.marginInlineStart]=\"depthIndent\"\n class=\"inline-flex items-center justify-center\">\n <!-- Reserves the chevron's own width (`.expand-icon` is a styling hook with\n no rule of its own), so a leaf lines up with its parent nodes. -->\n <span class=\"expand-icon w-4 shrink-0\"></span>\n <FileIcon class=\"size-4 shrink-0\" />\n </span>\n }\n @if (resolvedItemTemplate(); as tpl) {\n <span\n class=\"grow truncate select-none\"\n (click)=\"onTreeItemTextClick(node, $event)\">\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{\n $implicit: node,\n field: aggregation()?.column,\n selected: treeItem.selected() ?? false,\n count: node.count,\n name: label,\n searchText: searchText(),\n level: node.$level ?? 0,\n expanded: node.$opened ?? false,\n hasChildren: node.hasChildren ?? false\n }\" />\n </span>\n } @else {\n <!-- ce span est essentiel pour le style -->\n @let appliesOnLabelClick = quickFilter() || applyOnSelect();\n <span\n [class]=\"\n cn(\n 'grow truncate select-none',\n appliesOnLabelClick && 'hover:underline'\n )\n \"\n [title]=\"\n appliesOnLabelClick\n ? ((isTreeItemFiltered(node)\n ? 'filters.removeFilter'\n : 'filters.addFilter'\n ) | transloco) +\n ': ' +\n label\n : label\n \"\n (click)=\"onTreeItemTextClick(node, $event)\">\n @for (\n chunk of label | highlightWord: searchText() : 10;\n track $index\n ) {\n <span [class]=\"{ 'font-bold': chunk.match }\" aria-hidden=\"true\">{{\n chunk.text\n }}</span>\n }\n </span>\n @if (showCount() && node.count > 0) {\n <span class=\"ml-auto px-1 text-xs\" aria-hidden=\"true\">{{\n node.count\n }}</span>\n }\n }\n @let description = nodeDescription(node);\n @if (description) {\n <!-- `hidden`, not `sr-only`: read through aria-describedby only. A visually-hidden\n span would also be counted in the treeitem's name (computed from its content). -->\n <span hidden [id]=\"descriptionId(node.$path)\">{{ description }}</span>\n }\n </div>\n </li>\n @if (node.hasChildren) {\n <!-- No indentation here: it would shift the checkbox too. Depth is applied\n inside the row, after the checkbox, so every checkbox stays flush left. -->\n <!-- The <ul> sits INSIDE the deferred content, not around it: `ngTreeItemGroup` only\n instantiates its view when the node opens, so a <ul> declared outside stayed in the DOM,\n empty, for every collapsed node — a structural tag with nothing to structure (RGAA 8.9).\n The @if cannot move onto the <ul> either: the directive registers itself with its tree\n item on init and has to exist for as long as the item does. -->\n <ng-template\n ngTreeItemGroup\n [ownedBy]=\"treeItem\"\n #group=\"ngTreeItemGroup\">\n <ul role=\"group\">\n <ng-template\n [ngTemplateOutlet]=\"treeNodes\"\n [ngTemplateOutletContext]=\"{ nodes: node.items, parent: group }\" />\n </ul>\n </ng-template>\n }\n }\n</ng-template>\n"
|
|
26349
|
+
template: "@if (!aggregation()?.isTree) {\n <div role=\"alert\" class=\"p-2 text-sm text-red-500\">\n <triangle-alert-icon class=\"mr-1 shrink-0\" />\n The aggregationTree component does not support list aggregations. Please use\n the <Aggregation /> component instead.\n </div>\n}\n<aggregation-panel\n [id]=\"id()\"\n [collapsible]=\"collapsible()\"\n [collapsed]=\"collapsed()\"\n [isEmpty]=\"isEmpty()\"\n [isDate]=\"isDate()\"\n [aggregation]=\"aggregation()\"\n [showFiltersCount]=\"showFiltersCount()\"\n [filtersCount]=\"filtersCount()\"\n [hasFilters]=\"hasFilters()\"\n [selection]=\"selection()\"\n [isAllSelected]=\"isAllSelected()\"\n [headless]=\"headless()\"\n [selectedCount]=\"selected().length\"\n [(searchText)]=\"searchText\"\n [itemsLength]=\"items().length\"\n [hasMore]=\"aggregation()?.$hasMore ?? false\"\n [searchedItemsLength]=\"searchedItems().length\"\n (cleared)=\"clear()\"\n (applied)=\"apply()\"\n (allSelected)=\"selectAll()\"\n (allUnselected)=\"unselectAll()\"\n (loadedMore)=\"loadMore()\">\n <!-- Relay the host's `label` projection into the panel's `label` slot. When the\n host projects nothing, the local <ng-content> falls back to the icon + display\n (mirrors AggregationPanel's own fallback), so the header is never empty. -->\n <ng-container ngProjectAs=\"label\">\n <ng-content select=\"label\">\n @let labelIcon = aggregation()?.icon;\n @if (labelIcon) {\n <FaIcon [faClass]=\"labelIcon\" class=\"mr-1 shrink-0\" />\n }\n <span class=\"grow truncate\">{{\n aggregation()?.display | syslang | transloco\n }}</span>\n </ng-content>\n </ng-container>\n <div class=\"min-h-0 w-full flex-1 scrollbar-thin overflow-auto pb-3\">\n <ul\n ngTree\n #tree=\"ngTree\"\n [multi]=\"true\"\n [(value)]=\"selected\"\n (valueChange)=\"onSelectionChange($event)\"\n /* Translated like the flat list's own label (aggregation-list.html): this was the raw\n `display`, so the accessible name of a whole facet came out in the source language.\n The pipes apply to the whole nullish chain, `|` being the loosest operator Angular\n parses — no parentheses needed. */\n [attr.aria-label]=\"aggregation()?.display ?? name() | syslang | transloco\"\n class=\"basic-tree m-1\">\n <ng-template\n [ngTemplateOutlet]=\"treeNodes\"\n [ngTemplateOutletContext]=\"{ nodes: nodes(), parent: tree }\" />\n </ul>\n </div>\n</aggregation-panel>\n\n<ng-template #treeNodes let-nodes=\"nodes\" let-parent=\"parent\">\n @for (node of nodes; track node.$path) {\n @let label = node.display ?? node.value ?? \"\";\n <li\n ngTreeItem\n #treeItem=\"ngTreeItem\"\n [parent]=\"parent\"\n [value]=\"node.$path\"\n [label]=\"label\"\n [disabled]=\"node.disabled\"\n [(expanded)]=\"node.$opened\"\n (expandedChange)=\"onExpandedChange(node, $event)\"\n [attr.aria-label]=\"label\"\n [attr.aria-describedby]=\"\n nodeDescription(node) ? descriptionId(node.$path) : null\n \"\n [class]=\"cn('m-0 block w-full p-0', itemStateClass)\"\n (keydown.enter)=\"onTreeItemEnterKey(node, $event)\"\n (keydown.space)=\"onTreeItemSpaceKey(node, $event)\">\n <!-- 2. Ce div prend tout l'espace et intercepte tous les clics de la ligne. `ps-0.5`: the checkbox of a list option sits\n 2px further in than a tree's (the option has its own padding), so the checkboxes of the two facets, and the select-all of\n the bar of a popover, are on the same vertical line. -->\n <div\n class=\"flex w-full cursor-pointer items-center gap-2 py-0.5 ps-0.5 text-sm leading-6\"\n (click)=\"onTreeItemRowClick(node, $event)\"\n (mousedown)=\"$event.stopPropagation()\"\n (pointerdown)=\"$event.stopPropagation()\">\n @if (showCheckbox()) {\n <!-- `inert` keeps the native input out of the tab order and out of hit-testing:\n a role=\"treeitem\" admits no focusable descendant, and the click must reach\n the row handler above. Selection is announced by the <li>'s aria-selected. -->\n <!-- --spacing-xs: 0 kills the gap the checkbox keeps between its box and its\n (here empty) text block. Set as a custom property rather than a utility\n class: the CVA already sets gap-(--spacing-xs), and two same-specificity\n classes would be resolved by stylesheet order, not by us. -->\n <span\n inert\n aria-hidden=\"true\"\n class=\"inline-flex shrink-0\"\n style=\"--spacing-xs: 0\">\n <checkbox\n [checked]=\"treeItem.selected() || node.$selectedVisually || false\"\n [disabled]=\"node.disabled\"\n [class]=\"\n cn(\n 'pointer-events-none',\n (treeItem.selected() || node.$selectedVisually) &&\n 'selected-icon'\n )\n \" />\n </span>\n }\n <!-- Depth indent sits on the chevron, after the checkbox: the checkboxes of every\n level stay flush left (aligned with a flat list's), and since this adds no extra\n flex child, the step between consecutive levels is exactly one indent. -->\n @let depthIndent =\n \"calc(var(--agg-tree-indent, 0.5rem) * \" +\n (treeItem.level() - 1) +\n \")\";\n @if (node.hasChildren) {\n <!-- Expand/collapse affordance for the mouse only, hence a <span> and not a <button>:\n the keyboard already expands with ArrowRight / ArrowLeft, handled by ngTreeItem, and\n a role=\"treeitem\" admits no focusable descendant — so this was a <button> nobody\n could reach, already carrying aria-hidden and tabindex=\"-1\" to say so. Every audit\n reads it as a button with no accessible name (RGAA 7.1, 11.9). Same shape as the leaf\n branch below. -->\n <span\n aria-hidden=\"true\"\n (click)=\"toggleTreeNode(treeItem, $event)\"\n [style.marginInlineStart]=\"depthIndent\"\n class=\"inline-flex cursor-pointer items-center justify-center\">\n <ChevronRightIcon\n [class]=\"\n cn('expand-icon shrink-0', node.$opened && 'rotate-90')\n \" />\n <FolderIcon class=\"shrink-0\" />\n </span>\n } @else {\n <span\n aria-hidden=\"true\"\n [style.marginInlineStart]=\"depthIndent\"\n class=\"inline-flex items-center justify-center\">\n <!-- Reserves the chevron's own width (`.expand-icon` is a styling hook with\n no rule of its own), so a leaf lines up with its parent nodes. -->\n <span class=\"expand-icon w-4 shrink-0\"></span>\n <FileIcon class=\"size-4 shrink-0\" />\n </span>\n }\n @if (resolvedItemTemplate(); as tpl) {\n <!-- Cut off, the label shows whole in a tooltip; a label that fits needs none. -->\n <span\n class=\"grow truncate select-none\"\n truncated\n #cutCustom=\"truncated\"\n [tooltip]=\"label\"\n [tooltipDisabled]=\"!cutCustom.isTruncated()\"\n (click)=\"onTreeItemTextClick(node, $event)\">\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{\n $implicit: node,\n field: aggregation()?.column,\n selected: treeItem.selected() ?? false,\n count: node.count,\n name: label,\n searchText: searchText(),\n level: node.$level ?? 0,\n expanded: node.$opened ?? false,\n hasChildren: node.hasChildren ?? false\n }\" />\n </span>\n } @else {\n <!-- ce span est essentiel pour le style -->\n @let appliesOnLabelClick = quickFilter() || applyOnSelect();\n <!-- Cut off, the label shows whole in a tooltip (`truncated` measures it); a label that fits has none. -->\n <span\n [class]=\"\n cn(\n 'grow truncate select-none',\n appliesOnLabelClick && 'hover:underline'\n )\n \"\n truncated\n #cut=\"truncated\"\n [tooltip]=\"label\"\n [tooltipDisabled]=\"!cut.isTruncated()\"\n (click)=\"onTreeItemTextClick(node, $event)\">\n @for (\n chunk of label | highlightWord: searchText() : 10;\n track $index\n ) {\n <span [class]=\"{ 'font-bold': chunk.match }\" aria-hidden=\"true\">{{\n chunk.text\n }}</span>\n }\n </span>\n @if (showCount() && node.count > 0) {\n <span class=\"ml-auto px-1 text-xs\" aria-hidden=\"true\">{{\n node.count\n }}</span>\n }\n }\n @let description = nodeDescription(node);\n @if (description) {\n <!-- `hidden`, not `sr-only`: read through aria-describedby only. A visually-hidden\n span would also be counted in the treeitem's name (computed from its content). -->\n <span hidden [id]=\"descriptionId(node.$path)\">{{ description }}</span>\n }\n </div>\n </li>\n @if (node.hasChildren) {\n <!-- No indentation here: it would shift the checkbox too. Depth is applied\n inside the row, after the checkbox, so every checkbox stays flush left. -->\n <!-- The <ul> sits INSIDE the deferred content, not around it: `ngTreeItemGroup` only\n instantiates its view when the node opens, so a <ul> declared outside stayed in the DOM,\n empty, for every collapsed node — a structural tag with nothing to structure (RGAA 8.9).\n The @if cannot move onto the <ul> either: the directive registers itself with its tree\n item on init and has to exist for as long as the item does. -->\n <ng-template\n ngTreeItemGroup\n [ownedBy]=\"treeItem\"\n #group=\"ngTreeItemGroup\">\n <ul role=\"group\">\n <ng-template\n [ngTemplateOutlet]=\"treeNodes\"\n [ngTemplateOutletContext]=\"{ nodes: node.items, parent: group }\" />\n </ul>\n </ng-template>\n }\n }\n</ng-template>\n"
|
|
26219
26350
|
}]
|
|
26220
26351
|
}],
|
|
26221
26352
|
ctorParameters: () => [],
|
|
@@ -26923,7 +27054,7 @@ var AggregationListComponent = class AggregationListComponent {
|
|
|
26923
27054
|
}
|
|
26924
27055
|
],
|
|
26925
27056
|
ngImport: i0,
|
|
26926
|
-
template: "@if (aggregation()?.isTree) {\n <div class=\"p-2 text-sm text-red-500\">\n <triangle-alert-icon class=\"mr-1 shrink-0\" />\n The aggregation component no longer supports tree aggregations. Please use\n the <AggregationTree /> component instead.\n </div>\n}\n<aggregation-panel\n [id]=\"id()\"\n [collapsible]=\"collapsible()\"\n [collapsed]=\"collapsed()\"\n [isEmpty]=\"isEmpty()\"\n [isDate]=\"isDate()\"\n [aggregation]=\"aggregation()\"\n [showFiltersCount]=\"showFiltersCount()\"\n [filtersCount]=\"filtersCount()\"\n [hasFilters]=\"hasFilters()\"\n [selection]=\"selection()\"\n [isAllSelected]=\"isAllSelected()\"\n [headless]=\"headless()\"\n [selectedCount]=\"selectedValues().length\"\n [(searchText)]=\"searchText\"\n [itemsLength]=\"items().length\"\n [hasMore]=\"aggregation()?.$hasMore ?? false\"\n [searchedItemsLength]=\"searchedItems().length\"\n (cleared)=\"clear()\"\n (applied)=\"apply()\"\n (allSelected)=\"selectAll()\"\n (allUnselected)=\"unselectAll()\"\n (loadedMore)=\"loadMore()\">\n <!-- Relay the host's `label` projection into the panel's `label` slot. When the\n host projects nothing, the local <ng-content> falls back to the icon + display\n (mirrors AggregationPanel's own fallback), so the header is never empty. -->\n <ng-container ngProjectAs=\"label\">\n <ng-content select=\"label\">\n @let labelIcon = aggregation()?.icon;\n @if (labelIcon) {\n <FaIcon [faClass]=\"labelIcon\" class=\"mr-1 shrink-0\" />\n }\n <span class=\"grow truncate\">{{\n aggregation()?.display | syslang | transloco\n }}</span>\n </ng-content>\n </ng-container>\n <div\n #scrollElement\n class=\"min-h-0 w-full flex-1 scrollbar-thin overflow-auto px-1 pt-1 pb-3\">\n <div\n ngListbox\n multi\n selectionMode=\"explicit\"\n [value]=\"selectedValues()\"\n (valueChange)=\"syncListboxValue($event)\"\n [wrap]=\"false\"\n [softDisabled]=\"false\"\n class=\"relative w-full\"\n [style.height]=\"virtualizer.getTotalSize() + 'px'\"\n role=\"listbox\"\n aria-multiselectable=\"true\"\n [attr.aria-label]=\"aggregation()?.display | syslang | transloco\">\n @for (vItem of virtualizer.getVirtualItems(); track vItem.index) {\n @let item = items()[vItem.index];\n @let description = rowDescription(item);\n <div\n #row\n ngOption\n [attr.aria-describedby]=\"\n description ? descriptionId(vItem.index) : null\n \"\n [attr.data-index]=\"vItem.index\"\n [value]=\"item.value\"\n [disabled]=\"item.count === 0\"\n [class]=\"cn('absolute w-full', menuItemBaseClass)\"\n (keydown.enter)=\"onOptionEnterKey(item, $event)\"\n (keydown.space)=\"\n onListItemRowClick(item, $event); $event.preventDefault()\n \"\n [style.transform]=\"'translateY(' + vItem.start + 'px)'\"\n role=\"option\">\n <ng-container\n [ngTemplateOutlet]=\"listItemTpl\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n field: aggregation()?.column\n }\">\n </ng-container>\n @if (description) {\n <!-- `hidden`, not `sr-only`: read through aria-describedby only. A visually-hidden\n span would also be counted in the option's name (computed from its content). -->\n <span hidden [id]=\"descriptionId(vItem.index)\">{{\n description\n }}</span>\n }\n </div>\n }\n </div>\n </div>\n</aggregation-panel>\n\n<ng-template #listItemTpl let-item let-field=\"field\">\n <a\n [attr.aria-label]=\"listItemName(item) | syslang\"\n [class]=\"\n cn(\n '-m-1.5 flex min-w-0 grow items-center gap-2 leading-6',\n item.count === 0 && 'pointer-events-none text-(--font-neutral-muted)'\n )\n \"\n (click)=\"onListItemRowClick(item, $event)\">\n @if (showCheckbox()) {\n <!-- `inert` keeps the native input out of the tab order and out of hit-testing:\n keyboard navigation belongs to ngListbox/ngOption (roving tabindex), the\n selection is already exposed by role=\"option\" + aria-selected, and the click\n must reach the <a> above. Same treatment as the tree.\n --spacing-xs: 0 kills the gap the checkbox keeps between its box and its\n (here empty) text block. -->\n <span\n inert\n aria-hidden=\"true\"\n class=\"inline-flex shrink-0\"\n style=\"--spacing-xs: 0\">\n <checkbox\n [checked]=\"item.$selected ?? false\"\n [disabled]=\"item.count === 0\"\n [class]=\"'pointer-events-none'\" />\n </span>\n }\n @if (resolvedItemTemplate(); as tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n field: field,\n selected: item.$selected ?? false,\n count: item.count,\n name: (listItemName(item) | syslang) ?? '',\n searchText: searchText()\n }\" />\n } @else {\n @let icon = $any(item).icon;\n @if (icon) {\n <FaIcon\n [faClass]=\"icon\"\n class=\"shrink-0 self-center justify-self-center\" />\n }\n @let appliesOnLabelClick = quickFilter() || applyOnSelect();\n <span\n [class]=\"\n cn(\n 'line-clamp-1 cursor-pointer break-all text-ellipsis select-none',\n appliesOnLabelClick && 'hover:underline'\n )\n \"\n
|
|
27057
|
+
template: "@if (aggregation()?.isTree) {\n <div class=\"p-2 text-sm text-red-500\">\n <triangle-alert-icon class=\"mr-1 shrink-0\" />\n The aggregation component no longer supports tree aggregations. Please use\n the <AggregationTree /> component instead.\n </div>\n}\n<aggregation-panel\n [id]=\"id()\"\n [collapsible]=\"collapsible()\"\n [collapsed]=\"collapsed()\"\n [isEmpty]=\"isEmpty()\"\n [isDate]=\"isDate()\"\n [aggregation]=\"aggregation()\"\n [showFiltersCount]=\"showFiltersCount()\"\n [filtersCount]=\"filtersCount()\"\n [hasFilters]=\"hasFilters()\"\n [selection]=\"selection()\"\n [isAllSelected]=\"isAllSelected()\"\n [headless]=\"headless()\"\n [selectedCount]=\"selectedValues().length\"\n [(searchText)]=\"searchText\"\n [itemsLength]=\"items().length\"\n [hasMore]=\"aggregation()?.$hasMore ?? false\"\n [searchedItemsLength]=\"searchedItems().length\"\n (cleared)=\"clear()\"\n (applied)=\"apply()\"\n (allSelected)=\"selectAll()\"\n (allUnselected)=\"unselectAll()\"\n (loadedMore)=\"loadMore()\">\n <!-- Relay the host's `label` projection into the panel's `label` slot. When the\n host projects nothing, the local <ng-content> falls back to the icon + display\n (mirrors AggregationPanel's own fallback), so the header is never empty. -->\n <ng-container ngProjectAs=\"label\">\n <ng-content select=\"label\">\n @let labelIcon = aggregation()?.icon;\n @if (labelIcon) {\n <FaIcon [faClass]=\"labelIcon\" class=\"mr-1 shrink-0\" />\n }\n <span class=\"grow truncate\">{{\n aggregation()?.display | syslang | transloco\n }}</span>\n </ng-content>\n </ng-container>\n <div\n #scrollElement\n class=\"min-h-0 w-full flex-1 scrollbar-thin overflow-auto px-1 pt-1 pb-3\">\n <div\n ngListbox\n multi\n selectionMode=\"explicit\"\n [value]=\"selectedValues()\"\n (valueChange)=\"syncListboxValue($event)\"\n [wrap]=\"false\"\n [softDisabled]=\"false\"\n class=\"relative w-full\"\n [style.height]=\"virtualizer.getTotalSize() + 'px'\"\n role=\"listbox\"\n aria-multiselectable=\"true\"\n [attr.aria-label]=\"aggregation()?.display | syslang | transloco\">\n @for (vItem of virtualizer.getVirtualItems(); track vItem.index) {\n @let item = items()[vItem.index];\n @let description = rowDescription(item);\n <div\n #row\n ngOption\n [attr.aria-describedby]=\"\n description ? descriptionId(vItem.index) : null\n \"\n [attr.data-index]=\"vItem.index\"\n [value]=\"item.value\"\n [disabled]=\"item.count === 0\"\n [class]=\"cn('absolute w-full', menuItemBaseClass)\"\n (keydown.enter)=\"onOptionEnterKey(item, $event)\"\n (keydown.space)=\"\n onListItemRowClick(item, $event); $event.preventDefault()\n \"\n [style.transform]=\"'translateY(' + vItem.start + 'px)'\"\n role=\"option\">\n <ng-container\n [ngTemplateOutlet]=\"listItemTpl\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n field: aggregation()?.column\n }\">\n </ng-container>\n @if (description) {\n <!-- `hidden`, not `sr-only`: read through aria-describedby only. A visually-hidden\n span would also be counted in the option's name (computed from its content). -->\n <span hidden [id]=\"descriptionId(vItem.index)\">{{\n description\n }}</span>\n }\n </div>\n }\n </div>\n </div>\n</aggregation-panel>\n\n<ng-template #listItemTpl let-item let-field=\"field\">\n <a\n [attr.aria-label]=\"listItemName(item) | syslang\"\n [class]=\"\n cn(\n '-m-1.5 flex min-w-0 grow items-center gap-2 leading-6',\n item.count === 0 && 'pointer-events-none text-(--font-neutral-muted)'\n )\n \"\n (click)=\"onListItemRowClick(item, $event)\">\n @if (showCheckbox()) {\n <!-- `inert` keeps the native input out of the tab order and out of hit-testing:\n keyboard navigation belongs to ngListbox/ngOption (roving tabindex), the\n selection is already exposed by role=\"option\" + aria-selected, and the click\n must reach the <a> above. Same treatment as the tree.\n --spacing-xs: 0 kills the gap the checkbox keeps between its box and its\n (here empty) text block. -->\n <span\n inert\n aria-hidden=\"true\"\n class=\"inline-flex shrink-0\"\n style=\"--spacing-xs: 0\">\n <checkbox\n [checked]=\"item.$selected ?? false\"\n [disabled]=\"item.count === 0\"\n [class]=\"'pointer-events-none'\" />\n </span>\n }\n @if (resolvedItemTemplate(); as tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n field: field,\n selected: item.$selected ?? false,\n count: item.count,\n name: (listItemName(item) | syslang) ?? '',\n searchText: searchText()\n }\" />\n } @else {\n @let icon = $any(item).icon;\n @if (icon) {\n <FaIcon\n [faClass]=\"icon\"\n class=\"shrink-0 self-center justify-self-center\" />\n }\n @let appliesOnLabelClick = quickFilter() || applyOnSelect();\n <!-- Cut off, the label shows whole in a tooltip (`truncated` measures it); a label that fits has none. -->\n <span\n [class]=\"\n cn(\n 'line-clamp-1 cursor-pointer break-all text-ellipsis select-none',\n appliesOnLabelClick && 'hover:underline'\n )\n \"\n truncated\n #cut=\"truncated\"\n [tooltip]=\"listItemName(item) | syslang\"\n [tooltipDisabled]=\"!cut.isTruncated()\"\n (click)=\"onListItemTextClick(item, $event)\">\n @for (\n chunk of (listItemName(item) | syslang) ?? \"\"\n | highlightWord: searchText() : 10;\n track $index\n ) {\n <span [class]=\"{ 'font-bold': chunk.match }\" aria-hidden=\"true\">{{\n chunk.text\n }}</span>\n }\n </span>\n @if (showCount() && item.count > 0) {\n <span class=\"ml-auto px-1 text-xs empty:hidden\" aria-hidden=\"true\">{{\n item.count\n }}</span>\n }\n }\n </a>\n</ng-template>\n",
|
|
26927
27058
|
dependencies: [
|
|
26928
27059
|
{
|
|
26929
27060
|
kind: "directive",
|
|
@@ -26974,12 +27105,45 @@ var AggregationListComponent = class AggregationListComponent {
|
|
|
26974
27105
|
selector: "fa-icon, FaIcon",
|
|
26975
27106
|
inputs: ["faClass", "class"]
|
|
26976
27107
|
},
|
|
27108
|
+
{
|
|
27109
|
+
kind: "directive",
|
|
27110
|
+
type: TooltipDirective,
|
|
27111
|
+
selector: "[tooltip]",
|
|
27112
|
+
inputs: [
|
|
27113
|
+
"tooltip",
|
|
27114
|
+
"tooltipPosition",
|
|
27115
|
+
"tooltipEvent",
|
|
27116
|
+
"showDelay",
|
|
27117
|
+
"hideDelay",
|
|
27118
|
+
"life",
|
|
27119
|
+
"tooltipOffset",
|
|
27120
|
+
"autoHide",
|
|
27121
|
+
"hideOnEscape",
|
|
27122
|
+
"escape",
|
|
27123
|
+
"tooltipDisabled",
|
|
27124
|
+
"tooltipArrow",
|
|
27125
|
+
"tooltipStyleClass",
|
|
27126
|
+
"tooltipOptions",
|
|
27127
|
+
"tooltip-position",
|
|
27128
|
+
"tooltip-delay",
|
|
27129
|
+
"tooltip-duration",
|
|
27130
|
+
"tooltip-offset"
|
|
27131
|
+
],
|
|
27132
|
+
exportAs: ["tooltip"]
|
|
27133
|
+
},
|
|
26977
27134
|
{
|
|
26978
27135
|
kind: "component",
|
|
26979
27136
|
type: TriangleAlertIcon,
|
|
26980
27137
|
selector: "triangle-alert, triangle-alert-icon, TriangleAlert, TriangleAlertIcon",
|
|
26981
27138
|
inputs: ["class"]
|
|
26982
27139
|
},
|
|
27140
|
+
{
|
|
27141
|
+
kind: "directive",
|
|
27142
|
+
type: TruncatedDirective,
|
|
27143
|
+
selector: "[truncated]",
|
|
27144
|
+
inputs: ["truncated"],
|
|
27145
|
+
exportAs: ["truncated"]
|
|
27146
|
+
},
|
|
26983
27147
|
{
|
|
26984
27148
|
kind: "component",
|
|
26985
27149
|
type: AggregationPanelComponent,
|
|
@@ -27060,7 +27224,9 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
27060
27224
|
SyslangPipe,
|
|
27061
27225
|
TranslocoPipe,
|
|
27062
27226
|
FaIconComponent,
|
|
27227
|
+
TooltipDirective,
|
|
27063
27228
|
TriangleAlertIcon,
|
|
27229
|
+
TruncatedDirective,
|
|
27064
27230
|
HighlightWordPipe,
|
|
27065
27231
|
AggregationPanelComponent,
|
|
27066
27232
|
CheckboxComponent
|
|
@@ -27069,7 +27235,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
27069
27235
|
"[class]": "cn(\"flex h-[inherit] max-h-[inherit] flex-col\", class(), (isMissing() || (hideWhenEmpty() && isEmpty())) && \"hidden\")",
|
|
27070
27236
|
"(keydown.enter)": "onHostEnterKey($event)"
|
|
27071
27237
|
},
|
|
27072
|
-
template: "@if (aggregation()?.isTree) {\n <div class=\"p-2 text-sm text-red-500\">\n <triangle-alert-icon class=\"mr-1 shrink-0\" />\n The aggregation component no longer supports tree aggregations. Please use\n the <AggregationTree /> component instead.\n </div>\n}\n<aggregation-panel\n [id]=\"id()\"\n [collapsible]=\"collapsible()\"\n [collapsed]=\"collapsed()\"\n [isEmpty]=\"isEmpty()\"\n [isDate]=\"isDate()\"\n [aggregation]=\"aggregation()\"\n [showFiltersCount]=\"showFiltersCount()\"\n [filtersCount]=\"filtersCount()\"\n [hasFilters]=\"hasFilters()\"\n [selection]=\"selection()\"\n [isAllSelected]=\"isAllSelected()\"\n [headless]=\"headless()\"\n [selectedCount]=\"selectedValues().length\"\n [(searchText)]=\"searchText\"\n [itemsLength]=\"items().length\"\n [hasMore]=\"aggregation()?.$hasMore ?? false\"\n [searchedItemsLength]=\"searchedItems().length\"\n (cleared)=\"clear()\"\n (applied)=\"apply()\"\n (allSelected)=\"selectAll()\"\n (allUnselected)=\"unselectAll()\"\n (loadedMore)=\"loadMore()\">\n <!-- Relay the host's `label` projection into the panel's `label` slot. When the\n host projects nothing, the local <ng-content> falls back to the icon + display\n (mirrors AggregationPanel's own fallback), so the header is never empty. -->\n <ng-container ngProjectAs=\"label\">\n <ng-content select=\"label\">\n @let labelIcon = aggregation()?.icon;\n @if (labelIcon) {\n <FaIcon [faClass]=\"labelIcon\" class=\"mr-1 shrink-0\" />\n }\n <span class=\"grow truncate\">{{\n aggregation()?.display | syslang | transloco\n }}</span>\n </ng-content>\n </ng-container>\n <div\n #scrollElement\n class=\"min-h-0 w-full flex-1 scrollbar-thin overflow-auto px-1 pt-1 pb-3\">\n <div\n ngListbox\n multi\n selectionMode=\"explicit\"\n [value]=\"selectedValues()\"\n (valueChange)=\"syncListboxValue($event)\"\n [wrap]=\"false\"\n [softDisabled]=\"false\"\n class=\"relative w-full\"\n [style.height]=\"virtualizer.getTotalSize() + 'px'\"\n role=\"listbox\"\n aria-multiselectable=\"true\"\n [attr.aria-label]=\"aggregation()?.display | syslang | transloco\">\n @for (vItem of virtualizer.getVirtualItems(); track vItem.index) {\n @let item = items()[vItem.index];\n @let description = rowDescription(item);\n <div\n #row\n ngOption\n [attr.aria-describedby]=\"\n description ? descriptionId(vItem.index) : null\n \"\n [attr.data-index]=\"vItem.index\"\n [value]=\"item.value\"\n [disabled]=\"item.count === 0\"\n [class]=\"cn('absolute w-full', menuItemBaseClass)\"\n (keydown.enter)=\"onOptionEnterKey(item, $event)\"\n (keydown.space)=\"\n onListItemRowClick(item, $event); $event.preventDefault()\n \"\n [style.transform]=\"'translateY(' + vItem.start + 'px)'\"\n role=\"option\">\n <ng-container\n [ngTemplateOutlet]=\"listItemTpl\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n field: aggregation()?.column\n }\">\n </ng-container>\n @if (description) {\n <!-- `hidden`, not `sr-only`: read through aria-describedby only. A visually-hidden\n span would also be counted in the option's name (computed from its content). -->\n <span hidden [id]=\"descriptionId(vItem.index)\">{{\n description\n }}</span>\n }\n </div>\n }\n </div>\n </div>\n</aggregation-panel>\n\n<ng-template #listItemTpl let-item let-field=\"field\">\n <a\n [attr.aria-label]=\"listItemName(item) | syslang\"\n [class]=\"\n cn(\n '-m-1.5 flex min-w-0 grow items-center gap-2 leading-6',\n item.count === 0 && 'pointer-events-none text-(--font-neutral-muted)'\n )\n \"\n (click)=\"onListItemRowClick(item, $event)\">\n @if (showCheckbox()) {\n <!-- `inert` keeps the native input out of the tab order and out of hit-testing:\n keyboard navigation belongs to ngListbox/ngOption (roving tabindex), the\n selection is already exposed by role=\"option\" + aria-selected, and the click\n must reach the <a> above. Same treatment as the tree.\n --spacing-xs: 0 kills the gap the checkbox keeps between its box and its\n (here empty) text block. -->\n <span\n inert\n aria-hidden=\"true\"\n class=\"inline-flex shrink-0\"\n style=\"--spacing-xs: 0\">\n <checkbox\n [checked]=\"item.$selected ?? false\"\n [disabled]=\"item.count === 0\"\n [class]=\"'pointer-events-none'\" />\n </span>\n }\n @if (resolvedItemTemplate(); as tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n field: field,\n selected: item.$selected ?? false,\n count: item.count,\n name: (listItemName(item) | syslang) ?? '',\n searchText: searchText()\n }\" />\n } @else {\n @let icon = $any(item).icon;\n @if (icon) {\n <FaIcon\n [faClass]=\"icon\"\n class=\"shrink-0 self-center justify-self-center\" />\n }\n @let appliesOnLabelClick = quickFilter() || applyOnSelect();\n <span\n [class]=\"\n cn(\n 'line-clamp-1 cursor-pointer break-all text-ellipsis select-none',\n appliesOnLabelClick && 'hover:underline'\n )\n \"\n
|
|
27238
|
+
template: "@if (aggregation()?.isTree) {\n <div class=\"p-2 text-sm text-red-500\">\n <triangle-alert-icon class=\"mr-1 shrink-0\" />\n The aggregation component no longer supports tree aggregations. Please use\n the <AggregationTree /> component instead.\n </div>\n}\n<aggregation-panel\n [id]=\"id()\"\n [collapsible]=\"collapsible()\"\n [collapsed]=\"collapsed()\"\n [isEmpty]=\"isEmpty()\"\n [isDate]=\"isDate()\"\n [aggregation]=\"aggregation()\"\n [showFiltersCount]=\"showFiltersCount()\"\n [filtersCount]=\"filtersCount()\"\n [hasFilters]=\"hasFilters()\"\n [selection]=\"selection()\"\n [isAllSelected]=\"isAllSelected()\"\n [headless]=\"headless()\"\n [selectedCount]=\"selectedValues().length\"\n [(searchText)]=\"searchText\"\n [itemsLength]=\"items().length\"\n [hasMore]=\"aggregation()?.$hasMore ?? false\"\n [searchedItemsLength]=\"searchedItems().length\"\n (cleared)=\"clear()\"\n (applied)=\"apply()\"\n (allSelected)=\"selectAll()\"\n (allUnselected)=\"unselectAll()\"\n (loadedMore)=\"loadMore()\">\n <!-- Relay the host's `label` projection into the panel's `label` slot. When the\n host projects nothing, the local <ng-content> falls back to the icon + display\n (mirrors AggregationPanel's own fallback), so the header is never empty. -->\n <ng-container ngProjectAs=\"label\">\n <ng-content select=\"label\">\n @let labelIcon = aggregation()?.icon;\n @if (labelIcon) {\n <FaIcon [faClass]=\"labelIcon\" class=\"mr-1 shrink-0\" />\n }\n <span class=\"grow truncate\">{{\n aggregation()?.display | syslang | transloco\n }}</span>\n </ng-content>\n </ng-container>\n <div\n #scrollElement\n class=\"min-h-0 w-full flex-1 scrollbar-thin overflow-auto px-1 pt-1 pb-3\">\n <div\n ngListbox\n multi\n selectionMode=\"explicit\"\n [value]=\"selectedValues()\"\n (valueChange)=\"syncListboxValue($event)\"\n [wrap]=\"false\"\n [softDisabled]=\"false\"\n class=\"relative w-full\"\n [style.height]=\"virtualizer.getTotalSize() + 'px'\"\n role=\"listbox\"\n aria-multiselectable=\"true\"\n [attr.aria-label]=\"aggregation()?.display | syslang | transloco\">\n @for (vItem of virtualizer.getVirtualItems(); track vItem.index) {\n @let item = items()[vItem.index];\n @let description = rowDescription(item);\n <div\n #row\n ngOption\n [attr.aria-describedby]=\"\n description ? descriptionId(vItem.index) : null\n \"\n [attr.data-index]=\"vItem.index\"\n [value]=\"item.value\"\n [disabled]=\"item.count === 0\"\n [class]=\"cn('absolute w-full', menuItemBaseClass)\"\n (keydown.enter)=\"onOptionEnterKey(item, $event)\"\n (keydown.space)=\"\n onListItemRowClick(item, $event); $event.preventDefault()\n \"\n [style.transform]=\"'translateY(' + vItem.start + 'px)'\"\n role=\"option\">\n <ng-container\n [ngTemplateOutlet]=\"listItemTpl\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n field: aggregation()?.column\n }\">\n </ng-container>\n @if (description) {\n <!-- `hidden`, not `sr-only`: read through aria-describedby only. A visually-hidden\n span would also be counted in the option's name (computed from its content). -->\n <span hidden [id]=\"descriptionId(vItem.index)\">{{\n description\n }}</span>\n }\n </div>\n }\n </div>\n </div>\n</aggregation-panel>\n\n<ng-template #listItemTpl let-item let-field=\"field\">\n <a\n [attr.aria-label]=\"listItemName(item) | syslang\"\n [class]=\"\n cn(\n '-m-1.5 flex min-w-0 grow items-center gap-2 leading-6',\n item.count === 0 && 'pointer-events-none text-(--font-neutral-muted)'\n )\n \"\n (click)=\"onListItemRowClick(item, $event)\">\n @if (showCheckbox()) {\n <!-- `inert` keeps the native input out of the tab order and out of hit-testing:\n keyboard navigation belongs to ngListbox/ngOption (roving tabindex), the\n selection is already exposed by role=\"option\" + aria-selected, and the click\n must reach the <a> above. Same treatment as the tree.\n --spacing-xs: 0 kills the gap the checkbox keeps between its box and its\n (here empty) text block. -->\n <span\n inert\n aria-hidden=\"true\"\n class=\"inline-flex shrink-0\"\n style=\"--spacing-xs: 0\">\n <checkbox\n [checked]=\"item.$selected ?? false\"\n [disabled]=\"item.count === 0\"\n [class]=\"'pointer-events-none'\" />\n </span>\n }\n @if (resolvedItemTemplate(); as tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{\n $implicit: item,\n field: field,\n selected: item.$selected ?? false,\n count: item.count,\n name: (listItemName(item) | syslang) ?? '',\n searchText: searchText()\n }\" />\n } @else {\n @let icon = $any(item).icon;\n @if (icon) {\n <FaIcon\n [faClass]=\"icon\"\n class=\"shrink-0 self-center justify-self-center\" />\n }\n @let appliesOnLabelClick = quickFilter() || applyOnSelect();\n <!-- Cut off, the label shows whole in a tooltip (`truncated` measures it); a label that fits has none. -->\n <span\n [class]=\"\n cn(\n 'line-clamp-1 cursor-pointer break-all text-ellipsis select-none',\n appliesOnLabelClick && 'hover:underline'\n )\n \"\n truncated\n #cut=\"truncated\"\n [tooltip]=\"listItemName(item) | syslang\"\n [tooltipDisabled]=\"!cut.isTruncated()\"\n (click)=\"onListItemTextClick(item, $event)\">\n @for (\n chunk of (listItemName(item) | syslang) ?? \"\"\n | highlightWord: searchText() : 10;\n track $index\n ) {\n <span [class]=\"{ 'font-bold': chunk.match }\" aria-hidden=\"true\">{{\n chunk.text\n }}</span>\n }\n </span>\n @if (showCount() && item.count > 0) {\n <span class=\"ml-auto px-1 text-xs empty:hidden\" aria-hidden=\"true\">{{\n item.count\n }}</span>\n }\n }\n </a>\n</ng-template>\n"
|
|
27073
27239
|
}]
|
|
27074
27240
|
}],
|
|
27075
27241
|
ctorParameters: () => [],
|
|
@@ -28095,6 +28261,17 @@ function chipsForLeaf(leaf, source) {
|
|
|
28095
28261
|
}
|
|
28096
28262
|
var AppliedFiltersComponent = class AppliedFiltersComponent {
|
|
28097
28263
|
queryParamsStore = inject(QueryParamsStore);
|
|
28264
|
+
static nextId = 0;
|
|
28265
|
+
listId = `applied-filters-${AppliedFiltersComponent.nextId++}`;
|
|
28266
|
+
list = viewChild("list", ...ngDevMode ? [{ debugName: "list" }] : /* istanbul ignore next */ []);
|
|
28267
|
+
foldable = signal(false, ...ngDevMode ? [{ debugName: "foldable" }] : /* istanbul ignore next */ []);
|
|
28268
|
+
expanded = signal(false, ...ngDevMode ? [{ debugName: "expanded" }] : /* istanbul ignore next */ []);
|
|
28269
|
+
foldedHeight = signal(0, ...ngDevMode ? [{ debugName: "foldedHeight" }] : /* istanbul ignore next */ []);
|
|
28270
|
+
fullHeight = signal(0, ...ngDevMode ? [{ debugName: "fullHeight" }] : /* istanbul ignore next */ []);
|
|
28271
|
+
listHeight = computed(() => this.foldable() ? this.expanded() ? this.fullHeight() : this.foldedHeight() : null, ...ngDevMode ? [{ debugName: "listHeight" }] : /* istanbul ignore next */ []);
|
|
28272
|
+
toggle() {
|
|
28273
|
+
this.expanded.update((open) => !open);
|
|
28274
|
+
}
|
|
28098
28275
|
filters = input(void 0, ...ngDevMode ? [{ debugName: "filters" }] : /* istanbul ignore next */ []);
|
|
28099
28276
|
syncUrl = input(true, {
|
|
28100
28277
|
...ngDevMode ? { debugName: "syncUrl" } : /* istanbul ignore next */ {},
|
|
@@ -28108,10 +28285,62 @@ var AppliedFiltersComponent = class AppliedFiltersComponent {
|
|
|
28108
28285
|
scheme = input("grey", ...ngDevMode ? [{ debugName: "scheme" }] : /* istanbul ignore next */ []);
|
|
28109
28286
|
size = input("sm", ...ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []);
|
|
28110
28287
|
chipClass = input("", ...ngDevMode ? [{ debugName: "chipClass" }] : /* istanbul ignore next */ []);
|
|
28288
|
+
maxLines = input(2, {
|
|
28289
|
+
...ngDevMode ? { debugName: "maxLines" } : /* istanbul ignore next */ {},
|
|
28290
|
+
transform: numberAttribute
|
|
28291
|
+
});
|
|
28292
|
+
showClearAll = input(true, {
|
|
28293
|
+
...ngDevMode ? { debugName: "showClearAll" } : /* istanbul ignore next */ {},
|
|
28294
|
+
transform: booleanAttribute
|
|
28295
|
+
});
|
|
28111
28296
|
removed = output();
|
|
28112
28297
|
cleared = output();
|
|
28113
28298
|
filtersChange = output();
|
|
28114
28299
|
changed = output();
|
|
28300
|
+
constructor() {
|
|
28301
|
+
const destroyRef = inject(DestroyRef);
|
|
28302
|
+
afterRenderEffect(() => {
|
|
28303
|
+
this.chips();
|
|
28304
|
+
this.maxLines();
|
|
28305
|
+
this.expanded();
|
|
28306
|
+
this.measure();
|
|
28307
|
+
});
|
|
28308
|
+
afterRenderEffect((onCleanup) => {
|
|
28309
|
+
const element = this.list()?.nativeElement;
|
|
28310
|
+
if (!element || typeof ResizeObserver === "undefined") return;
|
|
28311
|
+
const observer = new ResizeObserver(() => this.measure());
|
|
28312
|
+
observer.observe(element);
|
|
28313
|
+
onCleanup(() => observer.disconnect());
|
|
28314
|
+
});
|
|
28315
|
+
destroyRef.onDestroy(() => this.foldable.set(false));
|
|
28316
|
+
}
|
|
28317
|
+
measure() {
|
|
28318
|
+
const element = this.list()?.nativeElement;
|
|
28319
|
+
const max = this.maxLines();
|
|
28320
|
+
if (!element || max <= 0) {
|
|
28321
|
+
this.foldable.set(false);
|
|
28322
|
+
return;
|
|
28323
|
+
}
|
|
28324
|
+
const items = Array.from(element.children);
|
|
28325
|
+
const first = items[0];
|
|
28326
|
+
if (!first) {
|
|
28327
|
+
this.foldable.set(false);
|
|
28328
|
+
return;
|
|
28329
|
+
}
|
|
28330
|
+
const origin = first.offsetTop;
|
|
28331
|
+
const tops = Array.from(new Set(items.map((item) => item.offsetTop - origin))).sort((a, b) => a - b);
|
|
28332
|
+
const gap = Number.parseFloat(getComputedStyle(element).rowGap) || 0;
|
|
28333
|
+
if (tops.length <= max) {
|
|
28334
|
+
this.foldable.set(false);
|
|
28335
|
+
items.forEach((item) => item.inert = false);
|
|
28336
|
+
return;
|
|
28337
|
+
}
|
|
28338
|
+
this.foldable.set(true);
|
|
28339
|
+
this.foldedHeight.set(Math.max(0, tops[max] - gap));
|
|
28340
|
+
this.fullHeight.set(element.scrollHeight);
|
|
28341
|
+
const folded = this.foldedHeight();
|
|
28342
|
+
items.forEach((item) => item.inert = !this.expanded() && item.offsetTop - origin >= folded);
|
|
28343
|
+
}
|
|
28115
28344
|
chips = computed(() => {
|
|
28116
28345
|
return (this.filters() ?? this.queryParamsStore.filters()).flatMap((source) => filterLeaves(source).flatMap((leaf) => chipsForLeaf(leaf, source)));
|
|
28117
28346
|
}, ...ngDevMode ? [{ debugName: "chips" }] : /* istanbul ignore next */ []);
|
|
@@ -28207,6 +28436,20 @@ var AppliedFiltersComponent = class AppliedFiltersComponent {
|
|
|
28207
28436
|
isSignal: true,
|
|
28208
28437
|
isRequired: false,
|
|
28209
28438
|
transformFunction: null
|
|
28439
|
+
},
|
|
28440
|
+
maxLines: {
|
|
28441
|
+
classPropertyName: "maxLines",
|
|
28442
|
+
publicName: "maxLines",
|
|
28443
|
+
isSignal: true,
|
|
28444
|
+
isRequired: false,
|
|
28445
|
+
transformFunction: null
|
|
28446
|
+
},
|
|
28447
|
+
showClearAll: {
|
|
28448
|
+
classPropertyName: "showClearAll",
|
|
28449
|
+
publicName: "showClearAll",
|
|
28450
|
+
isSignal: true,
|
|
28451
|
+
isRequired: false,
|
|
28452
|
+
transformFunction: null
|
|
28210
28453
|
}
|
|
28211
28454
|
},
|
|
28212
28455
|
outputs: {
|
|
@@ -28217,39 +28460,75 @@ var AppliedFiltersComponent = class AppliedFiltersComponent {
|
|
|
28217
28460
|
},
|
|
28218
28461
|
host: { properties: { "class.hidden": "chips().length === 0" } },
|
|
28219
28462
|
providers: [provideTranslocoScope("filters")],
|
|
28463
|
+
viewQueries: [{
|
|
28464
|
+
propertyName: "list",
|
|
28465
|
+
first: true,
|
|
28466
|
+
predicate: ["list"],
|
|
28467
|
+
descendants: true,
|
|
28468
|
+
isSignal: true
|
|
28469
|
+
}],
|
|
28220
28470
|
ngImport: i0,
|
|
28221
28471
|
template: `
|
|
28222
28472
|
@if (chips().length > 0) {
|
|
28223
|
-
|
|
28224
|
-
|
|
28225
|
-
|
|
28226
|
-
|
|
28227
|
-
|
|
28228
|
-
|
|
28229
|
-
|
|
28230
|
-
|
|
28231
|
-
|
|
28232
|
-
|
|
28233
|
-
|
|
28234
|
-
|
|
28235
|
-
|
|
28236
|
-
|
|
28237
|
-
|
|
28238
|
-
|
|
28239
|
-
[
|
|
28240
|
-
|
|
28241
|
-
[
|
|
28242
|
-
[
|
|
28243
|
-
(click)="remove(chip)"
|
|
28473
|
+
<!-- The zone the "show more" button belongs to: it appears when the pointer is on it or the focus is in it. -->
|
|
28474
|
+
<div class="group/applied relative" [class.pb-7]="foldable() && expanded()">
|
|
28475
|
+
<!-- Past maxLines lines the list is folded to that height — the rest is behind a fade, and a height that grows and
|
|
28476
|
+
shrinks shows it. The chips that fall in the folded part are inert (see measure): a remove button nobody can see
|
|
28477
|
+
must not stay in the tab order. The clip keeps a margin of a few pixels, so the focus ring of the visible chips is whole. -->
|
|
28478
|
+
<div
|
|
28479
|
+
#list
|
|
28480
|
+
[id]="listId"
|
|
28481
|
+
class="flex flex-wrap content-start items-center gap-1.5 [overflow-clip-margin:0.25rem] overflow-clip transition-[height] duration-200 ease-[cubic-bezier(0.32,0.72,0,1)] motion-reduce:transition-none"
|
|
28482
|
+
[style.height.px]="listHeight()"
|
|
28483
|
+
[style.mask-image]="foldable() && !expanded() ? 'linear-gradient(to bottom, black calc(100% - 1.5rem), transparent)' : null"
|
|
28484
|
+
role="list"
|
|
28485
|
+
>
|
|
28486
|
+
@for (chip of chips(); track chip.id) {
|
|
28487
|
+
<tag
|
|
28488
|
+
role="listitem"
|
|
28489
|
+
[variant]="variant()"
|
|
28490
|
+
[scheme]="scheme()"
|
|
28491
|
+
[size]="size()"
|
|
28492
|
+
[class]="'max-w-full items-center gap-1 ' + chipClass()"
|
|
28244
28493
|
>
|
|
28245
|
-
|
|
28494
|
+
@if (showField()) {
|
|
28495
|
+
<span>{{ (chip.name ?? chip.field) | syslang }}:</span>
|
|
28496
|
+
}
|
|
28497
|
+
<span class="truncate">{{ chip.display | syslang }}</span>
|
|
28498
|
+
<button
|
|
28499
|
+
variant="none"
|
|
28500
|
+
size="xs"
|
|
28501
|
+
[iconOnly]="true"
|
|
28502
|
+
class="shrink-0"
|
|
28503
|
+
[attr.aria-label]="('filters.removeFilter' | transloco) + ': ' + chip.display"
|
|
28504
|
+
[title]="('filters.removeFilter' | transloco) + ': ' + chip.display"
|
|
28505
|
+
(click)="remove(chip)"
|
|
28506
|
+
>
|
|
28507
|
+
<x-mark-icon class="size-3" />
|
|
28508
|
+
</button>
|
|
28509
|
+
</tag>
|
|
28510
|
+
}
|
|
28511
|
+
|
|
28512
|
+
@if (showClearAll() && chips().length > 1) {
|
|
28513
|
+
<button variant="tertiary" size="xs" (click)="clear()">
|
|
28514
|
+
{{ "filters.clearAllFilters" | transloco }}
|
|
28246
28515
|
</button>
|
|
28247
|
-
|
|
28248
|
-
|
|
28516
|
+
}
|
|
28517
|
+
</div>
|
|
28249
28518
|
|
|
28250
|
-
@if (
|
|
28251
|
-
|
|
28252
|
-
|
|
28519
|
+
@if (foldable()) {
|
|
28520
|
+
<!-- Out of sight until the zone is hovered or holds the focus — and always there on a screen with no hover. Still in the
|
|
28521
|
+
tab order, and revealed by the focus. Its own surface, since it lies over the chips. -->
|
|
28522
|
+
<button
|
|
28523
|
+
type="button"
|
|
28524
|
+
variant="secondary"
|
|
28525
|
+
size="xs"
|
|
28526
|
+
class="absolute right-0 bottom-0 bg-(--bg-neutral-white) opacity-0 shadow-(--shadow-1) transition-opacity duration-150 group-hover/applied:opacity-100 group-focus-within/applied:opacity-100 focus-visible:opacity-100 motion-reduce:transition-none [@media(hover:none)]:opacity-100"
|
|
28527
|
+
[attr.aria-expanded]="expanded()"
|
|
28528
|
+
[attr.aria-controls]="listId"
|
|
28529
|
+
(click)="toggle()"
|
|
28530
|
+
>
|
|
28531
|
+
{{ (expanded() ? "filters.showLess" : "filters.showMore") | transloco }}
|
|
28253
28532
|
</button>
|
|
28254
28533
|
}
|
|
28255
28534
|
</div>
|
|
@@ -28320,36 +28599,65 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
28320
28599
|
providers: [provideTranslocoScope("filters")],
|
|
28321
28600
|
template: `
|
|
28322
28601
|
@if (chips().length > 0) {
|
|
28323
|
-
|
|
28324
|
-
|
|
28325
|
-
|
|
28326
|
-
|
|
28327
|
-
|
|
28328
|
-
|
|
28329
|
-
|
|
28330
|
-
|
|
28331
|
-
|
|
28332
|
-
|
|
28333
|
-
|
|
28334
|
-
|
|
28335
|
-
|
|
28336
|
-
|
|
28337
|
-
|
|
28338
|
-
|
|
28339
|
-
[
|
|
28340
|
-
|
|
28341
|
-
[
|
|
28342
|
-
[
|
|
28343
|
-
(click)="remove(chip)"
|
|
28602
|
+
<!-- The zone the "show more" button belongs to: it appears when the pointer is on it or the focus is in it. -->
|
|
28603
|
+
<div class="group/applied relative" [class.pb-7]="foldable() && expanded()">
|
|
28604
|
+
<!-- Past maxLines lines the list is folded to that height — the rest is behind a fade, and a height that grows and
|
|
28605
|
+
shrinks shows it. The chips that fall in the folded part are inert (see measure): a remove button nobody can see
|
|
28606
|
+
must not stay in the tab order. The clip keeps a margin of a few pixels, so the focus ring of the visible chips is whole. -->
|
|
28607
|
+
<div
|
|
28608
|
+
#list
|
|
28609
|
+
[id]="listId"
|
|
28610
|
+
class="flex flex-wrap content-start items-center gap-1.5 [overflow-clip-margin:0.25rem] overflow-clip transition-[height] duration-200 ease-[cubic-bezier(0.32,0.72,0,1)] motion-reduce:transition-none"
|
|
28611
|
+
[style.height.px]="listHeight()"
|
|
28612
|
+
[style.mask-image]="foldable() && !expanded() ? 'linear-gradient(to bottom, black calc(100% - 1.5rem), transparent)' : null"
|
|
28613
|
+
role="list"
|
|
28614
|
+
>
|
|
28615
|
+
@for (chip of chips(); track chip.id) {
|
|
28616
|
+
<tag
|
|
28617
|
+
role="listitem"
|
|
28618
|
+
[variant]="variant()"
|
|
28619
|
+
[scheme]="scheme()"
|
|
28620
|
+
[size]="size()"
|
|
28621
|
+
[class]="'max-w-full items-center gap-1 ' + chipClass()"
|
|
28344
28622
|
>
|
|
28345
|
-
|
|
28623
|
+
@if (showField()) {
|
|
28624
|
+
<span>{{ (chip.name ?? chip.field) | syslang }}:</span>
|
|
28625
|
+
}
|
|
28626
|
+
<span class="truncate">{{ chip.display | syslang }}</span>
|
|
28627
|
+
<button
|
|
28628
|
+
variant="none"
|
|
28629
|
+
size="xs"
|
|
28630
|
+
[iconOnly]="true"
|
|
28631
|
+
class="shrink-0"
|
|
28632
|
+
[attr.aria-label]="('filters.removeFilter' | transloco) + ': ' + chip.display"
|
|
28633
|
+
[title]="('filters.removeFilter' | transloco) + ': ' + chip.display"
|
|
28634
|
+
(click)="remove(chip)"
|
|
28635
|
+
>
|
|
28636
|
+
<x-mark-icon class="size-3" />
|
|
28637
|
+
</button>
|
|
28638
|
+
</tag>
|
|
28639
|
+
}
|
|
28640
|
+
|
|
28641
|
+
@if (showClearAll() && chips().length > 1) {
|
|
28642
|
+
<button variant="tertiary" size="xs" (click)="clear()">
|
|
28643
|
+
{{ "filters.clearAllFilters" | transloco }}
|
|
28346
28644
|
</button>
|
|
28347
|
-
|
|
28348
|
-
|
|
28645
|
+
}
|
|
28646
|
+
</div>
|
|
28349
28647
|
|
|
28350
|
-
@if (
|
|
28351
|
-
|
|
28352
|
-
|
|
28648
|
+
@if (foldable()) {
|
|
28649
|
+
<!-- Out of sight until the zone is hovered or holds the focus — and always there on a screen with no hover. Still in the
|
|
28650
|
+
tab order, and revealed by the focus. Its own surface, since it lies over the chips. -->
|
|
28651
|
+
<button
|
|
28652
|
+
type="button"
|
|
28653
|
+
variant="secondary"
|
|
28654
|
+
size="xs"
|
|
28655
|
+
class="absolute right-0 bottom-0 bg-(--bg-neutral-white) opacity-0 shadow-(--shadow-1) transition-opacity duration-150 group-hover/applied:opacity-100 group-focus-within/applied:opacity-100 focus-visible:opacity-100 motion-reduce:transition-none [@media(hover:none)]:opacity-100"
|
|
28656
|
+
[attr.aria-expanded]="expanded()"
|
|
28657
|
+
[attr.aria-controls]="listId"
|
|
28658
|
+
(click)="toggle()"
|
|
28659
|
+
>
|
|
28660
|
+
{{ (expanded() ? "filters.showLess" : "filters.showMore") | transloco }}
|
|
28353
28661
|
</button>
|
|
28354
28662
|
}
|
|
28355
28663
|
</div>
|
|
@@ -28358,7 +28666,12 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
28358
28666
|
host: { "[class.hidden]": "chips().length === 0" }
|
|
28359
28667
|
}]
|
|
28360
28668
|
}],
|
|
28669
|
+
ctorParameters: () => [],
|
|
28361
28670
|
propDecorators: {
|
|
28671
|
+
list: [{
|
|
28672
|
+
type: i0.ViewChild,
|
|
28673
|
+
args: ["list", { isSignal: true }]
|
|
28674
|
+
}],
|
|
28362
28675
|
filters: [{
|
|
28363
28676
|
type: i0.Input,
|
|
28364
28677
|
args: [{
|
|
@@ -28415,6 +28728,22 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
28415
28728
|
required: false
|
|
28416
28729
|
}]
|
|
28417
28730
|
}],
|
|
28731
|
+
maxLines: [{
|
|
28732
|
+
type: i0.Input,
|
|
28733
|
+
args: [{
|
|
28734
|
+
isSignal: true,
|
|
28735
|
+
alias: "maxLines",
|
|
28736
|
+
required: false
|
|
28737
|
+
}]
|
|
28738
|
+
}],
|
|
28739
|
+
showClearAll: [{
|
|
28740
|
+
type: i0.Input,
|
|
28741
|
+
args: [{
|
|
28742
|
+
isSignal: true,
|
|
28743
|
+
alias: "showClearAll",
|
|
28744
|
+
required: false
|
|
28745
|
+
}]
|
|
28746
|
+
}],
|
|
28418
28747
|
removed: [{
|
|
28419
28748
|
type: i0.Output,
|
|
28420
28749
|
args: ["removed"]
|
|
@@ -31619,6 +31948,1049 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
31619
31948
|
}]
|
|
31620
31949
|
}
|
|
31621
31950
|
});
|
|
31951
|
+
function filterLabel(filter) {
|
|
31952
|
+
return filter.name || filter.field || "";
|
|
31953
|
+
}
|
|
31954
|
+
function filterValue(filter) {
|
|
31955
|
+
if (filter.filters?.length) return filter.filters.map((f) => filterLabel(f) ? `${filterLabel(f)}: ${filterValue(f)}` : filterValue(f)).join(", ");
|
|
31956
|
+
if (filter.values?.length) return filter.values.join(", ");
|
|
31957
|
+
return filter.display ?? filter.value ?? [filter.start, filter.end].filter((v) => v !== void 0 && v !== "").join(" – ");
|
|
31958
|
+
}
|
|
31959
|
+
var QuickFiltersListComponent = class QuickFiltersListComponent {
|
|
31960
|
+
searchable = input(false, ...ngDevMode ? [{ debugName: "searchable" }] : /* istanbul ignore next */ []);
|
|
31961
|
+
deletable = input(true, ...ngDevMode ? [{ debugName: "deletable" }] : /* istanbul ignore next */ []);
|
|
31962
|
+
emptyHint = input(...ngDevMode ? [void 0, { debugName: "emptyHint" }] : /* istanbul ignore next */ []);
|
|
31963
|
+
applied = output();
|
|
31964
|
+
deleted = output();
|
|
31965
|
+
userSettingsStore = inject(UserSettingsStore);
|
|
31966
|
+
queryParamsStore = inject(QueryParamsStore);
|
|
31967
|
+
appStore = inject(AppStore);
|
|
31968
|
+
transloco = inject(TranslocoService);
|
|
31969
|
+
host = inject(ElementRef);
|
|
31970
|
+
injector = inject(Injector);
|
|
31971
|
+
deleteKeys = isApplePlatform() ? ["Delete", "Backspace"] : ["Delete"];
|
|
31972
|
+
quickFilters = computed(() => this.userSettingsStore.quickFilters() ?? [], ...ngDevMode ? [{ debugName: "quickFilters" }] : /* istanbul ignore next */ []);
|
|
31973
|
+
query = signal("", ...ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []);
|
|
31974
|
+
filtered = computed(() => {
|
|
31975
|
+
const query = this.query().trim().toLowerCase();
|
|
31976
|
+
return query ? this.quickFilters().filter((quickFilter) => quickFilter.name.toLowerCase().includes(query)) : this.quickFilters();
|
|
31977
|
+
}, ...ngDevMode ? [{ debugName: "filtered" }] : /* istanbul ignore next */ []);
|
|
31978
|
+
announcement = signal("", ...ngDevMode ? [{ debugName: "announcement" }] : /* istanbul ignore next */ []);
|
|
31979
|
+
details = signal(null, ...ngDevMode ? [{ debugName: "details" }] : /* istanbul ignore next */ []);
|
|
31980
|
+
detailsCard = viewChild("detailsCard", ...ngDevMode ? [{ debugName: "detailsCard" }] : /* istanbul ignore next */ []);
|
|
31981
|
+
hideTimer;
|
|
31982
|
+
deleting = signal(false, ...ngDevMode ? [{ debugName: "deleting" }] : /* istanbul ignore next */ []);
|
|
31983
|
+
constructor() {
|
|
31984
|
+
injectFloatingPanel({
|
|
31985
|
+
panel: () => this.detailsCard()?.nativeElement,
|
|
31986
|
+
anchor: () => this.details()?.anchor,
|
|
31987
|
+
placement: "bottom-start",
|
|
31988
|
+
offset: 4,
|
|
31989
|
+
topLayer: true
|
|
31990
|
+
});
|
|
31991
|
+
inject(DestroyRef).onDestroy(() => this.cancelHideDetails());
|
|
31992
|
+
}
|
|
31993
|
+
chips(quickFilter) {
|
|
31994
|
+
return quickFilter.filters.map((filter) => {
|
|
31995
|
+
const icon = (filter.field ? this.appStore.getAggregationCustomization(filter.field, filter.name) : void 0)?.icon;
|
|
31996
|
+
const value = filterValue(filter);
|
|
31997
|
+
const label = filterLabel(filter);
|
|
31998
|
+
return {
|
|
31999
|
+
text: icon || !label || !value ? value || label : `${label}: ${value}`,
|
|
32000
|
+
icon
|
|
32001
|
+
};
|
|
32002
|
+
}).filter((chip) => chip.text);
|
|
32003
|
+
}
|
|
32004
|
+
showDetails(quickFilter, anchor) {
|
|
32005
|
+
this.cancelHideDetails();
|
|
32006
|
+
this.details.set({
|
|
32007
|
+
quickFilter,
|
|
32008
|
+
anchor
|
|
32009
|
+
});
|
|
32010
|
+
}
|
|
32011
|
+
scheduleHideDetails() {
|
|
32012
|
+
this.cancelHideDetails();
|
|
32013
|
+
this.hideTimer = setTimeout(() => this.details.set(null), 100);
|
|
32014
|
+
}
|
|
32015
|
+
cancelHideDetails() {
|
|
32016
|
+
clearTimeout(this.hideTimer);
|
|
32017
|
+
}
|
|
32018
|
+
onEscape(event) {
|
|
32019
|
+
if (!this.details()) return;
|
|
32020
|
+
event.stopPropagation();
|
|
32021
|
+
this.cancelHideDetails();
|
|
32022
|
+
this.details.set(null);
|
|
32023
|
+
}
|
|
32024
|
+
picked = signal([], ...ngDevMode ? [{ debugName: "picked" }] : /* istanbul ignore next */ []);
|
|
32025
|
+
pick(values) {
|
|
32026
|
+
const quickFilter = values.at(-1);
|
|
32027
|
+
this.picked.set([]);
|
|
32028
|
+
if (quickFilter) this.apply(quickFilter);
|
|
32029
|
+
}
|
|
32030
|
+
apply(quickFilter) {
|
|
32031
|
+
this.cancelHideDetails();
|
|
32032
|
+
this.details.set(null);
|
|
32033
|
+
this.queryParamsStore.patch({ filters: structuredClone(quickFilter.filters) });
|
|
32034
|
+
this.applied.emit(quickFilter);
|
|
32035
|
+
}
|
|
32036
|
+
async remove(quickFilter, index, event) {
|
|
32037
|
+
event.stopPropagation();
|
|
32038
|
+
if (this.deleting()) return;
|
|
32039
|
+
this.deleting.set(true);
|
|
32040
|
+
this.cancelHideDetails();
|
|
32041
|
+
this.details.set(null);
|
|
32042
|
+
const hadFocus = this.host.nativeElement.contains(document.activeElement);
|
|
32043
|
+
try {
|
|
32044
|
+
await this.userSettingsStore.removeQuickFilter(quickFilter.id);
|
|
32045
|
+
const message = this.transloco.translate("quickFilters.deleted", { name: quickFilter.name });
|
|
32046
|
+
notify.success(message, { duration: 2e3 });
|
|
32047
|
+
this.announcement.set(message);
|
|
32048
|
+
this.deleted.emit(quickFilter);
|
|
32049
|
+
if (hadFocus) afterNextRender(() => {
|
|
32050
|
+
const rows = this.host.nativeElement.querySelectorAll("[role='option']");
|
|
32051
|
+
(rows.length ? rows[Math.min(index, rows.length - 1)] : this.host.nativeElement).focus();
|
|
32052
|
+
}, { injector: this.injector });
|
|
32053
|
+
} finally {
|
|
32054
|
+
this.deleting.set(false);
|
|
32055
|
+
}
|
|
32056
|
+
}
|
|
32057
|
+
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
32058
|
+
minVersion: "12.0.0",
|
|
32059
|
+
version: "22.2.1",
|
|
32060
|
+
ngImport: i0,
|
|
32061
|
+
type: QuickFiltersListComponent,
|
|
32062
|
+
deps: [],
|
|
32063
|
+
target: i0.ɵɵFactoryTarget.Component
|
|
32064
|
+
});
|
|
32065
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({
|
|
32066
|
+
minVersion: "17.0.0",
|
|
32067
|
+
version: "22.2.1",
|
|
32068
|
+
type: QuickFiltersListComponent,
|
|
32069
|
+
isStandalone: true,
|
|
32070
|
+
selector: "QuickFiltersList, quick-filters-list",
|
|
32071
|
+
inputs: {
|
|
32072
|
+
searchable: {
|
|
32073
|
+
classPropertyName: "searchable",
|
|
32074
|
+
publicName: "searchable",
|
|
32075
|
+
isSignal: true,
|
|
32076
|
+
isRequired: false,
|
|
32077
|
+
transformFunction: null
|
|
32078
|
+
},
|
|
32079
|
+
deletable: {
|
|
32080
|
+
classPropertyName: "deletable",
|
|
32081
|
+
publicName: "deletable",
|
|
32082
|
+
isSignal: true,
|
|
32083
|
+
isRequired: false,
|
|
32084
|
+
transformFunction: null
|
|
32085
|
+
},
|
|
32086
|
+
emptyHint: {
|
|
32087
|
+
classPropertyName: "emptyHint",
|
|
32088
|
+
publicName: "emptyHint",
|
|
32089
|
+
isSignal: true,
|
|
32090
|
+
isRequired: false,
|
|
32091
|
+
transformFunction: null
|
|
32092
|
+
}
|
|
32093
|
+
},
|
|
32094
|
+
outputs: {
|
|
32095
|
+
applied: "applied",
|
|
32096
|
+
deleted: "deleted"
|
|
32097
|
+
},
|
|
32098
|
+
host: {
|
|
32099
|
+
attributes: { "tabindex": "-1" },
|
|
32100
|
+
listeners: { "keydown.escape": "onEscape($event)" },
|
|
32101
|
+
classAttribute: "flex min-h-0 flex-col gap-(--spacing-xs) outline-none"
|
|
32102
|
+
},
|
|
32103
|
+
providers: [provideTranslocoScope("quick-filters")],
|
|
32104
|
+
viewQueries: [{
|
|
32105
|
+
propertyName: "detailsCard",
|
|
32106
|
+
first: true,
|
|
32107
|
+
predicate: ["detailsCard"],
|
|
32108
|
+
descendants: true,
|
|
32109
|
+
isSignal: true
|
|
32110
|
+
}],
|
|
32111
|
+
ngImport: i0,
|
|
32112
|
+
template: `
|
|
32113
|
+
@if (searchable() && quickFilters().length > 0) {
|
|
32114
|
+
<SearchInput
|
|
32115
|
+
size="md"
|
|
32116
|
+
class="border-(--stroke-primary-light)"
|
|
32117
|
+
[(value)]="query"
|
|
32118
|
+
[placeholder]="'quickFilters.search' | transloco"
|
|
32119
|
+
[aria-label]="'quickFilters.search' | transloco"
|
|
32120
|
+
[clearLabel]="'quickFilters.clearSearch' | transloco" />
|
|
32121
|
+
}
|
|
32122
|
+
|
|
32123
|
+
@if (filtered().length) {
|
|
32124
|
+
<!-- The galactik List, as the design's List Item: a listbox of menu rows with an icon, the name and the number of filters.
|
|
32125
|
+
Choosing a row (click, Enter) applies it; the selection is not kept. The cross at the end of the row deletes it. -->
|
|
32126
|
+
<List
|
|
32127
|
+
variant="list-menu"
|
|
32128
|
+
size="medium"
|
|
32129
|
+
selectionMode="explicit"
|
|
32130
|
+
class="-mx-1 min-h-0 flex-1 overflow-auto px-1 [scrollbar-color:color-mix(in_srgb,currentColor_40%,transparent)_transparent] [scrollbar-width:thin]"
|
|
32131
|
+
[attr.aria-label]="'quickFilters.list' | transloco"
|
|
32132
|
+
[value]="picked()"
|
|
32133
|
+
(valueChange)="pick($event)">
|
|
32134
|
+
@for (quickFilter of filtered(); track quickFilter.id; let index = $index) {
|
|
32135
|
+
<ListItem
|
|
32136
|
+
[value]="quickFilter"
|
|
32137
|
+
[label]="quickFilter.name"
|
|
32138
|
+
(focusin)="showDetails(quickFilter, count)"
|
|
32139
|
+
(focusout)="scheduleHideDetails()">
|
|
32140
|
+
<ListItemLeft>
|
|
32141
|
+
<ListItemIcon><quick-filter-icon /></ListItemIcon>
|
|
32142
|
+
<ListItemText>
|
|
32143
|
+
<!-- The tooltip shows the whole name, and only when it is cut off: a full name needs none. -->
|
|
32144
|
+
<span truncated #cut="truncated" [tooltip]="quickFilter.name" [tooltipDisabled]="!cut.isTruncated()" class="block truncate">{{ quickFilter.name }}</span>
|
|
32145
|
+
<!-- The line of the count of filters is the one that shows what the quick filter holds, on hover (the row itself only applies
|
|
32146
|
+
it); on the keyboard, the row having the focus shows it. It takes the look of the subtext of a row. -->
|
|
32147
|
+
<span
|
|
32148
|
+
#count
|
|
32149
|
+
data-count
|
|
32150
|
+
class="list-item-subtext block w-fit! decoration-dotted underline-offset-2 hover:underline"
|
|
32151
|
+
(mouseenter)="showDetails(quickFilter, count)"
|
|
32152
|
+
(mouseleave)="scheduleHideDetails()">
|
|
32153
|
+
{{ (quickFilter.filters.length === 1 ? "quickFilters.countOne" : "quickFilters.countOther") | transloco: { count: quickFilter.filters.length } }}
|
|
32154
|
+
</span>
|
|
32155
|
+
</ListItemText>
|
|
32156
|
+
</ListItemLeft>
|
|
32157
|
+
@if (deletable()) {
|
|
32158
|
+
<ListItemAction
|
|
32159
|
+
[ariaLabel]="'quickFilters.delete' | transloco: { name: quickFilter.name }"
|
|
32160
|
+
[keys]="deleteKeys"
|
|
32161
|
+
[shortcut]="'quickFilters.deleteShortcut' | transloco"
|
|
32162
|
+
[description]="'quickFilters.deleteDescription' | transloco: { key: 'quickFilters.deleteShortcut' | transloco }"
|
|
32163
|
+
(click)="remove(quickFilter, index, $event)">
|
|
32164
|
+
<x-mark-icon />
|
|
32165
|
+
</ListItemAction>
|
|
32166
|
+
}
|
|
32167
|
+
</ListItem>
|
|
32168
|
+
}
|
|
32169
|
+
</List>
|
|
32170
|
+
|
|
32171
|
+
<!-- The hover card of the design: the filters a quick filter holds, as chips, hanging under its row. Placed with Floating UI in
|
|
32172
|
+
the top layer, so it is above whatever clips the list. It stays while the pointer is on it, and while the focus is on the row. -->
|
|
32173
|
+
@if (details(); as shown) {
|
|
32174
|
+
<div
|
|
32175
|
+
#detailsCard
|
|
32176
|
+
role="tooltip"
|
|
32177
|
+
class="fixed m-0 flex max-w-72 flex-wrap gap-(--spacing-3xs) rounded-(--radius-lg) border border-solid border-(--stroke-neutral-white) bg-(--bg-neutral-white) p-(--spacing-md) shadow-(--shadow-2)"
|
|
32178
|
+
(mouseenter)="cancelHideDetails()"
|
|
32179
|
+
(mouseleave)="scheduleHideDetails()">
|
|
32180
|
+
@for (chip of chips(shown.quickFilter); track $index) {
|
|
32181
|
+
<span
|
|
32182
|
+
data-chip
|
|
32183
|
+
class="inline-flex max-w-full min-w-0 items-center gap-(--spacing-3xs) rounded-(--radius-rounded) bg-(--bg-neutral-base) py-(--spacing-4xs) pr-(--spacing-2xs) pl-(--spacing-2xs) text-(length:--size-sm) font-medium text-(--font-neutral-base)">
|
|
32184
|
+
<!-- the icon the application gives the aggregation in the filters custom JSON, as on the filter button -->
|
|
32185
|
+
@if (chip.icon) {
|
|
32186
|
+
<fa-icon [faClass]="chip.icon" class="shrink-0" />
|
|
32187
|
+
}
|
|
32188
|
+
<!-- A chip that is cut off shows its whole text in a tooltip; one that fits has none. -->
|
|
32189
|
+
<span class="truncate" truncated #cutChip="truncated" [tooltip]="chip.text" [tooltipDisabled]="!cutChip.isTruncated()">{{ chip.text }}</span>
|
|
32190
|
+
</span>
|
|
32191
|
+
}
|
|
32192
|
+
</div>
|
|
32193
|
+
}
|
|
32194
|
+
} @else {
|
|
32195
|
+
<!-- The empty-state rule of the list features: the feature's icon on a coloured tile, a title, one hint. -->
|
|
32196
|
+
<div class="flex flex-col items-center gap-(--spacing-xs) px-(--spacing-md) py-(--spacing-lg) text-center">
|
|
32197
|
+
<span class="inline-flex size-12 items-center justify-center rounded-(--radius-md) bg-(--bg-cyan-base-alt) text-(--font-cyan-muted)" aria-hidden="true">
|
|
32198
|
+
<quick-filter-icon class="size-6 opacity-80" />
|
|
32199
|
+
</span>
|
|
32200
|
+
<span class="text-(length:--size-base) font-semibold text-(--font-neutral-base)">{{ (query() ? "quickFilters.noMatch" : "quickFilters.empty") | transloco }}</span>
|
|
32201
|
+
@if (!query()) {
|
|
32202
|
+
<span class="text-(length:--size-sm) text-(--font-neutral-muted)">{{ emptyHint() ?? ("quickFilters.emptyHint" | transloco) }}</span>
|
|
32203
|
+
}
|
|
32204
|
+
</div>
|
|
32205
|
+
}
|
|
32206
|
+
|
|
32207
|
+
<span role="status" class="sr-only">{{ announcement() }}</span>
|
|
32208
|
+
`,
|
|
32209
|
+
isInline: true,
|
|
32210
|
+
dependencies: [
|
|
32211
|
+
{
|
|
32212
|
+
kind: "component",
|
|
32213
|
+
type: FaIconComponent,
|
|
32214
|
+
selector: "fa-icon, FaIcon",
|
|
32215
|
+
inputs: ["faClass", "class"]
|
|
32216
|
+
},
|
|
32217
|
+
{
|
|
32218
|
+
kind: "directive",
|
|
32219
|
+
type: ListComponent,
|
|
32220
|
+
selector: "List,list",
|
|
32221
|
+
inputs: [
|
|
32222
|
+
"class",
|
|
32223
|
+
"variant",
|
|
32224
|
+
"size"
|
|
32225
|
+
],
|
|
32226
|
+
exportAs: ["list"]
|
|
32227
|
+
},
|
|
32228
|
+
{
|
|
32229
|
+
kind: "directive",
|
|
32230
|
+
type: ListItemComponent,
|
|
32231
|
+
selector: "ListItem,list-item,listitem",
|
|
32232
|
+
inputs: [
|
|
32233
|
+
"class",
|
|
32234
|
+
"variant",
|
|
32235
|
+
"size",
|
|
32236
|
+
"depth"
|
|
32237
|
+
],
|
|
32238
|
+
exportAs: ["listItem"]
|
|
32239
|
+
},
|
|
32240
|
+
{
|
|
32241
|
+
kind: "component",
|
|
32242
|
+
type: ListItemActionComponent,
|
|
32243
|
+
selector: "ListItemAction,list-item-action,listitemaction",
|
|
32244
|
+
inputs: [
|
|
32245
|
+
"ariaLabel",
|
|
32246
|
+
"disabled",
|
|
32247
|
+
"keys",
|
|
32248
|
+
"shortcut",
|
|
32249
|
+
"description"
|
|
32250
|
+
],
|
|
32251
|
+
exportAs: ["listItemAction"]
|
|
32252
|
+
},
|
|
32253
|
+
{
|
|
32254
|
+
kind: "directive",
|
|
32255
|
+
type: ListItemIconDirective,
|
|
32256
|
+
selector: "ListItemIcon,list-item-icon,listitemicon",
|
|
32257
|
+
inputs: ["class"]
|
|
32258
|
+
},
|
|
32259
|
+
{
|
|
32260
|
+
kind: "directive",
|
|
32261
|
+
type: ListItemLeftDirective,
|
|
32262
|
+
selector: "ListItemLeft,list-item-left,listitemleft",
|
|
32263
|
+
inputs: ["class"]
|
|
32264
|
+
},
|
|
32265
|
+
{
|
|
32266
|
+
kind: "component",
|
|
32267
|
+
type: ListItemTextComponent,
|
|
32268
|
+
selector: "ListItemText,list-item-text,listitemtext",
|
|
32269
|
+
inputs: ["class", "subtext"],
|
|
32270
|
+
exportAs: ["listItemText"]
|
|
32271
|
+
},
|
|
32272
|
+
{
|
|
32273
|
+
kind: "component",
|
|
32274
|
+
type: QuickFilterIcon,
|
|
32275
|
+
selector: "quick-filter, quick-filter-icon, QuickFilter, QuickFilterIcon",
|
|
32276
|
+
inputs: ["class"]
|
|
32277
|
+
},
|
|
32278
|
+
{
|
|
32279
|
+
kind: "component",
|
|
32280
|
+
type: SearchInputComponent,
|
|
32281
|
+
selector: "SearchInput:not([suggestions]), search-input:not([suggestions]), searchinput:not([suggestions])"
|
|
32282
|
+
},
|
|
32283
|
+
{
|
|
32284
|
+
kind: "directive",
|
|
32285
|
+
type: TooltipDirective,
|
|
32286
|
+
selector: "[tooltip]",
|
|
32287
|
+
inputs: [
|
|
32288
|
+
"tooltip",
|
|
32289
|
+
"tooltipPosition",
|
|
32290
|
+
"tooltipEvent",
|
|
32291
|
+
"showDelay",
|
|
32292
|
+
"hideDelay",
|
|
32293
|
+
"life",
|
|
32294
|
+
"tooltipOffset",
|
|
32295
|
+
"autoHide",
|
|
32296
|
+
"hideOnEscape",
|
|
32297
|
+
"escape",
|
|
32298
|
+
"tooltipDisabled",
|
|
32299
|
+
"tooltipArrow",
|
|
32300
|
+
"tooltipStyleClass",
|
|
32301
|
+
"tooltipOptions",
|
|
32302
|
+
"tooltip-position",
|
|
32303
|
+
"tooltip-delay",
|
|
32304
|
+
"tooltip-duration",
|
|
32305
|
+
"tooltip-offset"
|
|
32306
|
+
],
|
|
32307
|
+
exportAs: ["tooltip"]
|
|
32308
|
+
},
|
|
32309
|
+
{
|
|
32310
|
+
kind: "directive",
|
|
32311
|
+
type: TruncatedDirective,
|
|
32312
|
+
selector: "[truncated]",
|
|
32313
|
+
inputs: ["truncated"],
|
|
32314
|
+
exportAs: ["truncated"]
|
|
32315
|
+
},
|
|
32316
|
+
{
|
|
32317
|
+
kind: "component",
|
|
32318
|
+
type: XMarkIcon,
|
|
32319
|
+
selector: "x-mark, x-mark-icon, XMark, XMarkIcon",
|
|
32320
|
+
inputs: ["class"]
|
|
32321
|
+
},
|
|
32322
|
+
{
|
|
32323
|
+
kind: "pipe",
|
|
32324
|
+
type: TranslocoPipe,
|
|
32325
|
+
name: "transloco"
|
|
32326
|
+
}
|
|
32327
|
+
]
|
|
32328
|
+
});
|
|
32329
|
+
};
|
|
32330
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
32331
|
+
minVersion: "12.0.0",
|
|
32332
|
+
version: "22.2.1",
|
|
32333
|
+
ngImport: i0,
|
|
32334
|
+
type: QuickFiltersListComponent,
|
|
32335
|
+
decorators: [{
|
|
32336
|
+
type: Component,
|
|
32337
|
+
args: [{
|
|
32338
|
+
selector: "QuickFiltersList, quick-filters-list",
|
|
32339
|
+
imports: [
|
|
32340
|
+
TranslocoPipe,
|
|
32341
|
+
FaIconComponent,
|
|
32342
|
+
ListComponent,
|
|
32343
|
+
ListItemComponent,
|
|
32344
|
+
ListItemActionComponent,
|
|
32345
|
+
ListItemIconDirective,
|
|
32346
|
+
ListItemLeftDirective,
|
|
32347
|
+
ListItemTextComponent,
|
|
32348
|
+
QuickFilterIcon,
|
|
32349
|
+
SearchInputComponent,
|
|
32350
|
+
TooltipDirective,
|
|
32351
|
+
TruncatedDirective,
|
|
32352
|
+
XMarkIcon
|
|
32353
|
+
],
|
|
32354
|
+
providers: [provideTranslocoScope("quick-filters")],
|
|
32355
|
+
host: {
|
|
32356
|
+
class: "flex min-h-0 flex-col gap-(--spacing-xs) outline-none",
|
|
32357
|
+
tabindex: "-1",
|
|
32358
|
+
"(keydown.escape)": "onEscape($event)"
|
|
32359
|
+
},
|
|
32360
|
+
template: `
|
|
32361
|
+
@if (searchable() && quickFilters().length > 0) {
|
|
32362
|
+
<SearchInput
|
|
32363
|
+
size="md"
|
|
32364
|
+
class="border-(--stroke-primary-light)"
|
|
32365
|
+
[(value)]="query"
|
|
32366
|
+
[placeholder]="'quickFilters.search' | transloco"
|
|
32367
|
+
[aria-label]="'quickFilters.search' | transloco"
|
|
32368
|
+
[clearLabel]="'quickFilters.clearSearch' | transloco" />
|
|
32369
|
+
}
|
|
32370
|
+
|
|
32371
|
+
@if (filtered().length) {
|
|
32372
|
+
<!-- The galactik List, as the design's List Item: a listbox of menu rows with an icon, the name and the number of filters.
|
|
32373
|
+
Choosing a row (click, Enter) applies it; the selection is not kept. The cross at the end of the row deletes it. -->
|
|
32374
|
+
<List
|
|
32375
|
+
variant="list-menu"
|
|
32376
|
+
size="medium"
|
|
32377
|
+
selectionMode="explicit"
|
|
32378
|
+
class="-mx-1 min-h-0 flex-1 overflow-auto px-1 [scrollbar-color:color-mix(in_srgb,currentColor_40%,transparent)_transparent] [scrollbar-width:thin]"
|
|
32379
|
+
[attr.aria-label]="'quickFilters.list' | transloco"
|
|
32380
|
+
[value]="picked()"
|
|
32381
|
+
(valueChange)="pick($event)">
|
|
32382
|
+
@for (quickFilter of filtered(); track quickFilter.id; let index = $index) {
|
|
32383
|
+
<ListItem
|
|
32384
|
+
[value]="quickFilter"
|
|
32385
|
+
[label]="quickFilter.name"
|
|
32386
|
+
(focusin)="showDetails(quickFilter, count)"
|
|
32387
|
+
(focusout)="scheduleHideDetails()">
|
|
32388
|
+
<ListItemLeft>
|
|
32389
|
+
<ListItemIcon><quick-filter-icon /></ListItemIcon>
|
|
32390
|
+
<ListItemText>
|
|
32391
|
+
<!-- The tooltip shows the whole name, and only when it is cut off: a full name needs none. -->
|
|
32392
|
+
<span truncated #cut="truncated" [tooltip]="quickFilter.name" [tooltipDisabled]="!cut.isTruncated()" class="block truncate">{{ quickFilter.name }}</span>
|
|
32393
|
+
<!-- The line of the count of filters is the one that shows what the quick filter holds, on hover (the row itself only applies
|
|
32394
|
+
it); on the keyboard, the row having the focus shows it. It takes the look of the subtext of a row. -->
|
|
32395
|
+
<span
|
|
32396
|
+
#count
|
|
32397
|
+
data-count
|
|
32398
|
+
class="list-item-subtext block w-fit! decoration-dotted underline-offset-2 hover:underline"
|
|
32399
|
+
(mouseenter)="showDetails(quickFilter, count)"
|
|
32400
|
+
(mouseleave)="scheduleHideDetails()">
|
|
32401
|
+
{{ (quickFilter.filters.length === 1 ? "quickFilters.countOne" : "quickFilters.countOther") | transloco: { count: quickFilter.filters.length } }}
|
|
32402
|
+
</span>
|
|
32403
|
+
</ListItemText>
|
|
32404
|
+
</ListItemLeft>
|
|
32405
|
+
@if (deletable()) {
|
|
32406
|
+
<ListItemAction
|
|
32407
|
+
[ariaLabel]="'quickFilters.delete' | transloco: { name: quickFilter.name }"
|
|
32408
|
+
[keys]="deleteKeys"
|
|
32409
|
+
[shortcut]="'quickFilters.deleteShortcut' | transloco"
|
|
32410
|
+
[description]="'quickFilters.deleteDescription' | transloco: { key: 'quickFilters.deleteShortcut' | transloco }"
|
|
32411
|
+
(click)="remove(quickFilter, index, $event)">
|
|
32412
|
+
<x-mark-icon />
|
|
32413
|
+
</ListItemAction>
|
|
32414
|
+
}
|
|
32415
|
+
</ListItem>
|
|
32416
|
+
}
|
|
32417
|
+
</List>
|
|
32418
|
+
|
|
32419
|
+
<!-- The hover card of the design: the filters a quick filter holds, as chips, hanging under its row. Placed with Floating UI in
|
|
32420
|
+
the top layer, so it is above whatever clips the list. It stays while the pointer is on it, and while the focus is on the row. -->
|
|
32421
|
+
@if (details(); as shown) {
|
|
32422
|
+
<div
|
|
32423
|
+
#detailsCard
|
|
32424
|
+
role="tooltip"
|
|
32425
|
+
class="fixed m-0 flex max-w-72 flex-wrap gap-(--spacing-3xs) rounded-(--radius-lg) border border-solid border-(--stroke-neutral-white) bg-(--bg-neutral-white) p-(--spacing-md) shadow-(--shadow-2)"
|
|
32426
|
+
(mouseenter)="cancelHideDetails()"
|
|
32427
|
+
(mouseleave)="scheduleHideDetails()">
|
|
32428
|
+
@for (chip of chips(shown.quickFilter); track $index) {
|
|
32429
|
+
<span
|
|
32430
|
+
data-chip
|
|
32431
|
+
class="inline-flex max-w-full min-w-0 items-center gap-(--spacing-3xs) rounded-(--radius-rounded) bg-(--bg-neutral-base) py-(--spacing-4xs) pr-(--spacing-2xs) pl-(--spacing-2xs) text-(length:--size-sm) font-medium text-(--font-neutral-base)">
|
|
32432
|
+
<!-- the icon the application gives the aggregation in the filters custom JSON, as on the filter button -->
|
|
32433
|
+
@if (chip.icon) {
|
|
32434
|
+
<fa-icon [faClass]="chip.icon" class="shrink-0" />
|
|
32435
|
+
}
|
|
32436
|
+
<!-- A chip that is cut off shows its whole text in a tooltip; one that fits has none. -->
|
|
32437
|
+
<span class="truncate" truncated #cutChip="truncated" [tooltip]="chip.text" [tooltipDisabled]="!cutChip.isTruncated()">{{ chip.text }}</span>
|
|
32438
|
+
</span>
|
|
32439
|
+
}
|
|
32440
|
+
</div>
|
|
32441
|
+
}
|
|
32442
|
+
} @else {
|
|
32443
|
+
<!-- The empty-state rule of the list features: the feature's icon on a coloured tile, a title, one hint. -->
|
|
32444
|
+
<div class="flex flex-col items-center gap-(--spacing-xs) px-(--spacing-md) py-(--spacing-lg) text-center">
|
|
32445
|
+
<span class="inline-flex size-12 items-center justify-center rounded-(--radius-md) bg-(--bg-cyan-base-alt) text-(--font-cyan-muted)" aria-hidden="true">
|
|
32446
|
+
<quick-filter-icon class="size-6 opacity-80" />
|
|
32447
|
+
</span>
|
|
32448
|
+
<span class="text-(length:--size-base) font-semibold text-(--font-neutral-base)">{{ (query() ? "quickFilters.noMatch" : "quickFilters.empty") | transloco }}</span>
|
|
32449
|
+
@if (!query()) {
|
|
32450
|
+
<span class="text-(length:--size-sm) text-(--font-neutral-muted)">{{ emptyHint() ?? ("quickFilters.emptyHint" | transloco) }}</span>
|
|
32451
|
+
}
|
|
32452
|
+
</div>
|
|
32453
|
+
}
|
|
32454
|
+
|
|
32455
|
+
<span role="status" class="sr-only">{{ announcement() }}</span>
|
|
32456
|
+
`
|
|
32457
|
+
}]
|
|
32458
|
+
}],
|
|
32459
|
+
ctorParameters: () => [],
|
|
32460
|
+
propDecorators: {
|
|
32461
|
+
searchable: [{
|
|
32462
|
+
type: i0.Input,
|
|
32463
|
+
args: [{
|
|
32464
|
+
isSignal: true,
|
|
32465
|
+
alias: "searchable",
|
|
32466
|
+
required: false
|
|
32467
|
+
}]
|
|
32468
|
+
}],
|
|
32469
|
+
deletable: [{
|
|
32470
|
+
type: i0.Input,
|
|
32471
|
+
args: [{
|
|
32472
|
+
isSignal: true,
|
|
32473
|
+
alias: "deletable",
|
|
32474
|
+
required: false
|
|
32475
|
+
}]
|
|
32476
|
+
}],
|
|
32477
|
+
emptyHint: [{
|
|
32478
|
+
type: i0.Input,
|
|
32479
|
+
args: [{
|
|
32480
|
+
isSignal: true,
|
|
32481
|
+
alias: "emptyHint",
|
|
32482
|
+
required: false
|
|
32483
|
+
}]
|
|
32484
|
+
}],
|
|
32485
|
+
applied: [{
|
|
32486
|
+
type: i0.Output,
|
|
32487
|
+
args: ["applied"]
|
|
32488
|
+
}],
|
|
32489
|
+
deleted: [{
|
|
32490
|
+
type: i0.Output,
|
|
32491
|
+
args: ["deleted"]
|
|
32492
|
+
}],
|
|
32493
|
+
detailsCard: [{
|
|
32494
|
+
type: i0.ViewChild,
|
|
32495
|
+
args: ["detailsCard", { isSignal: true }]
|
|
32496
|
+
}]
|
|
32497
|
+
}
|
|
32498
|
+
});
|
|
32499
|
+
let nextPanelId = 0;
|
|
32500
|
+
var FiltersPanelComponent = class FiltersPanelComponent {
|
|
32501
|
+
documentCount = input(0, ...ngDevMode ? [{ debugName: "documentCount" }] : /* istanbul ignore next */ []);
|
|
32502
|
+
closed = output();
|
|
32503
|
+
appStore = inject(AppStore);
|
|
32504
|
+
aggregationsStore = inject(AggregationsStore);
|
|
32505
|
+
queryParamsStore = inject(QueryParamsStore);
|
|
32506
|
+
userSettingsStore = inject(UserSettingsStore);
|
|
32507
|
+
transloco = inject(TranslocoService);
|
|
32508
|
+
injector = inject(Injector);
|
|
32509
|
+
nameId = `filters-panel-quick-filter-name-${nextPanelId++}`;
|
|
32510
|
+
tab = signal("all", ...ngDevMode ? [{ debugName: "tab" }] : /* istanbul ignore next */ []);
|
|
32511
|
+
query = signal("", ...ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []);
|
|
32512
|
+
saving = signal(false, ...ngDevMode ? [{ debugName: "saving" }] : /* istanbul ignore next */ []);
|
|
32513
|
+
announcement = signal("", ...ngDevMode ? [{ debugName: "announcement" }] : /* istanbul ignore next */ []);
|
|
32514
|
+
name = signal("", ...ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []);
|
|
32515
|
+
appliedCount = computed(() => this.queryParamsStore.filters().flatMap((filter) => this.leaves(filter)).reduce((count, leaf) => count + this.valuesOf(leaf), 0), ...ngDevMode ? [{ debugName: "appliedCount" }] : /* istanbul ignore next */ []);
|
|
32516
|
+
nameInput = viewChild("nameInput", ...ngDevMode ? [{ debugName: "nameInput" }] : /* istanbul ignore next */ []);
|
|
32517
|
+
saveButton = viewChild("saveButton", ...ngDevMode ? [{ debugName: "saveButton" }] : /* istanbul ignore next */ []);
|
|
32518
|
+
matching = computed(() => {
|
|
32519
|
+
const needle = this.query().trim().toLocaleLowerCase();
|
|
32520
|
+
if (!needle) return [];
|
|
32521
|
+
const aside = this.appStore.filters().filter((f) => f.position === "left" || f.position === "both");
|
|
32522
|
+
return this.appStore.getAuthorized(aside).map((f) => this.aggregationsStore.getAggregation(f.name, "name") ?? this.aggregationsStore.getAggregation(f.column, "column")).filter((agg) => !!agg && `${agg.name} ${agg.column}`.toLocaleLowerCase().includes(needle)).map((agg) => agg.name);
|
|
32523
|
+
}, ...ngDevMode ? [{ debugName: "matching" }] : /* istanbul ignore next */ []);
|
|
32524
|
+
selectTab(values) {
|
|
32525
|
+
const next = values.at(-1);
|
|
32526
|
+
if (next) this.tab.set(next);
|
|
32527
|
+
}
|
|
32528
|
+
leaves(filter) {
|
|
32529
|
+
return filter.filters?.length ? filter.filters.flatMap((f) => this.leaves(f)) : [filter];
|
|
32530
|
+
}
|
|
32531
|
+
valuesOf(leaf) {
|
|
32532
|
+
if (leaf.values?.length) return leaf.values.length;
|
|
32533
|
+
return leaf.value !== void 0 && leaf.value !== null ? 1 : 0;
|
|
32534
|
+
}
|
|
32535
|
+
startSaving() {
|
|
32536
|
+
this.name.set("");
|
|
32537
|
+
this.saving.set(true);
|
|
32538
|
+
afterNextRender(() => this.nameInput()?.nativeElement.focus(), { injector: this.injector });
|
|
32539
|
+
}
|
|
32540
|
+
closeSaving() {
|
|
32541
|
+
this.saving.set(false);
|
|
32542
|
+
afterNextRender(() => this.saveButton()?.nativeElement.focus(), { injector: this.injector });
|
|
32543
|
+
}
|
|
32544
|
+
onSaveEscape(event) {
|
|
32545
|
+
event.stopPropagation();
|
|
32546
|
+
this.closeSaving();
|
|
32547
|
+
}
|
|
32548
|
+
async saveQuickFilter(event) {
|
|
32549
|
+
event.preventDefault();
|
|
32550
|
+
const saved = await this.userSettingsStore.addQuickFilter(this.name(), structuredClone(this.queryParamsStore.filters()));
|
|
32551
|
+
if (!saved) return;
|
|
32552
|
+
this.closeSaving();
|
|
32553
|
+
this.announcement.set(this.transloco.translate("filters.panelQuickFilterSaved", { name: saved.name }));
|
|
32554
|
+
}
|
|
32555
|
+
onQuickFilterApplied(quickFilter) {
|
|
32556
|
+
this.announcement.set(this.transloco.translate("filters.panelQuickFilterApplied", { name: quickFilter.name }));
|
|
32557
|
+
}
|
|
32558
|
+
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
32559
|
+
minVersion: "12.0.0",
|
|
32560
|
+
version: "22.2.1",
|
|
32561
|
+
ngImport: i0,
|
|
32562
|
+
type: FiltersPanelComponent,
|
|
32563
|
+
deps: [],
|
|
32564
|
+
target: i0.ɵɵFactoryTarget.Component
|
|
32565
|
+
});
|
|
32566
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({
|
|
32567
|
+
minVersion: "17.0.0",
|
|
32568
|
+
version: "22.2.1",
|
|
32569
|
+
type: FiltersPanelComponent,
|
|
32570
|
+
isStandalone: true,
|
|
32571
|
+
selector: "filters-panel, FiltersPanel, filterspanel",
|
|
32572
|
+
inputs: { documentCount: {
|
|
32573
|
+
classPropertyName: "documentCount",
|
|
32574
|
+
publicName: "documentCount",
|
|
32575
|
+
isSignal: true,
|
|
32576
|
+
isRequired: false,
|
|
32577
|
+
transformFunction: null
|
|
32578
|
+
} },
|
|
32579
|
+
outputs: { closed: "closed" },
|
|
32580
|
+
host: { classAttribute: "block h-full min-h-0" },
|
|
32581
|
+
providers: [provideTranslocoScope("filters")],
|
|
32582
|
+
viewQueries: [{
|
|
32583
|
+
propertyName: "nameInput",
|
|
32584
|
+
first: true,
|
|
32585
|
+
predicate: ["nameInput"],
|
|
32586
|
+
descendants: true,
|
|
32587
|
+
isSignal: true
|
|
32588
|
+
}, {
|
|
32589
|
+
propertyName: "saveButton",
|
|
32590
|
+
first: true,
|
|
32591
|
+
predicate: ["saveButton"],
|
|
32592
|
+
descendants: true,
|
|
32593
|
+
isSignal: true
|
|
32594
|
+
}],
|
|
32595
|
+
ngImport: i0,
|
|
32596
|
+
template: `
|
|
32597
|
+
<div class="flex h-full min-h-0 flex-col gap-(--spacing-xs)">
|
|
32598
|
+
<header class="flex items-center justify-between gap-(--spacing-xs)">
|
|
32599
|
+
<h2 class="flex items-center gap-(--spacing-2xs) text-(length:--size-lg) font-semibold">
|
|
32600
|
+
<filter-icon class="text-(length:--icon-sm) [&_svg]:fill-current" aria-hidden="true" />
|
|
32601
|
+
{{ "filters.panelTitle" | transloco }}
|
|
32602
|
+
</h2>
|
|
32603
|
+
<button variant="tertiary" size="sm" [iconOnly]="true" (click)="closed.emit()" [tooltip]="'filters.panelClose' | transloco" tooltipPosition="bottom">
|
|
32604
|
+
<x-mark-icon />
|
|
32605
|
+
<span class="sr-only">{{ "filters.panelClose" | transloco }}</span>
|
|
32606
|
+
</button>
|
|
32607
|
+
</header>
|
|
32608
|
+
|
|
32609
|
+
<!-- A two-way switch that must always have one side on: the group lets a click on the active item
|
|
32610
|
+
clear the selection, which would leave the panel with no content, so that case is ignored. -->
|
|
32611
|
+
<ToggleGroup class="w-full" [attr.aria-label]="'filters.panelViews' | transloco" [value]="[tab()]" (valueChange)="selectTab($event)">
|
|
32612
|
+
<Toggle class="flex-1 text-(length:--size-xs) font-semibold" value="all">{{ "filters.panelTabAll" | transloco }}</Toggle>
|
|
32613
|
+
<Toggle class="flex-1 text-(length:--size-xs) font-semibold" value="quick">{{ "filters.panelTabQuick" | transloco }}</Toggle>
|
|
32614
|
+
</ToggleGroup>
|
|
32615
|
+
|
|
32616
|
+
@if (tab() === "all") {
|
|
32617
|
+
<SearchInput
|
|
32618
|
+
size="md"
|
|
32619
|
+
class="[&>*]:h-8 [&>*]:rounded-full"
|
|
32620
|
+
[(value)]="query"
|
|
32621
|
+
[placeholder]="'filters.panelSearchPlaceholder' | transloco"
|
|
32622
|
+
[aria-label]="'filters.searchFilters' | transloco"
|
|
32623
|
+
[clearLabel]="'filters.clearSearch' | transloco" />
|
|
32624
|
+
|
|
32625
|
+
<!-- The bar is the anchor of the card: the card hangs under the button, as wide as the panel's content, and covers the facets
|
|
32626
|
+
below it until it is closed. -->
|
|
32627
|
+
<div class="relative flex h-8 items-center justify-between gap-(--spacing-xs) text-(length:--size-sm) text-(--font-primary-muted)">
|
|
32628
|
+
<span>{{ (documentCount() === 1 ? "filters.panelDocumentsOne" : "filters.panelDocumentsOther") | transloco: { count: documentCount() | localizedNumber } }}</span>
|
|
32629
|
+
<button
|
|
32630
|
+
#saveButton
|
|
32631
|
+
variant="secondary"
|
|
32632
|
+
size="sm"
|
|
32633
|
+
aria-haspopup="dialog"
|
|
32634
|
+
[attr.aria-expanded]="saving()"
|
|
32635
|
+
[disabled]="appliedCount() === 0"
|
|
32636
|
+
(click)="saving() ? closeSaving() : startSaving()">
|
|
32637
|
+
<quick-filter-icon />
|
|
32638
|
+
{{ "filters.panelSaveQuickFilter" | transloco }}
|
|
32639
|
+
</button>
|
|
32640
|
+
|
|
32641
|
+
@if (saving()) {
|
|
32642
|
+
<form
|
|
32643
|
+
role="dialog"
|
|
32644
|
+
[attr.aria-label]="'filters.panelSaveQuickFilter' | transloco"
|
|
32645
|
+
class="absolute inset-x-0 top-full z-10 mt-(--spacing-2xs) flex flex-col gap-(--spacing-sm) rounded-(--radius-lg) border border-(--stroke-neutral-white) bg-(--bg-neutral-white) p-(--spacing-md) text-(--font-neutral-black) shadow-(--shadow-2)"
|
|
32646
|
+
(submit)="saveQuickFilter($event)"
|
|
32647
|
+
(keydown.escape)="onSaveEscape($event)">
|
|
32648
|
+
<div class="flex flex-col gap-(--spacing-3xs)">
|
|
32649
|
+
<div class="flex items-center justify-between">
|
|
32650
|
+
<label [for]="nameId" class="text-(length:--size-sm) font-medium">{{ "filters.panelNameLabel" | transloco }}</label>
|
|
32651
|
+
<span class="rounded-(--radius-xs) bg-(--bg-neutral-base) px-1.5 py-px text-(length:--size-xs) text-(--font-neutral-muted)">{{ "filters.panelRequired" | transloco }}</span>
|
|
32652
|
+
</div>
|
|
32653
|
+
<input-group size="sm" class="w-full">
|
|
32654
|
+
<input
|
|
32655
|
+
#nameInput
|
|
32656
|
+
input-control
|
|
32657
|
+
[id]="nameId"
|
|
32658
|
+
type="text"
|
|
32659
|
+
autocomplete="off"
|
|
32660
|
+
required
|
|
32661
|
+
[placeholder]="'filters.panelQuickFilterName' | transloco"
|
|
32662
|
+
[value]="name()"
|
|
32663
|
+
(input)="name.set(nameInput.value)" />
|
|
32664
|
+
</input-group>
|
|
32665
|
+
</div>
|
|
32666
|
+
<div class="flex justify-end gap-(--spacing-2xs)">
|
|
32667
|
+
<button type="button" variant="secondary" size="sm" (click)="closeSaving()">{{ "filters.panelCancel" | transloco }}</button>
|
|
32668
|
+
<button type="submit" variant="primary" size="sm" [disabled]="!name().trim()">{{ "filters.panelSave" | transloco }}</button>
|
|
32669
|
+
</div>
|
|
32670
|
+
</form>
|
|
32671
|
+
}
|
|
32672
|
+
</div>
|
|
32673
|
+
|
|
32674
|
+
<!-- single outer scroll lives here; aside-filters' own overflow is neutralized so it is not a second, default-styled scroll container.
|
|
32675
|
+
relative, and it is load-bearing: a scroll container only clips the descendants it is a containing block for. The facets hold sr-only
|
|
32676
|
+
elements, which are position: absolute; with this div left static they escaped the clip, sat at their flow position far below the
|
|
32677
|
+
visible part of a long facet, and made the whole page scroll — a second scrollbar next to the facet's own. -->
|
|
32678
|
+
<div class="relative -mx-1 flex min-h-0 flex-1 scrollbar-thin flex-col overflow-y-auto px-1">
|
|
32679
|
+
@if (query() && matching().length === 0) {
|
|
32680
|
+
<p class="py-(--spacing-md) text-center text-(length:--size-sm) text-(--font-neutral-muted)">{{ "filters.noFilterMatch" | transloco }}</p>
|
|
32681
|
+
} @else {
|
|
32682
|
+
<aside-filters class="overflow-visible" [aggregations]="query() ? matching() : []" />
|
|
32683
|
+
}
|
|
32684
|
+
</div>
|
|
32685
|
+
} @else {
|
|
32686
|
+
<!-- The list of the quick filters pill, inline: it owns the rows, the hover card, the cross and the empty state. -->
|
|
32687
|
+
<quick-filters-list
|
|
32688
|
+
class="flex-1"
|
|
32689
|
+
[emptyHint]="'filters.panelNoQuickFiltersHint' | transloco"
|
|
32690
|
+
(applied)="onQuickFilterApplied($event)" />
|
|
32691
|
+
}
|
|
32692
|
+
|
|
32693
|
+
<span role="status" class="sr-only">{{ announcement() }}</span>
|
|
32694
|
+
</div>
|
|
32695
|
+
`,
|
|
32696
|
+
isInline: true,
|
|
32697
|
+
dependencies: [
|
|
32698
|
+
{
|
|
32699
|
+
kind: "component",
|
|
32700
|
+
type: AsideFiltersComponent,
|
|
32701
|
+
selector: "aside-filters, AsideFilters, asidefilters",
|
|
32702
|
+
inputs: [
|
|
32703
|
+
"class",
|
|
32704
|
+
"aggregations",
|
|
32705
|
+
"exclusive",
|
|
32706
|
+
"collapsible",
|
|
32707
|
+
"collapsed",
|
|
32708
|
+
"hideWhenEmpty"
|
|
32709
|
+
]
|
|
32710
|
+
},
|
|
32711
|
+
{
|
|
32712
|
+
kind: "directive",
|
|
32713
|
+
type: ButtonComponent,
|
|
32714
|
+
selector: "button",
|
|
32715
|
+
inputs: [
|
|
32716
|
+
"class",
|
|
32717
|
+
"variant",
|
|
32718
|
+
"scheme",
|
|
32719
|
+
"iconOnly",
|
|
32720
|
+
"size"
|
|
32721
|
+
]
|
|
32722
|
+
},
|
|
32723
|
+
{
|
|
32724
|
+
kind: "component",
|
|
32725
|
+
type: FilterIcon,
|
|
32726
|
+
selector: "filter, filter-icon, Filter, FilterIcon",
|
|
32727
|
+
inputs: ["class"]
|
|
32728
|
+
},
|
|
32729
|
+
{
|
|
32730
|
+
kind: "directive",
|
|
32731
|
+
type: InputComponent,
|
|
32732
|
+
selector: "input-group, inputgroup, InputGroup",
|
|
32733
|
+
inputs: [
|
|
32734
|
+
"class",
|
|
32735
|
+
"size",
|
|
32736
|
+
"scheme",
|
|
32737
|
+
"filled",
|
|
32738
|
+
"numbers",
|
|
32739
|
+
"autoHeight",
|
|
32740
|
+
"textSize"
|
|
32741
|
+
]
|
|
32742
|
+
},
|
|
32743
|
+
{
|
|
32744
|
+
kind: "directive",
|
|
32745
|
+
type: InputControlDirective,
|
|
32746
|
+
selector: "input[input-control], input[inputControl]",
|
|
32747
|
+
inputs: ["class"]
|
|
32748
|
+
},
|
|
32749
|
+
{
|
|
32750
|
+
kind: "component",
|
|
32751
|
+
type: QuickFilterIcon,
|
|
32752
|
+
selector: "quick-filter, quick-filter-icon, QuickFilter, QuickFilterIcon",
|
|
32753
|
+
inputs: ["class"]
|
|
32754
|
+
},
|
|
32755
|
+
{
|
|
32756
|
+
kind: "component",
|
|
32757
|
+
type: QuickFiltersListComponent,
|
|
32758
|
+
selector: "QuickFiltersList, quick-filters-list",
|
|
32759
|
+
inputs: [
|
|
32760
|
+
"searchable",
|
|
32761
|
+
"deletable",
|
|
32762
|
+
"emptyHint"
|
|
32763
|
+
],
|
|
32764
|
+
outputs: ["applied", "deleted"]
|
|
32765
|
+
},
|
|
32766
|
+
{
|
|
32767
|
+
kind: "component",
|
|
32768
|
+
type: SearchInputComponent,
|
|
32769
|
+
selector: "SearchInput:not([suggestions]), search-input:not([suggestions]), searchinput:not([suggestions])"
|
|
32770
|
+
},
|
|
32771
|
+
{
|
|
32772
|
+
kind: "directive",
|
|
32773
|
+
type: ToggleGroupComponent,
|
|
32774
|
+
selector: "ToggleGroup, toggle-group, togglegroup",
|
|
32775
|
+
inputs: [
|
|
32776
|
+
"class",
|
|
32777
|
+
"value",
|
|
32778
|
+
"multi"
|
|
32779
|
+
],
|
|
32780
|
+
outputs: ["valueChange"],
|
|
32781
|
+
exportAs: ["toggleGroup"]
|
|
32782
|
+
},
|
|
32783
|
+
{
|
|
32784
|
+
kind: "directive",
|
|
32785
|
+
type: ToggleItemComponent,
|
|
32786
|
+
selector: "Toggle[value]",
|
|
32787
|
+
inputs: [
|
|
32788
|
+
"value",
|
|
32789
|
+
"class",
|
|
32790
|
+
"variant",
|
|
32791
|
+
"size",
|
|
32792
|
+
"iconOnly",
|
|
32793
|
+
"showIcon"
|
|
32794
|
+
],
|
|
32795
|
+
exportAs: ["toggle"]
|
|
32796
|
+
},
|
|
32797
|
+
{
|
|
32798
|
+
kind: "directive",
|
|
32799
|
+
type: TooltipDirective,
|
|
32800
|
+
selector: "[tooltip]",
|
|
32801
|
+
inputs: [
|
|
32802
|
+
"tooltip",
|
|
32803
|
+
"tooltipPosition",
|
|
32804
|
+
"tooltipEvent",
|
|
32805
|
+
"showDelay",
|
|
32806
|
+
"hideDelay",
|
|
32807
|
+
"life",
|
|
32808
|
+
"tooltipOffset",
|
|
32809
|
+
"autoHide",
|
|
32810
|
+
"hideOnEscape",
|
|
32811
|
+
"escape",
|
|
32812
|
+
"tooltipDisabled",
|
|
32813
|
+
"tooltipArrow",
|
|
32814
|
+
"tooltipStyleClass",
|
|
32815
|
+
"tooltipOptions",
|
|
32816
|
+
"tooltip-position",
|
|
32817
|
+
"tooltip-delay",
|
|
32818
|
+
"tooltip-duration",
|
|
32819
|
+
"tooltip-offset"
|
|
32820
|
+
],
|
|
32821
|
+
exportAs: ["tooltip"]
|
|
32822
|
+
},
|
|
32823
|
+
{
|
|
32824
|
+
kind: "component",
|
|
32825
|
+
type: XMarkIcon,
|
|
32826
|
+
selector: "x-mark, x-mark-icon, XMark, XMarkIcon",
|
|
32827
|
+
inputs: ["class"]
|
|
32828
|
+
},
|
|
32829
|
+
{
|
|
32830
|
+
kind: "pipe",
|
|
32831
|
+
type: TranslocoPipe,
|
|
32832
|
+
name: "transloco"
|
|
32833
|
+
},
|
|
32834
|
+
{
|
|
32835
|
+
kind: "pipe",
|
|
32836
|
+
type: LocalizedNumberPipe,
|
|
32837
|
+
name: "localizedNumber"
|
|
32838
|
+
}
|
|
32839
|
+
]
|
|
32840
|
+
});
|
|
32841
|
+
};
|
|
32842
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
32843
|
+
minVersion: "12.0.0",
|
|
32844
|
+
version: "22.2.1",
|
|
32845
|
+
ngImport: i0,
|
|
32846
|
+
type: FiltersPanelComponent,
|
|
32847
|
+
decorators: [{
|
|
32848
|
+
type: Component,
|
|
32849
|
+
args: [{
|
|
32850
|
+
selector: "filters-panel, FiltersPanel, filterspanel",
|
|
32851
|
+
imports: [
|
|
32852
|
+
TranslocoPipe,
|
|
32853
|
+
LocalizedNumberPipe,
|
|
32854
|
+
AsideFiltersComponent,
|
|
32855
|
+
ButtonComponent,
|
|
32856
|
+
FilterIcon,
|
|
32857
|
+
InputComponent,
|
|
32858
|
+
InputControlDirective,
|
|
32859
|
+
QuickFilterIcon,
|
|
32860
|
+
QuickFiltersListComponent,
|
|
32861
|
+
SearchInputComponent,
|
|
32862
|
+
ToggleGroupComponent,
|
|
32863
|
+
ToggleItemComponent,
|
|
32864
|
+
TooltipDirective,
|
|
32865
|
+
XMarkIcon
|
|
32866
|
+
],
|
|
32867
|
+
providers: [provideTranslocoScope("filters")],
|
|
32868
|
+
host: { class: "block h-full min-h-0" },
|
|
32869
|
+
template: `
|
|
32870
|
+
<div class="flex h-full min-h-0 flex-col gap-(--spacing-xs)">
|
|
32871
|
+
<header class="flex items-center justify-between gap-(--spacing-xs)">
|
|
32872
|
+
<h2 class="flex items-center gap-(--spacing-2xs) text-(length:--size-lg) font-semibold">
|
|
32873
|
+
<filter-icon class="text-(length:--icon-sm) [&_svg]:fill-current" aria-hidden="true" />
|
|
32874
|
+
{{ "filters.panelTitle" | transloco }}
|
|
32875
|
+
</h2>
|
|
32876
|
+
<button variant="tertiary" size="sm" [iconOnly]="true" (click)="closed.emit()" [tooltip]="'filters.panelClose' | transloco" tooltipPosition="bottom">
|
|
32877
|
+
<x-mark-icon />
|
|
32878
|
+
<span class="sr-only">{{ "filters.panelClose" | transloco }}</span>
|
|
32879
|
+
</button>
|
|
32880
|
+
</header>
|
|
32881
|
+
|
|
32882
|
+
<!-- A two-way switch that must always have one side on: the group lets a click on the active item
|
|
32883
|
+
clear the selection, which would leave the panel with no content, so that case is ignored. -->
|
|
32884
|
+
<ToggleGroup class="w-full" [attr.aria-label]="'filters.panelViews' | transloco" [value]="[tab()]" (valueChange)="selectTab($event)">
|
|
32885
|
+
<Toggle class="flex-1 text-(length:--size-xs) font-semibold" value="all">{{ "filters.panelTabAll" | transloco }}</Toggle>
|
|
32886
|
+
<Toggle class="flex-1 text-(length:--size-xs) font-semibold" value="quick">{{ "filters.panelTabQuick" | transloco }}</Toggle>
|
|
32887
|
+
</ToggleGroup>
|
|
32888
|
+
|
|
32889
|
+
@if (tab() === "all") {
|
|
32890
|
+
<SearchInput
|
|
32891
|
+
size="md"
|
|
32892
|
+
class="[&>*]:h-8 [&>*]:rounded-full"
|
|
32893
|
+
[(value)]="query"
|
|
32894
|
+
[placeholder]="'filters.panelSearchPlaceholder' | transloco"
|
|
32895
|
+
[aria-label]="'filters.searchFilters' | transloco"
|
|
32896
|
+
[clearLabel]="'filters.clearSearch' | transloco" />
|
|
32897
|
+
|
|
32898
|
+
<!-- The bar is the anchor of the card: the card hangs under the button, as wide as the panel's content, and covers the facets
|
|
32899
|
+
below it until it is closed. -->
|
|
32900
|
+
<div class="relative flex h-8 items-center justify-between gap-(--spacing-xs) text-(length:--size-sm) text-(--font-primary-muted)">
|
|
32901
|
+
<span>{{ (documentCount() === 1 ? "filters.panelDocumentsOne" : "filters.panelDocumentsOther") | transloco: { count: documentCount() | localizedNumber } }}</span>
|
|
32902
|
+
<button
|
|
32903
|
+
#saveButton
|
|
32904
|
+
variant="secondary"
|
|
32905
|
+
size="sm"
|
|
32906
|
+
aria-haspopup="dialog"
|
|
32907
|
+
[attr.aria-expanded]="saving()"
|
|
32908
|
+
[disabled]="appliedCount() === 0"
|
|
32909
|
+
(click)="saving() ? closeSaving() : startSaving()">
|
|
32910
|
+
<quick-filter-icon />
|
|
32911
|
+
{{ "filters.panelSaveQuickFilter" | transloco }}
|
|
32912
|
+
</button>
|
|
32913
|
+
|
|
32914
|
+
@if (saving()) {
|
|
32915
|
+
<form
|
|
32916
|
+
role="dialog"
|
|
32917
|
+
[attr.aria-label]="'filters.panelSaveQuickFilter' | transloco"
|
|
32918
|
+
class="absolute inset-x-0 top-full z-10 mt-(--spacing-2xs) flex flex-col gap-(--spacing-sm) rounded-(--radius-lg) border border-(--stroke-neutral-white) bg-(--bg-neutral-white) p-(--spacing-md) text-(--font-neutral-black) shadow-(--shadow-2)"
|
|
32919
|
+
(submit)="saveQuickFilter($event)"
|
|
32920
|
+
(keydown.escape)="onSaveEscape($event)">
|
|
32921
|
+
<div class="flex flex-col gap-(--spacing-3xs)">
|
|
32922
|
+
<div class="flex items-center justify-between">
|
|
32923
|
+
<label [for]="nameId" class="text-(length:--size-sm) font-medium">{{ "filters.panelNameLabel" | transloco }}</label>
|
|
32924
|
+
<span class="rounded-(--radius-xs) bg-(--bg-neutral-base) px-1.5 py-px text-(length:--size-xs) text-(--font-neutral-muted)">{{ "filters.panelRequired" | transloco }}</span>
|
|
32925
|
+
</div>
|
|
32926
|
+
<input-group size="sm" class="w-full">
|
|
32927
|
+
<input
|
|
32928
|
+
#nameInput
|
|
32929
|
+
input-control
|
|
32930
|
+
[id]="nameId"
|
|
32931
|
+
type="text"
|
|
32932
|
+
autocomplete="off"
|
|
32933
|
+
required
|
|
32934
|
+
[placeholder]="'filters.panelQuickFilterName' | transloco"
|
|
32935
|
+
[value]="name()"
|
|
32936
|
+
(input)="name.set(nameInput.value)" />
|
|
32937
|
+
</input-group>
|
|
32938
|
+
</div>
|
|
32939
|
+
<div class="flex justify-end gap-(--spacing-2xs)">
|
|
32940
|
+
<button type="button" variant="secondary" size="sm" (click)="closeSaving()">{{ "filters.panelCancel" | transloco }}</button>
|
|
32941
|
+
<button type="submit" variant="primary" size="sm" [disabled]="!name().trim()">{{ "filters.panelSave" | transloco }}</button>
|
|
32942
|
+
</div>
|
|
32943
|
+
</form>
|
|
32944
|
+
}
|
|
32945
|
+
</div>
|
|
32946
|
+
|
|
32947
|
+
<!-- single outer scroll lives here; aside-filters' own overflow is neutralized so it is not a second, default-styled scroll container.
|
|
32948
|
+
relative, and it is load-bearing: a scroll container only clips the descendants it is a containing block for. The facets hold sr-only
|
|
32949
|
+
elements, which are position: absolute; with this div left static they escaped the clip, sat at their flow position far below the
|
|
32950
|
+
visible part of a long facet, and made the whole page scroll — a second scrollbar next to the facet's own. -->
|
|
32951
|
+
<div class="relative -mx-1 flex min-h-0 flex-1 scrollbar-thin flex-col overflow-y-auto px-1">
|
|
32952
|
+
@if (query() && matching().length === 0) {
|
|
32953
|
+
<p class="py-(--spacing-md) text-center text-(length:--size-sm) text-(--font-neutral-muted)">{{ "filters.noFilterMatch" | transloco }}</p>
|
|
32954
|
+
} @else {
|
|
32955
|
+
<aside-filters class="overflow-visible" [aggregations]="query() ? matching() : []" />
|
|
32956
|
+
}
|
|
32957
|
+
</div>
|
|
32958
|
+
} @else {
|
|
32959
|
+
<!-- The list of the quick filters pill, inline: it owns the rows, the hover card, the cross and the empty state. -->
|
|
32960
|
+
<quick-filters-list
|
|
32961
|
+
class="flex-1"
|
|
32962
|
+
[emptyHint]="'filters.panelNoQuickFiltersHint' | transloco"
|
|
32963
|
+
(applied)="onQuickFilterApplied($event)" />
|
|
32964
|
+
}
|
|
32965
|
+
|
|
32966
|
+
<span role="status" class="sr-only">{{ announcement() }}</span>
|
|
32967
|
+
</div>
|
|
32968
|
+
`
|
|
32969
|
+
}]
|
|
32970
|
+
}],
|
|
32971
|
+
propDecorators: {
|
|
32972
|
+
documentCount: [{
|
|
32973
|
+
type: i0.Input,
|
|
32974
|
+
args: [{
|
|
32975
|
+
isSignal: true,
|
|
32976
|
+
alias: "documentCount",
|
|
32977
|
+
required: false
|
|
32978
|
+
}]
|
|
32979
|
+
}],
|
|
32980
|
+
closed: [{
|
|
32981
|
+
type: i0.Output,
|
|
32982
|
+
args: ["closed"]
|
|
32983
|
+
}],
|
|
32984
|
+
nameInput: [{
|
|
32985
|
+
type: i0.ViewChild,
|
|
32986
|
+
args: ["nameInput", { isSignal: true }]
|
|
32987
|
+
}],
|
|
32988
|
+
saveButton: [{
|
|
32989
|
+
type: i0.ViewChild,
|
|
32990
|
+
args: ["saveButton", { isSignal: true }]
|
|
32991
|
+
}]
|
|
32992
|
+
}
|
|
32993
|
+
});
|
|
31622
32994
|
var LabelService = class LabelService {
|
|
31623
32995
|
appStore = inject(AppStore);
|
|
31624
32996
|
client = inject(ATOMIC_CLIENT);
|
|
@@ -32901,43 +34273,21 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
32901
34273
|
}
|
|
32902
34274
|
});
|
|
32903
34275
|
let popoverUid = 0;
|
|
32904
|
-
function filterLabel(filter) {
|
|
32905
|
-
return filter.name || filter.field || "";
|
|
32906
|
-
}
|
|
32907
|
-
function filterValue(filter) {
|
|
32908
|
-
if (filter.filters?.length) return filter.filters.map((f) => filterLabel(f) ? `${filterLabel(f)}: ${filterValue(f)}` : filterValue(f)).join(", ");
|
|
32909
|
-
if (filter.values?.length) return filter.values.join(", ");
|
|
32910
|
-
return filter.display ?? filter.value ?? [filter.start, filter.end].filter((v) => v !== void 0 && v !== "").join(" – ");
|
|
32911
|
-
}
|
|
32912
34276
|
var QuickFiltersComponent = class QuickFiltersComponent {
|
|
32913
34277
|
scheme = input("sage", ...ngDevMode ? [{ debugName: "scheme" }] : /* istanbul ignore next */ []);
|
|
32914
34278
|
appearance = input("pill", ...ngDevMode ? [{ debugName: "appearance" }] : /* istanbul ignore next */ []);
|
|
32915
34279
|
position = input("bottom-start", ...ngDevMode ? [{ debugName: "position" }] : /* istanbul ignore next */ []);
|
|
32916
34280
|
offset = input(8, ...ngDevMode ? [{ debugName: "offset" }] : /* istanbul ignore next */ []);
|
|
32917
34281
|
applied = output();
|
|
32918
|
-
userSettingsStore = inject(UserSettingsStore);
|
|
32919
|
-
queryParamsStore = inject(QueryParamsStore);
|
|
32920
|
-
appStore = inject(AppStore);
|
|
32921
34282
|
transloco = inject(TranslocoService);
|
|
32922
34283
|
host = inject(ElementRef);
|
|
32923
34284
|
popoverId = `quick-filters-popover-${popoverUid++}`;
|
|
32924
34285
|
resized = signal(false, ...ngDevMode ? [{ debugName: "resized" }] : /* istanbul ignore next */ []);
|
|
32925
34286
|
flippedAbove = signal(false, ...ngDevMode ? [{ debugName: "flippedAbove" }] : /* istanbul ignore next */ []);
|
|
32926
34287
|
placement = computed(() => this.resized() && this.flippedAbove() ? this.position().replace("bottom", "top") : this.position(), ...ngDevMode ? [{ debugName: "placement" }] : /* istanbul ignore next */ []);
|
|
32927
|
-
details = signal(null, ...ngDevMode ? [{ debugName: "details" }] : /* istanbul ignore next */ []);
|
|
32928
|
-
detailsCard = viewChild("detailsCard", ...ngDevMode ? [{ debugName: "detailsCard" }] : /* istanbul ignore next */ []);
|
|
32929
|
-
hideTimer;
|
|
32930
34288
|
popover = viewChild(PopoverDirective, ...ngDevMode ? [{ debugName: "popover" }] : /* istanbul ignore next */ []);
|
|
32931
34289
|
isOpen = computed(() => this.popover()?.state() === "open", ...ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []);
|
|
32932
34290
|
constructor() {
|
|
32933
|
-
injectFloatingPanel({
|
|
32934
|
-
panel: () => this.detailsCard()?.nativeElement,
|
|
32935
|
-
anchor: () => this.details()?.anchor,
|
|
32936
|
-
placement: "bottom-start",
|
|
32937
|
-
offset: 4,
|
|
32938
|
-
topLayer: true
|
|
32939
|
-
});
|
|
32940
|
-
inject(DestroyRef).onDestroy(() => this.cancelHideDetails());
|
|
32941
34291
|
effect((onCleanup) => {
|
|
32942
34292
|
if (!this.resized()) return;
|
|
32943
34293
|
const onScroll = (event) => {
|
|
@@ -32957,27 +34307,9 @@ var QuickFiltersComponent = class QuickFiltersComponent {
|
|
|
32957
34307
|
selected: false,
|
|
32958
34308
|
open: this.isOpen()
|
|
32959
34309
|
}) : this.isOpen() ? "border border-(--stroke-neutral-dark)/50" : "", ...ngDevMode ? [{ debugName: "triggerClass" }] : /* istanbul ignore next */ []);
|
|
32960
|
-
quickFilters = computed(() => this.userSettingsStore.quickFilters() ?? [], ...ngDevMode ? [{ debugName: "quickFilters" }] : /* istanbul ignore next */ []);
|
|
32961
|
-
query = signal("", ...ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []);
|
|
32962
|
-
filtered = computed(() => {
|
|
32963
|
-
const query = this.query().trim().toLowerCase();
|
|
32964
|
-
return query ? this.quickFilters().filter((quickFilter) => quickFilter.name.toLowerCase().includes(query)) : this.quickFilters();
|
|
32965
|
-
}, ...ngDevMode ? [{ debugName: "filtered" }] : /* istanbul ignore next */ []);
|
|
32966
34310
|
announcement = signal("", ...ngDevMode ? [{ debugName: "announcement" }] : /* istanbul ignore next */ []);
|
|
32967
|
-
chips(quickFilter) {
|
|
32968
|
-
return quickFilter.filters.map((filter) => {
|
|
32969
|
-
const icon = (filter.field ? this.appStore.getAggregationCustomization(filter.field, filter.name) : void 0)?.icon;
|
|
32970
|
-
const value = filterValue(filter);
|
|
32971
|
-
const label = filterLabel(filter);
|
|
32972
|
-
return {
|
|
32973
|
-
text: icon || !label || !value ? value || label : `${label}: ${value}`,
|
|
32974
|
-
icon
|
|
32975
|
-
};
|
|
32976
|
-
}).filter((chip) => chip.text);
|
|
32977
|
-
}
|
|
32978
34311
|
onToggle() {
|
|
32979
34312
|
if (this.popover()?.state() === "open") return;
|
|
32980
|
-
this.reset();
|
|
32981
34313
|
this.unpin();
|
|
32982
34314
|
}
|
|
32983
34315
|
unpin() {
|
|
@@ -32996,44 +34328,12 @@ var QuickFiltersComponent = class QuickFiltersComponent {
|
|
|
32996
34328
|
if (popover && trigger && element) this.flippedAbove.set(element.getBoundingClientRect().bottom <= trigger.getBoundingClientRect().top + 1);
|
|
32997
34329
|
this.resized.set(true);
|
|
32998
34330
|
}
|
|
32999
|
-
showDetails(quickFilter, event) {
|
|
33000
|
-
this.cancelHideDetails();
|
|
33001
|
-
this.details.set({
|
|
33002
|
-
quickFilter,
|
|
33003
|
-
anchor: event.currentTarget
|
|
33004
|
-
});
|
|
33005
|
-
}
|
|
33006
|
-
scheduleHideDetails() {
|
|
33007
|
-
this.cancelHideDetails();
|
|
33008
|
-
this.hideTimer = setTimeout(() => this.details.set(null), 100);
|
|
33009
|
-
}
|
|
33010
|
-
cancelHideDetails() {
|
|
33011
|
-
clearTimeout(this.hideTimer);
|
|
33012
|
-
}
|
|
33013
|
-
hideDetails() {
|
|
33014
|
-
this.cancelHideDetails();
|
|
33015
|
-
this.details.set(null);
|
|
33016
|
-
}
|
|
33017
|
-
reset() {
|
|
33018
|
-
this.cancelHideDetails();
|
|
33019
|
-
this.details.set(null);
|
|
33020
|
-
this.query.set("");
|
|
33021
|
-
}
|
|
33022
34331
|
closeOnFocusLeave(event) {
|
|
33023
34332
|
const next = event.relatedTarget;
|
|
33024
34333
|
if (!next || !(next instanceof Node) || this.host.nativeElement.contains(next)) return;
|
|
33025
34334
|
this.popover()?.close();
|
|
33026
34335
|
}
|
|
33027
|
-
picked = signal([], ...ngDevMode ? [{ debugName: "picked" }] : /* istanbul ignore next */ []);
|
|
33028
|
-
pick(values) {
|
|
33029
|
-
const quickFilter = values.at(-1);
|
|
33030
|
-
this.picked.set([]);
|
|
33031
|
-
if (quickFilter) this.apply(quickFilter);
|
|
33032
|
-
}
|
|
33033
34336
|
apply(quickFilter) {
|
|
33034
|
-
this.cancelHideDetails();
|
|
33035
|
-
this.details.set(null);
|
|
33036
|
-
this.queryParamsStore.patch({ filters: structuredClone(quickFilter.filters) });
|
|
33037
34337
|
this.popover()?.close();
|
|
33038
34338
|
this.applied.emit(quickFilter);
|
|
33039
34339
|
this.announcement.set(this.transloco.translate("quickFilters.applied", { name: quickFilter.name }));
|
|
@@ -33084,20 +34384,11 @@ var QuickFiltersComponent = class QuickFiltersComponent {
|
|
|
33084
34384
|
},
|
|
33085
34385
|
outputs: { applied: "applied" },
|
|
33086
34386
|
host: {
|
|
33087
|
-
listeners: {
|
|
33088
|
-
"focusout": "closeOnFocusLeave($event)",
|
|
33089
|
-
"keydown.escape": "hideDetails()"
|
|
33090
|
-
},
|
|
34387
|
+
listeners: { "focusout": "closeOnFocusLeave($event)" },
|
|
33091
34388
|
classAttribute: "flex rounded-(--radius-2xl)"
|
|
33092
34389
|
},
|
|
33093
34390
|
providers: [provideTranslocoScope("quick-filters")],
|
|
33094
34391
|
viewQueries: [{
|
|
33095
|
-
propertyName: "detailsCard",
|
|
33096
|
-
first: true,
|
|
33097
|
-
predicate: ["detailsCard"],
|
|
33098
|
-
descendants: true,
|
|
33099
|
-
isSignal: true
|
|
33100
|
-
}, {
|
|
33101
34392
|
propertyName: "popover",
|
|
33102
34393
|
first: true,
|
|
33103
34394
|
predicate: PopoverDirective,
|
|
@@ -33137,87 +34428,7 @@ var QuickFiltersComponent = class QuickFiltersComponent {
|
|
|
33137
34428
|
there is room under it. The list takes the height the popover is given. The popover has no padding above and under:
|
|
33138
34429
|
the handles (12px each) are that space, and hug the edges. -->
|
|
33139
34430
|
<PopoverResizeHandle edge="top" [label]="'quickFilters.resize' | transloco" (resizeStart)="onResizeStart()" />
|
|
33140
|
-
<
|
|
33141
|
-
@if (quickFilters().length > 0) {
|
|
33142
|
-
<SearchInput
|
|
33143
|
-
size="md"
|
|
33144
|
-
class="border-(--stroke-primary-light)"
|
|
33145
|
-
[(value)]="query"
|
|
33146
|
-
[placeholder]="'quickFilters.search' | transloco"
|
|
33147
|
-
[aria-label]="'quickFilters.search' | transloco"
|
|
33148
|
-
[clearLabel]="'quickFilters.clearSearch' | transloco" />
|
|
33149
|
-
}
|
|
33150
|
-
|
|
33151
|
-
@if (filtered().length) {
|
|
33152
|
-
<!-- The galactik List, as the design's List Item: a listbox of menu rows with an icon, the name and the number of
|
|
33153
|
-
filters. Choosing a row (click, Enter) applies it; the selection is not kept, the popover closes. Hovering a
|
|
33154
|
-
row lists the filters it holds in a card. -->
|
|
33155
|
-
<List
|
|
33156
|
-
variant="list-menu"
|
|
33157
|
-
size="medium"
|
|
33158
|
-
selectionMode="explicit"
|
|
33159
|
-
class="-mx-1 min-h-0 flex-1 overflow-auto px-1"
|
|
33160
|
-
[attr.aria-label]="'quickFilters.list' | transloco"
|
|
33161
|
-
[value]="picked()"
|
|
33162
|
-
(valueChange)="pick($event)">
|
|
33163
|
-
@for (quickFilter of filtered(); track quickFilter.id) {
|
|
33164
|
-
<ListItem
|
|
33165
|
-
[value]="quickFilter"
|
|
33166
|
-
[label]="quickFilter.name"
|
|
33167
|
-
(mouseenter)="showDetails(quickFilter, $event)"
|
|
33168
|
-
(mouseleave)="scheduleHideDetails()"
|
|
33169
|
-
(focusin)="showDetails(quickFilter, $event)"
|
|
33170
|
-
(focusout)="scheduleHideDetails()">
|
|
33171
|
-
<ListItemLeft>
|
|
33172
|
-
<ListItemIcon><quick-filter-icon /></ListItemIcon>
|
|
33173
|
-
<ListItemText
|
|
33174
|
-
[subtext]="(quickFilter.filters.length === 1 ? 'quickFilters.countOne' : 'quickFilters.countOther') | transloco: { count: quickFilter.filters.length }">
|
|
33175
|
-
{{ quickFilter.name }}
|
|
33176
|
-
</ListItemText>
|
|
33177
|
-
</ListItemLeft>
|
|
33178
|
-
</ListItem>
|
|
33179
|
-
}
|
|
33180
|
-
</List>
|
|
33181
|
-
|
|
33182
|
-
<!-- The hover card of the design: the filters a quick filter holds, as chips, hanging under its row. Placed with
|
|
33183
|
-
Floating UI like the popovers, in the top layer so it is above the popover it comes from. It stays while the
|
|
33184
|
-
pointer is on it, and while the focus is on the row. -->
|
|
33185
|
-
@if (details(); as shown) {
|
|
33186
|
-
<div
|
|
33187
|
-
#detailsCard
|
|
33188
|
-
role="tooltip"
|
|
33189
|
-
class="fixed m-0 flex max-w-72 flex-wrap gap-(--spacing-3xs) rounded-(--radius-lg) border border-solid border-(--stroke-neutral-white) bg-(--bg-neutral-white) p-(--spacing-md) shadow-(--shadow-2)"
|
|
33190
|
-
(mouseenter)="cancelHideDetails()"
|
|
33191
|
-
(mouseleave)="scheduleHideDetails()">
|
|
33192
|
-
@for (chip of chips(shown.quickFilter); track $index) {
|
|
33193
|
-
<span
|
|
33194
|
-
data-chip
|
|
33195
|
-
class="inline-flex items-center gap-(--spacing-3xs) rounded-(--radius-rounded) bg-(--bg-neutral-base) py-(--spacing-4xs) pr-(--spacing-2xs) pl-(--spacing-2xs) text-(length:--size-sm) font-medium text-(--font-neutral-base)">
|
|
33196
|
-
<!-- the icon the application gives the aggregation in the filters custom JSON, as on the filter button -->
|
|
33197
|
-
@if (chip.icon) {
|
|
33198
|
-
<fa-icon [faClass]="chip.icon" class="shrink-0" />
|
|
33199
|
-
}
|
|
33200
|
-
{{ chip.text }}
|
|
33201
|
-
</span>
|
|
33202
|
-
}
|
|
33203
|
-
</div>
|
|
33204
|
-
}
|
|
33205
|
-
} @else {
|
|
33206
|
-
<!-- The empty-state rule of the list features: the feature's icon on a coloured tile, a title, one hint. -->
|
|
33207
|
-
<div class="flex flex-col items-center gap-(--spacing-xs) px-(--spacing-md) py-(--spacing-lg) text-center">
|
|
33208
|
-
<span class="inline-flex size-12 items-center justify-center rounded-(--radius-md) bg-(--bg-cyan-base-alt) text-(--font-cyan-muted)" aria-hidden="true">
|
|
33209
|
-
<quick-filter-icon class="size-6 opacity-80" />
|
|
33210
|
-
</span>
|
|
33211
|
-
<span class="text-(length:--size-base) font-semibold text-(--font-neutral-base)">{{
|
|
33212
|
-
(query() ? "quickFilters.noMatch" : "quickFilters.empty") | transloco
|
|
33213
|
-
}}</span>
|
|
33214
|
-
@if (!query()) {
|
|
33215
|
-
<span class="text-(length:--size-sm) text-(--font-neutral-muted)">{{ "quickFilters.emptyHint" | transloco }}</span>
|
|
33216
|
-
}
|
|
33217
|
-
</div>
|
|
33218
|
-
}
|
|
33219
|
-
|
|
33220
|
-
</div>
|
|
34431
|
+
<quick-filters-list class="flex-1" [searchable]="true" (applied)="apply($event)" />
|
|
33221
34432
|
<PopoverResizeHandle edge="bottom" [label]="'quickFilters.resize' | transloco" (resizeStart)="onResizeStart()" />
|
|
33222
34433
|
}
|
|
33223
34434
|
</div>
|
|
@@ -33226,12 +34437,6 @@ var QuickFiltersComponent = class QuickFiltersComponent {
|
|
|
33226
34437
|
`,
|
|
33227
34438
|
isInline: true,
|
|
33228
34439
|
dependencies: [
|
|
33229
|
-
{
|
|
33230
|
-
kind: "component",
|
|
33231
|
-
type: FaIconComponent,
|
|
33232
|
-
selector: "fa-icon, FaIcon",
|
|
33233
|
-
inputs: ["faClass", "class"]
|
|
33234
|
-
},
|
|
33235
34440
|
{
|
|
33236
34441
|
kind: "directive",
|
|
33237
34442
|
type: ButtonComponent,
|
|
@@ -33244,48 +34449,6 @@ var QuickFiltersComponent = class QuickFiltersComponent {
|
|
|
33244
34449
|
"size"
|
|
33245
34450
|
]
|
|
33246
34451
|
},
|
|
33247
|
-
{
|
|
33248
|
-
kind: "directive",
|
|
33249
|
-
type: ListComponent,
|
|
33250
|
-
selector: "List,list",
|
|
33251
|
-
inputs: [
|
|
33252
|
-
"class",
|
|
33253
|
-
"variant",
|
|
33254
|
-
"size"
|
|
33255
|
-
],
|
|
33256
|
-
exportAs: ["list"]
|
|
33257
|
-
},
|
|
33258
|
-
{
|
|
33259
|
-
kind: "directive",
|
|
33260
|
-
type: ListItemComponent,
|
|
33261
|
-
selector: "ListItem,list-item,listitem",
|
|
33262
|
-
inputs: [
|
|
33263
|
-
"class",
|
|
33264
|
-
"variant",
|
|
33265
|
-
"size",
|
|
33266
|
-
"depth"
|
|
33267
|
-
],
|
|
33268
|
-
exportAs: ["listItem"]
|
|
33269
|
-
},
|
|
33270
|
-
{
|
|
33271
|
-
kind: "directive",
|
|
33272
|
-
type: ListItemIconDirective,
|
|
33273
|
-
selector: "ListItemIcon,list-item-icon,listitemicon",
|
|
33274
|
-
inputs: ["class"]
|
|
33275
|
-
},
|
|
33276
|
-
{
|
|
33277
|
-
kind: "directive",
|
|
33278
|
-
type: ListItemLeftDirective,
|
|
33279
|
-
selector: "ListItemLeft,list-item-left,listitemleft",
|
|
33280
|
-
inputs: ["class"]
|
|
33281
|
-
},
|
|
33282
|
-
{
|
|
33283
|
-
kind: "component",
|
|
33284
|
-
type: ListItemTextComponent,
|
|
33285
|
-
selector: "ListItemText,list-item-text,listitemtext",
|
|
33286
|
-
inputs: ["class", "subtext"],
|
|
33287
|
-
exportAs: ["listItemText"]
|
|
33288
|
-
},
|
|
33289
34452
|
{
|
|
33290
34453
|
kind: "directive",
|
|
33291
34454
|
type: PopoverDirective,
|
|
@@ -33314,17 +34477,23 @@ var QuickFiltersComponent = class QuickFiltersComponent {
|
|
|
33314
34477
|
],
|
|
33315
34478
|
outputs: ["resizeStart"]
|
|
33316
34479
|
},
|
|
33317
|
-
{
|
|
33318
|
-
kind: "component",
|
|
33319
|
-
type: SearchInputComponent,
|
|
33320
|
-
selector: "SearchInput:not([suggestions]), search-input:not([suggestions]), searchinput:not([suggestions])"
|
|
33321
|
-
},
|
|
33322
34480
|
{
|
|
33323
34481
|
kind: "component",
|
|
33324
34482
|
type: QuickFilterIcon,
|
|
33325
34483
|
selector: "quick-filter, quick-filter-icon, QuickFilter, QuickFilterIcon",
|
|
33326
34484
|
inputs: ["class"]
|
|
33327
34485
|
},
|
|
34486
|
+
{
|
|
34487
|
+
kind: "component",
|
|
34488
|
+
type: QuickFiltersListComponent,
|
|
34489
|
+
selector: "QuickFiltersList, quick-filters-list",
|
|
34490
|
+
inputs: [
|
|
34491
|
+
"searchable",
|
|
34492
|
+
"deletable",
|
|
34493
|
+
"emptyHint"
|
|
34494
|
+
],
|
|
34495
|
+
outputs: ["applied", "deleted"]
|
|
34496
|
+
},
|
|
33328
34497
|
{
|
|
33329
34498
|
kind: "pipe",
|
|
33330
34499
|
type: TranslocoPipe,
|
|
@@ -33344,23 +34513,16 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
33344
34513
|
selector: "QuickFilters, quick-filters",
|
|
33345
34514
|
imports: [
|
|
33346
34515
|
TranslocoPipe,
|
|
33347
|
-
FaIconComponent,
|
|
33348
34516
|
ButtonComponent,
|
|
33349
|
-
ListComponent,
|
|
33350
|
-
ListItemComponent,
|
|
33351
|
-
ListItemIconDirective,
|
|
33352
|
-
ListItemLeftDirective,
|
|
33353
|
-
ListItemTextComponent,
|
|
33354
34517
|
PopoverDirective,
|
|
33355
34518
|
PopoverResizeHandleComponent,
|
|
33356
|
-
|
|
33357
|
-
|
|
34519
|
+
QuickFilterIcon,
|
|
34520
|
+
QuickFiltersListComponent
|
|
33358
34521
|
],
|
|
33359
34522
|
providers: [provideTranslocoScope("quick-filters")],
|
|
33360
34523
|
host: {
|
|
33361
34524
|
class: "flex rounded-(--radius-2xl)",
|
|
33362
|
-
"(focusout)": "closeOnFocusLeave($event)"
|
|
33363
|
-
"(keydown.escape)": "hideDetails()"
|
|
34525
|
+
"(focusout)": "closeOnFocusLeave($event)"
|
|
33364
34526
|
},
|
|
33365
34527
|
template: `
|
|
33366
34528
|
<button
|
|
@@ -33394,87 +34556,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
33394
34556
|
there is room under it. The list takes the height the popover is given. The popover has no padding above and under:
|
|
33395
34557
|
the handles (12px each) are that space, and hug the edges. -->
|
|
33396
34558
|
<PopoverResizeHandle edge="top" [label]="'quickFilters.resize' | transloco" (resizeStart)="onResizeStart()" />
|
|
33397
|
-
<
|
|
33398
|
-
@if (quickFilters().length > 0) {
|
|
33399
|
-
<SearchInput
|
|
33400
|
-
size="md"
|
|
33401
|
-
class="border-(--stroke-primary-light)"
|
|
33402
|
-
[(value)]="query"
|
|
33403
|
-
[placeholder]="'quickFilters.search' | transloco"
|
|
33404
|
-
[aria-label]="'quickFilters.search' | transloco"
|
|
33405
|
-
[clearLabel]="'quickFilters.clearSearch' | transloco" />
|
|
33406
|
-
}
|
|
33407
|
-
|
|
33408
|
-
@if (filtered().length) {
|
|
33409
|
-
<!-- The galactik List, as the design's List Item: a listbox of menu rows with an icon, the name and the number of
|
|
33410
|
-
filters. Choosing a row (click, Enter) applies it; the selection is not kept, the popover closes. Hovering a
|
|
33411
|
-
row lists the filters it holds in a card. -->
|
|
33412
|
-
<List
|
|
33413
|
-
variant="list-menu"
|
|
33414
|
-
size="medium"
|
|
33415
|
-
selectionMode="explicit"
|
|
33416
|
-
class="-mx-1 min-h-0 flex-1 overflow-auto px-1"
|
|
33417
|
-
[attr.aria-label]="'quickFilters.list' | transloco"
|
|
33418
|
-
[value]="picked()"
|
|
33419
|
-
(valueChange)="pick($event)">
|
|
33420
|
-
@for (quickFilter of filtered(); track quickFilter.id) {
|
|
33421
|
-
<ListItem
|
|
33422
|
-
[value]="quickFilter"
|
|
33423
|
-
[label]="quickFilter.name"
|
|
33424
|
-
(mouseenter)="showDetails(quickFilter, $event)"
|
|
33425
|
-
(mouseleave)="scheduleHideDetails()"
|
|
33426
|
-
(focusin)="showDetails(quickFilter, $event)"
|
|
33427
|
-
(focusout)="scheduleHideDetails()">
|
|
33428
|
-
<ListItemLeft>
|
|
33429
|
-
<ListItemIcon><quick-filter-icon /></ListItemIcon>
|
|
33430
|
-
<ListItemText
|
|
33431
|
-
[subtext]="(quickFilter.filters.length === 1 ? 'quickFilters.countOne' : 'quickFilters.countOther') | transloco: { count: quickFilter.filters.length }">
|
|
33432
|
-
{{ quickFilter.name }}
|
|
33433
|
-
</ListItemText>
|
|
33434
|
-
</ListItemLeft>
|
|
33435
|
-
</ListItem>
|
|
33436
|
-
}
|
|
33437
|
-
</List>
|
|
33438
|
-
|
|
33439
|
-
<!-- The hover card of the design: the filters a quick filter holds, as chips, hanging under its row. Placed with
|
|
33440
|
-
Floating UI like the popovers, in the top layer so it is above the popover it comes from. It stays while the
|
|
33441
|
-
pointer is on it, and while the focus is on the row. -->
|
|
33442
|
-
@if (details(); as shown) {
|
|
33443
|
-
<div
|
|
33444
|
-
#detailsCard
|
|
33445
|
-
role="tooltip"
|
|
33446
|
-
class="fixed m-0 flex max-w-72 flex-wrap gap-(--spacing-3xs) rounded-(--radius-lg) border border-solid border-(--stroke-neutral-white) bg-(--bg-neutral-white) p-(--spacing-md) shadow-(--shadow-2)"
|
|
33447
|
-
(mouseenter)="cancelHideDetails()"
|
|
33448
|
-
(mouseleave)="scheduleHideDetails()">
|
|
33449
|
-
@for (chip of chips(shown.quickFilter); track $index) {
|
|
33450
|
-
<span
|
|
33451
|
-
data-chip
|
|
33452
|
-
class="inline-flex items-center gap-(--spacing-3xs) rounded-(--radius-rounded) bg-(--bg-neutral-base) py-(--spacing-4xs) pr-(--spacing-2xs) pl-(--spacing-2xs) text-(length:--size-sm) font-medium text-(--font-neutral-base)">
|
|
33453
|
-
<!-- the icon the application gives the aggregation in the filters custom JSON, as on the filter button -->
|
|
33454
|
-
@if (chip.icon) {
|
|
33455
|
-
<fa-icon [faClass]="chip.icon" class="shrink-0" />
|
|
33456
|
-
}
|
|
33457
|
-
{{ chip.text }}
|
|
33458
|
-
</span>
|
|
33459
|
-
}
|
|
33460
|
-
</div>
|
|
33461
|
-
}
|
|
33462
|
-
} @else {
|
|
33463
|
-
<!-- The empty-state rule of the list features: the feature's icon on a coloured tile, a title, one hint. -->
|
|
33464
|
-
<div class="flex flex-col items-center gap-(--spacing-xs) px-(--spacing-md) py-(--spacing-lg) text-center">
|
|
33465
|
-
<span class="inline-flex size-12 items-center justify-center rounded-(--radius-md) bg-(--bg-cyan-base-alt) text-(--font-cyan-muted)" aria-hidden="true">
|
|
33466
|
-
<quick-filter-icon class="size-6 opacity-80" />
|
|
33467
|
-
</span>
|
|
33468
|
-
<span class="text-(length:--size-base) font-semibold text-(--font-neutral-base)">{{
|
|
33469
|
-
(query() ? "quickFilters.noMatch" : "quickFilters.empty") | transloco
|
|
33470
|
-
}}</span>
|
|
33471
|
-
@if (!query()) {
|
|
33472
|
-
<span class="text-(length:--size-sm) text-(--font-neutral-muted)">{{ "quickFilters.emptyHint" | transloco }}</span>
|
|
33473
|
-
}
|
|
33474
|
-
</div>
|
|
33475
|
-
}
|
|
33476
|
-
|
|
33477
|
-
</div>
|
|
34559
|
+
<quick-filters-list class="flex-1" [searchable]="true" (applied)="apply($event)" />
|
|
33478
34560
|
<PopoverResizeHandle edge="bottom" [label]="'quickFilters.resize' | transloco" (resizeStart)="onResizeStart()" />
|
|
33479
34561
|
}
|
|
33480
34562
|
</div>
|
|
@@ -33521,10 +34603,6 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
33521
34603
|
type: i0.Output,
|
|
33522
34604
|
args: ["applied"]
|
|
33523
34605
|
}],
|
|
33524
|
-
detailsCard: [{
|
|
33525
|
-
type: i0.ViewChild,
|
|
33526
|
-
args: ["detailsCard", { isSignal: true }]
|
|
33527
|
-
}],
|
|
33528
34606
|
popover: [{
|
|
33529
34607
|
type: i0.ViewChild,
|
|
33530
34608
|
args: [i0.forwardRef(() => PopoverDirective), { isSignal: true }]
|
|
@@ -37783,6 +38861,6 @@ function provideSessionExpiry(options = {}) {
|
|
|
37783
38861
|
const queryNameResolver = () => {
|
|
37784
38862
|
return inject(AppStore).getDefaultQuery()?.name || "";
|
|
37785
38863
|
};
|
|
37786
|
-
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, HistoryPanelComponent, HistoryTitleMarqueeDirective, 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, QuickFiltersComponent, RECENT_SEARCHES_CONFIG, RECENT_SEARCHES_OPTIONS, ROUTE_COMPONENTS, RecentSearchesComponent, ResetUserSettings, ResetUserSettingsDialogComponent, SAVED_SEARCHES_CONFIG, SAVED_SEARCHES_OPTIONS, SEARCH_BOX_CATEGORIES_SORT, SEARCH_BOX_CATEGORY_ICONS, STARTUP_DATA_SOURCE, SavedSearch, SavedSearchDialog, SavedSearchPopover, SavedSearchesComponent, SavedSearchesService, SearchBoxComponent, 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, groupSearchesByDate, injectAdvancedFilters, injectAtomicClient, injectRouteNavigation, injectUserSettingsPatch, isAuthRoute, isSignInInFlight, itemLabel, layoutWordCloud, legacyFilterToFilter, makeWeightScale, multiSelectionToolbarVariants, paletteColor, processCssVars, provideAtomicClient, provideAtomicNotifications, provideAtomicScope, provideDocumentLang, provideKeyboardShortcuts, provideSessionExpiry, provideStartupAggregation, quantileScale, queryNameResolver, rebuildWithout, searchItemKey, seriesPalette, signIn, themeColorNameToCssVariable, themeColorsToCssVariables, timelinePalette, toAriaKeyshortcuts, toDisplayParts, toPercentages, toSeries, toastInterceptorFn, weekdaysFromFlags, withAggregationsFeatures, withAlertsFeatures, withAlpha, withAppFeatures, withApplicationFeatures, withAssistantFeatures, withBasketsFeatures, withBookmarkFeatures, withBootstrapApp, withExtractsFeatures, withFavoriteSearchesFeatures, withFetch, withMultiSelectionFeatures, withQueryParamsFeatures, withQuickFiltersFeatures, withRecentSearchesFeatures, withSavedSearchesFeatures, withSelectionFeatures, withThemeBodyHook, withThemes, withThemesFeatures, withUserSettingsFeatures };
|
|
38864
|
+
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, FiltersPanelComponent, FloatingToolbarComponent, ForgotPasswordComponent, HIGHLIGHTS, HeatmapComponent, HighlightWordPipe, HistoryPanelComponent, HistoryTitleMarqueeDirective, InfinityScrollDirective, InlineWorker, JsonMethodPluginService, KeyboardShortcutsDialog, KeyboardShortcutsDialogComponent, KeyboardShortcutsListComponent, KeyboardShortcutsService, LabelService, LabelsEditDialog, ListPanelItemDirective, LoadingComponent, LocalizedNumberPipe, 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, QuickFiltersComponent, QuickFiltersListComponent, RECENT_SEARCHES_CONFIG, RECENT_SEARCHES_OPTIONS, ROUTE_COMPONENTS, RecentSearchesComponent, ResetUserSettings, ResetUserSettingsDialogComponent, SAVED_SEARCHES_CONFIG, SAVED_SEARCHES_OPTIONS, SEARCH_BOX_CATEGORIES_SORT, SEARCH_BOX_CATEGORY_ICONS, STARTUP_DATA_SOURCE, SavedSearch, SavedSearchDialog, SavedSearchPopover, SavedSearchesComponent, SavedSearchesService, SearchBoxComponent, 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, groupSearchesByDate, injectAdvancedFilters, injectAtomicClient, injectRouteNavigation, injectUserSettingsPatch, isAuthRoute, isSignInInFlight, itemLabel, layoutWordCloud, legacyFilterToFilter, makeWeightScale, multiSelectionToolbarVariants, paletteColor, processCssVars, provideAtomicClient, provideAtomicNotifications, provideAtomicScope, provideDocumentLang, provideKeyboardShortcuts, provideSessionExpiry, provideStartupAggregation, quantileScale, queryNameResolver, rebuildWithout, searchItemKey, seriesPalette, signIn, themeColorNameToCssVariable, themeColorsToCssVariables, timelinePalette, toAriaKeyshortcuts, toDisplayParts, toPercentages, toSeries, toastInterceptorFn, weekdaysFromFlags, withAggregationsFeatures, withAlertsFeatures, withAlpha, withAppFeatures, withApplicationFeatures, withAssistantFeatures, withBasketsFeatures, withBookmarkFeatures, withBootstrapApp, withExtractsFeatures, withFavoriteSearchesFeatures, withFetch, withMultiSelectionFeatures, withQueryParamsFeatures, withQuickFiltersFeatures, withRecentSearchesFeatures, withSavedSearchesFeatures, withSelectionFeatures, withThemeBodyHook, withThemes, withThemesFeatures, withUserSettingsFeatures };
|
|
37787
38865
|
|
|
37788
38866
|
//# sourceMappingURL=sinequa-atomic-angular.mjs.map
|