@keenthemes/ktui 1.2.4 → 1.2.6

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 (152) hide show
  1. package/dist/ktui.js +2551 -2817
  2. package/dist/ktui.min.js +1 -1
  3. package/dist/ktui.min.js.map +1 -1
  4. package/dist/styles.css +136 -40
  5. package/lib/cjs/components/datatable/datatable-checkbox.d.ts.map +1 -1
  6. package/lib/cjs/components/datatable/datatable-checkbox.js +34 -15
  7. package/lib/cjs/components/datatable/datatable-checkbox.js.map +1 -1
  8. package/lib/cjs/components/datatable/datatable-contracts.d.ts +3 -3
  9. package/lib/cjs/components/datatable/datatable-contracts.d.ts.map +1 -1
  10. package/lib/cjs/components/datatable/datatable-layout-plugin.d.ts +7 -0
  11. package/lib/cjs/components/datatable/datatable-layout-plugin.d.ts.map +1 -0
  12. package/lib/cjs/components/datatable/datatable-layout-plugin.js +328 -0
  13. package/lib/cjs/components/datatable/datatable-layout-plugin.js.map +1 -0
  14. package/lib/cjs/components/datatable/datatable-local-provider.d.ts +2 -2
  15. package/lib/cjs/components/datatable/datatable-local-provider.d.ts.map +1 -1
  16. package/lib/cjs/components/datatable/datatable-local-provider.js +18 -10
  17. package/lib/cjs/components/datatable/datatable-local-provider.js.map +1 -1
  18. package/lib/cjs/components/datatable/datatable-pagination-renderer.d.ts.map +1 -1
  19. package/lib/cjs/components/datatable/datatable-pagination-renderer.js +40 -25
  20. package/lib/cjs/components/datatable/datatable-pagination-renderer.js.map +1 -1
  21. package/lib/cjs/components/datatable/datatable-remote-provider.d.ts.map +1 -1
  22. package/lib/cjs/components/datatable/datatable-remote-provider.js +3 -0
  23. package/lib/cjs/components/datatable/datatable-remote-provider.js.map +1 -1
  24. package/lib/cjs/components/datatable/datatable-table-renderer.d.ts.map +1 -1
  25. package/lib/cjs/components/datatable/datatable-table-renderer.js +14 -6
  26. package/lib/cjs/components/datatable/datatable-table-renderer.js.map +1 -1
  27. package/lib/cjs/components/datatable/datatable.d.ts +9 -0
  28. package/lib/cjs/components/datatable/datatable.d.ts.map +1 -1
  29. package/lib/cjs/components/datatable/datatable.js +200 -61
  30. package/lib/cjs/components/datatable/datatable.js.map +1 -1
  31. package/lib/cjs/components/datatable/index.d.ts +1 -1
  32. package/lib/cjs/components/datatable/index.d.ts.map +1 -1
  33. package/lib/cjs/components/datatable/types.d.ts +27 -0
  34. package/lib/cjs/components/datatable/types.d.ts.map +1 -1
  35. package/lib/cjs/components/dropdown/dropdown.d.ts +2 -2
  36. package/lib/cjs/components/dropdown/dropdown.d.ts.map +1 -1
  37. package/lib/cjs/components/dropdown/dropdown.js +68 -31
  38. package/lib/cjs/components/dropdown/dropdown.js.map +1 -1
  39. package/lib/cjs/components/input-number/index.d.ts +7 -0
  40. package/lib/cjs/components/input-number/index.d.ts.map +1 -0
  41. package/lib/cjs/components/input-number/index.js +10 -0
  42. package/lib/cjs/components/input-number/index.js.map +1 -0
  43. package/lib/cjs/components/input-number/input-number.d.ts +40 -0
  44. package/lib/cjs/components/input-number/input-number.d.ts.map +1 -0
  45. package/lib/cjs/components/input-number/input-number.js +248 -0
  46. package/lib/cjs/components/input-number/input-number.js.map +1 -0
  47. package/lib/cjs/components/input-number/types.d.ts +30 -0
  48. package/lib/cjs/components/input-number/types.d.ts.map +1 -0
  49. package/lib/cjs/components/input-number/types.js +7 -0
  50. package/lib/cjs/components/input-number/types.js.map +1 -0
  51. package/lib/cjs/components/select/config.d.ts +1 -0
  52. package/lib/cjs/components/select/config.d.ts.map +1 -1
  53. package/lib/cjs/components/select/config.js +2 -1
  54. package/lib/cjs/components/select/config.js.map +1 -1
  55. package/lib/cjs/components/select/select.d.ts +8 -1
  56. package/lib/cjs/components/select/select.d.ts.map +1 -1
  57. package/lib/cjs/components/select/select.js +14 -1
  58. package/lib/cjs/components/select/select.js.map +1 -1
  59. package/lib/cjs/components/select/tags.d.ts.map +1 -1
  60. package/lib/cjs/components/select/tags.js +10 -0
  61. package/lib/cjs/components/select/tags.js.map +1 -1
  62. package/lib/cjs/index.d.ts +5 -1
  63. package/lib/cjs/index.d.ts.map +1 -1
  64. package/lib/cjs/index.js +5 -1
  65. package/lib/cjs/index.js.map +1 -1
  66. package/lib/esm/components/datatable/datatable-checkbox.d.ts.map +1 -1
  67. package/lib/esm/components/datatable/datatable-checkbox.js +34 -15
  68. package/lib/esm/components/datatable/datatable-checkbox.js.map +1 -1
  69. package/lib/esm/components/datatable/datatable-contracts.d.ts +3 -3
  70. package/lib/esm/components/datatable/datatable-contracts.d.ts.map +1 -1
  71. package/lib/esm/components/datatable/datatable-layout-plugin.d.ts +7 -0
  72. package/lib/esm/components/datatable/datatable-layout-plugin.d.ts.map +1 -0
  73. package/lib/esm/components/datatable/datatable-layout-plugin.js +324 -0
  74. package/lib/esm/components/datatable/datatable-layout-plugin.js.map +1 -0
  75. package/lib/esm/components/datatable/datatable-local-provider.d.ts +2 -2
  76. package/lib/esm/components/datatable/datatable-local-provider.d.ts.map +1 -1
  77. package/lib/esm/components/datatable/datatable-local-provider.js +18 -10
  78. package/lib/esm/components/datatable/datatable-local-provider.js.map +1 -1
  79. package/lib/esm/components/datatable/datatable-pagination-renderer.d.ts.map +1 -1
  80. package/lib/esm/components/datatable/datatable-pagination-renderer.js +40 -25
  81. package/lib/esm/components/datatable/datatable-pagination-renderer.js.map +1 -1
  82. package/lib/esm/components/datatable/datatable-remote-provider.d.ts.map +1 -1
  83. package/lib/esm/components/datatable/datatable-remote-provider.js +3 -0
  84. package/lib/esm/components/datatable/datatable-remote-provider.js.map +1 -1
  85. package/lib/esm/components/datatable/datatable-table-renderer.d.ts.map +1 -1
  86. package/lib/esm/components/datatable/datatable-table-renderer.js +14 -6
  87. package/lib/esm/components/datatable/datatable-table-renderer.js.map +1 -1
  88. package/lib/esm/components/datatable/datatable.d.ts +9 -0
  89. package/lib/esm/components/datatable/datatable.d.ts.map +1 -1
  90. package/lib/esm/components/datatable/datatable.js +200 -61
  91. package/lib/esm/components/datatable/datatable.js.map +1 -1
  92. package/lib/esm/components/datatable/index.d.ts +1 -1
  93. package/lib/esm/components/datatable/index.d.ts.map +1 -1
  94. package/lib/esm/components/datatable/types.d.ts +27 -0
  95. package/lib/esm/components/datatable/types.d.ts.map +1 -1
  96. package/lib/esm/components/dropdown/dropdown.d.ts +2 -2
  97. package/lib/esm/components/dropdown/dropdown.d.ts.map +1 -1
  98. package/lib/esm/components/dropdown/dropdown.js +68 -31
  99. package/lib/esm/components/dropdown/dropdown.js.map +1 -1
  100. package/lib/esm/components/input-number/index.d.ts +7 -0
  101. package/lib/esm/components/input-number/index.d.ts.map +1 -0
  102. package/lib/esm/components/input-number/index.js +6 -0
  103. package/lib/esm/components/input-number/index.js.map +1 -0
  104. package/lib/esm/components/input-number/input-number.d.ts +40 -0
  105. package/lib/esm/components/input-number/input-number.d.ts.map +1 -0
  106. package/lib/esm/components/input-number/input-number.js +245 -0
  107. package/lib/esm/components/input-number/input-number.js.map +1 -0
  108. package/lib/esm/components/input-number/types.d.ts +30 -0
  109. package/lib/esm/components/input-number/types.d.ts.map +1 -0
  110. package/lib/esm/components/input-number/types.js +6 -0
  111. package/lib/esm/components/input-number/types.js.map +1 -0
  112. package/lib/esm/components/select/config.d.ts +1 -0
  113. package/lib/esm/components/select/config.d.ts.map +1 -1
  114. package/lib/esm/components/select/config.js +2 -1
  115. package/lib/esm/components/select/config.js.map +1 -1
  116. package/lib/esm/components/select/select.d.ts +8 -1
  117. package/lib/esm/components/select/select.d.ts.map +1 -1
  118. package/lib/esm/components/select/select.js +14 -1
  119. package/lib/esm/components/select/select.js.map +1 -1
  120. package/lib/esm/components/select/tags.d.ts.map +1 -1
  121. package/lib/esm/components/select/tags.js +11 -1
  122. package/lib/esm/components/select/tags.js.map +1 -1
  123. package/lib/esm/index.d.ts +5 -1
  124. package/lib/esm/index.d.ts.map +1 -1
  125. package/lib/esm/index.js +3 -0
  126. package/lib/esm/index.js.map +1 -1
  127. package/package.json +5 -11
  128. package/src/components/datatable/__tests__/locked-layout.test.ts +257 -0
  129. package/src/components/datatable/__tests__/pagination-reset.test.ts +18 -0
  130. package/src/components/datatable/datatable-checkbox.ts +35 -27
  131. package/src/components/datatable/datatable-contracts.ts +3 -3
  132. package/src/components/datatable/datatable-layout-plugin.ts +449 -0
  133. package/src/components/datatable/datatable-local-provider.ts +21 -14
  134. package/src/components/datatable/datatable-pagination-renderer.ts +40 -29
  135. package/src/components/datatable/datatable-remote-provider.ts +3 -0
  136. package/src/components/datatable/datatable-table-renderer.ts +40 -32
  137. package/src/components/datatable/datatable.css +98 -0
  138. package/src/components/datatable/datatable.ts +223 -86
  139. package/src/components/datatable/index.ts +5 -0
  140. package/src/components/datatable/types.ts +33 -0
  141. package/src/components/dropdown/dropdown.ts +86 -58
  142. package/src/components/input/input-group.css +14 -1
  143. package/src/components/input-number/__tests__/input-number.test.ts +278 -0
  144. package/src/components/input-number/index.ts +11 -0
  145. package/src/components/input-number/input-number.ts +267 -0
  146. package/src/components/input-number/types.ts +32 -0
  147. package/src/components/select/__tests__/ux-behaviors.test.ts +72 -0
  148. package/src/components/select/config.ts +3 -1
  149. package/src/components/select/select.css +23 -20
  150. package/src/components/select/select.ts +15 -1
  151. package/src/components/select/tags.ts +14 -1
  152. package/src/index.ts +14 -0
