@keenthemes/ktui 1.2.6 → 1.2.7

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 (190) hide show
  1. package/README.md +14 -5
  2. package/dist/ktui.js +3775 -2298
  3. package/dist/ktui.min.js +1 -1
  4. package/dist/ktui.min.js.map +1 -1
  5. package/dist/styles.css +25 -5
  6. package/lib/cjs/components/datatable/datatable-checkbox.d.ts +37 -1
  7. package/lib/cjs/components/datatable/datatable-checkbox.d.ts.map +1 -1
  8. package/lib/cjs/components/datatable/datatable-checkbox.js +143 -156
  9. package/lib/cjs/components/datatable/datatable-checkbox.js.map +1 -1
  10. package/lib/cjs/components/datatable/datatable-column-utils.d.ts +30 -0
  11. package/lib/cjs/components/datatable/datatable-column-utils.d.ts.map +1 -0
  12. package/lib/cjs/components/datatable/datatable-column-utils.js +42 -0
  13. package/lib/cjs/components/datatable/datatable-column-utils.js.map +1 -0
  14. package/lib/cjs/components/datatable/datatable-contracts.d.ts +2 -4
  15. package/lib/cjs/components/datatable/datatable-contracts.d.ts.map +1 -1
  16. package/lib/cjs/components/datatable/datatable-defaults.d.ts +20 -0
  17. package/lib/cjs/components/datatable/datatable-defaults.d.ts.map +1 -0
  18. package/lib/cjs/components/datatable/datatable-defaults.js +193 -0
  19. package/lib/cjs/components/datatable/datatable-defaults.js.map +1 -0
  20. package/lib/cjs/components/datatable/datatable-layout-plugin.d.ts.map +1 -1
  21. package/lib/cjs/components/datatable/datatable-layout-plugin.js +11 -1
  22. package/lib/cjs/components/datatable/datatable-layout-plugin.js.map +1 -1
  23. package/lib/cjs/components/datatable/datatable-local-provider.d.ts.map +1 -1
  24. package/lib/cjs/components/datatable/datatable-local-provider.js +80 -24
  25. package/lib/cjs/components/datatable/datatable-local-provider.js.map +1 -1
  26. package/lib/cjs/components/datatable/datatable-pagination-renderer.d.ts.map +1 -1
  27. package/lib/cjs/components/datatable/datatable-pagination-renderer.js +3 -2
  28. package/lib/cjs/components/datatable/datatable-pagination-renderer.js.map +1 -1
  29. package/lib/cjs/components/datatable/datatable-registry.d.ts +18 -0
  30. package/lib/cjs/components/datatable/datatable-registry.d.ts.map +1 -0
  31. package/lib/cjs/components/datatable/datatable-registry.js +66 -0
  32. package/lib/cjs/components/datatable/datatable-registry.js.map +1 -0
  33. package/lib/cjs/components/datatable/datatable-remote-provider.d.ts.map +1 -1
  34. package/lib/cjs/components/datatable/datatable-remote-provider.js +1 -2
  35. package/lib/cjs/components/datatable/datatable-remote-provider.js.map +1 -1
  36. package/lib/cjs/components/datatable/datatable-search-handler.d.ts +10 -0
  37. package/lib/cjs/components/datatable/datatable-search-handler.d.ts.map +1 -0
  38. package/lib/cjs/components/datatable/datatable-search-handler.js +65 -0
  39. package/lib/cjs/components/datatable/datatable-search-handler.js.map +1 -0
  40. package/lib/cjs/components/datatable/datatable-sort.d.ts +31 -4
  41. package/lib/cjs/components/datatable/datatable-sort.d.ts.map +1 -1
  42. package/lib/cjs/components/datatable/datatable-sort.js +86 -58
  43. package/lib/cjs/components/datatable/datatable-sort.js.map +1 -1
  44. package/lib/cjs/components/datatable/datatable-spinner.d.ts +30 -0
  45. package/lib/cjs/components/datatable/datatable-spinner.d.ts.map +1 -0
  46. package/lib/cjs/components/datatable/datatable-spinner.js +54 -0
  47. package/lib/cjs/components/datatable/datatable-spinner.js.map +1 -0
  48. package/lib/cjs/components/datatable/datatable-state-persistence.d.ts +19 -0
  49. package/lib/cjs/components/datatable/datatable-state-persistence.d.ts.map +1 -0
  50. package/lib/cjs/components/datatable/datatable-state-persistence.js +59 -0
  51. package/lib/cjs/components/datatable/datatable-state-persistence.js.map +1 -0
  52. package/lib/cjs/components/datatable/datatable-table-renderer.d.ts +2 -0
  53. package/lib/cjs/components/datatable/datatable-table-renderer.d.ts.map +1 -1
  54. package/lib/cjs/components/datatable/datatable-table-renderer.js +75 -16
  55. package/lib/cjs/components/datatable/datatable-table-renderer.js.map +1 -1
  56. package/lib/cjs/components/datatable/datatable-utils.d.ts +10 -0
  57. package/lib/cjs/components/datatable/datatable-utils.d.ts.map +1 -0
  58. package/lib/cjs/components/datatable/datatable-utils.js +15 -0
  59. package/lib/cjs/components/datatable/datatable-utils.js.map +1 -0
  60. package/lib/cjs/components/datatable/datatable.d.ts +26 -34
  61. package/lib/cjs/components/datatable/datatable.d.ts.map +1 -1
  62. package/lib/cjs/components/datatable/datatable.js +155 -492
  63. package/lib/cjs/components/datatable/datatable.js.map +1 -1
  64. package/lib/cjs/components/datatable/index.d.ts +1 -1
  65. package/lib/cjs/components/datatable/index.d.ts.map +1 -1
  66. package/lib/cjs/components/datatable/types.d.ts +100 -11
  67. package/lib/cjs/components/datatable/types.d.ts.map +1 -1
  68. package/lib/cjs/index.d.ts +1 -1
  69. package/lib/cjs/index.d.ts.map +1 -1
  70. package/lib/cjs/index.js +6 -0
  71. package/lib/cjs/index.js.map +1 -1
  72. package/lib/esm/components/datatable/datatable-checkbox.d.ts +37 -1
  73. package/lib/esm/components/datatable/datatable-checkbox.d.ts.map +1 -1
  74. package/lib/esm/components/datatable/datatable-checkbox.js +142 -155
  75. package/lib/esm/components/datatable/datatable-checkbox.js.map +1 -1
  76. package/lib/esm/components/datatable/datatable-column-utils.d.ts +30 -0
  77. package/lib/esm/components/datatable/datatable-column-utils.d.ts.map +1 -0
  78. package/lib/esm/components/datatable/datatable-column-utils.js +38 -0
  79. package/lib/esm/components/datatable/datatable-column-utils.js.map +1 -0
  80. package/lib/esm/components/datatable/datatable-contracts.d.ts +2 -4
  81. package/lib/esm/components/datatable/datatable-contracts.d.ts.map +1 -1
  82. package/lib/esm/components/datatable/datatable-defaults.d.ts +20 -0
  83. package/lib/esm/components/datatable/datatable-defaults.d.ts.map +1 -0
  84. package/lib/esm/components/datatable/datatable-defaults.js +190 -0
  85. package/lib/esm/components/datatable/datatable-defaults.js.map +1 -0
  86. package/lib/esm/components/datatable/datatable-layout-plugin.d.ts.map +1 -1
  87. package/lib/esm/components/datatable/datatable-layout-plugin.js +11 -1
  88. package/lib/esm/components/datatable/datatable-layout-plugin.js.map +1 -1
  89. package/lib/esm/components/datatable/datatable-local-provider.d.ts.map +1 -1
  90. package/lib/esm/components/datatable/datatable-local-provider.js +80 -24
  91. package/lib/esm/components/datatable/datatable-local-provider.js.map +1 -1
  92. package/lib/esm/components/datatable/datatable-pagination-renderer.d.ts.map +1 -1
  93. package/lib/esm/components/datatable/datatable-pagination-renderer.js +3 -2
  94. package/lib/esm/components/datatable/datatable-pagination-renderer.js.map +1 -1
  95. package/lib/esm/components/datatable/datatable-registry.d.ts +18 -0
  96. package/lib/esm/components/datatable/datatable-registry.d.ts.map +1 -0
  97. package/lib/esm/components/datatable/datatable-registry.js +63 -0
  98. package/lib/esm/components/datatable/datatable-registry.js.map +1 -0
  99. package/lib/esm/components/datatable/datatable-remote-provider.d.ts.map +1 -1
  100. package/lib/esm/components/datatable/datatable-remote-provider.js +1 -2
  101. package/lib/esm/components/datatable/datatable-remote-provider.js.map +1 -1
  102. package/lib/esm/components/datatable/datatable-search-handler.d.ts +10 -0
  103. package/lib/esm/components/datatable/datatable-search-handler.d.ts.map +1 -0
  104. package/lib/esm/components/datatable/datatable-search-handler.js +62 -0
  105. package/lib/esm/components/datatable/datatable-search-handler.js.map +1 -0
  106. package/lib/esm/components/datatable/datatable-sort.d.ts +31 -4
  107. package/lib/esm/components/datatable/datatable-sort.d.ts.map +1 -1
  108. package/lib/esm/components/datatable/datatable-sort.js +85 -57
  109. package/lib/esm/components/datatable/datatable-sort.js.map +1 -1
  110. package/lib/esm/components/datatable/datatable-spinner.d.ts +30 -0
  111. package/lib/esm/components/datatable/datatable-spinner.d.ts.map +1 -0
  112. package/lib/esm/components/datatable/datatable-spinner.js +51 -0
  113. package/lib/esm/components/datatable/datatable-spinner.js.map +1 -0
  114. package/lib/esm/components/datatable/datatable-state-persistence.d.ts +19 -0
  115. package/lib/esm/components/datatable/datatable-state-persistence.d.ts.map +1 -0
  116. package/lib/esm/components/datatable/datatable-state-persistence.js +55 -0
  117. package/lib/esm/components/datatable/datatable-state-persistence.js.map +1 -0
  118. package/lib/esm/components/datatable/datatable-table-renderer.d.ts +2 -0
  119. package/lib/esm/components/datatable/datatable-table-renderer.d.ts.map +1 -1
  120. package/lib/esm/components/datatable/datatable-table-renderer.js +75 -16
  121. package/lib/esm/components/datatable/datatable-table-renderer.js.map +1 -1
  122. package/lib/esm/components/datatable/datatable-utils.d.ts +10 -0
  123. package/lib/esm/components/datatable/datatable-utils.d.ts.map +1 -0
  124. package/lib/esm/components/datatable/datatable-utils.js +12 -0
  125. package/lib/esm/components/datatable/datatable-utils.js.map +1 -0
  126. package/lib/esm/components/datatable/datatable.d.ts +26 -34
  127. package/lib/esm/components/datatable/datatable.d.ts.map +1 -1
  128. package/lib/esm/components/datatable/datatable.js +157 -494
  129. package/lib/esm/components/datatable/datatable.js.map +1 -1
  130. package/lib/esm/components/datatable/index.d.ts +1 -1
  131. package/lib/esm/components/datatable/index.d.ts.map +1 -1
  132. package/lib/esm/components/datatable/types.d.ts +100 -11
  133. package/lib/esm/components/datatable/types.d.ts.map +1 -1
  134. package/lib/esm/index.d.ts +1 -1
  135. package/lib/esm/index.d.ts.map +1 -1
  136. package/lib/esm/index.js +6 -0
  137. package/lib/esm/index.js.map +1 -1
  138. package/package.json +5 -1
  139. package/skills/ktui/SKILL.md +711 -0
  140. package/skills/ktui-datatable/SKILL.md +302 -0
  141. package/skills/ktui-install/SKILL.md +150 -0
  142. package/skills/ktui-select/SKILL.md +271 -0
  143. package/src/components/__tests__/component.test.ts +347 -0
  144. package/src/components/collapse/collapse.css +2 -2
  145. package/src/components/datatable/__tests__/architecture-boundaries.test.ts +56 -8
  146. package/src/components/datatable/__tests__/currency-sort.test.ts +25 -28
  147. package/src/components/datatable/__tests__/datatable-checkbox.test.ts +527 -0
  148. package/src/components/datatable/__tests__/datatable-column-utils.test.ts +117 -0
  149. package/src/components/datatable/__tests__/datatable-defaults.test.ts +57 -0
  150. package/src/components/datatable/__tests__/datatable-finalize-extended.test.ts +361 -0
  151. package/src/components/datatable/__tests__/datatable-fixed-layout.test.ts +427 -0
  152. package/src/components/datatable/__tests__/datatable-improvements.test.ts +484 -0
  153. package/src/components/datatable/__tests__/datatable-pagination-extended.test.ts +508 -0
  154. package/src/components/datatable/__tests__/datatable-public-api.test.ts +269 -0
  155. package/src/components/datatable/__tests__/datatable-registry.test.ts +172 -0
  156. package/src/components/datatable/__tests__/datatable-remote-provider.test.ts +468 -0
  157. package/src/components/datatable/__tests__/datatable-search-handler.test.ts +124 -0
  158. package/src/components/datatable/__tests__/datatable-sort-extended.test.ts +417 -0
  159. package/src/components/datatable/__tests__/datatable-spinner.test.ts +95 -0
  160. package/src/components/datatable/__tests__/datatable-table-renderer-extended.test.ts +425 -0
  161. package/src/components/datatable/__tests__/datatable-types.test.ts +117 -0
  162. package/src/components/datatable/__tests__/datatable-utils.test.ts +52 -0
  163. package/src/components/datatable/__tests__/multi-row-headers.test.ts +7 -7
  164. package/src/components/datatable/__tests__/pagination-reset.test.ts +129 -6
  165. package/src/components/datatable/__tests__/race-conditions.test.ts +11 -11
  166. package/src/components/datatable/__tests__/setup.ts +12 -4
  167. package/src/components/datatable/datatable-checkbox.ts +144 -145
  168. package/src/components/datatable/datatable-column-utils.ts +63 -0
  169. package/src/components/datatable/datatable-contracts.ts +2 -3
  170. package/src/components/datatable/datatable-defaults.ts +204 -0
  171. package/src/components/datatable/datatable-layout-plugin.ts +11 -1
  172. package/src/components/datatable/datatable-local-provider.ts +91 -28
  173. package/src/components/datatable/datatable-pagination-renderer.ts +3 -2
  174. package/src/components/datatable/datatable-registry.ts +89 -0
  175. package/src/components/datatable/datatable-remote-provider.ts +1 -3
  176. package/src/components/datatable/datatable-search-handler.ts +97 -0
  177. package/src/components/datatable/datatable-sort.ts +111 -66
  178. package/src/components/datatable/datatable-spinner.ts +103 -0
  179. package/src/components/datatable/datatable-state-persistence.ts +67 -0
  180. package/src/components/datatable/datatable-table-renderer.ts +81 -18
  181. package/src/components/datatable/datatable-utils.ts +12 -0
  182. package/src/components/datatable/datatable.ts +191 -580
  183. package/src/components/datatable/index.ts +3 -0
  184. package/src/components/datatable/types.ts +124 -23
  185. package/src/helpers/__tests__/dom.test.ts +776 -0
  186. package/src/helpers/__tests__/utils.test.ts +332 -0
  187. package/src/index.ts +10 -0
  188. package/skills/ktui-components/SKILL.md +0 -41
  189. package/skills/ktui-theming/SKILL.md +0 -50
  190. package/src/components/datatable/datatable-event-adapter.ts +0 -21
