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,522 +1,378 @@
1
- /**
2
- * Modal Component
3
- *
4
- * Pure wrapper for modal dialogs using the native <dialog> element.
5
- * Provides backdrop, focus management, scroll locking, and keyboard interaction
6
- * without imposing any styling on content.
7
- *
8
- * Features:
9
- * - Native dialog element wrapper
10
- * - Dual API: declarative (open attribute) and imperative (show/hide methods)
11
- * - Cancellable `beforeclose` lifecycle event for unsaved-state flows
12
- * - Auto-hiding close button on hover (desktop only)
13
- * - Scroll locking with unique modal IDs
14
- * - Customizable backdrop via CSS variables
15
- *
16
- * @example
17
- * <ty-modal id="my-modal">
18
- * <div class="ty-elevated p-6 rounded-lg max-w-md">
19
- * <h3 class="text-lg font-semibold mb-4">Modal Title</h3>
20
- * <p class="ty-text- mb-4">Modal content here</p>
21
- * <ty-button onclick="this.closest('ty-modal').hide()">
22
- * Close
23
- * </ty-button>
24
- * </div>
25
- * </ty-modal>
26
- */
27
1
  import { lockScroll, unlockScroll } from '../utils/scroll-lock.js';
28
2
  import { ensureStyles } from '../utils/styles.js';
29
3
  import { modalStyles } from '../styles/modal.js';
30
- // ============================================================================
31
- // WeakMaps for State Management
32
- // ============================================================================
33
4
  const backdropClickHandlers = new WeakMap();
34
5
  const escapeKeyHandlers = new WeakMap();
35
6
  const closeButtonHandlers = new WeakMap();
36
7
  const hoverEnterHandlers = new WeakMap();
37
8
  const hoverLeaveHandlers = new WeakMap();
