@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.
- package/README.md +3 -3
- package/dist/chunks/chevron-up-BhW6hi8L.js +23 -0
- package/dist/chunks/chevron-up-BhW6hi8L.js.map +1 -0
- package/dist/chunks/{config-E1cTyRVV.js → config-8n1VR0ju.js} +1 -1
- package/dist/chunks/{config-E1cTyRVV.js.map → config-8n1VR0ju.js.map} +1 -1
- package/dist/chunks/{config-2vrOFn2f.js → config-AKpLyIGD.js} +2 -2
- package/dist/chunks/{config-2vrOFn2f.js.map → config-AKpLyIGD.js.map} +1 -1
- package/dist/chunks/{config-DIDz_E5m.js → config-B34mkd53.js} +66 -1
- package/dist/chunks/config-B34mkd53.js.map +1 -0
- package/dist/chunks/{config-B5SBUa6u.js → config-B7qZqiUB.js} +28 -1
- package/dist/chunks/config-B7qZqiUB.js.map +1 -0
- package/dist/chunks/config-BBrPUEye.js +8 -0
- package/dist/chunks/config-BBrPUEye.js.map +1 -0
- package/dist/chunks/config-BBtBQE3s.js +108 -0
- package/dist/chunks/config-BBtBQE3s.js.map +1 -0
- package/dist/chunks/{config-CT4lP6S4.js → config-BOCzHqWh.js} +2 -2
- package/dist/chunks/{config-CT4lP6S4.js.map → config-BOCzHqWh.js.map} +1 -1
- package/dist/chunks/{config-iemgzS4x.js → config-BP1ktzwh.js} +2 -24
- package/dist/chunks/config-BP1ktzwh.js.map +1 -0
- package/dist/chunks/config-BTxmdRzT.js +28 -0
- package/dist/chunks/config-BTxmdRzT.js.map +1 -0
- package/dist/chunks/{config-CjWOTgLL.js → config-Bk52xd5l.js} +12 -5
- package/dist/chunks/config-Bk52xd5l.js.map +1 -0
- package/dist/chunks/{config-D3HOuHDe.js → config-C3SqBqSQ.js} +3 -1
- package/dist/chunks/config-C3SqBqSQ.js.map +1 -0
- package/dist/chunks/{config-2ZHRsdOc.js → config-C3uqd-sW.js} +1 -1
- package/dist/chunks/config-C3uqd-sW.js.map +1 -0
- package/dist/chunks/{config-BXRmsn3t.js → config-CPhcZzHi.js} +72 -10
- package/dist/chunks/config-CPhcZzHi.js.map +1 -0
- package/dist/chunks/config-CWtfxkM9.js.map +1 -1
- package/dist/chunks/{config--t2dxj0t.js → config-CmLTCmY9.js} +2 -2
- package/dist/chunks/{config--t2dxj0t.js.map → config-CmLTCmY9.js.map} +1 -1
- package/dist/chunks/{config-BAse0laT.js → config-CvtqB4ki.js} +8 -3
- package/dist/chunks/config-CvtqB4ki.js.map +1 -0
- package/dist/chunks/{config-BcXX7S7B.js → config-D-JgqV0J.js} +5 -1
- package/dist/chunks/{config-BcXX7S7B.js.map → config-D-JgqV0J.js.map} +1 -1
- package/dist/chunks/{config-BHLMTJaR.js → config-D1oVht-E.js} +8 -3
- package/dist/chunks/config-D1oVht-E.js.map +1 -0
- package/dist/chunks/{config-BscVxZR3.js → config-DF1ezU6y.js} +1 -1
- package/dist/chunks/{config-BscVxZR3.js.map → config-DF1ezU6y.js.map} +1 -1
- package/dist/chunks/{config-B42iJ48O.js → config-DG41PD9i.js} +13 -1
- package/dist/chunks/config-DG41PD9i.js.map +1 -0
- package/dist/chunks/{config-Bs1HwUcx.js → config-DPJ3jtiB.js} +12 -2
- package/dist/chunks/config-DPJ3jtiB.js.map +1 -0
- package/dist/chunks/{config-tNqtapcT.js → config-DpqjlCfB.js} +2 -2
- package/dist/chunks/{config-tNqtapcT.js.map → config-DpqjlCfB.js.map} +1 -1
- package/dist/chunks/{config-DNDgp9bB.js → config-DpzFoSIh.js} +64 -2
- package/dist/chunks/config-DpzFoSIh.js.map +1 -0
- package/dist/chunks/config-MUkhktTP.js +267 -0
- package/dist/chunks/config-MUkhktTP.js.map +1 -0
- package/dist/chunks/config-SPugeaHT.js +37 -0
- package/dist/chunks/config-SPugeaHT.js.map +1 -0
- package/dist/chunks/{en-Bxyn5lW7.js → en-mIFywnV2.js} +27 -7
- package/dist/chunks/en-mIFywnV2.js.map +1 -0
- package/dist/chunks/{es-Dtl-a7ui.js → es-CvthYwtB.js} +27 -7
- package/dist/chunks/es-CvthYwtB.js.map +1 -0
- package/dist/chunks/{es-Bj_vc18Z.js → es-DlGd6Mrj.js} +3 -3
- package/dist/chunks/{es-Bj_vc18Z.js.map → es-DlGd6Mrj.js.map} +1 -1
- package/dist/chunks/{generated-hELWB0gZ.js → generated-Cx4C7GRN.js} +126 -0
- package/dist/chunks/{generated-hELWB0gZ.js.map → generated-Cx4C7GRN.js.map} +1 -1
- package/dist/chunks/{grBadgeStyles-DV0DrAew.js → grBadgeStyles-D51AXbj5.js} +57 -1
- package/dist/chunks/grBadgeStyles-D51AXbj5.js.map +1 -0
- package/dist/chunks/{grChipStyles-DcDyxAL0.js → grChipStyles-CA9Wj1_E.js} +1 -1
- package/dist/chunks/{grChipStyles-DcDyxAL0.js.map → grChipStyles-CA9Wj1_E.js.map} +1 -1
- package/dist/chunks/{grPopoverStyles-CYAeO3Ov.js → grPopoverStyles-CncEkiPu.js} +1 -1
- package/dist/chunks/{grPopoverStyles-CYAeO3Ov.js.map → grPopoverStyles-CncEkiPu.js.map} +1 -1
- package/dist/chunks/{grSelectStyles-D9EQ-pgK.js → grSelectStyles-Cam-IG9s.js} +3 -2
- package/dist/chunks/grSelectStyles-Cam-IG9s.js.map +1 -0
- package/dist/chunks/{grSliderStyles-kaz6VXZ3.js → grSliderStyles-CqzV0hll.js} +1 -1
- package/dist/chunks/grSliderStyles-CqzV0hll.js.map +1 -0
- package/dist/chunks/{config-CbEbLyDE.js → grSortableListStyles-CxieDmpe.js} +1 -27
- package/dist/chunks/grSortableListStyles-CxieDmpe.js.map +1 -0
- package/dist/chunks/grTableStyles-CPSxDTOW.js +66 -0
- package/dist/chunks/grTableStyles-CPSxDTOW.js.map +1 -0
- package/dist/chunks/{config-CMNfZO7S.js → grTabsStyles-DIz2VdIm.js} +1 -36
- package/dist/chunks/grTabsStyles-DIz2VdIm.js.map +1 -0
- package/dist/chunks/grTooltipStyles-CtbGEEGV.js +24 -0
- package/dist/chunks/grTooltipStyles-CtbGEEGV.js.map +1 -0
- package/dist/chunks/{granular-provider-BOQp7N5o.js → granular-provider-cv5vaM1a.js} +26 -22
- package/dist/chunks/granular-provider-cv5vaM1a.js.map +1 -0
- package/dist/chunks/hotkeySequence-BTHIM_5A.js +46 -0
- package/dist/chunks/hotkeySequence-BTHIM_5A.js.map +1 -0
- package/dist/chunks/{keyboard-DE2yhz_b.js → keyboard-BiFSgWLt.js} +70 -1
- package/dist/chunks/keyboard-BiFSgWLt.js.map +1 -0
- package/dist/chunks/optionPanel-DvY7Tv_4.js +182 -0
- package/dist/chunks/optionPanel-DvY7Tv_4.js.map +1 -0
- package/dist/chunks/overlayListPanel-D91jBCHU.js +21 -0
- package/dist/chunks/overlayListPanel-D91jBCHU.js.map +1 -0
- package/dist/chunks/{overlayStack-DH4Z7am1.js → overlayStack-DCkCgXZP.js} +1 -1
- package/dist/chunks/{overlayStack-DH4Z7am1.js.map → overlayStack-DCkCgXZP.js.map} +1 -1
- package/dist/chunks/{overlayPanelSurface-BKeMhRx4.js → overlayTransition-C0FDMx7Q.js} +24 -24
- package/dist/chunks/overlayTransition-C0FDMx7Q.js.map +1 -0
- package/dist/chunks/{ru-hrB4I1u_.js → ru-CQKDR-kD.js} +27 -7
- package/dist/chunks/ru-CQKDR-kD.js.map +1 -0
- package/dist/chunks/{safelist-x8USecK8.js → safelist-CSfynUC1.js} +5 -1
- package/dist/chunks/safelist-CSfynUC1.js.map +1 -0
- package/dist/chunks/{useMenuItemsFocus-B7Bl_aFA.js → useMenuItemsFocus-C031blYS.js} +1 -1
- package/dist/chunks/{useMenuItemsFocus-B7Bl_aFA.js.map → useMenuItemsFocus-C031blYS.js.map} +1 -1
- package/dist/chunks/{useModalOverlay-ACRkflzx.js → useModalOverlay-CKCQkGdB.js} +1 -1
- package/dist/chunks/{useModalOverlay-ACRkflzx.js.map → useModalOverlay-CKCQkGdB.js.map} +1 -1
- package/dist/components/GrAutocomplete/chunks/{GrAutocomplete-C-uovdv8.js → GrAutocomplete-Cyr1-ENw.js} +183 -125
- package/dist/components/GrAutocomplete/chunks/GrAutocomplete-Cyr1-ENw.js.map +1 -0
- package/dist/components/GrAutocomplete/index.js +2 -2
- package/dist/components/GrAutocomplete/styles.css +1 -1
- package/dist/components/GrBadge/chunks/GrBadge-C42KLyUD.js +71 -0
- package/dist/components/GrBadge/chunks/GrBadge-C42KLyUD.js.map +1 -0
- package/dist/components/GrBadge/index.js +3 -3
- package/dist/components/GrBadge/styles.css +1 -1
- package/dist/components/GrCarousel/chunks/{GrCarousel-ByuughtI.js → GrCarousel-CBj-PEPB.js} +186 -37
- package/dist/components/GrCarousel/chunks/GrCarousel-CBj-PEPB.js.map +1 -0
- package/dist/components/GrCarousel/index.js +2 -2
- package/dist/components/GrCarousel/styles.css +1 -1
- package/dist/components/GrCheckbox/chunks/{GrCheckbox-Dp201frb.js → GrCheckbox-B4NJc7Ur.js} +2 -1
- package/dist/components/GrCheckbox/chunks/GrCheckbox-B4NJc7Ur.js.map +1 -0
- package/dist/components/GrCheckbox/index.js +1 -1
- package/dist/components/GrCheckboxGroup/chunks/{GrCheckboxGroup-IpqG5qwm.js → GrCheckboxGroup-qZHpNFCX.js} +1 -1
- package/dist/components/GrCheckboxGroup/chunks/{GrCheckboxGroup-IpqG5qwm.js.map → GrCheckboxGroup-qZHpNFCX.js.map} +1 -1
- package/dist/components/GrCheckboxGroup/index.js +1 -1
- package/dist/components/GrChip/chunks/{GrChip-CKqyzYcv.js → GrChip-DoyS1Dr4.js} +1 -1
- package/dist/components/GrChip/chunks/{GrChip-CKqyzYcv.js.map → GrChip-DoyS1Dr4.js.map} +1 -1
- package/dist/components/GrChip/index.js +2 -2
- package/dist/components/GrColorPicker/chunks/{GrColorPicker-BRM_4lyI.js → GrColorPicker-vGSgpUV9.js} +353 -38
- package/dist/components/GrColorPicker/chunks/GrColorPicker-vGSgpUV9.js.map +1 -0
- package/dist/components/GrColorPicker/index.js +2 -2
- package/dist/components/GrColorPicker/styles.css +1 -1
- package/dist/components/GrCommandPalette/chunks/{GrCommandPalette-BJHmkDQv.js → GrCommandPalette-CAHgj0Aa.js} +23 -7
- package/dist/components/GrCommandPalette/chunks/GrCommandPalette-CAHgj0Aa.js.map +1 -0
- package/dist/components/GrCommandPalette/index.js +1 -1
- package/dist/components/GrCommandPalette/styles.css +1 -1
- package/dist/components/GrConfirmDialog/chunks/{GrConfirmDialog-BzUQwbec.js → GrConfirmDialog-dg021BMd.js} +1 -1
- package/dist/components/GrConfirmDialog/chunks/{GrConfirmDialog-BzUQwbec.js.map → GrConfirmDialog-dg021BMd.js.map} +1 -1
- package/dist/components/GrConfirmDialog/index.js +1 -1
- package/dist/components/GrContextMenu/chunks/{GrContextMenu-RpxMHQXH.js → GrContextMenu-bQ1Vvy2c.js} +3 -3
- package/dist/components/GrContextMenu/chunks/{GrContextMenu-RpxMHQXH.js.map → GrContextMenu-bQ1Vvy2c.js.map} +1 -1
- package/dist/components/GrContextMenu/index.js +1 -1
- package/dist/components/GrDataTable/chunks/{GrDataTable-DQxYYgQk.js → GrDataTable-Bb2jWnRG.js} +576 -136
- package/dist/components/GrDataTable/chunks/GrDataTable-Bb2jWnRG.js.map +1 -0
- package/dist/components/GrDataTable/index.js +2 -2
- package/dist/components/GrDelta/chunks/{GrDelta-CgTvfoIN.js → GrDelta-BSv1KdMy.js} +0 -0
- package/dist/components/GrDelta/chunks/{GrDelta-CgTvfoIN.js.map → GrDelta-BSv1KdMy.js.map} +1 -1
- package/dist/components/GrDelta/index.js +2 -2
- package/dist/components/GrDialog/chunks/{GrDialog-DDz_OEhk.js → GrDialog-C9Zu72jB.js} +1 -1
- package/dist/components/GrDialog/chunks/{GrDialog-DDz_OEhk.js.map → GrDialog-C9Zu72jB.js.map} +1 -1
- package/dist/components/GrDialog/index.js +1 -1
- package/dist/components/GrDialogService/chunks/{GrDialogService-Dtq0ZCN5.js → GrDialogService-DLBifO61.js} +2 -2
- package/dist/components/GrDialogService/chunks/{GrDialogService-Dtq0ZCN5.js.map → GrDialogService-DLBifO61.js.map} +1 -1
- package/dist/components/GrDialogService/index.js +1 -1
- package/dist/components/GrDrawer/chunks/{GrDrawer-CNpSMuN-.js → GrDrawer-DOcRTE8G.js} +1 -1
- package/dist/components/GrDrawer/chunks/{GrDrawer-CNpSMuN-.js.map → GrDrawer-DOcRTE8G.js.map} +1 -1
- package/dist/components/GrDrawer/index.js +1 -1
- package/dist/components/GrDropdown/chunks/{GrDropdown-cLZuZMEY.js → GrDropdown-kxwmgfSe.js} +2 -2
- package/dist/components/GrDropdown/chunks/{GrDropdown-cLZuZMEY.js.map → GrDropdown-kxwmgfSe.js.map} +1 -1
- package/dist/components/GrDropdown/index.js +2 -2
- package/dist/components/GrDropdownMenu/chunks/{GrDropdownMenu-Dxkz_ry4.js → GrDropdownMenu--VoK3VY0.js} +3 -34
- package/dist/components/GrDropdownMenu/chunks/GrDropdownMenu--VoK3VY0.js.map +1 -0
- package/dist/components/GrDropdownMenu/chunks/GrDropdownMenu-CkM3BFPL.js +34 -0
- package/dist/components/GrDropdownMenu/chunks/GrDropdownMenu-CkM3BFPL.js.map +1 -0
- package/dist/components/GrDropdownMenu/index.js +2 -1
- package/dist/components/GrEmptyState/chunks/{GrEmptyState-BbvwE-E0.js → GrEmptyState-Bz6-_ED4.js} +42 -6
- package/dist/components/GrEmptyState/chunks/GrEmptyState-Bz6-_ED4.js.map +1 -0
- package/dist/components/GrEmptyState/index.js +1 -1
- package/dist/components/GrFileUpload/chunks/{GrFileUpload-BwMR71zN.js → GrFileUpload-Bgdfnkd8.js} +5 -2
- package/dist/components/GrFileUpload/chunks/GrFileUpload-Bgdfnkd8.js.map +1 -0
- package/dist/components/GrFileUpload/index.js +1 -1
- package/dist/components/GrFormFile/chunks/{GrFormFile-Dlxx-9zi.js → GrFormFile-Ctq1isz-.js} +78 -11
- package/dist/components/GrFormFile/chunks/GrFormFile-Ctq1isz-.js.map +1 -0
- package/dist/components/GrFormFile/index.js +2 -2
- package/dist/components/GrFormFile/styles.css +1 -1
- package/dist/components/GrImageViewer/chunks/{GrImageViewer-DK-iVHHC.js → GrImageViewer-Bnql_dNP.js} +1 -1
- package/dist/components/GrImageViewer/chunks/{GrImageViewer-DK-iVHHC.js.map → GrImageViewer-Bnql_dNP.js.map} +1 -1
- package/dist/components/GrImageViewer/index.js +1 -1
- package/dist/components/GrInput/chunks/{GrInput-DV4gMWPR.js → GrInput-CoaFLpaU.js} +43 -22
- package/dist/components/GrInput/chunks/GrInput-CoaFLpaU.js.map +1 -0
- package/dist/components/GrInput/index.js +1 -1
- package/dist/components/GrInputTag/chunks/{GrInputTag-CO0YU-ok.js → GrInputTag-BzzNRBe3.js} +247 -20
- package/dist/components/GrInputTag/chunks/GrInputTag-BzzNRBe3.js.map +1 -0
- package/dist/components/GrInputTag/index.js +2 -2
- package/dist/components/GrJsonViewer/chunks/{GrJsonViewer-D7CyWzhT.js → GrJsonViewer-B6ZJ3GFH.js} +2 -2
- package/dist/components/GrJsonViewer/chunks/{GrJsonViewer-D7CyWzhT.js.map → GrJsonViewer-B6ZJ3GFH.js.map} +1 -1
- package/dist/components/GrJsonViewer/index.js +1 -1
- package/dist/components/GrKbd/chunks/{GrKbd-BNMeoaLV.js → GrKbd-D2rHDkQv.js} +42 -37
- package/dist/components/GrKbd/chunks/GrKbd-D2rHDkQv.js.map +1 -0
- package/dist/components/GrKbd/index.js +1 -1
- package/dist/components/GrModal/chunks/{GrModal-Cm0mtSXk.js → GrModal-SHb1U2qx.js} +1 -1
- package/dist/components/GrModal/chunks/{GrModal-Cm0mtSXk.js.map → GrModal-SHb1U2qx.js.map} +1 -1
- package/dist/components/GrModal/index.js +1 -1
- package/dist/components/GrNavbar/chunks/{GrNavbar-BRGuksiy.js → GrNavbar-IEIHC5Vt.js} +10 -4
- package/dist/components/GrNavbar/chunks/GrNavbar-IEIHC5Vt.js.map +1 -0
- package/dist/components/GrNavbar/index.js +2 -2
- package/dist/components/GrNumberInput/chunks/{GrNumberInput-BI6-vkcs.js → GrNumberInput-CmnUDaKi.js} +2 -22
- package/dist/components/GrNumberInput/chunks/{GrNumberInput-BI6-vkcs.js.map → GrNumberInput-CmnUDaKi.js.map} +1 -1
- package/dist/components/GrNumberInput/index.js +1 -1
- package/dist/components/GrOtpInput/chunks/GrOtpInput-B_Q-RDLw.js +419 -0
- package/dist/components/GrOtpInput/chunks/GrOtpInput-B_Q-RDLw.js.map +1 -0
- package/dist/components/GrOtpInput/index.js +3 -0
- package/dist/components/GrOtpInput/styles.css +1 -0
- package/dist/components/GrPagination/chunks/{GrPagination-BbaF5eXk.js → GrPagination-AMbn5D8I.js} +1 -1
- package/dist/components/GrPagination/chunks/{GrPagination-BbaF5eXk.js.map → GrPagination-AMbn5D8I.js.map} +1 -1
- package/dist/components/GrPagination/index.js +1 -1
- package/dist/components/GrPopover/chunks/{GrPopover-CcWvr1ZA.js → GrPopover-DjVxt8bf.js} +3 -3
- package/dist/components/GrPopover/chunks/{GrPopover-CcWvr1ZA.js.map → GrPopover-DjVxt8bf.js.map} +1 -1
- package/dist/components/GrPopover/index.js +2 -2
- package/dist/components/GrPromptDialog/chunks/{GrPromptDialog-BEL-QgwE.js → GrPromptDialog-BhbIZP1f.js} +3 -3
- package/dist/components/GrPromptDialog/chunks/{GrPromptDialog-BEL-QgwE.js.map → GrPromptDialog-BhbIZP1f.js.map} +1 -1
- package/dist/components/GrPromptDialog/index.js +1 -1
- package/dist/components/GrSelect/chunks/{GrSelect-0iI4wdR_.js → GrSelect-B-ykrHpL.js} +26 -182
- package/dist/components/GrSelect/chunks/GrSelect-B-ykrHpL.js.map +1 -0
- package/dist/components/GrSelect/index.js +2 -2
- package/dist/components/GrSelect/styles.css +1 -1
- package/dist/components/GrSidebar/chunks/GrSidebar-jVvoLGDO.js +543 -0
- package/dist/components/GrSidebar/chunks/GrSidebar-jVvoLGDO.js.map +1 -0
- package/dist/components/GrSidebar/index.js +2 -2
- package/dist/components/GrSlider/chunks/{GrSlider-CO8N9SF6.js → GrSlider-DF2Cq0rV.js} +29 -4
- package/dist/components/GrSlider/chunks/GrSlider-DF2Cq0rV.js.map +1 -0
- package/dist/components/GrSlider/index.js +2 -2
- package/dist/components/GrSortableList/chunks/{GrSortableList-NKriuGm_.js → GrSortableList-ChH9nQBv.js} +1 -1
- package/dist/components/GrSortableList/chunks/{GrSortableList-NKriuGm_.js.map → GrSortableList-ChH9nQBv.js.map} +1 -1
- package/dist/components/GrSortableList/index.js +2 -2
- package/dist/components/GrSwitch/chunks/{GrSwitch-CriHWT2A.js → GrSwitch-D7X42uIA.js} +61 -7
- package/dist/components/GrSwitch/chunks/GrSwitch-D7X42uIA.js.map +1 -0
- package/dist/components/GrSwitch/index.js +2 -2
- package/dist/components/GrTabPanels/chunks/GrTabPanels-BDt5oVdd.js +30 -0
- package/dist/components/GrTabPanels/chunks/GrTabPanels-BDt5oVdd.js.map +1 -0
- package/dist/components/GrTabPanels/chunks/GrTabPanels-BJvAflnA.js +83 -0
- package/dist/components/GrTabPanels/chunks/GrTabPanels-BJvAflnA.js.map +1 -0
- package/dist/components/GrTabPanels/index.js +2 -1
- package/dist/components/GrTable/chunks/{GrTable-CKJ-HrsN.js → GrTable-BQitSDep.js} +52 -16
- package/dist/components/GrTable/chunks/GrTable-BQitSDep.js.map +1 -0
- package/dist/components/GrTable/index.js +2 -2
- package/dist/components/GrTabs/chunks/{GrTabs-s9RzC1WX.js → GrTabs-DVC6nfWB.js} +1 -1
- package/dist/components/GrTabs/chunks/{GrTabs-s9RzC1WX.js.map → GrTabs-DVC6nfWB.js.map} +1 -1
- package/dist/components/GrTabs/index.js +2 -2
- package/dist/components/GrTabsWithPanels/chunks/GrTabsWithPanels-BYtuzouj.js +98 -0
- package/dist/components/GrTabsWithPanels/chunks/GrTabsWithPanels-BYtuzouj.js.map +1 -0
- package/dist/components/GrTabsWithPanels/index.js +3 -0
- package/dist/components/GrTextarea/chunks/{GrTextarea-Bfgc0DD2.js → GrTextarea-BFwyqcHB.js} +22 -5
- package/dist/components/GrTextarea/chunks/GrTextarea-BFwyqcHB.js.map +1 -0
- package/dist/components/GrTextarea/index.js +1 -1
- package/dist/components/GrToaster/chunks/{GrToaster-CZixNk0l.js → GrToaster-OzT1VKcp.js} +163 -32
- package/dist/components/GrToaster/chunks/GrToaster-OzT1VKcp.js.map +1 -0
- package/dist/components/GrToaster/index.js +2 -2
- package/dist/components/GrToaster/styles.css +1 -1
- package/dist/components/GrTooltip/chunks/{GrTooltip-DkMMM0uq.js → GrTooltip-BDcFycvW.js} +26 -13
- package/dist/components/GrTooltip/chunks/GrTooltip-BDcFycvW.js.map +1 -0
- package/dist/components/GrTooltip/index.js +2 -2
- package/dist/components/GrTransfer/chunks/{GrTransfer-DdfBUf35.js → GrTransfer-CV7BL5UK.js} +114 -12
- package/dist/components/GrTransfer/chunks/GrTransfer-CV7BL5UK.js.map +1 -0
- package/dist/components/GrTransfer/index.js +1 -1
- package/dist/components/GrTransfer/styles.css +1 -1
- package/dist/components/GrTree/chunks/{GrTree-6yZrncJE.js → GrTree-DpgaP8hk.js} +39 -1
- package/dist/components/GrTree/chunks/GrTree-DpgaP8hk.js.map +1 -0
- package/dist/components/GrTree/index.js +1 -1
- package/dist/components/GrTree/styles.css +1 -1
- package/dist/components/GrTreeSections/chunks/{GrTreeSections-DcmDcLwj.js → GrTreeSections-BeOj__44.js} +1 -1
- package/dist/components/GrTreeSections/chunks/{GrTreeSections-DcmDcLwj.js.map → GrTreeSections-BeOj__44.js.map} +1 -1
- package/dist/components/GrTreeSections/index.js +1 -1
- package/dist/components/GrTreeSelect/chunks/{GrTreeSelect-B6KoDYiZ.js → GrTreeSelect-BHq-T8wH.js} +113 -14
- package/dist/components/GrTreeSelect/chunks/GrTreeSelect-BHq-T8wH.js.map +1 -0
- package/dist/components/GrTreeSelect/index.js +2 -2
- package/dist/composables/useFloating.js +1 -1
- package/dist/composables/useHotkeys.js +89 -0
- package/dist/composables/useHotkeys.js.map +1 -0
- package/dist/composables/useOverlayLayer.js +1 -1
- package/dist/directives/hotkey.js +19 -46
- package/dist/directives/hotkey.js.map +1 -1
- package/dist/granular-provider-node.js +1 -1
- package/dist/granular-provider.js +1 -1
- package/dist/i18n/all.js +1 -1
- package/dist/i18n/index.js +1 -1
- package/dist/index.js +80 -71
- package/dist/theme.js +1 -1
- package/dist/tokens.js +1 -1
- package/dist/types/components/GrAutocomplete/GrAutocomplete.vue.d.ts +4 -4
- package/dist/types/components/GrAutocomplete/composables/useAutocompleteNavigation.d.ts +12 -3
- package/dist/types/components/GrAutocomplete/composables/useAutocompletePanel.d.ts +12 -2
- package/dist/types/components/GrAutocomplete/composables/useAutocompleteValues.d.ts +7 -4
- package/dist/types/components/GrAutocomplete/composables/useRemoteOptions.d.ts +4 -4
- package/dist/types/components/GrAutocomplete/grAutocompleteStyles.d.ts +15 -1
- package/dist/types/components/GrAutocomplete/index.d.ts +1 -1
- package/dist/types/components/GrBadge/GrBadge.vue.d.ts +15 -1
- package/dist/types/components/GrBadge/grBadgeStyles.d.ts +57 -0
- package/dist/types/components/GrBadge/index.d.ts +1 -1
- package/dist/types/components/GrCarousel/GrCarousel.vue.d.ts +29 -1
- package/dist/types/components/GrCarousel/composables/useCarouselSwipe.d.ts +5 -0
- package/dist/types/components/GrCarousel/grCarouselContext.d.ts +21 -0
- package/dist/types/components/GrCarousel/grCarouselStyles.d.ts +39 -9
- package/dist/types/components/GrCheckbox/GrCheckbox.vue.d.ts +13 -0
- package/dist/types/components/GrColorPicker/GrColorPicker.vue.d.ts +18 -2
- package/dist/types/components/GrColorPicker/color.d.ts +13 -1
- package/dist/types/components/GrColorPicker/colorArea.d.ts +59 -0
- package/dist/types/components/GrColorPicker/defaults.d.ts +3 -1
- package/dist/types/components/GrColorPicker/eyeDropper.d.ts +21 -0
- package/dist/types/components/GrColorPicker/grColorPickerStyles.d.ts +42 -0
- package/dist/types/components/GrCommandPalette/GrCommandPalette.vue.d.ts +10 -1
- package/dist/types/components/GrDataTable/GrDataTable.vue.d.ts +41 -1
- package/dist/types/components/GrDataTable/composables/useDataTableGridNavigation.d.ts +53 -0
- package/dist/types/components/GrDataTable/grDataTableStyles.d.ts +8 -0
- package/dist/types/components/GrDataTable/index.d.ts +1 -1
- package/dist/types/components/GrDelta/grDeltaStyles.d.ts +10 -3
- package/dist/types/components/GrEmptyState/GrEmptyState.vue.d.ts +11 -2
- package/dist/types/components/GrEmptyState/grEmptyStateStyles.d.ts +10 -0
- package/dist/types/components/GrEmptyState/index.d.ts +1 -1
- package/dist/types/components/GrFileUpload/GrFileUpload.vue.d.ts +2 -1
- package/dist/types/components/GrFormFile/GrFormFile.vue.d.ts +10 -0
- package/dist/types/components/GrInput/GrInput.vue.d.ts +15 -0
- package/dist/types/components/GrInputTag/GrInputTag.vue.d.ts +16 -0
- package/dist/types/components/GrInputTag/grInputTagStyles.d.ts +11 -0
- package/dist/types/components/GrKbd/GrKbd.vue.d.ts +14 -0
- package/dist/types/components/GrNavbar/GrNavbar.vue.d.ts +18 -0
- package/dist/types/components/GrNavbar/grNavbarStyles.d.ts +10 -1
- package/dist/types/components/GrNavbar/index.d.ts +1 -0
- package/dist/types/components/GrOtpInput/GrOtpInput.vue.d.ts +164 -0
- package/dist/types/components/GrOtpInput/config.d.ts +1 -0
- package/dist/types/components/GrOtpInput/defaults.d.ts +15 -0
- package/dist/types/components/GrOtpInput/grOtpInputStyles.d.ts +62 -0
- package/dist/types/components/GrOtpInput/index.d.ts +9 -0
- package/dist/types/components/GrOtpInput/otpValue.d.ts +71 -0
- package/dist/types/components/GrOtpInput/safelist.d.ts +7 -0
- package/dist/types/components/GrSelect/composables/useSelectPanelItems.d.ts +8 -34
- package/dist/types/components/GrSelect/composables/useSelectVirtualization.d.ts +7 -0
- package/dist/types/components/GrSelect/grSelectStyles.d.ts +1 -1
- package/dist/types/components/GrSidebar/GrSidebar.vue.d.ts +20 -0
- package/dist/types/components/GrSidebar/GrSidebarItem.vue.d.ts +39 -5
- package/dist/types/components/GrSidebar/grSidebarStyles.d.ts +32 -0
- package/dist/types/components/GrSidebar/index.d.ts +1 -1
- package/dist/types/components/GrSidebar/sidebarContext.d.ts +23 -1
- package/dist/types/components/GrSlider/GrSlider.vue.d.ts +39 -1
- package/dist/types/components/GrSlider/grSliderStyles.d.ts +1 -1
- package/dist/types/components/GrSwitch/GrSwitch.vue.d.ts +42 -4
- package/dist/types/components/GrSwitch/defaults.d.ts +2 -0
- package/dist/types/components/GrSwitch/grSwitchStyles.d.ts +129 -25
- package/dist/types/components/GrSwitch/index.d.ts +1 -1
- package/dist/types/components/GrSwitch/safelist.d.ts +4 -3
- package/dist/types/components/GrTabPanels/context.d.ts +6 -0
- package/dist/types/components/GrTable/GrTable.vue.d.ts +37 -0
- package/dist/types/components/GrTable/grTableStyles.d.ts +16 -7
- package/dist/types/components/GrTabsWithPanels/GrTabsWithPanels.vue.d.ts +77 -0
- package/dist/types/components/GrTabsWithPanels/config.d.ts +1 -0
- package/dist/types/components/GrTabsWithPanels/index.d.ts +4 -0
- package/dist/types/components/GrTextarea/GrTextarea.vue.d.ts +19 -1
- package/dist/types/components/GrToaster/GrToaster.vue.d.ts +12 -0
- package/dist/types/components/GrToaster/grToasterStyles.d.ts +26 -0
- package/dist/types/components/GrTooltip/GrTooltip.vue.d.ts +22 -0
- package/dist/types/components/GrTransfer/GrTransfer.vue.d.ts +23 -0
- package/dist/types/components/GrTreeSelect/grTreeSelectStyles.d.ts +1 -1
- package/dist/types/components/GrTreeSelect/grTreeSelectTypes.d.ts +20 -0
- package/dist/types/components/shared/hotkey.d.ts +8 -9
- package/dist/types/components/shared/optionPanel.d.ts +100 -0
- package/dist/types/components/shared/overlayListPanel.d.ts +18 -0
- package/dist/types/composables/useHotkeys.d.ts +43 -0
- package/dist/types/granular-provider/shared.d.ts +2 -0
- package/dist/types/i18n/messages/en.d.ts +32 -0
- package/dist/types/i18n/messages/es.d.ts +32 -0
- package/dist/types/i18n/messages/ru.d.ts +32 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/internal/hotkeySequence.d.ts +22 -0
- package/dist/types/internal/keyboard.d.ts +45 -0
- package/dist/web-types.json +784 -101
- package/package.json +13 -1
- package/dist/chunks/config-2ZHRsdOc.js.map +0 -1
- package/dist/chunks/config-B42iJ48O.js.map +0 -1
- package/dist/chunks/config-B5SBUa6u.js.map +0 -1
- package/dist/chunks/config-BAse0laT.js.map +0 -1
- package/dist/chunks/config-BHLMTJaR.js.map +0 -1
- package/dist/chunks/config-BXRmsn3t.js.map +0 -1
- package/dist/chunks/config-Bs1HwUcx.js.map +0 -1
- package/dist/chunks/config-CMNfZO7S.js.map +0 -1
- package/dist/chunks/config-CbEbLyDE.js.map +0 -1
- package/dist/chunks/config-CjWOTgLL.js.map +0 -1
- package/dist/chunks/config-D3HOuHDe.js.map +0 -1
- package/dist/chunks/config-DIDz_E5m.js.map +0 -1
- package/dist/chunks/config-DNDgp9bB.js.map +0 -1
- package/dist/chunks/config-Yy67uWHY.js +0 -140
- package/dist/chunks/config-Yy67uWHY.js.map +0 -1
- package/dist/chunks/config-iemgzS4x.js.map +0 -1
- package/dist/chunks/en-Bxyn5lW7.js.map +0 -1
- package/dist/chunks/es-Dtl-a7ui.js.map +0 -1
- package/dist/chunks/grBadgeStyles-DV0DrAew.js.map +0 -1
- package/dist/chunks/grSelectStyles-D9EQ-pgK.js.map +0 -1
- package/dist/chunks/grSliderStyles-kaz6VXZ3.js.map +0 -1
- package/dist/chunks/grTableStyles-DkQPMfx9.js +0 -39
- package/dist/chunks/grTableStyles-DkQPMfx9.js.map +0 -1
- package/dist/chunks/granular-provider-BOQp7N5o.js.map +0 -1
- package/dist/chunks/keyboard-DE2yhz_b.js.map +0 -1
- package/dist/chunks/overlayPanelSurface-BKeMhRx4.js.map +0 -1
- package/dist/chunks/ru-hrB4I1u_.js.map +0 -1
- package/dist/chunks/safelist-x8USecK8.js.map +0 -1
- package/dist/components/GrAutocomplete/chunks/GrAutocomplete-C-uovdv8.js.map +0 -1
- package/dist/components/GrBadge/chunks/GrBadge-DBeQXEir.js +0 -47
- package/dist/components/GrBadge/chunks/GrBadge-DBeQXEir.js.map +0 -1
- package/dist/components/GrCarousel/chunks/GrCarousel-ByuughtI.js.map +0 -1
- package/dist/components/GrCheckbox/chunks/GrCheckbox-Dp201frb.js.map +0 -1
- package/dist/components/GrColorPicker/chunks/GrColorPicker-BRM_4lyI.js.map +0 -1
- package/dist/components/GrCommandPalette/chunks/GrCommandPalette-BJHmkDQv.js.map +0 -1
- package/dist/components/GrDataTable/chunks/GrDataTable-DQxYYgQk.js.map +0 -1
- package/dist/components/GrDropdownMenu/chunks/GrDropdownMenu-Dxkz_ry4.js.map +0 -1
- package/dist/components/GrEmptyState/chunks/GrEmptyState-BbvwE-E0.js.map +0 -1
- package/dist/components/GrFileUpload/chunks/GrFileUpload-BwMR71zN.js.map +0 -1
- package/dist/components/GrFormFile/chunks/GrFormFile-Dlxx-9zi.js.map +0 -1
- package/dist/components/GrInput/chunks/GrInput-DV4gMWPR.js.map +0 -1
- package/dist/components/GrInputTag/chunks/GrInputTag-CO0YU-ok.js.map +0 -1
- package/dist/components/GrKbd/chunks/GrKbd-BNMeoaLV.js.map +0 -1
- package/dist/components/GrNavbar/chunks/GrNavbar-BRGuksiy.js.map +0 -1
- package/dist/components/GrSelect/chunks/GrSelect-0iI4wdR_.js.map +0 -1
- package/dist/components/GrSidebar/chunks/GrSidebar-DoKLlHSw.js +0 -264
- package/dist/components/GrSidebar/chunks/GrSidebar-DoKLlHSw.js.map +0 -1
- package/dist/components/GrSlider/chunks/GrSlider-CO8N9SF6.js.map +0 -1
- package/dist/components/GrSwitch/chunks/GrSwitch-CriHWT2A.js.map +0 -1
- package/dist/components/GrTabPanels/chunks/GrTabPanels-5zsbqS1v.js +0 -92
- package/dist/components/GrTabPanels/chunks/GrTabPanels-5zsbqS1v.js.map +0 -1
- package/dist/components/GrTable/chunks/GrTable-CKJ-HrsN.js.map +0 -1
- package/dist/components/GrTextarea/chunks/GrTextarea-Bfgc0DD2.js.map +0 -1
- package/dist/components/GrToaster/chunks/GrToaster-CZixNk0l.js.map +0 -1
- package/dist/components/GrTooltip/chunks/GrTooltip-DkMMM0uq.js.map +0 -1
- package/dist/components/GrTransfer/chunks/GrTransfer-DdfBUf35.js.map +0 -1
- package/dist/components/GrTree/chunks/GrTree-6yZrncJE.js.map +0 -1
- package/dist/components/GrTreeSelect/chunks/GrTreeSelect-B6KoDYiZ.js.map +0 -1
- package/dist/types/components/GrAutocomplete/composables/useAutocompleteVirtual.d.ts +0 -33
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GrFileUpload-Bgdfnkd8.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 * а `action` не используется; в dev-сборке об этом предупреждает консоль.\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/*\n * Куда отправлять файлы, компонент узнаёт из `action` или `request`, и обе\n * ошибки — «ни одного» и «оба сразу» — до первого выбора файла ничем себя не\n * выдают.\n *\n * Типом это не выражается, и не по недосмотру: `defineProps` принимает\n * объектный тип или интерфейс, но не discriminated union. Проверено —\n * `defineProps<{ action: string } | { request: … }>()` компилируется, а до места\n * употребления union не доезжает: ключи схлопываются в плоский объект, и\n * `vue-tsc` молчит и на «оба сразу», и на «ни одного».\n */\nonMounted(() => {\n if (!__GR_DEV__)\n return\n\n if (!props.action && !props.request) {\n console.warn('[GrFileUpload] не задан ни `action`, ни `request`: отправлять файлы некуда.')\n return\n }\n\n // Приоритет `request` сам по себе не ошибка, но молчаливым быть не должен:\n // переданный рядом `action` не отменяется и не дополняет — он просто не\n // участвует, и заметить это по поведению нельзя.\n if (props.action && props.request)\n console.warn('[GrFileUpload] заданы и `action`, и `request`: файлы отправит `request`, `action` не будет использован.')\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 > {{ 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 * а `action` не используется; в dev-сборке об этом предупреждает консоль.\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/*\n * Куда отправлять файлы, компонент узнаёт из `action` или `request`, и обе\n * ошибки — «ни одного» и «оба сразу» — до первого выбора файла ничем себя не\n * выдают.\n *\n * Типом это не выражается, и не по недосмотру: `defineProps` принимает\n * объектный тип или интерфейс, но не discriminated union. Проверено —\n * `defineProps<{ action: string } | { request: … }>()` компилируется, а до места\n * употребления union не доезжает: ключи схлопываются в плоский объект, и\n * `vue-tsc` молчит и на «оба сразу», и на «ни одного».\n */\nonMounted(() => {\n if (!__GR_DEV__)\n return\n\n if (!props.action && !props.request) {\n console.warn('[GrFileUpload] не задан ни `action`, ни `request`: отправлять файлы некуда.')\n return\n }\n\n // Приоритет `request` сам по себе не ошибка, но молчаливым быть не должен:\n // переданный рядом `action` не отменяется и не дополняет — он просто не\n // участвует, и заметить это по поведению нельзя.\n if (props.action && props.request)\n console.warn('[GrFileUpload] заданы и `action`, и `request`: файлы отправит `request`, `action` не будет использован.')\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 > {{ 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,4 +1,4 @@
|
|
|
1
1
|
import { p as FileValidationError } from "../../chunks/fileValidation-D5TFBhbv.js";
|
|
2
|
-
import { n as GrUploadAbortError, r as GrUploadHttpError, t as GrFileUpload_default } from "./chunks/GrFileUpload-
|
|
2
|
+
import { n as GrUploadAbortError, r as GrUploadHttpError, t as GrFileUpload_default } from "./chunks/GrFileUpload-Bgdfnkd8.js";
|
|
3
3
|
import { n as grFileUploadSafelist, t as grFileUploadConfig } from "../../chunks/config-BlJa1Mj9.js";
|
|
4
4
|
export { FileValidationError, GrFileUpload_default as GrFileUpload, GrFileUpload_default as default, GrUploadAbortError, GrUploadHttpError, grFileUploadConfig, grFileUploadSafelist };
|
|
@@ -10,7 +10,8 @@ import { t as useFocusWithin } from "../../../chunks/useFocusWithin-BwHE0rzm.js"
|
|
|
10
10
|
import { t as _plugin_vue_export_helper_default } from "../../../chunks/_plugin-vue_export-helper-BOaGB7Aw.js";
|
|
11
11
|
import { t as GrButton_default } from "../../GrButton/chunks/GrButton-CS4TqMtG.js";
|
|
12
12
|
import { t as useFilePreviews } from "../../../chunks/useFilePreviews-DnwgdyBs.js";
|
|
13
|
-
import {
|
|
13
|
+
import { t as GrSortableList_default } from "../../GrSortableList/chunks/GrSortableList-ChH9nQBv.js";
|
|
14
|
+
import { a as iconSizes, c as removeTextSizes, d as textSizes, i as iconOffsets, l as rowGaps, o as previewBaseClass, r as buttonSizes, s as previewSizes, u as stackGaps } from "../../../chunks/config-D-JgqV0J.js";
|
|
14
15
|
import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, markRaw, nextTick, normalizeClass, onBeforeUnmount, openBlock, ref, renderList, renderSlot, toDisplayString, unref, useId, watch, withCtx, withDirectives, withModifiers } from "vue";
|
|
15
16
|
import '../styles.css';//#region ~icons/lucide/upload
|
|
16
17
|
var _hoisted_1$1 = {
|
|
@@ -42,14 +43,25 @@ var _hoisted_2 = [
|
|
|
42
43
|
];
|
|
43
44
|
var _hoisted_3 = ["src"];
|
|
44
45
|
var _hoisted_4 = ["title"];
|
|
45
|
-
var _hoisted_5 =
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
var _hoisted_5 = {
|
|
47
|
+
class: "flex flex-1 items-center gap-2",
|
|
48
|
+
"data-gr-form-file-item": ""
|
|
49
|
+
};
|
|
50
|
+
var _hoisted_6 = ["src"];
|
|
51
|
+
var _hoisted_7 = ["title"];
|
|
52
|
+
var _hoisted_8 = [
|
|
53
|
+
"disabled",
|
|
54
|
+
"aria-label",
|
|
55
|
+
"onClick"
|
|
56
|
+
];
|
|
57
|
+
var _hoisted_9 = ["src"];
|
|
58
|
+
var _hoisted_10 = ["title"];
|
|
59
|
+
var _hoisted_11 = [
|
|
48
60
|
"disabled",
|
|
49
61
|
"aria-label",
|
|
50
62
|
"onClick"
|
|
51
63
|
];
|
|
52
|
-
var
|
|
64
|
+
var _hoisted_12 = ["id"];
|
|
53
65
|
//#endregion
|
|
54
66
|
//#region src/components/GrFormFile/GrFormFile.vue
|
|
55
67
|
var GrFormFile_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
|
|
@@ -90,6 +102,10 @@ var GrFormFile_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__P
|
|
|
90
102
|
type: Boolean,
|
|
91
103
|
default: false
|
|
92
104
|
},
|
|
105
|
+
reorderable: {
|
|
106
|
+
type: Boolean,
|
|
107
|
+
default: false
|
|
108
|
+
},
|
|
93
109
|
validate: {
|
|
94
110
|
type: Function,
|
|
95
111
|
default: void 0
|
|
@@ -213,6 +229,15 @@ var GrFormFile_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__P
|
|
|
213
229
|
emit("update:modelValue", value);
|
|
214
230
|
emit("change", value);
|
|
215
231
|
}
|
|
232
|
+
/**
|
|
233
|
+
* Перестановка — то же изменение набора, что удаление или добавление, поэтому
|
|
234
|
+
* идёт тем же путём: валидацию она не запускает (файлы те же), но модель и
|
|
235
|
+
* `change` обязана отдать, иначе порядок остался бы только на экране.
|
|
236
|
+
*/
|
|
237
|
+
function reorderFiles(nextFiles) {
|
|
238
|
+
if (isLocked.value) return;
|
|
239
|
+
emitModel(nextFiles);
|
|
240
|
+
}
|
|
216
241
|
async function applyFiles(nextFiles) {
|
|
217
242
|
const isPromiseLike = (value) => {
|
|
218
243
|
return !!value && typeof value.then === "function";
|
|
@@ -395,8 +420,50 @@ var GrFormFile_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__P
|
|
|
395
420
|
"data-gr-form-file-placeholder": ""
|
|
396
421
|
}, toDisplayString(resolvedPlaceholder.value), 3)) : createCommentVNode("", true)
|
|
397
422
|
], 2),
|
|
398
|
-
__props.multiple && hasFiles.value ? (openBlock(),
|
|
423
|
+
__props.multiple && hasFiles.value && __props.reorderable ? (openBlock(), createBlock(GrSortableList_default, {
|
|
399
424
|
key: 0,
|
|
425
|
+
"model-value": files.value,
|
|
426
|
+
"item-key": unref(fileKey),
|
|
427
|
+
disabled: unref(isLocked),
|
|
428
|
+
variant: "ghost",
|
|
429
|
+
"handle-only": "",
|
|
430
|
+
"data-gr-form-file-list": "",
|
|
431
|
+
"onUpdate:modelValue": reorderFiles
|
|
432
|
+
}, {
|
|
433
|
+
item: withCtx(({ item: file, index }) => [createElementVNode("div", _hoisted_5, [
|
|
434
|
+
unref(previewUrl)(file) ? (openBlock(), createElementBlock("img", {
|
|
435
|
+
key: 0,
|
|
436
|
+
"data-gr-form-file-preview": "",
|
|
437
|
+
src: unref(previewUrl)(file),
|
|
438
|
+
alt: "",
|
|
439
|
+
class: normalizeClass(previewClass.value)
|
|
440
|
+
}, null, 10, _hoisted_6)) : createCommentVNode("", true),
|
|
441
|
+
createElementVNode("span", {
|
|
442
|
+
class: normalizeClass(["text-[var(--gr-muted-fg)] truncate max-w-[240px]", textClass.value]),
|
|
443
|
+
title: file.name,
|
|
444
|
+
"data-gr-form-file-item-name": ""
|
|
445
|
+
}, toDisplayString(file.name), 11, _hoisted_7),
|
|
446
|
+
createElementVNode("span", {
|
|
447
|
+
class: normalizeClass(["text-[var(--gr-muted-fg)] shrink-0", removeTextClass.value]),
|
|
448
|
+
"data-gr-form-file-item-size": ""
|
|
449
|
+
}, toDisplayString(formatFileSize(file)), 3),
|
|
450
|
+
!unref(isReadonly) ? (openBlock(), createElementBlock("button", {
|
|
451
|
+
key: 1,
|
|
452
|
+
type: "button",
|
|
453
|
+
class: normalizeClass(["text-[var(--gr-muted-fg)] hover:text-[var(--gr-fg)]", removeTextClass.value]),
|
|
454
|
+
"data-gr-form-file-item-remove": "",
|
|
455
|
+
disabled: unref(isDisabled),
|
|
456
|
+
"aria-label": unref(t)("gr.formFile.removeFile", "Remove {fileName}", { fileName: file.name }),
|
|
457
|
+
onClick: withModifiers(($event) => removeAt(index), ["prevent"])
|
|
458
|
+
}, toDisplayString(resolvedRemoveText.value), 11, _hoisted_8)) : createCommentVNode("", true)
|
|
459
|
+
])]),
|
|
460
|
+
_: 1
|
|
461
|
+
}, 8, [
|
|
462
|
+
"model-value",
|
|
463
|
+
"item-key",
|
|
464
|
+
"disabled"
|
|
465
|
+
])) : __props.multiple && hasFiles.value ? (openBlock(), createElementBlock("div", {
|
|
466
|
+
key: 1,
|
|
400
467
|
class: normalizeClass(["flex flex-col", stackClass.value])
|
|
401
468
|
}, [(openBlock(true), createElementBlock(Fragment, null, renderList(files.value, (file, index) => {
|
|
402
469
|
return openBlock(), createElementBlock("div", {
|
|
@@ -410,12 +477,12 @@ var GrFormFile_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__P
|
|
|
410
477
|
src: unref(previewUrl)(file),
|
|
411
478
|
alt: "",
|
|
412
479
|
class: normalizeClass(previewClass.value)
|
|
413
|
-
}, null, 10,
|
|
480
|
+
}, null, 10, _hoisted_9)) : createCommentVNode("", true),
|
|
414
481
|
createElementVNode("span", {
|
|
415
482
|
class: normalizeClass(["text-[var(--gr-muted-fg)] truncate max-w-[240px]", textClass.value]),
|
|
416
483
|
title: file.name,
|
|
417
484
|
"data-gr-form-file-item-name": ""
|
|
418
|
-
}, toDisplayString(file.name), 11,
|
|
485
|
+
}, toDisplayString(file.name), 11, _hoisted_10),
|
|
419
486
|
createElementVNode("span", {
|
|
420
487
|
class: normalizeClass(["text-[var(--gr-muted-fg)] shrink-0", removeTextClass.value]),
|
|
421
488
|
"data-gr-form-file-item-size": ""
|
|
@@ -428,7 +495,7 @@ var GrFormFile_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__P
|
|
|
428
495
|
disabled: unref(isDisabled),
|
|
429
496
|
"aria-label": unref(t)("gr.formFile.removeFile", "Remove {fileName}", { fileName: file.name }),
|
|
430
497
|
onClick: withModifiers(($event) => removeAt(index), ["prevent"])
|
|
431
|
-
}, toDisplayString(resolvedRemoveText.value), 11,
|
|
498
|
+
}, toDisplayString(resolvedRemoveText.value), 11, _hoisted_11)) : createCommentVNode("", true)
|
|
432
499
|
]);
|
|
433
500
|
}), 128))], 2)) : createCommentVNode("", true),
|
|
434
501
|
renderSlot(_ctx.$slots, "error", { errors: displayedErrors.value }, () => [hasLocalErrors.value ? (openBlock(), createElementBlock("div", {
|
|
@@ -442,11 +509,11 @@ var GrFormFile_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__P
|
|
|
442
509
|
key: i,
|
|
443
510
|
"data-gr-form-file-error": ""
|
|
444
511
|
}, toDisplayString(issueMessage(e)), 1);
|
|
445
|
-
}), 128))], 10,
|
|
512
|
+
}), 128))], 10, _hoisted_12)) : createCommentVNode("", true)], true)
|
|
446
513
|
], 2)
|
|
447
514
|
], 34)), [[unref(vDropzone), dropzone.value]]);
|
|
448
515
|
};
|
|
449
516
|
}
|
|
450
|
-
}), [["__scopeId", "data-v-
|
|
517
|
+
}), [["__scopeId", "data-v-50f6edeb"]]);
|
|
451
518
|
//#endregion
|
|
452
519
|
export { GrFormFile_default as t };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GrFormFile-Ctq1isz-.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 GrSortableList from '../GrSortableList/GrSortableList.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 * Порядок набора задаёт пользователь: у строки появляется ручка переноса.\n *\n * Нужно там, где последовательность файлов что-то значит, — галерея,\n * страницы документа, очередь вложений. Осмысленно только с `multiple`.\n */\n reorderable?: 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 reorderable: 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\n/**\n * Перестановка — то же изменение набора, что удаление или добавление, поэтому\n * идёт тем же путём: валидацию она не запускает (файлы те же), но модель и\n * `change` обязана отдать, иначе порядок остался бы только на экране.\n */\nfunction reorderFiles(nextFiles: File[]): void {\n if (isLocked.value)\n return\n\n emitModel(nextFiles)\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 <!--\n При `reorderable` строки набора рисует `GrSortableList`: перенос\n указателем, клавиатура и объявления у него уже есть, и седьмая копия\n этой механики пакету не нужна. `ghost` — чтобы не появилась вторая\n рамка вокруг того же места, `handle-only` — чтобы тянулась ручка, а не\n строка с кнопкой удаления.\n -->\n <GrSortableList\n v-if=\"multiple && hasFiles && reorderable\"\n :model-value=\"files\"\n :item-key=\"fileKey\"\n :disabled=\"isLocked\"\n variant=\"ghost\"\n handle-only\n data-gr-form-file-list\n @update:model-value=\"reorderFiles\"\n >\n <template #item=\"{ item: file, index }\">\n <div class=\"flex flex-1 items-center gap-2\" data-gr-form-file-item>\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 >{{ file.name }}</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 </template>\n </GrSortableList>\n\n <div v-else-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 GrSortableList from '../GrSortableList/GrSortableList.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 * Порядок набора задаёт пользователь: у строки появляется ручка переноса.\n *\n * Нужно там, где последовательность файлов что-то значит, — галерея,\n * страницы документа, очередь вложений. Осмысленно только с `multiple`.\n */\n reorderable?: 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 reorderable: 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\n/**\n * Перестановка — то же изменение набора, что удаление или добавление, поэтому\n * идёт тем же путём: валидацию она не запускает (файлы те же), но модель и\n * `change` обязана отдать, иначе порядок остался бы только на экране.\n */\nfunction reorderFiles(nextFiles: File[]): void {\n if (isLocked.value)\n return\n\n emitModel(nextFiles)\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 <!--\n При `reorderable` строки набора рисует `GrSortableList`: перенос\n указателем, клавиатура и объявления у него уже есть, и седьмая копия\n этой механики пакету не нужна. `ghost` — чтобы не появилась вторая\n рамка вокруг того же места, `handle-only` — чтобы тянулась ручка, а не\n строка с кнопкой удаления.\n -->\n <GrSortableList\n v-if=\"multiple && hasFiles && reorderable\"\n :model-value=\"files\"\n :item-key=\"fileKey\"\n :disabled=\"isLocked\"\n variant=\"ghost\"\n handle-only\n data-gr-form-file-list\n @update:model-value=\"reorderFiles\"\n >\n <template #item=\"{ item: file, index }\">\n <div class=\"flex flex-1 items-center gap-2\" data-gr-form-file-item>\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 >{{ file.name }}</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 </template>\n </GrSortableList>\n\n <div v-else-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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"}
|