@keenthemes/ktui 1.0.3

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 (426) hide show
  1. package/CONTRIBUTING.md +88 -0
  2. package/LICENSE.md +21 -0
  3. package/README.md +124 -0
  4. package/dist/ktui.js +19201 -0
  5. package/dist/ktui.min.js +2 -0
  6. package/dist/ktui.min.js.map +1 -0
  7. package/lib/cjs/components/accordion/accordion.js +168 -0
  8. package/lib/cjs/components/accordion/accordion.js.map +1 -0
  9. package/lib/cjs/components/accordion/index.js +6 -0
  10. package/lib/cjs/components/accordion/index.js.map +1 -0
  11. package/lib/cjs/components/accordion/types.js +3 -0
  12. package/lib/cjs/components/accordion/types.js.map +1 -0
  13. package/lib/cjs/components/collapse/collapse.js +169 -0
  14. package/lib/cjs/components/collapse/collapse.js.map +1 -0
  15. package/lib/cjs/components/collapse/index.js +6 -0
  16. package/lib/cjs/components/collapse/index.js.map +1 -0
  17. package/lib/cjs/components/collapse/types.js +3 -0
  18. package/lib/cjs/components/collapse/types.js.map +1 -0
  19. package/lib/cjs/components/component.js +135 -0
  20. package/lib/cjs/components/component.js.map +1 -0
  21. package/lib/cjs/components/config.js +26 -0
  22. package/lib/cjs/components/config.js.map +1 -0
  23. package/lib/cjs/components/config.umd.js +23 -0
  24. package/lib/cjs/components/config.umd.js.map +1 -0
  25. package/lib/cjs/components/constants.js +15 -0
  26. package/lib/cjs/components/constants.js.map +1 -0
  27. package/lib/cjs/components/datatable/datatable.js +1464 -0
  28. package/lib/cjs/components/datatable/datatable.js.map +1 -0
  29. package/lib/cjs/components/datatable/index.js +6 -0
  30. package/lib/cjs/components/datatable/index.js.map +1 -0
  31. package/lib/cjs/components/datatable/types.js +3 -0
  32. package/lib/cjs/components/datatable/types.js.map +1 -0
  33. package/lib/cjs/components/dismiss/dismiss.js +131 -0
  34. package/lib/cjs/components/dismiss/dismiss.js.map +1 -0
  35. package/lib/cjs/components/dismiss/index.js +6 -0
  36. package/lib/cjs/components/dismiss/index.js.map +1 -0
  37. package/lib/cjs/components/dismiss/types.js +3 -0
  38. package/lib/cjs/components/dismiss/types.js.map +1 -0
  39. package/lib/cjs/components/drawer/drawer.js +347 -0
  40. package/lib/cjs/components/drawer/drawer.js.map +1 -0
  41. package/lib/cjs/components/drawer/index.js +6 -0
  42. package/lib/cjs/components/drawer/index.js.map +1 -0
  43. package/lib/cjs/components/drawer/types.js +3 -0
  44. package/lib/cjs/components/drawer/types.js.map +1 -0
  45. package/lib/cjs/components/dropdown/dropdown.js +403 -0
  46. package/lib/cjs/components/dropdown/dropdown.js.map +1 -0
  47. package/lib/cjs/components/dropdown/index.js +6 -0
  48. package/lib/cjs/components/dropdown/index.js.map +1 -0
  49. package/lib/cjs/components/dropdown/types.js +3 -0
  50. package/lib/cjs/components/dropdown/types.js.map +1 -0
  51. package/lib/cjs/components/image-input/image-input.js +191 -0
  52. package/lib/cjs/components/image-input/image-input.js.map +1 -0
  53. package/lib/cjs/components/image-input/index.js +6 -0
  54. package/lib/cjs/components/image-input/index.js.map +1 -0
  55. package/lib/cjs/components/image-input/types.js +3 -0
  56. package/lib/cjs/components/image-input/types.js.map +1 -0
  57. package/lib/cjs/components/menu/index.js +6 -0
  58. package/lib/cjs/components/menu/index.js.map +1 -0
  59. package/lib/cjs/components/menu/menu.js +1021 -0
  60. package/lib/cjs/components/menu/menu.js.map +1 -0
  61. package/lib/cjs/components/menu/types.js +3 -0
  62. package/lib/cjs/components/menu/types.js.map +1 -0
  63. package/lib/cjs/components/modal/index.js +6 -0
  64. package/lib/cjs/components/modal/index.js.map +1 -0
  65. package/lib/cjs/components/modal/modal.js +316 -0
  66. package/lib/cjs/components/modal/modal.js.map +1 -0
  67. package/lib/cjs/components/modal/types.js +3 -0
  68. package/lib/cjs/components/modal/types.js.map +1 -0
  69. package/lib/cjs/components/reparent/index.js +6 -0
  70. package/lib/cjs/components/reparent/index.js.map +1 -0
  71. package/lib/cjs/components/reparent/reparent.js +93 -0
  72. package/lib/cjs/components/reparent/reparent.js.map +1 -0
  73. package/lib/cjs/components/reparent/types.js +3 -0
  74. package/lib/cjs/components/reparent/types.js.map +1 -0
  75. package/lib/cjs/components/scrollable/index.js +6 -0
  76. package/lib/cjs/components/scrollable/index.js.map +1 -0
  77. package/lib/cjs/components/scrollable/scrollable.js +259 -0
  78. package/lib/cjs/components/scrollable/scrollable.js.map +1 -0
  79. package/lib/cjs/components/scrollable/types.js +3 -0
  80. package/lib/cjs/components/scrollable/types.js.map +1 -0
  81. package/lib/cjs/components/scrollspy/index.js +6 -0
  82. package/lib/cjs/components/scrollspy/index.js.map +1 -0
  83. package/lib/cjs/components/scrollspy/scrollspy.js +174 -0
  84. package/lib/cjs/components/scrollspy/scrollspy.js.map +1 -0
  85. package/lib/cjs/components/scrollspy/types.js +3 -0
  86. package/lib/cjs/components/scrollspy/types.js.map +1 -0
  87. package/lib/cjs/components/scrollto/index.js +6 -0
  88. package/lib/cjs/components/scrollto/index.js.map +1 -0
  89. package/lib/cjs/components/scrollto/scrollto.js +103 -0
  90. package/lib/cjs/components/scrollto/scrollto.js.map +1 -0
  91. package/lib/cjs/components/scrollto/types.js +3 -0
  92. package/lib/cjs/components/scrollto/types.js.map +1 -0
  93. package/lib/cjs/components/stepper/index.js +6 -0
  94. package/lib/cjs/components/stepper/index.js.map +1 -0
  95. package/lib/cjs/components/stepper/stepper.js +258 -0
  96. package/lib/cjs/components/stepper/stepper.js.map +1 -0
  97. package/lib/cjs/components/stepper/types.js +3 -0
  98. package/lib/cjs/components/stepper/types.js.map +1 -0
  99. package/lib/cjs/components/sticky/index.js +6 -0
  100. package/lib/cjs/components/sticky/index.js.map +1 -0
  101. package/lib/cjs/components/sticky/sticky.js +297 -0
  102. package/lib/cjs/components/sticky/sticky.js.map +1 -0
  103. package/lib/cjs/components/sticky/types.js +3 -0
  104. package/lib/cjs/components/sticky/types.js.map +1 -0
  105. package/lib/cjs/components/tabs/index.js +6 -0
  106. package/lib/cjs/components/tabs/index.js.map +1 -0
  107. package/lib/cjs/components/tabs/tabs.js +146 -0
  108. package/lib/cjs/components/tabs/tabs.js.map +1 -0
  109. package/lib/cjs/components/tabs/types.js +3 -0
  110. package/lib/cjs/components/tabs/types.js.map +1 -0
  111. package/lib/cjs/components/theme/index.js +6 -0
  112. package/lib/cjs/components/theme/index.js.map +1 -0
  113. package/lib/cjs/components/theme/theme.js +147 -0
  114. package/lib/cjs/components/theme/theme.js.map +1 -0
  115. package/lib/cjs/components/theme/types.js +3 -0
  116. package/lib/cjs/components/theme/types.js.map +1 -0
  117. package/lib/cjs/components/toggle/index.js +6 -0
  118. package/lib/cjs/components/toggle/index.js.map +1 -0
  119. package/lib/cjs/components/toggle/toggle.js +139 -0
  120. package/lib/cjs/components/toggle/toggle.js.map +1 -0
  121. package/lib/cjs/components/toggle/types.js +3 -0
  122. package/lib/cjs/components/toggle/types.js.map +1 -0
  123. package/lib/cjs/components/toggle-password/index.js +6 -0
  124. package/lib/cjs/components/toggle-password/index.js.map +1 -0
  125. package/lib/cjs/components/toggle-password/toggle-password.js +131 -0
  126. package/lib/cjs/components/toggle-password/toggle-password.js.map +1 -0
  127. package/lib/cjs/components/toggle-password/types.js +3 -0
  128. package/lib/cjs/components/toggle-password/types.js.map +1 -0
  129. package/lib/cjs/components/tooltip/index.js +6 -0
  130. package/lib/cjs/components/tooltip/index.js.map +1 -0
  131. package/lib/cjs/components/tooltip/tooltip.js +271 -0
  132. package/lib/cjs/components/tooltip/tooltip.js.map +1 -0
  133. package/lib/cjs/components/tooltip/types.js +3 -0
  134. package/lib/cjs/components/tooltip/types.js.map +1 -0
  135. package/lib/cjs/helpers/data.js +33 -0
  136. package/lib/cjs/helpers/data.js.map +1 -0
  137. package/lib/cjs/helpers/dom.js +297 -0
  138. package/lib/cjs/helpers/dom.js.map +1 -0
  139. package/lib/cjs/helpers/event-handler.js +36 -0
  140. package/lib/cjs/helpers/event-handler.js.map +1 -0
  141. package/lib/cjs/helpers/utils.js +94 -0
  142. package/lib/cjs/helpers/utils.js.map +1 -0
  143. package/lib/cjs/index.js +105 -0
  144. package/lib/cjs/index.js.map +1 -0
  145. package/lib/cjs/types.js +3 -0
  146. package/lib/cjs/types.js.map +1 -0
  147. package/lib/esm/components/accordion/accordion.js +165 -0
  148. package/lib/esm/components/accordion/accordion.js.map +1 -0
  149. package/lib/esm/components/accordion/index.js +2 -0
  150. package/lib/esm/components/accordion/index.js.map +1 -0
  151. package/lib/esm/components/accordion/types.js +2 -0
  152. package/lib/esm/components/accordion/types.js.map +1 -0
  153. package/lib/esm/components/collapse/collapse.js +166 -0
  154. package/lib/esm/components/collapse/collapse.js.map +1 -0
  155. package/lib/esm/components/collapse/index.js +2 -0
  156. package/lib/esm/components/collapse/index.js.map +1 -0
  157. package/lib/esm/components/collapse/types.js +2 -0
  158. package/lib/esm/components/collapse/types.js.map +1 -0
  159. package/lib/esm/components/component.js +133 -0
  160. package/lib/esm/components/component.js.map +1 -0
  161. package/lib/esm/components/config.js +24 -0
  162. package/lib/esm/components/config.js.map +1 -0
  163. package/lib/esm/components/config.umd.js +23 -0
  164. package/lib/esm/components/config.umd.js.map +1 -0
  165. package/lib/esm/components/constants.js +12 -0
  166. package/lib/esm/components/constants.js.map +1 -0
  167. package/lib/esm/components/datatable/datatable.js +1461 -0
  168. package/lib/esm/components/datatable/datatable.js.map +1 -0
  169. package/lib/esm/components/datatable/index.js +2 -0
  170. package/lib/esm/components/datatable/index.js.map +1 -0
  171. package/lib/esm/components/datatable/types.js +2 -0
  172. package/lib/esm/components/datatable/types.js.map +1 -0
  173. package/lib/esm/components/dismiss/dismiss.js +128 -0
  174. package/lib/esm/components/dismiss/dismiss.js.map +1 -0
  175. package/lib/esm/components/dismiss/index.js +2 -0
  176. package/lib/esm/components/dismiss/index.js.map +1 -0
  177. package/lib/esm/components/dismiss/types.js +2 -0
  178. package/lib/esm/components/dismiss/types.js.map +1 -0
  179. package/lib/esm/components/drawer/drawer.js +344 -0
  180. package/lib/esm/components/drawer/drawer.js.map +1 -0
  181. package/lib/esm/components/drawer/index.js +2 -0
  182. package/lib/esm/components/drawer/index.js.map +1 -0
  183. package/lib/esm/components/drawer/types.js +2 -0
  184. package/lib/esm/components/drawer/types.js.map +1 -0
  185. package/lib/esm/components/dropdown/dropdown.js +400 -0
  186. package/lib/esm/components/dropdown/dropdown.js.map +1 -0
  187. package/lib/esm/components/dropdown/index.js +2 -0
  188. package/lib/esm/components/dropdown/index.js.map +1 -0
  189. package/lib/esm/components/dropdown/types.js +2 -0
  190. package/lib/esm/components/dropdown/types.js.map +1 -0
  191. package/lib/esm/components/image-input/image-input.js +188 -0
  192. package/lib/esm/components/image-input/image-input.js.map +1 -0
  193. package/lib/esm/components/image-input/index.js +2 -0
  194. package/lib/esm/components/image-input/index.js.map +1 -0
  195. package/lib/esm/components/image-input/types.js +2 -0
  196. package/lib/esm/components/image-input/types.js.map +1 -0
  197. package/lib/esm/components/menu/index.js +2 -0
  198. package/lib/esm/components/menu/index.js.map +1 -0
  199. package/lib/esm/components/menu/menu.js +1018 -0
  200. package/lib/esm/components/menu/menu.js.map +1 -0
  201. package/lib/esm/components/menu/types.js +2 -0
  202. package/lib/esm/components/menu/types.js.map +1 -0
  203. package/lib/esm/components/modal/index.js +2 -0
  204. package/lib/esm/components/modal/index.js.map +1 -0
  205. package/lib/esm/components/modal/modal.js +313 -0
  206. package/lib/esm/components/modal/modal.js.map +1 -0
  207. package/lib/esm/components/modal/types.js +2 -0
  208. package/lib/esm/components/modal/types.js.map +1 -0
  209. package/lib/esm/components/reparent/index.js +2 -0
  210. package/lib/esm/components/reparent/index.js.map +1 -0
  211. package/lib/esm/components/reparent/reparent.js +90 -0
  212. package/lib/esm/components/reparent/reparent.js.map +1 -0
  213. package/lib/esm/components/reparent/types.js +2 -0
  214. package/lib/esm/components/reparent/types.js.map +1 -0
  215. package/lib/esm/components/scrollable/index.js +2 -0
  216. package/lib/esm/components/scrollable/index.js.map +1 -0
  217. package/lib/esm/components/scrollable/scrollable.js +256 -0
  218. package/lib/esm/components/scrollable/scrollable.js.map +1 -0
  219. package/lib/esm/components/scrollable/types.js +2 -0
  220. package/lib/esm/components/scrollable/types.js.map +1 -0
  221. package/lib/esm/components/scrollspy/index.js +2 -0
  222. package/lib/esm/components/scrollspy/index.js.map +1 -0
  223. package/lib/esm/components/scrollspy/scrollspy.js +171 -0
  224. package/lib/esm/components/scrollspy/scrollspy.js.map +1 -0
  225. package/lib/esm/components/scrollspy/types.js +2 -0
  226. package/lib/esm/components/scrollspy/types.js.map +1 -0
  227. package/lib/esm/components/scrollto/index.js +2 -0
  228. package/lib/esm/components/scrollto/index.js.map +1 -0
  229. package/lib/esm/components/scrollto/scrollto.js +100 -0
  230. package/lib/esm/components/scrollto/scrollto.js.map +1 -0
  231. package/lib/esm/components/scrollto/types.js +2 -0
  232. package/lib/esm/components/scrollto/types.js.map +1 -0
  233. package/lib/esm/components/stepper/index.js +2 -0
  234. package/lib/esm/components/stepper/index.js.map +1 -0
  235. package/lib/esm/components/stepper/stepper.js +255 -0
  236. package/lib/esm/components/stepper/stepper.js.map +1 -0
  237. package/lib/esm/components/stepper/types.js +2 -0
  238. package/lib/esm/components/stepper/types.js.map +1 -0
  239. package/lib/esm/components/sticky/index.js +2 -0
  240. package/lib/esm/components/sticky/index.js.map +1 -0
  241. package/lib/esm/components/sticky/sticky.js +294 -0
  242. package/lib/esm/components/sticky/sticky.js.map +1 -0
  243. package/lib/esm/components/sticky/types.js +2 -0
  244. package/lib/esm/components/sticky/types.js.map +1 -0
  245. package/lib/esm/components/tabs/index.js +2 -0
  246. package/lib/esm/components/tabs/index.js.map +1 -0
  247. package/lib/esm/components/tabs/tabs.js +143 -0
  248. package/lib/esm/components/tabs/tabs.js.map +1 -0
  249. package/lib/esm/components/tabs/types.js +2 -0
  250. package/lib/esm/components/tabs/types.js.map +1 -0
  251. package/lib/esm/components/theme/index.js +2 -0
  252. package/lib/esm/components/theme/index.js.map +1 -0
  253. package/lib/esm/components/theme/theme.js +144 -0
  254. package/lib/esm/components/theme/theme.js.map +1 -0
  255. package/lib/esm/components/theme/types.js +2 -0
  256. package/lib/esm/components/theme/types.js.map +1 -0
  257. package/lib/esm/components/toggle/index.js +2 -0
  258. package/lib/esm/components/toggle/index.js.map +1 -0
  259. package/lib/esm/components/toggle/toggle.js +136 -0
  260. package/lib/esm/components/toggle/toggle.js.map +1 -0
  261. package/lib/esm/components/toggle/types.js +2 -0
  262. package/lib/esm/components/toggle/types.js.map +1 -0
  263. package/lib/esm/components/toggle-password/index.js +2 -0
  264. package/lib/esm/components/toggle-password/index.js.map +1 -0
  265. package/lib/esm/components/toggle-password/toggle-password.js +128 -0
  266. package/lib/esm/components/toggle-password/toggle-password.js.map +1 -0
  267. package/lib/esm/components/toggle-password/types.js +2 -0
  268. package/lib/esm/components/toggle-password/types.js.map +1 -0
  269. package/lib/esm/components/tooltip/index.js +2 -0
  270. package/lib/esm/components/tooltip/index.js.map +1 -0
  271. package/lib/esm/components/tooltip/tooltip.js +268 -0
  272. package/lib/esm/components/tooltip/tooltip.js.map +1 -0
  273. package/lib/esm/components/tooltip/types.js +2 -0
  274. package/lib/esm/components/tooltip/types.js.map +1 -0
  275. package/lib/esm/helpers/data.js +31 -0
  276. package/lib/esm/helpers/data.js.map +1 -0
  277. package/lib/esm/helpers/dom.js +295 -0
  278. package/lib/esm/helpers/dom.js.map +1 -0
  279. package/lib/esm/helpers/event-handler.js +34 -0
  280. package/lib/esm/helpers/event-handler.js.map +1 -0
  281. package/lib/esm/helpers/utils.js +92 -0
  282. package/lib/esm/helpers/utils.js.map +1 -0
  283. package/lib/esm/index.js +79 -0
  284. package/lib/esm/index.js.map +1 -0
  285. package/lib/esm/types.js +2 -0
  286. package/lib/esm/types.js.map +1 -0
  287. package/package.json +85 -0
  288. package/prettier.config.js +9 -0
  289. package/src/components/accordion/accordion-menu.css +51 -0
  290. package/src/components/accordion/accordion.css +86 -0
  291. package/src/components/accordion/accordion.ts +221 -0
  292. package/src/components/accordion/index.ts +7 -0
  293. package/src/components/accordion/types.ts +16 -0
  294. package/src/components/alert/alert.css +282 -0
  295. package/src/components/avatar/avatar.css +46 -0
  296. package/src/components/badge/badge.css +176 -0
  297. package/src/components/breadcrumb/breadcrumb.css +38 -0
  298. package/src/components/btn/btn.css +227 -0
  299. package/src/components/card/card.css +158 -0
  300. package/src/components/checkbox/checkbox.css +74 -0
  301. package/src/components/collapse/collapse.css +14 -0
  302. package/src/components/collapse/collapse.ts +200 -0
  303. package/src/components/collapse/index.ts +7 -0
  304. package/src/components/collapse/types.ts +16 -0
  305. package/src/components/component.ts +132 -0
  306. package/src/components/constants.ts +16 -0
  307. package/src/components/datatable/datatable-checkbox.ts +236 -0
  308. package/src/components/datatable/datatable-sort.ts +154 -0
  309. package/src/components/datatable/datatable.css +110 -0
  310. package/src/components/datatable/datatable.ts +1657 -0
  311. package/src/components/datatable/index.ts +19 -0
  312. package/src/components/datatable/types.ts +203 -0
  313. package/src/components/datepicker/calendar.ts +1397 -0
  314. package/src/components/datepicker/config.ts +368 -0
  315. package/src/components/datepicker/datepicker.css +7 -0
  316. package/src/components/datepicker/datepicker.ts +1287 -0
  317. package/src/components/datepicker/dropdown.ts +757 -0
  318. package/src/components/datepicker/events.ts +149 -0
  319. package/src/components/datepicker/index.ts +10 -0
  320. package/src/components/datepicker/keyboard.ts +646 -0
  321. package/src/components/datepicker/locales.ts +80 -0
  322. package/src/components/datepicker/templates.ts +792 -0
  323. package/src/components/datepicker/types.ts +154 -0
  324. package/src/components/datepicker/utils.ts +631 -0
  325. package/src/components/dismiss/dismiss.css +10 -0
  326. package/src/components/dismiss/dismiss.ts +152 -0
  327. package/src/components/dismiss/index.ts +7 -0
  328. package/src/components/dismiss/types.ts +17 -0
  329. package/src/components/drawer/drawer.css +97 -0
  330. package/src/components/drawer/drawer.ts +437 -0
  331. package/src/components/drawer/index.ts +7 -0
  332. package/src/components/drawer/types.ts +25 -0
  333. package/src/components/dropdown/dropdown-menu.css +56 -0
  334. package/src/components/dropdown/dropdown.css +46 -0
  335. package/src/components/dropdown/dropdown.ts +549 -0
  336. package/src/components/dropdown/index.ts +7 -0
  337. package/src/components/dropdown/types.ts +28 -0
  338. package/src/components/form/form.css +54 -0
  339. package/src/components/image-input/image-input.css +56 -0
  340. package/src/components/image-input/image-input.ts +249 -0
  341. package/src/components/image-input/index.ts +10 -0
  342. package/src/components/image-input/types.ts +12 -0
  343. package/src/components/input/input-group.css +42 -0
  344. package/src/components/input/input.css +136 -0
  345. package/src/components/kbd/kbd.css +30 -0
  346. package/src/components/label/label.css +20 -0
  347. package/src/components/link/link.css +81 -0
  348. package/src/components/modal/index.ts +7 -0
  349. package/src/components/modal/modal.css +73 -0
  350. package/src/components/modal/modal.ts +382 -0
  351. package/src/components/modal/types.ts +21 -0
  352. package/src/components/pagination/pagination.css +26 -0
  353. package/src/components/popover/popover.css +22 -0
  354. package/src/components/progress/progress.css +51 -0
  355. package/src/components/radio/radio.css +51 -0
  356. package/src/components/reparent/index.ts +7 -0
  357. package/src/components/reparent/reparent.ts +109 -0
  358. package/src/components/reparent/types.ts +15 -0
  359. package/src/components/scrollable/index.ts +10 -0
  360. package/src/components/scrollable/scrollable.css +29 -0
  361. package/src/components/scrollable/scrollable.ts +297 -0
  362. package/src/components/scrollable/types.ts +16 -0
  363. package/src/components/scrollspy/index.ts +7 -0
  364. package/src/components/scrollspy/scrollspy.css +13 -0
  365. package/src/components/scrollspy/scrollspy.ts +224 -0
  366. package/src/components/scrollspy/types.ts +15 -0
  367. package/src/components/scrollto/index.ts +7 -0
  368. package/src/components/scrollto/scrollto.ts +127 -0
  369. package/src/components/scrollto/types.ts +15 -0
  370. package/src/components/select/combobox.ts +305 -0
  371. package/src/components/select/config.ts +324 -0
  372. package/src/components/select/dropdown.ts +510 -0
  373. package/src/components/select/index.ts +13 -0
  374. package/src/components/select/option.ts +43 -0
  375. package/src/components/select/remote.ts +477 -0
  376. package/src/components/select/search.ts +430 -0
  377. package/src/components/select/select.css +105 -0
  378. package/src/components/select/select.ts +1916 -0
  379. package/src/components/select/tags.ts +123 -0
  380. package/src/components/select/templates.ts +531 -0
  381. package/src/components/select/types.ts +36 -0
  382. package/src/components/select/utils.ts +747 -0
  383. package/src/components/select/variants.css +5 -0
  384. package/src/components/separator/separator.css +14 -0
  385. package/src/components/skeleton/skeleton.css +10 -0
  386. package/src/components/stepper/index.ts +7 -0
  387. package/src/components/stepper/stepper.css +49 -0
  388. package/src/components/stepper/stepper.ts +308 -0
  389. package/src/components/stepper/types.ts +13 -0
  390. package/src/components/sticky/index.ts +7 -0
  391. package/src/components/sticky/sticky.css +22 -0
  392. package/src/components/sticky/sticky.ts +381 -0
  393. package/src/components/sticky/types.ts +23 -0
  394. package/src/components/switch/switch.css +110 -0
  395. package/src/components/table/table.css +168 -0
  396. package/src/components/tabs/index.ts +7 -0
  397. package/src/components/tabs/tabs.css +40 -0
  398. package/src/components/tabs/tabs.ts +190 -0
  399. package/src/components/tabs/types.ts +13 -0
  400. package/src/components/textarea/textarea.css +35 -0
  401. package/src/components/theme-switch/index.ts +10 -0
  402. package/src/components/theme-switch/theme-switch.css +22 -0
  403. package/src/components/theme-switch/theme-switch.ts +176 -0
  404. package/src/components/theme-switch/types.ts +15 -0
  405. package/src/components/toggle/index.ts +7 -0
  406. package/src/components/toggle/toggle.css +13 -0
  407. package/src/components/toggle/toggle.ts +173 -0
  408. package/src/components/toggle/types.ts +18 -0
  409. package/src/components/toggle-group/toggle-group.css +55 -0
  410. package/src/components/toggle-password/index.ts +10 -0
  411. package/src/components/toggle-password/toggle-password.css +13 -0
  412. package/src/components/toggle-password/toggle-password.ts +159 -0
  413. package/src/components/toggle-password/types.ts +13 -0
  414. package/src/components/tooltip/index.ts +7 -0
  415. package/src/components/tooltip/tooltip.css +18 -0
  416. package/src/components/tooltip/tooltip.ts +361 -0
  417. package/src/components/tooltip/types.ts +28 -0
  418. package/src/helpers/data.ts +46 -0
  419. package/src/helpers/dom.ts +405 -0
  420. package/src/helpers/event-handler.ts +61 -0
  421. package/src/helpers/utils.ts +183 -0
  422. package/src/index.ts +113 -0
  423. package/src/types.ts +23 -0
  424. package/styles.css +48 -0
  425. package/tsconfig.json +17 -0
  426. package/webpack.config.js +113 -0