@@ -0,0 +1,63 @@
1
+ /**
2
+ * KTUI - Free & Open-Source Tailwind UI Components by Keenthemes
3
+ * Copyright 2025 by Keenthemes Inc
4
+ */
5
+
6
+ /**
7
+ * Shared column resolution utilities for KTDataTable.
8
+ * Eliminates duplicated column discovery logic across local-provider,
9
+ * table-renderer, and the main datatable class.
10
+ */
11
+
12
+ export interface ResolvedColumns {
13
+ /** All <th> elements from the thead */
14
+ allThs: HTMLTableCellElement[];
15
+ /** Only <th> elements that have data-kt-datatable-column attribute */
16
+ typedThs: HTMLTableCellElement[];
17
+ /** The column list to use for rendering: typedThs if all ths have the attr, else allThs */
18
+ columnsByIndex: HTMLTableCellElement[];
19
+ }
20
+
21
+ /**
22
+ * Resolve column headers from a thead element.
23
+ * Returns both the raw th list and the typed (data-kt-datatable-column) subset.
24
+ * When some ths lack the attribute, columnsByIndex falls back to all ths by index.
25
+ */
26
+ export function resolveColumns(
27
+ theadElement: HTMLTableSectionElement | null,
28
+ ): ResolvedColumns {
29
+ const allThs: HTMLTableCellElement[] = theadElement
30
+ ? Array.from(theadElement.querySelectorAll('th'))
31
+ : [];
32
+
33
+ const typedThs = allThs.filter((th) =>
34
+ th.hasAttribute('data-kt-datatable-column'),
35
+ );
36
+
37
+ const columnsByIndex = typedThs.length > 0 ? typedThs : allThs;
38
+
39
+ return { allThs, typedThs, columnsByIndex };
40
+ }
41
+
42
+ /**
43
+ * Get the logical column count: number of data columns.
44
+ * Prefers originalData keys, falls back to first tbody row td count,
45
+ * then to resolved columns from thead.
46
+ */
47
+ export function getLogicalColumnCount(
48
+ theadElement: HTMLTableSectionElement | null,
49
+ tbodyElement: HTMLTableSectionElement | null,
50
+ originalData?: Array<Record<string, unknown>>,
51
+ ): number {
52
+ if (originalData && originalData.length > 0) {
53
+ return Object.keys(originalData[0]).length;
54
+ }
55
+ if (tbodyElement) {
56
+ const firstRow = tbodyElement.querySelector<HTMLTableRowElement>('tr');
57
+ if (firstRow) {
58
+ return firstRow.querySelectorAll('td').length;
59
+ }
60
+ }
61
+ const { columnsByIndex } = resolveColumns(theadElement);
62
+ return columnsByIndex.length;
63
+ }
@@ -11,6 +11,7 @@ import {
11
11
  KTDataTableInterface,
12
12
  KTDataTableSortOrderInterface,
13
13
  KTDataTableStateInterface,
14
+ OriginalTableClasses,
14
15
  } from './types';
