@feugene/granularity 0.4.1 → 0.4.2

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 (257) hide show
  1. package/dist/chunks/{adapter-C7fKeUa4.js → adapter-K6zZTzBi.js} +0 -2
  2. package/dist/chunks/{adapter-C7fKeUa4.js.map → adapter-K6zZTzBi.js.map} +1 -1
  3. package/dist/chunks/arrow-up-DNXrMoAI.js +0 -2
  4. package/dist/chunks/classTokens-BPeBXNzu.js +0 -2
  5. package/dist/chunks/{config-BUGfFjmC.js → config-1I95Zy4V.js} +0 -2
  6. package/dist/chunks/{config-BUGfFjmC.js.map → config-1I95Zy4V.js.map} +1 -1
  7. package/dist/chunks/config-2wgwWaAB.js +0 -2
  8. package/dist/chunks/{config-DBYCntwl.js → config-BA0BWYzs.js} +0 -2
  9. package/dist/chunks/{config-DBYCntwl.js.map → config-BA0BWYzs.js.map} +1 -1
  10. package/dist/chunks/{config-dD6Po-Cq.js → config-BCPyma1I.js} +0 -2
  11. package/dist/chunks/{config-dD6Po-Cq.js.map → config-BCPyma1I.js.map} +1 -1
  12. package/dist/chunks/{config-CYPU3ztl.js → config-BDlh6iNz.js} +1 -3
  13. package/dist/chunks/{config-CYPU3ztl.js.map → config-BDlh6iNz.js.map} +1 -1
  14. package/dist/chunks/config-BKRmlivc.js +0 -2
  15. package/dist/chunks/{config-CcmeMlic.js → config-BN-B2K4r.js} +1 -3
  16. package/dist/chunks/{config-CcmeMlic.js.map → config-BN-B2K4r.js.map} +1 -1
  17. package/dist/chunks/{config-C0b_3LJV.js → config-BNOf_qjD.js} +0 -2
  18. package/dist/chunks/{config-C0b_3LJV.js.map → config-BNOf_qjD.js.map} +1 -1
  19. package/dist/chunks/config-BaMO9dQh.js +0 -2
  20. package/dist/chunks/config-Bv2G0OU6.js +0 -2
  21. package/dist/chunks/{config-CxCAfXZ1.js → config-BvuQUBtZ.js} +0 -2
  22. package/dist/chunks/{config-CxCAfXZ1.js.map → config-BvuQUBtZ.js.map} +1 -1
  23. package/dist/chunks/config-BzZGpWsj.js +0 -2
  24. package/dist/chunks/{config-KGTIAFD92.js → config-C0j836dC.js} +0 -2
  25. package/dist/chunks/config-C0j836dC.js.map +1 -0
  26. package/dist/chunks/{config-D7iuXJbl.js → config-C2iI7_QX.js} +0 -2
  27. package/dist/chunks/{config-D7iuXJbl.js.map → config-C2iI7_QX.js.map} +1 -1
  28. package/dist/chunks/{config-BcFOkUhh.js → config-C4AlNm912.js} +1 -3
  29. package/dist/chunks/config-C4AlNm912.js.map +1 -0
  30. package/dist/chunks/{config-rFwazRKi.js → config-C5SJXHQ4.js} +0 -2
  31. package/dist/chunks/{config-rFwazRKi.js.map → config-C5SJXHQ4.js.map} +1 -1
  32. package/dist/chunks/config-C6SBJxcH.js +0 -2
  33. package/dist/chunks/{config-BzQ4S4Hb.js → config-CByfsV-z2.js} +0 -2
  34. package/dist/chunks/config-CByfsV-z2.js.map +1 -0
  35. package/dist/chunks/{config-rxFL-6yF2.js → config-CM7nbATU2.js} +0 -2
  36. package/dist/chunks/{config-rxFL-6yF2.js.map → config-CM7nbATU2.js.map} +1 -1
  37. package/dist/chunks/config-CMZE2fu0.js +0 -2
  38. package/dist/chunks/config-CU_CBd0k.js +0 -2
  39. package/dist/chunks/{config-BhvNm4Pp.js → config-CUgvsjZx.js} +0 -2
  40. package/dist/chunks/{config-BhvNm4Pp.js.map → config-CUgvsjZx.js.map} +1 -1
  41. package/dist/chunks/{config-DxqMXzNV2.js → config-CWvHiaqr2.js} +0 -2
  42. package/dist/chunks/{config-DxqMXzNV2.js.map → config-CWvHiaqr2.js.map} +1 -1
  43. package/dist/chunks/{config-C2JKq8-2.js → config-Cf0J6wCX.js} +0 -2
  44. package/dist/chunks/{config-C2JKq8-2.js.map → config-Cf0J6wCX.js.map} +1 -1
  45. package/dist/chunks/{config-CdAx6ify2.js → config-CgAY2WP9.js} +0 -2
  46. package/dist/chunks/config-CgAY2WP9.js.map +1 -0
  47. package/dist/chunks/{config-D1gSaT52.js → config-Cgc-D1K4.js} +0 -2
  48. package/dist/chunks/{config-D1gSaT52.js.map → config-Cgc-D1K4.js.map} +1 -1
  49. package/dist/chunks/{config-cn6knwud.js → config-D19pyP4D.js} +0 -2
  50. package/dist/chunks/{config-cn6knwud.js.map → config-D19pyP4D.js.map} +1 -1
  51. package/dist/chunks/{config-ug8O8epd.js → config-DCQO6H6h.js} +0 -2
  52. package/dist/chunks/{config-ug8O8epd.js.map → config-DCQO6H6h.js.map} +1 -1
  53. package/dist/chunks/{config-BXffa3ta.js → config-DEv4m-ZP.js} +0 -2
  54. package/dist/chunks/{config-BXffa3ta.js.map → config-DEv4m-ZP.js.map} +1 -1
  55. package/dist/chunks/config-DFRYOcrP.js +0 -2
  56. package/dist/chunks/{config-DlLE49_I.js → config-DTpwUHgF.js} +0 -2
  57. package/dist/chunks/{config-DlLE49_I.js.map → config-DTpwUHgF.js.map} +1 -1
  58. package/dist/chunks/config-DW_RqmUp.js +0 -2
  59. package/dist/chunks/{config-CJeyaUTp.js → config-Da90XGsi.js} +0 -2
  60. package/dist/chunks/{config-CJeyaUTp.js.map → config-Da90XGsi.js.map} +1 -1
  61. package/dist/chunks/{config-CJbjLC5X.js → config-DgRJlF00.js} +0 -2
  62. package/dist/chunks/{config-CJbjLC5X.js.map → config-DgRJlF00.js.map} +1 -1
  63. package/dist/chunks/config-Dkq0HeLe.js +0 -2
  64. package/dist/chunks/{config-DwB5-qdP2.js → config-DmI1IQvI2.js} +0 -2
  65. package/dist/chunks/{config-DwB5-qdP2.js.map → config-DmI1IQvI2.js.map} +1 -1
  66. package/dist/chunks/{config-BRU5PR8_.js → config-Dnu4VuO0.js} +0 -2
  67. package/dist/chunks/{config-BRU5PR8_.js.map → config-Dnu4VuO0.js.map} +1 -1
  68. package/dist/chunks/config-DxqpV-KM.js +0 -2
  69. package/dist/chunks/config-TyzU6gLf.js +0 -2
  70. package/dist/chunks/{config-Bv5Dok4j.js → config-XwI6t8cn.js} +0 -2
  71. package/dist/chunks/{config-Bv5Dok4j.js.map → config-XwI6t8cn.js.map} +1 -1
  72. package/dist/chunks/{config-DvTUDLT1.js → config-bpQYM5O-.js} +0 -2
  73. package/dist/chunks/{config-DvTUDLT1.js.map → config-bpQYM5O-.js.map} +1 -1
  74. package/dist/chunks/{config-BbtK511H.js → config-etXh7Bsr2.js} +0 -2
  75. package/dist/chunks/config-etXh7Bsr2.js.map +1 -0
  76. package/dist/chunks/{config-BhubOeci.js → config-fCcRwjIB.js} +0 -2
  77. package/dist/chunks/{config-BhubOeci.js.map → config-fCcRwjIB.js.map} +1 -1
  78. package/dist/chunks/{config-D7tRu8u3.js → config-nfOMJXVs.js} +0 -2
  79. package/dist/chunks/{config-D7tRu8u3.js.map → config-nfOMJXVs.js.map} +1 -1
  80. package/dist/chunks/{config-BcpRsZTe.js → config-qJXIIY1E.js} +0 -2
  81. package/dist/chunks/{config-BcpRsZTe.js.map → config-qJXIIY1E.js.map} +1 -1
  82. package/dist/chunks/{config-Dzz9P-GV2.js → config-r6hzn2Bn2.js} +0 -2
  83. package/dist/chunks/{config-Dzz9P-GV2.js.map → config-r6hzn2Bn2.js.map} +1 -1
  84. package/dist/chunks/config-rQlbnEKn.js +0 -2
  85. package/dist/chunks/dialogShared-BtXUk6Zc.js +0 -2
  86. package/dist/chunks/{en-BMjJZttK.js → en-BeJa78lP.js} +0 -2
  87. package/dist/chunks/{en-BMjJZttK.js.map → en-BeJa78lP.js.map} +1 -1
  88. package/dist/chunks/{es-Cu_RlgIr.js → es-BPqm7rLm.js} +3 -5
  89. package/dist/chunks/{es-Cu_RlgIr.js.map → es-BPqm7rLm.js.map} +1 -1
  90. package/dist/chunks/{es-EYCf8G_1.js → es-axYOwwWD.js} +0 -2
  91. package/dist/chunks/{es-EYCf8G_1.js.map → es-axYOwwWD.js.map} +1 -1
  92. package/dist/chunks/{fileValidation-CgjTGoUx.js → fileValidation-CwJA8Vmn.js} +0 -2
  93. package/dist/chunks/{fileValidation-CgjTGoUx.js.map → fileValidation-CwJA8Vmn.js.map} +1 -1
  94. package/dist/chunks/grBadgeStyles-pqqbVgLk.js +0 -2
  95. package/dist/chunks/grButtonStyles-BxZW2x0-.js +0 -2
  96. package/dist/chunks/{grDropdownStyles-DQfLh-bp.js → grDropdownStyles-DMp7RrI9.js} +0 -2
  97. package/dist/chunks/{grDropdownStyles-DQfLh-bp.js.map → grDropdownStyles-DMp7RrI9.js.map} +1 -1
  98. package/dist/chunks/{grLoadingStyles-CERsxyk9.js → grLoadingStyles-CrxZHFL0.js} +0 -2
  99. package/dist/chunks/{grLoadingStyles-CERsxyk9.js.map → grLoadingStyles-CrxZHFL0.js.map} +1 -1
  100. package/dist/chunks/grModalStyles-Dv2NXBKi.js +0 -2
  101. package/dist/chunks/{grSelectStyles-C1zNEkV7.js → grSelectStyles-B7doeAQp.js} +0 -2
  102. package/dist/chunks/{grSelectStyles-C1zNEkV7.js.map → grSelectStyles-B7doeAQp.js.map} +1 -1
  103. package/dist/chunks/{granularityI18n-BEWP9aJs.js → granularityI18n-GCqUz_lo.js} +1 -3
  104. package/dist/chunks/{granularityI18n-BEWP9aJs.js.map → granularityI18n-GCqUz_lo.js.map} +1 -1
  105. package/dist/chunks/info-C0bHmdZq.js +0 -2
  106. package/dist/chunks/loader-circle-B2rWDk5u.js +0 -2
  107. package/dist/chunks/{ru-DKswMSdd.js → ru-Dqh-2C00.js} +0 -2
  108. package/dist/chunks/{ru-DKswMSdd.js.map → ru-Dqh-2C00.js.map} +1 -1
  109. package/dist/chunks/safelist-BF822K15.js +0 -2
  110. package/dist/chunks/safelist-BY7z8fOl.js +0 -2
  111. package/dist/chunks/{shared-Bvjkhhm2.js → shared-Cu5QkNmt.js} +33 -35
  112. package/dist/chunks/{shared-Bvjkhhm2.js.map → shared-Cu5QkNmt.js.map} +1 -1
  113. package/dist/chunks/transition-mJ3PZCHH.js +0 -2
  114. package/dist/chunks/x-Pa6C2hwi.js +0 -2
  115. package/dist/chunks/x-circle-DQSlBLmX.js +0 -2
  116. package/dist/components/GrAlert/chunks/GrAlert-BjerGcU8.js +0 -2
  117. package/dist/components/GrAvatar/chunks/GrAvatar-BjnPKNAL.js +0 -2
  118. package/dist/components/GrBadge/chunks/GrBadge-wuIb4_2K.js +0 -2
  119. package/dist/components/GrBadgeWrap/chunks/GrBadgeWrap-CxtamOZ-.js +0 -2
  120. package/dist/components/GrBottomNav/chunks/GrBottomNav-CsmQ7mTJ.js +0 -2
  121. package/dist/components/GrButton/chunks/GrButton-BmGdp8-4.js +0 -2
  122. package/dist/components/GrButtonGroup/chunks/GrButtonGroup-CBj4tpms.js +0 -2
  123. package/dist/components/GrCard/chunks/GrCard-xtDcXBr7.js +0 -2
  124. package/dist/components/GrCheckbox/chunks/GrCheckbox-Bt3d7ckE.js +0 -2
  125. package/dist/components/GrCollapse/chunks/GrCollapse-HU20VTVn.js +0 -2
  126. package/dist/components/GrConfirmDialog/chunks/GrConfirmDialog-DjGOX9Hd.js +0 -2
  127. package/dist/components/GrDataTable/chunks/GrDataTable-QDOJWmdY.js +0 -2
  128. package/dist/components/GrDialog/chunks/GrDialog-gObmaVEl.js +0 -2
  129. package/dist/components/GrDrawer/chunks/GrDrawer-BJL_gTxI.js +0 -2
  130. package/dist/components/GrDropdown/chunks/{GrDropdown-DVTU32T_.js → GrDropdown-BkACCXdp.js} +2 -4
  131. package/dist/components/GrDropdown/chunks/{GrDropdown-DVTU32T_.js.map → GrDropdown-BkACCXdp.js.map} +1 -1
  132. package/dist/components/GrDropdown/index.js +2 -2
  133. package/dist/components/GrDropdownMenu/chunks/{GrDropdownMenu-fePQRcgh.js → GrDropdownMenu-BEenXrJw.js} +1 -3
  134. package/dist/components/GrDropdownMenu/chunks/{GrDropdownMenu-fePQRcgh.js.map → GrDropdownMenu-BEenXrJw.js.map} +1 -1
  135. package/dist/components/GrDropdownMenu/index.js +2 -2
  136. package/dist/components/GrEmptyState/chunks/{GrEmptyState-Bs8d3tgb.js → GrEmptyState-BLbMJFW-.js} +0 -2
  137. package/dist/components/GrEmptyState/chunks/{GrEmptyState-Bs8d3tgb.js.map → GrEmptyState-BLbMJFW-.js.map} +1 -1
  138. package/dist/components/GrEmptyState/index.js +2 -2
  139. package/dist/components/GrFileUpload/chunks/{GrFileUpload-DVv8yXpl.js → GrFileUpload-u01j9S0t.js} +1 -3
  140. package/dist/components/GrFileUpload/chunks/{GrFileUpload-DVv8yXpl.js.map → GrFileUpload-u01j9S0t.js.map} +1 -1
  141. package/dist/components/GrFileUpload/index.js +3 -3
  142. package/dist/components/GrFormField/chunks/{GrFormField-BzezYcS8.js → GrFormField-C6PfAs4u.js} +0 -2
  143. package/dist/components/GrFormField/chunks/{GrFormField-BzezYcS8.js.map → GrFormField-C6PfAs4u.js.map} +1 -1
  144. package/dist/components/GrFormField/index.js +2 -2
  145. package/dist/components/GrFormFile/chunks/{GrFormFile-DtZOypJW.js → GrFormFile-DdM8Bc2B.js} +2 -4
  146. package/dist/components/GrFormFile/chunks/{GrFormFile-DtZOypJW.js.map → GrFormFile-DdM8Bc2B.js.map} +1 -1
  147. package/dist/components/GrFormFile/index.js +2 -2
  148. package/dist/components/GrFormSection/chunks/{GrFormSection-BIPB1RIk.js → GrFormSection-5djEaQZQ.js} +0 -2
  149. package/dist/components/GrFormSection/chunks/{GrFormSection-BIPB1RIk.js.map → GrFormSection-5djEaQZQ.js.map} +1 -1
  150. package/dist/components/GrFormSection/index.js +2 -2
  151. package/dist/components/GrIcon/chunks/GrIcon-BV7RBU_0.js +0 -2
  152. package/dist/components/GrImageViewer/chunks/{GrImageViewer-fn8ilJTA.js → GrImageViewer-CjSnohW7.js} +0 -2
  153. package/dist/components/GrImageViewer/chunks/{GrImageViewer-fn8ilJTA.js.map → GrImageViewer-CjSnohW7.js.map} +1 -1
  154. package/dist/components/GrImageViewer/index.js +2 -2
  155. package/dist/components/GrInput/chunks/{GrInput-DLLRrH1A.js → GrInput-ghxaTf6d.js} +0 -2
  156. package/dist/components/GrInput/chunks/{GrInput-DLLRrH1A.js.map → GrInput-ghxaTf6d.js.map} +1 -1
  157. package/dist/components/GrInput/index.js +2 -2
  158. package/dist/components/GrInputTag/chunks/{GrInputTag-CzrrBxgU.js → GrInputTag-B0x4U0Yk.js} +1 -3
  159. package/dist/components/GrInputTag/chunks/{GrInputTag-CzrrBxgU.js.map → GrInputTag-B0x4U0Yk.js.map} +1 -1
  160. package/dist/components/GrInputTag/index.js +2 -2
  161. package/dist/components/GrLink/chunks/{GrLink-Dhkid3Mp.js → GrLink-C7-Y1u88.js} +1 -3
  162. package/dist/components/GrLink/chunks/{GrLink-Dhkid3Mp.js.map → GrLink-C7-Y1u88.js.map} +1 -1
  163. package/dist/components/GrLink/index.js +2 -2
  164. package/dist/components/GrList/chunks/{GrList-CjgeVjhY.js → GrList--Ib4zLNw.js} +0 -2
  165. package/dist/components/GrList/chunks/{GrList-CjgeVjhY.js.map → GrList--Ib4zLNw.js.map} +1 -1
  166. package/dist/components/GrList/index.js +2 -2
  167. package/dist/components/GrLoading/chunks/{GrLoading-DihGX3ol.js → GrLoading-C3QZKwE-.js} +1 -3
  168. package/dist/components/GrLoading/chunks/{GrLoading-DihGX3ol.js.map → GrLoading-C3QZKwE-.js.map} +1 -1
  169. package/dist/components/GrLoading/index.js +2 -2
  170. package/dist/components/GrModal/chunks/GrModal-gvfvlp7k.js +0 -2
  171. package/dist/components/GrModal/index.js +1 -1
  172. package/dist/components/GrNavbar/chunks/{GrNavbar-CQOAfO6y.js → GrNavbar-nIyuiVj1.js} +1 -3
  173. package/dist/components/GrNavbar/chunks/{GrNavbar-CQOAfO6y.js.map → GrNavbar-nIyuiVj1.js.map} +1 -1
  174. package/dist/components/GrNavbar/index.js +2 -2
  175. package/dist/components/GrNumberInput/chunks/{GrNumberInput-CyHgUotG.js → GrNumberInput-B0mPLjdv.js} +1 -3
  176. package/dist/components/GrNumberInput/chunks/{GrNumberInput-CyHgUotG.js.map → GrNumberInput-B0mPLjdv.js.map} +1 -1
  177. package/dist/components/GrNumberInput/index.js +2 -2
  178. package/dist/components/GrPagination/chunks/{GrPagination-C5cxRJsF.js → GrPagination-BSTO6flk.js} +2 -4
  179. package/dist/components/GrPagination/chunks/{GrPagination-C5cxRJsF.js.map → GrPagination-BSTO6flk.js.map} +1 -1
  180. package/dist/components/GrPagination/index.js +2 -2
  181. package/dist/components/GrProgressBar/chunks/{GrProgressBar-C9wAAFxR.js → GrProgressBar-MvMnzsJl.js} +1 -3
  182. package/dist/components/GrProgressBar/chunks/{GrProgressBar-C9wAAFxR.js.map → GrProgressBar-MvMnzsJl.js.map} +1 -1
  183. package/dist/components/GrProgressBar/index.js +2 -2
  184. package/dist/components/GrPromptDialog/chunks/{GrPromptDialog-Cfq9bvL3.js → GrPromptDialog-DKhbN6ji.js} +2 -4
  185. package/dist/components/GrPromptDialog/chunks/{GrPromptDialog-Cfq9bvL3.js.map → GrPromptDialog-DKhbN6ji.js.map} +1 -1
  186. package/dist/components/GrPromptDialog/index.js +2 -2
  187. package/dist/components/GrRadio/chunks/{GrRadio-B_t_ZNVl.js → GrRadio-BxBs3WHq.js} +1 -3
  188. package/dist/components/GrRadio/chunks/{GrRadio-B_t_ZNVl.js.map → GrRadio-BxBs3WHq.js.map} +1 -1
  189. package/dist/components/GrRadio/index.js +2 -2
  190. package/dist/components/GrRadioGroup/chunks/{GrRadioGroup-CVlgpUBr.js → GrRadioGroup-CEAfJQ6A.js} +1 -3
  191. package/dist/components/GrRadioGroup/chunks/{GrRadioGroup-CVlgpUBr.js.map → GrRadioGroup-CEAfJQ6A.js.map} +1 -1
  192. package/dist/components/GrRadioGroup/index.js +2 -2
  193. package/dist/components/GrSegmented/chunks/{GrSegmented-BPl1koHc.js → GrSegmented-Bwn2qOY4.js} +1 -3
  194. package/dist/components/GrSegmented/chunks/{GrSegmented-BPl1koHc.js.map → GrSegmented-Bwn2qOY4.js.map} +1 -1
  195. package/dist/components/GrSegmented/index.js +2 -2
  196. package/dist/components/GrSelect/chunks/{GrSelect-aVqUL-1F.js → GrSelect-9wRWAu2R.js} +3 -5
  197. package/dist/components/GrSelect/chunks/{GrSelect-aVqUL-1F.js.map → GrSelect-9wRWAu2R.js.map} +1 -1
  198. package/dist/components/GrSelect/index.js +2 -2
  199. package/dist/components/GrSidebar/chunks/{GrSidebar-DVucgcfV.js → GrSidebar-CSZ3RpFB.js} +0 -2
  200. package/dist/components/GrSidebar/chunks/{GrSidebar-DVucgcfV.js.map → GrSidebar-CSZ3RpFB.js.map} +1 -1
  201. package/dist/components/GrSidebar/index.js +2 -2
  202. package/dist/components/GrSkeleton/chunks/{GrSkeleton-CDTvCSoH.js → GrSkeleton-DKXkqWDa.js} +0 -2
  203. package/dist/components/GrSkeleton/chunks/{GrSkeleton-CDTvCSoH.js.map → GrSkeleton-DKXkqWDa.js.map} +1 -1
  204. package/dist/components/GrSkeleton/index.js +2 -2
  205. package/dist/components/GrSwitch/chunks/{GrSwitch-Bnamgqb2.js → GrSwitch-CdBxwV3k.js} +1 -3
  206. package/dist/components/GrSwitch/chunks/{GrSwitch-Bnamgqb2.js.map → GrSwitch-CdBxwV3k.js.map} +1 -1
  207. package/dist/components/GrSwitch/index.js +2 -2
  208. package/dist/components/GrTable/chunks/GrTable-CyQ9uw8A.js +0 -2
  209. package/dist/components/GrTable/index.js +1 -1
  210. package/dist/components/GrTabs/chunks/{GrTabs-CiGmcmvm.js → GrTabs-BpF8AAUz.js} +0 -2
  211. package/dist/components/GrTabs/chunks/{GrTabs-CiGmcmvm.js.map → GrTabs-BpF8AAUz.js.map} +1 -1
  212. package/dist/components/GrTabs/index.js +2 -2
  213. package/dist/components/GrTextarea/chunks/{GrTextarea-VBSc6A7D.js → GrTextarea-DFS9ZkrA.js} +1 -3
  214. package/dist/components/GrTextarea/chunks/{GrTextarea-VBSc6A7D.js.map → GrTextarea-DFS9ZkrA.js.map} +1 -1
  215. package/dist/components/GrTextarea/index.js +2 -2
  216. package/dist/components/GrToaster/chunks/{GrToaster-I1e-zCpf.js → GrToaster-3Mf0Moxt.js} +1 -3
  217. package/dist/components/GrToaster/chunks/{GrToaster-I1e-zCpf.js.map → GrToaster-3Mf0Moxt.js.map} +1 -1
  218. package/dist/components/GrToaster/index.js +2 -2
  219. package/dist/components/GrTooltip/chunks/{GrTooltip-CrV1gI_c.js → GrTooltip-BvmsukLW.js} +0 -2
  220. package/dist/components/GrTooltip/chunks/{GrTooltip-CrV1gI_c.js.map → GrTooltip-BvmsukLW.js.map} +1 -1
  221. package/dist/components/GrTooltip/index.js +2 -2
  222. package/dist/components/GrTree/chunks/{GrTree-Dlev9oBd.js → GrTree-C2kN-O4Q.js} +0 -2
  223. package/dist/components/GrTree/chunks/{GrTree-Dlev9oBd.js.map → GrTree-C2kN-O4Q.js.map} +1 -1
  224. package/dist/components/GrTree/index.js +2 -2
  225. package/dist/components/GrTreeSelect/chunks/{GrTreeSelect-DN6XFjOD.js → GrTreeSelect-Da-CpRqm.js} +5 -7
  226. package/dist/components/GrTreeSelect/chunks/{GrTreeSelect-DN6XFjOD.js.map → GrTreeSelect-Da-CpRqm.js.map} +1 -1
  227. package/dist/components/GrTreeSelect/index.js +2 -2
  228. package/dist/composables/useTheme.js +0 -2
  229. package/dist/composables/useToast.js +0 -2
  230. package/dist/directives/autofocus.js +55 -0
  231. package/dist/directives/autofocus.js.map +1 -0
  232. package/dist/directives/autosize.js +117 -0
  233. package/dist/directives/autosize.js.map +1 -0
  234. package/dist/directives/clickOutside.js +96 -0
  235. package/dist/directives/clickOutside.js.map +1 -0
  236. package/dist/directives/dropzone.js +164 -0
  237. package/dist/directives/dropzone.js.map +1 -0
  238. package/dist/directives/hotkey.js +138 -0
  239. package/dist/directives/hotkey.js.map +1 -0
  240. package/dist/directives/index.js +6 -1
  241. package/dist/directives/loading.js +163 -0
  242. package/dist/directives/loading.js.map +1 -0
  243. package/dist/fileValidation/index.js +1 -1
  244. package/dist/granular-provider-node.js +2 -4
  245. package/dist/granular-provider.js +1 -3
  246. package/dist/i18n/all.js +1 -3
  247. package/dist/i18n/index.js +2 -2
  248. package/dist/index.js +71 -66
  249. package/dist/vue/index.js +0 -2
  250. package/package.json +25 -1
  251. package/dist/chunks/config-BbtK511H.js.map +0 -1
  252. package/dist/chunks/config-BcFOkUhh.js.map +0 -1
  253. package/dist/chunks/config-BzQ4S4Hb.js.map +0 -1
  254. package/dist/chunks/config-CdAx6ify2.js.map +0 -1
  255. package/dist/chunks/config-KGTIAFD92.js.map +0 -1
  256. package/dist/chunks/directives-Cuib0yOO.js +0 -728
  257. package/dist/chunks/directives-Cuib0yOO.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"GrFileUpload-DVv8yXpl.js","names":["$slots"],"sources":["../../../../src/components/GrFileUpload/GrFileUpload.vue","../../../../src/components/GrFileUpload/GrFileUpload.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { Comment, computed, Fragment, nextTick, ref, Text, useSlots, type VNode } from 'vue'\n\nimport IconArrowUp from '~icons/lucide/arrow-up'\n\nimport GrIcon from '../GrIcon/GrIcon.vue'\nimport type { FileValidator, FileValidatorSource } from '../../fileValidation'\n\nimport { FileValidationError, runFileValidators } from '../../fileValidation'\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\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}\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 },\n)\n\nconst emit = defineEmits<{\n (e: 'success', payload: any): void\n (e: 'error', error: unknown): void\n (e: 'progress', percent: number): void\n (e: 'change', files: File[]): 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\nlet activeController: AbortController | null = null\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\nfunction appendExtraFormData(body: FormData, extraData: GrFileUploadExtraData | 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\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 const body = new FormData()\n for (const file of files)\n body.append(props.name, file)\n\n appendExtraFormData(body, extraData)\n\n const response = await fetch(props.action, {\n method: 'POST',\n headers: props.headers,\n body,\n signal,\n credentials: props.withCredentials ? 'include' : 'same-origin',\n })\n\n const contentType = response.headers.get('content-type')\n const payload = contentType?.includes('application/json') ? await response.json() : await response.text()\n\n if (!response.ok) {\n const error = new Error(`Upload failed with status ${response.status}`)\n ;(error as any).payload = payload\n ;(error as any).status = response.status\n throw error\n }\n\n return payload\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\n const controller = new AbortController()\n activeController = controller\n\n emit('progress', 0)\n\n try {\n const payload = props.request\n ? await props.request(valid, { signal: controller.signal, extraData })\n : await uploadViaAction(valid, controller.signal, extraData)\n\n emit('success', payload)\n emit('change', valid)\n emit('progress', 100)\n } catch (error) {\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\ndefineExpose({\n uploadFiles: handleFiles,\n abort,\n openDialog,\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 />\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 </div>\n </div>\n </div>\n</template>","<script setup lang=\"ts\">\nimport { Comment, computed, Fragment, nextTick, ref, Text, useSlots, type VNode } from 'vue'\n\nimport IconArrowUp from '~icons/lucide/arrow-up'\n\nimport GrIcon from '../GrIcon/GrIcon.vue'\nimport type { FileValidator, FileValidatorSource } from '../../fileValidation'\n\nimport { FileValidationError, runFileValidators } from '../../fileValidation'\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\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}\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 },\n)\n\nconst emit = defineEmits<{\n (e: 'success', payload: any): void\n (e: 'error', error: unknown): void\n (e: 'progress', percent: number): void\n (e: 'change', files: File[]): 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\nlet activeController: AbortController | null = null\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\nfunction appendExtraFormData(body: FormData, extraData: GrFileUploadExtraData | 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\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 const body = new FormData()\n for (const file of files)\n body.append(props.name, file)\n\n appendExtraFormData(body, extraData)\n\n const response = await fetch(props.action, {\n method: 'POST',\n headers: props.headers,\n body,\n signal,\n credentials: props.withCredentials ? 'include' : 'same-origin',\n })\n\n const contentType = response.headers.get('content-type')\n const payload = contentType?.includes('application/json') ? await response.json() : await response.text()\n\n if (!response.ok) {\n const error = new Error(`Upload failed with status ${response.status}`)\n ;(error as any).payload = payload\n ;(error as any).status = response.status\n throw error\n }\n\n return payload\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\n const controller = new AbortController()\n activeController = controller\n\n emit('progress', 0)\n\n try {\n const payload = props.request\n ? await props.request(valid, { signal: controller.signal, extraData })\n : await uploadViaAction(valid, controller.signal, extraData)\n\n emit('success', payload)\n emit('change', valid)\n emit('progress', 100)\n } catch (error) {\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\ndefineExpose({\n uploadFiles: handleFiles,\n abort,\n openDialog,\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 />\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 </div>\n </div>\n </div>\n</template>"],"mappings":"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"}
