@keenthemes/ktui 1.0.28 → 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 (288) 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 +22 -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/CONTRIBUTING.md +0 -101
  199. package/examples/datatable/checkbox-events-test.html +0 -400
  200. package/examples/datatable/credentials-test.html +0 -423
  201. package/examples/datatable/remote-checkbox-test.html +0 -365
  202. package/examples/datatable/sorting-test.html +0 -258
  203. package/examples/image-input/file-upload-example.html +0 -189
  204. package/examples/modal/persistent.html +0 -205
  205. package/examples/modal/remote-select-dropdown.html +0 -166
  206. package/examples/modal/select-dropdown-container.html +0 -129
  207. package/examples/select/avatar.html +0 -47
  208. package/examples/select/basic-usage.html +0 -39
  209. package/examples/select/country.html +0 -43
  210. package/examples/select/dark-mode.html +0 -93
  211. package/examples/select/description.html +0 -53
  212. package/examples/select/disable-option.html +0 -37
  213. package/examples/select/disable-select.html +0 -35
  214. package/examples/select/dropdowncontainer.html +0 -111
  215. package/examples/select/dynamic-methods.html +0 -273
  216. package/examples/select/formdata-remote.html +0 -161
  217. package/examples/select/global-config.html +0 -81
  218. package/examples/select/icon-multiple.html +0 -50
  219. package/examples/select/icon.html +0 -48
  220. package/examples/select/max-selection.html +0 -38
  221. package/examples/select/modal-container.html +0 -128
  222. package/examples/select/modal-positioning-test.html +0 -338
  223. package/examples/select/modal.html +0 -80
  224. package/examples/select/multiple.html +0 -40
  225. package/examples/select/native-selected.html +0 -64
  226. package/examples/select/placeholder.html +0 -40
  227. package/examples/select/remote-data-preselected.html +0 -283
  228. package/examples/select/remote-data.html +0 -38
  229. package/examples/select/search.html +0 -57
  230. package/examples/select/sizes.html +0 -94
  231. package/examples/select/tags-enhanced.html +0 -86
  232. package/examples/select/tags-icons.html +0 -57
  233. package/examples/select/template-customization.html +0 -61
  234. package/examples/sticky/README.md +0 -158
  235. package/examples/sticky/debug-sticky.html +0 -144
  236. package/examples/sticky/test-runner.html +0 -175
  237. package/examples/sticky/test-sticky-logic.js +0 -369
  238. package/examples/sticky/test-sticky-positioning.html +0 -386
  239. package/examples/toast/example.html +0 -479
  240. package/lib/cjs/components/datepicker/calendar.js +0 -1061
  241. package/lib/cjs/components/datepicker/calendar.js.map +0 -1
  242. package/lib/cjs/components/datepicker/config.js +0 -332
  243. package/lib/cjs/components/datepicker/config.js.map +0 -1
  244. package/lib/cjs/components/datepicker/dropdown.js +0 -635
  245. package/lib/cjs/components/datepicker/dropdown.js.map +0 -1
  246. package/lib/cjs/components/datepicker/events.js +0 -129
  247. package/lib/cjs/components/datepicker/events.js.map +0 -1
  248. package/lib/cjs/components/datepicker/keyboard.js +0 -536
  249. package/lib/cjs/components/datepicker/keyboard.js.map +0 -1
  250. package/lib/cjs/components/datepicker/locales.js +0 -78
  251. package/lib/cjs/components/datepicker/locales.js.map +0 -1
  252. package/lib/cjs/components/datepicker/templates.js +0 -403
  253. package/lib/cjs/components/datepicker/templates.js.map +0 -1
  254. package/lib/cjs/components/datepicker/types.js +0 -23
  255. package/lib/cjs/components/datepicker/types.js.map +0 -1
  256. package/lib/cjs/components/datepicker/utils.js +0 -524
  257. package/lib/cjs/components/datepicker/utils.js.map +0 -1
  258. package/lib/esm/components/datepicker/calendar.js +0 -1058
  259. package/lib/esm/components/datepicker/calendar.js.map +0 -1
  260. package/lib/esm/components/datepicker/config.js +0 -329
  261. package/lib/esm/components/datepicker/config.js.map +0 -1
  262. package/lib/esm/components/datepicker/dropdown.js +0 -632
  263. package/lib/esm/components/datepicker/dropdown.js.map +0 -1
  264. package/lib/esm/components/datepicker/events.js +0 -126
  265. package/lib/esm/components/datepicker/events.js.map +0 -1
  266. package/lib/esm/components/datepicker/keyboard.js +0 -533
  267. package/lib/esm/components/datepicker/keyboard.js.map +0 -1
  268. package/lib/esm/components/datepicker/locales.js +0 -74
  269. package/lib/esm/components/datepicker/locales.js.map +0 -1
  270. package/lib/esm/components/datepicker/templates.js +0 -390
  271. package/lib/esm/components/datepicker/templates.js.map +0 -1
  272. package/lib/esm/components/datepicker/types.js +0 -20
  273. package/lib/esm/components/datepicker/types.js.map +0 -1
  274. package/lib/esm/components/datepicker/utils.js +0 -508
  275. package/lib/esm/components/datepicker/utils.js.map +0 -1
  276. package/prettier.config.js +0 -9
  277. package/src/components/datepicker/calendar.ts +0 -1397
  278. package/src/components/datepicker/config.ts +0 -368
  279. package/src/components/datepicker/dropdown.ts +0 -757
  280. package/src/components/datepicker/events.ts +0 -149
  281. package/src/components/datepicker/keyboard.ts +0 -646
  282. package/src/components/datepicker/locales.ts +0 -80
  283. package/src/components/datepicker/templates.ts +0 -792
  284. package/src/components/datepicker/types.ts +0 -154
  285. package/src/components/datepicker/utils.ts +0 -631
  286. package/src/components/select/variants.css +0 -4
  287. package/tsconfig.json +0 -17
  288. package/webpack.config.js +0 -118
