tyrell-components 1.0.0-RC13 → 1.0.0-RC15

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 (358) hide show
  1. package/README.md +10 -2
  2. package/css/tyrell-theme.css +192 -180
  3. package/css/tyrell.css +23 -0
  4. package/dist/tyrell-colors-static.css +1 -316
  5. package/dist/tyrell-theme.css +1 -1454
  6. package/dist/tyrell.css +1 -1371
  7. package/dist/tyrell.js +1 -1
  8. package/lib/base/ty-component.d.ts +20 -10
  9. package/lib/base/ty-component.js +196 -319
  10. package/lib/components/button.d.ts +3 -17
  11. package/lib/components/button.js +261 -295
  12. package/lib/components/calendar-month.d.ts +4 -50
  13. package/lib/components/calendar-month.js +443 -582
  14. package/lib/components/calendar-navigation.d.ts +3 -48
  15. package/lib/components/calendar-navigation.js +239 -362
  16. package/lib/components/calendar.d.ts +8 -83
  17. package/lib/components/calendar.js +616 -829
  18. package/lib/components/checkbox.d.ts +5 -72
  19. package/lib/components/checkbox.js +273 -416
  20. package/lib/components/copy.d.ts +4 -57
  21. package/lib/components/copy.js +280 -397
  22. package/lib/components/date-picker.d.ts +21 -163
  23. package/lib/components/date-picker.js +1143 -1674
  24. package/lib/components/file-upload.d.ts +9 -15
  25. package/lib/components/file-upload.js +404 -426
  26. package/lib/components/icon.d.ts +3 -24
  27. package/lib/components/icon.js +141 -230
  28. package/lib/components/input.d.ts +8 -108
  29. package/lib/components/input.js +593 -784
  30. package/lib/components/modal.d.ts +17 -26
  31. package/lib/components/modal.js +340 -484
  32. package/lib/components/option.d.ts +2 -9
  33. package/lib/components/option.js +168 -216
  34. package/lib/components/popup.d.ts +2 -17
  35. package/lib/components/popup.js +249 -380
  36. package/lib/components/radio.d.ts +5 -25
  37. package/lib/components/radio.js +407 -461
  38. package/lib/components/resize-observer.d.ts +3 -30
  39. package/lib/components/resize-observer.js +69 -103
  40. package/lib/components/scroll-container.d.ts +2 -12
  41. package/lib/components/scroll-container.js +293 -338
  42. package/lib/components/select.d.ts +87 -159
  43. package/lib/components/select.js +1609 -2090
  44. package/lib/components/selected-tags.d.ts +5 -48
  45. package/lib/components/selected-tags.js +188 -276
  46. package/lib/components/step.d.ts +1 -6
  47. package/lib/components/step.js +62 -68
  48. package/lib/components/switch.d.ts +1 -12
  49. package/lib/components/switch.js +214 -247
  50. package/lib/components/tab.d.ts +1 -6
  51. package/lib/components/tab.js +62 -69
  52. package/lib/components/tabs.d.ts +6 -63
  53. package/lib/components/tabs.js +545 -777
  54. package/lib/components/tag.d.ts +3 -37
  55. package/lib/components/tag.js +236 -335
  56. package/lib/components/textarea.d.ts +7 -87
  57. package/lib/components/textarea.js +457 -627
  58. package/lib/components/tooltip.d.ts +3 -11
  59. package/lib/components/tooltip.js +307 -443
  60. package/lib/components/wizard.d.ts +6 -54
  61. package/lib/components/wizard.js +405 -618
  62. package/lib/icons/fontawesome/brands.d.ts +0 -1
  63. package/lib/icons/fontawesome/brands.js +0 -8
  64. package/lib/icons/fontawesome/regular.d.ts +0 -1
  65. package/lib/icons/fontawesome/regular.js +0 -8
  66. package/lib/icons/fontawesome/solid.d.ts +0 -1
  67. package/lib/icons/fontawesome/solid.js +0 -8
  68. package/lib/icons/heroicons/micro.d.ts +0 -1
  69. package/lib/icons/heroicons/micro.js +392 -400
  70. package/lib/icons/heroicons/mini.d.ts +0 -1
  71. package/lib/icons/heroicons/mini.js +382 -390
  72. package/lib/icons/heroicons/outline.d.ts +0 -1
  73. package/lib/icons/heroicons/outline.js +337 -345
  74. package/lib/icons/heroicons/solid.d.ts +0 -1
  75. package/lib/icons/heroicons/solid.js +407 -415
  76. package/lib/icons/lucide.d.ts +0 -1
  77. package/lib/icons/lucide.js +24479 -26351
  78. package/lib/icons/material/filled.d.ts +0 -1
  79. package/lib/icons/material/filled.js +9653 -9663
  80. package/lib/icons/material/outlined.d.ts +0 -1
  81. package/lib/icons/material/outlined.js +10271 -10281
  82. package/lib/icons/material/round.d.ts +0 -1
  83. package/lib/icons/material/round.js +10495 -10505
  84. package/lib/icons/material/sharp.d.ts +0 -1
  85. package/lib/icons/material/sharp.js +9905 -9915
  86. package/lib/icons/material/two-tone.d.ts +0 -1
  87. package/lib/icons/material/two-tone.js +12792 -12802
  88. package/lib/index.d.ts +0 -1
  89. package/lib/index.js +21 -41
  90. package/lib/styles/button.d.ts +2 -20
  91. package/lib/styles/button.js +202 -404
  92. package/lib/styles/calendar-month.d.ts +1 -5
  93. package/lib/styles/calendar-month.js +127 -244
  94. package/lib/styles/calendar-navigation.d.ts +2 -6
  95. package/lib/styles/calendar-navigation.js +64 -104
  96. package/lib/styles/calendar.d.ts +1 -2
  97. package/lib/styles/calendar.js +9 -22
  98. package/lib/styles/checkbox.d.ts +0 -1
  99. package/lib/styles/checkbox.js +45 -94
  100. package/lib/styles/copy.d.ts +0 -1
  101. package/lib/styles/copy.js +67 -106
  102. package/lib/styles/custom-scrollbar.d.ts +1 -2
  103. package/lib/styles/custom-scrollbar.js +80 -115
  104. package/lib/styles/date-picker.d.ts +1 -5
  105. package/lib/styles/date-picker.js +261 -335
  106. package/lib/styles/field-size.d.ts +6 -0
  107. package/lib/styles/field-size.js +91 -0
  108. package/lib/styles/file-upload.d.ts +1 -2
  109. package/lib/styles/file-upload.js +143 -188
  110. package/lib/styles/icon.d.ts +2 -6
  111. package/lib/styles/icon.js +92 -170
  112. package/lib/styles/input.d.ts +1 -6
  113. package/lib/styles/input.js +125 -290
  114. package/lib/styles/modal.d.ts +1 -2
  115. package/lib/styles/modal.js +71 -108
  116. package/lib/styles/option.d.ts +2 -6
  117. package/lib/styles/option.js +49 -64
  118. package/lib/styles/popup.d.ts +1 -2
  119. package/lib/styles/popup.js +42 -77
  120. package/lib/styles/radio.d.ts +0 -1
  121. package/lib/styles/radio.js +69 -110
  122. package/lib/styles/resize-observer.d.ts +1 -2
  123. package/lib/styles/resize-observer.js +2 -12
  124. package/lib/styles/scroll-container.d.ts +1 -2
  125. package/lib/styles/scroll-container.js +100 -149
  126. package/lib/styles/select-base.d.ts +0 -1
  127. package/lib/styles/select-base.js +597 -785
  128. package/lib/styles/select.d.ts +1 -2
  129. package/lib/styles/select.js +140 -236
  130. package/lib/styles/step.d.ts +1 -2
  131. package/lib/styles/step.js +22 -33
  132. package/lib/styles/switch.d.ts +0 -1
  133. package/lib/styles/switch.js +34 -73
  134. package/lib/styles/tab.d.ts +1 -2
  135. package/lib/styles/tab.js +22 -34
  136. package/lib/styles/tabs.d.ts +3 -11
  137. package/lib/styles/tabs.js +190 -222
  138. package/lib/styles/tag.d.ts +1 -5
  139. package/lib/styles/tag.js +136 -218
  140. package/lib/styles/textarea.d.ts +1 -2
  141. package/lib/styles/textarea.js +210 -319
  142. package/lib/styles/tooltip.d.ts +0 -1
  143. package/lib/styles/tooltip.js +3 -13
  144. package/lib/styles/wizard.d.ts +5 -21
  145. package/lib/styles/wizard.js +170 -306
  146. package/lib/types/common.d.ts +2 -2
  147. package/lib/types/common.js +6 -17
  148. package/lib/utils/calendar-utils.d.ts +4 -21
  149. package/lib/utils/calendar-utils.js +100 -273
  150. package/lib/utils/custom-scrollbar.d.ts +6 -20
  151. package/lib/utils/custom-scrollbar.js +273 -317
  152. package/lib/utils/flavor-sheet.d.ts +0 -1
  153. package/lib/utils/flavor-sheet.js +14 -37
  154. package/lib/utils/icon-registry.d.ts +0 -2
  155. package/lib/utils/icon-registry.js +137 -243
  156. package/lib/utils/icons.d.ts +0 -1
  157. package/lib/utils/icons.js +0 -9
  158. package/lib/utils/loader-registry.d.ts +0 -1
  159. package/lib/utils/loader-registry.js +3 -35
  160. package/lib/utils/locale.d.ts +2 -23
  161. package/lib/utils/locale.js +34 -120
  162. package/lib/utils/mobile.d.ts +0 -1
  163. package/lib/utils/mobile.js +1 -18
  164. package/lib/utils/number-format.d.ts +6 -3
  165. package/lib/utils/number-format.js +54 -110
  166. package/lib/utils/positioning.d.ts +50 -7
  167. package/lib/utils/positioning.js +224 -234
  168. package/lib/utils/property-capture.d.ts +5 -27
  169. package/lib/utils/property-capture.js +36 -144
  170. package/lib/utils/property-manager.d.ts +0 -1
  171. package/lib/utils/property-manager.js +126 -196
  172. package/lib/utils/resize-observer.d.ts +0 -1
  173. package/lib/utils/resize-observer.js +23 -59
  174. package/lib/utils/scroll-lock.d.ts +4 -11
  175. package/lib/utils/scroll-lock.js +65 -172
  176. package/lib/utils/styles.d.ts +0 -1
  177. package/lib/utils/styles.js +17 -41
  178. package/lib/version.d.ts +1 -2
  179. package/lib/version.js +1 -11
  180. package/package.json +4 -4
  181. package/lib/base/ty-component.d.ts.map +0 -1
  182. package/lib/base/ty-component.js.map +0 -1
  183. package/lib/components/button.d.ts.map +0 -1
  184. package/lib/components/button.js.map +0 -1
  185. package/lib/components/calendar-month.d.ts.map +0 -1
  186. package/lib/components/calendar-month.js.map +0 -1
  187. package/lib/components/calendar-navigation.d.ts.map +0 -1
  188. package/lib/components/calendar-navigation.js.map +0 -1
  189. package/lib/components/calendar.d.ts.map +0 -1
  190. package/lib/components/calendar.js.map +0 -1
  191. package/lib/components/checkbox.d.ts.map +0 -1
  192. package/lib/components/checkbox.js.map +0 -1
  193. package/lib/components/copy.d.ts.map +0 -1
  194. package/lib/components/copy.js.map +0 -1
  195. package/lib/components/date-picker.d.ts.map +0 -1
  196. package/lib/components/date-picker.js.map +0 -1
  197. package/lib/components/file-upload.d.ts.map +0 -1
  198. package/lib/components/file-upload.js.map +0 -1
  199. package/lib/components/icon.d.ts.map +0 -1
  200. package/lib/components/icon.js.map +0 -1
  201. package/lib/components/input.d.ts.map +0 -1
  202. package/lib/components/input.js.map +0 -1
  203. package/lib/components/modal.d.ts.map +0 -1
  204. package/lib/components/modal.js.map +0 -1
  205. package/lib/components/option.d.ts.map +0 -1
  206. package/lib/components/option.js.map +0 -1
  207. package/lib/components/popup.d.ts.map +0 -1
  208. package/lib/components/popup.js.map +0 -1
  209. package/lib/components/radio.d.ts.map +0 -1
  210. package/lib/components/radio.js.map +0 -1
  211. package/lib/components/resize-observer.d.ts.map +0 -1
  212. package/lib/components/resize-observer.js.map +0 -1
  213. package/lib/components/scroll-container.d.ts.map +0 -1
  214. package/lib/components/scroll-container.js.map +0 -1
  215. package/lib/components/select.d.ts.map +0 -1
  216. package/lib/components/select.js.map +0 -1
  217. package/lib/components/selected-tags.d.ts.map +0 -1
  218. package/lib/components/selected-tags.js.map +0 -1
  219. package/lib/components/step.d.ts.map +0 -1
  220. package/lib/components/step.js.map +0 -1
  221. package/lib/components/switch.d.ts.map +0 -1
  222. package/lib/components/switch.js.map +0 -1
  223. package/lib/components/tab.d.ts.map +0 -1
  224. package/lib/components/tab.js.map +0 -1
  225. package/lib/components/tabs.d.ts.map +0 -1
  226. package/lib/components/tabs.js.map +0 -1
  227. package/lib/components/tag.d.ts.map +0 -1
  228. package/lib/components/tag.js.map +0 -1
  229. package/lib/components/textarea.d.ts.map +0 -1
  230. package/lib/components/textarea.js.map +0 -1
  231. package/lib/components/tooltip.d.ts.map +0 -1
  232. package/lib/components/tooltip.js.map +0 -1
  233. package/lib/components/wizard.d.ts.map +0 -1
  234. package/lib/components/wizard.js.map +0 -1
  235. package/lib/icons/fontawesome/brands.d.ts.map +0 -1
  236. package/lib/icons/fontawesome/brands.js.map +0 -1
  237. package/lib/icons/fontawesome/regular.d.ts.map +0 -1
  238. package/lib/icons/fontawesome/regular.js.map +0 -1
  239. package/lib/icons/fontawesome/solid.d.ts.map +0 -1
  240. package/lib/icons/fontawesome/solid.js.map +0 -1
  241. package/lib/icons/heroicons/micro.d.ts.map +0 -1
  242. package/lib/icons/heroicons/micro.js.map +0 -1
  243. package/lib/icons/heroicons/mini.d.ts.map +0 -1
  244. package/lib/icons/heroicons/mini.js.map +0 -1
  245. package/lib/icons/heroicons/outline.d.ts.map +0 -1
  246. package/lib/icons/heroicons/outline.js.map +0 -1
  247. package/lib/icons/heroicons/solid.d.ts.map +0 -1
  248. package/lib/icons/heroicons/solid.js.map +0 -1
  249. package/lib/icons/lucide.d.ts.map +0 -1
  250. package/lib/icons/lucide.js.map +0 -1
  251. package/lib/icons/material/filled.d.ts.map +0 -1
  252. package/lib/icons/material/filled.js.map +0 -1
  253. package/lib/icons/material/outlined.d.ts.map +0 -1
  254. package/lib/icons/material/outlined.js.map +0 -1
  255. package/lib/icons/material/round.d.ts.map +0 -1
  256. package/lib/icons/material/round.js.map +0 -1
  257. package/lib/icons/material/sharp.d.ts.map +0 -1
  258. package/lib/icons/material/sharp.js.map +0 -1
  259. package/lib/icons/material/two-tone.d.ts.map +0 -1
  260. package/lib/icons/material/two-tone.js.map +0 -1
  261. package/lib/index.d.ts.map +0 -1
  262. package/lib/index.js.map +0 -1
  263. package/lib/styles/button.d.ts.map +0 -1
  264. package/lib/styles/button.js.map +0 -1
  265. package/lib/styles/calendar-month.d.ts.map +0 -1
  266. package/lib/styles/calendar-month.js.map +0 -1
  267. package/lib/styles/calendar-navigation.d.ts.map +0 -1
  268. package/lib/styles/calendar-navigation.js.map +0 -1
  269. package/lib/styles/calendar.d.ts.map +0 -1
  270. package/lib/styles/calendar.js.map +0 -1
  271. package/lib/styles/checkbox.d.ts.map +0 -1
  272. package/lib/styles/checkbox.js.map +0 -1
  273. package/lib/styles/copy.d.ts.map +0 -1
  274. package/lib/styles/copy.js.map +0 -1
  275. package/lib/styles/custom-scrollbar.d.ts.map +0 -1
  276. package/lib/styles/custom-scrollbar.js.map +0 -1
  277. package/lib/styles/date-picker.d.ts.map +0 -1
  278. package/lib/styles/date-picker.js.map +0 -1
  279. package/lib/styles/file-upload.d.ts.map +0 -1
  280. package/lib/styles/file-upload.js.map +0 -1
  281. package/lib/styles/icon.d.ts.map +0 -1
  282. package/lib/styles/icon.js.map +0 -1
  283. package/lib/styles/input.d.ts.map +0 -1
  284. package/lib/styles/input.js.map +0 -1
  285. package/lib/styles/modal.d.ts.map +0 -1
  286. package/lib/styles/modal.js.map +0 -1
  287. package/lib/styles/multiselect.d.ts +0 -8
  288. package/lib/styles/multiselect.d.ts.map +0 -1
  289. package/lib/styles/multiselect.js +0 -849
  290. package/lib/styles/multiselect.js.map +0 -1
  291. package/lib/styles/option.d.ts.map +0 -1
  292. package/lib/styles/option.js.map +0 -1
  293. package/lib/styles/popup.d.ts.map +0 -1
  294. package/lib/styles/popup.js.map +0 -1
  295. package/lib/styles/radio.d.ts.map +0 -1
  296. package/lib/styles/radio.js.map +0 -1
  297. package/lib/styles/resize-observer.d.ts.map +0 -1
  298. package/lib/styles/resize-observer.js.map +0 -1
  299. package/lib/styles/scroll-container.d.ts.map +0 -1
  300. package/lib/styles/scroll-container.js.map +0 -1
  301. package/lib/styles/select-base.d.ts.map +0 -1
  302. package/lib/styles/select-base.js.map +0 -1
  303. package/lib/styles/select.d.ts.map +0 -1
  304. package/lib/styles/select.js.map +0 -1
  305. package/lib/styles/step.d.ts.map +0 -1
  306. package/lib/styles/step.js.map +0 -1
  307. package/lib/styles/switch.d.ts.map +0 -1
  308. package/lib/styles/switch.js.map +0 -1
  309. package/lib/styles/tab.d.ts.map +0 -1
  310. package/lib/styles/tab.js.map +0 -1
  311. package/lib/styles/tabs.d.ts.map +0 -1
  312. package/lib/styles/tabs.js.map +0 -1
  313. package/lib/styles/tag.d.ts.map +0 -1
  314. package/lib/styles/tag.js.map +0 -1
  315. package/lib/styles/textarea.d.ts.map +0 -1
  316. package/lib/styles/textarea.js.map +0 -1
  317. package/lib/styles/tooltip.d.ts.map +0 -1
  318. package/lib/styles/tooltip.js.map +0 -1
  319. package/lib/styles/wizard.d.ts.map +0 -1
  320. package/lib/styles/wizard.js.map +0 -1
  321. package/lib/types/common.d.ts.map +0 -1
  322. package/lib/types/common.js.map +0 -1
  323. package/lib/utils/calendar-utils.d.ts.map +0 -1
  324. package/lib/utils/calendar-utils.js.map +0 -1
  325. package/lib/utils/custom-scrollbar.d.ts.map +0 -1
  326. package/lib/utils/custom-scrollbar.js.map +0 -1
  327. package/lib/utils/flavor-sheet.d.ts.map +0 -1
  328. package/lib/utils/flavor-sheet.js.map +0 -1
  329. package/lib/utils/icon-registry.d.ts.map +0 -1
  330. package/lib/utils/icon-registry.js.map +0 -1
  331. package/lib/utils/icons.d.ts.map +0 -1
  332. package/lib/utils/icons.js.map +0 -1
  333. package/lib/utils/loader-registry.d.ts.map +0 -1
  334. package/lib/utils/loader-registry.js.map +0 -1
  335. package/lib/utils/locale.d.ts.map +0 -1
  336. package/lib/utils/locale.js.map +0 -1
  337. package/lib/utils/mobile.d.ts.map +0 -1
  338. package/lib/utils/mobile.js.map +0 -1
  339. package/lib/utils/number-format.d.ts.map +0 -1
  340. package/lib/utils/number-format.js.map +0 -1
  341. package/lib/utils/parse-boolean.d.ts +0 -39
  342. package/lib/utils/parse-boolean.d.ts.map +0 -1
  343. package/lib/utils/parse-boolean.js +0 -58
  344. package/lib/utils/parse-boolean.js.map +0 -1
  345. package/lib/utils/positioning.d.ts.map +0 -1
  346. package/lib/utils/positioning.js.map +0 -1
  347. package/lib/utils/property-capture.d.ts.map +0 -1
  348. package/lib/utils/property-capture.js.map +0 -1
  349. package/lib/utils/property-manager.d.ts.map +0 -1
  350. package/lib/utils/property-manager.js.map +0 -1
  351. package/lib/utils/resize-observer.d.ts.map +0 -1
  352. package/lib/utils/resize-observer.js.map +0 -1
  353. package/lib/utils/scroll-lock.d.ts.map +0 -1
  354. package/lib/utils/scroll-lock.js.map +0 -1
  355. package/lib/utils/styles.d.ts.map +0 -1
  356. package/lib/utils/styles.js.map +0 -1
  357. package/lib/version.d.ts.map +0 -1
  358. package/lib/version.js.map +0 -1
