@foxeltech/angular-ui 0.7.75 → 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 +66 -3
- 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 +641 -60
- package/fesm2022/foxeltech-angular-ui.mjs.map +1 -1
- package/package.json +3 -2
- package/src/lib/styles/components.css +66 -3
- package/src/lib/styles/utilities.css +8 -2
- 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/pending-badge/pending-badge.component.html +2 -2
- package/src/lib/ui/components/project-card/project-card.component.html +7 -3
- package/src/lib/ui/components/route-skeleton/route-skeleton.component.css +1 -1
- package/src/lib/ui/components/sample-frame/sample-frame.component.html +6 -7
- package/src/lib/ui/components/section-card/section-card.component.html +3 -1
- package/src/lib/ui/components/segmented/segmented.component.html +6 -2
- 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 +349 -14
- package/utilities.css +8 -2
|
@@ -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
|
|
|
@@ -1657,7 +1766,23 @@ function toneBadgeSolid(tone) {
|
|
|
1657
1766
|
* neutral, thay vì sinh ra class rác như thời còn ghép chuỗi `'bg-' + tone`.
|
|
1658
1767
|
*/
|
|
1659
1768
|
const isTone = (t) => typeof t === 'string' && Object.prototype.hasOwnProperty.call(TONE_TINT, t);
|
|
1660
|
-
/**
|
|
1769
|
+
/**
|
|
1770
|
+
* Chỉ MÀU của tông, không kèm hình dạng pill — cho ô icon, chip, ô nền.
|
|
1771
|
+
*
|
|
1772
|
+
* ⚠️ Đây là họ SOLID: `tone-tint-critical` đặt `background: --og-text`
|
|
1773
|
+
* (gần đen) VÀ `color: --og-surface` (trắng) trên CÙNG element.
|
|
1774
|
+
*
|
|
1775
|
+
* Hệ quả bắt buộc nhớ:
|
|
1776
|
+
* 1. Chỉ dùng cho **badge một phần tử**. Với hộp nhiều dòng thì con sẽ cần màu
|
|
1777
|
+
* chữ riêng, và nếu con ghi đè bằng `toneFg()` (đỏ sẫm) thì thành đỏ sẫm
|
|
1778
|
+
* trên gần đen — không đọc được. Đã bị đúng vậy ở hộp RISK của
|
|
1779
|
+
* `info-binary-detail` (anh Tú 03/09).
|
|
1780
|
+
* 2. **KHÔNG** đặt `toneFg()` lên phần tử con của một phần tử đã có
|
|
1781
|
+
* `toneTint()` — nó phá màu tương phản mà `toneTint` vừa đặt.
|
|
1782
|
+
*
|
|
1783
|
+
* Hộp/khối nhiều dòng thì dùng `toneScale()`: nền nhạt (color-mix 12%) + chữ
|
|
1784
|
+
* đúng tông, con chỉ cần THỪA HƯỞNG.
|
|
1785
|
+
*/
|
|
1661
1786
|
function toneTint(tone) {
|
|
1662
1787
|
return isTone(tone) ? TONE_TINT[tone] : 'tone-tint-neutral';
|
|
1663
1788
|
}
|
|
@@ -2454,11 +2579,11 @@ class ButtonComponent {
|
|
|
2454
2579
|
this.clicked.emit();
|
|
2455
2580
|
}
|
|
2456
2581
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: ButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2457
|
-
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 });
|
|
2458
2583
|
}
|
|
2459
2584
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: ButtonComponent, decorators: [{
|
|
2460
2585
|
type: Component,
|
|
2461
|
-
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" }]
|
|
2462
2587
|
}], propDecorators: { label: [{
|
|
2463
2588
|
type: Input
|
|
2464
2589
|
}], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], buttonVariant: [{ type: i0.Input, args: [{ isSignal: true, alias: "buttonVariant", required: false }] }], icon: [{
|
|
@@ -4082,6 +4207,77 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImpo
|
|
|
4082
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" }]
|
|
4083
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 }] }] } });
|
|
4084
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
|
+
|
|
4085
4281
|
/** KPI tile row: big number + tinted icon box + caption, in a responsive grid. */
|
|
4086
4282
|
class StatCardsComponent {
|
|
4087
4283
|
cards = input([], ...(ngDevMode ? [{ debugName: "cards" }] : /* istanbul ignore next */ []));
|
|
@@ -4093,10 +4289,14 @@ class StatCardsComponent {
|
|
|
4093
4289
|
variant = input('tile', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
|
|
4094
4290
|
/** Grid columns; defaults to one column per card. */
|
|
4095
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
|
+
*/
|
|
4096
4297
|
get gridColumns() {
|
|
4097
|
-
// No explicit cols → responsive auto-fit so tiles stack on narrow viewports.
|
|
4098
4298
|
if (this.cols === undefined) {
|
|
4099
|
-
return 'repeat(auto-fit, minmax(180px, 1fr))';
|
|
4299
|
+
return 'repeat(auto-fit, minmax(min(180px, 100%), 1fr))';
|
|
4100
4300
|
}
|
|
4101
4301
|
return `repeat(${this.cols}, minmax(0, 1fr))`;
|
|
4102
4302
|
}
|
|
@@ -4114,7 +4314,7 @@ class StatCardsComponent {
|
|
|
4114
4314
|
return card.subTone ? toneText(card.subTone) : 'text-text-secondary';
|
|
4115
4315
|
}
|
|
4116
4316
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: StatCardsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4117
|
-
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 });
|
|
4118
4318
|
}
|
|
4119
4319
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: StatCardsComponent, decorators: [{
|
|
4120
4320
|
type: Component,
|
|
@@ -4123,7 +4323,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImpo
|
|
|
4123
4323
|
NgClass,
|
|
4124
4324
|
HeroIconComponent,
|
|
4125
4325
|
SparkbarComponent,
|
|
4126
|
-
|
|
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"] }]
|
|
4127
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: [{
|
|
4128
4329
|
type: Input
|
|
4129
4330
|
}] } });
|
|
@@ -4213,6 +4414,12 @@ class FilterPillsComponent {
|
|
|
4213
4414
|
/** Accepts undefined so screens can bind an optional filter-bag key. */
|
|
4214
4415
|
value = input('', ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
4215
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 */ []));
|
|
4216
4423
|
isActive(option) {
|
|
4217
4424
|
return (this.value() ?? '') === option.value;
|
|
4218
4425
|
}
|
|
@@ -4223,20 +4430,23 @@ class FilterPillsComponent {
|
|
|
4223
4430
|
* trạng thái chọn của filter (anh Tú chốt 03/09).
|
|
4224
4431
|
*/
|
|
4225
4432
|
pillClass(option) {
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
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';
|
|
4229
4439
|
}
|
|
4230
4440
|
countClass(option) {
|
|
4231
4441
|
return this.isActive(option) ? 'opacity-70' : 'text-text-placeholder';
|
|
4232
4442
|
}
|
|
4233
4443
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: FilterPillsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4234
|
-
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 });
|
|
4235
4445
|
}
|
|
4236
4446
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: FilterPillsComponent, decorators: [{
|
|
4237
4447
|
type: Component,
|
|
4238
|
-
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-
|
|
4239
|
-
}], 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 }] }] } });
|
|
4240
4450
|
|
|
4241
4451
|
/**
|
|
4242
4452
|
* V2 stage progress dots — the walkthrough position indicator on the attack
|
|
@@ -4357,6 +4567,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImpo
|
|
|
4357
4567
|
* - titleStyle 'heading' (default): icon + heading, the pre-V2 look
|
|
4358
4568
|
* - titleStyle 'eyebrow': the V2 design's panel header — small tracked
|
|
4359
4569
|
* caption on the left, muted [aside] text on the right
|
|
4570
|
+
*
|
|
4571
|
+
* The host is a flex column (not the custom element's inline default) so that
|
|
4572
|
+
* in a CSS grid row every card stretches to the row height and panels line up
|
|
4573
|
+
* (anh Tú 04/09); a footer projected with `mt-auto` sticks to the bottom.
|
|
4360
4574
|
*/
|
|
4361
4575
|
class SectionCardComponent {
|
|
4362
4576
|
/** Caller-translated heading. */
|
|
@@ -4372,11 +4586,11 @@ class SectionCardComponent {
|
|
|
4372
4586
|
return iconTone ? toneText(iconTone) : 'text-text-secondary';
|
|
4373
4587
|
}
|
|
4374
4588
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: SectionCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4375
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: SectionCardComponent, isStandalone: true, selector: "fx-ui-section-card", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: false, isRequired: false, transformFunction: null }, iconTone: { classPropertyName: "iconTone", publicName: "iconTone", isSignal: true, isRequired: false, transformFunction: null }, titleStyle: { classPropertyName: "titleStyle", publicName: "titleStyle", isSignal: true, isRequired: false, transformFunction: null }, aside: { classPropertyName: "aside", publicName: "aside", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"card-common\">\n @if (titleStyle() === 'eyebrow') {\n <div class=\"flex items-baseline justify-between gap-normal mb-large\">\n <span class=\"txt-eyebrow\">{{ title() }}</span>\n @if (aside()) {\n <span class=\"txt-utility-description text-right\">{{ aside() }}</span>\n }\n </div>\n } @else {\n <div class=\"flex items-center gap-normal mb-large\">\n @if (icon) {\n <fx-ui-hero-icon [ngClass]=\"iconClass\" [icon]=\"icon\" [size]=\"18\"></fx-ui-hero-icon>\n }\n <span class=\"txt-heading-06\">{{ title() }}</span>\n </div>\n }\n <ng-content></ng-content>\n</div>\n", dependencies: [{ kind: "component", type: HeroIconComponent, selector: "fx-ui-hero-icon", inputs: ["stroke", "icon", "solid", "outline", "size", "color", "class"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4589
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: SectionCardComponent, isStandalone: true, selector: "fx-ui-section-card", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: false, isRequired: false, transformFunction: null }, iconTone: { classPropertyName: "iconTone", publicName: "iconTone", isSignal: true, isRequired: false, transformFunction: null }, titleStyle: { classPropertyName: "titleStyle", publicName: "titleStyle", isSignal: true, isRequired: false, transformFunction: null }, aside: { classPropertyName: "aside", publicName: "aside", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "flex flex-col min-w-0" }, ngImport: i0, template: "<!-- flex column filling the host: cards in one grid row share a height and a\n projected footer can sit at the bottom with `mt-auto` (design 01). -->\n<div class=\"card-common bg-surface flex flex-col flex-1 min-h-0\">\n @if (titleStyle() === 'eyebrow') {\n <div class=\"flex items-baseline justify-between gap-normal mb-large\">\n <span class=\"txt-eyebrow\">{{ title() }}</span>\n @if (aside()) {\n <span class=\"txt-utility-description text-right\">{{ aside() }}</span>\n }\n </div>\n } @else {\n <div class=\"flex items-center gap-normal mb-large\">\n @if (icon) {\n <fx-ui-hero-icon [ngClass]=\"iconClass\" [icon]=\"icon\" [size]=\"18\"></fx-ui-hero-icon>\n }\n <span class=\"txt-heading-06\">{{ title() }}</span>\n </div>\n }\n <ng-content></ng-content>\n</div>\n", dependencies: [{ kind: "component", type: HeroIconComponent, selector: "fx-ui-hero-icon", inputs: ["stroke", "icon", "solid", "outline", "size", "color", "class"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4376
4590
|
}
|
|
4377
4591
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: SectionCardComponent, decorators: [{
|
|
4378
4592
|
type: Component,
|
|
4379
|
-
args: [{ selector: 'fx-ui-section-card', changeDetection: ChangeDetectionStrategy.OnPush, imports: [HeroIconComponent, NgClass], template: "<div class=\"card-common\">\n @if (titleStyle() === 'eyebrow') {\n <div class=\"flex items-baseline justify-between gap-normal mb-large\">\n <span class=\"txt-eyebrow\">{{ title() }}</span>\n @if (aside()) {\n <span class=\"txt-utility-description text-right\">{{ aside() }}</span>\n }\n </div>\n } @else {\n <div class=\"flex items-center gap-normal mb-large\">\n @if (icon) {\n <fx-ui-hero-icon [ngClass]=\"iconClass\" [icon]=\"icon\" [size]=\"18\"></fx-ui-hero-icon>\n }\n <span class=\"txt-heading-06\">{{ title() }}</span>\n </div>\n }\n <ng-content></ng-content>\n</div>\n" }]
|
|
4593
|
+
args: [{ selector: 'fx-ui-section-card', changeDetection: ChangeDetectionStrategy.OnPush, imports: [HeroIconComponent, NgClass], host: { class: 'flex flex-col min-w-0' }, template: "<!-- flex column filling the host: cards in one grid row share a height and a\n projected footer can sit at the bottom with `mt-auto` (design 01). -->\n<div class=\"card-common bg-surface flex flex-col flex-1 min-h-0\">\n @if (titleStyle() === 'eyebrow') {\n <div class=\"flex items-baseline justify-between gap-normal mb-large\">\n <span class=\"txt-eyebrow\">{{ title() }}</span>\n @if (aside()) {\n <span class=\"txt-utility-description text-right\">{{ aside() }}</span>\n }\n </div>\n } @else {\n <div class=\"flex items-center gap-normal mb-large\">\n @if (icon) {\n <fx-ui-hero-icon [ngClass]=\"iconClass\" [icon]=\"icon\" [size]=\"18\"></fx-ui-hero-icon>\n }\n <span class=\"txt-heading-06\">{{ title() }}</span>\n </div>\n }\n <ng-content></ng-content>\n</div>\n" }]
|
|
4380
4594
|
}], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], icon: [{
|
|
4381
4595
|
type: Input
|
|
4382
4596
|
}], iconTone: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconTone", required: false }] }], titleStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "titleStyle", required: false }] }], aside: [{ type: i0.Input, args: [{ isSignal: true, alias: "aside", required: false }] }] } });
|
|
@@ -4453,11 +4667,11 @@ class SeverityBadgesComponent {
|
|
|
4453
4667
|
return badge.tone ? toneBadge(badge.tone) : 'bg-bg-hover text-text-primary';
|
|
4454
4668
|
}
|
|
4455
4669
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: SeverityBadgesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4456
|
-
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 });
|
|
4457
4671
|
}
|
|
4458
4672
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: SeverityBadgesComponent, decorators: [{
|
|
4459
4673
|
type: Component,
|
|
4460
|
-
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" }]
|
|
4461
4675
|
}], propDecorators: { stats: [{
|
|
4462
4676
|
type: Input
|
|
4463
4677
|
}], showZero: [{
|
|
@@ -4510,12 +4724,33 @@ class MasterDetailComponent {
|
|
|
4510
4724
|
/** Caller-translated Prev/Next labels ("X of Y" is numeric). */
|
|
4511
4725
|
prevLabel = input('Prev', ...(ngDevMode ? [{ debugName: "prevLabel" }] : /* istanbul ignore next */ []));
|
|
4512
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 */ []));
|
|
4513
4741
|
rowTemplate = contentChild.required('row');
|
|
4514
4742
|
detailTemplate = contentChild.required('detail');
|
|
4515
4743
|
paginator = viewChild.required(MatPaginator);
|
|
4516
4744
|
uid = `fx-md-${nextUid++}`;
|
|
4517
4745
|
ngAfterContentInit() {
|
|
4518
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
|
+
}
|
|
4519
4754
|
}
|
|
4520
4755
|
ngAfterViewInit() {
|
|
4521
4756
|
// The paginator lives in this view, not the host page's, so the host's
|
|
@@ -4525,16 +4760,13 @@ class MasterDetailComponent {
|
|
|
4525
4760
|
this.table.paginator = this.paginator();
|
|
4526
4761
|
}
|
|
4527
4762
|
}
|
|
4528
|
-
/** "X of Y"
|
|
4763
|
+
/** "Page X of Y" for the prevnext pagination style (design 11/14/17). */
|
|
4529
4764
|
rangeText() {
|
|
4530
4765
|
const total = this.table.total ?? 0;
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
const page = this.paginator().pageIndex;
|
|
4534
|
-
|
|
4535
|
-
const from = page * size + 1;
|
|
4536
|
-
const to = Math.min((page + 1) * size, total);
|
|
4537
|
-
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}`;
|
|
4538
4770
|
}
|
|
4539
4771
|
isSelected(row) {
|
|
4540
4772
|
return this.table.idOf(row) === this.table.selectedId;
|
|
@@ -4574,7 +4806,7 @@ class MasterDetailComponent {
|
|
|
4574
4806
|
el?.scrollIntoView({ block: 'nearest' });
|
|
4575
4807
|
}
|
|
4576
4808
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: MasterDetailComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4577
|
-
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"] }] });
|
|
4578
4810
|
}
|
|
4579
4811
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: MasterDetailComponent, decorators: [{
|
|
4580
4812
|
type: Component,
|
|
@@ -4585,10 +4817,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImpo
|
|
|
4585
4817
|
EmptyStateComponent,
|
|
4586
4818
|
MatPaginator,
|
|
4587
4819
|
SkeletonDetailComponent,
|
|
4588
|
-
|
|
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"] }]
|
|
4589
4822
|
}], propDecorators: { table: [{
|
|
4590
4823
|
type: Input
|
|
4591
|
-
}], 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 }] }] } });
|
|
4592
4825
|
|
|
4593
4826
|
/**
|
|
4594
4827
|
* Row-action dropdown wrapper over mat-menu (behavior-layer rule, Playbook
|
|
@@ -4669,6 +4902,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImpo
|
|
|
4669
4902
|
* <fx-ui-pending-badge [field]="'confirmed-chains weekly series'" />
|
|
4670
4903
|
*
|
|
4671
4904
|
* Keep the copy factual: it states that data is missing, never fakes a value.
|
|
4905
|
+
* Styling is neutral on purpose (anh Tú 04/09): a hint, not a warning.
|
|
4672
4906
|
*/
|
|
4673
4907
|
class PendingBadgeComponent {
|
|
4674
4908
|
/** Caller-translated chip text. */
|
|
@@ -4684,22 +4918,203 @@ class PendingBadgeComponent {
|
|
|
4684
4918
|
return this.field() ? `${this.label()} — ${this.field()}` : this.label();
|
|
4685
4919
|
}
|
|
4686
4920
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: PendingBadgeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4687
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: PendingBadgeComponent, isStandalone: true, selector: "fx-ui-pending-badge", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (variant() === 'dot') {\n <span\n class=\"inline-block w-[7px] h-[7px] rounded-full bg-
|
|
4921
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: PendingBadgeComponent, isStandalone: true, selector: "fx-ui-pending-badge", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, 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", dependencies: [{ kind: "component", type: HeroIconComponent, selector: "fx-ui-hero-icon", inputs: ["stroke", "icon", "solid", "outline", "size", "color", "class"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4688
4922
|
}
|
|
4689
4923
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: PendingBadgeComponent, decorators: [{
|
|
4690
4924
|
type: Component,
|
|
4691
|
-
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-
|
|
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" }]
|
|
4692
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 }] }] } });
|
|
4693
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
|
+
|
|
4694
5108
|
/**
|
|
4695
5109
|
* Wraps UI that is rendered from PLACEHOLDER data so the layout can be
|
|
4696
5110
|
* reviewed before its endpoint exists (anh Tú 29/08: "muốn có full giao diện
|
|
4697
5111
|
* trước để review").
|
|
4698
5112
|
*
|
|
4699
|
-
* The
|
|
4700
|
-
*
|
|
4701
|
-
*
|
|
4702
|
-
*
|
|
5113
|
+
* The marker is deliberately quiet (anh Tú 04/09: no yellow frame, just a
|
|
5114
|
+
* subtle "Pending API" chip): a neutral chip above the block, with the label
|
|
5115
|
+
* and missing field in its tooltip. The content itself is non-interactive so
|
|
5116
|
+
* sample shapes are never mistaken for live state. Drop the wrapper (and the
|
|
5117
|
+
* sample input) the moment the API lands.
|
|
4703
5118
|
*
|
|
4704
5119
|
* The pending chip is inlined rather than composed from fx-ui-pending-badge:
|
|
4705
5120
|
* one lib component importing another inside the same barrel tripped NG0919
|
|
@@ -4710,21 +5125,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImpo
|
|
|
4710
5125
|
* </fx-ui-sample-frame>
|
|
4711
5126
|
*/
|
|
4712
5127
|
class SampleFrameComponent {
|
|
4713
|
-
/** Caller-translated
|
|
5128
|
+
/** Caller-translated block label — surfaces in the chip tooltip only. */
|
|
4714
5129
|
label = input('Sample layout', ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
4715
5130
|
/** Field the backend still owes — shown on the pending badge tooltip. */
|
|
4716
5131
|
field = input('', ...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
|
|
4717
5132
|
/** Caller-translated pending-chip text. */
|
|
4718
5133
|
pendingLabel = input('Pending API', ...(ngDevMode ? [{ debugName: "pendingLabel" }] : /* istanbul ignore next */ []));
|
|
4719
5134
|
get tooltip() {
|
|
4720
|
-
|
|
5135
|
+
const head = `${this.label()} · ${this.pendingLabel()}`;
|
|
5136
|
+
return this.field() ? `${head} — ${this.field()}` : head;
|
|
4721
5137
|
}
|
|
4722
5138
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: SampleFrameComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4723
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.20", type: SampleFrameComponent, isStandalone: true, selector: "fx-ui-sample-frame", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: false, transformFunction: null }, pendingLabel: { classPropertyName: "pendingLabel", publicName: "pendingLabel", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"relative
|
|
5139
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.20", type: SampleFrameComponent, isStandalone: true, selector: "fx-ui-sample-frame", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: false, transformFunction: null }, pendingLabel: { classPropertyName: "pendingLabel", publicName: "pendingLabel", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "flex flex-col min-h-0" }, ngImport: i0, template: "<div class=\"relative flex flex-col flex-1 min-h-0\">\n <!-- One quiet chip marks the block as sample data; the tooltip names the\n missing field. flex-wrap so the chip drops a line in narrow rails. -->\n <div class=\"flex flex-wrap items-center gap-small mb-small\">\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\"\n [attr.title]=\"tooltip\"\n >\n <fx-ui-hero-icon icon=\"clock\" [size]=\"11\" class=\"flex\" />\n {{ pendingLabel() }}\n </span>\n </div>\n <!-- Sample content is non-interactive: nothing here reflects real state. -->\n <div class=\"pointer-events-none select-none opacity-90 flex-1 min-h-0 flex flex-col\">\n <ng-content></ng-content>\n </div>\n</div>\n", dependencies: [{ kind: "component", type: HeroIconComponent, selector: "fx-ui-hero-icon", inputs: ["stroke", "icon", "solid", "outline", "size", "color", "class"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4724
5140
|
}
|
|
4725
5141
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: SampleFrameComponent, decorators: [{
|
|
4726
5142
|
type: Component,
|
|
4727
|
-
args: [{ selector: 'fx-ui-sample-frame', changeDetection: ChangeDetectionStrategy.OnPush, imports: [HeroIconComponent], template: "<div class=\"relative
|
|
5143
|
+
args: [{ selector: 'fx-ui-sample-frame', changeDetection: ChangeDetectionStrategy.OnPush, imports: [HeroIconComponent], host: { class: 'flex flex-col min-h-0' }, template: "<div class=\"relative flex flex-col flex-1 min-h-0\">\n <!-- One quiet chip marks the block as sample data; the tooltip names the\n missing field. flex-wrap so the chip drops a line in narrow rails. -->\n <div class=\"flex flex-wrap items-center gap-small mb-small\">\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\"\n [attr.title]=\"tooltip\"\n >\n <fx-ui-hero-icon icon=\"clock\" [size]=\"11\" class=\"flex\" />\n {{ pendingLabel() }}\n </span>\n </div>\n <!-- Sample content is non-interactive: nothing here reflects real state. -->\n <div class=\"pointer-events-none select-none opacity-90 flex-1 min-h-0 flex flex-col\">\n <ng-content></ng-content>\n </div>\n</div>\n" }]
|
|
4728
5144
|
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: false }] }], pendingLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "pendingLabel", required: false }] }] } });
|
|
4729
5145
|
|
|
4730
5146
|
/**
|
|
@@ -4761,7 +5177,7 @@ class ScoreBadgeComponent {
|
|
|
4761
5177
|
sizeClass() {
|
|
4762
5178
|
switch (this.size()) {
|
|
4763
5179
|
case 'sm':
|
|
4764
|
-
return 'text-xs px-normal py-[
|
|
5180
|
+
return 'text-xs px-normal py-[3px]';
|
|
4765
5181
|
case 'lg':
|
|
4766
5182
|
return 'text-2xl px-semi py-small font-semibold';
|
|
4767
5183
|
default:
|
|
@@ -4822,12 +5238,53 @@ class SegmentedComponent {
|
|
|
4822
5238
|
* (design refs 04/05).
|
|
4823
5239
|
*/
|
|
4824
5240
|
variant = input('default', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
|
|
5241
|
+
/**
|
|
5242
|
+
* 'equal' (default): every segment takes the same width (view toggles such
|
|
5243
|
+
* as Catalog | Table). 'content': each segment is as wide as its label —
|
|
5244
|
+
* the demo's detail-pane tabs ("Affected software · Summary · Evidence"),
|
|
5245
|
+
* where equal columns would truncate the long label in a narrow pane.
|
|
5246
|
+
*/
|
|
5247
|
+
fit = input('equal', ...(ngDevMode ? [{ debugName: "fit" }] : /* istanbul ignore next */ []));
|
|
5248
|
+
optionEls = viewChildren('opt', ...(ngDevMode ? [{ debugName: "optionEls" }] : /* istanbul ignore next */ []));
|
|
5249
|
+
/** Measured box of the active segment — only used when fit === 'content'. */
|
|
5250
|
+
thumbBox = signal(null, ...(ngDevMode ? [{ debugName: "thumbBox" }] : /* istanbul ignore next */ []));
|
|
5251
|
+
constructor() {
|
|
5252
|
+
// Content-fit segments have no shared width to derive the thumb from, so
|
|
5253
|
+
// measure the active button after each render that could move it.
|
|
5254
|
+
afterRenderEffect(() => {
|
|
5255
|
+
const index = this.activeIndex();
|
|
5256
|
+
this.options();
|
|
5257
|
+
const els = this.optionEls();
|
|
5258
|
+
if (this.fit() !== 'content' || index < 0 || !els[index]) {
|
|
5259
|
+
untracked(() => this.thumbBox.set(null));
|
|
5260
|
+
return;
|
|
5261
|
+
}
|
|
5262
|
+
const el = els[index].nativeElement;
|
|
5263
|
+
const box = { left: el.offsetLeft, width: el.offsetWidth };
|
|
5264
|
+
untracked(() => {
|
|
5265
|
+
const prev = this.thumbBox();
|
|
5266
|
+
if (!prev || prev.left !== box.left || prev.width !== box.width)
|
|
5267
|
+
this.thumbBox.set(box);
|
|
5268
|
+
});
|
|
5269
|
+
});
|
|
5270
|
+
}
|
|
4825
5271
|
/** Index the sliding thumb sits on; -1 hides it (no/unknown selection). */
|
|
4826
5272
|
activeIndex = computed(() => this.options().findIndex((o) => o.value === this.value()), ...(ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []));
|
|
4827
5273
|
/** Equal-width tracks → the thumb is 1/n of the inner width and slides by
|
|
4828
5274
|
* multiples of its own width. Pure CSS transform, nothing to measure. */
|
|
4829
|
-
thumbWidth = computed(() =>
|
|
4830
|
-
|
|
5275
|
+
thumbWidth = computed(() => {
|
|
5276
|
+
const box = this.thumbBox();
|
|
5277
|
+
if (this.fit() === 'content')
|
|
5278
|
+
return box ? `${box.width}px` : '0px';
|
|
5279
|
+
return `calc((100% - 6px) / ${this.options().length || 1})`;
|
|
5280
|
+
}, ...(ngDevMode ? [{ debugName: "thumbWidth" }] : /* istanbul ignore next */ []));
|
|
5281
|
+
thumbTransform = computed(() => {
|
|
5282
|
+
const box = this.thumbBox();
|
|
5283
|
+
if (this.fit() === 'content')
|
|
5284
|
+
return box ? `translateX(${box.left - 3}px)` : 'none';
|
|
5285
|
+
return `translateX(${this.activeIndex() * 100}%)`;
|
|
5286
|
+
}, ...(ngDevMode ? [{ debugName: "thumbTransform" }] : /* istanbul ignore next */ []));
|
|
5287
|
+
gridClass = computed(() => (this.fit() === 'content' ? 'auto-cols-auto' : 'auto-cols-fr'), ...(ngDevMode ? [{ debugName: "gridClass" }] : /* istanbul ignore next */ []));
|
|
4831
5288
|
select(option) {
|
|
4832
5289
|
if (option.disabled || option.value === this.value())
|
|
4833
5290
|
return;
|
|
@@ -4845,12 +5302,12 @@ class SegmentedComponent {
|
|
|
4845
5302
|
return this.variant() === 'pill' ? 'text-accent-contrast' : 'text-strong';
|
|
4846
5303
|
}
|
|
4847
5304
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: SegmentedComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4848
|
-
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 } }, outputs: { value: "valueChange" }, ngImport: i0, template: "<div\n class=\"relative inline-grid max-w-full grid-flow-col
|
|
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 });
|
|
4849
5306
|
}
|
|
4850
5307
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: SegmentedComponent, decorators: [{
|
|
4851
5308
|
type: Component,
|
|
4852
|
-
args: [{ selector: 'fx-ui-segmented', changeDetection: ChangeDetectionStrategy.OnPush, imports: [HeroIconComponent], template: "<div\n class=\"relative inline-grid max-w-full grid-flow-col
|
|
4853
|
-
}], 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 }] }] } });
|
|
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"] }]
|
|
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 }] }] } });
|
|
4854
5311
|
|
|
4855
5312
|
/**
|
|
4856
5313
|
* V2 ListRow — presentational body for master-detail #row templates and other
|
|
@@ -4989,11 +5446,11 @@ class ProjectCardComponent {
|
|
|
4989
5446
|
return tone ? TREND_BLOCK[tone] : 'bg-bg-hover';
|
|
4990
5447
|
}
|
|
4991
5448
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: ProjectCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4992
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: ProjectCardComponent, isStandalone: true, selector: "fx-ui-project-card", inputs: { card: { classPropertyName: "card", publicName: "card", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { open: "open" }, ngImport: i0, template: "<button\n type=\"button\"\n class=\"card-common bg-surface w-full flex flex-col gap-normal hover:border-accent hover:shadow-md transition-all\"\n (click)=\"open.emit(card())\"\n>\n <!-- Header: avatar + name/sub + score chip (design 12) -->\n <div class=\"flex items-start justify-between gap-normal w-full\">\n <div class=\"flex items-start gap-normal min-w-0\">\n @if (card().avatar) {\n <span\n class=\"w-9 h-9 rounded-control bg-accent-soft text-accent flex items-center justify-center shrink-0 txt-utility-caption font-bold uppercase\"\n style=\"font-family: var(--og-font-figure)\"\n >{{ card().avatar }}</span\n >\n }\n <div class=\"min-w-0\">\n <div class=\"txt-heading-06 truncate text-left\">{{ card().name }}</div>\n @if (card().sub; as sub) {\n <div class=\"flex items-center gap-xs txt-utility-description mt-xs\">\n @if (sub.icon) {\n <fx-ui-hero-icon [icon]=\"sub.icon\" [size]=\"13\" class=\"flex shrink-0\" />\n }\n <span class=\"truncate\">{{ sub.label }}</span>\n </div>\n } @else if (card().description) {\n <div class=\"txt-card-content truncate mt-xs text-left\">{{ card().description }}</div>\n }\n </div>\n </div>\n @if (card().score !== undefined) {\n <div class=\"text-right shrink-0\">\n <span\n class=\"txt-tag-label px-normal py-[2px] rounded-pill whitespace-nowrap\"\n [ngClass]=\"scoreChipClass()\"\n >{{ card().score }}</span\n >\n @if (card().scoreLabel) {\n <div class=\"txt-utility-description mt-xs\">{{ card().scoreLabel }}</div>\n }\n </div>\n }\n </div>\n\n @if (card().chips?.length) {\n <div class=\"flex flex-wrap gap-xs w-full\">\n @for (chip of card().chips; track chip.label) {\n <span class=\"txt-tag-label px-normal py-[1px] rounded-pill\" [ngClass]=\"chipClass(chip)\">\n {{ chip.label }}\n </span>\n }\n </div>\n }\n\n <!-- Design-12 risk trend: toned blocks + caption line -->\n @if (card().trend; as trend) {\n <div class=\"w-full\">\n <div class=\"flex gap-xs w-full\">\n @for (tone of trend.tones; track $index) {\n <span class=\"flex-1 h-4 rounded-[2px]\" [ngClass]=\"blockClass(tone)\"></span>\n }\n </div>\n @if (trend.caption || trend.aside) {\n <div class=\"flex items-center justify-between mt-xs txt-utility-description font-mono\">\n <span>{{ trend.caption }}</span>\n <span>{{ trend.aside }}</span>\n </div>\n }\n </div>\n }\n\n @if (card().spark?.length) {\n <fx-ui-sparkbar class=\"w-full\" [values]=\"card().spark!\" [height]=\"24\" />\n }\n\n <!-- Metric columns: figure + caption (design 12) -->\n @if (card().metrics?.length) {\n <div\n class=\"grid gap-normal w-full\"\n [style.grid-template-columns]=\"'repeat(' + card().metrics!.length + ', minmax(0, 1fr))'\"\n >\n @for (m of card().metrics; track m.label) {\n <div class=\"min-w-0 text-left\">\n <div class=\"txt-heading-06\" style=\"font-family: var(--og-font-figure)\">{{ m.value }}</div>\n <div class=\"txt-utility-description truncate\">{{ m.label }}</div>\n </div>\n }\n </div>\n }\n\n @if (card().meta || card().openLabel) {\n <div\n class=\"flex items-center justify-between w-full pt-normal border-t border-border-default\"\n >\n <span class=\"txt-utility-description text-left\">{{ card().meta }}</span>\n @if (card().openLabel) {\n <span class=\"txt-utility-caption font-bold text-accent whitespace-nowrap\"\n >{{ card().openLabel }} \u203A</span\n >\n }\n </div>\n }\n</button>\n", dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: SparkbarComponent, selector: "fx-ui-sparkbar", inputs: ["values", "tone", "height"] }, { kind: "component", type: HeroIconComponent, selector: "fx-ui-hero-icon", inputs: ["stroke", "icon", "solid", "outline", "size", "color", "class"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
5449
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: ProjectCardComponent, isStandalone: true, selector: "fx-ui-project-card", inputs: { card: { classPropertyName: "card", publicName: "card", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { open: "open" }, host: { classAttribute: "flex flex-col min-w-0" }, ngImport: i0, template: "<button\n type=\"button\"\n class=\"card-common bg-surface w-full flex-1 flex flex-col gap-normal hover:border-accent hover:shadow-md transition-all\"\n (click)=\"open.emit(card())\"\n>\n <!-- Header: avatar + name/sub + score chip (design 12) -->\n <div class=\"flex items-start justify-between gap-normal w-full\">\n <div class=\"flex items-start gap-normal min-w-0\">\n @if (card().avatar) {\n <span\n class=\"w-9 h-9 rounded-control bg-accent-soft text-accent flex items-center justify-center shrink-0 txt-utility-caption font-bold uppercase\"\n style=\"font-family: var(--og-font-figure)\"\n >{{ card().avatar }}</span\n >\n }\n <!-- min-h 46px = name (24) + mt-xs (2) + sub (20): reserves the two-line\n header even when a card has no sub line, so trend/metric rows line\n up across the grid row (measured 04/09). -->\n <div class=\"min-w-0 min-h-[46px]\">\n <div class=\"txt-heading-06 truncate text-left\">{{ card().name }}</div>\n @if (card().sub; as sub) {\n <div class=\"flex items-center gap-xs txt-utility-description mt-xs\">\n @if (sub.icon) {\n <fx-ui-hero-icon [icon]=\"sub.icon\" [size]=\"13\" class=\"flex shrink-0\" />\n }\n <span class=\"truncate\">{{ sub.label }}</span>\n </div>\n } @else if (card().description) {\n <div class=\"txt-card-content truncate mt-xs text-left\">{{ card().description }}</div>\n }\n </div>\n </div>\n @if (card().score !== undefined) {\n <div class=\"text-right shrink-0\">\n <span\n class=\"txt-tag-label px-normal py-[2px] rounded-pill whitespace-nowrap\"\n [ngClass]=\"scoreChipClass()\"\n >{{ card().score }}</span\n >\n @if (card().scoreLabel) {\n <div class=\"txt-utility-description mt-xs\">{{ card().scoreLabel }}</div>\n }\n </div>\n }\n </div>\n\n @if (card().chips?.length) {\n <div class=\"flex flex-wrap gap-xs w-full\">\n @for (chip of card().chips; track chip.label) {\n <span class=\"txt-tag-label px-normal py-[1px] rounded-pill\" [ngClass]=\"chipClass(chip)\">\n {{ chip.label }}\n </span>\n }\n </div>\n }\n\n <!-- Design-12 risk trend: toned blocks + caption line -->\n @if (card().trend; as trend) {\n <div class=\"w-full\">\n <div class=\"flex gap-xs w-full\">\n @for (tone of trend.tones; track $index) {\n <span class=\"flex-1 h-4 rounded-[2px]\" [ngClass]=\"blockClass(tone)\"></span>\n }\n </div>\n @if (trend.caption || trend.aside) {\n <div class=\"flex items-center justify-between mt-xs txt-utility-description font-mono\">\n <span>{{ trend.caption }}</span>\n <span>{{ trend.aside }}</span>\n </div>\n }\n </div>\n }\n\n @if (card().spark?.length) {\n <fx-ui-sparkbar class=\"w-full\" [values]=\"card().spark!\" [height]=\"24\" />\n }\n\n <!-- Metric columns: figure + caption (design 12) -->\n @if (card().metrics?.length) {\n <div\n class=\"grid gap-normal w-full\"\n [style.grid-template-columns]=\"'repeat(' + card().metrics!.length + ', minmax(0, 1fr))'\"\n >\n @for (m of card().metrics; track m.label) {\n <div class=\"min-w-0 text-left\">\n <div class=\"txt-heading-06\" style=\"font-family: var(--og-font-figure)\">{{ m.value }}</div>\n <div class=\"txt-utility-description truncate\">{{ m.label }}</div>\n </div>\n }\n </div>\n }\n\n @if (card().meta || card().openLabel) {\n <!-- mt-auto: footers line up across a grid row whatever each card holds -->\n <div\n class=\"flex items-center justify-between w-full pt-normal mt-auto border-t border-border-default\"\n >\n <span class=\"txt-utility-description text-left\">{{ card().meta }}</span>\n @if (card().openLabel) {\n <span class=\"txt-utility-caption font-bold text-accent whitespace-nowrap\"\n >{{ card().openLabel }} \u203A</span\n >\n }\n </div>\n }\n</button>\n", dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: SparkbarComponent, selector: "fx-ui-sparkbar", inputs: ["values", "tone", "height"] }, { kind: "component", type: HeroIconComponent, selector: "fx-ui-hero-icon", inputs: ["stroke", "icon", "solid", "outline", "size", "color", "class"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4993
5450
|
}
|
|
4994
5451
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: ProjectCardComponent, decorators: [{
|
|
4995
5452
|
type: Component,
|
|
4996
|
-
args: [{ selector: 'fx-ui-project-card', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgClass, SparkbarComponent, HeroIconComponent], template: "<button\n type=\"button\"\n class=\"card-common bg-surface w-full flex flex-col gap-normal hover:border-accent hover:shadow-md transition-all\"\n (click)=\"open.emit(card())\"\n>\n <!-- Header: avatar + name/sub + score chip (design 12) -->\n <div class=\"flex items-start justify-between gap-normal w-full\">\n <div class=\"flex items-start gap-normal min-w-0\">\n @if (card().avatar) {\n <span\n class=\"w-9 h-9 rounded-control bg-accent-soft text-accent flex items-center justify-center shrink-0 txt-utility-caption font-bold uppercase\"\n style=\"font-family: var(--og-font-figure)\"\n >{{ card().avatar }}</span\n >\n }\n <div class=\"min-w-0\">\n <div class=\"txt-heading-06 truncate text-left\">{{ card().name }}</div>\n @if (card().sub; as sub) {\n <div class=\"flex items-center gap-xs txt-utility-description mt-xs\">\n @if (sub.icon) {\n <fx-ui-hero-icon [icon]=\"sub.icon\" [size]=\"13\" class=\"flex shrink-0\" />\n }\n <span class=\"truncate\">{{ sub.label }}</span>\n </div>\n } @else if (card().description) {\n <div class=\"txt-card-content truncate mt-xs text-left\">{{ card().description }}</div>\n }\n </div>\n </div>\n @if (card().score !== undefined) {\n <div class=\"text-right shrink-0\">\n <span\n class=\"txt-tag-label px-normal py-[2px] rounded-pill whitespace-nowrap\"\n [ngClass]=\"scoreChipClass()\"\n >{{ card().score }}</span\n >\n @if (card().scoreLabel) {\n <div class=\"txt-utility-description mt-xs\">{{ card().scoreLabel }}</div>\n }\n </div>\n }\n </div>\n\n @if (card().chips?.length) {\n <div class=\"flex flex-wrap gap-xs w-full\">\n @for (chip of card().chips; track chip.label) {\n <span class=\"txt-tag-label px-normal py-[1px] rounded-pill\" [ngClass]=\"chipClass(chip)\">\n {{ chip.label }}\n </span>\n }\n </div>\n }\n\n <!-- Design-12 risk trend: toned blocks + caption line -->\n @if (card().trend; as trend) {\n <div class=\"w-full\">\n <div class=\"flex gap-xs w-full\">\n @for (tone of trend.tones; track $index) {\n <span class=\"flex-1 h-4 rounded-[2px]\" [ngClass]=\"blockClass(tone)\"></span>\n }\n </div>\n @if (trend.caption || trend.aside) {\n <div class=\"flex items-center justify-between mt-xs txt-utility-description font-mono\">\n <span>{{ trend.caption }}</span>\n <span>{{ trend.aside }}</span>\n </div>\n }\n </div>\n }\n\n @if (card().spark?.length) {\n <fx-ui-sparkbar class=\"w-full\" [values]=\"card().spark!\" [height]=\"24\" />\n }\n\n <!-- Metric columns: figure + caption (design 12) -->\n @if (card().metrics?.length) {\n <div\n class=\"grid gap-normal w-full\"\n [style.grid-template-columns]=\"'repeat(' + card().metrics!.length + ', minmax(0, 1fr))'\"\n >\n @for (m of card().metrics; track m.label) {\n <div class=\"min-w-0 text-left\">\n <div class=\"txt-heading-06\" style=\"font-family: var(--og-font-figure)\">{{ m.value }}</div>\n <div class=\"txt-utility-description truncate\">{{ m.label }}</div>\n </div>\n }\n </div>\n }\n\n @if (card().meta || card().openLabel) {\n <div\n class=\"flex items-center justify-between w-full pt-normal border-t border-border-default\"\n >\n <span class=\"txt-utility-description text-left\">{{ card().meta }}</span>\n @if (card().openLabel) {\n <span class=\"txt-utility-caption font-bold text-accent whitespace-nowrap\"\n >{{ card().openLabel }} \u203A</span\n >\n }\n </div>\n }\n</button>\n" }]
|
|
5453
|
+
args: [{ selector: 'fx-ui-project-card', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgClass, SparkbarComponent, HeroIconComponent], host: { class: 'flex flex-col min-w-0' }, template: "<button\n type=\"button\"\n class=\"card-common bg-surface w-full flex-1 flex flex-col gap-normal hover:border-accent hover:shadow-md transition-all\"\n (click)=\"open.emit(card())\"\n>\n <!-- Header: avatar + name/sub + score chip (design 12) -->\n <div class=\"flex items-start justify-between gap-normal w-full\">\n <div class=\"flex items-start gap-normal min-w-0\">\n @if (card().avatar) {\n <span\n class=\"w-9 h-9 rounded-control bg-accent-soft text-accent flex items-center justify-center shrink-0 txt-utility-caption font-bold uppercase\"\n style=\"font-family: var(--og-font-figure)\"\n >{{ card().avatar }}</span\n >\n }\n <!-- min-h 46px = name (24) + mt-xs (2) + sub (20): reserves the two-line\n header even when a card has no sub line, so trend/metric rows line\n up across the grid row (measured 04/09). -->\n <div class=\"min-w-0 min-h-[46px]\">\n <div class=\"txt-heading-06 truncate text-left\">{{ card().name }}</div>\n @if (card().sub; as sub) {\n <div class=\"flex items-center gap-xs txt-utility-description mt-xs\">\n @if (sub.icon) {\n <fx-ui-hero-icon [icon]=\"sub.icon\" [size]=\"13\" class=\"flex shrink-0\" />\n }\n <span class=\"truncate\">{{ sub.label }}</span>\n </div>\n } @else if (card().description) {\n <div class=\"txt-card-content truncate mt-xs text-left\">{{ card().description }}</div>\n }\n </div>\n </div>\n @if (card().score !== undefined) {\n <div class=\"text-right shrink-0\">\n <span\n class=\"txt-tag-label px-normal py-[2px] rounded-pill whitespace-nowrap\"\n [ngClass]=\"scoreChipClass()\"\n >{{ card().score }}</span\n >\n @if (card().scoreLabel) {\n <div class=\"txt-utility-description mt-xs\">{{ card().scoreLabel }}</div>\n }\n </div>\n }\n </div>\n\n @if (card().chips?.length) {\n <div class=\"flex flex-wrap gap-xs w-full\">\n @for (chip of card().chips; track chip.label) {\n <span class=\"txt-tag-label px-normal py-[1px] rounded-pill\" [ngClass]=\"chipClass(chip)\">\n {{ chip.label }}\n </span>\n }\n </div>\n }\n\n <!-- Design-12 risk trend: toned blocks + caption line -->\n @if (card().trend; as trend) {\n <div class=\"w-full\">\n <div class=\"flex gap-xs w-full\">\n @for (tone of trend.tones; track $index) {\n <span class=\"flex-1 h-4 rounded-[2px]\" [ngClass]=\"blockClass(tone)\"></span>\n }\n </div>\n @if (trend.caption || trend.aside) {\n <div class=\"flex items-center justify-between mt-xs txt-utility-description font-mono\">\n <span>{{ trend.caption }}</span>\n <span>{{ trend.aside }}</span>\n </div>\n }\n </div>\n }\n\n @if (card().spark?.length) {\n <fx-ui-sparkbar class=\"w-full\" [values]=\"card().spark!\" [height]=\"24\" />\n }\n\n <!-- Metric columns: figure + caption (design 12) -->\n @if (card().metrics?.length) {\n <div\n class=\"grid gap-normal w-full\"\n [style.grid-template-columns]=\"'repeat(' + card().metrics!.length + ', minmax(0, 1fr))'\"\n >\n @for (m of card().metrics; track m.label) {\n <div class=\"min-w-0 text-left\">\n <div class=\"txt-heading-06\" style=\"font-family: var(--og-font-figure)\">{{ m.value }}</div>\n <div class=\"txt-utility-description truncate\">{{ m.label }}</div>\n </div>\n }\n </div>\n }\n\n @if (card().meta || card().openLabel) {\n <!-- mt-auto: footers line up across a grid row whatever each card holds -->\n <div\n class=\"flex items-center justify-between w-full pt-normal mt-auto border-t border-border-default\"\n >\n <span class=\"txt-utility-description text-left\">{{ card().meta }}</span>\n @if (card().openLabel) {\n <span class=\"txt-utility-caption font-bold text-accent whitespace-nowrap\"\n >{{ card().openLabel }} \u203A</span\n >\n }\n </div>\n }\n</button>\n" }]
|
|
4997
5454
|
}], propDecorators: { card: [{ type: i0.Input, args: [{ isSignal: true, alias: "card", required: true }] }], open: [{ type: i0.Output, args: ["open"] }] } });
|
|
4998
5455
|
|
|
4999
5456
|
/**
|
|
@@ -5181,11 +5638,11 @@ class RouteSkeletonComponent {
|
|
|
5181
5638
|
}
|
|
5182
5639
|
bars = range$1(7);
|
|
5183
5640
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: RouteSkeletonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5184
|
-
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 });
|
|
5185
5642
|
}
|
|
5186
5643
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: RouteSkeletonComponent, decorators: [{
|
|
5187
5644
|
type: Component,
|
|
5188
|
-
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"] }]
|
|
5189
5646
|
}], propDecorators: { hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }] } });
|
|
5190
5647
|
function range$1(n) {
|
|
5191
5648
|
return Array.from({ length: Math.max(0, Number(n) || 0) }, (_, i) => i);
|
|
@@ -5264,6 +5721,8 @@ const FX_COMPONENTS = [
|
|
|
5264
5721
|
MenuComponent,
|
|
5265
5722
|
ThemePickerComponent,
|
|
5266
5723
|
PendingBadgeComponent,
|
|
5724
|
+
BreadcrumbComponent,
|
|
5725
|
+
LoadStatusComponent,
|
|
5267
5726
|
SampleFrameComponent,
|
|
5268
5727
|
ScoreBadgeComponent,
|
|
5269
5728
|
StatusDotComponent,
|
|
@@ -5284,6 +5743,126 @@ const DIALOGS_COMPONENT = [
|
|
|
5284
5743
|
ConfirmationDialogComponent
|
|
5285
5744
|
];
|
|
5286
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
|
+
|
|
5287
5866
|
/**
|
|
5288
5867
|
* Compatibility barrel over the (now standalone) core components.
|
|
5289
5868
|
*
|
|
@@ -5301,7 +5880,7 @@ class UiModule {
|
|
|
5301
5880
|
ReactiveFormsModule,
|
|
5302
5881
|
RouterModule,
|
|
5303
5882
|
HasPermissionDirective,
|
|
5304
|
-
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,
|
|
5305
5884
|
ReactiveFormsModule,
|
|
5306
5885
|
RouterModule,
|
|
5307
5886
|
HasPermissionDirective,
|
|
@@ -5323,10 +5902,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImpo
|
|
|
5323
5902
|
TrimOnBlurDirective,
|
|
5324
5903
|
...FX_COMPONENTS,
|
|
5325
5904
|
...DIALOGS_COMPONENT,
|
|
5905
|
+
...FX_MOTION_DIRECTIVES,
|
|
5326
5906
|
],
|
|
5327
5907
|
exports: [
|
|
5328
5908
|
...FX_COMPONENTS,
|
|
5329
5909
|
...DIALOGS_COMPONENT,
|
|
5910
|
+
...FX_MOTION_DIRECTIVES,
|
|
5330
5911
|
FormsModule,
|
|
5331
5912
|
ReactiveFormsModule,
|
|
5332
5913
|
RouterModule,
|
|
@@ -5344,5 +5925,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImpo
|
|
|
5344
5925
|
* Generated bundle index. Do not edit.
|
|
5345
5926
|
*/
|
|
5346
5927
|
|
|
5347
|
-
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 };
|
|
5348
5929
|
//# sourceMappingURL=foxeltech-angular-ui.mjs.map
|