@keenthemes/ktui 1.2.6 → 1.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -5
- package/dist/ktui.js +3775 -2298
- package/dist/ktui.min.js +1 -1
- package/dist/ktui.min.js.map +1 -1
- package/dist/styles.css +25 -5
- package/lib/cjs/components/datatable/datatable-checkbox.d.ts +37 -1
- package/lib/cjs/components/datatable/datatable-checkbox.d.ts.map +1 -1
- package/lib/cjs/components/datatable/datatable-checkbox.js +143 -156
- package/lib/cjs/components/datatable/datatable-checkbox.js.map +1 -1
- package/lib/cjs/components/datatable/datatable-column-utils.d.ts +30 -0
- package/lib/cjs/components/datatable/datatable-column-utils.d.ts.map +1 -0
- package/lib/cjs/components/datatable/datatable-column-utils.js +42 -0
- package/lib/cjs/components/datatable/datatable-column-utils.js.map +1 -0
- package/lib/cjs/components/datatable/datatable-contracts.d.ts +2 -4
- package/lib/cjs/components/datatable/datatable-contracts.d.ts.map +1 -1
- package/lib/cjs/components/datatable/datatable-defaults.d.ts +20 -0
- package/lib/cjs/components/datatable/datatable-defaults.d.ts.map +1 -0
- package/lib/cjs/components/datatable/datatable-defaults.js +193 -0
- package/lib/cjs/components/datatable/datatable-defaults.js.map +1 -0
- package/lib/cjs/components/datatable/datatable-layout-plugin.d.ts.map +1 -1
- package/lib/cjs/components/datatable/datatable-layout-plugin.js +11 -1
- package/lib/cjs/components/datatable/datatable-layout-plugin.js.map +1 -1
- package/lib/cjs/components/datatable/datatable-local-provider.d.ts.map +1 -1
- package/lib/cjs/components/datatable/datatable-local-provider.js +80 -24
- package/lib/cjs/components/datatable/datatable-local-provider.js.map +1 -1
- package/lib/cjs/components/datatable/datatable-pagination-renderer.d.ts.map +1 -1
- package/lib/cjs/components/datatable/datatable-pagination-renderer.js +3 -2
- package/lib/cjs/components/datatable/datatable-pagination-renderer.js.map +1 -1
- package/lib/cjs/components/datatable/datatable-registry.d.ts +18 -0
- package/lib/cjs/components/datatable/datatable-registry.d.ts.map +1 -0
- package/lib/cjs/components/datatable/datatable-registry.js +66 -0
- package/lib/cjs/components/datatable/datatable-registry.js.map +1 -0
- package/lib/cjs/components/datatable/datatable-remote-provider.d.ts.map +1 -1
- package/lib/cjs/components/datatable/datatable-remote-provider.js +1 -2
- package/lib/cjs/components/datatable/datatable-remote-provider.js.map +1 -1
- package/lib/cjs/components/datatable/datatable-search-handler.d.ts +10 -0
- package/lib/cjs/components/datatable/datatable-search-handler.d.ts.map +1 -0
- package/lib/cjs/components/datatable/datatable-search-handler.js +65 -0
- package/lib/cjs/components/datatable/datatable-search-handler.js.map +1 -0
- package/lib/cjs/components/datatable/datatable-sort.d.ts +31 -4
- package/lib/cjs/components/datatable/datatable-sort.d.ts.map +1 -1
- package/lib/cjs/components/datatable/datatable-sort.js +86 -58
- package/lib/cjs/components/datatable/datatable-sort.js.map +1 -1
- package/lib/cjs/components/datatable/datatable-spinner.d.ts +30 -0
- package/lib/cjs/components/datatable/datatable-spinner.d.ts.map +1 -0
- package/lib/cjs/components/datatable/datatable-spinner.js +54 -0
- package/lib/cjs/components/datatable/datatable-spinner.js.map +1 -0
- package/lib/cjs/components/datatable/datatable-state-persistence.d.ts +19 -0
- package/lib/cjs/components/datatable/datatable-state-persistence.d.ts.map +1 -0
- package/lib/cjs/components/datatable/datatable-state-persistence.js +59 -0
- package/lib/cjs/components/datatable/datatable-state-persistence.js.map +1 -0
- package/lib/cjs/components/datatable/datatable-table-renderer.d.ts +2 -0
- package/lib/cjs/components/datatable/datatable-table-renderer.d.ts.map +1 -1
- package/lib/cjs/components/datatable/datatable-table-renderer.js +75 -16
- package/lib/cjs/components/datatable/datatable-table-renderer.js.map +1 -1
- package/lib/cjs/components/datatable/datatable-utils.d.ts +10 -0
- package/lib/cjs/components/datatable/datatable-utils.d.ts.map +1 -0
- package/lib/cjs/components/datatable/datatable-utils.js +15 -0
- package/lib/cjs/components/datatable/datatable-utils.js.map +1 -0
- package/lib/cjs/components/datatable/datatable.d.ts +26 -34
- package/lib/cjs/components/datatable/datatable.d.ts.map +1 -1
- package/lib/cjs/components/datatable/datatable.js +155 -492
- package/lib/cjs/components/datatable/datatable.js.map +1 -1
- package/lib/cjs/components/datatable/index.d.ts +1 -1
- package/lib/cjs/components/datatable/index.d.ts.map +1 -1
- package/lib/cjs/components/datatable/types.d.ts +100 -11
- package/lib/cjs/components/datatable/types.d.ts.map +1 -1
- package/lib/cjs/index.d.ts +1 -1
- package/lib/cjs/index.d.ts.map +1 -1
- package/lib/cjs/index.js +6 -0
- package/lib/cjs/index.js.map +1 -1
- package/lib/esm/components/datatable/datatable-checkbox.d.ts +37 -1
- package/lib/esm/components/datatable/datatable-checkbox.d.ts.map +1 -1
- package/lib/esm/components/datatable/datatable-checkbox.js +142 -155
- package/lib/esm/components/datatable/datatable-checkbox.js.map +1 -1
- package/lib/esm/components/datatable/datatable-column-utils.d.ts +30 -0
- package/lib/esm/components/datatable/datatable-column-utils.d.ts.map +1 -0
- package/lib/esm/components/datatable/datatable-column-utils.js +38 -0
- package/lib/esm/components/datatable/datatable-column-utils.js.map +1 -0
- package/lib/esm/components/datatable/datatable-contracts.d.ts +2 -4
- package/lib/esm/components/datatable/datatable-contracts.d.ts.map +1 -1
- package/lib/esm/components/datatable/datatable-defaults.d.ts +20 -0
- package/lib/esm/components/datatable/datatable-defaults.d.ts.map +1 -0
- package/lib/esm/components/datatable/datatable-defaults.js +190 -0
- package/lib/esm/components/datatable/datatable-defaults.js.map +1 -0
- package/lib/esm/components/datatable/datatable-layout-plugin.d.ts.map +1 -1
- package/lib/esm/components/datatable/datatable-layout-plugin.js +11 -1
- package/lib/esm/components/datatable/datatable-layout-plugin.js.map +1 -1
- package/lib/esm/components/datatable/datatable-local-provider.d.ts.map +1 -1
- package/lib/esm/components/datatable/datatable-local-provider.js +80 -24
- package/lib/esm/components/datatable/datatable-local-provider.js.map +1 -1
- package/lib/esm/components/datatable/datatable-pagination-renderer.d.ts.map +1 -1
- package/lib/esm/components/datatable/datatable-pagination-renderer.js +3 -2
- package/lib/esm/components/datatable/datatable-pagination-renderer.js.map +1 -1
- package/lib/esm/components/datatable/datatable-registry.d.ts +18 -0
- package/lib/esm/components/datatable/datatable-registry.d.ts.map +1 -0
- package/lib/esm/components/datatable/datatable-registry.js +63 -0
- package/lib/esm/components/datatable/datatable-registry.js.map +1 -0
- package/lib/esm/components/datatable/datatable-remote-provider.d.ts.map +1 -1
- package/lib/esm/components/datatable/datatable-remote-provider.js +1 -2
- package/lib/esm/components/datatable/datatable-remote-provider.js.map +1 -1
- package/lib/esm/components/datatable/datatable-search-handler.d.ts +10 -0
- package/lib/esm/components/datatable/datatable-search-handler.d.ts.map +1 -0
- package/lib/esm/components/datatable/datatable-search-handler.js +62 -0
- package/lib/esm/components/datatable/datatable-search-handler.js.map +1 -0
- package/lib/esm/components/datatable/datatable-sort.d.ts +31 -4
- package/lib/esm/components/datatable/datatable-sort.d.ts.map +1 -1
- package/lib/esm/components/datatable/datatable-sort.js +85 -57
- package/lib/esm/components/datatable/datatable-sort.js.map +1 -1
- package/lib/esm/components/datatable/datatable-spinner.d.ts +30 -0
- package/lib/esm/components/datatable/datatable-spinner.d.ts.map +1 -0
- package/lib/esm/components/datatable/datatable-spinner.js +51 -0
- package/lib/esm/components/datatable/datatable-spinner.js.map +1 -0
- package/lib/esm/components/datatable/datatable-state-persistence.d.ts +19 -0
- package/lib/esm/components/datatable/datatable-state-persistence.d.ts.map +1 -0
- package/lib/esm/components/datatable/datatable-state-persistence.js +55 -0
- package/lib/esm/components/datatable/datatable-state-persistence.js.map +1 -0
- package/lib/esm/components/datatable/datatable-table-renderer.d.ts +2 -0
- package/lib/esm/components/datatable/datatable-table-renderer.d.ts.map +1 -1
- package/lib/esm/components/datatable/datatable-table-renderer.js +75 -16
- package/lib/esm/components/datatable/datatable-table-renderer.js.map +1 -1
- package/lib/esm/components/datatable/datatable-utils.d.ts +10 -0
- package/lib/esm/components/datatable/datatable-utils.d.ts.map +1 -0
- package/lib/esm/components/datatable/datatable-utils.js +12 -0
- package/lib/esm/components/datatable/datatable-utils.js.map +1 -0
- package/lib/esm/components/datatable/datatable.d.ts +26 -34
- package/lib/esm/components/datatable/datatable.d.ts.map +1 -1
- package/lib/esm/components/datatable/datatable.js +157 -494
- package/lib/esm/components/datatable/datatable.js.map +1 -1
- package/lib/esm/components/datatable/index.d.ts +1 -1
- package/lib/esm/components/datatable/index.d.ts.map +1 -1
- package/lib/esm/components/datatable/types.d.ts +100 -11
- package/lib/esm/components/datatable/types.d.ts.map +1 -1
- package/lib/esm/index.d.ts +1 -1
- package/lib/esm/index.d.ts.map +1 -1
- package/lib/esm/index.js +6 -0
- package/lib/esm/index.js.map +1 -1
- package/package.json +5 -1
- package/skills/ktui/SKILL.md +711 -0
- package/skills/ktui-datatable/SKILL.md +302 -0
- package/skills/ktui-install/SKILL.md +150 -0
- package/skills/ktui-select/SKILL.md +271 -0
- package/src/components/__tests__/component.test.ts +347 -0
- package/src/components/collapse/collapse.css +2 -2
- package/src/components/datatable/__tests__/architecture-boundaries.test.ts +56 -8
- package/src/components/datatable/__tests__/currency-sort.test.ts +25 -28
- package/src/components/datatable/__tests__/datatable-checkbox.test.ts +527 -0
- package/src/components/datatable/__tests__/datatable-column-utils.test.ts +117 -0
- package/src/components/datatable/__tests__/datatable-defaults.test.ts +57 -0
- package/src/components/datatable/__tests__/datatable-finalize-extended.test.ts +361 -0
- package/src/components/datatable/__tests__/datatable-fixed-layout.test.ts +427 -0
- package/src/components/datatable/__tests__/datatable-improvements.test.ts +484 -0
- package/src/components/datatable/__tests__/datatable-pagination-extended.test.ts +508 -0
- package/src/components/datatable/__tests__/datatable-public-api.test.ts +269 -0
- package/src/components/datatable/__tests__/datatable-registry.test.ts +172 -0
- package/src/components/datatable/__tests__/datatable-remote-provider.test.ts +468 -0
- package/src/components/datatable/__tests__/datatable-search-handler.test.ts +124 -0
- package/src/components/datatable/__tests__/datatable-sort-extended.test.ts +417 -0
- package/src/components/datatable/__tests__/datatable-spinner.test.ts +95 -0
- package/src/components/datatable/__tests__/datatable-table-renderer-extended.test.ts +425 -0
- package/src/components/datatable/__tests__/datatable-types.test.ts +117 -0
- package/src/components/datatable/__tests__/datatable-utils.test.ts +52 -0
- package/src/components/datatable/__tests__/multi-row-headers.test.ts +7 -7
- package/src/components/datatable/__tests__/pagination-reset.test.ts +129 -6
- package/src/components/datatable/__tests__/race-conditions.test.ts +11 -11
- package/src/components/datatable/__tests__/setup.ts +12 -4
- package/src/components/datatable/datatable-checkbox.ts +144 -145
- package/src/components/datatable/datatable-column-utils.ts +63 -0
- package/src/components/datatable/datatable-contracts.ts +2 -3
- package/src/components/datatable/datatable-defaults.ts +204 -0
- package/src/components/datatable/datatable-layout-plugin.ts +11 -1
- package/src/components/datatable/datatable-local-provider.ts +91 -28
- package/src/components/datatable/datatable-pagination-renderer.ts +3 -2
- package/src/components/datatable/datatable-registry.ts +89 -0
- package/src/components/datatable/datatable-remote-provider.ts +1 -3
- package/src/components/datatable/datatable-search-handler.ts +97 -0
- package/src/components/datatable/datatable-sort.ts +111 -66
- package/src/components/datatable/datatable-spinner.ts +103 -0
- package/src/components/datatable/datatable-state-persistence.ts +67 -0
- package/src/components/datatable/datatable-table-renderer.ts +81 -18
- package/src/components/datatable/datatable-utils.ts +12 -0
- package/src/components/datatable/datatable.ts +191 -580
- package/src/components/datatable/index.ts +3 -0
- package/src/components/datatable/types.ts +124 -23
- package/src/helpers/__tests__/dom.test.ts +776 -0
- package/src/helpers/__tests__/utils.test.ts +332 -0
- package/src/index.ts +10 -0
- package/skills/ktui-components/SKILL.md +0 -41
- package/skills/ktui-theming/SKILL.md +0 -50
- package/src/components/datatable/datatable-event-adapter.ts +0 -21
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KTUI - Free & Open-Source Tailwind UI Components by Keenthemes
|
|
3
|
+
* Copyright 2025 by Keenthemes Inc
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Shared column resolution utilities for KTDataTable.
|
|
8
|
+
* Eliminates duplicated column discovery logic across local-provider,
|
|
9
|
+
* table-renderer, and the main datatable class.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export interface ResolvedColumns {
|
|
13
|
+
/** All <th> elements from the thead */
|
|
14
|
+
allThs: HTMLTableCellElement[];
|
|
15
|
+
/** Only <th> elements that have data-kt-datatable-column attribute */
|
|
16
|
+
typedThs: HTMLTableCellElement[];
|
|
17
|
+
/** The column list to use for rendering: typedThs if all ths have the attr, else allThs */
|
|
18
|
+
columnsByIndex: HTMLTableCellElement[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Resolve column headers from a thead element.
|
|
23
|
+
* Returns both the raw th list and the typed (data-kt-datatable-column) subset.
|
|
24
|
+
* When some ths lack the attribute, columnsByIndex falls back to all ths by index.
|
|
25
|
+
*/
|
|
26
|
+
export function resolveColumns(
|
|
27
|
+
theadElement: HTMLTableSectionElement | null,
|
|
28
|
+
): ResolvedColumns {
|
|
29
|
+
const allThs: HTMLTableCellElement[] = theadElement
|
|
30
|
+
? Array.from(theadElement.querySelectorAll('th'))
|
|
31
|
+
: [];
|
|
32
|
+
|
|
33
|
+
const typedThs = allThs.filter((th) =>
|
|
34
|
+
th.hasAttribute('data-kt-datatable-column'),
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const columnsByIndex = typedThs.length > 0 ? typedThs : allThs;
|
|
38
|
+
|
|
39
|
+
return { allThs, typedThs, columnsByIndex };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Get the logical column count: number of data columns.
|
|
44
|
+
* Prefers originalData keys, falls back to first tbody row td count,
|
|
45
|
+
* then to resolved columns from thead.
|
|
46
|
+
*/
|
|
47
|
+
export function getLogicalColumnCount(
|
|
48
|
+
theadElement: HTMLTableSectionElement | null,
|
|
49
|
+
tbodyElement: HTMLTableSectionElement | null,
|
|
50
|
+
originalData?: Array<Record<string, unknown>>,
|
|
51
|
+
): number {
|
|
52
|
+
if (originalData && originalData.length > 0) {
|
|
53
|
+
return Object.keys(originalData[0]).length;
|
|
54
|
+
}
|
|
55
|
+
if (tbodyElement) {
|
|
56
|
+
const firstRow = tbodyElement.querySelector<HTMLTableRowElement>('tr');
|
|
57
|
+
if (firstRow) {
|
|
58
|
+
return firstRow.querySelectorAll('td').length;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const { columnsByIndex } = resolveColumns(theadElement);
|
|
62
|
+
return columnsByIndex.length;
|
|
63
|
+
}
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
KTDataTableInterface,
|
|
12
12
|
KTDataTableSortOrderInterface,
|
|
13
13
|
KTDataTableStateInterface,
|
|
14
|
+
OriginalTableClasses,
|
|
14
15
|
} from './types';
|
|
15
16
|
|
|
16
17
|
export type KTDataTableEmit = (eventName: string, eventData?: object) => void;
|
|
@@ -64,9 +65,7 @@ export interface KTDataTableTableRendererInput<
|
|
|
64
65
|
data: T[];
|
|
65
66
|
getLogicalColumnCount: () => number;
|
|
66
67
|
getState: () => KTDataTableStateInterface;
|
|
67
|
-
|
|
68
|
-
originalTrClasses: string[];
|
|
69
|
-
originalTdClasses: string[][];
|
|
68
|
+
originalClasses: OriginalTableClasses;
|
|
70
69
|
tableElement: HTMLTableElement;
|
|
71
70
|
theadElement: HTMLTableSectionElement;
|
|
72
71
|
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KTUI - Free & Open-Source Tailwind UI Components by Keenthemes
|
|
3
|
+
* Copyright 2025 by Keenthemes Inc
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { KTDataTableConfigInterface, KTDataTableStateInterface } from './types';
|
|
7
|
+
|
|
8
|
+
/** Default page size options shown in the size selector */
|
|
9
|
+
export const DEFAULT_PAGE_SIZES = [5, 10, 20, 30, 50] as const;
|
|
10
|
+
|
|
11
|
+
/** Default debounce delay (ms) for search input */
|
|
12
|
+
export const DEFAULT_SEARCH_DELAY = 500;
|
|
13
|
+
|
|
14
|
+
/** Default maximum visible page buttons before showing '...' */
|
|
15
|
+
export const DEFAULT_PAGE_MORE_LIMIT = 3;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Default configuration for KTDataTable.
|
|
19
|
+
* Extracted from _initDefaultConfig() for separation of concerns.
|
|
20
|
+
* Note: sort.callback and search.callback are NOT included here because
|
|
21
|
+
* they require instance context (this._sortHandler). They are added
|
|
22
|
+
* in datatable.ts _initDefaultConfig().
|
|
23
|
+
*/
|
|
24
|
+
export const DATATABLE_DEFAULTS: Readonly<KTDataTableConfigInterface> = {
|
|
25
|
+
/**
|
|
26
|
+
* HTTP method for server-side API call
|
|
27
|
+
*/
|
|
28
|
+
requestMethod: 'GET',
|
|
29
|
+
/**
|
|
30
|
+
* Custom HTTP headers for the API request
|
|
31
|
+
*/
|
|
32
|
+
requestHeaders: {
|
|
33
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
34
|
+
},
|
|
35
|
+
/**
|
|
36
|
+
* Pagination info template
|
|
37
|
+
*/
|
|
38
|
+
info: '{start}-{end} of {total}',
|
|
39
|
+
/**
|
|
40
|
+
* Info text when there is no data
|
|
41
|
+
*/
|
|
42
|
+
infoEmpty: 'No records found',
|
|
43
|
+
/**
|
|
44
|
+
* Available page sizes
|
|
45
|
+
*/
|
|
46
|
+
pageSizes: [...DEFAULT_PAGE_SIZES],
|
|
47
|
+
/**
|
|
48
|
+
* Default page size
|
|
49
|
+
*/
|
|
50
|
+
pageSize: 10,
|
|
51
|
+
/**
|
|
52
|
+
* Enable or disable pagination more button
|
|
53
|
+
*/
|
|
54
|
+
pageMore: true,
|
|
55
|
+
/**
|
|
56
|
+
* Maximum number of pages before enabling pagination more button
|
|
57
|
+
*/
|
|
58
|
+
pageMoreLimit: 3,
|
|
59
|
+
/**
|
|
60
|
+
* Pagination button templates
|
|
61
|
+
*/
|
|
62
|
+
pagination: {
|
|
63
|
+
number: {
|
|
64
|
+
/**
|
|
65
|
+
* CSS classes to be added to the pagination button
|
|
66
|
+
*/
|
|
67
|
+
class: 'kt-datatable-pagination-button',
|
|
68
|
+
/**
|
|
69
|
+
* Text to be displayed in the pagination button
|
|
70
|
+
*/
|
|
71
|
+
text: '{page}',
|
|
72
|
+
},
|
|
73
|
+
previous: {
|
|
74
|
+
/**
|
|
75
|
+
* CSS classes to be added to the previous pagination button
|
|
76
|
+
*/
|
|
77
|
+
class: 'kt-datatable-pagination-button kt-datatable-pagination-prev',
|
|
78
|
+
/**
|
|
79
|
+
* Text to be displayed in the previous pagination button
|
|
80
|
+
*/
|
|
81
|
+
text: `
|
|
82
|
+
<svg class="rtl:transform rtl:rotate-180 size-3.5 shrink-0" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
83
|
+
<path d="M8.86501 16.7882V12.8481H21.1459C21.3724 12.8481 21.5897 12.7581 21.7498 12.5979C21.91 12.4378 22 12.2205 22 11.994C22 11.7675 21.91 11.5503 21.7498 11.3901C21.5897 11.2299 21.3724 11.1399 21.1459 11.1399H8.86501V7.2112C8.86628 7.10375 8.83517 6.9984 8.77573 6.90887C8.7163 6.81934 8.63129 6.74978 8.53177 6.70923C8.43225 6.66869 8.32283 6.65904 8.21775 6.68155C8.11267 6.70405 8.0168 6.75766 7.94262 6.83541L2.15981 11.6182C2.1092 11.668 2.06901 11.7274 2.04157 11.7929C2.01413 11.8584 2 11.9287 2 11.9997C2 12.0707 2.01413 12.141 2.04157 12.2065C2.06901 12.272 2.1092 12.3314 2.15981 12.3812L7.94262 17.164C8.0168 17.2417 8.11267 17.2953 8.21775 17.3178C8.32283 17.3403 8.43225 17.3307 8.53177 17.2902C8.63129 17.2496 8.7163 17.18 8.77573 17.0905C8.83517 17.001 8.86628 16.8956 8.86501 16.7882Z" fill="currentColor"/>
|
|
84
|
+
</svg>
|
|
85
|
+
`,
|
|
86
|
+
},
|
|
87
|
+
next: {
|
|
88
|
+
/**
|
|
89
|
+
* CSS classes to be added to the next pagination button
|
|
90
|
+
*/
|
|
91
|
+
class: 'kt-datatable-pagination-button kt-datatable-pagination-next',
|
|
92
|
+
/**
|
|
93
|
+
* Text to be displayed in the next pagination button
|
|
94
|
+
*/
|
|
95
|
+
text: `
|
|
96
|
+
<svg class="rtl:transform rtl:rotate-180 size-3.5 shrink-0" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
97
|
+
<path d="M15.135 7.21144V11.1516H2.85407C2.62756 11.1516 2.41032 11.2415 2.25015 11.4017C2.08998 11.5619 2 11.7791 2 12.0056C2 12.2321 2.08998 12.4494 2.25015 12.6096C2.41032 12.7697 2.62756 12.8597 2.85407 12.8597H15.135V16.7884C15.1337 16.8959 15.1648 17.0012 15.2243 17.0908C15.2837 17.1803 15.3687 17.2499 15.4682 17.2904C15.5677 17.3309 15.6772 17.3406 15.7822 17.3181C15.8873 17.2956 15.9832 17.242 16.0574 17.1642L21.8402 12.3814C21.8908 12.3316 21.931 12.2722 21.9584 12.2067C21.9859 12.1412 22 12.0709 22 11.9999C22 11.9289 21.9859 11.8586 21.9584 11.7931C21.931 11.7276 21.8908 11.6683 21.8402 11.6185L16.0574 6.83565C15.9832 6.75791 15.8873 6.70429 15.7822 6.68179C15.6772 6.65929 15.5677 6.66893 15.4682 6.70948C15.3687 6.75002 15.2837 6.81959 15.2243 6.90911C15.1648 6.99864 15.1337 7.10399 15.135 7.21144Z" fill="currentColor"/>
|
|
98
|
+
</svg>
|
|
99
|
+
`,
|
|
100
|
+
},
|
|
101
|
+
more: {
|
|
102
|
+
/**
|
|
103
|
+
* CSS classes to be added to the pagination more button
|
|
104
|
+
*/
|
|
105
|
+
class: 'kt-datatable-pagination-button kt-datatable-pagination-more',
|
|
106
|
+
/**
|
|
107
|
+
* Text to be displayed in the pagination more button
|
|
108
|
+
*/
|
|
109
|
+
text: '...',
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
/**
|
|
113
|
+
* Sorting options — classes only; callback is added in datatable.ts
|
|
114
|
+
*/
|
|
115
|
+
sort: {
|
|
116
|
+
/**
|
|
117
|
+
* CSS classes to be added to the sortable headers
|
|
118
|
+
*/
|
|
119
|
+
classes: {
|
|
120
|
+
base: 'kt-table-col',
|
|
121
|
+
asc: 'asc',
|
|
122
|
+
desc: 'desc',
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
/**
|
|
126
|
+
* Search options — delay only; callback is added in datatable.ts
|
|
127
|
+
*/
|
|
128
|
+
search: {
|
|
129
|
+
/**
|
|
130
|
+
* Delay in milliseconds before the search function is applied to the data array
|
|
131
|
+
* @default 500
|
|
132
|
+
*/
|
|
133
|
+
delay: 500, // ms
|
|
134
|
+
},
|
|
135
|
+
/**
|
|
136
|
+
* Loading spinner options
|
|
137
|
+
*/
|
|
138
|
+
loading: {
|
|
139
|
+
/**
|
|
140
|
+
* Template to be displayed during data fetching process
|
|
141
|
+
*/
|
|
142
|
+
template: `
|
|
143
|
+
<div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
|
|
144
|
+
<div class="kt-datatable-loading">
|
|
145
|
+
<svg class="animate-spin -ml-1 h-5 w-5 text-gray-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
146
|
+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="3"></circle>
|
|
147
|
+
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
148
|
+
</svg>
|
|
149
|
+
{content}
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
`,
|
|
153
|
+
/**
|
|
154
|
+
* Loading text to be displayed in the template
|
|
155
|
+
*/
|
|
156
|
+
content: 'Loading...',
|
|
157
|
+
},
|
|
158
|
+
/**
|
|
159
|
+
* Selectors of the elements to be targeted
|
|
160
|
+
*/
|
|
161
|
+
attributes: {
|
|
162
|
+
/**
|
|
163
|
+
* Data table element
|
|
164
|
+
*/
|
|
165
|
+
table: 'table[data-kt-datatable-table="true"]',
|
|
166
|
+
/**
|
|
167
|
+
* Pagination info element
|
|
168
|
+
*/
|
|
169
|
+
info: '[data-kt-datatable-info="true"]',
|
|
170
|
+
/**
|
|
171
|
+
* Page size dropdown element
|
|
172
|
+
*/
|
|
173
|
+
size: '[data-kt-datatable-size="true"]',
|
|
174
|
+
/**
|
|
175
|
+
* Pagination element
|
|
176
|
+
*/
|
|
177
|
+
pagination: '[data-kt-datatable-pagination="true"]',
|
|
178
|
+
/**
|
|
179
|
+
* Spinner element
|
|
180
|
+
*/
|
|
181
|
+
spinner: '[data-kt-datatable-spinner="true"]',
|
|
182
|
+
/**
|
|
183
|
+
* Checkbox element
|
|
184
|
+
*/
|
|
185
|
+
check: '[data-kt-datatable-check="true"]',
|
|
186
|
+
checkbox: '[data-kt-datatable-row-check="true"]',
|
|
187
|
+
},
|
|
188
|
+
/**
|
|
189
|
+
* Enable or disable state saving
|
|
190
|
+
*/
|
|
191
|
+
stateSave: true,
|
|
192
|
+
checkbox: {
|
|
193
|
+
checkedClass: 'checked',
|
|
194
|
+
},
|
|
195
|
+
/**
|
|
196
|
+
* Table layout algorithm: 'auto' (default) or 'fixed' for consistent column widths
|
|
197
|
+
*/
|
|
198
|
+
tableLayout: 'auto',
|
|
199
|
+
/**
|
|
200
|
+
* Private properties
|
|
201
|
+
*/
|
|
202
|
+
_state: {} as KTDataTableStateInterface,
|
|
203
|
+
loadingClass: 'loading',
|
|
204
|
+
};
|
|
@@ -434,8 +434,18 @@ export const createStickyLayoutPlugin =
|
|
|
434
434
|
detachResizeListener();
|
|
435
435
|
applyLayout(ctx);
|
|
436
436
|
|
|
437
|
+
// Throttle re-layout with rAF to avoid running on every resize/scroll event
|
|
438
|
+
let rafId = 0;
|
|
439
|
+
const throttledApply = () => {
|
|
440
|
+
if (rafId) return;
|
|
441
|
+
rafId = requestAnimationFrame(() => {
|
|
442
|
+
rafId = 0;
|
|
443
|
+
applyLayout(ctx);
|
|
444
|
+
});
|
|
445
|
+
};
|
|
446
|
+
|
|
437
447
|
const scrollContainer = getScrollContainer(ctx.rootElement);
|
|
438
|
-
resizeHandler =
|
|
448
|
+
resizeHandler = throttledApply;
|
|
439
449
|
window.addEventListener('resize', resizeHandler);
|
|
440
450
|
scrollContainerTarget = scrollContainer;
|
|
441
451
|
scrollContainer.addEventListener('scroll', resizeHandler);
|
|
@@ -15,6 +15,34 @@ import {
|
|
|
15
15
|
KTDataTableProviderResult,
|
|
16
16
|
KTDataTableStateStore,
|
|
17
17
|
} from './datatable-contracts';
|
|
18
|
+
import { resolveColumns } from './datatable-column-utils';
|
|
19
|
+
import { stripHtml } from './datatable-utils';
|
|
20
|
+
|
|
21
|
+
type FilterMatcher = (cellValue: unknown, filterValue: unknown) => boolean;
|
|
22
|
+
|
|
23
|
+
const FILTER_MATCHERS: Record<string, FilterMatcher> = {
|
|
24
|
+
text: (cellValue, filterValue) => {
|
|
25
|
+
if (!filterValue) return true;
|
|
26
|
+
return stripHtml(cellValue)
|
|
27
|
+
.toLowerCase()
|
|
28
|
+
.includes(String(filterValue).toLowerCase());
|
|
29
|
+
},
|
|
30
|
+
numeric: (cellValue, filterValue) => {
|
|
31
|
+
const num = parseFloat(
|
|
32
|
+
String(cellValue ?? '').replace(/[^0-9.-]/g, ''),
|
|
33
|
+
);
|
|
34
|
+
return !Number.isNaN(num) && num === filterValue;
|
|
35
|
+
},
|
|
36
|
+
dateRange: (cellValue, filterValue) => {
|
|
37
|
+
const range = filterValue as { from?: string; to?: string };
|
|
38
|
+
if (!range?.from && !range?.to) return true;
|
|
39
|
+
const cellDate = new Date(String(cellValue ?? ''));
|
|
40
|
+
if (Number.isNaN(cellDate.getTime())) return false;
|
|
41
|
+
if (range.from && cellDate < new Date(range.from)) return false;
|
|
42
|
+
if (range.to && cellDate > new Date(range.to)) return false;
|
|
43
|
+
return true;
|
|
44
|
+
},
|
|
45
|
+
};
|
|
18
46
|
|
|
19
47
|
interface KTDataTableLocalProviderOptions {
|
|
20
48
|
config: KTDataTableConfigInterface;
|
|
@@ -76,6 +104,18 @@ export class KTDataTableLocalDataProvider<
|
|
|
76
104
|
}
|
|
77
105
|
}
|
|
78
106
|
|
|
107
|
+
// Apply column filters
|
|
108
|
+
const { filters } = this.options.stateStore.getState();
|
|
109
|
+
if (filters && filters.length > 0) {
|
|
110
|
+
filteredData = data = data.filter((item: T) => {
|
|
111
|
+
return filters.every((filter) => {
|
|
112
|
+
const cellValue = item[filter.column as keyof T];
|
|
113
|
+
const matcher = FILTER_MATCHERS[filter.type];
|
|
114
|
+
return matcher ? matcher(cellValue, filter.value) : true;
|
|
115
|
+
});
|
|
116
|
+
}) as T[];
|
|
117
|
+
}
|
|
118
|
+
|
|
79
119
|
const sortCallback = this.options.config.sort?.callback;
|
|
80
120
|
if (
|
|
81
121
|
sortField !== undefined &&
|
|
@@ -110,6 +150,30 @@ export class KTDataTableLocalDataProvider<
|
|
|
110
150
|
);
|
|
111
151
|
|
|
112
152
|
if (this.options.stateStore.getState()._contentChecksum !== checksum) {
|
|
153
|
+
const domRowCount =
|
|
154
|
+
tbodyElement.querySelectorAll<HTMLTableRowElement>('tr').length;
|
|
155
|
+
const storedRowCount =
|
|
156
|
+
this.options.stateStore.getState().originalData?.length ?? 0;
|
|
157
|
+
|
|
158
|
+
// Programmatic dataset with an empty tbody must not re-import from the DOM.
|
|
159
|
+
if (domRowCount === 0 && storedRowCount > 0) {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// After pagination redraw the tbody only holds the current page. A checksum
|
|
164
|
+
// mismatch there must not shrink originalData to pageSize rows.
|
|
165
|
+
if (
|
|
166
|
+
storedRowCount > 0 &&
|
|
167
|
+
domRowCount > 0 &&
|
|
168
|
+
domRowCount < storedRowCount
|
|
169
|
+
) {
|
|
170
|
+
const { pageSize } = this.options.stateStore.getState();
|
|
171
|
+
if (domRowCount <= pageSize) {
|
|
172
|
+
this.options.stateStore.patchState({ _contentChecksum: checksum });
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
113
177
|
this.options.stateStore.patchState({ _contentChecksum: checksum });
|
|
114
178
|
return true;
|
|
115
179
|
}
|
|
@@ -120,10 +184,14 @@ export class KTDataTableLocalDataProvider<
|
|
|
120
184
|
private tableConfigInvalidate(): boolean {
|
|
121
185
|
const { _state, ...restConfig } = this.options.config;
|
|
122
186
|
const checksum: string = KTUtils.checksum(JSON.stringify(restConfig));
|
|
187
|
+
const previousChecksum = _state?._configChecksum ?? '';
|
|
123
188
|
|
|
124
|
-
if (
|
|
189
|
+
if (previousChecksum !== checksum) {
|
|
125
190
|
this.options.stateStore.patchState({ _configChecksum: checksum });
|
|
126
|
-
|
|
191
|
+
// First load skips this check (originalData === undefined). On the first
|
|
192
|
+
// pagination fetch, previousChecksum is still empty — record it but do
|
|
193
|
+
// not re-extract from the paginated DOM (that would shrink originalData).
|
|
194
|
+
return previousChecksum !== '';
|
|
127
195
|
}
|
|
128
196
|
|
|
129
197
|
return false;
|
|
@@ -140,15 +208,7 @@ export class KTDataTableLocalDataProvider<
|
|
|
140
208
|
this.options.storeOriginalClasses();
|
|
141
209
|
|
|
142
210
|
const rows = tbodyElement.querySelectorAll<HTMLTableRowElement>('tr');
|
|
143
|
-
const
|
|
144
|
-
? theadElement.querySelectorAll('th')
|
|
145
|
-
: ([] as unknown as NodeListOf<HTMLTableCellElement>);
|
|
146
|
-
|
|
147
|
-
const ths: HTMLTableCellElement[] = Array.from(allThs).filter((th) =>
|
|
148
|
-
th.hasAttribute('data-kt-datatable-column'),
|
|
149
|
-
);
|
|
150
|
-
const columnsByIndex: HTMLTableCellElement[] =
|
|
151
|
-
ths.length > 0 && ths.length !== allThs.length ? Array.from(allThs) : ths;
|
|
211
|
+
const { columnsByIndex } = resolveColumns(theadElement);
|
|
152
212
|
|
|
153
213
|
rows.forEach((row: HTMLTableRowElement) => {
|
|
154
214
|
const dataRow: T = {} as T;
|
|
@@ -177,25 +237,28 @@ export class KTDataTableLocalDataProvider<
|
|
|
177
237
|
|
|
178
238
|
private localTableHeaderInvalidate(): boolean {
|
|
179
239
|
const { originalData } = this.options.stateStore.getState();
|
|
240
|
+
if (!originalData?.length) {
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
|
|
180
244
|
const { theadElement } = this.options.elements();
|
|
245
|
+
const { typedThs } = resolveColumns(theadElement);
|
|
181
246
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
247
|
+
if (typedThs.length === 0) {
|
|
248
|
+
return (
|
|
249
|
+
this.options.getLogicalColumnCount() !==
|
|
250
|
+
Object.keys(originalData[0]).length
|
|
251
|
+
);
|
|
252
|
+
}
|
|
185
253
|
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
:
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
? allThs.length
|
|
196
|
-
: thsWithColumn.length
|
|
197
|
-
: this.options.getLogicalColumnCount();
|
|
198
|
-
|
|
199
|
-
return currentTableHeaders !== totalColumns;
|
|
254
|
+
const typedColumnNames = typedThs
|
|
255
|
+
.map((th) => th.getAttribute('data-kt-datatable-column'))
|
|
256
|
+
.filter((name): name is string => Boolean(name));
|
|
257
|
+
const dataColumnKeys = Object.keys(originalData[0]);
|
|
258
|
+
const matchingTypedColumns = typedColumnNames.filter((name) =>
|
|
259
|
+
dataColumnKeys.includes(name),
|
|
260
|
+
).length;
|
|
261
|
+
|
|
262
|
+
return typedColumnNames.length !== matchingTypedColumns;
|
|
200
263
|
}
|
|
201
264
|
}
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
KTDataTablePaginationRenderer,
|
|
9
9
|
KTDataTablePaginationRendererInput,
|
|
10
10
|
} from './datatable-contracts';
|
|
11
|
+
import { DEFAULT_PAGE_MORE_LIMIT, DEFAULT_PAGE_SIZES } from './datatable-defaults';
|
|
11
12
|
|
|
12
13
|
export class KTDataTableDomPaginationRenderer implements KTDataTablePaginationRenderer {
|
|
13
14
|
public render(
|
|
@@ -51,7 +52,7 @@ export class KTDataTableDomPaginationRenderer implements KTDataTablePaginationRe
|
|
|
51
52
|
return;
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
const pageSizes = input.config.pageSizes ??
|
|
55
|
+
const pageSizes = input.config.pageSizes ?? DEFAULT_PAGE_SIZES;
|
|
55
56
|
const options = pageSizes.map((size: number) => {
|
|
56
57
|
const option = document.createElement('option') as HTMLOptionElement;
|
|
57
58
|
option.value = String(size);
|
|
@@ -117,7 +118,7 @@ export class KTDataTableDomPaginationRenderer implements KTDataTablePaginationRe
|
|
|
117
118
|
|
|
118
119
|
const { page: currentPage, totalPages } = input.state;
|
|
119
120
|
const { previous, next, number, more } = pagination;
|
|
120
|
-
const pageMoreLimit = input.config.pageMoreLimit ??
|
|
121
|
+
const pageMoreLimit = input.config.pageMoreLimit ?? DEFAULT_PAGE_MORE_LIMIT;
|
|
121
122
|
|
|
122
123
|
const createButton = (
|
|
123
124
|
text: string,
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KTUI - Free & Open-Source Tailwind UI Components by Keenthemes
|
|
3
|
+
* Copyright 2025 by Keenthemes Inc
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import KTData from '../../helpers/data';
|
|
7
|
+
|
|
8
|
+
type DataTableInstance = {
|
|
9
|
+
dispose(): void;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export interface KTDataTableRegistry<T extends DataTableInstance> {
|
|
13
|
+
register(element: HTMLElement, instance: T): void;
|
|
14
|
+
get(element: HTMLElement): T | undefined;
|
|
15
|
+
remove(element: HTMLElement): void;
|
|
16
|
+
clear(): void;
|
|
17
|
+
createAll(factory: (element: HTMLElement) => T): void;
|
|
18
|
+
reinit(factory: (element: HTMLElement) => T): void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function createDataTableRegistry<
|
|
22
|
+
T extends DataTableInstance,
|
|
23
|
+
>(): KTDataTableRegistry<T> {
|
|
24
|
+
type ElementWithInstance = HTMLElement & { instance?: T };
|
|
25
|
+
const instances = new Map<HTMLElement, T>();
|
|
26
|
+
|
|
27
|
+
function toElementWithInstance(element: HTMLElement): ElementWithInstance {
|
|
28
|
+
return element as ElementWithInstance;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function register(element: HTMLElement, instance: T): void {
|
|
32
|
+
instances.set(element, instance);
|
|
33
|
+
toElementWithInstance(element).instance = instance;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function get(element: HTMLElement): T | undefined {
|
|
37
|
+
return instances.get(element) ?? toElementWithInstance(element).instance;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function remove(element: HTMLElement): void {
|
|
41
|
+
instances.delete(element);
|
|
42
|
+
const el = toElementWithInstance(element);
|
|
43
|
+
if (el.instance) delete el.instance;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function clear(): void {
|
|
47
|
+
instances.clear();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function createAll(factory: (element: HTMLElement) => T): void {
|
|
51
|
+
if (typeof document === 'undefined') return;
|
|
52
|
+
const elements = document.querySelectorAll<HTMLElement>(
|
|
53
|
+
'[data-kt-datatable="true"]',
|
|
54
|
+
);
|
|
55
|
+
elements.forEach((element) => {
|
|
56
|
+
if (
|
|
57
|
+
element.hasAttribute('data-kt-datatable') &&
|
|
58
|
+
!element.classList.contains('datatable-initialized')
|
|
59
|
+
) {
|
|
60
|
+
const instance = factory(element);
|
|
61
|
+
register(element, instance);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function reinit(factory: (element: HTMLElement) => T): void {
|
|
67
|
+
if (typeof document === 'undefined') return;
|
|
68
|
+
const elements = document.querySelectorAll<HTMLElement>(
|
|
69
|
+
'[data-kt-datatable="true"]',
|
|
70
|
+
);
|
|
71
|
+
elements.forEach((element) => {
|
|
72
|
+
try {
|
|
73
|
+
const instance = get(element);
|
|
74
|
+
if (instance && typeof instance.dispose === 'function') {
|
|
75
|
+
instance.dispose();
|
|
76
|
+
}
|
|
77
|
+
KTData.remove(element, 'datatable');
|
|
78
|
+
element.removeAttribute('data-kt-datatable-initialized');
|
|
79
|
+
element.classList.remove('datatable-initialized');
|
|
80
|
+
} catch {
|
|
81
|
+
// ignore per-element errors
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
clear();
|
|
85
|
+
createAll(factory);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return { register, get, remove, clear, createAll, reinit };
|
|
89
|
+
}
|
|
@@ -61,7 +61,7 @@ export class KTDataTableRemoteDataProvider<
|
|
|
61
61
|
try {
|
|
62
62
|
responseData = await response.json();
|
|
63
63
|
} catch (error) {
|
|
64
|
-
this.options.eventAdapter.emit('
|
|
64
|
+
this.options.eventAdapter.emit('fetchError', {
|
|
65
65
|
response,
|
|
66
66
|
error: String(error),
|
|
67
67
|
status: response.status,
|
|
@@ -74,8 +74,6 @@ export class KTDataTableRemoteDataProvider<
|
|
|
74
74
|
return { data: [], totalItems: 0, skipped: true };
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
this.options.eventAdapter.emit('fetched', { response: responseData });
|
|
78
|
-
|
|
79
77
|
if (typeof this.options.config.mapResponse === 'function') {
|
|
80
78
|
responseData = this.options.config.mapResponse.call(this, responseData);
|
|
81
79
|
}
|