@feugene/granularity 0.48.0 → 0.50.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (418) hide show
  1. package/README.md +3 -3
  2. package/dist/chunks/chevron-up-BhW6hi8L.js +23 -0
  3. package/dist/chunks/chevron-up-BhW6hi8L.js.map +1 -0
  4. package/dist/chunks/{config-E1cTyRVV.js → config-8n1VR0ju.js} +1 -1
  5. package/dist/chunks/{config-E1cTyRVV.js.map → config-8n1VR0ju.js.map} +1 -1
  6. package/dist/chunks/{config-2vrOFn2f.js → config-AKpLyIGD.js} +2 -2
  7. package/dist/chunks/{config-2vrOFn2f.js.map → config-AKpLyIGD.js.map} +1 -1
  8. package/dist/chunks/{config-DIDz_E5m.js → config-B34mkd53.js} +66 -1
  9. package/dist/chunks/config-B34mkd53.js.map +1 -0
  10. package/dist/chunks/{config-B5SBUa6u.js → config-B7qZqiUB.js} +28 -1
  11. package/dist/chunks/config-B7qZqiUB.js.map +1 -0
  12. package/dist/chunks/config-BBrPUEye.js +8 -0
  13. package/dist/chunks/config-BBrPUEye.js.map +1 -0
  14. package/dist/chunks/config-BBtBQE3s.js +108 -0
  15. package/dist/chunks/config-BBtBQE3s.js.map +1 -0
  16. package/dist/chunks/{config-CT4lP6S4.js → config-BOCzHqWh.js} +2 -2
  17. package/dist/chunks/{config-CT4lP6S4.js.map → config-BOCzHqWh.js.map} +1 -1
  18. package/dist/chunks/{config-iemgzS4x.js → config-BP1ktzwh.js} +2 -24
  19. package/dist/chunks/config-BP1ktzwh.js.map +1 -0
  20. package/dist/chunks/config-BTxmdRzT.js +28 -0
  21. package/dist/chunks/config-BTxmdRzT.js.map +1 -0
  22. package/dist/chunks/{config-CjWOTgLL.js → config-Bk52xd5l.js} +12 -5
  23. package/dist/chunks/config-Bk52xd5l.js.map +1 -0
  24. package/dist/chunks/{config-D3HOuHDe.js → config-C3SqBqSQ.js} +3 -1
  25. package/dist/chunks/config-C3SqBqSQ.js.map +1 -0
  26. package/dist/chunks/{config-2ZHRsdOc.js → config-C3uqd-sW.js} +1 -1
  27. package/dist/chunks/config-C3uqd-sW.js.map +1 -0
  28. package/dist/chunks/{config-BXRmsn3t.js → config-CPhcZzHi.js} +72 -10
  29. package/dist/chunks/config-CPhcZzHi.js.map +1 -0
  30. package/dist/chunks/config-CWtfxkM9.js.map +1 -1
  31. package/dist/chunks/{config--t2dxj0t.js → config-CmLTCmY9.js} +2 -2
  32. package/dist/chunks/{config--t2dxj0t.js.map → config-CmLTCmY9.js.map} +1 -1
  33. package/dist/chunks/{config-BAse0laT.js → config-CvtqB4ki.js} +8 -3
  34. package/dist/chunks/config-CvtqB4ki.js.map +1 -0
  35. package/dist/chunks/{config-BcXX7S7B.js → config-D-JgqV0J.js} +5 -1
  36. package/dist/chunks/{config-BcXX7S7B.js.map → config-D-JgqV0J.js.map} +1 -1
  37. package/dist/chunks/{config-BHLMTJaR.js → config-D1oVht-E.js} +8 -3
  38. package/dist/chunks/config-D1oVht-E.js.map +1 -0
  39. package/dist/chunks/{config-BscVxZR3.js → config-DF1ezU6y.js} +1 -1
  40. package/dist/chunks/{config-BscVxZR3.js.map → config-DF1ezU6y.js.map} +1 -1
  41. package/dist/chunks/{config-B42iJ48O.js → config-DG41PD9i.js} +13 -1
  42. package/dist/chunks/config-DG41PD9i.js.map +1 -0
  43. package/dist/chunks/{config-Bs1HwUcx.js → config-DPJ3jtiB.js} +12 -2
  44. package/dist/chunks/config-DPJ3jtiB.js.map +1 -0
  45. package/dist/chunks/{config-tNqtapcT.js → config-DpqjlCfB.js} +2 -2
  46. package/dist/chunks/{config-tNqtapcT.js.map → config-DpqjlCfB.js.map} +1 -1
  47. package/dist/chunks/{config-DNDgp9bB.js → config-DpzFoSIh.js} +64 -2
  48. package/dist/chunks/config-DpzFoSIh.js.map +1 -0
  49. package/dist/chunks/config-MUkhktTP.js +267 -0
  50. package/dist/chunks/config-MUkhktTP.js.map +1 -0
  51. package/dist/chunks/config-SPugeaHT.js +37 -0
  52. package/dist/chunks/config-SPugeaHT.js.map +1 -0
  53. package/dist/chunks/{en-Bxyn5lW7.js → en-mIFywnV2.js} +27 -7
  54. package/dist/chunks/en-mIFywnV2.js.map +1 -0
  55. package/dist/chunks/{es-Dtl-a7ui.js → es-CvthYwtB.js} +27 -7
  56. package/dist/chunks/es-CvthYwtB.js.map +1 -0
  57. package/dist/chunks/{es-Bj_vc18Z.js → es-DlGd6Mrj.js} +3 -3
  58. package/dist/chunks/{es-Bj_vc18Z.js.map → es-DlGd6Mrj.js.map} +1 -1
  59. package/dist/chunks/{generated-hELWB0gZ.js → generated-Cx4C7GRN.js} +126 -0
  60. package/dist/chunks/{generated-hELWB0gZ.js.map → generated-Cx4C7GRN.js.map} +1 -1
  61. package/dist/chunks/{grBadgeStyles-DV0DrAew.js → grBadgeStyles-D51AXbj5.js} +57 -1
  62. package/dist/chunks/grBadgeStyles-D51AXbj5.js.map +1 -0
  63. package/dist/chunks/{grChipStyles-DcDyxAL0.js → grChipStyles-CA9Wj1_E.js} +1 -1
  64. package/dist/chunks/{grChipStyles-DcDyxAL0.js.map → grChipStyles-CA9Wj1_E.js.map} +1 -1
  65. package/dist/chunks/{grPopoverStyles-CYAeO3Ov.js → grPopoverStyles-CncEkiPu.js} +1 -1
  66. package/dist/chunks/{grPopoverStyles-CYAeO3Ov.js.map → grPopoverStyles-CncEkiPu.js.map} +1 -1
  67. package/dist/chunks/{grSelectStyles-D9EQ-pgK.js → grSelectStyles-Cam-IG9s.js} +3 -2
  68. package/dist/chunks/grSelectStyles-Cam-IG9s.js.map +1 -0
  69. package/dist/chunks/{grSliderStyles-kaz6VXZ3.js → grSliderStyles-CqzV0hll.js} +1 -1
  70. package/dist/chunks/grSliderStyles-CqzV0hll.js.map +1 -0
  71. package/dist/chunks/{config-CbEbLyDE.js → grSortableListStyles-CxieDmpe.js} +1 -27
  72. package/dist/chunks/grSortableListStyles-CxieDmpe.js.map +1 -0
  73. package/dist/chunks/grTableStyles-CPSxDTOW.js +66 -0
  74. package/dist/chunks/grTableStyles-CPSxDTOW.js.map +1 -0
  75. package/dist/chunks/{config-CMNfZO7S.js → grTabsStyles-DIz2VdIm.js} +1 -36
  76. package/dist/chunks/grTabsStyles-DIz2VdIm.js.map +1 -0
  77. package/dist/chunks/grTooltipStyles-CtbGEEGV.js +24 -0
  78. package/dist/chunks/grTooltipStyles-CtbGEEGV.js.map +1 -0
  79. package/dist/chunks/{granular-provider-BOQp7N5o.js → granular-provider-cv5vaM1a.js} +26 -22
  80. package/dist/chunks/granular-provider-cv5vaM1a.js.map +1 -0
  81. package/dist/chunks/hotkeySequence-BTHIM_5A.js +46 -0
  82. package/dist/chunks/hotkeySequence-BTHIM_5A.js.map +1 -0
  83. package/dist/chunks/{keyboard-DE2yhz_b.js → keyboard-BiFSgWLt.js} +70 -1
  84. package/dist/chunks/keyboard-BiFSgWLt.js.map +1 -0
  85. package/dist/chunks/optionPanel-DvY7Tv_4.js +182 -0
  86. package/dist/chunks/optionPanel-DvY7Tv_4.js.map +1 -0
  87. package/dist/chunks/overlayListPanel-D91jBCHU.js +21 -0
  88. package/dist/chunks/overlayListPanel-D91jBCHU.js.map +1 -0
  89. package/dist/chunks/{overlayStack-DH4Z7am1.js → overlayStack-DCkCgXZP.js} +1 -1
  90. package/dist/chunks/{overlayStack-DH4Z7am1.js.map → overlayStack-DCkCgXZP.js.map} +1 -1
  91. package/dist/chunks/{overlayPanelSurface-BKeMhRx4.js → overlayTransition-C0FDMx7Q.js} +24 -24
  92. package/dist/chunks/overlayTransition-C0FDMx7Q.js.map +1 -0
  93. package/dist/chunks/{ru-hrB4I1u_.js → ru-CQKDR-kD.js} +27 -7
  94. package/dist/chunks/ru-CQKDR-kD.js.map +1 -0
  95. package/dist/chunks/{safelist-x8USecK8.js → safelist-CSfynUC1.js} +5 -1
  96. package/dist/chunks/safelist-CSfynUC1.js.map +1 -0
  97. package/dist/chunks/{useMenuItemsFocus-B7Bl_aFA.js → useMenuItemsFocus-C031blYS.js} +1 -1
  98. package/dist/chunks/{useMenuItemsFocus-B7Bl_aFA.js.map → useMenuItemsFocus-C031blYS.js.map} +1 -1
  99. package/dist/chunks/{useModalOverlay-ACRkflzx.js → useModalOverlay-CKCQkGdB.js} +1 -1
  100. package/dist/chunks/{useModalOverlay-ACRkflzx.js.map → useModalOverlay-CKCQkGdB.js.map} +1 -1
  101. package/dist/components/GrAutocomplete/chunks/{GrAutocomplete-C-uovdv8.js → GrAutocomplete-Cyr1-ENw.js} +183 -125
  102. package/dist/components/GrAutocomplete/chunks/GrAutocomplete-Cyr1-ENw.js.map +1 -0
  103. package/dist/components/GrAutocomplete/index.js +2 -2
  104. package/dist/components/GrAutocomplete/styles.css +1 -1
  105. package/dist/components/GrBadge/chunks/GrBadge-C42KLyUD.js +71 -0
  106. package/dist/components/GrBadge/chunks/GrBadge-C42KLyUD.js.map +1 -0
  107. package/dist/components/GrBadge/index.js +3 -3
  108. package/dist/components/GrBadge/styles.css +1 -1
  109. package/dist/components/GrCarousel/chunks/{GrCarousel-ByuughtI.js → GrCarousel-CBj-PEPB.js} +186 -37
  110. package/dist/components/GrCarousel/chunks/GrCarousel-CBj-PEPB.js.map +1 -0
  111. package/dist/components/GrCarousel/index.js +2 -2
  112. package/dist/components/GrCarousel/styles.css +1 -1
  113. package/dist/components/GrCheckbox/chunks/{GrCheckbox-Dp201frb.js → GrCheckbox-B4NJc7Ur.js} +2 -1
  114. package/dist/components/GrCheckbox/chunks/GrCheckbox-B4NJc7Ur.js.map +1 -0
  115. package/dist/components/GrCheckbox/index.js +1 -1
  116. package/dist/components/GrCheckboxGroup/chunks/{GrCheckboxGroup-IpqG5qwm.js → GrCheckboxGroup-qZHpNFCX.js} +1 -1
  117. package/dist/components/GrCheckboxGroup/chunks/{GrCheckboxGroup-IpqG5qwm.js.map → GrCheckboxGroup-qZHpNFCX.js.map} +1 -1
  118. package/dist/components/GrCheckboxGroup/index.js +1 -1
  119. package/dist/components/GrChip/chunks/{GrChip-CKqyzYcv.js → GrChip-DoyS1Dr4.js} +1 -1
  120. package/dist/components/GrChip/chunks/{GrChip-CKqyzYcv.js.map → GrChip-DoyS1Dr4.js.map} +1 -1
  121. package/dist/components/GrChip/index.js +2 -2
  122. package/dist/components/GrColorPicker/chunks/{GrColorPicker-BRM_4lyI.js → GrColorPicker-vGSgpUV9.js} +353 -38
  123. package/dist/components/GrColorPicker/chunks/GrColorPicker-vGSgpUV9.js.map +1 -0
  124. package/dist/components/GrColorPicker/index.js +2 -2
  125. package/dist/components/GrColorPicker/styles.css +1 -1
  126. package/dist/components/GrCommandPalette/chunks/{GrCommandPalette-BJHmkDQv.js → GrCommandPalette-CAHgj0Aa.js} +23 -7
  127. package/dist/components/GrCommandPalette/chunks/GrCommandPalette-CAHgj0Aa.js.map +1 -0
  128. package/dist/components/GrCommandPalette/index.js +1 -1
  129. package/dist/components/GrCommandPalette/styles.css +1 -1
  130. package/dist/components/GrConfirmDialog/chunks/{GrConfirmDialog-BzUQwbec.js → GrConfirmDialog-dg021BMd.js} +1 -1
  131. package/dist/components/GrConfirmDialog/chunks/{GrConfirmDialog-BzUQwbec.js.map → GrConfirmDialog-dg021BMd.js.map} +1 -1
  132. package/dist/components/GrConfirmDialog/index.js +1 -1
  133. package/dist/components/GrContextMenu/chunks/{GrContextMenu-RpxMHQXH.js → GrContextMenu-bQ1Vvy2c.js} +3 -3
  134. package/dist/components/GrContextMenu/chunks/{GrContextMenu-RpxMHQXH.js.map → GrContextMenu-bQ1Vvy2c.js.map} +1 -1
  135. package/dist/components/GrContextMenu/index.js +1 -1
  136. package/dist/components/GrDataTable/chunks/{GrDataTable-DQxYYgQk.js → GrDataTable-Bb2jWnRG.js} +576 -136
  137. package/dist/components/GrDataTable/chunks/GrDataTable-Bb2jWnRG.js.map +1 -0
  138. package/dist/components/GrDataTable/index.js +2 -2
  139. package/dist/components/GrDelta/chunks/{GrDelta-CgTvfoIN.js → GrDelta-BSv1KdMy.js} +0 -0
  140. package/dist/components/GrDelta/chunks/{GrDelta-CgTvfoIN.js.map → GrDelta-BSv1KdMy.js.map} +1 -1
  141. package/dist/components/GrDelta/index.js +2 -2
  142. package/dist/components/GrDialog/chunks/{GrDialog-DDz_OEhk.js → GrDialog-C9Zu72jB.js} +1 -1
  143. package/dist/components/GrDialog/chunks/{GrDialog-DDz_OEhk.js.map → GrDialog-C9Zu72jB.js.map} +1 -1
  144. package/dist/components/GrDialog/index.js +1 -1
  145. package/dist/components/GrDialogService/chunks/{GrDialogService-Dtq0ZCN5.js → GrDialogService-DLBifO61.js} +2 -2
  146. package/dist/components/GrDialogService/chunks/{GrDialogService-Dtq0ZCN5.js.map → GrDialogService-DLBifO61.js.map} +1 -1
  147. package/dist/components/GrDialogService/index.js +1 -1
  148. package/dist/components/GrDrawer/chunks/{GrDrawer-CNpSMuN-.js → GrDrawer-DOcRTE8G.js} +1 -1
  149. package/dist/components/GrDrawer/chunks/{GrDrawer-CNpSMuN-.js.map → GrDrawer-DOcRTE8G.js.map} +1 -1
  150. package/dist/components/GrDrawer/index.js +1 -1
  151. package/dist/components/GrDropdown/chunks/{GrDropdown-cLZuZMEY.js → GrDropdown-kxwmgfSe.js} +2 -2
  152. package/dist/components/GrDropdown/chunks/{GrDropdown-cLZuZMEY.js.map → GrDropdown-kxwmgfSe.js.map} +1 -1
  153. package/dist/components/GrDropdown/index.js +2 -2
  154. package/dist/components/GrDropdownMenu/chunks/{GrDropdownMenu-Dxkz_ry4.js → GrDropdownMenu--VoK3VY0.js} +3 -34
  155. package/dist/components/GrDropdownMenu/chunks/GrDropdownMenu--VoK3VY0.js.map +1 -0
  156. package/dist/components/GrDropdownMenu/chunks/GrDropdownMenu-CkM3BFPL.js +34 -0
  157. package/dist/components/GrDropdownMenu/chunks/GrDropdownMenu-CkM3BFPL.js.map +1 -0
  158. package/dist/components/GrDropdownMenu/index.js +2 -1
  159. package/dist/components/GrEmptyState/chunks/{GrEmptyState-BbvwE-E0.js → GrEmptyState-Bz6-_ED4.js} +42 -6
  160. package/dist/components/GrEmptyState/chunks/GrEmptyState-Bz6-_ED4.js.map +1 -0
  161. package/dist/components/GrEmptyState/index.js +1 -1
  162. package/dist/components/GrFileUpload/chunks/{GrFileUpload-BwMR71zN.js → GrFileUpload-Bgdfnkd8.js} +5 -2
  163. package/dist/components/GrFileUpload/chunks/GrFileUpload-Bgdfnkd8.js.map +1 -0
  164. package/dist/components/GrFileUpload/index.js +1 -1
  165. package/dist/components/GrFormFile/chunks/{GrFormFile-Dlxx-9zi.js → GrFormFile-Ctq1isz-.js} +78 -11
  166. package/dist/components/GrFormFile/chunks/GrFormFile-Ctq1isz-.js.map +1 -0
  167. package/dist/components/GrFormFile/index.js +2 -2
  168. package/dist/components/GrFormFile/styles.css +1 -1
  169. package/dist/components/GrImageViewer/chunks/{GrImageViewer-DK-iVHHC.js → GrImageViewer-Bnql_dNP.js} +1 -1
  170. package/dist/components/GrImageViewer/chunks/{GrImageViewer-DK-iVHHC.js.map → GrImageViewer-Bnql_dNP.js.map} +1 -1
  171. package/dist/components/GrImageViewer/index.js +1 -1
  172. package/dist/components/GrInput/chunks/{GrInput-DV4gMWPR.js → GrInput-CoaFLpaU.js} +43 -22
  173. package/dist/components/GrInput/chunks/GrInput-CoaFLpaU.js.map +1 -0
  174. package/dist/components/GrInput/index.js +1 -1
  175. package/dist/components/GrInputTag/chunks/{GrInputTag-CO0YU-ok.js → GrInputTag-BzzNRBe3.js} +247 -20
  176. package/dist/components/GrInputTag/chunks/GrInputTag-BzzNRBe3.js.map +1 -0
  177. package/dist/components/GrInputTag/index.js +2 -2
  178. package/dist/components/GrJsonViewer/chunks/{GrJsonViewer-D7CyWzhT.js → GrJsonViewer-B6ZJ3GFH.js} +2 -2
  179. package/dist/components/GrJsonViewer/chunks/{GrJsonViewer-D7CyWzhT.js.map → GrJsonViewer-B6ZJ3GFH.js.map} +1 -1
  180. package/dist/components/GrJsonViewer/index.js +1 -1
  181. package/dist/components/GrKbd/chunks/{GrKbd-BNMeoaLV.js → GrKbd-D2rHDkQv.js} +42 -37
  182. package/dist/components/GrKbd/chunks/GrKbd-D2rHDkQv.js.map +1 -0
  183. package/dist/components/GrKbd/index.js +1 -1
  184. package/dist/components/GrModal/chunks/{GrModal-Cm0mtSXk.js → GrModal-SHb1U2qx.js} +1 -1
  185. package/dist/components/GrModal/chunks/{GrModal-Cm0mtSXk.js.map → GrModal-SHb1U2qx.js.map} +1 -1
  186. package/dist/components/GrModal/index.js +1 -1
  187. package/dist/components/GrNavbar/chunks/{GrNavbar-BRGuksiy.js → GrNavbar-IEIHC5Vt.js} +10 -4
  188. package/dist/components/GrNavbar/chunks/GrNavbar-IEIHC5Vt.js.map +1 -0
  189. package/dist/components/GrNavbar/index.js +2 -2
  190. package/dist/components/GrNumberInput/chunks/{GrNumberInput-BI6-vkcs.js → GrNumberInput-CmnUDaKi.js} +2 -22
  191. package/dist/components/GrNumberInput/chunks/{GrNumberInput-BI6-vkcs.js.map → GrNumberInput-CmnUDaKi.js.map} +1 -1
  192. package/dist/components/GrNumberInput/index.js +1 -1
  193. package/dist/components/GrOtpInput/chunks/GrOtpInput-B_Q-RDLw.js +419 -0
  194. package/dist/components/GrOtpInput/chunks/GrOtpInput-B_Q-RDLw.js.map +1 -0
  195. package/dist/components/GrOtpInput/index.js +3 -0
  196. package/dist/components/GrOtpInput/styles.css +1 -0
  197. package/dist/components/GrPagination/chunks/{GrPagination-BbaF5eXk.js → GrPagination-AMbn5D8I.js} +1 -1
  198. package/dist/components/GrPagination/chunks/{GrPagination-BbaF5eXk.js.map → GrPagination-AMbn5D8I.js.map} +1 -1
  199. package/dist/components/GrPagination/index.js +1 -1
  200. package/dist/components/GrPopover/chunks/{GrPopover-CcWvr1ZA.js → GrPopover-DjVxt8bf.js} +3 -3
  201. package/dist/components/GrPopover/chunks/{GrPopover-CcWvr1ZA.js.map → GrPopover-DjVxt8bf.js.map} +1 -1
  202. package/dist/components/GrPopover/index.js +2 -2
  203. package/dist/components/GrPromptDialog/chunks/{GrPromptDialog-BEL-QgwE.js → GrPromptDialog-BhbIZP1f.js} +3 -3
  204. package/dist/components/GrPromptDialog/chunks/{GrPromptDialog-BEL-QgwE.js.map → GrPromptDialog-BhbIZP1f.js.map} +1 -1
  205. package/dist/components/GrPromptDialog/index.js +1 -1
  206. package/dist/components/GrSelect/chunks/{GrSelect-0iI4wdR_.js → GrSelect-B-ykrHpL.js} +26 -182
  207. package/dist/components/GrSelect/chunks/GrSelect-B-ykrHpL.js.map +1 -0
  208. package/dist/components/GrSelect/index.js +2 -2
  209. package/dist/components/GrSelect/styles.css +1 -1
  210. package/dist/components/GrSidebar/chunks/GrSidebar-jVvoLGDO.js +543 -0
  211. package/dist/components/GrSidebar/chunks/GrSidebar-jVvoLGDO.js.map +1 -0
  212. package/dist/components/GrSidebar/index.js +2 -2
  213. package/dist/components/GrSlider/chunks/{GrSlider-CO8N9SF6.js → GrSlider-DF2Cq0rV.js} +29 -4
  214. package/dist/components/GrSlider/chunks/GrSlider-DF2Cq0rV.js.map +1 -0
  215. package/dist/components/GrSlider/index.js +2 -2
  216. package/dist/components/GrSortableList/chunks/{GrSortableList-NKriuGm_.js → GrSortableList-ChH9nQBv.js} +1 -1
  217. package/dist/components/GrSortableList/chunks/{GrSortableList-NKriuGm_.js.map → GrSortableList-ChH9nQBv.js.map} +1 -1
  218. package/dist/components/GrSortableList/index.js +2 -2
  219. package/dist/components/GrSwitch/chunks/{GrSwitch-CriHWT2A.js → GrSwitch-D7X42uIA.js} +61 -7
  220. package/dist/components/GrSwitch/chunks/GrSwitch-D7X42uIA.js.map +1 -0
  221. package/dist/components/GrSwitch/index.js +2 -2
  222. package/dist/components/GrTabPanels/chunks/GrTabPanels-BDt5oVdd.js +30 -0
  223. package/dist/components/GrTabPanels/chunks/GrTabPanels-BDt5oVdd.js.map +1 -0
  224. package/dist/components/GrTabPanels/chunks/GrTabPanels-BJvAflnA.js +83 -0
  225. package/dist/components/GrTabPanels/chunks/GrTabPanels-BJvAflnA.js.map +1 -0
  226. package/dist/components/GrTabPanels/index.js +2 -1
  227. package/dist/components/GrTable/chunks/{GrTable-CKJ-HrsN.js → GrTable-BQitSDep.js} +52 -16
  228. package/dist/components/GrTable/chunks/GrTable-BQitSDep.js.map +1 -0
  229. package/dist/components/GrTable/index.js +2 -2
  230. package/dist/components/GrTabs/chunks/{GrTabs-s9RzC1WX.js → GrTabs-DVC6nfWB.js} +1 -1
  231. package/dist/components/GrTabs/chunks/{GrTabs-s9RzC1WX.js.map → GrTabs-DVC6nfWB.js.map} +1 -1
  232. package/dist/components/GrTabs/index.js +2 -2
  233. package/dist/components/GrTabsWithPanels/chunks/GrTabsWithPanels-BYtuzouj.js +98 -0
  234. package/dist/components/GrTabsWithPanels/chunks/GrTabsWithPanels-BYtuzouj.js.map +1 -0
  235. package/dist/components/GrTabsWithPanels/index.js +3 -0
  236. package/dist/components/GrTextarea/chunks/{GrTextarea-Bfgc0DD2.js → GrTextarea-BFwyqcHB.js} +22 -5
  237. package/dist/components/GrTextarea/chunks/GrTextarea-BFwyqcHB.js.map +1 -0
  238. package/dist/components/GrTextarea/index.js +1 -1
  239. package/dist/components/GrToaster/chunks/{GrToaster-CZixNk0l.js → GrToaster-OzT1VKcp.js} +163 -32
  240. package/dist/components/GrToaster/chunks/GrToaster-OzT1VKcp.js.map +1 -0
  241. package/dist/components/GrToaster/index.js +2 -2
  242. package/dist/components/GrToaster/styles.css +1 -1
  243. package/dist/components/GrTooltip/chunks/{GrTooltip-DkMMM0uq.js → GrTooltip-BDcFycvW.js} +26 -13
  244. package/dist/components/GrTooltip/chunks/GrTooltip-BDcFycvW.js.map +1 -0
  245. package/dist/components/GrTooltip/index.js +2 -2
  246. package/dist/components/GrTransfer/chunks/{GrTransfer-DdfBUf35.js → GrTransfer-CV7BL5UK.js} +114 -12
  247. package/dist/components/GrTransfer/chunks/GrTransfer-CV7BL5UK.js.map +1 -0
  248. package/dist/components/GrTransfer/index.js +1 -1
  249. package/dist/components/GrTransfer/styles.css +1 -1
  250. package/dist/components/GrTree/chunks/{GrTree-6yZrncJE.js → GrTree-DpgaP8hk.js} +39 -1
  251. package/dist/components/GrTree/chunks/GrTree-DpgaP8hk.js.map +1 -0
  252. package/dist/components/GrTree/index.js +1 -1
  253. package/dist/components/GrTree/styles.css +1 -1
  254. package/dist/components/GrTreeSections/chunks/{GrTreeSections-DcmDcLwj.js → GrTreeSections-BeOj__44.js} +1 -1
  255. package/dist/components/GrTreeSections/chunks/{GrTreeSections-DcmDcLwj.js.map → GrTreeSections-BeOj__44.js.map} +1 -1
  256. package/dist/components/GrTreeSections/index.js +1 -1
  257. package/dist/components/GrTreeSelect/chunks/{GrTreeSelect-B6KoDYiZ.js → GrTreeSelect-BHq-T8wH.js} +113 -14
  258. package/dist/components/GrTreeSelect/chunks/GrTreeSelect-BHq-T8wH.js.map +1 -0
  259. package/dist/components/GrTreeSelect/index.js +2 -2
  260. package/dist/composables/useFloating.js +1 -1
  261. package/dist/composables/useHotkeys.js +89 -0
  262. package/dist/composables/useHotkeys.js.map +1 -0
  263. package/dist/composables/useOverlayLayer.js +1 -1
  264. package/dist/directives/hotkey.js +19 -46
  265. package/dist/directives/hotkey.js.map +1 -1
  266. package/dist/granular-provider-node.js +1 -1
  267. package/dist/granular-provider.js +1 -1
  268. package/dist/i18n/all.js +1 -1
  269. package/dist/i18n/index.js +1 -1
  270. package/dist/index.js +80 -71
  271. package/dist/theme.js +1 -1
  272. package/dist/tokens.js +1 -1
  273. package/dist/types/components/GrAutocomplete/GrAutocomplete.vue.d.ts +4 -4
  274. package/dist/types/components/GrAutocomplete/composables/useAutocompleteNavigation.d.ts +12 -3
  275. package/dist/types/components/GrAutocomplete/composables/useAutocompletePanel.d.ts +12 -2
  276. package/dist/types/components/GrAutocomplete/composables/useAutocompleteValues.d.ts +7 -4
  277. package/dist/types/components/GrAutocomplete/composables/useRemoteOptions.d.ts +4 -4
  278. package/dist/types/components/GrAutocomplete/grAutocompleteStyles.d.ts +15 -1
  279. package/dist/types/components/GrAutocomplete/index.d.ts +1 -1
  280. package/dist/types/components/GrBadge/GrBadge.vue.d.ts +15 -1
  281. package/dist/types/components/GrBadge/grBadgeStyles.d.ts +57 -0
  282. package/dist/types/components/GrBadge/index.d.ts +1 -1
  283. package/dist/types/components/GrCarousel/GrCarousel.vue.d.ts +29 -1
  284. package/dist/types/components/GrCarousel/composables/useCarouselSwipe.d.ts +5 -0
  285. package/dist/types/components/GrCarousel/grCarouselContext.d.ts +21 -0
  286. package/dist/types/components/GrCarousel/grCarouselStyles.d.ts +39 -9
  287. package/dist/types/components/GrCheckbox/GrCheckbox.vue.d.ts +13 -0
  288. package/dist/types/components/GrColorPicker/GrColorPicker.vue.d.ts +18 -2
  289. package/dist/types/components/GrColorPicker/color.d.ts +13 -1
  290. package/dist/types/components/GrColorPicker/colorArea.d.ts +59 -0
  291. package/dist/types/components/GrColorPicker/defaults.d.ts +3 -1
  292. package/dist/types/components/GrColorPicker/eyeDropper.d.ts +21 -0
  293. package/dist/types/components/GrColorPicker/grColorPickerStyles.d.ts +42 -0
  294. package/dist/types/components/GrCommandPalette/GrCommandPalette.vue.d.ts +10 -1
  295. package/dist/types/components/GrDataTable/GrDataTable.vue.d.ts +41 -1
  296. package/dist/types/components/GrDataTable/composables/useDataTableGridNavigation.d.ts +53 -0
  297. package/dist/types/components/GrDataTable/grDataTableStyles.d.ts +8 -0
  298. package/dist/types/components/GrDataTable/index.d.ts +1 -1
  299. package/dist/types/components/GrDelta/grDeltaStyles.d.ts +10 -3
  300. package/dist/types/components/GrEmptyState/GrEmptyState.vue.d.ts +11 -2
  301. package/dist/types/components/GrEmptyState/grEmptyStateStyles.d.ts +10 -0
  302. package/dist/types/components/GrEmptyState/index.d.ts +1 -1
  303. package/dist/types/components/GrFileUpload/GrFileUpload.vue.d.ts +2 -1
  304. package/dist/types/components/GrFormFile/GrFormFile.vue.d.ts +10 -0
  305. package/dist/types/components/GrInput/GrInput.vue.d.ts +15 -0
  306. package/dist/types/components/GrInputTag/GrInputTag.vue.d.ts +16 -0
  307. package/dist/types/components/GrInputTag/grInputTagStyles.d.ts +11 -0
  308. package/dist/types/components/GrKbd/GrKbd.vue.d.ts +14 -0
  309. package/dist/types/components/GrNavbar/GrNavbar.vue.d.ts +18 -0
  310. package/dist/types/components/GrNavbar/grNavbarStyles.d.ts +10 -1
  311. package/dist/types/components/GrNavbar/index.d.ts +1 -0
  312. package/dist/types/components/GrOtpInput/GrOtpInput.vue.d.ts +164 -0
  313. package/dist/types/components/GrOtpInput/config.d.ts +1 -0
  314. package/dist/types/components/GrOtpInput/defaults.d.ts +15 -0
  315. package/dist/types/components/GrOtpInput/grOtpInputStyles.d.ts +62 -0
  316. package/dist/types/components/GrOtpInput/index.d.ts +9 -0
  317. package/dist/types/components/GrOtpInput/otpValue.d.ts +71 -0
  318. package/dist/types/components/GrOtpInput/safelist.d.ts +7 -0
  319. package/dist/types/components/GrSelect/composables/useSelectPanelItems.d.ts +8 -34
  320. package/dist/types/components/GrSelect/composables/useSelectVirtualization.d.ts +7 -0
  321. package/dist/types/components/GrSelect/grSelectStyles.d.ts +1 -1
  322. package/dist/types/components/GrSidebar/GrSidebar.vue.d.ts +20 -0
  323. package/dist/types/components/GrSidebar/GrSidebarItem.vue.d.ts +39 -5
  324. package/dist/types/components/GrSidebar/grSidebarStyles.d.ts +32 -0
  325. package/dist/types/components/GrSidebar/index.d.ts +1 -1
  326. package/dist/types/components/GrSidebar/sidebarContext.d.ts +23 -1
  327. package/dist/types/components/GrSlider/GrSlider.vue.d.ts +39 -1
  328. package/dist/types/components/GrSlider/grSliderStyles.d.ts +1 -1
  329. package/dist/types/components/GrSwitch/GrSwitch.vue.d.ts +42 -4
  330. package/dist/types/components/GrSwitch/defaults.d.ts +2 -0
  331. package/dist/types/components/GrSwitch/grSwitchStyles.d.ts +129 -25
  332. package/dist/types/components/GrSwitch/index.d.ts +1 -1
  333. package/dist/types/components/GrSwitch/safelist.d.ts +4 -3
  334. package/dist/types/components/GrTabPanels/context.d.ts +6 -0
  335. package/dist/types/components/GrTable/GrTable.vue.d.ts +37 -0
  336. package/dist/types/components/GrTable/grTableStyles.d.ts +16 -7
  337. package/dist/types/components/GrTabsWithPanels/GrTabsWithPanels.vue.d.ts +77 -0
  338. package/dist/types/components/GrTabsWithPanels/config.d.ts +1 -0
  339. package/dist/types/components/GrTabsWithPanels/index.d.ts +4 -0
  340. package/dist/types/components/GrTextarea/GrTextarea.vue.d.ts +19 -1
  341. package/dist/types/components/GrToaster/GrToaster.vue.d.ts +12 -0
  342. package/dist/types/components/GrToaster/grToasterStyles.d.ts +26 -0
  343. package/dist/types/components/GrTooltip/GrTooltip.vue.d.ts +22 -0
  344. package/dist/types/components/GrTransfer/GrTransfer.vue.d.ts +23 -0
  345. package/dist/types/components/GrTreeSelect/grTreeSelectStyles.d.ts +1 -1
  346. package/dist/types/components/GrTreeSelect/grTreeSelectTypes.d.ts +20 -0
  347. package/dist/types/components/shared/hotkey.d.ts +8 -9
  348. package/dist/types/components/shared/optionPanel.d.ts +100 -0
  349. package/dist/types/components/shared/overlayListPanel.d.ts +18 -0
  350. package/dist/types/composables/useHotkeys.d.ts +43 -0
  351. package/dist/types/granular-provider/shared.d.ts +2 -0
  352. package/dist/types/i18n/messages/en.d.ts +32 -0
  353. package/dist/types/i18n/messages/es.d.ts +32 -0
  354. package/dist/types/i18n/messages/ru.d.ts +32 -0
  355. package/dist/types/index.d.ts +4 -0
  356. package/dist/types/internal/hotkeySequence.d.ts +22 -0
  357. package/dist/types/internal/keyboard.d.ts +45 -0
  358. package/dist/web-types.json +784 -101
  359. package/package.json +13 -1
  360. package/dist/chunks/config-2ZHRsdOc.js.map +0 -1
  361. package/dist/chunks/config-B42iJ48O.js.map +0 -1
  362. package/dist/chunks/config-B5SBUa6u.js.map +0 -1
  363. package/dist/chunks/config-BAse0laT.js.map +0 -1
  364. package/dist/chunks/config-BHLMTJaR.js.map +0 -1
  365. package/dist/chunks/config-BXRmsn3t.js.map +0 -1
  366. package/dist/chunks/config-Bs1HwUcx.js.map +0 -1
  367. package/dist/chunks/config-CMNfZO7S.js.map +0 -1
  368. package/dist/chunks/config-CbEbLyDE.js.map +0 -1
  369. package/dist/chunks/config-CjWOTgLL.js.map +0 -1
  370. package/dist/chunks/config-D3HOuHDe.js.map +0 -1
  371. package/dist/chunks/config-DIDz_E5m.js.map +0 -1
  372. package/dist/chunks/config-DNDgp9bB.js.map +0 -1
  373. package/dist/chunks/config-Yy67uWHY.js +0 -140
  374. package/dist/chunks/config-Yy67uWHY.js.map +0 -1
  375. package/dist/chunks/config-iemgzS4x.js.map +0 -1
  376. package/dist/chunks/en-Bxyn5lW7.js.map +0 -1
  377. package/dist/chunks/es-Dtl-a7ui.js.map +0 -1
  378. package/dist/chunks/grBadgeStyles-DV0DrAew.js.map +0 -1
  379. package/dist/chunks/grSelectStyles-D9EQ-pgK.js.map +0 -1
  380. package/dist/chunks/grSliderStyles-kaz6VXZ3.js.map +0 -1
  381. package/dist/chunks/grTableStyles-DkQPMfx9.js +0 -39
  382. package/dist/chunks/grTableStyles-DkQPMfx9.js.map +0 -1
  383. package/dist/chunks/granular-provider-BOQp7N5o.js.map +0 -1
  384. package/dist/chunks/keyboard-DE2yhz_b.js.map +0 -1
  385. package/dist/chunks/overlayPanelSurface-BKeMhRx4.js.map +0 -1
  386. package/dist/chunks/ru-hrB4I1u_.js.map +0 -1
  387. package/dist/chunks/safelist-x8USecK8.js.map +0 -1
  388. package/dist/components/GrAutocomplete/chunks/GrAutocomplete-C-uovdv8.js.map +0 -1
  389. package/dist/components/GrBadge/chunks/GrBadge-DBeQXEir.js +0 -47
  390. package/dist/components/GrBadge/chunks/GrBadge-DBeQXEir.js.map +0 -1
  391. package/dist/components/GrCarousel/chunks/GrCarousel-ByuughtI.js.map +0 -1
  392. package/dist/components/GrCheckbox/chunks/GrCheckbox-Dp201frb.js.map +0 -1
  393. package/dist/components/GrColorPicker/chunks/GrColorPicker-BRM_4lyI.js.map +0 -1
  394. package/dist/components/GrCommandPalette/chunks/GrCommandPalette-BJHmkDQv.js.map +0 -1
  395. package/dist/components/GrDataTable/chunks/GrDataTable-DQxYYgQk.js.map +0 -1
  396. package/dist/components/GrDropdownMenu/chunks/GrDropdownMenu-Dxkz_ry4.js.map +0 -1
  397. package/dist/components/GrEmptyState/chunks/GrEmptyState-BbvwE-E0.js.map +0 -1
  398. package/dist/components/GrFileUpload/chunks/GrFileUpload-BwMR71zN.js.map +0 -1
  399. package/dist/components/GrFormFile/chunks/GrFormFile-Dlxx-9zi.js.map +0 -1
  400. package/dist/components/GrInput/chunks/GrInput-DV4gMWPR.js.map +0 -1
  401. package/dist/components/GrInputTag/chunks/GrInputTag-CO0YU-ok.js.map +0 -1
  402. package/dist/components/GrKbd/chunks/GrKbd-BNMeoaLV.js.map +0 -1
  403. package/dist/components/GrNavbar/chunks/GrNavbar-BRGuksiy.js.map +0 -1
  404. package/dist/components/GrSelect/chunks/GrSelect-0iI4wdR_.js.map +0 -1
  405. package/dist/components/GrSidebar/chunks/GrSidebar-DoKLlHSw.js +0 -264
  406. package/dist/components/GrSidebar/chunks/GrSidebar-DoKLlHSw.js.map +0 -1
  407. package/dist/components/GrSlider/chunks/GrSlider-CO8N9SF6.js.map +0 -1
  408. package/dist/components/GrSwitch/chunks/GrSwitch-CriHWT2A.js.map +0 -1
  409. package/dist/components/GrTabPanels/chunks/GrTabPanels-5zsbqS1v.js +0 -92
  410. package/dist/components/GrTabPanels/chunks/GrTabPanels-5zsbqS1v.js.map +0 -1
  411. package/dist/components/GrTable/chunks/GrTable-CKJ-HrsN.js.map +0 -1
  412. package/dist/components/GrTextarea/chunks/GrTextarea-Bfgc0DD2.js.map +0 -1
  413. package/dist/components/GrToaster/chunks/GrToaster-CZixNk0l.js.map +0 -1
  414. package/dist/components/GrTooltip/chunks/GrTooltip-DkMMM0uq.js.map +0 -1
  415. package/dist/components/GrTransfer/chunks/GrTransfer-DdfBUf35.js.map +0 -1
  416. package/dist/components/GrTree/chunks/GrTree-6yZrncJE.js.map +0 -1
  417. package/dist/components/GrTreeSelect/chunks/GrTreeSelect-B6KoDYiZ.js.map +0 -1
  418. package/dist/types/components/GrAutocomplete/composables/useAutocompleteVirtual.d.ts +0 -33
