@keenthemes/ktui 1.1.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (222) hide show
  1. package/README.md +0 -27
  2. package/dist/ktui.js +6790 -14063
  3. package/dist/ktui.min.js +1 -1
  4. package/dist/ktui.min.js.map +1 -1
  5. package/dist/styles.css +1132 -2705
  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/index.js +1 -10
  15. package/lib/cjs/index.js.map +1 -1
  16. package/lib/esm/components/datatable/__tests__/pagination-reset.test.js +594 -0
  17. package/lib/esm/components/datatable/__tests__/pagination-reset.test.js.map +1 -0
  18. package/lib/esm/components/datatable/__tests__/race-conditions.test.js +546 -0
  19. package/lib/esm/components/datatable/__tests__/race-conditions.test.js.map +1 -0
  20. package/lib/esm/components/datatable/__tests__/setup.js +58 -0
  21. package/lib/esm/components/datatable/__tests__/setup.js.map +1 -0
  22. package/lib/esm/components/datatable/datatable.js +92 -30
  23. package/lib/esm/components/datatable/datatable.js.map +1 -1
  24. package/lib/esm/index.js +0 -7
  25. package/lib/esm/index.js.map +1 -1
  26. package/package.json +7 -9
  27. package/src/components/alert/alert.css +188 -429
  28. package/src/components/datatable/__tests__/pagination-reset.test.ts +657 -0
  29. package/src/components/datatable/__tests__/race-conditions.test.ts +455 -0
  30. package/src/components/datatable/__tests__/setup.ts +67 -0
  31. package/src/components/datatable/datatable.ts +66 -11
  32. package/src/components/input/input.css +0 -1
  33. package/src/components/select/select.css +0 -1
  34. package/src/components/select/variants.css +4 -0
  35. package/src/components/textarea/textarea.css +0 -1
  36. package/src/index.ts +0 -10
  37. package/styles.css +0 -1
  38. package/lib/cjs/components/alert/alert.js +0 -1025
  39. package/lib/cjs/components/alert/alert.js.map +0 -1
  40. package/lib/cjs/components/alert/index.js +0 -20
  41. package/lib/cjs/components/alert/index.js.map +0 -1
  42. package/lib/cjs/components/alert/templates.js +0 -120
  43. package/lib/cjs/components/alert/templates.js.map +0 -1
  44. package/lib/cjs/components/alert/types.js +0 -7
  45. package/lib/cjs/components/alert/types.js.map +0 -1
  46. package/lib/cjs/components/datepicker/config/config.js +0 -42
  47. package/lib/cjs/components/datepicker/config/config.js.map +0 -1
  48. package/lib/cjs/components/datepicker/config/index.js +0 -24
  49. package/lib/cjs/components/datepicker/config/index.js.map +0 -1
  50. package/lib/cjs/components/datepicker/config/interfaces.js +0 -7
  51. package/lib/cjs/components/datepicker/config/interfaces.js.map +0 -1
  52. package/lib/cjs/components/datepicker/config/types.js +0 -7
  53. package/lib/cjs/components/datepicker/config/types.js.map +0 -1
  54. package/lib/cjs/components/datepicker/core/event-manager.js +0 -135
  55. package/lib/cjs/components/datepicker/core/event-manager.js.map +0 -1
  56. package/lib/cjs/components/datepicker/core/focus-manager.js +0 -167
  57. package/lib/cjs/components/datepicker/core/focus-manager.js.map +0 -1
  58. package/lib/cjs/components/datepicker/core/helpers.js +0 -219
  59. package/lib/cjs/components/datepicker/core/helpers.js.map +0 -1
  60. package/lib/cjs/components/datepicker/core/index.js +0 -25
  61. package/lib/cjs/components/datepicker/core/index.js.map +0 -1
  62. package/lib/cjs/components/datepicker/core/unified-state-manager.js +0 -394
  63. package/lib/cjs/components/datepicker/core/unified-state-manager.js.map +0 -1
  64. package/lib/cjs/components/datepicker/datepicker.js +0 -2252
  65. package/lib/cjs/components/datepicker/datepicker.js.map +0 -1
  66. package/lib/cjs/components/datepicker/index.js +0 -24
  67. package/lib/cjs/components/datepicker/index.js.map +0 -1
  68. package/lib/cjs/components/datepicker/ui/index.js +0 -23
  69. package/lib/cjs/components/datepicker/ui/index.js.map +0 -1
  70. package/lib/cjs/components/datepicker/ui/input/dropdown.js +0 -489
  71. package/lib/cjs/components/datepicker/ui/input/dropdown.js.map +0 -1
  72. package/lib/cjs/components/datepicker/ui/input/index.js +0 -23
  73. package/lib/cjs/components/datepicker/ui/input/index.js.map +0 -1
  74. package/lib/cjs/components/datepicker/ui/input/segmented-input.js +0 -640
  75. package/lib/cjs/components/datepicker/ui/input/segmented-input.js.map +0 -1
  76. package/lib/cjs/components/datepicker/ui/renderers/calendar.js +0 -446
  77. package/lib/cjs/components/datepicker/ui/renderers/calendar.js.map +0 -1
  78. package/lib/cjs/components/datepicker/ui/renderers/footer.js +0 -42
  79. package/lib/cjs/components/datepicker/ui/renderers/footer.js.map +0 -1
  80. package/lib/cjs/components/datepicker/ui/renderers/header.js +0 -32
  81. package/lib/cjs/components/datepicker/ui/renderers/header.js.map +0 -1
  82. package/lib/cjs/components/datepicker/ui/renderers/index.js +0 -25
  83. package/lib/cjs/components/datepicker/ui/renderers/index.js.map +0 -1
  84. package/lib/cjs/components/datepicker/ui/renderers/time-picker.js +0 -384
  85. package/lib/cjs/components/datepicker/ui/renderers/time-picker.js.map +0 -1
  86. package/lib/cjs/components/datepicker/ui/templates/index.js +0 -22
  87. package/lib/cjs/components/datepicker/ui/templates/index.js.map +0 -1
  88. package/lib/cjs/components/datepicker/ui/templates/templates.js +0 -253
  89. package/lib/cjs/components/datepicker/ui/templates/templates.js.map +0 -1
  90. package/lib/cjs/components/datepicker/utils/date-formatters.js +0 -88
  91. package/lib/cjs/components/datepicker/utils/date-formatters.js.map +0 -1
  92. package/lib/cjs/components/datepicker/utils/date-utils.js +0 -194
  93. package/lib/cjs/components/datepicker/utils/date-utils.js.map +0 -1
  94. package/lib/cjs/components/datepicker/utils/index.js +0 -24
  95. package/lib/cjs/components/datepicker/utils/index.js.map +0 -1
  96. package/lib/cjs/components/datepicker/utils/time-utils.js +0 -213
  97. package/lib/cjs/components/datepicker/utils/time-utils.js.map +0 -1
  98. package/lib/esm/components/alert/alert.js +0 -1022
  99. package/lib/esm/components/alert/alert.js.map +0 -1
  100. package/lib/esm/components/alert/index.js +0 -4
  101. package/lib/esm/components/alert/index.js.map +0 -1
  102. package/lib/esm/components/alert/templates.js +0 -112
  103. package/lib/esm/components/alert/templates.js.map +0 -1
  104. package/lib/esm/components/alert/types.js +0 -6
  105. package/lib/esm/components/alert/types.js.map +0 -1
  106. package/lib/esm/components/datepicker/config/config.js +0 -39
  107. package/lib/esm/components/datepicker/config/config.js.map +0 -1
  108. package/lib/esm/components/datepicker/config/index.js +0 -8
  109. package/lib/esm/components/datepicker/config/index.js.map +0 -1
  110. package/lib/esm/components/datepicker/config/interfaces.js +0 -6
  111. package/lib/esm/components/datepicker/config/interfaces.js.map +0 -1
  112. package/lib/esm/components/datepicker/config/types.js +0 -6
  113. package/lib/esm/components/datepicker/config/types.js.map +0 -1
  114. package/lib/esm/components/datepicker/core/event-manager.js +0 -133
  115. package/lib/esm/components/datepicker/core/event-manager.js.map +0 -1
  116. package/lib/esm/components/datepicker/core/focus-manager.js +0 -164
  117. package/lib/esm/components/datepicker/core/focus-manager.js.map +0 -1
  118. package/lib/esm/components/datepicker/core/helpers.js +0 -211
  119. package/lib/esm/components/datepicker/core/helpers.js.map +0 -1
  120. package/lib/esm/components/datepicker/core/index.js +0 -9
  121. package/lib/esm/components/datepicker/core/index.js.map +0 -1
  122. package/lib/esm/components/datepicker/core/unified-state-manager.js +0 -391
  123. package/lib/esm/components/datepicker/core/unified-state-manager.js.map +0 -1
  124. package/lib/esm/components/datepicker/datepicker.js +0 -2248
  125. package/lib/esm/components/datepicker/datepicker.js.map +0 -1
  126. package/lib/esm/components/datepicker/index.js +0 -7
  127. package/lib/esm/components/datepicker/index.js.map +0 -1
  128. package/lib/esm/components/datepicker/ui/index.js +0 -7
  129. package/lib/esm/components/datepicker/ui/index.js.map +0 -1
  130. package/lib/esm/components/datepicker/ui/input/dropdown.js +0 -486
  131. package/lib/esm/components/datepicker/ui/input/dropdown.js.map +0 -1
  132. package/lib/esm/components/datepicker/ui/input/index.js +0 -7
  133. package/lib/esm/components/datepicker/ui/input/index.js.map +0 -1
  134. package/lib/esm/components/datepicker/ui/input/segmented-input.js +0 -637
  135. package/lib/esm/components/datepicker/ui/input/segmented-input.js.map +0 -1
  136. package/lib/esm/components/datepicker/ui/renderers/calendar.js +0 -443
  137. package/lib/esm/components/datepicker/ui/renderers/calendar.js.map +0 -1
  138. package/lib/esm/components/datepicker/ui/renderers/footer.js +0 -39
  139. package/lib/esm/components/datepicker/ui/renderers/footer.js.map +0 -1
  140. package/lib/esm/components/datepicker/ui/renderers/header.js +0 -29
  141. package/lib/esm/components/datepicker/ui/renderers/header.js.map +0 -1
  142. package/lib/esm/components/datepicker/ui/renderers/index.js +0 -9
  143. package/lib/esm/components/datepicker/ui/renderers/index.js.map +0 -1
  144. package/lib/esm/components/datepicker/ui/renderers/time-picker.js +0 -381
  145. package/lib/esm/components/datepicker/ui/renderers/time-picker.js.map +0 -1
  146. package/lib/esm/components/datepicker/ui/templates/index.js +0 -6
  147. package/lib/esm/components/datepicker/ui/templates/index.js.map +0 -1
  148. package/lib/esm/components/datepicker/ui/templates/templates.js +0 -242
  149. package/lib/esm/components/datepicker/ui/templates/templates.js.map +0 -1
  150. package/lib/esm/components/datepicker/utils/date-formatters.js +0 -83
  151. package/lib/esm/components/datepicker/utils/date-formatters.js.map +0 -1
  152. package/lib/esm/components/datepicker/utils/date-utils.js +0 -184
  153. package/lib/esm/components/datepicker/utils/date-utils.js.map +0 -1
  154. package/lib/esm/components/datepicker/utils/index.js +0 -8
  155. package/lib/esm/components/datepicker/utils/index.js.map +0 -1
  156. package/lib/esm/components/datepicker/utils/time-utils.js +0 -201
  157. package/lib/esm/components/datepicker/utils/time-utils.js.map +0 -1
  158. package/src/components/alert/alert.ts +0 -990
  159. package/src/components/alert/index.ts +0 -4
  160. package/src/components/alert/templates.ts +0 -110
  161. package/src/components/alert/tests/accessibility/aria-roles.test.ts +0 -19
  162. package/src/components/alert/tests/accessibility/focus-management.test.ts +0 -19
  163. package/src/components/alert/tests/accessibility/keyboard-nav.test.ts +0 -22
  164. package/src/components/alert/tests/actions/confirm-cancel.test.ts +0 -122
  165. package/src/components/alert/tests/actions/input-field.test.ts +0 -180
  166. package/src/components/alert/tests/alert.basic.test.ts +0 -126
  167. package/src/components/alert/tests/alert.config.test.ts +0 -75
  168. package/src/components/alert/tests/alert.templates.test.ts +0 -17
  169. package/src/components/alert/tests/config/attribute-config.test.ts +0 -94
  170. package/src/components/alert/tests/config/json-config.test.ts +0 -119
  171. package/src/components/alert/tests/config/merging.test.ts +0 -89
  172. package/src/components/alert/tests/dismissal/auto-dismiss.test.ts +0 -96
  173. package/src/components/alert/tests/dismissal/escape-key-dismiss.test.ts +0 -105
  174. package/src/components/alert/tests/dismissal/manual-dismiss.test.ts +0 -90
  175. package/src/components/alert/tests/dismissal/outside-click-dismiss.test.ts +0 -91
  176. package/src/components/alert/tests/edge-cases/invalid-config.test.ts +0 -19
  177. package/src/components/alert/tests/edge-cases/multiple-alerts.test.ts +0 -19
  178. package/src/components/alert/tests/rendering/custom-content.test.ts +0 -81
  179. package/src/components/alert/tests/rendering/info-alert.test.ts +0 -84
  180. package/src/components/alert/tests/rendering/success-alert.test.ts +0 -100
  181. package/src/components/alert/tests/templates/default-templates.test.ts +0 -16
  182. package/src/components/alert/tests/templates/user-templates.test.ts +0 -16
  183. package/src/components/alert/types.ts +0 -145
  184. package/src/components/datepicker/__tests__/datepicker-events.test.ts +0 -356
  185. package/src/components/datepicker/__tests__/datepicker-init.test.ts +0 -343
  186. package/src/components/datepicker/__tests__/datepicker-integration.test.ts +0 -435
  187. package/src/components/datepicker/__tests__/datepicker-timezone.test.ts +0 -220
  188. package/src/components/datepicker/__tests__/segmented-input-focus.test.ts +0 -380
  189. package/src/components/datepicker/__tests__/selective-state-updates.test.ts +0 -400
  190. package/src/components/datepicker/__tests__/state-manager.test.ts +0 -421
  191. package/src/components/datepicker/__tests__/time-preservation.test.ts +0 -387
  192. package/src/components/datepicker/config/config.ts +0 -40
  193. package/src/components/datepicker/config/index.ts +0 -8
  194. package/src/components/datepicker/config/interfaces.ts +0 -82
  195. package/src/components/datepicker/config/types.ts +0 -188
  196. package/src/components/datepicker/core/event-manager.ts +0 -159
  197. package/src/components/datepicker/core/focus-manager.ts +0 -201
  198. package/src/components/datepicker/core/helpers.ts +0 -231
  199. package/src/components/datepicker/core/index.ts +0 -9
  200. package/src/components/datepicker/core/unified-state-manager.ts +0 -459
  201. package/src/components/datepicker/datepicker.css +0 -435
  202. package/src/components/datepicker/datepicker.ts +0 -2548
  203. package/src/components/datepicker/index.ts +0 -8
  204. package/src/components/datepicker/ui/index.ts +0 -7
  205. package/src/components/datepicker/ui/input/dropdown.ts +0 -552
  206. package/src/components/datepicker/ui/input/index.ts +0 -7
  207. package/src/components/datepicker/ui/input/segmented-input.ts +0 -638
  208. package/src/components/datepicker/ui/renderers/__tests__/calendar-optimizations.test.ts +0 -611
  209. package/src/components/datepicker/ui/renderers/calendar.ts +0 -530
  210. package/src/components/datepicker/ui/renderers/footer.ts +0 -43
  211. package/src/components/datepicker/ui/renderers/header.ts +0 -33
  212. package/src/components/datepicker/ui/renderers/index.ts +0 -9
  213. package/src/components/datepicker/ui/renderers/time-picker.ts +0 -438
  214. package/src/components/datepicker/ui/templates/index.ts +0 -6
  215. package/src/components/datepicker/ui/templates/templates.ts +0 -306
  216. package/src/components/datepicker/utils/__tests__/date-formatters.test.ts +0 -160
  217. package/src/components/datepicker/utils/__tests__/date-utils-keys.test.ts +0 -86
  218. package/src/components/datepicker/utils/__tests__/date-utils-timezone.test.ts +0 -215
  219. package/src/components/datepicker/utils/date-formatters.ts +0 -85
  220. package/src/components/datepicker/utils/date-utils.ts +0 -172
  221. package/src/components/datepicker/utils/index.ts +0 -8
  222. 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
- }