@keenthemes/ktui 1.1.0 → 1.1.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.
Files changed (258) hide show
  1. package/README.md +0 -27
  2. package/dist/ktui.js +5269 -12550
  3. package/dist/ktui.min.js +1 -1
  4. package/dist/ktui.min.js.map +1 -1
  5. package/dist/styles.css +1133 -2706
  6. package/lib/cjs/components/datatable/__tests__/pagination-reset.test.js +596 -0
  7. package/lib/cjs/components/datatable/__tests__/pagination-reset.test.js.map +1 -0
  8. package/lib/cjs/components/datatable/__tests__/race-conditions.test.js +548 -0
  9. package/lib/cjs/components/datatable/__tests__/race-conditions.test.js.map +1 -0
  10. package/lib/cjs/components/datatable/__tests__/setup.js +63 -0
  11. package/lib/cjs/components/datatable/__tests__/setup.js.map +1 -0
  12. package/lib/cjs/components/datatable/datatable.js +92 -30
  13. package/lib/cjs/components/datatable/datatable.js.map +1 -1
  14. package/lib/cjs/components/select/combobox.js +0 -2
  15. package/lib/cjs/components/select/combobox.js.map +1 -1
  16. package/lib/cjs/components/select/config.js +4 -1
  17. package/lib/cjs/components/select/config.js.map +1 -1
  18. package/lib/cjs/components/select/dropdown.js +0 -16
  19. package/lib/cjs/components/select/dropdown.js.map +1 -1
  20. package/lib/cjs/components/select/remote.js +0 -40
  21. package/lib/cjs/components/select/remote.js.map +1 -1
  22. package/lib/cjs/components/select/search.js +80 -19
  23. package/lib/cjs/components/select/search.js.map +1 -1
  24. package/lib/cjs/components/select/select.js +98 -110
  25. package/lib/cjs/components/select/select.js.map +1 -1
  26. package/lib/cjs/components/select/tags.js +0 -2
  27. package/lib/cjs/components/select/tags.js.map +1 -1
  28. package/lib/cjs/index.js +1 -10
  29. package/lib/cjs/index.js.map +1 -1
  30. package/lib/esm/components/datatable/__tests__/pagination-reset.test.js +594 -0
  31. package/lib/esm/components/datatable/__tests__/pagination-reset.test.js.map +1 -0
  32. package/lib/esm/components/datatable/__tests__/race-conditions.test.js +546 -0
  33. package/lib/esm/components/datatable/__tests__/race-conditions.test.js.map +1 -0
  34. package/lib/esm/components/datatable/__tests__/setup.js +58 -0
  35. package/lib/esm/components/datatable/__tests__/setup.js.map +1 -0
  36. package/lib/esm/components/datatable/datatable.js +92 -30
  37. package/lib/esm/components/datatable/datatable.js.map +1 -1
  38. package/lib/esm/components/select/combobox.js +0 -2
  39. package/lib/esm/components/select/combobox.js.map +1 -1
  40. package/lib/esm/components/select/config.js +4 -1
  41. package/lib/esm/components/select/config.js.map +1 -1
  42. package/lib/esm/components/select/dropdown.js +0 -16
  43. package/lib/esm/components/select/dropdown.js.map +1 -1
  44. package/lib/esm/components/select/remote.js +0 -40
  45. package/lib/esm/components/select/remote.js.map +1 -1
  46. package/lib/esm/components/select/search.js +80 -19
  47. package/lib/esm/components/select/search.js.map +1 -1
  48. package/lib/esm/components/select/select.js +98 -110
  49. package/lib/esm/components/select/select.js.map +1 -1
  50. package/lib/esm/components/select/tags.js +0 -2
  51. package/lib/esm/components/select/tags.js.map +1 -1
  52. package/lib/esm/index.js +0 -7
  53. package/lib/esm/index.js.map +1 -1
  54. package/package.json +7 -9
  55. package/src/components/alert/alert.css +188 -429
  56. package/src/components/datatable/__tests__/pagination-reset.test.ts +657 -0
  57. package/src/components/datatable/__tests__/race-conditions.test.ts +455 -0
  58. package/src/components/datatable/__tests__/setup.ts +67 -0
  59. package/src/components/datatable/datatable.ts +66 -11
  60. package/src/components/input/input.css +0 -1
  61. package/src/components/select/__tests__/ux-behaviors.test.ts +619 -0
  62. package/src/components/select/combobox.ts +0 -1
  63. package/src/components/select/config.ts +7 -1
  64. package/src/components/select/dropdown.ts +0 -24
  65. package/src/components/select/remote.ts +0 -49
  66. package/src/components/select/search.ts +85 -21
  67. package/src/components/select/select.css +0 -1
  68. package/src/components/select/select.ts +118 -149
  69. package/src/components/select/tags.ts +0 -1
  70. package/src/components/select/variants.css +4 -0
  71. package/src/components/textarea/textarea.css +0 -1
  72. package/src/index.ts +0 -10
  73. package/styles.css +0 -1
  74. package/lib/cjs/components/alert/alert.js +0 -1025
  75. package/lib/cjs/components/alert/alert.js.map +0 -1
  76. package/lib/cjs/components/alert/index.js +0 -20
  77. package/lib/cjs/components/alert/index.js.map +0 -1
  78. package/lib/cjs/components/alert/templates.js +0 -120
  79. package/lib/cjs/components/alert/templates.js.map +0 -1
  80. package/lib/cjs/components/alert/types.js +0 -7
  81. package/lib/cjs/components/alert/types.js.map +0 -1
  82. package/lib/cjs/components/datepicker/config/config.js +0 -42
  83. package/lib/cjs/components/datepicker/config/config.js.map +0 -1
  84. package/lib/cjs/components/datepicker/config/index.js +0 -24
  85. package/lib/cjs/components/datepicker/config/index.js.map +0 -1
  86. package/lib/cjs/components/datepicker/config/interfaces.js +0 -7
  87. package/lib/cjs/components/datepicker/config/interfaces.js.map +0 -1
  88. package/lib/cjs/components/datepicker/config/types.js +0 -7
  89. package/lib/cjs/components/datepicker/config/types.js.map +0 -1
  90. package/lib/cjs/components/datepicker/core/event-manager.js +0 -135
  91. package/lib/cjs/components/datepicker/core/event-manager.js.map +0 -1
  92. package/lib/cjs/components/datepicker/core/focus-manager.js +0 -167
  93. package/lib/cjs/components/datepicker/core/focus-manager.js.map +0 -1
  94. package/lib/cjs/components/datepicker/core/helpers.js +0 -219
  95. package/lib/cjs/components/datepicker/core/helpers.js.map +0 -1
  96. package/lib/cjs/components/datepicker/core/index.js +0 -25
  97. package/lib/cjs/components/datepicker/core/index.js.map +0 -1
  98. package/lib/cjs/components/datepicker/core/unified-state-manager.js +0 -394
  99. package/lib/cjs/components/datepicker/core/unified-state-manager.js.map +0 -1
  100. package/lib/cjs/components/datepicker/datepicker.js +0 -2252
  101. package/lib/cjs/components/datepicker/datepicker.js.map +0 -1
  102. package/lib/cjs/components/datepicker/index.js +0 -24
  103. package/lib/cjs/components/datepicker/index.js.map +0 -1
  104. package/lib/cjs/components/datepicker/ui/index.js +0 -23
  105. package/lib/cjs/components/datepicker/ui/index.js.map +0 -1
  106. package/lib/cjs/components/datepicker/ui/input/dropdown.js +0 -489
  107. package/lib/cjs/components/datepicker/ui/input/dropdown.js.map +0 -1
  108. package/lib/cjs/components/datepicker/ui/input/index.js +0 -23
  109. package/lib/cjs/components/datepicker/ui/input/index.js.map +0 -1
  110. package/lib/cjs/components/datepicker/ui/input/segmented-input.js +0 -640
  111. package/lib/cjs/components/datepicker/ui/input/segmented-input.js.map +0 -1
  112. package/lib/cjs/components/datepicker/ui/renderers/calendar.js +0 -446
  113. package/lib/cjs/components/datepicker/ui/renderers/calendar.js.map +0 -1
  114. package/lib/cjs/components/datepicker/ui/renderers/footer.js +0 -42
  115. package/lib/cjs/components/datepicker/ui/renderers/footer.js.map +0 -1
  116. package/lib/cjs/components/datepicker/ui/renderers/header.js +0 -32
  117. package/lib/cjs/components/datepicker/ui/renderers/header.js.map +0 -1
  118. package/lib/cjs/components/datepicker/ui/renderers/index.js +0 -25
  119. package/lib/cjs/components/datepicker/ui/renderers/index.js.map +0 -1
  120. package/lib/cjs/components/datepicker/ui/renderers/time-picker.js +0 -384
  121. package/lib/cjs/components/datepicker/ui/renderers/time-picker.js.map +0 -1
  122. package/lib/cjs/components/datepicker/ui/templates/index.js +0 -22
  123. package/lib/cjs/components/datepicker/ui/templates/index.js.map +0 -1
  124. package/lib/cjs/components/datepicker/ui/templates/templates.js +0 -253
  125. package/lib/cjs/components/datepicker/ui/templates/templates.js.map +0 -1
  126. package/lib/cjs/components/datepicker/utils/date-formatters.js +0 -88
  127. package/lib/cjs/components/datepicker/utils/date-formatters.js.map +0 -1
  128. package/lib/cjs/components/datepicker/utils/date-utils.js +0 -194
  129. package/lib/cjs/components/datepicker/utils/date-utils.js.map +0 -1
  130. package/lib/cjs/components/datepicker/utils/index.js +0 -24
  131. package/lib/cjs/components/datepicker/utils/index.js.map +0 -1
  132. package/lib/cjs/components/datepicker/utils/time-utils.js +0 -213
  133. package/lib/cjs/components/datepicker/utils/time-utils.js.map +0 -1
  134. package/lib/esm/components/alert/alert.js +0 -1022
  135. package/lib/esm/components/alert/alert.js.map +0 -1
  136. package/lib/esm/components/alert/index.js +0 -4
  137. package/lib/esm/components/alert/index.js.map +0 -1
  138. package/lib/esm/components/alert/templates.js +0 -112
  139. package/lib/esm/components/alert/templates.js.map +0 -1
  140. package/lib/esm/components/alert/types.js +0 -6
  141. package/lib/esm/components/alert/types.js.map +0 -1
  142. package/lib/esm/components/datepicker/config/config.js +0 -39
  143. package/lib/esm/components/datepicker/config/config.js.map +0 -1
  144. package/lib/esm/components/datepicker/config/index.js +0 -8
  145. package/lib/esm/components/datepicker/config/index.js.map +0 -1
  146. package/lib/esm/components/datepicker/config/interfaces.js +0 -6
  147. package/lib/esm/components/datepicker/config/interfaces.js.map +0 -1
  148. package/lib/esm/components/datepicker/config/types.js +0 -6
  149. package/lib/esm/components/datepicker/config/types.js.map +0 -1
  150. package/lib/esm/components/datepicker/core/event-manager.js +0 -133
  151. package/lib/esm/components/datepicker/core/event-manager.js.map +0 -1
  152. package/lib/esm/components/datepicker/core/focus-manager.js +0 -164
  153. package/lib/esm/components/datepicker/core/focus-manager.js.map +0 -1
  154. package/lib/esm/components/datepicker/core/helpers.js +0 -211
  155. package/lib/esm/components/datepicker/core/helpers.js.map +0 -1
  156. package/lib/esm/components/datepicker/core/index.js +0 -9
  157. package/lib/esm/components/datepicker/core/index.js.map +0 -1
  158. package/lib/esm/components/datepicker/core/unified-state-manager.js +0 -391
  159. package/lib/esm/components/datepicker/core/unified-state-manager.js.map +0 -1
  160. package/lib/esm/components/datepicker/datepicker.js +0 -2248
  161. package/lib/esm/components/datepicker/datepicker.js.map +0 -1
  162. package/lib/esm/components/datepicker/index.js +0 -7
  163. package/lib/esm/components/datepicker/index.js.map +0 -1
  164. package/lib/esm/components/datepicker/ui/index.js +0 -7
  165. package/lib/esm/components/datepicker/ui/index.js.map +0 -1
  166. package/lib/esm/components/datepicker/ui/input/dropdown.js +0 -486
  167. package/lib/esm/components/datepicker/ui/input/dropdown.js.map +0 -1
  168. package/lib/esm/components/datepicker/ui/input/index.js +0 -7
  169. package/lib/esm/components/datepicker/ui/input/index.js.map +0 -1
  170. package/lib/esm/components/datepicker/ui/input/segmented-input.js +0 -637
  171. package/lib/esm/components/datepicker/ui/input/segmented-input.js.map +0 -1
  172. package/lib/esm/components/datepicker/ui/renderers/calendar.js +0 -443
  173. package/lib/esm/components/datepicker/ui/renderers/calendar.js.map +0 -1
  174. package/lib/esm/components/datepicker/ui/renderers/footer.js +0 -39
  175. package/lib/esm/components/datepicker/ui/renderers/footer.js.map +0 -1
  176. package/lib/esm/components/datepicker/ui/renderers/header.js +0 -29
  177. package/lib/esm/components/datepicker/ui/renderers/header.js.map +0 -1
  178. package/lib/esm/components/datepicker/ui/renderers/index.js +0 -9
  179. package/lib/esm/components/datepicker/ui/renderers/index.js.map +0 -1
  180. package/lib/esm/components/datepicker/ui/renderers/time-picker.js +0 -381
  181. package/lib/esm/components/datepicker/ui/renderers/time-picker.js.map +0 -1
  182. package/lib/esm/components/datepicker/ui/templates/index.js +0 -6
  183. package/lib/esm/components/datepicker/ui/templates/index.js.map +0 -1
  184. package/lib/esm/components/datepicker/ui/templates/templates.js +0 -242
  185. package/lib/esm/components/datepicker/ui/templates/templates.js.map +0 -1
  186. package/lib/esm/components/datepicker/utils/date-formatters.js +0 -83
  187. package/lib/esm/components/datepicker/utils/date-formatters.js.map +0 -1
  188. package/lib/esm/components/datepicker/utils/date-utils.js +0 -184
  189. package/lib/esm/components/datepicker/utils/date-utils.js.map +0 -1
  190. package/lib/esm/components/datepicker/utils/index.js +0 -8
  191. package/lib/esm/components/datepicker/utils/index.js.map +0 -1
  192. package/lib/esm/components/datepicker/utils/time-utils.js +0 -201
  193. package/lib/esm/components/datepicker/utils/time-utils.js.map +0 -1
  194. package/src/components/alert/alert.ts +0 -990
  195. package/src/components/alert/index.ts +0 -4
  196. package/src/components/alert/templates.ts +0 -110
  197. package/src/components/alert/tests/accessibility/aria-roles.test.ts +0 -19
  198. package/src/components/alert/tests/accessibility/focus-management.test.ts +0 -19
  199. package/src/components/alert/tests/accessibility/keyboard-nav.test.ts +0 -22
  200. package/src/components/alert/tests/actions/confirm-cancel.test.ts +0 -122
  201. package/src/components/alert/tests/actions/input-field.test.ts +0 -180
  202. package/src/components/alert/tests/alert.basic.test.ts +0 -126
  203. package/src/components/alert/tests/alert.config.test.ts +0 -75
  204. package/src/components/alert/tests/alert.templates.test.ts +0 -17
  205. package/src/components/alert/tests/config/attribute-config.test.ts +0 -94
  206. package/src/components/alert/tests/config/json-config.test.ts +0 -119
  207. package/src/components/alert/tests/config/merging.test.ts +0 -89
  208. package/src/components/alert/tests/dismissal/auto-dismiss.test.ts +0 -96
  209. package/src/components/alert/tests/dismissal/escape-key-dismiss.test.ts +0 -105
  210. package/src/components/alert/tests/dismissal/manual-dismiss.test.ts +0 -90
  211. package/src/components/alert/tests/dismissal/outside-click-dismiss.test.ts +0 -91
  212. package/src/components/alert/tests/edge-cases/invalid-config.test.ts +0 -19
  213. package/src/components/alert/tests/edge-cases/multiple-alerts.test.ts +0 -19
  214. package/src/components/alert/tests/rendering/custom-content.test.ts +0 -81
  215. package/src/components/alert/tests/rendering/info-alert.test.ts +0 -84
  216. package/src/components/alert/tests/rendering/success-alert.test.ts +0 -100
  217. package/src/components/alert/tests/templates/default-templates.test.ts +0 -16
  218. package/src/components/alert/tests/templates/user-templates.test.ts +0 -16
  219. package/src/components/alert/types.ts +0 -145
  220. package/src/components/datepicker/__tests__/datepicker-events.test.ts +0 -356
  221. package/src/components/datepicker/__tests__/datepicker-init.test.ts +0 -343
  222. package/src/components/datepicker/__tests__/datepicker-integration.test.ts +0 -435
  223. package/src/components/datepicker/__tests__/datepicker-timezone.test.ts +0 -220
  224. package/src/components/datepicker/__tests__/segmented-input-focus.test.ts +0 -380
  225. package/src/components/datepicker/__tests__/selective-state-updates.test.ts +0 -400
  226. package/src/components/datepicker/__tests__/state-manager.test.ts +0 -421
  227. package/src/components/datepicker/__tests__/time-preservation.test.ts +0 -387
  228. package/src/components/datepicker/config/config.ts +0 -40
  229. package/src/components/datepicker/config/index.ts +0 -8
  230. package/src/components/datepicker/config/interfaces.ts +0 -82
  231. package/src/components/datepicker/config/types.ts +0 -188
  232. package/src/components/datepicker/core/event-manager.ts +0 -159
  233. package/src/components/datepicker/core/focus-manager.ts +0 -201
  234. package/src/components/datepicker/core/helpers.ts +0 -231
  235. package/src/components/datepicker/core/index.ts +0 -9
  236. package/src/components/datepicker/core/unified-state-manager.ts +0 -459
  237. package/src/components/datepicker/datepicker.css +0 -435
  238. package/src/components/datepicker/datepicker.ts +0 -2548
  239. package/src/components/datepicker/index.ts +0 -8
  240. package/src/components/datepicker/ui/index.ts +0 -7
  241. package/src/components/datepicker/ui/input/dropdown.ts +0 -552
  242. package/src/components/datepicker/ui/input/index.ts +0 -7
  243. package/src/components/datepicker/ui/input/segmented-input.ts +0 -638
  244. package/src/components/datepicker/ui/renderers/__tests__/calendar-optimizations.test.ts +0 -611
  245. package/src/components/datepicker/ui/renderers/calendar.ts +0 -530
  246. package/src/components/datepicker/ui/renderers/footer.ts +0 -43
  247. package/src/components/datepicker/ui/renderers/header.ts +0 -33
  248. package/src/components/datepicker/ui/renderers/index.ts +0 -9
  249. package/src/components/datepicker/ui/renderers/time-picker.ts +0 -438
  250. package/src/components/datepicker/ui/templates/index.ts +0 -6
  251. package/src/components/datepicker/ui/templates/templates.ts +0 -306
  252. package/src/components/datepicker/utils/__tests__/date-formatters.test.ts +0 -160
  253. package/src/components/datepicker/utils/__tests__/date-utils-keys.test.ts +0 -86
  254. package/src/components/datepicker/utils/__tests__/date-utils-timezone.test.ts +0 -215
  255. package/src/components/datepicker/utils/date-formatters.ts +0 -85
  256. package/src/components/datepicker/utils/date-utils.ts +0 -172
  257. package/src/components/datepicker/utils/index.ts +0 -8
  258. package/src/components/datepicker/utils/time-utils.ts +0 -221
