@foxeltech/angular-ui 0.3.1 → 0.7.44

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 (193) 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 +12 -12
  14. package/dialogs.css +2 -2
  15. package/fesm2022/foxeltech-angular-ui-chart.mjs +560 -0
  16. package/fesm2022/foxeltech-angular-ui-chart.mjs.map +1 -0
  17. package/fesm2022/foxeltech-angular-ui-flow.mjs +211 -0
  18. package/fesm2022/foxeltech-angular-ui-flow.mjs.map +1 -0
  19. package/fesm2022/foxeltech-angular-ui-rich-text.mjs +156 -0
  20. package/fesm2022/foxeltech-angular-ui-rich-text.mjs.map +1 -0
  21. package/fesm2022/foxeltech-angular-ui.mjs +1580 -1409
  22. package/fesm2022/foxeltech-angular-ui.mjs.map +1 -1
  23. package/fonts.css +462 -138
  24. package/fx-icons.svg +97 -0
  25. package/ibm-plex-mono/ibm-plex-mono-400-cyrillic-ext.woff2 +0 -0
  26. package/ibm-plex-mono/ibm-plex-mono-400-cyrillic.woff2 +0 -0
  27. package/ibm-plex-mono/ibm-plex-mono-400-latin-ext.woff2 +0 -0
  28. package/ibm-plex-mono/ibm-plex-mono-400-latin.woff2 +0 -0
  29. package/ibm-plex-mono/ibm-plex-mono-400-vietnamese.woff2 +0 -0
  30. package/ibm-plex-mono/ibm-plex-mono-500-cyrillic-ext.woff2 +0 -0
  31. package/ibm-plex-mono/ibm-plex-mono-500-cyrillic.woff2 +0 -0
  32. package/ibm-plex-mono/ibm-plex-mono-500-latin-ext.woff2 +0 -0
  33. package/ibm-plex-mono/ibm-plex-mono-500-latin.woff2 +0 -0
  34. package/ibm-plex-mono/ibm-plex-mono-500-vietnamese.woff2 +0 -0
  35. package/ibm-plex-mono/ibm-plex-mono-600-cyrillic-ext.woff2 +0 -0
  36. package/ibm-plex-mono/ibm-plex-mono-600-cyrillic.woff2 +0 -0
  37. package/ibm-plex-mono/ibm-plex-mono-600-latin-ext.woff2 +0 -0
  38. package/ibm-plex-mono/ibm-plex-mono-600-latin.woff2 +0 -0
  39. package/ibm-plex-mono/ibm-plex-mono-600-vietnamese.woff2 +0 -0
  40. package/jetbrains-mono/jetbrains-mono-400-cyrillic-ext.woff2 +0 -0
  41. package/jetbrains-mono/jetbrains-mono-400-cyrillic.woff2 +0 -0
  42. package/jetbrains-mono/jetbrains-mono-400-greek.woff2 +0 -0
  43. package/jetbrains-mono/jetbrains-mono-400-latin-ext.woff2 +0 -0
  44. package/jetbrains-mono/jetbrains-mono-400-latin.woff2 +0 -0
  45. package/jetbrains-mono/jetbrains-mono-400-vietnamese.woff2 +0 -0
  46. package/jetbrains-mono/jetbrains-mono-500-cyrillic-ext.woff2 +0 -0
  47. package/jetbrains-mono/jetbrains-mono-500-cyrillic.woff2 +0 -0
  48. package/jetbrains-mono/jetbrains-mono-500-greek.woff2 +0 -0
  49. package/jetbrains-mono/jetbrains-mono-500-latin-ext.woff2 +0 -0
  50. package/jetbrains-mono/jetbrains-mono-500-latin.woff2 +0 -0
  51. package/jetbrains-mono/jetbrains-mono-500-vietnamese.woff2 +0 -0
  52. package/jetbrains-mono/jetbrains-mono-600-cyrillic-ext.woff2 +0 -0
  53. package/jetbrains-mono/jetbrains-mono-600-cyrillic.woff2 +0 -0
  54. package/jetbrains-mono/jetbrains-mono-600-greek.woff2 +0 -0
  55. package/jetbrains-mono/jetbrains-mono-600-latin-ext.woff2 +0 -0
  56. package/jetbrains-mono/jetbrains-mono-600-latin.woff2 +0 -0
  57. package/jetbrains-mono/jetbrains-mono-600-vietnamese.woff2 +0 -0
  58. package/jetbrains-mono/jetbrains-mono-700-cyrillic-ext.woff2 +0 -0
  59. package/jetbrains-mono/jetbrains-mono-700-cyrillic.woff2 +0 -0
  60. package/jetbrains-mono/jetbrains-mono-700-greek.woff2 +0 -0
  61. package/jetbrains-mono/jetbrains-mono-700-latin-ext.woff2 +0 -0
  62. package/jetbrains-mono/jetbrains-mono-700-latin.woff2 +0 -0
  63. package/jetbrains-mono/jetbrains-mono-700-vietnamese.woff2 +0 -0
  64. package/newsreader/newsreader-400-latin-ext.woff2 +0 -0
  65. package/newsreader/newsreader-400-latin.woff2 +0 -0
  66. package/newsreader/newsreader-400-vietnamese.woff2 +0 -0
  67. package/newsreader/newsreader-500-latin-ext.woff2 +0 -0
  68. package/newsreader/newsreader-500-latin.woff2 +0 -0
  69. package/newsreader/newsreader-500-vietnamese.woff2 +0 -0
  70. package/newsreader/newsreader-600-latin-ext.woff2 +0 -0
  71. package/newsreader/newsreader-600-latin.woff2 +0 -0
  72. package/newsreader/newsreader-600-vietnamese.woff2 +0 -0
  73. package/package.json +18 -9
  74. package/source-serif-4/source-serif-4-400-cyrillic-ext.woff2 +0 -0
  75. package/source-serif-4/source-serif-4-400-cyrillic.woff2 +0 -0
  76. package/source-serif-4/source-serif-4-400-greek.woff2 +0 -0
  77. package/source-serif-4/source-serif-4-400-latin-ext.woff2 +0 -0
  78. package/source-serif-4/source-serif-4-400-latin.woff2 +0 -0
  79. package/source-serif-4/source-serif-4-400-vietnamese.woff2 +0 -0
  80. package/source-serif-4/source-serif-4-600-cyrillic-ext.woff2 +0 -0
  81. package/source-serif-4/source-serif-4-600-cyrillic.woff2 +0 -0
  82. package/source-serif-4/source-serif-4-600-greek.woff2 +0 -0
  83. package/source-serif-4/source-serif-4-600-latin-ext.woff2 +0 -0
  84. package/source-serif-4/source-serif-4-600-latin.woff2 +0 -0
  85. package/source-serif-4/source-serif-4-600-vietnamese.woff2 +0 -0
  86. package/src/lib/styles/components.css +12 -12
  87. package/src/lib/styles/dialogs.css +2 -2
  88. package/src/lib/styles/fonts.css +462 -138
  89. package/src/lib/styles/tailwind.css +1 -2
  90. package/src/lib/styles/theme.css +596 -69
  91. package/src/lib/styles/tokens.css +56 -19
  92. package/src/lib/styles/utilities.css +4 -1
  93. package/src/lib/ui/components/bar-list/bar-list.component.html +29 -0
  94. package/src/lib/ui/components/button/button.component.html +20 -18
  95. package/src/lib/ui/components/checkbox/checkbox.component.html +3 -3
  96. package/src/lib/ui/components/circle-progress-bar/circle-progress-bar.component.html +2 -2
  97. package/src/lib/ui/components/code-block/code-block.component.html +8 -0
  98. package/src/lib/ui/components/datetime-picker/datetime-picker.component.html +49 -27
  99. package/src/lib/ui/components/datetime-picker/datetime-picker.component.scss +25 -137
  100. package/src/lib/ui/components/dnd-upload/dnd-upload.component.html +3 -3
  101. package/src/lib/ui/components/drawer/drawer.component.html +36 -13
  102. package/src/lib/ui/components/drawer/drawer.component.scss +104 -18
  103. package/src/lib/ui/components/empty-state/empty-state.component.html +2 -2
  104. package/src/lib/ui/components/facets-rail/facets-rail.component.html +42 -0
  105. package/src/lib/ui/components/filter-pills/filter-pills.component.html +16 -0
  106. package/src/lib/ui/components/hero-icon/hero-icon.component.html +10 -9
  107. package/src/lib/ui/components/info-field/info-field.component.html +1 -1
  108. package/src/lib/ui/components/input/input.component.html +6 -6
  109. package/src/lib/ui/components/kanban-board/kanban-board.component.html +1 -1
  110. package/src/lib/ui/components/kanban-board/kanban-board.component.scss +9 -9
  111. package/src/lib/ui/components/list-row/list-row.component.html +20 -0
  112. package/src/lib/ui/components/loading-panel/loading-panel.component.html +1 -1
  113. package/src/lib/ui/components/master-detail/master-detail.component.css +22 -0
  114. package/src/lib/ui/components/master-detail/master-detail.component.html +42 -10
  115. package/src/lib/ui/components/menu/menu.component.html +27 -0
  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 +4 -4
  120. package/src/lib/ui/components/radio-button-toggle/radio-button-toggle.component.html +4 -4
  121. package/src/lib/ui/components/sample-frame/sample-frame.component.html +18 -0
  122. package/src/lib/ui/components/score-badge/score-badge.component.html +7 -0
  123. package/src/lib/ui/components/screenshot-frame/screenshot-frame.component.html +40 -0
  124. package/src/lib/ui/components/search-bar/search-bar.component.html +1 -1
  125. package/src/lib/ui/components/section-card/section-card.component.html +19 -6
  126. package/src/lib/ui/components/segmented/segmented.component.html +42 -0
  127. package/src/lib/ui/components/segmented/segmented.component.scss +12 -0
  128. package/src/lib/ui/components/select/select.component.html +6 -6
  129. package/src/lib/ui/components/skeleton-detail/skeleton-detail.component.html +2 -2
  130. package/src/lib/ui/components/skeleton-list/skeleton-list.component.html +1 -1
  131. package/src/lib/ui/components/skeleton-table-loading/skeleton-table-loading.component.html +5 -5
  132. package/src/lib/ui/components/slider/slider.component.html +8 -8
  133. package/src/lib/ui/components/sparkbar/sparkbar.component.html +14 -0
  134. package/src/lib/ui/components/stage-dots/stage-dots.component.html +13 -0
  135. package/src/lib/ui/components/stat-cards/stat-cards.component.html +45 -12
  136. package/src/lib/ui/components/status-dot/status-dot.component.html +13 -0
  137. package/src/lib/ui/components/switch/switch.component.html +1 -1
  138. package/src/lib/ui/components/tab-group/tab-group.component.html +41 -12
  139. package/src/lib/ui/components/tab-group/tab-group.component.scss +41 -1
  140. package/src/lib/ui/components/table-cell/table-cell.component.html +2 -2
  141. package/src/lib/ui/components/tag/tag.component.html +4 -4
  142. package/src/lib/ui/components/theme-picker/theme-picker.component.html +44 -0
  143. package/src/lib/ui/components/toast/toast.component.html +7 -7
  144. package/src/lib/ui/components/toast-container/toast-container.component.html +1 -0
  145. package/src/lib/ui/components/tree-diagram/tree-diagram.component.html +5 -5
  146. package/tailwind.css +1 -2
  147. package/theme.css +596 -69
  148. package/tokens.css +56 -19
  149. package/types/foxeltech-angular-ui-chart.d.ts +65 -0
  150. package/types/foxeltech-angular-ui-flow.d.ts +96 -0
  151. package/types/foxeltech-angular-ui-rich-text.d.ts +35 -0
  152. package/types/foxeltech-angular-ui.d.ts +1018 -395
  153. package/utilities.css +4 -1
  154. package/BeVietnamPro/BeVietnamPro-Bold.woff2 +0 -0
  155. package/BeVietnamPro/BeVietnamPro-ExtraLight.woff2 +0 -0
  156. package/BeVietnamPro/BeVietnamPro-Light.woff2 +0 -0
  157. package/BeVietnamPro/BeVietnamPro-Medium.woff2 +0 -0
  158. package/BeVietnamPro/BeVietnamPro-Regular.woff2 +0 -0
  159. package/BeVietnamPro/BeVietnamPro-SemiBold.woff2 +0 -0
  160. package/BeVietnamPro/BeVietnamPro-Thin.woff2 +0 -0
  161. package/SpaceMono-Regular.woff2 +0 -0
  162. package/dm-sans/DMSans-Bold.woff2 +0 -0
  163. package/dm-sans/DMSans-Light.woff2 +0 -0
  164. package/dm-sans/DMSans-Medium.woff2 +0 -0
  165. package/dm-sans/DMSans-Regular.woff2 +0 -0
  166. package/dm-sans/DMSans-SemiBold.woff2 +0 -0
  167. package/dm-sans/DMSans-Thin.woff2 +0 -0
  168. package/orbitron/Orbitron-Bold.woff2 +0 -0
  169. package/orbitron/Orbitron-Medium.woff2 +0 -0
  170. package/orbitron/Orbitron-Regular.woff2 +0 -0
  171. package/orbitron/Orbitron-SemiBold.woff2 +0 -0
  172. package/roboto/Roboto-Black.woff2 +0 -0
  173. package/roboto/Roboto-BlackItalic.woff2 +0 -0
  174. package/roboto/Roboto-Bold.woff2 +0 -0
  175. package/roboto/Roboto-BoldCondensed.woff2 +0 -0
  176. package/roboto/Roboto-BoldCondensedItalic.woff2 +0 -0
  177. package/roboto/Roboto-BoldItalic.woff2 +0 -0
  178. package/roboto/Roboto-Condensed.woff2 +0 -0
  179. package/roboto/Roboto-CondensedItalic.woff2 +0 -0
  180. package/roboto/Roboto-Italic.woff2 +0 -0
  181. package/roboto/Roboto-Light.woff2 +0 -0
  182. package/roboto/Roboto-LightItalic.woff2 +0 -0
  183. package/roboto/Roboto-Medium.woff2 +0 -0
  184. package/roboto/Roboto-MediumItalic.woff2 +0 -0
  185. package/roboto/Roboto-Regular.woff2 +0 -0
  186. package/roboto/Roboto-Thin.woff2 +0 -0
  187. package/roboto/Roboto-ThinItalic.woff2 +0 -0
  188. package/src/lib/ui/components/chart/chart.component.html +0 -20
  189. package/src/lib/ui/components/chart/chart.component.scss +0 -49
  190. package/src/lib/ui/components/flow-connection/flow-connection.component.html +0 -10
  191. package/src/lib/ui/components/flow-connection/flow-connection.component.scss +0 -0
  192. package/src/lib/ui/components/rich-text-area/rich-text-area.component.html +0 -3
  193. package/src/lib/ui/components/rich-text-area/rich-text-area.component.scss +0 -18