@@ -0,0 +1,1657 @@
1
+ /**
2
+ * KTUI - Free & Open-Source Tailwind UI Components by Keenthemes
3
+ * Copyright 2025 by Keenthemes Inc
4
+ * @version: 1.0.0
5
+ */
6
+ import KTComponent from '../component';
7
+ import {
8
+ KTDataTableDataInterface,
9
+ KTDataTableInterface,
10
+ KTDataTableConfigInterface as KTDataTableConfigInterface,
11
+ KTDataTableSortOrderInterface,
12
+ KTDataTableStateInterface,
13
+ KTDataTableColumnFilterInterface,
14
+ KTDataTableAttributeInterface,
15
+ } from './types';
16
+ import KTUtils from '../../helpers/utils';
17
+ import KTComponents from '../../index';
18
+ import KTData from '../../helpers/data';
19
+ import {
20
+ createCheckboxHandler,
21
+ KTDataTableCheckboxAPI,
22
+ } from './datatable-checkbox';
23
+ import { createSortHandler, KTDataTableSortAPI } from './datatable-sort';
24
+
25
+ /**
26
+ * Custom DataTable plugin class with server-side API, pagination, and sorting
27
+ * @classdesc A custom KTComponent class that integrates server-side API, pagination, and sorting functionality into a table.
28
+ * It supports fetching data from a server-side API, pagination, and sorting of the fetched data.
29
+ * @class
30
+ * @extends {KTComponent}
31
+ * @param {HTMLElement} element The table element
32
+ * @param {KTDataTableConfigInterface} [config] Additional configuration options
33
+ */
34
+ export class KTDataTable<T extends KTDataTableDataInterface>
35
+ extends KTComponent
36
+ implements KTDataTableInterface
37
+ {
38
+ protected override _name: string = 'datatable';
39
+ protected override _config: KTDataTableConfigInterface;
40
+ protected override _defaultConfig: KTDataTableConfigInterface;
41
+
42
+ private _tableElement: HTMLTableElement;
43
+ private _tbodyElement: HTMLTableSectionElement;
44
+ private _theadElement: HTMLTableSectionElement;
45
+ private _originalTbodyClass: string = ''; // Store original tbody class
46
+ private _originalTrClasses: string[] = []; // Store original tr classes
47
+ private _originalTheadClass: string = ''; // Store original thead class
48
+ private _originalTdClasses: string[][] = []; // Store original td classes as a 2D array [row][col]
49
+ private _originalThClasses: string[] = []; // Store original th classes
50
+
51
+ private _infoElement: HTMLElement;
52
+ private _sizeElement: HTMLSelectElement;
53
+ private _paginationElement: HTMLElement;
54
+
55
+ private _checkbox: KTDataTableCheckboxAPI;
56
+ private _sortHandler: KTDataTableSortAPI<T>;
57
+
58
+ private _data: T[] = [];
59
+
60
+ constructor(element: HTMLElement, config?: KTDataTableConfigInterface) {
61
+ super();
62
+
63
+ if (KTData.has(element as HTMLElement, this._name)) return;
64
+
65
+ this._defaultConfig = this._initDefaultConfig(config);
66
+
67
+ this._init(element);
68
+ this._buildConfig();
69
+
70
+ // Store the instance directly on the element
71
+ (element as any).instance = this;
72
+
73
+ this._initElements();
74
+
75
+ // Initialize checkbox handler
76
+ this._checkbox = createCheckboxHandler(
77
+ this._element,
78
+ this._config,
79
+ (eventName: string, eventData?: any) => {
80
+ this._fireEvent(eventName, eventData);
81
+ this._dispatchEvent(eventName, eventData);
82
+ },
83
+ );
84
+
85
+ // Initialize sort handler
86
+ this._sortHandler = createSortHandler(
87
+ this._config,
88
+ this._theadElement,
89
+ () => ({
90
+ sortField: this.getState().sortField,
91
+ sortOrder: this.getState().sortOrder,
92
+ }),
93
+ (field, order) => {
94
+ this._config._state.sortField = field as never;
95
+ this._config._state.sortOrder = order;
96
+ },
97
+ this._fireEvent.bind(this),
98
+ this._dispatchEvent.bind(this),
99
+ this._updateData.bind(this),
100
+ );
101
+
102
+ this._sortHandler.initSort();
103
+
104
+ if (this._config.stateSave === false) {
105
+ this._deleteState();
106
+ }
107
+
108
+ if (this._config.stateSave) {
109
+ this._loadState();
110
+ }
111
+
112
+ this._updateData();
113
+
114
+ this._fireEvent('init');
115
+ this._dispatchEvent('init');
116
+ }
117
+
118
+ /**
119
+ * Initialize default configuration for the datatable
120
+ * @param config User-provided configuration options
121
+ * @returns Default configuration merged with user-provided options
122
+ */
123
+ private _initDefaultConfig(
124
+ config?: KTDataTableConfigInterface,
125
+ ): KTDataTableConfigInterface {
126
+ return {
127
+ /**
128
+ * HTTP method for server-side API call
129
+ */
130
+ requestMethod: 'GET',
131
+ /**
132
+ * Custom HTTP headers for the API request
133
+ */
134
+ requestHeaders: {
135
+ 'Content-Type': 'application/x-www-form-urlencoded',
136
+ },
137
+ /**
138
+ * Pagination info template
139
+ */
140
+ info: '{start}-{end} of {total}',
141
+ /**
142
+ * Info text when there is no data
143
+ */
144
+ infoEmpty: 'No records found',
145
+ /**
146
+ * Available page sizes
147
+ */
148
+ pageSizes: [5, 10, 20, 30, 50],
149
+ /**
150
+ * Default page size
151
+ */
152
+ pageSize: 10,
153
+ /**
154
+ * Enable or disable pagination more button
155
+ */
156
+ pageMore: true,
157
+ /**
158
+ * Maximum number of pages before enabling pagination more button
159
+ */
160
+ pageMoreLimit: 3,
161
+ /**
162
+ * Pagination button templates
163
+ */
164
+ pagination: {
165
+ number: {
166
+ /**
167
+ * CSS classes to be added to the pagination button
168
+ */
169
+ class: 'kt-datatable-pagination-button',
170
+ /**
171
+ * Text to be displayed in the pagination button
172
+ */
173
+ text: '{page}',
174
+ },
175
+ previous: {
176
+ /**
177
+ * CSS classes to be added to the previous pagination button
178
+ */
179
+ class: 'kt-datatable-pagination-button kt-datatable-pagination-prev',
180
+ /**
181
+ * Text to be displayed in the previous pagination button
182
+ */
183
+ text: `
184
+ <svg class="rtl:transform rtl:rotate-180 size-3.5 shrink-0" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
185
+ <path d="M8.86501 16.7882V12.8481H21.1459C21.3724 12.8481 21.5897 12.7581 21.7498 12.5979C21.91 12.4378 22 12.2205 22 11.994C22 11.7675 21.91 11.5503 21.7498 11.3901C21.5897 11.2299 21.3724 11.1399 21.1459 11.1399H8.86501V7.2112C8.86628 7.10375 8.83517 6.9984 8.77573 6.90887C8.7163 6.81934 8.63129 6.74978 8.53177 6.70923C8.43225 6.66869 8.32283 6.65904 8.21775 6.68155C8.11267 6.70405 8.0168 6.75766 7.94262 6.83541L2.15981 11.6182C2.1092 11.668 2.06901 11.7274 2.04157 11.7929C2.01413 11.8584 2 11.9287 2 11.9997C2 12.0707 2.01413 12.141 2.04157 12.2065C2.06901 12.272 2.1092 12.3314 2.15981 12.3812L7.94262 17.164C8.0168 17.2417 8.11267 17.2953 8.21775 17.3178C8.32283 17.3403 8.43225 17.3307 8.53177 17.2902C8.63129 17.2496 8.7163 17.18 8.77573 17.0905C8.83517 17.001 8.86628 16.8956 8.86501 16.7882Z" fill="currentColor"/>
186
+ </svg>
187
+ `,
188
+ },
189
+ next: {
190
+ /**
191
+ * CSS classes to be added to the next pagination button
192
+ */
193
+ class: 'kt-datatable-pagination-button kt-datatable-pagination-next',
194
+ /**
195
+ * Text to be displayed in the next pagination button
196
+ */
197
+ text: `
198
+ <svg class="rtl:transform rtl:rotate-180 size-3.5 shrink-0" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
199
+ <path d="M15.135 7.21144V11.1516H2.85407C2.62756 11.1516 2.41032 11.2415 2.25015 11.4017C2.08998 11.5619 2 11.7791 2 12.0056C2 12.2321 2.08998 12.4494 2.25015 12.6096C2.41032 12.7697 2.62756 12.8597 2.85407 12.8597H15.135V16.7884C15.1337 16.8959 15.1648 17.0012 15.2243 17.0908C15.2837 17.1803 15.3687 17.2499 15.4682 17.2904C15.5677 17.3309 15.6772 17.3406 15.7822 17.3181C15.8873 17.2956 15.9832 17.242 16.0574 17.1642L21.8402 12.3814C21.8908 12.3316 21.931 12.2722 21.9584 12.2067C21.9859 12.1412 22 12.0709 22 11.9999C22 11.9289 21.9859 11.8586 21.9584 11.7931C21.931 11.7276 21.8908 11.6683 21.8402 11.6185L16.0574 6.83565C15.9832 6.75791 15.8873 6.70429 15.7822 6.68179C15.6772 6.65929 15.5677 6.66893 15.4682 6.70948C15.3687 6.75002 15.2837 6.81959 15.2243 6.90911C15.1648 6.99864 15.1337 7.10399 15.135 7.21144Z" fill="currentColor"/>
200
+ </svg>
201
+ `,
202
+ },
203
+ more: {
204
+ /**
205
+ * CSS classes to be added to the pagination more button
206
+ */
207
+ class: 'kt-datatable-pagination-button kt-datatable-pagination-more',
208
+ /**
209
+ * Text to be displayed in the pagination more button
210
+ */
211
+ text: '...',
212
+ },
213
+ },
214
+ /**
215
+ * Sorting options
216
+ */
217
+ sort: {
218
+ /**
219
+ * CSS classes to be added to the sortable headers
220
+ */
221
+ classes: {
222
+ base: 'kt-table-col',
223
+ asc: 'asc',
224
+ desc: 'desc',
225
+ },
226
+ /**
227
+ * Local sorting callback function
228
+ * Sorts the data array based on the sort field and order
229
+ * @param data Data array to be sorted
230
+ * @param sortField Property name of the data object to be sorted by
231
+ * @param sortOrder Sorting order (ascending or descending)
232
+ * @returns Sorted data array
233
+ */
234
+ callback: (
235
+ data: T[],
236
+ sortField: keyof T | number,
237
+ sortOrder: KTDataTableSortOrderInterface,
238
+ ): T[] => {
239
+ return this._sortHandler
240
+ ? this._sortHandler.sortData(data, sortField, sortOrder)
241
+ : data;
242
+ },
243
+ },
244
+ search: {
245
+ /**
246
+ * Delay in milliseconds before the search function is applied to the data array
247
+ * @default 500
248
+ */
249
+ delay: 500, // ms
250
+ /**
251
+ * Local search callback function
252
+ * Filters the data array based on the search string
253
+ * @param data Data array to be filtered
254
+ * @param search Search string used to filter the data array
255
+ * @returns Filtered data array
256
+ */
257
+ callback: (data: T[], search: string): T[] => {
258
+ if (!data || !search) {
259
+ return [];
260
+ }
261
+
262
+ return data.filter((item: T) => {
263
+ if (!item) {
264
+ return false;
265
+ }
266
+
267
+ return Object.values(item).some(
268
+ (value: string | number | boolean) => {
269
+ if (
270
+ typeof value !== 'string' &&
271
+ typeof value !== 'number' &&
272
+ typeof value !== 'boolean'
273
+ ) {
274
+ return false;
275
+ }
276
+
277
+ const valueText = String(value)
278
+ .replace(/<[^>]*>|&nbsp;/g, '')
279
+ .toLowerCase();
280
+ return valueText.includes(search.toLowerCase());
281
+ },
282
+ );
283
+ });
284
+ },
285
+ },
286
+ /**
287
+ * Loading spinner options
288
+ */
289
+ loading: {
290
+ /**
291
+ * Template to be displayed during data fetching process
292
+ */
293
+ template: `
294
+ <div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
295
+ <div class="kt-datatable-loading">
296
+ <svg class="animate-spin -ml-1 h-5 w-5 text-gray-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
297
+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="3"></circle>
298
+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
299
+ </svg>
300
+ {content}
301
+ </div>
302
+ </div>
303
+ `,
304
+ /**
305
+ * Loading text to be displayed in the template
306
+ */
307
+ content: 'Loading...',
308
+ },
309
+ /**
310
+ * Selectors of the elements to be targeted
311
+ */
312
+ attributes: {
313
+ /**
314
+ * Data table element
315
+ */
316
+ table: 'table[data-kt-datatable-table="true"]',
317
+ /**
318
+ * Pagination info element
319
+ */
320
+ info: '[data-kt-datatable-info="true"]',
321
+ /**
322
+ * Page size dropdown element
323
+ */
324
+ size: '[data-kt-datatable-size="true"]',
325
+ /**
326
+ * Pagination element
327
+ */
328
+ pagination: '[data-kt-datatable-pagination="true"]',
329
+ /**
330
+ * Spinner element
331
+ */
332
+ spinner: '[data-kt-datatable-spinner="true"]',
333
+ /**
334
+ * Checkbox element
335
+ */
336
+ check: '[data-kt-datatable-check="true"]',
337
+ checkbox: '[data-kt-datatable-row-check="true"]',
338
+ },
339
+ /**
340
+ * Enable or disable state saving
341
+ */
342
+ stateSave: true,
343
+ checkbox: {
344
+ checkedClass: 'checked',
345
+ },
346
+ /**
347
+ * Private properties
348
+ */
349
+ _state: {} as KTDataTableStateInterface,
350
+ loadingClass: 'loading',
351
+ ...config,
352
+ } as KTDataTableConfigInterface;
353
+ }
354
+
355
+ /**
356
+ * Initialize table, tbody, thead, info, size, and pagination elements
357
+ * @returns {void}
358
+ */
359
+ private _initElements(): void {
360
+ /**
361
+ * Data table element
362
+ */
363
+ this._tableElement = this._element.querySelector<HTMLTableElement>(
364
+ this._config.attributes.table,
365
+ )!;
366
+ /**
367
+ * Table body element
368
+ */
369
+ this._tbodyElement =
370
+ this._tableElement.tBodies[0] || this._tableElement.createTBody();
371
+ /**
372
+ * Table head element
373
+ */
374
+ this._theadElement = this._tableElement.tHead!;
375
+
376
+ // Store original classes
377
+ this._storeOriginalClasses();
378
+
379
+ /**
380
+ * Pagination info element
381
+ */
382
+ this._infoElement = this._element.querySelector<HTMLElement>(
383
+ this._config.attributes.info,
384
+ )!;
385
+ /**
386
+ * Page size dropdown element
387
+ */
388
+ this._sizeElement = this._element.querySelector<HTMLSelectElement>(
389
+ this._config.attributes.size,
390
+ )!;
391
+ /**
392
+ * Pagination element
393
+ */
394
+ this._paginationElement = this._element.querySelector<HTMLElement>(
395
+ this._config.attributes.pagination,
396
+ )!;
397
+ }
398
+
399
+ /**
400
+ * Store original classes from table elements
401
+ * @returns {void}
402
+ */
403
+ private _storeOriginalClasses(): void {
404
+ // Store tbody class
405
+ if (this._tbodyElement) {
406
+ this._originalTbodyClass = this._tbodyElement.className || '';
407
+ }
408
+
409
+ // Store thead class and th classes
410
+ if (this._theadElement) {
411
+ this._originalTheadClass = this._theadElement.className || '';
412
+
413
+ // Store th classes
414
+ const thElements =
415
+ this._theadElement.querySelectorAll<HTMLTableCellElement>('th');
416
+ this._originalThClasses = Array.from(thElements).map(
417
+ (th) => th.className || '',
418
+ );
419
+ }
420
+
421
+ // Store tr and td classes
422
+ if (this._tbodyElement) {
423
+ const originalRows =
424
+ this._tbodyElement.querySelectorAll<HTMLTableRowElement>('tr');
425
+ this._originalTrClasses = Array.from(originalRows).map(
426
+ (row) => row.className || '',
427
+ );
428
+
429
+ // Store td classes as a 2D array
430
+ this._originalTdClasses = [];
431
+ Array.from(originalRows).forEach((row, rowIndex) => {
432
+ const tdElements = row.querySelectorAll<HTMLTableCellElement>('td');
433
+ this._originalTdClasses[rowIndex] = Array.from(tdElements).map(
434
+ (td) => td.className || '',
435
+ );
436
+ });
437
+ }
438
+ }
439
+
440
+ /**
441
+ * Fetch data from the server or from the DOM if `apiEndpoint` is not defined.
442
+ * @returns {Promise<void>} Promise which is resolved after data has been fetched and checkbox plugin initialized.
443
+ */
444
+ private async _updateData(): Promise<void> {
445
+ this._showSpinner(); // Show spinner before fetching data
446
+
447
+ // Fetch data from the DOM and initialize the checkbox plugin
448
+ return typeof this._config.apiEndpoint === 'undefined'
449
+ ? this._fetchDataFromLocal().then(
450
+ this._finalize.bind(this) as () => Promise<void>,
451
+ )
452
+ : this._fetchDataFromServer().then(
453
+ this._finalize.bind(this) as () => Promise<void>,
454
+ );
455
+ }
456
+
457
+ /**
458
+ * Finalize data table after data has been fetched
459
+ * @returns {void}
460
+ */
461
+ private _finalize(): void {
462
+ this._element.classList.add('datatable-initialized');
463
+
464
+ // Initialize checkbox logic
465
+ this._checkbox.init();
466
+
467
+ this._attachSearchEvent();
468
+
469
+ if (typeof KTComponents !== 'undefined') {
470
+ KTComponents.init();
471
+ }
472
+
473
+ /**
474
+ * Hide spinner
475
+ */
476
+ this._hideSpinner();
477
+ }
478
+
479
+ /**
480
+ * Attach search event to the search input element
481
+ * @returns {void}
482
+ */
483
+ private _attachSearchEvent(): void {
484
+ const tableId: string = this._tableId();
485
+ const searchElement: HTMLInputElement | null =
486
+ document.querySelector<HTMLInputElement>(
487
+ `[data-kt-datatable-search="#${tableId}"]`,
488
+ );
489
+
490
+ // Get search state
491
+ const { search } = this.getState();
492
+ // Set search value
493
+ if (searchElement) {
494
+ searchElement.value =
495
+ typeof search === 'string' ? search : String(search);
496
+ }
497
+
498
+ if (searchElement) {
499
+ // Check if a debounced search function already exists
500
+ if ((searchElement as any)._debouncedSearch) {
501
+ // Remove the existing debounced event listener
502
+ searchElement.removeEventListener(
503
+ 'keyup',
504
+ (searchElement as any)._debouncedSearch,
505
+ );
506
+ }
507
+
508
+ // Create a new debounced search function
509
+ const debouncedSearch = this._debounce(() => {
510
+ this.search(searchElement.value);
511
+ }, this._config.search.delay);
512
+
513
+ // Store the new debounced function as a property of the element
514
+ (searchElement as any)._debouncedSearch = debouncedSearch;
515
+
516
+ // Add the new debounced event listener
517
+ searchElement.addEventListener('keyup', debouncedSearch);
518
+ }
519
+ }
520
+
521
+ /**
522
+ * Fetch data from the DOM
523
+ * Fetch data from the table element and save it to the `originalData` state property.
524
+ * This method is used when the data is not fetched from the server via an API endpoint.
525
+ */
526
+ private async _fetchDataFromLocal(): Promise<void> {
527
+ this._fireEvent('fetch');
528
+ this._dispatchEvent('fetch');
529
+
530
+ const { sortField, sortOrder, page, pageSize, search } = this.getState();
531
+ let { originalData } = this.getState();
532
+
533
+ // If the table element or the original data is not defined, bail
534
+ if (
535
+ !this._tableElement ||
536
+ originalData === undefined ||
537
+ this._tableConfigInvalidate() ||
538
+ this._localTableHeaderInvalidate() ||
539
+ this._localTableContentInvalidate()
540
+ ) {
541
+ this._deleteState();
542
+
543
+ const { originalData, originalDataAttributes } =
544
+ this._localExtractTableContent();
545
+
546
+ this._config._state.originalData = originalData;
547
+ this._config._state.originalDataAttributes = originalDataAttributes;
548
+ }
549
+
550
+ // Update the original data variable
551
+ originalData = this.getState().originalData;
552
+
553
+ // Clone the original data
554
+ let _temp = (this._data = [...originalData] as T[]);
555
+
556
+ if (search) {
557
+ _temp = this._data = this._config.search.callback.call(
558
+ this,
559
+ this._data,
560
+ search,
561
+ ) as T[];
562
+ }
563
+
564
+ // If sorting is defined, sort the data
565
+ if (
566
+ sortField !== undefined &&
567
+ sortOrder !== undefined &&
568
+ sortOrder !== ''
569
+ ) {
570
+ if (typeof this._config.sort.callback === 'function') {
571
+ this._data = this._config.sort.callback.call(
572
+ this,
573
+ this._data,
574
+ sortField as string,
575
+ sortOrder,
576
+ ) as T[];
577
+ }
578
+ }
579
+
580
+ // If there is data, slice it to the current page size
581
+ if (this._data?.length > 0) {
582
+ // Calculate the start and end indices for the current page
583
+ const startIndex = (page - 1) * pageSize;
584
+ const endIndex = startIndex + pageSize;
585
+
586
+ this._data = this._data.slice(startIndex, endIndex) as T[];
587
+ }
588
+
589
+ // Determine number of total rows
590
+ this._config._state.totalItems = _temp.length;
591
+
592
+ // Draw the data
593
+ await this._draw();
594
+ this._fireEvent('fetched');
595
+ this._dispatchEvent('fetched');
596
+ }
597
+
598
+ /**
599
+ * Checks if the table content has been invalidated by comparing the current checksum of the table body
600
+ * with the stored checksum in the state. If the checksums are different, the state is updated with the
601
+ * new checksum and `true` is returned. Otherwise, `false` is returned.
602
+ *
603
+ * @returns {boolean} `true` if the table content has been invalidated, `false` otherwise.
604
+ */
605
+ private _localTableContentInvalidate(): boolean {
606
+ const checksum: string = KTUtils.checksum(
607
+ JSON.stringify(this._tbodyElement.innerHTML),
608
+ );
609
+ if (this.getState()._contentChecksum !== checksum) {
610
+ this._config._state._contentChecksum = checksum;
611
+ return true;
612
+ }
613
+ return false;
614
+ }
615
+
616
+ private _tableConfigInvalidate(): boolean {
617
+ // Remove _data and _state from config
618
+ const { _data, _state, ...restConfig } = this._config;
619
+ const checksum: string = KTUtils.checksum(JSON.stringify(restConfig));
620
+ if (_state._configChecksum !== checksum) {
621
+ this._config._state._configChecksum = checksum;
622
+ return true;
623
+ }
624
+ return false;
625
+ }
626
+
627
+ /**
628
+ * Extract the table content and returns it as an object containing an array of original data and an array of original data attributes.
629
+ *
630
+ * @returns {{originalData: T[], originalDataAttributes: KTDataTableAttributeInterface[]}} - An object containing an array of original data and an array of original data attributes.
631
+ */
632
+ private _localExtractTableContent(): {
633
+ originalData: T[];
634
+ originalDataAttributes: KTDataTableAttributeInterface[];
635
+ } {
636
+ const originalData: T[] = [];
637
+ const originalDataAttributes: KTDataTableAttributeInterface[] = [];
638
+
639
+ this._storeOriginalClasses();
640
+
641
+ const rows = this._tbodyElement.querySelectorAll<HTMLTableRowElement>('tr');
642
+ const ths: NodeListOf<HTMLTableCellElement> = this._theadElement
643
+ ? this._theadElement.querySelectorAll('th')
644
+ : ([] as unknown as NodeListOf<HTMLTableCellElement>);
645
+
646
+ rows.forEach((row: HTMLTableRowElement) => {
647
+ const dataRow: T = {} as T;
648
+ const dataRowAttribute: KTDataTableAttributeInterface =
649
+ {} as KTDataTableAttributeInterface;
650
+
651
+ row.querySelectorAll<HTMLTableCellElement>('td').forEach((td, index) => {
652
+ const colName = ths[index]?.getAttribute('data-kt-datatable-column');
653
+ if (colName) {
654
+ dataRow[colName as keyof T] = td.innerHTML?.trim() as T[keyof T];
655
+ } else {
656
+ // Store the original HTML for fallback
657
+ dataRow[index as keyof T] = td.innerHTML?.trim() as T[keyof T];
658
+ }
659
+ });
660
+
661
+ if (Object.keys(dataRow).length > 0) {
662
+ originalData.push(dataRow);
663
+ originalDataAttributes.push(dataRowAttribute);
664
+ }
665
+ });
666
+
667
+ return { originalData, originalDataAttributes };
668
+ }
669
+
670
+ /**
671
+ * Check if the table header is invalidated
672
+ * @returns {boolean} - Returns true if the table header is invalidated, false otherwise
673
+ */
674
+ private _localTableHeaderInvalidate(): boolean {
675
+ const { originalData } = this.getState();
676
+ const currentTableHeaders = this._theadElement
677
+ ? this._theadElement.querySelectorAll('th').length
678
+ : 0;
679
+ const totalColumns = originalData.length
680
+ ? Object.keys(originalData[0]).length
681
+ : 0;
682
+
683
+ return currentTableHeaders !== totalColumns;
684
+ }
685
+
686
+ /**
687
+ * Fetch data from the server
688
+ */
689
+ private async _fetchDataFromServer(): Promise<void> {
690
+ this._fireEvent('fetch');
691
+ this._dispatchEvent('fetch');
692
+
693
+ const queryParams = this._getQueryParamsForFetchRequest();
694
+ const response = await this._performFetchRequest(queryParams);
695
+
696
+ let responseData = null;
697
+
698
+ try {
699
+ responseData = await response.json();
700
+ } catch (error) {
701
+ this._noticeOnTable(
702
+ 'Error parsing API response as JSON: ' + String(error),
703
+ );
704
+ return;
705
+ }
706
+
707
+ this._fireEvent('fetched', { response: responseData });
708
+ this._dispatchEvent('fetched', { response: responseData });
709
+
710
+ // Use the mapResponse function to transform the data if provided
711
+ if (typeof this._config.mapResponse === 'function') {
712
+ responseData = this._config.mapResponse.call(this, responseData);
713
+ }
714
+
715
+ this._data = responseData.data;
716
+
717
+ this._config._state.totalItems = responseData.totalCount;
718
+
719
+ await this._draw();
720
+ this._fireEvent('fetched');
721
+ this._dispatchEvent('fetched');
722
+ }
723
+
724
+ /**
725
+ * Get the query params for a fetch request
726
+ * @returns The query params for the fetch request
727
+ */
728
+ private _getQueryParamsForFetchRequest(): URLSearchParams {
729
+ // Get the current state of the datatable
730
+ const { page, pageSize, sortField, sortOrder, filters, search } =
731
+ this.getState();
732
+
733
+ // Create a new URLSearchParams object to store the query params
734
+ let queryParams = new URLSearchParams();
735
+
736
+ // Add the current page number and page size to the query params
737
+ queryParams.set('page', String(page));
738
+ queryParams.set('size', String(pageSize));
739
+
740
+ // If there is a sort order and field set, add them to the query params
741
+ if (sortOrder !== undefined) {
742
+ queryParams.set('sortOrder', String(sortOrder));
743
+ }
744
+
745
+ if (sortField !== undefined) {
746
+ queryParams.set('sortField', String(sortField));
747
+ }
748
+
749
+ // If there are any filters set, add them to the query params
750
+ if (Array.isArray(filters) && filters.length) {
751
+ queryParams.set(
752
+ 'filters',
753
+ JSON.stringify(
754
+ filters.map((filter: KTDataTableColumnFilterInterface) => ({
755
+ // Map the filter object to a simpler object with just the necessary properties
756
+ column: filter.column,
757
+ type: filter.type,
758
+ value: filter.value,
759
+ })),
760
+ ),
761
+ );
762
+ }
763
+
764
+ if (search) {
765
+ queryParams.set(
766
+ 'search',
767
+ typeof search === 'object' ? JSON.stringify(search) : search,
768
+ );
769
+ }
770
+
771
+ // If a mapRequest function is provided, call it with the query params object
772
+ if (typeof this._config.mapRequest === 'function') {
773
+ queryParams = this._config.mapRequest.call(this, queryParams);
774
+ }
775
+
776
+ // Return the query params object
777
+ return queryParams;
778
+ }
779
+
780
+ private async _performFetchRequest(
781
+ queryParams: URLSearchParams,
782
+ ): Promise<Response> {
783
+ let requestMethod: RequestInit['method'] = this._config.requestMethod;
784
+ let requestBody: RequestInit['body'] | undefined = undefined;
785
+
786
+ // If the request method is POST, send the query params as the request body
787
+ if (requestMethod === 'POST') {
788
+ requestBody = queryParams;
789
+ } else if (requestMethod === 'GET') {
790
+ // If the request method is GET, append the query params to the API endpoint
791
+ const apiEndpointWithQueryParams = new URL(this._config.apiEndpoint);
792
+ apiEndpointWithQueryParams.search = queryParams.toString();
793
+ this._config.apiEndpoint = apiEndpointWithQueryParams.toString();
794
+ }
795
+
796
+ return fetch(this._config.apiEndpoint, {
797
+ method: requestMethod,
798
+ body: requestBody,
799
+ headers: this._config.requestHeaders,
800
+ }).catch((error) => {
801
+ // Trigger an error event
802
+ this._fireEvent('error', { error });
803
+ this._dispatchEvent('error', { error });
804
+
805
+ this._noticeOnTable('Error performing fetch request: ' + String(error));
806
+ throw error;
807
+ });
808
+ }
809
+
810
+ /**
811
+ * Update the table and pagination controls with new data
812
+ * @returns {Promise<void>} A promise that resolves when the table and pagination controls are updated
813
+ */
814
+ private async _draw(): Promise<void> {
815
+ this._config._state.totalPages =
816
+ Math.ceil(this.getState().totalItems / this.getState().pageSize) || 0;
817
+
818
+ this._fireEvent('draw');
819
+ this._dispatchEvent('draw');
820
+
821
+ this._dispose();
822
+
823
+ // Update the table and pagination controls
824
+ if (this._theadElement && this._tbodyElement) {
825
+ this._updateTable();
826
+ }
827
+
828
+ if (this._infoElement && this._paginationElement) {
829
+ this._updatePagination();
830
+ }
831
+
832
+ this._fireEvent('drew');
833
+ this._dispatchEvent('drew');
834
+
835
+ this._hideSpinner(); // Hide spinner after data is fetched
836
+
837
+ if (this._config.stateSave) {
838
+ this._saveState();
839
+ }
840
+ }
841
+
842
+ /**
843
+ * Update the HTML table with new data
844
+ * @returns {HTMLTableSectionElement} The new table body element
845
+ */
846
+ private _updateTable(): HTMLTableSectionElement {
847
+ // Clear the existing table contents using a more efficient method
848
+ while (this._tableElement.tBodies.length) {
849
+ this._tableElement.removeChild(this._tableElement.tBodies[0]);
850
+ }
851
+
852
+ // Create the table body with the new data
853
+ const tbodyElement =
854
+ this._tableElement.createTBody() as HTMLTableSectionElement;
855
+
856
+ // Apply the original class to the new tbody element
857
+ if (this._originalTbodyClass) {
858
+ tbodyElement.className = this._originalTbodyClass;
859
+ }
860
+
861
+ this._updateTableContent(tbodyElement);
862
+
863
+ return tbodyElement;
864
+ }
865
+
866
+ /**
867
+ * Update the table content
868
+ * @param tbodyElement The table body element
869
+ * @returns {HTMLTableSectionElement} The updated table body element
870
+ */
871
+ private _updateTableContent(
872
+ tbodyElement: HTMLTableSectionElement,
873
+ ): HTMLTableSectionElement {
874
+ const fragment = document.createDocumentFragment();
875
+
876
+ tbodyElement.textContent = ''; // Clear the tbody element
877
+
878
+ if (this._data.length === 0) {
879
+ this._noticeOnTable(this._config.infoEmpty || '');
880
+ return tbodyElement;
881
+ }
882
+
883
+ const ths: NodeListOf<HTMLTableCellElement> = this._theadElement
884
+ ? this._theadElement.querySelectorAll('th')
885
+ : ([] as unknown as NodeListOf<HTMLTableCellElement>);
886
+
887
+ this._data.forEach((item: T, rowIndex: number) => {
888
+ const row = document.createElement('tr');
889
+
890
+ // Apply original tr class if available
891
+ if (this._originalTrClasses && this._originalTrClasses[rowIndex]) {
892
+ row.className = this._originalTrClasses[rowIndex];
893
+ }
894
+
895
+ if (!this._config.columns) {
896
+ const dataRowAttributes = this.getState().originalDataAttributes
897
+ ? this.getState().originalDataAttributes[rowIndex]
898
+ : null;
899
+
900
+ // Use the order of <th> elements to render <td>s in the correct order
901
+ ths.forEach((th, colIndex) => {
902
+ const colName = th.getAttribute('data-kt-datatable-column');
903
+ const td = document.createElement('td');
904
+ let value: any;
905
+ if (colName && Object.prototype.hasOwnProperty.call(item, colName)) {
906
+ value = item[colName as keyof T];
907
+ } else if (Object.prototype.hasOwnProperty.call(item, colIndex)) {
908
+ value = item[colIndex as keyof T];
909
+ } else {
910
+ value = '';
911
+ }
912
+ td.innerHTML = value as string;
913
+
914
+ // Apply original td class if available
915
+ if (
916
+ this._originalTdClasses &&
917
+ this._originalTdClasses[rowIndex] &&
918
+ this._originalTdClasses[rowIndex][colIndex]
919
+ ) {
920
+ td.className = this._originalTdClasses[rowIndex][colIndex];
921
+ }
922
+
923
+ if (dataRowAttributes && dataRowAttributes[colIndex]) {
924
+ for (const attr in dataRowAttributes[colIndex]) {
925
+ td.setAttribute(attr, dataRowAttributes[colIndex][attr]);
926
+ }
927
+ }
928
+
929
+ row.appendChild(td);
930
+ });
931
+ } else {
932
+ Object.keys(this._config.columns).forEach(
933
+ (key: keyof T, colIndex: number) => {
934
+ const td = document.createElement('td');
935
+ const columnDef = this._config.columns[key as string];
936
+
937
+ // Apply original td class if available
938
+ if (
939
+ this._originalTdClasses &&
940
+ this._originalTdClasses[rowIndex] &&
941
+ this._originalTdClasses[rowIndex][colIndex]
942
+ ) {
943
+ td.className = this._originalTdClasses[rowIndex][colIndex];
944
+ }
945
+
946
+ if (typeof columnDef.render === 'function') {
947
+ td.innerHTML = columnDef.render.call(
948
+ this,
949
+ item[key] as string,
950
+ item,
951
+ this,
952
+ ) as string;
953
+ } else {
954
+ td.textContent = item[key] as string;
955
+ }
956
+
957
+ if (typeof columnDef.createdCell === 'function') {
958
+ columnDef.createdCell.call(this, td, item[key], item, row);
959
+ }
960
+
961
+ row.appendChild(td);
962
+ },
963
+ );
964
+ }
965
+
966
+ fragment.appendChild(row);
967
+ });
968
+
969
+ tbodyElement.appendChild(fragment);
970
+ return tbodyElement;
971
+ }
972
+
973
+ /**
974
+ * Show a notice on the table
975
+ * @param message The message to show. If empty, the message will be removed
976
+ * @returns {void}
977
+ */
978
+ private _noticeOnTable(message: string = ''): void {
979
+ const row = this._tableElement.tBodies[0].insertRow();
980
+ const cell = row.insertCell();
981
+ cell.colSpan = this._theadElement
982
+ ? this._theadElement.querySelectorAll('th').length
983
+ : 0;
984
+ cell.innerHTML = message;
985
+ }
986
+
987
+ private _updatePagination(): void {
988
+ this._removeChildElements(this._sizeElement);
989
+ this._createPageSizeControls(this._sizeElement);
990
+
991
+ this._removeChildElements(this._paginationElement);
992
+ this._createPaginationControls(this._infoElement, this._paginationElement);
993
+ }
994
+
995
+ /**
996
+ * Removes all child elements from the given container element.
997
+ * @param container The container element to remove the child elements from.
998
+ */
999
+ private _removeChildElements(container: HTMLElement): void {
1000
+ if (!container) {
1001
+ return;
1002
+ }
1003
+
1004
+ // Loop through all child elements of the container and remove them one by one
1005
+ while (container.firstChild) {
1006
+ // Remove the first child element (which is the first element in the list of child elements)
1007
+ container.removeChild(container.firstChild);
1008
+ }
1009
+ }
1010
+
1011
+ /**
1012
+ * Creates a container element for the items per page selector.
1013
+ * @param _sizeElement The element to create the page size controls in.
1014
+ * @returns The container element.
1015
+ */
1016
+ private _createPageSizeControls(
1017
+ _sizeElement: HTMLSelectElement,
1018
+ ): HTMLSelectElement {
1019
+ // If no element is provided, return early
1020
+ if (!_sizeElement) {
1021
+ return _sizeElement;
1022
+ }
1023
+
1024
+ // Create <option> elements for each page size option
1025
+ const options = this._config.pageSizes.map((size: number) => {
1026
+ const option = document.createElement('option') as HTMLOptionElement;
1027
+ option.value = String(size);
1028
+ option.text = String(size);
1029
+ option.selected = this.getState().pageSize === size;
1030
+ return option;
1031
+ });
1032
+
1033
+ // Add the <option> elements to the provided element
1034
+ _sizeElement.append(...options);
1035
+
1036
+ // Create an event listener for the "change" event on the element
1037
+ const _pageSizeControlsEvent = (event: Event) => {
1038
+ // When the element changes, reload the page with the new page size and page number 1
1039
+ this._reloadPageSize(
1040
+ Number((event.target as HTMLSelectElement).value),
1041
+ 1,
1042
+ );
1043
+ };
1044
+
1045
+ // Bind the event listener to the component instance
1046
+ _sizeElement.onchange = _pageSizeControlsEvent.bind(this);
1047
+
1048
+ // Return the element
1049
+ return _sizeElement;
1050
+ }
1051
+
1052
+ /**
1053
+ * Reloads the data with the specified page size and optional page number.
1054
+ * @param pageSize The new page size.
1055
+ * @param page The new page number (optional, defaults to 1).
1056
+ */
1057
+ private _reloadPageSize(pageSize: number, page: number = 1): void {
1058
+ // Update the page size and page number in the state
1059
+ this._config._state.pageSize = pageSize;
1060
+ this._config._state.page = page;
1061
+
1062
+ // Update the data with the new page size and page number
1063
+ this._updateData();
1064
+ }
1065
+
1066
+ /**
1067
+ * Creates the pagination controls for the component.
1068
+ * @param _infoElement The element to set the info text in.
1069
+ * @param _paginationElement The element to create the pagination controls in.
1070
+ * @return {HTMLElement} The element containing the pagination controls.
1071
+ */
1072
+ private _createPaginationControls(
1073
+ _infoElement: HTMLElement,
1074
+ _paginationElement: HTMLElement,
1075
+ ): HTMLElement {
1076
+ if (!_infoElement || !_paginationElement || this._data.length === 0) {
1077
+ return null;
1078
+ }
1079
+
1080
+ this._setPaginationInfoText(_infoElement);
1081
+ const paginationContainer =
1082
+ this._createPaginationContainer(_paginationElement);
1083
+
1084
+ if (paginationContainer) {
1085
+ this._createPaginationButtons(paginationContainer);
1086
+ }
1087
+
1088
+ return paginationContainer;
1089
+ }
1090
+
1091
+ /**
1092
+ * Sets the info text for the pagination controls.
1093
+ * @param _infoElement The element to set the info text in.
1094
+ */
1095
+ private _setPaginationInfoText(_infoElement: HTMLElement): void {
1096
+ _infoElement.textContent = this._config.info
1097
+ .replace(
1098
+ '{start}',
1099
+ (this.getState().page - 1) * this.getState().pageSize + 1 + '',
1100
+ )
1101
+ .replace(
1102
+ '{end}',
1103
+ Math.min(
1104
+ this.getState().page * this.getState().pageSize,
1105
+ this.getState().totalItems,
1106
+ ) + '',
1107
+ )
1108
+ .replace('{total}', this.getState().totalItems + '');
1109
+ }
1110
+
1111
+ /**
1112
+ * Creates the container element for the pagination controls.
1113
+ * @param _paginationElement The element to create the pagination controls in.
1114
+ * @return {HTMLElement} The container element.
1115
+ */
1116
+ private _createPaginationContainer(
1117
+ _paginationElement: HTMLElement,
1118
+ ): HTMLElement {
1119
+ // No longer create a wrapping div. Just return the pagination element itself.
1120
+ return _paginationElement;
1121
+ }
1122
+
1123
+ /**
1124
+ * Creates the pagination buttons for the component.
1125
+ * @param paginationContainer The container element for the pagination controls.
1126
+ */
1127
+ private _createPaginationButtons(paginationContainer: HTMLElement): void {
1128
+ const { page: currentPage, totalPages } = this.getState();
1129
+ const { previous, next, number, more } = this._config.pagination;
1130
+
1131
+ // Helper function to create a button
1132
+ const createButton = (
1133
+ text: string,
1134
+ className: string,
1135
+ disabled: boolean,
1136
+ handleClick: () => void,
1137
+ ): HTMLButtonElement => {
1138
+ const button = document.createElement('button') as HTMLButtonElement;
1139
+ button.className = className;
1140
+ button.innerHTML = text;
1141
+ button.disabled = disabled;
1142
+ button.onclick = handleClick;
1143
+ return button;
1144
+ };
1145
+
1146
+ // Add Previous Button
1147
+ paginationContainer.appendChild(
1148
+ createButton(
1149
+ previous.text,
1150
+ `${previous.class}${currentPage === 1 ? ' disabled' : ''}`,
1151
+ currentPage === 1,
1152
+ () => this._paginateData(currentPage - 1),
1153
+ ),
1154
+ );
1155
+
1156
+ // Calculate range of pages
1157
+ const pageMoreEnabled = this._config.pageMore;
1158
+
1159
+ if (pageMoreEnabled) {
1160
+ const maxButtons = this._config.pageMoreLimit;
1161
+ const range = this._calculatePageRange(
1162
+ currentPage,
1163
+ totalPages,
1164
+ maxButtons,
1165
+ );
1166
+
1167
+ // Add start ellipsis
1168
+ if (range.start > 1) {
1169
+ paginationContainer.appendChild(
1170
+ createButton(more.text, more.class, false, () =>
1171
+ this._paginateData(Math.max(1, range.start - 1)),
1172
+ ),
1173
+ );
1174
+ }
1175
+
1176
+ // Add page buttons
1177
+ for (let i = range.start; i <= range.end; i++) {
1178
+ paginationContainer.appendChild(
1179
+ createButton(
1180
+ number.text.replace('{page}', i.toString()),
1181
+ `${number.class}${currentPage === i ? ' active disabled' : ''}`,
1182
+ currentPage === i,
1183
+ () => this._paginateData(i),
1184
+ ),
1185
+ );
1186
+ }
1187
+
1188
+ // Add end ellipsis
1189
+ if (pageMoreEnabled && range.end < totalPages) {
1190
+ paginationContainer.appendChild(
1191
+ createButton(more.text, more.class, false, () =>
1192
+ this._paginateData(Math.min(totalPages, range.end + 1)),
1193
+ ),
1194
+ );
1195
+ }
1196
+ } else {
1197
+ // Add page buttons
1198
+ for (let i = 1; i <= totalPages; i++) {
1199
+ paginationContainer.appendChild(
1200
+ createButton(
1201
+ number.text.replace('{page}', i.toString()),
1202
+ `${number.class}${currentPage === i ? ' active disabled' : ''}`,
1203
+ currentPage === i,
1204
+ () => this._paginateData(i),
1205
+ ),
1206
+ );
1207
+ }
1208
+ }
1209
+
1210
+ // Add Next Button
1211
+ paginationContainer.appendChild(
1212
+ createButton(
1213
+ next.text,
1214
+ `${next.class}${currentPage === totalPages ? ' disabled' : ''}`,
1215
+ currentPage === totalPages,
1216
+ () => this._paginateData(currentPage + 1),
1217
+ ),
1218
+ );
1219
+ }
1220
+
1221
+ // New helper method to calculate page range
1222
+ private _calculatePageRange(
1223
+ currentPage: number,
1224
+ totalPages: number,
1225
+ maxButtons: number,
1226
+ ): { start: number; end: number } {
1227
+ let startPage: number, endPage: number;
1228
+ const halfMaxButtons = Math.floor(maxButtons / 2);
1229
+
1230
+ if (totalPages <= maxButtons) {
1231
+ startPage = 1;
1232
+ endPage = totalPages;
1233
+ } else {
1234
+ startPage = Math.max(currentPage - halfMaxButtons, 1);
1235
+ endPage = Math.min(startPage + maxButtons - 1, totalPages);
1236
+ if (endPage - startPage < maxButtons - 1) {
1237
+ startPage = Math.max(endPage - maxButtons + 1, 1);
1238
+ }
1239
+ }
1240
+
1241
+ return { start: startPage, end: endPage };
1242
+ }
1243
+
1244
+ /**
1245
+ * Method for handling pagination
1246
+ * @param page - The page number to navigate to
1247
+ */
1248
+ private _paginateData(page: number): void {
1249
+ if (page < 1 || !Number.isInteger(page)) {
1250
+ return;
1251
+ }
1252
+
1253
+ this._fireEvent('pagination', { page: page });
1254
+ this._dispatchEvent('pagination', { page: page });
1255
+
1256
+ if (page >= 1 && page <= this.getState().totalPages) {
1257
+ this._config._state.page = page;
1258
+ this._updateData();
1259
+ }
1260
+ }
1261
+
1262
+ // Method to show the loading spinner
1263
+ private _showSpinner(): void {
1264
+ const spinner =
1265
+ this._element.querySelector<HTMLElement>(
1266
+ this._config.attributes.spinner,
1267
+ ) || this._createSpinner();
1268
+ if (spinner) {
1269
+ spinner.style.display = 'block';
1270
+ }
1271
+ this._element.classList.add(this._config.loadingClass);
1272
+ }
1273
+
1274
+ // Method to hide the loading spinner
1275
+ private _hideSpinner(): void {
1276
+ const spinner = this._element.querySelector<HTMLElement>(
1277
+ this._config.attributes.spinner,
1278
+ );
1279
+ if (spinner) {
1280
+ spinner.style.display = 'none';
1281
+ }
1282
+ this._element.classList.remove(this._config.loadingClass);
1283
+ }
1284
+
1285
+ // Method to create a spinner element if it doesn't exist
1286
+ private _createSpinner(): HTMLElement {
1287
+ if (typeof this._config.loading === 'undefined') {
1288
+ return null;
1289
+ }
1290
+
1291
+ const template = document.createElement('template');
1292
+ template.innerHTML = this._config.loading.template
1293
+ .trim()
1294
+ .replace('{content}', this._config.loading.content);
1295
+ const spinner = template.content.firstChild as HTMLElement;
1296
+ spinner.setAttribute('data-kt-datatable-spinner', 'true');
1297
+
1298
+ this._tableElement.appendChild(spinner);
1299
+
1300
+ return spinner;
1301
+ }
1302
+
1303
+ /**
1304
+ * Saves the current state of the table to local storage.
1305
+ * @returns {void}
1306
+ */
1307
+ private _saveState(): void {
1308
+ this._fireEvent('stateSave');
1309
+ this._dispatchEvent('stateSave');
1310
+
1311
+ const ns: string = this._tableNamespace();
1312
+
1313
+ if (ns) {
1314
+ localStorage.setItem(
1315
+ ns,
1316
+ JSON.stringify(this.getState() as KTDataTableStateInterface),
1317
+ );
1318
+ }
1319
+ }
1320
+
1321
+ /**
1322
+ * Loads the saved state of the table from local storage, if it exists.
1323
+ * @returns {Object} The saved state of the table, or null if no saved state exists.
1324
+ */
1325
+ private _loadState(): KTDataTableStateInterface | null {
1326
+ const stateString = localStorage.getItem(this._tableNamespace());
1327
+ if (!stateString) return null;
1328
+
1329
+ try {
1330
+ const state = JSON.parse(stateString) as KTDataTableStateInterface;
1331
+ if (state) this._config._state = state;
1332
+ return state;
1333
+ } catch {} // eslint-disable-line no-empty
1334
+
1335
+ return null;
1336
+ }
1337
+
1338
+ private _deleteState(): void {
1339
+ const ns = this._tableNamespace();
1340
+
1341
+ if (ns) {
1342
+ localStorage.removeItem(ns);
1343
+ }
1344
+ }
1345
+
1346
+ /**
1347
+ * Gets the namespace for the table's state.
1348
+ * If a namespace is specified in the config, it is used.
1349
+ * Otherwise, if the table element has an ID, it is used.
1350
+ * Otherwise, if the component element has an ID, it is used.
1351
+ * Finally, the component's UID is used.
1352
+ * @returns {string} The namespace for the table's state.
1353
+ */
1354
+ private _tableNamespace(): string {
1355
+ // Use the specified namespace, if one is given
1356
+ if (this._config.stateNamespace) {
1357
+ return this._config.stateNamespace;
1358
+ }
1359
+
1360
+ // Fallback to the component's UID
1361
+ return this._tableId() ?? this._name;
1362
+ }
1363
+
1364
+ private _tableId(): string {
1365
+ let id: string = null;
1366
+ // If the table element has an ID, use that
1367
+ if (this._tableElement?.getAttribute('id')) {
1368
+ id = this._tableElement.getAttribute('id') as string;
1369
+ }
1370
+
1371
+ // If the component element has an ID, use that
1372
+ if (this._element?.getAttribute('id')) {
1373
+ id = this._element.getAttribute('id') as string;
1374
+ }
1375
+
1376
+ return id;
1377
+ }
1378
+
1379
+ private _dispose() {
1380
+ // Remove all event listeners and clean up resources
1381
+ }
1382
+
1383
+ private _debounce(func: Function, wait: number) {
1384
+ let timeout: number | undefined;
1385
+ return function (...args: any[]) {
1386
+ const later = () => {
1387
+ clearTimeout(timeout);
1388
+ func(...args);
1389
+ };
1390
+ clearTimeout(timeout);
1391
+ timeout = window.setTimeout(later, wait);
1392
+ };
1393
+ }
1394
+
1395
+ /**
1396
+ * Gets the current state of the table.
1397
+ * @returns {KTDataTableStateInterface} The current state of the table.
1398
+ */
1399
+ public getState(): KTDataTableStateInterface {
1400
+ return {
1401
+ /**
1402
+ * The current page number.
1403
+ */
1404
+ page: 1,
1405
+ /**
1406
+ * The field that the data is sorted by.
1407
+ */
1408
+ sortField: null,
1409
+ /**
1410
+ * The sort order (ascending or descending).
1411
+ */
1412
+ sortOrder: '',
1413
+ /**
1414
+ * The number of rows to display per page.
1415
+ */
1416
+ pageSize: this._config.pageSize,
1417
+
1418
+ filters: [],
1419
+
1420
+ /**
1421
+ * Any additional state that may have been stored in the config.
1422
+ */
1423
+ ...this._config._state,
1424
+ };
1425
+ }
1426
+
1427
+ /**
1428
+ * Sorts the data in the table by the specified field.
1429
+ * @param field The field to sort by.
1430
+ */
1431
+ public sort(field: keyof T | number): void {
1432
+ // Use the sort handler to update state and trigger sorting
1433
+ const state = this.getState();
1434
+ const sortOrder = this._sortHandler.toggleSortOrder(
1435
+ state.sortField,
1436
+ state.sortOrder,
1437
+ field,
1438
+ );
1439
+ this._sortHandler.setSortIcon(field as keyof T, sortOrder);
1440
+ this._config._state.sortField = field as never;
1441
+ this._config._state.sortOrder = sortOrder;
1442
+ this._fireEvent('sort', { field, order: sortOrder });
1443
+ this._dispatchEvent('sort', { field, order: sortOrder });
1444
+ this._updateData();
1445
+ }
1446
+
1447
+ /**
1448
+ * Navigates to the specified page in the data table.
1449
+ * @param page The page number to navigate to.
1450
+ */
1451
+ public goPage(page: number): void {
1452
+ if (page < 1 || !Number.isInteger(page)) {
1453
+ return;
1454
+ }
1455
+
1456
+ // Navigate to the specified page
1457
+ this._paginateData(page);
1458
+ }
1459
+
1460
+ /**
1461
+ * Set the page size of the data table.
1462
+ * @param pageSize The new page size.
1463
+ */
1464
+ public setPageSize(pageSize: number): void {
1465
+ if (!Number.isInteger(pageSize)) {
1466
+ return;
1467
+ }
1468
+
1469
+ /**
1470
+ * Reload the page size of the data table.
1471
+ * @param pageSize The new page size.
1472
+ */
1473
+ this._reloadPageSize(pageSize);
1474
+ }
1475
+
1476
+ /**
1477
+ * Reloads the data from the server and updates the table.
1478
+ * Triggers the 'reload' event and the 'kt.datatable.reload' custom event.
1479
+ */
1480
+ public reload(): void {
1481
+ this._fireEvent('reload');
1482
+ this._dispatchEvent('reload');
1483
+
1484
+ // Fetch the data from the server using the current sort and filter settings
1485
+ this._updateData();
1486
+ }
1487
+
1488
+ public redraw(page: number = 1): void {
1489
+ this._fireEvent('redraw');
1490
+ this._dispatchEvent('redraw');
1491
+
1492
+ this._paginateData(page);
1493
+ }
1494
+
1495
+ /**
1496
+ * Show the loading spinner of the data table.
1497
+ */
1498
+ public showSpinner(): void {
1499
+ /**
1500
+ * Show the loading spinner of the data table.
1501
+ */
1502
+ this._showSpinner();
1503
+ }
1504
+
1505
+ /**
1506
+ * Hide the loading spinner of the data table.
1507
+ */
1508
+ public hideSpinner(): void {
1509
+ /**
1510
+ * Hide the loading spinner of the data table.
1511
+ */
1512
+ this._hideSpinner();
1513
+ }
1514
+
1515
+ /**
1516
+ * Filter data using the specified filter object.
1517
+ * Replaces the existing filter object for the column with the new one.
1518
+ * @param filter Filter object containing the column name and its value.
1519
+ * @returns The KTDataTable instance.
1520
+ * @throws Error if the filter object is null or undefined.
1521
+ */
1522
+ public setFilter(filter: KTDataTableColumnFilterInterface): KTDataTable<T> {
1523
+ this._config._state.filters = [
1524
+ ...(this.getState().filters || []).filter(
1525
+ (f) => f.column !== filter.column,
1526
+ ),
1527
+ filter,
1528
+ ];
1529
+ return this;
1530
+ }
1531
+
1532
+ public override dispose(): void {
1533
+ this._dispose();
1534
+ }
1535
+
1536
+ public search(query: string | object): void {
1537
+ this._config._state.search = query;
1538
+ this.reload();
1539
+ }
1540
+
1541
+ /**
1542
+ * Static variables
1543
+ */
1544
+ private static _instances = new Map<
1545
+ HTMLElement,
1546
+ KTDataTable<KTDataTableDataInterface>
1547
+ >();
1548
+
1549
+ /**
1550
+ * Create KTDataTable instances for all elements with a data-kt-datatable="true" attribute.
1551
+ *
1552
+ * This function should be called after the control(s) have been
1553
+ * loaded and parsed by the browser. It will create instances of
1554
+ * KTDataTable for all elements with a data-kt-datatable="true" attribute.
1555
+ */
1556
+ public static createInstances(): void {
1557
+ const elements = document.querySelectorAll<HTMLElement>(
1558
+ '[data-kt-datatable="true"]',
1559
+ );
1560
+
1561
+ elements.forEach((element) => {
1562
+ if (
1563
+ element.hasAttribute('data-kt-datatable') &&
1564
+ !element.classList.contains('datatable-initialized')
1565
+ ) {
1566
+ /**
1567
+ * Create an instance of KTDataTable for the given element
1568
+ * @param element The element to create an instance for
1569
+ */
1570
+ const instance = new KTDataTable(element);
1571
+ this._instances.set(element, instance);
1572
+ }
1573
+ });
1574
+ }
1575
+
1576
+ /**
1577
+ * Get the KTDataTable instance for a given element.
1578
+ *
1579
+ * @param element The element to retrieve the instance for
1580
+ * @returns The KTDataTable instance or undefined if not found
1581
+ */
1582
+ public static getInstance(
1583
+ element: HTMLElement,
1584
+ ): KTDataTable<KTDataTableDataInterface> | undefined {
1585
+ return this._instances.get(element);
1586
+ }
1587
+
1588
+ /**
1589
+ * Initializes all KTDataTable instances on the page.
1590
+ *
1591
+ * This function should be called after the control(s) have been
1592
+ * loaded and parsed by the browser.
1593
+ */
1594
+ public static init(): void {
1595
+ // Create instances of KTDataTable for all elements with a
1596
+ // data-kt-datatable="true" attribute
1597
+ KTDataTable.createInstances();
1598
+ }
1599
+
1600
+ /**
1601
+ * Check if all visible rows are checked (header checkbox state)
1602
+ * @returns {boolean}
1603
+ */
1604
+ public isChecked(): boolean {
1605
+ return this._checkbox.isChecked();
1606
+ }
1607
+
1608
+ /**
1609
+ * Toggle all visible row checkboxes (header checkbox)
1610
+ * @returns {void}
1611
+ */
1612
+ public toggle(): void {
1613
+ this._checkbox.toggle();
1614
+ }
1615
+
1616
+ /**
1617
+ * Check all visible row checkboxes
1618
+ * @returns {void}
1619
+ */
1620
+ public check(): void {
1621
+ this._checkbox.check();
1622
+ this._fireEvent('checked');
1623
+ this._dispatchEvent('checked');
1624
+ }
1625
+
1626
+ /**
1627
+ * Uncheck all visible row checkboxes
1628
+ * @returns {void}
1629
+ */
1630
+ public uncheck(): void {
1631
+ this._checkbox.uncheck();
1632
+ this._fireEvent('unchecked');
1633
+ this._dispatchEvent('unchecked');
1634
+ }
1635
+
1636
+ /**
1637
+ * Get all checked row IDs (across all pages if preserveSelection is true)
1638
+ * @returns {string[]}
1639
+ */
1640
+ public getChecked(): string[] {
1641
+ return this._checkbox.getChecked();
1642
+ }
1643
+
1644
+ /**
1645
+ * Reapply checked state to visible checkboxes (after redraw/pagination)
1646
+ * @returns {void}
1647
+ */
1648
+ public update(): void {
1649
+ this._checkbox.updateState();
1650
+ }
1651
+
1652
+ // Other plugin methods can be added here
1653
+ }
1654
+
1655
+ if (typeof window !== 'undefined') {
1656
+ window.KTDataTable = KTDataTable;
1657
+ }