@kupola/kupola 1.2.0

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 (354) hide show
  1. package/CHANGELOG.md +130 -0
  2. package/INTEGRATION.md +440 -0
  3. package/PROJECT_SUMMARY.md +312 -0
  4. package/README.md +286 -0
  5. package/SKILL.md +572 -0
  6. package/css/accessibility.css +119 -0
  7. package/css/animations.css +224 -0
  8. package/css/brand-themes.css +300 -0
  9. package/css/colors_and_type.css +441 -0
  10. package/css/components-ext.css +4165 -0
  11. package/css/components.css +1483 -0
  12. package/css/kupola.css +12 -0
  13. package/css/responsive.css +697 -0
  14. package/css/scaffold.css +145 -0
  15. package/css/states.css +316 -0
  16. package/css/theme-dark.css +296 -0
  17. package/css/theme-light.css +296 -0
  18. package/css/utilities.css +171 -0
  19. package/dist/css/kupola.css +12 -0
  20. package/dist/icons/icons/activity.svg +3 -0
  21. package/dist/icons/icons/alert-circle.svg +1 -0
  22. package/dist/icons/icons/alert-triangle.svg +1 -0
  23. package/dist/icons/icons/alert.d27e24.svg +1 -0
  24. package/dist/icons/icons/alert.svg +1 -0
  25. package/dist/icons/icons/apple.svg +1 -0
  26. package/dist/icons/icons/arrow-down.svg +1 -0
  27. package/dist/icons/icons/arrow-expand.svg +1 -0
  28. package/dist/icons/icons/arrow-left.svg +1 -0
  29. package/dist/icons/icons/arrow-minimize.svg +1 -0
  30. package/dist/icons/icons/arrow-right-to-line.svg +1 -0
  31. package/dist/icons/icons/arrow-right.svg +1 -0
  32. package/dist/icons/icons/arrow-up-right.svg +1 -0
  33. package/dist/icons/icons/arrow-up.0c0c0d.svg +1 -0
  34. package/dist/icons/icons/arrow-up.svg +1 -0
  35. package/dist/icons/icons/at.svg +1 -0
  36. package/dist/icons/icons/atom.0c0c0d.svg +1 -0
  37. package/dist/icons/icons/atom.svg +1 -0
  38. package/dist/icons/icons/bar-chart-2.svg +1 -0
  39. package/dist/icons/icons/bar.svg +1 -0
  40. package/dist/icons/icons/bell.svg +1 -0
  41. package/dist/icons/icons/box.svg +1 -0
  42. package/dist/icons/icons/bug.svg +1 -0
  43. package/dist/icons/icons/button.svg +1 -0
  44. package/dist/icons/icons/calendar.svg +1 -0
  45. package/dist/icons/icons/check-circle.33c192.svg +1 -0
  46. package/dist/icons/icons/check-circle.svg +1 -0
  47. package/dist/icons/icons/check-square.svg +4 -0
  48. package/dist/icons/icons/check.d1d3db.svg +1 -0
  49. package/dist/icons/icons/check.svg +1 -0
  50. package/dist/icons/icons/chevron-down.svg +1 -0
  51. package/dist/icons/icons/chevron-left.svg +1 -0
  52. package/dist/icons/icons/chevron-right.svg +1 -0
  53. package/dist/icons/icons/chevron-up.svg +1 -0
  54. package/dist/icons/icons/circle.svg +1 -0
  55. package/dist/icons/icons/clock.svg +1 -0
  56. package/dist/icons/icons/cmd.svg +1 -0
  57. package/dist/icons/icons/code.svg +1 -0
  58. package/dist/icons/icons/columns.svg +1 -0
  59. package/dist/icons/icons/copy.svg +1 -0
  60. package/dist/icons/icons/cpu.svg +1 -0
  61. package/dist/icons/icons/dashboard.0c0c0d.svg +1 -0
  62. package/dist/icons/icons/dashboard.svg +1 -0
  63. package/dist/icons/icons/dollar.svg +1 -0
  64. package/dist/icons/icons/download.svg +1 -0
  65. package/dist/icons/icons/edit.svg +1 -0
  66. package/dist/icons/icons/extensions.svg +1 -0
  67. package/dist/icons/icons/external.svg +1 -0
  68. package/dist/icons/icons/eye.svg +1 -0
  69. package/dist/icons/icons/file-text.svg +1 -0
  70. package/dist/icons/icons/file.svg +1 -0
  71. package/dist/icons/icons/files.svg +1 -0
  72. package/dist/icons/icons/filter.svg +1 -0
  73. package/dist/icons/icons/folder-open.svg +4 -0
  74. package/dist/icons/icons/folder.svg +1 -0
  75. package/dist/icons/icons/gear.svg +1 -0
  76. package/dist/icons/icons/git.svg +1 -0
  77. package/dist/icons/icons/github.svg +1 -0
  78. package/dist/icons/icons/globe.svg +1 -0
  79. package/dist/icons/icons/grid-2x2.svg +1 -0
  80. package/dist/icons/icons/hash.svg +1 -0
  81. package/dist/icons/icons/heart.svg +1 -0
  82. package/dist/icons/icons/help-circle.svg +5 -0
  83. package/dist/icons/icons/help.svg +1 -0
  84. package/dist/icons/icons/home.svg +1 -0
  85. package/dist/icons/icons/icons.svg +153 -0
  86. package/dist/icons/icons/image.svg +1 -0
  87. package/dist/icons/icons/info-circle.svg +1 -0
  88. package/dist/icons/icons/info-square.svg +1 -0
  89. package/dist/icons/icons/info.387bff.svg +1 -0
  90. package/dist/icons/icons/info.svg +1 -0
  91. package/dist/icons/icons/input.svg +1 -0
  92. package/dist/icons/icons/key.svg +1 -0
  93. package/dist/icons/icons/layers.svg +1 -0
  94. package/dist/icons/icons/layout-grid.svg +6 -0
  95. package/dist/icons/icons/layout.svg +1 -0
  96. package/dist/icons/icons/link.svg +1 -0
  97. package/dist/icons/icons/list.svg +1 -0
  98. package/dist/icons/icons/lock.svg +1 -0
  99. package/dist/icons/icons/log-out.svg +1 -0
  100. package/dist/icons/icons/logo.svg +1 -0
  101. package/dist/icons/icons/mail.svg +1 -0
  102. package/dist/icons/icons/menu.svg +1 -0
  103. package/dist/icons/icons/message-circle.svg +1 -0
  104. package/dist/icons/icons/message-plus.svg +1 -0
  105. package/dist/icons/icons/message-square.svg +1 -0
  106. package/dist/icons/icons/mic.svg +1 -0
  107. package/dist/icons/icons/minus.svg +1 -0
  108. package/dist/icons/icons/moon.svg +1 -0
  109. package/dist/icons/icons/more-h.svg +1 -0
  110. package/dist/icons/icons/mouse.svg +1 -0
  111. package/dist/icons/icons/palette.svg +1 -0
  112. package/dist/icons/icons/panel-bottom.svg +1 -0
  113. package/dist/icons/icons/pause.svg +1 -0
  114. package/dist/icons/icons/play.svg +1 -0
  115. package/dist/icons/icons/plug.svg +1 -0
  116. package/dist/icons/icons/plus.0c0c0d.svg +1 -0
  117. package/dist/icons/icons/plus.svg +1 -0
  118. package/dist/icons/icons/refresh.svg +1 -0
  119. package/dist/icons/icons/ruler.svg +1 -0
  120. package/dist/icons/icons/scroll-text.svg +1 -0
  121. package/dist/icons/icons/search-menu.svg +1 -0
  122. package/dist/icons/icons/search.svg +1 -0
  123. package/dist/icons/icons/send.0c0c0d.svg +1 -0
  124. package/dist/icons/icons/send.svg +1 -0
  125. package/dist/icons/icons/settings.0c0c0d.svg +1 -0
  126. package/dist/icons/icons/settings.svg +1 -0
  127. package/dist/icons/icons/shield.svg +1 -0
  128. package/dist/icons/icons/sidebar-left.svg +1 -0
  129. package/dist/icons/icons/sidebar-right.svg +1 -0
  130. package/dist/icons/icons/sliders.svg +1 -0
  131. package/dist/icons/icons/sparkles.32f08c.svg +1 -0
  132. package/dist/icons/icons/sparkles.svg +1 -0
  133. package/dist/icons/icons/square.svg +1 -0
  134. package/dist/icons/icons/star.svg +1 -0
  135. package/dist/icons/icons/sun.svg +1 -0
  136. package/dist/icons/icons/table.svg +1 -0
  137. package/dist/icons/icons/terminal.svg +1 -0
  138. package/dist/icons/icons/trash.svg +1 -0
  139. package/dist/icons/icons/trending-down.svg +1 -0
  140. package/dist/icons/icons/trending-up.svg +1 -0
  141. package/dist/icons/icons/type.svg +1 -0
  142. package/dist/icons/icons/upload.svg +1 -0
  143. package/dist/icons/icons/user-circle.svg +1 -0
  144. package/dist/icons/icons/user-plus.svg +6 -0
  145. package/dist/icons/icons/user.svg +1 -0
  146. package/dist/icons/icons/users.svg +1 -0
  147. package/dist/icons/icons/warning.svg +1 -0
  148. package/dist/icons/icons/wrench.svg +1 -0
  149. package/dist/icons/icons/x-circle.svg +1 -0
  150. package/dist/icons/icons/x.svg +1 -0
  151. package/dist/icons/icons/zap.0c0c0d.svg +1 -0
  152. package/dist/icons/icons/zap.svg +1 -0
  153. package/dist/kupola.cjs.js +258 -0
  154. package/dist/kupola.cjs.js.map +1 -0
  155. package/dist/kupola.esm.js +7640 -0
  156. package/dist/kupola.esm.js.map +1 -0
  157. package/dist/kupola.umd.js +258 -0
  158. package/dist/kupola.umd.js.map +1 -0
  159. package/dist/types/kupola.d.ts +1359 -0
  160. package/dist/utils/utils/Kupola.cs +77 -0
  161. package/dist/utils/utils/Kupola.java +104 -0
  162. package/dist/utils/utils/kupola.go +120 -0
  163. package/dist/utils/utils/kupola.js +63 -0
  164. package/dist/utils/utils/kupola.py +1392 -0
  165. package/dist/utils/utils/kupola.rb +69 -0
  166. package/icons/activity.svg +3 -0
  167. package/icons/alert-circle.svg +1 -0
  168. package/icons/alert-triangle.svg +1 -0
  169. package/icons/alert.d27e24.svg +1 -0
  170. package/icons/alert.svg +1 -0
  171. package/icons/apple.svg +1 -0
  172. package/icons/arrow-down.svg +1 -0
  173. package/icons/arrow-expand.svg +1 -0
  174. package/icons/arrow-left.svg +1 -0
  175. package/icons/arrow-minimize.svg +1 -0
  176. package/icons/arrow-right-to-line.svg +1 -0
  177. package/icons/arrow-right.svg +1 -0
  178. package/icons/arrow-up-right.svg +1 -0
  179. package/icons/arrow-up.0c0c0d.svg +1 -0
  180. package/icons/arrow-up.svg +1 -0
  181. package/icons/at.svg +1 -0
  182. package/icons/atom.0c0c0d.svg +1 -0
  183. package/icons/atom.svg +1 -0
  184. package/icons/bar-chart-2.svg +1 -0
  185. package/icons/bar.svg +1 -0
  186. package/icons/bell.svg +1 -0
  187. package/icons/box.svg +1 -0
  188. package/icons/bug.svg +1 -0
  189. package/icons/button.svg +1 -0
  190. package/icons/calendar.svg +1 -0
  191. package/icons/check-circle.33c192.svg +1 -0
  192. package/icons/check-circle.svg +1 -0
  193. package/icons/check-square.svg +4 -0
  194. package/icons/check.d1d3db.svg +1 -0
  195. package/icons/check.svg +1 -0
  196. package/icons/chevron-down.svg +1 -0
  197. package/icons/chevron-left.svg +1 -0
  198. package/icons/chevron-right.svg +1 -0
  199. package/icons/chevron-up.svg +1 -0
  200. package/icons/circle.svg +1 -0
  201. package/icons/clock.svg +1 -0
  202. package/icons/cmd.svg +1 -0
  203. package/icons/code.svg +1 -0
  204. package/icons/columns.svg +1 -0
  205. package/icons/copy.svg +1 -0
  206. package/icons/cpu.svg +1 -0
  207. package/icons/dashboard.0c0c0d.svg +1 -0
  208. package/icons/dashboard.svg +1 -0
  209. package/icons/dollar.svg +1 -0
  210. package/icons/download.svg +1 -0
  211. package/icons/edit.svg +1 -0
  212. package/icons/extensions.svg +1 -0
  213. package/icons/external.svg +1 -0
  214. package/icons/eye.svg +1 -0
  215. package/icons/file-text.svg +1 -0
  216. package/icons/file.svg +1 -0
  217. package/icons/files.svg +1 -0
  218. package/icons/filter.svg +1 -0
  219. package/icons/folder-open.svg +4 -0
  220. package/icons/folder.svg +1 -0
  221. package/icons/gear.svg +1 -0
  222. package/icons/git.svg +1 -0
  223. package/icons/github.svg +1 -0
  224. package/icons/globe.svg +1 -0
  225. package/icons/grid-2x2.svg +1 -0
  226. package/icons/hash.svg +1 -0
  227. package/icons/heart.svg +1 -0
  228. package/icons/help-circle.svg +5 -0
  229. package/icons/help.svg +1 -0
  230. package/icons/home.svg +1 -0
  231. package/icons/icons.svg +153 -0
  232. package/icons/image.svg +1 -0
  233. package/icons/info-circle.svg +1 -0
  234. package/icons/info-square.svg +1 -0
  235. package/icons/info.387bff.svg +1 -0
  236. package/icons/info.svg +1 -0
  237. package/icons/input.svg +1 -0
  238. package/icons/key.svg +1 -0
  239. package/icons/layers.svg +1 -0
  240. package/icons/layout-grid.svg +6 -0
  241. package/icons/layout.svg +1 -0
  242. package/icons/link.svg +1 -0
  243. package/icons/list.svg +1 -0
  244. package/icons/lock.svg +1 -0
  245. package/icons/log-out.svg +1 -0
  246. package/icons/logo.svg +1 -0
  247. package/icons/mail.svg +1 -0
  248. package/icons/menu.svg +1 -0
  249. package/icons/message-circle.svg +1 -0
  250. package/icons/message-plus.svg +1 -0
  251. package/icons/message-square.svg +1 -0
  252. package/icons/mic.svg +1 -0
  253. package/icons/minus.svg +1 -0
  254. package/icons/moon.svg +1 -0
  255. package/icons/more-h.svg +1 -0
  256. package/icons/mouse.svg +1 -0
  257. package/icons/palette.svg +1 -0
  258. package/icons/panel-bottom.svg +1 -0
  259. package/icons/pause.svg +1 -0
  260. package/icons/play.svg +1 -0
  261. package/icons/plug.svg +1 -0
  262. package/icons/plus.0c0c0d.svg +1 -0
  263. package/icons/plus.svg +1 -0
  264. package/icons/refresh.svg +1 -0
  265. package/icons/ruler.svg +1 -0
  266. package/icons/scroll-text.svg +1 -0
  267. package/icons/search-menu.svg +1 -0
  268. package/icons/search.svg +1 -0
  269. package/icons/send.0c0c0d.svg +1 -0
  270. package/icons/send.svg +1 -0
  271. package/icons/settings.0c0c0d.svg +1 -0
  272. package/icons/settings.svg +1 -0
  273. package/icons/shield.svg +1 -0
  274. package/icons/sidebar-left.svg +1 -0
  275. package/icons/sidebar-right.svg +1 -0
  276. package/icons/sliders.svg +1 -0
  277. package/icons/sparkles.32f08c.svg +1 -0
  278. package/icons/sparkles.svg +1 -0
  279. package/icons/square.svg +1 -0
  280. package/icons/star.svg +1 -0
  281. package/icons/sun.svg +1 -0
  282. package/icons/table.svg +1 -0
  283. package/icons/terminal.svg +1 -0
  284. package/icons/trash.svg +1 -0
  285. package/icons/trending-down.svg +1 -0
  286. package/icons/trending-up.svg +1 -0
  287. package/icons/type.svg +1 -0
  288. package/icons/upload.svg +1 -0
  289. package/icons/user-circle.svg +1 -0
  290. package/icons/user-plus.svg +6 -0
  291. package/icons/user.svg +1 -0
  292. package/icons/users.svg +1 -0
  293. package/icons/warning.svg +1 -0
  294. package/icons/wrench.svg +1 -0
  295. package/icons/x-circle.svg +1 -0
  296. package/icons/x.svg +1 -0
  297. package/icons/zap.0c0c0d.svg +1 -0
  298. package/icons/zap.svg +1 -0
  299. package/js/calendar.js +184 -0
  300. package/js/carousel.js +173 -0
  301. package/js/collapse.js +127 -0
  302. package/js/color-picker.js +144 -0
  303. package/js/component.js +276 -0
  304. package/js/composition-api.js +458 -0
  305. package/js/countdown.js +144 -0
  306. package/js/data-bind.js +1286 -0
  307. package/js/datepicker.js +270 -0
  308. package/js/dialog.js +131 -0
  309. package/js/drawer.js +118 -0
  310. package/js/dropdown.js +122 -0
  311. package/js/dynamic-tags.js +156 -0
  312. package/js/error-handler.js +181 -0
  313. package/js/fileupload.js +392 -0
  314. package/js/form.js +291 -0
  315. package/js/global-events.js +203 -0
  316. package/js/heatmap.js +405 -0
  317. package/js/http.js +419 -0
  318. package/js/i18n.js +228 -0
  319. package/js/icons.js +165 -0
  320. package/js/image-preview.js +218 -0
  321. package/js/initializer.js +162 -0
  322. package/js/kupola-core.js +66 -0
  323. package/js/kupola-devtools.js +598 -0
  324. package/js/kupola-lifecycle.js +510 -0
  325. package/js/message.js +60 -0
  326. package/js/modal.js +191 -0
  327. package/js/notification.js +73 -0
  328. package/js/numberinput.js +146 -0
  329. package/js/performance.js +250 -0
  330. package/js/registry.js +205 -0
  331. package/js/router.js +396 -0
  332. package/js/security.js +189 -0
  333. package/js/select.js +143 -0
  334. package/js/slide-captcha.js +504 -0
  335. package/js/slider.js +94 -0
  336. package/js/statcard.js +219 -0
  337. package/js/tag.js +83 -0
  338. package/js/test-utils.js +251 -0
  339. package/js/theme.js +363 -0
  340. package/js/timepicker.js +238 -0
  341. package/js/tooltip.js +122 -0
  342. package/js/utils.js +1458 -0
  343. package/js/validation.js +553 -0
  344. package/js/virtual-list.js +593 -0
  345. package/package.json +105 -0
  346. package/templates/base.html +30 -0
  347. package/templates/base_dashboard.html +99 -0
  348. package/types/kupola.d.ts +1359 -0
  349. package/utils/Kupola.cs +77 -0
  350. package/utils/Kupola.java +104 -0
  351. package/utils/kupola.go +120 -0
  352. package/utils/kupola.js +63 -0
  353. package/utils/kupola.py +1392 -0
  354. package/utils/kupola.rb +69 -0
