@keenthemes/ktui 1.0.29 → 1.1.0

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 (243) hide show
  1. package/README.md +27 -0
  2. package/dist/ktui.js +8780 -6199
  3. package/dist/ktui.min.js +1 -1
  4. package/dist/ktui.min.js.map +1 -1
  5. package/dist/styles.css +2744 -1367
  6. package/lib/cjs/components/alert/alert.js +1025 -0
  7. package/lib/cjs/components/alert/alert.js.map +1 -0
  8. package/lib/cjs/components/alert/index.js +20 -0
  9. package/lib/cjs/components/alert/index.js.map +1 -0
  10. package/lib/cjs/components/alert/templates.js +120 -0
  11. package/lib/cjs/components/alert/templates.js.map +1 -0
  12. package/lib/cjs/components/alert/types.js +7 -0
  13. package/lib/cjs/components/alert/types.js.map +1 -0
  14. package/lib/cjs/components/datepicker/config/config.js +42 -0
  15. package/lib/cjs/components/datepicker/config/config.js.map +1 -0
  16. package/lib/cjs/components/datepicker/config/index.js +24 -0
  17. package/lib/cjs/components/datepicker/config/index.js.map +1 -0
  18. package/lib/cjs/components/datepicker/config/interfaces.js +7 -0
  19. package/lib/cjs/components/datepicker/config/interfaces.js.map +1 -0
  20. package/lib/cjs/components/datepicker/config/types.js +7 -0
  21. package/lib/cjs/components/datepicker/config/types.js.map +1 -0
  22. package/lib/cjs/components/datepicker/core/event-manager.js +135 -0
  23. package/lib/cjs/components/datepicker/core/event-manager.js.map +1 -0
  24. package/lib/cjs/components/datepicker/core/focus-manager.js +167 -0
  25. package/lib/cjs/components/datepicker/core/focus-manager.js.map +1 -0
  26. package/lib/cjs/components/datepicker/core/helpers.js +219 -0
  27. package/lib/cjs/components/datepicker/core/helpers.js.map +1 -0
  28. package/lib/cjs/components/datepicker/core/index.js +25 -0
  29. package/lib/cjs/components/datepicker/core/index.js.map +1 -0
  30. package/lib/cjs/components/datepicker/core/unified-state-manager.js +394 -0
  31. package/lib/cjs/components/datepicker/core/unified-state-manager.js.map +1 -0
  32. package/lib/cjs/components/datepicker/datepicker.js +2066 -763
  33. package/lib/cjs/components/datepicker/datepicker.js.map +1 -1
  34. package/lib/cjs/components/datepicker/index.js +19 -8
  35. package/lib/cjs/components/datepicker/index.js.map +1 -1
  36. package/lib/cjs/components/datepicker/ui/index.js +23 -0
  37. package/lib/cjs/components/datepicker/ui/index.js.map +1 -0
  38. package/lib/cjs/components/datepicker/ui/input/dropdown.js +489 -0
  39. package/lib/cjs/components/datepicker/ui/input/dropdown.js.map +1 -0
  40. package/lib/cjs/components/datepicker/ui/input/index.js +23 -0
  41. package/lib/cjs/components/datepicker/ui/input/index.js.map +1 -0
  42. package/lib/cjs/components/datepicker/ui/input/segmented-input.js +640 -0
  43. package/lib/cjs/components/datepicker/ui/input/segmented-input.js.map +1 -0
  44. package/lib/cjs/components/datepicker/ui/renderers/calendar.js +446 -0
  45. package/lib/cjs/components/datepicker/ui/renderers/calendar.js.map +1 -0
  46. package/lib/cjs/components/datepicker/ui/renderers/footer.js +42 -0
  47. package/lib/cjs/components/datepicker/ui/renderers/footer.js.map +1 -0
  48. package/lib/cjs/components/datepicker/ui/renderers/header.js +32 -0
  49. package/lib/cjs/components/datepicker/ui/renderers/header.js.map +1 -0
  50. package/lib/cjs/components/datepicker/ui/renderers/index.js +25 -0
  51. package/lib/cjs/components/datepicker/ui/renderers/index.js.map +1 -0
  52. package/lib/cjs/components/datepicker/ui/renderers/time-picker.js +384 -0
  53. package/lib/cjs/components/datepicker/ui/renderers/time-picker.js.map +1 -0
  54. package/lib/cjs/components/datepicker/ui/templates/index.js +22 -0
  55. package/lib/cjs/components/datepicker/ui/templates/index.js.map +1 -0
  56. package/lib/cjs/components/datepicker/ui/templates/templates.js +253 -0
  57. package/lib/cjs/components/datepicker/ui/templates/templates.js.map +1 -0
  58. package/lib/cjs/components/datepicker/utils/date-formatters.js +88 -0
  59. package/lib/cjs/components/datepicker/utils/date-formatters.js.map +1 -0
  60. package/lib/cjs/components/datepicker/utils/date-utils.js +194 -0
  61. package/lib/cjs/components/datepicker/utils/date-utils.js.map +1 -0
  62. package/lib/cjs/components/datepicker/utils/index.js +24 -0
  63. package/lib/cjs/components/datepicker/utils/index.js.map +1 -0
  64. package/lib/cjs/components/datepicker/utils/time-utils.js +213 -0
  65. package/lib/cjs/components/datepicker/utils/time-utils.js.map +1 -0
  66. package/lib/cjs/index.js +6 -1
  67. package/lib/cjs/index.js.map +1 -1
  68. package/lib/esm/components/alert/alert.js +1022 -0
  69. package/lib/esm/components/alert/alert.js.map +1 -0
  70. package/lib/esm/components/alert/index.js +4 -0
  71. package/lib/esm/components/alert/index.js.map +1 -0
  72. package/lib/esm/components/alert/templates.js +112 -0
  73. package/lib/esm/components/alert/templates.js.map +1 -0
  74. package/lib/esm/components/alert/types.js +6 -0
  75. package/lib/esm/components/alert/types.js.map +1 -0
  76. package/lib/esm/components/datepicker/config/config.js +39 -0
  77. package/lib/esm/components/datepicker/config/config.js.map +1 -0
  78. package/lib/esm/components/datepicker/config/index.js +8 -0
  79. package/lib/esm/components/datepicker/config/index.js.map +1 -0
  80. package/lib/esm/components/datepicker/config/interfaces.js +6 -0
  81. package/lib/esm/components/datepicker/config/interfaces.js.map +1 -0
  82. package/lib/esm/components/datepicker/config/types.js +6 -0
  83. package/lib/esm/components/datepicker/config/types.js.map +1 -0
  84. package/lib/esm/components/datepicker/core/event-manager.js +133 -0
  85. package/lib/esm/components/datepicker/core/event-manager.js.map +1 -0
  86. package/lib/esm/components/datepicker/core/focus-manager.js +164 -0
  87. package/lib/esm/components/datepicker/core/focus-manager.js.map +1 -0
  88. package/lib/esm/components/datepicker/core/helpers.js +211 -0
  89. package/lib/esm/components/datepicker/core/helpers.js.map +1 -0
  90. package/lib/esm/components/datepicker/core/index.js +9 -0
  91. package/lib/esm/components/datepicker/core/index.js.map +1 -0
  92. package/lib/esm/components/datepicker/core/unified-state-manager.js +391 -0
  93. package/lib/esm/components/datepicker/core/unified-state-manager.js.map +1 -0
  94. package/lib/esm/components/datepicker/datepicker.js +2065 -763
  95. package/lib/esm/components/datepicker/datepicker.js.map +1 -1
  96. package/lib/esm/components/datepicker/index.js +6 -8
  97. package/lib/esm/components/datepicker/index.js.map +1 -1
  98. package/lib/esm/components/datepicker/ui/index.js +7 -0
  99. package/lib/esm/components/datepicker/ui/index.js.map +1 -0
  100. package/lib/esm/components/datepicker/ui/input/dropdown.js +486 -0
  101. package/lib/esm/components/datepicker/ui/input/dropdown.js.map +1 -0
  102. package/lib/esm/components/datepicker/ui/input/index.js +7 -0
  103. package/lib/esm/components/datepicker/ui/input/index.js.map +1 -0
  104. package/lib/esm/components/datepicker/ui/input/segmented-input.js +637 -0
  105. package/lib/esm/components/datepicker/ui/input/segmented-input.js.map +1 -0
  106. package/lib/esm/components/datepicker/ui/renderers/calendar.js +443 -0
  107. package/lib/esm/components/datepicker/ui/renderers/calendar.js.map +1 -0
  108. package/lib/esm/components/datepicker/ui/renderers/footer.js +39 -0
  109. package/lib/esm/components/datepicker/ui/renderers/footer.js.map +1 -0
  110. package/lib/esm/components/datepicker/ui/renderers/header.js +29 -0
  111. package/lib/esm/components/datepicker/ui/renderers/header.js.map +1 -0
  112. package/lib/esm/components/datepicker/ui/renderers/index.js +9 -0
  113. package/lib/esm/components/datepicker/ui/renderers/index.js.map +1 -0
  114. package/lib/esm/components/datepicker/ui/renderers/time-picker.js +381 -0
  115. package/lib/esm/components/datepicker/ui/renderers/time-picker.js.map +1 -0
  116. package/lib/esm/components/datepicker/ui/templates/index.js +6 -0
  117. package/lib/esm/components/datepicker/ui/templates/index.js.map +1 -0
  118. package/lib/esm/components/datepicker/ui/templates/templates.js +242 -0
  119. package/lib/esm/components/datepicker/ui/templates/templates.js.map +1 -0
  120. package/lib/esm/components/datepicker/utils/date-formatters.js +83 -0
  121. package/lib/esm/components/datepicker/utils/date-formatters.js.map +1 -0
  122. package/lib/esm/components/datepicker/utils/date-utils.js +184 -0
  123. package/lib/esm/components/datepicker/utils/date-utils.js.map +1 -0
  124. package/lib/esm/components/datepicker/utils/index.js +8 -0
  125. package/lib/esm/components/datepicker/utils/index.js.map +1 -0
  126. package/lib/esm/components/datepicker/utils/time-utils.js +201 -0
  127. package/lib/esm/components/datepicker/utils/time-utils.js.map +1 -0
  128. package/lib/esm/index.js +4 -0
  129. package/lib/esm/index.js.map +1 -1
  130. package/package.json +12 -3
  131. package/src/components/alert/alert.css +429 -188
  132. package/src/components/alert/alert.ts +990 -0
  133. package/src/components/alert/index.ts +4 -0
  134. package/src/components/alert/templates.ts +110 -0
  135. package/src/components/alert/tests/accessibility/aria-roles.test.ts +19 -0
  136. package/src/components/alert/tests/accessibility/focus-management.test.ts +19 -0
  137. package/src/components/alert/tests/accessibility/keyboard-nav.test.ts +22 -0
  138. package/src/components/alert/tests/actions/confirm-cancel.test.ts +122 -0
  139. package/src/components/alert/tests/actions/input-field.test.ts +180 -0
  140. package/src/components/alert/tests/alert.basic.test.ts +126 -0
  141. package/src/components/alert/tests/alert.config.test.ts +75 -0
  142. package/src/components/alert/tests/alert.templates.test.ts +17 -0
  143. package/src/components/alert/tests/config/attribute-config.test.ts +94 -0
  144. package/src/components/alert/tests/config/json-config.test.ts +119 -0
  145. package/src/components/alert/tests/config/merging.test.ts +89 -0
  146. package/src/components/alert/tests/dismissal/auto-dismiss.test.ts +96 -0
  147. package/src/components/alert/tests/dismissal/escape-key-dismiss.test.ts +105 -0
  148. package/src/components/alert/tests/dismissal/manual-dismiss.test.ts +90 -0
  149. package/src/components/alert/tests/dismissal/outside-click-dismiss.test.ts +91 -0
  150. package/src/components/alert/tests/edge-cases/invalid-config.test.ts +19 -0
  151. package/src/components/alert/tests/edge-cases/multiple-alerts.test.ts +19 -0
  152. package/src/components/alert/tests/rendering/custom-content.test.ts +81 -0
  153. package/src/components/alert/tests/rendering/info-alert.test.ts +84 -0
  154. package/src/components/alert/tests/rendering/success-alert.test.ts +100 -0
  155. package/src/components/alert/tests/templates/default-templates.test.ts +16 -0
  156. package/src/components/alert/tests/templates/user-templates.test.ts +16 -0
  157. package/src/components/alert/types.ts +145 -0
  158. package/src/components/datepicker/__tests__/datepicker-events.test.ts +356 -0
  159. package/src/components/datepicker/__tests__/datepicker-init.test.ts +343 -0
  160. package/src/components/datepicker/__tests__/datepicker-integration.test.ts +435 -0
  161. package/src/components/datepicker/__tests__/datepicker-timezone.test.ts +220 -0
  162. package/src/components/datepicker/__tests__/segmented-input-focus.test.ts +380 -0
  163. package/src/components/datepicker/__tests__/selective-state-updates.test.ts +400 -0
  164. package/src/components/datepicker/__tests__/state-manager.test.ts +421 -0
  165. package/src/components/datepicker/__tests__/time-preservation.test.ts +387 -0
  166. package/src/components/datepicker/config/config.ts +40 -0
  167. package/src/components/datepicker/config/index.ts +8 -0
  168. package/src/components/datepicker/config/interfaces.ts +82 -0
  169. package/src/components/datepicker/config/types.ts +188 -0
  170. package/src/components/datepicker/core/event-manager.ts +159 -0
  171. package/src/components/datepicker/core/focus-manager.ts +201 -0
  172. package/src/components/datepicker/core/helpers.ts +231 -0
  173. package/src/components/datepicker/core/index.ts +9 -0
  174. package/src/components/datepicker/core/unified-state-manager.ts +459 -0
  175. package/src/components/datepicker/datepicker.css +429 -1
  176. package/src/components/datepicker/datepicker.ts +2538 -1277
  177. package/src/components/datepicker/index.ts +6 -8
  178. package/src/components/datepicker/ui/index.ts +7 -0
  179. package/src/components/datepicker/ui/input/dropdown.ts +552 -0
  180. package/src/components/datepicker/ui/input/index.ts +7 -0
  181. package/src/components/datepicker/ui/input/segmented-input.ts +638 -0
  182. package/src/components/datepicker/ui/renderers/__tests__/calendar-optimizations.test.ts +611 -0
  183. package/src/components/datepicker/ui/renderers/calendar.ts +530 -0
  184. package/src/components/datepicker/ui/renderers/footer.ts +43 -0
  185. package/src/components/datepicker/ui/renderers/header.ts +33 -0
  186. package/src/components/datepicker/ui/renderers/index.ts +9 -0
  187. package/src/components/datepicker/ui/renderers/time-picker.ts +438 -0
  188. package/src/components/datepicker/ui/templates/index.ts +6 -0
  189. package/src/components/datepicker/ui/templates/templates.ts +306 -0
  190. package/src/components/datepicker/utils/__tests__/date-formatters.test.ts +160 -0
  191. package/src/components/datepicker/utils/__tests__/date-utils-keys.test.ts +86 -0
  192. package/src/components/datepicker/utils/__tests__/date-utils-timezone.test.ts +215 -0
  193. package/src/components/datepicker/utils/date-formatters.ts +85 -0
  194. package/src/components/datepicker/utils/date-utils.ts +172 -0
  195. package/src/components/datepicker/utils/index.ts +8 -0
  196. package/src/components/datepicker/utils/time-utils.ts +221 -0
  197. package/src/index.ts +7 -1
  198. package/lib/cjs/components/datepicker/calendar.js +0 -1061
  199. package/lib/cjs/components/datepicker/calendar.js.map +0 -1
  200. package/lib/cjs/components/datepicker/config.js +0 -332
  201. package/lib/cjs/components/datepicker/config.js.map +0 -1
  202. package/lib/cjs/components/datepicker/dropdown.js +0 -635
  203. package/lib/cjs/components/datepicker/dropdown.js.map +0 -1
  204. package/lib/cjs/components/datepicker/events.js +0 -129
  205. package/lib/cjs/components/datepicker/events.js.map +0 -1
  206. package/lib/cjs/components/datepicker/keyboard.js +0 -536
  207. package/lib/cjs/components/datepicker/keyboard.js.map +0 -1
  208. package/lib/cjs/components/datepicker/locales.js +0 -78
  209. package/lib/cjs/components/datepicker/locales.js.map +0 -1
  210. package/lib/cjs/components/datepicker/templates.js +0 -403
  211. package/lib/cjs/components/datepicker/templates.js.map +0 -1
  212. package/lib/cjs/components/datepicker/types.js +0 -23
  213. package/lib/cjs/components/datepicker/types.js.map +0 -1
  214. package/lib/cjs/components/datepicker/utils.js +0 -524
  215. package/lib/cjs/components/datepicker/utils.js.map +0 -1
  216. package/lib/esm/components/datepicker/calendar.js +0 -1058
  217. package/lib/esm/components/datepicker/calendar.js.map +0 -1
  218. package/lib/esm/components/datepicker/config.js +0 -329
  219. package/lib/esm/components/datepicker/config.js.map +0 -1
  220. package/lib/esm/components/datepicker/dropdown.js +0 -632
  221. package/lib/esm/components/datepicker/dropdown.js.map +0 -1
  222. package/lib/esm/components/datepicker/events.js +0 -126
  223. package/lib/esm/components/datepicker/events.js.map +0 -1
  224. package/lib/esm/components/datepicker/keyboard.js +0 -533
  225. package/lib/esm/components/datepicker/keyboard.js.map +0 -1
  226. package/lib/esm/components/datepicker/locales.js +0 -74
  227. package/lib/esm/components/datepicker/locales.js.map +0 -1
  228. package/lib/esm/components/datepicker/templates.js +0 -390
  229. package/lib/esm/components/datepicker/templates.js.map +0 -1
  230. package/lib/esm/components/datepicker/types.js +0 -20
  231. package/lib/esm/components/datepicker/types.js.map +0 -1
  232. package/lib/esm/components/datepicker/utils.js +0 -508
  233. package/lib/esm/components/datepicker/utils.js.map +0 -1
  234. package/src/components/datepicker/calendar.ts +0 -1397
  235. package/src/components/datepicker/config.ts +0 -368
  236. package/src/components/datepicker/dropdown.ts +0 -757
  237. package/src/components/datepicker/events.ts +0 -149
  238. package/src/components/datepicker/keyboard.ts +0 -646
  239. package/src/components/datepicker/locales.ts +0 -80
  240. package/src/components/datepicker/templates.ts +0 -792
  241. package/src/components/datepicker/types.ts +0 -154
  242. package/src/components/datepicker/utils.ts +0 -631
  243. package/src/components/select/variants.css +0 -4
