@keenthemes/ktui 1.1.0 → 1.1.1
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 +0 -27
- package/dist/ktui.js +6790 -14063
- package/dist/ktui.min.js +1 -1
- package/dist/ktui.min.js.map +1 -1
- package/dist/styles.css +1132 -2705
- package/lib/cjs/components/datatable/__tests__/pagination-reset.test.js +596 -0
- package/lib/cjs/components/datatable/__tests__/pagination-reset.test.js.map +1 -0
- package/lib/cjs/components/datatable/__tests__/race-conditions.test.js +548 -0
- package/lib/cjs/components/datatable/__tests__/race-conditions.test.js.map +1 -0
- package/lib/cjs/components/datatable/__tests__/setup.js +63 -0
- package/lib/cjs/components/datatable/__tests__/setup.js.map +1 -0
- package/lib/cjs/components/datatable/datatable.js +92 -30
- package/lib/cjs/components/datatable/datatable.js.map +1 -1
- package/lib/cjs/index.js +1 -10
- package/lib/cjs/index.js.map +1 -1
- package/lib/esm/components/datatable/__tests__/pagination-reset.test.js +594 -0
- package/lib/esm/components/datatable/__tests__/pagination-reset.test.js.map +1 -0
- package/lib/esm/components/datatable/__tests__/race-conditions.test.js +546 -0
- package/lib/esm/components/datatable/__tests__/race-conditions.test.js.map +1 -0
- package/lib/esm/components/datatable/__tests__/setup.js +58 -0
- package/lib/esm/components/datatable/__tests__/setup.js.map +1 -0
- package/lib/esm/components/datatable/datatable.js +92 -30
- package/lib/esm/components/datatable/datatable.js.map +1 -1
- package/lib/esm/index.js +0 -7
- package/lib/esm/index.js.map +1 -1
- package/package.json +7 -9
- package/src/components/alert/alert.css +188 -429
- package/src/components/datatable/__tests__/pagination-reset.test.ts +657 -0
- package/src/components/datatable/__tests__/race-conditions.test.ts +455 -0
- package/src/components/datatable/__tests__/setup.ts +67 -0
- package/src/components/datatable/datatable.ts +66 -11
- package/src/components/input/input.css +0 -1
- package/src/components/select/select.css +0 -1
- package/src/components/select/variants.css +4 -0
- package/src/components/textarea/textarea.css +0 -1
- package/src/index.ts +0 -10
- package/styles.css +0 -1
- package/lib/cjs/components/alert/alert.js +0 -1025
- package/lib/cjs/components/alert/alert.js.map +0 -1
- package/lib/cjs/components/alert/index.js +0 -20
- package/lib/cjs/components/alert/index.js.map +0 -1
- package/lib/cjs/components/alert/templates.js +0 -120
- package/lib/cjs/components/alert/templates.js.map +0 -1
- package/lib/cjs/components/alert/types.js +0 -7
- package/lib/cjs/components/alert/types.js.map +0 -1
- package/lib/cjs/components/datepicker/config/config.js +0 -42
- package/lib/cjs/components/datepicker/config/config.js.map +0 -1
- package/lib/cjs/components/datepicker/config/index.js +0 -24
- package/lib/cjs/components/datepicker/config/index.js.map +0 -1
- package/lib/cjs/components/datepicker/config/interfaces.js +0 -7
- package/lib/cjs/components/datepicker/config/interfaces.js.map +0 -1
- package/lib/cjs/components/datepicker/config/types.js +0 -7
- package/lib/cjs/components/datepicker/config/types.js.map +0 -1
- package/lib/cjs/components/datepicker/core/event-manager.js +0 -135
- package/lib/cjs/components/datepicker/core/event-manager.js.map +0 -1
- package/lib/cjs/components/datepicker/core/focus-manager.js +0 -167
- package/lib/cjs/components/datepicker/core/focus-manager.js.map +0 -1
- package/lib/cjs/components/datepicker/core/helpers.js +0 -219
- package/lib/cjs/components/datepicker/core/helpers.js.map +0 -1
- package/lib/cjs/components/datepicker/core/index.js +0 -25
- package/lib/cjs/components/datepicker/core/index.js.map +0 -1
- package/lib/cjs/components/datepicker/core/unified-state-manager.js +0 -394
- package/lib/cjs/components/datepicker/core/unified-state-manager.js.map +0 -1
- package/lib/cjs/components/datepicker/datepicker.js +0 -2252
- package/lib/cjs/components/datepicker/datepicker.js.map +0 -1
- package/lib/cjs/components/datepicker/index.js +0 -24
- package/lib/cjs/components/datepicker/index.js.map +0 -1
- package/lib/cjs/components/datepicker/ui/index.js +0 -23
- package/lib/cjs/components/datepicker/ui/index.js.map +0 -1
- package/lib/cjs/components/datepicker/ui/input/dropdown.js +0 -489
- package/lib/cjs/components/datepicker/ui/input/dropdown.js.map +0 -1
- package/lib/cjs/components/datepicker/ui/input/index.js +0 -23
- package/lib/cjs/components/datepicker/ui/input/index.js.map +0 -1
- package/lib/cjs/components/datepicker/ui/input/segmented-input.js +0 -640
- package/lib/cjs/components/datepicker/ui/input/segmented-input.js.map +0 -1
- package/lib/cjs/components/datepicker/ui/renderers/calendar.js +0 -446
- package/lib/cjs/components/datepicker/ui/renderers/calendar.js.map +0 -1
- package/lib/cjs/components/datepicker/ui/renderers/footer.js +0 -42
- package/lib/cjs/components/datepicker/ui/renderers/footer.js.map +0 -1
- package/lib/cjs/components/datepicker/ui/renderers/header.js +0 -32
- package/lib/cjs/components/datepicker/ui/renderers/header.js.map +0 -1
- package/lib/cjs/components/datepicker/ui/renderers/index.js +0 -25
- package/lib/cjs/components/datepicker/ui/renderers/index.js.map +0 -1
- package/lib/cjs/components/datepicker/ui/renderers/time-picker.js +0 -384
- package/lib/cjs/components/datepicker/ui/renderers/time-picker.js.map +0 -1
- package/lib/cjs/components/datepicker/ui/templates/index.js +0 -22
- package/lib/cjs/components/datepicker/ui/templates/index.js.map +0 -1
- package/lib/cjs/components/datepicker/ui/templates/templates.js +0 -253
- package/lib/cjs/components/datepicker/ui/templates/templates.js.map +0 -1
- package/lib/cjs/components/datepicker/utils/date-formatters.js +0 -88
- package/lib/cjs/components/datepicker/utils/date-formatters.js.map +0 -1
- package/lib/cjs/components/datepicker/utils/date-utils.js +0 -194
- package/lib/cjs/components/datepicker/utils/date-utils.js.map +0 -1
- package/lib/cjs/components/datepicker/utils/index.js +0 -24
- package/lib/cjs/components/datepicker/utils/index.js.map +0 -1
- package/lib/cjs/components/datepicker/utils/time-utils.js +0 -213
- package/lib/cjs/components/datepicker/utils/time-utils.js.map +0 -1
- package/lib/esm/components/alert/alert.js +0 -1022
- package/lib/esm/components/alert/alert.js.map +0 -1
- package/lib/esm/components/alert/index.js +0 -4
- package/lib/esm/components/alert/index.js.map +0 -1
- package/lib/esm/components/alert/templates.js +0 -112
- package/lib/esm/components/alert/templates.js.map +0 -1
- package/lib/esm/components/alert/types.js +0 -6
- package/lib/esm/components/alert/types.js.map +0 -1
- package/lib/esm/components/datepicker/config/config.js +0 -39
- package/lib/esm/components/datepicker/config/config.js.map +0 -1
- package/lib/esm/components/datepicker/config/index.js +0 -8
- package/lib/esm/components/datepicker/config/index.js.map +0 -1
- package/lib/esm/components/datepicker/config/interfaces.js +0 -6
- package/lib/esm/components/datepicker/config/interfaces.js.map +0 -1
- package/lib/esm/components/datepicker/config/types.js +0 -6
- package/lib/esm/components/datepicker/config/types.js.map +0 -1
- package/lib/esm/components/datepicker/core/event-manager.js +0 -133
- package/lib/esm/components/datepicker/core/event-manager.js.map +0 -1
- package/lib/esm/components/datepicker/core/focus-manager.js +0 -164
- package/lib/esm/components/datepicker/core/focus-manager.js.map +0 -1
- package/lib/esm/components/datepicker/core/helpers.js +0 -211
- package/lib/esm/components/datepicker/core/helpers.js.map +0 -1
- package/lib/esm/components/datepicker/core/index.js +0 -9
- package/lib/esm/components/datepicker/core/index.js.map +0 -1
- package/lib/esm/components/datepicker/core/unified-state-manager.js +0 -391
- package/lib/esm/components/datepicker/core/unified-state-manager.js.map +0 -1
- package/lib/esm/components/datepicker/datepicker.js +0 -2248
- package/lib/esm/components/datepicker/datepicker.js.map +0 -1
- package/lib/esm/components/datepicker/index.js +0 -7
- package/lib/esm/components/datepicker/index.js.map +0 -1
- package/lib/esm/components/datepicker/ui/index.js +0 -7
- package/lib/esm/components/datepicker/ui/index.js.map +0 -1
- package/lib/esm/components/datepicker/ui/input/dropdown.js +0 -486
- package/lib/esm/components/datepicker/ui/input/dropdown.js.map +0 -1
- package/lib/esm/components/datepicker/ui/input/index.js +0 -7
- package/lib/esm/components/datepicker/ui/input/index.js.map +0 -1
- package/lib/esm/components/datepicker/ui/input/segmented-input.js +0 -637
- package/lib/esm/components/datepicker/ui/input/segmented-input.js.map +0 -1
- package/lib/esm/components/datepicker/ui/renderers/calendar.js +0 -443
- package/lib/esm/components/datepicker/ui/renderers/calendar.js.map +0 -1
- package/lib/esm/components/datepicker/ui/renderers/footer.js +0 -39
- package/lib/esm/components/datepicker/ui/renderers/footer.js.map +0 -1
- package/lib/esm/components/datepicker/ui/renderers/header.js +0 -29
- package/lib/esm/components/datepicker/ui/renderers/header.js.map +0 -1
- package/lib/esm/components/datepicker/ui/renderers/index.js +0 -9
- package/lib/esm/components/datepicker/ui/renderers/index.js.map +0 -1
- package/lib/esm/components/datepicker/ui/renderers/time-picker.js +0 -381
- package/lib/esm/components/datepicker/ui/renderers/time-picker.js.map +0 -1
- package/lib/esm/components/datepicker/ui/templates/index.js +0 -6
- package/lib/esm/components/datepicker/ui/templates/index.js.map +0 -1
- package/lib/esm/components/datepicker/ui/templates/templates.js +0 -242
- package/lib/esm/components/datepicker/ui/templates/templates.js.map +0 -1
- package/lib/esm/components/datepicker/utils/date-formatters.js +0 -83
- package/lib/esm/components/datepicker/utils/date-formatters.js.map +0 -1
- package/lib/esm/components/datepicker/utils/date-utils.js +0 -184
- package/lib/esm/components/datepicker/utils/date-utils.js.map +0 -1
- package/lib/esm/components/datepicker/utils/index.js +0 -8
- package/lib/esm/components/datepicker/utils/index.js.map +0 -1
- package/lib/esm/components/datepicker/utils/time-utils.js +0 -201
- package/lib/esm/components/datepicker/utils/time-utils.js.map +0 -1
- package/src/components/alert/alert.ts +0 -990
- package/src/components/alert/index.ts +0 -4
- package/src/components/alert/templates.ts +0 -110
- package/src/components/alert/tests/accessibility/aria-roles.test.ts +0 -19
- package/src/components/alert/tests/accessibility/focus-management.test.ts +0 -19
- package/src/components/alert/tests/accessibility/keyboard-nav.test.ts +0 -22
- package/src/components/alert/tests/actions/confirm-cancel.test.ts +0 -122
- package/src/components/alert/tests/actions/input-field.test.ts +0 -180
- package/src/components/alert/tests/alert.basic.test.ts +0 -126
- package/src/components/alert/tests/alert.config.test.ts +0 -75
- package/src/components/alert/tests/alert.templates.test.ts +0 -17
- package/src/components/alert/tests/config/attribute-config.test.ts +0 -94
- package/src/components/alert/tests/config/json-config.test.ts +0 -119
- package/src/components/alert/tests/config/merging.test.ts +0 -89
- package/src/components/alert/tests/dismissal/auto-dismiss.test.ts +0 -96
- package/src/components/alert/tests/dismissal/escape-key-dismiss.test.ts +0 -105
- package/src/components/alert/tests/dismissal/manual-dismiss.test.ts +0 -90
- package/src/components/alert/tests/dismissal/outside-click-dismiss.test.ts +0 -91
- package/src/components/alert/tests/edge-cases/invalid-config.test.ts +0 -19
- package/src/components/alert/tests/edge-cases/multiple-alerts.test.ts +0 -19
- package/src/components/alert/tests/rendering/custom-content.test.ts +0 -81
- package/src/components/alert/tests/rendering/info-alert.test.ts +0 -84
- package/src/components/alert/tests/rendering/success-alert.test.ts +0 -100
- package/src/components/alert/tests/templates/default-templates.test.ts +0 -16
- package/src/components/alert/tests/templates/user-templates.test.ts +0 -16
- package/src/components/alert/types.ts +0 -145
- package/src/components/datepicker/__tests__/datepicker-events.test.ts +0 -356
- package/src/components/datepicker/__tests__/datepicker-init.test.ts +0 -343
- package/src/components/datepicker/__tests__/datepicker-integration.test.ts +0 -435
- package/src/components/datepicker/__tests__/datepicker-timezone.test.ts +0 -220
- package/src/components/datepicker/__tests__/segmented-input-focus.test.ts +0 -380
- package/src/components/datepicker/__tests__/selective-state-updates.test.ts +0 -400
- package/src/components/datepicker/__tests__/state-manager.test.ts +0 -421
- package/src/components/datepicker/__tests__/time-preservation.test.ts +0 -387
- package/src/components/datepicker/config/config.ts +0 -40
- package/src/components/datepicker/config/index.ts +0 -8
- package/src/components/datepicker/config/interfaces.ts +0 -82
- package/src/components/datepicker/config/types.ts +0 -188
- package/src/components/datepicker/core/event-manager.ts +0 -159
- package/src/components/datepicker/core/focus-manager.ts +0 -201
- package/src/components/datepicker/core/helpers.ts +0 -231
- package/src/components/datepicker/core/index.ts +0 -9
- package/src/components/datepicker/core/unified-state-manager.ts +0 -459
- package/src/components/datepicker/datepicker.css +0 -435
- package/src/components/datepicker/datepicker.ts +0 -2548
- package/src/components/datepicker/index.ts +0 -8
- package/src/components/datepicker/ui/index.ts +0 -7
- package/src/components/datepicker/ui/input/dropdown.ts +0 -552
- package/src/components/datepicker/ui/input/index.ts +0 -7
- package/src/components/datepicker/ui/input/segmented-input.ts +0 -638
- package/src/components/datepicker/ui/renderers/__tests__/calendar-optimizations.test.ts +0 -611
- package/src/components/datepicker/ui/renderers/calendar.ts +0 -530
- package/src/components/datepicker/ui/renderers/footer.ts +0 -43
- package/src/components/datepicker/ui/renderers/header.ts +0 -33
- package/src/components/datepicker/ui/renderers/index.ts +0 -9
- package/src/components/datepicker/ui/renderers/time-picker.ts +0 -438
- package/src/components/datepicker/ui/templates/index.ts +0 -6
- package/src/components/datepicker/ui/templates/templates.ts +0 -306
- package/src/components/datepicker/utils/__tests__/date-formatters.test.ts +0 -160
- package/src/components/datepicker/utils/__tests__/date-utils-keys.test.ts +0 -86
- package/src/components/datepicker/utils/__tests__/date-utils-timezone.test.ts +0 -215
- package/src/components/datepicker/utils/date-formatters.ts +0 -85
- package/src/components/datepicker/utils/date-utils.ts +0 -172
- package/src/components/datepicker/utils/index.ts +0 -8
- package/src/components/datepicker/utils/time-utils.ts +0 -221
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Race Condition Tests for KTDataTable
|
|
3
|
+
* Tests the fixes for concurrent request handling, request cancellation, and stale response detection
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
7
|
+
import { KTDataTable } from '../datatable';
|
|
8
|
+
import { waitFor } from './setup';
|
|
9
|
+
|
|
10
|
+
describe('KTDataTable Race Condition Fixes', () => {
|
|
11
|
+
let container: HTMLElement;
|
|
12
|
+
let mockFetch: ReturnType<typeof vi.fn>;
|
|
13
|
+
let abortSignals: AbortSignal[] = [];
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
// Setup DOM
|
|
17
|
+
container = document.createElement('div');
|
|
18
|
+
container.innerHTML = `
|
|
19
|
+
<div data-kt-datatable="true">
|
|
20
|
+
<table data-kt-datatable-table="true">
|
|
21
|
+
<thead>
|
|
22
|
+
<tr>
|
|
23
|
+
<th data-kt-datatable-column="id">ID</th>
|
|
24
|
+
<th data-kt-datatable-column="name">Name</th>
|
|
25
|
+
</tr>
|
|
26
|
+
</thead>
|
|
27
|
+
<tbody></tbody>
|
|
28
|
+
</table>
|
|
29
|
+
<div data-kt-datatable-info="true"></div>
|
|
30
|
+
<select data-kt-datatable-size="true"></select>
|
|
31
|
+
<div data-kt-datatable-pagination="true"></div>
|
|
32
|
+
</div>
|
|
33
|
+
`;
|
|
34
|
+
document.body.appendChild(container);
|
|
35
|
+
|
|
36
|
+
// Mock fetch to track requests and signals
|
|
37
|
+
abortSignals = [];
|
|
38
|
+
mockFetch = vi.fn((url, options) => {
|
|
39
|
+
// Store abort signal for verification
|
|
40
|
+
if (options?.signal) {
|
|
41
|
+
abortSignals.push(options.signal);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Simulate network delay
|
|
45
|
+
return new Promise((resolve, reject) => {
|
|
46
|
+
const timeout = setTimeout(() => {
|
|
47
|
+
if (options?.signal?.aborted) {
|
|
48
|
+
reject(new DOMException('The operation was aborted.', 'AbortError'));
|
|
49
|
+
} else {
|
|
50
|
+
resolve(
|
|
51
|
+
new Response(
|
|
52
|
+
JSON.stringify({
|
|
53
|
+
data: [
|
|
54
|
+
{ id: 1, name: 'Item 1' },
|
|
55
|
+
{ id: 2, name: 'Item 2' },
|
|
56
|
+
],
|
|
57
|
+
totalCount: 2,
|
|
58
|
+
}),
|
|
59
|
+
{ status: 200, headers: { 'Content-Type': 'application/json' } },
|
|
60
|
+
),
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
}, 100); // 100ms delay
|
|
64
|
+
|
|
65
|
+
// Handle abort
|
|
66
|
+
if (options?.signal) {
|
|
67
|
+
options.signal.addEventListener('abort', () => {
|
|
68
|
+
clearTimeout(timeout);
|
|
69
|
+
reject(new DOMException('The operation was aborted.', 'AbortError'));
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
global.fetch = mockFetch;
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
afterEach(() => {
|
|
79
|
+
document.body.removeChild(container);
|
|
80
|
+
vi.clearAllMocks();
|
|
81
|
+
abortSignals = [];
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe('AbortController Integration', () => {
|
|
85
|
+
it('should create AbortController for remote data requests', async () => {
|
|
86
|
+
const datatable = new KTDataTable(container.querySelector('[data-kt-datatable="true"]')!, {
|
|
87
|
+
apiEndpoint: '/api/data',
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
await waitFor(150);
|
|
91
|
+
|
|
92
|
+
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
93
|
+
expect(abortSignals.length).toBe(1);
|
|
94
|
+
expect(abortSignals[0]).toBeInstanceOf(AbortSignal);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should use _isFetching flag to prevent concurrent requests', async () => {
|
|
98
|
+
const datatable = new KTDataTable(container.querySelector('[data-kt-datatable="true"]')!, {
|
|
99
|
+
apiEndpoint: '/api/data',
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Try to trigger search during initial fetch
|
|
103
|
+
datatable.search('test'); // Should be blocked by _isFetching
|
|
104
|
+
|
|
105
|
+
await waitFor(150);
|
|
106
|
+
|
|
107
|
+
// Should only have 1 request (initial) because _isFetching blocked the second
|
|
108
|
+
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should allow new request after previous completes', async () => {
|
|
112
|
+
const datatable = new KTDataTable(container.querySelector('[data-kt-datatable="true"]')!, {
|
|
113
|
+
apiEndpoint: '/api/data',
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// Wait for initial fetch to complete
|
|
117
|
+
await waitFor(150);
|
|
118
|
+
|
|
119
|
+
// Now trigger a search - should succeed
|
|
120
|
+
datatable.search('test');
|
|
121
|
+
await waitFor(150);
|
|
122
|
+
|
|
123
|
+
// Should have 2 requests total
|
|
124
|
+
expect(mockFetch).toHaveBeenCalledTimes(2);
|
|
125
|
+
expect(abortSignals.length).toBe(2);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should abort previous request when _performFetchRequest is called again', async () => {
|
|
129
|
+
// This tests the AbortController logic directly by making multiple sequential requests
|
|
130
|
+
const datatable = new KTDataTable(container.querySelector('[data-kt-datatable="true"]')!, {
|
|
131
|
+
apiEndpoint: '/api/data',
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
await waitFor(150); // Complete initial request
|
|
135
|
+
|
|
136
|
+
// Trigger first search
|
|
137
|
+
datatable.search('first');
|
|
138
|
+
await waitFor(150);
|
|
139
|
+
|
|
140
|
+
// Trigger second search (first should complete, second starts fresh)
|
|
141
|
+
datatable.search('second');
|
|
142
|
+
await waitFor(150);
|
|
143
|
+
|
|
144
|
+
// Should have 3 requests: initial + first search + second search
|
|
145
|
+
expect(mockFetch).toHaveBeenCalledTimes(3);
|
|
146
|
+
expect(abortSignals.length).toBe(3);
|
|
147
|
+
|
|
148
|
+
// Each gets its own AbortController
|
|
149
|
+
abortSignals.forEach((signal) => {
|
|
150
|
+
expect(signal).toBeInstanceOf(AbortSignal);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
describe('Request ID Sequencing', () => {
|
|
156
|
+
it('should assign incremental request IDs for sequential requests', async () => {
|
|
157
|
+
let requestIds: number[] = [];
|
|
158
|
+
let callCount = 0;
|
|
159
|
+
|
|
160
|
+
// Mock to capture request sequence
|
|
161
|
+
mockFetch.mockImplementation((url, options) => {
|
|
162
|
+
callCount++;
|
|
163
|
+
const id = callCount;
|
|
164
|
+
requestIds.push(id);
|
|
165
|
+
|
|
166
|
+
return new Promise((resolve) => {
|
|
167
|
+
setTimeout(() => {
|
|
168
|
+
resolve(
|
|
169
|
+
new Response(
|
|
170
|
+
JSON.stringify({
|
|
171
|
+
data: [{ id: id, name: `Item ${id}` }],
|
|
172
|
+
totalCount: 1,
|
|
173
|
+
}),
|
|
174
|
+
{ status: 200 },
|
|
175
|
+
),
|
|
176
|
+
);
|
|
177
|
+
}, 50);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const datatable = new KTDataTable(container.querySelector('[data-kt-datatable="true"]')!, {
|
|
182
|
+
apiEndpoint: '/api/data',
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
await waitFor(100); // Complete initial request
|
|
186
|
+
|
|
187
|
+
datatable.search('a');
|
|
188
|
+
await waitFor(100); // Complete search
|
|
189
|
+
|
|
190
|
+
datatable.search('b');
|
|
191
|
+
await waitFor(100); // Complete second search
|
|
192
|
+
|
|
193
|
+
// Request IDs should be sequential: 1, 2, 3
|
|
194
|
+
expect(requestIds).toEqual([1, 2, 3]);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('should have request ID validation logic in place', async () => {
|
|
198
|
+
// This tests that request IDs are tracked internally
|
|
199
|
+
// The actual stale response scenario is prevented by _isFetching flag
|
|
200
|
+
const datatable = new KTDataTable(container.querySelector('[data-kt-datatable="true"]')!, {
|
|
201
|
+
apiEndpoint: '/api/data',
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
await waitFor(150); // Complete initial
|
|
205
|
+
|
|
206
|
+
datatable.search('first');
|
|
207
|
+
await waitFor(150); // Complete first search
|
|
208
|
+
|
|
209
|
+
datatable.search('second');
|
|
210
|
+
await waitFor(150); // Complete second search
|
|
211
|
+
|
|
212
|
+
// All requests should complete successfully with incremental request IDs
|
|
213
|
+
expect(mockFetch).toHaveBeenCalledTimes(3);
|
|
214
|
+
|
|
215
|
+
// Table should show data from the last successful request
|
|
216
|
+
const tbody = container.querySelector('tbody');
|
|
217
|
+
expect(tbody?.querySelectorAll('tr').length).toBeGreaterThan(0);
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
describe('_isFetching Flag Management', () => {
|
|
222
|
+
it('should prevent concurrent fetch executions', async () => {
|
|
223
|
+
const datatable = new KTDataTable(container.querySelector('[data-kt-datatable="true"]')!, {
|
|
224
|
+
apiEndpoint: '/api/data',
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// Try to trigger reload immediately (should be blocked by initial fetch)
|
|
228
|
+
datatable.reload(); // Blocked by _isFetching
|
|
229
|
+
datatable.reload(); // Blocked by _isFetching
|
|
230
|
+
|
|
231
|
+
await waitFor(150);
|
|
232
|
+
|
|
233
|
+
// Should only have 1 request: initial
|
|
234
|
+
// The reload calls are blocked by _isFetching
|
|
235
|
+
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('should reset _isFetching flag after fetch completes', async () => {
|
|
239
|
+
const datatable = new KTDataTable(container.querySelector('[data-kt-datatable="true"]')!, {
|
|
240
|
+
apiEndpoint: '/api/data',
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
await waitFor(150); // Wait for initial fetch
|
|
244
|
+
|
|
245
|
+
// Should be able to trigger new fetch after previous completes
|
|
246
|
+
datatable.reload();
|
|
247
|
+
await waitFor(150);
|
|
248
|
+
|
|
249
|
+
expect(mockFetch).toHaveBeenCalledTimes(2);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('should reset _isFetching flag even after fetch error', async () => {
|
|
253
|
+
let callCount = 0;
|
|
254
|
+
mockFetch.mockImplementation(() => {
|
|
255
|
+
callCount++;
|
|
256
|
+
if (callCount === 1) {
|
|
257
|
+
// Return invalid JSON to trigger parse error
|
|
258
|
+
return Promise.resolve(
|
|
259
|
+
new Response('Not JSON', { status: 200 }),
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
return Promise.resolve(
|
|
263
|
+
new Response(
|
|
264
|
+
JSON.stringify({
|
|
265
|
+
data: [{ id: 1, name: 'Success' }],
|
|
266
|
+
totalCount: 1,
|
|
267
|
+
}),
|
|
268
|
+
{ status: 200 },
|
|
269
|
+
),
|
|
270
|
+
);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
const datatable = new KTDataTable(container.querySelector('[data-kt-datatable="true"]')!, {
|
|
274
|
+
apiEndpoint: '/api/data',
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
await waitFor(150); // Initial request triggers parse error
|
|
278
|
+
|
|
279
|
+
// Should be able to retry after error
|
|
280
|
+
datatable.reload();
|
|
281
|
+
await waitFor(150);
|
|
282
|
+
|
|
283
|
+
expect(mockFetch).toHaveBeenCalledTimes(2);
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
describe('Loading Spinner Management', () => {
|
|
288
|
+
it('should show spinner during fetch', async () => {
|
|
289
|
+
const element = container.querySelector('[data-kt-datatable="true"]') as HTMLElement;
|
|
290
|
+
const datatable = new KTDataTable(element, {
|
|
291
|
+
apiEndpoint: '/api/data',
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
await waitFor(10); // Spinner should be visible
|
|
295
|
+
|
|
296
|
+
expect(element.classList.contains('loading')).toBe(true);
|
|
297
|
+
|
|
298
|
+
await waitFor(150); // Wait for fetch to complete
|
|
299
|
+
|
|
300
|
+
expect(element.classList.contains('loading')).toBe(false);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it('should keep spinner visible during overlapping requests', async () => {
|
|
304
|
+
const element = container.querySelector('[data-kt-datatable="true"]') as HTMLElement;
|
|
305
|
+
const datatable = new KTDataTable(element, {
|
|
306
|
+
apiEndpoint: '/api/data',
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
await waitFor(10);
|
|
310
|
+
expect(element.classList.contains('loading')).toBe(true);
|
|
311
|
+
|
|
312
|
+
// Trigger second request while first is in progress
|
|
313
|
+
datatable.search('test');
|
|
314
|
+
await waitFor(10);
|
|
315
|
+
|
|
316
|
+
// Spinner should still be visible
|
|
317
|
+
expect(element.classList.contains('loading')).toBe(true);
|
|
318
|
+
|
|
319
|
+
await waitFor(150);
|
|
320
|
+
|
|
321
|
+
// Spinner should hide only after last request completes
|
|
322
|
+
expect(element.classList.contains('loading')).toBe(false);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it('should not flicker spinner during rapid interactions', async () => {
|
|
326
|
+
const element = container.querySelector('[data-kt-datatable="true"]') as HTMLElement;
|
|
327
|
+
const datatable = new KTDataTable(element, {
|
|
328
|
+
apiEndpoint: '/api/data',
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
const spinnerStates: boolean[] = [];
|
|
332
|
+
const checkInterval = setInterval(() => {
|
|
333
|
+
spinnerStates.push(element.classList.contains('loading'));
|
|
334
|
+
}, 20);
|
|
335
|
+
|
|
336
|
+
await waitFor(10);
|
|
337
|
+
datatable.search('a');
|
|
338
|
+
await waitFor(10);
|
|
339
|
+
datatable.search('ab');
|
|
340
|
+
await waitFor(10);
|
|
341
|
+
datatable.search('abc');
|
|
342
|
+
|
|
343
|
+
await waitFor(150);
|
|
344
|
+
clearInterval(checkInterval);
|
|
345
|
+
|
|
346
|
+
// Spinner should go from false -> true -> false
|
|
347
|
+
// No flickering (true -> false -> true)
|
|
348
|
+
const transitions = spinnerStates.reduce((acc, curr, idx) => {
|
|
349
|
+
if (idx > 0 && spinnerStates[idx - 1] !== curr) {
|
|
350
|
+
acc.push(curr);
|
|
351
|
+
}
|
|
352
|
+
return acc;
|
|
353
|
+
}, [] as boolean[]);
|
|
354
|
+
|
|
355
|
+
// Should have exactly 2 transitions: off->on, on->off
|
|
356
|
+
// No additional transitions that would indicate flickering
|
|
357
|
+
expect(transitions.length).toBeLessThanOrEqual(2);
|
|
358
|
+
});
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
describe('Event Handling During Race Conditions', () => {
|
|
362
|
+
it('should fire fetch event for successful requests', async () => {
|
|
363
|
+
const fetchEvents: any[] = [];
|
|
364
|
+
|
|
365
|
+
const element = container.querySelector('[data-kt-datatable="true"]') as HTMLElement;
|
|
366
|
+
element.addEventListener('fetch', (e) => {
|
|
367
|
+
fetchEvents.push(e);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
const datatable = new KTDataTable(element, {
|
|
371
|
+
apiEndpoint: '/api/data',
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
await waitFor(150); // Complete initial
|
|
375
|
+
|
|
376
|
+
datatable.search('test');
|
|
377
|
+
await waitFor(150); // Complete search
|
|
378
|
+
|
|
379
|
+
// Should fire fetch for initial and search
|
|
380
|
+
expect(fetchEvents.length).toBeGreaterThanOrEqual(2);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
it('should fire fetched event after successful data load', async () => {
|
|
384
|
+
const fetchedEvents: any[] = [];
|
|
385
|
+
|
|
386
|
+
const element = container.querySelector('[data-kt-datatable="true"]') as HTMLElement;
|
|
387
|
+
element.addEventListener('fetched', (e) => {
|
|
388
|
+
fetchedEvents.push(e);
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
const datatable = new KTDataTable(element, {
|
|
392
|
+
apiEndpoint: '/api/data',
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
await waitFor(150);
|
|
396
|
+
|
|
397
|
+
// Should fire fetched for initial request
|
|
398
|
+
expect(fetchedEvents.length).toBeGreaterThanOrEqual(1);
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
it('should not fire error events for AbortError', async () => {
|
|
402
|
+
const errorEvents: any[] = [];
|
|
403
|
+
|
|
404
|
+
const element = container.querySelector('[data-kt-datatable="true"]') as HTMLElement;
|
|
405
|
+
element.addEventListener('error.kt.datatable', (e) => {
|
|
406
|
+
errorEvents.push(e);
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
const datatable = new KTDataTable(element, {
|
|
410
|
+
apiEndpoint: '/api/data',
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
await waitFor(10);
|
|
414
|
+
datatable.search('test'); // Cancels previous
|
|
415
|
+
|
|
416
|
+
await waitFor(150);
|
|
417
|
+
|
|
418
|
+
// AbortError should not trigger error event
|
|
419
|
+
expect(errorEvents.length).toBe(0);
|
|
420
|
+
});
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
describe('Backward Compatibility', () => {
|
|
424
|
+
it('should work with local data mode (no AbortController needed)', async () => {
|
|
425
|
+
const datatable = new KTDataTable(container.querySelector('[data-kt-datatable="true"]')!);
|
|
426
|
+
|
|
427
|
+
// Should not call fetch for local data
|
|
428
|
+
expect(mockFetch).not.toHaveBeenCalled();
|
|
429
|
+
|
|
430
|
+
// Should still work without errors
|
|
431
|
+
datatable.search('Item 1');
|
|
432
|
+
datatable.sort('name');
|
|
433
|
+
datatable.goPage(1);
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
it('should maintain existing API compatibility', async () => {
|
|
437
|
+
const datatable = new KTDataTable(container.querySelector('[data-kt-datatable="true"]')!, {
|
|
438
|
+
apiEndpoint: '/api/data',
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
await waitFor(150);
|
|
442
|
+
|
|
443
|
+
// All existing methods should work
|
|
444
|
+
expect(() => {
|
|
445
|
+
datatable.reload();
|
|
446
|
+
datatable.search('test');
|
|
447
|
+
datatable.sort('name');
|
|
448
|
+
datatable.goPage(1);
|
|
449
|
+
datatable.setPageSize(20);
|
|
450
|
+
datatable.getState();
|
|
451
|
+
}).not.toThrow();
|
|
452
|
+
});
|
|
453
|
+
});
|
|
454
|
+
});
|
|
455
|
+
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vitest setup file for datatable tests
|
|
3
|
+
* Provides DOM environment setup and utilities
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { beforeEach, afterEach, vi } from 'vitest';
|
|
7
|
+
|
|
8
|
+
// Mock KTComponents to prevent auto-initialization errors
|
|
9
|
+
vi.mock('../../../index', () => ({
|
|
10
|
+
default: {
|
|
11
|
+
init: vi.fn(),
|
|
12
|
+
},
|
|
13
|
+
KTComponents: {
|
|
14
|
+
init: vi.fn(),
|
|
15
|
+
},
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
// Setup DOM environment before each test
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
// Clear localStorage
|
|
21
|
+
localStorage.clear();
|
|
22
|
+
|
|
23
|
+
// Reset document body
|
|
24
|
+
document.body.innerHTML = '';
|
|
25
|
+
|
|
26
|
+
// Clear any global state
|
|
27
|
+
// Add any other global setup here
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Cleanup after each test
|
|
31
|
+
afterEach(() => {
|
|
32
|
+
// Clear localStorage
|
|
33
|
+
localStorage.clear();
|
|
34
|
+
|
|
35
|
+
// Reset document body
|
|
36
|
+
document.body.innerHTML = '';
|
|
37
|
+
|
|
38
|
+
// Clear all pending timers to prevent "document is not defined" errors
|
|
39
|
+
vi.clearAllTimers();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Mock window.matchMedia if needed
|
|
43
|
+
Object.defineProperty(window, 'matchMedia', {
|
|
44
|
+
writable: true,
|
|
45
|
+
value: (query: string) => ({
|
|
46
|
+
matches: false,
|
|
47
|
+
media: query,
|
|
48
|
+
onchange: null as ((this: MediaQueryList, ev: MediaQueryListEvent) => any) | null,
|
|
49
|
+
addListener: () => {}, // deprecated
|
|
50
|
+
removeListener: () => {}, // deprecated
|
|
51
|
+
addEventListener: () => {},
|
|
52
|
+
removeEventListener: () => {},
|
|
53
|
+
dispatchEvent: () => true,
|
|
54
|
+
}),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Export utilities that tests can use
|
|
58
|
+
export const waitFor = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
59
|
+
|
|
60
|
+
export const createMockElement = (tag: string, attributes: Record<string, string> = {}) => {
|
|
61
|
+
const el = document.createElement(tag);
|
|
62
|
+
Object.entries(attributes).forEach(([key, value]) => {
|
|
63
|
+
el.setAttribute(key, value);
|
|
64
|
+
});
|
|
65
|
+
return el;
|
|
66
|
+
};
|
|
67
|
+
|
|
@@ -58,6 +58,18 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
58
58
|
private _data: T[] = [];
|
|
59
59
|
private _isFetching: boolean = false;
|
|
60
60
|
|
|
61
|
+
/**
|
|
62
|
+
* AbortController for cancelling previous fetch requests
|
|
63
|
+
* Used to prevent race conditions when multiple requests are triggered rapidly
|
|
64
|
+
*/
|
|
65
|
+
private _abortController: AbortController | null = null;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Request ID counter for tracking request sequence
|
|
69
|
+
* Used to detect and ignore stale responses from older requests
|
|
70
|
+
*/
|
|
71
|
+
private _requestId: number = 0;
|
|
72
|
+
|
|
61
73
|
constructor(element: HTMLElement, config?: KTDataTableConfigInterface) {
|
|
62
74
|
super();
|
|
63
75
|
|
|
@@ -448,15 +460,16 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
448
460
|
try {
|
|
449
461
|
this._showSpinner(); // Show spinner before fetching data
|
|
450
462
|
|
|
451
|
-
// Fetch data
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
463
|
+
// Fetch data and finalize - properly await to ensure finally block runs after completion
|
|
464
|
+
if (typeof this._config.apiEndpoint === 'undefined') {
|
|
465
|
+
await this._fetchDataFromLocal();
|
|
466
|
+
await this._finalize();
|
|
467
|
+
} else {
|
|
468
|
+
await this._fetchDataFromServer();
|
|
469
|
+
await this._finalize();
|
|
470
|
+
}
|
|
459
471
|
} finally {
|
|
472
|
+
// Finally block now correctly executes after promises resolve, not immediately
|
|
460
473
|
this._isFetching = false;
|
|
461
474
|
}
|
|
462
475
|
}
|
|
@@ -703,11 +716,30 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
703
716
|
* Fetch data from the server
|
|
704
717
|
*/
|
|
705
718
|
private async _fetchDataFromServer(): Promise<void> {
|
|
719
|
+
// Increment request ID to track this specific request
|
|
720
|
+
const currentRequestId = ++this._requestId;
|
|
721
|
+
|
|
706
722
|
this._fireEvent('fetch');
|
|
707
723
|
this._dispatchEvent('fetch');
|
|
708
724
|
|
|
709
725
|
const queryParams = this._getQueryParamsForFetchRequest();
|
|
710
|
-
|
|
726
|
+
|
|
727
|
+
let response: Response;
|
|
728
|
+
try {
|
|
729
|
+
response = await this._performFetchRequest(queryParams);
|
|
730
|
+
} catch (error) {
|
|
731
|
+
// Silently ignore AbortError - request was cancelled
|
|
732
|
+
if ((error as Error).name === 'AbortError') {
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
throw error;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// Check if this response is stale (a newer request has been initiated)
|
|
739
|
+
if (currentRequestId !== this._requestId) {
|
|
740
|
+
// Ignore stale response - a more recent request is in progress or has completed
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
711
743
|
|
|
712
744
|
let responseData = null;
|
|
713
745
|
|
|
@@ -730,6 +762,11 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
730
762
|
return;
|
|
731
763
|
}
|
|
732
764
|
|
|
765
|
+
// Double-check request ID after JSON parsing (additional safety)
|
|
766
|
+
if (currentRequestId !== this._requestId) {
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
|
|
733
770
|
this._fireEvent('fetched', { response: responseData });
|
|
734
771
|
this._dispatchEvent('fetched', { response: responseData });
|
|
735
772
|
|
|
@@ -809,6 +846,14 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
809
846
|
let requestMethod: RequestInit['method'] = this._config.requestMethod;
|
|
810
847
|
let requestBody: RequestInit['body'] | undefined = undefined;
|
|
811
848
|
|
|
849
|
+
// Cancel previous request to prevent race conditions
|
|
850
|
+
if (this._abortController) {
|
|
851
|
+
this._abortController.abort();
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
// Create new AbortController for this request
|
|
855
|
+
this._abortController = new AbortController();
|
|
856
|
+
|
|
812
857
|
// If the request method is POST, send the query params as the request body
|
|
813
858
|
if (requestMethod === 'POST') {
|
|
814
859
|
requestBody = queryParams;
|
|
@@ -828,8 +873,15 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
828
873
|
...(this._config.requestCredentials && {
|
|
829
874
|
credentials: this._config.requestCredentials,
|
|
830
875
|
}),
|
|
876
|
+
// Add abort signal if available
|
|
877
|
+
...(this._abortController && { signal: this._abortController.signal }),
|
|
831
878
|
}).catch((error) => {
|
|
832
|
-
//
|
|
879
|
+
// Silently ignore AbortError - this is expected when requests are cancelled
|
|
880
|
+
if (error.name === 'AbortError') {
|
|
881
|
+
return Promise.reject(error);
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
// Trigger an error event for non-abort errors
|
|
833
885
|
this._fireEvent('error', { error });
|
|
834
886
|
this._dispatchEvent('error', { error });
|
|
835
887
|
|
|
@@ -897,7 +949,8 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
897
949
|
this._fireEvent('drew');
|
|
898
950
|
this._dispatchEvent('drew');
|
|
899
951
|
|
|
900
|
-
|
|
952
|
+
// Spinner is hidden in _finalize() to ensure it stays visible until the entire request completes
|
|
953
|
+
// Removed duplicate _hideSpinner() call here to prevent premature hiding
|
|
901
954
|
|
|
902
955
|
if (this._config.stateSave) {
|
|
903
956
|
this._saveState();
|
|
@@ -1668,6 +1721,7 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
1668
1721
|
),
|
|
1669
1722
|
filter,
|
|
1670
1723
|
];
|
|
1724
|
+
this._config._state.page = 1;
|
|
1671
1725
|
return this;
|
|
1672
1726
|
}
|
|
1673
1727
|
|
|
@@ -1677,6 +1731,7 @@ export class KTDataTable<T extends KTDataTableDataInterface>
|
|
|
1677
1731
|
|
|
1678
1732
|
public search(query: string | object): void {
|
|
1679
1733
|
this._config._state.search = query;
|
|
1734
|
+
this._config._state.page = 1;
|
|
1680
1735
|
this.reload();
|
|
1681
1736
|
}
|
|
1682
1737
|
|
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
@apply outline-0 block w-full bg-background border border-input shadow-xs shadow-[rgba(0,0,0,0.05)] transition-[color,box-shadow] text-foreground placeholder:text-muted-foreground;
|
|
11
11
|
@apply focus-visible:ring-ring/30 focus-visible:border-ring focus-visible:outline-none focus-visible:ring-[3px];
|
|
12
12
|
@apply disabled:cursor-not-allowed disabled:opacity-60;
|
|
13
|
-
@apply [&[readonly]]:bg-muted/80 [&[readonly]]:cursor-not-allowed [&[readonly]]:text-secondary-foreground/80;
|
|
14
13
|
@apply file:h-full [&[type=file]]:py-0;
|
|
15
14
|
@apply file:border-solid file:border-input file:bg-transparent file:font-medium file:not-italic file:text-foreground file:p-0 file:border-0 file:border-e;
|
|
16
15
|
@apply aria-invalid:border-destructive/60 aria-invalid:ring-destructive/10;
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
@apply cursor-pointer py-0 appearance-none flex items-center gap-2 w-full bg-background border border-input shadow-xs shadow-[rgba(0,0,0,0.05)] transition-[color,box-shadow] text-foreground placeholder:text-muted-foreground/80;
|
|
10
10
|
@apply focus-visible:ring-ring/30 focus-visible:border-ring focus-visible:outline-none focus-visible:ring-[3px];
|
|
11
11
|
@apply disabled:cursor-not-allowed disabled:opacity-60;
|
|
12
|
-
@apply [&[readonly]]:opacity-70;
|
|
13
12
|
@apply aria-invalid:border-destructive/60 aria-invalid:ring-destructive/10;
|
|
14
13
|
|
|
15
14
|
background-repeat: no-repeat;
|
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
@apply w-full bg-background border border-input text-foreground shadow-xs shadow-[rgba(0,0,0,0.05)] transition-[color,box-shadow] placeholder:text-muted-foreground/80;
|
|
11
11
|
@apply focus-visible:border-ring focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/30;
|
|
12
12
|
@apply disabled:cursor-not-allowed disabled:opacity-60;
|
|
13
|
-
@apply [&[readonly]]:bg-muted/80 [&[readonly]]:cursor-not-allowed;
|
|
14
13
|
@apply aria-invalid:border-destructive/60 aria-invalid:ring-destructive/10;
|
|
15
14
|
}
|
|
16
15
|
|