@keenthemes/ktui 1.2.4 → 1.2.5

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 (126) hide show
  1. package/dist/ktui.js +590 -131
  2. package/dist/ktui.min.js +1 -1
  3. package/dist/ktui.min.js.map +1 -1
  4. package/dist/styles.css +76 -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-local-provider.d.ts.map +1 -1
  11. package/lib/cjs/components/datatable/datatable-local-provider.js +13 -7
  12. package/lib/cjs/components/datatable/datatable-local-provider.js.map +1 -1
  13. package/lib/cjs/components/datatable/datatable-pagination-renderer.d.ts.map +1 -1
  14. package/lib/cjs/components/datatable/datatable-pagination-renderer.js +31 -15
  15. package/lib/cjs/components/datatable/datatable-pagination-renderer.js.map +1 -1
  16. package/lib/cjs/components/datatable/datatable-remote-provider.d.ts.map +1 -1
  17. package/lib/cjs/components/datatable/datatable-remote-provider.js +3 -0
  18. package/lib/cjs/components/datatable/datatable-remote-provider.js.map +1 -1
  19. package/lib/cjs/components/datatable/datatable-table-renderer.d.ts.map +1 -1
  20. package/lib/cjs/components/datatable/datatable-table-renderer.js +14 -6
  21. package/lib/cjs/components/datatable/datatable-table-renderer.js.map +1 -1
  22. package/lib/cjs/components/datatable/datatable.d.ts.map +1 -1
  23. package/lib/cjs/components/datatable/datatable.js +120 -54
  24. package/lib/cjs/components/datatable/datatable.js.map +1 -1
  25. package/lib/cjs/components/dropdown/dropdown.d.ts +2 -2
  26. package/lib/cjs/components/dropdown/dropdown.d.ts.map +1 -1
  27. package/lib/cjs/components/dropdown/dropdown.js +68 -31
  28. package/lib/cjs/components/dropdown/dropdown.js.map +1 -1
  29. package/lib/cjs/components/input-number/index.d.ts +7 -0
  30. package/lib/cjs/components/input-number/index.d.ts.map +1 -0
  31. package/lib/cjs/components/input-number/index.js +10 -0
  32. package/lib/cjs/components/input-number/index.js.map +1 -0
  33. package/lib/cjs/components/input-number/input-number.d.ts +40 -0
  34. package/lib/cjs/components/input-number/input-number.d.ts.map +1 -0
  35. package/lib/cjs/components/input-number/input-number.js +248 -0
  36. package/lib/cjs/components/input-number/input-number.js.map +1 -0
  37. package/lib/cjs/components/input-number/types.d.ts +30 -0
  38. package/lib/cjs/components/input-number/types.d.ts.map +1 -0
  39. package/lib/cjs/components/input-number/types.js +7 -0
  40. package/lib/cjs/components/input-number/types.js.map +1 -0
  41. package/lib/cjs/components/select/config.d.ts +1 -0
  42. package/lib/cjs/components/select/config.d.ts.map +1 -1
  43. package/lib/cjs/components/select/config.js +2 -1
  44. package/lib/cjs/components/select/config.js.map +1 -1
  45. package/lib/cjs/components/select/select.d.ts +8 -1
  46. package/lib/cjs/components/select/select.d.ts.map +1 -1
  47. package/lib/cjs/components/select/select.js +14 -1
  48. package/lib/cjs/components/select/select.js.map +1 -1
  49. package/lib/cjs/components/select/tags.d.ts.map +1 -1
  50. package/lib/cjs/components/select/tags.js +10 -0
  51. package/lib/cjs/components/select/tags.js.map +1 -1
  52. package/lib/cjs/index.d.ts +4 -0
  53. package/lib/cjs/index.d.ts.map +1 -1
  54. package/lib/cjs/index.js +5 -1
  55. package/lib/cjs/index.js.map +1 -1
  56. package/lib/esm/components/datatable/datatable-checkbox.d.ts.map +1 -1
  57. package/lib/esm/components/datatable/datatable-checkbox.js +34 -15
  58. package/lib/esm/components/datatable/datatable-checkbox.js.map +1 -1
  59. package/lib/esm/components/datatable/datatable-contracts.d.ts +3 -3
  60. package/lib/esm/components/datatable/datatable-contracts.d.ts.map +1 -1
  61. package/lib/esm/components/datatable/datatable-local-provider.d.ts.map +1 -1
  62. package/lib/esm/components/datatable/datatable-local-provider.js +13 -7
  63. package/lib/esm/components/datatable/datatable-local-provider.js.map +1 -1
  64. package/lib/esm/components/datatable/datatable-pagination-renderer.d.ts.map +1 -1
  65. package/lib/esm/components/datatable/datatable-pagination-renderer.js +31 -15
  66. package/lib/esm/components/datatable/datatable-pagination-renderer.js.map +1 -1
  67. package/lib/esm/components/datatable/datatable-remote-provider.d.ts.map +1 -1
  68. package/lib/esm/components/datatable/datatable-remote-provider.js +3 -0
  69. package/lib/esm/components/datatable/datatable-remote-provider.js.map +1 -1
  70. package/lib/esm/components/datatable/datatable-table-renderer.d.ts.map +1 -1
  71. package/lib/esm/components/datatable/datatable-table-renderer.js +14 -6
  72. package/lib/esm/components/datatable/datatable-table-renderer.js.map +1 -1
  73. package/lib/esm/components/datatable/datatable.d.ts.map +1 -1
  74. package/lib/esm/components/datatable/datatable.js +120 -54
  75. package/lib/esm/components/datatable/datatable.js.map +1 -1
  76. package/lib/esm/components/dropdown/dropdown.d.ts +2 -2
  77. package/lib/esm/components/dropdown/dropdown.d.ts.map +1 -1
  78. package/lib/esm/components/dropdown/dropdown.js +68 -31
  79. package/lib/esm/components/dropdown/dropdown.js.map +1 -1
  80. package/lib/esm/components/input-number/index.d.ts +7 -0
  81. package/lib/esm/components/input-number/index.d.ts.map +1 -0
  82. package/lib/esm/components/input-number/index.js +6 -0
  83. package/lib/esm/components/input-number/index.js.map +1 -0
  84. package/lib/esm/components/input-number/input-number.d.ts +40 -0
  85. package/lib/esm/components/input-number/input-number.d.ts.map +1 -0
  86. package/lib/esm/components/input-number/input-number.js +245 -0
  87. package/lib/esm/components/input-number/input-number.js.map +1 -0
  88. package/lib/esm/components/input-number/types.d.ts +30 -0
  89. package/lib/esm/components/input-number/types.d.ts.map +1 -0
  90. package/lib/esm/components/input-number/types.js +6 -0
  91. package/lib/esm/components/input-number/types.js.map +1 -0
  92. package/lib/esm/components/select/config.d.ts +1 -0
  93. package/lib/esm/components/select/config.d.ts.map +1 -1
  94. package/lib/esm/components/select/config.js +2 -1
  95. package/lib/esm/components/select/config.js.map +1 -1
  96. package/lib/esm/components/select/select.d.ts +8 -1
  97. package/lib/esm/components/select/select.d.ts.map +1 -1
  98. package/lib/esm/components/select/select.js +14 -1
  99. package/lib/esm/components/select/select.js.map +1 -1
  100. package/lib/esm/components/select/tags.d.ts.map +1 -1
  101. package/lib/esm/components/select/tags.js +11 -1
  102. package/lib/esm/components/select/tags.js.map +1 -1
  103. package/lib/esm/index.d.ts +4 -0
  104. package/lib/esm/index.d.ts.map +1 -1
  105. package/lib/esm/index.js +3 -0
  106. package/lib/esm/index.js.map +1 -1
  107. package/package.json +5 -11
  108. package/src/components/datatable/datatable-checkbox.ts +34 -23
  109. package/src/components/datatable/datatable-contracts.ts +3 -3
  110. package/src/components/datatable/datatable-local-provider.ts +12 -13
  111. package/src/components/datatable/datatable-pagination-renderer.ts +34 -20
  112. package/src/components/datatable/datatable-remote-provider.ts +3 -0
  113. package/src/components/datatable/datatable-table-renderer.ts +40 -32
  114. package/src/components/datatable/datatable.ts +122 -79
  115. package/src/components/dropdown/dropdown.ts +86 -58
  116. package/src/components/input/input-group.css +14 -1
  117. package/src/components/input-number/__tests__/input-number.test.ts +278 -0
  118. package/src/components/input-number/index.ts +11 -0
  119. package/src/components/input-number/input-number.ts +267 -0
  120. package/src/components/input-number/types.ts +32 -0
  121. package/src/components/select/__tests__/ux-behaviors.test.ts +72 -0
  122. package/src/components/select/config.ts +3 -1
  123. package/src/components/select/select.css +23 -20
  124. package/src/components/select/select.ts +15 -1
  125. package/src/components/select/tags.ts +14 -1
  126. package/src/index.ts +9 -0
