@keenthemes/ktui 1.1.5 → 1.1.6

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 (98) hide show
  1. package/dist/ktui.js +11232 -11095
  2. package/dist/ktui.min.js +1 -1
  3. package/dist/ktui.min.js.map +1 -1
  4. package/dist/styles.css +33 -27
  5. package/lib/cjs/components/collapse/collapse.js +0 -2
  6. package/lib/cjs/components/collapse/collapse.js.map +1 -1
  7. package/lib/cjs/components/component.js +3 -1
  8. package/lib/cjs/components/component.js.map +1 -1
  9. package/lib/cjs/components/datatable/datatable-sort.js +1 -2
  10. package/lib/cjs/components/datatable/datatable-sort.js.map +1 -1
  11. package/lib/cjs/components/datatable/datatable.js +45 -23
  12. package/lib/cjs/components/datatable/datatable.js.map +1 -1
  13. package/lib/cjs/components/drawer/drawer.js +21 -9
  14. package/lib/cjs/components/drawer/drawer.js.map +1 -1
  15. package/lib/cjs/components/dropdown/dropdown.js.map +1 -1
  16. package/lib/cjs/components/scrollto/scrollto.js +0 -2
  17. package/lib/cjs/components/scrollto/scrollto.js.map +1 -1
  18. package/lib/cjs/components/select/combobox.js.map +1 -1
  19. package/lib/cjs/components/select/dropdown.js.map +1 -1
  20. package/lib/cjs/components/select/remote.js.map +1 -1
  21. package/lib/cjs/components/select/search.js +9 -5
  22. package/lib/cjs/components/select/search.js.map +1 -1
  23. package/lib/cjs/components/select/select.js +22 -5
  24. package/lib/cjs/components/select/select.js.map +1 -1
  25. package/lib/cjs/components/select/tags.js.map +1 -1
  26. package/lib/cjs/components/select/templates.js.map +1 -1
  27. package/lib/cjs/components/select/utils.js +10 -0
  28. package/lib/cjs/components/select/utils.js.map +1 -1
  29. package/lib/cjs/components/sticky/sticky.js +104 -24
  30. package/lib/cjs/components/sticky/sticky.js.map +1 -1
  31. package/lib/cjs/components/theme-switch/theme-switch.js +0 -2
  32. package/lib/cjs/components/theme-switch/theme-switch.js.map +1 -1
  33. package/lib/cjs/components/toast/toast.js +1 -2
  34. package/lib/cjs/components/toast/toast.js.map +1 -1
  35. package/lib/cjs/helpers/dom.js +0 -2
  36. package/lib/cjs/helpers/dom.js.map +1 -1
  37. package/lib/esm/components/collapse/collapse.js +0 -2
  38. package/lib/esm/components/collapse/collapse.js.map +1 -1
  39. package/lib/esm/components/component.js +3 -1
  40. package/lib/esm/components/component.js.map +1 -1
  41. package/lib/esm/components/datatable/datatable-sort.js +1 -2
  42. package/lib/esm/components/datatable/datatable-sort.js.map +1 -1
  43. package/lib/esm/components/datatable/datatable.js +45 -23
  44. package/lib/esm/components/datatable/datatable.js.map +1 -1
  45. package/lib/esm/components/drawer/drawer.js +21 -9
  46. package/lib/esm/components/drawer/drawer.js.map +1 -1
  47. package/lib/esm/components/dropdown/dropdown.js.map +1 -1
  48. package/lib/esm/components/scrollto/scrollto.js +0 -2
  49. package/lib/esm/components/scrollto/scrollto.js.map +1 -1
  50. package/lib/esm/components/select/combobox.js.map +1 -1
  51. package/lib/esm/components/select/dropdown.js.map +1 -1
  52. package/lib/esm/components/select/remote.js.map +1 -1
  53. package/lib/esm/components/select/search.js +9 -5
  54. package/lib/esm/components/select/search.js.map +1 -1
  55. package/lib/esm/components/select/select.js +22 -5
  56. package/lib/esm/components/select/select.js.map +1 -1
  57. package/lib/esm/components/select/tags.js.map +1 -1
  58. package/lib/esm/components/select/templates.js.map +1 -1
  59. package/lib/esm/components/select/utils.js +10 -0
  60. package/lib/esm/components/select/utils.js.map +1 -1
  61. package/lib/esm/components/sticky/sticky.js +104 -24
  62. package/lib/esm/components/sticky/sticky.js.map +1 -1
  63. package/lib/esm/components/theme-switch/theme-switch.js +0 -2
  64. package/lib/esm/components/theme-switch/theme-switch.js.map +1 -1
  65. package/lib/esm/components/toast/toast.js +1 -2
  66. package/lib/esm/components/toast/toast.js.map +1 -1
  67. package/lib/esm/helpers/dom.js +0 -2
  68. package/lib/esm/helpers/dom.js.map +1 -1
  69. package/package.json +14 -7
  70. package/src/components/collapse/collapse.ts +0 -3
  71. package/src/components/component.ts +5 -5
  72. package/src/components/datatable/__tests__/currency-sort.test.ts +108 -0
  73. package/src/components/datatable/__tests__/multi-row-headers.test.ts +121 -0
  74. package/src/components/datatable/__tests__/pagination-reset.test.ts +13 -5
  75. package/src/components/datatable/__tests__/race-conditions.test.ts +138 -78
  76. package/src/components/datatable/__tests__/setup.ts +9 -4
  77. package/src/components/datatable/datatable-sort.ts +12 -16
  78. package/src/components/datatable/datatable.css +4 -4
  79. package/src/components/datatable/datatable.ts +56 -26
  80. package/src/components/datatable/types.ts +3 -1
  81. package/src/components/drawer/drawer.ts +61 -24
  82. package/src/components/dropdown/dropdown.ts +3 -1
  83. package/src/components/scrollto/scrollto.ts +0 -3
  84. package/src/components/select/__tests__/ux-behaviors.test.ts +274 -8
  85. package/src/components/select/combobox.ts +0 -1
  86. package/src/components/select/dropdown.ts +0 -2
  87. package/src/components/select/remote.ts +1 -6
  88. package/src/components/select/search.ts +14 -7
  89. package/src/components/select/select.ts +29 -29
  90. package/src/components/select/tags.ts +0 -1
  91. package/src/components/select/templates.ts +8 -8
  92. package/src/components/select/utils.ts +15 -2
  93. package/src/components/sticky/__tests__/sticky.test.ts +205 -0
  94. package/src/components/sticky/sticky.ts +119 -21
  95. package/src/components/sticky/types.ts +3 -0
  96. package/src/components/theme-switch/theme-switch.ts +0 -3
  97. package/src/components/toast/toast.ts +3 -2
  98. package/src/helpers/dom.ts +0 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keenthemes/ktui",
