@keenthemes/ktui 1.2.4 → 1.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/dist/ktui.js +2551 -2817
  2. package/dist/ktui.min.js +1 -1
  3. package/dist/ktui.min.js.map +1 -1
  4. package/dist/styles.css +136 -40
  5. package/lib/cjs/components/datatable/datatable-checkbox.d.ts.map +1 -1
  6. package/lib/cjs/components/datatable/datatable-checkbox.js +34 -15
  7. package/lib/cjs/components/datatable/datatable-checkbox.js.map +1 -1
  8. package/lib/cjs/components/datatable/datatable-contracts.d.ts +3 -3
  9. package/lib/cjs/components/datatable/datatable-contracts.d.ts.map +1 -1
  10. package/lib/cjs/components/datatable/datatable-layout-plugin.d.ts +7 -0
  11. package/lib/cjs/components/datatable/datatable-layout-plugin.d.ts.map +1 -0
  12. package/lib/cjs/components/datatable/datatable-layout-plugin.js +328 -0
  13. package/lib/cjs/components/datatable/datatable-layout-plugin.js.map +1 -0
  14. package/lib/cjs/components/datatable/datatable-local-provider.d.ts +2 -2
  15. package/lib/cjs/components/datatable/datatable-local-provider.d.ts.map +1 -1
  16. package/lib/cjs/components/datatable/datatable-local-provider.js +18 -10
  17. package/lib/cjs/components/datatable/datatable-local-provider.js.map +1 -1
  18. package/lib/cjs/components/datatable/datatable-pagination-renderer.d.ts.map +1 -1
  19. package/lib/cjs/components/datatable/datatable-pagination-renderer.js +40 -25
  20. package/lib/cjs/components/datatable/datatable-pagination-renderer.js.map +1 -1
  21. package/lib/cjs/components/datatable/datatable-remote-provider.d.ts.map +1 -1
  22. package/lib/cjs/components/datatable/datatable-remote-provider.js +3 -0
  23. package/lib/cjs/components/datatable/datatable-remote-provider.js.map +1 -1
  24. package/lib/cjs/components/datatable/datatable-table-renderer.d.ts.map +1 -1
  25. package/lib/cjs/components/datatable/datatable-table-renderer.js +14 -6
  26. package/lib/cjs/components/datatable/datatable-table-renderer.js.map +1 -1
  27. package/lib/cjs/components/datatable/datatable.d.ts +9 -0
  28. package/lib/cjs/components/datatable/datatable.d.ts.map +1 -1
  29. package/lib/cjs/components/datatable/datatable.js +200 -61
  30. package/lib/cjs/components/datatable/datatable.js.map +1 -1
  31. package/lib/cjs/components/datatable/index.d.ts +1 -1
  32. package/lib/cjs/components/datatable/index.d.ts.map +1 -1
  33. package/lib/cjs/components/datatable/types.d.ts +27 -0
  34. package/lib/cjs/components/datatable/types.d.ts.map +1 -1
  35. package/lib/cjs/components/dropdown/dropdown.d.ts +2 -2
  36. package/lib/cjs/components/dropdown/dropdown.d.ts.map +1 -1
  37. package/lib/cjs/components/dropdown/dropdown.js +68 -31
  38. package/lib/cjs/components/dropdown/dropdown.js.map +1 -1
  39. package/lib/cjs/components/input-number/index.d.ts +7 -0
  40. package/lib/cjs/components/input-number/index.d.ts.map +1 -0
  41. package/lib/cjs/components/input-number/index.js +10 -0
  42. package/lib/cjs/components/input-number/index.js.map +1 -0
  43. package/lib/cjs/components/input-number/input-number.d.ts +40 -0
  44. package/lib/cjs/components/input-number/input-number.d.ts.map +1 -0
  45. package/lib/cjs/components/input-number/input-number.js +248 -0
  46. package/lib/cjs/components/input-number/input-number.js.map +1 -0
  47. package/lib/cjs/components/input-number/types.d.ts +30 -0
  48. package/lib/cjs/components/input-number/types.d.ts.map +1 -0
  49. package/lib/cjs/components/input-number/types.js +7 -0
  50. package/lib/cjs/components/input-number/types.js.map +1 -0
  51. package/lib/cjs/components/select/config.d.ts +1 -0
  52. package/lib/cjs/components/select/config.d.ts.map +1 -1
  53. package/lib/cjs/components/select/config.js +2 -1
  54. package/lib/cjs/components/select/config.js.map +1 -1
  55. package/lib/cjs/components/select/select.d.ts +8 -1
  56. package/lib/cjs/components/select/select.d.ts.map +1 -1
  57. package/lib/cjs/components/select/select.js +14 -1
  58. package/lib/cjs/components/select/select.js.map +1 -1
  59. package/lib/cjs/components/select/tags.d.ts.map +1 -1
  60. package/lib/cjs/components/select/tags.js +10 -0
  61. package/lib/cjs/components/select/tags.js.map +1 -1
  62. package/lib/cjs/index.d.ts +5 -1
  63. package/lib/cjs/index.d.ts.map +1 -1
  64. package/lib/cjs/index.js +5 -1
  65. package/lib/cjs/index.js.map +1 -1
  66. package/lib/esm/components/datatable/datatable-checkbox.d.ts.map +1 -1
  67. package/lib/esm/components/datatable/datatable-checkbox.js +34 -15
  68. package/lib/esm/components/datatable/datatable-checkbox.js.map +1 -1
  69. package/lib/esm/components/datatable/datatable-contracts.d.ts +3 -3
  70. package/lib/esm/components/datatable/datatable-contracts.d.ts.map +1 -1
  71. package/lib/esm/components/datatable/datatable-layout-plugin.d.ts +7 -0
  72. package/lib/esm/components/datatable/datatable-layout-plugin.d.ts.map +1 -0
  73. package/lib/esm/components/datatable/datatable-layout-plugin.js +324 -0
  74. package/lib/esm/components/datatable/datatable-layout-plugin.js.map +1 -0
  75. package/lib/esm/components/datatable/datatable-local-provider.d.ts +2 -2
  76. package/lib/esm/components/datatable/datatable-local-provider.d.ts.map +1 -1
  77. package/lib/esm/components/datatable/datatable-local-provider.js +18 -10
  78. package/lib/esm/components/datatable/datatable-local-provider.js.map +1 -1
  79. package/lib/esm/components/datatable/datatable-pagination-renderer.d.ts.map +1 -1
  80. package/lib/esm/components/datatable/datatable-pagination-renderer.js +40 -25
  81. package/lib/esm/components/datatable/datatable-pagination-renderer.js.map +1 -1
  82. package/lib/esm/components/datatable/datatable-remote-provider.d.ts.map +1 -1
  83. package/lib/esm/components/datatable/datatable-remote-provider.js +3 -0
  84. package/lib/esm/components/datatable/datatable-remote-provider.js.map +1 -1
  85. package/lib/esm/components/datatable/datatable-table-renderer.d.ts.map +1 -1
  86. package/lib/esm/components/datatable/datatable-table-renderer.js +14 -6
  87. package/lib/esm/components/datatable/datatable-table-renderer.js.map +1 -1
  88. package/lib/esm/components/datatable/datatable.d.ts +9 -0
  89. package/lib/esm/components/datatable/datatable.d.ts.map +1 -1
  90. package/lib/esm/components/datatable/datatable.js +200 -61
  91. package/lib/esm/components/datatable/datatable.js.map +1 -1
  92. package/lib/esm/components/datatable/index.d.ts +1 -1
  93. package/lib/esm/components/datatable/index.d.ts.map +1 -1
  94. package/lib/esm/components/datatable/types.d.ts +27 -0
  95. package/lib/esm/components/datatable/types.d.ts.map +1 -1
  96. package/lib/esm/components/dropdown/dropdown.d.ts +2 -2
  97. package/lib/esm/components/dropdown/dropdown.d.ts.map +1 -1
  98. package/lib/esm/components/dropdown/dropdown.js +68 -31
  99. package/lib/esm/components/dropdown/dropdown.js.map +1 -1
  100. package/lib/esm/components/input-number/index.d.ts +7 -0
  101. package/lib/esm/components/input-number/index.d.ts.map +1 -0
  102. package/lib/esm/components/input-number/index.js +6 -0
  103. package/lib/esm/components/input-number/index.js.map +1 -0
  104. package/lib/esm/components/input-number/input-number.d.ts +40 -0
  105. package/lib/esm/components/input-number/input-number.d.ts.map +1 -0
  106. package/lib/esm/components/input-number/input-number.js +245 -0
  107. package/lib/esm/components/input-number/input-number.js.map +1 -0
  108. package/lib/esm/components/input-number/types.d.ts +30 -0
  109. package/lib/esm/components/input-number/types.d.ts.map +1 -0
  110. package/lib/esm/components/input-number/types.js +6 -0
  111. package/lib/esm/components/input-number/types.js.map +1 -0
  112. package/lib/esm/components/select/config.d.ts +1 -0
  113. package/lib/esm/components/select/config.d.ts.map +1 -1
  114. package/lib/esm/components/select/config.js +2 -1
  115. package/lib/esm/components/select/config.js.map +1 -1
  116. package/lib/esm/components/select/select.d.ts +8 -1
  117. package/lib/esm/components/select/select.d.ts.map +1 -1
  118. package/lib/esm/components/select/select.js +14 -1
  119. package/lib/esm/components/select/select.js.map +1 -1
  120. package/lib/esm/components/select/tags.d.ts.map +1 -1
  121. package/lib/esm/components/select/tags.js +11 -1
  122. package/lib/esm/components/select/tags.js.map +1 -1
  123. package/lib/esm/index.d.ts +5 -1
  124. package/lib/esm/index.d.ts.map +1 -1
  125. package/lib/esm/index.js +3 -0
  126. package/lib/esm/index.js.map +1 -1
  127. package/package.json +5 -11
  128. package/src/components/datatable/__tests__/locked-layout.test.ts +257 -0
  129. package/src/components/datatable/__tests__/pagination-reset.test.ts +18 -0
  130. package/src/components/datatable/datatable-checkbox.ts +35 -27
  131. package/src/components/datatable/datatable-contracts.ts +3 -3
  132. package/src/components/datatable/datatable-layout-plugin.ts +449 -0
  133. package/src/components/datatable/datatable-local-provider.ts +21 -14
  134. package/src/components/datatable/datatable-pagination-renderer.ts +40 -29
  135. package/src/components/datatable/datatable-remote-provider.ts +3 -0
  136. package/src/components/datatable/datatable-table-renderer.ts +40 -32
  137. package/src/components/datatable/datatable.css +98 -0
  138. package/src/components/datatable/datatable.ts +223 -86
  139. package/src/components/datatable/index.ts +5 -0
  140. package/src/components/datatable/types.ts +33 -0
  141. package/src/components/dropdown/dropdown.ts +86 -58
  142. package/src/components/input/input-group.css +14 -1
  143. package/src/components/input-number/__tests__/input-number.test.ts +278 -0
  144. package/src/components/input-number/index.ts +11 -0
  145. package/src/components/input-number/input-number.ts +267 -0
  146. package/src/components/input-number/types.ts +32 -0
  147. package/src/components/select/__tests__/ux-behaviors.test.ts +72 -0
  148. package/src/components/select/config.ts +3 -1
  149. package/src/components/select/select.css +23 -20
  150. package/src/components/select/select.ts +15 -1
  151. package/src/components/select/tags.ts +14 -1
  152. package/src/index.ts +14 -0
