@foxeltech/angular-ui 0.7.83 → 0.7.103
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components.css +40 -1
- package/fesm2022/foxeltech-angular-ui-chart.mjs +127 -44
- package/fesm2022/foxeltech-angular-ui-chart.mjs.map +1 -1
- package/fesm2022/foxeltech-angular-ui-flow.mjs +42 -5
- package/fesm2022/foxeltech-angular-ui-flow.mjs.map +1 -1
- package/fesm2022/foxeltech-angular-ui.mjs +559 -42
- package/fesm2022/foxeltech-angular-ui.mjs.map +1 -1
- package/package.json +3 -2
- package/src/lib/styles/components.css +40 -1
- package/src/lib/styles/utilities.css +2 -1
- package/src/lib/ui/components/breadcrumb/breadcrumb.component.css +262 -0
- package/src/lib/ui/components/breadcrumb/breadcrumb.component.html +91 -0
- package/src/lib/ui/components/button/button.component.html +1 -0
- package/src/lib/ui/components/filter-pills/filter-pills.component.html +1 -1
- package/src/lib/ui/components/load-status/load-status.component.css +96 -0
- package/src/lib/ui/components/load-status/load-status.component.html +20 -0
- package/src/lib/ui/components/master-detail/master-detail.component.css +63 -11
- package/src/lib/ui/components/master-detail/master-detail.component.html +71 -42
- package/src/lib/ui/components/route-skeleton/route-skeleton.component.css +1 -1
- package/src/lib/ui/components/segmented/segmented.component.html +3 -1
- package/src/lib/ui/components/severity-badges/severity-badges.component.html +1 -1
- package/src/lib/ui/components/stat-cards/stat-cards.component.css +18 -0
- package/src/lib/ui/components/stat-cards/stat-cards.component.html +3 -3
- package/types/foxeltech-angular-ui-chart.d.ts +56 -22
- package/types/foxeltech-angular-ui-flow.d.ts +7 -0
- package/types/foxeltech-angular-ui.d.ts +309 -8
- package/utilities.css +2 -1
package/components.css
CHANGED
|
@@ -361,6 +361,14 @@ input[type='password'] {
|
|
|
361
361
|
inset 0 0 0 1px rgb(0 0 0 / 0.08),
|
|
362
362
|
0 1px 2px rgb(0 0 0 / 0.12);
|
|
363
363
|
}
|
|
364
|
+
/* Icon-only button (no label): square h-8 × w-8, no side padding, so the
|
|
365
|
+
glyph sits dead-centre (anh Tú 04/09). Consumers may still size it with
|
|
366
|
+
utilities (`w-9 h-9`) — those win in the utilities layer. */
|
|
367
|
+
.fx-btn--icon {
|
|
368
|
+
width: 2rem;
|
|
369
|
+
padding-left: 0;
|
|
370
|
+
padding-right: 0;
|
|
371
|
+
}
|
|
364
372
|
/* Nút hành động chính theo demo (`.bi-newbtn`): màu cố định, không phải accent. */
|
|
365
373
|
/* ---- Tông severity theo ĐÚNG mô hình demo (đo 03/09) -----------------
|
|
366
374
|
Demo KHÔNG tô badge bằng màu rực. Nó dùng token semantic, và bậc nặng
|
|
@@ -755,6 +763,17 @@ input[type='password'] {
|
|
|
755
763
|
max-height: calc(100vh - 315px);
|
|
756
764
|
overflow: auto;
|
|
757
765
|
}
|
|
766
|
+
/* Phones: a data table must not squeeze its columns to a few px per cell —
|
|
767
|
+
give it a floor and let .table-page-container scroll it sideways
|
|
768
|
+
(anh Tú 04/09 mobile audit: Name column collapsed to one character/line). */
|
|
769
|
+
@media (max-width: 719px) {
|
|
770
|
+
/* !important like the other .mat-mdc-table overrides above: this file is
|
|
771
|
+
imported into a cascade @layer, so an unlayered Material rule
|
|
772
|
+
(`.mat-mdc-table{min-width:100%}`) beats it whatever the specificity. */
|
|
773
|
+
.mat-mdc-table {
|
|
774
|
+
min-width: 640px !important;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
758
777
|
.table-cell-padding {
|
|
759
778
|
padding-top: 8px !important;
|
|
760
779
|
padding-bottom: 8px !important;
|
|
@@ -790,8 +809,28 @@ input[type='password'] {
|
|
|
790
809
|
rgb(var(--gradient-primary-end))
|
|
791
810
|
) border-box;
|
|
792
811
|
}
|
|
812
|
+
/* Skeleton block = the design's `.bi-sk` shimmer ("Loading States", 04/09):
|
|
813
|
+
text at 7% → 13% → 7% sweeping left→right over 1.15 s, 6px radius. One
|
|
814
|
+
rule for every skeleton in the product — route skeleton, table cells,
|
|
815
|
+
dashboard cards — so the boot splash, route and in-panel states match. */
|
|
793
816
|
.skeleton-load {
|
|
794
|
-
|
|
817
|
+
display: block;
|
|
818
|
+
border-radius: 6px;
|
|
819
|
+
background: linear-gradient(
|
|
820
|
+
90deg,
|
|
821
|
+
rgb(var(--og-text) / 0.07) 0px,
|
|
822
|
+
rgb(var(--og-text) / 0.13) 90px,
|
|
823
|
+
rgb(var(--og-text) / 0.07) 180px
|
|
824
|
+
);
|
|
825
|
+
background-size: 400px 100%;
|
|
826
|
+
animation: fx-shimmer 1.15s linear infinite;
|
|
827
|
+
}
|
|
828
|
+
@keyframes fx-shimmer {
|
|
829
|
+
0% { background-position: -260px 0; }
|
|
830
|
+
100% { background-position: 260px 0; }
|
|
831
|
+
}
|
|
832
|
+
@media (prefers-reduced-motion: reduce) {
|
|
833
|
+
.skeleton-load { animation: none; background: rgb(var(--og-text) / 0.08); }
|
|
795
834
|
}
|
|
796
835
|
|
|
797
836
|
/* ============================================================================
|
|
@@ -41,6 +41,8 @@ class ChartComponent {
|
|
|
41
41
|
* dom.". Chỉ dựng directive khi đã có options thật ⇒ đúng một lần init.
|
|
42
42
|
*/
|
|
43
43
|
hasOptions = false;
|
|
44
|
+
/** Heights (%) of the pre-init placeholder bars — a fixed, calm silhouette. */
|
|
45
|
+
placeholderBars = [34, 52, 44, 66, 58, 78, 70, 88, 62, 74];
|
|
44
46
|
labelFontSize = 12;
|
|
45
47
|
chartInstance;
|
|
46
48
|
resizeObserver;
|
|
@@ -66,6 +68,24 @@ class ChartComponent {
|
|
|
66
68
|
pendingFrames = new Set();
|
|
67
69
|
isMobile = false;
|
|
68
70
|
containerWidth = 0;
|
|
71
|
+
containerHeight = 0;
|
|
72
|
+
/**
|
|
73
|
+
* Mốc (performance.now) mà animation "xuất hiện" của lần setOption gần nhất
|
|
74
|
+
* chắc chắn đã xong. ECharts mặc định animationDuration 1000ms; trong khoảng
|
|
75
|
+
* này KHÔNG được `resize()` — resize nhảy thẳng tới trạng thái cuối và giết
|
|
76
|
+
* animation (đo 05/09: chart PM bị cắt ở ~800ms khi thanh cuộn xuất hiện
|
|
77
|
+
* lúc lưới card nạp xong làm bề rộng đổi 720→710).
|
|
78
|
+
*/
|
|
79
|
+
appearUntil = 0;
|
|
80
|
+
static APPEAR_MS = 1100;
|
|
81
|
+
deferTimer;
|
|
82
|
+
/**
|
|
83
|
+
* Thay đổi kích thước ghi nhận TRONG cửa sổ appear, chờ áp sau. `measure()`
|
|
84
|
+
* đã cập nhật containerWidth/Height ngay lúc đo, nên lần gọi hoãn không thể
|
|
85
|
+
* dựa vào "có đổi không" nữa — phải nhớ bằng cờ.
|
|
86
|
+
*/
|
|
87
|
+
pendingResize = false;
|
|
88
|
+
pendingMobile = false;
|
|
69
89
|
constructor(ref, cdr) {
|
|
70
90
|
this.ref = ref;
|
|
71
91
|
this.cdr = cdr;
|
|
@@ -76,24 +96,27 @@ class ChartComponent {
|
|
|
76
96
|
this.intersectionObserver = new IntersectionObserver(([entry]) => {
|
|
77
97
|
if (entry.isIntersecting && !this.canInit && !this.destroyed) {
|
|
78
98
|
this.canInit = true;
|
|
79
|
-
this.
|
|
99
|
+
this.measure();
|
|
80
100
|
this.rebuildChart();
|
|
81
101
|
this.cdr.detectChanges();
|
|
82
102
|
}
|
|
83
103
|
}, { threshold: 0.1 });
|
|
84
104
|
this.intersectionObserver.observe(this.ref.nativeElement);
|
|
85
|
-
// Resize-safe (tabs, flex, async layout)
|
|
105
|
+
// Resize-safe (tabs, flex, async layout). Đây là ĐƯỜNG RESIZE DUY NHẤT:
|
|
106
|
+
// `autoResize` của NgxEchartsDirective đã tắt trong template — nó
|
|
107
|
+
// resize() mù sau throttle 100ms, kể cả về 0×H khi phần tử display:none
|
|
108
|
+
// (skeleton route ẩn nội dung cũ) rồi resize lại khi hiện → animation
|
|
109
|
+
// xuất hiện bị giết (đo 05/09 khi đổi project trên chip breadcrumb).
|
|
86
110
|
this.resizeObserver = new ResizeObserver(() => {
|
|
87
111
|
if (!this.canInit || this.destroyed)
|
|
88
112
|
return;
|
|
89
|
-
// forceMediaRecalc tự đo lại và chỉ dựng lại khi kích thước đổi.
|
|
90
113
|
this.forceMediaRecalc();
|
|
91
114
|
});
|
|
92
115
|
this.resizeObserver.observe(this.ref.nativeElement);
|
|
93
116
|
}
|
|
94
117
|
ngOnChanges() {
|
|
95
118
|
if (this.canInit && !this.destroyed) {
|
|
96
|
-
this.
|
|
119
|
+
this.measure();
|
|
97
120
|
this.rebuildChart();
|
|
98
121
|
}
|
|
99
122
|
}
|
|
@@ -106,6 +129,9 @@ class ChartComponent {
|
|
|
106
129
|
for (const id of this.pendingFrames)
|
|
107
130
|
cancelAnimationFrame(id);
|
|
108
131
|
this.pendingFrames.clear();
|
|
132
|
+
if (this.deferTimer !== undefined)
|
|
133
|
+
clearTimeout(this.deferTimer);
|
|
134
|
+
this.deferTimer = undefined;
|
|
109
135
|
this.resizeObserver?.disconnect();
|
|
110
136
|
this.intersectionObserver?.disconnect();
|
|
111
137
|
this.resizeObserver = undefined;
|
|
@@ -209,63 +235,91 @@ class ChartComponent {
|
|
|
209
235
|
chart.setOption(options, true);
|
|
210
236
|
}
|
|
211
237
|
}
|
|
238
|
+
// Lần vẽ đầu (directive setOption lúc init) cũng có animation xuất hiện.
|
|
239
|
+
this.markAppear();
|
|
212
240
|
this.chartInit.emit(chart);
|
|
213
241
|
this.schedule(() => this.forceMediaRecalc());
|
|
214
242
|
}
|
|
215
243
|
/* ================= CORE FIX ================= */
|
|
216
244
|
/**
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
* ghi chú ở `forceMediaRecalc`.
|
|
245
|
+
* Đo host và ghi lại; trả về CÁI GÌ đổi so với lần đo trước.
|
|
246
|
+
* Khi bị ẩn (0×0) thì KHÔNG ghi — giữ kích thước hiển thị cuối, để lúc hiện
|
|
247
|
+
* lại cùng cỡ thì coi như không đổi (không resize, không cắt animation).
|
|
221
248
|
*/
|
|
222
|
-
|
|
223
|
-
const { width } = this.ref.nativeElement.getBoundingClientRect();
|
|
224
|
-
if (width <= 0)
|
|
225
|
-
return false;
|
|
249
|
+
measure() {
|
|
250
|
+
const { width, height } = this.ref.nativeElement.getBoundingClientRect();
|
|
251
|
+
if (width <= 0 || height <= 0)
|
|
252
|
+
return { size: false, mobile: false };
|
|
226
253
|
const isMobile = width < this.mobileWidth();
|
|
227
|
-
const
|
|
254
|
+
const size = width !== this.containerWidth || height !== this.containerHeight;
|
|
255
|
+
const mobile = isMobile !== this.isMobile;
|
|
228
256
|
this.containerWidth = width;
|
|
257
|
+
this.containerHeight = height;
|
|
229
258
|
this.isMobile = isMobile;
|
|
230
|
-
return
|
|
259
|
+
return { size, mobile };
|
|
260
|
+
}
|
|
261
|
+
/** Animation xuất hiện vừa bắt đầu — khoá resize tới khi nó xong. */
|
|
262
|
+
markAppear() {
|
|
263
|
+
this.appearUntil = performance.now() + ChartComponent.APPEAR_MS;
|
|
264
|
+
}
|
|
265
|
+
/** Pie/sunburst đặt legend + số giữa theo px của containerWidth → phải dựng lại. */
|
|
266
|
+
needsLayoutRebuild() {
|
|
267
|
+
const type = this.detectType();
|
|
268
|
+
return type === 'pie' || type === 'sunburst';
|
|
269
|
+
}
|
|
270
|
+
deferResize(ms) {
|
|
271
|
+
if (this.deferTimer !== undefined)
|
|
272
|
+
clearTimeout(this.deferTimer);
|
|
273
|
+
this.deferTimer = setTimeout(() => {
|
|
274
|
+
this.deferTimer = undefined;
|
|
275
|
+
if (!this.destroyed)
|
|
276
|
+
this.forceMediaRecalc();
|
|
277
|
+
}, ms);
|
|
231
278
|
}
|
|
232
279
|
/**
|
|
233
|
-
*
|
|
234
|
-
* **animation lúc chart xuất hiện bị giết**.
|
|
235
|
-
*
|
|
236
|
-
* Đường đi: `onChartInit` → schedule(forceMediaRecalc) → chạy ở frame KẾ
|
|
237
|
-
* TIẾP. Nếu chỗ này vô điều kiện `rebuildChart()` (clear + setOption notMerge)
|
|
238
|
-
* thì nó xoá sạch và vẽ lại từ đầu ~16ms sau lần vẽ đầu, trong khi animation
|
|
239
|
-
* xuất hiện dài ~1000ms ⇒ người dùng thấy chart hiện ra đứng im.
|
|
240
|
-
*
|
|
241
|
-
* Đo được: lần xuất hiện đầu 1/12 frame khác nhau (đứng yên) trong khi vẽ lại
|
|
242
|
-
* bằng `theme-changed` cho 9/12 → animation của ECharts vẫn bật, chỉ lần đầu
|
|
243
|
-
* bị cắt.
|
|
244
|
-
*
|
|
245
|
-
* Trước Pha 0 lỗi này bị che: lần init đầu dùng `chartOptions = {}` nên
|
|
246
|
-
* animation xuất hiện xảy ra ở lần cập nhật SAU, tức sau khi forceMediaRecalc
|
|
247
|
-
* đã chạy xong. Cổng `hasOptions` đưa options thật về ngay lần init nên lộ ra.
|
|
280
|
+
* Phản ứng với kích thước host (RO, sau init, sau khi hiện lại).
|
|
248
281
|
*
|
|
249
|
-
*
|
|
282
|
+
* Ba quy tắc, đều đo được (0.7.75 và 05/09):
|
|
283
|
+
* 1. **Ẩn (0×H) thì không làm gì** — không resize canvas về 0. Chart hiện
|
|
284
|
+
* lại cùng cỡ thì animation đã bắt đầu lúc ẩn (setOption khi resolver về,
|
|
285
|
+
* ngay trước NavigationEnd) chạy tiếp bình thường.
|
|
286
|
+
* 2. **`resize()` nhảy thẳng tới trạng thái cuối** ⇒ trong cửa sổ appear
|
|
287
|
+
* (`appearUntil`) thì HOÃN, đo lại và xử lý sau khi animation xong. Không
|
|
288
|
+
* dựng lại (clear + setOption) lúc đó vì đó là "vẽ 2 lần" theo cách khác.
|
|
289
|
+
* 3. Ngoài cửa sổ: `resize()` trước, rồi chỉ dựng lại khi preset đổi (mobile)
|
|
290
|
+
* hoặc loại chart đặt layout theo px (pie/sunburst). Thứ tự này quan
|
|
291
|
+
* trọng: resize SAU rebuild sẽ cắt ngay animation vừa bắt đầu.
|
|
250
292
|
*/
|
|
251
293
|
forceMediaRecalc(retry = 0) {
|
|
252
294
|
if (!this.liveChart)
|
|
253
295
|
return;
|
|
254
296
|
const { width, height } = this.ref.nativeElement.getBoundingClientRect();
|
|
255
|
-
if (
|
|
256
|
-
|
|
297
|
+
if (width === 0 || height === 0) {
|
|
298
|
+
// Chưa layout xong (ngay sau init) thì thử lại vài frame; đang ẩn thì thôi.
|
|
299
|
+
if (retry < 10)
|
|
300
|
+
this.schedule(() => this.forceMediaRecalc(retry + 1));
|
|
257
301
|
return;
|
|
258
302
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
// Không cần resize ở lần đầu: ECharts tự lấy kích thước khi init, và vòng
|
|
264
|
-
// retry ở trên đã lo ca width/height = 0.
|
|
265
|
-
if (!this.updateResponsiveState())
|
|
303
|
+
const change = this.measure();
|
|
304
|
+
const size = change.size || this.pendingResize;
|
|
305
|
+
const mobile = change.mobile || this.pendingMobile;
|
|
306
|
+
if (!size && !mobile)
|
|
266
307
|
return;
|
|
267
|
-
this.
|
|
268
|
-
|
|
308
|
+
const remaining = this.appearUntil - performance.now();
|
|
309
|
+
if (remaining > 0) {
|
|
310
|
+
this.pendingResize = size;
|
|
311
|
+
this.pendingMobile = mobile;
|
|
312
|
+
this.deferResize(remaining + 30);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
this.pendingResize = false;
|
|
316
|
+
this.pendingMobile = false;
|
|
317
|
+
const chart = this.liveChart;
|
|
318
|
+
if (!chart)
|
|
319
|
+
return;
|
|
320
|
+
chart.resize();
|
|
321
|
+
if (mobile || this.needsLayoutRebuild())
|
|
322
|
+
this.rebuildChart();
|
|
269
323
|
}
|
|
270
324
|
rebuildChart() {
|
|
271
325
|
if (this.destroyed)
|
|
@@ -293,6 +347,7 @@ class ChartComponent {
|
|
|
293
347
|
this.hasOptions = true;
|
|
294
348
|
chart.clear();
|
|
295
349
|
chart.setOption(options, true);
|
|
350
|
+
this.markAppear();
|
|
296
351
|
}
|
|
297
352
|
checkEmpty() {
|
|
298
353
|
const type = this.detectType();
|
|
@@ -715,19 +770,47 @@ class ChartComponent {
|
|
|
715
770
|
});
|
|
716
771
|
}
|
|
717
772
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: ChartComponent, deps: [{ token: i0.ElementRef }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
718
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: ChartComponent, isStandalone: true, selector: "fx-ui-chart", inputs: { type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, palette: { classPropertyName: "palette", publicName: "palette", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, pieLegendAlign: { classPropertyName: "pieLegendAlign", publicName: "pieLegendAlign", isSignal: true, isRequired: false, transformFunction: null }, pieLegendPosition: { classPropertyName: "pieLegendPosition", publicName: "pieLegendPosition", isSignal: true, isRequired: false, transformFunction: null }, showPieLabel: { classPropertyName: "showPieLabel", publicName: "showPieLabel", isSignal: true, isRequired: false, transformFunction: null }, showPieLegend: { classPropertyName: "showPieLegend", publicName: "showPieLegend", isSignal: true, isRequired: false, transformFunction: null }, pieCenterLabel: { classPropertyName: "pieCenterLabel", publicName: "pieCenterLabel", isSignal: true, isRequired: false, transformFunction: null }, mobileWidth: { classPropertyName: "mobileWidth", publicName: "mobileWidth", isSignal: true, isRequired: false, transformFunction: null }, labelFontFamily: { classPropertyName: "labelFontFamily", publicName: "labelFontFamily", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { chartClick: "chartClick", chartInit: "chartInit" }, host: { listeners: { "window:theme-changed": "onThemeChange()" } }, providers: [provideEchartsCore({ echarts: () => import('echarts') })], usesOnChanges: true, ngImport: i0, template: "<div class=\"chart-wrapper\" [style.height]=\"height()\">\n @if (isEmpty) {\n <div class=\"chart-empty\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"chart-empty-icon\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 0 1 3 19.875v-6.75ZM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V8.625ZM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V4.125Z\" />\n </svg>\n <span class=\"chart-empty-text\">{{ emptyText() }}</span>\n </div>\n } @else if (hasOptions) {\n <div\n echarts\n [options]=\"chartOptions\"\n [loading]=\"loading()\"\n (chartInit)=\"onChartInit($event)\"\n (chartClick)=\"onChartClick($event)\"\n class=\"fx-chart\"\n [style.height]=\"height()\"\n ></div>\n }\n</div>\n", styles: ["
|
|
773
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: ChartComponent, isStandalone: true, selector: "fx-ui-chart", inputs: { type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, palette: { classPropertyName: "palette", publicName: "palette", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, pieLegendAlign: { classPropertyName: "pieLegendAlign", publicName: "pieLegendAlign", isSignal: true, isRequired: false, transformFunction: null }, pieLegendPosition: { classPropertyName: "pieLegendPosition", publicName: "pieLegendPosition", isSignal: true, isRequired: false, transformFunction: null }, showPieLabel: { classPropertyName: "showPieLabel", publicName: "showPieLabel", isSignal: true, isRequired: false, transformFunction: null }, showPieLegend: { classPropertyName: "showPieLegend", publicName: "showPieLegend", isSignal: true, isRequired: false, transformFunction: null }, pieCenterLabel: { classPropertyName: "pieCenterLabel", publicName: "pieCenterLabel", isSignal: true, isRequired: false, transformFunction: null }, mobileWidth: { classPropertyName: "mobileWidth", publicName: "mobileWidth", isSignal: true, isRequired: false, transformFunction: null }, labelFontFamily: { classPropertyName: "labelFontFamily", publicName: "labelFontFamily", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { chartClick: "chartClick", chartInit: "chartInit" }, host: { listeners: { "window:theme-changed": "onThemeChange()" } }, providers: [provideEchartsCore({ echarts: () => import('echarts') })], usesOnChanges: true, ngImport: i0, template: "<div class=\"chart-wrapper\" [style.height]=\"height()\">\n @if (isEmpty) {\n <div class=\"chart-empty\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"chart-empty-icon\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 0 1 3 19.875v-6.75ZM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V8.625ZM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V4.125Z\" />\n </svg>\n <span class=\"chart-empty-text\">{{ emptyText() }}</span>\n </div>\n } @else if (hasOptions) {\n <div\n echarts\n [options]=\"chartOptions\"\n [autoResize]=\"false\"\n [loading]=\"loading()\"\n (chartInit)=\"onChartInit($event)\"\n (chartClick)=\"onChartClick($event)\"\n class=\"fx-chart\"\n [style.height]=\"height()\"\n ></div>\n } @else {\n <!-- Until echarts is loaded + initialised (or the element scrolls into view)\n the panel keeps the chart's footprint with a quiet pulsing sketch\n instead of an empty box (anh T\u00FA 04/09: \"the page is too blank\"). -->\n <div class=\"chart-placeholder\" aria-hidden=\"true\">\n @for (h of placeholderBars; track $index) {\n <span class=\"chart-placeholder__bar\" [style.height.%]=\"h\"></span>\n }\n </div>\n }\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block;width:100%}.chart-wrapper{position:relative;width:100%;height:100%}.chart-container{width:100%;height:100%}.chart-loader{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:rgba(var(--bg-primary-hover)/.6);font-weight:500;font-size:1.1rem;z-index:2;border-radius:4px}.chart-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;gap:12px;color:rgba(var(--text-secondary)/1)}.chart-empty-icon{width:48px;height:48px;opacity:.4}.chart-empty-text{font-size:14px;font-weight:500}.chart-placeholder{position:absolute;inset:0;display:flex;align-items:flex-end;gap:6px;padding:12px 8px 22px;animation:chart-placeholder-pulse 1.6s ease-in-out infinite}.chart-placeholder__bar{flex:1;border-radius:3px 3px 0 0;background:rgb(var(--og-track))}@keyframes chart-placeholder-pulse{0%,to{opacity:1}50%{opacity:.45}}@media(prefers-reduced-motion:reduce){.chart-placeholder{animation:none}}\n"], dependencies: [{ kind: "directive", type: NgxEchartsDirective, selector: "echarts, [echarts]", inputs: ["options", "theme", "initOpts", "merge", "autoResize", "loading", "loadingType", "loadingOpts"], outputs: ["chartInit", "optionsError", "chartClick", "chartDblClick", "chartMouseDown", "chartMouseMove", "chartMouseUp", "chartMouseOver", "chartMouseOut", "chartGlobalOut", "chartContextMenu", "chartHighlight", "chartDownplay", "chartSelectChanged", "chartLegendSelectChanged", "chartLegendSelected", "chartLegendUnselected", "chartLegendLegendSelectAll", "chartLegendLegendInverseSelect", "chartLegendScroll", "chartDataZoom", "chartDataRangeSelected", "chartGraphRoam", "chartGeoRoam", "chartTreeRoam", "chartTimelineChanged", "chartTimelinePlayChanged", "chartRestore", "chartDataViewChanged", "chartMagicTypeChanged", "chartGeoSelectChanged", "chartGeoSelected", "chartGeoUnselected", "chartAxisAreaSelected", "chartBrush", "chartBrushEnd", "chartBrushSelected", "chartGlobalCursorTaken", "chartRendered", "chartFinished"], exportAs: ["echarts"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
719
774
|
}
|
|
720
775
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: ChartComponent, decorators: [{
|
|
721
776
|
type: Component,
|
|
722
|
-
args: [{ selector: 'fx-ui-chart', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgxEchartsDirective, InputComponent], providers: [provideEchartsCore({ echarts: () => import('echarts') })], template: "<div class=\"chart-wrapper\" [style.height]=\"height()\">\n @if (isEmpty) {\n <div class=\"chart-empty\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"chart-empty-icon\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 0 1 3 19.875v-6.75ZM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V8.625ZM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V4.125Z\" />\n </svg>\n <span class=\"chart-empty-text\">{{ emptyText() }}</span>\n </div>\n } @else if (hasOptions) {\n <div\n echarts\n [options]=\"chartOptions\"\n [loading]=\"loading()\"\n (chartInit)=\"onChartInit($event)\"\n (chartClick)=\"onChartClick($event)\"\n class=\"fx-chart\"\n [style.height]=\"height()\"\n ></div>\n }\n</div>\n", styles: ["
|
|
777
|
+
args: [{ selector: 'fx-ui-chart', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgxEchartsDirective, InputComponent], providers: [provideEchartsCore({ echarts: () => import('echarts') })], template: "<div class=\"chart-wrapper\" [style.height]=\"height()\">\n @if (isEmpty) {\n <div class=\"chart-empty\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"chart-empty-icon\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 0 1 3 19.875v-6.75ZM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V8.625ZM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V4.125Z\" />\n </svg>\n <span class=\"chart-empty-text\">{{ emptyText() }}</span>\n </div>\n } @else if (hasOptions) {\n <div\n echarts\n [options]=\"chartOptions\"\n [autoResize]=\"false\"\n [loading]=\"loading()\"\n (chartInit)=\"onChartInit($event)\"\n (chartClick)=\"onChartClick($event)\"\n class=\"fx-chart\"\n [style.height]=\"height()\"\n ></div>\n } @else {\n <!-- Until echarts is loaded + initialised (or the element scrolls into view)\n the panel keeps the chart's footprint with a quiet pulsing sketch\n instead of an empty box (anh T\u00FA 04/09: \"the page is too blank\"). -->\n <div class=\"chart-placeholder\" aria-hidden=\"true\">\n @for (h of placeholderBars; track $index) {\n <span class=\"chart-placeholder__bar\" [style.height.%]=\"h\"></span>\n }\n </div>\n }\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block;width:100%}.chart-wrapper{position:relative;width:100%;height:100%}.chart-container{width:100%;height:100%}.chart-loader{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:rgba(var(--bg-primary-hover)/.6);font-weight:500;font-size:1.1rem;z-index:2;border-radius:4px}.chart-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;gap:12px;color:rgba(var(--text-secondary)/1)}.chart-empty-icon{width:48px;height:48px;opacity:.4}.chart-empty-text{font-size:14px;font-weight:500}.chart-placeholder{position:absolute;inset:0;display:flex;align-items:flex-end;gap:6px;padding:12px 8px 22px;animation:chart-placeholder-pulse 1.6s ease-in-out infinite}.chart-placeholder__bar{flex:1;border-radius:3px 3px 0 0;background:rgb(var(--og-track))}@keyframes chart-placeholder-pulse{0%,to{opacity:1}50%{opacity:.45}}@media(prefers-reduced-motion:reduce){.chart-placeholder{animation:none}}\n"] }]
|
|
723
778
|
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.ChangeDetectorRef }], propDecorators: { type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], palette: [{ type: i0.Input, args: [{ isSignal: true, alias: "palette", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], pieLegendAlign: [{ type: i0.Input, args: [{ isSignal: true, alias: "pieLegendAlign", required: false }] }], pieLegendPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "pieLegendPosition", required: false }] }], showPieLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPieLabel", required: false }] }], showPieLegend: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPieLegend", required: false }] }], pieCenterLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "pieCenterLabel", required: false }] }], mobileWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "mobileWidth", required: false }] }], labelFontFamily: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelFontFamily", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], chartClick: [{ type: i0.Output, args: ["chartClick"] }], chartInit: [{ type: i0.Output, args: ["chartInit"] }], onThemeChange: [{
|
|
724
779
|
type: HostListener,
|
|
725
780
|
args: ['window:theme-changed']
|
|
726
781
|
}] } });
|
|
727
782
|
|
|
783
|
+
let pending;
|
|
784
|
+
/**
|
|
785
|
+
* Warm the echarts chunk before any chart is on screen.
|
|
786
|
+
*
|
|
787
|
+
* `fx-ui-chart` loads echarts lazily (`provideEchartsCore({ echarts: () =>
|
|
788
|
+
* import('echarts') })`), so the FIRST chart of a session pays the chunk
|
|
789
|
+
* download right when the user is looking at an empty panel. Call this once
|
|
790
|
+
* from an idle callback when the app's env injector is created (e.g. a
|
|
791
|
+
* `provideEnvironmentInitializer` in the remote's root route) and the dashboard
|
|
792
|
+
* charts appear on the frame they mount. Same module URL as the directive's
|
|
793
|
+
* import ⇒ one shared instance, no double load.
|
|
794
|
+
*/
|
|
795
|
+
function preloadEcharts() {
|
|
796
|
+
return (pending ??= import('echarts'));
|
|
797
|
+
}
|
|
798
|
+
/** Schedule `preloadEcharts()` when the main thread is idle (SSR/tests: no-op). */
|
|
799
|
+
function preloadEchartsWhenIdle(timeoutMs = 3000) {
|
|
800
|
+
if (typeof window === 'undefined')
|
|
801
|
+
return;
|
|
802
|
+
const w = window;
|
|
803
|
+
if (typeof w.requestIdleCallback === 'function') {
|
|
804
|
+
w.requestIdleCallback(() => void preloadEcharts(), { timeout: timeoutMs });
|
|
805
|
+
}
|
|
806
|
+
else {
|
|
807
|
+
setTimeout(() => void preloadEcharts(), 1500);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
|
|
728
811
|
/**
|
|
729
812
|
* Generated bundle index. Do not edit.
|
|
730
813
|
*/
|
|
731
814
|
|
|
732
|
-
export { ChartComponent };
|
|
815
|
+
export { ChartComponent, preloadEcharts, preloadEchartsWhenIdle };
|
|
733
816
|
//# sourceMappingURL=foxeltech-angular-ui-chart.mjs.map
|