@keenthemes/ktui 1.0.29 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (243) hide show
  1. package/README.md +27 -0
  2. package/dist/ktui.js +8780 -6199
  3. package/dist/ktui.min.js +1 -1
  4. package/dist/ktui.min.js.map +1 -1
  5. package/dist/styles.css +2744 -1367
  6. package/lib/cjs/components/alert/alert.js +1025 -0
  7. package/lib/cjs/components/alert/alert.js.map +1 -0
  8. package/lib/cjs/components/alert/index.js +20 -0
  9. package/lib/cjs/components/alert/index.js.map +1 -0
  10. package/lib/cjs/components/alert/templates.js +120 -0
  11. package/lib/cjs/components/alert/templates.js.map +1 -0
  12. package/lib/cjs/components/alert/types.js +7 -0
  13. package/lib/cjs/components/alert/types.js.map +1 -0
  14. package/lib/cjs/components/datepicker/config/config.js +42 -0
  15. package/lib/cjs/components/datepicker/config/config.js.map +1 -0
  16. package/lib/cjs/components/datepicker/config/index.js +24 -0
  17. package/lib/cjs/components/datepicker/config/index.js.map +1 -0
  18. package/lib/cjs/components/datepicker/config/interfaces.js +7 -0
  19. package/lib/cjs/components/datepicker/config/interfaces.js.map +1 -0
  20. package/lib/cjs/components/datepicker/config/types.js +7 -0
  21. package/lib/cjs/components/datepicker/config/types.js.map +1 -0
  22. package/lib/cjs/components/datepicker/core/event-manager.js +135 -0
  23. package/lib/cjs/components/datepicker/core/event-manager.js.map +1 -0
  24. package/lib/cjs/components/datepicker/core/focus-manager.js +167 -0
  25. package/lib/cjs/components/datepicker/core/focus-manager.js.map +1 -0
  26. package/lib/cjs/components/datepicker/core/helpers.js +219 -0
  27. package/lib/cjs/components/datepicker/core/helpers.js.map +1 -0
  28. package/lib/cjs/components/datepicker/core/index.js +25 -0
  29. package/lib/cjs/components/datepicker/core/index.js.map +1 -0
  30. package/lib/cjs/components/datepicker/core/unified-state-manager.js +394 -0
  31. package/lib/cjs/components/datepicker/core/unified-state-manager.js.map +1 -0
  32. package/lib/cjs/components/datepicker/datepicker.js +2066 -763
  33. package/lib/cjs/components/datepicker/datepicker.js.map +1 -1
  34. package/lib/cjs/components/datepicker/index.js +19 -8
  35. package/lib/cjs/components/datepicker/index.js.map +1 -1
  36. package/lib/cjs/components/datepicker/ui/index.js +23 -0
  37. package/lib/cjs/components/datepicker/ui/index.js.map +1 -0
  38. package/lib/cjs/components/datepicker/ui/input/dropdown.js +489 -0
  39. package/lib/cjs/components/datepicker/ui/input/dropdown.js.map +1 -0
  40. package/lib/cjs/components/datepicker/ui/input/index.js +23 -0
  41. package/lib/cjs/components/datepicker/ui/input/index.js.map +1 -0
  42. package/lib/cjs/components/datepicker/ui/input/segmented-input.js +640 -0
  43. package/lib/cjs/components/datepicker/ui/input/segmented-input.js.map +1 -0
  44. package/lib/cjs/components/datepicker/ui/renderers/calendar.js +446 -0
  45. package/lib/cjs/components/datepicker/ui/renderers/calendar.js.map +1 -0
  46. package/lib/cjs/components/datepicker/ui/renderers/footer.js +42 -0
  47. package/lib/cjs/components/datepicker/ui/renderers/footer.js.map +1 -0
  48. package/lib/cjs/components/datepicker/ui/renderers/header.js +32 -0
  49. package/lib/cjs/components/datepicker/ui/renderers/header.js.map +1 -0
  50. package/lib/cjs/components/datepicker/ui/renderers/index.js +25 -0
  51. package/lib/cjs/components/datepicker/ui/renderers/index.js.map +1 -0
  52. package/lib/cjs/components/datepicker/ui/renderers/time-picker.js +384 -0
  53. package/lib/cjs/components/datepicker/ui/renderers/time-picker.js.map +1 -0
  54. package/lib/cjs/components/datepicker/ui/templates/index.js +22 -0
  55. package/lib/cjs/components/datepicker/ui/templates/index.js.map +1 -0
  56. package/lib/cjs/components/datepicker/ui/templates/templates.js +253 -0
  57. package/lib/cjs/components/datepicker/ui/templates/templates.js.map +1 -0
  58. package/lib/cjs/components/datepicker/utils/date-formatters.js +88 -0
  59. package/lib/cjs/components/datepicker/utils/date-formatters.js.map +1 -0
  60. package/lib/cjs/components/datepicker/utils/date-utils.js +194 -0
  61. package/lib/cjs/components/datepicker/utils/date-utils.js.map +1 -0
  62. package/lib/cjs/components/datepicker/utils/index.js +24 -0
  63. package/lib/cjs/components/datepicker/utils/index.js.map +1 -0
  64. package/lib/cjs/components/datepicker/utils/time-utils.js +213 -0
  65. package/lib/cjs/components/datepicker/utils/time-utils.js.map +1 -0
  66. package/lib/cjs/index.js +6 -1
  67. package/lib/cjs/index.js.map +1 -1
  68. package/lib/esm/components/alert/alert.js +1022 -0
  69. package/lib/esm/components/alert/alert.js.map +1 -0
  70. package/lib/esm/components/alert/index.js +4 -0
  71. package/lib/esm/components/alert/index.js.map +1 -0
  72. package/lib/esm/components/alert/templates.js +112 -0
  73. package/lib/esm/components/alert/templates.js.map +1 -0
  74. package/lib/esm/components/alert/types.js +6 -0
  75. package/lib/esm/components/alert/types.js.map +1 -0
  76. package/lib/esm/components/datepicker/config/config.js +39 -0
  77. package/lib/esm/components/datepicker/config/config.js.map +1 -0
  78. package/lib/esm/components/datepicker/config/index.js +8 -0
  79. package/lib/esm/components/datepicker/config/index.js.map +1 -0
  80. package/lib/esm/components/datepicker/config/interfaces.js +6 -0
  81. package/lib/esm/components/datepicker/config/interfaces.js.map +1 -0
  82. package/lib/esm/components/datepicker/config/types.js +6 -0
  83. package/lib/esm/components/datepicker/config/types.js.map +1 -0
  84. package/lib/esm/components/datepicker/core/event-manager.js +133 -0
  85. package/lib/esm/components/datepicker/core/event-manager.js.map +1 -0
  86. package/lib/esm/components/datepicker/core/focus-manager.js +164 -0
  87. package/lib/esm/components/datepicker/core/focus-manager.js.map +1 -0
  88. package/lib/esm/components/datepicker/core/helpers.js +211 -0
  89. package/lib/esm/components/datepicker/core/helpers.js.map +1 -0
  90. package/lib/esm/components/datepicker/core/index.js +9 -0
  91. package/lib/esm/components/datepicker/core/index.js.map +1 -0
  92. package/lib/esm/components/datepicker/core/unified-state-manager.js +391 -0
  93. package/lib/esm/components/datepicker/core/unified-state-manager.js.map +1 -0
  94. package/lib/esm/components/datepicker/datepicker.js +2065 -763
  95. package/lib/esm/components/datepicker/datepicker.js.map +1 -1
  96. package/lib/esm/components/datepicker/index.js +6 -8
  97. package/lib/esm/components/datepicker/index.js.map +1 -1
  98. package/lib/esm/components/datepicker/ui/index.js +7 -0
  99. package/lib/esm/components/datepicker/ui/index.js.map +1 -0
  100. package/lib/esm/components/datepicker/ui/input/dropdown.js +486 -0
  101. package/lib/esm/components/datepicker/ui/input/dropdown.js.map +1 -0
  102. package/lib/esm/components/datepicker/ui/input/index.js +7 -0
  103. package/lib/esm/components/datepicker/ui/input/index.js.map +1 -0
  104. package/lib/esm/components/datepicker/ui/input/segmented-input.js +637 -0
  105. package/lib/esm/components/datepicker/ui/input/segmented-input.js.map +1 -0
  106. package/lib/esm/components/datepicker/ui/renderers/calendar.js +443 -0
  107. package/lib/esm/components/datepicker/ui/renderers/calendar.js.map +1 -0
  108. package/lib/esm/components/datepicker/ui/renderers/footer.js +39 -0
  109. package/lib/esm/components/datepicker/ui/renderers/footer.js.map +1 -0
  110. package/lib/esm/components/datepicker/ui/renderers/header.js +29 -0
  111. package/lib/esm/components/datepicker/ui/renderers/header.js.map +1 -0
  112. package/lib/esm/components/datepicker/ui/renderers/index.js +9 -0
  113. package/lib/esm/components/datepicker/ui/renderers/index.js.map +1 -0
  114. package/lib/esm/components/datepicker/ui/renderers/time-picker.js +381 -0
  115. package/lib/esm/components/datepicker/ui/renderers/time-picker.js.map +1 -0
  116. package/lib/esm/components/datepicker/ui/templates/index.js +6 -0
  117. package/lib/esm/components/datepicker/ui/templates/index.js.map +1 -0
  118. package/lib/esm/components/datepicker/ui/templates/templates.js +242 -0
  119. package/lib/esm/components/datepicker/ui/templates/templates.js.map +1 -0
  120. package/lib/esm/components/datepicker/utils/date-formatters.js +83 -0
  121. package/lib/esm/components/datepicker/utils/date-formatters.js.map +1 -0
  122. package/lib/esm/components/datepicker/utils/date-utils.js +184 -0
  123. package/lib/esm/components/datepicker/utils/date-utils.js.map +1 -0
  124. package/lib/esm/components/datepicker/utils/index.js +8 -0
  125. package/lib/esm/components/datepicker/utils/index.js.map +1 -0
  126. package/lib/esm/components/datepicker/utils/time-utils.js +201 -0
  127. package/lib/esm/components/datepicker/utils/time-utils.js.map +1 -0
  128. package/lib/esm/index.js +4 -0
  129. package/lib/esm/index.js.map +1 -1
  130. package/package.json +12 -3
  131. package/src/components/alert/alert.css +429 -188
  132. package/src/components/alert/alert.ts +990 -0
  133. package/src/components/alert/index.ts +4 -0
  134. package/src/components/alert/templates.ts +110 -0
  135. package/src/components/alert/tests/accessibility/aria-roles.test.ts +19 -0
  136. package/src/components/alert/tests/accessibility/focus-management.test.ts +19 -0
  137. package/src/components/alert/tests/accessibility/keyboard-nav.test.ts +22 -0
  138. package/src/components/alert/tests/actions/confirm-cancel.test.ts +122 -0
  139. package/src/components/alert/tests/actions/input-field.test.ts +180 -0
  140. package/src/components/alert/tests/alert.basic.test.ts +126 -0
  141. package/src/components/alert/tests/alert.config.test.ts +75 -0
  142. package/src/components/alert/tests/alert.templates.test.ts +17 -0
  143. package/src/components/alert/tests/config/attribute-config.test.ts +94 -0
  144. package/src/components/alert/tests/config/json-config.test.ts +119 -0
  145. package/src/components/alert/tests/config/merging.test.ts +89 -0
  146. package/src/components/alert/tests/dismissal/auto-dismiss.test.ts +96 -0
  147. package/src/components/alert/tests/dismissal/escape-key-dismiss.test.ts +105 -0
  148. package/src/components/alert/tests/dismissal/manual-dismiss.test.ts +90 -0
  149. package/src/components/alert/tests/dismissal/outside-click-dismiss.test.ts +91 -0
  150. package/src/components/alert/tests/edge-cases/invalid-config.test.ts +19 -0
  151. package/src/components/alert/tests/edge-cases/multiple-alerts.test.ts +19 -0
  152. package/src/components/alert/tests/rendering/custom-content.test.ts +81 -0
  153. package/src/components/alert/tests/rendering/info-alert.test.ts +84 -0
  154. package/src/components/alert/tests/rendering/success-alert.test.ts +100 -0
  155. package/src/components/alert/tests/templates/default-templates.test.ts +16 -0
  156. package/src/components/alert/tests/templates/user-templates.test.ts +16 -0
  157. package/src/components/alert/types.ts +145 -0
  158. package/src/components/datepicker/__tests__/datepicker-events.test.ts +356 -0
  159. package/src/components/datepicker/__tests__/datepicker-init.test.ts +343 -0
  160. package/src/components/datepicker/__tests__/datepicker-integration.test.ts +435 -0
  161. package/src/components/datepicker/__tests__/datepicker-timezone.test.ts +220 -0
  162. package/src/components/datepicker/__tests__/segmented-input-focus.test.ts +380 -0
  163. package/src/components/datepicker/__tests__/selective-state-updates.test.ts +400 -0
  164. package/src/components/datepicker/__tests__/state-manager.test.ts +421 -0
  165. package/src/components/datepicker/__tests__/time-preservation.test.ts +387 -0
  166. package/src/components/datepicker/config/config.ts +40 -0
  167. package/src/components/datepicker/config/index.ts +8 -0
  168. package/src/components/datepicker/config/interfaces.ts +82 -0
  169. package/src/components/datepicker/config/types.ts +188 -0
  170. package/src/components/datepicker/core/event-manager.ts +159 -0
  171. package/src/components/datepicker/core/focus-manager.ts +201 -0
  172. package/src/components/datepicker/core/helpers.ts +231 -0
  173. package/src/components/datepicker/core/index.ts +9 -0
  174. package/src/components/datepicker/core/unified-state-manager.ts +459 -0
  175. package/src/components/datepicker/datepicker.css +429 -1
  176. package/src/components/datepicker/datepicker.ts +2538 -1277
  177. package/src/components/datepicker/index.ts +6 -8
  178. package/src/components/datepicker/ui/index.ts +7 -0
  179. package/src/components/datepicker/ui/input/dropdown.ts +552 -0
  180. package/src/components/datepicker/ui/input/index.ts +7 -0
  181. package/src/components/datepicker/ui/input/segmented-input.ts +638 -0
  182. package/src/components/datepicker/ui/renderers/__tests__/calendar-optimizations.test.ts +611 -0
  183. package/src/components/datepicker/ui/renderers/calendar.ts +530 -0
  184. package/src/components/datepicker/ui/renderers/footer.ts +43 -0
  185. package/src/components/datepicker/ui/renderers/header.ts +33 -0
  186. package/src/components/datepicker/ui/renderers/index.ts +9 -0
  187. package/src/components/datepicker/ui/renderers/time-picker.ts +438 -0
  188. package/src/components/datepicker/ui/templates/index.ts +6 -0
  189. package/src/components/datepicker/ui/templates/templates.ts +306 -0
  190. package/src/components/datepicker/utils/__tests__/date-formatters.test.ts +160 -0
  191. package/src/components/datepicker/utils/__tests__/date-utils-keys.test.ts +86 -0
  192. package/src/components/datepicker/utils/__tests__/date-utils-timezone.test.ts +215 -0
  193. package/src/components/datepicker/utils/date-formatters.ts +85 -0
  194. package/src/components/datepicker/utils/date-utils.ts +172 -0
  195. package/src/components/datepicker/utils/index.ts +8 -0
  196. package/src/components/datepicker/utils/time-utils.ts +221 -0
  197. package/src/index.ts +7 -1
  198. package/lib/cjs/components/datepicker/calendar.js +0 -1061
  199. package/lib/cjs/components/datepicker/calendar.js.map +0 -1
  200. package/lib/cjs/components/datepicker/config.js +0 -332
  201. package/lib/cjs/components/datepicker/config.js.map +0 -1
  202. package/lib/cjs/components/datepicker/dropdown.js +0 -635
  203. package/lib/cjs/components/datepicker/dropdown.js.map +0 -1
  204. package/lib/cjs/components/datepicker/events.js +0 -129
  205. package/lib/cjs/components/datepicker/events.js.map +0 -1
  206. package/lib/cjs/components/datepicker/keyboard.js +0 -536
  207. package/lib/cjs/components/datepicker/keyboard.js.map +0 -1
  208. package/lib/cjs/components/datepicker/locales.js +0 -78
  209. package/lib/cjs/components/datepicker/locales.js.map +0 -1
  210. package/lib/cjs/components/datepicker/templates.js +0 -403
  211. package/lib/cjs/components/datepicker/templates.js.map +0 -1
  212. package/lib/cjs/components/datepicker/types.js +0 -23
  213. package/lib/cjs/components/datepicker/types.js.map +0 -1
  214. package/lib/cjs/components/datepicker/utils.js +0 -524
  215. package/lib/cjs/components/datepicker/utils.js.map +0 -1
  216. package/lib/esm/components/datepicker/calendar.js +0 -1058
  217. package/lib/esm/components/datepicker/calendar.js.map +0 -1
  218. package/lib/esm/components/datepicker/config.js +0 -329
  219. package/lib/esm/components/datepicker/config.js.map +0 -1
  220. package/lib/esm/components/datepicker/dropdown.js +0 -632
  221. package/lib/esm/components/datepicker/dropdown.js.map +0 -1
  222. package/lib/esm/components/datepicker/events.js +0 -126
  223. package/lib/esm/components/datepicker/events.js.map +0 -1
  224. package/lib/esm/components/datepicker/keyboard.js +0 -533
  225. package/lib/esm/components/datepicker/keyboard.js.map +0 -1
  226. package/lib/esm/components/datepicker/locales.js +0 -74
  227. package/lib/esm/components/datepicker/locales.js.map +0 -1
  228. package/lib/esm/components/datepicker/templates.js +0 -390
  229. package/lib/esm/components/datepicker/templates.js.map +0 -1
  230. package/lib/esm/components/datepicker/types.js +0 -20
  231. package/lib/esm/components/datepicker/types.js.map +0 -1
  232. package/lib/esm/components/datepicker/utils.js +0 -508
  233. package/lib/esm/components/datepicker/utils.js.map +0 -1
  234. package/src/components/datepicker/calendar.ts +0 -1397
  235. package/src/components/datepicker/config.ts +0 -368
  236. package/src/components/datepicker/dropdown.ts +0 -757
  237. package/src/components/datepicker/events.ts +0 -149
  238. package/src/components/datepicker/keyboard.ts +0 -646
  239. package/src/components/datepicker/locales.ts +0 -80
  240. package/src/components/datepicker/templates.ts +0 -792
  241. package/src/components/datepicker/types.ts +0 -154
  242. package/src/components/datepicker/utils.ts +0 -631
  243. package/src/components/select/variants.css +0 -4