@@ -1,990 +0,0 @@
1
- /*
2
- * alert.ts - Main entry point for KTAlert (modular alert/dialog component)
3
- *
4
- * Provides a modular, extensible alert/dialog system for notifications, confirmations, and custom content.
5
- * Follows the KTDatepicker pattern: extends KTComponent, uses a template system, and supports full customization.
6
- *
7
- * All UI fragments are rendered via dedicated methods and customizable via a one-level config/template system.
8
- *
9
- * Copyright 2025 by Keenthemes Inc
10
- */
11
-
12
- import KTComponent from '../component';
13
- import { KTAlertConfig, KTAlertState, KTAlertTemplateStrings } from './types';
14
- import { getTemplateStrings, coreTemplateStrings, renderTemplateString, isTemplateFunction, renderOptions } from './templates';
15
-
16
- /**
17
- * Default configuration for KTAlert
18
- */
19
- const defaultConfig: KTAlertConfig = {
20
- type: 'info',
21
- title: '',
22
- message: '',
23
- icon: undefined, // success, error, warning, info, question, or custom HTML
24
- position: 'center',
25
- dismissible: false,
26
- modal: false,
27
- input: false,
28
- inputPlaceholder: '',
29
- inputValue: '',
30
- inputType: 'text',
31
- inputLabel: '',
32
- inputAttributes: {},
33
- customContent: '',
34
- confirmText: 'OK',
35
- cancelText: 'Cancel',
36
- showConfirmButton: true,
37
- showCancelButton: false,
38
- showCloseButton: true,
39
- timer: undefined,
40
- allowOutsideClick: true,
41
- allowEscapeKey: true,
42
- focusConfirm: true,
43
- showLoaderOnConfirm: false,
44
- customClass: '',
45
- loaderHtml: '',
46
- };
47
-
48
- /**
49
- * KTAlert
50
- *
51
- * Modular alert/dialog component for notifications, confirmations, and custom content.
52
- *
53
- * @class
54
- * @extends KTComponent
55
- */
56
- export class KTAlert extends KTComponent {
57
- /**
58
- * Component name for data attributes and config
59
- * @protected
60
- * @type {string}
61
- */
62
- protected override readonly _name: string = 'alert';
63
-
64
- /**
65
- * Component configuration (merged from defaults, global, data attributes, and user config)
66
- * @protected
67
- * @type {KTAlertConfig}
68
- */
69
- protected override _config: KTAlertConfig;
70
-
71
- /**
72
- * Component state (open, modal, dismissed, etc.)
73
- * @protected
74
- * @type {KTAlertState}
75
- */
76
- protected _state: KTAlertState;
77
-
78
- /**
79
- * Set of template strings for all UI fragments
80
- * @protected
81
- * @type {KTAlertTemplateStrings}
82
- */
83
- protected _templateSet: ReturnType<typeof getTemplateStrings>;
84
-
85
- /**
86
- * User-provided template overrides
87
- * @private
88
- * @type {Record<string, string | ((data: any) => string)>}
89
- */
90
- private _userTemplates: Record<string, string | ((data: any) => string)> = {};
91
-
92
- /**
93
- * Main container element for the alert
94
- * @private
95
- * @type {HTMLElement}
96
- */
97
- private _container: HTMLElement;
98
-
99
- /**
100
- * Timer ID for auto-dismiss
101
- * @private
102
- * @type {ReturnType<typeof setTimeout> | null}
103
- */
104
- private _timerId: ReturnType<typeof setTimeout> | null = null;
105
-
106
- /**
107
- * Constructor: Initializes the alert component (matches KTDatepicker pattern)
108
- * @param element - The root element for the alert
109
- * @param config - Optional user config
110
- */
111
- constructor(element: HTMLElement, config?: KTAlertConfig) {
112
- super();
113
- this._init(element);
114
- this._buildConfig(config);
115
- this._templateSet = getTemplateStrings(this._config);
116
- this._state = {
117
- isOpen: true,
118
- isModal: !!this._config.modal,
119
- isDismissed: false,
120
- inputValue: this._config.inputValue || ''
121
- };
122
- this._render();
123
- // Auto-dismiss logic: start timer if timer is set in config
124
- if (typeof this._config.timer === 'number' && this._config.timer > 0) {
125
- this._timerId = setTimeout(() => {
126
- if (!this._state.isDismissed) {
127
- this._state.isDismissed = true;
128
- this._fireEvent('dismiss', { reason: 'timer' });
129
- this._element.innerHTML = '';
130
- }
131
- }, this._config.timer);
132
- }
133
- }
134
-
135
- /**
136
- * Initialize the component (placeholder)
137
- * @param {HTMLElement} element - The root element for the alert
138
- * @protected
139
- */
140
- protected _init(element: HTMLElement) {
141
- super._init(element);
142
- // To be implemented: config merging, template setup, event binding
143
- }
144
-
145
- /**
146
- * Build the component config by merging defaults, global, data attributes, and user config
147
- * @param {KTAlertConfig} [config] - Optional user config
148
- * @protected
149
- */
150
- protected _buildConfig(config?: KTAlertConfig) {
151
- if (!this._element) return;
152
- // Merge order: defaultConfig < globalConfig < data attributes < JSON config < user config
153
- const globalConfig = this._getGlobalConfig() as KTAlertConfig;
154
- // Parse data-kt-alert-* attributes
155
- const dataAttrs: Record<string, any> = {};
156
- Array.from(this._element.attributes).forEach(attr => {
157
- if (attr.name.startsWith('data-kt-alert-') && attr.name !== 'data-kt-alert-config') {
158
- // Convert kebab-case to camelCase
159
- const key = attr.name
160
- .replace('data-kt-alert-', '')
161
- .replace(/-([a-z])/g, (_, c) => c.toUpperCase());
162
- let value: any = attr.value;
163
- // Parse booleans and numbers for known config keys
164
- if ([
165
- 'dismissible', 'modal', 'input', 'showConfirmButton', 'showCancelButton', 'showCloseButton',
166
- 'allowOutsideClick', 'allowEscapeKey', 'focusConfirm', 'showLoaderOnConfirm'
167
- ].includes(key)) {
168
- value = value === 'true';
169
- } else if (['timer'].includes(key)) {
170
- value = Number(value);
171
- } else if (['inputAttributes'].includes(key)) {
172
- try { value = JSON.parse(value); } catch { value = {}; }
173
- }
174
- dataAttrs[key] = value;
175
- }
176
- });
177
- // JSON config (data-kt-alert-config)
178
- let jsonConfig = {};
179
- const jsonAttr = this._element.getAttribute('data-kt-alert-config');
180
- if (jsonAttr) {
181
- try {
182
- jsonConfig = JSON.parse(jsonAttr);
183
- } catch (e) {
184
- // Invalid JSON, ignore
185
- }
186
- }
187
- // Merge all config sources
188
- let mergedConfig: KTAlertConfig = {
189
- ...defaultConfig,
190
- ...globalConfig,
191
- ...dataAttrs,
192
- ...jsonConfig,
193
- ...(config || {}),
194
- };
195
- // Apply per-type theming if present
196
- if (mergedConfig.theme && mergedConfig.type && mergedConfig.theme[mergedConfig.type]) {
197
- const themeOverrides = mergedConfig.theme[mergedConfig.type];
198
- mergedConfig = {
199
- ...mergedConfig,
200
- ...themeOverrides,
201
- // Use custom class templates if provided
202
- templates: {
203
- ...((mergedConfig.templates as any) || {}),
204
- confirmButton: themeOverrides.confirmButtonClass ? coreTemplateStrings.confirmButtonCustomClass : undefined,
205
- cancelButton: themeOverrides.cancelButtonClass ? coreTemplateStrings.cancelButtonCustomClass : undefined,
206
- },
207
- };
208
- }
209
- this._config = mergedConfig;
210
- }
211
-
212
- /**
213
- * Render the alert UI by composing all fragments using templates and config
214
- * Adds ARIA roles and keyboard navigation for accessibility
215
- * @private
216
- */
217
- private _render() {
218
- if (!this._element) return;
219
- this._templateSet = getTemplateStrings(this._config);
220
- // Render fragments
221
- const icon = this._renderIcon();
222
- const title = this._renderTitle();
223
- const message = this._renderMessage();
224
- const input = this._renderInput();
225
- const customContent = this._renderCustomContent();
226
- const confirmButton = this._renderConfirmButton();
227
- const cancelButton = this._renderCancelButton();
228
- const actions = this._renderActions(confirmButton, cancelButton);
229
- const closeButton = this._renderCloseButton();
230
- // Compose content
231
- const content = [icon, title, message, input, customContent, actions, closeButton].join('');
232
- // Render container
233
- const containerTpl = this._templateSet.container;
234
- let containerHtml: string;
235
- if (isTemplateFunction(containerTpl)) {
236
- containerHtml = containerTpl({ ...this._config, content });
237
- } else if (typeof containerTpl === 'string') {
238
- containerHtml = renderTemplateString(containerTpl, { ...this._config, content });
239
- } else {
240
- // Use default container template
241
- const defaultContainer = coreTemplateStrings.container;
242
- if (isTemplateFunction(defaultContainer)) {
243
- containerHtml = defaultContainer({ ...this._config, content });
244
- } else if (typeof defaultContainer === 'string') {
245
- containerHtml = renderTemplateString(defaultContainer, { ...this._config, content });
246
- } else {
247
- containerHtml = `<div>${content}</div>`;
248
- }
249
- }
250
- // Create DOM node
251
- const temp = document.createElement('div');
252
- temp.innerHTML = containerHtml;
253
- this._container = temp.firstElementChild as HTMLElement;
254
- // Add custom class if set
255
- if (this._config.customClass) {
256
- this._container.classList.add(this._config.customClass);
257
- }
258
- // Set ARIA attributes for accessibility
259
- this._container.setAttribute('role', this._config.modal ? 'alertdialog' : 'alert');
260
- this._container.setAttribute('aria-modal', this._config.modal ? 'true' : 'false');
261
- this._container.setAttribute('aria-labelledby', 'kt-alert-title');
262
- this._container.setAttribute('aria-describedby', 'kt-alert-message');
263
- // Replace or append to element
264
- this._element.innerHTML = '';
265
- this._element.appendChild(this._container);
266
- // Focus first interactive element
267
- this._focusFirstInteractive();
268
- // Bind event listeners
269
- this._bindEvents();
270
- // Bind keyboard navigation
271
- this._bindKeyboardNav();
272
- }
273
-
274
- /**
275
- * Focus the first interactive element (input, confirm, cancel, close)
276
- * @private
277
- */
278
- private _focusFirstInteractive() {
279
- if (!this._container) return;
280
-
281
- // Auto-focus input if configured and input exists
282
- if (this._config.inputAutoFocus && this._config.input) {
283
- const inputElement = this._container.querySelector('[data-kt-alert-input]') as HTMLElement;
284
- if (inputElement) {
285
- inputElement.focus();
286
- return;
287
- }
288
- }
289
-
290
- // Fallback to first interactive element
291
- const first = this._container.querySelector('[data-kt-alert-input], [data-kt-alert-confirm], [data-kt-alert-cancel], [data-kt-alert-close]') as HTMLElement;
292
- if (first) first.focus();
293
- }
294
-
295
- /**
296
- * Keyboard navigation: focus trap, Escape closes, Enter triggers confirm
297
- * @private
298
- */
299
- private _bindKeyboardNav() {
300
- if (!this._container) return;
301
- const focusable = Array.from(this._container.querySelectorAll('[tabindex="0"]')) as HTMLElement[];
302
- if (focusable.length === 0) return;
303
- let current = 0;
304
- // Focus trap for modal
305
- if (this._config.modal) {
306
- this._container.addEventListener('keydown', (e: KeyboardEvent) => {
307
- if (e.key === 'Tab') {
308
- e.preventDefault();
309
- if (e.shiftKey) {
310
- current = (current - 1 + focusable.length) % focusable.length;
311
- } else {
312
- current = (current + 1) % focusable.length;
313
- }
314
- focusable[current].focus();
315
- }
316
- });
317
- }
318
- // Escape closes alert if dismissible or modal
319
- this._container.addEventListener('keydown', (e: KeyboardEvent) => {
320
- if (e.key === 'Escape' && (this._config.dismissible || this._config.modal)) {
321
- this._clearTimer();
322
- this._state.isDismissed = true;
323
- this._fireEvent('dismiss', {});
324
- this._element.innerHTML = '';
325
- }
326
- // Enter triggers confirm if present
327
- if (e.key === 'Enter') {
328
- const confirmBtn = this._container.querySelector('[data-kt-alert-confirm]') as HTMLElement;
329
- if (confirmBtn) {
330
- confirmBtn.click();
331
- }
332
- }
333
- });
334
- }
335
-
336
- /** Render icon fragment */
337
- private _renderIcon(): string {
338
- if (!this._config.icon || !this._templateSet.icon) return '';
339
- const iconVal = this._config.icon;
340
- const tpl = this._templateSet.icon;
341
- if (isTemplateFunction(tpl)) {
342
- return tpl({ ...this._config, icon: iconVal });
343
- } else if (typeof tpl === 'string') {
344
- return renderTemplateString(tpl, { ...this._config, icon: iconVal });
345
- }
346
- return '';
347
- }
348
-
349
- /** Render title fragment */
350
- private _renderTitle(): string {
351
- if (!this._templateSet.title) return '';
352
- const titleVal = this._config.title || '';
353
- const tpl = this._templateSet.title;
354
- if (isTemplateFunction(tpl)) {
355
- return tpl({ ...this._config, title: titleVal });
356
- } else if (typeof tpl === 'string') {
357
- return renderTemplateString(tpl, { ...this._config, title: titleVal });
358
- }
359
- return '';
360
- }
361
-
362
- /** Render message fragment */
363
- private _renderMessage(): string {
364
- if (!this._templateSet.message) return '';
365
- const messageVal = this._config.message || '';
366
- const tpl = this._templateSet.message;
367
- if (isTemplateFunction(tpl)) {
368
- return tpl({ ...this._config, message: messageVal });
369
- } else if (typeof tpl === 'string') {
370
- return renderTemplateString(tpl, { ...this._config, message: messageVal });
371
- }
372
- return '';
373
- }
374
-
375
- /** Render input fragment */
376
- private _renderInput(): string {
377
- if (!this._config.input) return '';
378
- const inputType = this._config.inputType || 'text';
379
- const inputPlaceholder = this._config.inputPlaceholder || '';
380
- const inputValue = this._config.inputValue || '';
381
- const inputLabel = this._config.inputLabel || '';
382
- const attrs = this._config.inputAttributes
383
- ? Object.entries(this._config.inputAttributes).map(([k, v]) => `${k}="${v}"`).join(' ')
384
- : '';
385
- const options = this._config.inputOptions || [];
386
- let tplKey: string = 'inputText';
387
- let optionsHtml = '';
388
-
389
- // Set template key and render options if needed
390
- switch (inputType) {
391
- case 'textarea':
392
- tplKey = 'inputTextarea';
393
- break;
394
- case 'select':
395
- tplKey = 'inputSelect';
396
- if (options.length > 0) {
397
- optionsHtml = renderOptions(options.map(opt => ({ ...opt, inputValue })), 'option', this._templateSet);
398
- }
399
- break;
400
- case 'radio':
401
- tplKey = 'inputRadio';
402
- if (options.length > 0) {
403
- optionsHtml = renderOptions(options.map(opt => ({ ...opt, inputValue })), 'radioOption', this._templateSet);
404
- }
405
- break;
406
- case 'checkbox':
407
- tplKey = 'inputCheckbox';
408
- if (options.length > 0) {
409
- optionsHtml = renderOptions(options, 'checkboxOption', this._templateSet);
410
- }
411
- break;
412
- default:
413
- tplKey = 'inputText';
414
- break;
415
- }
416
-
417
- // Use type assertion to satisfy TS for dynamic template keys
418
- const tpl = this._templateSet[tplKey as keyof typeof this._templateSet];
419
- const data = { ...this._config, inputType, inputPlaceholder, inputValue, inputLabel, attrs, optionsHtml };
420
- if (isTemplateFunction(tpl)) {
421
- return tpl(data);
422
- } else if (typeof tpl === 'string') {
423
- return renderTemplateString(tpl, data);
424
- }
425
- return '';
426
- }
427
-
428
- /** Render custom content fragment */
429
- private _renderCustomContent(): string {
430
- if (!this._templateSet.customContent) return '';
431
- const customVal = this._config.customContent || '';
432
- const tpl = this._templateSet.customContent;
433
- if (isTemplateFunction(tpl)) {
434
- return tpl({ ...this._config, customContent: customVal });
435
- } else if (typeof tpl === 'string') {
436
- return renderTemplateString(tpl, { ...this._config, customContent: customVal });
437
- }
438
- return '';
439
- }
440
-
441
- /** Render confirm button fragment */
442
- private _renderConfirmButton(): string {
443
- if (!this._config.showConfirmButton) return '';
444
- const confirmText = this._config.confirmText || 'OK';
445
- let tpl = this._templateSet.confirmButton;
446
- // Use custom class template if present
447
- if (this._config.confirmButtonClass && this._templateSet.confirmButtonCustomClass) {
448
- tpl = this._templateSet.confirmButtonCustomClass;
449
- }
450
- if (isTemplateFunction(tpl)) {
451
- return tpl({ ...this._config, confirmText, confirmButtonClass: this._config.confirmButtonClass });
452
- } else if (typeof tpl === 'string') {
453
- return renderTemplateString(tpl, { ...this._config, confirmText, confirmButtonClass: this._config.confirmButtonClass });
454
- }
455
- return '';
456
- }
457
-
458
- /** Render cancel button fragment */
459
- private _renderCancelButton(): string {
460
- if (!this._config.showCancelButton) return '';
461
- const cancelText = this._config.cancelText || 'Cancel';
462
- let tpl = this._templateSet.cancelButton;
463
- // Use custom class template if present
464
- if (this._config.cancelButtonClass && this._templateSet.cancelButtonCustomClass) {
465
- tpl = this._templateSet.cancelButtonCustomClass;
466
- }
467
- if (isTemplateFunction(tpl)) {
468
- return tpl({ ...this._config, cancelText, cancelButtonClass: this._config.cancelButtonClass });
469
- } else if (typeof tpl === 'string') {
470
- return renderTemplateString(tpl, { ...this._config, cancelText, cancelButtonClass: this._config.cancelButtonClass });
471
- }
472
- return '';
473
- }
474
-
475
- /** Render actions fragment (wraps confirm/cancel buttons) */
476
- private _renderActions(confirmButton: string, cancelButton: string): string {
477
- if (!this._templateSet.actions) return '';
478
- const tpl = this._templateSet.actions;
479
- if (isTemplateFunction(tpl)) {
480
- return tpl({ ...this._config, confirmButton, cancelButton });
481
- } else if (typeof tpl === 'string') {
482
- return renderTemplateString(tpl, { ...this._config, confirmButton, cancelButton });
483
- }
484
- return '';
485
- }
486
-
487
- /** Render close button fragment */
488
- private _renderCloseButton(): string {
489
- if (!this._config.showCloseButton || !this._templateSet.closeButton) return '';
490
- const tpl = this._templateSet.closeButton;
491
- if (isTemplateFunction(tpl)) {
492
- return tpl({ ...this._config });
493
- } else if (typeof tpl === 'string') {
494
- return renderTemplateString(tpl, { ...this._config });
495
- } else {
496
- return '';
497
- }
498
- }
499
-
500
- /**
501
- * Attach event listeners for dismiss, confirm, cancel, and input actions
502
- * @private
503
- */
504
- private _bindEvents() {
505
- if (!this._container) return;
506
- // Dismiss (close) button
507
- const closeBtn = this._container.querySelector('[data-kt-alert-close]');
508
- if (closeBtn) {
509
- closeBtn.addEventListener('click', () => {
510
- this._clearTimer();
511
- this._state.isDismissed = true;
512
- this._fireEvent('dismiss', {});
513
- this._element.innerHTML = '';
514
- });
515
- }
516
- // Confirm button
517
- const confirmBtn = this._container.querySelector('[data-kt-alert-confirm]');
518
- if (confirmBtn) {
519
- confirmBtn.addEventListener('click', async () => {
520
- this._clearTimer();
521
-
522
- // Gather input value(s) for all supported types
523
- let inputValue: any = undefined;
524
- const inputType = this._config.inputType || 'text';
525
- if (inputType === 'checkbox') {
526
- const checkboxes = this._container.querySelectorAll('input[type="checkbox"][data-kt-alert-input]');
527
- // Return as comma-separated string for type safety
528
- inputValue = Array.from(checkboxes).filter((el: any) => el.checked).map((el: any) => el.value).join(',');
529
- } else if (inputType === 'radio') {
530
- const radio = this._container.querySelector('input[type="radio"][data-kt-alert-input]:checked') as HTMLInputElement;
531
- inputValue = radio ? radio.value : undefined;
532
- } else {
533
- const inputEl = this._container.querySelector('[data-kt-alert-input]') as HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;
534
- inputValue = inputEl ? inputEl.value : undefined;
535
- }
536
-
537
- // Validate input if validator is provided
538
- if (this._config.inputValidator) {
539
- try {
540
- const validationResult = await this._config.inputValidator(inputValue || '');
541
- if (validationResult) {
542
- // Show validation error
543
- this._showValidationError(validationResult);
544
- return; // Don't proceed with confirmation
545
- }
546
- } catch (error) {
547
- // Show validation error for exceptions
548
- this._showValidationError('Validation failed. Please try again.');
549
- return;
550
- }
551
- }
552
-
553
- // Clear any existing validation errors
554
- this._clearValidationError();
555
-
556
- // Process input with preConfirm if provided
557
- if (this._config.preConfirm) {
558
- try {
559
- inputValue = await this._config.preConfirm(inputValue || '');
560
- } catch (error) {
561
- // Show error for pre-confirmation failures
562
- this._showValidationError('Processing failed. Please try again.');
563
- return;
564
- }
565
- }
566
-
567
- this._fireEvent('confirm', { inputValue });
568
- this._state.isDismissed = true;
569
- this._element.innerHTML = '';
570
- });
571
- }
572
- // Cancel button
573
- const cancelBtn = this._container.querySelector('[data-kt-alert-cancel]');
574
- if (cancelBtn) {
575
- cancelBtn.addEventListener('click', () => {
576
- this._clearTimer();
577
- this._fireEvent('cancel', {});
578
- this._state.isDismissed = true;
579
- this._element.innerHTML = '';
580
- });
581
- }
582
- // Outside click dismissal (for modal alerts)
583
- if (this._config.modal && this._config.allowOutsideClick) {
584
- const overlay = this._element.closest('[data-kt-alert-overlay]');
585
- if (overlay) {
586
- overlay.addEventListener('click', (e: Event) => {
587
- if (e.target === overlay) {
588
- this._clearTimer();
589
- this._state.isDismissed = true;
590
- this._fireEvent('dismiss', {});
591
- this._element.innerHTML = '';
592
- }
593
- });
594
- }
595
- }
596
- // Input field (update state on input/change)
597
- const inputType = this._config.inputType || 'text';
598
- if (inputType === 'checkbox' || inputType === 'radio' || inputType === 'select') {
599
- const inputs = this._container.querySelectorAll('[data-kt-alert-input]');
600
- inputs.forEach((input: any) => {
601
- input.addEventListener('change', (e: Event) => {
602
- if (inputType === 'checkbox') {
603
- const checkboxes = this._container.querySelectorAll('input[type="checkbox"][data-kt-alert-input]');
604
- // Store as comma-separated string for type safety
605
- this._state.inputValue = Array.from(checkboxes).filter((el: any) => el.checked).map((el: any) => el.value).join(',');
606
- } else if (inputType === 'radio') {
607
- const radio = this._container.querySelector('input[type="radio"][data-kt-alert-input]:checked') as HTMLInputElement;
608
- this._state.inputValue = radio ? radio.value : undefined;
609
- } else if (inputType === 'select') {
610
- const select = this._container.querySelector('select[data-kt-alert-input]') as HTMLSelectElement;
611
- this._state.inputValue = select ? select.value : undefined;
612
- }
613
- this._fireEvent('input', { value: this._state.inputValue });
614
- });
615
- });
616
- } else {
617
- const inputEl = this._container.querySelector('[data-kt-alert-input]') as HTMLInputElement | HTMLTextAreaElement;
618
- if (inputEl) {
619
- inputEl.addEventListener('input', (e: Event) => {
620
- this._state.inputValue = (e.target as HTMLInputElement | HTMLTextAreaElement).value;
621
- this._fireEvent('input', { value: this._state.inputValue });
622
- });
623
- }
624
- }
625
- }
626
-
627
- /**
628
- * Clear the current timer if it exists.
629
- * @private
630
- */
631
- private _clearTimer() {
632
- if (this._timerId) {
633
- clearTimeout(this._timerId);
634
- this._timerId = null;
635
- }
636
- }
637
-
638
- /**
639
- * Show validation error message below input field.
640
- * @private
641
- */
642
- private _showValidationError(message: string) {
643
- if (!this._container) return;
644
-
645
- // Clear any existing error
646
- this._clearValidationError();
647
-
648
- // Create error element
649
- const errorElement = document.createElement('div');
650
- errorElement.setAttribute('data-kt-alert-input-error', '');
651
- errorElement.className = 'kt-alert-input-error';
652
- errorElement.setAttribute('role', 'alert');
653
- errorElement.setAttribute('aria-live', 'polite');
654
- errorElement.textContent = message;
655
-
656
- // Find input label and insert error after it
657
- const inputLabel = this._container.querySelector('[data-kt-alert-input-label]');
658
- if (inputLabel) {
659
- inputLabel.parentNode?.insertBefore(errorElement, inputLabel.nextSibling);
660
- }
661
-
662
- // Mark input as invalid
663
- const inputElement = this._container.querySelector('[data-kt-alert-input]') as HTMLElement;
664
- if (inputElement) {
665
- inputElement.setAttribute('aria-invalid', 'true');
666
- }
667
- }
668
-
669
- /**
670
- * Clear validation error message.
671
- * @private
672
- */
673
- private _clearValidationError() {
674
- if (!this._container) return;
675
-
676
- // Remove error element
677
- const errorElement = this._container.querySelector('[data-kt-alert-input-error]');
678
- if (errorElement) {
679
- errorElement.remove();
680
- }
681
-
682
- // Remove invalid state from input
683
- const inputElement = this._container.querySelector('[data-kt-alert-input]') as HTMLElement;
684
- if (inputElement) {
685
- inputElement.removeAttribute('aria-invalid');
686
- }
687
- }
688
-
689
- /**
690
- * Show validation error message below input field (static method helper).
691
- * @private
692
- */
693
- private static showValidationError(alert: HTMLElement, message: string) {
694
- // Clear any existing error
695
- this.clearValidationError(alert);
696
-
697
- // Create error element
698
- const errorElement = document.createElement('div');
699
- errorElement.setAttribute('data-kt-alert-input-error', '');
700
- errorElement.className = 'kt-alert-input-error';
701
- errorElement.setAttribute('role', 'alert');
702
- errorElement.setAttribute('aria-live', 'polite');
703
- errorElement.textContent = message;
704
-
705
- // Find input label and insert error after it
706
- const inputLabel = alert.querySelector('[data-kt-alert-input-label]');
707
- if (inputLabel) {
708
- inputLabel.parentNode?.insertBefore(errorElement, inputLabel.nextSibling);
709
- }
710
-
711
- // Mark input as invalid
712
- const inputElement = alert.querySelector('[data-kt-alert-input]') as HTMLElement;
713
- if (inputElement) {
714
- inputElement.setAttribute('aria-invalid', 'true');
715
- }
716
- }
717
-
718
- /**
719
- * Clear validation error message (static method helper).
720
- * @private
721
- */
722
- private static clearValidationError(alert: HTMLElement) {
723
- // Remove error element
724
- const errorElement = alert.querySelector('[data-kt-alert-input-error]');
725
- if (errorElement) {
726
- errorElement.remove();
727
- }
728
-
729
- // Remove invalid state from input
730
- const inputElement = alert.querySelector('[data-kt-alert-input]') as HTMLElement;
731
- if (inputElement) {
732
- inputElement.removeAttribute('aria-invalid');
733
- }
734
- }
735
-
736
- /**
737
- * KTAlert.fire(options)
738
- * Accepts a config object and returns a Promise that resolves with the user's action and input value.
739
- */
740
- static fire(options: any): Promise<{ isConfirmed: boolean, isDismissed: boolean, isCanceled: boolean, value?: string }> {
741
- // Remove any existing overlay
742
- const existing = document.querySelector('[data-kt-alert-overlay]');
743
- if (existing) existing.parentNode?.removeChild(existing);
744
- // Prepare templates
745
- const templates = getTemplateStrings(options);
746
- // Helper to resolve template (string or function)
747
- function resolveTemplate(tpl: string | ((data: any) => string) | undefined, data: any): string {
748
- if (typeof tpl === 'function') return tpl(data);
749
- return tpl ? renderTemplateString(tpl, data) : '';
750
- }
751
-
752
- // Set default icon based on type if no explicit icon is provided
753
- const iconToUse = options.icon === false ? '' : (options.icon || (() => {
754
- switch (options.type) {
755
- case 'success': return '✓';
756
- case 'error': return '✕';
757
- case 'warning': return '⚠';
758
- case 'info': return 'ℹ';
759
- case 'question': return '?';
760
- default: return '';
761
- }
762
- })());
763
-
764
- // Render modal content (all fragments)
765
- const icon = iconToUse ? resolveTemplate(templates.icon, { ...options, icon: iconToUse }) : '';
766
- const title = resolveTemplate(templates.title, options);
767
- const message = resolveTemplate(templates.message, { ...options, message: options.text || options.message || '' });
768
-
769
- // Render input based on type
770
- let input = '';
771
- if (options.input) {
772
- const inputType = options.inputType || 'text';
773
- const inputPlaceholder = options.inputPlaceholder || '';
774
- const inputValue = options.inputValue || '';
775
- const inputLabel = options.inputLabel || '';
776
- const attrs = options.inputAttributes ? Object.entries(options.inputAttributes).map(([k, v]) => `${k}="${v}"`).join(' ') : '';
777
- const inputOptions = options.inputOptions || [];
778
-
779
- let tplKey = 'inputText';
780
- let optionsHtml = '';
781
-
782
- // Set template key and render options if needed
783
- switch (inputType) {
784
- case 'textarea':
785
- tplKey = 'inputTextarea';
786
- break;
787
- case 'select':
788
- tplKey = 'inputSelect';
789
- if (inputOptions.length > 0) {
790
- optionsHtml = renderOptions(inputOptions.map((opt: any) => ({ ...opt, inputValue })), 'option', templates);
791
- }
792
- break;
793
- case 'radio':
794
- tplKey = 'inputRadio';
795
- if (inputOptions.length > 0) {
796
- optionsHtml = renderOptions(inputOptions.map((opt: any) => ({ ...opt, inputValue })), 'radioOption', templates);
797
- }
798
- break;
799
- case 'checkbox':
800
- tplKey = 'inputCheckbox';
801
- if (inputOptions.length > 0) {
802
- optionsHtml = renderOptions(inputOptions, 'checkboxOption', templates);
803
- }
804
- break;
805
- default:
806
- tplKey = 'inputText';
807
- break;
808
- }
809
-
810
- const inputTemplate = templates[tplKey as keyof typeof templates];
811
- const inputData = {
812
- ...options,
813
- inputType,
814
- inputPlaceholder,
815
- inputValue,
816
- inputLabel,
817
- attrs,
818
- optionsHtml
819
- };
820
-
821
- if (typeof inputTemplate === 'string') {
822
- input = renderTemplateString(inputTemplate, inputData);
823
- } else if (typeof inputTemplate === 'function') {
824
- input = inputTemplate(inputData);
825
- }
826
- }
827
-
828
- const customContent = options.customContent ? resolveTemplate(templates.customContent, options) : '';
829
- const confirmButton = options.showConfirmButton !== false ? resolveTemplate(templates.confirmButton, { ...options, confirmText: options.confirmText || 'OK' }) : '';
830
- const cancelButton = options.showCancelButton ? resolveTemplate(templates.cancelButton, { ...options, cancelText: options.cancelText || 'Cancel' }) : '';
831
- const actions = resolveTemplate(templates.actions, { ...options, confirmButton, cancelButton });
832
- const closeButton = options.showCloseButton !== false ? resolveTemplate(templates.closeButton, options) : '';
833
- // Loader support
834
- const loaderHtml = options.showLoaderOnConfirm && options.loaderHtml ? resolveTemplate(templates.loaderHtml, options) : '';
835
- // Compose content
836
- const content = [icon, title, message, input, customContent, actions, closeButton, loaderHtml].join('');
837
- // Render modal container
838
- const modalHtml = resolveTemplate(templates.modal, {
839
- ...options,
840
- type: options.type || 'info',
841
- variant: options.variant || '',
842
- ariaModal: options.modal !== false ? 'true' : 'false',
843
- role: options.modal !== false ? 'alertdialog' : 'alert',
844
- content,
845
- customClass: options.customClass || '',
846
- position: options.position || 'center',
847
- });
848
- // Render overlay (if modal)
849
- const overlayHtml = options.modal !== false
850
- ? resolveTemplate(templates.overlay, { ...options, modal: modalHtml })
851
- : modalHtml;
852
- // Create DOM node from template
853
- const temp = document.createElement('div');
854
- temp.innerHTML = overlayHtml;
855
- const overlay = options.modal !== false
856
- ? temp.querySelector('[data-kt-alert-overlay]') as HTMLElement
857
- : temp.firstElementChild as HTMLElement;
858
- document.body.appendChild(overlay);
859
-
860
- // Set custom ID if provided
861
- const alert = overlay.querySelector('[data-kt-alert]') || overlay;
862
- if (options.id) {
863
- alert.id = options.id;
864
- }
865
-
866
- // Timer support (auto-dismiss)
867
- let timerId: ReturnType<typeof setTimeout> | null = null;
868
- // Promise for result
869
- return new Promise((resolve) => {
870
- // Helper to clean up overlay
871
- const cleanup = () => {
872
- if (timerId) clearTimeout(timerId);
873
- if (overlay.parentNode) overlay.parentNode.removeChild(overlay);
874
- };
875
-
876
- // Timer support (auto-dismiss) - moved inside Promise to access cleanup and resolve
877
- if (options.timer && typeof options.timer === 'number' && options.timer > 0) {
878
- timerId = setTimeout(() => {
879
- cleanup();
880
- resolve({ isConfirmed: false, isDismissed: true, isCanceled: false });
881
- }, options.timer);
882
- }
883
-
884
- // Bind events manually
885
- const alert = overlay.querySelector('[data-kt-alert]') || overlay;
886
-
887
- // Dismiss (close) button
888
- const closeBtn = alert.querySelector('[data-kt-alert-close]');
889
- if (closeBtn) {
890
- closeBtn.addEventListener('click', () => {
891
- cleanup();
892
- resolve({ isConfirmed: false, isDismissed: true, isCanceled: false });
893
- });
894
- }
895
-
896
- // Confirm button
897
- const confirmBtn = alert.querySelector('[data-kt-alert-confirm]');
898
- if (confirmBtn) {
899
- confirmBtn.addEventListener('click', async () => {
900
- // Gather input value(s) for all supported types
901
- let inputValue: any = undefined;
902
- const inputType = options.inputType || 'text';
903
- if (inputType === 'checkbox') {
904
- const checkboxes = alert.querySelectorAll('input[type="checkbox"][data-kt-alert-input]');
905
- inputValue = Array.from(checkboxes).filter((el: any) => el.checked).map((el: any) => el.value).join(',');
906
- } else if (inputType === 'radio') {
907
- const radio = alert.querySelector('input[type="radio"][data-kt-alert-input]:checked') as HTMLInputElement;
908
- inputValue = radio ? radio.value : undefined;
909
- } else {
910
- const inputEl = alert.querySelector('[data-kt-alert-input]') as HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;
911
- inputValue = inputEl ? inputEl.value : undefined;
912
- }
913
-
914
- // Validate input if validator is provided
915
- if (options.inputValidator) {
916
- try {
917
- const validationResult = await options.inputValidator(inputValue || '');
918
- if (validationResult) {
919
- // Show validation error
920
- KTAlert.showValidationError(alert as HTMLElement, validationResult);
921
- return; // Don't proceed with confirmation
922
- }
923
- } catch (error) {
924
- // Show validation error for exceptions
925
- KTAlert.showValidationError(alert as HTMLElement, 'Validation failed. Please try again.');
926
- return;
927
- }
928
- }
929
-
930
- // Clear any existing validation errors
931
- KTAlert.clearValidationError(alert as HTMLElement);
932
-
933
- // Process input with preConfirm if provided
934
- if (options.preConfirm) {
935
- try {
936
- inputValue = await options.preConfirm(inputValue || '');
937
- } catch (error) {
938
- // Show error for pre-confirmation failures
939
- KTAlert.showValidationError(alert as HTMLElement, 'Processing failed. Please try again.');
940
- return;
941
- }
942
- }
943
-
944
- cleanup();
945
- resolve({ isConfirmed: true, isDismissed: false, isCanceled: false, value: inputValue });
946
- });
947
- }
948
-
949
- // Cancel button
950
- const cancelBtn = alert.querySelector('[data-kt-alert-cancel]');
951
- if (cancelBtn) {
952
- cancelBtn.addEventListener('click', () => {
953
- cleanup();
954
- resolve({ isConfirmed: false, isDismissed: false, isCanceled: true });
955
- });
956
- }
957
-
958
- // Outside click dismissal (for modal alerts)
959
- if (options.modal && options.allowOutsideClick) {
960
- overlay.addEventListener('click', (e: Event) => {
961
- if (e.target === overlay) {
962
- cleanup();
963
- resolve({ isConfirmed: false, isDismissed: true, isCanceled: false });
964
- }
965
- });
966
- }
967
-
968
- // Keyboard events
969
- alert.addEventListener('keydown', (e: KeyboardEvent) => {
970
- if (e.key === 'Escape' && (options.allowEscapeKey !== false || options.dismissible || options.modal)) {
971
- cleanup();
972
- resolve({ isConfirmed: false, isDismissed: true, isCanceled: false });
973
- }
974
- if (e.key === 'Enter' && confirmBtn) {
975
- (confirmBtn as HTMLElement).click();
976
- }
977
- });
978
-
979
- // Auto-focus input if configured
980
- if (options.inputAutoFocus && options.input) {
981
- const inputElement = alert.querySelector('[data-kt-alert-input]') as HTMLElement;
982
- if (inputElement) {
983
- // Use setTimeout to ensure DOM is ready
984
- setTimeout(() => inputElement.focus(), 0);
985
- }
986
- }
987
- });
988
- }
989
-
990
- }