@@ -0,0 +1,159 @@
1
+ /*
2
+ * event-manager.ts - Internal event management for KTDatepicker
3
+ * Replaces external dependency on select component's EventManager
4
+ */
5
+
6
+ /**
7
+ * Event listener function type
8
+ */
9
+ export type EventListener = (event: Event) => void;
10
+
11
+ /**
12
+ * Event manager configuration
13
+ */
14
+ export interface EventManagerConfig {
15
+ enableEventBubbling: boolean;
16
+ enableCustomEvents: boolean;
17
+ enableValidation: boolean;
18
+ enableDebugging: boolean;
19
+ }
20
+
21
+ /**
22
+ * Event manager for handling DOM events
23
+ */
24
+ export class EventManager {
25
+ // Alias for backward compatibility
26
+ static KTDropdownEventManager = EventManager;
27
+ private _listeners: Map<HTMLElement, Map<string, Set<EventListener>>> = new Map();
28
+ private _config: EventManagerConfig;
29
+
30
+ constructor(config?: Partial<EventManagerConfig>) {
31
+ this._config = {
32
+ enableEventBubbling: true,
33
+ enableCustomEvents: false,
34
+ enableValidation: true,
35
+ enableDebugging: false,
36
+ ...config
37
+ };
38
+ }
39
+
40
+ /**
41
+ * Add event listener to element
42
+ */
43
+ public addListener(element: HTMLElement, eventType: string, listener: EventListener): void {
44
+ if (!this._listeners.has(element)) {
45
+ this._listeners.set(element, new Map());
46
+ }
47
+
48
+ const elementListeners = this._listeners.get(element)!;
49
+ if (!elementListeners.has(eventType)) {
50
+ elementListeners.set(eventType, new Set());
51
+ }
52
+
53
+ const listeners = elementListeners.get(eventType)!;
54
+ listeners.add(listener);
55
+
56
+ element.addEventListener(eventType, listener, !this._config.enableEventBubbling);
57
+ }
58
+
59
+ /**
60
+ * Remove event listener from element
61
+ */
62
+ public removeListener(element: HTMLElement, eventType: string, listener: EventListener): void {
63
+ const elementListeners = this._listeners.get(element);
64
+ if (!elementListeners) return;
65
+
66
+ const listeners = elementListeners.get(eventType);
67
+ if (!listeners) return;
68
+
69
+ listeners.delete(listener);
70
+ element.removeEventListener(eventType, listener, !this._config.enableEventBubbling);
71
+
72
+ if (listeners.size === 0) {
73
+ elementListeners.delete(eventType);
74
+ }
75
+
76
+ if (elementListeners.size === 0) {
77
+ this._listeners.delete(element);
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Remove all listeners from element
83
+ */
84
+ public removeAllListeners(element: HTMLElement): void {
85
+ const elementListeners = this._listeners.get(element);
86
+ if (!elementListeners) return;
87
+
88
+ elementListeners.forEach((listeners, eventType) => {
89
+ listeners.forEach(listener => {
90
+ element.removeEventListener(eventType, listener, !this._config.enableEventBubbling);
91
+ });
92
+ });
93
+
94
+ this._listeners.delete(element);
95
+ }
96
+
97
+ /**
98
+ * Remove all listeners from all elements
99
+ */
100
+ public removeAllListenersFromAll(): void {
101
+ this._listeners.forEach((elementListeners, element) => {
102
+ elementListeners.forEach((listeners, eventType) => {
103
+ listeners.forEach(listener => {
104
+ element.removeEventListener(eventType, listener, !this._config.enableEventBubbling);
105
+ });
106
+ });
107
+ });
108
+
109
+ this._listeners.clear();
110
+ }
111
+
112
+ /**
113
+ * Dispatch custom event
114
+ */
115
+ public dispatchEvent(element: HTMLElement, eventType: string, detail?: any): boolean {
116
+ if (!this._config.enableCustomEvents) {
117
+ return false;
118
+ }
119
+
120
+ const event = new CustomEvent(eventType, {
121
+ detail,
122
+ bubbles: this._config.enableEventBubbling,
123
+ cancelable: true
124
+ });
125
+
126
+ const result = element.dispatchEvent(event);
127
+
128
+ return result;
129
+ }
130
+
131
+ /**
132
+ * Get listener count for element and event type
133
+ */
134
+ public getListenerCount(element: HTMLElement, eventType?: string): number {
135
+ const elementListeners = this._listeners.get(element);
136
+ if (!elementListeners) return 0;
137
+
138
+ if (eventType) {
139
+ return elementListeners.get(eventType)?.size || 0;
140
+ }
141
+
142
+ let total = 0;
143
+ elementListeners.forEach(listeners => {
144
+ total += listeners.size;
145
+ });
146
+ return total;
147
+ }
148
+
149
+ /**
150
+ * Dispose of event manager
151
+ */
152
+ public dispose(): void {
153
+ this.removeAllListenersFromAll();
154
+ this._listeners.clear();
155
+ }
156
+ }
157
+
158
+ // Export alias for backward compatibility
159
+ export { EventManager as KTDropdownEventManager };
@@ -0,0 +1,201 @@
1
+ /*
2
+ * focus-manager.ts - Internal focus management for KTDatepicker
3
+ * Replaces external dependency on select component's FocusManager
4
+ */
5
+
6
+ /**
7
+ * Focus manager configuration
8
+ */
9
+ export interface FocusManagerConfig {
10
+ enableFocusTrapping: boolean;
11
+ enableFocusRestoration: boolean;
12
+ enableKeyboardNavigation: boolean;
13
+ enableDebugging: boolean;
14
+ }
15
+
16
+ /**
17
+ * Focusable element interface
18
+ */
19
+ export interface FocusableElement {
20
+ element: HTMLElement;
21
+ tabIndex: number;
22
+ isFocusable: boolean;
23
+ }
24
+
25
+ /**
26
+ * Focus manager for handling focus states and navigation
27
+ */
28
+ export class FocusManager {
29
+ private _config: FocusManagerConfig;
30
+ private _focusedElement: HTMLElement | null = null;
31
+ private _previousFocusedElement: HTMLElement | null = null;
32
+ private _focusableElements: FocusableElement[] = [];
33
+ private _currentFocusIndex: number = -1;
34
+
35
+ constructor(config?: Partial<FocusManagerConfig>) {
36
+ this._config = {
37
+ enableFocusTrapping: true,
38
+ enableFocusRestoration: true,
39
+ enableKeyboardNavigation: true,
40
+ enableDebugging: false,
41
+ ...config
42
+ };
43
+ }
44
+
45
+ /**
46
+ * Set focus to element
47
+ */
48
+ public focus(element: HTMLElement): boolean {
49
+ if (!element || !this._isElementFocusable(element)) {
50
+ return false;
51
+ }
52
+
53
+ this._previousFocusedElement = this._focusedElement;
54
+ this._focusedElement = element;
55
+
56
+ try {
57
+ element.focus();
58
+ return true;
59
+ } catch (error) {
60
+ return false;
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Get currently focused element
66
+ */
67
+ public getFocusedElement(): HTMLElement | null {
68
+ return this._focusedElement;
69
+ }
70
+
71
+ /**
72
+ * Get previously focused element
73
+ */
74
+ public getPreviousFocusedElement(): HTMLElement | null {
75
+ return this._previousFocusedElement;
76
+ }
77
+
78
+ /**
79
+ * Restore focus to previous element
80
+ */
81
+ public restoreFocus(): boolean {
82
+ if (!this._config.enableFocusRestoration || !this._previousFocusedElement) {
83
+ return false;
84
+ }
85
+
86
+ return this.focus(this._previousFocusedElement);
87
+ }
88
+
89
+ /**
90
+ * Set focusable elements for navigation
91
+ */
92
+ public setFocusableElements(elements: HTMLElement[]): void {
93
+ this._focusableElements = elements
94
+ .filter(element => this._isElementFocusable(element))
95
+ .map(element => ({
96
+ element,
97
+ tabIndex: this._getTabIndex(element),
98
+ isFocusable: true
99
+ }))
100
+ .sort((a, b) => a.tabIndex - b.tabIndex);
101
+ }
102
+
103
+ /**
104
+ * Focus next element
105
+ */
106
+ public focusNext(): boolean {
107
+ if (!this._config.enableKeyboardNavigation || this._focusableElements.length === 0) {
108
+ return false;
109
+ }
110
+
111
+ this._currentFocusIndex = (this._currentFocusIndex + 1) % this._focusableElements.length;
112
+ const nextElement = this._focusableElements[this._currentFocusIndex];
113
+
114
+ return this.focus(nextElement.element);
115
+ }
116
+
117
+ /**
118
+ * Focus previous element
119
+ */
120
+ public focusPrevious(): boolean {
121
+ if (!this._config.enableKeyboardNavigation || this._focusableElements.length === 0) {
122
+ return false;
123
+ }
124
+
125
+ this._currentFocusIndex = this._currentFocusIndex <= 0
126
+ ? this._focusableElements.length - 1
127
+ : this._currentFocusIndex - 1;
128
+
129
+ const prevElement = this._focusableElements[this._currentFocusIndex];
130
+
131
+ return this.focus(prevElement.element);
132
+ }
133
+
134
+ /**
135
+ * Focus first element
136
+ */
137
+ public focusFirst(): boolean {
138
+ if (this._focusableElements.length === 0) {
139
+ return false;
140
+ }
141
+
142
+ this._currentFocusIndex = 0;
143
+ return this.focus(this._focusableElements[0].element);
144
+ }
145
+
146
+ /**
147
+ * Focus last element
148
+ */
149
+ public focusLast(): boolean {
150
+ if (this._focusableElements.length === 0) {
151
+ return false;
152
+ }
153
+
154
+ this._currentFocusIndex = this._focusableElements.length - 1;
155
+ return this.focus(this._focusableElements[this._currentFocusIndex].element);
156
+ }
157
+
158
+ /**
159
+ * Check if element is focusable
160
+ */
161
+ private _isElementFocusable(element: HTMLElement): boolean {
162
+ if (!element || element.hidden || element.style.display === 'none') {
163
+ return false;
164
+ }
165
+
166
+ const tabIndex = this._getTabIndex(element);
167
+ return tabIndex >= 0;
168
+ }
169
+
170
+ /**
171
+ * Get tab index of element
172
+ */
173
+ private _getTabIndex(element: HTMLElement): number {
174
+ const tabIndex = element.getAttribute('tabindex');
175
+ if (tabIndex === null) {
176
+ // Check if element is naturally focusable
177
+ const tagName = element.tagName.toLowerCase();
178
+ const naturallyFocusable = ['input', 'button', 'select', 'textarea', 'a'].includes(tagName);
179
+ return naturallyFocusable ? 0 : -1;
180
+ }
181
+
182
+ return parseInt(tabIndex, 10);
183
+ }
184
+
185
+ /**
186
+ * Clear focus
187
+ */
188
+ public clearFocus(): void {
189
+ this._focusedElement = null;
190
+ this._currentFocusIndex = -1;
191
+ }
192
+
193
+ /**
194
+ * Dispose of focus manager
195
+ */
196
+ public dispose(): void {
197
+ this.clearFocus();
198
+ this._focusableElements = [];
199
+ this._previousFocusedElement = null;
200
+ }
201
+ }
@@ -0,0 +1,231 @@
1
+ /*
2
+ * datepicker-helpers.ts - Modular helpers for KTDatepicker input rendering and state
3
+ */
4
+ import { KTDatepickerConfig, KTDatepickerState } from '../config/types';
5
+ import { renderTemplateToDOM, createTemplateRenderer, getTemplateStrings } from '../ui/templates/templates';
6
+ import { SegmentedInput } from '../ui/input/segmented-input';
7
+ import { getTimeSegments } from '../utils/time-utils';
8
+ import { getSegmentOrderFromFormat, normalizeDateToLocalMidnight } from '../utils/date-utils';
9
+
10
+ /**
11
+ * Get segments array based on configuration (date + optional time)
12
+ * @param config Datepicker configuration
13
+ * @returns Array of segment types
14
+ */
15
+ export function getSegmentsForConfig(config: KTDatepickerConfig): Array<'day' | 'month' | 'year' | 'hour' | 'minute' | 'second' | 'ampm'> {
16
+ // Determine date segments based on format
17
+ let segments: Array<'day' | 'month' | 'year' | 'hour' | 'minute' | 'second' | 'ampm'>;
18
+
19
+ if (config.format && typeof config.format === 'string') {
20
+ // Use format-derived segment order for date parts
21
+ segments = getSegmentOrderFromFormat(config.format);
22
+ } else {
23
+ // Default fallback
24
+ segments = ['month', 'day', 'year'];
25
+ }
26
+
27
+ // Add time segments if time is enabled
28
+ if (config.enableTime) {
29
+ const timeSegments = getTimeSegments(config.timeGranularity || 'minute');
30
+ segments = [...segments, ...timeSegments];
31
+
32
+ // Add AM/PM for 12-hour format
33
+ if (config.timeFormat === '12h') {
34
+ segments.push('ampm');
35
+ }
36
+ }
37
+
38
+ return segments;
39
+ }
40
+
41
+ export {};
42
+
43
+ export function renderSingleSegmentedInputUI(
44
+ inputWrapperTpl: string | ((data: any) => string),
45
+ calendarButtonHtml: string,
46
+ config?: KTDatepickerConfig
47
+ ): HTMLElement {
48
+ // Get template renderer with proper configuration
49
+ const templates = getTemplateStrings(config);
50
+ const templateRenderer = createTemplateRenderer(templates);
51
+
52
+ // Create the segmented input container using the template system
53
+ const segmentedInputEl = templateRenderer.renderTemplateToElement({
54
+ templateKey: 'segmentedDateInput',
55
+ data: { segments: '' }, // Empty segments initially, will be populated by SegmentedInput
56
+ configClasses: config?.classes // Pass config classes for proper styling
57
+ });
58
+
59
+ let inputWrapperHtml: string;
60
+ if (typeof inputWrapperTpl === 'function') {
61
+ inputWrapperHtml = inputWrapperTpl({ input: segmentedInputEl.outerHTML, icon: calendarButtonHtml, class: '' });
62
+ } else {
63
+ inputWrapperHtml = inputWrapperTpl
64
+ .replace(/{{icon}}/g, calendarButtonHtml)
65
+ .replace(/{{input}}/g, segmentedInputEl.outerHTML)
66
+ .replace(/{{class}}/g, ''); // Replace class placeholder with empty string if no class provided
67
+ }
68
+ const inputWrapperFrag = renderTemplateToDOM(inputWrapperHtml);
69
+ return inputWrapperFrag.firstElementChild as HTMLElement;
70
+ }
71
+
72
+ export function renderRangeSegmentedInputUI(
73
+ inputWrapperTpl: string | ((data: any) => string),
74
+ rangeTpl: string | ((data: any) => string),
75
+ calendarButtonHtml: string,
76
+ config?: KTDatepickerConfig
77
+ ): { inputWrapperEl: HTMLElement; startContainer: HTMLElement; endContainer: HTMLElement } {
78
+ // Get template renderer with proper configuration
79
+ const templates = getTemplateStrings(config);
80
+ const templateRenderer = createTemplateRenderer(templates);
81
+
82
+ let inputWrapperHtml: string;
83
+ if (typeof inputWrapperTpl === 'function') {
84
+ inputWrapperHtml = inputWrapperTpl({ input: '', icon: calendarButtonHtml, class: '' });
85
+ } else {
86
+ inputWrapperHtml = inputWrapperTpl
87
+ .replace(/{{icon}}/g, calendarButtonHtml)
88
+ .replace(/{{input}}/g, '')
89
+ .replace(/{{class}}/g, ''); // Replace class placeholder with empty string if no class provided
90
+ }
91
+ const inputWrapperFrag = renderTemplateToDOM(inputWrapperHtml);
92
+ const inputWrapperEl = inputWrapperFrag.firstElementChild as HTMLElement;
93
+
94
+ // Create containers for start and end segmented inputs using template system
95
+ const startContainer = templateRenderer.renderTemplateToElement({
96
+ templateKey: 'segmentedDateInput',
97
+ data: { segments: '' },
98
+ configClasses: { ...config?.classes, segmentedDateInput: 'kt-segmented-input-start' }
99
+ });
100
+ startContainer.setAttribute('aria-label', 'Start date');
101
+ startContainer.setAttribute('data-kt-datepicker-start-container', '');
102
+
103
+ const endContainer = templateRenderer.renderTemplateToElement({
104
+ templateKey: 'segmentedDateInput',
105
+ data: { segments: '' },
106
+ configClasses: { ...config?.classes, segmentedDateInput: 'kt-segmented-input-end' }
107
+ });
108
+ endContainer.setAttribute('aria-label', 'End date');
109
+ endContainer.setAttribute('data-kt-datepicker-end-container', '');
110
+ // Render template with placeholders using template system
111
+ const separator = '–';
112
+
113
+ // Render start and end containers using templates
114
+ const startContainerHtml = templateRenderer.renderTemplateString({
115
+ templateKey: 'rangeStartContainer',
116
+ data: {},
117
+ configClasses: config?.classes
118
+ });
119
+
120
+ const endContainerHtml = templateRenderer.renderTemplateString({
121
+ templateKey: 'rangeEndContainer',
122
+ data: {},
123
+ configClasses: config?.classes
124
+ });
125
+
126
+ let rangeHtml: string;
127
+ if (typeof rangeTpl === 'function') {
128
+ rangeHtml = rangeTpl({
129
+ start: startContainerHtml,
130
+ separator,
131
+ end: endContainerHtml,
132
+ class: ''
133
+ });
134
+ } else {
135
+ rangeHtml = rangeTpl
136
+ .replace(/{{start}}/g, startContainerHtml)
137
+ .replace(/{{separator}}/g, separator)
138
+ .replace(/{{end}}/g, endContainerHtml)
139
+ .replace(/{{class}}/g, ''); // Replace class placeholder with empty string if no class provided
140
+ }
141
+ const rangeFrag = renderTemplateToDOM(rangeHtml);
142
+ // Find mount points
143
+ const startMount = rangeFrag.querySelector('[data-kt-datepicker-segmented-start]') as HTMLElement;
144
+ const endMount = rangeFrag.querySelector('[data-kt-datepicker-segmented-end]') as HTMLElement;
145
+ if (startMount) startMount.replaceWith(startContainer);
146
+ if (endMount) endMount.replaceWith(endContainer);
147
+ // Insert the range input UI at the start of the wrapper
148
+ inputWrapperEl.insertBefore(rangeFrag.firstElementChild!, inputWrapperEl.firstChild);
149
+ return { inputWrapperEl, startContainer, endContainer };
150
+ }
151
+
152
+ export function instantiateSingleSegmentedInput(
153
+ container: HTMLElement,
154
+ state: KTDatepickerState,
155
+ config: KTDatepickerConfig,
156
+ onChange: (date: Date) => void
157
+ ): void {
158
+ // Use shared utility to determine segments
159
+ const segments = getSegmentsForConfig(config);
160
+
161
+ SegmentedInput(container, {
162
+ value: state.selectedDate || state.currentDate || new Date(),
163
+ format: config.format,
164
+ segments,
165
+ disabled: !!config.disabled,
166
+ required: !!config.required,
167
+ readOnly: !!config.readOnly,
168
+ locale: config.locale,
169
+ timeFormat: config.timeFormat,
170
+ onChange,
171
+ });
172
+ }
173
+
174
+ export function instantiateRangeSegmentedInputs(
175
+ startContainer: HTMLElement,
176
+ endContainer: HTMLElement,
177
+ state: KTDatepickerState,
178
+ config: KTDatepickerConfig,
179
+ onStartChange: (date: Date) => void,
180
+ onEndChange: (date: Date) => void
181
+ ): void {
182
+ // Use shared utility to determine segments (includes time if enabled)
183
+ const segments = getSegmentsForConfig(config);
184
+
185
+ SegmentedInput(startContainer, {
186
+ value: state.selectedRange?.start || new Date(),
187
+ format: config.format,
188
+ segments,
189
+ disabled: !!config.disabled,
190
+ required: !!config.required,
191
+ readOnly: !!config.readOnly,
192
+ locale: config.locale,
193
+ timeFormat: config.timeFormat,
194
+ onChange: onStartChange,
195
+ });
196
+ SegmentedInput(endContainer, {
197
+ value: state.selectedRange?.end || new Date(),
198
+ format: config.format,
199
+ segments,
200
+ disabled: !!config.disabled,
201
+ required: !!config.required,
202
+ readOnly: !!config.readOnly,
203
+ locale: config.locale,
204
+ timeFormat: config.timeFormat,
205
+ onChange: onEndChange,
206
+ });
207
+ }
208
+
209
+ export function updateRangeSelection(
210
+ selectedRange: { start: Date | null; end: Date | null } | null,
211
+ date: Date
212
+ ): { start: Date | null; end: Date | null } {
213
+ // Normalize dates to local midnight for date-only comparisons
214
+ const normalizedDate = normalizeDateToLocalMidnight(date);
215
+
216
+ if (!selectedRange || (!selectedRange.start && !selectedRange.end)) {
217
+ return { start: normalizedDate, end: null };
218
+ } else if (selectedRange.start && !selectedRange.end) {
219
+ const normalizedStart = normalizeDateToLocalMidnight(selectedRange.start);
220
+ if (normalizedDate >= normalizedStart) {
221
+ // Forward selection: new date is after start, becomes end
222
+ return { start: selectedRange.start, end: normalizedDate };
223
+ } else {
224
+ // Backward selection: new date is before start, swap so start is always earlier
225
+ // The clicked date becomes the actual start, original start becomes end
226
+ return { start: normalizedDate, end: selectedRange.start };
227
+ }
228
+ } else {
229
+ return { start: normalizedDate, end: null };
230
+ }
231
+ }
@@ -0,0 +1,9 @@
1
+ /*
2
+ * core/index.ts - Core module exports
3
+ * Provides core logic components for KTDatepicker
4
+ */
5
+
6
+ export * from './unified-state-manager';
7
+ export * from './event-manager';
8
+ export * from './focus-manager';
9
+ export * from './helpers';