@@ -0,0 +1,267 @@
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
+ KTInputNumberConfigInterface,
10
+ KTInputNumberEventPayloadInterface,
11
+ KTInputNumberInterface,
12
+ } from './types';
13
+
14
+ declare global {
15
+ interface Window {
16
+ KTInputNumber: typeof KTInputNumber;
17
+ }
18
+ }
19
+
20
+ export class KTInputNumber
21
+ extends KTComponent
22
+ implements KTInputNumberInterface
23
+ {
24
+ protected override _name: string = 'input-number';
25
+ protected override _defaultConfig: KTInputNumberConfigInterface = {};
26
+ protected override _config: KTInputNumberConfigInterface =
27
+ this._defaultConfig;
28
+ protected _numberInput: HTMLInputElement | null = null;
29
+ protected _onNativeInput: ((e: Event) => void) | null = null;
30
+ protected _onNativeChange: ((e: Event) => void) | null = null;
31
+ protected _onDecrementClick: ((e: Event) => void) | null = null;
32
+ protected _onIncrementClick: ((e: Event) => void) | null = null;
33
+ protected _decrementElement: HTMLElement | null = null;
34
+ protected _incrementElement: HTMLElement | null = null;
35
+
36
+ constructor(
37
+ element: HTMLElement,
38
+ config: KTInputNumberConfigInterface | null = null,
39
+ ) {
40
+ super();
41
+
42
+ const input = KTInputNumber.findNumberInput(element);
43
+ if (!input) {
44
+ return;
45
+ }
46
+
47
+ if (this._shouldSkipInit(element)) {
48
+ return;
49
+ }
50
+
51
+ this._numberInput = input;
52
+ this._init(element);
53
+ this._buildConfig(config);
54
+
55
+ this._onNativeInput = this._handleNativeInput.bind(this);
56
+ this._onNativeChange = this._handleNativeChange.bind(this);
57
+ this._element?.addEventListener('input', this._onNativeInput);
58
+ this._element?.addEventListener('change', this._onNativeChange);
59
+
60
+ this._decrementElement =
61
+ this._element?.querySelector<HTMLElement>(
62
+ '[data-kt-input-number-decrement]',
63
+ ) ?? null;
64
+ this._incrementElement =
65
+ this._element?.querySelector<HTMLElement>(
66
+ '[data-kt-input-number-increment]',
67
+ ) ?? null;
68
+
69
+ this._onDecrementClick = (e: Event) => {
70
+ e.preventDefault();
71
+ if (!this._numberInput || this._numberInput.disabled) return;
72
+ if (typeof this._numberInput.stepDown === 'function') {
73
+ this._numberInput.stepDown();
74
+ }
75
+ };
76
+ this._onIncrementClick = (e: Event) => {
77
+ e.preventDefault();
78
+ if (!this._numberInput || this._numberInput.disabled) return;
79
+ if (typeof this._numberInput.stepUp === 'function') {
80
+ this._numberInput.stepUp();
81
+ }
82
+ };
83
+
84
+ if (this._decrementElement && this._onDecrementClick) {
85
+ this._decrementElement.addEventListener('click', this._onDecrementClick);
86
+ }
87
+ if (this._incrementElement && this._onIncrementClick) {
88
+ this._incrementElement.addEventListener('click', this._onIncrementClick);
89
+ }
90
+ }
91
+
92
+ private static findNumberInput(root: HTMLElement): HTMLInputElement | null {
93
+ if (root instanceof HTMLInputElement && root.type === 'number') {
94
+ return root;
95
+ }
96
+ return root.querySelector<HTMLInputElement>('input[type="number"]');
97
+ }
98
+
99
+ protected _getNumericMin(): number | undefined {
100
+ const input = this._numberInput;
101
+ if (!input) return undefined;
102
+ if (typeof input.min === 'string' && input.min !== '') {
103
+ const n = parseFloat(input.min);
104
+ if (Number.isFinite(n)) return n;
105
+ }
106
+ return undefined;
107
+ }
108
+
109
+ protected _getNumericMax(): number | undefined {
110
+ const input = this._numberInput;
111
+ if (!input) return undefined;
112
+ if (typeof input.max === 'string' && input.max !== '') {
113
+ const n = parseFloat(input.max);
114
+ if (Number.isFinite(n)) return n;
115
+ }
116
+ return undefined;
117
+ }
118
+
119
+ protected _getStepForPayload(): number | undefined {
120
+ const input = this._numberInput;
121
+ if (!input) return undefined;
122
+ const raw = input.getAttribute('step');
123
+ if (raw === 'any') return undefined;
124
+ if (raw === null || raw === '') return 1;
125
+ const n = parseFloat(raw);
126
+ return Number.isFinite(n) && n > 0 ? n : 1;
127
+ }
128
+
129
+ protected _getCurrentNumericValue(): number | null {
130
+ const input = this._numberInput;
131
+ if (!input) return null;
132
+ if (input.value === '') {
133
+ return null;
134
+ }
135
+ const n =
136
+ typeof input.valueAsNumber === 'number' &&
137
+ !Number.isNaN(input.valueAsNumber)
138
+ ? input.valueAsNumber
139
+ : parseFloat(input.value);
140
+ return Number.isFinite(n) ? n : null;
141
+ }
142
+
143
+ protected _buildEventPayload(): KTInputNumberEventPayloadInterface {
144
+ const input = this._numberInput;
145
+ const min = this._getNumericMin();
146
+ const max = this._getNumericMax();
147
+ const step = this._getStepForPayload();
148
+ const value = this._getCurrentNumericValue();
149
+ const valueAsString = input?.value ?? '';
150
+ return {
151
+ value,
152
+ valueAsString,
153
+ ...(min !== undefined ? { min } : {}),
154
+ ...(max !== undefined ? { max } : {}),
155
+ ...(step !== undefined ? { step } : {}),
156
+ };
157
+ }
158
+
159
+ protected _handleNativeInput(_event: Event): void {
160
+ const event = _event as Event;
161
+ const target = event.target;
162
+ if (!(target instanceof HTMLInputElement) || target.type !== 'number') {
163
+ return;
164
+ }
165
+
166
+ this._numberInput = target;
167
+ const payload = this._buildEventPayload();
168
+ this._fireEvent('input', payload);
169
+ this._dispatchEvent('kt.input-number.input', payload);
170
+ }
171
+
172
+ protected _handleNativeChange(_event: Event): void {
173
+ const event = _event as Event;
174
+ const target = event.target;
175
+ if (!(target instanceof HTMLInputElement) || target.type !== 'number') {
176
+ return;
177
+ }
178
+
179
+ this._numberInput = target;
180
+ const payload = this._buildEventPayload();
181
+ this._fireEvent('change', payload);
182
+ this._dispatchEvent('kt.input-number.change', payload);
183
+ }
184
+
185
+ public getNumberInput(): HTMLInputElement | null {
186
+ return this._numberInput;
187
+ }
188
+
189
+ public getValue(): number | null {
190
+ return this._getCurrentNumericValue();
191
+ }
192
+
193
+ public override dispose(): void {
194
+ if (this._element) {
195
+ if (this._onNativeInput) {
196
+ this._element.removeEventListener('input', this._onNativeInput);
197
+ }
198
+ if (this._onNativeChange) {
199
+ this._element.removeEventListener('change', this._onNativeChange);
200
+ }
201
+ }
202
+ if (this._decrementElement && this._onDecrementClick) {
203
+ this._decrementElement.removeEventListener(
204
+ 'click',
205
+ this._onDecrementClick,
206
+ );
207
+ }
208
+ if (this._incrementElement && this._onIncrementClick) {
209
+ this._incrementElement.removeEventListener(
210
+ 'click',
211
+ this._onIncrementClick,
212
+ );
213
+ }
214
+ this._onNativeInput = null;
215
+ this._onNativeChange = null;
216
+ this._onDecrementClick = null;
217
+ this._onIncrementClick = null;
218
+ this._decrementElement = null;
219
+ this._incrementElement = null;
220
+ this._numberInput = null;
221
+ super.dispose();
222
+ }
223
+
224
+ public static getInstance(element: HTMLElement): KTInputNumber | null {
225
+ if (!element) {
226
+ return null;
227
+ }
228
+ if (KTData.has(element, 'input-number')) {
229
+ return KTData.get(element, 'input-number') as KTInputNumber;
230
+ }
231
+ return null;
232
+ }
233
+
234
+ public static getOrCreateInstance(
235
+ element: HTMLElement,
236
+ config?: KTInputNumberConfigInterface,
237
+ ): KTInputNumber | null {
238
+ const existing = this.getInstance(element);
239
+ if (existing) {
240
+ return existing;
241
+ }
242
+ if (!this.findNumberInput(element)) {
243
+ return null;
244
+ }
245
+ new KTInputNumber(element, config ?? undefined);
246
+ return this.getInstance(element);
247
+ }
248
+
249
+ public static createInstances(): void {
250
+ document
251
+ .querySelectorAll<HTMLElement>('[data-kt-input-number]')
252
+ .forEach((el) => {
253
+ if (el.getAttribute('data-kt-input-number-lazy') === 'true') {
254
+ return;
255
+ }
256
+ new KTInputNumber(el);
257
+ });
258
+ }
259
+
260
+ public static init(): void {
261
+ KTInputNumber.createInstances();
262
+ }
263
+ }
264
+
265
+ if (typeof window !== 'undefined') {
266
+ window.KTInputNumber = KTInputNumber;
267
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * KTUI - Free & Open-Source Tailwind UI Components by Keenthemes
3
+ * Copyright 2025 by Keenthemes Inc
4
+ */
5
+
6
+ export interface KTInputNumberConfigInterface {
7
+ /** Reserved for future options merged from data attributes. */
8
+ [key: string]: string | number | boolean | undefined;
9
+ }
10
+
11
+ export interface KTInputNumberEventPayloadInterface {
12
+ /** Parsed numeric value, or `null` when the input is empty or not a finite number. */
13
+ value: number | null;
14
+ /** Raw `value` attribute string of the controlled input. */
15
+ valueAsString: string;
16
+ /** Effective minimum when set on the input. */
17
+ min?: number;
18
+ /** Effective maximum when set on the input. */
19
+ max?: number;
20
+ /** Effective step when numeric; omitted when `step="any"` or missing. */
21
+ step?: number;
22
+ }
23
+
24
+ export interface KTInputNumberInterface {
25
+ getNumberInput(): HTMLInputElement | null;
26
+ getValue(): number | null;
27
+ getOption(name: string): unknown;
28
+ getElement(): HTMLElement | null;
29
+ on(eventType: string, callback: CallableFunction): string;
30
+ off(eventType: string, eventId: string): void;
31
+ dispose(): void;
32
+ }
@@ -1279,4 +1279,76 @@ describe('KTSelect UX Behaviors', () => {
1279
1279
  expect(dropdownOption?.getAttribute('aria-selected')).toBe('true');
1280
1280
  });
1281
1281
  });