15
16
 
16
17
  export type KTDataTableEmit = (eventName: string, eventData?: object) => void;
@@ -64,9 +65,7 @@ export interface KTDataTableTableRendererInput<
64
65
  data: T[];
65
66
  getLogicalColumnCount: () => number;
66
67
  getState: () => KTDataTableStateInterface;
67
- originalTbodyClass: string;
68
- originalTrClasses: string[];
69
- originalTdClasses: string[][];
68
+ originalClasses: OriginalTableClasses;
70
69
  tableElement: HTMLTableElement;
71
70
  theadElement: HTMLTableSectionElement;
72
71
  }
@@ -0,0 +1,204 @@
1
+ /**
2
+ * KTUI - Free & Open-Source Tailwind UI Components by Keenthemes
3
+ * Copyright 2025 by Keenthemes Inc
4
+ */
5
+
6
+ import { KTDataTableConfigInterface, KTDataTableStateInterface } from './types';
7
+
8
+ /** Default page size options shown in the size selector */
9
+ export const DEFAULT_PAGE_SIZES = [5, 10, 20, 30, 50] as const;
10
+
11
+ /** Default debounce delay (ms) for search input */
12
+ export const DEFAULT_SEARCH_DELAY = 500;
13
+
14
+ /** Default maximum visible page buttons before showing '...' */
15
+ export const DEFAULT_PAGE_MORE_LIMIT = 3;
16
+
17
+ /**
18
+ * Default configuration for KTDataTable.
19
+ * Extracted from _initDefaultConfig() for separation of concerns.
20
+ * Note: sort.callback and search.callback are NOT included here because
21
+ * they require instance context (this._sortHandler). They are added
22
+ * in datatable.ts _initDefaultConfig().
23
+ */
24
+ export const DATATABLE_DEFAULTS: Readonly<KTDataTableConfigInterface> = {
25
+ /**
26
+ * HTTP method for server-side API call
27
+ */
28
+ requestMethod: 'GET',
29
+ /**
30
+ * Custom HTTP headers for the API request
31
+ */
32
+ requestHeaders: {
33
+ 'Content-Type': 'application/x-www-form-urlencoded',
34
+ },
35
+ /**
36
+ * Pagination info template
37
+ */
38
+ info: '{start}-{end} of {total}',
39
+ /**
40
+ * Info text when there is no data
41
+ */
42
+ infoEmpty: 'No records found',
43
+ /**
44
+ * Available page sizes
45
+ */
46
+ pageSizes: [...DEFAULT_PAGE_SIZES],
47
+ /**
48
+ * Default page size
49
+ */
50
+ pageSize: 10,
51
+ /**
52
+ * Enable or disable pagination more button
53
+ */
54
+ pageMore: true,
55
+ /**
56
+ * Maximum number of pages before enabling pagination more button
57
+ */
58
+ pageMoreLimit: 3,
59
+ /**
60
+ * Pagination button templates
61
+ */
62
+ pagination: {
63
+ number: {
64
+ /**
65
+ * CSS classes to be added to the pagination button
66
+ */
67
+ class: 'kt-datatable-pagination-button',
68
+ /**
69
+ * Text to be displayed in the pagination button
70
+ */
71
+ text: '{page}',
72
+ },
73
+ previous: {
74
+ /**
75
+ * CSS classes to be added to the previous pagination button
76
+ */
77
+ class: 'kt-datatable-pagination-button kt-datatable-pagination-prev',
78
+ /**
79
+ * Text to be displayed in the previous pagination button
80
+ */
81
+ text: `
82
+ <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">
83
+ <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"/>
84
+ </svg>
85
+ `,
86
+ },
87
+ next: {
88
+ /**
89
+ * CSS classes to be added to the next pagination button
90
+ */
91
+ class: 'kt-datatable-pagination-button kt-datatable-pagination-next',
92
+ /**
93
+ * Text to be displayed in the next pagination button
94
+ */
95
+ text: `
96
+ <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">
97
+ <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"/>
98
+ </svg>
99
+ `,
100
+ },
101
+ more: {
102
+ /**
103
+ * CSS classes to be added to the pagination more button
104
+ */
105
+ class: 'kt-datatable-pagination-button kt-datatable-pagination-more',
106
+ /**
107
+ * Text to be displayed in the pagination more button
108
+ */
109
+ text: '...',
110
+ },
111
+ },
112
+ /**
113
+ * Sorting options — classes only; callback is added in datatable.ts
114
+ */
115
+ sort: {
116
+ /**
117
+ * CSS classes to be added to the sortable headers
118
+ */
119
+ classes: {
120
+ base: 'kt-table-col',
121
+ asc: 'asc',
122
+ desc: 'desc',
123
+ },
124
+ },
125
+ /**
126
+ * Search options — delay only; callback is added in datatable.ts
127
+ */
128
+ search: {
129
+ /**
130
+ * Delay in milliseconds before the search function is applied to the data array
131
+ * @default 500
132
+ */
133
+ delay: 500, // ms
134
+ },
135
+ /**
136
+ * Loading spinner options
137
+ */
138
+ loading: {
139
+ /**
140
+ * Template to be displayed during data fetching process
141
+ */
142
+ template: `
143
+ <div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
144
+ <div class="kt-datatable-loading">
145
+ <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">
146
+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="3"></circle>
147
+ <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>
148
+ </svg>
149
+ {content}
150
+ </div>
151
+ </div>
152
+ `,
153
+ /**
154
+ * Loading text to be displayed in the template
155
+ */
156
+ content: 'Loading...',
157
+ },
158
+ /**
159
+ * Selectors of the elements to be targeted
160
+ */
161
+ attributes: {
162
+ /**
163
+ * Data table element
164
+ */
165
+ table: 'table[data-kt-datatable-table="true"]',
166
+ /**
167
+ * Pagination info element
168
+ */
169
+ info: '[data-kt-datatable-info="true"]',
170
+ /**
171
+ * Page size dropdown element
172
+ */
173
+ size: '[data-kt-datatable-size="true"]',
174
+ /**
175
+ * Pagination element
176
+ */
177
+ pagination: '[data-kt-datatable-pagination="true"]',
178
+ /**
179
+ * Spinner element
180
+ */
181
+ spinner: '[data-kt-datatable-spinner="true"]',
182
+ /**
183
+ * Checkbox element
184
+ */
185
+ check: '[data-kt-datatable-check="true"]',
186
+ checkbox: '[data-kt-datatable-row-check="true"]',
187
+ },
188
+ /**
189
+ * Enable or disable state saving
190
+ */
191
+ stateSave: true,
192
+ checkbox: {
193
+ checkedClass: 'checked',
194
+ },
195
+ /**
196
+ * Table layout algorithm: 'auto' (default) or 'fixed' for consistent column widths
197
+ */
198
+ tableLayout: 'auto',
199
+ /**
200
+ * Private properties
201
+ */
202
+ _state: {} as KTDataTableStateInterface,
203
+ loadingClass: 'loading',
204
+ };
@@ -434,8 +434,18 @@ export const createStickyLayoutPlugin =
434
434
  detachResizeListener();
