@keenthemes/ktui 1.2.1 → 1.2.2
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 +2858 -1515
- package/dist/ktui.min.js +1 -1
- package/dist/ktui.min.js.map +1 -1
- package/dist/styles.css +23 -0
- package/lib/cjs/components/carousel/carousel.d.ts +102 -0
- package/lib/cjs/components/carousel/carousel.d.ts.map +1 -0
- package/lib/cjs/components/carousel/carousel.js +769 -0
- package/lib/cjs/components/carousel/carousel.js.map +1 -0
- package/lib/cjs/components/carousel/index.d.ts +7 -0
- package/lib/cjs/components/carousel/index.d.ts.map +1 -0
- package/lib/cjs/components/carousel/index.js +10 -0
- package/lib/cjs/components/carousel/index.js.map +1 -0
- package/lib/cjs/components/carousel/types.d.ts +36 -0
- package/lib/cjs/components/carousel/types.d.ts.map +1 -0
- package/lib/cjs/components/carousel/types.js +7 -0
- package/lib/cjs/components/carousel/types.js.map +1 -0
- package/lib/cjs/components/component.d.ts +3 -3
- package/lib/cjs/components/component.d.ts.map +1 -1
- package/lib/cjs/components/component.js +9 -1
- package/lib/cjs/components/component.js.map +1 -1
- package/lib/cjs/components/datatable/datatable-checkbox.d.ts +1 -1
- package/lib/cjs/components/datatable/datatable-checkbox.d.ts.map +1 -1
- package/lib/cjs/components/datatable/datatable-checkbox.js +1 -1
- package/lib/cjs/components/datatable/datatable-checkbox.js.map +1 -1
- package/lib/cjs/components/datatable/datatable-sort.d.ts +1 -1
- package/lib/cjs/components/datatable/datatable-sort.d.ts.map +1 -1
- package/lib/cjs/components/datatable/datatable-sort.js.map +1 -1
- package/lib/cjs/components/datatable/datatable.d.ts +2 -0
- package/lib/cjs/components/datatable/datatable.d.ts.map +1 -1
- package/lib/cjs/components/datatable/datatable.js +29 -16
- package/lib/cjs/components/datatable/datatable.js.map +1 -1
- package/lib/cjs/components/datatable/types.d.ts +2 -1
- package/lib/cjs/components/datatable/types.d.ts.map +1 -1
- package/lib/cjs/components/drawer/drawer.d.ts.map +1 -1
- package/lib/cjs/components/drawer/drawer.js +3 -16
- package/lib/cjs/components/drawer/drawer.js.map +1 -1
- package/lib/cjs/components/dropdown/dropdown.d.ts +1 -1
- package/lib/cjs/components/dropdown/dropdown.d.ts.map +1 -1
- package/lib/cjs/components/dropdown/dropdown.js +2 -3
- package/lib/cjs/components/dropdown/dropdown.js.map +1 -1
- package/lib/cjs/components/pin-input/index.d.ts +3 -0
- package/lib/cjs/components/pin-input/index.d.ts.map +1 -0
- package/lib/cjs/components/pin-input/index.js +6 -0
- package/lib/cjs/components/pin-input/index.js.map +1 -0
- package/lib/cjs/components/pin-input/pin-input.d.ts +56 -0
- package/lib/cjs/components/pin-input/pin-input.d.ts.map +1 -0
- package/lib/cjs/components/pin-input/pin-input.js +455 -0
- package/lib/cjs/components/pin-input/pin-input.js.map +1 -0
- package/lib/cjs/components/pin-input/types.d.ts +41 -0
- package/lib/cjs/components/pin-input/types.d.ts.map +1 -0
- package/lib/cjs/components/pin-input/types.js +6 -0
- package/lib/cjs/components/pin-input/types.js.map +1 -0
- package/lib/cjs/components/rating/rating.d.ts.map +1 -1
- package/lib/cjs/components/rating/rating.js.map +1 -1
- package/lib/cjs/components/select/combobox.d.ts.map +1 -1
- package/lib/cjs/components/select/combobox.js +25 -15
- package/lib/cjs/components/select/combobox.js.map +1 -1
- package/lib/cjs/components/select/config.d.ts +2 -2
- package/lib/cjs/components/select/config.d.ts.map +1 -1
- package/lib/cjs/components/select/config.js +10 -9
- package/lib/cjs/components/select/config.js.map +1 -1
- package/lib/cjs/components/select/dropdown.js.map +1 -1
- package/lib/cjs/components/select/option.d.ts +2 -1
- package/lib/cjs/components/select/option.d.ts.map +1 -1
- package/lib/cjs/components/select/option.js +9 -3
- package/lib/cjs/components/select/option.js.map +1 -1
- package/lib/cjs/components/select/remote.d.ts +1 -0
- package/lib/cjs/components/select/remote.d.ts.map +1 -1
- package/lib/cjs/components/select/remote.js +21 -14
- package/lib/cjs/components/select/remote.js.map +1 -1
- package/lib/cjs/components/select/search.d.ts +1 -1
- package/lib/cjs/components/select/search.d.ts.map +1 -1
- package/lib/cjs/components/select/search.js +34 -25
- package/lib/cjs/components/select/search.js.map +1 -1
- package/lib/cjs/components/select/select.d.ts +5 -3
- package/lib/cjs/components/select/select.d.ts.map +1 -1
- package/lib/cjs/components/select/select.js +31 -31
- package/lib/cjs/components/select/select.js.map +1 -1
- package/lib/cjs/components/select/tags.d.ts.map +1 -1
- package/lib/cjs/components/select/tags.js +22 -13
- package/lib/cjs/components/select/tags.js.map +1 -1
- package/lib/cjs/components/select/templates.d.ts.map +1 -1
- package/lib/cjs/components/select/templates.js +4 -4
- package/lib/cjs/components/select/templates.js.map +1 -1
- package/lib/cjs/components/select/types.d.ts +1 -1
- package/lib/cjs/components/select/types.d.ts.map +1 -1
- package/lib/cjs/components/select/utils.d.ts +4 -4
- package/lib/cjs/components/select/utils.d.ts.map +1 -1
- package/lib/cjs/components/select/utils.js +2 -3
- package/lib/cjs/components/select/utils.js.map +1 -1
- package/lib/cjs/components/sticky/sticky.d.ts +1 -1
- package/lib/cjs/components/sticky/sticky.d.ts.map +1 -1
- package/lib/cjs/components/sticky/sticky.js +13 -13
- package/lib/cjs/components/sticky/sticky.js.map +1 -1
- package/lib/cjs/components/toast/toast.d.ts.map +1 -1
- package/lib/cjs/components/toast/toast.js +17 -9
- package/lib/cjs/components/toast/toast.js.map +1 -1
- package/lib/cjs/components/toast/types.d.ts +3 -0
- package/lib/cjs/components/toast/types.d.ts.map +1 -1
- package/lib/cjs/components/toggle-password/toggle-password.d.ts.map +1 -1
- package/lib/cjs/components/toggle-password/toggle-password.js.map +1 -1
- package/lib/cjs/helpers/dom.d.ts +4 -4
- package/lib/cjs/helpers/dom.d.ts.map +1 -1
- package/lib/cjs/helpers/dom.js +8 -10
- package/lib/cjs/helpers/dom.js.map +1 -1
- package/lib/cjs/helpers/event-handler.d.ts +1 -1
- package/lib/cjs/helpers/event-handler.d.ts.map +1 -1
- package/lib/cjs/helpers/event-handler.js +3 -1
- package/lib/cjs/helpers/event-handler.js.map +1 -1
- package/lib/cjs/helpers/utils.d.ts +1 -1
- package/lib/cjs/helpers/utils.d.ts.map +1 -1
- package/lib/cjs/helpers/utils.js +4 -1
- package/lib/cjs/helpers/utils.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/cjs/types.d.ts +1 -1
- package/lib/cjs/types.d.ts.map +1 -1
- package/lib/esm/components/carousel/carousel.d.ts +102 -0
- package/lib/esm/components/carousel/carousel.d.ts.map +1 -0
- package/lib/esm/components/carousel/carousel.js +766 -0
- package/lib/esm/components/carousel/carousel.js.map +1 -0
- package/lib/esm/components/carousel/index.d.ts +7 -0
- package/lib/esm/components/carousel/index.d.ts.map +1 -0
- package/lib/esm/components/carousel/index.js +6 -0
- package/lib/esm/components/carousel/index.js.map +1 -0
- package/lib/esm/components/carousel/types.d.ts +36 -0
- package/lib/esm/components/carousel/types.d.ts.map +1 -0
- package/lib/esm/components/carousel/types.js +6 -0
- package/lib/esm/components/carousel/types.js.map +1 -0
- package/lib/esm/components/component.d.ts +3 -3
- package/lib/esm/components/component.d.ts.map +1 -1
- package/lib/esm/components/component.js +9 -1
- package/lib/esm/components/component.js.map +1 -1
- package/lib/esm/components/datatable/datatable-checkbox.d.ts +1 -1
- package/lib/esm/components/datatable/datatable-checkbox.d.ts.map +1 -1
- package/lib/esm/components/datatable/datatable-checkbox.js +1 -1
- package/lib/esm/components/datatable/datatable-checkbox.js.map +1 -1
- package/lib/esm/components/datatable/datatable-sort.d.ts +1 -1
- package/lib/esm/components/datatable/datatable-sort.d.ts.map +1 -1
- package/lib/esm/components/datatable/datatable-sort.js.map +1 -1
- package/lib/esm/components/datatable/datatable.d.ts +2 -0
- package/lib/esm/components/datatable/datatable.d.ts.map +1 -1
- package/lib/esm/components/datatable/datatable.js +29 -16
- package/lib/esm/components/datatable/datatable.js.map +1 -1
- package/lib/esm/components/datatable/types.d.ts +2 -1
- package/lib/esm/components/datatable/types.d.ts.map +1 -1
- package/lib/esm/components/drawer/drawer.d.ts.map +1 -1
- package/lib/esm/components/drawer/drawer.js +3 -16
- package/lib/esm/components/drawer/drawer.js.map +1 -1
- package/lib/esm/components/dropdown/dropdown.d.ts +1 -1
- package/lib/esm/components/dropdown/dropdown.d.ts.map +1 -1
- package/lib/esm/components/dropdown/dropdown.js +2 -3
- package/lib/esm/components/dropdown/dropdown.js.map +1 -1
- package/lib/esm/components/pin-input/index.d.ts +3 -0
- package/lib/esm/components/pin-input/index.d.ts.map +1 -0
- package/lib/esm/components/pin-input/index.js +2 -0
- package/lib/esm/components/pin-input/index.js.map +1 -0
- package/lib/esm/components/pin-input/pin-input.d.ts +56 -0
- package/lib/esm/components/pin-input/pin-input.d.ts.map +1 -0
- package/lib/esm/components/pin-input/pin-input.js +452 -0
- package/lib/esm/components/pin-input/pin-input.js.map +1 -0
- package/lib/esm/components/pin-input/types.d.ts +41 -0
- package/lib/esm/components/pin-input/types.d.ts.map +1 -0
- package/lib/esm/components/pin-input/types.js +5 -0
- package/lib/esm/components/pin-input/types.js.map +1 -0
- package/lib/esm/components/rating/rating.d.ts.map +1 -1
- package/lib/esm/components/rating/rating.js.map +1 -1
- package/lib/esm/components/select/combobox.d.ts.map +1 -1
- package/lib/esm/components/select/combobox.js +25 -15
- package/lib/esm/components/select/combobox.js.map +1 -1
- package/lib/esm/components/select/config.d.ts +2 -2
- package/lib/esm/components/select/config.d.ts.map +1 -1
- package/lib/esm/components/select/config.js +10 -9
- package/lib/esm/components/select/config.js.map +1 -1
- package/lib/esm/components/select/dropdown.js.map +1 -1
- package/lib/esm/components/select/option.d.ts +2 -1
- package/lib/esm/components/select/option.d.ts.map +1 -1
- package/lib/esm/components/select/option.js +9 -3
- package/lib/esm/components/select/option.js.map +1 -1
- package/lib/esm/components/select/remote.d.ts +1 -0
- package/lib/esm/components/select/remote.d.ts.map +1 -1
- package/lib/esm/components/select/remote.js +21 -14
- package/lib/esm/components/select/remote.js.map +1 -1
- package/lib/esm/components/select/search.d.ts +1 -1
- package/lib/esm/components/select/search.d.ts.map +1 -1
- package/lib/esm/components/select/search.js +34 -25
- package/lib/esm/components/select/search.js.map +1 -1
- package/lib/esm/components/select/select.d.ts +5 -3
- package/lib/esm/components/select/select.d.ts.map +1 -1
- package/lib/esm/components/select/select.js +31 -31
- package/lib/esm/components/select/select.js.map +1 -1
- package/lib/esm/components/select/tags.d.ts.map +1 -1
- package/lib/esm/components/select/tags.js +22 -13
- package/lib/esm/components/select/tags.js.map +1 -1
- package/lib/esm/components/select/templates.d.ts.map +1 -1
- package/lib/esm/components/select/templates.js +4 -4
- package/lib/esm/components/select/templates.js.map +1 -1
- package/lib/esm/components/select/types.d.ts +1 -1
- package/lib/esm/components/select/types.d.ts.map +1 -1
- package/lib/esm/components/select/utils.d.ts +4 -4
- package/lib/esm/components/select/utils.d.ts.map +1 -1
- package/lib/esm/components/select/utils.js +2 -3
- package/lib/esm/components/select/utils.js.map +1 -1
- package/lib/esm/components/sticky/sticky.d.ts +1 -1
- package/lib/esm/components/sticky/sticky.d.ts.map +1 -1
- package/lib/esm/components/sticky/sticky.js +13 -13
- package/lib/esm/components/sticky/sticky.js.map +1 -1
- package/lib/esm/components/toast/toast.d.ts.map +1 -1
- package/lib/esm/components/toast/toast.js +17 -9
- package/lib/esm/components/toast/toast.js.map +1 -1
- package/lib/esm/components/toast/types.d.ts +3 -0
- package/lib/esm/components/toast/types.d.ts.map +1 -1
- package/lib/esm/components/toggle-password/toggle-password.d.ts.map +1 -1
- package/lib/esm/components/toggle-password/toggle-password.js.map +1 -1
- package/lib/esm/helpers/dom.d.ts +4 -4
- package/lib/esm/helpers/dom.d.ts.map +1 -1
- package/lib/esm/helpers/dom.js +8 -10
- package/lib/esm/helpers/dom.js.map +1 -1
- package/lib/esm/helpers/event-handler.d.ts +1 -1
- package/lib/esm/helpers/event-handler.d.ts.map +1 -1
- package/lib/esm/helpers/event-handler.js +3 -1
- package/lib/esm/helpers/event-handler.js.map +1 -1
- package/lib/esm/helpers/utils.d.ts +1 -1
- package/lib/esm/helpers/utils.d.ts.map +1 -1
- package/lib/esm/helpers/utils.js +4 -1
- package/lib/esm/helpers/utils.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/lib/esm/types.d.ts +1 -1
- package/lib/esm/types.d.ts.map +1 -1
- package/package.json +5 -2
- package/src/components/carousel/__tests__/carousel.test.ts +326 -0
- package/src/components/carousel/carousel.css +42 -0
- package/src/components/carousel/carousel.ts +847 -0
- package/src/components/carousel/index.ts +11 -0
- package/src/components/carousel/types.ts +38 -0
- package/src/components/clipboard/__tests__/clipboard.test.ts +4 -4
- package/src/components/component.ts +15 -5
- package/src/components/datatable/__tests__/currency-sort.test.ts +4 -3
- package/src/components/datatable/__tests__/pagination-reset.test.ts +7 -4
- package/src/components/datatable/__tests__/setup.ts +1 -1
- package/src/components/datatable/datatable-checkbox.ts +6 -4
- package/src/components/datatable/datatable-sort.ts +27 -7
- package/src/components/datatable/datatable.ts +64 -37
- package/src/components/datatable/types.ts +3 -1
- package/src/components/drawer/drawer.ts +3 -18
- package/src/components/dropdown/dropdown.ts +2 -3
- package/src/components/pin-input/__tests__/pin-input.test.ts +928 -0
- package/src/components/pin-input/index.ts +6 -0
- package/src/components/pin-input/pin-input.ts +499 -0
- package/src/components/pin-input/types.ts +45 -0
- package/src/components/rating/rating.ts +0 -1
- package/src/components/repeater/__tests__/repeater.test.ts +5 -5
- package/src/components/select/__tests__/ux-behaviors.test.ts +4 -3
- package/src/components/select/combobox.ts +23 -16
- package/src/components/select/config.ts +15 -14
- package/src/components/select/dropdown.ts +1 -1
- package/src/components/select/option.ts +14 -4
- package/src/components/select/remote.ts +68 -56
- package/src/components/select/search.ts +30 -27
- package/src/components/select/select.ts +41 -37
- package/src/components/select/tags.ts +14 -8
- package/src/components/select/templates.ts +11 -6
- package/src/components/select/types.ts +1 -1
- package/src/components/select/utils.ts +7 -9
- package/src/components/sticky/sticky.ts +2 -2
- package/src/components/toast/toast.ts +34 -21
- package/src/components/toast/types.ts +5 -1
- package/src/components/toggle-password/toggle-password.ts +0 -1
- package/src/helpers/dom.ts +14 -17
- package/src/helpers/event-handler.ts +5 -6
- package/src/helpers/utils.ts +5 -2
- package/src/index.ts +18 -0
- package/src/types.ts +1 -1
- package/styles.css +1 -0
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KTUI - PIN / OTP multi-field input
|
|
3
|
+
* Copyright 2025 by Keenthemes Inc
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import KTData from '../../helpers/data';
|
|
7
|
+
import KTComponent from '../component';
|
|
8
|
+
import {
|
|
9
|
+
KTPinInputConfigInterface,
|
|
10
|
+
KTPinInputEventPayloadInterface,
|
|
11
|
+
KTPinInputInterface,
|
|
12
|
+
} from './types';
|
|
13
|
+
|
|
14
|
+
declare global {
|
|
15
|
+
interface Window {
|
|
16
|
+
KTPinInput: typeof KTPinInput;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const ITEM_SELECTOR = '[data-kt-pin-input-item]';
|
|
21
|
+
|
|
22
|
+
export class KTPinInput extends KTComponent implements KTPinInputInterface {
|
|
23
|
+
protected override _name: string = 'pin-input';
|
|
24
|
+
|
|
25
|
+
protected override _defaultConfig: KTPinInputConfigInterface = {
|
|
26
|
+
lazy: false,
|
|
27
|
+
availableChars: '[0-9]',
|
|
28
|
+
name: '',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
protected override _config: KTPinInputConfigInterface = this
|
|
32
|
+
._defaultConfig as KTPinInputConfigInterface;
|
|
33
|
+
|
|
34
|
+
private _cells: HTMLInputElement[] = [];
|
|
35
|
+
private _hiddenInput: HTMLInputElement | null = null;
|
|
36
|
+
private _charRegex: RegExp | null = null;
|
|
37
|
+
private _wasComplete = false;
|
|
38
|
+
|
|
39
|
+
private _onKeydownBound = (e: KeyboardEvent) => this._onKeydown(e);
|
|
40
|
+
private _onBeforeInputBound = (e: Event) => this._onBeforeInput(e);
|
|
41
|
+
private _onInputBound = (e: Event) => this._onInput(e);
|
|
42
|
+
private _onPasteBound = (e: ClipboardEvent) => this._onPaste(e);
|
|
43
|
+
|
|
44
|
+
constructor(
|
|
45
|
+
element: HTMLElement,
|
|
46
|
+
config: KTPinInputConfigInterface | null = null,
|
|
47
|
+
) {
|
|
48
|
+
super();
|
|
49
|
+
|
|
50
|
+
const cells = KTPinInput.collectItems(element);
|
|
51
|
+
if (cells.length === 0) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (this._shouldSkipInit(element)) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
this._cells = cells;
|
|
60
|
+
this._init(element);
|
|
61
|
+
this._buildConfig(config);
|
|
62
|
+
this._compileRegex();
|
|
63
|
+
this._ensureHiddenInput();
|
|
64
|
+
this._prepareCells();
|
|
65
|
+
|
|
66
|
+
const el = this._element;
|
|
67
|
+
if (el) {
|
|
68
|
+
el.addEventListener('keydown', this._onKeydownBound, true);
|
|
69
|
+
el.addEventListener('beforeinput', this._onBeforeInputBound, true);
|
|
70
|
+
el.addEventListener('input', this._onInputBound, true);
|
|
71
|
+
el.addEventListener('paste', this._onPasteBound, true);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
this._syncFromDom(undefined, { silent: true });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private static collectItems(root: HTMLElement): HTMLInputElement[] {
|
|
78
|
+
return Array.from(
|
|
79
|
+
root.querySelectorAll<HTMLInputElement>(ITEM_SELECTOR),
|
|
80
|
+
).filter((el) => root.contains(el));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private _compileRegex(): void {
|
|
84
|
+
const raw = this._getOption('availableChars');
|
|
85
|
+
const pattern =
|
|
86
|
+
typeof raw === 'string' && raw.trim() !== '' ? raw.trim() : '[0-9]';
|
|
87
|
+
try {
|
|
88
|
+
this._charRegex = new RegExp(pattern);
|
|
89
|
+
} catch {
|
|
90
|
+
this._charRegex = /[0-9]/;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private _isValidChar(char: string): boolean {
|
|
95
|
+
return Boolean(char && this._charRegex?.test(char));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private _enabledCells(): HTMLInputElement[] {
|
|
99
|
+
return this._cells.filter((c) => !c.disabled);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private _prepareCells(): void {
|
|
103
|
+
for (const cell of this._cells) {
|
|
104
|
+
cell.maxLength = 1;
|
|
105
|
+
cell.setAttribute('maxlength', '1');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private _activeCell(target: EventTarget | null): HTMLInputElement | null {
|
|
110
|
+
if (!(target instanceof HTMLInputElement)) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
if (!this._cells.includes(target) || target.disabled) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
return target;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private _cellIndex(cell: HTMLInputElement): number {
|
|
120
|
+
return this._cells.indexOf(cell);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private _focusNextEnabled(afterCell: HTMLInputElement): void {
|
|
124
|
+
const enabled = this._enabledCells();
|
|
125
|
+
const idx = enabled.indexOf(afterCell);
|
|
126
|
+
if (idx < 0 || idx >= enabled.length - 1) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const next = enabled[idx + 1];
|
|
130
|
+
next.focus();
|
|
131
|
+
next.select();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private _fillCellAndAdvance(cell: HTMLInputElement, char: string): void {
|
|
135
|
+
cell.value = char;
|
|
136
|
+
this._syncFromDom(this._cellIndex(cell));
|
|
137
|
+
this._focusNextEnabled(cell);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private _routeCharFromCell(target: HTMLInputElement, char: string): void {
|
|
141
|
+
const start = target.selectionStart ?? 0;
|
|
142
|
+
const end = target.selectionEnd ?? 0;
|
|
143
|
+
const v = target.value;
|
|
144
|
+
const replacing = end > start;
|
|
145
|
+
const atEnd = start === end && start === v.length;
|
|
146
|
+
if (v.length >= 1 && !replacing && atEnd) {
|
|
147
|
+
const enabled = this._enabledCells();
|
|
148
|
+
const idx = enabled.indexOf(target);
|
|
149
|
+
if (idx >= 0 && idx < enabled.length - 1) {
|
|
150
|
+
this._fillCellAndAdvance(enabled[idx + 1], char);
|
|
151
|
+
}
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
this._fillCellAndAdvance(target, char);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private _ensureHiddenInput(): void {
|
|
158
|
+
const raw = this._getOption('name');
|
|
159
|
+
const name = typeof raw === 'string' ? raw.trim() : '';
|
|
160
|
+
if (!name || !this._element) {
|
|
161
|
+
this._hiddenInput = null;
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
let h = this._element.querySelector<HTMLInputElement>(
|
|
166
|
+
'input[type="hidden"][data-kt-pin-input-hidden]',
|
|
167
|
+
);
|
|
168
|
+
if (!h) {
|
|
169
|
+
h = document.createElement('input');
|
|
170
|
+
h.type = 'hidden';
|
|
171
|
+
h.name = name;
|
|
172
|
+
h.setAttribute('data-kt-pin-input-hidden', 'true');
|
|
173
|
+
this._element.appendChild(h);
|
|
174
|
+
}
|
|
175
|
+
this._hiddenInput = h;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private _focusCell(index: number, select = true): void {
|
|
179
|
+
const enabled = this._enabledCells();
|
|
180
|
+
const target = enabled[index];
|
|
181
|
+
if (!target) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
target.focus();
|
|
185
|
+
if (select) {
|
|
186
|
+
target.select();
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
private _focusRelative(cell: HTMLInputElement, delta: number): void {
|
|
191
|
+
const enabled = this._enabledCells();
|
|
192
|
+
const idx = enabled.indexOf(cell);
|
|
193
|
+
if (idx < 0) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const next = idx + delta;
|
|
197
|
+
if (next >= 0 && next < enabled.length) {
|
|
198
|
+
enabled[next].focus();
|
|
199
|
+
enabled[next].select();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private _filterString(str: string): string {
|
|
204
|
+
let out = '';
|
|
205
|
+
for (let i = 0; i < str.length; i++) {
|
|
206
|
+
const ch = str[i];
|
|
207
|
+
if (this._isValidChar(ch)) {
|
|
208
|
+
out += ch;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return out;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
private _buildPayload(cellIndex?: number): KTPinInputEventPayloadInterface {
|
|
215
|
+
const value = this.getValue();
|
|
216
|
+
const enabled = this._enabledCells();
|
|
217
|
+
const filled = enabled.filter((c) => Boolean(c.value)).length;
|
|
218
|
+
const complete = enabled.length > 0 && filled === enabled.length;
|
|
219
|
+
return {
|
|
220
|
+
value,
|
|
221
|
+
complete,
|
|
222
|
+
cellCount: enabled.length,
|
|
223
|
+
filledCount: filled,
|
|
224
|
+
...(cellIndex !== undefined ? { cellIndex } : {}),
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
private _emit(payload: KTPinInputEventPayloadInterface): void {
|
|
229
|
+
this._fireEvent('input', payload);
|
|
230
|
+
this._dispatchEvent('kt.pin-input.input', payload);
|
|
231
|
+
this._dispatchEvent('kt.pin-input.change', payload);
|
|
232
|
+
|
|
233
|
+
if (payload.complete && !this._wasComplete) {
|
|
234
|
+
this._dispatchEvent('kt.pin-input.complete', payload);
|
|
235
|
+
}
|
|
236
|
+
this._wasComplete = payload.complete;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private _syncHidden(value: string): void {
|
|
240
|
+
if (this._hiddenInput) {
|
|
241
|
+
this._hiddenInput.value = value;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
private _syncFromDom(cellIndex?: number, opts?: { silent?: boolean }): void {
|
|
246
|
+
const payload = this._buildPayload(cellIndex);
|
|
247
|
+
this._syncHidden(payload.value);
|
|
248
|
+
if (opts?.silent) {
|
|
249
|
+
this._wasComplete = payload.complete;
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
this._emit(payload);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private _onKeydown(e: KeyboardEvent): void {
|
|
256
|
+
const target = this._activeCell(e.target);
|
|
257
|
+
if (!target) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (e.key === 'Backspace') {
|
|
262
|
+
e.preventDefault();
|
|
263
|
+
if (target.value) {
|
|
264
|
+
target.value = '';
|
|
265
|
+
this._syncFromDom(this._cellIndex(target));
|
|
266
|
+
} else {
|
|
267
|
+
this._focusRelative(target, -1);
|
|
268
|
+
}
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (e.key === 'ArrowLeft') {
|
|
273
|
+
e.preventDefault();
|
|
274
|
+
this._focusRelative(target, -1);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
if (e.key === 'ArrowRight') {
|
|
278
|
+
e.preventDefault();
|
|
279
|
+
this._focusRelative(target, 1);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
if (e.key === 'Home') {
|
|
283
|
+
e.preventDefault();
|
|
284
|
+
this._focusCell(0);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
if (e.key === 'End') {
|
|
288
|
+
e.preventDefault();
|
|
289
|
+
this._focusCell(this._enabledCells().length - 1);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (e.ctrlKey || e.metaKey || e.altKey) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (e.key.length === 1) {
|
|
298
|
+
if (!this._isValidChar(e.key)) {
|
|
299
|
+
e.preventDefault();
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
e.preventDefault();
|
|
303
|
+
this._routeCharFromCell(target, e.key);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
private _onBeforeInput(e: Event): void {
|
|
308
|
+
if (!('inputType' in e)) {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
const ie = e as InputEvent;
|
|
312
|
+
if (ie.isComposing) {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
const target = this._activeCell(ie.target);
|
|
316
|
+
if (!target) {
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (
|
|
321
|
+
ie.inputType === 'insertFromPaste' ||
|
|
322
|
+
ie.inputType === 'insertFromYank'
|
|
323
|
+
) {
|
|
324
|
+
e.preventDefault();
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (ie.inputType !== 'insertText' || ie.data == null) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const data = ie.data;
|
|
333
|
+
if (data.length > 1) {
|
|
334
|
+
e.preventDefault();
|
|
335
|
+
const filtered = this._filterString(data);
|
|
336
|
+
if (filtered.length) {
|
|
337
|
+
this._distributeFromIndex(this._cellIndex(target), filtered);
|
|
338
|
+
}
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (data.length !== 1) {
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (!this._isValidChar(data)) {
|
|
347
|
+
e.preventDefault();
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const start = target.selectionStart ?? 0;
|
|
352
|
+
const end = target.selectionEnd ?? 0;
|
|
353
|
+
const v = target.value;
|
|
354
|
+
const replacing = end > start;
|
|
355
|
+
const nextLen = replacing ? v.length - (end - start) + 1 : v.length + 1;
|
|
356
|
+
if (nextLen > 1 && !replacing) {
|
|
357
|
+
e.preventDefault();
|
|
358
|
+
this._routeCharFromCell(target, data);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
private _onInput(e: Event): void {
|
|
363
|
+
const target = this._activeCell(e.target);
|
|
364
|
+
if (!target) {
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const v = target.value;
|
|
369
|
+
if (v.length > 1) {
|
|
370
|
+
const filtered = this._filterString(v);
|
|
371
|
+
target.value = '';
|
|
372
|
+
if (filtered.length) {
|
|
373
|
+
this._distributeFromIndex(this._cellIndex(target), filtered);
|
|
374
|
+
} else {
|
|
375
|
+
this._syncFromDom(this._cellIndex(target));
|
|
376
|
+
}
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (v.length === 1 && !this._isValidChar(v)) {
|
|
381
|
+
target.value = '';
|
|
382
|
+
this._syncFromDom(this._cellIndex(target));
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
this._syncFromDom(this._cellIndex(target));
|
|
387
|
+
if (v.length === 1) {
|
|
388
|
+
this._focusNextEnabled(target);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
private _onPaste(e: ClipboardEvent): void {
|
|
393
|
+
const target = this._activeCell(e.target);
|
|
394
|
+
if (!target) {
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
e.preventDefault();
|
|
398
|
+
const text = e.clipboardData?.getData('text') || '';
|
|
399
|
+
const filtered = this._filterString(text);
|
|
400
|
+
this._distributeFromIndex(this._cellIndex(target), filtered);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
private _distributeFromIndex(startCellIndex: number, chars: string): void {
|
|
404
|
+
if (chars.length === 0) {
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
const startCell = this._cells[startCellIndex];
|
|
408
|
+
if (!startCell || startCell.disabled) {
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
const enabled = this._enabledCells();
|
|
412
|
+
const startIdx = enabled.indexOf(startCell);
|
|
413
|
+
if (startIdx < 0) {
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
let charPos = 0;
|
|
417
|
+
for (let i = startIdx; i < enabled.length && charPos < chars.length; i++) {
|
|
418
|
+
enabled[i].value = chars[charPos];
|
|
419
|
+
charPos++;
|
|
420
|
+
}
|
|
421
|
+
if (charPos > 0) {
|
|
422
|
+
const lastIdx = Math.min(startIdx + charPos - 1, enabled.length - 1);
|
|
423
|
+
enabled[lastIdx].focus();
|
|
424
|
+
enabled[lastIdx].select();
|
|
425
|
+
}
|
|
426
|
+
this._syncFromDom(startCellIndex);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
public getValue(): string {
|
|
430
|
+
return this._cells.map((c) => (c.disabled ? '' : c.value || '')).join('');
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
public setValue(value: string): void {
|
|
434
|
+
const enabled = this._enabledCells();
|
|
435
|
+
const filtered = this._filterString(typeof value === 'string' ? value : '');
|
|
436
|
+
for (let i = 0; i < enabled.length; i++) {
|
|
437
|
+
enabled[i].value = filtered[i] ?? '';
|
|
438
|
+
}
|
|
439
|
+
this._syncFromDom(0);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
public override dispose(): void {
|
|
443
|
+
const el = this._element;
|
|
444
|
+
if (el) {
|
|
445
|
+
el.removeEventListener('keydown', this._onKeydownBound, true);
|
|
446
|
+
el.removeEventListener('beforeinput', this._onBeforeInputBound, true);
|
|
447
|
+
el.removeEventListener('input', this._onInputBound, true);
|
|
448
|
+
el.removeEventListener('paste', this._onPasteBound, true);
|
|
449
|
+
}
|
|
450
|
+
this._cells = [];
|
|
451
|
+
this._hiddenInput = null;
|
|
452
|
+
this._charRegex = null;
|
|
453
|
+
super.dispose();
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
public static getInstance(element: HTMLElement): KTPinInput | null {
|
|
457
|
+
if (!element) {
|
|
458
|
+
return null;
|
|
459
|
+
}
|
|
460
|
+
if (KTData.has(element, 'pin-input')) {
|
|
461
|
+
return KTData.get(element, 'pin-input') as KTPinInput;
|
|
462
|
+
}
|
|
463
|
+
return null;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
public static getOrCreateInstance(
|
|
467
|
+
element: HTMLElement,
|
|
468
|
+
config?: KTPinInputConfigInterface,
|
|
469
|
+
): KTPinInput | null {
|
|
470
|
+
const existing = this.getInstance(element);
|
|
471
|
+
if (existing) {
|
|
472
|
+
return existing;
|
|
473
|
+
}
|
|
474
|
+
if (this.collectItems(element).length === 0) {
|
|
475
|
+
return null;
|
|
476
|
+
}
|
|
477
|
+
new KTPinInput(element, config ?? undefined);
|
|
478
|
+
return this.getInstance(element);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
public static createInstances(): void {
|
|
482
|
+
document
|
|
483
|
+
.querySelectorAll<HTMLElement>('[data-kt-pin-input]')
|
|
484
|
+
.forEach((el) => {
|
|
485
|
+
if (el.getAttribute('data-kt-pin-input-lazy') === 'true') {
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
new KTPinInput(el);
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
public static init(): void {
|
|
493
|
+
KTPinInput.createInstances();
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
if (typeof window !== 'undefined') {
|
|
498
|
+
window.KTPinInput = KTPinInput;
|
|
499
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KtUI - PIN / OTP multi-field input
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface KTPinInputConfigInterface {
|
|
6
|
+
/**
|
|
7
|
+
* When true, skip auto-init; initialize manually with `getOrCreateInstance`.
|
|
8
|
+
*/
|
|
9
|
+
lazy?: boolean;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Regular expression pattern string tested against **each** typed or pasted character.
|
|
13
|
+
* Default `[0-9]` (digits only). Examples: `[0-9a-fA-F]` (hex), `[a-zA-Z0-9]` (alphanumeric).
|
|
14
|
+
*/
|
|
15
|
+
availableChars?: string;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* If set, keeps a hidden `input[type="hidden"]` in sync for form posts.
|
|
19
|
+
* Also read from `data-kt-pin-input-name` on the root.
|
|
20
|
+
*/
|
|
21
|
+
name?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface KTPinInputEventPayloadInterface {
|
|
25
|
+
/** Concatenated values of all cells (including empty slots as empty string per cell). */
|
|
26
|
+
value: string;
|
|
27
|
+
/** True when every enabled cell has at least one character. */
|
|
28
|
+
complete: boolean;
|
|
29
|
+
/** Total enabled cell count. */
|
|
30
|
+
cellCount: number;
|
|
31
|
+
/** Enabled cells that currently have a value. */
|
|
32
|
+
filledCount: number;
|
|
33
|
+
/** Index of the cell that triggered the update, if known. */
|
|
34
|
+
cellIndex?: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface KTPinInputInterface {
|
|
38
|
+
getOption(name: string): unknown;
|
|
39
|
+
getElement(): HTMLElement;
|
|
40
|
+
getValue(): string;
|
|
41
|
+
setValue(value: string): void;
|
|
42
|
+
on(eventType: string, callback: CallableFunction): string;
|
|
43
|
+
off(eventType: string, eventId: string): void;
|
|
44
|
+
dispose(): void;
|
|
45
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Tests for KTRepeater component
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { describe, it, expect,
|
|
5
|
+
import { describe, it, expect, afterEach } from 'vitest';
|
|
6
6
|
import { KTRepeater } from '../repeater';
|
|
7
7
|
|
|
8
8
|
function createFixture(options?: {
|
|
@@ -161,7 +161,7 @@ describe('KTRepeater', () => {
|
|
|
161
161
|
|
|
162
162
|
describe('limit', () => {
|
|
163
163
|
it('does not add more clones when at limit', () => {
|
|
164
|
-
const {
|
|
164
|
+
const { trigger, wrapper } = createFixture({ limit: 2 });
|
|
165
165
|
const instance = new KTRepeater(trigger);
|
|
166
166
|
expect(wrapper.children.length).toBe(1);
|
|
167
167
|
instance.add();
|
|
@@ -174,7 +174,7 @@ describe('KTRepeater', () => {
|
|
|
174
174
|
});
|
|
175
175
|
|
|
176
176
|
it('disables trigger when at limit', () => {
|
|
177
|
-
const {
|
|
177
|
+
const { trigger } = createFixture({ limit: 2 });
|
|
178
178
|
const instance = new KTRepeater(trigger);
|
|
179
179
|
expect(trigger.hasAttribute('disabled')).toBe(false);
|
|
180
180
|
instance.add();
|
|
@@ -201,7 +201,7 @@ describe('KTRepeater', () => {
|
|
|
201
201
|
});
|
|
202
202
|
|
|
203
203
|
it('allows unlimited clones when limit is 0 or omitted', () => {
|
|
204
|
-
const {
|
|
204
|
+
const { trigger, wrapper } = createFixture();
|
|
205
205
|
trigger.removeAttribute('data-kt-repeater-limit');
|
|
206
206
|
const instance = new KTRepeater(trigger);
|
|
207
207
|
for (let i = 0; i < 5; i++) instance.add();
|
|
@@ -279,7 +279,7 @@ describe('KTRepeater', () => {
|
|
|
279
279
|
});
|
|
280
280
|
|
|
281
281
|
it('createInstances initializes all data-kt-repeater elements', () => {
|
|
282
|
-
const { container,
|
|
282
|
+
const { container, trigger } = createFixture();
|
|
283
283
|
const trigger2 = document.createElement('button');
|
|
284
284
|
trigger2.setAttribute('data-kt-repeater', '');
|
|
285
285
|
trigger2.setAttribute('data-kt-repeater-target', '#repeater-target');
|
|
@@ -34,7 +34,7 @@ describe('KTSelect UX Behaviors', () => {
|
|
|
34
34
|
/**
|
|
35
35
|
* Helper to wait for KTSelect to fully initialize
|
|
36
36
|
*/
|
|
37
|
-
const waitForInit = async (
|
|
37
|
+
const waitForInit = async (_select: KTSelect): Promise<void> => {
|
|
38
38
|
// Wait for async initialization - KTSelect uses promises for setup
|
|
39
39
|
await waitFor(200);
|
|
40
40
|
// Wait for next tick to ensure all modules are initialized
|
|
@@ -52,7 +52,8 @@ describe('KTSelect UX Behaviors', () => {
|
|
|
52
52
|
// Clean up all KTSelect instances
|
|
53
53
|
const selects = document.querySelectorAll('.kt-select');
|
|
54
54
|
selects.forEach((select) => {
|
|
55
|
-
const instance = (select as
|
|
55
|
+
const instance = (select as HTMLElement & { instance?: KTSelect })
|
|
56
|
+
.instance;
|
|
56
57
|
if (instance && typeof instance.destroy === 'function') {
|
|
57
58
|
instance.destroy();
|
|
58
59
|
}
|
|
@@ -60,7 +61,7 @@ describe('KTSelect UX Behaviors', () => {
|
|
|
60
61
|
|
|
61
62
|
// Clear document body
|
|
62
63
|
document.body.innerHTML = '';
|
|
63
|
-
container = null as
|
|
64
|
+
container = null as unknown as HTMLElement;
|
|
64
65
|
|
|
65
66
|
// Clear all event listeners
|
|
66
67
|
vi.clearAllMocks();
|