@@ -11,6 +11,8 @@ import {
11
11
  KTDataTableSortOrderInterface,
12
12
  KTDataTableStateInterface,
13
13
  KTDataTableColumnFilterInterface,
14
+ KTDataTableLayoutPluginContextInterface,
15
+ KTDataTableLayoutPluginInterface,
14
16
  } from './types';
15
17
  import { KTOptionType } from '../../types';
16
18
  import KTComponents from '../../index';
@@ -20,6 +22,7 @@ import {
20
22
  KTDataTableCheckboxAPI,
21
23
  } from './datatable-checkbox';
22
24
  import { createSortHandler, KTDataTableSortAPI } from './datatable-sort';
25
+ import { createStickyLayoutPlugin } from './datatable-layout-plugin';
23
26
  import {
24
27
  KTDataTableCleanup,
25
28
  KTDataTableEventAdapter,
@@ -33,6 +36,7 @@ import { KTDataTableRemoteDataProvider } from './datatable-remote-provider';
33
36
  import { KTDataTableConfigStateStore } from './datatable-state-store';
34
37
  import { KTDataTableDomPaginationRenderer } from './datatable-pagination-renderer';
35
38
  import { KTDataTableDomTableRenderer } from './datatable-table-renderer';
39
+ import KTUtils from '../../helpers/utils';
36
40
 
37
41
  /**
38
42
  * Custom DataTable plugin class with server-side API, pagination, and sorting
@@ -74,12 +78,13 @@ export class KTDataTable<T extends KTDataTableDataInterface>
74
78
  private _originalTdClasses: string[][] = []; // Store original td classes as a 2D array [row][col]
75
79
  private _originalThClasses: string[] = []; // Store original th classes
76
80
 
77
- private _infoElement: HTMLElement;
78
- private _sizeElement: HTMLSelectElement;
79
- private _paginationElement: HTMLElement;
81
+ private _infoElement: HTMLElement | null = null;
82
+ private _sizeElement: HTMLSelectElement | null = null;
83
+ private _paginationElement: HTMLElement | null = null;
80
84
 
81
85
  private _checkbox: KTDataTableCheckboxAPI;
82
86
  private _sortHandler: KTDataTableSortAPI<T>;
87
+ private _layoutPlugin: KTDataTableLayoutPluginInterface | null = null;
83
88
  private _eventAdapter: KTDataTableEventAdapter;
84
89
  private _stateStore: KTDataTableStateStore;
85
90
  private _localProvider: KTDataTableLocalDataProvider<T>;
@@ -95,10 +100,10 @@ export class KTDataTable<T extends KTDataTableDataInterface>
95
100
  super();
96
101
 
97
102
  if (KTData.has(element as HTMLElement, this._name)) {
98
- // Already initialized (e.g. by createInstances). Merge user config so columns/sortType etc. apply.
103
+ // Already initialized (e.g. by createInstances). Merge demo config and redraw once.
99
104
  const existing = KTDataTable.getInstance(element as HTMLElement);
100
105
  if (existing && config) {
101
- existing._mergeConfig(config);
106
+ existing._applyRuntimeConfig(config);
102
107
  }
103
108
  return;
104
109
  }
@@ -106,7 +111,14 @@ export class KTDataTable<T extends KTDataTableDataInterface>
106
111
  this._defaultConfig = this._initDefaultConfig(config);
107
112
 
108
113
  this._init(element);
114
+ if (!this._element) {
115
+ return;
116
+ }
117
+ if (!this._element.hasAttribute('data-kt-datatable')) {
118
+ this._element.setAttribute('data-kt-datatable', 'true');
119
+ }
109
120
  this._buildConfig();
121
+ this._normalizePageSizeConfig();
110
122
  this._stateStore = new KTDataTableConfigStateStore(this._config);
111
123
  this._eventAdapter = createDataTableEventAdapter(
112
124
  this._fireEvent.bind(this),
@@ -117,6 +129,7 @@ export class KTDataTable<T extends KTDataTableDataInterface>
117
129
  KTDataTable.asElementWithInstance(element).instance = this;
118
130
 
119
131
  this._initElements();
132
+ this._layoutPlugin = this._createLayoutPlugin();
120
133
  this._tableRenderer = new KTDataTableDomTableRenderer<T>();
121
134
  this._paginationRenderer = new KTDataTableDomPaginationRenderer();
122
135
  this._initDataProviders();
@@ -152,6 +165,7 @@ export class KTDataTable<T extends KTDataTableDataInterface>
152
165
 
153
166
  if (this._config.stateSave) {
154
167
  this._loadState();
168
+ this._normalizePageState();
155
169
  }
156
170
 
157
171
  this._updateData();
@@ -185,6 +199,69 @@ export class KTDataTable<T extends KTDataTableDataInterface>
185
199
  });
186
200
  }
187
201
 
202
+ private _createLayoutPlugin(): KTDataTableLayoutPluginInterface | null {
203
+ if (this._config.layoutPlugin) {
204
+ return this._config.layoutPlugin;
205
+ }
206
+
207
+ if (this._config.lockedLayout) {
208
+ return createStickyLayoutPlugin();
209
+ }
210
+
211
+ return null;
212
+ }
213
+
214
+ /**
215
+ * Apply config from a late constructor call (e.g. docs demo script after auto-init).
216
+ */
217
+ private _applyRuntimeConfig(config: KTDataTableConfigInterface): void {
218
+ this._mergeConfig(config);
219
+ this._normalizePageSizeConfig();
220
+ this._layoutPlugin = this._createLayoutPlugin();
221
+ this.reload();
222
+ }
223
+
224
+ private _normalizePageSizeConfig(): void {
225
+ const configuredPageSizes = Array.isArray(this._config.pageSizes)
226
+ ? this._config.pageSizes
227
+ : [];
228
+ const pageSizes = configuredPageSizes
229
+ .map((size) => Number(size))
230
+ .filter((size) => Number.isFinite(size) && size > 0)
231
+ .map((size) => Math.floor(size));
232
+ const fallbackPageSizes = [5, 10, 20, 30, 50];
233
+ this._config.pageSizes =
234
+ pageSizes.length > 0 ? Array.from(new Set(pageSizes)) : fallbackPageSizes;
235
+
236
+ const configuredPageSize = Number(this._config.pageSize);
237
+ this._config.pageSize =
238
+ Number.isFinite(configuredPageSize) && configuredPageSize > 0
239
+ ? Math.floor(configuredPageSize)
240
+ : this._config.pageSizes[0];
241
+ }
242
+
243
+ private _normalizePageState(): void {
244
+ const statePageSize = Number(this._config._state.pageSize);
245
+ this._config._state.pageSize =
246
+ Number.isFinite(statePageSize) && statePageSize > 0
247
+ ? Math.floor(statePageSize)
248
+ : this._config.pageSize;
249
+
250
+ const statePage = Number(this._config._state.page);
251
+ this._config._state.page =
252
+ Number.isFinite(statePage) && statePage > 0 ? Math.floor(statePage) : 1;
253
+ }
254
+
255
+ private _getLayoutPluginContext(): KTDataTableLayoutPluginContextInterface {
256
+ return {
257
+ rootElement: this._element,
258
+ tableElement: this._tableElement,
259
+ theadElement: this._theadElement,
260
+ tbodyElement: this._tbodyElement,
261
+ config: this._config,
262
+ };
263
+ }
264
+
188
265
  /**
189
266
  * Initialize default configuration for the datatable
190
267
  * @param config User-provided configuration options
@@ -425,43 +502,37 @@ export class KTDataTable<T extends KTDataTableDataInterface>
425
502
  * @returns {void}
426
503
  */
427
504
  private _initElements(): void {
428
- /**
429
- * Data table element
430
- */
431
- this._tableElement = this._element.querySelector<HTMLTableElement>(
432
- this._config.attributes.table,
433
- )!;
434
- /**
435
- * Table body element
436
- */
505
+ const root = this._element;
506
+ const attrs = this._config.attributes;
507
+ if (!root || !attrs?.table) {
508
+ throw new Error(
509
+ 'KTDataTable: root element and table selector are required',
510
+ );
511
+ }
512
+
513
+ const tableEl = root.querySelector<HTMLTableElement>(attrs.table);
514
+ if (!tableEl) {
515
+ throw new Error(`KTDataTable: table element not found (${attrs.table})`);
516
+ }
517
+ this._tableElement = tableEl;
518
+
437
519
  this._tbodyElement =
438
520
  this._tableElement.tBodies[0] || this._tableElement.createTBody();
439
- /**
440
- * Table head element
441
- */
442
- this._theadElement = this._tableElement.tHead!;
443
521
 
444
- // Store original classes
522
+ this._theadElement =
523
+ this._tableElement.tHead ?? this._tableElement.createTHead();
524
+
445
525
  this._storeOriginalClasses();
446
526
 
447
- /**
448
- * Pagination info element
449
- */
450
- this._infoElement = this._element.querySelector<HTMLElement>(
451
- this._config.attributes.info,
452
- )!;
453
- /**
454
- * Page size dropdown element
455
- */
456
- this._sizeElement = this._element.querySelector<HTMLSelectElement>(
457
- this._config.attributes.size,
458
- )!;
459
- /**
460
- * Pagination element
461
- */
462
- this._paginationElement = this._element.querySelector<HTMLElement>(
463
- this._config.attributes.pagination,
464
- )!;
527
+ this._infoElement = attrs.info
528
+ ? root.querySelector<HTMLElement>(attrs.info)
529
+ : null;
530
+ this._sizeElement = attrs.size
531
+ ? root.querySelector<HTMLSelectElement>(attrs.size)
532
+ : null;
533
+ this._paginationElement = attrs.pagination
534
+ ? root.querySelector<HTMLElement>(attrs.pagination)
535
+ : null;
465
536
  }
466
537
 
467
538
  /**
@@ -540,7 +611,7 @@ export class KTDataTable<T extends KTDataTableDataInterface>
540
611
  * @returns {void}
541
612
  */
542
613
  private _finalize(): void {
543
- this._element.classList.add('datatable-initialized');
614
+ this._element?.classList.add('datatable-initialized');
544
615
 
545
616
  // Initialize checkbox logic
546
617
  this._checkbox.init();
@@ -600,7 +671,7 @@ export class KTDataTable<T extends KTDataTableDataInterface>
600
671
  // Create a new debounced search function
601
672
  const debouncedSearch = this._debounce(() => {
602
673
  this.search(searchElement.value);
603
- }, this._config.search.delay);
674
+ }, this._config.search?.delay ?? 500);
604
675
 
