@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,306 @@
1
+ /*
2
+ * templates.ts - Unified template system for KTDatepicker
3
+ * Consolidates all template functionality including default templates, merging logic,
4
+ * rendering utilities, and template management into a single, unified system.
5
+ */
6
+
7
+ import { KTDatepickerConfig, KTDatepickerTemplateStrings } from '../../config/types';
8
+
9
+ // Default template strings for all UI fragments
10
+ export const defaultTemplates: KTDatepickerTemplateStrings = {
11
+ // Add role="dialog" and aria-modal to the dropdown container
12
+ container: `<div data-kt-datepicker-container {{class}}></div>`,
13
+ header: `<div data-kt-datepicker-header {{class}}">{{prevButton}}<span data-kt-datepicker-month-year>{{month}} {{year}}</span>{{nextButton}}</div>`,
14
+ footer: `<div data-kt-datepicker-footer {{class}}">{{todayButton}} {{clearButton}} {{applyButton}}</div>`,
15
+ // Add role="grid" and aria-label to the calendar grid
16
+ calendarGrid: `<table data-kt-datepicker-calendar-grid role="grid" aria-label="Calendar" aria-readonly="true" {{class}}>{{calendar}}</table>`,
17
+ // Add role="gridcell" to dayCell, aria-selected, and tabindex for keyboard nav
18
+ dayCell: `<td data-kt-datepicker-day role="gridcell" {{attributes}} {{class}}><button type="button" data-day="{{day}}" aria-label="Select {{day}}" tabindex="-1">{{day}}</button></td>`,
19
+ // Month/year selectors (add aria-live for dynamic updates)
20
+ monthYearSelect: `<div data-kt-datepicker-monthyear-select aria-live="polite" {{class}}>{{monthSelect}} {{yearSelect}}</div>`,
21
+ monthSelection: `<div data-kt-datepicker-month-selection {{class}}>{{months}}</div>`,
22
+ yearSelection: `<div data-kt-datepicker-year-selection {{class}}>{{years}}</div>`,
23
+ inputWrapper: `<div data-kt-datepicker-input-wrapper class="flex items-center {{class}}">{{input}} {{icon}}</div>`,
24
+ segmentedDateInput: `<div data-kt-datepicker-segmented-input class="flex items-center {{class}}">{{segments}}</div>`,
25
+ segmentedDateRangeInput: `<div data-kt-datepicker-segmented-range-input class="flex items-center {{class}}">{{start}}{{separator}}{{end}}</div>`,
26
+ /**
27
+ * Template for a single date segment (e.g., day, month, year)
28
+ * Placeholders: segmentType, segmentValue, ariaLabel, ariaValueNow, ariaValueText, ariaValueMin, ariaValueMax, tabindex, contenteditable
29
+ */
30
+ dateSegment: `<span data-segment="{{segmentType}}" role="spinbutton" aria-label="{{ariaLabel}}" aria-valuenow="{{ariaValueNow}}" aria-valuetext="{{ariaValueText}}" aria-valuemin="{{ariaValueMin}}" aria-valuemax="{{ariaValueMax}}" tabindex="{{tabindex}}" contenteditable="{{contenteditable}}" {{class}}>{{segmentValue}}</span>`,
31
+ /**
32
+ * Template for a segment separator (e.g., / or space)
33
+ * Placeholders: separator
34
+ */
35
+ segmentSeparator: `<span data-segment-separator {{class}}>{{separator}}</span>`,
36
+ placeholder: `<span data-kt-datepicker-placeholder {{class}}>{{placeholder}}</span>`,
37
+ displayWrapper: `<div data-kt-datepicker-display-wrapper {{class}}>{{value}}</div>`,
38
+ displayElement: `<span data-kt-datepicker-display-element {{class}}>{{value}}</span>`,
39
+ timePanel: `<div data-kt-datepicker-time-panel {{class}}>{{hours}}:{{minutes}}:{{seconds}} {{amPm}}</div>`,
40
+ multiDateTag: `<span data-kt-datepicker-multidate-tag {{class}}>{{date}} <button>{{removeButton}}</button></span>`,
41
+ emptyState: `<div data-kt-datepicker-empty {{class}}>{{message}}</div>`,
42
+ // Add aria-haspopup and aria-expanded to calendar button
43
+ calendarButton: `<button type="button" data-kt-datepicker-calendar-btn aria-label="Open calendar" aria-haspopup="dialog" aria-expanded="false" {{class}}>
44
+ <svg width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" aria-hidden="true">
45
+ <rect x="3" y="4" width="18" height="18" rx="2"/>
46
+ <path d="M16 2v4M8 2v4M3 10h18"/>
47
+ </svg>
48
+ </button>`,
49
+ // Add role="dialog" and aria-modal to dropdown
50
+ dropdown: `<div data-kt-datepicker-dropdown role="dialog" aria-modal="true" aria-label="Date picker" class="hidden {{class}}"></div>`,
51
+ prevButton: `<button type="button" data-kt-datepicker-prev aria-label="Previous month" {{class}}>
52
+ <svg width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24" aria-hidden="true">
53
+ <path d="m15 18-6-6 6-6"/>
54
+ </svg>
55
+ </button>`,
56
+ nextButton: `<button type="button" data-kt-datepicker-next aria-label="Next month" {{class}}>
57
+ <svg width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24" aria-hidden="true">
58
+ <path d="m9 18 6-6-6-6"/>
59
+ </svg>
60
+ </button>`,
61
+ // Add role="row" to calendar table rows
62
+ calendarTable: `<table data-kt-datepicker-calendar-table role="grid" aria-label="Calendar" aria-readonly="true" {{class}}><thead><tr role="row">
63
+ <th class="py-1 px-1 text-xs font-semibold text-gray-500 dark:text-gray-400">{{sunday}}</th>
64
+ <th class="py-1 px-1 text-xs font-semibold text-gray-500 dark:text-gray-400">{{monday}}</th>
65
+ <th class="py-1 px-1 text-xs font-semibold text-gray-500 dark:text-gray-400">{{tuesday}}</th>
66
+ <th class="py-1 px-1 text-xs font-semibold text-gray-500 dark:text-gray-400">{{wednesday}}</th>
67
+ <th class="py-1 px-1 text-xs font-semibold text-gray-500 dark:text-gray-400">{{thursday}}</th>
68
+ <th class="py-1 px-1 text-xs font-semibold text-gray-500 dark:text-gray-400">{{friday}}</th>
69
+ <th class="py-1 px-1 text-xs font-semibold text-gray-500 dark:text-gray-400">{{saturday}}</th>
70
+ </tr></thead>{{body}}</table>`,
71
+ calendarBody: `<tbody data-kt-datepicker-calendar-body {{class}}>{{rows}}</tbody>`,
72
+ calendarRow: `<tr data-kt-datepicker-calendar-row role="row" {{class}}>{{cells}}</tr>`,
73
+ todayButton: `<button type="button" data-kt-datepicker-today {{class}}>Today</button>`,
74
+ clearButton: `<button type="button" data-kt-datepicker-clear {{class}}>Clear</button>`,
75
+ applyButton: `<button type="button" data-kt-datepicker-apply {{class}}>Apply</button>`,
76
+ /**
77
+ * Container for multiple calendar months (horizontal multi-month view)
78
+ */
79
+ multiMonthContainer: `<div data-kt-datepicker-multimonth-container class="flex flex-col md:flex-row gap-4 {{class}}">{{calendars}}</div>`,
80
+
81
+ // Time picker templates
82
+ timePickerWrapper: `<div data-kt-datepicker-time-picker {{class}}>{{timeDisplay}} {{timeControls}}</div>`,
83
+ timeDisplay: `<div data-kt-datepicker-time-display {{class}}>{{timeValue}}</div>`,
84
+ timeControls: `<div data-kt-datepicker-time-controls {{class}}>{{timeUnits}} {{ampmControl}}</div>`,
85
+ timeUnit: `<div data-kt-datepicker-time-unit data-kt-datepicker-time-unit="{{unitType}}" {{class}}>{{upButton}} {{valueDisplay}} {{downButton}}</div>`,
86
+ timeUpButton: `<button type="button" data-kt-datepicker-time-up aria-label="Increment {{unitType}}" {{class}} {{disabled}}>▲</button>`,
87
+ timeDownButton: `<button type="button" data-kt-datepicker-time-down aria-label="Decrement {{unitType}}" {{class}} {{disabled}}>▼</button>`,
88
+ timeValue: `<span data-kt-datepicker-time-value {{class}}>{{value}}</span>`,
89
+ timeSeparator: `<span data-kt-datepicker-time-separator {{class}}>{{separator}}</span>`,
90
+ ampmControl: `<div data-kt-datepicker-ampm-control {{class}}>{{ampmButton}}</div>`,
91
+ ampmButton: `<button type="button" data-kt-datepicker-ampm-button aria-label="Toggle AM/PM" {{class}} {{disabled}}>{{ampmValue}}</button>`,
92
+ /**
93
+ * Panel wrapper for header + calendar
94
+ */
95
+ panel: `<div data-kt-datepicker-panel {{class}}>{{header}}{{calendar}}</div>`,
96
+ /**
97
+ * Live region for accessibility announcements
98
+ */
99
+ liveRegion: `<div data-kt-datepicker-live aria-live="polite" {{class}}></div>`,
100
+ /**
101
+ * Container for range input start date segment
102
+ */
103
+ rangeStartContainer: `<div data-kt-datepicker-segmented-start {{class}}></div>`,
104
+ /**
105
+ * Container for range input end date segment
106
+ */
107
+ rangeEndContainer: `<div data-kt-datepicker-segmented-end {{class}}></div>`,
108
+ };
109
+
110
+ /**
111
+ * Template rendering options
112
+ */
113
+ export interface TemplateRenderOptions {
114
+ templateKey: keyof KTDatepickerTemplateStrings;
115
+ data: Record<string, any>;
116
+ configClasses?: Record<string, string>;
117
+ fallbackTemplate?: string | ((data: any) => string);
118
+ }
119
+
120
+ /**
121
+ * Merges default templates with user overrides.
122
+ * User overrides take precedence.
123
+ */
124
+ export function mergeTemplates(
125
+ defaults: KTDatepickerTemplateStrings,
126
+ overrides?: KTDatepickerTemplateStrings
127
+ ): KTDatepickerTemplateStrings {
128
+ return { ...defaults, ...(overrides || {}) };
129
+ }
130
+
131
+ /**
132
+ * Merges default, config, and user templates (string or function)
133
+ * Precedence: default < config < user
134
+ */
135
+ export function getMergedTemplates(
136
+ configTemplates?: Record<string, string | ((data: any) => string)>,
137
+ userTemplates?: Record<string, string | ((data: any) => string)>
138
+ ): Record<string, string | ((data: any) => string)> {
139
+ return {
140
+ ...(defaultTemplates as Record<string, string | ((data: any) => string)>),
141
+ ...(configTemplates || {}),
142
+ ...(userTemplates || {}),
143
+ };
144
+ }
145
+
146
+ /**
147
+ * Renders a template string with data using {{key}} placeholders.
148
+ * Enhanced to handle class placeholders specifically.
149
+ */
150
+ export function renderTemplateString(
151
+ template: string,
152
+ data: Record<string, any>
153
+ ): string {
154
+ return template.replace(/{{(\w+)}}/g, (_, key) => {
155
+ const value = data[key];
156
+ if (value !== undefined) {
157
+ return String(value);
158
+ }
159
+ return '';
160
+ });
161
+ }
162
+
163
+ /**
164
+ * Merges class data with template data for rendering.
165
+ * Extracts class for specific template key from config classes object.
166
+ */
167
+ export function mergeClassData(
168
+ templateKey: string,
169
+ templateData: Record<string, any>,
170
+ configClasses?: Record<string, string>
171
+ ): Record<string, any> {
172
+ const classValue = configClasses?.[templateKey] || '';
173
+ return {
174
+ ...templateData,
175
+ class: classValue
176
+ };
177
+ }
178
+
179
+ /**
180
+ * Checks if a template is a function.
181
+ */
182
+ export function isTemplateFunction(tpl: unknown): tpl is (data: any) => string {
183
+ return typeof tpl === 'function';
184
+ }
185
+
186
+ /**
187
+ * Renders a template string with data and returns a DocumentFragment.
188
+ * Usage: const frag = renderTemplateToDOM(template, data)
189
+ */
190
+ export function renderTemplateToDOM(template: string, data: Record<string, any> = {}): DocumentFragment {
191
+ const html = renderTemplateString(template, data);
192
+ const frag = document.createDocumentFragment();
193
+ const temp = document.createElement('div');
194
+ temp.innerHTML = html;
195
+ while (temp.firstChild) {
196
+ frag.appendChild(temp.firstChild);
197
+ }
198
+ return frag;
199
+ }
200
+
201
+ /**
202
+ * Unified template renderer for all datepicker UI components
203
+ * Ensures consistent template usage and eliminates scattered rendering logic
204
+ */
205
+ export class TemplateRenderer {
206
+ private _templates: KTDatepickerTemplateStrings;
207
+
208
+ constructor(templates: KTDatepickerTemplateStrings) {
209
+ this._templates = templates;
210
+ }
211
+
212
+ /**
213
+ * Render a template with data and return HTML string
214
+ */
215
+ renderTemplateString(options: TemplateRenderOptions): string {
216
+ const { templateKey, data, configClasses, fallbackTemplate } = options;
217
+
218
+ // Get template from template set
219
+ let template = this._templates[templateKey];
220
+
221
+ // Use fallback if template not found
222
+ if (!template && fallbackTemplate) {
223
+ template = fallbackTemplate;
224
+ }
225
+
226
+ // Validate template exists
227
+ if (!template) {
228
+ throw new Error(`Template not found for key: ${templateKey}`);
229
+ }
230
+
231
+ // Merge class data
232
+ const mergedData = mergeClassData(templateKey, data, configClasses);
233
+
234
+ // Render template
235
+ if (isTemplateFunction(template)) {
236
+ return template(mergedData);
237
+ } else {
238
+ return renderTemplateString(template as string, mergedData);
239
+ }
240
+ }
241
+
242
+ /**
243
+ * Render a template with data and return HTMLElement
244
+ */
245
+ renderTemplateToElement(options: TemplateRenderOptions): HTMLElement {
246
+ const html = this.renderTemplateString(options);
247
+ const fragment = renderTemplateToDOM(html);
248
+ const element = fragment.firstElementChild as HTMLElement;
249
+
250
+ if (!element) {
251
+ throw new Error(`Failed to render template to element for key: ${options.templateKey}`);
252
+ }
253
+
254
+ return element;
255
+ }
256
+
257
+ /**
258
+ * Render a template with data and return DocumentFragment
259
+ */
260
+ renderTemplateToFragment(options: TemplateRenderOptions): DocumentFragment {
261
+ const html = this.renderTemplateString(options);
262
+ return renderTemplateToDOM(html);
263
+ }
264
+
265
+ /**
266
+ * Check if a template exists
267
+ */
268
+ hasTemplate(templateKey: keyof KTDatepickerTemplateStrings): boolean {
269
+ return !!this._templates[templateKey];
270
+ }
271
+
272
+ /**
273
+ * Get template by key
274
+ */
275
+ getTemplate(templateKey: keyof KTDatepickerTemplateStrings): string | ((data: any) => string) | undefined {
276
+ return this._templates[templateKey];
277
+ }
278
+
279
+ /**
280
+ * Update templates
281
+ */
282
+ updateTemplates(templates: KTDatepickerTemplateStrings): void {
283
+ this._templates = templates;
284
+ }
285
+
286
+ /**
287
+ * Get all templates
288
+ */
289
+ getTemplates(): KTDatepickerTemplateStrings {
290
+ return { ...this._templates };
291
+ }
292
+ }
293
+
294
+ /**
295
+ * Factory function to create a template renderer
296
+ */
297
+ export function createTemplateRenderer(templates: KTDatepickerTemplateStrings): TemplateRenderer {
298
+ return new TemplateRenderer(templates);
299
+ }
300
+
301
+ /**
302
+ * Returns the merged template set for a given config.
303
+ */
304
+ export function getTemplateStrings(config?: KTDatepickerConfig): KTDatepickerTemplateStrings {
305
+ return mergeTemplates(defaultTemplates, config?.templates);
306
+ }
@@ -0,0 +1,160 @@
1
+ /*
2
+ * date-formatters.test.ts - Unit tests for date formatting utilities
3
+ */
4
+
5
+ import { describe, it, expect } from 'vitest';
6
+ import { formatDate, isSameDay, normalizeDateToMidnight } from '../date-formatters';
7
+
8
+ describe('formatDate', () => {
9
+ it('should format date with yyyy-MM-dd format', () => {
10
+ const date = new Date(2024, 0, 15); // January 15, 2024
11
+ expect(formatDate(date, 'yyyy-MM-dd')).toBe('2024-01-15');
12
+ });
13
+
14
+ it('should format date with dd/MM/yyyy format', () => {
15
+ const date = new Date(2024, 0, 15);
16
+ expect(formatDate(date, 'dd/MM/yyyy')).toBe('15/01/2024');
17
+ });
18
+
19
+ it('should format date with time components', () => {
20
+ const date = new Date(2024, 0, 15, 14, 30, 45);
21
+ expect(formatDate(date, 'HH:mm:ss')).toBe('14:30:45');
22
+ });
23
+
24
+ it('should format date with AM/PM indicator', () => {
25
+ const date = new Date(2024, 0, 15, 14, 30); // 2:30 PM
26
+ expect(formatDate(date, 'HH:mm a')).toBe('14:30 PM');
27
+ const morningDate = new Date(2024, 0, 15, 9, 30); // 9:30 AM
28
+ expect(formatDate(morningDate, 'HH:mm a')).toBe('09:30 AM');
29
+ const midnightDate = new Date(2024, 0, 15, 0, 0); // Midnight
30
+ expect(formatDate(midnightDate, 'HH:mm a')).toBe('00:00 AM');
31
+ });
32
+
33
+ it('should handle single digit months and days', () => {
34
+ const date = new Date(2024, 0, 5); // January 5, 2024
35
+ expect(formatDate(date, 'M/d/yyyy')).toBe('1/5/2024');
36
+ expect(formatDate(date, 'MM/dd/yyyy')).toBe('01/05/2024');
37
+ });
38
+
39
+ it('should handle 2-digit year format', () => {
40
+ const date = new Date(2024, 0, 15);
41
+ expect(formatDate(date, 'yy-MM-dd')).toBe('24-01-15');
42
+ });
43
+
44
+ it('should return empty string for invalid date', () => {
45
+ const invalidDate = new Date('invalid');
46
+ expect(formatDate(invalidDate, 'yyyy-MM-dd')).toBe('');
47
+ });
48
+
49
+ it('should return empty string for non-Date object', () => {
50
+ expect(formatDate(null as any, 'yyyy-MM-dd')).toBe('');
51
+ expect(formatDate(undefined as any, 'yyyy-MM-dd')).toBe('');
52
+ });
53
+
54
+ it('should handle complex format strings', () => {
55
+ const date = new Date(2024, 0, 15, 14, 30, 45);
56
+ expect(formatDate(date, 'yyyy-MM-dd HH:mm:ss')).toBe('2024-01-15 14:30:45');
57
+ });
58
+ });
59
+
60
+ describe('isSameDay', () => {
61
+ it('should return true for same day with different times', () => {
62
+ const date1 = new Date(2024, 0, 15, 10, 30);
63
+ const date2 = new Date(2024, 0, 15, 14, 45);
64
+ expect(isSameDay(date1, date2)).toBe(true);
65
+ });
66
+
67
+ it('should return false for different days', () => {
68
+ const date1 = new Date(2024, 0, 15);
69
+ const date2 = new Date(2024, 0, 16);
70
+ expect(isSameDay(date1, date2)).toBe(false);
71
+ });
72
+
73
+ it('should return false for different months', () => {
74
+ const date1 = new Date(2024, 0, 15);
75
+ const date2 = new Date(2024, 1, 15);
76
+ expect(isSameDay(date1, date2)).toBe(false);
77
+ });
78
+
79
+ it('should return false for different years', () => {
80
+ const date1 = new Date(2024, 0, 15);
81
+ const date2 = new Date(2025, 0, 15);
82
+ expect(isSameDay(date1, date2)).toBe(false);
83
+ });
84
+
85
+ it('should return true for same date at midnight', () => {
86
+ const date1 = new Date(2024, 0, 15, 0, 0, 0);
87
+ const date2 = new Date(2024, 0, 15, 0, 0, 0);
88
+ expect(isSameDay(date1, date2)).toBe(true);
89
+ });
90
+
91
+ it('should handle edge case: last day of month', () => {
92
+ const date1 = new Date(2024, 0, 31, 10, 0);
93
+ const date2 = new Date(2024, 0, 31, 20, 0);
94
+ expect(isSameDay(date1, date2)).toBe(true);
95
+ });
96
+
97
+ it('should handle edge case: first day of month', () => {
98
+ const date1 = new Date(2024, 0, 1, 0, 0);
99
+ const date2 = new Date(2024, 0, 1, 23, 59);
100
+ expect(isSameDay(date1, date2)).toBe(true);
101
+ });
102
+ });
103
+
104
+ describe('normalizeDateToMidnight', () => {
105
+ it('should set time to 00:00:00', () => {
106
+ const date = new Date(2024, 0, 15, 14, 30, 45);
107
+ const normalized = normalizeDateToMidnight(date);
108
+ expect(normalized.getHours()).toBe(0);
109
+ expect(normalized.getMinutes()).toBe(0);
110
+ expect(normalized.getSeconds()).toBe(0);
111
+ expect(normalized.getMilliseconds()).toBe(0);
112
+ });
113
+
114
+ it('should preserve date components', () => {
115
+ const date = new Date(2024, 0, 15, 14, 30, 45);
116
+ const normalized = normalizeDateToMidnight(date);
117
+ expect(normalized.getFullYear()).toBe(2024);
118
+ expect(normalized.getMonth()).toBe(0);
119
+ expect(normalized.getDate()).toBe(15);
120
+ });
121
+
122
+ it('should return a new Date object', () => {
123
+ const date = new Date(2024, 0, 15, 14, 30, 45);
124
+ const normalized = normalizeDateToMidnight(date);
125
+ expect(normalized).not.toBe(date);
126
+ expect(date.getHours()).toBe(14); // Original date unchanged
127
+ });
128
+
129
+ it('should handle date already at midnight', () => {
130
+ const date = new Date(2024, 0, 15, 0, 0, 0);
131
+ const normalized = normalizeDateToMidnight(date);
132
+ expect(normalized.getHours()).toBe(0);
133
+ expect(normalized.getMinutes()).toBe(0);
134
+ expect(normalized.getSeconds()).toBe(0);
135
+ });
136
+
137
+ it('should handle date at end of day', () => {
138
+ const date = new Date(2024, 0, 15, 23, 59, 59);
139
+ const normalized = normalizeDateToMidnight(date);
140
+ expect(normalized.getHours()).toBe(0);
141
+ expect(normalized.getDate()).toBe(15); // Same day
142
+ });
143
+
144
+ it('should work with dates at different times', () => {
145
+ const times = [
146
+ new Date(2024, 0, 15, 0, 0, 0),
147
+ new Date(2024, 0, 15, 12, 0, 0),
148
+ new Date(2024, 0, 15, 23, 59, 59)
149
+ ];
150
+
151
+ times.forEach(date => {
152
+ const normalized = normalizeDateToMidnight(date);
153
+ expect(normalized.getHours()).toBe(0);
154
+ expect(normalized.getMinutes()).toBe(0);
155
+ expect(normalized.getSeconds()).toBe(0);
156
+ expect(normalized.getDate()).toBe(15);
157
+ });
158
+ });
159
+ });
160
+
@@ -0,0 +1,86 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { getDateKey, isSameDayByKey } from '../date-utils';
3
+
4
+ describe('Date Utils - Date Key Functions', () => {
5
+ describe('getDateKey', () => {
6
+ it('should generate correct date key for a given date', () => {
7
+ const date = new Date(2024, 0, 15); // Jan 15, 2024
8
+ const key = getDateKey(date);
9
+ expect(key).toBe(20240115); // year * 10000 + month * 100 + day
10
+ });
11
+
12
+ it('should handle single-digit months and days', () => {
13
+ const date = new Date(2024, 0, 5); // Jan 5, 2024
14
+ const key = getDateKey(date);
15
+ expect(key).toBe(20240105);
16
+ });
17
+
18
+ it('should handle year boundaries correctly', () => {
19
+ const date1 = new Date(2023, 11, 31); // Dec 31, 2023
20
+ const date2 = new Date(2024, 0, 1); // Jan 1, 2024
21
+ expect(getDateKey(date1)).toBe(20231231);
22
+ expect(getDateKey(date2)).toBe(20240101);
23
+ });
24
+
25
+ it('should handle leap years correctly', () => {
26
+ const date = new Date(2024, 1, 29); // Feb 29, 2024 (leap year)
27
+ const key = getDateKey(date);
28
+ expect(key).toBe(20240229);
29
+ });
30
+
31
+ it('should ignore time components', () => {
32
+ const date1 = new Date(2024, 0, 15, 0, 0, 0); // Jan 15, 2024 00:00:00
33
+ const date2 = new Date(2024, 0, 15, 23, 59, 59); // Jan 15, 2024 23:59:59
34
+ expect(getDateKey(date1)).toBe(getDateKey(date2));
35
+ });
36
+
37
+ it('should generate unique keys for different dates', () => {
38
+ const date1 = new Date(2024, 0, 15);
39
+ const date2 = new Date(2024, 0, 16);
40
+ const date3 = new Date(2024, 1, 15);
41
+ const date4 = new Date(2025, 0, 15);
42
+
43
+ const key1 = getDateKey(date1);
44
+ const key2 = getDateKey(date2);
45
+ const key3 = getDateKey(date3);
46
+ const key4 = getDateKey(date4);
47
+
48
+ expect(key1).not.toBe(key2);
49
+ expect(key1).not.toBe(key3);
50
+ expect(key1).not.toBe(key4);
51
+ });
52
+ });
53
+
54
+ describe('isSameDayByKey', () => {
55
+ it('should return true for same calendar day', () => {
56
+ const date1 = new Date(2024, 0, 15, 10, 30, 0);
57
+ const date2 = new Date(2024, 0, 15, 20, 45, 0);
58
+ expect(isSameDayByKey(date1, date2)).toBe(true);
59
+ });
60
+
61
+ it('should return false for different days', () => {
62
+ const date1 = new Date(2024, 0, 15);
63
+ const date2 = new Date(2024, 0, 16);
64
+ expect(isSameDayByKey(date1, date2)).toBe(false);
65
+ });
66
+
67
+ it('should return false for different months', () => {
68
+ const date1 = new Date(2024, 0, 15);
69
+ const date2 = new Date(2024, 1, 15);
70
+ expect(isSameDayByKey(date1, date2)).toBe(false);
71
+ });
72
+
73
+ it('should return false for different years', () => {
74
+ const date1 = new Date(2024, 0, 15);
75
+ const date2 = new Date(2025, 0, 15);
76
+ expect(isSameDayByKey(date1, date2)).toBe(false);
77
+ });
78
+
79
+ it('should handle year boundaries correctly', () => {
80
+ const date1 = new Date(2023, 11, 31);
81
+ const date2 = new Date(2024, 0, 1);
82
+ expect(isSameDayByKey(date1, date2)).toBe(false);
83
+ });
84
+ });
85
+ });
86
+