435
435
  applyLayout(ctx);
436
436
 
437
+ // Throttle re-layout with rAF to avoid running on every resize/scroll event
438
+ let rafId = 0;
439
+ const throttledApply = () => {
440
+ if (rafId) return;
441
+ rafId = requestAnimationFrame(() => {
442
+ rafId = 0;
443
+ applyLayout(ctx);
444
+ });
445
+ };
446
+
437
447
  const scrollContainer = getScrollContainer(ctx.rootElement);
438
- resizeHandler = () => applyLayout(ctx);
448
+ resizeHandler = throttledApply;
439
449
  window.addEventListener('resize', resizeHandler);
440
450
  scrollContainerTarget = scrollContainer;
441
451
  scrollContainer.addEventListener('scroll', resizeHandler);
@@ -15,6 +15,34 @@ import {
15
15
  KTDataTableProviderResult,
16
16
  KTDataTableStateStore,
17
17
  } from './datatable-contracts';
18
+ import { resolveColumns } from './datatable-column-utils';
19
+ import { stripHtml } from './datatable-utils';
20
+
21
+ type FilterMatcher = (cellValue: unknown, filterValue: unknown) => boolean;
22
+
23
+ const FILTER_MATCHERS: Record<string, FilterMatcher> = {
24
+ text: (cellValue, filterValue) => {
25
+ if (!filterValue) return true;
26
+ return stripHtml(cellValue)
27
+ .toLowerCase()
28
+ .includes(String(filterValue).toLowerCase());
29
+ },
30
+ numeric: (cellValue, filterValue) => {
31
+ const num = parseFloat(
32
+ String(cellValue ?? '').replace(/[^0-9.-]/g, ''),
33
+ );
34
+ return !Number.isNaN(num) && num === filterValue;
35
+ },
36
+ dateRange: (cellValue, filterValue) => {
37
+ const range = filterValue as { from?: string; to?: string };
38
+ if (!range?.from && !range?.to) return true;
39
+ const cellDate = new Date(String(cellValue ?? ''));
40
+ if (Number.isNaN(cellDate.getTime())) return false;
41
+ if (range.from && cellDate < new Date(range.from)) return false;
42
+ if (range.to && cellDate > new Date(range.to)) return false;
43
+ return true;
44
+ },
45
+ };
18
46
 
