@foxeltech/angular-ui 0.4.0 → 0.7.75

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.
Files changed (189) hide show
  1. package/archivo/archivo-400-latin-ext.woff2 +0 -0
  2. package/archivo/archivo-400-latin.woff2 +0 -0
  3. package/archivo/archivo-400-vietnamese.woff2 +0 -0
  4. package/archivo/archivo-500-latin-ext.woff2 +0 -0
  5. package/archivo/archivo-500-latin.woff2 +0 -0
  6. package/archivo/archivo-500-vietnamese.woff2 +0 -0
  7. package/archivo/archivo-600-latin-ext.woff2 +0 -0
  8. package/archivo/archivo-600-latin.woff2 +0 -0
  9. package/archivo/archivo-600-vietnamese.woff2 +0 -0
  10. package/archivo/archivo-700-latin-ext.woff2 +0 -0
  11. package/archivo/archivo-700-latin.woff2 +0 -0
  12. package/archivo/archivo-700-vietnamese.woff2 +0 -0
  13. package/components.css +332 -30
  14. package/dialogs.css +2 -2
  15. package/familjen-grotesk/familjen-grotesk-400-latin-ext.woff2 +0 -0
  16. package/familjen-grotesk/familjen-grotesk-400-latin.woff2 +0 -0
  17. package/familjen-grotesk/familjen-grotesk-400-vietnamese.woff2 +0 -0
  18. package/familjen-grotesk/familjen-grotesk-500-latin-ext.woff2 +0 -0
  19. package/familjen-grotesk/familjen-grotesk-500-latin.woff2 +0 -0
  20. package/familjen-grotesk/familjen-grotesk-500-vietnamese.woff2 +0 -0
  21. package/familjen-grotesk/familjen-grotesk-600-latin-ext.woff2 +0 -0
  22. package/familjen-grotesk/familjen-grotesk-600-latin.woff2 +0 -0
  23. package/familjen-grotesk/familjen-grotesk-600-vietnamese.woff2 +0 -0
  24. package/familjen-grotesk/familjen-grotesk-700-latin-ext.woff2 +0 -0
  25. package/familjen-grotesk/familjen-grotesk-700-latin.woff2 +0 -0
  26. package/familjen-grotesk/familjen-grotesk-700-vietnamese.woff2 +0 -0
  27. package/fesm2022/foxeltech-angular-ui-chart.mjs +381 -40
  28. package/fesm2022/foxeltech-angular-ui-chart.mjs.map +1 -1
  29. package/fesm2022/foxeltech-angular-ui-flow.mjs +176 -3
  30. package/fesm2022/foxeltech-angular-ui-flow.mjs.map +1 -1
  31. package/fesm2022/foxeltech-angular-ui-rich-text.mjs +2 -2
  32. package/fesm2022/foxeltech-angular-ui-rich-text.mjs.map +1 -1
  33. package/fesm2022/foxeltech-angular-ui.mjs +1327 -151
  34. package/fesm2022/foxeltech-angular-ui.mjs.map +1 -1
  35. package/fonts.css +558 -133
  36. package/ibm-plex-mono/ibm-plex-mono-400-cyrillic-ext.woff2 +0 -0
  37. package/ibm-plex-mono/ibm-plex-mono-400-cyrillic.woff2 +0 -0
  38. package/ibm-plex-mono/ibm-plex-mono-400-latin-ext.woff2 +0 -0
  39. package/ibm-plex-mono/ibm-plex-mono-400-latin.woff2 +0 -0
  40. package/ibm-plex-mono/ibm-plex-mono-400-vietnamese.woff2 +0 -0
  41. package/ibm-plex-mono/ibm-plex-mono-500-cyrillic-ext.woff2 +0 -0
  42. package/ibm-plex-mono/ibm-plex-mono-500-cyrillic.woff2 +0 -0
  43. package/ibm-plex-mono/ibm-plex-mono-500-latin-ext.woff2 +0 -0
  44. package/ibm-plex-mono/ibm-plex-mono-500-latin.woff2 +0 -0
  45. package/ibm-plex-mono/ibm-plex-mono-500-vietnamese.woff2 +0 -0
  46. package/ibm-plex-mono/ibm-plex-mono-600-cyrillic-ext.woff2 +0 -0
  47. package/ibm-plex-mono/ibm-plex-mono-600-cyrillic.woff2 +0 -0
  48. package/ibm-plex-mono/ibm-plex-mono-600-latin-ext.woff2 +0 -0
  49. package/ibm-plex-mono/ibm-plex-mono-600-latin.woff2 +0 -0
  50. package/ibm-plex-mono/ibm-plex-mono-600-vietnamese.woff2 +0 -0
  51. package/jetbrains-mono/jetbrains-mono-400-cyrillic-ext.woff2 +0 -0
  52. package/jetbrains-mono/jetbrains-mono-400-cyrillic.woff2 +0 -0
  53. package/jetbrains-mono/jetbrains-mono-400-greek.woff2 +0 -0
  54. package/jetbrains-mono/jetbrains-mono-400-latin-ext.woff2 +0 -0
  55. package/jetbrains-mono/jetbrains-mono-400-latin.woff2 +0 -0
  56. package/jetbrains-mono/jetbrains-mono-400-vietnamese.woff2 +0 -0
  57. package/jetbrains-mono/jetbrains-mono-500-cyrillic-ext.woff2 +0 -0
  58. package/jetbrains-mono/jetbrains-mono-500-cyrillic.woff2 +0 -0
  59. package/jetbrains-mono/jetbrains-mono-500-greek.woff2 +0 -0
  60. package/jetbrains-mono/jetbrains-mono-500-latin-ext.woff2 +0 -0
  61. package/jetbrains-mono/jetbrains-mono-500-latin.woff2 +0 -0
  62. package/jetbrains-mono/jetbrains-mono-500-vietnamese.woff2 +0 -0
  63. package/jetbrains-mono/jetbrains-mono-600-cyrillic-ext.woff2 +0 -0
  64. package/jetbrains-mono/jetbrains-mono-600-cyrillic.woff2 +0 -0
  65. package/jetbrains-mono/jetbrains-mono-600-greek.woff2 +0 -0
  66. package/jetbrains-mono/jetbrains-mono-600-latin-ext.woff2 +0 -0
  67. package/jetbrains-mono/jetbrains-mono-600-latin.woff2 +0 -0
  68. package/jetbrains-mono/jetbrains-mono-600-vietnamese.woff2 +0 -0
  69. package/jetbrains-mono/jetbrains-mono-700-cyrillic-ext.woff2 +0 -0
  70. package/jetbrains-mono/jetbrains-mono-700-cyrillic.woff2 +0 -0
  71. package/jetbrains-mono/jetbrains-mono-700-greek.woff2 +0 -0
  72. package/jetbrains-mono/jetbrains-mono-700-latin-ext.woff2 +0 -0
  73. package/jetbrains-mono/jetbrains-mono-700-latin.woff2 +0 -0
  74. package/jetbrains-mono/jetbrains-mono-700-vietnamese.woff2 +0 -0
  75. package/newsreader/newsreader-400-latin-ext.woff2 +0 -0
  76. package/newsreader/newsreader-400-latin.woff2 +0 -0
  77. package/newsreader/newsreader-400-vietnamese.woff2 +0 -0
  78. package/newsreader/newsreader-500-latin-ext.woff2 +0 -0
  79. package/newsreader/newsreader-500-latin.woff2 +0 -0
  80. package/newsreader/newsreader-500-vietnamese.woff2 +0 -0
  81. package/newsreader/newsreader-600-latin-ext.woff2 +0 -0
  82. package/newsreader/newsreader-600-latin.woff2 +0 -0
  83. package/newsreader/newsreader-600-vietnamese.woff2 +0 -0
  84. package/package.json +5 -2
  85. package/source-serif-4/source-serif-4-400-cyrillic-ext.woff2 +0 -0
  86. package/source-serif-4/source-serif-4-400-cyrillic.woff2 +0 -0
  87. package/source-serif-4/source-serif-4-400-greek.woff2 +0 -0
  88. package/source-serif-4/source-serif-4-400-latin-ext.woff2 +0 -0
  89. package/source-serif-4/source-serif-4-400-latin.woff2 +0 -0
  90. package/source-serif-4/source-serif-4-400-vietnamese.woff2 +0 -0
  91. package/source-serif-4/source-serif-4-600-cyrillic-ext.woff2 +0 -0
  92. package/source-serif-4/source-serif-4-600-cyrillic.woff2 +0 -0
  93. package/source-serif-4/source-serif-4-600-greek.woff2 +0 -0
  94. package/source-serif-4/source-serif-4-600-latin-ext.woff2 +0 -0
  95. package/source-serif-4/source-serif-4-600-latin.woff2 +0 -0
  96. package/source-serif-4/source-serif-4-600-vietnamese.woff2 +0 -0
  97. package/src/lib/styles/components.css +332 -30
  98. package/src/lib/styles/dialogs.css +2 -2
  99. package/src/lib/styles/fonts.css +558 -133
  100. package/src/lib/styles/tailwind.css +13 -1
  101. package/src/lib/styles/theme.css +642 -69
  102. package/src/lib/styles/tokens.css +81 -19
  103. package/src/lib/styles/utilities.css +16 -1
  104. package/src/lib/ui/components/bar-list/bar-list.component.html +29 -0
  105. package/src/lib/ui/components/button/button.component.html +9 -5
  106. package/src/lib/ui/components/checkbox/checkbox.component.html +1 -1
  107. package/src/lib/ui/components/code-block/code-block.component.html +8 -0
  108. package/src/lib/ui/components/drawer/drawer.component.html +35 -12
  109. package/src/lib/ui/components/drawer/drawer.component.scss +106 -18
  110. package/src/lib/ui/components/facets-rail/facets-rail.component.html +37 -0
  111. package/src/lib/ui/components/filter-pills/filter-pills.component.html +16 -0
  112. package/src/lib/ui/components/kanban-board/kanban-board.component.scss +9 -9
  113. package/src/lib/ui/components/list-row/list-row.component.html +20 -0
  114. package/src/lib/ui/components/master-detail/master-detail.component.css +79 -5
  115. package/src/lib/ui/components/master-detail/master-detail.component.html +57 -7
  116. package/src/lib/ui/components/pending-badge/pending-badge.component.html +15 -0
  117. package/src/lib/ui/components/pipeline-stepper/pipeline-stepper.component.html +45 -0
  118. package/src/lib/ui/components/project-card/project-card.component.html +102 -0
  119. package/src/lib/ui/components/radio-button/radio-button.component.html +1 -1
  120. package/src/lib/ui/components/radio-button-toggle/radio-button-toggle.component.html +1 -1
  121. package/src/lib/ui/components/route-skeleton/route-skeleton.component.css +38 -0
  122. package/src/lib/ui/components/route-skeleton/route-skeleton.component.html +184 -0
  123. package/src/lib/ui/components/sample-frame/sample-frame.component.html +18 -0
  124. package/src/lib/ui/components/score-badge/score-badge.component.html +7 -0
  125. package/src/lib/ui/components/screenshot-frame/screenshot-frame.component.html +40 -0
  126. package/src/lib/ui/components/section-card/section-card.component.html +15 -6
  127. package/src/lib/ui/components/segmented/segmented.component.html +42 -0
  128. package/src/lib/ui/components/segmented/segmented.component.scss +12 -0
  129. package/src/lib/ui/components/skeleton-dashboard/skeleton-dashboard.component.html +24 -0
  130. package/src/lib/ui/components/skeleton-detail/skeleton-detail.component.html +8 -8
  131. package/src/lib/ui/components/skeleton-facets/skeleton-facets.component.html +15 -0
  132. package/src/lib/ui/components/skeleton-frame/skeleton-frame.component.html +59 -0
  133. package/src/lib/ui/components/skeleton-list/skeleton-list.component.html +6 -6
  134. package/src/lib/ui/components/slider/slider.component.html +2 -2
  135. package/src/lib/ui/components/sparkbar/sparkbar.component.html +14 -0
  136. package/src/lib/ui/components/stage-dots/stage-dots.component.html +13 -0
  137. package/src/lib/ui/components/stat-cards/stat-cards.component.html +44 -11
  138. package/src/lib/ui/components/status-dot/status-dot.component.html +13 -0
  139. package/src/lib/ui/components/switch/switch.component.html +1 -1
  140. package/src/lib/ui/components/tab-group/tab-group.component.html +37 -8
  141. package/src/lib/ui/components/tab-group/tab-group.component.scss +41 -1
  142. package/src/lib/ui/components/tag/tag.component.html +2 -2
  143. package/src/lib/ui/components/theme-picker/theme-picker.component.html +44 -0
  144. package/src/lib/ui/components/toast/toast.component.html +6 -6
  145. package/src/lib/ui/components/tree-diagram/tree-diagram.component.html +3 -3
  146. package/tailwind.css +13 -1
  147. package/theme.css +642 -69
  148. package/tokens.css +81 -19
  149. package/types/foxeltech-angular-ui-chart.d.ts +90 -3
  150. package/types/foxeltech-angular-ui-flow.d.ts +83 -1
  151. package/types/foxeltech-angular-ui.d.ts +909 -48
  152. package/utilities.css +16 -1
  153. package/BeVietnamPro/BeVietnamPro-Bold.woff2 +0 -0
  154. package/BeVietnamPro/BeVietnamPro-ExtraLight.woff2 +0 -0
  155. package/BeVietnamPro/BeVietnamPro-Light.woff2 +0 -0
  156. package/BeVietnamPro/BeVietnamPro-Medium.woff2 +0 -0
  157. package/BeVietnamPro/BeVietnamPro-Regular.woff2 +0 -0
  158. package/BeVietnamPro/BeVietnamPro-SemiBold.woff2 +0 -0
  159. package/BeVietnamPro/BeVietnamPro-Thin.woff2 +0 -0
  160. package/SpaceMono-Regular.woff2 +0 -0
  161. package/dm-sans/DMSans-Bold.woff2 +0 -0
  162. package/dm-sans/DMSans-Light.woff2 +0 -0
  163. package/dm-sans/DMSans-Medium.woff2 +0 -0
  164. package/dm-sans/DMSans-Regular.woff2 +0 -0
  165. package/dm-sans/DMSans-SemiBold.woff2 +0 -0
  166. package/dm-sans/DMSans-Thin.woff2 +0 -0
  167. package/orbitron/Orbitron-Bold.woff2 +0 -0
  168. package/orbitron/Orbitron-Medium.woff2 +0 -0
  169. package/orbitron/Orbitron-Regular.woff2 +0 -0
  170. package/orbitron/Orbitron-SemiBold.woff2 +0 -0
  171. package/roboto/Roboto-Black.woff2 +0 -0
  172. package/roboto/Roboto-BlackItalic.woff2 +0 -0
  173. package/roboto/Roboto-Bold.woff2 +0 -0
  174. package/roboto/Roboto-BoldCondensed.woff2 +0 -0
  175. package/roboto/Roboto-BoldCondensedItalic.woff2 +0 -0
  176. package/roboto/Roboto-BoldItalic.woff2 +0 -0
  177. package/roboto/Roboto-Condensed.woff2 +0 -0
  178. package/roboto/Roboto-CondensedItalic.woff2 +0 -0
  179. package/roboto/Roboto-Italic.woff2 +0 -0
  180. package/roboto/Roboto-Light.woff2 +0 -0
  181. package/roboto/Roboto-LightItalic.woff2 +0 -0
  182. package/roboto/Roboto-Medium.woff2 +0 -0
  183. package/roboto/Roboto-MediumItalic.woff2 +0 -0
  184. package/roboto/Roboto-Regular.woff2 +0 -0
  185. package/roboto/Roboto-Thin.woff2 +0 -0
  186. package/roboto/Roboto-ThinItalic.woff2 +0 -0
  187. package/src/lib/ui/components/skeleton-table-loading/skeleton-table-loading.component.html +0 -21
  188. package/src/lib/ui/components/skeleton-table-loading/skeleton-table-loading.component.scss +0 -0
  189. package/src/lib/ui/components/tab-component/tab.component.html +0 -1
