@feugene/granularity 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (306) hide show
  1. package/dist/GrBadge.css +1 -0
  2. package/dist/chunks/{adapter-CRyH4YC8.js.map → adapter-BqqjEZGU.js.map} +1 -1
  3. package/dist/chunks/arrow-up-DZdIrpaF.js.map +1 -0
  4. package/dist/chunks/{config-Dkq0HeLe.js → config-3I3vqDW4.js} +1 -1
  5. package/dist/chunks/{config-Dkq0HeLe.js.map → config-3I3vqDW4.js.map} +1 -1
  6. package/dist/chunks/config-72sW9Bag2.js.map +1 -0
  7. package/dist/chunks/{config-B8rC_K97.js.map → config-B1kFmhpM.js.map} +1 -1
  8. package/dist/chunks/{config-BhvNm4Pp.js.map → config-B2YTEe_h.js.map} +1 -1
  9. package/dist/chunks/{config-BaMO9dQh.js → config-B61cX08u.js} +1 -1
  10. package/dist/chunks/{config-BaMO9dQh.js.map → config-B61cX08u.js.map} +1 -1
  11. package/dist/chunks/{config-C5TN9J2Z.js.map → config-BK6Odc3T.js.map} +1 -1
  12. package/dist/chunks/{config-C0rHS1-l.js.map → config-BOdu-O_B.js.map} +1 -1
  13. package/dist/chunks/{config-D27BL0H9.js.map → config-BU0olrvr.js.map} +1 -1
  14. package/dist/chunks/{config-XoANT5mU.js → config-BY81UKv0.js} +1 -1
  15. package/dist/chunks/{config-XoANT5mU.js.map → config-BY81UKv0.js.map} +1 -1
  16. package/dist/chunks/config-B_nSBc7W.js.map +1 -0
  17. package/dist/chunks/{config-DbqFk-IW.js.map → config-BbKroFtR.js.map} +1 -1
  18. package/dist/chunks/config-Bcf-8XlK2.js.map +1 -0
  19. package/dist/chunks/{config-C_AOYGBr.js.map → config-BduXEXjt.js.map} +1 -1
  20. package/dist/chunks/config-BeQuykDk2.js.map +1 -0
  21. package/dist/chunks/{config-C_-02Pu-.js.map → config-BgBWt9-A.js.map} +1 -1
  22. package/dist/chunks/{config-Dusr9jcE.js.map → config-BnWSHTEU.js.map} +1 -1
  23. package/dist/chunks/{config-C4AlNm912.js → config-Br_qI3Za.js} +1 -1
  24. package/dist/chunks/config-Br_qI3Za.js.map +1 -0
  25. package/dist/chunks/config-C6zT56Jc2.js.map +1 -0
  26. package/dist/chunks/{config-ZJVOVgqR.js → config-CFf7xaG1.js} +1 -1
  27. package/dist/chunks/{config-ZJVOVgqR.js.map → config-CFf7xaG1.js.map} +1 -1
  28. package/dist/chunks/{config-Cn8TG49a.js.map → config-CUwF4E4R.js.map} +1 -1
  29. package/dist/chunks/{config-wo-ncgTH.js.map → config-CdJpnjDj.js.map} +1 -1
  30. package/dist/chunks/config-CetsgpaZ2.js.map +1 -0
  31. package/dist/chunks/{config-D1DI3roo.js.map → config-Ci9nXGUV.js.map} +1 -1
  32. package/dist/chunks/{config-CjEplP6D.js.map → config-CpMYmvVT.js.map} +1 -1
  33. package/dist/chunks/{config-BXffa3ta.js → config-Cp_XHsze.js} +2 -2
  34. package/dist/chunks/{config-BXffa3ta.js.map → config-Cp_XHsze.js.map} +1 -1
  35. package/dist/chunks/{config-D98m8iyz.js.map → config-CvGdaXvH.js.map} +1 -1
  36. package/dist/chunks/{config-CtomKf-k.js.map → config-D4s_QWVP.js.map} +1 -1
  37. package/dist/chunks/{config-D32znLtV2.js.map → config-D6Oe3blW2.js.map} +1 -1
  38. package/dist/chunks/{config-CVnluhKO.js.map → config-DF_RlCIO.js.map} +1 -1
  39. package/dist/chunks/{config-C4qAA0Sb.js.map → config-DSBlHQVa.js.map} +1 -1
  40. package/dist/chunks/{config-Mp9DJ9Mq2.js.map → config-DaHI5bD_2.js.map} +1 -1
  41. package/dist/chunks/{config-3SftO0uG.js.map → config-Dcb7xK-C.js.map} +1 -1
  42. package/dist/chunks/config-DmjwIqx1.js.map +1 -0
  43. package/dist/chunks/{config-D6Af6WZw.js.map → config-DoBk_CzG.js.map} +1 -1
  44. package/dist/chunks/config-Dzd6BzIN2.js.map +1 -0
  45. package/dist/chunks/{config-Db4BEG8Y.js.map → config-O1_FTqD3.js.map} +1 -1
  46. package/dist/chunks/{config-C9AOlKah.js.map → config-V3LF-b_F.js.map} +1 -1
  47. package/dist/chunks/config-_3De4HFf.js.map +1 -0
  48. package/dist/chunks/{config-B44QVIge.js → config-d386uO3O.js} +1 -1
  49. package/dist/chunks/{config-B44QVIge.js.map → config-d386uO3O.js.map} +1 -1
  50. package/dist/chunks/{config-DudL02ag.js.map → config-iuMz79Mg.js.map} +1 -1
  51. package/dist/chunks/{config-DJWf0kWe.js.map → config-vcDVCYXo.js.map} +1 -1
  52. package/dist/chunks/{en-QNZhjmSJ.js.map → en-foQA8imU.js.map} +1 -1
  53. package/dist/chunks/{es-CKLPuE0h.js → es-Bx3Kuy6z.js} +3 -3
  54. package/dist/chunks/{es-CKLPuE0h.js.map → es-Bx3Kuy6z.js.map} +1 -1
  55. package/dist/chunks/{es-C1c5XsIK.js.map → es-DZFSojYL.js.map} +1 -1
  56. package/dist/chunks/{fileValidation-CwJA8Vmn.js.map → fileValidation-c_iBg74s.js.map} +1 -1
  57. package/dist/chunks/{grBadgeStyles-pqqbVgLk.js → grBadgeStyles-Cq0hH2Ls.js} +7 -5
  58. package/dist/chunks/grBadgeStyles-Cq0hH2Ls.js.map +1 -0
  59. package/dist/chunks/{grButtonStyles-BxZW2x0-.js.map → grButtonStyles-DKuXUemw.js.map} +1 -1
  60. package/dist/chunks/{grDropdownStyles-DMp7RrI9.js.map → grDropdownStyles-B41mVR1H.js.map} +1 -1
  61. package/dist/chunks/{grLoadingStyles-CrxZHFL0.js.map → grLoadingStyles-DmI-SJ3i.js.map} +1 -1
  62. package/dist/chunks/{grSelectStyles-DTAYNzxn.js.map → grSelectStyles-pUt19VGy.js.map} +1 -1
  63. package/dist/chunks/{grStyle-5EMYYctb.js.map → grStyle-CUaTmpyW.js.map} +1 -1
  64. package/dist/chunks/{granularityI18n-CXa_uN7f.js → granularityI18n-LcJ88Vce.js} +1 -1
  65. package/dist/chunks/{granularityI18n-CXa_uN7f.js.map → granularityI18n-LcJ88Vce.js.map} +1 -1
  66. package/dist/chunks/loader-circle-BUntgosj.js.map +1 -0
  67. package/dist/chunks/{ru-DcF7ciz0.js.map → ru-LhkPpPIl.js.map} +1 -1
  68. package/dist/chunks/{safelist-BY7z8fOl.js → safelist-DccvJqCE.js} +1 -1
  69. package/dist/chunks/{safelist-BY7z8fOl.js.map → safelist-DccvJqCE.js.map} +1 -1
  70. package/dist/chunks/safelist-_UVzJrgE2.js.map +1 -0
  71. package/dist/chunks/{shared-BkIIEolF.js → shared-D24hqkyM.js} +42 -42
  72. package/dist/chunks/{shared-BkIIEolF.js.map → shared-D24hqkyM.js.map} +1 -1
  73. package/dist/components/GrBadge/chunks/GrBadge-Cs_cVfuO.js +36 -0
  74. package/dist/components/GrBadge/chunks/GrBadge-Cs_cVfuO.js.map +1 -0
  75. package/dist/components/GrBadge/index.js +2 -2
  76. package/dist/components/GrBadgeWrap/chunks/{GrBadgeWrap-CxtamOZ-.js.map → GrBadgeWrap-BEnE9DyJ.js.map} +1 -1
  77. package/dist/components/GrBadgeWrap/index.js +2 -2
  78. package/dist/components/GrBottomNav/chunks/{GrBottomNav-CsmQ7mTJ.js.map → GrBottomNav-XPHwd7UQ.js.map} +1 -1
  79. package/dist/components/GrBottomNav/index.js +2 -2
  80. package/dist/components/GrButton/chunks/{GrButton-BmGdp8-4.js → GrButton-CPDKonoB.js} +2 -2
  81. package/dist/components/GrButton/chunks/{GrButton-BmGdp8-4.js.map → GrButton-CPDKonoB.js.map} +1 -1
  82. package/dist/components/GrButton/index.js +4 -4
  83. package/dist/components/GrButtonGroup/chunks/{GrButtonGroup-CBj4tpms.js.map → GrButtonGroup-CPOH6V-P.js.map} +1 -1
  84. package/dist/components/GrButtonGroup/index.js +2 -2
  85. package/dist/components/GrCard/chunks/{GrCard-xtDcXBr7.js → GrCard-6yk1RRAQ.js} +1 -1
  86. package/dist/components/GrCard/chunks/{GrCard-xtDcXBr7.js.map → GrCard-6yk1RRAQ.js.map} +1 -1
  87. package/dist/components/GrCard/index.js +1 -1
  88. package/dist/components/GrCollapse/chunks/{GrCollapse-HU20VTVn.js → GrCollapse-CU94BTAR.js} +1 -1
  89. package/dist/components/GrCollapse/chunks/{GrCollapse-HU20VTVn.js.map → GrCollapse-CU94BTAR.js.map} +1 -1
  90. package/dist/components/GrCollapse/index.js +1 -1
  91. package/dist/components/GrConfirmDialog/chunks/{GrConfirmDialog-DjGOX9Hd.js → GrConfirmDialog-BGmyfDd2.js} +32 -9
  92. package/dist/components/GrConfirmDialog/chunks/GrConfirmDialog-BGmyfDd2.js.map +1 -0
  93. package/dist/components/GrConfirmDialog/index.js +2 -2
  94. package/dist/components/GrDataTable/chunks/{GrDataTable-QDOJWmdY.js → GrDataTable-ClcYiyc8.js} +2 -2
  95. package/dist/components/GrDataTable/chunks/{GrDataTable-QDOJWmdY.js.map → GrDataTable-ClcYiyc8.js.map} +1 -1
  96. package/dist/components/GrDataTable/index.js +2 -2
  97. package/dist/components/GrDialog/chunks/{GrDialog-gObmaVEl.js → GrDialog-Ba3Ojcsq.js} +1 -1
  98. package/dist/components/GrDialog/chunks/{GrDialog-gObmaVEl.js.map → GrDialog-Ba3Ojcsq.js.map} +1 -1
  99. package/dist/components/GrDialog/index.js +2 -2
  100. package/dist/components/GrDrawer/chunks/{GrDrawer-BJL_gTxI.js → GrDrawer-BW3wtI7h.js} +2 -2
  101. package/dist/components/GrDrawer/chunks/{GrDrawer-BJL_gTxI.js.map → GrDrawer-BW3wtI7h.js.map} +1 -1
  102. package/dist/components/GrDrawer/index.js +2 -2
  103. package/dist/components/GrDropdown/chunks/{GrDropdown-BkACCXdp.js → GrDropdown-DOvHKnih.js} +1 -1
  104. package/dist/components/GrDropdown/chunks/{GrDropdown-BkACCXdp.js.map → GrDropdown-DOvHKnih.js.map} +1 -1
  105. package/dist/components/GrDropdown/index.js +2 -2
  106. package/dist/components/GrDropdownMenu/chunks/{GrDropdownMenu-BEenXrJw.js → GrDropdownMenu-CLPwbiyG.js} +1 -1
  107. package/dist/components/GrDropdownMenu/chunks/{GrDropdownMenu-BEenXrJw.js.map → GrDropdownMenu-CLPwbiyG.js.map} +1 -1
  108. package/dist/components/GrDropdownMenu/index.js +2 -2
  109. package/dist/components/GrEmptyState/chunks/{GrEmptyState-BLbMJFW-.js.map → GrEmptyState-Dj2V5GaX.js.map} +1 -1
  110. package/dist/components/GrEmptyState/index.js +2 -2
  111. package/dist/components/GrFileUpload/chunks/{GrFileUpload-DYRERhI2.js → GrFileUpload-BEKNB1RM.js} +5 -5
  112. package/dist/components/GrFileUpload/chunks/GrFileUpload-BEKNB1RM.js.map +1 -0
  113. package/dist/components/GrFileUpload/index.js +3 -3
  114. package/dist/components/GrFormField/chunks/{GrFormField-C1oWTT_0.js.map → GrFormField-U7c_aDPJ.js.map} +1 -1
  115. package/dist/components/GrFormField/index.js +2 -2
  116. package/dist/components/GrFormFile/chunks/{GrFormFile-deDPicM2.js → GrFormFile-CIleXnfr.js} +3 -3
  117. package/dist/components/GrFormFile/chunks/{GrFormFile-deDPicM2.js.map → GrFormFile-CIleXnfr.js.map} +1 -1
  118. package/dist/components/GrFormFile/index.js +2 -2
  119. package/dist/components/GrFormSection/chunks/{GrFormSection-CWBk2EUm.js.map → GrFormSection-DpGhY7MX.js.map} +1 -1
  120. package/dist/components/GrFormSection/index.js +2 -2
  121. package/dist/components/GrImageViewer/chunks/{GrImageViewer-Cpj5zyZQ.js.map → GrImageViewer-Cn1X-sWS.js.map} +1 -1
  122. package/dist/components/GrImageViewer/index.js +2 -2
  123. package/dist/components/GrInput/chunks/{GrInput-CfO2RsM-.js.map → GrInput-gwFQ1sk4.js.map} +1 -1
  124. package/dist/components/GrInput/index.js +2 -2
  125. package/dist/components/GrInputTag/chunks/{GrInputTag-G_VYJPF9.js → GrInputTag-Dcj3F6rQ.js} +2 -2
  126. package/dist/components/GrInputTag/chunks/{GrInputTag-G_VYJPF9.js.map → GrInputTag-Dcj3F6rQ.js.map} +1 -1
  127. package/dist/components/GrInputTag/index.js +2 -2
  128. package/dist/components/GrLink/chunks/{GrLink-BLPigqij.js → GrLink-BMwn2iJ9.js} +1 -1
  129. package/dist/components/GrLink/chunks/{GrLink-BLPigqij.js.map → GrLink-BMwn2iJ9.js.map} +1 -1
  130. package/dist/components/GrLink/index.js +2 -2
  131. package/dist/components/GrList/chunks/{GrList-BPjQf-72.js → GrList-D8nM1FuV.js} +1 -1
  132. package/dist/components/GrList/chunks/{GrList-BPjQf-72.js.map → GrList-D8nM1FuV.js.map} +1 -1
  133. package/dist/components/GrList/index.js +2 -2
  134. package/dist/components/GrLoading/chunks/{GrLoading-C3QZKwE-.js → GrLoading-mfyfghxs.js} +2 -2
  135. package/dist/components/GrLoading/chunks/{GrLoading-C3QZKwE-.js.map → GrLoading-mfyfghxs.js.map} +1 -1
  136. package/dist/components/GrLoading/index.js +2 -2
  137. package/dist/components/GrModal/index.js +1 -1
  138. package/dist/components/GrNavbar/chunks/{GrNavbar-BoVuSZ-V.js → GrNavbar-BH7XiF9q.js} +2 -2
  139. package/dist/components/GrNavbar/chunks/{GrNavbar-BoVuSZ-V.js.map → GrNavbar-BH7XiF9q.js.map} +1 -1
  140. package/dist/components/GrNavbar/index.js +2 -2
  141. package/dist/components/GrNumberInput/chunks/{GrNumberInput-ECtFCIKV.js → GrNumberInput-YIWhV85S.js} +1 -1
  142. package/dist/components/GrNumberInput/chunks/{GrNumberInput-ECtFCIKV.js.map → GrNumberInput-YIWhV85S.js.map} +1 -1
  143. package/dist/components/GrNumberInput/index.js +2 -2
  144. package/dist/components/GrPagination/chunks/{GrPagination-DW44c2-a.js → GrPagination-cD6rFO9W.js} +3 -3
  145. package/dist/components/GrPagination/chunks/{GrPagination-DW44c2-a.js.map → GrPagination-cD6rFO9W.js.map} +1 -1
  146. package/dist/components/GrPagination/index.js +2 -2
  147. package/dist/components/GrProgressBar/chunks/{GrProgressBar-hQQK8cYh.js → GrProgressBar-C2HdYjp4.js} +1 -1
  148. package/dist/components/GrProgressBar/chunks/{GrProgressBar-hQQK8cYh.js.map → GrProgressBar-C2HdYjp4.js.map} +1 -1
  149. package/dist/components/GrProgressBar/index.js +2 -2
  150. package/dist/components/GrPromptDialog/chunks/{GrPromptDialog-CUMW6_58.js → GrPromptDialog-Du1aLfcA.js} +55 -29
  151. package/dist/components/GrPromptDialog/chunks/GrPromptDialog-Du1aLfcA.js.map +1 -0
  152. package/dist/components/GrPromptDialog/index.js +2 -2
  153. package/dist/components/GrRadio/chunks/{GrRadio-B_t_ZNVl.js → GrRadio-IFU9Hiu2.js} +1 -1
  154. package/dist/components/GrRadio/chunks/{GrRadio-B_t_ZNVl.js.map → GrRadio-IFU9Hiu2.js.map} +1 -1
  155. package/dist/components/GrRadio/index.js +2 -2
  156. package/dist/components/GrRadioGroup/chunks/{GrRadioGroup-CVlgpUBr.js → GrRadioGroup-C8IcJuuF.js} +2 -2
  157. package/dist/components/GrRadioGroup/chunks/{GrRadioGroup-CVlgpUBr.js.map → GrRadioGroup-C8IcJuuF.js.map} +1 -1
  158. package/dist/components/GrRadioGroup/index.js +2 -2
  159. package/dist/components/GrResponseErrorBanner/chunks/GrResponseErrorBanner-BMvXREcv.js +221 -0
  160. package/dist/components/GrResponseErrorBanner/chunks/GrResponseErrorBanner-BMvXREcv.js.map +1 -0
  161. package/dist/components/GrResponseErrorBanner/chunks/GrResponseErrorBanner-CzdTh4gd.js +782 -0
  162. package/dist/components/GrResponseErrorBanner/chunks/GrResponseErrorBanner-CzdTh4gd.js.map +1 -0
  163. package/dist/components/GrResponseErrorBanner/index.js +3 -0
  164. package/dist/components/GrSegmented/chunks/{GrSegmented-BPl1koHc.js → GrSegmented-BRmwBsEG.js} +1 -1
  165. package/dist/components/GrSegmented/chunks/{GrSegmented-BPl1koHc.js.map → GrSegmented-BRmwBsEG.js.map} +1 -1
  166. package/dist/components/GrSegmented/index.js +2 -2
  167. package/dist/components/GrSelect/chunks/{GrSelect-C3NAfTqL.js → GrSelect-W9lbAjRZ.js} +2 -2
  168. package/dist/components/GrSelect/chunks/{GrSelect-C3NAfTqL.js.map → GrSelect-W9lbAjRZ.js.map} +1 -1
  169. package/dist/components/GrSelect/index.js +2 -2
  170. package/dist/components/GrSidebar/chunks/{GrSidebar-DVucgcfV.js.map → GrSidebar-BpBtAGBs.js.map} +1 -1
  171. package/dist/components/GrSidebar/index.js +2 -2
  172. package/dist/components/GrSkeleton/chunks/{GrSkeleton-CDTvCSoH.js.map → GrSkeleton-URyMkBqI.js.map} +1 -1
  173. package/dist/components/GrSkeleton/index.js +2 -2
  174. package/dist/components/GrSwitch/chunks/{GrSwitch-Bnamgqb2.js → GrSwitch-29h0QhMO.js} +1 -1
  175. package/dist/components/GrSwitch/chunks/{GrSwitch-Bnamgqb2.js.map → GrSwitch-29h0QhMO.js.map} +1 -1
  176. package/dist/components/GrSwitch/index.js +2 -2
  177. package/dist/components/GrTable/chunks/{GrTable-CyQ9uw8A.js.map → GrTable-dTWUxzRm.js.map} +1 -1
  178. package/dist/components/GrTable/index.js +2 -2
  179. package/dist/components/GrTabs/chunks/{GrTabs-CiGmcmvm.js.map → GrTabs-D3ntDdtg.js.map} +1 -1
  180. package/dist/components/GrTabs/index.js +2 -2
  181. package/dist/components/GrTextarea/chunks/{GrTextarea-VBSc6A7D.js → GrTextarea-Cv6r2GuC.js} +1 -1
  182. package/dist/components/GrTextarea/chunks/{GrTextarea-VBSc6A7D.js.map → GrTextarea-Cv6r2GuC.js.map} +1 -1
  183. package/dist/components/GrTextarea/index.js +2 -2
  184. package/dist/components/GrToaster/chunks/{GrToaster-I1e-zCpf.js → GrToaster-DluJmR5D.js} +2 -2
  185. package/dist/components/GrToaster/chunks/{GrToaster-I1e-zCpf.js.map → GrToaster-DluJmR5D.js.map} +1 -1
  186. package/dist/components/GrToaster/index.js +2 -2
  187. package/dist/components/GrTooltip/chunks/{GrTooltip-CrV1gI_c.js.map → GrTooltip-B7I1AVIC.js.map} +1 -1
  188. package/dist/components/GrTooltip/index.js +2 -2
  189. package/dist/components/GrTree/chunks/{GrTree-Dlev9oBd.js → GrTree-B2SMU5Py.js} +1 -1
  190. package/dist/components/GrTree/chunks/{GrTree-Dlev9oBd.js.map → GrTree-B2SMU5Py.js.map} +1 -1
  191. package/dist/components/GrTree/index.js +2 -2
  192. package/dist/components/GrTreeSelect/chunks/{GrTreeSelect-DkAIksRW.js → GrTreeSelect-C1abr5uH.js} +4 -4
  193. package/dist/components/GrTreeSelect/chunks/{GrTreeSelect-DkAIksRW.js.map → GrTreeSelect-C1abr5uH.js.map} +1 -1
  194. package/dist/components/GrTreeSelect/index.js +2 -2
  195. package/dist/directives/dropzone.js +1 -1
  196. package/dist/directives/loading.js +1 -1
  197. package/dist/fileValidation/index.js +1 -1
  198. package/dist/granular-provider-node.js +3 -3
  199. package/dist/granular-provider.js +1 -1
  200. package/dist/i18n/all.js +1 -1
  201. package/dist/i18n/index.js +2 -2
  202. package/dist/index.js +486 -1027
  203. package/dist/index.js.map +1 -1
  204. package/dist/types/src/components/GrBadge/grBadgeStyles.d.ts +1 -1
  205. package/dist/types/src/components/GrConfirmDialog/GrConfirmDialog.vue.d.ts +35 -2
  206. package/dist/types/src/components/GrDialogService/GrDialogServiceHost.vue.d.ts +3 -0
  207. package/dist/types/src/components/GrDialogService/config.d.ts +1 -0
  208. package/dist/types/src/components/GrDialogService/index.d.ts +4 -0
  209. package/dist/types/src/components/GrDialogService/store.d.ts +22 -0
  210. package/dist/types/src/components/GrDialogService/types.d.ts +141 -0
  211. package/dist/types/src/components/GrDialogService/useDialogService.d.ts +13 -0
  212. package/dist/types/src/components/GrFileUpload/GrFileUpload.vue.d.ts +3 -3
  213. package/dist/types/src/components/GrFormFile/GrFormFile.vue.d.ts +2 -2
  214. package/dist/types/src/components/GrInput/GrInput.vue.d.ts +3 -3
  215. package/dist/types/src/components/GrInputTag/GrInputTag.vue.d.ts +1 -1
  216. package/dist/types/src/components/GrNumberInput/GrNumberInput.vue.d.ts +4 -4
  217. package/dist/types/src/components/GrPromptDialog/GrPromptDialog.vue.d.ts +41 -2
  218. package/dist/types/src/components/GrResponseErrorBanner/GrUploadErrorBanner.vue.d.ts +1 -1
  219. package/dist/types/src/components/GrResponseErrorBanner/index.d.ts +2 -2
  220. package/dist/types/src/components/GrResponseErrorBanner/parsers/createResponseErrorClassifier.d.ts +27 -0
  221. package/dist/types/src/components/GrResponseErrorBanner/parsers/index.d.ts +2 -2
  222. package/dist/types/src/components/GrSelect/GrSelect.vue.d.ts +1 -1
  223. package/dist/types/src/components/GrTextarea/GrTextarea.vue.d.ts +2 -2
  224. package/dist/types/src/components/GrToaster/GrToaster.vue.d.ts +1 -1
  225. package/dist/types/src/index.d.ts +1 -0
  226. package/package.json +5 -1
  227. package/dist/chunks/arrow-up-DNXrMoAI.js.map +0 -1
  228. package/dist/chunks/config-C0b_3LJV.js.map +0 -1
  229. package/dist/chunks/config-C4AlNm912.js.map +0 -1
  230. package/dist/chunks/config-CxCAfXZ1.js.map +0 -1
  231. package/dist/chunks/config-DDEWvph-2.js.map +0 -1
  232. package/dist/chunks/config-DDLoUczC.js.map +0 -1
  233. package/dist/chunks/config-DQOSlWvp.js.map +0 -1
  234. package/dist/chunks/config-DnsISjD8.js.map +0 -1
  235. package/dist/chunks/config-DxqMXzNV2.js.map +0 -1
  236. package/dist/chunks/config-DxqpV-KM.js.map +0 -1
  237. package/dist/chunks/config-rxFL-6yF2.js.map +0 -1
  238. package/dist/chunks/grBadgeStyles-pqqbVgLk.js.map +0 -1
  239. package/dist/chunks/loader-circle-B2rWDk5u.js.map +0 -1
  240. package/dist/chunks/safelist-BF822K15.js.map +0 -1
  241. package/dist/components/GrBadge/chunks/GrBadge-wuIb4_2K.js +0 -32
  242. package/dist/components/GrBadge/chunks/GrBadge-wuIb4_2K.js.map +0 -1
  243. package/dist/components/GrConfirmDialog/chunks/GrConfirmDialog-DjGOX9Hd.js.map +0 -1
  244. package/dist/components/GrFileUpload/chunks/GrFileUpload-DYRERhI2.js.map +0 -1
  245. package/dist/components/GrPromptDialog/chunks/GrPromptDialog-CUMW6_58.js.map +0 -1
  246. /package/dist/chunks/{_plugin-vue_export-helper-BG_EqiDU.js → _plugin-vue_export-helper-DQR7Sy7Q.js} +0 -0
  247. /package/dist/chunks/{adapter-CRyH4YC8.js → adapter-BqqjEZGU.js} +0 -0
  248. /package/dist/chunks/{arrow-up-DNXrMoAI.js → arrow-up-DZdIrpaF.js} +0 -0
  249. /package/dist/chunks/{config-C0b_3LJV.js → config-72sW9Bag2.js} +0 -0
  250. /package/dist/chunks/{config-B8rC_K97.js → config-B1kFmhpM.js} +0 -0
  251. /package/dist/chunks/{config-BhvNm4Pp.js → config-B2YTEe_h.js} +0 -0
  252. /package/dist/chunks/{config-C5TN9J2Z.js → config-BK6Odc3T.js} +0 -0
  253. /package/dist/chunks/{config-C0rHS1-l.js → config-BOdu-O_B.js} +0 -0
  254. /package/dist/chunks/{config-D27BL0H9.js → config-BU0olrvr.js} +0 -0
  255. /package/dist/chunks/{config-DxqMXzNV2.js → config-B_nSBc7W.js} +0 -0
  256. /package/dist/chunks/{config-DbqFk-IW.js → config-BbKroFtR.js} +0 -0
  257. /package/dist/chunks/{config-DQOSlWvp.js → config-Bcf-8XlK2.js} +0 -0
  258. /package/dist/chunks/{config-C_AOYGBr.js → config-BduXEXjt.js} +0 -0
  259. /package/dist/chunks/{config-DnsISjD8.js → config-BeQuykDk2.js} +0 -0
  260. /package/dist/chunks/{config-C_-02Pu-.js → config-BgBWt9-A.js} +0 -0
  261. /package/dist/chunks/{config-Dusr9jcE.js → config-BnWSHTEU.js} +0 -0
  262. /package/dist/chunks/{config-DxqpV-KM.js → config-C6zT56Jc2.js} +0 -0
  263. /package/dist/chunks/{config-Cn8TG49a.js → config-CUwF4E4R.js} +0 -0
  264. /package/dist/chunks/{config-wo-ncgTH.js → config-CdJpnjDj.js} +0 -0
  265. /package/dist/chunks/{config-CxCAfXZ1.js → config-CetsgpaZ2.js} +0 -0
  266. /package/dist/chunks/{config-D1DI3roo.js → config-Ci9nXGUV.js} +0 -0
  267. /package/dist/chunks/{config-CjEplP6D.js → config-CpMYmvVT.js} +0 -0
  268. /package/dist/chunks/{config-D98m8iyz.js → config-CvGdaXvH.js} +0 -0
  269. /package/dist/chunks/{config-CtomKf-k.js → config-D4s_QWVP.js} +0 -0
  270. /package/dist/chunks/{config-D32znLtV2.js → config-D6Oe3blW2.js} +0 -0
  271. /package/dist/chunks/{config-CVnluhKO.js → config-DF_RlCIO.js} +0 -0
  272. /package/dist/chunks/{config-C4qAA0Sb.js → config-DSBlHQVa.js} +0 -0
  273. /package/dist/chunks/{config-Mp9DJ9Mq2.js → config-DaHI5bD_2.js} +0 -0
  274. /package/dist/chunks/{config-3SftO0uG.js → config-Dcb7xK-C.js} +0 -0
  275. /package/dist/chunks/{config-DDEWvph-2.js → config-DmjwIqx1.js} +0 -0
  276. /package/dist/chunks/{config-D6Af6WZw.js → config-DoBk_CzG.js} +0 -0
  277. /package/dist/chunks/{config-DDLoUczC.js → config-Dzd6BzIN2.js} +0 -0
  278. /package/dist/chunks/{config-Db4BEG8Y.js → config-O1_FTqD3.js} +0 -0
  279. /package/dist/chunks/{config-C9AOlKah.js → config-V3LF-b_F.js} +0 -0
  280. /package/dist/chunks/{config-rxFL-6yF2.js → config-_3De4HFf.js} +0 -0
  281. /package/dist/chunks/{config-DudL02ag.js → config-iuMz79Mg.js} +0 -0
  282. /package/dist/chunks/{config-DJWf0kWe.js → config-vcDVCYXo.js} +0 -0
  283. /package/dist/chunks/{en-QNZhjmSJ.js → en-foQA8imU.js} +0 -0
  284. /package/dist/chunks/{es-C1c5XsIK.js → es-DZFSojYL.js} +0 -0
  285. /package/dist/chunks/{fileValidation-CwJA8Vmn.js → fileValidation-c_iBg74s.js} +0 -0
  286. /package/dist/chunks/{grButtonStyles-BxZW2x0-.js → grButtonStyles-DKuXUemw.js} +0 -0
  287. /package/dist/chunks/{grDropdownStyles-DMp7RrI9.js → grDropdownStyles-B41mVR1H.js} +0 -0
  288. /package/dist/chunks/{grLoadingStyles-CrxZHFL0.js → grLoadingStyles-DmI-SJ3i.js} +0 -0
  289. /package/dist/chunks/{grSelectStyles-DTAYNzxn.js → grSelectStyles-pUt19VGy.js} +0 -0
  290. /package/dist/chunks/{grStyle-5EMYYctb.js → grStyle-CUaTmpyW.js} +0 -0
  291. /package/dist/chunks/{loader-circle-B2rWDk5u.js → loader-circle-BUntgosj.js} +0 -0
  292. /package/dist/chunks/{ru-DcF7ciz0.js → ru-LhkPpPIl.js} +0 -0
  293. /package/dist/chunks/{safelist-BF822K15.js → safelist-_UVzJrgE2.js} +0 -0
  294. /package/dist/components/GrBadgeWrap/chunks/{GrBadgeWrap-CxtamOZ-.js → GrBadgeWrap-BEnE9DyJ.js} +0 -0
  295. /package/dist/components/GrBottomNav/chunks/{GrBottomNav-CsmQ7mTJ.js → GrBottomNav-XPHwd7UQ.js} +0 -0
  296. /package/dist/components/GrButtonGroup/chunks/{GrButtonGroup-CBj4tpms.js → GrButtonGroup-CPOH6V-P.js} +0 -0
  297. /package/dist/components/GrEmptyState/chunks/{GrEmptyState-BLbMJFW-.js → GrEmptyState-Dj2V5GaX.js} +0 -0
  298. /package/dist/components/GrFormField/chunks/{GrFormField-C1oWTT_0.js → GrFormField-U7c_aDPJ.js} +0 -0
  299. /package/dist/components/GrFormSection/chunks/{GrFormSection-CWBk2EUm.js → GrFormSection-DpGhY7MX.js} +0 -0
  300. /package/dist/components/GrImageViewer/chunks/{GrImageViewer-Cpj5zyZQ.js → GrImageViewer-Cn1X-sWS.js} +0 -0
  301. /package/dist/components/GrInput/chunks/{GrInput-CfO2RsM-.js → GrInput-gwFQ1sk4.js} +0 -0
  302. /package/dist/components/GrSidebar/chunks/{GrSidebar-DVucgcfV.js → GrSidebar-BpBtAGBs.js} +0 -0
  303. /package/dist/components/GrSkeleton/chunks/{GrSkeleton-CDTvCSoH.js → GrSkeleton-URyMkBqI.js} +0 -0
  304. /package/dist/components/GrTable/chunks/{GrTable-CyQ9uw8A.js → GrTable-dTWUxzRm.js} +0 -0
  305. /package/dist/components/GrTabs/chunks/{GrTabs-CiGmcmvm.js → GrTabs-D3ntDdtg.js} +0 -0
  306. /package/dist/components/GrTooltip/chunks/{GrTooltip-CrV1gI_c.js → GrTooltip-B7I1AVIC.js} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GrFileUpload-BEKNB1RM.js","names":["$slots"],"sources":["../../../../src/components/GrFileUpload/uploadViaXhr.ts","../../../../src/components/GrFileUpload/uploadState.ts","../../../../src/components/GrFileUpload/GrFileUpload.vue","../../../../src/components/GrFileUpload/GrFileUpload.vue"],"sourcesContent":["/**\n * Загрузка файлов через `XMLHttpRequest` с поддержкой прогресса аплоада и\n * отмены по `AbortSignal`. Используется в `GrFileUpload` для ветки `action`,\n * а также может применяться пользователем напрямую в `request`-загрузчике.\n */\n\nexport type GrUploadExtraDataValue = string | Blob\n\nexport type GrUploadExtraData = Record<string, GrUploadExtraDataValue | GrUploadExtraDataValue[]>\n\nexport interface GrUploadProgressInfo {\n /** Процент `0..100`. Для запросов без `lengthComputable` значение `0` и `indeterminate = true`. */\n percent: number\n loaded: number\n total: number\n indeterminate: boolean\n}\n\nexport interface UploadViaXhrOptions {\n url: string\n files: File[]\n name: string\n headers?: Record<string, string>\n withCredentials?: boolean\n extraData?: GrUploadExtraData\n signal: AbortSignal\n onProgress?: (info: GrUploadProgressInfo) => void\n}\n\nexport class GrUploadAbortError extends Error {\n constructor(message = 'Upload aborted') {\n super(message)\n this.name = 'GrUploadAbortError'\n }\n}\n\nexport class GrUploadHttpError extends Error {\n status: number\n payload: unknown\n\n constructor(status: number, payload: unknown) {\n super(`Upload failed with status ${status}`)\n this.name = 'GrUploadHttpError'\n this.status = status\n this.payload = payload\n }\n}\n\nfunction appendExtraFormData(body: FormData, extraData: GrUploadExtraData | undefined) {\n if (!extraData) return\n\n for (const [key, value] of Object.entries(extraData)) {\n if (Array.isArray(value)) {\n for (const entry of value) body.append(key, entry)\n continue\n }\n\n body.append(key, value)\n }\n}\n\nfunction parseResponse(xhr: XMLHttpRequest): unknown {\n const contentType = xhr.getResponseHeader('content-type') ?? ''\n const raw = xhr.responseText\n\n if (contentType.includes('application/json')) {\n try {\n return JSON.parse(raw)\n } catch {\n return raw\n }\n }\n\n return raw\n}\n\nexport function uploadViaXhr(options: UploadViaXhrOptions): Promise<unknown> {\n const { url, files, name, headers, withCredentials, extraData, signal, onProgress } = options\n\n return new Promise<unknown>((resolve, reject) => {\n if (signal.aborted) {\n reject(new GrUploadAbortError())\n return\n }\n\n const body = new FormData()\n for (const file of files) body.append(name, file)\n appendExtraFormData(body, extraData)\n\n const xhr = new XMLHttpRequest()\n xhr.open('POST', url, true)\n xhr.withCredentials = !!withCredentials\n\n if (headers) {\n for (const [key, value] of Object.entries(headers))\n xhr.setRequestHeader(key, value)\n }\n\n const onAbort = () => {\n try {\n xhr.abort()\n } catch {\n /* ignore */\n }\n }\n\n signal.addEventListener('abort', onAbort)\n\n const cleanup = () => {\n signal.removeEventListener('abort', onAbort)\n }\n\n if (onProgress) {\n xhr.upload.addEventListener('progress', (event) => {\n const indeterminate = !event.lengthComputable\n const total = indeterminate ? 0 : event.total\n const loaded = indeterminate ? 0 : event.loaded\n const percent = indeterminate || total === 0 ? 0 : Math.min(100, (loaded / total) * 100)\n onProgress({ percent, loaded, total, indeterminate })\n })\n }\n\n xhr.addEventListener('load', () => {\n cleanup()\n const payload = parseResponse(xhr)\n const status = xhr.status\n\n if (status >= 200 && status < 300) {\n if (onProgress) onProgress({ percent: 100, loaded: 1, total: 1, indeterminate: false })\n resolve(payload)\n return\n }\n\n reject(new GrUploadHttpError(status, payload))\n })\n\n xhr.addEventListener('error', () => {\n cleanup()\n reject(new Error('Network error during upload'))\n })\n\n xhr.addEventListener('abort', () => {\n cleanup()\n reject(new GrUploadAbortError())\n })\n\n xhr.send(body)\n })\n}\n","/**\n * Состояние процесса загрузки в `GrFileUpload`.\n *\n * Используется как для внутреннего управления UI, так и пробрасывается наружу\n * через scoped-слоты (`progress`, `default`) и `defineExpose`.\n */\n\nexport type GrUploadPhase = 'idle' | 'uploading' | 'success' | 'error'\n\nexport interface GrUploadStateIdle {\n phase: 'idle'\n percent: 0\n indeterminate: false\n loaded: 0\n total: 0\n}\n\nexport interface GrUploadStateUploading {\n phase: 'uploading'\n percent: number\n indeterminate: boolean\n loaded: number\n total: number\n}\n\nexport interface GrUploadStateSuccess {\n phase: 'success'\n percent: 100\n indeterminate: false\n loaded: number\n total: number\n}\n\nexport interface GrUploadStateError {\n phase: 'error'\n percent: number\n indeterminate: false\n loaded: number\n total: number\n error: unknown\n}\n\nexport type GrUploadState =\n | GrUploadStateIdle\n | GrUploadStateUploading\n | GrUploadStateSuccess\n | GrUploadStateError\n\nexport const GR_UPLOAD_STATE_IDLE: GrUploadStateIdle = Object.freeze({\n phase: 'idle',\n percent: 0,\n indeterminate: false,\n loaded: 0,\n total: 0,\n})\n","<script setup lang=\"ts\">\nimport { Comment, computed, Fragment, nextTick, reactive, ref, Text, useSlots, type VNode } from 'vue'\n\nimport IconArrowUp from '~icons/lucide/arrow-up'\n\nimport GrIcon from '../GrIcon/GrIcon.vue'\nimport GrProgressBar from '../GrProgressBar/GrProgressBar.vue'\nimport type { GrProgressBarTone } from '../GrProgressBar/grStyle'\nimport type { FileValidator, FileValidatorSource } from '../../fileValidation'\nimport type { GrUploadProgressInfo } from './uploadViaXhr'\nimport type { GrUploadState } from './uploadState'\n\nimport { FileValidationError, runFileValidators } from '../../fileValidation'\nimport { GrUploadAbortError, uploadViaXhr } from './uploadViaXhr'\nimport { GR_UPLOAD_STATE_IDLE } from './uploadState'\n\nexport type GrFileUploadExtraDataValue = string | Blob\n\nexport type GrFileUploadExtraData = Record<string, GrFileUploadExtraDataValue | GrFileUploadExtraDataValue[]>\n\nexport type GrFileUploadRequestCtx = {\n signal: AbortSignal\n extraData?: GrFileUploadExtraData\n /**\n * Колбэк прогресса аплоада. Пользовательский `request` (например, axios)\n * должен вызывать его из `onUploadProgress`, чтобы `GrFileUpload` отображал\n * прогресс через слот `progress` / дефолтный `GrProgressBar`.\n */\n onProgress?: (info: GrUploadProgressInfo) => void\n}\n\nexport type GrFileUploadRequest = (files: File[], ctx: GrFileUploadRequestCtx) => Promise<any>\n\n/**\n * Пропы `GrFileUpload`.\n *\n * Либо `action` (URL для POST multipart/form-data), либо `request` — кастомный\n * загрузчик (например, через axios). Если переданы оба — приоритет у `request`.\n *\n * `placeholder` — надпись в дефолтном UI-варианте (без слота default).\n */\nexport interface GrFileUploadProps {\n action?: string\n request?: GrFileUploadRequest\n name?: string\n multiple?: boolean\n limit?: number\n onExceed?: (files: File[], limit: number) => void\n beforeUpload?: (file: File) => boolean | Promise<unknown>\n validators?: FileValidator[]\n disabled?: boolean\n headers?: Record<string, string>\n withCredentials?: boolean\n showFileList?: boolean\n uploadExtraData?: (files: File[]) => GrFileUploadExtraData | undefined\n /** i18n: надпись-подсказка в дефолтном UI. */\n placeholder?: string\n /** Показывать дефолтный прогресс-бар (если не используется слот `progress`). */\n showProgress?: boolean\n /** Цветовой тон полосы прогресса в фазе `uploading`. */\n progressTone?: GrProgressBarTone\n /** aria-label для прогресс-бара. */\n progressLabel?: string\n /** Через сколько мс после `success` скрыть прогресс-бар. `0` — не скрывать. */\n hideProgressOnSuccess?: number\n}\n\nconst props = withDefaults(\n defineProps<GrFileUploadProps>(),\n {\n action: undefined,\n request: undefined,\n name: 'file',\n multiple: false,\n limit: undefined,\n onExceed: undefined,\n beforeUpload: undefined,\n validators: undefined,\n disabled: false,\n headers: undefined,\n withCredentials: false,\n showFileList: false,\n uploadExtraData: undefined,\n placeholder: 'Drag files here or click to select',\n showProgress: true,\n progressTone: 'primary',\n progressLabel: 'Upload progress',\n hideProgressOnSuccess: 800,\n },\n)\n\nconst emit = defineEmits<{\n (e: 'success', payload: any): void\n (e: 'error', error: unknown): void\n (e: 'progress', percent: number, info?: GrUploadProgressInfo): void\n (e: 'change', files: File[]): void\n (e: 'stateChange', state: GrUploadState): void\n}>()\n\nconst slots = useSlots()\n\nfunction flattenSlotNodes(nodes: VNode[]): VNode[] {\n const out: VNode[] = []\n\n for (const node of nodes) {\n if (node.type === Fragment && Array.isArray(node.children)) {\n out.push(...flattenSlotNodes(node.children as VNode[]))\n continue\n }\n\n out.push(node)\n }\n\n return out\n}\n\nfunction isWhitespaceTextNode(node: VNode): boolean {\n if (node.type !== Text) return false\n return typeof node.children === 'string' && node.children.trim().length === 0\n}\n\nfunction slotIsTextOnly(nodes: VNode[]): boolean {\n const flat = flattenSlotNodes(nodes)\n const meaningful = flat.filter(node => node.type !== Comment && !isWhitespaceTextNode(node))\n\n if (meaningful.length === 0) return true\n\n for (const node of meaningful) {\n if (node.type !== Text) return false\n }\n\n return true\n}\n\nconst defaultSlotNodes = computed(() => {\n return slots.default ? flattenSlotNodes(slots.default({} as any)) : []\n})\n\nconst hasCustomUi = computed(() => {\n return !!slots.default && !slotIsTextOnly(defaultSlotNodes.value)\n})\n\nconst inputRef = ref<HTMLInputElement | null>(null)\nconst isOver = ref(false)\nlet overCounter = 0\n\nconst lastFiles = ref<File[]>([])\n\nconst state = reactive<GrUploadState>({ ...GR_UPLOAD_STATE_IDLE }) as GrUploadState\n\nlet activeController: AbortController | null = null\nlet hideSuccessTimer: ReturnType<typeof setTimeout> | null = null\n\nfunction clearHideSuccessTimer() {\n if (hideSuccessTimer !== null) {\n clearTimeout(hideSuccessTimer)\n hideSuccessTimer = null\n }\n}\n\nfunction assignState(next: GrUploadState) {\n // мутируем через Object.assign, чтобы сохранить reactive ссылку\n for (const key of Object.keys(state) as (keyof GrUploadState)[])\n delete (state as any)[key]\n Object.assign(state, next)\n emit('stateChange', state as GrUploadState)\n}\n\nfunction setStateIdle() {\n assignState({ ...GR_UPLOAD_STATE_IDLE })\n}\n\nfunction setStateUploading(info?: GrUploadProgressInfo) {\n assignState({\n phase: 'uploading',\n percent: info?.percent ?? 0,\n indeterminate: info?.indeterminate ?? true,\n loaded: info?.loaded ?? 0,\n total: info?.total ?? 0,\n })\n}\n\nfunction setStateSuccess(info: { loaded: number, total: number }) {\n assignState({\n phase: 'success',\n percent: 100,\n indeterminate: false,\n loaded: info.loaded,\n total: info.total,\n })\n}\n\nfunction setStateError(error: unknown) {\n const prev = state\n assignState({\n phase: 'error',\n percent: prev.phase === 'uploading' ? prev.percent : 0,\n indeterminate: false,\n loaded: prev.phase === 'uploading' ? prev.loaded : 0,\n total: prev.phase === 'uploading' ? prev.total : 0,\n error,\n })\n}\n\nfunction handleProgress(info: GrUploadProgressInfo) {\n if (state.phase !== 'uploading') return\n assignState({\n phase: 'uploading',\n percent: info.percent,\n indeterminate: info.indeterminate,\n loaded: info.loaded,\n total: info.total,\n })\n emit('progress', info.percent, info)\n}\n\nconst tabIndex = computed(() => (props.disabled ? -1 : 0))\n\nconst hiddenInputStyle = {\n position: 'fixed',\n top: '-9999px',\n left: '-9999px',\n width: '1px',\n height: '1px',\n opacity: '0',\n overflow: 'hidden',\n pointerEvents: 'none',\n} as const satisfies Record<string, string>\n\nfunction setOver(next: boolean) {\n if (isOver.value === next) return\n isOver.value = next\n}\n\nfunction openDialog() {\n if (props.disabled) return\n inputRef.value?.click()\n}\n\nfunction abort() {\n activeController?.abort()\n activeController = null\n}\n\nfunction normalizeLimit(limit: number | undefined): number | undefined {\n if (typeof limit !== 'number') return undefined\n if (!Number.isFinite(limit)) return undefined\n if (limit <= 0) return undefined\n return Math.floor(limit)\n}\n\n\nasync function uploadViaAction(files: File[], signal: AbortSignal, extraData: GrFileUploadExtraData | undefined) {\n if (!props.action) throw new Error('GrFileUpload: either `action` or `request` must be provided')\n\n return uploadViaXhr({\n url: props.action,\n files,\n name: props.name,\n headers: props.headers,\n withCredentials: props.withCredentials,\n extraData,\n signal,\n onProgress: handleProgress,\n })\n}\n\nasync function runBeforeUpload(files: File[]): Promise<'ok' | { aborted: true; reason: unknown }> {\n if (!props.beforeUpload) return 'ok'\n\n for (const file of files) {\n try {\n const result = props.beforeUpload(file)\n\n if (result === false) {\n return { aborted: true, reason: new Error('Upload aborted by beforeUpload') }\n }\n\n if (result && typeof (result as any).then === 'function')\n await result\n } catch (error) {\n return { aborted: true, reason: error }\n }\n }\n\n return 'ok'\n}\n\nasync function handleFiles(files: File[], source: FileValidatorSource = 'input') {\n if (props.disabled) return\n if (!files.length) return\n\n const normalizedLimit = normalizeLimit(props.limit)\n if (props.multiple && normalizedLimit && files.length > normalizedLimit) {\n props.onExceed?.(files, normalizedLimit)\n emit('error', new Error(`Too many files selected, limit=${normalizedLimit}`))\n return\n }\n\n const { files: valid, issues } = await runFileValidators(files, props.validators ?? [], {\n source,\n multiple: props.multiple,\n })\n\n if (issues.length > 0) {\n emit('error', new FileValidationError(issues, valid))\n return\n }\n\n if (!valid.length) return\n\n lastFiles.value = valid\n\n const before = await runBeforeUpload(valid)\n if (before !== 'ok') {\n emit('error', before.reason)\n return\n }\n\n let extraData: GrFileUploadExtraData | undefined\n try {\n extraData = props.uploadExtraData?.(valid)\n } catch (error) {\n emit('error', error)\n return\n }\n\n abort()\n clearHideSuccessTimer()\n\n const controller = new AbortController()\n activeController = controller\n\n setStateUploading()\n emit('progress', 0, { percent: 0, loaded: 0, total: 0, indeterminate: true })\n\n try {\n const payload = props.request\n ? await props.request(valid, { signal: controller.signal, extraData, onProgress: handleProgress })\n : await uploadViaAction(valid, controller.signal, extraData)\n\n const finalLoaded = state.phase === 'uploading' ? state.total || state.loaded : 0\n setStateSuccess({ loaded: finalLoaded, total: finalLoaded })\n emit('success', payload)\n emit('change', valid)\n emit('progress', 100, { percent: 100, loaded: finalLoaded, total: finalLoaded, indeterminate: false })\n\n if (props.hideProgressOnSuccess && props.hideProgressOnSuccess > 0) {\n hideSuccessTimer = setTimeout(() => {\n if (state.phase === 'success') setStateIdle()\n hideSuccessTimer = null\n }, props.hideProgressOnSuccess)\n }\n } catch (error) {\n if (error instanceof GrUploadAbortError) {\n setStateIdle()\n } else {\n setStateError(error)\n }\n emit('error', error)\n } finally {\n if (activeController === controller) activeController = null\n }\n}\n\nfunction onDragEnter(event: DragEvent) {\n if (hasCustomUi.value) return\n if (props.disabled) return\n event.preventDefault()\n overCounter += 1\n setOver(true)\n}\n\nfunction onDragOver(event: DragEvent) {\n if (hasCustomUi.value) return\n if (props.disabled) return\n event.preventDefault()\n if (event.dataTransfer) event.dataTransfer.dropEffect = 'copy'\n}\n\nfunction onDragLeave(event: DragEvent) {\n if (hasCustomUi.value) return\n if (props.disabled) return\n event.preventDefault()\n overCounter = Math.max(0, overCounter - 1)\n if (overCounter === 0) setOver(false)\n}\n\nfunction onDrop(event: DragEvent) {\n if (hasCustomUi.value) return\n if (props.disabled) return\n event.preventDefault()\n overCounter = 0\n setOver(false)\n\n const files = event.dataTransfer?.files ? Array.prototype.slice.call(event.dataTransfer.files) as File[] : []\n void handleFiles(files, 'drop')\n}\n\nasync function onInputChange(event: Event) {\n if (props.disabled) return\n\n const target = event.target as HTMLInputElement | null\n const files = target?.files ? Array.prototype.slice.call(target.files) as File[] : []\n\n if (target) target.value = ''\n\n await nextTick()\n await handleFiles(files, 'input')\n}\n\nfunction onKeydown(event: KeyboardEvent) {\n if (hasCustomUi.value) return\n if (props.disabled) return\n\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n openDialog()\n }\n}\n\nfunction onRootClick() {\n if (hasCustomUi.value) return\n openDialog()\n}\n\nfunction onRootKeydown(event: KeyboardEvent) {\n if (hasCustomUi.value) return\n onKeydown(event)\n}\n\nconst effectiveProgressTone = computed<GrProgressBarTone>(() => {\n if (state.phase === 'error') return 'danger'\n if (state.phase === 'success') return 'success'\n return props.progressTone\n})\n\nconst progressVisible = computed(() => state.phase !== 'idle')\nconst progressPercent = computed(() => (state.phase === 'uploading' && state.indeterminate ? 0 : state.percent))\nconst progressText = computed(() => {\n if (state.phase === 'uploading' && state.indeterminate) return ''\n return `${Math.round(progressPercent.value)}%`\n})\n\ndefineExpose({\n uploadFiles: handleFiles,\n abort,\n openDialog,\n state,\n})\n</script>\n\n<template>\n <div\n data-ds-file-upload\n :role=\"hasCustomUi ? undefined : 'button'\"\n :tabindex=\"hasCustomUi ? undefined : tabIndex\"\n :aria-disabled=\"hasCustomUi ? undefined : disabled ? 'true' : 'false'\"\n :class=\"\n hasCustomUi\n ? 'inline-block'\n : [\n 'relative w-full rounded-[var(--ds-radius-lg)] border border-dashed border-[var(--brd)] bg-[var(--card)] px-5 py-6 outline-none transition',\n disabled\n ? 'opacity-60 cursor-not-allowed'\n : 'cursor-pointer hover:bg-[var(--muted)] focus-visible:ring-2 focus-visible:ring-[var(--ring)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--bg)]',\n isOver && !disabled ? 'border-[var(--ring)] bg-[var(--muted)]' : '',\n ]\n \"\n @click=\"onRootClick\"\n @keydown=\"onRootKeydown\"\n @dragenter=\"onDragEnter\"\n @dragover=\"onDragOver\"\n @dragleave=\"onDragLeave\"\n @drop=\"onDrop\"\n >\n <input\n ref=\"inputRef\"\n data-ds-file-upload-input\n :style=\"hiddenInputStyle\"\n type=\"file\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n :name=\"name\"\n :multiple=\"multiple\"\n :disabled=\"disabled\"\n @change=\"onInputChange\"\n >\n\n <slot\n v-if=\"hasCustomUi\"\n :open-dialog=\"openDialog\"\n :abort=\"abort\"\n :disabled=\"disabled\"\n :files=\"lastFiles\"\n :is-over=\"isOver\"\n :state=\"state\"\n />\n\n <div v-else class=\"flex items-start gap-4\">\n <div\n data-ds-file-upload-icon\n class=\"h-12 w-12 shrink-0 rounded-[12px] bg-[var(--muted)] border border-[var(--brd)] flex items-center justify-center\"\n aria-hidden=\"true\"\n >\n <GrIcon class=\"h-6 w-6 text-[var(--muted-fg)]\">\n <IconArrowUp />\n </GrIcon>\n </div>\n\n <div class=\"min-w-0\">\n <div data-ds-file-upload-label class=\"text-[14px] font-700\">\n <slot name=\"label\">\n <slot>\n {{ placeholder }}\n </slot>\n </slot>\n </div>\n\n <div v-if=\"$slots.tip\" data-ds-file-upload-tip class=\"mt-1 text-[13px] text-[var(--muted-fg)]\">\n <slot name=\"tip\" />\n </div>\n <div v-else class=\"mt-1 text-[13px] text-[var(--muted-fg)]\" />\n\n <ul v-if=\"showFileList && lastFiles.length\" data-ds-file-upload-list class=\"mt-3 space-y-1\">\n <li\n v-for=\"file in lastFiles\"\n :key=\"file.name\"\n data-ds-file-upload-item\n class=\"text-[13px]\"\n >\n <span class=\"font-600\">{{ file.name }}</span>\n <span class=\"text-[var(--muted-fg)]\"> · {{ Math.ceil(file.size / 1024) }} KB</span>\n </li>\n </ul>\n\n <slot\n v-if=\"showProgress || $slots.progress\"\n name=\"progress\"\n :state=\"state\"\n :percent=\"state.percent\"\n :indeterminate=\"state.phase === 'uploading' && state.indeterminate\"\n :phase=\"state.phase\"\n :files=\"lastFiles\"\n :abort=\"abort\"\n >\n <div\n v-if=\"showProgress\"\n data-ds-file-upload-progress\n class=\"mt-3 transition-opacity duration-150\"\n :class=\"progressVisible ? 'opacity-100' : 'opacity-0 pointer-events-none'\"\n :aria-hidden=\"!progressVisible\"\n >\n <div class=\"flex items-center gap-2\">\n <GrProgressBar\n :value=\"progressPercent\"\n :tone=\"effectiveProgressTone\"\n :aria-label=\"progressLabel\"\n />\n <span\n data-ds-file-upload-progress-text\n class=\"text-[12px] text-[var(--muted-fg)] tabular-nums min-w-[3ch] text-right\"\n >{{ progressText }}</span>\n </div>\n </div>\n </slot>\n </div>\n </div>\n </div>\n</template>","<script setup lang=\"ts\">\nimport { Comment, computed, Fragment, nextTick, reactive, ref, Text, useSlots, type VNode } from 'vue'\n\nimport IconArrowUp from '~icons/lucide/arrow-up'\n\nimport GrIcon from '../GrIcon/GrIcon.vue'\nimport GrProgressBar from '../GrProgressBar/GrProgressBar.vue'\nimport type { GrProgressBarTone } from '../GrProgressBar/grStyle'\nimport type { FileValidator, FileValidatorSource } from '../../fileValidation'\nimport type { GrUploadProgressInfo } from './uploadViaXhr'\nimport type { GrUploadState } from './uploadState'\n\nimport { FileValidationError, runFileValidators } from '../../fileValidation'\nimport { GrUploadAbortError, uploadViaXhr } from './uploadViaXhr'\nimport { GR_UPLOAD_STATE_IDLE } from './uploadState'\n\nexport type GrFileUploadExtraDataValue = string | Blob\n\nexport type GrFileUploadExtraData = Record<string, GrFileUploadExtraDataValue | GrFileUploadExtraDataValue[]>\n\nexport type GrFileUploadRequestCtx = {\n signal: AbortSignal\n extraData?: GrFileUploadExtraData\n /**\n * Колбэк прогресса аплоада. Пользовательский `request` (например, axios)\n * должен вызывать его из `onUploadProgress`, чтобы `GrFileUpload` отображал\n * прогресс через слот `progress` / дефолтный `GrProgressBar`.\n */\n onProgress?: (info: GrUploadProgressInfo) => void\n}\n\nexport type GrFileUploadRequest = (files: File[], ctx: GrFileUploadRequestCtx) => Promise<any>\n\n/**\n * Пропы `GrFileUpload`.\n *\n * Либо `action` (URL для POST multipart/form-data), либо `request` — кастомный\n * загрузчик (например, через axios). Если переданы оба — приоритет у `request`.\n *\n * `placeholder` — надпись в дефолтном UI-варианте (без слота default).\n */\nexport interface GrFileUploadProps {\n action?: string\n request?: GrFileUploadRequest\n name?: string\n multiple?: boolean\n limit?: number\n onExceed?: (files: File[], limit: number) => void\n beforeUpload?: (file: File) => boolean | Promise<unknown>\n validators?: FileValidator[]\n disabled?: boolean\n headers?: Record<string, string>\n withCredentials?: boolean\n showFileList?: boolean\n uploadExtraData?: (files: File[]) => GrFileUploadExtraData | undefined\n /** i18n: надпись-подсказка в дефолтном UI. */\n placeholder?: string\n /** Показывать дефолтный прогресс-бар (если не используется слот `progress`). */\n showProgress?: boolean\n /** Цветовой тон полосы прогресса в фазе `uploading`. */\n progressTone?: GrProgressBarTone\n /** aria-label для прогресс-бара. */\n progressLabel?: string\n /** Через сколько мс после `success` скрыть прогресс-бар. `0` — не скрывать. */\n hideProgressOnSuccess?: number\n}\n\nconst props = withDefaults(\n defineProps<GrFileUploadProps>(),\n {\n action: undefined,\n request: undefined,\n name: 'file',\n multiple: false,\n limit: undefined,\n onExceed: undefined,\n beforeUpload: undefined,\n validators: undefined,\n disabled: false,\n headers: undefined,\n withCredentials: false,\n showFileList: false,\n uploadExtraData: undefined,\n placeholder: 'Drag files here or click to select',\n showProgress: true,\n progressTone: 'primary',\n progressLabel: 'Upload progress',\n hideProgressOnSuccess: 800,\n },\n)\n\nconst emit = defineEmits<{\n (e: 'success', payload: any): void\n (e: 'error', error: unknown): void\n (e: 'progress', percent: number, info?: GrUploadProgressInfo): void\n (e: 'change', files: File[]): void\n (e: 'stateChange', state: GrUploadState): void\n}>()\n\nconst slots = useSlots()\n\nfunction flattenSlotNodes(nodes: VNode[]): VNode[] {\n const out: VNode[] = []\n\n for (const node of nodes) {\n if (node.type === Fragment && Array.isArray(node.children)) {\n out.push(...flattenSlotNodes(node.children as VNode[]))\n continue\n }\n\n out.push(node)\n }\n\n return out\n}\n\nfunction isWhitespaceTextNode(node: VNode): boolean {\n if (node.type !== Text) return false\n return typeof node.children === 'string' && node.children.trim().length === 0\n}\n\nfunction slotIsTextOnly(nodes: VNode[]): boolean {\n const flat = flattenSlotNodes(nodes)\n const meaningful = flat.filter(node => node.type !== Comment && !isWhitespaceTextNode(node))\n\n if (meaningful.length === 0) return true\n\n for (const node of meaningful) {\n if (node.type !== Text) return false\n }\n\n return true\n}\n\nconst defaultSlotNodes = computed(() => {\n return slots.default ? flattenSlotNodes(slots.default({} as any)) : []\n})\n\nconst hasCustomUi = computed(() => {\n return !!slots.default && !slotIsTextOnly(defaultSlotNodes.value)\n})\n\nconst inputRef = ref<HTMLInputElement | null>(null)\nconst isOver = ref(false)\nlet overCounter = 0\n\nconst lastFiles = ref<File[]>([])\n\nconst state = reactive<GrUploadState>({ ...GR_UPLOAD_STATE_IDLE }) as GrUploadState\n\nlet activeController: AbortController | null = null\nlet hideSuccessTimer: ReturnType<typeof setTimeout> | null = null\n\nfunction clearHideSuccessTimer() {\n if (hideSuccessTimer !== null) {\n clearTimeout(hideSuccessTimer)\n hideSuccessTimer = null\n }\n}\n\nfunction assignState(next: GrUploadState) {\n // мутируем через Object.assign, чтобы сохранить reactive ссылку\n for (const key of Object.keys(state) as (keyof GrUploadState)[])\n delete (state as any)[key]\n Object.assign(state, next)\n emit('stateChange', state as GrUploadState)\n}\n\nfunction setStateIdle() {\n assignState({ ...GR_UPLOAD_STATE_IDLE })\n}\n\nfunction setStateUploading(info?: GrUploadProgressInfo) {\n assignState({\n phase: 'uploading',\n percent: info?.percent ?? 0,\n indeterminate: info?.indeterminate ?? true,\n loaded: info?.loaded ?? 0,\n total: info?.total ?? 0,\n })\n}\n\nfunction setStateSuccess(info: { loaded: number, total: number }) {\n assignState({\n phase: 'success',\n percent: 100,\n indeterminate: false,\n loaded: info.loaded,\n total: info.total,\n })\n}\n\nfunction setStateError(error: unknown) {\n const prev = state\n assignState({\n phase: 'error',\n percent: prev.phase === 'uploading' ? prev.percent : 0,\n indeterminate: false,\n loaded: prev.phase === 'uploading' ? prev.loaded : 0,\n total: prev.phase === 'uploading' ? prev.total : 0,\n error,\n })\n}\n\nfunction handleProgress(info: GrUploadProgressInfo) {\n if (state.phase !== 'uploading') return\n assignState({\n phase: 'uploading',\n percent: info.percent,\n indeterminate: info.indeterminate,\n loaded: info.loaded,\n total: info.total,\n })\n emit('progress', info.percent, info)\n}\n\nconst tabIndex = computed(() => (props.disabled ? -1 : 0))\n\nconst hiddenInputStyle = {\n position: 'fixed',\n top: '-9999px',\n left: '-9999px',\n width: '1px',\n height: '1px',\n opacity: '0',\n overflow: 'hidden',\n pointerEvents: 'none',\n} as const satisfies Record<string, string>\n\nfunction setOver(next: boolean) {\n if (isOver.value === next) return\n isOver.value = next\n}\n\nfunction openDialog() {\n if (props.disabled) return\n inputRef.value?.click()\n}\n\nfunction abort() {\n activeController?.abort()\n activeController = null\n}\n\nfunction normalizeLimit(limit: number | undefined): number | undefined {\n if (typeof limit !== 'number') return undefined\n if (!Number.isFinite(limit)) return undefined\n if (limit <= 0) return undefined\n return Math.floor(limit)\n}\n\n\nasync function uploadViaAction(files: File[], signal: AbortSignal, extraData: GrFileUploadExtraData | undefined) {\n if (!props.action) throw new Error('GrFileUpload: either `action` or `request` must be provided')\n\n return uploadViaXhr({\n url: props.action,\n files,\n name: props.name,\n headers: props.headers,\n withCredentials: props.withCredentials,\n extraData,\n signal,\n onProgress: handleProgress,\n })\n}\n\nasync function runBeforeUpload(files: File[]): Promise<'ok' | { aborted: true; reason: unknown }> {\n if (!props.beforeUpload) return 'ok'\n\n for (const file of files) {\n try {\n const result = props.beforeUpload(file)\n\n if (result === false) {\n return { aborted: true, reason: new Error('Upload aborted by beforeUpload') }\n }\n\n if (result && typeof (result as any).then === 'function')\n await result\n } catch (error) {\n return { aborted: true, reason: error }\n }\n }\n\n return 'ok'\n}\n\nasync function handleFiles(files: File[], source: FileValidatorSource = 'input') {\n if (props.disabled) return\n if (!files.length) return\n\n const normalizedLimit = normalizeLimit(props.limit)\n if (props.multiple && normalizedLimit && files.length > normalizedLimit) {\n props.onExceed?.(files, normalizedLimit)\n emit('error', new Error(`Too many files selected, limit=${normalizedLimit}`))\n return\n }\n\n const { files: valid, issues } = await runFileValidators(files, props.validators ?? [], {\n source,\n multiple: props.multiple,\n })\n\n if (issues.length > 0) {\n emit('error', new FileValidationError(issues, valid))\n return\n }\n\n if (!valid.length) return\n\n lastFiles.value = valid\n\n const before = await runBeforeUpload(valid)\n if (before !== 'ok') {\n emit('error', before.reason)\n return\n }\n\n let extraData: GrFileUploadExtraData | undefined\n try {\n extraData = props.uploadExtraData?.(valid)\n } catch (error) {\n emit('error', error)\n return\n }\n\n abort()\n clearHideSuccessTimer()\n\n const controller = new AbortController()\n activeController = controller\n\n setStateUploading()\n emit('progress', 0, { percent: 0, loaded: 0, total: 0, indeterminate: true })\n\n try {\n const payload = props.request\n ? await props.request(valid, { signal: controller.signal, extraData, onProgress: handleProgress })\n : await uploadViaAction(valid, controller.signal, extraData)\n\n const finalLoaded = state.phase === 'uploading' ? state.total || state.loaded : 0\n setStateSuccess({ loaded: finalLoaded, total: finalLoaded })\n emit('success', payload)\n emit('change', valid)\n emit('progress', 100, { percent: 100, loaded: finalLoaded, total: finalLoaded, indeterminate: false })\n\n if (props.hideProgressOnSuccess && props.hideProgressOnSuccess > 0) {\n hideSuccessTimer = setTimeout(() => {\n if (state.phase === 'success') setStateIdle()\n hideSuccessTimer = null\n }, props.hideProgressOnSuccess)\n }\n } catch (error) {\n if (error instanceof GrUploadAbortError) {\n setStateIdle()\n } else {\n setStateError(error)\n }\n emit('error', error)\n } finally {\n if (activeController === controller) activeController = null\n }\n}\n\nfunction onDragEnter(event: DragEvent) {\n if (hasCustomUi.value) return\n if (props.disabled) return\n event.preventDefault()\n overCounter += 1\n setOver(true)\n}\n\nfunction onDragOver(event: DragEvent) {\n if (hasCustomUi.value) return\n if (props.disabled) return\n event.preventDefault()\n if (event.dataTransfer) event.dataTransfer.dropEffect = 'copy'\n}\n\nfunction onDragLeave(event: DragEvent) {\n if (hasCustomUi.value) return\n if (props.disabled) return\n event.preventDefault()\n overCounter = Math.max(0, overCounter - 1)\n if (overCounter === 0) setOver(false)\n}\n\nfunction onDrop(event: DragEvent) {\n if (hasCustomUi.value) return\n if (props.disabled) return\n event.preventDefault()\n overCounter = 0\n setOver(false)\n\n const files = event.dataTransfer?.files ? Array.prototype.slice.call(event.dataTransfer.files) as File[] : []\n void handleFiles(files, 'drop')\n}\n\nasync function onInputChange(event: Event) {\n if (props.disabled) return\n\n const target = event.target as HTMLInputElement | null\n const files = target?.files ? Array.prototype.slice.call(target.files) as File[] : []\n\n if (target) target.value = ''\n\n await nextTick()\n await handleFiles(files, 'input')\n}\n\nfunction onKeydown(event: KeyboardEvent) {\n if (hasCustomUi.value) return\n if (props.disabled) return\n\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n openDialog()\n }\n}\n\nfunction onRootClick() {\n if (hasCustomUi.value) return\n openDialog()\n}\n\nfunction onRootKeydown(event: KeyboardEvent) {\n if (hasCustomUi.value) return\n onKeydown(event)\n}\n\nconst effectiveProgressTone = computed<GrProgressBarTone>(() => {\n if (state.phase === 'error') return 'danger'\n if (state.phase === 'success') return 'success'\n return props.progressTone\n})\n\nconst progressVisible = computed(() => state.phase !== 'idle')\nconst progressPercent = computed(() => (state.phase === 'uploading' && state.indeterminate ? 0 : state.percent))\nconst progressText = computed(() => {\n if (state.phase === 'uploading' && state.indeterminate) return ''\n return `${Math.round(progressPercent.value)}%`\n})\n\ndefineExpose({\n uploadFiles: handleFiles,\n abort,\n openDialog,\n state,\n})\n</script>\n\n<template>\n <div\n data-ds-file-upload\n :role=\"hasCustomUi ? undefined : 'button'\"\n :tabindex=\"hasCustomUi ? undefined : tabIndex\"\n :aria-disabled=\"hasCustomUi ? undefined : disabled ? 'true' : 'false'\"\n :class=\"\n hasCustomUi\n ? 'inline-block'\n : [\n 'relative w-full rounded-[var(--ds-radius-lg)] border border-dashed border-[var(--brd)] bg-[var(--card)] px-5 py-6 outline-none transition',\n disabled\n ? 'opacity-60 cursor-not-allowed'\n : 'cursor-pointer hover:bg-[var(--muted)] focus-visible:ring-2 focus-visible:ring-[var(--ring)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--bg)]',\n isOver && !disabled ? 'border-[var(--ring)] bg-[var(--muted)]' : '',\n ]\n \"\n @click=\"onRootClick\"\n @keydown=\"onRootKeydown\"\n @dragenter=\"onDragEnter\"\n @dragover=\"onDragOver\"\n @dragleave=\"onDragLeave\"\n @drop=\"onDrop\"\n >\n <input\n ref=\"inputRef\"\n data-ds-file-upload-input\n :style=\"hiddenInputStyle\"\n type=\"file\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n :name=\"name\"\n :multiple=\"multiple\"\n :disabled=\"disabled\"\n @change=\"onInputChange\"\n >\n\n <slot\n v-if=\"hasCustomUi\"\n :open-dialog=\"openDialog\"\n :abort=\"abort\"\n :disabled=\"disabled\"\n :files=\"lastFiles\"\n :is-over=\"isOver\"\n :state=\"state\"\n />\n\n <div v-else class=\"flex items-start gap-4\">\n <div\n data-ds-file-upload-icon\n class=\"h-12 w-12 shrink-0 rounded-[12px] bg-[var(--muted)] border border-[var(--brd)] flex items-center justify-center\"\n aria-hidden=\"true\"\n >\n <GrIcon class=\"h-6 w-6 text-[var(--muted-fg)]\">\n <IconArrowUp />\n </GrIcon>\n </div>\n\n <div class=\"min-w-0\">\n <div data-ds-file-upload-label class=\"text-[14px] font-700\">\n <slot name=\"label\">\n <slot>\n {{ placeholder }}\n </slot>\n </slot>\n </div>\n\n <div v-if=\"$slots.tip\" data-ds-file-upload-tip class=\"mt-1 text-[13px] text-[var(--muted-fg)]\">\n <slot name=\"tip\" />\n </div>\n <div v-else class=\"mt-1 text-[13px] text-[var(--muted-fg)]\" />\n\n <ul v-if=\"showFileList && lastFiles.length\" data-ds-file-upload-list class=\"mt-3 space-y-1\">\n <li\n v-for=\"file in lastFiles\"\n :key=\"file.name\"\n data-ds-file-upload-item\n class=\"text-[13px]\"\n >\n <span class=\"font-600\">{{ file.name }}</span>\n <span class=\"text-[var(--muted-fg)]\"> · {{ Math.ceil(file.size / 1024) }} KB</span>\n </li>\n </ul>\n\n <slot\n v-if=\"showProgress || $slots.progress\"\n name=\"progress\"\n :state=\"state\"\n :percent=\"state.percent\"\n :indeterminate=\"state.phase === 'uploading' && state.indeterminate\"\n :phase=\"state.phase\"\n :files=\"lastFiles\"\n :abort=\"abort\"\n >\n <div\n v-if=\"showProgress\"\n data-ds-file-upload-progress\n class=\"mt-3 transition-opacity duration-150\"\n :class=\"progressVisible ? 'opacity-100' : 'opacity-0 pointer-events-none'\"\n :aria-hidden=\"!progressVisible\"\n >\n <div class=\"flex items-center gap-2\">\n <GrProgressBar\n :value=\"progressPercent\"\n :tone=\"effectiveProgressTone\"\n :aria-label=\"progressLabel\"\n />\n <span\n data-ds-file-upload-progress-text\n class=\"text-[12px] text-[var(--muted-fg)] tabular-nums min-w-[3ch] text-right\"\n >{{ progressText }}</span>\n </div>\n </div>\n </slot>\n </div>\n </div>\n </div>\n</template>"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { a as allowedExtensionsValidator, c as normalizeFiles, i as allowedMimeTypesValidator, l as runFileValidators, n as maxSizeMbValidator, o as acceptValidator, r as maxFileSizeBytesValidator, s as matchAccept, t as maxTotalSizeBytesValidator, u as FileValidationError } from "../../chunks/fileValidation-CwJA8Vmn.js";