605
676
  // Store the new debounced function as a property of the element
606
677
  searchWithDebounce._debouncedSearch = debouncedSearch;
@@ -647,7 +718,9 @@ export class KTDataTable<T extends KTDataTableDataInterface>
647
718
 
648
719
  private _createUrl(
649
720
  pathOrUrl: string,
650
- baseUrl: string | null = window.location.origin,
721
+ baseUrl: string | null = typeof window !== 'undefined'
722
+ ? window.location.origin
723
+ : null,
651
724
  ): URL {
652
725
  // Regular expression to check if the input is a full URL
653
726
  const isFullUrl = /^[a-zA-Z][a-zA-Z\d+\-.]*:\/\//.test(pathOrUrl);
@@ -661,7 +734,39 @@ export class KTDataTable<T extends KTDataTableDataInterface>
661
734
  ? pathOrUrl
662
735
  : `/${pathOrUrl}`;
663
736
 
664
- return new URL(normalizedPath, baseUrl);
737
+ // Opaque origins (e.g. srcdoc iframes) serialize as the string "null", which is not a valid URL base.
738
+ const bases: string[] = [];
739
+ if (baseUrl && baseUrl !== 'null') {
740
+ bases.push(baseUrl);
741
+ }
742
+ if (typeof window !== 'undefined') {
743
+ const href = window.location.href;
744
+ if (href && !bases.includes(href)) {
745
+ bases.push(href);
746
+ }
747
+ try {
748
+ if (window.parent !== window && window.parent.location?.href) {
749
+ const parentHref = window.parent.location.href;
750
+ if (parentHref && !bases.includes(parentHref)) {
751
+ bases.push(parentHref);
752
+ }
753
+ }
754
+ } catch {
755
+ // parent is cross-origin
756
+ }
757
+ }
758
+
759
+ for (const base of bases) {
760
+ try {
761
+ return new URL(normalizedPath, base);
762
+ } catch {
763
+ // try next base
764
+ }
765
+ }
766
+
767
+ throw new Error(
768
+ `KTDataTable: cannot resolve relative apiEndpoint "${pathOrUrl}" (no valid base URL; use an absolute apiEndpoint).`,
769
+ );
665
770
  }
