@keenthemes/ktui 1.2.3 → 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 (213) hide show
  1. package/dist/ktui.js +2244 -1061
  2. package/dist/ktui.min.js +1 -1
  3. package/dist/ktui.min.js.map +1 -1
  4. package/dist/styles.css +185 -40
  5. package/lib/cjs/components/context-menu/context-menu.d.ts +66 -0
  6. package/lib/cjs/components/context-menu/context-menu.d.ts.map +1 -0
  7. package/lib/cjs/components/context-menu/context-menu.js +423 -0
  8. package/lib/cjs/components/context-menu/context-menu.js.map +1 -0
  9. package/lib/cjs/components/context-menu/index.d.ts +7 -0
  10. package/lib/cjs/components/context-menu/index.d.ts.map +1 -0
  11. package/lib/cjs/components/context-menu/index.js +10 -0
  12. package/lib/cjs/components/context-menu/index.js.map +1 -0
  13. package/lib/cjs/components/context-menu/types.d.ts +30 -0
  14. package/lib/cjs/components/context-menu/types.d.ts.map +1 -0
  15. package/lib/cjs/components/context-menu/types.js +7 -0
  16. package/lib/cjs/components/context-menu/types.js.map +1 -0
  17. package/lib/cjs/components/datatable/datatable-checkbox.d.ts.map +1 -1
  18. package/lib/cjs/components/datatable/datatable-checkbox.js +34 -15
  19. package/lib/cjs/components/datatable/datatable-checkbox.js.map +1 -1
  20. package/lib/cjs/components/datatable/datatable-contracts.d.ts +66 -0
  21. package/lib/cjs/components/datatable/datatable-contracts.d.ts.map +1 -0
  22. package/lib/cjs/components/datatable/datatable-contracts.js +7 -0
  23. package/lib/cjs/components/datatable/datatable-contracts.js.map +1 -0
  24. package/lib/cjs/components/datatable/datatable-event-adapter.d.ts +7 -0
  25. package/lib/cjs/components/datatable/datatable-event-adapter.d.ts.map +1 -0
  26. package/lib/cjs/components/datatable/datatable-event-adapter.js +16 -0
  27. package/lib/cjs/components/datatable/datatable-event-adapter.js.map +1 -0
  28. package/lib/cjs/components/datatable/datatable-local-provider.d.ts +25 -0
  29. package/lib/cjs/components/datatable/datatable-local-provider.d.ts.map +1 -0
  30. package/lib/cjs/components/datatable/datatable-local-provider.js +190 -0
  31. package/lib/cjs/components/datatable/datatable-local-provider.js.map +1 -0
  32. package/lib/cjs/components/datatable/datatable-pagination-renderer.d.ts +15 -0
  33. package/lib/cjs/components/datatable/datatable-pagination-renderer.d.ts.map +1 -0
  34. package/lib/cjs/components/datatable/datatable-pagination-renderer.js +144 -0
  35. package/lib/cjs/components/datatable/datatable-pagination-renderer.js.map +1 -0
  36. package/lib/cjs/components/datatable/datatable-remote-provider.d.ts +25 -0
  37. package/lib/cjs/components/datatable/datatable-remote-provider.d.ts.map +1 -0
  38. package/lib/cjs/components/datatable/datatable-remote-provider.js +191 -0
  39. package/lib/cjs/components/datatable/datatable-remote-provider.js.map +1 -0
  40. package/lib/cjs/components/datatable/datatable-state-store.d.ts +21 -0
  41. package/lib/cjs/components/datatable/datatable-state-store.d.ts.map +1 -0
  42. package/lib/cjs/components/datatable/datatable-state-store.js +81 -0
  43. package/lib/cjs/components/datatable/datatable-state-store.js.map +1 -0
  44. package/lib/cjs/components/datatable/datatable-table-renderer.d.ts +16 -0
  45. package/lib/cjs/components/datatable/datatable-table-renderer.d.ts.map +1 -0
  46. package/lib/cjs/components/datatable/datatable-table-renderer.js +141 -0
  47. package/lib/cjs/components/datatable/datatable-table-renderer.js.map +1 -0
  48. package/lib/cjs/components/datatable/datatable.d.ts +9 -87
  49. package/lib/cjs/components/datatable/datatable.d.ts.map +1 -1
  50. package/lib/cjs/components/datatable/datatable.js +234 -740
  51. package/lib/cjs/components/datatable/datatable.js.map +1 -1
  52. package/lib/cjs/components/dropdown/dropdown.d.ts +2 -2
  53. package/lib/cjs/components/dropdown/dropdown.d.ts.map +1 -1
  54. package/lib/cjs/components/dropdown/dropdown.js +68 -31
  55. package/lib/cjs/components/dropdown/dropdown.js.map +1 -1
  56. package/lib/cjs/components/input-number/index.d.ts +7 -0
  57. package/lib/cjs/components/input-number/index.d.ts.map +1 -0
  58. package/lib/cjs/components/input-number/index.js +10 -0
  59. package/lib/cjs/components/input-number/index.js.map +1 -0
  60. package/lib/cjs/components/input-number/input-number.d.ts +40 -0
  61. package/lib/cjs/components/input-number/input-number.d.ts.map +1 -0
  62. package/lib/cjs/components/input-number/input-number.js +248 -0
  63. package/lib/cjs/components/input-number/input-number.js.map +1 -0
  64. package/lib/cjs/components/input-number/types.d.ts +30 -0
  65. package/lib/cjs/components/input-number/types.d.ts.map +1 -0
  66. package/lib/cjs/components/input-number/types.js +7 -0
  67. package/lib/cjs/components/input-number/types.js.map +1 -0
  68. package/lib/cjs/components/select/config.d.ts +1 -0
  69. package/lib/cjs/components/select/config.d.ts.map +1 -1
  70. package/lib/cjs/components/select/config.js +2 -1
  71. package/lib/cjs/components/select/config.js.map +1 -1
  72. package/lib/cjs/components/select/index.d.ts +1 -1
  73. package/lib/cjs/components/select/index.d.ts.map +1 -1
  74. package/lib/cjs/components/select/select.d.ts +8 -1
  75. package/lib/cjs/components/select/select.d.ts.map +1 -1
  76. package/lib/cjs/components/select/select.js +14 -1
  77. package/lib/cjs/components/select/select.js.map +1 -1
  78. package/lib/cjs/components/select/tags.d.ts.map +1 -1
  79. package/lib/cjs/components/select/tags.js +10 -0
  80. package/lib/cjs/components/select/tags.js.map +1 -1
  81. package/lib/cjs/index.d.ts +9 -1
  82. package/lib/cjs/index.d.ts.map +1 -1
  83. package/lib/cjs/index.js +11 -7
  84. package/lib/cjs/index.js.map +1 -1
  85. package/lib/cjs/init-all.d.ts +6 -0
  86. package/lib/cjs/init-all.d.ts.map +1 -0
  87. package/lib/cjs/init-all.js +17 -0
  88. package/lib/cjs/init-all.js.map +1 -0
  89. package/lib/cjs/legacy.d.ts +8 -0
  90. package/lib/cjs/legacy.d.ts.map +1 -0
  91. package/lib/cjs/legacy.js +26 -0
  92. package/lib/cjs/legacy.js.map +1 -0
  93. package/lib/esm/components/context-menu/context-menu.d.ts +66 -0
  94. package/lib/esm/components/context-menu/context-menu.d.ts.map +1 -0
  95. package/lib/esm/components/context-menu/context-menu.js +420 -0
  96. package/lib/esm/components/context-menu/context-menu.js.map +1 -0
  97. package/lib/esm/components/context-menu/index.d.ts +7 -0
  98. package/lib/esm/components/context-menu/index.d.ts.map +1 -0
  99. package/lib/esm/components/context-menu/index.js +6 -0
  100. package/lib/esm/components/context-menu/index.js.map +1 -0
  101. package/lib/esm/components/context-menu/types.d.ts +30 -0
  102. package/lib/esm/components/context-menu/types.d.ts.map +1 -0
  103. package/lib/esm/components/context-menu/types.js +6 -0
  104. package/lib/esm/components/context-menu/types.js.map +1 -0
  105. package/lib/esm/components/datatable/datatable-checkbox.d.ts.map +1 -1
  106. package/lib/esm/components/datatable/datatable-checkbox.js +34 -15
  107. package/lib/esm/components/datatable/datatable-checkbox.js.map +1 -1
  108. package/lib/esm/components/datatable/datatable-contracts.d.ts +66 -0
  109. package/lib/esm/components/datatable/datatable-contracts.d.ts.map +1 -0
  110. package/lib/esm/components/datatable/datatable-contracts.js +6 -0
  111. package/lib/esm/components/datatable/datatable-contracts.js.map +1 -0
  112. package/lib/esm/components/datatable/datatable-event-adapter.d.ts +7 -0
  113. package/lib/esm/components/datatable/datatable-event-adapter.d.ts.map +1 -0
  114. package/lib/esm/components/datatable/datatable-event-adapter.js +13 -0
  115. package/lib/esm/components/datatable/datatable-event-adapter.js.map +1 -0
  116. package/lib/esm/components/datatable/datatable-local-provider.d.ts +25 -0
  117. package/lib/esm/components/datatable/datatable-local-provider.d.ts.map +1 -0
  118. package/lib/esm/components/datatable/datatable-local-provider.js +187 -0
  119. package/lib/esm/components/datatable/datatable-local-provider.js.map +1 -0
  120. package/lib/esm/components/datatable/datatable-pagination-renderer.d.ts +15 -0
  121. package/lib/esm/components/datatable/datatable-pagination-renderer.d.ts.map +1 -0
  122. package/lib/esm/components/datatable/datatable-pagination-renderer.js +141 -0
  123. package/lib/esm/components/datatable/datatable-pagination-renderer.js.map +1 -0
  124. package/lib/esm/components/datatable/datatable-remote-provider.d.ts +25 -0
  125. package/lib/esm/components/datatable/datatable-remote-provider.d.ts.map +1 -0
  126. package/lib/esm/components/datatable/datatable-remote-provider.js +188 -0
  127. package/lib/esm/components/datatable/datatable-remote-provider.js.map +1 -0
  128. package/lib/esm/components/datatable/datatable-state-store.d.ts +21 -0
  129. package/lib/esm/components/datatable/datatable-state-store.d.ts.map +1 -0
  130. package/lib/esm/components/datatable/datatable-state-store.js +78 -0
  131. package/lib/esm/components/datatable/datatable-state-store.js.map +1 -0
  132. package/lib/esm/components/datatable/datatable-table-renderer.d.ts +16 -0
  133. package/lib/esm/components/datatable/datatable-table-renderer.d.ts.map +1 -0
  134. package/lib/esm/components/datatable/datatable-table-renderer.js +138 -0
  135. package/lib/esm/components/datatable/datatable-table-renderer.js.map +1 -0
  136. package/lib/esm/components/datatable/datatable.d.ts +9 -87
  137. package/lib/esm/components/datatable/datatable.d.ts.map +1 -1
  138. package/lib/esm/components/datatable/datatable.js +234 -740
  139. package/lib/esm/components/datatable/datatable.js.map +1 -1
  140. package/lib/esm/components/dropdown/dropdown.d.ts +2 -2
  141. package/lib/esm/components/dropdown/dropdown.d.ts.map +1 -1
  142. package/lib/esm/components/dropdown/dropdown.js +68 -31
  143. package/lib/esm/components/dropdown/dropdown.js.map +1 -1
  144. package/lib/esm/components/input-number/index.d.ts +7 -0
  145. package/lib/esm/components/input-number/index.d.ts.map +1 -0
  146. package/lib/esm/components/input-number/index.js +6 -0
  147. package/lib/esm/components/input-number/index.js.map +1 -0
  148. package/lib/esm/components/input-number/input-number.d.ts +40 -0
  149. package/lib/esm/components/input-number/input-number.d.ts.map +1 -0
  150. package/lib/esm/components/input-number/input-number.js +245 -0
  151. package/lib/esm/components/input-number/input-number.js.map +1 -0
  152. package/lib/esm/components/input-number/types.d.ts +30 -0
  153. package/lib/esm/components/input-number/types.d.ts.map +1 -0
  154. package/lib/esm/components/input-number/types.js +6 -0
  155. package/lib/esm/components/input-number/types.js.map +1 -0
  156. package/lib/esm/components/select/config.d.ts +1 -0
  157. package/lib/esm/components/select/config.d.ts.map +1 -1
  158. package/lib/esm/components/select/config.js +2 -1
  159. package/lib/esm/components/select/config.js.map +1 -1
  160. package/lib/esm/components/select/index.d.ts +1 -1
  161. package/lib/esm/components/select/index.d.ts.map +1 -1
  162. package/lib/esm/components/select/select.d.ts +8 -1
  163. package/lib/esm/components/select/select.d.ts.map +1 -1
  164. package/lib/esm/components/select/select.js +14 -1
  165. package/lib/esm/components/select/select.js.map +1 -1
  166. package/lib/esm/components/select/tags.d.ts.map +1 -1
  167. package/lib/esm/components/select/tags.js +11 -1
  168. package/lib/esm/components/select/tags.js.map +1 -1
  169. package/lib/esm/index.d.ts +9 -1
  170. package/lib/esm/index.d.ts.map +1 -1
  171. package/lib/esm/index.js +7 -5
  172. package/lib/esm/index.js.map +1 -1
  173. package/lib/esm/init-all.d.ts +6 -0
  174. package/lib/esm/init-all.d.ts.map +1 -0
  175. package/lib/esm/init-all.js +13 -0
  176. package/lib/esm/init-all.js.map +1 -0
  177. package/lib/esm/legacy.d.ts +8 -0
  178. package/lib/esm/legacy.d.ts.map +1 -0
  179. package/lib/esm/legacy.js +8 -0
  180. package/lib/esm/legacy.js.map +1 -0
  181. package/package.json +35 -11
  182. package/src/__tests__/entrypoints.test.ts +71 -0
  183. package/src/components/context-menu/__tests__/context-menu.test.ts +117 -0
  184. package/src/components/context-menu/context-menu.css +32 -0
  185. package/src/components/context-menu/context-menu.ts +529 -0
  186. package/src/components/context-menu/index.ts +10 -0
  187. package/src/components/context-menu/types.ts +32 -0
  188. package/src/components/datatable/__tests__/architecture-boundaries.test.ts +259 -0
  189. package/src/components/datatable/datatable-checkbox.ts +34 -23
  190. package/src/components/datatable/datatable-contracts.ts +96 -0
  191. package/src/components/datatable/datatable-event-adapter.ts +21 -0
  192. package/src/components/datatable/datatable-local-provider.ts +193 -0
  193. package/src/components/datatable/datatable-pagination-renderer.ts +225 -0
  194. package/src/components/datatable/datatable-remote-provider.ts +178 -0
  195. package/src/components/datatable/datatable-state-store.ts +94 -0
  196. package/src/components/datatable/datatable-table-renderer.ts +214 -0
  197. package/src/components/datatable/datatable.ts +250 -918
  198. package/src/components/dropdown/dropdown.ts +86 -58
  199. package/src/components/input/input-group.css +14 -1
  200. package/src/components/input-number/__tests__/input-number.test.ts +278 -0
  201. package/src/components/input-number/index.ts +11 -0
  202. package/src/components/input-number/input-number.ts +267 -0
  203. package/src/components/input-number/types.ts +32 -0
  204. package/src/components/select/__tests__/ux-behaviors.test.ts +72 -0
  205. package/src/components/select/config.ts +3 -1
  206. package/src/components/select/index.ts +1 -1
  207. package/src/components/select/select.css +23 -20
  208. package/src/components/select/select.ts +15 -1
  209. package/src/components/select/tags.ts +14 -1
  210. package/src/index.ts +18 -5
  211. package/src/init-all.ts +15 -0
  212. package/src/legacy.ts +9 -0
  213. package/styles.css +1 -0