2
- import { i as GrUploadHttpError, n as GR_UPLOAD_STATE_IDLE, r as GrUploadAbortError, t as GrFileUpload_default } from "./chunks/GrFileUpload-DYRERhI2.js";
3
- import { t as grFileUploadConfig } from "../../chunks/config-CVnluhKO.js";
1
+ import { a as allowedExtensionsValidator, c as normalizeFiles, i as allowedMimeTypesValidator, l as runFileValidators, n as maxSizeMbValidator, o as acceptValidator, r as maxFileSizeBytesValidator, s as matchAccept, t as maxTotalSizeBytesValidator, u as FileValidationError } from "../../chunks/fileValidation-c_iBg74s.js";
2
+ import { i as GrUploadHttpError, n as GR_UPLOAD_STATE_IDLE, r as GrUploadAbortError, t as GrFileUpload_default } from "./chunks/GrFileUpload-BEKNB1RM.js";
3
+ import { t as grFileUploadConfig } from "../../chunks/config-DF_RlCIO.js";
4
4
  export { FileValidationError, GR_UPLOAD_STATE_IDLE, GrFileUpload_default as GrFileUpload, GrFileUpload_default as default, GrUploadAbortError, GrUploadHttpError, acceptValidator, allowedExtensionsValidator, allowedMimeTypesValidator, grFileUploadConfig, matchAccept, maxFileSizeBytesValidator, maxSizeMbValidator, maxTotalSizeBytesValidator, normalizeFiles, runFileValidators };
