@keenthemes/ktui 1.1.0 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. package/README.md +0 -27
  2. package/dist/ktui.js +5269 -12550
  3. package/dist/ktui.min.js +1 -1
  4. package/dist/ktui.min.js.map +1 -1
  5. package/dist/styles.css +1133 -2706
  6. package/lib/cjs/components/datatable/__tests__/pagination-reset.test.js +596 -0
  7. package/lib/cjs/components/datatable/__tests__/pagination-reset.test.js.map +1 -0
  8. package/lib/cjs/components/datatable/__tests__/race-conditions.test.js +548 -0
  9. package/lib/cjs/components/datatable/__tests__/race-conditions.test.js.map +1 -0
  10. package/lib/cjs/components/datatable/__tests__/setup.js +63 -0
  11. package/lib/cjs/components/datatable/__tests__/setup.js.map +1 -0
  12. package/lib/cjs/components/datatable/datatable.js +92 -30
  13. package/lib/cjs/components/datatable/datatable.js.map +1 -1
  14. package/lib/cjs/components/select/combobox.js +0 -2
  15. package/lib/cjs/components/select/combobox.js.map +1 -1
  16. package/lib/cjs/components/select/config.js +4 -1
  17. package/lib/cjs/components/select/config.js.map +1 -1
  18. package/lib/cjs/components/select/dropdown.js +0 -16
  19. package/lib/cjs/components/select/dropdown.js.map +1 -1
  20. package/lib/cjs/components/select/remote.js +0 -40
  21. package/lib/cjs/components/select/remote.js.map +1 -1
  22. package/lib/cjs/components/select/search.js +80 -19
  23. package/lib/cjs/components/select/search.js.map +1 -1
  24. package/lib/cjs/components/select/select.js +98 -110
  25. package/lib/cjs/components/select/select.js.map +1 -1
  26. package/lib/cjs/components/select/tags.js +0 -2
  27. package/lib/cjs/components/select/tags.js.map +1 -1
  28. package/lib/cjs/index.js +1 -10
  29. package/lib/cjs/index.js.map +1 -1
  30. package/lib/esm/components/datatable/__tests__/pagination-reset.test.js +594 -0
  31. package/lib/esm/components/datatable/__tests__/pagination-reset.test.js.map +1 -0
  32. package/lib/esm/components/datatable/__tests__/race-conditions.test.js +546 -0
  33. package/lib/esm/components/datatable/__tests__/race-conditions.test.js.map +1 -0
  34. package/lib/esm/components/datatable/__tests__/setup.js +58 -0
  35. package/lib/esm/components/datatable/__tests__/setup.js.map +1 -0
  36. package/lib/esm/components/datatable/datatable.js +92 -30
  37. package/lib/esm/components/datatable/datatable.js.map +1 -1
  38. package/lib/esm/components/select/combobox.js +0 -2
  39. package/lib/esm/components/select/combobox.js.map +1 -1
  40. package/lib/esm/components/select/config.js +4 -1
  41. package/lib/esm/components/select/config.js.map +1 -1
  42. package/lib/esm/components/select/dropdown.js +0 -16
  43. package/lib/esm/components/select/dropdown.js.map +1 -1
  44. package/lib/esm/components/select/remote.js +0 -40
  45. package/lib/esm/components/select/remote.js.map +1 -1
  46. package/lib/esm/components/select/search.js +80 -19
  47. package/lib/esm/components/select/search.js.map +1 -1
  48. package/lib/esm/components/select/select.js +98 -110
  49. package/lib/esm/components/select/select.js.map +1 -1
  50. package/lib/esm/components/select/tags.js +0 -2
  51. package/lib/esm/components/select/tags.js.map +1 -1
  52. package/lib/esm/index.js +0 -7
  53. package/lib/esm/index.js.map +1 -1
  54. package/package.json +7 -9
  55. package/src/components/alert/alert.css +188 -429
  56. package/src/components/datatable/__tests__/pagination-reset.test.ts +657 -0
  57. package/src/components/datatable/__tests__/race-conditions.test.ts +455 -0
  58. package/src/components/datatable/__tests__/setup.ts +67 -0
  59. package/src/components/datatable/datatable.ts +66 -11
  60. package/src/components/input/input.css +0 -1
  61. package/src/components/select/__tests__/ux-behaviors.test.ts +619 -0
  62. package/src/components/select/combobox.ts +0 -1
  63. package/src/components/select/config.ts +7 -1
  64. package/src/components/select/dropdown.ts +0 -24
  65. package/src/components/select/remote.ts +0 -49
  66. package/src/components/select/search.ts +85 -21
  67. package/src/components/select/select.css +0 -1
  68. package/src/components/select/select.ts +118 -149
  69. package/src/components/select/tags.ts +0 -1
  70. package/src/components/select/variants.css +4 -0
  71. package/src/components/textarea/textarea.css +0 -1
  72. package/src/index.ts +0 -10
  73. package/styles.css +0 -1
  74. package/lib/cjs/components/alert/alert.js +0 -1025
  75. package/lib/cjs/components/alert/alert.js.map +0 -1
  76. package/lib/cjs/components/alert/index.js +0 -20
  77. package/lib/cjs/components/alert/index.js.map +0 -1
  78. package/lib/cjs/components/alert/templates.js +0 -120
  79. package/lib/cjs/components/alert/templates.js.map +0 -1
  80. package/lib/cjs/components/alert/types.js +0 -7
  81. package/lib/cjs/components/alert/types.js.map +0 -1
  82. package/lib/cjs/components/datepicker/config/config.js +0 -42
  83. package/lib/cjs/components/datepicker/config/config.js.map +0 -1
  84. package/lib/cjs/components/datepicker/config/index.js +0 -24
  85. package/lib/cjs/components/datepicker/config/index.js.map +0 -1
  86. package/lib/cjs/components/datepicker/config/interfaces.js +0 -7
  87. package/lib/cjs/components/datepicker/config/interfaces.js.map +0 -1
  88. package/lib/cjs/components/datepicker/config/types.js +0 -7
  89. package/lib/cjs/components/datepicker/config/types.js.map +0 -1
  90. package/lib/cjs/components/datepicker/core/event-manager.js +0 -135
  91. package/lib/cjs/components/datepicker/core/event-manager.js.map +0 -1
  92. package/lib/cjs/components/datepicker/core/focus-manager.js +0 -167
  93. package/lib/cjs/components/datepicker/core/focus-manager.js.map +0 -1
  94. package/lib/cjs/components/datepicker/core/helpers.js +0 -219
  95. package/lib/cjs/components/datepicker/core/helpers.js.map +0 -1
  96. package/lib/cjs/components/datepicker/core/index.js +0 -25
  97. package/lib/cjs/components/datepicker/core/index.js.map +0 -1
  98. package/lib/cjs/components/datepicker/core/unified-state-manager.js +0 -394
  99. package/lib/cjs/components/datepicker/core/unified-state-manager.js.map +0 -1
  100. package/lib/cjs/components/datepicker/datepicker.js +0 -2252
  101. package/lib/cjs/components/datepicker/datepicker.js.map +0 -1
  102. package/lib/cjs/components/datepicker/index.js +0 -24
  103. package/lib/cjs/components/datepicker/index.js.map +0 -1
  104. package/lib/cjs/components/datepicker/ui/index.js +0 -23
  105. package/lib/cjs/components/datepicker/ui/index.js.map +0 -1
  106. package/lib/cjs/components/datepicker/ui/input/dropdown.js +0 -489
  107. package/lib/cjs/components/datepicker/ui/input/dropdown.js.map +0 -1
  108. package/lib/cjs/components/datepicker/ui/input/index.js +0 -23
  109. package/lib/cjs/components/datepicker/ui/input/index.js.map +0 -1
  110. package/lib/cjs/components/datepicker/ui/input/segmented-input.js +0 -640
  111. package/lib/cjs/components/datepicker/ui/input/segmented-input.js.map +0 -1
  112. package/lib/cjs/components/datepicker/ui/renderers/calendar.js +0 -446
  113. package/lib/cjs/components/datepicker/ui/renderers/calendar.js.map +0 -1
  114. package/lib/cjs/components/datepicker/ui/renderers/footer.js +0 -42
  115. package/lib/cjs/components/datepicker/ui/renderers/footer.js.map +0 -1
  116. package/lib/cjs/components/datepicker/ui/renderers/header.js +0 -32
  117. package/lib/cjs/components/datepicker/ui/renderers/header.js.map +0 -1
  118. package/lib/cjs/components/datepicker/ui/renderers/index.js +0 -25
  119. package/lib/cjs/components/datepicker/ui/renderers/index.js.map +0 -1
  120. package/lib/cjs/components/datepicker/ui/renderers/time-picker.js +0 -384
  121. package/lib/cjs/components/datepicker/ui/renderers/time-picker.js.map +0 -1
  122. package/lib/cjs/components/datepicker/ui/templates/index.js +0 -22
  123. package/lib/cjs/components/datepicker/ui/templates/index.js.map +0 -1
  124. package/lib/cjs/components/datepicker/ui/templates/templates.js +0 -253
  125. package/lib/cjs/components/datepicker/ui/templates/templates.js.map +0 -1
  126. package/lib/cjs/components/datepicker/utils/date-formatters.js +0 -88
  127. package/lib/cjs/components/datepicker/utils/date-formatters.js.map +0 -1
  128. package/lib/cjs/components/datepicker/utils/date-utils.js +0 -194
  129. package/lib/cjs/components/datepicker/utils/date-utils.js.map +0 -1
  130. package/lib/cjs/components/datepicker/utils/index.js +0 -24
  131. package/lib/cjs/components/datepicker/utils/index.js.map +0 -1
  132. package/lib/cjs/components/datepicker/utils/time-utils.js +0 -213
  133. package/lib/cjs/components/datepicker/utils/time-utils.js.map +0 -1
  134. package/lib/esm/components/alert/alert.js +0 -1022
  135. package/lib/esm/components/alert/alert.js.map +0 -1
  136. package/lib/esm/components/alert/index.js +0 -4
  137. package/lib/esm/components/alert/index.js.map +0 -1
  138. package/lib/esm/components/alert/templates.js +0 -112
  139. package/lib/esm/components/alert/templates.js.map +0 -1
  140. package/lib/esm/components/alert/types.js +0 -6
  141. package/lib/esm/components/alert/types.js.map +0 -1
  142. package/lib/esm/components/datepicker/config/config.js +0 -39
  143. package/lib/esm/components/datepicker/config/config.js.map +0 -1
  144. package/lib/esm/components/datepicker/config/index.js +0 -8
  145. package/lib/esm/components/datepicker/config/index.js.map +0 -1
  146. package/lib/esm/components/datepicker/config/interfaces.js +0 -6
  147. package/lib/esm/components/datepicker/config/interfaces.js.map +0 -1
  148. package/lib/esm/components/datepicker/config/types.js +0 -6
  149. package/lib/esm/components/datepicker/config/types.js.map +0 -1
  150. package/lib/esm/components/datepicker/core/event-manager.js +0 -133
  151. package/lib/esm/components/datepicker/core/event-manager.js.map +0 -1
  152. package/lib/esm/components/datepicker/core/focus-manager.js +0 -164
  153. package/lib/esm/components/datepicker/core/focus-manager.js.map +0 -1
  154. package/lib/esm/components/datepicker/core/helpers.js +0 -211
  155. package/lib/esm/components/datepicker/core/helpers.js.map +0 -1
  156. package/lib/esm/components/datepicker/core/index.js +0 -9
  157. package/lib/esm/components/datepicker/core/index.js.map +0 -1
  158. package/lib/esm/components/datepicker/core/unified-state-manager.js +0 -391
  159. package/lib/esm/components/datepicker/core/unified-state-manager.js.map +0 -1
  160. package/lib/esm/components/datepicker/datepicker.js +0 -2248
  161. package/lib/esm/components/datepicker/datepicker.js.map +0 -1
  162. package/lib/esm/components/datepicker/index.js +0 -7
  163. package/lib/esm/components/datepicker/index.js.map +0 -1
  164. package/lib/esm/components/datepicker/ui/index.js +0 -7
  165. package/lib/esm/components/datepicker/ui/index.js.map +0 -1
  166. package/lib/esm/components/datepicker/ui/input/dropdown.js +0 -486
  167. package/lib/esm/components/datepicker/ui/input/dropdown.js.map +0 -1
  168. package/lib/esm/components/datepicker/ui/input/index.js +0 -7
  169. package/lib/esm/components/datepicker/ui/input/index.js.map +0 -1
  170. package/lib/esm/components/datepicker/ui/input/segmented-input.js +0 -637
  171. package/lib/esm/components/datepicker/ui/input/segmented-input.js.map +0 -1
  172. package/lib/esm/components/datepicker/ui/renderers/calendar.js +0 -443
  173. package/lib/esm/components/datepicker/ui/renderers/calendar.js.map +0 -1
  174. package/lib/esm/components/datepicker/ui/renderers/footer.js +0 -39
  175. package/lib/esm/components/datepicker/ui/renderers/footer.js.map +0 -1
  176. package/lib/esm/components/datepicker/ui/renderers/header.js +0 -29
  177. package/lib/esm/components/datepicker/ui/renderers/header.js.map +0 -1
  178. package/lib/esm/components/datepicker/ui/renderers/index.js +0 -9
  179. package/lib/esm/components/datepicker/ui/renderers/index.js.map +0 -1
  180. package/lib/esm/components/datepicker/ui/renderers/time-picker.js +0 -381
  181. package/lib/esm/components/datepicker/ui/renderers/time-picker.js.map +0 -1
  182. package/lib/esm/components/datepicker/ui/templates/index.js +0 -6
  183. package/lib/esm/components/datepicker/ui/templates/index.js.map +0 -1
  184. package/lib/esm/components/datepicker/ui/templates/templates.js +0 -242
  185. package/lib/esm/components/datepicker/ui/templates/templates.js.map +0 -1
  186. package/lib/esm/components/datepicker/utils/date-formatters.js +0 -83
  187. package/lib/esm/components/datepicker/utils/date-formatters.js.map +0 -1
  188. package/lib/esm/components/datepicker/utils/date-utils.js +0 -184
  189. package/lib/esm/components/datepicker/utils/date-utils.js.map +0 -1
  190. package/lib/esm/components/datepicker/utils/index.js +0 -8
  191. package/lib/esm/components/datepicker/utils/index.js.map +0 -1
  192. package/lib/esm/components/datepicker/utils/time-utils.js +0 -201
  193. package/lib/esm/components/datepicker/utils/time-utils.js.map +0 -1
  194. package/src/components/alert/alert.ts +0 -990
  195. package/src/components/alert/index.ts +0 -4
  196. package/src/components/alert/templates.ts +0 -110
  197. package/src/components/alert/tests/accessibility/aria-roles.test.ts +0 -19
  198. package/src/components/alert/tests/accessibility/focus-management.test.ts +0 -19
  199. package/src/components/alert/tests/accessibility/keyboard-nav.test.ts +0 -22
  200. package/src/components/alert/tests/actions/confirm-cancel.test.ts +0 -122
  201. package/src/components/alert/tests/actions/input-field.test.ts +0 -180
  202. package/src/components/alert/tests/alert.basic.test.ts +0 -126
  203. package/src/components/alert/tests/alert.config.test.ts +0 -75
  204. package/src/components/alert/tests/alert.templates.test.ts +0 -17
  205. package/src/components/alert/tests/config/attribute-config.test.ts +0 -94
  206. package/src/components/alert/tests/config/json-config.test.ts +0 -119
  207. package/src/components/alert/tests/config/merging.test.ts +0 -89
  208. package/src/components/alert/tests/dismissal/auto-dismiss.test.ts +0 -96
  209. package/src/components/alert/tests/dismissal/escape-key-dismiss.test.ts +0 -105
  210. package/src/components/alert/tests/dismissal/manual-dismiss.test.ts +0 -90
  211. package/src/components/alert/tests/dismissal/outside-click-dismiss.test.ts +0 -91
  212. package/src/components/alert/tests/edge-cases/invalid-config.test.ts +0 -19
  213. package/src/components/alert/tests/edge-cases/multiple-alerts.test.ts +0 -19
  214. package/src/components/alert/tests/rendering/custom-content.test.ts +0 -81
  215. package/src/components/alert/tests/rendering/info-alert.test.ts +0 -84
  216. package/src/components/alert/tests/rendering/success-alert.test.ts +0 -100
  217. package/src/components/alert/tests/templates/default-templates.test.ts +0 -16
  218. package/src/components/alert/tests/templates/user-templates.test.ts +0 -16
  219. package/src/components/alert/types.ts +0 -145
  220. package/src/components/datepicker/__tests__/datepicker-events.test.ts +0 -356
  221. package/src/components/datepicker/__tests__/datepicker-init.test.ts +0 -343
  222. package/src/components/datepicker/__tests__/datepicker-integration.test.ts +0 -435
  223. package/src/components/datepicker/__tests__/datepicker-timezone.test.ts +0 -220
  224. package/src/components/datepicker/__tests__/segmented-input-focus.test.ts +0 -380
  225. package/src/components/datepicker/__tests__/selective-state-updates.test.ts +0 -400
  226. package/src/components/datepicker/__tests__/state-manager.test.ts +0 -421
  227. package/src/components/datepicker/__tests__/time-preservation.test.ts +0 -387
  228. package/src/components/datepicker/config/config.ts +0 -40
  229. package/src/components/datepicker/config/index.ts +0 -8
  230. package/src/components/datepicker/config/interfaces.ts +0 -82
  231. package/src/components/datepicker/config/types.ts +0 -188
  232. package/src/components/datepicker/core/event-manager.ts +0 -159
  233. package/src/components/datepicker/core/focus-manager.ts +0 -201
  234. package/src/components/datepicker/core/helpers.ts +0 -231
  235. package/src/components/datepicker/core/index.ts +0 -9
  236. package/src/components/datepicker/core/unified-state-manager.ts +0 -459
  237. package/src/components/datepicker/datepicker.css +0 -435
  238. package/src/components/datepicker/datepicker.ts +0 -2548
  239. package/src/components/datepicker/index.ts +0 -8
  240. package/src/components/datepicker/ui/index.ts +0 -7
  241. package/src/components/datepicker/ui/input/dropdown.ts +0 -552
  242. package/src/components/datepicker/ui/input/index.ts +0 -7
  243. package/src/components/datepicker/ui/input/segmented-input.ts +0 -638
  244. package/src/components/datepicker/ui/renderers/__tests__/calendar-optimizations.test.ts +0 -611
  245. package/src/components/datepicker/ui/renderers/calendar.ts +0 -530
  246. package/src/components/datepicker/ui/renderers/footer.ts +0 -43
  247. package/src/components/datepicker/ui/renderers/header.ts +0 -33
  248. package/src/components/datepicker/ui/renderers/index.ts +0 -9
  249. package/src/components/datepicker/ui/renderers/time-picker.ts +0 -438
  250. package/src/components/datepicker/ui/templates/index.ts +0 -6
  251. package/src/components/datepicker/ui/templates/templates.ts +0 -306
  252. package/src/components/datepicker/utils/__tests__/date-formatters.test.ts +0 -160
  253. package/src/components/datepicker/utils/__tests__/date-utils-keys.test.ts +0 -86
  254. package/src/components/datepicker/utils/__tests__/date-utils-timezone.test.ts +0 -215
  255. package/src/components/datepicker/utils/date-formatters.ts +0 -85
  256. package/src/components/datepicker/utils/date-utils.ts +0 -172
  257. package/src/components/datepicker/utils/index.ts +0 -8
  258. package/src/components/datepicker/utils/time-utils.ts +0 -221
