@keenthemes/ktui 1.2.3 → 1.2.5

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 (213) hide show
  1. package/dist/ktui.js +2244 -1061
  2. package/dist/ktui.min.js +1 -1
  3. package/dist/ktui.min.js.map +1 -1
  4. package/dist/styles.css +185 -40
  5. package/lib/cjs/components/context-menu/context-menu.d.ts +66 -0
  6. package/lib/cjs/components/context-menu/context-menu.d.ts.map +1 -0
  7. package/lib/cjs/components/context-menu/context-menu.js +423 -0
  8. package/lib/cjs/components/context-menu/context-menu.js.map +1 -0
  9. package/lib/cjs/components/context-menu/index.d.ts +7 -0
  10. package/lib/cjs/components/context-menu/index.d.ts.map +1 -0
  11. package/lib/cjs/components/context-menu/index.js +10 -0
  12. package/lib/cjs/components/context-menu/index.js.map +1 -0
  13. package/lib/cjs/components/context-menu/types.d.ts +30 -0
  14. package/lib/cjs/components/context-menu/types.d.ts.map +1 -0
  15. package/lib/cjs/components/context-menu/types.js +7 -0
  16. package/lib/cjs/components/context-menu/types.js.map +1 -0
  17. package/lib/cjs/components/datatable/datatable-checkbox.d.ts.map +1 -1
  18. package/lib/cjs/components/datatable/datatable-checkbox.js +34 -15
  19. package/lib/cjs/components/datatable/datatable-checkbox.js.map +1 -1
  20. package/lib/cjs/components/datatable/datatable-contracts.d.ts +66 -0
  21. package/lib/cjs/components/datatable/datatable-contracts.d.ts.map +1 -0
  22. package/lib/cjs/components/datatable/datatable-contracts.js +7 -0
  23. package/lib/cjs/components/datatable/datatable-contracts.js.map +1 -0
  24. package/lib/cjs/components/datatable/datatable-event-adapter.d.ts +7 -0
  25. package/lib/cjs/components/datatable/datatable-event-adapter.d.ts.map +1 -0
  26. package/lib/cjs/components/datatable/datatable-event-adapter.js +16 -0
  27. package/lib/cjs/components/datatable/datatable-event-adapter.js.map +1 -0
  28. package/lib/cjs/components/datatable/datatable-local-provider.d.ts +25 -0
  29. package/lib/cjs/components/datatable/datatable-local-provider.d.ts.map +1 -0
  30. package/lib/cjs/components/datatable/datatable-local-provider.js +190 -0
  31. package/lib/cjs/components/datatable/datatable-local-provider.js.map +1 -0
  32. package/lib/cjs/components/datatable/datatable-pagination-renderer.d.ts +15 -0
  33. package/lib/cjs/components/datatable/datatable-pagination-renderer.d.ts.map +1 -0
  34. package/lib/cjs/components/datatable/datatable-pagination-renderer.js +144 -0
  35. package/lib/cjs/components/datatable/datatable-pagination-renderer.js.map +1 -0
  36. package/lib/cjs/components/datatable/datatable-remote-provider.d.ts +25 -0
  37. package/lib/cjs/components/datatable/datatable-remote-provider.d.ts.map +1 -0
  38. package/lib/cjs/components/datatable/datatable-remote-provider.js +191 -0
  39. package/lib/cjs/components/datatable/datatable-remote-provider.js.map +1 -0
  40. package/lib/cjs/components/datatable/datatable-state-store.d.ts +21 -0
  41. package/lib/cjs/components/datatable/datatable-state-store.d.ts.map +1 -0
  42. package/lib/cjs/components/datatable/datatable-state-store.js +81 -0
  43. package/lib/cjs/components/datatable/datatable-state-store.js.map +1 -0
  44. package/lib/cjs/components/datatable/datatable-table-renderer.d.ts +16 -0
  45. package/lib/cjs/components/datatable/datatable-table-renderer.d.ts.map +1 -0
  46. package/lib/cjs/components/datatable/datatable-table-renderer.js +141 -0
  47. package/lib/cjs/components/datatable/datatable-table-renderer.js.map +1 -0
  48. package/lib/cjs/components/datatable/datatable.d.ts +9 -87
  49. package/lib/cjs/components/datatable/datatable.d.ts.map +1 -1
  50. package/lib/cjs/components/datatable/datatable.js +234 -740
  51. package/lib/cjs/components/datatable/datatable.js.map +1 -1
  52. package/lib/cjs/components/dropdown/dropdown.d.ts +2 -2
  53. package/lib/cjs/components/dropdown/dropdown.d.ts.map +1 -1
  54. package/lib/cjs/components/dropdown/dropdown.js +68 -31
  55. package/lib/cjs/components/dropdown/dropdown.js.map +1 -1
  56. package/lib/cjs/components/input-number/index.d.ts +7 -0
  57. package/lib/cjs/components/input-number/index.d.ts.map +1 -0
  58. package/lib/cjs/components/input-number/index.js +10 -0
  59. package/lib/cjs/components/input-number/index.js.map +1 -0
  60. package/lib/cjs/components/input-number/input-number.d.ts +40 -0
  61. package/lib/cjs/components/input-number/input-number.d.ts.map +1 -0
  62. package/lib/cjs/components/input-number/input-number.js +248 -0
  63. package/lib/cjs/components/input-number/input-number.js.map +1 -0
  64. package/lib/cjs/components/input-number/types.d.ts +30 -0
  65. package/lib/cjs/components/input-number/types.d.ts.map +1 -0
  66. package/lib/cjs/components/input-number/types.js +7 -0
  67. package/lib/cjs/components/input-number/types.js.map +1 -0
  68. package/lib/cjs/components/select/config.d.ts +1 -0
  69. package/lib/cjs/components/select/config.d.ts.map +1 -1
  70. package/lib/cjs/components/select/config.js +2 -1
  71. package/lib/cjs/components/select/config.js.map +1 -1
  72. package/lib/cjs/components/select/index.d.ts +1 -1
  73. package/lib/cjs/components/select/index.d.ts.map +1 -1
  74. package/lib/cjs/components/select/select.d.ts +8 -1
  75. package/lib/cjs/components/select/select.d.ts.map +1 -1
  76. package/lib/cjs/components/select/select.js +14 -1
  77. package/lib/cjs/components/select/select.js.map +1 -1
  78. package/lib/cjs/components/select/tags.d.ts.map +1 -1
  79. package/lib/cjs/components/select/tags.js +10 -0
  80. package/lib/cjs/components/select/tags.js.map +1 -1
  81. package/lib/cjs/index.d.ts +9 -1
  82. package/lib/cjs/index.d.ts.map +1 -1
  83. package/lib/cjs/index.js +11 -7
  84. package/lib/cjs/index.js.map +1 -1
  85. package/lib/cjs/init-all.d.ts +6 -0
  86. package/lib/cjs/init-all.d.ts.map +1 -0
  87. package/lib/cjs/init-all.js +17 -0
  88. package/lib/cjs/init-all.js.map +1 -0
  89. package/lib/cjs/legacy.d.ts +8 -0
  90. package/lib/cjs/legacy.d.ts.map +1 -0
  91. package/lib/cjs/legacy.js +26 -0
  92. package/lib/cjs/legacy.js.map +1 -0
  93. package/lib/esm/components/context-menu/context-menu.d.ts +66 -0
  94. package/lib/esm/components/context-menu/context-menu.d.ts.map +1 -0
  95. package/lib/esm/components/context-menu/context-menu.js +420 -0
  96. package/lib/esm/components/context-menu/context-menu.js.map +1 -0
  97. package/lib/esm/components/context-menu/index.d.ts +7 -0
  98. package/lib/esm/components/context-menu/index.d.ts.map +1 -0
  99. package/lib/esm/components/context-menu/index.js +6 -0
  100. package/lib/esm/components/context-menu/index.js.map +1 -0
  101. package/lib/esm/components/context-menu/types.d.ts +30 -0
  102. package/lib/esm/components/context-menu/types.d.ts.map +1 -0
  103. package/lib/esm/components/context-menu/types.js +6 -0
  104. package/lib/esm/components/context-menu/types.js.map +1 -0
  105. package/lib/esm/components/datatable/datatable-checkbox.d.ts.map +1 -1
  106. package/lib/esm/components/datatable/datatable-checkbox.js +34 -15
  107. package/lib/esm/components/datatable/datatable-checkbox.js.map +1 -1
  108. package/lib/esm/components/datatable/datatable-contracts.d.ts +66 -0
  109. package/lib/esm/components/datatable/datatable-contracts.d.ts.map +1 -0
  110. package/lib/esm/components/datatable/datatable-contracts.js +6 -0
  111. package/lib/esm/components/datatable/datatable-contracts.js.map +1 -0
  112. package/lib/esm/components/datatable/datatable-event-adapter.d.ts +7 -0
  113. package/lib/esm/components/datatable/datatable-event-adapter.d.ts.map +1 -0
  114. package/lib/esm/components/datatable/datatable-event-adapter.js +13 -0
  115. package/lib/esm/components/datatable/datatable-event-adapter.js.map +1 -0
  116. package/lib/esm/components/datatable/datatable-local-provider.d.ts +25 -0
  117. package/lib/esm/components/datatable/datatable-local-provider.d.ts.map +1 -0
  118. package/lib/esm/components/datatable/datatable-local-provider.js +187 -0
  119. package/lib/esm/components/datatable/datatable-local-provider.js.map +1 -0
  120. package/lib/esm/components/datatable/datatable-pagination-renderer.d.ts +15 -0
  121. package/lib/esm/components/datatable/datatable-pagination-renderer.d.ts.map +1 -0
  122. package/lib/esm/components/datatable/datatable-pagination-renderer.js +141 -0
  123. package/lib/esm/components/datatable/datatable-pagination-renderer.js.map +1 -0
  124. package/lib/esm/components/datatable/datatable-remote-provider.d.ts +25 -0
  125. package/lib/esm/components/datatable/datatable-remote-provider.d.ts.map +1 -0
  126. package/lib/esm/components/datatable/datatable-remote-provider.js +188 -0
  127. package/lib/esm/components/datatable/datatable-remote-provider.js.map +1 -0
  128. package/lib/esm/components/datatable/datatable-state-store.d.ts +21 -0
  129. package/lib/esm/components/datatable/datatable-state-store.d.ts.map +1 -0
  130. package/lib/esm/components/datatable/datatable-state-store.js +78 -0
  131. package/lib/esm/components/datatable/datatable-state-store.js.map +1 -0
  132. package/lib/esm/components/datatable/datatable-table-renderer.d.ts +16 -0
  133. package/lib/esm/components/datatable/datatable-table-renderer.d.ts.map +1 -0
  134. package/lib/esm/components/datatable/datatable-table-renderer.js +138 -0
  135. package/lib/esm/components/datatable/datatable-table-renderer.js.map +1 -0
  136. package/lib/esm/components/datatable/datatable.d.ts +9 -87
  137. package/lib/esm/components/datatable/datatable.d.ts.map +1 -1
  138. package/lib/esm/components/datatable/datatable.js +234 -740
  139. package/lib/esm/components/datatable/datatable.js.map +1 -1
  140. package/lib/esm/components/dropdown/dropdown.d.ts +2 -2
  141. package/lib/esm/components/dropdown/dropdown.d.ts.map +1 -1
  142. package/lib/esm/components/dropdown/dropdown.js +68 -31
  143. package/lib/esm/components/dropdown/dropdown.js.map +1 -1
  144. package/lib/esm/components/input-number/index.d.ts +7 -0
  145. package/lib/esm/components/input-number/index.d.ts.map +1 -0
  146. package/lib/esm/components/input-number/index.js +6 -0
  147. package/lib/esm/components/input-number/index.js.map +1 -0
  148. package/lib/esm/components/input-number/input-number.d.ts +40 -0
  149. package/lib/esm/components/input-number/input-number.d.ts.map +1 -0
  150. package/lib/esm/components/input-number/input-number.js +245 -0
  151. package/lib/esm/components/input-number/input-number.js.map +1 -0
  152. package/lib/esm/components/input-number/types.d.ts +30 -0
  153. package/lib/esm/components/input-number/types.d.ts.map +1 -0
  154. package/lib/esm/components/input-number/types.js +6 -0
  155. package/lib/esm/components/input-number/types.js.map +1 -0
  156. package/lib/esm/components/select/config.d.ts +1 -0
  157. package/lib/esm/components/select/config.d.ts.map +1 -1
  158. package/lib/esm/components/select/config.js +2 -1
  159. package/lib/esm/components/select/config.js.map +1 -1
  160. package/lib/esm/components/select/index.d.ts +1 -1
  161. package/lib/esm/components/select/index.d.ts.map +1 -1
  162. package/lib/esm/components/select/select.d.ts +8 -1
  163. package/lib/esm/components/select/select.d.ts.map +1 -1
  164. package/lib/esm/components/select/select.js +14 -1
  165. package/lib/esm/components/select/select.js.map +1 -1
  166. package/lib/esm/components/select/tags.d.ts.map +1 -1
  167. package/lib/esm/components/select/tags.js +11 -1
  168. package/lib/esm/components/select/tags.js.map +1 -1
  169. package/lib/esm/index.d.ts +9 -1
  170. package/lib/esm/index.d.ts.map +1 -1
  171. package/lib/esm/index.js +7 -5
  172. package/lib/esm/index.js.map +1 -1
  173. package/lib/esm/init-all.d.ts +6 -0
  174. package/lib/esm/init-all.d.ts.map +1 -0
  175. package/lib/esm/init-all.js +13 -0
  176. package/lib/esm/init-all.js.map +1 -0
  177. package/lib/esm/legacy.d.ts +8 -0
  178. package/lib/esm/legacy.d.ts.map +1 -0
  179. package/lib/esm/legacy.js +8 -0
  180. package/lib/esm/legacy.js.map +1 -0
  181. package/package.json +35 -11
  182. package/src/__tests__/entrypoints.test.ts +71 -0
  183. package/src/components/context-menu/__tests__/context-menu.test.ts +117 -0
  184. package/src/components/context-menu/context-menu.css +32 -0
  185. package/src/components/context-menu/context-menu.ts +529 -0
  186. package/src/components/context-menu/index.ts +10 -0
  187. package/src/components/context-menu/types.ts +32 -0
  188. package/src/components/datatable/__tests__/architecture-boundaries.test.ts +259 -0
  189. package/src/components/datatable/datatable-checkbox.ts +34 -23
  190. package/src/components/datatable/datatable-contracts.ts +96 -0
  191. package/src/components/datatable/datatable-event-adapter.ts +21 -0
  192. package/src/components/datatable/datatable-local-provider.ts +193 -0
  193. package/src/components/datatable/datatable-pagination-renderer.ts +225 -0
  194. package/src/components/datatable/datatable-remote-provider.ts +178 -0
  195. package/src/components/datatable/datatable-state-store.ts +94 -0
  196. package/src/components/datatable/datatable-table-renderer.ts +214 -0
  197. package/src/components/datatable/datatable.ts +250 -918
  198. package/src/components/dropdown/dropdown.ts +86 -58
  199. package/src/components/input/input-group.css +14 -1
  200. package/src/components/input-number/__tests__/input-number.test.ts +278 -0
  201. package/src/components/input-number/index.ts +11 -0
  202. package/src/components/input-number/input-number.ts +267 -0
  203. package/src/components/input-number/types.ts +32 -0
  204. package/src/components/select/__tests__/ux-behaviors.test.ts +72 -0
  205. package/src/components/select/config.ts +3 -1
  206. package/src/components/select/index.ts +1 -1
  207. package/src/components/select/select.css +23 -20
  208. package/src/components/select/select.ts +15 -1
  209. package/src/components/select/tags.ts +14 -1
  210. package/src/index.ts +18 -5
  211. package/src/init-all.ts +15 -0
  212. package/src/legacy.ts +9 -0
  213. package/styles.css +1 -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
