@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
@@ -13,16 +13,18 @@ import {
13
13
  KTDataTableColumnFilterInterface,
14
14
  KTDataTableLayoutPluginContextInterface,
15
15
  KTDataTableLayoutPluginInterface,
16
+ OriginalTableClasses,
16
17
  } from './types';
17
18
  import { KTOptionType } from '../../types';
18
- import KTComponents from '../../index';
19
19
  import KTData from '../../helpers/data';
20
20
  import {
21
- createCheckboxHandler,
21
+ KTDataTableCheckboxHandler,
22
22
  KTDataTableCheckboxAPI,
23
23
  } from './datatable-checkbox';
24
- import { createSortHandler, KTDataTableSortAPI } from './datatable-sort';
24
+ import { KTDataTableSortHandler, KTDataTableSortAPI } from './datatable-sort';
25
25
  import { createStickyLayoutPlugin } from './datatable-layout-plugin';
26
+ import { DATATABLE_DEFAULTS, DEFAULT_PAGE_SIZES, DEFAULT_SEARCH_DELAY } from './datatable-defaults';
27
+ import { getLogicalColumnCount } from './datatable-column-utils';
26
28
  import {
27
29
  KTDataTableCleanup,
28
30
  KTDataTableEventAdapter,
@@ -30,13 +32,20 @@ import {
30
32
  KTDataTableStateStore,
31
33
  KTDataTableTableRenderer,
32
34
  } from './datatable-contracts';
33
- import { createDataTableEventAdapter } from './datatable-event-adapter';
34
35
  import { KTDataTableLocalDataProvider } from './datatable-local-provider';
35
36
  import { KTDataTableRemoteDataProvider } from './datatable-remote-provider';
36
37
  import { KTDataTableConfigStateStore } from './datatable-state-store';
37
38
  import { KTDataTableDomPaginationRenderer } from './datatable-pagination-renderer';
38
39
  import { KTDataTableDomTableRenderer } from './datatable-table-renderer';
39
40
  import KTUtils from '../../helpers/utils';
41
+ import { createSearchHandler } from './datatable-search-handler';
42
+ import {
43
+ createStatePersistence,
44
+ resolveTableNamespace,
45
+ } from './datatable-state-persistence';
46
+ import { createSpinner } from './datatable-spinner';
47
+ import { createDataTableRegistry } from './datatable-registry';
48
+ import { stripHtml } from './datatable-utils';
40
49
 
41
50
  /**
42
51
  * Custom DataTable plugin class with server-side API, pagination, and sorting
@@ -47,24 +56,14 @@ import KTUtils from '../../helpers/utils';
47
56
  * @param {HTMLElement} element The table element
48
57
  * @param {KTDataTableConfigInterface} [config] Additional configuration options
49
58
  */
59
+ const datatableRegistry = createDataTableRegistry<
60
+ KTDataTable<KTDataTableDataInterface>
61
+ >();
62
+
50
63
  export class KTDataTable<T extends KTDataTableDataInterface>
51
64
  extends KTComponent
52
65
  implements KTDataTableInterface
53
66
  {
54
- private static asElementWithInstance(element: HTMLElement): HTMLElement & {
55
- instance?: KTDataTable<KTDataTableDataInterface>;
56
- } {
57
- return element as HTMLElement & {
58
- instance?: KTDataTable<KTDataTableDataInterface>;
59
- };
60
- }
61
-
62
- private static asSearchElementWithDebounce(
63
- element: HTMLInputElement,
64
- ): HTMLInputElement & { _debouncedSearch?: EventListener } {
65
- return element as HTMLInputElement & { _debouncedSearch?: EventListener };
66
- }
67
-
68
67
  protected override _name: string = 'datatable';
69
68
  protected override _config: KTDataTableConfigInterface;
70
69
  protected override _defaultConfig: KTDataTableConfigInterface;
@@ -72,11 +71,13 @@ export class KTDataTable<T extends KTDataTableDataInterface>
72
71
  private _tableElement: HTMLTableElement;
73
72
  private _tbodyElement: HTMLTableSectionElement;
74
73
  private _theadElement: HTMLTableSectionElement;
75
- private _originalTbodyClass: string = ''; // Store original tbody class
76
- private _originalTrClasses: string[] = []; // Store original tr classes
77
- private _originalTheadClass: string = ''; // Store original thead class
78
- private _originalTdClasses: string[][] = []; // Store original td classes as a 2D array [row][col]
79
- private _originalThClasses: string[] = []; // Store original th classes
74
+ private _originalClasses: OriginalTableClasses = {
75
+ tbody: '',
76
+ thead: '',
77
+ tr: [],
78
+ td: [],
79
+ th: [],
80
+ };
80
81
 
81
82
  private _infoElement: HTMLElement | null = null;
82
83
  private _sizeElement: HTMLSelectElement | null = null;
@@ -93,6 +94,10 @@ export class KTDataTable<T extends KTDataTableDataInterface>
93
94
  private _paginationRenderer: KTDataTablePaginationRenderer;
94
95
  private _cleanupCallbacks: KTDataTableCleanup[] = [];
95
96
 
97
+ private _searchHandler = createSearchHandler();
98
+ private _statePersistence = createStatePersistence();
99
+ private _spinner = createSpinner();
100
+
96
101
  private _data: T[] = [];
97
102
  private _isFetching: boolean = false;
98
103
 
@@ -120,13 +125,14 @@ export class KTDataTable<T extends KTDataTableDataInterface>
120
125
  this._buildConfig();
121
126
  this._normalizePageSizeConfig();
122
127
  this._stateStore = new KTDataTableConfigStateStore(this._config);
123
- this._eventAdapter = createDataTableEventAdapter(
124
- this._fireEvent.bind(this),
125
- this._dispatchEvent.bind(this),
126
- );
128
+ this._eventAdapter = {
129
+ emit: (eventName: string, eventData?: object) => {
130
+ this._emit(eventName, eventData);
131
+ },
132
+ };
127
133
 
128
134
  // Store the instance directly on the element
129
- KTDataTable.asElementWithInstance(element).instance = this;
135
+ datatableRegistry.register(element, this);
130
136
 
131
137
  this._initElements();
132
138
  this._layoutPlugin = this._createLayoutPlugin();
@@ -135,27 +141,32 @@ export class KTDataTable<T extends KTDataTableDataInterface>
135
141
  this._initDataProviders();
136
142
 
137
143
  // Initialize checkbox handler
138
- this._checkbox = createCheckboxHandler(
144
+ this._checkbox = new KTDataTableCheckboxHandler(
139
145
  this._element,
140
146
  this._config,
141
147
  this._emit.bind(this),
148
+ {
149
+ getState: () => this._stateStore.getState(),
150
+ setSelectedRows: (rows) => {
151
+ this._stateStore.patchState({ selectedRows: rows });
152
+ },
153
+ },
142
154
  );
143
155
 
144
156
  // Initialize sort handler
145
- this._sortHandler = createSortHandler(
146
- this._config,
147
- this._theadElement,
148
- () => ({
157
+ this._sortHandler = new KTDataTableSortHandler({
158
+ config: this._config,
159
+ theadElement: this._theadElement,
160
+ getState: () => ({
149
161
  sortField: this.getState().sortField,
150
162
  sortOrder: this.getState().sortOrder,
151
163
  }),
152
- (field, order) => {
164
+ setState: (field, order) => {
153
165
  this._stateStore.setSort(field as never, order);
154
166
  },
155
- this._fireEvent.bind(this),
156
- this._dispatchEvent.bind(this),
157
- this._updateData.bind(this),
158
- );
167
+ emit: this._emit.bind(this),
168
+ updateData: this._updateData.bind(this),
169
+ });
159
170
 
160
171
  this._sortHandler.initSort();
161
172
 
@@ -169,12 +180,11 @@ export class KTDataTable<T extends KTDataTableDataInterface>
169
180
  }
170
181
 
171
182
  this._updateData();
172
-
173
- this._emit('init');
174
183
  }
175
184
 
176
185
  private _emit(eventName: string, eventData?: object): void {
177
- this._eventAdapter.emit(eventName, eventData);
186
+ this._fireEvent(eventName, eventData);
187
+ this._dispatchEvent(`kt.datatable.${eventName}`, eventData);
178
188
  }
179
189
 
180
190
  private _initDataProviders(): void {
@@ -229,7 +239,7 @@ export class KTDataTable<T extends KTDataTableDataInterface>
229
239
  .map((size) => Number(size))
230
240
  .filter((size) => Number.isFinite(size) && size > 0)
231
241
  .map((size) => Math.floor(size));
232
- const fallbackPageSizes = [5, 10, 20, 30, 50];
242
+ const fallbackPageSizes: number[] = [...DEFAULT_PAGE_SIZES];
233
243
  this._config.pageSizes =
234
244
  pageSizes.length > 0 ? Array.from(new Set(pageSizes)) : fallbackPageSizes;
235
245
 
@@ -267,117 +277,43 @@ export class KTDataTable<T extends KTDataTableDataInterface>
267
277
  * @param config User-provided configuration options
268
278
  * @returns Default configuration merged with user-provided options
269
279
  */
280
+ private _createDefaultSearchCallback(): (
281
+ data: KTDataTableDataInterface[],
282
+ search: string,
283
+ ) => KTDataTableDataInterface[] {
284
+ return ((data: T[], search: string): T[] => {
285
+ if (!data || !search) {
286
+ return [];
287
+ }
288
+ const searchLower = search.toLowerCase();
289
+ return data.filter((item: T) => {
290
+ if (!item) {
291
+ return false;
292
+ }
293
+ return Object.values(item).some((value: KTOptionType) => {
294
+ if (
295
+ typeof value !== 'string' &&
296
+ typeof value !== 'number' &&
297
+ typeof value !== 'boolean'
298
+ ) {
299
+ return false;
300
+ }
301
+ const valueText = stripHtml(value).toLowerCase();
302
+ return valueText.includes(searchLower);
303
+ });
304
+ });
305
+ }) as unknown as (data: KTDataTableDataInterface[], search: string) => KTDataTableDataInterface[];
306
+ }
307
+
270
308
  private _initDefaultConfig(
271
309
  config?: KTDataTableConfigInterface,
272
310
  ): KTDataTableConfigInterface {
273
311
  return {
274
- /**
275
- * HTTP method for server-side API call
276
- */
277
- requestMethod: 'GET',
278
- /**
279
- * Custom HTTP headers for the API request
280
- */
281
- requestHeaders: {
282
- 'Content-Type': 'application/x-www-form-urlencoded',
283
- },
284
- /**
285
- * Pagination info template
286
- */
287
- info: '{start}-{end} of {total}',
288
- /**
289
- * Info text when there is no data
290
- */
291
- infoEmpty: 'No records found',
292
- /**
293
- * Available page sizes
294
- */
295
- pageSizes: [5, 10, 20, 30, 50],
296
- /**
297
- * Default page size
298
- */
299
- pageSize: 10,
300
- /**
301
- * Enable or disable pagination more button
302
- */
303
- pageMore: true,
304
- /**
305
- * Maximum number of pages before enabling pagination more button
306
- */
307
- pageMoreLimit: 3,
308
- /**
309
- * Pagination button templates
310
- */
311
- pagination: {
312
- number: {
313
- /**
314
- * CSS classes to be added to the pagination button
315
- */
316
- class: 'kt-datatable-pagination-button',
317
- /**
318
- * Text to be displayed in the pagination button
319
- */
320
- text: '{page}',
321
- },
322
- previous: {
323
- /**
324
- * CSS classes to be added to the previous pagination button
325
- */
326
- class: 'kt-datatable-pagination-button kt-datatable-pagination-prev',
327
- /**
328
- * Text to be displayed in the previous pagination button
329
- */
330
- text: `
331
- <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">
332
- <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"/>
333
- </svg>
334
- `,
335
- },
336
- next: {
337
- /**
338
- * CSS classes to be added to the next pagination button
339
- */
340
- class: 'kt-datatable-pagination-button kt-datatable-pagination-next',
341
- /**
342
- * Text to be displayed in the next pagination button
343
- */
344
- text: `
345
- <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">
346
- <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"/>
347
- </svg>
348
- `,
349
- },
350
- more: {
351
- /**
352
- * CSS classes to be added to the pagination more button
353
- */
354
- class: 'kt-datatable-pagination-button kt-datatable-pagination-more',
355
- /**
356
- * Text to be displayed in the pagination more button
357
- */
358
- text: '...',
359
- },
360
- },
361
- /**
362
- * Sorting options
363
- */
312
+ ...DATATABLE_DEFAULTS,
313
+ // Per-instance state; DATATABLE_DEFAULTS._state is a shared singleton.
314
+ _state: {} as KTDataTableStateInterface,
364
315
  sort: {
365
- /**
366
- * CSS classes to be added to the sortable headers
367
- */
368
- classes: {
369
- base: 'kt-table-col',
370
- asc: 'asc',
371
- desc: 'desc',
372
- },
373
- /**
374
- * Local sorting callback function
375
- * Sorts the data array based on the sort field and order
376
- * @param data Data array to be sorted
377
- * @param sortField Property name of the data object to be sorted by
378
- * @param sortOrder Sorting order (ascending or descending)
379
- * @returns Sorted data array
380
- */
316
+ ...DATATABLE_DEFAULTS.sort,
381
317
  callback: (
382
318
  data: T[],
383
319
  sortField: keyof T | number,
@@ -389,110 +325,9 @@ export class KTDataTable<T extends KTDataTableDataInterface>
389
325
  },
390
326
  },
391
327
  search: {
392
- /**
393
- * Delay in milliseconds before the search function is applied to the data array
394
- * @default 500
395
- */
396
- delay: 500, // ms
397
- /**
398
- * Local search callback function
399
- * Filters the data array based on the search string
400
- * @param data Data array to be filtered
401
- * @param search Search string used to filter the data array
402
- * @returns Filtered data array
403
- */
404
- callback: (data: T[], search: string): T[] => {
405
- if (!data || !search) {
406
- return [];
407
- }
408
-
409
- return data.filter((item: T) => {
410
- if (!item) {
411
- return false;
412
- }
413
-
414
- return Object.values(item).some((value: KTOptionType) => {
415
- if (
416
- typeof value !== 'string' &&
417
- typeof value !== 'number' &&
418
- typeof value !== 'boolean'
419
- ) {
420
- return false;
421
- }
422
-
423
- const valueText = String(value)
424
- .replace(/<|>|&nbsp;/g, '')
425
- .toLowerCase();
426
- return valueText.includes(search.toLowerCase());
427
- });
428
- });
429
- },
430
- },
431
- /**
432
- * Loading spinner options
433
- */
434
- loading: {
435
- /**
436
- * Template to be displayed during data fetching process
437
- */
438
- template: `
439
- <div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
440
- <div class="kt-datatable-loading">
441
- <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">
442
- <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="3"></circle>
443
- <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>
444
- </svg>
445
- {content}
446
- </div>
447
- </div>
448
- `,
449
- /**
450
- * Loading text to be displayed in the template
451
- */
452
- content: 'Loading...',
328
+ ...DATATABLE_DEFAULTS.search,
329
+ callback: this._createDefaultSearchCallback(),
453
330
  },
454
- /**
455
- * Selectors of the elements to be targeted
456
- */
457
- attributes: {
458
- /**
459
- * Data table element
460
- */
461
- table: 'table[data-kt-datatable-table="true"]',
462
- /**
463
- * Pagination info element
464
- */
465
- info: '[data-kt-datatable-info="true"]',
466
- /**
467
- * Page size dropdown element
468
- */
469
- size: '[data-kt-datatable-size="true"]',
470
- /**
471
- * Pagination element
472
- */
473
- pagination: '[data-kt-datatable-pagination="true"]',
474
- /**
475
- * Spinner element
476
- */
477
- spinner: '[data-kt-datatable-spinner="true"]',
478
- /**
479
- * Checkbox element
480
- */
481
- check: '[data-kt-datatable-check="true"]',
482
- checkbox: '[data-kt-datatable-row-check="true"]',
483
- },
484
- /**
485
- * Enable or disable state saving
486
- */
487
- stateSave: true,
488
- checkbox: {
489
- checkedClass: 'checked',
490
- },
491
- /**
492
- * Private properties
493
- */
494
- _state: {} as KTDataTableStateInterface,
495
- loadingClass: 'loading',
496
331
  ...config,
497
332
  } as KTDataTableConfigInterface;
498
333
  }
@@ -542,17 +377,17 @@ export class KTDataTable<T extends KTDataTableDataInterface>
542
377
  private _storeOriginalClasses(): void {
543
378
  // Store tbody class
544
379
  if (this._tbodyElement) {
545
- this._originalTbodyClass = this._tbodyElement.className || '';
380
+ this._originalClasses.tbody = this._tbodyElement.className || '';
546
381
  }
547
382
 
548
383
  // Store thead class and th classes
549
384
  if (this._theadElement) {
550
- this._originalTheadClass = this._theadElement.className || '';
385
+ this._originalClasses.thead = this._theadElement.className || '';
551
386
 
552
387
  // Store th classes
553
388
  const thElements =
554
389
  this._theadElement.querySelectorAll<HTMLTableCellElement>('th');
555
- this._originalThClasses = Array.from(thElements).map(
390
+ this._originalClasses.th = Array.from(thElements).map(
556
391
  (th) => th.className || '',
557
392
  );
558
393
  }
@@ -561,15 +396,15 @@ export class KTDataTable<T extends KTDataTableDataInterface>
561
396
  if (this._tbodyElement) {
562
397
  const originalRows =
563
398
  this._tbodyElement.querySelectorAll<HTMLTableRowElement>('tr');
564
- this._originalTrClasses = Array.from(originalRows).map(
399
+ this._originalClasses.tr = Array.from(originalRows).map(
565
400
  (row) => row.className || '',
566
401
  );
567
402
 
568
403
  // Store td classes as a 2D array
569
- this._originalTdClasses = [];
404
+ this._originalClasses.td = [];
570
405
  Array.from(originalRows).forEach((row, rowIndex) => {
571
406
  const tdElements = row.querySelectorAll<HTMLTableCellElement>('td');
572
- this._originalTdClasses[rowIndex] = Array.from(tdElements).map(
407
+ this._originalClasses.td[rowIndex] = Array.from(tdElements).map(
573
408
  (td) => td.className || '',
574
409
  );
575
410
  });
@@ -584,9 +419,8 @@ export class KTDataTable<T extends KTDataTableDataInterface>
584
419
  if (this._isFetching) return; // Prevent duplicate fetches
585
420
  this._isFetching = true;
586
421
  try {
587
- this._showSpinner(); // Show spinner before fetching data
422
+ this._spinner.show(this._element, this._config, this._tableElement); // Show spinner before fetching data
588
423
 
589
- this._emit('fetch');
590
424
  const result =
591
425
  typeof this._config.apiEndpoint === 'undefined'
592
426
  ? this._localProvider.fetchSync()
@@ -596,10 +430,11 @@ export class KTDataTable<T extends KTDataTableDataInterface>
596
430
  this._data = result.data;
597
431
  this._stateStore.patchState({ totalItems: result.totalItems });
598
432
  await this._draw();
599
- this._emit('fetched');
600
433
  }
601
434
 
602
435
  await this._finalize();
436
+
437
+ this._emit('update');
603
438
  } finally {
604
439
  // Finally block now correctly executes after promises resolve, not immediately
605
440
  this._isFetching = false;
@@ -621,63 +456,29 @@ export class KTDataTable<T extends KTDataTableDataInterface>
621
456
  this._sortHandler.initSort();
622
457
  }
623
458
 
624
- this._attachSearchEvent();
625
-
626
- if (typeof KTComponents !== 'undefined') {
627
- KTComponents.init();
628
- }
459
+ this._searchHandler.attach(
460
+ this._tableId(),
461
+ this.getState().search,
462
+ this._config.search?.delay ?? DEFAULT_SEARCH_DELAY,
463
+ (query) => this.search(query),
464
+ );
629
465
 
630
466
  /**
631
467
  * Hide spinner
632
468
  */
633
- this._hideSpinner();
634
- }
635
-
636
- /**
637
- * Attach search event to the search input element
638
- * @returns {void}
639
- */
640
- private _attachSearchEvent(): void {
641
- const tableId: string = this._tableId();
642
- const searchElement: HTMLInputElement | null =
643
- document.querySelector<HTMLInputElement>(
644
- `[data-kt-datatable-search="#${tableId}"]`,
645
- );
646
-
647
- // Get search state
648
- const { search } = this.getState();
649
- // Set search value
650
- if (searchElement) {
651
- searchElement.value =
652
- search === undefined || search === null
653
- ? ''
654
- : typeof search === 'string'
655
- ? search
656
- : String(search);
657
- }
658
-
659
- if (searchElement) {
660
- // Check if a debounced search function already exists
661
- const searchWithDebounce =
662
- KTDataTable.asSearchElementWithDebounce(searchElement);
663
- if (searchWithDebounce._debouncedSearch) {
664
- // Remove the existing debounced event listener
665
- searchElement.removeEventListener(
666
- 'keyup',
667
- searchWithDebounce._debouncedSearch,
668
- );
669
- }
670
-
671
- // Create a new debounced search function
672
- const debouncedSearch = this._debounce(() => {
673
- this.search(searchElement.value);
674
- }, this._config.search?.delay ?? 500);
469
+ this._spinner.hide(this._element, this._config);
675
470
 
676
- // Store the new debounced function as a property of the element
677
- searchWithDebounce._debouncedSearch = debouncedSearch;
678
-
679
- // Add the new debounced event listener
680
- searchElement.addEventListener('keyup', debouncedSearch);
471
+ // Update content checksum AFTER all DOM modifications (checkbox init
472
+ // adds checked-class to <tr> elements which changes tbody innerHTML).
473
+ // If we save the checksum earlier (in _draw), the next fetchSync()
474
+ // sees a mismatch, re-extracts from the DOM, and loses rows that
475
+ // were on other pages — making pagination show empty.
476
+ if (!this._config.apiEndpoint) {
477
+ this._stateStore.patchState({
478
+ _contentChecksum: KTUtils.checksum(
479
+ JSON.stringify(this._tbodyElement.innerHTML),
480
+ ),
481
+ });
681
482
  }
682
483
  }
683
484
 
@@ -687,18 +488,11 @@ export class KTDataTable<T extends KTDataTableDataInterface>
687
488
  * @returns {number} Number of data columns, or 0 if unknown
688
489
  */
689
490
  private _getLogicalColumnCount(): number {
690
- const { originalData } = this.getState();
691
- if (originalData && originalData.length > 0) {
692
- return Object.keys(originalData[0]).length;
693
- }
694
- if (this._tbodyElement) {
695
- const firstRow =
696
- this._tbodyElement.querySelector<HTMLTableRowElement>('tr');
697
- if (firstRow) {
698
- return firstRow.querySelectorAll<HTMLTableCellElement>('td').length;
699
- }
700
- }
701
- return 0;
491
+ return getLogicalColumnCount(
492
+ this._theadElement,
493
+ this._tbodyElement,
494
+ this.getState().originalData as Array<Record<string, unknown>> | undefined,
495
+ );
702
496
  }
703
497
 
704
498
  /**
@@ -788,9 +582,8 @@ export class KTDataTable<T extends KTDataTableDataInterface>
788
582
  this._stateStore.patchState({ totalPages, page });
789
583
 
790
584
  this._layoutPlugin?.beforeDraw?.(this._getLayoutPluginContext());
791
- this._emit('draw');
792
585
 
793
- this._dispose();
586
+ this._cleanupForRedraw();
794
587
 
795
588
  // Update the table and pagination controls
796
589
  if (this._theadElement && this._tbodyElement) {
@@ -802,15 +595,6 @@ export class KTDataTable<T extends KTDataTableDataInterface>
802
595
  }
803
596
 
804
597
  this._layoutPlugin?.afterDraw?.(this._getLayoutPluginContext());
805
- if (!this._config.apiEndpoint) {
806
- this._stateStore.patchState({
807
- _contentChecksum: KTUtils.checksum(
808
- JSON.stringify(this._tbodyElement.innerHTML),
809
- ),
810
- });
811
- }
812
-
813
- this._emit('drew');
814
598
 
815
599
  // Spinner is hidden in _finalize() to ensure it stays visible until the entire request completes
816
600
  // Removed duplicate _hideSpinner() call here to prevent premature hiding
@@ -831,9 +615,7 @@ export class KTDataTable<T extends KTDataTableDataInterface>
831
615
  data: this._data,
832
616
  getLogicalColumnCount: this._getLogicalColumnCount.bind(this),
833
617
  getState: this.getState.bind(this),
834
- originalTbodyClass: this._originalTbodyClass,
835
- originalTrClasses: this._originalTrClasses,
836
- originalTdClasses: this._originalTdClasses,
618
+ originalClasses: this._originalClasses,
837
619
  tableElement: this._tableElement,
838
620
  theadElement: this._theadElement,
839
621
  });
@@ -892,77 +674,21 @@ export class KTDataTable<T extends KTDataTableDataInterface>
892
674
  return;
893
675
  }
894
676
 
895
- this._emit('pagination', { page: page });
896
-
897
677
  if (page >= 1 && page <= this.getState().totalPages) {
898
678
  this._stateStore.setPage(page);
899
679
  this._updateData();
900
680
  }
901
681
  }
902
682
 
903
- // Method to show the loading spinner
904
- private _showSpinner(): void {
905
- const root = this._element;
906
- const spinnerSel = this._config.attributes?.spinner;
907
- const fromDom =
908
- root && spinnerSel ? root.querySelector<HTMLElement>(spinnerSel) : null;
909
- const spinner = fromDom ?? this._createSpinner();
910
- if (spinner) {
911
- spinner.style.display = 'block';
912
- }
913
- root?.classList.add(this._config.loadingClass ?? 'loading');
914
- }
915
-
916
- // Method to hide the loading spinner
917
- private _hideSpinner(): void {
918
- const root = this._element;
919
- const spinnerSel = this._config.attributes?.spinner;
920
- const spinner =
921
- root && spinnerSel ? root.querySelector<HTMLElement>(spinnerSel) : null;
922
- if (spinner) {
923
- spinner.style.display = 'none';
924
- }
925
- root?.classList.remove(this._config.loadingClass ?? 'loading');
926
- }
927
-
928
- // Method to create a spinner element if it doesn't exist
929
- private _createSpinner(): HTMLElement | null {
930
- const loading = this._config.loading;
931
- if (!loading) {
932
- return null;
933
- }
934
-
935
- const template = document.createElement('template');
936
- template.innerHTML = loading.template
937
- .trim()
938
- .replace('{content}', loading.content);
939
- const first = template.content.firstChild;
940
- if (!first || !(first instanceof HTMLElement)) {
941
- return null;
942
- }
943
- const spinner = first;
944
- spinner.setAttribute('data-kt-datatable-spinner', 'true');
945
-
946
- this._tableElement.appendChild(spinner);
947
-
948
- return spinner;
949
- }
950
-
951
683
  /**
952
684
  * Saves the current state of the table to local storage.
953
685
  * @returns {void}
954
686
  */
955
687
  private _saveState(): void {
956
- this._emit('stateSave');
957
-
958
- const ns: string = this._tableNamespace();
959
-
960
- if (ns) {
961
- localStorage.setItem(
962
- ns,
963
- JSON.stringify(this.getState() as KTDataTableStateInterface),
964
- );
965
- }
688
+ this._statePersistence.save(
689
+ this._tableNamespace(),
690
+ this.getState() as KTDataTableStateInterface,
691
+ );
966
692
  }
967
693
 
968
694
  /**
@@ -970,24 +696,16 @@ export class KTDataTable<T extends KTDataTableDataInterface>
970
696
  * @returns {Object} The saved state of the table, or null if no saved state exists.
971
697
  */
972
698
  private _loadState(): KTDataTableStateInterface | null {
973
- const stateString = localStorage.getItem(this._tableNamespace());
974
- if (!stateString) return null;
975
-
976
- try {
977
- const state = JSON.parse(stateString) as KTDataTableStateInterface;
978
- if (state) this._stateStore.replaceState(state);
979
- return state;
980
- } catch {}
981
-
982
- return null;
699
+ const ns = this._tableNamespace();
700
+ const saved = this._statePersistence.load(ns);
701
+ if (saved) this._stateStore.replaceState(saved);
702
+ return saved;
983
703
  }
984
704
 
985
705
  private _deleteState(): void {
986
- const ns = this._tableNamespace();
987
-
988
- if (ns) {
989
- localStorage.removeItem(ns);
990
- }
706
+ this._statePersistence.remove(
707
+ this._tableNamespace(),
708
+ );
991
709
  }
992
710
 
993
711
  /**
@@ -999,13 +717,12 @@ export class KTDataTable<T extends KTDataTableDataInterface>
999
717
  * @returns {string} The namespace for the table's state.
1000
718
  */
1001
719
  private _tableNamespace(): string {
1002
- // Use the specified namespace, if one is given
1003
- if (this._config.stateNamespace) {
1004
- return this._config.stateNamespace;
1005
- }
1006
-
1007
- // Fallback to the component's UID
1008
- return this._tableId() ?? this._name;
720
+ return resolveTableNamespace(
721
+ this._config,
722
+ this._tableElement,
723
+ this._element,
724
+ this._name,
725
+ );
1009
726
  }
1010
727
 
1011
728
  private _tableId(): string {
@@ -1024,109 +741,51 @@ export class KTDataTable<T extends KTDataTableDataInterface>
1024
741
  * Clean up all event listeners, handlers, and DOM nodes created by this instance.
1025
742
  * This method is called before re-rendering or when disposing the component.
1026
743
  */
1027
- private _dispose() {
744
+ /**
745
+ * Clean up event listeners and DOM artifacts for a redraw cycle.
746
+ * Does NOT remove the instance from the registry — the datatable
747
+ * remains accessible via getInstance() during the redraw window.
748
+ */
749
+ private _cleanupForRedraw(): void {
1028
750
  this._layoutPlugin?.dispose?.(this._getLayoutPluginContext());
1029
751
 
1030
- const root = this._element;
1031
- if (!root) {
752
+ if (!this._element) {
1032
753
  return;
1033
754
  }
1034
755
 
1035
756
  this._cleanupCallbacks.forEach((cleanup) => cleanup());
1036
757
  this._cleanupCallbacks = [];
1037
758
 
1038
- // --- 1. Remove search input event listener (debounced) ---
1039
- const tableId: string = this._tableId();
1040
- const searchElement: HTMLInputElement | null =
1041
- document.querySelector<HTMLInputElement>(
1042
- `[data-kt-datatable-search="#${tableId}"]`,
1043
- );
1044
- if (searchElement) {
1045
- const searchWithDebounce =
1046
- KTDataTable.asSearchElementWithDebounce(searchElement);
1047
- if (searchWithDebounce._debouncedSearch) {
1048
- searchElement.removeEventListener(
1049
- 'keyup',
1050
- searchWithDebounce._debouncedSearch,
1051
- );
1052
- delete searchWithDebounce._debouncedSearch;
1053
- }
1054
- }
759
+ this._searchHandler.detach(this._tableId());
1055
760
 
1056
- // --- 2. Remove page size dropdown event listener ---
1057
761
  if (this._sizeElement && this._sizeElement.onchange) {
1058
762
  this._sizeElement.onchange = null;
1059
763
  }
1060
764
 
1061
- // --- 3. Remove all pagination button event listeners ---
1062
765
  if (this._paginationElement) {
1063
- // Remove all child nodes (buttons) to ensure no lingering listeners
1064
766
  while (this._paginationElement.firstChild) {
1065
767
  this._paginationElement.removeChild(this._paginationElement.firstChild);
1066
768
  }
1067
769
  }
1068
770
 
1069
- // --- 4. Dispose of handler objects (checkbox, sort) ---
1070
- // KTDataTableCheckboxAPI does not have a dispose method, but we can remove header checkbox listener
1071
- const checkboxWithDispose = this._checkbox as KTDataTableCheckboxAPI & {
1072
- dispose?: () => void;
1073
- };
1074
- if (this._checkbox && typeof checkboxWithDispose.dispose === 'function') {
1075
- checkboxWithDispose.dispose();
1076
- } else {
1077
- const checkSel = this._config.attributes?.check;
1078
- if (checkSel) {
1079
- const headerCheckElement =
1080
- root.querySelector<HTMLInputElement>(checkSel);
1081
- if (headerCheckElement) {
1082
- headerCheckElement.replaceWith(headerCheckElement.cloneNode(true));
1083
- }
1084
- }
1085
- }
1086
- // KTDataTableSortAPI does not have a dispose method, but we can remove th click listeners by replacing them
1087
- if (this._theadElement) {
1088
- const ths = this._theadElement.querySelectorAll('th');
1089
- ths.forEach((th) => {
1090
- th.replaceWith(th.cloneNode(true));
1091
- });
1092
- }
1093
-
1094
- // --- 5. Remove spinner DOM node if it exists ---
1095
- const spinnerSel = this._config.attributes?.spinner;
1096
- if (spinnerSel) {
1097
- const spinner = root.querySelector<HTMLElement>(spinnerSel);
1098
- if (spinner?.parentNode) {
1099
- spinner.parentNode.removeChild(spinner);
1100
- }
1101
- }
1102
- root.classList.remove(this._config.loadingClass ?? 'loading');
1103
-
1104
- // --- 6. Remove instance reference from the DOM element ---
1105
- const elementWithInstance = KTDataTable.asElementWithInstance(root);
1106
- if (elementWithInstance.instance) {
1107
- delete elementWithInstance.instance;
1108
- }
771
+ this._checkbox.dispose();
772
+ this._sortHandler.dispose();
1109
773
 
1110
- KTData.remove(root, this._name);
1111
-
1112
- // --- 7. (Optional) Clear localStorage state ---
1113
- // Uncomment the following line if you want to clear state on dispose:
1114
- // this._deleteState();
774
+ this._spinner.remove(this._element, this._config);
1115
775
  }
1116
776
 
1117
- private _debounce<TArgs extends unknown[]>(
1118
- func: (...args: TArgs) => void,
1119
- wait: number,
1120
- ): (...args: TArgs) => void {
1121
- let timeout: number | undefined;
1122
- return function (...args: TArgs) {
1123
- const later = () => {
1124
- clearTimeout(timeout);
1125
- func(...args);
1126
- };
1127
- clearTimeout(timeout);
1128
- timeout = window.setTimeout(later, wait);
1129
- };
777
+ /**
778
+ * Full disposal cleans up listeners AND removes the instance from
779
+ * the registry. Only called when the component is being destroyed.
780
+ */
781
+ private _dispose(): void {
782
+ this._cleanupForRedraw();
783
+
784
+ const root = this._element;
785
+ if (root) {
786
+ datatableRegistry.remove(root);
787
+ KTData.remove(root, this._name);
788
+ }
1130
789
  }
1131
790
 
1132
791
  /**
@@ -1139,16 +798,22 @@ export class KTDataTable<T extends KTDataTableDataInterface>
1139
798
 
1140
799
  /**
1141
800
  * Sorts the data in the table by the specified field.
801
+ * When `order` is provided, applies that sort direction instead of toggling.
1142
802
  * @param field The field to sort by.
1143
- */
1144
- public sort(field: keyof T | number): void {
1145
- // Use the sort handler to update state and trigger sorting
1146
- const state = this.getState();
1147
- const sortOrder = this._sortHandler.toggleSortOrder(
1148
- state.sortField,
1149
- state.sortOrder,
1150
- field,
1151
- );
803
+ * @param order Optional sort direction (`asc`, `desc`, or `''` to clear).
804
+ */
805
+ public sort(
806
+ field: keyof T | number,
807
+ order?: KTDataTableSortOrderInterface,
808
+ ): void {
809
+ const sortOrder =
810
+ order !== undefined
811
+ ? order
812
+ : this._sortHandler.toggleSortOrder(
813
+ this.getState().sortField,
814
+ this.getState().sortOrder,
815
+ field,
816
+ );
1152
817
  this._sortHandler.setSortIcon(field as keyof T, sortOrder);
1153
818
  this._stateStore.setSort(field as never, sortOrder);
1154
819
  this._emit('sort', { field, order: sortOrder });
@@ -1185,19 +850,15 @@ export class KTDataTable<T extends KTDataTableDataInterface>
1185
850
  }
1186
851
 
1187
852
  /**
1188
- * Reloads the data from the server and updates the table.
1189
- * Triggers the 'reload' event and the 'kt.datatable.reload' custom event.
853
+ * Reloads the data from the source (API or DOM) and redraws the table.
854
+ * @returns {Promise<void>}
1190
855
  */
1191
856
  public reload(): void {
1192
- this._emit('reload');
1193
-
1194
857
  // Fetch the data from the server using the current sort and filter settings
1195
858
  this._updateData();
1196
859
  }
1197
860
 
1198
861
  public redraw(page: number = 1): void {
1199
- this._emit('redraw');
1200
-
1201
862
  this._paginateData(page);
1202
863
  }
1203
864
 
@@ -1205,20 +866,14 @@ export class KTDataTable<T extends KTDataTableDataInterface>
1205
866
  * Show the loading spinner of the data table.
1206
867
  */
1207
868
  public showSpinner(): void {
1208
- /**
1209
- * Show the loading spinner of the data table.
1210
- */
1211
- this._showSpinner();
869
+ this._spinner.show(this._element, this._config, this._tableElement);
1212
870
  }
1213
871
 
1214
872
  /**
1215
873
  * Hide the loading spinner of the data table.
1216
874
  */
1217
875
  public hideSpinner(): void {
1218
- /**
1219
- * Hide the loading spinner of the data table.
1220
- */
1221
- this._hideSpinner();
876
+ this._spinner.hide(this._element, this._config);
1222
877
  }
1223
878
 
1224
879
  /**
@@ -1243,37 +898,12 @@ export class KTDataTable<T extends KTDataTableDataInterface>
1243
898
  this.reload();
1244
899
  }
1245
900
 
1246
- /**
1247
- * Static variables
1248
- */
1249
- private static _instances = new Map<
1250
- HTMLElement,
1251
- KTDataTable<KTDataTableDataInterface>
1252
- >();
1253
-
1254
901
  /**
1255
902
  * Create KTDataTable instances for all elements with a data-kt-datatable="true" attribute.
1256
903
  * This function is now browser-guarded and must be called explicitly.
1257
904
  */
1258
905
  public static createInstances(): void {
1259
- if (typeof document === 'undefined') return;
1260
- const elements = document.querySelectorAll<HTMLElement>(
1261
- '[data-kt-datatable="true"]',
1262
- );
1263
-
1264
- elements.forEach((element) => {
1265
- if (
1266
- element.hasAttribute('data-kt-datatable') &&
1267
- !element.classList.contains('datatable-initialized')
1268
- ) {
1269
- /**
1270
- * Create an instance of KTDataTable for the given element
1271
- * @param element The element to create an instance for
1272
- */
1273
- const instance = new KTDataTable(element);
1274
- this._instances.set(element, instance);
1275
- }
1276
- });
906
+ datatableRegistry.createAll((el) => new KTDataTable(el));
1277
907
  }
1278
908
 
1279
909
  /**
@@ -1285,14 +915,7 @@ export class KTDataTable<T extends KTDataTableDataInterface>
1285
915
  public static getInstance(
1286
916
  element: HTMLElement,
1287
917
  ): KTDataTable<KTDataTableDataInterface> | undefined {
1288
- // First check the static Map (for instances created via createInstances)
1289
- const instanceFromMap = this._instances.get(element);
1290
- if (instanceFromMap) {
1291
- return instanceFromMap;
1292
- }
1293
-
1294
- // Fallback to element's instance property (for manually created instances)
1295
- return KTDataTable.asElementWithInstance(element).instance;
918
+ return datatableRegistry.get(element);
1296
919
  }
1297
920
 
1298
921
  /**
@@ -1300,7 +923,6 @@ export class KTDataTable<T extends KTDataTableDataInterface>
1300
923
  * This function is now browser-guarded and must be called explicitly.
1301
924
  */
1302
925
  public static init(): void {
1303
- if (typeof document === 'undefined') return;
1304
926
  KTDataTable.createInstances();
1305
927
  }
1306
928
 
@@ -1309,25 +931,7 @@ export class KTDataTable<T extends KTDataTableDataInterface>
1309
931
  * Useful for Livewire wire:navigate where the DOM is replaced and new tables need to be initialized.
1310
932
  */
1311
933
  public static reinit(): void {
1312
- if (typeof document === 'undefined') return;
1313
- const elements = document.querySelectorAll<HTMLElement>(
1314
- '[data-kt-datatable="true"]',
1315
- );
1316
- elements.forEach((element) => {
1317
- try {
1318
- const instance = KTDataTable.getInstance(element);
1319
- if (instance && typeof instance.dispose === 'function') {
1320
- instance.dispose();
1321
- }
1322
- KTData.remove(element, 'datatable');
1323
- element.removeAttribute('data-kt-datatable-initialized');
1324
- element.classList.remove('datatable-initialized');
1325
- } catch {
1326
- // ignore per-element errors
1327
- }
1328
- });
1329
- KTDataTable._instances.clear();
1330
- KTDataTable.createInstances();
934
+ datatableRegistry.reinit((el) => new KTDataTable(el));
1331
935
  }
1332
936
 
1333
937
  /**
@@ -1373,13 +977,20 @@ export class KTDataTable<T extends KTDataTableDataInterface>
1373
977
  }
1374
978
 
1375
979
  /**
1376
- * Reapply checked state to visible checkboxes (after redraw/pagination)
980
+ * Re-apply checkbox checked states to visible rows after a redraw or pagination change.
1377
981
  * @returns {void}
1378
982
  */
1379
- public update(): void {
983
+ public refreshCheckboxes(): void {
1380
984
  this._checkbox.updateState();
1381
985
  }
1382
986
 
987
+ /**
988
+ * @deprecated Use {@link refreshCheckboxes} instead.
989
+ */
990
+ public update(): void {
991
+ this.refreshCheckboxes();
992
+ }
993
+
1383
994
  // Other plugin methods can be added here
1384
995
  }
1385
996