@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/theme.js ADDED
@@ -0,0 +1,363 @@
1
+ const THEME_KEY = 'kupola-theme';
2
+ const BRAND_KEY = 'kupola-brand';
3
+
4
+ const BRAND_OPTIONS = [
5
+ { id: 'green', name: '翠绿', color: '#32F08C' },
6
+ { id: 'xionghuang', name: '雄黄', color: '#FF9900' },
7
+ { id: 'jianghuang', name: '姜黄', color: '#E2C027' },
8
+ { id: 'lanlv', name: '蓝绿', color: '#12A182' },
9
+ { id: 'kongquelan', name: '孔雀蓝', color: '#0EB0C9' },
10
+ { id: 'meiguizi', name: '玫瑰紫', color: '#BA2F7B' },
11
+ { id: 'shihong', name: '柿红', color: '#F2481B' },
12
+ { id: 'quhong', name: '紫云', color: '#B1A6CC' },
13
+ { id: 'shanchahong', name: '山茶红', color: '#F05A46' },
14
+ { id: 'zengqing', name: '曾青', color: '#535164' },
15
+ { id: 'roulan', name: '柔蓝', color: '#106898' }
16
+ ];
17
+
18
+ function getTheme() {
19
+ return localStorage.getItem(THEME_KEY) || 'dark';
20
+ }
21
+
22
+ function setTheme(theme) {
23
+ if (theme !== 'dark' && theme !== 'light') return;
24
+
25
+ document.documentElement.setAttribute('data-theme', theme);
26
+ localStorage.setItem(THEME_KEY, theme);
27
+
28
+ const toggleBtn = document.querySelector('[data-theme-toggle]');
29
+ if (toggleBtn) {
30
+ toggleBtn.setAttribute('data-current-theme', theme);
31
+ const icon = toggleBtn.querySelector('.theme-icon');
32
+ if (icon) {
33
+ const iconPath = icon.src.substring(0, icon.src.lastIndexOf('/') + 1);
34
+ icon.src = theme === 'dark'
35
+ ? iconPath + 'sun.svg'
36
+ : iconPath + 'moon.svg';
37
+ }
38
+ }
39
+ }
40
+
41
+ function getBrand() {
42
+ return localStorage.getItem(BRAND_KEY) || 'zengqing';
43
+ }
44
+
45
+ function setBrand(brandId) {
46
+ const brand = BRAND_OPTIONS.find(b => b.id === brandId);
47
+ if (!brand) return;
48
+
49
+ document.documentElement.setAttribute('data-brand', brandId);
50
+ localStorage.setItem(BRAND_KEY, brandId);
51
+
52
+ const brandToggle = document.querySelector('[data-brand-toggle]');
53
+ if (brandToggle) {
54
+ brandToggle.setAttribute('data-current-brand', brandId);
55
+ const brandIcon = brandToggle.querySelector('.brand-icon');
56
+ if (brandIcon) {
57
+ brandIcon.style.backgroundColor = brand.color;
58
+ }
59
+ const brandName = brandToggle.querySelector('.brand-name');
60
+ if (brandName) {
61
+ brandName.textContent = brand.name;
62
+ }
63
+ }
64
+
65
+ const brandButtons = document.querySelectorAll('[data-brand-btn]');
66
+ brandButtons.forEach(btn => {
67
+ if (btn.getAttribute('data-brand-btn') === brandId) {
68
+ btn.classList.add('is-active');
69
+ } else {
70
+ btn.classList.remove('is-active');
71
+ }
72
+ });
73
+ }
74
+
75
+ function initTheme() {
76
+
77
+ const savedTheme = getTheme();
78
+ setTheme(savedTheme);
79
+
80
+ const savedBrand = getBrand();
81
+ setBrand(savedBrand);
82
+
83
+ const toggleBtn = document.querySelector('[data-theme-toggle]');
84
+ if (toggleBtn) {
85
+ toggleBtn.addEventListener('click', () => {
86
+ const currentTheme = getTheme();
87
+ const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
88
+ setTheme(newTheme);
89
+ });
90
+ }
91
+
92
+ let brandPicker = document.getElementById('brand-picker');
93
+ if (!brandPicker) {
94
+ brandPicker = document.createElement('div');
95
+ brandPicker.id = 'brand-picker';
96
+ brandPicker.style.position = 'fixed';
97
+ brandPicker.style.top = '64px';
98
+ brandPicker.style.right = '16px';
99
+ brandPicker.style.zIndex = '9998';
100
+ brandPicker.style.display = 'none';
101
+ brandPicker.style.padding = '12px';
102
+ brandPicker.style.width = '200px';
103
+ brandPicker.style.gridTemplateColumns = 'repeat(3, 1fr)';
104
+ brandPicker.style.gap = '6px';
105
+ brandPicker.style.backgroundColor = 'var(--bg-base-secondary)';
106
+ brandPicker.style.border = '1px solid var(--border-neutral-l1)';
107
+ brandPicker.style.borderRadius = '8px';
108
+ brandPicker.style.boxShadow = '0 4px 20px rgba(0, 0, 0, 0.2)';
109
+ brandPicker.style.overflow = 'hidden';
110
+
111
+ BRAND_OPTIONS.forEach(brand => {
112
+ const btn = document.createElement('button');
113
+ btn.setAttribute('data-brand-btn', brand.id);
114
+ btn.style.display = 'flex';
115
+ btn.style.justifyContent = 'center';
116
+ btn.style.alignItems = 'center';
117
+ btn.style.height = '60px';
118
+ btn.style.backgroundColor = brand.color;
119
+ btn.style.color = ['#32F08C', '#FF9900', '#E2C027', '#0EB0C9', '#B1A6CC'].includes(brand.color) ? '#0C0C0D' : '#FFFFFF';
120
+ btn.style.fontWeight = '500';
121
+ btn.style.borderRadius = '4px';
122
+ btn.style.border = 'none';
123
+ btn.style.cursor = 'pointer';
124
+ btn.style.margin = '0';
125
+ btn.style.padding = '0';
126
+ btn.textContent = brand.name;
127
+ brandPicker.appendChild(btn);
128
+ });
129
+
130
+ document.body.appendChild(brandPicker);
131
+ }
132
+
133
+ const brandToggle = document.querySelector('[data-brand-toggle]');
134
+
135
+ if (brandToggle && brandPicker) {
136
+ brandToggle.onclick = function(e) {
137
+ e.stopPropagation();
138
+ e.preventDefault();
139
+ const isHidden = brandPicker.style.display === 'none';
140
+ brandPicker.style.display = isHidden ? 'grid' : 'none';
141
+
142
+ if (isHidden) {
143
+ setTimeout(() => {
144
+ document.addEventListener('click', closeBrandPicker, true);
145
+ }, 0);
146
+ } else {
147
+ document.removeEventListener('click', closeBrandPicker, true);
148
+ }
149
+ };
150
+
151
+ brandPicker.onclick = function(e) {
152
+ e.stopPropagation();
153
+ };
154
+ }
155
+
156
+ function closeBrandPicker(e) {
157
+ if (brandPicker && brandToggle) {
158
+ if (!brandPicker.contains(e.target) && !brandToggle.contains(e.target)) {
159
+ brandPicker.style.display = 'none';
160
+ document.removeEventListener('click', closeBrandPicker, true);
161
+ }
162
+ }
163
+ }
164
+
165
+ const brandButtons = document.querySelectorAll('[data-brand-btn]');
166
+
167
+ brandButtons.forEach(btn => {
168
+ btn.addEventListener('click', (e) => {
169
+ e.stopPropagation();
170
+ const brandId = btn.getAttribute('data-brand-btn');
171
+ setBrand(brandId);
172
+ if (brandPicker) {
173
+ brandPicker.style.display = 'none';
174
+ }
175
+ });
176
+ });
177
+ }
178
+
179
+ function createThemeToggle() {
180
+ const btn = document.createElement('button');
181
+ btn.setAttribute('data-theme-toggle', '');
182
+ btn.setAttribute('data-current-theme', getTheme());
183
+ btn.className = 'ds-btn ds-btn--ghost ds-btn--sm ds-btn--icon';
184
+ btn.style.position = 'fixed';
185
+ btn.style.top = '16px';
186
+ btn.style.right = '16px';
187
+ btn.style.zIndex = '9999';
188
+
189
+ const icon = document.createElement('img');
190
+ icon.className = 'theme-icon';
191
+ const scripts = document.getElementsByTagName('script');
192
+ const currentScript = scripts[scripts.length - 1];
193
+ const scriptPath = currentScript.src.substring(0, currentScript.src.lastIndexOf('/') + 1);
194
+ icon.src = getTheme() === 'dark'
195
+ ? scriptPath + '../icons/sun.svg'
196
+ : scriptPath + '../icons/moon.svg';
197
+ icon.width = 14;
198
+ icon.height = 14;
199
+ icon.alt = 'Toggle theme';
200
+
201
+ btn.appendChild(icon);
202
+ document.body.appendChild(btn);
203
+
204
+ btn.addEventListener('click', () => {
205
+ const currentTheme = getTheme();
206
+ const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
207
+ setTheme(newTheme);
208
+ });
209
+
210
+ return btn;
211
+ }
212
+
213
+ function createBrandPicker() {
214
+ const container = document.createElement('div');
215
+ container.id = 'brand-picker-auto';
216
+ container.style.position = 'fixed';
217
+ container.style.top = '56px';
218
+ container.style.right = '16px';
219
+ container.style.zIndex = '9998';
220
+ container.style.display = 'none';
221
+ container.style.padding = '12px';
222
+ container.style.width = '200px';
223
+ container.style.gridTemplateColumns = 'repeat(3, 1fr)';
224
+ container.style.gap = '6px';
225
+ container.style.backgroundColor = 'var(--bg-base-secondary)';
226
+ container.style.border = '1px solid var(--border-neutral-l1)';
227
+ container.style.borderRadius = '8px';
228
+ container.style.boxShadow = '0 4px 20px rgba(0, 0, 0, 0.2)';
229
+ container.style.overflow = 'hidden';
230
+
231
+ BRAND_OPTIONS.forEach(brand => {
232
+ const btn = document.createElement('button');
233
+ btn.setAttribute('data-brand-btn', brand.id);
234
+ btn.style.display = 'flex';
235
+ btn.style.justifyContent = 'center';
236
+ btn.style.alignItems = 'center';
237
+ btn.style.height = '60px';
238
+ btn.style.backgroundColor = brand.color;
239
+ btn.style.color = ['#32F08C', '#FF9900', '#E2C027', '#0EB0C9', '#B1A6CC'].includes(brand.color) ? '#0C0C0D' : '#FFFFFF';
240
+ btn.style.fontWeight = '500';
241
+ btn.style.borderRadius = '4px';
242
+ btn.style.border = 'none';
243
+ btn.style.cursor = 'pointer';
244
+ btn.style.margin = '0';
245
+ btn.style.padding = '0';
246
+ btn.textContent = brand.name;
247
+ container.appendChild(btn);
248
+ });
249
+
250
+ document.body.appendChild(container);
251
+
252
+ const toggleBtn = document.createElement('button');
253
+ toggleBtn.setAttribute('data-brand-toggle', '');
254
+ toggleBtn.setAttribute('data-current-brand', getBrand());
255
+ toggleBtn.className = 'ds-btn ds-btn--ghost ds-btn--sm';
256
+ toggleBtn.style.position = 'fixed';
257
+ toggleBtn.style.top = '16px';
258
+ toggleBtn.style.right = '56px';
259
+ toggleBtn.style.zIndex = '9999';
260
+ toggleBtn.style.display = 'flex';
261
+ toggleBtn.style.alignItems = 'center';
262
+ toggleBtn.style.gap = '6px';
263
+
264
+ const brandIcon = document.createElement('span');
265
+ brandIcon.className = 'brand-icon';
266
+ brandIcon.style.width = '12px';
267
+ brandIcon.style.height = '12px';
268
+ brandIcon.style.borderRadius = '50%';
269
+ brandIcon.style.backgroundColor = BRAND_OPTIONS.find(b => b.id === getBrand()).color;
270
+
271
+ const brandName = document.createElement('span');
272
+ brandName.className = 'brand-name';
273
+ brandName.style.fontSize = '11px';
274
+ brandName.textContent = BRAND_OPTIONS.find(b => b.id === getBrand()).name;
275
+
276
+ toggleBtn.appendChild(brandIcon);
277
+ toggleBtn.appendChild(brandName);
278
+ document.body.appendChild(toggleBtn);
279
+
280
+ toggleBtn.onclick = function(e) {
281
+ e.stopPropagation();
282
+ e.preventDefault();
283
+ const isHidden = container.style.display === 'none';
284
+ container.style.display = isHidden ? 'grid' : 'none';
285
+
286
+ if (isHidden) {
287
+ setTimeout(() => {
288
+ document.addEventListener('click', closeBrandPickerAuto, true);
289
+ }, 0);
290
+ } else {
291
+ document.removeEventListener('click', closeBrandPickerAuto, true);
292
+ }
293
+ };
294
+
295
+ container.onclick = function(e) {
296
+ e.stopPropagation();
297
+ };
298
+
299
+ function closeBrandPickerAuto(e) {
300
+ if (!container.contains(e.target) && !toggleBtn.contains(e.target)) {
301
+ container.style.display = 'none';
302
+ document.removeEventListener('click', closeBrandPickerAuto, true);
303
+ }
304
+ }
305
+
306
+ const brandButtons = container.querySelectorAll('[data-brand-btn]');
307
+ brandButtons.forEach(btn => {
308
+ btn.addEventListener('click', (e) => {
309
+ e.stopPropagation();
310
+ const brandId = btn.getAttribute('data-brand-btn');
311
+ setBrand(brandId);
312
+ container.style.display = 'none';
313
+ });
314
+ });
315
+
316
+ return { toggleBtn, container };
317
+ }
318
+
319
+ function loadDevTools() {
320
+ const urlParams = new URLSearchParams(window.location.search);
321
+ const enabled = urlParams.has('dev') || urlParams.has('debug');
322
+
323
+ if (!enabled) return;
324
+
325
+ const themeScript = document.querySelector('script[src*="theme.js"]');
326
+ if (!themeScript || !themeScript.src) return;
327
+
328
+ const scriptPath = themeScript.src.substring(0, themeScript.src.lastIndexOf('/') + 1);
329
+
330
+ const devToolsScript = document.createElement('script');
331
+ devToolsScript.src = scriptPath + 'kupola-devtools.js?v=15';
332
+ devToolsScript.async = true;
333
+
334
+ document.head.appendChild(devToolsScript);
335
+ }
336
+
337
+ if (typeof module !== 'undefined' && module.exports) {
338
+ module.exports = {
339
+ getTheme, setTheme, initTheme, createThemeToggle,
340
+ getBrand, setBrand, BRAND_OPTIONS, createBrandPicker,
341
+ loadDevTools
342
+ };
343
+ } else {
344
+ if (!window.__kupolaThemeInitialized) {
345
+ window.__kupolaThemeInitialized = true;
346
+
347
+ function runInit() {
348
+ initTheme();
349
+ loadDevTools();
350
+ }
351
+
352
+ if (document.readyState === 'loading') {
353
+ document.addEventListener('readystatechange', function readyStateHandler() {
354
+ if (document.readyState !== 'loading') {
355
+ document.removeEventListener('readystatechange', readyStateHandler);
356
+ runInit();
357
+ }
358
+ });
359
+ } else {
360
+ setTimeout(runInit, 0);
361
+ }
362
+ }
363
+ }
@@ -0,0 +1,238 @@
1
+ class Timepicker {
2
+ constructor(element) {
3
+ this.element = element;
4
+ this.input = element.querySelector('input');
5
+ this.inputWrap = element.querySelector('.ds-timepicker__input-wrap');
6
+ this.panelEl = null;
7
+ this.scope = `timepicker-${Math.random().toString(36).substr(2, 9)}`;
8
+
9
+ this._inputWrapClickHandler = null;
10
+ this._documentClickHandler = null;
11
+ this._documentClickListener = null;
12
+ this._resizeHandler = null;
13
+ this._resizeListener = null;
14
+
15
+ this.selectedHour = 12;
16
+ this.selectedMinute = 0;
17
+ }
18
+
19
+ init() {
20
+ if (this.element.__kupolaInitialized) return;
21
+
22
+ this._inputWrapClickHandler = (e) => {
23
+ e.stopPropagation();
24
+ if (this.panelEl && this.panelEl.style.display === 'block') {
25
+ this.panelEl.style.display = 'none';
26
+ } else {
27
+ this.showTimepicker();
28
+ }
29
+ };
30
+
31
+ this.inputWrap.addEventListener('click', this._inputWrapClickHandler);
32
+
33
+ if (window.globalEvents) {
34
+ this._documentClickListener = window.globalEvents.on(document, 'click', (e) => this.hideTimepicker(e), { scope: this.scope });
35
+ this._resizeListener = window.globalEvents.on(window, 'resize', () => this.resizeHandler(), { scope: this.scope });
36
+ } else {
37
+ document.addEventListener('click', (e) => this.hideTimepicker(e));
38
+ window.addEventListener('resize', () => this.resizeHandler());
39
+ this._documentClickHandler = (e) => this.hideTimepicker(e);
40
+ this._resizeHandler = () => this.resizeHandler();
41
+ }
42
+
43
+ this.element.__kupolaInitialized = true;
44
+ }
45
+
46
+ calculatePosition() {
47
+ if (!this.panelEl) return;
48
+
49
+ const pickerRect = this.element.getBoundingClientRect();
50
+ const panelRect = this.panelEl.getBoundingClientRect();
51
+ const viewportHeight = window.innerHeight;
52
+
53
+ const spaceBelow = viewportHeight - pickerRect.bottom;
54
+ const spaceAbove = pickerRect.top;
55
+ const panelHeight = panelRect.height || 320;
56
+
57
+ if (spaceBelow >= panelHeight) {
58
+ this.panelEl.style.top = 'calc(100% + 4px)';
59
+ this.panelEl.style.bottom = 'auto';
60
+ } else if (spaceAbove >= panelHeight) {
61
+ this.panelEl.style.top = 'auto';
62
+ this.panelEl.style.bottom = 'calc(100% + 4px)';
63
+ } else {
64
+ this.panelEl.style.top = 'calc(100% + 4px)';
65
+ this.panelEl.style.bottom = 'auto';
66
+ }
67
+ }
68
+
69
+ showTimepicker() {
70
+ if (this.panelEl) {
71
+ this.panelEl.style.display = 'block';
72
+ this.calculatePosition();
73
+ return;
74
+ }
75
+
76
+ this.panelEl = document.createElement('div');
77
+ this.panelEl.className = 'ds-timepicker__panel';
78
+ this.panelEl.innerHTML = `
79
+ <div class="ds-timepicker__header">
80
+ <div class="ds-timepicker__display">
81
+ <span class="ds-timepicker__display-hour">12</span>
82
+ <span class="ds-timepicker__separator">:</span>
83
+ <span class="ds-timepicker__display-minute">00</span>
84
+ </div>
85
+ </div>
86
+ <div class="ds-timepicker__body">
87
+ <div class="ds-timepicker__section">
88
+ <div class="ds-timepicker__section-label">Hour</div>
89
+ <div class="ds-timepicker__grid ds-timepicker__grid--hour" data-type="hour"></div>
90
+ </div>
91
+ <div class="ds-timepicker__section">
92
+ <div class="ds-timepicker__section-label">Minute</div>
93
+ <div class="ds-timepicker__grid ds-timepicker__grid--minute" data-type="minute"></div>
94
+ </div>
95
+ </div>
96
+ `;
97
+ this.element.appendChild(this.panelEl);
98
+
99
+ if (this.input.value) {
100
+ const timeParts = this.input.value.split(':');
101
+ if (timeParts.length >= 2) {
102
+ this.selectedHour = parseInt(timeParts[0]) || 0;
103
+ this.selectedMinute = parseInt(timeParts[1]) || 0;
104
+ }
105
+ }
106
+
107
+ const displayHour = this.panelEl.querySelector('.ds-timepicker__display-hour');
108
+ const displayMinute = this.panelEl.querySelector('.ds-timepicker__display-minute');
109
+
110
+ function updateDisplay() {
111
+ displayHour.textContent = String(this.selectedHour).padStart(2, '0');
112
+ displayMinute.textContent = String(this.selectedMinute).padStart(2, '0');
113
+ }
114
+
115
+ function updateInput() {
116
+ this.input.value = `${String(this.selectedHour).padStart(2, '0')}:${String(this.selectedMinute).padStart(2, '0')}`;
117
+ }
118
+
119
+ function selectTimeAndClose() {
120
+ updateInput.call(this);
121
+ this.panelEl.style.display = 'none';
122
+ this.input.dispatchEvent(new Event('change'));
123
+ }
124
+
125
+ const hourGrid = this.panelEl.querySelector('[data-type="hour"]');
126
+ const minuteGrid = this.panelEl.querySelector('[data-type="minute"]');
127
+
128
+ for (let i = 0; i < 24; i++) {
129
+ const btn = document.createElement('button');
130
+ btn.className = 'ds-timepicker__item';
131
+ btn.textContent = String(i).padStart(2, '0');
132
+ if (i === this.selectedHour) btn.classList.add('is-selected');
133
+ btn.addEventListener('click', () => {
134
+ hourGrid.querySelectorAll('.ds-timepicker__item').forEach(b => b.classList.remove('is-selected'));
135
+ btn.classList.add('is-selected');
136
+ this.selectedHour = i;
137
+ updateDisplay.call(this);
138
+ selectTimeAndClose.call(this);
139
+ });
140
+ hourGrid.appendChild(btn);
141
+ }
142
+
143
+ for (let i = 0; i < 60; i += 5) {
144
+ const btn = document.createElement('button');
145
+ btn.className = 'ds-timepicker__item';
146
+ btn.textContent = String(i).padStart(2, '0');
147
+ if (i === this.selectedMinute) btn.classList.add('is-selected');
148
+ btn.addEventListener('click', () => {
149
+ minuteGrid.querySelectorAll('.ds-timepicker__item').forEach(b => b.classList.remove('is-selected'));
150
+ btn.classList.add('is-selected');
151
+ this.selectedMinute = i;
152
+ updateDisplay.call(this);
153
+ selectTimeAndClose.call(this);
154
+ });
155
+ minuteGrid.appendChild(btn);
156
+ }
157
+
158
+ this.panelEl.addEventListener('click', (e) => {
159
+ e.stopPropagation();
160
+ });
161
+
162
+ updateDisplay.call(this);
163
+ updateInput.call(this);
164
+
165
+ setTimeout(() => {
166
+ this.calculatePosition();
167
+ }, 0);
168
+ }
169
+
170
+ hideTimepicker(e) {
171
+ if (this.panelEl && this.panelEl.style.display === 'block') {
172
+ if (!this.element.contains(e.target)) {
173
+ this.panelEl.style.display = 'none';
174
+ }
175
+ }
176
+ }
177
+
178
+ resizeHandler() {
179
+ if (this.panelEl && this.panelEl.style.display === 'block') {
180
+ this.calculatePosition();
181
+ }
182
+ }
183
+
184
+ destroy() {
185
+ if (!this.element.__kupolaInitialized) return;
186
+
187
+ if (this.inputWrap && this._inputWrapClickHandler) {
188
+ this.inputWrap.removeEventListener('click', this._inputWrapClickHandler);
189
+ }
190
+
191
+ if (this._documentClickListener && this._documentClickListener.unsubscribe) {
192
+ this._documentClickListener.unsubscribe();
193
+ } else if (this._documentClickHandler) {
194
+ document.removeEventListener('click', this._documentClickHandler);
195
+ }
196
+
197
+ if (this._resizeListener && this._resizeListener.unsubscribe) {
198
+ this._resizeListener.unsubscribe();
199
+ } else if (this._resizeHandler) {
200
+ window.removeEventListener('resize', this._resizeHandler);
201
+ }
202
+
203
+ if (this.panelEl) {
204
+ this.panelEl.remove();
205
+ this.panelEl = null;
206
+ }
207
+
208
+ this._inputWrapClickHandler = null;
209
+ this._documentClickHandler = null;
210
+ this._documentClickListener = null;
211
+ this._resizeHandler = null;
212
+ this._resizeListener = null;
213
+ this.element.__kupolaInitialized = false;
214
+ }
215
+ }
216
+
217
+ function initTimepicker(element) {
218
+ const picker = new Timepicker(element);
219
+ picker.init();
220
+ element._kupolaTimepicker = picker;
221
+ }
222
+
223
+ function initTimepickers(root = document) {
224
+ root.querySelectorAll('.ds-timepicker').forEach(picker => {
225
+ initTimepicker(picker);
226
+ });
227
+ }
228
+
229
+ function cleanupTimepicker(element) {
230
+ if (element._kupolaTimepicker) {
231
+ element._kupolaTimepicker.destroy();
232
+ element._kupolaTimepicker = null;
233
+ }
234
+ }
235
+
236
+ if (window.kupolaInitializer) {
237
+ window.kupolaInitializer.register('timepicker', initTimepicker, cleanupTimepicker);
238
+ }