@@ -0,0 +1,225 @@
1
+ /**
2
+ * KTUI - Free & Open-Source Tailwind UI Components by Keenthemes
3
+ * Copyright 2025 by Keenthemes Inc
4
+ */
5
+
6
+ import {
7
+ KTDataTableCleanup,
8
+ KTDataTablePaginationRenderer,
9
+ KTDataTablePaginationRendererInput,
10
+ } from './datatable-contracts';
11
+
12
+ export class KTDataTableDomPaginationRenderer implements KTDataTablePaginationRenderer {
13
+ public render(
14
+ input: KTDataTablePaginationRendererInput,
15
+ ): KTDataTableCleanup | void {
16
+ if (input.sizeElement) {
17
+ this.removeChildElements(input.sizeElement);
18
+ this.createPageSizeControls(input);
19
+ }
20
+
21
+ if (input.paginationElement) {
22
+ this.removeChildElements(input.paginationElement);
23
+ this.createPaginationControls(input);
24
+ }
25
+
26
+ return () => {
27
+ if (input.sizeElement) {
28
+ input.sizeElement.onchange = null;
29
+ }
30
+ if (input.paginationElement) {
31
+ this.removeChildElements(input.paginationElement);
32
+ }
33
+ };
34
+ }
35
+
36
+ private removeChildElements(container?: HTMLElement | null): void {
37
+ if (!container) {
38
+ return;
39
+ }
40
+
41
+ while (container.firstChild) {
42
+ container.removeChild(container.firstChild);
43
+ }
44
+ }
45
+
46
+ private createPageSizeControls(
47
+ input: KTDataTablePaginationRendererInput,
48
+ ): void {
49
+ if (!input.sizeElement) {
50
+ return;
51
+ }
52
+
53
+ const pageSizes = input.config.pageSizes ?? [5, 10, 20, 30, 50];
54
+
55
+ setTimeout(() => {
56
+ const options = pageSizes.map((size: number) => {
57
+ const option = document.createElement('option') as HTMLOptionElement;
58
+ option.value = String(size);
59
+ option.text = String(size);
60
+ option.selected = input.state.pageSize === size;
61
+ return option;
62
+ });
63
+
64
+ input.sizeElement.append(...options);
65
+ }, 100);
66
+
67
+ input.sizeElement.onchange = (event: Event) => {
68
+ input.reloadPageSize(
69
+ Number((event.target as HTMLSelectElement).value),
70
+ 1,
71
+ );
72
+ };
73
+ }
74
+
75
+ private createPaginationControls(
76
+ input: KTDataTablePaginationRendererInput,
77
+ ): HTMLElement | null {
78
+ if (!input.paginationElement || input.dataLength === 0) {
79
+ return null;
80
+ }
81
+
82
+ this.setPaginationInfoText(input);
83
+ this.createPaginationButtons(input.paginationElement, input);
84
+
85
+ return input.paginationElement;
86
+ }
87
+
88
+ private setPaginationInfoText(
89
+ input: KTDataTablePaginationRendererInput,
90
+ ): void {
91
+ if (!input.infoElement) {
92
+ return;
93
+ }
94
+
95
+ const infoTemplate =
96
+ input.config.info ?? '{start}-{end} of {total}';
97
+ input.infoElement.textContent = infoTemplate
98
+ .replace(
99
+ '{start}',
100
+ (input.state.page - 1) * input.state.pageSize + 1 + '',
101
+ )
102
+ .replace(
103
+ '{end}',
104
+ Math.min(
105
+ input.state.page * input.state.pageSize,
106
+ input.state.totalItems,
107
+ ) + '',
108
+ )
109
+ .replace('{total}', input.state.totalItems + '');
110
+ }
111
+
112
+ private createPaginationButtons(
113
+ paginationContainer: HTMLElement,
114
+ input: KTDataTablePaginationRendererInput,
115
+ ): void {
116
+ const pagination = input.config.pagination;
117
+ if (!pagination) {
118
+ return;
119
+ }
120
+
121
+ const { page: currentPage, totalPages } = input.state;
122
+ const { previous, next, number, more } = pagination;
123
+ const pageMoreLimit = input.config.pageMoreLimit ?? 3;
124
+
125
+ const createButton = (
126
+ text: string,
127
+ className: string,
128
+ disabled: boolean,
129
+ handleClick: () => void,
130
+ ): HTMLButtonElement => {
131
+ const button = document.createElement('button') as HTMLButtonElement;
132
+ button.className = className;
133
+ button.innerHTML = text;
134
+ button.disabled = disabled;
135
+ button.onclick = handleClick;
136
+ return button;
137
+ };
138
+
139
+ paginationContainer.appendChild(
140
+ createButton(
141
+ previous.text,
142
+ `${previous.class}${currentPage === 1 ? ' disabled' : ''}`,
143
+ currentPage === 1,
144
+ () => input.paginateData(currentPage - 1),
145
+ ),
146
+ );
147
+
148
+ if (input.config.pageMore) {
149
+ const range = this.calculatePageRange(
150
+ currentPage,
151
+ totalPages,
152
+ pageMoreLimit,
153
+ );
154
+
155
+ if (range.start > 1) {
156
+ paginationContainer.appendChild(
157
+ createButton(more.text, more.class, false, () =>
158
+ input.paginateData(Math.max(1, range.start - 1)),
159
+ ),
160
+ );
161
+ }
162
+
163
+ for (let i = range.start; i <= range.end; i++) {
164
+ paginationContainer.appendChild(
165
+ createButton(
166
+ number.text.replace('{page}', i.toString()),
167
+ `${number.class}${currentPage === i ? ' active disabled' : ''}`,
168
+ currentPage === i,
169
+ () => input.paginateData(i),
170
+ ),
171
+ );
172
+ }
173
+
174
+ if (range.end < totalPages) {
175
+ paginationContainer.appendChild(
176
+ createButton(more.text, more.class, false, () =>
177
+ input.paginateData(Math.min(totalPages, range.end + 1)),
178
+ ),
179
+ );
180
+ }
181
+ } else {
182
+ for (let i = 1; i <= totalPages; i++) {
183
+ paginationContainer.appendChild(
184
+ createButton(
185
+ number.text.replace('{page}', i.toString()),
186
+ `${number.class}${currentPage === i ? ' active disabled' : ''}`,
187
+ currentPage === i,
188
+ () => input.paginateData(i),
189
+ ),
190
+ );
191
+ }
192
+ }
193
+
194
+ paginationContainer.appendChild(
195
+ createButton(
196
+ next.text,
197
+ `${next.class}${currentPage === totalPages ? ' disabled' : ''}`,
198
+ currentPage === totalPages,
199
+ () => input.paginateData(currentPage + 1),
200
+ ),
201
+ );
202
+ }
203
+
204
+ private calculatePageRange(
205
+ currentPage: number,
206
+ totalPages: number,
207
+ maxButtons: number,
208
+ ): { start: number; end: number } {
209
+ let startPage: number, endPage: number;
210
+ const halfMaxButtons = Math.floor(maxButtons / 2);
211
+
212
+ if (totalPages <= maxButtons) {
213
+ startPage = 1;
214
+ endPage = totalPages;
215
+ } else {
216
+ startPage = Math.max(currentPage - halfMaxButtons, 1);
217
+ endPage = Math.min(startPage + maxButtons - 1, totalPages);
218
+ if (endPage - startPage < maxButtons - 1) {
219
+ startPage = Math.max(endPage - maxButtons + 1, 1);
220
+ }
221
+ }
222
+
223
+ return { start: startPage, end: endPage };
224
+ }
225
+ }
@@ -0,0 +1,178 @@
1
+ /**
2
+ * KTUI - Free & Open-Source Tailwind UI Components by Keenthemes
3
+ * Copyright 2025 by Keenthemes Inc
4
+ */
5
+
6
+ import {
7
+ KTDataTableColumnFilterInterface,
8
+ KTDataTableConfigInterface,
9
+ KTDataTableDataInterface,
10
+ } from './types';
11
+ import {
12
+ KTDataTableDataProvider,
13
+ KTDataTableEventAdapter,
14
+ KTDataTableProviderResult,
15
+ KTDataTableStateStore,
16
+ } from './datatable-contracts';
17
+
18
+ interface KTDataTableRemoteProviderOptions {
19
+ config: KTDataTableConfigInterface;
20
+ createUrl: (pathOrUrl: string) => URL;
21
+ eventAdapter: KTDataTableEventAdapter;
22
+ noticeOnTable: (message?: string) => void;
23
+ stateStore: KTDataTableStateStore;
24
+ }
25
+
26
+ export class KTDataTableRemoteDataProvider<
27
+ T extends KTDataTableDataInterface,
28
+ > implements KTDataTableDataProvider<T> {
29
+ private abortController: AbortController | null = null;
30
+ private requestId = 0;
31
+
32
+ constructor(private readonly options: KTDataTableRemoteProviderOptions) {}
33
+
34
+ public dispose(): void {
35
+ if (this.abortController) {
36
+ this.abortController.abort();
37
+ this.abortController = null;
38
+ }
39
+ }
40
+
41
+ public async fetch(): Promise<KTDataTableProviderResult<T>> {
42
+ const currentRequestId = ++this.requestId;
43
+ const queryParams = this.getQueryParamsForFetchRequest();
44
+
45
+ let response: Response;
46
+ try {
47
+ response = await this.performFetchRequest(queryParams);
48
+ } catch (error) {
49
+ if ((error as Error).name === 'AbortError') {
50
+ return { data: [], totalItems: 0, skipped: true };
51
+ }
52
+ throw error;
53
+ }
54
+
55
+ if (currentRequestId !== this.requestId) {
56
+ return { data: [], totalItems: 0, skipped: true };
57
+ }
58
+
59
+ let responseData = null;
60
+
61
+ try {
62
+ responseData = await response.json();
63
+ } catch (error) {
64
+ this.options.eventAdapter.emit('parseError', {
65
+ response,
66
+ error: String(error),
67
+ status: response.status,
68
+ statusText: response.statusText,
69
+ });
70
+ return { data: [], totalItems: 0, skipped: true };
71
+ }
72
+
73
+ if (currentRequestId !== this.requestId) {
74
+ return { data: [], totalItems: 0, skipped: true };
75
+ }
76
+
77
+ this.options.eventAdapter.emit('fetched', { response: responseData });
78
+
79
+ if (typeof this.options.config.mapResponse === 'function') {
80
+ responseData = this.options.config.mapResponse.call(this, responseData);
81
+ }
82
+
83
+ return {
84
+ data: responseData.data as T[],
85
+ totalItems: responseData.totalCount,
86
+ response: responseData,
87
+ };
88
+ }
89
+
90
+ private getQueryParamsForFetchRequest(): URLSearchParams {
91
+ const { page, pageSize, sortField, sortOrder, filters, search } =
92
+ this.options.stateStore.getState();
93
+
94
+ let queryParams = new URLSearchParams();
95
+ queryParams.set('page', String(page));
96
+ queryParams.set('size', String(pageSize));
97
+
98
+ if (sortOrder !== undefined) {
99
+ queryParams.set('sortOrder', String(sortOrder));
100
+ }
101
+
102
+ if (sortField !== undefined) {
103
+ queryParams.set('sortField', String(sortField));
104
+ }
105
+
106
+ if (Array.isArray(filters) && filters.length) {
107
+ queryParams.set(
108
+ 'filters',
109
+ JSON.stringify(
110
+ filters.map((filter: KTDataTableColumnFilterInterface) => ({
111
+ column: filter.column,
112
+ type: filter.type,
113
+ value: filter.value,
114
+ })),
115
+ ),
116
+ );
117
+ }
118
+
119
+ if (search) {
120
+ queryParams.set(
121
+ 'search',
122
+ typeof search === 'object' ? JSON.stringify(search) : search,
123
+ );
124
+ }
125
+
126
+ if (typeof this.options.config.mapRequest === 'function') {
127
+ queryParams = this.options.config.mapRequest.call(this, queryParams);
128
+ }
129
+
130
+ return queryParams;
131
+ }
132
+
133
+ private async performFetchRequest(
134
+ queryParams: URLSearchParams,
135
+ ): Promise<Response> {
136
+ const requestMethod: RequestInit['method'] =
137
+ this.options.config.requestMethod;
138
+ let requestBody: RequestInit['body'] | undefined = undefined;
139
+ let apiEndpoint = this.options.config.apiEndpoint;
140
+ if (!apiEndpoint) {
141
+ throw new Error('KTDataTable: apiEndpoint is required for remote fetch');
142
+ }
143
+
144
+ if (this.abortController) {
145
+ this.abortController.abort();
146
+ }
147
+
148
+ this.abortController = new AbortController();
149
+
150
+ if (requestMethod === 'POST') {
151
+ requestBody = queryParams;
152
+ } else if (requestMethod === 'GET') {
153
+ const apiEndpointWithQueryParams = this.options.createUrl(apiEndpoint);
154
+ apiEndpointWithQueryParams.search = queryParams.toString();
155
+ apiEndpoint = apiEndpointWithQueryParams.toString();
156
+ }
157
+
158
+ return fetch(apiEndpoint, {
159
+ method: requestMethod,
160
+ body: requestBody,
161
+ headers: this.options.config.requestHeaders,
162
+ ...(this.options.config.requestCredentials && {
163
+ credentials: this.options.config.requestCredentials,
164
+ }),
165
+ ...(this.abortController && { signal: this.abortController.signal }),
166
+ }).catch((error) => {
167
+ if (error.name === 'AbortError') {
168
+ return Promise.reject(error);
169
+ }
170
+
171
+ this.options.eventAdapter.emit('error', { error });
172
+ this.options.noticeOnTable(
173
+ 'Error performing fetch request: ' + String(error),
174
+ );
175
+ throw error;
176
+ });
177
+ }
178
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * KTUI - Free & Open-Source Tailwind UI Components by Keenthemes
3
+ * Copyright 2025 by Keenthemes Inc
4
+ */
5
+
6
+ import {
7
+ KTDataTableAttributeInterface,
8
+ KTDataTableColumnFilterInterface,
9
+ KTDataTableConfigInterface,
10
+ KTDataTableDataInterface,
11
+ KTDataTableSortOrderInterface,
12
+ KTDataTableStateInterface,
13
+ } from './types';
14
+ import { KTDataTableStateStore } from './datatable-contracts';
15
+
16
+ export class KTDataTableConfigStateStore implements KTDataTableStateStore {
17
+ constructor(private readonly config: KTDataTableConfigInterface) {
18
+ this.ensureState();
19
+ }
20
+
21
+ public getState(): KTDataTableStateInterface {
22
+ this.ensureState();
23
+ return {
24
+ page: 1,
25
+ sortField: null,
26
+ sortOrder: '',
27
+ pageSize: this.config.pageSize,
28
+ filters: [],
29
+ ...this.config._state,
30
+ } as KTDataTableStateInterface;
31
+ }
32
+
33
+ public replaceState(state: KTDataTableStateInterface): void {
34
+ this.config._state = state;
35
+ this.ensureState();
36
+ }
37
+
38
+ public patchState(state: Partial<KTDataTableStateInterface>): void {
39
+ this.ensureState();
40
+ this.config._state = {
41
+ ...this.config._state,
42
+ ...state,
43
+ } as KTDataTableStateInterface;
44
+ }
45
+
46
+ public setPage(page: number): void {
47
+ this.patchState({ page });
48
+ }
49
+
50
+ public setPageSize(pageSize: number, page: number = 1): void {
51
+ this.patchState({ pageSize, page });
52
+ }
53
+
54
+ public setSort(
55
+ field: keyof KTDataTableDataInterface | number,
56
+ order: KTDataTableSortOrderInterface,
57
+ ): void {
58
+ this.patchState({
59
+ sortField: field,
60
+ sortOrder: order,
61
+ } as Partial<KTDataTableStateInterface>);
62
+ }
63
+
64
+ public setSearch(search: string | object): void {
65
+ this.patchState({ search, page: 1 });
66
+ }
67
+
68
+ public setFilter(filter: KTDataTableColumnFilterInterface): void {
69
+ const filters = [
70
+ ...(this.getState().filters || []).filter(
71
+ (currentFilter) => currentFilter.column !== filter.column,
72
+ ),
73
+ filter,
74
+ ];
75
+
76
+ this.patchState({ filters, page: 1 });
77
+ }
78
+
79
+ public setOriginalData(
80
+ originalData: KTDataTableDataInterface[],
81
+ originalDataAttributes: KTDataTableAttributeInterface[],
82
+ ): void {
83
+ this.patchState({
84
+ originalData,
85
+ originalDataAttributes,
86
+ } as Partial<KTDataTableStateInterface>);
87
+ }
88
+
89
+ private ensureState(): void {
90
+ if (!this.config._state) {
91
+ this.config._state = {} as KTDataTableStateInterface;
92
+ }
93
+ }
94
+ }
@@ -0,0 +1,214 @@
1
+ /**
2
+ * KTUI - Free & Open-Source Tailwind UI Components by Keenthemes
3
+ * Copyright 2025 by Keenthemes Inc
4
+ */
5
+
6
+ import { KTOptionType } from '../../types';
7
+ import {
8
+ KTDataTableDataInterface,
9
+ KTDataTableAttributeInterface,
10
+ } from './types';
11
+ import {
12
+ KTDataTableTableRenderer,
13
+ KTDataTableTableRendererInput,
14
+ } from './datatable-contracts';
15
+
16
+ export class KTDataTableDomTableRenderer<
17
+ T extends KTDataTableDataInterface,
18
+ > implements KTDataTableTableRenderer<T> {
19
+ public render(
20
+ input: KTDataTableTableRendererInput<T>,
21
+ ): HTMLTableSectionElement {
22
+ while (input.tableElement.tBodies.length) {
23
+ input.tableElement.removeChild(input.tableElement.tBodies[0]);
24
+ }
25
+
26
+ const tbodyElement =
27
+ input.tableElement.createTBody() as HTMLTableSectionElement;
28
+
29
+ if (input.originalTbodyClass) {
30
+ tbodyElement.className = input.originalTbodyClass;
31
+ }
32
+
33
+ this.renderContent(input, tbodyElement);
34
+
35
+ return tbodyElement;
36
+ }
37
+
38
+ public notice(
39
+ tableElement: HTMLTableElement,
40
+ getLogicalColumnCount: () => number,
41
+ message: string = '',
42
+ ): void {
43
+ const row = tableElement.tBodies[0].insertRow();
44
+ const cell = row.insertCell();
45
+ const logicalCount = getLogicalColumnCount();
46
+ cell.colSpan = logicalCount > 0 ? logicalCount : 1;
47
+ cell.innerHTML = message;
48
+ }
49
+
50
+ private renderContent(
51
+ input: KTDataTableTableRendererInput<T>,
52
+ tbodyElement: HTMLTableSectionElement,
53
+ ): HTMLTableSectionElement {
54
+ const fragment = document.createDocumentFragment();
55
+
56
+ tbodyElement.textContent = '';
57
+
58
+ if (input.data.length === 0) {
59
+ this.notice(
60
+ input.tableElement,
61
+ input.getLogicalColumnCount,
62
+ input.config.infoEmpty || '',
63
+ );
64
+ return tbodyElement;
65
+ }
66
+
67
+ const allThs: NodeListOf<HTMLTableCellElement> = input.theadElement
68
+ ? input.theadElement.querySelectorAll('th')
69
+ : ([] as unknown as NodeListOf<HTMLTableCellElement>);
70
+
71
+ const ths: HTMLTableCellElement[] = Array.from(allThs).filter((th) =>
72
+ th.hasAttribute('data-kt-datatable-column'),
73
+ );
74
+ const columnsToRender: HTMLTableCellElement[] =
75
+ ths.length > 0 && ths.length !== allThs.length ? Array.from(allThs) : ths;
76
+ const logicalColumnCount =
77
+ columnsToRender.length > 0
78
+ ? columnsToRender.length
79
+ : input.getLogicalColumnCount();
80
+
81
+ input.data.forEach((item: T, rowIndex: number) => {
82
+ const row = document.createElement('tr');
83
+
84
+ if (input.originalTrClasses && input.originalTrClasses[rowIndex]) {
85
+ row.className = input.originalTrClasses[rowIndex];
86
+ }
87
+
88
+ if (!input.config.columns) {
89
+ this.renderImplicitColumns(input, row, item, rowIndex, {
90
+ columnsToRender,
91
+ logicalColumnCount,
92
+ });
93
+ } else {
94
+ this.renderConfiguredColumns(input, row, item, rowIndex);
95
+ }
96
+
97
+ fragment.appendChild(row);
98
+ });
99
+
100
+ tbodyElement.appendChild(fragment);
101
+ return tbodyElement;
102
+ }
103
+
104
+ private renderImplicitColumns(
105
+ input: KTDataTableTableRendererInput<T>,
106
+ row: HTMLTableRowElement,
107
+ item: T,
108
+ rowIndex: number,
109
+ options: {
110
+ columnsToRender: HTMLTableCellElement[];
111
+ logicalColumnCount: number;
112
+ },
113
+ ): void {
114
+ const dataRowAttributes = input.getState().originalDataAttributes
115
+ ? input.getState().originalDataAttributes[rowIndex]
116
+ : null;
117
+
118
+ for (let colIndex = 0; colIndex < options.logicalColumnCount; colIndex++) {
119
+ const th = options.columnsToRender[colIndex];
120
+ const colName = th?.getAttribute('data-kt-datatable-column');
121
+ const td = document.createElement('td');
122
+ let value: KTOptionType | '';
123
+ if (colName && Object.prototype.hasOwnProperty.call(item, colName)) {
124
+ value = item[colName as keyof T];
125
+ } else if (Object.prototype.hasOwnProperty.call(item, colIndex)) {
126
+ value = item[colIndex as keyof T];
127
+ } else {
128
+ value = '';
129
+ }
130
+ td.innerHTML = value as string;
131
+
132
+ this.applyOriginalTdClass(input, td, rowIndex, colIndex);
133
+ this.applyDataRowAttributes(td, dataRowAttributes ?? null, colIndex);
134
+
135
+ row.appendChild(td);
136
+ }
137
+ }
138
+
139
+ private renderConfiguredColumns(
140
+ input: KTDataTableTableRendererInput<T>,
141
+ row: HTMLTableRowElement,
142
+ item: T,
143
+ rowIndex: number,
144
+ ): void {
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);
160
+
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;
175
+ }
176
+ } else {
177
+ td.textContent = item[colKey] as string;
178
+ }
179
+
180
+ if (typeof columnDef.createdCell === 'function') {
181
+ columnDef.createdCell.call(input.context, td, item[colKey], item, row);
182
+ }
183
+
184
+ row.appendChild(td);
185
+ });
186
+ }
187
+
188
+ private applyOriginalTdClass(
189
+ input: KTDataTableTableRendererInput<T>,
190
+ td: HTMLTableCellElement,
191
+ rowIndex: number,
192
+ colIndex: number,
193
+ ): void {
194
+ if (
195
+ input.originalTdClasses &&
196
+ input.originalTdClasses[rowIndex] &&
197
+ input.originalTdClasses[rowIndex][colIndex]
198
+ ) {
199
+ td.className = input.originalTdClasses[rowIndex][colIndex];
200
+ }
201
+ }
202
+
203
+ private applyDataRowAttributes(
204
+ td: HTMLTableCellElement,
205
+ dataRowAttributes: KTDataTableAttributeInterface | null,
206
+ colIndex: number,
207
+ ): void {
208
+ if (dataRowAttributes && dataRowAttributes[colIndex]) {
209
+ for (const attr in dataRowAttributes[colIndex]) {
210
+ td.setAttribute(attr, dataRowAttributes[colIndex][attr]);
211
+ }
212
+ }
213
+ }
214
+ }