@@ -15,4 +15,4 @@ export {
15
15
  EventManager,
16
16
  TypeToSearchBuffer,
17
17
  } from './utils';
18
- export { KTSelectConfigInterface, KTSelectOption } from './config';
18
+ export type { KTSelectConfigInterface, KTSelectOption } from './config';
@@ -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
@@ -8,6 +8,7 @@ import KTUtils from './helpers/utils';
8
8
  import KTEventHandler from './helpers/event-handler';
9
9
  import KTData from './helpers/data';
10
10
  import { KTDropdown } from './components/dropdown';
11
+ import { KTContextMenu } from './components/context-menu';
11
12
  import { KTModal } from './components/modal';
12
13
  import { KTDrawer } from './components/drawer';
13
14
  import { KTCollapse } from './components/collapse';
@@ -33,9 +34,11 @@ import { KTRepeater } from './components/repeater';
33
34
  import { KTClipboard } from './components/clipboard';
34
35
  import { KTRangeSlider } from './components/range-slider';
35
36
  import { KTPinInput } from './components/pin-input';
37
+ import { KTInputNumber } from './components/input-number';
36
38
  import { KTCarousel } from './components/carousel';
37
39
 
38
40
  export { KTDropdown } from './components/dropdown';
41
+ export { KTContextMenu } from './components/context-menu';
39
42
  export { KTModal } from './components/modal';