1
+ {"version":3,"file":"GrFileUpload-u01j9S0t.js","names":["$slots"],"sources":["../../../../src/components/GrFileUpload/GrFileUpload.vue","../../../../src/components/GrFileUpload/GrFileUpload.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { Comment, computed, Fragment, nextTick, ref, Text, useSlots, type VNode } from 'vue'\n\nimport IconArrowUp from '~icons/lucide/arrow-up'\n\nimport GrIcon from '../GrIcon/GrIcon.vue'\nimport type { FileValidator, FileValidatorSource } from '../../fileValidation'\n\nimport { FileValidationError, runFileValidators } from '../../fileValidation'\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\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}\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 },\n)\n\nconst emit = defineEmits<{\n (e: 'success', payload: any): void\n (e: 'error', error: unknown): void\n (e: 'progress', percent: number): void\n (e: 'change', files: File[]): 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\nlet activeController: AbortController | null = null\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\nfunction appendExtraFormData(body: FormData, extraData: GrFileUploadExtraData | 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\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 const body = new FormData()\n for (const file of files)\n body.append(props.name, file)\n\n appendExtraFormData(body, extraData)\n\n const response = await fetch(props.action, {\n method: 'POST',\n headers: props.headers,\n body,\n signal,\n credentials: props.withCredentials ? 'include' : 'same-origin',\n })\n\n const contentType = response.headers.get('content-type')\n const payload = contentType?.includes('application/json') ? await response.json() : await response.text()\n\n if (!response.ok) {\n const error = new Error(`Upload failed with status ${response.status}`)\n ;(error as any).payload = payload\n ;(error as any).status = response.status\n throw error\n }\n\n return payload\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\n const controller = new AbortController()\n activeController = controller\n\n emit('progress', 0)\n\n try {\n const payload = props.request\n ? await props.request(valid, { signal: controller.signal, extraData })\n : await uploadViaAction(valid, controller.signal, extraData)\n\n emit('success', payload)\n emit('change', valid)\n emit('progress', 100)\n } catch (error) {\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\ndefineExpose({\n uploadFiles: handleFiles,\n abort,\n openDialog,\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 />\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 </div>\n </div>\n </div>\n</template>","<script setup lang=\"ts\">\nimport { Comment, computed, Fragment, nextTick, ref, Text, useSlots, type VNode } from 'vue'\n\nimport IconArrowUp from '~icons/lucide/arrow-up'\n\nimport GrIcon from '../GrIcon/GrIcon.vue'\nimport type { FileValidator, FileValidatorSource } from '../../fileValidation'\n\nimport { FileValidationError, runFileValidators } from '../../fileValidation'\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\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}\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 },\n)\n\nconst emit = defineEmits<{\n (e: 'success', payload: any): void\n (e: 'error', error: unknown): void\n (e: 'progress', percent: number): void\n (e: 'change', files: File[]): 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\nlet activeController: AbortController | null = null\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\nfunction appendExtraFormData(body: FormData, extraData: GrFileUploadExtraData | 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\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 const body = new FormData()\n for (const file of files)\n body.append(props.name, file)\n\n appendExtraFormData(body, extraData)\n\n const response = await fetch(props.action, {\n method: 'POST',\n headers: props.headers,\n body,\n signal,\n credentials: props.withCredentials ? 'include' : 'same-origin',\n })\n\n const contentType = response.headers.get('content-type')\n const payload = contentType?.includes('application/json') ? await response.json() : await response.text()\n\n if (!response.ok) {\n const error = new Error(`Upload failed with status ${response.status}`)\n ;(error as any).payload = payload\n ;(error as any).status = response.status\n throw error\n }\n\n return payload\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\n const controller = new AbortController()\n activeController = controller\n\n emit('progress', 0)\n\n try {\n const payload = props.request\n ? await props.request(valid, { signal: controller.signal, extraData })\n : await uploadViaAction(valid, controller.signal, extraData)\n\n emit('success', payload)\n emit('change', valid)\n emit('progress', 100)\n } catch (error) {\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\ndefineExpose({\n uploadFiles: handleFiles,\n abort,\n openDialog,\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 />\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 </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-CgjTGoUx.js";
2
- import { t as GrFileUpload_default } from "./chunks/GrFileUpload-DVv8yXpl.js";
3
- import { t as grFileUploadConfig } from "../../chunks/config-BzQ4S4Hb.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-CwJA8Vmn.js";
2
+ import { t as GrFileUpload_default } from "./chunks/GrFileUpload-u01j9S0t.js";
3
+ import { t as grFileUploadConfig } from "../../chunks/config-CByfsV-z2.js";
4
4
  export { FileValidationError, GrFileUpload_default as GrFileUpload, GrFileUpload_default as default, acceptValidator, allowedExtensionsValidator, allowedMimeTypesValidator, grFileUploadConfig, matchAccept, maxFileSizeBytesValidator, maxSizeMbValidator, maxTotalSizeBytesValidator, normalizeFiles, runFileValidators };
@@ -33,5 +33,3 @@ var GrFormField_default = /* @__PURE__ */ defineComponent({
33
33
  });
34
34
  //#endregion
35
35
  export { GrFormField_default as t };
36
-
37
- //# sourceMappingURL=GrFormField-BzezYcS8.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"GrFormField-BzezYcS8.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-C6PfAs4u.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-BzezYcS8.js";
2
- import { t as grFormFieldConfig } from "../../chunks/config-BRU5PR8_.js";
1
+ import { t as GrFormField_default } from "./chunks/GrFormField-C6PfAs4u.js";
2
+ import { t as grFormFieldConfig } from "../../chunks/config-Dnu4VuO0.js";
3
3
  export { GrFormField_default as GrFormField, GrFormField_default as default, grFormFieldConfig };
@@ -2,8 +2,8 @@ import { t as x_default } from "../../../chunks/x-Pa6C2hwi.js";
2
2
  import { t as GrButton_default } from "../../GrButton/chunks/GrButton-BmGdp8-4.js";
3
3
  import { t as _plugin_vue_export_helper_default } from "../../../chunks/_plugin-vue_export-helper-BG_EqiDU.js";
4
4
  import { t as GrIcon_default } from "../../GrIcon/chunks/GrIcon-BV7RBU_0.js";
5
- import { i as vDropzone } from "../../../chunks/directives-Cuib0yOO.js";
6
- import { l as runFileValidators, o as acceptValidator, u as FileValidationError } from "../../../chunks/fileValidation-CgjTGoUx.js";
5
+ import { l as runFileValidators, o as acceptValidator, u as FileValidationError } from "../../../chunks/fileValidation-CwJA8Vmn.js";
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
9
9
  var _hoisted_1$1 = {
@@ -311,5 +311,3 @@ var GrFormFile_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @_
311
311
  }), [["__scopeId", "data-v-a60cb9f7"]]);
