@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
@@ -0,0 +1,203 @@
1
+ class GlobalEventManager {
2
+ constructor() {
3
+ this.listeners = new Map();
4
+ this.scopes = new Map();
5
+ }
6
+
7
+ on(target, eventType, handler, options = {}) {
8
+ const key = this._getKey(target, eventType);
9
+
10
+ if (!this.listeners.has(key)) {
11
+ this.listeners.set(key, []);
12
+ }
13
+
14
+ const wrapper = (e) => {
15
+ if (handler.before && typeof handler.before === 'function') {
16
+ handler.before(e);
17
+ }
18
+
19
+ const result = handler(e);
20
+
21
+ if (handler.after && typeof handler.after === 'function') {
22
+ handler.after(e, result);
23
+ }
24
+
25
+ return result;
26
+ };
27
+
28
+ const listener = {
29
+ handler,
30
+ wrapper,
31
+ options,
32
+ active: true
33
+ };
34
+
35
+ this.listeners.get(key).push(listener);
36
+
37
+ if (options.scope) {
38
+ if (!this.scopes.has(options.scope)) {
39
+ this.scopes.set(options.scope, []);
40
+ }
41
+ this.scopes.get(options.scope).push({ target, eventType, handler, wrapper });
42
+ }
43
+
44
+ target.addEventListener(eventType, wrapper, options);
45
+
46
+ const unsubscribe = () => {
47
+ target.removeEventListener(eventType, wrapper, options);
48
+ if (this.listeners.has(key)) {
49
+ const list = this.listeners.get(key);
50
+ const idx = list.indexOf(listener);
51
+ if (idx !== -1) {
52
+ list.splice(idx, 1);
53
+ }
54
+ }
55
+ if (options.scope && this.scopes.has(options.scope)) {
56
+ const scopeList = this.scopes.get(options.scope);
57
+ const idx = scopeList.findIndex(
58
+ s => s.target === target && s.eventType === eventType && s.handler === handler
59
+ );
60
+ if (idx !== -1) {
61
+ scopeList.splice(idx, 1);
62
+ }
63
+ }
64
+ listener.active = false;
65
+ };
66
+
67
+ listener.unsubscribe = unsubscribe;
68
+
69
+ return listener;
70
+ }
71
+
72
+ off(target, eventType, handler) {
73
+ const key = this._getKey(target, eventType);
74
+
75
+ if (!this.listeners.has(key)) return;
76
+
77
+ const listenerList = this.listeners.get(key);
78
+ const index = listenerList.findIndex(l => l.handler === handler);
79
+
80
+ if (index !== -1) {
81
+ const listener = listenerList[index];
82
+ target.removeEventListener(eventType, listener.wrapper, listener.options);
83
+ listener.active = false;
84
+ listenerList.splice(index, 1);
85
+
86
+ if (listener.options.scope) {
87
+ this._removeFromScope(listener.options.scope, target, eventType, handler);
88
+ }
89
+ }
90
+ }
91
+
92
+ once(target, eventType, handler, options = {}) {
93
+ const onceHandler = (e) => {
94
+ this.off(target, eventType, onceHandler);
95
+ return handler(e);
96
+ };
97
+
98
+ return this.on(target, eventType, onceHandler, options);
99
+ }
100
+
101
+ clearScope(scope) {
102
+ if (!this.scopes.has(scope)) return;
103
+
104
+ const scopeListeners = this.scopes.get(scope);
105
+
106
+ scopeListeners.forEach(({ target, eventType, handler, wrapper }) => {
107
+ target.removeEventListener(eventType, wrapper);
108
+
109
+ const key = this._getKey(target, eventType);
110
+ if (this.listeners.has(key)) {
111
+ const listenerList = this.listeners.get(key);
112
+ const index = listenerList.findIndex(l => l.handler === handler);
113
+ if (index !== -1) {
114
+ listenerList[index].active = false;
115
+ listenerList.splice(index, 1);
116
+ }
117
+ }
118
+ });
119
+
120
+ this.scopes.delete(scope);
121
+ }
122
+
123
+ _removeFromScope(scope, target, eventType, handler) {
124
+ if (!this.scopes.has(scope)) return;
125
+
126
+ const scopeListeners = this.scopes.get(scope);
127
+ const index = scopeListeners.findIndex(
128
+ l => l.target === target && l.eventType === eventType && l.handler === handler
129
+ );
130
+
131
+ if (index !== -1) {
132
+ scopeListeners.splice(index, 1);
133
+ }
134
+ }
135
+
136
+ _getKey(target, eventType) {
137
+ const targetName = target === document ? 'document' :
138
+ target === window ? 'window' :
139
+ target === document.body ? 'body' :
140
+ target.nodeName ? target.nodeName :
141
+ 'unknown';
142
+
143
+ return `${targetName}:${eventType}`;
144
+ }
145
+
146
+ getListenerCount(target, eventType) {
147
+ const key = this._getKey(target, eventType);
148
+ return this.listeners.has(key) ? this.listeners.get(key).length : 0;
149
+ }
150
+
151
+ getAllListenerCounts() {
152
+ const counts = {};
153
+ this.listeners.forEach((listeners, key) => {
154
+ counts[key] = listeners.filter(l => l.active).length;
155
+ });
156
+ return counts;
157
+ }
158
+
159
+ cleanup() {
160
+ this.listeners.forEach((listenerList, key) => {
161
+ const [targetName, eventType] = key.split(':');
162
+ let target;
163
+
164
+ if (targetName === 'document') target = document;
165
+ else if (targetName === 'window') target = window;
166
+ else if (targetName === 'body') target = document.body;
167
+
168
+ if (target) {
169
+ listenerList.forEach(listener => {
170
+ target.removeEventListener(eventType, listener.wrapper, listener.options);
171
+ });
172
+ }
173
+ });
174
+
175
+ this.listeners.clear();
176
+ this.scopes.clear();
177
+ }
178
+ }
179
+
180
+ const globalEvents = new GlobalEventManager();
181
+
182
+ function on(target, eventType, handler, options) {
183
+ return globalEvents.on(target, eventType, handler, options);
184
+ }
185
+
186
+ function off(target, eventType, handler) {
187
+ globalEvents.off(target, eventType, handler);
188
+ }
189
+
190
+ function once(target, eventType, handler, options) {
191
+ return globalEvents.once(target, eventType, handler, options);
192
+ }
193
+
194
+ function clearScope(scope) {
195
+ globalEvents.clearScope(scope);
196
+ }
197
+
198
+ window.GlobalEventManager = GlobalEventManager;
199
+ window.globalEvents = globalEvents;
200
+ window.on = on;
201
+ window.off = off;
202
+ window.once = once;
203
+ window.clearScope = clearScope;
package/js/heatmap.js ADDED
@@ -0,0 +1,405 @@
1
+ class Heatmap {
2
+ constructor(element, options = {}) {
3
+ this.element = element;
4
+ this.data = options.data || [];
5
+ this.startDate = options.startDate || this.getOneYearAgo();
6
+ this.endDate = options.endDate || new Date();
7
+ this.cellSize = options.cellSize || 14;
8
+ this.onCellClick = options.onCellClick || null;
9
+ this.tooltip = null;
10
+ this.baseColor = options.color || element.getAttribute('data-color') || '#22c55e';
11
+ this._listeners = [];
12
+
13
+ this.init();
14
+ }
15
+
16
+ getOneYearAgo() {
17
+ const date = new Date();
18
+ date.setFullYear(date.getFullYear() - 1);
19
+ return date;
20
+ }
21
+
22
+ init() {
23
+ this.render();
24
+ this.createTooltip();
25
+ }
26
+
27
+ getDataByDate(date) {
28
+ const dateStr = this.formatDate(date);
29
+ const item = this.data.find(d => d.date === dateStr);
30
+ return item ? item.value : 0;
31
+ }
32
+
33
+ formatDate(date) {
34
+ const year = date.getFullYear();
35
+ const month = String(date.getMonth() + 1).padStart(2, '0');
36
+ const day = String(date.getDate()).padStart(2, '0');
37
+ return `${year}-${month}-${day}`;
38
+ }
39
+
40
+ getLevel(value, maxValue) {
41
+ if (value === 0) return 0;
42
+ if (!maxValue || maxValue === 0) maxValue = Math.max(...this.data.map(d => d.value), 1);
43
+ const ratio = value / maxValue;
44
+ if (ratio < 0.2) return 1;
45
+ if (ratio < 0.4) return 2;
46
+ if (ratio < 0.6) return 3;
47
+ if (ratio < 0.8) return 4;
48
+ return 5;
49
+ }
50
+
51
+ hexToRgb(hex) {
52
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
53
+ return result ? {
54
+ r: parseInt(result[1], 16),
55
+ g: parseInt(result[2], 16),
56
+ b: parseInt(result[3], 16)
57
+ } : { r: 34, g: 197, b: 94 };
58
+ }
59
+
60
+ getCellColor(level) {
61
+ const rgb = this.hexToRgb(this.baseColor);
62
+ if (level === 0) return 'rgba(0, 0, 0, 0.1)';
63
+ const opacity = [0.2, 0.4, 0.6, 0.8, 1][level - 1];
64
+ return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${opacity})`;
65
+ }
66
+
67
+ getWeekdayLabels() {
68
+ return ['', '一', '', '三', '', '五', ''];
69
+ }
70
+
71
+ getMonthLabels() {
72
+ const months = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'];
73
+ const result = [];
74
+ let currentMonth = -1;
75
+
76
+ for (let d = new Date(this.startDate); d <= this.endDate; d.setDate(d.getDate() + 1)) {
77
+ const month = d.getMonth();
78
+ const day = d.getDate();
79
+ if (month !== currentMonth && day === 1) {
80
+ result.push({ month, label: months[month], offset: Math.floor((d - this.startDate) / (1000 * 60 * 60 * 24)) });
81
+ currentMonth = month;
82
+ }
83
+ }
84
+
85
+ return result;
86
+ }
87
+
88
+ getWeekCount() {
89
+ let weeks = 0;
90
+ let firstDate = new Date(this.startDate);
91
+ const firstDayOfWeek = firstDate.getDay();
92
+
93
+ for (let d = new Date(this.startDate); d <= this.endDate; d.setDate(d.getDate() + 1)) {
94
+ if (d.getDay() === 0) {
95
+ weeks++;
96
+ }
97
+ }
98
+
99
+ if (firstDayOfWeek !== 0) {
100
+ weeks++;
101
+ }
102
+
103
+ return weeks;
104
+ }
105
+
106
+ render() {
107
+ const body = this.element.querySelector('.ds-heatmap__body');
108
+ if (!body) return;
109
+
110
+ body.innerHTML = '';
111
+
112
+ const weeks = [];
113
+ let currentWeek = [];
114
+
115
+ let firstDate = new Date(this.startDate);
116
+ const firstDayOfWeek = firstDate.getDay();
117
+ for (let i = 1; i < firstDayOfWeek; i++) {
118
+ currentWeek.push(null);
119
+ }
120
+
121
+ for (let d = new Date(this.startDate); d <= this.endDate; d.setDate(d.getDate() + 1)) {
122
+ currentWeek.push(new Date(d));
123
+ if (d.getDay() === 6 || d.getTime() === this.endDate.getTime()) {
124
+ weeks.push(currentWeek);
125
+ currentWeek = [];
126
+ }
127
+ }
128
+
129
+ const weekCount = weeks.length;
130
+ const cellWidth = this.element.classList.contains('ds-heatmap--compact') ? 12 : 16;
131
+ const totalGridWidth = weekCount * cellWidth;
132
+
133
+ const container = document.createElement('div');
134
+ container.className = 'ds-heatmap__container';
135
+
136
+ const labelsAndGrid = document.createElement('div');
137
+ labelsAndGrid.className = 'ds-heatmap__labels-and-grid';
138
+
139
+ const weekdayLabels = document.createElement('div');
140
+ weekdayLabels.className = 'ds-heatmap__weekday-labels';
141
+ const weekdayLabelHeight = this.element.classList.contains('ds-heatmap--compact') ? 12 : 16;
142
+ this.getWeekdayLabels().forEach(label => {
143
+ const el = document.createElement('div');
144
+ el.className = 'ds-heatmap__weekday-label';
145
+ el.textContent = label;
146
+ el.style.height = weekdayLabelHeight + 'px';
147
+ el.style.lineHeight = weekdayLabelHeight + 'px';
148
+ weekdayLabels.appendChild(el);
149
+ });
150
+ labelsAndGrid.appendChild(weekdayLabels);
151
+
152
+ const gridContainer = document.createElement('div');
153
+ gridContainer.className = 'ds-heatmap__grid-container';
154
+
155
+ const monthLabels = document.createElement('div');
156
+ monthLabels.className = 'ds-heatmap__month-labels';
157
+ monthLabels.style.width = totalGridWidth + 'px';
158
+
159
+ const monthInfo = this.getMonthLabels();
160
+ monthInfo.forEach((item, index) => {
161
+ const el = document.createElement('div');
162
+ el.className = 'ds-heatmap__month-label';
163
+ el.textContent = item.label;
164
+
165
+ const nextMonth = monthInfo[index + 1];
166
+ let weekSpan;
167
+ if (nextMonth) {
168
+ weekSpan = Math.ceil((nextMonth.offset - item.offset) / 7);
169
+ } else {
170
+ weekSpan = weekCount - Math.floor(item.offset / 7);
171
+ }
172
+
173
+ el.style.width = (weekSpan * cellWidth) + 'px';
174
+ monthLabels.appendChild(el);
175
+ });
176
+ gridContainer.appendChild(monthLabels);
177
+
178
+ const grid = document.createElement('div');
179
+ grid.className = 'ds-heatmap__grid';
180
+
181
+ weeks.forEach((week) => {
182
+ const weekColumn = document.createElement('div');
183
+ weekColumn.className = 'ds-heatmap__week-column';
184
+
185
+ week.forEach(date => {
186
+ if (date === null) {
187
+ const emptyCell = document.createElement('div');
188
+ emptyCell.className = 'ds-heatmap__cell';
189
+ emptyCell.style.visibility = 'hidden';
190
+ weekColumn.appendChild(emptyCell);
191
+ } else {
192
+ const value = this.getDataByDate(date);
193
+ const maxValue = Math.max(...this.data.map(d => d.value), 1);
194
+ const level = this.getLevel(value, maxValue);
195
+
196
+ const cell = document.createElement('div');
197
+ cell.className = 'ds-heatmap__cell';
198
+ cell.dataset.date = this.formatDate(date);
199
+ cell.dataset.value = value;
200
+ cell.style.backgroundColor = this.getCellColor(level);
201
+
202
+ const mouseenterHandler = (e) => this.showTooltip(e, date, value);
203
+ const mouseleaveHandler = () => this.hideTooltip();
204
+ const clickHandler = () => {
205
+ if (this.onCellClick) {
206
+ this.onCellClick({ date: this.formatDate(date), value });
207
+ }
208
+ };
209
+
210
+ cell.addEventListener('mouseenter', mouseenterHandler);
211
+ cell.addEventListener('mouseleave', mouseleaveHandler);
212
+ cell.addEventListener('click', clickHandler);
213
+
214
+ this._listeners.push(
215
+ { el: cell, event: 'mouseenter', handler: mouseenterHandler },
216
+ { el: cell, event: 'mouseleave', handler: mouseleaveHandler },
217
+ { el: cell, event: 'click', handler: clickHandler }
218
+ );
219
+
220
+ weekColumn.appendChild(cell);
221
+ }
222
+ });
223
+
224
+ grid.appendChild(weekColumn);
225
+ });
226
+
227
+ gridContainer.appendChild(grid);
228
+ labelsAndGrid.appendChild(gridContainer);
229
+ container.appendChild(labelsAndGrid);
230
+
231
+ body.appendChild(container);
232
+ this.renderLegend(body);
233
+ }
234
+
235
+ renderLegend(parent) {
236
+ const legend = document.createElement('div');
237
+ legend.className = 'ds-heatmap__legend';
238
+
239
+ const label = document.createElement('span');
240
+ label.className = 'ds-heatmap__legend-label';
241
+ label.textContent = '少';
242
+
243
+ const cells = document.createElement('div');
244
+ cells.className = 'ds-heatmap__legend-cells';
245
+
246
+ for (let i = 0; i <= 5; i++) {
247
+ const cell = document.createElement('div');
248
+ cell.className = 'ds-heatmap__legend-cell';
249
+ cell.style.backgroundColor = this.getCellColor(i);
250
+ cells.appendChild(cell);
251
+ }
252
+
253
+ const label2 = document.createElement('span');
254
+ label2.className = 'ds-heatmap__legend-label';
255
+ label2.textContent = '多';
256
+
257
+ legend.appendChild(label);
258
+ legend.appendChild(cells);
259
+ legend.appendChild(label2);
260
+ parent.appendChild(legend);
261
+ }
262
+
263
+ createTooltip() {
264
+ this.tooltip = document.createElement('div');
265
+ this.tooltip.className = 'ds-heatmap__tooltip';
266
+ document.body.appendChild(this.tooltip);
267
+ }
268
+
269
+ showTooltip(e, date, value) {
270
+ const rect = e.target.getBoundingClientRect();
271
+ const tooltipWidth = 150;
272
+
273
+ this.tooltip.innerHTML = `
274
+ <div class="ds-heatmap__tooltip-date">${date.getFullYear()}年${date.getMonth() + 1}月${date.getDate()}日</div>
275
+ <div class="ds-heatmap__tooltip-value">${value} contributions</div>
276
+ `;
277
+
278
+ this.tooltip.style.left = Math.min(rect.left + rect.width / 2 - tooltipWidth / 2, window.innerWidth - tooltipWidth - 16) + 'px';
279
+ this.tooltip.style.top = (rect.top - 50) + 'px';
280
+ this.tooltip.classList.add('is-visible');
281
+ }
282
+
283
+ hideTooltip() {
284
+ this.tooltip.classList.remove('is-visible');
285
+ }
286
+
287
+ updateData(data) {
288
+ this._listeners.forEach(({ el, event, handler }) => {
289
+ el.removeEventListener(event, handler);
290
+ });
291
+ this._listeners = [];
292
+ this.data = data;
293
+ this.render();
294
+ }
295
+
296
+ setDateRange(startDate, endDate) {
297
+ this.startDate = startDate;
298
+ this.endDate = endDate;
299
+ this.render();
300
+ }
301
+
302
+ destroy() {
303
+ this._listeners.forEach(({ el, event, handler }) => {
304
+ el.removeEventListener(event, handler);
305
+ });
306
+ this._listeners = null;
307
+
308
+ if (this.tooltip && this.tooltip.parentNode) {
309
+ this.tooltip.parentNode.removeChild(this.tooltip);
310
+ }
311
+ this.tooltip = null;
312
+ this.data = [];
313
+ this.element = null;
314
+ }
315
+ }
316
+
317
+ function initHeatmap(element) {
318
+ if (element.__kupolaInitialized) return;
319
+
320
+ const dataAttr = element.getAttribute('data-heatmap-data');
321
+ let data = [];
322
+
323
+ if (dataAttr) {
324
+ try {
325
+ data = JSON.parse(dataAttr);
326
+ } catch (e) {
327
+ data = generateMockHeatmapData();
328
+ }
329
+ } else {
330
+ data = generateMockHeatmapData();
331
+ }
332
+
333
+ const instance = new Heatmap(element, {
334
+ data: data,
335
+ onCellClick: (info) => {
336
+ }
337
+ });
338
+
339
+ element.__kupolaInstance = instance;
340
+ element.__kupolaInitialized = true;
341
+ }
342
+
343
+ function cleanupHeatmap(element) {
344
+ if (!element.__kupolaInitialized || !element.__kupolaInstance) return;
345
+
346
+ const instance = element.__kupolaInstance;
347
+ instance.destroy();
348
+
349
+ element.__kupolaInstance = null;
350
+ element.__kupolaInitialized = false;
351
+ }
352
+
353
+ function initHeatmaps() {
354
+ document.querySelectorAll('.ds-heatmap').forEach(element => {
355
+ initHeatmap(element);
356
+ });
357
+ }
358
+
359
+ function generateMockHeatmapData() {
360
+ const data = [];
361
+ const today = new Date();
362
+ const oneYearAgo = new Date();
363
+ oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1);
364
+
365
+ for (let d = new Date(oneYearAgo); d <= today; d.setDate(d.getDate() + 1)) {
366
+ const year = d.getFullYear();
367
+ const month = String(d.getMonth() + 1).padStart(2, '0');
368
+ const day = String(d.getDate()).padStart(2, '0');
369
+
370
+ const isWeekend = d.getDay() === 0 || d.getDay() === 6;
371
+ const baseValue = isWeekend ? Math.random() * 20 : Math.random() * 50;
372
+ const value = Math.floor(baseValue);
373
+
374
+ data.push({
375
+ date: `${year}-${month}-${day}`,
376
+ value: value > 0 ? value : Math.floor(Math.random() * 30) + 1
377
+ });
378
+ }
379
+
380
+ return data;
381
+ }
382
+
383
+ function createHeatmap(selector, options = {}) {
384
+ const element = document.querySelector(selector);
385
+ if (element) {
386
+ const data = options.data || generateMockHeatmapData();
387
+ return new Heatmap(element, {
388
+ data: data,
389
+ ...options
390
+ });
391
+ }
392
+ return null;
393
+ }
394
+
395
+ if (typeof module !== 'undefined' && module.exports) {
396
+ module.exports = { Heatmap, initHeatmaps, initHeatmap, cleanupHeatmap, createHeatmap, generateMockHeatmapData };
397
+ } else {
398
+ window.Heatmap = Heatmap;
399
+ window.initHeatmap = initHeatmap;
400
+ window.cleanupHeatmap = cleanupHeatmap;
401
+ }
402
+
403
+ if (window.kupolaInitializer) {
404
+ window.kupolaInitializer.register('heatmap', initHeatmap, cleanupHeatmap);
405
+ }