38
- const modalIds = new WeakMap(); // Store unique modal ID for scroll locking
39
- // ============================================================================
40
- // Constants
41
- // ============================================================================
42
- /**
43
- * SVG icon for the close button (Lucide X icon)
44
- */
9
+ const modalIds = new WeakMap();
10
+ const pendingCloseReasons = new WeakMap();
45
11
  const CLOSE_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>`;
46
- // ============================================================================
47
- // Helper Functions
48
- // ============================================================================
49
- /**
50
- * Parse boolean attribute
51
- * Boolean attribute semantics: presence -> true, explicit 'false' -> false
52
- * Matches ClojureScript implementation
53
- */
54
12
  function parseBoolAttr(el, name) {
55
- if (!el.hasAttribute(name)) {
56
- return false;
57
- }
58
- const value = el.getAttribute(name);
59
- // Explicit "false" string means false
60
- if (value !== null && value.toLowerCase() === 'false') {
61
- return false;
62
- }
63
- // Attribute present (even empty) means true
64
- return true;
65
- }
66
- /**
67
- * Read modal attributes with defaults
68
- */
13
+ if (!el.hasAttribute(name)) {
14
+ return false;
15
+ }
16
+ const value = el.getAttribute(name);
17
+ if (value !== null && value.toLowerCase() === 'false') {
18
+ return false;
19
+ }
20
+ return true;
21
+ }
69
22
  function getModalAttributes(el) {
70
- return {
71
- open: parseBoolAttr(el, 'open'),
72
- backdrop: el.hasAttribute('backdrop') ? parseBoolAttr(el, 'backdrop') : true,
73
- closeOnOutsideClick: el.hasAttribute('close-on-outside-click')
74
- ? parseBoolAttr(el, 'close-on-outside-click')
75
- : true,
76
- closeOnEscape: el.hasAttribute('close-on-escape')
77
- ? parseBoolAttr(el, 'close-on-escape')
78
- : true,
79
- };
80
- }
81
- /**
82
- * Get the modal dialog element from shadow root
83
- */
23
+ return {
24
+ open: parseBoolAttr(el, 'open'),
25
+ backdrop: el.hasAttribute('backdrop') ? parseBoolAttr(el, 'backdrop') : true,
26
+ closeOnOutsideClick: parseBoolAttr(el, 'prevent-outside-click')
27
+ ? false
28
+ : el.hasAttribute('close-on-outside-click')
29
+ ? parseBoolAttr(el, 'close-on-outside-click')
30
+ : true,
31
+ closeOnEscape: parseBoolAttr(el, 'prevent-escape')
32
+ ? false
33
+ : el.hasAttribute('close-on-escape')
34
+ ? parseBoolAttr(el, 'close-on-escape')
35
+ : true,
36
+ };
37
+ }
84
38
  function getModalDialog(shadowRoot) {
85
- return shadowRoot.querySelector('.ty-modal-dialog');
39
+ return shadowRoot.querySelector('.ty-modal-dialog');
86
40
  }
87
- /**
88
- * Get or create a unique modal ID for scroll locking
89
- */
90
41
  function getModalId(el) {
91
- let id = modalIds.get(el);
92
- if (!id) {
93
- id = `modal-${el.id || Math.random().toString(36).slice(2, 11)}`;
94
- modalIds.set(el, id);
95
- }
96
- return id;
97
- }
98
- /**
99
- * Check if device is mobile (for hover behavior)
100
- */
42
+ let id = modalIds.get(el);
43
+ if (!id) {
44
+ id = `modal-${el.id || Math.random().toString(36).slice(2, 11)}`;
45
+ modalIds.set(el, id);
46
+ }
47
+ return id;
48
+ }
101
49
  function isMobile() {
102
- return window.innerWidth < 768 || navigator.maxTouchPoints > 0;
50
+ return window.innerWidth < 768 || navigator.maxTouchPoints > 0;
103
51
  }
104
- /**
105
- * Dispatch custom modal event
106
- */
107
52
  function dispatchModalEvent(el, eventType, detail) {
108
- const event = new CustomEvent(eventType, {
109
- detail,
110
- bubbles: true,
111
- cancelable: true,
112
- });
113
- el.dispatchEvent(event);
114
- }
115
- // ============================================================================
116
- // Dialog Structure Creation
117
- // ============================================================================
118
- /**
119
- * Ensure the internal dialog structure exists in shadow DOM
120
- */
53
+ const event = new CustomEvent(eventType, {
54
+ detail,
55
+ cancelable: true,
56
+ });
57
+ el.dispatchEvent(event);
58
+ }
121
59
  function ensureInternalDialog(shadowRoot) {
122
- let dialog = getModalDialog(shadowRoot);
123
- if (!dialog) {
124
- dialog = document.createElement('dialog');
125
- const closeButton = document.createElement('button');
126
- const contentDiv = document.createElement('div');
127
- const slot = document.createElement('slot');
128
- // Setup dialog structure
129
- dialog.className = 'ty-modal-dialog';
130
- // Setup close button with SVG icon
131
- closeButton.className = 'close-button';
132
- closeButton.type = 'button';
133
- closeButton.setAttribute('aria-label', 'Close modal');
134
- closeButton.innerHTML = CLOSE_ICON;
135
- // Setup content container
136
- contentDiv.className = 'modal-content';
137
- contentDiv.appendChild(slot);
138
- // Assemble: dialog > [close-button, content-div]
139
- dialog.appendChild(closeButton);
140
- dialog.appendChild(contentDiv);
141
- shadowRoot.appendChild(dialog);
142
- }
143
- return dialog;
144
- }
145
- // ============================================================================
146
- // Event Handlers
147
- // ============================================================================
148
- /**
149
- * Close modal with cancellable beforeclose lifecycle.
150
- *
151
- * 1. Emit `beforeclose` (cancellable, bubbles, composed). Consumers can
152
- * `event.preventDefault()` to abort the close — render your own UI for
153
- * unsaved-state flows.
154
- * 2. Remove the `open` attribute, which triggers the actual close.
155
- *
156
- * Callers pass `reason` so consumers can decide differently based on intent
157
- * (e.g. "OK to discard via ESC, but not via .hide() during a save").
158
- */
60
+ let dialog = getModalDialog(shadowRoot);
61
+ if (!dialog) {
62
+ dialog = document.createElement('dialog');
63
+ const closeButton = document.createElement('button');
64
+ const contentDiv = document.createElement('div');
65
+ const slot = document.createElement('slot');
66
+ dialog.className = 'ty-modal-dialog';
67
+ closeButton.className = 'close-button';
68
+ closeButton.type = 'button';
69
+ closeButton.setAttribute('aria-label', 'Close modal');
70
+ closeButton.innerHTML = CLOSE_ICON;
71
+ contentDiv.className = 'modal-content';
72
+ contentDiv.appendChild(slot);
73
+ dialog.appendChild(closeButton);
74
+ dialog.appendChild(contentDiv);
75
+ shadowRoot.appendChild(dialog);
76
+ }
77
+ return dialog;
78
+ }
159
79
  function closeModal(el, reason = 'programmatic', opts) {
160
- const shadowRoot = el.shadowRoot;
161
- const dialog = shadowRoot ? getModalDialog(shadowRoot) : null;
162
- if (!dialog || !dialog.open)
163
- return;
164
- // `force: true` bypasses the cancellable event. Used by consumers AFTER
165
- // they've shown their own confirm UI and the user said "yes, close" —
166
- // calling `.hide()` again without force would re-trigger their intercept.
167
- if (!opts?.force) {
168
- const beforeClose = new CustomEvent('beforeclose', {
169
- detail: { reason },
170
- bubbles: true,
171
- composed: true,
172
- cancelable: true,
173
- });
174
- el.dispatchEvent(beforeClose);
175
- if (beforeClose.defaultPrevented)
176
- return;
177
- }
178
- // Remove open attribute (triggers actual close).
179
- el.removeAttribute('open');
180
- }
181
- /**
182
- * Open modal programmatically
183
- */
80
+ const shadowRoot = el.shadowRoot;
81
+ const dialog = shadowRoot ? getModalDialog(shadowRoot) : null;
82
+ if (!dialog || !dialog.open)
83
+ return;
84
+ if (!opts?.force) {
85
+ const beforeClose = new CustomEvent('beforeclose', {
86
+ detail: { reason },
87
+ cancelable: true,
88
+ });
89
+ el.dispatchEvent(beforeClose);
90
+ if (beforeClose.defaultPrevented)
91
+ return;
92
+ }
93
+ pendingCloseReasons.set(el, reason);
94
+ el.removeAttribute('open');
95
+ }
184
96
  function openModal(el) {
185
- el.setAttribute('open', 'true');
97
+ el.setAttribute('open', 'true');
186
98
  }
187
- /**
188
- * Handle backdrop click
189
- */
190
99
  function handleBackdropClick(el, event) {
191
- event.stopPropagation();
192
- const shadowRoot = el.shadowRoot;
193
- const dialog = shadowRoot ? getModalDialog(shadowRoot) : null;
194
- // Only close if clicking on dialog backdrop (not content)
195
- if (dialog && event.target === dialog) {
196
- event.preventDefault();
197
- closeModal(el, 'backdrop');
198
- }
199
- }
200
- /**
201
- * Handle escape key press
202
- */
100
+ event.stopPropagation();
101
+ const shadowRoot = el.shadowRoot;
102
+ const dialog = shadowRoot ? getModalDialog(shadowRoot) : null;
103
+ if (dialog && event.target === dialog) {
104
+ event.preventDefault();
105
+ closeModal(el, 'backdrop');
106
+ }
107
+ }
203
108
  function handleEscapeKey(el, event) {
204
- event.stopPropagation();
205
- if (event.key === 'Escape') {
206
- event.preventDefault();
207
- closeModal(el, 'escape');
208
- }
209
- }
210
- /**
211
- * Handle close button click
212
- */
109
+ event.stopPropagation();
110
+ if (event.key === 'Escape') {
111
+ event.preventDefault();
112
+ closeModal(el, 'escape');
113
+ }
114
+ }
213
115
  function handleCloseButtonClick(el, event) {
214
- event.preventDefault();
215
- event.stopPropagation();
216
- closeModal(el, 'close-button');
116
+ event.preventDefault();
117
+ event.stopPropagation();
118
+ closeModal(el, 'close-button');
217
119
  }
218
- /**
219
- * Handle hover enter on modal content (hide close button)
220
- */
221
120
  function handleHoverEnter(_el, event) {
222
- const content = event.currentTarget;
223
- const dialog = content.closest('.ty-modal-dialog');
224
- if (dialog) {
225
- const closeButton = dialog.querySelector('.close-button');
226
- if (closeButton) {
227
- closeButton.classList.add('hide');
228
- }
229
- }
230
- }
231
- /**
232
- * Handle hover leave on modal content (show close button)
233
- */
121
+ const content = event.currentTarget;
122
+ const dialog = content.closest('.ty-modal-dialog');
123
+ if (dialog) {
124
+ const closeButton = dialog.querySelector('.close-button');
125
+ if (closeButton) {
126
+ closeButton.classList.add('hide');
127
+ }
128
+ }
129
+ }
234
130
  function handleHoverLeave(_el, event) {
235
- const content = event.currentTarget;
236
- const dialog = content.closest('.ty-modal-dialog');
237
- if (dialog) {
238
- const closeButton = dialog.querySelector('.close-button');
239
- if (closeButton) {
240
- closeButton.classList.remove('hide');
241
- }
242
- }
243
- }
244
- /**
245
- * Setup backdrop click handling
246
- */
131
+ const content = event.currentTarget;
132
+ const dialog = content.closest('.ty-modal-dialog');
133
+ if (dialog) {
134
+ const closeButton = dialog.querySelector('.close-button');
135
+ if (closeButton) {
136
+ closeButton.classList.remove('hide');
137
+ }
138
+ }
139
+ }
247
140
  function setupBackdropClick(el, dialog, enabled) {
248
- // Remove existing listener
249
- const existingHandler = backdropClickHandlers.get(el);
250
- if (existingHandler) {
251
- dialog.removeEventListener('click', existingHandler);
252
- backdropClickHandlers.delete(el);
253
- }
254
- // Add new listener if enabled
255
- if (enabled) {
256
- const handler = (e) => handleBackdropClick(el, e);
257
- backdropClickHandlers.set(el, handler);
258
- dialog.addEventListener('click', handler);
259
- }
260
- }
261
- /**
262
- * Setup escape key handling
263
- */
141
+ const existingHandler = backdropClickHandlers.get(el);
142
+ if (existingHandler) {
143
+ dialog.removeEventListener('click', existingHandler);
144
+ backdropClickHandlers.delete(el);
145
+ }
146
+ if (enabled) {
147
+ const handler = (e) => handleBackdropClick(el, e);
148
+ backdropClickHandlers.set(el, handler);
149
+ dialog.addEventListener('click', handler);
150
+ }
151
+ }
264
152
  function setupEscapeKey(el, dialog, enabled) {
265
- // Remove existing listener
266
- const existingHandler = escapeKeyHandlers.get(el);
267
- if (existingHandler) {
268
- dialog.removeEventListener('keydown', existingHandler);
269
- escapeKeyHandlers.delete(el);
270
- }
271
- // Add new listener if enabled
272
- if (enabled) {
273
- const handler = (e) => handleEscapeKey(el, e);
274
- escapeKeyHandlers.set(el, handler);
275
- dialog.addEventListener('keydown', handler);
276
- }
277
- }
278
- /**
279
- * Setup close button click handling and visibility
280
- */
153
+ const existingHandler = escapeKeyHandlers.get(el);
154
+ if (existingHandler) {
155
+ dialog.removeEventListener('keydown', existingHandler);
156
+ escapeKeyHandlers.delete(el);
157
+ }
158
+ if (enabled) {
159
+ const handler = (e) => handleEscapeKey(el, e);
160
+ escapeKeyHandlers.set(el, handler);
161
+ dialog.addEventListener('keydown', handler);
162
+ }
163
+ else {
164
+ const handler = (e) => {
165
+ if (e.key === 'Escape')
166
+ e.preventDefault();
167
+ };
168
+ escapeKeyHandlers.set(el, handler);
169
+ dialog.addEventListener('keydown', handler);
170
+ }
171
+ }
281
172
  function setupCloseButton(el, dialog, showButton) {
282
- const closeButton = dialog.querySelector('.close-button');
283
- if (!closeButton)
284
- return;
285
- // Show or hide close button based on close-on-outside-click setting
286
- if (showButton) {
287
- closeButton.style.display = '';
288
- }
289
- else {
290
- closeButton.style.display = 'none';
291
- }
292
- // Remove existing listener
293
- const existingHandler = closeButtonHandlers.get(el);
294
- if (existingHandler) {
295
- closeButton.removeEventListener('click', existingHandler);
296
- closeButtonHandlers.delete(el);
297
- }
298
- // Add click listener only if button is visible
299
- if (showButton) {
300
- const handler = (e) => handleCloseButtonClick(el, e);
301
- closeButtonHandlers.set(el, handler);
302
- closeButton.addEventListener('click', handler);
303
- }
304
- }
305
- /**
306
- * Setup close button hover handlers (desktop only)
307
- */
173
+ const closeButton = dialog.querySelector('.close-button');
174
+ if (!closeButton)
175
+ return;
176
+ if (showButton) {
177
+ closeButton.style.display = '';
178
+ }
179
+ else {
180
+ closeButton.style.display = 'none';
181
+ }
182
+ const existingHandler = closeButtonHandlers.get(el);
183
+ if (existingHandler) {
184
+ closeButton.removeEventListener('click', existingHandler);
185
+ closeButtonHandlers.delete(el);
186
+ }
187
+ if (showButton) {
188
+ const handler = (e) => handleCloseButtonClick(el, e);
189
+ closeButtonHandlers.set(el, handler);
190
+ closeButton.addEventListener('click', handler);
191
+ }
192
+ }
308
193
  function setupCloseButtonHover(el, dialog) {
309
- const modalContent = dialog.querySelector('.modal-content');
310
- if (!modalContent)
311
- return;
312
- // Remove existing listeners
313
- const existingEnterHandler = hoverEnterHandlers.get(el);
314
- const existingLeaveHandler = hoverLeaveHandlers.get(el);
315
- if (existingEnterHandler) {
316
- modalContent.removeEventListener('mouseenter', existingEnterHandler);
317
- hoverEnterHandlers.delete(el);
318
- }
319
- if (existingLeaveHandler) {
320
- modalContent.removeEventListener('mouseleave', existingLeaveHandler);
321
- hoverLeaveHandlers.delete(el);
322
- }
323
- // Add hover listeners only on desktop
324
- if (!isMobile()) {
325
- const enterHandler = (e) => handleHoverEnter(el, e);
326
- const leaveHandler = (e) => handleHoverLeave(el, e);
327
- hoverEnterHandlers.set(el, enterHandler);
328
- hoverLeaveHandlers.set(el, leaveHandler);
329
- modalContent.addEventListener('mouseenter', enterHandler);
330
- modalContent.addEventListener('mouseleave', leaveHandler);
331
- }
332
- }
333
- // ============================================================================
334
- // Core Render Function
335
- // ============================================================================
336
- /**
337
- * Render the modal structure and sync state
338
- */
194
+ const modalContent = dialog.querySelector('.modal-content');
195
+ if (!modalContent)
196
+ return;
197
+ const existingEnterHandler = hoverEnterHandlers.get(el);
198
+ const existingLeaveHandler = hoverLeaveHandlers.get(el);
199
+ if (existingEnterHandler) {
200
+ modalContent.removeEventListener('mouseenter', existingEnterHandler);
201
+ hoverEnterHandlers.delete(el);
202
+ }
203
+ if (existingLeaveHandler) {
204
+ modalContent.removeEventListener('mouseleave', existingLeaveHandler);
205
+ hoverLeaveHandlers.delete(el);
206
+ }
207
+ if (!isMobile()) {
208
+ const enterHandler = (e) => handleHoverEnter(el, e);
209
+ const leaveHandler = (e) => handleHoverLeave(el, e);
210
+ hoverEnterHandlers.set(el, enterHandler);
211
+ hoverLeaveHandlers.set(el, leaveHandler);
212
+ modalContent.addEventListener('mouseenter', enterHandler);
213
+ modalContent.addEventListener('mouseleave', leaveHandler);
214
+ }
215
+ }
339
216
  function render(el) {
340
- const shadowRoot = el.shadowRoot;
341
- if (!shadowRoot)
342
- return;
343
- const attributes = getModalAttributes(el);
344
- const dialog = ensureInternalDialog(shadowRoot);
345
- const modalId = getModalId(el);
346
- // Ensure styles are loaded
347
- ensureStyles(shadowRoot, { css: modalStyles, id: 'ty-modal' });
348
- // Apply dialog class
349
- dialog.className = 'ty-modal-dialog';
350
- // Apply backdrop attribute
351
- if (attributes.backdrop) {
352
- dialog.setAttribute('data-backdrop', 'true');
353
- }
354
- else {
355
- dialog.removeAttribute('data-backdrop');
356
- }
357
- // Accessible name: <dialog> has an implicit role="dialog" for free, but
358
- // nothing gives it a NAME unless the consumer's own slotted heading
359
- // happens to get wired up manually. Opt-in `label` attribute (same
360
- // pattern as ty-select/ty-date-picker's `label` prop) — unset stays
361
- // exactly as before (no regression, just no fix without opting in).
362
- const label = el.getAttribute('label');
363
- if (label) {
364
- dialog.setAttribute('aria-label', label);
365
- }
366
- else {
367
- dialog.removeAttribute('aria-label');
368
- }
369
- // Setup event handlers
370
- setupBackdropClick(el, dialog, attributes.closeOnOutsideClick);
371
- setupEscapeKey(el, dialog, attributes.closeOnEscape);
372
- setupCloseButton(el, dialog, attributes.closeOnOutsideClick);
373
- setupCloseButtonHover(el, dialog);
374
- // Sync open state with native dialog
375
- if (attributes.open) {
376
- if (!dialog.open) {
377
- // Lock scroll
378
- lockScroll(modalId);
379
- // Show dialog
380
- if (attributes.backdrop) {
381
- dialog.showModal();
382
- }
383
- else {
384
- dialog.show();
385
- }
386
- // Dispatch open event
387
- dispatchModalEvent(el, 'open', {});
388
- }
389
- }
390
- else {
391
- if (dialog.open) {
392
- // Unlock scroll
393
- unlockScroll(modalId);
394
- // Close dialog
395
- dialog.close();
396
- // Dispatch close event
397
- dispatchModalEvent(el, 'close', {
398
- reason: 'programmatic'
399
- });
400
- }
401
- }
402
- // Handle dialog's native close event.
403
- //
404
- // Child components (ty-select, ty-date-picker) dispatch a
405
- // bubbling+composed `close` custom event when their popups close. Those
406
- // events bubble up to *this* dialog and trigger `onclose` too — without this
407
- // guard, opening a dropdown inside a modal and closing the dropdown would
408
- // close the whole modal. The native dialog `close` event is not composed
409
- // and targets the dialog itself, so we only react when `event.target` is
410
- // the dialog.
411
- dialog.onclose = (event) => {
412
- if (event.target !== dialog)
413
- return;
414
- // Ensure scroll is unlocked
415
- unlockScroll(modalId);
416
- // Sync the open attribute
417
- if (el.hasAttribute('open')) {
418
- el.removeAttribute('open');
419
- dispatchModalEvent(el, 'close', {
420
- reason: 'native',
421
- returnValue: dialog.returnValue || undefined
422
- });
423
- }
424
- };
425
- }
426
- /**
427
- * Cleanup all resources when component disconnects
428
- */
217
+ const shadowRoot = el.shadowRoot;
218
+ if (!shadowRoot)
219
+ return;
220
+ const attributes = getModalAttributes(el);
221
+ const dialog = ensureInternalDialog(shadowRoot);
222
+ const modalId = getModalId(el);
223
+ ensureStyles(shadowRoot, { css: modalStyles, id: 'ty-modal' });
224
+ dialog.className = 'ty-modal-dialog';
225
+ if (attributes.backdrop) {
226
+ dialog.setAttribute('data-backdrop', 'true');
227
+ }
228
+ else {
229
+ dialog.removeAttribute('data-backdrop');
230
+ }
231
+ const label = el.getAttribute('label');
232
+ if (label) {
233
+ dialog.setAttribute('aria-label', label);
234
+ }
235
+ else {
236
+ dialog.removeAttribute('aria-label');
237
+ }
238
+ setupBackdropClick(el, dialog, attributes.closeOnOutsideClick);
239
+ setupEscapeKey(el, dialog, attributes.closeOnEscape);
240
+ dialog.oncancel = (event) => {
241
+ event.preventDefault();
242
+ if (getModalAttributes(el).closeOnEscape)
243
+ closeModal(el, 'escape');
244
+ };
245
+ setupCloseButton(el, dialog, attributes.closeOnOutsideClick);
246
+ setupCloseButtonHover(el, dialog);
247
+ if (attributes.open) {
248
+ if (!dialog.open) {
249
+ lockScroll(modalId);
250
+ if (attributes.backdrop) {
251
+ dialog.showModal();
252
+ }
253
+ else {
254
+ dialog.show();
255
+ }
256
+ syncBackdropZoom();
257
+ dispatchModalEvent(el, 'open', {});
258
+ }
259
+ }
260
+ else {
261
+ if (dialog.open) {
262
+ unlockScroll(modalId);
263
+ dialog.close();
264
+ syncBackdropZoom();
265
+ const reason = pendingCloseReasons.get(el) ?? 'programmatic';
266
+ pendingCloseReasons.delete(el);
267
+ dispatchModalEvent(el, 'close', { reason });
268
+ }
269
+ }
270
+ dialog.onclose = (event) => {
271
+ if (event.target !== dialog)
272
+ return;
273
+ unlockScroll(modalId);
274
+ if (el.hasAttribute('open')) {
275
+ el.removeAttribute('open');
276
+ dispatchModalEvent(el, 'close', {
277
+ reason: 'native',
278
+ returnValue: dialog.returnValue || undefined
279
+ });
280
+ }
281
+ syncBackdropZoom();
282
+ };
283
+ }
284
+ function syncBackdropZoom() {
285
+ const anyOpen = Array.from(document.querySelectorAll('ty-modal[open][backdrop-zoom], ty-dialog[open][backdrop-zoom]')).some((m) => {
286
+ const v = m.getAttribute('backdrop-zoom').toLowerCase().trim();
287
+ return v !== 'false' && v !== '0';
288
+ });
289
+ document.documentElement.classList.toggle('ty-modal-zoom', anyOpen);
290
+ }
429
291
  function cleanup(el) {
430
- const modalId = modalIds.get(el);
431
- const shadowRoot = el.shadowRoot;
432
- const dialog = shadowRoot ? getModalDialog(shadowRoot) : null;
433
- // Force unlock scroll if modal removed while open
434
- if (modalId) {
435
- unlockScroll(modalId);
436
- modalIds.delete(el);
437
- }
438
- if (!dialog)
439
- return;
440
- // Cleanup backdrop click handler
441
- const backdropHandler = backdropClickHandlers.get(el);
442
- if (backdropHandler) {
443
- dialog.removeEventListener('click', backdropHandler);
444
- backdropClickHandlers.delete(el);
445
- }
446
- // Cleanup escape key handler
447
- const escapeHandler = escapeKeyHandlers.get(el);
448
- if (escapeHandler) {
449
- dialog.removeEventListener('keydown', escapeHandler);
450
- escapeKeyHandlers.delete(el);
451
- }
452
- // Cleanup close button handler
453
- const closeButton = dialog.querySelector('.close-button');
454
- if (closeButton) {
455
- const closeHandler = closeButtonHandlers.get(el);
456
- if (closeHandler) {
457
- closeButton.removeEventListener('click', closeHandler);
458
- closeButtonHandlers.delete(el);
459
- }
460
- }
461
- // Cleanup hover handlers
462
- const modalContent = dialog.querySelector('.modal-content');
463
- if (modalContent) {
464
- const enterHandler = hoverEnterHandlers.get(el);
465
- const leaveHandler = hoverLeaveHandlers.get(el);
466
- if (enterHandler) {
467
- modalContent.removeEventListener('mouseenter', enterHandler);
468
- hoverEnterHandlers.delete(el);
469
- }
470
- if (leaveHandler) {
471
- modalContent.removeEventListener('mouseleave', leaveHandler);
472
- hoverLeaveHandlers.delete(el);
473
- }
474
- }
475
- }
476
- // ============================================================================
477
- // Component Definition
478
- // ============================================================================
479
- /**
480
- * TyModal Web Component
481
- */
292
+ const modalId = modalIds.get(el);
293
+ const shadowRoot = el.shadowRoot;
294
+ const dialog = shadowRoot ? getModalDialog(shadowRoot) : null;
295
+ if (modalId) {
296
+ unlockScroll(modalId);
297
+ modalIds.delete(el);
298
+ }
299
+ syncBackdropZoom();
300
+ if (!dialog)
301
+ return;
302
+ const backdropHandler = backdropClickHandlers.get(el);
303
+ if (backdropHandler) {
304
+ dialog.removeEventListener('click', backdropHandler);
305
+ backdropClickHandlers.delete(el);
306
+ }
307
+ const escapeHandler = escapeKeyHandlers.get(el);
308
+ if (escapeHandler) {
309
+ dialog.removeEventListener('keydown', escapeHandler);
310
+ escapeKeyHandlers.delete(el);
311
+ }
312
+ const closeButton = dialog.querySelector('.close-button');
313
+ if (closeButton) {
314
+ const closeHandler = closeButtonHandlers.get(el);
315
+ if (closeHandler) {
316
+ closeButton.removeEventListener('click', closeHandler);
317
+ closeButtonHandlers.delete(el);
318
+ }
319
+ }
320
+ const modalContent = dialog.querySelector('.modal-content');
321
+ if (modalContent) {
322
+ const enterHandler = hoverEnterHandlers.get(el);
323
+ const leaveHandler = hoverLeaveHandlers.get(el);
324
+ if (enterHandler) {
325
+ modalContent.removeEventListener('mouseenter', enterHandler);
326
+ hoverEnterHandlers.delete(el);
327
+ }
328
+ if (leaveHandler) {
329
+ modalContent.removeEventListener('mouseleave', leaveHandler);
330
+ hoverLeaveHandlers.delete(el);
331
+ }
332
+ }
333
+ }
482
334
  export class TyModal extends HTMLElement {
483
- /** Observed attributes */
484
- static get observedAttributes() {
485
- return ['open', 'backdrop', 'close-on-outside-click', 'close-on-escape', 'label'];
486
- }
487
- constructor() {
488
- super();
489
- this.attachShadow({ mode: 'open' });
490
- }
491
- connectedCallback() {
492
- render(this);
493
- // Add public API methods
494
- this.show = () => openModal(this);
495
- this.hide = (opts) => closeModal(this, 'programmatic', opts);
496
- }
497
- disconnectedCallback() {
498
- cleanup(this);
499
- }
500
- attributeChangedCallback(_name, _oldValue, _newValue) {
501
- // React (and any framework that conditionally mounts an already-open
502
- // modal) sets initial attributes BEFORE inserting the element into the
503
- // document, and showModal() on a disconnected <dialog> throws
504
- // InvalidStateError. Defer — connectedCallback render()s on insertion
505
- // and applies `open` then.
506
- if (!this.isConnected)
507
- return;
508
- // Re-render on attribute changes
509
- render(this);
510
- }
511
- }
512
- // Register the custom element
335
+ static get observedAttributes() {
336
+ return ['open', 'backdrop', 'close-on-outside-click', 'close-on-escape', 'prevent-outside-click', 'prevent-escape', 'label'];
337
+ }
338
+ get open() { return parseBoolAttr(this, 'open'); }
339
+ set open(v) { v ? this.setAttribute('open', 'true') : this.removeAttribute('open'); }
340
+ get backdrop() { return getModalAttributes(this).backdrop; }
341
+ set backdrop(v) { this.setAttribute('backdrop', String(!!v)); }
342
+ get closeOnOutsideClick() { return getModalAttributes(this).closeOnOutsideClick; }
343
+ set closeOnOutsideClick(v) { this.setAttribute('close-on-outside-click', String(!!v)); }
344
+ get closeOnEscape() { return getModalAttributes(this).closeOnEscape; }
345
+ set closeOnEscape(v) { this.setAttribute('close-on-escape', String(!!v)); }
346
+ get preventOutsideClick() { return parseBoolAttr(this, 'prevent-outside-click'); }
347
+ set preventOutsideClick(v) {
348
+ v ? this.setAttribute('prevent-outside-click', '') : this.removeAttribute('prevent-outside-click');
349
+ }
350
+ get preventEscape() { return parseBoolAttr(this, 'prevent-escape'); }
351
+ set preventEscape(v) {
352
+ v ? this.setAttribute('prevent-escape', '') : this.removeAttribute('prevent-escape');
353
+ }
354
+ constructor() {
355
+ super();
356
+ this.attachShadow({ mode: 'open' });
357
+ }
358
+ connectedCallback() {
359
+ render(this);
360
+ this.show = () => openModal(this);
361
+ this.hide = (opts) => closeModal(this, 'programmatic', opts);
362
+ }
363
+ disconnectedCallback() {
364
+ cleanup(this);
365
+ }
366
+ attributeChangedCallback(_name, _oldValue, _newValue) {
367
+ if (!this.isConnected)
368
+ return;
369
+ render(this);
370
+ }
371
+ }
513
372
  if (!customElements.get('ty-modal')) {
514
- customElements.define('ty-modal', TyModal);
373
+ customElements.define('ty-modal', TyModal);
515
374
  }
516
- // Also exposed as ty-dialog — the platform/ARIA name (wraps native <dialog>).
517
- // A constructor can only register once, hence the subclass.
518
375
  if (!customElements.get('ty-dialog')) {
519
- customElements.define('ty-dialog', class TyDialog extends TyModal {
520
- });
376
+ customElements.define('ty-dialog', class TyDialog extends TyModal {
377
+ });
521
378
  }
522
- //# sourceMappingURL=modal.js.map