1282
+
1283
+ describe('getValue()', () => {
1284
+ it('returns null for single-select with no explicit selection after clear', async () => {
1285
+ const selectEl = createSelectElement([
1286
+ { value: '1', text: 'Option 1' },
1287
+ { value: '2', text: 'Option 2' },
1288
+ ]);
1289
+ container.appendChild(selectEl);
1290
+
1291
+ const select = new KTSelect(selectEl, { height: 250 });
1292
+ await waitForInit(select);
1293
+
1294
+ const option1 = selectEl.querySelector(
1295
+ 'option[value="1"]',
1296
+ ) as HTMLOptionElement;
1297
+ select.setSelectedOptions([option1]);
1298
+ await waitFor(50);
1299
+ select.setSelectedOptions([]);
1300
+ await waitFor(50);
1301
+
1302
+ expect(select.getValue()).toBe(null);
1303
+ expect(select.getSelectedOptions()).toEqual([]);
1304
+ });
1305
+
1306
+ it('returns the selected value for single-select', async () => {
1307
+ const selectEl = createSelectElement([
1308
+ { value: '1', text: 'Option 1' },
1309
+ { value: '2', text: 'Option 2' },
1310
+ ]);
1311
+ container.appendChild(selectEl);
1312
+
1313
+ const select = new KTSelect(selectEl, { height: 250 });
1314
+ await waitForInit(select);
1315
+
1316
+ const option2 = selectEl.querySelector(
1317
+ 'option[value="2"]',
1318
+ ) as HTMLOptionElement;
1319
+ select.setSelectedOptions([option2]);
1320
+ await waitFor(50);
1321
+
1322
+ expect(select.getValue()).toBe('2');
1323
+ expect(select.getSelectedOptions()).toEqual(['2']);
1324
+ });
1325
+
1326
+ it('returns null for multiple-select even when options are selected', async () => {
1327
+ const selectEl = createSelectElement([
1328
+ { value: 'a', text: 'A' },
1329
+ { value: 'b', text: 'B' },
1330
+ ]);
1331
+ selectEl.setAttribute('multiple', 'multiple');
1332
+ container.appendChild(selectEl);
1333
+
1334
+ const select = new KTSelect(selectEl, {
1335
+ multiple: true,
1336
+ height: 250,
1337
+ });
1338
+ await waitForInit(select);
1339
+
1340
+ const optionA = selectEl.querySelector(
1341
+ 'option[value="a"]',
1342
+ ) as HTMLOptionElement;
1343
+ const optionB = selectEl.querySelector(
1344
+ 'option[value="b"]',
1345
+ ) as HTMLOptionElement;
1346
+ select.setSelectedOptions([optionA, optionB]);
1347
+ await waitFor(50);
1348
+
1349
+ expect(select.getValue()).toBe(null);
1350
+ expect(select.getSelectedOptions()).toContain('a');
1351
+ expect(select.getSelectedOptions()).toContain('b');
1352
+ });
1353
+ });
1282
1354
  });
