@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.
- package/README.md +0 -27
- package/dist/ktui.js +5269 -12550
- package/dist/ktui.min.js +1 -1
- package/dist/ktui.min.js.map +1 -1
- package/dist/styles.css +1133 -2706
- package/lib/cjs/components/datatable/__tests__/pagination-reset.test.js +596 -0
- package/lib/cjs/components/datatable/__tests__/pagination-reset.test.js.map +1 -0
- package/lib/cjs/components/datatable/__tests__/race-conditions.test.js +548 -0
- package/lib/cjs/components/datatable/__tests__/race-conditions.test.js.map +1 -0
- package/lib/cjs/components/datatable/__tests__/setup.js +63 -0
- package/lib/cjs/components/datatable/__tests__/setup.js.map +1 -0
- package/lib/cjs/components/datatable/datatable.js +92 -30
- package/lib/cjs/components/datatable/datatable.js.map +1 -1
- package/lib/cjs/components/select/combobox.js +0 -2
- package/lib/cjs/components/select/combobox.js.map +1 -1
- package/lib/cjs/components/select/config.js +4 -1
- package/lib/cjs/components/select/config.js.map +1 -1
- package/lib/cjs/components/select/dropdown.js +0 -16
- package/lib/cjs/components/select/dropdown.js.map +1 -1
- package/lib/cjs/components/select/remote.js +0 -40
- package/lib/cjs/components/select/remote.js.map +1 -1
- package/lib/cjs/components/select/search.js +80 -19
- package/lib/cjs/components/select/search.js.map +1 -1
- package/lib/cjs/components/select/select.js +98 -110
- package/lib/cjs/components/select/select.js.map +1 -1
- package/lib/cjs/components/select/tags.js +0 -2
- package/lib/cjs/components/select/tags.js.map +1 -1
- package/lib/cjs/index.js +1 -10
- package/lib/cjs/index.js.map +1 -1
- package/lib/esm/components/datatable/__tests__/pagination-reset.test.js +594 -0
- package/lib/esm/components/datatable/__tests__/pagination-reset.test.js.map +1 -0
- package/lib/esm/components/datatable/__tests__/race-conditions.test.js +546 -0
- package/lib/esm/components/datatable/__tests__/race-conditions.test.js.map +1 -0
- package/lib/esm/components/datatable/__tests__/setup.js +58 -0
- package/lib/esm/components/datatable/__tests__/setup.js.map +1 -0
- package/lib/esm/components/datatable/datatable.js +92 -30
- package/lib/esm/components/datatable/datatable.js.map +1 -1
- package/lib/esm/components/select/combobox.js +0 -2
- package/lib/esm/components/select/combobox.js.map +1 -1
- package/lib/esm/components/select/config.js +4 -1
- package/lib/esm/components/select/config.js.map +1 -1
- package/lib/esm/components/select/dropdown.js +0 -16
- package/lib/esm/components/select/dropdown.js.map +1 -1
- package/lib/esm/components/select/remote.js +0 -40
- package/lib/esm/components/select/remote.js.map +1 -1
- package/lib/esm/components/select/search.js +80 -19
- package/lib/esm/components/select/search.js.map +1 -1
- package/lib/esm/components/select/select.js +98 -110
- package/lib/esm/components/select/select.js.map +1 -1
- package/lib/esm/components/select/tags.js +0 -2
- package/lib/esm/components/select/tags.js.map +1 -1
- package/lib/esm/index.js +0 -7
- package/lib/esm/index.js.map +1 -1
- package/package.json +7 -9
- package/src/components/alert/alert.css +188 -429
- package/src/components/datatable/__tests__/pagination-reset.test.ts +657 -0
- package/src/components/datatable/__tests__/race-conditions.test.ts +455 -0
- package/src/components/datatable/__tests__/setup.ts +67 -0
- package/src/components/datatable/datatable.ts +66 -11
- package/src/components/input/input.css +0 -1
- package/src/components/select/__tests__/ux-behaviors.test.ts +619 -0
- package/src/components/select/combobox.ts +0 -1
- package/src/components/select/config.ts +7 -1
- package/src/components/select/dropdown.ts +0 -24
- package/src/components/select/remote.ts +0 -49
- package/src/components/select/search.ts +85 -21
- package/src/components/select/select.css +0 -1
- package/src/components/select/select.ts +118 -149
- package/src/components/select/tags.ts +0 -1
- package/src/components/select/variants.css +4 -0
- package/src/components/textarea/textarea.css +0 -1
- package/src/index.ts +0 -10
- package/styles.css +0 -1
- package/lib/cjs/components/alert/alert.js +0 -1025
- package/lib/cjs/components/alert/alert.js.map +0 -1
- package/lib/cjs/components/alert/index.js +0 -20
- package/lib/cjs/components/alert/index.js.map +0 -1
- package/lib/cjs/components/alert/templates.js +0 -120
- package/lib/cjs/components/alert/templates.js.map +0 -1
- package/lib/cjs/components/alert/types.js +0 -7
- package/lib/cjs/components/alert/types.js.map +0 -1
- package/lib/cjs/components/datepicker/config/config.js +0 -42
- package/lib/cjs/components/datepicker/config/config.js.map +0 -1
- package/lib/cjs/components/datepicker/config/index.js +0 -24
- package/lib/cjs/components/datepicker/config/index.js.map +0 -1
- package/lib/cjs/components/datepicker/config/interfaces.js +0 -7
- package/lib/cjs/components/datepicker/config/interfaces.js.map +0 -1
- package/lib/cjs/components/datepicker/config/types.js +0 -7
- package/lib/cjs/components/datepicker/config/types.js.map +0 -1
- package/lib/cjs/components/datepicker/core/event-manager.js +0 -135
- package/lib/cjs/components/datepicker/core/event-manager.js.map +0 -1
- package/lib/cjs/components/datepicker/core/focus-manager.js +0 -167
- package/lib/cjs/components/datepicker/core/focus-manager.js.map +0 -1
- package/lib/cjs/components/datepicker/core/helpers.js +0 -219
- package/lib/cjs/components/datepicker/core/helpers.js.map +0 -1
- package/lib/cjs/components/datepicker/core/index.js +0 -25
- package/lib/cjs/components/datepicker/core/index.js.map +0 -1
- package/lib/cjs/components/datepicker/core/unified-state-manager.js +0 -394
- package/lib/cjs/components/datepicker/core/unified-state-manager.js.map +0 -1
- package/lib/cjs/components/datepicker/datepicker.js +0 -2252
- package/lib/cjs/components/datepicker/datepicker.js.map +0 -1
- package/lib/cjs/components/datepicker/index.js +0 -24
- package/lib/cjs/components/datepicker/index.js.map +0 -1
- package/lib/cjs/components/datepicker/ui/index.js +0 -23
- package/lib/cjs/components/datepicker/ui/index.js.map +0 -1
- package/lib/cjs/components/datepicker/ui/input/dropdown.js +0 -489
- package/lib/cjs/components/datepicker/ui/input/dropdown.js.map +0 -1
- package/lib/cjs/components/datepicker/ui/input/index.js +0 -23
- package/lib/cjs/components/datepicker/ui/input/index.js.map +0 -1
- package/lib/cjs/components/datepicker/ui/input/segmented-input.js +0 -640
- package/lib/cjs/components/datepicker/ui/input/segmented-input.js.map +0 -1
- package/lib/cjs/components/datepicker/ui/renderers/calendar.js +0 -446
- package/lib/cjs/components/datepicker/ui/renderers/calendar.js.map +0 -1
- package/lib/cjs/components/datepicker/ui/renderers/footer.js +0 -42
- package/lib/cjs/components/datepicker/ui/renderers/footer.js.map +0 -1
- package/lib/cjs/components/datepicker/ui/renderers/header.js +0 -32
- package/lib/cjs/components/datepicker/ui/renderers/header.js.map +0 -1
- package/lib/cjs/components/datepicker/ui/renderers/index.js +0 -25
- package/lib/cjs/components/datepicker/ui/renderers/index.js.map +0 -1
- package/lib/cjs/components/datepicker/ui/renderers/time-picker.js +0 -384
- package/lib/cjs/components/datepicker/ui/renderers/time-picker.js.map +0 -1
- package/lib/cjs/components/datepicker/ui/templates/index.js +0 -22
- package/lib/cjs/components/datepicker/ui/templates/index.js.map +0 -1
- package/lib/cjs/components/datepicker/ui/templates/templates.js +0 -253
- package/lib/cjs/components/datepicker/ui/templates/templates.js.map +0 -1
- package/lib/cjs/components/datepicker/utils/date-formatters.js +0 -88
- package/lib/cjs/components/datepicker/utils/date-formatters.js.map +0 -1
- package/lib/cjs/components/datepicker/utils/date-utils.js +0 -194
- package/lib/cjs/components/datepicker/utils/date-utils.js.map +0 -1
- package/lib/cjs/components/datepicker/utils/index.js +0 -24
- package/lib/cjs/components/datepicker/utils/index.js.map +0 -1
- package/lib/cjs/components/datepicker/utils/time-utils.js +0 -213
- package/lib/cjs/components/datepicker/utils/time-utils.js.map +0 -1
- package/lib/esm/components/alert/alert.js +0 -1022
- package/lib/esm/components/alert/alert.js.map +0 -1
- package/lib/esm/components/alert/index.js +0 -4
- package/lib/esm/components/alert/index.js.map +0 -1
- package/lib/esm/components/alert/templates.js +0 -112
- package/lib/esm/components/alert/templates.js.map +0 -1
- package/lib/esm/components/alert/types.js +0 -6
- package/lib/esm/components/alert/types.js.map +0 -1
- package/lib/esm/components/datepicker/config/config.js +0 -39
- package/lib/esm/components/datepicker/config/config.js.map +0 -1
- package/lib/esm/components/datepicker/config/index.js +0 -8
- package/lib/esm/components/datepicker/config/index.js.map +0 -1
- package/lib/esm/components/datepicker/config/interfaces.js +0 -6
- package/lib/esm/components/datepicker/config/interfaces.js.map +0 -1
- package/lib/esm/components/datepicker/config/types.js +0 -6
- package/lib/esm/components/datepicker/config/types.js.map +0 -1
- package/lib/esm/components/datepicker/core/event-manager.js +0 -133
- package/lib/esm/components/datepicker/core/event-manager.js.map +0 -1
- package/lib/esm/components/datepicker/core/focus-manager.js +0 -164
- package/lib/esm/components/datepicker/core/focus-manager.js.map +0 -1
- package/lib/esm/components/datepicker/core/helpers.js +0 -211
- package/lib/esm/components/datepicker/core/helpers.js.map +0 -1
- package/lib/esm/components/datepicker/core/index.js +0 -9
- package/lib/esm/components/datepicker/core/index.js.map +0 -1
- package/lib/esm/components/datepicker/core/unified-state-manager.js +0 -391
- package/lib/esm/components/datepicker/core/unified-state-manager.js.map +0 -1
- package/lib/esm/components/datepicker/datepicker.js +0 -2248
- package/lib/esm/components/datepicker/datepicker.js.map +0 -1
- package/lib/esm/components/datepicker/index.js +0 -7
- package/lib/esm/components/datepicker/index.js.map +0 -1
- package/lib/esm/components/datepicker/ui/index.js +0 -7
- package/lib/esm/components/datepicker/ui/index.js.map +0 -1
- package/lib/esm/components/datepicker/ui/input/dropdown.js +0 -486
- package/lib/esm/components/datepicker/ui/input/dropdown.js.map +0 -1
- package/lib/esm/components/datepicker/ui/input/index.js +0 -7
- package/lib/esm/components/datepicker/ui/input/index.js.map +0 -1
- package/lib/esm/components/datepicker/ui/input/segmented-input.js +0 -637
- package/lib/esm/components/datepicker/ui/input/segmented-input.js.map +0 -1
- package/lib/esm/components/datepicker/ui/renderers/calendar.js +0 -443
- package/lib/esm/components/datepicker/ui/renderers/calendar.js.map +0 -1
- package/lib/esm/components/datepicker/ui/renderers/footer.js +0 -39
- package/lib/esm/components/datepicker/ui/renderers/footer.js.map +0 -1
- package/lib/esm/components/datepicker/ui/renderers/header.js +0 -29
- package/lib/esm/components/datepicker/ui/renderers/header.js.map +0 -1
- package/lib/esm/components/datepicker/ui/renderers/index.js +0 -9
- package/lib/esm/components/datepicker/ui/renderers/index.js.map +0 -1
- package/lib/esm/components/datepicker/ui/renderers/time-picker.js +0 -381
- package/lib/esm/components/datepicker/ui/renderers/time-picker.js.map +0 -1
- package/lib/esm/components/datepicker/ui/templates/index.js +0 -6
- package/lib/esm/components/datepicker/ui/templates/index.js.map +0 -1
- package/lib/esm/components/datepicker/ui/templates/templates.js +0 -242
- package/lib/esm/components/datepicker/ui/templates/templates.js.map +0 -1
- package/lib/esm/components/datepicker/utils/date-formatters.js +0 -83
- package/lib/esm/components/datepicker/utils/date-formatters.js.map +0 -1
- package/lib/esm/components/datepicker/utils/date-utils.js +0 -184
- package/lib/esm/components/datepicker/utils/date-utils.js.map +0 -1
- package/lib/esm/components/datepicker/utils/index.js +0 -8
- package/lib/esm/components/datepicker/utils/index.js.map +0 -1
- package/lib/esm/components/datepicker/utils/time-utils.js +0 -201
- package/lib/esm/components/datepicker/utils/time-utils.js.map +0 -1
- package/src/components/alert/alert.ts +0 -990
- package/src/components/alert/index.ts +0 -4
- package/src/components/alert/templates.ts +0 -110
- package/src/components/alert/tests/accessibility/aria-roles.test.ts +0 -19
- package/src/components/alert/tests/accessibility/focus-management.test.ts +0 -19
- package/src/components/alert/tests/accessibility/keyboard-nav.test.ts +0 -22
- package/src/components/alert/tests/actions/confirm-cancel.test.ts +0 -122
- package/src/components/alert/tests/actions/input-field.test.ts +0 -180
- package/src/components/alert/tests/alert.basic.test.ts +0 -126
- package/src/components/alert/tests/alert.config.test.ts +0 -75
- package/src/components/alert/tests/alert.templates.test.ts +0 -17
- package/src/components/alert/tests/config/attribute-config.test.ts +0 -94
- package/src/components/alert/tests/config/json-config.test.ts +0 -119
- package/src/components/alert/tests/config/merging.test.ts +0 -89
- package/src/components/alert/tests/dismissal/auto-dismiss.test.ts +0 -96
- package/src/components/alert/tests/dismissal/escape-key-dismiss.test.ts +0 -105
- package/src/components/alert/tests/dismissal/manual-dismiss.test.ts +0 -90
- package/src/components/alert/tests/dismissal/outside-click-dismiss.test.ts +0 -91
- package/src/components/alert/tests/edge-cases/invalid-config.test.ts +0 -19
- package/src/components/alert/tests/edge-cases/multiple-alerts.test.ts +0 -19
- package/src/components/alert/tests/rendering/custom-content.test.ts +0 -81
- package/src/components/alert/tests/rendering/info-alert.test.ts +0 -84
- package/src/components/alert/tests/rendering/success-alert.test.ts +0 -100
- package/src/components/alert/tests/templates/default-templates.test.ts +0 -16
- package/src/components/alert/tests/templates/user-templates.test.ts +0 -16
- package/src/components/alert/types.ts +0 -145
- package/src/components/datepicker/__tests__/datepicker-events.test.ts +0 -356
- package/src/components/datepicker/__tests__/datepicker-init.test.ts +0 -343
- package/src/components/datepicker/__tests__/datepicker-integration.test.ts +0 -435
- package/src/components/datepicker/__tests__/datepicker-timezone.test.ts +0 -220
- package/src/components/datepicker/__tests__/segmented-input-focus.test.ts +0 -380
- package/src/components/datepicker/__tests__/selective-state-updates.test.ts +0 -400
- package/src/components/datepicker/__tests__/state-manager.test.ts +0 -421
- package/src/components/datepicker/__tests__/time-preservation.test.ts +0 -387
- package/src/components/datepicker/config/config.ts +0 -40
- package/src/components/datepicker/config/index.ts +0 -8
- package/src/components/datepicker/config/interfaces.ts +0 -82
- package/src/components/datepicker/config/types.ts +0 -188
- package/src/components/datepicker/core/event-manager.ts +0 -159
- package/src/components/datepicker/core/focus-manager.ts +0 -201
- package/src/components/datepicker/core/helpers.ts +0 -231
- package/src/components/datepicker/core/index.ts +0 -9
- package/src/components/datepicker/core/unified-state-manager.ts +0 -459
- package/src/components/datepicker/datepicker.css +0 -435
- package/src/components/datepicker/datepicker.ts +0 -2548
- package/src/components/datepicker/index.ts +0 -8
- package/src/components/datepicker/ui/index.ts +0 -7
- package/src/components/datepicker/ui/input/dropdown.ts +0 -552
- package/src/components/datepicker/ui/input/index.ts +0 -7
- package/src/components/datepicker/ui/input/segmented-input.ts +0 -638
- package/src/components/datepicker/ui/renderers/__tests__/calendar-optimizations.test.ts +0 -611
- package/src/components/datepicker/ui/renderers/calendar.ts +0 -530
- package/src/components/datepicker/ui/renderers/footer.ts +0 -43
- package/src/components/datepicker/ui/renderers/header.ts +0 -33
- package/src/components/datepicker/ui/renderers/index.ts +0 -9
- package/src/components/datepicker/ui/renderers/time-picker.ts +0 -438
- package/src/components/datepicker/ui/templates/index.ts +0 -6
- package/src/components/datepicker/ui/templates/templates.ts +0 -306
- package/src/components/datepicker/utils/__tests__/date-formatters.test.ts +0 -160
- package/src/components/datepicker/utils/__tests__/date-utils-keys.test.ts +0 -86
- package/src/components/datepicker/utils/__tests__/date-utils-timezone.test.ts +0 -215
- package/src/components/datepicker/utils/date-formatters.ts +0 -85
- package/src/components/datepicker/utils/date-utils.ts +0 -172
- package/src/components/datepicker/utils/index.ts +0 -8
- 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
|
-
}
|