@@ -1,845 +1,632 @@
1
- /**
2
- * TyCalendar Web Component
3
- * PORTED FROM: cljs/ty/components/calendar.cljs
4
- *
5
- * A complete calendar orchestration component that combines navigation and month display.
6
- * Manages selection state, form participation, and event coordination.
7
- *
8
- * Architecture:
9
- * - year/month/day HTML attributes for intuitive API
10
- * - Internal state management (private properties)
11
- * - Distributes properties to child components (navigation + month)
12
- * - Single 'change' event with complete day context
13
- * - Form participation via ElementInternals
14
- *
15
- * Features:
16
- * - Combines ty-calendar-navigation + ty-calendar-month
17
- * - Date selection with visual feedback
18
- * - Form integration (works with FormData)
19
- * - Custom render functions (dayContentFn)
20
- * - Custom CSS injection
21
- * - Automatic date validation
22
- * - Event coordination between components
23
- *
24
- * @example
25
- * ```html
26
- * <!-- Basic usage -->
27
- * <ty-calendar
28
- * year="2025"
29
- * month="10"
30
- * day="15">
31
- * </ty-calendar>
32
- *
33
- * <!-- Form integration -->
34
- * <form>
35
- * <ty-calendar name="booking-date"></ty-calendar>
36
- * <button type="submit">Submit</button>
37
- * </form>
38
- *
39
- * <!-- With custom rendering -->
40
- * <ty-calendar id="custom"></ty-calendar>
41
- * <script type="module">
42
- * const cal = document.getElementById('custom');
43
- * cal.dayContentFn = (ctx) => {
44
- * const el = document.createElement('div');
45
- * el.textContent = ctx.dayInMonth;
46
- * if (ctx.today) el.style.fontWeight = 'bold';
47
- * return el;
48
- * };
49
- * </script>
50
- * ```
51
- */
52
1
  import { ensureStyles } from '../utils/styles.js';
