@keenthemes/ktui 1.2.5 → 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 (196) hide show
  1. package/README.md +14 -5
  2. package/dist/ktui.js +1538 -786
  3. package/dist/ktui.min.js +1 -1
  4. package/dist/ktui.min.js.map +1 -1
  5. package/dist/styles.css +85 -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 +7 -0
  21. package/lib/cjs/components/datatable/datatable-layout-plugin.d.ts.map +1 -0
  22. package/lib/cjs/components/datatable/datatable-layout-plugin.js +338 -0
  23. package/lib/cjs/components/datatable/datatable-layout-plugin.js.map +1 -0
  24. package/lib/cjs/components/datatable/datatable-local-provider.d.ts +2 -2
  25. package/lib/cjs/components/datatable/datatable-local-provider.d.ts.map +1 -1
  26. package/lib/cjs/components/datatable/datatable-local-provider.js +85 -27
  27. package/lib/cjs/components/datatable/datatable-local-provider.js.map +1 -1
  28. package/lib/cjs/components/datatable/datatable-pagination-renderer.d.ts.map +1 -1
  29. package/lib/cjs/components/datatable/datatable-pagination-renderer.js +13 -13
  30. package/lib/cjs/components/datatable/datatable-pagination-renderer.js.map +1 -1
  31. package/lib/cjs/components/datatable/datatable-registry.d.ts +18 -0
  32. package/lib/cjs/components/datatable/datatable-registry.d.ts.map +1 -0
  33. package/lib/cjs/components/datatable/datatable-registry.js +66 -0
  34. package/lib/cjs/components/datatable/datatable-registry.js.map +1 -0
  35. package/lib/cjs/components/datatable/datatable-remote-provider.d.ts.map +1 -1
  36. package/lib/cjs/components/datatable/datatable-remote-provider.js +1 -2
  37. package/lib/cjs/components/datatable/datatable-remote-provider.js.map +1 -1
  38. package/lib/cjs/components/datatable/datatable-search-handler.d.ts +10 -0
  39. package/lib/cjs/components/datatable/datatable-search-handler.d.ts.map +1 -0
  40. package/lib/cjs/components/datatable/datatable-search-handler.js +65 -0
  41. package/lib/cjs/components/datatable/datatable-search-handler.js.map +1 -0
  42. package/lib/cjs/components/datatable/datatable-sort.d.ts +31 -4
  43. package/lib/cjs/components/datatable/datatable-sort.d.ts.map +1 -1
  44. package/lib/cjs/components/datatable/datatable-sort.js +86 -58
  45. package/lib/cjs/components/datatable/datatable-sort.js.map +1 -1
  46. package/lib/cjs/components/datatable/datatable-spinner.d.ts +30 -0
  47. package/lib/cjs/components/datatable/datatable-spinner.d.ts.map +1 -0
  48. package/lib/cjs/components/datatable/datatable-spinner.js +54 -0
  49. package/lib/cjs/components/datatable/datatable-spinner.js.map +1 -0
  50. package/lib/cjs/components/datatable/datatable-state-persistence.d.ts +19 -0
  51. package/lib/cjs/components/datatable/datatable-state-persistence.d.ts.map +1 -0
  52. package/lib/cjs/components/datatable/datatable-state-persistence.js +59 -0
  53. package/lib/cjs/components/datatable/datatable-state-persistence.js.map +1 -0
  54. package/lib/cjs/components/datatable/datatable-table-renderer.d.ts +2 -0
  55. package/lib/cjs/components/datatable/datatable-table-renderer.d.ts.map +1 -1
  56. package/lib/cjs/components/datatable/datatable-table-renderer.js +75 -16
  57. package/lib/cjs/components/datatable/datatable-table-renderer.js.map +1 -1
  58. package/lib/cjs/components/datatable/datatable-utils.d.ts +10 -0
  59. package/lib/cjs/components/datatable/datatable-utils.d.ts.map +1 -0
  60. package/lib/cjs/components/datatable/datatable-utils.js +15 -0
  61. package/lib/cjs/components/datatable/datatable-utils.js.map +1 -0
  62. package/lib/cjs/components/datatable/datatable.d.ts +35 -34
  63. package/lib/cjs/components/datatable/datatable.d.ts.map +1 -1
  64. package/lib/cjs/components/datatable/datatable.js +233 -497
  65. package/lib/cjs/components/datatable/datatable.js.map +1 -1
  66. package/lib/cjs/components/datatable/index.d.ts +1 -1
  67. package/lib/cjs/components/datatable/index.d.ts.map +1 -1
  68. package/lib/cjs/components/datatable/types.d.ts +127 -11
  69. package/lib/cjs/components/datatable/types.d.ts.map +1 -1
  70. package/lib/cjs/index.d.ts +1 -1
  71. package/lib/cjs/index.d.ts.map +1 -1
  72. package/lib/cjs/index.js +6 -0
  73. package/lib/cjs/index.js.map +1 -1
  74. package/lib/esm/components/datatable/datatable-checkbox.d.ts +37 -1
  75. package/lib/esm/components/datatable/datatable-checkbox.d.ts.map +1 -1
  76. package/lib/esm/components/datatable/datatable-checkbox.js +142 -155
  77. package/lib/esm/components/datatable/datatable-checkbox.js.map +1 -1
  78. package/lib/esm/components/datatable/datatable-column-utils.d.ts +30 -0
  79. package/lib/esm/components/datatable/datatable-column-utils.d.ts.map +1 -0
  80. package/lib/esm/components/datatable/datatable-column-utils.js +38 -0
  81. package/lib/esm/components/datatable/datatable-column-utils.js.map +1 -0
  82. package/lib/esm/components/datatable/datatable-contracts.d.ts +2 -4
  83. package/lib/esm/components/datatable/datatable-contracts.d.ts.map +1 -1
  84. package/lib/esm/components/datatable/datatable-defaults.d.ts +20 -0
  85. package/lib/esm/components/datatable/datatable-defaults.d.ts.map +1 -0
  86. package/lib/esm/components/datatable/datatable-defaults.js +190 -0
  87. package/lib/esm/components/datatable/datatable-defaults.js.map +1 -0
  88. package/lib/esm/components/datatable/datatable-layout-plugin.d.ts +7 -0
  89. package/lib/esm/components/datatable/datatable-layout-plugin.d.ts.map +1 -0
  90. package/lib/esm/components/datatable/datatable-layout-plugin.js +334 -0
  91. package/lib/esm/components/datatable/datatable-layout-plugin.js.map +1 -0
  92. package/lib/esm/components/datatable/datatable-local-provider.d.ts +2 -2
  93. package/lib/esm/components/datatable/datatable-local-provider.d.ts.map +1 -1
  94. package/lib/esm/components/datatable/datatable-local-provider.js +85 -27
  95. package/lib/esm/components/datatable/datatable-local-provider.js.map +1 -1
  96. package/lib/esm/components/datatable/datatable-pagination-renderer.d.ts.map +1 -1
  97. package/lib/esm/components/datatable/datatable-pagination-renderer.js +13 -13
  98. package/lib/esm/components/datatable/datatable-pagination-renderer.js.map +1 -1
  99. package/lib/esm/components/datatable/datatable-registry.d.ts +18 -0
  100. package/lib/esm/components/datatable/datatable-registry.d.ts.map +1 -0
  101. package/lib/esm/components/datatable/datatable-registry.js +63 -0
  102. package/lib/esm/components/datatable/datatable-registry.js.map +1 -0
  103. package/lib/esm/components/datatable/datatable-remote-provider.d.ts.map +1 -1
  104. package/lib/esm/components/datatable/datatable-remote-provider.js +1 -2
  105. package/lib/esm/components/datatable/datatable-remote-provider.js.map +1 -1
  106. package/lib/esm/components/datatable/datatable-search-handler.d.ts +10 -0
  107. package/lib/esm/components/datatable/datatable-search-handler.d.ts.map +1 -0
  108. package/lib/esm/components/datatable/datatable-search-handler.js +62 -0
  109. package/lib/esm/components/datatable/datatable-search-handler.js.map +1 -0
  110. package/lib/esm/components/datatable/datatable-sort.d.ts +31 -4
  111. package/lib/esm/components/datatable/datatable-sort.d.ts.map +1 -1
  112. package/lib/esm/components/datatable/datatable-sort.js +85 -57
  113. package/lib/esm/components/datatable/datatable-sort.js.map +1 -1
  114. package/lib/esm/components/datatable/datatable-spinner.d.ts +30 -0
  115. package/lib/esm/components/datatable/datatable-spinner.d.ts.map +1 -0
  116. package/lib/esm/components/datatable/datatable-spinner.js +51 -0
  117. package/lib/esm/components/datatable/datatable-spinner.js.map +1 -0
  118. package/lib/esm/components/datatable/datatable-state-persistence.d.ts +19 -0
  119. package/lib/esm/components/datatable/datatable-state-persistence.d.ts.map +1 -0
  120. package/lib/esm/components/datatable/datatable-state-persistence.js +55 -0
  121. package/lib/esm/components/datatable/datatable-state-persistence.js.map +1 -0
  122. package/lib/esm/components/datatable/datatable-table-renderer.d.ts +2 -0
  123. package/lib/esm/components/datatable/datatable-table-renderer.d.ts.map +1 -1
  124. package/lib/esm/components/datatable/datatable-table-renderer.js +75 -16
  125. package/lib/esm/components/datatable/datatable-table-renderer.js.map +1 -1
  126. package/lib/esm/components/datatable/datatable-utils.d.ts +10 -0
  127. package/lib/esm/components/datatable/datatable-utils.d.ts.map +1 -0
  128. package/lib/esm/components/datatable/datatable-utils.js +12 -0
  129. package/lib/esm/components/datatable/datatable-utils.js.map +1 -0
  130. package/lib/esm/components/datatable/datatable.d.ts +35 -34
  131. package/lib/esm/components/datatable/datatable.d.ts.map +1 -1
  132. package/lib/esm/components/datatable/datatable.js +235 -499
  133. package/lib/esm/components/datatable/datatable.js.map +1 -1
  134. package/lib/esm/components/datatable/index.d.ts +1 -1
  135. package/lib/esm/components/datatable/index.d.ts.map +1 -1
  136. package/lib/esm/components/datatable/types.d.ts +127 -11
  137. package/lib/esm/components/datatable/types.d.ts.map +1 -1
  138. package/lib/esm/index.d.ts +1 -1
  139. package/lib/esm/index.d.ts.map +1 -1
  140. package/lib/esm/index.js +6 -0
  141. package/lib/esm/index.js.map +1 -1
  142. package/package.json +5 -1
  143. package/skills/ktui/SKILL.md +711 -0
  144. package/skills/ktui-datatable/SKILL.md +302 -0
  145. package/skills/ktui-install/SKILL.md +150 -0
  146. package/skills/ktui-select/SKILL.md +271 -0
  147. package/src/components/__tests__/component.test.ts +347 -0
  148. package/src/components/collapse/collapse.css +2 -2
  149. package/src/components/datatable/__tests__/architecture-boundaries.test.ts +56 -8
  150. package/src/components/datatable/__tests__/currency-sort.test.ts +25 -28
  151. package/src/components/datatable/__tests__/datatable-checkbox.test.ts +527 -0
  152. package/src/components/datatable/__tests__/datatable-column-utils.test.ts +117 -0
  153. package/src/components/datatable/__tests__/datatable-defaults.test.ts +57 -0
  154. package/src/components/datatable/__tests__/datatable-finalize-extended.test.ts +361 -0
  155. package/src/components/datatable/__tests__/datatable-fixed-layout.test.ts +427 -0
  156. package/src/components/datatable/__tests__/datatable-improvements.test.ts +484 -0
  157. package/src/components/datatable/__tests__/datatable-pagination-extended.test.ts +508 -0
  158. package/src/components/datatable/__tests__/datatable-public-api.test.ts +269 -0
  159. package/src/components/datatable/__tests__/datatable-registry.test.ts +172 -0
  160. package/src/components/datatable/__tests__/datatable-remote-provider.test.ts +468 -0
  161. package/src/components/datatable/__tests__/datatable-search-handler.test.ts +124 -0
  162. package/src/components/datatable/__tests__/datatable-sort-extended.test.ts +417 -0
  163. package/src/components/datatable/__tests__/datatable-spinner.test.ts +95 -0
  164. package/src/components/datatable/__tests__/datatable-table-renderer-extended.test.ts +425 -0
  165. package/src/components/datatable/__tests__/datatable-types.test.ts +117 -0
  166. package/src/components/datatable/__tests__/datatable-utils.test.ts +52 -0
  167. package/src/components/datatable/__tests__/locked-layout.test.ts +257 -0
  168. package/src/components/datatable/__tests__/multi-row-headers.test.ts +7 -7
  169. package/src/components/datatable/__tests__/pagination-reset.test.ts +147 -6
  170. package/src/components/datatable/__tests__/race-conditions.test.ts +11 -11
  171. package/src/components/datatable/__tests__/setup.ts +12 -4
  172. package/src/components/datatable/datatable-checkbox.ts +139 -143
  173. package/src/components/datatable/datatable-column-utils.ts +63 -0
  174. package/src/components/datatable/datatable-contracts.ts +2 -3
  175. package/src/components/datatable/datatable-defaults.ts +204 -0
  176. package/src/components/datatable/datatable-layout-plugin.ts +459 -0
  177. package/src/components/datatable/datatable-local-provider.ts +106 -35
  178. package/src/components/datatable/datatable-pagination-renderer.ts +13 -15
  179. package/src/components/datatable/datatable-registry.ts +89 -0
  180. package/src/components/datatable/datatable-remote-provider.ts +1 -3
  181. package/src/components/datatable/datatable-search-handler.ts +97 -0
  182. package/src/components/datatable/datatable-sort.ts +111 -66
  183. package/src/components/datatable/datatable-spinner.ts +103 -0
  184. package/src/components/datatable/datatable-state-persistence.ts +67 -0
  185. package/src/components/datatable/datatable-table-renderer.ts +81 -18
  186. package/src/components/datatable/datatable-utils.ts +12 -0
  187. package/src/components/datatable/datatable.css +98 -0
  188. package/src/components/datatable/datatable.ts +288 -583
  189. package/src/components/datatable/index.ts +8 -0
  190. package/src/components/datatable/types.ts +157 -23
  191. package/src/helpers/__tests__/dom.test.ts +776 -0
  192. package/src/helpers/__tests__/utils.test.ts +332 -0
  193. package/src/index.ts +15 -0
  194. package/skills/ktui-components/SKILL.md +0 -41
  195. package/skills/ktui-theming/SKILL.md +0 -50
  196. package/src/components/datatable/datatable-event-adapter.ts +0 -21
