@keenthemes/ktui 1.2.0 → 1.2.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/dist/ktui.js +3349 -1550
- package/dist/ktui.min.js +1 -1
- package/dist/ktui.min.js.map +1 -1
- package/dist/styles.css +1 -1
- package/lib/cjs/components/clipboard/clipboard.d.ts +37 -0
- package/lib/cjs/components/clipboard/clipboard.d.ts.map +1 -0
- package/lib/cjs/components/clipboard/clipboard.js +402 -0
- package/lib/cjs/components/clipboard/clipboard.js.map +1 -0
- package/lib/cjs/components/clipboard/index.d.ts +3 -0
- package/lib/cjs/components/clipboard/index.d.ts.map +1 -0
- package/lib/cjs/components/clipboard/index.js +6 -0
- package/lib/cjs/components/clipboard/index.js.map +1 -0
- package/lib/cjs/components/clipboard/types.d.ts +44 -0
- package/lib/cjs/components/clipboard/types.d.ts.map +1 -0
- package/lib/cjs/components/clipboard/types.js +7 -0
- package/lib/cjs/components/clipboard/types.js.map +1 -0
- package/lib/cjs/components/datatable/datatable.d.ts.map +1 -1
- package/lib/cjs/components/datatable/datatable.js.map +1 -1
- package/lib/cjs/components/range-slider/index.d.ts +7 -0
- package/lib/cjs/components/range-slider/index.d.ts.map +1 -0
- package/lib/cjs/components/range-slider/index.js +10 -0
- package/lib/cjs/components/range-slider/index.js.map +1 -0
- package/lib/cjs/components/range-slider/range-slider.d.ts +42 -0
- package/lib/cjs/components/range-slider/range-slider.d.ts.map +1 -0
- package/lib/cjs/components/range-slider/range-slider.js +254 -0
- package/lib/cjs/components/range-slider/range-slider.js.map +1 -0
- package/lib/cjs/components/range-slider/types.d.ts +33 -0
- package/lib/cjs/components/range-slider/types.d.ts.map +1 -0
- package/lib/cjs/components/range-slider/types.js +7 -0
- package/lib/cjs/components/range-slider/types.js.map +1 -0
- package/lib/cjs/components/rating/rating.d.ts.map +1 -1
- package/lib/cjs/components/rating/rating.js +8 -3
- package/lib/cjs/components/rating/rating.js.map +1 -1
- package/lib/cjs/components/repeater/repeater.d.ts.map +1 -1
- package/lib/cjs/components/repeater/repeater.js +3 -2
- package/lib/cjs/components/repeater/repeater.js.map +1 -1
- package/lib/cjs/components/select/utils.d.ts.map +1 -1
- package/lib/cjs/components/select/utils.js +3 -1
- package/lib/cjs/components/select/utils.js.map +1 -1
- package/lib/cjs/components/sticky/sticky.d.ts.map +1 -1
- package/lib/cjs/components/sticky/sticky.js +3 -1
- package/lib/cjs/components/sticky/sticky.js.map +1 -1
- package/lib/cjs/index.d.ts +8 -0
- package/lib/cjs/index.d.ts.map +1 -1
- package/lib/cjs/index.js +9 -1
- package/lib/cjs/index.js.map +1 -1
- package/lib/esm/components/clipboard/clipboard.d.ts +37 -0
- package/lib/esm/components/clipboard/clipboard.d.ts.map +1 -0
- package/lib/esm/components/clipboard/clipboard.js +399 -0
- package/lib/esm/components/clipboard/clipboard.js.map +1 -0
- package/lib/esm/components/clipboard/index.d.ts +3 -0
- package/lib/esm/components/clipboard/index.d.ts.map +1 -0
- package/lib/esm/components/clipboard/index.js +2 -0
- package/lib/esm/components/clipboard/index.js.map +1 -0
- package/lib/esm/components/clipboard/types.d.ts +44 -0
- package/lib/esm/components/clipboard/types.d.ts.map +1 -0
- package/lib/esm/components/clipboard/types.js +6 -0
- package/lib/esm/components/clipboard/types.js.map +1 -0
- package/lib/esm/components/datatable/datatable.d.ts.map +1 -1
- package/lib/esm/components/datatable/datatable.js.map +1 -1
- package/lib/esm/components/range-slider/index.d.ts +7 -0
- package/lib/esm/components/range-slider/index.d.ts.map +1 -0
- package/lib/esm/components/range-slider/index.js +6 -0
- package/lib/esm/components/range-slider/index.js.map +1 -0
- package/lib/esm/components/range-slider/range-slider.d.ts +42 -0
- package/lib/esm/components/range-slider/range-slider.d.ts.map +1 -0
- package/lib/esm/components/range-slider/range-slider.js +251 -0
- package/lib/esm/components/range-slider/range-slider.js.map +1 -0
- package/lib/esm/components/range-slider/types.d.ts +33 -0
- package/lib/esm/components/range-slider/types.d.ts.map +1 -0
- package/lib/esm/components/range-slider/types.js +6 -0
- package/lib/esm/components/range-slider/types.js.map +1 -0
- package/lib/esm/components/rating/rating.d.ts.map +1 -1
- package/lib/esm/components/rating/rating.js +8 -3
- package/lib/esm/components/rating/rating.js.map +1 -1
- package/lib/esm/components/repeater/repeater.d.ts.map +1 -1
- package/lib/esm/components/repeater/repeater.js +3 -2
- package/lib/esm/components/repeater/repeater.js.map +1 -1
- package/lib/esm/components/select/utils.d.ts.map +1 -1
- package/lib/esm/components/select/utils.js +3 -1
- package/lib/esm/components/select/utils.js.map +1 -1
- package/lib/esm/components/sticky/sticky.d.ts.map +1 -1
- package/lib/esm/components/sticky/sticky.js +3 -1
- package/lib/esm/components/sticky/sticky.js.map +1 -1
- package/lib/esm/index.d.ts +8 -0
- 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 +1 -2
- package/src/components/clipboard/__tests__/clipboard.test.ts +438 -0
- package/src/components/clipboard/clipboard.ts +416 -0
- package/src/components/clipboard/index.ts +2 -0
- package/src/components/clipboard/types.ts +51 -0
- package/src/components/datatable/__tests__/currency-sort.test.ts +2 -10
- package/src/components/datatable/__tests__/multi-row-headers.test.ts +2 -2
- package/src/components/datatable/__tests__/race-conditions.test.ts +11 -14
- package/src/components/datatable/datatable.ts +3 -5
- package/src/components/range-slider/__tests__/range-slider.test.ts +659 -0
- package/src/components/range-slider/index.ts +11 -0
- package/src/components/range-slider/range-slider.ts +276 -0
- package/src/components/range-slider/types.ts +36 -0
- package/src/components/rating/__tests__/rating.test.ts +11 -4
- package/src/components/rating/rating.ts +22 -11
- package/src/components/repeater/__tests__/repeater.test.ts +19 -6
- package/src/components/repeater/repeater.ts +5 -3
- package/src/components/select/__tests__/ux-behaviors.test.ts +21 -3
- package/src/components/select/utils.ts +5 -1
- package/src/components/sticky/__tests__/sticky.test.ts +10 -3
- package/src/components/sticky/sticky.ts +14 -24
- package/src/components/sticky/types.ts +3 -3
- package/src/index.ts +17 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KTUI - Free & Open-Source Tailwind UI Components by Keenthemes
|
|
3
|
+
* Copyright 2025 by Keenthemes Inc
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import KTData from '../../helpers/data';
|
|
7
|
+
import KTComponent from '../component';
|
|
8
|
+
import {
|
|
9
|
+
KTRangeSliderConfigInterface,
|
|
10
|
+
KTRangeSliderEventPayloadInterface,
|
|
11
|
+
KTRangeSliderInterface,
|
|
12
|
+
} from './types';
|
|
13
|
+
|
|
14
|
+
declare global {
|
|
15
|
+
interface Window {
|
|
16
|
+
KTRangeSlider: typeof KTRangeSlider;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const FILL_VAR = '--kt-range-fill';
|
|
21
|
+
|
|
22
|
+
export class KTRangeSlider
|
|
23
|
+
extends KTComponent
|
|
24
|
+
implements KTRangeSliderInterface
|
|
25
|
+
{
|
|
26
|
+
protected override _name: string = 'range-slider';
|
|
27
|
+
protected override _defaultConfig: KTRangeSliderConfigInterface = {
|
|
28
|
+
output: '',
|
|
29
|
+
lazy: false,
|
|
30
|
+
};
|
|
31
|
+
protected override _config: KTRangeSliderConfigInterface =
|
|
32
|
+
this._defaultConfig;
|
|
33
|
+
protected _rangeInput: HTMLInputElement | null = null;
|
|
34
|
+
protected _onNativeInput: ((e: Event) => void) | null = null;
|
|
35
|
+
protected _onNativeChange: ((e: Event) => void) | null = null;
|
|
36
|
+
|
|
37
|
+
constructor(
|
|
38
|
+
element: HTMLElement,
|
|
39
|
+
config: KTRangeSliderConfigInterface | null = null,
|
|
40
|
+
) {
|
|
41
|
+
super();
|
|
42
|
+
|
|
43
|
+
const input = KTRangeSlider.findRangeInput(element);
|
|
44
|
+
if (!input) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (this._shouldSkipInit(element)) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
this._rangeInput = input;
|
|
53
|
+
this._init(element);
|
|
54
|
+
this._buildConfig(config);
|
|
55
|
+
|
|
56
|
+
this._onNativeInput = this._handleNativeInput.bind(this);
|
|
57
|
+
this._onNativeChange = this._handleNativeChange.bind(this);
|
|
58
|
+
// Delegate events to the root so we keep working if the preview
|
|
59
|
+
// re-renders and replaces the native range input element.
|
|
60
|
+
this._element?.addEventListener('input', this._onNativeInput);
|
|
61
|
+
this._element?.addEventListener('change', this._onNativeChange);
|
|
62
|
+
|
|
63
|
+
this._syncFromInput();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private static findRangeInput(root: HTMLElement): HTMLInputElement | null {
|
|
67
|
+
if (root instanceof HTMLInputElement && root.type === 'range') {
|
|
68
|
+
return root;
|
|
69
|
+
}
|
|
70
|
+
return root.querySelector<HTMLInputElement>('input[type="range"]');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
protected _getOutputSelector(): string {
|
|
74
|
+
const raw = this._getOption('output');
|
|
75
|
+
return typeof raw === 'string' ? raw.trim() : '';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
protected _resolveOutputElement(): HTMLElement | null {
|
|
79
|
+
const selector = this._getOutputSelector();
|
|
80
|
+
if (!selector || !this._element) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
const fromRoot = this._element.querySelector<HTMLElement>(selector);
|
|
84
|
+
if (fromRoot) {
|
|
85
|
+
return fromRoot;
|
|
86
|
+
}
|
|
87
|
+
return document.querySelector<HTMLElement>(selector);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
protected _getNumericMin(): number {
|
|
91
|
+
const input = this._rangeInput;
|
|
92
|
+
if (!input) return 0;
|
|
93
|
+
const n =
|
|
94
|
+
typeof input.min === 'string' && input.min !== ''
|
|
95
|
+
? parseFloat(input.min)
|
|
96
|
+
: 0;
|
|
97
|
+
return Number.isFinite(n) ? n : 0;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
protected _getNumericMax(): number {
|
|
101
|
+
const input = this._rangeInput;
|
|
102
|
+
if (!input) return 100;
|
|
103
|
+
if (typeof input.max === 'string' && input.max !== '') {
|
|
104
|
+
const n = parseFloat(input.max);
|
|
105
|
+
if (Number.isFinite(n)) return n;
|
|
106
|
+
}
|
|
107
|
+
return 100;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Reflects `step` for events; `undefined` when `step="any"`. */
|
|
111
|
+
protected _getStepForPayload(): number | undefined {
|
|
112
|
+
const input = this._rangeInput;
|
|
113
|
+
if (!input) return undefined;
|
|
114
|
+
const raw = input.getAttribute('step');
|
|
115
|
+
if (raw === 'any') return undefined;
|
|
116
|
+
if (raw === null || raw === '') return 1;
|
|
117
|
+
const n = parseFloat(raw);
|
|
118
|
+
return Number.isFinite(n) && n > 0 ? n : 1;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
protected _getCurrentValue(): number {
|
|
122
|
+
const input = this._rangeInput;
|
|
123
|
+
if (!input) return 0;
|
|
124
|
+
const v =
|
|
125
|
+
typeof input.valueAsNumber === 'number' &&
|
|
126
|
+
!Number.isNaN(input.valueAsNumber)
|
|
127
|
+
? input.valueAsNumber
|
|
128
|
+
: parseFloat(input.value);
|
|
129
|
+
return Number.isFinite(v) ? v : 0;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
protected _clamp(value: number, min: number, max: number): number {
|
|
133
|
+
if (max < min) {
|
|
134
|
+
return value;
|
|
135
|
+
}
|
|
136
|
+
return Math.min(max, Math.max(min, value));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
protected _fillRatio(value: number, min: number, max: number): number {
|
|
140
|
+
if (max === min) {
|
|
141
|
+
return 0;
|
|
142
|
+
}
|
|
143
|
+
return (this._clamp(value, min, max) - min) / (max - min);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
protected _buildEventPayload(): KTRangeSliderEventPayloadInterface {
|
|
147
|
+
const min = this._getNumericMin();
|
|
148
|
+
const max = this._getNumericMax();
|
|
149
|
+
const value = this._getCurrentValue();
|
|
150
|
+
const step = this._getStepForPayload();
|
|
151
|
+
return {
|
|
152
|
+
value,
|
|
153
|
+
min,
|
|
154
|
+
max,
|
|
155
|
+
...(step !== undefined ? { step } : {}),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
protected _syncFromInput(): void {
|
|
160
|
+
if (!this._element || !this._rangeInput) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const min = this._getNumericMin();
|
|
165
|
+
const max = this._getNumericMax();
|
|
166
|
+
const value = this._getCurrentValue();
|
|
167
|
+
const ratio = this._fillRatio(value, min, max);
|
|
168
|
+
|
|
169
|
+
this._element.style.setProperty(FILL_VAR, String(ratio));
|
|
170
|
+
|
|
171
|
+
const out = this._resolveOutputElement();
|
|
172
|
+
if (out) {
|
|
173
|
+
out.textContent = String(this._rangeInput.value);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
protected _handleNativeInput(_event: Event): void {
|
|
178
|
+
const event = _event as Event;
|
|
179
|
+
const target = event.target;
|
|
180
|
+
if (!(target instanceof HTMLInputElement) || target.type !== 'range') {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
this._rangeInput = target;
|
|
185
|
+
this._syncFromInput();
|
|
186
|
+
|
|
187
|
+
const payload = this._buildEventPayload();
|
|
188
|
+
this._fireEvent('input', payload);
|
|
189
|
+
this._dispatchEvent('kt.range-slider.input', payload);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
protected _handleNativeChange(_event: Event): void {
|
|
193
|
+
const event = _event as Event;
|
|
194
|
+
const target = event.target;
|
|
195
|
+
if (!(target instanceof HTMLInputElement) || target.type !== 'range') {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
this._rangeInput = target;
|
|
200
|
+
this._syncFromInput();
|
|
201
|
+
|
|
202
|
+
const payload = this._buildEventPayload();
|
|
203
|
+
this._fireEvent('change', payload);
|
|
204
|
+
this._dispatchEvent('kt.range-slider.change', payload);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
public getRangeInput(): HTMLInputElement | null {
|
|
208
|
+
return this._rangeInput;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
public getValue(): number {
|
|
212
|
+
return this._getCurrentValue();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
public override dispose(): void {
|
|
216
|
+
if (this._element) {
|
|
217
|
+
if (this._onNativeInput) {
|
|
218
|
+
this._element.removeEventListener('input', this._onNativeInput);
|
|
219
|
+
}
|
|
220
|
+
if (this._onNativeChange) {
|
|
221
|
+
this._element.removeEventListener('change', this._onNativeChange);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
this._onNativeInput = null;
|
|
225
|
+
this._onNativeChange = null;
|
|
226
|
+
this._rangeInput = null;
|
|
227
|
+
if (this._element) {
|
|
228
|
+
this._element.style.removeProperty(FILL_VAR);
|
|
229
|
+
}
|
|
230
|
+
super.dispose();
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
public static getInstance(element: HTMLElement): KTRangeSlider | null {
|
|
234
|
+
if (!element) {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
if (KTData.has(element, 'range-slider')) {
|
|
238
|
+
return KTData.get(element, 'range-slider') as KTRangeSlider;
|
|
239
|
+
}
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
public static getOrCreateInstance(
|
|
244
|
+
element: HTMLElement,
|
|
245
|
+
config?: KTRangeSliderConfigInterface,
|
|
246
|
+
): KTRangeSlider | null {
|
|
247
|
+
const existing = this.getInstance(element);
|
|
248
|
+
if (existing) {
|
|
249
|
+
return existing;
|
|
250
|
+
}
|
|
251
|
+
if (!this.findRangeInput(element)) {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
new KTRangeSlider(element, config ?? undefined);
|
|
255
|
+
return this.getInstance(element);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
public static createInstances(): void {
|
|
259
|
+
document
|
|
260
|
+
.querySelectorAll<HTMLElement>('[data-kt-range-slider]')
|
|
261
|
+
.forEach((el) => {
|
|
262
|
+
if (el.getAttribute('data-kt-range-slider-lazy') === 'true') {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
new KTRangeSlider(el);
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
public static init(): void {
|
|
270
|
+
KTRangeSlider.createInstances();
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (typeof window !== 'undefined') {
|
|
275
|
+
window.KTRangeSlider = KTRangeSlider;
|
|
276
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KTUI - Free & Open-Source Tailwind UI Components by Keenthemes
|
|
3
|
+
* Copyright 2025 by Keenthemes Inc
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Optional CSS selector (document or root-relative) for an element whose text
|
|
8
|
+
* updates with the current range value. Prefer `data-kt-range-slider-output` on the root.
|
|
9
|
+
*/
|
|
10
|
+
export interface KTRangeSliderConfigInterface {
|
|
11
|
+
/** Selector for the output element; also read from `data-kt-range-slider-output`. */
|
|
12
|
+
output?: string;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* When true, skip auto-init; use `getInstance` / `new KTRangeSlider(element)` manually.
|
|
16
|
+
*/
|
|
17
|
+
lazy?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface KTRangeSliderEventPayloadInterface {
|
|
21
|
+
value: number;
|
|
22
|
+
min: number;
|
|
23
|
+
max: number;
|
|
24
|
+
/** From the input `step` attribute; omitted when `step="any"`. */
|
|
25
|
+
step?: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface KTRangeSliderInterface {
|
|
29
|
+
getOption(name: string): unknown;
|
|
30
|
+
getElement(): HTMLElement;
|
|
31
|
+
getRangeInput(): HTMLInputElement | null;
|
|
32
|
+
getValue(): number;
|
|
33
|
+
on(eventType: string, callback: CallableFunction): string;
|
|
34
|
+
off(eventType: string, eventId: string): void;
|
|
35
|
+
dispose(): void;
|
|
36
|
+
}
|
|
@@ -104,7 +104,9 @@ describe('KTRating', () => {
|
|
|
104
104
|
const instance = new KTRating(ratingEl);
|
|
105
105
|
instance.setValue(3);
|
|
106
106
|
expect(instance.getValue()).toBe(3);
|
|
107
|
-
const checked = ratingEl.querySelector<HTMLInputElement>(
|
|
107
|
+
const checked = ratingEl.querySelector<HTMLInputElement>(
|
|
108
|
+
'input[type="radio"]:checked',
|
|
109
|
+
);
|
|
108
110
|
expect(checked?.value).toBe('3');
|
|
109
111
|
instance.dispose();
|
|
110
112
|
});
|
|
@@ -121,8 +123,11 @@ describe('KTRating', () => {
|
|
|
121
123
|
it('dispatches kt.rating.change when user selects a value', () => {
|
|
122
124
|
const instance = new KTRating(ratingEl);
|
|
123
125
|
const events: CustomEvent[] = [];
|
|
124
|
-
ratingEl.addEventListener('kt.rating.change', ((e: CustomEvent) =>
|
|
125
|
-
|
|
126
|
+
ratingEl.addEventListener('kt.rating.change', ((e: CustomEvent) =>
|
|
127
|
+
events.push(e)) as EventListener);
|
|
128
|
+
const radio3 = ratingEl.querySelector<HTMLInputElement>(
|
|
129
|
+
'input[type="radio"][value="3"]',
|
|
130
|
+
);
|
|
126
131
|
expect(radio3).not.toBeNull();
|
|
127
132
|
radio3!.checked = true;
|
|
128
133
|
radio3!.dispatchEvent(new Event('change', { bubbles: true }));
|
|
@@ -226,7 +231,9 @@ describe('KTRating', () => {
|
|
|
226
231
|
ratingEl.setAttribute('data-kt-rating-value', '2');
|
|
227
232
|
const instance = new KTRating(ratingEl);
|
|
228
233
|
expect(instance.getValue()).toBe(2);
|
|
229
|
-
const checked = ratingEl.querySelector<HTMLInputElement>(
|
|
234
|
+
const checked = ratingEl.querySelector<HTMLInputElement>(
|
|
235
|
+
'input[type="radio"]:checked',
|
|
236
|
+
);
|
|
230
237
|
expect(checked?.value).toBe('2');
|
|
231
238
|
instance.dispose();
|
|
232
239
|
});
|
|
@@ -79,7 +79,8 @@ export class KTRating extends KTComponent implements KTRatingInterface {
|
|
|
79
79
|
const svg = `<svg class="shrink-0 size-5" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">${path}</svg>`;
|
|
80
80
|
|
|
81
81
|
const container = document.createElement('div');
|
|
82
|
-
container.className =
|
|
82
|
+
container.className =
|
|
83
|
+
'kt-rating flex flex-row-reverse justify-end items-center gap-0';
|
|
83
84
|
container.setAttribute('role', readonly ? 'img' : 'group');
|
|
84
85
|
container.setAttribute('aria-label', `Rating: ${value} of ${max}`);
|
|
85
86
|
if (readonly) {
|
|
@@ -141,13 +142,21 @@ export class KTRating extends KTComponent implements KTRatingInterface {
|
|
|
141
142
|
'text-muted-foreground/50 dark:text-muted-foreground/50';
|
|
142
143
|
const filledTokens = filledClass.split(' ');
|
|
143
144
|
const unfilledTokens = unfilledClass.split(' ');
|
|
144
|
-
this._container
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
145
|
+
this._container
|
|
146
|
+
.querySelectorAll<HTMLElement>('.kt-rating-label')
|
|
147
|
+
.forEach((label) => {
|
|
148
|
+
const v = parseInt(
|
|
149
|
+
label.getAttribute('data-kt-rating-value') || '0',
|
|
150
|
+
10,
|
|
151
|
+
);
|
|
152
|
+
const filled = v <= (val ?? 0);
|
|
153
|
+
label.classList.remove(...filledTokens, ...unfilledTokens);
|
|
154
|
+
label.classList.add(...(filled ? filledTokens : unfilledTokens));
|
|
155
|
+
});
|
|
156
|
+
this._container.setAttribute(
|
|
157
|
+
'aria-valuenow',
|
|
158
|
+
val != null ? String(val) : '0',
|
|
159
|
+
);
|
|
151
160
|
this._container.setAttribute('aria-label', `Rating: ${val ?? 0} of ${max}`);
|
|
152
161
|
}
|
|
153
162
|
|
|
@@ -189,9 +198,11 @@ export class KTRating extends KTComponent implements KTRatingInterface {
|
|
|
189
198
|
this._updateInteractiveDisplay();
|
|
190
199
|
}
|
|
191
200
|
} else {
|
|
192
|
-
this._container
|
|
193
|
-
|
|
194
|
-
|
|
201
|
+
this._container
|
|
202
|
+
.querySelectorAll<HTMLInputElement>('input[type="radio"]')
|
|
203
|
+
.forEach((r) => {
|
|
204
|
+
r.checked = false;
|
|
205
|
+
});
|
|
195
206
|
this._updateInteractiveDisplay();
|
|
196
207
|
}
|
|
197
208
|
}
|
|
@@ -150,7 +150,8 @@ describe('KTRepeater', () => {
|
|
|
150
150
|
const { trigger } = createFixture();
|
|
151
151
|
const instance = new KTRepeater(trigger);
|
|
152
152
|
const events: CustomEvent[] = [];
|
|
153
|
-
trigger.addEventListener('add', ((e: Event) =>
|
|
153
|
+
trigger.addEventListener('add', ((e: Event) =>
|
|
154
|
+
events.push(e as CustomEvent)) as EventListener);
|
|
154
155
|
instance.add();
|
|
155
156
|
expect(events.length).toBe(1);
|
|
156
157
|
expect(events[0].detail?.payload?.element).toBeInstanceOf(HTMLElement);
|
|
@@ -182,7 +183,10 @@ describe('KTRepeater', () => {
|
|
|
182
183
|
});
|
|
183
184
|
|
|
184
185
|
it('re-enables trigger when a clone is removed (delete) and count drops below limit', () => {
|
|
185
|
-
const { wrapper, trigger } = createFixture({
|
|
186
|
+
const { wrapper, trigger } = createFixture({
|
|
187
|
+
limit: 2,
|
|
188
|
+
withDeleteInTarget: true,
|
|
189
|
+
});
|
|
186
190
|
const instance = new KTRepeater(trigger);
|
|
187
191
|
instance.add();
|
|
188
192
|
expect(wrapper.children.length).toBe(2);
|
|
@@ -213,7 +217,9 @@ describe('KTRepeater', () => {
|
|
|
213
217
|
instance.add();
|
|
214
218
|
expect(wrapper.children.length).toBe(2);
|
|
215
219
|
const firstClone = wrapper.children[1];
|
|
216
|
-
const deleteBtn = firstClone.querySelector(
|
|
220
|
+
const deleteBtn = firstClone.querySelector(
|
|
221
|
+
'[data-kt-repeater-delete]',
|
|
222
|
+
) as HTMLElement;
|
|
217
223
|
deleteBtn.click();
|
|
218
224
|
expect(wrapper.children.length).toBe(1);
|
|
219
225
|
instance.dispose();
|
|
@@ -224,7 +230,9 @@ describe('KTRepeater', () => {
|
|
|
224
230
|
const instance = new KTRepeater(trigger);
|
|
225
231
|
expect(wrapper.children.length).toBe(1);
|
|
226
232
|
const onlyRow = wrapper.children[0];
|
|
227
|
-
const deleteBtn = onlyRow.querySelector(
|
|
233
|
+
const deleteBtn = onlyRow.querySelector(
|
|
234
|
+
'[data-kt-repeater-delete]',
|
|
235
|
+
) as HTMLElement;
|
|
228
236
|
deleteBtn.click();
|
|
229
237
|
expect(wrapper.children.length).toBe(1);
|
|
230
238
|
instance.dispose();
|
|
@@ -233,12 +241,17 @@ describe('KTRepeater', () => {
|
|
|
233
241
|
|
|
234
242
|
describe('predefined (hidden) template', () => {
|
|
235
243
|
it('clones from hidden template and appends to wrapper', () => {
|
|
236
|
-
const { wrapper, trigger } = createFixture({
|
|
244
|
+
const { wrapper, trigger } = createFixture({
|
|
245
|
+
hiddenTemplate: true,
|
|
246
|
+
withInitialChild: false,
|
|
247
|
+
});
|
|
237
248
|
expect(wrapper.children.length).toBe(0);
|
|
238
249
|
const instance = new KTRepeater(trigger);
|
|
239
250
|
instance.add();
|
|
240
251
|
expect(wrapper.children.length).toBe(1);
|
|
241
|
-
expect(
|
|
252
|
+
expect(
|
|
253
|
+
wrapper.querySelector('input[placeholder="Enter Name"]'),
|
|
254
|
+
).not.toBeNull();
|
|
242
255
|
instance.dispose();
|
|
243
256
|
});
|
|
244
257
|
});
|
|
@@ -23,8 +23,8 @@ export class KTRepeater extends KTComponent implements KTRepeaterInterface {
|
|
|
23
23
|
wrapper: '',
|
|
24
24
|
limit: 0,
|
|
25
25
|
};
|
|
26
|
-
protected override _config: KTRepeaterConfigInterface =
|
|
27
|
-
|
|
26
|
+
protected override _config: KTRepeaterConfigInterface = this
|
|
27
|
+
._defaultConfig as KTRepeaterConfigInterface;
|
|
28
28
|
protected _wrapperElement: HTMLElement | null = null;
|
|
29
29
|
protected _deleteHandler: (e: Event) => void;
|
|
30
30
|
|
|
@@ -155,7 +155,9 @@ export class KTRepeater extends KTComponent implements KTRepeaterInterface {
|
|
|
155
155
|
element: HTMLElement,
|
|
156
156
|
config?: KTRepeaterConfigInterface,
|
|
157
157
|
): KTRepeater {
|
|
158
|
-
return
|
|
158
|
+
return (
|
|
159
|
+
this.getInstance(element) || new KTRepeater(element, config ?? undefined)
|
|
160
|
+
);
|
|
159
161
|
}
|
|
160
162
|
|
|
161
163
|
public static createInstances(): void {
|
|
@@ -349,12 +349,30 @@ describe('KTSelect UX Behaviors', () => {
|
|
|
349
349
|
await waitFor(50);
|
|
350
350
|
|
|
351
351
|
// ArrowDown focuses first option; ArrowUp from first wraps to last (3). Enter selects focused.
|
|
352
|
-
searchInput.dispatchEvent(
|
|
352
|
+
searchInput.dispatchEvent(
|
|
353
|
+
new KeyboardEvent('keydown', {
|
|
354
|
+
key: 'ArrowDown',
|
|
355
|
+
bubbles: true,
|
|
356
|
+
cancelable: true,
|
|
357
|
+
}),
|
|
358
|
+
);
|
|
353
359
|
await waitFor(20);
|
|
354
|
-
searchInput.dispatchEvent(
|
|
360
|
+
searchInput.dispatchEvent(
|
|
361
|
+
new KeyboardEvent('keydown', {
|
|
362
|
+
key: 'ArrowUp',
|
|
363
|
+
bubbles: true,
|
|
364
|
+
cancelable: true,
|
|
365
|
+
}),
|
|
366
|
+
);
|
|
355
367
|
await waitFor(20);
|
|
356
368
|
|
|
357
|
-
searchInput.dispatchEvent(
|
|
369
|
+
searchInput.dispatchEvent(
|
|
370
|
+
new KeyboardEvent('keydown', {
|
|
371
|
+
key: 'Enter',
|
|
372
|
+
bubbles: true,
|
|
373
|
+
cancelable: true,
|
|
374
|
+
}),
|
|
375
|
+
);
|
|
358
376
|
await waitFor(150);
|
|
359
377
|
|
|
360
378
|
expect(select.getSelectedOptions()).toContain('3');
|
|
@@ -372,7 +372,11 @@ export class FocusManager {
|
|
|
372
372
|
const focusedEl = this._element.querySelector(
|
|
373
373
|
`${this._optionsSelector}.${this._focusClass}`,
|
|
374
374
|
) as HTMLElement | null;
|
|
375
|
-
if (
|
|
375
|
+
if (
|
|
376
|
+
focusedEl &&
|
|
377
|
+
!focusedEl.classList.contains('hidden') &&
|
|
378
|
+
focusedEl.style.display !== 'none'
|
|
379
|
+
) {
|
|
376
380
|
const idx = options.indexOf(focusedEl);
|
|
377
381
|
if (idx >= 0) {
|
|
378
382
|
this._focusedOptionIndex = idx;
|
|
@@ -15,8 +15,13 @@ describe('KTSticky', () => {
|
|
|
15
15
|
beforeEach(() => {
|
|
16
16
|
document.body.innerHTML = '';
|
|
17
17
|
scrollTop = 0;
|
|
18
|
-
vi.spyOn(KTDomModule.default, 'getScrollTop').mockImplementation(
|
|
19
|
-
|
|
18
|
+
vi.spyOn(KTDomModule.default, 'getScrollTop').mockImplementation(
|
|
19
|
+
() => scrollTop,
|
|
20
|
+
);
|
|
21
|
+
vi.spyOn(KTDomModule.default, 'getViewPort').mockReturnValue({
|
|
22
|
+
height: 800,
|
|
23
|
+
width: 1024,
|
|
24
|
+
});
|
|
20
25
|
|
|
21
26
|
wrapper = document.createElement('div');
|
|
22
27
|
wrapper.setAttribute('data-kt-sticky-wrapper', 'true');
|
|
@@ -151,7 +156,9 @@ describe('KTSticky', () => {
|
|
|
151
156
|
it('fires change event when sticky becomes active and when released', async () => {
|
|
152
157
|
const instance = new KTSticky(stickyEl);
|
|
153
158
|
const changes: { active: boolean }[] = [];
|
|
154
|
-
instance.on('change', (payload: { active: boolean }) =>
|
|
159
|
+
instance.on('change', (payload: { active: boolean }) =>
|
|
160
|
+
changes.push(payload),
|
|
161
|
+
);
|
|
155
162
|
|
|
156
163
|
scrollTop = 150;
|
|
157
164
|
window.dispatchEvent(new Event('scroll', { bubbles: true }));
|
|
@@ -119,42 +119,35 @@ export class KTSticky extends KTComponent implements KTStickyInterface {
|
|
|
119
119
|
|
|
120
120
|
// Store scroll handler reference for cleanup
|
|
121
121
|
this._scrollHandler = () => {
|
|
122
|
-
|
|
123
122
|
this._isScrolling = true;
|
|
124
123
|
|
|
125
|
-
if(this._isActive() === true){
|
|
126
|
-
|
|
124
|
+
if (this._isActive() === true) {
|
|
127
125
|
this._debounceScroll(() => {
|
|
128
|
-
|
|
129
126
|
this._isScrolling = false;
|
|
130
127
|
this._process();
|
|
131
|
-
|
|
132
128
|
}, 200);
|
|
133
|
-
|
|
134
|
-
}else{
|
|
135
|
-
|
|
129
|
+
} else {
|
|
136
130
|
this._isScrolling = false;
|
|
137
131
|
this._process();
|
|
138
|
-
|
|
139
132
|
}
|
|
140
|
-
|
|
141
133
|
};
|
|
142
134
|
|
|
143
135
|
if (this._targetElement) {
|
|
144
136
|
if (this._targetElement === document) {
|
|
145
|
-
window.addEventListener('scroll', this._scrollHandler, {
|
|
137
|
+
window.addEventListener('scroll', this._scrollHandler, {
|
|
138
|
+
passive: true,
|
|
139
|
+
});
|
|
146
140
|
} else {
|
|
147
141
|
(this._targetElement as HTMLElement).addEventListener(
|
|
148
142
|
'scroll',
|
|
149
143
|
this._scrollHandler,
|
|
150
|
-
{ passive: true }
|
|
144
|
+
{ passive: true },
|
|
151
145
|
);
|
|
152
146
|
}
|
|
153
147
|
}
|
|
154
148
|
}
|
|
155
149
|
|
|
156
150
|
protected _debounceScroll(callback: () => void, delay: number = 200): void {
|
|
157
|
-
|
|
158
151
|
if (this._scrollTimeout) {
|
|
159
152
|
clearTimeout(this._scrollTimeout);
|
|
160
153
|
}
|
|
@@ -162,7 +155,6 @@ export class KTSticky extends KTComponent implements KTStickyInterface {
|
|
|
162
155
|
this._scrollTimeout = setTimeout(() => {
|
|
163
156
|
callback();
|
|
164
157
|
}, delay);
|
|
165
|
-
|
|
166
158
|
}
|
|
167
159
|
|
|
168
160
|
protected _process(): void {
|
|
@@ -352,7 +344,7 @@ export class KTSticky extends KTComponent implements KTStickyInterface {
|
|
|
352
344
|
const activeClassList = this._getOption('activeClass') as string;
|
|
353
345
|
if (activeClassList) {
|
|
354
346
|
KTDom.addClass(this._element, activeClassList);
|
|
355
|
-
}else{
|
|
347
|
+
} else {
|
|
356
348
|
const classList = this._getOption('class') as string;
|
|
357
349
|
if (classList) {
|
|
358
350
|
KTDom.addClass(this._element, classList);
|
|
@@ -387,10 +379,10 @@ export class KTSticky extends KTComponent implements KTStickyInterface {
|
|
|
387
379
|
const activeClassList = this._getOption('activeClass') as string;
|
|
388
380
|
if (activeClassList) {
|
|
389
381
|
KTDom.removeClass(this._element, activeClassList);
|
|
390
|
-
}else{
|
|
382
|
+
} else {
|
|
391
383
|
const classList = this._getOption('class') as string;
|
|
392
384
|
if (classList) {
|
|
393
|
-
|
|
385
|
+
KTDom.removeClass(this._element, classList);
|
|
394
386
|
}
|
|
395
387
|
}
|
|
396
388
|
|
|
@@ -399,12 +391,11 @@ export class KTSticky extends KTComponent implements KTStickyInterface {
|
|
|
399
391
|
KTDom.addClass(this._element, releaseClassList);
|
|
400
392
|
}
|
|
401
393
|
|
|
402
|
-
if(this._eventTriggerState === false){
|
|
394
|
+
if (this._eventTriggerState === false) {
|
|
403
395
|
const releaseDelay = this._getOption('releaseDelay') as number;
|
|
404
|
-
if(releaseDelay && this._timeoutState === null){
|
|
396
|
+
if (releaseDelay && this._timeoutState === null) {
|
|
405
397
|
this._timeoutState = setTimeout(() => {
|
|
406
|
-
|
|
407
|
-
if (!this._element){
|
|
398
|
+
if (!this._element) {
|
|
408
399
|
return;
|
|
409
400
|
}
|
|
410
401
|
|
|
@@ -413,12 +404,11 @@ export class KTSticky extends KTComponent implements KTStickyInterface {
|
|
|
413
404
|
}
|
|
414
405
|
|
|
415
406
|
this._timeoutState = null;
|
|
416
|
-
|
|
417
407
|
}, releaseDelay);
|
|
418
|
-
}else{
|
|
408
|
+
} else {
|
|
419
409
|
this._resetStyles();
|
|
420
410
|
}
|
|
421
|
-
}else{
|
|
411
|
+
} else {
|
|
422
412
|
this._timeoutState = null;
|
|
423
413
|
}
|
|
424
414
|
}
|
|
@@ -19,9 +19,9 @@ export interface KTStickyConfigInterface {
|
|
|
19
19
|
reverse: boolean;
|
|
20
20
|
release: string;
|
|
21
21
|
activate: string;
|
|
22
|
-
releaseDelay: number
|
|
23
|
-
activeClass: string
|
|
24
|
-
releaseClass: string
|
|
22
|
+
releaseDelay: number;
|
|
23
|
+
activeClass: string;
|
|
24
|
+
releaseClass: string;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export interface KTStickyInterface {
|