312
312
  //#endregion
313
313
  export { GrFormFile_default as t };
314
-
315
- //# sourceMappingURL=GrFormFile-DtZOypJW.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"GrFormFile-DtZOypJW.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-DdM8Bc2B.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-DtZOypJW.js";
2
- import { t as grFormFileConfig } from "../../chunks/config-rFwazRKi.js";
1
+ import { t as GrFormFile_default } from "./chunks/GrFormFile-DdM8Bc2B.js";
2
+ import { t as grFormFileConfig } from "../../chunks/config-C5SJXHQ4.js";
3
3
  export { GrFormFile_default as GrFormFile, GrFormFile_default as default, grFormFileConfig };
@@ -33,5 +33,3 @@ var GrFormSection_default = /* @__PURE__ */ defineComponent({
33
33
  });
34
34
  //#endregion
35
35
  export { GrFormSection_default as t };
36
-
37
- //# sourceMappingURL=GrFormSection-BIPB1RIk.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"GrFormSection-BIPB1RIk.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-5djEaQZQ.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-BIPB1RIk.js";
2
- import { t as grFormSectionConfig } from "../../chunks/config-dD6Po-Cq.js";
1
+ import { t as GrFormSection_default } from "./chunks/GrFormSection-5djEaQZQ.js";
2
+ import { t as grFormSectionConfig } from "../../chunks/config-BCPyma1I.js";
3
3
  export { GrFormSection_default as GrFormSection, GrFormSection_default as default, grFormSectionConfig };
@@ -30,5 +30,3 @@ var GrIcon_default = /* @__PURE__ */ defineComponent({
30
30
  });
31
31
  //#endregion
32
32
  export { GrIcon_default as t };
33
-
34
- //# sourceMappingURL=GrIcon-BV7RBU_0.js.map
@@ -395,5 +395,3 @@ var GrImageViewer_default = /* @__PURE__ */ defineComponent({
395
395
  });
396
396
  //#endregion
397
397
  export { GrImageViewer_default as t };
398
-
399
- //# sourceMappingURL=GrImageViewer-fn8ilJTA.js.map