@@ -17,8 +17,14 @@ class ChartComponent {
17
17
  pieLegendAlign = input('10%', ...(ngDevMode ? [{ debugName: "pieLegendAlign" }] : /* istanbul ignore next */ []));
18
18
  pieLegendPosition = input('middle', ...(ngDevMode ? [{ debugName: "pieLegendPosition" }] : /* istanbul ignore next */ []));
19
19
  showPieLabel = input(true, ...(ngDevMode ? [{ debugName: "showPieLabel" }] : /* istanbul ignore next */ []));
20
+ /** Hide the built-in legend (panels that render their own legend beside the
21
+ donut); the donut then centres in the container. */
22
+ showPieLegend = input(true, ...(ngDevMode ? [{ debugName: "showPieLegend" }] : /* istanbul ignore next */ []));
23
+ /** Caption under the donut's centre total (design: "42 / confirmed"). */
24
+ pieCenterLabel = input('', ...(ngDevMode ? [{ debugName: "pieCenterLabel" }] : /* istanbul ignore next */ []));
20
25
  mobileWidth = input(350, ...(ngDevMode ? [{ debugName: "mobileWidth" }] : /* istanbul ignore next */ []));
21
- labelFontFamily = input('Roboto', ...(ngDevMode ? [{ debugName: "labelFontFamily" }] : /* istanbul ignore next */ []));
26
+ /** Override font for canvas text; empty = follow the theme's --og-font-figure. */
27
+ labelFontFamily = input('', ...(ngDevMode ? [{ debugName: "labelFontFamily" }] : /* istanbul ignore next */ []));
22
28
  emptyText = input('No data available', ...(ngDevMode ? [{ debugName: "emptyText" }] : /* istanbul ignore next */ []));
23
29
  /* ================= OUTPUTS ================= */
24
30
  chartClick = output();
@@ -26,11 +32,38 @@ class ChartComponent {
26
32
  /* ================= INTERNAL ================= */
27
33
  chartOptions = {};
28
34
  isEmpty = false;
35
+ /**
36
+ * ⚠️ Cổng cho `<div echarts>` trong template. Trước đây div render NGAY với
37
+ * `chartOptions = {}`, nên NgxEchartsDirective khởi tạo chart bằng options
38
+ * RỖNG; options thật gán vào giữa lúc `await createChart()` của lần đó ⇒
39
+ * onOptionsChange() thấy `chart == null` và createChart() LẦN HAI trên cùng
40
+ * DOM node ⇒ "[ECharts] There is a chart instance already initialized on the
41
+ * dom.". Chỉ dựng directive khi đã có options thật ⇒ đúng một lần init.
42
+ */
43
+ hasOptions = false;
29
44
  labelFontSize = 12;
30
45
  chartInstance;
31
46
  resizeObserver;
32
47
  intersectionObserver;
33
48
  canInit = false;
49
+ destroyed = false;
50
+ /**
51
+ * ⚠️ Đang chờ init đầu tiên của directive hoàn tất?
52
+ *
53
+ * NgxEchartsDirective đăng ký `changeFilter.notFirstAndEmpty('options', …)`:
54
+ * lần gán `options` ĐẦU bị bỏ qua (ngAfterViewInit lo), lần gán THỨ HAI gọi
55
+ * onOptionsChange(). Mà onOptionsChange() chỉ createChart() khi `this.chart`
56
+ * còn null — và nó null suốt thời gian `await createChart()` của lần đầu.
57
+ * Nên nếu ta gán `chartOptions` (object mới) trong khoảng await đó thì
58
+ * init() chạy HAI LẦN trên cùng DOM node ⇒
59
+ * "[ECharts] There is a chart instance already initialized on the dom."
60
+ *
61
+ * Cách chặn: trong khoảng chờ, giữ options ở `deferredOptions` chứ không đụng
62
+ * vào property đang bind; init xong thì áp một lần qua instance.
63
+ */
64
+ awaitingFirstInit = false;
65
+ deferredOptions;
66
+ pendingFrames = new Set();
34
67
  isMobile = false;
35
68
  containerWidth = 0;
36
69
  constructor(ref, cdr) {
@@ -41,7 +74,7 @@ class ChartComponent {
41
74
  ngAfterViewInit() {
42
75
  // Only initialize when visible (tab-safe)
43
76
  this.intersectionObserver = new IntersectionObserver(([entry]) => {
44
- if (entry.isIntersecting && !this.canInit) {
77
+ if (entry.isIntersecting && !this.canInit && !this.destroyed) {
45
78
  this.canInit = true;
46
79
  this.updateResponsiveState();
47
80
  this.rebuildChart();
@@ -51,69 +84,215 @@ class ChartComponent {
51
84
  this.intersectionObserver.observe(this.ref.nativeElement);
52
85
  // Resize-safe (tabs, flex, async layout)
53
86
  this.resizeObserver = new ResizeObserver(() => {
54
- if (!this.canInit)
87
+ if (!this.canInit || this.destroyed)
55
88
  return;
56
- this.updateResponsiveState();
89
+ // forceMediaRecalc tự đo lại và chỉ dựng lại khi kích thước đổi.
57
90
  this.forceMediaRecalc();
58
91
  });
59
92
  this.resizeObserver.observe(this.ref.nativeElement);
60
93
  }
61
94
  ngOnChanges() {
62
- if (this.canInit) {
95
+ if (this.canInit && !this.destroyed) {
63
96
  this.updateResponsiveState();
64
97
  this.rebuildChart();
65
98
  }
66
99
  }
67
100
  ngOnDestroy() {
101
+ // NgxEchartsDirective tự dispose instance của nó, nên KHÔNG dispose ở đây
102
+ // (sẽ dispose trùng). Việc phải làm là ngắt mọi đường còn chạm được vào
103
+ // instance sau khi nó đã bị dispose: callback rAF đang chờ + tham chiếu
104
+ // `chartInstance` mà forceMediaRecalc/rebuildChart vẫn dùng.
105
+ this.destroyed = true;
106
+ for (const id of this.pendingFrames)
107
+ cancelAnimationFrame(id);
108
+ this.pendingFrames.clear();
68
109
  this.resizeObserver?.disconnect();
69
110
  this.intersectionObserver?.disconnect();
111
+ this.resizeObserver = undefined;
112
+ this.intersectionObserver = undefined;
113
+ this.chartInstance = undefined;
114
+ this.awaitingFirstInit = false;
115
+ this.deferredOptions = undefined;
116
+ this.sweepOrphanInstance();
117
+ }
118
+ /**
119
+ * ⚠️ RACE TRONG NGX-ECHARTS v20 — nguồn của
120
+ * "[ECharts] There is a chart instance already initialized on the dom."
121
+ *
122
+ * NgxEchartsDirective.onOptionsChange() làm:
123
+ * this.chart = await this.createChart(); // ← destroy có thể rơi vào đây
124
+ * this.chart$.next(this.chart);
125
+ * this.chartInit.emit(this.chart);
126
+ *
127
+ * Destroy đúng lúc đang await thì ngOnDestroy → dispose() thấy `this.chart == null`
128
+ * nên KHÔNG dispose gì. Sau đó await resolve, gán một instance ECharts còn sống cho
129
+ * directive đã chết → instance MỒ CÔI, vẫn nằm trong registry của echarts gắn với
130
+ * DOM node đó. Angular tái dùng node ⇒ init() lần sau báo "already initialized".
131
+ *
132
+ * Không thể bắt qua (chartInit): Angular v20 TỪ CHỐI emit trên OutputRef đã destroy
133
+ * (chỉ log NG0953, listener không hề được gọi). Nên đường duy nhất là hỏi thẳng
134
+ * echarts xem node còn instance nào không, ở các macrotask SAU destroy.
135
+ */
136
+ sweepOrphanInstance() {
137
+ const host = this.ref.nativeElement.querySelector('.fx-chart');
138
+ if (!host)
139
+ return;
140
+ // Chỉ nạp echarts nếu nó đã ở trong module cache — không tự kéo bundle về
141
+ // chỉ để đi dọn.
142
+ let attempt = 0;
143
+ const sweep = () => {
144
+ import('echarts').then(({ getInstanceByDom }) => {
145
+ const orphan = getInstanceByDom(host);
146
+ if (orphan) {
147
+ if (!orphan.isDisposed())
148
+ orphan.dispose();
149
+ return;
150
+ }
151
+ // createChart() phải chờ dynamic import nên instance mồ côi có thể xuất
152
+ // hiện muộn hơn một macrotask; thử lại vài nhịp rồi dừng.
153
+ if (++attempt < 5)
154
+ setTimeout(sweep, 50 * attempt);
155
+ });
156
+ };
157
+ setTimeout(sweep);
158
+ }
159
+ /** rAF có thể huỷ được khi destroy — tránh chạm instance đã dispose. */
160
+ schedule(fn) {
161
+ if (this.destroyed)
162
+ return;
163
+ const id = requestAnimationFrame(() => {
164
+ this.pendingFrames.delete(id);
165
+ if (this.destroyed)
166
+ return;
167
+ fn();
168
+ });
169
+ this.pendingFrames.add(id);
170
+ }
171
+ /** Instance còn dùng được? (đã dispose thì mọi API echarts đều ném lỗi) */
172
+ get liveChart() {
173
+ const chart = this.chartInstance;
174
+ if (!chart || this.destroyed || chart.isDisposed())
175
+ return undefined;
176
+ return chart;
70
177
  }
71
178
  onThemeChange() {
72
- if (!this.canInit)
179
+ if (!this.canInit || this.destroyed)
73
180
  return;
74
- requestAnimationFrame(() => this.rebuildChart());
181
+ this.schedule(() => this.rebuildChart());
75
182
  }
76
183
  /* ================= EVENTS ================= */
77
184
  onChartClick(event) {
78
185
  this.chartClick.emit(event);
79
186
  }
80
- onChartInit(chart) {
187
+ // ⚠️ Phải để `any`: NgxEchartsDirective emit `EChartsType` từ
188
+ // 'echarts/types/dist/shared', TS coi nó KHÔNG tương thích với `ECharts`
189
+ // của 'echarts' (private `_ssr` khai báo riêng) → template typecheck fail.
190
+ onChartInit(raw) {
191
+ const chart = raw;
192
+ // Phòng thân: bình thường Angular đã chặn emit sau destroy (NG0953) nên
193
+ // nhánh này không tới; nhưng nếu tới thì tuyệt đối không được gán lại
194
+ // `chartInstance` sau ngOnDestroy. Việc thu dọn instance mồ côi do
195
+ // sweepOrphanInstance() lo — xem ghi chú ở đó.
196
+ if (this.destroyed) {
197
+ if (!chart.isDisposed())
198
+ chart.dispose();
199
+ return;
200
+ }
81
201
  this.chartInstance = chart;
202
+ this.awaitingFirstInit = false;
203
+ if (this.deferredOptions) {
204
+ const options = this.deferredOptions;
205
+ this.deferredOptions = undefined;
206
+ this.chartOptions = options;
207
+ if (!chart.isDisposed()) {
208
+ chart.clear();
209
+ chart.setOption(options, true);
210
+ }
211
+ }
82
212
  this.chartInit.emit(chart);
83
- requestAnimationFrame(() => this.forceMediaRecalc());
213
+ this.schedule(() => this.forceMediaRecalc());
84
214
  }
85
215
  /* ================= CORE FIX ================= */
216
+ /**
217
+ * @returns true nếu trạng thái responsive THỰC SỰ đổi (nên dựng lại options).
218
+ *
219
+ * Trả về boolean là điều then chốt cho animation lúc chart xuất hiện — xem
220
+ * ghi chú ở `forceMediaRecalc`.
221
+ */
86
222
  updateResponsiveState() {
87
223
  const { width } = this.ref.nativeElement.getBoundingClientRect();
88
- if (width > 0) {
89
- this.containerWidth = width;
90
- this.isMobile = width < this.mobileWidth();
91
- }
224
+ if (width <= 0)
225
+ return false;
226
+ const isMobile = width < this.mobileWidth();
227
+ const changed = width !== this.containerWidth || isMobile !== this.isMobile;
228
+ this.containerWidth = width;
229
+ this.isMobile = isMobile;
230
+ return changed;
92
231
  }
232
+ /**
233
+ * ⚠️ CHỈ dựng lại options khi kích thước thật sự đổi — nếu không thì
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.
248
+ *
249
+ * `resize()` thì luôn gọi được — nó không cắt animation.
250
+ */
93
251
  forceMediaRecalc(retry = 0) {
94
- if (!this.chartInstance)
252
+ if (!this.liveChart)
95
253
  return;
96
254
  const { width, height } = this.ref.nativeElement.getBoundingClientRect();
97
255
  if ((width === 0 || height === 0) && retry < 10) {
98
- requestAnimationFrame(() => this.forceMediaRecalc(retry + 1));
256
+ this.schedule(() => this.forceMediaRecalc(retry + 1));
99
257
  return;
100
258
  }
259
+ // ⚠️ `resize()` cũng phải CÓ ĐIỀU KIỆN. ECharts nhảy thẳng tới trạng thái
260
+ // cuối khi resize, nên gọi nó ở frame ngay sau init sẽ CẮT animation lúc
261
+ // chart xuất hiện — đo được: 3 lần đổi khung trong 200ms rồi đứng im, dù
262
+ // đã đặt animationDuration 3000ms.
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())
266
+ return;
101
267
  this.rebuildChart();
102
- this.chartInstance.resize();
268
+ this.liveChart?.resize();
103
269
  }
104
270
  rebuildChart() {
271
+ if (this.destroyed)
272
+ return;
105
273
  this.isEmpty = this.checkEmpty();
106
274
  if (this.isEmpty) {
107
- if (this.chartInstance) {
108
- this.chartInstance.clear();
109
- }
275
+ this.liveChart?.clear();
110
276
  return;
111
277
  }
112
- this.chartOptions = this.buildOptions();
113
- if (this.chartInstance) {
114
- this.chartInstance.clear();
115
- this.chartInstance.setOption(this.chartOptions, true);
278
+ const options = this.buildOptions();
279
+ const chart = this.liveChart;
280
+ if (!chart) {
281
+ if (this.awaitingFirstInit) {
282
+ // Đang trong khoảng await của lần init đầu — KHÔNG gán vào property
283
+ // đang bind, xem ghi chú ở `awaitingFirstInit`.
284
+ this.deferredOptions = options;
285
+ return;
286
+ }
287
+ this.awaitingFirstInit = true;
288
+ this.chartOptions = options;
289
+ this.hasOptions = true;
290
+ return;
116
291
  }
292
+ this.chartOptions = options;
293
+ this.hasOptions = true;
294
+ chart.clear();
295
+ chart.setOption(options, true);
117
296
  }
118
297
  checkEmpty() {
119
298
  const type = this.detectType();
@@ -123,6 +302,14 @@ class ChartComponent {
123
302
  const values = data?.values;
124
303
  return !categories?.length || !values?.length;
125
304
  }
305
+ if (type === 'line') {
306
+ const data = this.data();
307
+ return !data?.categories?.length || !data?.values?.length;
308
+ }
309
+ if (type === 'area-trend') {
310
+ const data = this.data();
311
+ return !data?.categories?.length || !data?.current?.length;
312
+ }
126
313
  if (type === 'pie') {
127
314
  const data = this.data();
128
315
  if (!Array.isArray(data) || data.length === 0)
@@ -146,7 +333,7 @@ class ChartComponent {
146
333
  width: 80,
147
334
  overflow: 'truncate',
148
335
  ellipsis: '…',
149
- fontFamily: this.labelFontFamily(),
336
+ fontFamily: this.resolvedFontFamily(),
150
337
  fontSize: this.labelFontSize,
151
338
  color: FxUtils.convertColorFromVariable('--text-secondary'),
152
339
  },
@@ -159,7 +346,7 @@ class ChartComponent {
159
346
  yAxis: {
160
347
  type: 'value',
161
348
  axisLabel: {
162
- fontFamily: this.labelFontFamily(),
349
+ fontFamily: this.resolvedFontFamily(),
163
350
  fontSize: this.labelFontSize,
164
351
  color: FxUtils.convertColorFromVariable('--text-secondary'),
165
352
  },
@@ -185,7 +372,7 @@ class ChartComponent {
185
372
  label: {
186
373
  show: true,
187
374
  position: 'top',
188
- fontFamily: this.labelFontFamily(),
375
+ fontFamily: this.resolvedFontFamily(),
189
376
  fontSize: this.labelFontSize,
190
377
  color: FxUtils.convertColorFromVariable('--text-primary'),
191
378
  },
@@ -199,6 +386,7 @@ class ChartComponent {
199
386
  color: colors,
200
387
  tooltip: { trigger: 'item' },
201
388
  legend: {
389
+ show: this.showPieLegend(),
202
390
  type: 'scroll',
203
391
  orient: layout.legendOrient,
204
392
  icon: 'circle',
@@ -213,7 +401,7 @@ class ChartComponent {
213
401
  return item ? `${name}: ${item.value}` : name;
214
402
  },
215
403
  textStyle: {
216
- fontFamily: this.labelFontFamily(),
404
+ fontFamily: this.resolvedFontFamily(),
217
405
  fontSize: 13,
218
406
  fontWeight: 500,
219
407
  color: FxUtils.convertColorFromVariable('--text-primary'),
@@ -231,7 +419,7 @@ class ChartComponent {
231
419
  position: 'outside',
232
420
  formatter: '{d}%',
233
421
  fontSize: this.labelFontSize,
234
- fontFamily: this.labelFontFamily(),
422
+ fontFamily: this.resolvedFontFamily(),
235
423
  color: FxUtils.convertColorFromVariable('--text-primary'),
236
424
  textBorderWidth: 0,
237
425
  textBorderColor: 'transparent',
@@ -243,26 +431,124 @@ class ChartComponent {
243
431
  },
244
432
  },
245
433
  ],
246
- graphic: {
247
- type: 'group',
248
- left: layout.graphicLeft,
249
- top: layout.graphicTop,
250
- bounding: 'raw',
251
- children: [
434
+ // Centre figure: positioned by x/y in pixels — graphic left/top
435
+ // percentages place the element's bounding-box EDGE, which shifted the
436
+ // total right/down by half its own size (mis-centred, caption clipped).
437
+ graphic: (() => {
438
+ const cx = (this.containerWidth || 400) * (parseFloat(layout.pieCenter[0]) / 100);
439
+ const ch = this.ref.nativeElement.clientHeight || 300;
440
+ const cy = ch * (parseFloat(layout.pieCenter[1]) / 100);
441
+ return [
252
442
  {
253
443
  type: 'text',
444
+ // Shift the total up when a caption sits under it.
445
+ x: cx,
446
+ y: cy + (this.pieCenterLabel() ? -9 : 0),
254
447
  style: {
255
448
  text: total.toString(),
256
449
  fontSize: 24,
257
450
  fontWeight: 600,
258
- fontFamily: this.labelFontFamily(),
451
+ fontFamily: this.resolvedFontFamily(),
259
452
  fill: FxUtils.convertColorFromVariable('--text-primary'),
260
453
  align: 'center',
261
454
  verticalAlign: 'middle',
262
455
  },
263
456
  },
264
- ],
457
+ ...(this.pieCenterLabel()
458
+ ? [
459
+ {
460
+ type: 'text',
461
+ x: cx,
462
+ y: cy + 12,
463
+ style: {
464
+ text: this.pieCenterLabel(),
465
+ fontSize: 11,
466
+ fontFamily: this.resolvedFontFamily(),
467
+ fill: FxUtils.convertColorFromVariable('--text-secondary'),
468
+ align: 'center',
469
+ verticalAlign: 'middle',
470
+ },
471
+ },
472
+ ]
473
+ : []),
474
+ ];
475
+ })(),
476
+ };
477
+ },
478
+ // V2 preset: single line, accent-coloured — "risk score by release".
479
+ line: (data) => ({
480
+ tooltip: { trigger: 'axis' },
481
+ grid: { left: '3%', right: '4%', bottom: '8%', top: '8%', containLabel: true },
482
+ xAxis: this.categoryAxis(data?.categories || []),
483
+ yAxis: this.valueAxis(),
484
+ series: [
485
+ {
486
+ type: 'line',
487
+ data: data?.values || [],
488
+ smooth: false,
489
+ symbol: 'circle',
490
+ symbolSize: 7,
491
+ lineStyle: { width: 2, color: FxUtils.convertColorFromVariable('--og-accent', 1, { legacyCommaSyntax: true }) },
492
+ itemStyle: { color: FxUtils.convertColorFromVariable('--og-accent', 1, { legacyCommaSyntax: true }) },
493
+ label: {
494
+ show: true,
495
+ position: 'top',
496
+ fontFamily: this.resolvedFontFamily(),
497
+ fontSize: this.labelFontSize,
498
+ color: FxUtils.convertColorFromVariable('--text-primary'),
499
+ },
500
+ },
501
+ ],
502
+ }),
503
+ // V2 preset: weekly trend — accent gradient fill + gray comparison line.
504
+ // data: { categories: string[], current: number[], previous?: number[] }
505
+ 'area-trend': (data) => {
506
+ // legacyCommaSyntax: zrender's colour parser chokes on CSS4 space syntax.
507
+ const accent = FxUtils.convertColorFromVariable('--og-accent', 1, { legacyCommaSyntax: true });
508
+ const accentSoft = FxUtils.convertColorFromVariable('--og-accent', 0.25, { legacyCommaSyntax: true });
509
+ const accentFade = FxUtils.convertColorFromVariable('--og-accent', 0.02, { legacyCommaSyntax: true });
510
+ const series = [
511
+ {
512
+ type: 'line',
513
+ name: data?.currentLabel || 'Current',
514
+ data: data?.current || [],
515
+ smooth: true,
516
+ symbol: 'none',
517
+ lineStyle: { width: 2, color: accent },
518
+ itemStyle: { color: accent },
519
+ areaStyle: {
520
+ color: {
521
+ type: 'linear',
522
+ x: 0, y: 0, x2: 0, y2: 1,
523
+ colorStops: [
524
+ { offset: 0, color: accentSoft },
525
+ { offset: 1, color: accentFade },
526
+ ],
527
+ },
528
+ },
265
529
  },
530
+ ];
531
+ if (data?.previous?.length) {
532
+ series.push({
533
+ type: 'line',
534
+ name: data?.previousLabel || 'Previous',
535
+ data: data.previous,
536
+ smooth: true,
537
+ symbol: 'none',
538
+ lineStyle: {
539
+ width: 1.5,
540
+ type: 'dashed',
541
+ color: FxUtils.convertColorFromVariable('--text-placeholder', 1, { legacyCommaSyntax: true }),
542
+ },
543
+ itemStyle: { color: FxUtils.convertColorFromVariable('--text-placeholder', 1, { legacyCommaSyntax: true }) },
544
+ });
545
+ }
546
+ return {
547
+ tooltip: { trigger: 'axis' },
548
+ grid: { left: '3%', right: '4%', bottom: '8%', top: '10%', containLabel: true },
549
+ xAxis: { ...this.categoryAxis(data?.categories || []), boundaryGap: false },
550
+ yAxis: this.valueAxis(),
551
+ series,
266
552
  };
267
553
  },
268
554
  sunburst: (data, colors) => ({
@@ -279,7 +565,7 @@ class ChartComponent {
279
565
  label: {
280
566
  rotate: 'tangential',
281
567
  fontSize: this.labelFontSize,
282
- fontFamily: this.labelFontFamily(),
568
+ fontFamily: this.resolvedFontFamily(),
283
569
  color: FxUtils.convertColorFromVariable('--text-primary'),
284
570
  textBorderWidth: 0,
285
571
  textBorderColor: 'transparent',
@@ -291,6 +577,19 @@ class ChartComponent {
291
577
  /* ================= PIE LAYOUT ================= */
292
578
  calcPieLayout(data) {
293
579
  const cw = this.containerWidth || 400;
580
+ if (!this.showPieLegend()) {
581
+ return {
582
+ legendOrient: 'vertical',
583
+ legendLeft: undefined,
584
+ legendRight: undefined,
585
+ legendTop: undefined,
586
+ legendBottom: undefined,
587
+ legendWidth: undefined,
588
+ pieCenter: ['50%', '50%'],
589
+ graphicLeft: '50%',
590
+ graphicTop: '50%',
591
+ };
592
+ }
294
593
  if (this.isMobile) {
295
594
  return {
296
595
  legendOrient: 'horizontal',
@@ -351,6 +650,48 @@ class ChartComponent {
351
650
  ...this.options(),
352
651
  };
353
652
  }
653
+ categoryAxis(categories) {
654
+ return {
655
+ type: 'category',
656
+ data: categories,
657
+ axisLabel: {
658
+ fontFamily: this.resolvedFontFamily(),
659
+ fontSize: this.labelFontSize,
660
+ color: FxUtils.convertColorFromVariable('--text-secondary'),
661
+ },
662
+ axisLine: {
663
+ lineStyle: { color: FxUtils.convertColorFromVariable('--border-strong') },
664
+ },
665
+ };
666
+ }
667
+ valueAxis() {
668
+ return {
669
+ type: 'value',
670
+ axisLabel: {
671
+ fontFamily: this.resolvedFontFamily(),
672
+ fontSize: this.labelFontSize,
673
+ color: FxUtils.convertColorFromVariable('--text-secondary'),
674
+ },
675
+ splitLine: {
676
+ lineStyle: { color: FxUtils.convertColorFromVariable('--border-default') },
677
+ },
678
+ };
679
+ }
680
+ /** Canvas không ăn var() — resolve font stack thật tại render; chart đã
681
+ rebuild theo event theme-changed nên tự đổi font khi đổi theme
682
+ (blueprint/terminal dùng mono qua --og-font-figure). */
683
+ resolvedFontFamily() {
684
+ const override = this.labelFontFamily();
685
+ if (override)
686
+ return override;
687
+ // Dev-mode CSS pretty-printer bẻ dòng giá trị dài — normalize whitespace
688
+ // trước khi chuỗi này vào ctx.font của canvas.
689
+ const figure = getComputedStyle(document.body)
690
+ .getPropertyValue('--og-font-figure')
691
+ .replace(/\s+/g, ' ')
692
+ .trim();
693
+ return figure || 'Archivo, system-ui, sans-serif';
694
+ }
354
695
  resolvePalette() {
355
696
  const defaults = [
356
697
  'rgba(var(--success) / 1)',
@@ -374,12 +715,12 @@ class ChartComponent {
374
715
  });
375
716
  }
376
717
  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 });
377
- 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 }, 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 {\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: [":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}\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 });
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: [":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}\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 });
378
719
  }
379
720
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: ChartComponent, decorators: [{
380
721
  type: Component,
381
- 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 {\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: [":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}\n"] }]
382
- }], 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 }] }], 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: [{
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: [":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}\n"] }]
723
+ }], 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: [{
383
724
  type: HostListener,
384
725
  args: ['window:theme-changed']
385
726
  }] } });