@feugene/granularity 0.49.0 → 0.50.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/chunks/chevron-up-BhW6hi8L.js +23 -0
- package/dist/chunks/chevron-up-BhW6hi8L.js.map +1 -0
- package/dist/chunks/{config-E1cTyRVV.js → config-8n1VR0ju.js} +1 -1
- package/dist/chunks/{config-E1cTyRVV.js.map → config-8n1VR0ju.js.map} +1 -1
- package/dist/chunks/{config-DIDz_E5m.js → config-B34mkd53.js} +66 -1
- package/dist/chunks/config-B34mkd53.js.map +1 -0
- package/dist/chunks/{config-B5SBUa6u.js → config-B7qZqiUB.js} +28 -1
- package/dist/chunks/config-B7qZqiUB.js.map +1 -0
- package/dist/chunks/config-BBrPUEye.js +8 -0
- package/dist/chunks/config-BBrPUEye.js.map +1 -0
- package/dist/chunks/{config-CT4lP6S4.js → config-BOCzHqWh.js} +2 -2
- package/dist/chunks/{config-CT4lP6S4.js.map → config-BOCzHqWh.js.map} +1 -1
- package/dist/chunks/{config-iemgzS4x.js → config-BP1ktzwh.js} +2 -24
- package/dist/chunks/config-BP1ktzwh.js.map +1 -0
- package/dist/chunks/config-BTxmdRzT.js +28 -0
- package/dist/chunks/config-BTxmdRzT.js.map +1 -0
- package/dist/chunks/{config-CjWOTgLL.js → config-Bk52xd5l.js} +12 -5
- package/dist/chunks/config-Bk52xd5l.js.map +1 -0
- package/dist/chunks/{config-D3HOuHDe.js → config-C3SqBqSQ.js} +3 -1
- package/dist/chunks/config-C3SqBqSQ.js.map +1 -0
- package/dist/chunks/{config-2ZHRsdOc.js → config-C3uqd-sW.js} +1 -1
- package/dist/chunks/config-C3uqd-sW.js.map +1 -0
- package/dist/chunks/{config-BXRmsn3t.js → config-CPhcZzHi.js} +72 -10
- package/dist/chunks/config-CPhcZzHi.js.map +1 -0
- package/dist/chunks/config-CWtfxkM9.js.map +1 -1
- package/dist/chunks/{config--t2dxj0t.js → config-CmLTCmY9.js} +2 -2
- package/dist/chunks/{config--t2dxj0t.js.map → config-CmLTCmY9.js.map} +1 -1
- package/dist/chunks/{config-BAse0laT.js → config-CvtqB4ki.js} +8 -3
- package/dist/chunks/config-CvtqB4ki.js.map +1 -0
- package/dist/chunks/{config-BcXX7S7B.js → config-D-JgqV0J.js} +5 -1
- package/dist/chunks/{config-BcXX7S7B.js.map → config-D-JgqV0J.js.map} +1 -1
- package/dist/chunks/{config-BHLMTJaR.js → config-D1oVht-E.js} +8 -3
- package/dist/chunks/config-D1oVht-E.js.map +1 -0
- package/dist/chunks/{config-B42iJ48O.js → config-DG41PD9i.js} +13 -1
- package/dist/chunks/config-DG41PD9i.js.map +1 -0
- package/dist/chunks/{config-Bs1HwUcx.js → config-DPJ3jtiB.js} +12 -2
- package/dist/chunks/config-DPJ3jtiB.js.map +1 -0
- package/dist/chunks/{config-tNqtapcT.js → config-DpqjlCfB.js} +2 -2
- package/dist/chunks/{config-tNqtapcT.js.map → config-DpqjlCfB.js.map} +1 -1
- package/dist/chunks/{config-DNDgp9bB.js → config-DpzFoSIh.js} +64 -2
- package/dist/chunks/config-DpzFoSIh.js.map +1 -0
- package/dist/chunks/config-SPugeaHT.js +37 -0
- package/dist/chunks/config-SPugeaHT.js.map +1 -0
- package/dist/chunks/{en-CsQUoBS2.js → en-mIFywnV2.js} +22 -6
- package/dist/chunks/en-mIFywnV2.js.map +1 -0
- package/dist/chunks/{es-9nbkPvdA.js → es-CvthYwtB.js} +22 -6
- package/dist/chunks/es-CvthYwtB.js.map +1 -0
- package/dist/chunks/{es-DrUz5J6I.js → es-DlGd6Mrj.js} +3 -3
- package/dist/chunks/{es-DrUz5J6I.js.map → es-DlGd6Mrj.js.map} +1 -1
- package/dist/chunks/{generated-CxvC5O0V.js → generated-Cx4C7GRN.js} +84 -0
- package/dist/chunks/{generated-CxvC5O0V.js.map → generated-Cx4C7GRN.js.map} +1 -1
- package/dist/chunks/{grPopoverStyles-CYAeO3Ov.js → grPopoverStyles-CncEkiPu.js} +1 -1
- package/dist/chunks/{grPopoverStyles-CYAeO3Ov.js.map → grPopoverStyles-CncEkiPu.js.map} +1 -1
- package/dist/chunks/{grSelectStyles-D9EQ-pgK.js → grSelectStyles-Cam-IG9s.js} +3 -2
- package/dist/chunks/grSelectStyles-Cam-IG9s.js.map +1 -0
- package/dist/chunks/{grSliderStyles-kaz6VXZ3.js → grSliderStyles-CqzV0hll.js} +1 -1
- package/dist/chunks/grSliderStyles-CqzV0hll.js.map +1 -0
- package/dist/chunks/{config-CbEbLyDE.js → grSortableListStyles-CxieDmpe.js} +1 -27
- package/dist/chunks/grSortableListStyles-CxieDmpe.js.map +1 -0
- package/dist/chunks/grTableStyles-CPSxDTOW.js +66 -0
- package/dist/chunks/grTableStyles-CPSxDTOW.js.map +1 -0
- package/dist/chunks/{config-CMNfZO7S.js → grTabsStyles-DIz2VdIm.js} +1 -36
- package/dist/chunks/grTabsStyles-DIz2VdIm.js.map +1 -0
- package/dist/chunks/grTooltipStyles-CtbGEEGV.js +24 -0
- package/dist/chunks/grTooltipStyles-CtbGEEGV.js.map +1 -0
- package/dist/chunks/{granular-provider-B6xomapW.js → granular-provider-cv5vaM1a.js} +21 -19
- package/dist/chunks/granular-provider-cv5vaM1a.js.map +1 -0
- package/dist/chunks/hotkeySequence-BTHIM_5A.js +46 -0
- package/dist/chunks/hotkeySequence-BTHIM_5A.js.map +1 -0
- package/dist/chunks/{keyboard-DE2yhz_b.js → keyboard-BiFSgWLt.js} +70 -1
- package/dist/chunks/keyboard-BiFSgWLt.js.map +1 -0
- package/dist/chunks/optionPanel-DvY7Tv_4.js +182 -0
- package/dist/chunks/optionPanel-DvY7Tv_4.js.map +1 -0
- package/dist/chunks/overlayListPanel-D91jBCHU.js +21 -0
- package/dist/chunks/overlayListPanel-D91jBCHU.js.map +1 -0
- package/dist/chunks/{overlayStack-DH4Z7am1.js → overlayStack-DCkCgXZP.js} +1 -1
- package/dist/chunks/{overlayStack-DH4Z7am1.js.map → overlayStack-DCkCgXZP.js.map} +1 -1
- package/dist/chunks/{overlayPanelSurface-BKeMhRx4.js → overlayTransition-C0FDMx7Q.js} +24 -24
- package/dist/chunks/overlayTransition-C0FDMx7Q.js.map +1 -0
- package/dist/chunks/{ru-88i1WG0b.js → ru-CQKDR-kD.js} +22 -6
- package/dist/chunks/ru-CQKDR-kD.js.map +1 -0
- package/dist/chunks/{useMenuItemsFocus-B7Bl_aFA.js → useMenuItemsFocus-C031blYS.js} +1 -1
- package/dist/chunks/{useMenuItemsFocus-B7Bl_aFA.js.map → useMenuItemsFocus-C031blYS.js.map} +1 -1
- package/dist/chunks/{useModalOverlay-ACRkflzx.js → useModalOverlay-CKCQkGdB.js} +1 -1
- package/dist/chunks/{useModalOverlay-ACRkflzx.js.map → useModalOverlay-CKCQkGdB.js.map} +1 -1
- package/dist/components/GrAutocomplete/chunks/{GrAutocomplete-Chhw65pI.js → GrAutocomplete-Cyr1-ENw.js} +181 -123
- package/dist/components/GrAutocomplete/chunks/GrAutocomplete-Cyr1-ENw.js.map +1 -0
- package/dist/components/GrAutocomplete/index.js +2 -2
- package/dist/components/GrAutocomplete/styles.css +1 -1
- package/dist/components/GrCarousel/chunks/{GrCarousel-ByuughtI.js → GrCarousel-CBj-PEPB.js} +186 -37
- package/dist/components/GrCarousel/chunks/GrCarousel-CBj-PEPB.js.map +1 -0
- package/dist/components/GrCarousel/index.js +2 -2
- package/dist/components/GrCarousel/styles.css +1 -1
- package/dist/components/GrCheckbox/chunks/{GrCheckbox-Dp201frb.js → GrCheckbox-B4NJc7Ur.js} +2 -1
- package/dist/components/GrCheckbox/chunks/GrCheckbox-B4NJc7Ur.js.map +1 -0
- package/dist/components/GrCheckbox/index.js +1 -1
- package/dist/components/GrCheckboxGroup/chunks/{GrCheckboxGroup-IpqG5qwm.js → GrCheckboxGroup-qZHpNFCX.js} +1 -1
- package/dist/components/GrCheckboxGroup/chunks/{GrCheckboxGroup-IpqG5qwm.js.map → GrCheckboxGroup-qZHpNFCX.js.map} +1 -1
- package/dist/components/GrCheckboxGroup/index.js +1 -1
- package/dist/components/GrColorPicker/chunks/{GrColorPicker-BRM_4lyI.js → GrColorPicker-vGSgpUV9.js} +353 -38
- package/dist/components/GrColorPicker/chunks/GrColorPicker-vGSgpUV9.js.map +1 -0
- package/dist/components/GrColorPicker/index.js +2 -2
- package/dist/components/GrColorPicker/styles.css +1 -1
- package/dist/components/GrCommandPalette/chunks/{GrCommandPalette-BJHmkDQv.js → GrCommandPalette-CAHgj0Aa.js} +23 -7
- package/dist/components/GrCommandPalette/chunks/GrCommandPalette-CAHgj0Aa.js.map +1 -0
- package/dist/components/GrCommandPalette/index.js +1 -1
- package/dist/components/GrCommandPalette/styles.css +1 -1
- package/dist/components/GrConfirmDialog/chunks/{GrConfirmDialog-BzUQwbec.js → GrConfirmDialog-dg021BMd.js} +1 -1
- package/dist/components/GrConfirmDialog/chunks/{GrConfirmDialog-BzUQwbec.js.map → GrConfirmDialog-dg021BMd.js.map} +1 -1
- package/dist/components/GrConfirmDialog/index.js +1 -1
- package/dist/components/GrContextMenu/chunks/{GrContextMenu-RpxMHQXH.js → GrContextMenu-bQ1Vvy2c.js} +3 -3
- package/dist/components/GrContextMenu/chunks/{GrContextMenu-RpxMHQXH.js.map → GrContextMenu-bQ1Vvy2c.js.map} +1 -1
- package/dist/components/GrContextMenu/index.js +1 -1
- package/dist/components/GrDataTable/chunks/{GrDataTable-DQxYYgQk.js → GrDataTable-Bb2jWnRG.js} +576 -136
- package/dist/components/GrDataTable/chunks/GrDataTable-Bb2jWnRG.js.map +1 -0
- package/dist/components/GrDataTable/index.js +2 -2
- package/dist/components/GrDelta/chunks/{GrDelta-CgTvfoIN.js → GrDelta-BSv1KdMy.js} +0 -0
- package/dist/components/GrDelta/chunks/{GrDelta-CgTvfoIN.js.map → GrDelta-BSv1KdMy.js.map} +1 -1
- package/dist/components/GrDelta/index.js +2 -2
- package/dist/components/GrDialog/chunks/{GrDialog-DDz_OEhk.js → GrDialog-C9Zu72jB.js} +1 -1
- package/dist/components/GrDialog/chunks/{GrDialog-DDz_OEhk.js.map → GrDialog-C9Zu72jB.js.map} +1 -1
- package/dist/components/GrDialog/index.js +1 -1
- package/dist/components/GrDialogService/chunks/{GrDialogService-Dtq0ZCN5.js → GrDialogService-DLBifO61.js} +2 -2
- package/dist/components/GrDialogService/chunks/{GrDialogService-Dtq0ZCN5.js.map → GrDialogService-DLBifO61.js.map} +1 -1
- package/dist/components/GrDialogService/index.js +1 -1
- package/dist/components/GrDrawer/chunks/{GrDrawer-CNpSMuN-.js → GrDrawer-DOcRTE8G.js} +1 -1
- package/dist/components/GrDrawer/chunks/{GrDrawer-CNpSMuN-.js.map → GrDrawer-DOcRTE8G.js.map} +1 -1
- package/dist/components/GrDrawer/index.js +1 -1
- package/dist/components/GrDropdown/chunks/{GrDropdown-cLZuZMEY.js → GrDropdown-kxwmgfSe.js} +2 -2
- package/dist/components/GrDropdown/chunks/{GrDropdown-cLZuZMEY.js.map → GrDropdown-kxwmgfSe.js.map} +1 -1
- package/dist/components/GrDropdown/index.js +2 -2
- package/dist/components/GrDropdownMenu/chunks/{GrDropdownMenu-Dxkz_ry4.js → GrDropdownMenu--VoK3VY0.js} +3 -34
- package/dist/components/GrDropdownMenu/chunks/GrDropdownMenu--VoK3VY0.js.map +1 -0
- package/dist/components/GrDropdownMenu/chunks/GrDropdownMenu-CkM3BFPL.js +34 -0
- package/dist/components/GrDropdownMenu/chunks/GrDropdownMenu-CkM3BFPL.js.map +1 -0
- package/dist/components/GrDropdownMenu/index.js +2 -1
- package/dist/components/GrEmptyState/chunks/{GrEmptyState-BbvwE-E0.js → GrEmptyState-Bz6-_ED4.js} +42 -6
- package/dist/components/GrEmptyState/chunks/GrEmptyState-Bz6-_ED4.js.map +1 -0
- package/dist/components/GrEmptyState/index.js +1 -1
- package/dist/components/GrFileUpload/chunks/{GrFileUpload-BwMR71zN.js → GrFileUpload-Bgdfnkd8.js} +5 -2
- package/dist/components/GrFileUpload/chunks/GrFileUpload-Bgdfnkd8.js.map +1 -0
- package/dist/components/GrFileUpload/index.js +1 -1
- package/dist/components/GrFormFile/chunks/{GrFormFile-Dlxx-9zi.js → GrFormFile-Ctq1isz-.js} +78 -11
- package/dist/components/GrFormFile/chunks/GrFormFile-Ctq1isz-.js.map +1 -0
- package/dist/components/GrFormFile/index.js +2 -2
- package/dist/components/GrFormFile/styles.css +1 -1
- package/dist/components/GrImageViewer/chunks/{GrImageViewer-DK-iVHHC.js → GrImageViewer-Bnql_dNP.js} +1 -1
- package/dist/components/GrImageViewer/chunks/{GrImageViewer-DK-iVHHC.js.map → GrImageViewer-Bnql_dNP.js.map} +1 -1
- package/dist/components/GrImageViewer/index.js +1 -1
- package/dist/components/GrInput/chunks/{GrInput-DV4gMWPR.js → GrInput-CoaFLpaU.js} +43 -22
- package/dist/components/GrInput/chunks/GrInput-CoaFLpaU.js.map +1 -0
- package/dist/components/GrInput/index.js +1 -1
- package/dist/components/GrInputTag/chunks/{GrInputTag-qnRdAZnn.js → GrInputTag-BzzNRBe3.js} +245 -18
- package/dist/components/GrInputTag/chunks/GrInputTag-BzzNRBe3.js.map +1 -0
- package/dist/components/GrInputTag/index.js +2 -2
- package/dist/components/GrJsonViewer/chunks/{GrJsonViewer-D7CyWzhT.js → GrJsonViewer-B6ZJ3GFH.js} +2 -2
- package/dist/components/GrJsonViewer/chunks/{GrJsonViewer-D7CyWzhT.js.map → GrJsonViewer-B6ZJ3GFH.js.map} +1 -1
- package/dist/components/GrJsonViewer/index.js +1 -1
- package/dist/components/GrKbd/chunks/{GrKbd-BNMeoaLV.js → GrKbd-D2rHDkQv.js} +42 -37
- package/dist/components/GrKbd/chunks/GrKbd-D2rHDkQv.js.map +1 -0
- package/dist/components/GrKbd/index.js +1 -1
- package/dist/components/GrModal/chunks/{GrModal-Cm0mtSXk.js → GrModal-SHb1U2qx.js} +1 -1
- package/dist/components/GrModal/chunks/{GrModal-Cm0mtSXk.js.map → GrModal-SHb1U2qx.js.map} +1 -1
- package/dist/components/GrModal/index.js +1 -1
- package/dist/components/GrNavbar/chunks/{GrNavbar-BRGuksiy.js → GrNavbar-IEIHC5Vt.js} +10 -4
- package/dist/components/GrNavbar/chunks/GrNavbar-IEIHC5Vt.js.map +1 -0
- package/dist/components/GrNavbar/index.js +2 -2
- package/dist/components/GrNumberInput/chunks/{GrNumberInput-BI6-vkcs.js → GrNumberInput-CmnUDaKi.js} +2 -22
- package/dist/components/GrNumberInput/chunks/{GrNumberInput-BI6-vkcs.js.map → GrNumberInput-CmnUDaKi.js.map} +1 -1
- package/dist/components/GrNumberInput/index.js +1 -1
- package/dist/components/GrPagination/chunks/{GrPagination-CMrLPzvL.js → GrPagination-AMbn5D8I.js} +1 -1
- package/dist/components/GrPagination/chunks/{GrPagination-CMrLPzvL.js.map → GrPagination-AMbn5D8I.js.map} +1 -1
- package/dist/components/GrPagination/index.js +1 -1
- package/dist/components/GrPopover/chunks/{GrPopover-CcWvr1ZA.js → GrPopover-DjVxt8bf.js} +3 -3
- package/dist/components/GrPopover/chunks/{GrPopover-CcWvr1ZA.js.map → GrPopover-DjVxt8bf.js.map} +1 -1
- package/dist/components/GrPopover/index.js +2 -2
- package/dist/components/GrPromptDialog/chunks/{GrPromptDialog-BEL-QgwE.js → GrPromptDialog-BhbIZP1f.js} +3 -3
- package/dist/components/GrPromptDialog/chunks/{GrPromptDialog-BEL-QgwE.js.map → GrPromptDialog-BhbIZP1f.js.map} +1 -1
- package/dist/components/GrPromptDialog/index.js +1 -1
- package/dist/components/GrSelect/chunks/{GrSelect-DvSq-nV-.js → GrSelect-B-ykrHpL.js} +24 -180
- package/dist/components/GrSelect/chunks/GrSelect-B-ykrHpL.js.map +1 -0
- package/dist/components/GrSelect/index.js +2 -2
- package/dist/components/GrSelect/styles.css +1 -1
- package/dist/components/GrSidebar/chunks/GrSidebar-jVvoLGDO.js +543 -0
- package/dist/components/GrSidebar/chunks/GrSidebar-jVvoLGDO.js.map +1 -0
- package/dist/components/GrSidebar/index.js +2 -2
- package/dist/components/GrSlider/chunks/{GrSlider-CO8N9SF6.js → GrSlider-DF2Cq0rV.js} +29 -4
- package/dist/components/GrSlider/chunks/GrSlider-DF2Cq0rV.js.map +1 -0
- package/dist/components/GrSlider/index.js +2 -2
- package/dist/components/GrSortableList/chunks/{GrSortableList-NKriuGm_.js → GrSortableList-ChH9nQBv.js} +1 -1
- package/dist/components/GrSortableList/chunks/{GrSortableList-NKriuGm_.js.map → GrSortableList-ChH9nQBv.js.map} +1 -1
- package/dist/components/GrSortableList/index.js +2 -2
- package/dist/components/GrTabPanels/chunks/GrTabPanels-BDt5oVdd.js +30 -0
- package/dist/components/GrTabPanels/chunks/GrTabPanels-BDt5oVdd.js.map +1 -0
- package/dist/components/GrTabPanels/chunks/GrTabPanels-BJvAflnA.js +83 -0
- package/dist/components/GrTabPanels/chunks/GrTabPanels-BJvAflnA.js.map +1 -0
- package/dist/components/GrTabPanels/index.js +2 -1
- package/dist/components/GrTable/chunks/{GrTable-CKJ-HrsN.js → GrTable-BQitSDep.js} +52 -16
- package/dist/components/GrTable/chunks/GrTable-BQitSDep.js.map +1 -0
- package/dist/components/GrTable/index.js +2 -2
- package/dist/components/GrTabs/chunks/{GrTabs-s9RzC1WX.js → GrTabs-DVC6nfWB.js} +1 -1
- package/dist/components/GrTabs/chunks/{GrTabs-s9RzC1WX.js.map → GrTabs-DVC6nfWB.js.map} +1 -1
- package/dist/components/GrTabs/index.js +2 -2
- package/dist/components/GrTabsWithPanels/chunks/GrTabsWithPanels-BYtuzouj.js +98 -0
- package/dist/components/GrTabsWithPanels/chunks/GrTabsWithPanels-BYtuzouj.js.map +1 -0
- package/dist/components/GrTabsWithPanels/index.js +3 -0
- package/dist/components/GrTextarea/chunks/{GrTextarea-Bfgc0DD2.js → GrTextarea-BFwyqcHB.js} +22 -5
- package/dist/components/GrTextarea/chunks/GrTextarea-BFwyqcHB.js.map +1 -0
- package/dist/components/GrTextarea/index.js +1 -1
- package/dist/components/GrToaster/chunks/{GrToaster-CZixNk0l.js → GrToaster-OzT1VKcp.js} +163 -32
- package/dist/components/GrToaster/chunks/GrToaster-OzT1VKcp.js.map +1 -0
- package/dist/components/GrToaster/index.js +2 -2
- package/dist/components/GrToaster/styles.css +1 -1
- package/dist/components/GrTooltip/chunks/{GrTooltip-DkMMM0uq.js → GrTooltip-BDcFycvW.js} +26 -13
- package/dist/components/GrTooltip/chunks/GrTooltip-BDcFycvW.js.map +1 -0
- package/dist/components/GrTooltip/index.js +2 -2
- package/dist/components/GrTransfer/chunks/{GrTransfer-DdfBUf35.js → GrTransfer-CV7BL5UK.js} +114 -12
- package/dist/components/GrTransfer/chunks/GrTransfer-CV7BL5UK.js.map +1 -0
- package/dist/components/GrTransfer/index.js +1 -1
- package/dist/components/GrTransfer/styles.css +1 -1
- package/dist/components/GrTree/chunks/{GrTree-6yZrncJE.js → GrTree-DpgaP8hk.js} +39 -1
- package/dist/components/GrTree/chunks/GrTree-DpgaP8hk.js.map +1 -0
- package/dist/components/GrTree/index.js +1 -1
- package/dist/components/GrTree/styles.css +1 -1
- package/dist/components/GrTreeSections/chunks/{GrTreeSections-DcmDcLwj.js → GrTreeSections-BeOj__44.js} +1 -1
- package/dist/components/GrTreeSections/chunks/{GrTreeSections-DcmDcLwj.js.map → GrTreeSections-BeOj__44.js.map} +1 -1
- package/dist/components/GrTreeSections/index.js +1 -1
- package/dist/components/GrTreeSelect/chunks/{GrTreeSelect-B6KoDYiZ.js → GrTreeSelect-BHq-T8wH.js} +113 -14
- package/dist/components/GrTreeSelect/chunks/GrTreeSelect-BHq-T8wH.js.map +1 -0
- package/dist/components/GrTreeSelect/index.js +2 -2
- package/dist/composables/useFloating.js +1 -1
- package/dist/composables/useHotkeys.js +89 -0
- package/dist/composables/useHotkeys.js.map +1 -0
- package/dist/composables/useOverlayLayer.js +1 -1
- package/dist/directives/hotkey.js +19 -46
- package/dist/directives/hotkey.js.map +1 -1
- package/dist/granular-provider-node.js +1 -1
- package/dist/granular-provider.js +1 -1
- package/dist/i18n/all.js +1 -1
- package/dist/i18n/index.js +1 -1
- package/dist/index.js +71 -63
- package/dist/theme.js +1 -1
- package/dist/tokens.js +1 -1
- package/dist/types/components/GrAutocomplete/GrAutocomplete.vue.d.ts +4 -4
- package/dist/types/components/GrAutocomplete/composables/useAutocompleteNavigation.d.ts +12 -3
- package/dist/types/components/GrAutocomplete/composables/useAutocompletePanel.d.ts +12 -2
- package/dist/types/components/GrAutocomplete/composables/useAutocompleteValues.d.ts +7 -4
- package/dist/types/components/GrAutocomplete/composables/useRemoteOptions.d.ts +4 -4
- package/dist/types/components/GrAutocomplete/grAutocompleteStyles.d.ts +15 -1
- package/dist/types/components/GrAutocomplete/index.d.ts +1 -1
- package/dist/types/components/GrCarousel/GrCarousel.vue.d.ts +29 -1
- package/dist/types/components/GrCarousel/composables/useCarouselSwipe.d.ts +5 -0
- package/dist/types/components/GrCarousel/grCarouselContext.d.ts +21 -0
- package/dist/types/components/GrCarousel/grCarouselStyles.d.ts +39 -9
- package/dist/types/components/GrCheckbox/GrCheckbox.vue.d.ts +13 -0
- package/dist/types/components/GrColorPicker/GrColorPicker.vue.d.ts +18 -2
- package/dist/types/components/GrColorPicker/color.d.ts +13 -1
- package/dist/types/components/GrColorPicker/colorArea.d.ts +59 -0
- package/dist/types/components/GrColorPicker/defaults.d.ts +3 -1
- package/dist/types/components/GrColorPicker/eyeDropper.d.ts +21 -0
- package/dist/types/components/GrColorPicker/grColorPickerStyles.d.ts +42 -0
- package/dist/types/components/GrCommandPalette/GrCommandPalette.vue.d.ts +10 -1
- package/dist/types/components/GrDataTable/GrDataTable.vue.d.ts +41 -1
- package/dist/types/components/GrDataTable/composables/useDataTableGridNavigation.d.ts +53 -0
- package/dist/types/components/GrDataTable/grDataTableStyles.d.ts +8 -0
- package/dist/types/components/GrDataTable/index.d.ts +1 -1
- package/dist/types/components/GrDelta/grDeltaStyles.d.ts +10 -3
- package/dist/types/components/GrEmptyState/GrEmptyState.vue.d.ts +11 -2
- package/dist/types/components/GrEmptyState/grEmptyStateStyles.d.ts +10 -0
- package/dist/types/components/GrEmptyState/index.d.ts +1 -1
- package/dist/types/components/GrFileUpload/GrFileUpload.vue.d.ts +2 -1
- package/dist/types/components/GrFormFile/GrFormFile.vue.d.ts +10 -0
- package/dist/types/components/GrInput/GrInput.vue.d.ts +15 -0
- package/dist/types/components/GrInputTag/GrInputTag.vue.d.ts +16 -0
- package/dist/types/components/GrInputTag/grInputTagStyles.d.ts +11 -0
- package/dist/types/components/GrKbd/GrKbd.vue.d.ts +14 -0
- package/dist/types/components/GrNavbar/GrNavbar.vue.d.ts +18 -0
- package/dist/types/components/GrNavbar/grNavbarStyles.d.ts +10 -1
- package/dist/types/components/GrNavbar/index.d.ts +1 -0
- package/dist/types/components/GrSelect/composables/useSelectPanelItems.d.ts +8 -34
- package/dist/types/components/GrSelect/composables/useSelectVirtualization.d.ts +7 -0
- package/dist/types/components/GrSelect/grSelectStyles.d.ts +1 -1
- package/dist/types/components/GrSidebar/GrSidebar.vue.d.ts +20 -0
- package/dist/types/components/GrSidebar/GrSidebarItem.vue.d.ts +39 -5
- package/dist/types/components/GrSidebar/grSidebarStyles.d.ts +32 -0
- package/dist/types/components/GrSidebar/index.d.ts +1 -1
- package/dist/types/components/GrSidebar/sidebarContext.d.ts +23 -1
- package/dist/types/components/GrSlider/GrSlider.vue.d.ts +39 -1
- package/dist/types/components/GrSlider/grSliderStyles.d.ts +1 -1
- package/dist/types/components/GrTabPanels/context.d.ts +6 -0
- package/dist/types/components/GrTable/GrTable.vue.d.ts +37 -0
- package/dist/types/components/GrTable/grTableStyles.d.ts +16 -7
- package/dist/types/components/GrTabsWithPanels/GrTabsWithPanels.vue.d.ts +77 -0
- package/dist/types/components/GrTabsWithPanels/config.d.ts +1 -0
- package/dist/types/components/GrTabsWithPanels/index.d.ts +4 -0
- package/dist/types/components/GrTextarea/GrTextarea.vue.d.ts +19 -1
- package/dist/types/components/GrToaster/GrToaster.vue.d.ts +12 -0
- package/dist/types/components/GrToaster/grToasterStyles.d.ts +26 -0
- package/dist/types/components/GrTooltip/GrTooltip.vue.d.ts +22 -0
- package/dist/types/components/GrTransfer/GrTransfer.vue.d.ts +23 -0
- package/dist/types/components/GrTreeSelect/grTreeSelectStyles.d.ts +1 -1
- package/dist/types/components/GrTreeSelect/grTreeSelectTypes.d.ts +20 -0
- package/dist/types/components/shared/hotkey.d.ts +8 -9
- package/dist/types/components/shared/optionPanel.d.ts +100 -0
- package/dist/types/components/shared/overlayListPanel.d.ts +18 -0
- package/dist/types/composables/useHotkeys.d.ts +43 -0
- package/dist/types/granular-provider/shared.d.ts +1 -0
- package/dist/types/i18n/messages/en.d.ts +28 -0
- package/dist/types/i18n/messages/es.d.ts +28 -0
- package/dist/types/i18n/messages/ru.d.ts +28 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/internal/hotkeySequence.d.ts +22 -0
- package/dist/types/internal/keyboard.d.ts +45 -0
- package/dist/web-types.json +463 -26
- package/package.json +9 -1
- package/dist/chunks/config-2ZHRsdOc.js.map +0 -1
- package/dist/chunks/config-B42iJ48O.js.map +0 -1
- package/dist/chunks/config-B5SBUa6u.js.map +0 -1
- package/dist/chunks/config-BAse0laT.js.map +0 -1
- package/dist/chunks/config-BHLMTJaR.js.map +0 -1
- package/dist/chunks/config-BXRmsn3t.js.map +0 -1
- package/dist/chunks/config-Bs1HwUcx.js.map +0 -1
- package/dist/chunks/config-CMNfZO7S.js.map +0 -1
- package/dist/chunks/config-CbEbLyDE.js.map +0 -1
- package/dist/chunks/config-CjWOTgLL.js.map +0 -1
- package/dist/chunks/config-D3HOuHDe.js.map +0 -1
- package/dist/chunks/config-DIDz_E5m.js.map +0 -1
- package/dist/chunks/config-DNDgp9bB.js.map +0 -1
- package/dist/chunks/config-iemgzS4x.js.map +0 -1
- package/dist/chunks/en-CsQUoBS2.js.map +0 -1
- package/dist/chunks/es-9nbkPvdA.js.map +0 -1
- package/dist/chunks/grSelectStyles-D9EQ-pgK.js.map +0 -1
- package/dist/chunks/grSliderStyles-kaz6VXZ3.js.map +0 -1
- package/dist/chunks/grTableStyles-DkQPMfx9.js +0 -39
- package/dist/chunks/grTableStyles-DkQPMfx9.js.map +0 -1
- package/dist/chunks/granular-provider-B6xomapW.js.map +0 -1
- package/dist/chunks/keyboard-DE2yhz_b.js.map +0 -1
- package/dist/chunks/overlayPanelSurface-BKeMhRx4.js.map +0 -1
- package/dist/chunks/ru-88i1WG0b.js.map +0 -1
- package/dist/components/GrAutocomplete/chunks/GrAutocomplete-Chhw65pI.js.map +0 -1
- package/dist/components/GrCarousel/chunks/GrCarousel-ByuughtI.js.map +0 -1
- package/dist/components/GrCheckbox/chunks/GrCheckbox-Dp201frb.js.map +0 -1
- package/dist/components/GrColorPicker/chunks/GrColorPicker-BRM_4lyI.js.map +0 -1
- package/dist/components/GrCommandPalette/chunks/GrCommandPalette-BJHmkDQv.js.map +0 -1
- package/dist/components/GrDataTable/chunks/GrDataTable-DQxYYgQk.js.map +0 -1
- package/dist/components/GrDropdownMenu/chunks/GrDropdownMenu-Dxkz_ry4.js.map +0 -1
- package/dist/components/GrEmptyState/chunks/GrEmptyState-BbvwE-E0.js.map +0 -1
- package/dist/components/GrFileUpload/chunks/GrFileUpload-BwMR71zN.js.map +0 -1
- package/dist/components/GrFormFile/chunks/GrFormFile-Dlxx-9zi.js.map +0 -1
- package/dist/components/GrInput/chunks/GrInput-DV4gMWPR.js.map +0 -1
- package/dist/components/GrInputTag/chunks/GrInputTag-qnRdAZnn.js.map +0 -1
- package/dist/components/GrKbd/chunks/GrKbd-BNMeoaLV.js.map +0 -1
- package/dist/components/GrNavbar/chunks/GrNavbar-BRGuksiy.js.map +0 -1
- package/dist/components/GrSelect/chunks/GrSelect-DvSq-nV-.js.map +0 -1
- package/dist/components/GrSidebar/chunks/GrSidebar-DoKLlHSw.js +0 -264
- package/dist/components/GrSidebar/chunks/GrSidebar-DoKLlHSw.js.map +0 -1
- package/dist/components/GrSlider/chunks/GrSlider-CO8N9SF6.js.map +0 -1
- package/dist/components/GrTabPanels/chunks/GrTabPanels-5zsbqS1v.js +0 -92
- package/dist/components/GrTabPanels/chunks/GrTabPanels-5zsbqS1v.js.map +0 -1
- package/dist/components/GrTable/chunks/GrTable-CKJ-HrsN.js.map +0 -1
- package/dist/components/GrTextarea/chunks/GrTextarea-Bfgc0DD2.js.map +0 -1
- package/dist/components/GrToaster/chunks/GrToaster-CZixNk0l.js.map +0 -1
- package/dist/components/GrTooltip/chunks/GrTooltip-DkMMM0uq.js.map +0 -1
- package/dist/components/GrTransfer/chunks/GrTransfer-DdfBUf35.js.map +0 -1
- package/dist/components/GrTree/chunks/GrTree-6yZrncJE.js.map +0 -1
- package/dist/components/GrTreeSelect/chunks/GrTreeSelect-B6KoDYiZ.js.map +0 -1
- package/dist/types/components/GrAutocomplete/composables/useAutocompleteVirtual.d.ts +0 -33
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GrTabs-s9RzC1WX.js","names":[],"sources":["../../../../src/components/GrTabs/GrTabs.vue","../../../../src/components/GrTabs/GrTabs.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, nextTick, onBeforeUnmount, onMounted, ref, watch, watchEffect, type Component } from 'vue'\n\nimport IconX from '~icons/lucide/x'\n\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { useRovingFocus } from '../../composables/useRovingFocus'\nimport { iconClass, iconTag } from '../shared/icon'\nimport { resolveScrollOverflow, type GrScrollOverflow } from '../shared/scrollOverflow'\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\nimport {\n grTabsBadgeClass,\n grTabsCloseClass,\n grTabsEmptyClass,\n grTabsListClass,\n grTabsTabClass,\n tabCloseIconSizes,\n tabContentClass,\n tabIconClass,\n type GrTabsOrientation,\n type GrTabsSize,\n type GrTabsVariant,\n} from './grTabsStyles'\n\nexport interface GrTab {\n value: string\n label: string\n badge?: string\n /**\n * Иконка слева от подписи: Vue-компонент либо класс иконки вашей UnoCSS-сборки\n * (`'i-lucide-user'` — тогда нужен ваш `presetIcons`, см. `docs/installation.md`).\n */\n icon?: string | Component\n disabled?: boolean\n /** Перекрывает `closable` ряда в обе стороны: закрепить вкладку или закрыть одну её. */\n closable?: boolean\n}\n\nexport interface GrTabsProps {\n modelValue: string\n tabs: GrTab[]\n /**\n * База id для ARIA-связки с `GrTabPanels`. Если задана, каждая вкладка\n * получает `id=\"<idBase>-tab-<value>\"` и `aria-controls=\"<idBase>-panel-<value>\"`.\n * Передайте тот же `idBase` в `GrTabPanels`, чтобы связать `tab`↔`tabpanel`.\n */\n idBase?: string\n size?: GrTabsSize\n /** Вид ряда: обойма с таблетками или ряд с подчёркиванием. */\n variant?: GrTabsVariant\n /**\n * `automatic` (по умолчанию) — стрелка сразу переключает вкладку;\n * `manual` — стрелка двигает только фокус, выбор подтверждается\n * `Enter`/`Space`. Второй режим для вкладок с тяжёлой загрузкой: перебор\n * стрелками иначе тянет каждую панель.\n */\n activationMode?: GrTabsActivationMode\n /** Горизонтальный (по умолчанию) или вертикальный список вкладок. */\n orientation?: GrTabsOrientation\n /**\n * Крестик на вкладках и закрытие по `Delete`/`Backspace`. Точечно снимается\n * `closable: false` у самой вкладки.\n */\n closable?: boolean\n /** Текст, когда список вкладок пуст. Слот `#empty` сильнее. */\n emptyText?: string\n}\n\nexport interface GrTabsEmits {\n (e: 'update:modelValue', value: string): void\n /**\n * Просьба закрыть вкладку. Компонент список не трогает: `tabs` — проп, и\n * закрытие может не состояться («сохранить изменения?»).\n */\n (e: 'close', value: string): void\n}\n\nexport type GrTabsActivationMode = 'automatic' | 'manual'\n\nexport type { GrTabsOrientation, GrTabsVariant } from './grTabsStyles'\n\n/**\n * GrTabs — горизонтальная группа вкладок с паттерном WAI-ARIA `tablist`.\n *\n * A11y:\n * - `role=\"tablist\"` на корне, `role=\"tab\"` на каждой вкладке.\n * - Roving `tabindex`: активная вкладка `0`, остальные `-1`.\n * - Клавиатура: `ArrowLeft`/`ArrowRight` — цикличный переход, `Home`/`End` — к первой/последней.\n * - При переключении стрелками DOM-фокус программно переносится на новую вкладку.\n *\n * Сам компонент не рендерит `tabpanel` — это ответственность консьюмера (по `aria-controls`/внешней разметке).\n */\nconst props = withDefaults(defineProps<GrTabsProps>(), {\n idBase: undefined,\n size: undefined,\n // Дефолт живёт в резолвере: Vue подставил бы свой раньше, чем компонент\n // заглянет в `GrConfigProvider`.\n variant: undefined,\n activationMode: 'automatic',\n orientation: 'horizontal',\n closable: false,\n emptyText: undefined,\n})\n\ndefineSlots<{\n /** Содержимое вкладки целиком — вместо подписи, иконки и счётчика. Крестик остаётся. */\n tab?: (props: { tab: GrTab, active: boolean, disabled: boolean }) => unknown\n /** Пустой ряд — вместо текста из локали. */\n empty?: () => unknown\n}>()\n\nconst emit = defineEmits<GrTabsEmits>()\n\nconst { t } = useGranularityTranslations()\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrTabs' })\nconst resolvedVariant = useGrComponentProp('GrTabs', 'variant', () => props.variant, 'pills')\n\nconst tablistClass = computed(() => grTabsListClass({\n variant: resolvedVariant.value,\n size: resolvedSize.value,\n orientation: props.orientation,\n}))\n\nfunction tabClass(tab: GrTab): string {\n return grTabsTabClass({\n variant: resolvedVariant.value,\n size: resolvedSize.value,\n active: tab.value === props.modelValue,\n disabled: Boolean(tab.disabled),\n })\n}\n\nconst badgeClass = computed(() => grTabsBadgeClass(resolvedSize.value))\nconst closeClass = computed(() => grTabsCloseClass(resolvedSize.value))\nconst closeIconClass = computed(() => tabCloseIconSizes[resolvedSize.value])\nconst emptyClass = computed(() => grTabsEmptyClass(resolvedSize.value))\n\n/**\n * Пустой ряд — не пустой `tablist`: роль обязана владеть потомками `tab`,\n * и текст внутри неё был бы нарушением, а не пустым состоянием. Поэтому\n * пустое состояние рисуется отдельной веткой, а не содержимым `tablist`.\n */\nconst isEmpty = computed(() => props.tabs.length === 0)\nconst resolvedEmptyText = computed(() => props.emptyText ?? t('gr.tabs.empty', 'Nothing here yet'))\nconst closeTitle = computed(() => t('gr.tabs.close', 'Close tab'))\n\n/** Отключённая вкладка не принимает взаимодействия, а закрытие — тоже взаимодействие. */\nfunction isClosable(tab: GrTab): boolean {\n return !tab.disabled && (tab.closable ?? props.closable)\n}\n\nconst buttonRefs = ref<HTMLButtonElement[]>([])\n\n/**\n * Vue зовёт ref-функцию с `null`, когда узел исчез. Игнорировать такой вызов\n * нельзя: в массиве оставались отсоединённые от DOM кнопки, и `focus()` по\n * сократившемуся списку молча проваливался в `<body>`.\n */\nfunction setButtonRef(el: unknown, index: number): void {\n if (el instanceof HTMLButtonElement)\n buttonRefs.value[index] = el\n else\n delete buttonRefs.value[index]\n\n if (buttonRefs.value.length > props.tabs.length)\n buttonRefs.value.length = props.tabs.length\n}\n\nconst activeIndex = computed(() => props.tabs.findIndex(t => t.value === props.modelValue))\n\nfunction isEnabled(tab: GrTab): boolean {\n return !tab.disabled\n}\n\nconst tabIndexes = computed(() => props.tabs.map((_, index) => index))\n\n/**\n * Кольцо roving-фокуса. При `activationMode: 'manual'` фокус уезжает вперёд\n * выбора — это и есть разница между `activeKey` примитива и `initialKey`:\n * первый ведёт фокус, второй подставляет выбранную вкладку, пока фокус не\n * трогали.\n *\n * Выбор стрелка **не** переносит: этим заведует `onKeydown` по\n * `activationMode`. Через `onMove` это выразить нельзя — клик тоже зовёт\n * `focusKey`, и выбор эмитился бы дважды.\n */\nconst roving = useRovingFocus<number>({\n items: () => tabIndexes.value,\n elementFor: index => buttonRefs.value[index],\n isDisabled: index => Boolean(props.tabs[index]?.disabled),\n orientation: () => (props.orientation === 'vertical' ? 'vertical' : 'horizontal'),\n skipDisabled: () => true,\n /**\n * Пока фокус не трогали, остановку `Tab` держит выбранная вкладка. Если\n * `modelValue` не совпал ни с одной (пустое начальное значение, асинхронный\n * список, удалённая активная вкладка), примитив ставит её на первую\n * доступную — иначе tablist выпал бы из обхода целиком и молча.\n *\n * Выключенность здесь не проверяется намеренно: на кнопке `aria-disabled`, а\n * не нативный `disabled`, поэтому выключенная вкладка фокусируется и держать\n * остановку вправе.\n */\n initialKey: () => (activeIndex.value >= 0 ? activeIndex.value : undefined),\n /**\n * Прокрутка до фокуса, а не после: иначе браузер доскроллит сам по-своему, и\n * ряд дёрнется дважды. Синхронно — фокус обязан остаться в том же такте, что\n * и раньше: тик, где он нужен, добавляет `focusIndex`.\n */\n beforeFocus: index => scrollIntoView(index),\n})\n\n/**\n * Фокус после перерисовки — для путей, где список только что изменился\n * (закрытие вкладки). Клавиатура идёт через примитив напрямую: там ряд уже\n * отрисован, и лишний тик только отложил бы фокус.\n */\nasync function focusIndex(index: number): Promise<void> {\n await nextTick()\n await roving.focusKey(index)\n}\n\n/**\n * Ряд прокручивается, а не переносится, поэтому активная вкладка может уехать\n * за край — например, когда её выбрали снаружи. `scrollIntoView` нет в jsdom и\n * в старых движках, поэтому вызов необязательный.\n */\nfunction scrollIntoView(index: number): void {\n buttonRefs.value[index]?.scrollIntoView?.({ block: 'nearest', inline: 'nearest' })\n}\n\nwatch(() => props.modelValue, async () => {\n await nextTick()\n if (activeIndex.value >= 0)\n scrollIntoView(activeIndex.value)\n})\n\n/**\n * Признак того, что ряд продолжается за краем.\n *\n * Полоса прокрутки у ряда скрыта намеренно, и без этого признака продолжение\n * не выдаёт себя ничем: вкладки за краем достижимы стрелками, но о том, что они\n * там есть, догадаться нечем. Затухание рисует CSS по `data-overflow`.\n *\n * Вертикальный ряд не скроллится вовсе — он растёт вниз, — поэтому состояние\n * считается только для горизонтального.\n */\nconst tablistEl = ref<HTMLElement | null>(null)\nconst scrollOverflow = ref<GrScrollOverflow>('none')\n\nlet resizeObserver: ResizeObserver | null = null\nlet scheduled = false\n\nfunction measureOverflow(): void {\n const el = tablistEl.value\n\n if (!el || props.orientation === 'vertical') {\n scrollOverflow.value = 'none'\n return\n }\n\n scrollOverflow.value = resolveScrollOverflow(el.scrollLeft, el.scrollWidth, el.clientWidth)\n}\n\nfunction scheduleMeasure(): void {\n if (scheduled)\n return\n\n scheduled = true\n void nextTick(() => {\n scheduled = false\n measureOverflow()\n })\n}\n\nonMounted(() => {\n scheduleMeasure()\n\n // На сервере и в jsdom `ResizeObserver` отсутствует — измерять там нечего.\n if (typeof ResizeObserver === 'undefined')\n return\n\n resizeObserver = new ResizeObserver(scheduleMeasure)\n if (tablistEl.value)\n resizeObserver.observe(tablistEl.value)\n})\n\nonBeforeUnmount(() => {\n resizeObserver?.disconnect()\n resizeObserver = null\n})\n\n// Состав ряда меняет его ширину, ориентация — само наличие прокрутки.\nwatch(() => [props.tabs.length, props.orientation], scheduleMeasure)\n\nasync function selectByIndex(index: number, focus = false): Promise<void> {\n const tab = props.tabs[index]\n if (!tab || !isEnabled(tab))\n return\n if (tab.value !== props.modelValue)\n emit('update:modelValue', tab.value)\n if (focus)\n await focusIndex(index)\n}\n\nfunction onKeydown(event: KeyboardEvent): void {\n if (props.tabs.length === 0)\n return\n\n const currentIndex = roving.rovingKey.value ?? 0\n\n switch (event.key) {\n case 'Enter':\n case ' ':\n // В ручном режиме выбор подтверждается явно — на той вкладке, где фокус.\n if (props.activationMode === 'manual') {\n event.preventDefault()\n void selectByIndex(currentIndex, false)\n }\n return\n case 'Delete':\n case 'Backspace': {\n // Единственный клавиатурный путь к закрытию: крестик в таб-порядке не стоит.\n const tab = props.tabs[currentIndex]\n if (tab && isClosable(tab)) {\n event.preventDefault()\n requestClose(currentIndex)\n }\n return\n }\n }\n\n // Стрелки, `Home`, `End` — фокус ведёт примитив по объявленной ориентации.\n if (!roving.handleNavigationKeys(event))\n return\n\n // `automatic`: выбор едет за фокусом. `manual`: ждёт `Enter`/`Space`.\n if (props.activationMode !== 'manual')\n void selectByIndex(roving.rovingKey.value ?? currentIndex, false)\n}\n\n/**\n * Закрытая вкладка, которая держала фокус. Компонент список не укорачивает —\n * это делает потребитель, — поэтому фокус возвращается только после того, как\n * `tabs` действительно стал короче.\n */\nlet pendingClose: { index: number, value: string } | null = null\n\nfunction requestClose(index: number): void {\n const tab = props.tabs[index]\n if (!tab || !isClosable(tab))\n return\n\n const held = buttonRefs.value[index] === document.activeElement\n pendingClose = held ? { index, value: tab.value } : null\n\n emit('close', tab.value)\n}\n\n// Без этого клавиатурное закрытие роняет фокус в `<body>`: кнопка, на которой\n// он стоял, исчезает из DOM вместе со вкладкой.\nwatch(() => props.tabs.length, async (next, prev) => {\n const pending = pendingClose\n pendingClose = null\n\n if (!pending || next >= prev)\n return\n // Ушла не та вкладка — потребитель поменял список по своей причине.\n if (props.tabs.some(tab => tab.value === pending.value))\n return\n\n await nextTick()\n\n const target = Math.min(pending.index, next - 1)\n if (target >= 0)\n void focusIndex(target)\n else\n roving.reset()\n})\n\nfunction onClick(event: MouseEvent, tab: GrTab, index: number): void {\n if (!isEnabled(tab))\n return\n\n // Крестик — не кнопка, поэтому его клик приходит сюда же и разбирается по цели.\n if (event.target instanceof Element && event.target.closest('[data-gr-tab-close]')) {\n requestClose(index)\n return\n }\n\n roving.setActive(index)\n\n if (tab.value !== props.modelValue)\n emit('update:modelValue', tab.value)\n}\n\nif (__GR_DEV__) {\n watchEffect(() => {\n if (props.modelValue === undefined) {\n console.warn(\n `[granularity] GrTabs: обязательный проп \\`modelValue\\` не передан — получено ${String(props.modelValue)}.`,\n )\n }\n\n if (!Array.isArray(props.tabs)) {\n console.warn(\n `[granularity] GrTabs: обязательный проп \\`tabs\\` должен быть массивом — получено ${String(props.tabs)}.`,\n )\n }\n })\n}\n</script>\n\n<template>\n <div v-if=\"isEmpty\" data-gr-tabs-empty :class=\"emptyClass\">\n <slot name=\"empty\">\n {{ resolvedEmptyText }}\n </slot>\n </div>\n\n <div\n v-else\n ref=\"tablistEl\"\n role=\"tablist\"\n data-gr-tabs\n :aria-orientation=\"orientation\"\n :data-overflow=\"orientation === 'vertical' ? undefined : scrollOverflow\"\n :class=\"tablistClass\"\n @keydown=\"onKeydown\"\n @scroll.passive=\"scheduleMeasure\"\n >\n <button\n v-for=\"(tab, index) in tabs\"\n :id=\"idBase ? `${idBase}-tab-${tab.value}` : undefined\"\n :key=\"tab.value\"\n :ref=\"el => setButtonRef(el, index)\"\n type=\"button\"\n role=\"tab\"\n data-gr-tab\n :aria-controls=\"idBase ? `${idBase}-panel-${tab.value}` : undefined\"\n :aria-selected=\"tab.value === modelValue ? 'true' : 'false'\"\n :aria-disabled=\"tab.disabled ? 'true' : undefined\"\n :aria-keyshortcuts=\"isClosable(tab) ? 'Delete' : undefined\"\n :tabindex=\"roving.tabindexFor(index)\"\n :class=\"tabClass(tab)\"\n @click=\"onClick($event, tab, index)\"\n >\n <span :class=\"tabContentClass\">\n <slot\n name=\"tab\"\n :tab=\"tab\"\n :active=\"tab.value === modelValue\"\n :disabled=\"Boolean(tab.disabled)\"\n >\n <component\n :is=\"iconTag(tab.icon)\"\n v-if=\"tab.icon\"\n :class=\"[tabIconClass, iconClass(tab.icon)]\"\n aria-hidden=\"true\"\n />\n <span>{{ tab.label }}</span>\n <span v-if=\"tab.badge\" :class=\"badgeClass\">\n {{ tab.badge }}\n </span>\n </slot>\n\n <!-- Снаружи слота: своя разметка вкладки не должна отнимать закрытие. -->\n <span\n v-if=\"isClosable(tab)\"\n data-gr-tab-close\n :class=\"closeClass\"\n :title=\"closeTitle\"\n aria-hidden=\"true\"\n >\n <IconX :class=\"closeIconClass\" />\n </span>\n </span>\n </button>\n </div>\n</template>\n\n<style>\n/*\n * Затухание у края, за которым ряд продолжается.\n *\n * Здесь, а не утилитами, по двум причинам сразу. `mask-image` в `presetMini` не\n * выражается — это уже зафиксировано отказом в `GrImageCrop`. И маской, а не\n * градиентом-подложкой, потому что фон полосы у вариантов разный: `pills` несёт\n * свой непрозрачный `--gr-muted`, а `line` прозрачен и лежит на фоне родителя,\n * которого компонент не знает. Градиенту пришлось бы красить в цвет подложки —\n * для `line` его неоткуда взять. Маска гасит содержимое независимо от того, что\n * под ним.\n *\n * Состояние считается логически (начало/конец ряда), а направление градиента в\n * CSS выражается только физически — отсюда зеркальные правила для RTL.\n */\n\n[data-gr-tabs] {\n /*\n * `scrollIntoView({ inline: 'nearest' })` из кольца фокуса прижимает вкладку\n * вплотную к краю. Без отступа прокрутки кольцо фокуса у клавиатурного\n * пользователя оказалось бы ровно под затуханием.\n */\n scroll-padding-inline: var(--gr-tabs-scroll-fade, 1.5rem);\n}\n\n[data-gr-tabs][data-overflow='start'] {\n --gr-tabs-mask: linear-gradient(to right, transparent 0, #000 var(--gr-tabs-scroll-fade, 1.5rem));\n}\n\n[data-gr-tabs][data-overflow='end'] {\n --gr-tabs-mask: linear-gradient(to right, #000 calc(100% - var(--gr-tabs-scroll-fade, 1.5rem)), transparent 100%);\n}\n\n[data-gr-tabs][data-overflow='both'] {\n --gr-tabs-mask: linear-gradient(\n to right,\n transparent 0,\n #000 var(--gr-tabs-scroll-fade, 1.5rem),\n #000 calc(100% - var(--gr-tabs-scroll-fade, 1.5rem)),\n transparent 100%\n );\n}\n\n[dir='rtl'] [data-gr-tabs][data-overflow='start'] {\n --gr-tabs-mask: linear-gradient(to left, transparent 0, #000 var(--gr-tabs-scroll-fade, 1.5rem));\n}\n\n[dir='rtl'] [data-gr-tabs][data-overflow='end'] {\n --gr-tabs-mask: linear-gradient(to left, #000 calc(100% - var(--gr-tabs-scroll-fade, 1.5rem)), transparent 100%);\n}\n\n[data-gr-tabs]:is([data-overflow='start'], [data-overflow='end'], [data-overflow='both']) {\n -webkit-mask-image: var(--gr-tabs-mask);\n mask-image: var(--gr-tabs-mask);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, nextTick, onBeforeUnmount, onMounted, ref, watch, watchEffect, type Component } from 'vue'\n\nimport IconX from '~icons/lucide/x'\n\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { useRovingFocus } from '../../composables/useRovingFocus'\nimport { iconClass, iconTag } from '../shared/icon'\nimport { resolveScrollOverflow, type GrScrollOverflow } from '../shared/scrollOverflow'\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\nimport {\n grTabsBadgeClass,\n grTabsCloseClass,\n grTabsEmptyClass,\n grTabsListClass,\n grTabsTabClass,\n tabCloseIconSizes,\n tabContentClass,\n tabIconClass,\n type GrTabsOrientation,\n type GrTabsSize,\n type GrTabsVariant,\n} from './grTabsStyles'\n\nexport interface GrTab {\n value: string\n label: string\n badge?: string\n /**\n * Иконка слева от подписи: Vue-компонент либо класс иконки вашей UnoCSS-сборки\n * (`'i-lucide-user'` — тогда нужен ваш `presetIcons`, см. `docs/installation.md`).\n */\n icon?: string | Component\n disabled?: boolean\n /** Перекрывает `closable` ряда в обе стороны: закрепить вкладку или закрыть одну её. */\n closable?: boolean\n}\n\nexport interface GrTabsProps {\n modelValue: string\n tabs: GrTab[]\n /**\n * База id для ARIA-связки с `GrTabPanels`. Если задана, каждая вкладка\n * получает `id=\"<idBase>-tab-<value>\"` и `aria-controls=\"<idBase>-panel-<value>\"`.\n * Передайте тот же `idBase` в `GrTabPanels`, чтобы связать `tab`↔`tabpanel`.\n */\n idBase?: string\n size?: GrTabsSize\n /** Вид ряда: обойма с таблетками или ряд с подчёркиванием. */\n variant?: GrTabsVariant\n /**\n * `automatic` (по умолчанию) — стрелка сразу переключает вкладку;\n * `manual` — стрелка двигает только фокус, выбор подтверждается\n * `Enter`/`Space`. Второй режим для вкладок с тяжёлой загрузкой: перебор\n * стрелками иначе тянет каждую панель.\n */\n activationMode?: GrTabsActivationMode\n /** Горизонтальный (по умолчанию) или вертикальный список вкладок. */\n orientation?: GrTabsOrientation\n /**\n * Крестик на вкладках и закрытие по `Delete`/`Backspace`. Точечно снимается\n * `closable: false` у самой вкладки.\n */\n closable?: boolean\n /** Текст, когда список вкладок пуст. Слот `#empty` сильнее. */\n emptyText?: string\n}\n\nexport interface GrTabsEmits {\n (e: 'update:modelValue', value: string): void\n /**\n * Просьба закрыть вкладку. Компонент список не трогает: `tabs` — проп, и\n * закрытие может не состояться («сохранить изменения?»).\n */\n (e: 'close', value: string): void\n}\n\nexport type GrTabsActivationMode = 'automatic' | 'manual'\n\nexport type { GrTabsOrientation, GrTabsVariant } from './grTabsStyles'\n\n/**\n * GrTabs — горизонтальная группа вкладок с паттерном WAI-ARIA `tablist`.\n *\n * A11y:\n * - `role=\"tablist\"` на корне, `role=\"tab\"` на каждой вкладке.\n * - Roving `tabindex`: активная вкладка `0`, остальные `-1`.\n * - Клавиатура: `ArrowLeft`/`ArrowRight` — цикличный переход, `Home`/`End` — к первой/последней.\n * - При переключении стрелками DOM-фокус программно переносится на новую вкладку.\n *\n * Сам компонент не рендерит `tabpanel` — это ответственность консьюмера (по `aria-controls`/внешней разметке).\n */\nconst props = withDefaults(defineProps<GrTabsProps>(), {\n idBase: undefined,\n size: undefined,\n // Дефолт живёт в резолвере: Vue подставил бы свой раньше, чем компонент\n // заглянет в `GrConfigProvider`.\n variant: undefined,\n activationMode: 'automatic',\n orientation: 'horizontal',\n closable: false,\n emptyText: undefined,\n})\n\ndefineSlots<{\n /** Содержимое вкладки целиком — вместо подписи, иконки и счётчика. Крестик остаётся. */\n tab?: (props: { tab: GrTab, active: boolean, disabled: boolean }) => unknown\n /** Пустой ряд — вместо текста из локали. */\n empty?: () => unknown\n}>()\n\nconst emit = defineEmits<GrTabsEmits>()\n\nconst { t } = useGranularityTranslations()\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrTabs' })\nconst resolvedVariant = useGrComponentProp('GrTabs', 'variant', () => props.variant, 'pills')\n\nconst tablistClass = computed(() => grTabsListClass({\n variant: resolvedVariant.value,\n size: resolvedSize.value,\n orientation: props.orientation,\n}))\n\nfunction tabClass(tab: GrTab): string {\n return grTabsTabClass({\n variant: resolvedVariant.value,\n size: resolvedSize.value,\n active: tab.value === props.modelValue,\n disabled: Boolean(tab.disabled),\n })\n}\n\nconst badgeClass = computed(() => grTabsBadgeClass(resolvedSize.value))\nconst closeClass = computed(() => grTabsCloseClass(resolvedSize.value))\nconst closeIconClass = computed(() => tabCloseIconSizes[resolvedSize.value])\nconst emptyClass = computed(() => grTabsEmptyClass(resolvedSize.value))\n\n/**\n * Пустой ряд — не пустой `tablist`: роль обязана владеть потомками `tab`,\n * и текст внутри неё был бы нарушением, а не пустым состоянием. Поэтому\n * пустое состояние рисуется отдельной веткой, а не содержимым `tablist`.\n */\nconst isEmpty = computed(() => props.tabs.length === 0)\nconst resolvedEmptyText = computed(() => props.emptyText ?? t('gr.tabs.empty', 'Nothing here yet'))\nconst closeTitle = computed(() => t('gr.tabs.close', 'Close tab'))\n\n/** Отключённая вкладка не принимает взаимодействия, а закрытие — тоже взаимодействие. */\nfunction isClosable(tab: GrTab): boolean {\n return !tab.disabled && (tab.closable ?? props.closable)\n}\n\nconst buttonRefs = ref<HTMLButtonElement[]>([])\n\n/**\n * Vue зовёт ref-функцию с `null`, когда узел исчез. Игнорировать такой вызов\n * нельзя: в массиве оставались отсоединённые от DOM кнопки, и `focus()` по\n * сократившемуся списку молча проваливался в `<body>`.\n */\nfunction setButtonRef(el: unknown, index: number): void {\n if (el instanceof HTMLButtonElement)\n buttonRefs.value[index] = el\n else\n delete buttonRefs.value[index]\n\n if (buttonRefs.value.length > props.tabs.length)\n buttonRefs.value.length = props.tabs.length\n}\n\nconst activeIndex = computed(() => props.tabs.findIndex(t => t.value === props.modelValue))\n\nfunction isEnabled(tab: GrTab): boolean {\n return !tab.disabled\n}\n\nconst tabIndexes = computed(() => props.tabs.map((_, index) => index))\n\n/**\n * Кольцо roving-фокуса. При `activationMode: 'manual'` фокус уезжает вперёд\n * выбора — это и есть разница между `activeKey` примитива и `initialKey`:\n * первый ведёт фокус, второй подставляет выбранную вкладку, пока фокус не\n * трогали.\n *\n * Выбор стрелка **не** переносит: этим заведует `onKeydown` по\n * `activationMode`. Через `onMove` это выразить нельзя — клик тоже зовёт\n * `focusKey`, и выбор эмитился бы дважды.\n */\nconst roving = useRovingFocus<number>({\n items: () => tabIndexes.value,\n elementFor: index => buttonRefs.value[index],\n isDisabled: index => Boolean(props.tabs[index]?.disabled),\n orientation: () => (props.orientation === 'vertical' ? 'vertical' : 'horizontal'),\n skipDisabled: () => true,\n /**\n * Пока фокус не трогали, остановку `Tab` держит выбранная вкладка. Если\n * `modelValue` не совпал ни с одной (пустое начальное значение, асинхронный\n * список, удалённая активная вкладка), примитив ставит её на первую\n * доступную — иначе tablist выпал бы из обхода целиком и молча.\n *\n * Выключенность здесь не проверяется намеренно: на кнопке `aria-disabled`, а\n * не нативный `disabled`, поэтому выключенная вкладка фокусируется и держать\n * остановку вправе.\n */\n initialKey: () => (activeIndex.value >= 0 ? activeIndex.value : undefined),\n /**\n * Прокрутка до фокуса, а не после: иначе браузер доскроллит сам по-своему, и\n * ряд дёрнется дважды. Синхронно — фокус обязан остаться в том же такте, что\n * и раньше: тик, где он нужен, добавляет `focusIndex`.\n */\n beforeFocus: index => scrollIntoView(index),\n})\n\n/**\n * Фокус после перерисовки — для путей, где список только что изменился\n * (закрытие вкладки). Клавиатура идёт через примитив напрямую: там ряд уже\n * отрисован, и лишний тик только отложил бы фокус.\n */\nasync function focusIndex(index: number): Promise<void> {\n await nextTick()\n await roving.focusKey(index)\n}\n\n/**\n * Ряд прокручивается, а не переносится, поэтому активная вкладка может уехать\n * за край — например, когда её выбрали снаружи. `scrollIntoView` нет в jsdom и\n * в старых движках, поэтому вызов необязательный.\n */\nfunction scrollIntoView(index: number): void {\n buttonRefs.value[index]?.scrollIntoView?.({ block: 'nearest', inline: 'nearest' })\n}\n\nwatch(() => props.modelValue, async () => {\n await nextTick()\n if (activeIndex.value >= 0)\n scrollIntoView(activeIndex.value)\n})\n\n/**\n * Признак того, что ряд продолжается за краем.\n *\n * Полоса прокрутки у ряда скрыта намеренно, и без этого признака продолжение\n * не выдаёт себя ничем: вкладки за краем достижимы стрелками, но о том, что они\n * там есть, догадаться нечем. Затухание рисует CSS по `data-overflow`.\n *\n * Вертикальный ряд не скроллится вовсе — он растёт вниз, — поэтому состояние\n * считается только для горизонтального.\n */\nconst tablistEl = ref<HTMLElement | null>(null)\nconst scrollOverflow = ref<GrScrollOverflow>('none')\n\nlet resizeObserver: ResizeObserver | null = null\nlet scheduled = false\n\nfunction measureOverflow(): void {\n const el = tablistEl.value\n\n if (!el || props.orientation === 'vertical') {\n scrollOverflow.value = 'none'\n return\n }\n\n scrollOverflow.value = resolveScrollOverflow(el.scrollLeft, el.scrollWidth, el.clientWidth)\n}\n\nfunction scheduleMeasure(): void {\n if (scheduled)\n return\n\n scheduled = true\n void nextTick(() => {\n scheduled = false\n measureOverflow()\n })\n}\n\nonMounted(() => {\n scheduleMeasure()\n\n // На сервере и в jsdom `ResizeObserver` отсутствует — измерять там нечего.\n if (typeof ResizeObserver === 'undefined')\n return\n\n resizeObserver = new ResizeObserver(scheduleMeasure)\n if (tablistEl.value)\n resizeObserver.observe(tablistEl.value)\n})\n\nonBeforeUnmount(() => {\n resizeObserver?.disconnect()\n resizeObserver = null\n})\n\n// Состав ряда меняет его ширину, ориентация — само наличие прокрутки.\nwatch(() => [props.tabs.length, props.orientation], scheduleMeasure)\n\nasync function selectByIndex(index: number, focus = false): Promise<void> {\n const tab = props.tabs[index]\n if (!tab || !isEnabled(tab))\n return\n if (tab.value !== props.modelValue)\n emit('update:modelValue', tab.value)\n if (focus)\n await focusIndex(index)\n}\n\nfunction onKeydown(event: KeyboardEvent): void {\n if (props.tabs.length === 0)\n return\n\n const currentIndex = roving.rovingKey.value ?? 0\n\n switch (event.key) {\n case 'Enter':\n case ' ':\n // В ручном режиме выбор подтверждается явно — на той вкладке, где фокус.\n if (props.activationMode === 'manual') {\n event.preventDefault()\n void selectByIndex(currentIndex, false)\n }\n return\n case 'Delete':\n case 'Backspace': {\n // Единственный клавиатурный путь к закрытию: крестик в таб-порядке не стоит.\n const tab = props.tabs[currentIndex]\n if (tab && isClosable(tab)) {\n event.preventDefault()\n requestClose(currentIndex)\n }\n return\n }\n }\n\n // Стрелки, `Home`, `End` — фокус ведёт примитив по объявленной ориентации.\n if (!roving.handleNavigationKeys(event))\n return\n\n // `automatic`: выбор едет за фокусом. `manual`: ждёт `Enter`/`Space`.\n if (props.activationMode !== 'manual')\n void selectByIndex(roving.rovingKey.value ?? currentIndex, false)\n}\n\n/**\n * Закрытая вкладка, которая держала фокус. Компонент список не укорачивает —\n * это делает потребитель, — поэтому фокус возвращается только после того, как\n * `tabs` действительно стал короче.\n */\nlet pendingClose: { index: number, value: string } | null = null\n\nfunction requestClose(index: number): void {\n const tab = props.tabs[index]\n if (!tab || !isClosable(tab))\n return\n\n const held = buttonRefs.value[index] === document.activeElement\n pendingClose = held ? { index, value: tab.value } : null\n\n emit('close', tab.value)\n}\n\n// Без этого клавиатурное закрытие роняет фокус в `<body>`: кнопка, на которой\n// он стоял, исчезает из DOM вместе со вкладкой.\nwatch(() => props.tabs.length, async (next, prev) => {\n const pending = pendingClose\n pendingClose = null\n\n if (!pending || next >= prev)\n return\n // Ушла не та вкладка — потребитель поменял список по своей причине.\n if (props.tabs.some(tab => tab.value === pending.value))\n return\n\n await nextTick()\n\n const target = Math.min(pending.index, next - 1)\n if (target >= 0)\n void focusIndex(target)\n else\n roving.reset()\n})\n\nfunction onClick(event: MouseEvent, tab: GrTab, index: number): void {\n if (!isEnabled(tab))\n return\n\n // Крестик — не кнопка, поэтому его клик приходит сюда же и разбирается по цели.\n if (event.target instanceof Element && event.target.closest('[data-gr-tab-close]')) {\n requestClose(index)\n return\n }\n\n roving.setActive(index)\n\n if (tab.value !== props.modelValue)\n emit('update:modelValue', tab.value)\n}\n\nif (__GR_DEV__) {\n watchEffect(() => {\n if (props.modelValue === undefined) {\n console.warn(\n `[granularity] GrTabs: обязательный проп \\`modelValue\\` не передан — получено ${String(props.modelValue)}.`,\n )\n }\n\n if (!Array.isArray(props.tabs)) {\n console.warn(\n `[granularity] GrTabs: обязательный проп \\`tabs\\` должен быть массивом — получено ${String(props.tabs)}.`,\n )\n }\n })\n}\n</script>\n\n<template>\n <div v-if=\"isEmpty\" data-gr-tabs-empty :class=\"emptyClass\">\n <slot name=\"empty\">\n {{ resolvedEmptyText }}\n </slot>\n </div>\n\n <div\n v-else\n ref=\"tablistEl\"\n role=\"tablist\"\n data-gr-tabs\n :aria-orientation=\"orientation\"\n :data-overflow=\"orientation === 'vertical' ? undefined : scrollOverflow\"\n :class=\"tablistClass\"\n @keydown=\"onKeydown\"\n @scroll.passive=\"scheduleMeasure\"\n >\n <button\n v-for=\"(tab, index) in tabs\"\n :id=\"idBase ? `${idBase}-tab-${tab.value}` : undefined\"\n :key=\"tab.value\"\n :ref=\"el => setButtonRef(el, index)\"\n type=\"button\"\n role=\"tab\"\n data-gr-tab\n :aria-controls=\"idBase ? `${idBase}-panel-${tab.value}` : undefined\"\n :aria-selected=\"tab.value === modelValue ? 'true' : 'false'\"\n :aria-disabled=\"tab.disabled ? 'true' : undefined\"\n :aria-keyshortcuts=\"isClosable(tab) ? 'Delete' : undefined\"\n :tabindex=\"roving.tabindexFor(index)\"\n :class=\"tabClass(tab)\"\n @click=\"onClick($event, tab, index)\"\n >\n <span :class=\"tabContentClass\">\n <slot\n name=\"tab\"\n :tab=\"tab\"\n :active=\"tab.value === modelValue\"\n :disabled=\"Boolean(tab.disabled)\"\n >\n <component\n :is=\"iconTag(tab.icon)\"\n v-if=\"tab.icon\"\n :class=\"[tabIconClass, iconClass(tab.icon)]\"\n aria-hidden=\"true\"\n />\n <span>{{ tab.label }}</span>\n <span v-if=\"tab.badge\" :class=\"badgeClass\">\n {{ tab.badge }}\n </span>\n </slot>\n\n <!-- Снаружи слота: своя разметка вкладки не должна отнимать закрытие. -->\n <span\n v-if=\"isClosable(tab)\"\n data-gr-tab-close\n :class=\"closeClass\"\n :title=\"closeTitle\"\n aria-hidden=\"true\"\n >\n <IconX :class=\"closeIconClass\" />\n </span>\n </span>\n </button>\n </div>\n</template>\n\n<style>\n/*\n * Затухание у края, за которым ряд продолжается.\n *\n * Здесь, а не утилитами, по двум причинам сразу. `mask-image` в `presetMini` не\n * выражается — это уже зафиксировано отказом в `GrImageCrop`. И маской, а не\n * градиентом-подложкой, потому что фон полосы у вариантов разный: `pills` несёт\n * свой непрозрачный `--gr-muted`, а `line` прозрачен и лежит на фоне родителя,\n * которого компонент не знает. Градиенту пришлось бы красить в цвет подложки —\n * для `line` его неоткуда взять. Маска гасит содержимое независимо от того, что\n * под ним.\n *\n * Состояние считается логически (начало/конец ряда), а направление градиента в\n * CSS выражается только физически — отсюда зеркальные правила для RTL.\n */\n\n[data-gr-tabs] {\n /*\n * `scrollIntoView({ inline: 'nearest' })` из кольца фокуса прижимает вкладку\n * вплотную к краю. Без отступа прокрутки кольцо фокуса у клавиатурного\n * пользователя оказалось бы ровно под затуханием.\n */\n scroll-padding-inline: var(--gr-tabs-scroll-fade, 1.5rem);\n}\n\n[data-gr-tabs][data-overflow='start'] {\n --gr-tabs-mask: linear-gradient(to right, transparent 0, #000 var(--gr-tabs-scroll-fade, 1.5rem));\n}\n\n[data-gr-tabs][data-overflow='end'] {\n --gr-tabs-mask: linear-gradient(to right, #000 calc(100% - var(--gr-tabs-scroll-fade, 1.5rem)), transparent 100%);\n}\n\n[data-gr-tabs][data-overflow='both'] {\n --gr-tabs-mask: linear-gradient(\n to right,\n transparent 0,\n #000 var(--gr-tabs-scroll-fade, 1.5rem),\n #000 calc(100% - var(--gr-tabs-scroll-fade, 1.5rem)),\n transparent 100%\n );\n}\n\n[dir='rtl'] [data-gr-tabs][data-overflow='start'] {\n --gr-tabs-mask: linear-gradient(to left, transparent 0, #000 var(--gr-tabs-scroll-fade, 1.5rem));\n}\n\n[dir='rtl'] [data-gr-tabs][data-overflow='end'] {\n --gr-tabs-mask: linear-gradient(to left, #000 calc(100% - var(--gr-tabs-scroll-fade, 1.5rem)), transparent 100%);\n}\n\n[data-gr-tabs]:is([data-overflow='start'], [data-overflow='end'], [data-overflow='both']) {\n -webkit-mask-image: var(--gr-tabs-mask);\n mask-image: var(--gr-tabs-mask);\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4FA,MAAM,QAAQ;EAmBd,MAAM,OAAO;EAEb,MAAM,EAAE,MAAM,2BAA2B;EAEzC,MAAM,eAAe,yBAAyB,MAAM,MAAM,EAAE,WAAW,SAAS,CAAC;EACjF,MAAM,kBAAkB,mBAAmB,UAAU,iBAAiB,MAAM,SAAS,OAAO;EAE5F,MAAM,eAAe,eAAe,gBAAgB;GAClD,SAAS,gBAAgB;GACzB,MAAM,aAAa;GACnB,aAAa,MAAM;EACrB,CAAC,CAAC;EAEF,SAAS,SAAS,KAAoB;GACpC,OAAO,eAAe;IACpB,SAAS,gBAAgB;IACzB,MAAM,aAAa;IACnB,QAAQ,IAAI,UAAU,MAAM;IAC5B,UAAU,QAAQ,IAAI,QAAQ;GAChC,CAAC;EACH;EAEA,MAAM,aAAa,eAAe,iBAAiB,aAAa,KAAK,CAAC;EACtE,MAAM,aAAa,eAAe,iBAAiB,aAAa,KAAK,CAAC;EACtE,MAAM,iBAAiB,eAAe,kBAAkB,aAAa,MAAM;EAC3E,MAAM,aAAa,eAAe,iBAAiB,aAAa,KAAK,CAAC;;;;;;EAOtE,MAAM,UAAU,eAAe,MAAM,KAAK,WAAW,CAAC;EACtD,MAAM,oBAAoB,eAAe,MAAM,aAAa,EAAE,iBAAiB,kBAAkB,CAAC;EAClG,MAAM,aAAa,eAAe,EAAE,iBAAiB,WAAW,CAAC;;EAGjE,SAAS,WAAW,KAAqB;GACvC,OAAO,CAAC,IAAI,aAAa,IAAI,YAAY,MAAM;EACjD;EAEA,MAAM,aAAa,IAAyB,CAAC,CAAC;;;;;;EAO9C,SAAS,aAAa,IAAa,OAAqB;GACtD,IAAI,cAAc,mBAChB,WAAW,MAAM,SAAS;QAE1B,OAAO,WAAW,MAAM;GAE1B,IAAI,WAAW,MAAM,SAAS,MAAM,KAAK,QACvC,WAAW,MAAM,SAAS,MAAM,KAAK;EACzC;EAEA,MAAM,cAAc,eAAe,MAAM,KAAK,WAAU,MAAK,EAAE,UAAU,MAAM,UAAU,CAAC;EAE1F,SAAS,UAAU,KAAqB;GACtC,OAAO,CAAC,IAAI;EACd;EAEA,MAAM,aAAa,eAAe,MAAM,KAAK,KAAK,GAAG,UAAU,KAAK,CAAC;;;;;;;;;;;EAYrE,MAAM,SAAS,eAAuB;GACpC,aAAa,WAAW;GACxB,aAAY,UAAS,WAAW,MAAM;GACtC,aAAY,UAAS,QAAQ,MAAM,KAAK,MAAM,EAAE,QAAQ;GACxD,mBAAoB,MAAM,gBAAgB,aAAa,aAAa;GACpE,oBAAoB;;;;;;;;;;;GAWpB,kBAAmB,YAAY,SAAS,IAAI,YAAY,QAAQ,KAAA;;;;;;GAMhE,cAAa,UAAS,eAAe,KAAK;EAC5C,CAAC;;;;;;EAOD,eAAe,WAAW,OAA8B;GACtD,MAAM,SAAS;GACf,MAAM,OAAO,SAAS,KAAK;EAC7B;;;;;;EAOA,SAAS,eAAe,OAAqB;GAC3C,WAAW,MAAM,MAAM,EAAE,iBAAiB;IAAE,OAAO;IAAW,QAAQ;GAAU,CAAC;EACnF;EAEA,YAAY,MAAM,YAAY,YAAY;GACxC,MAAM,SAAS;GACf,IAAI,YAAY,SAAS,GACvB,eAAe,YAAY,KAAK;EACpC,CAAC;;;;;;;;;;;EAYD,MAAM,YAAY,IAAwB,IAAI;EAC9C,MAAM,iBAAiB,IAAsB,MAAM;EAEnD,IAAI,iBAAwC;EAC5C,IAAI,YAAY;EAEhB,SAAS,kBAAwB;GAC/B,MAAM,KAAK,UAAU;GAErB,IAAI,CAAC,MAAM,MAAM,gBAAgB,YAAY;IAC3C,eAAe,QAAQ;IACvB;GACF;GAEA,eAAe,QAAQ,sBAAsB,GAAG,YAAY,GAAG,aAAa,GAAG,WAAW;EAC5F;EAEA,SAAS,kBAAwB;GAC/B,IAAI,WACF;GAEF,YAAY;GACZ,eAAoB;IAClB,YAAY;IACZ,gBAAgB;GAClB,CAAC;EACH;EAEA,gBAAgB;GACd,gBAAgB;GAGhB,IAAI,OAAO,mBAAmB,aAC5B;GAEF,iBAAiB,IAAI,eAAe,eAAe;GACnD,IAAI,UAAU,OACZ,eAAe,QAAQ,UAAU,KAAK;EAC1C,CAAC;EAED,sBAAsB;GACpB,gBAAgB,WAAW;GAC3B,iBAAiB;EACnB,CAAC;EAGD,YAAY,CAAC,MAAM,KAAK,QAAQ,MAAM,WAAW,GAAG,eAAe;EAEnE,eAAe,cAAc,OAAe,QAAQ,OAAsB;GACxE,MAAM,MAAM,MAAM,KAAK;GACvB,IAAI,CAAC,OAAO,CAAC,UAAU,GAAG,GACxB;GACF,IAAI,IAAI,UAAU,MAAM,YACtB,KAAK,qBAAqB,IAAI,KAAK;GACrC,IAAI,OACF,MAAM,WAAW,KAAK;EAC1B;EAEA,SAAS,UAAU,OAA4B;GAC7C,IAAI,MAAM,KAAK,WAAW,GACxB;GAEF,MAAM,eAAe,OAAO,UAAU,SAAS;GAE/C,QAAQ,MAAM,KAAd;IACE,KAAK;IACL,KAAK;KAEH,IAAI,MAAM,mBAAmB,UAAU;MACrC,MAAM,eAAe;MACrB,cAAmB,cAAc,KAAK;KACxC;KACA;IACF,KAAK;IACL,KAAK,aAAa;KAEhB,MAAM,MAAM,MAAM,KAAK;KACvB,IAAI,OAAO,WAAW,GAAG,GAAG;MAC1B,MAAM,eAAe;MACrB,aAAa,YAAY;KAC3B;KACA;IACF;GACF;GAGA,IAAI,CAAC,OAAO,qBAAqB,KAAK,GACpC;GAGF,IAAI,MAAM,mBAAmB,UAC3B,cAAmB,OAAO,UAAU,SAAS,cAAc,KAAK;EACpE;;;;;;EAOA,IAAI,eAAwD;EAE5D,SAAS,aAAa,OAAqB;GACzC,MAAM,MAAM,MAAM,KAAK;GACvB,IAAI,CAAC,OAAO,CAAC,WAAW,GAAG,GACzB;GAGF,eADa,WAAW,MAAM,WAAW,SAAS,gBAC5B;IAAE;IAAO,OAAO,IAAI;GAAM,IAAI;GAEpD,KAAK,SAAS,IAAI,KAAK;EACzB;EAIA,YAAY,MAAM,KAAK,QAAQ,OAAO,MAAM,SAAS;GACnD,MAAM,UAAU;GAChB,eAAe;GAEf,IAAI,CAAC,WAAW,QAAQ,MACtB;GAEF,IAAI,MAAM,KAAK,MAAK,QAAO,IAAI,UAAU,QAAQ,KAAK,GACpD;GAEF,MAAM,SAAS;GAEf,MAAM,SAAS,KAAK,IAAI,QAAQ,OAAO,OAAO,CAAC;GAC/C,IAAI,UAAU,GACZ,WAAgB,MAAM;QAEtB,OAAO,MAAM;EACjB,CAAC;EAED,SAAS,QAAQ,OAAmB,KAAY,OAAqB;GACnE,IAAI,CAAC,UAAU,GAAG,GAChB;GAGF,IAAI,MAAM,kBAAkB,WAAW,MAAM,OAAO,QAAQ,qBAAqB,GAAG;IAClF,aAAa,KAAK;IAClB;GACF;GAEA,OAAO,UAAU,KAAK;GAEtB,IAAI,IAAI,UAAU,MAAM,YACtB,KAAK,qBAAqB,IAAI,KAAK;EACvC;EAEA,IAAA,QAAA,IAAA,aAAA,cACE,kBAAkB;GAChB,IAAI,MAAM,eAAe,KAAA,GACvB,QAAQ,KACN,gFAAgF,OAAO,MAAM,UAAU,EAAE,EAC3G;GAGF,IAAI,CAAC,MAAM,QAAQ,MAAM,IAAI,GAC3B,QAAQ,KACN,oFAAoF,OAAO,MAAM,IAAI,EAAE,EACzG;EAEJ,CAAC;;GAKU,OAAA,QAAA,SAAX,UAAA,GAAA,mBAIM,OAAA;;IAJc,sBAAA;IAAoB,OAAK,eAAE,WAAA,KAAU;GACvD,GAAA,CAAA,WAEO,KAAA,QAAA,SAAA,CAAA,SAAA,CADF,gBAAA,gBAAA,kBAAA,KAAiB,GAAA,CAAA,CAAA,CAAA,CAAA,GAAA,CAAA,MAIxB,UAAA,GAAA,mBA0DM,OAAA;;IAxDA,SAAA;IAAJ,KAAI;IACJ,MAAK;IACL,gBAAA;IACC,oBAAkB,QAAA;IAClB,iBAAe,QAAA,gBAAW,aAAkB,KAAA,IAAY,eAAA;IACxD,OAAK,eAAE,aAAA,KAAY;IACV;IACO,iBAAA;GAEjB,GAAA,EAAA,UAAA,IAAA,GAAA,mBA8CS,UAAA,MAAA,WA7CgB,QAAA,OAAf,KAAK,UAAK;IADpB,OAAA,UAAA,GAAA,mBA8CS,UAAA;KA5CN,IAAI,QAAA,SAAM,GAAM,QAAA,OAAM,OAAQ,IAAI,UAAU,KAAA;KAC5C,KAAK,IAAI;;KACT,MAAK,OAAM,aAAa,IAAI,KAAK;KAClC,MAAK;KACL,MAAK;KACL,eAAA;KACC,iBAAe,QAAA,SAAM,GAAM,QAAA,OAAM,SAAU,IAAI,UAAU,KAAA;KACzD,iBAAe,IAAI,UAAU,QAAA,aAAU,SAAA;KACvC,iBAAe,IAAI,WAAQ,SAAY,KAAA;KACvC,qBAAmB,WAAW,GAAG,IAAA,WAAe,KAAA;KAChD,UAAU,MAAA,MAAA,CAAM,CAAC,YAAY,KAAK;KAClC,OAAK,eAAE,SAAS,GAAG,CAAA;KACnB,UAAK,WAAE,QAAQ,QAAQ,KAAK,KAAK;IAElC,GAAA,CAAA,mBA6BO,QAAA,EA7BA,OAAK,eAAE,MAAA,eAAA,CAAe,EAAA,GAAA,CAC3B,WAgBO,KAAA,QAAA,OAAA;KAdC;KACL,QAAQ,IAAI,UAAU,QAAA;KACtB,UAAU,QAAQ,IAAI,QAAQ;IAJjC,SAgBO;KARG,IAAI,QAFZ,UAAA,GAAA,YAKE,wBAJK,MAAA,OAAA,CAAO,CAAC,IAAI,IAAI,CAAA,GAAA;;MAEpB,OAAK,eAAA,CAAG,MAAA,YAAA,GAAc,MAAA,SAAA,CAAS,CAAC,IAAI,IAAI,CAAA,CAAA;MACzC,eAAY;;KAEd,mBAA4B,QAAA,MAAA,gBAAnB,IAAI,KAAK,GAAA,CAAA;KACN,IAAI,SAAhB,UAAA,GAAA,mBAEO,QAAA;;MAFiB,OAAK,eAAE,WAAA,KAAU;KACpC,GAAA,gBAAA,IAAI,KAAK,GAAA,CAAA,KAAA,mBAAA,IAAA,IAAA;IAMR,CAAA,GAAA,WAAW,GAAG,KADtB,UAAA,GAAA,mBAQO,QAAA;;KANL,qBAAA;KACC,OAAK,eAAE,WAAA,KAAU;KACjB,OAAO,WAAA;KACR,eAAY;IAEZ,GAAA,CAAA,YAAiC,MAAA,SAAA,GAAA,EAAzB,OAAK,eAAE,eAAA,KAAc,EAAA,GAAA,MAAA,GAAA,CAAA,OAAA,CAAA,CAAA,GAAA,IAAA,UAAA,KAAA,mBAAA,IAAA,IAAA,CAAA,GAAA,CAAA,CAAA,GAAA,IAAA,UAAA"}
|
|
1
|
+
{"version":3,"file":"GrTabs-DVC6nfWB.js","names":[],"sources":["../../../../src/components/GrTabs/GrTabs.vue","../../../../src/components/GrTabs/GrTabs.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, nextTick, onBeforeUnmount, onMounted, ref, watch, watchEffect, type Component } from 'vue'\n\nimport IconX from '~icons/lucide/x'\n\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { useRovingFocus } from '../../composables/useRovingFocus'\nimport { iconClass, iconTag } from '../shared/icon'\nimport { resolveScrollOverflow, type GrScrollOverflow } from '../shared/scrollOverflow'\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\nimport {\n grTabsBadgeClass,\n grTabsCloseClass,\n grTabsEmptyClass,\n grTabsListClass,\n grTabsTabClass,\n tabCloseIconSizes,\n tabContentClass,\n tabIconClass,\n type GrTabsOrientation,\n type GrTabsSize,\n type GrTabsVariant,\n} from './grTabsStyles'\n\nexport interface GrTab {\n value: string\n label: string\n badge?: string\n /**\n * Иконка слева от подписи: Vue-компонент либо класс иконки вашей UnoCSS-сборки\n * (`'i-lucide-user'` — тогда нужен ваш `presetIcons`, см. `docs/installation.md`).\n */\n icon?: string | Component\n disabled?: boolean\n /** Перекрывает `closable` ряда в обе стороны: закрепить вкладку или закрыть одну её. */\n closable?: boolean\n}\n\nexport interface GrTabsProps {\n modelValue: string\n tabs: GrTab[]\n /**\n * База id для ARIA-связки с `GrTabPanels`. Если задана, каждая вкладка\n * получает `id=\"<idBase>-tab-<value>\"` и `aria-controls=\"<idBase>-panel-<value>\"`.\n * Передайте тот же `idBase` в `GrTabPanels`, чтобы связать `tab`↔`tabpanel`.\n */\n idBase?: string\n size?: GrTabsSize\n /** Вид ряда: обойма с таблетками или ряд с подчёркиванием. */\n variant?: GrTabsVariant\n /**\n * `automatic` (по умолчанию) — стрелка сразу переключает вкладку;\n * `manual` — стрелка двигает только фокус, выбор подтверждается\n * `Enter`/`Space`. Второй режим для вкладок с тяжёлой загрузкой: перебор\n * стрелками иначе тянет каждую панель.\n */\n activationMode?: GrTabsActivationMode\n /** Горизонтальный (по умолчанию) или вертикальный список вкладок. */\n orientation?: GrTabsOrientation\n /**\n * Крестик на вкладках и закрытие по `Delete`/`Backspace`. Точечно снимается\n * `closable: false` у самой вкладки.\n */\n closable?: boolean\n /** Текст, когда список вкладок пуст. Слот `#empty` сильнее. */\n emptyText?: string\n}\n\nexport interface GrTabsEmits {\n (e: 'update:modelValue', value: string): void\n /**\n * Просьба закрыть вкладку. Компонент список не трогает: `tabs` — проп, и\n * закрытие может не состояться («сохранить изменения?»).\n */\n (e: 'close', value: string): void\n}\n\nexport type GrTabsActivationMode = 'automatic' | 'manual'\n\nexport type { GrTabsOrientation, GrTabsVariant } from './grTabsStyles'\n\n/**\n * GrTabs — горизонтальная группа вкладок с паттерном WAI-ARIA `tablist`.\n *\n * A11y:\n * - `role=\"tablist\"` на корне, `role=\"tab\"` на каждой вкладке.\n * - Roving `tabindex`: активная вкладка `0`, остальные `-1`.\n * - Клавиатура: `ArrowLeft`/`ArrowRight` — цикличный переход, `Home`/`End` — к первой/последней.\n * - При переключении стрелками DOM-фокус программно переносится на новую вкладку.\n *\n * Сам компонент не рендерит `tabpanel` — это ответственность консьюмера (по `aria-controls`/внешней разметке).\n */\nconst props = withDefaults(defineProps<GrTabsProps>(), {\n idBase: undefined,\n size: undefined,\n // Дефолт живёт в резолвере: Vue подставил бы свой раньше, чем компонент\n // заглянет в `GrConfigProvider`.\n variant: undefined,\n activationMode: 'automatic',\n orientation: 'horizontal',\n closable: false,\n emptyText: undefined,\n})\n\ndefineSlots<{\n /** Содержимое вкладки целиком — вместо подписи, иконки и счётчика. Крестик остаётся. */\n tab?: (props: { tab: GrTab, active: boolean, disabled: boolean }) => unknown\n /** Пустой ряд — вместо текста из локали. */\n empty?: () => unknown\n}>()\n\nconst emit = defineEmits<GrTabsEmits>()\n\nconst { t } = useGranularityTranslations()\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrTabs' })\nconst resolvedVariant = useGrComponentProp('GrTabs', 'variant', () => props.variant, 'pills')\n\nconst tablistClass = computed(() => grTabsListClass({\n variant: resolvedVariant.value,\n size: resolvedSize.value,\n orientation: props.orientation,\n}))\n\nfunction tabClass(tab: GrTab): string {\n return grTabsTabClass({\n variant: resolvedVariant.value,\n size: resolvedSize.value,\n active: tab.value === props.modelValue,\n disabled: Boolean(tab.disabled),\n })\n}\n\nconst badgeClass = computed(() => grTabsBadgeClass(resolvedSize.value))\nconst closeClass = computed(() => grTabsCloseClass(resolvedSize.value))\nconst closeIconClass = computed(() => tabCloseIconSizes[resolvedSize.value])\nconst emptyClass = computed(() => grTabsEmptyClass(resolvedSize.value))\n\n/**\n * Пустой ряд — не пустой `tablist`: роль обязана владеть потомками `tab`,\n * и текст внутри неё был бы нарушением, а не пустым состоянием. Поэтому\n * пустое состояние рисуется отдельной веткой, а не содержимым `tablist`.\n */\nconst isEmpty = computed(() => props.tabs.length === 0)\nconst resolvedEmptyText = computed(() => props.emptyText ?? t('gr.tabs.empty', 'Nothing here yet'))\nconst closeTitle = computed(() => t('gr.tabs.close', 'Close tab'))\n\n/** Отключённая вкладка не принимает взаимодействия, а закрытие — тоже взаимодействие. */\nfunction isClosable(tab: GrTab): boolean {\n return !tab.disabled && (tab.closable ?? props.closable)\n}\n\nconst buttonRefs = ref<HTMLButtonElement[]>([])\n\n/**\n * Vue зовёт ref-функцию с `null`, когда узел исчез. Игнорировать такой вызов\n * нельзя: в массиве оставались отсоединённые от DOM кнопки, и `focus()` по\n * сократившемуся списку молча проваливался в `<body>`.\n */\nfunction setButtonRef(el: unknown, index: number): void {\n if (el instanceof HTMLButtonElement)\n buttonRefs.value[index] = el\n else\n delete buttonRefs.value[index]\n\n if (buttonRefs.value.length > props.tabs.length)\n buttonRefs.value.length = props.tabs.length\n}\n\nconst activeIndex = computed(() => props.tabs.findIndex(t => t.value === props.modelValue))\n\nfunction isEnabled(tab: GrTab): boolean {\n return !tab.disabled\n}\n\nconst tabIndexes = computed(() => props.tabs.map((_, index) => index))\n\n/**\n * Кольцо roving-фокуса. При `activationMode: 'manual'` фокус уезжает вперёд\n * выбора — это и есть разница между `activeKey` примитива и `initialKey`:\n * первый ведёт фокус, второй подставляет выбранную вкладку, пока фокус не\n * трогали.\n *\n * Выбор стрелка **не** переносит: этим заведует `onKeydown` по\n * `activationMode`. Через `onMove` это выразить нельзя — клик тоже зовёт\n * `focusKey`, и выбор эмитился бы дважды.\n */\nconst roving = useRovingFocus<number>({\n items: () => tabIndexes.value,\n elementFor: index => buttonRefs.value[index],\n isDisabled: index => Boolean(props.tabs[index]?.disabled),\n orientation: () => (props.orientation === 'vertical' ? 'vertical' : 'horizontal'),\n skipDisabled: () => true,\n /**\n * Пока фокус не трогали, остановку `Tab` держит выбранная вкладка. Если\n * `modelValue` не совпал ни с одной (пустое начальное значение, асинхронный\n * список, удалённая активная вкладка), примитив ставит её на первую\n * доступную — иначе tablist выпал бы из обхода целиком и молча.\n *\n * Выключенность здесь не проверяется намеренно: на кнопке `aria-disabled`, а\n * не нативный `disabled`, поэтому выключенная вкладка фокусируется и держать\n * остановку вправе.\n */\n initialKey: () => (activeIndex.value >= 0 ? activeIndex.value : undefined),\n /**\n * Прокрутка до фокуса, а не после: иначе браузер доскроллит сам по-своему, и\n * ряд дёрнется дважды. Синхронно — фокус обязан остаться в том же такте, что\n * и раньше: тик, где он нужен, добавляет `focusIndex`.\n */\n beforeFocus: index => scrollIntoView(index),\n})\n\n/**\n * Фокус после перерисовки — для путей, где список только что изменился\n * (закрытие вкладки). Клавиатура идёт через примитив напрямую: там ряд уже\n * отрисован, и лишний тик только отложил бы фокус.\n */\nasync function focusIndex(index: number): Promise<void> {\n await nextTick()\n await roving.focusKey(index)\n}\n\n/**\n * Ряд прокручивается, а не переносится, поэтому активная вкладка может уехать\n * за край — например, когда её выбрали снаружи. `scrollIntoView` нет в jsdom и\n * в старых движках, поэтому вызов необязательный.\n */\nfunction scrollIntoView(index: number): void {\n buttonRefs.value[index]?.scrollIntoView?.({ block: 'nearest', inline: 'nearest' })\n}\n\nwatch(() => props.modelValue, async () => {\n await nextTick()\n if (activeIndex.value >= 0)\n scrollIntoView(activeIndex.value)\n})\n\n/**\n * Признак того, что ряд продолжается за краем.\n *\n * Полоса прокрутки у ряда скрыта намеренно, и без этого признака продолжение\n * не выдаёт себя ничем: вкладки за краем достижимы стрелками, но о том, что они\n * там есть, догадаться нечем. Затухание рисует CSS по `data-overflow`.\n *\n * Вертикальный ряд не скроллится вовсе — он растёт вниз, — поэтому состояние\n * считается только для горизонтального.\n */\nconst tablistEl = ref<HTMLElement | null>(null)\nconst scrollOverflow = ref<GrScrollOverflow>('none')\n\nlet resizeObserver: ResizeObserver | null = null\nlet scheduled = false\n\nfunction measureOverflow(): void {\n const el = tablistEl.value\n\n if (!el || props.orientation === 'vertical') {\n scrollOverflow.value = 'none'\n return\n }\n\n scrollOverflow.value = resolveScrollOverflow(el.scrollLeft, el.scrollWidth, el.clientWidth)\n}\n\nfunction scheduleMeasure(): void {\n if (scheduled)\n return\n\n scheduled = true\n void nextTick(() => {\n scheduled = false\n measureOverflow()\n })\n}\n\nonMounted(() => {\n scheduleMeasure()\n\n // На сервере и в jsdom `ResizeObserver` отсутствует — измерять там нечего.\n if (typeof ResizeObserver === 'undefined')\n return\n\n resizeObserver = new ResizeObserver(scheduleMeasure)\n if (tablistEl.value)\n resizeObserver.observe(tablistEl.value)\n})\n\nonBeforeUnmount(() => {\n resizeObserver?.disconnect()\n resizeObserver = null\n})\n\n// Состав ряда меняет его ширину, ориентация — само наличие прокрутки.\nwatch(() => [props.tabs.length, props.orientation], scheduleMeasure)\n\nasync function selectByIndex(index: number, focus = false): Promise<void> {\n const tab = props.tabs[index]\n if (!tab || !isEnabled(tab))\n return\n if (tab.value !== props.modelValue)\n emit('update:modelValue', tab.value)\n if (focus)\n await focusIndex(index)\n}\n\nfunction onKeydown(event: KeyboardEvent): void {\n if (props.tabs.length === 0)\n return\n\n const currentIndex = roving.rovingKey.value ?? 0\n\n switch (event.key) {\n case 'Enter':\n case ' ':\n // В ручном режиме выбор подтверждается явно — на той вкладке, где фокус.\n if (props.activationMode === 'manual') {\n event.preventDefault()\n void selectByIndex(currentIndex, false)\n }\n return\n case 'Delete':\n case 'Backspace': {\n // Единственный клавиатурный путь к закрытию: крестик в таб-порядке не стоит.\n const tab = props.tabs[currentIndex]\n if (tab && isClosable(tab)) {\n event.preventDefault()\n requestClose(currentIndex)\n }\n return\n }\n }\n\n // Стрелки, `Home`, `End` — фокус ведёт примитив по объявленной ориентации.\n if (!roving.handleNavigationKeys(event))\n return\n\n // `automatic`: выбор едет за фокусом. `manual`: ждёт `Enter`/`Space`.\n if (props.activationMode !== 'manual')\n void selectByIndex(roving.rovingKey.value ?? currentIndex, false)\n}\n\n/**\n * Закрытая вкладка, которая держала фокус. Компонент список не укорачивает —\n * это делает потребитель, — поэтому фокус возвращается только после того, как\n * `tabs` действительно стал короче.\n */\nlet pendingClose: { index: number, value: string } | null = null\n\nfunction requestClose(index: number): void {\n const tab = props.tabs[index]\n if (!tab || !isClosable(tab))\n return\n\n const held = buttonRefs.value[index] === document.activeElement\n pendingClose = held ? { index, value: tab.value } : null\n\n emit('close', tab.value)\n}\n\n// Без этого клавиатурное закрытие роняет фокус в `<body>`: кнопка, на которой\n// он стоял, исчезает из DOM вместе со вкладкой.\nwatch(() => props.tabs.length, async (next, prev) => {\n const pending = pendingClose\n pendingClose = null\n\n if (!pending || next >= prev)\n return\n // Ушла не та вкладка — потребитель поменял список по своей причине.\n if (props.tabs.some(tab => tab.value === pending.value))\n return\n\n await nextTick()\n\n const target = Math.min(pending.index, next - 1)\n if (target >= 0)\n void focusIndex(target)\n else\n roving.reset()\n})\n\nfunction onClick(event: MouseEvent, tab: GrTab, index: number): void {\n if (!isEnabled(tab))\n return\n\n // Крестик — не кнопка, поэтому его клик приходит сюда же и разбирается по цели.\n if (event.target instanceof Element && event.target.closest('[data-gr-tab-close]')) {\n requestClose(index)\n return\n }\n\n roving.setActive(index)\n\n if (tab.value !== props.modelValue)\n emit('update:modelValue', tab.value)\n}\n\nif (__GR_DEV__) {\n watchEffect(() => {\n if (props.modelValue === undefined) {\n console.warn(\n `[granularity] GrTabs: обязательный проп \\`modelValue\\` не передан — получено ${String(props.modelValue)}.`,\n )\n }\n\n if (!Array.isArray(props.tabs)) {\n console.warn(\n `[granularity] GrTabs: обязательный проп \\`tabs\\` должен быть массивом — получено ${String(props.tabs)}.`,\n )\n }\n })\n}\n</script>\n\n<template>\n <div v-if=\"isEmpty\" data-gr-tabs-empty :class=\"emptyClass\">\n <slot name=\"empty\">\n {{ resolvedEmptyText }}\n </slot>\n </div>\n\n <div\n v-else\n ref=\"tablistEl\"\n role=\"tablist\"\n data-gr-tabs\n :aria-orientation=\"orientation\"\n :data-overflow=\"orientation === 'vertical' ? undefined : scrollOverflow\"\n :class=\"tablistClass\"\n @keydown=\"onKeydown\"\n @scroll.passive=\"scheduleMeasure\"\n >\n <button\n v-for=\"(tab, index) in tabs\"\n :id=\"idBase ? `${idBase}-tab-${tab.value}` : undefined\"\n :key=\"tab.value\"\n :ref=\"el => setButtonRef(el, index)\"\n type=\"button\"\n role=\"tab\"\n data-gr-tab\n :aria-controls=\"idBase ? `${idBase}-panel-${tab.value}` : undefined\"\n :aria-selected=\"tab.value === modelValue ? 'true' : 'false'\"\n :aria-disabled=\"tab.disabled ? 'true' : undefined\"\n :aria-keyshortcuts=\"isClosable(tab) ? 'Delete' : undefined\"\n :tabindex=\"roving.tabindexFor(index)\"\n :class=\"tabClass(tab)\"\n @click=\"onClick($event, tab, index)\"\n >\n <span :class=\"tabContentClass\">\n <slot\n name=\"tab\"\n :tab=\"tab\"\n :active=\"tab.value === modelValue\"\n :disabled=\"Boolean(tab.disabled)\"\n >\n <component\n :is=\"iconTag(tab.icon)\"\n v-if=\"tab.icon\"\n :class=\"[tabIconClass, iconClass(tab.icon)]\"\n aria-hidden=\"true\"\n />\n <span>{{ tab.label }}</span>\n <span v-if=\"tab.badge\" :class=\"badgeClass\">\n {{ tab.badge }}\n </span>\n </slot>\n\n <!-- Снаружи слота: своя разметка вкладки не должна отнимать закрытие. -->\n <span\n v-if=\"isClosable(tab)\"\n data-gr-tab-close\n :class=\"closeClass\"\n :title=\"closeTitle\"\n aria-hidden=\"true\"\n >\n <IconX :class=\"closeIconClass\" />\n </span>\n </span>\n </button>\n </div>\n</template>\n\n<style>\n/*\n * Затухание у края, за которым ряд продолжается.\n *\n * Здесь, а не утилитами, по двум причинам сразу. `mask-image` в `presetMini` не\n * выражается — это уже зафиксировано отказом в `GrImageCrop`. И маской, а не\n * градиентом-подложкой, потому что фон полосы у вариантов разный: `pills` несёт\n * свой непрозрачный `--gr-muted`, а `line` прозрачен и лежит на фоне родителя,\n * которого компонент не знает. Градиенту пришлось бы красить в цвет подложки —\n * для `line` его неоткуда взять. Маска гасит содержимое независимо от того, что\n * под ним.\n *\n * Состояние считается логически (начало/конец ряда), а направление градиента в\n * CSS выражается только физически — отсюда зеркальные правила для RTL.\n */\n\n[data-gr-tabs] {\n /*\n * `scrollIntoView({ inline: 'nearest' })` из кольца фокуса прижимает вкладку\n * вплотную к краю. Без отступа прокрутки кольцо фокуса у клавиатурного\n * пользователя оказалось бы ровно под затуханием.\n */\n scroll-padding-inline: var(--gr-tabs-scroll-fade, 1.5rem);\n}\n\n[data-gr-tabs][data-overflow='start'] {\n --gr-tabs-mask: linear-gradient(to right, transparent 0, #000 var(--gr-tabs-scroll-fade, 1.5rem));\n}\n\n[data-gr-tabs][data-overflow='end'] {\n --gr-tabs-mask: linear-gradient(to right, #000 calc(100% - var(--gr-tabs-scroll-fade, 1.5rem)), transparent 100%);\n}\n\n[data-gr-tabs][data-overflow='both'] {\n --gr-tabs-mask: linear-gradient(\n to right,\n transparent 0,\n #000 var(--gr-tabs-scroll-fade, 1.5rem),\n #000 calc(100% - var(--gr-tabs-scroll-fade, 1.5rem)),\n transparent 100%\n );\n}\n\n[dir='rtl'] [data-gr-tabs][data-overflow='start'] {\n --gr-tabs-mask: linear-gradient(to left, transparent 0, #000 var(--gr-tabs-scroll-fade, 1.5rem));\n}\n\n[dir='rtl'] [data-gr-tabs][data-overflow='end'] {\n --gr-tabs-mask: linear-gradient(to left, #000 calc(100% - var(--gr-tabs-scroll-fade, 1.5rem)), transparent 100%);\n}\n\n[data-gr-tabs]:is([data-overflow='start'], [data-overflow='end'], [data-overflow='both']) {\n -webkit-mask-image: var(--gr-tabs-mask);\n mask-image: var(--gr-tabs-mask);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, nextTick, onBeforeUnmount, onMounted, ref, watch, watchEffect, type Component } from 'vue'\n\nimport IconX from '~icons/lucide/x'\n\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { useRovingFocus } from '../../composables/useRovingFocus'\nimport { iconClass, iconTag } from '../shared/icon'\nimport { resolveScrollOverflow, type GrScrollOverflow } from '../shared/scrollOverflow'\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\nimport {\n grTabsBadgeClass,\n grTabsCloseClass,\n grTabsEmptyClass,\n grTabsListClass,\n grTabsTabClass,\n tabCloseIconSizes,\n tabContentClass,\n tabIconClass,\n type GrTabsOrientation,\n type GrTabsSize,\n type GrTabsVariant,\n} from './grTabsStyles'\n\nexport interface GrTab {\n value: string\n label: string\n badge?: string\n /**\n * Иконка слева от подписи: Vue-компонент либо класс иконки вашей UnoCSS-сборки\n * (`'i-lucide-user'` — тогда нужен ваш `presetIcons`, см. `docs/installation.md`).\n */\n icon?: string | Component\n disabled?: boolean\n /** Перекрывает `closable` ряда в обе стороны: закрепить вкладку или закрыть одну её. */\n closable?: boolean\n}\n\nexport interface GrTabsProps {\n modelValue: string\n tabs: GrTab[]\n /**\n * База id для ARIA-связки с `GrTabPanels`. Если задана, каждая вкладка\n * получает `id=\"<idBase>-tab-<value>\"` и `aria-controls=\"<idBase>-panel-<value>\"`.\n * Передайте тот же `idBase` в `GrTabPanels`, чтобы связать `tab`↔`tabpanel`.\n */\n idBase?: string\n size?: GrTabsSize\n /** Вид ряда: обойма с таблетками или ряд с подчёркиванием. */\n variant?: GrTabsVariant\n /**\n * `automatic` (по умолчанию) — стрелка сразу переключает вкладку;\n * `manual` — стрелка двигает только фокус, выбор подтверждается\n * `Enter`/`Space`. Второй режим для вкладок с тяжёлой загрузкой: перебор\n * стрелками иначе тянет каждую панель.\n */\n activationMode?: GrTabsActivationMode\n /** Горизонтальный (по умолчанию) или вертикальный список вкладок. */\n orientation?: GrTabsOrientation\n /**\n * Крестик на вкладках и закрытие по `Delete`/`Backspace`. Точечно снимается\n * `closable: false` у самой вкладки.\n */\n closable?: boolean\n /** Текст, когда список вкладок пуст. Слот `#empty` сильнее. */\n emptyText?: string\n}\n\nexport interface GrTabsEmits {\n (e: 'update:modelValue', value: string): void\n /**\n * Просьба закрыть вкладку. Компонент список не трогает: `tabs` — проп, и\n * закрытие может не состояться («сохранить изменения?»).\n */\n (e: 'close', value: string): void\n}\n\nexport type GrTabsActivationMode = 'automatic' | 'manual'\n\nexport type { GrTabsOrientation, GrTabsVariant } from './grTabsStyles'\n\n/**\n * GrTabs — горизонтальная группа вкладок с паттерном WAI-ARIA `tablist`.\n *\n * A11y:\n * - `role=\"tablist\"` на корне, `role=\"tab\"` на каждой вкладке.\n * - Roving `tabindex`: активная вкладка `0`, остальные `-1`.\n * - Клавиатура: `ArrowLeft`/`ArrowRight` — цикличный переход, `Home`/`End` — к первой/последней.\n * - При переключении стрелками DOM-фокус программно переносится на новую вкладку.\n *\n * Сам компонент не рендерит `tabpanel` — это ответственность консьюмера (по `aria-controls`/внешней разметке).\n */\nconst props = withDefaults(defineProps<GrTabsProps>(), {\n idBase: undefined,\n size: undefined,\n // Дефолт живёт в резолвере: Vue подставил бы свой раньше, чем компонент\n // заглянет в `GrConfigProvider`.\n variant: undefined,\n activationMode: 'automatic',\n orientation: 'horizontal',\n closable: false,\n emptyText: undefined,\n})\n\ndefineSlots<{\n /** Содержимое вкладки целиком — вместо подписи, иконки и счётчика. Крестик остаётся. */\n tab?: (props: { tab: GrTab, active: boolean, disabled: boolean }) => unknown\n /** Пустой ряд — вместо текста из локали. */\n empty?: () => unknown\n}>()\n\nconst emit = defineEmits<GrTabsEmits>()\n\nconst { t } = useGranularityTranslations()\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrTabs' })\nconst resolvedVariant = useGrComponentProp('GrTabs', 'variant', () => props.variant, 'pills')\n\nconst tablistClass = computed(() => grTabsListClass({\n variant: resolvedVariant.value,\n size: resolvedSize.value,\n orientation: props.orientation,\n}))\n\nfunction tabClass(tab: GrTab): string {\n return grTabsTabClass({\n variant: resolvedVariant.value,\n size: resolvedSize.value,\n active: tab.value === props.modelValue,\n disabled: Boolean(tab.disabled),\n })\n}\n\nconst badgeClass = computed(() => grTabsBadgeClass(resolvedSize.value))\nconst closeClass = computed(() => grTabsCloseClass(resolvedSize.value))\nconst closeIconClass = computed(() => tabCloseIconSizes[resolvedSize.value])\nconst emptyClass = computed(() => grTabsEmptyClass(resolvedSize.value))\n\n/**\n * Пустой ряд — не пустой `tablist`: роль обязана владеть потомками `tab`,\n * и текст внутри неё был бы нарушением, а не пустым состоянием. Поэтому\n * пустое состояние рисуется отдельной веткой, а не содержимым `tablist`.\n */\nconst isEmpty = computed(() => props.tabs.length === 0)\nconst resolvedEmptyText = computed(() => props.emptyText ?? t('gr.tabs.empty', 'Nothing here yet'))\nconst closeTitle = computed(() => t('gr.tabs.close', 'Close tab'))\n\n/** Отключённая вкладка не принимает взаимодействия, а закрытие — тоже взаимодействие. */\nfunction isClosable(tab: GrTab): boolean {\n return !tab.disabled && (tab.closable ?? props.closable)\n}\n\nconst buttonRefs = ref<HTMLButtonElement[]>([])\n\n/**\n * Vue зовёт ref-функцию с `null`, когда узел исчез. Игнорировать такой вызов\n * нельзя: в массиве оставались отсоединённые от DOM кнопки, и `focus()` по\n * сократившемуся списку молча проваливался в `<body>`.\n */\nfunction setButtonRef(el: unknown, index: number): void {\n if (el instanceof HTMLButtonElement)\n buttonRefs.value[index] = el\n else\n delete buttonRefs.value[index]\n\n if (buttonRefs.value.length > props.tabs.length)\n buttonRefs.value.length = props.tabs.length\n}\n\nconst activeIndex = computed(() => props.tabs.findIndex(t => t.value === props.modelValue))\n\nfunction isEnabled(tab: GrTab): boolean {\n return !tab.disabled\n}\n\nconst tabIndexes = computed(() => props.tabs.map((_, index) => index))\n\n/**\n * Кольцо roving-фокуса. При `activationMode: 'manual'` фокус уезжает вперёд\n * выбора — это и есть разница между `activeKey` примитива и `initialKey`:\n * первый ведёт фокус, второй подставляет выбранную вкладку, пока фокус не\n * трогали.\n *\n * Выбор стрелка **не** переносит: этим заведует `onKeydown` по\n * `activationMode`. Через `onMove` это выразить нельзя — клик тоже зовёт\n * `focusKey`, и выбор эмитился бы дважды.\n */\nconst roving = useRovingFocus<number>({\n items: () => tabIndexes.value,\n elementFor: index => buttonRefs.value[index],\n isDisabled: index => Boolean(props.tabs[index]?.disabled),\n orientation: () => (props.orientation === 'vertical' ? 'vertical' : 'horizontal'),\n skipDisabled: () => true,\n /**\n * Пока фокус не трогали, остановку `Tab` держит выбранная вкладка. Если\n * `modelValue` не совпал ни с одной (пустое начальное значение, асинхронный\n * список, удалённая активная вкладка), примитив ставит её на первую\n * доступную — иначе tablist выпал бы из обхода целиком и молча.\n *\n * Выключенность здесь не проверяется намеренно: на кнопке `aria-disabled`, а\n * не нативный `disabled`, поэтому выключенная вкладка фокусируется и держать\n * остановку вправе.\n */\n initialKey: () => (activeIndex.value >= 0 ? activeIndex.value : undefined),\n /**\n * Прокрутка до фокуса, а не после: иначе браузер доскроллит сам по-своему, и\n * ряд дёрнется дважды. Синхронно — фокус обязан остаться в том же такте, что\n * и раньше: тик, где он нужен, добавляет `focusIndex`.\n */\n beforeFocus: index => scrollIntoView(index),\n})\n\n/**\n * Фокус после перерисовки — для путей, где список только что изменился\n * (закрытие вкладки). Клавиатура идёт через примитив напрямую: там ряд уже\n * отрисован, и лишний тик только отложил бы фокус.\n */\nasync function focusIndex(index: number): Promise<void> {\n await nextTick()\n await roving.focusKey(index)\n}\n\n/**\n * Ряд прокручивается, а не переносится, поэтому активная вкладка может уехать\n * за край — например, когда её выбрали снаружи. `scrollIntoView` нет в jsdom и\n * в старых движках, поэтому вызов необязательный.\n */\nfunction scrollIntoView(index: number): void {\n buttonRefs.value[index]?.scrollIntoView?.({ block: 'nearest', inline: 'nearest' })\n}\n\nwatch(() => props.modelValue, async () => {\n await nextTick()\n if (activeIndex.value >= 0)\n scrollIntoView(activeIndex.value)\n})\n\n/**\n * Признак того, что ряд продолжается за краем.\n *\n * Полоса прокрутки у ряда скрыта намеренно, и без этого признака продолжение\n * не выдаёт себя ничем: вкладки за краем достижимы стрелками, но о том, что они\n * там есть, догадаться нечем. Затухание рисует CSS по `data-overflow`.\n *\n * Вертикальный ряд не скроллится вовсе — он растёт вниз, — поэтому состояние\n * считается только для горизонтального.\n */\nconst tablistEl = ref<HTMLElement | null>(null)\nconst scrollOverflow = ref<GrScrollOverflow>('none')\n\nlet resizeObserver: ResizeObserver | null = null\nlet scheduled = false\n\nfunction measureOverflow(): void {\n const el = tablistEl.value\n\n if (!el || props.orientation === 'vertical') {\n scrollOverflow.value = 'none'\n return\n }\n\n scrollOverflow.value = resolveScrollOverflow(el.scrollLeft, el.scrollWidth, el.clientWidth)\n}\n\nfunction scheduleMeasure(): void {\n if (scheduled)\n return\n\n scheduled = true\n void nextTick(() => {\n scheduled = false\n measureOverflow()\n })\n}\n\nonMounted(() => {\n scheduleMeasure()\n\n // На сервере и в jsdom `ResizeObserver` отсутствует — измерять там нечего.\n if (typeof ResizeObserver === 'undefined')\n return\n\n resizeObserver = new ResizeObserver(scheduleMeasure)\n if (tablistEl.value)\n resizeObserver.observe(tablistEl.value)\n})\n\nonBeforeUnmount(() => {\n resizeObserver?.disconnect()\n resizeObserver = null\n})\n\n// Состав ряда меняет его ширину, ориентация — само наличие прокрутки.\nwatch(() => [props.tabs.length, props.orientation], scheduleMeasure)\n\nasync function selectByIndex(index: number, focus = false): Promise<void> {\n const tab = props.tabs[index]\n if (!tab || !isEnabled(tab))\n return\n if (tab.value !== props.modelValue)\n emit('update:modelValue', tab.value)\n if (focus)\n await focusIndex(index)\n}\n\nfunction onKeydown(event: KeyboardEvent): void {\n if (props.tabs.length === 0)\n return\n\n const currentIndex = roving.rovingKey.value ?? 0\n\n switch (event.key) {\n case 'Enter':\n case ' ':\n // В ручном режиме выбор подтверждается явно — на той вкладке, где фокус.\n if (props.activationMode === 'manual') {\n event.preventDefault()\n void selectByIndex(currentIndex, false)\n }\n return\n case 'Delete':\n case 'Backspace': {\n // Единственный клавиатурный путь к закрытию: крестик в таб-порядке не стоит.\n const tab = props.tabs[currentIndex]\n if (tab && isClosable(tab)) {\n event.preventDefault()\n requestClose(currentIndex)\n }\n return\n }\n }\n\n // Стрелки, `Home`, `End` — фокус ведёт примитив по объявленной ориентации.\n if (!roving.handleNavigationKeys(event))\n return\n\n // `automatic`: выбор едет за фокусом. `manual`: ждёт `Enter`/`Space`.\n if (props.activationMode !== 'manual')\n void selectByIndex(roving.rovingKey.value ?? currentIndex, false)\n}\n\n/**\n * Закрытая вкладка, которая держала фокус. Компонент список не укорачивает —\n * это делает потребитель, — поэтому фокус возвращается только после того, как\n * `tabs` действительно стал короче.\n */\nlet pendingClose: { index: number, value: string } | null = null\n\nfunction requestClose(index: number): void {\n const tab = props.tabs[index]\n if (!tab || !isClosable(tab))\n return\n\n const held = buttonRefs.value[index] === document.activeElement\n pendingClose = held ? { index, value: tab.value } : null\n\n emit('close', tab.value)\n}\n\n// Без этого клавиатурное закрытие роняет фокус в `<body>`: кнопка, на которой\n// он стоял, исчезает из DOM вместе со вкладкой.\nwatch(() => props.tabs.length, async (next, prev) => {\n const pending = pendingClose\n pendingClose = null\n\n if (!pending || next >= prev)\n return\n // Ушла не та вкладка — потребитель поменял список по своей причине.\n if (props.tabs.some(tab => tab.value === pending.value))\n return\n\n await nextTick()\n\n const target = Math.min(pending.index, next - 1)\n if (target >= 0)\n void focusIndex(target)\n else\n roving.reset()\n})\n\nfunction onClick(event: MouseEvent, tab: GrTab, index: number): void {\n if (!isEnabled(tab))\n return\n\n // Крестик — не кнопка, поэтому его клик приходит сюда же и разбирается по цели.\n if (event.target instanceof Element && event.target.closest('[data-gr-tab-close]')) {\n requestClose(index)\n return\n }\n\n roving.setActive(index)\n\n if (tab.value !== props.modelValue)\n emit('update:modelValue', tab.value)\n}\n\nif (__GR_DEV__) {\n watchEffect(() => {\n if (props.modelValue === undefined) {\n console.warn(\n `[granularity] GrTabs: обязательный проп \\`modelValue\\` не передан — получено ${String(props.modelValue)}.`,\n )\n }\n\n if (!Array.isArray(props.tabs)) {\n console.warn(\n `[granularity] GrTabs: обязательный проп \\`tabs\\` должен быть массивом — получено ${String(props.tabs)}.`,\n )\n }\n })\n}\n</script>\n\n<template>\n <div v-if=\"isEmpty\" data-gr-tabs-empty :class=\"emptyClass\">\n <slot name=\"empty\">\n {{ resolvedEmptyText }}\n </slot>\n </div>\n\n <div\n v-else\n ref=\"tablistEl\"\n role=\"tablist\"\n data-gr-tabs\n :aria-orientation=\"orientation\"\n :data-overflow=\"orientation === 'vertical' ? undefined : scrollOverflow\"\n :class=\"tablistClass\"\n @keydown=\"onKeydown\"\n @scroll.passive=\"scheduleMeasure\"\n >\n <button\n v-for=\"(tab, index) in tabs\"\n :id=\"idBase ? `${idBase}-tab-${tab.value}` : undefined\"\n :key=\"tab.value\"\n :ref=\"el => setButtonRef(el, index)\"\n type=\"button\"\n role=\"tab\"\n data-gr-tab\n :aria-controls=\"idBase ? `${idBase}-panel-${tab.value}` : undefined\"\n :aria-selected=\"tab.value === modelValue ? 'true' : 'false'\"\n :aria-disabled=\"tab.disabled ? 'true' : undefined\"\n :aria-keyshortcuts=\"isClosable(tab) ? 'Delete' : undefined\"\n :tabindex=\"roving.tabindexFor(index)\"\n :class=\"tabClass(tab)\"\n @click=\"onClick($event, tab, index)\"\n >\n <span :class=\"tabContentClass\">\n <slot\n name=\"tab\"\n :tab=\"tab\"\n :active=\"tab.value === modelValue\"\n :disabled=\"Boolean(tab.disabled)\"\n >\n <component\n :is=\"iconTag(tab.icon)\"\n v-if=\"tab.icon\"\n :class=\"[tabIconClass, iconClass(tab.icon)]\"\n aria-hidden=\"true\"\n />\n <span>{{ tab.label }}</span>\n <span v-if=\"tab.badge\" :class=\"badgeClass\">\n {{ tab.badge }}\n </span>\n </slot>\n\n <!-- Снаружи слота: своя разметка вкладки не должна отнимать закрытие. -->\n <span\n v-if=\"isClosable(tab)\"\n data-gr-tab-close\n :class=\"closeClass\"\n :title=\"closeTitle\"\n aria-hidden=\"true\"\n >\n <IconX :class=\"closeIconClass\" />\n </span>\n </span>\n </button>\n </div>\n</template>\n\n<style>\n/*\n * Затухание у края, за которым ряд продолжается.\n *\n * Здесь, а не утилитами, по двум причинам сразу. `mask-image` в `presetMini` не\n * выражается — это уже зафиксировано отказом в `GrImageCrop`. И маской, а не\n * градиентом-подложкой, потому что фон полосы у вариантов разный: `pills` несёт\n * свой непрозрачный `--gr-muted`, а `line` прозрачен и лежит на фоне родителя,\n * которого компонент не знает. Градиенту пришлось бы красить в цвет подложки —\n * для `line` его неоткуда взять. Маска гасит содержимое независимо от того, что\n * под ним.\n *\n * Состояние считается логически (начало/конец ряда), а направление градиента в\n * CSS выражается только физически — отсюда зеркальные правила для RTL.\n */\n\n[data-gr-tabs] {\n /*\n * `scrollIntoView({ inline: 'nearest' })` из кольца фокуса прижимает вкладку\n * вплотную к краю. Без отступа прокрутки кольцо фокуса у клавиатурного\n * пользователя оказалось бы ровно под затуханием.\n */\n scroll-padding-inline: var(--gr-tabs-scroll-fade, 1.5rem);\n}\n\n[data-gr-tabs][data-overflow='start'] {\n --gr-tabs-mask: linear-gradient(to right, transparent 0, #000 var(--gr-tabs-scroll-fade, 1.5rem));\n}\n\n[data-gr-tabs][data-overflow='end'] {\n --gr-tabs-mask: linear-gradient(to right, #000 calc(100% - var(--gr-tabs-scroll-fade, 1.5rem)), transparent 100%);\n}\n\n[data-gr-tabs][data-overflow='both'] {\n --gr-tabs-mask: linear-gradient(\n to right,\n transparent 0,\n #000 var(--gr-tabs-scroll-fade, 1.5rem),\n #000 calc(100% - var(--gr-tabs-scroll-fade, 1.5rem)),\n transparent 100%\n );\n}\n\n[dir='rtl'] [data-gr-tabs][data-overflow='start'] {\n --gr-tabs-mask: linear-gradient(to left, transparent 0, #000 var(--gr-tabs-scroll-fade, 1.5rem));\n}\n\n[dir='rtl'] [data-gr-tabs][data-overflow='end'] {\n --gr-tabs-mask: linear-gradient(to left, #000 calc(100% - var(--gr-tabs-scroll-fade, 1.5rem)), transparent 100%);\n}\n\n[data-gr-tabs]:is([data-overflow='start'], [data-overflow='end'], [data-overflow='both']) {\n -webkit-mask-image: var(--gr-tabs-mask);\n mask-image: var(--gr-tabs-mask);\n}\n</style>\n"],"mappings":"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"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as
|
|
1
|
+
import { t as GrTabs_default } from "./chunks/GrTabs-DVC6nfWB.js";
|
|
2
|
+
import { n as grTabsSafelist, t as grTabsConfig } from "../../chunks/config-SPugeaHT.js";
|
|
3
3
|
export { GrTabs_default as GrTabs, GrTabs_default as default, grTabsConfig, grTabsSafelist };
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { t as GrTabPanels_default } from "../../GrTabPanels/chunks/GrTabPanels-BDt5oVdd.js";
|
|
2
|
+
import { t as GrTabs_default } from "../../GrTabs/chunks/GrTabs-DVC6nfWB.js";
|
|
3
|
+
import { computed, createElementBlock, createSlots, createVNode, defineComponent, guardReactiveProps, normalizeClass, normalizeProps, openBlock, renderSlot, useId, withCtx } from "vue";
|
|
4
|
+
//#region src/components/GrTabsWithPanels/GrTabsWithPanels.vue?vue&type=script&setup=true&lang.ts
|
|
5
|
+
var _hoisted_1 = ["data-orientation"];
|
|
6
|
+
//#endregion
|
|
7
|
+
//#region src/components/GrTabsWithPanels/GrTabsWithPanels.vue
|
|
8
|
+
var GrTabsWithPanels_default = /* @__PURE__ */ defineComponent({
|
|
9
|
+
__name: "GrTabsWithPanels",
|
|
10
|
+
props: {
|
|
11
|
+
modelValue: {},
|
|
12
|
+
tabs: {},
|
|
13
|
+
idBase: { default: void 0 },
|
|
14
|
+
size: { default: void 0 },
|
|
15
|
+
variant: { default: void 0 },
|
|
16
|
+
activationMode: { default: void 0 },
|
|
17
|
+
orientation: { default: void 0 },
|
|
18
|
+
closable: {
|
|
19
|
+
type: Boolean,
|
|
20
|
+
default: void 0
|
|
21
|
+
},
|
|
22
|
+
emptyText: { default: void 0 }
|
|
23
|
+
},
|
|
24
|
+
emits: ["update:modelValue", "close"],
|
|
25
|
+
setup(__props, { emit: __emit }) {
|
|
26
|
+
/**
|
|
27
|
+
* GrTabsWithPanels — ряд вкладок вместе с их панелями.
|
|
28
|
+
*
|
|
29
|
+
* Пара `GrTabs` + `GrTabPanels` требует двух одинаковых значений в двух местах:
|
|
30
|
+
* активная вкладка и `idBase`, из которого строится связка `tab` ↔ `tabpanel`.
|
|
31
|
+
* Дублирование расходилось молча — `GrTabs` проставляет вкладкам `id` только при
|
|
32
|
+
* явном `idBase`, поэтому при обычном использовании панель ссылалась в пустоту.
|
|
33
|
+
*
|
|
34
|
+
* Здесь оба значения принадлежат одному компоненту: `v-model` один, `idBase`
|
|
35
|
+
* генерируется внутри и раздаётся обеим половинам.
|
|
36
|
+
*/
|
|
37
|
+
const props = __props;
|
|
38
|
+
const emit = __emit;
|
|
39
|
+
/**
|
|
40
|
+
* Связка держится на совпадении вычисленных id, и генерировать базу обязан один
|
|
41
|
+
* владелец. Явный `idBase` уважается: на эти id ссылаются и снаружи.
|
|
42
|
+
*/
|
|
43
|
+
const generatedIdBase = useId();
|
|
44
|
+
const resolvedIdBase = computed(() => props.idBase ?? generatedIdBase);
|
|
45
|
+
/** Вертикальный ряд вкладок стоит сбоку от панелей, а не над ними. */
|
|
46
|
+
const isVertical = computed(() => props.orientation === "vertical");
|
|
47
|
+
return (_ctx, _cache) => {
|
|
48
|
+
return openBlock(), createElementBlock("div", {
|
|
49
|
+
"data-gr-tabs-with-panels": "",
|
|
50
|
+
"data-orientation": isVertical.value ? "vertical" : "horizontal",
|
|
51
|
+
class: normalizeClass(isVertical.value ? "flex items-start gap-4" : "flex flex-col gap-3")
|
|
52
|
+
}, [createVNode(GrTabs_default, {
|
|
53
|
+
"model-value": __props.modelValue,
|
|
54
|
+
tabs: __props.tabs,
|
|
55
|
+
"id-base": resolvedIdBase.value,
|
|
56
|
+
size: __props.size,
|
|
57
|
+
variant: __props.variant,
|
|
58
|
+
"activation-mode": __props.activationMode,
|
|
59
|
+
orientation: __props.orientation,
|
|
60
|
+
closable: __props.closable,
|
|
61
|
+
"empty-text": __props.emptyText,
|
|
62
|
+
"onUpdate:modelValue": _cache[0] || (_cache[0] = (value) => emit("update:modelValue", value)),
|
|
63
|
+
onClose: _cache[1] || (_cache[1] = (value) => emit("close", value))
|
|
64
|
+
}, createSlots({ _: 2 }, [_ctx.$slots.tab ? {
|
|
65
|
+
name: "tab",
|
|
66
|
+
fn: withCtx((slotProps) => [renderSlot(_ctx.$slots, "tab", normalizeProps(guardReactiveProps(slotProps)))]),
|
|
67
|
+
key: "0"
|
|
68
|
+
} : void 0, _ctx.$slots.empty ? {
|
|
69
|
+
name: "empty",
|
|
70
|
+
fn: withCtx(() => [renderSlot(_ctx.$slots, "empty")]),
|
|
71
|
+
key: "1"
|
|
72
|
+
} : void 0]), 1032, [
|
|
73
|
+
"model-value",
|
|
74
|
+
"tabs",
|
|
75
|
+
"id-base",
|
|
76
|
+
"size",
|
|
77
|
+
"variant",
|
|
78
|
+
"activation-mode",
|
|
79
|
+
"orientation",
|
|
80
|
+
"closable",
|
|
81
|
+
"empty-text"
|
|
82
|
+
]), createVNode(GrTabPanels_default, {
|
|
83
|
+
"model-value": __props.modelValue,
|
|
84
|
+
"id-base": resolvedIdBase.value,
|
|
85
|
+
class: normalizeClass(isVertical.value ? "min-w-0 flex-1" : "")
|
|
86
|
+
}, {
|
|
87
|
+
default: withCtx(() => [renderSlot(_ctx.$slots, "default")]),
|
|
88
|
+
_: 3
|
|
89
|
+
}, 8, [
|
|
90
|
+
"model-value",
|
|
91
|
+
"id-base",
|
|
92
|
+
"class"
|
|
93
|
+
])], 10, _hoisted_1);
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
//#endregion
|
|
98
|
+
export { GrTabsWithPanels_default as t };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GrTabsWithPanels-BYtuzouj.js","names":["$slots"],"sources":["../../../../src/components/GrTabsWithPanels/GrTabsWithPanels.vue","../../../../src/components/GrTabsWithPanels/GrTabsWithPanels.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * GrTabsWithPanels — ряд вкладок вместе с их панелями.\n *\n * Пара `GrTabs` + `GrTabPanels` требует двух одинаковых значений в двух местах:\n * активная вкладка и `idBase`, из которого строится связка `tab` ↔ `tabpanel`.\n * Дублирование расходилось молча — `GrTabs` проставляет вкладкам `id` только при\n * явном `idBase`, поэтому при обычном использовании панель ссылалась в пустоту.\n *\n * Здесь оба значения принадлежат одному компоненту: `v-model` один, `idBase`\n * генерируется внутри и раздаётся обеим половинам.\n */\nimport { computed, useId } from 'vue'\n\nimport GrTabPanels from '../GrTabPanels/GrTabPanels.vue'\nimport GrTabs from '../GrTabs/GrTabs.vue'\nimport type { GrTab, GrTabsProps } from '../GrTabs/GrTabs.vue'\n\nexport interface GrTabsWithPanelsProps extends GrTabsProps {}\n\nexport interface GrTabsWithPanelsEmits {\n (e: 'update:modelValue', value: string): void\n /** Просьба закрыть вкладку — всплывает из `GrTabs` как есть. */\n (e: 'close', value: string): void\n}\n\n/*\n * Дефолт у каждого проброшенного пропа — `undefined`, включая булев `closable`.\n * Иначе Vue привёл бы его к `false`, и в `GrTabs` он приехал бы как явно\n * переданный, а не как отсутствующий. У `variant` и `size` это принципиально:\n * их дефолт живёт в резолвере `GrConfigProvider`, и подставленное здесь значение\n * перекрыло бы провайдера.\n */\nconst props = withDefaults(defineProps<GrTabsWithPanelsProps>(), {\n idBase: undefined,\n size: undefined,\n variant: undefined,\n activationMode: undefined,\n orientation: undefined,\n closable: undefined,\n emptyText: undefined,\n})\n\nconst emit = defineEmits<GrTabsWithPanelsEmits>()\n\ndefineSlots<{\n /** Панели (`GrTabPanel`). */\n default?: () => any\n /** Содержимое вкладки целиком — уходит в `GrTabs`. */\n tab?: (props: { tab: GrTab, active: boolean, disabled: boolean }) => any\n /** Пустой ряд вкладок — уходит в `GrTabs`. */\n empty?: () => any\n}>()\n\n/**\n * Связка держится на совпадении вычисленных id, и генерировать базу обязан один\n * владелец. Явный `idBase` уважается: на эти id ссылаются и снаружи.\n */\nconst generatedIdBase = useId()\nconst resolvedIdBase = computed(() => props.idBase ?? generatedIdBase)\n\n/** Вертикальный ряд вкладок стоит сбоку от панелей, а не над ними. */\nconst isVertical = computed(() => props.orientation === 'vertical')\n</script>\n\n<template>\n <div\n data-gr-tabs-with-panels\n :data-orientation=\"isVertical ? 'vertical' : 'horizontal'\"\n :class=\"isVertical ? 'flex items-start gap-4' : 'flex flex-col gap-3'\"\n >\n <GrTabs\n :model-value=\"modelValue\"\n :tabs=\"tabs\"\n :id-base=\"resolvedIdBase\"\n :size=\"size\"\n :variant=\"variant\"\n :activation-mode=\"activationMode\"\n :orientation=\"orientation\"\n :closable=\"closable\"\n :empty-text=\"emptyText\"\n @update:model-value=\"value => emit('update:modelValue', value)\"\n @close=\"value => emit('close', value)\"\n >\n <template v-if=\"$slots.tab\" #tab=\"slotProps\">\n <slot name=\"tab\" v-bind=\"slotProps\" />\n </template>\n <template v-if=\"$slots.empty\" #empty>\n <slot name=\"empty\" />\n </template>\n </GrTabs>\n\n <GrTabPanels\n :model-value=\"modelValue\"\n :id-base=\"resolvedIdBase\"\n :class=\"isVertical ? 'min-w-0 flex-1' : ''\"\n >\n <slot />\n </GrTabPanels>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * GrTabsWithPanels — ряд вкладок вместе с их панелями.\n *\n * Пара `GrTabs` + `GrTabPanels` требует двух одинаковых значений в двух местах:\n * активная вкладка и `idBase`, из которого строится связка `tab` ↔ `tabpanel`.\n * Дублирование расходилось молча — `GrTabs` проставляет вкладкам `id` только при\n * явном `idBase`, поэтому при обычном использовании панель ссылалась в пустоту.\n *\n * Здесь оба значения принадлежат одному компоненту: `v-model` один, `idBase`\n * генерируется внутри и раздаётся обеим половинам.\n */\nimport { computed, useId } from 'vue'\n\nimport GrTabPanels from '../GrTabPanels/GrTabPanels.vue'\nimport GrTabs from '../GrTabs/GrTabs.vue'\nimport type { GrTab, GrTabsProps } from '../GrTabs/GrTabs.vue'\n\nexport interface GrTabsWithPanelsProps extends GrTabsProps {}\n\nexport interface GrTabsWithPanelsEmits {\n (e: 'update:modelValue', value: string): void\n /** Просьба закрыть вкладку — всплывает из `GrTabs` как есть. */\n (e: 'close', value: string): void\n}\n\n/*\n * Дефолт у каждого проброшенного пропа — `undefined`, включая булев `closable`.\n * Иначе Vue привёл бы его к `false`, и в `GrTabs` он приехал бы как явно\n * переданный, а не как отсутствующий. У `variant` и `size` это принципиально:\n * их дефолт живёт в резолвере `GrConfigProvider`, и подставленное здесь значение\n * перекрыло бы провайдера.\n */\nconst props = withDefaults(defineProps<GrTabsWithPanelsProps>(), {\n idBase: undefined,\n size: undefined,\n variant: undefined,\n activationMode: undefined,\n orientation: undefined,\n closable: undefined,\n emptyText: undefined,\n})\n\nconst emit = defineEmits<GrTabsWithPanelsEmits>()\n\ndefineSlots<{\n /** Панели (`GrTabPanel`). */\n default?: () => any\n /** Содержимое вкладки целиком — уходит в `GrTabs`. */\n tab?: (props: { tab: GrTab, active: boolean, disabled: boolean }) => any\n /** Пустой ряд вкладок — уходит в `GrTabs`. */\n empty?: () => any\n}>()\n\n/**\n * Связка держится на совпадении вычисленных id, и генерировать базу обязан один\n * владелец. Явный `idBase` уважается: на эти id ссылаются и снаружи.\n */\nconst generatedIdBase = useId()\nconst resolvedIdBase = computed(() => props.idBase ?? generatedIdBase)\n\n/** Вертикальный ряд вкладок стоит сбоку от панелей, а не над ними. */\nconst isVertical = computed(() => props.orientation === 'vertical')\n</script>\n\n<template>\n <div\n data-gr-tabs-with-panels\n :data-orientation=\"isVertical ? 'vertical' : 'horizontal'\"\n :class=\"isVertical ? 'flex items-start gap-4' : 'flex flex-col gap-3'\"\n >\n <GrTabs\n :model-value=\"modelValue\"\n :tabs=\"tabs\"\n :id-base=\"resolvedIdBase\"\n :size=\"size\"\n :variant=\"variant\"\n :activation-mode=\"activationMode\"\n :orientation=\"orientation\"\n :closable=\"closable\"\n :empty-text=\"emptyText\"\n @update:model-value=\"value => emit('update:modelValue', value)\"\n @close=\"value => emit('close', value)\"\n >\n <template v-if=\"$slots.tab\" #tab=\"slotProps\">\n <slot name=\"tab\" v-bind=\"slotProps\" />\n </template>\n <template v-if=\"$slots.empty\" #empty>\n <slot name=\"empty\" />\n </template>\n </GrTabs>\n\n <GrTabPanels\n :model-value=\"modelValue\"\n :id-base=\"resolvedIdBase\"\n :class=\"isVertical ? 'min-w-0 flex-1' : ''\"\n >\n <slot />\n </GrTabPanels>\n </div>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiCA,MAAM,QAAQ;EAUd,MAAM,OAAO;;;;;EAeb,MAAM,kBAAkB,MAAM;EAC9B,MAAM,iBAAiB,eAAe,MAAM,UAAU,eAAe;;EAGrE,MAAM,aAAa,eAAe,MAAM,gBAAgB,UAAU;;GAIhE,OAAA,UAAA,GAAA,mBAiCM,OAAA;IAhCJ,4BAAA;IACC,oBAAkB,WAAA,QAAU,aAAA;IAC5B,OAAK,eAAE,WAAA,QAAU,2BAAA,qBAAA;GAElB,GAAA,CAAA,YAmBS,gBAAA;IAlBN,eAAa,QAAA;IACb,MAAM,QAAA;IACN,WAAS,eAAA;IACT,MAAM,QAAA;IACN,SAAS,QAAA;IACT,mBAAiB,QAAA;IACjB,aAAa,QAAA;IACb,UAAU,QAAA;IACV,cAAY,QAAA;IACZ,uBAAkB,OAAA,OAAA,OAAA,MAAE,UAAS,KAAI,qBAAsB,KAAK;IAC5D,SAAK,OAAA,OAAA,OAAA,MAAE,UAAS,KAAI,SAAU,KAAK;GAEpBA,GAAAA,YAAAA,EAAAA,GAAAA,EAAAA,GAAAA,CAAAA,KAAAA,OAAO,MAAA;IAAM,MAAA;IAC3B,IAAA,SADgC,cAAS,CACzC,WAAsC,KAAA,QAAA,OAAA,eAAA,mBAAb,SAAS,CAAA,CAAA,CAAA,CAAA;;GAEpBA,IAAAA,KAAAA,GAAAA,KAAAA,OAAO,QAAA;IAAQ,MAAA;IAC7B,IAAA,cAAqB,CAArB,WAAqB,KAAA,QAAA,OAAA,CAAA,CAAA;;;;;;;;;;;;GAIzB,CAAA,GAAA,YAMc,qBAAA;IALX,eAAa,QAAA;IACb,WAAS,eAAA;IACT,OAAK,eAAE,WAAA,QAAU,mBAAA,EAAA;;IAElB,SAAA,cAAQ,CAAR,WAAQ,KAAA,QAAA,SAAA,CAAA,CAAA"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { t as GrTabsWithPanels_default } from "./chunks/GrTabsWithPanels-BYtuzouj.js";
|
|
2
|
+
import { t as grTabsWithPanelsConfig } from "../../chunks/config-BBrPUEye.js";
|
|
3
|
+
export { GrTabsWithPanels_default as GrTabsWithPanels, GrTabsWithPanels_default as default, grTabsWithPanelsConfig };
|
|
@@ -7,7 +7,7 @@ import { n as useGrFormFieldContext } from "../../../chunks/context-DdNIxa1w.js"
|
|
|
7
7
|
import { useGrFormControl } from "../../../composables/useGrFormControl.js";
|
|
8
8
|
import { a as controlStateTextKey, i as controlStateIconColors, n as controlStateFallbackText, r as controlStateIconClass, t as controlSignalState } from "../../../chunks/controlState-DoImKciE.js";
|
|
9
9
|
import { a as enabledSurfaceClass, c as lineCountClass, i as disabledSurfaceClass, l as resizeClass, n as countClass, o as grTextareaClass, r as countRowClass, u as sizes } from "../../../chunks/grTextareaStyles-Bp35BGna.js";
|
|
10
|
-
import { computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, mergeProps, normalizeClass, openBlock, ref, resolveDynamicComponent, toDisplayString, unref, useId, withDirectives } from "vue";
|
|
10
|
+
import { computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, guardReactiveProps, mergeProps, normalizeClass, normalizeProps, openBlock, ref, renderSlot, resolveDynamicComponent, toDisplayString, unref, useId, useSlots, withDirectives } from "vue";
|
|
11
11
|
//#region src/components/GrTextarea/GrTextarea.vue?vue&type=script&setup=true&lang.ts
|
|
12
12
|
var _hoisted_1 = {
|
|
13
13
|
key: 0,
|
|
@@ -90,16 +90,33 @@ var GrTextarea_default = /* @__PURE__ */ defineComponent({
|
|
|
90
90
|
* - `invalid`: форсирует `danger`-состояние и проставляет `aria-invalid="true"`.
|
|
91
91
|
*/
|
|
92
92
|
const props = __props;
|
|
93
|
+
const slots = useSlots();
|
|
93
94
|
const emit = __emit;
|
|
94
95
|
const field = useGrFormFieldContext();
|
|
95
96
|
const resolvedId = computed(() => props.id ?? field?.id.value);
|
|
96
97
|
const countId = useId();
|
|
97
98
|
const lineCountId = useId();
|
|
98
99
|
const stateTextId = useId();
|
|
100
|
+
/**
|
|
101
|
+
* Слот считается за просьбу показать счётчик: заданный `#count` без
|
|
102
|
+
* `show-count` не рисовал бы ничего, и потребитель искал бы опечатку в имени
|
|
103
|
+
* слота, а не забытый проп.
|
|
104
|
+
*/
|
|
105
|
+
const hasCharCount = computed(() => props.showCount || Boolean(slots.count));
|
|
99
106
|
/** Обёртка нужна любому из счётчиков — и кнопке очистки. */
|
|
100
|
-
const hasCounters = computed(() =>
|
|
107
|
+
const hasCounters = computed(() => hasCharCount.value || props.showLineCount);
|
|
101
108
|
const lineCount = computed(() => props.modelValue.split("\n").length);
|
|
102
109
|
const countText = computed(() => props.maxlength !== void 0 ? `${props.modelValue.length} / ${props.maxlength}` : String(props.modelValue.length));
|
|
110
|
+
/**
|
|
111
|
+
* `remaining` не зажимается в ноль: `maxlength` держит ввод с клавиатуры, но
|
|
112
|
+
* значение, пришедшее в `v-model` из кода, ограничение перешагивает — и «-3»
|
|
113
|
+
* там честнее нуля.
|
|
114
|
+
*/
|
|
115
|
+
const countSlotProps = computed(() => ({
|
|
116
|
+
length: props.modelValue.length,
|
|
117
|
+
maxlength: props.maxlength,
|
|
118
|
+
remaining: props.maxlength === void 0 ? void 0 : props.maxlength - props.modelValue.length
|
|
119
|
+
}));
|
|
103
120
|
const { disabled: isDisabled, invalid: isInvalid, required: isRequired, readonly: isReadonly } = useGrFormControl(() => props);
|
|
104
121
|
/**
|
|
105
122
|
* Небуквенный признак состояния: иконка для глаз, скрытая подпись для
|
|
@@ -110,7 +127,7 @@ var GrTextarea_default = /* @__PURE__ */ defineComponent({
|
|
|
110
127
|
const stateIconClass = computed(() => signalState.value ? `${controlStateIconClass} ${controlStateIconColors[signalState.value]}` : "");
|
|
111
128
|
const describedBy = computed(() => [
|
|
112
129
|
field?.describedById.value,
|
|
113
|
-
|
|
130
|
+
hasCharCount.value ? countId : void 0,
|
|
114
131
|
props.showLineCount ? lineCountId : void 0,
|
|
115
132
|
signalState.value ? stateTextId : void 0
|
|
116
133
|
].filter(Boolean).join(" ") || void 0);
|
|
@@ -220,12 +237,12 @@ var GrTextarea_default = /* @__PURE__ */ defineComponent({
|
|
|
220
237
|
id: unref(lineCountId),
|
|
221
238
|
"data-gr-textarea-line-count": "",
|
|
222
239
|
class: normalizeClass(unref(lineCountClass))
|
|
223
|
-
}, toDisplayString(lineCountText.value), 11, _hoisted_4)) : createCommentVNode("", true),
|
|
240
|
+
}, toDisplayString(lineCountText.value), 11, _hoisted_4)) : createCommentVNode("", true), hasCharCount.value ? (openBlock(), createElementBlock("div", {
|
|
224
241
|
key: 1,
|
|
225
242
|
id: unref(countId),
|
|
226
243
|
"data-gr-textarea-count": "",
|
|
227
244
|
class: normalizeClass(["ml-auto", unref(countClass)])
|
|
228
|
-
}, toDisplayString(countText.value),
|
|
245
|
+
}, [renderSlot(_ctx.$slots, "count", normalizeProps(guardReactiveProps(countSlotProps.value)), () => [createTextVNode(toDisplayString(countText.value), 1)])], 10, _hoisted_5)) : createCommentVNode("", true)], 2)
|
|
229
246
|
])) : unref(resolvedClearable) || signalState.value ? (openBlock(), createElementBlock("div", _hoisted_6, [
|
|
230
247
|
withDirectives(createElementVNode("textarea", mergeProps({
|
|
231
248
|
ref_key: "textareaEl",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GrTextarea-BFwyqcHB.js","names":["$attrs"],"sources":["../../../../src/components/GrTextarea/GrTextarea.vue","../../../../src/components/GrTextarea/GrTextarea.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * GrTextarea — многострочное поле ввода GR-примитива.\n *\n * Состояния:\n * - `state`: визуальный оттенок рамки (`default | success | warning | danger`).\n * - `invalid`: форсирует `danger`-состояние и проставляет `aria-invalid=\"true\"`.\n */\nimport { computed, ref, useId } from 'vue'\nimport {\n countClass,\n countRowClass,\n lineCountClass,\n disabledSurfaceClass,\n enabledSurfaceClass,\n grTextareaClass,\n resizeClass,\n sizes,\n type GrTextareaResize,\n type GrTextareaSize,\n type GrTextareaState,\n} from './grTextareaStyles'\nimport IconX from '~icons/lucide/x'\nimport IconCheckCircle from '~icons/lucide/check-circle'\nimport IconAlertTriangle from '~icons/lucide/alert-triangle'\nimport { vAutosize } from '../../directives'\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\nimport { useGrFormFieldContext } from '../GrFormField/context'\nimport { useGrFormControl } from '../../composables/useGrFormControl'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport {\n controlSignalState,\n controlStateFallbackText,\n controlStateIconClass,\n controlStateIconColors,\n controlStateTextKey,\n} from '../shared/controlState'\n\nexport interface GrTextareaProps {\n modelValue: string\n placeholder?: string\n autocomplete?: string\n disabled?: boolean\n /** Только для чтения: значение видно и уходит в форму, но не редактируется. */\n readonly?: boolean\n invalid?: boolean\n /** Обязательное поле (`aria-required`). Складывается с `required` у `GrFormField`. */\n required?: boolean\n /** Доступное имя вне `GrFormField`. */\n ariaLabel?: string\n state?: GrTextareaState\n name?: string\n id?: string\n rows?: number\n size?: GrTextareaSize\n /** Ограничение длины + основа для счётчика символов. */\n maxlength?: number\n /** Показывать счётчик символов (`len` или `len/maxlength`). */\n showCount?: boolean\n /**\n * Показывать счётчик строк. Считаются **логические** строки (переводы строки), а\n * не визуальные переносы: при `autosize` число не меняется от ширины поля.\n */\n showLineCount?: boolean\n /**\n * Ориентир по числу строк для счётчика (`3 / 10`). Ввод не ограничивает: обрезать\n * набранный текст за пользователя компонент не вправе.\n */\n maxLines?: number\n /** Подгонять высоту под содержимое (директива `v-autosize`). */\n autosize?: boolean\n /** Кнопка очистки значения. Настраивается через `GrConfigProvider`. */\n clearable?: boolean\n /** A11y-подпись кнопки очистки. */\n clearLabel?: string\n /** Ручное изменение размера пользователем. */\n resize?: GrTextareaResize\n}\n\nexport interface GrTextareaEmits {\n (e: 'update:modelValue', value: string): void\n /** Значение зафиксировано нативным `change` — по `blur`. */\n (e: 'change', value: string): void\n /** Значение стёрто кнопкой очистки. */\n (e: 'clear'): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n}\n\nconst props = withDefaults(defineProps<GrTextareaProps>(), {\n placeholder: undefined,\n autocomplete: undefined,\n disabled: false,\n readonly: false,\n invalid: false,\n required: false,\n ariaLabel: undefined,\n state: 'default',\n name: undefined,\n id: undefined,\n rows: 4,\n size: undefined,\n maxlength: undefined,\n showCount: false,\n autosize: false,\n clearable: undefined,\n clearLabel: undefined,\n resize: 'vertical',\n})\n\ndefineOptions({\n // Иначе атрибуты потребителя (`data-*`, `aria-*`, `name`) садятся на корень,\n // а корень со `showCount` — обёртка счётчика, а не само поле.\n inheritAttrs: false,\n})\n\nconst slots = defineSlots<{\n /**\n * Своя формулировка счётчика символов вместо `12 / 60` — «осталось 48»,\n * «почти предел» и что угодно ещё. Встроенных счётчиков два, но оба\n * фиксированы по форме, и вторая готовая формулировка первой не заменяет.\n */\n count?: (props: { length: number, maxlength?: number, remaining?: number }) => any\n}>()\n\nconst emit = defineEmits<GrTextareaEmits>()\n\n// Fallback из контекста `GrFormField` (id/aria-describedby/invalid/required).\nconst field = useGrFormFieldContext()\nconst resolvedId = computed(() => props.id ?? field?.id.value)\n\n// Счётчик обязан быть частью описания поля: иначе «12 / 60» видно глазами, но\n// не слышно — при том что ограничение длины и есть его смысл.\nconst countId = useId()\nconst lineCountId = useId()\nconst stateTextId = useId()\n\n/**\n * Слот считается за просьбу показать счётчик: заданный `#count` без\n * `show-count` не рисовал бы ничего, и потребитель искал бы опечатку в имени\n * слота, а не забытый проп.\n */\nconst hasCharCount = computed(() => props.showCount || Boolean(slots.count))\n\n/** Обёртка нужна любому из счётчиков — и кнопке очистки. */\nconst hasCounters = computed(() => hasCharCount.value || props.showLineCount)\n\nconst lineCount = computed(() => props.modelValue.split('\\n').length)\n\nconst countText = computed(() =>\n props.maxlength !== undefined ? `${props.modelValue.length} / ${props.maxlength}` : String(props.modelValue.length),\n)\n\n/**\n * `remaining` не зажимается в ноль: `maxlength` держит ввод с клавиатуры, но\n * значение, пришедшее в `v-model` из кода, ограничение перешагивает — и «-3»\n * там честнее нуля.\n */\nconst countSlotProps = computed(() => ({\n length: props.modelValue.length,\n maxlength: props.maxlength,\n remaining: props.maxlength === undefined ? undefined : props.maxlength - props.modelValue.length,\n}))\nconst {\n disabled: isDisabled,\n invalid: isInvalid,\n required: isRequired,\n readonly: isReadonly,\n} = useGrFormControl(() => props)\n\n/**\n * Небуквенный признак состояния: иконка для глаз, скрытая подпись для\n * скринридера. Разбор — `shared/controlState`.\n */\nconst signalState = computed(() => controlSignalState(props.state, isInvalid.value))\nconst stateIcon = computed(() => (signalState.value === 'success' ? IconCheckCircle : IconAlertTriangle))\nconst stateIconClass = computed(() => (signalState.value\n ? `${controlStateIconClass} ${controlStateIconColors[signalState.value]}`\n : ''))\n\nconst describedBy = computed(() =>\n [\n field?.describedById.value,\n hasCharCount.value ? countId : undefined,\n props.showLineCount ? lineCountId : undefined,\n signalState.value ? stateTextId : undefined,\n ]\n .filter(Boolean)\n .join(' ') || undefined,\n)\n\nconst textareaEl = ref<HTMLTextAreaElement | null>(null)\n\nfunction focus(): void {\n textareaEl.value?.focus()\n}\n\nfunction blur(): void {\n textareaEl.value?.blur()\n}\n\ndefineExpose({ focus, blur })\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrTextarea' })\nconst resolvedClearable = useGrComponentProp('GrTextarea', 'clearable', () => props.clearable, false)\n\nconst { t } = useGranularityTranslations()\n\nconst stateText = computed(() => (signalState.value\n ? t(controlStateTextKey[signalState.value], controlStateFallbackText[signalState.value])\n : ''))\nconst resolvedClearLabel = computed(() => props.clearLabel ?? t('gr.input.clear', 'Clear'))\n\n/** С `maxLines` — «3 / 10», без него — локализованная подпись с формой числа. */\nconst lineCountText = computed(() => (\n props.maxLines !== undefined\n ? `${lineCount.value} / ${props.maxLines}`\n : t('gr.textarea.lines', '{n} lines', { n: lineCount.value })\n))\n\nconst showClear = computed(() =>\n resolvedClearable.value && !isDisabled.value && !isReadonly.value && props.modelValue !== '',\n)\n\nfunction clear(): void {\n emit('update:modelValue', '')\n emit('change', '')\n emit('clear')\n focus()\n}\n\nconst baseClass = 'w-full rounded-[var(--gr-radius-control)] border text-[var(--gr-fg)] placeholder:text-[var(--gr-muted-fg)] transition-colors duration-[var(--gr-duration-fast)] focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--gr-ring)] disabled:cursor-not-allowed'\n\nconst className = computed(() => [\n sizes[resolvedSize.value],\n resizeClass[props.resize],\n isDisabled.value ? disabledSurfaceClass : enabledSurfaceClass,\n grTextareaClass({\n state: props.state,\n invalid: isInvalid.value,\n }),\n].join(' '))\n\n// Обе ветки шаблона (со счётчиком и без) рендерят одно и то же поле, поэтому\n// атрибуты живут одним объектом: двадцать строк копипасты расходятся молча.\nconst textareaAttrs = computed(() => ({\n 'id': resolvedId.value,\n 'data-gr-textarea': '',\n 'name': props.name,\n 'rows': props.rows,\n 'maxlength': props.maxlength,\n 'autocomplete': props.autocomplete,\n 'placeholder': props.placeholder,\n 'disabled': isDisabled.value,\n 'value': props.modelValue,\n 'aria-invalid': isInvalid.value ? ('true' as const) : undefined,\n 'aria-describedby': describedBy.value,\n 'aria-required': isRequired.value ? ('true' as const) : undefined,\n 'aria-readonly': isReadonly.value ? ('true' as const) : undefined,\n 'aria-label': props.ariaLabel,\n 'readonly': isReadonly.value,\n 'class': [baseClass, className.value],\n}))\n\nfunction onInput(e: Event): void {\n emit('update:modelValue', (e.target as HTMLTextAreaElement).value)\n}\n\n// Объявленный emit уходит из `$attrs`, поэтому нативные события переизлучаем\n// руками — иначе `@change`/`@focus`/`@blur` у потребителя перестали бы работать.\nfunction onChange(e: Event): void {\n emit('change', (e.target as HTMLTextAreaElement).value)\n}\n\nfunction onFocus(e: FocusEvent): void {\n emit('focus', e)\n}\n\nfunction onBlur(e: FocusEvent): void {\n emit('blur', e)\n}\n</script>\n\n<template>\n <div v-if=\"hasCounters\" data-gr-textarea-wrap class=\"relative w-full\">\n <textarea\n ref=\"textareaEl\"\n v-autosize=\"autosize\"\n v-bind=\"{ ...textareaAttrs, ...$attrs }\"\n @input=\"onInput\"\n @change=\"onChange\"\n @focus=\"onFocus\"\n @blur=\"onBlur\"\n />\n\n <button\n v-if=\"showClear\"\n type=\"button\"\n data-gr-textarea-clear\n :aria-label=\"resolvedClearLabel\"\n class=\"absolute right-2 top-2 flex h-6 w-6 items-center justify-center rounded-[var(--gr-radius-sm)] text-[var(--gr-muted-fg)] transition-colors hover:bg-[var(--gr-muted)] hover:text-[var(--gr-fg)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--gr-ring)]\"\n @click=\"clear\"\n >\n <IconX class=\"h-4 w-4\" aria-hidden=\"true\" />\n </button>\n\n <span\n v-if=\"signalState\"\n data-gr-textarea-state\n class=\"absolute top-2\" :class=\"[stateIconClass, showClear ? 'right-10' : 'right-2']\"\n aria-hidden=\"true\"\n >\n <component :is=\"stateIcon\" class=\"h-4 w-4\" />\n </span>\n\n <span v-if=\"signalState\" :id=\"stateTextId\" data-gr-textarea-state-text class=\"sr-only\">{{ stateText }}</span>\n\n <div :class=\"countRowClass\">\n <div\n v-if=\"showLineCount\"\n :id=\"lineCountId\"\n data-gr-textarea-line-count\n :class=\"lineCountClass\"\n >\n {{ lineCountText }}\n </div>\n\n <!-- Символьный счётчик всегда прижат вправо: без счётчика строк он остаётся\n единственным в ряду, и `justify-between` его туда и отправляет. -->\n <div\n v-if=\"hasCharCount\"\n :id=\"countId\"\n data-gr-textarea-count\n class=\"ml-auto\"\n :class=\"countClass\"\n >\n <slot name=\"count\" v-bind=\"countSlotProps\">\n {{ countText }}\n </slot>\n </div>\n </div>\n </div>\n\n <!-- Обёртка появляется под кнопку очистки и под признак состояния: без них\n поле остаётся корневым элементом — на этом стоит контракт\n fallthrough-атрибутов. -->\n <div v-else-if=\"resolvedClearable || signalState\" class=\"relative w-full\">\n <textarea\n ref=\"textareaEl\"\n v-autosize=\"autosize\"\n v-bind=\"{ ...textareaAttrs, ...$attrs }\"\n @input=\"onInput\"\n @change=\"onChange\"\n @focus=\"onFocus\"\n @blur=\"onBlur\"\n />\n\n <button\n v-if=\"showClear\"\n type=\"button\"\n data-gr-textarea-clear\n :aria-label=\"resolvedClearLabel\"\n class=\"absolute right-2 top-2 flex h-6 w-6 items-center justify-center rounded-[var(--gr-radius-sm)] text-[var(--gr-muted-fg)] transition-colors hover:bg-[var(--gr-muted)] hover:text-[var(--gr-fg)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--gr-ring)]\"\n @click=\"clear\"\n >\n <IconX class=\"h-4 w-4\" aria-hidden=\"true\" />\n </button>\n\n <span\n v-if=\"signalState\"\n data-gr-textarea-state\n class=\"absolute top-2\" :class=\"[stateIconClass, showClear ? 'right-10' : 'right-2']\"\n aria-hidden=\"true\"\n >\n <component :is=\"stateIcon\" class=\"h-4 w-4\" />\n </span>\n\n <span v-if=\"signalState\" :id=\"stateTextId\" data-gr-textarea-state-text class=\"sr-only\">{{ stateText }}</span>\n </div>\n\n <textarea\n v-else\n ref=\"textareaEl\"\n v-autosize=\"autosize\"\n v-bind=\"{ ...textareaAttrs, ...$attrs }\"\n @input=\"onInput\"\n @change=\"onChange\"\n @focus=\"onFocus\"\n @blur=\"onBlur\"\n />\n</template>\n","<script setup lang=\"ts\">\n/**\n * GrTextarea — многострочное поле ввода GR-примитива.\n *\n * Состояния:\n * - `state`: визуальный оттенок рамки (`default | success | warning | danger`).\n * - `invalid`: форсирует `danger`-состояние и проставляет `aria-invalid=\"true\"`.\n */\nimport { computed, ref, useId } from 'vue'\nimport {\n countClass,\n countRowClass,\n lineCountClass,\n disabledSurfaceClass,\n enabledSurfaceClass,\n grTextareaClass,\n resizeClass,\n sizes,\n type GrTextareaResize,\n type GrTextareaSize,\n type GrTextareaState,\n} from './grTextareaStyles'\nimport IconX from '~icons/lucide/x'\nimport IconCheckCircle from '~icons/lucide/check-circle'\nimport IconAlertTriangle from '~icons/lucide/alert-triangle'\nimport { vAutosize } from '../../directives'\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\nimport { useGrFormFieldContext } from '../GrFormField/context'\nimport { useGrFormControl } from '../../composables/useGrFormControl'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport {\n controlSignalState,\n controlStateFallbackText,\n controlStateIconClass,\n controlStateIconColors,\n controlStateTextKey,\n} from '../shared/controlState'\n\nexport interface GrTextareaProps {\n modelValue: string\n placeholder?: string\n autocomplete?: string\n disabled?: boolean\n /** Только для чтения: значение видно и уходит в форму, но не редактируется. */\n readonly?: boolean\n invalid?: boolean\n /** Обязательное поле (`aria-required`). Складывается с `required` у `GrFormField`. */\n required?: boolean\n /** Доступное имя вне `GrFormField`. */\n ariaLabel?: string\n state?: GrTextareaState\n name?: string\n id?: string\n rows?: number\n size?: GrTextareaSize\n /** Ограничение длины + основа для счётчика символов. */\n maxlength?: number\n /** Показывать счётчик символов (`len` или `len/maxlength`). */\n showCount?: boolean\n /**\n * Показывать счётчик строк. Считаются **логические** строки (переводы строки), а\n * не визуальные переносы: при `autosize` число не меняется от ширины поля.\n */\n showLineCount?: boolean\n /**\n * Ориентир по числу строк для счётчика (`3 / 10`). Ввод не ограничивает: обрезать\n * набранный текст за пользователя компонент не вправе.\n */\n maxLines?: number\n /** Подгонять высоту под содержимое (директива `v-autosize`). */\n autosize?: boolean\n /** Кнопка очистки значения. Настраивается через `GrConfigProvider`. */\n clearable?: boolean\n /** A11y-подпись кнопки очистки. */\n clearLabel?: string\n /** Ручное изменение размера пользователем. */\n resize?: GrTextareaResize\n}\n\nexport interface GrTextareaEmits {\n (e: 'update:modelValue', value: string): void\n /** Значение зафиксировано нативным `change` — по `blur`. */\n (e: 'change', value: string): void\n /** Значение стёрто кнопкой очистки. */\n (e: 'clear'): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n}\n\nconst props = withDefaults(defineProps<GrTextareaProps>(), {\n placeholder: undefined,\n autocomplete: undefined,\n disabled: false,\n readonly: false,\n invalid: false,\n required: false,\n ariaLabel: undefined,\n state: 'default',\n name: undefined,\n id: undefined,\n rows: 4,\n size: undefined,\n maxlength: undefined,\n showCount: false,\n autosize: false,\n clearable: undefined,\n clearLabel: undefined,\n resize: 'vertical',\n})\n\ndefineOptions({\n // Иначе атрибуты потребителя (`data-*`, `aria-*`, `name`) садятся на корень,\n // а корень со `showCount` — обёртка счётчика, а не само поле.\n inheritAttrs: false,\n})\n\nconst slots = defineSlots<{\n /**\n * Своя формулировка счётчика символов вместо `12 / 60` — «осталось 48»,\n * «почти предел» и что угодно ещё. Встроенных счётчиков два, но оба\n * фиксированы по форме, и вторая готовая формулировка первой не заменяет.\n */\n count?: (props: { length: number, maxlength?: number, remaining?: number }) => any\n}>()\n\nconst emit = defineEmits<GrTextareaEmits>()\n\n// Fallback из контекста `GrFormField` (id/aria-describedby/invalid/required).\nconst field = useGrFormFieldContext()\nconst resolvedId = computed(() => props.id ?? field?.id.value)\n\n// Счётчик обязан быть частью описания поля: иначе «12 / 60» видно глазами, но\n// не слышно — при том что ограничение длины и есть его смысл.\nconst countId = useId()\nconst lineCountId = useId()\nconst stateTextId = useId()\n\n/**\n * Слот считается за просьбу показать счётчик: заданный `#count` без\n * `show-count` не рисовал бы ничего, и потребитель искал бы опечатку в имени\n * слота, а не забытый проп.\n */\nconst hasCharCount = computed(() => props.showCount || Boolean(slots.count))\n\n/** Обёртка нужна любому из счётчиков — и кнопке очистки. */\nconst hasCounters = computed(() => hasCharCount.value || props.showLineCount)\n\nconst lineCount = computed(() => props.modelValue.split('\\n').length)\n\nconst countText = computed(() =>\n props.maxlength !== undefined ? `${props.modelValue.length} / ${props.maxlength}` : String(props.modelValue.length),\n)\n\n/**\n * `remaining` не зажимается в ноль: `maxlength` держит ввод с клавиатуры, но\n * значение, пришедшее в `v-model` из кода, ограничение перешагивает — и «-3»\n * там честнее нуля.\n */\nconst countSlotProps = computed(() => ({\n length: props.modelValue.length,\n maxlength: props.maxlength,\n remaining: props.maxlength === undefined ? undefined : props.maxlength - props.modelValue.length,\n}))\nconst {\n disabled: isDisabled,\n invalid: isInvalid,\n required: isRequired,\n readonly: isReadonly,\n} = useGrFormControl(() => props)\n\n/**\n * Небуквенный признак состояния: иконка для глаз, скрытая подпись для\n * скринридера. Разбор — `shared/controlState`.\n */\nconst signalState = computed(() => controlSignalState(props.state, isInvalid.value))\nconst stateIcon = computed(() => (signalState.value === 'success' ? IconCheckCircle : IconAlertTriangle))\nconst stateIconClass = computed(() => (signalState.value\n ? `${controlStateIconClass} ${controlStateIconColors[signalState.value]}`\n : ''))\n\nconst describedBy = computed(() =>\n [\n field?.describedById.value,\n hasCharCount.value ? countId : undefined,\n props.showLineCount ? lineCountId : undefined,\n signalState.value ? stateTextId : undefined,\n ]\n .filter(Boolean)\n .join(' ') || undefined,\n)\n\nconst textareaEl = ref<HTMLTextAreaElement | null>(null)\n\nfunction focus(): void {\n textareaEl.value?.focus()\n}\n\nfunction blur(): void {\n textareaEl.value?.blur()\n}\n\ndefineExpose({ focus, blur })\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrTextarea' })\nconst resolvedClearable = useGrComponentProp('GrTextarea', 'clearable', () => props.clearable, false)\n\nconst { t } = useGranularityTranslations()\n\nconst stateText = computed(() => (signalState.value\n ? t(controlStateTextKey[signalState.value], controlStateFallbackText[signalState.value])\n : ''))\nconst resolvedClearLabel = computed(() => props.clearLabel ?? t('gr.input.clear', 'Clear'))\n\n/** С `maxLines` — «3 / 10», без него — локализованная подпись с формой числа. */\nconst lineCountText = computed(() => (\n props.maxLines !== undefined\n ? `${lineCount.value} / ${props.maxLines}`\n : t('gr.textarea.lines', '{n} lines', { n: lineCount.value })\n))\n\nconst showClear = computed(() =>\n resolvedClearable.value && !isDisabled.value && !isReadonly.value && props.modelValue !== '',\n)\n\nfunction clear(): void {\n emit('update:modelValue', '')\n emit('change', '')\n emit('clear')\n focus()\n}\n\nconst baseClass = 'w-full rounded-[var(--gr-radius-control)] border text-[var(--gr-fg)] placeholder:text-[var(--gr-muted-fg)] transition-colors duration-[var(--gr-duration-fast)] focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--gr-ring)] disabled:cursor-not-allowed'\n\nconst className = computed(() => [\n sizes[resolvedSize.value],\n resizeClass[props.resize],\n isDisabled.value ? disabledSurfaceClass : enabledSurfaceClass,\n grTextareaClass({\n state: props.state,\n invalid: isInvalid.value,\n }),\n].join(' '))\n\n// Обе ветки шаблона (со счётчиком и без) рендерят одно и то же поле, поэтому\n// атрибуты живут одним объектом: двадцать строк копипасты расходятся молча.\nconst textareaAttrs = computed(() => ({\n 'id': resolvedId.value,\n 'data-gr-textarea': '',\n 'name': props.name,\n 'rows': props.rows,\n 'maxlength': props.maxlength,\n 'autocomplete': props.autocomplete,\n 'placeholder': props.placeholder,\n 'disabled': isDisabled.value,\n 'value': props.modelValue,\n 'aria-invalid': isInvalid.value ? ('true' as const) : undefined,\n 'aria-describedby': describedBy.value,\n 'aria-required': isRequired.value ? ('true' as const) : undefined,\n 'aria-readonly': isReadonly.value ? ('true' as const) : undefined,\n 'aria-label': props.ariaLabel,\n 'readonly': isReadonly.value,\n 'class': [baseClass, className.value],\n}))\n\nfunction onInput(e: Event): void {\n emit('update:modelValue', (e.target as HTMLTextAreaElement).value)\n}\n\n// Объявленный emit уходит из `$attrs`, поэтому нативные события переизлучаем\n// руками — иначе `@change`/`@focus`/`@blur` у потребителя перестали бы работать.\nfunction onChange(e: Event): void {\n emit('change', (e.target as HTMLTextAreaElement).value)\n}\n\nfunction onFocus(e: FocusEvent): void {\n emit('focus', e)\n}\n\nfunction onBlur(e: FocusEvent): void {\n emit('blur', e)\n}\n</script>\n\n<template>\n <div v-if=\"hasCounters\" data-gr-textarea-wrap class=\"relative w-full\">\n <textarea\n ref=\"textareaEl\"\n v-autosize=\"autosize\"\n v-bind=\"{ ...textareaAttrs, ...$attrs }\"\n @input=\"onInput\"\n @change=\"onChange\"\n @focus=\"onFocus\"\n @blur=\"onBlur\"\n />\n\n <button\n v-if=\"showClear\"\n type=\"button\"\n data-gr-textarea-clear\n :aria-label=\"resolvedClearLabel\"\n class=\"absolute right-2 top-2 flex h-6 w-6 items-center justify-center rounded-[var(--gr-radius-sm)] text-[var(--gr-muted-fg)] transition-colors hover:bg-[var(--gr-muted)] hover:text-[var(--gr-fg)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--gr-ring)]\"\n @click=\"clear\"\n >\n <IconX class=\"h-4 w-4\" aria-hidden=\"true\" />\n </button>\n\n <span\n v-if=\"signalState\"\n data-gr-textarea-state\n class=\"absolute top-2\" :class=\"[stateIconClass, showClear ? 'right-10' : 'right-2']\"\n aria-hidden=\"true\"\n >\n <component :is=\"stateIcon\" class=\"h-4 w-4\" />\n </span>\n\n <span v-if=\"signalState\" :id=\"stateTextId\" data-gr-textarea-state-text class=\"sr-only\">{{ stateText }}</span>\n\n <div :class=\"countRowClass\">\n <div\n v-if=\"showLineCount\"\n :id=\"lineCountId\"\n data-gr-textarea-line-count\n :class=\"lineCountClass\"\n >\n {{ lineCountText }}\n </div>\n\n <!-- Символьный счётчик всегда прижат вправо: без счётчика строк он остаётся\n единственным в ряду, и `justify-between` его туда и отправляет. -->\n <div\n v-if=\"hasCharCount\"\n :id=\"countId\"\n data-gr-textarea-count\n class=\"ml-auto\"\n :class=\"countClass\"\n >\n <slot name=\"count\" v-bind=\"countSlotProps\">\n {{ countText }}\n </slot>\n </div>\n </div>\n </div>\n\n <!-- Обёртка появляется под кнопку очистки и под признак состояния: без них\n поле остаётся корневым элементом — на этом стоит контракт\n fallthrough-атрибутов. -->\n <div v-else-if=\"resolvedClearable || signalState\" class=\"relative w-full\">\n <textarea\n ref=\"textareaEl\"\n v-autosize=\"autosize\"\n v-bind=\"{ ...textareaAttrs, ...$attrs }\"\n @input=\"onInput\"\n @change=\"onChange\"\n @focus=\"onFocus\"\n @blur=\"onBlur\"\n />\n\n <button\n v-if=\"showClear\"\n type=\"button\"\n data-gr-textarea-clear\n :aria-label=\"resolvedClearLabel\"\n class=\"absolute right-2 top-2 flex h-6 w-6 items-center justify-center rounded-[var(--gr-radius-sm)] text-[var(--gr-muted-fg)] transition-colors hover:bg-[var(--gr-muted)] hover:text-[var(--gr-fg)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--gr-ring)]\"\n @click=\"clear\"\n >\n <IconX class=\"h-4 w-4\" aria-hidden=\"true\" />\n </button>\n\n <span\n v-if=\"signalState\"\n data-gr-textarea-state\n class=\"absolute top-2\" :class=\"[stateIconClass, showClear ? 'right-10' : 'right-2']\"\n aria-hidden=\"true\"\n >\n <component :is=\"stateIcon\" class=\"h-4 w-4\" />\n </span>\n\n <span v-if=\"signalState\" :id=\"stateTextId\" data-gr-textarea-state-text class=\"sr-only\">{{ stateText }}</span>\n </div>\n\n <textarea\n v-else\n ref=\"textareaEl\"\n v-autosize=\"autosize\"\n v-bind=\"{ ...textareaAttrs, ...$attrs }\"\n @input=\"onInput\"\n @change=\"onChange\"\n @focus=\"onFocus\"\n @blur=\"onBlur\"\n />\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAuOA,IAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA9IlB,MAAM,QAAQ;EA2Bd,MAAM,QAAQ,SAAA;EASd,MAAM,OAAO;EAGb,MAAM,QAAQ,sBAAsB;EACpC,MAAM,aAAa,eAAe,MAAM,MAAM,OAAO,GAAG,KAAK;EAI7D,MAAM,UAAU,MAAM;EACtB,MAAM,cAAc,MAAM;EAC1B,MAAM,cAAc,MAAM;;;;;;EAO1B,MAAM,eAAe,eAAe,MAAM,aAAa,QAAQ,MAAM,KAAK,CAAC;;EAG3E,MAAM,cAAc,eAAe,aAAa,SAAS,MAAM,aAAa;EAE5E,MAAM,YAAY,eAAe,MAAM,WAAW,MAAM,IAAI,CAAC,CAAC,MAAM;EAEpE,MAAM,YAAY,eAChB,MAAM,cAAc,KAAA,IAAY,GAAG,MAAM,WAAW,OAAO,KAAK,MAAM,cAAc,OAAO,MAAM,WAAW,MAAM,CACpH;;;;;;EAOA,MAAM,iBAAiB,gBAAgB;GACrC,QAAQ,MAAM,WAAW;GACzB,WAAW,MAAM;GACjB,WAAW,MAAM,cAAc,KAAA,IAAY,KAAA,IAAY,MAAM,YAAY,MAAM,WAAW;EAC5F,EAAE;EACF,MAAM,EACJ,UAAU,YACV,SAAS,WACT,UAAU,YACV,UAAU,eACR,uBAAuB,KAAK;;;;;EAMhC,MAAM,cAAc,eAAe,mBAAmB,MAAM,OAAO,UAAU,KAAK,CAAC;EACnF,MAAM,YAAY,eAAgB,YAAY,UAAU,YAAY,uBAAkB,sBAAkB;EACxG,MAAM,iBAAiB,eAAgB,YAAY,QAC/C,GAAG,sBAAsB,GAAG,uBAAuB,YAAY,WAC/D,EAAG;EAEP,MAAM,cAAc,eAClB;GACE,OAAO,cAAc;GACrB,aAAa,QAAQ,UAAU,KAAA;GAC/B,MAAM,gBAAgB,cAAc,KAAA;GACpC,YAAY,QAAQ,cAAc,KAAA;EACpC,CAAA,CACG,OAAO,OAAO,CAAA,CACd,KAAK,GAAG,KAAK,KAAA,CAClB;EAEA,MAAM,aAAa,IAAgC,IAAI;EAEvD,SAAS,QAAc;GACrB,WAAW,OAAO,MAAM;EAC1B;EAEA,SAAS,OAAa;GACpB,WAAW,OAAO,KAAK;EACzB;EAEA,SAAa;GAAE;GAAO;EAAK,CAAC;EAE5B,MAAM,eAAe,yBAAyB,MAAM,MAAM,EAAE,WAAW,aAAa,CAAC;EACrF,MAAM,oBAAoB,mBAAmB,cAAc,mBAAmB,MAAM,WAAW,KAAK;EAEpG,MAAM,EAAE,MAAM,2BAA2B;EAEzC,MAAM,YAAY,eAAgB,YAAY,QAC1C,EAAE,oBAAoB,YAAY,QAAQ,yBAAyB,YAAY,MAAM,IACrF,EAAG;EACP,MAAM,qBAAqB,eAAe,MAAM,cAAc,EAAE,kBAAkB,OAAO,CAAC;;EAG1F,MAAM,gBAAgB,eACpB,MAAM,aAAa,KAAA,IACf,GAAG,UAAU,MAAM,KAAK,MAAM,aAC9B,EAAE,qBAAqB,aAAa,EAAE,GAAG,UAAU,MAAM,CAAC,CAC/D;EAED,MAAM,YAAY,eAChB,kBAAkB,SAAS,CAAC,WAAW,SAAS,CAAC,WAAW,SAAS,MAAM,eAAe,EAC5F;EAEA,SAAS,QAAc;GACrB,KAAK,qBAAqB,EAAE;GAC5B,KAAK,UAAU,EAAE;GACjB,KAAK,OAAO;GACZ,MAAM;EACR;EAIA,MAAM,YAAY,eAAe;GAC/B,MAAM,aAAa;GACnB,YAAY,MAAM;GAClB,WAAW,QAAQ,uBAAuB;GAC1C,gBAAgB;IACd,OAAO,MAAM;IACb,SAAS,UAAU;GACrB,CAAC;EACH,CAAC,CAAC,KAAK,GAAG,CAAC;EAIX,MAAM,gBAAgB,gBAAgB;GACpC,MAAM,WAAW;GACjB,oBAAoB;GACpB,QAAQ,MAAM;GACd,QAAQ,MAAM;GACd,aAAa,MAAM;GACnB,gBAAgB,MAAM;GACtB,eAAe,MAAM;GACrB,YAAY,WAAW;GACvB,SAAS,MAAM;GACf,gBAAgB,UAAU,QAAS,SAAmB,KAAA;GACtD,oBAAoB,YAAY;GAChC,iBAAiB,WAAW,QAAS,SAAmB,KAAA;GACxD,iBAAiB,WAAW,QAAS,SAAmB,KAAA;GACxD,cAAc,MAAM;GACpB,YAAY,WAAW;GACvB,SAAS,CAAC,WAAW,UAAU,KAAK;EACtC,EAAE;EAEF,SAAS,QAAQ,GAAgB;GAC/B,KAAK,qBAAsB,EAAE,OAA+B,KAAK;EACnE;EAIA,SAAS,SAAS,GAAgB;GAChC,KAAK,UAAW,EAAE,OAA+B,KAAK;EACxD;EAEA,SAAS,QAAQ,GAAqB;GACpC,KAAK,SAAS,CAAC;EACjB;EAEA,SAAS,OAAO,GAAqB;GACnC,KAAK,QAAQ,CAAC;EAChB;;GAIa,OAAA,YAAA,SAAX,UAAA,GAAA,mBAyDM,OAzDN,YAyDM;IAxDJ,eAAA,mBAQE,YARF,WAQE;KAPI,SAAA;KAAJ,KAAI;IAES,GAAA;KAAA,GAAA,cAAA;KAAa,GAAKA,KAAAA;IAAM,GAAA;KAC7B;KACC;KACD;KACD;IALK,CAAA,GAAA,MAAA,EAAA,GAAA,CAAA,CAAA,MAAA,SAAA,GAAA,QAAA,QAAQ,CAAA,CAAA;IASd,UAAA,SADR,UAAA,GAAA,mBASS,UAAA;;KAPP,MAAK;KACL,0BAAA;KACC,cAAY,mBAAA;KACb,OAAM;KACL,SAAO;IAER,GAAA,CAAA,YAA4C,MAAA,SAAA,GAAA;KAArC,OAAM;KAAU,eAAY;;IAI7B,YAAA,SADR,UAAA,GAAA,mBAOO,QAAA;;KALL,0BAAA;KACA,OAAK,eAAA,CAAC,kBAAgB,CAAU,eAAA,OAAgB,UAAA,QAAS,aAAA,SAAA,CAAA,CAAA;KACzD,eAAY;IAEZ,GAAA,EAAA,UAAA,GAAA,YAA6C,wBAA7B,UAAA,KAAS,GAAA,EAAE,OAAM,UAAS,CAAA,EAAA,GAAA,CAAA,KAAA,mBAAA,IAAA,IAAA;IAGhC,YAAA,SAAZ,UAAA,GAAA,mBAA6G,QAAA;;KAAnF,IAAI,MAAA,WAAA;KAAa,+BAAA;KAA4B,OAAM;IAAa,GAAA,gBAAA,UAAA,KAAS,GAAA,GAAA,UAAA,KAAA,mBAAA,IAAA,IAAA;IAEnG,mBAuBM,OAAA,EAvBA,OAAK,eAAE,MAAA,aAAA,CAAa,EAAA,GAAA,CAEhB,QAAA,iBADR,UAAA,GAAA,mBAOM,OAAA;;KALH,IAAI,MAAA,WAAA;KACL,+BAAA;KACC,OAAK,eAAE,MAAA,cAAA,CAAc;IAEnB,GAAA,gBAAA,cAAA,KAAa,GAAA,IAAA,UAAA,KAAA,mBAAA,IAAA,IAAA,GAMV,aAAA,SADR,UAAA,GAAA,mBAUM,OAAA;;KARH,IAAI,MAAA,OAAA;KACL,0BAAA;KACA,OAAK,eAAA,CAAC,WACE,MAAA,UAAA,CAAU,CAAA;IAElB,GAAA,CAAA,WAEO,KAAA,QAAA,SAAA,eAAA,mBAFoB,eAAA,KAAc,CAAA,SAElC,CADF,gBAAA,gBAAA,UAAA,KAAS,GAAA,CAAA,CAAA,CAAA,CAAA,GAAA,IAAA,UAAA,KAAA,mBAAA,IAAA,IAAA,CAAA,GAAA,CAAA;GASJ,CAAA,KAAA,MAAA,iBAAA,KAAqB,YAAA,SAArC,UAAA,GAAA,mBAgCM,OAhCN,YAgCM;IA/BJ,eAAA,mBAQE,YARF,WAQE;KAPI,SAAA;KAAJ,KAAI;IAES,GAAA;KAAA,GAAA,cAAA;KAAa,GAAKA,KAAAA;IAAM,GAAA;KAC7B;KACC;KACD;KACD;IALK,CAAA,GAAA,MAAA,EAAA,GAAA,CAAA,CAAA,MAAA,SAAA,GAAA,QAAA,QAAQ,CAAA,CAAA;IASd,UAAA,SADR,UAAA,GAAA,mBASS,UAAA;;KAPP,MAAK;KACL,0BAAA;KACC,cAAY,mBAAA;KACb,OAAM;KACL,SAAO;IAER,GAAA,CAAA,YAA4C,MAAA,SAAA,GAAA;KAArC,OAAM;KAAU,eAAY;;IAI7B,YAAA,SADR,UAAA,GAAA,mBAOO,QAAA;;KALL,0BAAA;KACA,OAAK,eAAA,CAAC,kBAAgB,CAAU,eAAA,OAAgB,UAAA,QAAS,aAAA,SAAA,CAAA,CAAA;KACzD,eAAY;IAEZ,GAAA,EAAA,UAAA,GAAA,YAA6C,wBAA7B,UAAA,KAAS,GAAA,EAAE,OAAM,UAAS,CAAA,EAAA,GAAA,CAAA,KAAA,mBAAA,IAAA,IAAA;IAGhC,YAAA,SAAZ,UAAA,GAAA,mBAA6G,QAAA;;KAAnF,IAAI,MAAA,WAAA;KAAa,+BAAA;KAA4B,OAAM;IAAa,GAAA,gBAAA,UAAA,KAAS,GAAA,GAAA,UAAA,KAAA,mBAAA,IAAA,IAAA;GAGrG,CAAA,KAAA,gBAAA,UAAA,GAAA,mBASE,YATF,WASE;;IAPI,SAAA;IAAJ,KAAI;GAES,GAAA;IAAA,GAAA,cAAA;IAAa,GAAKA,KAAAA;GAAM,GAAA;IAC7B;IACC;IACD;IACD;GALK,CAAA,GAAA,MAAA,EAAA,IAAA,CAAA,CAAA,MAAA,SAAA,GAAA,QAAA,QAAQ,CAAA,CAAA"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { t as GrTextarea_default } from "./chunks/GrTextarea-
|
|
1
|
+
import { t as GrTextarea_default } from "./chunks/GrTextarea-BFwyqcHB.js";
|
|
2
2
|
import { n as grTextareaSafelist, t as grTextareaConfig } from "../../chunks/config-DENqOoh0.js";
|
|
3
3
|
export { GrTextarea_default as GrTextarea, GrTextarea_default as default, grTextareaConfig, grTextareaSafelist };
|