@@ -59,7 +59,8 @@ export const DefaultConfig: KTSelectConfigInterface = {
59
59
  selectAllText: 'Select all', // Text for the "Select All" option (if implemented)
60
60
  clearAllText: 'Clear all', // Text for the "Clear All" option (if implemented)
61
61
  enableSelectAll: false, // Enable/disable "Select All" button for multi-select
62
- showSelectedCount: true, // Show the number of selected options in multi-select mode
62
+ showSelectedCount: false, // Tags mode: show "N selected" before chips when true
63
+ selectedCountText: '{{count}} selected', // Tags mode; use {{count}} placeholder
63
64
  renderSelected: undefined, // Custom function to render the selected value(s) in the display area
64
65
 
65
66
  // Accessibility & Usability
@@ -112,6 +113,7 @@ export interface KTSelectConfigInterface {
112
113
  clearAllText?: string;
113
114
  enableSelectAll?: boolean;
114
115
  showSelectedCount?: boolean;
116
+ selectedCountText?: string;
115
117
  renderSelected?: (selectedOptions: string[]) => string;
116
118
 
117
119
  // Accessibility & Usability
@@ -137,34 +137,37 @@
137
137
 
138
138
  /* Enhanced Tag Styles */
139
139
  .kt-select-tag {
140
- @apply inline-flex items-center gap-2 px-3 py-1.5 rounded-lg text-sm font-medium;
141
- @apply bg-accent/10 text-accent-foreground border border-border;
140
+ @apply inline-flex items-center gap-1 px-2 py-1 rounded-none text-xs font-medium leading-tight;
141
+ @apply bg-accent/10 text-accent-foreground border-[0.5px] border-border/80;
142
142
  @apply max-w-[200px] truncate flex-shrink-0;
143
- @apply shadow-sm;
144
- @apply leading-tight;
145
143
  }
146
144
 
147
145
  .kt-select-tag-remove {
148
- @apply flex items-center justify-center w-5 h-5 rounded-full;
146
+ @apply flex items-center justify-center size-4 rounded-full;
147
+ @apply border-0 bg-transparent p-0 shadow-none;
149
148
  @apply hover:bg-accent/20 text-muted-foreground hover:text-accent-foreground;
150
- @apply transition-all duration-200 cursor-pointer;
149
+ @apply transition-[color,background-color,transform] duration-200 cursor-pointer;
151
150
  @apply flex-shrink-0 opacity-100;
152
- @apply hover:scale-110;
151
+ @apply hover:scale-105;
153
152
  }
154
153
 
155
154
  .kt-select-tag-remove svg {
156
- @apply w-3.5 h-3.5;
155
+ @apply size-3;
157
156
  @apply transition-transform duration-200;
158
157
  }
159
158
 
160
159
  .kt-select-tag-remove:hover svg {
161
- @apply scale-110;
160
+ @apply scale-105;
161
+ }
162
+
163
+ .kt-select-selected-count {
164
+ @apply inline-flex items-center text-xs font-medium leading-tight text-muted-foreground flex-shrink-0;
162
165
  }
163
166
 
164
167
  /* Enhanced Multi-select display container */
165
168
  .kt-select-display[data-multiple='true'] {
166
- @apply flex flex-wrap items-center gap-2 p-3 min-h-[3rem];
167
- @apply w-full;
169
+ @apply flex flex-wrap items-center gap-1;
170
+ @apply w-full min-h-0;
168
171
  @apply bg-background border border-input rounded-md;
169
172
  @apply focus-within:border-ring focus-within:ring-2 focus-within:ring-ring/20;
170
173
  @apply transition-all duration-200;
@@ -178,8 +181,8 @@
178
181
  background-position: right 0.5rem center;
179
182
 
180
183
  &[data-multiple='true'] {
181
- @apply h-auto min-h-8.5 py-1.5 flex-wrap;
182
- background-position: right 0.5rem top 0.675rem;
184
+ @apply h-auto min-h-8.5 py-0 flex-wrap;
185
+ background-position: right 0.5rem center;
183
186
  }
184
187
  }
185
188
 
@@ -188,8 +191,8 @@
188
191
  background-position: right 0.5rem center;
189
192
 
190
193
  &[data-multiple='true'] {
191
- @apply h-auto min-h-7;
192
- background-position: right 0.5rem top 0.575rem;
194
+ @apply h-auto min-h-7 py-0 flex-wrap;
195
+ background-position: right 0.5rem center;
193
196
  }
194
197
  }
195
198
 
@@ -198,8 +201,8 @@
198
201
  background-position: right 0.6rem center;
199
202
 
200
203
  &[data-multiple='true'] {
201
- @apply h-auto min-h-10 py-2;
202
- background-position: right 0.6rem top 0.85rem;
204
+ @apply h-auto min-h-10 py-0 flex-wrap;
205
+ background-position: right 0.6rem center;
203
206
  }
204
207
  }