53
2
  import { calendarStyles } from '../styles/calendar.js';
54
- // Side-effect imports: register the child elements this component createElements
55
3
  import './calendar-month.js';
56
4
  import './calendar-navigation.js';
57
5
  import { parseISODate } from '../utils/calendar-utils.js';
58
6
  import { getEffectiveLocale, observeLocaleChanges } from '../utils/locale.js';
59
- // ============================================================================
60
- // Helper Functions
61
- // ============================================================================
62
- /**
63
- * Get current date for defaults
64
- */
65
7
  function getCurrentDate() {
66
- const now = new Date();
67
- return {
68
- year: now.getFullYear(),
69
- month: now.getMonth() + 1, // 1-based
70
- day: now.getDate(),
71
- };
72
- }
73
- /**
74
- * Validate and parse year from string
75
- */
8
+ const now = new Date();
9
+ return {
10
+ year: now.getFullYear(),
11
+ month: now.getMonth() + 1,
12
+ day: now.getDate(),
13
+ };
14
+ }
76
15
  function parseYear(yearStr) {
77
- if (!yearStr || !/^\d{4}$/.test(yearStr))
78
- return null;
79
- return parseInt(yearStr, 10);
16
+ if (!yearStr || !/^\d{4}$/.test(yearStr))
17
+ return null;
18
+ return parseInt(yearStr, 10);
80
19
  }