@@ -0,0 +1,560 @@
1
+ import * as i0 from '@angular/core';
2
+ import { input, output, HostListener, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { FxUtils, InputComponent } from '@foxeltech/angular-ui';
4
+ import { NgxEchartsDirective, provideEchartsCore } from 'ngx-echarts';
5
+
6
+ class ChartComponent {
7
+ ref;
8
+ cdr;
9
+ /* ================= INPUTS ================= */
10
+ type = input('auto', ...(ngDevMode ? [{ debugName: "type" }] : /* istanbul ignore next */ []));
11
+ data = input(...(ngDevMode ? [undefined, { debugName: "data" }] : /* istanbul ignore next */ []));
12
+ options = input(...(ngDevMode ? [undefined, { debugName: "options" }] : /* istanbul ignore next */ []));
13
+ palette = input(...(ngDevMode ? [undefined, { debugName: "palette" }] : /* istanbul ignore next */ []));
14
+ height = input('400px', ...(ngDevMode ? [{ debugName: "height" }] : /* istanbul ignore next */ []));
15
+ loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
16
+ // pie controls
17
+ pieLegendAlign = input('10%', ...(ngDevMode ? [{ debugName: "pieLegendAlign" }] : /* istanbul ignore next */ []));
18
+ pieLegendPosition = input('middle', ...(ngDevMode ? [{ debugName: "pieLegendPosition" }] : /* istanbul ignore next */ []));
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 */ []));
25
+ mobileWidth = input(350, ...(ngDevMode ? [{ debugName: "mobileWidth" }] : /* 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 */ []));
28
+ emptyText = input('No data available', ...(ngDevMode ? [{ debugName: "emptyText" }] : /* istanbul ignore next */ []));
29
+ /* ================= OUTPUTS ================= */
30
+ chartClick = output();
31
+ chartInit = output();
32
+ /* ================= INTERNAL ================= */
33
+ chartOptions = {};
34
+ isEmpty = false;
35
+ labelFontSize = 12;
36
+ chartInstance;
37
+ resizeObserver;
38
+ intersectionObserver;
39
+ canInit = false;
40
+ isMobile = false;
41
+ containerWidth = 0;
42
+ constructor(ref, cdr) {
43
+ this.ref = ref;
44
+ this.cdr = cdr;
45
+ }
46
+ /* ================= LIFECYCLE ================= */
47
+ ngAfterViewInit() {
48
+ // Only initialize when visible (tab-safe)
49
+ this.intersectionObserver = new IntersectionObserver(([entry]) => {
50
+ if (entry.isIntersecting && !this.canInit) {
51
+ this.canInit = true;
52
+ this.updateResponsiveState();
53
+ this.rebuildChart();
54
+ this.cdr.detectChanges();
55
+ }
56
+ }, { threshold: 0.1 });
57
+ this.intersectionObserver.observe(this.ref.nativeElement);
58
+ // Resize-safe (tabs, flex, async layout)
59
+ this.resizeObserver = new ResizeObserver(() => {
60
+ if (!this.canInit)
61
+ return;
62
+ this.updateResponsiveState();
63
+ this.forceMediaRecalc();
64
+ });
65
+ this.resizeObserver.observe(this.ref.nativeElement);
66
+ }
67
+ ngOnChanges() {
68
+ if (this.canInit) {
69
+ this.updateResponsiveState();
70
+ this.rebuildChart();
71
+ }
72
+ }
73
+ ngOnDestroy() {
74
+ this.resizeObserver?.disconnect();
75
+ this.intersectionObserver?.disconnect();
76
+ }
77
+ onThemeChange() {
78
+ if (!this.canInit)
79
+ return;
80
+ requestAnimationFrame(() => this.rebuildChart());
81
+ }
82
+ /* ================= EVENTS ================= */
83
+ onChartClick(event) {
84
+ this.chartClick.emit(event);
85
+ }
86
+ onChartInit(chart) {
87
+ this.chartInstance = chart;
88
+ this.chartInit.emit(chart);
89
+ requestAnimationFrame(() => this.forceMediaRecalc());
90
+ }
91
+ /* ================= CORE FIX ================= */
92
+ updateResponsiveState() {
93
+ const { width } = this.ref.nativeElement.getBoundingClientRect();
94
+ if (width > 0) {
95
+ this.containerWidth = width;
96
+ this.isMobile = width < this.mobileWidth();
97
+ }
98
+ }
99
+ forceMediaRecalc(retry = 0) {
100
+ if (!this.chartInstance)
101
+ return;
102
+ const { width, height } = this.ref.nativeElement.getBoundingClientRect();
103
+ if ((width === 0 || height === 0) && retry < 10) {
104
+ requestAnimationFrame(() => this.forceMediaRecalc(retry + 1));
105
+ return;
106
+ }
107
+ this.rebuildChart();
108
+ this.chartInstance.resize();
109
+ }
110
+ rebuildChart() {
111
+ this.isEmpty = this.checkEmpty();
112
+ if (this.isEmpty) {
113
+ if (this.chartInstance) {
114
+ this.chartInstance.clear();
115
+ }
116
+ return;
117
+ }
118
+ this.chartOptions = this.buildOptions();
119
+ if (this.chartInstance) {
120
+ this.chartInstance.clear();
121
+ this.chartInstance.setOption(this.chartOptions, true);
122
+ }
123
+ }
124
+ checkEmpty() {
125
+ const type = this.detectType();
126
+ if (type === 'bar') {
127
+ const data = this.data();
128
+ const categories = data?.categories;
129
+ const values = data?.values;
130
+ return !categories?.length || !values?.length;
131
+ }
132
+ if (type === 'line') {
133
+ const data = this.data();
134
+ return !data?.categories?.length || !data?.values?.length;
135
+ }
136
+ if (type === 'area-trend') {
137
+ const data = this.data();
138
+ return !data?.categories?.length || !data?.current?.length;
139
+ }
140
+ if (type === 'pie') {
141
+ const data = this.data();
142
+ if (!Array.isArray(data) || data.length === 0)
143
+ return true;
144
+ const total = data.reduce((sum, d) => sum + (d.value || 0), 0);
145
+ return total === 0;
146
+ }
147
+ return false;
148
+ }
149
+ /* ================= REGISTRY ================= */
150
+ registry = {
151
+ bar: (data, colors) => ({
152
+ color: colors,
153
+ tooltip: { trigger: 'axis' },
154
+ grid: { left: '5%', right: '5%', bottom: '10%', containLabel: true },
155
+ xAxis: {
156
+ type: 'category',
157
+ data: data?.categories || [],
158
+ axisLabel: {
159
+ interval: 0,
160
+ width: 80,
161
+ overflow: 'truncate',
162
+ ellipsis: '…',
163
+ fontFamily: this.resolvedFontFamily(),
164
+ fontSize: this.labelFontSize,
165
+ color: FxUtils.convertColorFromVariable('--text-secondary'),
166
+ },
167
+ axisLine: {
168
+ lineStyle: {
169
+ color: FxUtils.convertColorFromVariable('--border-strong'),
170
+ },
171
+ },
172
+ },
173
+ yAxis: {
174
+ type: 'value',
175
+ axisLabel: {
176
+ fontFamily: this.resolvedFontFamily(),
177
+ fontSize: this.labelFontSize,
178
+ color: FxUtils.convertColorFromVariable('--text-secondary'),
179
+ },
180
+ axisLine: {
181
+ lineStyle: {
182
+ color: FxUtils.convertColorFromVariable('--border-strong'),
183
+ },
184
+ },
185
+ splitLine: {
186
+ lineStyle: {
187
+ color: FxUtils.convertColorFromVariable('--border-default'),
188
+ },
189
+ },
190
+ },
191
+ series: [
192
+ {
193
+ type: 'bar',
194
+ data: data?.values || [],
195
+ barMaxWidth: 70,
196
+ itemStyle: {
197
+ color: (p) => colors[p.dataIndex % colors.length],
198
+ },
199
+ label: {
200
+ show: true,
201
+ position: 'top',
202
+ fontFamily: this.resolvedFontFamily(),
203
+ fontSize: this.labelFontSize,
204
+ color: FxUtils.convertColorFromVariable('--text-primary'),
205
+ },
206
+ },
207
+ ],
208
+ }),
209
+ pie: (data, colors) => {
210
+ const total = data.reduce((sum, d) => sum + d.value, 0);
211
+ const layout = this.calcPieLayout(data);
212
+ return {
213
+ color: colors,
214
+ tooltip: { trigger: 'item' },
215
+ legend: {
216
+ show: this.showPieLegend(),
217
+ type: 'scroll',
218
+ orient: layout.legendOrient,
219
+ icon: 'circle',
220
+ itemGap: 12,
221
+ right: layout.legendRight,
222
+ top: layout.legendTop,
223
+ left: layout.legendLeft,
224
+ bottom: layout.legendBottom,
225
+ width: layout.legendWidth,
226
+ formatter: (name) => {
227
+ const item = data.find((d) => d.name === name);
228
+ return item ? `${name}: ${item.value}` : name;
229
+ },
230
+ textStyle: {
231
+ fontFamily: this.resolvedFontFamily(),
232
+ fontSize: 13,
233
+ fontWeight: 500,
234
+ color: FxUtils.convertColorFromVariable('--text-primary'),
235
+ overflow: 'truncate',
236
+ },
237
+ },
238
+ series: [
239
+ {
240
+ type: 'pie',
241
+ radius: ['40%', '70%'],
242
+ center: layout.pieCenter,
243
+ data,
244
+ label: {
245
+ show: this.showPieLabel(),
246
+ position: 'outside',
247
+ formatter: '{d}%',
248
+ fontSize: this.labelFontSize,
249
+ fontFamily: this.resolvedFontFamily(),
250
+ color: FxUtils.convertColorFromVariable('--text-primary'),
251
+ textBorderWidth: 0,
252
+ textBorderColor: 'transparent',
253
+ },
254
+ labelLine: {
255
+ show: this.showPieLabel(),
256
+ length: 7,
257
+ length2: 7,
258
+ },
259
+ },
260
+ ],
261
+ // Centre figure: positioned by x/y in pixels — graphic left/top
262
+ // percentages place the element's bounding-box EDGE, which shifted the
263
+ // total right/down by half its own size (mis-centred, caption clipped).
264
+ graphic: (() => {
265
+ const cx = (this.containerWidth || 400) * (parseFloat(layout.pieCenter[0]) / 100);
266
+ const ch = this.ref.nativeElement.clientHeight || 300;
267
+ const cy = ch * (parseFloat(layout.pieCenter[1]) / 100);
268
+ return [
269
+ {
270
+ type: 'text',
271
+ // Shift the total up when a caption sits under it.
272
+ x: cx,
273
+ y: cy + (this.pieCenterLabel() ? -9 : 0),
274
+ style: {
275
+ text: total.toString(),
276
+ fontSize: 24,
277
+ fontWeight: 600,
278
+ fontFamily: this.resolvedFontFamily(),
279
+ fill: FxUtils.convertColorFromVariable('--text-primary'),
280
+ align: 'center',
281
+ verticalAlign: 'middle',
282
+ },
283
+ },
284
+ ...(this.pieCenterLabel()
285
+ ? [
286
+ {
287
+ type: 'text',
288
+ x: cx,
289
+ y: cy + 12,
290
+ style: {
291
+ text: this.pieCenterLabel(),
292
+ fontSize: 11,
293
+ fontFamily: this.resolvedFontFamily(),
294
+ fill: FxUtils.convertColorFromVariable('--text-secondary'),
295
+ align: 'center',
296
+ verticalAlign: 'middle',
297
+ },
298
+ },
299
+ ]
300
+ : []),
301
+ ];
302
+ })(),
303
+ };
304
+ },
305
+ // V2 preset: single line, accent-coloured — "risk score by release".
306
+ line: (data) => ({
307
+ tooltip: { trigger: 'axis' },
308
+ grid: { left: '3%', right: '4%', bottom: '8%', top: '8%', containLabel: true },
309
+ xAxis: this.categoryAxis(data?.categories || []),
310
+ yAxis: this.valueAxis(),
311
+ series: [
312
+ {
313
+ type: 'line',
314
+ data: data?.values || [],
315
+ smooth: false,
316
+ symbol: 'circle',
317
+ symbolSize: 7,
318
+ lineStyle: { width: 2, color: FxUtils.convertColorFromVariable('--og-accent', 1, { legacyCommaSyntax: true }) },
319
+ itemStyle: { color: FxUtils.convertColorFromVariable('--og-accent', 1, { legacyCommaSyntax: true }) },
320
+ label: {
321
+ show: true,
322
+ position: 'top',
323
+ fontFamily: this.resolvedFontFamily(),
324
+ fontSize: this.labelFontSize,
325
+ color: FxUtils.convertColorFromVariable('--text-primary'),
326
+ },
327
+ },
328
+ ],
329
+ }),
330
+ // V2 preset: weekly trend — accent gradient fill + gray comparison line.
331
+ // data: { categories: string[], current: number[], previous?: number[] }
332
+ 'area-trend': (data) => {
333
+ // legacyCommaSyntax: zrender's colour parser chokes on CSS4 space syntax.
334
+ const accent = FxUtils.convertColorFromVariable('--og-accent', 1, { legacyCommaSyntax: true });
335
+ const accentSoft = FxUtils.convertColorFromVariable('--og-accent', 0.25, { legacyCommaSyntax: true });
336
+ const accentFade = FxUtils.convertColorFromVariable('--og-accent', 0.02, { legacyCommaSyntax: true });
337
+ const series = [
338
+ {
339
+ type: 'line',
340
+ name: data?.currentLabel || 'Current',
341
+ data: data?.current || [],
342
+ smooth: true,
343
+ symbol: 'none',
344
+ lineStyle: { width: 2, color: accent },
345
+ itemStyle: { color: accent },
346
+ areaStyle: {
347
+ color: {
348
+ type: 'linear',
349
+ x: 0, y: 0, x2: 0, y2: 1,
350
+ colorStops: [
351
+ { offset: 0, color: accentSoft },
352
+ { offset: 1, color: accentFade },
353
+ ],
354
+ },
355
+ },
356
+ },
357
+ ];
358
+ if (data?.previous?.length) {
359
+ series.push({
360
+ type: 'line',
361
+ name: data?.previousLabel || 'Previous',
362
+ data: data.previous,
363
+ smooth: true,
364
+ symbol: 'none',
365
+ lineStyle: {
366
+ width: 1.5,
367
+ type: 'dashed',
368
+ color: FxUtils.convertColorFromVariable('--text-placeholder', 1, { legacyCommaSyntax: true }),
369
+ },
370
+ itemStyle: { color: FxUtils.convertColorFromVariable('--text-placeholder', 1, { legacyCommaSyntax: true }) },
371
+ });
372
+ }
373
+ return {
374
+ tooltip: { trigger: 'axis' },
375
+ grid: { left: '3%', right: '4%', bottom: '8%', top: '10%', containLabel: true },
376
+ xAxis: { ...this.categoryAxis(data?.categories || []), boundaryGap: false },
377
+ yAxis: this.valueAxis(),
378
+ series,
379
+ };
380
+ },
381
+ sunburst: (data, colors) => ({
382
+ color: colors,
383
+ series: [
384
+ {
385
+ type: 'sunburst',
386
+ data,
387
+ radius: ['15%', '90%'],
388
+ itemStyle: {
389
+ borderWidth: 1,
390
+ borderColor: FxUtils.convertColorFromVariable('--border-default'),
391
+ },
392
+ label: {
393
+ rotate: 'tangential',
394
+ fontSize: this.labelFontSize,
395
+ fontFamily: this.resolvedFontFamily(),
396
+ color: FxUtils.convertColorFromVariable('--text-primary'),
397
+ textBorderWidth: 0,
398
+ textBorderColor: 'transparent',
399
+ },
400
+ },
401
+ ],
402
+ }),
403
+ };
404
+ /* ================= PIE LAYOUT ================= */
405
+ calcPieLayout(data) {
406
+ const cw = this.containerWidth || 400;
407
+ if (!this.showPieLegend()) {
408
+ return {
409
+ legendOrient: 'vertical',
410
+ legendLeft: undefined,
411
+ legendRight: undefined,
412
+ legendTop: undefined,
413
+ legendBottom: undefined,
414
+ legendWidth: undefined,
415
+ pieCenter: ['50%', '50%'],
416
+ graphicLeft: '50%',
417
+ graphicTop: '50%',
418
+ };
419
+ }
420
+ if (this.isMobile) {
421
+ return {
422
+ legendOrient: 'horizontal',
423
+ legendLeft: 'center',
424
+ legendRight: undefined,
425
+ legendTop: undefined,
426
+ legendBottom: 0,
427
+ legendWidth: undefined,
428
+ pieCenter: ['50%', '40%'],
429
+ graphicLeft: '50%',
430
+ graphicTop: '40%',
431
+ };
432
+ }
433
+ // Estimate legend pixel width from longest formatted label
434
+ const CHAR_WIDTH = 7.5; // approx px per char at fontSize 13
435
+ const ICON_GAP = 30; // legend icon + padding
436
+ const longestLabel = data.reduce((max, d) => {
437
+ return Math.max(max, `${d.name}: ${d.value}`.length);
438
+ }, 0);
439
+ const rawLegendPx = longestLabel * CHAR_WIDTH + ICON_GAP;
440
+ // Clamp: min 100px, max 45% of container
441
+ const legendPx = Math.max(100, Math.min(rawLegendPx, cw * 0.45));
442
+ const legendPct = (legendPx / cw) * 100;
443
+ // Pie center: middle of the remaining space after legend
444
+ const pieCenterX = Math.round((100 - legendPct) / 2);
445
+ return {
446
+ legendOrient: 'vertical',
447
+ legendLeft: undefined,
448
+ legendRight: '2%',
449
+ legendTop: 'middle',
450
+ legendBottom: undefined,
451
+ legendWidth: Math.round(legendPx),
452
+ pieCenter: [`${pieCenterX}%`, '50%'],
453
+ graphicLeft: `${pieCenterX}%`,
454
+ graphicTop: '50%',
455
+ };
456
+ }
457
+ /* ================= BUILD ================= */
458
+ detectType() {
459
+ const type = this.type();
460
+ if (type !== 'auto')
461
+ return type;
462
+ const data = this.data();
463
+ if (data?.children)
464
+ return 'sunburst';
465
+ if (data?.categories && data?.values)
466
+ return 'bar';
467
+ if (Array.isArray(data))
468
+ return 'pie';
469
+ return 'bar';
470
+ }
471
+ buildOptions() {
472
+ const type = this.detectType();
473
+ const palette = this.resolvePalette();
474
+ const base = this.registry[type]?.(this.data(), palette) || {};
475
+ return {
476
+ ...base,
477
+ ...this.options(),
478
+ };
479
+ }
480
+ categoryAxis(categories) {
481
+ return {
482
+ type: 'category',
483
+ data: categories,
484
+ axisLabel: {
485
+ fontFamily: this.resolvedFontFamily(),
486
+ fontSize: this.labelFontSize,
487
+ color: FxUtils.convertColorFromVariable('--text-secondary'),
488
+ },
489
+ axisLine: {
490
+ lineStyle: { color: FxUtils.convertColorFromVariable('--border-strong') },
491
+ },
492
+ };
493
+ }
494
+ valueAxis() {
495
+ return {
496
+ type: 'value',
497
+ axisLabel: {
498
+ fontFamily: this.resolvedFontFamily(),
499
+ fontSize: this.labelFontSize,
500
+ color: FxUtils.convertColorFromVariable('--text-secondary'),
501
+ },
502
+ splitLine: {
503
+ lineStyle: { color: FxUtils.convertColorFromVariable('--border-default') },
504
+ },
505
+ };
506
+ }
507
+ /** Canvas không ăn var() — resolve font stack thật tại render; chart đã
508
+ rebuild theo event theme-changed nên tự đổi font khi đổi theme
509
+ (blueprint/terminal dùng mono qua --og-font-figure). */
510
+ resolvedFontFamily() {
511
+ const override = this.labelFontFamily();
512
+ if (override)
513
+ return override;
514
+ // Dev-mode CSS pretty-printer bẻ dòng giá trị dài — normalize whitespace
515
+ // trước khi chuỗi này vào ctx.font của canvas.
516
+ const figure = getComputedStyle(document.body)
517
+ .getPropertyValue('--og-font-figure')
518
+ .replace(/\s+/g, ' ')
519
+ .trim();
520
+ return figure || 'Archivo, system-ui, sans-serif';
521
+ }
522
+ resolvePalette() {
523
+ const defaults = [
524
+ 'rgba(var(--success) / 1)',
525
+ 'rgba(var(--information) / 1)',
526
+ 'rgba(var(--critical) / 1)',
527
+ 'rgba(var(--danger) / 1)',
528
+ 'rgba(var(--warning) / 1)',
529
+ 'rgba(var(--discovery) / 1)',
530
+ ];
531
+ const paletteValue = this.palette();
532
+ const palette = paletteValue?.length ? paletteValue : defaults;
533
+ return palette.map((raw) => {
534
+ if (!raw.includes('var('))
535
+ return raw;
536
+ const cssVar = raw.match(/var\((.*?)\)/)?.[1];
537
+ const rgb = getComputedStyle(document.body)
538
+ .getPropertyValue(cssVar || '')
539
+ .trim()
540
+ .replace(/\s+/g, ',');
541
+ return `rgba(${rgb},1)`;
542
+ });
543
+ }
544
+ 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 });
545
+ 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 {\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 });
546
+ }
547
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: ChartComponent, decorators: [{
548
+ type: Component,
549
+ 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"] }]
550
+ }], 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: [{
551
+ type: HostListener,
552
+ args: ['window:theme-changed']
553
+ }] } });
554
+
555
+ /**
556
+ * Generated bundle index. Do not edit.
557
+ */
558
+
559
+ export { ChartComponent };
560
+ //# sourceMappingURL=foxeltech-angular-ui-chart.mjs.map