@@ -8,7 +8,6 @@ import {
8
8
  KTDataTableAttributeInterface,
9
9
  KTDataTableConfigInterface,
10
10
  KTDataTableDataInterface,
11
- KTDataTableStateInterface,
12
11
  } from './types';
13
12
  import {
14
13
  KTDataTableDataProvider,
@@ -16,8 +15,36 @@ import {
16
15
  KTDataTableProviderResult,
17
16
  KTDataTableStateStore,
18
17
  } from './datatable-contracts';
18
+ import { resolveColumns } from './datatable-column-utils';
19
+ import { stripHtml } from './datatable-utils';
19
20
 
20
- interface KTDataTableLocalProviderOptions<T extends KTDataTableDataInterface> {
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
+ };
46
+
47
+ interface KTDataTableLocalProviderOptions {
21
48
  config: KTDataTableConfigInterface;
22
49
  elements: () => KTDataTableLocalProviderElements;
23
50
  getLogicalColumnCount: () => number;
@@ -28,7 +55,7 @@ interface KTDataTableLocalProviderOptions<T extends KTDataTableDataInterface> {
28
55
  export class KTDataTableLocalDataProvider<
29
56
  T extends KTDataTableDataInterface,
30
57
  > implements KTDataTableDataProvider<T> {
31
- constructor(private readonly options: KTDataTableLocalProviderOptions<T>) {}
58
+ constructor(private readonly options: KTDataTableLocalProviderOptions) {}
32
59
 
33
60
  public async fetch(): Promise<KTDataTableProviderResult<T>> {
34
61
  return this.fetchSync();
@@ -37,13 +64,17 @@ export class KTDataTableLocalDataProvider<
37
64
  public fetchSync(): KTDataTableProviderResult<T> {
38
65
  const state = this.options.stateStore.getState();
39
66
  let { originalData } = state;
67
+ const skipDomInvalidation = Boolean(
68
+ this.options.config.lockedLayout || this.options.config.layoutPlugin,
69
+ );
40
70
 
41
71
  if (
42
72
  !this.options.elements().tableElement ||
43
73
  originalData === undefined ||
44
- this.tableConfigInvalidate() ||
45
- this.localTableHeaderInvalidate() ||
46
- this.localTableContentInvalidate()
74
+ (!skipDomInvalidation &&
75
+ (this.tableConfigInvalidate() ||
76
+ this.localTableHeaderInvalidate() ||
77
+ this.localTableContentInvalidate()))
47
78
  ) {
48
79
  const { originalData, originalDataAttributes } =
49
80
  this.localExtractTableContent();
@@ -73,6 +104,18 @@ export class KTDataTableLocalDataProvider<
73
104
  }
74
105
  }
75
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
+
76
119
  const sortCallback = this.options.config.sort?.callback;
77
120
  if (
78
121
  sortField !== undefined &&
@@ -80,7 +123,12 @@ export class KTDataTableLocalDataProvider<
80
123
  sortOrder !== '' &&
81
124
  typeof sortCallback === 'function'
82
125
  ) {
83
- data = sortCallback.call(this, data, sortField as string, sortOrder) as T[];
126
+ data = sortCallback.call(
127
+ this,
128
+ data,
129
+ sortField as string,
130
+ sortOrder,
131
+ ) as T[];
84
132
  }
85
133
 
86
134
  if (data?.length > 0) {
@@ -102,6 +150,30 @@ export class KTDataTableLocalDataProvider<
102
150
  );
103
151
 
104
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
+
105
177
  this.options.stateStore.patchState({ _contentChecksum: checksum });
106
178
  return true;
107
179
  }
@@ -112,10 +184,14 @@ export class KTDataTableLocalDataProvider<
112
184
  private tableConfigInvalidate(): boolean {
113
185
  const { _state, ...restConfig } = this.options.config;
114
186
  const checksum: string = KTUtils.checksum(JSON.stringify(restConfig));
187
+ const previousChecksum = _state?._configChecksum ?? '';
115
188
 
116
- if ((_state?._configChecksum ?? '') !== checksum) {
189
+ if (previousChecksum !== checksum) {
117
190
  this.options.stateStore.patchState({ _configChecksum: checksum });
118
- 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 !== '';
119
195
  }
120
196
 
121
197
  return false;
@@ -132,15 +208,7 @@ export class KTDataTableLocalDataProvider<
132
208
  this.options.storeOriginalClasses();
133
209
 
134
210
  const rows = tbodyElement.querySelectorAll<HTMLTableRowElement>('tr');
135
- const allThs: NodeListOf<HTMLTableCellElement> = theadElement
136
- ? theadElement.querySelectorAll('th')
137
- : ([] as unknown as NodeListOf<HTMLTableCellElement>);
138
-
139
- const ths: HTMLTableCellElement[] = Array.from(allThs).filter((th) =>
140
- th.hasAttribute('data-kt-datatable-column'),
141
- );
142
- const columnsByIndex: HTMLTableCellElement[] =
143
- ths.length > 0 && ths.length !== allThs.length ? Array.from(allThs) : ths;
211
+ const { columnsByIndex } = resolveColumns(theadElement);
144
212
 
145
213
  rows.forEach((row: HTMLTableRowElement) => {
146
214
  const dataRow: T = {} as T;
@@ -169,25 +237,28 @@ export class KTDataTableLocalDataProvider<
169
237
 
170
238
  private localTableHeaderInvalidate(): boolean {
171
239
  const { originalData } = this.options.stateStore.getState();
240
+ if (!originalData?.length) {
241
+ return false;
242
+ }
243
+
172
244
  const { theadElement } = this.options.elements();
245
+ const { typedThs } = resolveColumns(theadElement);
173
246
 
174
- const totalColumns = originalData.length
175
- ? Object.keys(originalData[0]).length
176
- : 0;
247
+ if (typedThs.length === 0) {
248
+ return (
249
+ this.options.getLogicalColumnCount() !==
250
+ Object.keys(originalData[0]).length
251
+ );
252
+ }
177
253
 
178
- const allThs: NodeListOf<HTMLTableCellElement> = theadElement
179
- ? theadElement.querySelectorAll('th')
180
- : ([] as unknown as NodeListOf<HTMLTableCellElement>);
181
- const thsWithColumn = Array.from(allThs).filter((th) =>
182
- th.hasAttribute('data-kt-datatable-column'),
183
- );
184
- const currentTableHeaders =
185
- thsWithColumn.length > 0
186
- ? thsWithColumn.length !== allThs.length
187
- ? allThs.length
188
- : thsWithColumn.length
189
- : this.options.getLogicalColumnCount();
190
-
191
- 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;
192
263
  }
193
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(
@@ -26,6 +27,7 @@ export class KTDataTableDomPaginationRenderer implements KTDataTablePaginationRe
26
27
  return () => {
27
28
  if (input.sizeElement) {
28
29
  input.sizeElement.onchange = null;
30
+ this.removeChildElements(input.sizeElement);
29
31
  }
30
32
  if (input.paginationElement) {
31
33
  this.removeChildElements(input.paginationElement);
@@ -50,19 +52,16 @@ export class KTDataTableDomPaginationRenderer implements KTDataTablePaginationRe
50
52
  return;
51
53
  }
52
54
 
53
- const pageSizes = input.config.pageSizes ?? [5, 10, 20, 30, 50];
55
+ const pageSizes = input.config.pageSizes ?? DEFAULT_PAGE_SIZES;
56
+ const options = pageSizes.map((size: number) => {
57
+ const option = document.createElement('option') as HTMLOptionElement;
58
+ option.value = String(size);
59
+ option.text = String(size);
60
+ option.selected = input.state.pageSize === size;
61
+ return option;
62
+ });
54
63
 
55
- setTimeout(() => {
56
- const options = pageSizes.map((size: number) => {
57
- const option = document.createElement('option') as HTMLOptionElement;
58
- option.value = String(size);
59
- option.text = String(size);
60
- option.selected = input.state.pageSize === size;
61
- return option;
62
- });
63
-
64
- input.sizeElement.append(...options);
65
- }, 100);
64
+ input.sizeElement.append(...options);
66
65
 
67
66
  input.sizeElement.onchange = (event: Event) => {
68
67
  input.reloadPageSize(
@@ -92,8 +91,7 @@ export class KTDataTableDomPaginationRenderer implements KTDataTablePaginationRe
92
91
  return;
93
92
  }
94
93
 
95
- const infoTemplate =
96
- input.config.info ?? '{start}-{end} of {total}';
94
+ const infoTemplate = input.config.info ?? '{start}-{end} of {total}';
97
95
  input.infoElement.textContent = infoTemplate
98
96
  .replace(
99
97
  '{start}',
@@ -120,7 +118,7 @@ export class KTDataTableDomPaginationRenderer implements KTDataTablePaginationRe
120
118
 
121
119
  const { page: currentPage, totalPages } = input.state;
122
120
  const { previous, next, number, more } = pagination;
123
- const pageMoreLimit = input.config.pageMoreLimit ?? 3;
121
+ const pageMoreLimit = input.config.pageMoreLimit ?? DEFAULT_PAGE_MORE_LIMIT;
124
122
 
125
123
  const createButton = (
126
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
  }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * KTUI - Free & Open-Source Tailwind UI Components by Keenthemes
3
+ * Copyright 2025 by Keenthemes Inc
4
+ */
5
+
6
+ /**
7
+ * Search binding utilities for KTDataTable.
8
+ * Manages debounced search input binding and cleanup.
9
+ */
10
+
11
+ type SearchElementWithDebounce = HTMLInputElement & {
12
+ _debouncedSearch?: EventListener;
13
+ };
14
+
15
+ export interface KTDataTableSearchHandler {
16
+ attach(
17
+ tableId: string,
18
+ currentSearch: string | object | undefined,
19
+ delay: number,
20
+ onSearch: (query: string) => void,
21
+ ): void;
22
+ detach(tableId: string): void;
23
+ }
24
+
25
+ export function createSearchHandler(): KTDataTableSearchHandler {
26
+ function findSearchElement(tableId: string): HTMLInputElement | null {
27
+ return document.querySelector<HTMLInputElement>(
28
+ `[data-kt-datatable-search="#${tableId}"]`,
29
+ );
30
+ }
31
+
32
+ function asSearchElementWithDebounce(
33
+ element: HTMLInputElement,
34
+ ): SearchElementWithDebounce {
35
+ return element as SearchElementWithDebounce;
36
+ }
37
+
38
+ function debounce<TArgs extends unknown[]>(
39
+ func: (...args: TArgs) => void,
40
+ wait: number,
41
+ ): (...args: TArgs) => void {
42
+ let timeout: number | undefined;
43
+ return function (...args: TArgs) {
44
+ const later = () => {
45
+ clearTimeout(timeout);
46
+ func(...args);
47
+ };
48
+ clearTimeout(timeout);
49
+ timeout = window.setTimeout(later, wait);
50
+ };
51
+ }
52
+
53
+ function attach(
54
+ tableId: string,
55
+ currentSearch: string | object | undefined,
56
+ delay: number,
57
+ onSearch: (query: string) => void,
58
+ ): void {
59
+ const searchElement = findSearchElement(tableId);
60
+ if (!searchElement) return;
61
+
62
+ // Restore search value from state
63
+ if (currentSearch !== undefined && currentSearch !== null) {
64
+ searchElement.value =
65
+ typeof currentSearch === 'string'
66
+ ? currentSearch
67
+ : String(currentSearch);
68
+ }
69
+
70
+ // Remove existing debounced listener if any
71
+ const el = asSearchElementWithDebounce(searchElement);
72
+ if (el._debouncedSearch) {
73
+ searchElement.removeEventListener('keyup', el._debouncedSearch);
74
+ }
75
+
76
+ // Create and attach new debounced search
77
+ const debouncedSearch = debounce(() => {
78
+ onSearch(searchElement.value);
79
+ }, delay);
80
+
81
+ el._debouncedSearch = debouncedSearch;
82
+ searchElement.addEventListener('keyup', debouncedSearch);
83
+ }
84
+
85
+ function detach(tableId: string): void {
86
+ const searchElement = findSearchElement(tableId);
87
+ if (!searchElement) return;
88
+
89
+ const el = asSearchElementWithDebounce(searchElement);
90
+ if (el._debouncedSearch) {
91
+ searchElement.removeEventListener('keyup', el._debouncedSearch);
92
+ delete el._debouncedSearch;
93
+ }
94
+ }
95
+
96
+ return { attach, detach };
97
+ }