@@ -0,0 +1,459 @@
1
+ /*
2
+ * unified-state-manager.ts - Centralized state management for KTDatepicker
3
+ * Provides single source of truth for all datepicker state with observer pattern
4
+ * for automatic UI synchronization across all components.
5
+ */
6
+
7
+ import { KTDatepickerState, TimeState, DropdownState } from '../config/types';
8
+
9
+ /**
10
+ * State observer interface for UI components
11
+ */
12
+ export interface StateObserver {
13
+ onStateChange(newState: KTDatepickerState, oldState: KTDatepickerState): void;
14
+ getUpdatePriority(): number; // For update ordering (lower = higher priority)
15
+ }
16
+
17
+ /**
18
+ * State validation result
19
+ */
20
+ export interface ValidationResult {
21
+ isValid: boolean;
22
+ errors: string[];
23
+ }
24
+
25
+ /**
26
+ * State manager configuration
27
+ */
28
+ export interface StateManagerConfig {
29
+ enableValidation: boolean;
30
+ enableDebugging: boolean;
31
+ enableUpdateBatching: boolean;
32
+ batchDelay: number; // milliseconds
33
+ }
34
+
35
+ /**
36
+ * State change event
37
+ */
38
+ export interface StateChangeEvent {
39
+ oldState: KTDatepickerState;
40
+ newState: KTDatepickerState;
41
+ source: string;
42
+ timestamp: number;
43
+ changes: Partial<KTDatepickerState>;
44
+ changedProperties: Set<string>; // Set of property keys that actually changed
45
+ }
46
+
47
+ /**
48
+ * KTDatepickerUnifiedStateManager
49
+ *
50
+ * Centralized state management for datepicker with observer pattern.
51
+ * Ensures all UI components automatically sync to state changes.
52
+ *
53
+ * Immediate vs Batched Updates:
54
+ * - Immediate updates: Use for user interactions requiring synchronous event firing
55
+ * (date selection, dropdown open/close). These bypass batching delays.
56
+ * - Batched updates: Use for programmatic changes that don't require immediate
57
+ * user feedback (navigation, initialization). These are batched for performance.
58
+ */
59
+ export class KTDatepickerUnifiedStateManager {
60
+ private _state: KTDatepickerState;
61
+ private _observers: Set<StateObserver> = new Set();
62
+ private _config: StateManagerConfig;
63
+ private _batchTimeout: number | null = null;
64
+ private _pendingUpdates: Partial<KTDatepickerState> = {};
65
+ private _isUpdating = false;
66
+ private _lastUpdateSource: string = 'unknown';
67
+ private _lastChangedProperties: Set<string> = new Set();
68
+
69
+ constructor(config?: Partial<StateManagerConfig>) {
70
+ this._config = {
71
+ enableValidation: true,
72
+ enableDebugging: false,
73
+ enableUpdateBatching: true,
74
+ batchDelay: 16, // ~60fps
75
+ ...config
76
+ };
77
+
78
+ this._state = this._getInitialState();
79
+ }
80
+
81
+ /**
82
+ * Get initial state
83
+ */
84
+ private _getInitialState(): KTDatepickerState {
85
+ return {
86
+ currentDate: new Date(),
87
+ selectedDate: null,
88
+ selectedRange: null,
89
+ selectedDates: [],
90
+ selectedTime: null,
91
+ timeGranularity: 'minute',
92
+ viewMode: 'days',
93
+ isOpen: false,
94
+ isFocused: false,
95
+ isTransitioning: false,
96
+ isDisabled: false,
97
+ validationErrors: [],
98
+ isValid: true,
99
+ dropdownState: {
100
+ isOpen: false,
101
+ isTransitioning: false,
102
+ isDisabled: false,
103
+ isFocused: false
104
+ }
105
+ };
106
+ }
107
+
108
+ /**
109
+ * Get current state (immutable)
110
+ */
111
+ public getState(): Readonly<KTDatepickerState> {
112
+ return { ...this._state };
113
+ }
114
+
115
+ /**
116
+ * Get the source of the last state update
117
+ * @returns The source identifier of the last state update
118
+ */
119
+ public getLastUpdateSource(): string {
120
+ return this._lastUpdateSource;
121
+ }
122
+
123
+ /**
124
+ * Get the set of properties that changed in the last state update
125
+ * @returns Set of property keys that changed
126
+ */
127
+ public getLastChangedProperties(): ReadonlySet<string> {
128
+ return new Set(this._lastChangedProperties);
129
+ }
130
+
131
+ /**
132
+ * Update state with validation and observer notification
133
+ * @param updates - Partial state updates to apply
134
+ * @param source - Source identifier for debugging
135
+ * @param immediate - If true, bypass batching and apply updates immediately
136
+ */
137
+ public updateState(updates: Partial<KTDatepickerState>, source: string = 'unknown', immediate: boolean = false): boolean {
138
+ if (this._isUpdating) {
139
+ return false;
140
+ }
141
+
142
+ // If immediate is requested, or batching is disabled, apply updates immediately
143
+ if (immediate || !this._config.enableUpdateBatching) {
144
+ return this._applyUpdates(source, updates);
145
+ }
146
+
147
+ // Merge updates with pending updates for batched processing
148
+ this._pendingUpdates = { ...this._pendingUpdates, ...updates };
149
+
150
+ if (this._batchTimeout) {
151
+ clearTimeout(this._batchTimeout);
152
+ }
153
+
154
+ this._batchTimeout = window.setTimeout(() => {
155
+ this._applyUpdates(source);
156
+ }, this._config.batchDelay);
157
+
158
+ return true;
159
+ }
160
+
161
+ /**
162
+ * Apply updates to state
163
+ */
164
+ private _applyUpdates(source: string, updates?: Partial<KTDatepickerState>): boolean {
165
+ const changes = updates || this._pendingUpdates;
166
+ const oldState = { ...this._state };
167
+
168
+ // Create new state with updates
169
+ const newState = { ...this._state, ...changes };
170
+
171
+ // Validate state if enabled
172
+ if (this._config.enableValidation) {
173
+ const validation = this._validateState(newState);
174
+ if (!validation.isValid) {
175
+ return false;
176
+ }
177
+ newState.validationErrors = validation.errors;
178
+ newState.isValid = validation.isValid;
179
+ }
180
+
181
+ // Update state
182
+ this._state = newState;
183
+ this._pendingUpdates = {};
184
+
185
+ // Notify observers
186
+ this._notifyObservers(oldState, newState, source, changes);
187
+
188
+ return true;
189
+ }
190
+
191
+ /**
192
+ * Validate state
193
+ */
194
+ private _validateState(state: KTDatepickerState): ValidationResult {
195
+ const errors: string[] = [];
196
+
197
+ // Validate dates
198
+ if (state.selectedDate && isNaN(state.selectedDate.getTime())) {
199
+ errors.push('Invalid selectedDate');
200
+ }
201
+
202
+ if (state.currentDate && isNaN(state.currentDate.getTime())) {
203
+ errors.push('Invalid currentDate');
204
+ }
205
+
206
+ // Validate range
207
+ if (state.selectedRange) {
208
+ if (state.selectedRange.start && isNaN(state.selectedRange.start.getTime())) {
209
+ errors.push('Invalid range start date');
210
+ }
211
+ if (state.selectedRange.end && isNaN(state.selectedRange.end.getTime())) {
212
+ errors.push('Invalid range end date');
213
+ }
214
+ if (state.selectedRange.start && state.selectedRange.end &&
215
+ state.selectedRange.start > state.selectedRange.end) {
216
+ errors.push('Range start date cannot be after end date');
217
+ }
218
+ }
219
+
220
+ // Validate time
221
+ if (state.selectedTime) {
222
+ if (state.selectedTime.hour < 0 || state.selectedTime.hour > 23) {
223
+ errors.push('Invalid hour value');
224
+ }
225
+ if (state.selectedTime.minute < 0 || state.selectedTime.minute > 59) {
226
+ errors.push('Invalid minute value');
227
+ }
228
+ if (state.selectedTime.second < 0 || state.selectedTime.second > 59) {
229
+ errors.push('Invalid second value');
230
+ }
231
+ }
232
+
233
+ return {
234
+ isValid: errors.length === 0,
235
+ errors
236
+ };
237
+ }
238
+
239
+ /**
240
+ * Subscribe to state changes
241
+ */
242
+ public subscribe(observer: StateObserver): () => void {
243
+ this._observers.add(observer);
244
+
245
+ // Return unsubscribe function
246
+ return () => {
247
+ this._observers.delete(observer);
248
+ };
249
+ }
250
+
251
+ /**
252
+ * Compute which properties actually changed between old and new state
253
+ */
254
+ private _computeChangedProperties(oldState: KTDatepickerState, newState: KTDatepickerState, changes: Partial<KTDatepickerState>): Set<string> {
255
+ const changedProperties = new Set<string>();
256
+
257
+ // Check each property in the changes object
258
+ for (const key in changes) {
259
+ if (changes.hasOwnProperty(key)) {
260
+ const oldValue = (oldState as any)[key];
261
+ const newValue = (newState as any)[key];
262
+
263
+ // Handle Date objects - compare by time value
264
+ if (oldValue instanceof Date && newValue instanceof Date) {
265
+ if (oldValue.getTime() !== newValue.getTime()) {
266
+ changedProperties.add(key);
267
+ }
268
+ }
269
+ // Handle arrays - compare by JSON string (for selectedDates)
270
+ else if (Array.isArray(oldValue) && Array.isArray(newValue)) {
271
+ if (JSON.stringify(oldValue.map(d => d instanceof Date ? d.getTime() : d)) !==
272
+ JSON.stringify(newValue.map(d => d instanceof Date ? d.getTime() : d))) {
273
+ changedProperties.add(key);
274
+ }
275
+ }
276
+ // Handle objects (like selectedRange, dropdownState)
277
+ else if (typeof oldValue === 'object' && oldValue !== null &&
278
+ typeof newValue === 'object' && newValue !== null) {
279
+ if (JSON.stringify(oldValue) !== JSON.stringify(newValue)) {
280
+ changedProperties.add(key);
281
+ // Also add nested properties if it's a complex object
282
+ if (key === 'selectedRange') {
283
+ if (oldValue.start?.getTime() !== newValue.start?.getTime()) changedProperties.add('selectedRange.start');
284
+ if (oldValue.end?.getTime() !== newValue.end?.getTime()) changedProperties.add('selectedRange.end');
285
+ }
286
+ }
287
+ }
288
+ // Primitive values
289
+ else if (oldValue !== newValue) {
290
+ changedProperties.add(key);
291
+ }
292
+ }
293
+ }
294
+
295
+ return changedProperties;
296
+ }
297
+
298
+ /**
299
+ * Notify all observers of state change
300
+ */
301
+ private _notifyObservers(oldState: KTDatepickerState, newState: KTDatepickerState, source: string, changes: Partial<KTDatepickerState>): void {
302
+ if (this._observers.size === 0) return;
303
+
304
+ // Store the last update source for external queries
305
+ this._lastUpdateSource = source;
306
+
307
+ // Compute which properties actually changed
308
+ const changedProperties = this._computeChangedProperties(oldState, newState, changes);
309
+ this._lastChangedProperties = changedProperties; // Store for observers to access
310
+
311
+ // Sort observers by priority
312
+ const sortedObservers = Array.from(this._observers).sort((a, b) =>
313
+ a.getUpdatePriority() - b.getUpdatePriority()
314
+ );
315
+
316
+ const event: StateChangeEvent = {
317
+ oldState,
318
+ newState,
319
+ source,
320
+ timestamp: Date.now(),
321
+ changes,
322
+ changedProperties
323
+ };
324
+
325
+ // Notify observers in priority order
326
+ for (const observer of sortedObservers) {
327
+ try {
328
+ observer.onStateChange(newState, oldState);
329
+ } catch (error) {
330
+ // Observer error - continue with other observers
331
+ }
332
+ }
333
+ }
334
+
335
+ /**
336
+ * Convenience methods for common state updates
337
+ */
338
+
339
+ /**
340
+ * Set selected date - uses immediate update for synchronous event firing
341
+ */
342
+ public setSelectedDate(date: Date | null, source: string = 'manual'): boolean {
343
+ return this.updateState({ selectedDate: date }, source, true); // immediate
344
+ }
345
+
346
+ public setSelectedTime(time: TimeState | null, source: string = 'manual'): boolean {
347
+ return this.updateState({ selectedTime: time }, source);
348
+ }
349
+
350
+ public setCurrentDate(date: Date, source: string = 'manual'): boolean {
351
+ return this.updateState({ currentDate: date }, source);
352
+ }
353
+
354
+ /**
355
+ * Set selected range - uses immediate update for synchronous event firing
356
+ */
357
+ public setSelectedRange(range: { start: Date | null; end: Date | null } | null, source: string = 'manual'): boolean {
358
+ return this.updateState({ selectedRange: range }, source, true); // immediate
359
+ }
360
+
361
+ /**
362
+ * Set selected dates (multi-date) - uses immediate update for synchronous event firing
363
+ */
364
+ public setSelectedDates(dates: Date[], source: string = 'manual'): boolean {
365
+ return this.updateState({ selectedDates: dates }, source, true); // immediate
366
+ }
367
+
368
+ public setViewMode(mode: 'days' | 'months' | 'years', source: string = 'manual'): boolean {
369
+ return this.updateState({ viewMode: mode }, source);
370
+ }
371
+
372
+ /**
373
+ * Set overall open state - uses immediate update for synchronous event firing
374
+ */
375
+ public setOpen(isOpen: boolean, source: string = 'manual'): boolean {
376
+ return this.updateState({ isOpen }, source, true); // immediate
377
+ }
378
+
379
+ public setFocused(isFocused: boolean, source: string = 'manual'): boolean {
380
+ return this.updateState({ isFocused }, source);
381
+ }
382
+
383
+ public setDisabled(isDisabled: boolean, source: string = 'manual'): boolean {
384
+ return this.updateState({ isDisabled }, source);
385
+ }
386
+
387
+ public setTransitioning(isTransitioning: boolean, source: string = 'manual'): boolean {
388
+ return this.updateState({ isTransitioning }, source);
389
+ }
390
+
391
+ // Dropdown state methods (consolidated from legacy state manager)
392
+ /**
393
+ * Set dropdown open state - uses immediate update for synchronous event firing
394
+ */
395
+ public setDropdownOpen(isOpen: boolean, source: string = 'manual'): boolean {
396
+ return this.updateState({
397
+ dropdownState: { ...this._state.dropdownState, isOpen },
398
+ isOpen // Also update the legacy isOpen field for compatibility
399
+ }, source, true); // immediate
400
+ }
401
+
402
+ public setDropdownTransitioning(isTransitioning: boolean, source: string = 'manual'): boolean {
403
+ return this.updateState({
404
+ dropdownState: { ...this._state.dropdownState, isTransitioning }
405
+ }, source);
406
+ }
407
+
408
+ public setDropdownDisabled(isDisabled: boolean, source: string = 'manual'): boolean {
409
+ return this.updateState({
410
+ dropdownState: { ...this._state.dropdownState, isDisabled }
411
+ }, source);
412
+ }
413
+
414
+ public setDropdownFocused(isFocused: boolean, source: string = 'manual'): boolean {
415
+ return this.updateState({
416
+ dropdownState: { ...this._state.dropdownState, isFocused }
417
+ }, source);
418
+ }
419
+
420
+ public getDropdownState(): Readonly<DropdownState> {
421
+ return { ...this._state.dropdownState };
422
+ }
423
+
424
+ public isDropdownOpen(): boolean {
425
+ return this._state.dropdownState.isOpen;
426
+ }
427
+
428
+ public isDropdownTransitioning(): boolean {
429
+ return this._state.dropdownState.isTransitioning;
430
+ }
431
+
432
+ public isDropdownDisabled(): boolean {
433
+ return this._state.dropdownState.isDisabled;
434
+ }
435
+
436
+ public isDropdownFocused(): boolean {
437
+ return this._state.dropdownState.isFocused;
438
+ }
439
+
440
+ /**
441
+ * Reset state to initial values
442
+ */
443
+ public reset(source: string = 'manual'): void {
444
+ const oldState = { ...this._state };
445
+ this._state = this._getInitialState();
446
+ this._notifyObservers(oldState, this._state, source, this._state);
447
+ }
448
+
449
+ /**
450
+ * Dispose of state manager
451
+ */
452
+ public dispose(): void {
453
+ this._observers.clear();
454
+ if (this._batchTimeout) {
455
+ clearTimeout(this._batchTimeout);
456
+ this._batchTimeout = null;
457
+ }
458
+ }
459
+ }