@@ -1 +1 @@
1
- {"version":3,"file":"GrFormField-C1oWTT_0.js","names":[],"sources":["../../../../src/components/GrFormField/GrFormField.vue","../../../../src/components/GrFormField/GrFormField.vue"],"sourcesContent":["<script setup lang=\"ts\">\ntype LabelClass = string | string[] | Record<string, boolean>\n\nconst props = withDefaults(\n defineProps<{\n label?: string\n forId?: string\n error?: string\n labelClass?: LabelClass\n }>(),\n {\n label: undefined,\n forId: undefined,\n error: undefined,\n labelClass: undefined,\n },\n)\n</script>\n\n<template>\n <div class=\"flex flex-col gap-2\">\n <label\n v-if=\"props.label\"\n :for=\"props.forId\"\n class=\"text-sm text-[var(--muted-fg)]\"\n :class=\"props.labelClass\"\n >\n {{ props.label }}\n </label>\n <slot />\n <div v-if=\"props.error\" class=\"text-sm text-[var(--ds-danger)]\">\n {{ props.error }}\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\ntype LabelClass = string | string[] | Record<string, boolean>\n\nconst props = withDefaults(\n defineProps<{\n label?: string\n forId?: string\n error?: string\n labelClass?: LabelClass\n }>(),\n {\n label: undefined,\n forId: undefined,\n error: undefined,\n labelClass: undefined,\n },\n)\n</script>\n\n<template>\n <div class=\"flex flex-col gap-2\">\n <label\n v-if=\"props.label\"\n :for=\"props.forId\"\n class=\"text-sm text-[var(--muted-fg)]\"\n :class=\"props.labelClass\"\n >\n {{ props.label }}\n </label>\n <slot />\n <div v-if=\"props.error\" class=\"text-sm text-[var(--ds-danger)]\">\n {{ props.error }}\n </div>\n </div>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;EAGA,MAAM,QAAQ;;uBAiBZ,mBAaM,OAbN,YAaM;IAXI,MAAM,SAAA,WAAA,EADd,mBAOQ,SAAA;;KALL,KAAK,MAAM;KACZ,OAAK,eAAA,CAAC,kCACE,MAAM,WAAU,CAAA;uBAErB,MAAM,MAAK,EAAA,IAAA,WAAA,IAAA,mBAAA,IAAA,KAAA;IAEhB,WAAQ,KAAA,QAAA,UAAA;IACG,MAAM,SAAA,WAAA,EAAjB,mBAEM,OAFN,YAEM,gBADD,MAAM,MAAK,EAAA,EAAA,IAAA,mBAAA,IAAA,KAAA"}
1
+ {"version":3,"file":"GrFormField-U7c_aDPJ.js","names":[],"sources":["../../../../src/components/GrFormField/GrFormField.vue","../../../../src/components/GrFormField/GrFormField.vue"],"sourcesContent":["<script setup lang=\"ts\">\ntype LabelClass = string | string[] | Record<string, boolean>\n\nconst props = withDefaults(\n defineProps<{\n label?: string\n forId?: string\n error?: string\n labelClass?: LabelClass\n }>(),\n {\n label: undefined,\n forId: undefined,\n error: undefined,\n labelClass: undefined,\n },\n)\n</script>\n\n<template>\n <div class=\"flex flex-col gap-2\">\n <label\n v-if=\"props.label\"\n :for=\"props.forId\"\n class=\"text-sm text-[var(--muted-fg)]\"\n :class=\"props.labelClass\"\n >\n {{ props.label }}\n </label>\n <slot />\n <div v-if=\"props.error\" class=\"text-sm text-[var(--ds-danger)]\">\n {{ props.error }}\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\ntype LabelClass = string | string[] | Record<string, boolean>\n\nconst props = withDefaults(\n defineProps<{\n label?: string\n forId?: string\n error?: string\n labelClass?: LabelClass\n }>(),\n {\n label: undefined,\n forId: undefined,\n error: undefined,\n labelClass: undefined,\n },\n)\n</script>\n\n<template>\n <div class=\"flex flex-col gap-2\">\n <label\n v-if=\"props.label\"\n :for=\"props.forId\"\n class=\"text-sm text-[var(--muted-fg)]\"\n :class=\"props.labelClass\"\n >\n {{ props.label }}\n </label>\n <slot />\n <div v-if=\"props.error\" class=\"text-sm text-[var(--ds-danger)]\">\n {{ props.error }}\n </div>\n </div>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;EAGA,MAAM,QAAQ;;uBAiBZ,mBAaM,OAbN,YAaM;IAXI,MAAM,SAAA,WAAA,EADd,mBAOQ,SAAA;;KALL,KAAK,MAAM;KACZ,OAAK,eAAA,CAAC,kCACE,MAAM,WAAU,CAAA;uBAErB,MAAM,MAAK,EAAA,IAAA,WAAA,IAAA,mBAAA,IAAA,KAAA;IAEhB,WAAQ,KAAA,QAAA,UAAA;IACG,MAAM,SAAA,WAAA,EAAjB,mBAEM,OAFN,YAEM,gBADD,MAAM,MAAK,EAAA,EAAA,IAAA,mBAAA,IAAA,KAAA"}
@@ -1,3 +1,3 @@
1
- import { t as GrFormField_default } from "./chunks/GrFormField-C1oWTT_0.js";
2
- import { t as grFormFieldConfig } from "../../chunks/config-3SftO0uG.js";
1
+ import { t as GrFormField_default } from "./chunks/GrFormField-U7c_aDPJ.js";
2
+ import { t as grFormFieldConfig } from "../../chunks/config-Dcb7xK-C.js";
3
3
  export { GrFormField_default as GrFormField, GrFormField_default as default, grFormFieldConfig };
@@ -1,8 +1,8 @@
1
1
  import { t as x_default } from "../../../chunks/x-Pa6C2hwi.js";
2
- import { t as GrButton_default } from "../../GrButton/chunks/GrButton-BmGdp8-4.js";
3
- import { t as _plugin_vue_export_helper_default } from "../../../chunks/_plugin-vue_export-helper-BG_EqiDU.js";
2
+ import { t as _plugin_vue_export_helper_default } from "../../../chunks/_plugin-vue_export-helper-DQR7Sy7Q.js";
3
+ import { t as GrButton_default } from "../../GrButton/chunks/GrButton-CPDKonoB.js";
4
4
  import { t as GrIcon_default } from "../../GrIcon/chunks/GrIcon-BV7RBU_0.js";
5
- import { l as runFileValidators, o as acceptValidator, u as FileValidationError } from "../../../chunks/fileValidation-CwJA8Vmn.js";
5
+ import { l as runFileValidators, o as acceptValidator, u as FileValidationError } from "../../../chunks/fileValidation-c_iBg74s.js";
6
6
  import { vDropzone } from "../../../directives/dropzone.js";
7
7
  import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, markRaw, nextTick, normalizeClass, openBlock, ref, renderList, renderSlot, toDisplayString, unref, watch, withCtx, withDirectives, withModifiers } from "vue";
8
8
  import '../../../GrFormFile.css';//#region ~icons/lucide/upload
@@ -1 +1 @@
1
- {"version":3,"file":"GrFormFile-deDPicM2.js","names":[],"sources":["../../../../src/components/GrFormFile/GrFormFile.vue","../../../../src/components/GrFormFile/GrFormFile.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, nextTick, ref, watch } from 'vue'\n\nimport IconUpload from '~icons/lucide/upload'\nimport IconX from '~icons/lucide/x'\n\nimport GrButton from '../GrButton/GrButton.vue'\nimport GrIcon from '../GrIcon/GrIcon.vue'\nimport { vDropzone } from '../../directives'\nimport { acceptValidator, FileValidationError, runFileValidators } from '../../fileValidation'\nimport type { FileValidationIssue, FileValidator } from '../../fileValidation'\n\ndefineOptions({\n name: 'GrFormFile',\n})\n\nexport type GrFormFileError = FileValidationIssue\n\n/**\n * Пропы `GrFormFile` — форма-поле для выбора файлов с встроенной валидацией,\n * drag&drop (через `v-dropzone`) и списком выбранных файлов.\n *\n * Все `*Text`/`placeholder` — i18n-friendly, принимают готовые строки локали.\n */\nexport interface GrFormFileProps {\n modelValue: File | File[] | null\n multiple?: boolean\n disabled?: boolean\n /** W3C `accept` для `<input type=\"file\">` + sugar к `acceptValidator(...)`. */\n accept?: string\n validators?: FileValidator[]\n uploadText?: string\n changeText?: string\n removeText?: string\n clearAllText?: string\n placeholder?: string\n /** Дополнительная (кастомная) валидация на стороне потребителя. */\n validate?: (files: File[]) => GrFormFileError[] | Promise<GrFormFileError[]>\n}\n\nconst props = withDefaults(\n defineProps<GrFormFileProps>(),\n {\n multiple: false,\n disabled: false,\n accept: undefined,\n uploadText: 'Upload file',\n changeText: 'Change file',\n removeText: 'Remove',\n clearAllText: 'Clear all',\n placeholder: 'No files selected',\n validators: undefined,\n validate: undefined,\n },\n)\n\nconst emit = defineEmits<{\n (e: 'update:modelValue', value: File | File[] | null): void\n (e: 'change', value: File | File[] | null): void\n (e: 'clear'): void\n (e: 'validation', errors: GrFormFileError[]): void\n (e: 'update:errors', errors: GrFormFileError[]): void\n}>()\n\nconst inputRef = ref<HTMLInputElement | null>(null)\nconst localErrors = ref<GrFormFileError[]>([])\n\nconst files = computed<File[]>(() => {\n if (props.multiple) {\n return Array.isArray(props.modelValue) ? props.modelValue : []\n }\n\n return props.modelValue instanceof File ? [props.modelValue] : []\n})\n\nconst hasFiles = computed(() => files.value.length > 0)\n\nfunction clearInputValue() {\n if (!inputRef.value) return\n inputRef.value.value = ''\n}\n\nfunction setErrors(next: GrFormFileError[]) {\n localErrors.value = next\n emit('validation', next)\n emit('update:errors', next)\n}\n\nfunction clearErrors() {\n if (localErrors.value.length === 0) return\n setErrors([])\n}\n\nfunction openDialog() {\n if (props.disabled) return\n inputRef.value?.click()\n}\n\nfunction emitModel(nextFiles: File[]) {\n const value: File | File[] | null = props.multiple\n ? nextFiles\n : (nextFiles[0] ?? null)\n\n emit('update:modelValue', value)\n emit('change', value)\n}\n\nasync function applyFiles(nextFiles: File[]) {\n const isPromiseLike = (value: unknown): value is PromiseLike<any> => {\n return !!value && typeof (value as any).then === 'function'\n }\n\n const customValidator: FileValidator | undefined = props.validate\n ? async ({ files }) => (await props.validate?.(files)) ?? []\n : undefined\n\n const validators: FileValidator[] = [\n acceptValidator(props.accept),\n ...(props.validators ?? []),\n ...(customValidator ? [customValidator] : []),\n ]\n\n const res = runFileValidators(nextFiles, validators, {\n source: 'input',\n multiple: props.multiple,\n })\n\n const { files: picked, issues } = isPromiseLike(res) ? await res : res\n\n if (issues.length > 0) {\n setErrors(issues)\n clearInputValue()\n await nextTick()\n return\n }\n\n clearErrors()\n emitModel(picked)\n clearInputValue()\n await nextTick()\n}\n\nasync function onInputChange(event: Event) {\n if (props.disabled) return\n\n const target = event.target as HTMLInputElement | null\n const nextFiles = target?.files ? Array.prototype.slice.call(target.files) as File[] : []\n\n // Reset value so selecting the same file twice triggers change.\n if (target) target.value = ''\n await nextTick()\n\n await applyFiles(nextFiles)\n}\n\nfunction clearAll() {\n if (props.disabled) return\n clearErrors()\n clearInputValue()\n emit('clear')\n\n emit('update:modelValue', props.multiple ? [] : null)\n emit('change', props.multiple ? [] : null)\n}\n\nfunction removeAt(index: number) {\n if (props.disabled) return\n if (!props.multiple) {\n clearAll()\n return\n }\n\n const next = files.value.slice()\n next.splice(index, 1)\n\n emit('update:modelValue', next)\n emit('change', next)\n}\n\nfunction issueMessage(issue: GrFormFileError): string {\n if (issue.fileName) return `${issue.fileName}: ${issue.message}`\n return issue.message\n}\n\nconst dropzone = computed(() => {\n const customValidator: FileValidator | undefined = props.validate\n ? async ({ files }) => (await props.validate?.(files)) ?? []\n : undefined\n\n const validators: FileValidator[] = [\n acceptValidator(props.accept),\n ...(props.validators ?? []),\n ...(customValidator ? [customValidator] : []),\n ]\n\n return {\n enabled: !props.disabled,\n multiple: props.multiple,\n validators,\n onFiles: async (dropped: File[]) => {\n // `v-dropzone` уже выполнил валидаторы и нормализацию по `multiple`.\n clearErrors()\n emitModel(dropped)\n clearInputValue()\n await nextTick()\n },\n onError: (error: unknown) => {\n if (error instanceof FileValidationError) {\n setErrors(error.issues)\n }\n else {\n setErrors([\n {\n code: 'accept',\n message: 'Failed to validate dropped files',\n },\n ])\n }\n },\n }\n})\n\nwatch(\n () => props.modelValue,\n (value) => {\n if (!inputRef.value) return\n\n if (!value || (Array.isArray(value) && value.length === 0)) {\n // Позволяет выбрать тот же файл снова после внешнего сброса.\n inputRef.value.value = ''\n }\n },\n)\n</script>\n\n<template>\n <div\n v-dropzone=\"dropzone\"\n data-ds-form-file\n class=\"rounded-[var(--ds-radius-md)]\"\n :class=\"disabled ? 'opacity-60 cursor-not-allowed' : ''\"\n >\n <input\n ref=\"inputRef\"\n data-ds-form-file-input\n type=\"file\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n class=\"absolute opacity-0 w-px h-px pointer-events-none\"\n :multiple=\"multiple\"\n :accept=\"accept\"\n :disabled=\"disabled\"\n @change=\"onInputChange\"\n >\n\n <div class=\"flex flex-col gap-2\">\n <div class=\"flex flex-wrap items-center gap-3\">\n <GrButton\n variant=\"secondary\"\n size=\"sm\"\n data-ds-form-file-upload-btn\n :disabled=\"disabled\"\n @click.prevent=\"openDialog\"\n >\n <GrIcon size=\"sm\" aria-hidden=\"true\">\n <IconUpload />\n </GrIcon>\n <span class=\"ml-2\">{{ hasFiles ? changeText : uploadText }}</span>\n </GrButton>\n\n <GrButton\n v-if=\"hasFiles && !multiple\"\n variant=\"secondary\"\n size=\"sm\"\n data-ds-form-file-clear-btn\n :disabled=\"disabled\"\n @click.prevent=\"clearAll\"\n >\n <GrIcon size=\"sm\" aria-hidden=\"true\">\n <IconX />\n </GrIcon>\n <span class=\"ml-2\">{{ removeText }}</span>\n </GrButton>\n\n <GrButton\n v-if=\"multiple && hasFiles\"\n variant=\"secondary\"\n size=\"sm\"\n data-ds-form-file-clear-all-btn\n :disabled=\"disabled\"\n @click.prevent=\"clearAll\"\n >\n <GrIcon size=\"sm\" aria-hidden=\"true\">\n <IconX />\n </GrIcon>\n <span class=\"ml-2\">{{ clearAllText }}</span>\n </GrButton>\n\n <span\n v-if=\"!multiple && hasFiles\"\n class=\"text-sm text-[var(--muted-fg)] truncate max-w-[240px]\"\n data-ds-form-file-single-name\n :title=\"files[0]?.name\"\n >\n {{ files[0]?.name }}\n </span>\n\n <span\n v-if=\"!hasFiles\"\n class=\"text-sm text-[var(--muted-fg)]\"\n data-ds-form-file-placeholder\n >\n {{ placeholder }}\n </span>\n </div>\n\n <div v-if=\"multiple && hasFiles\" class=\"flex flex-col gap-2\">\n <div\n v-for=\"(file, index) in files\"\n :key=\"`${file.name}-${file.size}-${index}`\"\n class=\"flex items-center gap-2\"\n data-ds-form-file-item\n >\n <span\n class=\"text-sm text-[var(--muted-fg)] truncate max-w-[240px]\"\n :title=\"file.name\"\n data-ds-form-file-item-name\n >\n {{ file.name }}\n </span>\n\n <button\n type=\"button\"\n class=\"text-xs text-[var(--muted-fg)] hover:text-[var(--fg)]\"\n data-ds-form-file-item-remove\n :disabled=\"disabled\"\n @click.prevent=\"removeAt(index)\"\n >\n {{ removeText }}\n </button>\n </div>\n </div>\n\n <slot name=\"errors\" :errors=\"localErrors\">\n <div v-if=\"localErrors.length\" class=\"text-sm text-[var(--ds-danger)]\" data-ds-form-file-errors>\n <div v-for=\"(e, i) in localErrors\" :key=\"i\" data-ds-form-file-error>\n {{ issueMessage(e) }}\n </div>\n </div>\n </slot>\n </div>\n </div>\n</template>\n\n<style scoped>\n[data-ds-form-file].ds-dropzone--over {\n outline: 2px solid var(--ring);\n outline-offset: 2px;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, nextTick, ref, watch } from 'vue'\n\nimport IconUpload from '~icons/lucide/upload'\nimport IconX from '~icons/lucide/x'\n\nimport GrButton from '../GrButton/GrButton.vue'\nimport GrIcon from '../GrIcon/GrIcon.vue'\nimport { vDropzone } from '../../directives'\nimport { acceptValidator, FileValidationError, runFileValidators } from '../../fileValidation'\nimport type { FileValidationIssue, FileValidator } from '../../fileValidation'\n\ndefineOptions({\n name: 'GrFormFile',\n})\n\nexport type GrFormFileError = FileValidationIssue\n\n/**\n * Пропы `GrFormFile` — форма-поле для выбора файлов с встроенной валидацией,\n * drag&drop (через `v-dropzone`) и списком выбранных файлов.\n *\n * Все `*Text`/`placeholder` — i18n-friendly, принимают готовые строки локали.\n */\nexport interface GrFormFileProps {\n modelValue: File | File[] | null\n multiple?: boolean\n disabled?: boolean\n /** W3C `accept` для `<input type=\"file\">` + sugar к `acceptValidator(...)`. */\n accept?: string\n validators?: FileValidator[]\n uploadText?: string\n changeText?: string\n removeText?: string\n clearAllText?: string\n placeholder?: string\n /** Дополнительная (кастомная) валидация на стороне потребителя. */\n validate?: (files: File[]) => GrFormFileError[] | Promise<GrFormFileError[]>\n}\n\nconst props = withDefaults(\n defineProps<GrFormFileProps>(),\n {\n multiple: false,\n disabled: false,\n accept: undefined,\n uploadText: 'Upload file',\n changeText: 'Change file',\n removeText: 'Remove',\n clearAllText: 'Clear all',\n placeholder: 'No files selected',\n validators: undefined,\n validate: undefined,\n },\n)\n\nconst emit = defineEmits<{\n (e: 'update:modelValue', value: File | File[] | null): void\n (e: 'change', value: File | File[] | null): void\n (e: 'clear'): void\n (e: 'validation', errors: GrFormFileError[]): void\n (e: 'update:errors', errors: GrFormFileError[]): void\n}>()\n\nconst inputRef = ref<HTMLInputElement | null>(null)\nconst localErrors = ref<GrFormFileError[]>([])\n\nconst files = computed<File[]>(() => {\n if (props.multiple) {\n return Array.isArray(props.modelValue) ? props.modelValue : []\n }\n\n return props.modelValue instanceof File ? [props.modelValue] : []\n})\n\nconst hasFiles = computed(() => files.value.length > 0)\n\nfunction clearInputValue() {\n if (!inputRef.value) return\n inputRef.value.value = ''\n}\n\nfunction setErrors(next: GrFormFileError[]) {\n localErrors.value = next\n emit('validation', next)\n emit('update:errors', next)\n}\n\nfunction clearErrors() {\n if (localErrors.value.length === 0) return\n setErrors([])\n}\n\nfunction openDialog() {\n if (props.disabled) return\n inputRef.value?.click()\n}\n\nfunction emitModel(nextFiles: File[]) {\n const value: File | File[] | null = props.multiple\n ? nextFiles\n : (nextFiles[0] ?? null)\n\n emit('update:modelValue', value)\n emit('change', value)\n}\n\nasync function applyFiles(nextFiles: File[]) {\n const isPromiseLike = (value: unknown): value is PromiseLike<any> => {\n return !!value && typeof (value as any).then === 'function'\n }\n\n const customValidator: FileValidator | undefined = props.validate\n ? async ({ files }) => (await props.validate?.(files)) ?? []\n : undefined\n\n const validators: FileValidator[] = [\n acceptValidator(props.accept),\n ...(props.validators ?? []),\n ...(customValidator ? [customValidator] : []),\n ]\n\n const res = runFileValidators(nextFiles, validators, {\n source: 'input',\n multiple: props.multiple,\n })\n\n const { files: picked, issues } = isPromiseLike(res) ? await res : res\n\n if (issues.length > 0) {\n setErrors(issues)\n clearInputValue()\n await nextTick()\n return\n }\n\n clearErrors()\n emitModel(picked)\n clearInputValue()\n await nextTick()\n}\n\nasync function onInputChange(event: Event) {\n if (props.disabled) return\n\n const target = event.target as HTMLInputElement | null\n const nextFiles = target?.files ? Array.prototype.slice.call(target.files) as File[] : []\n\n // Reset value so selecting the same file twice triggers change.\n if (target) target.value = ''\n await nextTick()\n\n await applyFiles(nextFiles)\n}\n\nfunction clearAll() {\n if (props.disabled) return\n clearErrors()\n clearInputValue()\n emit('clear')\n\n emit('update:modelValue', props.multiple ? [] : null)\n emit('change', props.multiple ? [] : null)\n}\n\nfunction removeAt(index: number) {\n if (props.disabled) return\n if (!props.multiple) {\n clearAll()\n return\n }\n\n const next = files.value.slice()\n next.splice(index, 1)\n\n emit('update:modelValue', next)\n emit('change', next)\n}\n\nfunction issueMessage(issue: GrFormFileError): string {\n if (issue.fileName) return `${issue.fileName}: ${issue.message}`\n return issue.message\n}\n\nconst dropzone = computed(() => {\n const customValidator: FileValidator | undefined = props.validate\n ? async ({ files }) => (await props.validate?.(files)) ?? []\n : undefined\n\n const validators: FileValidator[] = [\n acceptValidator(props.accept),\n ...(props.validators ?? []),\n ...(customValidator ? [customValidator] : []),\n ]\n\n return {\n enabled: !props.disabled,\n multiple: props.multiple,\n validators,\n onFiles: async (dropped: File[]) => {\n // `v-dropzone` уже выполнил валидаторы и нормализацию по `multiple`.\n clearErrors()\n emitModel(dropped)\n clearInputValue()\n await nextTick()\n },\n onError: (error: unknown) => {\n if (error instanceof FileValidationError) {\n setErrors(error.issues)\n }\n else {\n setErrors([\n {\n code: 'accept',\n message: 'Failed to validate dropped files',\n },\n ])\n }\n },\n }\n})\n\nwatch(\n () => props.modelValue,\n (value) => {\n if (!inputRef.value) return\n\n if (!value || (Array.isArray(value) && value.length === 0)) {\n // Позволяет выбрать тот же файл снова после внешнего сброса.\n inputRef.value.value = ''\n }\n },\n)\n</script>\n\n<template>\n <div\n v-dropzone=\"dropzone\"\n data-ds-form-file\n class=\"rounded-[var(--ds-radius-md)]\"\n :class=\"disabled ? 'opacity-60 cursor-not-allowed' : ''\"\n >\n <input\n ref=\"inputRef\"\n data-ds-form-file-input\n type=\"file\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n class=\"absolute opacity-0 w-px h-px pointer-events-none\"\n :multiple=\"multiple\"\n :accept=\"accept\"\n :disabled=\"disabled\"\n @change=\"onInputChange\"\n >\n\n <div class=\"flex flex-col gap-2\">\n <div class=\"flex flex-wrap items-center gap-3\">\n <GrButton\n variant=\"secondary\"\n size=\"sm\"\n data-ds-form-file-upload-btn\n :disabled=\"disabled\"\n @click.prevent=\"openDialog\"\n >\n <GrIcon size=\"sm\" aria-hidden=\"true\">\n <IconUpload />\n </GrIcon>\n <span class=\"ml-2\">{{ hasFiles ? changeText : uploadText }}</span>\n </GrButton>\n\n <GrButton\n v-if=\"hasFiles && !multiple\"\n variant=\"secondary\"\n size=\"sm\"\n data-ds-form-file-clear-btn\n :disabled=\"disabled\"\n @click.prevent=\"clearAll\"\n >\n <GrIcon size=\"sm\" aria-hidden=\"true\">\n <IconX />\n </GrIcon>\n <span class=\"ml-2\">{{ removeText }}</span>\n </GrButton>\n\n <GrButton\n v-if=\"multiple && hasFiles\"\n variant=\"secondary\"\n size=\"sm\"\n data-ds-form-file-clear-all-btn\n :disabled=\"disabled\"\n @click.prevent=\"clearAll\"\n >\n <GrIcon size=\"sm\" aria-hidden=\"true\">\n <IconX />\n </GrIcon>\n <span class=\"ml-2\">{{ clearAllText }}</span>\n </GrButton>\n\n <span\n v-if=\"!multiple && hasFiles\"\n class=\"text-sm text-[var(--muted-fg)] truncate max-w-[240px]\"\n data-ds-form-file-single-name\n :title=\"files[0]?.name\"\n >\n {{ files[0]?.name }}\n </span>\n\n <span\n v-if=\"!hasFiles\"\n class=\"text-sm text-[var(--muted-fg)]\"\n data-ds-form-file-placeholder\n >\n {{ placeholder }}\n </span>\n </div>\n\n <div v-if=\"multiple && hasFiles\" class=\"flex flex-col gap-2\">\n <div\n v-for=\"(file, index) in files\"\n :key=\"`${file.name}-${file.size}-${index}`\"\n class=\"flex items-center gap-2\"\n data-ds-form-file-item\n >\n <span\n class=\"text-sm text-[var(--muted-fg)] truncate max-w-[240px]\"\n :title=\"file.name\"\n data-ds-form-file-item-name\n >\n {{ file.name }}\n </span>\n\n <button\n type=\"button\"\n class=\"text-xs text-[var(--muted-fg)] hover:text-[var(--fg)]\"\n data-ds-form-file-item-remove\n :disabled=\"disabled\"\n @click.prevent=\"removeAt(index)\"\n >\n {{ removeText }}\n </button>\n </div>\n </div>\n\n <slot name=\"errors\" :errors=\"localErrors\">\n <div v-if=\"localErrors.length\" class=\"text-sm text-[var(--ds-danger)]\" data-ds-form-file-errors>\n <div v-for=\"(e, i) in localErrors\" :key=\"i\" data-ds-form-file-error>\n {{ issueMessage(e) }}\n </div>\n </div>\n </slot>\n </div>\n </div>\n</template>\n\n<style scoped>\n[data-ds-form-file].ds-dropzone--over {\n outline: 2px solid var(--ring);\n outline-offset: 2px;\n}\n</style>\n"],"mappings":"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"}
1
+ {"version":3,"file":"GrFormFile-CIleXnfr.js","names":[],"sources":["../../../../src/components/GrFormFile/GrFormFile.vue","../../../../src/components/GrFormFile/GrFormFile.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, nextTick, ref, watch } from 'vue'\n\nimport IconUpload from '~icons/lucide/upload'\nimport IconX from '~icons/lucide/x'\n\nimport GrButton from '../GrButton/GrButton.vue'\nimport GrIcon from '../GrIcon/GrIcon.vue'\nimport { vDropzone } from '../../directives'\nimport { acceptValidator, FileValidationError, runFileValidators } from '../../fileValidation'\nimport type { FileValidationIssue, FileValidator } from '../../fileValidation'\n\ndefineOptions({\n name: 'GrFormFile',\n})\n\nexport type GrFormFileError = FileValidationIssue\n\n/**\n * Пропы `GrFormFile` — форма-поле для выбора файлов с встроенной валидацией,\n * drag&drop (через `v-dropzone`) и списком выбранных файлов.\n *\n * Все `*Text`/`placeholder` — i18n-friendly, принимают готовые строки локали.\n */\nexport interface GrFormFileProps {\n modelValue: File | File[] | null\n multiple?: boolean\n disabled?: boolean\n /** W3C `accept` для `<input type=\"file\">` + sugar к `acceptValidator(...)`. */\n accept?: string\n validators?: FileValidator[]\n uploadText?: string\n changeText?: string\n removeText?: string\n clearAllText?: string\n placeholder?: string\n /** Дополнительная (кастомная) валидация на стороне потребителя. */\n validate?: (files: File[]) => GrFormFileError[] | Promise<GrFormFileError[]>\n}\n\nconst props = withDefaults(\n defineProps<GrFormFileProps>(),\n {\n multiple: false,\n disabled: false,\n accept: undefined,\n uploadText: 'Upload file',\n changeText: 'Change file',\n removeText: 'Remove',\n clearAllText: 'Clear all',\n placeholder: 'No files selected',\n validators: undefined,\n validate: undefined,\n },\n)\n\nconst emit = defineEmits<{\n (e: 'update:modelValue', value: File | File[] | null): void\n (e: 'change', value: File | File[] | null): void\n (e: 'clear'): void\n (e: 'validation', errors: GrFormFileError[]): void\n (e: 'update:errors', errors: GrFormFileError[]): void\n}>()\n\nconst inputRef = ref<HTMLInputElement | null>(null)\nconst localErrors = ref<GrFormFileError[]>([])\n\nconst files = computed<File[]>(() => {\n if (props.multiple) {\n return Array.isArray(props.modelValue) ? props.modelValue : []\n }\n\n return props.modelValue instanceof File ? [props.modelValue] : []\n})\n\nconst hasFiles = computed(() => files.value.length > 0)\n\nfunction clearInputValue() {\n if (!inputRef.value) return\n inputRef.value.value = ''\n}\n\nfunction setErrors(next: GrFormFileError[]) {\n localErrors.value = next\n emit('validation', next)\n emit('update:errors', next)\n}\n\nfunction clearErrors() {\n if (localErrors.value.length === 0) return\n setErrors([])\n}\n\nfunction openDialog() {\n if (props.disabled) return\n inputRef.value?.click()\n}\n\nfunction emitModel(nextFiles: File[]) {\n const value: File | File[] | null = props.multiple\n ? nextFiles\n : (nextFiles[0] ?? null)\n\n emit('update:modelValue', value)\n emit('change', value)\n}\n\nasync function applyFiles(nextFiles: File[]) {\n const isPromiseLike = (value: unknown): value is PromiseLike<any> => {\n return !!value && typeof (value as any).then === 'function'\n }\n\n const customValidator: FileValidator | undefined = props.validate\n ? async ({ files }) => (await props.validate?.(files)) ?? []\n : undefined\n\n const validators: FileValidator[] = [\n acceptValidator(props.accept),\n ...(props.validators ?? []),\n ...(customValidator ? [customValidator] : []),\n ]\n\n const res = runFileValidators(nextFiles, validators, {\n source: 'input',\n multiple: props.multiple,\n })\n\n const { files: picked, issues } = isPromiseLike(res) ? await res : res\n\n if (issues.length > 0) {\n setErrors(issues)\n clearInputValue()\n await nextTick()\n return\n }\n\n clearErrors()\n emitModel(picked)\n clearInputValue()\n await nextTick()\n}\n\nasync function onInputChange(event: Event) {\n if (props.disabled) return\n\n const target = event.target as HTMLInputElement | null\n const nextFiles = target?.files ? Array.prototype.slice.call(target.files) as File[] : []\n\n // Reset value so selecting the same file twice triggers change.\n if (target) target.value = ''\n await nextTick()\n\n await applyFiles(nextFiles)\n}\n\nfunction clearAll() {\n if (props.disabled) return\n clearErrors()\n clearInputValue()\n emit('clear')\n\n emit('update:modelValue', props.multiple ? [] : null)\n emit('change', props.multiple ? [] : null)\n}\n\nfunction removeAt(index: number) {\n if (props.disabled) return\n if (!props.multiple) {\n clearAll()\n return\n }\n\n const next = files.value.slice()\n next.splice(index, 1)\n\n emit('update:modelValue', next)\n emit('change', next)\n}\n\nfunction issueMessage(issue: GrFormFileError): string {\n if (issue.fileName) return `${issue.fileName}: ${issue.message}`\n return issue.message\n}\n\nconst dropzone = computed(() => {\n const customValidator: FileValidator | undefined = props.validate\n ? async ({ files }) => (await props.validate?.(files)) ?? []\n : undefined\n\n const validators: FileValidator[] = [\n acceptValidator(props.accept),\n ...(props.validators ?? []),\n ...(customValidator ? [customValidator] : []),\n ]\n\n return {\n enabled: !props.disabled,\n multiple: props.multiple,\n validators,\n onFiles: async (dropped: File[]) => {\n // `v-dropzone` уже выполнил валидаторы и нормализацию по `multiple`.\n clearErrors()\n emitModel(dropped)\n clearInputValue()\n await nextTick()\n },\n onError: (error: unknown) => {\n if (error instanceof FileValidationError) {\n setErrors(error.issues)\n }\n else {\n setErrors([\n {\n code: 'accept',\n message: 'Failed to validate dropped files',\n },\n ])\n }\n },\n }\n})\n\nwatch(\n () => props.modelValue,\n (value) => {\n if (!inputRef.value) return\n\n if (!value || (Array.isArray(value) && value.length === 0)) {\n // Позволяет выбрать тот же файл снова после внешнего сброса.\n inputRef.value.value = ''\n }\n },\n)\n</script>\n\n<template>\n <div\n v-dropzone=\"dropzone\"\n data-ds-form-file\n class=\"rounded-[var(--ds-radius-md)]\"\n :class=\"disabled ? 'opacity-60 cursor-not-allowed' : ''\"\n >\n <input\n ref=\"inputRef\"\n data-ds-form-file-input\n type=\"file\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n class=\"absolute opacity-0 w-px h-px pointer-events-none\"\n :multiple=\"multiple\"\n :accept=\"accept\"\n :disabled=\"disabled\"\n @change=\"onInputChange\"\n >\n\n <div class=\"flex flex-col gap-2\">\n <div class=\"flex flex-wrap items-center gap-3\">\n <GrButton\n variant=\"secondary\"\n size=\"sm\"\n data-ds-form-file-upload-btn\n :disabled=\"disabled\"\n @click.prevent=\"openDialog\"\n >\n <GrIcon size=\"sm\" aria-hidden=\"true\">\n <IconUpload />\n </GrIcon>\n <span class=\"ml-2\">{{ hasFiles ? changeText : uploadText }}</span>\n </GrButton>\n\n <GrButton\n v-if=\"hasFiles && !multiple\"\n variant=\"secondary\"\n size=\"sm\"\n data-ds-form-file-clear-btn\n :disabled=\"disabled\"\n @click.prevent=\"clearAll\"\n >\n <GrIcon size=\"sm\" aria-hidden=\"true\">\n <IconX />\n </GrIcon>\n <span class=\"ml-2\">{{ removeText }}</span>\n </GrButton>\n\n <GrButton\n v-if=\"multiple && hasFiles\"\n variant=\"secondary\"\n size=\"sm\"\n data-ds-form-file-clear-all-btn\n :disabled=\"disabled\"\n @click.prevent=\"clearAll\"\n >\n <GrIcon size=\"sm\" aria-hidden=\"true\">\n <IconX />\n </GrIcon>\n <span class=\"ml-2\">{{ clearAllText }}</span>\n </GrButton>\n\n <span\n v-if=\"!multiple && hasFiles\"\n class=\"text-sm text-[var(--muted-fg)] truncate max-w-[240px]\"\n data-ds-form-file-single-name\n :title=\"files[0]?.name\"\n >\n {{ files[0]?.name }}\n </span>\n\n <span\n v-if=\"!hasFiles\"\n class=\"text-sm text-[var(--muted-fg)]\"\n data-ds-form-file-placeholder\n >\n {{ placeholder }}\n </span>\n </div>\n\n <div v-if=\"multiple && hasFiles\" class=\"flex flex-col gap-2\">\n <div\n v-for=\"(file, index) in files\"\n :key=\"`${file.name}-${file.size}-${index}`\"\n class=\"flex items-center gap-2\"\n data-ds-form-file-item\n >\n <span\n class=\"text-sm text-[var(--muted-fg)] truncate max-w-[240px]\"\n :title=\"file.name\"\n data-ds-form-file-item-name\n >\n {{ file.name }}\n </span>\n\n <button\n type=\"button\"\n class=\"text-xs text-[var(--muted-fg)] hover:text-[var(--fg)]\"\n data-ds-form-file-item-remove\n :disabled=\"disabled\"\n @click.prevent=\"removeAt(index)\"\n >\n {{ removeText }}\n </button>\n </div>\n </div>\n\n <slot name=\"errors\" :errors=\"localErrors\">\n <div v-if=\"localErrors.length\" class=\"text-sm text-[var(--ds-danger)]\" data-ds-form-file-errors>\n <div v-for=\"(e, i) in localErrors\" :key=\"i\" data-ds-form-file-error>\n {{ issueMessage(e) }}\n </div>\n </div>\n </slot>\n </div>\n </div>\n</template>\n\n<style scoped>\n[data-ds-form-file].ds-dropzone--over {\n outline: 2px solid var(--ring);\n outline-offset: 2px;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, nextTick, ref, watch } from 'vue'\n\nimport IconUpload from '~icons/lucide/upload'\nimport IconX from '~icons/lucide/x'\n\nimport GrButton from '../GrButton/GrButton.vue'\nimport GrIcon from '../GrIcon/GrIcon.vue'\nimport { vDropzone } from '../../directives'\nimport { acceptValidator, FileValidationError, runFileValidators } from '../../fileValidation'\nimport type { FileValidationIssue, FileValidator } from '../../fileValidation'\n\ndefineOptions({\n name: 'GrFormFile',\n})\n\nexport type GrFormFileError = FileValidationIssue\n\n/**\n * Пропы `GrFormFile` — форма-поле для выбора файлов с встроенной валидацией,\n * drag&drop (через `v-dropzone`) и списком выбранных файлов.\n *\n * Все `*Text`/`placeholder` — i18n-friendly, принимают готовые строки локали.\n */\nexport interface GrFormFileProps {\n modelValue: File | File[] | null\n multiple?: boolean\n disabled?: boolean\n /** W3C `accept` для `<input type=\"file\">` + sugar к `acceptValidator(...)`. */\n accept?: string\n validators?: FileValidator[]\n uploadText?: string\n changeText?: string\n removeText?: string\n clearAllText?: string\n placeholder?: string\n /** Дополнительная (кастомная) валидация на стороне потребителя. */\n validate?: (files: File[]) => GrFormFileError[] | Promise<GrFormFileError[]>\n}\n\nconst props = withDefaults(\n defineProps<GrFormFileProps>(),\n {\n multiple: false,\n disabled: false,\n accept: undefined,\n uploadText: 'Upload file',\n changeText: 'Change file',\n removeText: 'Remove',\n clearAllText: 'Clear all',\n placeholder: 'No files selected',\n validators: undefined,\n validate: undefined,\n },\n)\n\nconst emit = defineEmits<{\n (e: 'update:modelValue', value: File | File[] | null): void\n (e: 'change', value: File | File[] | null): void\n (e: 'clear'): void\n (e: 'validation', errors: GrFormFileError[]): void\n (e: 'update:errors', errors: GrFormFileError[]): void\n}>()\n\nconst inputRef = ref<HTMLInputElement | null>(null)\nconst localErrors = ref<GrFormFileError[]>([])\n\nconst files = computed<File[]>(() => {\n if (props.multiple) {\n return Array.isArray(props.modelValue) ? props.modelValue : []\n }\n\n return props.modelValue instanceof File ? [props.modelValue] : []\n})\n\nconst hasFiles = computed(() => files.value.length > 0)\n\nfunction clearInputValue() {\n if (!inputRef.value) return\n inputRef.value.value = ''\n}\n\nfunction setErrors(next: GrFormFileError[]) {\n localErrors.value = next\n emit('validation', next)\n emit('update:errors', next)\n}\n\nfunction clearErrors() {\n if (localErrors.value.length === 0) return\n setErrors([])\n}\n\nfunction openDialog() {\n if (props.disabled) return\n inputRef.value?.click()\n}\n\nfunction emitModel(nextFiles: File[]) {\n const value: File | File[] | null = props.multiple\n ? nextFiles\n : (nextFiles[0] ?? null)\n\n emit('update:modelValue', value)\n emit('change', value)\n}\n\nasync function applyFiles(nextFiles: File[]) {\n const isPromiseLike = (value: unknown): value is PromiseLike<any> => {\n return !!value && typeof (value as any).then === 'function'\n }\n\n const customValidator: FileValidator | undefined = props.validate\n ? async ({ files }) => (await props.validate?.(files)) ?? []\n : undefined\n\n const validators: FileValidator[] = [\n acceptValidator(props.accept),\n ...(props.validators ?? []),\n ...(customValidator ? [customValidator] : []),\n ]\n\n const res = runFileValidators(nextFiles, validators, {\n source: 'input',\n multiple: props.multiple,\n })\n\n const { files: picked, issues } = isPromiseLike(res) ? await res : res\n\n if (issues.length > 0) {\n setErrors(issues)\n clearInputValue()\n await nextTick()\n return\n }\n\n clearErrors()\n emitModel(picked)\n clearInputValue()\n await nextTick()\n}\n\nasync function onInputChange(event: Event) {\n if (props.disabled) return\n\n const target = event.target as HTMLInputElement | null\n const nextFiles = target?.files ? Array.prototype.slice.call(target.files) as File[] : []\n\n // Reset value so selecting the same file twice triggers change.\n if (target) target.value = ''\n await nextTick()\n\n await applyFiles(nextFiles)\n}\n\nfunction clearAll() {\n if (props.disabled) return\n clearErrors()\n clearInputValue()\n emit('clear')\n\n emit('update:modelValue', props.multiple ? [] : null)\n emit('change', props.multiple ? [] : null)\n}\n\nfunction removeAt(index: number) {\n if (props.disabled) return\n if (!props.multiple) {\n clearAll()\n return\n }\n\n const next = files.value.slice()\n next.splice(index, 1)\n\n emit('update:modelValue', next)\n emit('change', next)\n}\n\nfunction issueMessage(issue: GrFormFileError): string {\n if (issue.fileName) return `${issue.fileName}: ${issue.message}`\n return issue.message\n}\n\nconst dropzone = computed(() => {\n const customValidator: FileValidator | undefined = props.validate\n ? async ({ files }) => (await props.validate?.(files)) ?? []\n : undefined\n\n const validators: FileValidator[] = [\n acceptValidator(props.accept),\n ...(props.validators ?? []),\n ...(customValidator ? [customValidator] : []),\n ]\n\n return {\n enabled: !props.disabled,\n multiple: props.multiple,\n validators,\n onFiles: async (dropped: File[]) => {\n // `v-dropzone` уже выполнил валидаторы и нормализацию по `multiple`.\n clearErrors()\n emitModel(dropped)\n clearInputValue()\n await nextTick()\n },\n onError: (error: unknown) => {\n if (error instanceof FileValidationError) {\n setErrors(error.issues)\n }\n else {\n setErrors([\n {\n code: 'accept',\n message: 'Failed to validate dropped files',\n },\n ])\n }\n },\n }\n})\n\nwatch(\n () => props.modelValue,\n (value) => {\n if (!inputRef.value) return\n\n if (!value || (Array.isArray(value) && value.length === 0)) {\n // Позволяет выбрать тот же файл снова после внешнего сброса.\n inputRef.value.value = ''\n }\n },\n)\n</script>\n\n<template>\n <div\n v-dropzone=\"dropzone\"\n data-ds-form-file\n class=\"rounded-[var(--ds-radius-md)]\"\n :class=\"disabled ? 'opacity-60 cursor-not-allowed' : ''\"\n >\n <input\n ref=\"inputRef\"\n data-ds-form-file-input\n type=\"file\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n class=\"absolute opacity-0 w-px h-px pointer-events-none\"\n :multiple=\"multiple\"\n :accept=\"accept\"\n :disabled=\"disabled\"\n @change=\"onInputChange\"\n >\n\n <div class=\"flex flex-col gap-2\">\n <div class=\"flex flex-wrap items-center gap-3\">\n <GrButton\n variant=\"secondary\"\n size=\"sm\"\n data-ds-form-file-upload-btn\n :disabled=\"disabled\"\n @click.prevent=\"openDialog\"\n >\n <GrIcon size=\"sm\" aria-hidden=\"true\">\n <IconUpload />\n </GrIcon>\n <span class=\"ml-2\">{{ hasFiles ? changeText : uploadText }}</span>\n </GrButton>\n\n <GrButton\n v-if=\"hasFiles && !multiple\"\n variant=\"secondary\"\n size=\"sm\"\n data-ds-form-file-clear-btn\n :disabled=\"disabled\"\n @click.prevent=\"clearAll\"\n >\n <GrIcon size=\"sm\" aria-hidden=\"true\">\n <IconX />\n </GrIcon>\n <span class=\"ml-2\">{{ removeText }}</span>\n </GrButton>\n\n <GrButton\n v-if=\"multiple && hasFiles\"\n variant=\"secondary\"\n size=\"sm\"\n data-ds-form-file-clear-all-btn\n :disabled=\"disabled\"\n @click.prevent=\"clearAll\"\n >\n <GrIcon size=\"sm\" aria-hidden=\"true\">\n <IconX />\n </GrIcon>\n <span class=\"ml-2\">{{ clearAllText }}</span>\n </GrButton>\n\n <span\n v-if=\"!multiple && hasFiles\"\n class=\"text-sm text-[var(--muted-fg)] truncate max-w-[240px]\"\n data-ds-form-file-single-name\n :title=\"files[0]?.name\"\n >\n {{ files[0]?.name }}\n </span>\n\n <span\n v-if=\"!hasFiles\"\n class=\"text-sm text-[var(--muted-fg)]\"\n data-ds-form-file-placeholder\n >\n {{ placeholder }}\n </span>\n </div>\n\n <div v-if=\"multiple && hasFiles\" class=\"flex flex-col gap-2\">\n <div\n v-for=\"(file, index) in files\"\n :key=\"`${file.name}-${file.size}-${index}`\"\n class=\"flex items-center gap-2\"\n data-ds-form-file-item\n >\n <span\n class=\"text-sm text-[var(--muted-fg)] truncate max-w-[240px]\"\n :title=\"file.name\"\n data-ds-form-file-item-name\n >\n {{ file.name }}\n </span>\n\n <button\n type=\"button\"\n class=\"text-xs text-[var(--muted-fg)] hover:text-[var(--fg)]\"\n data-ds-form-file-item-remove\n :disabled=\"disabled\"\n @click.prevent=\"removeAt(index)\"\n >\n {{ removeText }}\n </button>\n </div>\n </div>\n\n <slot name=\"errors\" :errors=\"localErrors\">\n <div v-if=\"localErrors.length\" class=\"text-sm text-[var(--ds-danger)]\" data-ds-form-file-errors>\n <div v-for=\"(e, i) in localErrors\" :key=\"i\" data-ds-form-file-error>\n {{ issueMessage(e) }}\n </div>\n </div>\n </slot>\n </div>\n </div>\n</template>\n\n<style scoped>\n[data-ds-form-file].ds-dropzone--over {\n outline: 2px solid var(--ring);\n outline-offset: 2px;\n}\n</style>\n"],"mappings":"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"}
@@ -1,3 +1,3 @@
1
- import { t as GrFormFile_default } from "./chunks/GrFormFile-deDPicM2.js";
2
- import { t as grFormFileConfig } from "../../chunks/config-DbqFk-IW.js";
1
+ import { t as GrFormFile_default } from "./chunks/GrFormFile-CIleXnfr.js";
2
+ import { t as grFormFileConfig } from "../../chunks/config-BbKroFtR.js";
3
3
  export { GrFormFile_default as GrFormFile, GrFormFile_default as default, grFormFileConfig };
@@ -1 +1 @@
1
- {"version":3,"file":"GrFormSection-CWBk2EUm.js","names":[],"sources":["../../../../src/components/GrFormSection/GrFormSection.vue","../../../../src/components/GrFormSection/GrFormSection.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { useId } from 'vue'\n\n/**\n * GrFormSection — секция формы с заголовком, опциональным описанием и контентом.\n *\n * - `section` связан с заголовком через `aria-labelledby` и c описанием\n * (если есть) через `aria-describedby` — для a11y.\n */\nexport interface GrFormSectionProps {\n title: string\n description?: string\n}\n\nwithDefaults(defineProps<GrFormSectionProps>(), {\n description: undefined,\n})\n\nconst titleId = useId()\nconst descriptionId = useId()\n</script>\n\n<template>\n <section\n data-ds-form-section\n class=\"grid gap-4\"\n :aria-labelledby=\"titleId\"\n :aria-describedby=\"description ? descriptionId : undefined\"\n >\n <div>\n <div :id=\"titleId\" class=\"text-[14px] font-700\">\n {{ title }}\n </div>\n <div\n v-if=\"description\"\n :id=\"descriptionId\"\n class=\"mt-1 text-[13px] text-[var(--muted-fg)]\"\n >\n {{ description }}\n </div>\n </div>\n <slot />\n </section>\n</template>\n","<script setup lang=\"ts\">\nimport { useId } from 'vue'\n\n/**\n * GrFormSection — секция формы с заголовком, опциональным описанием и контентом.\n *\n * - `section` связан с заголовком через `aria-labelledby` и c описанием\n * (если есть) через `aria-describedby` — для a11y.\n */\nexport interface GrFormSectionProps {\n title: string\n description?: string\n}\n\nwithDefaults(defineProps<GrFormSectionProps>(), {\n description: undefined,\n})\n\nconst titleId = useId()\nconst descriptionId = useId()\n</script>\n\n<template>\n <section\n data-ds-form-section\n class=\"grid gap-4\"\n :aria-labelledby=\"titleId\"\n :aria-describedby=\"description ? descriptionId : undefined\"\n >\n <div>\n <div :id=\"titleId\" class=\"text-[14px] font-700\">\n {{ title }}\n </div>\n <div\n v-if=\"description\"\n :id=\"descriptionId\"\n class=\"mt-1 text-[13px] text-[var(--muted-fg)]\"\n >\n {{ description }}\n </div>\n </div>\n <slot />\n </section>\n</template>\n"],"mappings":";;;;;;;;;;;;;;EAkBA,MAAM,UAAU,OAAM;EACtB,MAAM,gBAAgB,OAAM;;uBAI1B,mBAmBU,WAAA;IAlBR,wBAAA;IACA,OAAM;IACL,mBAAiB,MAAA,QAAO;IACxB,oBAAkB,QAAA,cAAc,MAAA,cAAa,GAAG,KAAA;OAEjD,mBAWM,OAAA,MAAA,CAVJ,mBAEM,OAAA;IAFA,IAAI,MAAA,QAAO;IAAE,OAAM;sBACpB,QAAA,MAAK,EAAA,GAAA,WAAA,EAGF,QAAA,eAAA,WAAA,EADR,mBAMM,OAAA;;IAJH,IAAI,MAAA,cAAa;IAClB,OAAM;sBAEH,QAAA,YAAW,EAAA,GAAA,WAAA,IAAA,mBAAA,IAAA,KAAA,CAAA,CAAA,EAGlB,WAAQ,KAAA,QAAA,UAAA,CAAA,EAAA,GAAA,WAAA"}
1
+ {"version":3,"file":"GrFormSection-DpGhY7MX.js","names":[],"sources":["../../../../src/components/GrFormSection/GrFormSection.vue","../../../../src/components/GrFormSection/GrFormSection.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { useId } from 'vue'\n\n/**\n * GrFormSection — секция формы с заголовком, опциональным описанием и контентом.\n *\n * - `section` связан с заголовком через `aria-labelledby` и c описанием\n * (если есть) через `aria-describedby` — для a11y.\n */\nexport interface GrFormSectionProps {\n title: string\n description?: string\n}\n\nwithDefaults(defineProps<GrFormSectionProps>(), {\n description: undefined,\n})\n\nconst titleId = useId()\nconst descriptionId = useId()\n</script>\n\n<template>\n <section\n data-ds-form-section\n class=\"grid gap-4\"\n :aria-labelledby=\"titleId\"\n :aria-describedby=\"description ? descriptionId : undefined\"\n >\n <div>\n <div :id=\"titleId\" class=\"text-[14px] font-700\">\n {{ title }}\n </div>\n <div\n v-if=\"description\"\n :id=\"descriptionId\"\n class=\"mt-1 text-[13px] text-[var(--muted-fg)]\"\n >\n {{ description }}\n </div>\n </div>\n <slot />\n </section>\n</template>\n","<script setup lang=\"ts\">\nimport { useId } from 'vue'\n\n/**\n * GrFormSection — секция формы с заголовком, опциональным описанием и контентом.\n *\n * - `section` связан с заголовком через `aria-labelledby` и c описанием\n * (если есть) через `aria-describedby` — для a11y.\n */\nexport interface GrFormSectionProps {\n title: string\n description?: string\n}\n\nwithDefaults(defineProps<GrFormSectionProps>(), {\n description: undefined,\n})\n\nconst titleId = useId()\nconst descriptionId = useId()\n</script>\n\n<template>\n <section\n data-ds-form-section\n class=\"grid gap-4\"\n :aria-labelledby=\"titleId\"\n :aria-describedby=\"description ? descriptionId : undefined\"\n >\n <div>\n <div :id=\"titleId\" class=\"text-[14px] font-700\">\n {{ title }}\n </div>\n <div\n v-if=\"description\"\n :id=\"descriptionId\"\n class=\"mt-1 text-[13px] text-[var(--muted-fg)]\"\n >\n {{ description }}\n </div>\n </div>\n <slot />\n </section>\n</template>\n"],"mappings":";;;;;;;;;;;;;;EAkBA,MAAM,UAAU,OAAM;EACtB,MAAM,gBAAgB,OAAM;;uBAI1B,mBAmBU,WAAA;IAlBR,wBAAA;IACA,OAAM;IACL,mBAAiB,MAAA,QAAO;IACxB,oBAAkB,QAAA,cAAc,MAAA,cAAa,GAAG,KAAA;OAEjD,mBAWM,OAAA,MAAA,CAVJ,mBAEM,OAAA;IAFA,IAAI,MAAA,QAAO;IAAE,OAAM;sBACpB,QAAA,MAAK,EAAA,GAAA,WAAA,EAGF,QAAA,eAAA,WAAA,EADR,mBAMM,OAAA;;IAJH,IAAI,MAAA,cAAa;IAClB,OAAM;sBAEH,QAAA,YAAW,EAAA,GAAA,WAAA,IAAA,mBAAA,IAAA,KAAA,CAAA,CAAA,EAGlB,WAAQ,KAAA,QAAA,UAAA,CAAA,EAAA,GAAA,WAAA"}
@@ -1,3 +1,3 @@
1
- import { t as GrFormSection_default } from "./chunks/GrFormSection-CWBk2EUm.js";
2
- import { t as grFormSectionConfig } from "../../chunks/config-D6Af6WZw.js";
1
+ import { t as GrFormSection_default } from "./chunks/GrFormSection-DpGhY7MX.js";
2
+ import { t as grFormSectionConfig } from "../../chunks/config-DoBk_CzG.js";
3
3
  export { GrFormSection_default as GrFormSection, GrFormSection_default as default, grFormSectionConfig };
@@ -1 +1 @@
1
- {"version":3,"file":"GrImageViewer-Cpj5zyZQ.js","names":["$slots"],"sources":["../../../../src/components/GrImageViewer/GrImageViewer.vue","../../../../src/components/GrImageViewer/GrImageViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport { Dialog, DialogPanel, TransitionChild, TransitionRoot } from '@headlessui/vue'\n\n/**\n * Usage:\n *\n * <GrImageViewer\n * v-model=\"open\"\n * :url-list=\"images\"\n * :initial-index=\"0\"\n * show-progress\n * hide-on-click-modal\n * />\n */\nconst props = withDefaults(\n defineProps<{\n modelValue: boolean\n urlList: string[]\n initialIndex?: number\n zoomRate?: number\n minScale?: number\n maxScale?: number\n hideOnClickModal?: boolean\n closeOnPressEscape?: boolean\n showProgress?: boolean\n showZoomValue?: boolean\n zIndex?: number\n /** i18n: aria-label кнопки закрытия. */\n closeLabel?: string\n /** i18n: aria-label кнопки «предыдущее изображение». */\n prevLabel?: string\n /** i18n: aria-label кнопки «следующее изображение». */\n nextLabel?: string\n /** i18n: aria-label кнопки «увеличить». */\n zoomInLabel?: string\n /** i18n: aria-label кнопки «уменьшить». */\n zoomOutLabel?: string\n /** i18n: aria-label кнопки «сбросить масштаб». */\n resetZoomLabel?: string\n /** i18n: aria-label кнопки «повернуть влево». */\n rotateLeftLabel?: string\n /** i18n: aria-label кнопки «повернуть вправо». */\n rotateRightLabel?: string\n /** i18n: текст в пустом состоянии (нет изображений). */\n emptyText?: string\n }>(),\n {\n initialIndex: 0,\n zoomRate: 1.2,\n minScale: 0.5,\n maxScale: 5,\n hideOnClickModal: false,\n closeOnPressEscape: true,\n showProgress: false,\n showZoomValue: true,\n zIndex: undefined,\n closeLabel: 'Close image viewer',\n prevLabel: 'Previous image',\n nextLabel: 'Next image',\n zoomInLabel: 'Zoom in',\n zoomOutLabel: 'Zoom out',\n resetZoomLabel: 'Reset zoom',\n rotateLeftLabel: 'Rotate left',\n rotateRightLabel: 'Rotate right',\n emptyText: 'No image',\n },\n)\n\nconst emit = defineEmits<{\n (e: 'update:modelValue', value: boolean): void\n (e: 'close'): void\n (e: 'switch', newIndex: number): void\n (e: 'rotate', deg: number): void\n}>()\n\ntype GrImageViewerToolbarActions = {\n close: () => void\n prev: () => void\n next: () => void\n zoomIn: () => void\n zoomOut: () => void\n reset: () => void\n rotateLeft: () => void\n rotateRight: () => void\n}\n\nconst DEFAULT_Z_INDEX = 2000\nconst TRAILING_ZERO_DECIMAL_RE = /\\.0$/\n\nconst open = computed(() => props.modelValue)\nconst total = computed(() => props.urlList.length)\nconst hasImages = computed(() => total.value > 0)\n\nconst currentIndex = ref(0)\nconst scale = ref(1)\nconst rotation = ref(0)\nconst offsetX = ref(0)\nconst offsetY = ref(0)\n\nconst currentUrl = computed(() => {\n if (!hasImages.value) {\n return undefined\n }\n\n return props.urlList[currentIndex.value]\n})\n\nconst displayIndex = computed(() => {\n if (!hasImages.value) {\n return 0\n }\n\n return currentIndex.value + 1\n})\n\nconst viewerStyle = computed(() => ({\n zIndex: String(Number.isFinite(props.zIndex) ? props.zIndex : DEFAULT_Z_INDEX),\n}))\n\nconst imageStyle = computed(() => ({\n transform: `translate3d(${offsetX.value}px, ${offsetY.value}px, 0) scale(${scale.value}) rotate(${rotation.value}deg)`,\n}))\n\nconst zoomValueText = computed(() => {\n const zoomPercent = scale.value * 100\n\n if (Number.isInteger(zoomPercent)) {\n return String(zoomPercent)\n }\n\n return zoomPercent.toFixed(1).replace(TRAILING_ZERO_DECIMAL_RE, '')\n})\n\nfunction normalizeIndex(value: number): number {\n if (!hasImages.value) {\n return 0\n }\n\n const normalized = Number.isFinite(value) ? Math.trunc(value) : 0\n\n return ((normalized % total.value) + total.value) % total.value\n}\n\nfunction resetTransform(): void {\n scale.value = 1\n rotation.value = 0\n offsetX.value = 0\n offsetY.value = 0\n}\n\nfunction setIndex(nextIndex: number, options?: { emitSwitch?: boolean }): void {\n const normalizedIndex = normalizeIndex(nextIndex)\n\n if (normalizedIndex === currentIndex.value) {\n return\n }\n\n currentIndex.value = normalizedIndex\n resetTransform()\n\n if (options?.emitSwitch !== false) {\n emit('switch', normalizedIndex)\n }\n}\n\nfunction syncIndexFromInitial(): void {\n if (!hasImages.value) {\n currentIndex.value = 0\n return\n }\n\n currentIndex.value = normalizeIndex(props.initialIndex)\n resetTransform()\n}\n\nfunction preloadAt(index: number): void {\n const url = props.urlList[index]\n\n if (!url) {\n return\n }\n\n const image = new Image()\n image.decoding = 'async'\n image.src = url\n}\n\nfunction closeViewer(): void {\n emit('update:modelValue', false)\n emit('close')\n}\n\nfunction prev(): void {\n setIndex(currentIndex.value - 1)\n}\n\nfunction next(): void {\n setIndex(currentIndex.value + 1)\n}\n\nfunction zoomIn(): void {\n scale.value = Math.min(props.maxScale, Number((scale.value * props.zoomRate).toFixed(4)))\n}\n\nfunction zoomOut(): void {\n scale.value = Math.max(props.minScale, Number((scale.value / props.zoomRate).toFixed(4)))\n}\n\nfunction rotateLeft(): void {\n rotation.value -= 90\n emit('rotate', rotation.value)\n}\n\nfunction rotateRight(): void {\n rotation.value += 90\n emit('rotate', rotation.value)\n}\n\nconst toolbarActions: GrImageViewerToolbarActions = {\n close: closeViewer,\n prev,\n next,\n zoomIn,\n zoomOut,\n reset: resetTransform,\n rotateLeft,\n rotateRight,\n}\n\nfunction onBackdropClick(): void {\n if (!props.hideOnClickModal) {\n return\n }\n\n closeViewer()\n}\n\nfunction onKeydown(event: KeyboardEvent): void {\n switch (event.key) {\n case 'Escape':\n if (!props.closeOnPressEscape) {\n return\n }\n\n event.preventDefault()\n closeViewer()\n return\n\n case 'ArrowLeft':\n event.preventDefault()\n prev()\n return\n\n case 'ArrowRight':\n event.preventDefault()\n next()\n return\n\n case '+':\n case '=':\n case 'Add':\n event.preventDefault()\n zoomIn()\n return\n\n case '-':\n case '_':\n case 'Subtract':\n event.preventDefault()\n zoomOut()\n return\n\n case '0':\n event.preventDefault()\n resetTransform()\n break\n\n default:\n }\n}\n\nwatch(\n () => props.modelValue,\n (isOpen) => {\n if (!isOpen) {\n return\n }\n\n syncIndexFromInitial()\n },\n { immediate: true },\n)\n\nwatch(\n () => props.initialIndex,\n () => {\n if (!props.modelValue) {\n return\n }\n\n syncIndexFromInitial()\n },\n)\n\nwatch(\n () => props.urlList,\n () => {\n if (!props.modelValue) {\n return\n }\n\n syncIndexFromInitial()\n },\n { deep: true },\n)\n\nwatch(\n currentIndex,\n (index) => {\n if (!hasImages.value || total.value < 2) {\n return\n }\n\n preloadAt(normalizeIndex(index - 1))\n preloadAt(normalizeIndex(index + 1))\n },\n { immediate: true },\n)\n</script>\n\n<template>\n <teleport to=\"body\">\n <TransitionRoot :show=\"open\" as=\"template\">\n <Dialog\n as=\"div\"\n class=\"fixed inset-0\"\n :style=\"viewerStyle\"\n role=\"dialog\"\n aria-modal=\"true\"\n :static=\"true\"\n @keydown=\"onKeydown\"\n >\n <div class=\"fixed inset-0 overflow-hidden\">\n <TransitionChild\n as=\"template\"\n enter=\"duration-150 ease-out\"\n enter-from=\"opacity-0\"\n enter-to=\"opacity-100\"\n leave=\"duration-120 ease-in\"\n leave-from=\"opacity-100\"\n leave-to=\"opacity-0\"\n >\n <div\n data-ds-image-viewer-overlay\n class=\"absolute inset-0 bg-black/60 backdrop-blur-sm\"\n aria-hidden=\"true\"\n @click=\"onBackdropClick\"\n />\n </TransitionChild>\n\n <TransitionChild\n as=\"template\"\n enter=\"duration-180 ease-out\"\n enter-from=\"opacity-0 scale-98\"\n enter-to=\"opacity-100 scale-100\"\n leave=\"duration-130 ease-in\"\n leave-from=\"opacity-100 scale-100\"\n leave-to=\"opacity-0 scale-98\"\n >\n <DialogPanel\n data-ds-image-viewer-panel\n class=\"relative z-10 h-full w-full outline-none\"\n tabindex=\"-1\"\n >\n <div class=\"pointer-events-none absolute inset-x-0 top-0 z-30 px-3 py-3 sm:px-6\">\n <div class=\"pointer-events-auto flex items-center justify-between gap-3\">\n <div class=\"flex items-center gap-2\">\n <div\n v-if=\"showProgress\"\n data-ds-image-viewer-progress\n class=\"rounded-full bg-black/35 px-3 py-1 text-xs font-600 text-white/95 sm:text-sm\"\n >\n {{ displayIndex }} / {{ total }}\n </div>\n\n <div\n v-if=\"showZoomValue\"\n data-ds-image-viewer-zoom-value\n class=\"rounded-full bg-black/35 px-3 py-1 text-xs font-700 text-white/95 sm:text-sm\"\n >\n {{ zoomValueText }}%\n </div>\n </div>\n\n <button\n type=\"button\"\n data-ds-image-viewer-close\n :aria-label=\"closeLabel\"\n class=\"h-11 w-11 flex items-center justify-center rounded-full border border-white/20 bg-black/35 text-white transition-colors hover:bg-black/55 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80\"\n @click=\"closeViewer\"\n >\n <span aria-hidden=\"true\" class=\"text-lg leading-none\">✕</span>\n </button>\n </div>\n </div>\n\n <div class=\"relative z-10 h-full w-full flex items-center justify-center px-16 py-16 sm:px-24 sm:py-20\">\n <button\n v-if=\"total > 1\"\n type=\"button\"\n data-ds-image-viewer-prev\n :aria-label=\"prevLabel\"\n class=\"absolute left-3 top-1/2 z-20 h-12 w-12 -translate-y-1/2 flex items-center justify-center rounded-full border border-white/20 bg-black/35 text-white transition-colors hover:bg-black/55 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80 sm:left-6\"\n @click=\"prev\"\n >\n <span aria-hidden=\"true\" class=\"text-xl leading-none\">‹</span>\n </button>\n\n <button\n v-if=\"total > 1\"\n type=\"button\"\n data-ds-image-viewer-next\n :aria-label=\"nextLabel\"\n class=\"absolute right-3 top-1/2 z-20 h-12 w-12 -translate-y-1/2 flex items-center justify-center rounded-full border border-white/20 bg-black/35 text-white transition-colors hover:bg-black/55 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80 sm:right-6\"\n @click=\"next\"\n >\n <span aria-hidden=\"true\" class=\"text-xl leading-none\">›</span>\n </button>\n\n <div class=\"h-full w-full flex items-center justify-center\">\n <img\n v-if=\"currentUrl\"\n data-ds-image-viewer-image\n :src=\"currentUrl\"\n alt=\"\"\n draggable=\"false\"\n class=\"max-h-full max-w-full select-none object-contain will-change-transform transition-transform duration-150 ease-out\"\n :style=\"imageStyle\"\n >\n\n <div\n v-else\n class=\"rounded-[var(--ds-radius-xl)] border border-white/20 bg-black/25 px-4 py-3 text-sm text-white/80\"\n >\n {{ emptyText }}\n </div>\n </div>\n </div>\n\n <div class=\"pointer-events-none absolute inset-x-0 bottom-0 z-30 px-3 pb-3 sm:px-6 sm:pb-6\">\n <div class=\"pointer-events-auto mx-auto w-full max-w-max\">\n <slot\n name=\"toolbar\"\n :index=\"currentIndex\"\n :display-index=\"displayIndex\"\n :total=\"total\"\n :scale=\"scale\"\n :rotation=\"rotation\"\n :actions=\"toolbarActions\"\n >\n <div class=\"rounded-full border border-white/20 bg-black/35 p-1 backdrop-blur-sm flex items-center gap-1\">\n <button\n type=\"button\"\n data-ds-image-viewer-zoom-out\n :aria-label=\"zoomOutLabel\"\n class=\"h-11 min-w-11 px-2 flex items-center justify-center rounded-full text-sm font-600 text-white transition-colors hover:bg-white/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80\"\n @click=\"zoomOut\"\n >\n −\n </button>\n\n <button\n type=\"button\"\n data-ds-image-viewer-zoom-reset\n :aria-label=\"resetZoomLabel\"\n class=\"h-11 min-w-11 px-3 flex items-center justify-center rounded-full text-xs font-700 text-white transition-colors hover:bg-white/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80\"\n @click=\"resetTransform\"\n >\n 100%\n </button>\n\n <button\n type=\"button\"\n data-ds-image-viewer-zoom-in\n :aria-label=\"zoomInLabel\"\n class=\"h-11 min-w-11 px-2 flex items-center justify-center rounded-full text-sm font-600 text-white transition-colors hover:bg-white/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80\"\n @click=\"zoomIn\"\n >\n +\n </button>\n\n <template v-if=\"$slots['toolbar-actions']\">\n <div class=\"mx-1 h-6 w-px bg-white/25\" aria-hidden=\"true\" />\n\n <slot\n name=\"toolbar-actions\"\n :index=\"currentIndex\"\n :display-index=\"displayIndex\"\n :total=\"total\"\n :scale=\"scale\"\n :rotation=\"rotation\"\n :actions=\"toolbarActions\"\n />\n\n <div class=\"mx-1 h-6 w-px bg-white/25\" aria-hidden=\"true\" />\n </template>\n\n <div v-else class=\"mx-1 h-6 w-px bg-white/25\" aria-hidden=\"true\" />\n\n <button\n type=\"button\"\n data-ds-image-viewer-rotate-left\n :aria-label=\"rotateLeftLabel\"\n class=\"h-11 min-w-11 px-2 flex items-center justify-center rounded-full text-sm font-600 text-white transition-colors hover:bg-white/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80\"\n @click=\"rotateLeft\"\n >\n ↺\n </button>\n\n <button\n type=\"button\"\n data-ds-image-viewer-rotate-right\n :aria-label=\"rotateRightLabel\"\n class=\"h-11 min-w-11 px-2 flex items-center justify-center rounded-full text-sm font-600 text-white transition-colors hover:bg-white/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80\"\n @click=\"rotateRight\"\n >\n ↻\n </button>\n </div>\n </slot>\n </div>\n </div>\n </DialogPanel>\n </TransitionChild>\n </div>\n </Dialog>\n </TransitionRoot>\n </teleport>\n</template>","<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport { Dialog, DialogPanel, TransitionChild, TransitionRoot } from '@headlessui/vue'\n\n/**\n * Usage:\n *\n * <GrImageViewer\n * v-model=\"open\"\n * :url-list=\"images\"\n * :initial-index=\"0\"\n * show-progress\n * hide-on-click-modal\n * />\n */\nconst props = withDefaults(\n defineProps<{\n modelValue: boolean\n urlList: string[]\n initialIndex?: number\n zoomRate?: number\n minScale?: number\n maxScale?: number\n hideOnClickModal?: boolean\n closeOnPressEscape?: boolean\n showProgress?: boolean\n showZoomValue?: boolean\n zIndex?: number\n /** i18n: aria-label кнопки закрытия. */\n closeLabel?: string\n /** i18n: aria-label кнопки «предыдущее изображение». */\n prevLabel?: string\n /** i18n: aria-label кнопки «следующее изображение». */\n nextLabel?: string\n /** i18n: aria-label кнопки «увеличить». */\n zoomInLabel?: string\n /** i18n: aria-label кнопки «уменьшить». */\n zoomOutLabel?: string\n /** i18n: aria-label кнопки «сбросить масштаб». */\n resetZoomLabel?: string\n /** i18n: aria-label кнопки «повернуть влево». */\n rotateLeftLabel?: string\n /** i18n: aria-label кнопки «повернуть вправо». */\n rotateRightLabel?: string\n /** i18n: текст в пустом состоянии (нет изображений). */\n emptyText?: string\n }>(),\n {\n initialIndex: 0,\n zoomRate: 1.2,\n minScale: 0.5,\n maxScale: 5,\n hideOnClickModal: false,\n closeOnPressEscape: true,\n showProgress: false,\n showZoomValue: true,\n zIndex: undefined,\n closeLabel: 'Close image viewer',\n prevLabel: 'Previous image',\n nextLabel: 'Next image',\n zoomInLabel: 'Zoom in',\n zoomOutLabel: 'Zoom out',\n resetZoomLabel: 'Reset zoom',\n rotateLeftLabel: 'Rotate left',\n rotateRightLabel: 'Rotate right',\n emptyText: 'No image',\n },\n)\n\nconst emit = defineEmits<{\n (e: 'update:modelValue', value: boolean): void\n (e: 'close'): void\n (e: 'switch', newIndex: number): void\n (e: 'rotate', deg: number): void\n}>()\n\ntype GrImageViewerToolbarActions = {\n close: () => void\n prev: () => void\n next: () => void\n zoomIn: () => void\n zoomOut: () => void\n reset: () => void\n rotateLeft: () => void\n rotateRight: () => void\n}\n\nconst DEFAULT_Z_INDEX = 2000\nconst TRAILING_ZERO_DECIMAL_RE = /\\.0$/\n\nconst open = computed(() => props.modelValue)\nconst total = computed(() => props.urlList.length)\nconst hasImages = computed(() => total.value > 0)\n\nconst currentIndex = ref(0)\nconst scale = ref(1)\nconst rotation = ref(0)\nconst offsetX = ref(0)\nconst offsetY = ref(0)\n\nconst currentUrl = computed(() => {\n if (!hasImages.value) {\n return undefined\n }\n\n return props.urlList[currentIndex.value]\n})\n\nconst displayIndex = computed(() => {\n if (!hasImages.value) {\n return 0\n }\n\n return currentIndex.value + 1\n})\n\nconst viewerStyle = computed(() => ({\n zIndex: String(Number.isFinite(props.zIndex) ? props.zIndex : DEFAULT_Z_INDEX),\n}))\n\nconst imageStyle = computed(() => ({\n transform: `translate3d(${offsetX.value}px, ${offsetY.value}px, 0) scale(${scale.value}) rotate(${rotation.value}deg)`,\n}))\n\nconst zoomValueText = computed(() => {\n const zoomPercent = scale.value * 100\n\n if (Number.isInteger(zoomPercent)) {\n return String(zoomPercent)\n }\n\n return zoomPercent.toFixed(1).replace(TRAILING_ZERO_DECIMAL_RE, '')\n})\n\nfunction normalizeIndex(value: number): number {\n if (!hasImages.value) {\n return 0\n }\n\n const normalized = Number.isFinite(value) ? Math.trunc(value) : 0\n\n return ((normalized % total.value) + total.value) % total.value\n}\n\nfunction resetTransform(): void {\n scale.value = 1\n rotation.value = 0\n offsetX.value = 0\n offsetY.value = 0\n}\n\nfunction setIndex(nextIndex: number, options?: { emitSwitch?: boolean }): void {\n const normalizedIndex = normalizeIndex(nextIndex)\n\n if (normalizedIndex === currentIndex.value) {\n return\n }\n\n currentIndex.value = normalizedIndex\n resetTransform()\n\n if (options?.emitSwitch !== false) {\n emit('switch', normalizedIndex)\n }\n}\n\nfunction syncIndexFromInitial(): void {\n if (!hasImages.value) {\n currentIndex.value = 0\n return\n }\n\n currentIndex.value = normalizeIndex(props.initialIndex)\n resetTransform()\n}\n\nfunction preloadAt(index: number): void {\n const url = props.urlList[index]\n\n if (!url) {\n return\n }\n\n const image = new Image()\n image.decoding = 'async'\n image.src = url\n}\n\nfunction closeViewer(): void {\n emit('update:modelValue', false)\n emit('close')\n}\n\nfunction prev(): void {\n setIndex(currentIndex.value - 1)\n}\n\nfunction next(): void {\n setIndex(currentIndex.value + 1)\n}\n\nfunction zoomIn(): void {\n scale.value = Math.min(props.maxScale, Number((scale.value * props.zoomRate).toFixed(4)))\n}\n\nfunction zoomOut(): void {\n scale.value = Math.max(props.minScale, Number((scale.value / props.zoomRate).toFixed(4)))\n}\n\nfunction rotateLeft(): void {\n rotation.value -= 90\n emit('rotate', rotation.value)\n}\n\nfunction rotateRight(): void {\n rotation.value += 90\n emit('rotate', rotation.value)\n}\n\nconst toolbarActions: GrImageViewerToolbarActions = {\n close: closeViewer,\n prev,\n next,\n zoomIn,\n zoomOut,\n reset: resetTransform,\n rotateLeft,\n rotateRight,\n}\n\nfunction onBackdropClick(): void {\n if (!props.hideOnClickModal) {\n return\n }\n\n closeViewer()\n}\n\nfunction onKeydown(event: KeyboardEvent): void {\n switch (event.key) {\n case 'Escape':\n if (!props.closeOnPressEscape) {\n return\n }\n\n event.preventDefault()\n closeViewer()\n return\n\n case 'ArrowLeft':\n event.preventDefault()\n prev()\n return\n\n case 'ArrowRight':\n event.preventDefault()\n next()\n return\n\n case '+':\n case '=':\n case 'Add':\n event.preventDefault()\n zoomIn()\n return\n\n case '-':\n case '_':\n case 'Subtract':\n event.preventDefault()\n zoomOut()\n return\n\n case '0':\n event.preventDefault()\n resetTransform()\n break\n\n default:\n }\n}\n\nwatch(\n () => props.modelValue,\n (isOpen) => {\n if (!isOpen) {\n return\n }\n\n syncIndexFromInitial()\n },\n { immediate: true },\n)\n\nwatch(\n () => props.initialIndex,\n () => {\n if (!props.modelValue) {\n return\n }\n\n syncIndexFromInitial()\n },\n)\n\nwatch(\n () => props.urlList,\n () => {\n if (!props.modelValue) {\n return\n }\n\n syncIndexFromInitial()\n },\n { deep: true },\n)\n\nwatch(\n currentIndex,\n (index) => {\n if (!hasImages.value || total.value < 2) {\n return\n }\n\n preloadAt(normalizeIndex(index - 1))\n preloadAt(normalizeIndex(index + 1))\n },\n { immediate: true },\n)\n</script>\n\n<template>\n <teleport to=\"body\">\n <TransitionRoot :show=\"open\" as=\"template\">\n <Dialog\n as=\"div\"\n class=\"fixed inset-0\"\n :style=\"viewerStyle\"\n role=\"dialog\"\n aria-modal=\"true\"\n :static=\"true\"\n @keydown=\"onKeydown\"\n >\n <div class=\"fixed inset-0 overflow-hidden\">\n <TransitionChild\n as=\"template\"\n enter=\"duration-150 ease-out\"\n enter-from=\"opacity-0\"\n enter-to=\"opacity-100\"\n leave=\"duration-120 ease-in\"\n leave-from=\"opacity-100\"\n leave-to=\"opacity-0\"\n >\n <div\n data-ds-image-viewer-overlay\n class=\"absolute inset-0 bg-black/60 backdrop-blur-sm\"\n aria-hidden=\"true\"\n @click=\"onBackdropClick\"\n />\n </TransitionChild>\n\n <TransitionChild\n as=\"template\"\n enter=\"duration-180 ease-out\"\n enter-from=\"opacity-0 scale-98\"\n enter-to=\"opacity-100 scale-100\"\n leave=\"duration-130 ease-in\"\n leave-from=\"opacity-100 scale-100\"\n leave-to=\"opacity-0 scale-98\"\n >\n <DialogPanel\n data-ds-image-viewer-panel\n class=\"relative z-10 h-full w-full outline-none\"\n tabindex=\"-1\"\n >\n <div class=\"pointer-events-none absolute inset-x-0 top-0 z-30 px-3 py-3 sm:px-6\">\n <div class=\"pointer-events-auto flex items-center justify-between gap-3\">\n <div class=\"flex items-center gap-2\">\n <div\n v-if=\"showProgress\"\n data-ds-image-viewer-progress\n class=\"rounded-full bg-black/35 px-3 py-1 text-xs font-600 text-white/95 sm:text-sm\"\n >\n {{ displayIndex }} / {{ total }}\n </div>\n\n <div\n v-if=\"showZoomValue\"\n data-ds-image-viewer-zoom-value\n class=\"rounded-full bg-black/35 px-3 py-1 text-xs font-700 text-white/95 sm:text-sm\"\n >\n {{ zoomValueText }}%\n </div>\n </div>\n\n <button\n type=\"button\"\n data-ds-image-viewer-close\n :aria-label=\"closeLabel\"\n class=\"h-11 w-11 flex items-center justify-center rounded-full border border-white/20 bg-black/35 text-white transition-colors hover:bg-black/55 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80\"\n @click=\"closeViewer\"\n >\n <span aria-hidden=\"true\" class=\"text-lg leading-none\">✕</span>\n </button>\n </div>\n </div>\n\n <div class=\"relative z-10 h-full w-full flex items-center justify-center px-16 py-16 sm:px-24 sm:py-20\">\n <button\n v-if=\"total > 1\"\n type=\"button\"\n data-ds-image-viewer-prev\n :aria-label=\"prevLabel\"\n class=\"absolute left-3 top-1/2 z-20 h-12 w-12 -translate-y-1/2 flex items-center justify-center rounded-full border border-white/20 bg-black/35 text-white transition-colors hover:bg-black/55 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80 sm:left-6\"\n @click=\"prev\"\n >\n <span aria-hidden=\"true\" class=\"text-xl leading-none\">‹</span>\n </button>\n\n <button\n v-if=\"total > 1\"\n type=\"button\"\n data-ds-image-viewer-next\n :aria-label=\"nextLabel\"\n class=\"absolute right-3 top-1/2 z-20 h-12 w-12 -translate-y-1/2 flex items-center justify-center rounded-full border border-white/20 bg-black/35 text-white transition-colors hover:bg-black/55 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80 sm:right-6\"\n @click=\"next\"\n >\n <span aria-hidden=\"true\" class=\"text-xl leading-none\">›</span>\n </button>\n\n <div class=\"h-full w-full flex items-center justify-center\">\n <img\n v-if=\"currentUrl\"\n data-ds-image-viewer-image\n :src=\"currentUrl\"\n alt=\"\"\n draggable=\"false\"\n class=\"max-h-full max-w-full select-none object-contain will-change-transform transition-transform duration-150 ease-out\"\n :style=\"imageStyle\"\n >\n\n <div\n v-else\n class=\"rounded-[var(--ds-radius-xl)] border border-white/20 bg-black/25 px-4 py-3 text-sm text-white/80\"\n >\n {{ emptyText }}\n </div>\n </div>\n </div>\n\n <div class=\"pointer-events-none absolute inset-x-0 bottom-0 z-30 px-3 pb-3 sm:px-6 sm:pb-6\">\n <div class=\"pointer-events-auto mx-auto w-full max-w-max\">\n <slot\n name=\"toolbar\"\n :index=\"currentIndex\"\n :display-index=\"displayIndex\"\n :total=\"total\"\n :scale=\"scale\"\n :rotation=\"rotation\"\n :actions=\"toolbarActions\"\n >\n <div class=\"rounded-full border border-white/20 bg-black/35 p-1 backdrop-blur-sm flex items-center gap-1\">\n <button\n type=\"button\"\n data-ds-image-viewer-zoom-out\n :aria-label=\"zoomOutLabel\"\n class=\"h-11 min-w-11 px-2 flex items-center justify-center rounded-full text-sm font-600 text-white transition-colors hover:bg-white/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80\"\n @click=\"zoomOut\"\n >\n −\n </button>\n\n <button\n type=\"button\"\n data-ds-image-viewer-zoom-reset\n :aria-label=\"resetZoomLabel\"\n class=\"h-11 min-w-11 px-3 flex items-center justify-center rounded-full text-xs font-700 text-white transition-colors hover:bg-white/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80\"\n @click=\"resetTransform\"\n >\n 100%\n </button>\n\n <button\n type=\"button\"\n data-ds-image-viewer-zoom-in\n :aria-label=\"zoomInLabel\"\n class=\"h-11 min-w-11 px-2 flex items-center justify-center rounded-full text-sm font-600 text-white transition-colors hover:bg-white/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80\"\n @click=\"zoomIn\"\n >\n +\n </button>\n\n <template v-if=\"$slots['toolbar-actions']\">\n <div class=\"mx-1 h-6 w-px bg-white/25\" aria-hidden=\"true\" />\n\n <slot\n name=\"toolbar-actions\"\n :index=\"currentIndex\"\n :display-index=\"displayIndex\"\n :total=\"total\"\n :scale=\"scale\"\n :rotation=\"rotation\"\n :actions=\"toolbarActions\"\n />\n\n <div class=\"mx-1 h-6 w-px bg-white/25\" aria-hidden=\"true\" />\n </template>\n\n <div v-else class=\"mx-1 h-6 w-px bg-white/25\" aria-hidden=\"true\" />\n\n <button\n type=\"button\"\n data-ds-image-viewer-rotate-left\n :aria-label=\"rotateLeftLabel\"\n class=\"h-11 min-w-11 px-2 flex items-center justify-center rounded-full text-sm font-600 text-white transition-colors hover:bg-white/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80\"\n @click=\"rotateLeft\"\n >\n ↺\n </button>\n\n <button\n type=\"button\"\n data-ds-image-viewer-rotate-right\n :aria-label=\"rotateRightLabel\"\n class=\"h-11 min-w-11 px-2 flex items-center justify-center rounded-full text-sm font-600 text-white transition-colors hover:bg-white/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80\"\n @click=\"rotateRight\"\n >\n ↻\n </button>\n </div>\n </slot>\n </div>\n </div>\n </DialogPanel>\n </TransitionChild>\n </div>\n </Dialog>\n </TransitionRoot>\n </teleport>\n</template>"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuFA,IAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAxExB,MAAM,QAAQ;EAsDd,MAAM,OAAO;EAmBb,MAAM,2BAA2B;EAEjC,MAAM,OAAO,eAAe,MAAM,WAAU;EAC5C,MAAM,QAAQ,eAAe,MAAM,QAAQ,OAAM;EACjD,MAAM,YAAY,eAAe,MAAM,QAAQ,EAAC;EAEhD,MAAM,eAAe,IAAI,EAAC;EAC1B,MAAM,QAAQ,IAAI,EAAC;EACnB,MAAM,WAAW,IAAI,EAAC;EACtB,MAAM,UAAU,IAAI,EAAC;EACrB,MAAM,UAAU,IAAI,EAAC;EAErB,MAAM,aAAa,eAAe;AAChC,OAAI,CAAC,UAAU,MACb;AAGF,UAAO,MAAM,QAAQ,aAAa;IACnC;EAED,MAAM,eAAe,eAAe;AAClC,OAAI,CAAC,UAAU,MACb,QAAO;AAGT,UAAO,aAAa,QAAQ;IAC7B;EAED,MAAM,cAAc,gBAAgB,EAClC,QAAQ,OAAO,OAAO,SAAS,MAAM,OAAO,GAAG,MAAM,SAAS,gBAAgB,EAC/E,EAAC;EAEF,MAAM,aAAa,gBAAgB,EACjC,WAAW,eAAe,QAAQ,MAAM,MAAM,QAAQ,MAAM,eAAe,MAAM,MAAM,WAAW,SAAS,MAAM,OAClH,EAAC;EAEF,MAAM,gBAAgB,eAAe;GACnC,MAAM,cAAc,MAAM,QAAQ;AAElC,OAAI,OAAO,UAAU,YAAY,CAC/B,QAAO,OAAO,YAAW;AAG3B,UAAO,YAAY,QAAQ,EAAE,CAAC,QAAQ,0BAA0B,GAAE;IACnE;EAED,SAAS,eAAe,OAAuB;AAC7C,OAAI,CAAC,UAAU,MACb,QAAO;AAKT,YAFmB,OAAO,SAAS,MAAM,GAAG,KAAK,MAAM,MAAM,GAAG,KAE1C,MAAM,QAAS,MAAM,SAAS,MAAM;;EAG5D,SAAS,iBAAuB;AAC9B,SAAM,QAAQ;AACd,YAAS,QAAQ;AACjB,WAAQ,QAAQ;AAChB,WAAQ,QAAQ;;EAGlB,SAAS,SAAS,WAAmB,SAA0C;GAC7E,MAAM,kBAAkB,eAAe,UAAS;AAEhD,OAAI,oBAAoB,aAAa,MACnC;AAGF,gBAAa,QAAQ;AACrB,mBAAe;AAEf,OAAI,SAAS,eAAe,MAC1B,MAAK,UAAU,gBAAe;;EAIlC,SAAS,uBAA6B;AACpC,OAAI,CAAC,UAAU,OAAO;AACpB,iBAAa,QAAQ;AACrB;;AAGF,gBAAa,QAAQ,eAAe,MAAM,aAAY;AACtD,mBAAe;;EAGjB,SAAS,UAAU,OAAqB;GACtC,MAAM,MAAM,MAAM,QAAQ;AAE1B,OAAI,CAAC,IACH;GAGF,MAAM,QAAQ,IAAI,OAAM;AACxB,SAAM,WAAW;AACjB,SAAM,MAAM;;EAGd,SAAS,cAAoB;AAC3B,QAAK,qBAAqB,MAAK;AAC/B,QAAK,QAAO;;EAGd,SAAS,OAAa;AACpB,YAAS,aAAa,QAAQ,EAAC;;EAGjC,SAAS,OAAa;AACpB,YAAS,aAAa,QAAQ,EAAC;;EAGjC,SAAS,SAAe;AACtB,SAAM,QAAQ,KAAK,IAAI,MAAM,UAAU,QAAQ,MAAM,QAAQ,MAAM,UAAU,QAAQ,EAAE,CAAC,CAAA;;EAG1F,SAAS,UAAgB;AACvB,SAAM,QAAQ,KAAK,IAAI,MAAM,UAAU,QAAQ,MAAM,QAAQ,MAAM,UAAU,QAAQ,EAAE,CAAC,CAAA;;EAG1F,SAAS,aAAmB;AAC1B,YAAS,SAAS;AAClB,QAAK,UAAU,SAAS,MAAK;;EAG/B,SAAS,cAAoB;AAC3B,YAAS,SAAS;AAClB,QAAK,UAAU,SAAS,MAAK;;EAG/B,MAAM,iBAA8C;GAClD,OAAO;GACP;GACA;GACA;GACA;GACA,OAAO;GACP;GACA;GACF;EAEA,SAAS,kBAAwB;AAC/B,OAAI,CAAC,MAAM,iBACT;AAGF,gBAAY;;EAGd,SAAS,UAAU,OAA4B;AAC7C,WAAQ,MAAM,KAAd;IACE,KAAK;AACH,SAAI,CAAC,MAAM,mBACT;AAGF,WAAM,gBAAe;AACrB,kBAAY;AACZ;IAEF,KAAK;AACH,WAAM,gBAAe;AACrB,WAAK;AACL;IAEF,KAAK;AACH,WAAM,gBAAe;AACrB,WAAK;AACL;IAEF,KAAK;IACL,KAAK;IACL,KAAK;AACH,WAAM,gBAAe;AACrB,aAAO;AACP;IAEF,KAAK;IACL,KAAK;IACL,KAAK;AACH,WAAM,gBAAe;AACrB,cAAQ;AACR;IAEF,KAAK;AACH,WAAM,gBAAe;AACrB,qBAAe;AACf;IAEF;;;AAIJ,cACQ,MAAM,aACX,WAAW;AACV,OAAI,CAAC,OACH;AAGF,yBAAqB;KAEvB,EAAE,WAAW,MAAM,CACrB;AAEA,cACQ,MAAM,oBACN;AACJ,OAAI,CAAC,MAAM,WACT;AAGF,yBAAqB;IAEzB;AAEA,cACQ,MAAM,eACN;AACJ,OAAI,CAAC,MAAM,WACT;AAGF,yBAAqB;KAEvB,EAAE,MAAM,MAAM,CAChB;AAEA,QACE,eACC,UAAU;AACT,OAAI,CAAC,UAAU,SAAS,MAAM,QAAQ,EACpC;AAGF,aAAU,eAAe,QAAQ,EAAE,CAAA;AACnC,aAAU,eAAe,QAAQ,EAAE,CAAA;KAErC,EAAE,WAAW,MAAM,CACrB;;uBAIE,YA8MW,UAAA,EA9MD,IAAG,QAAM,EAAA,CACjB,YA4MiB,MAAA,GAAA,EAAA;IA5MA,MAAM,KAAA;IAAM,IAAG;;2BA2MrB,CA1MT,YA0MS,MAAA,GAAA,EAAA;KAzMP,IAAG;KACH,OAAM;KACL,OAAK,eAAE,YAAA,MAAW;KACnB,MAAK;KACL,cAAW;KACV,QAAQ;KACC;;4BAkMJ,CAhMN,mBAgMM,OAhMN,YAgMM,CA/LJ,YAekB,MAAA,GAAA,EAAA;MAdhB,IAAG;MACH,OAAM;MACN,cAAW;MACX,YAAS;MACT,OAAM;MACN,cAAW;MACX,YAAS;;6BAOP,CALF,mBAKE,OAAA;OAJA,gCAAA;OACA,OAAM;OACN,eAAY;OACX,SAAO;;;SAIZ,YA6KkB,MAAA,GAAA,EAAA;MA5KhB,IAAG;MACH,OAAM;MACN,cAAW;MACX,YAAS;MACT,OAAM;MACN,cAAW;MACX,YAAS;;6BAqKK,CAnKd,YAmKc,MAAA,GAAA,EAAA;OAlKZ,8BAAA;OACA,OAAM;OACN,UAAS;;8BAgCH;QA9BN,mBA8BM,OA9BN,YA8BM,CA7BJ,mBA4BM,OA5BN,YA4BM,CA3BJ,mBAgBM,OAhBN,YAgBM,CAdI,QAAA,gBAAA,WAAA,EADR,mBAMM,OANN,YAMM,gBADD,aAAA,MAAY,GAAG,QAAG,gBAAG,MAAA,MAAK,EAAA,EAAA,IAAA,mBAAA,IAAA,KAAA,EAIvB,QAAA,iBAAA,WAAA,EADR,mBAMM,OANN,YAMM,gBADD,cAAA,MAAa,GAAG,MACrB,EAAA,IAAA,mBAAA,IAAA,KAAA,CAAA,CAAA,EAGF,mBAQS,UAAA;SAPP,MAAK;SACL,8BAAA;SACC,cAAY,QAAA;SACb,OAAM;SACL,SAAO;0CAER,mBAA8D,QAAA;SAAxD,eAAY;SAAO,OAAM;WAAuB,KAAC,GAAA,CAAA,EAAA,EAAA,GAAA,WAAA,CAAA,CAAA,CAAA,CAAA;QAK7D,mBAyCM,OAzCN,YAyCM;SAvCI,MAAA,QAAK,KAAA,WAAA,EADb,mBASS,UAAA;;UAPP,MAAK;UACL,6BAAA;UACC,cAAY,QAAA;UACb,OAAM;UACL,SAAO;2CAER,mBAA8D,QAAA;UAAxD,eAAY;UAAO,OAAM;YAAuB,KAAC,GAAA,CAAA,EAAA,EAAA,GAAA,WAAA,IAAA,mBAAA,IAAA,KAAA;SAIjD,MAAA,QAAK,KAAA,WAAA,EADb,mBASS,UAAA;;UAPP,MAAK;UACL,6BAAA;UACC,cAAY,QAAA;UACb,OAAM;UACL,SAAO;2CAER,mBAA8D,QAAA;UAAxD,eAAY;UAAO,OAAM;YAAuB,KAAC,GAAA,CAAA,EAAA,EAAA,GAAA,YAAA,IAAA,mBAAA,IAAA,KAAA;SAGzD,mBAiBM,OAjBN,aAiBM,CAfI,WAAA,SAAA,WAAA,EADR,mBAQC,OAAA;;UANC,8BAAA;UACC,KAAK,WAAA;UACN,KAAI;UACJ,WAAU;UACV,OAAM;UACL,OAAK,eAAE,WAAA,MAAU;oDAGpB,mBAKM,OALN,aAKM,gBADD,QAAA,UAAS,EAAA,EAAA,EAAA,CAAA;;QAKlB,mBAkFM,OAlFN,aAkFM,CAjFJ,mBAgFM,OAhFN,aAgFM,CA/EJ,WA8EO,KAAA,QAAA,WAAA;SA5EJ,OAAO,aAAA;SACP,cAAe,aAAA;SACf,OAAO,MAAA;SACP,OAAO,MAAA;SACP,UAAU,SAAA;SACV,SAAS;iBAuEL,CArEL,mBAoEM,OApEN,aAoEM;SAnEJ,mBAQS,UAAA;UAPP,MAAK;UACL,iCAAA;UACC,cAAY,QAAA;UACb,OAAM;UACL,SAAO;YACT,OAED,GAAA,YAAA;SAEA,mBAQS,UAAA;UAPP,MAAK;UACL,mCAAA;UACC,cAAY,QAAA;UACb,OAAM;UACL,SAAO;YACT,UAED,GAAA,YAAA;SAEA,mBAQS,UAAA;UAPP,MAAK;UACL,gCAAA;UACC,cAAY,QAAA;UACb,OAAM;UACL,SAAO;YACT,OAED,GAAA,YAAA;SAEgBA,KAAAA,OAAM,sBAAA,WAAA,EAAtB,mBAcW,UAAA,EAAA,KAAA,GAAA,EAAA;oCAbT,mBAA4D,OAAA;WAAvD,OAAM;WAA4B,eAAY;;UAEnD,WAQE,KAAA,QAAA,mBAAA;WANC,OAAO,aAAA;WACP,cAAe,aAAA;WACf,OAAO,MAAA;WACP,OAAO,MAAA;WACP,UAAU,SAAA;WACV,SAAS;;oCAGZ,mBAA4D,OAAA;WAAvD,OAAM;WAA4B,eAAY;;iCAGrD,mBAAmE,OAAnE,YAAmE;SAEnE,mBAQS,UAAA;UAPP,MAAK;UACL,oCAAA;UACC,cAAY,QAAA;UACb,OAAM;UACL,SAAO;YACT,OAED,GAAA,YAAA;SAEA,mBAQS,UAAA;UAPP,MAAK;UACL,qCAAA;UACC,cAAY,QAAA;UACb,OAAM;UACL,SAAO;YACT,OAED,GAAA,YAAA"}
1
+ {"version":3,"file":"GrImageViewer-Cn1X-sWS.js","names":["$slots"],"sources":["../../../../src/components/GrImageViewer/GrImageViewer.vue","../../../../src/components/GrImageViewer/GrImageViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport { Dialog, DialogPanel, TransitionChild, TransitionRoot } from '@headlessui/vue'\n\n/**\n * Usage:\n *\n * <GrImageViewer\n * v-model=\"open\"\n * :url-list=\"images\"\n * :initial-index=\"0\"\n * show-progress\n * hide-on-click-modal\n * />\n */\nconst props = withDefaults(\n defineProps<{\n modelValue: boolean\n urlList: string[]\n initialIndex?: number\n zoomRate?: number\n minScale?: number\n maxScale?: number\n hideOnClickModal?: boolean\n closeOnPressEscape?: boolean\n showProgress?: boolean\n showZoomValue?: boolean\n zIndex?: number\n /** i18n: aria-label кнопки закрытия. */\n closeLabel?: string\n /** i18n: aria-label кнопки «предыдущее изображение». */\n prevLabel?: string\n /** i18n: aria-label кнопки «следующее изображение». */\n nextLabel?: string\n /** i18n: aria-label кнопки «увеличить». */\n zoomInLabel?: string\n /** i18n: aria-label кнопки «уменьшить». */\n zoomOutLabel?: string\n /** i18n: aria-label кнопки «сбросить масштаб». */\n resetZoomLabel?: string\n /** i18n: aria-label кнопки «повернуть влево». */\n rotateLeftLabel?: string\n /** i18n: aria-label кнопки «повернуть вправо». */\n rotateRightLabel?: string\n /** i18n: текст в пустом состоянии (нет изображений). */\n emptyText?: string\n }>(),\n {\n initialIndex: 0,\n zoomRate: 1.2,\n minScale: 0.5,\n maxScale: 5,\n hideOnClickModal: false,\n closeOnPressEscape: true,\n showProgress: false,\n showZoomValue: true,\n zIndex: undefined,\n closeLabel: 'Close image viewer',\n prevLabel: 'Previous image',\n nextLabel: 'Next image',\n zoomInLabel: 'Zoom in',\n zoomOutLabel: 'Zoom out',\n resetZoomLabel: 'Reset zoom',\n rotateLeftLabel: 'Rotate left',\n rotateRightLabel: 'Rotate right',\n emptyText: 'No image',\n },\n)\n\nconst emit = defineEmits<{\n (e: 'update:modelValue', value: boolean): void\n (e: 'close'): void\n (e: 'switch', newIndex: number): void\n (e: 'rotate', deg: number): void\n}>()\n\ntype GrImageViewerToolbarActions = {\n close: () => void\n prev: () => void\n next: () => void\n zoomIn: () => void\n zoomOut: () => void\n reset: () => void\n rotateLeft: () => void\n rotateRight: () => void\n}\n\nconst DEFAULT_Z_INDEX = 2000\nconst TRAILING_ZERO_DECIMAL_RE = /\\.0$/\n\nconst open = computed(() => props.modelValue)\nconst total = computed(() => props.urlList.length)\nconst hasImages = computed(() => total.value > 0)\n\nconst currentIndex = ref(0)\nconst scale = ref(1)\nconst rotation = ref(0)\nconst offsetX = ref(0)\nconst offsetY = ref(0)\n\nconst currentUrl = computed(() => {\n if (!hasImages.value) {\n return undefined\n }\n\n return props.urlList[currentIndex.value]\n})\n\nconst displayIndex = computed(() => {\n if (!hasImages.value) {\n return 0\n }\n\n return currentIndex.value + 1\n})\n\nconst viewerStyle = computed(() => ({\n zIndex: String(Number.isFinite(props.zIndex) ? props.zIndex : DEFAULT_Z_INDEX),\n}))\n\nconst imageStyle = computed(() => ({\n transform: `translate3d(${offsetX.value}px, ${offsetY.value}px, 0) scale(${scale.value}) rotate(${rotation.value}deg)`,\n}))\n\nconst zoomValueText = computed(() => {\n const zoomPercent = scale.value * 100\n\n if (Number.isInteger(zoomPercent)) {\n return String(zoomPercent)\n }\n\n return zoomPercent.toFixed(1).replace(TRAILING_ZERO_DECIMAL_RE, '')\n})\n\nfunction normalizeIndex(value: number): number {\n if (!hasImages.value) {\n return 0\n }\n\n const normalized = Number.isFinite(value) ? Math.trunc(value) : 0\n\n return ((normalized % total.value) + total.value) % total.value\n}\n\nfunction resetTransform(): void {\n scale.value = 1\n rotation.value = 0\n offsetX.value = 0\n offsetY.value = 0\n}\n\nfunction setIndex(nextIndex: number, options?: { emitSwitch?: boolean }): void {\n const normalizedIndex = normalizeIndex(nextIndex)\n\n if (normalizedIndex === currentIndex.value) {\n return\n }\n\n currentIndex.value = normalizedIndex\n resetTransform()\n\n if (options?.emitSwitch !== false) {\n emit('switch', normalizedIndex)\n }\n}\n\nfunction syncIndexFromInitial(): void {\n if (!hasImages.value) {\n currentIndex.value = 0\n return\n }\n\n currentIndex.value = normalizeIndex(props.initialIndex)\n resetTransform()\n}\n\nfunction preloadAt(index: number): void {\n const url = props.urlList[index]\n\n if (!url) {\n return\n }\n\n const image = new Image()\n image.decoding = 'async'\n image.src = url\n}\n\nfunction closeViewer(): void {\n emit('update:modelValue', false)\n emit('close')\n}\n\nfunction prev(): void {\n setIndex(currentIndex.value - 1)\n}\n\nfunction next(): void {\n setIndex(currentIndex.value + 1)\n}\n\nfunction zoomIn(): void {\n scale.value = Math.min(props.maxScale, Number((scale.value * props.zoomRate).toFixed(4)))\n}\n\nfunction zoomOut(): void {\n scale.value = Math.max(props.minScale, Number((scale.value / props.zoomRate).toFixed(4)))\n}\n\nfunction rotateLeft(): void {\n rotation.value -= 90\n emit('rotate', rotation.value)\n}\n\nfunction rotateRight(): void {\n rotation.value += 90\n emit('rotate', rotation.value)\n}\n\nconst toolbarActions: GrImageViewerToolbarActions = {\n close: closeViewer,\n prev,\n next,\n zoomIn,\n zoomOut,\n reset: resetTransform,\n rotateLeft,\n rotateRight,\n}\n\nfunction onBackdropClick(): void {\n if (!props.hideOnClickModal) {\n return\n }\n\n closeViewer()\n}\n\nfunction onKeydown(event: KeyboardEvent): void {\n switch (event.key) {\n case 'Escape':\n if (!props.closeOnPressEscape) {\n return\n }\n\n event.preventDefault()\n closeViewer()\n return\n\n case 'ArrowLeft':\n event.preventDefault()\n prev()\n return\n\n case 'ArrowRight':\n event.preventDefault()\n next()\n return\n\n case '+':\n case '=':\n case 'Add':\n event.preventDefault()\n zoomIn()\n return\n\n case '-':\n case '_':\n case 'Subtract':\n event.preventDefault()\n zoomOut()\n return\n\n case '0':\n event.preventDefault()\n resetTransform()\n break\n\n default:\n }\n}\n\nwatch(\n () => props.modelValue,\n (isOpen) => {\n if (!isOpen) {\n return\n }\n\n syncIndexFromInitial()\n },\n { immediate: true },\n)\n\nwatch(\n () => props.initialIndex,\n () => {\n if (!props.modelValue) {\n return\n }\n\n syncIndexFromInitial()\n },\n)\n\nwatch(\n () => props.urlList,\n () => {\n if (!props.modelValue) {\n return\n }\n\n syncIndexFromInitial()\n },\n { deep: true },\n)\n\nwatch(\n currentIndex,\n (index) => {\n if (!hasImages.value || total.value < 2) {\n return\n }\n\n preloadAt(normalizeIndex(index - 1))\n preloadAt(normalizeIndex(index + 1))\n },\n { immediate: true },\n)\n</script>\n\n<template>\n <teleport to=\"body\">\n <TransitionRoot :show=\"open\" as=\"template\">\n <Dialog\n as=\"div\"\n class=\"fixed inset-0\"\n :style=\"viewerStyle\"\n role=\"dialog\"\n aria-modal=\"true\"\n :static=\"true\"\n @keydown=\"onKeydown\"\n >\n <div class=\"fixed inset-0 overflow-hidden\">\n <TransitionChild\n as=\"template\"\n enter=\"duration-150 ease-out\"\n enter-from=\"opacity-0\"\n enter-to=\"opacity-100\"\n leave=\"duration-120 ease-in\"\n leave-from=\"opacity-100\"\n leave-to=\"opacity-0\"\n >\n <div\n data-ds-image-viewer-overlay\n class=\"absolute inset-0 bg-black/60 backdrop-blur-sm\"\n aria-hidden=\"true\"\n @click=\"onBackdropClick\"\n />\n </TransitionChild>\n\n <TransitionChild\n as=\"template\"\n enter=\"duration-180 ease-out\"\n enter-from=\"opacity-0 scale-98\"\n enter-to=\"opacity-100 scale-100\"\n leave=\"duration-130 ease-in\"\n leave-from=\"opacity-100 scale-100\"\n leave-to=\"opacity-0 scale-98\"\n >\n <DialogPanel\n data-ds-image-viewer-panel\n class=\"relative z-10 h-full w-full outline-none\"\n tabindex=\"-1\"\n >\n <div class=\"pointer-events-none absolute inset-x-0 top-0 z-30 px-3 py-3 sm:px-6\">\n <div class=\"pointer-events-auto flex items-center justify-between gap-3\">\n <div class=\"flex items-center gap-2\">\n <div\n v-if=\"showProgress\"\n data-ds-image-viewer-progress\n class=\"rounded-full bg-black/35 px-3 py-1 text-xs font-600 text-white/95 sm:text-sm\"\n >\n {{ displayIndex }} / {{ total }}\n </div>\n\n <div\n v-if=\"showZoomValue\"\n data-ds-image-viewer-zoom-value\n class=\"rounded-full bg-black/35 px-3 py-1 text-xs font-700 text-white/95 sm:text-sm\"\n >\n {{ zoomValueText }}%\n </div>\n </div>\n\n <button\n type=\"button\"\n data-ds-image-viewer-close\n :aria-label=\"closeLabel\"\n class=\"h-11 w-11 flex items-center justify-center rounded-full border border-white/20 bg-black/35 text-white transition-colors hover:bg-black/55 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80\"\n @click=\"closeViewer\"\n >\n <span aria-hidden=\"true\" class=\"text-lg leading-none\">✕</span>\n </button>\n </div>\n </div>\n\n <div class=\"relative z-10 h-full w-full flex items-center justify-center px-16 py-16 sm:px-24 sm:py-20\">\n <button\n v-if=\"total > 1\"\n type=\"button\"\n data-ds-image-viewer-prev\n :aria-label=\"prevLabel\"\n class=\"absolute left-3 top-1/2 z-20 h-12 w-12 -translate-y-1/2 flex items-center justify-center rounded-full border border-white/20 bg-black/35 text-white transition-colors hover:bg-black/55 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80 sm:left-6\"\n @click=\"prev\"\n >\n <span aria-hidden=\"true\" class=\"text-xl leading-none\">‹</span>\n </button>\n\n <button\n v-if=\"total > 1\"\n type=\"button\"\n data-ds-image-viewer-next\n :aria-label=\"nextLabel\"\n class=\"absolute right-3 top-1/2 z-20 h-12 w-12 -translate-y-1/2 flex items-center justify-center rounded-full border border-white/20 bg-black/35 text-white transition-colors hover:bg-black/55 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80 sm:right-6\"\n @click=\"next\"\n >\n <span aria-hidden=\"true\" class=\"text-xl leading-none\">›</span>\n </button>\n\n <div class=\"h-full w-full flex items-center justify-center\">\n <img\n v-if=\"currentUrl\"\n data-ds-image-viewer-image\n :src=\"currentUrl\"\n alt=\"\"\n draggable=\"false\"\n class=\"max-h-full max-w-full select-none object-contain will-change-transform transition-transform duration-150 ease-out\"\n :style=\"imageStyle\"\n >\n\n <div\n v-else\n class=\"rounded-[var(--ds-radius-xl)] border border-white/20 bg-black/25 px-4 py-3 text-sm text-white/80\"\n >\n {{ emptyText }}\n </div>\n </div>\n </div>\n\n <div class=\"pointer-events-none absolute inset-x-0 bottom-0 z-30 px-3 pb-3 sm:px-6 sm:pb-6\">\n <div class=\"pointer-events-auto mx-auto w-full max-w-max\">\n <slot\n name=\"toolbar\"\n :index=\"currentIndex\"\n :display-index=\"displayIndex\"\n :total=\"total\"\n :scale=\"scale\"\n :rotation=\"rotation\"\n :actions=\"toolbarActions\"\n >\n <div class=\"rounded-full border border-white/20 bg-black/35 p-1 backdrop-blur-sm flex items-center gap-1\">\n <button\n type=\"button\"\n data-ds-image-viewer-zoom-out\n :aria-label=\"zoomOutLabel\"\n class=\"h-11 min-w-11 px-2 flex items-center justify-center rounded-full text-sm font-600 text-white transition-colors hover:bg-white/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80\"\n @click=\"zoomOut\"\n >\n −\n </button>\n\n <button\n type=\"button\"\n data-ds-image-viewer-zoom-reset\n :aria-label=\"resetZoomLabel\"\n class=\"h-11 min-w-11 px-3 flex items-center justify-center rounded-full text-xs font-700 text-white transition-colors hover:bg-white/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80\"\n @click=\"resetTransform\"\n >\n 100%\n </button>\n\n <button\n type=\"button\"\n data-ds-image-viewer-zoom-in\n :aria-label=\"zoomInLabel\"\n class=\"h-11 min-w-11 px-2 flex items-center justify-center rounded-full text-sm font-600 text-white transition-colors hover:bg-white/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80\"\n @click=\"zoomIn\"\n >\n +\n </button>\n\n <template v-if=\"$slots['toolbar-actions']\">\n <div class=\"mx-1 h-6 w-px bg-white/25\" aria-hidden=\"true\" />\n\n <slot\n name=\"toolbar-actions\"\n :index=\"currentIndex\"\n :display-index=\"displayIndex\"\n :total=\"total\"\n :scale=\"scale\"\n :rotation=\"rotation\"\n :actions=\"toolbarActions\"\n />\n\n <div class=\"mx-1 h-6 w-px bg-white/25\" aria-hidden=\"true\" />\n </template>\n\n <div v-else class=\"mx-1 h-6 w-px bg-white/25\" aria-hidden=\"true\" />\n\n <button\n type=\"button\"\n data-ds-image-viewer-rotate-left\n :aria-label=\"rotateLeftLabel\"\n class=\"h-11 min-w-11 px-2 flex items-center justify-center rounded-full text-sm font-600 text-white transition-colors hover:bg-white/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80\"\n @click=\"rotateLeft\"\n >\n ↺\n </button>\n\n <button\n type=\"button\"\n data-ds-image-viewer-rotate-right\n :aria-label=\"rotateRightLabel\"\n class=\"h-11 min-w-11 px-2 flex items-center justify-center rounded-full text-sm font-600 text-white transition-colors hover:bg-white/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80\"\n @click=\"rotateRight\"\n >\n ↻\n </button>\n </div>\n </slot>\n </div>\n </div>\n </DialogPanel>\n </TransitionChild>\n </div>\n </Dialog>\n </TransitionRoot>\n </teleport>\n</template>","<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport { Dialog, DialogPanel, TransitionChild, TransitionRoot } from '@headlessui/vue'\n\n/**\n * Usage:\n *\n * <GrImageViewer\n * v-model=\"open\"\n * :url-list=\"images\"\n * :initial-index=\"0\"\n * show-progress\n * hide-on-click-modal\n * />\n */\nconst props = withDefaults(\n defineProps<{\n modelValue: boolean\n urlList: string[]\n initialIndex?: number\n zoomRate?: number\n minScale?: number\n maxScale?: number\n hideOnClickModal?: boolean\n closeOnPressEscape?: boolean\n showProgress?: boolean\n showZoomValue?: boolean\n zIndex?: number\n /** i18n: aria-label кнопки закрытия. */\n closeLabel?: string\n /** i18n: aria-label кнопки «предыдущее изображение». */\n prevLabel?: string\n /** i18n: aria-label кнопки «следующее изображение». */\n nextLabel?: string\n /** i18n: aria-label кнопки «увеличить». */\n zoomInLabel?: string\n /** i18n: aria-label кнопки «уменьшить». */\n zoomOutLabel?: string\n /** i18n: aria-label кнопки «сбросить масштаб». */\n resetZoomLabel?: string\n /** i18n: aria-label кнопки «повернуть влево». */\n rotateLeftLabel?: string\n /** i18n: aria-label кнопки «повернуть вправо». */\n rotateRightLabel?: string\n /** i18n: текст в пустом состоянии (нет изображений). */\n emptyText?: string\n }>(),\n {\n initialIndex: 0,\n zoomRate: 1.2,\n minScale: 0.5,\n maxScale: 5,\n hideOnClickModal: false,\n closeOnPressEscape: true,\n showProgress: false,\n showZoomValue: true,\n zIndex: undefined,\n closeLabel: 'Close image viewer',\n prevLabel: 'Previous image',\n nextLabel: 'Next image',\n zoomInLabel: 'Zoom in',\n zoomOutLabel: 'Zoom out',\n resetZoomLabel: 'Reset zoom',\n rotateLeftLabel: 'Rotate left',\n rotateRightLabel: 'Rotate right',\n emptyText: 'No image',\n },\n)\n\nconst emit = defineEmits<{\n (e: 'update:modelValue', value: boolean): void\n (e: 'close'): void\n (e: 'switch', newIndex: number): void\n (e: 'rotate', deg: number): void\n}>()\n\ntype GrImageViewerToolbarActions = {\n close: () => void\n prev: () => void\n next: () => void\n zoomIn: () => void\n zoomOut: () => void\n reset: () => void\n rotateLeft: () => void\n rotateRight: () => void\n}\n\nconst DEFAULT_Z_INDEX = 2000\nconst TRAILING_ZERO_DECIMAL_RE = /\\.0$/\n\nconst open = computed(() => props.modelValue)\nconst total = computed(() => props.urlList.length)\nconst hasImages = computed(() => total.value > 0)\n\nconst currentIndex = ref(0)\nconst scale = ref(1)\nconst rotation = ref(0)\nconst offsetX = ref(0)\nconst offsetY = ref(0)\n\nconst currentUrl = computed(() => {\n if (!hasImages.value) {\n return undefined\n }\n\n return props.urlList[currentIndex.value]\n})\n\nconst displayIndex = computed(() => {\n if (!hasImages.value) {\n return 0\n }\n\n return currentIndex.value + 1\n})\n\nconst viewerStyle = computed(() => ({\n zIndex: String(Number.isFinite(props.zIndex) ? props.zIndex : DEFAULT_Z_INDEX),\n}))\n\nconst imageStyle = computed(() => ({\n transform: `translate3d(${offsetX.value}px, ${offsetY.value}px, 0) scale(${scale.value}) rotate(${rotation.value}deg)`,\n}))\n\nconst zoomValueText = computed(() => {\n const zoomPercent = scale.value * 100\n\n if (Number.isInteger(zoomPercent)) {\n return String(zoomPercent)\n }\n\n return zoomPercent.toFixed(1).replace(TRAILING_ZERO_DECIMAL_RE, '')\n})\n\nfunction normalizeIndex(value: number): number {\n if (!hasImages.value) {\n return 0\n }\n\n const normalized = Number.isFinite(value) ? Math.trunc(value) : 0\n\n return ((normalized % total.value) + total.value) % total.value\n}\n\nfunction resetTransform(): void {\n scale.value = 1\n rotation.value = 0\n offsetX.value = 0\n offsetY.value = 0\n}\n\nfunction setIndex(nextIndex: number, options?: { emitSwitch?: boolean }): void {\n const normalizedIndex = normalizeIndex(nextIndex)\n\n if (normalizedIndex === currentIndex.value) {\n return\n }\n\n currentIndex.value = normalizedIndex\n resetTransform()\n\n if (options?.emitSwitch !== false) {\n emit('switch', normalizedIndex)\n }\n}\n\nfunction syncIndexFromInitial(): void {\n if (!hasImages.value) {\n currentIndex.value = 0\n return\n }\n\n currentIndex.value = normalizeIndex(props.initialIndex)\n resetTransform()\n}\n\nfunction preloadAt(index: number): void {\n const url = props.urlList[index]\n\n if (!url) {\n return\n }\n\n const image = new Image()\n image.decoding = 'async'\n image.src = url\n}\n\nfunction closeViewer(): void {\n emit('update:modelValue', false)\n emit('close')\n}\n\nfunction prev(): void {\n setIndex(currentIndex.value - 1)\n}\n\nfunction next(): void {\n setIndex(currentIndex.value + 1)\n}\n\nfunction zoomIn(): void {\n scale.value = Math.min(props.maxScale, Number((scale.value * props.zoomRate).toFixed(4)))\n}\n\nfunction zoomOut(): void {\n scale.value = Math.max(props.minScale, Number((scale.value / props.zoomRate).toFixed(4)))\n}\n\nfunction rotateLeft(): void {\n rotation.value -= 90\n emit('rotate', rotation.value)\n}\n\nfunction rotateRight(): void {\n rotation.value += 90\n emit('rotate', rotation.value)\n}\n\nconst toolbarActions: GrImageViewerToolbarActions = {\n close: closeViewer,\n prev,\n next,\n zoomIn,\n zoomOut,\n reset: resetTransform,\n rotateLeft,\n rotateRight,\n}\n\nfunction onBackdropClick(): void {\n if (!props.hideOnClickModal) {\n return\n }\n\n closeViewer()\n}\n\nfunction onKeydown(event: KeyboardEvent): void {\n switch (event.key) {\n case 'Escape':\n if (!props.closeOnPressEscape) {\n return\n }\n\n event.preventDefault()\n closeViewer()\n return\n\n case 'ArrowLeft':\n event.preventDefault()\n prev()\n return\n\n case 'ArrowRight':\n event.preventDefault()\n next()\n return\n\n case '+':\n case '=':\n case 'Add':\n event.preventDefault()\n zoomIn()\n return\n\n case '-':\n case '_':\n case 'Subtract':\n event.preventDefault()\n zoomOut()\n return\n\n case '0':\n event.preventDefault()\n resetTransform()\n break\n\n default:\n }\n}\n\nwatch(\n () => props.modelValue,\n (isOpen) => {\n if (!isOpen) {\n return\n }\n\n syncIndexFromInitial()\n },\n { immediate: true },\n)\n\nwatch(\n () => props.initialIndex,\n () => {\n if (!props.modelValue) {\n return\n }\n\n syncIndexFromInitial()\n },\n)\n\nwatch(\n () => props.urlList,\n () => {\n if (!props.modelValue) {\n return\n }\n\n syncIndexFromInitial()\n },\n { deep: true },\n)\n\nwatch(\n currentIndex,\n (index) => {\n if (!hasImages.value || total.value < 2) {\n return\n }\n\n preloadAt(normalizeIndex(index - 1))\n preloadAt(normalizeIndex(index + 1))\n },\n { immediate: true },\n)\n</script>\n\n<template>\n <teleport to=\"body\">\n <TransitionRoot :show=\"open\" as=\"template\">\n <Dialog\n as=\"div\"\n class=\"fixed inset-0\"\n :style=\"viewerStyle\"\n role=\"dialog\"\n aria-modal=\"true\"\n :static=\"true\"\n @keydown=\"onKeydown\"\n >\n <div class=\"fixed inset-0 overflow-hidden\">\n <TransitionChild\n as=\"template\"\n enter=\"duration-150 ease-out\"\n enter-from=\"opacity-0\"\n enter-to=\"opacity-100\"\n leave=\"duration-120 ease-in\"\n leave-from=\"opacity-100\"\n leave-to=\"opacity-0\"\n >\n <div\n data-ds-image-viewer-overlay\n class=\"absolute inset-0 bg-black/60 backdrop-blur-sm\"\n aria-hidden=\"true\"\n @click=\"onBackdropClick\"\n />\n </TransitionChild>\n\n <TransitionChild\n as=\"template\"\n enter=\"duration-180 ease-out\"\n enter-from=\"opacity-0 scale-98\"\n enter-to=\"opacity-100 scale-100\"\n leave=\"duration-130 ease-in\"\n leave-from=\"opacity-100 scale-100\"\n leave-to=\"opacity-0 scale-98\"\n >\n <DialogPanel\n data-ds-image-viewer-panel\n class=\"relative z-10 h-full w-full outline-none\"\n tabindex=\"-1\"\n >\n <div class=\"pointer-events-none absolute inset-x-0 top-0 z-30 px-3 py-3 sm:px-6\">\n <div class=\"pointer-events-auto flex items-center justify-between gap-3\">\n <div class=\"flex items-center gap-2\">\n <div\n v-if=\"showProgress\"\n data-ds-image-viewer-progress\n class=\"rounded-full bg-black/35 px-3 py-1 text-xs font-600 text-white/95 sm:text-sm\"\n >\n {{ displayIndex }} / {{ total }}\n </div>\n\n <div\n v-if=\"showZoomValue\"\n data-ds-image-viewer-zoom-value\n class=\"rounded-full bg-black/35 px-3 py-1 text-xs font-700 text-white/95 sm:text-sm\"\n >\n {{ zoomValueText }}%\n </div>\n </div>\n\n <button\n type=\"button\"\n data-ds-image-viewer-close\n :aria-label=\"closeLabel\"\n class=\"h-11 w-11 flex items-center justify-center rounded-full border border-white/20 bg-black/35 text-white transition-colors hover:bg-black/55 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80\"\n @click=\"closeViewer\"\n >\n <span aria-hidden=\"true\" class=\"text-lg leading-none\">✕</span>\n </button>\n </div>\n </div>\n\n <div class=\"relative z-10 h-full w-full flex items-center justify-center px-16 py-16 sm:px-24 sm:py-20\">\n <button\n v-if=\"total > 1\"\n type=\"button\"\n data-ds-image-viewer-prev\n :aria-label=\"prevLabel\"\n class=\"absolute left-3 top-1/2 z-20 h-12 w-12 -translate-y-1/2 flex items-center justify-center rounded-full border border-white/20 bg-black/35 text-white transition-colors hover:bg-black/55 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80 sm:left-6\"\n @click=\"prev\"\n >\n <span aria-hidden=\"true\" class=\"text-xl leading-none\">‹</span>\n </button>\n\n <button\n v-if=\"total > 1\"\n type=\"button\"\n data-ds-image-viewer-next\n :aria-label=\"nextLabel\"\n class=\"absolute right-3 top-1/2 z-20 h-12 w-12 -translate-y-1/2 flex items-center justify-center rounded-full border border-white/20 bg-black/35 text-white transition-colors hover:bg-black/55 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80 sm:right-6\"\n @click=\"next\"\n >\n <span aria-hidden=\"true\" class=\"text-xl leading-none\">›</span>\n </button>\n\n <div class=\"h-full w-full flex items-center justify-center\">\n <img\n v-if=\"currentUrl\"\n data-ds-image-viewer-image\n :src=\"currentUrl\"\n alt=\"\"\n draggable=\"false\"\n class=\"max-h-full max-w-full select-none object-contain will-change-transform transition-transform duration-150 ease-out\"\n :style=\"imageStyle\"\n >\n\n <div\n v-else\n class=\"rounded-[var(--ds-radius-xl)] border border-white/20 bg-black/25 px-4 py-3 text-sm text-white/80\"\n >\n {{ emptyText }}\n </div>\n </div>\n </div>\n\n <div class=\"pointer-events-none absolute inset-x-0 bottom-0 z-30 px-3 pb-3 sm:px-6 sm:pb-6\">\n <div class=\"pointer-events-auto mx-auto w-full max-w-max\">\n <slot\n name=\"toolbar\"\n :index=\"currentIndex\"\n :display-index=\"displayIndex\"\n :total=\"total\"\n :scale=\"scale\"\n :rotation=\"rotation\"\n :actions=\"toolbarActions\"\n >\n <div class=\"rounded-full border border-white/20 bg-black/35 p-1 backdrop-blur-sm flex items-center gap-1\">\n <button\n type=\"button\"\n data-ds-image-viewer-zoom-out\n :aria-label=\"zoomOutLabel\"\n class=\"h-11 min-w-11 px-2 flex items-center justify-center rounded-full text-sm font-600 text-white transition-colors hover:bg-white/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80\"\n @click=\"zoomOut\"\n >\n −\n </button>\n\n <button\n type=\"button\"\n data-ds-image-viewer-zoom-reset\n :aria-label=\"resetZoomLabel\"\n class=\"h-11 min-w-11 px-3 flex items-center justify-center rounded-full text-xs font-700 text-white transition-colors hover:bg-white/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80\"\n @click=\"resetTransform\"\n >\n 100%\n </button>\n\n <button\n type=\"button\"\n data-ds-image-viewer-zoom-in\n :aria-label=\"zoomInLabel\"\n class=\"h-11 min-w-11 px-2 flex items-center justify-center rounded-full text-sm font-600 text-white transition-colors hover:bg-white/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80\"\n @click=\"zoomIn\"\n >\n +\n </button>\n\n <template v-if=\"$slots['toolbar-actions']\">\n <div class=\"mx-1 h-6 w-px bg-white/25\" aria-hidden=\"true\" />\n\n <slot\n name=\"toolbar-actions\"\n :index=\"currentIndex\"\n :display-index=\"displayIndex\"\n :total=\"total\"\n :scale=\"scale\"\n :rotation=\"rotation\"\n :actions=\"toolbarActions\"\n />\n\n <div class=\"mx-1 h-6 w-px bg-white/25\" aria-hidden=\"true\" />\n </template>\n\n <div v-else class=\"mx-1 h-6 w-px bg-white/25\" aria-hidden=\"true\" />\n\n <button\n type=\"button\"\n data-ds-image-viewer-rotate-left\n :aria-label=\"rotateLeftLabel\"\n class=\"h-11 min-w-11 px-2 flex items-center justify-center rounded-full text-sm font-600 text-white transition-colors hover:bg-white/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80\"\n @click=\"rotateLeft\"\n >\n ↺\n </button>\n\n <button\n type=\"button\"\n data-ds-image-viewer-rotate-right\n :aria-label=\"rotateRightLabel\"\n class=\"h-11 min-w-11 px-2 flex items-center justify-center rounded-full text-sm font-600 text-white transition-colors hover:bg-white/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80\"\n @click=\"rotateRight\"\n >\n ↻\n </button>\n </div>\n </slot>\n </div>\n </div>\n </DialogPanel>\n </TransitionChild>\n </div>\n </Dialog>\n </TransitionRoot>\n </teleport>\n</template>"],"mappings":"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"}
@@ -1,3 +1,3 @@
1
- import { t as GrImageViewer_default } from "./chunks/GrImageViewer-Cpj5zyZQ.js";
2
- import { t as grImageViewerConfig } from "../../chunks/config-D98m8iyz.js";
1
+ import { t as GrImageViewer_default } from "./chunks/GrImageViewer-Cn1X-sWS.js";
2
+ import { t as grImageViewerConfig } from "../../chunks/config-CvGdaXvH.js";
3
3
  export { GrImageViewer_default as GrImageViewer, GrImageViewer_default as default, grImageViewerConfig };