666
771
 
667
772
  /**
@@ -669,11 +774,20 @@ export class KTDataTable<T extends KTDataTableDataInterface>
669
774
  * @returns {Promise<void>} A promise that resolves when the table and pagination controls are updated
670
775
  */
671
776
  private async _draw(): Promise<void> {
672
- this._stateStore.patchState({
673
- totalPages:
674
- Math.ceil(this.getState().totalItems / this.getState().pageSize) || 0,
675
- });
777
+ const normalizedPageSize = Math.max(
778
+ 1,
779
+ Number(this.getState().pageSize) || Number(this._config.pageSize) || 1,
780
+ );
781
+ const totalPages =
782
+ Math.ceil(this.getState().totalItems / normalizedPageSize) || 0;
783
+ const page =
784
+ totalPages > 0
785
+ ? Math.min(Math.max(1, this.getState().page), totalPages)
786
+ : 1;
787
+
788
+ this._stateStore.patchState({ totalPages, page });
676
789
 
790
+ this._layoutPlugin?.beforeDraw?.(this._getLayoutPluginContext());
677
791
  this._emit('draw');
678
792
 
679
793
  this._dispose();
@@ -683,10 +797,19 @@ export class KTDataTable<T extends KTDataTableDataInterface>
683
797
  this._updateTable();
684
798
  }
