@foxeltech/angular-ui 0.7.83 → 0.7.101
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/components.css +40 -1
- package/fesm2022/foxeltech-angular-ui-chart.mjs +33 -3
- package/fesm2022/foxeltech-angular-ui-chart.mjs.map +1 -1
- package/fesm2022/foxeltech-angular-ui-flow.mjs +42 -5
- package/fesm2022/foxeltech-angular-ui-flow.mjs.map +1 -1
- package/fesm2022/foxeltech-angular-ui.mjs +559 -42
- package/fesm2022/foxeltech-angular-ui.mjs.map +1 -1
- package/package.json +3 -2
- package/src/lib/styles/components.css +40 -1
- package/src/lib/styles/utilities.css +2 -1
- package/src/lib/ui/components/breadcrumb/breadcrumb.component.css +262 -0
- package/src/lib/ui/components/breadcrumb/breadcrumb.component.html +91 -0
- package/src/lib/ui/components/button/button.component.html +1 -0
- package/src/lib/ui/components/filter-pills/filter-pills.component.html +1 -1
- package/src/lib/ui/components/load-status/load-status.component.css +96 -0
- package/src/lib/ui/components/load-status/load-status.component.html +20 -0
- package/src/lib/ui/components/master-detail/master-detail.component.css +63 -11
- package/src/lib/ui/components/master-detail/master-detail.component.html +71 -42
- package/src/lib/ui/components/route-skeleton/route-skeleton.component.css +1 -1
- package/src/lib/ui/components/segmented/segmented.component.html +3 -1
- package/src/lib/ui/components/severity-badges/severity-badges.component.html +1 -1
- package/src/lib/ui/components/stat-cards/stat-cards.component.css +18 -0
- package/src/lib/ui/components/stat-cards/stat-cards.component.html +3 -3
- package/types/foxeltech-angular-ui-chart.d.ts +18 -1
- package/types/foxeltech-angular-ui-flow.d.ts +7 -0
- package/types/foxeltech-angular-ui.d.ts +309 -8
- package/utilities.css +2 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, DestroyRef, Injector, Directive, InjectionToken, input, isDevMode, ChangeDetectionStrategy, Component, Input, createComponent,
|
|
2
|
+
import { inject, DestroyRef, Injector, Directive, signal, computed, Injectable, InjectionToken, input, isDevMode, ChangeDetectionStrategy, Component, Input, createComponent, viewChild, ViewChild, Inject, EventEmitter, NgModule, HostListener, output, ChangeDetectorRef, contentChild, TemplateRef, contentChildren, Output, ViewChildren, ElementRef, effect, linkedSignal, model, viewChildren, afterRenderEffect, untracked, afterNextRender } from '@angular/core';
|
|
3
3
|
import { MatPaginator } from '@angular/material/paginator';
|
|
4
4
|
import { MatTableDataSource, MatTable } from '@angular/material/table';
|
|
5
|
+
import { Subject, Observable, firstValueFrom, debounceTime, distinctUntilChanged, BehaviorSubject, throwError, filter, takeUntil } from 'rxjs';
|
|
5
6
|
import { NgClass, NgStyle, NgTemplateOutlet } from '@angular/common';
|
|
6
|
-
import { Observable, firstValueFrom, Subject, debounceTime, distinctUntilChanged, BehaviorSubject, throwError, filter, takeUntil } from 'rxjs';
|
|
7
7
|
import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
|
|
8
8
|
import * as i2 from '@angular/common/http';
|
|
9
9
|
import { HttpRequest, HttpClient } from '@angular/common/http';
|
|
@@ -23,11 +23,12 @@ import * as i1$2 from '@angular/material/datepicker';
|
|
|
23
23
|
import { MatDatepickerModule } from '@angular/material/datepicker';
|
|
24
24
|
import * as i2$2 from '@angular/material/timepicker';
|
|
25
25
|
import { MatTimepickerModule } from '@angular/material/timepicker';
|
|
26
|
-
import {
|
|
26
|
+
import { provideNativeDateAdapter, MAT_DATE_LOCALE } from '@angular/material/core';
|
|
27
27
|
import { MatTooltip } from '@angular/material/tooltip';
|
|
28
28
|
import { MatFormField, MatSelect, MatOption } from '@angular/material/select';
|
|
29
29
|
import { moveItemInArray, transferArrayItem, CdkDropList, CdkDrag, CdkDragPlaceholder } from '@angular/cdk/drag-drop';
|
|
30
|
-
import { trigger, state,
|
|
30
|
+
import { trigger, state, transition, style, animate } from '@angular/animations';
|
|
31
|
+
import gsap from 'gsap';
|
|
31
32
|
import * as i1$4 from '@angular/material/menu';
|
|
32
33
|
import { MatMenuModule } from '@angular/material/menu';
|
|
33
34
|
|
|
@@ -50,6 +51,81 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImpo
|
|
|
50
51
|
type: Directive
|
|
51
52
|
}], ctorParameters: () => [{ type: i0.Injector }] });
|
|
52
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Backend-request bookkeeping behind the topbar load status (design
|
|
56
|
+
* "Loading States", 04/09): while any tracked request is in flight the shell
|
|
57
|
+
* shows a spinner + "Loading <label> · k of n" and a thin indeterminate bar;
|
|
58
|
+
* once the batch settles it shows "● Up to date · just now".
|
|
59
|
+
*
|
|
60
|
+
* A batch = every job started while at least one job is still pending. When
|
|
61
|
+
* the last one settles the batch closes (`lastUpdated` moves) and the next
|
|
62
|
+
* job opens a fresh count — so "k of n" always reads against the requests of
|
|
63
|
+
* the current page load, not the session total.
|
|
64
|
+
*
|
|
65
|
+
* Wire-ups: `fxListResolver` and `BaseTableComponent.fetch()` track
|
|
66
|
+
* automatically; screens with their own fetches call `track()`.
|
|
67
|
+
*/
|
|
68
|
+
class FxLoadStatusService {
|
|
69
|
+
seq = 0;
|
|
70
|
+
_pending = signal([], ...(ngDevMode ? [{ debugName: "_pending" }] : /* istanbul ignore next */ []));
|
|
71
|
+
_batchTotal = signal(0, ...(ngDevMode ? [{ debugName: "_batchTotal" }] : /* istanbul ignore next */ []));
|
|
72
|
+
_lastUpdated = signal(null, ...(ngDevMode ? [{ debugName: "_lastUpdated" }] : /* istanbul ignore next */ []));
|
|
73
|
+
_reload = new Subject();
|
|
74
|
+
/** Jobs still in flight, oldest first. */
|
|
75
|
+
pending = this._pending.asReadonly();
|
|
76
|
+
/** Requests started in the current batch. */
|
|
77
|
+
total = this._batchTotal.asReadonly();
|
|
78
|
+
done = computed(() => Math.max(0, this._batchTotal() - this._pending().length), ...(ngDevMode ? [{ debugName: "done" }] : /* istanbul ignore next */ []));
|
|
79
|
+
loading = computed(() => this._pending().length > 0, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
|
|
80
|
+
/** Label of the oldest pending request — what the status line names. */
|
|
81
|
+
current = computed(() => this._pending()[0]?.label ?? '', ...(ngDevMode ? [{ debugName: "current" }] : /* istanbul ignore next */ []));
|
|
82
|
+
/** When the last batch settled; null until the first one does. */
|
|
83
|
+
lastUpdated = this._lastUpdated.asReadonly();
|
|
84
|
+
/** Fired by the topbar ↻ — the shell decides how to re-fetch the route. */
|
|
85
|
+
reload$ = this._reload.asObservable();
|
|
86
|
+
/** Register a request; call the returned function when it settles. */
|
|
87
|
+
begin(label) {
|
|
88
|
+
const job = { id: ++this.seq, label };
|
|
89
|
+
if (this._pending().length === 0)
|
|
90
|
+
this._batchTotal.set(0);
|
|
91
|
+
this._batchTotal.update((n) => n + 1);
|
|
92
|
+
this._pending.update((list) => [...list, job]);
|
|
93
|
+
let ended = false;
|
|
94
|
+
return () => {
|
|
95
|
+
if (ended)
|
|
96
|
+
return;
|
|
97
|
+
ended = true;
|
|
98
|
+
this._pending.update((list) => list.filter((j) => j.id !== job.id));
|
|
99
|
+
if (this._pending().length === 0)
|
|
100
|
+
this._lastUpdated.set(new Date());
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/** Track a promise (or a factory producing one) under `label`. */
|
|
104
|
+
async track(label, work) {
|
|
105
|
+
const end = this.begin(label);
|
|
106
|
+
try {
|
|
107
|
+
return await (typeof work === 'function' ? work() : work);
|
|
108
|
+
}
|
|
109
|
+
finally {
|
|
110
|
+
end();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
requestReload() {
|
|
114
|
+
this._reload.next();
|
|
115
|
+
}
|
|
116
|
+
/** Short label from a request url: `/TEST1/history` → "history", `/search` → "search". */
|
|
117
|
+
static labelFromUrl(url) {
|
|
118
|
+
const parts = String(url).split('?')[0].split('/').filter(Boolean);
|
|
119
|
+
return parts[parts.length - 1] ?? 'data';
|
|
120
|
+
}
|
|
121
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: FxLoadStatusService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
122
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: FxLoadStatusService, providedIn: 'root' });
|
|
123
|
+
}
|
|
124
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: FxLoadStatusService, decorators: [{
|
|
125
|
+
type: Injectable,
|
|
126
|
+
args: [{ providedIn: 'root' }]
|
|
127
|
+
}] });
|
|
128
|
+
|
|
53
129
|
/**
|
|
54
130
|
* Where the generated Heroicons sprite is served from. Defaults to
|
|
55
131
|
* 'fx-icons.svg' at the app base — consumers copy it from the package via an
|
|
@@ -686,11 +762,38 @@ class BaseTableComponent extends BaseComponent {
|
|
|
686
762
|
loading = false;
|
|
687
763
|
expandedElement;
|
|
688
764
|
toastr;
|
|
765
|
+
/** Topbar load status; optional so hand-rolled test injectors keep working. */
|
|
766
|
+
loadStatus = null;
|
|
689
767
|
_lock = false;
|
|
690
768
|
keywordChange$ = new Subject();
|
|
691
769
|
dataSourcePlaceholder;
|
|
692
770
|
config;
|
|
693
|
-
|
|
771
|
+
/**
|
|
772
|
+
* The paging engine. Either the page's own <mat-paginator> (found by the
|
|
773
|
+
* ViewChild query) or one handed in by a child such as fx-ui-master-detail,
|
|
774
|
+
* whose paginator lives in ITS view where the query cannot see it.
|
|
775
|
+
*
|
|
776
|
+
* ⚠️ A plain `@ViewChild` property was clobbered: the query re-runs before
|
|
777
|
+
* every ngAfterViewInit and, finding nothing, wrote `undefined` over the
|
|
778
|
+
* instance the child had assigned a moment earlier — so page events were
|
|
779
|
+
* never subscribed and Prev/Next changed the range text but not the rows
|
|
780
|
+
* (anh Tú 04/09). The setter attaches on any real assignment and ignores
|
|
781
|
+
* `undefined`, so whichever side provides the paginator wins.
|
|
782
|
+
*/
|
|
783
|
+
set paginator(p) {
|
|
784
|
+
if (!p || p === this._paginator)
|
|
785
|
+
return;
|
|
786
|
+
this._paginator = p;
|
|
787
|
+
this._pageSub?.unsubscribe();
|
|
788
|
+
this._pageSub = p.page.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((event) => {
|
|
789
|
+
void this.onPageChange(event);
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
get paginator() {
|
|
793
|
+
return this._paginator;
|
|
794
|
+
}
|
|
795
|
+
_paginator;
|
|
796
|
+
_pageSub;
|
|
694
797
|
table = viewChild.required(MatTable);
|
|
695
798
|
constructor(injector, api, modal, ref) {
|
|
696
799
|
super(injector);
|
|
@@ -698,6 +801,7 @@ class BaseTableComponent extends BaseComponent {
|
|
|
698
801
|
this.modal = modal;
|
|
699
802
|
this.ref = ref;
|
|
700
803
|
this.toastr = injector.get(FxToastrService);
|
|
804
|
+
this.loadStatus = injector.get(FxLoadStatusService, null);
|
|
701
805
|
}
|
|
702
806
|
ngOnInit() {
|
|
703
807
|
this.keywordChange$
|
|
@@ -710,11 +814,7 @@ class BaseTableComponent extends BaseComponent {
|
|
|
710
814
|
}
|
|
711
815
|
init() { }
|
|
712
816
|
ngAfterViewInit() {
|
|
713
|
-
|
|
714
|
-
this.paginator.page.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((event) => {
|
|
715
|
-
this.onPageChange(event);
|
|
716
|
-
});
|
|
717
|
-
}
|
|
817
|
+
// Page events are wired by the `paginator` setter (see above).
|
|
718
818
|
this.ref.detectChanges();
|
|
719
819
|
}
|
|
720
820
|
async refresh() {
|
|
@@ -746,6 +846,7 @@ class BaseTableComponent extends BaseComponent {
|
|
|
746
846
|
// a newer one and overwrite the table with stale data.
|
|
747
847
|
const seq = ++this._fetchSeq;
|
|
748
848
|
this.loading = true;
|
|
849
|
+
const endStatus = this.loadStatus?.begin(this.listKey) ?? null;
|
|
749
850
|
try {
|
|
750
851
|
const params = { page: (this.page ?? 0) + 1, pageSize: this.pageSize, ...this.filters };
|
|
751
852
|
const req = this.listRequest();
|
|
@@ -764,6 +865,7 @@ class BaseTableComponent extends BaseComponent {
|
|
|
764
865
|
this.toastr.error(FxUtils.parseError(err));
|
|
765
866
|
}
|
|
766
867
|
finally {
|
|
868
|
+
endStatus?.();
|
|
767
869
|
if (seq === this._fetchSeq) {
|
|
768
870
|
this.loading = false;
|
|
769
871
|
this.ref.detectChanges();
|
|
@@ -1346,9 +1448,13 @@ function fxListResolver(apiType, request) {
|
|
|
1346
1448
|
const api = inject(apiType);
|
|
1347
1449
|
const toastr = inject(FxToastrService);
|
|
1348
1450
|
const storage = inject(FxStorageService);
|
|
1451
|
+
const status = inject(FxLoadStatusService);
|
|
1452
|
+
let end = null;
|
|
1349
1453
|
try {
|
|
1350
1454
|
const ctx = { pageSize: storage.resolve(state.url) || 10 };
|
|
1351
1455
|
const req = request(route, state, ctx);
|
|
1456
|
+
// Topbar "Loading <x> · k of n" — one job per resolver request.
|
|
1457
|
+
end = status.begin(FxLoadStatusService.labelFromUrl(req.url));
|
|
1352
1458
|
if ((req.method ?? 'get') === 'post') {
|
|
1353
1459
|
return await api.post(req.url, req.body ?? req.params ?? {});
|
|
1354
1460
|
}
|
|
@@ -1358,6 +1464,9 @@ function fxListResolver(apiType, request) {
|
|
|
1358
1464
|
toastr.error(FxUtils.parseError(err), 'Error');
|
|
1359
1465
|
return null;
|
|
1360
1466
|
}
|
|
1467
|
+
finally {
|
|
1468
|
+
end?.();
|
|
1469
|
+
}
|
|
1361
1470
|
};
|
|
1362
1471
|
}
|
|
1363
1472
|
|
|
@@ -2470,11 +2579,11 @@ class ButtonComponent {
|
|
|
2470
2579
|
this.clicked.emit();
|
|
2471
2580
|
}
|
|
2472
2581
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: ButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2473
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: ButtonComponent, isStandalone: true, selector: "fx-ui-button", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: false, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, buttonVariant: { classPropertyName: "buttonVariant", publicName: "buttonVariant", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: false, isRequired: false, transformFunction: null }, class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { clicked: "clicked" }, ngImport: i0, template: "<button\n [attr.type]=\"type()\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-busy]=\"loading() || null\"\n [class]=\"class()\"\n [ngClass]=\"{\n 'btn-default': buttonVariant() === 'default',\n 'btn-primary': buttonVariant() === 'primary',\n 'btn-success': buttonVariant() === 'success',\n 'btn-cta': buttonVariant() === 'cta',\n 'btn-alternative': buttonVariant() === 'alternative',\n }\"\n [disabled]=\"disabled() || loading()\"\n (click)=\"onClick()\"\n>\n @if (loading()) {\n <div class=\"flex w-full items-center justify-center\" aria-hidden=\"true\">\n <svg\n [attr.fill]=\"\n buttonVariant() !== 'alternative' ? 'rgb(var(--text-primary))' : 'rgb(var(--primary))'\n \"\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <circle cx=\"4\" cy=\"12\" r=\"0\">\n <animate\n begin=\"0;spinner_z0Or.end\"\n attributeName=\"r\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"0;3\"\n fill=\"freeze\"\n />\n <animate\n begin=\"spinner_OLMs.end\"\n attributeName=\"cx\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"4;12\"\n fill=\"freeze\"\n />\n <animate\n begin=\"spinner_UHR2.end\"\n attributeName=\"cx\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"12;20\"\n fill=\"freeze\"\n />\n <animate\n id=\"spinner_lo66\"\n begin=\"spinner_Aguh.end\"\n attributeName=\"r\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"3;0\"\n fill=\"freeze\"\n />\n <animate\n id=\"spinner_z0Or\"\n begin=\"spinner_lo66.end\"\n attributeName=\"cx\"\n dur=\"0.001s\"\n values=\"20;4\"\n fill=\"freeze\"\n />\n </circle>\n <circle cx=\"4\" cy=\"12\" r=\"3\">\n <animate\n begin=\"0;spinner_z0Or.end\"\n attributeName=\"cx\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"4;12\"\n fill=\"freeze\"\n />\n <animate\n begin=\"spinner_OLMs.end\"\n attributeName=\"cx\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"12;20\"\n fill=\"freeze\"\n />\n <animate\n id=\"spinner_JsnR\"\n begin=\"spinner_UHR2.end\"\n attributeName=\"r\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"3;0\"\n fill=\"freeze\"\n />\n <animate\n id=\"spinner_Aguh\"\n begin=\"spinner_JsnR.end\"\n attributeName=\"cx\"\n dur=\"0.001s\"\n values=\"20;4\"\n fill=\"freeze\"\n />\n <animate\n begin=\"spinner_Aguh.end\"\n attributeName=\"r\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"0;3\"\n fill=\"freeze\"\n />\n </circle>\n <circle cx=\"12\" cy=\"12\" r=\"3\">\n <animate\n begin=\"0;spinner_z0Or.end\"\n attributeName=\"cx\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"12;20\"\n fill=\"freeze\"\n />\n <animate\n id=\"spinner_hSjk\"\n begin=\"spinner_OLMs.end\"\n attributeName=\"r\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"3;0\"\n fill=\"freeze\"\n />\n <animate\n id=\"spinner_UHR2\"\n begin=\"spinner_hSjk.end\"\n attributeName=\"cx\"\n dur=\"0.001s\"\n values=\"20;4\"\n fill=\"freeze\"\n />\n <animate\n begin=\"spinner_UHR2.end\"\n attributeName=\"r\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"0;3\"\n fill=\"freeze\"\n />\n <animate\n begin=\"spinner_Aguh.end\"\n attributeName=\"cx\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"4;12\"\n fill=\"freeze\"\n />\n </circle>\n <circle cx=\"20\" cy=\"12\" r=\"3\">\n <animate\n id=\"spinner_4v5M\"\n begin=\"0;spinner_z0Or.end\"\n attributeName=\"r\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"3;0\"\n fill=\"freeze\"\n />\n <animate\n id=\"spinner_OLMs\"\n begin=\"spinner_4v5M.end\"\n attributeName=\"cx\"\n dur=\"0.001s\"\n values=\"20;4\"\n fill=\"freeze\"\n />\n <animate\n begin=\"spinner_OLMs.end\"\n attributeName=\"r\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"0;3\"\n fill=\"freeze\"\n />\n <animate\n begin=\"spinner_UHR2.end\"\n attributeName=\"cx\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"4;12\"\n fill=\"freeze\"\n />\n <animate\n begin=\"spinner_Aguh.end\"\n attributeName=\"cx\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"12;20\"\n fill=\"freeze\"\n />\n </circle>\n </svg>\n </div>\n } @else {\n <ng-container>\n <div class=\"flex items-center justify-center gap-small\">\n @if (icon) {\n <fx-ui-hero-icon\n [icon]=\"icon\"\n [size]=\"16\"\n class=\"flex\"\n [ngClass]=\"{\n 'text-text-inverse': buttonVariant() === 'default',\n 'text-text-primary': buttonVariant() === 'alternative',\n 'text-accent-contrast': buttonVariant() === 'primary',\n 'text-surface': buttonVariant() === 'success',\n 'text-white': buttonVariant() === 'cta',\n }\"\n ></fx-ui-hero-icon>\n }\n @if (label) {\n <!-- text-box: optical centering \u2014 Archivo's leading floats ink up otherwise -->\n <div class=\"[text-box:trim-both_cap_alphabetic]\">{{ label }}</div>\n }\n </div>\n </ng-container>\n }\n</button>\n", styles: [""], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: HeroIconComponent, selector: "fx-ui-hero-icon", inputs: ["stroke", "icon", "solid", "outline", "size", "color", "class"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2582
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: ButtonComponent, isStandalone: true, selector: "fx-ui-button", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: false, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, buttonVariant: { classPropertyName: "buttonVariant", publicName: "buttonVariant", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: false, isRequired: false, transformFunction: null }, class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { clicked: "clicked" }, ngImport: i0, template: "<button\n [attr.type]=\"type()\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-busy]=\"loading() || null\"\n [class]=\"class()\"\n [ngClass]=\"{\n 'fx-btn--icon': !!icon && !label,\n 'btn-default': buttonVariant() === 'default',\n 'btn-primary': buttonVariant() === 'primary',\n 'btn-success': buttonVariant() === 'success',\n 'btn-cta': buttonVariant() === 'cta',\n 'btn-alternative': buttonVariant() === 'alternative',\n }\"\n [disabled]=\"disabled() || loading()\"\n (click)=\"onClick()\"\n>\n @if (loading()) {\n <div class=\"flex w-full items-center justify-center\" aria-hidden=\"true\">\n <svg\n [attr.fill]=\"\n buttonVariant() !== 'alternative' ? 'rgb(var(--text-primary))' : 'rgb(var(--primary))'\n \"\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <circle cx=\"4\" cy=\"12\" r=\"0\">\n <animate\n begin=\"0;spinner_z0Or.end\"\n attributeName=\"r\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"0;3\"\n fill=\"freeze\"\n />\n <animate\n begin=\"spinner_OLMs.end\"\n attributeName=\"cx\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"4;12\"\n fill=\"freeze\"\n />\n <animate\n begin=\"spinner_UHR2.end\"\n attributeName=\"cx\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"12;20\"\n fill=\"freeze\"\n />\n <animate\n id=\"spinner_lo66\"\n begin=\"spinner_Aguh.end\"\n attributeName=\"r\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"3;0\"\n fill=\"freeze\"\n />\n <animate\n id=\"spinner_z0Or\"\n begin=\"spinner_lo66.end\"\n attributeName=\"cx\"\n dur=\"0.001s\"\n values=\"20;4\"\n fill=\"freeze\"\n />\n </circle>\n <circle cx=\"4\" cy=\"12\" r=\"3\">\n <animate\n begin=\"0;spinner_z0Or.end\"\n attributeName=\"cx\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"4;12\"\n fill=\"freeze\"\n />\n <animate\n begin=\"spinner_OLMs.end\"\n attributeName=\"cx\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"12;20\"\n fill=\"freeze\"\n />\n <animate\n id=\"spinner_JsnR\"\n begin=\"spinner_UHR2.end\"\n attributeName=\"r\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"3;0\"\n fill=\"freeze\"\n />\n <animate\n id=\"spinner_Aguh\"\n begin=\"spinner_JsnR.end\"\n attributeName=\"cx\"\n dur=\"0.001s\"\n values=\"20;4\"\n fill=\"freeze\"\n />\n <animate\n begin=\"spinner_Aguh.end\"\n attributeName=\"r\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"0;3\"\n fill=\"freeze\"\n />\n </circle>\n <circle cx=\"12\" cy=\"12\" r=\"3\">\n <animate\n begin=\"0;spinner_z0Or.end\"\n attributeName=\"cx\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"12;20\"\n fill=\"freeze\"\n />\n <animate\n id=\"spinner_hSjk\"\n begin=\"spinner_OLMs.end\"\n attributeName=\"r\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"3;0\"\n fill=\"freeze\"\n />\n <animate\n id=\"spinner_UHR2\"\n begin=\"spinner_hSjk.end\"\n attributeName=\"cx\"\n dur=\"0.001s\"\n values=\"20;4\"\n fill=\"freeze\"\n />\n <animate\n begin=\"spinner_UHR2.end\"\n attributeName=\"r\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"0;3\"\n fill=\"freeze\"\n />\n <animate\n begin=\"spinner_Aguh.end\"\n attributeName=\"cx\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"4;12\"\n fill=\"freeze\"\n />\n </circle>\n <circle cx=\"20\" cy=\"12\" r=\"3\">\n <animate\n id=\"spinner_4v5M\"\n begin=\"0;spinner_z0Or.end\"\n attributeName=\"r\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"3;0\"\n fill=\"freeze\"\n />\n <animate\n id=\"spinner_OLMs\"\n begin=\"spinner_4v5M.end\"\n attributeName=\"cx\"\n dur=\"0.001s\"\n values=\"20;4\"\n fill=\"freeze\"\n />\n <animate\n begin=\"spinner_OLMs.end\"\n attributeName=\"r\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"0;3\"\n fill=\"freeze\"\n />\n <animate\n begin=\"spinner_UHR2.end\"\n attributeName=\"cx\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"4;12\"\n fill=\"freeze\"\n />\n <animate\n begin=\"spinner_Aguh.end\"\n attributeName=\"cx\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"12;20\"\n fill=\"freeze\"\n />\n </circle>\n </svg>\n </div>\n } @else {\n <ng-container>\n <div class=\"flex items-center justify-center gap-small\">\n @if (icon) {\n <fx-ui-hero-icon\n [icon]=\"icon\"\n [size]=\"16\"\n class=\"flex\"\n [ngClass]=\"{\n 'text-text-inverse': buttonVariant() === 'default',\n 'text-text-primary': buttonVariant() === 'alternative',\n 'text-accent-contrast': buttonVariant() === 'primary',\n 'text-surface': buttonVariant() === 'success',\n 'text-white': buttonVariant() === 'cta',\n }\"\n ></fx-ui-hero-icon>\n }\n @if (label) {\n <!-- text-box: optical centering \u2014 Archivo's leading floats ink up otherwise -->\n <div class=\"[text-box:trim-both_cap_alphabetic]\">{{ label }}</div>\n }\n </div>\n </ng-container>\n }\n</button>\n", styles: [""], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: HeroIconComponent, selector: "fx-ui-hero-icon", inputs: ["stroke", "icon", "solid", "outline", "size", "color", "class"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2474
2583
|
}
|
|
2475
2584
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: ButtonComponent, decorators: [{
|
|
2476
2585
|
type: Component,
|
|
2477
|
-
args: [{ selector: 'fx-ui-button', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgClass, HeroIconComponent], template: "<button\n [attr.type]=\"type()\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-busy]=\"loading() || null\"\n [class]=\"class()\"\n [ngClass]=\"{\n 'btn-default': buttonVariant() === 'default',\n 'btn-primary': buttonVariant() === 'primary',\n 'btn-success': buttonVariant() === 'success',\n 'btn-cta': buttonVariant() === 'cta',\n 'btn-alternative': buttonVariant() === 'alternative',\n }\"\n [disabled]=\"disabled() || loading()\"\n (click)=\"onClick()\"\n>\n @if (loading()) {\n <div class=\"flex w-full items-center justify-center\" aria-hidden=\"true\">\n <svg\n [attr.fill]=\"\n buttonVariant() !== 'alternative' ? 'rgb(var(--text-primary))' : 'rgb(var(--primary))'\n \"\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <circle cx=\"4\" cy=\"12\" r=\"0\">\n <animate\n begin=\"0;spinner_z0Or.end\"\n attributeName=\"r\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"0;3\"\n fill=\"freeze\"\n />\n <animate\n begin=\"spinner_OLMs.end\"\n attributeName=\"cx\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"4;12\"\n fill=\"freeze\"\n />\n <animate\n begin=\"spinner_UHR2.end\"\n attributeName=\"cx\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"12;20\"\n fill=\"freeze\"\n />\n <animate\n id=\"spinner_lo66\"\n begin=\"spinner_Aguh.end\"\n attributeName=\"r\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"3;0\"\n fill=\"freeze\"\n />\n <animate\n id=\"spinner_z0Or\"\n begin=\"spinner_lo66.end\"\n attributeName=\"cx\"\n dur=\"0.001s\"\n values=\"20;4\"\n fill=\"freeze\"\n />\n </circle>\n <circle cx=\"4\" cy=\"12\" r=\"3\">\n <animate\n begin=\"0;spinner_z0Or.end\"\n attributeName=\"cx\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"4;12\"\n fill=\"freeze\"\n />\n <animate\n begin=\"spinner_OLMs.end\"\n attributeName=\"cx\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"12;20\"\n fill=\"freeze\"\n />\n <animate\n id=\"spinner_JsnR\"\n begin=\"spinner_UHR2.end\"\n attributeName=\"r\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"3;0\"\n fill=\"freeze\"\n />\n <animate\n id=\"spinner_Aguh\"\n begin=\"spinner_JsnR.end\"\n attributeName=\"cx\"\n dur=\"0.001s\"\n values=\"20;4\"\n fill=\"freeze\"\n />\n <animate\n begin=\"spinner_Aguh.end\"\n attributeName=\"r\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"0;3\"\n fill=\"freeze\"\n />\n </circle>\n <circle cx=\"12\" cy=\"12\" r=\"3\">\n <animate\n begin=\"0;spinner_z0Or.end\"\n attributeName=\"cx\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"12;20\"\n fill=\"freeze\"\n />\n <animate\n id=\"spinner_hSjk\"\n begin=\"spinner_OLMs.end\"\n attributeName=\"r\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"3;0\"\n fill=\"freeze\"\n />\n <animate\n id=\"spinner_UHR2\"\n begin=\"spinner_hSjk.end\"\n attributeName=\"cx\"\n dur=\"0.001s\"\n values=\"20;4\"\n fill=\"freeze\"\n />\n <animate\n begin=\"spinner_UHR2.end\"\n attributeName=\"r\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"0;3\"\n fill=\"freeze\"\n />\n <animate\n begin=\"spinner_Aguh.end\"\n attributeName=\"cx\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"4;12\"\n fill=\"freeze\"\n />\n </circle>\n <circle cx=\"20\" cy=\"12\" r=\"3\">\n <animate\n id=\"spinner_4v5M\"\n begin=\"0;spinner_z0Or.end\"\n attributeName=\"r\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"3;0\"\n fill=\"freeze\"\n />\n <animate\n id=\"spinner_OLMs\"\n begin=\"spinner_4v5M.end\"\n attributeName=\"cx\"\n dur=\"0.001s\"\n values=\"20;4\"\n fill=\"freeze\"\n />\n <animate\n begin=\"spinner_OLMs.end\"\n attributeName=\"r\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"0;3\"\n fill=\"freeze\"\n />\n <animate\n begin=\"spinner_UHR2.end\"\n attributeName=\"cx\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"4;12\"\n fill=\"freeze\"\n />\n <animate\n begin=\"spinner_Aguh.end\"\n attributeName=\"cx\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"12;20\"\n fill=\"freeze\"\n />\n </circle>\n </svg>\n </div>\n } @else {\n <ng-container>\n <div class=\"flex items-center justify-center gap-small\">\n @if (icon) {\n <fx-ui-hero-icon\n [icon]=\"icon\"\n [size]=\"16\"\n class=\"flex\"\n [ngClass]=\"{\n 'text-text-inverse': buttonVariant() === 'default',\n 'text-text-primary': buttonVariant() === 'alternative',\n 'text-accent-contrast': buttonVariant() === 'primary',\n 'text-surface': buttonVariant() === 'success',\n 'text-white': buttonVariant() === 'cta',\n }\"\n ></fx-ui-hero-icon>\n }\n @if (label) {\n <!-- text-box: optical centering \u2014 Archivo's leading floats ink up otherwise -->\n <div class=\"[text-box:trim-both_cap_alphabetic]\">{{ label }}</div>\n }\n </div>\n </ng-container>\n }\n</button>\n" }]
|
|
2586
|
+
args: [{ selector: 'fx-ui-button', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgClass, HeroIconComponent], template: "<button\n [attr.type]=\"type()\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-busy]=\"loading() || null\"\n [class]=\"class()\"\n [ngClass]=\"{\n 'fx-btn--icon': !!icon && !label,\n 'btn-default': buttonVariant() === 'default',\n 'btn-primary': buttonVariant() === 'primary',\n 'btn-success': buttonVariant() === 'success',\n 'btn-cta': buttonVariant() === 'cta',\n 'btn-alternative': buttonVariant() === 'alternative',\n }\"\n [disabled]=\"disabled() || loading()\"\n (click)=\"onClick()\"\n>\n @if (loading()) {\n <div class=\"flex w-full items-center justify-center\" aria-hidden=\"true\">\n <svg\n [attr.fill]=\"\n buttonVariant() !== 'alternative' ? 'rgb(var(--text-primary))' : 'rgb(var(--primary))'\n \"\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <circle cx=\"4\" cy=\"12\" r=\"0\">\n <animate\n begin=\"0;spinner_z0Or.end\"\n attributeName=\"r\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"0;3\"\n fill=\"freeze\"\n />\n <animate\n begin=\"spinner_OLMs.end\"\n attributeName=\"cx\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"4;12\"\n fill=\"freeze\"\n />\n <animate\n begin=\"spinner_UHR2.end\"\n attributeName=\"cx\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"12;20\"\n fill=\"freeze\"\n />\n <animate\n id=\"spinner_lo66\"\n begin=\"spinner_Aguh.end\"\n attributeName=\"r\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"3;0\"\n fill=\"freeze\"\n />\n <animate\n id=\"spinner_z0Or\"\n begin=\"spinner_lo66.end\"\n attributeName=\"cx\"\n dur=\"0.001s\"\n values=\"20;4\"\n fill=\"freeze\"\n />\n </circle>\n <circle cx=\"4\" cy=\"12\" r=\"3\">\n <animate\n begin=\"0;spinner_z0Or.end\"\n attributeName=\"cx\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"4;12\"\n fill=\"freeze\"\n />\n <animate\n begin=\"spinner_OLMs.end\"\n attributeName=\"cx\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"12;20\"\n fill=\"freeze\"\n />\n <animate\n id=\"spinner_JsnR\"\n begin=\"spinner_UHR2.end\"\n attributeName=\"r\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"3;0\"\n fill=\"freeze\"\n />\n <animate\n id=\"spinner_Aguh\"\n begin=\"spinner_JsnR.end\"\n attributeName=\"cx\"\n dur=\"0.001s\"\n values=\"20;4\"\n fill=\"freeze\"\n />\n <animate\n begin=\"spinner_Aguh.end\"\n attributeName=\"r\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"0;3\"\n fill=\"freeze\"\n />\n </circle>\n <circle cx=\"12\" cy=\"12\" r=\"3\">\n <animate\n begin=\"0;spinner_z0Or.end\"\n attributeName=\"cx\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"12;20\"\n fill=\"freeze\"\n />\n <animate\n id=\"spinner_hSjk\"\n begin=\"spinner_OLMs.end\"\n attributeName=\"r\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"3;0\"\n fill=\"freeze\"\n />\n <animate\n id=\"spinner_UHR2\"\n begin=\"spinner_hSjk.end\"\n attributeName=\"cx\"\n dur=\"0.001s\"\n values=\"20;4\"\n fill=\"freeze\"\n />\n <animate\n begin=\"spinner_UHR2.end\"\n attributeName=\"r\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"0;3\"\n fill=\"freeze\"\n />\n <animate\n begin=\"spinner_Aguh.end\"\n attributeName=\"cx\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"4;12\"\n fill=\"freeze\"\n />\n </circle>\n <circle cx=\"20\" cy=\"12\" r=\"3\">\n <animate\n id=\"spinner_4v5M\"\n begin=\"0;spinner_z0Or.end\"\n attributeName=\"r\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"3;0\"\n fill=\"freeze\"\n />\n <animate\n id=\"spinner_OLMs\"\n begin=\"spinner_4v5M.end\"\n attributeName=\"cx\"\n dur=\"0.001s\"\n values=\"20;4\"\n fill=\"freeze\"\n />\n <animate\n begin=\"spinner_OLMs.end\"\n attributeName=\"r\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"0;3\"\n fill=\"freeze\"\n />\n <animate\n begin=\"spinner_UHR2.end\"\n attributeName=\"cx\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"4;12\"\n fill=\"freeze\"\n />\n <animate\n begin=\"spinner_Aguh.end\"\n attributeName=\"cx\"\n calcMode=\"spline\"\n dur=\"0.5s\"\n keySplines=\".36,.6,.31,1\"\n values=\"12;20\"\n fill=\"freeze\"\n />\n </circle>\n </svg>\n </div>\n } @else {\n <ng-container>\n <div class=\"flex items-center justify-center gap-small\">\n @if (icon) {\n <fx-ui-hero-icon\n [icon]=\"icon\"\n [size]=\"16\"\n class=\"flex\"\n [ngClass]=\"{\n 'text-text-inverse': buttonVariant() === 'default',\n 'text-text-primary': buttonVariant() === 'alternative',\n 'text-accent-contrast': buttonVariant() === 'primary',\n 'text-surface': buttonVariant() === 'success',\n 'text-white': buttonVariant() === 'cta',\n }\"\n ></fx-ui-hero-icon>\n }\n @if (label) {\n <!-- text-box: optical centering \u2014 Archivo's leading floats ink up otherwise -->\n <div class=\"[text-box:trim-both_cap_alphabetic]\">{{ label }}</div>\n }\n </div>\n </ng-container>\n }\n</button>\n" }]
|
|
2478
2587
|
}], propDecorators: { label: [{
|
|
2479
2588
|
type: Input
|
|
2480
2589
|
}], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], buttonVariant: [{ type: i0.Input, args: [{ isSignal: true, alias: "buttonVariant", required: false }] }], icon: [{
|
|
@@ -4098,6 +4207,77 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImpo
|
|
|
4098
4207
|
args: [{ selector: 'fx-ui-sparkbar', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgClass], template: "<div\n class=\"flex items-end gap-[2px]\"\n [style.height.px]=\"height()\"\n role=\"img\"\n [attr.aria-label]=\"'Trend: ' + values().join(', ')\"\n>\n @for (bar of bars(); track $index) {\n <div\n class=\"flex-1 min-w-[3px] rounded-[1px] opacity-80\"\n [ngClass]=\"barClass()\"\n [style.height.%]=\"bar\"\n ></div>\n }\n</div>\n" }]
|
|
4099
4208
|
}], propDecorators: { values: [{ type: i0.Input, args: [{ isSignal: true, alias: "values", required: false }] }], tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }] } });
|
|
4100
4209
|
|
|
4210
|
+
/** Users who asked the OS for reduced motion get instant state changes. */
|
|
4211
|
+
function fxMotionAllowed() {
|
|
4212
|
+
return (typeof window !== 'undefined' &&
|
|
4213
|
+
typeof window.matchMedia === 'function' &&
|
|
4214
|
+
!window.matchMedia('(prefers-reduced-motion: reduce)').matches);
|
|
4215
|
+
}
|
|
4216
|
+
/** Shared timing so every screen moves the same way. */
|
|
4217
|
+
const FX_MOTION = {
|
|
4218
|
+
duration: 0.5,
|
|
4219
|
+
/** Delay between consecutive slots. */
|
|
4220
|
+
stagger: 0.06,
|
|
4221
|
+
/** Never let a long list push the last item beyond this delay. */
|
|
4222
|
+
maxSlot: 12,
|
|
4223
|
+
travelPx: 12,
|
|
4224
|
+
ease: 'power2.out',
|
|
4225
|
+
};
|
|
4226
|
+
|
|
4227
|
+
/**
|
|
4228
|
+
* Counts a figure up from 0 (GSAP) whenever its value changes.
|
|
4229
|
+
*
|
|
4230
|
+
* <span [fxCountUp]="card.value"></span>
|
|
4231
|
+
*
|
|
4232
|
+
* Numbers tween over 0.8 s and render with the user's locale grouping; any
|
|
4233
|
+
* non-numeric value ('--', '98 / 100', '1,224') is written verbatim so pending
|
|
4234
|
+
* or composite figures never animate to nonsense. Reduced motion → final value.
|
|
4235
|
+
*/
|
|
4236
|
+
class FxCountUpDirective {
|
|
4237
|
+
fxCountUp = input(undefined, ...(ngDevMode ? [{ debugName: "fxCountUp" }] : /* istanbul ignore next */ []));
|
|
4238
|
+
el = inject(ElementRef).nativeElement;
|
|
4239
|
+
tween;
|
|
4240
|
+
state = { v: 0 };
|
|
4241
|
+
constructor() {
|
|
4242
|
+
effect(() => {
|
|
4243
|
+
const raw = this.fxCountUp();
|
|
4244
|
+
const n = typeof raw === 'number' ? raw : Number(raw);
|
|
4245
|
+
const numeric = raw !== '' && raw !== null && raw !== undefined && Number.isFinite(n);
|
|
4246
|
+
this.tween?.kill();
|
|
4247
|
+
if (!numeric) {
|
|
4248
|
+
this.el.textContent = raw === null || raw === undefined ? '' : String(raw);
|
|
4249
|
+
return;
|
|
4250
|
+
}
|
|
4251
|
+
if (!fxMotionAllowed()) {
|
|
4252
|
+
this.state.v = n;
|
|
4253
|
+
this.el.textContent = this.format(n);
|
|
4254
|
+
return;
|
|
4255
|
+
}
|
|
4256
|
+
this.tween = gsap.to(this.state, {
|
|
4257
|
+
v: n,
|
|
4258
|
+
duration: 0.8,
|
|
4259
|
+
ease: 'power2.out',
|
|
4260
|
+
onUpdate: () => (this.el.textContent = this.format(this.state.v)),
|
|
4261
|
+
onComplete: () => (this.el.textContent = this.format(n)),
|
|
4262
|
+
});
|
|
4263
|
+
});
|
|
4264
|
+
}
|
|
4265
|
+
format(v) {
|
|
4266
|
+
return Math.round(v).toLocaleString();
|
|
4267
|
+
}
|
|
4268
|
+
ngOnDestroy() {
|
|
4269
|
+
this.tween?.kill();
|
|
4270
|
+
}
|
|
4271
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: FxCountUpDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
4272
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.20", type: FxCountUpDirective, isStandalone: true, selector: "[fxCountUp]", inputs: { fxCountUp: { classPropertyName: "fxCountUp", publicName: "fxCountUp", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
|
|
4273
|
+
}
|
|
4274
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: FxCountUpDirective, decorators: [{
|
|
4275
|
+
type: Directive,
|
|
4276
|
+
args: [{
|
|
4277
|
+
selector: '[fxCountUp]',
|
|
4278
|
+
}]
|
|
4279
|
+
}], ctorParameters: () => [], propDecorators: { fxCountUp: [{ type: i0.Input, args: [{ isSignal: true, alias: "fxCountUp", required: false }] }] } });
|
|
4280
|
+
|
|
4101
4281
|
/** KPI tile row: big number + tinted icon box + caption, in a responsive grid. */
|
|
4102
4282
|
class StatCardsComponent {
|
|
4103
4283
|
cards = input([], ...(ngDevMode ? [{ debugName: "cards" }] : /* istanbul ignore next */ []));
|
|
@@ -4109,10 +4289,14 @@ class StatCardsComponent {
|
|
|
4109
4289
|
variant = input('tile', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
|
|
4110
4290
|
/** Grid columns; defaults to one column per card. */
|
|
4111
4291
|
cols;
|
|
4292
|
+
/**
|
|
4293
|
+
* Column count for the ≥ md (720px) container. Below that the CSS takes
|
|
4294
|
+
* over (2-up, then 1-up under 360px) — see stat-cards.component.css — so an
|
|
4295
|
+
* explicit `cols` never squeezes four figures into a phone-width row.
|
|
4296
|
+
*/
|
|
4112
4297
|
get gridColumns() {
|
|
4113
|
-
// No explicit cols → responsive auto-fit so tiles stack on narrow viewports.
|
|
4114
4298
|
if (this.cols === undefined) {
|
|
4115
|
-
return 'repeat(auto-fit, minmax(180px, 1fr))';
|
|
4299
|
+
return 'repeat(auto-fit, minmax(min(180px, 100%), 1fr))';
|
|
4116
4300
|
}
|
|
4117
4301
|
return `repeat(${this.cols}, minmax(0, 1fr))`;
|
|
4118
4302
|
}
|
|
@@ -4130,7 +4314,7 @@ class StatCardsComponent {
|
|
|
4130
4314
|
return card.subTone ? toneText(card.subTone) : 'text-text-secondary';
|
|
4131
4315
|
}
|
|
4132
4316
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: StatCardsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4133
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: StatCardsComponent, isStandalone: true, selector: "fx-ui-stat-cards", inputs: { cards: { classPropertyName: "cards", publicName: "cards", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, cols: { classPropertyName: "cols", publicName: "cols", isSignal: false, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"grid gap-normal\" [ngStyle]=\"{ 'grid-template-columns': gridColumns }\">\n @for (card of cards(); track card.label) {\n @if (variant() === 'figure') {\n <div class=\"card-common flex flex-col gap-small min-w-0\">\n <span class=\"txt-utility-caption text-text-secondary truncate\">{{ card.label }}</span>\n <span\n class=\"txt-heading-03\"\n [ngClass]=\"numClass(card)\"\n style=\"font-family: var(--og-font-figure)\"\n
|
|
4317
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: StatCardsComponent, isStandalone: true, selector: "fx-ui-stat-cards", inputs: { cards: { classPropertyName: "cards", publicName: "cards", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, cols: { classPropertyName: "cols", publicName: "cols", isSignal: false, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"fx-stat-grid grid gap-normal\" [ngStyle]=\"{ 'grid-template-columns': gridColumns }\">\n @for (card of cards(); track card.label) {\n @if (variant() === 'figure') {\n <div class=\"card-common flex flex-col gap-small min-w-0\">\n <span class=\"txt-utility-caption text-text-secondary truncate\">{{ card.label }}</span>\n <span\n class=\"txt-heading-03\"\n [ngClass]=\"numClass(card)\"\n style=\"font-family: var(--og-font-figure)\"\n ><span [fxCountUp]=\"card.value\"></span></span\n >\n @if (card.sub) {\n <span class=\"txt-utility-caption\" [ngClass]=\"subClass(card)\">{{ card.sub }}</span>\n }\n @if (card.spark?.length) {\n <fx-ui-sparkbar class=\"mt-xs\" [values]=\"card.spark!\" [height]=\"16\" [tone]=\"card.tone ?? null\" />\n }\n </div>\n } @else {\n <div class=\"card-common flex items-center gap-normal\">\n @if (card.icon) {\n <div\n class=\"w-10 h-10 rounded-lg flex items-center justify-center shrink-0\"\n [ngClass]=\"boxClass(card)\"\n >\n <fx-ui-hero-icon [icon]=\"card.icon\" [size]=\"20\"></fx-ui-hero-icon>\n </div>\n }\n <div class=\"flex flex-col min-w-0 flex-1\">\n <div class=\"flex items-baseline gap-small\">\n <span\n class=\"txt-heading-04\"\n [ngClass]=\"numClass(card)\"\n style=\"font-family: var(--og-font-figure)\"\n ><span [fxCountUp]=\"card.value\"></span></span\n >\n @if (card.delta) {\n <span class=\"text-xs font-semibold\" [ngClass]=\"deltaClass(card)\">\n {{ card.delta.direction === 'up' ? '\u25B2' : '\u25BC' }} {{ card.delta.value }}\n </span>\n }\n </div>\n <span class=\"txt-utility-caption text-text-secondary truncate\">{{ card.label }}</span>\n @if (card.spark?.length) {\n <fx-ui-sparkbar class=\"mt-xs\" [values]=\"card.spark!\" [height]=\"16\" [tone]=\"card.tone ?? null\" />\n }\n </div>\n </div>\n }\n }\n</div>\n", styles: [":host{display:block;container-type:inline-size}@container (width < 720px){.fx-stat-grid{grid-template-columns:repeat(2,minmax(0,1fr))!important}}@container (width < 360px){.fx-stat-grid{grid-template-columns:minmax(0,1fr)!important}}\n"], dependencies: [{ kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: HeroIconComponent, selector: "fx-ui-hero-icon", inputs: ["stroke", "icon", "solid", "outline", "size", "color", "class"] }, { kind: "component", type: SparkbarComponent, selector: "fx-ui-sparkbar", inputs: ["values", "tone", "height"] }, { kind: "directive", type: FxCountUpDirective, selector: "[fxCountUp]", inputs: ["fxCountUp"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4134
4318
|
}
|
|
4135
4319
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: StatCardsComponent, decorators: [{
|
|
4136
4320
|
type: Component,
|
|
@@ -4139,7 +4323,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImpo
|
|
|
4139
4323
|
NgClass,
|
|
4140
4324
|
HeroIconComponent,
|
|
4141
4325
|
SparkbarComponent,
|
|
4142
|
-
|
|
4326
|
+
FxCountUpDirective,
|
|
4327
|
+
], template: "<div class=\"fx-stat-grid grid gap-normal\" [ngStyle]=\"{ 'grid-template-columns': gridColumns }\">\n @for (card of cards(); track card.label) {\n @if (variant() === 'figure') {\n <div class=\"card-common flex flex-col gap-small min-w-0\">\n <span class=\"txt-utility-caption text-text-secondary truncate\">{{ card.label }}</span>\n <span\n class=\"txt-heading-03\"\n [ngClass]=\"numClass(card)\"\n style=\"font-family: var(--og-font-figure)\"\n ><span [fxCountUp]=\"card.value\"></span></span\n >\n @if (card.sub) {\n <span class=\"txt-utility-caption\" [ngClass]=\"subClass(card)\">{{ card.sub }}</span>\n }\n @if (card.spark?.length) {\n <fx-ui-sparkbar class=\"mt-xs\" [values]=\"card.spark!\" [height]=\"16\" [tone]=\"card.tone ?? null\" />\n }\n </div>\n } @else {\n <div class=\"card-common flex items-center gap-normal\">\n @if (card.icon) {\n <div\n class=\"w-10 h-10 rounded-lg flex items-center justify-center shrink-0\"\n [ngClass]=\"boxClass(card)\"\n >\n <fx-ui-hero-icon [icon]=\"card.icon\" [size]=\"20\"></fx-ui-hero-icon>\n </div>\n }\n <div class=\"flex flex-col min-w-0 flex-1\">\n <div class=\"flex items-baseline gap-small\">\n <span\n class=\"txt-heading-04\"\n [ngClass]=\"numClass(card)\"\n style=\"font-family: var(--og-font-figure)\"\n ><span [fxCountUp]=\"card.value\"></span></span\n >\n @if (card.delta) {\n <span class=\"text-xs font-semibold\" [ngClass]=\"deltaClass(card)\">\n {{ card.delta.direction === 'up' ? '\u25B2' : '\u25BC' }} {{ card.delta.value }}\n </span>\n }\n </div>\n <span class=\"txt-utility-caption text-text-secondary truncate\">{{ card.label }}</span>\n @if (card.spark?.length) {\n <fx-ui-sparkbar class=\"mt-xs\" [values]=\"card.spark!\" [height]=\"16\" [tone]=\"card.tone ?? null\" />\n }\n </div>\n </div>\n }\n }\n</div>\n", styles: [":host{display:block;container-type:inline-size}@container (width < 720px){.fx-stat-grid{grid-template-columns:repeat(2,minmax(0,1fr))!important}}@container (width < 360px){.fx-stat-grid{grid-template-columns:minmax(0,1fr)!important}}\n"] }]
|
|
4143
4328
|
}], propDecorators: { cards: [{ type: i0.Input, args: [{ isSignal: true, alias: "cards", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], cols: [{
|
|
4144
4329
|
type: Input
|
|
4145
4330
|
}] } });
|
|
@@ -4229,6 +4414,12 @@ class FilterPillsComponent {
|
|
|
4229
4414
|
/** Accepts undefined so screens can bind an optional filter-bag key. */
|
|
4230
4415
|
value = input('', ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
4231
4416
|
valueChange = output();
|
|
4417
|
+
/**
|
|
4418
|
+
* Fill of the ACTIVE pill. 'accent' (default, design 11 vulnerabilities);
|
|
4419
|
+
* 'solid' = `--og-text` on inverse text — the demo's Attack-paths panel on
|
|
4420
|
+
* the Overview tab (design 02 "All paths 164") and Recent analyses (design 13).
|
|
4421
|
+
*/
|
|
4422
|
+
tone = input('accent', ...(ngDevMode ? [{ debugName: "tone" }] : /* istanbul ignore next */ []));
|
|
4232
4423
|
isActive(option) {
|
|
4233
4424
|
return (this.value() ?? '') === option.value;
|
|
4234
4425
|
}
|
|
@@ -4239,20 +4430,23 @@ class FilterPillsComponent {
|
|
|
4239
4430
|
* trạng thái chọn của filter (anh Tú chốt 03/09).
|
|
4240
4431
|
*/
|
|
4241
4432
|
pillClass(option) {
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4433
|
+
if (!this.isActive(option)) {
|
|
4434
|
+
return 'border-border-default text-text-secondary hover:border-border-strong';
|
|
4435
|
+
}
|
|
4436
|
+
return this.tone() === 'solid'
|
|
4437
|
+
? 'bg-text-primary text-text-inverse border-text-primary'
|
|
4438
|
+
: 'bg-accent text-accent-contrast border-accent';
|
|
4245
4439
|
}
|
|
4246
4440
|
countClass(option) {
|
|
4247
4441
|
return this.isActive(option) ? 'opacity-70' : 'text-text-placeholder';
|
|
4248
4442
|
}
|
|
4249
4443
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: FilterPillsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4250
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: FilterPillsComponent, isStandalone: true, selector: "fx-ui-filter-pills", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: "<div class=\"flex flex-wrap items-center gap-small\">\n @for (option of options(); track option.value) {\n <button\n type=\"button\"\n [attr.aria-pressed]=\"isActive(option)\"\n class=\"flex items-center gap-small px-
|
|
4444
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: FilterPillsComponent, isStandalone: true, selector: "fx-ui-filter-pills", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, tone: { classPropertyName: "tone", publicName: "tone", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: "<div class=\"flex flex-wrap items-center gap-small\">\n @for (option of options(); track option.value) {\n <button\n type=\"button\"\n [attr.aria-pressed]=\"isActive(option)\"\n class=\"flex items-center gap-small px-[11px] py-[4px] rounded-full border txt-utility-caption font-bold cursor-pointer transition-colors\"\n [ngClass]=\"pillClass(option)\"\n (click)=\"valueChange.emit(option.value)\"\n >\n {{ option.label }}\n @if (option.count !== null && option.count !== undefined) {\n <span [ngClass]=\"countClass(option)\" style=\"font-family: var(--og-font-figure)\">{{ option.count }}</span>\n }\n </button>\n }\n</div>\n", dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4251
4445
|
}
|
|
4252
4446
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: FilterPillsComponent, decorators: [{
|
|
4253
4447
|
type: Component,
|
|
4254
|
-
args: [{ selector: 'fx-ui-filter-pills', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgClass], template: "<div class=\"flex flex-wrap items-center gap-small\">\n @for (option of options(); track option.value) {\n <button\n type=\"button\"\n [attr.aria-pressed]=\"isActive(option)\"\n class=\"flex items-center gap-small px-
|
|
4255
|
-
}], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }] } });
|
|
4448
|
+
args: [{ selector: 'fx-ui-filter-pills', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgClass], template: "<div class=\"flex flex-wrap items-center gap-small\">\n @for (option of options(); track option.value) {\n <button\n type=\"button\"\n [attr.aria-pressed]=\"isActive(option)\"\n class=\"flex items-center gap-small px-[11px] py-[4px] rounded-full border txt-utility-caption font-bold cursor-pointer transition-colors\"\n [ngClass]=\"pillClass(option)\"\n (click)=\"valueChange.emit(option.value)\"\n >\n {{ option.label }}\n @if (option.count !== null && option.count !== undefined) {\n <span [ngClass]=\"countClass(option)\" style=\"font-family: var(--og-font-figure)\">{{ option.count }}</span>\n }\n </button>\n }\n</div>\n" }]
|
|
4449
|
+
}], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }], tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }] } });
|
|
4256
4450
|
|
|
4257
4451
|
/**
|
|
4258
4452
|
* V2 stage progress dots — the walkthrough position indicator on the attack
|
|
@@ -4473,11 +4667,11 @@ class SeverityBadgesComponent {
|
|
|
4473
4667
|
return badge.tone ? toneBadge(badge.tone) : 'bg-bg-hover text-text-primary';
|
|
4474
4668
|
}
|
|
4475
4669
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: SeverityBadgesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4476
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: SeverityBadgesComponent, isStandalone: true, selector: "fx-ui-severity-badges", inputs: { stats: "stats", showZero: "showZero", suffixed: "suffixed" }, outputs: { badgeClick: "badgeClick" }, ngImport: i0, template: "<div class=\"flex items-center gap-small\">\n @for (badge of badges; track badge.key) {\n <button\n type=\"button\"\n class=\"px-2 py-
|
|
4670
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: SeverityBadgesComponent, isStandalone: true, selector: "fx-ui-severity-badges", inputs: { stats: "stats", showZero: "showZero", suffixed: "suffixed" }, outputs: { badgeClick: "badgeClick" }, ngImport: i0, template: "<div class=\"flex items-center gap-small\">\n @for (badge of badges; track badge.key) {\n <button\n type=\"button\"\n class=\"px-2 py-[3px] rounded-full txt-utility-caption font-medium cursor-pointer focus-visible:ring-2 focus-visible:ring-primary/50\"\n [ngClass]=\"badgeClass(badge)\"\n [attr.aria-label]=\"badge.key + ': ' + badge.label\"\n (click)=\"badgeClick.emit(badge.key)\"\n >{{ badge.label }}</button>\n }\n</div>\n", dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4477
4671
|
}
|
|
4478
4672
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: SeverityBadgesComponent, decorators: [{
|
|
4479
4673
|
type: Component,
|
|
4480
|
-
args: [{ selector: 'fx-ui-severity-badges', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgClass], template: "<div class=\"flex items-center gap-small\">\n @for (badge of badges; track badge.key) {\n <button\n type=\"button\"\n class=\"px-2 py-
|
|
4674
|
+
args: [{ selector: 'fx-ui-severity-badges', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgClass], template: "<div class=\"flex items-center gap-small\">\n @for (badge of badges; track badge.key) {\n <button\n type=\"button\"\n class=\"px-2 py-[3px] rounded-full txt-utility-caption font-medium cursor-pointer focus-visible:ring-2 focus-visible:ring-primary/50\"\n [ngClass]=\"badgeClass(badge)\"\n [attr.aria-label]=\"badge.key + ': ' + badge.label\"\n (click)=\"badgeClick.emit(badge.key)\"\n >{{ badge.label }}</button>\n }\n</div>\n" }]
|
|
4481
4675
|
}], propDecorators: { stats: [{
|
|
4482
4676
|
type: Input
|
|
4483
4677
|
}], showZero: [{
|
|
@@ -4530,12 +4724,33 @@ class MasterDetailComponent {
|
|
|
4530
4724
|
/** Caller-translated Prev/Next labels ("X of Y" is numeric). */
|
|
4531
4725
|
prevLabel = input('Prev', ...(ngDevMode ? [{ debugName: "prevLabel" }] : /* istanbul ignore next */ []));
|
|
4532
4726
|
nextLabel = input('Next', ...(ngDevMode ? [{ debugName: "nextLabel" }] : /* istanbul ignore next */ []));
|
|
4727
|
+
/** "Page 1 of 4" (design 11/14/17) — the two words, caller-translated. */
|
|
4728
|
+
pageLabel = input('Page', ...(ngDevMode ? [{ debugName: "pageLabel" }] : /* istanbul ignore next */ []));
|
|
4729
|
+
ofLabel = input('of', ...(ngDevMode ? [{ debugName: "ofLabel" }] : /* istanbul ignore next */ []));
|
|
4730
|
+
/**
|
|
4731
|
+
* Facets rail open (design 11/14) or collapsed to a 40px strip (design 17).
|
|
4732
|
+
* The user can toggle either way; this only sets the initial state.
|
|
4733
|
+
*/
|
|
4734
|
+
facetsOpen = input(true, ...(ngDevMode ? [{ debugName: "facetsOpen" }] : /* istanbul ignore next */ []));
|
|
4735
|
+
/** Caller-translated rail heading ("Scope & facets"). */
|
|
4736
|
+
facetsLabel = input('Scope & facets', ...(ngDevMode ? [{ debugName: "facetsLabel" }] : /* istanbul ignore next */ []));
|
|
4737
|
+
expandFacetsLabel = input('Show scope & facets', ...(ngDevMode ? [{ debugName: "expandFacetsLabel" }] : /* istanbul ignore next */ []));
|
|
4738
|
+
collapseFacetsLabel = input('Hide scope & facets', ...(ngDevMode ? [{ debugName: "collapseFacetsLabel" }] : /* istanbul ignore next */ []));
|
|
4739
|
+
/** Current rail state — follows `facetsOpen` until the user toggles. */
|
|
4740
|
+
railOpen = linkedSignal(() => this.facetsOpen(), ...(ngDevMode ? [{ debugName: "railOpen" }] : /* istanbul ignore next */ []));
|
|
4533
4741
|
rowTemplate = contentChild.required('row');
|
|
4534
4742
|
detailTemplate = contentChild.required('detail');
|
|
4535
4743
|
paginator = viewChild.required(MatPaginator);
|
|
4536
4744
|
uid = `fx-md-${nextUid++}`;
|
|
4537
4745
|
ngAfterContentInit() {
|
|
4538
4746
|
this.hasFacets = !!this.hostEl.nativeElement.querySelector('[mdFacets]');
|
|
4747
|
+
// Stacked layout (container narrower than md = 720px): start with the rail
|
|
4748
|
+
// collapsed so the list is the first thing on screen — an open rail with
|
|
4749
|
+
// six facet groups pushed the list a full screen down on phones
|
|
4750
|
+
// (anh Tú 04/09 mobile audit). The user can still expand it.
|
|
4751
|
+
if (this.hasFacets && this.hostEl.nativeElement.clientWidth > 0 && this.hostEl.nativeElement.clientWidth < 720) {
|
|
4752
|
+
this.railOpen.set(false);
|
|
4753
|
+
}
|
|
4539
4754
|
}
|
|
4540
4755
|
ngAfterViewInit() {
|
|
4541
4756
|
// The paginator lives in this view, not the host page's, so the host's
|
|
@@ -4545,16 +4760,13 @@ class MasterDetailComponent {
|
|
|
4545
4760
|
this.table.paginator = this.paginator();
|
|
4546
4761
|
}
|
|
4547
4762
|
}
|
|
4548
|
-
/** "X of Y"
|
|
4763
|
+
/** "Page X of Y" for the prevnext pagination style (design 11/14/17). */
|
|
4549
4764
|
rangeText() {
|
|
4550
4765
|
const total = this.table.total ?? 0;
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
const page = this.paginator().pageIndex;
|
|
4554
|
-
|
|
4555
|
-
const from = page * size + 1;
|
|
4556
|
-
const to = Math.min((page + 1) * size, total);
|
|
4557
|
-
return `${from}–${to} of ${total}`;
|
|
4766
|
+
const size = this.table.pageSize || 1;
|
|
4767
|
+
const pages = Math.max(1, Math.ceil(total / size));
|
|
4768
|
+
const page = Math.min(pages, (this.paginator().pageIndex ?? 0) + 1);
|
|
4769
|
+
return `${this.pageLabel()} ${page} ${this.ofLabel()} ${pages}`;
|
|
4558
4770
|
}
|
|
4559
4771
|
isSelected(row) {
|
|
4560
4772
|
return this.table.idOf(row) === this.table.selectedId;
|
|
@@ -4594,7 +4806,7 @@ class MasterDetailComponent {
|
|
|
4594
4806
|
el?.scrollIntoView({ block: 'nearest' });
|
|
4595
4807
|
}
|
|
4596
4808
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: MasterDetailComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4597
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: MasterDetailComponent, isStandalone: true, selector: "fx-ui-master-detail", inputs: { table: { classPropertyName: "table", publicName: "table", isSignal: false, isRequired: false, transformFunction: null }, listWidth: { classPropertyName: "listWidth", publicName: "listWidth", isSignal: true, isRequired: false, transformFunction: null }, paneHeight: { classPropertyName: "paneHeight", publicName: "paneHeight", isSignal: true, isRequired: false, transformFunction: null }, pageSizeOptions: { classPropertyName: "pageSizeOptions", publicName: "pageSizeOptions", isSignal: true, isRequired: false, transformFunction: null }, emptyMessage: { classPropertyName: "emptyMessage", publicName: "emptyMessage", isSignal: true, isRequired: false, transformFunction: null }, listLabel: { classPropertyName: "listLabel", publicName: "listLabel", isSignal: true, isRequired: false, transformFunction: null }, skeletonCards: { classPropertyName: "skeletonCards", publicName: "skeletonCards", isSignal: true, isRequired: false, transformFunction: null }, paginationStyle: { classPropertyName: "paginationStyle", publicName: "paginationStyle", isSignal: true, isRequired: false, transformFunction: null }, prevLabel: { classPropertyName: "prevLabel", publicName: "prevLabel", isSignal: true, isRequired: false, transformFunction: null }, nextLabel: { classPropertyName: "nextLabel", publicName: "nextLabel", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "rowTemplate", first: true, predicate: ["row"], descendants: true, isSignal: true }, { propertyName: "detailTemplate", first: true, predicate: ["detail"], descendants: true, isSignal: true }], viewQueries: [{ propertyName: "paginator", first: true, predicate: MatPaginator, descendants: true, isSignal: true }], ngImport: i0, template: "<!-- 3 c\u1EE5m CHUNG chi\u1EC1u cao, m\u1ED7i c\u1EE5m SCROLL RI\u00CANG (anh T\u00FA ch\u1ED1t 03/09).\n `items-stretch` + chi\u1EC1u cao \u0111\u1EB7t tr\u00EAn h\u00E0ng (xem .fx-md-row trong CSS) \u0111\u1EC3\n c\u1EA3 ba pane b\u1EB1ng nhau; m\u1ED7i pane l\u00E0 flex-col + min-h-0 v\u00E0 ch\u1EC9 ph\u1EA7n th\u00E2n\n `overflow-auto flex-1` cu\u1ED9n \u2014 header/filter/paginator \u0111\u1EE9ng y\u00EAn.\n \u26A0\uFE0F H\u01B0\u1EDBng h\u00E0ng + chi\u1EC1u cao do CONTAINER QUERY trong CSS quy\u1EBFt \u0111\u1ECBnh (theo\n b\u1EC1 r\u1ED9ng c\u1EE7a ch\u00EDnh h\u00E0ng), KH\u00D4NG ph\u1EA3i `lg:flex-row`. \u0110\u1EEBng th\u00EAm l\u1EA1i class\n `lg:*` \u1EDF \u0111\u00E2y \u2014 `lg` trong lib l\u00E0 1080px v\u00E0 viewport l\u00E0 proxy sai; xem\n ghi ch\u00FA \u0111\u1EA7u master-detail.component.css. -->\n<div\n class=\"fx-md-row flex flex-col items-stretch gap-xl\"\n [style.--fx-md-list-width]=\"listWidth()\"\n [style.--fx-md-pane-h]=\"paneHeight()\"\n>\n <!-- Facets rail (V2 3-column lookup) \u2014 wrapper always rendered so ng-content\n stays unconditional; hidden when the host projects nothing. -->\n <div\n class=\"fx-md-facets card-common bg-surface flex flex-col min-h-0\"\n [class.hidden]=\"!hasFacets\"\n >\n <div class=\"flex-1 min-h-0 overflow-auto pr-xs\">\n <ng-content select=\"[mdFacets]\"></ng-content>\n </div>\n </div>\n\n <!-- Master: list pane -->\n <div class=\"fx-md-list card-common flex flex-col min-h-0\">\n <ng-content select=\"[mdHeader]\"></ng-content>\n <ng-content select=\"[mdFilters]\"></ng-content>\n <div\n class=\"flex flex-col gap-normal overflow-auto pr-xs flex-1 min-h-0\"\n role=\"listbox\"\n [attr.aria-label]=\"listLabel() || null\"\n >\n @if (table.loading) {\n <fx-ui-skeleton-list [rows]=\"table.pageSize\"></fx-ui-skeleton-list>\n } @else {\n @for (row of table.dataSource.data; track table.idOf(row); let i = $index) {\n <button\n type=\"button\"\n role=\"option\"\n [id]=\"optionId(i)\"\n [attr.aria-selected]=\"isSelected(row)\"\n [tabindex]=\"isSelected(row) ? 0 : -1\"\n (click)=\"table.selectItem(row)\"\n (keydown)=\"onKeydown($event, i)\"\n class=\"text-left rounded-lg border p-semi transition-colors\"\n [ngClass]=\"\n isSelected(row)\n ? 'border-accent bg-accent-soft/60'\n : 'border-border-default hover:border-border-interactive bg-bg-primary'\n \"\n >\n <ng-container\n [ngTemplateOutlet]=\"rowTemplate()\"\n [ngTemplateOutletContext]=\"{ $implicit: row, selected: isSelected(row) }\"\n ></ng-container>\n </button>\n }\n @if (!table.dataSource.data.length) {\n <fx-ui-empty-state [message]=\"emptyMessage()\"></fx-ui-empty-state>\n }\n }\n </div>\n <!-- No (page) binding: ngAfterViewInit hands this paginator to the table,\n whose own BaseTable subscription (running after this child's hook)\n wires page events \u2014 a binding here would double-fetch every change. -->\n <mat-paginator\n class=\"mt-normal\"\n [class.fx-md-paginator-hidden]=\"paginationStyle() === 'prevnext'\"\n [length]=\"table.total\"\n [pageSize]=\"table.pageSize\"\n [pageSizeOptions]=\"pageSizeOptions()\"\n ></mat-paginator>\n @if (paginationStyle() === 'prevnext') {\n <div class=\"flex items-center justify-between mt-normal\">\n <span class=\"txt-utility-description\" style=\"font-family: var(--og-font-figure)\">\n {{ rangeText() }}\n </span>\n <div class=\"flex gap-small\">\n <button\n type=\"button\"\n class=\"btn-alternative !h-8 !px-semi text-sm\"\n [disabled]=\"!paginator().hasPreviousPage()\"\n (click)=\"paginator().previousPage()\"\n >\n {{ prevLabel() }}\n </button>\n <button\n type=\"button\"\n class=\"btn-alternative !h-8 !px-semi text-sm\"\n [disabled]=\"!paginator().hasNextPage()\"\n (click)=\"paginator().nextPage()\"\n >\n {{ nextLabel() }}\n </button>\n </div>\n </div>\n }\n </div>\n\n <!-- Detail pane -->\n <div class=\"fx-md-detail w-full min-w-0 flex flex-col min-h-0\">\n <div class=\"flex-1 min-h-0 overflow-auto\">\n @if (table.selected) {\n @if (table.detailBusy) {\n <fx-ui-skeleton-detail [cards]=\"skeletonCards()\"></fx-ui-skeleton-detail>\n } @else {\n <ng-container\n [ngTemplateOutlet]=\"detailTemplate()\"\n [ngTemplateOutletContext]=\"{ $implicit: table.detail, selected: table.selected }\"\n ></ng-container>\n }\n } @else {\n <div class=\"card-common flex items-center justify-center h-full min-h-[300px]\">\n <fx-ui-empty-state [message]=\"emptyMessage()\"></fx-ui-empty-state>\n </div>\n }\n </div>\n </div>\n</div>\n", styles: [":host{display:block;container-type:inline-size;container-name:fx-md}.fx-md-list,.fx-md-facets{width:100%}@container fx-md (min-width: 968px){.fx-md-row{flex-direction:row;height:var(--fx-md-pane-h, calc(100vh - 300px) );min-height:420px}.fx-md-facets{flex:0 1 230px;min-width:200px;max-width:260px}.fx-md-list{width:auto;flex:0 1 var(--fx-md-list-width, 420px);min-width:320px;max-width:var(--fx-md-list-width, 420px)}.fx-md-detail{flex:1 1 400px;min-width:400px}}.fx-md-paginator-hidden{position:absolute!important;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)}\n"], dependencies: [{ kind: "component", type: SkeletonListComponent, selector: "fx-ui-skeleton-list", inputs: ["rows"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: EmptyStateComponent, selector: "fx-ui-empty-state", inputs: ["message", "icon", "height"] }, { kind: "component", type: MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }, { kind: "component", type: SkeletonDetailComponent, selector: "fx-ui-skeleton-detail", inputs: ["cards", "rows"] }] });
|
|
4809
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: MasterDetailComponent, isStandalone: true, selector: "fx-ui-master-detail", inputs: { table: { classPropertyName: "table", publicName: "table", isSignal: false, isRequired: false, transformFunction: null }, listWidth: { classPropertyName: "listWidth", publicName: "listWidth", isSignal: true, isRequired: false, transformFunction: null }, paneHeight: { classPropertyName: "paneHeight", publicName: "paneHeight", isSignal: true, isRequired: false, transformFunction: null }, pageSizeOptions: { classPropertyName: "pageSizeOptions", publicName: "pageSizeOptions", isSignal: true, isRequired: false, transformFunction: null }, emptyMessage: { classPropertyName: "emptyMessage", publicName: "emptyMessage", isSignal: true, isRequired: false, transformFunction: null }, listLabel: { classPropertyName: "listLabel", publicName: "listLabel", isSignal: true, isRequired: false, transformFunction: null }, skeletonCards: { classPropertyName: "skeletonCards", publicName: "skeletonCards", isSignal: true, isRequired: false, transformFunction: null }, paginationStyle: { classPropertyName: "paginationStyle", publicName: "paginationStyle", isSignal: true, isRequired: false, transformFunction: null }, prevLabel: { classPropertyName: "prevLabel", publicName: "prevLabel", isSignal: true, isRequired: false, transformFunction: null }, nextLabel: { classPropertyName: "nextLabel", publicName: "nextLabel", isSignal: true, isRequired: false, transformFunction: null }, pageLabel: { classPropertyName: "pageLabel", publicName: "pageLabel", isSignal: true, isRequired: false, transformFunction: null }, ofLabel: { classPropertyName: "ofLabel", publicName: "ofLabel", isSignal: true, isRequired: false, transformFunction: null }, facetsOpen: { classPropertyName: "facetsOpen", publicName: "facetsOpen", isSignal: true, isRequired: false, transformFunction: null }, facetsLabel: { classPropertyName: "facetsLabel", publicName: "facetsLabel", isSignal: true, isRequired: false, transformFunction: null }, expandFacetsLabel: { classPropertyName: "expandFacetsLabel", publicName: "expandFacetsLabel", isSignal: true, isRequired: false, transformFunction: null }, collapseFacetsLabel: { classPropertyName: "collapseFacetsLabel", publicName: "collapseFacetsLabel", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "rowTemplate", first: true, predicate: ["row"], descendants: true, isSignal: true }, { propertyName: "detailTemplate", first: true, predicate: ["detail"], descendants: true, isSignal: true }], viewQueries: [{ propertyName: "paginator", first: true, predicate: MatPaginator, descendants: true, isSignal: true }], ngImport: i0, template: "<!-- ONE frame, three columns (designs 11/14/17): facets rail \u00B7 list \u00B7 detail,\n separated by hairlines, sharing one height; only each pane's body scrolls.\n Row direction / height come from the CONTAINER QUERY in the CSS (the row's\n own width), never from `lg:*` \u2014 see the note at the top of the .css. -->\n<div\n class=\"fx-md-row flex flex-col items-stretch\"\n [class.fx-md-row--rail-open]=\"railOpen()\"\n [style.--fx-md-list-width]=\"listWidth()\"\n [style.--fx-md-pane-h]=\"paneHeight()\"\n>\n <!-- Facets rail \u2014 collapsible (\u00BB / \u00AB), collapsed = 40px strip with the label\n set vertically. The [mdFacets] projection is unconditional (hasFacets is\n detected by querying it) and only hidden while collapsed. -->\n <aside\n class=\"fx-md-facets flex flex-col min-h-0 min-w-0\"\n [class.hidden]=\"!hasFacets\"\n [class.fx-md-facets--open]=\"railOpen()\"\n >\n <div class=\"fx-md-rail-head flex items-center gap-small px-normal py-semi border-b border-border-subtle shrink-0\" [class.hidden]=\"!railOpen()\">\n <button\n type=\"button\"\n class=\"flex items-center justify-center w-6 h-6 rounded-control text-muted hover:bg-bg-hover shrink-0\"\n [attr.aria-label]=\"collapseFacetsLabel()\"\n (click)=\"railOpen.set(false)\"\n >\n <fx-ui-hero-icon icon=\"chevron-double-left\" [size]=\"14\" class=\"flex\" />\n </button>\n <span class=\"txt-eyebrow truncate\">{{ facetsLabel() }}</span>\n </div>\n <div class=\"flex-1 min-h-0 overflow-auto px-normal py-normal\" [class.hidden]=\"!railOpen()\">\n <ng-content select=\"[mdFacets]\"></ng-content>\n </div>\n <button\n type=\"button\"\n class=\"fx-md-rail-toggle flex items-center gap-normal text-muted hover:bg-bg-hover transition-colors\"\n [class.hidden]=\"railOpen()\"\n [attr.aria-label]=\"expandFacetsLabel()\"\n [attr.aria-expanded]=\"false\"\n (click)=\"railOpen.set(true)\"\n >\n <fx-ui-hero-icon icon=\"chevron-double-right\" [size]=\"14\" class=\"flex shrink-0\" />\n <span class=\"fx-md-rail-label txt-eyebrow whitespace-nowrap\">{{ facetsLabel() }}</span>\n </button>\n </aside>\n\n <!-- Master: list pane -->\n <section class=\"fx-md-list flex flex-col min-h-0 min-w-0\">\n <div class=\"fx-md-list-head px-large pt-large pb-small border-b border-border-subtle shrink-0\">\n <ng-content select=\"[mdHeader]\"></ng-content>\n <ng-content select=\"[mdFilters]\"></ng-content>\n </div>\n <div\n class=\"flex flex-col overflow-auto flex-1 min-h-0\"\n role=\"listbox\"\n [attr.aria-label]=\"listLabel() || null\"\n >\n @if (table.loading) {\n <div class=\"px-large py-normal\">\n <fx-ui-skeleton-list [rows]=\"table.pageSize\"></fx-ui-skeleton-list>\n </div>\n } @else {\n @for (row of table.dataSource.data; track table.idOf(row); let i = $index) {\n <!-- Hairline rows; the selected one is tinted with an accent bar on\n the left (design 11/14 selected finding / component). -->\n <button\n type=\"button\"\n role=\"option\"\n [id]=\"optionId(i)\"\n [attr.aria-selected]=\"isSelected(row)\"\n [tabindex]=\"isSelected(row) ? 0 : -1\"\n (click)=\"table.selectItem(row)\"\n (keydown)=\"onKeydown($event, i)\"\n class=\"text-left px-large py-semi border-l-2 border-b border-b-border-subtle transition-colors\"\n [ngClass]=\"\n isSelected(row)\n ? 'border-l-accent bg-accent-soft/60'\n : 'border-l-transparent hover:bg-surface-sunken'\n \"\n >\n <ng-container\n [ngTemplateOutlet]=\"rowTemplate()\"\n [ngTemplateOutletContext]=\"{ $implicit: row, selected: isSelected(row) }\"\n ></ng-container>\n </button>\n }\n @if (!table.dataSource.data.length) {\n <div class=\"px-large py-large\">\n <fx-ui-empty-state [message]=\"emptyMessage()\"></fx-ui-empty-state>\n </div>\n }\n }\n </div>\n <!-- No (page) binding: ngAfterViewInit hands this paginator to the table,\n whose own BaseTable subscription (running after this child's hook)\n wires page events \u2014 a binding here would double-fetch every change. -->\n <mat-paginator\n class=\"fx-md-paginator\"\n [class.fx-md-paginator-hidden]=\"paginationStyle() === 'prevnext'\"\n [length]=\"table.total\"\n [pageSize]=\"table.pageSize\"\n [pageSizeOptions]=\"pageSizeOptions()\"\n ></mat-paginator>\n @if (paginationStyle() === 'prevnext') {\n <div class=\"flex items-center justify-between gap-normal px-large py-normal border-t border-border-subtle shrink-0\">\n <span class=\"txt-utility-description\" style=\"font-family: var(--og-font-figure)\">\n {{ rangeText() }}\n </span>\n <div class=\"flex gap-small\">\n <button\n type=\"button\"\n class=\"btn-alternative !h-8 !px-semi text-sm\"\n [disabled]=\"!paginator().hasPreviousPage()\"\n (click)=\"paginator().previousPage()\"\n >\n {{ prevLabel() }}\n </button>\n <button\n type=\"button\"\n class=\"btn-alternative !h-8 !px-semi text-sm\"\n [disabled]=\"!paginator().hasNextPage()\"\n (click)=\"paginator().nextPage()\"\n >\n {{ nextLabel() }}\n </button>\n </div>\n </div>\n }\n </section>\n\n <!-- Detail pane -->\n <section class=\"fx-md-detail w-full min-w-0 flex flex-col min-h-0\">\n <div class=\"flex-1 min-h-0 overflow-auto px-large py-large\">\n @if (table.selected) {\n @if (table.detailBusy) {\n <fx-ui-skeleton-detail [cards]=\"skeletonCards()\"></fx-ui-skeleton-detail>\n } @else {\n <ng-container\n [ngTemplateOutlet]=\"detailTemplate()\"\n [ngTemplateOutletContext]=\"{ $implicit: table.detail, selected: table.selected }\"\n ></ng-container>\n }\n } @else {\n <div class=\"flex items-center justify-center h-full min-h-[300px]\">\n <fx-ui-empty-state [message]=\"emptyMessage()\"></fx-ui-empty-state>\n </div>\n }\n </div>\n </section>\n</div>\n", styles: [":host{display:block;container-type:inline-size;container-name:fx-md}.fx-md-row{background:rgb(var(--og-surface));border:1px solid rgb(var(--border-default));border-radius:.5rem;overflow:hidden;box-shadow:0 1px 2px #0000000a,0 3px 8px #0000000d}.fx-md-facets,.fx-md-list{border-bottom:1px solid rgb(var(--og-border-subtle))}.fx-md-rail-toggle{width:100%;padding:10px 12px;flex-direction:row;justify-content:flex-start}.fx-md-list,.fx-md-facets{width:100%}@container fx-md (min-width: 720px){.fx-md-row{flex-direction:row;height:var(--fx-md-pane-h, calc(100vh - 300px) );min-height:420px}.fx-md-facets,.fx-md-list{border-bottom:0;border-right:1px solid rgb(var(--og-border-subtle))}.fx-md-facets{flex:0 0 40px;width:40px;min-width:40px;max-width:40px}.fx-md-facets--open{flex:0 1 230px;width:auto;min-width:200px;max-width:260px}.fx-md-rail-toggle{height:100%;padding:12px 0;flex-direction:column;justify-content:flex-start}.fx-md-rail-label{writing-mode:vertical-rl;transform:rotate(180deg)}.fx-md-list{width:auto;flex:0 1 var(--fx-md-list-width, 420px);min-width:260px;max-width:var(--fx-md-list-width, 420px)}.fx-md-detail{flex:1 1 360px;min-width:360px}}.fx-md-paginator-hidden{position:absolute!important;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)}.fx-md-paginator{padding:0 12px;border-top:1px solid rgb(var(--og-border-subtle))}\n"], dependencies: [{ kind: "component", type: SkeletonListComponent, selector: "fx-ui-skeleton-list", inputs: ["rows"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: EmptyStateComponent, selector: "fx-ui-empty-state", inputs: ["message", "icon", "height"] }, { kind: "component", type: MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }, { kind: "component", type: SkeletonDetailComponent, selector: "fx-ui-skeleton-detail", inputs: ["cards", "rows"] }, { kind: "component", type: HeroIconComponent, selector: "fx-ui-hero-icon", inputs: ["stroke", "icon", "solid", "outline", "size", "color", "class"] }] });
|
|
4598
4810
|
}
|
|
4599
4811
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: MasterDetailComponent, decorators: [{
|
|
4600
4812
|
type: Component,
|
|
@@ -4605,10 +4817,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImpo
|
|
|
4605
4817
|
EmptyStateComponent,
|
|
4606
4818
|
MatPaginator,
|
|
4607
4819
|
SkeletonDetailComponent,
|
|
4608
|
-
|
|
4820
|
+
HeroIconComponent,
|
|
4821
|
+
], template: "<!-- ONE frame, three columns (designs 11/14/17): facets rail \u00B7 list \u00B7 detail,\n separated by hairlines, sharing one height; only each pane's body scrolls.\n Row direction / height come from the CONTAINER QUERY in the CSS (the row's\n own width), never from `lg:*` \u2014 see the note at the top of the .css. -->\n<div\n class=\"fx-md-row flex flex-col items-stretch\"\n [class.fx-md-row--rail-open]=\"railOpen()\"\n [style.--fx-md-list-width]=\"listWidth()\"\n [style.--fx-md-pane-h]=\"paneHeight()\"\n>\n <!-- Facets rail \u2014 collapsible (\u00BB / \u00AB), collapsed = 40px strip with the label\n set vertically. The [mdFacets] projection is unconditional (hasFacets is\n detected by querying it) and only hidden while collapsed. -->\n <aside\n class=\"fx-md-facets flex flex-col min-h-0 min-w-0\"\n [class.hidden]=\"!hasFacets\"\n [class.fx-md-facets--open]=\"railOpen()\"\n >\n <div class=\"fx-md-rail-head flex items-center gap-small px-normal py-semi border-b border-border-subtle shrink-0\" [class.hidden]=\"!railOpen()\">\n <button\n type=\"button\"\n class=\"flex items-center justify-center w-6 h-6 rounded-control text-muted hover:bg-bg-hover shrink-0\"\n [attr.aria-label]=\"collapseFacetsLabel()\"\n (click)=\"railOpen.set(false)\"\n >\n <fx-ui-hero-icon icon=\"chevron-double-left\" [size]=\"14\" class=\"flex\" />\n </button>\n <span class=\"txt-eyebrow truncate\">{{ facetsLabel() }}</span>\n </div>\n <div class=\"flex-1 min-h-0 overflow-auto px-normal py-normal\" [class.hidden]=\"!railOpen()\">\n <ng-content select=\"[mdFacets]\"></ng-content>\n </div>\n <button\n type=\"button\"\n class=\"fx-md-rail-toggle flex items-center gap-normal text-muted hover:bg-bg-hover transition-colors\"\n [class.hidden]=\"railOpen()\"\n [attr.aria-label]=\"expandFacetsLabel()\"\n [attr.aria-expanded]=\"false\"\n (click)=\"railOpen.set(true)\"\n >\n <fx-ui-hero-icon icon=\"chevron-double-right\" [size]=\"14\" class=\"flex shrink-0\" />\n <span class=\"fx-md-rail-label txt-eyebrow whitespace-nowrap\">{{ facetsLabel() }}</span>\n </button>\n </aside>\n\n <!-- Master: list pane -->\n <section class=\"fx-md-list flex flex-col min-h-0 min-w-0\">\n <div class=\"fx-md-list-head px-large pt-large pb-small border-b border-border-subtle shrink-0\">\n <ng-content select=\"[mdHeader]\"></ng-content>\n <ng-content select=\"[mdFilters]\"></ng-content>\n </div>\n <div\n class=\"flex flex-col overflow-auto flex-1 min-h-0\"\n role=\"listbox\"\n [attr.aria-label]=\"listLabel() || null\"\n >\n @if (table.loading) {\n <div class=\"px-large py-normal\">\n <fx-ui-skeleton-list [rows]=\"table.pageSize\"></fx-ui-skeleton-list>\n </div>\n } @else {\n @for (row of table.dataSource.data; track table.idOf(row); let i = $index) {\n <!-- Hairline rows; the selected one is tinted with an accent bar on\n the left (design 11/14 selected finding / component). -->\n <button\n type=\"button\"\n role=\"option\"\n [id]=\"optionId(i)\"\n [attr.aria-selected]=\"isSelected(row)\"\n [tabindex]=\"isSelected(row) ? 0 : -1\"\n (click)=\"table.selectItem(row)\"\n (keydown)=\"onKeydown($event, i)\"\n class=\"text-left px-large py-semi border-l-2 border-b border-b-border-subtle transition-colors\"\n [ngClass]=\"\n isSelected(row)\n ? 'border-l-accent bg-accent-soft/60'\n : 'border-l-transparent hover:bg-surface-sunken'\n \"\n >\n <ng-container\n [ngTemplateOutlet]=\"rowTemplate()\"\n [ngTemplateOutletContext]=\"{ $implicit: row, selected: isSelected(row) }\"\n ></ng-container>\n </button>\n }\n @if (!table.dataSource.data.length) {\n <div class=\"px-large py-large\">\n <fx-ui-empty-state [message]=\"emptyMessage()\"></fx-ui-empty-state>\n </div>\n }\n }\n </div>\n <!-- No (page) binding: ngAfterViewInit hands this paginator to the table,\n whose own BaseTable subscription (running after this child's hook)\n wires page events \u2014 a binding here would double-fetch every change. -->\n <mat-paginator\n class=\"fx-md-paginator\"\n [class.fx-md-paginator-hidden]=\"paginationStyle() === 'prevnext'\"\n [length]=\"table.total\"\n [pageSize]=\"table.pageSize\"\n [pageSizeOptions]=\"pageSizeOptions()\"\n ></mat-paginator>\n @if (paginationStyle() === 'prevnext') {\n <div class=\"flex items-center justify-between gap-normal px-large py-normal border-t border-border-subtle shrink-0\">\n <span class=\"txt-utility-description\" style=\"font-family: var(--og-font-figure)\">\n {{ rangeText() }}\n </span>\n <div class=\"flex gap-small\">\n <button\n type=\"button\"\n class=\"btn-alternative !h-8 !px-semi text-sm\"\n [disabled]=\"!paginator().hasPreviousPage()\"\n (click)=\"paginator().previousPage()\"\n >\n {{ prevLabel() }}\n </button>\n <button\n type=\"button\"\n class=\"btn-alternative !h-8 !px-semi text-sm\"\n [disabled]=\"!paginator().hasNextPage()\"\n (click)=\"paginator().nextPage()\"\n >\n {{ nextLabel() }}\n </button>\n </div>\n </div>\n }\n </section>\n\n <!-- Detail pane -->\n <section class=\"fx-md-detail w-full min-w-0 flex flex-col min-h-0\">\n <div class=\"flex-1 min-h-0 overflow-auto px-large py-large\">\n @if (table.selected) {\n @if (table.detailBusy) {\n <fx-ui-skeleton-detail [cards]=\"skeletonCards()\"></fx-ui-skeleton-detail>\n } @else {\n <ng-container\n [ngTemplateOutlet]=\"detailTemplate()\"\n [ngTemplateOutletContext]=\"{ $implicit: table.detail, selected: table.selected }\"\n ></ng-container>\n }\n } @else {\n <div class=\"flex items-center justify-center h-full min-h-[300px]\">\n <fx-ui-empty-state [message]=\"emptyMessage()\"></fx-ui-empty-state>\n </div>\n }\n </div>\n </section>\n</div>\n", styles: [":host{display:block;container-type:inline-size;container-name:fx-md}.fx-md-row{background:rgb(var(--og-surface));border:1px solid rgb(var(--border-default));border-radius:.5rem;overflow:hidden;box-shadow:0 1px 2px #0000000a,0 3px 8px #0000000d}.fx-md-facets,.fx-md-list{border-bottom:1px solid rgb(var(--og-border-subtle))}.fx-md-rail-toggle{width:100%;padding:10px 12px;flex-direction:row;justify-content:flex-start}.fx-md-list,.fx-md-facets{width:100%}@container fx-md (min-width: 720px){.fx-md-row{flex-direction:row;height:var(--fx-md-pane-h, calc(100vh - 300px) );min-height:420px}.fx-md-facets,.fx-md-list{border-bottom:0;border-right:1px solid rgb(var(--og-border-subtle))}.fx-md-facets{flex:0 0 40px;width:40px;min-width:40px;max-width:40px}.fx-md-facets--open{flex:0 1 230px;width:auto;min-width:200px;max-width:260px}.fx-md-rail-toggle{height:100%;padding:12px 0;flex-direction:column;justify-content:flex-start}.fx-md-rail-label{writing-mode:vertical-rl;transform:rotate(180deg)}.fx-md-list{width:auto;flex:0 1 var(--fx-md-list-width, 420px);min-width:260px;max-width:var(--fx-md-list-width, 420px)}.fx-md-detail{flex:1 1 360px;min-width:360px}}.fx-md-paginator-hidden{position:absolute!important;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)}.fx-md-paginator{padding:0 12px;border-top:1px solid rgb(var(--og-border-subtle))}\n"] }]
|
|
4609
4822
|
}], propDecorators: { table: [{
|
|
4610
4823
|
type: Input
|
|
4611
|
-
}], listWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "listWidth", required: false }] }], paneHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "paneHeight", required: false }] }], pageSizeOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSizeOptions", required: false }] }], emptyMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyMessage", required: false }] }], listLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "listLabel", required: false }] }], skeletonCards: [{ type: i0.Input, args: [{ isSignal: true, alias: "skeletonCards", required: false }] }], paginationStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "paginationStyle", required: false }] }], prevLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "prevLabel", required: false }] }], nextLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "nextLabel", required: false }] }], rowTemplate: [{ type: i0.ContentChild, args: ['row', { isSignal: true }] }], detailTemplate: [{ type: i0.ContentChild, args: ['detail', { isSignal: true }] }], paginator: [{ type: i0.ViewChild, args: [i0.forwardRef(() => MatPaginator), { isSignal: true }] }] } });
|
|
4824
|
+
}], listWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "listWidth", required: false }] }], paneHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "paneHeight", required: false }] }], pageSizeOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSizeOptions", required: false }] }], emptyMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyMessage", required: false }] }], listLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "listLabel", required: false }] }], skeletonCards: [{ type: i0.Input, args: [{ isSignal: true, alias: "skeletonCards", required: false }] }], paginationStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "paginationStyle", required: false }] }], prevLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "prevLabel", required: false }] }], nextLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "nextLabel", required: false }] }], pageLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageLabel", required: false }] }], ofLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ofLabel", required: false }] }], facetsOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "facetsOpen", required: false }] }], facetsLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "facetsLabel", required: false }] }], expandFacetsLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandFacetsLabel", required: false }] }], collapseFacetsLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapseFacetsLabel", required: false }] }], rowTemplate: [{ type: i0.ContentChild, args: ['row', { isSignal: true }] }], detailTemplate: [{ type: i0.ContentChild, args: ['detail', { isSignal: true }] }], paginator: [{ type: i0.ViewChild, args: [i0.forwardRef(() => MatPaginator), { isSignal: true }] }] } });
|
|
4612
4825
|
|
|
4613
4826
|
/**
|
|
4614
4827
|
* Row-action dropdown wrapper over mat-menu (behavior-layer rule, Playbook
|
|
@@ -4712,6 +4925,186 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImpo
|
|
|
4712
4925
|
args: [{ selector: 'fx-ui-pending-badge', changeDetection: ChangeDetectionStrategy.OnPush, imports: [HeroIconComponent], template: "@if (variant() === 'dot') {\n <span\n class=\"inline-block w-[7px] h-[7px] rounded-full bg-muted/60 align-middle\"\n [attr.title]=\"tooltip\"\n [attr.aria-label]=\"tooltip\"\n ></span>\n} @else {\n <span\n class=\"inline-flex items-center gap-xs px-small py-[1px] rounded-pill border border-border-subtle bg-neutral-soft text-muted txt-tag-label whitespace-nowrap align-middle\"\n [attr.title]=\"tooltip\"\n >\n <fx-ui-hero-icon icon=\"clock\" [size]=\"11\" class=\"flex\" />\n {{ label() }}\n </span>\n}\n" }]
|
|
4713
4926
|
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }] } });
|
|
4714
4927
|
|
|
4928
|
+
/**
|
|
4929
|
+
* V2 topbar breadcrumb (design 02/12/13, measured on the demo 04/09).
|
|
4930
|
+
*
|
|
4931
|
+
* Projects / [◳ RocketChat ▾] / [⬡ rocketchat.exe · 4.8.1 ▾]
|
|
4932
|
+
*
|
|
4933
|
+
* Plain crumbs are quiet links (12.5px / 500 / muted); the last one is strong.
|
|
4934
|
+
* A crumb with `icon` or `switcher` renders as a chip — hairline border,
|
|
4935
|
+
* surface fill, 1px shadow — and a `switcher` opens a listbox with a filter
|
|
4936
|
+
* box, the sibling options, an optional "View all" row and a "Create" CTA.
|
|
4937
|
+
* Data comes from the app through `BreadcrumbService`; this component only
|
|
4938
|
+
* draws and navigates.
|
|
4939
|
+
*/
|
|
4940
|
+
class BreadcrumbComponent {
|
|
4941
|
+
service = inject(BreadcrumbService);
|
|
4942
|
+
router = inject(Router);
|
|
4943
|
+
host = inject(ElementRef);
|
|
4944
|
+
crumbs = toSignal(this.service.get(), { initialValue: [] });
|
|
4945
|
+
/** Index of the crumb whose switcher panel is open; null = closed. */
|
|
4946
|
+
openIndex = signal(null, ...(ngDevMode ? [{ debugName: "openIndex" }] : /* istanbul ignore next */ []));
|
|
4947
|
+
options = signal([], ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
|
|
4948
|
+
loading = signal(false, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
|
|
4949
|
+
keyword = signal('', ...(ngDevMode ? [{ debugName: "keyword" }] : /* istanbul ignore next */ []));
|
|
4950
|
+
/** Drops results of a superseded load (typing fast, reopening). */
|
|
4951
|
+
loadSeq = 0;
|
|
4952
|
+
isChip(crumb) {
|
|
4953
|
+
return !!(crumb.icon || crumb.switcher);
|
|
4954
|
+
}
|
|
4955
|
+
onChipClick(crumb, index, event) {
|
|
4956
|
+
event.stopPropagation();
|
|
4957
|
+
if (crumb.switcher) {
|
|
4958
|
+
if (this.openIndex() === index) {
|
|
4959
|
+
this.close();
|
|
4960
|
+
}
|
|
4961
|
+
else {
|
|
4962
|
+
this.open(crumb, index);
|
|
4963
|
+
}
|
|
4964
|
+
return;
|
|
4965
|
+
}
|
|
4966
|
+
if (crumb.url)
|
|
4967
|
+
this.navigate(crumb.url);
|
|
4968
|
+
}
|
|
4969
|
+
open(crumb, index) {
|
|
4970
|
+
this.openIndex.set(index);
|
|
4971
|
+
this.keyword.set('');
|
|
4972
|
+
void this.load(crumb, '');
|
|
4973
|
+
// Focus the filter box once it exists so typing starts immediately.
|
|
4974
|
+
setTimeout(() => {
|
|
4975
|
+
this.host.nativeElement.querySelector('.fx-crumb-panel__filter')?.focus();
|
|
4976
|
+
});
|
|
4977
|
+
}
|
|
4978
|
+
close() {
|
|
4979
|
+
this.openIndex.set(null);
|
|
4980
|
+
this.options.set([]);
|
|
4981
|
+
this.loading.set(false);
|
|
4982
|
+
}
|
|
4983
|
+
onFilter(crumb, value) {
|
|
4984
|
+
this.keyword.set(value);
|
|
4985
|
+
void this.load(crumb, value);
|
|
4986
|
+
}
|
|
4987
|
+
async load(crumb, keyword) {
|
|
4988
|
+
const switcher = crumb.switcher;
|
|
4989
|
+
if (!switcher)
|
|
4990
|
+
return;
|
|
4991
|
+
const seq = ++this.loadSeq;
|
|
4992
|
+
this.loading.set(true);
|
|
4993
|
+
try {
|
|
4994
|
+
const rows = await switcher.load(keyword);
|
|
4995
|
+
if (seq !== this.loadSeq)
|
|
4996
|
+
return;
|
|
4997
|
+
this.options.set(rows ?? []);
|
|
4998
|
+
}
|
|
4999
|
+
catch {
|
|
5000
|
+
if (seq !== this.loadSeq)
|
|
5001
|
+
return;
|
|
5002
|
+
this.options.set([]);
|
|
5003
|
+
}
|
|
5004
|
+
finally {
|
|
5005
|
+
if (seq === this.loadSeq)
|
|
5006
|
+
this.loading.set(false);
|
|
5007
|
+
}
|
|
5008
|
+
}
|
|
5009
|
+
pick(option, event) {
|
|
5010
|
+
event.stopPropagation();
|
|
5011
|
+
this.close();
|
|
5012
|
+
this.navigate(option.url);
|
|
5013
|
+
}
|
|
5014
|
+
go(event, url) {
|
|
5015
|
+
event.preventDefault();
|
|
5016
|
+
event.stopPropagation();
|
|
5017
|
+
this.close();
|
|
5018
|
+
if (url)
|
|
5019
|
+
this.navigate(url);
|
|
5020
|
+
}
|
|
5021
|
+
navigate(url) {
|
|
5022
|
+
this.router.navigateByUrl(url).catch(() => {
|
|
5023
|
+
window.location.href = url;
|
|
5024
|
+
});
|
|
5025
|
+
}
|
|
5026
|
+
onDocumentClick(event) {
|
|
5027
|
+
if (this.openIndex() === null)
|
|
5028
|
+
return;
|
|
5029
|
+
if (!this.host.nativeElement.contains(event.target))
|
|
5030
|
+
this.close();
|
|
5031
|
+
}
|
|
5032
|
+
onEscape() {
|
|
5033
|
+
if (this.openIndex() !== null)
|
|
5034
|
+
this.close();
|
|
5035
|
+
}
|
|
5036
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: BreadcrumbComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5037
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: BreadcrumbComponent, isStandalone: true, selector: "fx-ui-breadcrumb", host: { listeners: { "document:click": "onDocumentClick($event)", "document:keydown.escape": "onEscape()" } }, ngImport: i0, template: "<nav aria-label=\"Breadcrumb\" class=\"fx-crumbs\">\n @for (crumb of crumbs(); track $index; let last = $last; let i = $index) {\n @if (i > 0) {\n <span aria-hidden=\"true\" class=\"fx-crumb-sep\">/</span>\n }\n @if (isChip(crumb)) {\n <span class=\"relative inline-flex min-w-0\">\n <button\n type=\"button\"\n class=\"fx-crumb-chip\"\n [class.fx-crumb-chip--mono]=\"crumb.mono\"\n [attr.aria-haspopup]=\"crumb.switcher ? 'listbox' : null\"\n [attr.aria-expanded]=\"crumb.switcher ? openIndex() === i : null\"\n [attr.title]=\"crumb.switcher?.title || crumb.label\"\n (click)=\"onChipClick(crumb, i, $event)\"\n >\n @if (crumb.icon) {\n <fx-ui-hero-icon [icon]=\"crumb.icon\" [size]=\"13\" class=\"flex shrink-0 text-accent\" />\n }\n <span class=\"truncate\">{{ crumb.label }}</span>\n @if (crumb.switcher) {\n <fx-ui-hero-icon icon=\"chevron-down\" [size]=\"10\" class=\"flex shrink-0 text-faint\" />\n }\n </button>\n\n @if (crumb.switcher; as sw) {\n @if (openIndex() === i) {\n <div role=\"listbox\" class=\"fx-crumb-panel\" [class.fx-crumb-panel--mono]=\"sw.mono\">\n <div class=\"fx-crumb-panel__title\">{{ sw.title }}</div>\n @if (sw.filterPlaceholder) {\n <input\n type=\"search\"\n class=\"fx-crumb-panel__filter\"\n [placeholder]=\"sw.filterPlaceholder\"\n [attr.aria-label]=\"sw.filterPlaceholder\"\n [value]=\"keyword()\"\n (input)=\"onFilter(crumb, $any($event.target).value)\"\n (click)=\"$event.stopPropagation()\"\n />\n }\n @if (loading() && options().length === 0) {\n <div class=\"fx-crumb-panel__skeleton\" aria-busy=\"true\">\n <span></span><span></span><span></span>\n </div>\n } @else if (options().length === 0) {\n <div class=\"fx-crumb-panel__empty\">{{ sw.emptyLabel || '\u2014' }}</div>\n }\n @for (opt of options(); track opt.value) {\n <button\n type=\"button\"\n role=\"option\"\n class=\"fx-crumb-panel__opt\"\n [class.fx-crumb-panel__opt--on]=\"opt.value === sw.selected\"\n [attr.aria-selected]=\"opt.value === sw.selected\"\n (click)=\"pick(opt, $event)\"\n >\n <span class=\"truncate\">{{ opt.label }}</span>\n @if (opt.meta) {\n <span class=\"fx-crumb-panel__meta\">{{ opt.meta }}</span>\n }\n @if (opt.value === sw.selected) {\n <fx-ui-hero-icon icon=\"check\" [size]=\"13\" class=\"flex shrink-0 text-strong\" />\n }\n </button>\n }\n @if (sw.viewAll || sw.create) {\n <div class=\"fx-crumb-panel__rule\"></div>\n }\n @if (sw.viewAll; as more) {\n <button type=\"button\" class=\"fx-crumb-panel__more\" (click)=\"go($event, more.url)\">\n <fx-ui-hero-icon icon=\"ellipsis-horizontal\" [size]=\"13\" class=\"flex shrink-0 text-faint\" />\n {{ more.label }}\n </button>\n }\n @if (sw.create; as create) {\n <button type=\"button\" class=\"fx-crumb-panel__create\" (click)=\"go($event, create.url)\">\n <fx-ui-hero-icon icon=\"plus\" [size]=\"14\" class=\"flex shrink-0\" />\n {{ create.label }}\n </button>\n }\n </div>\n }\n }\n </span>\n } @else if (!last && crumb.url) {\n <a class=\"fx-crumb-link\" [href]=\"crumb.url\" (click)=\"go($event, crumb.url)\">{{ crumb.label }}</a>\n } @else {\n <span class=\"fx-crumb-current\">{{ crumb.label }}</span>\n }\n }\n</nav>\n", styles: [":host{display:block;min-width:0}.fx-crumbs{display:flex;align-items:center;gap:7px;min-width:0;white-space:nowrap}.fx-crumb-sep{flex:0 0 auto;padding:0 2px;font-size:13px;color:rgb(var(--og-text-faint))}.fx-crumb-link{flex:0 0 auto;max-width:260px;overflow:hidden;text-overflow:ellipsis;font-size:var(--og-fs-sm);font-weight:500;letter-spacing:.005em;color:rgb(var(--og-text-muted));text-decoration:none;transition:color .15s}.fx-crumb-link:hover{color:rgb(var(--og-text))}.fx-crumb-current{min-width:0;overflow:hidden;text-overflow:ellipsis;font-size:var(--og-fs-sm);font-weight:600;color:rgb(var(--og-text))}.fx-crumb-chip{appearance:none;display:inline-flex;align-items:center;gap:8px;min-width:0;max-width:320px;padding:4px 12px;line-height:1.5;border:1px solid rgb(var(--og-border));border-radius:var(--og-radius-control);background:rgb(var(--og-surface));font-size:var(--og-fs-xs);font-weight:600;color:rgb(var(--og-text));box-shadow:0 1px 2px #0000000a;white-space:nowrap;cursor:pointer;transition:box-shadow .12s,border-color .12s}.fx-crumb-chip:hover,.fx-crumb-chip[aria-expanded=true]{box-shadow:0 2px 7px #0000001a}.fx-crumb-chip--mono{font-family:var(--og-font-mono)}.fx-crumb-panel{position:absolute;top:calc(100% + 8px);left:0;z-index:60;min-width:240px;max-width:min(380px,90vw);display:flex;flex-direction:column;padding:6px;background:rgb(var(--og-surface));border:1px solid rgb(var(--og-border));border-radius:var(--og-radius-card);box-shadow:0 10px 28px #00000024;text-align:left;white-space:nowrap;animation:fx-crumb-panel-in .16s ease-out}@keyframes fx-crumb-panel-in{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}@media(prefers-reduced-motion:reduce){.fx-crumb-panel{animation:none}}.fx-crumb-panel__title{padding:6px 12px 8px;margin-bottom:4px;font-size:var(--og-fs-2xs);letter-spacing:.02em;color:rgb(var(--og-text-faint));border-bottom:1px solid rgb(var(--og-border-subtle))}.fx-crumb-panel__filter{margin:2px 6px 6px;padding:6px 10px;border:1px solid rgb(var(--og-border));border-radius:var(--og-radius-control);background:rgb(var(--og-surface-sunken));color:rgb(var(--og-text));font-family:var(--og-font-body);font-size:var(--og-fs-xs);outline:none}.fx-crumb-panel__filter:focus{border-color:rgb(var(--og-accent))}.fx-crumb-panel__opt{appearance:none;width:100%;display:flex;align-items:center;gap:8px;padding:8px 12px;border:0;border-radius:var(--og-radius-control);background:transparent;font-family:var(--og-font-body);font-size:var(--og-fs-sm);font-weight:400;color:rgb(var(--og-text-muted));text-align:left;cursor:pointer;transition:background .12s}.fx-crumb-panel--mono .fx-crumb-panel__opt{font-family:var(--og-font-mono);font-size:var(--og-fs-xs)}.fx-crumb-panel__opt:hover{background:rgb(var(--og-surface-sunken))}.fx-crumb-panel__opt--on{font-weight:600;color:rgb(var(--og-text))}.fx-crumb-panel__meta{margin-left:auto;font-family:var(--og-font-mono);font-size:var(--og-fs-2xs);color:rgb(var(--og-text-faint))}.fx-crumb-panel__opt>fx-ui-hero-icon:last-child{margin-left:auto}.fx-crumb-panel__opt>.fx-crumb-panel__meta+fx-ui-hero-icon{margin-left:0}.fx-crumb-panel__empty{padding:8px 12px;font-size:var(--og-fs-xs);color:rgb(var(--og-text-faint))}.fx-crumb-panel__skeleton{display:flex;flex-direction:column;gap:8px;padding:8px 12px}.fx-crumb-panel__skeleton span{display:block;height:10px;border-radius:4px;background:rgb(var(--og-border-subtle));animation:fx-crumb-pulse 1.2s ease-in-out infinite}.fx-crumb-panel__skeleton span:nth-child(1){width:62%}.fx-crumb-panel__skeleton span:nth-child(2){width:48%;animation-delay:.15s}.fx-crumb-panel__skeleton span:nth-child(3){width:55%;animation-delay:.3s}@keyframes fx-crumb-pulse{0%,to{opacity:1}50%{opacity:.45}}.fx-crumb-panel__rule{margin:4px 0;border-top:1px solid rgb(var(--og-border-subtle))}.fx-crumb-panel__more{appearance:none;width:100%;display:flex;align-items:center;gap:8px;padding:7px 12px;border:0;border-radius:var(--og-radius-control);background:transparent;font-family:var(--og-font-body);font-size:var(--og-fs-xs);color:rgb(var(--og-text-muted));text-align:left;cursor:pointer}.fx-crumb-panel__more:hover{background:rgb(var(--og-surface-sunken));color:rgb(var(--og-text))}.fx-crumb-panel__create{appearance:none;width:calc(100% - 12px);margin:4px 6px 2px;display:flex;align-items:center;gap:8px;padding:9px 12px;border:0;border-radius:var(--og-radius-control);background:rgb(var(--og-accent-soft-bg));color:rgb(var(--og-accent-soft-fg));font-family:var(--og-font-body);font-size:var(--og-fs-sm);font-weight:600;text-align:left;cursor:pointer;transition:background .15s,color .15s}.fx-crumb-panel__create:hover{background:rgb(var(--og-accent));color:rgb(var(--og-accent-contrast))}.text-accent{color:rgb(var(--og-accent))}.text-faint{color:rgb(var(--og-text-faint))}.text-strong{color:rgb(var(--og-text))}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}\n"], dependencies: [{ kind: "component", type: HeroIconComponent, selector: "fx-ui-hero-icon", inputs: ["stroke", "icon", "solid", "outline", "size", "color", "class"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
5038
|
+
}
|
|
5039
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: BreadcrumbComponent, decorators: [{
|
|
5040
|
+
type: Component,
|
|
5041
|
+
args: [{ selector: 'fx-ui-breadcrumb', changeDetection: ChangeDetectionStrategy.OnPush, imports: [HeroIconComponent], template: "<nav aria-label=\"Breadcrumb\" class=\"fx-crumbs\">\n @for (crumb of crumbs(); track $index; let last = $last; let i = $index) {\n @if (i > 0) {\n <span aria-hidden=\"true\" class=\"fx-crumb-sep\">/</span>\n }\n @if (isChip(crumb)) {\n <span class=\"relative inline-flex min-w-0\">\n <button\n type=\"button\"\n class=\"fx-crumb-chip\"\n [class.fx-crumb-chip--mono]=\"crumb.mono\"\n [attr.aria-haspopup]=\"crumb.switcher ? 'listbox' : null\"\n [attr.aria-expanded]=\"crumb.switcher ? openIndex() === i : null\"\n [attr.title]=\"crumb.switcher?.title || crumb.label\"\n (click)=\"onChipClick(crumb, i, $event)\"\n >\n @if (crumb.icon) {\n <fx-ui-hero-icon [icon]=\"crumb.icon\" [size]=\"13\" class=\"flex shrink-0 text-accent\" />\n }\n <span class=\"truncate\">{{ crumb.label }}</span>\n @if (crumb.switcher) {\n <fx-ui-hero-icon icon=\"chevron-down\" [size]=\"10\" class=\"flex shrink-0 text-faint\" />\n }\n </button>\n\n @if (crumb.switcher; as sw) {\n @if (openIndex() === i) {\n <div role=\"listbox\" class=\"fx-crumb-panel\" [class.fx-crumb-panel--mono]=\"sw.mono\">\n <div class=\"fx-crumb-panel__title\">{{ sw.title }}</div>\n @if (sw.filterPlaceholder) {\n <input\n type=\"search\"\n class=\"fx-crumb-panel__filter\"\n [placeholder]=\"sw.filterPlaceholder\"\n [attr.aria-label]=\"sw.filterPlaceholder\"\n [value]=\"keyword()\"\n (input)=\"onFilter(crumb, $any($event.target).value)\"\n (click)=\"$event.stopPropagation()\"\n />\n }\n @if (loading() && options().length === 0) {\n <div class=\"fx-crumb-panel__skeleton\" aria-busy=\"true\">\n <span></span><span></span><span></span>\n </div>\n } @else if (options().length === 0) {\n <div class=\"fx-crumb-panel__empty\">{{ sw.emptyLabel || '\u2014' }}</div>\n }\n @for (opt of options(); track opt.value) {\n <button\n type=\"button\"\n role=\"option\"\n class=\"fx-crumb-panel__opt\"\n [class.fx-crumb-panel__opt--on]=\"opt.value === sw.selected\"\n [attr.aria-selected]=\"opt.value === sw.selected\"\n (click)=\"pick(opt, $event)\"\n >\n <span class=\"truncate\">{{ opt.label }}</span>\n @if (opt.meta) {\n <span class=\"fx-crumb-panel__meta\">{{ opt.meta }}</span>\n }\n @if (opt.value === sw.selected) {\n <fx-ui-hero-icon icon=\"check\" [size]=\"13\" class=\"flex shrink-0 text-strong\" />\n }\n </button>\n }\n @if (sw.viewAll || sw.create) {\n <div class=\"fx-crumb-panel__rule\"></div>\n }\n @if (sw.viewAll; as more) {\n <button type=\"button\" class=\"fx-crumb-panel__more\" (click)=\"go($event, more.url)\">\n <fx-ui-hero-icon icon=\"ellipsis-horizontal\" [size]=\"13\" class=\"flex shrink-0 text-faint\" />\n {{ more.label }}\n </button>\n }\n @if (sw.create; as create) {\n <button type=\"button\" class=\"fx-crumb-panel__create\" (click)=\"go($event, create.url)\">\n <fx-ui-hero-icon icon=\"plus\" [size]=\"14\" class=\"flex shrink-0\" />\n {{ create.label }}\n </button>\n }\n </div>\n }\n }\n </span>\n } @else if (!last && crumb.url) {\n <a class=\"fx-crumb-link\" [href]=\"crumb.url\" (click)=\"go($event, crumb.url)\">{{ crumb.label }}</a>\n } @else {\n <span class=\"fx-crumb-current\">{{ crumb.label }}</span>\n }\n }\n</nav>\n", styles: [":host{display:block;min-width:0}.fx-crumbs{display:flex;align-items:center;gap:7px;min-width:0;white-space:nowrap}.fx-crumb-sep{flex:0 0 auto;padding:0 2px;font-size:13px;color:rgb(var(--og-text-faint))}.fx-crumb-link{flex:0 0 auto;max-width:260px;overflow:hidden;text-overflow:ellipsis;font-size:var(--og-fs-sm);font-weight:500;letter-spacing:.005em;color:rgb(var(--og-text-muted));text-decoration:none;transition:color .15s}.fx-crumb-link:hover{color:rgb(var(--og-text))}.fx-crumb-current{min-width:0;overflow:hidden;text-overflow:ellipsis;font-size:var(--og-fs-sm);font-weight:600;color:rgb(var(--og-text))}.fx-crumb-chip{appearance:none;display:inline-flex;align-items:center;gap:8px;min-width:0;max-width:320px;padding:4px 12px;line-height:1.5;border:1px solid rgb(var(--og-border));border-radius:var(--og-radius-control);background:rgb(var(--og-surface));font-size:var(--og-fs-xs);font-weight:600;color:rgb(var(--og-text));box-shadow:0 1px 2px #0000000a;white-space:nowrap;cursor:pointer;transition:box-shadow .12s,border-color .12s}.fx-crumb-chip:hover,.fx-crumb-chip[aria-expanded=true]{box-shadow:0 2px 7px #0000001a}.fx-crumb-chip--mono{font-family:var(--og-font-mono)}.fx-crumb-panel{position:absolute;top:calc(100% + 8px);left:0;z-index:60;min-width:240px;max-width:min(380px,90vw);display:flex;flex-direction:column;padding:6px;background:rgb(var(--og-surface));border:1px solid rgb(var(--og-border));border-radius:var(--og-radius-card);box-shadow:0 10px 28px #00000024;text-align:left;white-space:nowrap;animation:fx-crumb-panel-in .16s ease-out}@keyframes fx-crumb-panel-in{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}@media(prefers-reduced-motion:reduce){.fx-crumb-panel{animation:none}}.fx-crumb-panel__title{padding:6px 12px 8px;margin-bottom:4px;font-size:var(--og-fs-2xs);letter-spacing:.02em;color:rgb(var(--og-text-faint));border-bottom:1px solid rgb(var(--og-border-subtle))}.fx-crumb-panel__filter{margin:2px 6px 6px;padding:6px 10px;border:1px solid rgb(var(--og-border));border-radius:var(--og-radius-control);background:rgb(var(--og-surface-sunken));color:rgb(var(--og-text));font-family:var(--og-font-body);font-size:var(--og-fs-xs);outline:none}.fx-crumb-panel__filter:focus{border-color:rgb(var(--og-accent))}.fx-crumb-panel__opt{appearance:none;width:100%;display:flex;align-items:center;gap:8px;padding:8px 12px;border:0;border-radius:var(--og-radius-control);background:transparent;font-family:var(--og-font-body);font-size:var(--og-fs-sm);font-weight:400;color:rgb(var(--og-text-muted));text-align:left;cursor:pointer;transition:background .12s}.fx-crumb-panel--mono .fx-crumb-panel__opt{font-family:var(--og-font-mono);font-size:var(--og-fs-xs)}.fx-crumb-panel__opt:hover{background:rgb(var(--og-surface-sunken))}.fx-crumb-panel__opt--on{font-weight:600;color:rgb(var(--og-text))}.fx-crumb-panel__meta{margin-left:auto;font-family:var(--og-font-mono);font-size:var(--og-fs-2xs);color:rgb(var(--og-text-faint))}.fx-crumb-panel__opt>fx-ui-hero-icon:last-child{margin-left:auto}.fx-crumb-panel__opt>.fx-crumb-panel__meta+fx-ui-hero-icon{margin-left:0}.fx-crumb-panel__empty{padding:8px 12px;font-size:var(--og-fs-xs);color:rgb(var(--og-text-faint))}.fx-crumb-panel__skeleton{display:flex;flex-direction:column;gap:8px;padding:8px 12px}.fx-crumb-panel__skeleton span{display:block;height:10px;border-radius:4px;background:rgb(var(--og-border-subtle));animation:fx-crumb-pulse 1.2s ease-in-out infinite}.fx-crumb-panel__skeleton span:nth-child(1){width:62%}.fx-crumb-panel__skeleton span:nth-child(2){width:48%;animation-delay:.15s}.fx-crumb-panel__skeleton span:nth-child(3){width:55%;animation-delay:.3s}@keyframes fx-crumb-pulse{0%,to{opacity:1}50%{opacity:.45}}.fx-crumb-panel__rule{margin:4px 0;border-top:1px solid rgb(var(--og-border-subtle))}.fx-crumb-panel__more{appearance:none;width:100%;display:flex;align-items:center;gap:8px;padding:7px 12px;border:0;border-radius:var(--og-radius-control);background:transparent;font-family:var(--og-font-body);font-size:var(--og-fs-xs);color:rgb(var(--og-text-muted));text-align:left;cursor:pointer}.fx-crumb-panel__more:hover{background:rgb(var(--og-surface-sunken));color:rgb(var(--og-text))}.fx-crumb-panel__create{appearance:none;width:calc(100% - 12px);margin:4px 6px 2px;display:flex;align-items:center;gap:8px;padding:9px 12px;border:0;border-radius:var(--og-radius-control);background:rgb(var(--og-accent-soft-bg));color:rgb(var(--og-accent-soft-fg));font-family:var(--og-font-body);font-size:var(--og-fs-sm);font-weight:600;text-align:left;cursor:pointer;transition:background .15s,color .15s}.fx-crumb-panel__create:hover{background:rgb(var(--og-accent));color:rgb(var(--og-accent-contrast))}.text-accent{color:rgb(var(--og-accent))}.text-faint{color:rgb(var(--og-text-faint))}.text-strong{color:rgb(var(--og-text))}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}\n"] }]
|
|
5042
|
+
}], propDecorators: { onDocumentClick: [{
|
|
5043
|
+
type: HostListener,
|
|
5044
|
+
args: ['document:click', ['$event']]
|
|
5045
|
+
}], onEscape: [{
|
|
5046
|
+
type: HostListener,
|
|
5047
|
+
args: ['document:keydown.escape']
|
|
5048
|
+
}] } });
|
|
5049
|
+
|
|
5050
|
+
/**
|
|
5051
|
+
* Topbar load status (design "Loading States", measured 04/09):
|
|
5052
|
+
*
|
|
5053
|
+
* ◌ Loading charts · 1 of 4 → while requests are pending
|
|
5054
|
+
* ● Up to date · just now ↻ → once the batch settled
|
|
5055
|
+
*
|
|
5056
|
+
* Mono 10.5px; muted while loading, faint when idle; 11px spinner (2px ring,
|
|
5057
|
+
* accent top). With `bar` a 2px indeterminate strip is drawn along the top
|
|
5058
|
+
* edge of the nearest positioned ancestor (the topbar) while loading.
|
|
5059
|
+
* Labels are caller-translated inputs; the ↻ button emits `reload`.
|
|
5060
|
+
*/
|
|
5061
|
+
class LoadStatusComponent {
|
|
5062
|
+
status = inject(FxLoadStatusService);
|
|
5063
|
+
loadingLabel = input('Loading', ...(ngDevMode ? [{ debugName: "loadingLabel" }] : /* istanbul ignore next */ []));
|
|
5064
|
+
ofLabel = input('of', ...(ngDevMode ? [{ debugName: "ofLabel" }] : /* istanbul ignore next */ []));
|
|
5065
|
+
upToDateLabel = input('Up to date', ...(ngDevMode ? [{ debugName: "upToDateLabel" }] : /* istanbul ignore next */ []));
|
|
5066
|
+
justNowLabel = input('just now', ...(ngDevMode ? [{ debugName: "justNowLabel" }] : /* istanbul ignore next */ []));
|
|
5067
|
+
/** `{n}` is replaced by the minute count. */
|
|
5068
|
+
minutesAgoLabel = input('{n} min ago', ...(ngDevMode ? [{ debugName: "minutesAgoLabel" }] : /* istanbul ignore next */ []));
|
|
5069
|
+
reloadLabel = input('Reload data', ...(ngDevMode ? [{ debugName: "reloadLabel" }] : /* istanbul ignore next */ []));
|
|
5070
|
+
/** Draw the 2px indeterminate bar along the top of the positioned ancestor. */
|
|
5071
|
+
bar = input(false, ...(ngDevMode ? [{ debugName: "bar" }] : /* istanbul ignore next */ []));
|
|
5072
|
+
/** Show the ↻ button. */
|
|
5073
|
+
showReload = input(true, ...(ngDevMode ? [{ debugName: "showReload" }] : /* istanbul ignore next */ []));
|
|
5074
|
+
reload = output();
|
|
5075
|
+
/** Ticks every 30 s so "just now" ages into "N min ago" without traffic. */
|
|
5076
|
+
tick = signal(0, ...(ngDevMode ? [{ debugName: "tick" }] : /* istanbul ignore next */ []));
|
|
5077
|
+
constructor() {
|
|
5078
|
+
const timer = setInterval(() => this.tick.update((n) => n + 1), 30_000);
|
|
5079
|
+
inject(DestroyRef).onDestroy(() => clearInterval(timer));
|
|
5080
|
+
}
|
|
5081
|
+
loadingText = computed(() => {
|
|
5082
|
+
const total = Math.max(this.status.total(), 1);
|
|
5083
|
+
const done = Math.min(this.status.done(), total);
|
|
5084
|
+
const label = this.status.current();
|
|
5085
|
+
return `${this.loadingLabel()}${label ? ' ' + label : ''} · ${done} ${this.ofLabel()} ${total}`;
|
|
5086
|
+
}, ...(ngDevMode ? [{ debugName: "loadingText" }] : /* istanbul ignore next */ []));
|
|
5087
|
+
idleText = computed(() => {
|
|
5088
|
+
this.tick();
|
|
5089
|
+
const at = this.status.lastUpdated();
|
|
5090
|
+
if (!at)
|
|
5091
|
+
return this.upToDateLabel();
|
|
5092
|
+
const minutes = Math.floor((Date.now() - at.getTime()) / 60_000);
|
|
5093
|
+
const when = minutes < 1 ? this.justNowLabel() : this.minutesAgoLabel().replace('{n}', String(minutes));
|
|
5094
|
+
return `${this.upToDateLabel()} · ${when}`;
|
|
5095
|
+
}, ...(ngDevMode ? [{ debugName: "idleText" }] : /* istanbul ignore next */ []));
|
|
5096
|
+
onReload() {
|
|
5097
|
+
this.reload.emit();
|
|
5098
|
+
this.status.requestReload();
|
|
5099
|
+
}
|
|
5100
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: LoadStatusComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5101
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: LoadStatusComponent, isStandalone: true, selector: "fx-ui-load-status", inputs: { loadingLabel: { classPropertyName: "loadingLabel", publicName: "loadingLabel", isSignal: true, isRequired: false, transformFunction: null }, ofLabel: { classPropertyName: "ofLabel", publicName: "ofLabel", isSignal: true, isRequired: false, transformFunction: null }, upToDateLabel: { classPropertyName: "upToDateLabel", publicName: "upToDateLabel", isSignal: true, isRequired: false, transformFunction: null }, justNowLabel: { classPropertyName: "justNowLabel", publicName: "justNowLabel", isSignal: true, isRequired: false, transformFunction: null }, minutesAgoLabel: { classPropertyName: "minutesAgoLabel", publicName: "minutesAgoLabel", isSignal: true, isRequired: false, transformFunction: null }, reloadLabel: { classPropertyName: "reloadLabel", publicName: "reloadLabel", isSignal: true, isRequired: false, transformFunction: null }, bar: { classPropertyName: "bar", publicName: "bar", isSignal: true, isRequired: false, transformFunction: null }, showReload: { classPropertyName: "showReload", publicName: "showReload", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { reload: "reload" }, ngImport: i0, template: "@if (bar() && status.loading()) {\n <div class=\"fx-load-bar\" aria-hidden=\"true\"><div class=\"fx-load-bar__run\"></div></div>\n}\n<div class=\"fx-load-status\" role=\"status\" aria-live=\"polite\" [class.fx-load-status--idle]=\"!status.loading()\">\n @if (status.loading()) {\n <span class=\"fx-load-spinner\" aria-hidden=\"true\"></span>\n <span class=\"truncate\">{{ loadingText() }}</span>\n } @else {\n <span class=\"fx-load-dot\" aria-hidden=\"true\">\u25CF</span>\n <span class=\"truncate\">{{ idleText() }}</span>\n }\n</div>\n@if (showReload()) {\n <button type=\"button\" class=\"fx-load-reload\" [attr.aria-label]=\"reloadLabel()\" [title]=\"reloadLabel()\" (click)=\"onReload()\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M21 12a9 9 0 1 1-3.1-6.8\" />\n <path d=\"M21 3v6h-6\" />\n </svg>\n </button>\n}\n", styles: [":host{display:inline-flex;align-items:center;gap:8px;min-width:0}.fx-load-status{display:inline-flex;align-items:center;gap:7px;min-width:0;font-family:var(--og-font-mono);font-size:var(--og-fs-2xs);color:rgb(var(--og-text-muted));white-space:nowrap}.fx-load-status--idle{color:rgb(var(--og-text-faint))}.fx-load-dot{color:rgb(var(--og-positive))}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.fx-load-spinner{display:inline-block;flex:none;width:11px;height:11px;border-radius:50%;border:2px solid rgb(var(--og-text) / .14);border-top-color:rgb(var(--og-accent));animation:fx-load-spin .8s linear infinite}@keyframes fx-load-spin{to{transform:rotate(360deg)}}.fx-load-reload{appearance:none;display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;padding:0;border:0;border-radius:var(--og-radius-control);background:transparent;color:rgb(var(--og-text-muted));cursor:pointer;transition:background .12s,color .12s}.fx-load-reload:hover{background:rgb(var(--og-surface-sunken));color:rgb(var(--og-text))}.fx-load-bar{position:absolute;left:0;right:0;top:0;height:2px;overflow:hidden;background:rgb(var(--og-accent) / .18);pointer-events:none}.fx-load-bar__run{width:38%;height:100%;border-radius:2px;background:rgb(var(--og-accent));animation:fx-load-indet 1.15s cubic-bezier(.4,0,.2,1) infinite}@keyframes fx-load-indet{0%{transform:translate(-100%)}60%{transform:translate(160%)}to{transform:translate(160%)}}@media(prefers-reduced-motion:reduce){.fx-load-spinner,.fx-load-bar__run{animation:none}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
5102
|
+
}
|
|
5103
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: LoadStatusComponent, decorators: [{
|
|
5104
|
+
type: Component,
|
|
5105
|
+
args: [{ selector: 'fx-ui-load-status', changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (bar() && status.loading()) {\n <div class=\"fx-load-bar\" aria-hidden=\"true\"><div class=\"fx-load-bar__run\"></div></div>\n}\n<div class=\"fx-load-status\" role=\"status\" aria-live=\"polite\" [class.fx-load-status--idle]=\"!status.loading()\">\n @if (status.loading()) {\n <span class=\"fx-load-spinner\" aria-hidden=\"true\"></span>\n <span class=\"truncate\">{{ loadingText() }}</span>\n } @else {\n <span class=\"fx-load-dot\" aria-hidden=\"true\">\u25CF</span>\n <span class=\"truncate\">{{ idleText() }}</span>\n }\n</div>\n@if (showReload()) {\n <button type=\"button\" class=\"fx-load-reload\" [attr.aria-label]=\"reloadLabel()\" [title]=\"reloadLabel()\" (click)=\"onReload()\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M21 12a9 9 0 1 1-3.1-6.8\" />\n <path d=\"M21 3v6h-6\" />\n </svg>\n </button>\n}\n", styles: [":host{display:inline-flex;align-items:center;gap:8px;min-width:0}.fx-load-status{display:inline-flex;align-items:center;gap:7px;min-width:0;font-family:var(--og-font-mono);font-size:var(--og-fs-2xs);color:rgb(var(--og-text-muted));white-space:nowrap}.fx-load-status--idle{color:rgb(var(--og-text-faint))}.fx-load-dot{color:rgb(var(--og-positive))}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.fx-load-spinner{display:inline-block;flex:none;width:11px;height:11px;border-radius:50%;border:2px solid rgb(var(--og-text) / .14);border-top-color:rgb(var(--og-accent));animation:fx-load-spin .8s linear infinite}@keyframes fx-load-spin{to{transform:rotate(360deg)}}.fx-load-reload{appearance:none;display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;padding:0;border:0;border-radius:var(--og-radius-control);background:transparent;color:rgb(var(--og-text-muted));cursor:pointer;transition:background .12s,color .12s}.fx-load-reload:hover{background:rgb(var(--og-surface-sunken));color:rgb(var(--og-text))}.fx-load-bar{position:absolute;left:0;right:0;top:0;height:2px;overflow:hidden;background:rgb(var(--og-accent) / .18);pointer-events:none}.fx-load-bar__run{width:38%;height:100%;border-radius:2px;background:rgb(var(--og-accent));animation:fx-load-indet 1.15s cubic-bezier(.4,0,.2,1) infinite}@keyframes fx-load-indet{0%{transform:translate(-100%)}60%{transform:translate(160%)}to{transform:translate(160%)}}@media(prefers-reduced-motion:reduce){.fx-load-spinner,.fx-load-bar__run{animation:none}}\n"] }]
|
|
5106
|
+
}], ctorParameters: () => [], propDecorators: { loadingLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadingLabel", required: false }] }], ofLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ofLabel", required: false }] }], upToDateLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "upToDateLabel", required: false }] }], justNowLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "justNowLabel", required: false }] }], minutesAgoLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "minutesAgoLabel", required: false }] }], reloadLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "reloadLabel", required: false }] }], bar: [{ type: i0.Input, args: [{ isSignal: true, alias: "bar", required: false }] }], showReload: [{ type: i0.Input, args: [{ isSignal: true, alias: "showReload", required: false }] }], reload: [{ type: i0.Output, args: ["reload"] }] } });
|
|
5107
|
+
|
|
4715
5108
|
/**
|
|
4716
5109
|
* Wraps UI that is rendered from PLACEHOLDER data so the layout can be
|
|
4717
5110
|
* reviewed before its endpoint exists (anh Tú 29/08: "muốn có full giao diện
|
|
@@ -4784,7 +5177,7 @@ class ScoreBadgeComponent {
|
|
|
4784
5177
|
sizeClass() {
|
|
4785
5178
|
switch (this.size()) {
|
|
4786
5179
|
case 'sm':
|
|
4787
|
-
return 'text-xs px-normal py-[
|
|
5180
|
+
return 'text-xs px-normal py-[3px]';
|
|
4788
5181
|
case 'lg':
|
|
4789
5182
|
return 'text-2xl px-semi py-small font-semibold';
|
|
4790
5183
|
default:
|
|
@@ -4909,11 +5302,11 @@ class SegmentedComponent {
|
|
|
4909
5302
|
return this.variant() === 'pill' ? 'text-accent-contrast' : 'text-strong';
|
|
4910
5303
|
}
|
|
4911
5304
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: SegmentedComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4912
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: SegmentedComponent, isStandalone: true, selector: "fx-ui-segmented", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, fit: { classPropertyName: "fit", publicName: "fit", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, viewQueries: [{ propertyName: "optionEls", predicate: ["opt"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n class=\"relative inline-grid max-w-full grid-flow-col items-stretch p-[3px] bg-surface-sunken border border-border rounded-full\"\n [class]=\"gridClass()\"\n role=\"group\"\n [attr.aria-label]=\"ariaLabel()\"\n>\n <!-- Sliding thumb: one element that translates to the active option -->\n @if (activeIndex() >= 0) {\n <span\n aria-hidden=\"true\"\n class=\"fx-segmented__thumb absolute top-[3px] bottom-[3px] left-[3px] rounded-full pointer-events-none\"\n [class]=\"thumbClass()\"\n [style.width]=\"thumbWidth()\"\n [style.transform]=\"thumbTransform()\"\n ></span>\n }\n @for (option of options(); track option.value) {\n <button\n #opt\n type=\"button\"\n (click)=\"select(option)\"\n [disabled]=\"option.disabled\"\n [attr.aria-pressed]=\"option.value === value()\"\n class=\"relative z-[1] flex min-w-0 items-center justify-center gap-xs px-
|
|
5305
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: SegmentedComponent, isStandalone: true, selector: "fx-ui-segmented", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, fit: { classPropertyName: "fit", publicName: "fit", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, viewQueries: [{ propertyName: "optionEls", predicate: ["opt"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n class=\"relative inline-grid max-w-full grid-flow-col items-stretch p-[3px] bg-surface-sunken border border-border rounded-full\"\n [class]=\"gridClass()\"\n role=\"group\"\n [attr.aria-label]=\"ariaLabel()\"\n>\n <!-- Sliding thumb: one element that translates to the active option -->\n @if (activeIndex() >= 0) {\n <span\n aria-hidden=\"true\"\n class=\"fx-segmented__thumb absolute top-[3px] bottom-[3px] left-[3px] rounded-full pointer-events-none\"\n [class]=\"thumbClass()\"\n [style.width]=\"thumbWidth()\"\n [style.transform]=\"thumbTransform()\"\n ></span>\n }\n <!-- option 30px tall (7px \u00D7 18px padding): the text-box trim below collapses the\n glyph box to ~8px, so padding alone left the pill at 22px (anh T\u00FA 04/09) -->\n @for (option of options(); track option.value) {\n <button\n #opt\n type=\"button\"\n (click)=\"select(option)\"\n [disabled]=\"option.disabled\"\n [attr.aria-pressed]=\"option.value === value()\"\n class=\"relative z-[1] flex min-w-0 min-h-[30px] items-center justify-center gap-xs px-[18px] py-[7px] rounded-full text-[13px] leading-none font-medium whitespace-nowrap transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed\"\n [class]=\"optionClass(option)\"\n >\n @if (option.icon) {\n <fx-ui-hero-icon [icon]=\"option.icon\" [size]=\"14\" class=\"flex\" />\n }\n <!-- text-box trims Archivo's uneven leading \u2192 true optical centering\n (Chrome 128+/Safari 18.2+; older browsers ignore it harmlessly).\n truncate adds overflow:hidden, and the trimmed box ends at cap\n height / baseline \u2014 so the dot on \"i\" and the tail on \"y\" fall\n outside it and get clipped. py/-my pair gives both back without\n changing the control's height. -->\n <span class=\"min-w-0 truncate py-[3px] -my-[3px] [text-box:trim-both_cap_alphabetic]\">{{ option.label }}</span>\n @if (option.count !== undefined) {\n <span class=\"txt-tag-label px-small rounded-pill bg-neutral-soft text-neutral-soft-fg\">\n {{ option.count }}\n </span>\n }\n </button>\n }\n</div>\n", styles: ["@charset \"UTF-8\";.fx-segmented__thumb{transition:transform .2s cubic-bezier(.2,.9,.2,1),width .2s cubic-bezier(.2,.9,.2,1);will-change:transform}@media(prefers-reduced-motion:reduce){.fx-segmented__thumb{transition:none}}\n"], dependencies: [{ kind: "component", type: HeroIconComponent, selector: "fx-ui-hero-icon", inputs: ["stroke", "icon", "solid", "outline", "size", "color", "class"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4913
5306
|
}
|
|
4914
5307
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: SegmentedComponent, decorators: [{
|
|
4915
5308
|
type: Component,
|
|
4916
|
-
args: [{ selector: 'fx-ui-segmented', changeDetection: ChangeDetectionStrategy.OnPush, imports: [HeroIconComponent], template: "<div\n class=\"relative inline-grid max-w-full grid-flow-col items-stretch p-[3px] bg-surface-sunken border border-border rounded-full\"\n [class]=\"gridClass()\"\n role=\"group\"\n [attr.aria-label]=\"ariaLabel()\"\n>\n <!-- Sliding thumb: one element that translates to the active option -->\n @if (activeIndex() >= 0) {\n <span\n aria-hidden=\"true\"\n class=\"fx-segmented__thumb absolute top-[3px] bottom-[3px] left-[3px] rounded-full pointer-events-none\"\n [class]=\"thumbClass()\"\n [style.width]=\"thumbWidth()\"\n [style.transform]=\"thumbTransform()\"\n ></span>\n }\n @for (option of options(); track option.value) {\n <button\n #opt\n type=\"button\"\n (click)=\"select(option)\"\n [disabled]=\"option.disabled\"\n [attr.aria-pressed]=\"option.value === value()\"\n class=\"relative z-[1] flex min-w-0 items-center justify-center gap-xs px-
|
|
5309
|
+
args: [{ selector: 'fx-ui-segmented', changeDetection: ChangeDetectionStrategy.OnPush, imports: [HeroIconComponent], template: "<div\n class=\"relative inline-grid max-w-full grid-flow-col items-stretch p-[3px] bg-surface-sunken border border-border rounded-full\"\n [class]=\"gridClass()\"\n role=\"group\"\n [attr.aria-label]=\"ariaLabel()\"\n>\n <!-- Sliding thumb: one element that translates to the active option -->\n @if (activeIndex() >= 0) {\n <span\n aria-hidden=\"true\"\n class=\"fx-segmented__thumb absolute top-[3px] bottom-[3px] left-[3px] rounded-full pointer-events-none\"\n [class]=\"thumbClass()\"\n [style.width]=\"thumbWidth()\"\n [style.transform]=\"thumbTransform()\"\n ></span>\n }\n <!-- option 30px tall (7px \u00D7 18px padding): the text-box trim below collapses the\n glyph box to ~8px, so padding alone left the pill at 22px (anh T\u00FA 04/09) -->\n @for (option of options(); track option.value) {\n <button\n #opt\n type=\"button\"\n (click)=\"select(option)\"\n [disabled]=\"option.disabled\"\n [attr.aria-pressed]=\"option.value === value()\"\n class=\"relative z-[1] flex min-w-0 min-h-[30px] items-center justify-center gap-xs px-[18px] py-[7px] rounded-full text-[13px] leading-none font-medium whitespace-nowrap transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed\"\n [class]=\"optionClass(option)\"\n >\n @if (option.icon) {\n <fx-ui-hero-icon [icon]=\"option.icon\" [size]=\"14\" class=\"flex\" />\n }\n <!-- text-box trims Archivo's uneven leading \u2192 true optical centering\n (Chrome 128+/Safari 18.2+; older browsers ignore it harmlessly).\n truncate adds overflow:hidden, and the trimmed box ends at cap\n height / baseline \u2014 so the dot on \"i\" and the tail on \"y\" fall\n outside it and get clipped. py/-my pair gives both back without\n changing the control's height. -->\n <span class=\"min-w-0 truncate py-[3px] -my-[3px] [text-box:trim-both_cap_alphabetic]\">{{ option.label }}</span>\n @if (option.count !== undefined) {\n <span class=\"txt-tag-label px-small rounded-pill bg-neutral-soft text-neutral-soft-fg\">\n {{ option.count }}\n </span>\n }\n </button>\n }\n</div>\n", styles: ["@charset \"UTF-8\";.fx-segmented__thumb{transition:transform .2s cubic-bezier(.2,.9,.2,1),width .2s cubic-bezier(.2,.9,.2,1);will-change:transform}@media(prefers-reduced-motion:reduce){.fx-segmented__thumb{transition:none}}\n"] }]
|
|
4917
5310
|
}], ctorParameters: () => [], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], fit: [{ type: i0.Input, args: [{ isSignal: true, alias: "fit", required: false }] }], optionEls: [{ type: i0.ViewChildren, args: ['opt', { isSignal: true }] }] } });
|
|
4918
5311
|
|
|
4919
5312
|
/**
|
|
@@ -5245,11 +5638,11 @@ class RouteSkeletonComponent {
|
|
|
5245
5638
|
}
|
|
5246
5639
|
bars = range$1(7);
|
|
5247
5640
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: RouteSkeletonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5248
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: RouteSkeletonComponent, isStandalone: true, selector: "fx-ui-route-skeleton", inputs: { hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"w-full flex flex-col gap-xl\">\n @for (b of blocks(); track $index) {\n @switch (b.kind) {\n @case ('heading') {\n <div data-fx-skel=\"heading\" class=\"flex flex-col md:flex-row md:items-start justify-between gap-normal\">\n <div class=\"flex flex-col gap-small\">\n <div class=\"skeleton-load h-7 w-64\"></div>\n <div class=\"skeleton-load h-3 w-32\"></div>\n </div>\n @if (items(b, 2).length) {\n <div class=\"flex items-center gap-normal\">\n @for (a of items(b, 2); track a) {\n <div class=\"skeleton-load h-9 rounded-lg\" [class]=\"a === 0 ? 'w-28' : 'w-32'\"></div>\n }\n </div>\n }\n </div>\n }\n\n @case ('toolbar') {\n <div data-fx-skel=\"toolbar\" class=\"flex flex-wrap items-center gap-normal\">\n <div class=\"skeleton-load h-9 w-72 rounded-lg\"></div>\n @for (f of items(b, 2); track f) {\n <div class=\"skeleton-load h-7 rounded-full\" [class]=\"f % 2 ? 'w-24' : 'w-20'\"></div>\n }\n </div>\n }\n\n @case ('table') {\n <!-- B\u1EA3ng: m\u1ED7i \u00D4 l\u00E0 m\u1ED9t `.skeleton-load` ri\u00EAng, \u0111\u00FAng c\u00E1ch\n `fx-ui-table-cell [loading]` l\u00E0m tr\u00EAn b\u1EA3ng th\u1EADt. -->\n <div data-fx-skel=\"table\" class=\"card-common overflow-x-auto\">\n <table class=\"w-full\">\n <thead>\n <tr class=\"border-b border-border-default\">\n @for (c of cols(b); track c) {\n <th class=\"px-normal py-semi text-left\">\n <div class=\"skeleton-load h-3\" [style.width.%]=\"headWidth(c)\"></div>\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (r of rows(b); track r) {\n <tr class=\"border-b border-border-subtle\">\n @for (c of cols(b); track c) {\n <td class=\"px-normal py-semi align-middle\">\n <div class=\"skeleton-load h-4\" [style.width.%]=\"cellWidth(r, c)\"></div>\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n\n @case ('stat-cards') {\n <div data-fx-skel=\"stat-cards\" class=\"grid gap-normal grid-cols-2 md:grid-cols-4\">\n @for (i of items(b, 4); track i) {\n <div class=\"card-common flex flex-col gap-normal\">\n <div class=\"skeleton-load h-3 w-16\"></div>\n <div class=\"skeleton-load h-8 w-24\"></div>\n <div class=\"skeleton-load h-3 w-20\"></div>\n </div>\n }\n </div>\n }\n\n @case ('kpi-strip') {\n <!-- M\u1ED8T card chia c\u1ED9t (ki\u1EC3u app-dash-kpi-row), kh\u00F4ng ph\u1EA3i c\u00E1c tile r\u1EDDi -->\n <div data-fx-skel=\"kpi-strip\" class=\"card-common !p-0\">\n <div class=\"grid grid-cols-2 md:grid-cols-4\">\n @for (i of items(b, 4); track i) {\n <div\n class=\"flex flex-col gap-normal px-xl py-large border-border-subtle\"\n [class.border-l]=\"!$first\"\n >\n <div class=\"skeleton-load h-3 w-20\"></div>\n <div class=\"skeleton-load h-9 w-28\"></div>\n <div class=\"skeleton-load h-3 w-24\"></div>\n </div>\n }\n </div>\n </div>\n }\n\n @case ('panels') {\n <div data-fx-skel=\"panels\" class=\"grid gap-xl\" [class]=\"gridClass(b)\">\n @for (i of items(b, 2); track i) {\n <div class=\"card-common flex flex-col gap-large\">\n <div class=\"skeleton-load h-4 w-1/3\"></div>\n <div class=\"flex items-end gap-normal h-40\">\n @for (x of bars; track x) {\n <div class=\"skeleton-load flex-1 rounded-t\" [style.height.%]=\"barHeight(x)\"></div>\n }\n </div>\n </div>\n }\n </div>\n }\n\n @case ('cards') {\n <div data-fx-skel=\"cards\" class=\"grid gap-xl\" [class]=\"gridClass(b)\">\n @for (i of items(b, 6); track i) {\n <div class=\"card-common flex flex-col gap-normal\">\n <div class=\"flex items-center gap-normal\">\n <div class=\"skeleton-load w-9 h-9 rounded-lg shrink-0\"></div>\n <div class=\"flex flex-col gap-small flex-1 min-w-0\">\n <div class=\"skeleton-load h-4 w-3/4\"></div>\n <div class=\"skeleton-load h-3 w-1/2\"></div>\n </div>\n <div class=\"skeleton-load h-5 w-14 rounded-full shrink-0\"></div>\n </div>\n <div class=\"grid grid-cols-4 gap-normal pt-normal border-t border-border-subtle\">\n @for (m of [0, 1, 2, 3]; track m) {\n <div class=\"flex flex-col gap-small\">\n <div class=\"skeleton-load h-2.5 w-full\"></div>\n <div class=\"skeleton-load h-4 w-2/3\"></div>\n </div>\n }\n </div>\n </div>\n }\n </div>\n }\n\n @case ('card') {\n <div data-fx-skel=\"card\" class=\"card-common flex flex-col gap-large\">\n <div class=\"skeleton-load h-4 w-1/3\"></div>\n <div class=\"flex flex-col gap-normal\">\n @for (r of [0, 1, 2, 3]; track r) {\n <div class=\"skeleton-load h-3\" [style.width.%]=\"cellWidth(r, 1)\"></div>\n }\n </div>\n </div>\n }\n\n @case ('tabs') {\n <div data-fx-skel=\"tabs\" class=\"flex items-center gap-large border-b border-border-default pb-normal\">\n @for (t of items(b, 6); track t) {\n <div class=\"flex items-center gap-small\">\n <div class=\"skeleton-load w-4 h-4\"></div>\n <div class=\"skeleton-load h-3 w-16\"></div>\n </div>\n }\n </div>\n }\n\n @case ('facets') {\n <div data-fx-skel=\"facets\" class=\"fx-rs-row\">\n <div class=\"fx-rs-facets card-common overflow-hidden\">\n <fx-ui-skeleton-facets />\n </div>\n <div class=\"fx-rs-list card-common overflow-hidden\">\n <fx-ui-skeleton-list [rows]=\"6\" />\n </div>\n <div class=\"flex-1 min-w-0 overflow-hidden\">\n <fx-ui-skeleton-detail />\n </div>\n </div>\n }\n\n @case ('detail') {\n <fx-ui-skeleton-detail data-fx-skel=\"detail\" />\n }\n\n @case ('form') {\n <div data-fx-skel=\"form\" class=\"card-common flex flex-col gap-large\">\n @for (r of [0, 1, 2, 3]; track r) {\n <div class=\"flex flex-col gap-small\">\n <div class=\"skeleton-load h-3 w-24\"></div>\n <div class=\"skeleton-load h-9 w-full rounded-lg\"></div>\n </div>\n }\n <div class=\"flex justify-end gap-normal\">\n <div class=\"skeleton-load h-9 w-24 rounded-lg\"></div>\n <div class=\"skeleton-load h-9 w-28 rounded-lg\"></div>\n </div>\n </div>\n }\n }\n }\n</div>\n", styles: [":host{display:block;container-type:inline-size;container-name:fx-rs}.fx-rs-row{display:flex;flex-direction:column;gap:var(--spacing-xl, 1.5rem)}@container fx-rs (min-width:
|
|
5641
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: RouteSkeletonComponent, isStandalone: true, selector: "fx-ui-route-skeleton", inputs: { hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"w-full flex flex-col gap-xl\">\n @for (b of blocks(); track $index) {\n @switch (b.kind) {\n @case ('heading') {\n <div data-fx-skel=\"heading\" class=\"flex flex-col md:flex-row md:items-start justify-between gap-normal\">\n <div class=\"flex flex-col gap-small\">\n <div class=\"skeleton-load h-7 w-64\"></div>\n <div class=\"skeleton-load h-3 w-32\"></div>\n </div>\n @if (items(b, 2).length) {\n <div class=\"flex items-center gap-normal\">\n @for (a of items(b, 2); track a) {\n <div class=\"skeleton-load h-9 rounded-lg\" [class]=\"a === 0 ? 'w-28' : 'w-32'\"></div>\n }\n </div>\n }\n </div>\n }\n\n @case ('toolbar') {\n <div data-fx-skel=\"toolbar\" class=\"flex flex-wrap items-center gap-normal\">\n <div class=\"skeleton-load h-9 w-72 rounded-lg\"></div>\n @for (f of items(b, 2); track f) {\n <div class=\"skeleton-load h-7 rounded-full\" [class]=\"f % 2 ? 'w-24' : 'w-20'\"></div>\n }\n </div>\n }\n\n @case ('table') {\n <!-- B\u1EA3ng: m\u1ED7i \u00D4 l\u00E0 m\u1ED9t `.skeleton-load` ri\u00EAng, \u0111\u00FAng c\u00E1ch\n `fx-ui-table-cell [loading]` l\u00E0m tr\u00EAn b\u1EA3ng th\u1EADt. -->\n <div data-fx-skel=\"table\" class=\"card-common overflow-x-auto\">\n <table class=\"w-full\">\n <thead>\n <tr class=\"border-b border-border-default\">\n @for (c of cols(b); track c) {\n <th class=\"px-normal py-semi text-left\">\n <div class=\"skeleton-load h-3\" [style.width.%]=\"headWidth(c)\"></div>\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (r of rows(b); track r) {\n <tr class=\"border-b border-border-subtle\">\n @for (c of cols(b); track c) {\n <td class=\"px-normal py-semi align-middle\">\n <div class=\"skeleton-load h-4\" [style.width.%]=\"cellWidth(r, c)\"></div>\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n\n @case ('stat-cards') {\n <div data-fx-skel=\"stat-cards\" class=\"grid gap-normal grid-cols-2 md:grid-cols-4\">\n @for (i of items(b, 4); track i) {\n <div class=\"card-common flex flex-col gap-normal\">\n <div class=\"skeleton-load h-3 w-16\"></div>\n <div class=\"skeleton-load h-8 w-24\"></div>\n <div class=\"skeleton-load h-3 w-20\"></div>\n </div>\n }\n </div>\n }\n\n @case ('kpi-strip') {\n <!-- M\u1ED8T card chia c\u1ED9t (ki\u1EC3u app-dash-kpi-row), kh\u00F4ng ph\u1EA3i c\u00E1c tile r\u1EDDi -->\n <div data-fx-skel=\"kpi-strip\" class=\"card-common !p-0\">\n <div class=\"grid grid-cols-2 md:grid-cols-4\">\n @for (i of items(b, 4); track i) {\n <div\n class=\"flex flex-col gap-normal px-xl py-large border-border-subtle\"\n [class.border-l]=\"!$first\"\n >\n <div class=\"skeleton-load h-3 w-20\"></div>\n <div class=\"skeleton-load h-9 w-28\"></div>\n <div class=\"skeleton-load h-3 w-24\"></div>\n </div>\n }\n </div>\n </div>\n }\n\n @case ('panels') {\n <div data-fx-skel=\"panels\" class=\"grid gap-xl\" [class]=\"gridClass(b)\">\n @for (i of items(b, 2); track i) {\n <div class=\"card-common flex flex-col gap-large\">\n <div class=\"skeleton-load h-4 w-1/3\"></div>\n <div class=\"flex items-end gap-normal h-40\">\n @for (x of bars; track x) {\n <div class=\"skeleton-load flex-1 rounded-t\" [style.height.%]=\"barHeight(x)\"></div>\n }\n </div>\n </div>\n }\n </div>\n }\n\n @case ('cards') {\n <div data-fx-skel=\"cards\" class=\"grid gap-xl\" [class]=\"gridClass(b)\">\n @for (i of items(b, 6); track i) {\n <div class=\"card-common flex flex-col gap-normal\">\n <div class=\"flex items-center gap-normal\">\n <div class=\"skeleton-load w-9 h-9 rounded-lg shrink-0\"></div>\n <div class=\"flex flex-col gap-small flex-1 min-w-0\">\n <div class=\"skeleton-load h-4 w-3/4\"></div>\n <div class=\"skeleton-load h-3 w-1/2\"></div>\n </div>\n <div class=\"skeleton-load h-5 w-14 rounded-full shrink-0\"></div>\n </div>\n <div class=\"grid grid-cols-4 gap-normal pt-normal border-t border-border-subtle\">\n @for (m of [0, 1, 2, 3]; track m) {\n <div class=\"flex flex-col gap-small\">\n <div class=\"skeleton-load h-2.5 w-full\"></div>\n <div class=\"skeleton-load h-4 w-2/3\"></div>\n </div>\n }\n </div>\n </div>\n }\n </div>\n }\n\n @case ('card') {\n <div data-fx-skel=\"card\" class=\"card-common flex flex-col gap-large\">\n <div class=\"skeleton-load h-4 w-1/3\"></div>\n <div class=\"flex flex-col gap-normal\">\n @for (r of [0, 1, 2, 3]; track r) {\n <div class=\"skeleton-load h-3\" [style.width.%]=\"cellWidth(r, 1)\"></div>\n }\n </div>\n </div>\n }\n\n @case ('tabs') {\n <div data-fx-skel=\"tabs\" class=\"flex items-center gap-large border-b border-border-default pb-normal\">\n @for (t of items(b, 6); track t) {\n <div class=\"flex items-center gap-small\">\n <div class=\"skeleton-load w-4 h-4\"></div>\n <div class=\"skeleton-load h-3 w-16\"></div>\n </div>\n }\n </div>\n }\n\n @case ('facets') {\n <div data-fx-skel=\"facets\" class=\"fx-rs-row\">\n <div class=\"fx-rs-facets card-common overflow-hidden\">\n <fx-ui-skeleton-facets />\n </div>\n <div class=\"fx-rs-list card-common overflow-hidden\">\n <fx-ui-skeleton-list [rows]=\"6\" />\n </div>\n <div class=\"flex-1 min-w-0 overflow-hidden\">\n <fx-ui-skeleton-detail />\n </div>\n </div>\n }\n\n @case ('detail') {\n <fx-ui-skeleton-detail data-fx-skel=\"detail\" />\n }\n\n @case ('form') {\n <div data-fx-skel=\"form\" class=\"card-common flex flex-col gap-large\">\n @for (r of [0, 1, 2, 3]; track r) {\n <div class=\"flex flex-col gap-small\">\n <div class=\"skeleton-load h-3 w-24\"></div>\n <div class=\"skeleton-load h-9 w-full rounded-lg\"></div>\n </div>\n }\n <div class=\"flex justify-end gap-normal\">\n <div class=\"skeleton-load h-9 w-24 rounded-lg\"></div>\n <div class=\"skeleton-load h-9 w-28 rounded-lg\"></div>\n </div>\n </div>\n }\n }\n }\n</div>\n", styles: [":host{display:block;container-type:inline-size;container-name:fx-rs}.fx-rs-row{display:flex;flex-direction:column;gap:var(--spacing-xl, 1.5rem)}@container fx-rs (min-width: 720px){.fx-rs-row{flex-direction:row;align-items:stretch;height:calc(100vh - 300px);min-height:420px}.fx-rs-facets{flex:0 1 230px;min-width:200px;max-width:260px}.fx-rs-list{flex:0 1 420px;min-width:320px;max-width:420px}}\n"], dependencies: [{ kind: "component", type: SkeletonListComponent, selector: "fx-ui-skeleton-list", inputs: ["rows"] }, { kind: "component", type: SkeletonDetailComponent, selector: "fx-ui-skeleton-detail", inputs: ["cards", "rows"] }, { kind: "component", type: SkeletonFacetsComponent, selector: "fx-ui-skeleton-facets", inputs: ["groups", "rows"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
5249
5642
|
}
|
|
5250
5643
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: RouteSkeletonComponent, decorators: [{
|
|
5251
5644
|
type: Component,
|
|
5252
|
-
args: [{ selector: 'fx-ui-route-skeleton', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SkeletonListComponent, SkeletonDetailComponent, SkeletonFacetsComponent], template: "<div class=\"w-full flex flex-col gap-xl\">\n @for (b of blocks(); track $index) {\n @switch (b.kind) {\n @case ('heading') {\n <div data-fx-skel=\"heading\" class=\"flex flex-col md:flex-row md:items-start justify-between gap-normal\">\n <div class=\"flex flex-col gap-small\">\n <div class=\"skeleton-load h-7 w-64\"></div>\n <div class=\"skeleton-load h-3 w-32\"></div>\n </div>\n @if (items(b, 2).length) {\n <div class=\"flex items-center gap-normal\">\n @for (a of items(b, 2); track a) {\n <div class=\"skeleton-load h-9 rounded-lg\" [class]=\"a === 0 ? 'w-28' : 'w-32'\"></div>\n }\n </div>\n }\n </div>\n }\n\n @case ('toolbar') {\n <div data-fx-skel=\"toolbar\" class=\"flex flex-wrap items-center gap-normal\">\n <div class=\"skeleton-load h-9 w-72 rounded-lg\"></div>\n @for (f of items(b, 2); track f) {\n <div class=\"skeleton-load h-7 rounded-full\" [class]=\"f % 2 ? 'w-24' : 'w-20'\"></div>\n }\n </div>\n }\n\n @case ('table') {\n <!-- B\u1EA3ng: m\u1ED7i \u00D4 l\u00E0 m\u1ED9t `.skeleton-load` ri\u00EAng, \u0111\u00FAng c\u00E1ch\n `fx-ui-table-cell [loading]` l\u00E0m tr\u00EAn b\u1EA3ng th\u1EADt. -->\n <div data-fx-skel=\"table\" class=\"card-common overflow-x-auto\">\n <table class=\"w-full\">\n <thead>\n <tr class=\"border-b border-border-default\">\n @for (c of cols(b); track c) {\n <th class=\"px-normal py-semi text-left\">\n <div class=\"skeleton-load h-3\" [style.width.%]=\"headWidth(c)\"></div>\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (r of rows(b); track r) {\n <tr class=\"border-b border-border-subtle\">\n @for (c of cols(b); track c) {\n <td class=\"px-normal py-semi align-middle\">\n <div class=\"skeleton-load h-4\" [style.width.%]=\"cellWidth(r, c)\"></div>\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n\n @case ('stat-cards') {\n <div data-fx-skel=\"stat-cards\" class=\"grid gap-normal grid-cols-2 md:grid-cols-4\">\n @for (i of items(b, 4); track i) {\n <div class=\"card-common flex flex-col gap-normal\">\n <div class=\"skeleton-load h-3 w-16\"></div>\n <div class=\"skeleton-load h-8 w-24\"></div>\n <div class=\"skeleton-load h-3 w-20\"></div>\n </div>\n }\n </div>\n }\n\n @case ('kpi-strip') {\n <!-- M\u1ED8T card chia c\u1ED9t (ki\u1EC3u app-dash-kpi-row), kh\u00F4ng ph\u1EA3i c\u00E1c tile r\u1EDDi -->\n <div data-fx-skel=\"kpi-strip\" class=\"card-common !p-0\">\n <div class=\"grid grid-cols-2 md:grid-cols-4\">\n @for (i of items(b, 4); track i) {\n <div\n class=\"flex flex-col gap-normal px-xl py-large border-border-subtle\"\n [class.border-l]=\"!$first\"\n >\n <div class=\"skeleton-load h-3 w-20\"></div>\n <div class=\"skeleton-load h-9 w-28\"></div>\n <div class=\"skeleton-load h-3 w-24\"></div>\n </div>\n }\n </div>\n </div>\n }\n\n @case ('panels') {\n <div data-fx-skel=\"panels\" class=\"grid gap-xl\" [class]=\"gridClass(b)\">\n @for (i of items(b, 2); track i) {\n <div class=\"card-common flex flex-col gap-large\">\n <div class=\"skeleton-load h-4 w-1/3\"></div>\n <div class=\"flex items-end gap-normal h-40\">\n @for (x of bars; track x) {\n <div class=\"skeleton-load flex-1 rounded-t\" [style.height.%]=\"barHeight(x)\"></div>\n }\n </div>\n </div>\n }\n </div>\n }\n\n @case ('cards') {\n <div data-fx-skel=\"cards\" class=\"grid gap-xl\" [class]=\"gridClass(b)\">\n @for (i of items(b, 6); track i) {\n <div class=\"card-common flex flex-col gap-normal\">\n <div class=\"flex items-center gap-normal\">\n <div class=\"skeleton-load w-9 h-9 rounded-lg shrink-0\"></div>\n <div class=\"flex flex-col gap-small flex-1 min-w-0\">\n <div class=\"skeleton-load h-4 w-3/4\"></div>\n <div class=\"skeleton-load h-3 w-1/2\"></div>\n </div>\n <div class=\"skeleton-load h-5 w-14 rounded-full shrink-0\"></div>\n </div>\n <div class=\"grid grid-cols-4 gap-normal pt-normal border-t border-border-subtle\">\n @for (m of [0, 1, 2, 3]; track m) {\n <div class=\"flex flex-col gap-small\">\n <div class=\"skeleton-load h-2.5 w-full\"></div>\n <div class=\"skeleton-load h-4 w-2/3\"></div>\n </div>\n }\n </div>\n </div>\n }\n </div>\n }\n\n @case ('card') {\n <div data-fx-skel=\"card\" class=\"card-common flex flex-col gap-large\">\n <div class=\"skeleton-load h-4 w-1/3\"></div>\n <div class=\"flex flex-col gap-normal\">\n @for (r of [0, 1, 2, 3]; track r) {\n <div class=\"skeleton-load h-3\" [style.width.%]=\"cellWidth(r, 1)\"></div>\n }\n </div>\n </div>\n }\n\n @case ('tabs') {\n <div data-fx-skel=\"tabs\" class=\"flex items-center gap-large border-b border-border-default pb-normal\">\n @for (t of items(b, 6); track t) {\n <div class=\"flex items-center gap-small\">\n <div class=\"skeleton-load w-4 h-4\"></div>\n <div class=\"skeleton-load h-3 w-16\"></div>\n </div>\n }\n </div>\n }\n\n @case ('facets') {\n <div data-fx-skel=\"facets\" class=\"fx-rs-row\">\n <div class=\"fx-rs-facets card-common overflow-hidden\">\n <fx-ui-skeleton-facets />\n </div>\n <div class=\"fx-rs-list card-common overflow-hidden\">\n <fx-ui-skeleton-list [rows]=\"6\" />\n </div>\n <div class=\"flex-1 min-w-0 overflow-hidden\">\n <fx-ui-skeleton-detail />\n </div>\n </div>\n }\n\n @case ('detail') {\n <fx-ui-skeleton-detail data-fx-skel=\"detail\" />\n }\n\n @case ('form') {\n <div data-fx-skel=\"form\" class=\"card-common flex flex-col gap-large\">\n @for (r of [0, 1, 2, 3]; track r) {\n <div class=\"flex flex-col gap-small\">\n <div class=\"skeleton-load h-3 w-24\"></div>\n <div class=\"skeleton-load h-9 w-full rounded-lg\"></div>\n </div>\n }\n <div class=\"flex justify-end gap-normal\">\n <div class=\"skeleton-load h-9 w-24 rounded-lg\"></div>\n <div class=\"skeleton-load h-9 w-28 rounded-lg\"></div>\n </div>\n </div>\n }\n }\n }\n</div>\n", styles: [":host{display:block;container-type:inline-size;container-name:fx-rs}.fx-rs-row{display:flex;flex-direction:column;gap:var(--spacing-xl, 1.5rem)}@container fx-rs (min-width:
|
|
5645
|
+
args: [{ selector: 'fx-ui-route-skeleton', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SkeletonListComponent, SkeletonDetailComponent, SkeletonFacetsComponent], template: "<div class=\"w-full flex flex-col gap-xl\">\n @for (b of blocks(); track $index) {\n @switch (b.kind) {\n @case ('heading') {\n <div data-fx-skel=\"heading\" class=\"flex flex-col md:flex-row md:items-start justify-between gap-normal\">\n <div class=\"flex flex-col gap-small\">\n <div class=\"skeleton-load h-7 w-64\"></div>\n <div class=\"skeleton-load h-3 w-32\"></div>\n </div>\n @if (items(b, 2).length) {\n <div class=\"flex items-center gap-normal\">\n @for (a of items(b, 2); track a) {\n <div class=\"skeleton-load h-9 rounded-lg\" [class]=\"a === 0 ? 'w-28' : 'w-32'\"></div>\n }\n </div>\n }\n </div>\n }\n\n @case ('toolbar') {\n <div data-fx-skel=\"toolbar\" class=\"flex flex-wrap items-center gap-normal\">\n <div class=\"skeleton-load h-9 w-72 rounded-lg\"></div>\n @for (f of items(b, 2); track f) {\n <div class=\"skeleton-load h-7 rounded-full\" [class]=\"f % 2 ? 'w-24' : 'w-20'\"></div>\n }\n </div>\n }\n\n @case ('table') {\n <!-- B\u1EA3ng: m\u1ED7i \u00D4 l\u00E0 m\u1ED9t `.skeleton-load` ri\u00EAng, \u0111\u00FAng c\u00E1ch\n `fx-ui-table-cell [loading]` l\u00E0m tr\u00EAn b\u1EA3ng th\u1EADt. -->\n <div data-fx-skel=\"table\" class=\"card-common overflow-x-auto\">\n <table class=\"w-full\">\n <thead>\n <tr class=\"border-b border-border-default\">\n @for (c of cols(b); track c) {\n <th class=\"px-normal py-semi text-left\">\n <div class=\"skeleton-load h-3\" [style.width.%]=\"headWidth(c)\"></div>\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (r of rows(b); track r) {\n <tr class=\"border-b border-border-subtle\">\n @for (c of cols(b); track c) {\n <td class=\"px-normal py-semi align-middle\">\n <div class=\"skeleton-load h-4\" [style.width.%]=\"cellWidth(r, c)\"></div>\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n\n @case ('stat-cards') {\n <div data-fx-skel=\"stat-cards\" class=\"grid gap-normal grid-cols-2 md:grid-cols-4\">\n @for (i of items(b, 4); track i) {\n <div class=\"card-common flex flex-col gap-normal\">\n <div class=\"skeleton-load h-3 w-16\"></div>\n <div class=\"skeleton-load h-8 w-24\"></div>\n <div class=\"skeleton-load h-3 w-20\"></div>\n </div>\n }\n </div>\n }\n\n @case ('kpi-strip') {\n <!-- M\u1ED8T card chia c\u1ED9t (ki\u1EC3u app-dash-kpi-row), kh\u00F4ng ph\u1EA3i c\u00E1c tile r\u1EDDi -->\n <div data-fx-skel=\"kpi-strip\" class=\"card-common !p-0\">\n <div class=\"grid grid-cols-2 md:grid-cols-4\">\n @for (i of items(b, 4); track i) {\n <div\n class=\"flex flex-col gap-normal px-xl py-large border-border-subtle\"\n [class.border-l]=\"!$first\"\n >\n <div class=\"skeleton-load h-3 w-20\"></div>\n <div class=\"skeleton-load h-9 w-28\"></div>\n <div class=\"skeleton-load h-3 w-24\"></div>\n </div>\n }\n </div>\n </div>\n }\n\n @case ('panels') {\n <div data-fx-skel=\"panels\" class=\"grid gap-xl\" [class]=\"gridClass(b)\">\n @for (i of items(b, 2); track i) {\n <div class=\"card-common flex flex-col gap-large\">\n <div class=\"skeleton-load h-4 w-1/3\"></div>\n <div class=\"flex items-end gap-normal h-40\">\n @for (x of bars; track x) {\n <div class=\"skeleton-load flex-1 rounded-t\" [style.height.%]=\"barHeight(x)\"></div>\n }\n </div>\n </div>\n }\n </div>\n }\n\n @case ('cards') {\n <div data-fx-skel=\"cards\" class=\"grid gap-xl\" [class]=\"gridClass(b)\">\n @for (i of items(b, 6); track i) {\n <div class=\"card-common flex flex-col gap-normal\">\n <div class=\"flex items-center gap-normal\">\n <div class=\"skeleton-load w-9 h-9 rounded-lg shrink-0\"></div>\n <div class=\"flex flex-col gap-small flex-1 min-w-0\">\n <div class=\"skeleton-load h-4 w-3/4\"></div>\n <div class=\"skeleton-load h-3 w-1/2\"></div>\n </div>\n <div class=\"skeleton-load h-5 w-14 rounded-full shrink-0\"></div>\n </div>\n <div class=\"grid grid-cols-4 gap-normal pt-normal border-t border-border-subtle\">\n @for (m of [0, 1, 2, 3]; track m) {\n <div class=\"flex flex-col gap-small\">\n <div class=\"skeleton-load h-2.5 w-full\"></div>\n <div class=\"skeleton-load h-4 w-2/3\"></div>\n </div>\n }\n </div>\n </div>\n }\n </div>\n }\n\n @case ('card') {\n <div data-fx-skel=\"card\" class=\"card-common flex flex-col gap-large\">\n <div class=\"skeleton-load h-4 w-1/3\"></div>\n <div class=\"flex flex-col gap-normal\">\n @for (r of [0, 1, 2, 3]; track r) {\n <div class=\"skeleton-load h-3\" [style.width.%]=\"cellWidth(r, 1)\"></div>\n }\n </div>\n </div>\n }\n\n @case ('tabs') {\n <div data-fx-skel=\"tabs\" class=\"flex items-center gap-large border-b border-border-default pb-normal\">\n @for (t of items(b, 6); track t) {\n <div class=\"flex items-center gap-small\">\n <div class=\"skeleton-load w-4 h-4\"></div>\n <div class=\"skeleton-load h-3 w-16\"></div>\n </div>\n }\n </div>\n }\n\n @case ('facets') {\n <div data-fx-skel=\"facets\" class=\"fx-rs-row\">\n <div class=\"fx-rs-facets card-common overflow-hidden\">\n <fx-ui-skeleton-facets />\n </div>\n <div class=\"fx-rs-list card-common overflow-hidden\">\n <fx-ui-skeleton-list [rows]=\"6\" />\n </div>\n <div class=\"flex-1 min-w-0 overflow-hidden\">\n <fx-ui-skeleton-detail />\n </div>\n </div>\n }\n\n @case ('detail') {\n <fx-ui-skeleton-detail data-fx-skel=\"detail\" />\n }\n\n @case ('form') {\n <div data-fx-skel=\"form\" class=\"card-common flex flex-col gap-large\">\n @for (r of [0, 1, 2, 3]; track r) {\n <div class=\"flex flex-col gap-small\">\n <div class=\"skeleton-load h-3 w-24\"></div>\n <div class=\"skeleton-load h-9 w-full rounded-lg\"></div>\n </div>\n }\n <div class=\"flex justify-end gap-normal\">\n <div class=\"skeleton-load h-9 w-24 rounded-lg\"></div>\n <div class=\"skeleton-load h-9 w-28 rounded-lg\"></div>\n </div>\n </div>\n }\n }\n }\n</div>\n", styles: [":host{display:block;container-type:inline-size;container-name:fx-rs}.fx-rs-row{display:flex;flex-direction:column;gap:var(--spacing-xl, 1.5rem)}@container fx-rs (min-width: 720px){.fx-rs-row{flex-direction:row;align-items:stretch;height:calc(100vh - 300px);min-height:420px}.fx-rs-facets{flex:0 1 230px;min-width:200px;max-width:260px}.fx-rs-list{flex:0 1 420px;min-width:320px;max-width:420px}}\n"] }]
|
|
5253
5646
|
}], propDecorators: { hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }] } });
|
|
5254
5647
|
function range$1(n) {
|
|
5255
5648
|
return Array.from({ length: Math.max(0, Number(n) || 0) }, (_, i) => i);
|
|
@@ -5328,6 +5721,8 @@ const FX_COMPONENTS = [
|
|
|
5328
5721
|
MenuComponent,
|
|
5329
5722
|
ThemePickerComponent,
|
|
5330
5723
|
PendingBadgeComponent,
|
|
5724
|
+
BreadcrumbComponent,
|
|
5725
|
+
LoadStatusComponent,
|
|
5331
5726
|
SampleFrameComponent,
|
|
5332
5727
|
ScoreBadgeComponent,
|
|
5333
5728
|
StatusDotComponent,
|
|
@@ -5348,6 +5743,126 @@ const DIALOGS_COMPONENT = [
|
|
|
5348
5743
|
ConfirmationDialogComponent
|
|
5349
5744
|
];
|
|
5350
5745
|
|
|
5746
|
+
/**
|
|
5747
|
+
* Staggered entrance for one element (GSAP).
|
|
5748
|
+
*
|
|
5749
|
+
* <fx-ui-section-card [fxReveal]="2">…</fx-ui-section-card>
|
|
5750
|
+
*
|
|
5751
|
+
* The host starts invisible and rises into place; `fxReveal` is its slot in
|
|
5752
|
+
* the stagger (× 60 ms). Elements that mount later (async lists) animate on
|
|
5753
|
+
* their own mount, so late data arrives with motion instead of a jump.
|
|
5754
|
+
* Reduced-motion users see the final state immediately. For a whole page use
|
|
5755
|
+
* `fxRevealGroup` on the container instead of numbering children by hand.
|
|
5756
|
+
*/
|
|
5757
|
+
class FxRevealDirective {
|
|
5758
|
+
/** Stagger slot; 0 = first. */
|
|
5759
|
+
fxReveal = input(0, ...(ngDevMode ? [{ debugName: "fxReveal" }] : /* istanbul ignore next */ []));
|
|
5760
|
+
el = inject(ElementRef).nativeElement;
|
|
5761
|
+
constructor() {
|
|
5762
|
+
if (!fxMotionAllowed())
|
|
5763
|
+
return;
|
|
5764
|
+
// Hide before first paint — otherwise the element flashes, then animates.
|
|
5765
|
+
this.el.style.opacity = '0';
|
|
5766
|
+
afterNextRender(() => fxRevealElement(this.el, Number(this.fxReveal()) || 0));
|
|
5767
|
+
}
|
|
5768
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: FxRevealDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
5769
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.20", type: FxRevealDirective, isStandalone: true, selector: "[fxReveal]", inputs: { fxReveal: { classPropertyName: "fxReveal", publicName: "fxReveal", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
|
|
5770
|
+
}
|
|
5771
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: FxRevealDirective, decorators: [{
|
|
5772
|
+
type: Directive,
|
|
5773
|
+
args: [{
|
|
5774
|
+
selector: '[fxReveal]',
|
|
5775
|
+
}]
|
|
5776
|
+
}], ctorParameters: () => [], propDecorators: { fxReveal: [{ type: i0.Input, args: [{ isSignal: true, alias: "fxReveal", required: false }] }] } });
|
|
5777
|
+
/** Run the standard reveal on an element at the given stagger slot. */
|
|
5778
|
+
function fxRevealElement(el, slot = 0) {
|
|
5779
|
+
gsap.fromTo(el, { opacity: 0, y: FX_MOTION.travelPx }, {
|
|
5780
|
+
opacity: 1,
|
|
5781
|
+
y: 0,
|
|
5782
|
+
duration: FX_MOTION.duration,
|
|
5783
|
+
delay: Math.min(Math.max(slot, 0), FX_MOTION.maxSlot) * FX_MOTION.stagger,
|
|
5784
|
+
ease: FX_MOTION.ease,
|
|
5785
|
+
clearProps: 'opacity,transform',
|
|
5786
|
+
overwrite: 'auto',
|
|
5787
|
+
});
|
|
5788
|
+
}
|
|
5789
|
+
|
|
5790
|
+
/**
|
|
5791
|
+
* One attribute on a page root reveals its sections top-to-bottom.
|
|
5792
|
+
*
|
|
5793
|
+
* <div fxRevealGroup> ← page root
|
|
5794
|
+
* <div>heading…</div> ← slot 0
|
|
5795
|
+
* <fx-ui-stat-cards …/> ← slot 1
|
|
5796
|
+
* <div class="grid …">…</div> ← slot 2 (the grid moves as one block)
|
|
5797
|
+
* </div>
|
|
5798
|
+
*
|
|
5799
|
+
* Direct element children present at first render get consecutive slots.
|
|
5800
|
+
* Children added later (a list that arrives after its request, an `@if` that
|
|
5801
|
+
* flips) are revealed as they mount, so async content never pops in flat.
|
|
5802
|
+
* Skeletons (`fx-ui-route-skeleton`, `[aria-busy]`) are left alone. Replaces
|
|
5803
|
+
* the page-level `animate.enter="fade-in-animation"` one-shot fade.
|
|
5804
|
+
*/
|
|
5805
|
+
class FxRevealGroupDirective {
|
|
5806
|
+
/** Cap on how many children get their own slot; the rest share the last one. */
|
|
5807
|
+
fxRevealGroup = input(undefined, ...(ngDevMode ? [{ debugName: "fxRevealGroup" }] : /* istanbul ignore next */ []));
|
|
5808
|
+
el = inject(ElementRef).nativeElement;
|
|
5809
|
+
observer;
|
|
5810
|
+
seen = new WeakSet();
|
|
5811
|
+
constructor() {
|
|
5812
|
+
if (!fxMotionAllowed())
|
|
5813
|
+
return;
|
|
5814
|
+
// Children are hidden right away so nothing paints before its reveal.
|
|
5815
|
+
for (const child of this.children())
|
|
5816
|
+
this.hide(child);
|
|
5817
|
+
afterNextRender(() => {
|
|
5818
|
+
const kids = this.children();
|
|
5819
|
+
kids.forEach((child, i) => this.reveal(child, i));
|
|
5820
|
+
this.observer = new MutationObserver((records) => {
|
|
5821
|
+
for (const r of records) {
|
|
5822
|
+
r.addedNodes.forEach((n) => {
|
|
5823
|
+
if (n instanceof HTMLElement && n.parentElement === this.el && this.eligible(n) && !this.seen.has(n)) {
|
|
5824
|
+
this.hide(n);
|
|
5825
|
+
this.reveal(n, 0);
|
|
5826
|
+
}
|
|
5827
|
+
});
|
|
5828
|
+
}
|
|
5829
|
+
});
|
|
5830
|
+
this.observer.observe(this.el, { childList: true });
|
|
5831
|
+
});
|
|
5832
|
+
}
|
|
5833
|
+
children() {
|
|
5834
|
+
return Array.from(this.el.children).filter((c) => c instanceof HTMLElement && this.eligible(c));
|
|
5835
|
+
}
|
|
5836
|
+
eligible(c) {
|
|
5837
|
+
const tag = c.tagName.toLowerCase();
|
|
5838
|
+
return tag !== 'fx-ui-route-skeleton' && tag !== 'script' && tag !== 'style' && !c.hasAttribute('aria-busy');
|
|
5839
|
+
}
|
|
5840
|
+
hide(c) {
|
|
5841
|
+
if (this.seen.has(c))
|
|
5842
|
+
return;
|
|
5843
|
+
this.seen.add(c);
|
|
5844
|
+
c.style.opacity = '0';
|
|
5845
|
+
}
|
|
5846
|
+
reveal(c, slot) {
|
|
5847
|
+
const cap = Number(this.fxRevealGroup());
|
|
5848
|
+
fxRevealElement(c, Number.isFinite(cap) && cap > 0 ? Math.min(slot, cap) : slot);
|
|
5849
|
+
}
|
|
5850
|
+
ngOnDestroy() {
|
|
5851
|
+
this.observer?.disconnect();
|
|
5852
|
+
}
|
|
5853
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: FxRevealGroupDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
5854
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.20", type: FxRevealGroupDirective, isStandalone: true, selector: "[fxRevealGroup]", inputs: { fxRevealGroup: { classPropertyName: "fxRevealGroup", publicName: "fxRevealGroup", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
|
|
5855
|
+
}
|
|
5856
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: FxRevealGroupDirective, decorators: [{
|
|
5857
|
+
type: Directive,
|
|
5858
|
+
args: [{
|
|
5859
|
+
selector: '[fxRevealGroup]',
|
|
5860
|
+
}]
|
|
5861
|
+
}], ctorParameters: () => [], propDecorators: { fxRevealGroup: [{ type: i0.Input, args: [{ isSignal: true, alias: "fxRevealGroup", required: false }] }] } });
|
|
5862
|
+
|
|
5863
|
+
/** Motion directives exported by UiModule (GSAP-based, reduced-motion aware). */
|
|
5864
|
+
const FX_MOTION_DIRECTIVES = [FxRevealDirective, FxRevealGroupDirective, FxCountUpDirective];
|
|
5865
|
+
|
|
5351
5866
|
/**
|
|
5352
5867
|
* Compatibility barrel over the (now standalone) core components.
|
|
5353
5868
|
*
|
|
@@ -5365,7 +5880,7 @@ class UiModule {
|
|
|
5365
5880
|
ReactiveFormsModule,
|
|
5366
5881
|
RouterModule,
|
|
5367
5882
|
HasPermissionDirective,
|
|
5368
|
-
TrimOnBlurDirective, InputComponent, SelectComponent, RadioButtonComponent, CheckboxComponent, DndUploadComponent, ButtonComponent, RadioButtonToggleComponent, DatetimePicker, LoadingPanel, SearchBarComponent, TabGroupComponent, TabComponent, HeroIconComponent, ToastComponent, ToastContainerComponent, TagComponent, SliderComponent, SwitchComponent, CircleProgressBar, TreeDiagram, TableCell, KanbanBoardComponent, DrawerComponent, SkeletonListComponent, SkeletonDetailComponent, StatCardsComponent, EmptyStateComponent, FilterChipsComponent, FilterPillsComponent, StageDotsComponent, CodeBlockComponent, ScreenshotFrameComponent, MeterComponent, SectionCardComponent, InfoFieldComponent, SeverityBadgesComponent, MasterDetailComponent, MenuComponent, ThemePickerComponent, PendingBadgeComponent, SampleFrameComponent, ScoreBadgeComponent, StatusDotComponent, SparkbarComponent, SegmentedComponent, ListRowComponent, BarListComponent, FacetsRailComponent, ProjectCardComponent, PipelineStepperComponent, SkeletonFacetsComponent, SkeletonDashboardComponent, SkeletonFrameComponent, RouteSkeletonComponent, ConfirmationDialogComponent], exports: [InputComponent, SelectComponent, RadioButtonComponent, CheckboxComponent, DndUploadComponent, ButtonComponent, RadioButtonToggleComponent, DatetimePicker, LoadingPanel, SearchBarComponent, TabGroupComponent, TabComponent, HeroIconComponent, ToastComponent, ToastContainerComponent, TagComponent, SliderComponent, SwitchComponent, CircleProgressBar, TreeDiagram, TableCell, KanbanBoardComponent, DrawerComponent, SkeletonListComponent, SkeletonDetailComponent, StatCardsComponent, EmptyStateComponent, FilterChipsComponent, FilterPillsComponent, StageDotsComponent, CodeBlockComponent, ScreenshotFrameComponent, MeterComponent, SectionCardComponent, InfoFieldComponent, SeverityBadgesComponent, MasterDetailComponent, MenuComponent, ThemePickerComponent, PendingBadgeComponent, SampleFrameComponent, ScoreBadgeComponent, StatusDotComponent, SparkbarComponent, SegmentedComponent, ListRowComponent, BarListComponent, FacetsRailComponent, ProjectCardComponent, PipelineStepperComponent, SkeletonFacetsComponent, SkeletonDashboardComponent, SkeletonFrameComponent, RouteSkeletonComponent, ConfirmationDialogComponent, FormsModule,
|
|
5883
|
+
TrimOnBlurDirective, InputComponent, SelectComponent, RadioButtonComponent, CheckboxComponent, DndUploadComponent, ButtonComponent, RadioButtonToggleComponent, DatetimePicker, LoadingPanel, SearchBarComponent, TabGroupComponent, TabComponent, HeroIconComponent, ToastComponent, ToastContainerComponent, TagComponent, SliderComponent, SwitchComponent, CircleProgressBar, TreeDiagram, TableCell, KanbanBoardComponent, DrawerComponent, SkeletonListComponent, SkeletonDetailComponent, StatCardsComponent, EmptyStateComponent, FilterChipsComponent, FilterPillsComponent, StageDotsComponent, CodeBlockComponent, ScreenshotFrameComponent, MeterComponent, SectionCardComponent, InfoFieldComponent, SeverityBadgesComponent, MasterDetailComponent, MenuComponent, ThemePickerComponent, PendingBadgeComponent, BreadcrumbComponent, LoadStatusComponent, SampleFrameComponent, ScoreBadgeComponent, StatusDotComponent, SparkbarComponent, SegmentedComponent, ListRowComponent, BarListComponent, FacetsRailComponent, ProjectCardComponent, PipelineStepperComponent, SkeletonFacetsComponent, SkeletonDashboardComponent, SkeletonFrameComponent, RouteSkeletonComponent, ConfirmationDialogComponent, FxRevealDirective, FxRevealGroupDirective, FxCountUpDirective], exports: [InputComponent, SelectComponent, RadioButtonComponent, CheckboxComponent, DndUploadComponent, ButtonComponent, RadioButtonToggleComponent, DatetimePicker, LoadingPanel, SearchBarComponent, TabGroupComponent, TabComponent, HeroIconComponent, ToastComponent, ToastContainerComponent, TagComponent, SliderComponent, SwitchComponent, CircleProgressBar, TreeDiagram, TableCell, KanbanBoardComponent, DrawerComponent, SkeletonListComponent, SkeletonDetailComponent, StatCardsComponent, EmptyStateComponent, FilterChipsComponent, FilterPillsComponent, StageDotsComponent, CodeBlockComponent, ScreenshotFrameComponent, MeterComponent, SectionCardComponent, InfoFieldComponent, SeverityBadgesComponent, MasterDetailComponent, MenuComponent, ThemePickerComponent, PendingBadgeComponent, BreadcrumbComponent, LoadStatusComponent, SampleFrameComponent, ScoreBadgeComponent, StatusDotComponent, SparkbarComponent, SegmentedComponent, ListRowComponent, BarListComponent, FacetsRailComponent, ProjectCardComponent, PipelineStepperComponent, SkeletonFacetsComponent, SkeletonDashboardComponent, SkeletonFrameComponent, RouteSkeletonComponent, ConfirmationDialogComponent, FxRevealDirective, FxRevealGroupDirective, FxCountUpDirective, FormsModule,
|
|
5369
5884
|
ReactiveFormsModule,
|
|
5370
5885
|
RouterModule,
|
|
5371
5886
|
HasPermissionDirective,
|
|
@@ -5387,10 +5902,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImpo
|
|
|
5387
5902
|
TrimOnBlurDirective,
|
|
5388
5903
|
...FX_COMPONENTS,
|
|
5389
5904
|
...DIALOGS_COMPONENT,
|
|
5905
|
+
...FX_MOTION_DIRECTIVES,
|
|
5390
5906
|
],
|
|
5391
5907
|
exports: [
|
|
5392
5908
|
...FX_COMPONENTS,
|
|
5393
5909
|
...DIALOGS_COMPONENT,
|
|
5910
|
+
...FX_MOTION_DIRECTIVES,
|
|
5394
5911
|
FormsModule,
|
|
5395
5912
|
ReactiveFormsModule,
|
|
5396
5913
|
RouterModule,
|
|
@@ -5408,5 +5925,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImpo
|
|
|
5408
5925
|
* Generated bundle index. Do not edit.
|
|
5409
5926
|
*/
|
|
5410
5927
|
|
|
5411
|
-
export { AuthInterceptor, AuthStateService, BarListComponent, BaseComponent, BaseDialogComponent, BaseMasterDetailComponent, BaseTableComponent, BreadcrumbService, ButtonComponent, CheckboxComponent, CircleProgressBar, CodeBlockComponent, ConfirmationDialogComponent, DatetimePicker, DndUploadComponent, DrawerComponent, EmptyStateComponent, FX_ICON_SPRITE_URL, FX_SKELETON_DATA_KEY, FacetsRailComponent, FilterChipsComponent, FilterPillsComponent, FxComponent, FxDialogService, FxLoadingService, FxRouteSkeletonService, FxStorageService, FxToastrService, FxUtils, FxValidators, HasPermissionDirective, HeroIconComponent, HttpLoaderFactory, HttpWrapper, InfoFieldComponent, InputComponent, KanbanBoardComponent, ListRowComponent, LoadingPanel, MasterDetailComponent, MenuComponent, MeterComponent, NotificationService, OG_LIGHT_THEMES, OG_PICKER_THEMES, OG_PICKER_THEME_META, OG_THEMES, OG_THEME_META, PendingBadgeComponent, PermissionGuard, PermissionService, PipelineStepperComponent, ProjectCardComponent, RadioButtonComponent, RadioButtonToggleComponent, RealtimeTableSync, RouteSkeletonComponent, SampleFrameComponent, ScoreBadgeComponent, ScreenshotFrameComponent, SearchBarComponent, SectionCardComponent, SegmentedComponent, SelectComponent, SeverityBadgesComponent, SkeletonDashboardComponent, SkeletonDetailComponent, SkeletonFacetsComponent, SkeletonFrameComponent, SkeletonListComponent, SliderComponent, SparkbarComponent, StageDotsComponent, StatCardsComponent, StatusDotComponent, SwitchComponent, TabComponent, TabGroupComponent, TableCell, TagComponent, ThemePickerComponent, ThemeService, ToastComponent, ToastContainerComponent, TranslationModule, TranslationService, TreeDiagram, TrimOnBlurDirective, UiModule, anyFeatureEnabled, countTone, createApi, featureEnabled, fxListResolver, scoreTone, severityTone, toneBadge, toneBadgeSolid, toneBg, toneBorder, toneColor, toneDot, toneDotChart, toneFg, toneScale, toneText, toneTint };
|
|
5928
|
+
export { AuthInterceptor, AuthStateService, BarListComponent, BaseComponent, BaseDialogComponent, BaseMasterDetailComponent, BaseTableComponent, BreadcrumbComponent, BreadcrumbService, ButtonComponent, CheckboxComponent, CircleProgressBar, CodeBlockComponent, ConfirmationDialogComponent, DatetimePicker, DndUploadComponent, DrawerComponent, EmptyStateComponent, FX_ICON_SPRITE_URL, FX_MOTION, FX_MOTION_DIRECTIVES, FX_SKELETON_DATA_KEY, FacetsRailComponent, FilterChipsComponent, FilterPillsComponent, FxComponent, FxCountUpDirective, FxDialogService, FxLoadStatusService, FxLoadingService, FxRevealDirective, FxRevealGroupDirective, FxRouteSkeletonService, FxStorageService, FxToastrService, FxUtils, FxValidators, HasPermissionDirective, HeroIconComponent, HttpLoaderFactory, HttpWrapper, InfoFieldComponent, InputComponent, KanbanBoardComponent, ListRowComponent, LoadStatusComponent, LoadingPanel, MasterDetailComponent, MenuComponent, MeterComponent, NotificationService, OG_LIGHT_THEMES, OG_PICKER_THEMES, OG_PICKER_THEME_META, OG_THEMES, OG_THEME_META, PendingBadgeComponent, PermissionGuard, PermissionService, PipelineStepperComponent, ProjectCardComponent, RadioButtonComponent, RadioButtonToggleComponent, RealtimeTableSync, RouteSkeletonComponent, SampleFrameComponent, ScoreBadgeComponent, ScreenshotFrameComponent, SearchBarComponent, SectionCardComponent, SegmentedComponent, SelectComponent, SeverityBadgesComponent, SkeletonDashboardComponent, SkeletonDetailComponent, SkeletonFacetsComponent, SkeletonFrameComponent, SkeletonListComponent, SliderComponent, SparkbarComponent, StageDotsComponent, StatCardsComponent, StatusDotComponent, SwitchComponent, TabComponent, TabGroupComponent, TableCell, TagComponent, ThemePickerComponent, ThemeService, ToastComponent, ToastContainerComponent, TranslationModule, TranslationService, TreeDiagram, TrimOnBlurDirective, UiModule, anyFeatureEnabled, countTone, createApi, featureEnabled, fxListResolver, fxMotionAllowed, fxRevealElement, scoreTone, severityTone, toneBadge, toneBadgeSolid, toneBg, toneBorder, toneColor, toneDot, toneDotChart, toneFg, toneScale, toneText, toneTint };
|
|
5412
5929
|
//# sourceMappingURL=foxeltech-angular-ui.mjs.map
|