@@ -1 +0,0 @@
1
- {"version":3,"file":"GrFileUpload-BwMR71zN.js","names":["$slots"],"sources":["../../../../src/components/GrFileUpload/fileEntry.ts","../../../../src/components/GrFileUpload/useDropZone.ts","../../../../src/components/GrFileUpload/uploadViaXhr.ts","../../../../src/components/GrFileUpload/usePerFileUpload.ts","../../../../src/components/GrFileUpload/uploadState.ts","../../../../src/components/GrFileUpload/useUploadState.ts","../../../../src/components/GrFileUpload/GrFileUpload.vue","../../../../src/components/GrFileUpload/GrFileUpload.vue"],"sourcesContent":["import type { GrUploadState } from './uploadState'\n\n/**\n * Состояние одного файла в пофайловом режиме (`uploadMode=\"per-file\"`).\n *\n * Батчевый режим по-прежнему знает только сводное `GrUploadState`: весь набор\n * уходит одним запросом, и говорить про отдельный файл там нечем.\n */\nexport type GrFileUploadStatus = 'pending' | 'uploading' | 'success' | 'error'\n\nexport interface GrFileUploadEntry {\n file: File\n status: GrFileUploadStatus\n /** Процент `0..100`. У `pending` — 0, у `success` — 100. */\n percent: number\n /** Причина ошибки, если `status === 'error'`. */\n error?: unknown\n}\n\nexport function createFileEntry(file: File): GrFileUploadEntry {\n return { file, status: 'pending', percent: 0 }\n}\n\n/**\n * Сводное состояние набора: грузимся, пока грузится хоть один; ошибка, если\n * после завершения есть хоть одна; успех — когда успешны все.\n *\n * Процент — среднее по файлам, а не по байтам: у пофайловых запросов размеры\n * разные, и «взвешенная» полоса дёргалась бы назад, когда большой файл\n * стартует после маленького.\n */\nexport function summarizeFileEntries(entries: GrFileUploadEntry[]): GrUploadState {\n if (!entries.length)\n return { phase: 'idle', percent: 0, indeterminate: false, loaded: 0, total: 0 }\n\n const total = entries.length\n const percent = Math.round(entries.reduce((sum, entry) => sum + entry.percent, 0) / total)\n const failed = entries.filter(entry => entry.status === 'error')\n const active = entries.some(entry => entry.status === 'uploading' || entry.status === 'pending')\n\n if (active) {\n return {\n phase: 'uploading',\n percent,\n // Неопределённым считаем только самое начало: пока ни один файл не\n // отчитался, полоса не должна притворяться, что что-то знает.\n indeterminate: percent === 0,\n loaded: 0,\n total: 0,\n }\n }\n\n if (failed.length) {\n return {\n phase: 'error',\n percent,\n indeterminate: false,\n loaded: 0,\n total: 0,\n error: failed[0].error,\n }\n }\n\n return { phase: 'success', percent: 100, indeterminate: false, loaded: 0, total: 0 }\n}\n","import { ref, type Ref } from 'vue'\n\nexport interface UseDropZoneOptions {\n /** Зона выключена: ввод не принимается (`disabled` или `readonly`). */\n locked: () => boolean\n /** UI рисует слот — зона перестаёт быть зоной, обработка уезжает к нему. */\n inactive: () => boolean\n onDrop: (files: File[]) => void\n}\n\nexport interface UseDropZoneReturn {\n /** Файл висит над зоной. */\n isOver: Ref<boolean>\n onDragEnter: (event: DragEvent) => void\n onDragOver: (event: DragEvent) => void\n onDragLeave: (event: DragEvent) => void\n onDropFiles: (event: DragEvent) => void\n}\n\n/**\n * Drag&drop поверх зоны.\n *\n * Счётчик входов, а не флаг: `dragenter`/`dragleave` приходят и от вложенных\n * элементов, и без счётчика подсветка гасла, стоило курсору пересечь границу\n * подписи внутри зоны.\n */\nexport function useDropZone(options: UseDropZoneOptions): UseDropZoneReturn {\n const isOver = ref(false)\n let overCounter = 0\n\n function setOver(next: boolean): void {\n if (isOver.value === next)\n return\n isOver.value = next\n }\n\n function ignored(): boolean {\n return options.inactive() || options.locked()\n }\n\n function onDragEnter(event: DragEvent): void {\n if (ignored())\n return\n\n event.preventDefault()\n overCounter += 1\n setOver(true)\n }\n\n function onDragOver(event: DragEvent): void {\n if (ignored())\n return\n\n event.preventDefault()\n if (event.dataTransfer)\n event.dataTransfer.dropEffect = 'copy'\n }\n\n function onDragLeave(event: DragEvent): void {\n if (ignored())\n return\n\n event.preventDefault()\n overCounter = Math.max(0, overCounter - 1)\n if (overCounter === 0)\n setOver(false)\n }\n\n function onDropFiles(event: DragEvent): void {\n if (ignored())\n return\n\n event.preventDefault()\n overCounter = 0\n setOver(false)\n\n const files = event.dataTransfer?.files\n ? Array.prototype.slice.call(event.dataTransfer.files) as File[]\n : []\n\n options.onDrop(files)\n }\n\n return { isOver, onDragEnter, onDragOver, onDragLeave, onDropFiles }\n}\n","/**\n * Загрузка файлов через `XMLHttpRequest` с поддержкой прогресса аплоада и\n * отмены по `AbortSignal`. Используется в `GrFileUpload` для ветки `action`,\n * а также может применяться пользователем напрямую в `request`-загрузчике.\n */\n\nexport type GrUploadExtraDataValue = string | Blob\n\nexport type GrUploadExtraData = Record<string, GrUploadExtraDataValue | GrUploadExtraDataValue[]>\n\nexport interface GrUploadProgressInfo {\n /** Процент `0..100`. Для запросов без `lengthComputable` значение `0` и `indeterminate = true`. */\n percent: number\n loaded: number\n total: number\n indeterminate: boolean\n}\n\nexport interface UploadViaXhrOptions {\n url: string\n files: File[]\n name: string\n headers?: Record<string, string>\n withCredentials?: boolean\n extraData?: GrUploadExtraData\n signal: AbortSignal\n onProgress?: (info: GrUploadProgressInfo) => void\n}\n\nexport class GrUploadAbortError extends Error {\n constructor(message = 'Upload aborted') {\n super(message)\n this.name = 'GrUploadAbortError'\n }\n}\n\nexport class GrUploadHttpError extends Error {\n status: number\n payload: unknown\n\n constructor(status: number, payload: unknown) {\n super(`Upload failed with status ${status}`)\n this.name = 'GrUploadHttpError'\n this.status = status\n this.payload = payload\n }\n}\n\nfunction appendExtraFormData(body: FormData, extraData: GrUploadExtraData | undefined) {\n if (!extraData)\n return\n\n for (const [key, value] of Object.entries(extraData)) {\n if (Array.isArray(value)) {\n for (const entry of value) body.append(key, entry)\n continue\n }\n\n body.append(key, value)\n }\n}\n\nfunction parseResponse(xhr: XMLHttpRequest): unknown {\n const contentType = xhr.getResponseHeader('content-type') ?? ''\n const raw = xhr.responseText\n\n if (contentType.includes('application/json')) {\n try {\n return JSON.parse(raw)\n }\n catch {\n return raw\n }\n }\n\n return raw\n}\n\nexport function uploadViaXhr(options: UploadViaXhrOptions): Promise<unknown> {\n const { url, files, name, headers, withCredentials, extraData, signal, onProgress } = options\n\n return new Promise<unknown>((resolve, reject) => {\n if (signal.aborted) {\n reject(new GrUploadAbortError())\n return\n }\n\n const body = new FormData()\n for (const file of files) body.append(name, file)\n appendExtraFormData(body, extraData)\n\n const xhr = new XMLHttpRequest()\n xhr.open('POST', url, true)\n xhr.withCredentials = !!withCredentials\n\n if (headers) {\n for (const [key, value] of Object.entries(headers))\n xhr.setRequestHeader(key, value)\n }\n\n const onAbort = () => {\n try {\n xhr.abort()\n }\n catch {\n /* ignore */\n }\n }\n\n signal.addEventListener('abort', onAbort)\n\n const cleanup = () => {\n signal.removeEventListener('abort', onAbort)\n }\n\n if (onProgress) {\n xhr.upload.addEventListener('progress', (event) => {\n const indeterminate = !event.lengthComputable\n const total = indeterminate ? 0 : event.total\n const loaded = indeterminate ? 0 : event.loaded\n const percent = indeterminate || total === 0 ? 0 : Math.min(100, (loaded / total) * 100)\n onProgress({ percent, loaded, total, indeterminate })\n })\n }\n\n xhr.addEventListener('load', () => {\n cleanup()\n const payload = parseResponse(xhr)\n const status = xhr.status\n\n if (status >= 200 && status < 300) {\n if (onProgress)\n onProgress({ percent: 100, loaded: 1, total: 1, indeterminate: false })\n resolve(payload)\n return\n }\n\n reject(new GrUploadHttpError(status, payload))\n })\n\n xhr.addEventListener('error', () => {\n cleanup()\n reject(new Error('Network error during upload'))\n })\n\n xhr.addEventListener('abort', () => {\n cleanup()\n reject(new GrUploadAbortError())\n })\n\n xhr.send(body)\n })\n}\n","import { ref, type Ref } from 'vue'\n\nimport { createFileEntry, summarizeFileEntries, type GrFileUploadEntry } from './fileEntry'\nimport type { GrUploadState } from './uploadState'\nimport { GrUploadAbortError, type GrUploadProgressInfo } from './uploadViaXhr'\n\nexport interface UsePerFileUploadOptions {\n /** Сколько файлов грузится одновременно. */\n concurrency: () => number\n /** Отправка одного файла. Контракт тот же, что у батча: всегда массив. */\n send: (files: File[], ctx: { signal: AbortSignal, onProgress: (info: GrUploadProgressInfo) => void }) => Promise<unknown>\n /** Сводное состояние пересчитывается из записей по файлам. */\n onSummary: (state: GrUploadState) => void\n onProgress: (info: GrUploadProgressInfo, file: File) => void\n onSuccess: (payload: unknown, file: File) => void\n onError: (error: unknown, file: File) => void\n onSetChange: (files: File[]) => void\n}\n\nexport interface UsePerFileUploadReturn {\n entries: Ref<GrFileUploadEntry[]>\n entryOf: (file: File) => GrFileUploadEntry | undefined\n /** Загрузить набор пофайлово: у каждого файла свой запрос и свой статус. */\n run: (files: File[]) => Promise<void>\n /** Повторить один файл — соседей это не касается. */\n retryFile: (file: File) => Promise<void>\n /** Оборвать загрузку одного файла. */\n abortFile: (file: File) => void\n abortAll: () => void\n /** Убрать файл из набора записей и пересчитать сводное состояние. */\n dropEntry: (file: File) => void\n reset: () => void\n}\n\n/**\n * Пофайловый режим: у каждого файла свой запрос, свой контроллер и свой статус,\n * а сводное состояние выводится из них.\n */\nexport function usePerFileUpload(options: UsePerFileUploadOptions): UsePerFileUploadReturn {\n const entries = ref<GrFileUploadEntry[]>([])\n\n /** Контроллер на файл: `abortFile` обязан обрывать свой запрос, а не соседний. */\n const controllers = new Map<File, AbortController>()\n\n function syncSummary(): void {\n options.onSummary(summarizeFileEntries(entries.value))\n }\n\n function entryOf(file: File): GrFileUploadEntry | undefined {\n return entries.value.find(entry => entry.file === file)\n }\n\n function patch(file: File, next: Partial<GrFileUploadEntry>): void {\n const entry = entryOf(file)\n if (!entry)\n return\n\n Object.assign(entry, next)\n syncSummary()\n }\n\n async function uploadOne(file: File): Promise<void> {\n const controller = new AbortController()\n controllers.set(file, controller)\n patch(file, { status: 'uploading', percent: 0, error: undefined })\n\n const onProgress = (info: GrUploadProgressInfo): void => {\n patch(file, { percent: info.indeterminate ? 0 : info.percent })\n options.onProgress(info, file)\n }\n\n try {\n const payload = await options.send([file], { signal: controller.signal, onProgress })\n\n patch(file, { status: 'success', percent: 100, error: undefined })\n options.onSuccess(payload, file)\n options.onProgress({ percent: 100, loaded: 0, total: 0, indeterminate: false }, file)\n }\n catch (error) {\n // Отмена — не ошибка файла: строка возвращается в исходное состояние, а\n // пользователь решает, повторять её или убрать.\n if (error instanceof GrUploadAbortError || (error as { name?: string })?.name === 'AbortError')\n patch(file, { status: 'pending', percent: 0, error: undefined })\n else\n patch(file, { status: 'error', error })\n\n options.onError(error, file)\n }\n finally {\n controllers.delete(file)\n }\n }\n\n /**\n * Очередь с ограничением параллелизма. Без неё «пофайлово» означало бы «все\n * сразу»: сотня файлов открыла бы сотню соединений.\n */\n async function runWithConcurrency(files: File[]): Promise<void> {\n const limit = Math.max(1, Math.floor(options.concurrency()))\n const queue = [...files]\n\n const workers = Array.from({ length: Math.min(limit, queue.length) }, async () => {\n while (queue.length) {\n const next = queue.shift()\n if (!next)\n return\n await uploadOne(next)\n }\n })\n\n await Promise.all(workers)\n }\n\n async function run(files: File[]): Promise<void> {\n entries.value = files.map(createFileEntry)\n syncSummary()\n\n await runWithConcurrency(files)\n options.onSetChange(files)\n }\n\n async function retryFile(file: File): Promise<void> {\n if (!entryOf(file))\n return\n\n await uploadOne(file)\n }\n\n function abortFile(file: File): void {\n controllers.get(file)?.abort()\n controllers.delete(file)\n }\n\n function abortAll(): void {\n for (const controller of controllers.values()) controller.abort()\n controllers.clear()\n }\n\n function dropEntry(file: File): void {\n entries.value = entries.value.filter(entry => entry.file !== file)\n }\n\n function reset(): void {\n entries.value = []\n }\n\n return { entries, entryOf, run, retryFile, abortFile, abortAll, dropEntry, reset }\n}\n","/**\n * Состояние процесса загрузки в `GrFileUpload`.\n *\n * Используется как для внутреннего управления UI, так и пробрасывается наружу\n * через scoped-слоты (`progress`, `default`) и `defineExpose`.\n */\n\nexport type GrUploadPhase = 'idle' | 'uploading' | 'success' | 'error'\n\nexport interface GrUploadStateIdle {\n phase: 'idle'\n percent: 0\n indeterminate: false\n loaded: 0\n total: 0\n}\n\nexport interface GrUploadStateUploading {\n phase: 'uploading'\n percent: number\n indeterminate: boolean\n loaded: number\n total: number\n}\n\nexport interface GrUploadStateSuccess {\n phase: 'success'\n percent: 100\n indeterminate: false\n loaded: number\n total: number\n}\n\nexport interface GrUploadStateError {\n phase: 'error'\n percent: number\n indeterminate: false\n loaded: number\n total: number\n error: unknown\n}\n\nexport type GrUploadState\n = | GrUploadStateIdle\n | GrUploadStateUploading\n | GrUploadStateSuccess\n | GrUploadStateError\n\nexport const GR_UPLOAD_STATE_IDLE: GrUploadStateIdle = Object.freeze({\n phase: 'idle',\n percent: 0,\n indeterminate: false,\n loaded: 0,\n total: 0,\n})\n","import { reactive } from 'vue'\n\nimport { GR_UPLOAD_STATE_IDLE, type GrUploadState } from './uploadState'\nimport type { GrUploadProgressInfo } from './uploadViaXhr'\n\nexport interface UseUploadStateOptions {\n /** Компонент отчитывается о каждой смене фазы наружу. */\n onChange: (state: GrUploadState) => void\n}\n\nexport interface UseUploadStateReturn {\n state: GrUploadState\n setIdle: () => void\n setUploading: (info?: GrUploadProgressInfo) => void\n setSuccess: (info: { loaded: number, total: number }) => void\n setError: (error: unknown) => void\n /** Прогресс не двигает состояние вне фазы `uploading`. */\n applyProgress: (info: GrUploadProgressInfo) => void\n assign: (next: GrUploadState) => void\n /** Отложенный переход `success` → `idle`. */\n scheduleIdle: (delayMs: number) => void\n clearScheduledIdle: () => void\n}\n\n/**\n * Сводное состояние загрузки: фаза, проценты и таймер скрытия успеха.\n *\n * Состояние — `reactive`-объект с меняющейся формой (у `error` есть поле\n * `error`, у `idle` его нет), поэтому при смене фазы старые ключи удаляются:\n * иначе `phase: 'idle'` тащил бы за собой `error` от прошлой попытки.\n */\nexport function useUploadState(options: UseUploadStateOptions): UseUploadStateReturn {\n const state = reactive<GrUploadState>({ ...GR_UPLOAD_STATE_IDLE }) as GrUploadState\n\n let idleTimer: ReturnType<typeof setTimeout> | null = null\n\n function clearScheduledIdle(): void {\n if (idleTimer === null)\n return\n\n clearTimeout(idleTimer)\n idleTimer = null\n }\n\n function assign(next: GrUploadState): void {\n // Форма состояния меняется вместе с фазой (`error` есть только у ошибки),\n // поэтому старые ключи снимаются: иначе `idle` тащил бы `error` прошлой\n // попытки.\n for (const key of Object.keys(state))\n delete (state as unknown as Record<string, unknown>)[key]\n\n Object.assign(state, next)\n options.onChange(state)\n }\n\n function setIdle(): void {\n assign({ ...GR_UPLOAD_STATE_IDLE })\n }\n\n function setUploading(info?: GrUploadProgressInfo): void {\n assign({\n phase: 'uploading',\n percent: info?.percent ?? 0,\n indeterminate: info?.indeterminate ?? true,\n loaded: info?.loaded ?? 0,\n total: info?.total ?? 0,\n })\n }\n\n function setSuccess(info: { loaded: number, total: number }): void {\n assign({\n phase: 'success',\n percent: 100,\n indeterminate: false,\n loaded: info.loaded,\n total: info.total,\n })\n }\n\n function setError(error: unknown): void {\n const prev = state\n assign({\n phase: 'error',\n percent: prev.phase === 'uploading' ? prev.percent : 0,\n indeterminate: false,\n loaded: prev.phase === 'uploading' ? prev.loaded : 0,\n total: prev.phase === 'uploading' ? prev.total : 0,\n error,\n })\n }\n\n function applyProgress(info: GrUploadProgressInfo): void {\n if (state.phase !== 'uploading')\n return\n\n assign({\n phase: 'uploading',\n percent: info.percent,\n indeterminate: info.indeterminate,\n loaded: info.loaded,\n total: info.total,\n })\n }\n\n function scheduleIdle(delayMs: number): void {\n clearScheduledIdle()\n idleTimer = setTimeout(() => {\n if (state.phase === 'success')\n setIdle()\n idleTimer = null\n }, delayMs)\n }\n\n return {\n state,\n setIdle,\n setUploading,\n setSuccess,\n setError,\n applyProgress,\n assign,\n scheduleIdle,\n clearScheduledIdle,\n }\n}\n","<script setup lang=\"ts\" generic=\"TResponse = unknown\">\nimport { computed, nextTick, onBeforeUnmount, onMounted, ref, Text, useSlots, watch, type VNode } from 'vue'\n\nimport IconArrowUp from '~icons/lucide/arrow-up'\nimport IconClose from '~icons/lucide/x'\n\nimport GrIcon from '../GrIcon/GrIcon.vue'\nimport GrProgressBar from '../GrProgressBar/GrProgressBar.vue'\nimport type { GrProgressBarTone } from '../GrProgressBar/grStyle'\nimport type { FileValidator, FileValidatorSource } from '../../fileValidation'\nimport type { GrUploadProgressInfo } from './uploadViaXhr'\nimport type { GrUploadState } from './uploadState'\nimport { summarizeFileEntries, type GrFileUploadEntry } from './fileEntry'\nimport { useDropZone } from './useDropZone'\nimport { useFilePreviews } from '../../composables/internal/useFilePreviews'\nimport { usePerFileUpload } from './usePerFileUpload'\nimport { useUploadState } from './useUploadState'\n\nimport { useGrComponentSize } from '../GrConfigProvider/context'\nimport { flattenSlotNodes, meaningfulSlotNodes } from '../shared/slotNodes'\nimport {\n type GrFileUploadSize,\n grFileUploadZoneClass,\n hintSizes,\n iconGlyphSizes,\n iconTileSizes,\n labelSizes,\n progressBarSizes,\n progressTextSizes,\n zoneGaps,\n} from './grFileUploadStyles'\nimport { acceptValidator, FileValidationError, runFileValidators } from '../../fileValidation'\nimport { GrUploadAbortError, uploadViaXhr } from './uploadViaXhr'\nimport { useGrFormFieldContext } from '../GrFormField/context'\nimport { useGrFormControl } from '../../composables/useGrFormControl'\nimport { useFocusWithin } from '../../composables/internal/useFocusWithin'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\n\nexport type GrFileUploadMode = 'batch' | 'per-file'\n\nexport type GrFileUploadExtraDataValue = string | Blob\n\nexport type GrFileUploadExtraData = Record<string, GrFileUploadExtraDataValue | GrFileUploadExtraDataValue[]>\n\nexport type GrFileUploadRequestCtx = {\n signal: AbortSignal\n extraData?: GrFileUploadExtraData\n /**\n * Колбэк прогресса аплоада. Пользовательский `request` (например, axios)\n * должен вызывать его из `onUploadProgress`, чтобы `GrFileUpload` отображал\n * прогресс через слот `progress` / дефолтный `GrProgressBar`.\n */\n onProgress?: (info: GrUploadProgressInfo) => void\n}\n\nexport type GrFileUploadRequest<TResponse = unknown> = (\n files: File[],\n ctx: GrFileUploadRequestCtx,\n) => Promise<TResponse>\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<TResponse = unknown> {\n /**\n * Набор файлов — тот, что показан в списке и уйдёт в `retry`.\n *\n * Проп **необязателен**: без него компонент держит набор сам. Передан —\n * набор следует за ним, и потребитель может очистить или подменить список\n * снаружи.\n */\n modelValue?: File[]\n action?: string\n request?: GrFileUploadRequest<TResponse>\n name?: string\n multiple?: boolean\n limit?: number\n /**\n * Guard перед отправкой. Остаётся пропом, а не эмитом, осознанно: эмит не\n * возвращает значения, а этот колбэк обязан ответить «пускать или нет».\n * Уведомления — `exceed`, `success`, `error`, `progress` — эмиты.\n */\n beforeUpload?: (file: File) => boolean | Promise<unknown>\n validators?: FileValidator[]\n /**\n * W3C `accept` для `<input type=\"file\">` — и sugar к `acceptValidator(...)`.\n *\n * Только атрибута мало: он фильтрует системный диалог, но не drag&drop —\n * перетащить можно что угодно. Поэтому то же значение уходит и в валидаторы,\n * как в `GrFormFile`.\n */\n accept?: string\n /** `capture` для мобильной камеры/микрофона. */\n capture?: 'user' | 'environment'\n /** Выбор каталога целиком (`webkitdirectory`). Поддержка — Chromium и Safari. */\n directory?: boolean\n disabled?: boolean\n /** Только для чтения: значение видно и уходит в форму, но не редактируется. */\n readonly?: boolean\n /** Визуальное и ARIA-состояние ошибки. */\n invalid?: boolean\n /** Обязательное поле (`aria-required`). */\n required?: boolean\n /** Доступное имя вне `GrFormField`. */\n ariaLabel?: string\n headers?: Record<string, string>\n withCredentials?: boolean\n showFileList?: boolean\n uploadExtraData?: (files: File[]) => GrFileUploadExtraData | undefined\n /** i18n: надпись-подсказка в дефолтном UI. */\n placeholder?: string\n /** Показывать дефолтный прогресс-бар (если не используется слот `progress`). */\n showProgress?: boolean\n /** Цветовой тон полосы прогресса в фазе `uploading`. */\n progressTone?: GrProgressBarTone\n /** aria-label для прогресс-бара. */\n progressLabel?: string\n /** Через сколько мс после `success` скрыть прогресс-бар. `0` — не скрывать. */\n hideProgressOnSuccess?: number\n /** Размер дефолтного UI: поля дроп-зоны, плитка иконки, кегль подписей. */\n size?: GrFileUploadSize\n /**\n * Как уходит набор файлов:\n *\n * - `batch` (по умолчанию) — весь набор одним запросом. Про отдельный файл\n * сказать нечего, поэтому и статуса у него нет;\n * - `per-file` — на каждый файл свой запрос (`request` зовётся с массивом из\n * одного файла, контракт не меняется). Появляются статус и процент строки,\n * `retryFile` и `abortFile`.\n */\n uploadMode?: GrFileUploadMode\n /** Сколько файлов грузится одновременно в режиме `per-file`. */\n concurrency?: number\n /** Миниатюры для `image/*` в списке файлов. */\n preview?: boolean\n}\n\nexport interface GrFileUploadEmits<TResponse = unknown> {\n /** Набор файлов сменился: новый выбор или удаление. Не путать с `change`. */\n (e: 'update:modelValue', files: File[]): void\n /** Выбрано больше файлов, чем разрешает `limit`. Загрузка не стартует. */\n (e: 'exceed', files: File[], limit: number): void\n /** `file` приходит только в режиме `per-file`: в батче отчитываться нечем. */\n (e: 'success', payload: TResponse, file?: File): void\n (e: 'error', error: unknown, file?: File): void\n (e: 'progress', percent: number, info?: GrUploadProgressInfo, file?: File): void\n (e: 'change', files: File[]): void\n (e: 'stateChange', state: GrUploadState): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n}\n\nconst props = withDefaults(\n defineProps<GrFileUploadProps<TResponse>>(),\n {\n modelValue: undefined,\n action: undefined,\n request: undefined,\n name: 'file',\n multiple: false,\n limit: undefined,\n beforeUpload: undefined,\n validators: undefined,\n accept: undefined,\n capture: undefined,\n directory: false,\n disabled: false,\n readonly: false,\n invalid: false,\n required: false,\n ariaLabel: undefined,\n headers: undefined,\n withCredentials: false,\n showFileList: false,\n uploadExtraData: undefined,\n placeholder: undefined,\n showProgress: true,\n progressTone: 'primary',\n progressLabel: undefined,\n hideProgressOnSuccess: 800,\n size: undefined,\n uploadMode: 'batch',\n concurrency: 3,\n preview: false,\n },\n)\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrFileUpload' })\n\nconst zoneGapClass = computed(() => zoneGaps[resolvedSize.value])\nconst iconTileClass = computed(() => iconTileSizes[resolvedSize.value])\nconst iconGlyphSize = computed(() => iconGlyphSizes[resolvedSize.value])\nconst labelClass = computed(() => labelSizes[resolvedSize.value])\nconst hintClass = computed(() => hintSizes[resolvedSize.value])\nconst progressTextClass = computed(() => progressTextSizes[resolvedSize.value])\nconst progressBarSize = computed(() => progressBarSizes[resolvedSize.value])\n\nconst emit = defineEmits<GrFileUploadEmits<TResponse>>()\ndefineSlots<{\n /** Полностью своя зона загрузки: контрол отдаёт наружу всё своё состояние. */\n default?: (props: {\n openDialog: () => void\n abort: () => void\n disabled: boolean\n files: File[]\n isOver: boolean\n state: GrUploadState\n retry: () => Promise<void>\n removeFile: (file: File) => void\n fileEntries: GrFileUploadEntry[]\n retryFile: (file: File) => Promise<void>\n abortFile: (file: File) => void\n }) => any\n /** Заголовок зоны вместо `placeholder`. */\n label?: () => any\n /** Подпись под заголовком: ограничения по типу и размеру. */\n tip?: () => any\n /** Свой индикатор прогресса вместо встроенного. */\n progress?: (props: {\n state: GrUploadState\n percent: number\n indeterminate: boolean\n phase: GrUploadState['phase']\n files: File[]\n abort: () => void\n retry: () => Promise<void>\n fileEntries: GrFileUploadEntry[]\n retryFile: (file: File) => Promise<void>\n abortFile: (file: File) => void\n }) => any\n}>()\n\nconst slots = useSlots()\n\nconst { t } = useGranularityTranslations()\n\n// Контекст `GrFormField`: доступным контролом служит сам нативный\n// `<input type=\"file\">` — он и остаётся целью `<label for>`.\nconst field = useGrFormFieldContext()\nconst fieldId = computed(() => field?.id.value)\nconst describedBy = computed(() => field?.describedById.value)\nconst {\n disabled: isDisabled,\n readonly: isReadonly,\n invalid: isInvalid,\n required: isRequired,\n // `locked` — «ввод не принимается»: `disabled` или `readonly`. Набор в\n // `readonly` виден и уходит в форму, но поменять его нельзя.\n locked: isLocked,\n} = useGrFormControl(() => props)\nconst resolvedPlaceholder = computed(() => props.placeholder ?? t('gr.fileUpload.placeholder', 'Drag files here or click to select'))\nconst resolvedProgressLabel = computed(() => props.progressLabel ?? t('gr.fileUpload.progress', 'Upload progress'))\n\nfunction slotIsTextOnly(nodes: VNode[]): boolean {\n const meaningful = meaningfulSlotNodes(nodes)\n\n if (meaningful.length === 0)\n return true\n\n for (const node of meaningful) {\n if (node.type !== Text)\n 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 rootEl = ref<HTMLElement | null>(null)\n\n// Внутри зоны фокус ходит между нативным input'ом и кнопками списка файлов:\n// без границы каждое перемещение давало бы потребителю пару `blur` + `focus`.\nconst { onFocusIn, onFocusOut } = useFocusWithin(rootEl, {\n enter: event => emit('focus', event),\n leave: event => emit('blur', event),\n})\n\nconst lastFiles = ref<File[]>(props.modelValue ? [...props.modelValue] : [])\n\n/**\n * Набор меняется только здесь: иначе `update:modelValue` рано или поздно\n * разошёлся бы с тем, что показано в списке.\n */\nfunction setFiles(next: File[]): void {\n lastFiles.value = next\n emit('update:modelValue', next)\n}\n\n// Контролируемый режим: проп задан — он и есть источник истины.\nwatch(() => props.modelValue, (next) => {\n if (next === undefined)\n return\n if (next === lastFiles.value)\n return\n\n lastFiles.value = [...next]\n}, { deep: false })\n\nconst {\n state,\n setIdle: setStateIdle,\n setUploading: setStateUploading,\n setSuccess: setStateSuccess,\n setError: setStateError,\n applyProgress,\n assign: assignState,\n scheduleIdle,\n clearScheduledIdle,\n} = useUploadState({ onChange: next => emit('stateChange', next) })\n\nlet activeController: AbortController | null = null\n\nfunction handleProgress(info: GrUploadProgressInfo) {\n applyProgress(info)\n emit('progress', info.percent, info)\n}\n\nconst { fileKey, previewUrl, revokePreview, revokeAllPreviews } = useFilePreviews({\n enabled: () => props.preview,\n})\n\n/**\n * `extraData` считается один раз на набор и не меняется между файлами, поэтому\n * живёт рядом с очередью, а не путешествует через её контракт.\n */\nlet perFileExtraData: GrFileUploadExtraData | undefined\n\nconst perFile = usePerFileUpload({\n concurrency: () => props.concurrency,\n send: (files, ctx) => (props.request\n ? props.request(files, { signal: ctx.signal, extraData: perFileExtraData, onProgress: ctx.onProgress })\n : uploadViaXhr({\n url: props.action ?? '',\n files,\n name: props.name,\n headers: props.headers,\n withCredentials: props.withCredentials,\n extraData: perFileExtraData,\n signal: ctx.signal,\n onProgress: ctx.onProgress,\n })),\n onSummary: assignState,\n onProgress: (info, file) => emit('progress', info.percent, info, file),\n onSuccess: (payload, file) => emit('success', payload as TResponse, file),\n onError: (error, file) => emit('error', error, file),\n onSetChange: files => emit('change', files),\n})\n\nconst fileEntries = perFile.entries\nconst { retryFile, abortFile } = perFile\n\nconst dropZone = useDropZone({\n locked: () => isLocked.value,\n inactive: () => hasCustomUi.value,\n onDrop: files => void handleFiles(files, 'drop'),\n})\nconst isOver = dropZone.isOver\n\n/**\n * Дефолтный слот вызывается из двух мест — как своя зона целиком и как подпись\n * внутри стандартной. Набор пропов у него один: две копии разъехались бы.\n */\nconst defaultSlotProps = computed(() => ({\n openDialog,\n abort,\n disabled: isDisabled.value,\n files: lastFiles.value,\n isOver: isOver.value,\n state,\n retry,\n removeFile,\n fileEntries: fileEntries.value,\n retryFile,\n abortFile,\n}))\n\nconst zoneClass = computed(() => grFileUploadZoneClass({\n size: resolvedSize.value,\n disabled: isDisabled.value,\n readonly: isReadonly.value,\n over: isOver.value,\n}))\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 openDialog() {\n if (isLocked.value)\n return\n inputRef.value?.click()\n}\n\nfunction abort() {\n activeController?.abort()\n activeController = null\n}\n\nfunction totalSizeOf(files: File[]): number {\n return files.reduce((sum, file) => sum + file.size, 0)\n}\n\nfunction normalizeLimit(limit: number | undefined): number | undefined {\n if (typeof limit !== 'number')\n return undefined\n if (!Number.isFinite(limit))\n return undefined\n if (limit <= 0)\n return undefined\n return Math.floor(limit)\n}\n\nasync function uploadViaAction(files: File[], signal: AbortSignal, extraData: GrFileUploadExtraData | undefined) {\n if (!props.action)\n throw new Error('GrFileUpload: either `action` or `request` must be provided')\n\n return uploadViaXhr({\n url: props.action,\n files,\n name: props.name,\n headers: props.headers,\n withCredentials: props.withCredentials,\n extraData,\n signal,\n onProgress: handleProgress,\n })\n}\n\nasync function runBeforeUpload(files: File[]): Promise<'ok' | { aborted: true, reason: unknown }> {\n if (!props.beforeUpload)\n 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 }\n catch (error) {\n return { aborted: true, reason: error }\n }\n }\n\n return 'ok'\n}\n\n/**\n * `accept` идёт первым валидатором: атрибут на input фильтрует только диалог,\n * а drop мимо него проходит.\n */\nfunction effectiveValidators(): FileValidator[] {\n return [acceptValidator(props.accept), ...(props.validators ?? [])]\n}\n\n/**\n * Номер запуска. Валидация и `beforeUpload` асинхронны, поэтому два быстрых\n * выбора подряд идут внахлёст: без номера «побеждал» тот, чьи валидаторы\n * отработали позже, — он же обрывал уже стартовавшую загрузку соседа.\n * Актуален всегда последний выбор, остальные тихо сходят с дистанции.\n */\nlet runCounter = 0\n\nasync function handleFiles(files: File[], source: FileValidatorSource = 'input') {\n if (isLocked.value)\n return\n if (!files.length)\n return\n\n runCounter += 1\n const runId = runCounter\n const isStale = (): boolean => runId !== runCounter\n\n const normalizedLimit = normalizeLimit(props.limit)\n if (props.multiple && normalizedLimit && files.length > normalizedLimit) {\n emit('exceed', 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, effectiveValidators(), {\n source,\n multiple: props.multiple,\n })\n\n if (isStale())\n return\n\n if (issues.length > 0) {\n emit('error', new FileValidationError(issues, valid))\n return\n }\n\n if (!valid.length)\n return\n\n // Набор сменился — миниатюры прежнего больше не нужны.\n revokeAllPreviews()\n setFiles(valid)\n\n const before = await runBeforeUpload(valid)\n if (isStale())\n return\n\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 }\n catch (error) {\n emit('error', error)\n return\n }\n\n abort()\n clearScheduledIdle()\n\n if (props.uploadMode === 'per-file') {\n perFileExtraData = extraData\n await perFile.run(valid)\n return\n }\n\n const controller = new AbortController()\n activeController = controller\n\n // Батч: пофайловых записей нет, показываем набор без статусов.\n perFile.reset()\n\n setStateUploading()\n emit('progress', 0, { percent: 0, loaded: 0, total: 0, indeterminate: true })\n\n try {\n // `action`-ветка отдаёт разобранный ответ сервера: типом его знает только\n // потребитель, поэтому приводим к его же `TResponse`.\n const payload = (props.request\n ? await props.request(valid, { signal: controller.signal, extraData, onProgress: handleProgress })\n : await uploadViaAction(valid, controller.signal, extraData)) as TResponse\n\n // Кастомный `request` не обязан звать `onProgress` — тогда байты неизвестны.\n // Сумма размеров выбранных файлов честнее нуля: «100%» при `total: 0`\n // потребитель прочитает как «загружено ноль».\n const measured = state.phase === 'uploading' ? state.total || state.loaded : 0\n const finalLoaded = measured || totalSizeOf(valid)\n setStateSuccess({ loaded: finalLoaded, total: finalLoaded })\n emit('success', payload)\n emit('change', valid)\n emit('progress', 100, { percent: 100, loaded: finalLoaded, total: finalLoaded, indeterminate: false })\n\n if (props.hideProgressOnSuccess && props.hideProgressOnSuccess > 0)\n scheduleIdle(props.hideProgressOnSuccess)\n }\n catch (error) {\n if (error instanceof GrUploadAbortError) {\n setStateIdle()\n }\n else {\n setStateError(error)\n }\n emit('error', error)\n }\n finally {\n if (activeController === controller)\n activeController = null\n }\n}\n\n/**\n * `<input type=\"file\">` не понимает `readonly` — атрибута для него в HTML нет.\n * Поэтому системный диалог гасим отменой действия по умолчанию: input остаётся\n * в порядке `Tab` и объявляется как «только чтение», но нового файла не примет.\n */\nfunction onInputClick(event: MouseEvent) {\n if (isReadonly.value && !isDisabled.value)\n event.preventDefault()\n}\n\nasync function onInputChange(event: Event) {\n if (isLocked.value)\n 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)\n target.value = ''\n\n await nextTick()\n await handleFiles(files, 'input')\n}\n\n// Клавиатуры на зоне нет: Enter/Space обрабатывает сам file-input, на котором и\n// стоит фокус. Собственный обработчик здесь только дублировал бы нативный и\n// открывал диалог дважды.\nfunction onRootClick(event: MouseEvent) {\n if (hasCustomUi.value)\n return\n // `openDialog` кликает по input программно, и этот клик всплывает обратно сюда.\n // Без отсечки зона открывала бы диалог по кругу.\n if (event.target === inputRef.value)\n return\n\n openDialog()\n}\n\nconst effectiveProgressTone = computed<GrProgressBarTone>(() => {\n if (state.phase === 'error')\n return 'danger'\n if (state.phase === 'success')\n return 'success'\n return props.progressTone\n})\n\n/**\n * Живой регион существует с первого рендера и пуст, пока объявлять нечего:\n * регион, появляющийся сразу с текстом, часть AT не объявляет вовсе. Прогресс\n * в процентах сюда не идёт — диктор захлебнётся; объявляются только фазы.\n */\nconst liveMessage = computed(() => {\n if (state.phase === 'uploading')\n return t('gr.fileUpload.uploading', 'Uploading…')\n if (state.phase === 'success')\n return t('gr.fileUpload.success', 'Upload complete')\n if (state.phase === 'error')\n return t('gr.fileUpload.error', 'Upload failed')\n return ''\n})\n\nconst progressVisible = computed(() => state.phase !== 'idle')\nconst progressIndeterminate = computed(() => state.phase === 'uploading' && state.indeterminate)\nconst progressPercent = computed(() => (progressIndeterminate.value ? 0 : state.percent))\nconst progressText = computed(() => {\n if (progressIndeterminate.value)\n return ''\n return `${Math.round(progressPercent.value)}%`\n})\n\nconst isPerFile = computed(() => props.uploadMode === 'per-file')\n\nconst statusTextByStatus = computed<Record<string, string>>(() => ({\n pending: t('gr.fileUpload.statusPending', 'Pending'),\n uploading: t('gr.fileUpload.statusUploading', 'Uploading'),\n success: t('gr.fileUpload.statusSuccess', 'Uploaded'),\n error: t('gr.fileUpload.statusError', 'Failed'),\n}))\n\n/** Запись файла для разметки: в батчевом режиме её нет — и статуса тоже. */\nfunction entryFor(file: File): GrFileUploadEntry | undefined {\n return isPerFile.value ? perFile.entryOf(file) : undefined\n}\n\n/** Повторить загрузку текущего набора — после ошибки выбирать файлы заново незачем. */\nasync function retry(): Promise<void> {\n if (!lastFiles.value.length)\n return\n\n await handleFiles([...lastFiles.value], 'input')\n}\n\n/**\n * Убрать файл из набора. Идущая загрузка при этом обрывается: она была про\n * прежний набор, и её результат к новому отношения не имеет.\n */\nfunction removeFile(file: File): void {\n const next = lastFiles.value.filter(item => item !== file)\n if (next.length === lastFiles.value.length)\n return\n\n abortFile(file)\n abort()\n clearScheduledIdle()\n revokePreview(file)\n setFiles(next)\n perFile.dropEntry(file)\n\n // В пофайловом режиме остальные файлы своих статусов не теряют: удалили один\n // — сводное состояние просто пересчитывается по оставшимся.\n if (props.uploadMode === 'per-file' && fileEntries.value.length)\n assignState(summarizeFileEntries(fileEntries.value))\n else if (state.phase !== 'idle')\n setStateIdle()\n}\n\nfunction focus(): void {\n inputRef.value?.focus()\n}\n\nfunction blur(): void {\n inputRef.value?.blur()\n}\n\n// Ни `action`, ни `request` — загружать некуда, и узнать об этом на первом же\n// выборе файла поздно. Выразить требование типом нельзя: `defineProps` в SFC\n// принимает объектный тип или интерфейс, но не discriminated union.\nonMounted(() => {\n if (!__GR_DEV__)\n return\n if (props.action || props.request)\n return\n\n console.warn('[GrFileUpload] не задан ни `action`, ни `request`: отправлять файлы некуда.')\n})\n\n// Хвосты, переживающие размонтирование: незавершённый XHR продолжает качать\n// файл, а таймер скрытия успеха дёргает `setStateIdle()` на уничтоженном\n// инстансе. Сценарий обычный — успешная загрузка и уход со страницы сразу после.\nonBeforeUnmount(() => {\n clearScheduledIdle()\n abort()\n perFile.abortAll()\n revokeAllPreviews()\n})\n\ndefineExpose({\n focus,\n blur,\n uploadFiles: handleFiles,\n abort,\n openDialog,\n state,\n /** Текущий набор файлов — тот, что показан в списке и уйдёт в `retry`. */\n files: lastFiles,\n retry,\n removeFile,\n /** Записи по файлам — пусты в батчевом режиме. */\n fileEntries,\n retryFile,\n abortFile,\n})\n\n/**\n * Drop-zone намеренно БЕЗ `role=\"button\"` и `tabindex`: доступным контролом\n * служит сам нативный `<input type=\"file\">`. Роль-виджет объявляет потомков\n * презентационными, поэтому input внутри неё теряется для скринридеров и\n * падает в axe на `nested-interactive`; а обёртывать его в собственный\n * «button» — значит вручную переизобретать то, что file-input уже умеет\n * нативно (открытие диалога с клавиатуры, объявление «кнопка выбора файла»).\n * Зона остаётся кликабельной и принимает drag&drop, фокус input'а показывает\n * через `focus-within`.\n */\n</script>\n\n<template>\n <div\n ref=\"rootEl\"\n data-gr-file-upload\n :class=\"hasCustomUi ? 'inline-block' : zoneClass\"\n @click=\"onRootClick\"\n @focusin=\"onFocusIn\"\n @focusout=\"onFocusOut\"\n @dragenter=\"dropZone.onDragEnter\"\n @dragover=\"dropZone.onDragOver\"\n @dragleave=\"dropZone.onDragLeave\"\n @drop=\"dropZone.onDropFiles\"\n >\n <!-- В custom-UI контрол рисует слот (он и вызывает `openDialog`), поэтому там\n input прячется и от таба, и от дерева доступности — иначе получим два\n контрола на один смысл. -->\n <input\n :id=\"fieldId\"\n ref=\"inputRef\"\n data-gr-file-upload-input\n :style=\"hiddenInputStyle\"\n type=\"file\"\n :tabindex=\"hasCustomUi || isDisabled ? -1 : 0\"\n :aria-hidden=\"hasCustomUi ? 'true' : undefined\"\n :aria-label=\"ariaLabel ?? (hasCustomUi ? undefined : resolvedPlaceholder)\"\n :aria-describedby=\"describedBy\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n :aria-required=\"isRequired ? 'true' : undefined\"\n :aria-readonly=\"isReadonly ? 'true' : undefined\"\n :name=\"name\"\n :accept=\"accept\"\n :capture=\"capture\"\n :webkitdirectory=\"directory || undefined\"\n :multiple=\"multiple\"\n :disabled=\"isDisabled\"\n @click=\"onInputClick\"\n @change=\"onInputChange\"\n >\n\n <span\n data-gr-file-upload-live\n class=\"sr-only\"\n role=\"status\"\n aria-live=\"polite\"\n >{{ liveMessage }}</span>\n\n <slot v-if=\"hasCustomUi\" v-bind=\"defaultSlotProps\" />\n\n <div v-else class=\"flex items-start\" :class=\"zoneGapClass\">\n <div\n data-gr-file-upload-icon\n class=\"shrink-0 bg-[var(--gr-muted)] border border-[var(--gr-brd)] flex items-center justify-center\"\n :class=\"iconTileClass\"\n aria-hidden=\"true\"\n >\n <GrIcon class=\"text-[var(--gr-muted-fg)]\" :size=\"iconGlyphSize\">\n <IconArrowUp />\n </GrIcon>\n </div>\n\n <div class=\"min-w-0\">\n <div data-gr-file-upload-label class=\"font-700\" :class=\"labelClass\">\n <slot name=\"label\">\n <slot v-bind=\"defaultSlotProps\">\n {{ resolvedPlaceholder }}\n </slot>\n </slot>\n </div>\n\n <div v-if=\"$slots.tip\" data-gr-file-upload-tip class=\"mt-1 text-[var(--gr-muted-fg)]\" :class=\"hintClass\">\n <slot name=\"tip\" />\n </div>\n <div v-else class=\"mt-1 text-[var(--gr-muted-fg)]\" :class=\"hintClass\" />\n\n <ul v-if=\"showFileList && lastFiles.length\" data-gr-file-upload-list class=\"mt-3 space-y-1\">\n <li\n v-for=\"file in lastFiles\"\n :key=\"fileKey(file)\"\n data-gr-file-upload-item\n class=\"flex items-center gap-2\"\n :class=\"hintClass\"\n >\n <img\n v-if=\"previewUrl(file)\"\n data-gr-file-upload-preview\n :src=\"previewUrl(file)\"\n alt=\"\"\n class=\"h-8 w-8 shrink-0 rounded-[var(--gr-radius-sm)] object-cover border border-[var(--gr-brd)]\"\n >\n <span class=\"font-600\">{{ file.name }}</span>\n <span class=\"text-[var(--gr-muted-fg)]\"> · {{ Math.ceil(file.size / 1024) }} KB</span>\n\n <template v-if=\"entryFor(file)\">\n <span\n data-gr-file-upload-status\n :data-status=\"entryFor(file)!.status\"\n class=\"text-[var(--gr-muted-fg)]\"\n >· {{ statusTextByStatus[entryFor(file)!.status] }}<template\n v-if=\"entryFor(file)!.status === 'uploading'\"\n >&nbsp;{{ Math.round(entryFor(file)!.percent) }}%</template></span>\n\n <button\n v-if=\"entryFor(file)!.status === 'uploading'\"\n data-gr-file-upload-abort-file\n type=\"button\"\n class=\"ml-auto text-[var(--gr-muted-fg)] hover:text-[var(--gr-fg)] transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--gr-ring)] rounded-[var(--gr-radius-sm)]\"\n :aria-label=\"t('gr.fileUpload.abortFile', 'Cancel upload of {fileName}', { fileName: file.name })\"\n @click.stop=\"abortFile(file)\"\n >\n <GrIcon :size=\"iconGlyphSize\">\n <IconClose />\n </GrIcon>\n </button>\n\n <button\n v-else-if=\"entryFor(file)!.status === 'error' && !isDisabled && !isReadonly\"\n data-gr-file-upload-retry-file\n type=\"button\"\n class=\"ml-auto text-[var(--gr-danger-text)] transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--gr-ring)] rounded-[var(--gr-radius-sm)] underline\"\n :aria-label=\"t('gr.fileUpload.retryFile', 'Retry {fileName}', { fileName: file.name })\"\n @click.stop=\"retryFile(file)\"\n >\n {{ t('gr.fileUpload.retry', 'Retry upload') }}\n </button>\n </template>\n\n <button\n v-if=\"!isDisabled && !isReadonly\"\n data-gr-file-upload-remove\n type=\"button\"\n class=\"ml-auto text-[var(--gr-muted-fg)] hover:text-[var(--gr-danger-text)] transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--gr-ring)] rounded-[var(--gr-radius-sm)]\"\n :aria-label=\"t('gr.fileUpload.remove', 'Remove {fileName}', { fileName: file.name })\"\n @click.stop=\"removeFile(file)\"\n >\n <GrIcon :size=\"iconGlyphSize\">\n <IconClose />\n </GrIcon>\n </button>\n </li>\n </ul>\n\n <slot\n v-if=\"showProgress || $slots.progress\"\n name=\"progress\"\n :state=\"state\"\n :percent=\"state.percent\"\n :indeterminate=\"state.phase === 'uploading' && state.indeterminate\"\n :phase=\"state.phase\"\n :files=\"lastFiles\"\n :abort=\"abort\"\n :retry=\"retry\"\n :file-entries=\"fileEntries\"\n :retry-file=\"retryFile\"\n :abort-file=\"abortFile\"\n >\n <div\n v-if=\"showProgress\"\n data-gr-file-upload-progress\n class=\"mt-3 transition-opacity duration-[var(--gr-duration-fast)]\"\n :class=\"progressVisible ? 'opacity-100' : 'opacity-0 pointer-events-none'\"\n :aria-hidden=\"!progressVisible\"\n >\n <div class=\"flex items-center gap-2\">\n <GrProgressBar\n :value=\"progressPercent\"\n :indeterminate=\"progressIndeterminate\"\n :tone=\"effectiveProgressTone\"\n :size=\"progressBarSize\"\n :aria-label=\"resolvedProgressLabel\"\n />\n <span\n data-gr-file-upload-progress-text\n class=\"text-[var(--gr-muted-fg)] tabular-nums min-w-[3ch] text-right\"\n :class=\"progressTextClass\"\n >{{ progressText }}</span>\n </div>\n </div>\n </slot>\n </div>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\" generic=\"TResponse = unknown\">\nimport { computed, nextTick, onBeforeUnmount, onMounted, ref, Text, useSlots, watch, type VNode } from 'vue'\n\nimport IconArrowUp from '~icons/lucide/arrow-up'\nimport IconClose from '~icons/lucide/x'\n\nimport GrIcon from '../GrIcon/GrIcon.vue'\nimport GrProgressBar from '../GrProgressBar/GrProgressBar.vue'\nimport type { GrProgressBarTone } from '../GrProgressBar/grStyle'\nimport type { FileValidator, FileValidatorSource } from '../../fileValidation'\nimport type { GrUploadProgressInfo } from './uploadViaXhr'\nimport type { GrUploadState } from './uploadState'\nimport { summarizeFileEntries, type GrFileUploadEntry } from './fileEntry'\nimport { useDropZone } from './useDropZone'\nimport { useFilePreviews } from '../../composables/internal/useFilePreviews'\nimport { usePerFileUpload } from './usePerFileUpload'\nimport { useUploadState } from './useUploadState'\n\nimport { useGrComponentSize } from '../GrConfigProvider/context'\nimport { flattenSlotNodes, meaningfulSlotNodes } from '../shared/slotNodes'\nimport {\n type GrFileUploadSize,\n grFileUploadZoneClass,\n hintSizes,\n iconGlyphSizes,\n iconTileSizes,\n labelSizes,\n progressBarSizes,\n progressTextSizes,\n zoneGaps,\n} from './grFileUploadStyles'\nimport { acceptValidator, FileValidationError, runFileValidators } from '../../fileValidation'\nimport { GrUploadAbortError, uploadViaXhr } from './uploadViaXhr'\nimport { useGrFormFieldContext } from '../GrFormField/context'\nimport { useGrFormControl } from '../../composables/useGrFormControl'\nimport { useFocusWithin } from '../../composables/internal/useFocusWithin'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\n\nexport type GrFileUploadMode = 'batch' | 'per-file'\n\nexport type GrFileUploadExtraDataValue = string | Blob\n\nexport type GrFileUploadExtraData = Record<string, GrFileUploadExtraDataValue | GrFileUploadExtraDataValue[]>\n\nexport type GrFileUploadRequestCtx = {\n signal: AbortSignal\n extraData?: GrFileUploadExtraData\n /**\n * Колбэк прогресса аплоада. Пользовательский `request` (например, axios)\n * должен вызывать его из `onUploadProgress`, чтобы `GrFileUpload` отображал\n * прогресс через слот `progress` / дефолтный `GrProgressBar`.\n */\n onProgress?: (info: GrUploadProgressInfo) => void\n}\n\nexport type GrFileUploadRequest<TResponse = unknown> = (\n files: File[],\n ctx: GrFileUploadRequestCtx,\n) => Promise<TResponse>\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<TResponse = unknown> {\n /**\n * Набор файлов — тот, что показан в списке и уйдёт в `retry`.\n *\n * Проп **необязателен**: без него компонент держит набор сам. Передан —\n * набор следует за ним, и потребитель может очистить или подменить список\n * снаружи.\n */\n modelValue?: File[]\n action?: string\n request?: GrFileUploadRequest<TResponse>\n name?: string\n multiple?: boolean\n limit?: number\n /**\n * Guard перед отправкой. Остаётся пропом, а не эмитом, осознанно: эмит не\n * возвращает значения, а этот колбэк обязан ответить «пускать или нет».\n * Уведомления — `exceed`, `success`, `error`, `progress` — эмиты.\n */\n beforeUpload?: (file: File) => boolean | Promise<unknown>\n validators?: FileValidator[]\n /**\n * W3C `accept` для `<input type=\"file\">` — и sugar к `acceptValidator(...)`.\n *\n * Только атрибута мало: он фильтрует системный диалог, но не drag&drop —\n * перетащить можно что угодно. Поэтому то же значение уходит и в валидаторы,\n * как в `GrFormFile`.\n */\n accept?: string\n /** `capture` для мобильной камеры/микрофона. */\n capture?: 'user' | 'environment'\n /** Выбор каталога целиком (`webkitdirectory`). Поддержка — Chromium и Safari. */\n directory?: boolean\n disabled?: boolean\n /** Только для чтения: значение видно и уходит в форму, но не редактируется. */\n readonly?: boolean\n /** Визуальное и ARIA-состояние ошибки. */\n invalid?: boolean\n /** Обязательное поле (`aria-required`). */\n required?: boolean\n /** Доступное имя вне `GrFormField`. */\n ariaLabel?: string\n headers?: Record<string, string>\n withCredentials?: boolean\n showFileList?: boolean\n uploadExtraData?: (files: File[]) => GrFileUploadExtraData | undefined\n /** i18n: надпись-подсказка в дефолтном UI. */\n placeholder?: string\n /** Показывать дефолтный прогресс-бар (если не используется слот `progress`). */\n showProgress?: boolean\n /** Цветовой тон полосы прогресса в фазе `uploading`. */\n progressTone?: GrProgressBarTone\n /** aria-label для прогресс-бара. */\n progressLabel?: string\n /** Через сколько мс после `success` скрыть прогресс-бар. `0` — не скрывать. */\n hideProgressOnSuccess?: number\n /** Размер дефолтного UI: поля дроп-зоны, плитка иконки, кегль подписей. */\n size?: GrFileUploadSize\n /**\n * Как уходит набор файлов:\n *\n * - `batch` (по умолчанию) — весь набор одним запросом. Про отдельный файл\n * сказать нечего, поэтому и статуса у него нет;\n * - `per-file` — на каждый файл свой запрос (`request` зовётся с массивом из\n * одного файла, контракт не меняется). Появляются статус и процент строки,\n * `retryFile` и `abortFile`.\n */\n uploadMode?: GrFileUploadMode\n /** Сколько файлов грузится одновременно в режиме `per-file`. */\n concurrency?: number\n /** Миниатюры для `image/*` в списке файлов. */\n preview?: boolean\n}\n\nexport interface GrFileUploadEmits<TResponse = unknown> {\n /** Набор файлов сменился: новый выбор или удаление. Не путать с `change`. */\n (e: 'update:modelValue', files: File[]): void\n /** Выбрано больше файлов, чем разрешает `limit`. Загрузка не стартует. */\n (e: 'exceed', files: File[], limit: number): void\n /** `file` приходит только в режиме `per-file`: в батче отчитываться нечем. */\n (e: 'success', payload: TResponse, file?: File): void\n (e: 'error', error: unknown, file?: File): void\n (e: 'progress', percent: number, info?: GrUploadProgressInfo, file?: File): void\n (e: 'change', files: File[]): void\n (e: 'stateChange', state: GrUploadState): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n}\n\nconst props = withDefaults(\n defineProps<GrFileUploadProps<TResponse>>(),\n {\n modelValue: undefined,\n action: undefined,\n request: undefined,\n name: 'file',\n multiple: false,\n limit: undefined,\n beforeUpload: undefined,\n validators: undefined,\n accept: undefined,\n capture: undefined,\n directory: false,\n disabled: false,\n readonly: false,\n invalid: false,\n required: false,\n ariaLabel: undefined,\n headers: undefined,\n withCredentials: false,\n showFileList: false,\n uploadExtraData: undefined,\n placeholder: undefined,\n showProgress: true,\n progressTone: 'primary',\n progressLabel: undefined,\n hideProgressOnSuccess: 800,\n size: undefined,\n uploadMode: 'batch',\n concurrency: 3,\n preview: false,\n },\n)\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrFileUpload' })\n\nconst zoneGapClass = computed(() => zoneGaps[resolvedSize.value])\nconst iconTileClass = computed(() => iconTileSizes[resolvedSize.value])\nconst iconGlyphSize = computed(() => iconGlyphSizes[resolvedSize.value])\nconst labelClass = computed(() => labelSizes[resolvedSize.value])\nconst hintClass = computed(() => hintSizes[resolvedSize.value])\nconst progressTextClass = computed(() => progressTextSizes[resolvedSize.value])\nconst progressBarSize = computed(() => progressBarSizes[resolvedSize.value])\n\nconst emit = defineEmits<GrFileUploadEmits<TResponse>>()\ndefineSlots<{\n /** Полностью своя зона загрузки: контрол отдаёт наружу всё своё состояние. */\n default?: (props: {\n openDialog: () => void\n abort: () => void\n disabled: boolean\n files: File[]\n isOver: boolean\n state: GrUploadState\n retry: () => Promise<void>\n removeFile: (file: File) => void\n fileEntries: GrFileUploadEntry[]\n retryFile: (file: File) => Promise<void>\n abortFile: (file: File) => void\n }) => any\n /** Заголовок зоны вместо `placeholder`. */\n label?: () => any\n /** Подпись под заголовком: ограничения по типу и размеру. */\n tip?: () => any\n /** Свой индикатор прогресса вместо встроенного. */\n progress?: (props: {\n state: GrUploadState\n percent: number\n indeterminate: boolean\n phase: GrUploadState['phase']\n files: File[]\n abort: () => void\n retry: () => Promise<void>\n fileEntries: GrFileUploadEntry[]\n retryFile: (file: File) => Promise<void>\n abortFile: (file: File) => void\n }) => any\n}>()\n\nconst slots = useSlots()\n\nconst { t } = useGranularityTranslations()\n\n// Контекст `GrFormField`: доступным контролом служит сам нативный\n// `<input type=\"file\">` — он и остаётся целью `<label for>`.\nconst field = useGrFormFieldContext()\nconst fieldId = computed(() => field?.id.value)\nconst describedBy = computed(() => field?.describedById.value)\nconst {\n disabled: isDisabled,\n readonly: isReadonly,\n invalid: isInvalid,\n required: isRequired,\n // `locked` — «ввод не принимается»: `disabled` или `readonly`. Набор в\n // `readonly` виден и уходит в форму, но поменять его нельзя.\n locked: isLocked,\n} = useGrFormControl(() => props)\nconst resolvedPlaceholder = computed(() => props.placeholder ?? t('gr.fileUpload.placeholder', 'Drag files here or click to select'))\nconst resolvedProgressLabel = computed(() => props.progressLabel ?? t('gr.fileUpload.progress', 'Upload progress'))\n\nfunction slotIsTextOnly(nodes: VNode[]): boolean {\n const meaningful = meaningfulSlotNodes(nodes)\n\n if (meaningful.length === 0)\n return true\n\n for (const node of meaningful) {\n if (node.type !== Text)\n 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 rootEl = ref<HTMLElement | null>(null)\n\n// Внутри зоны фокус ходит между нативным input'ом и кнопками списка файлов:\n// без границы каждое перемещение давало бы потребителю пару `blur` + `focus`.\nconst { onFocusIn, onFocusOut } = useFocusWithin(rootEl, {\n enter: event => emit('focus', event),\n leave: event => emit('blur', event),\n})\n\nconst lastFiles = ref<File[]>(props.modelValue ? [...props.modelValue] : [])\n\n/**\n * Набор меняется только здесь: иначе `update:modelValue` рано или поздно\n * разошёлся бы с тем, что показано в списке.\n */\nfunction setFiles(next: File[]): void {\n lastFiles.value = next\n emit('update:modelValue', next)\n}\n\n// Контролируемый режим: проп задан — он и есть источник истины.\nwatch(() => props.modelValue, (next) => {\n if (next === undefined)\n return\n if (next === lastFiles.value)\n return\n\n lastFiles.value = [...next]\n}, { deep: false })\n\nconst {\n state,\n setIdle: setStateIdle,\n setUploading: setStateUploading,\n setSuccess: setStateSuccess,\n setError: setStateError,\n applyProgress,\n assign: assignState,\n scheduleIdle,\n clearScheduledIdle,\n} = useUploadState({ onChange: next => emit('stateChange', next) })\n\nlet activeController: AbortController | null = null\n\nfunction handleProgress(info: GrUploadProgressInfo) {\n applyProgress(info)\n emit('progress', info.percent, info)\n}\n\nconst { fileKey, previewUrl, revokePreview, revokeAllPreviews } = useFilePreviews({\n enabled: () => props.preview,\n})\n\n/**\n * `extraData` считается один раз на набор и не меняется между файлами, поэтому\n * живёт рядом с очередью, а не путешествует через её контракт.\n */\nlet perFileExtraData: GrFileUploadExtraData | undefined\n\nconst perFile = usePerFileUpload({\n concurrency: () => props.concurrency,\n send: (files, ctx) => (props.request\n ? props.request(files, { signal: ctx.signal, extraData: perFileExtraData, onProgress: ctx.onProgress })\n : uploadViaXhr({\n url: props.action ?? '',\n files,\n name: props.name,\n headers: props.headers,\n withCredentials: props.withCredentials,\n extraData: perFileExtraData,\n signal: ctx.signal,\n onProgress: ctx.onProgress,\n })),\n onSummary: assignState,\n onProgress: (info, file) => emit('progress', info.percent, info, file),\n onSuccess: (payload, file) => emit('success', payload as TResponse, file),\n onError: (error, file) => emit('error', error, file),\n onSetChange: files => emit('change', files),\n})\n\nconst fileEntries = perFile.entries\nconst { retryFile, abortFile } = perFile\n\nconst dropZone = useDropZone({\n locked: () => isLocked.value,\n inactive: () => hasCustomUi.value,\n onDrop: files => void handleFiles(files, 'drop'),\n})\nconst isOver = dropZone.isOver\n\n/**\n * Дефолтный слот вызывается из двух мест — как своя зона целиком и как подпись\n * внутри стандартной. Набор пропов у него один: две копии разъехались бы.\n */\nconst defaultSlotProps = computed(() => ({\n openDialog,\n abort,\n disabled: isDisabled.value,\n files: lastFiles.value,\n isOver: isOver.value,\n state,\n retry,\n removeFile,\n fileEntries: fileEntries.value,\n retryFile,\n abortFile,\n}))\n\nconst zoneClass = computed(() => grFileUploadZoneClass({\n size: resolvedSize.value,\n disabled: isDisabled.value,\n readonly: isReadonly.value,\n over: isOver.value,\n}))\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 openDialog() {\n if (isLocked.value)\n return\n inputRef.value?.click()\n}\n\nfunction abort() {\n activeController?.abort()\n activeController = null\n}\n\nfunction totalSizeOf(files: File[]): number {\n return files.reduce((sum, file) => sum + file.size, 0)\n}\n\nfunction normalizeLimit(limit: number | undefined): number | undefined {\n if (typeof limit !== 'number')\n return undefined\n if (!Number.isFinite(limit))\n return undefined\n if (limit <= 0)\n return undefined\n return Math.floor(limit)\n}\n\nasync function uploadViaAction(files: File[], signal: AbortSignal, extraData: GrFileUploadExtraData | undefined) {\n if (!props.action)\n throw new Error('GrFileUpload: either `action` or `request` must be provided')\n\n return uploadViaXhr({\n url: props.action,\n files,\n name: props.name,\n headers: props.headers,\n withCredentials: props.withCredentials,\n extraData,\n signal,\n onProgress: handleProgress,\n })\n}\n\nasync function runBeforeUpload(files: File[]): Promise<'ok' | { aborted: true, reason: unknown }> {\n if (!props.beforeUpload)\n 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 }\n catch (error) {\n return { aborted: true, reason: error }\n }\n }\n\n return 'ok'\n}\n\n/**\n * `accept` идёт первым валидатором: атрибут на input фильтрует только диалог,\n * а drop мимо него проходит.\n */\nfunction effectiveValidators(): FileValidator[] {\n return [acceptValidator(props.accept), ...(props.validators ?? [])]\n}\n\n/**\n * Номер запуска. Валидация и `beforeUpload` асинхронны, поэтому два быстрых\n * выбора подряд идут внахлёст: без номера «побеждал» тот, чьи валидаторы\n * отработали позже, — он же обрывал уже стартовавшую загрузку соседа.\n * Актуален всегда последний выбор, остальные тихо сходят с дистанции.\n */\nlet runCounter = 0\n\nasync function handleFiles(files: File[], source: FileValidatorSource = 'input') {\n if (isLocked.value)\n return\n if (!files.length)\n return\n\n runCounter += 1\n const runId = runCounter\n const isStale = (): boolean => runId !== runCounter\n\n const normalizedLimit = normalizeLimit(props.limit)\n if (props.multiple && normalizedLimit && files.length > normalizedLimit) {\n emit('exceed', 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, effectiveValidators(), {\n source,\n multiple: props.multiple,\n })\n\n if (isStale())\n return\n\n if (issues.length > 0) {\n emit('error', new FileValidationError(issues, valid))\n return\n }\n\n if (!valid.length)\n return\n\n // Набор сменился — миниатюры прежнего больше не нужны.\n revokeAllPreviews()\n setFiles(valid)\n\n const before = await runBeforeUpload(valid)\n if (isStale())\n return\n\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 }\n catch (error) {\n emit('error', error)\n return\n }\n\n abort()\n clearScheduledIdle()\n\n if (props.uploadMode === 'per-file') {\n perFileExtraData = extraData\n await perFile.run(valid)\n return\n }\n\n const controller = new AbortController()\n activeController = controller\n\n // Батч: пофайловых записей нет, показываем набор без статусов.\n perFile.reset()\n\n setStateUploading()\n emit('progress', 0, { percent: 0, loaded: 0, total: 0, indeterminate: true })\n\n try {\n // `action`-ветка отдаёт разобранный ответ сервера: типом его знает только\n // потребитель, поэтому приводим к его же `TResponse`.\n const payload = (props.request\n ? await props.request(valid, { signal: controller.signal, extraData, onProgress: handleProgress })\n : await uploadViaAction(valid, controller.signal, extraData)) as TResponse\n\n // Кастомный `request` не обязан звать `onProgress` — тогда байты неизвестны.\n // Сумма размеров выбранных файлов честнее нуля: «100%» при `total: 0`\n // потребитель прочитает как «загружено ноль».\n const measured = state.phase === 'uploading' ? state.total || state.loaded : 0\n const finalLoaded = measured || totalSizeOf(valid)\n setStateSuccess({ loaded: finalLoaded, total: finalLoaded })\n emit('success', payload)\n emit('change', valid)\n emit('progress', 100, { percent: 100, loaded: finalLoaded, total: finalLoaded, indeterminate: false })\n\n if (props.hideProgressOnSuccess && props.hideProgressOnSuccess > 0)\n scheduleIdle(props.hideProgressOnSuccess)\n }\n catch (error) {\n if (error instanceof GrUploadAbortError) {\n setStateIdle()\n }\n else {\n setStateError(error)\n }\n emit('error', error)\n }\n finally {\n if (activeController === controller)\n activeController = null\n }\n}\n\n/**\n * `<input type=\"file\">` не понимает `readonly` — атрибута для него в HTML нет.\n * Поэтому системный диалог гасим отменой действия по умолчанию: input остаётся\n * в порядке `Tab` и объявляется как «только чтение», но нового файла не примет.\n */\nfunction onInputClick(event: MouseEvent) {\n if (isReadonly.value && !isDisabled.value)\n event.preventDefault()\n}\n\nasync function onInputChange(event: Event) {\n if (isLocked.value)\n 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)\n target.value = ''\n\n await nextTick()\n await handleFiles(files, 'input')\n}\n\n// Клавиатуры на зоне нет: Enter/Space обрабатывает сам file-input, на котором и\n// стоит фокус. Собственный обработчик здесь только дублировал бы нативный и\n// открывал диалог дважды.\nfunction onRootClick(event: MouseEvent) {\n if (hasCustomUi.value)\n return\n // `openDialog` кликает по input программно, и этот клик всплывает обратно сюда.\n // Без отсечки зона открывала бы диалог по кругу.\n if (event.target === inputRef.value)\n return\n\n openDialog()\n}\n\nconst effectiveProgressTone = computed<GrProgressBarTone>(() => {\n if (state.phase === 'error')\n return 'danger'\n if (state.phase === 'success')\n return 'success'\n return props.progressTone\n})\n\n/**\n * Живой регион существует с первого рендера и пуст, пока объявлять нечего:\n * регион, появляющийся сразу с текстом, часть AT не объявляет вовсе. Прогресс\n * в процентах сюда не идёт — диктор захлебнётся; объявляются только фазы.\n */\nconst liveMessage = computed(() => {\n if (state.phase === 'uploading')\n return t('gr.fileUpload.uploading', 'Uploading…')\n if (state.phase === 'success')\n return t('gr.fileUpload.success', 'Upload complete')\n if (state.phase === 'error')\n return t('gr.fileUpload.error', 'Upload failed')\n return ''\n})\n\nconst progressVisible = computed(() => state.phase !== 'idle')\nconst progressIndeterminate = computed(() => state.phase === 'uploading' && state.indeterminate)\nconst progressPercent = computed(() => (progressIndeterminate.value ? 0 : state.percent))\nconst progressText = computed(() => {\n if (progressIndeterminate.value)\n return ''\n return `${Math.round(progressPercent.value)}%`\n})\n\nconst isPerFile = computed(() => props.uploadMode === 'per-file')\n\nconst statusTextByStatus = computed<Record<string, string>>(() => ({\n pending: t('gr.fileUpload.statusPending', 'Pending'),\n uploading: t('gr.fileUpload.statusUploading', 'Uploading'),\n success: t('gr.fileUpload.statusSuccess', 'Uploaded'),\n error: t('gr.fileUpload.statusError', 'Failed'),\n}))\n\n/** Запись файла для разметки: в батчевом режиме её нет — и статуса тоже. */\nfunction entryFor(file: File): GrFileUploadEntry | undefined {\n return isPerFile.value ? perFile.entryOf(file) : undefined\n}\n\n/** Повторить загрузку текущего набора — после ошибки выбирать файлы заново незачем. */\nasync function retry(): Promise<void> {\n if (!lastFiles.value.length)\n return\n\n await handleFiles([...lastFiles.value], 'input')\n}\n\n/**\n * Убрать файл из набора. Идущая загрузка при этом обрывается: она была про\n * прежний набор, и её результат к новому отношения не имеет.\n */\nfunction removeFile(file: File): void {\n const next = lastFiles.value.filter(item => item !== file)\n if (next.length === lastFiles.value.length)\n return\n\n abortFile(file)\n abort()\n clearScheduledIdle()\n revokePreview(file)\n setFiles(next)\n perFile.dropEntry(file)\n\n // В пофайловом режиме остальные файлы своих статусов не теряют: удалили один\n // — сводное состояние просто пересчитывается по оставшимся.\n if (props.uploadMode === 'per-file' && fileEntries.value.length)\n assignState(summarizeFileEntries(fileEntries.value))\n else if (state.phase !== 'idle')\n setStateIdle()\n}\n\nfunction focus(): void {\n inputRef.value?.focus()\n}\n\nfunction blur(): void {\n inputRef.value?.blur()\n}\n\n// Ни `action`, ни `request` — загружать некуда, и узнать об этом на первом же\n// выборе файла поздно. Выразить требование типом нельзя: `defineProps` в SFC\n// принимает объектный тип или интерфейс, но не discriminated union.\nonMounted(() => {\n if (!__GR_DEV__)\n return\n if (props.action || props.request)\n return\n\n console.warn('[GrFileUpload] не задан ни `action`, ни `request`: отправлять файлы некуда.')\n})\n\n// Хвосты, переживающие размонтирование: незавершённый XHR продолжает качать\n// файл, а таймер скрытия успеха дёргает `setStateIdle()` на уничтоженном\n// инстансе. Сценарий обычный — успешная загрузка и уход со страницы сразу после.\nonBeforeUnmount(() => {\n clearScheduledIdle()\n abort()\n perFile.abortAll()\n revokeAllPreviews()\n})\n\ndefineExpose({\n focus,\n blur,\n uploadFiles: handleFiles,\n abort,\n openDialog,\n state,\n /** Текущий набор файлов — тот, что показан в списке и уйдёт в `retry`. */\n files: lastFiles,\n retry,\n removeFile,\n /** Записи по файлам — пусты в батчевом режиме. */\n fileEntries,\n retryFile,\n abortFile,\n})\n\n/**\n * Drop-zone намеренно БЕЗ `role=\"button\"` и `tabindex`: доступным контролом\n * служит сам нативный `<input type=\"file\">`. Роль-виджет объявляет потомков\n * презентационными, поэтому input внутри неё теряется для скринридеров и\n * падает в axe на `nested-interactive`; а обёртывать его в собственный\n * «button» — значит вручную переизобретать то, что file-input уже умеет\n * нативно (открытие диалога с клавиатуры, объявление «кнопка выбора файла»).\n * Зона остаётся кликабельной и принимает drag&drop, фокус input'а показывает\n * через `focus-within`.\n */\n</script>\n\n<template>\n <div\n ref=\"rootEl\"\n data-gr-file-upload\n :class=\"hasCustomUi ? 'inline-block' : zoneClass\"\n @click=\"onRootClick\"\n @focusin=\"onFocusIn\"\n @focusout=\"onFocusOut\"\n @dragenter=\"dropZone.onDragEnter\"\n @dragover=\"dropZone.onDragOver\"\n @dragleave=\"dropZone.onDragLeave\"\n @drop=\"dropZone.onDropFiles\"\n >\n <!-- В custom-UI контрол рисует слот (он и вызывает `openDialog`), поэтому там\n input прячется и от таба, и от дерева доступности — иначе получим два\n контрола на один смысл. -->\n <input\n :id=\"fieldId\"\n ref=\"inputRef\"\n data-gr-file-upload-input\n :style=\"hiddenInputStyle\"\n type=\"file\"\n :tabindex=\"hasCustomUi || isDisabled ? -1 : 0\"\n :aria-hidden=\"hasCustomUi ? 'true' : undefined\"\n :aria-label=\"ariaLabel ?? (hasCustomUi ? undefined : resolvedPlaceholder)\"\n :aria-describedby=\"describedBy\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n :aria-required=\"isRequired ? 'true' : undefined\"\n :aria-readonly=\"isReadonly ? 'true' : undefined\"\n :name=\"name\"\n :accept=\"accept\"\n :capture=\"capture\"\n :webkitdirectory=\"directory || undefined\"\n :multiple=\"multiple\"\n :disabled=\"isDisabled\"\n @click=\"onInputClick\"\n @change=\"onInputChange\"\n >\n\n <span\n data-gr-file-upload-live\n class=\"sr-only\"\n role=\"status\"\n aria-live=\"polite\"\n >{{ liveMessage }}</span>\n\n <slot v-if=\"hasCustomUi\" v-bind=\"defaultSlotProps\" />\n\n <div v-else class=\"flex items-start\" :class=\"zoneGapClass\">\n <div\n data-gr-file-upload-icon\n class=\"shrink-0 bg-[var(--gr-muted)] border border-[var(--gr-brd)] flex items-center justify-center\"\n :class=\"iconTileClass\"\n aria-hidden=\"true\"\n >\n <GrIcon class=\"text-[var(--gr-muted-fg)]\" :size=\"iconGlyphSize\">\n <IconArrowUp />\n </GrIcon>\n </div>\n\n <div class=\"min-w-0\">\n <div data-gr-file-upload-label class=\"font-700\" :class=\"labelClass\">\n <slot name=\"label\">\n <slot v-bind=\"defaultSlotProps\">\n {{ resolvedPlaceholder }}\n </slot>\n </slot>\n </div>\n\n <div v-if=\"$slots.tip\" data-gr-file-upload-tip class=\"mt-1 text-[var(--gr-muted-fg)]\" :class=\"hintClass\">\n <slot name=\"tip\" />\n </div>\n <div v-else class=\"mt-1 text-[var(--gr-muted-fg)]\" :class=\"hintClass\" />\n\n <ul v-if=\"showFileList && lastFiles.length\" data-gr-file-upload-list class=\"mt-3 space-y-1\">\n <li\n v-for=\"file in lastFiles\"\n :key=\"fileKey(file)\"\n data-gr-file-upload-item\n class=\"flex items-center gap-2\"\n :class=\"hintClass\"\n >\n <img\n v-if=\"previewUrl(file)\"\n data-gr-file-upload-preview\n :src=\"previewUrl(file)\"\n alt=\"\"\n class=\"h-8 w-8 shrink-0 rounded-[var(--gr-radius-sm)] object-cover border border-[var(--gr-brd)]\"\n >\n <span class=\"font-600\">{{ file.name }}</span>\n <span class=\"text-[var(--gr-muted-fg)]\"> · {{ Math.ceil(file.size / 1024) }} KB</span>\n\n <template v-if=\"entryFor(file)\">\n <span\n data-gr-file-upload-status\n :data-status=\"entryFor(file)!.status\"\n class=\"text-[var(--gr-muted-fg)]\"\n >· {{ statusTextByStatus[entryFor(file)!.status] }}<template\n v-if=\"entryFor(file)!.status === 'uploading'\"\n >&nbsp;{{ Math.round(entryFor(file)!.percent) }}%</template></span>\n\n <button\n v-if=\"entryFor(file)!.status === 'uploading'\"\n data-gr-file-upload-abort-file\n type=\"button\"\n class=\"ml-auto text-[var(--gr-muted-fg)] hover:text-[var(--gr-fg)] transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--gr-ring)] rounded-[var(--gr-radius-sm)]\"\n :aria-label=\"t('gr.fileUpload.abortFile', 'Cancel upload of {fileName}', { fileName: file.name })\"\n @click.stop=\"abortFile(file)\"\n >\n <GrIcon :size=\"iconGlyphSize\">\n <IconClose />\n </GrIcon>\n </button>\n\n <button\n v-else-if=\"entryFor(file)!.status === 'error' && !isDisabled && !isReadonly\"\n data-gr-file-upload-retry-file\n type=\"button\"\n class=\"ml-auto text-[var(--gr-danger-text)] transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--gr-ring)] rounded-[var(--gr-radius-sm)] underline\"\n :aria-label=\"t('gr.fileUpload.retryFile', 'Retry {fileName}', { fileName: file.name })\"\n @click.stop=\"retryFile(file)\"\n >\n {{ t('gr.fileUpload.retry', 'Retry upload') }}\n </button>\n </template>\n\n <button\n v-if=\"!isDisabled && !isReadonly\"\n data-gr-file-upload-remove\n type=\"button\"\n class=\"ml-auto text-[var(--gr-muted-fg)] hover:text-[var(--gr-danger-text)] transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--gr-ring)] rounded-[var(--gr-radius-sm)]\"\n :aria-label=\"t('gr.fileUpload.remove', 'Remove {fileName}', { fileName: file.name })\"\n @click.stop=\"removeFile(file)\"\n >\n <GrIcon :size=\"iconGlyphSize\">\n <IconClose />\n </GrIcon>\n </button>\n </li>\n </ul>\n\n <slot\n v-if=\"showProgress || $slots.progress\"\n name=\"progress\"\n :state=\"state\"\n :percent=\"state.percent\"\n :indeterminate=\"state.phase === 'uploading' && state.indeterminate\"\n :phase=\"state.phase\"\n :files=\"lastFiles\"\n :abort=\"abort\"\n :retry=\"retry\"\n :file-entries=\"fileEntries\"\n :retry-file=\"retryFile\"\n :abort-file=\"abortFile\"\n >\n <div\n v-if=\"showProgress\"\n data-gr-file-upload-progress\n class=\"mt-3 transition-opacity duration-[var(--gr-duration-fast)]\"\n :class=\"progressVisible ? 'opacity-100' : 'opacity-0 pointer-events-none'\"\n :aria-hidden=\"!progressVisible\"\n >\n <div class=\"flex items-center gap-2\">\n <GrProgressBar\n :value=\"progressPercent\"\n :indeterminate=\"progressIndeterminate\"\n :tone=\"effectiveProgressTone\"\n :size=\"progressBarSize\"\n :aria-label=\"resolvedProgressLabel\"\n />\n <span\n data-gr-file-upload-progress-text\n class=\"text-[var(--gr-muted-fg)] tabular-nums min-w-[3ch] text-right\"\n :class=\"progressTextClass\"\n >{{ progressText }}</span>\n </div>\n </div>\n </slot>\n </div>\n </div>\n </div>\n</template>\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"GrFormFile-Dlxx-9zi.js","names":[],"sources":["../../../../src/components/GrFormFile/GrFormFile.vue","../../../../src/components/GrFormFile/GrFormFile.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, nextTick, onBeforeUnmount, ref, useId, 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 { useGrComponentSize } from '../GrConfigProvider/context'\nimport {\n type GrFormFileSize,\n buttonSizes,\n iconOffsets,\n iconSizes,\n previewBaseClass,\n previewSizes,\n removeTextSizes,\n rowGaps,\n stackGaps,\n textSizes,\n} from './grFormFileStyles'\nimport { useGrFormFieldContext } from '../GrFormField/context'\nimport { useGrFormControl } from '../../composables/useGrFormControl'\nimport { useFocusWithin } from '../../composables/internal/useFocusWithin'\nimport { useFilePreviews } from '../../composables/internal/useFilePreviews'\nimport { vDropzone } from '../../directives'\nimport { acceptValidator, FileValidationError, maxCountValidator, resolveFileValidationMessage, runFileValidators } from '../../fileValidation'\nimport type { FileValidationIssue, FileValidator } from '../../fileValidation'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\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 /** Только для чтения: значение видно и уходит в форму, но не редактируется. */\n readonly?: boolean\n /** Визуальное и ARIA-состояние ошибки. */\n invalid?: boolean\n /** Обязательное поле (`aria-required`). */\n required?: boolean\n /** Доступное имя вне `GrFormField`. */\n ariaLabel?: string\n /** W3C `accept` для `<input type=\"file\">` + sugar к `acceptValidator(...)`. */\n accept?: string\n validators?: FileValidator[]\n /** Максимум файлов в наборе. Лишние не обрезаются молча — набор отбивается ошибкой. */\n limit?: number\n uploadText?: string\n changeText?: string\n removeText?: string\n clearAllText?: string\n placeholder?: string\n /** Размер кнопок, иконок и подписей. */\n size?: GrFormFileSize\n /** Миниатюры для картинок в наборе. Файлы других типов остаются строкой. */\n preview?: boolean\n /** Дополнительная (кастомная) валидация на стороне потребителя. */\n validate?: (files: File[]) => GrFormFileError[] | Promise<GrFormFileError[]>\n /**\n * Контролируемый список ошибок: `v-model:errors`. Задан — показывается он, и\n * внутренняя валидация его не перетирает. Сюда же кладутся ошибки, пришедшие\n * с сервера. Не задан — компонент держит свои ошибки сам.\n */\n errors?: GrFormFileError[]\n}\n\nexport interface GrFormFileEmits {\n (e: 'update:modelValue', value: File | File[] | null): void\n (e: 'change', value: File | File[] | null): void\n (e: 'clear'): void\n /** Результат валидации. Канал один: `validation` дублировал эту же нагрузку. */\n (e: 'update:errors', errors: GrFormFileError[]): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n}\n\nconst props = withDefaults(\n defineProps<GrFormFileProps>(),\n {\n multiple: false,\n preview: false,\n disabled: false,\n readonly: false,\n invalid: false,\n required: false,\n ariaLabel: undefined,\n accept: undefined,\n limit: undefined,\n uploadText: undefined,\n changeText: undefined,\n removeText: undefined,\n clearAllText: undefined,\n placeholder: undefined,\n size: undefined,\n validators: undefined,\n validate: undefined,\n errors: undefined,\n },\n)\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrFormFile' })\n\nconst rowClass = computed(() => rowGaps[resolvedSize.value])\nconst stackClass = computed(() => stackGaps[resolvedSize.value])\nconst textClass = computed(() => textSizes[resolvedSize.value])\nconst removeTextClass = computed(() => removeTextSizes[resolvedSize.value])\nconst iconOffsetClass = computed(() => iconOffsets[resolvedSize.value])\nconst buttonSize = computed(() => buttonSizes[resolvedSize.value])\nconst iconSize = computed(() => iconSizes[resolvedSize.value])\nconst previewClass = computed(() => [previewSizes[resolvedSize.value], previewBaseClass])\n\nconst emit = defineEmits<GrFormFileEmits>()\ndefineSlots<{\n /** Собственный вывод ошибок вместо списка по умолчанию. */\n error?: (props: { errors: GrFormFileError[] }) => any\n}>()\n\nconst { t } = useGranularityTranslations()\n\n// Контекст `GrFormField`. Виджет здесь — кнопка выбора файла, а не нативный\n// `<input type=\"file\">`: он `aria-hidden` и вне таб-порядка, id на нём увёл бы\n// `<label for>` в невидимый элемент.\nconst field = useGrFormFieldContext()\nconst fieldId = computed(() => field?.id.value)\nconst {\n disabled: isDisabled,\n invalid: isInvalid,\n required: isRequired,\n readonly: isReadonly,\n // `locked` — «ввод не принимается»: `disabled` или `readonly`. Набор в\n // `readonly` виден и уходит в форму, но поменять его нельзя ни диалогом, ни\n // перетаскиванием, ни удалением.\n locked: isLocked,\n} = useGrFormControl(() => props)\nconst resolvedUploadText = computed(() => props.uploadText ?? t('gr.formFile.upload', 'Upload file'))\nconst resolvedChangeText = computed(() => props.changeText ?? t('gr.formFile.change', 'Change file'))\nconst resolvedRemoveText = computed(() => props.removeText ?? t('gr.formFile.remove', 'Remove'))\nconst resolvedClearAllText = computed(() => props.clearAllText ?? t('gr.formFile.clearAll', 'Clear all'))\nconst resolvedPlaceholder = computed(() => props.placeholder ?? t('gr.formFile.placeholder', 'No files selected'))\n\nconst inputRef = ref<HTMLInputElement | null>(null)\nconst rootEl = ref<HTMLElement | null>(null)\n\n// Внутри зоны фокус ходит между нативным input'ом и кнопками: без границы\n// каждое перемещение давало бы потребителю пару `blur` + `focus`.\nconst { onFocusIn, onFocusOut } = useFocusWithin(rootEl, {\n enter: event => emit('focus', event),\n leave: event => emit('blur', event),\n})\nconst localErrors = ref<GrFormFileError[]>([])\nconst uploadBtnEl = ref<HTMLElement | null>(null)\n\n// Собственные ошибки компонент обязан и объявить (`role=\"alert\"`), и связать с\n// контролом: до этого «уронил файл не того типа» для скринридера выглядело как\n// «ничего не произошло». Ошибка поля из `GrFormField` при этом остаётся —\n// `aria-describedby` держит оба id.\nconst errorsId = useId()\n// Контролируемый список сильнее внутреннего — как `sortKey` у `GrDataTable`.\nconst displayedErrors = computed(() => props.errors ?? localErrors.value)\nconst hasLocalErrors = computed(() => displayedErrors.value.length > 0)\n\n/**\n * `aria-required` и `aria-readonly` роль `button` не поддерживает — axe роняет\n * это как critical `aria-allowed-attr`. Состояния уходят в описание кнопки:\n * иначе они пропали бы для диктора совсем (маркер `*` у `GrFormField`\n * декоративен и скрыт).\n */\nconst stateHintId = useId()\nconst stateHint = computed(() => [\n isRequired.value ? t('gr.form.required', 'This field is required') : undefined,\n isReadonly.value ? t('gr.form.readonly', 'Read only') : undefined,\n].filter(Boolean).join('. ') || undefined)\n\nconst describedByIds = computed(() => {\n return [\n field?.describedById.value,\n hasLocalErrors.value ? errorsId : undefined,\n stateHint.value ? stateHintId : undefined,\n ]\n .filter(Boolean)\n .join(' ') || undefined\n})\n\nconst showsInvalid = computed(() => isInvalid.value || hasLocalErrors.value)\n\nfunction focus(): void {\n uploadBtnEl.value?.focus()\n}\n\nfunction blur(): void {\n uploadBtnEl.value?.blur()\n}\n\ndefineExpose({ focus, blur })\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\nconst { fileKey, previewUrl, revokePreview, revokeAllPreviews } = useFilePreviews({\n enabled: () => props.preview,\n})\n\n/**\n * Отзыв `object URL` привязан к самому набору, а не к местам, где его меняют.\n * Файл уходит из набора десятком путей — кнопка строки, «очистить всё», новый\n * выбор, внешний сброс `v-model`, — и точечные вызовы разъехались бы с первой\n * же новой веткой; blob при этом висел бы в памяти вкладки до перезагрузки.\n */\nwatch(files, (next, prev) => {\n for (const file of prev ?? []) {\n if (!next.includes(file))\n revokePreview(file)\n }\n})\n\nonBeforeUnmount(revokeAllPreviews)\n\n/**\n * Один набор валидаторов на оба пути ввода. Собери его отдельно в `applyFiles`\n * и в `dropzone` — копии разъедутся при первой же правке, и выбор через диалог\n * начнёт вести себя иначе, чем перетаскивание.\n */\nconst effectiveValidators = computed<FileValidator[]>(() => {\n const customValidator: FileValidator | undefined = props.validate\n ? async ({ files }) => (await props.validate?.(files)) ?? []\n : undefined\n\n return [\n acceptValidator(props.accept),\n maxCountValidator(props.limit),\n ...(props.validators ?? []),\n ...(customValidator ? [customValidator] : []),\n ]\n})\n\nfunction clearInputValue() {\n if (!inputRef.value)\n return\n inputRef.value.value = ''\n}\n\nfunction setErrors(next: GrFormFileError[]) {\n localErrors.value = next\n emit('update:errors', next)\n}\n\nfunction clearErrors() {\n if (displayedErrors.value.length === 0)\n return\n setErrors([])\n}\n\nfunction openDialog() {\n if (isLocked.value)\n 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 res = runFileValidators(nextFiles, effectiveValidators.value, {\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 (isLocked.value)\n 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)\n target.value = ''\n await nextTick()\n\n await applyFiles(nextFiles)\n}\n\nfunction clearAll() {\n if (isLocked.value)\n 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 (isLocked.value)\n 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\n/** Размер файла в строке списка — как в `GrFileUpload`: без него список не отвечает «сколько». */\nfunction formatFileSize(file: File): string {\n return `${Math.ceil(file.size / 1024)} KB`\n}\n\nfunction issueMessage(issue: GrFormFileError): string {\n const text = resolveFileValidationMessage(issue, t)\n\n // Префикс с именем файла нужен только тем сообщениям, которые сами его не\n // называют, — то есть валидаторам потребителя. Встроенные передают `fileName`\n // параметром и подставляют его в текст, и приписка давала бы «photo.png:\n // Файл «photo.png» …». Признак — наличие параметра, а не поиск подстроки.\n if (issue.fileName && issue.i18nParams?.fileName === undefined)\n return `${issue.fileName}: ${text}`\n\n return text\n}\n\nconst dropzone = computed(() => {\n return {\n enabled: !isLocked.value,\n multiple: props.multiple,\n validators: effectiveValidators.value,\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: t('gr.formFile.validateError', 'Failed to validate dropped files'),\n },\n ])\n }\n },\n }\n})\n\nwatch(\n () => props.modelValue,\n (value) => {\n if (!inputRef.value)\n 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 ref=\"rootEl\"\n v-dropzone=\"dropzone\"\n data-gr-form-file\n class=\"rounded-[var(--gr-radius-md)]\"\n :class=\"isDisabled ? 'cursor-not-allowed' : ''\"\n @focusin=\"onFocusIn\"\n @focusout=\"onFocusOut\"\n >\n <span v-if=\"stateHint\" :id=\"stateHintId\" class=\"sr-only\">{{ stateHint }}</span>\n\n <input\n ref=\"inputRef\"\n data-gr-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=\"isDisabled\"\n @change=\"onInputChange\"\n >\n\n <div class=\"flex flex-col\" :class=\"stackClass\">\n <div class=\"flex flex-wrap items-center\" :class=\"rowClass\">\n <GrButton\n :id=\"fieldId\"\n ref=\"uploadBtnEl\"\n variant=\"secondary\"\n :size=\"buttonSize\"\n data-gr-form-file-upload-btn\n :aria-describedby=\"describedByIds\"\n :aria-invalid=\"showsInvalid ? 'true' : undefined\"\n :aria-label=\"ariaLabel\"\n :disabled=\"isDisabled\"\n @click.prevent=\"openDialog\"\n >\n <GrIcon :size=\"iconSize\">\n <IconUpload />\n </GrIcon>\n <span :class=\"iconOffsetClass\">{{ hasFiles ? resolvedChangeText : resolvedUploadText }}</span>\n </GrButton>\n\n <GrButton\n v-if=\"hasFiles && !multiple && !isReadonly\"\n variant=\"secondary\"\n :size=\"buttonSize\"\n data-gr-form-file-clear-btn\n :disabled=\"isDisabled\"\n @click.prevent=\"clearAll\"\n >\n <GrIcon :size=\"iconSize\">\n <IconX />\n </GrIcon>\n <span :class=\"iconOffsetClass\">{{ resolvedRemoveText }}</span>\n </GrButton>\n\n <GrButton\n v-if=\"multiple && hasFiles && !isReadonly\"\n variant=\"secondary\"\n :size=\"buttonSize\"\n data-gr-form-file-clear-all-btn\n :disabled=\"isDisabled\"\n @click.prevent=\"clearAll\"\n >\n <GrIcon :size=\"iconSize\">\n <IconX />\n </GrIcon>\n <span :class=\"iconOffsetClass\">{{ resolvedClearAllText }}</span>\n </GrButton>\n\n <img\n v-if=\"!multiple && hasFiles && previewUrl(files[0]!)\"\n data-gr-form-file-preview\n :src=\"previewUrl(files[0]!)\"\n alt=\"\"\n :class=\"previewClass\"\n >\n\n <span\n v-if=\"!multiple && hasFiles\"\n class=\"text-[var(--gr-muted-fg)] truncate max-w-[240px]\"\n :class=\"textClass\"\n data-gr-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-[var(--gr-muted-fg)]\"\n :class=\"textClass\"\n data-gr-form-file-placeholder\n >\n {{ resolvedPlaceholder }}\n </span>\n </div>\n\n <div v-if=\"multiple && hasFiles\" class=\"flex flex-col\" :class=\"stackClass\">\n <div\n v-for=\"(file, index) in files\"\n :key=\"fileKey(file)\"\n class=\"flex items-center gap-2\"\n data-gr-form-file-item\n >\n <img\n v-if=\"previewUrl(file)\"\n data-gr-form-file-preview\n :src=\"previewUrl(file)\"\n alt=\"\"\n :class=\"previewClass\"\n >\n\n <span\n class=\"text-[var(--gr-muted-fg)] truncate max-w-[240px]\"\n :class=\"textClass\"\n :title=\"file.name\"\n data-gr-form-file-item-name\n >\n {{ file.name }}\n </span>\n\n <span\n class=\"text-[var(--gr-muted-fg)] shrink-0\"\n :class=\"removeTextClass\"\n data-gr-form-file-item-size\n >{{ formatFileSize(file) }}</span>\n\n <button\n v-if=\"!isReadonly\"\n type=\"button\"\n class=\"text-[var(--gr-muted-fg)] hover:text-[var(--gr-fg)]\"\n :class=\"removeTextClass\"\n data-gr-form-file-item-remove\n :disabled=\"isDisabled\"\n :aria-label=\"t('gr.formFile.removeFile', 'Remove {fileName}', { fileName: file.name })\"\n @click.prevent=\"removeAt(index)\"\n >\n {{ resolvedRemoveText }}\n </button>\n </div>\n </div>\n\n <slot name=\"error\" :errors=\"displayedErrors\">\n <div\n v-if=\"hasLocalErrors\"\n :id=\"errorsId\"\n class=\"text-[var(--gr-danger-text)]\"\n :class=\"textClass\"\n data-gr-form-file-errors\n role=\"alert\"\n >\n <div v-for=\"(e, i) in displayedErrors\" :key=\"i\" data-gr-form-file-error>\n {{ issueMessage(e) }}\n </div>\n </div>\n </slot>\n </div>\n </div>\n</template>\n\n<style scoped>\n[data-gr-form-file].gr-dropzone--over {\n outline: 2px solid var(--gr-ring);\n outline-offset: 2px;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, nextTick, onBeforeUnmount, ref, useId, 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 { useGrComponentSize } from '../GrConfigProvider/context'\nimport {\n type GrFormFileSize,\n buttonSizes,\n iconOffsets,\n iconSizes,\n previewBaseClass,\n previewSizes,\n removeTextSizes,\n rowGaps,\n stackGaps,\n textSizes,\n} from './grFormFileStyles'\nimport { useGrFormFieldContext } from '../GrFormField/context'\nimport { useGrFormControl } from '../../composables/useGrFormControl'\nimport { useFocusWithin } from '../../composables/internal/useFocusWithin'\nimport { useFilePreviews } from '../../composables/internal/useFilePreviews'\nimport { vDropzone } from '../../directives'\nimport { acceptValidator, FileValidationError, maxCountValidator, resolveFileValidationMessage, runFileValidators } from '../../fileValidation'\nimport type { FileValidationIssue, FileValidator } from '../../fileValidation'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\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 /** Только для чтения: значение видно и уходит в форму, но не редактируется. */\n readonly?: boolean\n /** Визуальное и ARIA-состояние ошибки. */\n invalid?: boolean\n /** Обязательное поле (`aria-required`). */\n required?: boolean\n /** Доступное имя вне `GrFormField`. */\n ariaLabel?: string\n /** W3C `accept` для `<input type=\"file\">` + sugar к `acceptValidator(...)`. */\n accept?: string\n validators?: FileValidator[]\n /** Максимум файлов в наборе. Лишние не обрезаются молча — набор отбивается ошибкой. */\n limit?: number\n uploadText?: string\n changeText?: string\n removeText?: string\n clearAllText?: string\n placeholder?: string\n /** Размер кнопок, иконок и подписей. */\n size?: GrFormFileSize\n /** Миниатюры для картинок в наборе. Файлы других типов остаются строкой. */\n preview?: boolean\n /** Дополнительная (кастомная) валидация на стороне потребителя. */\n validate?: (files: File[]) => GrFormFileError[] | Promise<GrFormFileError[]>\n /**\n * Контролируемый список ошибок: `v-model:errors`. Задан — показывается он, и\n * внутренняя валидация его не перетирает. Сюда же кладутся ошибки, пришедшие\n * с сервера. Не задан — компонент держит свои ошибки сам.\n */\n errors?: GrFormFileError[]\n}\n\nexport interface GrFormFileEmits {\n (e: 'update:modelValue', value: File | File[] | null): void\n (e: 'change', value: File | File[] | null): void\n (e: 'clear'): void\n /** Результат валидации. Канал один: `validation` дублировал эту же нагрузку. */\n (e: 'update:errors', errors: GrFormFileError[]): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n}\n\nconst props = withDefaults(\n defineProps<GrFormFileProps>(),\n {\n multiple: false,\n preview: false,\n disabled: false,\n readonly: false,\n invalid: false,\n required: false,\n ariaLabel: undefined,\n accept: undefined,\n limit: undefined,\n uploadText: undefined,\n changeText: undefined,\n removeText: undefined,\n clearAllText: undefined,\n placeholder: undefined,\n size: undefined,\n validators: undefined,\n validate: undefined,\n errors: undefined,\n },\n)\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrFormFile' })\n\nconst rowClass = computed(() => rowGaps[resolvedSize.value])\nconst stackClass = computed(() => stackGaps[resolvedSize.value])\nconst textClass = computed(() => textSizes[resolvedSize.value])\nconst removeTextClass = computed(() => removeTextSizes[resolvedSize.value])\nconst iconOffsetClass = computed(() => iconOffsets[resolvedSize.value])\nconst buttonSize = computed(() => buttonSizes[resolvedSize.value])\nconst iconSize = computed(() => iconSizes[resolvedSize.value])\nconst previewClass = computed(() => [previewSizes[resolvedSize.value], previewBaseClass])\n\nconst emit = defineEmits<GrFormFileEmits>()\ndefineSlots<{\n /** Собственный вывод ошибок вместо списка по умолчанию. */\n error?: (props: { errors: GrFormFileError[] }) => any\n}>()\n\nconst { t } = useGranularityTranslations()\n\n// Контекст `GrFormField`. Виджет здесь — кнопка выбора файла, а не нативный\n// `<input type=\"file\">`: он `aria-hidden` и вне таб-порядка, id на нём увёл бы\n// `<label for>` в невидимый элемент.\nconst field = useGrFormFieldContext()\nconst fieldId = computed(() => field?.id.value)\nconst {\n disabled: isDisabled,\n invalid: isInvalid,\n required: isRequired,\n readonly: isReadonly,\n // `locked` — «ввод не принимается»: `disabled` или `readonly`. Набор в\n // `readonly` виден и уходит в форму, но поменять его нельзя ни диалогом, ни\n // перетаскиванием, ни удалением.\n locked: isLocked,\n} = useGrFormControl(() => props)\nconst resolvedUploadText = computed(() => props.uploadText ?? t('gr.formFile.upload', 'Upload file'))\nconst resolvedChangeText = computed(() => props.changeText ?? t('gr.formFile.change', 'Change file'))\nconst resolvedRemoveText = computed(() => props.removeText ?? t('gr.formFile.remove', 'Remove'))\nconst resolvedClearAllText = computed(() => props.clearAllText ?? t('gr.formFile.clearAll', 'Clear all'))\nconst resolvedPlaceholder = computed(() => props.placeholder ?? t('gr.formFile.placeholder', 'No files selected'))\n\nconst inputRef = ref<HTMLInputElement | null>(null)\nconst rootEl = ref<HTMLElement | null>(null)\n\n// Внутри зоны фокус ходит между нативным input'ом и кнопками: без границы\n// каждое перемещение давало бы потребителю пару `blur` + `focus`.\nconst { onFocusIn, onFocusOut } = useFocusWithin(rootEl, {\n enter: event => emit('focus', event),\n leave: event => emit('blur', event),\n})\nconst localErrors = ref<GrFormFileError[]>([])\nconst uploadBtnEl = ref<HTMLElement | null>(null)\n\n// Собственные ошибки компонент обязан и объявить (`role=\"alert\"`), и связать с\n// контролом: до этого «уронил файл не того типа» для скринридера выглядело как\n// «ничего не произошло». Ошибка поля из `GrFormField` при этом остаётся —\n// `aria-describedby` держит оба id.\nconst errorsId = useId()\n// Контролируемый список сильнее внутреннего — как `sortKey` у `GrDataTable`.\nconst displayedErrors = computed(() => props.errors ?? localErrors.value)\nconst hasLocalErrors = computed(() => displayedErrors.value.length > 0)\n\n/**\n * `aria-required` и `aria-readonly` роль `button` не поддерживает — axe роняет\n * это как critical `aria-allowed-attr`. Состояния уходят в описание кнопки:\n * иначе они пропали бы для диктора совсем (маркер `*` у `GrFormField`\n * декоративен и скрыт).\n */\nconst stateHintId = useId()\nconst stateHint = computed(() => [\n isRequired.value ? t('gr.form.required', 'This field is required') : undefined,\n isReadonly.value ? t('gr.form.readonly', 'Read only') : undefined,\n].filter(Boolean).join('. ') || undefined)\n\nconst describedByIds = computed(() => {\n return [\n field?.describedById.value,\n hasLocalErrors.value ? errorsId : undefined,\n stateHint.value ? stateHintId : undefined,\n ]\n .filter(Boolean)\n .join(' ') || undefined\n})\n\nconst showsInvalid = computed(() => isInvalid.value || hasLocalErrors.value)\n\nfunction focus(): void {\n uploadBtnEl.value?.focus()\n}\n\nfunction blur(): void {\n uploadBtnEl.value?.blur()\n}\n\ndefineExpose({ focus, blur })\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\nconst { fileKey, previewUrl, revokePreview, revokeAllPreviews } = useFilePreviews({\n enabled: () => props.preview,\n})\n\n/**\n * Отзыв `object URL` привязан к самому набору, а не к местам, где его меняют.\n * Файл уходит из набора десятком путей — кнопка строки, «очистить всё», новый\n * выбор, внешний сброс `v-model`, — и точечные вызовы разъехались бы с первой\n * же новой веткой; blob при этом висел бы в памяти вкладки до перезагрузки.\n */\nwatch(files, (next, prev) => {\n for (const file of prev ?? []) {\n if (!next.includes(file))\n revokePreview(file)\n }\n})\n\nonBeforeUnmount(revokeAllPreviews)\n\n/**\n * Один набор валидаторов на оба пути ввода. Собери его отдельно в `applyFiles`\n * и в `dropzone` — копии разъедутся при первой же правке, и выбор через диалог\n * начнёт вести себя иначе, чем перетаскивание.\n */\nconst effectiveValidators = computed<FileValidator[]>(() => {\n const customValidator: FileValidator | undefined = props.validate\n ? async ({ files }) => (await props.validate?.(files)) ?? []\n : undefined\n\n return [\n acceptValidator(props.accept),\n maxCountValidator(props.limit),\n ...(props.validators ?? []),\n ...(customValidator ? [customValidator] : []),\n ]\n})\n\nfunction clearInputValue() {\n if (!inputRef.value)\n return\n inputRef.value.value = ''\n}\n\nfunction setErrors(next: GrFormFileError[]) {\n localErrors.value = next\n emit('update:errors', next)\n}\n\nfunction clearErrors() {\n if (displayedErrors.value.length === 0)\n return\n setErrors([])\n}\n\nfunction openDialog() {\n if (isLocked.value)\n 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 res = runFileValidators(nextFiles, effectiveValidators.value, {\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 (isLocked.value)\n 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)\n target.value = ''\n await nextTick()\n\n await applyFiles(nextFiles)\n}\n\nfunction clearAll() {\n if (isLocked.value)\n 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 (isLocked.value)\n 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\n/** Размер файла в строке списка — как в `GrFileUpload`: без него список не отвечает «сколько». */\nfunction formatFileSize(file: File): string {\n return `${Math.ceil(file.size / 1024)} KB`\n}\n\nfunction issueMessage(issue: GrFormFileError): string {\n const text = resolveFileValidationMessage(issue, t)\n\n // Префикс с именем файла нужен только тем сообщениям, которые сами его не\n // называют, — то есть валидаторам потребителя. Встроенные передают `fileName`\n // параметром и подставляют его в текст, и приписка давала бы «photo.png:\n // Файл «photo.png» …». Признак — наличие параметра, а не поиск подстроки.\n if (issue.fileName && issue.i18nParams?.fileName === undefined)\n return `${issue.fileName}: ${text}`\n\n return text\n}\n\nconst dropzone = computed(() => {\n return {\n enabled: !isLocked.value,\n multiple: props.multiple,\n validators: effectiveValidators.value,\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: t('gr.formFile.validateError', 'Failed to validate dropped files'),\n },\n ])\n }\n },\n }\n})\n\nwatch(\n () => props.modelValue,\n (value) => {\n if (!inputRef.value)\n 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 ref=\"rootEl\"\n v-dropzone=\"dropzone\"\n data-gr-form-file\n class=\"rounded-[var(--gr-radius-md)]\"\n :class=\"isDisabled ? 'cursor-not-allowed' : ''\"\n @focusin=\"onFocusIn\"\n @focusout=\"onFocusOut\"\n >\n <span v-if=\"stateHint\" :id=\"stateHintId\" class=\"sr-only\">{{ stateHint }}</span>\n\n <input\n ref=\"inputRef\"\n data-gr-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=\"isDisabled\"\n @change=\"onInputChange\"\n >\n\n <div class=\"flex flex-col\" :class=\"stackClass\">\n <div class=\"flex flex-wrap items-center\" :class=\"rowClass\">\n <GrButton\n :id=\"fieldId\"\n ref=\"uploadBtnEl\"\n variant=\"secondary\"\n :size=\"buttonSize\"\n data-gr-form-file-upload-btn\n :aria-describedby=\"describedByIds\"\n :aria-invalid=\"showsInvalid ? 'true' : undefined\"\n :aria-label=\"ariaLabel\"\n :disabled=\"isDisabled\"\n @click.prevent=\"openDialog\"\n >\n <GrIcon :size=\"iconSize\">\n <IconUpload />\n </GrIcon>\n <span :class=\"iconOffsetClass\">{{ hasFiles ? resolvedChangeText : resolvedUploadText }}</span>\n </GrButton>\n\n <GrButton\n v-if=\"hasFiles && !multiple && !isReadonly\"\n variant=\"secondary\"\n :size=\"buttonSize\"\n data-gr-form-file-clear-btn\n :disabled=\"isDisabled\"\n @click.prevent=\"clearAll\"\n >\n <GrIcon :size=\"iconSize\">\n <IconX />\n </GrIcon>\n <span :class=\"iconOffsetClass\">{{ resolvedRemoveText }}</span>\n </GrButton>\n\n <GrButton\n v-if=\"multiple && hasFiles && !isReadonly\"\n variant=\"secondary\"\n :size=\"buttonSize\"\n data-gr-form-file-clear-all-btn\n :disabled=\"isDisabled\"\n @click.prevent=\"clearAll\"\n >\n <GrIcon :size=\"iconSize\">\n <IconX />\n </GrIcon>\n <span :class=\"iconOffsetClass\">{{ resolvedClearAllText }}</span>\n </GrButton>\n\n <img\n v-if=\"!multiple && hasFiles && previewUrl(files[0]!)\"\n data-gr-form-file-preview\n :src=\"previewUrl(files[0]!)\"\n alt=\"\"\n :class=\"previewClass\"\n >\n\n <span\n v-if=\"!multiple && hasFiles\"\n class=\"text-[var(--gr-muted-fg)] truncate max-w-[240px]\"\n :class=\"textClass\"\n data-gr-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-[var(--gr-muted-fg)]\"\n :class=\"textClass\"\n data-gr-form-file-placeholder\n >\n {{ resolvedPlaceholder }}\n </span>\n </div>\n\n <div v-if=\"multiple && hasFiles\" class=\"flex flex-col\" :class=\"stackClass\">\n <div\n v-for=\"(file, index) in files\"\n :key=\"fileKey(file)\"\n class=\"flex items-center gap-2\"\n data-gr-form-file-item\n >\n <img\n v-if=\"previewUrl(file)\"\n data-gr-form-file-preview\n :src=\"previewUrl(file)\"\n alt=\"\"\n :class=\"previewClass\"\n >\n\n <span\n class=\"text-[var(--gr-muted-fg)] truncate max-w-[240px]\"\n :class=\"textClass\"\n :title=\"file.name\"\n data-gr-form-file-item-name\n >\n {{ file.name }}\n </span>\n\n <span\n class=\"text-[var(--gr-muted-fg)] shrink-0\"\n :class=\"removeTextClass\"\n data-gr-form-file-item-size\n >{{ formatFileSize(file) }}</span>\n\n <button\n v-if=\"!isReadonly\"\n type=\"button\"\n class=\"text-[var(--gr-muted-fg)] hover:text-[var(--gr-fg)]\"\n :class=\"removeTextClass\"\n data-gr-form-file-item-remove\n :disabled=\"isDisabled\"\n :aria-label=\"t('gr.formFile.removeFile', 'Remove {fileName}', { fileName: file.name })\"\n @click.prevent=\"removeAt(index)\"\n >\n {{ resolvedRemoveText }}\n </button>\n </div>\n </div>\n\n <slot name=\"error\" :errors=\"displayedErrors\">\n <div\n v-if=\"hasLocalErrors\"\n :id=\"errorsId\"\n class=\"text-[var(--gr-danger-text)]\"\n :class=\"textClass\"\n data-gr-form-file-errors\n role=\"alert\"\n >\n <div v-for=\"(e, i) in displayedErrors\" :key=\"i\" data-gr-form-file-error>\n {{ issueMessage(e) }}\n </div>\n </div>\n </slot>\n </div>\n </div>\n</template>\n\n<style scoped>\n[data-gr-form-file].gr-dropzone--over {\n outline: 2px solid var(--gr-ring);\n outline-offset: 2px;\n}\n</style>\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"GrInput-DV4gMWPR.js","names":["$slots","$attrs"],"sources":["../../../../src/components/GrInput/GrInput.vue","../../../../src/components/GrInput/GrInput.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { GrComponentSize } from '../shared/sizes'\nimport {\n controlSignalState,\n controlStateFallbackText,\n controlStateIconClass,\n controlStateIconColors,\n controlStateTextKey,\n} from '../shared/controlState'\nimport { computed, ref, useId } from 'vue'\n\nimport { useControlAddons } from '../../composables/internal/useControlAddons'\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\nimport { useGrFormFieldContext } from '../GrFormField/context'\nimport { useGrFormControl } from '../../composables/useGrFormControl'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport type { InputHTMLAttributes } from 'vue'\n\nimport { addonInlinePrefixClass, addonInlineSuffixClass, addonSegmentPrefixClass, addonSegmentSuffixClass, grInputFieldClass, grInputShellClass, paddingX } from './grInputStyles'\n\nimport IconLoader from '~icons/lucide/loader-2'\nimport IconX from '~icons/lucide/x'\nimport IconCheckCircle from '~icons/lucide/check-circle'\nimport IconAlertTriangle from '~icons/lucide/alert-triangle'\nimport IconEye from '~icons/lucide/eye'\nimport IconEyeOff from '~icons/lucide/eye-off'\n\nexport interface GrInputProps {\n /**\n * Значение поля. Необязательное: без `v-model` поле рисуется пустым.\n * Дефолт — пустая строка, а не `undefined`: длина значения читается\n * напрямую (`showClear`), и `undefined` уронил бы рендер.\n */\n modelValue?: string\n type?: 'text' | 'email' | 'password' | 'number' | 'search' | 'tel' | 'url'\n placeholder?: string\n autocomplete?: string\n inputmode?: InputHTMLAttributes['inputmode']\n disabled?: boolean\n /** Только для чтения: значение видно и выделяемо, но не редактируется. */\n readonly?: boolean\n invalid?: boolean\n /** Обязательное поле (`aria-required`). Складывается с `required` у `GrFormField`. */\n required?: boolean\n /** Доступное имя вне `GrFormField`. */\n ariaLabel?: string\n state?: 'default' | 'success' | 'warning' | 'danger'\n name?: string\n id?: string\n size?: GrInputSize\n\n /** Показывать кнопку очистки, когда есть значение (и не disabled/readonly). */\n clearable?: boolean\n /** i18n aria-label кнопки очистки. */\n clearLabel?: string\n /** Ограничение длины + основа для счётчика символов. */\n maxlength?: number\n /** Показывать счётчик символов (`len` или `len/maxlength`). */\n showCount?: boolean\n /**\n * Фоновая работа по полю (проверка занятости логина, автосохранение):\n * спиннер в trailing-области + `aria-busy`. Ввод не блокируется —\n * для этого есть `disabled`/`readonly`.\n */\n loading?: boolean\n /** Кнопка показать/скрыть пароль (только при `type=\"password\"`). */\n passwordToggle?: boolean\n /** i18n aria-label кнопки показать/скрыть пароль. */\n passwordShowLabel?: string\n passwordHideLabel?: string\n\n textAlign?: GrInputTextAlign\n\n prefixMinWidth?: string\n prefixMaxWidth?: string\n suffixMinWidth?: string\n suffixMaxWidth?: string\n /**\n * Фиксированная ширина у prefix/suffix: аддон получает жёсткую ширину\n * (из `*MaxWidth` → `*MinWidth` → дефолт), а контент обрезается по краю\n * (prefix — справа, suffix — слева). По умолчанию аддоны «растягиваются»\n * под контент (в пределах min/max), а излишек клипается оболочкой.\n */\n prefixFixed?: boolean\n suffixFixed?: boolean\n /**\n * Как выглядят аддоны `#prefix`/`#suffix`.\n *\n * `segment` (по умолчанию) — отдельный отсек, отрезанный рамкой и выровненный\n * по ступени размера: так поле с «₽» и поле с «USD» стоят в колонку.\n * `inline` — украшение внутри рамки: ни разделителя, ни своей ширины.\n *\n * Разница не косметическая. Поисковая строка с лупой в сегменте читается\n * составным элементом — полем с приклеенной кнопкой, — а не одним полем;\n * именно поэтому иконку внутри рамки нельзя было выразить аддоном, и\n * потребители отказывались от неё вовсе.\n */\n addon?: 'segment' | 'inline'\n}\n\nexport interface GrInputEmits {\n (e: 'update:modelValue', value: string): void\n /** Значение зафиксировано нативным `change` — по `blur` или `Enter`. */\n (e: 'change', value: string): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n /**\n * Значение стёрто кнопкой очистки. Отдельное событие потому, что по\n * `update:modelValue` программную очистку от ручного стирания не отличить.\n */\n (e: 'clear'): void\n}\n\ndefineOptions({\n inheritAttrs: false,\n})\n\nexport type GrInputSize = GrComponentSize\nexport type GrInputTextAlign = 'left' | 'center' | 'right'\n\nconst props = withDefaults(\n defineProps<GrInputProps>(),\n {\n modelValue: '',\n type: 'text',\n placeholder: undefined,\n autocomplete: undefined,\n inputmode: undefined,\n disabled: false,\n readonly: false,\n invalid: false,\n required: false,\n ariaLabel: undefined,\n state: 'default',\n name: undefined,\n id: undefined,\n size: undefined,\n\n // Настраивается через `GrConfigProvider`; дефолт — в резолвере ниже.\n clearable: undefined,\n clearLabel: undefined,\n maxlength: undefined,\n showCount: false,\n loading: false,\n passwordToggle: false,\n passwordShowLabel: undefined,\n passwordHideLabel: undefined,\n\n textAlign: 'left',\n\n prefixMinWidth: undefined,\n prefixMaxWidth: undefined,\n suffixMinWidth: undefined,\n suffixMaxWidth: undefined,\n prefixFixed: false,\n suffixFixed: false,\n addon: 'segment',\n },\n)\n\nconst emit = defineEmits<GrInputEmits>()\ndefineSlots<{\n /** Аддон слева от поля: иконка, код валюты, метка. */\n prefix?: () => any\n /** Аддон справа от поля: единица измерения, подсказка. */\n suffix?: () => any\n}>()\n\n// Контекст `GrFormField` (если инпут внутри него): даёт id/aria-describedby/\n// invalid/required как fallback, чтобы не прокидывать `forId` вручную.\nconst field = useGrFormFieldContext()\n\n// Эффективные значения: локальный проп → `GrConfigProvider` → дефолт компонента.\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrInput' })\nconst resolvedClearable = useGrComponentProp('GrInput', 'clearable', () => props.clearable, false)\n\nconst resolvedId = computed(() => props.id ?? field?.id.value)\n\n// Счётчик обязан быть частью описания поля: иначе «12 / 60» видно глазами, но\n// не слышно — при том, что ограничение длины и есть смысл счётчика.\nconst countId = useId()\nconst {\n disabled: isDisabled,\n invalid: isInvalid,\n required: isRequired,\n readonly: isReadonly,\n} = useGrFormControl(() => props)\n\nconst inputEl = ref<HTMLInputElement | null>(null)\n\nfunction focus(): void {\n inputEl.value?.focus()\n}\n\nfunction blur(): void {\n inputEl.value?.blur()\n}\n\n/** Выделить содержимое — спутник `focus()` для «подставили значение, перезапишите». */\nfunction select(): void {\n inputEl.value?.select()\n}\n\ndefineExpose({\n focus,\n blur,\n select,\n})\n\nconst ADDON_MIN_WIDTH_BY_SIZE: Record<GrInputSize, string> = {\n xs: '2rem', // w-8\n sm: '2.25rem', // w-9\n md: '2.5rem', // w-10\n lg: '3rem', // w-12\n}\n\nconst passwordVisible = ref(false)\n// Тип поля с учётом переключателя пароля.\nconst resolvedType = computed(() => (props.type === 'password' && passwordVisible.value ? 'text' : props.type))\n\nconst showPasswordToggle = computed(() => props.passwordToggle && props.type === 'password' && !isDisabled.value)\nconst showClear = computed(() => resolvedClearable.value && props.modelValue.length > 0 && !isDisabled.value && !isReadonly.value)\n\n/**\n * Небуквенный признак состояния: иконка для глаз плюс скрытая подпись для\n * скринридера. Смысл `success`/`warning` иначе несёт один цвет рамки — WCAG\n * 1.4.1. Подробности и почему `danger` его не получает — `shared/controlState`.\n */\nconst signalState = computed(() => controlSignalState(props.state, isInvalid.value))\nconst stateIcon = computed(() => (signalState.value === 'success' ? IconCheckCircle : IconAlertTriangle))\nconst stateIconClass = computed(() => (signalState.value\n ? `${controlStateIconClass} ${controlStateIconColors[signalState.value]}`\n : ''))\n\nconst stateTextId = useId()\nconst describedBy = computed(() =>\n [field?.describedById.value, props.showCount ? countId : undefined, signalState.value ? stateTextId : undefined]\n .filter(Boolean)\n .join(' ') || undefined,\n)\n\nconst trailingCount = computed(() => (showClear.value ? 1 : 0) + (showPasswordToggle.value ? 1 : 0) + (props.loading ? 1 : 0) + (signalState.value ? 1 : 0))\nconst trailingReserve = computed(() => (trailingCount.value > 0 ? `${trailingCount.value * 28}px` : '0px'))\n\nconst isInlineAddon = computed(() => props.addon === 'inline')\n\nconst prefixAddonClass = computed(() => (isInlineAddon.value ? addonInlinePrefixClass : addonSegmentPrefixClass))\nconst suffixAddonClass = computed(() => (isInlineAddon.value ? addonInlineSuffixClass : addonSegmentSuffixClass))\n\nconst {\n prefixEl,\n suffixEl,\n prefixStyle,\n suffixStyle,\n fieldPadding: inputStyle,\n} = useControlAddons(() => props, {\n // У украшения своей ширины нет — иначе иконка висела бы в пустом отсеке\n // шириной со ступень размера.\n defaultMinWidth: () => (isInlineAddon.value ? '0px' : ADDON_MIN_WIDTH_BY_SIZE[resolvedSize.value]),\n paddingX: () => paddingX[resolvedSize.value],\n trailingReserve: () => trailingReserve.value,\n})\n\n// Border/ring/disabled — на оболочке (`focus-within`), размеры/выравнивание — на инпуте.\nconst shellClass = computed(() => grInputShellClass({\n state: props.state,\n invalid: isInvalid.value,\n disabled: isDisabled.value,\n}))\n\nconst className = computed(() => grInputFieldClass({\n size: resolvedSize.value,\n align: props.textAlign,\n}))\n\nfunction onInput(e: Event): void {\n emit('update:modelValue', (e.target as HTMLInputElement).value)\n}\n\n// Объявленный emit уходит из `$attrs`, поэтому нативные события переизлучаем\n// руками — иначе `@change`/`@focus`/`@blur` у потребителя перестали бы работать.\nfunction onChange(e: Event): void {\n emit('change', (e.target as HTMLInputElement).value)\n}\n\nfunction onFocus(e: FocusEvent): void {\n emit('focus', e)\n}\n\nfunction onBlur(e: FocusEvent): void {\n emit('blur', e)\n}\n\n// ————— Trailing-контролы: очистка, переключатель пароля, счётчик символов.\nconst { t } = useGranularityTranslations()\n\nconst stateText = computed(() => (signalState.value\n ? t(controlStateTextKey[signalState.value], controlStateFallbackText[signalState.value])\n : ''))\nconst resolvedClearLabel = computed(() => props.clearLabel ?? t('gr.input.clear', 'Clear'))\nconst resolvedPasswordShowLabel = computed(() => props.passwordShowLabel ?? t('gr.input.showPassword', 'Show password'))\nconst resolvedPasswordHideLabel = computed(() => props.passwordHideLabel ?? t('gr.input.hidePassword', 'Hide password'))\n\n// Счётчик символов: `len` или `len / maxlength`.\nconst countText = computed(() =>\n props.maxlength !== undefined ? `${props.modelValue.length} / ${props.maxlength}` : String(props.modelValue.length),\n)\n\n/**\n * Живой регион объявляет только исчерпание лимита. Читать вслух каждый символ\n * нельзя — диктор захлебнётся, а сам счётчик и так связан с полем через\n * `aria-describedby` и читается при фокусе.\n */\nconst limitReached = computed(() =>\n props.maxlength !== undefined && props.modelValue.length >= props.maxlength,\n)\nconst liveMessage = computed(() =>\n limitReached.value ? t('gr.input.limitReached', 'Character limit reached') : '',\n)\n\nfunction clear(): void {\n emit('update:modelValue', '')\n // Очистка — такая же фиксация значения, как уход фокуса: без `change`\n // подписка «значение установилось» пропускала бы ровно её.\n emit('change', '')\n emit('clear')\n inputEl.value?.focus()\n}\n\nfunction togglePassword(): void {\n passwordVisible.value = !passwordVisible.value\n inputEl.value?.focus()\n}\n</script>\n\n<template>\n <div data-gr-input class=\"w-full\">\n <div :class=\"shellClass\">\n <div\n v-if=\"$slots.prefix\"\n ref=\"prefixEl\"\n data-testid=\"gr-input-prefix\"\n class=\"absolute inset-y-0 left-0 flex items-center justify-center text-[var(--gr-muted-fg)] pointer-events-none select-none truncate\"\n :class=\"prefixAddonClass\"\n :style=\"prefixStyle\"\n aria-hidden=\"true\"\n >\n <slot name=\"prefix\" />\n </div>\n\n <input\n :id=\"resolvedId\"\n ref=\"inputEl\"\n v-bind=\"$attrs\"\n :name=\"props.name\"\n :type=\"resolvedType\"\n :inputmode=\"props.inputmode\"\n :autocomplete=\"props.autocomplete\"\n :placeholder=\"props.placeholder\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :maxlength=\"props.maxlength\"\n :value=\"props.modelValue\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n :aria-describedby=\"describedBy\"\n :aria-required=\"isRequired ? 'true' : undefined\"\n :aria-readonly=\"isReadonly ? 'true' : undefined\"\n :aria-label=\"ariaLabel\"\n :aria-busy=\"props.loading ? 'true' : undefined\"\n class=\"w-full bg-transparent text-[var(--gr-fg)] placeholder:text-[var(--gr-muted-fg)] focus:placeholder:text-transparent focus:outline-none disabled:cursor-not-allowed disabled:text-[var(--gr-muted-fg)]\"\n :class=\"className\"\n :style=\"inputStyle\"\n @input=\"onInput\"\n @change=\"onChange\"\n @focus=\"onFocus\"\n @blur=\"onBlur\"\n >\n\n <div\n v-if=\"trailingCount > 0\"\n data-gr-input-trailing\n class=\"absolute inset-y-0 right-1 flex items-center gap-0.5\"\n >\n <span\n v-if=\"signalState\"\n data-gr-input-state\n :class=\"stateIconClass\"\n aria-hidden=\"true\"\n >\n <component :is=\"stateIcon\" class=\"h-4 w-4\" />\n </span>\n\n <span\n v-if=\"loading\"\n data-gr-input-spinner\n class=\"flex h-6 w-6 items-center justify-center text-[var(--gr-muted-fg)]\"\n aria-hidden=\"true\"\n >\n <IconLoader class=\"h-4 w-4 animate-spin\" />\n </span>\n\n <button\n v-if=\"showClear\"\n type=\"button\"\n data-gr-input-clear\n :aria-label=\"resolvedClearLabel\"\n class=\"flex h-6 w-6 items-center justify-center rounded-[var(--gr-radius-sm)] text-[var(--gr-muted-fg)] transition-colors hover:bg-[var(--gr-muted)] hover:text-[var(--gr-fg)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--gr-ring)]\"\n @click=\"clear\"\n >\n <IconX class=\"h-4 w-4\" aria-hidden=\"true\" />\n </button>\n\n <button\n v-if=\"showPasswordToggle\"\n type=\"button\"\n data-gr-input-password-toggle\n :aria-label=\"passwordVisible ? resolvedPasswordHideLabel : resolvedPasswordShowLabel\"\n :aria-pressed=\"passwordVisible ? 'true' : 'false'\"\n class=\"flex h-6 w-6 items-center justify-center rounded-[var(--gr-radius-sm)] text-[var(--gr-muted-fg)] transition-colors hover:bg-[var(--gr-muted)] hover:text-[var(--gr-fg)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--gr-ring)]\"\n @click=\"togglePassword\"\n >\n <IconEyeOff v-if=\"passwordVisible\" class=\"h-4 w-4\" aria-hidden=\"true\" />\n <IconEye v-else class=\"h-4 w-4\" aria-hidden=\"true\" />\n </button>\n </div>\n\n <div\n v-if=\"$slots.suffix\"\n ref=\"suffixEl\"\n data-testid=\"gr-input-suffix\"\n class=\"absolute inset-y-0 right-0 flex items-center justify-center text-[var(--gr-muted-fg)] pointer-events-none select-none truncate\"\n :class=\"[suffixAddonClass, suffixFixed ? '[direction:rtl]' : '']\"\n :style=\"suffixStyle\"\n aria-hidden=\"true\"\n >\n <slot name=\"suffix\" />\n </div>\n </div>\n\n <span v-if=\"signalState\" :id=\"stateTextId\" data-gr-input-state-text class=\"sr-only\">{{ stateText }}</span>\n\n <div\n v-if=\"showCount\"\n :id=\"countId\"\n data-gr-input-count\n class=\"mt-1 text-right text-[length:var(--gr-control-text-xs)] leading-[var(--gr-leading-xs)] text-[var(--gr-muted-fg)] tabular-nums\"\n >\n {{ countText }}\n </div>\n\n <span\n v-if=\"showCount\"\n data-gr-input-live\n class=\"sr-only\"\n role=\"status\"\n aria-live=\"polite\"\n >{{ liveMessage }}</span>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport type { GrComponentSize } from '../shared/sizes'\nimport {\n controlSignalState,\n controlStateFallbackText,\n controlStateIconClass,\n controlStateIconColors,\n controlStateTextKey,\n} from '../shared/controlState'\nimport { computed, ref, useId } from 'vue'\n\nimport { useControlAddons } from '../../composables/internal/useControlAddons'\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\nimport { useGrFormFieldContext } from '../GrFormField/context'\nimport { useGrFormControl } from '../../composables/useGrFormControl'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport type { InputHTMLAttributes } from 'vue'\n\nimport { addonInlinePrefixClass, addonInlineSuffixClass, addonSegmentPrefixClass, addonSegmentSuffixClass, grInputFieldClass, grInputShellClass, paddingX } from './grInputStyles'\n\nimport IconLoader from '~icons/lucide/loader-2'\nimport IconX from '~icons/lucide/x'\nimport IconCheckCircle from '~icons/lucide/check-circle'\nimport IconAlertTriangle from '~icons/lucide/alert-triangle'\nimport IconEye from '~icons/lucide/eye'\nimport IconEyeOff from '~icons/lucide/eye-off'\n\nexport interface GrInputProps {\n /**\n * Значение поля. Необязательное: без `v-model` поле рисуется пустым.\n * Дефолт — пустая строка, а не `undefined`: длина значения читается\n * напрямую (`showClear`), и `undefined` уронил бы рендер.\n */\n modelValue?: string\n type?: 'text' | 'email' | 'password' | 'number' | 'search' | 'tel' | 'url'\n placeholder?: string\n autocomplete?: string\n inputmode?: InputHTMLAttributes['inputmode']\n disabled?: boolean\n /** Только для чтения: значение видно и выделяемо, но не редактируется. */\n readonly?: boolean\n invalid?: boolean\n /** Обязательное поле (`aria-required`). Складывается с `required` у `GrFormField`. */\n required?: boolean\n /** Доступное имя вне `GrFormField`. */\n ariaLabel?: string\n state?: 'default' | 'success' | 'warning' | 'danger'\n name?: string\n id?: string\n size?: GrInputSize\n\n /** Показывать кнопку очистки, когда есть значение (и не disabled/readonly). */\n clearable?: boolean\n /** i18n aria-label кнопки очистки. */\n clearLabel?: string\n /** Ограничение длины + основа для счётчика символов. */\n maxlength?: number\n /** Показывать счётчик символов (`len` или `len/maxlength`). */\n showCount?: boolean\n /**\n * Фоновая работа по полю (проверка занятости логина, автосохранение):\n * спиннер в trailing-области + `aria-busy`. Ввод не блокируется —\n * для этого есть `disabled`/`readonly`.\n */\n loading?: boolean\n /** Кнопка показать/скрыть пароль (только при `type=\"password\"`). */\n passwordToggle?: boolean\n /** i18n aria-label кнопки показать/скрыть пароль. */\n passwordShowLabel?: string\n passwordHideLabel?: string\n\n textAlign?: GrInputTextAlign\n\n prefixMinWidth?: string\n prefixMaxWidth?: string\n suffixMinWidth?: string\n suffixMaxWidth?: string\n /**\n * Фиксированная ширина у prefix/suffix: аддон получает жёсткую ширину\n * (из `*MaxWidth` → `*MinWidth` → дефолт), а контент обрезается по краю\n * (prefix — справа, suffix — слева). По умолчанию аддоны «растягиваются»\n * под контент (в пределах min/max), а излишек клипается оболочкой.\n */\n prefixFixed?: boolean\n suffixFixed?: boolean\n /**\n * Как выглядят аддоны `#prefix`/`#suffix`.\n *\n * `segment` (по умолчанию) — отдельный отсек, отрезанный рамкой и выровненный\n * по ступени размера: так поле с «₽» и поле с «USD» стоят в колонку.\n * `inline` — украшение внутри рамки: ни разделителя, ни своей ширины.\n *\n * Разница не косметическая. Поисковая строка с лупой в сегменте читается\n * составным элементом — полем с приклеенной кнопкой, — а не одним полем;\n * именно поэтому иконку внутри рамки нельзя было выразить аддоном, и\n * потребители отказывались от неё вовсе.\n */\n addon?: 'segment' | 'inline'\n}\n\nexport interface GrInputEmits {\n (e: 'update:modelValue', value: string): void\n /** Значение зафиксировано нативным `change` — по `blur` или `Enter`. */\n (e: 'change', value: string): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n /**\n * Значение стёрто кнопкой очистки. Отдельное событие потому, что по\n * `update:modelValue` программную очистку от ручного стирания не отличить.\n */\n (e: 'clear'): void\n}\n\ndefineOptions({\n inheritAttrs: false,\n})\n\nexport type GrInputSize = GrComponentSize\nexport type GrInputTextAlign = 'left' | 'center' | 'right'\n\nconst props = withDefaults(\n defineProps<GrInputProps>(),\n {\n modelValue: '',\n type: 'text',\n placeholder: undefined,\n autocomplete: undefined,\n inputmode: undefined,\n disabled: false,\n readonly: false,\n invalid: false,\n required: false,\n ariaLabel: undefined,\n state: 'default',\n name: undefined,\n id: undefined,\n size: undefined,\n\n // Настраивается через `GrConfigProvider`; дефолт — в резолвере ниже.\n clearable: undefined,\n clearLabel: undefined,\n maxlength: undefined,\n showCount: false,\n loading: false,\n passwordToggle: false,\n passwordShowLabel: undefined,\n passwordHideLabel: undefined,\n\n textAlign: 'left',\n\n prefixMinWidth: undefined,\n prefixMaxWidth: undefined,\n suffixMinWidth: undefined,\n suffixMaxWidth: undefined,\n prefixFixed: false,\n suffixFixed: false,\n addon: 'segment',\n },\n)\n\nconst emit = defineEmits<GrInputEmits>()\ndefineSlots<{\n /** Аддон слева от поля: иконка, код валюты, метка. */\n prefix?: () => any\n /** Аддон справа от поля: единица измерения, подсказка. */\n suffix?: () => any\n}>()\n\n// Контекст `GrFormField` (если инпут внутри него): даёт id/aria-describedby/\n// invalid/required как fallback, чтобы не прокидывать `forId` вручную.\nconst field = useGrFormFieldContext()\n\n// Эффективные значения: локальный проп → `GrConfigProvider` → дефолт компонента.\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrInput' })\nconst resolvedClearable = useGrComponentProp('GrInput', 'clearable', () => props.clearable, false)\n\nconst resolvedId = computed(() => props.id ?? field?.id.value)\n\n// Счётчик обязан быть частью описания поля: иначе «12 / 60» видно глазами, но\n// не слышно — при том, что ограничение длины и есть смысл счётчика.\nconst countId = useId()\nconst {\n disabled: isDisabled,\n invalid: isInvalid,\n required: isRequired,\n readonly: isReadonly,\n} = useGrFormControl(() => props)\n\nconst inputEl = ref<HTMLInputElement | null>(null)\n\nfunction focus(): void {\n inputEl.value?.focus()\n}\n\nfunction blur(): void {\n inputEl.value?.blur()\n}\n\n/** Выделить содержимое — спутник `focus()` для «подставили значение, перезапишите». */\nfunction select(): void {\n inputEl.value?.select()\n}\n\ndefineExpose({\n focus,\n blur,\n select,\n})\n\nconst ADDON_MIN_WIDTH_BY_SIZE: Record<GrInputSize, string> = {\n xs: '2rem', // w-8\n sm: '2.25rem', // w-9\n md: '2.5rem', // w-10\n lg: '3rem', // w-12\n}\n\nconst passwordVisible = ref(false)\n// Тип поля с учётом переключателя пароля.\nconst resolvedType = computed(() => (props.type === 'password' && passwordVisible.value ? 'text' : props.type))\n\nconst showPasswordToggle = computed(() => props.passwordToggle && props.type === 'password' && !isDisabled.value)\nconst showClear = computed(() => resolvedClearable.value && props.modelValue.length > 0 && !isDisabled.value && !isReadonly.value)\n\n/**\n * Небуквенный признак состояния: иконка для глаз плюс скрытая подпись для\n * скринридера. Смысл `success`/`warning` иначе несёт один цвет рамки — WCAG\n * 1.4.1. Подробности и почему `danger` его не получает — `shared/controlState`.\n */\nconst signalState = computed(() => controlSignalState(props.state, isInvalid.value))\nconst stateIcon = computed(() => (signalState.value === 'success' ? IconCheckCircle : IconAlertTriangle))\nconst stateIconClass = computed(() => (signalState.value\n ? `${controlStateIconClass} ${controlStateIconColors[signalState.value]}`\n : ''))\n\nconst stateTextId = useId()\nconst describedBy = computed(() =>\n [field?.describedById.value, props.showCount ? countId : undefined, signalState.value ? stateTextId : undefined]\n .filter(Boolean)\n .join(' ') || undefined,\n)\n\nconst trailingCount = computed(() => (showClear.value ? 1 : 0) + (showPasswordToggle.value ? 1 : 0) + (props.loading ? 1 : 0) + (signalState.value ? 1 : 0))\nconst trailingReserve = computed(() => (trailingCount.value > 0 ? `${trailingCount.value * 28}px` : '0px'))\n\nconst isInlineAddon = computed(() => props.addon === 'inline')\n\nconst prefixAddonClass = computed(() => (isInlineAddon.value ? addonInlinePrefixClass : addonSegmentPrefixClass))\nconst suffixAddonClass = computed(() => (isInlineAddon.value ? addonInlineSuffixClass : addonSegmentSuffixClass))\n\nconst {\n prefixEl,\n suffixEl,\n prefixStyle,\n suffixStyle,\n fieldPadding: inputStyle,\n} = useControlAddons(() => props, {\n // У украшения своей ширины нет — иначе иконка висела бы в пустом отсеке\n // шириной со ступень размера.\n defaultMinWidth: () => (isInlineAddon.value ? '0px' : ADDON_MIN_WIDTH_BY_SIZE[resolvedSize.value]),\n paddingX: () => paddingX[resolvedSize.value],\n trailingReserve: () => trailingReserve.value,\n})\n\n// Border/ring/disabled — на оболочке (`focus-within`), размеры/выравнивание — на инпуте.\nconst shellClass = computed(() => grInputShellClass({\n state: props.state,\n invalid: isInvalid.value,\n disabled: isDisabled.value,\n}))\n\nconst className = computed(() => grInputFieldClass({\n size: resolvedSize.value,\n align: props.textAlign,\n}))\n\nfunction onInput(e: Event): void {\n emit('update:modelValue', (e.target as HTMLInputElement).value)\n}\n\n// Объявленный emit уходит из `$attrs`, поэтому нативные события переизлучаем\n// руками — иначе `@change`/`@focus`/`@blur` у потребителя перестали бы работать.\nfunction onChange(e: Event): void {\n emit('change', (e.target as HTMLInputElement).value)\n}\n\nfunction onFocus(e: FocusEvent): void {\n emit('focus', e)\n}\n\nfunction onBlur(e: FocusEvent): void {\n emit('blur', e)\n}\n\n// ————— Trailing-контролы: очистка, переключатель пароля, счётчик символов.\nconst { t } = useGranularityTranslations()\n\nconst stateText = computed(() => (signalState.value\n ? t(controlStateTextKey[signalState.value], controlStateFallbackText[signalState.value])\n : ''))\nconst resolvedClearLabel = computed(() => props.clearLabel ?? t('gr.input.clear', 'Clear'))\nconst resolvedPasswordShowLabel = computed(() => props.passwordShowLabel ?? t('gr.input.showPassword', 'Show password'))\nconst resolvedPasswordHideLabel = computed(() => props.passwordHideLabel ?? t('gr.input.hidePassword', 'Hide password'))\n\n// Счётчик символов: `len` или `len / maxlength`.\nconst countText = computed(() =>\n props.maxlength !== undefined ? `${props.modelValue.length} / ${props.maxlength}` : String(props.modelValue.length),\n)\n\n/**\n * Живой регион объявляет только исчерпание лимита. Читать вслух каждый символ\n * нельзя — диктор захлебнётся, а сам счётчик и так связан с полем через\n * `aria-describedby` и читается при фокусе.\n */\nconst limitReached = computed(() =>\n props.maxlength !== undefined && props.modelValue.length >= props.maxlength,\n)\nconst liveMessage = computed(() =>\n limitReached.value ? t('gr.input.limitReached', 'Character limit reached') : '',\n)\n\nfunction clear(): void {\n emit('update:modelValue', '')\n // Очистка — такая же фиксация значения, как уход фокуса: без `change`\n // подписка «значение установилось» пропускала бы ровно её.\n emit('change', '')\n emit('clear')\n inputEl.value?.focus()\n}\n\nfunction togglePassword(): void {\n passwordVisible.value = !passwordVisible.value\n inputEl.value?.focus()\n}\n</script>\n\n<template>\n <div data-gr-input class=\"w-full\">\n <div :class=\"shellClass\">\n <div\n v-if=\"$slots.prefix\"\n ref=\"prefixEl\"\n data-testid=\"gr-input-prefix\"\n class=\"absolute inset-y-0 left-0 flex items-center justify-center text-[var(--gr-muted-fg)] pointer-events-none select-none truncate\"\n :class=\"prefixAddonClass\"\n :style=\"prefixStyle\"\n aria-hidden=\"true\"\n >\n <slot name=\"prefix\" />\n </div>\n\n <input\n :id=\"resolvedId\"\n ref=\"inputEl\"\n v-bind=\"$attrs\"\n :name=\"props.name\"\n :type=\"resolvedType\"\n :inputmode=\"props.inputmode\"\n :autocomplete=\"props.autocomplete\"\n :placeholder=\"props.placeholder\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :maxlength=\"props.maxlength\"\n :value=\"props.modelValue\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n :aria-describedby=\"describedBy\"\n :aria-required=\"isRequired ? 'true' : undefined\"\n :aria-readonly=\"isReadonly ? 'true' : undefined\"\n :aria-label=\"ariaLabel\"\n :aria-busy=\"props.loading ? 'true' : undefined\"\n class=\"w-full bg-transparent text-[var(--gr-fg)] placeholder:text-[var(--gr-muted-fg)] focus:placeholder:text-transparent focus:outline-none disabled:cursor-not-allowed disabled:text-[var(--gr-muted-fg)]\"\n :class=\"className\"\n :style=\"inputStyle\"\n @input=\"onInput\"\n @change=\"onChange\"\n @focus=\"onFocus\"\n @blur=\"onBlur\"\n >\n\n <div\n v-if=\"trailingCount > 0\"\n data-gr-input-trailing\n class=\"absolute inset-y-0 right-1 flex items-center gap-0.5\"\n >\n <span\n v-if=\"signalState\"\n data-gr-input-state\n :class=\"stateIconClass\"\n aria-hidden=\"true\"\n >\n <component :is=\"stateIcon\" class=\"h-4 w-4\" />\n </span>\n\n <span\n v-if=\"loading\"\n data-gr-input-spinner\n class=\"flex h-6 w-6 items-center justify-center text-[var(--gr-muted-fg)]\"\n aria-hidden=\"true\"\n >\n <IconLoader class=\"h-4 w-4 animate-spin\" />\n </span>\n\n <button\n v-if=\"showClear\"\n type=\"button\"\n data-gr-input-clear\n :aria-label=\"resolvedClearLabel\"\n class=\"flex h-6 w-6 items-center justify-center rounded-[var(--gr-radius-sm)] text-[var(--gr-muted-fg)] transition-colors hover:bg-[var(--gr-muted)] hover:text-[var(--gr-fg)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--gr-ring)]\"\n @click=\"clear\"\n >\n <IconX class=\"h-4 w-4\" aria-hidden=\"true\" />\n </button>\n\n <button\n v-if=\"showPasswordToggle\"\n type=\"button\"\n data-gr-input-password-toggle\n :aria-label=\"passwordVisible ? resolvedPasswordHideLabel : resolvedPasswordShowLabel\"\n :aria-pressed=\"passwordVisible ? 'true' : 'false'\"\n class=\"flex h-6 w-6 items-center justify-center rounded-[var(--gr-radius-sm)] text-[var(--gr-muted-fg)] transition-colors hover:bg-[var(--gr-muted)] hover:text-[var(--gr-fg)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--gr-ring)]\"\n @click=\"togglePassword\"\n >\n <IconEyeOff v-if=\"passwordVisible\" class=\"h-4 w-4\" aria-hidden=\"true\" />\n <IconEye v-else class=\"h-4 w-4\" aria-hidden=\"true\" />\n </button>\n </div>\n\n <div\n v-if=\"$slots.suffix\"\n ref=\"suffixEl\"\n data-testid=\"gr-input-suffix\"\n class=\"absolute inset-y-0 right-0 flex items-center justify-center text-[var(--gr-muted-fg)] pointer-events-none select-none truncate\"\n :class=\"[suffixAddonClass, suffixFixed ? '[direction:rtl]' : '']\"\n :style=\"suffixStyle\"\n aria-hidden=\"true\"\n >\n <slot name=\"suffix\" />\n </div>\n </div>\n\n <span v-if=\"signalState\" :id=\"stateTextId\" data-gr-input-state-text class=\"sr-only\">{{ stateText }}</span>\n\n <div\n v-if=\"showCount\"\n :id=\"countId\"\n data-gr-input-count\n class=\"mt-1 text-right text-[length:var(--gr-control-text-xs)] leading-[var(--gr-leading-xs)] text-[var(--gr-muted-fg)] tabular-nums\"\n >\n {{ countText }}\n </div>\n\n <span\n v-if=\"showCount\"\n data-gr-input-live\n class=\"sr-only\"\n role=\"status\"\n aria-live=\"polite\"\n >{{ liveMessage }}</span>\n </div>\n</template>\n"],"mappings":"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"}