@@ -1,530 +0,0 @@
1
- /*
2
- * calendar.ts - Calendar renderer for KTDatepicker
3
- * Renders the calendar grid (days) using provided template and data.
4
- */
5
-
6
- import { isTemplateFunction, renderTemplateString, renderTemplateToDOM } from '../templates/templates';
7
- import { defaultTemplates } from '../templates/templates';
8
- import { formatDateToLocalString, parseLocalDate, getDateKey } from '../../utils/date-utils';
9
-
10
- // Cache for localized day names to avoid regeneration on every render
11
- const dayNameCache = new Map<string, string[]>();
12
-
13
- /**
14
- * Gets localized day names for a given locale, using cache to avoid regeneration.
15
- * @param locale Locale string (e.g., 'en-US', 'de-DE')
16
- * @returns Array of 7 day names starting with Sunday
17
- */
18
- function getDayNames(locale: string): string[] {
19
- if (dayNameCache.has(locale)) {
20
- return dayNameCache.get(locale)!;
21
- }
22
-
23
- const dayNames: string[] = [];
24
- for (let i = 0; i < 7; i++) {
25
- const date = new Date(2023, 0, i + 1); // Use January 1st, 2023 as reference (Sunday = 0)
26
- const dayName = date.toLocaleDateString(locale, { weekday: 'short' });
27
- dayNames.push(dayName);
28
- }
29
-
30
- dayNameCache.set(locale, dayNames);
31
- return dayNames;
32
- }
33
-
34
- /**
35
- * Day cell computation data interface for centralized day cell rendering logic.
36
- * Used by both single-month and multi-month views to ensure consistent rendering.
37
- */
38
- interface DayCellData {
39
- day: number;
40
- date: Date;
41
- isCurrentMonth: boolean;
42
- isToday: boolean;
43
- isSelected: boolean;
44
- inRange: boolean;
45
- attributes: string;
46
- }
47
-
48
- /**
49
- * Computes day cell data (attributes, states) for a given day.
50
- * This centralized helper function ensures consistent day cell rendering
51
- * across single-month and multi-month views.
52
- *
53
- * @param day - The date for this day cell
54
- * @param currentDate - The current month being viewed (for isCurrentMonth calculation)
55
- * @param todayKey - Pre-computed date key for today (for performance)
56
- * @param checkIsSelected - Function to check if a date is selected
57
- * @param selectedRange - Optional range selection
58
- * @param dayIndex - Index of this day in the calendar grid (0-based)
59
- * @param tabbableIndex - Index of the tabbable day (for keyboard navigation)
60
- * @returns Day cell data object with computed states and attributes
61
- */
62
- function computeDayCellData(
63
- day: Date,
64
- currentDate: Date,
65
- todayKey: number,
66
- checkIsSelected: (day: Date) => boolean,
67
- selectedRange: { start: Date | null; end: Date | null } | undefined,
68
- dayIndex: number,
69
- tabbableIndex: number
70
- ): DayCellData {
71
- const isCurrentMonth = day.getMonth() === currentDate.getMonth();
72
- const isToday = getDateKey(day) === todayKey;
73
- const isSelected = checkIsSelected(day);
74
-
75
- let inRange = false;
76
- if (selectedRange && selectedRange.start && selectedRange.end) {
77
- // Use date keys for efficient range comparison (no Date object creation needed)
78
- const dayKey = getDateKey(day);
79
- const startKey = getDateKey(selectedRange.start);
80
- const endKey = getDateKey(selectedRange.end);
81
- inRange = dayKey >= startKey && dayKey <= endKey;
82
- }
83
-
84
- const dateLocal = formatDateToLocalString(day);
85
- const attributes = [
86
- isSelected ? 'data-kt-selected="true" aria-selected="true"' : '',
87
- isToday ? 'data-today="true"' : '',
88
- isCurrentMonth ? '' : 'data-outside="true"',
89
- // Use data-kt-hover-range for completed ranges too (consolidated with hover preview)
90
- inRange ? 'data-kt-hover-range="true"' : '',
91
- `data-date="${dateLocal}"`,
92
- `data-day-index="${dayIndex}"`,
93
- `tabindex=\"${dayIndex === tabbableIndex ? '0' : '-1'}\"`
94
- ].filter(Boolean).join(' ');
95
-
96
- return {
97
- day: day.getDate(),
98
- date: day,
99
- isCurrentMonth,
100
- isToday,
101
- isSelected,
102
- inRange,
103
- attributes
104
- };
105
- }
106
-
107
- /**
108
- * Renders the datepicker calendar and returns an HTMLElement.
109
- *
110
- * This function uses centralized day cell computation logic (computeDayCellData)
111
- * to ensure consistent rendering across single-month and multi-month views.
112
- * Both views call this function with identical state parameters, guaranteeing
113
- * that the same date appears identically regardless of which view is displayed.
114
- *
115
- * @param tpl - The template string or function for the day cell
116
- * @param days - Array of Date objects for the calendar grid
117
- * @param currentDate - The current month being viewed
118
- * @param selectedDate - The currently selected date
119
- * @param onDayClick - Callback for day cell click, receives the Date
120
- * @param locale - Locale string for day name localization
121
- * @param selectedRange - Optional range selection
122
- * @param selectedDates - Optional array of selected dates for multi-date mode
123
- */
124
- export function renderCalendar(
125
- tpl: string | ((data: any) => string),
126
- days: Date[],
127
- currentDate: Date,
128
- selectedDate: Date | null,
129
- onDayClick: (date: Date) => void,
130
- locale?: string,
131
- selectedRange?: { start: Date | null; end: Date | null },
132
- selectedDates?: Date[]
133
- ): HTMLElement {
134
- // Get localized day names from cache
135
- const localeToUse = locale || 'en-US';
136
- const dayNames = getDayNames(localeToUse);
137
-
138
- // Cache "today" date object to avoid creating it multiple times
139
- const today = new Date();
140
- today.setHours(0, 0, 0, 0);
141
- const todayKey = getDateKey(today);
142
-
143
- // Pre-compute date keys for selected dates for O(1) lookups
144
- const selectedDateKey = selectedDate ? getDateKey(selectedDate) : null;
145
- const selectedDatesKeys = selectedDates && selectedDates.length > 0
146
- ? new Set(selectedDates.map(d => getDateKey(d)))
147
- : null;
148
- const selectedRangeStartKey = selectedRange?.start ? getDateKey(selectedRange.start) : null;
149
- const selectedRangeEndKey = selectedRange?.end ? getDateKey(selectedRange.end) : null;
150
-
151
- // Use template system for table, tbody, tr, td
152
- const tableTpl = defaultTemplates.calendarTable;
153
- const bodyTpl = defaultTemplates.calendarBody;
154
- const rowTpl = defaultTemplates.calendarRow;
155
- const rows = [];
156
- // Determine which day should be tabbable (selected, or today if none) using date keys
157
- let tabbableIndex = -1;
158
- if (selectedDateKey !== null) {
159
- tabbableIndex = days.findIndex(d => getDateKey(d) === selectedDateKey);
160
- }
161
- if (tabbableIndex === -1 && selectedDatesKeys) {
162
- tabbableIndex = days.findIndex(d => selectedDatesKeys.has(getDateKey(d)));
163
- }
164
- if (tabbableIndex === -1 && selectedRangeStartKey !== null) {
165
- tabbableIndex = days.findIndex(d => getDateKey(d) === selectedRangeStartKey);
166
- }
167
- if (tabbableIndex === -1) {
168
- tabbableIndex = days.findIndex(d => getDateKey(d) === todayKey);
169
- }
170
-
171
- // Helper function to check if a date is selected using date keys for O(1) lookups
172
- const checkIsSelected = (day: Date): boolean => {
173
- const dayKey = getDateKey(day);
174
- // Check single date selection
175
- if (selectedDateKey !== null && dayKey === selectedDateKey) {
176
- return true;
177
- }
178
- // Check multi-date selection
179
- if (selectedDatesKeys && selectedDatesKeys.has(dayKey)) {
180
- return true;
181
- }
182
- // Check range selection start/end dates
183
- if (selectedRangeStartKey !== null && dayKey === selectedRangeStartKey) {
184
- return true;
185
- }
186
- if (selectedRangeEndKey !== null && dayKey === selectedRangeEndKey) {
187
- return true;
188
- }
189
- return false;
190
- };
191
-
192
- // Render calendar weeks using centralized day cell computation
193
- // This ensures consistent rendering across single-month and multi-month views
194
- for (let i = 0; i < days.length; i += 7) {
195
- const week = days.slice(i, i + 7);
196
- const tds = week.map((day, j) => {
197
- const dayIndex = i + j;
198
- // Use centralized helper function to compute day cell data
199
- // This guarantees identical logic for single-month and multi-month views
200
- const data = computeDayCellData(
201
- day,
202
- currentDate,
203
- todayKey,
204
- checkIsSelected,
205
- selectedRange,
206
- dayIndex,
207
- tabbableIndex
208
- );
209
- return isTemplateFunction(tpl)
210
- ? tpl(data)
211
- : renderTemplateString(typeof tpl === 'string' ? tpl : (typeof defaultTemplates.dayCell === 'string' ? defaultTemplates.dayCell : ''), data);
212
- }).join('');
213
- // Use row template
214
- const rowHtml = isTemplateFunction(rowTpl)
215
- ? rowTpl({ cells: tds })
216
- : (rowTpl as string).replace(/{{cells}}/g, tds);
217
- rows.push(rowHtml);
218
- }
219
- // Use body template
220
- const bodyHtml = isTemplateFunction(bodyTpl)
221
- ? bodyTpl({ rows: rows.join('') })
222
- : (bodyTpl as string).replace(/{{rows}}/g, rows.join(''));
223
- // Use table template with localized day names
224
- const tableHtml = isTemplateFunction(tableTpl)
225
- ? tableTpl({
226
- body: bodyHtml,
227
- sunday: dayNames[0],
228
- monday: dayNames[1],
229
- tuesday: dayNames[2],
230
- wednesday: dayNames[3],
231
- thursday: dayNames[4],
232
- friday: dayNames[5],
233
- saturday: dayNames[6]
234
- })
235
- : (tableTpl as string)
236
- .replace(/{{body}}/g, bodyHtml)
237
- .replace(/{{sunday}}/g, dayNames[0])
238
- .replace(/{{monday}}/g, dayNames[1])
239
- .replace(/{{tuesday}}/g, dayNames[2])
240
- .replace(/{{wednesday}}/g, dayNames[3])
241
- .replace(/{{thursday}}/g, dayNames[4])
242
- .replace(/{{friday}}/g, dayNames[5])
243
- .replace(/{{saturday}}/g, dayNames[6]);
244
- const calendarFrag = renderTemplateToDOM(tableHtml);
245
- const calendar = calendarFrag.firstElementChild as HTMLElement;
246
-
247
- // Build cell reference cache for O(1) lookups during hover range updates
248
- // Map<dateKey, HTMLElement> for efficient cell lookups
249
- const cellCache = new Map<number, HTMLElement>();
250
- const allCells = calendar.querySelectorAll('td[data-kt-datepicker-day]');
251
- allCells.forEach((cell) => {
252
- const dateAttr = cell.getAttribute('data-date');
253
- if (dateAttr) {
254
- const cellDate = parseLocalDate(dateAttr);
255
- const dateKey = getDateKey(cellDate);
256
- cellCache.set(dateKey, cell as HTMLElement);
257
- }
258
- });
259
-
260
- // Store selectedRange state on the calendar element so hover handlers can access current state
261
- // This allows the state to be updated without re-rendering the entire calendar
262
- // The _updateCalendar() method will update these attributes when the state changes
263
- if (selectedRange) {
264
- if (selectedRange.start) {
265
- calendar.setAttribute('data-kt-range-start', formatDateToLocalString(selectedRange.start));
266
- }
267
- if (selectedRange.end) {
268
- calendar.setAttribute('data-kt-range-end', formatDateToLocalString(selectedRange.end));
269
- }
270
- }
271
-
272
- // Helper function to parse ISO date string (YYYY-MM-DD) as local date to avoid timezone issues
273
- const parseLocalDateFromAttr = (dateStr: string): Date => {
274
- // Parse YYYY-MM-DD string and create local date at midnight
275
- return parseLocalDate(dateStr);
276
- };
277
-
278
- // Helper function to get current selectedRange from calendar element data attributes
279
- const getCurrentSelectedRange = (): { start: Date | null; end: Date | null } | null => {
280
- const startAttr = calendar.getAttribute('data-kt-range-start');
281
- const endAttr = calendar.getAttribute('data-kt-range-end');
282
-
283
- if (!startAttr && !endAttr) {
284
- return null;
285
- }
286
-
287
- return {
288
- start: startAttr ? parseLocalDateFromAttr(startAttr) : null,
289
- end: endAttr ? parseLocalDateFromAttr(endAttr) : null
290
- };
291
- };
292
-
293
- // Helper function to check if we're in range preview mode (checked dynamically)
294
- const isRangePreviewMode = (): boolean => {
295
- const currentRange = getCurrentSelectedRange();
296
- return !!(currentRange && currentRange.start && !currentRange.end);
297
- };
298
-
299
- // Helper function to clear all hover range attributes
300
- const clearHoverRange = () => {
301
- calendar.querySelectorAll('[data-kt-hover-range]').forEach((cell) => {
302
- (cell as HTMLElement).removeAttribute('data-kt-hover-range');
303
- });
304
- };
305
-
306
- // Helper function to get all dates in a range (inclusive)
307
- const getDatesInRange = (startDate: Date, endDate: Date): Date[] => {
308
- const dates: Date[] = [];
309
- const start = new Date(startDate);
310
- const end = new Date(endDate);
311
-
312
- // Normalize dates to midnight for accurate comparison
313
- start.setHours(0, 0, 0, 0);
314
- end.setHours(0, 0, 0, 0);
315
-
316
- // Determine the actual start and end (handles reverse ranges)
317
- const actualStart = start <= end ? start : end;
318
- const actualEnd = start <= end ? end : start;
319
-
320
- // Generate all dates in the range
321
- const current = new Date(actualStart);
322
- while (current <= actualEnd) {
323
- dates.push(new Date(current));
324
- current.setDate(current.getDate() + 1);
325
- }
326
-
327
- return dates;
328
- };
329
-
330
- // Helper function to find a date cell by date using cached cell references
331
- const findDateCell = (targetDate: Date): HTMLElement | null => {
332
- const targetKey = getDateKey(targetDate);
333
- return cellCache.get(targetKey) || null;
334
- };
335
-
336
- // Helper function to update hover range preview
337
- const updateHoverRange = (hoveredDate: Date) => {
338
- if (!isRangePreviewMode()) {
339
- return;
340
- }
341
-
342
- // Get current range state from calendar element (reads dynamically)
343
- const currentRange = getCurrentSelectedRange();
344
- if (!currentRange?.start) {
345
- return;
346
- }
347
-
348
- // Normalize hovered date
349
- const normalizedHovered = new Date(hoveredDate);
350
- normalizedHovered.setHours(0, 0, 0, 0);
351
-
352
- // Normalize start date
353
- const normalizedStart = new Date(currentRange.start);
354
- normalizedStart.setHours(0, 0, 0, 0);
355
-
356
- // Clear previous hover range from ALL calendars in multi-month view
357
- const dropdownEl = calendar.closest('[data-kt-datepicker-dropdown]') as HTMLElement;
358
- if (dropdownEl) {
359
- const allCalendars = dropdownEl.querySelectorAll('[data-kt-datepicker-calendar-table]');
360
- allCalendars.forEach((cal) => {
361
- const hoverCells = cal.querySelectorAll('[data-kt-hover-range]');
362
- hoverCells.forEach((cell) => {
363
- cell.removeAttribute('data-kt-hover-range');
364
- });
365
- });
366
- } else {
367
- clearHoverRange();
368
- }
369
-
370
- // Calculate range between start date and hovered date
371
- const rangeDates = getDatesInRange(normalizedStart, normalizedHovered);
372
-
373
- // Add data-kt-hover-range to all dates in the range across ALL visible calendars
374
- // This enables hover preview to work across multiple months
375
- const allCalendars = dropdownEl
376
- ? Array.from(dropdownEl.querySelectorAll('[data-kt-datepicker-calendar-table]')) as HTMLElement[]
377
- : [calendar];
378
-
379
- rangeDates.forEach((date) => {
380
- const dateLocal = formatDateToLocalString(date);
381
- // Search across all calendars to find the cell by data-date attribute
382
- for (const cal of allCalendars) {
383
- const cell = cal.querySelector(`td[data-kt-datepicker-day][data-date="${dateLocal}"]`) as HTMLElement;
384
- if (cell) {
385
- // Set attribute if cell is found in this calendar
386
- cell.setAttribute('data-kt-hover-range', '');
387
- break; // Found in this calendar, no need to search others
388
- }
389
- }
390
- });
391
- };
392
-
393
- // Event delegation: attach single listeners to calendar table instead of individual cell listeners
394
- // This reduces memory footprint from 42+ listeners to 2-3 listeners
395
- calendar.addEventListener('click', (e) => {
396
- const target = e.target as HTMLElement;
397
- // Check if click is on a button or inside a button within a calendar cell
398
- const button = target.closest('button[data-day]') as HTMLButtonElement;
399
- if (!button) return;
400
-
401
- const cell = button.closest('td[data-kt-datepicker-day]') as HTMLElement;
402
- if (!cell) return;
403
-
404
- const dateAttr = cell.getAttribute('data-date');
405
- if (!dateAttr) return;
406
-
407
- const dayObj = parseLocalDate(dateAttr);
408
- if (dayObj.getMonth() === currentDate.getMonth()) {
409
- e.stopPropagation();
410
- onDayClick(dayObj);
411
- }
412
- });
413
-
414
- // Track currently hovered cell to prevent race conditions during fast mouse movement
415
- let currentHoveredCell: HTMLElement | null = null;
416
- let hoverTimeout: number | null = null;
417
-
418
- // Delegated hover handlers for single date hover and range preview
419
- // Use mouseover/mouseout on calendar with relatedTarget check for proper delegation
420
- calendar.addEventListener('mouseover', (e) => {
421
- const target = e.target as HTMLElement;
422
- // Check if mouseover is on a button or inside a button within a calendar cell
423
- const button = target.closest('button[data-day]') as HTMLButtonElement;
424
- if (!button) return;
425
-
426
- const cell = button.closest('td[data-kt-datepicker-day]') as HTMLElement;
427
- if (!cell) return;
428
-
429
- const dateAttr = cell.getAttribute('data-date');
430
- if (!dateAttr) return;
431
-
432
- // Clear any pending hover removal timeout
433
- if (hoverTimeout) {
434
- clearTimeout(hoverTimeout);
435
- hoverTimeout = null;
436
- }
437
-
438
- // Clear previous hovered cell if different
439
- if (currentHoveredCell && currentHoveredCell !== cell) {
440
- currentHoveredCell.removeAttribute('data-kt-hover');
441
- }
442
-
443
- // Set new hovered cell
444
- currentHoveredCell = cell;
445
- cell.setAttribute('data-kt-hover', '');
446
-
447
- // If in range preview mode, update hover range
448
- if (isRangePreviewMode()) {
449
- const dayObj = parseLocalDate(dateAttr);
450
- updateHoverRange(dayObj);
451
- }
452
- });
453
-
454
- calendar.addEventListener('mouseout', (e) => {
455
- const target = e.target as HTMLElement;
456
- const relatedTarget = (e as MouseEvent).relatedTarget as HTMLElement;
457
-
458
- // Check if mouseout is leaving a button
459
- const button = target.closest('button[data-day]') as HTMLButtonElement;
460
- if (!button) return;
461
-
462
- // If moving to another element within the same calendar, don't remove hover
463
- if (relatedTarget && calendar.contains(relatedTarget)) {
464
- const relatedButton = relatedTarget.closest('button[data-day]');
465
- if (relatedButton) {
466
- // Moving to another button - mouseover will handle the transition
467
- return;
468
- }
469
- }
470
-
471
- const cell = button.closest('td[data-kt-datepicker-day]') as HTMLElement;
472
- if (!cell) return;
473
-
474
- // Clear any pending hover removal timeout
475
- if (hoverTimeout) {
476
- clearTimeout(hoverTimeout);
477
- hoverTimeout = null;
478
- }
479
-
480
- // Use a small delay to prevent flickering when moving fast between cells
481
- // This allows mouseover on the next cell to fire first
482
- hoverTimeout = window.setTimeout(() => {
483
- // Only remove if this is still the current hovered cell
484
- if (currentHoveredCell === cell) {
485
- cell.removeAttribute('data-kt-hover');
486
- currentHoveredCell = null;
487
- }
488
- hoverTimeout = null;
489
- }, 50);
490
-
491
- // Note: Hover range clearing is handled at calendar level to prevent flickering
492
- });
493
-
494
- // Track hover state to prevent flickering when moving between dates
495
- let hoverRangeClearTimeout: number | null = null;
496
-
497
- // Handle calendar-level mouseleave to clear hover range when mouse leaves calendar entirely
498
- calendar.addEventListener('mouseleave', (e) => {
499
- if (!isRangePreviewMode()) return;
500
-
501
- // Check if we're moving to another button within the calendar
502
- const relatedTarget = (e as MouseEvent).relatedTarget as HTMLElement;
503
- if (relatedTarget && calendar.contains(relatedTarget)) {
504
- // Mouse is moving to another element within the calendar, don't clear
505
- return;
506
- }
507
-
508
- // Clear hover range with a small delay to allow smooth transitions
509
- if (hoverRangeClearTimeout) {
510
- clearTimeout(hoverRangeClearTimeout);
511
- }
512
- hoverRangeClearTimeout = window.setTimeout(() => {
513
- clearHoverRange();
514
- hoverRangeClearTimeout = null;
515
- }, 50);
516
- });
517
-
518
- // Clear timeout if mouse re-enters calendar
519
- calendar.addEventListener('mouseenter', () => {
520
- if (hoverRangeClearTimeout) {
521
- clearTimeout(hoverRangeClearTimeout);
522
- hoverRangeClearTimeout = null;
523
- }
524
- });
525
-
526
- return calendar;
527
- }
528
-
529
- // Note: isSameDay function removed - all date comparisons now use date keys for better performance
530
- // If needed elsewhere, use isSameLocalDay or isSameDayByKey from date-utils
@@ -1,43 +0,0 @@
1
- /*
2
- * footer.ts - Footer renderer for KTDatepicker
3
- * Renders the calendar footer (today, clear, apply buttons) using provided template and data.
4
- */
5
-
6
- import { isTemplateFunction, renderTemplateString, renderTemplateToDOM } from '../templates/templates';
7
- import { defaultTemplates } from '../templates/templates';
8
-
9
- /**
10
- * Renders the datepicker footer and returns an HTMLElement.
11
- * @param tpl - The template string or function for the footer
12
- * @param data - Data for the template (todayButton, clearButton, applyButton, etc.)
13
- * @param onToday - Callback for today button
14
- * @param onClear - Callback for clear button
15
- * @param onApply - Callback for apply button
16
- */
17
- export function renderFooter(
18
- tpl: string | ((data: any) => string),
19
- data: any,
20
- onToday?: (e: Event) => void,
21
- onClear?: (e: Event) => void,
22
- onApply?: (e: Event) => void
23
- ): HTMLElement {
24
- const footerHtml = isTemplateFunction(tpl)
25
- ? tpl(data)
26
- : renderTemplateString(typeof tpl === 'string' ? tpl : (typeof defaultTemplates.footer === 'string' ? defaultTemplates.footer : ''), data);
27
- const footerFrag = renderTemplateToDOM(footerHtml);
28
- const footer = footerFrag.firstElementChild as HTMLElement;
29
- // Add button event listeners if present
30
- if (onToday) {
31
- const todayBtn = footer.querySelector('[data-kt-datepicker-today]');
32
- if (todayBtn) todayBtn.addEventListener('click', onToday);
33
- }
34
- if (onClear) {
35
- const clearBtn = footer.querySelector('[data-kt-datepicker-clear]');
36
- if (clearBtn) clearBtn.addEventListener('click', onClear);
37
- }
38
- if (onApply) {
39
- const applyBtn = footer.querySelector('[data-kt-datepicker-apply]');
40
- if (applyBtn) applyBtn.addEventListener('click', onApply);
41
- }
42
- return footer;
43
- }
@@ -1,33 +0,0 @@
1
- /*
2
- * header.ts - Header renderer for KTDatepicker
3
- * Renders the calendar header (month, year, navigation buttons) using provided template and data.
4
- */
5
-
6
- import { isTemplateFunction, renderTemplateString, renderTemplateToDOM } from '../templates/templates';
7
- import { defaultTemplates } from '../templates/templates';
8
-
9
- /**
10
- * Renders the datepicker header and returns an HTMLElement.
11
- * @param tpl - The template string or function for the header
12
- * @param data - Data for the template (month, year, prevButton, nextButton)
13
- * @param onPrev - Callback for previous month button
14
- * @param onNext - Callback for next month button
15
- */
16
- export function renderHeader(
17
- tpl: string | ((data: any) => string),
18
- data: any,
19
- onPrev: (e: Event) => void,
20
- onNext: (e: Event) => void
21
- ): HTMLElement {
22
- const headerHtml = isTemplateFunction(tpl)
23
- ? tpl(data)
24
- : renderTemplateString(typeof tpl === 'string' ? tpl : (typeof defaultTemplates.header === 'string' ? defaultTemplates.header : ''), data);
25
- const headerFrag = renderTemplateToDOM(headerHtml);
26
- const header = headerFrag.firstElementChild as HTMLElement;
27
- // Add navigation event listeners
28
- const prevBtn = header.querySelector('[data-kt-datepicker-prev]');
29
- const nextBtn = header.querySelector('[data-kt-datepicker-next]');
30
- if (prevBtn) prevBtn.addEventListener('click', onPrev);
31
- if (nextBtn) nextBtn.addEventListener('click', onNext);
32
- return header;
33
- }
@@ -1,9 +0,0 @@
1
- /*
2
- * ui/renderers/index.ts - Renderer module exports
3
- * Provides UI rendering components for KTDatepicker
4
- */
5
-
6
- export * from './calendar';
7
- export * from './header';
8
- export * from './footer';
9
- export * from './time-picker';