3
- "version": "1.1.5",
3
+ "version": "1.1.6",
4
4
  "description": "Free & Open-Source Tailwind UI Components by Keenthemes",
5
5
  "homepage": "https://ktui.io",
6
6
  "repository": {
@@ -79,7 +79,7 @@
79
79
  "build:css": "npx @tailwindcss/cli -i ./styles/main.css -o ./dist/styles.css --watch",
80
80
  "build:css:once": "npx @tailwindcss/cli -i ./styles/main.css -o ./dist/styles.css",
81
81
  "dev": "concurrently \"npm run build:webpack:watch\" \"npm run build:css\"",
82
- "lint": "npx eslint 'src/**/*.ts' --max-warnings 100",
82
+ "lint": "npx eslint 'src/**/*.ts' --max-warnings 110",
83
83
  "lint:fix": "npx eslint 'src/**/*.ts' --fix",
84
84
  "format": "npx prettier --write 'src/**/*.ts'",
85
85
  "format:check": "npx prettier --check 'src/**/*.ts'",
@@ -98,24 +98,31 @@
98
98
  "@vitest/ui": "^4.0.16",
99
99
  "autoprefixer": "^10.4.21",
100
100
  "babel-loader": "^10.0.0",
101
- "clean-webpack-plugin": "^4.0.0",
102
101
  "concurrently": "^9.1.2",
103
- "eslint": "^9.27.0",
104
- "eslint-plugin-prettier": "^5.5.4",
102
+ "eslint": "^10.0.0",
103
+ "eslint-plugin-prettier": "^5.4.0",
105
104
  "jsdom": "^25.0.1",
106
105
  "mini-svg-data-uri": "^1.4.4",
107
106
  "prettier": "^3.5.3",
108
107
  "source-map-loader": "^5.0.0",
109
108
  "tailwindcss": "^4.1.11",
110
- "terser-webpack-plugin": "^5.3.14",
109
+ "terser-webpack-plugin": "^5.3.17",
111
110
  "ts-loader": "^9.5.2",
112
111
  "typescript": "^5.8.3",
112
+ "typescript-eslint": "^8.18.0",
113
113
  "vitest": "^4.0.16",
114
114
  "webpack": "^5.99.9",
115
115
  "webpack-cli": "^6.0.1",
116
- "webpack-merge-and-include-globally": "^2.3.4"
116
+ "webpack-merge-and-include-globally": "^1.0.7"
117
117
  },
