@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,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KTUI - Free & Open-Source Tailwind UI Components by Keenthemes
|
|
3
|
+
* Copyright 2025 by Keenthemes Inc
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Search binding utilities for KTDataTable.
|
|
8
|
+
* Manages debounced search input binding and cleanup.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
type SearchElementWithDebounce = HTMLInputElement & {
|
|
12
|
+
_debouncedSearch?: EventListener;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export interface KTDataTableSearchHandler {
|
|
16
|
+
attach(
|
|
17
|
+
tableId: string,
|
|
18
|
+
currentSearch: string | object | undefined,
|
|
19
|
+
delay: number,
|
|
20
|
+
onSearch: (query: string) => void,
|
|
21
|
+
): void;
|
|
22
|
+
detach(tableId: string): void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function createSearchHandler(): KTDataTableSearchHandler {
|
|
26
|
+
function findSearchElement(tableId: string): HTMLInputElement | null {
|
|
27
|
+
return document.querySelector<HTMLInputElement>(
|
|
28
|
+
`[data-kt-datatable-search="#${tableId}"]`,
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function asSearchElementWithDebounce(
|
|
33
|
+
element: HTMLInputElement,
|
|
34
|
+
): SearchElementWithDebounce {
|
|
35
|
+
return element as SearchElementWithDebounce;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function debounce<TArgs extends unknown[]>(
|
|
39
|
+
func: (...args: TArgs) => void,
|
|
40
|
+
wait: number,
|
|
41
|
+
): (...args: TArgs) => void {
|
|
42
|
+
let timeout: number | undefined;
|
|
43
|
+
return function (...args: TArgs) {
|
|
44
|
+
const later = () => {
|
|
45
|
+
clearTimeout(timeout);
|
|
46
|
+
func(...args);
|
|
47
|
+
};
|
|
48
|
+
clearTimeout(timeout);
|
|
49
|
+
timeout = window.setTimeout(later, wait);
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function attach(
|
|
54
|
+
tableId: string,
|
|
55
|
+
currentSearch: string | object | undefined,
|
|
56
|
+
delay: number,
|
|
57
|
+
onSearch: (query: string) => void,
|
|
58
|
+
): void {
|
|
59
|
+
const searchElement = findSearchElement(tableId);
|
|
60
|
+
if (!searchElement) return;
|
|
61
|
+
|
|
62
|
+
// Restore search value from state
|
|
63
|
+
if (currentSearch !== undefined && currentSearch !== null) {
|
|
64
|
+
searchElement.value =
|
|
65
|
+
typeof currentSearch === 'string'
|
|
66
|
+
? currentSearch
|
|
67
|
+
: String(currentSearch);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Remove existing debounced listener if any
|
|
71
|
+
const el = asSearchElementWithDebounce(searchElement);
|
|
72
|
+
if (el._debouncedSearch) {
|
|
73
|
+
searchElement.removeEventListener('keyup', el._debouncedSearch);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Create and attach new debounced search
|
|
77
|
+
const debouncedSearch = debounce(() => {
|
|
78
|
+
onSearch(searchElement.value);
|
|
79
|
+
}, delay);
|
|
80
|
+
|
|
81
|
+
el._debouncedSearch = debouncedSearch;
|
|
82
|
+
searchElement.addEventListener('keyup', debouncedSearch);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function detach(tableId: string): void {
|
|
86
|
+
const searchElement = findSearchElement(tableId);
|
|
87
|
+
if (!searchElement) return;
|
|
88
|
+
|
|
89
|
+
const el = asSearchElementWithDebounce(searchElement);
|
|
90
|
+
if (el._debouncedSearch) {
|
|
91
|
+
searchElement.removeEventListener('keyup', el._debouncedSearch);
|
|
92
|
+
delete el._debouncedSearch;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return { attach, detach };
|
|
97
|
+
}
|
|
@@ -10,6 +10,19 @@ import {
|
|
|
10
10
|
KTDataTableSortOrderInterface,
|
|
11
11
|
KTDataTableDataInterface,
|
|
12
12
|
} from './types';
|
|
13
|
+
import { stripHtml } from './datatable-utils';
|
|
14
|
+
|
|
15
|
+
export interface KTDataTableSortHandlerDeps<T> {
|
|
16
|
+
config: KTDataTableConfigInterface;
|
|
17
|
+
theadElement: HTMLTableSectionElement;
|
|
18
|
+
getState: () => {
|
|
19
|
+
sortField: keyof T | number;
|
|
20
|
+
sortOrder: KTDataTableSortOrderInterface;
|
|
21
|
+
};
|
|
22
|
+
setState: (field: keyof T | number, order: KTDataTableSortOrderInterface) => void;
|
|
23
|
+
emit: (eventName: string, eventData?: object) => void;
|
|
24
|
+
updateData: () => void;
|
|
25
|
+
}
|
|
13
26
|
|
|
14
27
|
export interface KTDataTableSortAPI<T = KTDataTableDataInterface> {
|
|
15
28
|
initSort(): void;
|
|
@@ -27,31 +40,42 @@ export interface KTDataTableSortAPI<T = KTDataTableDataInterface> {
|
|
|
27
40
|
sortField: keyof T,
|
|
28
41
|
sortOrder: KTDataTableSortOrderInterface,
|
|
29
42
|
): void;
|
|
43
|
+
dispose(): void;
|
|
30
44
|
}
|
|
31
45
|
|
|
32
|
-
export
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
46
|
+
export class KTDataTableSortHandler<T = KTDataTableDataInterface>
|
|
47
|
+
implements KTDataTableSortAPI<T>
|
|
48
|
+
{
|
|
49
|
+
private _config: KTDataTableConfigInterface;
|
|
50
|
+
private _theadElement: HTMLTableSectionElement;
|
|
51
|
+
private _getState: () => {
|
|
36
52
|
sortField: keyof T | number;
|
|
37
53
|
sortOrder: KTDataTableSortOrderInterface;
|
|
38
|
-
}
|
|
39
|
-
|
|
54
|
+
};
|
|
55
|
+
private _setState: (
|
|
40
56
|
field: keyof T | number,
|
|
41
57
|
order: KTDataTableSortOrderInterface,
|
|
42
|
-
) => void
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
58
|
+
) => void;
|
|
59
|
+
private _emit: (eventName: string, eventData?: object) => void;
|
|
60
|
+
private _updateData: () => void;
|
|
61
|
+
private _sortAbortController: AbortController | null = null;
|
|
62
|
+
|
|
63
|
+
constructor(deps: KTDataTableSortHandlerDeps<T>) {
|
|
64
|
+
this._config = deps.config;
|
|
65
|
+
this._theadElement = deps.theadElement;
|
|
66
|
+
this._getState = deps.getState;
|
|
67
|
+
this._setState = deps.setState;
|
|
68
|
+
this._emit = deps.emit;
|
|
69
|
+
this._updateData = deps.updateData;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private static _compareValues(
|
|
49
73
|
a: unknown,
|
|
50
74
|
b: unknown,
|
|
51
75
|
sortOrder: KTDataTableSortOrderInterface,
|
|
52
76
|
): number {
|
|
53
|
-
const aText =
|
|
54
|
-
const bText =
|
|
77
|
+
const aText = stripHtml(a);
|
|
78
|
+
const bText = stripHtml(b);
|
|
55
79
|
return aText > bText
|
|
56
80
|
? sortOrder === 'asc'
|
|
57
81
|
? 1
|
|
@@ -63,8 +87,7 @@ export function createSortHandler<T = KTDataTableDataInterface>(
|
|
|
63
87
|
: 0;
|
|
64
88
|
}
|
|
65
89
|
|
|
66
|
-
|
|
67
|
-
function parseNumeric(value: unknown): number {
|
|
90
|
+
private static _parseNumeric(value: unknown): number {
|
|
68
91
|
if (value === null || value === undefined || value === '') {
|
|
69
92
|
return Number.NaN;
|
|
70
93
|
}
|
|
@@ -73,8 +96,7 @@ export function createSortHandler<T = KTDataTableDataInterface>(
|
|
|
73
96
|
return Number.isNaN(n) ? Number.NaN : n;
|
|
74
97
|
}
|
|
75
98
|
|
|
76
|
-
|
|
77
|
-
function compareNumeric(
|
|
99
|
+
private static _compareNumeric(
|
|
78
100
|
aNum: number,
|
|
79
101
|
bNum: number,
|
|
80
102
|
sortOrder: KTDataTableSortOrderInterface,
|
|
@@ -89,7 +111,7 @@ export function createSortHandler<T = KTDataTableDataInterface>(
|
|
|
89
111
|
return 0;
|
|
90
112
|
}
|
|
91
113
|
|
|
92
|
-
|
|
114
|
+
private _getColumnDef(sortField: keyof T | number):
|
|
93
115
|
| {
|
|
94
116
|
sortType?: 'string' | 'numeric';
|
|
95
117
|
sortValue?: (
|
|
@@ -100,7 +122,7 @@ export function createSortHandler<T = KTDataTableDataInterface>(
|
|
|
100
122
|
) => number | string;
|
|
101
123
|
}
|
|
102
124
|
| undefined {
|
|
103
|
-
const columns =
|
|
125
|
+
const columns = this._config.columns;
|
|
104
126
|
if (!columns) return undefined;
|
|
105
127
|
const key =
|
|
106
128
|
typeof sortField === 'number'
|
|
@@ -109,15 +131,25 @@ export function createSortHandler<T = KTDataTableDataInterface>(
|
|
|
109
131
|
return key !== undefined ? columns[key as string] : undefined;
|
|
110
132
|
}
|
|
111
133
|
|
|
112
|
-
|
|
134
|
+
public sortData(
|
|
113
135
|
data: T[],
|
|
114
136
|
sortField: keyof T | number,
|
|
115
137
|
sortOrder: KTDataTableSortOrderInterface,
|
|
116
138
|
): T[] {
|
|
117
|
-
const columnDef =
|
|
139
|
+
const columnDef = this._getColumnDef(sortField);
|
|
118
140
|
const sortValueFn = columnDef?.sortValue;
|
|
119
141
|
const useNumeric = !sortValueFn && columnDef?.sortType === 'numeric';
|
|
120
142
|
|
|
143
|
+
// Pre-strip HTML from cell values once (instead of on every comparison).
|
|
144
|
+
// For N rows this runs N regex replacements instead of N*log(N).
|
|
145
|
+
const strippedCache = new Map<T, string>();
|
|
146
|
+
|
|
147
|
+
if (!sortValueFn) {
|
|
148
|
+
for (const item of data) {
|
|
149
|
+
strippedCache.set(item, stripHtml(item[sortField as keyof T]));
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
121
153
|
return data.sort((a, b) => {
|
|
122
154
|
const aRaw = a[sortField as keyof T] as unknown;
|
|
123
155
|
const bRaw = b[sortField as keyof T] as unknown;
|
|
@@ -135,31 +167,33 @@ export function createSortHandler<T = KTDataTableDataInterface>(
|
|
|
135
167
|
| string,
|
|
136
168
|
b as KTDataTableDataInterface,
|
|
137
169
|
);
|
|
138
|
-
const aNum =
|
|
139
|
-
|
|
170
|
+
const aNum =
|
|
171
|
+
typeof aVal === 'number'
|
|
172
|
+
? aVal
|
|
173
|
+
: KTDataTableSortHandler._parseNumeric(aVal);
|
|
174
|
+
const bNum =
|
|
175
|
+
typeof bVal === 'number'
|
|
176
|
+
? bVal
|
|
177
|
+
: KTDataTableSortHandler._parseNumeric(bVal);
|
|
140
178
|
if (typeof aVal === 'number' && typeof bVal === 'number') {
|
|
141
|
-
return
|
|
179
|
+
return KTDataTableSortHandler._compareNumeric(aNum, bNum, sortOrder);
|
|
142
180
|
}
|
|
143
|
-
return
|
|
181
|
+
return KTDataTableSortHandler._compareValues(aVal, bVal, sortOrder);
|
|
144
182
|
}
|
|
145
183
|
if (useNumeric) {
|
|
146
|
-
const aNum =
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
| string,
|
|
150
|
-
);
|
|
151
|
-
const bNum = parseNumeric(
|
|
152
|
-
bRaw as
|
|
153
|
-
| KTDataTableDataInterface[keyof KTDataTableDataInterface]
|
|
154
|
-
| string,
|
|
155
|
-
);
|
|
156
|
-
return compareNumeric(aNum, bNum, sortOrder);
|
|
184
|
+
const aNum = KTDataTableSortHandler._parseNumeric(strippedCache.get(a) ?? aRaw);
|
|
185
|
+
const bNum = KTDataTableSortHandler._parseNumeric(strippedCache.get(b) ?? bRaw);
|
|
186
|
+
return KTDataTableSortHandler._compareNumeric(aNum, bNum, sortOrder);
|
|
157
187
|
}
|
|
158
|
-
return
|
|
188
|
+
return KTDataTableSortHandler._compareValues(
|
|
189
|
+
strippedCache.get(a) ?? aRaw,
|
|
190
|
+
strippedCache.get(b) ?? bRaw,
|
|
191
|
+
sortOrder,
|
|
192
|
+
);
|
|
159
193
|
});
|
|
160
194
|
}
|
|
161
195
|
|
|
162
|
-
|
|
196
|
+
public toggleSortOrder(
|
|
163
197
|
currentField: keyof T | number,
|
|
164
198
|
currentOrder: KTDataTableSortOrderInterface,
|
|
165
199
|
newField: keyof T | number,
|
|
@@ -177,18 +211,17 @@ export function createSortHandler<T = KTDataTableDataInterface>(
|
|
|
177
211
|
return 'asc';
|
|
178
212
|
}
|
|
179
213
|
|
|
180
|
-
|
|
214
|
+
public setSortIcon(
|
|
181
215
|
sortField: keyof T,
|
|
182
216
|
sortOrder: KTDataTableSortOrderInterface,
|
|
183
217
|
): void {
|
|
184
|
-
const baseClass =
|
|
218
|
+
const baseClass = this._config.sort?.classes?.base || '';
|
|
185
219
|
const sortClass = sortOrder
|
|
186
220
|
? sortOrder === 'asc'
|
|
187
|
-
?
|
|
188
|
-
:
|
|
221
|
+
? this._config.sort?.classes?.asc || ''
|
|
222
|
+
: this._config.sort?.classes?.desc || ''
|
|
189
223
|
: '';
|
|
190
|
-
|
|
191
|
-
const allTh = theadElement.querySelectorAll('th');
|
|
224
|
+
const allTh = this._theadElement.querySelectorAll('th');
|
|
192
225
|
allTh.forEach((header) => {
|
|
193
226
|
const el = header as HTMLElement;
|
|
194
227
|
el.setAttribute('aria-sort', 'none');
|
|
@@ -197,11 +230,10 @@ export function createSortHandler<T = KTDataTableDataInterface>(
|
|
|
197
230
|
sortElement.className = baseClass;
|
|
198
231
|
}
|
|
199
232
|
});
|
|
200
|
-
// Apply sort state to the active column so table.css [aria-sort='asc'] / [aria-sort='desc'] can highlight the arrow
|
|
201
233
|
const th =
|
|
202
234
|
typeof sortField === 'number'
|
|
203
235
|
? allTh[sortField]
|
|
204
|
-
: (
|
|
236
|
+
: (this._theadElement.querySelector(
|
|
205
237
|
`th[data-kt-datatable-column="${String(sortField)}"], th[data-kt-datatable-column-sort="${String(sortField)}"]`,
|
|
206
238
|
) as HTMLElement);
|
|
207
239
|
if (th) {
|
|
@@ -217,17 +249,25 @@ export function createSortHandler<T = KTDataTableDataInterface>(
|
|
|
217
249
|
}
|
|
218
250
|
}
|
|
219
251
|
|
|
220
|
-
|
|
221
|
-
if (!
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
252
|
+
public initSort(): void {
|
|
253
|
+
if (!this._theadElement) return;
|
|
254
|
+
|
|
255
|
+
// Abort previous sort listeners before attaching new ones
|
|
256
|
+
if (this._sortAbortController) {
|
|
257
|
+
this._sortAbortController.abort();
|
|
258
|
+
}
|
|
259
|
+
this._sortAbortController = new AbortController();
|
|
260
|
+
const signal = this._sortAbortController.signal;
|
|
261
|
+
|
|
262
|
+
this.setSortIcon(
|
|
263
|
+
this._getState().sortField as keyof T,
|
|
264
|
+
this._getState().sortOrder,
|
|
265
|
+
);
|
|
266
|
+
const headers = Array.from(this._theadElement.querySelectorAll('th'));
|
|
226
267
|
headers.forEach((header) => {
|
|
227
|
-
|
|
228
|
-
|
|
268
|
+
if (!header.querySelector(`.${this._config.sort?.classes?.base}`))
|
|
269
|
+
return;
|
|
229
270
|
|
|
230
|
-
// Check if sorting is disabled for this column
|
|
231
271
|
const sortDisabled =
|
|
232
272
|
header.getAttribute('data-kt-datatable-column-sort') === 'false';
|
|
233
273
|
if (sortDisabled) return;
|
|
@@ -239,20 +279,25 @@ export function createSortHandler<T = KTDataTableDataInterface>(
|
|
|
239
279
|
? (sortAttribute as keyof T)
|
|
240
280
|
: (header.cellIndex as keyof T);
|
|
241
281
|
header.addEventListener('click', () => {
|
|
242
|
-
const state =
|
|
243
|
-
const sortOrder = toggleSortOrder(
|
|
282
|
+
const state = this._getState();
|
|
283
|
+
const sortOrder = this.toggleSortOrder(
|
|
244
284
|
state.sortField,
|
|
245
285
|
state.sortOrder,
|
|
246
286
|
sortField,
|
|
247
287
|
);
|
|
248
|
-
setSortIcon(sortField, sortOrder);
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
});
|
|
288
|
+
this.setSortIcon(sortField, sortOrder);
|
|
289
|
+
this._setState(sortField, sortOrder);
|
|
290
|
+
this._emit('sort', { field: sortField, order: sortOrder });
|
|
291
|
+
this._updateData();
|
|
292
|
+
}, { signal });
|
|
254
293
|
});
|
|
255
294
|
}
|
|
256
295
|
|
|
257
|
-
|
|
296
|
+
public dispose(): void {
|
|
297
|
+
if (this._sortAbortController) {
|
|
298
|
+
this._sortAbortController.abort();
|
|
299
|
+
this._sortAbortController = null;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
258
302
|
}
|
|
303
|
+
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KTUI - Free & Open-Source Tailwind UI Components by Keenthemes
|
|
3
|
+
* Copyright 2025 by Keenthemes Inc
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface KTDataTableSpinner {
|
|
7
|
+
show(
|
|
8
|
+
root: HTMLElement | null,
|
|
9
|
+
config: {
|
|
10
|
+
attributes?: { spinner?: string };
|
|
11
|
+
loadingClass?: string;
|
|
12
|
+
loading?: { template: string; content: string };
|
|
13
|
+
},
|
|
14
|
+
tableElement: HTMLTableElement,
|
|
15
|
+
): void;
|
|
16
|
+
hide(
|
|
17
|
+
root: HTMLElement | null,
|
|
18
|
+
config: {
|
|
19
|
+
attributes?: { spinner?: string };
|
|
20
|
+
loadingClass?: string;
|
|
21
|
+
},
|
|
22
|
+
): void;
|
|
23
|
+
remove(
|
|
24
|
+
root: HTMLElement | null,
|
|
25
|
+
config: {
|
|
26
|
+
attributes?: { spinner?: string };
|
|
27
|
+
loadingClass?: string;
|
|
28
|
+
},
|
|
29
|
+
): void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function createSpinner(): KTDataTableSpinner {
|
|
33
|
+
function findSpinner(
|
|
34
|
+
root: HTMLElement | null,
|
|
35
|
+
spinnerSel: string | undefined,
|
|
36
|
+
): HTMLElement | null {
|
|
37
|
+
return root && spinnerSel
|
|
38
|
+
? root.querySelector<HTMLElement>(spinnerSel)
|
|
39
|
+
: null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function createSpinnerElement(
|
|
43
|
+
tableElement: HTMLTableElement,
|
|
44
|
+
loading: { template: string; content: string },
|
|
45
|
+
): HTMLElement | null {
|
|
46
|
+
const template = document.createElement('template');
|
|
47
|
+
template.innerHTML = loading.template
|
|
48
|
+
.trim()
|
|
49
|
+
.replace('{content}', loading.content);
|
|
50
|
+
const first = template.content.firstChild;
|
|
51
|
+
if (!first || !(first instanceof HTMLElement)) return null;
|
|
52
|
+
const spinner = first;
|
|
53
|
+
spinner.setAttribute('data-kt-datatable-spinner', 'true');
|
|
54
|
+
tableElement.appendChild(spinner);
|
|
55
|
+
return spinner;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function show(
|
|
59
|
+
root: HTMLElement | null,
|
|
60
|
+
config: {
|
|
61
|
+
attributes?: { spinner?: string };
|
|
62
|
+
loadingClass?: string;
|
|
63
|
+
loading?: { template: string; content: string };
|
|
64
|
+
},
|
|
65
|
+
tableElement: HTMLTableElement,
|
|
66
|
+
): void {
|
|
67
|
+
const spinnerSel = config.attributes?.spinner;
|
|
68
|
+
const fromDom = findSpinner(root, spinnerSel);
|
|
69
|
+
const spinner =
|
|
70
|
+
fromDom ??
|
|
71
|
+
(config.loading
|
|
72
|
+
? createSpinnerElement(tableElement, config.loading)
|
|
73
|
+
: null);
|
|
74
|
+
if (spinner) spinner.style.display = 'block';
|
|
75
|
+
root?.classList.add(config.loadingClass ?? 'loading');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function hide(
|
|
79
|
+
root: HTMLElement | null,
|
|
80
|
+
config: {
|
|
81
|
+
attributes?: { spinner?: string };
|
|
82
|
+
loadingClass?: string;
|
|
83
|
+
},
|
|
84
|
+
): void {
|
|
85
|
+
const spinner = findSpinner(root, config.attributes?.spinner);
|
|
86
|
+
if (spinner) spinner.style.display = 'none';
|
|
87
|
+
root?.classList.remove(config.loadingClass ?? 'loading');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function remove(
|
|
91
|
+
root: HTMLElement | null,
|
|
92
|
+
config: {
|
|
93
|
+
attributes?: { spinner?: string };
|
|
94
|
+
loadingClass?: string;
|
|
95
|
+
},
|
|
96
|
+
): void {
|
|
97
|
+
const spinner = findSpinner(root, config.attributes?.spinner);
|
|
98
|
+
if (spinner?.parentNode) spinner.parentNode.removeChild(spinner);
|
|
99
|
+
root?.classList.remove(config.loadingClass ?? 'loading');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return { show, hide, remove };
|
|
103
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KTUI - Free & Open-Source Tailwind UI Components by Keenthemes
|
|
3
|
+
* Copyright 2025 by Keenthemes Inc
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { KTDataTableStateInterface } from './types';
|
|
7
|
+
|
|
8
|
+
export interface KTDataTableStatePersistence {
|
|
9
|
+
save(namespace: string, state: KTDataTableStateInterface): void;
|
|
10
|
+
load(namespace: string): KTDataTableStateInterface | null;
|
|
11
|
+
remove(namespace: string): void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function createStatePersistence(): KTDataTableStatePersistence {
|
|
15
|
+
function save(namespace: string, state: KTDataTableStateInterface): void {
|
|
16
|
+
if (namespace) {
|
|
17
|
+
try {
|
|
18
|
+
localStorage.setItem(namespace, JSON.stringify(state));
|
|
19
|
+
} catch {
|
|
20
|
+
// localStorage unavailable (e.g. Node.js without --localstorage-file)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function load(namespace: string): KTDataTableStateInterface | null {
|
|
26
|
+
try {
|
|
27
|
+
const stateString = localStorage.getItem(namespace);
|
|
28
|
+
if (!stateString) return null;
|
|
29
|
+
|
|
30
|
+
return JSON.parse(stateString) as KTDataTableStateInterface;
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function remove(namespace: string): void {
|
|
37
|
+
if (namespace) {
|
|
38
|
+
try {
|
|
39
|
+
localStorage.removeItem(namespace);
|
|
40
|
+
} catch {
|
|
41
|
+
// localStorage unavailable
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return { save, load, remove };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Resolve the namespace for a datatable state key.
|
|
51
|
+
* Priority: config.stateNamespace > table element ID > root element ID > fallback name.
|
|
52
|
+
*/
|
|
53
|
+
export function resolveTableNamespace(
|
|
54
|
+
config: { stateNamespace?: string },
|
|
55
|
+
tableElement: HTMLTableElement | null,
|
|
56
|
+
rootElement: HTMLElement | null,
|
|
57
|
+
fallbackName: string,
|
|
58
|
+
): string {
|
|
59
|
+
if (config.stateNamespace) {
|
|
60
|
+
return config.stateNamespace;
|
|
61
|
+
}
|
|
62
|
+
const tableIdAttr = tableElement?.getAttribute('id');
|
|
63
|
+
if (tableIdAttr) return tableIdAttr;
|
|
64
|
+
const rootIdAttr = rootElement?.getAttribute('id');
|
|
65
|
+
if (rootIdAttr) return rootIdAttr;
|
|
66
|
+
return fallbackName;
|
|
67
|
+
}
|