205
208
  }
@@ -211,7 +214,7 @@
211
214
  background-position: left 0.5rem center;
212
215
 
213
216
  &[data-multiple='true'] {
214
- background-position: left 0.5rem top 0.675rem;
217
+ background-position: left 0.5rem center;
215
218
  }
216
219
  }
217
220
 
@@ -219,7 +222,7 @@
219
222
  background-position: left 0.5rem center;
220
223
 
221
224
  &[data-multiple='true'] {
222
- background-position: left 0.5rem top 0.575rem;
225
+ background-position: left 0.5rem center;
223
226
  }
224
227
  }
225
228
 
@@ -227,7 +230,7 @@
227
230
  background-position: left 0.75rem center;
228
231
 
229
232
  &[data-multiple='true'] {
230
- background-position: left 0.75rem top 0.85rem;
233
+ background-position: left 0.75rem center;
231
234
  }
232
235
  }
233
236
  }
@@ -1627,12 +1627,26 @@ export class KTSelect extends KTComponent {
1627
1627
  }
1628
1628
 
1629
1629
  /**
1630
- * Get selected options
1630
+ * Returns the current selection as **option value** strings (not labels, not DOM nodes).
1631
+ * For single-select, the array has zero or one entry; use {@link getValue} for a scalar.
1632
+ * For multiple-select, the array may contain multiple values in arbitrary order.
1631
1633
  */
