tyrell-components 1.0.0-TC45 → 1.0.0-TC46

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 (350) hide show
  1. package/dist/tyrell.js +1 -1
  2. package/lib/base/ty-component.d.ts +12 -7
  3. package/lib/base/ty-component.js +8 -139
  4. package/lib/components/button.d.ts +2 -16
  5. package/lib/components/button.js +0 -36
  6. package/lib/components/calendar-month.d.ts +2 -48
  7. package/lib/components/calendar-month.js +4 -166
  8. package/lib/components/calendar-navigation.d.ts +3 -48
  9. package/lib/components/calendar-navigation.js +3 -126
  10. package/lib/components/calendar.d.ts +3 -80
  11. package/lib/components/calendar.js +8 -230
  12. package/lib/components/checkbox.d.ts +4 -71
  13. package/lib/components/checkbox.js +7 -151
  14. package/lib/components/copy.d.ts +3 -56
  15. package/lib/components/copy.js +1 -120
  16. package/lib/components/date-picker.d.ts +5 -162
  17. package/lib/components/date-picker.js +28 -583
  18. package/lib/components/file-upload.d.ts +3 -15
  19. package/lib/components/file-upload.js +0 -37
  20. package/lib/components/icon.d.ts +3 -24
  21. package/lib/components/icon.js +1 -90
  22. package/lib/components/input.d.ts +7 -107
  23. package/lib/components/input.js +4 -193
  24. package/lib/components/modal.d.ts +3 -26
  25. package/lib/components/modal.js +1 -188
  26. package/lib/components/option.d.ts +2 -9
  27. package/lib/components/option.js +0 -48
  28. package/lib/components/popup.d.ts +2 -17
  29. package/lib/components/popup.js +3 -130
  30. package/lib/components/radio.d.ts +3 -23
  31. package/lib/components/radio.js +0 -63
  32. package/lib/components/resize-observer.d.ts +2 -30
  33. package/lib/components/resize-observer.js +0 -45
  34. package/lib/components/scroll-container.d.ts +2 -12
  35. package/lib/components/scroll-container.js +3 -46
  36. package/lib/components/select.d.ts +11 -150
  37. package/lib/components/select.js +7 -581
  38. package/lib/components/selected-tags.d.ts +4 -47
  39. package/lib/components/selected-tags.js +0 -94
  40. package/lib/components/step.d.ts +1 -6
  41. package/lib/components/step.js +0 -6
  42. package/lib/components/switch.d.ts +0 -11
  43. package/lib/components/switch.js +2 -36
  44. package/lib/components/tab.d.ts +1 -6
  45. package/lib/components/tab.js +0 -7
  46. package/lib/components/tabs.d.ts +3 -63
  47. package/lib/components/tabs.js +2 -282
  48. package/lib/components/tag.d.ts +2 -36
  49. package/lib/components/tag.js +5 -105
  50. package/lib/components/textarea.d.ts +6 -86
  51. package/lib/components/textarea.js +2 -173
  52. package/lib/components/tooltip.d.ts +3 -11
  53. package/lib/components/tooltip.js +0 -154
  54. package/lib/components/wizard.d.ts +4 -54
  55. package/lib/components/wizard.js +0 -228
  56. package/lib/icons/fontawesome/brands.d.ts +0 -1
  57. package/lib/icons/fontawesome/brands.js +0 -8
  58. package/lib/icons/fontawesome/regular.d.ts +0 -1
  59. package/lib/icons/fontawesome/regular.js +0 -8
  60. package/lib/icons/fontawesome/solid.d.ts +0 -1
  61. package/lib/icons/fontawesome/solid.js +0 -8
  62. package/lib/icons/heroicons/micro.d.ts +0 -1
  63. package/lib/icons/heroicons/micro.js +0 -8
  64. package/lib/icons/heroicons/mini.d.ts +0 -1
  65. package/lib/icons/heroicons/mini.js +0 -8
  66. package/lib/icons/heroicons/outline.d.ts +0 -1
  67. package/lib/icons/heroicons/outline.js +0 -8
  68. package/lib/icons/heroicons/solid.d.ts +0 -1
  69. package/lib/icons/heroicons/solid.js +0 -8
  70. package/lib/icons/lucide.d.ts +0 -1
  71. package/lib/icons/lucide.js +0 -1872
  72. package/lib/icons/material/filled.d.ts +0 -1
  73. package/lib/icons/material/filled.js +0 -10
  74. package/lib/icons/material/outlined.d.ts +0 -1
  75. package/lib/icons/material/outlined.js +0 -10
  76. package/lib/icons/material/round.d.ts +0 -1
  77. package/lib/icons/material/round.js +0 -10
  78. package/lib/icons/material/sharp.d.ts +0 -1
  79. package/lib/icons/material/sharp.js +0 -10
  80. package/lib/icons/material/two-tone.d.ts +0 -1
  81. package/lib/icons/material/two-tone.js +0 -10
  82. package/lib/index.d.ts +0 -1
  83. package/lib/index.js +0 -20
  84. package/lib/styles/button.d.ts +2 -20
  85. package/lib/styles/button.js +13 -120
  86. package/lib/styles/calendar-month.d.ts +1 -5
  87. package/lib/styles/calendar-month.js +3 -60
  88. package/lib/styles/calendar-navigation.d.ts +2 -6
  89. package/lib/styles/calendar-navigation.js +1 -26
  90. package/lib/styles/calendar.d.ts +1 -2
  91. package/lib/styles/calendar.js +0 -13
  92. package/lib/styles/checkbox.d.ts +0 -1
  93. package/lib/styles/checkbox.js +1 -23
  94. package/lib/styles/copy.d.ts +0 -1
  95. package/lib/styles/copy.js +2 -17
  96. package/lib/styles/custom-scrollbar.d.ts +1 -2
  97. package/lib/styles/custom-scrollbar.js +3 -18
  98. package/lib/styles/date-picker.d.ts +1 -5
  99. package/lib/styles/date-picker.js +5 -35
  100. package/lib/styles/file-upload.d.ts +1 -2
  101. package/lib/styles/file-upload.js +0 -15
  102. package/lib/styles/icon.d.ts +2 -6
  103. package/lib/styles/icon.js +1 -26
  104. package/lib/styles/input.d.ts +1 -6
  105. package/lib/styles/input.js +3 -53
  106. package/lib/styles/modal.d.ts +1 -2
  107. package/lib/styles/modal.js +3 -23
  108. package/lib/styles/option.d.ts +2 -6
  109. package/lib/styles/option.js +1 -11
  110. package/lib/styles/popup.d.ts +1 -2
  111. package/lib/styles/popup.js +4 -25
  112. package/lib/styles/radio.d.ts +0 -1
  113. package/lib/styles/radio.js +1 -15
  114. package/lib/styles/resize-observer.d.ts +1 -2
  115. package/lib/styles/resize-observer.js +0 -7
  116. package/lib/styles/scroll-container.d.ts +1 -2
  117. package/lib/styles/scroll-container.js +0 -19
  118. package/lib/styles/select-base.d.ts +0 -1
  119. package/lib/styles/select-base.js +9 -70
  120. package/lib/styles/select.d.ts +1 -2
  121. package/lib/styles/select.js +2 -14
  122. package/lib/styles/step.d.ts +1 -2
  123. package/lib/styles/step.js +0 -4
  124. package/lib/styles/switch.d.ts +0 -1
  125. package/lib/styles/switch.js +2 -20
  126. package/lib/styles/tab.d.ts +1 -2
  127. package/lib/styles/tab.js +0 -5
  128. package/lib/styles/tabs.d.ts +3 -11
  129. package/lib/styles/tabs.js +4 -47
  130. package/lib/styles/tag.d.ts +1 -5
  131. package/lib/styles/tag.js +2 -31
  132. package/lib/styles/textarea.d.ts +1 -2
  133. package/lib/styles/textarea.js +0 -44
  134. package/lib/styles/tooltip.d.ts +0 -1
  135. package/lib/styles/tooltip.js +0 -10
  136. package/lib/styles/wizard.d.ts +5 -21
  137. package/lib/styles/wizard.js +5 -85
  138. package/lib/types/common.d.ts +0 -2
  139. package/lib/types/common.js +0 -12
  140. package/lib/utils/calendar-utils.d.ts +4 -21
  141. package/lib/utils/calendar-utils.js +4 -177
  142. package/lib/utils/custom-scrollbar.d.ts +6 -20
  143. package/lib/utils/custom-scrollbar.js +1 -45
  144. package/lib/utils/flavor-sheet.d.ts +0 -1
  145. package/lib/utils/flavor-sheet.js +0 -23
  146. package/lib/utils/icon-registry.d.ts +0 -2
  147. package/lib/utils/icon-registry.js +1 -107
  148. package/lib/utils/icons.d.ts +0 -1
  149. package/lib/utils/icons.js +0 -9
  150. package/lib/utils/loader-registry.d.ts +0 -1
  151. package/lib/utils/loader-registry.js +0 -32
  152. package/lib/utils/locale.d.ts +2 -23
  153. package/lib/utils/locale.js +0 -86
  154. package/lib/utils/mobile.d.ts +0 -1
  155. package/lib/utils/mobile.js +0 -17
  156. package/lib/utils/number-format.d.ts +0 -2
  157. package/lib/utils/number-format.js +2 -65
  158. package/lib/utils/positioning.d.ts +0 -2
  159. package/lib/utils/positioning.js +0 -54
  160. package/lib/utils/property-capture.d.ts +5 -27
  161. package/lib/utils/property-capture.js +0 -108
  162. package/lib/utils/property-manager.d.ts +0 -1
  163. package/lib/utils/property-manager.js +0 -70
  164. package/lib/utils/resize-observer.d.ts +0 -1
  165. package/lib/utils/resize-observer.js +0 -36
  166. package/lib/utils/scroll-lock.d.ts +4 -11
  167. package/lib/utils/scroll-lock.js +0 -107
  168. package/lib/utils/styles.d.ts +0 -1
  169. package/lib/utils/styles.js +0 -24
  170. package/lib/version.d.ts +1 -2
  171. package/lib/version.js +1 -11
  172. package/package.json +2 -2
  173. package/lib/base/ty-component.d.ts.map +0 -1
  174. package/lib/base/ty-component.js.map +0 -1
  175. package/lib/components/button.d.ts.map +0 -1
  176. package/lib/components/button.js.map +0 -1
  177. package/lib/components/calendar-month.d.ts.map +0 -1
  178. package/lib/components/calendar-month.js.map +0 -1
  179. package/lib/components/calendar-navigation.d.ts.map +0 -1
  180. package/lib/components/calendar-navigation.js.map +0 -1
  181. package/lib/components/calendar.d.ts.map +0 -1
  182. package/lib/components/calendar.js.map +0 -1
  183. package/lib/components/checkbox.d.ts.map +0 -1
  184. package/lib/components/checkbox.js.map +0 -1
  185. package/lib/components/copy.d.ts.map +0 -1
  186. package/lib/components/copy.js.map +0 -1
  187. package/lib/components/date-picker.d.ts.map +0 -1
  188. package/lib/components/date-picker.js.map +0 -1
  189. package/lib/components/file-upload.d.ts.map +0 -1
  190. package/lib/components/file-upload.js.map +0 -1
  191. package/lib/components/icon.d.ts.map +0 -1
  192. package/lib/components/icon.js.map +0 -1
  193. package/lib/components/input.d.ts.map +0 -1
  194. package/lib/components/input.js.map +0 -1
  195. package/lib/components/modal.d.ts.map +0 -1
  196. package/lib/components/modal.js.map +0 -1
  197. package/lib/components/option.d.ts.map +0 -1
  198. package/lib/components/option.js.map +0 -1
  199. package/lib/components/popup.d.ts.map +0 -1
  200. package/lib/components/popup.js.map +0 -1
  201. package/lib/components/radio.d.ts.map +0 -1
  202. package/lib/components/radio.js.map +0 -1
  203. package/lib/components/resize-observer.d.ts.map +0 -1
  204. package/lib/components/resize-observer.js.map +0 -1
  205. package/lib/components/scroll-container.d.ts.map +0 -1
  206. package/lib/components/scroll-container.js.map +0 -1
  207. package/lib/components/select.d.ts.map +0 -1
  208. package/lib/components/select.js.map +0 -1
  209. package/lib/components/selected-tags.d.ts.map +0 -1
  210. package/lib/components/selected-tags.js.map +0 -1
  211. package/lib/components/step.d.ts.map +0 -1
  212. package/lib/components/step.js.map +0 -1
  213. package/lib/components/switch.d.ts.map +0 -1
  214. package/lib/components/switch.js.map +0 -1
  215. package/lib/components/tab.d.ts.map +0 -1
  216. package/lib/components/tab.js.map +0 -1
  217. package/lib/components/tabs.d.ts.map +0 -1
  218. package/lib/components/tabs.js.map +0 -1
  219. package/lib/components/tag.d.ts.map +0 -1
  220. package/lib/components/tag.js.map +0 -1
  221. package/lib/components/textarea.d.ts.map +0 -1
  222. package/lib/components/textarea.js.map +0 -1
  223. package/lib/components/tooltip.d.ts.map +0 -1
  224. package/lib/components/tooltip.js.map +0 -1
  225. package/lib/components/wizard.d.ts.map +0 -1
  226. package/lib/components/wizard.js.map +0 -1
  227. package/lib/icons/fontawesome/brands.d.ts.map +0 -1
  228. package/lib/icons/fontawesome/brands.js.map +0 -1
  229. package/lib/icons/fontawesome/regular.d.ts.map +0 -1
  230. package/lib/icons/fontawesome/regular.js.map +0 -1
  231. package/lib/icons/fontawesome/solid.d.ts.map +0 -1
  232. package/lib/icons/fontawesome/solid.js.map +0 -1
  233. package/lib/icons/heroicons/micro.d.ts.map +0 -1
  234. package/lib/icons/heroicons/micro.js.map +0 -1
  235. package/lib/icons/heroicons/mini.d.ts.map +0 -1
  236. package/lib/icons/heroicons/mini.js.map +0 -1
  237. package/lib/icons/heroicons/outline.d.ts.map +0 -1
  238. package/lib/icons/heroicons/outline.js.map +0 -1
  239. package/lib/icons/heroicons/solid.d.ts.map +0 -1
  240. package/lib/icons/heroicons/solid.js.map +0 -1
  241. package/lib/icons/lucide.d.ts.map +0 -1
  242. package/lib/icons/lucide.js.map +0 -1
  243. package/lib/icons/material/filled.d.ts.map +0 -1
  244. package/lib/icons/material/filled.js.map +0 -1
  245. package/lib/icons/material/outlined.d.ts.map +0 -1
  246. package/lib/icons/material/outlined.js.map +0 -1
  247. package/lib/icons/material/round.d.ts.map +0 -1
  248. package/lib/icons/material/round.js.map +0 -1
  249. package/lib/icons/material/sharp.d.ts.map +0 -1
  250. package/lib/icons/material/sharp.js.map +0 -1
  251. package/lib/icons/material/two-tone.d.ts.map +0 -1
  252. package/lib/icons/material/two-tone.js.map +0 -1
  253. package/lib/index.d.ts.map +0 -1
  254. package/lib/index.js.map +0 -1
  255. package/lib/styles/button.d.ts.map +0 -1
  256. package/lib/styles/button.js.map +0 -1
  257. package/lib/styles/calendar-month.d.ts.map +0 -1
  258. package/lib/styles/calendar-month.js.map +0 -1
  259. package/lib/styles/calendar-navigation.d.ts.map +0 -1
  260. package/lib/styles/calendar-navigation.js.map +0 -1
  261. package/lib/styles/calendar.d.ts.map +0 -1
  262. package/lib/styles/calendar.js.map +0 -1
  263. package/lib/styles/checkbox.d.ts.map +0 -1
  264. package/lib/styles/checkbox.js.map +0 -1
  265. package/lib/styles/copy.d.ts.map +0 -1
  266. package/lib/styles/copy.js.map +0 -1
  267. package/lib/styles/custom-scrollbar.d.ts.map +0 -1
  268. package/lib/styles/custom-scrollbar.js.map +0 -1
  269. package/lib/styles/date-picker.d.ts.map +0 -1
  270. package/lib/styles/date-picker.js.map +0 -1
  271. package/lib/styles/file-upload.d.ts.map +0 -1
  272. package/lib/styles/file-upload.js.map +0 -1
  273. package/lib/styles/icon.d.ts.map +0 -1
  274. package/lib/styles/icon.js.map +0 -1
  275. package/lib/styles/input.d.ts.map +0 -1
  276. package/lib/styles/input.js.map +0 -1
  277. package/lib/styles/modal.d.ts.map +0 -1
  278. package/lib/styles/modal.js.map +0 -1
  279. package/lib/styles/multiselect.d.ts +0 -8
  280. package/lib/styles/multiselect.d.ts.map +0 -1
  281. package/lib/styles/multiselect.js +0 -849
  282. package/lib/styles/multiselect.js.map +0 -1
  283. package/lib/styles/option.d.ts.map +0 -1
  284. package/lib/styles/option.js.map +0 -1
  285. package/lib/styles/popup.d.ts.map +0 -1
  286. package/lib/styles/popup.js.map +0 -1
  287. package/lib/styles/radio.d.ts.map +0 -1
  288. package/lib/styles/radio.js.map +0 -1
  289. package/lib/styles/resize-observer.d.ts.map +0 -1
  290. package/lib/styles/resize-observer.js.map +0 -1
  291. package/lib/styles/scroll-container.d.ts.map +0 -1
  292. package/lib/styles/scroll-container.js.map +0 -1
  293. package/lib/styles/select-base.d.ts.map +0 -1
  294. package/lib/styles/select-base.js.map +0 -1
  295. package/lib/styles/select.d.ts.map +0 -1
  296. package/lib/styles/select.js.map +0 -1
  297. package/lib/styles/step.d.ts.map +0 -1
  298. package/lib/styles/step.js.map +0 -1
  299. package/lib/styles/switch.d.ts.map +0 -1
  300. package/lib/styles/switch.js.map +0 -1
  301. package/lib/styles/tab.d.ts.map +0 -1
  302. package/lib/styles/tab.js.map +0 -1
  303. package/lib/styles/tabs.d.ts.map +0 -1
  304. package/lib/styles/tabs.js.map +0 -1
  305. package/lib/styles/tag.d.ts.map +0 -1
  306. package/lib/styles/tag.js.map +0 -1
  307. package/lib/styles/textarea.d.ts.map +0 -1
  308. package/lib/styles/textarea.js.map +0 -1
  309. package/lib/styles/tooltip.d.ts.map +0 -1
  310. package/lib/styles/tooltip.js.map +0 -1
  311. package/lib/styles/wizard.d.ts.map +0 -1
  312. package/lib/styles/wizard.js.map +0 -1
  313. package/lib/types/common.d.ts.map +0 -1
  314. package/lib/types/common.js.map +0 -1
  315. package/lib/utils/calendar-utils.d.ts.map +0 -1
  316. package/lib/utils/calendar-utils.js.map +0 -1
  317. package/lib/utils/custom-scrollbar.d.ts.map +0 -1
  318. package/lib/utils/custom-scrollbar.js.map +0 -1
  319. package/lib/utils/flavor-sheet.d.ts.map +0 -1
  320. package/lib/utils/flavor-sheet.js.map +0 -1
  321. package/lib/utils/icon-registry.d.ts.map +0 -1
  322. package/lib/utils/icon-registry.js.map +0 -1
  323. package/lib/utils/icons.d.ts.map +0 -1
  324. package/lib/utils/icons.js.map +0 -1
  325. package/lib/utils/loader-registry.d.ts.map +0 -1
  326. package/lib/utils/loader-registry.js.map +0 -1
  327. package/lib/utils/locale.d.ts.map +0 -1
  328. package/lib/utils/locale.js.map +0 -1
  329. package/lib/utils/mobile.d.ts.map +0 -1
  330. package/lib/utils/mobile.js.map +0 -1
  331. package/lib/utils/number-format.d.ts.map +0 -1
  332. package/lib/utils/number-format.js.map +0 -1
  333. package/lib/utils/parse-boolean.d.ts +0 -39
  334. package/lib/utils/parse-boolean.d.ts.map +0 -1
  335. package/lib/utils/parse-boolean.js +0 -58
  336. package/lib/utils/parse-boolean.js.map +0 -1
  337. package/lib/utils/positioning.d.ts.map +0 -1
  338. package/lib/utils/positioning.js.map +0 -1
  339. package/lib/utils/property-capture.d.ts.map +0 -1
  340. package/lib/utils/property-capture.js.map +0 -1
  341. package/lib/utils/property-manager.d.ts.map +0 -1
  342. package/lib/utils/property-manager.js.map +0 -1
  343. package/lib/utils/resize-observer.d.ts.map +0 -1
  344. package/lib/utils/resize-observer.js.map +0 -1
  345. package/lib/utils/scroll-lock.d.ts.map +0 -1
  346. package/lib/utils/scroll-lock.js.map +0 -1
  347. package/lib/utils/styles.d.ts.map +0 -1
  348. package/lib/utils/styles.js.map +0 -1
  349. package/lib/version.d.ts.map +0 -1
  350. package/lib/version.js.map +0 -1
