@keenthemes/ktui 1.2.3 → 1.2.4

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 (141) hide show
  1. package/dist/ktui.js +1681 -957
  2. package/dist/ktui.min.js +1 -1
  3. package/dist/ktui.min.js.map +1 -1
  4. package/dist/styles.css +110 -1
  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-contracts.d.ts +66 -0
  18. package/lib/cjs/components/datatable/datatable-contracts.d.ts.map +1 -0
  19. package/lib/cjs/components/datatable/datatable-contracts.js +7 -0
  20. package/lib/cjs/components/datatable/datatable-contracts.js.map +1 -0
  21. package/lib/cjs/components/datatable/datatable-event-adapter.d.ts +7 -0
  22. package/lib/cjs/components/datatable/datatable-event-adapter.d.ts.map +1 -0
  23. package/lib/cjs/components/datatable/datatable-event-adapter.js +16 -0
  24. package/lib/cjs/components/datatable/datatable-event-adapter.js.map +1 -0
  25. package/lib/cjs/components/datatable/datatable-local-provider.d.ts +25 -0
  26. package/lib/cjs/components/datatable/datatable-local-provider.d.ts.map +1 -0
  27. package/lib/cjs/components/datatable/datatable-local-provider.js +184 -0
  28. package/lib/cjs/components/datatable/datatable-local-provider.js.map +1 -0
  29. package/lib/cjs/components/datatable/datatable-pagination-renderer.d.ts +15 -0
  30. package/lib/cjs/components/datatable/datatable-pagination-renderer.d.ts.map +1 -0
  31. package/lib/cjs/components/datatable/datatable-pagination-renderer.js +128 -0
  32. package/lib/cjs/components/datatable/datatable-pagination-renderer.js.map +1 -0
  33. package/lib/cjs/components/datatable/datatable-remote-provider.d.ts +25 -0
  34. package/lib/cjs/components/datatable/datatable-remote-provider.d.ts.map +1 -0
  35. package/lib/cjs/components/datatable/datatable-remote-provider.js +188 -0
  36. package/lib/cjs/components/datatable/datatable-remote-provider.js.map +1 -0
  37. package/lib/cjs/components/datatable/datatable-state-store.d.ts +21 -0
  38. package/lib/cjs/components/datatable/datatable-state-store.d.ts.map +1 -0
  39. package/lib/cjs/components/datatable/datatable-state-store.js +81 -0
  40. package/lib/cjs/components/datatable/datatable-state-store.js.map +1 -0
  41. package/lib/cjs/components/datatable/datatable-table-renderer.d.ts +16 -0
  42. package/lib/cjs/components/datatable/datatable-table-renderer.d.ts.map +1 -0
  43. package/lib/cjs/components/datatable/datatable-table-renderer.js +133 -0
  44. package/lib/cjs/components/datatable/datatable-table-renderer.js.map +1 -0
  45. package/lib/cjs/components/datatable/datatable.d.ts +9 -87
  46. package/lib/cjs/components/datatable/datatable.d.ts.map +1 -1
  47. package/lib/cjs/components/datatable/datatable.js +114 -686
  48. package/lib/cjs/components/datatable/datatable.js.map +1 -1
  49. package/lib/cjs/components/select/index.d.ts +1 -1
  50. package/lib/cjs/components/select/index.d.ts.map +1 -1
  51. package/lib/cjs/index.d.ts +5 -1
  52. package/lib/cjs/index.d.ts.map +1 -1
  53. package/lib/cjs/index.js +7 -7
  54. package/lib/cjs/index.js.map +1 -1
  55. package/lib/cjs/init-all.d.ts +6 -0
  56. package/lib/cjs/init-all.d.ts.map +1 -0
  57. package/lib/cjs/init-all.js +17 -0
  58. package/lib/cjs/init-all.js.map +1 -0
  59. package/lib/cjs/legacy.d.ts +8 -0
  60. package/lib/cjs/legacy.d.ts.map +1 -0
  61. package/lib/cjs/legacy.js +26 -0
  62. package/lib/cjs/legacy.js.map +1 -0
  63. package/lib/esm/components/context-menu/context-menu.d.ts +66 -0
  64. package/lib/esm/components/context-menu/context-menu.d.ts.map +1 -0
  65. package/lib/esm/components/context-menu/context-menu.js +420 -0
  66. package/lib/esm/components/context-menu/context-menu.js.map +1 -0
  67. package/lib/esm/components/context-menu/index.d.ts +7 -0
  68. package/lib/esm/components/context-menu/index.d.ts.map +1 -0
  69. package/lib/esm/components/context-menu/index.js +6 -0
  70. package/lib/esm/components/context-menu/index.js.map +1 -0
  71. package/lib/esm/components/context-menu/types.d.ts +30 -0
  72. package/lib/esm/components/context-menu/types.d.ts.map +1 -0
  73. package/lib/esm/components/context-menu/types.js +6 -0
  74. package/lib/esm/components/context-menu/types.js.map +1 -0
  75. package/lib/esm/components/datatable/datatable-contracts.d.ts +66 -0
  76. package/lib/esm/components/datatable/datatable-contracts.d.ts.map +1 -0
  77. package/lib/esm/components/datatable/datatable-contracts.js +6 -0
  78. package/lib/esm/components/datatable/datatable-contracts.js.map +1 -0
  79. package/lib/esm/components/datatable/datatable-event-adapter.d.ts +7 -0
  80. package/lib/esm/components/datatable/datatable-event-adapter.d.ts.map +1 -0
  81. package/lib/esm/components/datatable/datatable-event-adapter.js +13 -0
  82. package/lib/esm/components/datatable/datatable-event-adapter.js.map +1 -0
  83. package/lib/esm/components/datatable/datatable-local-provider.d.ts +25 -0
  84. package/lib/esm/components/datatable/datatable-local-provider.d.ts.map +1 -0
  85. package/lib/esm/components/datatable/datatable-local-provider.js +181 -0
  86. package/lib/esm/components/datatable/datatable-local-provider.js.map +1 -0
  87. package/lib/esm/components/datatable/datatable-pagination-renderer.d.ts +15 -0
  88. package/lib/esm/components/datatable/datatable-pagination-renderer.d.ts.map +1 -0
  89. package/lib/esm/components/datatable/datatable-pagination-renderer.js +125 -0
  90. package/lib/esm/components/datatable/datatable-pagination-renderer.js.map +1 -0
  91. package/lib/esm/components/datatable/datatable-remote-provider.d.ts +25 -0
  92. package/lib/esm/components/datatable/datatable-remote-provider.d.ts.map +1 -0
  93. package/lib/esm/components/datatable/datatable-remote-provider.js +185 -0
  94. package/lib/esm/components/datatable/datatable-remote-provider.js.map +1 -0
  95. package/lib/esm/components/datatable/datatable-state-store.d.ts +21 -0
  96. package/lib/esm/components/datatable/datatable-state-store.d.ts.map +1 -0
  97. package/lib/esm/components/datatable/datatable-state-store.js +78 -0
  98. package/lib/esm/components/datatable/datatable-state-store.js.map +1 -0
  99. package/lib/esm/components/datatable/datatable-table-renderer.d.ts +16 -0
  100. package/lib/esm/components/datatable/datatable-table-renderer.d.ts.map +1 -0
  101. package/lib/esm/components/datatable/datatable-table-renderer.js +130 -0
  102. package/lib/esm/components/datatable/datatable-table-renderer.js.map +1 -0
  103. package/lib/esm/components/datatable/datatable.d.ts +9 -87
  104. package/lib/esm/components/datatable/datatable.d.ts.map +1 -1
  105. package/lib/esm/components/datatable/datatable.js +114 -686
  106. package/lib/esm/components/datatable/datatable.js.map +1 -1
  107. package/lib/esm/components/select/index.d.ts +1 -1
  108. package/lib/esm/components/select/index.d.ts.map +1 -1
  109. package/lib/esm/index.d.ts +5 -1
  110. package/lib/esm/index.d.ts.map +1 -1
  111. package/lib/esm/index.js +4 -5
  112. package/lib/esm/index.js.map +1 -1
  113. package/lib/esm/init-all.d.ts +6 -0
  114. package/lib/esm/init-all.d.ts.map +1 -0
  115. package/lib/esm/init-all.js +13 -0
  116. package/lib/esm/init-all.js.map +1 -0
  117. package/lib/esm/legacy.d.ts +8 -0
  118. package/lib/esm/legacy.d.ts.map +1 -0
  119. package/lib/esm/legacy.js +8 -0
  120. package/lib/esm/legacy.js.map +1 -0
  121. package/package.json +34 -4
  122. package/src/__tests__/entrypoints.test.ts +71 -0
  123. package/src/components/context-menu/__tests__/context-menu.test.ts +117 -0
  124. package/src/components/context-menu/context-menu.css +32 -0
  125. package/src/components/context-menu/context-menu.ts +529 -0
  126. package/src/components/context-menu/index.ts +10 -0
  127. package/src/components/context-menu/types.ts +32 -0
  128. package/src/components/datatable/__tests__/architecture-boundaries.test.ts +259 -0
  129. package/src/components/datatable/datatable-contracts.ts +96 -0
  130. package/src/components/datatable/datatable-event-adapter.ts +21 -0
  131. package/src/components/datatable/datatable-local-provider.ts +194 -0
  132. package/src/components/datatable/datatable-pagination-renderer.ts +211 -0
  133. package/src/components/datatable/datatable-remote-provider.ts +175 -0
  134. package/src/components/datatable/datatable-state-store.ts +94 -0
  135. package/src/components/datatable/datatable-table-renderer.ts +206 -0
  136. package/src/components/datatable/datatable.ts +128 -839
  137. package/src/components/select/index.ts +1 -1
  138. package/src/index.ts +9 -5
  139. package/src/init-all.ts +15 -0
  140. package/src/legacy.ts +9 -0
  141. package/styles.css +1 -0