19
47
  interface KTDataTableLocalProviderOptions {
20
48
  config: KTDataTableConfigInterface;
@@ -76,6 +104,18 @@ export class KTDataTableLocalDataProvider<
76
104
  }
77
105
  }
78
106
 
107
+ // Apply column filters
108
+ const { filters } = this.options.stateStore.getState();
109
+ if (filters && filters.length > 0) {
110
+ filteredData = data = data.filter((item: T) => {
111
+ return filters.every((filter) => {
112
+ const cellValue = item[filter.column as keyof T];
113
+ const matcher = FILTER_MATCHERS[filter.type];
114
+ return matcher ? matcher(cellValue, filter.value) : true;
115
+ });
116
+ }) as T[];
117
+ }
118
+
79
119
  const sortCallback = this.options.config.sort?.callback;
80
120
  if (
81
121
  sortField !== undefined &&
@@ -110,6 +150,30 @@ export class KTDataTableLocalDataProvider<
110
150
  );
111
151
 
112
152
  if (this.options.stateStore.getState()._contentChecksum !== checksum) {
153
+ const domRowCount =
154
+ tbodyElement.querySelectorAll<HTMLTableRowElement>('tr').length;
155
+ const storedRowCount =
156
+ this.options.stateStore.getState().originalData?.length ?? 0;
157
+
158
+ // Programmatic dataset with an empty tbody must not re-import from the DOM.
159
+ if (domRowCount === 0 && storedRowCount > 0) {
160
+ return false;
161
+ }
162
+
163
+ // After pagination redraw the tbody only holds the current page. A checksum
164
+ // mismatch there must not shrink originalData to pageSize rows.
165
+ if (
166
+ storedRowCount > 0 &&
167
+ domRowCount > 0 &&
168
+ domRowCount < storedRowCount
169
+ ) {
170
+ const { pageSize } = this.options.stateStore.getState();
171
+ if (domRowCount <= pageSize) {
172
+ this.options.stateStore.patchState({ _contentChecksum: checksum });
173
+ return false;
174
+ }
175
+ }
176
+
113
177
  this.options.stateStore.patchState({ _contentChecksum: checksum });
114
178
  return true;
115
179
  }