685
799
 
686
- if (this._infoElement && this._paginationElement) {
800
+ if (this._infoElement || this._sizeElement || this._paginationElement) {
687
801
  this._updatePagination();
688
802
  }
689
803
 
804
+ 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
+
690
813
  this._emit('drew');
691
814
 
692
815
  // Spinner is hidden in _finalize() to ensure it stays visible until the entire request completes
@@ -702,7 +825,7 @@ export class KTDataTable<T extends KTDataTableDataInterface>
702
825
  * @returns {HTMLTableSectionElement} The new table body element
703
826
  */
704
827
  private _updateTable(): HTMLTableSectionElement {
705
- return this._tableRenderer.render({
828
+ this._tbodyElement = this._tableRenderer.render({
706
829
  config: this._config,
707
830
  context: this,
708
831
  data: this._data,
@@ -714,6 +837,7 @@ export class KTDataTable<T extends KTDataTableDataInterface>
714
837
  tableElement: this._tableElement,
715
838
  theadElement: this._theadElement,
716
839
  });
840
+ return this._tbodyElement;
717
841
  }
718
842
 
719
843
  /**
@@ -778,38 +902,45 @@ export class KTDataTable<T extends KTDataTableDataInterface>
778
902
 
779
903
  // Method to show the loading spinner
780
904
  private _showSpinner(): void {
781
- const spinner =
782
- this._element.querySelector<HTMLElement>(
783
- this._config.attributes.spinner,
784
- ) || this._createSpinner();
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();
785
910
  if (spinner) {
786
911
  spinner.style.display = 'block';
787
912
  }
788
- this._element.classList.add(this._config.loadingClass);
913
+ root?.classList.add(this._config.loadingClass ?? 'loading');
789
914
  }
790
915
 
791
916
  // Method to hide the loading spinner
792
917
  private _hideSpinner(): void {
793
- const spinner = this._element.querySelector<HTMLElement>(
794
- this._config.attributes.spinner,
795
- );
918
+ const root = this._element;
919
+ const spinnerSel = this._config.attributes?.spinner;
920
+ const spinner =
921
+ root && spinnerSel ? root.querySelector<HTMLElement>(spinnerSel) : null;
796
922
  if (spinner) {
797
923
  spinner.style.display = 'none';
798
924
  }
799
- this._element.classList.remove(this._config.loadingClass);
925
+ root?.classList.remove(this._config.loadingClass ?? 'loading');
800
926
  }
801
927
 
802
928
  // Method to create a spinner element if it doesn't exist
803
- private _createSpinner(): HTMLElement {
804
- if (typeof this._config.loading === 'undefined') {
929
+ private _createSpinner(): HTMLElement | null {
930
+ const loading = this._config.loading;
931
+ if (!loading) {
805
932
  return null;
806
933
  }
807
934
 
808
935
  const template = document.createElement('template');
809
- template.innerHTML = this._config.loading.template
936
+ template.innerHTML = loading.template
810
937
  .trim()
811
- .replace('{content}', this._config.loading.content);
812
- const spinner = template.content.firstChild as HTMLElement;
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;
813
944
  spinner.setAttribute('data-kt-datatable-spinner', 'true');
814
945
 
815
946
  this._tableElement.appendChild(spinner);
@@ -878,18 +1009,15 @@ export class KTDataTable<T extends KTDataTableDataInterface>
878
1009
  }
879
1010
 
880
1011
  private _tableId(): string {
881
- let id: string = null;
882
- // If the table element has an ID, use that
883
- if (this._tableElement?.getAttribute('id')) {
884
- id = this._tableElement.getAttribute('id') as string;
1012
+ const tableIdAttr = this._tableElement?.getAttribute('id');
1013
+ if (tableIdAttr) {
1014
+ return tableIdAttr;
885
1015
  }
886
-
887
- // If the component element has an ID, use that
888
- if (this._element?.getAttribute('id')) {
889
- id = this._element.getAttribute('id') as string;
1016
+ const rootIdAttr = this._element?.getAttribute('id');
1017
+ if (rootIdAttr) {
1018
+ return rootIdAttr;
890
1019
  }
891
-
892
- return id;
1020
+ return '';
893
1021
  }
894
1022
 
895
1023
  /**
@@ -897,6 +1025,13 @@ export class KTDataTable<T extends KTDataTableDataInterface>
897
1025
  * This method is called before re-rendering or when disposing the component.
898
1026
  */
899
1027
  private _dispose() {
1028
+ this._layoutPlugin?.dispose?.(this._getLayoutPluginContext());
1029
+
1030
+ const root = this._element;
1031
+ if (!root) {
1032
+ return;
1033
+ }
1034
+
900
1035
  this._cleanupCallbacks.forEach((cleanup) => cleanup());
901
1036
  this._cleanupCallbacks = [];
902
1037
 
@@ -939,12 +1074,13 @@ export class KTDataTable<T extends KTDataTableDataInterface>
939
1074
  if (this._checkbox && typeof checkboxWithDispose.dispose === 'function') {
940
1075
  checkboxWithDispose.dispose();
941
1076
  } else {
942
- // Remove header checkbox event listener if possible
943
- const headerCheckElement = this._element.querySelector<HTMLInputElement>(
944
- this._config.attributes.check,
945
- );
946
- if (headerCheckElement) {
947
- headerCheckElement.replaceWith(headerCheckElement.cloneNode(true));
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
+ }
948
1084
  }
949
1085
  }
950
1086
  // KTDataTableSortAPI does not have a dispose method, but we can remove th click listeners by replacing them
@@ -956,22 +1092,23 @@ export class KTDataTable<T extends KTDataTableDataInterface>
956
1092
  }
957
1093
 
958
1094
  // --- 5. Remove spinner DOM node if it exists ---
959
- const spinner = this._element.querySelector<HTMLElement>(
960
- this._config.attributes.spinner,
961
- );
962
- if (spinner && spinner.parentNode) {
963
- spinner.parentNode.removeChild(spinner);
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
+ }
964
1101
  }
965
- this._element.classList.remove(this._config.loadingClass);
1102
+ root.classList.remove(this._config.loadingClass ?? 'loading');
966
1103
 
967
1104
  // --- 6. Remove instance reference from the DOM element ---
968
- const elementWithInstance = KTDataTable.asElementWithInstance(
969
- this._element,
970
- );
1105
+ const elementWithInstance = KTDataTable.asElementWithInstance(root);
971
1106
  if (elementWithInstance.instance) {
972
1107
  delete elementWithInstance.instance;
973
1108
  }
974
1109
 
1110
+ KTData.remove(root, this._name);
1111
+
975
1112
  // --- 7. (Optional) Clear localStorage state ---
976
1113
  // Uncomment the following line if you want to clear state on dispose:
977
1114
  // this._deleteState();
@@ -16,4 +16,9 @@ export type {
16
16
  KTDataTableCheckConfigInterface,
17
17
  KTDataTableCheckInterface,
18
18
  KTDataTableCheckChangePayloadInterface,
19
+ KTDataTableLockedRowsConfigInterface,
20
+ KTDataTableLockedColumnsConfigInterface,
21
+ KTDataTableLockedLayoutConfigInterface,
22
+ KTDataTableLayoutPluginContextInterface,
23
+ KTDataTableLayoutPluginInterface,
19
24
  } from './types';
@@ -69,6 +69,36 @@ export interface KTDataTableResponseDataInterface {
69
69
  totalCount: number;
70
70
  }
71
71
 
72
+ export interface KTDataTableLockedRowsConfigInterface {
73
+ top?: number;
74
+ bottom?: number;
75
+ }
76
+
77
+ export interface KTDataTableLockedColumnsConfigInterface {
78
+ left?: string[];
79
+ right?: string[];
80
+ }
81
+
82
+ export interface KTDataTableLockedLayoutConfigInterface {
83
+ stickyHeader?: boolean;
84
+ stickyRows?: KTDataTableLockedRowsConfigInterface;
85
+ stickyColumns?: KTDataTableLockedColumnsConfigInterface;
86
+ }
87
+
88
+ export interface KTDataTableLayoutPluginContextInterface {
89
+ rootElement: HTMLElement;
90
+ tableElement: HTMLTableElement;
91
+ theadElement: HTMLTableSectionElement;
92
+ tbodyElement: HTMLTableSectionElement;
93
+ config: KTDataTableConfigInterface;
94
+ }
95
+
96
+ export interface KTDataTableLayoutPluginInterface {
97
+ beforeDraw?: (ctx: KTDataTableLayoutPluginContextInterface) => void;
98
+ afterDraw?: (ctx: KTDataTableLayoutPluginContextInterface) => void;
99
+ dispose?: (ctx: KTDataTableLayoutPluginContextInterface) => void;
100
+ }
101
+
72
102
  // Define the DataTable options type
73
103
  export interface KTDataTableConfigInterface {
74
104
  requestMethod?: string;
@@ -187,6 +217,9 @@ export interface KTDataTableConfigInterface {
187
217
  preserveSelection?: boolean;
188
218
  };
189
219
 
220
+ lockedLayout?: KTDataTableLockedLayoutConfigInterface;
221
+ layoutPlugin?: KTDataTableLayoutPluginInterface;
222
+
190
223
  _state?: KTDataTableStateInterface;
191
224
  _data?: KTDataTableDataInterface[];
192
225