118
118
  "dependencies": {
119
119
  "@popperjs/core": "^2.11.8"
120
+ },
121
+ "overrides": {
122
+ "ajv": "^8.0.0",
123
+ "minimatch": "^10.2.3",
124
+ "rollup": "^4.59.0",
125
+ "serialize-javascript": "^7.0.3",
126
+ "terser-webpack-plugin": "^5.3.17"
120
127
  }
121
128
  }
@@ -3,9 +3,6 @@
3
3
  * Copyright 2025 by Keenthemes Inc
4
4
  */
5
5
 
6
- /* eslint-disable max-len */
7
- /* eslint-disable require-jsdoc */
8
-
9
6
  import KTData from '../../helpers/data';
10
7
  import KTDom from '../../helpers/dom';
11
8
  import KTComponent from '../component';
@@ -3,10 +3,6 @@
3
3
  * Copyright 2025 by Keenthemes Inc
4
4
  */
5
5
 
6
- /* eslint-disable guard-for-in */
7
- /* eslint-disable max-len */
8
- /* eslint-disable require-jsdoc */
9
-
10
6
  declare global {
11
7
  interface Window {
12
8
  KTGlobalComponentsConfig: object;
@@ -144,7 +140,11 @@ export default class KTComponent {
144
140
  * already-initialized instance so handlers that hold a reference to _config see updates.
145
141
  */
146
142
  protected _mergeConfig(config: object): void {
147
- if (config && typeof config === 'object' && Object.keys(config).length > 0) {
143
+ if (
144
+ config &&
145
+ typeof config === 'object' &&
146
+ Object.keys(config).length > 0
147
+ ) {
148
148
  Object.assign(this._config, config);
149
149
  }
150
150
  }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * currency-sort.test.ts
3
+ * Tests that price/currency columns sort by numeric value, not lexicographically.
4
+ *
5
+ * Spec: fix-datatable-sort-arrow-and-numeric-sort
6
+ * Requirement: Numeric and Custom Column Sort Configuration
7
+ */
8
+
9
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
10
+ import { createSortHandler } from '../datatable-sort';
11
+
12
+ describe('KTDataTable - Currency/numeric sort', () => {
13
+ let thead: HTMLTableSectionElement;
14
+ const noop = vi.fn();
15
+
16
+ beforeEach(() => {
17
+ thead = document.createElement('thead');
18
+ const tr = document.createElement('tr');
19
+ const th = document.createElement('th');
20
+ th.setAttribute('data-kt-datatable-column', 'price');
21
+ tr.appendChild(th);
22
+ thead.appendChild(tr);
23
+ });
24
+
25
+ it('sorts currency column by numeric value ascending (e.g. £5, £20, £123)', () => {
26
+ const config = {
27
+ columns: {
28
+ price: { sortType: 'numeric' as const },
29
+ },
30
+ };
31
+ const handler = createSortHandler(
32
+ config as any,
33
+ thead,
34
+ () => ({ sortField: null, sortOrder: '' }),
35
+ noop,
36
+ noop,
37
+ noop,
38
+ noop,
39
+ );
40
+
41
+ const data = [
42
+ { price: '£123' },
43
+ { price: '£20' },
44
+ { price: '£5' },
45
+ { price: '£9.99' },
46
+ ];
47
+ const sorted = handler.sortData(data, 'price', 'asc');
48
+
49
+ // Must be numeric order: 5, 9.99, 20, 123 (not lexicographic £123, £20, £5)
50
+ const values = sorted.map((row) => row.price);
51
+ expect(values).toEqual(['£5', '£9.99', '£20', '£123']);
52
+ });
53
+
54
+ it('sorts currency column by numeric value descending', () => {
55
+ const config = {
56
+ columns: {
57
+ price: { sortType: 'numeric' as const },
58
+ },
59
+ };
60
+ const handler = createSortHandler(
61
+ config as any,
62
+ thead,
63
+ () => ({ sortField: null, sortOrder: '' }),
64
+ noop,
65
+ noop,
66
+ noop,
67
+ noop,
68
+ );
69
+
70
+ const data = [
71
+ { price: '£5' },
72
+ { price: '£20' },
73
+ { price: '£123' },
74
+ ];
75
+ const sorted = handler.sortData(data, 'price', 'desc');
76
+
77
+ const numericOrder = sorted.map((row) =>
78
+ parseFloat(String(row.price).replace(/[^0-9.-]/g, '')),
79
+ );
80
+ expect(numericOrder).toEqual([123, 20, 5]);
81
+ });
82
+
83
+ it('without sortType numeric, sorts lexicographically (e.g. £123 before £20)', () => {
84
+ const config = { columns: {} };
85
+ const handler = createSortHandler(
86
+ config as any,
87
+ thead,
88
+ () => ({ sortField: null, sortOrder: '' }),
89
+ noop,
90
+ noop,
91
+ noop,
92
+ noop,
93
+ );
94
+
95
+ const data = [
96
+ { price: '£123' },
97
+ { price: '£20' },
98
+ { price: '£5' },
99
+ ];
100
+ const sorted = handler.sortData(data, 'price', 'asc');
101
+
102
+ // String sort: "£123" < "£20" < "£5" (1 < 2 < 5)
103
+ const values = sorted.map((row) => row.price);
104
+ expect(values[0]).toBe('£123');
105
+ expect(values[1]).toBe('£20');
106
+ expect(values[2]).toBe('£5');
107
+ });
108
+ });
@@ -0,0 +1,121 @@
1
+ /**
2
+ * multi-row-headers.test.ts
3
+ * Tests for datatable column count with multi-row (grouped) headers.
4
+ *
5
+ * Spec: openspec/changes/fix-datatable-multi-row-header-column-count
6
+ * Requirement: Multi-Row (Grouped) Header Column Count
7
+ */
8
+
9
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
10
+ import { KTDataTable } from '../datatable';
11
+
12
+ describe('KTDataTable - Multi-row header column count', () => {
13
+ let container: HTMLElement;
14
+ let tableElement: HTMLTableElement;
15
+
16
+ /**
17
+ * Create a table with multi-row thead (no data-kt-datatable-column) and 17 data columns.
18
+ * Row 1: Person (rowspan=2), Backlog (colspan=3), Floater2 (colspan=3), Floater1 (colspan=3), CL2025 (colspan=3), LWP (colspan=3), Action (rowspan=2) = 7 th
19
+ * Row 2: 15 leaf th (Assigned, Used, Balance × 5)
20
+ * Total 22 th in DOM but only 17 data columns.
21
+ */
22
+ const createMultiRowHeaderTable = (bodyRowCount: number = 2) => {
23
+ container = document.createElement('div');
24
+ container.id = 'kt_datatable_multirow';
25
+ container.setAttribute('data-kt-datatable', 'true');
26
+
27
+ tableElement = document.createElement('table');
28
+ tableElement.setAttribute('data-kt-datatable-table', 'true');
29
+
30
+ const thead = document.createElement('thead');
31
+ const row1 = document.createElement('tr');
32
+ row1.innerHTML = `
33
+ <th rowspan="2">Person</th>
34
+ <th colspan="3">Backlog</th>
35
+ <th colspan="3">Floater (2) 2025</th>
36
+ <th colspan="3">Floater (1) 2025</th>
37
+ <th colspan="3">CL2025</th>
38
+ <th colspan="3">LWP</th>
39
+ <th rowspan="2">Action</th>
40
+ `;
41
+ thead.appendChild(row1);
42
+
43
+ const row2 = document.createElement('tr');
44
+ row2.innerHTML = `
45
+ <th>Assigned</th><th>Used</th><th>Balance</th>
46
+ <th>Assigned</th><th>Used</th><th>Balance</th>
47
+ <th>Assigned</th><th>Used</th><th>Balance</th>
48
+ <th>Assigned</th><th>Used</th><th>Balance</th>
49
+ <th>Assigned</th><th>Used</th><th>Balance</th>
50
+ `;
51
+ thead.appendChild(row2);
52
+ tableElement.appendChild(thead);
53
+
54
+ const tbody = document.createElement('tbody');
55
+ const tdCount = 17;
56
+ for (let r = 0; r < bodyRowCount; r++) {
57
+ const tr = document.createElement('tr');
58
+ for (let c = 0; c < tdCount; c++) {
59
+ const td = document.createElement('td');
60
+ td.textContent = c === 0 ? `Person ${r + 1}` : String(c);
61
+ tr.appendChild(td);
62
+ }
63
+ tbody.appendChild(tr);
64
+ }
65
+ tableElement.appendChild(tbody);
66
+
67
+ const wrapper = document.createElement('div');
68
+ wrapper.appendChild(tableElement);
69
+
70
+ const infoElement = document.createElement('span');
71
+ infoElement.setAttribute('data-kt-datatable-info', 'true');
72
+ const sizeElement = document.createElement('select');
73
+ sizeElement.setAttribute('data-kt-datatable-size', 'true');
74
+ const paginationElement = document.createElement('div');
75
+ paginationElement.setAttribute('data-kt-datatable-pagination', 'true');
76
+
77
+ container.appendChild(wrapper);
78
+ container.appendChild(infoElement);
79
+ container.appendChild(sizeElement);
80
+ container.appendChild(paginationElement);
81
+ document.body.appendChild(container);
82
+
83
+ return { container, tableElement, tbody };
84
+ };
85
+
86
+ beforeEach(() => {
87
+ vi.useFakeTimers();
88
+ });
89
+
90
+ it('should render exactly 17 columns when thead has multi-row headers and no data-kt-datatable-column', async () => {
91
+ createMultiRowHeaderTable(2);
92
+ const datatable = new KTDataTable(container, { stateSave: false });
93
+ await vi.runAllTimersAsync();
94
+
95
+ const tbody = tableElement.tBodies[0];
96
+ expect(tbody).toBeDefined();
97
+ const rows = tbody.querySelectorAll('tr');
98
+ // Two data rows
99
+ expect(rows.length).toBe(2);
100
+ rows.forEach((row) => {
101
+ const cells = row.querySelectorAll('td');
102
+ expect(cells.length).toBe(17);
103
+ });
104
+ });
105
+
106
+ it('should use logical column count for empty-state row colspan', async () => {
107
+ createMultiRowHeaderTable(0);
108
+ const tbody = tableElement.querySelector('tbody');
109
+ expect(tbody).toBeDefined();
110
+ const datatable = new KTDataTable(container, { stateSave: false });
111
+ await vi.runAllTimersAsync();
112
+
113
+ const noticeRow = tableElement.tBodies[0].querySelector('tr');
114
+ expect(noticeRow).toBeDefined();
115
+ const cell = noticeRow?.querySelector('td');
116
+ expect(cell).toBeDefined();
117
+ // Should span 17 (logical columns from originalData) or 1 if no data; after extract we have 0 rows so logicalCount could be 0 -> we use 1
118
+ // With 0 body rows we never have originalData, so _getLogicalColumnCount() returns first tbody row td count (0) or 0; we set colspan to 1
119
+ expect(cell!.colSpan).toBeGreaterThanOrEqual(1);
120
+ });
121
+ });
@@ -184,7 +184,9 @@ describe('KTDataTable - Pagination Reset', () => {
184
184
  // String search
185
185
  return data.filter((item: any) =>
186
186
  Object.values(item).some((value: any) =>
187
- String(value).toLowerCase().includes((search as string).toLowerCase()),
187
+ String(value)
188
+ .toLowerCase()
189
+ .includes((search as string).toLowerCase()),
188
190
  ),
189
191
  );
190
192
  },
@@ -326,7 +328,11 @@ describe('KTDataTable - Pagination Reset', () => {
326
328
  expect(datatable.getState().page).toBe(1);
327
329
 
328
330
  // Replace filter on same column
329
- datatable.setFilter({ column: 'status', type: 'text', value: 'inactive' });
331
+ datatable.setFilter({
332
+ column: 'status',
333
+ type: 'text',
334
+ value: 'inactive',
335
+ });
330
336
  expect(datatable.getState().page).toBe(1);
331
337
 
332
338
  // Should only have one filter for 'status' column
@@ -498,7 +504,7 @@ describe('KTDataTable - Pagination Reset', () => {
498
504
  const namespace = 'test-datatable-restore';
499
505
 
500
506
  // First instance
501
- let table1 = new KTDataTable(container, {
507
+ const table1 = new KTDataTable(container, {
502
508
  pageSize: 10,
503
509
  stateSave: true,
504
510
  stateNamespace: namespace,
@@ -637,7 +643,10 @@ describe('KTDataTable - Pagination Reset', () => {
637
643
 
638
644
  it('should not break existing event handlers', async () => {
639
645
  const { container } = createMockDataTable(25);
640
- datatable = new KTDataTable(container, { pageSize: 10, stateSave: false });
646
+ datatable = new KTDataTable(container, {
647
+ pageSize: 10,
648
+ stateSave: false,
649
+ });
641
650
 
642
651
  const reloadSpy = vi.fn();
643
652
  // Listen for 'reload' event directly (CustomEvent)
@@ -654,4 +663,3 @@ describe('KTDataTable - Pagination Reset', () => {
654
663
  });
655
664
  });
656
665
  });
657
-