@@ -1,51 +1,3 @@
1
- /**
2
- * TyDatePicker Web Component
3
- * PORTED FROM: cljs/ty/components/date_picker.cljs
4
- *
5
- * A date picker component with read-only input and calendar dropdown.
6
- * Supports date-only and date+time modes with smart time input.
7
- *
8
- * Architecture:
9
- * - Read-only input stub (displays formatted date)
10
- * - Calendar dropdown (modal dialog with ty-calendar)
11
- * - Optional time input (with smart digit navigation)
12
- * - Form participation via ElementInternals
13
- * - UTC output, local display
14
- *
15
- * Features:
16
- * - Date selection with calendar dropdown
17
- * - Optional time input with smart navigation
18
- * - Form integration (works with FormData)
19
- * - UTC value output for server communication
20
- * - Localized display formatting (Intl API)
21
- * - Clearable with clear button
22
- * - Keyboard navigation (Escape to close)
23
- * - Outside click handling
24
- *
25
- * @example
26
- * ```html
27
- * <!-- Basic date picker -->
28
- * <ty-date-picker
29
- * label="Select Date"
30
- * placeholder="Choose a date">
31
- * </ty-date-picker>
32
- *
33
- * <!-- With time -->
34
- * <ty-date-picker
35
- * with-time="true"
36
- * label="Select Date & Time">
37
- * </ty-date-picker>
38
- *
39
- * <!-- Form integration -->
40
- * <form>
41
- * <ty-date-picker name="booking-date"></ty-date-picker>
42
- * <button type="submit">Submit</button>
43
- * </form>
44
- *
45
- * <!-- Pre-filled value (UTC) -->
46
- * <ty-date-picker value="2024-09-21T08:30:00.000Z"></ty-date-picker>
47
- * ```
48
- */
49
1
  import { ensureStyles } from '../utils/styles.js';