package/js/statcard.js ADDED
@@ -0,0 +1,219 @@
1
+ class StatCard {
2
+ constructor(element) {
3
+ this.element = element;
4
+ this.valueElement = element.querySelector('.ds-statcard__value');
5
+ this.progressFill = element.querySelector('.ds-statcard__progress-fill');
6
+ this.animated = false;
7
+ this._observer = null;
8
+
9
+ this.init();
10
+ }
11
+
12
+ init() {
13
+ this.animateValue();
14
+ this.animateProgress();
15
+
16
+ this._observer = new IntersectionObserver((entries) => {
17
+ entries.forEach(entry => {
18
+ if (entry.isIntersecting && !this.animated) {
19
+ this.animateValue();
20
+ this.animateProgress();
21
+ this.animated = true;
22
+ }
23
+ });
24
+ }, { threshold: 0.3 });
25
+
26
+ this._observer.observe(this.element);
27
+ }
28
+
29
+ animateValue() {
30
+ if (!this.valueElement) return;
31
+
32
+ const text = this.valueElement.textContent;
33
+ const match = text.match(/[\d.,]+/);
34
+ if (!match) return;
35
+
36
+ const targetValue = parseFloat(match[0].replace(',', ''));
37
+ const prefix = text.substring(0, match.index);
38
+ const suffix = text.substring(match.index + match[0].length);
39
+
40
+ const duration = 1500;
41
+ const startTime = performance.now();
42
+ const startValue = 0;
43
+
44
+ const animate = (currentTime) => {
45
+ const elapsed = currentTime - startTime;
46
+ const progress = Math.min(elapsed / duration, 1);
47
+ const easeProgress = 1 - Math.pow(1 - progress, 3);
48
+
49
+ const currentValue = startValue + (targetValue - startValue) * easeProgress;
50
+
51
+ let formattedValue;
52
+ if (targetValue >= 1000000) {
53
+ formattedValue = (currentValue / 1000000).toFixed(1) + 'M';
54
+ } else if (targetValue >= 1000) {
55
+ formattedValue = (currentValue / 1000).toFixed(1) + 'K';
56
+ } else if (Number.isInteger(targetValue)) {
57
+ formattedValue = Math.floor(currentValue).toLocaleString();
58
+ } else {
59
+ formattedValue = currentValue.toFixed(2);
60
+ }
61
+
62
+ this.valueElement.textContent = prefix + formattedValue + suffix;
63
+
64
+ if (progress < 1) {
65
+ requestAnimationFrame(animate);
66
+ }
67
+ };
68
+
69
+ requestAnimationFrame(animate);
70
+ }
71
+
72
+ animateProgress() {
73
+ if (!this.progressFill) return;
74
+
75
+ const width = this.progressFill.getAttribute('data-width') || '0%';
76
+ this.progressFill.style.width = width;
77
+ }
78
+
79
+ updateValue(newValue, options = {}) {
80
+ if (!this.valueElement) return;
81
+
82
+ const duration = options.duration || 800;
83
+ const text = this.valueElement.textContent;
84
+ const match = text.match(/[\d.,]+/);
85
+
86
+ if (!match) {
87
+ this.valueElement.textContent = newValue;
88
+ return;
89
+ }
90
+
91
+ const prefix = text.substring(0, match.index);
92
+ const suffix = text.substring(match.index + match[0].length);
93
+ const startValue = parseFloat(match[0].replace(',', ''));
94
+ const targetValue = parseFloat(newValue);
95
+
96
+ const startTime = performance.now();
97
+
98
+ const animate = (currentTime) => {
99
+ const elapsed = currentTime - startTime;
100
+ const progress = Math.min(elapsed / duration, 1);
101
+ const easeProgress = 1 - Math.pow(1 - progress, 3);
102
+
103
+ const currentValue = startValue + (targetValue - startValue) * easeProgress;
104
+
105
+ let formattedValue;
106
+ if (targetValue >= 1000000) {
107
+ formattedValue = (currentValue / 1000000).toFixed(1) + 'M';
108
+ } else if (targetValue >= 1000) {
109
+ formattedValue = (currentValue / 1000).toFixed(1) + 'K';
110
+ } else if (Number.isInteger(targetValue)) {
111
+ formattedValue = Math.floor(currentValue).toLocaleString();
112
+ } else {
113
+ formattedValue = currentValue.toFixed(2);
114
+ }
115
+
116
+ this.valueElement.textContent = prefix + formattedValue + suffix;
117
+
118
+ if (progress < 1) {
119
+ requestAnimationFrame(animate);
120
+ }
121
+ };
122
+
123
+ requestAnimationFrame(animate);
124
+ }
125
+
126
+ updateProgress(percent, options = {}) {
127
+ if (!this.progressFill) return;
128
+
129
+ const duration = options.duration || 600;
130
+ const currentWidth = parseFloat(this.progressFill.style.width || '0');
131
+ const targetWidth = Math.min(Math.max(percent, 0), 100);
132
+
133
+ const startTime = performance.now();
134
+
135
+ const animate = (currentTime) => {
136
+ const elapsed = currentTime - startTime;
137
+ const progress = Math.min(elapsed / duration, 1);
138
+ const easeProgress = 1 - Math.pow(1 - progress, 3);
139
+
140
+ const currentWidthValue = currentWidth + (targetWidth - currentWidth) * easeProgress;
141
+ this.progressFill.style.width = currentWidthValue + '%';
142
+
143
+ if (progress < 1) {
144
+ requestAnimationFrame(animate);
145
+ }
146
+ };
147
+
148
+ requestAnimationFrame(animate);
149
+ }
150
+
151
+ setTrend(direction, value) {
152
+ const trendElement = this.element.querySelector('.ds-statcard__trend');
153
+ if (!trendElement) return;
154
+
155
+ trendElement.className = `ds-statcard__trend ds-statcard__trend--${direction}`;
156
+
157
+ const iconSvg = direction === 'up'
158
+ ? '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="23 6 13.5 15.5 8.5 10.5 1 18"/></svg>'
159
+ : direction === 'down'
160
+ ? '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="1 18 10.5 8.5 15.5 13.5 23 6"/></svg>'
161
+ : '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="12 19 18 13 12 7 6 13"/></svg>';
162
+
163
+ trendElement.innerHTML = iconSvg + value;
164
+ }
165
+
166
+ destroy() {
167
+ if (this._observer) {
168
+ this._observer.disconnect();
169
+ this._observer = null;
170
+ }
171
+ this.animated = false;
172
+ this.valueElement = null;
173
+ this.progressFill = null;
174
+ this.element = null;
175
+ }
176
+ }
177
+
178
+ function initStatCard(element) {
179
+ if (element.__kupolaInitialized) return;
180
+
181
+ const instance = new StatCard(element);
182
+ element.__kupolaInstance = instance;
183
+ element.__kupolaInitialized = true;
184
+ }
185
+
186
+ function cleanupStatCard(element) {
187
+ if (!element.__kupolaInitialized || !element.__kupolaInstance) return;
188
+
189
+ const instance = element.__kupolaInstance;
190
+ instance.destroy();
191
+
192
+ element.__kupolaInstance = null;
193
+ element.__kupolaInitialized = false;
194
+ }
195
+
196
+ function initStatCards() {
197
+ document.querySelectorAll('.ds-statcard').forEach(card => {
198
+ initStatCard(card);
199
+ });
200
+ }
201
+
202
+ function updateStatCard(selector, newValue) {
203
+ const card = document.querySelector(selector);
204
+ if (card && card.__kupolaInstance) {
205
+ card.__kupolaInstance.updateValue(newValue);
206
+ }
207
+ }
208
+
209
+ if (typeof module !== 'undefined' && module.exports) {
210
+ module.exports = { StatCard, initStatCards, initStatCard, cleanupStatCard, updateStatCard };
211
+ } else {
212
+ window.StatCard = StatCard;
213
+ window.initStatCard = initStatCard;
214
+ window.cleanupStatCard = cleanupStatCard;
215
+ }
216
+
217
+ if (window.kupolaInitializer) {
218
+ window.kupolaInitializer.register('statcard', initStatCard, cleanupStatCard);
219
+ }
package/js/tag.js ADDED
@@ -0,0 +1,83 @@
1
+ class Tag {
2
+ constructor(element) {
3
+ this.element = element;
4
+ this.closeBtn = element.querySelector('.ds-tag__close');
5
+ this._listeners = [];
6
+ this.init();
7
+ }
8
+
9
+ init() {
10
+ if (this.closeBtn) {
11
+ const closeHandler = (e) => {
12
+ e.stopPropagation();
13
+ this.element.dispatchEvent(new CustomEvent('ds-tag-remove', {
14
+ detail: { tag: this.element },
15
+ bubbles: true
16
+ }));
17
+ this.element.remove();
18
+ };
19
+ this.closeBtn.addEventListener('click', closeHandler);
20
+ this._listeners.push({ el: this.closeBtn, event: 'click', handler: closeHandler });
21
+ }
22
+ }
23
+
24
+ setContent(content) {
25
+ const textNodes = [];
26
+ this.element.childNodes.forEach(node => {
27
+ if (node.nodeType === Node.TEXT_NODE) {
28
+ textNodes.push(node);
29
+ }
30
+ });
31
+ textNodes.forEach(node => node.remove());
32
+ this.element.insertBefore(document.createTextNode(content), this.closeBtn || null);
33
+ }
34
+
35
+ getContent() {
36
+ return this.element.textContent.trim();
37
+ }
38
+
39
+ destroy() {
40
+ this._listeners.forEach(({ el, event, handler }) => {
41
+ el.removeEventListener(event, handler);
42
+ });
43
+ this._listeners = null;
44
+ this.closeBtn = null;
45
+ this.element = null;
46
+ }
47
+ }
48
+
49
+ function initTag(element) {
50
+ if (element.__kupolaInitialized) return;
51
+
52
+ const instance = new Tag(element);
53
+ element.__kupolaInstance = instance;
54
+ element.__kupolaInitialized = true;
55
+ }
56
+
57
+ function cleanupTag(element) {
58
+ if (!element.__kupolaInitialized || !element.__kupolaInstance) return;
59
+
60
+ const instance = element.__kupolaInstance;
61
+ instance.destroy();
62
+
63
+ element.__kupolaInstance = null;
64
+ element.__kupolaInitialized = false;
65
+ }
66
+
67
+ function initTags() {
68
+ document.querySelectorAll('.ds-tag').forEach(tag => {
69
+ initTag(tag);
70
+ });
71
+ }
72
+
73
+ if (typeof module !== 'undefined' && module.exports) {
74
+ module.exports = { Tag, initTags, initTag, cleanupTag };
75
+ } else {
76
+ window.Tag = Tag;
77
+ window.initTag = initTag;
78
+ window.cleanupTag = cleanupTag;
79
+ }
80
+
81
+ if (window.kupolaInitializer) {
82
+ window.kupolaInitializer.register('tag', initTag, cleanupTag);
83
+ }
@@ -0,0 +1,251 @@
1
+ class KupolaTestUtils {
2
+ static createElement(html) {
3
+ const container = document.createElement('div');
4
+ container.innerHTML = html;
5
+ return container.firstElementChild;
6
+ }
7
+
8
+ static mountComponent(ComponentClass, props = {}, html = '') {
9
+ const element = this.createElement(html);
10
+ const component = new ComponentClass(element);
11
+ Object.assign(component.props, props);
12
+ return { component, element };
13
+ }
14
+
15
+ static async waitFor(condition, timeout = 1000) {
16
+ const startTime = Date.now();
17
+ return new Promise((resolve, reject) => {
18
+ const check = async () => {
19
+ try {
20
+ const result = await condition();
21
+ if (result) {
22
+ resolve(result);
23
+ } else if (Date.now() - startTime < timeout) {
24
+ requestAnimationFrame(check);
25
+ } else {
26
+ reject(new Error('waitFor timeout'));
27
+ }
28
+ } catch (error) {
29
+ if (Date.now() - startTime < timeout) {
30
+ requestAnimationFrame(check);
31
+ } else {
32
+ reject(new Error('waitFor timeout'));
33
+ }
34
+ }
35
+ };
36
+ requestAnimationFrame(check);
37
+ });
38
+ }
39
+
40
+ static async waitForElement(selector, parent = document, timeout = 1000) {
41
+ return this.waitFor(() => parent.querySelector(selector), timeout);
42
+ }
43
+
44
+ static async waitForText(text, parent = document, timeout = 1000) {
45
+ return this.waitFor(() => parent.textContent.includes(text), timeout);
46
+ }
47
+
48
+ static fireEvent(element, eventName, detail = {}) {
49
+ const event = new CustomEvent(eventName, {
50
+ bubbles: true,
51
+ cancelable: true,
52
+ detail
53
+ });
54
+ element.dispatchEvent(event);
55
+ }
56
+
57
+ static fireInput(element, value) {
58
+ element.value = value;
59
+ this.fireEvent(element, 'input');
60
+ this.fireEvent(element, 'change');
61
+ }
62
+
63
+ static fireClick(element) {
64
+ this.fireEvent(element, 'click');
65
+ }
66
+
67
+ static fireKeyDown(element, key) {
68
+ const event = new KeyboardEvent('keydown', { key, bubbles: true });
69
+ element.dispatchEvent(event);
70
+ }
71
+
72
+ static async nextTick() {
73
+ return new Promise(resolve => requestAnimationFrame(resolve));
74
+ }
75
+
76
+ static mockDate(date) {
77
+ const originalNow = Date.now;
78
+ const originalDate = Date;
79
+
80
+ Date.now = () => date.getTime();
81
+ globalThis.Date = class extends Date {
82
+ constructor(...args) {
83
+ super(...args);
84
+ if (args.length === 0) {
85
+ return new Date(date);
86
+ }
87
+ return new originalDate(...args);
88
+ }
89
+ };
90
+
91
+ return () => {
92
+ Date.now = originalNow;
93
+ globalThis.Date = originalDate;
94
+ };
95
+ }
96
+
97
+ static mockFetch(mockResponses) {
98
+ const originalFetch = fetch;
99
+
100
+ globalThis.fetch = jest.fn(async (url, options) => {
101
+ for (const mock of mockResponses) {
102
+ if (typeof mock.url === 'string' && url === mock.url) {
103
+ return mock.response();
104
+ }
105
+ if (mock.url instanceof RegExp && mock.url.test(url)) {
106
+ return mock.response();
107
+ }
108
+ if (mock.method && options && options.method === mock.method) {
109
+ return mock.response();
110
+ }
111
+ }
112
+ return originalFetch(url, options);
113
+ });
114
+
115
+ return () => {
116
+ globalThis.fetch = originalFetch;
117
+ };
118
+ }
119
+
120
+ static async mountAndWait(ComponentClass, props = {}, html = '') {
121
+ const { component, element } = this.mountComponent(ComponentClass, props, html);
122
+ await component.mount();
123
+ await this.nextTick();
124
+ return { component, element };
125
+ }
126
+
127
+ static async unmountAndWait(component) {
128
+ await component.unmount();
129
+ await this.nextTick();
130
+ }
131
+
132
+ static getByText(parent, text) {
133
+ const elements = parent.querySelectorAll('*');
134
+ for (const element of elements) {
135
+ if (element.textContent.trim() === text) {
136
+ return element;
137
+ }
138
+ }
139
+ throw new Error(`Element with text "${text}" not found`);
140
+ }
141
+
142
+ static queryByText(parent, text) {
143
+ const elements = parent.querySelectorAll('*');
144
+ for (const element of elements) {
145
+ if (element.textContent.trim() === text) {
146
+ return element;
147
+ }
148
+ }
149
+ return null;
150
+ }
151
+
152
+ static getByTestId(parent, testId) {
153
+ const element = parent.querySelector(`[data-testid="${testId}"]`);
154
+ if (!element) {
155
+ throw new Error(`Element with data-testid="${testId}" not found`);
156
+ }
157
+ return element;
158
+ }
159
+
160
+ static queryByTestId(parent, testId) {
161
+ return parent.querySelector(`[data-testid="${testId}"]`);
162
+ }
163
+
164
+ static getByRole(parent, role) {
165
+ const element = parent.querySelector(`[role="${role}"]`);
166
+ if (!element) {
167
+ throw new Error(`Element with role="${role}" not found`);
168
+ }
169
+ return element;
170
+ }
171
+
172
+ static queryByRole(parent, role) {
173
+ return parent.querySelector(`[role="${role}"]`);
174
+ }
175
+
176
+ static async simulateDrag(element, startX, startY, endX, endY) {
177
+ this.fireEvent(element, 'dragstart', {
178
+ clientX: startX,
179
+ clientY: startY
180
+ });
181
+
182
+ this.fireEvent(element, 'drag', {
183
+ clientX: (startX + endX) / 2,
184
+ clientY: (startY + endY) / 2
185
+ });
186
+
187
+ this.fireEvent(element, 'dragend', {
188
+ clientX: endX,
189
+ clientY: endY
190
+ });
191
+ }
192
+
193
+ static async simulateTouch(element, startX, startY, endX, endY) {
194
+ const touchStart = new TouchEvent('touchstart', {
195
+ touches: [{ clientX: startX, clientY: startY }],
196
+ bubbles: true
197
+ });
198
+ element.dispatchEvent(touchStart);
199
+
200
+ const touchMove = new TouchEvent('touchmove', {
201
+ touches: [{ clientX: (startX + endX) / 2, clientY: (startY + endY) / 2 }],
202
+ bubbles: true
203
+ });
204
+ element.dispatchEvent(touchMove);
205
+
206
+ const touchEnd = new TouchEvent('touchend', {
207
+ touches: [{ clientX: endX, clientY: endY }],
208
+ bubbles: true
209
+ });
210
+ element.dispatchEvent(touchEnd);
211
+ }
212
+
213
+ static async waitForState(component, stateKey, expectedValue) {
214
+ return this.waitFor(() => {
215
+ return component.state[stateKey] === expectedValue;
216
+ });
217
+ }
218
+
219
+ static async waitForProps(component, propKey, expectedValue) {
220
+ return this.waitFor(() => {
221
+ return component.props[propKey] === expectedValue;
222
+ });
223
+ }
224
+
225
+ static async waitForEmitted(component, eventName) {
226
+ return this.waitFor(() => {
227
+ return component._emitted && component._emitted[eventName];
228
+ });
229
+ }
230
+
231
+ static spyOn(obj, methodName) {
232
+ const original = obj[methodName];
233
+ const spy = function(...args) {
234
+ spy.called = true;
235
+ spy.callCount = (spy.callCount || 0) + 1;
236
+ spy.args = (spy.args || []).concat([args]);
237
+ return original.apply(this, args);
238
+ };
239
+ obj[methodName] = spy;
240
+ return () => {
241
+ obj[methodName] = original;
242
+ };
243
+ }
244
+
245
+ static async flushPromises() {
246
+ return new Promise(resolve => setImmediate(resolve));
247
+ }
248
+ }
249
+
250
+ window.KupolaTestUtils = KupolaTestUtils;
251
+ window.testUtils = KupolaTestUtils;