81
- /**
82
- * Validate and parse month from string (1-12)
83
- */
84
20
  function parseMonth(monthStr) {
85
- if (!monthStr || !/^\d{1,2}$/.test(monthStr))
86
- return null;
87
- const month = parseInt(monthStr, 10);
88
- return month >= 1 && month <= 12 ? month : null;
89
- }
90
- /**
91
- * Validate and parse day from string (1-31, validated against month)
92
- */
21
+ if (!monthStr || !/^\d{1,2}$/.test(monthStr))
22
+ return null;
23
+ const month = parseInt(monthStr, 10);
24
+ return month >= 1 && month <= 12 ? month : null;
25
+ }
93
26
  function parseDay(dayStr, year, month) {
94
- if (!dayStr || !/^\d{1,2}$/.test(dayStr))
95
- return null;
96
- const day = parseInt(dayStr, 10);
97
- // Get days in month for validation
98
- const daysInMonth = new Date(year, month, 0).getDate();
99
- return day >= 1 && day <= daysInMonth ? day : null;
100
- }
101
- /**
102
- * Format date as ISO string (YYYY-MM-DD) for form submission
103
- */
27
+ if (!dayStr || !/^\d{1,2}$/.test(dayStr))
28
+ return null;
29
+ const day = parseInt(dayStr, 10);
30
+ const daysInMonth = new Date(year, month, 0).getDate();
31
+ return day >= 1 && day <= daysInMonth ? day : null;
32
+ }
104
33
  function formatDateISO(year, month, day) {
105
- const monthStr = month.toString().padStart(2, '0');
106
- const dayStr = day.toString().padStart(2, '0');
107
- return `${year}-${monthStr}-${dayStr}`;
108
- }
109
- // ============================================================================
110
- // Component Implementation
111
- // ============================================================================
112
- /**
113
- * TyCalendar Web Component
114
- */
34
+ const monthStr = month.toString().padStart(2, '0');
35
+ const dayStr = day.toString().padStart(2, '0');
36
+ return `${year}-${monthStr}-${dayStr}`;
37
+ }
115
38
  export class TyCalendar extends HTMLElement {
116
- /**
117
- * Observed attributes
118
- */
119
- static get observedAttributes() {
120
- return ['year', 'month', 'day', 'show-navigation', 'stateless', 'locale', 'name', 'size', 'width', 'min', 'max', 'flavor'];
121
- }
122
- constructor() {
123
- super();
124
- this._showNavigation = true;
125
- this._stateless = false;
126
- this._size = 'md';
127
- this._flavor = 'primary'; // passed to month display only (nav stays neutral chrome)
128
- // Initialize state with current date
129
- const current = getCurrentDate();
130
- this._state = {
131
- displayYear: current.year,
132
- displayMonth: current.month,
133
- };
134
- this.attachShadow({ mode: 'open' });
135
- // Attach ElementInternals for form participation
136
- if ('attachInternals' in this) {
137
- this._internals = this.attachInternals();
138
- }
139
- }
140
- // ==========================================================================
141
- // Lifecycle Methods
142
- // ==========================================================================
143
- connectedCallback() {
144
- // Parse initial attributes
145
- this.initializeFromAttributes();
146
- // ✅ CHECK: Properties set before custom element upgrade
147
- // When scripts run before element is fully upgraded, properties are set as plain properties
148
- // without triggering setters. We need to migrate them to private fields.
149
- // Check for dayContentFn set before upgrade
150
- const plainDayContentFn = this.dayContentFn;
151
- if (plainDayContentFn && !this._dayContentFn) {
152
- this._dayContentFn = plainDayContentFn;
153
- delete this.dayContentFn; // Clean up plain property
154
- }
155
- // Check for customCSS set before upgrade
156
- const plainCustomCSS = this.customCSS;
157
- if (plainCustomCSS && !this._customCSS) {
158
- this._customCSS = plainCustomCSS;
159
- delete this.customCSS; // Clean up plain property
160
- }
161
- // Check for value set before upgrade
162
- const plainValue = this.value;
163
- if (plainValue && typeof plainValue === 'string' && plainValue !== this.value) {
164
- // Parse and set the value properly
165
- const match = plainValue.match(/^(\d{4})-(\d{2})-(\d{2})$/);
166
- if (match) {
167
- const year = parseInt(match[1], 10);
168
- const month = parseInt(match[2], 10);
169
- const day = parseInt(match[3], 10);
170
- this._state.selectedYear = year;
171
- this._state.selectedMonth = month;
172
- this._state.selectedDay = day;
173
- this._state.displayYear = year;
174
- this._state.displayMonth = month;
175
- // Update attributes if not in stateless mode
176
- if (!this._stateless) {
177
- this.setAttribute('year', year.toString());
178
- this.setAttribute('month', month.toString());
179
- this.setAttribute('day', day.toString());
180
- }
181
- }
182
- delete this.value; // Clean up plain property
183
- }
184
- // Render the calendar
185
- this.render();
186
- // Set initial form value if date is selected
187
- this.updateFormValue();
188
- // Setup locale observer to watch for ancestor lang changes
189
- this._localeObserver = observeLocaleChanges(this, () => {
190
- this.render();
191
- });
192
- }
193
- disconnectedCallback() {
194
- // Cleanup locale observer
195
- if (this._localeObserver) {
196
- this._localeObserver();
197
- this._localeObserver = undefined;
198
- }
199
- // Cleanup references
200
- this._navigation = undefined;
201
- this._monthDisplay = undefined;
202
- }
203
- attributeChangedCallback(name, oldValue, newValue) {
204
- if (oldValue === newValue)
205
- return;
206
- switch (name) {
207
- case 'year':
208
- case 'month':
209
- case 'day':
210
- this.syncStateFromAttributes();
211
- this.syncChildComponents();
212
- this.updateFormValue();
213
- break;
214
- case 'show-navigation':
215
- this._showNavigation = newValue !== 'false';
216
- this.render();
217
- break;
218
- case 'stateless':
219
- this._stateless = newValue !== null && newValue !== 'false';
220
- // No re-render needed, just changes behavior
221
- break;
222
- case 'locale':
223
- // Attribute is the source of truth — `get locale` resolves it via
224
- // getEffectiveLocale; children just need a re-sync.
225
- this.syncChildComponents();
226
- break;
227
- case 'size':
228
- if (newValue === 'sm' || newValue === 'md' || newValue === 'lg') {
229
- this._size = newValue;
230
- // Clear width when size is set (mutually exclusive)
231
- this._width = undefined;
232
- this.removeAttribute('width');
233
- this.syncChildComponents();
234
- }
235
- break;
236
- case 'width':
237
- this._width = newValue || undefined;
238
- // Clear size when width is set (mutually exclusive)
239
- if (newValue) {
240
- this._size = 'md'; // Reset to default
241
- this.removeAttribute('size');
242
- }
243
- this.syncChildComponents();
244
- break;
245
- case 'name':
246
- // Name change triggers form value update
247
- this.updateFormValue();
248
- break;
249
- case 'min':
250
- case 'max':
251
- this[name === 'min' ? '_min' : '_max'] = newValue || undefined;
252
- this.syncChildComponents();
253
- this.updateValidity(); // bounds change can (in)validate current selection
254
- break;
255
- case 'flavor':
256
- this._flavor = newValue || 'primary';
257
- this.syncChildComponents();
258
- break;
259
- }
260
- }
261
- // ==========================================================================
262
- // Property Getters/Setters
263
- // ==========================================================================
264
- get min() {
265
- return this._min;
266
- }
267
- set min(value) {
268
- // Reflect to attribute; attributeChangedCallback syncs children
269
- if (value)
270
- this.setAttribute('min', value);
271
- else
272
- this.removeAttribute('min');
273
- }
274
- get max() {
275
- return this._max;
276
- }
277
- set max(value) {
278
- if (value)
279
- this.setAttribute('max', value);
280
- else
281
- this.removeAttribute('max');
282
- }
283
- get flavor() {
284
- return this._flavor;
285
- }
286
- set flavor(value) {
287
- if (value)
288
- this.setAttribute('flavor', value);
289
- else
290
- this.removeAttribute('flavor');
291
- }
292
- get year() {
293
- return this._state.selectedYear;
294
- }
295
- set year(value) {
296
- if (value !== undefined) {
297
- // In stateless mode, only update display
298
- if (this._stateless) {
299
- this._state.displayYear = value;
300
- this.setAttribute('year', value.toString());
301
- }
302
- else {
303
- // In stateful mode, update both selection and display
304
- this._state.selectedYear = value;
305
- this._state.displayYear = value;
306
- this.setAttribute('year', value.toString());
307
- }
308
- }
309
- else {
310
- if (!this._stateless) {
311
- delete this._state.selectedYear;
312
- }
313
- this.removeAttribute('year');
314
- }
315
- this.syncChildComponents();
316
- if (!this._stateless) {
317
- this.updateFormValue();
318
- }
319
- }
320
- get month() {
321
- return this._state.selectedMonth;
322
- }
323
- set month(value) {
324
- if (value !== undefined) {
325
- // In stateless mode, only update display
326
- if (this._stateless) {
327
- this._state.displayMonth = value;
328
- this.setAttribute('month', value.toString());
329
- }
330
- else {
331
- // In stateful mode, update both selection and display
332
- this._state.selectedMonth = value;
333
- this._state.displayMonth = value;
334
- this.setAttribute('month', value.toString());
335
- }
336
- }
337
- else {
338
- if (!this._stateless) {
339
- delete this._state.selectedMonth;
340
- }
341
- this.removeAttribute('month');
342
- }
343
- this.syncChildComponents();
344
- if (!this._stateless) {
345
- this.updateFormValue();
346
- }
347
- }
348
- get day() {
349
- return this._state.selectedDay;
350
- }
351
- set day(value) {
352
- if (value !== undefined) {
353
- this._state.selectedDay = value;
354
- this.setAttribute('day', value.toString());
355
- }
356
- else {
357
- delete this._state.selectedDay;
358
- this.removeAttribute('day');
359
- }
360
- this.syncChildComponents();
361
- this.updateFormValue();
362
- }
363
- get locale() {
364
- return getEffectiveLocale(this, this.getAttribute('locale'));
365
- }
366
- set locale(value) {
367
- this.setAttribute('locale', value);
368
- }
369
- get showNavigation() {
370
- return this._showNavigation;
371
- }
372
- set showNavigation(value) {
373
- this._showNavigation = value;
374
- this.setAttribute('show-navigation', value.toString());
375
- }
376
- get stateless() {
377
- return this._stateless;
378
- }
379
- set stateless(value) {
380
- this._stateless = value;
381
- if (value) {
382
- this.setAttribute('stateless', '');
383
- }
384
- else {
385
- this.removeAttribute('stateless');
386
- }
387
- }
388
- get size() {
389
- return this._size;
390
- }
391
- set size(value) {
392
- if (this._size !== value) {
393
- this._size = value;
394
- // Clear width when size is set (mutually exclusive)
395
- this._width = undefined;
396
- this.removeAttribute('width');
397
- this.setAttribute('size', value);
398
- this.syncChildComponents();
399
- }
400
- }
401
- get width() {
402
- return this._width;
403
- }
404
- set width(value) {
405
- if (this._width !== value) {
406
- this._width = value;
407
- if (value) {
408
- // Clear size when width is set (mutually exclusive)
409
- this._size = 'md'; // Reset to default
410
- this.removeAttribute('size');
411
- this.setAttribute('width', value);
412
- }
413
- else {
414
- this.removeAttribute('width');
415
- }
416
- this.syncChildComponents();
417
- }
418
- }
419
- get dayContentFn() {
420
- return this._dayContentFn;
421
- }
422
- set dayContentFn(fn) {
423
- this._dayContentFn = fn;
424
- this.syncChildComponents();
425
- }
426
- get customCSS() {
427
- return this._customCSS;
428
- }
429
- set customCSS(sheet) {
430
- this._customCSS = sheet;
431
- this.syncChildComponents();
432
- }
433
- get value() {
434
- const { selectedYear, selectedMonth, selectedDay } = this._state;
435
- if (selectedYear && selectedMonth && selectedDay) {
436
- return formatDateISO(selectedYear, selectedMonth, selectedDay);
437
- }
438
- return '';
439
- }
440
- set value(isoDate) {
441
- if (!isoDate) {
442
- // Clear selection
443
- delete this._state.selectedYear;
444
- delete this._state.selectedMonth;
445
- delete this._state.selectedDay;
446
- // In normal mode, also clear attributes
447
- if (!this._stateless) {
448
- this.removeAttribute('year');
449
- this.removeAttribute('month');
450
- this.removeAttribute('day');
451
- }
452
- }
453
- else {
454
- // Parse ISO date (YYYY-MM-DD)
455
- const match = isoDate.match(/^(\d{4})-(\d{2})-(\d{2})$/);
456
- if (match) {
457
- const year = parseInt(match[1], 10);
458
- const month = parseInt(match[2], 10);
459
- const day = parseInt(match[3], 10);
460
- // Update internal selection state
461
- this._state.selectedYear = year;
462
- this._state.selectedMonth = month;
463
- this._state.selectedDay = day;
464
- // Update display to show this month
465
- this._state.displayYear = year;
466
- this._state.displayMonth = month;
467
- // In normal mode, update attributes
468
- // In stateless mode, skip attribute updates (parent controls attributes)
469
- if (!this._stateless) {
470
- this.setAttribute('year', year.toString());
471
- this.setAttribute('month', month.toString());
472
- this.setAttribute('day', day.toString());
473
- }
474
- }
475
- }
476
- this.syncChildComponents();
477
- // In normal mode, update form value
478
- if (!this._stateless) {
479
- this.updateFormValue();
480
- }
481
- }
482
- // ==========================================================================
483
- // State Management
484
- // ==========================================================================
485
- /**
486
- * Initialize state from HTML attributes on first load
487
- */
488
- initializeFromAttributes() {
489
- const yearStr = this.getAttribute('year');
490
- const monthStr = this.getAttribute('month');
491
- const dayStr = this.getAttribute('day');
492
- const showNavStr = this.getAttribute('show-navigation');
493
- const statelessStr = this.getAttribute('stateless');
494
- const sizeStr = this.getAttribute('size');
495
- const widthStr = this.getAttribute('width');
496
- // Date bounds
497
- this._min = this.getAttribute('min') || undefined;
498
- this._max = this.getAttribute('max') || undefined;
499
- // Parse year
500
- const year = parseYear(yearStr);
501
- if (year) {
502
- this._state.displayYear = year;
503
- }
504
- // Parse month
505
- const month = parseMonth(monthStr);
506
- if (month) {
507
- this._state.displayMonth = month;
508
- }
509
- // Parse day (only if year and month are valid)
510
- if (year && month) {
511
- const day = parseDay(dayStr, year, month);
512
- if (day) {
513
- this._state.selectedYear = year;
514
- this._state.selectedMonth = month;
515
- this._state.selectedDay = day;
516
- }
517
- }
518
- // Show navigation
519
- if (showNavStr) {
520
- this._showNavigation = showNavStr !== 'false';
521
- }
522
- // Stateless mode
523
- if (statelessStr !== null) {
524
- this._stateless = statelessStr !== 'false';
525
- }
526
- // Size
527
- if (sizeStr && (sizeStr === 'sm' || sizeStr === 'md' || sizeStr === 'lg')) {
528
- this._size = sizeStr;
529
- }
530
- // Width
531
- if (widthStr) {
532
- this._width = widthStr;
533
- }
534
- }
535
- /**
536
- * Sync state from changed attributes
537
- */
538
- syncStateFromAttributes() {
539
- const yearStr = this.getAttribute('year');
540
- const monthStr = this.getAttribute('month');
541
- const dayStr = this.getAttribute('day');
542
- const year = parseYear(yearStr);
543
- const month = parseMonth(monthStr);
544
- // Update display year/month
545
- if (year)
546
- this._state.displayYear = year;
547
- if (month)
548
- this._state.displayMonth = month;
549
- // Update selection (only if all three are valid)
550
- if (year && month) {
551
- const day = parseDay(dayStr, year, month);
552
- if (day) {
553
- this._state.selectedYear = year;
554
- this._state.selectedMonth = month;
555
- this._state.selectedDay = day;
556
- }
557
- else {
558
- // Clear selection if day is invalid
559
- delete this._state.selectedYear;
560
- delete this._state.selectedMonth;
561
- delete this._state.selectedDay;
562
- }
563
- }
564
- else {
565
- // Clear selection if year or month is invalid
566
- delete this._state.selectedYear;
567
- delete this._state.selectedMonth;
568
- delete this._state.selectedDay;
569
- }
570
- }
571
- /**
572
- * Update child components with current state
573
- */
574
- syncChildComponents() {
575
- // Update navigation
576
- if (this._navigation) {
577
- this._navigation.displayMonth = this._state.displayMonth;
578
- this._navigation.displayYear = this._state.displayYear;
579
- this._navigation.locale = this.locale;
580
- this._navigation.size = this._size;
581
- this._navigation.min = this._min ?? '';
582
- this._navigation.max = this._max ?? '';
583
- // Always sync width (set or clear)
584
- this._navigation.width = this._width;
585
- }
586
- // Update month display
587
- if (this._monthDisplay) {
588
- this._monthDisplay.displayMonth = this._state.displayMonth;
589
- this._monthDisplay.displayYear = this._state.displayYear;
590
- this._monthDisplay.locale = this.locale;
591
- this._monthDisplay.size = this._size;
592
- this._monthDisplay.min = this._min;
593
- this._monthDisplay.max = this._max;
594
- this._monthDisplay.flavor = this._flavor;
595
- // Always sync width (set or clear)
596
- this._monthDisplay.width = this._width;
597
- // Pass render functions
598
- if (this._dayContentFn) {
599
- this._monthDisplay.dayContentFn = this._dayContentFn;
600
- }
601
- // Pass custom CSS
602
- if (this._customCSS) {
603
- this._monthDisplay.customCSS = this._customCSS;
604
- }
605
- // Update selection value (convert to Date timestamp)
606
- const { selectedYear, selectedMonth, selectedDay } = this._state;
607
- if (selectedYear && selectedMonth && selectedDay) {
608
- const date = new Date(selectedYear, selectedMonth - 1, selectedDay);
609
- this._monthDisplay.value = date.getTime();
610
- }
611
- else {
612
- this._monthDisplay.value = null;
613
- }
614
- }
615
- }
616
- /**
617
- * Update form value using ElementInternals
618
- */
619
- updateFormValue() {
620
- // Skip form participation in stateless mode
621
- if (this._stateless)
622
- return;
623
- if (!this._internals)
624
- return;
625
- const elementName = this.getAttribute('name');
626
- const { selectedYear, selectedMonth, selectedDay } = this._state;
627
- if (elementName && selectedYear && selectedMonth && selectedDay) {
628
- const isoDate = formatDateISO(selectedYear, selectedMonth, selectedDay);
629
- this._internals.setFormValue(isoDate);
630
- }
631
- else {
632
- this._internals.setFormValue('');
633
- }
634
- this.updateValidity();
635
- }
636
- /**
637
- * Selection outside [min, max] => rangeUnderflow/rangeOverflow.
638
- * UI can't produce this (days are disabled), but programmatic values can.
639
- */
640
- updateValidity() {
641
- if (!this._internals)
642
- return;
643
- const { selectedYear, selectedMonth, selectedDay } = this._state;
644
- if (selectedYear && selectedMonth && selectedDay) {
645
- const sel = Date.UTC(selectedYear, selectedMonth - 1, selectedDay);
646
- const min = this._min ? parseISODate(this._min) : null;
647
- const max = this._max ? parseISODate(this._max) : null;
648
- if (min && sel < Date.UTC(min.year, min.month - 1, min.day)) {
649
- this._internals.setValidity({ rangeUnderflow: true }, `Date must be ${this._min} or later`);
650
- return;
651
- }
652
- if (max && sel > Date.UTC(max.year, max.month - 1, max.day)) {
653
- this._internals.setValidity({ rangeOverflow: true }, `Date must be ${this._max} or earlier`);
654
- return;
655
- }
656
- }
657
- this._internals.setValidity({});
658
- }
659
- // ==========================================================================
660
- // Event Handlers
661
- // ==========================================================================
662
- /**
663
- * Handle navigation change (month/year navigation)
664
- */
665
- handleNavigationChange(event) {
666
- event.preventDefault();
667
- event.stopPropagation();
668
- const { month, year } = event.detail;
669
- // Update display state
670
- this._state.displayMonth = month;
671
- this._state.displayYear = year;
672
- // Sync child components
673
- this.syncChildComponents();
674
- // Emit navigate event
675
- const navigateDetail = {
676
- month,
677
- year,
678
- action: 'navigate',
679
- source: 'navigation',
680
- };
681
- this.dispatchEvent(new CustomEvent('navigate', {
682
- detail: navigateDetail,
683
- bubbles: true,
684
- composed: true,
685
- cancelable: false,
686
- }));
687
- }
688
- /**
689
- * Handle day click (day selection)
690
- */
691
- handleDayClick(event) {
692
- event.preventDefault();
693
- event.stopPropagation();
694
- const { dayContext, year, month, day } = event.detail;
695
- // In stateless mode, just re-dispatch the event without updating internal state
696
- if (this._stateless) {
697
- // Re-dispatch day-click event for parent to handle
698
- this.dispatchEvent(new CustomEvent('day-click', {
699
- detail: event.detail,
700
- bubbles: true,
701
- composed: true,
702
- cancelable: true,
703
- }));
704
- return;
705
- }
706
- // Normal mode: update selection state
707
- this._state.selectedYear = year;
708
- this._state.selectedMonth = month;
709
- this._state.selectedDay = day;
710
- this._state.displayYear = year;
711
- this._state.displayMonth = month;
712
- // Update HTML attributes
713
- this.setAttribute('year', year.toString());
714
- this.setAttribute('month', month.toString());
715
- this.setAttribute('day', day.toString());
716
- // Update form value
717
- this.updateFormValue();
718
- // Sync child components
719
- this.syncChildComponents();
720
- // Emit change event
721
- const changeDetail = {
722
- year,
723
- month,
724
- day,
725
- action: 'select',
726
- source: 'day-click',
727
- dayContext,
728
- };
729
- this.dispatchEvent(new CustomEvent('change', {
730
- detail: changeDetail,
731
- bubbles: true,
732
- composed: true,
733
- cancelable: false,
734
- }));
735
- }
736
- // ==========================================================================
737
- // Public Methods
738
- // ==========================================================================
739
- /**
740
- * Force re-render of the calendar
741
- * Useful after updating dayContentFn or other dynamic properties
742
- */
743
- refresh() {
744
- this.syncChildComponents();
745
- // Force month display to re-render (for async data updates)
746
- if (this._monthDisplay && typeof this._monthDisplay.refresh === 'function') {
747
- this._monthDisplay.refresh();
748
- }
749
- }
750
- // ==========================================================================
751
- // Rendering
752
- // ==========================================================================
753
- /**
754
- * Create navigation element
755
- */
756
- createNavigation() {
757
- const nav = document.createElement('ty-calendar-navigation');
758
- // Set properties
759
- nav.displayMonth = this._state.displayMonth;
760
- nav.displayYear = this._state.displayYear;
761
- nav.locale = this.locale;
762
- nav.size = this._size;
763
- nav.min = this._min ?? '';
764
- nav.max = this._max ?? '';
765
- // Only set width if explicitly provided
766
- if (this._width) {
767
- nav.width = this._width;
768
- }
769
- // Listen for change events
770
- nav.addEventListener('change', (e) => this.handleNavigationChange(e));
771
- // Store reference
772
- this._navigation = nav;
773
- return nav;
774
- }
775
- /**
776
- * Create month display element
777
- */
778
- createMonthDisplay() {
779
- const month = document.createElement('ty-calendar-month');
780
- // Set properties
781
- month.displayMonth = this._state.displayMonth;
782
- month.displayYear = this._state.displayYear;
783
- month.locale = this.locale;
784
- month.size = this._size;
785
- month.min = this._min;
786
- month.max = this._max;
787
- month.flavor = this._flavor;
788
- // Only set width if explicitly provided
789
- if (this._width) {
790
- month.width = this._width;
791
- }
792
- // Set selection value if exists
793
- const { selectedYear, selectedMonth, selectedDay } = this._state;
794
- if (selectedYear && selectedMonth && selectedDay) {
795
- const date = new Date(selectedYear, selectedMonth - 1, selectedDay);
796
- month.value = date.getTime();
797
- }
798
- // Pass render functions
799
- if (this._dayContentFn) {
800
- month.dayContentFn = this._dayContentFn;
801
- }
802
- // Pass custom CSS
803
- if (this._customCSS) {
804
- month.customCSS = this._customCSS;
805
- }
806
- // Listen for day-click events
807
- month.addEventListener('day-click', (e) => this.handleDayClick(e));
808
- // Store reference
809
- this._monthDisplay = month;
810
- return month;
811
- }
812
- /**
813
- * Main render function
814
- */
815
- render() {
816
- const root = this.shadowRoot;
817
- if (!root)
818
- return;
819
- // Ensure styles are loaded
820
- ensureStyles(root, { css: calendarStyles, id: 'ty-calendar' });
821
- // Clear and rebuild
822
- root.innerHTML = '';
823
- // Create main container
824
- const container = document.createElement('div');
825
- container.className = 'calendar-container';
826
- // Add navigation if requested
827
- if (this._showNavigation) {
828
- const nav = this.createNavigation();
829
- container.appendChild(nav);
830
- }
831
- // Add month display
832
- const month = this.createMonthDisplay();
833
- container.appendChild(month);
834
- root.appendChild(container);
835
- }
836
- }
837
- /**
838
- * Form-associated custom element
839
- */
39
+ static get observedAttributes() {
40
+ return ['value', 'year', 'month', 'day', 'hide-navigation', 'stateless', 'locale', 'name', 'size', 'width', 'min', 'max', 'flavor'];
41
+ }
42
+ constructor() {
43
+ super();
44
+ this._hideNavigation = false;
45
+ this._stateless = false;
46
+ this._size = 'md';
47
+ this._flavor = 'primary';
48
+ this._initialValue = '';
49
+ const current = getCurrentDate();
50
+ this._state = {
51
+ displayYear: current.year,
52
+ displayMonth: current.month,
53
+ };
54
+ this.attachShadow({ mode: 'open' });
55
+ if ('attachInternals' in this) {
56
+ this._internals = this.attachInternals();
57
+ }
58
+ }
59
+ connectedCallback() {
60
+ this.initializeFromAttributes();
61
+ const plainDayContentFn = this.dayContentFn;
62
+ if (plainDayContentFn && !this._dayContentFn) {
63
+ this._dayContentFn = plainDayContentFn;
64
+ delete this.dayContentFn;
65
+ }
66
+ const plainCustomCSS = this.customCSS;
67
+ if (plainCustomCSS && !this._customCSS) {
68
+ this._customCSS = plainCustomCSS;
69
+ delete this.customCSS;
70
+ }
71
+ const plainValue = this.value;
72
+ if (plainValue && typeof plainValue === 'string' && plainValue !== this.value) {
73
+ const match = plainValue.match(/^(\d{4})-(\d{2})-(\d{2})$/);
74
+ if (match) {
75
+ const year = parseInt(match[1], 10);
76
+ const month = parseInt(match[2], 10);
77
+ const day = parseInt(match[3], 10);
78
+ this._state.selectedYear = year;
79
+ this._state.selectedMonth = month;
80
+ this._state.selectedDay = day;
81
+ this._state.displayYear = year;
82
+ this._state.displayMonth = month;
83
+ if (!this._stateless) {
84
+ this.setAttribute('year', year.toString());
85
+ this.setAttribute('month', month.toString());
86
+ this.setAttribute('day', day.toString());
87
+ }
88
+ }
89
+ delete this.value;
90
+ }
91
+ this.render();
92
+ this.updateFormValue();
93
+ this._localeObserver = observeLocaleChanges(this, () => {
94
+ this.render();
95
+ });
96
+ }
97
+ disconnectedCallback() {
98
+ if (this._localeObserver) {
99
+ this._localeObserver();
100
+ this._localeObserver = undefined;
101
+ }
102
+ this._navigation = undefined;
103
+ this._monthDisplay = undefined;
104
+ }
105
+ attributeChangedCallback(name, oldValue, newValue) {
106
+ if (oldValue === newValue)
107
+ return;
108
+ switch (name) {
109
+ case 'value':
110
+ this.value = newValue || '';
111
+ break;
112
+ case 'year':
113
+ case 'month':
114
+ case 'day':
115
+ this.syncStateFromAttributes();
116
+ this.syncChildComponents();
117
+ this.updateFormValue();
118
+ break;
119
+ case 'hide-navigation':
120
+ this._hideNavigation = newValue !== null;
121
+ this.render();
122
+ break;
123
+ case 'stateless':
124
+ this._stateless = newValue !== null && newValue !== 'false';
125
+ break;
126
+ case 'locale':
127
+ this.syncChildComponents();
128
+ break;
129
+ case 'size':
130
+ if (newValue === 'sm' || newValue === 'md' || newValue === 'lg') {
131
+ this._size = newValue;
132
+ this._width = undefined;
133
+ this.removeAttribute('width');
134
+ this.syncChildComponents();
135
+ }
136
+ break;
137
+ case 'width':
138
+ this._width = newValue || undefined;
139
+ if (newValue) {
140
+ this._size = 'md';
141
+ this.removeAttribute('size');
142
+ }
143
+ this.syncChildComponents();
144
+ break;
145
+ case 'name':
146
+ this.updateFormValue();
147
+ break;
148
+ case 'min':
149
+ case 'max':
150
+ this[name === 'min' ? '_min' : '_max'] = newValue || undefined;
151
+ this.syncChildComponents();
152
+ this.updateValidity();
153
+ break;
154
+ case 'flavor':
155
+ this._flavor = newValue || 'primary';
156
+ this.syncChildComponents();
157
+ break;
158
+ }
159
+ }
160
+ get min() {
161
+ return this._min;
162
+ }
163
+ set min(value) {
164
+ if (value)
165
+ this.setAttribute('min', value);
166
+ else
167
+ this.removeAttribute('min');
168
+ }
169
+ get max() {
170
+ return this._max;
171
+ }
172
+ set max(value) {
173
+ if (value)
174
+ this.setAttribute('max', value);
175
+ else
176
+ this.removeAttribute('max');
177
+ }
178
+ get flavor() {
179
+ return this._flavor;
180
+ }
181
+ set flavor(value) {
182
+ if (value)
183
+ this.setAttribute('flavor', value);
184
+ else
185
+ this.removeAttribute('flavor');
186
+ }
187
+ get year() {
188
+ return this._state.selectedYear;
189
+ }
190
+ set year(value) {
191
+ if (value !== undefined) {
192
+ if (this._stateless) {
193
+ this._state.displayYear = value;
194
+ this.setAttribute('year', value.toString());
195
+ }
196
+ else {
197
+ this._state.selectedYear = value;
198
+ this._state.displayYear = value;
199
+ this.setAttribute('year', value.toString());
200
+ }
201
+ }
202
+ else {
203
+ if (!this._stateless) {
204
+ delete this._state.selectedYear;
205
+ }
206
+ this.removeAttribute('year');
207
+ }
208
+ this.syncChildComponents();
209
+ if (!this._stateless) {
210
+ this.updateFormValue();
211
+ }
212
+ }
213
+ get month() {
214
+ return this._state.selectedMonth;
215
+ }
216
+ set month(value) {
217
+ if (value !== undefined) {
218
+ if (this._stateless) {
219
+ this._state.displayMonth = value;
220
+ this.setAttribute('month', value.toString());
221
+ }
222
+ else {
223
+ this._state.selectedMonth = value;
224
+ this._state.displayMonth = value;
225
+ this.setAttribute('month', value.toString());
226
+ }
227
+ }
228
+ else {
229
+ if (!this._stateless) {
230
+ delete this._state.selectedMonth;
231
+ }
232
+ this.removeAttribute('month');
233
+ }
234
+ this.syncChildComponents();
235
+ if (!this._stateless) {
236
+ this.updateFormValue();
237
+ }
238
+ }
239
+ get day() {
240
+ return this._state.selectedDay;
241
+ }
242
+ set day(value) {
243
+ if (value !== undefined) {
244
+ this._state.selectedDay = value;
245
+ this.setAttribute('day', value.toString());
246
+ }
247
+ else {
248
+ delete this._state.selectedDay;
249
+ this.removeAttribute('day');
250
+ }
251
+ this.syncChildComponents();
252
+ this.updateFormValue();
253
+ }
254
+ get locale() {
255
+ return getEffectiveLocale(this, this.getAttribute('locale'));
256
+ }
257
+ set locale(value) {
258
+ this.setAttribute('locale', value);
259
+ }
260
+ get hideNavigation() {
261
+ return this._hideNavigation;
262
+ }
263
+ set hideNavigation(value) {
264
+ this._hideNavigation = value;
265
+ if (value)
266
+ this.setAttribute('hide-navigation', '');
267
+ else
268
+ this.removeAttribute('hide-navigation');
269
+ }
270
+ get stateless() {
271
+ return this._stateless;
272
+ }
273
+ set stateless(value) {
274
+ this._stateless = value;
275
+ if (value) {
276
+ this.setAttribute('stateless', '');
277
+ }
278
+ else {
279
+ this.removeAttribute('stateless');
280
+ }
281
+ }
282
+ get size() {
283
+ return this._size;
284
+ }
285
+ set size(value) {
286
+ if (this._size !== value) {
287
+ this._size = value;
288
+ this._width = undefined;
289
+ this.removeAttribute('width');
290
+ this.setAttribute('size', value);
291
+ this.syncChildComponents();
292
+ }
293
+ }
294
+ get width() {
295
+ return this._width;
296
+ }
297
+ set width(value) {
298
+ if (this._width !== value) {
299
+ this._width = value;
300
+ if (value) {
301
+ this._size = 'md';
302
+ this.removeAttribute('size');
303
+ this.setAttribute('width', value);
304
+ }
305
+ else {
306
+ this.removeAttribute('width');
307
+ }
308
+ this.syncChildComponents();
309
+ }
310
+ }
311
+ get dayContentFn() {
312
+ return this._dayContentFn;
313
+ }
314
+ set dayContentFn(fn) {
315
+ this._dayContentFn = fn;
316
+ this.syncChildComponents();
317
+ }
318
+ get customCSS() {
319
+ return this._customCSS;
320
+ }
321
+ set customCSS(sheet) {
322
+ this._customCSS = sheet;
323
+ this.syncChildComponents();
324
+ }
325
+ get value() {
326
+ const { selectedYear, selectedMonth, selectedDay } = this._state;
327
+ if (selectedYear && selectedMonth && selectedDay) {
328
+ return formatDateISO(selectedYear, selectedMonth, selectedDay);
329
+ }
330
+ return '';
331
+ }
332
+ set value(isoDate) {
333
+ if (!isoDate) {
334
+ delete this._state.selectedYear;
335
+ delete this._state.selectedMonth;
336
+ delete this._state.selectedDay;
337
+ if (!this._stateless) {
338
+ this.removeAttribute('year');
339
+ this.removeAttribute('month');
340
+ this.removeAttribute('day');
341
+ }
342
+ }
343
+ else {
344
+ const match = isoDate.match(/^(\d{4})-(\d{2})-(\d{2})$/);
345
+ if (match) {
346
+ const year = parseInt(match[1], 10);
347
+ const month = parseInt(match[2], 10);
348
+ const day = parseInt(match[3], 10);
349
+ this._state.selectedYear = year;
350
+ this._state.selectedMonth = month;
351
+ this._state.selectedDay = day;
352
+ this._state.displayYear = year;
353
+ this._state.displayMonth = month;
354
+ if (!this._stateless) {
355
+ this.setAttribute('year', year.toString());
356
+ this.setAttribute('month', month.toString());
357
+ this.setAttribute('day', day.toString());
358
+ }
359
+ }
360
+ }
361
+ this.syncChildComponents();
362
+ if (!this._stateless) {
363
+ this.updateFormValue();
364
+ }
365
+ }
366
+ initializeFromAttributes() {
367
+ const yearStr = this.getAttribute('year');
368
+ const monthStr = this.getAttribute('month');
369
+ const dayStr = this.getAttribute('day');
370
+ const hideNav = this.hasAttribute('hide-navigation');
371
+ const statelessStr = this.getAttribute('stateless');
372
+ const sizeStr = this.getAttribute('size');
373
+ const widthStr = this.getAttribute('width');
374
+ this._min = this.getAttribute('min') || undefined;
375
+ this._max = this.getAttribute('max') || undefined;
376
+ const year = parseYear(yearStr);
377
+ if (year) {
378
+ this._state.displayYear = year;
379
+ }
380
+ const month = parseMonth(monthStr);
381
+ if (month) {
382
+ this._state.displayMonth = month;
383
+ }
384
+ if (year && month) {
385
+ const day = parseDay(dayStr, year, month);
386
+ if (day) {
387
+ this._state.selectedYear = year;
388
+ this._state.selectedMonth = month;
389
+ this._state.selectedDay = day;
390
+ }
391
+ }
392
+ this._hideNavigation = hideNav;
393
+ if (statelessStr !== null) {
394
+ this._stateless = statelessStr !== 'false';
395
+ }
396
+ if (sizeStr && (sizeStr === 'sm' || sizeStr === 'md' || sizeStr === 'lg')) {
397
+ this._size = sizeStr;
398
+ }
399
+ if (widthStr) {
400
+ this._width = widthStr;
401
+ }
402
+ this._initialValue = this.value;
403
+ }
404
+ formResetCallback() {
405
+ this.value = this._initialValue;
406
+ }
407
+ syncStateFromAttributes() {
408
+ const yearStr = this.getAttribute('year');
409
+ const monthStr = this.getAttribute('month');
410
+ const dayStr = this.getAttribute('day');
411
+ const year = parseYear(yearStr);
412
+ const month = parseMonth(monthStr);
413
+ if (year)
414
+ this._state.displayYear = year;
415
+ if (month)
416
+ this._state.displayMonth = month;
417
+ if (year && month) {
418
+ const day = parseDay(dayStr, year, month);
419
+ if (day) {
420
+ this._state.selectedYear = year;
421
+ this._state.selectedMonth = month;
422
+ this._state.selectedDay = day;
423
+ }
424
+ else {
425
+ delete this._state.selectedYear;
426
+ delete this._state.selectedMonth;
427
+ delete this._state.selectedDay;
428
+ }
429
+ }
430
+ else {
431
+ delete this._state.selectedYear;
432
+ delete this._state.selectedMonth;
433
+ delete this._state.selectedDay;
434
+ }
435
+ }
436
+ syncChildComponents() {
437
+ if (this._navigation) {
438
+ this._navigation.displayMonth = this._state.displayMonth;
439
+ this._navigation.displayYear = this._state.displayYear;
440
+ this._navigation.locale = this.locale;
441
+ this._navigation.size = this._size;
442
+ this._navigation.min = this._min ?? '';
443
+ this._navigation.max = this._max ?? '';
444
+ this._navigation.width = this._width;
445
+ }
446
+ if (this._monthDisplay) {
447
+ this._monthDisplay.displayMonth = this._state.displayMonth;
448
+ this._monthDisplay.displayYear = this._state.displayYear;
449
+ this._monthDisplay.locale = this.locale;
450
+ this._monthDisplay.size = this._size;
451
+ this._monthDisplay.min = this._min;
452
+ this._monthDisplay.max = this._max;
453
+ this._monthDisplay.flavor = this._flavor;
454
+ this._monthDisplay.width = this._width;
455
+ if (this._dayContentFn) {
456
+ this._monthDisplay.dayContentFn = this._dayContentFn;
457
+ }
458
+ if (this._customCSS) {
459
+ this._monthDisplay.customCSS = this._customCSS;
460
+ }
461
+ const { selectedYear, selectedMonth, selectedDay } = this._state;
462
+ if (selectedYear && selectedMonth && selectedDay) {
463
+ const date = new Date(selectedYear, selectedMonth - 1, selectedDay);
464
+ this._monthDisplay.value = date.getTime();
465
+ }
466
+ else {
467
+ this._monthDisplay.value = null;
468
+ }
469
+ }
470
+ }
471
+ updateFormValue() {
472
+ if (this._stateless)
473
+ return;
474
+ if (!this._internals)
475
+ return;
476
+ const elementName = this.getAttribute('name');
477
+ const { selectedYear, selectedMonth, selectedDay } = this._state;
478
+ if (elementName && selectedYear && selectedMonth && selectedDay) {
479
+ const isoDate = formatDateISO(selectedYear, selectedMonth, selectedDay);
480
+ this._internals.setFormValue(isoDate);
481
+ }
482
+ else {
483
+ this._internals.setFormValue('');
484
+ }
485
+ this.updateValidity();
486
+ }
487
+ updateValidity() {
488
+ if (!this._internals)
489
+ return;
490
+ const { selectedYear, selectedMonth, selectedDay } = this._state;
491
+ if (selectedYear && selectedMonth && selectedDay) {
492
+ const sel = Date.UTC(selectedYear, selectedMonth - 1, selectedDay);
493
+ const min = this._min ? parseISODate(this._min) : null;
494
+ const max = this._max ? parseISODate(this._max) : null;
495
+ if (min && sel < Date.UTC(min.year, min.month - 1, min.day)) {
496
+ this._internals.setValidity({ rangeUnderflow: true }, `Date must be ${this._min} or later`);
497
+ return;
498
+ }
499
+ if (max && sel > Date.UTC(max.year, max.month - 1, max.day)) {
500
+ this._internals.setValidity({ rangeOverflow: true }, `Date must be ${this._max} or earlier`);
501
+ return;
502
+ }
503
+ }
504
+ this._internals.setValidity({});
505
+ }
506
+ handleNavigationChange(event) {
507
+ event.preventDefault();
508
+ event.stopPropagation();
509
+ const { month, year } = event.detail;
510
+ this._state.displayMonth = month;
511
+ this._state.displayYear = year;
512
+ this.syncChildComponents();
513
+ const navigateDetail = {
514
+ month,
515
+ year,
516
+ action: 'navigate',
517
+ source: 'navigation',
518
+ };
519
+ this.dispatchEvent(new CustomEvent('navigate', {
520
+ detail: navigateDetail,
521
+ bubbles: true,
522
+ composed: true,
523
+ cancelable: false,
524
+ }));
525
+ }
526
+ handleDayClick(event) {
527
+ event.preventDefault();
528
+ event.stopPropagation();
529
+ const { dayContext, year, month, day } = event.detail;
530
+ if (this._stateless) {
531
+ this.dispatchEvent(new CustomEvent('day-click', {
532
+ detail: event.detail,
533
+ bubbles: true,
534
+ composed: true,
535
+ cancelable: true,
536
+ }));
537
+ return;
538
+ }
539
+ this._state.selectedYear = year;
540
+ this._state.selectedMonth = month;
541
+ this._state.selectedDay = day;
542
+ this._state.displayYear = year;
543
+ this._state.displayMonth = month;
544
+ this.setAttribute('year', year.toString());
545
+ this.setAttribute('month', month.toString());
546
+ this.setAttribute('day', day.toString());
547
+ this.updateFormValue();
548
+ this.syncChildComponents();
549
+ const changeDetail = {
550
+ year,
551
+ month,
552
+ day,
553
+ action: 'select',
554
+ source: 'day-click',
555
+ dayContext,
556
+ };
557
+ this.dispatchEvent(new CustomEvent('change', {
558
+ detail: changeDetail,
559
+ bubbles: true,
560
+ composed: true,
561
+ cancelable: false,
562
+ }));
563
+ }
564
+ refresh() {
565
+ this.syncChildComponents();
566
+ if (this._monthDisplay && typeof this._monthDisplay.refresh === 'function') {
567
+ this._monthDisplay.refresh();
568
+ }
569
+ }
570
+ createNavigation() {
571
+ const nav = document.createElement('ty-calendar-navigation');
572
+ nav.displayMonth = this._state.displayMonth;
573
+ nav.displayYear = this._state.displayYear;
574
+ nav.locale = this.locale;
575
+ nav.size = this._size;
576
+ nav.min = this._min ?? '';
577
+ nav.max = this._max ?? '';
578
+ if (this._width) {
579
+ nav.width = this._width;
580
+ }
581
+ nav.addEventListener('change', (e) => this.handleNavigationChange(e));
582
+ this._navigation = nav;
583
+ return nav;
584
+ }
585
+ createMonthDisplay() {
586
+ const month = document.createElement('ty-calendar-month');
587
+ month.displayMonth = this._state.displayMonth;
588
+ month.displayYear = this._state.displayYear;
589
+ month.locale = this.locale;
590
+ month.size = this._size;
591
+ month.min = this._min;
592
+ month.max = this._max;
593
+ month.flavor = this._flavor;
594
+ if (this._width) {
595
+ month.width = this._width;
596
+ }
597
+ const { selectedYear, selectedMonth, selectedDay } = this._state;
598
+ if (selectedYear && selectedMonth && selectedDay) {
599
+ const date = new Date(selectedYear, selectedMonth - 1, selectedDay);
600
+ month.value = date.getTime();
601
+ }
602
+ if (this._dayContentFn) {
603
+ month.dayContentFn = this._dayContentFn;
604
+ }
605
+ if (this._customCSS) {
606
+ month.customCSS = this._customCSS;
607
+ }
608
+ month.addEventListener('day-click', (e) => this.handleDayClick(e));
609
+ this._monthDisplay = month;
610
+ return month;
611
+ }
612
+ render() {
613
+ const root = this.shadowRoot;
614
+ if (!root)
615
+ return;
616
+ ensureStyles(root, { css: calendarStyles, id: 'ty-calendar' });
617
+ root.innerHTML = '';
618
+ const container = document.createElement('div');
619
+ container.className = 'calendar-container';
620
+ if (!this._hideNavigation) {
621
+ const nav = this.createNavigation();
622
+ container.appendChild(nav);
623
+ }
624
+ const month = this.createMonthDisplay();
625
+ container.appendChild(month);
626
+ root.appendChild(container);
627
+ }
628
+ }
840
629
  TyCalendar.formAssociated = true;
841
- // Register the custom element
842
630
  if (!customElements.get('ty-calendar')) {
843
- customElements.define('ty-calendar', TyCalendar);
631
+ customElements.define('ty-calendar', TyCalendar);
844
632
  }
845
- //# sourceMappingURL=calendar.js.map