50
2
  import { syncCustomFlavorSheet } from '../utils/flavor-sheet.js';
51
3
  import { datePickerStyles, datePickerCustomFlavorCss } from '../styles/date-picker.js';
@@ -54,16 +6,9 @@ import { computeAnchoredPosition } from '../utils/positioning.js';
54
6
  import { getEffectiveLocale, observeLocaleChanges } from '../utils/locale.js';
55
7
  import { isMobileTouch } from '../utils/mobile.js';
56
8
  import { TyComponent } from '../base/ty-component.js';
57
- // ============================================================================
58
- // Constants
59
- // ============================================================================
60
9
  const CALENDAR_ICON_SVG = `<svg stroke='currentColor' fill='none' stroke-width='2' viewBox='0 0 24 24' width='16' height='16' xmlns='http://www.w3.org/2000/svg'><rect x='3' y='4' width='18' height='18' rx='2' ry='2'></rect><line x1='16' y1='2' x2='16' y2='6'></line><line x1='8' y1='2' x2='8' y2='6'></line><line x1='3' y1='10' x2='21' y2='10'></line></svg>`;
61
10
  const CLEAR_ICON_SVG = `<svg stroke='currentColor' fill='none' stroke-width='2' viewBox='0 0 24 24' width='14' height='14' xmlns='http://www.w3.org/2000/svg'><line x1='18' y1='6' x2='6' y2='18'></line><line x1='6' y1='6' x2='18' y2='18'></line></svg>`;
62
11
  const SCHEDULE_ICON_SVG = `<svg stroke='currentColor' fill='none' stroke-width='2' viewBox='0 0 24 24' width='16' height='16' xmlns='http://www.w3.org/2000/svg'><circle cx='12' cy='12' r='10'></circle><polyline points='12,6 12,12 16,14'></polyline></svg>`;
63
- // Stable per-instance id (survives across full-rebuild re-renders, unlike a
64
- // fresh Math.random() per render) — used to wire the stub's aria-labelledby
65
- // to the label element it's paired with. Same pattern as select.ts's
66
- // getElementHash.
67
12
  const elementIds = new WeakMap();
68
13
  let elementIdCounter = 0;
69
14
  function getElementHash(element) {
@@ -74,86 +19,43 @@ function getElementHash(element) {
74
19
  }
75
20
  return id;
76
21
  }
77
- // ============================================================================
78
- // Helper Functions - Date Parsing & Conversion
79
- // ============================================================================
80
- /**
81
- * Parse ANY input format into year/month/day/hour/minute components.
82
- * Accepts:
83
- * - UTC strings: '2024-09-21T08:30:00Z' or '2024-09-21T08:30:00.000Z'
84
- * - Datetime-local: '2024-09-21T10:30'
85
- * - Date only: '2024-09-21'
86
- * - Timestamps: milliseconds since epoch
87
- * - With timezone: '2024-09-21T10:30:00+02:00'
88
- *
89
- * Always extracts components in LOCAL timezone for display/editing.
90
- *
91
- * PORTED FROM: parse-value in date_picker.cljs
92
- */
93
22
  function parseValue(value, withTime) {
94
23
  if (!value)
95
24
  return null;
96
25
  let dateObj = null;
97
26
  if (typeof value === 'string') {
98
- // Date-only format: YYYY-MM-DD
99
27
  if (/^\d{4}-\d{2}-\d{2}$/.test(value)) {
100
28
  const [yearStr, monthStr, dayStr] = value.split('-');
101
29
  const year = parseInt(yearStr, 10);
102
- const month = parseInt(monthStr, 10) - 1; // 0-based for JS Date
30
+ const month = parseInt(monthStr, 10) - 1;
103
31
  const day = parseInt(dayStr, 10);
104
- // Create at midnight local time
105
32
  dateObj = new Date(year, month, day, 0, 0, 0, 0);
106
33
  }
107
34
  else {
108
- // Let JS Date handle datetime-local, UTC, with timezone
109
35
  dateObj = new Date(value);
110
36
  }
111
37
  }
112
38
  else if (typeof value === 'number') {
113
- // Numeric inputs (milliseconds)
114
39
  dateObj = new Date(value);
115
40
  }
116
41
  if (!dateObj || isNaN(dateObj.getTime())) {
117
42
  return null;
118
43
  }
119
- // Extract components in LOCAL timezone for display
120
44
  const components = {
121
45
  year: dateObj.getFullYear(),
122
- month: dateObj.getMonth() + 1, // Convert to 1-based
46
+ month: dateObj.getMonth() + 1,
123
47
  day: dateObj.getDate(),
124
48
  hour: withTime ? dateObj.getHours() : 0,
125
49
  minute: withTime ? dateObj.getMinutes() : 0,
126
50
  };
127
51
  return components;
128
52
  }
129
- /**
130
- * Convert internal components to Date object.
131
- *
132
- * PORTED FROM: components->date-object in date_picker.cljs
133
- */
134
53
  function componentsToDateObject(components) {
135
54
  const { year, month, day, hour, minute } = components;
136
55
  if (!year || !month || !day)
137
56
  return null;
138
- // Create local date
139
- return new Date(year, month - 1, // Convert to 0-based
140
- day, hour || 0, minute || 0, 0, // seconds
141
- 0 // milliseconds
142
- );
57
+ return new Date(year, month - 1, day, hour || 0, minute || 0, 0, 0);
143
58
  }