40
43
  export { KTDrawer } from './components/drawer';
41
44
  export { KTCollapse } from './components/collapse';
@@ -61,6 +64,7 @@ export { KTRepeater } from './components/repeater';
61
64
  export { KTClipboard } from './components/clipboard';
62
65
  export { KTRangeSlider } from './components/range-slider';
63
66
  export { KTPinInput } from './components/pin-input';
67
+ export { KTInputNumber } from './components/input-number';
64
68
  export { KTCarousel } from './components/carousel';
65
69
 
66
70
  export type {
@@ -96,6 +100,10 @@ export type {
96
100
  KTDropdownConfigInterface,
97
101
  KTDropdownInterface,
98
102
  } from './components/dropdown';
103
+ export type {
104
+ KTContextMenuConfigInterface,
105
+ KTContextMenuInterface,
106
+ } from './components/context-menu';
99
107
  export type {
100
108
  KTImageInputConfigInterface,
101
109
  KTImageInputInterface,
@@ -131,6 +139,11 @@ export type {
131
139
  KTPinInputEventPayloadInterface,
132
140
  KTPinInputInterface,
133
141
  } from './components/pin-input';
142
+ export type {
143
+ KTInputNumberConfigInterface,
144
+ KTInputNumberEventPayloadInterface,
145
+ KTInputNumberInterface,
146
+ } from './components/input-number';
134
147
  export type {
135
148
  KTCarouselConfigInterface,
136
149
  KTCarouselChangePayloadInterface,
@@ -182,9 +195,10 @@ export type {
182
195
  KTTooltipInterface,
183
196
  } from './components/tooltip';
184
197
 
185
- const KTComponents = {
198
+ export const KTComponents = {
186
199
  init(): void {
187
200
  KTDropdown.init();
201
+ KTContextMenu.init();
188
202
  KTModal.init();
189
203
  KTDrawer.init();
190
204
  KTCollapse.init();
@@ -210,6 +224,7 @@ const KTComponents = {
210
224
  KTClipboard.init();
211
225
  KTRangeSlider.init();
212
226
  KTPinInput.init();
227
+ KTInputNumber.init();
213
228
  KTCarousel.init();
214
229
  },
215
230
  };
@@ -221,6 +236,7 @@ declare global {
221
236
  KTEventHandler: typeof KTEventHandler;
222
237
  KTData: typeof KTData;
223
238
  KTDropdown: typeof KTDropdown;
239
+ KTContextMenu: typeof KTContextMenu;
224
240
  KTModal: typeof KTModal;
225
241
  KTDrawer: typeof KTDrawer;
226
242
  KTCollapse: typeof KTCollapse;
@@ -246,13 +262,10 @@ declare global {
246
262
  KTClipboard: typeof KTClipboard;
247
263
  KTRangeSlider: typeof KTRangeSlider;
248
264
  KTPinInput: typeof KTPinInput;
265
+ KTInputNumber: typeof KTInputNumber;
249
266
  KTCarousel: typeof KTCarousel;
250
267
  KTComponents: typeof KTComponents;
251
268
  }
252
269
  }
253
270
 
254
271
  export default KTComponents;
255
-
256
- KTDom.ready(() => {
257
- KTComponents.init();
258
- });
@@ -0,0 +1,15 @@
1
+ /**
2
+ * KTUI - Free & Open-Source Tailwind UI Components by Keenthemes
3
+ * Copyright 2025 by Keenthemes Inc
4
+ */
5
+
6
+ import KTDom from './helpers/dom';
7
+ import { KTComponents } from './index';
8
+
9
+ export const initAllComponents = (): void => {
10
+ KTDom.ready(() => {
11
+ KTComponents.init();
12
+ });
13
+ };
14
+
15
+ initAllComponents();
package/src/legacy.ts ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * KTUI - Free & Open-Source Tailwind UI Components by Keenthemes
3
+ * Copyright 2025 by Keenthemes Inc
4
+ */
5
+
6
+ export * from './index';
7
+ export { default } from './index';
8
+
9
+ import './init-all';
package/styles.css CHANGED
@@ -17,6 +17,7 @@
17
17
  @import './src/components/drawer/drawer.css';
18
18
  @import './src/components/dropdown/dropdown.css';
19
19
  @import './src/components/dropdown/dropdown-menu.css';
20
+ @import './src/components/context-menu/context-menu.css';
20
21
  @import './src/components/image-input/image-input.css';
21
22
  @import './src/components/input/input.css';
22
23
  @import './src/components/input/input-group.css';