@@ -0,0 +1,611 @@
1
+ /**
2
+ * calendar-optimizations.test.ts - Tests for calendar rendering optimizations
3
+ * Tests day name caching, event delegation, cell reference caching, and date key optimizations
4
+ */
5
+
6
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
7
+ import { renderCalendar } from '../calendar';
8
+ import { getDateKey } from '../../../utils/date-utils';
9
+ import { defaultTemplates } from '../../templates/templates';
10
+
11
+ describe('Calendar Rendering Optimizations', () => {
12
+ let container: HTMLElement;
13
+
14
+ beforeEach(() => {
15
+ container = document.createElement('div');
16
+ document.body.appendChild(container);
17
+ });
18
+
19
+ afterEach(() => {
20
+ if (container.parentNode) {
21
+ document.body.removeChild(container);
22
+ }
23
+ });
24
+
25
+ // Helper to generate calendar days for a month
26
+ function getCalendarDays(year: number, month: number): Date[] {
27
+ const firstDay = new Date(year, month, 1);
28
+ const lastDay = new Date(year, month + 1, 0);
29
+ const days: Date[] = [];
30
+ let start = new Date(firstDay);
31
+ start.setDate(firstDay.getDate() - firstDay.getDay());
32
+ let end = new Date(lastDay);
33
+ end.setDate(lastDay.getDate() + (6 - lastDay.getDay()));
34
+ for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
35
+ days.push(new Date(d));
36
+ }
37
+ return days;
38
+ }
39
+
40
+ describe('Day Name Caching', () => {
41
+ it('should cache day names per locale', () => {
42
+ const days = getCalendarDays(2024, 0);
43
+ const onDayClick = vi.fn();
44
+ const currentDate = new Date(2024, 0, 15);
45
+
46
+ // First render with en-US
47
+ const calendar1 = renderCalendar(
48
+ defaultTemplates.dayCell as string,
49
+ days,
50
+ currentDate,
51
+ null,
52
+ onDayClick,
53
+ 'en-US'
54
+ );
55
+
56
+ // Second render with same locale - should use cache
57
+ const calendar2 = renderCalendar(
58
+ defaultTemplates.dayCell as string,
59
+ days,
60
+ currentDate,
61
+ null,
62
+ onDayClick,
63
+ 'en-US'
64
+ );
65
+
66
+ // Both should have same day names in thead
67
+ const dayNames1 = Array.from(calendar1.querySelectorAll('thead th')).map(th => th.textContent?.trim()).filter(Boolean);
68
+ const dayNames2 = Array.from(calendar2.querySelectorAll('thead th')).map(th => th.textContent?.trim()).filter(Boolean);
69
+ expect(dayNames1.length).toBeGreaterThan(0);
70
+ expect(dayNames1).toEqual(dayNames2);
71
+ });
72
+
73
+ it('should cache different locales separately', () => {
74
+ const days = getCalendarDays(2024, 0);
75
+ const onDayClick = vi.fn();
76
+ const currentDate = new Date(2024, 0, 15);
77
+
78
+ const calendarEN = renderCalendar(
79
+ defaultTemplates.dayCell as string,
80
+ days,
81
+ currentDate,
82
+ null,
83
+ onDayClick,
84
+ 'en-US'
85
+ );
86
+
87
+ const calendarDE = renderCalendar(
88
+ defaultTemplates.dayCell as string,
89
+ days,
90
+ currentDate,
91
+ null,
92
+ onDayClick,
93
+ 'de-DE'
94
+ );
95
+
96
+ // Day names should be different for different locales
97
+ const dayNamesEN = Array.from(calendarEN.querySelectorAll('thead th')).map(th => th.textContent?.trim()).filter(Boolean);
98
+ const dayNamesDE = Array.from(calendarDE.querySelectorAll('thead th')).map(th => th.textContent?.trim()).filter(Boolean);
99
+ expect(dayNamesEN.length).toBeGreaterThan(0);
100
+ expect(dayNamesDE.length).toBeGreaterThan(0);
101
+ // Note: In some test environments, locale formatting might be the same
102
+ // So we just verify both have day names
103
+ });
104
+
105
+ it('should work with multiple locales (en-US, de-DE, fr-FR)', () => {
106
+ const days = getCalendarDays(2024, 0);
107
+ const onDayClick = vi.fn();
108
+ const currentDate = new Date(2024, 0, 15);
109
+
110
+ // Render calendars for all three locales
111
+ const calendarEN = renderCalendar(
112
+ defaultTemplates.dayCell as string,
113
+ days,
114
+ currentDate,
115
+ null,
116
+ onDayClick,
117
+ 'en-US'
118
+ );
119
+
120
+ const calendarDE = renderCalendar(
121
+ defaultTemplates.dayCell as string,
122
+ days,
123
+ currentDate,
124
+ null,
125
+ onDayClick,
126
+ 'de-DE'
127
+ );
128
+
129
+ const calendarFR = renderCalendar(
130
+ defaultTemplates.dayCell as string,
131
+ days,
132
+ currentDate,
133
+ null,
134
+ onDayClick,
135
+ 'fr-FR'
136
+ );
137
+
138
+ // All should have day names
139
+ const dayNamesEN = Array.from(calendarEN.querySelectorAll('thead th')).map(th => th.textContent?.trim()).filter(Boolean);
140
+ const dayNamesDE = Array.from(calendarDE.querySelectorAll('thead th')).map(th => th.textContent?.trim()).filter(Boolean);
141
+ const dayNamesFR = Array.from(calendarFR.querySelectorAll('thead th')).map(th => th.textContent?.trim()).filter(Boolean);
142
+
143
+ expect(dayNamesEN.length).toBe(7);
144
+ expect(dayNamesDE.length).toBe(7);
145
+ expect(dayNamesFR.length).toBe(7);
146
+
147
+ // Verify cache persists across multiple renders of same locale
148
+ const calendarEN2 = renderCalendar(
149
+ defaultTemplates.dayCell as string,
150
+ days,
151
+ currentDate,
152
+ null,
153
+ onDayClick,
154
+ 'en-US'
155
+ );
156
+ const dayNamesEN2 = Array.from(calendarEN2.querySelectorAll('thead th')).map(th => th.textContent?.trim()).filter(Boolean);
157
+ expect(dayNamesEN).toEqual(dayNamesEN2); // Should be cached
158
+ });
159
+
160
+ it('should work correctly across multiple datepicker instances', () => {
161
+ const days = getCalendarDays(2024, 0);
162
+ const onDayClick = vi.fn();
163
+ const currentDate = new Date(2024, 0, 15);
164
+
165
+ // Simulate multiple instances using same locale
166
+ const instance1_calendar1 = renderCalendar(
167
+ defaultTemplates.dayCell as string,
168
+ days,
169
+ currentDate,
170
+ null,
171
+ onDayClick,
172
+ 'en-US'
173
+ );
174
+
175
+ const instance2_calendar1 = renderCalendar(
176
+ defaultTemplates.dayCell as string,
177
+ days,
178
+ currentDate,
179
+ null,
180
+ onDayClick,
181
+ 'en-US'
182
+ );
183
+
184
+ // Both instances should use the same cached day names
185
+ const dayNames1 = Array.from(instance1_calendar1.querySelectorAll('thead th')).map(th => th.textContent?.trim()).filter(Boolean);
186
+ const dayNames2 = Array.from(instance2_calendar1.querySelectorAll('thead th')).map(th => th.textContent?.trim()).filter(Boolean);
187
+
188
+ expect(dayNames1.length).toBe(7);
189
+ expect(dayNames2.length).toBe(7);
190
+ expect(dayNames1).toEqual(dayNames2); // Cache should be shared across instances
191
+
192
+ // Render again with different months - cache should still work
193
+ const days2 = getCalendarDays(2024, 1);
194
+ const instance1_calendar2 = renderCalendar(
195
+ defaultTemplates.dayCell as string,
196
+ days2,
197
+ new Date(2024, 1, 15),
198
+ null,
199
+ onDayClick,
200
+ 'en-US'
201
+ );
202
+
203
+ const dayNames1_month2 = Array.from(instance1_calendar2.querySelectorAll('thead th')).map(th => th.textContent?.trim()).filter(Boolean);
204
+ expect(dayNames1_month2).toEqual(dayNames1); // Same locale = same cached day names
205
+ });
206
+ });
207
+
208
+ describe('Event Delegation', () => {
209
+ it('should use event delegation for click events', () => {
210
+ const days = getCalendarDays(2024, 0);
211
+ const onDayClick = vi.fn();
212
+ const currentDate = new Date(2024, 0, 15);
213
+
214
+ const calendar = renderCalendar(
215
+ defaultTemplates.dayCell as string,
216
+ days,
217
+ currentDate,
218
+ null,
219
+ onDayClick
220
+ );
221
+
222
+ // Count event listeners - should be minimal (delegated)
223
+ const buttons = calendar.querySelectorAll('button[data-day]');
224
+ expect(buttons.length).toBeGreaterThan(0);
225
+
226
+ // Click a button - should trigger delegated handler
227
+ const firstButton = buttons[0] as HTMLButtonElement;
228
+ firstButton.click();
229
+
230
+ // Should have called onDayClick if date is in current month
231
+ // Note: onDayClick only fires for dates in current month
232
+ const firstCell = firstButton.closest('td[data-kt-datepicker-day]') as HTMLElement;
233
+ const dateAttr = firstCell?.getAttribute('data-date');
234
+ if (dateAttr) {
235
+ const [year, month] = dateAttr.split('-').map(Number);
236
+ if (month - 1 === currentDate.getMonth()) {
237
+ expect(onDayClick).toHaveBeenCalled();
238
+ }
239
+ }
240
+ });
241
+
242
+ it('should handle hover events via delegation', () => {
243
+ const days = getCalendarDays(2024, 0);
244
+ const onDayClick = vi.fn();
245
+ const currentDate = new Date(2024, 0, 15);
246
+
247
+ const calendar = renderCalendar(
248
+ defaultTemplates.dayCell as string,
249
+ days,
250
+ currentDate,
251
+ null,
252
+ onDayClick
253
+ );
254
+
255
+ const firstCell = calendar.querySelector('td[data-kt-datepicker-day]') as HTMLElement;
256
+ const firstButton = firstCell?.querySelector('button[data-day]') as HTMLButtonElement;
257
+
258
+ if (firstButton) {
259
+ // Simulate mouseover - should trigger delegated handler
260
+ const mouseoverEvent = new MouseEvent('mouseover', { bubbles: true });
261
+ firstButton.dispatchEvent(mouseoverEvent);
262
+
263
+ // Cell should have hover attribute set by delegated handler
264
+ expect(firstCell.hasAttribute('data-kt-hover')).toBe(true);
265
+ }
266
+ });
267
+
268
+ it('should only attach listeners to calendar table, not individual cells', () => {
269
+ const days = getCalendarDays(2024, 0);
270
+ const onDayClick = vi.fn();
271
+ const currentDate = new Date(2024, 0, 15);
272
+
273
+ const calendar = renderCalendar(
274
+ defaultTemplates.dayCell as string,
275
+ days,
276
+ currentDate,
277
+ null,
278
+ onDayClick
279
+ );
280
+
281
+ // Verify calendar has event listeners (delegated)
282
+ // We can't directly count listeners, but we can verify behavior
283
+ const buttons = calendar.querySelectorAll('button[data-day]');
284
+ expect(buttons.length).toBeGreaterThan(0);
285
+
286
+ // All buttons should be clickable via delegation
287
+ // Click a button in current month
288
+ const currentMonthButtons = Array.from(buttons).filter(btn => {
289
+ const cell = btn.closest('td[data-kt-datepicker-day]') as HTMLElement;
290
+ const dateAttr = cell?.getAttribute('data-date');
291
+ if (dateAttr) {
292
+ const [year, month] = dateAttr.split('-').map(Number);
293
+ return month - 1 === currentDate.getMonth();
294
+ }
295
+ return false;
296
+ });
297
+
298
+ if (currentMonthButtons.length > 0) {
299
+ (currentMonthButtons[0] as HTMLButtonElement).click();
300
+ expect(onDayClick).toHaveBeenCalled();
301
+ }
302
+ });
303
+ });
304
+
305
+ describe('Date Key Optimization', () => {
306
+ it('should use date keys for selected date highlighting', () => {
307
+ const days = getCalendarDays(2024, 0);
308
+ const onDayClick = vi.fn();
309
+ const currentDate = new Date(2024, 0, 15);
310
+ const selectedDate = new Date(2024, 0, 15);
311
+
312
+ const calendar = renderCalendar(
313
+ defaultTemplates.dayCell as string,
314
+ days,
315
+ currentDate,
316
+ selectedDate,
317
+ onDayClick
318
+ );
319
+
320
+ // Find the selected cell
321
+ const selectedCell = calendar.querySelector('[data-kt-selected="true"]') as HTMLElement;
322
+ expect(selectedCell).toBeTruthy();
323
+
324
+ // Verify it has the correct date
325
+ const dateAttr = selectedCell.getAttribute('data-date');
326
+ expect(dateAttr).toBe('2024-01-15');
327
+ });
328
+
329
+ it('should use date keys for multi-date selection', () => {
330
+ const days = getCalendarDays(2024, 0);
331
+ const onDayClick = vi.fn();
332
+ const currentDate = new Date(2024, 0, 15);
333
+ const selectedDates = [
334
+ new Date(2024, 0, 10),
335
+ new Date(2024, 0, 15),
336
+ new Date(2024, 0, 20)
337
+ ];
338
+
339
+ const calendar = renderCalendar(
340
+ defaultTemplates.dayCell as string,
341
+ days,
342
+ currentDate,
343
+ null,
344
+ onDayClick,
345
+ 'en-US',
346
+ undefined,
347
+ selectedDates
348
+ );
349
+
350
+ // All selected dates should be highlighted
351
+ const selectedCells = calendar.querySelectorAll('[data-kt-selected="true"]');
352
+ expect(selectedCells.length).toBe(3);
353
+ });
354
+
355
+ it('should use date keys for range selection', () => {
356
+ const days = getCalendarDays(2024, 0);
357
+ const onDayClick = vi.fn();
358
+ const currentDate = new Date(2024, 0, 15);
359
+ const selectedRange = {
360
+ start: new Date(2024, 0, 10),
361
+ end: new Date(2024, 0, 20)
362
+ };
363
+
364
+ const calendar = renderCalendar(
365
+ defaultTemplates.dayCell as string,
366
+ days,
367
+ currentDate,
368
+ null,
369
+ onDayClick,
370
+ 'en-US',
371
+ selectedRange
372
+ );
373
+
374
+ // Start and end dates should be highlighted
375
+ const selectedCells = calendar.querySelectorAll('[data-kt-selected="true"]');
376
+ expect(selectedCells.length).toBeGreaterThanOrEqual(2);
377
+
378
+ // Range dates should have data-kt-hover-range attribute (consolidated from data-in-range)
379
+ const inRangeCells = calendar.querySelectorAll('[data-kt-hover-range]');
380
+ expect(inRangeCells.length).toBeGreaterThan(0);
381
+ });
382
+
383
+ it('should optimize tabbable index calculation using date keys', () => {
384
+ const days = getCalendarDays(2024, 0);
385
+ const onDayClick = vi.fn();
386
+ const currentDate = new Date(2024, 0, 15);
387
+ const selectedDate = new Date(2024, 0, 15);
388
+
389
+ const calendar = renderCalendar(
390
+ defaultTemplates.dayCell as string,
391
+ days,
392
+ currentDate,
393
+ selectedDate,
394
+ onDayClick
395
+ );
396
+
397
+ // Selected date should be tabbable (tabindex="0")
398
+ const selectedCell = calendar.querySelector('[data-kt-selected="true"]') as HTMLElement;
399
+ expect(selectedCell).toBeTruthy();
400
+ const selectedButton = selectedCell?.querySelector('button[data-day]') as HTMLButtonElement;
401
+ expect(selectedButton).toBeTruthy();
402
+
403
+ // tabindex is set in the attributes string on the td, not directly on button
404
+ // The button has tabindex="-1" by default in template, but the cell has tabindex="0" for selected
405
+ // Check that the cell has the correct tabindex attribute
406
+ const cellTabindex = selectedCell.getAttribute('tabindex');
407
+ // The tabindex should be 0 for the selected date (set in attributes)
408
+ // Since attributes are in the td, we check the cell
409
+ expect(cellTabindex === '0' || selectedCell.tabIndex === 0).toBe(true);
410
+ });
411
+ });
412
+
413
+ describe('Cell Reference Caching', () => {
414
+ it('should cache cell references for hover range updates', () => {
415
+ const days = getCalendarDays(2024, 0);
416
+ const onDayClick = vi.fn();
417
+ const currentDate = new Date(2024, 0, 15);
418
+ const selectedRange = {
419
+ start: new Date(2024, 0, 10),
420
+ end: null
421
+ };
422
+
423
+ const calendar = renderCalendar(
424
+ defaultTemplates.dayCell as string,
425
+ days,
426
+ currentDate,
427
+ null,
428
+ onDayClick,
429
+ 'en-US',
430
+ selectedRange
431
+ );
432
+
433
+ // All cells should have data-date attributes for caching
434
+ const cells = calendar.querySelectorAll('td[data-kt-datepicker-day]');
435
+ expect(cells.length).toBeGreaterThan(0);
436
+ cells.forEach(cell => {
437
+ expect(cell.hasAttribute('data-date')).toBe(true);
438
+ });
439
+ });
440
+
441
+ it('should use cached cell references for hover range', () => {
442
+ const days = getCalendarDays(2024, 0);
443
+ const onDayClick = vi.fn();
444
+ const currentDate = new Date(2024, 0, 15);
445
+ const selectedRange = {
446
+ start: new Date(2024, 0, 10),
447
+ end: null
448
+ };
449
+
450
+ const calendar = renderCalendar(
451
+ defaultTemplates.dayCell as string,
452
+ days,
453
+ currentDate,
454
+ null,
455
+ onDayClick,
456
+ 'en-US',
457
+ selectedRange
458
+ );
459
+
460
+ // Simulate hover on a date in range preview mode
461
+ const hoverCell = calendar.querySelector(`[data-date="2024-01-15"]`) as HTMLElement;
462
+ const hoverButton = hoverCell?.querySelector('button[data-day]') as HTMLButtonElement;
463
+
464
+ if (hoverButton) {
465
+ const mouseoverEvent = new MouseEvent('mouseover', { bubbles: true });
466
+ hoverButton.dispatchEvent(mouseoverEvent);
467
+
468
+ // Should have hover range attributes set (if in range preview mode)
469
+ // Note: This depends on the range state being set correctly
470
+ const hoverRangeCells = calendar.querySelectorAll('[data-kt-hover-range]');
471
+ // May be 0 if range preview mode conditions aren't met, but cell should exist
472
+ expect(hoverCell).toBeTruthy();
473
+ }
474
+ });
475
+ });
476
+
477
+ describe('Date Object Reuse', () => {
478
+ it('should cache today date object', () => {
479
+ const days = getCalendarDays(2024, 0);
480
+ const onDayClick = vi.fn();
481
+ const currentDate = new Date(2024, 0, 15);
482
+
483
+ const calendar = renderCalendar(
484
+ defaultTemplates.dayCell as string,
485
+ days,
486
+ currentDate,
487
+ null,
488
+ onDayClick
489
+ );
490
+
491
+ // Today's date should be marked if it's in the visible month
492
+ const today = new Date();
493
+ if (today.getMonth() === currentDate.getMonth() && today.getFullYear() === currentDate.getFullYear()) {
494
+ const todayKey = getDateKey(today);
495
+ const todayDateStr = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`;
496
+ const todayCell = calendar.querySelector(`[data-date="${todayDateStr}"]`) as HTMLElement;
497
+ if (todayCell) {
498
+ expect(todayCell.hasAttribute('data-today')).toBe(true);
499
+ }
500
+ }
501
+ });
502
+
503
+ it('should use date keys for range normalization instead of creating Date objects', () => {
504
+ const days = getCalendarDays(2024, 0);
505
+ const onDayClick = vi.fn();
506
+ const currentDate = new Date(2024, 0, 15);
507
+ const selectedRange = {
508
+ start: new Date(2024, 0, 10),
509
+ end: new Date(2024, 0, 20)
510
+ };
511
+
512
+ const calendar = renderCalendar(
513
+ defaultTemplates.dayCell as string,
514
+ days,
515
+ currentDate,
516
+ null,
517
+ onDayClick,
518
+ 'en-US',
519
+ selectedRange
520
+ );
521
+
522
+ // Range dates should be marked without creating Date objects in loop
523
+ // Note: Uses data-kt-hover-range (consolidated from data-in-range)
524
+ const inRangeCells = calendar.querySelectorAll('[data-kt-hover-range]');
525
+ expect(inRangeCells.length).toBeGreaterThan(0);
526
+
527
+ // Verify range calculation is correct using date keys
528
+ const startKey = getDateKey(selectedRange.start);
529
+ const endKey = getDateKey(selectedRange.end);
530
+ inRangeCells.forEach(cell => {
531
+ const dateAttr = cell.getAttribute('data-date');
532
+ if (dateAttr) {
533
+ const [year, month, day] = dateAttr.split('-').map(Number);
534
+ const cellKey = getDateKey(new Date(year, month - 1, day));
535
+ expect(cellKey).toBeGreaterThanOrEqual(startKey);
536
+ expect(cellKey).toBeLessThanOrEqual(endKey);
537
+ }
538
+ });
539
+ });
540
+ });
541
+
542
+ describe('Performance Optimizations Integration', () => {
543
+ it('should render calendar efficiently with all optimizations', () => {
544
+ const days = getCalendarDays(2024, 0);
545
+ const onDayClick = vi.fn();
546
+ const currentDate = new Date(2024, 0, 15);
547
+ const selectedDate = new Date(2024, 0, 15);
548
+ const selectedDates = [
549
+ new Date(2024, 0, 10),
550
+ new Date(2024, 0, 20)
551
+ ];
552
+ const selectedRange = {
553
+ start: new Date(2024, 0, 5),
554
+ end: new Date(2024, 0, 25)
555
+ };
556
+
557
+ // Render with all selection modes to test optimizations
558
+ const calendar = renderCalendar(
559
+ defaultTemplates.dayCell as string,
560
+ days,
561
+ currentDate,
562
+ selectedDate,
563
+ onDayClick,
564
+ 'en-US',
565
+ selectedRange,
566
+ selectedDates
567
+ );
568
+
569
+ // Verify calendar rendered correctly
570
+ expect(calendar).toBeTruthy();
571
+ const cells = calendar.querySelectorAll('td[data-kt-datepicker-day]');
572
+ expect(cells.length).toBeGreaterThan(0);
573
+
574
+ // Verify optimizations are working
575
+ // 1. Day names should be cached (no way to directly test, but rendering works)
576
+ // 2. Event delegation should work (tested separately)
577
+ // 3. Date keys should work (selected dates highlighted)
578
+ const selectedCells = calendar.querySelectorAll('[data-kt-selected="true"]');
579
+ expect(selectedCells.length).toBeGreaterThan(0);
580
+ });
581
+
582
+ it('should handle multiple renders efficiently', () => {
583
+ const days = getCalendarDays(2024, 0);
584
+ const onDayClick = vi.fn();
585
+ const currentDate = new Date(2024, 0, 15);
586
+
587
+ // Render multiple times - day name cache should speed up subsequent renders
588
+ const calendars: HTMLElement[] = [];
589
+ for (let i = 0; i < 5; i++) {
590
+ const calendar = renderCalendar(
591
+ defaultTemplates.dayCell as string,
592
+ days,
593
+ currentDate,
594
+ null,
595
+ onDayClick,
596
+ 'en-US'
597
+ );
598
+ calendars.push(calendar);
599
+ }
600
+
601
+ // All calendars should render correctly
602
+ expect(calendars.length).toBe(5);
603
+ calendars.forEach(cal => {
604
+ expect(cal).toBeTruthy();
605
+ const cells = cal.querySelectorAll('td[data-kt-datepicker-day]');
606
+ expect(cells.length).toBeGreaterThan(0);
607
+ });
608
+ });
609
+ });
610
+ });
611
+