@@ -83,11 +83,11 @@ export interface KTDataTableTableRenderer<T extends KTDataTableDataInterface> {
83
83
  export interface KTDataTablePaginationRendererInput {
84
84
  config: KTDataTableConfigInterface;
85
85
  dataLength: number;
86
- infoElement: HTMLElement;
86
+ infoElement?: HTMLElement | null;
87
87
  paginateData: (page: number) => void;
88
- paginationElement: HTMLElement;
88
+ paginationElement?: HTMLElement | null;
89
89
  reloadPageSize: (pageSize: number, page?: number) => void;
90
- sizeElement: HTMLSelectElement;
90
+ sizeElement?: HTMLSelectElement | null;
91
91
  state: KTDataTableStateInterface;
92
92
  }
93
93
 
@@ -63,25 +63,24 @@ export class KTDataTableLocalDataProvider<
63
63
 
64
64
  if (search) {
65
65
  const searchTerm = typeof search === 'string' ? search : '';
66
- filteredData = data = this.options.config.search.callback.call(
67
- this,
68
- data,
69
- searchTerm,
70
- ) as T[];
66
+ const searchCallback = this.options.config.search?.callback;
67
+ if (searchCallback) {
68
+ filteredData = data = searchCallback.call(
69
+ this,
70
+ data,
71
+ searchTerm,
72
+ ) as T[];
73
+ }
71
74
  }
72
75
 
76
+ const sortCallback = this.options.config.sort?.callback;
73
77
  if (
74
78
  sortField !== undefined &&
75
79
  sortOrder !== undefined &&
76
80
  sortOrder !== '' &&
77
- typeof this.options.config.sort.callback === 'function'
81
+ typeof sortCallback === 'function'
78
82
  ) {
79
- data = this.options.config.sort.callback.call(
80
- this,
81
- data,
82
- sortField as string,
83
- sortOrder,
84
- ) as T[];
83
+ data = sortCallback.call(this, data, sortField as string, sortOrder) as T[];
85
84
  }
86
85
 
87
86
  if (data?.length > 0) {
@@ -114,7 +113,7 @@ export class KTDataTableLocalDataProvider<
114
113
  const { _state, ...restConfig } = this.options.config;
115
114
  const checksum: string = KTUtils.checksum(JSON.stringify(restConfig));
116
115
 
117
- if (_state._configChecksum !== checksum) {
116
+ if ((_state?._configChecksum ?? '') !== checksum) {
118
117
  this.options.stateStore.patchState({ _configChecksum: checksum });
119
118
  return true;
120
119
  }
@@ -13,21 +13,27 @@ export class KTDataTableDomPaginationRenderer implements KTDataTablePaginationRe
13
13
  public render(
14
14
  input: KTDataTablePaginationRendererInput,
15
15
  ): KTDataTableCleanup | void {
16
- this.removeChildElements(input.sizeElement);
17
- this.createPageSizeControls(input);
16
+ if (input.sizeElement) {
17
+ this.removeChildElements(input.sizeElement);
18
+ this.createPageSizeControls(input);
19
+ }
18
20
 
19
- this.removeChildElements(input.paginationElement);
20
- this.createPaginationControls(input);
21
+ if (input.paginationElement) {
22
+ this.removeChildElements(input.paginationElement);
23
+ this.createPaginationControls(input);
24
+ }
21
25
 
22
26
  return () => {
23
27
  if (input.sizeElement) {
24
28
  input.sizeElement.onchange = null;
25
29
  }
26
- this.removeChildElements(input.paginationElement);
30
+ if (input.paginationElement) {
31
+ this.removeChildElements(input.paginationElement);
32
+ }
27
33
  };
28
34
  }
29
35
 
30
- private removeChildElements(container: HTMLElement): void {
36
+ private removeChildElements(container?: HTMLElement | null): void {
31
37
  if (!container) {
32
38
  return;
33
39
  }
@@ -39,13 +45,15 @@ export class KTDataTableDomPaginationRenderer implements KTDataTablePaginationRe
39
45
 
40
46
  private createPageSizeControls(
41
47
  input: KTDataTablePaginationRendererInput,
42
- ): HTMLSelectElement {
48
+ ): void {
43
49
  if (!input.sizeElement) {
44
- return input.sizeElement;
50
+ return;
45
51
  }
46
52
 
53
+ const pageSizes = input.config.pageSizes ?? [5, 10, 20, 30, 50];
54
+
47
55
  setTimeout(() => {
48
- const options = input.config.pageSizes.map((size: number) => {
56
+ const options = pageSizes.map((size: number) => {
49
57
  const option = document.createElement('option') as HTMLOptionElement;
50
58
  option.value = String(size);
51
59
  option.text = String(size);
@@ -62,18 +70,12 @@ export class KTDataTableDomPaginationRenderer implements KTDataTablePaginationRe
62
70
  1,
63
71
  );
64
72
  };
65
-
66
- return input.sizeElement;
67
73
  }
68
74
 
69
75
  private createPaginationControls(
70
76
  input: KTDataTablePaginationRendererInput,
71
- ): HTMLElement {
72
- if (
73
- !input.infoElement ||
74
- !input.paginationElement ||
75
- input.dataLength === 0
76
- ) {
77
+ ): HTMLElement | null {
78
+ if (!input.paginationElement || input.dataLength === 0) {
77
79
  return null;
78
80
  }
79
81
 
@@ -86,7 +88,13 @@ export class KTDataTableDomPaginationRenderer implements KTDataTablePaginationRe
86
88
  private setPaginationInfoText(
87
89
  input: KTDataTablePaginationRendererInput,
88
90
  ): void {
89
- input.infoElement.textContent = input.config.info
91
+ if (!input.infoElement) {
92
+ return;
93
+ }
94
+
95
+ const infoTemplate =
96
+ input.config.info ?? '{start}-{end} of {total}';
97
+ input.infoElement.textContent = infoTemplate
90
98
  .replace(
91
99
  '{start}',
92
100
  (input.state.page - 1) * input.state.pageSize + 1 + '',
@@ -105,8 +113,14 @@ export class KTDataTableDomPaginationRenderer implements KTDataTablePaginationRe
105
113
  paginationContainer: HTMLElement,
106
114
  input: KTDataTablePaginationRendererInput,
107
115
  ): void {
116
+ const pagination = input.config.pagination;
117
+ if (!pagination) {
118
+ return;
119
+ }
120
+
108
121
  const { page: currentPage, totalPages } = input.state;
109
- const { previous, next, number, more } = input.config.pagination;
122
+ const { previous, next, number, more } = pagination;
123
+ const pageMoreLimit = input.config.pageMoreLimit ?? 3;
110
124
 
111
125
  const createButton = (
112
126
  text: string,
@@ -135,7 +149,7 @@ export class KTDataTableDomPaginationRenderer implements KTDataTablePaginationRe
135
149
  const range = this.calculatePageRange(
136
150
  currentPage,
137
151
  totalPages,
138
- input.config.pageMoreLimit,
152
+ pageMoreLimit,
139
153
  );
140
154
 
141
155
  if (range.start > 1) {
@@ -137,6 +137,9 @@ export class KTDataTableRemoteDataProvider<
137
137
  this.options.config.requestMethod;
138
138
  let requestBody: RequestInit['body'] | undefined = undefined;
139
139
  let apiEndpoint = this.options.config.apiEndpoint;
140
+ if (!apiEndpoint) {
141
+ throw new Error('KTDataTable: apiEndpoint is required for remote fetch');
142
+ }
140
143
 
141
144
  if (this.abortController) {
142
145
  this.abortController.abort();
@@ -130,7 +130,7 @@ export class KTDataTableDomTableRenderer<
130
130
  td.innerHTML = value as string;
131
131
 
132
132
  this.applyOriginalTdClass(input, td, rowIndex, colIndex);
133
- this.applyDataRowAttributes(td, dataRowAttributes, colIndex);
133
+ this.applyDataRowAttributes(td, dataRowAttributes ?? null, colIndex);
134
134
 
135
135
  row.appendChild(td);
136
136
  }
@@ -142,39 +142,47 @@ export class KTDataTableDomTableRenderer<
142
142
  item: T,
143
143
  rowIndex: number,
144
144
  ): void {
145
- Object.keys(input.config.columns).forEach(
146
- (key: keyof T, colIndex: number) => {
147
- const td = document.createElement('td');
148
- const columnDef = input.config.columns[key as string];
149
-
150
- this.applyOriginalTdClass(input, td, rowIndex, colIndex);
151
-
152
- if (typeof columnDef.render === 'function') {
153
- const result = columnDef.render.call(
154
- input.context,
155
- item[key],
156
- item,
157
- input.context,
158
- );
159
- if (
160
- result instanceof HTMLElement ||
161
- result instanceof DocumentFragment
162
- ) {
163
- td.appendChild(result);
164
- } else if (typeof result === 'string') {
165
- td.innerHTML = result as string;
166
- }
167
- } else {
168
- td.textContent = item[key] as string;
169
- }
145
+ const columns = input.config.columns;
146
+ if (!columns) {
147
+ return;
148
+ }
149
+
150
+ Object.keys(columns).forEach((key, colIndex) => {
151
+ const columnDef = columns[key];
152
+ if (!columnDef) {
153
+ return;
154
+ }
155
+ const colKey = key as keyof T;
156
+
157
+ const td = document.createElement('td');
158
+
159
+ this.applyOriginalTdClass(input, td, rowIndex, colIndex);
170
160
 
171
- if (typeof columnDef.createdCell === 'function') {
172
- columnDef.createdCell.call(input.context, td, item[key], item, row);
161
+ if (typeof columnDef.render === 'function') {
162
+ const result = columnDef.render.call(
163
+ input.context,
164
+ item[colKey],
165
+ item,
166
+ input.context,
167
+ );
168
+ if (
169
+ result instanceof HTMLElement ||
170
+ result instanceof DocumentFragment
171
+ ) {
172
+ td.appendChild(result);
173
+ } else if (typeof result === 'string') {
174
+ td.innerHTML = result as string;
173
175
  }
176
+ } else {
177
+ td.textContent = item[colKey] as string;
178
+ }
174
179
 
175
- row.appendChild(td);
176
- },
177
- );
180
+ if (typeof columnDef.createdCell === 'function') {
181
+ columnDef.createdCell.call(input.context, td, item[colKey], item, row);
182
+ }
183
+
184
+ row.appendChild(td);
185
+ });
178
186
  }
179
187
 
180
188
  private applyOriginalTdClass(
@@ -194,7 +202,7 @@ export class KTDataTableDomTableRenderer<
194
202
 
195
203
  private applyDataRowAttributes(
196
204
  td: HTMLTableCellElement,
197
- dataRowAttributes: KTDataTableAttributeInterface,
205
+ dataRowAttributes: KTDataTableAttributeInterface | null,
198
206
  colIndex: number,
199
207
  ): void {
200
208
  if (dataRowAttributes && dataRowAttributes[colIndex]) {
@@ -74,9 +74,9 @@ export class KTDataTable<T extends KTDataTableDataInterface>
74
74
  private _originalTdClasses: string[][] = []; // Store original td classes as a 2D array [row][col]
75
75
  private _originalThClasses: string[] = []; // Store original th classes
76
76
 
77
- private _infoElement: HTMLElement;
78
- private _sizeElement: HTMLSelectElement;
79
- private _paginationElement: HTMLElement;
77
+ private _infoElement: HTMLElement | null = null;
78
+ private _sizeElement: HTMLSelectElement | null = null;
79
+ private _paginationElement: HTMLElement | null = null;
80
80
 
81
81
  private _checkbox: KTDataTableCheckboxAPI;
82
82
  private _sortHandler: KTDataTableSortAPI<T>;
@@ -106,6 +106,9 @@ export class KTDataTable<T extends KTDataTableDataInterface>
106
106
  this._defaultConfig = this._initDefaultConfig(config);
107
107
 
108
108
  this._init(element);
109
+ if (!this._element) {
110
+ return;
111
+ }
109
112
  this._buildConfig();
110
113
  this._stateStore = new KTDataTableConfigStateStore(this._config);
111
114
  this._eventAdapter = createDataTableEventAdapter(
@@ -425,43 +428,35 @@ export class KTDataTable<T extends KTDataTableDataInterface>
425
428
  * @returns {void}
426
429
  */
427
430
  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
- */
431
+ const root = this._element;
432
+ const attrs = this._config.attributes;
433
+ if (!root || !attrs?.table) {
434
+ throw new Error('KTDataTable: root element and table selector are required');
435
+ }
436
+
437
+ const tableEl = root.querySelector<HTMLTableElement>(attrs.table);
438
+ if (!tableEl) {
439
+ throw new Error(`KTDataTable: table element not found (${attrs.table})`);
440
+ }
441
+ this._tableElement = tableEl;
442
+
437
443
  this._tbodyElement =
438
444
  this._tableElement.tBodies[0] || this._tableElement.createTBody();
439
- /**
440
- * Table head element
441
- */
442
- this._theadElement = this._tableElement.tHead!;
443
445
 
444
- // Store original classes
446
+ this._theadElement =
447
+ this._tableElement.tHead ?? this._tableElement.createTHead();
448
+
445
449
  this._storeOriginalClasses();
446
450
 
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
- )!;
451
+ this._infoElement = attrs.info
452
+ ? root.querySelector<HTMLElement>(attrs.info)
453
+ : null;
454
+ this._sizeElement = attrs.size
455
+ ? root.querySelector<HTMLSelectElement>(attrs.size)
456
+ : null;
457
+ this._paginationElement = attrs.pagination
458
+ ? root.querySelector<HTMLElement>(attrs.pagination)
459
+ : null;
465
460
  }
466
461
 
467
462
  /**
@@ -540,7 +535,7 @@ export class KTDataTable<T extends KTDataTableDataInterface>
540
535
  * @returns {void}
541
536
  */
542
537
  private _finalize(): void {
543
- this._element.classList.add('datatable-initialized');
538
+ this._element?.classList.add('datatable-initialized');
544
539
 
545
540
  // Initialize checkbox logic
546
541
  this._checkbox.init();
@@ -600,7 +595,7 @@ export class KTDataTable<T extends KTDataTableDataInterface>
600
595
  // Create a new debounced search function
601
596
  const debouncedSearch = this._debounce(() => {
602
597
  this.search(searchElement.value);
603
- }, this._config.search.delay);
598
+ }, this._config.search?.delay ?? 500);
604
599
 
605
600
  // Store the new debounced function as a property of the element
606
601
  searchWithDebounce._debouncedSearch = debouncedSearch;
@@ -647,7 +642,9 @@ export class KTDataTable<T extends KTDataTableDataInterface>
647
642
 
648
643
  private _createUrl(
649
644
  pathOrUrl: string,
650
- baseUrl: string | null = window.location.origin,
645
+ baseUrl: string | null = typeof window !== 'undefined'
646
+ ? window.location.origin
647
+ : null,
651
648
  ): URL {
652
649
  // Regular expression to check if the input is a full URL
653
650
  const isFullUrl = /^[a-zA-Z][a-zA-Z\d+\-.]*:\/\//.test(pathOrUrl);
@@ -661,7 +658,39 @@ export class KTDataTable<T extends KTDataTableDataInterface>
661
658
  ? pathOrUrl
662
659
  : `/${pathOrUrl}`;
663
660
 
664
- return new URL(normalizedPath, baseUrl);
661
+ // Opaque origins (e.g. srcdoc iframes) serialize as the string "null", which is not a valid URL base.
662
+ const bases: string[] = [];
663
+ if (baseUrl && baseUrl !== 'null') {
664
+ bases.push(baseUrl);
665
+ }
666
+ if (typeof window !== 'undefined') {
667
+ const href = window.location.href;
668
+ if (href && !bases.includes(href)) {
669
+ bases.push(href);
670
+ }
671
+ try {
672
+ if (window.parent !== window && window.parent.location?.href) {
673
+ const parentHref = window.parent.location.href;
674
+ if (parentHref && !bases.includes(parentHref)) {
675
+ bases.push(parentHref);
676
+ }
677
+ }
678
+ } catch {
679
+ // parent is cross-origin
680
+ }
681
+ }
682
+
683
+ for (const base of bases) {
684
+ try {
685
+ return new URL(normalizedPath, base);
686
+ } catch {
687
+ // try next base
688
+ }
689
+ }
690
+
691
+ throw new Error(
692
+ `KTDataTable: cannot resolve relative apiEndpoint "${pathOrUrl}" (no valid base URL; use an absolute apiEndpoint).`,
693
+ );
665
694
  }
666
695
 
667
696
  /**
@@ -683,7 +712,7 @@ export class KTDataTable<T extends KTDataTableDataInterface>
683
712
  this._updateTable();
684
713
  }
685
714
 
686
- if (this._infoElement && this._paginationElement) {
715
+ if (this._infoElement || this._sizeElement || this._paginationElement) {
687
716
  this._updatePagination();
688
717
  }
689
718
 
@@ -778,38 +807,49 @@ export class KTDataTable<T extends KTDataTableDataInterface>
778
807
 
779
808
  // Method to show the loading spinner
780
809
  private _showSpinner(): void {
781
- const spinner =
782
- this._element.querySelector<HTMLElement>(
783
- this._config.attributes.spinner,
784
- ) || this._createSpinner();
810
+ const root = this._element;
811
+ const spinnerSel = this._config.attributes?.spinner;
812
+ const fromDom =
813
+ root && spinnerSel
814
+ ? root.querySelector<HTMLElement>(spinnerSel)
815
+ : null;
816
+ const spinner = fromDom ?? this._createSpinner();
785
817
  if (spinner) {
786
818
  spinner.style.display = 'block';
787
819
  }
788
- this._element.classList.add(this._config.loadingClass);
820
+ root?.classList.add(this._config.loadingClass ?? 'loading');
789
821
  }
790
822
 
791
823
  // Method to hide the loading spinner
792
824
  private _hideSpinner(): void {
793
- const spinner = this._element.querySelector<HTMLElement>(
794
- this._config.attributes.spinner,
795
- );
825
+ const root = this._element;
826
+ const spinnerSel = this._config.attributes?.spinner;
827
+ const spinner =
828
+ root && spinnerSel
829
+ ? root.querySelector<HTMLElement>(spinnerSel)
830
+ : null;
796
831
  if (spinner) {
797
832
  spinner.style.display = 'none';
798
833
  }
799
- this._element.classList.remove(this._config.loadingClass);
834
+ root?.classList.remove(this._config.loadingClass ?? 'loading');
800
835
  }
801
836
 
802
837
  // Method to create a spinner element if it doesn't exist
803
- private _createSpinner(): HTMLElement {
804
- if (typeof this._config.loading === 'undefined') {
838
+ private _createSpinner(): HTMLElement | null {
839
+ const loading = this._config.loading;
840
+ if (!loading) {
805
841
  return null;
806
842
  }
807
843
 
808
844
  const template = document.createElement('template');
809
- template.innerHTML = this._config.loading.template
845
+ template.innerHTML = loading.template
810
846
  .trim()
811
- .replace('{content}', this._config.loading.content);
812
- const spinner = template.content.firstChild as HTMLElement;
847
+ .replace('{content}', loading.content);
848
+ const first = template.content.firstChild;
849
+ if (!first || !(first instanceof HTMLElement)) {
850
+ return null;
851
+ }
852
+ const spinner = first;
813
853
  spinner.setAttribute('data-kt-datatable-spinner', 'true');
814
854
 
815
855
  this._tableElement.appendChild(spinner);
@@ -878,18 +918,15 @@ export class KTDataTable<T extends KTDataTableDataInterface>
878
918
  }
879
919
 
880
920
  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;
921
+ const tableIdAttr = this._tableElement?.getAttribute('id');
922
+ if (tableIdAttr) {
923
+ return tableIdAttr;
885
924
  }
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;
925
+ const rootIdAttr = this._element?.getAttribute('id');
926
+ if (rootIdAttr) {
927
+ return rootIdAttr;
890
928
  }
891
-
892
- return id;
929
+ return '';
893
930
  }
894
931
 
895
932
  /**
@@ -897,6 +934,11 @@ export class KTDataTable<T extends KTDataTableDataInterface>
897
934
  * This method is called before re-rendering or when disposing the component.
898
935
  */
899
936
  private _dispose() {
937
+ const root = this._element;
938
+ if (!root) {
939
+ return;
940
+ }
941
+
900
942
  this._cleanupCallbacks.forEach((cleanup) => cleanup());
901
943
  this._cleanupCallbacks = [];
902
944
 
@@ -939,12 +981,12 @@ export class KTDataTable<T extends KTDataTableDataInterface>
939
981
  if (this._checkbox && typeof checkboxWithDispose.dispose === 'function') {
940
982
  checkboxWithDispose.dispose();
941
983
  } 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));
984
+ const checkSel = this._config.attributes?.check;
985
+ if (checkSel) {
986
+ const headerCheckElement = root.querySelector<HTMLInputElement>(checkSel);
987
+ if (headerCheckElement) {
988
+ headerCheckElement.replaceWith(headerCheckElement.cloneNode(true));
989
+ }
948
990
  }
949
991
  }
950
992
  // KTDataTableSortAPI does not have a dispose method, but we can remove th click listeners by replacing them
@@ -956,22 +998,23 @@ export class KTDataTable<T extends KTDataTableDataInterface>
956
998
  }
957
999
 
958
1000
  // --- 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);
1001
+ const spinnerSel = this._config.attributes?.spinner;
1002
+ if (spinnerSel) {
1003
+ const spinner = root.querySelector<HTMLElement>(spinnerSel);
1004
+ if (spinner?.parentNode) {
1005
+ spinner.parentNode.removeChild(spinner);
1006
+ }
964
1007
  }
965
- this._element.classList.remove(this._config.loadingClass);
1008
+ root.classList.remove(this._config.loadingClass ?? 'loading');
966
1009
 
967
1010
  // --- 6. Remove instance reference from the DOM element ---
968
- const elementWithInstance = KTDataTable.asElementWithInstance(
969
- this._element,
970
- );
1011
+ const elementWithInstance = KTDataTable.asElementWithInstance(root);
971
1012
  if (elementWithInstance.instance) {
972
1013
  delete elementWithInstance.instance;
973
1014
  }
974
1015
 
1016
+ KTData.remove(root, this._name);
1017
+
975
1018
  // --- 7. (Optional) Clear localStorage state ---
976
1019
  // Uncomment the following line if you want to clear state on dispose:
977
1020
  // this._deleteState();