@@ -120,10 +184,14 @@ export class KTDataTableLocalDataProvider<
120
184
  private tableConfigInvalidate(): boolean {
121
185
  const { _state, ...restConfig } = this.options.config;
122
186
  const checksum: string = KTUtils.checksum(JSON.stringify(restConfig));
187
+ const previousChecksum = _state?._configChecksum ?? '';
123
188
 
124
- if ((_state?._configChecksum ?? '') !== checksum) {
189
+ if (previousChecksum !== checksum) {
125
190
  this.options.stateStore.patchState({ _configChecksum: checksum });
126
- return true;
191
+ // First load skips this check (originalData === undefined). On the first
192
+ // pagination fetch, previousChecksum is still empty — record it but do
193
+ // not re-extract from the paginated DOM (that would shrink originalData).
194
+ return previousChecksum !== '';
127
195
  }
128
196
 
129
197
  return false;
@@ -140,15 +208,7 @@ export class KTDataTableLocalDataProvider<
140
208
  this.options.storeOriginalClasses();
141
209
 
142
210
  const rows = tbodyElement.querySelectorAll<HTMLTableRowElement>('tr');
143
- const allThs: NodeListOf<HTMLTableCellElement> = theadElement
144
- ? theadElement.querySelectorAll('th')
145
- : ([] as unknown as NodeListOf<HTMLTableCellElement>);
146
-
147
- const ths: HTMLTableCellElement[] = Array.from(allThs).filter((th) =>
148
- th.hasAttribute('data-kt-datatable-column'),
149
- );
150
- const columnsByIndex: HTMLTableCellElement[] =
151
- ths.length > 0 && ths.length !== allThs.length ? Array.from(allThs) : ths;
211
+ const { columnsByIndex } = resolveColumns(theadElement);
152
212
 
153
213
  rows.forEach((row: HTMLTableRowElement) => {
154
214
  const dataRow: T = {} as T;
@@ -177,25 +237,28 @@ export class KTDataTableLocalDataProvider<
177
237
 
178
238
  private localTableHeaderInvalidate(): boolean {
179
239
  const { originalData } = this.options.stateStore.getState();
240
+ if (!originalData?.length) {
241
+ return false;
242
+ }
243
+
180
244
  const { theadElement } = this.options.elements();
245
+ const { typedThs } = resolveColumns(theadElement);
181
246
 
182
- const totalColumns = originalData.length
183
- ? Object.keys(originalData[0]).length
184
- : 0;
247
+ if (typedThs.length === 0) {
248
+ return (
249
+ this.options.getLogicalColumnCount() !==
250
+ Object.keys(originalData[0]).length
251
+ );
252
+ }
185
253
 
186
- const allThs: NodeListOf<HTMLTableCellElement> = theadElement
187
- ? theadElement.querySelectorAll('th')
188
- : ([] as unknown as NodeListOf<HTMLTableCellElement>);
189
- const thsWithColumn = Array.from(allThs).filter((th) =>
190
- th.hasAttribute('data-kt-datatable-column'),
191
- );
192
- const currentTableHeaders =
193
- thsWithColumn.length > 0
194
- ? thsWithColumn.length !== allThs.length
195
- ? allThs.length
196
- : thsWithColumn.length
197
- : this.options.getLogicalColumnCount();
198
-
199
- return currentTableHeaders !== totalColumns;
254
+ const typedColumnNames = typedThs
255
+ .map((th) => th.getAttribute('data-kt-datatable-column'))
256
+ .filter((name): name is string => Boolean(name));
257
+ const dataColumnKeys = Object.keys(originalData[0]);
258
+ const matchingTypedColumns = typedColumnNames.filter((name) =>
259
+ dataColumnKeys.includes(name),
260
+ ).length;
261
+
262
+ return typedColumnNames.length !== matchingTypedColumns;
200
263
  }
201
264
  }
@@ -8,6 +8,7 @@ import {
8
8
  KTDataTablePaginationRenderer,
9
9
  KTDataTablePaginationRendererInput,
10
10
  } from './datatable-contracts';
11
+ import { DEFAULT_PAGE_MORE_LIMIT, DEFAULT_PAGE_SIZES } from './datatable-defaults';
11
12
 
12
13
  export class KTDataTableDomPaginationRenderer implements KTDataTablePaginationRenderer {
13
14
  public render(
@@ -51,7 +52,7 @@ export class KTDataTableDomPaginationRenderer implements KTDataTablePaginationRe
51
52
  return;
52
53
  }
53
54
 
54
- const pageSizes = input.config.pageSizes ?? [5, 10, 20, 30, 50];
55
+ const pageSizes = input.config.pageSizes ?? DEFAULT_PAGE_SIZES;
55
56
  const options = pageSizes.map((size: number) => {
56
57
  const option = document.createElement('option') as HTMLOptionElement;
57
58
  option.value = String(size);
@@ -117,7 +118,7 @@ export class KTDataTableDomPaginationRenderer implements KTDataTablePaginationRe
117
118
 
118
119
  const { page: currentPage, totalPages } = input.state;
119
120
  const { previous, next, number, more } = pagination;
120
- const pageMoreLimit = input.config.pageMoreLimit ?? 3;
121
+ const pageMoreLimit = input.config.pageMoreLimit ?? DEFAULT_PAGE_MORE_LIMIT;
121
122
 
122
123
  const createButton = (
123
124
  text: string,
@@ -0,0 +1,89 @@
1
+ /**
2
+ * KTUI - Free & Open-Source Tailwind UI Components by Keenthemes
3
+ * Copyright 2025 by Keenthemes Inc
4
+ */
5
+
6
+ import KTData from '../../helpers/data';
7
+
8
+ type DataTableInstance = {
9
+ dispose(): void;
10
+ };
11
+
12
+ export interface KTDataTableRegistry<T extends DataTableInstance> {
13
+ register(element: HTMLElement, instance: T): void;
14
+ get(element: HTMLElement): T | undefined;
15
+ remove(element: HTMLElement): void;
16
+ clear(): void;
17
+ createAll(factory: (element: HTMLElement) => T): void;
18
+ reinit(factory: (element: HTMLElement) => T): void;
19
+ }
20
+
21
+ export function createDataTableRegistry<
22
+ T extends DataTableInstance,
23
+ >(): KTDataTableRegistry<T> {
24
+ type ElementWithInstance = HTMLElement & { instance?: T };
25
+ const instances = new Map<HTMLElement, T>();
26
+
27
+ function toElementWithInstance(element: HTMLElement): ElementWithInstance {
28
+ return element as ElementWithInstance;
29
+ }
30
+
31
+ function register(element: HTMLElement, instance: T): void {
32
+ instances.set(element, instance);
33
+ toElementWithInstance(element).instance = instance;
34
+ }
35
+
36
+ function get(element: HTMLElement): T | undefined {
37
+ return instances.get(element) ?? toElementWithInstance(element).instance;
38
+ }
39
+
40
+ function remove(element: HTMLElement): void {
41
+ instances.delete(element);
42
+ const el = toElementWithInstance(element);
43
+ if (el.instance) delete el.instance;
44
+ }
45
+
46
+ function clear(): void {
47
+ instances.clear();
48
+ }
49
+
50
+ function createAll(factory: (element: HTMLElement) => T): void {
51
+ if (typeof document === 'undefined') return;
52
+ const elements = document.querySelectorAll<HTMLElement>(
53
+ '[data-kt-datatable="true"]',
54
+ );
55
+ elements.forEach((element) => {
56
+ if (
57
+ element.hasAttribute('data-kt-datatable') &&
58
+ !element.classList.contains('datatable-initialized')
59
+ ) {
60
+ const instance = factory(element);
61
+ register(element, instance);
62
+ }
63
+ });
64
+ }
65
+
66
+ function reinit(factory: (element: HTMLElement) => T): void {
67
+ if (typeof document === 'undefined') return;
68
+ const elements = document.querySelectorAll<HTMLElement>(
69
+ '[data-kt-datatable="true"]',
70
+ );
71
+ elements.forEach((element) => {
72
+ try {
73
+ const instance = get(element);
74
+ if (instance && typeof instance.dispose === 'function') {
75
+ instance.dispose();
76
+ }
77
+ KTData.remove(element, 'datatable');
78
+ element.removeAttribute('data-kt-datatable-initialized');
79
+ element.classList.remove('datatable-initialized');
80
+ } catch {
81
+ // ignore per-element errors
82
+ }
83
+ });
84
+ clear();
85
+ createAll(factory);
86
+ }
87
+
88
+ return { register, get, remove, clear, createAll, reinit };
89
+ }
@@ -61,7 +61,7 @@ export class KTDataTableRemoteDataProvider<
61
61
  try {
62
62
  responseData = await response.json();
63
63
  } catch (error) {
64
- this.options.eventAdapter.emit('parseError', {
64
+ this.options.eventAdapter.emit('fetchError', {
65
65
  response,
66
66
  error: String(error),
67
67
  status: response.status,
@@ -74,8 +74,6 @@ export class KTDataTableRemoteDataProvider<
74
74
  return { data: [], totalItems: 0, skipped: true };
75
75
  }
76
76
 
77
- this.options.eventAdapter.emit('fetched', { response: responseData });
78
-
79
77
  if (typeof this.options.config.mapResponse === 'function') {
80
78
  responseData = this.options.config.mapResponse.call(this, responseData);
81
79
  }