tyrell-components 1.0.0-TC44 → 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 +10 -160
  17. package/lib/components/date-picker.js +31 -577
  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 +43 -150
  37. package/lib/components/select.js +68 -558
  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 +50 -18
  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 +4 -2
  159. package/lib/utils/positioning.js +2 -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,39 +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
- // Rendering & DOM Management
905
- // ==========================================================================
906
- /**
907
- * Build CSS classes for the stub element
908
- *
909
- * PORTED FROM: build-stub-classes in date_picker.cljs
910
- */
545
+ clear() {
546
+ this.clearValue();
547
+ }
911
548
  buildStubClasses() {
912
549
  const classes = ['date-picker-stub'];
913
550
  const size = this.getProperty('size') || 'md';
@@ -920,11 +557,6 @@ export class TyDatePicker extends TyComponent {
920
557
  classes.push('open');
921
558
  return classes.join(' ');
922
559
  }
923
- /**
924
- * Render the date picker stub (input display)
925
- *
926
- * PORTED FROM: render-date-picker-stub in date_picker.cljs
927
- */
928
560
  renderStub() {
929
561
  const stub = document.createElement('div');
930
562
  stub.className = this.buildStubClasses();
@@ -932,11 +564,6 @@ export class TyDatePicker extends TyComponent {
932
564
  if (isDisabled) {
933
565
  stub.setAttribute('disabled', 'true');
934
566
  }
935
- // Not text-editable (click/Enter/Space just opens the calendar dialog),
936
- // so role="button" — not role="combobox", which implies typing. The
937
- // popup is a real <dialog> opened via showModal() (see openDropdown),
938
- // so it already gets role="dialog" + focus-trap from the browser for
939
- // free; only the trigger itself needs wiring.
940
567
  stub.setAttribute('role', 'button');
941
568
  stub.setAttribute('aria-haspopup', 'dialog');
942
569
  stub.setAttribute('aria-expanded', String(this._state.open));
@@ -946,9 +573,6 @@ export class TyDatePicker extends TyComponent {
946
573
  if (this.getProperty('label')) {
947
574
  stub.setAttribute('aria-labelledby', `date-picker-label-${getElementHash(this)}`);
948
575
  }
949
- // Same gap as ty-select had: a plain div's Enter/Space don't turn into
950
- // click events on their own, so without this a keyboard-only user could
951
- // Tab to the field but never open it.
952
576
  stub.addEventListener('keydown', (e) => {
953
577
  const ke = e;
954
578
  if (this._state.open)
@@ -958,10 +582,8 @@ export class TyDatePicker extends TyComponent {
958
582
  this.handleStubClick(e);
959
583
  }
960
584
  });
961
- // Start slot — leading icon (search, calendar variant, etc.)
962
585
  const startSlot = document.createElement('slot');
963
586
  startSlot.name = 'start';
964
- // Display text
965
587
  const displayText = document.createElement('span');
966
588
  displayText.className = 'stub-text';
967
589
  const formattedValue = this.getFormattedValue();
@@ -970,10 +592,8 @@ export class TyDatePicker extends TyComponent {
970
592
  if (!formattedValue) {
971
593
  displayText.classList.add('placeholder');
972
594
  }
973
- // Icons container
974
595
  const iconContainer = document.createElement('div');
975
596
  iconContainer.className = 'stub-icons';
976
- // Clear button
977
597
  const clearable = this.getProperty('clearable');
978
598
  if (clearable && formattedValue && !isDisabled) {
979
599
  const clearButton = document.createElement('button');
@@ -983,23 +603,16 @@ export class TyDatePicker extends TyComponent {
983
603
  clearButton.addEventListener('click', (e) => this.handleClearClick(e));
984
604
  iconContainer.appendChild(clearButton);
985
605
  }
986
- // Calendar icon
987
606
  const calendarIcon = document.createElement('span');
988
607
  calendarIcon.className = 'stub-arrow';
989
608
  calendarIcon.innerHTML = CALENDAR_ICON_SVG;
990
609
  iconContainer.appendChild(calendarIcon);
991
- // Stub click handler
992
610
  stub.addEventListener('click', (e) => this.handleStubClick(e));
993
611
  stub.appendChild(startSlot);
994
612
  stub.appendChild(displayText);
995
613
  stub.appendChild(iconContainer);
996
614
  return stub;
997
615
  }
998
- /**
999
- * Create time input element
1000
- *
1001
- * PORTED FROM: create-time-input in date_picker.cljs
1002
- */
1003
616
  createTimeInputElement() {
1004
617
  const input = document.createElement('input');
1005
618
  input.type = 'text';
@@ -1009,9 +622,6 @@ export class TyDatePicker extends TyComponent {
1009
622
  input.maxLength = 5;
1010
623
  return input;
1011
624
  }
1012
- /**
1013
- * Render time input section
1014
- */
1015
625
  renderTimeSection() {
1016
626
  const timeSection = document.createElement('div');
1017
627
  timeSection.className = 'time-section';
@@ -1019,7 +629,6 @@ export class TyDatePicker extends TyComponent {
1019
629
  timeLabel.className = 'time-label';
1020
630
  timeLabel.textContent = 'Time:';
1021
631
  const timeInputElement = this.createTimeInputElement();
1022
- // Create TimeInput instance
1023
632
  const hour = this._state.hour || 0;
1024
633
  const minute = this._state.minute || 0;
1025
634
  this._timeInput = new TimeInput(timeInputElement, this, hour, minute);
@@ -1031,19 +640,12 @@ export class TyDatePicker extends TyComponent {
1031
640
  timeSection.appendChild(timeIcon);
1032
641
  return timeSection;
1033
642
  }
1034
- /**
1035
- * Render calendar dropdown with dialog
1036
- *
1037
- * PORTED FROM: render-calendar-dropdown in date_picker.cljs
1038
- */
1039
643
  renderCalendarDropdown() {
1040
644
  const dialog = document.createElement('dialog');
1041
645
  dialog.className = 'calendar-dialog';
1042
646
  const contentWrapper = document.createElement('div');
1043
647
  contentWrapper.className = 'calendar-content';
1044
- // Create ty-calendar
1045
648
  const calendar = document.createElement('ty-calendar');
1046
- // Set current date if available
1047
649
  if (this._state.year && this._state.month && this._state.day) {
1048
650
  calendar.setAttribute('year', this._state.year.toString());
1049
651
  calendar.setAttribute('month', this._state.month.toString());
@@ -1053,38 +655,26 @@ export class TyDatePicker extends TyComponent {
1053
655
  if (locale) {
1054
656
  calendar.setAttribute('locale', locale);
1055
657
  }
1056
- // Pass through date bounds
1057
658
  const min = this.getProperty('min');
1058
659
  const max = this.getProperty('max');
1059
660
  if (min)
1060
661
  calendar.setAttribute('min', min);
1061
662
  if (max)
1062
663
  calendar.setAttribute('max', max);
1063
- // Match the popup calendar to the stub's flavor. 'default' is the
1064
- // stub's own unstyled state, not a real flavor — leave the calendar on
1065
- // its own default rather than forwarding the literal string.
1066
664
  const flavor = this.getProperty('flavor');
1067
665
  if (flavor && flavor !== 'default')
1068
666
  calendar.setAttribute('flavor', flavor);
1069
- // Add calendar change handler
1070
667
  calendar.addEventListener('change', (e) => this.handleCalendarChange(e));
1071
668
  contentWrapper.appendChild(calendar);
1072
- // Add time section if with-time enabled
1073
669
  if (this._state.withTime) {
1074
670
  contentWrapper.appendChild(this.renderTimeSection());
1075
671
  }
1076
672
  dialog.appendChild(contentWrapper);
1077
- // Dialog close handler
1078
673
  dialog.addEventListener('close', () => {
1079
674
  this.updateState({ open: false });
1080
675
  });
1081
676
  return dialog;
1082
677
  }
1083
- /**
1084
- * Render native date input for mobile touch devices.
1085
- * Uses <input type="date"> or <input type="datetime-local"> when with-time.
1086
- * Reuses .date-picker-stub styling and existing event/state infrastructure.
1087
- */
1088
678
  renderNativeInput() {
1089
679
  const wrapper = document.createElement('div');
1090
680
  wrapper.className = 'dropdown-wrapper';
@@ -1094,7 +684,6 @@ export class TyDatePicker extends TyComponent {
1094
684
  if (isDisabled) {
1095
685
  stub.setAttribute('disabled', 'true');
1096
686
  }
1097
- // Native input (hidden, activated via picker indicator)
1098
687
  const input = document.createElement('input');
1099
688
  input.className = 'native-date-input';
1100
689
  input.type = this._state.withTime ? 'datetime-local' : 'date';
@@ -1102,25 +691,21 @@ export class TyDatePicker extends TyComponent {
1102
691
  input.disabled = true;
1103
692
  if (this.getProperty('required'))
1104
693
  input.required = true;
1105
- // Native min/max (datetime-local wants a time suffix)
1106
694
  const minBound = this.getProperty('min');
1107
695
  const maxBound = this.getProperty('max');
1108
696
  if (minBound)
1109
697
  input.min = this._state.withTime ? `${minBound}T00:00` : minBound;
1110
698
  if (maxBound)
1111
699
  input.max = this._state.withTime ? `${maxBound}T23:59` : maxBound;
1112
- // Pre-fill value
1113
700
  const localValue = componentsToLocalValue(this.getComponents(), this._state.withTime);
1114
701
  if (localValue) {
1115
702
  input.value = localValue;
1116
703
  }
1117
- // Placeholder span (native date inputs ignore placeholder attr)
1118
704
  const placeholder = this.getProperty('placeholder') || 'Select date...';
1119
705
  const formattedValue = this.getFormattedValue();
1120
706
  const placeholderEl = document.createElement('span');
1121
707
  placeholderEl.className = 'stub-text' + (formattedValue ? '' : ' placeholder');
1122
708
  placeholderEl.textContent = formattedValue || placeholder;
1123
- // Change listener
1124
709
  input.addEventListener('change', () => {
1125
710
  if (!input.value) {
1126
711
  this.clearValue();
@@ -1132,13 +717,11 @@ export class TyDatePicker extends TyComponent {
1132
717
  this.emitChangeEvent(parsed, 'selection');
1133
718
  }
1134
719
  });
1135
- // Start slot — leading icon
1136
720
  const startSlot = document.createElement('slot');
1137
721
  startSlot.name = 'start';
1138
722
  stub.appendChild(startSlot);
1139
723
  stub.appendChild(input);
1140
724
  stub.appendChild(placeholderEl);
1141
- // Icons container (clear + calendar icon)
1142
725
  const iconContainer = document.createElement('div');
1143
726
  iconContainer.className = 'stub-icons';
1144
727
  const clearable = this.getProperty('clearable');
@@ -1158,15 +741,9 @@ export class TyDatePicker extends TyComponent {
1158
741
  wrapper.appendChild(stub);
1159
742
  return wrapper;
1160
743
  }
1161
- /**
1162
- * Calculate calendar position
1163
- *
1164
- * PORTED FROM: calculate-calendar-position! in date_picker.cljs
1165
- */
1166
744
  calculateCalendarPosition() {
1167
745
  if (!this.shadowRoot)
1168
746
  return;
1169
- // Mobile uses native input, no positioning needed
1170
747
  if (isMobileTouch())
1171
748
  return;
1172
749
  const stub = this.shadowRoot.querySelector('.date-picker-stub');
@@ -1174,7 +751,6 @@ export class TyDatePicker extends TyComponent {
1174
751
  if (!stub || !dialog)
1175
752
  return;
1176
753
  const stubRect = stub.getBoundingClientRect();
1177
- // Measure when shown; the estimate only covers the pre-layout call
1178
754
  const dialogRect = dialog.getBoundingClientRect();
1179
755
  const estimatedHeight = dialogRect.height || 400;
1180
756
  const popupWidth = dialogRect.width || 320;
@@ -1188,12 +764,10 @@ export class TyDatePicker extends TyComponent {
1188
764
  const positionBelow = pos.below;
1189
765
  const x = pos.x - wrapPadding;
1190
766
  const y = positionBelow ? pos.topY : pos.bottomY;
1191
- // Set CSS variables for positioning
1192
767
  this.style.setProperty('--calendar-x', `${x}px`);
1193
768
  this.style.setProperty('--calendar-y', `${y}px`);
1194
769
  this.style.setProperty('--calendar-offset-x', '0px');
1195
770
  this.style.setProperty('--calendar-offset-y', '0px');
1196
- // Set direction classes
1197
771
  if (positionBelow) {
1198
772
  dialog.classList.add('position-below');
1199
773
  dialog.classList.remove('position-above');
@@ -1203,11 +777,6 @@ export class TyDatePicker extends TyComponent {
1203
777
  dialog.classList.remove('position-below');
1204
778
  }
1205
779
  }
1206
- /**
1207
- * Render container structure
1208
- *
1209
- * PORTED FROM: render-container-structure in date_picker.cljs
1210
- */
1211
780
  renderContainer() {
1212
781
  const container = document.createElement('div');
1213
782
  container.className = 'dropdown-container';
@@ -1221,40 +790,29 @@ export class TyDatePicker extends TyComponent {
1221
790
  }
1222
791
  return container;
1223
792
  }
1224
- /**
1225
- * Main render method
1226
- *
1227
- * PORTED FROM: render! in date_picker.cljs
1228
- */
1229
793
  render() {
1230
794
  if (!this.shadowRoot)
1231
795
  return;
1232
796
  ensureStyles(this.shadowRoot, { css: datePickerStyles, id: 'ty-date-picker' });
1233
- // Mobile: native input, no dialog/event listeners/scroll lock
1234
797
  if (isMobileTouch()) {
1235
798
  const existingNativeInput = this.shadowRoot.querySelector('.native-date-input');
1236
799
  if (existingNativeInput) {
1237
- // PARTIAL UPDATE: don't destroy DOM while native picker may be open
1238
800
  this.updateDisplay();
1239
801
  return;
1240
802
  }
1241
- // FIRST RENDER: build from scratch
1242
803
  this.shadowRoot.innerHTML = '';
1243
804
  const container = this.renderContainer();
1244
805
  container.appendChild(this.renderNativeInput());
1245
806
  this.shadowRoot.appendChild(container);
1246
807
  return;
1247
808
  }
1248
- // Check if dialog is currently open
1249
809
  const existingDialog = this.shadowRoot.querySelector('.calendar-dialog');
1250
810
  const isDialogOpen = existingDialog && existingDialog.open;
1251
811
  if (isDialogOpen && this._state.open) {
1252
- // PARTIAL UPDATE: Dialog is open - just update display
1253
812
  this.updateDisplay();
1254
813
  this.calculateCalendarPosition();
1255
814
  return;
1256
815
  }
1257
- // FULL REBUILD: Dialog is closed or doesn't exist
1258
816
  this.shadowRoot.innerHTML = '';
1259
817
  const container = this.renderContainer();
1260
818
  const wrapper = document.createElement('div');
@@ -1263,14 +821,8 @@ export class TyDatePicker extends TyComponent {
1263
821
  wrapper.appendChild(this.renderCalendarDropdown());
1264
822
  container.appendChild(wrapper);
1265
823
  this.shadowRoot.appendChild(container);
1266
- // Setup document-level event listeners
1267
824
  this.setupEventListeners();
1268
825
  }
1269
- /**
1270
- * Update display without destroying DOM (for open dialog)
1271
- *
1272
- * PORTED FROM: update-display! in date_picker.cljs
1273
- */
1274
826
  updateDisplay() {
1275
827
  if (!this.shadowRoot)
1276
828
  return;
@@ -1279,18 +831,15 @@ export class TyDatePicker extends TyComponent {
1279
831
  const hasValue = this.hasDate();
1280
832
  const clearable = this.getProperty('clearable');
1281
833
  const isDisabled = this.getProperty('disabled');
1282
- // Update stub text (shared between mobile and desktop)
1283
834
  const stubText = this.shadowRoot.querySelector('.stub-text');
1284
835
  if (stubText) {
1285
836
  stubText.textContent = formattedValue || placeholder;
1286
837
  stubText.classList.toggle('placeholder', !formattedValue);
1287
838
  }
1288
- // Update clear button visibility
1289
839
  const clearButton = this.shadowRoot.querySelector('.stub-clear');
1290
840
  if (clearButton) {
1291
841
  clearButton.style.display = (clearable && hasValue && !isDisabled) ? '' : 'none';
1292
842
  }
1293
- // Mobile: also sync native input value
1294
843
  if (isMobileTouch()) {
1295
844
  const nativeInput = this.shadowRoot.querySelector('.native-date-input');
1296
845
  if (nativeInput) {
@@ -1299,17 +848,12 @@ export class TyDatePicker extends TyComponent {
1299
848
  }
1300
849
  return;
1301
850
  }
1302
- // Desktop: update calendar and time input
1303
851
  const calendar = this.shadowRoot.querySelector('ty-calendar');
1304
852
  if (calendar && hasValue) {
1305
853
  calendar.setAttribute('year', this._state.year.toString());
1306
854
  calendar.setAttribute('month', this._state.month.toString());
1307
855
  calendar.setAttribute('day', this._state.day.toString());
1308
856
  }
1309
- // Keep an already-open popup's flavor in sync. render() only rebuilds
1310
- // (and re-forwards flavor) while the dialog is CLOSED — while open it
1311
- // takes this partial-update path instead, so without this the calendar
1312
- // would stay stuck on whatever flavor was active when it was opened.
1313
857
  if (calendar) {
1314
858
  const flavor = this.getProperty('flavor');
1315
859
  if (flavor && flavor !== 'default')
@@ -1321,55 +865,35 @@ export class TyDatePicker extends TyComponent {
1321
865
  this._timeInput.setTime(this._state.hour, this._state.minute);
1322
866
  }
1323
867
  }
1324
- /**
1325
- * Setup document-level event listeners
1326
- */
1327
868
  setupEventListeners() {
1328
- // Remove old listeners if they exist
1329
869
  if (this._clickListener) {
1330
870
  document.removeEventListener('click', this._clickListener);
1331
871
  }
1332
872
  if (this._keydownListener) {
1333
873
  document.removeEventListener('keydown', this._keydownListener);
1334
874
  }
1335
- // Create new listeners
1336
875
  this._clickListener = (e) => this.handleOutsideClick(e);
1337
876
  this._keydownListener = (e) => this.handleEscapeKey(e);
1338
- // Add document listeners
1339
877
  document.addEventListener('click', this._clickListener);
1340
878
  document.addEventListener('keydown', this._keydownListener);
1341
- // Add dialog click listener
1342
879
  const dialog = this.shadowRoot?.querySelector('.calendar-dialog');
1343
880
  if (dialog) {
1344
881
  this._dialogClickListener = (e) => this.handleDialogClick(e);
1345
882
  dialog.addEventListener('click', this._dialogClickListener);
1346
883
  }
1347
884
  }
1348
- // ==========================================================================
1349
- // Event Handlers
1350
- // ==========================================================================
1351
885
  handleStubClick(event) {
1352
886
  event.preventDefault();
1353
- // Heal state/DOM desync: a host re-render can replace the dialog element
1354
- // while `open` is still true, stranding the picker (openDropdown
1355
- // early-returns on _state.open). Reality wins: closed dialog → closed state.
1356
887
  const dialog = this.shadowRoot?.querySelector('.calendar-dialog');
1357
888
  if (this._state.open && (!dialog || !dialog.open)) {
1358
889
  this.updateState({ open: false }, true);
1359
890
  unlockScroll(`date-picker-${this.id || this.toString()}`);
1360
891
  }
1361
892
  const disabled = this.getProperty('disabled');
1362
- // FIXME: Timestamp guard is a workaround. Calendar day cells use pointerdown,
1363
- // which closes the dialog before the click event fires — the click then hits the
1364
- // stub and reopens. Proper fix: switch calendar-month day cells from pointerdown
1365
- // to click so the event is consumed before the dialog closes.
1366
893
  if (!disabled && Date.now() - this._closeTimestamp > 300) {
1367
894
  this.openDropdown();
1368
895
  }
1369
896
  }
1370
- /**
1371
- * Clear the date value, sync state, emit event, and re-render.
1372
- */
1373
897
  clearValue() {
1374
898
  this.updateState({
1375
899
  year: undefined,
@@ -1387,11 +911,6 @@ export class TyDatePicker extends TyComponent {
1387
911
  event.stopPropagation();
1388
912
  this.clearValue();
1389
913
  }
1390
- /**
1391
- * Handle calendar date selection
1392
- *
1393
- * PORTED FROM: handle-calendar-change! in date_picker.cljs
1394
- */
1395
914
  handleCalendarChange(event) {
1396
915
  event.preventDefault();
1397
916
  event.stopPropagation();
@@ -1400,7 +919,6 @@ export class TyDatePicker extends TyComponent {
1400
919
  const dayContext = detail.dayContext;
1401
920
  if (!dayContext)
1402
921
  return;
1403
- // Update state with new date
1404
922
  const newComponents = {
1405
923
  year: dayContext.year,
1406
924
  month: dayContext.month,
@@ -1408,15 +926,12 @@ export class TyDatePicker extends TyComponent {
1408
926
  hour: this._state.hour,
1409
927
  minute: this._state.minute,
1410
928
  };
1411
- // Force sync when calendar date changes
1412
929
  this.updateState(newComponents, true);
1413
930
  this.emitChangeEvent(newComponents, 'selection');
1414
- // Close calendar if time is not required
1415
931
  if (!this._state.withTime) {
1416
932
  this.closeDropdown();
1417
933
  }
1418
934
  else {
1419
- // Auto-focus time input after date selection
1420
935
  requestAnimationFrame(() => {
1421
936
  if (!this.shadowRoot)
1422
937
  return;
@@ -1427,43 +942,26 @@ export class TyDatePicker extends TyComponent {
1427
942
  });
1428
943
  }
1429
944
  }
1430
- /**
1431
- * Handle dialog backdrop clicks
1432
- *
1433
- * PORTED FROM: handle-dialog-click! in date_picker.cljs
1434
- */
1435
945
  handleDialogClick(event) {
1436
946
  if (!this.shadowRoot)
1437
947
  return;
1438
948
  const dialog = this.shadowRoot.querySelector('.calendar-dialog');
1439
949
  const content = this.shadowRoot.querySelector('.calendar-content');
1440
- // Close if clicking on dialog backdrop (not calendar content)
1441
950
  if (event.target === dialog && this._state.open && content && !content.contains(event.target)) {
1442
951
  event.preventDefault();
1443
952
  event.stopPropagation();
1444
953
  this.closeDropdown();
1445
954
  }
1446
955
  }
1447
- /**
1448
- * Handle clicks outside the date picker
1449
- *
1450
- * PORTED FROM: handle-outside-click! in date_picker.cljs
1451
- */
1452
956
  handleOutsideClick(event) {
1453
957
  if (!this.shadowRoot)
1454
958
  return;
1455
959
  const target = event.target;
1456
960
  const dialog = this.shadowRoot.querySelector('.calendar-dialog');
1457
- // Check if click is truly outside everything
1458
961
  if (this._state.open && dialog && !this.contains(target) && !dialog.contains(target)) {
1459
962
  this.closeDropdown();
1460
963
  }
1461
964
  }
1462
- /**
1463
- * Handle Escape key press
1464
- *
1465
- * PORTED FROM: handle-escape-key! in date_picker.cljs
1466
- */
1467
965
  handleEscapeKey(event) {
1468
966
  const keyboardEvent = event;
1469
967
  if (keyboardEvent.key === 'Escape' && this._state.open) {
@@ -1471,18 +969,12 @@ export class TyDatePicker extends TyComponent {
1471
969
  this.closeDropdown();
1472
970
  }
1473
971
  }
1474
- /**
1475
- * Open calendar dropdown
1476
- *
1477
- * PORTED FROM: open-dropdown! in date_picker.cljs
1478
- */
1479
972
  openDropdown() {
1480
973
  if (this._state.open)
1481
974
  return;
1482
975
  if (isMobileTouch())
1483
976
  return;
1484
977
  this.updateState({ open: true });
1485
- // Dispatch open event
1486
978
  this.dispatchEvent(new CustomEvent('open', {
1487
979
  bubbles: true,
1488
980
  composed: true
@@ -1501,7 +993,6 @@ export class TyDatePicker extends TyComponent {
1501
993
  const stubEl = this.shadowRoot.querySelector('.date-picker-stub');
1502
994
  if (stubEl)
1503
995
  stubEl.setAttribute('aria-expanded', 'true');
1504
- // Remove focus from any focused elements to prevent the blue outline
1505
996
  const focusedElement = this.shadowRoot.activeElement;
1506
997
  if (focusedElement) {
1507
998
  focusedElement.blur();
@@ -1509,17 +1000,11 @@ export class TyDatePicker extends TyComponent {
1509
1000
  }
1510
1001
  });
1511
1002
  }
1512
- /**
1513
- * Close calendar dropdown
1514
- *
1515
- * PORTED FROM: close-dropdown! in date_picker.cljs
1516
- */
1517
1003
  closeDropdown() {
1518
1004
  if (!this._state.open)
1519
1005
  return;
1520
1006
  this._closeTimestamp = Date.now();
1521
1007
  const pickerId = `date-picker-${this.id || this.toString()}`;
1522
- // Force sync any staged updates when closing
1523
1008
  this.updateState({ open: false }, true);
1524
1009
  unlockScroll(pickerId);
1525
1010
  if (this.shadowRoot) {
@@ -1528,8 +1013,6 @@ export class TyDatePicker extends TyComponent {
1528
1013
  dialog.classList.remove('position-above', 'position-below', 'open');
1529
1014
  dialog.close();
1530
1015
  }
1531
- // closeDropdown() doesn't call render(), so the stub's aria-expanded
1532
- // (still "true" from open) needs an explicit flip here too.
1533
1016
  const stubEl = this.shadowRoot.querySelector('.date-picker-stub');
1534
1017
  if (stubEl)
1535
1018
  stubEl.setAttribute('aria-expanded', 'false');
@@ -1540,51 +1023,34 @@ export class TyDatePicker extends TyComponent {
1540
1023
  }));
1541
1024
  this.render();
1542
1025
  }
1543
- // ==========================================================================
1544
- // Cleanup
1545
- // ==========================================================================
1546
- /**
1547
- * Clean up event listeners and state
1548
- *
1549
- * PORTED FROM: cleanup! in date_picker.cljs
1550
- */
1551
1026
  cleanup() {
1552
- // Remove document listeners
1553
1027
  if (this._clickListener) {
1554
1028
  document.removeEventListener('click', this._clickListener);
1555
1029
  }
1556
1030
  if (this._keydownListener) {
1557
1031
  document.removeEventListener('keydown', this._keydownListener);
1558
1032
  }
1559
- // Remove dialog listener
1560
1033
  if (this._dialogClickListener && this.shadowRoot) {
1561
1034
  const dialog = this.shadowRoot.querySelector('.calendar-dialog');
1562
1035
  if (dialog) {
1563
1036
  dialog.removeEventListener('click', this._dialogClickListener);
1564
1037
  }
1565
1038
  }
1566
- // Unlock scroll if open
1567
1039
  if (this._state.open) {
1568
1040
  const pickerId = `date-picker-${this.id || this.toString()}`;
1569
1041
  unlockScroll(pickerId);
1570
1042
  }
1571
- // Clear references
1572
1043
  this._clickListener = undefined;
1573
1044
  this._keydownListener = undefined;
1574
1045
  this._dialogClickListener = undefined;
1575
1046
  this._timeInput = undefined;
1576
1047
  }
1577
1048
  }
1578
- // ============================================================================
1579
- // PROPERTY CONFIGURATION - Single source of truth
1580
- // ============================================================================
1581
1049
  TyDatePicker.properties = {
1582
- // String properties
1583
1050
  size: {
1584
1051
  type: 'string',
1585
1052
  visual: true,
1586
1053
  default: 'md',
1587
- // Fields come in exactly three sizes; legacy xs/xl map to sm/lg.
1588
1054
  validate: (v) => ['sm', 'md', 'lg'].includes(v),
1589
1055
  coerce: (v) => {
1590
1056
  if (v === 'xs')
@@ -1615,10 +1081,9 @@ TyDatePicker.properties = {
1615
1081
  },
1616
1082
  name: {
1617
1083
  type: 'string',
1618
- visual: false, // Non-visual, just for form field name
1084
+ visual: false,
1619
1085
  default: ''
1620
1086
  },
1621
- // Date bounds (ISO) — passed through to the embedded ty-calendar
1622
1087
  min: {
1623
1088
  type: 'string',
1624
1089
  visual: true,
@@ -1629,7 +1094,6 @@ TyDatePicker.properties = {
1629
1094
  visual: true,
1630
1095
  default: ''
1631
1096
  },
1632
- // Boolean properties
1633
1097
  required: {
1634
1098
  type: 'boolean',
1635
1099
  visual: true,
@@ -1648,7 +1112,6 @@ TyDatePicker.properties = {
1648
1112
  'not-clearable': false
1649
1113
  }
1650
1114
  },
1651
- // Format property (medium complexity)
1652
1115
  format: {
1653
1116
  type: 'string',
1654
1117
  visual: true,
@@ -1663,34 +1126,25 @@ TyDatePicker.properties = {
1663
1126
  return v;
1664
1127
  }
1665
1128
  },
1666
- // Locale property (medium complexity - has observer)
1667
1129
  locale: {
1668
1130
  type: 'string',
1669
1131
  visual: true,
1670
1132
  default: ''
1671
1133
  },
1672
- // Value property (high complexity - UTC parsing, date components)
1673
- // Note: Custom getter/setter will handle the complex logic
1674
1134
  value: {
1675
1135
  type: 'string',
1676
1136
  visual: true,
1677
- formValue: true, // Syncs to form
1678
- emitChange: false, // We emit custom 'change' events manually with full detail
1679
- default: null // null when no date selected
1137
+ formValue: true,
1138
+ emitChange: false,
1139
+ default: null
1680
1140
  },
1681
- // With-time property (high complexity - affects parsing and rendering)
1682
1141
  'with-time': {
1683
1142
  type: 'boolean',
1684
1143
  visual: true,
1685
1144
  default: false
1686
1145
  }
1687
1146
  };
1688
- /**
1689
- * Form-associated custom element
1690
- */
1691
1147
  TyDatePicker.formAssociated = true;
1692
- // Register custom element
1693
1148
  if (!customElements.get('ty-date-picker')) {
1694
1149
  customElements.define('ty-date-picker', TyDatePicker);
1695
1150
  }
1696
- //# sourceMappingURL=date-picker.js.map