@feugene/granularity 0.25.0 → 0.27.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/dist/chunks/classTokens-8v2XC74x.js +16 -0
- package/dist/chunks/classTokens-8v2XC74x.js.map +1 -0
- package/dist/chunks/{config-Bpt9Xbxm.js → config-2ZHRsdOc.js} +2 -2
- package/dist/chunks/config-2ZHRsdOc.js.map +1 -0
- package/dist/chunks/{config-D6W1rnlp.js → config-B0uvcTPH.js} +1 -1
- package/dist/chunks/{config-D6W1rnlp.js.map → config-B0uvcTPH.js.map} +1 -1
- package/dist/chunks/{config-Bsh948nP.js → config-B3J0alyY.js} +1 -1
- package/dist/chunks/{config-Bsh948nP.js.map → config-B3J0alyY.js.map} +1 -1
- package/dist/chunks/{config-ScuvHw6k.js → config-B5SBUa6u.js} +1 -1
- package/dist/chunks/{config-ScuvHw6k.js.map → config-B5SBUa6u.js.map} +1 -1
- package/dist/chunks/{config-BeZRuonp.js → config-BDSaD-5p.js} +1 -1
- package/dist/chunks/{config-BeZRuonp.js.map → config-BDSaD-5p.js.map} +1 -1
- package/dist/chunks/{config-Dgj1Q6JY.js → config-BEO7brpO.js} +3 -3
- package/dist/chunks/config-BEO7brpO.js.map +1 -0
- package/dist/chunks/{config-B84z1aos.js → config-BHcS5Cwm.js} +1 -1
- package/dist/chunks/{config-B84z1aos.js.map → config-BHcS5Cwm.js.map} +1 -1
- package/dist/chunks/{config-DA6SnEoy.js → config-BRESvs9g.js} +2 -2
- package/dist/chunks/{config-DA6SnEoy.js.map → config-BRESvs9g.js.map} +1 -1
- package/dist/chunks/{config-CN2ULSQL.js → config-BTmfQFwi.js} +5 -5
- package/dist/chunks/config-BTmfQFwi.js.map +1 -0
- package/dist/chunks/{config-DtgYuGUn2.js → config-BTwYMQaz2.js} +5 -3
- package/dist/chunks/config-BTwYMQaz2.js.map +1 -0
- package/dist/chunks/{config-QJ54_fcM.js → config-BaWANzzB.js} +1 -1
- package/dist/chunks/{config-QJ54_fcM.js.map → config-BaWANzzB.js.map} +1 -1
- package/dist/chunks/{config-COvrUfhR.js → config-BcXX7S7B.js} +5 -5
- package/dist/chunks/config-BcXX7S7B.js.map +1 -0
- package/dist/chunks/{config-COpuWGr_.js → config-Bd7QfiGj.js} +1 -1
- package/dist/chunks/{config-COpuWGr_.js.map → config-Bd7QfiGj.js.map} +1 -1
- package/dist/chunks/{config-BDfTXHo1.js → config-BhfNgyWL.js} +2 -2
- package/dist/chunks/{config-BDfTXHo1.js.map → config-BhfNgyWL.js.map} +1 -1
- package/dist/chunks/{config-CjnbevbD.js → config-Bjn30smH.js} +1 -1
- package/dist/chunks/{config-CjnbevbD.js.map → config-Bjn30smH.js.map} +1 -1
- package/dist/chunks/{config-CN970ZcJ.js → config-BlJa1Mj9.js} +13 -13
- package/dist/chunks/config-BlJa1Mj9.js.map +1 -0
- package/dist/chunks/{config-DU2DNSRx.js → config-BoUI0t-t.js} +1 -1
- package/dist/chunks/{config-DU2DNSRx.js.map → config-BoUI0t-t.js.map} +1 -1
- package/dist/chunks/{config-BN4sXYXn.js → config-BqT6PMzO.js} +7 -14
- package/dist/chunks/config-BqT6PMzO.js.map +1 -0
- package/dist/chunks/config-BuGlNPE9.js.map +1 -0
- package/dist/chunks/{config-MRvhUl-O.js → config-CJzmDHfI.js} +1 -1
- package/dist/chunks/{config-MRvhUl-O.js.map → config-CJzmDHfI.js.map} +1 -1
- package/dist/chunks/{config-DGWFal5h.js → config-CLjjjPZj.js} +7 -7
- package/dist/chunks/config-CLjjjPZj.js.map +1 -0
- package/dist/chunks/{config-Cl4ST6Qd.js → config-CQLNjc9X2.js} +2 -2
- package/dist/chunks/config-CQLNjc9X2.js.map +1 -0
- package/dist/chunks/{config-vSji8w90.js → config-CWtfxkM9.js} +9 -9
- package/dist/chunks/config-CWtfxkM9.js.map +1 -0
- package/dist/chunks/{config-DKISz9gR.js → config-CWthWcWY.js} +2 -2
- package/dist/chunks/{config-DKISz9gR.js.map → config-CWthWcWY.js.map} +1 -1
- package/dist/chunks/{config-Iu684VNI.js → config-CZPDPJlg.js} +1 -1
- package/dist/chunks/{config-Iu684VNI.js.map → config-CZPDPJlg.js.map} +1 -1
- package/dist/chunks/{config-DAX8Deh1.js → config-CbEbLyDE.js} +3 -3
- package/dist/chunks/config-CbEbLyDE.js.map +1 -0
- package/dist/chunks/{config-D94Z9po5.js → config-ChPg-BQv.js} +2 -2
- package/dist/chunks/config-ChPg-BQv.js.map +1 -0
- package/dist/chunks/{config-DSJY6aEl.js → config-CjKpUcmy.js} +17 -17
- package/dist/chunks/config-CjKpUcmy.js.map +1 -0
- package/dist/chunks/{config-BIa3ilQm.js → config-CjWOTgLL.js} +4 -4
- package/dist/chunks/{config-BIa3ilQm.js.map → config-CjWOTgLL.js.map} +1 -1
- package/dist/chunks/{config-rkcKArZQ.js → config-CnYdE3HZ2.js} +5 -5
- package/dist/chunks/config-CnYdE3HZ2.js.map +1 -0
- package/dist/chunks/{config-BE6n20np.js → config-CvbVM4ep.js} +1 -1
- package/dist/chunks/{config-BE6n20np.js.map → config-CvbVM4ep.js.map} +1 -1
- package/dist/chunks/{config-9PYRZlKE.js → config-D0NzGYaE.js} +6 -6
- package/dist/chunks/config-D0NzGYaE.js.map +1 -0
- package/dist/chunks/{config-CKULiuvc.js → config-D0W8mGFD.js} +4 -4
- package/dist/chunks/{config-CKULiuvc.js.map → config-D0W8mGFD.js.map} +1 -1
- package/dist/chunks/{config-CY0_6cMi.js → config-D82r782v.js} +1 -1
- package/dist/chunks/{config-CY0_6cMi.js.map → config-D82r782v.js.map} +1 -1
- package/dist/chunks/config-DBmdgoVf2.js.map +1 -0
- package/dist/chunks/{config-WTNhdh6T.js → config-DCq4JySZ.js} +2 -2
- package/dist/chunks/{config-WTNhdh6T.js.map → config-DCq4JySZ.js.map} +1 -1
- package/dist/chunks/{config-C8tVRv8i.js → config-DGC00bTp.js} +13 -13
- package/dist/chunks/config-DGC00bTp.js.map +1 -0
- package/dist/chunks/{config-BsPDQ0dS.js → config-DIDz_E5m.js} +9 -9
- package/dist/chunks/config-DIDz_E5m.js.map +1 -0
- package/dist/chunks/{config-Dguto_6o.js → config-DM0lsduD.js} +6 -6
- package/dist/chunks/config-DM0lsduD.js.map +1 -0
- package/dist/chunks/{config-BfsBepkH.js → config-DNDgp9bB.js} +5 -5
- package/dist/chunks/config-DNDgp9bB.js.map +1 -0
- package/dist/chunks/{config-DpeQchsV.js → config-DO8mjeIo.js} +2 -2
- package/dist/chunks/{config-DpeQchsV.js.map → config-DO8mjeIo.js.map} +1 -1
- package/dist/chunks/{config-Baj7Wvx-.js → config-DRbojEzO.js} +4 -4
- package/dist/chunks/config-DRbojEzO.js.map +1 -0
- package/dist/chunks/{config-CQzGyi1C.js → config-DWL81-aM.js} +1 -1
- package/dist/chunks/{config-CQzGyi1C.js.map → config-DWL81-aM.js.map} +1 -1
- package/dist/chunks/{config-esdviAPL.js → config-DZCUPHPU.js} +1 -1
- package/dist/chunks/{config-esdviAPL.js.map → config-DZCUPHPU.js.map} +1 -1
- package/dist/chunks/{config-DKdlqXiY.js → config-DcfWZOi5.js} +5 -5
- package/dist/chunks/config-DcfWZOi5.js.map +1 -0
- package/dist/chunks/{config-BC8d_smz.js → config-DeyT9dJl.js} +3 -3
- package/dist/chunks/{config-BC8d_smz.js.map → config-DeyT9dJl.js.map} +1 -1
- package/dist/chunks/{config-Cwd3yjRw.js → config-DnF472Ox.js} +6 -4
- package/dist/chunks/config-DnF472Ox.js.map +1 -0
- package/dist/chunks/{config-B_qi4Fma.js → config-DolULhjJ.js} +5 -5
- package/dist/chunks/config-DolULhjJ.js.map +1 -0
- package/dist/chunks/{config-CIcynHGr.js → config-DpAYidyY2.js} +1 -1
- package/dist/chunks/config-DpAYidyY2.js.map +1 -0
- package/dist/chunks/{config-DYgfMrIF.js → config-Dpk-eVkS.js} +1 -1
- package/dist/chunks/{config-DYgfMrIF.js.map → config-Dpk-eVkS.js.map} +1 -1
- package/dist/chunks/config-Dv_r92GL.js +35 -0
- package/dist/chunks/config-Dv_r92GL.js.map +1 -0
- package/dist/chunks/{config-B4w5gBD0.js → config-DwABJHEo.js} +2 -2
- package/dist/chunks/config-DwABJHEo.js.map +1 -0
- package/dist/chunks/{config-zjeAJ4ho2.js → config-MmPym-Z72.js} +5 -5
- package/dist/chunks/config-MmPym-Z72.js.map +1 -0
- package/dist/chunks/{config-DCFvmHEv.js → config-T3-DMXYm.js} +9 -7
- package/dist/chunks/config-T3-DMXYm.js.map +1 -0
- package/dist/chunks/{config-BoNR7_PD.js → config-Yy67uWHY.js} +1 -1
- package/dist/chunks/{config-BoNR7_PD.js.map → config-Yy67uWHY.js.map} +1 -1
- package/dist/chunks/{config-BFJIxE8b.js → config-Z5LeUXys.js} +1 -1
- package/dist/chunks/{config-BFJIxE8b.js.map → config-Z5LeUXys.js.map} +1 -1
- package/dist/chunks/{config-DWN4x6_e.js → config-_QF0O1vq.js} +9 -9
- package/dist/chunks/config-_QF0O1vq.js.map +1 -0
- package/dist/chunks/{config-Cp0DE0GH.js → config-_jO3q1Le.js} +1 -1
- package/dist/chunks/{config-Cp0DE0GH.js.map → config-_jO3q1Le.js.map} +1 -1
- package/dist/chunks/{config-DGCMHPCR.js → config-g8-JGZ_Y.js} +1 -1
- package/dist/chunks/{config-DGCMHPCR.js.map → config-g8-JGZ_Y.js.map} +1 -1
- package/dist/chunks/{config-BTCOJO5z2.js → config-jyr6AIZt2.js} +3 -3
- package/dist/chunks/{config-BTCOJO5z2.js.map → config-jyr6AIZt2.js.map} +1 -1
- package/dist/chunks/{config-BtXRpAtq.js → config-mlheIMtV.js} +3 -6
- package/dist/chunks/config-mlheIMtV.js.map +1 -0
- package/dist/chunks/{config-BEVBsvni2.js → config-rLkc9Zn2.js} +1 -1
- package/dist/chunks/config-rLkc9Zn2.js.map +1 -0
- package/dist/chunks/{config-T3qdQBgU.js → config-tiUtEn4Q.js} +1 -1
- package/dist/chunks/{config-T3qdQBgU.js.map → config-tiUtEn4Q.js.map} +1 -1
- package/dist/chunks/{config-BViooYNv.js → config-uzUuosv7.js} +7 -2
- package/dist/chunks/config-uzUuosv7.js.map +1 -0
- package/dist/chunks/{config-BbY2SKGE.js → config-vYMJ3Cr1.js} +11 -9
- package/dist/chunks/config-vYMJ3Cr1.js.map +1 -0
- package/dist/chunks/context-BfIQv7m1.js.map +1 -0
- package/dist/chunks/{generated-CJe6hdiE.js → generated-DJ2dJ__C.js} +48 -0
- package/dist/chunks/generated-DJ2dJ__C.js.map +1 -0
- package/dist/chunks/{grBadgeStyles-C1S56Alo.js → grBadgeStyles-D8Mz73B5.js} +4 -4
- package/dist/chunks/grBadgeStyles-D8Mz73B5.js.map +1 -0
- package/dist/chunks/{config-D7zgE_Gr.js → grChipStyles-3JZApw03.js} +21 -43
- package/dist/chunks/grChipStyles-3JZApw03.js.map +1 -0
- package/dist/chunks/{grDropdownMenuStyles-D1LOkdVj.js → grDropdownMenuStyles-DG3aYHkT.js} +3 -3
- package/dist/chunks/grDropdownMenuStyles-DG3aYHkT.js.map +1 -0
- package/dist/chunks/{grIconStyles-BwJrml2L.js → grIconStyles-B6BRqUld.js} +1 -1
- package/dist/chunks/{grIconStyles-BwJrml2L.js.map → grIconStyles-B6BRqUld.js.map} +1 -1
- package/dist/chunks/{grInputStyles-CJeMhUe5.js → grInputStyles-CAk3xKIb.js} +4 -4
- package/dist/chunks/grInputStyles-CAk3xKIb.js.map +1 -0
- package/dist/chunks/{grKbdStyles-CeZhGQHr.js → grKbdStyles-DENu8pUK.js} +5 -0
- package/dist/chunks/grKbdStyles-DENu8pUK.js.map +1 -0
- package/dist/chunks/{grLinkStyles-BoL8QYcV.js → grLinkStyles-FME_qMpi.js} +4 -4
- package/dist/chunks/grLinkStyles-FME_qMpi.js.map +1 -0
- package/dist/chunks/{grLoadingStyles-DEmji2wv.js → grLoadingStyles-CnQaf5ry.js} +1 -1
- package/dist/chunks/{grLoadingStyles-DEmji2wv.js.map → grLoadingStyles-CnQaf5ry.js.map} +1 -1
- package/dist/chunks/{grPopoverStyles-Cke_BZsM.js → grPopoverStyles-Djh5RNhn.js} +4 -4
- package/dist/chunks/grPopoverStyles-Djh5RNhn.js.map +1 -0
- package/dist/chunks/{grRadioStyles-BTjGNhtU.js → grRadioStyles-D4gnU65n.js} +4 -4
- package/dist/chunks/grRadioStyles-D4gnU65n.js.map +1 -0
- package/dist/chunks/{grSelectStyles-eTz4uPMP.js → grSelectStyles-Cdtd10eY.js} +9 -9
- package/dist/chunks/grSelectStyles-Cdtd10eY.js.map +1 -0
- package/dist/chunks/{grTableStyles-C-iaqQUN.js → grTableStyles-uuMPSOBR.js} +2 -2
- package/dist/chunks/{grTableStyles-C-iaqQUN.js.map → grTableStyles-uuMPSOBR.js.map} +1 -1
- package/dist/chunks/{grTextareaStyles-iOcHuVMM.js → grTextareaStyles-Bp35BGna.js} +4 -4
- package/dist/chunks/grTextareaStyles-Bp35BGna.js.map +1 -0
- package/dist/chunks/{granular-provider-BaHkLjFX.js → granular-provider-DvvRayEh.js} +67 -67
- package/dist/chunks/{granular-provider-BaHkLjFX.js.map → granular-provider-DvvRayEh.js.map} +1 -1
- package/dist/chunks/numericStep-Bq2lj4ew.js +23 -0
- package/dist/chunks/numericStep-Bq2lj4ew.js.map +1 -0
- package/dist/chunks/overlayTransition-0Fj5tQbq.js +36 -0
- package/dist/chunks/overlayTransition-0Fj5tQbq.js.map +1 -0
- package/dist/chunks/{safelist-CudBLyAD.js → safelist-BOq5Li7p.js} +1 -1
- package/dist/chunks/{safelist-CudBLyAD.js.map → safelist-BOq5Li7p.js.map} +1 -1
- package/dist/chunks/{safelist-DOfCQR5O.js → safelist-CtiHPgrj.js} +2 -2
- package/dist/chunks/{safelist-DOfCQR5O.js.map → safelist-CtiHPgrj.js.map} +1 -1
- package/dist/chunks/{safelist-CkF4v7DC.js → safelist-Y3zrlhOa.js} +1 -1
- package/dist/chunks/{safelist-CkF4v7DC.js.map → safelist-Y3zrlhOa.js.map} +1 -1
- package/dist/chunks/{useMenuItemsFocus-OyOnIAlr.js → useMenuItemsFocus-B6TldmGf.js} +1 -43
- package/dist/chunks/useMenuItemsFocus-B6TldmGf.js.map +1 -0
- package/dist/chunks/useTypeahead-mxPtPBf2.js +44 -0
- package/dist/chunks/useTypeahead-mxPtPBf2.js.map +1 -0
- package/dist/chunks/{validation-DauXRIkL.js → validation-5HhpanPP.js} +1 -1
- package/dist/chunks/{validation-DauXRIkL.js.map → validation-5HhpanPP.js.map} +1 -1
- package/dist/components/GrAlert/chunks/{GrAlert-B21QGele.js → GrAlert-FAVf-ksH.js} +1 -1
- package/dist/components/GrAlert/chunks/{GrAlert-B21QGele.js.map → GrAlert-FAVf-ksH.js.map} +1 -1
- package/dist/components/GrAlert/index.js +1 -1
- package/dist/components/GrAutocomplete/chunks/{GrAutocomplete-D9c9BNFg.js → GrAutocomplete-BwhUTqmG.js} +88 -81
- package/dist/components/GrAutocomplete/chunks/GrAutocomplete-BwhUTqmG.js.map +1 -0
- package/dist/components/GrAutocomplete/index.js +2 -2
- package/dist/components/GrAutocomplete/styles.css +1 -1
- package/dist/components/GrAvatar/chunks/{GrAvatar-BbdKRX8i.js → GrAvatar-CNmLmgcr.js} +1 -1
- package/dist/components/GrAvatar/chunks/{GrAvatar-BbdKRX8i.js.map → GrAvatar-CNmLmgcr.js.map} +1 -1
- package/dist/components/GrAvatar/index.js +2 -2
- package/dist/components/GrBadge/chunks/{GrBadge-_9rCHuE1.js → GrBadge-BM3f9PME.js} +2 -2
- package/dist/components/GrBadge/chunks/GrBadge-BM3f9PME.js.map +1 -0
- package/dist/components/GrBadge/index.js +3 -3
- package/dist/components/GrBadge/styles.css +1 -1
- package/dist/components/GrBadgeWrap/chunks/{GrBadgeWrap-DDoFQscR.js → GrBadgeWrap-B-A3PRkE.js} +1 -1
- package/dist/components/GrBadgeWrap/chunks/GrBadgeWrap-B-A3PRkE.js.map +1 -0
- package/dist/components/GrBadgeWrap/index.js +2 -2
- package/dist/components/GrBottomNav/chunks/{GrBottomNav-JUu4ylYv.js → GrBottomNav-wtFatLjB.js} +1 -1
- package/dist/components/GrBottomNav/chunks/{GrBottomNav-JUu4ylYv.js.map → GrBottomNav-wtFatLjB.js.map} +1 -1
- package/dist/components/GrBottomNav/index.js +2 -2
- package/dist/components/GrBreadcrumbs/chunks/{GrBreadcrumbs-CQ44iU2o.js → GrBreadcrumbs-jtSnMEtg.js} +2 -2
- package/dist/components/GrBreadcrumbs/chunks/{GrBreadcrumbs-CQ44iU2o.js.map → GrBreadcrumbs-jtSnMEtg.js.map} +1 -1
- package/dist/components/GrBreadcrumbs/index.js +2 -2
- package/dist/components/GrButton/chunks/{GrButton-DCz-Wn-V.js → GrButton-Bw2AH4uX.js} +1 -1
- package/dist/components/GrButton/chunks/GrButton-Bw2AH4uX.js.map +1 -0
- package/dist/components/GrButton/index.js +3 -3
- package/dist/components/GrButtonGroup/chunks/{GrButtonGroup-BQo5VQ_c.js → GrButtonGroup-CKyiAV8K.js} +1 -1
- package/dist/components/GrButtonGroup/chunks/GrButtonGroup-CKyiAV8K.js.map +1 -0
- package/dist/components/GrButtonGroup/index.js +3 -3
- package/dist/components/GrCard/chunks/{GrCard-aQIjk6I9.js → GrCard-DR3ehBt8.js} +1 -1
- package/dist/components/GrCard/chunks/GrCard-DR3ehBt8.js.map +1 -0
- package/dist/components/GrCard/index.js +2 -2
- package/dist/components/GrCheckbox/index.js +1 -1
- package/dist/components/GrCheckboxGroup/index.js +1 -1
- package/dist/components/GrChip/chunks/{GrChip-BnL2FedO.js → GrChip-Df4orxRH.js} +15 -4
- package/dist/components/GrChip/chunks/GrChip-Df4orxRH.js.map +1 -0
- package/dist/components/GrChip/index.js +2 -2
- package/dist/components/GrChipGroup/chunks/{GrChipGroup-DefmSdY9.js → GrChipGroup-C78Vcwos.js} +2 -2
- package/dist/components/GrChipGroup/chunks/{GrChipGroup-DefmSdY9.js.map → GrChipGroup-C78Vcwos.js.map} +1 -1
- package/dist/components/GrChipGroup/index.js +2 -2
- package/dist/components/GrCodeBlock/chunks/{GrCodeBlock-CyptiEo4.js → GrCodeBlock-Dkvkr91t.js} +2 -2
- package/dist/components/GrCodeBlock/chunks/{GrCodeBlock-CyptiEo4.js.map → GrCodeBlock-Dkvkr91t.js.map} +1 -1
- package/dist/components/GrCodeBlock/index.js +2 -2
- package/dist/components/GrCollapse/chunks/{GrCollapse-DUROgAgc.js → GrCollapse-CytiA2Sx.js} +3 -3
- package/dist/components/GrCollapse/chunks/{GrCollapse-DUROgAgc.js.map → GrCollapse-CytiA2Sx.js.map} +1 -1
- package/dist/components/GrCollapse/index.js +2 -2
- package/dist/components/GrColorPicker/chunks/{GrColorPicker-CDaJRVIM.js → GrColorPicker-0n_KGeUy.js} +4 -4
- package/dist/components/GrColorPicker/chunks/{GrColorPicker-CDaJRVIM.js.map → GrColorPicker-0n_KGeUy.js.map} +1 -1
- package/dist/components/GrColorPicker/index.js +2 -2
- package/dist/components/GrCommandPalette/chunks/{GrCommandPalette-BPsVN_XU.js → GrCommandPalette-C5abL2Y7.js} +3 -3
- package/dist/components/GrCommandPalette/chunks/GrCommandPalette-C5abL2Y7.js.map +1 -0
- package/dist/components/GrCommandPalette/index.js +2 -2
- package/dist/components/GrCommandPalette/styles.css +1 -1
- package/dist/components/GrConfigProvider/chunks/GrConfigProvider-BF7R3hgg.js.map +1 -1
- package/dist/components/GrConfirmDialog/chunks/{GrConfirmDialog-CwXNLyzk.js → GrConfirmDialog-B9mIU78E.js} +4 -4
- package/dist/components/GrConfirmDialog/chunks/GrConfirmDialog-B9mIU78E.js.map +1 -0
- package/dist/components/GrConfirmDialog/index.js +1 -1
- package/dist/components/GrContextMenu/chunks/{GrContextMenu-BbAJholy.js → GrContextMenu-DDAUvrDK.js} +3 -3
- package/dist/components/GrContextMenu/chunks/GrContextMenu-DDAUvrDK.js.map +1 -0
- package/dist/components/GrContextMenu/index.js +1 -1
- package/dist/components/GrDataTable/chunks/{GrDataTable-NRbrXpdx.js → GrDataTable-DU00V44y.js} +14 -4
- package/dist/components/GrDataTable/chunks/GrDataTable-DU00V44y.js.map +1 -0
- package/dist/components/GrDataTable/index.js +2 -2
- package/dist/components/GrDelta/chunks/{GrDelta-NE5HhXoo.js → GrDelta-04j9WmEY.js} +0 -0
- package/dist/components/GrDelta/chunks/{GrDelta-NE5HhXoo.js.map → GrDelta-04j9WmEY.js.map} +1 -1
- package/dist/components/GrDelta/index.js +2 -2
- package/dist/components/GrDescriptionList/chunks/{GrDescriptionList-1mICfrqX.js → GrDescriptionList-BMeCgFV3.js} +1 -1
- package/dist/components/GrDescriptionList/chunks/GrDescriptionList-BMeCgFV3.js.map +1 -0
- package/dist/components/GrDescriptionList/index.js +2 -2
- package/dist/components/GrDialog/chunks/{GrDialog-5Y5K87ss.js → GrDialog-bOUDYfmh.js} +2 -2
- package/dist/components/GrDialog/chunks/GrDialog-bOUDYfmh.js.map +1 -0
- package/dist/components/GrDialog/index.js +2 -2
- package/dist/components/GrDialogService/chunks/{GrDialogService-CHhzGRWy.js → GrDialogService-B--br5so.js} +4 -4
- package/dist/components/GrDialogService/chunks/{GrDialogService-CHhzGRWy.js.map → GrDialogService-B--br5so.js.map} +1 -1
- package/dist/components/GrDialogService/index.js +1 -1
- package/dist/components/GrDivider/chunks/{GrDivider-CAhm9jbE.js → GrDivider-BXavGcMX.js} +1 -1
- package/dist/components/GrDivider/chunks/GrDivider-BXavGcMX.js.map +1 -0
- package/dist/components/GrDivider/index.js +2 -2
- package/dist/components/GrDrawer/chunks/{GrDrawer-CXy3-SVd.js → GrDrawer-BCreL-eq.js} +3 -3
- package/dist/components/GrDrawer/chunks/{GrDrawer-CXy3-SVd.js.map → GrDrawer-BCreL-eq.js.map} +1 -1
- package/dist/components/GrDrawer/index.js +2 -2
- package/dist/components/GrDropdown/chunks/{GrDropdown-BfTmdPyh.js → GrDropdown-13Wt9eB_.js} +16 -8
- package/dist/components/GrDropdown/chunks/GrDropdown-13Wt9eB_.js.map +1 -0
- package/dist/components/GrDropdown/index.js +2 -2
- package/dist/components/GrDropdownMenu/chunks/{GrDropdownMenu-l5HEqzjr.js → GrDropdownMenu-5zoOL8Oj.js} +3 -3
- package/dist/components/GrDropdownMenu/chunks/GrDropdownMenu-5zoOL8Oj.js.map +1 -0
- package/dist/components/GrDropdownMenu/chunks/{GrDropdownMenuList-BGA_xNVo.js → GrDropdownMenuList-CT_1TQJo.js} +1 -1
- package/dist/components/GrDropdownMenu/chunks/GrDropdownMenuList-CT_1TQJo.js.map +1 -0
- package/dist/components/GrDropdownMenu/index.js +3 -3
- package/dist/components/GrEmptyState/chunks/{GrEmptyState-CS9do8eM.js → GrEmptyState-D4T5pzXA.js} +2 -2
- package/dist/components/GrEmptyState/chunks/{GrEmptyState-CS9do8eM.js.map → GrEmptyState-D4T5pzXA.js.map} +1 -1
- package/dist/components/GrEmptyState/index.js +2 -2
- package/dist/components/GrFilePreview/chunks/{GrFilePreview-D0Bt-0nn.js → GrFilePreview-C9Z7hMK8.js} +1 -1
- package/dist/components/GrFilePreview/chunks/{GrFilePreview-D0Bt-0nn.js.map → GrFilePreview-C9Z7hMK8.js.map} +1 -1
- package/dist/components/GrFilePreview/index.js +2 -2
- package/dist/components/GrFileUpload/chunks/{GrFileUpload-wNXzHb5N.js → GrFileUpload-FjbaYGjL.js} +2 -2
- package/dist/components/GrFileUpload/chunks/{GrFileUpload-wNXzHb5N.js.map → GrFileUpload-FjbaYGjL.js.map} +1 -1
- package/dist/components/GrFileUpload/index.js +2 -2
- package/dist/components/GrForm/chunks/{GrForm-B_Qq_13k.js → GrForm-CrnfCsgj.js} +9 -3
- package/dist/components/GrForm/chunks/GrForm-CrnfCsgj.js.map +1 -0
- package/dist/components/GrForm/index.js +3 -3
- package/dist/components/GrFormField/chunks/GrFormField-DSylU7IU.js.map +1 -1
- package/dist/components/GrFormField/index.js +1 -1
- package/dist/components/GrFormFile/chunks/{GrFormFile-DhVkB2gm.js → GrFormFile-ChdKlN83.js} +3 -3
- package/dist/components/GrFormFile/chunks/{GrFormFile-DhVkB2gm.js.map → GrFormFile-ChdKlN83.js.map} +1 -1
- package/dist/components/GrFormFile/index.js +2 -2
- package/dist/components/GrFormSection/chunks/{GrFormSection-CdGWvzXO.js → GrFormSection-ejieOg9H.js} +1 -1
- package/dist/components/GrFormSection/chunks/{GrFormSection-CdGWvzXO.js.map → GrFormSection-ejieOg9H.js.map} +1 -1
- package/dist/components/GrFormSection/index.js +2 -2
- package/dist/components/GrIcon/chunks/{GrIcon-bQwtZtYT.js → GrIcon-Bn1XEriL.js} +1 -1
- package/dist/components/GrIcon/chunks/GrIcon-Bn1XEriL.js.map +1 -0
- package/dist/components/GrIcon/index.js +2 -2
- package/dist/components/GrImageViewer/chunks/{GrImageViewer-ng7el4wn.js → GrImageViewer-COUcpOgR.js} +2 -2
- package/dist/components/GrImageViewer/chunks/GrImageViewer-COUcpOgR.js.map +1 -0
- package/dist/components/GrImageViewer/index.js +2 -2
- package/dist/components/GrInput/chunks/{GrInput-Bb_pxqpT.js → GrInput-c53iltz0.js} +1 -1
- package/dist/components/GrInput/chunks/{GrInput-Bb_pxqpT.js.map → GrInput-c53iltz0.js.map} +1 -1
- package/dist/components/GrInput/index.js +2 -2
- package/dist/components/GrInputTag/chunks/{GrInputTag-UI8kIB_F.js → GrInputTag-LP3smqOv.js} +46 -49
- package/dist/components/GrInputTag/chunks/GrInputTag-LP3smqOv.js.map +1 -0
- package/dist/components/GrInputTag/index.js +2 -2
- package/dist/components/GrJsonViewer/chunks/{GrJsonViewer-jk3ylOTc.js → GrJsonViewer-ClTvjlFx.js} +4 -4
- package/dist/components/GrJsonViewer/chunks/{GrJsonViewer-jk3ylOTc.js.map → GrJsonViewer-ClTvjlFx.js.map} +1 -1
- package/dist/components/GrJsonViewer/index.js +2 -2
- package/dist/components/GrKbd/chunks/{GrKbd-BXbhsH0M.js → GrKbd-D2ga1H9i.js} +1 -1
- package/dist/components/GrKbd/chunks/{GrKbd-BXbhsH0M.js.map → GrKbd-D2ga1H9i.js.map} +1 -1
- package/dist/components/GrKbd/index.js +2 -2
- package/dist/components/GrLink/chunks/{GrLink-BQb7U4so.js → GrLink-DAGCFYO7.js} +2 -2
- package/dist/components/GrLink/chunks/GrLink-DAGCFYO7.js.map +1 -0
- package/dist/components/GrLink/index.js +2 -2
- package/dist/components/GrList/chunks/{GrList-B5bclmHV.js → GrList-DrXrKU37.js} +3 -3
- package/dist/components/GrList/chunks/GrList-DrXrKU37.js.map +1 -0
- package/dist/components/GrList/index.js +2 -2
- package/dist/components/GrList/styles.css +1 -1
- package/dist/components/GrLoading/chunks/{GrLoading-DeBFTvFX.js → GrLoading-DDD8WAIH.js} +2 -2
- package/dist/components/GrLoading/chunks/{GrLoading-DeBFTvFX.js.map → GrLoading-DDD8WAIH.js.map} +1 -1
- package/dist/components/GrLoading/index.js +2 -2
- package/dist/components/GrModal/index.js +1 -1
- package/dist/components/GrNavbar/chunks/{GrNavbar-DPj30sHX.js → GrNavbar-DhswlYyh.js} +3 -3
- package/dist/components/GrNavbar/chunks/{GrNavbar-DPj30sHX.js.map → GrNavbar-DhswlYyh.js.map} +1 -1
- package/dist/components/GrNavbar/index.js +2 -2
- package/dist/components/GrNumberInput/chunks/{GrNumberInput-V2mZvDDC.js → GrNumberInput-Daud9Jdm.js} +3 -14
- package/dist/components/GrNumberInput/chunks/GrNumberInput-Daud9Jdm.js.map +1 -0
- package/dist/components/GrNumberInput/index.js +2 -2
- package/dist/components/GrPagination/chunks/{GrPagination-DG7kp6VL.js → GrPagination-CaNTZc-v.js} +3 -3
- package/dist/components/GrPagination/chunks/{GrPagination-DG7kp6VL.js.map → GrPagination-CaNTZc-v.js.map} +1 -1
- package/dist/components/GrPagination/index.js +2 -2
- package/dist/components/GrPopover/chunks/{GrPopover-DDChM1jD.js → GrPopover-CofG-Tfe.js} +16 -8
- package/dist/components/GrPopover/chunks/GrPopover-CofG-Tfe.js.map +1 -0
- package/dist/components/GrPopover/index.js +2 -2
- package/dist/components/GrProgressBar/index.js +1 -1
- package/dist/components/GrProgressCircle/chunks/{GrProgressCircle-BeUx1g1f.js → GrProgressCircle-D3yWXYDa.js} +1 -1
- package/dist/components/GrProgressCircle/chunks/{GrProgressCircle-BeUx1g1f.js.map → GrProgressCircle-D3yWXYDa.js.map} +1 -1
- package/dist/components/GrProgressCircle/index.js +2 -2
- package/dist/components/GrPromptDialog/chunks/{GrPromptDialog-DjF-5oD9.js → GrPromptDialog-DMhALhSS.js} +7 -7
- package/dist/components/GrPromptDialog/chunks/GrPromptDialog-DMhALhSS.js.map +1 -0
- package/dist/components/GrPromptDialog/index.js +1 -1
- package/dist/components/GrRadio/chunks/{GrRadio-BdhknbO8.js → GrRadio-C_rESc5g.js} +1 -1
- package/dist/components/GrRadio/chunks/GrRadio-C_rESc5g.js.map +1 -0
- package/dist/components/GrRadio/index.js +2 -2
- package/dist/components/GrRadioGroup/chunks/{GrRadioGroup-7w4bXIg_.js → GrRadioGroup-CdQO4G-U.js} +2 -2
- package/dist/components/GrRadioGroup/chunks/{GrRadioGroup-7w4bXIg_.js.map → GrRadioGroup-CdQO4G-U.js.map} +1 -1
- package/dist/components/GrRadioGroup/index.js +1 -1
- package/dist/components/GrRating/chunks/{GrRating-BSEtaibx.js → GrRating-IEiTOdnF.js} +1 -1
- package/dist/components/GrRating/chunks/{GrRating-BSEtaibx.js.map → GrRating-IEiTOdnF.js.map} +1 -1
- package/dist/components/GrRating/index.js +2 -2
- package/dist/components/GrResponseErrorBanner/chunks/{GrResponseErrorBanner-DA4q90Zt.js → GrResponseErrorBanner-C_skObtv.js} +1 -1
- package/dist/components/GrResponseErrorBanner/chunks/{GrResponseErrorBanner-DA4q90Zt.js.map → GrResponseErrorBanner-C_skObtv.js.map} +1 -1
- package/dist/components/GrResponseErrorBanner/chunks/{GrResponseErrorBanner-BhP-xw2P.js → GrResponseErrorBanner-DudGc9LO.js} +4 -4
- package/dist/components/GrResponseErrorBanner/chunks/{GrResponseErrorBanner-BhP-xw2P.js.map → GrResponseErrorBanner-DudGc9LO.js.map} +1 -1
- package/dist/components/GrResponseErrorBanner/index.js +2 -2
- package/dist/components/GrSegmented/chunks/{GrSegmented-DKpo3cnC.js → GrSegmented-CB-VR-yL.js} +1 -1
- package/dist/components/GrSegmented/chunks/{GrSegmented-DKpo3cnC.js.map → GrSegmented-CB-VR-yL.js.map} +1 -1
- package/dist/components/GrSegmented/index.js +2 -2
- package/dist/components/GrSelect/chunks/{GrSelect-BA1wfX3c.js → GrSelect-BE1VNTiY.js} +96 -89
- package/dist/components/GrSelect/chunks/GrSelect-BE1VNTiY.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-DY-LgIbS.js → GrSidebar-DxPhIiWs.js} +3 -3
- package/dist/components/GrSidebar/chunks/{GrSidebar-DY-LgIbS.js.map → GrSidebar-DxPhIiWs.js.map} +1 -1
- package/dist/components/GrSidebar/index.js +2 -2
- package/dist/components/GrSlider/chunks/{GrSlider-s99yAyz6.js → GrSlider-DJfeEl4h.js} +5 -4
- package/dist/components/GrSlider/chunks/GrSlider-DJfeEl4h.js.map +1 -0
- package/dist/components/GrSlider/index.js +2 -2
- package/dist/components/GrSortableList/chunks/{GrSortableList-BMXyxmRc.js → GrSortableList-DGs3Pi2J.js} +2 -2
- package/dist/components/GrSortableList/chunks/{GrSortableList-BMXyxmRc.js.map → GrSortableList-DGs3Pi2J.js.map} +1 -1
- package/dist/components/GrSortableList/index.js +2 -2
- package/dist/components/GrSplitter/chunks/{GrSplitter-B9_JrT7q.js → GrSplitter-MltjaADn.js} +1 -1
- package/dist/components/GrSplitter/chunks/{GrSplitter-B9_JrT7q.js.map → GrSplitter-MltjaADn.js.map} +1 -1
- package/dist/components/GrSplitter/index.js +2 -2
- package/dist/components/GrStatistic/chunks/{GrStatistic-BJQv5aOS.js → GrStatistic-qHVasK37.js} +2 -2
- package/dist/components/GrStatistic/chunks/GrStatistic-qHVasK37.js.map +1 -0
- package/dist/components/GrStatistic/index.js +2 -2
- package/dist/components/GrSteps/chunks/{GrSteps-Cce4rfr3.js → GrSteps-C2jxpZoN.js} +1 -1
- package/dist/components/GrSteps/chunks/{GrSteps-Cce4rfr3.js.map → GrSteps-C2jxpZoN.js.map} +1 -1
- package/dist/components/GrSteps/index.js +2 -2
- package/dist/components/GrSwitch/chunks/{GrSwitch-CHiIj9GT.js → GrSwitch-KRlRMwgp.js} +1 -1
- package/dist/components/GrSwitch/chunks/{GrSwitch-CHiIj9GT.js.map → GrSwitch-KRlRMwgp.js.map} +1 -1
- package/dist/components/GrSwitch/index.js +2 -2
- package/dist/components/GrTabPanels/chunks/GrTabPanels-BZQS8-JM.js.map +1 -1
- package/dist/components/GrTable/chunks/{GrTable-f3qCWa99.js → GrTable-Zcdz2St_.js} +1 -1
- package/dist/components/GrTable/chunks/GrTable-Zcdz2St_.js.map +1 -0
- package/dist/components/GrTable/index.js +2 -2
- package/dist/components/GrTabs/chunks/{GrTabs-CBHZkOFY.js → GrTabs-ugDnkk-2.js} +2 -2
- package/dist/components/GrTabs/chunks/{GrTabs-CBHZkOFY.js.map → GrTabs-ugDnkk-2.js.map} +1 -1
- package/dist/components/GrTabs/index.js +2 -2
- package/dist/components/GrTextarea/chunks/{GrTextarea-CNnkcIG1.js → GrTextarea-KREK75Bu.js} +1 -1
- package/dist/components/GrTextarea/chunks/{GrTextarea-CNnkcIG1.js.map → GrTextarea-KREK75Bu.js.map} +1 -1
- package/dist/components/GrTextarea/index.js +2 -2
- package/dist/components/GrTimeline/chunks/{GrTimeline-DIDdkTn7.js → GrTimeline-BiHpArsg.js} +1 -1
- package/dist/components/GrTimeline/chunks/{GrTimeline-DIDdkTn7.js.map → GrTimeline-BiHpArsg.js.map} +1 -1
- package/dist/components/GrTimeline/index.js +2 -2
- package/dist/components/GrToaster/chunks/{GrToaster-CC49q3a4.js → GrToaster-CUi3ldJ0.js} +6 -6
- package/dist/components/GrToaster/chunks/GrToaster-CUi3ldJ0.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-B6AwE_UX.js → GrTooltip-BKFjqu0E.js} +4 -4
- package/dist/components/GrTooltip/chunks/GrTooltip-BKFjqu0E.js.map +1 -0
- package/dist/components/GrTooltip/index.js +2 -2
- package/dist/components/GrTree/chunks/{GrTree-J5q8aCya.js → GrTree-D5ozu6U4.js} +15 -25
- package/dist/components/GrTree/chunks/GrTree-D5ozu6U4.js.map +1 -0
- package/dist/components/GrTree/index.js +1 -1
- package/dist/components/GrTree/styles.css +1 -1
- package/dist/components/GrTreeSelect/chunks/{GrTreeSelect-DEfxWAc3.js → GrTreeSelect-Db_uKdNa.js} +18 -10
- package/dist/components/GrTreeSelect/chunks/GrTreeSelect-Db_uKdNa.js.map +1 -0
- package/dist/components/GrTreeSelect/index.js +2 -2
- package/dist/components/GrValue/chunks/{GrValue-CZsuZ4x-.js → GrValue-ezkQM05Q.js} +1 -1
- package/dist/components/GrValue/chunks/{GrValue-CZsuZ4x-.js.map → GrValue-ezkQM05Q.js.map} +1 -1
- package/dist/components/GrValue/index.js +2 -2
- package/dist/directives/loading.js +1 -1
- package/dist/granular-provider-node.js +1 -1
- package/dist/granular-provider.js +1 -1
- package/dist/index.js +145 -145
- package/dist/styles/tokens.css +11 -0
- package/dist/theme.js +1 -1
- package/dist/tokens.js +1 -1
- package/dist/types/components/GrAutocomplete/GrAutocomplete.vue.d.ts +4 -3
- package/dist/types/components/GrAutocomplete/grAutocompleteStyles.d.ts +2 -2
- package/dist/types/components/GrBadge/GrBadge.vue.d.ts +3 -3
- package/dist/types/components/GrBadgeWrap/GrBadgeWrap.vue.d.ts +3 -3
- package/dist/types/components/GrBadgeWrap/grBadgeWrapStyles.d.ts +1 -1
- package/dist/types/components/GrBottomNav/grBottomNavStyles.d.ts +1 -1
- package/dist/types/components/GrButton/GrButton.vue.d.ts +7 -7
- package/dist/types/components/GrButtonGroup/GrButtonGroup.vue.d.ts +3 -3
- package/dist/types/components/GrCard/GrCard.vue.d.ts +7 -9
- package/dist/types/components/GrCheckbox/GrCheckbox.vue.d.ts +1 -1
- package/dist/types/components/GrCheckboxGroup/GrCheckboxGroup.vue.d.ts +1 -1
- package/dist/types/components/GrChip/GrChip.vue.d.ts +19 -1
- package/dist/types/components/GrChip/grChipStyles.d.ts +10 -6
- package/dist/types/components/GrChipGroup/GrChipGroup.vue.d.ts +2 -2
- package/dist/types/components/GrCollapse/GrCollapseItem.vue.d.ts +9 -11
- package/dist/types/components/GrColorPicker/GrColorPicker.vue.d.ts +1 -1
- package/dist/types/components/GrCommandPalette/GrCommandPalette.vue.d.ts +13 -13
- package/dist/types/components/GrCommandPalette/grCommandPaletteStyles.d.ts +5 -5
- package/dist/types/components/GrConfigProvider/GrConfigProvider.vue.d.ts +3 -3
- package/dist/types/components/GrConfirmDialog/GrConfirmDialog.vue.d.ts +9 -9
- package/dist/types/components/GrContextMenu/GrContextMenu.vue.d.ts +9 -9
- package/dist/types/components/GrDataTable/GrDataTable.vue.d.ts +9 -17
- package/dist/types/components/GrDescriptionList/GrDescriptionList.vue.d.ts +16 -13
- package/dist/types/components/GrDialog/GrDialogFooter.vue.d.ts +3 -3
- package/dist/types/components/GrDivider/GrDivider.vue.d.ts +3 -3
- package/dist/types/components/GrDrawer/grDrawerStyles.d.ts +1 -1
- package/dist/types/components/GrDropdown/GrDropdown.vue.d.ts +16 -20
- package/dist/types/components/GrDropdownMenu/GrDropdownMenu.vue.d.ts +16 -19
- package/dist/types/components/GrDropdownMenu/GrDropdownMenuColumn.vue.d.ts +3 -3
- package/dist/types/components/GrDropdownMenu/GrDropdownMenuColumns.vue.d.ts +3 -3
- package/dist/types/components/GrDropdownMenu/GrDropdownMenuGroup.vue.d.ts +3 -3
- package/dist/types/components/GrDropdownMenu/GrDropdownMenuHeader.vue.d.ts +3 -3
- package/dist/types/components/GrDropdownMenu/GrDropdownMenuItem.vue.d.ts +7 -7
- package/dist/types/components/GrDropdownMenu/GrDropdownMenuList.vue.d.ts +3 -3
- package/dist/types/components/GrDropdownMenu/grDropdownMenuStyles.d.ts +3 -3
- package/dist/types/components/GrForm/GrForm.vue.d.ts +5 -1
- package/dist/types/components/GrForm/index.d.ts +1 -1
- package/dist/types/components/GrFormField/GrFormField.vue.d.ts +11 -11
- package/dist/types/components/GrFormFile/GrFormFile.vue.d.ts +1 -1
- package/dist/types/components/GrFormSection/grFormSectionStyles.d.ts +2 -2
- package/dist/types/components/GrIcon/GrIcon.vue.d.ts +3 -3
- package/dist/types/components/GrImageViewer/GrImageViewer.vue.d.ts +6 -25
- package/dist/types/components/GrInput/GrInput.vue.d.ts +1 -1
- package/dist/types/components/GrInput/grInputStyles.d.ts +4 -4
- package/dist/types/components/GrInputTag/GrInputTag.vue.d.ts +2 -2
- package/dist/types/components/GrInputTag/grInputTagStyles.d.ts +0 -6
- package/dist/types/components/GrKbd/GrKbd.vue.d.ts +3 -3
- package/dist/types/components/GrKbd/grKbdStyles.d.ts +5 -0
- package/dist/types/components/GrLink/GrLink.vue.d.ts +3 -3
- package/dist/types/components/GrList/GrList.vue.d.ts +12 -6
- package/dist/types/components/GrList/GrListItem.vue.d.ts +9 -9
- package/dist/types/components/GrList/grListStyles.d.ts +3 -3
- package/dist/types/components/GrNavbar/grNavbarStyles.d.ts +1 -1
- package/dist/types/components/GrNumberInput/GrNumberInput.vue.d.ts +1 -1
- package/dist/types/components/GrNumberInput/numberInputMath.d.ts +1 -6
- package/dist/types/components/GrPopover/GrPopover.vue.d.ts +16 -17
- package/dist/types/components/GrPromptDialog/GrPromptDialog.vue.d.ts +9 -9
- package/dist/types/components/GrRadio/GrRadio.vue.d.ts +6 -8
- package/dist/types/components/GrRadioGroup/GrRadioGroup.vue.d.ts +1 -1
- package/dist/types/components/GrRating/GrRating.vue.d.ts +1 -1
- package/dist/types/components/GrSegmented/GrSegmented.vue.d.ts +1 -1
- package/dist/types/components/GrSelect/grSelectStyles.d.ts +1 -1
- package/dist/types/components/GrSidebar/GrSidebar.vue.d.ts +7 -7
- package/dist/types/components/GrSidebar/grSidebarStyles.d.ts +4 -4
- package/dist/types/components/GrSlider/GrSlider.vue.d.ts +1 -1
- package/dist/types/components/GrSortableList/grSortableListStyles.d.ts +2 -2
- package/dist/types/components/GrSplitter/GrSplitter.vue.d.ts +1 -1
- package/dist/types/components/GrStatistic/GrStatistic.vue.d.ts +13 -13
- package/dist/types/components/GrSteps/grStepsStyles.d.ts +2 -2
- package/dist/types/components/GrSwitch/GrSwitch.vue.d.ts +1 -1
- package/dist/types/components/GrTabPanels/GrTabPanel.vue.d.ts +3 -3
- package/dist/types/components/GrTabPanels/GrTabPanels.vue.d.ts +3 -3
- package/dist/types/components/GrTable/GrTable.vue.d.ts +13 -13
- package/dist/types/components/GrTextarea/GrTextarea.vue.d.ts +1 -1
- package/dist/types/components/GrTreeSelect/grTreeSelectStyles.d.ts +1 -1
- package/dist/types/components/shared/classTokens.d.ts +8 -0
- package/dist/types/components/shared/numericStep.d.ts +14 -0
- package/dist/types/components/shared/overlayTransition.d.ts +33 -0
- package/dist/web-types.json +292 -218
- package/package.json +2 -2
- package/tokens/foundation.json +41 -1
- package/dist/chunks/classTokens-BzazaERD.js +0 -6
- package/dist/chunks/classTokens-BzazaERD.js.map +0 -1
- package/dist/chunks/config-9PYRZlKE.js.map +0 -1
- package/dist/chunks/config-B4w5gBD0.js.map +0 -1
- package/dist/chunks/config-BEVBsvni2.js.map +0 -1
- package/dist/chunks/config-BN4sXYXn.js.map +0 -1
- package/dist/chunks/config-BViooYNv.js.map +0 -1
- package/dist/chunks/config-B_qi4Fma.js.map +0 -1
- package/dist/chunks/config-Baj7Wvx-.js.map +0 -1
- package/dist/chunks/config-BbY2SKGE.js.map +0 -1
- package/dist/chunks/config-BfsBepkH.js.map +0 -1
- package/dist/chunks/config-Bpt9Xbxm.js.map +0 -1
- package/dist/chunks/config-BsPDQ0dS.js.map +0 -1
- package/dist/chunks/config-BtXRpAtq.js.map +0 -1
- package/dist/chunks/config-BuGlNPE92.js.map +0 -1
- package/dist/chunks/config-C8tVRv8i.js.map +0 -1
- package/dist/chunks/config-CIcynHGr.js.map +0 -1
- package/dist/chunks/config-CN2ULSQL.js.map +0 -1
- package/dist/chunks/config-CN970ZcJ.js.map +0 -1
- package/dist/chunks/config-COvrUfhR.js.map +0 -1
- package/dist/chunks/config-Cl4ST6Qd.js.map +0 -1
- package/dist/chunks/config-Cwd3yjRw.js.map +0 -1
- package/dist/chunks/config-D7zgE_Gr.js.map +0 -1
- package/dist/chunks/config-D94Z9po5.js.map +0 -1
- package/dist/chunks/config-DAX8Deh1.js.map +0 -1
- package/dist/chunks/config-DBmdgoVf.js.map +0 -1
- package/dist/chunks/config-DCFvmHEv.js.map +0 -1
- package/dist/chunks/config-DGWFal5h.js.map +0 -1
- package/dist/chunks/config-DKdlqXiY.js.map +0 -1
- package/dist/chunks/config-DSJY6aEl.js.map +0 -1
- package/dist/chunks/config-DWN4x6_e.js.map +0 -1
- package/dist/chunks/config-Dgj1Q6JY.js.map +0 -1
- package/dist/chunks/config-Dguto_6o.js.map +0 -1
- package/dist/chunks/config-DtgYuGUn2.js.map +0 -1
- package/dist/chunks/config-rkcKArZQ.js.map +0 -1
- package/dist/chunks/config-vSji8w90.js.map +0 -1
- package/dist/chunks/config-zjeAJ4ho2.js.map +0 -1
- package/dist/chunks/context-BfIQv7m12.js.map +0 -1
- package/dist/chunks/generated-CJe6hdiE.js.map +0 -1
- package/dist/chunks/grBadgeStyles-C1S56Alo.js.map +0 -1
- package/dist/chunks/grDropdownMenuStyles-D1LOkdVj.js.map +0 -1
- package/dist/chunks/grInputStyles-CJeMhUe5.js.map +0 -1
- package/dist/chunks/grKbdStyles-CeZhGQHr.js.map +0 -1
- package/dist/chunks/grLinkStyles-BoL8QYcV.js.map +0 -1
- package/dist/chunks/grPopoverStyles-Cke_BZsM.js.map +0 -1
- package/dist/chunks/grRadioStyles-BTjGNhtU.js.map +0 -1
- package/dist/chunks/grSelectStyles-eTz4uPMP.js.map +0 -1
- package/dist/chunks/grTextareaStyles-iOcHuVMM.js.map +0 -1
- package/dist/chunks/useMenuItemsFocus-OyOnIAlr.js.map +0 -1
- package/dist/components/GrAutocomplete/chunks/GrAutocomplete-D9c9BNFg.js.map +0 -1
- package/dist/components/GrBadge/chunks/GrBadge-_9rCHuE1.js.map +0 -1
- package/dist/components/GrBadgeWrap/chunks/GrBadgeWrap-DDoFQscR.js.map +0 -1
- package/dist/components/GrButton/chunks/GrButton-DCz-Wn-V.js.map +0 -1
- package/dist/components/GrButtonGroup/chunks/GrButtonGroup-BQo5VQ_c.js.map +0 -1
- package/dist/components/GrCard/chunks/GrCard-aQIjk6I9.js.map +0 -1
- package/dist/components/GrChip/chunks/GrChip-BnL2FedO.js.map +0 -1
- package/dist/components/GrCommandPalette/chunks/GrCommandPalette-BPsVN_XU.js.map +0 -1
- package/dist/components/GrConfirmDialog/chunks/GrConfirmDialog-CwXNLyzk.js.map +0 -1
- package/dist/components/GrContextMenu/chunks/GrContextMenu-BbAJholy.js.map +0 -1
- package/dist/components/GrDataTable/chunks/GrDataTable-NRbrXpdx.js.map +0 -1
- package/dist/components/GrDescriptionList/chunks/GrDescriptionList-1mICfrqX.js.map +0 -1
- package/dist/components/GrDialog/chunks/GrDialog-5Y5K87ss.js.map +0 -1
- package/dist/components/GrDivider/chunks/GrDivider-CAhm9jbE.js.map +0 -1
- package/dist/components/GrDropdown/chunks/GrDropdown-BfTmdPyh.js.map +0 -1
- package/dist/components/GrDropdownMenu/chunks/GrDropdownMenu-l5HEqzjr.js.map +0 -1
- package/dist/components/GrDropdownMenu/chunks/GrDropdownMenuList-BGA_xNVo.js.map +0 -1
- package/dist/components/GrForm/chunks/GrForm-B_Qq_13k.js.map +0 -1
- package/dist/components/GrIcon/chunks/GrIcon-bQwtZtYT.js.map +0 -1
- package/dist/components/GrImageViewer/chunks/GrImageViewer-ng7el4wn.js.map +0 -1
- package/dist/components/GrInputTag/chunks/GrInputTag-UI8kIB_F.js.map +0 -1
- package/dist/components/GrLink/chunks/GrLink-BQb7U4so.js.map +0 -1
- package/dist/components/GrList/chunks/GrList-B5bclmHV.js.map +0 -1
- package/dist/components/GrNumberInput/chunks/GrNumberInput-V2mZvDDC.js.map +0 -1
- package/dist/components/GrPopover/chunks/GrPopover-DDChM1jD.js.map +0 -1
- package/dist/components/GrPromptDialog/chunks/GrPromptDialog-DjF-5oD9.js.map +0 -1
- package/dist/components/GrRadio/chunks/GrRadio-BdhknbO8.js.map +0 -1
- package/dist/components/GrSelect/chunks/GrSelect-BA1wfX3c.js.map +0 -1
- package/dist/components/GrSlider/chunks/GrSlider-s99yAyz6.js.map +0 -1
- package/dist/components/GrStatistic/chunks/GrStatistic-BJQv5aOS.js.map +0 -1
- package/dist/components/GrTable/chunks/GrTable-f3qCWa99.js.map +0 -1
- package/dist/components/GrToaster/chunks/GrToaster-CC49q3a4.js.map +0 -1
- package/dist/components/GrTooltip/chunks/GrTooltip-B6AwE_UX.js.map +0 -1
- package/dist/components/GrTree/chunks/GrTree-J5q8aCya.js.map +0 -1
- package/dist/components/GrTreeSelect/chunks/GrTreeSelect-DEfxWAc3.js.map +0 -1
- /package/dist/chunks/{config-BuGlNPE92.js → config-BuGlNPE9.js} +0 -0
- /package/dist/chunks/{config-DBmdgoVf.js → config-DBmdgoVf2.js} +0 -0
- /package/dist/chunks/{context-BfIQv7m12.js → context-BfIQv7m1.js} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"GrImageViewer-ng7el4wn.js","names":["$slots"],"sources":["../../../../src/components/GrImageViewer/composables/useZoomPan.ts","../../../../src/components/GrImageViewer/composables/useWheelGesture.ts","../../../../src/components/GrImageViewer/composables/usePointerGestures.ts","../../../../src/components/GrImageViewer/composables/useViewerKeyboard.ts","../../../../src/components/GrImageViewer/GrImageViewer.vue","../../../../src/components/GrImageViewer/GrImageViewer.vue"],"sourcesContent":["import { computed, ref, type Ref } from 'vue'\n\nimport { useDragGesture } from '../../../composables/useDragGesture'\n\nconst TRAILING_ZERO_DECIMAL_RE = /\\.0$/\n\nexport function formatPercent(value: number): string {\n if (Number.isInteger(value))\n return String(value)\n return value.toFixed(1).replace(TRAILING_ZERO_DECIMAL_RE, '')\n}\n\n/** Точка в координатах вьюпорта — курсор или середина между пальцами. */\nexport interface ZoomAnchor {\n clientX: number\n clientY: number\n}\n\nexport interface UseZoomPanOptions {\n minScale: () => number\n maxScale: () => number\n zoomRate: () => number\n /** Тянуть можно и на неувеличенном кадре. Увеличенный тянется всегда. */\n draggable: () => boolean\n /** Ссылка на `<img>` — для измерения layout-размера и подписки ResizeObserver. */\n imageEl: Ref<HTMLImageElement | null>\n /** Область кадра: задаёт систему координат для якорного зума и границы пана. */\n viewportEl: Ref<HTMLElement | null>\n /** Колбэк на поворот (эмит `rotate`). */\n onRotate: (deg: number) => void\n}\n\n/**\n * useZoomPan — состояние и логика масштаба/поворота/панорамирования картинки,\n * а также метрики (натуральный/вписанный/фактический размер, реальный масштаб).\n */\nexport function useZoomPan(options: UseZoomPanOptions) {\n const scale = ref(1)\n const rotation = ref(0)\n const offsetX = ref(0)\n const offsetY = ref(0)\n\n const isDragging = ref(false)\n let dragStartX = 0\n let dragStartY = 0\n let dragOffsetStartX = 0\n let dragOffsetStartY = 0\n\n // Натуральный размер (из `<img>`) и layout-размер вписанного (`object-contain`) изображения.\n const naturalWidth = ref(0)\n const naturalHeight = ref(0)\n const fittedWidth = ref(0)\n const fittedHeight = ref(0)\n\n let resizeObserver: ResizeObserver | null = null\n\n const imageStyle = computed(() => ({\n transform: `translate3d(${offsetX.value}px, ${offsetY.value}px, 0) scale(${scale.value}) rotate(${rotation.value}deg)`,\n }))\n\n const zoomValueText = computed(() => formatPercent(scale.value * 100))\n\n // Фактический размер изображения на экране вдоль его осей (footprint), c учётом scale.\n const renderedWidth = computed(() => Math.round(fittedWidth.value * scale.value))\n const renderedHeight = computed(() => Math.round(fittedHeight.value * scale.value))\n\n // Реальный масштаб относительно натурального размера (доля). Не зависит от rotation.\n const realScale = computed(() => {\n if (!naturalWidth.value)\n return 0\n return (fittedWidth.value * scale.value) / naturalWidth.value\n })\n const realScalePercent = computed(() => formatPercent(realScale.value * 100))\n\n /** Увеличенный кадр тянется всегда: иначе до его краёв не добраться вовсе. */\n const isPannable = computed(() => options.draggable() || scale.value > 1)\n\n /**\n * Граница смещения по осям экрана.\n *\n * Тянуть имеет смысл ровно настолько, насколько кадр вылез за область\n * просмотра, — дальше пользователь утаскивает картинку в пустоту и достаёт её\n * только сбросом. Поворот на 90°/270° меняет оси местами, поэтому границы\n * считаются по bounding box повёрнутого footprint'а.\n */\n function panBounds(): { x: number, y: number } {\n const viewport = options.viewportEl.value\n if (!viewport) return { x: 0, y: 0 }\n\n const quarterTurns = Math.abs(Math.round(rotation.value / 90) % 2)\n const width = quarterTurns === 1 ? renderedHeight.value : renderedWidth.value\n const height = quarterTurns === 1 ? renderedWidth.value : renderedHeight.value\n\n return {\n x: Math.max(0, (width - viewport.clientWidth) / 2),\n y: Math.max(0, (height - viewport.clientHeight) / 2),\n }\n }\n\n function clampOffsets(): void {\n const bounds = panBounds()\n offsetX.value = Math.min(bounds.x, Math.max(-bounds.x, offsetX.value))\n offsetY.value = Math.min(bounds.y, Math.max(-bounds.y, offsetY.value))\n }\n\n function clampScale(value: number): number {\n const clamped = Math.min(options.maxScale(), Math.max(options.minScale(), value))\n return Number(clamped.toFixed(4))\n }\n\n function setScale(value: number): void {\n scale.value = clampScale(value)\n clampOffsets()\n }\n\n /**\n * Масштаб с якорем: точка под курсором (или между пальцами) остаётся на\n * месте. Без этого зум всегда идёт от центра, и увеличить нужный угол кадра\n * можно только зумом с последующим таскиванием.\n */\n function setScaleAt(value: number, anchor?: ZoomAnchor): void {\n const viewport = options.viewportEl.value\n const previous = scale.value\n const next = clampScale(value)\n\n if (next === previous) return\n\n if (anchor && viewport) {\n const rect = viewport.getBoundingClientRect()\n // Координаты якоря относительно центра области — там же начало отсчёта\n // трансформации картинки.\n const anchorX = anchor.clientX - (rect.left + rect.width / 2)\n const anchorY = anchor.clientY - (rect.top + rect.height / 2)\n const ratio = next / previous\n\n offsetX.value = anchorX - (anchorX - offsetX.value) * ratio\n offsetY.value = anchorY - (anchorY - offsetY.value) * ratio\n }\n\n scale.value = next\n clampOffsets()\n }\n\n function zoomIn(anchor?: ZoomAnchor): void {\n setScaleAt(scale.value * options.zoomRate(), anchor)\n }\n\n function zoomOut(anchor?: ZoomAnchor): void {\n setScaleAt(scale.value / options.zoomRate(), anchor)\n }\n\n function rotateLeft(): void {\n rotation.value -= 90\n clampOffsets()\n options.onRotate(rotation.value)\n }\n\n function rotateRight(): void {\n rotation.value += 90\n clampOffsets()\n options.onRotate(rotation.value)\n }\n\n /**\n * Масштаб «один к одному»: пиксель картинки на пиксель экрана.\n *\n * Считается от вписанного размера, потому что `scale` номинальный — единица\n * означает «вписано в окно», а не «натуральный размер». У крупного кадра\n * реальные 100% приходятся на номинальные 6–8×, и вручную такую кнопку\n * потребителю пришлось бы собирать из `naturalWidth`/`renderedWidth`.\n *\n * Потолок `maxScale` действует и здесь: он ограничивает зум сознательно, и\n * «один к одному» — не повод его обойти. Если кадру нужно больше, поднимите\n * `maxScale`.\n */\n function zoomToNatural(): void {\n if (!naturalWidth.value || !fittedWidth.value)\n return\n\n setScale(naturalWidth.value / fittedWidth.value)\n }\n\n function resetTransform(): void {\n scale.value = 1\n rotation.value = 0\n offsetX.value = 0\n offsetY.value = 0\n isDragging.value = false\n }\n\n function resetImageMetrics(): void {\n naturalWidth.value = 0\n naturalHeight.value = 0\n fittedWidth.value = 0\n fittedHeight.value = 0\n }\n\n function measureFitted(): void {\n const el = options.imageEl.value\n if (!el)\n return\n // offsetWidth/Height — layout-размер вписанного изображения, без CSS transform.\n fittedWidth.value = el.offsetWidth\n fittedHeight.value = el.offsetHeight\n clampOffsets()\n }\n\n function onImageLoad(event: Event): void {\n const el = event.target as HTMLImageElement\n naturalWidth.value = el.naturalWidth\n naturalHeight.value = el.naturalHeight\n measureFitted()\n }\n\n function startObservingImage(): void {\n const el = options.imageEl.value\n if (!el || typeof ResizeObserver === 'undefined')\n return\n\n if (!resizeObserver)\n resizeObserver = new ResizeObserver(() => measureFitted())\n\n resizeObserver.observe(el)\n\n // Если картинка уже в кэше и `load` не сработал — считаем метрики сразу.\n if (el.complete && el.naturalWidth) {\n naturalWidth.value = el.naturalWidth\n naturalHeight.value = el.naturalHeight\n }\n\n measureFitted()\n }\n\n function stopObservingImage(): void {\n resizeObserver?.disconnect()\n }\n\n /** Начало перетаскивания: используется и мышью, и жестами. */\n function startPan(x: number, y: number): void {\n isDragging.value = true\n dragStartX = x\n dragStartY = y\n dragOffsetStartX = offsetX.value\n dragOffsetStartY = offsetY.value\n }\n\n function movePan(x: number, y: number): void {\n if (!isDragging.value)\n return\n offsetX.value = dragOffsetStartX + (x - dragStartX)\n offsetY.value = dragOffsetStartY + (y - dragStartY)\n clampOffsets()\n }\n\n function endPan(): void {\n isDragging.value = false\n }\n\n /**\n * Перетаскивание мышью и пером — на общем примитиве жеста.\n *\n * Слушатели живут на `window`, а не на захвате указателя: захват привязан к\n * `<img>`, и смена кадра посреди жеста подменяла элемент — отпускание не\n * приходило никуда, а `isDragging` оставался поднятым.\n */\n const drag = useDragGesture({\n disabled: () => !isPannable.value,\n onStart: (event) => {\n // Тач ведёт `usePointerGestures`: у него свои pinch и свайп, и второй\n // владелец жеста тянул бы кадр дважды.\n if (event.pointerType === 'touch') return false\n\n event.preventDefault()\n startPan(event.clientX, event.clientY)\n },\n onMove: event => movePan(event.clientX, event.clientY),\n onEnd: endPan,\n // Обрыв не откатывает панораму: кадр уже сдвинут пользователем, и возврат\n // к исходной точке читался бы как рывок, а не как отмена.\n onCancel: endPan,\n })\n\n /** Принудительно закончить перетаскивание — закрытие и смена кадра. */\n function cancelPan(): void {\n drag.stop()\n endPan()\n }\n\n return {\n scale,\n rotation,\n offsetX,\n offsetY,\n isDragging,\n isPannable,\n naturalWidth,\n naturalHeight,\n fittedWidth,\n fittedHeight,\n imageStyle,\n zoomValueText,\n renderedWidth,\n renderedHeight,\n realScale,\n realScalePercent,\n setScale,\n setScaleAt,\n zoomIn,\n zoomOut,\n zoomToNatural,\n rotateLeft,\n rotateRight,\n resetTransform,\n resetImageMetrics,\n measureFitted,\n clampOffsets,\n onImageLoad,\n startObservingImage,\n stopObservingImage,\n startPan,\n movePan,\n endPan,\n cancelPan,\n onPointerDown: drag.start,\n }\n}\n","import { ref } from 'vue'\n\nimport type { ZoomAnchor } from './useZoomPan'\n\n// Чувствительность масштабирования колесом/трекпадом: чем больше — тем резче реакция.\nconst WHEEL_ZOOM_SENSITIVITY = 0.0015\n// Сколько ms простоя после последнего wheel-события считаем «зум завершён»\n// (после этого возвращаем плавный CSS-переход).\nconst WHEEL_IDLE_MS = 120\n\nexport interface UseWheelGestureOptions {\n /** Разрешён ли зум колесом (проп `wheelZoom` + наличие изображений). */\n enabled: () => boolean\n /** Применить множитель масштаба (накопленный за кадр) с якорем в точке курсора. */\n applyZoomFactor: (factor: number, anchor: ZoomAnchor) => void\n}\n\n/**\n * useWheelGesture — масштабирование колесом мыши / жестом трекпада с батчингом\n * по `requestAnimationFrame` (рендер не чаще кадра — без «дёрганья»/наслоения) и\n * временным отключением CSS-перехода на время жеста (`isWheelZooming`).\n */\nexport function useWheelGesture(options: UseWheelGestureOptions) {\n const isWheelZooming = ref(false)\n\n let pendingWheelDelta = 0\n let wheelFrameId: number | null = null\n let wheelIdleTimer: ReturnType<typeof setTimeout> | null = null\n // Якорь берётся из последнего события кадра: зум обязан идти в точку под\n // курсором, а не от центра — иначе до угла увеличенного кадра не добраться.\n let anchor: ZoomAnchor = { clientX: 0, clientY: 0 }\n\n function cancelWheelFrame(): void {\n if (wheelFrameId !== null) {\n cancelAnimationFrame(wheelFrameId)\n wheelFrameId = null\n }\n }\n\n function endWheelZoom(): void {\n if (wheelIdleTimer !== null) {\n clearTimeout(wheelIdleTimer)\n wheelIdleTimer = null\n }\n\n isWheelZooming.value = false\n pendingWheelDelta = 0\n cancelWheelFrame()\n }\n\n // Применяем накопленный за кадр deltaY одним обновлением scale.\n function flushWheelZoom(): void {\n wheelFrameId = null\n\n const delta = pendingWheelDelta\n pendingWheelDelta = 0\n\n if (!delta)\n return\n\n // Экспоненциальный шаг — плавно и одинаково для колеса и трекпада.\n // delta < 0 (scroll up) — приближение, delta > 0 — отдаление.\n const factor = Math.exp(-delta * WHEEL_ZOOM_SENSITIVITY)\n options.applyZoomFactor(factor, anchor)\n }\n\n function onWheel(event: WheelEvent): void {\n if (!options.enabled())\n return\n\n // Не даём странице/оверлею проскроллиться — колесо отдаём под зум.\n event.preventDefault()\n\n // Во время непрерывного зума отключаем CSS-переход: плавность даёт само\n // покадровое обновление, а transition на микрошагах вызывает лаг/наслоение.\n isWheelZooming.value = true\n\n pendingWheelDelta += event.deltaY\n anchor = { clientX: event.clientX, clientY: event.clientY }\n\n if (wheelFrameId === null)\n wheelFrameId = requestAnimationFrame(flushWheelZoom)\n\n if (wheelIdleTimer !== null)\n clearTimeout(wheelIdleTimer)\n\n // По завершении жеста возвращаем плавный переход для дискретных зумов.\n wheelIdleTimer = setTimeout(() => {\n wheelIdleTimer = null\n isWheelZooming.value = false\n }, WHEEL_IDLE_MS)\n }\n\n return { isWheelZooming, onWheel, endWheelZoom }\n}\n","import { ref } from 'vue'\n\nimport type { ZoomAnchor } from './useZoomPan'\n\n/** Сколько пикселей должен пройти палец, чтобы это считалось свайпом. */\nconst SWIPE_THRESHOLD_PX = 60\n/** Во сколько раз горизонталь должна превысить вертикаль: иначе это не листание. */\nconst SWIPE_AXIS_RATIO = 1.5\n\nexport interface UsePointerGesturesOptions {\n /** Включены ли жесты вовсе (есть кадры). */\n enabled: () => boolean\n /** Текущий масштаб: на неувеличенном кадре одиночный жест листает, а не тянет. */\n scale: () => number\n /** Есть ли куда листать (кадров больше одного). */\n canSwipe: () => boolean\n /** Масштаб с якорем — тот же, что у колеса. */\n setScaleAt: (value: number, anchor: ZoomAnchor) => void\n pan: {\n start: (x: number, y: number) => void\n move: (x: number, y: number) => void\n end: () => void\n }\n onSwipeLeft: () => void\n onSwipeRight: () => void\n}\n\ninterface TrackedPointer {\n x: number\n y: number\n}\n\n/**\n * usePointerGestures — сенсорные жесты просмотрщика: pinch-zoom двумя пальцами\n * и горизонтальный свайп для листания.\n *\n * Живёт на тех же pointer-событиях, что и перетаскивание мышью: разделение идёт\n * по числу активных указателей, а не по типу устройства — трекпад и стилус\n * ведут себя как мышь, палец как палец, отдельных веток на `touch*` не нужно.\n *\n * Что делает одиночный жест, решает масштаб: на увеличенном кадре тянуть важнее\n * (иначе до краёв не добраться), на вписанном — листать.\n */\nexport function usePointerGestures(options: UsePointerGesturesOptions) {\n const pointers = new Map<number, TrackedPointer>()\n\n /** Идёт ли сейчас pinch: на время жеста компонент гасит CSS-переход. */\n const isPinching = ref(false)\n\n let pinchStartDistance = 0\n let pinchStartScale = 1\n\n let swipeStartX = 0\n let swipeStartY = 0\n let isSwipeCandidate = false\n\n function distance(a: TrackedPointer, b: TrackedPointer): number {\n return Math.hypot(a.x - b.x, a.y - b.y)\n }\n\n function midpoint(a: TrackedPointer, b: TrackedPointer): ZoomAnchor {\n return { clientX: (a.x + b.x) / 2, clientY: (a.y + b.y) / 2 }\n }\n\n function activePair(): [TrackedPointer, TrackedPointer] | null {\n if (pointers.size < 2) return null\n const [first, second] = [...pointers.values()]\n return [first, second]\n }\n\n function onPointerDown(event: PointerEvent): void {\n if (!options.enabled() || event.pointerType === 'mouse')\n return\n\n pointers.set(event.pointerId, { x: event.clientX, y: event.clientY })\n\n const pair = activePair()\n if (pair) {\n // Второй палец превращает начатое перетаскивание в pinch.\n options.pan.end()\n isSwipeCandidate = false\n isPinching.value = true\n pinchStartDistance = distance(pair[0], pair[1]) || 1\n pinchStartScale = options.scale()\n return\n }\n\n if (options.scale() > 1) {\n options.pan.start(event.clientX, event.clientY)\n return\n }\n\n isSwipeCandidate = options.canSwipe()\n swipeStartX = event.clientX\n swipeStartY = event.clientY\n }\n\n function onPointerMove(event: PointerEvent): void {\n if (!pointers.has(event.pointerId))\n return\n\n pointers.set(event.pointerId, { x: event.clientX, y: event.clientY })\n\n const pair = activePair()\n if (pair && isPinching.value) {\n const current = distance(pair[0], pair[1])\n if (!current) return\n\n options.setScaleAt(pinchStartScale * (current / pinchStartDistance), midpoint(pair[0], pair[1]))\n return\n }\n\n if (!isSwipeCandidate)\n options.pan.move(event.clientX, event.clientY)\n }\n\n function finishSwipe(event: PointerEvent): void {\n const deltaX = event.clientX - swipeStartX\n const deltaY = event.clientY - swipeStartY\n\n isSwipeCandidate = false\n\n // Вертикальное движение — это скролл или случайное дрожание, листать по\n // нему нельзя: пользователь целился не туда.\n if (Math.abs(deltaX) < SWIPE_THRESHOLD_PX) return\n if (Math.abs(deltaX) < Math.abs(deltaY) * SWIPE_AXIS_RATIO) return\n\n if (deltaX < 0) options.onSwipeLeft()\n else options.onSwipeRight()\n }\n\n function onPointerUp(event: PointerEvent): void {\n if (!pointers.has(event.pointerId))\n return\n\n const wasSwipeCandidate = isSwipeCandidate\n pointers.delete(event.pointerId)\n\n if (isPinching.value && pointers.size < 2) {\n isPinching.value = false\n // Оставшийся палец продолжает жест уже как перетаскивание.\n const [remaining] = [...pointers.values()]\n if (remaining && options.scale() > 1) options.pan.start(remaining.x, remaining.y)\n return\n }\n\n if (wasSwipeCandidate) {\n finishSwipe(event)\n return\n }\n\n options.pan.end()\n }\n\n /**\n * Обрыв — не отпускание: браузер забрал указатель (системный жест, звонок,\n * потеря окна). Пройденное расстояние при этом не считается свайпом, иначе\n * прерванный жест листает кадр, которого пользователь не листал.\n */\n function onPointerCancel(event: PointerEvent): void {\n if (!pointers.has(event.pointerId))\n return\n\n pointers.delete(event.pointerId)\n isSwipeCandidate = false\n isPinching.value = false\n options.pan.end()\n }\n\n /** Сброс при закрытии, смене кадра и размонтировании. */\n function reset(): void {\n pointers.clear()\n isPinching.value = false\n isSwipeCandidate = false\n }\n\n return { isPinching, onPointerDown, onPointerMove, onPointerUp, onPointerCancel, reset }\n}\n","export interface ViewerKeyboardActions {\n close: () => void\n prev: () => void\n next: () => void\n zoomIn: () => void\n zoomOut: () => void\n reset: () => void\n}\n\nexport interface UseViewerKeyboardOptions {\n actions: ViewerKeyboardActions\n}\n\n/**\n * useViewerKeyboard — клавиатурное управление просмотрщиком: стрелки —\n * переключение, `+`/`-`/`0` — зум/сброс.\n *\n * Esc сюда не входит: он идёт через общий стек dismissible-слоёв\n * (`useDismissible`), иначе просмотрщик, открытый поверх модалки, закрывал бы\n * не себя. Локальный `@keydown` до него всё равно не дошёл бы — стек гасит\n * событие в capture-фазе.\n */\nexport function useViewerKeyboard(options: UseViewerKeyboardOptions) {\n const { actions } = options\n\n function onKeydown(event: KeyboardEvent): void {\n switch (event.key) {\n case 'ArrowLeft':\n event.preventDefault()\n actions.prev()\n return\n\n case 'ArrowRight':\n event.preventDefault()\n actions.next()\n return\n\n case '+':\n case '=':\n case 'Add':\n event.preventDefault()\n actions.zoomIn()\n return\n\n case '-':\n case '_':\n case 'Subtract':\n event.preventDefault()\n actions.zoomOut()\n return\n\n case '0':\n event.preventDefault()\n actions.reset()\n break\n\n default:\n }\n }\n\n return { onKeydown }\n}\n","<script setup lang=\"ts\">\nimport { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'\n\nimport { useAnnouncer } from '../../composables/useAnnouncer'\nimport { useModalOverlay } from '../../composables/internal/useModalOverlay'\n\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport IconChevronLeft from '~icons/lucide/chevron-left'\nimport IconChevronRight from '~icons/lucide/chevron-right'\nimport IconDownload from '~icons/lucide/download'\nimport IconMinus from '~icons/lucide/minus'\nimport IconPlus from '~icons/lucide/plus'\nimport IconRotateCcw from '~icons/lucide/rotate-ccw'\nimport IconRotateCw from '~icons/lucide/rotate-cw'\nimport IconX from '~icons/lucide/x'\n\nimport GrIcon from '../GrIcon/GrIcon.vue'\n\nimport {\n badgeClass,\n chromeButtonClass,\n emptyStateClass,\n scrimClass,\n toolbarButtonClass,\n toolbarSeparatorClass,\n toolbarShellClass,\n} from './grImageViewerStyles'\nimport { useZoomPan } from './composables/useZoomPan'\nimport { useWheelGesture } from './composables/useWheelGesture'\nimport { usePointerGestures } from './composables/usePointerGestures'\nimport { useViewerKeyboard } from './composables/useViewerKeyboard'\n\n/**\n * Usage:\n *\n * <GrImageViewer\n * v-model=\"open\"\n * :url-list=\"images\"\n * :initial-index=\"0\"\n * show-progress\n * hide-on-click-modal\n * />\n *\n * Логика вынесена в composables: `useZoomPan` (масштаб/поворот/пан + метрики),\n * `useWheelGesture` (зум колесом/трекпадом с rAF-батчингом), `useViewerKeyboard`\n * (клавиатура). Сам SFC отвечает за оверлей, индекс изображений и композицию.\n */\n/** Кадр просмотрщика: только адрес или адрес с альтернативным текстом. */\nexport type GrImageViewerItem = { src: string, alt?: string }\nexport type GrImageViewerSource = string | GrImageViewerItem\n\n\nexport interface GrImageViewerProps {\n modelValue: boolean\n /**\n * Кадры. Строка — только адрес; объект `{ src, alt }` даёт изображению\n * альтернативный текст: без него просмотрщик пуст для незрячего\n * пользователя, а придумать текст за потребителя компонент не может.\n */\n urlList: GrImageViewerSource[]\n initialIndex?: number\n zoomRate?: number\n minScale?: number\n maxScale?: number\n hideOnClickModal?: boolean\n closeOnPressEscape?: boolean\n showProgress?: boolean\n showZoomValue?: boolean\n /** Включает масштабирование колесом мыши / жестом на трекпаде. По умолчанию включено. */\n wheelZoom?: boolean\n /** Включает перетаскивание (pan) картинки мышью. При наведении курсор «рука». По умолчанию выключено. */\n draggable?: boolean\n /**\n * Имя CSS-переменной слоя — escape-hatch мимо `--gr-z-modal`. Сырое число\n * компонент не принимает: слой задаётся шкалой, см. `docs/z-index.md`.\n */\n zIndexVar?: string\n /** i18n: aria-label кнопки закрытия. */\n closeLabel?: string\n /** i18n: aria-label кнопки «предыдущее изображение». */\n prevLabel?: string\n /** i18n: aria-label кнопки «следующее изображение». */\n nextLabel?: string\n /** i18n: aria-label кнопки «увеличить». */\n zoomInLabel?: string\n /** i18n: aria-label кнопки «уменьшить». */\n zoomOutLabel?: string\n /** i18n: aria-label кнопки «сбросить масштаб». */\n resetZoomLabel?: string\n /** i18n: aria-label кнопки «до натурального размера». */\n zoomToNaturalLabel?: string\n /** i18n: aria-label кнопки «повернуть влево». */\n rotateLeftLabel?: string\n /** i18n: aria-label кнопки «повернуть вправо». */\n rotateRightLabel?: string\n /** Показывать кнопку скачивания текущего кадра. */\n showDownload?: boolean\n /** i18n: aria-label кнопки «скачать». */\n downloadLabel?: string\n /** i18n: текст в пустом состоянии (нет изображений). */\n emptyText?: string\n /**\n * Доступное имя слоя. Модальный диалог без имени — нарушение\n * `aria-dialog-name`: диктор объявит «диалог» и замолчит.\n */\n ariaLabel?: string\n}\n\nexport interface GrImageViewerEmits {\n (e: 'update:modelValue', value: boolean): void\n (e: 'close'): void\n /** Показан другой кадр. */\n (e: 'change', newIndex: number): void\n (e: 'rotate', deg: number): void\n /** Нажата кнопка скачивания. Само скачивание компонент уже запустил. */\n (e: 'download', payload: { src: string, alt: string, index: number }): void\n}\n\nconst props = withDefaults(\n defineProps<GrImageViewerProps>(),\n {\n initialIndex: 0,\n zoomRate: 1.2,\n minScale: 0.5,\n maxScale: 5,\n hideOnClickModal: false,\n closeOnPressEscape: true,\n showProgress: false,\n showZoomValue: true,\n wheelZoom: true,\n draggable: false,\n zIndexVar: undefined,\n closeLabel: undefined,\n prevLabel: undefined,\n nextLabel: undefined,\n zoomInLabel: undefined,\n zoomOutLabel: undefined,\n resetZoomLabel: undefined,\n zoomToNaturalLabel: undefined,\n rotateLeftLabel: undefined,\n rotateRightLabel: undefined,\n showDownload: false,\n downloadLabel: undefined,\n emptyText: undefined,\n ariaLabel: undefined,\n },\n)\n\nconst { t } = useGranularityTranslations()\nconst resolvedCloseLabel = computed(() => props.closeLabel ?? t('gr.imageViewer.close', 'Close image viewer'))\nconst resolvedPrevLabel = computed(() => props.prevLabel ?? t('gr.imageViewer.prev', 'Previous image'))\nconst resolvedNextLabel = computed(() => props.nextLabel ?? t('gr.imageViewer.next', 'Next image'))\nconst resolvedZoomInLabel = computed(() => props.zoomInLabel ?? t('gr.imageViewer.zoomIn', 'Zoom in'))\nconst resolvedZoomOutLabel = computed(() => props.zoomOutLabel ?? t('gr.imageViewer.zoomOut', 'Zoom out'))\nconst resolvedResetZoomLabel = computed(() => props.resetZoomLabel ?? t('gr.imageViewer.resetZoom', 'Reset zoom'))\nconst resolvedZoomToNaturalLabel = computed(() => props.zoomToNaturalLabel ?? t('gr.imageViewer.zoomToNatural', 'Actual size (1:1)'))\nconst resolvedRotateLeftLabel = computed(() => props.rotateLeftLabel ?? t('gr.imageViewer.rotateLeft', 'Rotate left'))\nconst resolvedRotateRightLabel = computed(() => props.rotateRightLabel ?? t('gr.imageViewer.rotateRight', 'Rotate right'))\nconst resolvedDownloadLabel = computed(() => props.downloadLabel ?? t('gr.imageViewer.download', 'Download image'))\nconst resolvedEmptyText = computed(() => props.emptyText ?? t('gr.imageViewer.empty', 'No image'))\nconst resolvedAriaLabel = computed(() => props.ariaLabel ?? t('gr.imageViewer.label', 'Image viewer'))\n\nconst emit = defineEmits<GrImageViewerEmits>()\n\ntype GrImageViewerToolbarActions = {\n close: () => void\n prev: () => void\n next: () => void\n zoomIn: () => void\n zoomOut: () => void\n /** Масштаб «один к одному»: реальные 100%, а не номинальные. */\n zoomToNatural: () => void\n reset: () => void\n rotateLeft: () => void\n rotateRight: () => void\n /** Скачать текущий кадр — то же, что делает кнопка тулбара. */\n download: () => void\n}\n\ntype GrImageViewerSlotProps = {\n index: number\n /** Текущий кадр — адрес и альтернативный текст. */\n src?: string\n alt: string\n displayIndex: number\n total: number\n scale: number\n rotation: number\n /** Натуральный (исходный) размер картинки, px. 0 пока изображение не загружено. */\n naturalWidth: number\n naturalHeight: number\n /** Фактический размер изображения на экране c учётом scale, px (footprint вдоль осей картинки). */\n renderedWidth: number\n renderedHeight: number\n /** Реальный масштаб относительно натурального размера (доля): renderedWidth / naturalWidth. */\n realScale: number\n /** Реальный масштаб в процентах, отформатированный (например `67`). */\n realScalePercent: string\n actions: GrImageViewerToolbarActions\n}\n\nconst open = computed(() => props.modelValue)\n\nconst items = computed<GrImageViewerItem[]>(() =>\n props.urlList.map(item => (typeof item === 'string' ? { src: item } : item)),\n)\n\nconst total = computed(() => items.value.length)\nconst hasImages = computed(() => total.value > 0)\n\nconst currentIndex = ref(0)\nconst imageEl = ref<HTMLImageElement | null>(null)\n// Область кадра: система координат для якорного зума и границы перетаскивания.\nconst stageEl = ref<HTMLElement | null>(null)\n\n// Масштаб / поворот / панорамирование + метрики изображения.\nconst {\n scale,\n rotation,\n isDragging,\n naturalWidth,\n naturalHeight,\n renderedWidth,\n renderedHeight,\n realScale,\n realScalePercent,\n imageStyle,\n zoomValueText,\n zoomIn,\n zoomOut,\n zoomToNatural,\n rotateLeft,\n rotateRight,\n resetTransform,\n resetImageMetrics,\n onImageLoad,\n startObservingImage,\n stopObservingImage,\n isPannable,\n setScaleAt,\n startPan,\n movePan,\n endPan,\n cancelPan,\n onPointerDown: onPanPointerDown,\n} = useZoomPan({\n minScale: () => props.minScale,\n maxScale: () => props.maxScale,\n zoomRate: () => props.zoomRate,\n draggable: () => props.draggable,\n imageEl,\n viewportEl: stageEl,\n onRotate: deg => emit('rotate', deg),\n})\n\n// Зум колесом мыши / жестом трекпада — с якорем в точке под курсором.\nconst { isWheelZooming, onWheel, endWheelZoom } = useWheelGesture({\n enabled: () => props.wheelZoom && hasImages.value,\n applyZoomFactor: (factor, anchor) => setScaleAt(scale.value * factor, anchor),\n})\n\nconst currentItem = computed(() => (hasImages.value ? items.value[currentIndex.value] : undefined))\nconst currentUrl = computed(() => currentItem.value?.src)\nconst currentAlt = computed(() => currentItem.value?.alt ?? '')\nconst displayIndex = computed(() => (hasImages.value ? currentIndex.value + 1 : 0))\n\n/**\n * Позицию объявляем только на смене кадра: при открытии её скажет имя диалога.\n *\n * Регион общий и живёт вне просмотрщика — он помечен `data-gr-live-region` и\n * потому не гасится `inert`, которым просмотрщик накрывает страницу.\n */\nconst { announce } = useAnnouncer()\n\nwatch(displayIndex, (index) => {\n if (!hasImages.value) return\n\n announce(t('gr.imageViewer.position', 'Image {index} of {total}', { index, total: total.value }))\n})\n\n// Просмотрщик — оверлей модального класса, значит и слой у него модальный.\n// `zIndexVar` подменяет переменную слоя своей — тот же escape-hatch, что у\n// `useFloating` и `GrLoading`.\nconst viewerStyle = computed(() => ({\n zIndex: `var(${props.zIndexVar ?? '--gr-z-modal'})`,\n}))\n\n// Сенсорные жесты: pinch двумя пальцами и свайп для листания. Мышь идёт мимо —\n// у неё своя ветка перетаскивания в `useZoomPan`.\nconst {\n isPinching,\n onPointerDown: onGesturePointerDown,\n onPointerMove: onGesturePointerMove,\n onPointerUp: onGesturePointerUp,\n onPointerCancel: onGesturePointerCancel,\n reset: resetGestures,\n} = usePointerGestures({\n enabled: () => hasImages.value,\n scale: () => scale.value,\n canSwipe: () => total.value > 1,\n setScaleAt,\n pan: { start: startPan, move: movePan, end: endPan },\n onSwipeLeft: () => next(),\n onSwipeRight: () => prev(),\n})\n\nfunction onPointerDown(event: PointerEvent): void {\n onGesturePointerDown(event)\n onPanPointerDown(event)\n}\n\n// Движение и отпускание — только сенсорная ветка: панораму мышью и пером ведёт\n// `useDragGesture` со своими слушателями на `window`.\nfunction onPointerMove(event: PointerEvent): void {\n onGesturePointerMove(event)\n}\n\nfunction onPointerUp(event: PointerEvent): void {\n onGesturePointerUp(event)\n}\n\nfunction onPointerCancel(event: PointerEvent): void {\n onGesturePointerCancel(event)\n}\n\n// Плавный CSS-переход только для дискретных зумов; при wheel-зуме/перетаскивании отключаем.\nconst imageTransitionClass = computed(() =>\n isWheelZooming.value || isDragging.value || isPinching.value\n ? ''\n : 'transition-transform duration-[var(--gr-duration-fast)] ease-[var(--gr-ease-out)]',\n)\n\n// Курсор «рука» (grab/grabbing), только если включён drag.\nconst imageCursorClass = computed(() => {\n if (!isPannable.value)\n return ''\n return isDragging.value ? 'cursor-grabbing' : 'cursor-grab'\n})\n\nfunction normalizeIndex(value: number): number {\n if (!hasImages.value)\n return 0\n const normalized = Number.isFinite(value) ? Math.trunc(value) : 0\n return ((normalized % total.value) + total.value) % total.value\n}\n\nfunction setIndex(nextIndex: number, options?: { emitSwitch?: boolean }): void {\n const normalizedIndex = normalizeIndex(nextIndex)\n if (normalizedIndex === currentIndex.value)\n return\n\n currentIndex.value = normalizedIndex\n resetTransform()\n resetImageMetrics()\n endWheelZoom()\n\n if (options?.emitSwitch !== false)\n emit('change', normalizedIndex)\n}\n\nfunction syncIndexFromInitial(): void {\n if (!hasImages.value) {\n currentIndex.value = 0\n return\n }\n currentIndex.value = normalizeIndex(props.initialIndex)\n resetTransform()\n resetImageMetrics()\n endWheelZoom()\n}\n\n/**\n * Предзагруженные соседи. Держим ссылки, чтобы загрузку можно было оборвать:\n * сброс `src` у `Image` отменяет незавершённый запрос. Без этого быстрое\n * перелистывание галереи копит запросы, которые уже никому не нужны.\n */\nlet preloadedImages: HTMLImageElement[] = []\n\nfunction cancelPreload(): void {\n for (const image of preloadedImages) image.src = ''\n preloadedImages = []\n}\n\nfunction preloadAt(index: number): void {\n const url = items.value[index]?.src\n if (!url)\n return\n const image = new Image()\n image.decoding = 'async'\n image.src = url\n preloadedImages.push(image)\n}\n\nfunction closeViewer(): void {\n emit('update:modelValue', false)\n emit('close')\n}\n\nfunction prev(): void {\n setIndex(currentIndex.value - 1)\n}\n\nfunction next(): void {\n setIndex(currentIndex.value + 1)\n}\n\nconst toolbarActions: GrImageViewerToolbarActions = {\n close: closeViewer,\n prev,\n next,\n zoomIn,\n zoomOut,\n zoomToNatural,\n reset: resetTransform,\n rotateLeft,\n rotateRight,\n download,\n}\n\n// Наружу отдаём тот же набор, что получает слот тулбара. Открытия здесь нет\n// намеренно: оно принадлежит `v-model`, и вторая точка входа рассинхронизировала\n// бы состояние с моделью.\ndefineExpose(toolbarActions)\n\nconst toolbarSlotProps = computed<GrImageViewerSlotProps>(() => ({\n index: currentIndex.value,\n src: currentUrl.value,\n alt: currentAlt.value,\n displayIndex: displayIndex.value,\n total: total.value,\n scale: scale.value,\n rotation: rotation.value,\n naturalWidth: naturalWidth.value,\n naturalHeight: naturalHeight.value,\n renderedWidth: renderedWidth.value,\n renderedHeight: renderedHeight.value,\n realScale: realScale.value,\n realScalePercent: realScalePercent.value,\n actions: toolbarActions,\n}))\n\n/**\n * Скачивание текущего кадра.\n *\n * Компонент делает очевидное — `<a download>` на текущий `src` — и сообщает о\n * нажатии событием. Кросс-доменный адрес браузер всё равно скачает не всегда\n * (атрибут `download` он там игнорирует), поэтому подписанные ссылки и свои\n * запросы делаются иначе: `showDownload: false` плюс своя кнопка в слоте\n * `#toolbar-actions`.\n */\nfunction download(): void {\n const item = currentItem.value\n if (!item?.src) return\n\n emit('download', { src: item.src, alt: item.alt ?? '', index: currentIndex.value })\n\n if (typeof document === 'undefined') return\n\n const link = document.createElement('a')\n link.href = item.src\n link.download = ''\n link.rel = 'noopener'\n document.body.appendChild(link)\n link.click()\n link.remove()\n}\n\nconst { onKeydown } = useViewerKeyboard({\n actions: { close: closeViewer, prev, next, zoomIn, zoomOut, reset: resetTransform },\n})\n\n// Просмотрщик — модальный класс: бэкдроп, scroll-lock, ловушка фокуса, место в\n// общем стеке. Открытый поверх модалки, он закрывает по Esc себя, а не её.\nconst panelEl = ref<HTMLElement | null>(null)\n\nconst {\n rootEl,\n isMounted,\n isVisible,\n inertAttr,\n portalTarget,\n teleportEnabled,\n themeAttrs,\n onPanelAfterLeave: releasePresence,\n backdrop,\n} = useModalOverlay(open, closeViewer, {\n panel: panelEl,\n closeOnEscape: () => props.closeOnPressEscape,\n closeOnBackdrop: () => props.hideOnClickModal,\n})\n\nwatch(\n () => props.modelValue,\n async (isOpen) => {\n if (!isOpen) {\n stopObservingImage()\n endWheelZoom()\n resetGestures()\n cancelPan()\n return\n }\n\n syncIndexFromInitial()\n await nextTick()\n startObservingImage()\n },\n { immediate: true },\n)\n\nwatch(\n () => props.initialIndex,\n () => {\n if (!props.modelValue)\n return\n syncIndexFromInitial()\n },\n)\n\n/**\n * Список кадров меняется у живой галереи постоянно: догрузилась следующая\n * страница, заменили один url. Дёрни на любое такое изменение\n * `syncIndexFromInitial()` — пользователя выбросит на `initialIndex` вместе со\n * сброшенным зумом и поворотом.\n *\n * Держимся кадра, а не позиции: если текущий `src` остался в наборе, идём за\n * ним (и трансформации не трогаем). Исчез — остаёмся на своём месте в списке,\n * прижав индекс к границам, и только тогда сбрасываем трансформации: показываем\n * уже другую картинку.\n */\nwatch(\n items,\n (next, prev) => {\n if (!props.modelValue)\n return\n\n if (!next.length) {\n currentIndex.value = 0\n resetTransform()\n resetImageMetrics()\n endWheelZoom()\n return\n }\n\n const shownSrc = prev?.[currentIndex.value]?.src\n const keptIndex = shownSrc ? next.findIndex(item => item.src === shownSrc) : -1\n\n if (keptIndex >= 0) {\n currentIndex.value = keptIndex\n return\n }\n\n currentIndex.value = Math.min(currentIndex.value, next.length - 1)\n resetTransform()\n resetImageMetrics()\n endWheelZoom()\n },\n { deep: true },\n)\n\n/**\n * Соседние кадры греются только у **открытого** просмотрщика: закрытый на\n * странице не должен тянуть два полноразмерных изображения просто потому, что\n * он там есть. `new Image()` существует только в браузере — на сервере эта\n * ветка не выполняется, потому что закрытый просмотрщик до неё не доходит.\n */\nfunction preloadNeighbours(index: number): void {\n cancelPreload()\n\n if (!props.modelValue || !hasImages.value || total.value < 2)\n return\n\n preloadAt(normalizeIndex(index - 1))\n preloadAt(normalizeIndex(index + 1))\n}\n\nwatch([currentIndex, open], ([index]) => preloadNeighbours(index), { immediate: true })\n\nonBeforeUnmount(() => {\n stopObservingImage()\n endWheelZoom()\n cancelPreload()\n resetGestures()\n cancelPan()\n})\n</script>\n\n<template>\n <teleport :to=\"portalTarget\" :disabled=\"!teleportEnabled\">\n <div\n v-if=\"teleportEnabled && isMounted\"\n ref=\"rootEl\"\n v-bind=\"themeAttrs\"\n data-gr-overlay-root\n :inert=\"inertAttr\"\n class=\"fixed inset-0\"\n :style=\"viewerStyle\"\n role=\"dialog\"\n aria-modal=\"true\"\n :aria-label=\"resolvedAriaLabel\"\n @keydown=\"onKeydown\"\n >\n <div class=\"fixed inset-0 overflow-hidden\">\n <Transition\n appear\n enter-active-class=\"duration-[var(--gr-duration-fast)] ease-[var(--gr-ease-out)]\"\n enter-from-class=\"opacity-0\"\n leave-active-class=\"duration-[var(--gr-duration-fast)] ease-[var(--gr-ease-in)]\"\n leave-to-class=\"opacity-0\"\n >\n <div\n v-if=\"isVisible\"\n data-gr-image-viewer-overlay\n :class=\"scrimClass\"\n aria-hidden=\"true\"\n v-on=\"backdrop\"\n />\n </Transition>\n\n <Transition\n appear\n enter-active-class=\"duration-[var(--gr-duration-base)] ease-[var(--gr-ease-out)]\"\n enter-from-class=\"opacity-0 scale-98\"\n leave-active-class=\"duration-[var(--gr-duration-fast)] ease-[var(--gr-ease-in)]\"\n leave-to-class=\"opacity-0 scale-98\"\n @after-leave=\"releasePresence\"\n >\n <div\n v-if=\"isVisible\"\n ref=\"panelEl\"\n data-gr-image-viewer-panel\n class=\"relative z-10 h-full w-full outline-none\"\n tabindex=\"-1\"\n >\n <div class=\"pointer-events-none absolute inset-x-0 top-0 z-30 px-3 py-3 sm:px-6\">\n <div class=\"pointer-events-auto flex items-center justify-between gap-3\">\n <div class=\"flex items-center gap-2\">\n <div\n v-if=\"showProgress\"\n data-gr-image-viewer-progress\n class=\"font-600\"\n :class=\"badgeClass\"\n >\n {{ displayIndex }} / {{ total }}\n </div>\n\n <div\n v-if=\"showZoomValue\"\n data-gr-image-viewer-zoom-value\n class=\"font-700\"\n :class=\"badgeClass\"\n >\n {{ zoomValueText }}%\n </div>\n </div>\n\n <button\n type=\"button\"\n data-gr-image-viewer-close\n :aria-label=\"resolvedCloseLabel\"\n class=\"h-11 w-11\"\n :class=\"chromeButtonClass\"\n @click=\"closeViewer\"\n >\n <GrIcon size=\"md\">\n <IconX />\n </GrIcon>\n </button>\n </div>\n </div>\n\n <div\n ref=\"stageEl\"\n class=\"relative z-10 h-full w-full flex items-center justify-center px-16 py-16 sm:px-24 sm:py-20\"\n @wheel=\"onWheel\"\n >\n <button\n v-if=\"total > 1\"\n type=\"button\"\n data-gr-image-viewer-prev\n :aria-label=\"resolvedPrevLabel\"\n class=\"absolute left-3 top-1/2 z-20 h-12 w-12 -translate-y-1/2 sm:left-6\"\n :class=\"chromeButtonClass\"\n @click=\"prev\"\n >\n <GrIcon size=\"lg\">\n <IconChevronLeft />\n </GrIcon>\n </button>\n\n <button\n v-if=\"total > 1\"\n type=\"button\"\n data-gr-image-viewer-next\n :aria-label=\"resolvedNextLabel\"\n class=\"absolute right-3 top-1/2 z-20 h-12 w-12 -translate-y-1/2 sm:right-6\"\n :class=\"chromeButtonClass\"\n @click=\"next\"\n >\n <GrIcon size=\"lg\">\n <IconChevronRight />\n </GrIcon>\n </button>\n\n <div class=\"h-full w-full flex items-center justify-center\">\n <img\n v-if=\"currentUrl\"\n ref=\"imageEl\"\n data-gr-image-viewer-image\n :src=\"currentUrl\"\n :alt=\"currentAlt\"\n draggable=\"false\"\n class=\"max-h-full max-w-full select-none object-contain will-change-transform [touch-action:none]\"\n :class=\"[imageTransitionClass, imageCursorClass]\"\n :style=\"imageStyle\"\n @load=\"onImageLoad\"\n @pointerdown=\"onPointerDown\"\n @pointermove=\"onPointerMove\"\n @pointerup=\"onPointerUp\"\n @pointercancel=\"onPointerCancel\"\n >\n\n <div\n v-else\n :class=\"emptyStateClass\"\n >\n {{ resolvedEmptyText }}\n </div>\n </div>\n </div>\n\n <div class=\"pointer-events-none absolute inset-x-0 bottom-0 z-30 px-3 pb-3 sm:px-6 sm:pb-6\">\n <div class=\"pointer-events-auto mx-auto w-full max-w-max\">\n <slot\n name=\"toolbar\"\n v-bind=\"toolbarSlotProps\"\n >\n <div :class=\"toolbarShellClass\">\n <button\n type=\"button\"\n data-gr-image-viewer-zoom-out\n :aria-label=\"resolvedZoomOutLabel\"\n class=\"h-11 min-w-11 px-2 text-[length:var(--gr-text-sm)] leading-[var(--gr-leading-sm)] font-600\"\n :class=\"toolbarButtonClass\"\n @click=\"zoomOut\"\n >\n <GrIcon size=\"sm\">\n <IconMinus />\n </GrIcon>\n </button>\n\n <button\n type=\"button\"\n data-gr-image-viewer-zoom-reset\n :aria-label=\"resolvedResetZoomLabel\"\n class=\"h-11 min-w-11 px-3 text-[length:var(--gr-text-xs)] leading-[var(--gr-leading-xs)] font-700\"\n :class=\"toolbarButtonClass\"\n @click=\"resetTransform\"\n >\n 100%\n </button>\n\n <!--\n «1:1» рядом с «100%» не дубль: сброс возвращает вписанный в\n окно кадр (номинальные 100%), а это — пиксель в пиксель, то\n есть реальные 100%. У крупной фотографии между ними разница\n в разы, и собирать эту кнопку из `naturalWidth`/`renderedWidth`\n каждому потребителю незачем.\n -->\n <button\n type=\"button\"\n data-gr-image-viewer-zoom-natural\n :aria-label=\"resolvedZoomToNaturalLabel\"\n class=\"h-11 min-w-11 px-3 text-[length:var(--gr-text-xs)] leading-[var(--gr-leading-xs)] font-700\"\n :class=\"toolbarButtonClass\"\n @click=\"zoomToNatural\"\n >\n 1:1\n </button>\n\n <button\n type=\"button\"\n data-gr-image-viewer-zoom-in\n :aria-label=\"resolvedZoomInLabel\"\n class=\"h-11 min-w-11 px-2 text-[length:var(--gr-text-sm)] leading-[var(--gr-leading-sm)] font-600\"\n :class=\"toolbarButtonClass\"\n @click=\"zoomIn\"\n >\n <GrIcon size=\"sm\">\n <IconPlus />\n </GrIcon>\n </button>\n\n <button\n v-if=\"showDownload\"\n type=\"button\"\n data-gr-image-viewer-download\n :aria-label=\"resolvedDownloadLabel\"\n class=\"h-11 min-w-11 px-2 text-[length:var(--gr-text-sm)] leading-[var(--gr-leading-sm)] font-600\"\n :class=\"toolbarButtonClass\"\n @click=\"download\"\n >\n <GrIcon size=\"sm\">\n <IconDownload />\n </GrIcon>\n </button>\n\n <template v-if=\"$slots['toolbar-actions']\">\n <div :class=\"toolbarSeparatorClass\" aria-hidden=\"true\" />\n\n <slot\n name=\"toolbar-actions\"\n v-bind=\"toolbarSlotProps\"\n />\n\n <div :class=\"toolbarSeparatorClass\" aria-hidden=\"true\" />\n </template>\n\n <div v-else :class=\"toolbarSeparatorClass\" aria-hidden=\"true\" />\n\n <button\n type=\"button\"\n data-gr-image-viewer-rotate-left\n :aria-label=\"resolvedRotateLeftLabel\"\n class=\"h-11 min-w-11 px-2 text-[length:var(--gr-text-sm)] leading-[var(--gr-leading-sm)] font-600\"\n :class=\"toolbarButtonClass\"\n @click=\"rotateLeft\"\n >\n <GrIcon size=\"sm\">\n <IconRotateCcw />\n </GrIcon>\n </button>\n\n <button\n type=\"button\"\n data-gr-image-viewer-rotate-right\n :aria-label=\"resolvedRotateRightLabel\"\n class=\"h-11 min-w-11 px-2 text-[length:var(--gr-text-sm)] leading-[var(--gr-leading-sm)] font-600\"\n :class=\"toolbarButtonClass\"\n @click=\"rotateRight\"\n >\n <GrIcon size=\"sm\">\n <IconRotateCw />\n </GrIcon>\n </button>\n </div>\n </slot>\n </div>\n </div>\n </div>\n </Transition>\n </div>\n </div>\n </teleport>\n</template>","<script setup lang=\"ts\">\nimport { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'\n\nimport { useAnnouncer } from '../../composables/useAnnouncer'\nimport { useModalOverlay } from '../../composables/internal/useModalOverlay'\n\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport IconChevronLeft from '~icons/lucide/chevron-left'\nimport IconChevronRight from '~icons/lucide/chevron-right'\nimport IconDownload from '~icons/lucide/download'\nimport IconMinus from '~icons/lucide/minus'\nimport IconPlus from '~icons/lucide/plus'\nimport IconRotateCcw from '~icons/lucide/rotate-ccw'\nimport IconRotateCw from '~icons/lucide/rotate-cw'\nimport IconX from '~icons/lucide/x'\n\nimport GrIcon from '../GrIcon/GrIcon.vue'\n\nimport {\n badgeClass,\n chromeButtonClass,\n emptyStateClass,\n scrimClass,\n toolbarButtonClass,\n toolbarSeparatorClass,\n toolbarShellClass,\n} from './grImageViewerStyles'\nimport { useZoomPan } from './composables/useZoomPan'\nimport { useWheelGesture } from './composables/useWheelGesture'\nimport { usePointerGestures } from './composables/usePointerGestures'\nimport { useViewerKeyboard } from './composables/useViewerKeyboard'\n\n/**\n * Usage:\n *\n * <GrImageViewer\n * v-model=\"open\"\n * :url-list=\"images\"\n * :initial-index=\"0\"\n * show-progress\n * hide-on-click-modal\n * />\n *\n * Логика вынесена в composables: `useZoomPan` (масштаб/поворот/пан + метрики),\n * `useWheelGesture` (зум колесом/трекпадом с rAF-батчингом), `useViewerKeyboard`\n * (клавиатура). Сам SFC отвечает за оверлей, индекс изображений и композицию.\n */\n/** Кадр просмотрщика: только адрес или адрес с альтернативным текстом. */\nexport type GrImageViewerItem = { src: string, alt?: string }\nexport type GrImageViewerSource = string | GrImageViewerItem\n\n\nexport interface GrImageViewerProps {\n modelValue: boolean\n /**\n * Кадры. Строка — только адрес; объект `{ src, alt }` даёт изображению\n * альтернативный текст: без него просмотрщик пуст для незрячего\n * пользователя, а придумать текст за потребителя компонент не может.\n */\n urlList: GrImageViewerSource[]\n initialIndex?: number\n zoomRate?: number\n minScale?: number\n maxScale?: number\n hideOnClickModal?: boolean\n closeOnPressEscape?: boolean\n showProgress?: boolean\n showZoomValue?: boolean\n /** Включает масштабирование колесом мыши / жестом на трекпаде. По умолчанию включено. */\n wheelZoom?: boolean\n /** Включает перетаскивание (pan) картинки мышью. При наведении курсор «рука». По умолчанию выключено. */\n draggable?: boolean\n /**\n * Имя CSS-переменной слоя — escape-hatch мимо `--gr-z-modal`. Сырое число\n * компонент не принимает: слой задаётся шкалой, см. `docs/z-index.md`.\n */\n zIndexVar?: string\n /** i18n: aria-label кнопки закрытия. */\n closeLabel?: string\n /** i18n: aria-label кнопки «предыдущее изображение». */\n prevLabel?: string\n /** i18n: aria-label кнопки «следующее изображение». */\n nextLabel?: string\n /** i18n: aria-label кнопки «увеличить». */\n zoomInLabel?: string\n /** i18n: aria-label кнопки «уменьшить». */\n zoomOutLabel?: string\n /** i18n: aria-label кнопки «сбросить масштаб». */\n resetZoomLabel?: string\n /** i18n: aria-label кнопки «до натурального размера». */\n zoomToNaturalLabel?: string\n /** i18n: aria-label кнопки «повернуть влево». */\n rotateLeftLabel?: string\n /** i18n: aria-label кнопки «повернуть вправо». */\n rotateRightLabel?: string\n /** Показывать кнопку скачивания текущего кадра. */\n showDownload?: boolean\n /** i18n: aria-label кнопки «скачать». */\n downloadLabel?: string\n /** i18n: текст в пустом состоянии (нет изображений). */\n emptyText?: string\n /**\n * Доступное имя слоя. Модальный диалог без имени — нарушение\n * `aria-dialog-name`: диктор объявит «диалог» и замолчит.\n */\n ariaLabel?: string\n}\n\nexport interface GrImageViewerEmits {\n (e: 'update:modelValue', value: boolean): void\n (e: 'close'): void\n /** Показан другой кадр. */\n (e: 'change', newIndex: number): void\n (e: 'rotate', deg: number): void\n /** Нажата кнопка скачивания. Само скачивание компонент уже запустил. */\n (e: 'download', payload: { src: string, alt: string, index: number }): void\n}\n\nconst props = withDefaults(\n defineProps<GrImageViewerProps>(),\n {\n initialIndex: 0,\n zoomRate: 1.2,\n minScale: 0.5,\n maxScale: 5,\n hideOnClickModal: false,\n closeOnPressEscape: true,\n showProgress: false,\n showZoomValue: true,\n wheelZoom: true,\n draggable: false,\n zIndexVar: undefined,\n closeLabel: undefined,\n prevLabel: undefined,\n nextLabel: undefined,\n zoomInLabel: undefined,\n zoomOutLabel: undefined,\n resetZoomLabel: undefined,\n zoomToNaturalLabel: undefined,\n rotateLeftLabel: undefined,\n rotateRightLabel: undefined,\n showDownload: false,\n downloadLabel: undefined,\n emptyText: undefined,\n ariaLabel: undefined,\n },\n)\n\nconst { t } = useGranularityTranslations()\nconst resolvedCloseLabel = computed(() => props.closeLabel ?? t('gr.imageViewer.close', 'Close image viewer'))\nconst resolvedPrevLabel = computed(() => props.prevLabel ?? t('gr.imageViewer.prev', 'Previous image'))\nconst resolvedNextLabel = computed(() => props.nextLabel ?? t('gr.imageViewer.next', 'Next image'))\nconst resolvedZoomInLabel = computed(() => props.zoomInLabel ?? t('gr.imageViewer.zoomIn', 'Zoom in'))\nconst resolvedZoomOutLabel = computed(() => props.zoomOutLabel ?? t('gr.imageViewer.zoomOut', 'Zoom out'))\nconst resolvedResetZoomLabel = computed(() => props.resetZoomLabel ?? t('gr.imageViewer.resetZoom', 'Reset zoom'))\nconst resolvedZoomToNaturalLabel = computed(() => props.zoomToNaturalLabel ?? t('gr.imageViewer.zoomToNatural', 'Actual size (1:1)'))\nconst resolvedRotateLeftLabel = computed(() => props.rotateLeftLabel ?? t('gr.imageViewer.rotateLeft', 'Rotate left'))\nconst resolvedRotateRightLabel = computed(() => props.rotateRightLabel ?? t('gr.imageViewer.rotateRight', 'Rotate right'))\nconst resolvedDownloadLabel = computed(() => props.downloadLabel ?? t('gr.imageViewer.download', 'Download image'))\nconst resolvedEmptyText = computed(() => props.emptyText ?? t('gr.imageViewer.empty', 'No image'))\nconst resolvedAriaLabel = computed(() => props.ariaLabel ?? t('gr.imageViewer.label', 'Image viewer'))\n\nconst emit = defineEmits<GrImageViewerEmits>()\n\ntype GrImageViewerToolbarActions = {\n close: () => void\n prev: () => void\n next: () => void\n zoomIn: () => void\n zoomOut: () => void\n /** Масштаб «один к одному»: реальные 100%, а не номинальные. */\n zoomToNatural: () => void\n reset: () => void\n rotateLeft: () => void\n rotateRight: () => void\n /** Скачать текущий кадр — то же, что делает кнопка тулбара. */\n download: () => void\n}\n\ntype GrImageViewerSlotProps = {\n index: number\n /** Текущий кадр — адрес и альтернативный текст. */\n src?: string\n alt: string\n displayIndex: number\n total: number\n scale: number\n rotation: number\n /** Натуральный (исходный) размер картинки, px. 0 пока изображение не загружено. */\n naturalWidth: number\n naturalHeight: number\n /** Фактический размер изображения на экране c учётом scale, px (footprint вдоль осей картинки). */\n renderedWidth: number\n renderedHeight: number\n /** Реальный масштаб относительно натурального размера (доля): renderedWidth / naturalWidth. */\n realScale: number\n /** Реальный масштаб в процентах, отформатированный (например `67`). */\n realScalePercent: string\n actions: GrImageViewerToolbarActions\n}\n\nconst open = computed(() => props.modelValue)\n\nconst items = computed<GrImageViewerItem[]>(() =>\n props.urlList.map(item => (typeof item === 'string' ? { src: item } : item)),\n)\n\nconst total = computed(() => items.value.length)\nconst hasImages = computed(() => total.value > 0)\n\nconst currentIndex = ref(0)\nconst imageEl = ref<HTMLImageElement | null>(null)\n// Область кадра: система координат для якорного зума и границы перетаскивания.\nconst stageEl = ref<HTMLElement | null>(null)\n\n// Масштаб / поворот / панорамирование + метрики изображения.\nconst {\n scale,\n rotation,\n isDragging,\n naturalWidth,\n naturalHeight,\n renderedWidth,\n renderedHeight,\n realScale,\n realScalePercent,\n imageStyle,\n zoomValueText,\n zoomIn,\n zoomOut,\n zoomToNatural,\n rotateLeft,\n rotateRight,\n resetTransform,\n resetImageMetrics,\n onImageLoad,\n startObservingImage,\n stopObservingImage,\n isPannable,\n setScaleAt,\n startPan,\n movePan,\n endPan,\n cancelPan,\n onPointerDown: onPanPointerDown,\n} = useZoomPan({\n minScale: () => props.minScale,\n maxScale: () => props.maxScale,\n zoomRate: () => props.zoomRate,\n draggable: () => props.draggable,\n imageEl,\n viewportEl: stageEl,\n onRotate: deg => emit('rotate', deg),\n})\n\n// Зум колесом мыши / жестом трекпада — с якорем в точке под курсором.\nconst { isWheelZooming, onWheel, endWheelZoom } = useWheelGesture({\n enabled: () => props.wheelZoom && hasImages.value,\n applyZoomFactor: (factor, anchor) => setScaleAt(scale.value * factor, anchor),\n})\n\nconst currentItem = computed(() => (hasImages.value ? items.value[currentIndex.value] : undefined))\nconst currentUrl = computed(() => currentItem.value?.src)\nconst currentAlt = computed(() => currentItem.value?.alt ?? '')\nconst displayIndex = computed(() => (hasImages.value ? currentIndex.value + 1 : 0))\n\n/**\n * Позицию объявляем только на смене кадра: при открытии её скажет имя диалога.\n *\n * Регион общий и живёт вне просмотрщика — он помечен `data-gr-live-region` и\n * потому не гасится `inert`, которым просмотрщик накрывает страницу.\n */\nconst { announce } = useAnnouncer()\n\nwatch(displayIndex, (index) => {\n if (!hasImages.value) return\n\n announce(t('gr.imageViewer.position', 'Image {index} of {total}', { index, total: total.value }))\n})\n\n// Просмотрщик — оверлей модального класса, значит и слой у него модальный.\n// `zIndexVar` подменяет переменную слоя своей — тот же escape-hatch, что у\n// `useFloating` и `GrLoading`.\nconst viewerStyle = computed(() => ({\n zIndex: `var(${props.zIndexVar ?? '--gr-z-modal'})`,\n}))\n\n// Сенсорные жесты: pinch двумя пальцами и свайп для листания. Мышь идёт мимо —\n// у неё своя ветка перетаскивания в `useZoomPan`.\nconst {\n isPinching,\n onPointerDown: onGesturePointerDown,\n onPointerMove: onGesturePointerMove,\n onPointerUp: onGesturePointerUp,\n onPointerCancel: onGesturePointerCancel,\n reset: resetGestures,\n} = usePointerGestures({\n enabled: () => hasImages.value,\n scale: () => scale.value,\n canSwipe: () => total.value > 1,\n setScaleAt,\n pan: { start: startPan, move: movePan, end: endPan },\n onSwipeLeft: () => next(),\n onSwipeRight: () => prev(),\n})\n\nfunction onPointerDown(event: PointerEvent): void {\n onGesturePointerDown(event)\n onPanPointerDown(event)\n}\n\n// Движение и отпускание — только сенсорная ветка: панораму мышью и пером ведёт\n// `useDragGesture` со своими слушателями на `window`.\nfunction onPointerMove(event: PointerEvent): void {\n onGesturePointerMove(event)\n}\n\nfunction onPointerUp(event: PointerEvent): void {\n onGesturePointerUp(event)\n}\n\nfunction onPointerCancel(event: PointerEvent): void {\n onGesturePointerCancel(event)\n}\n\n// Плавный CSS-переход только для дискретных зумов; при wheel-зуме/перетаскивании отключаем.\nconst imageTransitionClass = computed(() =>\n isWheelZooming.value || isDragging.value || isPinching.value\n ? ''\n : 'transition-transform duration-[var(--gr-duration-fast)] ease-[var(--gr-ease-out)]',\n)\n\n// Курсор «рука» (grab/grabbing), только если включён drag.\nconst imageCursorClass = computed(() => {\n if (!isPannable.value)\n return ''\n return isDragging.value ? 'cursor-grabbing' : 'cursor-grab'\n})\n\nfunction normalizeIndex(value: number): number {\n if (!hasImages.value)\n return 0\n const normalized = Number.isFinite(value) ? Math.trunc(value) : 0\n return ((normalized % total.value) + total.value) % total.value\n}\n\nfunction setIndex(nextIndex: number, options?: { emitSwitch?: boolean }): void {\n const normalizedIndex = normalizeIndex(nextIndex)\n if (normalizedIndex === currentIndex.value)\n return\n\n currentIndex.value = normalizedIndex\n resetTransform()\n resetImageMetrics()\n endWheelZoom()\n\n if (options?.emitSwitch !== false)\n emit('change', normalizedIndex)\n}\n\nfunction syncIndexFromInitial(): void {\n if (!hasImages.value) {\n currentIndex.value = 0\n return\n }\n currentIndex.value = normalizeIndex(props.initialIndex)\n resetTransform()\n resetImageMetrics()\n endWheelZoom()\n}\n\n/**\n * Предзагруженные соседи. Держим ссылки, чтобы загрузку можно было оборвать:\n * сброс `src` у `Image` отменяет незавершённый запрос. Без этого быстрое\n * перелистывание галереи копит запросы, которые уже никому не нужны.\n */\nlet preloadedImages: HTMLImageElement[] = []\n\nfunction cancelPreload(): void {\n for (const image of preloadedImages) image.src = ''\n preloadedImages = []\n}\n\nfunction preloadAt(index: number): void {\n const url = items.value[index]?.src\n if (!url)\n return\n const image = new Image()\n image.decoding = 'async'\n image.src = url\n preloadedImages.push(image)\n}\n\nfunction closeViewer(): void {\n emit('update:modelValue', false)\n emit('close')\n}\n\nfunction prev(): void {\n setIndex(currentIndex.value - 1)\n}\n\nfunction next(): void {\n setIndex(currentIndex.value + 1)\n}\n\nconst toolbarActions: GrImageViewerToolbarActions = {\n close: closeViewer,\n prev,\n next,\n zoomIn,\n zoomOut,\n zoomToNatural,\n reset: resetTransform,\n rotateLeft,\n rotateRight,\n download,\n}\n\n// Наружу отдаём тот же набор, что получает слот тулбара. Открытия здесь нет\n// намеренно: оно принадлежит `v-model`, и вторая точка входа рассинхронизировала\n// бы состояние с моделью.\ndefineExpose(toolbarActions)\n\nconst toolbarSlotProps = computed<GrImageViewerSlotProps>(() => ({\n index: currentIndex.value,\n src: currentUrl.value,\n alt: currentAlt.value,\n displayIndex: displayIndex.value,\n total: total.value,\n scale: scale.value,\n rotation: rotation.value,\n naturalWidth: naturalWidth.value,\n naturalHeight: naturalHeight.value,\n renderedWidth: renderedWidth.value,\n renderedHeight: renderedHeight.value,\n realScale: realScale.value,\n realScalePercent: realScalePercent.value,\n actions: toolbarActions,\n}))\n\n/**\n * Скачивание текущего кадра.\n *\n * Компонент делает очевидное — `<a download>` на текущий `src` — и сообщает о\n * нажатии событием. Кросс-доменный адрес браузер всё равно скачает не всегда\n * (атрибут `download` он там игнорирует), поэтому подписанные ссылки и свои\n * запросы делаются иначе: `showDownload: false` плюс своя кнопка в слоте\n * `#toolbar-actions`.\n */\nfunction download(): void {\n const item = currentItem.value\n if (!item?.src) return\n\n emit('download', { src: item.src, alt: item.alt ?? '', index: currentIndex.value })\n\n if (typeof document === 'undefined') return\n\n const link = document.createElement('a')\n link.href = item.src\n link.download = ''\n link.rel = 'noopener'\n document.body.appendChild(link)\n link.click()\n link.remove()\n}\n\nconst { onKeydown } = useViewerKeyboard({\n actions: { close: closeViewer, prev, next, zoomIn, zoomOut, reset: resetTransform },\n})\n\n// Просмотрщик — модальный класс: бэкдроп, scroll-lock, ловушка фокуса, место в\n// общем стеке. Открытый поверх модалки, он закрывает по Esc себя, а не её.\nconst panelEl = ref<HTMLElement | null>(null)\n\nconst {\n rootEl,\n isMounted,\n isVisible,\n inertAttr,\n portalTarget,\n teleportEnabled,\n themeAttrs,\n onPanelAfterLeave: releasePresence,\n backdrop,\n} = useModalOverlay(open, closeViewer, {\n panel: panelEl,\n closeOnEscape: () => props.closeOnPressEscape,\n closeOnBackdrop: () => props.hideOnClickModal,\n})\n\nwatch(\n () => props.modelValue,\n async (isOpen) => {\n if (!isOpen) {\n stopObservingImage()\n endWheelZoom()\n resetGestures()\n cancelPan()\n return\n }\n\n syncIndexFromInitial()\n await nextTick()\n startObservingImage()\n },\n { immediate: true },\n)\n\nwatch(\n () => props.initialIndex,\n () => {\n if (!props.modelValue)\n return\n syncIndexFromInitial()\n },\n)\n\n/**\n * Список кадров меняется у живой галереи постоянно: догрузилась следующая\n * страница, заменили один url. Дёрни на любое такое изменение\n * `syncIndexFromInitial()` — пользователя выбросит на `initialIndex` вместе со\n * сброшенным зумом и поворотом.\n *\n * Держимся кадра, а не позиции: если текущий `src` остался в наборе, идём за\n * ним (и трансформации не трогаем). Исчез — остаёмся на своём месте в списке,\n * прижав индекс к границам, и только тогда сбрасываем трансформации: показываем\n * уже другую картинку.\n */\nwatch(\n items,\n (next, prev) => {\n if (!props.modelValue)\n return\n\n if (!next.length) {\n currentIndex.value = 0\n resetTransform()\n resetImageMetrics()\n endWheelZoom()\n return\n }\n\n const shownSrc = prev?.[currentIndex.value]?.src\n const keptIndex = shownSrc ? next.findIndex(item => item.src === shownSrc) : -1\n\n if (keptIndex >= 0) {\n currentIndex.value = keptIndex\n return\n }\n\n currentIndex.value = Math.min(currentIndex.value, next.length - 1)\n resetTransform()\n resetImageMetrics()\n endWheelZoom()\n },\n { deep: true },\n)\n\n/**\n * Соседние кадры греются только у **открытого** просмотрщика: закрытый на\n * странице не должен тянуть два полноразмерных изображения просто потому, что\n * он там есть. `new Image()` существует только в браузере — на сервере эта\n * ветка не выполняется, потому что закрытый просмотрщик до неё не доходит.\n */\nfunction preloadNeighbours(index: number): void {\n cancelPreload()\n\n if (!props.modelValue || !hasImages.value || total.value < 2)\n return\n\n preloadAt(normalizeIndex(index - 1))\n preloadAt(normalizeIndex(index + 1))\n}\n\nwatch([currentIndex, open], ([index]) => preloadNeighbours(index), { immediate: true })\n\nonBeforeUnmount(() => {\n stopObservingImage()\n endWheelZoom()\n cancelPreload()\n resetGestures()\n cancelPan()\n})\n</script>\n\n<template>\n <teleport :to=\"portalTarget\" :disabled=\"!teleportEnabled\">\n <div\n v-if=\"teleportEnabled && isMounted\"\n ref=\"rootEl\"\n v-bind=\"themeAttrs\"\n data-gr-overlay-root\n :inert=\"inertAttr\"\n class=\"fixed inset-0\"\n :style=\"viewerStyle\"\n role=\"dialog\"\n aria-modal=\"true\"\n :aria-label=\"resolvedAriaLabel\"\n @keydown=\"onKeydown\"\n >\n <div class=\"fixed inset-0 overflow-hidden\">\n <Transition\n appear\n enter-active-class=\"duration-[var(--gr-duration-fast)] ease-[var(--gr-ease-out)]\"\n enter-from-class=\"opacity-0\"\n leave-active-class=\"duration-[var(--gr-duration-fast)] ease-[var(--gr-ease-in)]\"\n leave-to-class=\"opacity-0\"\n >\n <div\n v-if=\"isVisible\"\n data-gr-image-viewer-overlay\n :class=\"scrimClass\"\n aria-hidden=\"true\"\n v-on=\"backdrop\"\n />\n </Transition>\n\n <Transition\n appear\n enter-active-class=\"duration-[var(--gr-duration-base)] ease-[var(--gr-ease-out)]\"\n enter-from-class=\"opacity-0 scale-98\"\n leave-active-class=\"duration-[var(--gr-duration-fast)] ease-[var(--gr-ease-in)]\"\n leave-to-class=\"opacity-0 scale-98\"\n @after-leave=\"releasePresence\"\n >\n <div\n v-if=\"isVisible\"\n ref=\"panelEl\"\n data-gr-image-viewer-panel\n class=\"relative z-10 h-full w-full outline-none\"\n tabindex=\"-1\"\n >\n <div class=\"pointer-events-none absolute inset-x-0 top-0 z-30 px-3 py-3 sm:px-6\">\n <div class=\"pointer-events-auto flex items-center justify-between gap-3\">\n <div class=\"flex items-center gap-2\">\n <div\n v-if=\"showProgress\"\n data-gr-image-viewer-progress\n class=\"font-600\"\n :class=\"badgeClass\"\n >\n {{ displayIndex }} / {{ total }}\n </div>\n\n <div\n v-if=\"showZoomValue\"\n data-gr-image-viewer-zoom-value\n class=\"font-700\"\n :class=\"badgeClass\"\n >\n {{ zoomValueText }}%\n </div>\n </div>\n\n <button\n type=\"button\"\n data-gr-image-viewer-close\n :aria-label=\"resolvedCloseLabel\"\n class=\"h-11 w-11\"\n :class=\"chromeButtonClass\"\n @click=\"closeViewer\"\n >\n <GrIcon size=\"md\">\n <IconX />\n </GrIcon>\n </button>\n </div>\n </div>\n\n <div\n ref=\"stageEl\"\n class=\"relative z-10 h-full w-full flex items-center justify-center px-16 py-16 sm:px-24 sm:py-20\"\n @wheel=\"onWheel\"\n >\n <button\n v-if=\"total > 1\"\n type=\"button\"\n data-gr-image-viewer-prev\n :aria-label=\"resolvedPrevLabel\"\n class=\"absolute left-3 top-1/2 z-20 h-12 w-12 -translate-y-1/2 sm:left-6\"\n :class=\"chromeButtonClass\"\n @click=\"prev\"\n >\n <GrIcon size=\"lg\">\n <IconChevronLeft />\n </GrIcon>\n </button>\n\n <button\n v-if=\"total > 1\"\n type=\"button\"\n data-gr-image-viewer-next\n :aria-label=\"resolvedNextLabel\"\n class=\"absolute right-3 top-1/2 z-20 h-12 w-12 -translate-y-1/2 sm:right-6\"\n :class=\"chromeButtonClass\"\n @click=\"next\"\n >\n <GrIcon size=\"lg\">\n <IconChevronRight />\n </GrIcon>\n </button>\n\n <div class=\"h-full w-full flex items-center justify-center\">\n <img\n v-if=\"currentUrl\"\n ref=\"imageEl\"\n data-gr-image-viewer-image\n :src=\"currentUrl\"\n :alt=\"currentAlt\"\n draggable=\"false\"\n class=\"max-h-full max-w-full select-none object-contain will-change-transform [touch-action:none]\"\n :class=\"[imageTransitionClass, imageCursorClass]\"\n :style=\"imageStyle\"\n @load=\"onImageLoad\"\n @pointerdown=\"onPointerDown\"\n @pointermove=\"onPointerMove\"\n @pointerup=\"onPointerUp\"\n @pointercancel=\"onPointerCancel\"\n >\n\n <div\n v-else\n :class=\"emptyStateClass\"\n >\n {{ resolvedEmptyText }}\n </div>\n </div>\n </div>\n\n <div class=\"pointer-events-none absolute inset-x-0 bottom-0 z-30 px-3 pb-3 sm:px-6 sm:pb-6\">\n <div class=\"pointer-events-auto mx-auto w-full max-w-max\">\n <slot\n name=\"toolbar\"\n v-bind=\"toolbarSlotProps\"\n >\n <div :class=\"toolbarShellClass\">\n <button\n type=\"button\"\n data-gr-image-viewer-zoom-out\n :aria-label=\"resolvedZoomOutLabel\"\n class=\"h-11 min-w-11 px-2 text-[length:var(--gr-text-sm)] leading-[var(--gr-leading-sm)] font-600\"\n :class=\"toolbarButtonClass\"\n @click=\"zoomOut\"\n >\n <GrIcon size=\"sm\">\n <IconMinus />\n </GrIcon>\n </button>\n\n <button\n type=\"button\"\n data-gr-image-viewer-zoom-reset\n :aria-label=\"resolvedResetZoomLabel\"\n class=\"h-11 min-w-11 px-3 text-[length:var(--gr-text-xs)] leading-[var(--gr-leading-xs)] font-700\"\n :class=\"toolbarButtonClass\"\n @click=\"resetTransform\"\n >\n 100%\n </button>\n\n <!--\n «1:1» рядом с «100%» не дубль: сброс возвращает вписанный в\n окно кадр (номинальные 100%), а это — пиксель в пиксель, то\n есть реальные 100%. У крупной фотографии между ними разница\n в разы, и собирать эту кнопку из `naturalWidth`/`renderedWidth`\n каждому потребителю незачем.\n -->\n <button\n type=\"button\"\n data-gr-image-viewer-zoom-natural\n :aria-label=\"resolvedZoomToNaturalLabel\"\n class=\"h-11 min-w-11 px-3 text-[length:var(--gr-text-xs)] leading-[var(--gr-leading-xs)] font-700\"\n :class=\"toolbarButtonClass\"\n @click=\"zoomToNatural\"\n >\n 1:1\n </button>\n\n <button\n type=\"button\"\n data-gr-image-viewer-zoom-in\n :aria-label=\"resolvedZoomInLabel\"\n class=\"h-11 min-w-11 px-2 text-[length:var(--gr-text-sm)] leading-[var(--gr-leading-sm)] font-600\"\n :class=\"toolbarButtonClass\"\n @click=\"zoomIn\"\n >\n <GrIcon size=\"sm\">\n <IconPlus />\n </GrIcon>\n </button>\n\n <button\n v-if=\"showDownload\"\n type=\"button\"\n data-gr-image-viewer-download\n :aria-label=\"resolvedDownloadLabel\"\n class=\"h-11 min-w-11 px-2 text-[length:var(--gr-text-sm)] leading-[var(--gr-leading-sm)] font-600\"\n :class=\"toolbarButtonClass\"\n @click=\"download\"\n >\n <GrIcon size=\"sm\">\n <IconDownload />\n </GrIcon>\n </button>\n\n <template v-if=\"$slots['toolbar-actions']\">\n <div :class=\"toolbarSeparatorClass\" aria-hidden=\"true\" />\n\n <slot\n name=\"toolbar-actions\"\n v-bind=\"toolbarSlotProps\"\n />\n\n <div :class=\"toolbarSeparatorClass\" aria-hidden=\"true\" />\n </template>\n\n <div v-else :class=\"toolbarSeparatorClass\" aria-hidden=\"true\" />\n\n <button\n type=\"button\"\n data-gr-image-viewer-rotate-left\n :aria-label=\"resolvedRotateLeftLabel\"\n class=\"h-11 min-w-11 px-2 text-[length:var(--gr-text-sm)] leading-[var(--gr-leading-sm)] font-600\"\n :class=\"toolbarButtonClass\"\n @click=\"rotateLeft\"\n >\n <GrIcon size=\"sm\">\n <IconRotateCcw />\n </GrIcon>\n </button>\n\n <button\n type=\"button\"\n data-gr-image-viewer-rotate-right\n :aria-label=\"resolvedRotateRightLabel\"\n class=\"h-11 min-w-11 px-2 text-[length:var(--gr-text-sm)] leading-[var(--gr-leading-sm)] font-600\"\n :class=\"toolbarButtonClass\"\n @click=\"rotateRight\"\n >\n <GrIcon size=\"sm\">\n <IconRotateCw />\n </GrIcon>\n </button>\n </div>\n </slot>\n </div>\n </div>\n </div>\n </Transition>\n </div>\n </div>\n </teleport>\n</template>"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"GrInputTag-UI8kIB_F.js","names":[],"sources":["../../../../src/components/GrInputTag/GrInputTag.vue","../../../../src/components/GrInputTag/GrInputTag.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, nextTick, ref } from 'vue'\n\nimport GrBadge from '../GrBadge/GrBadge.vue'\nimport type { GrBadgeRadius, GrBadgeSize, GrBadgeTone } from '../GrBadge'\nimport GrIcon from '../GrIcon/GrIcon.vue'\nimport IconClose from '~icons/lucide/x'\nimport IconLoader from '~icons/lucide/loader-2'\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\nimport { useControlAddons } from '../../composables/internal/useControlAddons'\nimport { useGrFormFieldContext } from '../GrFormField/context'\nimport { useAnnouncer } from '../../composables/useAnnouncer'\nimport { useRovingFocus } from '../../composables/useRovingFocus'\nimport { useGrFormControl } from '../../composables/useGrFormControl'\nimport { useFocusWithin } from '../../composables/internal/useFocusWithin'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { isComposingEvent } from '../../internal/keyboard'\n\nimport {\n clearButtonClass,\n grInputTagInputClass,\n grInputTagWrapperClass,\n removeButtonClass,\n spinnerClass,\n type GrInputTagSize,\n type GrInputTagState,\n} from './grInputTagStyles'\n\nexport type { GrInputTagSize, GrInputTagState } from './grInputTagStyles'\n\nconst REGEX_SPECIAL_CHAR_RE = /[.*+?^${}()|[\\]\\\\]/g\n\n/**\n * Props for {@link GrInputTag}.\n * Tag-input primitive: renders a set of badges with an inline `<input>` for entering new tags.\n * Splits input by configurable `separators`, supports paste, Enter and Backspace editing.\n */\nexport interface GrInputTagProps {\n modelValue: string[]\n placeholder?: string\n disabled?: boolean\n readonly?: boolean\n invalid?: boolean\n /** Обязательное поле (`aria-required`). Складывается с `required` у `GrFormField`. */\n required?: boolean\n state?: GrInputTagState\n size?: GrInputTagSize\n separators?: string[]\n allowDuplicates?: boolean\n trim?: boolean\n max?: number\n addOnBlur?: boolean\n clearInputOnAdd?: boolean\n /**\n * Проверка тега перед добавлением. Может быть асинхронной (проверка на\n * сервере). Отклонённый тег не добавляется и уходит в событие `reject`.\n */\n beforeAdd?: (tag: string) => boolean | Promise<boolean>\n /** Кнопка «снести все теги». Настраивается через `GrConfigProvider`. */\n clearable?: boolean\n /** Фоновая работа: спиннер + `aria-busy`. Асинхронный `beforeAdd` поднимает его сам. */\n loading?: boolean\n tagTone?: GrBadgeTone\n tagDark?: boolean\n tagSize?: GrBadgeSize\n tagRadius?: GrBadgeRadius\n tagClosable?: boolean\n /** i18n-friendly aria-label for the per-tag remove button. */\n removeTagLabel?: string\n /** i18n aria-label кнопки «снести все». */\n clearAllLabel?: string\n /**\n * Имя контрола, когда он используется вне `GrFormField`. Внутри поля имя даёт\n * связка с его `<label for>` — там проп не нужен. Без того и другого у инпута\n * нет доступного имени вовсе: placeholder именем не считается.\n */\n ariaLabel?: string\n /** Имя для нативной формы: hidden input на каждый тег. */\n name?: string\n /**\n * Ширины аддонов `prefix`/`suffix` — общий контракт контролов пакета\n * (`docs/form-controls.md`).\n */\n prefixMinWidth?: string\n prefixMaxWidth?: string\n suffixMinWidth?: string\n suffixMaxWidth?: string\n prefixFixed?: boolean\n suffixFixed?: boolean\n}\n\nexport interface GrInputTagEmits {\n (e: 'update:modelValue', value: string[]): void\n (e: 'add', value: string): void\n (e: 'remove', value: string, index: number): void\n /** Тег не прошёл `beforeAdd`. */\n (e: 'reject', value: string): void\n /** Набор снесён кнопкой «очистить». */\n (e: 'clear'): void\n /** Набор тегов изменился. */\n (e: 'change', value: string[]): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n}\n\nconst props = withDefaults(\n defineProps<GrInputTagProps>(),\n {\n placeholder: undefined,\n disabled: false,\n readonly: false,\n invalid: false,\n required: false,\n state: 'default',\n // Настраивается через `GrConfigProvider`; дефолт — в резолвере ниже.\n size: undefined,\n separators: () => [','],\n allowDuplicates: false,\n trim: true,\n max: undefined,\n addOnBlur: false,\n clearInputOnAdd: true,\n beforeAdd: undefined,\n clearable: undefined,\n loading: false,\n tagTone: 'neutral',\n tagDark: false,\n tagSize: 'md',\n tagRadius: 'round',\n tagClosable: true,\n removeTagLabel: undefined,\n clearAllLabel: undefined,\n ariaLabel: undefined,\n name: undefined,\n },\n)\n\nconst emit = defineEmits<GrInputTagEmits>()\ndefineSlots<{\n /** Аддон слева от чипов: иконка, метка. */\n prefix?: () => any\n /** Аддон справа, перед кнопкой очистки. */\n suffix?: () => any\n /** Свой чип: `remove` снимает значение. */\n tag?: (props: { tag: string, index: number, remove: () => void }) => any\n}>()\n\n\n// Fallback из контекста `GrFormField` (id/aria-describedby/invalid/required) —\n// как у GrInput и GrSelect: контрол не знает про форму, знает только про поле.\nconst field = useGrFormFieldContext()\nconst resolvedId = computed(() => field?.id.value)\nconst describedBy = computed(() => field?.describedById.value)\nconst {\n disabled: isDisabled,\n invalid: isInvalid,\n required: isRequired,\n readonly: isReadonly,\n} = useGrFormControl(() => props)\n\n// Эффективные значения: локальный проп → `GrConfigProvider` → дефолт компонента.\n/** Ширина аддона по умолчанию — та же лестница, что у `GrInput`. */\nconst ADDON_MIN_WIDTH_BY_SIZE: Record<GrInputTagSize, string> = {\n xs: '2rem',\n sm: '2.25rem',\n md: '2.5rem',\n lg: '3rem',\n}\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrInputTag' })\n\n/**\n * Аддоны — флекс-соседи поля: оболочка тегового поля и так флекс-строка с\n * бейджами, поэтому места паддингом резервировать не нужно.\n */\nconst { hasPrefix, hasSuffix, prefixEl, suffixEl, prefixStyle, suffixStyle } = useControlAddons(() => props, {\n defaultMinWidth: () => ADDON_MIN_WIDTH_BY_SIZE[resolvedSize.value],\n paddingX: () => '0px',\n})\nconst resolvedClearable = useGrComponentProp('GrInputTag', 'clearable', () => props.clearable, false)\n\nconst { t } = useGranularityTranslations()\nconst resolvedClearAllLabel = computed(() => props.clearAllLabel ?? t('gr.inputTag.clearAll', 'Clear all tags'))\n\nfunction removeTagLabelFor(tag: string): string {\n // Безликое «Remove tag» на двадцати кнопках подряд не даёт выбрать нужную:\n // имя обязано называть сам тег.\n return props.removeTagLabel ?? t('gr.inputTag.removeTagNamed', 'Remove tag {tag}', { tag })\n}\n\nconst inputValue = ref('')\nconst inputEl = ref<HTMLInputElement | null>(null)\nconst rootEl = ref<HTMLElement | null>(null)\n\n// Внутри корня фокус ходит между полем и кнопками удаления чипов: без границы\n// каждое такое перемещение давало бы потребителю пару `blur` + `focus`.\nconst { onFocusIn, onFocusOut } = useFocusWithin(rootEl, {\n enter: event => emit('focus', event),\n leave: event => emit('blur', event),\n})\nconst removeEls = ref<HTMLButtonElement[]>([])\n\nfunction setRemoveEl(index: number) {\n return (el: unknown): void => {\n removeEls.value[index] = el as HTMLButtonElement\n }\n}\n\nfunction focus(): void {\n inputEl.value?.focus()\n}\n\nfunction blur(): void {\n inputEl.value?.blur()\n}\n\nconst isMaxed = computed(() => props.max !== undefined && props.modelValue.length >= props.max)\nconst canEdit = computed(() => !isDisabled.value && !isReadonly.value)\nconst showRemove = computed(() => props.tagClosable && canEdit.value)\n\n// Добавление, удаление и достигнутый предел — события, а не состояние виджета:\n// показывать их негде, объявить нужно. Это работа общего живого региона.\nconst { announce } = useAnnouncer()\n\n/**\n * Roving tabindex по крестикам: двадцать тегов давали двадцать одну остановку\n * `Tab`. В таб-порядке ровно один крестик, между ними ходят стрелками.\n *\n * Адресация индексом, а не значением тега: теги допускают повторы, и по\n * значению два одинаковых чипа было бы не различить.\n */\nconst tagIndexes = computed(() => props.modelValue.map((_, index) => index))\n\nconst roving = useRovingFocus<number>({\n items: () => tagIndexes.value,\n elementFor: index => removeEls.value[index],\n orientation: () => 'horizontal',\n // Ряд чипов — не кольцо: у него есть край, и за краем стоит поле ввода.\n wrap: () => false,\n onOverflow: (edge) => {\n // За последним чипом — поле ввода: продолжение того же ряда. Слева от\n // первого — начало строки, фокус остаётся на месте.\n if (edge === 'end') focus()\n return true\n },\n // Удаление чипа перерисовывает ряд: без ожидания фокус уехал бы на узел,\n // которого уже нет.\n beforeFocus: () => nextTick(),\n})\n\nasync function focusTag(index: number): Promise<void> {\n const last = props.modelValue.length - 1\n if (last < 0) {\n focus()\n return\n }\n\n await roving.focusKey(Math.min(Math.max(index, 0), last))\n}\n\nfunction escapeRegexChar(ch: string): string {\n return ch.replace(REGEX_SPECIAL_CHAR_RE, '\\\\$&')\n}\n\nconst splitRegex = computed(() => {\n const seps = (props.separators ?? []).filter(Boolean).map(s => s[0]!)\n const uniq = [...new Set([...seps, '\\n', '\\r', '\\t'])]\n const body = uniq.map(escapeRegexChar).join('')\n return new RegExp(`[${body}]+`, 'g')\n})\n\nfunction normalizeTag(raw: string): string {\n return props.trim ? raw.trim() : raw\n}\n\nfunction splitToTags(raw: string): string[] {\n return raw\n .split(splitRegex.value)\n .map(v => normalizeTag(v))\n .filter(Boolean)\n}\n\nfunction addMany(rawTags: string[]): boolean {\n if (!canEdit.value || isMaxed.value)\n return false\n\n const next = props.modelValue.slice()\n const added: string[] = []\n\n for (const raw of rawTags) {\n const tag = normalizeTag(raw)\n if (!tag)\n continue\n\n if (!props.allowDuplicates && next.includes(tag))\n continue\n\n if (props.max !== undefined && next.length >= props.max)\n break\n\n next.push(tag)\n added.push(tag)\n emit('add', tag)\n }\n\n if (!added.length)\n return false\n\n emit('update:modelValue', next)\n emit('change', next)\n announce(added.length === 1\n ? t('gr.inputTag.added', 'Tag added: {tag}', { tag: added[0]! })\n : t('gr.inputTag.addedMany', '{count} tags added', { count: added.length }))\n\n return true\n}\n\n// Счётчик запуска: пока идёт асинхронная проверка, пользователь успевает нажать\n// Enter второй раз — результат устаревшей проверки дописывать нельзя.\nlet validationRun = 0\nconst validating = ref(false)\nconst isBusy = computed(() => props.loading || validating.value)\n\nasync function filterAllowed(tags: string[], run: number): Promise<string[] | null> {\n const check = props.beforeAdd\n if (!check)\n return tags\n\n const allowed: string[] = []\n\n for (const tag of tags) {\n const ok = await check(tag)\n if (run !== validationRun)\n return null\n\n if (ok)\n allowed.push(tag)\n else\n emit('reject', tag)\n }\n\n return allowed\n}\n\nasync function commitInput(): Promise<void> {\n if (!canEdit.value || !inputValue.value)\n return\n\n if (isMaxed.value) {\n announce(t('gr.inputTag.limitReached', 'Tag limit reached'))\n return\n }\n\n const raw = inputValue.value\n const tags = splitToTags(raw)\n if (!tags.length)\n return\n\n const run = ++validationRun\n validating.value = Boolean(props.beforeAdd)\n\n const allowed = await filterAllowed(tags, run)\n\n if (run === validationRun)\n validating.value = false\n\n if (allowed === null || !allowed.length)\n return\n\n const changed = addMany(allowed)\n // Значение могло измениться, пока шла проверка: стирать чужой ввод нельзя.\n if (changed && props.clearInputOnAdd && inputValue.value === raw)\n inputValue.value = ''\n}\n\nfunction removeAt(index: number): void {\n if (!canEdit.value)\n return\n\n const next = props.modelValue.slice()\n const removed = next[index]\n if (removed === undefined)\n return\n\n next.splice(index, 1)\n emit('update:modelValue', next)\n emit('change', next)\n emit('remove', removed, index)\n announce(t('gr.inputTag.removed', 'Tag removed: {tag}', { tag: removed }))\n}\n\n/** Удаление с клавиатуры: фокус переезжает на соседний чип, а не пропадает. */\nasync function removeAtFromKeyboard(index: number): Promise<void> {\n removeAt(index)\n await nextTick()\n\n if (props.modelValue.length === 0) {\n focus()\n return\n }\n\n await focusTag(Math.min(index, props.modelValue.length - 1))\n}\n\nfunction clearAll(): void {\n if (!canEdit.value || props.modelValue.length === 0)\n return\n\n emit('update:modelValue', [])\n emit('change', [])\n emit('clear')\n announce(t('gr.inputTag.cleared', 'All tags removed'))\n focus()\n}\n\nfunction onInput(e: Event): void {\n inputValue.value = (e.target as HTMLInputElement).value\n}\n\nfunction onKeydown(e: KeyboardEvent): void {\n // Enter/сепаратор во время IME-композиции коммитят её, а не тег.\n if (isComposingEvent(e))\n return\n if (!canEdit.value)\n return\n\n if (e.key === 'Enter') {\n e.preventDefault()\n void commitInput()\n return\n }\n\n if ((props.separators ?? []).includes(e.key)) {\n e.preventDefault()\n void commitInput()\n return\n }\n\n if (inputValue.value.length === 0 && props.modelValue.length > 0) {\n if (e.key === 'Backspace') {\n e.preventDefault()\n removeAt(props.modelValue.length - 1)\n return\n }\n\n // Из пустого поля стрелка влево уводит на последний чип — дальше по чипам\n // работают те же стрелки, `Tab` для этого не нужен.\n if (e.key === 'ArrowLeft') {\n e.preventDefault()\n void focusTag(props.modelValue.length - 1)\n }\n }\n}\n\nfunction onTagKeydown(e: KeyboardEvent, index: number): void {\n if (roving.handleNavigationKeys(e)) {\n return\n }\n\n if (e.key === 'Delete' || e.key === 'Backspace') {\n e.preventDefault()\n void removeAtFromKeyboard(index)\n }\n}\n\nfunction onBlur(): void {\n if (props.addOnBlur)\n void commitInput()\n}\n\nfunction onPaste(e: ClipboardEvent): void {\n if (!canEdit.value || isMaxed.value)\n return\n\n const pasted = e.clipboardData?.getData('text')\n if (!pasted)\n return\n\n const tags = splitToTags(pasted)\n if (tags.length <= 1)\n return\n\n e.preventDefault()\n\n const run = ++validationRun\n validating.value = Boolean(props.beforeAdd)\n\n void filterAllowed(tags, run).then((allowed) => {\n if (run === validationRun)\n validating.value = false\n\n if (allowed?.length)\n addMany(allowed)\n })\n}\n\nconst wrapperClassName = computed(() => {\n return grInputTagWrapperClass({\n size: resolvedSize.value,\n state: props.state,\n invalid: isInvalid.value,\n disabled: isDisabled.value,\n })\n})\n\nconst inputClassName = computed(() => grInputTagInputClass(resolvedSize.value))\n\nconst showClearAll = computed(() => resolvedClearable.value && canEdit.value && props.modelValue.length > 0)\n\nconst placeholderText = computed(() => props.modelValue.length > 0 ? undefined : props.placeholder)\n\ndefineExpose({ focus, blur, clear: clearAll })\n</script>\n\n<template>\n <div\n ref=\"rootEl\"\n data-gr-input-tag\n data-testid=\"gr-input-tag\"\n :class=\"wrapperClassName\"\n @click=\"focus\"\n @focusin=\"onFocusIn\"\n @focusout=\"onFocusOut\"\n >\n <span\n v-if=\"hasPrefix\"\n ref=\"prefixEl\"\n data-gr-input-tag-prefix\n class=\"inline-flex shrink-0 items-center justify-center text-[var(--gr-muted-fg)]\"\n :style=\"prefixStyle\"\n >\n <slot name=\"prefix\" />\n </span>\n\n <!-- Нативная форма: hidden на каждый тег — стандартная сериализация набора. -->\n <template v-if=\"name\">\n <input\n v-for=\"(tag, tagIndex) in modelValue\"\n :key=\"`hidden-${tagIndex}`\"\n type=\"hidden\"\n :name=\"name\"\n :value=\"tag\"\n >\n </template>\n <!--\n `display: contents` — список нужен ради роли, а не ради раскладки: чипы\n обязаны переноситься в одном потоке с полем ввода.\n -->\n <span v-if=\"modelValue.length\" role=\"list\" class=\"contents\">\n <GrBadge\n v-for=\"(tag, i) in modelValue\"\n :key=\"`${tag}-${i}`\"\n role=\"listitem\"\n :tone=\"tagTone\"\n :dark=\"tagDark\"\n :size=\"tagSize\"\n :radius=\"tagRadius\"\n data-gr-input-tag-item\n data-testid=\"gr-input-tag-item\"\n :data-index=\"i\"\n >\n <span class=\"inline-flex items-center gap-1 align-middle\">\n <slot name=\"tag\" :tag=\"tag\" :index=\"i\" :remove=\"() => removeAt(i)\">\n <span class=\"truncate max-w-[18rem]\">{{ tag }}</span>\n </slot>\n\n <button\n v-if=\"showRemove\"\n :ref=\"setRemoveEl(i)\"\n type=\"button\"\n :class=\"removeButtonClass\"\n :aria-label=\"removeTagLabelFor(tag)\"\n :tabindex=\"roving.tabindexFor(i)\"\n data-gr-input-tag-remove\n data-testid=\"gr-input-tag-remove\"\n :data-index=\"i\"\n @mousedown.prevent.stop\n @focus=\"roving.setActive(i)\"\n @keydown=\"onTagKeydown($event, i)\"\n @click.stop=\"removeAt(i)\"\n >\n <GrIcon size=\"sm\">\n <IconClose />\n </GrIcon>\n </button>\n </span>\n </GrBadge>\n </span>\n\n <input\n :id=\"resolvedId\"\n ref=\"inputEl\"\n data-gr-input-tag-input\n data-testid=\"gr-input-tag-input\"\n :value=\"inputValue\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :placeholder=\"placeholderText\"\n :aria-label=\"ariaLabel\"\n :aria-describedby=\"describedBy\"\n :aria-required=\"isRequired ? 'true' : undefined\"\n :aria-readonly=\"isReadonly ? 'true' : undefined\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n :aria-busy=\"isBusy ? 'true' : undefined\"\n class=\"flex-1 min-w-[120px] bg-transparent border-none outline-none placeholder:text-[var(--gr-muted-fg)]\"\n :class=\"inputClassName\"\n @input=\"onInput\"\n @keydown=\"onKeydown\"\n @blur=\"onBlur\"\n @paste=\"onPaste\"\n >\n\n <span\n v-if=\"hasSuffix\"\n ref=\"suffixEl\"\n data-gr-input-tag-suffix\n class=\"inline-flex shrink-0 items-center justify-center text-[var(--gr-muted-fg)]\"\n :style=\"suffixStyle\"\n >\n <slot name=\"suffix\" />\n </span>\n\n <span\n v-if=\"isBusy\"\n data-gr-input-tag-spinner\n :class=\"spinnerClass\"\n aria-hidden=\"true\"\n >\n <GrIcon size=\"sm\">\n <IconLoader class=\"animate-spin\" />\n </GrIcon>\n </span>\n\n <button\n v-if=\"showClearAll\"\n type=\"button\"\n data-gr-input-tag-clear\n :class=\"clearButtonClass\"\n :aria-label=\"resolvedClearAllLabel\"\n @mousedown.prevent.stop\n @click.stop=\"clearAll\"\n >\n <GrIcon size=\"sm\">\n <IconClose />\n </GrIcon>\n </button>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, nextTick, ref } from 'vue'\n\nimport GrBadge from '../GrBadge/GrBadge.vue'\nimport type { GrBadgeRadius, GrBadgeSize, GrBadgeTone } from '../GrBadge'\nimport GrIcon from '../GrIcon/GrIcon.vue'\nimport IconClose from '~icons/lucide/x'\nimport IconLoader from '~icons/lucide/loader-2'\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\nimport { useControlAddons } from '../../composables/internal/useControlAddons'\nimport { useGrFormFieldContext } from '../GrFormField/context'\nimport { useAnnouncer } from '../../composables/useAnnouncer'\nimport { useRovingFocus } from '../../composables/useRovingFocus'\nimport { useGrFormControl } from '../../composables/useGrFormControl'\nimport { useFocusWithin } from '../../composables/internal/useFocusWithin'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { isComposingEvent } from '../../internal/keyboard'\n\nimport {\n clearButtonClass,\n grInputTagInputClass,\n grInputTagWrapperClass,\n removeButtonClass,\n spinnerClass,\n type GrInputTagSize,\n type GrInputTagState,\n} from './grInputTagStyles'\n\nexport type { GrInputTagSize, GrInputTagState } from './grInputTagStyles'\n\nconst REGEX_SPECIAL_CHAR_RE = /[.*+?^${}()|[\\]\\\\]/g\n\n/**\n * Props for {@link GrInputTag}.\n * Tag-input primitive: renders a set of badges with an inline `<input>` for entering new tags.\n * Splits input by configurable `separators`, supports paste, Enter and Backspace editing.\n */\nexport interface GrInputTagProps {\n modelValue: string[]\n placeholder?: string\n disabled?: boolean\n readonly?: boolean\n invalid?: boolean\n /** Обязательное поле (`aria-required`). Складывается с `required` у `GrFormField`. */\n required?: boolean\n state?: GrInputTagState\n size?: GrInputTagSize\n separators?: string[]\n allowDuplicates?: boolean\n trim?: boolean\n max?: number\n addOnBlur?: boolean\n clearInputOnAdd?: boolean\n /**\n * Проверка тега перед добавлением. Может быть асинхронной (проверка на\n * сервере). Отклонённый тег не добавляется и уходит в событие `reject`.\n */\n beforeAdd?: (tag: string) => boolean | Promise<boolean>\n /** Кнопка «снести все теги». Настраивается через `GrConfigProvider`. */\n clearable?: boolean\n /** Фоновая работа: спиннер + `aria-busy`. Асинхронный `beforeAdd` поднимает его сам. */\n loading?: boolean\n tagTone?: GrBadgeTone\n tagDark?: boolean\n tagSize?: GrBadgeSize\n tagRadius?: GrBadgeRadius\n tagClosable?: boolean\n /** i18n-friendly aria-label for the per-tag remove button. */\n removeTagLabel?: string\n /** i18n aria-label кнопки «снести все». */\n clearAllLabel?: string\n /**\n * Имя контрола, когда он используется вне `GrFormField`. Внутри поля имя даёт\n * связка с его `<label for>` — там проп не нужен. Без того и другого у инпута\n * нет доступного имени вовсе: placeholder именем не считается.\n */\n ariaLabel?: string\n /** Имя для нативной формы: hidden input на каждый тег. */\n name?: string\n /**\n * Ширины аддонов `prefix`/`suffix` — общий контракт контролов пакета\n * (`docs/form-controls.md`).\n */\n prefixMinWidth?: string\n prefixMaxWidth?: string\n suffixMinWidth?: string\n suffixMaxWidth?: string\n prefixFixed?: boolean\n suffixFixed?: boolean\n}\n\nexport interface GrInputTagEmits {\n (e: 'update:modelValue', value: string[]): void\n (e: 'add', value: string): void\n (e: 'remove', value: string, index: number): void\n /** Тег не прошёл `beforeAdd`. */\n (e: 'reject', value: string): void\n /** Набор снесён кнопкой «очистить». */\n (e: 'clear'): void\n /** Набор тегов изменился. */\n (e: 'change', value: string[]): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n}\n\nconst props = withDefaults(\n defineProps<GrInputTagProps>(),\n {\n placeholder: undefined,\n disabled: false,\n readonly: false,\n invalid: false,\n required: false,\n state: 'default',\n // Настраивается через `GrConfigProvider`; дефолт — в резолвере ниже.\n size: undefined,\n separators: () => [','],\n allowDuplicates: false,\n trim: true,\n max: undefined,\n addOnBlur: false,\n clearInputOnAdd: true,\n beforeAdd: undefined,\n clearable: undefined,\n loading: false,\n tagTone: 'neutral',\n tagDark: false,\n tagSize: 'md',\n tagRadius: 'round',\n tagClosable: true,\n removeTagLabel: undefined,\n clearAllLabel: undefined,\n ariaLabel: undefined,\n name: undefined,\n },\n)\n\nconst emit = defineEmits<GrInputTagEmits>()\ndefineSlots<{\n /** Аддон слева от чипов: иконка, метка. */\n prefix?: () => any\n /** Аддон справа, перед кнопкой очистки. */\n suffix?: () => any\n /** Свой чип: `remove` снимает значение. */\n tag?: (props: { tag: string, index: number, remove: () => void }) => any\n}>()\n\n\n// Fallback из контекста `GrFormField` (id/aria-describedby/invalid/required) —\n// как у GrInput и GrSelect: контрол не знает про форму, знает только про поле.\nconst field = useGrFormFieldContext()\nconst resolvedId = computed(() => field?.id.value)\nconst describedBy = computed(() => field?.describedById.value)\nconst {\n disabled: isDisabled,\n invalid: isInvalid,\n required: isRequired,\n readonly: isReadonly,\n} = useGrFormControl(() => props)\n\n// Эффективные значения: локальный проп → `GrConfigProvider` → дефолт компонента.\n/** Ширина аддона по умолчанию — та же лестница, что у `GrInput`. */\nconst ADDON_MIN_WIDTH_BY_SIZE: Record<GrInputTagSize, string> = {\n xs: '2rem',\n sm: '2.25rem',\n md: '2.5rem',\n lg: '3rem',\n}\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrInputTag' })\n\n/**\n * Аддоны — флекс-соседи поля: оболочка тегового поля и так флекс-строка с\n * бейджами, поэтому места паддингом резервировать не нужно.\n */\nconst { hasPrefix, hasSuffix, prefixEl, suffixEl, prefixStyle, suffixStyle } = useControlAddons(() => props, {\n defaultMinWidth: () => ADDON_MIN_WIDTH_BY_SIZE[resolvedSize.value],\n paddingX: () => '0px',\n})\nconst resolvedClearable = useGrComponentProp('GrInputTag', 'clearable', () => props.clearable, false)\n\nconst { t } = useGranularityTranslations()\nconst resolvedClearAllLabel = computed(() => props.clearAllLabel ?? t('gr.inputTag.clearAll', 'Clear all tags'))\n\nfunction removeTagLabelFor(tag: string): string {\n // Безликое «Remove tag» на двадцати кнопках подряд не даёт выбрать нужную:\n // имя обязано называть сам тег.\n return props.removeTagLabel ?? t('gr.inputTag.removeTagNamed', 'Remove tag {tag}', { tag })\n}\n\nconst inputValue = ref('')\nconst inputEl = ref<HTMLInputElement | null>(null)\nconst rootEl = ref<HTMLElement | null>(null)\n\n// Внутри корня фокус ходит между полем и кнопками удаления чипов: без границы\n// каждое такое перемещение давало бы потребителю пару `blur` + `focus`.\nconst { onFocusIn, onFocusOut } = useFocusWithin(rootEl, {\n enter: event => emit('focus', event),\n leave: event => emit('blur', event),\n})\nconst removeEls = ref<HTMLButtonElement[]>([])\n\nfunction setRemoveEl(index: number) {\n return (el: unknown): void => {\n removeEls.value[index] = el as HTMLButtonElement\n }\n}\n\nfunction focus(): void {\n inputEl.value?.focus()\n}\n\nfunction blur(): void {\n inputEl.value?.blur()\n}\n\nconst isMaxed = computed(() => props.max !== undefined && props.modelValue.length >= props.max)\nconst canEdit = computed(() => !isDisabled.value && !isReadonly.value)\nconst showRemove = computed(() => props.tagClosable && canEdit.value)\n\n// Добавление, удаление и достигнутый предел — события, а не состояние виджета:\n// показывать их негде, объявить нужно. Это работа общего живого региона.\nconst { announce } = useAnnouncer()\n\n/**\n * Roving tabindex по крестикам: двадцать тегов давали двадцать одну остановку\n * `Tab`. В таб-порядке ровно один крестик, между ними ходят стрелками.\n *\n * Адресация индексом, а не значением тега: теги допускают повторы, и по\n * значению два одинаковых чипа было бы не различить.\n */\nconst tagIndexes = computed(() => props.modelValue.map((_, index) => index))\n\nconst roving = useRovingFocus<number>({\n items: () => tagIndexes.value,\n elementFor: index => removeEls.value[index],\n orientation: () => 'horizontal',\n // Ряд чипов — не кольцо: у него есть край, и за краем стоит поле ввода.\n wrap: () => false,\n onOverflow: (edge) => {\n // За последним чипом — поле ввода: продолжение того же ряда. Слева от\n // первого — начало строки, фокус остаётся на месте.\n if (edge === 'end') focus()\n return true\n },\n // Удаление чипа перерисовывает ряд: без ожидания фокус уехал бы на узел,\n // которого уже нет.\n beforeFocus: () => nextTick(),\n})\n\nasync function focusTag(index: number): Promise<void> {\n const last = props.modelValue.length - 1\n if (last < 0) {\n focus()\n return\n }\n\n await roving.focusKey(Math.min(Math.max(index, 0), last))\n}\n\nfunction escapeRegexChar(ch: string): string {\n return ch.replace(REGEX_SPECIAL_CHAR_RE, '\\\\$&')\n}\n\nconst splitRegex = computed(() => {\n const seps = (props.separators ?? []).filter(Boolean).map(s => s[0]!)\n const uniq = [...new Set([...seps, '\\n', '\\r', '\\t'])]\n const body = uniq.map(escapeRegexChar).join('')\n return new RegExp(`[${body}]+`, 'g')\n})\n\nfunction normalizeTag(raw: string): string {\n return props.trim ? raw.trim() : raw\n}\n\nfunction splitToTags(raw: string): string[] {\n return raw\n .split(splitRegex.value)\n .map(v => normalizeTag(v))\n .filter(Boolean)\n}\n\nfunction addMany(rawTags: string[]): boolean {\n if (!canEdit.value || isMaxed.value)\n return false\n\n const next = props.modelValue.slice()\n const added: string[] = []\n\n for (const raw of rawTags) {\n const tag = normalizeTag(raw)\n if (!tag)\n continue\n\n if (!props.allowDuplicates && next.includes(tag))\n continue\n\n if (props.max !== undefined && next.length >= props.max)\n break\n\n next.push(tag)\n added.push(tag)\n emit('add', tag)\n }\n\n if (!added.length)\n return false\n\n emit('update:modelValue', next)\n emit('change', next)\n announce(added.length === 1\n ? t('gr.inputTag.added', 'Tag added: {tag}', { tag: added[0]! })\n : t('gr.inputTag.addedMany', '{count} tags added', { count: added.length }))\n\n return true\n}\n\n// Счётчик запуска: пока идёт асинхронная проверка, пользователь успевает нажать\n// Enter второй раз — результат устаревшей проверки дописывать нельзя.\nlet validationRun = 0\nconst validating = ref(false)\nconst isBusy = computed(() => props.loading || validating.value)\n\nasync function filterAllowed(tags: string[], run: number): Promise<string[] | null> {\n const check = props.beforeAdd\n if (!check)\n return tags\n\n const allowed: string[] = []\n\n for (const tag of tags) {\n const ok = await check(tag)\n if (run !== validationRun)\n return null\n\n if (ok)\n allowed.push(tag)\n else\n emit('reject', tag)\n }\n\n return allowed\n}\n\nasync function commitInput(): Promise<void> {\n if (!canEdit.value || !inputValue.value)\n return\n\n if (isMaxed.value) {\n announce(t('gr.inputTag.limitReached', 'Tag limit reached'))\n return\n }\n\n const raw = inputValue.value\n const tags = splitToTags(raw)\n if (!tags.length)\n return\n\n const run = ++validationRun\n validating.value = Boolean(props.beforeAdd)\n\n const allowed = await filterAllowed(tags, run)\n\n if (run === validationRun)\n validating.value = false\n\n if (allowed === null || !allowed.length)\n return\n\n const changed = addMany(allowed)\n // Значение могло измениться, пока шла проверка: стирать чужой ввод нельзя.\n if (changed && props.clearInputOnAdd && inputValue.value === raw)\n inputValue.value = ''\n}\n\nfunction removeAt(index: number): void {\n if (!canEdit.value)\n return\n\n const next = props.modelValue.slice()\n const removed = next[index]\n if (removed === undefined)\n return\n\n next.splice(index, 1)\n emit('update:modelValue', next)\n emit('change', next)\n emit('remove', removed, index)\n announce(t('gr.inputTag.removed', 'Tag removed: {tag}', { tag: removed }))\n}\n\n/** Удаление с клавиатуры: фокус переезжает на соседний чип, а не пропадает. */\nasync function removeAtFromKeyboard(index: number): Promise<void> {\n removeAt(index)\n await nextTick()\n\n if (props.modelValue.length === 0) {\n focus()\n return\n }\n\n await focusTag(Math.min(index, props.modelValue.length - 1))\n}\n\nfunction clearAll(): void {\n if (!canEdit.value || props.modelValue.length === 0)\n return\n\n emit('update:modelValue', [])\n emit('change', [])\n emit('clear')\n announce(t('gr.inputTag.cleared', 'All tags removed'))\n focus()\n}\n\nfunction onInput(e: Event): void {\n inputValue.value = (e.target as HTMLInputElement).value\n}\n\nfunction onKeydown(e: KeyboardEvent): void {\n // Enter/сепаратор во время IME-композиции коммитят её, а не тег.\n if (isComposingEvent(e))\n return\n if (!canEdit.value)\n return\n\n if (e.key === 'Enter') {\n e.preventDefault()\n void commitInput()\n return\n }\n\n if ((props.separators ?? []).includes(e.key)) {\n e.preventDefault()\n void commitInput()\n return\n }\n\n if (inputValue.value.length === 0 && props.modelValue.length > 0) {\n if (e.key === 'Backspace') {\n e.preventDefault()\n removeAt(props.modelValue.length - 1)\n return\n }\n\n // Из пустого поля стрелка влево уводит на последний чип — дальше по чипам\n // работают те же стрелки, `Tab` для этого не нужен.\n if (e.key === 'ArrowLeft') {\n e.preventDefault()\n void focusTag(props.modelValue.length - 1)\n }\n }\n}\n\nfunction onTagKeydown(e: KeyboardEvent, index: number): void {\n if (roving.handleNavigationKeys(e)) {\n return\n }\n\n if (e.key === 'Delete' || e.key === 'Backspace') {\n e.preventDefault()\n void removeAtFromKeyboard(index)\n }\n}\n\nfunction onBlur(): void {\n if (props.addOnBlur)\n void commitInput()\n}\n\nfunction onPaste(e: ClipboardEvent): void {\n if (!canEdit.value || isMaxed.value)\n return\n\n const pasted = e.clipboardData?.getData('text')\n if (!pasted)\n return\n\n const tags = splitToTags(pasted)\n if (tags.length <= 1)\n return\n\n e.preventDefault()\n\n const run = ++validationRun\n validating.value = Boolean(props.beforeAdd)\n\n void filterAllowed(tags, run).then((allowed) => {\n if (run === validationRun)\n validating.value = false\n\n if (allowed?.length)\n addMany(allowed)\n })\n}\n\nconst wrapperClassName = computed(() => {\n return grInputTagWrapperClass({\n size: resolvedSize.value,\n state: props.state,\n invalid: isInvalid.value,\n disabled: isDisabled.value,\n })\n})\n\nconst inputClassName = computed(() => grInputTagInputClass(resolvedSize.value))\n\nconst showClearAll = computed(() => resolvedClearable.value && canEdit.value && props.modelValue.length > 0)\n\nconst placeholderText = computed(() => props.modelValue.length > 0 ? undefined : props.placeholder)\n\ndefineExpose({ focus, blur, clear: clearAll })\n</script>\n\n<template>\n <div\n ref=\"rootEl\"\n data-gr-input-tag\n data-testid=\"gr-input-tag\"\n :class=\"wrapperClassName\"\n @click=\"focus\"\n @focusin=\"onFocusIn\"\n @focusout=\"onFocusOut\"\n >\n <span\n v-if=\"hasPrefix\"\n ref=\"prefixEl\"\n data-gr-input-tag-prefix\n class=\"inline-flex shrink-0 items-center justify-center text-[var(--gr-muted-fg)]\"\n :style=\"prefixStyle\"\n >\n <slot name=\"prefix\" />\n </span>\n\n <!-- Нативная форма: hidden на каждый тег — стандартная сериализация набора. -->\n <template v-if=\"name\">\n <input\n v-for=\"(tag, tagIndex) in modelValue\"\n :key=\"`hidden-${tagIndex}`\"\n type=\"hidden\"\n :name=\"name\"\n :value=\"tag\"\n >\n </template>\n <!--\n `display: contents` — список нужен ради роли, а не ради раскладки: чипы\n обязаны переноситься в одном потоке с полем ввода.\n -->\n <span v-if=\"modelValue.length\" role=\"list\" class=\"contents\">\n <GrBadge\n v-for=\"(tag, i) in modelValue\"\n :key=\"`${tag}-${i}`\"\n role=\"listitem\"\n :tone=\"tagTone\"\n :dark=\"tagDark\"\n :size=\"tagSize\"\n :radius=\"tagRadius\"\n data-gr-input-tag-item\n data-testid=\"gr-input-tag-item\"\n :data-index=\"i\"\n >\n <span class=\"inline-flex items-center gap-1 align-middle\">\n <slot name=\"tag\" :tag=\"tag\" :index=\"i\" :remove=\"() => removeAt(i)\">\n <span class=\"truncate max-w-[18rem]\">{{ tag }}</span>\n </slot>\n\n <button\n v-if=\"showRemove\"\n :ref=\"setRemoveEl(i)\"\n type=\"button\"\n :class=\"removeButtonClass\"\n :aria-label=\"removeTagLabelFor(tag)\"\n :tabindex=\"roving.tabindexFor(i)\"\n data-gr-input-tag-remove\n data-testid=\"gr-input-tag-remove\"\n :data-index=\"i\"\n @mousedown.prevent.stop\n @focus=\"roving.setActive(i)\"\n @keydown=\"onTagKeydown($event, i)\"\n @click.stop=\"removeAt(i)\"\n >\n <GrIcon size=\"sm\">\n <IconClose />\n </GrIcon>\n </button>\n </span>\n </GrBadge>\n </span>\n\n <input\n :id=\"resolvedId\"\n ref=\"inputEl\"\n data-gr-input-tag-input\n data-testid=\"gr-input-tag-input\"\n :value=\"inputValue\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :placeholder=\"placeholderText\"\n :aria-label=\"ariaLabel\"\n :aria-describedby=\"describedBy\"\n :aria-required=\"isRequired ? 'true' : undefined\"\n :aria-readonly=\"isReadonly ? 'true' : undefined\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n :aria-busy=\"isBusy ? 'true' : undefined\"\n class=\"flex-1 min-w-[120px] bg-transparent border-none outline-none placeholder:text-[var(--gr-muted-fg)]\"\n :class=\"inputClassName\"\n @input=\"onInput\"\n @keydown=\"onKeydown\"\n @blur=\"onBlur\"\n @paste=\"onPaste\"\n >\n\n <span\n v-if=\"hasSuffix\"\n ref=\"suffixEl\"\n data-gr-input-tag-suffix\n class=\"inline-flex shrink-0 items-center justify-center text-[var(--gr-muted-fg)]\"\n :style=\"suffixStyle\"\n >\n <slot name=\"suffix\" />\n </span>\n\n <span\n v-if=\"isBusy\"\n data-gr-input-tag-spinner\n :class=\"spinnerClass\"\n aria-hidden=\"true\"\n >\n <GrIcon size=\"sm\">\n <IconLoader class=\"animate-spin\" />\n </GrIcon>\n </span>\n\n <button\n v-if=\"showClearAll\"\n type=\"button\"\n data-gr-input-tag-clear\n :class=\"clearButtonClass\"\n :aria-label=\"resolvedClearAllLabel\"\n @mousedown.prevent.stop\n @click.stop=\"clearAll\"\n >\n <GrIcon size=\"sm\">\n <IconClose />\n </GrIcon>\n </button>\n </div>\n</template>\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"GrLink-BQb7U4so.js","names":[],"sources":["../../../../src/components/GrLink/GrLink.vue","../../../../src/components/GrLink/GrLink.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { useGrComponentSize } from '../GrConfigProvider/context'\n/**\n * GrLink — GR-примитив ссылки.\n *\n * Корневой тег зависит от пропов:\n * - кастомный компонент/тег из пропа `as` — когда `as` передан и не `disabled`\n * (например, `Link` от `@inertiajs/vue3` или `RouterLink` от Vue Router);\n * - `<a>` — когда задан `href` и не `disabled`;\n * - `<span>` — во всех остальных случаях (включая `disabled`, даже если задан\n * `href`/`as`).\n *\n * Это осознанное решение, чтобы отключённая ссылка не была кликабельной и\n * не участвовала в порядке табуляции. Учтите это в внешних CSS-селекторах.\n *\n * Проп `as` нужен для интеграции с роутерами (Inertia, Vue Router и пр.):\n * стили/поведение GR остаются едиными, а тег корня подменяется. Все\n * специфичные для роутера атрибуты (`method`, `replace`, `preserve-scroll` и т.п.)\n * проходят через fallthrough-attrs.\n */\nimport { computed, markRaw, useAttrs, type Component } from 'vue'\n\nimport GrIcon from '../GrIcon/GrIcon.vue'\nimport IconExternal from '~icons/lucide/external-link'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\n\nimport {\n baseRootClass,\n focusRingClass,\n type GrLinkSize,\n type GrLinkTone,\n type GrLinkUnderline,\n type GrLinkVariant,\n grLinkClass,\n grLinkColorStyle,\n} from './grLinkStyles'\n\nexport interface GrLinkProps {\n /**\n * Кастомный корневой тег/компонент. Если передан и компонент не `disabled` —\n * рендерится через `<component :is=\"as\">`. Игнорируется при `disabled`.\n */\n as?: string | Component\n href?: string\n external?: boolean\n target?: string\n rel?: string\n disabled?: boolean\n ariaLabel?: string\n /** Семантический цвет ссылки из палитры `GrTone`. */\n tone?: GrLinkTone\n /** Уровень акцента: `default` (окрашен) или `muted` (приглушён, акцент на hover). */\n variant?: GrLinkVariant\n underline?: GrLinkUnderline\n size?: GrLinkSize\n /**\n * Иконка внешней ссылки. По умолчанию показывается у любой ссылки, которая\n * открывается в новой вкладке, — не только при `external`.\n */\n externalIcon?: boolean\n /** i18n: скрытая подсказка «откроется в новой вкладке». */\n newTabLabel?: string\n}\n\ndefineOptions({\n inheritAttrs: false,\n})\n\nconst props = withDefaults(defineProps<GrLinkProps>(), {\n as: undefined,\n href: undefined,\n external: false,\n target: undefined,\n rel: undefined,\n disabled: false,\n ariaLabel: undefined,\n tone: 'primary',\n variant: 'default',\n underline: 'auto',\n size: undefined,\n externalIcon: undefined,\n newTabLabel: undefined,\n})\n\nconst attrs = useAttrs()\n\n// Кастомный корень имеет приоритет над `<a>` — GR не пытается угадывать\n// «правильный» тег за пользователя, если он явно его указал.\nconst isInteractive = computed(() => !props.disabled && (!!props.as || !!props.href))\n\nconst renderAs = computed<string | Component>(() => {\n if (!isInteractive.value) return 'span'\n if (props.as) {\n // Если `as` — объект-компонент, помечаем его как нереактивный, чтобы избежать\n // оверхеда от Vue-прокси и предупреждений в консоли. Строки (нативные теги)\n // оставляем как есть.\n return typeof props.as === 'string' ? props.as : markRaw(props.as)\n }\n return 'a'\n})\n\nconst resolvedTarget = computed(() => {\n if (props.target)\n return props.target\n\n if (props.external)\n return '_blank'\n\n return undefined\n})\n\nconst resolvedRel = computed(() => {\n if (props.rel)\n return props.rel\n\n // Авто-защита для любых ссылок, открывающихся в новой вкладке,\n // а не только когда выставлен `external`.\n if (resolvedTarget.value === '_blank')\n return 'noopener noreferrer'\n\n return undefined\n})\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrLink' })\n\nconst { t } = useGranularityTranslations()\n\n/**\n * Смена контекста должна быть объявлена (WCAG 3.2.5). Условие — ссылка реально\n * открывается в новой вкладке, а не наличие пропа `external`: `target=\"_blank\"`\n * снаружи даёт ровно тот же сюрприз (та же генерализация, что у `rel`).\n */\nconst opensInNewTab = computed(() => isInteractive.value && resolvedTarget.value === '_blank')\nconst newTabHint = computed(() => props.newTabLabel ?? t('gr.link.opensInNewTab', 'opens in a new tab'))\n\nconst showExternalIcon = computed(() => props.externalIcon ?? opensInNewTab.value)\n\n/**\n * `aria-label` перекрывает содержимое целиком — вместе со скрытой подсказкой.\n * Поэтому, если имя задано руками, подсказка дописывается к нему.\n */\nconst resolvedAriaLabel = computed(() => {\n if (!props.ariaLabel)\n return undefined\n\n return opensInNewTab.value ? `${props.ariaLabel}, ${newTabHint.value}` : props.ariaLabel\n})\n\nconst iconSize = computed(() => (resolvedSize.value === 'lg' ? 'sm' : 'xs'))\n\nconst rootClass = computed(() => {\n const variantClass = grLinkClass({\n size: resolvedSize.value,\n underline: props.underline,\n disabled: props.disabled,\n })\n\n // На disabled-ветке (`<span>`) focus-ring не нужен: элемент не фокусируется,\n // а если снаружи ему навесят `tabindex`, фокус-рамка на «отключённой» ссылке смотрелась бы странно.\n return props.disabled\n ? `${baseRootClass} ${variantClass}`\n : `${baseRootClass} ${focusRingClass} ${variantClass}`\n})\n\n// Цвет (tone × variant) прокидываем через CSS-переменные, чтобы не плодить классы.\nconst colorStyle = computed(() => grLinkColorStyle({\n tone: props.tone,\n variant: props.variant,\n disabled: props.disabled,\n}))\n</script>\n\n<template>\n <component\n :is=\"renderAs\"\n v-bind=\"attrs\"\n :href=\"isInteractive ? href : undefined\"\n :target=\"isInteractive ? resolvedTarget : undefined\"\n :rel=\"isInteractive ? resolvedRel : undefined\"\n :aria-label=\"resolvedAriaLabel\"\n :aria-disabled=\"disabled ? 'true' : undefined\"\n :class=\"rootClass\"\n :style=\"colorStyle\"\n >\n <slot />\n\n <GrIcon v-if=\"showExternalIcon\" :size=\"iconSize\">\n <IconExternal />\n </GrIcon>\n\n <!-- Подсказка не дублируется, когда её уже вобрал `aria-label`. -->\n <span v-if=\"opensInNewTab && !ariaLabel\" class=\"sr-only\">({{ newTabHint }})</span>\n </component>\n</template>\n","<script setup lang=\"ts\">\nimport { useGrComponentSize } from '../GrConfigProvider/context'\n/**\n * GrLink — GR-примитив ссылки.\n *\n * Корневой тег зависит от пропов:\n * - кастомный компонент/тег из пропа `as` — когда `as` передан и не `disabled`\n * (например, `Link` от `@inertiajs/vue3` или `RouterLink` от Vue Router);\n * - `<a>` — когда задан `href` и не `disabled`;\n * - `<span>` — во всех остальных случаях (включая `disabled`, даже если задан\n * `href`/`as`).\n *\n * Это осознанное решение, чтобы отключённая ссылка не была кликабельной и\n * не участвовала в порядке табуляции. Учтите это в внешних CSS-селекторах.\n *\n * Проп `as` нужен для интеграции с роутерами (Inertia, Vue Router и пр.):\n * стили/поведение GR остаются едиными, а тег корня подменяется. Все\n * специфичные для роутера атрибуты (`method`, `replace`, `preserve-scroll` и т.п.)\n * проходят через fallthrough-attrs.\n */\nimport { computed, markRaw, useAttrs, type Component } from 'vue'\n\nimport GrIcon from '../GrIcon/GrIcon.vue'\nimport IconExternal from '~icons/lucide/external-link'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\n\nimport {\n baseRootClass,\n focusRingClass,\n type GrLinkSize,\n type GrLinkTone,\n type GrLinkUnderline,\n type GrLinkVariant,\n grLinkClass,\n grLinkColorStyle,\n} from './grLinkStyles'\n\nexport interface GrLinkProps {\n /**\n * Кастомный корневой тег/компонент. Если передан и компонент не `disabled` —\n * рендерится через `<component :is=\"as\">`. Игнорируется при `disabled`.\n */\n as?: string | Component\n href?: string\n external?: boolean\n target?: string\n rel?: string\n disabled?: boolean\n ariaLabel?: string\n /** Семантический цвет ссылки из палитры `GrTone`. */\n tone?: GrLinkTone\n /** Уровень акцента: `default` (окрашен) или `muted` (приглушён, акцент на hover). */\n variant?: GrLinkVariant\n underline?: GrLinkUnderline\n size?: GrLinkSize\n /**\n * Иконка внешней ссылки. По умолчанию показывается у любой ссылки, которая\n * открывается в новой вкладке, — не только при `external`.\n */\n externalIcon?: boolean\n /** i18n: скрытая подсказка «откроется в новой вкладке». */\n newTabLabel?: string\n}\n\ndefineOptions({\n inheritAttrs: false,\n})\n\nconst props = withDefaults(defineProps<GrLinkProps>(), {\n as: undefined,\n href: undefined,\n external: false,\n target: undefined,\n rel: undefined,\n disabled: false,\n ariaLabel: undefined,\n tone: 'primary',\n variant: 'default',\n underline: 'auto',\n size: undefined,\n externalIcon: undefined,\n newTabLabel: undefined,\n})\n\nconst attrs = useAttrs()\n\n// Кастомный корень имеет приоритет над `<a>` — GR не пытается угадывать\n// «правильный» тег за пользователя, если он явно его указал.\nconst isInteractive = computed(() => !props.disabled && (!!props.as || !!props.href))\n\nconst renderAs = computed<string | Component>(() => {\n if (!isInteractive.value) return 'span'\n if (props.as) {\n // Если `as` — объект-компонент, помечаем его как нереактивный, чтобы избежать\n // оверхеда от Vue-прокси и предупреждений в консоли. Строки (нативные теги)\n // оставляем как есть.\n return typeof props.as === 'string' ? props.as : markRaw(props.as)\n }\n return 'a'\n})\n\nconst resolvedTarget = computed(() => {\n if (props.target)\n return props.target\n\n if (props.external)\n return '_blank'\n\n return undefined\n})\n\nconst resolvedRel = computed(() => {\n if (props.rel)\n return props.rel\n\n // Авто-защита для любых ссылок, открывающихся в новой вкладке,\n // а не только когда выставлен `external`.\n if (resolvedTarget.value === '_blank')\n return 'noopener noreferrer'\n\n return undefined\n})\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrLink' })\n\nconst { t } = useGranularityTranslations()\n\n/**\n * Смена контекста должна быть объявлена (WCAG 3.2.5). Условие — ссылка реально\n * открывается в новой вкладке, а не наличие пропа `external`: `target=\"_blank\"`\n * снаружи даёт ровно тот же сюрприз (та же генерализация, что у `rel`).\n */\nconst opensInNewTab = computed(() => isInteractive.value && resolvedTarget.value === '_blank')\nconst newTabHint = computed(() => props.newTabLabel ?? t('gr.link.opensInNewTab', 'opens in a new tab'))\n\nconst showExternalIcon = computed(() => props.externalIcon ?? opensInNewTab.value)\n\n/**\n * `aria-label` перекрывает содержимое целиком — вместе со скрытой подсказкой.\n * Поэтому, если имя задано руками, подсказка дописывается к нему.\n */\nconst resolvedAriaLabel = computed(() => {\n if (!props.ariaLabel)\n return undefined\n\n return opensInNewTab.value ? `${props.ariaLabel}, ${newTabHint.value}` : props.ariaLabel\n})\n\nconst iconSize = computed(() => (resolvedSize.value === 'lg' ? 'sm' : 'xs'))\n\nconst rootClass = computed(() => {\n const variantClass = grLinkClass({\n size: resolvedSize.value,\n underline: props.underline,\n disabled: props.disabled,\n })\n\n // На disabled-ветке (`<span>`) focus-ring не нужен: элемент не фокусируется,\n // а если снаружи ему навесят `tabindex`, фокус-рамка на «отключённой» ссылке смотрелась бы странно.\n return props.disabled\n ? `${baseRootClass} ${variantClass}`\n : `${baseRootClass} ${focusRingClass} ${variantClass}`\n})\n\n// Цвет (tone × variant) прокидываем через CSS-переменные, чтобы не плодить классы.\nconst colorStyle = computed(() => grLinkColorStyle({\n tone: props.tone,\n variant: props.variant,\n disabled: props.disabled,\n}))\n</script>\n\n<template>\n <component\n :is=\"renderAs\"\n v-bind=\"attrs\"\n :href=\"isInteractive ? href : undefined\"\n :target=\"isInteractive ? resolvedTarget : undefined\"\n :rel=\"isInteractive ? resolvedRel : undefined\"\n :aria-label=\"resolvedAriaLabel\"\n :aria-disabled=\"disabled ? 'true' : undefined\"\n :class=\"rootClass\"\n :style=\"colorStyle\"\n >\n <slot />\n\n <GrIcon v-if=\"showExternalIcon\" :size=\"iconSize\">\n <IconExternal />\n </GrIcon>\n\n <!-- Подсказка не дублируется, когда её уже вобрал `aria-label`. -->\n <span v-if=\"opensInNewTab && !ariaLabel\" class=\"sr-only\">({{ newTabHint }})</span>\n </component>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoEA,MAAM,QAAQ;EAgBd,MAAM,QAAQ,SAAS;EAIvB,MAAM,gBAAgB,eAAe,CAAC,MAAM,aAAa,CAAC,CAAC,MAAM,MAAM,CAAC,CAAC,MAAM,KAAK;EAEpF,MAAM,WAAW,eAAmC;GAClD,IAAI,CAAC,cAAc,OAAO,OAAO;GACjC,IAAI,MAAM,IAIR,OAAO,OAAO,MAAM,OAAO,WAAW,MAAM,KAAK,QAAQ,MAAM,EAAE;GAEnE,OAAO;EACT,CAAC;EAED,MAAM,iBAAiB,eAAe;GACpC,IAAI,MAAM,QACR,OAAO,MAAM;GAEf,IAAI,MAAM,UACR,OAAO;EAGX,CAAC;EAED,MAAM,cAAc,eAAe;GACjC,IAAI,MAAM,KACR,OAAO,MAAM;GAIf,IAAI,eAAe,UAAU,UAC3B,OAAO;EAGX,CAAC;EAED,MAAM,eAAe,yBAAyB,MAAM,MAAM,EAAE,WAAW,SAAS,CAAC;EAEjF,MAAM,EAAE,MAAM,2BAA2B;;;;;;EAOzC,MAAM,gBAAgB,eAAe,cAAc,SAAS,eAAe,UAAU,QAAQ;EAC7F,MAAM,aAAa,eAAe,MAAM,eAAe,EAAE,yBAAyB,oBAAoB,CAAC;EAEvG,MAAM,mBAAmB,eAAe,MAAM,gBAAgB,cAAc,KAAK;;;;;EAMjF,MAAM,oBAAoB,eAAe;GACvC,IAAI,CAAC,MAAM,WACT,OAAO,KAAA;GAET,OAAO,cAAc,QAAQ,GAAG,MAAM,UAAU,IAAI,WAAW,UAAU,MAAM;EACjF,CAAC;EAED,MAAM,WAAW,eAAgB,aAAa,UAAU,OAAO,OAAO,IAAK;EAE3E,MAAM,YAAY,eAAe;GAC/B,MAAM,eAAe,YAAY;IAC/B,MAAM,aAAa;IACnB,WAAW,MAAM;IACjB,UAAU,MAAM;GAClB,CAAC;GAID,OAAO,MAAM,WACT,GAAG,cAAc,GAAG,iBACpB,GAAG,cAAc,GAAG,eAAe,GAAG;EAC5C,CAAC;EAGD,MAAM,aAAa,eAAe,iBAAiB;GACjD,MAAM,MAAM;GACZ,SAAS,MAAM;GACf,UAAU,MAAM;EAClB,CAAC,CAAC;;uBAIA,YAmBY,wBAlBL,SAAA,KAAQ,GADf,WAEU,MAiBE,KAAA,GAjBG;IACZ,MAAM,cAAA,QAAgB,QAAA,OAAO,KAAA;IAC7B,QAAQ,cAAA,QAAgB,eAAA,QAAiB,KAAA;IACzC,KAAK,cAAA,QAAgB,YAAA,QAAc,KAAA;IACnC,cAAY,kBAAA;IACZ,iBAAe,QAAA,WAAQ,SAAY,KAAA;IACnC,OAAO,UAAA;IACP,OAAO,WAAA;;2BAEA;KAAR,WAAQ,KAAA,QAAA,SAAA;KAEM,iBAAA,SAAA,UAAA,GAAd,YAES,gBAAA;;MAFwB,MAAM,SAAA;;6BACrB,CAAhB,YAAgB,MAAA,qBAAA,CAAA,CAAA,CAAA;;;KAIN,cAAA,SAAa,CAAK,QAAA,aAAA,UAAA,GAA9B,mBAAkF,QAAlF,YAAyD,MAAC,gBAAG,WAAA,KAAU,IAAG,KAAC,CAAA,KAAA,mBAAA,IAAA,IAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"GrList-B5bclmHV.js","names":["$slots"],"sources":["../../../../src/components/GrList/GrList.vue","../../../../src/components/GrList/GrList.vue","../../../../src/components/GrList/GrListItem.vue","../../../../src/components/GrList/GrListItem.vue"],"sourcesContent":["<script setup lang=\"ts\" generic=\"T = unknown\">\nimport { computed, onMounted, onUpdated, ref, useSlots, watch } from 'vue'\n\nimport GrCard from '../GrCard'\nimport type { GrCardVariant } from '../GrCard/grCardStyles'\nimport GrSkeleton from '../GrSkeleton/GrSkeleton.vue'\nimport { hasMeaningfulSlotContent } from '../shared/slotNodes'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { useVirtualList, type GrVirtualAlign } from '../../composables/useVirtualList'\n\nimport { dividedClass, emptyClass, listSurfaceClass, loadingRowClass } from './grListStyles'\n\nexport interface GrListProps<TItem = unknown> {\n /**\n * Поверхность списка — вариант карточки под ним. `ghost` убирает рамку и\n * тень: список внутри уже существующей карточки не должен давать вторую.\n */\n variant?: GrCardVariant\n /** Показывать ли горизонтальные разделители между элементами (по умолчанию — да). */\n divided?: boolean\n /** Идёт загрузка: вместо пунктов — строки-скелетоны, контейнер помечен `aria-busy`. */\n loading?: boolean\n /** Сколько строк-заглушек показать при `loading`. */\n loadingRows?: number\n /**\n * Список пуст. По умолчанию определяется сам — по содержимому слота; проп\n * нужен там, где потребитель знает лучше (например, слот заполнен\n * заголовками групп, а данных в них нет).\n */\n empty?: boolean\n /** Текст пустого состояния. Слот `#empty` сильнее. */\n emptyText?: string\n /**\n * Данные списка. С ними пункты рисует слот `#item`, а не слот по умолчанию, —\n * и только так список знает размер набора, то есть может его виртуализировать.\n */\n items?: TItem[]\n /** Ключ пункта для `v-for`: имя поля или функция. Без него — индекс. */\n itemKey?: string | ((item: TItem, index: number) => string | number)\n /** Высота видимой части: контейнер становится скроллером. Число — пиксели. */\n maxHeight?: number | string\n /** Держать в DOM только окно вокруг вьюпорта. Требует `items` и `maxHeight`. */\n virtual?: boolean\n /** Оценка высоты пункта до замера. Уточняется по факту при первом рендере. */\n estimatedItemSize?: number\n}\n\nconst props = withDefaults(defineProps<GrListProps<T>>(), {\n // Дефолт живёт в `GrCard` (и его резолвере `GrConfigProvider`), поэтому здесь\n // остаётся `undefined` — иначе список перебивал бы настройку карточки.\n variant: undefined,\n divided: true,\n loading: false,\n loadingRows: 3,\n empty: undefined,\n emptyText: undefined,\n items: undefined,\n itemKey: undefined,\n maxHeight: undefined,\n virtual: false,\n // Строка обычной плотности с заголовком и описанием.\n estimatedItemSize: 56,\n})\n\nconst slots = useSlots()\nconst { t } = useGranularityTranslations()\n\nconst listEl = ref<HTMLElement | null>(null)\n\nconst resolvedEmptyText = computed(() => props.emptyText ?? t('gr.list.empty', 'Nothing here yet'))\n\nconst dataMode = computed(() => props.items !== undefined)\n\n// Пустоту видно по слоту: `v-for` по пустому массиву оставляет фрагмент без\n// узлов, `v-if` — комментарий, и ни то ни другое пунктом списка не является.\nconst isEmpty = computed(() => {\n if (props.empty !== undefined)\n return props.empty\n\n if (dataMode.value)\n return props.items!.length === 0\n\n return !hasMeaningfulSlotContent(slots.default?.() ?? [])\n})\n\nconst showItems = computed(() => !props.loading && !isEmpty.value)\n\n// Разделители нужны только между пунктами: в пустой и загрузочной ветках они\n// оставили бы висящую линию. Клип по радиусу карточки — безусловный.\nconst listClass = computed(() => [\n listSurfaceClass,\n props.divided && showItems.value ? dividedClass : '',\n].filter(Boolean).join(' '))\n\nconst loadingRowCount = computed(() => Math.max(1, Math.trunc(props.loadingRows)))\n\nconst virtualEnabled = computed(() => props.virtual && dataMode.value && props.maxHeight !== undefined)\n\nconst virtualizer = useVirtualList({\n container: listEl,\n count: () => (virtualEnabled.value && showItems.value ? props.items!.length : 0),\n // Замена массива сбрасывает замеры; append в тот же массив их сохраняет.\n source: () => props.items,\n itemSize: () => props.estimatedItemSize,\n // Первый рендер обязан совпасть с серверным: контейнера ещё нет, и окно\n // считается от объявленной высоты, а не от измеренной.\n viewportSize: () => (typeof props.maxHeight === 'number' ? props.maxHeight : undefined),\n})\n\n/** Окно к отрисовке: при выключенной виртуализации — весь набор. */\nconst renderedItems = computed(() => {\n const items = props.items ?? []\n if (!virtualEnabled.value) return items.map((item, index) => ({ item, index }))\n\n const { start, end } = virtualizer.range.value\n return items.slice(start, end).map((item, offset) => ({ item, index: start + offset }))\n})\n\nfunction keyOf(item: T, index: number): string | number {\n const key = props.itemKey\n if (typeof key === 'function') return key(item, index)\n if (typeof key === 'string') return (item as Record<string, unknown>)[key] as string | number\n\n return index\n}\n\n/**\n * ARIA-набор для пункта.\n *\n * Только в виртуальном режиме: в DOM тогда неполный набор, и без этих атрибутов\n * диктор объявил бы «7 из 20» на списке в пять тысяч. Когда набор в DOM целиком,\n * его размер и позицию браузер считает сам, а руками проставленные атрибуты\n * начинают расходиться с разметкой при любой фильтрации.\n */\nfunction ariaOf(index: number): Record<string, number> {\n if (!virtualEnabled.value) return {}\n\n return { 'aria-setsize': props.items!.length, 'aria-posinset': index + 1 }\n}\n\nconst maxHeightStyle = computed(() => {\n const value = props.maxHeight\n if (value == null) return undefined\n return typeof value === 'number' ? `${value}px` : value\n})\n\nconst listStyle = computed(() => {\n if (props.maxHeight === undefined) return undefined\n\n const scroller = { overflowY: 'auto' as const, maxHeight: maxHeightStyle.value }\n if (!virtualEnabled.value) return scroller\n\n return { ...scroller, ...virtualizer.spacerStyle.value }\n})\n\n/**\n * Замер отрисованных пунктов.\n *\n * Идёт по детям контейнера, а не по `ref` на элементе: пункт рисует слот\n * потребителя, привязать к нему `ref` неоткуда. В виртуальном режиме прямые\n * дети контейнера — ровно окно (распорки живут псевдоэлементами и детьми не\n * считаются), поэтому сопоставление по позиции однозначно.\n */\nfunction measureRendered(): void {\n if (!virtualEnabled.value) return\n // В состояниях loading/empty дети контейнера — скелетоны и заглушки: их\n // высоты записались бы в замеры под индексами будущих строк данных.\n if (!showItems.value) return\n\n const container = listEl.value\n if (!container) return\n\n const { start } = virtualizer.range.value\n const children = container.children\n for (let offset = 0; offset < children.length; offset++) {\n virtualizer.measure(start + offset, children[offset])\n }\n}\n\nonMounted(measureRendered)\nonUpdated(measureRendered)\n\nfunction scrollToIndex(index: number, align?: GrVirtualAlign): void {\n virtualizer.scrollToIndex(index, align)\n}\n\ndefineExpose({ scrollToIndex })\n\nif (__GR_DEV__) {\n watch(\n () => [props.virtual, dataMode.value, props.maxHeight] as const,\n ([virtual, hasItems, maxHeight]) => {\n if (!virtual) return\n\n if (!hasItems) {\n console.warn(\n '[granularity] GrList: `virtual` требует `items` — пункты из слота по умолчанию '\n + 'список не считает и резать окно ему не из чего.',\n )\n }\n else if (maxHeight === undefined) {\n console.warn(\n '[granularity] GrList: `virtual` требует `maxHeight` — без ограничения высоты '\n + 'вьюпорта нет, и в окно попадает весь набор.',\n )\n }\n },\n { immediate: true },\n )\n\n watch(\n () => [dataMode.value, Boolean(slots.item)] as const,\n ([hasItems, hasSlot]) => {\n if (hasItems && !hasSlot) {\n console.warn(\n '[granularity] GrList: с `items` пункты рисует слот `#item` — без него список пуст.',\n )\n }\n },\n { immediate: true },\n )\n\n onMounted(() => {\n if (!virtualEnabled.value) return\n\n const first = listEl.value?.firstElementChild\n if (first && !first.hasAttribute('aria-posinset')) {\n console.warn(\n '[granularity] GrList: при `virtual` пункты обязаны нести `aria-setsize`/`aria-posinset` — '\n + 'пробросьте `aria` из слота `#item`: <GrListItem v-bind=\"aria\" … />. Иначе диктор '\n + 'посчитает набор по отрисованному окну.',\n )\n }\n })\n}\n</script>\n\n<template>\n <GrCard :variant=\"variant\">\n <div\n ref=\"listEl\"\n data-gr-list\n role=\"list\"\n :class=\"listClass\"\n :style=\"listStyle\"\n :tabindex=\"maxHeight === undefined ? undefined : 0\"\n :data-gr-virtual=\"virtualEnabled ? '' : undefined\"\n :aria-busy=\"loading ? 'true' : undefined\"\n >\n <template v-if=\"loading\">\n <slot name=\"loading\">\n <div\n v-for=\"row in loadingRowCount\"\n :key=\"row\"\n data-gr-list-loading-row\n :class=\"loadingRowClass\"\n >\n <GrSkeleton />\n </div>\n </slot>\n </template>\n\n <div\n v-else-if=\"isEmpty\"\n data-gr-list-empty\n :class=\"emptyClass\"\n >\n <slot name=\"empty\">\n {{ resolvedEmptyText }}\n </slot>\n </div>\n\n <template v-else-if=\"dataMode\">\n <template\n v-for=\"entry in renderedItems\"\n :key=\"keyOf(entry.item, entry.index)\"\n >\n <slot\n name=\"item\"\n :item=\"entry.item\"\n :index=\"entry.index\"\n :aria=\"ariaOf(entry.index)\"\n />\n </template>\n </template>\n\n <slot v-else />\n </div>\n </GrCard>\n</template>\n\n<style scoped>\n/* Срезанное сверху и снизу держат псевдоэлементы контейнера: их не за что\n размонтировать, в дереве доступности их нет, а высота приезжает переменными\n в том же патче, что и пункты. Почему не отступы контейнера и не поля крайних\n пунктов — `docs/virtual-list.md`. */\n[data-gr-virtual]::before,\n[data-gr-virtual]::after {\n content: '';\n display: block;\n flex: none;\n}\n\n[data-gr-virtual]::before {\n height: var(--gr-virtual-before, 0px);\n}\n\n[data-gr-virtual]::after {\n height: var(--gr-virtual-after, 0px);\n}\n</style>\n","<script setup lang=\"ts\" generic=\"T = unknown\">\nimport { computed, onMounted, onUpdated, ref, useSlots, watch } from 'vue'\n\nimport GrCard from '../GrCard'\nimport type { GrCardVariant } from '../GrCard/grCardStyles'\nimport GrSkeleton from '../GrSkeleton/GrSkeleton.vue'\nimport { hasMeaningfulSlotContent } from '../shared/slotNodes'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { useVirtualList, type GrVirtualAlign } from '../../composables/useVirtualList'\n\nimport { dividedClass, emptyClass, listSurfaceClass, loadingRowClass } from './grListStyles'\n\nexport interface GrListProps<TItem = unknown> {\n /**\n * Поверхность списка — вариант карточки под ним. `ghost` убирает рамку и\n * тень: список внутри уже существующей карточки не должен давать вторую.\n */\n variant?: GrCardVariant\n /** Показывать ли горизонтальные разделители между элементами (по умолчанию — да). */\n divided?: boolean\n /** Идёт загрузка: вместо пунктов — строки-скелетоны, контейнер помечен `aria-busy`. */\n loading?: boolean\n /** Сколько строк-заглушек показать при `loading`. */\n loadingRows?: number\n /**\n * Список пуст. По умолчанию определяется сам — по содержимому слота; проп\n * нужен там, где потребитель знает лучше (например, слот заполнен\n * заголовками групп, а данных в них нет).\n */\n empty?: boolean\n /** Текст пустого состояния. Слот `#empty` сильнее. */\n emptyText?: string\n /**\n * Данные списка. С ними пункты рисует слот `#item`, а не слот по умолчанию, —\n * и только так список знает размер набора, то есть может его виртуализировать.\n */\n items?: TItem[]\n /** Ключ пункта для `v-for`: имя поля или функция. Без него — индекс. */\n itemKey?: string | ((item: TItem, index: number) => string | number)\n /** Высота видимой части: контейнер становится скроллером. Число — пиксели. */\n maxHeight?: number | string\n /** Держать в DOM только окно вокруг вьюпорта. Требует `items` и `maxHeight`. */\n virtual?: boolean\n /** Оценка высоты пункта до замера. Уточняется по факту при первом рендере. */\n estimatedItemSize?: number\n}\n\nconst props = withDefaults(defineProps<GrListProps<T>>(), {\n // Дефолт живёт в `GrCard` (и его резолвере `GrConfigProvider`), поэтому здесь\n // остаётся `undefined` — иначе список перебивал бы настройку карточки.\n variant: undefined,\n divided: true,\n loading: false,\n loadingRows: 3,\n empty: undefined,\n emptyText: undefined,\n items: undefined,\n itemKey: undefined,\n maxHeight: undefined,\n virtual: false,\n // Строка обычной плотности с заголовком и описанием.\n estimatedItemSize: 56,\n})\n\nconst slots = useSlots()\nconst { t } = useGranularityTranslations()\n\nconst listEl = ref<HTMLElement | null>(null)\n\nconst resolvedEmptyText = computed(() => props.emptyText ?? t('gr.list.empty', 'Nothing here yet'))\n\nconst dataMode = computed(() => props.items !== undefined)\n\n// Пустоту видно по слоту: `v-for` по пустому массиву оставляет фрагмент без\n// узлов, `v-if` — комментарий, и ни то ни другое пунктом списка не является.\nconst isEmpty = computed(() => {\n if (props.empty !== undefined)\n return props.empty\n\n if (dataMode.value)\n return props.items!.length === 0\n\n return !hasMeaningfulSlotContent(slots.default?.() ?? [])\n})\n\nconst showItems = computed(() => !props.loading && !isEmpty.value)\n\n// Разделители нужны только между пунктами: в пустой и загрузочной ветках они\n// оставили бы висящую линию. Клип по радиусу карточки — безусловный.\nconst listClass = computed(() => [\n listSurfaceClass,\n props.divided && showItems.value ? dividedClass : '',\n].filter(Boolean).join(' '))\n\nconst loadingRowCount = computed(() => Math.max(1, Math.trunc(props.loadingRows)))\n\nconst virtualEnabled = computed(() => props.virtual && dataMode.value && props.maxHeight !== undefined)\n\nconst virtualizer = useVirtualList({\n container: listEl,\n count: () => (virtualEnabled.value && showItems.value ? props.items!.length : 0),\n // Замена массива сбрасывает замеры; append в тот же массив их сохраняет.\n source: () => props.items,\n itemSize: () => props.estimatedItemSize,\n // Первый рендер обязан совпасть с серверным: контейнера ещё нет, и окно\n // считается от объявленной высоты, а не от измеренной.\n viewportSize: () => (typeof props.maxHeight === 'number' ? props.maxHeight : undefined),\n})\n\n/** Окно к отрисовке: при выключенной виртуализации — весь набор. */\nconst renderedItems = computed(() => {\n const items = props.items ?? []\n if (!virtualEnabled.value) return items.map((item, index) => ({ item, index }))\n\n const { start, end } = virtualizer.range.value\n return items.slice(start, end).map((item, offset) => ({ item, index: start + offset }))\n})\n\nfunction keyOf(item: T, index: number): string | number {\n const key = props.itemKey\n if (typeof key === 'function') return key(item, index)\n if (typeof key === 'string') return (item as Record<string, unknown>)[key] as string | number\n\n return index\n}\n\n/**\n * ARIA-набор для пункта.\n *\n * Только в виртуальном режиме: в DOM тогда неполный набор, и без этих атрибутов\n * диктор объявил бы «7 из 20» на списке в пять тысяч. Когда набор в DOM целиком,\n * его размер и позицию браузер считает сам, а руками проставленные атрибуты\n * начинают расходиться с разметкой при любой фильтрации.\n */\nfunction ariaOf(index: number): Record<string, number> {\n if (!virtualEnabled.value) return {}\n\n return { 'aria-setsize': props.items!.length, 'aria-posinset': index + 1 }\n}\n\nconst maxHeightStyle = computed(() => {\n const value = props.maxHeight\n if (value == null) return undefined\n return typeof value === 'number' ? `${value}px` : value\n})\n\nconst listStyle = computed(() => {\n if (props.maxHeight === undefined) return undefined\n\n const scroller = { overflowY: 'auto' as const, maxHeight: maxHeightStyle.value }\n if (!virtualEnabled.value) return scroller\n\n return { ...scroller, ...virtualizer.spacerStyle.value }\n})\n\n/**\n * Замер отрисованных пунктов.\n *\n * Идёт по детям контейнера, а не по `ref` на элементе: пункт рисует слот\n * потребителя, привязать к нему `ref` неоткуда. В виртуальном режиме прямые\n * дети контейнера — ровно окно (распорки живут псевдоэлементами и детьми не\n * считаются), поэтому сопоставление по позиции однозначно.\n */\nfunction measureRendered(): void {\n if (!virtualEnabled.value) return\n // В состояниях loading/empty дети контейнера — скелетоны и заглушки: их\n // высоты записались бы в замеры под индексами будущих строк данных.\n if (!showItems.value) return\n\n const container = listEl.value\n if (!container) return\n\n const { start } = virtualizer.range.value\n const children = container.children\n for (let offset = 0; offset < children.length; offset++) {\n virtualizer.measure(start + offset, children[offset])\n }\n}\n\nonMounted(measureRendered)\nonUpdated(measureRendered)\n\nfunction scrollToIndex(index: number, align?: GrVirtualAlign): void {\n virtualizer.scrollToIndex(index, align)\n}\n\ndefineExpose({ scrollToIndex })\n\nif (__GR_DEV__) {\n watch(\n () => [props.virtual, dataMode.value, props.maxHeight] as const,\n ([virtual, hasItems, maxHeight]) => {\n if (!virtual) return\n\n if (!hasItems) {\n console.warn(\n '[granularity] GrList: `virtual` требует `items` — пункты из слота по умолчанию '\n + 'список не считает и резать окно ему не из чего.',\n )\n }\n else if (maxHeight === undefined) {\n console.warn(\n '[granularity] GrList: `virtual` требует `maxHeight` — без ограничения высоты '\n + 'вьюпорта нет, и в окно попадает весь набор.',\n )\n }\n },\n { immediate: true },\n )\n\n watch(\n () => [dataMode.value, Boolean(slots.item)] as const,\n ([hasItems, hasSlot]) => {\n if (hasItems && !hasSlot) {\n console.warn(\n '[granularity] GrList: с `items` пункты рисует слот `#item` — без него список пуст.',\n )\n }\n },\n { immediate: true },\n )\n\n onMounted(() => {\n if (!virtualEnabled.value) return\n\n const first = listEl.value?.firstElementChild\n if (first && !first.hasAttribute('aria-posinset')) {\n console.warn(\n '[granularity] GrList: при `virtual` пункты обязаны нести `aria-setsize`/`aria-posinset` — '\n + 'пробросьте `aria` из слота `#item`: <GrListItem v-bind=\"aria\" … />. Иначе диктор '\n + 'посчитает набор по отрисованному окну.',\n )\n }\n })\n}\n</script>\n\n<template>\n <GrCard :variant=\"variant\">\n <div\n ref=\"listEl\"\n data-gr-list\n role=\"list\"\n :class=\"listClass\"\n :style=\"listStyle\"\n :tabindex=\"maxHeight === undefined ? undefined : 0\"\n :data-gr-virtual=\"virtualEnabled ? '' : undefined\"\n :aria-busy=\"loading ? 'true' : undefined\"\n >\n <template v-if=\"loading\">\n <slot name=\"loading\">\n <div\n v-for=\"row in loadingRowCount\"\n :key=\"row\"\n data-gr-list-loading-row\n :class=\"loadingRowClass\"\n >\n <GrSkeleton />\n </div>\n </slot>\n </template>\n\n <div\n v-else-if=\"isEmpty\"\n data-gr-list-empty\n :class=\"emptyClass\"\n >\n <slot name=\"empty\">\n {{ resolvedEmptyText }}\n </slot>\n </div>\n\n <template v-else-if=\"dataMode\">\n <template\n v-for=\"entry in renderedItems\"\n :key=\"keyOf(entry.item, entry.index)\"\n >\n <slot\n name=\"item\"\n :item=\"entry.item\"\n :index=\"entry.index\"\n :aria=\"ariaOf(entry.index)\"\n />\n </template>\n </template>\n\n <slot v-else />\n </div>\n </GrCard>\n</template>\n\n<style scoped>\n/* Срезанное сверху и снизу держат псевдоэлементы контейнера: их не за что\n размонтировать, в дереве доступности их нет, а высота приезжает переменными\n в том же патче, что и пункты. Почему не отступы контейнера и не поля крайних\n пунктов — `docs/virtual-list.md`. */\n[data-gr-virtual]::before,\n[data-gr-virtual]::after {\n content: '';\n display: block;\n flex: none;\n}\n\n[data-gr-virtual]::before {\n height: var(--gr-virtual-before, 0px);\n}\n\n[data-gr-virtual]::after {\n height: var(--gr-virtual-after, 0px);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, markRaw, useSlots, watch, type Component } from 'vue'\n\nimport {\n grListItemPaddingClass,\n itemDescriptionClass,\n itemDisabledClass,\n itemHoverClass,\n itemInteractiveClass,\n itemLayoutClass,\n itemTitleClass,\n type GrListItemDensity,\n} from './grListStyles'\n\nexport type { GrListItemDensity } from './grListStyles'\n\nexport interface GrListItemProps {\n /** Заголовок строки. Если передан слот `#title`, проп игнорируется. */\n title?: string\n /** Описание под заголовком. Если передан слот `#description`, проп игнорируется. */\n description?: string\n /** Плотность вертикальных отступов: `regular` — 12px, `compact` — 8px. */\n density?: GrListItemDensity\n /** Ссылка: строка становится `<a>`. */\n href?: string\n /** Свой корневой тег строки (`RouterLink`, `Link` от Inertia). Сильнее `href`. */\n as?: string | Component\n /** Кликабельная строка без ссылки — `<button>` с событием `click`. */\n clickable?: boolean\n /** Подсветка при наведении без интерактивности. */\n hoverable?: boolean\n disabled?: boolean\n}\n\nexport interface GrListItemEmits {\n (e: 'click', event: MouseEvent): void\n}\n\nconst props = withDefaults(defineProps<GrListItemProps>(), {\n title: undefined,\n description: undefined,\n density: 'regular',\n href: undefined,\n as: undefined,\n clickable: false,\n hoverable: false,\n disabled: false,\n})\n\nconst emit = defineEmits<GrListItemEmits>()\n\nconst slots = useSlots()\n\nconst hasTitle = computed(() => !!slots.title || !!props.title)\nconst hasDescription = computed(() => !!slots.description || !!props.description)\n\nconst isInteractive = computed(() => !props.disabled && (!!props.as || !!props.href || props.clickable))\n\n/**\n * `role=\"listitem\"` остаётся на обёртке, а строка целиком — вложенный элемент:\n * `<a role=\"listitem\">` потерял бы роль ссылки, а интерактив снаружи разорвал бы\n * связку `list` → `listitem`. Обычная строка идёт по той же схеме, чтобы\n * разметка содержимого была написана один раз, а не по копии на ветку.\n */\nconst rowTag = computed<string | Component>(() => {\n if (!isInteractive.value)\n return 'div'\n\n if (props.as)\n return typeof props.as === 'string' ? props.as : markRaw(props.as)\n\n return props.href ? 'a' : 'button'\n})\n\nconst rowClass = computed(() => [\n itemLayoutClass,\n grListItemPaddingClass(props.density),\n isInteractive.value ? `${itemInteractiveClass} ${itemHoverClass}` : '',\n !isInteractive.value && props.hoverable && !props.disabled ? itemHoverClass : '',\n props.disabled ? itemDisabledClass : '',\n].filter(Boolean).join(' '))\n\nfunction onClick(event: MouseEvent): void {\n // `disabled` сюда попадает вместе с обычной строкой: и то и другое —\n // не-интерактив, а кликов не эмитит ни один.\n if (!isInteractive.value)\n return\n\n emit('click', event)\n}\n\n/** Теги, попадающие в таб-порядок сами. `<a>` — только со ссылкой. */\nfunction isFocusableTag(tag: string): boolean {\n return tag === 'button' || (tag === 'a' && !!props.href)\n}\n\nif (__GR_DEV__) {\n watch(\n () => [isInteractive.value, props.as] as const,\n ([interactive, as]) => {\n // Компонент из `as` (`RouterLink`, Inertia `Link`) рендерит `<a>`, но узнать\n // это до рендера нельзя — предупреждаем только по явно названному тегу.\n if (!interactive || typeof as !== 'string' || isFocusableTag(as)) return\n\n console.warn(\n `[granularity] GrListItem: as=\"${as}\" не попадает в таб-порядок — строка кликается `\n + 'мышью, но не с клавиатуры. Возьмите тег, умеющий фокус (`button`, `a` со ссылкой), '\n + 'или компонент роутера.',\n )\n },\n { immediate: true },\n )\n}\n</script>\n\n<template>\n <div\n data-gr-list-item\n role=\"listitem\"\n :aria-disabled=\"disabled ? 'true' : undefined\"\n >\n <component\n :is=\"rowTag\"\n :data-gr-list-item-action=\"isInteractive ? '' : undefined\"\n :type=\"rowTag === 'button' ? 'button' : undefined\"\n :href=\"isInteractive ? href : undefined\"\n :class=\"rowClass\"\n @click=\"onClick\"\n >\n <div v-if=\"$slots.prefix\" class=\"shrink-0\">\n <slot name=\"prefix\" />\n </div>\n <div class=\"min-w-0 flex-1\">\n <div v-if=\"hasTitle\" :class=\"itemTitleClass\">\n <slot name=\"title\">\n{{ title }}\n</slot>\n </div>\n <div\n v-if=\"hasDescription\"\n :class=\"itemDescriptionClass\"\n >\n <slot name=\"description\">\n{{ description }}\n</slot>\n </div>\n </div>\n <div v-if=\"$slots.default\" class=\"shrink-0\">\n <slot />\n </div>\n </component>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, markRaw, useSlots, watch, type Component } from 'vue'\n\nimport {\n grListItemPaddingClass,\n itemDescriptionClass,\n itemDisabledClass,\n itemHoverClass,\n itemInteractiveClass,\n itemLayoutClass,\n itemTitleClass,\n type GrListItemDensity,\n} from './grListStyles'\n\nexport type { GrListItemDensity } from './grListStyles'\n\nexport interface GrListItemProps {\n /** Заголовок строки. Если передан слот `#title`, проп игнорируется. */\n title?: string\n /** Описание под заголовком. Если передан слот `#description`, проп игнорируется. */\n description?: string\n /** Плотность вертикальных отступов: `regular` — 12px, `compact` — 8px. */\n density?: GrListItemDensity\n /** Ссылка: строка становится `<a>`. */\n href?: string\n /** Свой корневой тег строки (`RouterLink`, `Link` от Inertia). Сильнее `href`. */\n as?: string | Component\n /** Кликабельная строка без ссылки — `<button>` с событием `click`. */\n clickable?: boolean\n /** Подсветка при наведении без интерактивности. */\n hoverable?: boolean\n disabled?: boolean\n}\n\nexport interface GrListItemEmits {\n (e: 'click', event: MouseEvent): void\n}\n\nconst props = withDefaults(defineProps<GrListItemProps>(), {\n title: undefined,\n description: undefined,\n density: 'regular',\n href: undefined,\n as: undefined,\n clickable: false,\n hoverable: false,\n disabled: false,\n})\n\nconst emit = defineEmits<GrListItemEmits>()\n\nconst slots = useSlots()\n\nconst hasTitle = computed(() => !!slots.title || !!props.title)\nconst hasDescription = computed(() => !!slots.description || !!props.description)\n\nconst isInteractive = computed(() => !props.disabled && (!!props.as || !!props.href || props.clickable))\n\n/**\n * `role=\"listitem\"` остаётся на обёртке, а строка целиком — вложенный элемент:\n * `<a role=\"listitem\">` потерял бы роль ссылки, а интерактив снаружи разорвал бы\n * связку `list` → `listitem`. Обычная строка идёт по той же схеме, чтобы\n * разметка содержимого была написана один раз, а не по копии на ветку.\n */\nconst rowTag = computed<string | Component>(() => {\n if (!isInteractive.value)\n return 'div'\n\n if (props.as)\n return typeof props.as === 'string' ? props.as : markRaw(props.as)\n\n return props.href ? 'a' : 'button'\n})\n\nconst rowClass = computed(() => [\n itemLayoutClass,\n grListItemPaddingClass(props.density),\n isInteractive.value ? `${itemInteractiveClass} ${itemHoverClass}` : '',\n !isInteractive.value && props.hoverable && !props.disabled ? itemHoverClass : '',\n props.disabled ? itemDisabledClass : '',\n].filter(Boolean).join(' '))\n\nfunction onClick(event: MouseEvent): void {\n // `disabled` сюда попадает вместе с обычной строкой: и то и другое —\n // не-интерактив, а кликов не эмитит ни один.\n if (!isInteractive.value)\n return\n\n emit('click', event)\n}\n\n/** Теги, попадающие в таб-порядок сами. `<a>` — только со ссылкой. */\nfunction isFocusableTag(tag: string): boolean {\n return tag === 'button' || (tag === 'a' && !!props.href)\n}\n\nif (__GR_DEV__) {\n watch(\n () => [isInteractive.value, props.as] as const,\n ([interactive, as]) => {\n // Компонент из `as` (`RouterLink`, Inertia `Link`) рендерит `<a>`, но узнать\n // это до рендера нельзя — предупреждаем только по явно названному тегу.\n if (!interactive || typeof as !== 'string' || isFocusableTag(as)) return\n\n console.warn(\n `[granularity] GrListItem: as=\"${as}\" не попадает в таб-порядок — строка кликается `\n + 'мышью, но не с клавиатуры. Возьмите тег, умеющий фокус (`button`, `a` со ссылкой), '\n + 'или компонент роутера.',\n )\n },\n { immediate: true },\n )\n}\n</script>\n\n<template>\n <div\n data-gr-list-item\n role=\"listitem\"\n :aria-disabled=\"disabled ? 'true' : undefined\"\n >\n <component\n :is=\"rowTag\"\n :data-gr-list-item-action=\"isInteractive ? '' : undefined\"\n :type=\"rowTag === 'button' ? 'button' : undefined\"\n :href=\"isInteractive ? href : undefined\"\n :class=\"rowClass\"\n @click=\"onClick\"\n >\n <div v-if=\"$slots.prefix\" class=\"shrink-0\">\n <slot name=\"prefix\" />\n </div>\n <div class=\"min-w-0 flex-1\">\n <div v-if=\"hasTitle\" :class=\"itemTitleClass\">\n <slot name=\"title\">\n{{ title }}\n</slot>\n </div>\n <div\n v-if=\"hasDescription\"\n :class=\"itemDescriptionClass\"\n >\n <slot name=\"description\">\n{{ description }}\n</slot>\n </div>\n </div>\n <div v-if=\"$slots.default\" class=\"shrink-0\">\n <slot />\n </div>\n </component>\n </div>\n</template>\n"],"mappings":"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"}
|