@@ -0,0 +1,211 @@
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
+ this.removeChildElements(input.sizeElement);
17
+ this.createPageSizeControls(input);
18
+
19
+ this.removeChildElements(input.paginationElement);
20
+ this.createPaginationControls(input);
21
+
22
+ return () => {
23
+ if (input.sizeElement) {
24
+ input.sizeElement.onchange = null;
25
+ }
26
+ this.removeChildElements(input.paginationElement);
27
+ };
28
+ }
29
+
30
+ private removeChildElements(container: HTMLElement): void {
31
+ if (!container) {
32
+ return;
33
+ }
34
+
35
+ while (container.firstChild) {
36
+ container.removeChild(container.firstChild);
37
+ }
38
+ }
39
+
40
+ private createPageSizeControls(
41
+ input: KTDataTablePaginationRendererInput,
42
+ ): HTMLSelectElement {
43
+ if (!input.sizeElement) {
44
+ return input.sizeElement;
45
+ }
46
+
47
+ setTimeout(() => {
48
+ const options = input.config.pageSizes.map((size: number) => {
49
+ const option = document.createElement('option') as HTMLOptionElement;
50
+ option.value = String(size);
51
+ option.text = String(size);
52
+ option.selected = input.state.pageSize === size;
53
+ return option;
54
+ });
55
+
56
+ input.sizeElement.append(...options);
57
+ }, 100);
58
+
59
+ input.sizeElement.onchange = (event: Event) => {
60
+ input.reloadPageSize(
61
+ Number((event.target as HTMLSelectElement).value),
62
+ 1,
63
+ );
64
+ };
65
+
66
+ return input.sizeElement;
67
+ }
68
+
69
+ private createPaginationControls(
70
+ input: KTDataTablePaginationRendererInput,
71
+ ): HTMLElement {
72
+ if (
73
+ !input.infoElement ||
74
+ !input.paginationElement ||
75
+ input.dataLength === 0
76
+ ) {
77
+ return null;
78
+ }
79
+
80
+ this.setPaginationInfoText(input);
81
+ this.createPaginationButtons(input.paginationElement, input);
82
+
83
+ return input.paginationElement;
84
+ }
85
+
86
+ private setPaginationInfoText(
87
+ input: KTDataTablePaginationRendererInput,
88
+ ): void {
89
+ input.infoElement.textContent = input.config.info
90
+ .replace(
91
+ '{start}',
92
+ (input.state.page - 1) * input.state.pageSize + 1 + '',
93
+ )
94
+ .replace(
95
+ '{end}',
96
+ Math.min(
97
+ input.state.page * input.state.pageSize,
98
+ input.state.totalItems,
99
+ ) + '',
100
+ )
101
+ .replace('{total}', input.state.totalItems + '');
102
+ }
103
+
104
+ private createPaginationButtons(
105
+ paginationContainer: HTMLElement,
106
+ input: KTDataTablePaginationRendererInput,
107
+ ): void {
108
+ const { page: currentPage, totalPages } = input.state;
109
+ const { previous, next, number, more } = input.config.pagination;
110
+
111
+ const createButton = (
112
+ text: string,
113
+ className: string,
114
+ disabled: boolean,
115
+ handleClick: () => void,
116
+ ): HTMLButtonElement => {
117
+ const button = document.createElement('button') as HTMLButtonElement;
118
+ button.className = className;
119
+ button.innerHTML = text;
120
+ button.disabled = disabled;
121
+ button.onclick = handleClick;
122
+ return button;
123
+ };
124
+
125
+ paginationContainer.appendChild(
126
+ createButton(
127
+ previous.text,
128
+ `${previous.class}${currentPage === 1 ? ' disabled' : ''}`,
129
+ currentPage === 1,
130
+ () => input.paginateData(currentPage - 1),
131
+ ),
132
+ );
133
+
134
+ if (input.config.pageMore) {
135
+ const range = this.calculatePageRange(
136
+ currentPage,
137
+ totalPages,
138
+ input.config.pageMoreLimit,
139
+ );
140
+
141
+ if (range.start > 1) {
142
+ paginationContainer.appendChild(
143
+ createButton(more.text, more.class, false, () =>
144
+ input.paginateData(Math.max(1, range.start - 1)),
145
+ ),
146
+ );
147
+ }
148
+
149
+ for (let i = range.start; i <= range.end; i++) {
150
+ paginationContainer.appendChild(
151
+ createButton(
152
+ number.text.replace('{page}', i.toString()),
153
+ `${number.class}${currentPage === i ? ' active disabled' : ''}`,
154
+ currentPage === i,
155
+ () => input.paginateData(i),
156
+ ),
157
+ );
158
+ }
159
+
160
+ if (range.end < totalPages) {
161
+ paginationContainer.appendChild(
162
+ createButton(more.text, more.class, false, () =>
163
+ input.paginateData(Math.min(totalPages, range.end + 1)),
164
+ ),
165
+ );
166
+ }
167
+ } else {
168
+ for (let i = 1; i <= totalPages; i++) {
169
+ paginationContainer.appendChild(
170
+ createButton(
171
+ number.text.replace('{page}', i.toString()),
172
+ `${number.class}${currentPage === i ? ' active disabled' : ''}`,
173
+ currentPage === i,
174
+ () => input.paginateData(i),
175
+ ),
176
+ );
177
+ }
178
+ }
179
+
180
+ paginationContainer.appendChild(
181
+ createButton(
182
+ next.text,
183
+ `${next.class}${currentPage === totalPages ? ' disabled' : ''}`,
184
+ currentPage === totalPages,
185
+ () => input.paginateData(currentPage + 1),
186
+ ),
187
+ );
188
+ }
189
+
190
+ private calculatePageRange(
191
+ currentPage: number,
192
+ totalPages: number,
193
+ maxButtons: number,
194
+ ): { start: number; end: number } {
195
+ let startPage: number, endPage: number;
196
+ const halfMaxButtons = Math.floor(maxButtons / 2);
197
+
198
+ if (totalPages <= maxButtons) {
199
+ startPage = 1;
200
+ endPage = totalPages;
201
+ } else {
202
+ startPage = Math.max(currentPage - halfMaxButtons, 1);
203
+ endPage = Math.min(startPage + maxButtons - 1, totalPages);
204
+ if (endPage - startPage < maxButtons - 1) {
205
+ startPage = Math.max(endPage - maxButtons + 1, 1);
206
+ }
207
+ }
208
+
209
+ return { start: startPage, end: endPage };
210
+ }
211
+ }
@@ -0,0 +1,175 @@
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
+
141
+ if (this.abortController) {
142
+ this.abortController.abort();
143
+ }
144
+
145
+ this.abortController = new AbortController();
146
+
147
+ if (requestMethod === 'POST') {
148
+ requestBody = queryParams;
149
+ } else if (requestMethod === 'GET') {
150
+ const apiEndpointWithQueryParams = this.options.createUrl(apiEndpoint);
151
+ apiEndpointWithQueryParams.search = queryParams.toString();
152
+ apiEndpoint = apiEndpointWithQueryParams.toString();
153
+ }
154
+
155
+ return fetch(apiEndpoint, {
156
+ method: requestMethod,
157
+ body: requestBody,
158
+ headers: this.options.config.requestHeaders,
159
+ ...(this.options.config.requestCredentials && {
160
+ credentials: this.options.config.requestCredentials,
161
+ }),
162
+ ...(this.abortController && { signal: this.abortController.signal }),
163
+ }).catch((error) => {
164
+ if (error.name === 'AbortError') {
165
+ return Promise.reject(error);
166
+ }
167
+
168
+ this.options.eventAdapter.emit('error', { error });
169
+ this.options.noticeOnTable(
170
+ 'Error performing fetch request: ' + String(error),
171
+ );
172
+ throw error;
173
+ });
174
+ }
175
+ }
@@ -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,206 @@
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, 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
+ 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
+ }
170
+
171
+ if (typeof columnDef.createdCell === 'function') {
172
+ columnDef.createdCell.call(input.context, td, item[key], item, row);
173
+ }
174
+
175
+ row.appendChild(td);
176
+ },
177
+ );
178
+ }
179
+
180
+ private applyOriginalTdClass(
181
+ input: KTDataTableTableRendererInput<T>,
182
+ td: HTMLTableCellElement,
183
+ rowIndex: number,
184
+ colIndex: number,
185
+ ): void {
186
+ if (
187
+ input.originalTdClasses &&
188
+ input.originalTdClasses[rowIndex] &&
189
+ input.originalTdClasses[rowIndex][colIndex]
190
+ ) {
191
+ td.className = input.originalTdClasses[rowIndex][colIndex];
192
+ }
193
+ }
194
+
195
+ private applyDataRowAttributes(
196
+ td: HTMLTableCellElement,
197
+ dataRowAttributes: KTDataTableAttributeInterface,
198
+ colIndex: number,
199
+ ): void {
200
+ if (dataRowAttributes && dataRowAttributes[colIndex]) {
201
+ for (const attr in dataRowAttributes[colIndex]) {
202
+ td.setAttribute(attr, dataRowAttributes[colIndex][attr]);
203
+ }
204
+ }
205
+ }
206
+ }