@feugene/granularity 0.36.0 → 0.38.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/config-2ZHRsdOc.js.map +1 -1
- package/dist/chunks/config-2vrOFn2f.js.map +1 -1
- package/dist/chunks/config-3bQeQvqd.js.map +1 -1
- package/dist/chunks/config-87fszqnD.js.map +1 -1
- package/dist/chunks/config-8Pcutu1j.js.map +1 -1
- package/dist/chunks/config-A-bBiotF.js.map +1 -1
- package/dist/chunks/config-B0uvcTPH.js.map +1 -1
- package/dist/chunks/config-B3J0alyY.js.map +1 -1
- package/dist/chunks/config-B5SBUa6u.js.map +1 -1
- package/dist/chunks/config-B7W9VlZ7.js.map +1 -1
- package/dist/chunks/config-BDSaD-5p.js.map +1 -1
- package/dist/chunks/config-BEO7brpO.js.map +1 -1
- package/dist/chunks/config-BNu8lFQB.js.map +1 -1
- package/dist/chunks/config-BRESvs9g.js.map +1 -1
- package/dist/chunks/config-BTmfQFwi.js.map +1 -1
- package/dist/chunks/{config-CuQmUkMw2.js → config-BUNxHnEz.js} +1 -1
- package/dist/chunks/config-BUNxHnEz.js.map +1 -0
- package/dist/chunks/config-BaWANzzB.js.map +1 -1
- package/dist/chunks/config-BcXX7S7B.js.map +1 -1
- package/dist/chunks/config-Bd7QfiGj.js.map +1 -1
- package/dist/chunks/config-BhfNgyWL.js.map +1 -1
- package/dist/chunks/config-Bjn30smH.js.map +1 -1
- package/dist/chunks/config-BlJa1Mj9.js.map +1 -1
- package/dist/chunks/config-BoUI0t-t.js.map +1 -1
- package/dist/chunks/config-BqT6PMzO.js.map +1 -1
- package/dist/chunks/config-BscVxZR3.js.map +1 -1
- package/dist/chunks/config-BuGlNPE9.js.map +1 -1
- package/dist/chunks/config-C0Ea9ePP.js.map +1 -1
- package/dist/chunks/config-C0Eu8jpG.js.map +1 -1
- package/dist/chunks/config-CDat1wXf.js.map +1 -1
- package/dist/chunks/config-CIkdsWUk.js.map +1 -1
- package/dist/chunks/config-CJzmDHfI.js.map +1 -1
- package/dist/chunks/config-CLjjjPZj.js.map +1 -1
- package/dist/chunks/config-CMNfZO7S.js.map +1 -1
- package/dist/chunks/config-CQLNjc9X.js.map +1 -0
- package/dist/chunks/config-CT4lP6S4.js.map +1 -1
- package/dist/chunks/config-CWtfxkM9.js.map +1 -1
- package/dist/chunks/config-CYthTVK2.js.map +1 -1
- package/dist/chunks/config-CZPDPJlg.js.map +1 -1
- package/dist/chunks/config-CbEbLyDE.js.map +1 -1
- package/dist/chunks/config-ChPg-BQv.js.map +1 -1
- package/dist/chunks/config-CjKpUcmy.js.map +1 -1
- package/dist/chunks/config-CjWOTgLL.js.map +1 -1
- package/dist/chunks/config-CjXjhphs.js.map +1 -1
- package/dist/chunks/config-CnYdE3HZ.js.map +1 -0
- package/dist/chunks/config-CsCUuukB.js.map +1 -1
- package/dist/chunks/config-CvbVM4ep.js.map +1 -1
- package/dist/chunks/config-D0NzGYaE.js.map +1 -1
- package/dist/chunks/config-D0W8mGFD.js.map +1 -1
- package/dist/chunks/config-D82r782v.js.map +1 -1
- package/dist/chunks/config-DBi0GVce.js.map +1 -1
- package/dist/chunks/config-DBmdgoVf.js.map +1 -0
- package/dist/chunks/config-DCq4JySZ.js.map +1 -1
- package/dist/chunks/config-DGC00bTp.js.map +1 -1
- package/dist/chunks/config-DIDz_E5m.js.map +1 -1
- package/dist/chunks/config-DM0lsduD.js.map +1 -1
- package/dist/chunks/config-DNDgp9bB.js.map +1 -1
- package/dist/chunks/config-DOYz_SVK.js.map +1 -1
- package/dist/chunks/config-DRbojEzO.js.map +1 -1
- package/dist/chunks/config-DUNH02Fg.js.map +1 -1
- package/dist/chunks/config-DUbbGcfw.js.map +1 -1
- package/dist/chunks/config-DVCNvhIG.js.map +1 -0
- package/dist/chunks/config-DWL81-aM.js.map +1 -1
- package/dist/chunks/config-DZCUPHPU.js.map +1 -1
- package/dist/chunks/config-DeyT9dJl.js.map +1 -1
- package/dist/chunks/config-DolULhjJ.js.map +1 -1
- package/dist/chunks/config-DpAYidyY.js.map +1 -0
- package/dist/chunks/config-Droqn55j.js.map +1 -1
- package/dist/chunks/config-DwABJHEo.js.map +1 -1
- package/dist/chunks/config-E1cTyRVV.js.map +1 -1
- package/dist/chunks/config-MmPym-Z7.js.map +1 -0
- package/dist/chunks/config-QaIc_gRa.js.map +1 -1
- package/dist/chunks/config-Yy67uWHY.js.map +1 -1
- package/dist/chunks/config-Z5LeUXys.js.map +1 -1
- package/dist/chunks/config-_jbgsFpw.js.map +1 -1
- package/dist/chunks/config-eXB87DOi.js.map +1 -1
- package/dist/chunks/config-rLkc9Zn2.js.map +1 -1
- package/dist/chunks/{context-CTbuwcYs.js → context-CPMRfwVr.js} +1 -1
- package/dist/chunks/{context-CTbuwcYs.js.map → context-CPMRfwVr.js.map} +1 -1
- package/dist/chunks/devHook-CiqijpNM.js +44 -0
- package/dist/chunks/devHook-CiqijpNM.js.map +1 -0
- package/dist/chunks/{grRadioStyles-DGL2QLy6.js → grRadioStyles-BwRyCjQQ.js} +4 -2
- package/dist/chunks/{grRadioStyles-DGL2QLy6.js.map → grRadioStyles-BwRyCjQQ.js.map} +1 -1
- package/dist/chunks/{granular-provider-Bp_BCBnr.js → granular-provider-DP2gk0oa.js} +9 -8
- package/dist/chunks/{granular-provider-Bp_BCBnr.js.map → granular-provider-DP2gk0oa.js.map} +1 -1
- package/dist/chunks/{overlayStack-0CZS99Da.js → overlayStack-DH4Z7am1.js} +45 -25
- package/dist/chunks/overlayStack-DH4Z7am1.js.map +1 -0
- package/dist/chunks/{renamedProp-BwcN-SJg.js → renamedProp-Dy96tT-F.js} +1 -1
- package/dist/chunks/{renamedProp-BwcN-SJg.js.map → renamedProp-Dy96tT-F.js.map} +1 -1
- package/dist/chunks/{useModalOverlay-BnAGZpcI.js → useModalOverlay-ACRkflzx.js} +3 -3
- package/dist/chunks/{useModalOverlay-BnAGZpcI.js.map → useModalOverlay-ACRkflzx.js.map} +1 -1
- package/dist/chunks/{usePortalTarget-C0p-mhaM.js → usePortalTarget-BhrRgdqW.js} +1 -1
- package/dist/chunks/{usePortalTarget-C0p-mhaM.js.map → usePortalTarget-BhrRgdqW.js.map} +1 -1
- package/dist/chunks/{validation-5HhpanPP.js → validation-BDjqsuGB.js} +1 -1
- package/dist/chunks/{validation-5HhpanPP.js.map → validation-BDjqsuGB.js.map} +1 -1
- package/dist/components/GrAlert/chunks/{GrAlert-FAVf-ksH.js → GrAlert-D72y1IEh.js} +1 -1
- package/dist/components/GrAlert/chunks/{GrAlert-FAVf-ksH.js.map → GrAlert-D72y1IEh.js.map} +1 -1
- package/dist/components/GrAlert/index.js +1 -1
- package/dist/components/GrAutocomplete/chunks/{GrAutocomplete-BQsSE7sj.js → GrAutocomplete-BpmFmpKs.js} +4 -6
- package/dist/components/GrAutocomplete/chunks/{GrAutocomplete-BQsSE7sj.js.map → GrAutocomplete-BpmFmpKs.js.map} +1 -1
- package/dist/components/GrAutocomplete/index.js +1 -1
- package/dist/components/GrAvatar/chunks/{GrAvatar-CNmLmgcr.js → GrAvatar-dWEXiCJG.js} +1 -1
- package/dist/components/GrAvatar/chunks/{GrAvatar-CNmLmgcr.js.map → GrAvatar-dWEXiCJG.js.map} +1 -1
- package/dist/components/GrAvatar/index.js +1 -1
- package/dist/components/GrBadge/chunks/{GrBadge-DpUDXKbq.js → GrBadge-DBeQXEir.js} +2 -2
- package/dist/components/GrBadge/chunks/{GrBadge-DpUDXKbq.js.map → GrBadge-DBeQXEir.js.map} +1 -1
- package/dist/components/GrBadge/index.js +1 -1
- package/dist/components/GrBadgeWrap/chunks/{GrBadgeWrap-B-A3PRkE.js → GrBadgeWrap-wdfxaXiu.js} +3 -1
- package/dist/components/GrBadgeWrap/chunks/{GrBadgeWrap-B-A3PRkE.js.map → GrBadgeWrap-wdfxaXiu.js.map} +1 -1
- package/dist/components/GrBadgeWrap/index.js +1 -1
- package/dist/components/GrBottomNav/chunks/{GrBottomNav-wtFatLjB.js → GrBottomNav-nE08pVUJ.js} +1 -1
- package/dist/components/GrBottomNav/chunks/{GrBottomNav-wtFatLjB.js.map → GrBottomNav-nE08pVUJ.js.map} +1 -1
- package/dist/components/GrBottomNav/index.js +1 -1
- package/dist/components/GrBreadcrumbs/chunks/{GrBreadcrumbs-DRDNan_n.js → GrBreadcrumbs-DOpwY2EF.js} +3 -3
- package/dist/components/GrBreadcrumbs/chunks/{GrBreadcrumbs-DRDNan_n.js.map → GrBreadcrumbs-DOpwY2EF.js.map} +1 -1
- package/dist/components/GrBreadcrumbs/index.js +1 -1
- package/dist/components/GrButton/chunks/{GrButton-7GP0zObj.js → GrButton-CS4TqMtG.js} +1 -1
- package/dist/components/GrButton/chunks/{GrButton-7GP0zObj.js.map → GrButton-CS4TqMtG.js.map} +1 -1
- package/dist/components/GrButton/index.js +1 -1
- package/dist/components/GrButtonGroup/chunks/GrButtonGroup-CKyiAV8K.js.map +1 -1
- package/dist/components/GrButtonGroup/index.js +1 -1
- package/dist/components/GrCard/chunks/{GrCard-BttXz1pQ.js → GrCard-Ck5--lMF.js} +2 -2
- package/dist/components/GrCard/chunks/{GrCard-BttXz1pQ.js.map → GrCard-Ck5--lMF.js.map} +1 -1
- package/dist/components/GrCard/index.js +1 -1
- package/dist/components/GrCheckbox/chunks/{GrCheckbox-CEcHIsXM.js → GrCheckbox-Dp201frb.js} +1 -1
- package/dist/components/GrCheckbox/chunks/{GrCheckbox-CEcHIsXM.js.map → GrCheckbox-Dp201frb.js.map} +1 -1
- package/dist/components/GrCheckbox/index.js +1 -1
- package/dist/components/GrCheckboxGroup/chunks/{GrCheckboxGroup-BOwrVcFr.js → GrCheckboxGroup-IpqG5qwm.js} +2 -2
- package/dist/components/GrCheckboxGroup/chunks/{GrCheckboxGroup-BOwrVcFr.js.map → GrCheckboxGroup-IpqG5qwm.js.map} +1 -1
- package/dist/components/GrCheckboxGroup/index.js +1 -1
- package/dist/components/GrChip/chunks/{GrChip-BTivtwff.js → GrChip-CKqyzYcv.js} +10 -7
- package/dist/components/GrChip/chunks/{GrChip-BTivtwff.js.map → GrChip-CKqyzYcv.js.map} +1 -1
- package/dist/components/GrChip/index.js +1 -1
- package/dist/components/GrChipGroup/chunks/{GrChipGroup-C78Vcwos.js → GrChipGroup-dDGwnC04.js} +1 -1
- package/dist/components/GrChipGroup/chunks/{GrChipGroup-C78Vcwos.js.map → GrChipGroup-dDGwnC04.js.map} +1 -1
- package/dist/components/GrChipGroup/index.js +1 -1
- package/dist/components/GrCodeBlock/chunks/{GrCodeBlock-Dp0eEBTZ.js → GrCodeBlock-BUOwKwQE.js} +2 -2
- package/dist/components/GrCodeBlock/chunks/{GrCodeBlock-Dp0eEBTZ.js.map → GrCodeBlock-BUOwKwQE.js.map} +1 -1
- package/dist/components/GrCodeBlock/index.js +1 -1
- package/dist/components/GrCollapse/chunks/{GrCollapse-BlW6ybdL.js → GrCollapse-DWH3QAnU.js} +6 -5
- package/dist/components/GrCollapse/chunks/{GrCollapse-BlW6ybdL.js.map → GrCollapse-DWH3QAnU.js.map} +1 -1
- package/dist/components/GrCollapse/index.js +1 -1
- package/dist/components/GrColorPicker/chunks/{GrColorPicker-B-L4CWMt.js → GrColorPicker-BwzRLs-i.js} +4 -4
- package/dist/components/GrColorPicker/chunks/{GrColorPicker-B-L4CWMt.js.map → GrColorPicker-BwzRLs-i.js.map} +1 -1
- package/dist/components/GrColorPicker/index.js +1 -1
- package/dist/components/GrCommandPalette/chunks/{GrCommandPalette-8HkYmMVP.js → GrCommandPalette-CnzPuoIM.js} +7 -7
- package/dist/components/GrCommandPalette/chunks/{GrCommandPalette-8HkYmMVP.js.map → GrCommandPalette-CnzPuoIM.js.map} +1 -1
- package/dist/components/GrCommandPalette/index.js +1 -1
- package/dist/components/GrConfigProvider/chunks/{GrConfigProvider-BF7R3hgg.js → GrConfigProvider-t9x2UToH.js} +13 -12
- package/dist/components/GrConfigProvider/chunks/{GrConfigProvider-BF7R3hgg.js.map → GrConfigProvider-t9x2UToH.js.map} +1 -1
- package/dist/components/GrConfigProvider/index.js +2 -2
- package/dist/components/GrConfirmDialog/chunks/{GrConfirmDialog-CZEO2JR4.js → GrConfirmDialog-e15nFI8M.js} +4 -4
- package/dist/components/GrConfirmDialog/chunks/{GrConfirmDialog-CZEO2JR4.js.map → GrConfirmDialog-e15nFI8M.js.map} +1 -1
- package/dist/components/GrConfirmDialog/index.js +1 -1
- package/dist/components/GrContextMenu/chunks/{GrContextMenu-DlFWm0eW.js → GrContextMenu-RpxMHQXH.js} +1 -1
- package/dist/components/GrContextMenu/chunks/{GrContextMenu-DlFWm0eW.js.map → GrContextMenu-RpxMHQXH.js.map} +1 -1
- package/dist/components/GrContextMenu/index.js +1 -1
- package/dist/components/GrDataTable/chunks/{GrDataTable-4JAgOQ-7.js → GrDataTable-BYYG_XZA.js} +5 -5
- package/dist/components/GrDataTable/chunks/{GrDataTable-4JAgOQ-7.js.map → GrDataTable-BYYG_XZA.js.map} +1 -1
- package/dist/components/GrDataTable/index.js +1 -1
- package/dist/components/GrDelta/chunks/{GrDelta-BZwkgQW-.js → GrDelta-BpfqebEV.js} +0 -0
- package/dist/components/GrDelta/chunks/{GrDelta-BZwkgQW-.js.map → GrDelta-BpfqebEV.js.map} +1 -1
- package/dist/components/GrDelta/index.js +1 -1
- package/dist/components/GrDescriptionList/chunks/{GrDescriptionList-BMeCgFV3.js → GrDescriptionList-D8ANFpn_.js} +2 -2
- package/dist/components/GrDescriptionList/chunks/{GrDescriptionList-BMeCgFV3.js.map → GrDescriptionList-D8ANFpn_.js.map} +1 -1
- package/dist/components/GrDescriptionList/index.js +2 -2
- package/dist/components/GrDialog/chunks/{GrDialog-CKNbnxwO.js → GrDialog-DDz_OEhk.js} +3 -3
- package/dist/components/GrDialog/chunks/{GrDialog-CKNbnxwO.js.map → GrDialog-DDz_OEhk.js.map} +1 -1
- package/dist/components/GrDialog/index.js +1 -1
- package/dist/components/GrDialogService/chunks/{GrDialogService-Ut8iiBQh.js → GrDialogService-BmXiYmUp.js} +9 -8
- package/dist/components/GrDialogService/chunks/{GrDialogService-Ut8iiBQh.js.map → GrDialogService-BmXiYmUp.js.map} +1 -1
- package/dist/components/GrDialogService/index.js +1 -1
- package/dist/components/GrDivider/chunks/{GrDivider-BXavGcMX.js → GrDivider-DxA-zV25.js} +1 -1
- package/dist/components/GrDivider/chunks/{GrDivider-BXavGcMX.js.map → GrDivider-DxA-zV25.js.map} +1 -1
- package/dist/components/GrDivider/index.js +1 -1
- package/dist/components/GrDrawer/chunks/{GrDrawer-xfoy-MCj.js → GrDrawer-BRMHhvbd.js} +5 -5
- package/dist/components/GrDrawer/chunks/{GrDrawer-xfoy-MCj.js.map → GrDrawer-BRMHhvbd.js.map} +1 -1
- package/dist/components/GrDrawer/index.js +1 -1
- package/dist/components/GrDropdown/chunks/{GrDropdown-7r772-Cq.js → GrDropdown-cLZuZMEY.js} +6 -8
- package/dist/components/GrDropdown/chunks/{GrDropdown-7r772-Cq.js.map → GrDropdown-cLZuZMEY.js.map} +1 -1
- package/dist/components/GrDropdown/index.js +1 -1
- package/dist/components/GrDropdownMenu/chunks/{GrDropdownMenu-DNurbsWc.js → GrDropdownMenu-Dxkz_ry4.js} +1 -1
- package/dist/components/GrDropdownMenu/chunks/{GrDropdownMenu-DNurbsWc.js.map → GrDropdownMenu-Dxkz_ry4.js.map} +1 -1
- package/dist/components/GrDropdownMenu/chunks/GrDropdownMenuList-B33ZzwfB.js.map +1 -1
- package/dist/components/GrDropdownMenu/index.js +1 -1
- package/dist/components/GrEmptyState/chunks/{GrEmptyState-RqsWvgI9.js → GrEmptyState-BbvwE-E0.js} +2 -2
- package/dist/components/GrEmptyState/chunks/{GrEmptyState-RqsWvgI9.js.map → GrEmptyState-BbvwE-E0.js.map} +1 -1
- package/dist/components/GrEmptyState/index.js +1 -1
- package/dist/components/GrFilePreview/chunks/{GrFilePreview-C9Z7hMK8.js → GrFilePreview-PabkLgYX.js} +1 -1
- package/dist/components/GrFilePreview/chunks/{GrFilePreview-C9Z7hMK8.js.map → GrFilePreview-PabkLgYX.js.map} +1 -1
- package/dist/components/GrFilePreview/index.js +1 -1
- package/dist/components/GrFileUpload/chunks/{GrFileUpload-C0ROpmOY.js → GrFileUpload-BwMR71zN.js} +4 -4
- package/dist/components/GrFileUpload/chunks/{GrFileUpload-C0ROpmOY.js.map → GrFileUpload-BwMR71zN.js.map} +1 -1
- package/dist/components/GrFileUpload/index.js +1 -1
- package/dist/components/GrForm/chunks/{GrForm-CrnfCsgj.js → GrForm-xTtavR-K.js} +1 -1
- package/dist/components/GrForm/chunks/{GrForm-CrnfCsgj.js.map → GrForm-xTtavR-K.js.map} +1 -1
- package/dist/components/GrForm/index.js +2 -2
- package/dist/components/GrFormField/chunks/{GrFormField-DSylU7IU.js → GrFormField-DO61vxpS.js} +2 -2
- package/dist/components/GrFormField/chunks/{GrFormField-DSylU7IU.js.map → GrFormField-DO61vxpS.js.map} +1 -1
- package/dist/components/GrFormField/index.js +1 -1
- package/dist/components/GrFormFile/chunks/{GrFormFile-B1jGZyIj.js → GrFormFile-Dlxx-9zi.js} +3 -3
- package/dist/components/GrFormFile/chunks/{GrFormFile-B1jGZyIj.js.map → GrFormFile-Dlxx-9zi.js.map} +1 -1
- package/dist/components/GrFormFile/index.js +1 -1
- package/dist/components/GrFormSection/chunks/{GrFormSection-ejieOg9H.js → GrFormSection-DV1S6R_N.js} +1 -1
- package/dist/components/GrFormSection/chunks/{GrFormSection-ejieOg9H.js.map → GrFormSection-DV1S6R_N.js.map} +1 -1
- package/dist/components/GrFormSection/index.js +1 -1
- package/dist/components/GrIcon/chunks/{GrIcon-Dmf0c9ba.js → GrIcon-DTZIgmje.js} +1 -1
- package/dist/components/GrIcon/chunks/{GrIcon-Dmf0c9ba.js.map → GrIcon-DTZIgmje.js.map} +1 -1
- package/dist/components/GrIcon/index.js +1 -1
- package/dist/components/GrImageViewer/chunks/{GrImageViewer--BU8yrfx.js → GrImageViewer-CcduIFsa.js} +2 -4
- package/dist/components/GrImageViewer/chunks/{GrImageViewer--BU8yrfx.js.map → GrImageViewer-CcduIFsa.js.map} +1 -1
- package/dist/components/GrImageViewer/index.js +1 -1
- package/dist/components/GrInput/chunks/{GrInput-CJ7eu3fR.js → GrInput-TFeBm3gQ.js} +1 -1
- package/dist/components/GrInput/chunks/{GrInput-CJ7eu3fR.js.map → GrInput-TFeBm3gQ.js.map} +1 -1
- package/dist/components/GrInput/index.js +1 -1
- package/dist/components/GrInputTag/chunks/{GrInputTag-ByD9NugQ.js → GrInputTag-DhfUNOA5.js} +3 -3
- package/dist/components/GrInputTag/chunks/{GrInputTag-ByD9NugQ.js.map → GrInputTag-DhfUNOA5.js.map} +1 -1
- package/dist/components/GrInputTag/index.js +1 -1
- package/dist/components/GrJsonViewer/chunks/{GrJsonViewer-BXG7DdTe.js → GrJsonViewer-CdtmUpZ3.js} +6 -6
- package/dist/components/GrJsonViewer/chunks/{GrJsonViewer-BXG7DdTe.js.map → GrJsonViewer-CdtmUpZ3.js.map} +1 -1
- package/dist/components/GrJsonViewer/index.js +2 -2
- package/dist/components/GrKbd/chunks/{GrKbd-D2ga1H9i.js → GrKbd-D_a1mPLe.js} +1 -1
- package/dist/components/GrKbd/chunks/{GrKbd-D2ga1H9i.js.map → GrKbd-D_a1mPLe.js.map} +1 -1
- package/dist/components/GrKbd/index.js +1 -1
- package/dist/components/GrLink/chunks/{GrLink-DYXw3W9w.js → GrLink-DwWErumU.js} +2 -2
- package/dist/components/GrLink/chunks/{GrLink-DYXw3W9w.js.map → GrLink-DwWErumU.js.map} +1 -1
- package/dist/components/GrLink/index.js +2 -2
- package/dist/components/GrList/chunks/{GrList-B_CRJB4c.js → GrList-Unya-LXb.js} +3 -3
- package/dist/components/GrList/chunks/{GrList-B_CRJB4c.js.map → GrList-Unya-LXb.js.map} +1 -1
- package/dist/components/GrList/index.js +1 -1
- package/dist/components/GrLoading/chunks/{GrLoading-CMD3Ri9P.js → GrLoading-BDrlrup_.js} +1 -1
- package/dist/components/GrLoading/chunks/{GrLoading-CMD3Ri9P.js.map → GrLoading-BDrlrup_.js.map} +1 -1
- package/dist/components/GrLoading/index.js +1 -1
- package/dist/components/GrModal/chunks/{GrModal-DixnhJcu.js → GrModal-Cm0mtSXk.js} +4 -4
- package/dist/components/GrModal/chunks/{GrModal-DixnhJcu.js.map → GrModal-Cm0mtSXk.js.map} +1 -1
- package/dist/components/GrModal/index.js +1 -1
- package/dist/components/GrNavbar/chunks/{GrNavbar-Dr8e5kW-.js → GrNavbar-BRGuksiy.js} +3 -3
- package/dist/components/GrNavbar/chunks/{GrNavbar-Dr8e5kW-.js.map → GrNavbar-BRGuksiy.js.map} +1 -1
- package/dist/components/GrNavbar/index.js +1 -1
- package/dist/components/GrNumberInput/chunks/{GrNumberInput-DQ59YICu.js → GrNumberInput-BqPydXpK.js} +7 -9
- package/dist/components/GrNumberInput/chunks/{GrNumberInput-DQ59YICu.js.map → GrNumberInput-BqPydXpK.js.map} +1 -1
- package/dist/components/GrNumberInput/index.js +2 -2
- package/dist/components/GrPagination/chunks/{GrPagination-68iyAwQz.js → GrPagination-DhbNZHEF.js} +4 -4
- package/dist/components/GrPagination/chunks/{GrPagination-68iyAwQz.js.map → GrPagination-DhbNZHEF.js.map} +1 -1
- package/dist/components/GrPagination/index.js +1 -1
- package/dist/components/GrPopover/chunks/{GrPopover-DEFxDffj.js → GrPopover-CcWvr1ZA.js} +3 -3
- package/dist/components/GrPopover/chunks/{GrPopover-DEFxDffj.js.map → GrPopover-CcWvr1ZA.js.map} +1 -1
- package/dist/components/GrPopover/index.js +2 -2
- package/dist/components/GrProgressBar/chunks/{GrProgressBar-Cd7AaJtR.js → GrProgressBar-C3NRBqIP.js} +2 -2
- package/dist/components/GrProgressBar/chunks/{GrProgressBar-Cd7AaJtR.js.map → GrProgressBar-C3NRBqIP.js.map} +1 -1
- package/dist/components/GrProgressBar/index.js +1 -1
- package/dist/components/GrProgressCircle/chunks/{GrProgressCircle-D3yWXYDa.js → GrProgressCircle-CdvbINQc.js} +2 -2
- package/dist/components/GrProgressCircle/chunks/{GrProgressCircle-D3yWXYDa.js.map → GrProgressCircle-CdvbINQc.js.map} +1 -1
- package/dist/components/GrProgressCircle/index.js +1 -1
- package/dist/components/GrPromptDialog/chunks/{GrPromptDialog-Ckeombia.js → GrPromptDialog-8RxQsOQ_.js} +8 -8
- package/dist/components/GrPromptDialog/chunks/{GrPromptDialog-Ckeombia.js.map → GrPromptDialog-8RxQsOQ_.js.map} +1 -1
- package/dist/components/GrPromptDialog/index.js +1 -1
- package/dist/components/GrRadio/chunks/{GrRadio-Aq3v8XgG.js → GrRadio-B1776V9D.js} +2 -2
- package/dist/components/GrRadio/chunks/{GrRadio-Aq3v8XgG.js.map → GrRadio-B1776V9D.js.map} +1 -1
- package/dist/components/GrRadio/index.js +2 -2
- package/dist/components/GrRadioGroup/chunks/{GrRadioGroup-y9LVdTWh.js → GrRadioGroup-UgitrBFl.js} +2 -2
- package/dist/components/GrRadioGroup/chunks/{GrRadioGroup-y9LVdTWh.js.map → GrRadioGroup-UgitrBFl.js.map} +1 -1
- package/dist/components/GrRadioGroup/index.js +1 -1
- package/dist/components/GrRating/chunks/{GrRating-IEiTOdnF.js → GrRating-DTriyyqG.js} +1 -1
- package/dist/components/GrRating/chunks/{GrRating-IEiTOdnF.js.map → GrRating-DTriyyqG.js.map} +1 -1
- package/dist/components/GrRating/index.js +1 -1
- package/dist/components/GrResponseErrorBanner/chunks/{GrResponseErrorBanner-DMeOWlzj.js → GrResponseErrorBanner-NEEJZ8zi.js} +2 -2
- package/dist/components/GrResponseErrorBanner/chunks/{GrResponseErrorBanner-DMeOWlzj.js.map → GrResponseErrorBanner-NEEJZ8zi.js.map} +1 -1
- package/dist/components/GrResponseErrorBanner/chunks/{GrResponseErrorBanner-CBVRpgIG.js → GrResponseErrorBanner-ZvBvEQDZ.js} +1 -1
- package/dist/components/GrResponseErrorBanner/chunks/{GrResponseErrorBanner-CBVRpgIG.js.map → GrResponseErrorBanner-ZvBvEQDZ.js.map} +1 -1
- package/dist/components/GrResponseErrorBanner/index.js +2 -2
- package/dist/components/GrSegmented/chunks/{GrSegmented-CB-VR-yL.js → GrSegmented-NKTi9bMG.js} +1 -1
- package/dist/components/GrSegmented/chunks/{GrSegmented-CB-VR-yL.js.map → GrSegmented-NKTi9bMG.js.map} +1 -1
- package/dist/components/GrSegmented/index.js +1 -1
- package/dist/components/GrSelect/chunks/{GrSelect-Bm3Jugx8.js → GrSelect-Do1iAyp1.js} +6 -6
- package/dist/components/GrSelect/chunks/{GrSelect-Bm3Jugx8.js.map → GrSelect-Do1iAyp1.js.map} +1 -1
- package/dist/components/GrSelect/index.js +1 -1
- package/dist/components/GrSidebar/chunks/{GrSidebar-alkwBUzL.js → GrSidebar-DoKLlHSw.js} +2 -2
- package/dist/components/GrSidebar/chunks/{GrSidebar-alkwBUzL.js.map → GrSidebar-DoKLlHSw.js.map} +1 -1
- package/dist/components/GrSidebar/index.js +1 -1
- package/dist/components/GrSkeleton/chunks/GrSkeleton-CgfXxPQ2.js.map +1 -1
- package/dist/components/GrSlider/chunks/{GrSlider-DM6W6U9c.js → GrSlider-Db-jACXz.js} +6 -4
- package/dist/components/GrSlider/chunks/{GrSlider-DM6W6U9c.js.map → GrSlider-Db-jACXz.js.map} +1 -1
- package/dist/components/GrSlider/index.js +1 -1
- package/dist/components/GrSortableList/chunks/{GrSortableList-HyKX--rO.js → GrSortableList-CQCaWivn.js} +1 -1
- package/dist/components/GrSortableList/chunks/{GrSortableList-HyKX--rO.js.map → GrSortableList-CQCaWivn.js.map} +1 -1
- package/dist/components/GrSortableList/index.js +1 -1
- package/dist/components/GrSplitter/chunks/{GrSplitter-MltjaADn.js → GrSplitter-C-XQzq7q.js} +1 -1
- package/dist/components/GrSplitter/chunks/{GrSplitter-MltjaADn.js.map → GrSplitter-C-XQzq7q.js.map} +1 -1
- package/dist/components/GrSplitter/index.js +1 -1
- package/dist/components/GrStatistic/chunks/{GrStatistic-BoLkXw7F.js → GrStatistic-C1_nY4Gc.js} +2 -2
- package/dist/components/GrStatistic/chunks/{GrStatistic-BoLkXw7F.js.map → GrStatistic-C1_nY4Gc.js.map} +1 -1
- package/dist/components/GrStatistic/index.js +1 -1
- package/dist/components/GrSteps/chunks/{GrSteps-CpGHXLOc.js → GrSteps-DXj7m-WO.js} +2 -2
- package/dist/components/GrSteps/chunks/{GrSteps-CpGHXLOc.js.map → GrSteps-DXj7m-WO.js.map} +1 -1
- package/dist/components/GrSteps/index.js +1 -1
- package/dist/components/GrSwitch/chunks/{GrSwitch-7Oe5QfQZ.js → GrSwitch-CriHWT2A.js} +1 -1
- package/dist/components/GrSwitch/chunks/{GrSwitch-7Oe5QfQZ.js.map → GrSwitch-CriHWT2A.js.map} +1 -1
- package/dist/components/GrSwitch/index.js +1 -1
- package/dist/components/GrTabPanels/chunks/{GrTabPanels-BZQS8-JM.js → GrTabPanels-5zsbqS1v.js} +1 -1
- package/dist/components/GrTabPanels/chunks/{GrTabPanels-BZQS8-JM.js.map → GrTabPanels-5zsbqS1v.js.map} +1 -1
- package/dist/components/GrTabPanels/index.js +1 -1
- package/dist/components/GrTable/chunks/{GrTable-jhUTczjv.js → GrTable-iwzHvlMC.js} +1 -1
- package/dist/components/GrTable/chunks/{GrTable-jhUTczjv.js.map → GrTable-iwzHvlMC.js.map} +1 -1
- package/dist/components/GrTable/index.js +1 -1
- package/dist/components/GrTabs/chunks/{GrTabs-CT2nk_HV.js → GrTabs-DHfv67cp.js} +1 -1
- package/dist/components/GrTabs/chunks/{GrTabs-CT2nk_HV.js.map → GrTabs-DHfv67cp.js.map} +1 -1
- package/dist/components/GrTabs/index.js +1 -1
- package/dist/components/GrTextarea/chunks/{GrTextarea-DinXG12-.js → GrTextarea-BII3w61K.js} +1 -1
- package/dist/components/GrTextarea/chunks/{GrTextarea-DinXG12-.js.map → GrTextarea-BII3w61K.js.map} +1 -1
- package/dist/components/GrTextarea/index.js +1 -1
- package/dist/components/GrTimeline/chunks/{GrTimeline-BiHpArsg.js → GrTimeline-Bs60huw0.js} +2 -2
- package/dist/components/GrTimeline/chunks/{GrTimeline-BiHpArsg.js.map → GrTimeline-Bs60huw0.js.map} +1 -1
- package/dist/components/GrTimeline/index.js +1 -1
- package/dist/components/GrToaster/chunks/{GrToaster-COXIBKzq.js → GrToaster-D-sV-9of.js} +4 -4
- package/dist/components/GrToaster/chunks/{GrToaster-COXIBKzq.js.map → GrToaster-D-sV-9of.js.map} +1 -1
- package/dist/components/GrToaster/index.js +1 -1
- package/dist/components/GrTooltip/chunks/{GrTooltip-uYETdbCD.js → GrTooltip-BUiTbROv.js} +3 -3
- package/dist/components/GrTooltip/chunks/{GrTooltip-uYETdbCD.js.map → GrTooltip-BUiTbROv.js.map} +1 -1
- package/dist/components/GrTooltip/index.js +1 -1
- package/dist/components/GrTree/chunks/{GrTree-Ci6CZfKR.js → GrTree-CkELQwR8.js} +1 -2
- package/dist/components/GrTree/chunks/{GrTree-Ci6CZfKR.js.map → GrTree-CkELQwR8.js.map} +1 -1
- package/dist/components/GrTree/index.js +1 -1
- package/dist/components/GrTreeSelect/chunks/{GrTreeSelect-MGsyvkJV.js → GrTreeSelect-DurwZ7Cz.js} +4 -4
- package/dist/components/GrTreeSelect/chunks/{GrTreeSelect-MGsyvkJV.js.map → GrTreeSelect-DurwZ7Cz.js.map} +1 -1
- package/dist/components/GrTreeSelect/index.js +1 -1
- package/dist/components/GrValue/chunks/GrValue-ezkQM05Q.js.map +1 -1
- package/dist/composables/useFloating.js +1 -1
- package/dist/composables/useGrComponentConfig.js +1 -1
- package/dist/composables/useOverlayLayer.js +35 -6
- package/dist/composables/useOverlayLayer.js.map +1 -1
- package/dist/composables/usePortalTarget.js +1 -1
- package/dist/composables/useTheme.js +1 -1
- package/dist/composables/useTheme.js.map +1 -1
- package/dist/composables/useToast.js +1 -1
- package/dist/composables/useToast.js.map +1 -1
- package/dist/composables/useVirtualList.js +26 -6
- package/dist/composables/useVirtualList.js.map +1 -1
- 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 +86 -86
- package/dist/testing.js +1 -1
- package/dist/testing.js.map +1 -1
- package/dist/types/components/GrDialog/GrDialog.vue.d.ts +1 -1
- package/dist/types/components/GrDrawer/GrDrawer.vue.d.ts +1 -1
- package/dist/types/components/GrModal/GrModal.vue.d.ts +1 -1
- package/dist/types/composables/internal/overlayStack.d.ts +11 -0
- package/dist/types/internal/devHook.d.ts +119 -0
- package/dist/vue/index.js +2 -2
- package/dist/vue/index.js.map +1 -1
- package/dist/web-types.json +1 -1
- package/package.json +16 -16
- package/dist/chunks/config-CQLNjc9X2.js.map +0 -1
- package/dist/chunks/config-CnYdE3HZ2.js.map +0 -1
- package/dist/chunks/config-CuQmUkMw2.js.map +0 -1
- package/dist/chunks/config-DBmdgoVf2.js.map +0 -1
- package/dist/chunks/config-DVCNvhIG2.js.map +0 -1
- package/dist/chunks/config-DpAYidyY2.js.map +0 -1
- package/dist/chunks/config-MmPym-Z72.js.map +0 -1
- package/dist/chunks/overlayStack-0CZS99Da.js.map +0 -1
- /package/dist/chunks/{config-CQLNjc9X2.js → config-CQLNjc9X.js} +0 -0
- /package/dist/chunks/{config-CnYdE3HZ2.js → config-CnYdE3HZ.js} +0 -0
- /package/dist/chunks/{config-DBmdgoVf2.js → config-DBmdgoVf.js} +0 -0
- /package/dist/chunks/{config-DVCNvhIG2.js → config-DVCNvhIG.js} +0 -0
- /package/dist/chunks/{config-DpAYidyY2.js → config-DpAYidyY.js} +0 -0
- /package/dist/chunks/{config-MmPym-Z72.js → config-MmPym-Z7.js} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GrStatistic-BoLkXw7F.js","names":["$slots"],"sources":["../../../../src/components/GrStatistic/countUp.ts","../../../../src/components/GrStatistic/formatStatisticValue.ts","../../../../src/components/GrStatistic/GrStatistic.vue","../../../../src/components/GrStatistic/GrStatistic.vue"],"sourcesContent":["/**\n * Перебор чисел для `GrStatistic`: чистая арифметика кадра без Vue и таймеров.\n *\n * Вынесено отдельным модулем по той же причине, что и `formatStatisticValue`:\n * кривая и округление проверяются напрямую, без монтирования и подмены `rAF`.\n */\n\n/** Быстрый старт, мягкая остановка — та же форма, что у `--gr-ease-out`. */\nexport function easeOutCubic(t: number): number {\n const clamped = Math.min(Math.max(t, 0), 1)\n return 1 - (1 - clamped) ** 3\n}\n\n/**\n * Значение кадра на `elapsed` миллисекунде перехода `from → to`.\n *\n * Округление до `precision` обязательно: без него кадры показывали бы\n * `1283.6666666666667` там, где показатель целый, и ширина строки прыгала бы\n * даже при `tabular-nums`. Последний кадр возвращает ровно `to` — приближение\n * не должно оставлять показатель в 1283.9999.\n */\nexport function countUpFrame(\n from: number,\n to: number,\n elapsed: number,\n duration: number,\n precision?: number,\n): number {\n if (!(duration > 0) || elapsed >= duration)\n return to\n\n const digits = Math.min(Math.max(precision ?? 0, 0), 20)\n const value = from + (to - from) * easeOutCubic(elapsed / duration)\n\n return Number(value.toFixed(digits))\n}\n","export type GrStatisticFormatOptions = {\n /** Число знаков после запятой. `undefined` — оставить как есть. */\n precision?: number\n /** Разделитель групп разрядов. Сильнее локали. */\n groupSeparator?: string\n /** Десятичный разделитель. Сильнее локали. */\n decimalSeparator?: string\n /** BCP-47 локаль: разделители берутся из `Intl.NumberFormat`. */\n locale?: string\n}\n\nconst DEFAULT_GROUP_SEPARATOR = ' '\nconst DEFAULT_DECIMAL_SEPARATOR = '.'\n\n/**\n * Форматирует значение показателя: фиксирует точность и расставляет разделители.\n *\n * Нечисловые значения возвращаются без изменений — это позволяет показывать в\n * `GrStatistic` уже готовые строки («2 ч 15 мин», «—») без обходных пропов.\n * Вынесено из SFC отдельным модулем, чтобы тестировалось без монтирования.\n *\n * Порядок разрешения разделителей: явный проп → локаль → встроенный дефолт.\n */\nexport function formatStatisticValue(\n value: number | string,\n options: GrStatisticFormatOptions = {},\n): string {\n const { precision, groupSeparator, decimalSeparator, locale } = options\n\n const numeric = typeof value === 'number' ? value : Number(value)\n if (typeof value === 'string' && (value.trim() === '' || !Number.isFinite(numeric))) {\n return value\n }\n if (!Number.isFinite(numeric))\n return String(value)\n\n if (locale) {\n return formatByLocale(numeric, locale, { precision, groupSeparator, decimalSeparator })\n }\n\n const fixed = precision === undefined ? String(numeric) : numeric.toFixed(precision)\n // `toFixed` не даёт экспоненциальной записи, а `String(число)` — может (1e21+).\n // В таком случае группировать нечего: возвращаем как есть.\n if (fixed.includes('e') || fixed.includes('E'))\n return fixed\n\n const negative = fixed.startsWith('-')\n const [intPart, fracPart] = (negative ? fixed.slice(1) : fixed).split('.')\n\n const separator = groupSeparator ?? DEFAULT_GROUP_SEPARATOR\n const grouped = separator\n ? intPart.replace(/\\B(?=(\\d{3})+(?!\\d))/g, separator)\n : intPart\n\n return [\n negative ? '-' : '',\n grouped,\n fracPart ? `${decimalSeparator ?? DEFAULT_DECIMAL_SEPARATOR}${fracPart}` : '',\n ].join('')\n}\n\n/**\n * `formatToParts` вместо `format`: даёт заменить отдельные разделители, оставив\n * остальные правила локали (индийская группировка, знак минуса, порядок частей).\n */\nfunction formatByLocale(\n value: number,\n locale: string,\n options: Pick<GrStatisticFormatOptions, 'precision' | 'groupSeparator' | 'decimalSeparator'>,\n): string {\n const { precision, groupSeparator, decimalSeparator } = options\n // `Intl` принимает не больше 20 знаков и падает `RangeError` на большем,\n // тогда как ручная ветка через `toFixed` пережила бы до 100.\n const digits = precision === undefined ? undefined : Math.min(Math.max(precision, 0), 20)\n\n return new Intl.NumberFormat(locale, {\n useGrouping: true,\n minimumFractionDigits: digits,\n maximumFractionDigits: digits ?? 20,\n })\n .formatToParts(value)\n .map((part) => {\n if (part.type === 'group' && groupSeparator !== undefined)\n return groupSeparator\n if (part.type === 'decimal' && decimalSeparator !== undefined)\n return decimalSeparator\n return part.value\n })\n .join('')\n}\n","<script setup lang=\"ts\">\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\nimport { computed, markRaw, onBeforeUnmount, onMounted, ref, useSlots, watch, watchEffect, type Component } from 'vue'\n\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { deltaTone, type GrDeltaPolarity } from '../GrDelta/deltaTone'\nimport { iconClass, iconTag } from '../shared/icon'\nimport GrSkeleton from '../GrSkeleton/GrSkeleton.vue'\nimport GrValue from '../GrValue'\n\nimport IconMinus from '~icons/lucide/minus'\nimport IconTrendingDown from '~icons/lucide/trending-down'\nimport IconTrendingUp from '~icons/lucide/trending-up'\n\nimport { countUpFrame } from './countUp'\nimport { formatStatisticValue } from './formatStatisticValue'\n\nimport {\n statisticAffixSizeVarBySize,\n statisticPlaceholderHeightBySize,\n statisticRootClass,\n statisticTitleClass,\n statisticTitleSizeBySize,\n statisticTrendClassByTrend,\n statisticTrendSizeBySize,\n statisticValueClass,\n type GrStatisticSize,\n type GrStatisticTone,\n type GrStatisticTrend,\n} from './grStatisticStyles'\n\nexport type {\n GrStatisticSize,\n GrStatisticTone,\n GrStatisticTrend,\n} from './grStatisticStyles'\nexport type { GrStatisticFormatOptions } from './formatStatisticValue'\n\n/**\n * Публичный GR-примитив «Statistic» — крупный числовой показатель с подписью,\n * префиксом/суффиксом и строкой динамики.\n *\n * Компонент презентационный: не грузит данные и не считает дельту, только\n * форматирует и подаёт. Форматирование — чистая функция `formatStatisticValue`.\n */\nexport interface GrStatisticProps {\n /** Значение показателя. Нечисловая строка выводится как есть. */\n value: number | string\n /** Подпись над значением. */\n title?: string\n /** Число знаков после запятой. */\n precision?: number\n /** Разделитель разрядов. Сильнее локали; без него и без локали — узкий пробел. */\n groupSeparator?: string\n /** Десятичный разделитель. Сильнее локали; без него и без локали — точка. */\n decimalSeparator?: string\n /**\n * BCP-47 локаль форматирования. Не задана — берётся из i18n-адаптера; нет и\n * адаптера — работают ручные разделители.\n */\n locale?: string\n /** Приписка перед значением (валюта, знак). */\n prefix?: string\n /** Приписка после значения (единица измерения, `%`). */\n suffix?: string\n /**\n * Иконка слева от блока: Vue-компонент либо класс иконки вашей UnoCSS-сборки\n * (`'i-lucide-users'` — тогда нужен ваш `presetIcons`, см. `docs/installation.md`).\n */\n icon?: string | Component\n size?: GrStatisticSize\n /** Тон значения; точечно перекрывается `--gr-statistic-value-color`. */\n tone?: GrStatisticTone\n /**\n * Что считать хорошим: тон выводится из знака самого значения. Для выручки\n * рост — успех, для себестоимости и оттока — наоборот. Явный `tone` сильнее:\n * выведенный тон — умолчание, а не диктат.\n */\n polarity?: GrDeltaPolarity\n /** Направление динамики — задаёт цвет и иконку строки под значением. */\n trend?: GrStatisticTrend\n /** Текст динамики (например `+12,5% к прошлой неделе`). */\n trendText?: string\n /** Состояние загрузки: вместо значения — плейсхолдер. */\n loading?: boolean\n /**\n * Перебирать числа при появлении и при смене значения. Нечисловое значение\n * ставится сразу, под `prefers-reduced-motion: reduce` — тоже.\n */\n animate?: boolean\n /**\n * Длительность перебора в миллисекундах. Не токеном: шкала `--gr-duration-*`\n * заканчивается на 300 мс и описывает смену состояния, а перебор чисел —\n * другой жанр, и его число живёт там же, где твин.\n */\n animateDuration?: number\n /** Свой корневой тег (`RouterLink`, `Link` от Inertia). Сильнее `href`. */\n as?: string | Component\n /** Показатель-ссылка: переход к деталям. */\n href?: string\n /** Показатель-кнопка: интерактивна вся плитка. */\n clickable?: boolean\n}\n\nexport interface GrStatisticEmits {\n (e: 'click', event: MouseEvent): void\n}\n\nconst props = withDefaults(\n defineProps<GrStatisticProps>(),\n {\n title: undefined,\n precision: undefined,\n // Дефолты живут в `formatStatisticValue`: иначе «пользователь задал пробел»\n // было бы неотличимо от «сработал дефолт», и локаль не смогла бы победить.\n groupSeparator: undefined,\n decimalSeparator: undefined,\n locale: undefined,\n prefix: undefined,\n suffix: undefined,\n icon: undefined,\n size: undefined,\n // Дефолт тона живёт в `resolvedTone`: с ним здесь «пользователь задал\n // neutral» было бы неотличимо от «сработал дефолт», и явный\n // `tone=\"neutral\"` не смог бы отменить `polarity`.\n tone: undefined,\n polarity: undefined,\n trend: undefined,\n trendText: undefined,\n loading: false,\n animate: false,\n animateDuration: 600,\n as: undefined,\n href: undefined,\n clickable: false,\n },\n)\n\nconst emit = defineEmits<GrStatisticEmits>()\n\nconst slots = useSlots()\n\nconst { t, locale } = useGranularityTranslations()\n\nconst resolvedLocale = computed(() => props.locale ?? locale.value)\n\n/**\n * Число текущего кадра перебора. `null` — перебора нет, показываем сам проп:\n * так значение остаётся конечным и на сервере, и до первого твина.\n */\nconst frameValue = ref<number | null>(null)\n\n// Обязательное значение может не доехать — промах вызова, асинхронный\n// источник, — и плитка напечатала бы слово `undefined`. Прочерк — тот же знак\n// пустоты, которым `GrDelta` рисует отсутствующую величину.\nconst resolvedValue = computed<number | string>(() => props.value ?? '—')\n\nconst formatted = computed(() => formatStatisticValue(frameValue.value ?? resolvedValue.value, {\n precision: props.precision,\n groupSeparator: props.groupSeparator,\n decimalSeparator: props.decimalSeparator,\n locale: resolvedLocale.value,\n}))\n\n/** Конечное значение — то, что читает диктор, пока на экране идёт перебор. */\nconst finalFormatted = computed(() => formatStatisticValue(resolvedValue.value, {\n precision: props.precision,\n groupSeparator: props.groupSeparator,\n decimalSeparator: props.decimalSeparator,\n locale: resolvedLocale.value,\n}))\n\nconst isCounting = computed(() => frameValue.value !== null)\n\nif (__GR_DEV__) {\n watchEffect(() => {\n if (props.value === undefined || props.value === null) {\n console.warn(\n `[granularity] GrStatistic: обязательный проп \\`value\\` не передан — получено ${String(props.value)}.`,\n )\n }\n })\n}\n\nconst hasTitle = computed(() => Boolean(props.title || slots.title))\n\nconst isInteractive = computed(() => Boolean(props.as || props.href || props.clickable))\n\nconst rootTag = computed<string | Component>(() => {\n if (props.as)\n return typeof props.as === 'string' ? props.as : markRaw(props.as)\n\n if (props.href)\n return 'a'\n\n return props.clickable ? 'button' : 'div'\n})\n\n/**\n * Компонент-ссылка (`Link` от Inertia, `RouterLink`) рендерит `<a>` сам, и без\n * `href` он ведёт в никуда. Строковый тег, кроме `a`, атрибут не понимает —\n * там он и гасится.\n */\nconst rootHref = computed(() => (\n typeof rootTag.value === 'string' && rootTag.value !== 'a' ? undefined : props.href\n))\n\nconst rootClass = computed(() => statisticRootClass({ interactive: isInteractive.value }))\n\nfunction toNumber(value: number | string): number | null {\n const numeric = typeof value === 'number' ? value : Number(value)\n return Number.isFinite(numeric) ? numeric : null\n}\n\nlet frameHandle: number | null = null\n\nfunction stopCounting(): void {\n if (frameHandle !== null)\n cancelAnimationFrame(frameHandle)\n frameHandle = null\n}\n\n/**\n * Перебор чисел ведёт JS, а глобальный кламп в `base.css` гасит только CSS —\n * значит уважать «уменьшить движение» обязан сам компонент (`docs/motion.md`).\n * Читается в момент старта, а не в теле `setup`: `matchMedia` на сервере нет, и\n * первый клиентский рендер разошёлся бы с серверным.\n */\nfunction prefersReducedMotion(): boolean {\n return typeof window !== 'undefined'\n && window.matchMedia?.('(prefers-reduced-motion: reduce)')?.matches === true\n}\n\nfunction runCountUp(from: number, to: number): void {\n stopCounting()\n\n const duration = props.animateDuration\n if (!(duration > 0) || from === to || prefersReducedMotion()) {\n frameValue.value = null\n return\n }\n\n const started = performance.now()\n frameValue.value = from\n\n const step = (now: number): void => {\n const elapsed = now - started\n const value = countUpFrame(from, to, elapsed, duration, props.precision)\n\n if (elapsed >= duration) {\n // Конец перебора снимает и подмену значения, и подмену для диктора.\n frameValue.value = null\n frameHandle = null\n return\n }\n\n frameValue.value = value\n frameHandle = requestAnimationFrame(step)\n }\n\n frameHandle = requestAnimationFrame(step)\n}\n\n/** Перебирать можно только числа: «2 ч 15 мин» и «—» ставятся сразу. */\nfunction startIfNumeric(from: number | string, to: number | string): void {\n if (!props.animate || props.loading) {\n stopCounting()\n frameValue.value = null\n return\n }\n\n const target = toNumber(to)\n const source = toNumber(from)\n\n if (target === null || source === null) {\n stopCounting()\n frameValue.value = null\n return\n }\n\n runCountUp(source, target)\n}\n\n// Появление плитки считается переходом от нуля: ради этого эффекта проп и нужен.\nonMounted(() => startIfNumeric(0, props.value))\n\nwatch(() => props.value, (next, prev) => startIfNumeric(prev, next))\n\nonBeforeUnmount(stopCounting)\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrStatistic' })\nconst resolvedPolarity = useGrComponentProp('GrStatistic', 'polarity', () => props.polarity, 'none')\n/**\n * Кегль приписки идёт ступенью шкалы плитки, а не долей от величины: на `md`\n * число набрано `--gr-text-3xl`, и доля подняла бы приписку вровень с ним.\n */\nconst affixStyle = computed(() => ({ '--gr-value-suffix-size': statisticAffixSizeVarBySize[resolvedSize.value] }))\n\n/**\n * Тон значения: явный проп → выведенный из знака → нейтральный.\n *\n * Отдельной ветки «полярность не задана» нет намеренно: `deltaTone(x, 'none')`\n * нейтрален при любом значении, то есть `'none'` и есть прежнее поведение.\n *\n * Знак берётся у самого пропа, а не у кадра перебора: `frameValue` стартует с\n * нуля, и цвет плитки мигал бы нейтральным на каждой анимации.\n */\nconst resolvedTone = computed<GrStatisticTone>(() => {\n if (props.tone !== undefined)\n return props.tone\n\n const numeric = toNumber(props.value)\n\n // «2 ч 15 мин» и «—» знака не имеют — красить нечем.\n return numeric === null ? 'neutral' : deltaTone(numeric, resolvedPolarity.value)\n})\n\nconst trendIconByTrend: Record<GrStatisticTrend, Component> = {\n up: IconTrendingUp,\n down: IconTrendingDown,\n flat: IconMinus,\n}\n\n// Иконка направления `aria-hidden`, а «+12,5 %» само по себе рост от падения не\n// отличает: направление доносит скрытая подпись.\nconst trendLabel = computed(() => {\n if (!props.trend)\n return undefined\n\n const labels = {\n up: () => t('gr.statistic.trendUp', 'Increase'),\n down: () => t('gr.statistic.trendDown', 'Decrease'),\n flat: () => t('gr.statistic.trendFlat', 'No change'),\n }\n\n return labels[props.trend]()\n})\n\ndefineSlots<{\n /** Значение вместо пропа `value`. */\n default?: () => any\n /** Иконка перед подписью. */\n icon?: () => any\n /** Подпись показателя вместо пропа `title`. */\n title?: () => any\n /** Приписка перед значением: знак валюты, стрелка. */\n prefix?: () => any\n /** Приписка после значения: единица измерения, процент. */\n suffix?: () => any\n /** Динамика под значением вместо встроенного `GrDelta`. */\n trend?: () => any\n}>()\n</script>\n\n<template>\n <component\n :is=\"rootTag\"\n data-gr-statistic\n :type=\"rootTag === 'button' ? 'button' : undefined\"\n :href=\"rootHref\"\n :class=\"rootClass\"\n @click=\"emit('click', $event)\"\n >\n <span\n v-if=\"icon || $slots.icon\"\n data-gr-statistic-icon\n class=\"mt-0.5 inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-[var(--gr-radius-lg)] bg-[var(--gr-muted)] text-[var(--gr-muted-fg)]\"\n >\n <slot name=\"icon\">\n <component :is=\"iconTag(icon)\" class=\"block h-4 w-4\" :class=\"iconClass(icon)\" aria-hidden=\"true\" />\n </slot>\n </span>\n\n <div class=\"min-w-0\">\n <!--\n Подпись и значение — пара «термин — значение», а не два соседних блока.\n `<dl>` появляется только вместе с подписью: список определений без `<dt>`\n — та же бессвязность, только с претензией на семантику. Поля обнуляются\n руками: preflight пакета сбрасывает их лишь у `body`.\n -->\n <component :is=\"hasTitle ? 'dl' : 'div'\" class=\"m-0\">\n <dt\n v-if=\"hasTitle\"\n data-gr-statistic-title\n :class=\"[statisticTitleClass, statisticTitleSizeBySize[resolvedSize]]\"\n >\n <slot name=\"title\">\n {{ title }}\n </slot>\n </dt>\n\n <!-- Плейсхолдер загрузки повторяет высоту строки значения, чтобы блок не прыгал. -->\n <component\n :is=\"hasTitle ? 'dd' : 'div'\"\n v-if=\"loading\"\n data-gr-statistic-placeholder\n data-testid=\"gr-statistic-placeholder\"\n class=\"m-0 mt-1\"\n role=\"status\"\n aria-busy=\"true\"\n >\n <GrSkeleton\n width=\"6rem\"\n :height=\"statisticPlaceholderHeightBySize[resolvedSize]\"\n rounded=\"var(--gr-radius-md)\"\n />\n <span class=\"sr-only\">{{ t('gr.loading.defaultText', 'Loading...') }}</span>\n </component>\n\n <component\n :is=\"hasTitle ? 'dd' : 'div'\"\n v-else\n data-gr-statistic-value\n data-testid=\"gr-statistic-value\"\n class=\"m-0 mt-0.5 flex items-baseline\"\n :class=\"statisticValueClass({ size: resolvedSize, tone: resolvedTone })\"\n >\n <!--\n Кегль и тон стоят на контейнере, а не на каждом узле: `GrValue`\n наследует их обоих, и отрицательная сумма краснеет целиком, вместе\n со знаком валюты. Оформление приписок настраивается токенами\n `--gr-value-*` — плитка за потребителя этого не решает.\n -->\n <GrValue :prefix=\"prefix\" :suffix=\"suffix\" :style=\"affixStyle\">\n <template v-if=\"$slots.prefix\" #prefix>\n <slot name=\"prefix\" />\n </template>\n\n <span :aria-hidden=\"isCounting ? 'true' : undefined\">\n <slot>{{ formatted }}</slot>\n </span>\n\n <!--\n Пока идёт перебор, на экране промежуточное число. Диктору отдаётся\n конечное: «1 284» глазами и «743» в ушах — не шум, а неверные данные.\n -->\n <template v-if=\"isCounting\" #trail>\n <span data-gr-statistic-final class=\"sr-only\">{{ finalFormatted }}</span>\n </template>\n\n <template v-if=\"$slots.suffix\" #suffix>\n <slot name=\"suffix\" />\n </template>\n </GrValue>\n </component>\n </component>\n\n <div\n v-if=\"trend || trendText || $slots.trend\"\n data-gr-statistic-trend\n data-testid=\"gr-statistic-trend\"\n class=\"mt-1 inline-flex items-center gap-1\"\n :class=\"[statisticTrendSizeBySize[resolvedSize], statisticTrendClassByTrend[trend ?? 'flat']]\"\n >\n <span v-if=\"trendLabel\" data-gr-statistic-trend-label class=\"sr-only\">{{ trendLabel }}</span>\n\n <slot name=\"trend\">\n <component\n :is=\"trendIconByTrend[trend]\"\n v-if=\"trend\"\n class=\"block h-3.5 w-3.5\"\n aria-hidden=\"true\"\n />\n <span>{{ trendText }}</span>\n </slot>\n </div>\n </div>\n </component>\n</template>\n","<script setup lang=\"ts\">\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\nimport { computed, markRaw, onBeforeUnmount, onMounted, ref, useSlots, watch, watchEffect, type Component } from 'vue'\n\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { deltaTone, type GrDeltaPolarity } from '../GrDelta/deltaTone'\nimport { iconClass, iconTag } from '../shared/icon'\nimport GrSkeleton from '../GrSkeleton/GrSkeleton.vue'\nimport GrValue from '../GrValue'\n\nimport IconMinus from '~icons/lucide/minus'\nimport IconTrendingDown from '~icons/lucide/trending-down'\nimport IconTrendingUp from '~icons/lucide/trending-up'\n\nimport { countUpFrame } from './countUp'\nimport { formatStatisticValue } from './formatStatisticValue'\n\nimport {\n statisticAffixSizeVarBySize,\n statisticPlaceholderHeightBySize,\n statisticRootClass,\n statisticTitleClass,\n statisticTitleSizeBySize,\n statisticTrendClassByTrend,\n statisticTrendSizeBySize,\n statisticValueClass,\n type GrStatisticSize,\n type GrStatisticTone,\n type GrStatisticTrend,\n} from './grStatisticStyles'\n\nexport type {\n GrStatisticSize,\n GrStatisticTone,\n GrStatisticTrend,\n} from './grStatisticStyles'\nexport type { GrStatisticFormatOptions } from './formatStatisticValue'\n\n/**\n * Публичный GR-примитив «Statistic» — крупный числовой показатель с подписью,\n * префиксом/суффиксом и строкой динамики.\n *\n * Компонент презентационный: не грузит данные и не считает дельту, только\n * форматирует и подаёт. Форматирование — чистая функция `formatStatisticValue`.\n */\nexport interface GrStatisticProps {\n /** Значение показателя. Нечисловая строка выводится как есть. */\n value: number | string\n /** Подпись над значением. */\n title?: string\n /** Число знаков после запятой. */\n precision?: number\n /** Разделитель разрядов. Сильнее локали; без него и без локали — узкий пробел. */\n groupSeparator?: string\n /** Десятичный разделитель. Сильнее локали; без него и без локали — точка. */\n decimalSeparator?: string\n /**\n * BCP-47 локаль форматирования. Не задана — берётся из i18n-адаптера; нет и\n * адаптера — работают ручные разделители.\n */\n locale?: string\n /** Приписка перед значением (валюта, знак). */\n prefix?: string\n /** Приписка после значения (единица измерения, `%`). */\n suffix?: string\n /**\n * Иконка слева от блока: Vue-компонент либо класс иконки вашей UnoCSS-сборки\n * (`'i-lucide-users'` — тогда нужен ваш `presetIcons`, см. `docs/installation.md`).\n */\n icon?: string | Component\n size?: GrStatisticSize\n /** Тон значения; точечно перекрывается `--gr-statistic-value-color`. */\n tone?: GrStatisticTone\n /**\n * Что считать хорошим: тон выводится из знака самого значения. Для выручки\n * рост — успех, для себестоимости и оттока — наоборот. Явный `tone` сильнее:\n * выведенный тон — умолчание, а не диктат.\n */\n polarity?: GrDeltaPolarity\n /** Направление динамики — задаёт цвет и иконку строки под значением. */\n trend?: GrStatisticTrend\n /** Текст динамики (например `+12,5% к прошлой неделе`). */\n trendText?: string\n /** Состояние загрузки: вместо значения — плейсхолдер. */\n loading?: boolean\n /**\n * Перебирать числа при появлении и при смене значения. Нечисловое значение\n * ставится сразу, под `prefers-reduced-motion: reduce` — тоже.\n */\n animate?: boolean\n /**\n * Длительность перебора в миллисекундах. Не токеном: шкала `--gr-duration-*`\n * заканчивается на 300 мс и описывает смену состояния, а перебор чисел —\n * другой жанр, и его число живёт там же, где твин.\n */\n animateDuration?: number\n /** Свой корневой тег (`RouterLink`, `Link` от Inertia). Сильнее `href`. */\n as?: string | Component\n /** Показатель-ссылка: переход к деталям. */\n href?: string\n /** Показатель-кнопка: интерактивна вся плитка. */\n clickable?: boolean\n}\n\nexport interface GrStatisticEmits {\n (e: 'click', event: MouseEvent): void\n}\n\nconst props = withDefaults(\n defineProps<GrStatisticProps>(),\n {\n title: undefined,\n precision: undefined,\n // Дефолты живут в `formatStatisticValue`: иначе «пользователь задал пробел»\n // было бы неотличимо от «сработал дефолт», и локаль не смогла бы победить.\n groupSeparator: undefined,\n decimalSeparator: undefined,\n locale: undefined,\n prefix: undefined,\n suffix: undefined,\n icon: undefined,\n size: undefined,\n // Дефолт тона живёт в `resolvedTone`: с ним здесь «пользователь задал\n // neutral» было бы неотличимо от «сработал дефолт», и явный\n // `tone=\"neutral\"` не смог бы отменить `polarity`.\n tone: undefined,\n polarity: undefined,\n trend: undefined,\n trendText: undefined,\n loading: false,\n animate: false,\n animateDuration: 600,\n as: undefined,\n href: undefined,\n clickable: false,\n },\n)\n\nconst emit = defineEmits<GrStatisticEmits>()\n\nconst slots = useSlots()\n\nconst { t, locale } = useGranularityTranslations()\n\nconst resolvedLocale = computed(() => props.locale ?? locale.value)\n\n/**\n * Число текущего кадра перебора. `null` — перебора нет, показываем сам проп:\n * так значение остаётся конечным и на сервере, и до первого твина.\n */\nconst frameValue = ref<number | null>(null)\n\n// Обязательное значение может не доехать — промах вызова, асинхронный\n// источник, — и плитка напечатала бы слово `undefined`. Прочерк — тот же знак\n// пустоты, которым `GrDelta` рисует отсутствующую величину.\nconst resolvedValue = computed<number | string>(() => props.value ?? '—')\n\nconst formatted = computed(() => formatStatisticValue(frameValue.value ?? resolvedValue.value, {\n precision: props.precision,\n groupSeparator: props.groupSeparator,\n decimalSeparator: props.decimalSeparator,\n locale: resolvedLocale.value,\n}))\n\n/** Конечное значение — то, что читает диктор, пока на экране идёт перебор. */\nconst finalFormatted = computed(() => formatStatisticValue(resolvedValue.value, {\n precision: props.precision,\n groupSeparator: props.groupSeparator,\n decimalSeparator: props.decimalSeparator,\n locale: resolvedLocale.value,\n}))\n\nconst isCounting = computed(() => frameValue.value !== null)\n\nif (__GR_DEV__) {\n watchEffect(() => {\n if (props.value === undefined || props.value === null) {\n console.warn(\n `[granularity] GrStatistic: обязательный проп \\`value\\` не передан — получено ${String(props.value)}.`,\n )\n }\n })\n}\n\nconst hasTitle = computed(() => Boolean(props.title || slots.title))\n\nconst isInteractive = computed(() => Boolean(props.as || props.href || props.clickable))\n\nconst rootTag = computed<string | Component>(() => {\n if (props.as)\n return typeof props.as === 'string' ? props.as : markRaw(props.as)\n\n if (props.href)\n return 'a'\n\n return props.clickable ? 'button' : 'div'\n})\n\n/**\n * Компонент-ссылка (`Link` от Inertia, `RouterLink`) рендерит `<a>` сам, и без\n * `href` он ведёт в никуда. Строковый тег, кроме `a`, атрибут не понимает —\n * там он и гасится.\n */\nconst rootHref = computed(() => (\n typeof rootTag.value === 'string' && rootTag.value !== 'a' ? undefined : props.href\n))\n\nconst rootClass = computed(() => statisticRootClass({ interactive: isInteractive.value }))\n\nfunction toNumber(value: number | string): number | null {\n const numeric = typeof value === 'number' ? value : Number(value)\n return Number.isFinite(numeric) ? numeric : null\n}\n\nlet frameHandle: number | null = null\n\nfunction stopCounting(): void {\n if (frameHandle !== null)\n cancelAnimationFrame(frameHandle)\n frameHandle = null\n}\n\n/**\n * Перебор чисел ведёт JS, а глобальный кламп в `base.css` гасит только CSS —\n * значит уважать «уменьшить движение» обязан сам компонент (`docs/motion.md`).\n * Читается в момент старта, а не в теле `setup`: `matchMedia` на сервере нет, и\n * первый клиентский рендер разошёлся бы с серверным.\n */\nfunction prefersReducedMotion(): boolean {\n return typeof window !== 'undefined'\n && window.matchMedia?.('(prefers-reduced-motion: reduce)')?.matches === true\n}\n\nfunction runCountUp(from: number, to: number): void {\n stopCounting()\n\n const duration = props.animateDuration\n if (!(duration > 0) || from === to || prefersReducedMotion()) {\n frameValue.value = null\n return\n }\n\n const started = performance.now()\n frameValue.value = from\n\n const step = (now: number): void => {\n const elapsed = now - started\n const value = countUpFrame(from, to, elapsed, duration, props.precision)\n\n if (elapsed >= duration) {\n // Конец перебора снимает и подмену значения, и подмену для диктора.\n frameValue.value = null\n frameHandle = null\n return\n }\n\n frameValue.value = value\n frameHandle = requestAnimationFrame(step)\n }\n\n frameHandle = requestAnimationFrame(step)\n}\n\n/** Перебирать можно только числа: «2 ч 15 мин» и «—» ставятся сразу. */\nfunction startIfNumeric(from: number | string, to: number | string): void {\n if (!props.animate || props.loading) {\n stopCounting()\n frameValue.value = null\n return\n }\n\n const target = toNumber(to)\n const source = toNumber(from)\n\n if (target === null || source === null) {\n stopCounting()\n frameValue.value = null\n return\n }\n\n runCountUp(source, target)\n}\n\n// Появление плитки считается переходом от нуля: ради этого эффекта проп и нужен.\nonMounted(() => startIfNumeric(0, props.value))\n\nwatch(() => props.value, (next, prev) => startIfNumeric(prev, next))\n\nonBeforeUnmount(stopCounting)\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrStatistic' })\nconst resolvedPolarity = useGrComponentProp('GrStatistic', 'polarity', () => props.polarity, 'none')\n/**\n * Кегль приписки идёт ступенью шкалы плитки, а не долей от величины: на `md`\n * число набрано `--gr-text-3xl`, и доля подняла бы приписку вровень с ним.\n */\nconst affixStyle = computed(() => ({ '--gr-value-suffix-size': statisticAffixSizeVarBySize[resolvedSize.value] }))\n\n/**\n * Тон значения: явный проп → выведенный из знака → нейтральный.\n *\n * Отдельной ветки «полярность не задана» нет намеренно: `deltaTone(x, 'none')`\n * нейтрален при любом значении, то есть `'none'` и есть прежнее поведение.\n *\n * Знак берётся у самого пропа, а не у кадра перебора: `frameValue` стартует с\n * нуля, и цвет плитки мигал бы нейтральным на каждой анимации.\n */\nconst resolvedTone = computed<GrStatisticTone>(() => {\n if (props.tone !== undefined)\n return props.tone\n\n const numeric = toNumber(props.value)\n\n // «2 ч 15 мин» и «—» знака не имеют — красить нечем.\n return numeric === null ? 'neutral' : deltaTone(numeric, resolvedPolarity.value)\n})\n\nconst trendIconByTrend: Record<GrStatisticTrend, Component> = {\n up: IconTrendingUp,\n down: IconTrendingDown,\n flat: IconMinus,\n}\n\n// Иконка направления `aria-hidden`, а «+12,5 %» само по себе рост от падения не\n// отличает: направление доносит скрытая подпись.\nconst trendLabel = computed(() => {\n if (!props.trend)\n return undefined\n\n const labels = {\n up: () => t('gr.statistic.trendUp', 'Increase'),\n down: () => t('gr.statistic.trendDown', 'Decrease'),\n flat: () => t('gr.statistic.trendFlat', 'No change'),\n }\n\n return labels[props.trend]()\n})\n\ndefineSlots<{\n /** Значение вместо пропа `value`. */\n default?: () => any\n /** Иконка перед подписью. */\n icon?: () => any\n /** Подпись показателя вместо пропа `title`. */\n title?: () => any\n /** Приписка перед значением: знак валюты, стрелка. */\n prefix?: () => any\n /** Приписка после значения: единица измерения, процент. */\n suffix?: () => any\n /** Динамика под значением вместо встроенного `GrDelta`. */\n trend?: () => any\n}>()\n</script>\n\n<template>\n <component\n :is=\"rootTag\"\n data-gr-statistic\n :type=\"rootTag === 'button' ? 'button' : undefined\"\n :href=\"rootHref\"\n :class=\"rootClass\"\n @click=\"emit('click', $event)\"\n >\n <span\n v-if=\"icon || $slots.icon\"\n data-gr-statistic-icon\n class=\"mt-0.5 inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-[var(--gr-radius-lg)] bg-[var(--gr-muted)] text-[var(--gr-muted-fg)]\"\n >\n <slot name=\"icon\">\n <component :is=\"iconTag(icon)\" class=\"block h-4 w-4\" :class=\"iconClass(icon)\" aria-hidden=\"true\" />\n </slot>\n </span>\n\n <div class=\"min-w-0\">\n <!--\n Подпись и значение — пара «термин — значение», а не два соседних блока.\n `<dl>` появляется только вместе с подписью: список определений без `<dt>`\n — та же бессвязность, только с претензией на семантику. Поля обнуляются\n руками: preflight пакета сбрасывает их лишь у `body`.\n -->\n <component :is=\"hasTitle ? 'dl' : 'div'\" class=\"m-0\">\n <dt\n v-if=\"hasTitle\"\n data-gr-statistic-title\n :class=\"[statisticTitleClass, statisticTitleSizeBySize[resolvedSize]]\"\n >\n <slot name=\"title\">\n {{ title }}\n </slot>\n </dt>\n\n <!-- Плейсхолдер загрузки повторяет высоту строки значения, чтобы блок не прыгал. -->\n <component\n :is=\"hasTitle ? 'dd' : 'div'\"\n v-if=\"loading\"\n data-gr-statistic-placeholder\n data-testid=\"gr-statistic-placeholder\"\n class=\"m-0 mt-1\"\n role=\"status\"\n aria-busy=\"true\"\n >\n <GrSkeleton\n width=\"6rem\"\n :height=\"statisticPlaceholderHeightBySize[resolvedSize]\"\n rounded=\"var(--gr-radius-md)\"\n />\n <span class=\"sr-only\">{{ t('gr.loading.defaultText', 'Loading...') }}</span>\n </component>\n\n <component\n :is=\"hasTitle ? 'dd' : 'div'\"\n v-else\n data-gr-statistic-value\n data-testid=\"gr-statistic-value\"\n class=\"m-0 mt-0.5 flex items-baseline\"\n :class=\"statisticValueClass({ size: resolvedSize, tone: resolvedTone })\"\n >\n <!--\n Кегль и тон стоят на контейнере, а не на каждом узле: `GrValue`\n наследует их обоих, и отрицательная сумма краснеет целиком, вместе\n со знаком валюты. Оформление приписок настраивается токенами\n `--gr-value-*` — плитка за потребителя этого не решает.\n -->\n <GrValue :prefix=\"prefix\" :suffix=\"suffix\" :style=\"affixStyle\">\n <template v-if=\"$slots.prefix\" #prefix>\n <slot name=\"prefix\" />\n </template>\n\n <span :aria-hidden=\"isCounting ? 'true' : undefined\">\n <slot>{{ formatted }}</slot>\n </span>\n\n <!--\n Пока идёт перебор, на экране промежуточное число. Диктору отдаётся\n конечное: «1 284» глазами и «743» в ушах — не шум, а неверные данные.\n -->\n <template v-if=\"isCounting\" #trail>\n <span data-gr-statistic-final class=\"sr-only\">{{ finalFormatted }}</span>\n </template>\n\n <template v-if=\"$slots.suffix\" #suffix>\n <slot name=\"suffix\" />\n </template>\n </GrValue>\n </component>\n </component>\n\n <div\n v-if=\"trend || trendText || $slots.trend\"\n data-gr-statistic-trend\n data-testid=\"gr-statistic-trend\"\n class=\"mt-1 inline-flex items-center gap-1\"\n :class=\"[statisticTrendSizeBySize[resolvedSize], statisticTrendClassByTrend[trend ?? 'flat']]\"\n >\n <span v-if=\"trendLabel\" data-gr-statistic-trend-label class=\"sr-only\">{{ trendLabel }}</span>\n\n <slot name=\"trend\">\n <component\n :is=\"trendIconByTrend[trend]\"\n v-if=\"trend\"\n class=\"block h-3.5 w-3.5\"\n aria-hidden=\"true\"\n />\n <span>{{ trendText }}</span>\n </slot>\n </div>\n </div>\n </component>\n</template>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"GrStatistic-C1_nY4Gc.js","names":["$slots"],"sources":["../../../../src/components/GrStatistic/countUp.ts","../../../../src/components/GrStatistic/formatStatisticValue.ts","../../../../src/components/GrStatistic/GrStatistic.vue","../../../../src/components/GrStatistic/GrStatistic.vue"],"sourcesContent":["/**\n * Перебор чисел для `GrStatistic`: чистая арифметика кадра без Vue и таймеров.\n *\n * Вынесено отдельным модулем по той же причине, что и `formatStatisticValue`:\n * кривая и округление проверяются напрямую, без монтирования и подмены `rAF`.\n */\n\n/** Быстрый старт, мягкая остановка — та же форма, что у `--gr-ease-out`. */\nexport function easeOutCubic(t: number): number {\n const clamped = Math.min(Math.max(t, 0), 1)\n return 1 - (1 - clamped) ** 3\n}\n\n/**\n * Значение кадра на `elapsed` миллисекунде перехода `from → to`.\n *\n * Округление до `precision` обязательно: без него кадры показывали бы\n * `1283.6666666666667` там, где показатель целый, и ширина строки прыгала бы\n * даже при `tabular-nums`. Последний кадр возвращает ровно `to` — приближение\n * не должно оставлять показатель в 1283.9999.\n */\nexport function countUpFrame(\n from: number,\n to: number,\n elapsed: number,\n duration: number,\n precision?: number,\n): number {\n if (!(duration > 0) || elapsed >= duration)\n return to\n\n const digits = Math.min(Math.max(precision ?? 0, 0), 20)\n const value = from + (to - from) * easeOutCubic(elapsed / duration)\n\n return Number(value.toFixed(digits))\n}\n","export type GrStatisticFormatOptions = {\n /** Число знаков после запятой. `undefined` — оставить как есть. */\n precision?: number\n /** Разделитель групп разрядов. Сильнее локали. */\n groupSeparator?: string\n /** Десятичный разделитель. Сильнее локали. */\n decimalSeparator?: string\n /** BCP-47 локаль: разделители берутся из `Intl.NumberFormat`. */\n locale?: string\n}\n\nconst DEFAULT_GROUP_SEPARATOR = ' '\nconst DEFAULT_DECIMAL_SEPARATOR = '.'\n\n/**\n * Форматирует значение показателя: фиксирует точность и расставляет разделители.\n *\n * Нечисловые значения возвращаются без изменений — это позволяет показывать в\n * `GrStatistic` уже готовые строки («2 ч 15 мин», «—») без обходных пропов.\n * Вынесено из SFC отдельным модулем, чтобы тестировалось без монтирования.\n *\n * Порядок разрешения разделителей: явный проп → локаль → встроенный дефолт.\n */\nexport function formatStatisticValue(\n value: number | string,\n options: GrStatisticFormatOptions = {},\n): string {\n const { precision, groupSeparator, decimalSeparator, locale } = options\n\n const numeric = typeof value === 'number' ? value : Number(value)\n if (typeof value === 'string' && (value.trim() === '' || !Number.isFinite(numeric))) {\n return value\n }\n if (!Number.isFinite(numeric))\n return String(value)\n\n if (locale) {\n return formatByLocale(numeric, locale, { precision, groupSeparator, decimalSeparator })\n }\n\n const fixed = precision === undefined ? String(numeric) : numeric.toFixed(precision)\n // `toFixed` не даёт экспоненциальной записи, а `String(число)` — может (1e21+).\n // В таком случае группировать нечего: возвращаем как есть.\n if (fixed.includes('e') || fixed.includes('E'))\n return fixed\n\n const negative = fixed.startsWith('-')\n const [intPart, fracPart] = (negative ? fixed.slice(1) : fixed).split('.')\n\n const separator = groupSeparator ?? DEFAULT_GROUP_SEPARATOR\n const grouped = separator\n ? intPart.replace(/\\B(?=(\\d{3})+(?!\\d))/g, separator)\n : intPart\n\n return [\n negative ? '-' : '',\n grouped,\n fracPart ? `${decimalSeparator ?? DEFAULT_DECIMAL_SEPARATOR}${fracPart}` : '',\n ].join('')\n}\n\n/**\n * `formatToParts` вместо `format`: даёт заменить отдельные разделители, оставив\n * остальные правила локали (индийская группировка, знак минуса, порядок частей).\n */\nfunction formatByLocale(\n value: number,\n locale: string,\n options: Pick<GrStatisticFormatOptions, 'precision' | 'groupSeparator' | 'decimalSeparator'>,\n): string {\n const { precision, groupSeparator, decimalSeparator } = options\n // `Intl` принимает не больше 20 знаков и падает `RangeError` на большем,\n // тогда как ручная ветка через `toFixed` пережила бы до 100.\n const digits = precision === undefined ? undefined : Math.min(Math.max(precision, 0), 20)\n\n return new Intl.NumberFormat(locale, {\n useGrouping: true,\n minimumFractionDigits: digits,\n maximumFractionDigits: digits ?? 20,\n })\n .formatToParts(value)\n .map((part) => {\n if (part.type === 'group' && groupSeparator !== undefined)\n return groupSeparator\n if (part.type === 'decimal' && decimalSeparator !== undefined)\n return decimalSeparator\n return part.value\n })\n .join('')\n}\n","<script setup lang=\"ts\">\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\nimport { computed, markRaw, onBeforeUnmount, onMounted, ref, useSlots, watch, watchEffect, type Component } from 'vue'\n\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { deltaTone, type GrDeltaPolarity } from '../GrDelta/deltaTone'\nimport { iconClass, iconTag } from '../shared/icon'\nimport GrSkeleton from '../GrSkeleton/GrSkeleton.vue'\nimport GrValue from '../GrValue'\n\nimport IconMinus from '~icons/lucide/minus'\nimport IconTrendingDown from '~icons/lucide/trending-down'\nimport IconTrendingUp from '~icons/lucide/trending-up'\n\nimport { countUpFrame } from './countUp'\nimport { formatStatisticValue } from './formatStatisticValue'\n\nimport {\n statisticAffixSizeVarBySize,\n statisticPlaceholderHeightBySize,\n statisticRootClass,\n statisticTitleClass,\n statisticTitleSizeBySize,\n statisticTrendClassByTrend,\n statisticTrendSizeBySize,\n statisticValueClass,\n type GrStatisticSize,\n type GrStatisticTone,\n type GrStatisticTrend,\n} from './grStatisticStyles'\n\nexport type {\n GrStatisticSize,\n GrStatisticTone,\n GrStatisticTrend,\n} from './grStatisticStyles'\nexport type { GrStatisticFormatOptions } from './formatStatisticValue'\n\n/**\n * Публичный GR-примитив «Statistic» — крупный числовой показатель с подписью,\n * префиксом/суффиксом и строкой динамики.\n *\n * Компонент презентационный: не грузит данные и не считает дельту, только\n * форматирует и подаёт. Форматирование — чистая функция `formatStatisticValue`.\n */\nexport interface GrStatisticProps {\n /** Значение показателя. Нечисловая строка выводится как есть. */\n value: number | string\n /** Подпись над значением. */\n title?: string\n /** Число знаков после запятой. */\n precision?: number\n /** Разделитель разрядов. Сильнее локали; без него и без локали — узкий пробел. */\n groupSeparator?: string\n /** Десятичный разделитель. Сильнее локали; без него и без локали — точка. */\n decimalSeparator?: string\n /**\n * BCP-47 локаль форматирования. Не задана — берётся из i18n-адаптера; нет и\n * адаптера — работают ручные разделители.\n */\n locale?: string\n /** Приписка перед значением (валюта, знак). */\n prefix?: string\n /** Приписка после значения (единица измерения, `%`). */\n suffix?: string\n /**\n * Иконка слева от блока: Vue-компонент либо класс иконки вашей UnoCSS-сборки\n * (`'i-lucide-users'` — тогда нужен ваш `presetIcons`, см. `docs/installation.md`).\n */\n icon?: string | Component\n size?: GrStatisticSize\n /** Тон значения; точечно перекрывается `--gr-statistic-value-color`. */\n tone?: GrStatisticTone\n /**\n * Что считать хорошим: тон выводится из знака самого значения. Для выручки\n * рост — успех, для себестоимости и оттока — наоборот. Явный `tone` сильнее:\n * выведенный тон — умолчание, а не диктат.\n */\n polarity?: GrDeltaPolarity\n /** Направление динамики — задаёт цвет и иконку строки под значением. */\n trend?: GrStatisticTrend\n /** Текст динамики (например `+12,5% к прошлой неделе`). */\n trendText?: string\n /** Состояние загрузки: вместо значения — плейсхолдер. */\n loading?: boolean\n /**\n * Перебирать числа при появлении и при смене значения. Нечисловое значение\n * ставится сразу, под `prefers-reduced-motion: reduce` — тоже.\n */\n animate?: boolean\n /**\n * Длительность перебора в миллисекундах. Не токеном: шкала `--gr-duration-*`\n * заканчивается на 300 мс и описывает смену состояния, а перебор чисел —\n * другой жанр, и его число живёт там же, где твин.\n */\n animateDuration?: number\n /** Свой корневой тег (`RouterLink`, `Link` от Inertia). Сильнее `href`. */\n as?: string | Component\n /** Показатель-ссылка: переход к деталям. */\n href?: string\n /** Показатель-кнопка: интерактивна вся плитка. */\n clickable?: boolean\n}\n\nexport interface GrStatisticEmits {\n (e: 'click', event: MouseEvent): void\n}\n\nconst props = withDefaults(\n defineProps<GrStatisticProps>(),\n {\n title: undefined,\n precision: undefined,\n // Дефолты живут в `formatStatisticValue`: иначе «пользователь задал пробел»\n // было бы неотличимо от «сработал дефолт», и локаль не смогла бы победить.\n groupSeparator: undefined,\n decimalSeparator: undefined,\n locale: undefined,\n prefix: undefined,\n suffix: undefined,\n icon: undefined,\n size: undefined,\n // Дефолт тона живёт в `resolvedTone`: с ним здесь «пользователь задал\n // neutral» было бы неотличимо от «сработал дефолт», и явный\n // `tone=\"neutral\"` не смог бы отменить `polarity`.\n tone: undefined,\n polarity: undefined,\n trend: undefined,\n trendText: undefined,\n loading: false,\n animate: false,\n animateDuration: 600,\n as: undefined,\n href: undefined,\n clickable: false,\n },\n)\n\nconst emit = defineEmits<GrStatisticEmits>()\n\nconst slots = useSlots()\n\nconst { t, locale } = useGranularityTranslations()\n\nconst resolvedLocale = computed(() => props.locale ?? locale.value)\n\n/**\n * Число текущего кадра перебора. `null` — перебора нет, показываем сам проп:\n * так значение остаётся конечным и на сервере, и до первого твина.\n */\nconst frameValue = ref<number | null>(null)\n\n// Обязательное значение может не доехать — промах вызова, асинхронный\n// источник, — и плитка напечатала бы слово `undefined`. Прочерк — тот же знак\n// пустоты, которым `GrDelta` рисует отсутствующую величину.\nconst resolvedValue = computed<number | string>(() => props.value ?? '—')\n\nconst formatted = computed(() => formatStatisticValue(frameValue.value ?? resolvedValue.value, {\n precision: props.precision,\n groupSeparator: props.groupSeparator,\n decimalSeparator: props.decimalSeparator,\n locale: resolvedLocale.value,\n}))\n\n/** Конечное значение — то, что читает диктор, пока на экране идёт перебор. */\nconst finalFormatted = computed(() => formatStatisticValue(resolvedValue.value, {\n precision: props.precision,\n groupSeparator: props.groupSeparator,\n decimalSeparator: props.decimalSeparator,\n locale: resolvedLocale.value,\n}))\n\nconst isCounting = computed(() => frameValue.value !== null)\n\nif (__GR_DEV__) {\n watchEffect(() => {\n if (props.value === undefined || props.value === null) {\n console.warn(\n `[granularity] GrStatistic: обязательный проп \\`value\\` не передан — получено ${String(props.value)}.`,\n )\n }\n })\n}\n\nconst hasTitle = computed(() => Boolean(props.title || slots.title))\n\nconst isInteractive = computed(() => Boolean(props.as || props.href || props.clickable))\n\nconst rootTag = computed<string | Component>(() => {\n if (props.as)\n return typeof props.as === 'string' ? props.as : markRaw(props.as)\n\n if (props.href)\n return 'a'\n\n return props.clickable ? 'button' : 'div'\n})\n\n/**\n * Компонент-ссылка (`Link` от Inertia, `RouterLink`) рендерит `<a>` сам, и без\n * `href` он ведёт в никуда. Строковый тег, кроме `a`, атрибут не понимает —\n * там он и гасится.\n */\nconst rootHref = computed(() => (\n typeof rootTag.value === 'string' && rootTag.value !== 'a' ? undefined : props.href\n))\n\nconst rootClass = computed(() => statisticRootClass({ interactive: isInteractive.value }))\n\nfunction toNumber(value: number | string): number | null {\n const numeric = typeof value === 'number' ? value : Number(value)\n return Number.isFinite(numeric) ? numeric : null\n}\n\nlet frameHandle: number | null = null\n\nfunction stopCounting(): void {\n if (frameHandle !== null)\n cancelAnimationFrame(frameHandle)\n frameHandle = null\n}\n\n/**\n * Перебор чисел ведёт JS, а глобальный кламп в `base.css` гасит только CSS —\n * значит уважать «уменьшить движение» обязан сам компонент (`docs/motion.md`).\n * Читается в момент старта, а не в теле `setup`: `matchMedia` на сервере нет, и\n * первый клиентский рендер разошёлся бы с серверным.\n */\nfunction prefersReducedMotion(): boolean {\n return typeof window !== 'undefined'\n && window.matchMedia?.('(prefers-reduced-motion: reduce)')?.matches === true\n}\n\nfunction runCountUp(from: number, to: number): void {\n stopCounting()\n\n const duration = props.animateDuration\n if (!(duration > 0) || from === to || prefersReducedMotion()) {\n frameValue.value = null\n return\n }\n\n const started = performance.now()\n frameValue.value = from\n\n const step = (now: number): void => {\n const elapsed = now - started\n const value = countUpFrame(from, to, elapsed, duration, props.precision)\n\n if (elapsed >= duration) {\n // Конец перебора снимает и подмену значения, и подмену для диктора.\n frameValue.value = null\n frameHandle = null\n return\n }\n\n frameValue.value = value\n frameHandle = requestAnimationFrame(step)\n }\n\n frameHandle = requestAnimationFrame(step)\n}\n\n/** Перебирать можно только числа: «2 ч 15 мин» и «—» ставятся сразу. */\nfunction startIfNumeric(from: number | string, to: number | string): void {\n if (!props.animate || props.loading) {\n stopCounting()\n frameValue.value = null\n return\n }\n\n const target = toNumber(to)\n const source = toNumber(from)\n\n if (target === null || source === null) {\n stopCounting()\n frameValue.value = null\n return\n }\n\n runCountUp(source, target)\n}\n\n// Появление плитки считается переходом от нуля: ради этого эффекта проп и нужен.\nonMounted(() => startIfNumeric(0, props.value))\n\nwatch(() => props.value, (next, prev) => startIfNumeric(prev, next))\n\nonBeforeUnmount(stopCounting)\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrStatistic' })\nconst resolvedPolarity = useGrComponentProp('GrStatistic', 'polarity', () => props.polarity, 'none')\n/**\n * Кегль приписки идёт ступенью шкалы плитки, а не долей от величины: на `md`\n * число набрано `--gr-text-3xl`, и доля подняла бы приписку вровень с ним.\n */\nconst affixStyle = computed(() => ({ '--gr-value-suffix-size': statisticAffixSizeVarBySize[resolvedSize.value] }))\n\n/**\n * Тон значения: явный проп → выведенный из знака → нейтральный.\n *\n * Отдельной ветки «полярность не задана» нет намеренно: `deltaTone(x, 'none')`\n * нейтрален при любом значении, то есть `'none'` и есть прежнее поведение.\n *\n * Знак берётся у самого пропа, а не у кадра перебора: `frameValue` стартует с\n * нуля, и цвет плитки мигал бы нейтральным на каждой анимации.\n */\nconst resolvedTone = computed<GrStatisticTone>(() => {\n if (props.tone !== undefined)\n return props.tone\n\n const numeric = toNumber(props.value)\n\n // «2 ч 15 мин» и «—» знака не имеют — красить нечем.\n return numeric === null ? 'neutral' : deltaTone(numeric, resolvedPolarity.value)\n})\n\nconst trendIconByTrend: Record<GrStatisticTrend, Component> = {\n up: IconTrendingUp,\n down: IconTrendingDown,\n flat: IconMinus,\n}\n\n// Иконка направления `aria-hidden`, а «+12,5 %» само по себе рост от падения не\n// отличает: направление доносит скрытая подпись.\nconst trendLabel = computed(() => {\n if (!props.trend)\n return undefined\n\n const labels = {\n up: () => t('gr.statistic.trendUp', 'Increase'),\n down: () => t('gr.statistic.trendDown', 'Decrease'),\n flat: () => t('gr.statistic.trendFlat', 'No change'),\n }\n\n return labels[props.trend]()\n})\n\ndefineSlots<{\n /** Значение вместо пропа `value`. */\n default?: () => any\n /** Иконка перед подписью. */\n icon?: () => any\n /** Подпись показателя вместо пропа `title`. */\n title?: () => any\n /** Приписка перед значением: знак валюты, стрелка. */\n prefix?: () => any\n /** Приписка после значения: единица измерения, процент. */\n suffix?: () => any\n /** Динамика под значением вместо встроенного `GrDelta`. */\n trend?: () => any\n}>()\n</script>\n\n<template>\n <component\n :is=\"rootTag\"\n data-gr-statistic\n :type=\"rootTag === 'button' ? 'button' : undefined\"\n :href=\"rootHref\"\n :class=\"rootClass\"\n @click=\"emit('click', $event)\"\n >\n <span\n v-if=\"icon || $slots.icon\"\n data-gr-statistic-icon\n class=\"mt-0.5 inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-[var(--gr-radius-lg)] bg-[var(--gr-muted)] text-[var(--gr-muted-fg)]\"\n >\n <slot name=\"icon\">\n <component :is=\"iconTag(icon)\" class=\"block h-4 w-4\" :class=\"iconClass(icon)\" aria-hidden=\"true\" />\n </slot>\n </span>\n\n <div class=\"min-w-0\">\n <!--\n Подпись и значение — пара «термин — значение», а не два соседних блока.\n `<dl>` появляется только вместе с подписью: список определений без `<dt>`\n — та же бессвязность, только с претензией на семантику. Поля обнуляются\n руками: preflight пакета сбрасывает их лишь у `body`.\n -->\n <component :is=\"hasTitle ? 'dl' : 'div'\" class=\"m-0\">\n <dt\n v-if=\"hasTitle\"\n data-gr-statistic-title\n :class=\"[statisticTitleClass, statisticTitleSizeBySize[resolvedSize]]\"\n >\n <slot name=\"title\">\n {{ title }}\n </slot>\n </dt>\n\n <!-- Плейсхолдер загрузки повторяет высоту строки значения, чтобы блок не прыгал. -->\n <component\n :is=\"hasTitle ? 'dd' : 'div'\"\n v-if=\"loading\"\n data-gr-statistic-placeholder\n data-testid=\"gr-statistic-placeholder\"\n class=\"m-0 mt-1\"\n role=\"status\"\n aria-busy=\"true\"\n >\n <GrSkeleton\n width=\"6rem\"\n :height=\"statisticPlaceholderHeightBySize[resolvedSize]\"\n rounded=\"var(--gr-radius-md)\"\n />\n <span class=\"sr-only\">{{ t('gr.loading.defaultText', 'Loading...') }}</span>\n </component>\n\n <component\n :is=\"hasTitle ? 'dd' : 'div'\"\n v-else\n data-gr-statistic-value\n data-testid=\"gr-statistic-value\"\n class=\"m-0 mt-0.5 flex items-baseline\"\n :class=\"statisticValueClass({ size: resolvedSize, tone: resolvedTone })\"\n >\n <!--\n Кегль и тон стоят на контейнере, а не на каждом узле: `GrValue`\n наследует их обоих, и отрицательная сумма краснеет целиком, вместе\n со знаком валюты. Оформление приписок настраивается токенами\n `--gr-value-*` — плитка за потребителя этого не решает.\n -->\n <GrValue :prefix=\"prefix\" :suffix=\"suffix\" :style=\"affixStyle\">\n <template v-if=\"$slots.prefix\" #prefix>\n <slot name=\"prefix\" />\n </template>\n\n <span :aria-hidden=\"isCounting ? 'true' : undefined\">\n <slot>{{ formatted }}</slot>\n </span>\n\n <!--\n Пока идёт перебор, на экране промежуточное число. Диктору отдаётся\n конечное: «1 284» глазами и «743» в ушах — не шум, а неверные данные.\n -->\n <template v-if=\"isCounting\" #trail>\n <span data-gr-statistic-final class=\"sr-only\">{{ finalFormatted }}</span>\n </template>\n\n <template v-if=\"$slots.suffix\" #suffix>\n <slot name=\"suffix\" />\n </template>\n </GrValue>\n </component>\n </component>\n\n <div\n v-if=\"trend || trendText || $slots.trend\"\n data-gr-statistic-trend\n data-testid=\"gr-statistic-trend\"\n class=\"mt-1 inline-flex items-center gap-1\"\n :class=\"[statisticTrendSizeBySize[resolvedSize], statisticTrendClassByTrend[trend ?? 'flat']]\"\n >\n <span v-if=\"trendLabel\" data-gr-statistic-trend-label class=\"sr-only\">{{ trendLabel }}</span>\n\n <slot name=\"trend\">\n <component\n :is=\"trendIconByTrend[trend]\"\n v-if=\"trend\"\n class=\"block h-3.5 w-3.5\"\n aria-hidden=\"true\"\n />\n <span>{{ trendText }}</span>\n </slot>\n </div>\n </div>\n </component>\n</template>\n","<script setup lang=\"ts\">\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\nimport { computed, markRaw, onBeforeUnmount, onMounted, ref, useSlots, watch, watchEffect, type Component } from 'vue'\n\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { deltaTone, type GrDeltaPolarity } from '../GrDelta/deltaTone'\nimport { iconClass, iconTag } from '../shared/icon'\nimport GrSkeleton from '../GrSkeleton/GrSkeleton.vue'\nimport GrValue from '../GrValue'\n\nimport IconMinus from '~icons/lucide/minus'\nimport IconTrendingDown from '~icons/lucide/trending-down'\nimport IconTrendingUp from '~icons/lucide/trending-up'\n\nimport { countUpFrame } from './countUp'\nimport { formatStatisticValue } from './formatStatisticValue'\n\nimport {\n statisticAffixSizeVarBySize,\n statisticPlaceholderHeightBySize,\n statisticRootClass,\n statisticTitleClass,\n statisticTitleSizeBySize,\n statisticTrendClassByTrend,\n statisticTrendSizeBySize,\n statisticValueClass,\n type GrStatisticSize,\n type GrStatisticTone,\n type GrStatisticTrend,\n} from './grStatisticStyles'\n\nexport type {\n GrStatisticSize,\n GrStatisticTone,\n GrStatisticTrend,\n} from './grStatisticStyles'\nexport type { GrStatisticFormatOptions } from './formatStatisticValue'\n\n/**\n * Публичный GR-примитив «Statistic» — крупный числовой показатель с подписью,\n * префиксом/суффиксом и строкой динамики.\n *\n * Компонент презентационный: не грузит данные и не считает дельту, только\n * форматирует и подаёт. Форматирование — чистая функция `formatStatisticValue`.\n */\nexport interface GrStatisticProps {\n /** Значение показателя. Нечисловая строка выводится как есть. */\n value: number | string\n /** Подпись над значением. */\n title?: string\n /** Число знаков после запятой. */\n precision?: number\n /** Разделитель разрядов. Сильнее локали; без него и без локали — узкий пробел. */\n groupSeparator?: string\n /** Десятичный разделитель. Сильнее локали; без него и без локали — точка. */\n decimalSeparator?: string\n /**\n * BCP-47 локаль форматирования. Не задана — берётся из i18n-адаптера; нет и\n * адаптера — работают ручные разделители.\n */\n locale?: string\n /** Приписка перед значением (валюта, знак). */\n prefix?: string\n /** Приписка после значения (единица измерения, `%`). */\n suffix?: string\n /**\n * Иконка слева от блока: Vue-компонент либо класс иконки вашей UnoCSS-сборки\n * (`'i-lucide-users'` — тогда нужен ваш `presetIcons`, см. `docs/installation.md`).\n */\n icon?: string | Component\n size?: GrStatisticSize\n /** Тон значения; точечно перекрывается `--gr-statistic-value-color`. */\n tone?: GrStatisticTone\n /**\n * Что считать хорошим: тон выводится из знака самого значения. Для выручки\n * рост — успех, для себестоимости и оттока — наоборот. Явный `tone` сильнее:\n * выведенный тон — умолчание, а не диктат.\n */\n polarity?: GrDeltaPolarity\n /** Направление динамики — задаёт цвет и иконку строки под значением. */\n trend?: GrStatisticTrend\n /** Текст динамики (например `+12,5% к прошлой неделе`). */\n trendText?: string\n /** Состояние загрузки: вместо значения — плейсхолдер. */\n loading?: boolean\n /**\n * Перебирать числа при появлении и при смене значения. Нечисловое значение\n * ставится сразу, под `prefers-reduced-motion: reduce` — тоже.\n */\n animate?: boolean\n /**\n * Длительность перебора в миллисекундах. Не токеном: шкала `--gr-duration-*`\n * заканчивается на 300 мс и описывает смену состояния, а перебор чисел —\n * другой жанр, и его число живёт там же, где твин.\n */\n animateDuration?: number\n /** Свой корневой тег (`RouterLink`, `Link` от Inertia). Сильнее `href`. */\n as?: string | Component\n /** Показатель-ссылка: переход к деталям. */\n href?: string\n /** Показатель-кнопка: интерактивна вся плитка. */\n clickable?: boolean\n}\n\nexport interface GrStatisticEmits {\n (e: 'click', event: MouseEvent): void\n}\n\nconst props = withDefaults(\n defineProps<GrStatisticProps>(),\n {\n title: undefined,\n precision: undefined,\n // Дефолты живут в `formatStatisticValue`: иначе «пользователь задал пробел»\n // было бы неотличимо от «сработал дефолт», и локаль не смогла бы победить.\n groupSeparator: undefined,\n decimalSeparator: undefined,\n locale: undefined,\n prefix: undefined,\n suffix: undefined,\n icon: undefined,\n size: undefined,\n // Дефолт тона живёт в `resolvedTone`: с ним здесь «пользователь задал\n // neutral» было бы неотличимо от «сработал дефолт», и явный\n // `tone=\"neutral\"` не смог бы отменить `polarity`.\n tone: undefined,\n polarity: undefined,\n trend: undefined,\n trendText: undefined,\n loading: false,\n animate: false,\n animateDuration: 600,\n as: undefined,\n href: undefined,\n clickable: false,\n },\n)\n\nconst emit = defineEmits<GrStatisticEmits>()\n\nconst slots = useSlots()\n\nconst { t, locale } = useGranularityTranslations()\n\nconst resolvedLocale = computed(() => props.locale ?? locale.value)\n\n/**\n * Число текущего кадра перебора. `null` — перебора нет, показываем сам проп:\n * так значение остаётся конечным и на сервере, и до первого твина.\n */\nconst frameValue = ref<number | null>(null)\n\n// Обязательное значение может не доехать — промах вызова, асинхронный\n// источник, — и плитка напечатала бы слово `undefined`. Прочерк — тот же знак\n// пустоты, которым `GrDelta` рисует отсутствующую величину.\nconst resolvedValue = computed<number | string>(() => props.value ?? '—')\n\nconst formatted = computed(() => formatStatisticValue(frameValue.value ?? resolvedValue.value, {\n precision: props.precision,\n groupSeparator: props.groupSeparator,\n decimalSeparator: props.decimalSeparator,\n locale: resolvedLocale.value,\n}))\n\n/** Конечное значение — то, что читает диктор, пока на экране идёт перебор. */\nconst finalFormatted = computed(() => formatStatisticValue(resolvedValue.value, {\n precision: props.precision,\n groupSeparator: props.groupSeparator,\n decimalSeparator: props.decimalSeparator,\n locale: resolvedLocale.value,\n}))\n\nconst isCounting = computed(() => frameValue.value !== null)\n\nif (__GR_DEV__) {\n watchEffect(() => {\n if (props.value === undefined || props.value === null) {\n console.warn(\n `[granularity] GrStatistic: обязательный проп \\`value\\` не передан — получено ${String(props.value)}.`,\n )\n }\n })\n}\n\nconst hasTitle = computed(() => Boolean(props.title || slots.title))\n\nconst isInteractive = computed(() => Boolean(props.as || props.href || props.clickable))\n\nconst rootTag = computed<string | Component>(() => {\n if (props.as)\n return typeof props.as === 'string' ? props.as : markRaw(props.as)\n\n if (props.href)\n return 'a'\n\n return props.clickable ? 'button' : 'div'\n})\n\n/**\n * Компонент-ссылка (`Link` от Inertia, `RouterLink`) рендерит `<a>` сам, и без\n * `href` он ведёт в никуда. Строковый тег, кроме `a`, атрибут не понимает —\n * там он и гасится.\n */\nconst rootHref = computed(() => (\n typeof rootTag.value === 'string' && rootTag.value !== 'a' ? undefined : props.href\n))\n\nconst rootClass = computed(() => statisticRootClass({ interactive: isInteractive.value }))\n\nfunction toNumber(value: number | string): number | null {\n const numeric = typeof value === 'number' ? value : Number(value)\n return Number.isFinite(numeric) ? numeric : null\n}\n\nlet frameHandle: number | null = null\n\nfunction stopCounting(): void {\n if (frameHandle !== null)\n cancelAnimationFrame(frameHandle)\n frameHandle = null\n}\n\n/**\n * Перебор чисел ведёт JS, а глобальный кламп в `base.css` гасит только CSS —\n * значит уважать «уменьшить движение» обязан сам компонент (`docs/motion.md`).\n * Читается в момент старта, а не в теле `setup`: `matchMedia` на сервере нет, и\n * первый клиентский рендер разошёлся бы с серверным.\n */\nfunction prefersReducedMotion(): boolean {\n return typeof window !== 'undefined'\n && window.matchMedia?.('(prefers-reduced-motion: reduce)')?.matches === true\n}\n\nfunction runCountUp(from: number, to: number): void {\n stopCounting()\n\n const duration = props.animateDuration\n if (!(duration > 0) || from === to || prefersReducedMotion()) {\n frameValue.value = null\n return\n }\n\n const started = performance.now()\n frameValue.value = from\n\n const step = (now: number): void => {\n const elapsed = now - started\n const value = countUpFrame(from, to, elapsed, duration, props.precision)\n\n if (elapsed >= duration) {\n // Конец перебора снимает и подмену значения, и подмену для диктора.\n frameValue.value = null\n frameHandle = null\n return\n }\n\n frameValue.value = value\n frameHandle = requestAnimationFrame(step)\n }\n\n frameHandle = requestAnimationFrame(step)\n}\n\n/** Перебирать можно только числа: «2 ч 15 мин» и «—» ставятся сразу. */\nfunction startIfNumeric(from: number | string, to: number | string): void {\n if (!props.animate || props.loading) {\n stopCounting()\n frameValue.value = null\n return\n }\n\n const target = toNumber(to)\n const source = toNumber(from)\n\n if (target === null || source === null) {\n stopCounting()\n frameValue.value = null\n return\n }\n\n runCountUp(source, target)\n}\n\n// Появление плитки считается переходом от нуля: ради этого эффекта проп и нужен.\nonMounted(() => startIfNumeric(0, props.value))\n\nwatch(() => props.value, (next, prev) => startIfNumeric(prev, next))\n\nonBeforeUnmount(stopCounting)\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrStatistic' })\nconst resolvedPolarity = useGrComponentProp('GrStatistic', 'polarity', () => props.polarity, 'none')\n/**\n * Кегль приписки идёт ступенью шкалы плитки, а не долей от величины: на `md`\n * число набрано `--gr-text-3xl`, и доля подняла бы приписку вровень с ним.\n */\nconst affixStyle = computed(() => ({ '--gr-value-suffix-size': statisticAffixSizeVarBySize[resolvedSize.value] }))\n\n/**\n * Тон значения: явный проп → выведенный из знака → нейтральный.\n *\n * Отдельной ветки «полярность не задана» нет намеренно: `deltaTone(x, 'none')`\n * нейтрален при любом значении, то есть `'none'` и есть прежнее поведение.\n *\n * Знак берётся у самого пропа, а не у кадра перебора: `frameValue` стартует с\n * нуля, и цвет плитки мигал бы нейтральным на каждой анимации.\n */\nconst resolvedTone = computed<GrStatisticTone>(() => {\n if (props.tone !== undefined)\n return props.tone\n\n const numeric = toNumber(props.value)\n\n // «2 ч 15 мин» и «—» знака не имеют — красить нечем.\n return numeric === null ? 'neutral' : deltaTone(numeric, resolvedPolarity.value)\n})\n\nconst trendIconByTrend: Record<GrStatisticTrend, Component> = {\n up: IconTrendingUp,\n down: IconTrendingDown,\n flat: IconMinus,\n}\n\n// Иконка направления `aria-hidden`, а «+12,5 %» само по себе рост от падения не\n// отличает: направление доносит скрытая подпись.\nconst trendLabel = computed(() => {\n if (!props.trend)\n return undefined\n\n const labels = {\n up: () => t('gr.statistic.trendUp', 'Increase'),\n down: () => t('gr.statistic.trendDown', 'Decrease'),\n flat: () => t('gr.statistic.trendFlat', 'No change'),\n }\n\n return labels[props.trend]()\n})\n\ndefineSlots<{\n /** Значение вместо пропа `value`. */\n default?: () => any\n /** Иконка перед подписью. */\n icon?: () => any\n /** Подпись показателя вместо пропа `title`. */\n title?: () => any\n /** Приписка перед значением: знак валюты, стрелка. */\n prefix?: () => any\n /** Приписка после значения: единица измерения, процент. */\n suffix?: () => any\n /** Динамика под значением вместо встроенного `GrDelta`. */\n trend?: () => any\n}>()\n</script>\n\n<template>\n <component\n :is=\"rootTag\"\n data-gr-statistic\n :type=\"rootTag === 'button' ? 'button' : undefined\"\n :href=\"rootHref\"\n :class=\"rootClass\"\n @click=\"emit('click', $event)\"\n >\n <span\n v-if=\"icon || $slots.icon\"\n data-gr-statistic-icon\n class=\"mt-0.5 inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-[var(--gr-radius-lg)] bg-[var(--gr-muted)] text-[var(--gr-muted-fg)]\"\n >\n <slot name=\"icon\">\n <component :is=\"iconTag(icon)\" class=\"block h-4 w-4\" :class=\"iconClass(icon)\" aria-hidden=\"true\" />\n </slot>\n </span>\n\n <div class=\"min-w-0\">\n <!--\n Подпись и значение — пара «термин — значение», а не два соседних блока.\n `<dl>` появляется только вместе с подписью: список определений без `<dt>`\n — та же бессвязность, только с претензией на семантику. Поля обнуляются\n руками: preflight пакета сбрасывает их лишь у `body`.\n -->\n <component :is=\"hasTitle ? 'dl' : 'div'\" class=\"m-0\">\n <dt\n v-if=\"hasTitle\"\n data-gr-statistic-title\n :class=\"[statisticTitleClass, statisticTitleSizeBySize[resolvedSize]]\"\n >\n <slot name=\"title\">\n {{ title }}\n </slot>\n </dt>\n\n <!-- Плейсхолдер загрузки повторяет высоту строки значения, чтобы блок не прыгал. -->\n <component\n :is=\"hasTitle ? 'dd' : 'div'\"\n v-if=\"loading\"\n data-gr-statistic-placeholder\n data-testid=\"gr-statistic-placeholder\"\n class=\"m-0 mt-1\"\n role=\"status\"\n aria-busy=\"true\"\n >\n <GrSkeleton\n width=\"6rem\"\n :height=\"statisticPlaceholderHeightBySize[resolvedSize]\"\n rounded=\"var(--gr-radius-md)\"\n />\n <span class=\"sr-only\">{{ t('gr.loading.defaultText', 'Loading...') }}</span>\n </component>\n\n <component\n :is=\"hasTitle ? 'dd' : 'div'\"\n v-else\n data-gr-statistic-value\n data-testid=\"gr-statistic-value\"\n class=\"m-0 mt-0.5 flex items-baseline\"\n :class=\"statisticValueClass({ size: resolvedSize, tone: resolvedTone })\"\n >\n <!--\n Кегль и тон стоят на контейнере, а не на каждом узле: `GrValue`\n наследует их обоих, и отрицательная сумма краснеет целиком, вместе\n со знаком валюты. Оформление приписок настраивается токенами\n `--gr-value-*` — плитка за потребителя этого не решает.\n -->\n <GrValue :prefix=\"prefix\" :suffix=\"suffix\" :style=\"affixStyle\">\n <template v-if=\"$slots.prefix\" #prefix>\n <slot name=\"prefix\" />\n </template>\n\n <span :aria-hidden=\"isCounting ? 'true' : undefined\">\n <slot>{{ formatted }}</slot>\n </span>\n\n <!--\n Пока идёт перебор, на экране промежуточное число. Диктору отдаётся\n конечное: «1 284» глазами и «743» в ушах — не шум, а неверные данные.\n -->\n <template v-if=\"isCounting\" #trail>\n <span data-gr-statistic-final class=\"sr-only\">{{ finalFormatted }}</span>\n </template>\n\n <template v-if=\"$slots.suffix\" #suffix>\n <slot name=\"suffix\" />\n </template>\n </GrValue>\n </component>\n </component>\n\n <div\n v-if=\"trend || trendText || $slots.trend\"\n data-gr-statistic-trend\n data-testid=\"gr-statistic-trend\"\n class=\"mt-1 inline-flex items-center gap-1\"\n :class=\"[statisticTrendSizeBySize[resolvedSize], statisticTrendClassByTrend[trend ?? 'flat']]\"\n >\n <span v-if=\"trendLabel\" data-gr-statistic-trend-label class=\"sr-only\">{{ trendLabel }}</span>\n\n <slot name=\"trend\">\n <component\n :is=\"trendIconByTrend[trend]\"\n v-if=\"trend\"\n class=\"block h-3.5 w-3.5\"\n aria-hidden=\"true\"\n />\n <span>{{ trendText }}</span>\n </slot>\n </div>\n </div>\n </component>\n</template>\n"],"mappings":"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"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { n as formatStatisticValue, t as GrStatistic_default } from "./chunks/GrStatistic-
|
|
1
|
+
import { n as formatStatisticValue, t as GrStatistic_default } from "./chunks/GrStatistic-C1_nY4Gc.js";
|
|
2
2
|
import { n as grStatisticSafelist, t as grStatisticConfig } from "../../chunks/config-DGC00bTp.js";
|
|
3
3
|
export { GrStatistic_default as GrStatistic, GrStatistic_default as default, formatStatisticValue, grStatisticConfig, grStatisticSafelist };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { a as useGrComponentSize, i as useGrComponentProp } from "../../../chunks/context-
|
|
1
|
+
import { a as useGrComponentSize, i as useGrComponentProp } from "../../../chunks/context-CPMRfwVr.js";
|
|
2
2
|
import { n as useGranularityTranslations } from "../../../chunks/granularityI18n-BC5zsj_d.js";
|
|
3
3
|
import { t as check_default } from "../../../chunks/check-BZI24Qs4.js";
|
|
4
|
-
import { t as GrProgressBar_default } from "../../GrProgressBar/chunks/GrProgressBar-
|
|
4
|
+
import { t as GrProgressBar_default } from "../../GrProgressBar/chunks/GrProgressBar-C3NRBqIP.js";
|
|
5
5
|
import { _ as stepsSizeClassBySize, a as stepsCompactBarClass, b as stepsTriggerEnabledClass, c as stepsCompactRowClass, d as stepsConnectorPendingClass, f as stepsDescriptionClass, g as stepsRootClass, h as stepsListClass, i as grStepsMarkerClass, l as stepsConnectorClass, m as stepsItemConnectedClass, o as stepsCompactCounterClass, p as stepsItemClass, r as grStepsLabelClass, s as stepsCompactLabelClass, u as stepsConnectorDoneClass, v as stepsTriggerClass, y as stepsTriggerClassByOrientation } from "../../../chunks/config-CLjjjPZj.js";
|
|
6
6
|
import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, normalizeClass, openBlock, renderList, renderSlot, resolveDynamicComponent, toDisplayString, unref, withCtx } from "vue";
|
|
7
7
|
//#region src/components/GrSteps/stepsModel.ts
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GrSteps-CpGHXLOc.js","names":[],"sources":["../../../../src/components/GrSteps/stepsModel.ts","../../../../src/components/GrSteps/GrSteps.vue","../../../../src/components/GrSteps/GrSteps.vue"],"sourcesContent":["import type { Component } from 'vue'\n\n/**\n * Правила шагового мастера — чистая арифметика без Vue.\n *\n * Вынесено отдельным модулем и тестируется без монтирования: «можно ли уйти на\n * шаг N» и «какого он статуса» — вся содержательная логика компонента, и\n * проверять её через отрендеренный DOM было бы дороже и слабее (эталон —\n * `GrForm/validation.ts`).\n */\n\n/**\n * Состояние шага.\n *\n * `error` не выводится из позиции — его ставит приложение, когда шаг пройден,\n * но не прошёл проверку. Без него мастер не умеет сказать «на втором шаге\n * остались ошибки», ради чего гейт перехода и заводится.\n */\nexport type GrStepStatus = 'complete' | 'current' | 'upcoming' | 'error'\n\nexport type GrStep = {\n value: string\n label: string\n /** Вторая строка под подписью: что от пользователя ждут на этом шаге. */\n description?: string\n /**\n * Иконка вместо номера — декоративна. Vue-компонент либо класс иконки вашей\n * UnoCSS-сборки (`i-lucide-*` требует вашего `presetIcons`).\n */\n icon?: string | Component\n /** Явный статус. Не задан — выводится из позиции относительно текущего шага. */\n status?: GrStepStatus\n disabled?: boolean\n}\n\nexport function stepIndexOf(steps: readonly GrStep[], value: string): number {\n return steps.findIndex(step => step.value === value)\n}\n\n/**\n * Статус шага: явный побеждает позиционный.\n *\n * Позиция даёт три состояния из четырёх, и только `error` приходит снаружи —\n * вывести его неоткуда, ошибка живёт в форме, а не в порядке шагов.\n */\nexport function stepStatusAt(steps: readonly GrStep[], index: number, currentIndex: number): GrStepStatus {\n const explicit = steps[index]?.status\n if (explicit)\n return explicit\n\n if (index < currentIndex)\n return 'complete'\n if (index === currentIndex)\n return 'current'\n return 'upcoming'\n}\n\n/**\n * Можно ли уйти на шаг по его индексу.\n *\n * `linear` ограничивает **только движение вперёд**, и не одним шагом, а первым\n * непройденным: вернуться назад и снова прыгнуть на тот же шаг, до которого уже\n * дошёл, — нормальный сценарий правки заполненного мастера. Запрещать его\n * значило бы заставлять пользователя проходить всё заново ради одной опечатки.\n */\nexport function canEnterStep(options: {\n steps: readonly GrStep[]\n index: number\n currentIndex: number\n linear: boolean\n}): boolean {\n const { steps, index, currentIndex, linear } = options\n const step = steps[index]\n\n if (!step || step.disabled)\n return false\n if (index === currentIndex)\n return false\n if (!linear || index < currentIndex)\n return true\n\n return index <= furthestReachableIndex(steps, currentIndex)\n}\n\n/**\n * Дальний край доступного в линейном режиме: текущий шаг плюс идущие за ним\n * подряд помеченные пройденными. Шаг с ошибкой краем не является — он и есть\n * причина, по которой дальше идти нельзя.\n */\nfunction furthestReachableIndex(steps: readonly GrStep[], currentIndex: number): number {\n let furthest = currentIndex + 1\n\n for (let index = currentIndex + 1; index < steps.length; index += 1) {\n if (steps[index]?.status !== 'complete')\n break\n furthest = index + 1\n }\n\n return Math.min(furthest, steps.length - 1)\n}\n\n/** Индекс следующего доступного шага, или `-1`, если дальше идти некуда. */\nexport function nextEnterableIndex(steps: readonly GrStep[], currentIndex: number): number {\n for (let index = currentIndex + 1; index < steps.length; index += 1) {\n if (!steps[index]?.disabled)\n return index\n }\n\n return -1\n}\n\n/** Индекс предыдущего доступного шага, или `-1`. */\nexport function previousEnterableIndex(steps: readonly GrStep[], currentIndex: number): number {\n for (let index = currentIndex - 1; index >= 0; index -= 1) {\n if (!steps[index]?.disabled)\n return index\n }\n\n return -1\n}\n","<script setup lang=\"ts\">\nimport type { Component } from 'vue'\nimport { computed } from 'vue'\n\nimport IconCheck from '~icons/lucide/check'\n\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\nimport GrProgressBar from '../GrProgressBar/GrProgressBar.vue'\n\nimport type { GrStepsOrientation, GrStepsSize, GrStepsVariant } from './grStepsStyles'\nimport {\n grStepsLabelClass,\n grStepsMarkerClass,\n stepsCompactBarClass,\n stepsCompactCounterClass,\n stepsCompactLabelClass,\n stepsCompactRowClass,\n stepsConnectorClass,\n stepsConnectorDoneClass,\n stepsConnectorPendingClass,\n stepsDescriptionClass,\n stepsItemClass,\n stepsItemConnectedClass,\n stepsListClass,\n stepsRootClass,\n stepsSizeClassBySize,\n stepsTriggerClass,\n stepsTriggerClassByOrientation,\n stepsTriggerEnabledClass,\n} from './grStepsStyles'\nimport type { GrStep, GrStepStatus } from './stepsModel'\nimport {\n canEnterStep,\n nextEnterableIndex,\n previousEnterableIndex,\n stepIndexOf,\n stepStatusAt,\n} from './stepsModel'\n\n/**\n * Индикатор шагов многошагового мастера.\n *\n * По семантике это **навигация**, а не составной виджет выбора: `<nav>` со\n * списком, где пройденный шаг — кнопка со своей остановкой `Tab`, текущий —\n * `aria-current=\"step\"`, будущий — вне таб-порядка. Той же формы держатся\n * `GrBreadcrumbs` и `GrBottomNav`; `role=\"tab\"` здесь неприменим — tablist без\n * `tabpanel` это сломанный паттерн, а панель шага никакой роли не несёт.\n *\n * Контент шага компонент не рисует: его ставит потребитель обычным `v-if`.\n */\nexport interface GrStepsProps {\n modelValue: string\n steps: GrStep[]\n orientation?: GrStepsOrientation\n size?: GrStepsSize\n /** Компактный вид для узкой колонки: подпись текущего шага и полоса прогресса. */\n variant?: GrStepsVariant\n /**\n * Вперёд — не дальше первого непройденного. Назад свободно всегда: вернуться\n * и поправить заполненное — обычный сценарий, а не обход правила.\n */\n linear?: boolean\n /** Переход кликом по шагу. Выключен — лента становится только индикатором. */\n clickable?: boolean\n ariaLabel?: string\n /**\n * Гейт перехода. Вернул `false` — перехода нет.\n *\n * Сюда потребитель кладёт валидацию шага: `GrSteps` про `GrForm` ничего не\n * знает и знать не должен, а форма уже отдаёт `validate()`/`validateField()`.\n */\n beforeLeave?: (from: string, to: string) => boolean | Promise<boolean>\n}\n\nexport interface GrStepsEmits {\n (e: 'update:modelValue', value: string): void\n (e: 'change', value: string): void\n}\n\nconst props = withDefaults(defineProps<GrStepsProps>(), {\n // Дефолты живут в резолверах ниже: Vue подставил бы свой раньше, чем\n // компонент заглянет в `GrConfigProvider`.\n orientation: undefined,\n size: undefined,\n variant: undefined,\n linear: undefined,\n clickable: undefined,\n ariaLabel: undefined,\n beforeLeave: undefined,\n})\n\nconst emit = defineEmits<GrStepsEmits>()\n\ndefineSlots<{\n /**\n * Своя разметка пункта. Корневой тег, `aria-current` и клик остаются за\n * компонентом — иначе доступность шага пришлось бы собирать заново.\n */\n step?: (props: { step: GrStep, index: number, status: GrStepStatus, enterable: boolean }) => unknown\n}>()\n\nconst { t } = useGranularityTranslations()\n\nconst resolvedSize = useGrComponentSize<GrStepsSize>(() => props.size, { component: 'GrSteps' })\nconst resolvedOrientation = useGrComponentProp('GrSteps', 'orientation', () => props.orientation, 'horizontal')\nconst resolvedVariant = useGrComponentProp('GrSteps', 'variant', () => props.variant, 'steps')\nconst resolvedLinear = useGrComponentProp('GrSteps', 'linear', () => props.linear, true)\nconst resolvedClickable = useGrComponentProp('GrSteps', 'clickable', () => props.clickable, true)\n\nconst currentIndex = computed(() => stepIndexOf(props.steps, props.modelValue))\nconst currentStep = computed(() => props.steps[currentIndex.value])\n\nconst resolvedAriaLabel = computed(() => props.ariaLabel ?? t('gr.steps.label', 'Progress'))\n\nfunction statusAt(index: number): GrStepStatus {\n return stepStatusAt(props.steps, index, currentIndex.value)\n}\n\nfunction isEnterable(index: number): boolean {\n if (!resolvedClickable.value)\n return false\n return canEnterStep({\n steps: props.steps,\n index,\n currentIndex: currentIndex.value,\n linear: resolvedLinear.value,\n })\n}\n\n/**\n * Пройденный шаг печатает галку вместо номера, шаг с ошибкой — восклицание.\n * Оба декоративны: смысл несёт скрытая подпись состояния рядом с меткой.\n */\nfunction markerText(index: number): string {\n return statusAt(index) === 'error' ? '!' : String(index + 1)\n}\n\nfunction stateText(status: GrStepStatus): string {\n if (status === 'complete')\n return t('gr.steps.completed', 'completed')\n if (status === 'error')\n return t('gr.steps.error', 'has errors')\n return ''\n}\n\nconst statusText = computed(() => t('gr.steps.status', 'Step {step} of {count}', {\n step: currentIndex.value + 1,\n count: props.steps.length,\n}))\n\n/** Полоса компактного вида: доля пройденного, а не номер шага. */\nconst progressValue = computed(() => {\n if (props.steps.length <= 1)\n return 100\n return Math.round((currentIndex.value / (props.steps.length - 1)) * 100)\n})\n\nfunction tagFor(index: number): 'button' | 'span' {\n return isEnterable(index) ? 'button' : 'span'\n}\n\nfunction triggerClass(index: number): string {\n const base = `${stepsTriggerClass} ${stepsTriggerClassByOrientation[resolvedOrientation.value]}`\n return isEnterable(index) ? `${base} ${stepsTriggerEnabledClass}` : base\n}\n\n/** Место под соединитель нужно только тем пунктам, за которыми ещё есть шаг. */\nfunction itemClass(index: number): string {\n const base = stepsItemClass[resolvedOrientation.value]\n if (index >= props.steps.length - 1)\n return base\n return `${base} ${stepsItemConnectedClass[resolvedOrientation.value]}`.trim()\n}\n\nfunction connectorClass(index: number): string {\n const done = statusAt(index) === 'complete'\n return `${stepsConnectorClass[resolvedOrientation.value]} ${done ? stepsConnectorDoneClass : stepsConnectorPendingClass}`\n}\n\nfunction iconOf(step: GrStep): string | Component | undefined {\n return step.icon\n}\n\nasync function goToIndex(index: number): Promise<boolean> {\n const target = props.steps[index]\n if (!target || target.value === props.modelValue)\n return false\n\n if (props.beforeLeave) {\n const allowed = await props.beforeLeave(props.modelValue, target.value)\n if (!allowed)\n return false\n }\n\n emit('update:modelValue', target.value)\n emit('change', target.value)\n\n return true\n}\n\nasync function onStepClick(index: number): Promise<void> {\n if (!isEnterable(index))\n return\n await goToIndex(index)\n}\n\nasync function next(): Promise<boolean> {\n const index = nextEnterableIndex(props.steps, currentIndex.value)\n return index === -1 ? false : goToIndex(index)\n}\n\nasync function back(): Promise<boolean> {\n const index = previousEnterableIndex(props.steps, currentIndex.value)\n return index === -1 ? false : goToIndex(index)\n}\n\nasync function goTo(value: string): Promise<boolean> {\n return goToIndex(stepIndexOf(props.steps, value))\n}\n\ndefineExpose({\n next,\n back,\n goTo,\n isFirst: computed(() => previousEnterableIndex(props.steps, currentIndex.value) === -1),\n isLast: computed(() => nextEnterableIndex(props.steps, currentIndex.value) === -1),\n})\n</script>\n\n<template>\n <nav\n data-gr-steps\n :data-orientation=\"resolvedOrientation\"\n :class=\"[stepsRootClass, stepsSizeClassBySize[resolvedSize]]\"\n :aria-label=\"resolvedAriaLabel\"\n >\n <div v-if=\"resolvedVariant === 'compact'\" data-gr-steps-compact>\n <div :class=\"stepsCompactRowClass\">\n <span :class=\"stepsCompactLabelClass\">{{ currentStep?.label }}</span>\n <span :class=\"stepsCompactCounterClass\">{{ currentIndex + 1 }} / {{ steps.length }}</span>\n </div>\n <!--\n Полоса декоративна: то же самое уже сказано текстом рядом и скрытым\n живым регионом ниже, а второй `progressbar` в дереве заставил бы\n диктора читать прогресс дважды.\n -->\n <GrProgressBar\n :value=\"progressValue\"\n :size=\"resolvedSize === 'lg' ? 'md' : 'sm'\"\n :class=\"stepsCompactBarClass\"\n aria-hidden=\"true\"\n />\n </div>\n\n <ol v-else data-gr-steps-list :class=\"stepsListClass[resolvedOrientation]\">\n <li\n v-for=\"(step, index) in steps\"\n :key=\"step.value\"\n data-gr-step\n :data-value=\"step.value\"\n :data-status=\"statusAt(index)\"\n :class=\"itemClass(index)\"\n >\n <component\n :is=\"tagFor(index)\"\n data-gr-step-trigger\n :type=\"tagFor(index) === 'button' ? 'button' : undefined\"\n :class=\"triggerClass(index)\"\n :aria-current=\"statusAt(index) === 'current' ? 'step' : undefined\"\n :aria-disabled=\"step.disabled ? 'true' : undefined\"\n @click=\"tagFor(index) === 'button' ? onStepClick(index) : undefined\"\n >\n <slot\n name=\"step\"\n :step=\"step\"\n :index=\"index\"\n :status=\"statusAt(index)\"\n :enterable=\"isEnterable(index)\"\n >\n <span data-gr-step-marker :class=\"grStepsMarkerClass(statusAt(index))\" aria-hidden=\"true\">\n <component :is=\"iconOf(step)\" v-if=\"step.icon\" class=\"h-[60%] w-[60%]\" />\n <IconCheck v-else-if=\"statusAt(index) === 'complete'\" class=\"h-[60%] w-[60%]\" />\n <template v-else>{{ markerText(index) }}</template>\n </span>\n\n <span class=\"block min-w-0\">\n <span data-gr-step-label :class=\"grStepsLabelClass(statusAt(index))\">{{ step.label }}</span>\n <!--\n Состояние шага словом: пройденный и шаг с ошибкой иначе звучат\n одинаково — цвет маркера диктору недоступен.\n -->\n <span v-if=\"stateText(statusAt(index))\" class=\"sr-only\"> — {{ stateText(statusAt(index)) }}</span>\n <span v-if=\"step.description\" data-gr-step-description :class=\"stepsDescriptionClass\">\n {{ step.description }}\n </span>\n </span>\n </slot>\n </component>\n\n <span\n v-if=\"index < steps.length - 1\"\n data-gr-step-connector\n :class=\"connectorClass(index)\"\n aria-hidden=\"true\"\n />\n </li>\n </ol>\n\n <!--\n «Шаг 2 из 5» — состояние виджета, а не событие, поэтому живёт своим\n регионом в разметке, а не уходит в общий объявитель (`docs/announcer.md`).\n Образец — компактный индикатор `GrPagination`.\n -->\n <div data-gr-steps-status role=\"status\" class=\"sr-only\">\n{{ statusText }}\n</div>\n </nav>\n</template>\n","<script setup lang=\"ts\">\nimport type { Component } from 'vue'\nimport { computed } from 'vue'\n\nimport IconCheck from '~icons/lucide/check'\n\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\nimport GrProgressBar from '../GrProgressBar/GrProgressBar.vue'\n\nimport type { GrStepsOrientation, GrStepsSize, GrStepsVariant } from './grStepsStyles'\nimport {\n grStepsLabelClass,\n grStepsMarkerClass,\n stepsCompactBarClass,\n stepsCompactCounterClass,\n stepsCompactLabelClass,\n stepsCompactRowClass,\n stepsConnectorClass,\n stepsConnectorDoneClass,\n stepsConnectorPendingClass,\n stepsDescriptionClass,\n stepsItemClass,\n stepsItemConnectedClass,\n stepsListClass,\n stepsRootClass,\n stepsSizeClassBySize,\n stepsTriggerClass,\n stepsTriggerClassByOrientation,\n stepsTriggerEnabledClass,\n} from './grStepsStyles'\nimport type { GrStep, GrStepStatus } from './stepsModel'\nimport {\n canEnterStep,\n nextEnterableIndex,\n previousEnterableIndex,\n stepIndexOf,\n stepStatusAt,\n} from './stepsModel'\n\n/**\n * Индикатор шагов многошагового мастера.\n *\n * По семантике это **навигация**, а не составной виджет выбора: `<nav>` со\n * списком, где пройденный шаг — кнопка со своей остановкой `Tab`, текущий —\n * `aria-current=\"step\"`, будущий — вне таб-порядка. Той же формы держатся\n * `GrBreadcrumbs` и `GrBottomNav`; `role=\"tab\"` здесь неприменим — tablist без\n * `tabpanel` это сломанный паттерн, а панель шага никакой роли не несёт.\n *\n * Контент шага компонент не рисует: его ставит потребитель обычным `v-if`.\n */\nexport interface GrStepsProps {\n modelValue: string\n steps: GrStep[]\n orientation?: GrStepsOrientation\n size?: GrStepsSize\n /** Компактный вид для узкой колонки: подпись текущего шага и полоса прогресса. */\n variant?: GrStepsVariant\n /**\n * Вперёд — не дальше первого непройденного. Назад свободно всегда: вернуться\n * и поправить заполненное — обычный сценарий, а не обход правила.\n */\n linear?: boolean\n /** Переход кликом по шагу. Выключен — лента становится только индикатором. */\n clickable?: boolean\n ariaLabel?: string\n /**\n * Гейт перехода. Вернул `false` — перехода нет.\n *\n * Сюда потребитель кладёт валидацию шага: `GrSteps` про `GrForm` ничего не\n * знает и знать не должен, а форма уже отдаёт `validate()`/`validateField()`.\n */\n beforeLeave?: (from: string, to: string) => boolean | Promise<boolean>\n}\n\nexport interface GrStepsEmits {\n (e: 'update:modelValue', value: string): void\n (e: 'change', value: string): void\n}\n\nconst props = withDefaults(defineProps<GrStepsProps>(), {\n // Дефолты живут в резолверах ниже: Vue подставил бы свой раньше, чем\n // компонент заглянет в `GrConfigProvider`.\n orientation: undefined,\n size: undefined,\n variant: undefined,\n linear: undefined,\n clickable: undefined,\n ariaLabel: undefined,\n beforeLeave: undefined,\n})\n\nconst emit = defineEmits<GrStepsEmits>()\n\ndefineSlots<{\n /**\n * Своя разметка пункта. Корневой тег, `aria-current` и клик остаются за\n * компонентом — иначе доступность шага пришлось бы собирать заново.\n */\n step?: (props: { step: GrStep, index: number, status: GrStepStatus, enterable: boolean }) => unknown\n}>()\n\nconst { t } = useGranularityTranslations()\n\nconst resolvedSize = useGrComponentSize<GrStepsSize>(() => props.size, { component: 'GrSteps' })\nconst resolvedOrientation = useGrComponentProp('GrSteps', 'orientation', () => props.orientation, 'horizontal')\nconst resolvedVariant = useGrComponentProp('GrSteps', 'variant', () => props.variant, 'steps')\nconst resolvedLinear = useGrComponentProp('GrSteps', 'linear', () => props.linear, true)\nconst resolvedClickable = useGrComponentProp('GrSteps', 'clickable', () => props.clickable, true)\n\nconst currentIndex = computed(() => stepIndexOf(props.steps, props.modelValue))\nconst currentStep = computed(() => props.steps[currentIndex.value])\n\nconst resolvedAriaLabel = computed(() => props.ariaLabel ?? t('gr.steps.label', 'Progress'))\n\nfunction statusAt(index: number): GrStepStatus {\n return stepStatusAt(props.steps, index, currentIndex.value)\n}\n\nfunction isEnterable(index: number): boolean {\n if (!resolvedClickable.value)\n return false\n return canEnterStep({\n steps: props.steps,\n index,\n currentIndex: currentIndex.value,\n linear: resolvedLinear.value,\n })\n}\n\n/**\n * Пройденный шаг печатает галку вместо номера, шаг с ошибкой — восклицание.\n * Оба декоративны: смысл несёт скрытая подпись состояния рядом с меткой.\n */\nfunction markerText(index: number): string {\n return statusAt(index) === 'error' ? '!' : String(index + 1)\n}\n\nfunction stateText(status: GrStepStatus): string {\n if (status === 'complete')\n return t('gr.steps.completed', 'completed')\n if (status === 'error')\n return t('gr.steps.error', 'has errors')\n return ''\n}\n\nconst statusText = computed(() => t('gr.steps.status', 'Step {step} of {count}', {\n step: currentIndex.value + 1,\n count: props.steps.length,\n}))\n\n/** Полоса компактного вида: доля пройденного, а не номер шага. */\nconst progressValue = computed(() => {\n if (props.steps.length <= 1)\n return 100\n return Math.round((currentIndex.value / (props.steps.length - 1)) * 100)\n})\n\nfunction tagFor(index: number): 'button' | 'span' {\n return isEnterable(index) ? 'button' : 'span'\n}\n\nfunction triggerClass(index: number): string {\n const base = `${stepsTriggerClass} ${stepsTriggerClassByOrientation[resolvedOrientation.value]}`\n return isEnterable(index) ? `${base} ${stepsTriggerEnabledClass}` : base\n}\n\n/** Место под соединитель нужно только тем пунктам, за которыми ещё есть шаг. */\nfunction itemClass(index: number): string {\n const base = stepsItemClass[resolvedOrientation.value]\n if (index >= props.steps.length - 1)\n return base\n return `${base} ${stepsItemConnectedClass[resolvedOrientation.value]}`.trim()\n}\n\nfunction connectorClass(index: number): string {\n const done = statusAt(index) === 'complete'\n return `${stepsConnectorClass[resolvedOrientation.value]} ${done ? stepsConnectorDoneClass : stepsConnectorPendingClass}`\n}\n\nfunction iconOf(step: GrStep): string | Component | undefined {\n return step.icon\n}\n\nasync function goToIndex(index: number): Promise<boolean> {\n const target = props.steps[index]\n if (!target || target.value === props.modelValue)\n return false\n\n if (props.beforeLeave) {\n const allowed = await props.beforeLeave(props.modelValue, target.value)\n if (!allowed)\n return false\n }\n\n emit('update:modelValue', target.value)\n emit('change', target.value)\n\n return true\n}\n\nasync function onStepClick(index: number): Promise<void> {\n if (!isEnterable(index))\n return\n await goToIndex(index)\n}\n\nasync function next(): Promise<boolean> {\n const index = nextEnterableIndex(props.steps, currentIndex.value)\n return index === -1 ? false : goToIndex(index)\n}\n\nasync function back(): Promise<boolean> {\n const index = previousEnterableIndex(props.steps, currentIndex.value)\n return index === -1 ? false : goToIndex(index)\n}\n\nasync function goTo(value: string): Promise<boolean> {\n return goToIndex(stepIndexOf(props.steps, value))\n}\n\ndefineExpose({\n next,\n back,\n goTo,\n isFirst: computed(() => previousEnterableIndex(props.steps, currentIndex.value) === -1),\n isLast: computed(() => nextEnterableIndex(props.steps, currentIndex.value) === -1),\n})\n</script>\n\n<template>\n <nav\n data-gr-steps\n :data-orientation=\"resolvedOrientation\"\n :class=\"[stepsRootClass, stepsSizeClassBySize[resolvedSize]]\"\n :aria-label=\"resolvedAriaLabel\"\n >\n <div v-if=\"resolvedVariant === 'compact'\" data-gr-steps-compact>\n <div :class=\"stepsCompactRowClass\">\n <span :class=\"stepsCompactLabelClass\">{{ currentStep?.label }}</span>\n <span :class=\"stepsCompactCounterClass\">{{ currentIndex + 1 }} / {{ steps.length }}</span>\n </div>\n <!--\n Полоса декоративна: то же самое уже сказано текстом рядом и скрытым\n живым регионом ниже, а второй `progressbar` в дереве заставил бы\n диктора читать прогресс дважды.\n -->\n <GrProgressBar\n :value=\"progressValue\"\n :size=\"resolvedSize === 'lg' ? 'md' : 'sm'\"\n :class=\"stepsCompactBarClass\"\n aria-hidden=\"true\"\n />\n </div>\n\n <ol v-else data-gr-steps-list :class=\"stepsListClass[resolvedOrientation]\">\n <li\n v-for=\"(step, index) in steps\"\n :key=\"step.value\"\n data-gr-step\n :data-value=\"step.value\"\n :data-status=\"statusAt(index)\"\n :class=\"itemClass(index)\"\n >\n <component\n :is=\"tagFor(index)\"\n data-gr-step-trigger\n :type=\"tagFor(index) === 'button' ? 'button' : undefined\"\n :class=\"triggerClass(index)\"\n :aria-current=\"statusAt(index) === 'current' ? 'step' : undefined\"\n :aria-disabled=\"step.disabled ? 'true' : undefined\"\n @click=\"tagFor(index) === 'button' ? onStepClick(index) : undefined\"\n >\n <slot\n name=\"step\"\n :step=\"step\"\n :index=\"index\"\n :status=\"statusAt(index)\"\n :enterable=\"isEnterable(index)\"\n >\n <span data-gr-step-marker :class=\"grStepsMarkerClass(statusAt(index))\" aria-hidden=\"true\">\n <component :is=\"iconOf(step)\" v-if=\"step.icon\" class=\"h-[60%] w-[60%]\" />\n <IconCheck v-else-if=\"statusAt(index) === 'complete'\" class=\"h-[60%] w-[60%]\" />\n <template v-else>{{ markerText(index) }}</template>\n </span>\n\n <span class=\"block min-w-0\">\n <span data-gr-step-label :class=\"grStepsLabelClass(statusAt(index))\">{{ step.label }}</span>\n <!--\n Состояние шага словом: пройденный и шаг с ошибкой иначе звучат\n одинаково — цвет маркера диктору недоступен.\n -->\n <span v-if=\"stateText(statusAt(index))\" class=\"sr-only\"> — {{ stateText(statusAt(index)) }}</span>\n <span v-if=\"step.description\" data-gr-step-description :class=\"stepsDescriptionClass\">\n {{ step.description }}\n </span>\n </span>\n </slot>\n </component>\n\n <span\n v-if=\"index < steps.length - 1\"\n data-gr-step-connector\n :class=\"connectorClass(index)\"\n aria-hidden=\"true\"\n />\n </li>\n </ol>\n\n <!--\n «Шаг 2 из 5» — состояние виджета, а не событие, поэтому живёт своим\n регионом в разметке, а не уходит в общий объявитель (`docs/announcer.md`).\n Образец — компактный индикатор `GrPagination`.\n -->\n <div data-gr-steps-status role=\"status\" class=\"sr-only\">\n{{ statusText }}\n</div>\n </nav>\n</template>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"GrSteps-DXj7m-WO.js","names":[],"sources":["../../../../src/components/GrSteps/stepsModel.ts","../../../../src/components/GrSteps/GrSteps.vue","../../../../src/components/GrSteps/GrSteps.vue"],"sourcesContent":["import type { Component } from 'vue'\n\n/**\n * Правила шагового мастера — чистая арифметика без Vue.\n *\n * Вынесено отдельным модулем и тестируется без монтирования: «можно ли уйти на\n * шаг N» и «какого он статуса» — вся содержательная логика компонента, и\n * проверять её через отрендеренный DOM было бы дороже и слабее (эталон —\n * `GrForm/validation.ts`).\n */\n\n/**\n * Состояние шага.\n *\n * `error` не выводится из позиции — его ставит приложение, когда шаг пройден,\n * но не прошёл проверку. Без него мастер не умеет сказать «на втором шаге\n * остались ошибки», ради чего гейт перехода и заводится.\n */\nexport type GrStepStatus = 'complete' | 'current' | 'upcoming' | 'error'\n\nexport type GrStep = {\n value: string\n label: string\n /** Вторая строка под подписью: что от пользователя ждут на этом шаге. */\n description?: string\n /**\n * Иконка вместо номера — декоративна. Vue-компонент либо класс иконки вашей\n * UnoCSS-сборки (`i-lucide-*` требует вашего `presetIcons`).\n */\n icon?: string | Component\n /** Явный статус. Не задан — выводится из позиции относительно текущего шага. */\n status?: GrStepStatus\n disabled?: boolean\n}\n\nexport function stepIndexOf(steps: readonly GrStep[], value: string): number {\n return steps.findIndex(step => step.value === value)\n}\n\n/**\n * Статус шага: явный побеждает позиционный.\n *\n * Позиция даёт три состояния из четырёх, и только `error` приходит снаружи —\n * вывести его неоткуда, ошибка живёт в форме, а не в порядке шагов.\n */\nexport function stepStatusAt(steps: readonly GrStep[], index: number, currentIndex: number): GrStepStatus {\n const explicit = steps[index]?.status\n if (explicit)\n return explicit\n\n if (index < currentIndex)\n return 'complete'\n if (index === currentIndex)\n return 'current'\n return 'upcoming'\n}\n\n/**\n * Можно ли уйти на шаг по его индексу.\n *\n * `linear` ограничивает **только движение вперёд**, и не одним шагом, а первым\n * непройденным: вернуться назад и снова прыгнуть на тот же шаг, до которого уже\n * дошёл, — нормальный сценарий правки заполненного мастера. Запрещать его\n * значило бы заставлять пользователя проходить всё заново ради одной опечатки.\n */\nexport function canEnterStep(options: {\n steps: readonly GrStep[]\n index: number\n currentIndex: number\n linear: boolean\n}): boolean {\n const { steps, index, currentIndex, linear } = options\n const step = steps[index]\n\n if (!step || step.disabled)\n return false\n if (index === currentIndex)\n return false\n if (!linear || index < currentIndex)\n return true\n\n return index <= furthestReachableIndex(steps, currentIndex)\n}\n\n/**\n * Дальний край доступного в линейном режиме: текущий шаг плюс идущие за ним\n * подряд помеченные пройденными. Шаг с ошибкой краем не является — он и есть\n * причина, по которой дальше идти нельзя.\n */\nfunction furthestReachableIndex(steps: readonly GrStep[], currentIndex: number): number {\n let furthest = currentIndex + 1\n\n for (let index = currentIndex + 1; index < steps.length; index += 1) {\n if (steps[index]?.status !== 'complete')\n break\n furthest = index + 1\n }\n\n return Math.min(furthest, steps.length - 1)\n}\n\n/** Индекс следующего доступного шага, или `-1`, если дальше идти некуда. */\nexport function nextEnterableIndex(steps: readonly GrStep[], currentIndex: number): number {\n for (let index = currentIndex + 1; index < steps.length; index += 1) {\n if (!steps[index]?.disabled)\n return index\n }\n\n return -1\n}\n\n/** Индекс предыдущего доступного шага, или `-1`. */\nexport function previousEnterableIndex(steps: readonly GrStep[], currentIndex: number): number {\n for (let index = currentIndex - 1; index >= 0; index -= 1) {\n if (!steps[index]?.disabled)\n return index\n }\n\n return -1\n}\n","<script setup lang=\"ts\">\nimport type { Component } from 'vue'\nimport { computed } from 'vue'\n\nimport IconCheck from '~icons/lucide/check'\n\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\nimport GrProgressBar from '../GrProgressBar/GrProgressBar.vue'\n\nimport type { GrStepsOrientation, GrStepsSize, GrStepsVariant } from './grStepsStyles'\nimport {\n grStepsLabelClass,\n grStepsMarkerClass,\n stepsCompactBarClass,\n stepsCompactCounterClass,\n stepsCompactLabelClass,\n stepsCompactRowClass,\n stepsConnectorClass,\n stepsConnectorDoneClass,\n stepsConnectorPendingClass,\n stepsDescriptionClass,\n stepsItemClass,\n stepsItemConnectedClass,\n stepsListClass,\n stepsRootClass,\n stepsSizeClassBySize,\n stepsTriggerClass,\n stepsTriggerClassByOrientation,\n stepsTriggerEnabledClass,\n} from './grStepsStyles'\nimport type { GrStep, GrStepStatus } from './stepsModel'\nimport {\n canEnterStep,\n nextEnterableIndex,\n previousEnterableIndex,\n stepIndexOf,\n stepStatusAt,\n} from './stepsModel'\n\n/**\n * Индикатор шагов многошагового мастера.\n *\n * По семантике это **навигация**, а не составной виджет выбора: `<nav>` со\n * списком, где пройденный шаг — кнопка со своей остановкой `Tab`, текущий —\n * `aria-current=\"step\"`, будущий — вне таб-порядка. Той же формы держатся\n * `GrBreadcrumbs` и `GrBottomNav`; `role=\"tab\"` здесь неприменим — tablist без\n * `tabpanel` это сломанный паттерн, а панель шага никакой роли не несёт.\n *\n * Контент шага компонент не рисует: его ставит потребитель обычным `v-if`.\n */\nexport interface GrStepsProps {\n modelValue: string\n steps: GrStep[]\n orientation?: GrStepsOrientation\n size?: GrStepsSize\n /** Компактный вид для узкой колонки: подпись текущего шага и полоса прогресса. */\n variant?: GrStepsVariant\n /**\n * Вперёд — не дальше первого непройденного. Назад свободно всегда: вернуться\n * и поправить заполненное — обычный сценарий, а не обход правила.\n */\n linear?: boolean\n /** Переход кликом по шагу. Выключен — лента становится только индикатором. */\n clickable?: boolean\n ariaLabel?: string\n /**\n * Гейт перехода. Вернул `false` — перехода нет.\n *\n * Сюда потребитель кладёт валидацию шага: `GrSteps` про `GrForm` ничего не\n * знает и знать не должен, а форма уже отдаёт `validate()`/`validateField()`.\n */\n beforeLeave?: (from: string, to: string) => boolean | Promise<boolean>\n}\n\nexport interface GrStepsEmits {\n (e: 'update:modelValue', value: string): void\n (e: 'change', value: string): void\n}\n\nconst props = withDefaults(defineProps<GrStepsProps>(), {\n // Дефолты живут в резолверах ниже: Vue подставил бы свой раньше, чем\n // компонент заглянет в `GrConfigProvider`.\n orientation: undefined,\n size: undefined,\n variant: undefined,\n linear: undefined,\n clickable: undefined,\n ariaLabel: undefined,\n beforeLeave: undefined,\n})\n\nconst emit = defineEmits<GrStepsEmits>()\n\ndefineSlots<{\n /**\n * Своя разметка пункта. Корневой тег, `aria-current` и клик остаются за\n * компонентом — иначе доступность шага пришлось бы собирать заново.\n */\n step?: (props: { step: GrStep, index: number, status: GrStepStatus, enterable: boolean }) => unknown\n}>()\n\nconst { t } = useGranularityTranslations()\n\nconst resolvedSize = useGrComponentSize<GrStepsSize>(() => props.size, { component: 'GrSteps' })\nconst resolvedOrientation = useGrComponentProp('GrSteps', 'orientation', () => props.orientation, 'horizontal')\nconst resolvedVariant = useGrComponentProp('GrSteps', 'variant', () => props.variant, 'steps')\nconst resolvedLinear = useGrComponentProp('GrSteps', 'linear', () => props.linear, true)\nconst resolvedClickable = useGrComponentProp('GrSteps', 'clickable', () => props.clickable, true)\n\nconst currentIndex = computed(() => stepIndexOf(props.steps, props.modelValue))\nconst currentStep = computed(() => props.steps[currentIndex.value])\n\nconst resolvedAriaLabel = computed(() => props.ariaLabel ?? t('gr.steps.label', 'Progress'))\n\nfunction statusAt(index: number): GrStepStatus {\n return stepStatusAt(props.steps, index, currentIndex.value)\n}\n\nfunction isEnterable(index: number): boolean {\n if (!resolvedClickable.value)\n return false\n return canEnterStep({\n steps: props.steps,\n index,\n currentIndex: currentIndex.value,\n linear: resolvedLinear.value,\n })\n}\n\n/**\n * Пройденный шаг печатает галку вместо номера, шаг с ошибкой — восклицание.\n * Оба декоративны: смысл несёт скрытая подпись состояния рядом с меткой.\n */\nfunction markerText(index: number): string {\n return statusAt(index) === 'error' ? '!' : String(index + 1)\n}\n\nfunction stateText(status: GrStepStatus): string {\n if (status === 'complete')\n return t('gr.steps.completed', 'completed')\n if (status === 'error')\n return t('gr.steps.error', 'has errors')\n return ''\n}\n\nconst statusText = computed(() => t('gr.steps.status', 'Step {step} of {count}', {\n step: currentIndex.value + 1,\n count: props.steps.length,\n}))\n\n/** Полоса компактного вида: доля пройденного, а не номер шага. */\nconst progressValue = computed(() => {\n if (props.steps.length <= 1)\n return 100\n return Math.round((currentIndex.value / (props.steps.length - 1)) * 100)\n})\n\nfunction tagFor(index: number): 'button' | 'span' {\n return isEnterable(index) ? 'button' : 'span'\n}\n\nfunction triggerClass(index: number): string {\n const base = `${stepsTriggerClass} ${stepsTriggerClassByOrientation[resolvedOrientation.value]}`\n return isEnterable(index) ? `${base} ${stepsTriggerEnabledClass}` : base\n}\n\n/** Место под соединитель нужно только тем пунктам, за которыми ещё есть шаг. */\nfunction itemClass(index: number): string {\n const base = stepsItemClass[resolvedOrientation.value]\n if (index >= props.steps.length - 1)\n return base\n return `${base} ${stepsItemConnectedClass[resolvedOrientation.value]}`.trim()\n}\n\nfunction connectorClass(index: number): string {\n const done = statusAt(index) === 'complete'\n return `${stepsConnectorClass[resolvedOrientation.value]} ${done ? stepsConnectorDoneClass : stepsConnectorPendingClass}`\n}\n\nfunction iconOf(step: GrStep): string | Component | undefined {\n return step.icon\n}\n\nasync function goToIndex(index: number): Promise<boolean> {\n const target = props.steps[index]\n if (!target || target.value === props.modelValue)\n return false\n\n if (props.beforeLeave) {\n const allowed = await props.beforeLeave(props.modelValue, target.value)\n if (!allowed)\n return false\n }\n\n emit('update:modelValue', target.value)\n emit('change', target.value)\n\n return true\n}\n\nasync function onStepClick(index: number): Promise<void> {\n if (!isEnterable(index))\n return\n await goToIndex(index)\n}\n\nasync function next(): Promise<boolean> {\n const index = nextEnterableIndex(props.steps, currentIndex.value)\n return index === -1 ? false : goToIndex(index)\n}\n\nasync function back(): Promise<boolean> {\n const index = previousEnterableIndex(props.steps, currentIndex.value)\n return index === -1 ? false : goToIndex(index)\n}\n\nasync function goTo(value: string): Promise<boolean> {\n return goToIndex(stepIndexOf(props.steps, value))\n}\n\ndefineExpose({\n next,\n back,\n goTo,\n isFirst: computed(() => previousEnterableIndex(props.steps, currentIndex.value) === -1),\n isLast: computed(() => nextEnterableIndex(props.steps, currentIndex.value) === -1),\n})\n</script>\n\n<template>\n <nav\n data-gr-steps\n :data-orientation=\"resolvedOrientation\"\n :class=\"[stepsRootClass, stepsSizeClassBySize[resolvedSize]]\"\n :aria-label=\"resolvedAriaLabel\"\n >\n <div v-if=\"resolvedVariant === 'compact'\" data-gr-steps-compact>\n <div :class=\"stepsCompactRowClass\">\n <span :class=\"stepsCompactLabelClass\">{{ currentStep?.label }}</span>\n <span :class=\"stepsCompactCounterClass\">{{ currentIndex + 1 }} / {{ steps.length }}</span>\n </div>\n <!--\n Полоса декоративна: то же самое уже сказано текстом рядом и скрытым\n живым регионом ниже, а второй `progressbar` в дереве заставил бы\n диктора читать прогресс дважды.\n -->\n <GrProgressBar\n :value=\"progressValue\"\n :size=\"resolvedSize === 'lg' ? 'md' : 'sm'\"\n :class=\"stepsCompactBarClass\"\n aria-hidden=\"true\"\n />\n </div>\n\n <ol v-else data-gr-steps-list :class=\"stepsListClass[resolvedOrientation]\">\n <li\n v-for=\"(step, index) in steps\"\n :key=\"step.value\"\n data-gr-step\n :data-value=\"step.value\"\n :data-status=\"statusAt(index)\"\n :class=\"itemClass(index)\"\n >\n <component\n :is=\"tagFor(index)\"\n data-gr-step-trigger\n :type=\"tagFor(index) === 'button' ? 'button' : undefined\"\n :class=\"triggerClass(index)\"\n :aria-current=\"statusAt(index) === 'current' ? 'step' : undefined\"\n :aria-disabled=\"step.disabled ? 'true' : undefined\"\n @click=\"tagFor(index) === 'button' ? onStepClick(index) : undefined\"\n >\n <slot\n name=\"step\"\n :step=\"step\"\n :index=\"index\"\n :status=\"statusAt(index)\"\n :enterable=\"isEnterable(index)\"\n >\n <span data-gr-step-marker :class=\"grStepsMarkerClass(statusAt(index))\" aria-hidden=\"true\">\n <component :is=\"iconOf(step)\" v-if=\"step.icon\" class=\"h-[60%] w-[60%]\" />\n <IconCheck v-else-if=\"statusAt(index) === 'complete'\" class=\"h-[60%] w-[60%]\" />\n <template v-else>{{ markerText(index) }}</template>\n </span>\n\n <span class=\"block min-w-0\">\n <span data-gr-step-label :class=\"grStepsLabelClass(statusAt(index))\">{{ step.label }}</span>\n <!--\n Состояние шага словом: пройденный и шаг с ошибкой иначе звучат\n одинаково — цвет маркера диктору недоступен.\n -->\n <span v-if=\"stateText(statusAt(index))\" class=\"sr-only\"> — {{ stateText(statusAt(index)) }}</span>\n <span v-if=\"step.description\" data-gr-step-description :class=\"stepsDescriptionClass\">\n {{ step.description }}\n </span>\n </span>\n </slot>\n </component>\n\n <span\n v-if=\"index < steps.length - 1\"\n data-gr-step-connector\n :class=\"connectorClass(index)\"\n aria-hidden=\"true\"\n />\n </li>\n </ol>\n\n <!--\n «Шаг 2 из 5» — состояние виджета, а не событие, поэтому живёт своим\n регионом в разметке, а не уходит в общий объявитель (`docs/announcer.md`).\n Образец — компактный индикатор `GrPagination`.\n -->\n <div data-gr-steps-status role=\"status\" class=\"sr-only\">\n{{ statusText }}\n</div>\n </nav>\n</template>\n","<script setup lang=\"ts\">\nimport type { Component } from 'vue'\nimport { computed } from 'vue'\n\nimport IconCheck from '~icons/lucide/check'\n\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\nimport GrProgressBar from '../GrProgressBar/GrProgressBar.vue'\n\nimport type { GrStepsOrientation, GrStepsSize, GrStepsVariant } from './grStepsStyles'\nimport {\n grStepsLabelClass,\n grStepsMarkerClass,\n stepsCompactBarClass,\n stepsCompactCounterClass,\n stepsCompactLabelClass,\n stepsCompactRowClass,\n stepsConnectorClass,\n stepsConnectorDoneClass,\n stepsConnectorPendingClass,\n stepsDescriptionClass,\n stepsItemClass,\n stepsItemConnectedClass,\n stepsListClass,\n stepsRootClass,\n stepsSizeClassBySize,\n stepsTriggerClass,\n stepsTriggerClassByOrientation,\n stepsTriggerEnabledClass,\n} from './grStepsStyles'\nimport type { GrStep, GrStepStatus } from './stepsModel'\nimport {\n canEnterStep,\n nextEnterableIndex,\n previousEnterableIndex,\n stepIndexOf,\n stepStatusAt,\n} from './stepsModel'\n\n/**\n * Индикатор шагов многошагового мастера.\n *\n * По семантике это **навигация**, а не составной виджет выбора: `<nav>` со\n * списком, где пройденный шаг — кнопка со своей остановкой `Tab`, текущий —\n * `aria-current=\"step\"`, будущий — вне таб-порядка. Той же формы держатся\n * `GrBreadcrumbs` и `GrBottomNav`; `role=\"tab\"` здесь неприменим — tablist без\n * `tabpanel` это сломанный паттерн, а панель шага никакой роли не несёт.\n *\n * Контент шага компонент не рисует: его ставит потребитель обычным `v-if`.\n */\nexport interface GrStepsProps {\n modelValue: string\n steps: GrStep[]\n orientation?: GrStepsOrientation\n size?: GrStepsSize\n /** Компактный вид для узкой колонки: подпись текущего шага и полоса прогресса. */\n variant?: GrStepsVariant\n /**\n * Вперёд — не дальше первого непройденного. Назад свободно всегда: вернуться\n * и поправить заполненное — обычный сценарий, а не обход правила.\n */\n linear?: boolean\n /** Переход кликом по шагу. Выключен — лента становится только индикатором. */\n clickable?: boolean\n ariaLabel?: string\n /**\n * Гейт перехода. Вернул `false` — перехода нет.\n *\n * Сюда потребитель кладёт валидацию шага: `GrSteps` про `GrForm` ничего не\n * знает и знать не должен, а форма уже отдаёт `validate()`/`validateField()`.\n */\n beforeLeave?: (from: string, to: string) => boolean | Promise<boolean>\n}\n\nexport interface GrStepsEmits {\n (e: 'update:modelValue', value: string): void\n (e: 'change', value: string): void\n}\n\nconst props = withDefaults(defineProps<GrStepsProps>(), {\n // Дефолты живут в резолверах ниже: Vue подставил бы свой раньше, чем\n // компонент заглянет в `GrConfigProvider`.\n orientation: undefined,\n size: undefined,\n variant: undefined,\n linear: undefined,\n clickable: undefined,\n ariaLabel: undefined,\n beforeLeave: undefined,\n})\n\nconst emit = defineEmits<GrStepsEmits>()\n\ndefineSlots<{\n /**\n * Своя разметка пункта. Корневой тег, `aria-current` и клик остаются за\n * компонентом — иначе доступность шага пришлось бы собирать заново.\n */\n step?: (props: { step: GrStep, index: number, status: GrStepStatus, enterable: boolean }) => unknown\n}>()\n\nconst { t } = useGranularityTranslations()\n\nconst resolvedSize = useGrComponentSize<GrStepsSize>(() => props.size, { component: 'GrSteps' })\nconst resolvedOrientation = useGrComponentProp('GrSteps', 'orientation', () => props.orientation, 'horizontal')\nconst resolvedVariant = useGrComponentProp('GrSteps', 'variant', () => props.variant, 'steps')\nconst resolvedLinear = useGrComponentProp('GrSteps', 'linear', () => props.linear, true)\nconst resolvedClickable = useGrComponentProp('GrSteps', 'clickable', () => props.clickable, true)\n\nconst currentIndex = computed(() => stepIndexOf(props.steps, props.modelValue))\nconst currentStep = computed(() => props.steps[currentIndex.value])\n\nconst resolvedAriaLabel = computed(() => props.ariaLabel ?? t('gr.steps.label', 'Progress'))\n\nfunction statusAt(index: number): GrStepStatus {\n return stepStatusAt(props.steps, index, currentIndex.value)\n}\n\nfunction isEnterable(index: number): boolean {\n if (!resolvedClickable.value)\n return false\n return canEnterStep({\n steps: props.steps,\n index,\n currentIndex: currentIndex.value,\n linear: resolvedLinear.value,\n })\n}\n\n/**\n * Пройденный шаг печатает галку вместо номера, шаг с ошибкой — восклицание.\n * Оба декоративны: смысл несёт скрытая подпись состояния рядом с меткой.\n */\nfunction markerText(index: number): string {\n return statusAt(index) === 'error' ? '!' : String(index + 1)\n}\n\nfunction stateText(status: GrStepStatus): string {\n if (status === 'complete')\n return t('gr.steps.completed', 'completed')\n if (status === 'error')\n return t('gr.steps.error', 'has errors')\n return ''\n}\n\nconst statusText = computed(() => t('gr.steps.status', 'Step {step} of {count}', {\n step: currentIndex.value + 1,\n count: props.steps.length,\n}))\n\n/** Полоса компактного вида: доля пройденного, а не номер шага. */\nconst progressValue = computed(() => {\n if (props.steps.length <= 1)\n return 100\n return Math.round((currentIndex.value / (props.steps.length - 1)) * 100)\n})\n\nfunction tagFor(index: number): 'button' | 'span' {\n return isEnterable(index) ? 'button' : 'span'\n}\n\nfunction triggerClass(index: number): string {\n const base = `${stepsTriggerClass} ${stepsTriggerClassByOrientation[resolvedOrientation.value]}`\n return isEnterable(index) ? `${base} ${stepsTriggerEnabledClass}` : base\n}\n\n/** Место под соединитель нужно только тем пунктам, за которыми ещё есть шаг. */\nfunction itemClass(index: number): string {\n const base = stepsItemClass[resolvedOrientation.value]\n if (index >= props.steps.length - 1)\n return base\n return `${base} ${stepsItemConnectedClass[resolvedOrientation.value]}`.trim()\n}\n\nfunction connectorClass(index: number): string {\n const done = statusAt(index) === 'complete'\n return `${stepsConnectorClass[resolvedOrientation.value]} ${done ? stepsConnectorDoneClass : stepsConnectorPendingClass}`\n}\n\nfunction iconOf(step: GrStep): string | Component | undefined {\n return step.icon\n}\n\nasync function goToIndex(index: number): Promise<boolean> {\n const target = props.steps[index]\n if (!target || target.value === props.modelValue)\n return false\n\n if (props.beforeLeave) {\n const allowed = await props.beforeLeave(props.modelValue, target.value)\n if (!allowed)\n return false\n }\n\n emit('update:modelValue', target.value)\n emit('change', target.value)\n\n return true\n}\n\nasync function onStepClick(index: number): Promise<void> {\n if (!isEnterable(index))\n return\n await goToIndex(index)\n}\n\nasync function next(): Promise<boolean> {\n const index = nextEnterableIndex(props.steps, currentIndex.value)\n return index === -1 ? false : goToIndex(index)\n}\n\nasync function back(): Promise<boolean> {\n const index = previousEnterableIndex(props.steps, currentIndex.value)\n return index === -1 ? false : goToIndex(index)\n}\n\nasync function goTo(value: string): Promise<boolean> {\n return goToIndex(stepIndexOf(props.steps, value))\n}\n\ndefineExpose({\n next,\n back,\n goTo,\n isFirst: computed(() => previousEnterableIndex(props.steps, currentIndex.value) === -1),\n isLast: computed(() => nextEnterableIndex(props.steps, currentIndex.value) === -1),\n})\n</script>\n\n<template>\n <nav\n data-gr-steps\n :data-orientation=\"resolvedOrientation\"\n :class=\"[stepsRootClass, stepsSizeClassBySize[resolvedSize]]\"\n :aria-label=\"resolvedAriaLabel\"\n >\n <div v-if=\"resolvedVariant === 'compact'\" data-gr-steps-compact>\n <div :class=\"stepsCompactRowClass\">\n <span :class=\"stepsCompactLabelClass\">{{ currentStep?.label }}</span>\n <span :class=\"stepsCompactCounterClass\">{{ currentIndex + 1 }} / {{ steps.length }}</span>\n </div>\n <!--\n Полоса декоративна: то же самое уже сказано текстом рядом и скрытым\n живым регионом ниже, а второй `progressbar` в дереве заставил бы\n диктора читать прогресс дважды.\n -->\n <GrProgressBar\n :value=\"progressValue\"\n :size=\"resolvedSize === 'lg' ? 'md' : 'sm'\"\n :class=\"stepsCompactBarClass\"\n aria-hidden=\"true\"\n />\n </div>\n\n <ol v-else data-gr-steps-list :class=\"stepsListClass[resolvedOrientation]\">\n <li\n v-for=\"(step, index) in steps\"\n :key=\"step.value\"\n data-gr-step\n :data-value=\"step.value\"\n :data-status=\"statusAt(index)\"\n :class=\"itemClass(index)\"\n >\n <component\n :is=\"tagFor(index)\"\n data-gr-step-trigger\n :type=\"tagFor(index) === 'button' ? 'button' : undefined\"\n :class=\"triggerClass(index)\"\n :aria-current=\"statusAt(index) === 'current' ? 'step' : undefined\"\n :aria-disabled=\"step.disabled ? 'true' : undefined\"\n @click=\"tagFor(index) === 'button' ? onStepClick(index) : undefined\"\n >\n <slot\n name=\"step\"\n :step=\"step\"\n :index=\"index\"\n :status=\"statusAt(index)\"\n :enterable=\"isEnterable(index)\"\n >\n <span data-gr-step-marker :class=\"grStepsMarkerClass(statusAt(index))\" aria-hidden=\"true\">\n <component :is=\"iconOf(step)\" v-if=\"step.icon\" class=\"h-[60%] w-[60%]\" />\n <IconCheck v-else-if=\"statusAt(index) === 'complete'\" class=\"h-[60%] w-[60%]\" />\n <template v-else>{{ markerText(index) }}</template>\n </span>\n\n <span class=\"block min-w-0\">\n <span data-gr-step-label :class=\"grStepsLabelClass(statusAt(index))\">{{ step.label }}</span>\n <!--\n Состояние шага словом: пройденный и шаг с ошибкой иначе звучат\n одинаково — цвет маркера диктору недоступен.\n -->\n <span v-if=\"stateText(statusAt(index))\" class=\"sr-only\"> — {{ stateText(statusAt(index)) }}</span>\n <span v-if=\"step.description\" data-gr-step-description :class=\"stepsDescriptionClass\">\n {{ step.description }}\n </span>\n </span>\n </slot>\n </component>\n\n <span\n v-if=\"index < steps.length - 1\"\n data-gr-step-connector\n :class=\"connectorClass(index)\"\n aria-hidden=\"true\"\n />\n </li>\n </ol>\n\n <!--\n «Шаг 2 из 5» — состояние виджета, а не событие, поэтому живёт своим\n регионом в разметке, а не уходит в общий объявитель (`docs/announcer.md`).\n Образец — компактный индикатор `GrPagination`.\n -->\n <div data-gr-steps-status role=\"status\" class=\"sr-only\">\n{{ statusText }}\n</div>\n </nav>\n</template>\n"],"mappings":"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"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { n as grStepsSafelist, t as grStepsConfig } from "../../chunks/config-CLjjjPZj.js";
|
|
2
|
-
import { t as GrSteps_default } from "./chunks/GrSteps-
|
|
2
|
+
import { t as GrSteps_default } from "./chunks/GrSteps-DXj7m-WO.js";
|
|
3
3
|
export { GrSteps_default as GrSteps, GrSteps_default as default, grStepsConfig, grStepsSafelist };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as useGrComponentSize } from "../../../chunks/context-
|
|
1
|
+
import { a as useGrComponentSize } from "../../../chunks/context-CPMRfwVr.js";
|
|
2
2
|
import { n as useGranularityTranslations } from "../../../chunks/granularityI18n-BC5zsj_d.js";
|
|
3
3
|
import { t as loader_circle_default } from "../../../chunks/loader-circle-CfVLz7lb.js";
|
|
4
4
|
import { n as useGrFormFieldContext } from "../../../chunks/context-DdNIxa1w.js";
|
package/dist/components/GrSwitch/chunks/{GrSwitch-7Oe5QfQZ.js.map → GrSwitch-CriHWT2A.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GrSwitch-7Oe5QfQZ.js","names":["$attrs","$slots"],"sources":["../../../../src/components/GrSwitch/GrSwitch.vue","../../../../src/components/GrSwitch/GrSwitch.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport IconLoader from '~icons/lucide/loader-circle'\n\nimport { useGrComponentSize } from '../GrConfigProvider/context'\nimport { useGrFormFieldContext } from '../GrFormField/context'\nimport { useGrFormControl } from '../../composables/useGrFormControl'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\n\nimport {\n grSwitchLabelClass,\n grSwitchRootClass,\n grSwitchSpinnerClass,\n grSwitchThumbClass,\n grSwitchTrackClass,\n type GrSwitchLabelPosition,\n type GrSwitchSize,\n} from './grSwitchStyles'\n\nexport type { GrSwitchLabelPosition, GrSwitchSize } from './grSwitchStyles'\n\n/**\n * Пропсы публичного GR-примитива «Switch».\n */\nexport interface GrSwitchProps {\n /**\n * Значение переключателя. Необязательное: без `v-model` контрол рисуется\n * выключенным — так же, как `GrCheckbox`. Обязательный проп заставлял бы\n * заводить `ref` даже там, где переключатель presentational.\n */\n modelValue?: boolean\n disabled?: boolean\n /** Только для чтения: состояние видно, но не переключается. */\n readonly?: boolean\n /** Визуальное и ARIA-состояние ошибки. */\n invalid?: boolean\n /** Обязательное поле (`aria-required`). */\n required?: boolean\n ariaLabel?: string\n size?: GrSwitchSize\n /** Сторона подписи относительно дорожки. */\n labelPosition?: GrSwitchLabelPosition\n /** Идёт сохранение: бегунок показывает спиннер, переключение заблокировано. */\n loading?: boolean\n /** i18n: что именно грузится. `aria-busy` сам по себе часть AT не объявляет. */\n loadingText?: string\n /** Имя поля для нативной отправки формы. Без него скрытое поле не рендерится. */\n name?: string\n /** Значение, уходящее в форму во включённом состоянии. */\n value?: string\n /** `id` формы, если переключатель лежит вне неё. */\n form?: string\n /** Кастомный цвет фона в активном состоянии. Если не задан — `var(--gr-primary)`. */\n activeBackgroundColor?: string\n /** Кастомный цвет фона в неактивном состоянии. Если не задан — `var(--gr-muted)`. */\n inactiveBackgroundColor?: string\n}\n\nexport interface GrSwitchEmits {\n (e: 'update:modelValue', value: boolean): void\n (e: 'change', value: boolean): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n}\n\nconst getCustomColor = (value?: string) => value?.trim() || undefined\n\n// Скрытое поле — сосед кнопки, а не её потомок: интерактивный контент внутри\n// `<button>` невалиден. Из-за этого корень компонента — фрагмент, и атрибуты\n// потребителя надо донести до кнопки руками.\ndefineOptions({ inheritAttrs: false })\n\nconst props = withDefaults(\n defineProps<GrSwitchProps>(),\n {\n modelValue: false,\n disabled: false,\n readonly: false,\n invalid: false,\n required: false,\n ariaLabel: undefined,\n size: undefined,\n labelPosition: 'end',\n loading: false,\n loadingText: undefined,\n name: undefined,\n value: 'on',\n form: undefined,\n activeBackgroundColor: undefined,\n inactiveBackgroundColor: undefined,\n },\n)\n\n// Контекст `GrFormField`: id для `<label for>`, описание ошибкой, невалидность.\n// `<button>` — labelable-элемент, поэтому клик по подписи фокусирует переключатель.\nconst field = useGrFormFieldContext()\nconst fieldId = 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\nconst { t } = useGranularityTranslations()\n\nconst rootEl = ref<HTMLButtonElement | null>(null)\n\nfunction focus(): void {\n rootEl.value?.focus()\n}\n\nfunction blur(): void {\n rootEl.value?.blur()\n}\n\ndefineExpose({ focus, blur })\n\n// Эффективный размер: локальный проп → `GrConfigProvider` → дефолт компонента.\nconst resolvedSize = useGrComponentSize(() => props.size, {\n component: 'GrSwitch',\n supported: ['xs', 'sm', 'md', 'lg'],\n})\n\nconst emit = defineEmits<GrSwitchEmits>()\ndefineSlots<{\n /** Подпись переключателя. */\n default?: () => any\n}>()\n\nconst rootClass = computed(() => grSwitchRootClass(props.labelPosition))\n\nconst trackClass = computed(() => grSwitchTrackClass(resolvedSize.value))\n\nconst trackStyle = computed(() => {\n const isChecked = props.modelValue\n const defaultBackgroundColor = isChecked ? 'var(--gr-primary)' : 'var(--gr-muted)'\n const customBackgroundColor = getCustomColor(\n isChecked ? props.activeBackgroundColor : props.inactiveBackgroundColor,\n )\n // Недоступность гасится токеном и перебивает кастомный цвет: иначе выключенный\n // переключатель остался бы таким же ярким, как рабочий.\n const backgroundColor = isDisabled.value\n ? 'var(--gr-disabled-bg)'\n : customBackgroundColor ?? defaultBackgroundColor\n\n return {\n '--gr-switch-track-bg': backgroundColor,\n '--gr-switch-track-brd': isDisabled.value\n ? 'var(--gr-disabled-brd)'\n : customBackgroundColor\n ? backgroundColor\n : isChecked\n ? 'var(--gr-primary)'\n : 'var(--gr-brd)',\n 'backgroundColor': 'var(--gr-switch-track-bg)',\n }\n})\n\nconst thumbClass = computed(() => grSwitchThumbClass({ size: resolvedSize.value, checked: props.modelValue }))\n\nconst spinnerClass = computed(() => grSwitchSpinnerClass(resolvedSize.value))\n\nconst labelClass = computed(() => grSwitchLabelClass(resolvedSize.value, isDisabled.value))\n\nconst resolvedLoadingText = computed(() => props.loadingText ?? t('gr.switch.loading', 'Saving…'))\n\n// Выключенный переключатель не отправляется вовсе — так устроен чекбокс в HTML,\n// и сервер отличает «выкл» по отсутствию ключа.\nconst submitsValue = computed(\n () => props.modelValue && !isDisabled.value && Boolean(props.name),\n)\n\nfunction toggle(): void {\n if (isDisabled.value || isReadonly.value || props.loading) {\n return\n }\n\n const next = !props.modelValue\n emit('update:modelValue', next)\n emit('change', next)\n}\n</script>\n\n<template>\n <input\n v-if=\"submitsValue\"\n type=\"hidden\"\n :name=\"name\"\n :value=\"value\"\n :form=\"form\"\n >\n\n <button\n :id=\"fieldId\"\n ref=\"rootEl\"\n v-bind=\"$attrs\"\n type=\"button\"\n role=\"switch\"\n data-gr-switch\n :aria-checked=\"modelValue ? 'true' : 'false'\"\n :aria-label=\"ariaLabel\"\n :aria-describedby=\"describedBy\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n :aria-required=\"isRequired ? 'true' : undefined\"\n :aria-readonly=\"isReadonly ? 'true' : undefined\"\n :aria-busy=\"loading ? 'true' : undefined\"\n :disabled=\"isDisabled\"\n :class=\"rootClass\"\n @click=\"toggle\"\n @focus=\"emit('focus', $event)\"\n @blur=\"emit('blur', $event)\"\n >\n <span\n data-testid=\"gr-switch-track\"\n data-gr-switch-track\n :class=\"trackClass\"\n :style=\"trackStyle\"\n >\n <span\n data-testid=\"gr-switch-thumb\"\n data-gr-switch-thumb\n :class=\"thumbClass\"\n aria-hidden=\"true\"\n >\n <IconLoader v-if=\"loading\" data-gr-switch-spinner :class=\"spinnerClass\" />\n </span>\n </span>\n <span\n v-if=\"$slots.default\"\n data-gr-switch-label\n :class=\"labelClass\"\n >\n <slot />\n </span>\n <span v-if=\"loading\" data-gr-switch-loading-text class=\"sr-only\">{{ resolvedLoadingText }}</span>\n </button>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport IconLoader from '~icons/lucide/loader-circle'\n\nimport { useGrComponentSize } from '../GrConfigProvider/context'\nimport { useGrFormFieldContext } from '../GrFormField/context'\nimport { useGrFormControl } from '../../composables/useGrFormControl'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\n\nimport {\n grSwitchLabelClass,\n grSwitchRootClass,\n grSwitchSpinnerClass,\n grSwitchThumbClass,\n grSwitchTrackClass,\n type GrSwitchLabelPosition,\n type GrSwitchSize,\n} from './grSwitchStyles'\n\nexport type { GrSwitchLabelPosition, GrSwitchSize } from './grSwitchStyles'\n\n/**\n * Пропсы публичного GR-примитива «Switch».\n */\nexport interface GrSwitchProps {\n /**\n * Значение переключателя. Необязательное: без `v-model` контрол рисуется\n * выключенным — так же, как `GrCheckbox`. Обязательный проп заставлял бы\n * заводить `ref` даже там, где переключатель presentational.\n */\n modelValue?: boolean\n disabled?: boolean\n /** Только для чтения: состояние видно, но не переключается. */\n readonly?: boolean\n /** Визуальное и ARIA-состояние ошибки. */\n invalid?: boolean\n /** Обязательное поле (`aria-required`). */\n required?: boolean\n ariaLabel?: string\n size?: GrSwitchSize\n /** Сторона подписи относительно дорожки. */\n labelPosition?: GrSwitchLabelPosition\n /** Идёт сохранение: бегунок показывает спиннер, переключение заблокировано. */\n loading?: boolean\n /** i18n: что именно грузится. `aria-busy` сам по себе часть AT не объявляет. */\n loadingText?: string\n /** Имя поля для нативной отправки формы. Без него скрытое поле не рендерится. */\n name?: string\n /** Значение, уходящее в форму во включённом состоянии. */\n value?: string\n /** `id` формы, если переключатель лежит вне неё. */\n form?: string\n /** Кастомный цвет фона в активном состоянии. Если не задан — `var(--gr-primary)`. */\n activeBackgroundColor?: string\n /** Кастомный цвет фона в неактивном состоянии. Если не задан — `var(--gr-muted)`. */\n inactiveBackgroundColor?: string\n}\n\nexport interface GrSwitchEmits {\n (e: 'update:modelValue', value: boolean): void\n (e: 'change', value: boolean): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n}\n\nconst getCustomColor = (value?: string) => value?.trim() || undefined\n\n// Скрытое поле — сосед кнопки, а не её потомок: интерактивный контент внутри\n// `<button>` невалиден. Из-за этого корень компонента — фрагмент, и атрибуты\n// потребителя надо донести до кнопки руками.\ndefineOptions({ inheritAttrs: false })\n\nconst props = withDefaults(\n defineProps<GrSwitchProps>(),\n {\n modelValue: false,\n disabled: false,\n readonly: false,\n invalid: false,\n required: false,\n ariaLabel: undefined,\n size: undefined,\n labelPosition: 'end',\n loading: false,\n loadingText: undefined,\n name: undefined,\n value: 'on',\n form: undefined,\n activeBackgroundColor: undefined,\n inactiveBackgroundColor: undefined,\n },\n)\n\n// Контекст `GrFormField`: id для `<label for>`, описание ошибкой, невалидность.\n// `<button>` — labelable-элемент, поэтому клик по подписи фокусирует переключатель.\nconst field = useGrFormFieldContext()\nconst fieldId = 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\nconst { t } = useGranularityTranslations()\n\nconst rootEl = ref<HTMLButtonElement | null>(null)\n\nfunction focus(): void {\n rootEl.value?.focus()\n}\n\nfunction blur(): void {\n rootEl.value?.blur()\n}\n\ndefineExpose({ focus, blur })\n\n// Эффективный размер: локальный проп → `GrConfigProvider` → дефолт компонента.\nconst resolvedSize = useGrComponentSize(() => props.size, {\n component: 'GrSwitch',\n supported: ['xs', 'sm', 'md', 'lg'],\n})\n\nconst emit = defineEmits<GrSwitchEmits>()\ndefineSlots<{\n /** Подпись переключателя. */\n default?: () => any\n}>()\n\nconst rootClass = computed(() => grSwitchRootClass(props.labelPosition))\n\nconst trackClass = computed(() => grSwitchTrackClass(resolvedSize.value))\n\nconst trackStyle = computed(() => {\n const isChecked = props.modelValue\n const defaultBackgroundColor = isChecked ? 'var(--gr-primary)' : 'var(--gr-muted)'\n const customBackgroundColor = getCustomColor(\n isChecked ? props.activeBackgroundColor : props.inactiveBackgroundColor,\n )\n // Недоступность гасится токеном и перебивает кастомный цвет: иначе выключенный\n // переключатель остался бы таким же ярким, как рабочий.\n const backgroundColor = isDisabled.value\n ? 'var(--gr-disabled-bg)'\n : customBackgroundColor ?? defaultBackgroundColor\n\n return {\n '--gr-switch-track-bg': backgroundColor,\n '--gr-switch-track-brd': isDisabled.value\n ? 'var(--gr-disabled-brd)'\n : customBackgroundColor\n ? backgroundColor\n : isChecked\n ? 'var(--gr-primary)'\n : 'var(--gr-brd)',\n 'backgroundColor': 'var(--gr-switch-track-bg)',\n }\n})\n\nconst thumbClass = computed(() => grSwitchThumbClass({ size: resolvedSize.value, checked: props.modelValue }))\n\nconst spinnerClass = computed(() => grSwitchSpinnerClass(resolvedSize.value))\n\nconst labelClass = computed(() => grSwitchLabelClass(resolvedSize.value, isDisabled.value))\n\nconst resolvedLoadingText = computed(() => props.loadingText ?? t('gr.switch.loading', 'Saving…'))\n\n// Выключенный переключатель не отправляется вовсе — так устроен чекбокс в HTML,\n// и сервер отличает «выкл» по отсутствию ключа.\nconst submitsValue = computed(\n () => props.modelValue && !isDisabled.value && Boolean(props.name),\n)\n\nfunction toggle(): void {\n if (isDisabled.value || isReadonly.value || props.loading) {\n return\n }\n\n const next = !props.modelValue\n emit('update:modelValue', next)\n emit('change', next)\n}\n</script>\n\n<template>\n <input\n v-if=\"submitsValue\"\n type=\"hidden\"\n :name=\"name\"\n :value=\"value\"\n :form=\"form\"\n >\n\n <button\n :id=\"fieldId\"\n ref=\"rootEl\"\n v-bind=\"$attrs\"\n type=\"button\"\n role=\"switch\"\n data-gr-switch\n :aria-checked=\"modelValue ? 'true' : 'false'\"\n :aria-label=\"ariaLabel\"\n :aria-describedby=\"describedBy\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n :aria-required=\"isRequired ? 'true' : undefined\"\n :aria-readonly=\"isReadonly ? 'true' : undefined\"\n :aria-busy=\"loading ? 'true' : undefined\"\n :disabled=\"isDisabled\"\n :class=\"rootClass\"\n @click=\"toggle\"\n @focus=\"emit('focus', $event)\"\n @blur=\"emit('blur', $event)\"\n >\n <span\n data-testid=\"gr-switch-track\"\n data-gr-switch-track\n :class=\"trackClass\"\n :style=\"trackStyle\"\n >\n <span\n data-testid=\"gr-switch-thumb\"\n data-gr-switch-thumb\n :class=\"thumbClass\"\n aria-hidden=\"true\"\n >\n <IconLoader v-if=\"loading\" data-gr-switch-spinner :class=\"spinnerClass\" />\n </span>\n </span>\n <span\n v-if=\"$slots.default\"\n data-gr-switch-label\n :class=\"labelClass\"\n >\n <slot />\n </span>\n <span v-if=\"loading\" data-gr-switch-loading-text class=\"sr-only\">{{ resolvedLoadingText }}</span>\n </button>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiEA,MAAM,kBAAkB,UAAmB,OAAO,KAAK,KAAK,KAAA;EAO5D,MAAM,QAAQ;EAuBd,MAAM,QAAQ,sBAAsB;EACpC,MAAM,UAAU,eAAe,OAAO,GAAG,KAAK;EAC9C,MAAM,cAAc,eAAe,OAAO,cAAc,KAAK;EAC7D,MAAM,EACJ,UAAU,YACV,SAAS,WACT,UAAU,YACV,UAAU,eACR,uBAAuB,KAAK;EAEhC,MAAM,EAAE,MAAM,2BAA2B;EAEzC,MAAM,SAAS,IAA8B,IAAI;EAEjD,SAAS,QAAc;GACrB,OAAO,OAAO,MAAM;EACtB;EAEA,SAAS,OAAa;GACpB,OAAO,OAAO,KAAK;EACrB;EAEA,SAAa;GAAE;GAAO;EAAK,CAAC;EAG5B,MAAM,eAAe,yBAAyB,MAAM,MAAM;GACxD,WAAW;GACX,WAAW;IAAC;IAAM;IAAM;IAAM;GAAI;EACpC,CAAC;EAED,MAAM,OAAO;EAMb,MAAM,YAAY,eAAe,kBAAkB,MAAM,aAAa,CAAC;EAEvE,MAAM,aAAa,eAAe,mBAAmB,aAAa,KAAK,CAAC;EAExE,MAAM,aAAa,eAAe;GAChC,MAAM,YAAY,MAAM;GACxB,MAAM,yBAAyB,YAAY,sBAAsB;GACjE,MAAM,wBAAwB,eAC5B,YAAY,MAAM,wBAAwB,MAAM,uBAClD;GAGA,MAAM,kBAAkB,WAAW,QAC/B,0BACA,yBAAyB;GAE7B,OAAO;IACL,wBAAwB;IACxB,yBAAyB,WAAW,QAChC,2BACA,wBACE,kBACA,YACE,sBACA;IACR,mBAAmB;GACrB;EACF,CAAC;EAED,MAAM,aAAa,eAAe,mBAAmB;GAAE,MAAM,aAAa;GAAO,SAAS,MAAM;EAAW,CAAC,CAAC;EAE7G,MAAM,eAAe,eAAe,qBAAqB,aAAa,KAAK,CAAC;EAE5E,MAAM,aAAa,eAAe,mBAAmB,aAAa,OAAO,WAAW,KAAK,CAAC;EAE1F,MAAM,sBAAsB,eAAe,MAAM,eAAe,EAAE,qBAAqB,SAAS,CAAC;EAIjG,MAAM,eAAe,eACb,MAAM,cAAc,CAAC,WAAW,SAAS,QAAQ,MAAM,IAAI,CACnE;EAEA,SAAS,SAAe;GACtB,IAAI,WAAW,SAAS,WAAW,SAAS,MAAM,SAChD;GAGF,MAAM,OAAO,CAAC,MAAM;GACpB,KAAK,qBAAqB,IAAI;GAC9B,KAAK,UAAU,IAAI;EACrB;;2DAKY,aAAA,SAAA,UAAA,GADV,mBAMC,SAAA;;IAJG,MAAK;IACJ,MAAM,QAAA;IACN,OAAO,QAAA;IACP,MAAM,QAAA;4DAGX,mBA2CS,UA3CT,WA2CS;IA1CJ,IAAI,QAAA;aACD;IAAJ,KAAI;MACIA,KAAAA,QAAM;IACd,MAAK;IACL,MAAK;IACL,kBAAA;IACC,gBAAc,QAAA,aAAU,SAAA;IACxB,cAAY,QAAA;IACZ,oBAAkB,YAAA;IAClB,gBAAc,MAAA,SAAA,IAAS,SAAY,KAAA;IACnC,iBAAe,MAAA,UAAA,IAAU,SAAY,KAAA;IACrC,iBAAe,MAAA,UAAA,IAAU,SAAY,KAAA;IACrC,aAAW,QAAA,UAAO,SAAY,KAAA;IAC9B,UAAU,MAAA,UAAA;IACV,OAAO,UAAA;IACP,SAAO;IACP,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,KAAI,SAAU,MAAM;IAC3B,QAAI,OAAA,OAAA,OAAA,MAAA,WAAE,KAAI,QAAS,MAAM;;IAE5B,mBAcO,QAAA;KAbH,eAAY;KACZ,wBAAA;KACC,OAAK,eAAE,WAAA,KAAU;KACjB,OAAK,eAAE,WAAA,KAAU;QAEpB,mBAOO,QAAA;KANH,eAAY;KACZ,wBAAA;KACC,OAAK,eAAE,WAAA,KAAU;KAClB,eAAY;QAEI,QAAA,WAAA,UAAA,GAAlB,YAA0E,MAAA,qBAAA,GAAA;;KAA/C,0BAAA;KAAwB,OAAK,eAAE,aAAA,KAAY;;IAIhEC,KAAAA,OAAO,WAAA,UAAA,GADjB,mBAMO,QAAA;;KAJH,wBAAA;KACC,OAAK,eAAE,WAAA,KAAU;QAEpB,WAAQ,KAAA,QAAA,SAAA,CAAA,GAAA,CAAA,KAAA,mBAAA,IAAA,IAAA;IAEE,QAAA,WAAA,UAAA,GAAZ,mBAAiG,QAAjG,YAAiG,gBAA7B,oBAAA,KAAmB,GAAA,CAAA,KAAA,mBAAA,IAAA,IAAA"}
|
|
1
|
+
{"version":3,"file":"GrSwitch-CriHWT2A.js","names":["$attrs","$slots"],"sources":["../../../../src/components/GrSwitch/GrSwitch.vue","../../../../src/components/GrSwitch/GrSwitch.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport IconLoader from '~icons/lucide/loader-circle'\n\nimport { useGrComponentSize } from '../GrConfigProvider/context'\nimport { useGrFormFieldContext } from '../GrFormField/context'\nimport { useGrFormControl } from '../../composables/useGrFormControl'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\n\nimport {\n grSwitchLabelClass,\n grSwitchRootClass,\n grSwitchSpinnerClass,\n grSwitchThumbClass,\n grSwitchTrackClass,\n type GrSwitchLabelPosition,\n type GrSwitchSize,\n} from './grSwitchStyles'\n\nexport type { GrSwitchLabelPosition, GrSwitchSize } from './grSwitchStyles'\n\n/**\n * Пропсы публичного GR-примитива «Switch».\n */\nexport interface GrSwitchProps {\n /**\n * Значение переключателя. Необязательное: без `v-model` контрол рисуется\n * выключенным — так же, как `GrCheckbox`. Обязательный проп заставлял бы\n * заводить `ref` даже там, где переключатель presentational.\n */\n modelValue?: boolean\n disabled?: boolean\n /** Только для чтения: состояние видно, но не переключается. */\n readonly?: boolean\n /** Визуальное и ARIA-состояние ошибки. */\n invalid?: boolean\n /** Обязательное поле (`aria-required`). */\n required?: boolean\n ariaLabel?: string\n size?: GrSwitchSize\n /** Сторона подписи относительно дорожки. */\n labelPosition?: GrSwitchLabelPosition\n /** Идёт сохранение: бегунок показывает спиннер, переключение заблокировано. */\n loading?: boolean\n /** i18n: что именно грузится. `aria-busy` сам по себе часть AT не объявляет. */\n loadingText?: string\n /** Имя поля для нативной отправки формы. Без него скрытое поле не рендерится. */\n name?: string\n /** Значение, уходящее в форму во включённом состоянии. */\n value?: string\n /** `id` формы, если переключатель лежит вне неё. */\n form?: string\n /** Кастомный цвет фона в активном состоянии. Если не задан — `var(--gr-primary)`. */\n activeBackgroundColor?: string\n /** Кастомный цвет фона в неактивном состоянии. Если не задан — `var(--gr-muted)`. */\n inactiveBackgroundColor?: string\n}\n\nexport interface GrSwitchEmits {\n (e: 'update:modelValue', value: boolean): void\n (e: 'change', value: boolean): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n}\n\nconst getCustomColor = (value?: string) => value?.trim() || undefined\n\n// Скрытое поле — сосед кнопки, а не её потомок: интерактивный контент внутри\n// `<button>` невалиден. Из-за этого корень компонента — фрагмент, и атрибуты\n// потребителя надо донести до кнопки руками.\ndefineOptions({ inheritAttrs: false })\n\nconst props = withDefaults(\n defineProps<GrSwitchProps>(),\n {\n modelValue: false,\n disabled: false,\n readonly: false,\n invalid: false,\n required: false,\n ariaLabel: undefined,\n size: undefined,\n labelPosition: 'end',\n loading: false,\n loadingText: undefined,\n name: undefined,\n value: 'on',\n form: undefined,\n activeBackgroundColor: undefined,\n inactiveBackgroundColor: undefined,\n },\n)\n\n// Контекст `GrFormField`: id для `<label for>`, описание ошибкой, невалидность.\n// `<button>` — labelable-элемент, поэтому клик по подписи фокусирует переключатель.\nconst field = useGrFormFieldContext()\nconst fieldId = 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\nconst { t } = useGranularityTranslations()\n\nconst rootEl = ref<HTMLButtonElement | null>(null)\n\nfunction focus(): void {\n rootEl.value?.focus()\n}\n\nfunction blur(): void {\n rootEl.value?.blur()\n}\n\ndefineExpose({ focus, blur })\n\n// Эффективный размер: локальный проп → `GrConfigProvider` → дефолт компонента.\nconst resolvedSize = useGrComponentSize(() => props.size, {\n component: 'GrSwitch',\n supported: ['xs', 'sm', 'md', 'lg'],\n})\n\nconst emit = defineEmits<GrSwitchEmits>()\ndefineSlots<{\n /** Подпись переключателя. */\n default?: () => any\n}>()\n\nconst rootClass = computed(() => grSwitchRootClass(props.labelPosition))\n\nconst trackClass = computed(() => grSwitchTrackClass(resolvedSize.value))\n\nconst trackStyle = computed(() => {\n const isChecked = props.modelValue\n const defaultBackgroundColor = isChecked ? 'var(--gr-primary)' : 'var(--gr-muted)'\n const customBackgroundColor = getCustomColor(\n isChecked ? props.activeBackgroundColor : props.inactiveBackgroundColor,\n )\n // Недоступность гасится токеном и перебивает кастомный цвет: иначе выключенный\n // переключатель остался бы таким же ярким, как рабочий.\n const backgroundColor = isDisabled.value\n ? 'var(--gr-disabled-bg)'\n : customBackgroundColor ?? defaultBackgroundColor\n\n return {\n '--gr-switch-track-bg': backgroundColor,\n '--gr-switch-track-brd': isDisabled.value\n ? 'var(--gr-disabled-brd)'\n : customBackgroundColor\n ? backgroundColor\n : isChecked\n ? 'var(--gr-primary)'\n : 'var(--gr-brd)',\n 'backgroundColor': 'var(--gr-switch-track-bg)',\n }\n})\n\nconst thumbClass = computed(() => grSwitchThumbClass({ size: resolvedSize.value, checked: props.modelValue }))\n\nconst spinnerClass = computed(() => grSwitchSpinnerClass(resolvedSize.value))\n\nconst labelClass = computed(() => grSwitchLabelClass(resolvedSize.value, isDisabled.value))\n\nconst resolvedLoadingText = computed(() => props.loadingText ?? t('gr.switch.loading', 'Saving…'))\n\n// Выключенный переключатель не отправляется вовсе — так устроен чекбокс в HTML,\n// и сервер отличает «выкл» по отсутствию ключа.\nconst submitsValue = computed(\n () => props.modelValue && !isDisabled.value && Boolean(props.name),\n)\n\nfunction toggle(): void {\n if (isDisabled.value || isReadonly.value || props.loading) {\n return\n }\n\n const next = !props.modelValue\n emit('update:modelValue', next)\n emit('change', next)\n}\n</script>\n\n<template>\n <input\n v-if=\"submitsValue\"\n type=\"hidden\"\n :name=\"name\"\n :value=\"value\"\n :form=\"form\"\n >\n\n <button\n :id=\"fieldId\"\n ref=\"rootEl\"\n v-bind=\"$attrs\"\n type=\"button\"\n role=\"switch\"\n data-gr-switch\n :aria-checked=\"modelValue ? 'true' : 'false'\"\n :aria-label=\"ariaLabel\"\n :aria-describedby=\"describedBy\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n :aria-required=\"isRequired ? 'true' : undefined\"\n :aria-readonly=\"isReadonly ? 'true' : undefined\"\n :aria-busy=\"loading ? 'true' : undefined\"\n :disabled=\"isDisabled\"\n :class=\"rootClass\"\n @click=\"toggle\"\n @focus=\"emit('focus', $event)\"\n @blur=\"emit('blur', $event)\"\n >\n <span\n data-testid=\"gr-switch-track\"\n data-gr-switch-track\n :class=\"trackClass\"\n :style=\"trackStyle\"\n >\n <span\n data-testid=\"gr-switch-thumb\"\n data-gr-switch-thumb\n :class=\"thumbClass\"\n aria-hidden=\"true\"\n >\n <IconLoader v-if=\"loading\" data-gr-switch-spinner :class=\"spinnerClass\" />\n </span>\n </span>\n <span\n v-if=\"$slots.default\"\n data-gr-switch-label\n :class=\"labelClass\"\n >\n <slot />\n </span>\n <span v-if=\"loading\" data-gr-switch-loading-text class=\"sr-only\">{{ resolvedLoadingText }}</span>\n </button>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport IconLoader from '~icons/lucide/loader-circle'\n\nimport { useGrComponentSize } from '../GrConfigProvider/context'\nimport { useGrFormFieldContext } from '../GrFormField/context'\nimport { useGrFormControl } from '../../composables/useGrFormControl'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\n\nimport {\n grSwitchLabelClass,\n grSwitchRootClass,\n grSwitchSpinnerClass,\n grSwitchThumbClass,\n grSwitchTrackClass,\n type GrSwitchLabelPosition,\n type GrSwitchSize,\n} from './grSwitchStyles'\n\nexport type { GrSwitchLabelPosition, GrSwitchSize } from './grSwitchStyles'\n\n/**\n * Пропсы публичного GR-примитива «Switch».\n */\nexport interface GrSwitchProps {\n /**\n * Значение переключателя. Необязательное: без `v-model` контрол рисуется\n * выключенным — так же, как `GrCheckbox`. Обязательный проп заставлял бы\n * заводить `ref` даже там, где переключатель presentational.\n */\n modelValue?: boolean\n disabled?: boolean\n /** Только для чтения: состояние видно, но не переключается. */\n readonly?: boolean\n /** Визуальное и ARIA-состояние ошибки. */\n invalid?: boolean\n /** Обязательное поле (`aria-required`). */\n required?: boolean\n ariaLabel?: string\n size?: GrSwitchSize\n /** Сторона подписи относительно дорожки. */\n labelPosition?: GrSwitchLabelPosition\n /** Идёт сохранение: бегунок показывает спиннер, переключение заблокировано. */\n loading?: boolean\n /** i18n: что именно грузится. `aria-busy` сам по себе часть AT не объявляет. */\n loadingText?: string\n /** Имя поля для нативной отправки формы. Без него скрытое поле не рендерится. */\n name?: string\n /** Значение, уходящее в форму во включённом состоянии. */\n value?: string\n /** `id` формы, если переключатель лежит вне неё. */\n form?: string\n /** Кастомный цвет фона в активном состоянии. Если не задан — `var(--gr-primary)`. */\n activeBackgroundColor?: string\n /** Кастомный цвет фона в неактивном состоянии. Если не задан — `var(--gr-muted)`. */\n inactiveBackgroundColor?: string\n}\n\nexport interface GrSwitchEmits {\n (e: 'update:modelValue', value: boolean): void\n (e: 'change', value: boolean): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n}\n\nconst getCustomColor = (value?: string) => value?.trim() || undefined\n\n// Скрытое поле — сосед кнопки, а не её потомок: интерактивный контент внутри\n// `<button>` невалиден. Из-за этого корень компонента — фрагмент, и атрибуты\n// потребителя надо донести до кнопки руками.\ndefineOptions({ inheritAttrs: false })\n\nconst props = withDefaults(\n defineProps<GrSwitchProps>(),\n {\n modelValue: false,\n disabled: false,\n readonly: false,\n invalid: false,\n required: false,\n ariaLabel: undefined,\n size: undefined,\n labelPosition: 'end',\n loading: false,\n loadingText: undefined,\n name: undefined,\n value: 'on',\n form: undefined,\n activeBackgroundColor: undefined,\n inactiveBackgroundColor: undefined,\n },\n)\n\n// Контекст `GrFormField`: id для `<label for>`, описание ошибкой, невалидность.\n// `<button>` — labelable-элемент, поэтому клик по подписи фокусирует переключатель.\nconst field = useGrFormFieldContext()\nconst fieldId = 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\nconst { t } = useGranularityTranslations()\n\nconst rootEl = ref<HTMLButtonElement | null>(null)\n\nfunction focus(): void {\n rootEl.value?.focus()\n}\n\nfunction blur(): void {\n rootEl.value?.blur()\n}\n\ndefineExpose({ focus, blur })\n\n// Эффективный размер: локальный проп → `GrConfigProvider` → дефолт компонента.\nconst resolvedSize = useGrComponentSize(() => props.size, {\n component: 'GrSwitch',\n supported: ['xs', 'sm', 'md', 'lg'],\n})\n\nconst emit = defineEmits<GrSwitchEmits>()\ndefineSlots<{\n /** Подпись переключателя. */\n default?: () => any\n}>()\n\nconst rootClass = computed(() => grSwitchRootClass(props.labelPosition))\n\nconst trackClass = computed(() => grSwitchTrackClass(resolvedSize.value))\n\nconst trackStyle = computed(() => {\n const isChecked = props.modelValue\n const defaultBackgroundColor = isChecked ? 'var(--gr-primary)' : 'var(--gr-muted)'\n const customBackgroundColor = getCustomColor(\n isChecked ? props.activeBackgroundColor : props.inactiveBackgroundColor,\n )\n // Недоступность гасится токеном и перебивает кастомный цвет: иначе выключенный\n // переключатель остался бы таким же ярким, как рабочий.\n const backgroundColor = isDisabled.value\n ? 'var(--gr-disabled-bg)'\n : customBackgroundColor ?? defaultBackgroundColor\n\n return {\n '--gr-switch-track-bg': backgroundColor,\n '--gr-switch-track-brd': isDisabled.value\n ? 'var(--gr-disabled-brd)'\n : customBackgroundColor\n ? backgroundColor\n : isChecked\n ? 'var(--gr-primary)'\n : 'var(--gr-brd)',\n 'backgroundColor': 'var(--gr-switch-track-bg)',\n }\n})\n\nconst thumbClass = computed(() => grSwitchThumbClass({ size: resolvedSize.value, checked: props.modelValue }))\n\nconst spinnerClass = computed(() => grSwitchSpinnerClass(resolvedSize.value))\n\nconst labelClass = computed(() => grSwitchLabelClass(resolvedSize.value, isDisabled.value))\n\nconst resolvedLoadingText = computed(() => props.loadingText ?? t('gr.switch.loading', 'Saving…'))\n\n// Выключенный переключатель не отправляется вовсе — так устроен чекбокс в HTML,\n// и сервер отличает «выкл» по отсутствию ключа.\nconst submitsValue = computed(\n () => props.modelValue && !isDisabled.value && Boolean(props.name),\n)\n\nfunction toggle(): void {\n if (isDisabled.value || isReadonly.value || props.loading) {\n return\n }\n\n const next = !props.modelValue\n emit('update:modelValue', next)\n emit('change', next)\n}\n</script>\n\n<template>\n <input\n v-if=\"submitsValue\"\n type=\"hidden\"\n :name=\"name\"\n :value=\"value\"\n :form=\"form\"\n >\n\n <button\n :id=\"fieldId\"\n ref=\"rootEl\"\n v-bind=\"$attrs\"\n type=\"button\"\n role=\"switch\"\n data-gr-switch\n :aria-checked=\"modelValue ? 'true' : 'false'\"\n :aria-label=\"ariaLabel\"\n :aria-describedby=\"describedBy\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n :aria-required=\"isRequired ? 'true' : undefined\"\n :aria-readonly=\"isReadonly ? 'true' : undefined\"\n :aria-busy=\"loading ? 'true' : undefined\"\n :disabled=\"isDisabled\"\n :class=\"rootClass\"\n @click=\"toggle\"\n @focus=\"emit('focus', $event)\"\n @blur=\"emit('blur', $event)\"\n >\n <span\n data-testid=\"gr-switch-track\"\n data-gr-switch-track\n :class=\"trackClass\"\n :style=\"trackStyle\"\n >\n <span\n data-testid=\"gr-switch-thumb\"\n data-gr-switch-thumb\n :class=\"thumbClass\"\n aria-hidden=\"true\"\n >\n <IconLoader v-if=\"loading\" data-gr-switch-spinner :class=\"spinnerClass\" />\n </span>\n </span>\n <span\n v-if=\"$slots.default\"\n data-gr-switch-label\n :class=\"labelClass\"\n >\n <slot />\n </span>\n <span v-if=\"loading\" data-gr-switch-loading-text class=\"sr-only\">{{ resolvedLoadingText }}</span>\n </button>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiEA,MAAM,kBAAkB,UAAmB,OAAO,KAAK,KAAK,KAAA;EAO5D,MAAM,QAAQ;EAuBd,MAAM,QAAQ,sBAAsB;EACpC,MAAM,UAAU,eAAe,OAAO,GAAG,KAAK;EAC9C,MAAM,cAAc,eAAe,OAAO,cAAc,KAAK;EAC7D,MAAM,EACJ,UAAU,YACV,SAAS,WACT,UAAU,YACV,UAAU,eACR,uBAAuB,KAAK;EAEhC,MAAM,EAAE,MAAM,2BAA2B;EAEzC,MAAM,SAAS,IAA8B,IAAI;EAEjD,SAAS,QAAc;GACrB,OAAO,OAAO,MAAM;EACtB;EAEA,SAAS,OAAa;GACpB,OAAO,OAAO,KAAK;EACrB;EAEA,SAAa;GAAE;GAAO;EAAK,CAAC;EAG5B,MAAM,eAAe,yBAAyB,MAAM,MAAM;GACxD,WAAW;GACX,WAAW;IAAC;IAAM;IAAM;IAAM;GAAI;EACpC,CAAC;EAED,MAAM,OAAO;EAMb,MAAM,YAAY,eAAe,kBAAkB,MAAM,aAAa,CAAC;EAEvE,MAAM,aAAa,eAAe,mBAAmB,aAAa,KAAK,CAAC;EAExE,MAAM,aAAa,eAAe;GAChC,MAAM,YAAY,MAAM;GACxB,MAAM,yBAAyB,YAAY,sBAAsB;GACjE,MAAM,wBAAwB,eAC5B,YAAY,MAAM,wBAAwB,MAAM,uBAClD;GAGA,MAAM,kBAAkB,WAAW,QAC/B,0BACA,yBAAyB;GAE7B,OAAO;IACL,wBAAwB;IACxB,yBAAyB,WAAW,QAChC,2BACA,wBACE,kBACA,YACE,sBACA;IACR,mBAAmB;GACrB;EACF,CAAC;EAED,MAAM,aAAa,eAAe,mBAAmB;GAAE,MAAM,aAAa;GAAO,SAAS,MAAM;EAAW,CAAC,CAAC;EAE7G,MAAM,eAAe,eAAe,qBAAqB,aAAa,KAAK,CAAC;EAE5E,MAAM,aAAa,eAAe,mBAAmB,aAAa,OAAO,WAAW,KAAK,CAAC;EAE1F,MAAM,sBAAsB,eAAe,MAAM,eAAe,EAAE,qBAAqB,SAAS,CAAC;EAIjG,MAAM,eAAe,eACb,MAAM,cAAc,CAAC,WAAW,SAAS,QAAQ,MAAM,IAAI,CACnE;EAEA,SAAS,SAAe;GACtB,IAAI,WAAW,SAAS,WAAW,SAAS,MAAM,SAChD;GAGF,MAAM,OAAO,CAAC,MAAM;GACpB,KAAK,qBAAqB,IAAI;GAC9B,KAAK,UAAU,IAAI;EACrB;;GAKY,OAAA,UAAA,GAAA,mBAAA,UAAA,MAAA,CAAA,aAAA,SADV,UAAA,GAAA,mBAMC,SAAA;;IAJG,MAAK;IACJ,MAAM,QAAA;IACN,OAAO,QAAA;IACP,MAAM,QAAA;GAGX,GAAA,MAAA,GAAA,UAAA,KAAA,mBAAA,IAAA,IAAA,GAAA,mBA2CS,UA3CT,WA2CS;IA1CJ,IAAI,QAAA;IACD,SAAA;IAAJ,KAAI;GACIA,GAAAA,KAAAA,QAAM;IACd,MAAK;IACL,MAAK;IACL,kBAAA;IACC,gBAAc,QAAA,aAAU,SAAA;IACxB,cAAY,QAAA;IACZ,oBAAkB,YAAA;IAClB,gBAAc,MAAA,SAAA,IAAS,SAAY,KAAA;IACnC,iBAAe,MAAA,UAAA,IAAU,SAAY,KAAA;IACrC,iBAAe,MAAA,UAAA,IAAU,SAAY,KAAA;IACrC,aAAW,QAAA,UAAO,SAAY,KAAA;IAC9B,UAAU,MAAA,UAAA;IACV,OAAO,UAAA;IACP,SAAO;IACP,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,KAAI,SAAU,MAAM;IAC3B,QAAI,OAAA,OAAA,OAAA,MAAA,WAAE,KAAI,QAAS,MAAM;;IAE5B,mBAcO,QAAA;KAbH,eAAY;KACZ,wBAAA;KACC,OAAK,eAAE,WAAA,KAAU;KACjB,OAAK,eAAE,WAAA,KAAU;IAEpB,GAAA,CAAA,mBAOO,QAAA;KANH,eAAY;KACZ,wBAAA;KACC,OAAK,eAAE,WAAA,KAAU;KAClB,eAAY;IAEI,GAAA,CAAA,QAAA,WAAlB,UAAA,GAAA,YAA0E,MAAA,qBAAA,GAAA;;KAA/C,0BAAA;KAAwB,OAAK,eAAE,aAAA,KAAY;;IAIhEC,KAAAA,OAAO,WADjB,UAAA,GAAA,mBAMO,QAAA;;KAJH,wBAAA;KACC,OAAK,eAAE,WAAA,KAAU;IAEpB,GAAA,CAAA,WAAQ,KAAA,QAAA,SAAA,CAAA,GAAA,CAAA,KAAA,mBAAA,IAAA,IAAA;IAEE,QAAA,WAAZ,UAAA,GAAA,mBAAiG,QAAjG,YAAiG,gBAA7B,oBAAA,KAAmB,GAAA,CAAA,KAAA,mBAAA,IAAA,IAAA"}
|