1632
1634
  public getSelectedOptions() {
1633
1635
  return this._state.getSelectedOptions();
1634
1636
  }
1635
1637
 
1638
+ /**
1639
+ * Returns the selected option **value** for single-select, or `null` when nothing is selected.
1640
+ * When the select is configured with `multiple: true`, always returns `null` — use {@link getSelectedOptions} instead.
1641
+ */
1642
+ public getValue(): string | null {
1643
+ if (this._config.multiple) {
1644
+ return null;
1645
+ }
1646
+ const values = this._state.getSelectedOptions();
1647
+ return values.length > 0 ? values[0] : null;
1648
+ }
1649
+
1636
1650
  /**
1637
1651
  * Get configuration
1638
1652
  */
@@ -6,7 +6,7 @@
6
6
  import { KTSelectConfigInterface } from './config';
7
7
  import { KTSelect } from './select';
8
8
  import { defaultTemplates } from './templates';
9
- import { EventManager } from './utils';
9
+ import { EventManager, renderTemplateString } from './utils';
10
10
 
11
11
  /**
12
12
  * KTSelectTags - Handles tags-specific functionality for KTSelect
@@ -50,6 +50,19 @@ export class KTSelectTags {
50
50
  // Clear all existing content before adding tags
51
51
  valueDisplayElement.innerHTML = '';
52
52
 
53
+ // Optional summary count (config exists for tags mode; non-tag display uses templates instead)
54
+ if (this._config.showSelectedCount && selectedOptions.length > 0) {
55
+ const countEl = document.createElement('span');
56
+ countEl.setAttribute('data-kt-select-selected-count', 'true');
57
+ countEl.className = 'kt-select-selected-count';
58
+ countEl.setAttribute('aria-live', 'polite');
59
+ countEl.textContent = renderTemplateString(
60
+ this._config.selectedCountText ?? '{{count}} selected',
61
+ { count: selectedOptions.length },
62
+ );
63
+ valueDisplayElement.appendChild(countEl);
64
+ }
65
+
53
66
  // Insert each tag before the display element
54
67
  selectedOptions.forEach((optionValue) => {
55
68
  // Find the original option element (in dropdown or select)
package/src/index.ts CHANGED
@@ -34,6 +34,7 @@ import { KTRepeater } from './components/repeater';
34
34
  import { KTClipboard } from './components/clipboard';
35
35
  import { KTRangeSlider } from './components/range-slider';
36
36
  import { KTPinInput } from './components/pin-input';
37
+ import { KTInputNumber } from './components/input-number';
37
38
  import { KTCarousel } from './components/carousel';
38
39
 
39
40
  export { KTDropdown } from './components/dropdown';
@@ -63,6 +64,7 @@ export { KTRepeater } from './components/repeater';
63
64
  export { KTClipboard } from './components/clipboard';
64
65
  export { KTRangeSlider } from './components/range-slider';
65
66
  export { KTPinInput } from './components/pin-input';
67
+ export { KTInputNumber } from './components/input-number';
66
68
  export { KTCarousel } from './components/carousel';
67
69
 
68
70
  export type {
@@ -85,6 +87,11 @@ export type {
85
87
  KTDataTableCheckConfigInterface,
86
88
  KTDataTableCheckInterface,
87
89
  KTDataTableCheckChangePayloadInterface,
90
+ KTDataTableLockedRowsConfigInterface,
91
+ KTDataTableLockedColumnsConfigInterface,
92
+ KTDataTableLockedLayoutConfigInterface,
93
+ KTDataTableLayoutPluginContextInterface,
94
+ KTDataTableLayoutPluginInterface,
88
95
  } from './components/datatable';
89
96
  export type {
90
97
  KTDismissConfigInterface,
@@ -137,6 +144,11 @@ export type {
137
144
  KTPinInputEventPayloadInterface,
138
145
  KTPinInputInterface,
139
146
  } from './components/pin-input';
147
+ export type {
148
+ KTInputNumberConfigInterface,
149
+ KTInputNumberEventPayloadInterface,
150
+ KTInputNumberInterface,
151
+ } from './components/input-number';
140
152
  export type {
141
153
  KTCarouselConfigInterface,
142
154
  KTCarouselChangePayloadInterface,
@@ -217,6 +229,7 @@ export const KTComponents = {
217
229
  KTClipboard.init();
218
230
  KTRangeSlider.init();
219
231
  KTPinInput.init();
232
+ KTInputNumber.init();
220
233
  KTCarousel.init();
221
234
  },
222
235
  };
@@ -254,6 +267,7 @@ declare global {
254
267
  KTClipboard: typeof KTClipboard;
255
268
  KTRangeSlider: typeof KTRangeSlider;
256
269
  KTPinInput: typeof KTPinInput;
270
+ KTInputNumber: typeof KTInputNumber;
257
271
  KTCarousel: typeof KTCarousel;
258
272
  KTComponents: typeof KTComponents;
259
273
  }