@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,593 @@
1
+ class VirtualList {
2
+ constructor(element, options = {}) {
3
+ this.element = element;
4
+ this.data = options.data || [];
5
+ this.itemHeight = options.itemHeight || 48;
6
+ this.itemWidth = options.itemWidth || 200;
7
+ this.bufferSize = options.bufferSize || 5;
8
+ this.renderItem = options.renderItem || this.defaultRenderItem;
9
+ this.onItemClick = options.onItemClick || null;
10
+ this.onItemSelect = options.onItemSelect || null;
11
+ this.onScroll = options.onScroll || null;
12
+ this.onScrollEnd = options.onScrollEnd || null;
13
+ this.selectedKey = options.selectedKey || null;
14
+ this.keyField = options.keyField || 'id';
15
+ this.useDynamicHeight = options.useDynamicHeight || false;
16
+ this.dynamicHeightCache = new Map();
17
+ this.estimatedHeight = options.estimatedHeight || 48;
18
+
19
+ this.container = null;
20
+ this.scrollbarTrack = null;
21
+ this.scrollbarThumb = null;
22
+ this.totalHeight = 0;
23
+ this.startIndex = 0;
24
+ this.endIndex = 0;
25
+ this.isScrolling = false;
26
+ this.scrollTimeout = null;
27
+ this.lastScrollTop = 0;
28
+ this.lastScrollLeft = 0;
29
+
30
+ this.init();
31
+ }
32
+
33
+ defaultRenderItem(item, index) {
34
+ return `
35
+ <div class="ds-virtual-list__item-content">
36
+ <div class="ds-virtual-list__item-icon">
37
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
38
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
39
+ <polyline points="14 2 14 8 20 8"/>
40
+ <line x1="16" y1="13" x2="8" y2="13"/>
41
+ <line x1="16" y1="17" x2="8" y2="17"/>
42
+ <polyline points="10 9 9 9 8 9"/>
43
+ </svg>
44
+ </div>
45
+ <div>
46
+ <div class="ds-virtual-list__item-title">${item.title || item.name || `Item ${index + 1}`}</div>
47
+ <div class="ds-virtual-list__item-subtitle">${item.subtitle || 'Subtitle'}</div>
48
+ </div>
49
+ </div>
50
+ `;
51
+ }
52
+
53
+ init() {
54
+ this.createStructure();
55
+ this.update();
56
+ this.bindEvents();
57
+ }
58
+
59
+ createStructure() {
60
+ this.element.innerHTML = `
61
+ <div class="ds-virtual-list__scrollbar">
62
+ <div class="ds-virtual-list__scrollbar-track">
63
+ <div class="ds-virtual-list__scrollbar-thumb"></div>
64
+ </div>
65
+ </div>
66
+ <div class="ds-virtual-list__container"></div>
67
+ `;
68
+
69
+ this.container = this.element.querySelector('.ds-virtual-list__container');
70
+ this.scrollbarThumb = this.element.querySelector('.ds-virtual-list__scrollbar-thumb');
71
+ }
72
+
73
+ bindEvents() {
74
+ this._scrollHandler = (e) => this.handleScroll(e);
75
+ this._thumbDragStartHandler = (e) => this.handleThumbDragStart(e);
76
+ this._thumbDragMoveHandler = (e) => this.handleThumbDragMove(e);
77
+ this._thumbDragEndHandler = () => this.handleThumbDragEnd();
78
+ this._wheelHandler = (e) => {
79
+ e.preventDefault();
80
+
81
+ const isHorizontal = this.element.classList.contains('ds-virtual-list--horizontal');
82
+
83
+ if (isHorizontal) {
84
+ this.element.scrollLeft += e.deltaX + e.deltaY;
85
+ } else {
86
+ this.element.scrollTop += e.deltaY + e.deltaX;
87
+ }
88
+ };
89
+
90
+ this.element.addEventListener('scroll', this._scrollHandler);
91
+ this.scrollbarThumb.addEventListener('mousedown', this._thumbDragStartHandler);
92
+
93
+ document.addEventListener('mousemove', this._thumbDragMoveHandler);
94
+ document.addEventListener('mouseup', this._thumbDragEndHandler);
95
+
96
+ this.element.addEventListener('wheel', this._wheelHandler, { passive: false });
97
+
98
+ this._listeners = [
99
+ { el: this.element, event: 'scroll', handler: this._scrollHandler },
100
+ { el: this.scrollbarThumb, event: 'mousedown', handler: this._thumbDragStartHandler },
101
+ { el: document, event: 'mousemove', handler: this._thumbDragMoveHandler },
102
+ { el: document, event: 'mouseup', handler: this._thumbDragEndHandler },
103
+ { el: this.element, event: 'wheel', handler: this._wheelHandler }
104
+ ];
105
+ }
106
+
107
+ handleScroll(e) {
108
+ const isHorizontal = this.element.classList.contains('ds-virtual-list--horizontal');
109
+ const scrollOffset = isHorizontal ? this.element.scrollLeft : this.element.scrollTop;
110
+
111
+ if (this.onScroll) {
112
+ this.onScroll({
113
+ scrollOffset,
114
+ isHorizontal,
115
+ dataLength: this.data.length,
116
+ startIndex: this.startIndex,
117
+ endIndex: this.endIndex
118
+ });
119
+ }
120
+
121
+ this.updateScrollState();
122
+ this.renderVisibleItems();
123
+ this.updateScrollbar();
124
+ }
125
+
126
+ updateScrollState() {
127
+ this.isScrolling = true;
128
+ this.element.classList.add('ds-virtual-list--scrolling');
129
+
130
+ if (this.scrollTimeout) {
131
+ clearTimeout(this.scrollTimeout);
132
+ }
133
+
134
+ this.scrollTimeout = setTimeout(() => {
135
+ this.isScrolling = false;
136
+ this.element.classList.remove('ds-virtual-list--scrolling');
137
+
138
+ if (this.onScrollEnd) {
139
+ const isHorizontal = this.element.classList.contains('ds-virtual-list--horizontal');
140
+ const scrollOffset = isHorizontal ? this.element.scrollLeft : this.element.scrollTop;
141
+ this.onScrollEnd({
142
+ scrollOffset,
143
+ isHorizontal,
144
+ dataLength: this.data.length,
145
+ startIndex: this.startIndex,
146
+ endIndex: this.endIndex
147
+ });
148
+ }
149
+ }, 200);
150
+ }
151
+
152
+ getItemSize(index) {
153
+ const isHorizontal = this.element.classList.contains('ds-virtual-list--horizontal');
154
+
155
+ if (this.useDynamicHeight) {
156
+ const key = this.data[index][this.keyField] || index;
157
+ if (this.dynamicHeightCache.has(key)) {
158
+ return this.dynamicHeightCache.get(key);
159
+ }
160
+ }
161
+
162
+ return isHorizontal ? this.itemWidth : this.itemHeight;
163
+ }
164
+
165
+ getItemPositions() {
166
+ const positions = [];
167
+ let currentPosition = 0;
168
+
169
+ this.data.forEach((item, index) => {
170
+ const size = this.getItemSize(index);
171
+ positions.push({
172
+ start: currentPosition,
173
+ end: currentPosition + size,
174
+ size
175
+ });
176
+ currentPosition += size;
177
+ });
178
+
179
+ return positions;
180
+ }
181
+
182
+ getTotalSize() {
183
+ if (this.useDynamicHeight) {
184
+ return this.data.reduce((total, item, index) => {
185
+ return total + this.getItemSize(index);
186
+ }, 0);
187
+ }
188
+
189
+ const isHorizontal = this.element.classList.contains('ds-virtual-list--horizontal');
190
+ const itemSize = isHorizontal ? this.itemWidth : this.itemHeight;
191
+ return this.data.length * itemSize;
192
+ }
193
+
194
+ getIndexAtOffset(offset) {
195
+ if (this.useDynamicHeight) {
196
+ const positions = this.getItemPositions();
197
+ for (let i = 0; i < positions.length; i++) {
198
+ if (offset >= positions[i].start && offset < positions[i].end) {
199
+ return i;
200
+ }
201
+ }
202
+ return this.data.length - 1;
203
+ }
204
+
205
+ const isHorizontal = this.element.classList.contains('ds-virtual-list--horizontal');
206
+ const itemSize = isHorizontal ? this.itemWidth : this.itemHeight;
207
+ return Math.floor(offset / itemSize);
208
+ }
209
+
210
+ renderVisibleItems() {
211
+ const isHorizontal = this.element.classList.contains('ds-virtual-list--horizontal');
212
+ const scrollOffset = isHorizontal ? this.element.scrollLeft : this.element.scrollTop;
213
+ const viewportSize = isHorizontal ? this.element.clientWidth : this.element.clientHeight;
214
+
215
+ const startIndex = Math.max(0, this.getIndexAtOffset(scrollOffset) - this.bufferSize);
216
+ let endIndex = Math.min(
217
+ this.data.length - 1,
218
+ this.getIndexAtOffset(scrollOffset + viewportSize) + this.bufferSize
219
+ );
220
+
221
+ if (endIndex < startIndex) endIndex = startIndex;
222
+
223
+ this.startIndex = startIndex;
224
+ this.endIndex = endIndex;
225
+
226
+ const visibleData = this.data.slice(startIndex, endIndex + 1);
227
+
228
+ let html = '';
229
+ let currentPosition = 0;
230
+
231
+ if (this.useDynamicHeight) {
232
+ const positions = this.getItemPositions();
233
+ currentPosition = positions[startIndex]?.start || 0;
234
+ } else {
235
+ const itemSize = isHorizontal ? this.itemWidth : this.itemHeight;
236
+ currentPosition = startIndex * itemSize;
237
+ }
238
+
239
+ visibleData.forEach((item, i) => {
240
+ const actualIndex = startIndex + i;
241
+ const key = item[this.keyField] || actualIndex;
242
+ const isSelected = this.selectedKey === key;
243
+ const itemSize = this.getItemSize(actualIndex);
244
+ const position = currentPosition;
245
+
246
+ if (isHorizontal) {
247
+ html += `
248
+ <div
249
+ class="ds-virtual-list__item${isSelected ? ' is-selected' : ''}"
250
+ style="position: absolute; top: 0; left: ${position}px; width: ${itemSize}px; height: 100%;"
251
+ data-index="${actualIndex}"
252
+ data-key="${key}"
253
+ >
254
+ ${this.renderItem(item, actualIndex)}
255
+ </div>
256
+ `;
257
+ } else {
258
+ html += `
259
+ <div
260
+ class="ds-virtual-list__item${isSelected ? ' is-selected' : ''}"
261
+ style="position: absolute; top: ${position}px; left: 0; right: 0; height: ${itemSize}px;"
262
+ data-index="${actualIndex}"
263
+ data-key="${key}"
264
+ >
265
+ ${this.renderItem(item, actualIndex)}
266
+ </div>
267
+ `;
268
+ }
269
+
270
+ currentPosition += itemSize;
271
+ });
272
+
273
+ this.container.innerHTML = html;
274
+
275
+ if (this.useDynamicHeight) {
276
+ this.updateDynamicHeights();
277
+ }
278
+
279
+ this.container.querySelectorAll('.ds-virtual-list__item').forEach(item => {
280
+ item.addEventListener('click', () => this.handleItemClick(item));
281
+ });
282
+ }
283
+
284
+ updateDynamicHeights() {
285
+ if (this.isUpdating) return;
286
+
287
+ let hasChanges = false;
288
+ this.container.querySelectorAll('.ds-virtual-list__item').forEach(item => {
289
+ const index = parseInt(item.dataset.index);
290
+ const key = this.data[index][this.keyField] || index;
291
+ const actualHeight = item.offsetHeight;
292
+
293
+ if (actualHeight !== this.getItemSize(index)) {
294
+ this.dynamicHeightCache.set(key, actualHeight);
295
+ hasChanges = true;
296
+ }
297
+ });
298
+
299
+ if (hasChanges) {
300
+ this.isUpdating = true;
301
+ this.update();
302
+ this.isUpdating = false;
303
+ }
304
+ }
305
+
306
+ handleItemClick(item) {
307
+ const index = parseInt(item.dataset.index);
308
+ const key = item.dataset.key;
309
+ const dataItem = this.data[index];
310
+
311
+ if (this.onItemClick) {
312
+ this.onItemClick({ item: dataItem, index, key });
313
+ }
314
+
315
+ if (this.onItemSelect) {
316
+ this.select(key);
317
+ }
318
+ }
319
+
320
+ select(key) {
321
+ this.selectedKey = key;
322
+
323
+ if (this.onItemSelect) {
324
+ const index = this.data.findIndex(item => item[this.keyField] === key);
325
+ if (index !== -1) {
326
+ this.onItemSelect({ item: this.data[index], index, key });
327
+ }
328
+ }
329
+
330
+ this.renderVisibleItems();
331
+ }
332
+
333
+ updateScrollbar() {
334
+ const isHorizontal = this.element.classList.contains('ds-virtual-list--horizontal');
335
+ const totalSize = this.getTotalSize();
336
+ const viewportSize = isHorizontal ? this.element.clientWidth : this.element.clientHeight;
337
+ const scrollOffset = isHorizontal ? this.element.scrollLeft : this.element.scrollTop;
338
+
339
+ if (isHorizontal) {
340
+ this.scrollbarThumb.style.display = 'none';
341
+ return;
342
+ }
343
+
344
+ const thumbHeight = Math.max(20, (viewportSize / totalSize) * viewportSize);
345
+ const maxTop = viewportSize - thumbHeight;
346
+ const thumbTop = (scrollOffset / (totalSize - viewportSize || 1)) * maxTop;
347
+
348
+ this.scrollbarThumb.style.height = thumbHeight + 'px';
349
+ this.scrollbarThumb.style.top = thumbTop + 'px';
350
+ }
351
+
352
+ handleThumbDragStart(e) {
353
+ e.preventDefault();
354
+ this.isDragging = true;
355
+ this.dragStartY = e.clientY;
356
+ this.dragStartTop = parseFloat(this.scrollbarThumb.style.top) || 0;
357
+ }
358
+
359
+ handleThumbDragMove(e) {
360
+ if (!this.isDragging) return;
361
+
362
+ const viewportHeight = this.element.clientHeight;
363
+ const totalHeight = this.getTotalSize();
364
+ const thumbHeight = parseFloat(this.scrollbarThumb.style.height) || viewportHeight;
365
+ const maxTop = viewportHeight - thumbHeight;
366
+
367
+ const deltaY = e.clientY - this.dragStartY;
368
+ let newTop = this.dragStartTop + deltaY;
369
+ newTop = Math.max(0, Math.min(newTop, maxTop));
370
+
371
+ this.scrollbarThumb.style.top = newTop + 'px';
372
+
373
+ const scrollTop = (newTop / maxTop) * (totalHeight - viewportHeight || 0);
374
+ this.element.scrollTop = scrollTop;
375
+ }
376
+
377
+ handleThumbDragEnd() {
378
+ this.isDragging = false;
379
+ }
380
+
381
+ update() {
382
+ const isHorizontal = this.element.classList.contains('ds-virtual-list--horizontal');
383
+ const totalSize = this.getTotalSize();
384
+
385
+ if (isHorizontal) {
386
+ this.container.style.width = totalSize + 'px';
387
+ this.container.style.height = '100%';
388
+ } else {
389
+ this.container.style.height = totalSize + 'px';
390
+ this.container.style.width = '100%';
391
+ }
392
+
393
+ this.renderVisibleItems();
394
+ this.updateScrollbar();
395
+ }
396
+
397
+ setData(data) {
398
+ this.data = data;
399
+ if (this.useDynamicHeight) {
400
+ this.dynamicHeightCache.clear();
401
+ }
402
+ this.update();
403
+ }
404
+
405
+ addItem(item) {
406
+ this.data.push(item);
407
+ this.update();
408
+ }
409
+
410
+ removeItem(index) {
411
+ this.data.splice(index, 1);
412
+ if (this.useDynamicHeight) {
413
+ this.dynamicHeightCache.clear();
414
+ }
415
+ this.update();
416
+ }
417
+
418
+ insertItem(index, item) {
419
+ this.data.splice(index, 0, item);
420
+ if (this.useDynamicHeight) {
421
+ this.dynamicHeightCache.clear();
422
+ }
423
+ this.update();
424
+ }
425
+
426
+ scrollTo(index, behavior = 'smooth') {
427
+ const isHorizontal = this.element.classList.contains('ds-virtual-list--horizontal');
428
+
429
+ let scrollOffset = 0;
430
+ if (this.useDynamicHeight) {
431
+ const positions = this.getItemPositions();
432
+ scrollOffset = positions[index]?.start || 0;
433
+ } else {
434
+ const itemSize = isHorizontal ? this.itemWidth : this.itemHeight;
435
+ scrollOffset = index * itemSize;
436
+ }
437
+
438
+ if (isHorizontal) {
439
+ this.element.scrollTo({ left: scrollOffset, behavior });
440
+ } else {
441
+ this.element.scrollTo({ top: scrollOffset, behavior });
442
+ }
443
+ }
444
+
445
+ scrollToKey(key, behavior = 'smooth') {
446
+ const index = this.data.findIndex(item => item[this.keyField] === key);
447
+ if (index !== -1) {
448
+ this.scrollTo(index, behavior);
449
+ }
450
+ }
451
+
452
+ scrollToTop(behavior = 'smooth') {
453
+ const isHorizontal = this.element.classList.contains('ds-virtual-list--horizontal');
454
+ this.element.scrollTo({ [isHorizontal ? 'left' : 'top']: 0, behavior });
455
+ }
456
+
457
+ scrollToBottom(behavior = 'smooth') {
458
+ const isHorizontal = this.element.classList.contains('ds-virtual-list--horizontal');
459
+ const totalSize = this.getTotalSize();
460
+ const viewportSize = isHorizontal ? this.element.clientWidth : this.element.clientHeight;
461
+ this.element.scrollTo({ [isHorizontal ? 'left' : 'top']: totalSize - viewportSize, behavior });
462
+ }
463
+
464
+ getVisibleItems() {
465
+ return this.data.slice(this.startIndex, this.endIndex + 1).map((item, i) => ({
466
+ item,
467
+ index: this.startIndex + i,
468
+ key: item[this.keyField] || this.startIndex + i
469
+ }));
470
+ }
471
+
472
+ getItemIndex(key) {
473
+ return this.data.findIndex(item => item[this.keyField] === key);
474
+ }
475
+
476
+ getItem(key) {
477
+ const index = this.getItemIndex(key);
478
+ return index !== -1 ? this.data[index] : null;
479
+ }
480
+
481
+ refreshCache() {
482
+ this.dynamicHeightCache.clear();
483
+ this.update();
484
+ }
485
+
486
+ destroy() {
487
+ if (this.scrollTimeout) {
488
+ clearTimeout(this.scrollTimeout);
489
+ }
490
+
491
+ this._listeners?.forEach(({ el, event, handler }) => {
492
+ el.removeEventListener(event, handler);
493
+ });
494
+
495
+ this.data = [];
496
+ this.dynamicHeightCache.clear();
497
+ this.container.innerHTML = '';
498
+
499
+ this._listeners = null;
500
+ this._scrollHandler = null;
501
+ this._thumbDragStartHandler = null;
502
+ this._thumbDragMoveHandler = null;
503
+ this._thumbDragEndHandler = null;
504
+ this._wheelHandler = null;
505
+ this.container = null;
506
+ this.scrollbarThumb = null;
507
+ this.element = null;
508
+ }
509
+ }
510
+
511
+ function generateMockVirtualListData(count = 1000) {
512
+ const data = [];
513
+ const titles = ['Document', 'Image', 'Video', 'Folder', 'Archive', 'Spreadsheet', 'Presentation', 'Code'];
514
+
515
+ for (let i = 1; i <= count; i++) {
516
+ const titleIndex = Math.floor(Math.random() * titles.length);
517
+ const randomNum = Math.floor(Math.random() * 10000);
518
+
519
+ data.push({
520
+ id: i,
521
+ title: `${titles[titleIndex]} ${randomNum}`,
522
+ subtitle: `Last modified ${Math.floor(Math.random() * 30)} days ago`,
523
+ type: titles[titleIndex].toLowerCase()
524
+ });
525
+ }
526
+
527
+ return data;
528
+ }
529
+
530
+ function createVirtualList(selector, options = {}) {
531
+ const element = document.querySelector(selector);
532
+ if (element) {
533
+ const data = options.data || generateMockVirtualListData(options.count || 1000);
534
+ return new VirtualList(element, {
535
+ data: data,
536
+ ...options
537
+ });
538
+ }
539
+ return null;
540
+ }
541
+
542
+ function initVirtualList(element) {
543
+ if (element.__kupolaInitialized) return;
544
+
545
+ const dataAttr = element.getAttribute('data-virtual-list');
546
+ let data = [];
547
+
548
+ if (dataAttr) {
549
+ try {
550
+ data = JSON.parse(dataAttr);
551
+ } catch (e) {
552
+ data = generateMockVirtualListData(1000);
553
+ }
554
+ } else {
555
+ data = generateMockVirtualListData(1000);
556
+ }
557
+
558
+ const instance = new VirtualList(element, {
559
+ data: data,
560
+ onItemClick: (info) => {},
561
+ onItemSelect: (info) => {}
562
+ });
563
+
564
+ element.__kupolaInstance = instance;
565
+ element.__kupolaInitialized = true;
566
+ }
567
+
568
+ function cleanupVirtualList(element) {
569
+ if (!element.__kupolaInitialized || !element.__kupolaInstance) return;
570
+
571
+ const instance = element.__kupolaInstance;
572
+ instance.destroy();
573
+
574
+ element.__kupolaInstance = null;
575
+ element.__kupolaInitialized = false;
576
+ }
577
+
578
+ function initVirtualLists() {
579
+ document.querySelectorAll('.ds-virtual-list').forEach(element => {
580
+ initVirtualList(element);
581
+ });
582
+ }
583
+
584
+ if (typeof module !== 'undefined' && module.exports) {
585
+ module.exports = { VirtualList, initVirtualLists, initVirtualList, cleanupVirtualList, createVirtualList, generateMockVirtualListData };
586
+ } else {
587
+ window.initVirtualList = initVirtualList;
588
+ window.cleanupVirtualList = cleanupVirtualList;
589
+ }
590
+
591
+ if (window.kupolaInitializer) {
592
+ window.kupolaInitializer.register('virtual-list', initVirtualList, cleanupVirtualList);
593
+ }
package/package.json ADDED
@@ -0,0 +1,105 @@
1
+ {
2
+ "name": "@kupola/kupola",
3
+ "version": "1.2.0",
4
+ "description": "Lightweight UI toolkit for any web project. No heavy frontend frameworks required. Works seamlessly with Flask, Django, Gin, Spring Boot, ASP.NET Core, Express, Ruby on Rails, and any backend framework that outputs HTML.",
5
+ "type": "module",
6
+ "main": "dist/kupola.cjs.js",
7
+ "module": "dist/kupola.esm.js",
8
+ "types": "dist/types/kupola.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/kupola.esm.js",
12
+ "require": "./dist/kupola.cjs.js",
13
+ "types": "./dist/types/kupola.d.ts"
14
+ },
15
+ "./css": {
16
+ "import": "./dist/css/kupola.css",
17
+ "require": "./dist/css/kupola.css"
18
+ },
19
+ "./icons": "./icons/",
20
+ "./js/": "./js/",
21
+ "./css/": "./css/"
22
+ },
23
+ "scripts": {
24
+ "build": "vite build",
25
+ "prepublishOnly": "npm run build",
26
+ "build:rollup": "rollup -c",
27
+ "watch:rollup": "rollup -c -w",
28
+ "watch:vite": "vite build --watch",
29
+ "dev": "vite",
30
+ "start": "npx http-server -p 8080",
31
+ "test": "jest",
32
+ "test:watch": "jest --watch",
33
+ "test:coverage": "jest --coverage",
34
+ "lint": "eslint js/ src/",
35
+ "lint:fix": "eslint js/ src/ --fix",
36
+ "format": "prettier --write js/ src/ css/"
37
+ },
38
+ "keywords": [
39
+ "ui",
40
+ "components",
41
+ "ui-components",
42
+ "design-system",
43
+ "vanilla-js",
44
+ "no-framework",
45
+ "lightweight",
46
+ "web-components",
47
+ "frontend",
48
+ "css-framework",
49
+ "javascript-ui",
50
+ "form-validation",
51
+ "data-binding",
52
+ "responsive",
53
+ "dark-theme",
54
+ "accessibility",
55
+ "flask",
56
+ "django",
57
+ "python",
58
+ "gin",
59
+ "go",
60
+ "spring-boot",
61
+ "java",
62
+ "aspnetcore",
63
+ "csharp",
64
+ "express",
65
+ "nodejs",
66
+ "rails",
67
+ "ruby",
68
+ "backend-agnostic",
69
+ "html-css-js"
70
+ ],
71
+ "author": "kupola-cn",
72
+ "license": "MIT",
73
+ "files": [
74
+ "dist",
75
+ "js",
76
+ "css",
77
+ "icons",
78
+ "utils",
79
+ "types",
80
+ "templates",
81
+ "README.md",
82
+ "SKILL.md",
83
+ "INTEGRATION.md",
84
+ "CHANGELOG.md",
85
+ "PROJECT_SUMMARY.md"
86
+ ],
87
+ "devDependencies": {
88
+ "@rollup/plugin-commonjs": "^29.0.3",
89
+ "@rollup/plugin-node-resolve": "^16.0.3",
90
+ "@rollup/plugin-terser": "^1.0.0",
91
+ "clean-css": "^5.3.3",
92
+ "rollup": "^4.62.2",
93
+ "rollup-plugin-copy": "^3.5.0",
94
+ "rollup-plugin-postcss": "^4.0.2",
95
+ "terser": "^5.31.0",
96
+ "vite": "^5.4.1",
97
+ "eslint": "^9.5.0",
98
+ "prettier": "^3.3.2",
99
+ "jest": "^29.7.0",
100
+ "jest-environment-jsdom": "^29.7.0"
101
+ },
102
+ "dependencies": {
103
+ "ws": "^8.21.0"
104
+ }
105
+ }