144
- /**
145
- * Convert internal components to UTC ISO 8601 format.
146
- *
147
- * For date+time mode: Outputs full UTC timestamp with milliseconds
148
- * Example: '2024-09-21T08:30:00.000Z'
149
- *
150
- * For date-only mode: Outputs UTC timestamp at midnight local time
151
- * Example: '2024-09-20T22:00:00.000Z' (midnight Sept 21 CEST = 10pm Sept 20 UTC)
152
- *
153
- * Always returns UTC to ensure unambiguous server communication.
154
- *
155
- * PORTED FROM: components->output-value in date_picker.cljs
156
- */
157
59
  function componentsToOutputValue(components) {
158
60
  if (!components.year || !components.month || !components.day) {
159
61
  return null;
@@ -161,18 +63,6 @@ function componentsToOutputValue(components) {
161
63
  const dateObj = componentsToDateObject(components);
162
64
  return dateObj ? dateObj.toISOString() : null;
163
65
  }
164
- /**
165
- * Convert internal components to local datetime-local format.
166
- *
167
- * For date+time mode: Outputs local datetime without timezone
168
- * Example: '2024-09-21T10:30'
169
- *
170
- * For date-only mode: Outputs date only
171
- * Example: '2024-09-21'
172
- *
173
- * This format matches HTML5 <input type="datetime-local"> and is useful
174
- * for setting other inputs or displaying local time without timezone conversion.
175
- */
176
66
  function componentsToLocalValue(components, withTime) {
177
67
  if (!components.year || !components.month || !components.day) {
178
68
  return null;
@@ -189,11 +79,6 @@ function componentsToLocalValue(components, withTime) {
189
79
  return `${year}-${month}-${day}`;
190
80
  }
191
81
  }
192
- /**
193
- * Format components for display in input using Intl API.
194
- *
195
- * PORTED FROM: format-display-value in date_picker.cljs
196
- */
197
82
  function formatDisplayValue(components, formatType, locale, withTime) {
198
83
  if (!components.year || !components.month || !components.day) {
199
84
  return null;
@@ -204,21 +89,12 @@ function formatDisplayValue(components, formatType, locale, withTime) {
204
89
  const options = {
205
90
  dateStyle: formatType || 'long',
206
91
  };
207
- // Add time styling if with-time is enabled
208
92
  if (withTime) {
209
93
  options.timeStyle = 'short';
210
94
  }
211
95
  const formatter = new Intl.DateTimeFormat(locale, options);
212
96
  return formatter.format(dateObj);
213
97
  }
214
- // ============================================================================
215
- // Helper Functions - Time Input
216
- // ============================================================================
217
- /**
218
- * Parse hour and minute from raw digits (4 chars: "HHmm")
219
- *
220
- * PORTED FROM: parse-time-components in date_picker.cljs
221
- */
222
98
  function parseTimeComponents(rawDigits) {
223
99
  if (!rawDigits || rawDigits.length !== 4)
224
100
  return null;
@@ -231,99 +107,60 @@ function parseTimeComponents(rawDigits) {
231
107
  }
232
108
  return { hour, minute };
233
109
  }
234
- /**
235
- * Validate if digit is valid for given position
236
- * Position 0: first hour digit (0-2)
237
- * Position 1: second hour digit (0-9, but 0-3 if first is 2)
238
- * Position 3: first minute digit (0-5)
239
- * Position 4: second minute digit (0-9)
240
- *
241
- * PORTED FROM: validate-time-digit in date_picker.cljs
242
- */
243
110
  function validateTimeDigit(digit, position, currentDigits) {
244
111
  switch (position) {
245
112
  case 0:
246
- return digit <= 2; // First hour digit: 0-2
113
+ return digit <= 2;
247
114
  case 1: {
248
115
  const firstHour = parseInt(currentDigits[0], 10);
249
- return firstHour === 2 ? digit <= 3 : true; // If hour starts with 2, max 23
116
+ return firstHour === 2 ? digit <= 3 : true;
250
117
  }
251
118
  case 3:
252
- return digit <= 5; // First minute digit: 0-5
119
+ return digit <= 5;
253
120
  case 4:
254
- return true; // Second minute digit: 0-9
121
+ return true;
255
122
  default:
256
123
  return false;
257
124
  }
258
125
  }
259
- /**
260
- * Format hour and minute into "HH:mm" display
261
- *
262
- * PORTED FROM: format-time-display in date_picker.cljs
263
- */
264
126
  function formatTimeDisplay(hour, minute) {
265
127
  const hourStr = hour.toString().padStart(2, '0');
266
128
  const minuteStr = minute.toString().padStart(2, '0');
267
129
  return `${hourStr}:${minuteStr}`;
268
130
  }
269
- /**
270
- * Find next editable position, skipping delimiter at position 2
271
- *
272
- * PORTED FROM: find-next-editable-position in date_picker.cljs
273
- */
274
131
  function findNextEditablePosition(currentPos) {
275
132
  switch (currentPos) {
276
- case 0: return 1; // 0 -> 1 (within hour)
277
- case 1: return 3; // 1 -> 3 (skip delimiter, go to minute)
278
- case 3: return 4; // 3 -> 4 (within minute)
279
- case 4: return 5; // 4 -> 5 (after last digit)
280
- case 5: return 5; // 5 -> 5 (stay at end)
133
+ case 0: return 1;
134
+ case 1: return 3;
135
+ case 3: return 4;
136
+ case 4: return 5;
137
+ case 5: return 5;
281
138
  default: return currentPos;
282
139
  }
283
140
  }
284
- /**
285
- * Find previous editable position, skipping delimiter at position 2
286
- *
287
- * PORTED FROM: find-prev-editable-position in date_picker.cljs
288
- */
289
141
  function findPrevEditablePosition(currentPos) {
290
142
  switch (currentPos) {
291
- case 5: return 4; // 5 -> 4 (from after last digit)
292
- case 4: return 3; // 4 -> 3 (within minute)
293
- case 3: return 1; // 3 -> 1 (skip delimiter, go to hour)
294
- case 1: return 0; // 1 -> 0 (within hour)
295
- case 0: return 0; // 0 -> 0 (stay at start)
143
+ case 5: return 4;
144
+ case 4: return 3;
145
+ case 3: return 1;
146
+ case 1: return 0;
147
+ case 0: return 0;
296
148
  default: return currentPos;
297
149
  }
298
150
  }
299
- /**
300
- * Convert internal position (0,1,3,4) to raw digits index (0,1,2,3)
301
- *
302
- * PORTED FROM: position->raw-digit-index in date_picker.cljs
303
- */
304
151
  function positionToRawDigitIndex(internalPos) {
305
152
  switch (internalPos) {
306
- case 0: return 0; // Position 0 → raw digit 0 (first hour)
307
- case 1: return 1; // Position 1 → raw digit 1 (second hour)
308
- case 3: return 2; // Position 3 → raw digit 2 (first minute)
309
- case 4: return 3; // Position 4 → raw digit 3 (second minute)
153
+ case 0: return 0;
154
+ case 1: return 1;
155
+ case 3: return 2;
156
+ case 4: return 3;
310
157
  default: return 0;
311
158
  }
312
159
  }
313
- // ============================================================================
314
- // TimeInput Helper Class
315
- // ============================================================================
316
- /**
317
- * TimeInput manages the state and behavior of the time input element.
318
- * Handles smart cursor navigation, digit replacement, and validation.
319
- *
320
- * PORTED FROM: Time input state management functions in date_picker.cljs
321
- */
322
160
  class TimeInput {
323
161
  constructor(element, datePickerElement, hour = 0, minute = 0) {
324
162
  this.element = element;
325
163
  this.datePickerElement = datePickerElement;
326
- // Initialize state
327
164
  const display = formatTimeDisplay(hour, minute);
328
165
  const rawDigits = hour.toString().padStart(2, '0') + minute.toString().padStart(2, '0');
329
166
  this.state = {
@@ -333,23 +170,15 @@ class TimeInput {
333
170
  displayValue: display,
334
171
  rawDigits,
335
172
  };
336
- // Set initial value
337
173
  this.element.value = display;
338
- // Bind event handlers
339
174
  this.setupEventListeners();
340
175
  }
341
- /**
342
- * Setup all event listeners for the time input
343
- */
344
176
  setupEventListeners() {
345
177
  this.element.addEventListener('keydown', (e) => this.handleKeyDown(e));
346
178
  this.element.addEventListener('input', (e) => this.handleInput(e));
347
179
  this.element.addEventListener('click', () => this.handleClick());
348
180
  this.element.addEventListener('focus', () => this.handleFocus());
349
181
  }
350
- /**
351
- * Handle keydown events
352
- */
353
182
  handleKeyDown(event) {
354
183
  const key = event.key;
355
184
  switch (key) {
@@ -374,89 +203,52 @@ class TimeInput {
374
203
  this.updateState({ caretPosition: 5 });
375
204
  break;
376
205
  case 'Tab':
377
- // Allow default tab behavior
378
206
  break;
379
207
  default:
380
- // Handle digit input
381
208
  if (/^\d$/.test(key)) {
382
209
  this.handleDigitInput(event, parseInt(key, 10));
383
210
  }
384
211
  break;
385
212
  }
386
213
  }
387
- /**
388
- * Handle input events (prevent default browser input)
389
- */
390
214
  handleInput(event) {
391
215
  event.preventDefault();
392
216
  }
393
- /**
394
- * Handle click events - position cursor at first digit
395
- */
396
217
  handleClick() {
397
218
  this.updateState({ caretPosition: 0 });
398
219
  }
399
- /**
400
- * Handle focus events - position cursor at first digit
401
- */
402
220
  handleFocus() {
403
221
  this.updateState({ caretPosition: 0 });
404
222
  }
405
- /**
406
- * Handle arrow right - move to next editable position
407
- *
408
- * PORTED FROM: handle-time-arrow-right! in date_picker.cljs
409
- */
410
223
  handleArrowRight(event) {
411
224
  event.preventDefault();
412
225
  const nextPos = findNextEditablePosition(this.state.caretPosition);
413
226
  this.updateState({ caretPosition: nextPos });
414
227
  }
415
- /**
416
- * Handle arrow left - move to previous editable position
417
- *
418
- * PORTED FROM: handle-time-arrow-left! in date_picker.cljs
419
- */
420
228
  handleArrowLeft(event) {
421
229
  event.preventDefault();
422
230
  const prevPos = findPrevEditablePosition(this.state.caretPosition);
423
231
  this.updateState({ caretPosition: prevPos });
424
232
  }
425
- /**
426
- * Handle digit input - replace digit at cursor position
427
- *
428
- * PORTED FROM: handle-time-digit-input! in date_picker.cljs
429
- */
430
233
  handleDigitInput(event, digit) {
431
234
  event.preventDefault();
432
235
  const currentPos = this.state.caretPosition;
433
- // Only process if at editable position and digit is valid
434
236
  if (![0, 1, 3, 4].includes(currentPos))
435
237
  return;
436
238
  if (!validateTimeDigit(digit, currentPos, this.state.rawDigits))
437
239
  return;
438
- // Replace digit at current position
439
240
  const newState = this.replaceDigitAtPosition(currentPos, digit);
440
241
  if (!newState)
441
242
  return;
442
- // Move to next position
443
243
  const nextPos = findNextEditablePosition(currentPos);
444
244
  this.updateState({ ...newState, caretPosition: nextPos });
445
- // Notify date picker of time change
446
245
  this.notifyTimeChange();
447
246
  }
448
- /**
449
- * Handle backspace - zero digit and move back
450
- *
451
- * PORTED FROM: handle-time-backspace! in date_picker.cljs
452
- */
453
247
  handleBackspace(event) {
454
248
  event.preventDefault();
455
249
  const currentPos = this.state.caretPosition;
456
- // Can't go back from position 0
457
250
  if (currentPos === 0)
458
251
  return;
459
- // Find target position to zero
460
252
  const targetPos = currentPos === 1 ? 0 :
461
253
  currentPos === 3 ? 1 :
462
254
  currentPos === 4 ? 3 :
@@ -467,15 +259,9 @@ class TimeInput {
467
259
  this.updateState({ ...newState, caretPosition: targetPos });
468
260
  this.notifyTimeChange();
469
261
  }
470
- /**
471
- * Handle delete - zero digit at current position
472
- *
473
- * PORTED FROM: handle-time-delete! in date_picker.cljs
474
- */
475
262
  handleDelete(event) {
476
263
  event.preventDefault();
477
264
  const currentPos = this.state.caretPosition;
478
- // Only at editable positions
479
265
  if (![0, 1, 3, 4].includes(currentPos))
480
266
  return;
481
267
  const newState = this.zeroDigitAtPosition(currentPos);
@@ -484,11 +270,6 @@ class TimeInput {
484
270
  this.updateState({ ...newState, caretPosition: currentPos });
485
271
  this.notifyTimeChange();
486
272
  }
487
- /**
488
- * Replace digit at specific position
489
- *
490
- * PORTED FROM: replace-digit-at-position in date_picker.cljs
491
- */
492
273
  replaceDigitAtPosition(position, newDigit) {
493
274
  const rawIndex = positionToRawDigitIndex(position);
494
275
  const digitsArray = this.state.rawDigits.split('');
@@ -504,24 +285,12 @@ class TimeInput {
504
285
  displayValue: formatTimeDisplay(parsed.hour, parsed.minute),
505
286
  };
506
287
  }
507
- /**
508
- * Zero digit at specific position
509
- *
510
- * PORTED FROM: zero-digit-at-position in date_picker.cljs
511
- */
512
288
  zeroDigitAtPosition(position) {
513
289
  return this.replaceDigitAtPosition(position, 0);
514
290
  }
515
- /**
516
- * Update internal state and refresh display
517
- *
518
- * PORTED FROM: update-time-input-state! in date_picker.cljs
519
- */
520
291
  updateState(updates) {
521
292
  this.state = { ...this.state, ...updates };
522
- // Update display value
523
293
  this.element.value = this.state.displayValue;
524
- // Set cursor position, mapping internal positions to DOM positions
525
294
  const caretPos = this.state.caretPosition;
526
295
  const actualPos = caretPos === 0 ? 0 :
527
296
  caretPos === 1 ? 1 :
@@ -531,24 +300,15 @@ class TimeInput {
531
300
  caretPos === 5 ? 5 : caretPos;
532
301
  this.element.setSelectionRange(actualPos, actualPos);
533
302
  }
534
- /**
535
- * Notify date picker of time change
536
- */
537
303
  notifyTimeChange() {
538
304
  this.datePickerElement.handleTimeInputChange(this.state.hour, this.state.minute);
539
305
  }
540
- /**
541
- * Get current time values
542
- */
543
306
  getTime() {
544
307
  return {
545
308
  hour: this.state.hour,
546
309
  minute: this.state.minute,
547
310
  };
548
311
  }
549
- /**
550
- * Update time from external source
551
- */
552
312
  setTime(hour, minute) {
553
313
  const display = formatTimeDisplay(hour, minute);
554
314
  const rawDigits = hour.toString().padStart(2, '0') + minute.toString().padStart(2, '0');
@@ -562,76 +322,43 @@ class TimeInput {
562
322
  this.element.value = display;
563
323
  }
564
324
  }
565
- // ============================================================================
566
- // TyDatePicker Custom Element
567
- // ============================================================================
568
325
  export class TyDatePicker extends TyComponent {
569
- // observedAttributes is auto-generated by TyComponent from properties config
570
326
  constructor() {
571
- super(); // TyComponent handles attachShadow and attachInternals
572
- // ============================================================================
573
- // INTERNAL STATE
574
- // ============================================================================
575
- // Internal state
327
+ super();
576
328
  this._state = {
577
329
  withTime: false,
578
330
  open: false,
579
331
  };
580
- // Reopen guard — prevents click event from reopening after pointerdown close
581
332
  this._closeTimestamp = 0;
582
333
  this._customFlavorSheet = null;
583
- // Initialize styles in shadow root
584
334
  const shadow = this.shadowRoot;
585
335
  ensureStyles(shadow, { css: datePickerStyles, id: 'ty-date-picker' });
586
336
  }
587
- // ==========================================================================
588
- // Lifecycle Hooks (TyComponent)
589
- // ==========================================================================
590
- /**
591
- * Called when component connects to DOM
592
- * TyComponent handles property capture automatically
593
- */
594
337
  onConnect() {
595
- // Initialize component state
596
338
  this.initializeState();
597
- // Initial render
598
339
  this.render();
599
- // Setup locale observer to watch for ancestor lang changes
600
340
  this._localeObserver = observeLocaleChanges(this, () => {
601
341
  this.render();
602
342
  });
603
343
  this._syncCustomFlavor();
604
344
  }
605
- /** Custom (non-built-in) flavors — see utils/flavor-sheet.ts. */
606
345
  _syncCustomFlavor() {
607
- // 'default' is this component's neutral: map it to a built-in so no
608
- // custom sheet is generated for it.
609
346
  const flavor = this.flavor === 'default' ? 'neutral' : this.flavor;
610
347
  this._customFlavorSheet = syncCustomFlavorSheet(this.shadowRoot, this._customFlavorSheet, flavor, ({ base }) => datePickerCustomFlavorCss(base));
611
348
  }
612
- /**
613
- * Called when component disconnects from DOM
614
- */
615
349
  onDisconnect() {
616
- // Cleanup locale observer
617
350
  if (this._localeObserver) {
618
351
  this._localeObserver();
619
352
  this._localeObserver = undefined;
620
353
  }
621
- // Cleanup event listeners and state
622
354
  this.cleanup();
623
355
  }
624
- /**
625
- * Called when properties change
626
- * Handle state updates BEFORE render
627
- */
628
356
  onPropertiesChanged(changes) {
629
357
  for (const { name, newValue } of changes) {
630
358
  switch (name) {
631
359
  case 'flavor':
632
360
  this._syncCustomFlavor();
633
361
  break;
634
- // Simple visual properties - just trigger re-render (handled by TyComponent)
635
362
  case 'size':
636
363
  case 'label':
637
364
  case 'placeholder':
@@ -640,30 +367,21 @@ export class TyDatePicker extends TyComponent {
640
367
  case 'clearable':
641
368
  case 'format':
642
369
  case 'locale':
643
- // These properties just affect rendering, no internal state to update
644
- // TyComponent will call render() automatically for visual properties
645
370
  break;
646
- // Complex properties
647
371
  case 'value': {
648
- // Parse the new value using current withTime setting
649
372
  const newComponents = parseValue(newValue, this._state.withTime);
650
- // If newComponents is null, CLEAR the state completely
651
373
  if (newComponents === null) {
652
- // Check if we actually have a date to clear
653
374
  const hasDate = this._state.year !== undefined ||
654
375
  this._state.month !== undefined ||
655
376
  this._state.day !== undefined;
656
377
  if (hasDate) {
657
- // Clear all date components, keeping only withTime and open flags
658
378
  this._state = {
659
379
  withTime: this._state.withTime,
660
380
  open: this._state.open,
661
- // year, month, day, hour, minute are now undefined
662
381
  };
663
382
  }
664
383
  break;
665
384
  }
666
- // Check if components actually changed
667
385
  const currentComponents = {
668
386
  year: this._state.year,
669
387
  month: this._state.month,
@@ -677,7 +395,6 @@ export class TyDatePicker extends TyComponent {
677
395
  newComponents?.hour !== currentComponents.hour ||
678
396
  newComponents?.minute !== currentComponents.minute;
679
397
  if (changed) {
680
- // Update state with new date components
681
398
  this._state = {
682
399
  ...this._state,
683
400
  ...newComponents,
@@ -686,13 +403,10 @@ export class TyDatePicker extends TyComponent {
686
403
  break;
687
404
  }
688
405
  case 'with-time': {
689
- // Update internal withTime flag
690
406
  const newWithTime = newValue;
691
407
  const oldWithTime = this._state.withTime;
692
408
  if (newWithTime !== oldWithTime) {
693
409
  this._state.withTime = newWithTime;
694
- // If we have an existing date, re-sync form value
695
- // (format changes based on withTime flag)
696
410
  const hasDate = this._state.year !== undefined &&
697
411
  this._state.month !== undefined &&
698
412
  this._state.day !== undefined;
@@ -705,20 +419,9 @@ export class TyDatePicker extends TyComponent {
705
419
  }
706
420
  }
707
421
  }
708
- // attributeChangedCallback removed - TyComponent handles all attribute changes via onPropertiesChanged
709
- /**
710
- * Get form value - returns UTC string from current state
711
- * TyComponent calls this automatically when value property changes
712
- */
713
422
  getFormValue() {
714
423
  return componentsToOutputValue(this.getComponents());
715
424
  }
716
- // ==========================================================================
717
- // Internal Helpers
718
- // ==========================================================================
719
- /**
720
- * Extract current date components from internal state
721
- */
722
425
  getComponents() {
723
426
  return {
724
427
  year: this._state.year,
@@ -728,34 +431,19 @@ export class TyDatePicker extends TyComponent {
728
431
  minute: this._state.minute,
729
432
  };
730
433
  }
731
- /**
732
- * Whether internal state has a complete date
733
- */
734
434
  hasDate() {
735
435
  return !!(this._state.year && this._state.month && this._state.day);
736
436
  }
737
- /**
738
- * Format current components for display using Intl API
739
- */
740
437
  getFormattedValue(components) {
741
438
  const c = components || this.getComponents();
742
439
  if (!c.year || !c.month || !c.day)
743
440
  return null;
744
441
  return formatDisplayValue(c, this.getProperty('format') || 'long', getEffectiveLocale(this, this.getProperty('locale')), this._state.withTime);
745
442
  }
746
- // ==========================================================================
747
- // State Management
748
- // ==========================================================================
749
- /**
750
- * Initialize component state from attributes
751
- *
752
- * PORTED FROM: init-component-state! in date_picker.cljs
753
- */
754
443
  initializeState() {
755
444
  const valueAttr = this.getProperty('value');
756
445
  const withTime = this.getProperty('with-time');
757
446
  const components = parseValue(valueAttr, withTime);
758
- // If components is null, initialize with empty state
759
447
  if (components === null) {
760
448
  this._state = {
761
449
  withTime,
@@ -769,34 +457,19 @@ export class TyDatePicker extends TyComponent {
769
457
  open: false,
770
458
  };
771
459
  }
772
- // Convert to UTC and sync to form
773
460
  this.syncFormValue();
774
461
  }
775
- /**
776
- * Update internal state
777
- *
778
- * PORTED FROM: update-component-state! in date_picker.cljs
779
- */
780
462
  updateState(updates, forceSync = false) {
781
463
  this._state = { ...this._state, ...updates };
782
- // STAGING: Only sync attributes if dialog is closed OR force-sync is true
783
- // This prevents re-renders during time input editing
784
464
  const shouldSync = forceSync || !this._state.open || !this._state.withTime;
785
465
  if (shouldSync) {
786
466
  this.syncFormValue();
787
467
  }
788
468
  }
789
- /**
790
- * Sync form value with current state
791
- * Compares before setting to prevent circular triggers
792
- */
793
469
  syncFormValue() {
794
470
  const utcValue = this.getFormValue();
795
471
  const currentValue = this.getProperty('value');
796
- // Only update property if value actually changed (prevents circular triggers)
797
472
  if (utcValue !== currentValue) {
798
- // Use property setter to maintain TyComponent lifecycle
799
- // This will automatically handle attribute sync and form value update
800
473
  if (utcValue) {
801
474
  this.setProperty('value', utcValue);
802
475
  }
@@ -804,17 +477,7 @@ export class TyDatePicker extends TyComponent {
804
477
  this.setProperty('value', null);
805
478
  }
806
479
  }
807
- // Form value sync handled automatically by TyComponent (formValue: true)
808
- }
809
- // handleValueChange removed - logic moved to onPropertiesChanged hook
810
- // ==========================================================================
811
- // Time Input Handling
812
- // ==========================================================================
813
- /**
814
- * Handle time input changes from TimeInput class
815
- *
816
- * PORTED FROM: handle-time-change! in date_picker.cljs
817
- */
480
+ }
818
481
  handleTimeInputChange(hour, minute) {
819
482
  if (!this.hasDate())
820
483
  return;
@@ -822,11 +485,6 @@ export class TyDatePicker extends TyComponent {
822
485
  this.updateState(components, true);
823
486
  this.emitChangeEvent(components, 'time-change');
824
487
  }
825
- /**
826
- * Emit change event
827
- *
828
- * PORTED FROM: emit-change-event! in date_picker.cljs
829
- */
830
488
  emitChangeEvent(components, source) {
831
489
  const utcValue = components ? componentsToOutputValue(components) : null;
832
490
  const localValue = components ? componentsToLocalValue(components, this._state.withTime) : null;
@@ -846,10 +504,6 @@ export class TyDatePicker extends TyComponent {
846
504
  });
847
505
  this.dispatchEvent(event);
848
506
  }
849
- // ==========================================================================
850
- // Public API (Properties) - Simple Accessors
851
- // ==========================================================================
852
- // String properties
853
507
  get size() { return this.getProperty('size'); }
854
508
  set size(v) { this.setProperty('size', v); }
855
509
  get flavor() { return this.getProperty('flavor'); }
@@ -864,7 +518,6 @@ export class TyDatePicker extends TyComponent {
864
518
  set format(v) { this.setProperty('format', v); }
865
519
  get locale() { return this.getProperty('locale'); }
866
520
  set locale(v) { this.setProperty('locale', v); }
867
- // Boolean properties
868
521
  get required() { return this.getProperty('required'); }
869
522
  set required(v) { this.setProperty('required', v); }
870
523
  get disabled() { return this.getProperty('disabled'); }
@@ -875,48 +528,23 @@ export class TyDatePicker extends TyComponent {
875
528
  set min(v) { this.setProperty('min', v ?? ''); }
876
529
  get max() { return this.getProperty('max'); }
877
530
  set max(v) { this.setProperty('max', v ?? ''); }
878
- // With-time property
879
531
  get withTime() { return this.getProperty('with-time'); }
880
532
  set withTime(v) { this.setProperty('with-time', v); }
881
- // Value property - Keep custom getter/setter for now (complex UTC parsing logic)
882
- /**
883
- * Get current value (UTC ISO string)
884
- */
885
533
  get value() {
886
534
  return this.getProperty('value') || null;
887
535
  }
888
- /**
889
- * Set value (UTC ISO string, Date object, or null)
890
- *
891
- * When set to null/undefined/empty string, the attribute is removed.
892
- * When set to a valid date, the attribute is set to ISO UTC string.
893
- */
894
536
  set value(val) {
895
537
  if (val === null || val === undefined || val === '') {
896
- this.setProperty('value', null); // TyComponent will remove attribute
538
+ this.setProperty('value', null);
897
539
  }
898
540
  else {
899
541
  const strValue = val instanceof Date ? val.toISOString() : val;
900
542
  this.setProperty('value', strValue);
901
543
  }
902
544
  }
903
- /**
904
- * Clear the date value programmatically WITH a change event (source:
905
- * 'clear'). Works regardless of the `clearable` attribute — lets any
906
- * external trigger (e.g. a button elsewhere in the page) clear the value
907
- * without needing the built-in clear button. Mirrors ty-select's clear().
908
- */
909
545
  clear() {
910
546
  this.clearValue();
911
547
  }
912
- // ==========================================================================
913
- // Rendering & DOM Management
914
- // ==========================================================================
915
- /**
916
- * Build CSS classes for the stub element
917
- *
918
- * PORTED FROM: build-stub-classes in date_picker.cljs
919
- */
920
548
  buildStubClasses() {
921
549
  const classes = ['date-picker-stub'];
922
550
  const size = this.getProperty('size') || 'md';
@@ -929,11 +557,6 @@ export class TyDatePicker extends TyComponent {
929
557
  classes.push('open');
930
558
  return classes.join(' ');
931
559
  }
932
- /**
933
- * Render the date picker stub (input display)
934
- *
935
- * PORTED FROM: render-date-picker-stub in date_picker.cljs
936
- */
937
560
  renderStub() {
938
561
  const stub = document.createElement('div');
939
562
  stub.className = this.buildStubClasses();
@@ -941,11 +564,6 @@ export class TyDatePicker extends TyComponent {
941
564
  if (isDisabled) {
942
565
  stub.setAttribute('disabled', 'true');
943
566
  }
944
- // Not text-editable (click/Enter/Space just opens the calendar dialog),
945
- // so role="button" — not role="combobox", which implies typing. The
946
- // popup is a real <dialog> opened via showModal() (see openDropdown),
947
- // so it already gets role="dialog" + focus-trap from the browser for
948
- // free; only the trigger itself needs wiring.
949
567
  stub.setAttribute('role', 'button');
950
568
  stub.setAttribute('aria-haspopup', 'dialog');
951
569
  stub.setAttribute('aria-expanded', String(this._state.open));
@@ -955,9 +573,6 @@ export class TyDatePicker extends TyComponent {
955
573
  if (this.getProperty('label')) {
956
574
  stub.setAttribute('aria-labelledby', `date-picker-label-${getElementHash(this)}`);
957
575
  }
958
- // Same gap as ty-select had: a plain div's Enter/Space don't turn into
959
- // click events on their own, so without this a keyboard-only user could
960
- // Tab to the field but never open it.
961
576
  stub.addEventListener('keydown', (e) => {
962
577
  const ke = e;
963
578
  if (this._state.open)
@@ -967,10 +582,8 @@ export class TyDatePicker extends TyComponent {
967
582
  this.handleStubClick(e);
968
583
  }
969
584
  });
970
- // Start slot — leading icon (search, calendar variant, etc.)
971
585
  const startSlot = document.createElement('slot');
972
586
  startSlot.name = 'start';
973
- // Display text
974
587
  const displayText = document.createElement('span');
975
588
  displayText.className = 'stub-text';
976
589
  const formattedValue = this.getFormattedValue();
@@ -979,10 +592,8 @@ export class TyDatePicker extends TyComponent {
979
592
  if (!formattedValue) {
980
593
  displayText.classList.add('placeholder');
981
594
  }
982
- // Icons container
983
595
  const iconContainer = document.createElement('div');
984
596
  iconContainer.className = 'stub-icons';
985
- // Clear button
986
597
  const clearable = this.getProperty('clearable');
987
598
  if (clearable && formattedValue && !isDisabled) {
988
599
  const clearButton = document.createElement('button');
@@ -992,23 +603,16 @@ export class TyDatePicker extends TyComponent {
992
603
  clearButton.addEventListener('click', (e) => this.handleClearClick(e));
993
604
  iconContainer.appendChild(clearButton);
994
605
  }
995
- // Calendar icon
996
606
  const calendarIcon = document.createElement('span');
997
607
  calendarIcon.className = 'stub-arrow';
998
608
  calendarIcon.innerHTML = CALENDAR_ICON_SVG;
999
609
  iconContainer.appendChild(calendarIcon);
1000
- // Stub click handler
1001
610
  stub.addEventListener('click', (e) => this.handleStubClick(e));
1002
611
  stub.appendChild(startSlot);
1003
612
  stub.appendChild(displayText);
1004
613
  stub.appendChild(iconContainer);
1005
614
  return stub;
1006
615
  }
1007
- /**
1008
- * Create time input element
1009
- *
1010
- * PORTED FROM: create-time-input in date_picker.cljs
1011
- */
1012
616
  createTimeInputElement() {
1013
617
  const input = document.createElement('input');
1014
618
  input.type = 'text';
@@ -1018,9 +622,6 @@ export class TyDatePicker extends TyComponent {
1018
622
  input.maxLength = 5;
1019
623
  return input;
1020
624
  }
1021
- /**
1022
- * Render time input section
1023
- */
1024
625
  renderTimeSection() {
1025
626
  const timeSection = document.createElement('div');
1026
627
  timeSection.className = 'time-section';
@@ -1028,7 +629,6 @@ export class TyDatePicker extends TyComponent {
1028
629
  timeLabel.className = 'time-label';
1029
630
  timeLabel.textContent = 'Time:';
1030
631
  const timeInputElement = this.createTimeInputElement();
1031
- // Create TimeInput instance
1032
632
  const hour = this._state.hour || 0;
1033
633
  const minute = this._state.minute || 0;
1034
634
  this._timeInput = new TimeInput(timeInputElement, this, hour, minute);
@@ -1040,19 +640,12 @@ export class TyDatePicker extends TyComponent {
1040
640
  timeSection.appendChild(timeIcon);
1041
641
  return timeSection;
1042
642
  }
1043
- /**
1044
- * Render calendar dropdown with dialog
1045
- *
1046
- * PORTED FROM: render-calendar-dropdown in date_picker.cljs
1047
- */
1048
643
  renderCalendarDropdown() {
1049
644
  const dialog = document.createElement('dialog');
1050
645
  dialog.className = 'calendar-dialog';
1051
646
  const contentWrapper = document.createElement('div');
1052
647
  contentWrapper.className = 'calendar-content';
1053
- // Create ty-calendar
1054
648
  const calendar = document.createElement('ty-calendar');
1055
- // Set current date if available
1056
649
  if (this._state.year && this._state.month && this._state.day) {
1057
650
  calendar.setAttribute('year', this._state.year.toString());
1058
651
  calendar.setAttribute('month', this._state.month.toString());
@@ -1062,38 +655,26 @@ export class TyDatePicker extends TyComponent {
1062
655
  if (locale) {
1063
656
  calendar.setAttribute('locale', locale);
1064
657
  }
1065
- // Pass through date bounds
1066
658
  const min = this.getProperty('min');
1067
659
  const max = this.getProperty('max');
1068
660
  if (min)
1069
661
  calendar.setAttribute('min', min);
1070
662
  if (max)
1071
663
  calendar.setAttribute('max', max);
1072
- // Match the popup calendar to the stub's flavor. 'default' is the
1073
- // stub's own unstyled state, not a real flavor — leave the calendar on
1074
- // its own default rather than forwarding the literal string.
1075
664
  const flavor = this.getProperty('flavor');
1076
665
  if (flavor && flavor !== 'default')
1077
666
  calendar.setAttribute('flavor', flavor);
1078
- // Add calendar change handler
1079
667
  calendar.addEventListener('change', (e) => this.handleCalendarChange(e));
1080
668
  contentWrapper.appendChild(calendar);
1081
- // Add time section if with-time enabled
1082
669
  if (this._state.withTime) {
1083
670
  contentWrapper.appendChild(this.renderTimeSection());
1084
671
  }
1085
672
  dialog.appendChild(contentWrapper);
1086
- // Dialog close handler
1087
673
  dialog.addEventListener('close', () => {
1088
674
  this.updateState({ open: false });
1089
675
  });
1090
676
  return dialog;
1091
677
  }
1092
- /**
1093
- * Render native date input for mobile touch devices.
1094
- * Uses <input type="date"> or <input type="datetime-local"> when with-time.
1095
- * Reuses .date-picker-stub styling and existing event/state infrastructure.
1096
- */
1097
678
  renderNativeInput() {
1098
679
  const wrapper = document.createElement('div');
1099
680
  wrapper.className = 'dropdown-wrapper';
@@ -1103,7 +684,6 @@ export class TyDatePicker extends TyComponent {
1103
684
  if (isDisabled) {
1104
685
  stub.setAttribute('disabled', 'true');
1105
686
  }
1106
- // Native input (hidden, activated via picker indicator)
1107
687
  const input = document.createElement('input');
1108
688
  input.className = 'native-date-input';
1109
689
  input.type = this._state.withTime ? 'datetime-local' : 'date';
@@ -1111,25 +691,21 @@ export class TyDatePicker extends TyComponent {
1111
691
  input.disabled = true;
1112
692
  if (this.getProperty('required'))
1113
693
  input.required = true;
1114
- // Native min/max (datetime-local wants a time suffix)
1115
694
  const minBound = this.getProperty('min');
1116
695
  const maxBound = this.getProperty('max');
1117
696
  if (minBound)
1118
697
  input.min = this._state.withTime ? `${minBound}T00:00` : minBound;
1119
698
  if (maxBound)
1120
699
  input.max = this._state.withTime ? `${maxBound}T23:59` : maxBound;
1121
- // Pre-fill value
1122
700
  const localValue = componentsToLocalValue(this.getComponents(), this._state.withTime);
1123
701
  if (localValue) {
1124
702
  input.value = localValue;
1125
703
  }
1126
- // Placeholder span (native date inputs ignore placeholder attr)
1127
704
  const placeholder = this.getProperty('placeholder') || 'Select date...';
1128
705
  const formattedValue = this.getFormattedValue();
1129
706
  const placeholderEl = document.createElement('span');
1130
707
  placeholderEl.className = 'stub-text' + (formattedValue ? '' : ' placeholder');
1131
708
  placeholderEl.textContent = formattedValue || placeholder;
1132
- // Change listener
1133
709
  input.addEventListener('change', () => {
1134
710
  if (!input.value) {
1135
711
  this.clearValue();
@@ -1141,13 +717,11 @@ export class TyDatePicker extends TyComponent {
1141
717
  this.emitChangeEvent(parsed, 'selection');
1142
718
  }
1143
719
  });
1144
- // Start slot — leading icon
1145
720
  const startSlot = document.createElement('slot');
1146
721
  startSlot.name = 'start';
1147
722
  stub.appendChild(startSlot);
1148
723
  stub.appendChild(input);
1149
724
  stub.appendChild(placeholderEl);
1150
- // Icons container (clear + calendar icon)
1151
725
  const iconContainer = document.createElement('div');
1152
726
  iconContainer.className = 'stub-icons';
1153
727
  const clearable = this.getProperty('clearable');
@@ -1167,15 +741,9 @@ export class TyDatePicker extends TyComponent {
1167
741
  wrapper.appendChild(stub);
1168
742
  return wrapper;
1169
743
  }
1170
- /**
1171
- * Calculate calendar position
1172
- *
1173
- * PORTED FROM: calculate-calendar-position! in date_picker.cljs
1174
- */
1175
744
  calculateCalendarPosition() {
1176
745
  if (!this.shadowRoot)
1177
746
  return;
1178
- // Mobile uses native input, no positioning needed
1179
747
  if (isMobileTouch())
1180
748
  return;
1181
749
  const stub = this.shadowRoot.querySelector('.date-picker-stub');
@@ -1183,7 +751,6 @@ export class TyDatePicker extends TyComponent {
1183
751
  if (!stub || !dialog)
1184
752
  return;
1185
753
  const stubRect = stub.getBoundingClientRect();
1186
- // Measure when shown; the estimate only covers the pre-layout call
1187
754
  const dialogRect = dialog.getBoundingClientRect();
1188
755
  const estimatedHeight = dialogRect.height || 400;
1189
756
  const popupWidth = dialogRect.width || 320;
@@ -1197,12 +764,10 @@ export class TyDatePicker extends TyComponent {
1197
764
  const positionBelow = pos.below;
1198
765
  const x = pos.x - wrapPadding;
1199
766
  const y = positionBelow ? pos.topY : pos.bottomY;
1200
- // Set CSS variables for positioning
1201
767
  this.style.setProperty('--calendar-x', `${x}px`);
1202
768
  this.style.setProperty('--calendar-y', `${y}px`);
1203
769
  this.style.setProperty('--calendar-offset-x', '0px');
1204
770
  this.style.setProperty('--calendar-offset-y', '0px');
1205
- // Set direction classes
1206
771
  if (positionBelow) {
1207
772
  dialog.classList.add('position-below');
1208
773
  dialog.classList.remove('position-above');
@@ -1212,11 +777,6 @@ export class TyDatePicker extends TyComponent {
1212
777
  dialog.classList.remove('position-below');
1213
778
  }
1214
779
  }
1215
- /**
1216
- * Render container structure
1217
- *
1218
- * PORTED FROM: render-container-structure in date_picker.cljs
1219
- */
1220
780
  renderContainer() {
1221
781
  const container = document.createElement('div');
1222
782
  container.className = 'dropdown-container';
@@ -1230,40 +790,29 @@ export class TyDatePicker extends TyComponent {
1230
790
  }
1231
791
  return container;
1232
792
  }
1233
- /**
1234
- * Main render method
1235
- *
1236
- * PORTED FROM: render! in date_picker.cljs
1237
- */
1238
793
  render() {
1239
794
  if (!this.shadowRoot)
1240
795
  return;
1241
796
  ensureStyles(this.shadowRoot, { css: datePickerStyles, id: 'ty-date-picker' });
1242
- // Mobile: native input, no dialog/event listeners/scroll lock
1243
797
  if (isMobileTouch()) {
1244
798
  const existingNativeInput = this.shadowRoot.querySelector('.native-date-input');
1245
799
  if (existingNativeInput) {
1246
- // PARTIAL UPDATE: don't destroy DOM while native picker may be open
1247
800
  this.updateDisplay();
1248
801
  return;
1249
802
  }
1250
- // FIRST RENDER: build from scratch
1251
803
  this.shadowRoot.innerHTML = '';
1252
804
  const container = this.renderContainer();
1253
805
  container.appendChild(this.renderNativeInput());
1254
806
  this.shadowRoot.appendChild(container);
1255
807
  return;
1256
808
  }
1257
- // Check if dialog is currently open
1258
809
  const existingDialog = this.shadowRoot.querySelector('.calendar-dialog');
1259
810
  const isDialogOpen = existingDialog && existingDialog.open;
1260
811
  if (isDialogOpen && this._state.open) {
1261
- // PARTIAL UPDATE: Dialog is open - just update display
1262
812
  this.updateDisplay();
1263
813
  this.calculateCalendarPosition();
1264
814
  return;
1265
815
  }
1266
- // FULL REBUILD: Dialog is closed or doesn't exist
1267
816
  this.shadowRoot.innerHTML = '';
1268
817
  const container = this.renderContainer();
1269
818
  const wrapper = document.createElement('div');
@@ -1272,14 +821,8 @@ export class TyDatePicker extends TyComponent {
1272
821
  wrapper.appendChild(this.renderCalendarDropdown());
1273
822
  container.appendChild(wrapper);
1274
823
  this.shadowRoot.appendChild(container);
1275
- // Setup document-level event listeners
1276
824
  this.setupEventListeners();
1277
825
  }
1278
- /**
1279
- * Update display without destroying DOM (for open dialog)
1280
- *
1281
- * PORTED FROM: update-display! in date_picker.cljs
1282
- */
1283
826
  updateDisplay() {
1284
827
  if (!this.shadowRoot)
1285
828
  return;
@@ -1288,18 +831,15 @@ export class TyDatePicker extends TyComponent {
1288
831
  const hasValue = this.hasDate();
1289
832
  const clearable = this.getProperty('clearable');
1290
833
  const isDisabled = this.getProperty('disabled');
1291
- // Update stub text (shared between mobile and desktop)
1292
834
  const stubText = this.shadowRoot.querySelector('.stub-text');
1293
835
  if (stubText) {
1294
836
  stubText.textContent = formattedValue || placeholder;
1295
837
  stubText.classList.toggle('placeholder', !formattedValue);
1296
838
  }
1297
- // Update clear button visibility
1298
839
  const clearButton = this.shadowRoot.querySelector('.stub-clear');
1299
840
  if (clearButton) {
1300
841
  clearButton.style.display = (clearable && hasValue && !isDisabled) ? '' : 'none';
1301
842
  }
1302
- // Mobile: also sync native input value
1303
843
  if (isMobileTouch()) {
1304
844
  const nativeInput = this.shadowRoot.querySelector('.native-date-input');
1305
845
  if (nativeInput) {
@@ -1308,17 +848,12 @@ export class TyDatePicker extends TyComponent {
1308
848
  }
1309
849
  return;
1310
850
  }
1311
- // Desktop: update calendar and time input
1312
851
  const calendar = this.shadowRoot.querySelector('ty-calendar');
1313
852
  if (calendar && hasValue) {
1314
853
  calendar.setAttribute('year', this._state.year.toString());
1315
854
  calendar.setAttribute('month', this._state.month.toString());
1316
855
  calendar.setAttribute('day', this._state.day.toString());
1317
856
  }
1318
- // Keep an already-open popup's flavor in sync. render() only rebuilds
1319
- // (and re-forwards flavor) while the dialog is CLOSED — while open it
1320
- // takes this partial-update path instead, so without this the calendar
1321
- // would stay stuck on whatever flavor was active when it was opened.
1322
857
  if (calendar) {
1323
858
  const flavor = this.getProperty('flavor');
1324
859
  if (flavor && flavor !== 'default')
@@ -1330,55 +865,35 @@ export class TyDatePicker extends TyComponent {
1330
865
  this._timeInput.setTime(this._state.hour, this._state.minute);
1331
866
  }
1332
867
  }
1333
- /**
1334
- * Setup document-level event listeners
1335
- */
1336
868
  setupEventListeners() {
1337
- // Remove old listeners if they exist
1338
869
  if (this._clickListener) {
1339
870
  document.removeEventListener('click', this._clickListener);
1340
871
  }
1341
872
  if (this._keydownListener) {
1342
873
  document.removeEventListener('keydown', this._keydownListener);
1343
874
  }
1344
- // Create new listeners
1345
875
  this._clickListener = (e) => this.handleOutsideClick(e);
1346
876
  this._keydownListener = (e) => this.handleEscapeKey(e);
1347
- // Add document listeners
1348
877
  document.addEventListener('click', this._clickListener);
1349
878
  document.addEventListener('keydown', this._keydownListener);
1350
- // Add dialog click listener
1351
879
  const dialog = this.shadowRoot?.querySelector('.calendar-dialog');
1352
880
  if (dialog) {
1353
881
  this._dialogClickListener = (e) => this.handleDialogClick(e);
1354
882
  dialog.addEventListener('click', this._dialogClickListener);
1355
883
  }
1356
884
  }
1357
- // ==========================================================================
1358
- // Event Handlers
1359
- // ==========================================================================
1360
885
  handleStubClick(event) {
1361
886
  event.preventDefault();
1362
- // Heal state/DOM desync: a host re-render can replace the dialog element
1363
- // while `open` is still true, stranding the picker (openDropdown
1364
- // early-returns on _state.open). Reality wins: closed dialog → closed state.
1365
887
  const dialog = this.shadowRoot?.querySelector('.calendar-dialog');
1366
888
  if (this._state.open && (!dialog || !dialog.open)) {
1367
889
  this.updateState({ open: false }, true);
1368
890
  unlockScroll(`date-picker-${this.id || this.toString()}`);
1369
891
  }
1370
892
  const disabled = this.getProperty('disabled');
1371
- // FIXME: Timestamp guard is a workaround. Calendar day cells use pointerdown,
1372
- // which closes the dialog before the click event fires — the click then hits the
1373
- // stub and reopens. Proper fix: switch calendar-month day cells from pointerdown
1374
- // to click so the event is consumed before the dialog closes.
1375
893
  if (!disabled && Date.now() - this._closeTimestamp > 300) {
1376
894
  this.openDropdown();
1377
895
  }
1378
896
  }
1379
- /**
1380
- * Clear the date value, sync state, emit event, and re-render.
1381
- */
1382
897
  clearValue() {
1383
898
  this.updateState({
1384
899
  year: undefined,
@@ -1396,11 +911,6 @@ export class TyDatePicker extends TyComponent {
1396
911
  event.stopPropagation();
1397
912
  this.clearValue();
1398
913
  }
1399
- /**
1400
- * Handle calendar date selection
1401
- *
1402
- * PORTED FROM: handle-calendar-change! in date_picker.cljs
1403
- */
1404
914
  handleCalendarChange(event) {
1405
915
  event.preventDefault();
1406
916
  event.stopPropagation();
@@ -1409,7 +919,6 @@ export class TyDatePicker extends TyComponent {
1409
919
  const dayContext = detail.dayContext;
1410
920
  if (!dayContext)
1411
921
  return;
1412
- // Update state with new date
1413
922
  const newComponents = {
1414
923
  year: dayContext.year,
1415
924
  month: dayContext.month,
@@ -1417,15 +926,12 @@ export class TyDatePicker extends TyComponent {
1417
926
  hour: this._state.hour,
1418
927
  minute: this._state.minute,
1419
928
  };
1420
- // Force sync when calendar date changes
1421
929
  this.updateState(newComponents, true);
1422
930
  this.emitChangeEvent(newComponents, 'selection');
1423
- // Close calendar if time is not required
1424
931
  if (!this._state.withTime) {
1425
932
  this.closeDropdown();
1426
933
  }
1427
934
  else {
1428
- // Auto-focus time input after date selection
1429
935
  requestAnimationFrame(() => {
1430
936
  if (!this.shadowRoot)
1431
937
  return;
@@ -1436,43 +942,26 @@ export class TyDatePicker extends TyComponent {
1436
942
  });
1437
943
  }
1438
944
  }
1439
- /**
1440
- * Handle dialog backdrop clicks
1441
- *
1442
- * PORTED FROM: handle-dialog-click! in date_picker.cljs
1443
- */
1444
945
  handleDialogClick(event) {
1445
946
  if (!this.shadowRoot)
1446
947
  return;
1447
948
  const dialog = this.shadowRoot.querySelector('.calendar-dialog');
1448
949
  const content = this.shadowRoot.querySelector('.calendar-content');
1449
- // Close if clicking on dialog backdrop (not calendar content)
1450
950
  if (event.target === dialog && this._state.open && content && !content.contains(event.target)) {
1451
951
  event.preventDefault();
1452
952
  event.stopPropagation();
1453
953
  this.closeDropdown();
1454
954
  }
1455
955
  }
1456
- /**
1457
- * Handle clicks outside the date picker
1458
- *
1459
- * PORTED FROM: handle-outside-click! in date_picker.cljs
1460
- */
1461
956
  handleOutsideClick(event) {
1462
957
  if (!this.shadowRoot)
1463
958
  return;
1464
959
  const target = event.target;
1465
960
  const dialog = this.shadowRoot.querySelector('.calendar-dialog');
1466
- // Check if click is truly outside everything
1467
961
  if (this._state.open && dialog && !this.contains(target) && !dialog.contains(target)) {
1468
962
  this.closeDropdown();
1469
963
  }
1470
964
  }
1471
- /**
1472
- * Handle Escape key press
1473
- *
1474
- * PORTED FROM: handle-escape-key! in date_picker.cljs
1475
- */
1476
965
  handleEscapeKey(event) {
1477
966
  const keyboardEvent = event;
1478
967
  if (keyboardEvent.key === 'Escape' && this._state.open) {
@@ -1480,18 +969,12 @@ export class TyDatePicker extends TyComponent {
1480
969
  this.closeDropdown();
1481
970
  }
1482
971
  }
1483
- /**
1484
- * Open calendar dropdown
1485
- *
1486
- * PORTED FROM: open-dropdown! in date_picker.cljs
1487
- */
1488
972
  openDropdown() {
1489
973
  if (this._state.open)
1490
974
  return;
1491
975
  if (isMobileTouch())
1492
976
  return;
1493
977
  this.updateState({ open: true });
1494
- // Dispatch open event
1495
978
  this.dispatchEvent(new CustomEvent('open', {
1496
979
  bubbles: true,
1497
980
  composed: true
@@ -1510,7 +993,6 @@ export class TyDatePicker extends TyComponent {
1510
993
  const stubEl = this.shadowRoot.querySelector('.date-picker-stub');
1511
994
  if (stubEl)
1512
995
  stubEl.setAttribute('aria-expanded', 'true');
1513
- // Remove focus from any focused elements to prevent the blue outline
1514
996
  const focusedElement = this.shadowRoot.activeElement;
1515
997
  if (focusedElement) {
1516
998
  focusedElement.blur();
@@ -1518,17 +1000,11 @@ export class TyDatePicker extends TyComponent {
1518
1000
  }
1519
1001
  });
1520
1002
  }
1521
- /**
1522
- * Close calendar dropdown
1523
- *
1524
- * PORTED FROM: close-dropdown! in date_picker.cljs
1525
- */
1526
1003
  closeDropdown() {
1527
1004
  if (!this._state.open)
1528
1005
  return;
1529
1006
  this._closeTimestamp = Date.now();
1530
1007
  const pickerId = `date-picker-${this.id || this.toString()}`;
1531
- // Force sync any staged updates when closing
1532
1008
  this.updateState({ open: false }, true);
1533
1009
  unlockScroll(pickerId);
1534
1010
  if (this.shadowRoot) {
@@ -1537,8 +1013,6 @@ export class TyDatePicker extends TyComponent {
1537
1013
  dialog.classList.remove('position-above', 'position-below', 'open');
1538
1014
  dialog.close();
1539
1015
  }
1540
- // closeDropdown() doesn't call render(), so the stub's aria-expanded
1541
- // (still "true" from open) needs an explicit flip here too.
1542
1016
  const stubEl = this.shadowRoot.querySelector('.date-picker-stub');
1543
1017
  if (stubEl)
1544
1018
  stubEl.setAttribute('aria-expanded', 'false');
@@ -1549,51 +1023,34 @@ export class TyDatePicker extends TyComponent {
1549
1023
  }));
1550
1024
  this.render();
1551
1025
  }
1552
- // ==========================================================================
1553
- // Cleanup
1554
- // ==========================================================================
1555
- /**
1556
- * Clean up event listeners and state
1557
- *
1558
- * PORTED FROM: cleanup! in date_picker.cljs
1559
- */
1560
1026
  cleanup() {
1561
- // Remove document listeners
1562
1027
  if (this._clickListener) {
1563
1028
  document.removeEventListener('click', this._clickListener);
1564
1029
  }
1565
1030
  if (this._keydownListener) {
1566
1031
  document.removeEventListener('keydown', this._keydownListener);
1567
1032
  }
1568
- // Remove dialog listener
1569
1033
  if (this._dialogClickListener && this.shadowRoot) {
1570
1034
  const dialog = this.shadowRoot.querySelector('.calendar-dialog');
1571
1035
  if (dialog) {
1572
1036
  dialog.removeEventListener('click', this._dialogClickListener);
1573
1037
  }
1574
1038
  }
1575
- // Unlock scroll if open
1576
1039
  if (this._state.open) {
1577
1040
  const pickerId = `date-picker-${this.id || this.toString()}`;
1578
1041
  unlockScroll(pickerId);
1579
1042
  }
1580
- // Clear references
1581
1043
  this._clickListener = undefined;
1582
1044
  this._keydownListener = undefined;
1583
1045
  this._dialogClickListener = undefined;
1584
1046
  this._timeInput = undefined;
1585
1047
  }
1586
1048
  }
1587
- // ============================================================================
1588
- // PROPERTY CONFIGURATION - Single source of truth
1589
- // ============================================================================
1590
1049
  TyDatePicker.properties = {
1591
- // String properties
1592
1050
  size: {
1593
1051
  type: 'string',
1594
1052
  visual: true,
1595
1053
  default: 'md',
1596
- // Fields come in exactly three sizes; legacy xs/xl map to sm/lg.
1597
1054
  validate: (v) => ['sm', 'md', 'lg'].includes(v),
1598
1055
  coerce: (v) => {
1599
1056
  if (v === 'xs')
@@ -1624,10 +1081,9 @@ TyDatePicker.properties = {
1624
1081
  },
1625
1082
  name: {
1626
1083
  type: 'string',
1627
- visual: false, // Non-visual, just for form field name
1084
+ visual: false,
1628
1085
  default: ''
1629
1086
  },
1630
- // Date bounds (ISO) — passed through to the embedded ty-calendar
1631
1087
  min: {
1632
1088
  type: 'string',
1633
1089
  visual: true,
@@ -1638,7 +1094,6 @@ TyDatePicker.properties = {
1638
1094
  visual: true,
1639
1095
  default: ''
1640
1096
  },
1641
- // Boolean properties
1642
1097
  required: {
1643
1098
  type: 'boolean',
1644
1099
  visual: true,
@@ -1657,7 +1112,6 @@ TyDatePicker.properties = {
1657
1112
  'not-clearable': false
1658
1113
  }
1659
1114
  },
1660
- // Format property (medium complexity)
1661
1115
  format: {
1662
1116
  type: 'string',
1663
1117
  visual: true,
@@ -1672,34 +1126,25 @@ TyDatePicker.properties = {
1672
1126
  return v;
1673
1127
  }
1674
1128
  },
1675
- // Locale property (medium complexity - has observer)
1676
1129
  locale: {
1677
1130
  type: 'string',
1678
1131
  visual: true,
1679
1132
  default: ''
1680
1133
  },
1681
- // Value property (high complexity - UTC parsing, date components)
1682
- // Note: Custom getter/setter will handle the complex logic
1683
1134
  value: {
1684
1135
  type: 'string',
1685
1136
  visual: true,
1686
- formValue: true, // Syncs to form
1687
- emitChange: false, // We emit custom 'change' events manually with full detail
1688
- default: null // null when no date selected
1137
+ formValue: true,
1138
+ emitChange: false,
1139
+ default: null
1689
1140
  },
1690
- // With-time property (high complexity - affects parsing and rendering)
1691
1141
  'with-time': {
1692
1142
  type: 'boolean',
1693
1143
  visual: true,
1694
1144
  default: false
1695
1145
  }
1696
1146
  };
1697
- /**
1698
- * Form-associated custom element
1699
- */
1700
1147
  TyDatePicker.formAssociated = true;
1701
- // Register custom element
1702
1148
  if (!customElements.get('ty-date-picker')) {
1703
1149
  customElements.define('ty-date-picker', TyDatePicker);
1704
1150
  }
1705
- //# sourceMappingURL=date-picker.js.map