@feugene/granularity 0.16.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunks/{config-Zc_bUvZY2.js → config-BDfTXHo12.js} +1 -1
- package/dist/chunks/{config-Zc_bUvZY2.js.map → config-BDfTXHo12.js.map} +1 -1
- package/dist/chunks/config-BE6n20np2.js.map +1 -0
- package/dist/chunks/{config-CpwwkFBO.js → config-Baj7Wvx-.js} +1 -1
- package/dist/chunks/{config-CpwwkFBO.js.map → config-Baj7Wvx-.js.map} +1 -1
- package/dist/chunks/config-BoNR7_PD2.js.map +1 -0
- package/dist/chunks/{config-DxCTVBfx.js → config-BsPDQ0dS.js} +2 -2
- package/dist/chunks/config-BsPDQ0dS.js.map +1 -0
- package/dist/chunks/config-CN2ULSQL.js.map +1 -0
- package/dist/chunks/{config-Cw6TDzWv2.js → config-COpuWGr_.js} +1 -1
- package/dist/chunks/config-COpuWGr_.js.map +1 -0
- package/dist/chunks/config-D2aNP7Es.js +70 -0
- package/dist/chunks/config-D2aNP7Es.js.map +1 -0
- package/dist/chunks/config-D94Z9po5.js.map +1 -0
- package/dist/chunks/{config-B-g6lT19.js → config-DA6SnEoy.js} +5 -2
- package/dist/chunks/config-DA6SnEoy.js.map +1 -0
- package/dist/chunks/config-DCFvmHEv2.js.map +1 -0
- package/dist/chunks/config-DEvsMTQE.js +150 -0
- package/dist/chunks/config-DEvsMTQE.js.map +1 -0
- package/dist/chunks/{config-CvC0sX8p.js → config-MRvhUl-O.js} +1 -1
- package/dist/chunks/{config-CvC0sX8p.js.map → config-MRvhUl-O.js.map} +1 -1
- package/dist/chunks/config-ScuvHw6k2.js +42 -0
- package/dist/chunks/config-ScuvHw6k2.js.map +1 -0
- package/dist/chunks/config-T3qdQBgU2.js.map +1 -0
- package/dist/chunks/config-eXB87DOi.js.map +1 -0
- package/dist/chunks/{config-D8tSR2uS.js → config-gvq_hWNM.js} +1 -1
- package/dist/chunks/{config-D8tSR2uS.js.map → config-gvq_hWNM.js.map} +1 -1
- package/dist/chunks/context-BSYR7RZs.js.map +1 -1
- package/dist/chunks/{en-B3xMeswz.js → en-COdzxgAX.js} +30 -5
- package/dist/chunks/en-COdzxgAX.js.map +1 -0
- package/dist/chunks/{es-CA-mkU--.js → es-ClqJ-4hr.js} +30 -5
- package/dist/chunks/es-ClqJ-4hr.js.map +1 -0
- package/dist/chunks/{es-Bv8vvqdT.js → es-DGMHwknJ.js} +3 -3
- package/dist/chunks/{es-Bv8vvqdT.js.map → es-DGMHwknJ.js.map} +1 -1
- package/dist/chunks/grKbdStyles-CeZhGQHr.js +43 -0
- package/dist/chunks/grKbdStyles-CeZhGQHr.js.map +1 -0
- package/dist/chunks/{grTextareaStyles-CWI2SqC8.js → grTextareaStyles-iOcHuVMM.js} +2 -2
- package/dist/chunks/{grTextareaStyles-CWI2SqC8.js.map → grTextareaStyles-iOcHuVMM.js.map} +1 -1
- package/dist/chunks/{granular-provider-CtrXGa7c.js → granular-provider-B9nXQCMu.js} +18 -16
- package/dist/chunks/granular-provider-B9nXQCMu.js.map +1 -0
- package/dist/chunks/{keyboard-zYvvyHd4.js → keyboard-DE2yhz_b.js} +10 -1
- package/dist/chunks/keyboard-DE2yhz_b.js.map +1 -0
- package/dist/chunks/{optionFilter-BVY988pC.js → optionFilter-DYw9XPPx.js} +1 -71
- package/dist/chunks/optionFilter-DYw9XPPx.js.map +1 -0
- package/dist/chunks/{useOverlayLayer-BupuP_hs.js → overlayStack-C8cfsqXa.js} +11 -61
- package/dist/chunks/overlayStack-C8cfsqXa.js.map +1 -0
- package/dist/chunks/{ru-COWxgh2A.js → ru-9z8ix45c.js} +30 -5
- package/dist/chunks/ru-9z8ix45c.js.map +1 -0
- package/dist/chunks/useDragSort-0l47h4yb.js +265 -0
- package/dist/chunks/useDragSort-0l47h4yb.js.map +1 -0
- package/dist/chunks/{useModalOverlay-DCM4d3yA.js → useModalOverlay-BTq-0ImG.js} +1 -1
- package/dist/chunks/{useModalOverlay-DCM4d3yA.js.map → useModalOverlay-BTq-0ImG.js.map} +1 -1
- package/dist/components/GrAutocomplete/chunks/{GrAutocomplete-CQ9T7Lly.js → GrAutocomplete-CPEQxmLZ.js} +4 -3
- package/dist/components/GrAutocomplete/chunks/GrAutocomplete-CPEQxmLZ.js.map +1 -0
- package/dist/components/GrAutocomplete/index.js +1 -1
- package/dist/components/GrAutocomplete/styles.css +1 -1
- package/dist/components/GrColorPicker/chunks/{GrColorPicker-bnYBRGk8.js → GrColorPicker-DAdQ-Gc3.js} +4 -4
- package/dist/components/GrColorPicker/chunks/{GrColorPicker-bnYBRGk8.js.map → GrColorPicker-DAdQ-Gc3.js.map} +1 -1
- package/dist/components/GrColorPicker/index.js +2 -2
- package/dist/components/GrCommandPalette/chunks/{GrCommandPalette-MWDYDPNC.js → GrCommandPalette-DMeSqkEv.js} +6 -7
- package/dist/components/GrCommandPalette/chunks/GrCommandPalette-DMeSqkEv.js.map +1 -0
- package/dist/components/GrCommandPalette/index.js +1 -1
- package/dist/components/GrCommandPalette/styles.css +1 -1
- package/dist/components/GrConfigProvider/index.js +2 -2
- package/dist/components/GrConfirmDialog/chunks/{GrConfirmDialog-DaQmlp1_.js → GrConfirmDialog-hUTi-GdJ.js} +1 -1
- package/dist/components/GrConfirmDialog/chunks/{GrConfirmDialog-DaQmlp1_.js.map → GrConfirmDialog-hUTi-GdJ.js.map} +1 -1
- package/dist/components/GrConfirmDialog/index.js +1 -1
- package/dist/components/GrDataTable/chunks/{GrDataTable-DVFho9Cj.js → GrDataTable-DzaGLG3A.js} +495 -47
- package/dist/components/GrDataTable/chunks/GrDataTable-DzaGLG3A.js.map +1 -0
- package/dist/components/GrDataTable/index.js +2 -2
- package/dist/components/GrDialog/chunks/{GrDialog-AE1gIZuz.js → GrDialog-MD9Fzy9C.js} +1 -1
- package/dist/components/GrDialog/chunks/{GrDialog-AE1gIZuz.js.map → GrDialog-MD9Fzy9C.js.map} +1 -1
- package/dist/components/GrDialog/index.js +1 -1
- package/dist/components/GrDialogService/chunks/{GrDialogService-B4DYWDkA.js → GrDialogService-DtgUijMj.js} +2 -2
- package/dist/components/GrDialogService/chunks/{GrDialogService-B4DYWDkA.js.map → GrDialogService-DtgUijMj.js.map} +1 -1
- package/dist/components/GrDialogService/index.js +1 -1
- package/dist/components/GrDrawer/chunks/{GrDrawer-DQwi6aRw.js → GrDrawer-DWHdZZzW.js} +2 -2
- package/dist/components/GrDrawer/chunks/{GrDrawer-DQwi6aRw.js.map → GrDrawer-DWHdZZzW.js.map} +1 -1
- package/dist/components/GrDrawer/index.js +2 -2
- package/dist/components/GrDropdown/chunks/{GrDropdown-BLng6V1m.js → GrDropdown-0tJK01Kq.js} +2 -2
- package/dist/components/GrDropdown/chunks/{GrDropdown-BLng6V1m.js.map → GrDropdown-0tJK01Kq.js.map} +1 -1
- package/dist/components/GrDropdown/index.js +1 -1
- package/dist/components/GrDropdownMenu/chunks/{GrDropdownMenu-BImk2Vs7.js → GrDropdownMenu-VPPrpCoA.js} +1 -1
- package/dist/components/GrDropdownMenu/chunks/{GrDropdownMenu-BImk2Vs7.js.map → GrDropdownMenu-VPPrpCoA.js.map} +1 -1
- package/dist/components/GrDropdownMenu/index.js +1 -1
- package/dist/components/GrFileUpload/chunks/{GrFileUpload-D0B-BEsd.js → GrFileUpload-DuDzC43S.js} +2 -2
- package/dist/components/GrFileUpload/chunks/{GrFileUpload-D0B-BEsd.js.map → GrFileUpload-DuDzC43S.js.map} +1 -1
- package/dist/components/GrFileUpload/index.js +1 -1
- package/dist/components/GrImageViewer/chunks/{GrImageViewer-BeCnE8XQ.js → GrImageViewer-DE7MBbF8.js} +46 -22
- package/dist/components/GrImageViewer/chunks/GrImageViewer-DE7MBbF8.js.map +1 -0
- package/dist/components/GrImageViewer/index.js +1 -1
- package/dist/components/GrInput/chunks/{GrInput-DD2gYdqS.js → GrInput-2wd7Crs0.js} +1 -1
- package/dist/components/GrInput/chunks/GrInput-2wd7Crs0.js.map +1 -0
- package/dist/components/GrInput/index.js +1 -1
- package/dist/components/GrInputTag/chunks/{GrInputTag-DmNunixz.js → GrInputTag-CyV2eY7Z.js} +20 -33
- package/dist/components/GrInputTag/chunks/GrInputTag-CyV2eY7Z.js.map +1 -0
- package/dist/components/GrInputTag/index.js +1 -1
- package/dist/components/GrKbd/chunks/{GrKbd-DRFPvW3G.js → GrKbd-DU2lBkRV.js} +218 -52
- package/dist/components/GrKbd/chunks/GrKbd-DU2lBkRV.js.map +1 -0
- package/dist/components/GrKbd/index.js +3 -3
- package/dist/components/GrModal/chunks/{GrModal-B8I78T0N.js → GrModal-BbhcsDQ8.js} +1 -1
- package/dist/components/GrModal/chunks/{GrModal-B8I78T0N.js.map → GrModal-BbhcsDQ8.js.map} +1 -1
- package/dist/components/GrModal/index.js +1 -1
- package/dist/components/GrPagination/chunks/{GrPagination-B6N0QZBf.js → GrPagination-waKhxBP7.js} +4 -4
- package/dist/components/GrPagination/chunks/GrPagination-waKhxBP7.js.map +1 -0
- package/dist/components/GrPagination/index.js +1 -1
- package/dist/components/GrPopover/chunks/{GrPopover-B1eRCOoO.js → GrPopover-DuqeoFaI.js} +1 -1
- package/dist/components/GrPopover/chunks/{GrPopover-B1eRCOoO.js.map → GrPopover-DuqeoFaI.js.map} +1 -1
- package/dist/components/GrPopover/index.js +1 -1
- package/dist/components/GrProgressBar/chunks/{GrProgressBar-BiHnaitu.js → GrProgressBar-xwbNM9QB.js} +1 -1
- package/dist/components/GrProgressBar/chunks/GrProgressBar-xwbNM9QB.js.map +1 -0
- package/dist/components/GrProgressBar/index.js +1 -1
- package/dist/components/GrProgressCircle/chunks/{GrProgressCircle-CTogjcHz.js → GrProgressCircle-B8olXRo5.js} +1 -1
- package/dist/components/GrProgressCircle/chunks/{GrProgressCircle-CTogjcHz.js.map → GrProgressCircle-B8olXRo5.js.map} +1 -1
- package/dist/components/GrProgressCircle/index.js +2 -2
- package/dist/components/GrPromptDialog/chunks/{GrPromptDialog-Ct174NVD.js → GrPromptDialog-JVVqBsPY.js} +3 -3
- package/dist/components/GrPromptDialog/chunks/{GrPromptDialog-Ct174NVD.js.map → GrPromptDialog-JVVqBsPY.js.map} +1 -1
- package/dist/components/GrPromptDialog/index.js +1 -1
- package/dist/components/GrRadio/chunks/{GrRadio-DksDro_U.js → GrRadio-DWiRaMzt.js} +8 -32
- package/dist/components/GrRadio/chunks/GrRadio-DWiRaMzt.js.map +1 -0
- package/dist/components/GrRadio/index.js +1 -1
- package/dist/components/GrRadioGroup/chunks/{GrRadioGroup-BOIhejE6.js → GrRadioGroup-DIuBw0wk.js} +34 -27
- package/dist/components/GrRadioGroup/chunks/GrRadioGroup-DIuBw0wk.js.map +1 -0
- package/dist/components/GrRadioGroup/index.js +1 -1
- package/dist/components/GrRating/chunks/{GrRating-JrHXz0Rv.js → GrRating-LarIiwJL.js} +2 -2
- package/dist/components/GrRating/chunks/GrRating-LarIiwJL.js.map +1 -0
- package/dist/components/GrRating/index.js +2 -2
- package/dist/components/GrSegmented/chunks/{GrSegmented-BQIlH5KI.js → GrSegmented-CQw3ijqa.js} +47 -65
- package/dist/components/GrSegmented/chunks/GrSegmented-CQw3ijqa.js.map +1 -0
- package/dist/components/GrSegmented/index.js +1 -1
- package/dist/components/GrSelect/chunks/{GrSelect-aqkPCqjd.js → GrSelect-DAdu044K.js} +4 -3
- package/dist/components/GrSelect/chunks/GrSelect-DAdu044K.js.map +1 -0
- package/dist/components/GrSelect/index.js +1 -1
- package/dist/components/GrSlider/chunks/{GrSlider-DkU9IQSX.js → GrSlider-CyhVsNKn.js} +21 -34
- package/dist/components/GrSlider/chunks/GrSlider-CyhVsNKn.js.map +1 -0
- package/dist/components/GrSlider/index.js +2 -2
- package/dist/components/GrSortableList/chunks/GrSortableList-BefVfWKh.js +276 -0
- package/dist/components/GrSortableList/chunks/GrSortableList-BefVfWKh.js.map +1 -0
- package/dist/components/GrSortableList/index.js +3 -0
- package/dist/components/GrSplitter/chunks/{GrSplitter-7To8jbJ8.js → GrSplitter-BzxVQwbY.js} +17 -37
- package/dist/components/GrSplitter/chunks/GrSplitter-BzxVQwbY.js.map +1 -0
- package/dist/components/GrSplitter/index.js +2 -2
- package/dist/components/GrStatistic/chunks/{GrStatistic-CttyoYPT.js → GrStatistic-J4qLs3TH.js} +1 -1
- package/dist/components/GrStatistic/chunks/{GrStatistic-CttyoYPT.js.map → GrStatistic-J4qLs3TH.js.map} +1 -1
- package/dist/components/GrStatistic/index.js +2 -2
- package/dist/components/GrSwitch/chunks/{GrSwitch-B9TYbiih.js → GrSwitch-DpyvVrZG.js} +1 -1
- package/dist/components/GrSwitch/chunks/{GrSwitch-B9TYbiih.js.map → GrSwitch-DpyvVrZG.js.map} +1 -1
- package/dist/components/GrSwitch/index.js +2 -2
- package/dist/components/GrTabPanels/index.js +1 -1
- package/dist/components/GrTable/chunks/{GrTable-BB-mI_NM.js → GrTable-BfqgwETQ.js} +7 -1
- package/dist/components/GrTable/chunks/GrTable-BfqgwETQ.js.map +1 -0
- package/dist/components/GrTable/index.js +1 -1
- package/dist/components/GrTabs/chunks/{GrTabs-B8CDq2D9.js → GrTabs-TeXaU5Uh.js} +50 -61
- package/dist/components/GrTabs/chunks/GrTabs-TeXaU5Uh.js.map +1 -0
- package/dist/components/GrTabs/index.js +1 -1
- package/dist/components/GrTextarea/chunks/{GrTextarea-DUh5Tlo4.js → GrTextarea-DSvd1nSy.js} +1 -1
- package/dist/components/GrTextarea/chunks/{GrTextarea-DUh5Tlo4.js.map → GrTextarea-DSvd1nSy.js.map} +1 -1
- package/dist/components/GrTextarea/index.js +2 -2
- package/dist/components/GrTimeline/chunks/{GrTimeline-CE9b3s18.js → GrTimeline-B-h2Yanw.js} +1 -1
- package/dist/components/GrTimeline/chunks/{GrTimeline-CE9b3s18.js.map → GrTimeline-B-h2Yanw.js.map} +1 -1
- package/dist/components/GrTimeline/index.js +2 -2
- package/dist/components/GrToaster/chunks/{GrToaster-BKQy3Srm.js → GrToaster-Ct-39hga.js} +113 -12
- package/dist/components/GrToaster/chunks/GrToaster-Ct-39hga.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-DEbejfhA.js → GrTooltip-xAcqs0bP.js} +1 -1
- package/dist/components/GrTooltip/chunks/{GrTooltip-DEbejfhA.js.map → GrTooltip-xAcqs0bP.js.map} +1 -1
- package/dist/components/GrTooltip/index.js +2 -2
- package/dist/components/GrTree/chunks/{GrTree-wfdbvygw.js → GrTree-CaYK8dGZ.js} +124 -92
- package/dist/components/GrTree/chunks/GrTree-CaYK8dGZ.js.map +1 -0
- package/dist/components/GrTree/index.js +1 -1
- package/dist/components/GrTree/styles.css +1 -1
- package/dist/components/GrTreeSelect/chunks/{GrTreeSelect-lLWmAMtI.js → GrTreeSelect-DZp7CqqW.js} +4 -4
- package/dist/components/GrTreeSelect/chunks/{GrTreeSelect-lLWmAMtI.js.map → GrTreeSelect-DZp7CqqW.js.map} +1 -1
- package/dist/components/GrTreeSelect/index.js +2 -2
- package/dist/composables/useComboboxNavigation.js +71 -0
- package/dist/composables/useComboboxNavigation.js.map +1 -0
- package/dist/composables/useDismissible.js +1 -1
- package/dist/composables/useDragGesture.js +55 -0
- package/dist/composables/useDragGesture.js.map +1 -0
- package/dist/composables/useDragSort.js +2 -0
- package/dist/composables/useFloating.js +22 -1
- package/dist/composables/useFloating.js.map +1 -1
- package/dist/composables/useGrComponentConfig.js +2 -0
- package/dist/composables/useGranularityTranslations.js +2 -0
- package/dist/composables/useOverlayLayer.js +64 -1
- package/dist/composables/useOverlayLayer.js.map +1 -0
- package/dist/composables/useRovingFocus.js +150 -0
- package/dist/composables/useRovingFocus.js.map +1 -0
- package/dist/directives/hotkey.js +13 -7
- 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 +67 -58
- package/dist/tokens.js +84 -0
- package/dist/tokens.js.map +1 -1
- package/dist/types/src/components/GrAlert/defaults.d.ts +1 -1
- package/dist/types/src/components/GrAutocomplete/defaults.d.ts +1 -1
- package/dist/types/src/components/GrAvatar/defaults.d.ts +1 -1
- package/dist/types/src/components/GrBadge/defaults.d.ts +1 -1
- package/dist/types/src/components/GrBottomNav/defaults.d.ts +1 -1
- package/dist/types/src/components/GrBreadcrumbs/defaults.d.ts +1 -1
- package/dist/types/src/components/GrButton/defaults.d.ts +1 -1
- package/dist/types/src/components/GrCard/defaults.d.ts +1 -1
- package/dist/types/src/components/GrCheckbox/defaults.d.ts +1 -1
- package/dist/types/src/components/GrCheckboxGroup/defaults.d.ts +1 -1
- package/dist/types/src/components/GrCollapse/defaults.d.ts +1 -1
- package/dist/types/src/components/GrColorPicker/defaults.d.ts +1 -1
- package/dist/types/src/components/GrColorPicker/grColorPickerStyles.d.ts +2 -2
- package/dist/types/src/components/GrCommandPalette/defaults.d.ts +1 -1
- package/dist/types/src/components/GrConfigProvider/context.d.ts +6 -22
- package/dist/types/src/components/GrConfigProvider/index.d.ts +1 -1
- package/dist/types/src/components/GrConfirmDialog/defaults.d.ts +1 -1
- package/dist/types/src/components/GrDataTable/GrDataTable.vue.d.ts +53 -0
- package/dist/types/src/components/GrDataTable/defaults.d.ts +1 -1
- package/dist/types/src/components/GrDataTable/grDataTableStyles.d.ts +39 -0
- package/dist/types/src/components/GrDialog/defaults.d.ts +1 -1
- package/dist/types/src/components/GrDivider/defaults.d.ts +1 -1
- package/dist/types/src/components/GrDrawer/defaults.d.ts +1 -1
- package/dist/types/src/components/GrEmptyState/defaults.d.ts +1 -1
- package/dist/types/src/components/GrFileUpload/defaults.d.ts +1 -1
- package/dist/types/src/components/GrFormField/defaults.d.ts +1 -1
- package/dist/types/src/components/GrFormFile/defaults.d.ts +1 -1
- package/dist/types/src/components/GrFormSection/defaults.d.ts +1 -1
- package/dist/types/src/components/GrIcon/defaults.d.ts +1 -1
- package/dist/types/src/components/GrImageViewer/composables/usePointerGestures.d.ts +1 -0
- package/dist/types/src/components/GrImageViewer/composables/useZoomPan.d.ts +1 -2
- package/dist/types/src/components/GrInput/defaults.d.ts +1 -1
- package/dist/types/src/components/GrInputTag/defaults.d.ts +1 -1
- package/dist/types/src/components/GrKbd/GrKbd.vue.d.ts +18 -3
- package/dist/types/src/components/GrKbd/defaults.d.ts +1 -1
- package/dist/types/src/components/GrKbd/grKbdStyles.d.ts +20 -3
- package/dist/types/src/components/GrKbd/index.d.ts +6 -0
- package/dist/types/src/components/GrLink/defaults.d.ts +1 -1
- package/dist/types/src/components/GrModal/defaults.d.ts +1 -1
- package/dist/types/src/components/GrNumberInput/defaults.d.ts +1 -1
- package/dist/types/src/components/GrPagination/defaults.d.ts +1 -1
- package/dist/types/src/components/GrPopover/GrPopover.vue.d.ts +1 -1
- package/dist/types/src/components/GrPopover/defaults.d.ts +1 -1
- package/dist/types/src/components/GrProgressBar/defaults.d.ts +1 -1
- package/dist/types/src/components/GrProgressCircle/defaults.d.ts +1 -1
- package/dist/types/src/components/GrProgressCircle/grProgressCircleStyles.d.ts +1 -1
- package/dist/types/src/components/GrPromptDialog/defaults.d.ts +1 -1
- package/dist/types/src/components/GrRadio/defaults.d.ts +1 -1
- package/dist/types/src/components/GrRadio/grRadioGroupContext.d.ts +12 -4
- package/dist/types/src/components/GrRadioGroup/defaults.d.ts +1 -1
- package/dist/types/src/components/GrRating/defaults.d.ts +1 -1
- package/dist/types/src/components/GrSegmented/defaults.d.ts +1 -1
- package/dist/types/src/components/GrSelect/defaults.d.ts +1 -1
- package/dist/types/src/components/GrSlider/defaults.d.ts +1 -1
- package/dist/types/src/components/GrSortableList/GrSortableList.vue.d.ts +68 -0
- package/dist/types/src/components/GrSortableList/config.d.ts +1 -0
- package/dist/types/src/components/GrSortableList/grSortableListStyles.d.ts +38 -0
- package/dist/types/src/components/GrSortableList/index.d.ts +9 -0
- package/dist/types/src/components/GrSortableList/safelist.d.ts +1 -0
- package/dist/types/src/components/GrSplitter/defaults.d.ts +1 -1
- package/dist/types/src/components/GrStatistic/defaults.d.ts +1 -1
- package/dist/types/src/components/GrStatistic/grStatisticStyles.d.ts +1 -1
- package/dist/types/src/components/GrSwitch/defaults.d.ts +1 -1
- package/dist/types/src/components/GrTable/GrTable.vue.d.ts +13 -0
- package/dist/types/src/components/GrTable/defaults.d.ts +1 -1
- package/dist/types/src/components/GrTabs/defaults.d.ts +1 -1
- package/dist/types/src/components/GrTextarea/defaults.d.ts +1 -1
- package/dist/types/src/components/GrTextarea/grTextareaStyles.d.ts +2 -2
- package/dist/types/src/components/GrTimeline/defaults.d.ts +1 -1
- package/dist/types/src/components/GrTimeline/grTimelineStyles.d.ts +1 -1
- package/dist/types/src/components/GrToaster/GrToaster.vue.d.ts +8 -0
- package/dist/types/src/components/GrToaster/grToasterStyles.d.ts +27 -0
- package/dist/types/src/components/GrTooltip/defaults.d.ts +1 -1
- package/dist/types/src/components/GrTree/defaults.d.ts +1 -1
- package/dist/types/src/components/GrTree/grTreeInteractionContext.d.ts +0 -1
- package/dist/types/src/components/GrTreeSelect/defaults.d.ts +1 -1
- package/dist/types/src/components/shared/hotkey.d.ts +7 -15
- package/dist/types/src/components/shared/hotkeyTokens.d.ts +37 -0
- package/dist/types/src/composables/internal/dragSortGeometry.d.ts +59 -0
- package/dist/types/src/composables/internal/overlayStack.d.ts +11 -0
- package/dist/types/src/composables/useDragGesture.d.ts +63 -0
- package/dist/types/src/composables/useDragSort.d.ts +80 -0
- package/dist/types/src/composables/useFloating.d.ts +5 -1
- package/dist/types/src/composables/useGrComponentConfig.d.ts +41 -0
- package/dist/types/src/composables/useGranularityTranslations.d.ts +13 -0
- package/dist/types/src/composables/useRovingFocus.d.ts +108 -0
- package/dist/types/src/granular-provider/shared.d.ts +1 -0
- package/dist/types/src/i18n/messages/en.d.ts +48 -0
- package/dist/types/src/i18n/messages/es.d.ts +48 -0
- package/dist/types/src/i18n/messages/ru.d.ts +48 -0
- package/dist/types/src/index.d.ts +12 -0
- package/dist/types/src/internal/keyboard.d.ts +6 -0
- package/dist/web-types.json +201 -3
- package/package.json +31 -3
- package/dist/chunks/config-B-g6lT19.js.map +0 -1
- package/dist/chunks/config-BE6n20np.js.map +0 -1
- package/dist/chunks/config-BNfQwyL4.js +0 -97
- package/dist/chunks/config-BNfQwyL4.js.map +0 -1
- package/dist/chunks/config-BoNR7_PD.js.map +0 -1
- package/dist/chunks/config-CN2ULSQL2.js.map +0 -1
- package/dist/chunks/config-Cw6TDzWv2.js.map +0 -1
- package/dist/chunks/config-D94Z9po52.js.map +0 -1
- package/dist/chunks/config-DCFvmHEv.js.map +0 -1
- package/dist/chunks/config-DxCTVBfx.js.map +0 -1
- package/dist/chunks/config-LGrZIdaZ.js +0 -21
- package/dist/chunks/config-LGrZIdaZ.js.map +0 -1
- package/dist/chunks/config-T3qdQBgU.js.map +0 -1
- package/dist/chunks/config-eXB87DOi2.js.map +0 -1
- package/dist/chunks/en-B3xMeswz.js.map +0 -1
- package/dist/chunks/es-CA-mkU--.js.map +0 -1
- package/dist/chunks/grKbdStyles-DAoxiytu.js +0 -31
- package/dist/chunks/grKbdStyles-DAoxiytu.js.map +0 -1
- package/dist/chunks/granular-provider-CtrXGa7c.js.map +0 -1
- package/dist/chunks/keyboard-zYvvyHd4.js.map +0 -1
- package/dist/chunks/optionFilter-BVY988pC.js.map +0 -1
- package/dist/chunks/ru-COWxgh2A.js.map +0 -1
- package/dist/chunks/useOverlayLayer-BupuP_hs.js.map +0 -1
- package/dist/components/GrAutocomplete/chunks/GrAutocomplete-CQ9T7Lly.js.map +0 -1
- package/dist/components/GrCommandPalette/chunks/GrCommandPalette-MWDYDPNC.js.map +0 -1
- package/dist/components/GrDataTable/chunks/GrDataTable-DVFho9Cj.js.map +0 -1
- package/dist/components/GrImageViewer/chunks/GrImageViewer-BeCnE8XQ.js.map +0 -1
- package/dist/components/GrInput/chunks/GrInput-DD2gYdqS.js.map +0 -1
- package/dist/components/GrInputTag/chunks/GrInputTag-DmNunixz.js.map +0 -1
- package/dist/components/GrKbd/chunks/GrKbd-DRFPvW3G.js.map +0 -1
- package/dist/components/GrPagination/chunks/GrPagination-B6N0QZBf.js.map +0 -1
- package/dist/components/GrProgressBar/chunks/GrProgressBar-BiHnaitu.js.map +0 -1
- package/dist/components/GrRadio/chunks/GrRadio-DksDro_U.js.map +0 -1
- package/dist/components/GrRadioGroup/chunks/GrRadioGroup-BOIhejE6.js.map +0 -1
- package/dist/components/GrRating/chunks/GrRating-JrHXz0Rv.js.map +0 -1
- package/dist/components/GrSegmented/chunks/GrSegmented-BQIlH5KI.js.map +0 -1
- package/dist/components/GrSelect/chunks/GrSelect-aqkPCqjd.js.map +0 -1
- package/dist/components/GrSlider/chunks/GrSlider-DkU9IQSX.js.map +0 -1
- package/dist/components/GrSplitter/chunks/GrSplitter-7To8jbJ8.js.map +0 -1
- package/dist/components/GrTable/chunks/GrTable-BB-mI_NM.js.map +0 -1
- package/dist/components/GrTabs/chunks/GrTabs-B8CDq2D9.js.map +0 -1
- package/dist/components/GrToaster/chunks/GrToaster-BKQy3Srm.js.map +0 -1
- package/dist/components/GrTree/chunks/GrTree-wfdbvygw.js.map +0 -1
- /package/dist/chunks/{config-BE6n20np.js → config-BE6n20np2.js} +0 -0
- /package/dist/chunks/{config-BoNR7_PD.js → config-BoNR7_PD2.js} +0 -0
- /package/dist/chunks/{config-CN2ULSQL2.js → config-CN2ULSQL.js} +0 -0
- /package/dist/chunks/{config-D94Z9po52.js → config-D94Z9po5.js} +0 -0
- /package/dist/chunks/{config-DCFvmHEv.js → config-DCFvmHEv2.js} +0 -0
- /package/dist/chunks/{config-T3qdQBgU.js → config-T3qdQBgU2.js} +0 -0
- /package/dist/chunks/{config-eXB87DOi2.js → config-eXB87DOi.js} +0 -0
- /package/dist/types/src/composables/{internal/useComboboxNavigation.d.ts → useComboboxNavigation.d.ts} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"GrInputTag-DmNunixz.js","names":[],"sources":["../../../../src/components/GrInputTag/GrInputTag.vue","../../../../src/components/GrInputTag/GrInputTag.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, nextTick, ref } from 'vue'\n\nimport GrBadge from '../GrBadge/GrBadge.vue'\nimport type { GrBadgeRadius, GrBadgeSize, GrBadgeTone } from '../GrBadge'\nimport GrIcon from '../GrIcon/GrIcon.vue'\nimport IconClose from '~icons/lucide/x'\nimport IconLoader from '~icons/lucide/loader-2'\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\nimport { useControlAddons } from '../../composables/internal/useControlAddons'\nimport { useGrFormFieldContext } from '../GrFormField/context'\nimport { useAnnouncer } from '../../composables/useAnnouncer'\nimport { useGrFormControl } from '../../composables/useGrFormControl'\nimport { useFocusWithin } from '../../composables/internal/useFocusWithin'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { isComposingEvent } from '../../internal/keyboard'\n\nimport {\n clearButtonClass,\n grInputTagInputClass,\n grInputTagWrapperClass,\n removeButtonClass,\n spinnerClass,\n type GrInputTagSize,\n type GrInputTagState,\n} from './grInputTagStyles'\n\nexport type { GrInputTagSize, GrInputTagState } from './grInputTagStyles'\n\nconst REGEX_SPECIAL_CHAR_RE = /[.*+?^${}()|[\\]\\\\]/g\n\n/**\n * Props for {@link GrInputTag}.\n * Tag-input primitive: renders a set of badges with an inline `<input>` for entering new tags.\n * Splits input by configurable `separators`, supports paste, Enter and Backspace editing.\n */\nexport interface GrInputTagProps {\n modelValue: string[]\n placeholder?: string\n disabled?: boolean\n readonly?: boolean\n invalid?: boolean\n /** Обязательное поле (`aria-required`). Складывается с `required` у `GrFormField`. */\n required?: boolean\n state?: GrInputTagState\n size?: GrInputTagSize\n separators?: string[]\n allowDuplicates?: boolean\n trim?: boolean\n max?: number\n addOnBlur?: boolean\n clearInputOnAdd?: boolean\n /**\n * Проверка тега перед добавлением. Может быть асинхронной (проверка на\n * сервере). Отклонённый тег не добавляется и уходит в событие `reject`.\n */\n beforeAdd?: (tag: string) => boolean | Promise<boolean>\n /** Кнопка «снести все теги». Настраивается через `GrConfigProvider`. */\n clearable?: boolean\n /** Фоновая работа: спиннер + `aria-busy`. Асинхронный `beforeAdd` поднимает его сам. */\n loading?: boolean\n tagTone?: GrBadgeTone\n tagDark?: boolean\n tagSize?: GrBadgeSize\n tagRadius?: GrBadgeRadius\n tagClosable?: boolean\n /** i18n-friendly aria-label for the per-tag remove button. */\n removeTagLabel?: string\n /** i18n aria-label кнопки «снести все». */\n clearAllLabel?: string\n /**\n * Имя контрола, когда он используется вне `GrFormField`. Внутри поля имя даёт\n * связка с его `<label for>` — там проп не нужен. Без того и другого у инпута\n * нет доступного имени вовсе: placeholder именем не считается.\n */\n ariaLabel?: string\n /** Имя для нативной формы: hidden input на каждый тег. */\n name?: string\n /**\n * Ширины аддонов `prefix`/`suffix` — общий контракт контролов пакета\n * (`docs/form-controls.md`).\n */\n prefixMinWidth?: string\n prefixMaxWidth?: string\n suffixMinWidth?: string\n suffixMaxWidth?: string\n prefixFixed?: boolean\n suffixFixed?: boolean\n}\n\nexport interface GrInputTagEmits {\n (e: 'update:modelValue', value: string[]): void\n (e: 'add', value: string): void\n (e: 'remove', value: string, index: number): void\n /** Тег не прошёл `beforeAdd`. */\n (e: 'reject', value: string): void\n /** Набор снесён кнопкой «очистить». */\n (e: 'clear'): void\n /** Набор тегов изменился. */\n (e: 'change', value: string[]): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n}\n\nconst props = withDefaults(\n defineProps<GrInputTagProps>(),\n {\n placeholder: undefined,\n disabled: false,\n readonly: false,\n invalid: false,\n required: false,\n state: 'default',\n // Настраивается через `GrConfigProvider`; дефолт — в резолвере ниже.\n size: undefined,\n separators: () => [','],\n allowDuplicates: false,\n trim: true,\n max: undefined,\n addOnBlur: false,\n clearInputOnAdd: true,\n beforeAdd: undefined,\n clearable: undefined,\n loading: false,\n tagTone: 'neutral',\n tagDark: false,\n tagSize: 'md',\n tagRadius: 'round',\n tagClosable: true,\n removeTagLabel: undefined,\n clearAllLabel: undefined,\n ariaLabel: undefined,\n name: undefined,\n },\n)\n\nconst emit = defineEmits<GrInputTagEmits>()\ndefineSlots<{\n /** Аддон слева от чипов: иконка, метка. */\n prefix?: () => any\n /** Аддон справа, перед кнопкой очистки. */\n suffix?: () => any\n /** Свой чип: `remove` снимает значение. */\n tag?: (props: { tag: string, index: number, remove: () => void }) => any\n}>()\n\n\n// Fallback из контекста `GrFormField` (id/aria-describedby/invalid/required) —\n// как у GrInput и GrSelect: контрол не знает про форму, знает только про поле.\nconst field = useGrFormFieldContext()\nconst resolvedId = computed(() => field?.id.value)\nconst describedBy = computed(() => field?.describedById.value)\nconst {\n disabled: isDisabled,\n invalid: isInvalid,\n required: isRequired,\n readonly: isReadonly,\n} = useGrFormControl(() => props)\n\n// Эффективные значения: локальный проп → `GrConfigProvider` → дефолт компонента.\n/** Ширина аддона по умолчанию — та же лестница, что у `GrInput`. */\nconst ADDON_MIN_WIDTH_BY_SIZE: Record<GrInputTagSize, string> = {\n xs: '2rem',\n sm: '2.25rem',\n md: '2.5rem',\n lg: '3rem',\n}\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrInputTag' })\n\n/**\n * Аддоны — флекс-соседи поля: оболочка тегового поля и так флекс-строка с\n * бейджами, поэтому места паддингом резервировать не нужно.\n */\nconst { hasPrefix, hasSuffix, prefixEl, suffixEl, prefixStyle, suffixStyle } = useControlAddons(() => props, {\n defaultMinWidth: () => ADDON_MIN_WIDTH_BY_SIZE[resolvedSize.value],\n paddingX: () => '0px',\n})\nconst resolvedClearable = useGrComponentProp('GrInputTag', 'clearable', () => props.clearable, false)\n\nconst { t } = useGranularityTranslations()\nconst resolvedClearAllLabel = computed(() => props.clearAllLabel ?? t('gr.inputTag.clearAll', 'Clear all tags'))\n\nfunction removeTagLabelFor(tag: string): string {\n // Безликое «Remove tag» на двадцати кнопках подряд не даёт выбрать нужную:\n // имя обязано называть сам тег.\n return props.removeTagLabel ?? t('gr.inputTag.removeTagNamed', 'Remove tag {tag}', { tag })\n}\n\nconst inputValue = ref('')\nconst inputEl = ref<HTMLInputElement | null>(null)\nconst rootEl = ref<HTMLElement | null>(null)\n\n// Внутри корня фокус ходит между полем и кнопками удаления чипов: без границы\n// каждое такое перемещение давало бы потребителю пару `blur` + `focus`.\nconst { onFocusIn, onFocusOut } = useFocusWithin(rootEl, {\n enter: event => emit('focus', event),\n leave: event => emit('blur', event),\n})\nconst removeEls = ref<HTMLButtonElement[]>([])\n\nfunction setRemoveEl(index: number) {\n return (el: unknown): void => {\n removeEls.value[index] = el as HTMLButtonElement\n }\n}\n\nfunction focus(): void {\n inputEl.value?.focus()\n}\n\nfunction blur(): void {\n inputEl.value?.blur()\n}\n\nconst isMaxed = computed(() => props.max !== undefined && props.modelValue.length >= props.max)\nconst canEdit = computed(() => !isDisabled.value && !isReadonly.value)\nconst showRemove = computed(() => props.tagClosable && canEdit.value)\n\n// Добавление, удаление и достигнутый предел — события, а не состояние виджета:\n// показывать их негде, объявить нужно. Это работа общего живого региона.\nconst { announce } = useAnnouncer()\n\n/**\n * Roving tabindex по крестикам: двадцать тегов давали двадцать одну остановку\n * `Tab`. В таб-порядке ровно один крестик, между ними ходят стрелками.\n */\nconst focusedTagIndex = ref(0)\nconst rovingIndex = computed(() => {\n const last = props.modelValue.length - 1\n if (last < 0)\n return -1\n return Math.min(Math.max(focusedTagIndex.value, 0), last)\n})\n\nasync function focusTag(index: number): Promise<void> {\n const last = props.modelValue.length - 1\n if (last < 0) {\n focus()\n return\n }\n\n const target = Math.min(Math.max(index, 0), last)\n focusedTagIndex.value = target\n await nextTick()\n removeEls.value[target]?.focus()\n}\n\nfunction escapeRegexChar(ch: string): string {\n return ch.replace(REGEX_SPECIAL_CHAR_RE, '\\\\$&')\n}\n\nconst splitRegex = computed(() => {\n const seps = (props.separators ?? []).filter(Boolean).map(s => s[0]!)\n const uniq = [...new Set([...seps, '\\n', '\\r', '\\t'])]\n const body = uniq.map(escapeRegexChar).join('')\n return new RegExp(`[${body}]+`, 'g')\n})\n\nfunction normalizeTag(raw: string): string {\n return props.trim ? raw.trim() : raw\n}\n\nfunction splitToTags(raw: string): string[] {\n return raw\n .split(splitRegex.value)\n .map(v => normalizeTag(v))\n .filter(Boolean)\n}\n\nfunction addMany(rawTags: string[]): boolean {\n if (!canEdit.value || isMaxed.value)\n return false\n\n const next = props.modelValue.slice()\n const added: string[] = []\n\n for (const raw of rawTags) {\n const tag = normalizeTag(raw)\n if (!tag)\n continue\n\n if (!props.allowDuplicates && next.includes(tag))\n continue\n\n if (props.max !== undefined && next.length >= props.max)\n break\n\n next.push(tag)\n added.push(tag)\n emit('add', tag)\n }\n\n if (!added.length)\n return false\n\n emit('update:modelValue', next)\n emit('change', next)\n announce(added.length === 1\n ? t('gr.inputTag.added', 'Tag added: {tag}', { tag: added[0]! })\n : t('gr.inputTag.addedMany', '{count} tags added', { count: added.length }))\n\n return true\n}\n\n// Счётчик запуска: пока идёт асинхронная проверка, пользователь успевает нажать\n// Enter второй раз — результат устаревшей проверки дописывать нельзя.\nlet validationRun = 0\nconst validating = ref(false)\nconst isBusy = computed(() => props.loading || validating.value)\n\nasync function filterAllowed(tags: string[], run: number): Promise<string[] | null> {\n const check = props.beforeAdd\n if (!check)\n return tags\n\n const allowed: string[] = []\n\n for (const tag of tags) {\n const ok = await check(tag)\n if (run !== validationRun)\n return null\n\n if (ok)\n allowed.push(tag)\n else\n emit('reject', tag)\n }\n\n return allowed\n}\n\nasync function commitInput(): Promise<void> {\n if (!canEdit.value || !inputValue.value)\n return\n\n if (isMaxed.value) {\n announce(t('gr.inputTag.limitReached', 'Tag limit reached'))\n return\n }\n\n const raw = inputValue.value\n const tags = splitToTags(raw)\n if (!tags.length)\n return\n\n const run = ++validationRun\n validating.value = Boolean(props.beforeAdd)\n\n const allowed = await filterAllowed(tags, run)\n\n if (run === validationRun)\n validating.value = false\n\n if (allowed === null || !allowed.length)\n return\n\n const changed = addMany(allowed)\n // Значение могло измениться, пока шла проверка: стирать чужой ввод нельзя.\n if (changed && props.clearInputOnAdd && inputValue.value === raw)\n inputValue.value = ''\n}\n\nfunction removeAt(index: number): void {\n if (!canEdit.value)\n return\n\n const next = props.modelValue.slice()\n const removed = next[index]\n if (removed === undefined)\n return\n\n next.splice(index, 1)\n emit('update:modelValue', next)\n emit('change', next)\n emit('remove', removed, index)\n announce(t('gr.inputTag.removed', 'Tag removed: {tag}', { tag: removed }))\n}\n\n/** Удаление с клавиатуры: фокус переезжает на соседний чип, а не пропадает. */\nasync function removeAtFromKeyboard(index: number): Promise<void> {\n removeAt(index)\n await nextTick()\n\n if (props.modelValue.length === 0) {\n focus()\n return\n }\n\n await focusTag(Math.min(index, props.modelValue.length - 1))\n}\n\nfunction clearAll(): void {\n if (!canEdit.value || props.modelValue.length === 0)\n return\n\n emit('update:modelValue', [])\n emit('change', [])\n emit('clear')\n announce(t('gr.inputTag.cleared', 'All tags removed'))\n focus()\n}\n\nfunction onInput(e: Event): void {\n inputValue.value = (e.target as HTMLInputElement).value\n}\n\nfunction onKeydown(e: KeyboardEvent): void {\n // Enter/сепаратор во время IME-композиции коммитят её, а не тег.\n if (isComposingEvent(e))\n return\n if (!canEdit.value)\n return\n\n if (e.key === 'Enter') {\n e.preventDefault()\n void commitInput()\n return\n }\n\n if ((props.separators ?? []).includes(e.key)) {\n e.preventDefault()\n void commitInput()\n return\n }\n\n if (inputValue.value.length === 0 && props.modelValue.length > 0) {\n if (e.key === 'Backspace') {\n e.preventDefault()\n removeAt(props.modelValue.length - 1)\n return\n }\n\n // Из пустого поля стрелка влево уводит на последний чип — дальше по чипам\n // работают те же стрелки, `Tab` для этого не нужен.\n if (e.key === 'ArrowLeft') {\n e.preventDefault()\n void focusTag(props.modelValue.length - 1)\n }\n }\n}\n\nfunction onTagKeydown(e: KeyboardEvent, index: number): void {\n if (e.key === 'ArrowLeft') {\n e.preventDefault()\n void focusTag(index - 1)\n return\n }\n\n if (e.key === 'ArrowRight') {\n e.preventDefault()\n // За последним чипом — поле ввода: продолжение того же ряда.\n if (index === props.modelValue.length - 1)\n focus()\n else\n void focusTag(index + 1)\n return\n }\n\n if (e.key === 'Home') {\n e.preventDefault()\n void focusTag(0)\n return\n }\n\n if (e.key === 'End') {\n e.preventDefault()\n void focusTag(props.modelValue.length - 1)\n return\n }\n\n if (e.key === 'Delete' || e.key === 'Backspace') {\n e.preventDefault()\n void removeAtFromKeyboard(index)\n }\n}\n\nfunction onBlur(): void {\n if (props.addOnBlur)\n void commitInput()\n}\n\nfunction onPaste(e: ClipboardEvent): void {\n if (!canEdit.value || isMaxed.value)\n return\n\n const pasted = e.clipboardData?.getData('text')\n if (!pasted)\n return\n\n const tags = splitToTags(pasted)\n if (tags.length <= 1)\n return\n\n e.preventDefault()\n\n const run = ++validationRun\n validating.value = Boolean(props.beforeAdd)\n\n void filterAllowed(tags, run).then((allowed) => {\n if (run === validationRun)\n validating.value = false\n\n if (allowed?.length)\n addMany(allowed)\n })\n}\n\nconst wrapperClassName = computed(() => {\n return grInputTagWrapperClass({\n size: resolvedSize.value,\n state: props.state,\n invalid: isInvalid.value,\n disabled: isDisabled.value,\n })\n})\n\nconst inputClassName = computed(() => grInputTagInputClass(resolvedSize.value))\n\nconst showClearAll = computed(() => resolvedClearable.value && canEdit.value && props.modelValue.length > 0)\n\nconst placeholderText = computed(() => props.modelValue.length > 0 ? undefined : props.placeholder)\n\ndefineExpose({ focus, blur, clear: clearAll })\n</script>\n\n<template>\n <div\n ref=\"rootEl\"\n data-gr-input-tag\n data-testid=\"gr-input-tag\"\n :class=\"wrapperClassName\"\n @click=\"focus\"\n @focusin=\"onFocusIn\"\n @focusout=\"onFocusOut\"\n >\n <span\n v-if=\"hasPrefix\"\n ref=\"prefixEl\"\n data-gr-input-tag-prefix\n class=\"inline-flex shrink-0 items-center justify-center text-[var(--gr-muted-fg)]\"\n :style=\"prefixStyle\"\n >\n <slot name=\"prefix\" />\n </span>\n\n <!-- Нативная форма: hidden на каждый тег — стандартная сериализация набора. -->\n <template v-if=\"name\">\n <input\n v-for=\"(tag, tagIndex) in modelValue\"\n :key=\"`hidden-${tagIndex}`\"\n type=\"hidden\"\n :name=\"name\"\n :value=\"tag\"\n >\n </template>\n <!--\n `display: contents` — список нужен ради роли, а не ради раскладки: чипы\n обязаны переноситься в одном потоке с полем ввода.\n -->\n <span v-if=\"modelValue.length\" role=\"list\" class=\"contents\">\n <GrBadge\n v-for=\"(tag, i) in modelValue\"\n :key=\"`${tag}-${i}`\"\n role=\"listitem\"\n :tone=\"tagTone\"\n :dark=\"tagDark\"\n :size=\"tagSize\"\n :radius=\"tagRadius\"\n data-gr-input-tag-item\n data-testid=\"gr-input-tag-item\"\n :data-index=\"i\"\n >\n <span class=\"inline-flex items-center gap-1 align-middle\">\n <slot name=\"tag\" :tag=\"tag\" :index=\"i\" :remove=\"() => removeAt(i)\">\n <span class=\"truncate max-w-[18rem]\">{{ tag }}</span>\n </slot>\n\n <button\n v-if=\"showRemove\"\n :ref=\"setRemoveEl(i)\"\n type=\"button\"\n :class=\"removeButtonClass\"\n :aria-label=\"removeTagLabelFor(tag)\"\n :tabindex=\"rovingIndex === i ? 0 : -1\"\n data-gr-input-tag-remove\n data-testid=\"gr-input-tag-remove\"\n :data-index=\"i\"\n @mousedown.prevent.stop\n @focus=\"focusedTagIndex = i\"\n @keydown=\"onTagKeydown($event, i)\"\n @click.stop=\"removeAt(i)\"\n >\n <GrIcon size=\"sm\">\n <IconClose />\n </GrIcon>\n </button>\n </span>\n </GrBadge>\n </span>\n\n <input\n :id=\"resolvedId\"\n ref=\"inputEl\"\n data-gr-input-tag-input\n data-testid=\"gr-input-tag-input\"\n :value=\"inputValue\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :placeholder=\"placeholderText\"\n :aria-label=\"ariaLabel\"\n :aria-describedby=\"describedBy\"\n :aria-required=\"isRequired ? 'true' : undefined\"\n :aria-readonly=\"isReadonly ? 'true' : undefined\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n :aria-busy=\"isBusy ? 'true' : undefined\"\n class=\"flex-1 min-w-[120px] bg-transparent border-none outline-none placeholder:text-[var(--gr-muted-fg)]\"\n :class=\"inputClassName\"\n @input=\"onInput\"\n @keydown=\"onKeydown\"\n @blur=\"onBlur\"\n @paste=\"onPaste\"\n >\n\n <span\n v-if=\"hasSuffix\"\n ref=\"suffixEl\"\n data-gr-input-tag-suffix\n class=\"inline-flex shrink-0 items-center justify-center text-[var(--gr-muted-fg)]\"\n :style=\"suffixStyle\"\n >\n <slot name=\"suffix\" />\n </span>\n\n <span\n v-if=\"isBusy\"\n data-gr-input-tag-spinner\n :class=\"spinnerClass\"\n aria-hidden=\"true\"\n >\n <GrIcon size=\"sm\">\n <IconLoader class=\"animate-spin\" />\n </GrIcon>\n </span>\n\n <button\n v-if=\"showClearAll\"\n type=\"button\"\n data-gr-input-tag-clear\n :class=\"clearButtonClass\"\n :aria-label=\"resolvedClearAllLabel\"\n @mousedown.prevent.stop\n @click.stop=\"clearAll\"\n >\n <GrIcon size=\"sm\">\n <IconClose />\n </GrIcon>\n </button>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, nextTick, ref } from 'vue'\n\nimport GrBadge from '../GrBadge/GrBadge.vue'\nimport type { GrBadgeRadius, GrBadgeSize, GrBadgeTone } from '../GrBadge'\nimport GrIcon from '../GrIcon/GrIcon.vue'\nimport IconClose from '~icons/lucide/x'\nimport IconLoader from '~icons/lucide/loader-2'\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\nimport { useControlAddons } from '../../composables/internal/useControlAddons'\nimport { useGrFormFieldContext } from '../GrFormField/context'\nimport { useAnnouncer } from '../../composables/useAnnouncer'\nimport { useGrFormControl } from '../../composables/useGrFormControl'\nimport { useFocusWithin } from '../../composables/internal/useFocusWithin'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { isComposingEvent } from '../../internal/keyboard'\n\nimport {\n clearButtonClass,\n grInputTagInputClass,\n grInputTagWrapperClass,\n removeButtonClass,\n spinnerClass,\n type GrInputTagSize,\n type GrInputTagState,\n} from './grInputTagStyles'\n\nexport type { GrInputTagSize, GrInputTagState } from './grInputTagStyles'\n\nconst REGEX_SPECIAL_CHAR_RE = /[.*+?^${}()|[\\]\\\\]/g\n\n/**\n * Props for {@link GrInputTag}.\n * Tag-input primitive: renders a set of badges with an inline `<input>` for entering new tags.\n * Splits input by configurable `separators`, supports paste, Enter and Backspace editing.\n */\nexport interface GrInputTagProps {\n modelValue: string[]\n placeholder?: string\n disabled?: boolean\n readonly?: boolean\n invalid?: boolean\n /** Обязательное поле (`aria-required`). Складывается с `required` у `GrFormField`. */\n required?: boolean\n state?: GrInputTagState\n size?: GrInputTagSize\n separators?: string[]\n allowDuplicates?: boolean\n trim?: boolean\n max?: number\n addOnBlur?: boolean\n clearInputOnAdd?: boolean\n /**\n * Проверка тега перед добавлением. Может быть асинхронной (проверка на\n * сервере). Отклонённый тег не добавляется и уходит в событие `reject`.\n */\n beforeAdd?: (tag: string) => boolean | Promise<boolean>\n /** Кнопка «снести все теги». Настраивается через `GrConfigProvider`. */\n clearable?: boolean\n /** Фоновая работа: спиннер + `aria-busy`. Асинхронный `beforeAdd` поднимает его сам. */\n loading?: boolean\n tagTone?: GrBadgeTone\n tagDark?: boolean\n tagSize?: GrBadgeSize\n tagRadius?: GrBadgeRadius\n tagClosable?: boolean\n /** i18n-friendly aria-label for the per-tag remove button. */\n removeTagLabel?: string\n /** i18n aria-label кнопки «снести все». */\n clearAllLabel?: string\n /**\n * Имя контрола, когда он используется вне `GrFormField`. Внутри поля имя даёт\n * связка с его `<label for>` — там проп не нужен. Без того и другого у инпута\n * нет доступного имени вовсе: placeholder именем не считается.\n */\n ariaLabel?: string\n /** Имя для нативной формы: hidden input на каждый тег. */\n name?: string\n /**\n * Ширины аддонов `prefix`/`suffix` — общий контракт контролов пакета\n * (`docs/form-controls.md`).\n */\n prefixMinWidth?: string\n prefixMaxWidth?: string\n suffixMinWidth?: string\n suffixMaxWidth?: string\n prefixFixed?: boolean\n suffixFixed?: boolean\n}\n\nexport interface GrInputTagEmits {\n (e: 'update:modelValue', value: string[]): void\n (e: 'add', value: string): void\n (e: 'remove', value: string, index: number): void\n /** Тег не прошёл `beforeAdd`. */\n (e: 'reject', value: string): void\n /** Набор снесён кнопкой «очистить». */\n (e: 'clear'): void\n /** Набор тегов изменился. */\n (e: 'change', value: string[]): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n}\n\nconst props = withDefaults(\n defineProps<GrInputTagProps>(),\n {\n placeholder: undefined,\n disabled: false,\n readonly: false,\n invalid: false,\n required: false,\n state: 'default',\n // Настраивается через `GrConfigProvider`; дефолт — в резолвере ниже.\n size: undefined,\n separators: () => [','],\n allowDuplicates: false,\n trim: true,\n max: undefined,\n addOnBlur: false,\n clearInputOnAdd: true,\n beforeAdd: undefined,\n clearable: undefined,\n loading: false,\n tagTone: 'neutral',\n tagDark: false,\n tagSize: 'md',\n tagRadius: 'round',\n tagClosable: true,\n removeTagLabel: undefined,\n clearAllLabel: undefined,\n ariaLabel: undefined,\n name: undefined,\n },\n)\n\nconst emit = defineEmits<GrInputTagEmits>()\ndefineSlots<{\n /** Аддон слева от чипов: иконка, метка. */\n prefix?: () => any\n /** Аддон справа, перед кнопкой очистки. */\n suffix?: () => any\n /** Свой чип: `remove` снимает значение. */\n tag?: (props: { tag: string, index: number, remove: () => void }) => any\n}>()\n\n\n// Fallback из контекста `GrFormField` (id/aria-describedby/invalid/required) —\n// как у GrInput и GrSelect: контрол не знает про форму, знает только про поле.\nconst field = useGrFormFieldContext()\nconst resolvedId = computed(() => field?.id.value)\nconst describedBy = computed(() => field?.describedById.value)\nconst {\n disabled: isDisabled,\n invalid: isInvalid,\n required: isRequired,\n readonly: isReadonly,\n} = useGrFormControl(() => props)\n\n// Эффективные значения: локальный проп → `GrConfigProvider` → дефолт компонента.\n/** Ширина аддона по умолчанию — та же лестница, что у `GrInput`. */\nconst ADDON_MIN_WIDTH_BY_SIZE: Record<GrInputTagSize, string> = {\n xs: '2rem',\n sm: '2.25rem',\n md: '2.5rem',\n lg: '3rem',\n}\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrInputTag' })\n\n/**\n * Аддоны — флекс-соседи поля: оболочка тегового поля и так флекс-строка с\n * бейджами, поэтому места паддингом резервировать не нужно.\n */\nconst { hasPrefix, hasSuffix, prefixEl, suffixEl, prefixStyle, suffixStyle } = useControlAddons(() => props, {\n defaultMinWidth: () => ADDON_MIN_WIDTH_BY_SIZE[resolvedSize.value],\n paddingX: () => '0px',\n})\nconst resolvedClearable = useGrComponentProp('GrInputTag', 'clearable', () => props.clearable, false)\n\nconst { t } = useGranularityTranslations()\nconst resolvedClearAllLabel = computed(() => props.clearAllLabel ?? t('gr.inputTag.clearAll', 'Clear all tags'))\n\nfunction removeTagLabelFor(tag: string): string {\n // Безликое «Remove tag» на двадцати кнопках подряд не даёт выбрать нужную:\n // имя обязано называть сам тег.\n return props.removeTagLabel ?? t('gr.inputTag.removeTagNamed', 'Remove tag {tag}', { tag })\n}\n\nconst inputValue = ref('')\nconst inputEl = ref<HTMLInputElement | null>(null)\nconst rootEl = ref<HTMLElement | null>(null)\n\n// Внутри корня фокус ходит между полем и кнопками удаления чипов: без границы\n// каждое такое перемещение давало бы потребителю пару `blur` + `focus`.\nconst { onFocusIn, onFocusOut } = useFocusWithin(rootEl, {\n enter: event => emit('focus', event),\n leave: event => emit('blur', event),\n})\nconst removeEls = ref<HTMLButtonElement[]>([])\n\nfunction setRemoveEl(index: number) {\n return (el: unknown): void => {\n removeEls.value[index] = el as HTMLButtonElement\n }\n}\n\nfunction focus(): void {\n inputEl.value?.focus()\n}\n\nfunction blur(): void {\n inputEl.value?.blur()\n}\n\nconst isMaxed = computed(() => props.max !== undefined && props.modelValue.length >= props.max)\nconst canEdit = computed(() => !isDisabled.value && !isReadonly.value)\nconst showRemove = computed(() => props.tagClosable && canEdit.value)\n\n// Добавление, удаление и достигнутый предел — события, а не состояние виджета:\n// показывать их негде, объявить нужно. Это работа общего живого региона.\nconst { announce } = useAnnouncer()\n\n/**\n * Roving tabindex по крестикам: двадцать тегов давали двадцать одну остановку\n * `Tab`. В таб-порядке ровно один крестик, между ними ходят стрелками.\n */\nconst focusedTagIndex = ref(0)\nconst rovingIndex = computed(() => {\n const last = props.modelValue.length - 1\n if (last < 0)\n return -1\n return Math.min(Math.max(focusedTagIndex.value, 0), last)\n})\n\nasync function focusTag(index: number): Promise<void> {\n const last = props.modelValue.length - 1\n if (last < 0) {\n focus()\n return\n }\n\n const target = Math.min(Math.max(index, 0), last)\n focusedTagIndex.value = target\n await nextTick()\n removeEls.value[target]?.focus()\n}\n\nfunction escapeRegexChar(ch: string): string {\n return ch.replace(REGEX_SPECIAL_CHAR_RE, '\\\\$&')\n}\n\nconst splitRegex = computed(() => {\n const seps = (props.separators ?? []).filter(Boolean).map(s => s[0]!)\n const uniq = [...new Set([...seps, '\\n', '\\r', '\\t'])]\n const body = uniq.map(escapeRegexChar).join('')\n return new RegExp(`[${body}]+`, 'g')\n})\n\nfunction normalizeTag(raw: string): string {\n return props.trim ? raw.trim() : raw\n}\n\nfunction splitToTags(raw: string): string[] {\n return raw\n .split(splitRegex.value)\n .map(v => normalizeTag(v))\n .filter(Boolean)\n}\n\nfunction addMany(rawTags: string[]): boolean {\n if (!canEdit.value || isMaxed.value)\n return false\n\n const next = props.modelValue.slice()\n const added: string[] = []\n\n for (const raw of rawTags) {\n const tag = normalizeTag(raw)\n if (!tag)\n continue\n\n if (!props.allowDuplicates && next.includes(tag))\n continue\n\n if (props.max !== undefined && next.length >= props.max)\n break\n\n next.push(tag)\n added.push(tag)\n emit('add', tag)\n }\n\n if (!added.length)\n return false\n\n emit('update:modelValue', next)\n emit('change', next)\n announce(added.length === 1\n ? t('gr.inputTag.added', 'Tag added: {tag}', { tag: added[0]! })\n : t('gr.inputTag.addedMany', '{count} tags added', { count: added.length }))\n\n return true\n}\n\n// Счётчик запуска: пока идёт асинхронная проверка, пользователь успевает нажать\n// Enter второй раз — результат устаревшей проверки дописывать нельзя.\nlet validationRun = 0\nconst validating = ref(false)\nconst isBusy = computed(() => props.loading || validating.value)\n\nasync function filterAllowed(tags: string[], run: number): Promise<string[] | null> {\n const check = props.beforeAdd\n if (!check)\n return tags\n\n const allowed: string[] = []\n\n for (const tag of tags) {\n const ok = await check(tag)\n if (run !== validationRun)\n return null\n\n if (ok)\n allowed.push(tag)\n else\n emit('reject', tag)\n }\n\n return allowed\n}\n\nasync function commitInput(): Promise<void> {\n if (!canEdit.value || !inputValue.value)\n return\n\n if (isMaxed.value) {\n announce(t('gr.inputTag.limitReached', 'Tag limit reached'))\n return\n }\n\n const raw = inputValue.value\n const tags = splitToTags(raw)\n if (!tags.length)\n return\n\n const run = ++validationRun\n validating.value = Boolean(props.beforeAdd)\n\n const allowed = await filterAllowed(tags, run)\n\n if (run === validationRun)\n validating.value = false\n\n if (allowed === null || !allowed.length)\n return\n\n const changed = addMany(allowed)\n // Значение могло измениться, пока шла проверка: стирать чужой ввод нельзя.\n if (changed && props.clearInputOnAdd && inputValue.value === raw)\n inputValue.value = ''\n}\n\nfunction removeAt(index: number): void {\n if (!canEdit.value)\n return\n\n const next = props.modelValue.slice()\n const removed = next[index]\n if (removed === undefined)\n return\n\n next.splice(index, 1)\n emit('update:modelValue', next)\n emit('change', next)\n emit('remove', removed, index)\n announce(t('gr.inputTag.removed', 'Tag removed: {tag}', { tag: removed }))\n}\n\n/** Удаление с клавиатуры: фокус переезжает на соседний чип, а не пропадает. */\nasync function removeAtFromKeyboard(index: number): Promise<void> {\n removeAt(index)\n await nextTick()\n\n if (props.modelValue.length === 0) {\n focus()\n return\n }\n\n await focusTag(Math.min(index, props.modelValue.length - 1))\n}\n\nfunction clearAll(): void {\n if (!canEdit.value || props.modelValue.length === 0)\n return\n\n emit('update:modelValue', [])\n emit('change', [])\n emit('clear')\n announce(t('gr.inputTag.cleared', 'All tags removed'))\n focus()\n}\n\nfunction onInput(e: Event): void {\n inputValue.value = (e.target as HTMLInputElement).value\n}\n\nfunction onKeydown(e: KeyboardEvent): void {\n // Enter/сепаратор во время IME-композиции коммитят её, а не тег.\n if (isComposingEvent(e))\n return\n if (!canEdit.value)\n return\n\n if (e.key === 'Enter') {\n e.preventDefault()\n void commitInput()\n return\n }\n\n if ((props.separators ?? []).includes(e.key)) {\n e.preventDefault()\n void commitInput()\n return\n }\n\n if (inputValue.value.length === 0 && props.modelValue.length > 0) {\n if (e.key === 'Backspace') {\n e.preventDefault()\n removeAt(props.modelValue.length - 1)\n return\n }\n\n // Из пустого поля стрелка влево уводит на последний чип — дальше по чипам\n // работают те же стрелки, `Tab` для этого не нужен.\n if (e.key === 'ArrowLeft') {\n e.preventDefault()\n void focusTag(props.modelValue.length - 1)\n }\n }\n}\n\nfunction onTagKeydown(e: KeyboardEvent, index: number): void {\n if (e.key === 'ArrowLeft') {\n e.preventDefault()\n void focusTag(index - 1)\n return\n }\n\n if (e.key === 'ArrowRight') {\n e.preventDefault()\n // За последним чипом — поле ввода: продолжение того же ряда.\n if (index === props.modelValue.length - 1)\n focus()\n else\n void focusTag(index + 1)\n return\n }\n\n if (e.key === 'Home') {\n e.preventDefault()\n void focusTag(0)\n return\n }\n\n if (e.key === 'End') {\n e.preventDefault()\n void focusTag(props.modelValue.length - 1)\n return\n }\n\n if (e.key === 'Delete' || e.key === 'Backspace') {\n e.preventDefault()\n void removeAtFromKeyboard(index)\n }\n}\n\nfunction onBlur(): void {\n if (props.addOnBlur)\n void commitInput()\n}\n\nfunction onPaste(e: ClipboardEvent): void {\n if (!canEdit.value || isMaxed.value)\n return\n\n const pasted = e.clipboardData?.getData('text')\n if (!pasted)\n return\n\n const tags = splitToTags(pasted)\n if (tags.length <= 1)\n return\n\n e.preventDefault()\n\n const run = ++validationRun\n validating.value = Boolean(props.beforeAdd)\n\n void filterAllowed(tags, run).then((allowed) => {\n if (run === validationRun)\n validating.value = false\n\n if (allowed?.length)\n addMany(allowed)\n })\n}\n\nconst wrapperClassName = computed(() => {\n return grInputTagWrapperClass({\n size: resolvedSize.value,\n state: props.state,\n invalid: isInvalid.value,\n disabled: isDisabled.value,\n })\n})\n\nconst inputClassName = computed(() => grInputTagInputClass(resolvedSize.value))\n\nconst showClearAll = computed(() => resolvedClearable.value && canEdit.value && props.modelValue.length > 0)\n\nconst placeholderText = computed(() => props.modelValue.length > 0 ? undefined : props.placeholder)\n\ndefineExpose({ focus, blur, clear: clearAll })\n</script>\n\n<template>\n <div\n ref=\"rootEl\"\n data-gr-input-tag\n data-testid=\"gr-input-tag\"\n :class=\"wrapperClassName\"\n @click=\"focus\"\n @focusin=\"onFocusIn\"\n @focusout=\"onFocusOut\"\n >\n <span\n v-if=\"hasPrefix\"\n ref=\"prefixEl\"\n data-gr-input-tag-prefix\n class=\"inline-flex shrink-0 items-center justify-center text-[var(--gr-muted-fg)]\"\n :style=\"prefixStyle\"\n >\n <slot name=\"prefix\" />\n </span>\n\n <!-- Нативная форма: hidden на каждый тег — стандартная сериализация набора. -->\n <template v-if=\"name\">\n <input\n v-for=\"(tag, tagIndex) in modelValue\"\n :key=\"`hidden-${tagIndex}`\"\n type=\"hidden\"\n :name=\"name\"\n :value=\"tag\"\n >\n </template>\n <!--\n `display: contents` — список нужен ради роли, а не ради раскладки: чипы\n обязаны переноситься в одном потоке с полем ввода.\n -->\n <span v-if=\"modelValue.length\" role=\"list\" class=\"contents\">\n <GrBadge\n v-for=\"(tag, i) in modelValue\"\n :key=\"`${tag}-${i}`\"\n role=\"listitem\"\n :tone=\"tagTone\"\n :dark=\"tagDark\"\n :size=\"tagSize\"\n :radius=\"tagRadius\"\n data-gr-input-tag-item\n data-testid=\"gr-input-tag-item\"\n :data-index=\"i\"\n >\n <span class=\"inline-flex items-center gap-1 align-middle\">\n <slot name=\"tag\" :tag=\"tag\" :index=\"i\" :remove=\"() => removeAt(i)\">\n <span class=\"truncate max-w-[18rem]\">{{ tag }}</span>\n </slot>\n\n <button\n v-if=\"showRemove\"\n :ref=\"setRemoveEl(i)\"\n type=\"button\"\n :class=\"removeButtonClass\"\n :aria-label=\"removeTagLabelFor(tag)\"\n :tabindex=\"rovingIndex === i ? 0 : -1\"\n data-gr-input-tag-remove\n data-testid=\"gr-input-tag-remove\"\n :data-index=\"i\"\n @mousedown.prevent.stop\n @focus=\"focusedTagIndex = i\"\n @keydown=\"onTagKeydown($event, i)\"\n @click.stop=\"removeAt(i)\"\n >\n <GrIcon size=\"sm\">\n <IconClose />\n </GrIcon>\n </button>\n </span>\n </GrBadge>\n </span>\n\n <input\n :id=\"resolvedId\"\n ref=\"inputEl\"\n data-gr-input-tag-input\n data-testid=\"gr-input-tag-input\"\n :value=\"inputValue\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :placeholder=\"placeholderText\"\n :aria-label=\"ariaLabel\"\n :aria-describedby=\"describedBy\"\n :aria-required=\"isRequired ? 'true' : undefined\"\n :aria-readonly=\"isReadonly ? 'true' : undefined\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n :aria-busy=\"isBusy ? 'true' : undefined\"\n class=\"flex-1 min-w-[120px] bg-transparent border-none outline-none placeholder:text-[var(--gr-muted-fg)]\"\n :class=\"inputClassName\"\n @input=\"onInput\"\n @keydown=\"onKeydown\"\n @blur=\"onBlur\"\n @paste=\"onPaste\"\n >\n\n <span\n v-if=\"hasSuffix\"\n ref=\"suffixEl\"\n data-gr-input-tag-suffix\n class=\"inline-flex shrink-0 items-center justify-center text-[var(--gr-muted-fg)]\"\n :style=\"suffixStyle\"\n >\n <slot name=\"suffix\" />\n </span>\n\n <span\n v-if=\"isBusy\"\n data-gr-input-tag-spinner\n :class=\"spinnerClass\"\n aria-hidden=\"true\"\n >\n <GrIcon size=\"sm\">\n <IconLoader class=\"animate-spin\" />\n </GrIcon>\n </span>\n\n <button\n v-if=\"showClearAll\"\n type=\"button\"\n data-gr-input-tag-clear\n :class=\"clearButtonClass\"\n :aria-label=\"resolvedClearAllLabel\"\n @mousedown.prevent.stop\n @click.stop=\"clearAll\"\n >\n <GrIcon size=\"sm\">\n <IconClose />\n </GrIcon>\n </button>\n </div>\n</template>\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"GrKbd-DRFPvW3G.js","names":[],"sources":["../../../../src/components/shared/hotkey.ts","../../../../src/components/GrKbd/GrKbd.vue","../../../../src/components/GrKbd/GrKbd.vue"],"sourcesContent":["/**\n * Разбор, матчинг и человекочитаемое представление сочетаний клавиш.\n *\n * Общий модуль, а не часть `GrCommandPalette`: то же нужно `GrKbd`, а импорт из\n * чужой компонентной директории на сборке дал бы ребро `GrKbd →\n * GrCommandPalette` (хелпер уехал бы в чанк палитры) — `granular doctor` считает\n * такое незадекларированной зависимостью.\n *\n * Директива `v-hotkey` для палитры не годится: ей нужен смонтированный элемент,\n * а палитра в закрытом состоянии не рендерит ничего. Поэтому здесь — свой\n * матчер на `window`, с тем же синтаксисом комбинаций плюс токен `mod`.\n */\n\nimport { eventMatchesKey, isComposingEvent, shiftSatisfied } from '../../internal/keyboard'\n\nexport type ParsedCommandHotkey = {\n key: string\n ctrl: boolean\n meta: boolean\n alt: boolean\n shift: boolean\n /** `mod` — Cmd на macOS, Ctrl на остальных платформах. */\n mod: boolean\n}\n\nexport function parseCommandHotkey(combo: string): ParsedCommandHotkey | null {\n const parts = combo.split('+').map(p => p.trim()).filter(Boolean)\n const keyToken = parts.at(-1)\n if (!keyToken) return null\n\n const parsed: ParsedCommandHotkey = {\n key: keyToken.length === 1 ? keyToken.toLowerCase() : keyToken,\n ctrl: false,\n meta: false,\n alt: false,\n shift: false,\n mod: false,\n }\n\n for (const part of parts.slice(0, -1)) {\n const token = part.toLowerCase()\n if (token === 'mod') parsed.mod = true\n else if (token === 'ctrl' || token === 'control') parsed.ctrl = true\n else if (token === 'meta' || token === 'cmd' || token === 'command' || token === '⌘') parsed.meta = true\n else if (token === 'alt' || token === 'option') parsed.alt = true\n else if (token === 'shift') parsed.shift = true\n }\n\n return parsed\n}\n\n/** macOS определяем по платформе — от неё зависит, чем является `mod`. */\nexport function isAppleDevice(): boolean {\n if (typeof navigator === 'undefined') return false\n return /Mac|iPhone|iPad|iPod/.test(navigator.platform || navigator.userAgent)\n}\n\nexport function matchesCommandHotkey(\n event: KeyboardEvent,\n hotkey: ParsedCommandHotkey,\n apple = isAppleDevice(),\n): boolean {\n // Клавиша во время IME-композиции принадлежит композиции, а не сочетанию.\n if (isComposingEvent(event)) return false\n\n const expectMeta = hotkey.meta || (hotkey.mod && apple)\n const expectCtrl = hotkey.ctrl || (hotkey.mod && !apple)\n\n if (event.metaKey !== expectMeta) return false\n if (event.ctrlKey !== expectCtrl) return false\n if (event.altKey !== hotkey.alt) return false\n if (!shiftSatisfied(event, hotkey.key, hotkey.shift)) return false\n\n // Комбинация с модификаторами матчится и по физическому коду: на нелатинской\n // раскладке `mod+K` приходит как `key: 'л'`, и без кода сочетание мертво.\n return eventMatchesKey(event, hotkey.key, {\n codeFallback: expectMeta || expectCtrl || hotkey.alt,\n })\n}\n\n/** Человекочитаемые клавиши для подсказки в поле ввода (`⌘` / `Ctrl`). */\nexport function formatCommandHotkey(hotkey: ParsedCommandHotkey, apple = isAppleDevice()): string[] {\n const keys: string[] = []\n if (hotkey.meta || (hotkey.mod && apple)) keys.push('⌘')\n if (hotkey.ctrl || (hotkey.mod && !apple)) keys.push('Ctrl')\n if (hotkey.alt) keys.push(apple ? '⌥' : 'Alt')\n if (hotkey.shift) keys.push(apple ? '⇧' : 'Shift')\n keys.push(hotkey.key.length === 1 ? hotkey.key.toUpperCase() : hotkey.key)\n return keys\n}\n\n/** Одна клавиша в человекочитаемом виде. */\nexport interface HotkeyKeyView {\n /** Что видно на экране: символ (`⌘`) или слово (`Ctrl`). */\n label: string\n /**\n * Ключ читаемого имени (`gr.kbd.<name>`) для символьных клавиш. У `K` или\n * `Ctrl` его нет — они и так читаются вслух.\n */\n name?: 'command' | 'option' | 'shift' | 'control' | 'enter' | 'escape'\n}\n\n/**\n * Токен → то, что видит пользователь. `mod` зависит от платформы: на macOS это\n * Cmd, на остальных — Ctrl. Символы отдаются вместе с ключом читаемого имени:\n * `⌘` без имени диктор произносит как «знак места интереса».\n */\nexport function formatHotkeyToken(token: string, apple: boolean): HotkeyKeyView {\n const t = token.trim()\n const lower = t.toLowerCase()\n\n // Читаемое имя добавляется только к символу: у слова `Ctrl` оно дало бы\n // диктору «Ctrl Control».\n if (lower === 'mod')\n return apple ? { label: '⌘', name: 'command' } : { label: 'Ctrl' }\n\n if (lower === 'meta' || lower === 'cmd' || lower === 'command' || t === '⌘')\n return apple ? { label: '⌘', name: 'command' } : { label: 'Meta' }\n\n if (lower === 'ctrl' || lower === 'control' || t === '⌃')\n return apple ? { label: '⌃', name: 'control' } : { label: 'Ctrl' }\n\n if (lower === 'alt' || lower === 'option' || t === '⌥')\n return apple ? { label: '⌥', name: 'option' } : { label: 'Alt' }\n\n if (lower === 'shift' || t === '⇧')\n return apple ? { label: '⇧', name: 'shift' } : { label: 'Shift' }\n\n if (lower === 'enter' || lower === 'return' || t === '↵' || t === '↩')\n return apple ? { label: '↩', name: 'enter' } : { label: 'Enter' }\n\n if (lower === 'esc' || lower === 'escape')\n return { label: 'Esc', name: 'escape' }\n\n if (lower === 'space')\n return { label: 'Space' }\n\n // Одиночный символ — заглавным: `k` и `K` на клавише выглядят одинаково.\n return { label: t.length === 1 ? t.toUpperCase() : t }\n}\n\nexport function formatHotkeyTokens(tokens: readonly string[], apple: boolean): HotkeyKeyView[] {\n return tokens.map(token => formatHotkeyToken(token, apple))\n}\n\n/** Комбинация строкой (`mod+shift+K`) — в набор токенов в порядке показа. */\nexport function splitHotkeyCombo(combo: string): string[] {\n return combo.split('+').map(part => part.trim()).filter(Boolean)\n}\n","<script setup lang=\"ts\">\nimport { computed, onMounted, ref } from 'vue'\n\nimport { useGrComponentSize } from '../GrConfigProvider/context'\nimport type { GrComponentSize } from '../shared/sizes'\nimport { formatHotkeyTokens, isAppleDevice, splitHotkeyCombo } from '../shared/hotkey'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\n\nimport { grKbdComboClass, grKbdKeyClass, separatorClass } from './grKbdStyles'\n\nexport interface GrKbdProps {\n size?: GrKbdSize\n /**\n * Сочетание: строкой (`\"mod+shift+K\"`) или набором токенов\n * (`['mod', 'K']`). Токен `mod` — Cmd на macOS, Ctrl на остальных.\n */\n keys?: string | string[]\n /** Разделитель между клавишами сочетания. Пустая строка — только зазор. */\n separator?: string\n platform?: GrKbdPlatform\n}\n\n/**\n * GrKbd — GR-примитив для отображения клавиш и сочетаний (`<kbd>`).\n *\n * Одна клавиша — слотом: `<GrKbd>Esc</GrKbd>`.\n * Сочетание — пропом: `<GrKbd keys=\"mod+K\" />` (на macOS `⌘ K`, иначе `Ctrl K`).\n */\nexport type GrKbdSize = GrComponentSize\n\n/** Откуда брать платформу для токена `mod`. */\nexport type GrKbdPlatform = 'auto' | 'apple' | 'other'\n\nconst props = withDefaults(\n defineProps<GrKbdProps>(),\n {\n size: undefined,\n keys: undefined,\n separator: '+',\n platform: 'auto',\n },\n)\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrKbd' })\nconst { t } = useGranularityTranslations()\n\n/**\n * Платформа уточняется только после монтирования: `navigator` в теле `setup`\n * либо роняет серверный рендер, либо расходится с ним и ломает гидрацию.\n * До этого момента показываем не-Apple вариант — он же приходит с сервера.\n */\nconst detectedApple = ref(false)\n\nonMounted(() => {\n detectedApple.value = isAppleDevice()\n})\n\nconst isApple = computed(() => {\n if (props.platform === 'apple') return true\n if (props.platform === 'other') return false\n return detectedApple.value\n})\n\nconst tokens = computed(() => {\n if (props.keys === undefined) return []\n return Array.isArray(props.keys) ? props.keys : splitHotkeyCombo(props.keys)\n})\n\n// Сочетание рисуется вложенными `<kbd>` — приём из спецификации HTML, а не\n// обёртка-div: сочетание остаётся клавиатурным вводом целиком.\nconst keyViews = computed(() => formatHotkeyTokens(tokens.value, isApple.value))\nconst hasCombo = computed(() => keyViews.value.length > 0)\n\nconst keyClass = computed(() => grKbdKeyClass(resolvedSize.value))\nconst comboClass = computed(() => grKbdComboClass(resolvedSize.value))\n\nconst NAME_FALLBACKS: Record<string, string> = {\n command: 'Command',\n option: 'Option',\n shift: 'Shift',\n control: 'Control',\n enter: 'Enter',\n escape: 'Escape',\n}\n\n/** Читаемое имя символьной клавиши: `⌘` без него диктор произносит как знак. */\nfunction readableName(name: string): string {\n return t(`gr.kbd.${name}`, NAME_FALLBACKS[name] ?? name)\n}\n</script>\n\n<template>\n <kbd\n v-if=\"hasCombo\"\n data-gr-kbd\n data-gr-kbd-combo\n :class=\"comboClass\"\n ><template\n v-for=\"(key, index) in keyViews\"\n :key=\"`${key.label}-${index}`\"\n ><span\n v-if=\"index > 0 && separator\"\n :class=\"separatorClass\"\n aria-hidden=\"true\"\n >{{ separator }}</span><kbd\n data-gr-kbd-key\n :class=\"keyClass\"\n ><span :aria-hidden=\"key.name ? 'true' : undefined\">{{ key.label }}</span><span\n v-if=\"key.name\"\n class=\"sr-only\"\n >{{ readableName(key.name) }}</span></kbd></template></kbd>\n\n <kbd\n v-else\n data-gr-kbd\n data-gr-kbd-key\n :class=\"keyClass\"\n ><slot /></kbd>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, onMounted, ref } from 'vue'\n\nimport { useGrComponentSize } from '../GrConfigProvider/context'\nimport type { GrComponentSize } from '../shared/sizes'\nimport { formatHotkeyTokens, isAppleDevice, splitHotkeyCombo } from '../shared/hotkey'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\n\nimport { grKbdComboClass, grKbdKeyClass, separatorClass } from './grKbdStyles'\n\nexport interface GrKbdProps {\n size?: GrKbdSize\n /**\n * Сочетание: строкой (`\"mod+shift+K\"`) или набором токенов\n * (`['mod', 'K']`). Токен `mod` — Cmd на macOS, Ctrl на остальных.\n */\n keys?: string | string[]\n /** Разделитель между клавишами сочетания. Пустая строка — только зазор. */\n separator?: string\n platform?: GrKbdPlatform\n}\n\n/**\n * GrKbd — GR-примитив для отображения клавиш и сочетаний (`<kbd>`).\n *\n * Одна клавиша — слотом: `<GrKbd>Esc</GrKbd>`.\n * Сочетание — пропом: `<GrKbd keys=\"mod+K\" />` (на macOS `⌘ K`, иначе `Ctrl K`).\n */\nexport type GrKbdSize = GrComponentSize\n\n/** Откуда брать платформу для токена `mod`. */\nexport type GrKbdPlatform = 'auto' | 'apple' | 'other'\n\nconst props = withDefaults(\n defineProps<GrKbdProps>(),\n {\n size: undefined,\n keys: undefined,\n separator: '+',\n platform: 'auto',\n },\n)\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrKbd' })\nconst { t } = useGranularityTranslations()\n\n/**\n * Платформа уточняется только после монтирования: `navigator` в теле `setup`\n * либо роняет серверный рендер, либо расходится с ним и ломает гидрацию.\n * До этого момента показываем не-Apple вариант — он же приходит с сервера.\n */\nconst detectedApple = ref(false)\n\nonMounted(() => {\n detectedApple.value = isAppleDevice()\n})\n\nconst isApple = computed(() => {\n if (props.platform === 'apple') return true\n if (props.platform === 'other') return false\n return detectedApple.value\n})\n\nconst tokens = computed(() => {\n if (props.keys === undefined) return []\n return Array.isArray(props.keys) ? props.keys : splitHotkeyCombo(props.keys)\n})\n\n// Сочетание рисуется вложенными `<kbd>` — приём из спецификации HTML, а не\n// обёртка-div: сочетание остаётся клавиатурным вводом целиком.\nconst keyViews = computed(() => formatHotkeyTokens(tokens.value, isApple.value))\nconst hasCombo = computed(() => keyViews.value.length > 0)\n\nconst keyClass = computed(() => grKbdKeyClass(resolvedSize.value))\nconst comboClass = computed(() => grKbdComboClass(resolvedSize.value))\n\nconst NAME_FALLBACKS: Record<string, string> = {\n command: 'Command',\n option: 'Option',\n shift: 'Shift',\n control: 'Control',\n enter: 'Enter',\n escape: 'Escape',\n}\n\n/** Читаемое имя символьной клавиши: `⌘` без него диктор произносит как знак. */\nfunction readableName(name: string): string {\n return t(`gr.kbd.${name}`, NAME_FALLBACKS[name] ?? name)\n}\n</script>\n\n<template>\n <kbd\n v-if=\"hasCombo\"\n data-gr-kbd\n data-gr-kbd-combo\n :class=\"comboClass\"\n ><template\n v-for=\"(key, index) in keyViews\"\n :key=\"`${key.label}-${index}`\"\n ><span\n v-if=\"index > 0 && separator\"\n :class=\"separatorClass\"\n aria-hidden=\"true\"\n >{{ separator }}</span><kbd\n data-gr-kbd-key\n :class=\"keyClass\"\n ><span :aria-hidden=\"key.name ? 'true' : undefined\">{{ key.label }}</span><span\n v-if=\"key.name\"\n class=\"sr-only\"\n >{{ readableName(key.name) }}</span></kbd></template></kbd>\n\n <kbd\n v-else\n data-gr-kbd\n data-gr-kbd-key\n :class=\"keyClass\"\n ><slot /></kbd>\n</template>\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"GrPagination-B6N0QZBf.js","names":[],"sources":["../../../../src/components/GrPagination/GrPagination.vue","../../../../src/components/GrPagination/GrPagination.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\n\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport GrButton from '../GrButton/GrButton.vue'\nimport GrSelect from '../GrSelect/GrSelect.vue'\nimport { useGrComponentSize } from '../GrConfigProvider/context'\nimport {\n type GrPaginationSize,\n ellipsisSizes,\n jumperSizes,\n labelSizes,\n navButtonSizes,\n pageListGaps,\n pageSizes,\n pageSizeSelectWidths,\n rowGaps,\n} from './grPaginationStyles'\n\n/**\n * Пропы пагинации.\n *\n * `total` интерпретируется как общее количество элементов; число страниц\n * вычисляется из `total` и `pageSize`. Номера усекаются многоточием\n * (алгоритм boundary/sibling, как у MUI): всегда видны первая/последняя страница\n * и `siblingCount` соседей вокруг текущей.\n */\nexport interface GrPaginationProps {\n page: number\n pageSize: number\n total: number\n pageSizes?: number[]\n /** Сколько соседних страниц показывать вокруг текущей. По умолчанию `1`. */\n siblingCount?: number\n /** Сколько крайних страниц всегда показывать с каждого края. По умолчанию `1`. */\n boundaryCount?: number\n /**\n * Компактный вариант: вместо нумерованных страниц показывается индикатор\n * «текущая / всего» — удобно для узких мест (мобайл, тулбары таблиц).\n */\n compact?: boolean\n /** Показывать поле «перейти к странице» с быстрым переходом по вводу номера. */\n showJumper?: boolean\n /** Показывать селект размера страницы. */\n showPageSize?: boolean\n /** Показывать диапазон показанных элементов — «1–20 из 137». Слот `#total` сильнее. */\n showTotal?: boolean\n /** i18n-подпись перед полем перехода. По умолчанию — `gr.pagination.jumpTo`. */\n jumperLabel?: string\n /**\n * Имя навигационного лендмарка. По умолчанию — `gr.pagination.label`; задавать\n * стоит там, где пагинаций на странице несколько и их надо различать.\n */\n ariaLabel?: string\n /** Гасит всю пагинацию: номера, кнопки, селект размера и поле перехода. */\n disabled?: boolean\n size?: GrPaginationSize\n}\n\nexport interface GrPaginationEmits {\n (e: 'update:page', value: number): void\n (e: 'update:pageSize', value: number): void\n}\n\nconst { t } = useGranularityTranslations()\n\nconst props = withDefaults(defineProps<GrPaginationProps>(), {\n pageSizes: () => [10, 20, 50],\n siblingCount: 1,\n boundaryCount: 1,\n compact: false,\n showJumper: false,\n showPageSize: false,\n showTotal: false,\n jumperLabel: undefined,\n ariaLabel: undefined,\n disabled: false,\n size: undefined,\n})\n\ndefineSlots<{\n /** Диапазон показанных элементов целиком — вместо строки из локали. */\n total?: (props: { from: number, to: number, total: number }) => unknown\n}>()\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrPagination' })\n\nconst rowClass = computed(() => rowGaps[resolvedSize.value])\nconst pageListClass = computed(() => pageListGaps[resolvedSize.value])\nconst pageClass = computed(() => pageSizes[resolvedSize.value])\nconst jumperClass = computed(() => jumperSizes[resolvedSize.value])\nconst ellipsisClass = computed(() => ellipsisSizes[resolvedSize.value])\nconst labelClass = computed(() => labelSizes[resolvedSize.value])\nconst selectWrapClass = computed(() => pageSizeSelectWidths[resolvedSize.value])\nconst navButtonSize = computed(() => navButtonSizes[resolvedSize.value])\n\nconst emit = defineEmits<GrPaginationEmits>()\n\n// Делитель клампится: `pageSize: 0` дал бы `Infinity` страниц и бесконечный\n// цикл в `range()`.\nconst resolvedPageSize = computed(() => Math.max(1, Math.trunc(props.pageSize)))\nconst pageCount = computed(() => Math.max(1, Math.ceil(props.total / resolvedPageSize.value)))\n\n// Рендер идёт от зажатого номера: вотчер ниже просит родителя подтянуть `page`,\n// но на первом рендере он молчит, и без клампа пагинация осталась бы вовсе без\n// активной страницы.\nconst currentPage = computed(() => Math.min(Math.max(Math.trunc(props.page), 1), pageCount.value))\n\n// Контролируемый компонент: если `total`/`pageSize` уменьшились и текущая `page`\n// вышла за диапазон — просим родителя подтянуть её к последней странице.\nwatch(pageCount, (count) => {\n if (props.page > count)\n emit('update:page', count)\n})\n\nconst pageSizeModel = computed({\n get: () => String(props.pageSize),\n set: (value: string) => emit('update:pageSize', Number(value)),\n})\n\nconst pageSizeOptions = computed(() =>\n props.pageSizes.map(pageSize => ({\n value: String(pageSize),\n label: String(pageSize),\n })),\n)\n\nconst rangeFrom = computed(() => (props.total === 0 ? 0 : (currentPage.value - 1) * resolvedPageSize.value + 1))\nconst rangeTo = computed(() => Math.min(currentPage.value * resolvedPageSize.value, props.total))\n\ntype PaginationItem = number | 'ellipsis-start' | 'ellipsis-end'\n\nfunction range(start: number, end: number): number[] {\n const list: number[] = []\n for (let page = start; page <= end; page += 1)\n list.push(page)\n return list\n}\n\n// Алгоритм usePagination как в MUI: крайние `boundaryCount` страниц + `siblingCount`\n// соседей вокруг текущей, разрывы схлопываются в многоточие. Одиночный «пропущенный»\n// номер показываем как номер, а не как «…» (визуально ровнее).\nconst items = computed<PaginationItem[]>(() => {\n const count = pageCount.value\n const current = currentPage.value\n const boundaryCount = Math.max(0, props.boundaryCount)\n const siblingCount = Math.max(0, props.siblingCount)\n\n const startPages = range(1, Math.min(boundaryCount, count))\n const endPages = range(Math.max(count - boundaryCount + 1, boundaryCount + 1), count)\n\n const siblingsStart = Math.max(\n Math.min(current - siblingCount, count - boundaryCount - siblingCount * 2 - 1),\n boundaryCount + 2,\n )\n const siblingsEnd = Math.min(\n Math.max(current + siblingCount, boundaryCount + siblingCount * 2 + 2),\n endPages.length > 0 ? endPages[0] - 2 : count - 1,\n )\n\n return [\n ...startPages,\n ...(siblingsStart > boundaryCount + 2\n ? ['ellipsis-start' as const]\n : boundaryCount + 1 < count - boundaryCount\n ? [boundaryCount + 1]\n : []),\n ...range(siblingsStart, siblingsEnd),\n ...(siblingsEnd < count - boundaryCount - 1\n ? ['ellipsis-end' as const]\n : count - boundaryCount > boundaryCount\n ? [count - boundaryCount]\n : []),\n ...endPages,\n ]\n})\n\nfunction goTo(page: number): void {\n if (props.disabled)\n return\n\n emit('update:page', page)\n}\n\nfunction prev(): void {\n goTo(Math.max(1, currentPage.value - 1))\n}\n\nfunction next(): void {\n goTo(Math.min(pageCount.value, currentPage.value + 1))\n}\n\nfunction first(): void {\n goTo(1)\n}\n\nfunction last(): void {\n goTo(pageCount.value)\n}\n\n// «Перейти к странице»: клампим введённый номер к диапазону [1, pageCount]\n// и сбрасываем поле после перехода.\nconst jumperValue = ref('')\n\nfunction submitJumper(): void {\n const parsed = Number.parseInt(jumperValue.value, 10)\n jumperValue.value = ''\n if (Number.isNaN(parsed))\n return\n\n goTo(Math.min(pageCount.value, Math.max(1, parsed)))\n}\n\nconst resolvedJumperLabel = computed(() => props.jumperLabel ?? t('gr.pagination.jumpTo', 'Go to'))\nconst resolvedAriaLabel = computed(() => props.ariaLabel ?? t('gr.pagination.label', 'Pagination'))\nconst statusText = computed(() =>\n t('gr.pagination.status', 'Page {page} of {count}', { page: currentPage.value, count: pageCount.value }),\n)\nconst totalText = computed(() =>\n t('gr.pagination.total', '{from}–{to} of {total}', { from: rangeFrom.value, to: rangeTo.value, total: props.total }),\n)\n</script>\n\n<template>\n <div\n class=\"flex flex-wrap items-center justify-end\"\n :class=\"rowClass\"\n data-gr-pagination\n role=\"navigation\"\n :aria-label=\"resolvedAriaLabel\"\n >\n <div\n v-if=\"showTotal\"\n data-gr-pagination-total\n class=\"text-[var(--gr-muted-fg)] [font-variant-numeric:tabular-nums]\"\n :class=\"labelClass\"\n >\n <slot name=\"total\" :from=\"rangeFrom\" :to=\"rangeTo\" :total=\"total\">\n {{ totalText }}\n </slot>\n </div>\n\n <div v-if=\"showPageSize\" :class=\"selectWrapClass\">\n <GrSelect\n v-model=\"pageSizeModel\"\n :options=\"pageSizeOptions\"\n :size=\"resolvedSize\"\n :disabled=\"disabled\"\n :aria-label=\"t('gr.pagination.pageSize', 'Page size')\"\n />\n </div>\n\n <GrButton variant=\"ghost\" :size=\"navButtonSize\" :disabled=\"disabled || currentPage <= 1\" :aria-label=\"t('gr.pagination.first', 'First page')\" data-gr-pagination-first @click=\"first\">\n «\n </GrButton>\n\n <GrButton variant=\"ghost\" :size=\"navButtonSize\" :disabled=\"disabled || currentPage <= 1\" data-gr-pagination-prev @click=\"prev\">\n {{ t('gr.pagination.prev', 'Prev') }}\n </GrButton>\n\n <!-- Компактный вариант: индикатор «текущая / всего» вместо нумерованных страниц. -->\n <div\n v-if=\"compact\"\n data-gr-pagination-compact\n class=\"px-2 text-[var(--gr-fg)] [font-variant-numeric:tabular-nums]\"\n :class=\"labelClass\"\n role=\"status\"\n >\n {{ currentPage }} / {{ pageCount }}\n </div>\n\n <template v-else>\n <ul class=\"flex items-center [list-style:none]\" :class=\"pageListClass\" data-gr-pagination-pages role=\"list\">\n <li v-for=\"(item, index) in items\" :key=\"index\" :aria-hidden=\"typeof item === 'string' ? 'true' : undefined\">\n <span\n v-if=\"item === 'ellipsis-start' || item === 'ellipsis-end'\"\n data-gr-pagination-ellipsis\n class=\"grid place-items-center text-[var(--gr-muted-fg)]\"\n :class=\"ellipsisClass\"\n >…</span>\n <button\n v-else\n type=\"button\"\n data-gr-pagination-page\n :disabled=\"disabled\"\n :aria-current=\"item === currentPage ? 'page' : undefined\"\n :aria-label=\"t('gr.pagination.page', 'Page {n}', { n: item })\"\n class=\"rounded-[var(--gr-radius-control)] font-600 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 :class=\"[\n pageClass,\n item === currentPage\n ? 'bg-[var(--gr-primary)] text-[var(--gr-primary-fg)]'\n // Подложка тональная, а не `--gr-muted`: серый на белом фоне даёт\n // 1.10:1 и на светлой теме hover был практически не виден, хотя у\n // соседних «назад/вперёд» (ghost-кнопки) он тонирован. Ряд обязан\n // читаться одним контролом.\n : 'text-[var(--gr-muted-fg)] hover:bg-[var(--gr-accent)] hover:text-[var(--gr-accent-fg)]',\n ]\"\n @click=\"goTo(item)\"\n >\n {{ item }}\n </button>\n </li>\n </ul>\n\n <!-- В компактном режиме смену страницы объявляет видимый индикатор. -->\n <div data-gr-pagination-status role=\"status\" class=\"sr-only\">\n {{ statusText }}\n </div>\n </template>\n\n <GrButton variant=\"ghost\" :size=\"navButtonSize\" :disabled=\"disabled || currentPage >= pageCount\" data-gr-pagination-next @click=\"next\">\n {{ t('gr.pagination.next', 'Next') }}\n </GrButton>\n\n <GrButton variant=\"ghost\" :size=\"navButtonSize\" :disabled=\"disabled || currentPage >= pageCount\" :aria-label=\"t('gr.pagination.last', 'Last page')\" data-gr-pagination-last @click=\"last\">\n »\n </GrButton>\n\n <!-- «Перейти к странице»: Enter или blur применяют введённый номер. -->\n <div v-if=\"showJumper\" class=\"flex items-center gap-2 text-[var(--gr-muted-fg)]\" :class=\"labelClass\">\n <span>{{ resolvedJumperLabel }}</span>\n <input\n v-model=\"jumperValue\"\n type=\"number\"\n min=\"1\"\n :max=\"pageCount\"\n inputmode=\"numeric\"\n data-gr-pagination-jumper\n :disabled=\"disabled\"\n :aria-label=\"resolvedJumperLabel\"\n class=\"rounded-[var(--gr-radius-control)] border border-[var(--gr-brd)] bg-[var(--gr-bg)] px-2 text-center text-[var(--gr-fg)] [font-variant-numeric:tabular-nums] 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 :class=\"jumperClass\"\n @keydown.enter=\"submitJumper\"\n @blur=\"submitJumper\"\n >\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\n\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport GrButton from '../GrButton/GrButton.vue'\nimport GrSelect from '../GrSelect/GrSelect.vue'\nimport { useGrComponentSize } from '../GrConfigProvider/context'\nimport {\n type GrPaginationSize,\n ellipsisSizes,\n jumperSizes,\n labelSizes,\n navButtonSizes,\n pageListGaps,\n pageSizes,\n pageSizeSelectWidths,\n rowGaps,\n} from './grPaginationStyles'\n\n/**\n * Пропы пагинации.\n *\n * `total` интерпретируется как общее количество элементов; число страниц\n * вычисляется из `total` и `pageSize`. Номера усекаются многоточием\n * (алгоритм boundary/sibling, как у MUI): всегда видны первая/последняя страница\n * и `siblingCount` соседей вокруг текущей.\n */\nexport interface GrPaginationProps {\n page: number\n pageSize: number\n total: number\n pageSizes?: number[]\n /** Сколько соседних страниц показывать вокруг текущей. По умолчанию `1`. */\n siblingCount?: number\n /** Сколько крайних страниц всегда показывать с каждого края. По умолчанию `1`. */\n boundaryCount?: number\n /**\n * Компактный вариант: вместо нумерованных страниц показывается индикатор\n * «текущая / всего» — удобно для узких мест (мобайл, тулбары таблиц).\n */\n compact?: boolean\n /** Показывать поле «перейти к странице» с быстрым переходом по вводу номера. */\n showJumper?: boolean\n /** Показывать селект размера страницы. */\n showPageSize?: boolean\n /** Показывать диапазон показанных элементов — «1–20 из 137». Слот `#total` сильнее. */\n showTotal?: boolean\n /** i18n-подпись перед полем перехода. По умолчанию — `gr.pagination.jumpTo`. */\n jumperLabel?: string\n /**\n * Имя навигационного лендмарка. По умолчанию — `gr.pagination.label`; задавать\n * стоит там, где пагинаций на странице несколько и их надо различать.\n */\n ariaLabel?: string\n /** Гасит всю пагинацию: номера, кнопки, селект размера и поле перехода. */\n disabled?: boolean\n size?: GrPaginationSize\n}\n\nexport interface GrPaginationEmits {\n (e: 'update:page', value: number): void\n (e: 'update:pageSize', value: number): void\n}\n\nconst { t } = useGranularityTranslations()\n\nconst props = withDefaults(defineProps<GrPaginationProps>(), {\n pageSizes: () => [10, 20, 50],\n siblingCount: 1,\n boundaryCount: 1,\n compact: false,\n showJumper: false,\n showPageSize: false,\n showTotal: false,\n jumperLabel: undefined,\n ariaLabel: undefined,\n disabled: false,\n size: undefined,\n})\n\ndefineSlots<{\n /** Диапазон показанных элементов целиком — вместо строки из локали. */\n total?: (props: { from: number, to: number, total: number }) => unknown\n}>()\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrPagination' })\n\nconst rowClass = computed(() => rowGaps[resolvedSize.value])\nconst pageListClass = computed(() => pageListGaps[resolvedSize.value])\nconst pageClass = computed(() => pageSizes[resolvedSize.value])\nconst jumperClass = computed(() => jumperSizes[resolvedSize.value])\nconst ellipsisClass = computed(() => ellipsisSizes[resolvedSize.value])\nconst labelClass = computed(() => labelSizes[resolvedSize.value])\nconst selectWrapClass = computed(() => pageSizeSelectWidths[resolvedSize.value])\nconst navButtonSize = computed(() => navButtonSizes[resolvedSize.value])\n\nconst emit = defineEmits<GrPaginationEmits>()\n\n// Делитель клампится: `pageSize: 0` дал бы `Infinity` страниц и бесконечный\n// цикл в `range()`.\nconst resolvedPageSize = computed(() => Math.max(1, Math.trunc(props.pageSize)))\nconst pageCount = computed(() => Math.max(1, Math.ceil(props.total / resolvedPageSize.value)))\n\n// Рендер идёт от зажатого номера: вотчер ниже просит родителя подтянуть `page`,\n// но на первом рендере он молчит, и без клампа пагинация осталась бы вовсе без\n// активной страницы.\nconst currentPage = computed(() => Math.min(Math.max(Math.trunc(props.page), 1), pageCount.value))\n\n// Контролируемый компонент: если `total`/`pageSize` уменьшились и текущая `page`\n// вышла за диапазон — просим родителя подтянуть её к последней странице.\nwatch(pageCount, (count) => {\n if (props.page > count)\n emit('update:page', count)\n})\n\nconst pageSizeModel = computed({\n get: () => String(props.pageSize),\n set: (value: string) => emit('update:pageSize', Number(value)),\n})\n\nconst pageSizeOptions = computed(() =>\n props.pageSizes.map(pageSize => ({\n value: String(pageSize),\n label: String(pageSize),\n })),\n)\n\nconst rangeFrom = computed(() => (props.total === 0 ? 0 : (currentPage.value - 1) * resolvedPageSize.value + 1))\nconst rangeTo = computed(() => Math.min(currentPage.value * resolvedPageSize.value, props.total))\n\ntype PaginationItem = number | 'ellipsis-start' | 'ellipsis-end'\n\nfunction range(start: number, end: number): number[] {\n const list: number[] = []\n for (let page = start; page <= end; page += 1)\n list.push(page)\n return list\n}\n\n// Алгоритм usePagination как в MUI: крайние `boundaryCount` страниц + `siblingCount`\n// соседей вокруг текущей, разрывы схлопываются в многоточие. Одиночный «пропущенный»\n// номер показываем как номер, а не как «…» (визуально ровнее).\nconst items = computed<PaginationItem[]>(() => {\n const count = pageCount.value\n const current = currentPage.value\n const boundaryCount = Math.max(0, props.boundaryCount)\n const siblingCount = Math.max(0, props.siblingCount)\n\n const startPages = range(1, Math.min(boundaryCount, count))\n const endPages = range(Math.max(count - boundaryCount + 1, boundaryCount + 1), count)\n\n const siblingsStart = Math.max(\n Math.min(current - siblingCount, count - boundaryCount - siblingCount * 2 - 1),\n boundaryCount + 2,\n )\n const siblingsEnd = Math.min(\n Math.max(current + siblingCount, boundaryCount + siblingCount * 2 + 2),\n endPages.length > 0 ? endPages[0] - 2 : count - 1,\n )\n\n return [\n ...startPages,\n ...(siblingsStart > boundaryCount + 2\n ? ['ellipsis-start' as const]\n : boundaryCount + 1 < count - boundaryCount\n ? [boundaryCount + 1]\n : []),\n ...range(siblingsStart, siblingsEnd),\n ...(siblingsEnd < count - boundaryCount - 1\n ? ['ellipsis-end' as const]\n : count - boundaryCount > boundaryCount\n ? [count - boundaryCount]\n : []),\n ...endPages,\n ]\n})\n\nfunction goTo(page: number): void {\n if (props.disabled)\n return\n\n emit('update:page', page)\n}\n\nfunction prev(): void {\n goTo(Math.max(1, currentPage.value - 1))\n}\n\nfunction next(): void {\n goTo(Math.min(pageCount.value, currentPage.value + 1))\n}\n\nfunction first(): void {\n goTo(1)\n}\n\nfunction last(): void {\n goTo(pageCount.value)\n}\n\n// «Перейти к странице»: клампим введённый номер к диапазону [1, pageCount]\n// и сбрасываем поле после перехода.\nconst jumperValue = ref('')\n\nfunction submitJumper(): void {\n const parsed = Number.parseInt(jumperValue.value, 10)\n jumperValue.value = ''\n if (Number.isNaN(parsed))\n return\n\n goTo(Math.min(pageCount.value, Math.max(1, parsed)))\n}\n\nconst resolvedJumperLabel = computed(() => props.jumperLabel ?? t('gr.pagination.jumpTo', 'Go to'))\nconst resolvedAriaLabel = computed(() => props.ariaLabel ?? t('gr.pagination.label', 'Pagination'))\nconst statusText = computed(() =>\n t('gr.pagination.status', 'Page {page} of {count}', { page: currentPage.value, count: pageCount.value }),\n)\nconst totalText = computed(() =>\n t('gr.pagination.total', '{from}–{to} of {total}', { from: rangeFrom.value, to: rangeTo.value, total: props.total }),\n)\n</script>\n\n<template>\n <div\n class=\"flex flex-wrap items-center justify-end\"\n :class=\"rowClass\"\n data-gr-pagination\n role=\"navigation\"\n :aria-label=\"resolvedAriaLabel\"\n >\n <div\n v-if=\"showTotal\"\n data-gr-pagination-total\n class=\"text-[var(--gr-muted-fg)] [font-variant-numeric:tabular-nums]\"\n :class=\"labelClass\"\n >\n <slot name=\"total\" :from=\"rangeFrom\" :to=\"rangeTo\" :total=\"total\">\n {{ totalText }}\n </slot>\n </div>\n\n <div v-if=\"showPageSize\" :class=\"selectWrapClass\">\n <GrSelect\n v-model=\"pageSizeModel\"\n :options=\"pageSizeOptions\"\n :size=\"resolvedSize\"\n :disabled=\"disabled\"\n :aria-label=\"t('gr.pagination.pageSize', 'Page size')\"\n />\n </div>\n\n <GrButton variant=\"ghost\" :size=\"navButtonSize\" :disabled=\"disabled || currentPage <= 1\" :aria-label=\"t('gr.pagination.first', 'First page')\" data-gr-pagination-first @click=\"first\">\n «\n </GrButton>\n\n <GrButton variant=\"ghost\" :size=\"navButtonSize\" :disabled=\"disabled || currentPage <= 1\" data-gr-pagination-prev @click=\"prev\">\n {{ t('gr.pagination.prev', 'Prev') }}\n </GrButton>\n\n <!-- Компактный вариант: индикатор «текущая / всего» вместо нумерованных страниц. -->\n <div\n v-if=\"compact\"\n data-gr-pagination-compact\n class=\"px-2 text-[var(--gr-fg)] [font-variant-numeric:tabular-nums]\"\n :class=\"labelClass\"\n role=\"status\"\n >\n {{ currentPage }} / {{ pageCount }}\n </div>\n\n <template v-else>\n <ul class=\"flex items-center [list-style:none]\" :class=\"pageListClass\" data-gr-pagination-pages role=\"list\">\n <li v-for=\"(item, index) in items\" :key=\"index\" :aria-hidden=\"typeof item === 'string' ? 'true' : undefined\">\n <span\n v-if=\"item === 'ellipsis-start' || item === 'ellipsis-end'\"\n data-gr-pagination-ellipsis\n class=\"grid place-items-center text-[var(--gr-muted-fg)]\"\n :class=\"ellipsisClass\"\n >…</span>\n <button\n v-else\n type=\"button\"\n data-gr-pagination-page\n :disabled=\"disabled\"\n :aria-current=\"item === currentPage ? 'page' : undefined\"\n :aria-label=\"t('gr.pagination.page', 'Page {n}', { n: item })\"\n class=\"rounded-[var(--gr-radius-control)] font-600 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 :class=\"[\n pageClass,\n item === currentPage\n ? 'bg-[var(--gr-primary)] text-[var(--gr-primary-fg)]'\n // Подложка тональная, а не `--gr-muted`: серый на белом фоне даёт\n // 1.10:1 и на светлой теме hover был практически не виден, хотя у\n // соседних «назад/вперёд» (ghost-кнопки) он тонирован. Ряд обязан\n // читаться одним контролом.\n : 'text-[var(--gr-muted-fg)] hover:bg-[var(--gr-accent)] hover:text-[var(--gr-accent-fg)]',\n ]\"\n @click=\"goTo(item)\"\n >\n {{ item }}\n </button>\n </li>\n </ul>\n\n <!-- В компактном режиме смену страницы объявляет видимый индикатор. -->\n <div data-gr-pagination-status role=\"status\" class=\"sr-only\">\n {{ statusText }}\n </div>\n </template>\n\n <GrButton variant=\"ghost\" :size=\"navButtonSize\" :disabled=\"disabled || currentPage >= pageCount\" data-gr-pagination-next @click=\"next\">\n {{ t('gr.pagination.next', 'Next') }}\n </GrButton>\n\n <GrButton variant=\"ghost\" :size=\"navButtonSize\" :disabled=\"disabled || currentPage >= pageCount\" :aria-label=\"t('gr.pagination.last', 'Last page')\" data-gr-pagination-last @click=\"last\">\n »\n </GrButton>\n\n <!-- «Перейти к странице»: Enter или blur применяют введённый номер. -->\n <div v-if=\"showJumper\" class=\"flex items-center gap-2 text-[var(--gr-muted-fg)]\" :class=\"labelClass\">\n <span>{{ resolvedJumperLabel }}</span>\n <input\n v-model=\"jumperValue\"\n type=\"number\"\n min=\"1\"\n :max=\"pageCount\"\n inputmode=\"numeric\"\n data-gr-pagination-jumper\n :disabled=\"disabled\"\n :aria-label=\"resolvedJumperLabel\"\n class=\"rounded-[var(--gr-radius-control)] border border-[var(--gr-brd)] bg-[var(--gr-bg)] px-2 text-center text-[var(--gr-fg)] [font-variant-numeric:tabular-nums] 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 :class=\"jumperClass\"\n @keydown.enter=\"submitJumper\"\n @blur=\"submitJumper\"\n >\n </div>\n </div>\n</template>\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"GrProgressBar-BiHnaitu.js","names":[],"sources":["../../../../src/components/GrProgressBar/GrProgressBar.vue","../../../../src/components/GrProgressBar/GrProgressBar.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\nimport {\n bufferClass,\n grProgressBarFillClass,\n type GrProgressBarSize,\n type GrProgressBarTone,\n rowGaps,\n trackSizes,\n valueTextSizes,\n} from './grStyle'\n\n/**\n * Пропы {@link GrProgressBar}.\n *\n * Линейный индикатор прогресса: определённый режим по `value` и неопределённый\n * по `indeterminate`, необязательный слой буфера и подпись значения.\n */\nexport interface GrProgressBarProps {\n /** Текущее значение `0..100`; выходящие за границы клампятся, `NaN` → `0`. */\n value: number\n /** Загружено с запасом: слой позади заливки, `0..100`. Не задан — слоя нет. */\n buffer?: number\n /** Прогресс неизвестен: полоса бежит, значение наружу не объявляется. */\n indeterminate?: boolean\n /** Показать значение подписью справа от трека. */\n showValue?: boolean\n /** Свой формат значения. Управляет и подписью, и `aria-valuetext`. */\n formatValue?: (value: number) => string\n /** Метка для скринридера (обязательна, если рядом нет видимого заголовка). */\n ariaLabel?: string\n /** Цветовая тональность заливки. */\n tone?: GrProgressBarTone\n /** Толщина трека. */\n size?: GrProgressBarSize\n /** Убрать рамку трека: внутри карточки вторая рамка только шумит. */\n borderless?: boolean\n}\n\nconst props = withDefaults(defineProps<GrProgressBarProps>(), {\n buffer: undefined,\n indeterminate: false,\n showValue: false,\n formatValue: undefined,\n ariaLabel: undefined,\n tone: 'primary',\n size: undefined,\n // `undefined`, а не `false`: иначе значение из `componentDefaults` не дошло бы.\n borderless: undefined,\n})\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrProgressBar' })\nconst resolvedBorderless = useGrComponentProp('GrProgressBar', 'borderless', () => props.borderless, false)\nconst trackClassName = computed(() => trackSizes[resolvedSize.value])\nconst rowGapClassName = computed(() => rowGaps[resolvedSize.value])\nconst valueClassName = computed(() => valueTextSizes[resolvedSize.value])\n\nfunction clampValue(value: number): number {\n if (Number.isNaN(value))\n return 0\n return Math.min(100, Math.max(0, value))\n}\n\nconst safe = computed(() => clampValue(props.value))\nconst safeBuffer = computed(() => (props.buffer === undefined ? undefined : clampValue(props.buffer)))\n\nconst fillClassName = computed(() => grProgressBarFillClass(props.tone))\n\n/**\n * `aria-valuetext` нужен только со своим форматом: «45» при `aria-valuemax=\"100\"`\n * диктор и так читает как проценты, дублировать это текстом незачем.\n */\nconst valueText = computed(() => (props.formatValue ? props.formatValue(safe.value) : undefined))\nconst valueLabel = computed(() => valueText.value ?? `${Math.round(safe.value)}%`)\n</script>\n\n<template>\n <div\n data-gr-progress-bar\n class=\"w-full flex items-center\"\n :class=\"rowGapClassName\"\n >\n <div\n data-gr-progress-bar-track\n :data-gr-progress-bar-indeterminate=\"indeterminate ? '' : undefined\"\n role=\"progressbar\"\n :aria-label=\"ariaLabel\"\n :aria-valuenow=\"indeterminate ? undefined : safe\"\n :aria-valuetext=\"indeterminate ? undefined : valueText\"\n aria-valuemin=\"0\"\n aria-valuemax=\"100\"\n class=\"relative min-w-0 flex-1 rounded-[var(--gr-radius-full)] bg-[var(--gr-muted)] overflow-hidden\"\n :class=\"[trackClassName, resolvedBorderless ? '' : 'border border-[var(--gr-brd)]']\"\n >\n <div\n v-if=\"safeBuffer !== undefined && !indeterminate\"\n data-gr-progress-bar-buffer\n class=\"absolute inset-y-0 left-0\"\n :class=\"bufferClass\"\n :style=\"{ width: `${safeBuffer}%` }\"\n />\n <div\n data-gr-progress-bar-fill\n class=\"absolute inset-y-0 left-0\"\n :class=\"fillClassName\"\n :style=\"indeterminate ? undefined : { width: `${safe}%` }\"\n />\n </div>\n <span\n v-if=\"showValue && !indeterminate\"\n data-gr-progress-bar-value\n class=\"text-[var(--gr-muted-fg)] [font-variant-numeric:tabular-nums] min-w-[4ch] text-right\"\n :class=\"valueClassName\"\n >{{ valueLabel }}</span>\n </div>\n</template>\n\n<style>\n@keyframes gr-progress-indeterminate {\n from {\n transform: translateX(-100%);\n }\n to {\n transform: translateX(250%);\n }\n}\n\n/* Ширина бегущей полосы задаётся здесь, а не инлайном: инлайн-стиль перебил бы\n раскладку под `reduce` ниже. */\n[data-gr-progress-bar-indeterminate] [data-gr-progress-bar-fill] {\n width: 40%;\n animation-name: gr-progress-indeterminate;\n animation-duration: var(--gr-progress-indeterminate-duration, 1.4s);\n animation-timing-function: linear;\n animation-iteration-count: infinite;\n}\n\n/* Замерев в исходном кадре, полоса легла бы у левого края и читалась как\n «прогресс 40%». Нейтральная заливка на всю ширину не притворяется ни нулём,\n ни завершением: она говорит «работа идёт, значение неизвестно». */\n@media (prefers-reduced-motion: reduce) {\n [data-gr-progress-bar-indeterminate] [data-gr-progress-bar-fill] {\n width: 100%;\n animation: none;\n background-color: var(--gr-progress-indeterminate-bg, var(--gr-muted-fg));\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\nimport {\n bufferClass,\n grProgressBarFillClass,\n type GrProgressBarSize,\n type GrProgressBarTone,\n rowGaps,\n trackSizes,\n valueTextSizes,\n} from './grStyle'\n\n/**\n * Пропы {@link GrProgressBar}.\n *\n * Линейный индикатор прогресса: определённый режим по `value` и неопределённый\n * по `indeterminate`, необязательный слой буфера и подпись значения.\n */\nexport interface GrProgressBarProps {\n /** Текущее значение `0..100`; выходящие за границы клампятся, `NaN` → `0`. */\n value: number\n /** Загружено с запасом: слой позади заливки, `0..100`. Не задан — слоя нет. */\n buffer?: number\n /** Прогресс неизвестен: полоса бежит, значение наружу не объявляется. */\n indeterminate?: boolean\n /** Показать значение подписью справа от трека. */\n showValue?: boolean\n /** Свой формат значения. Управляет и подписью, и `aria-valuetext`. */\n formatValue?: (value: number) => string\n /** Метка для скринридера (обязательна, если рядом нет видимого заголовка). */\n ariaLabel?: string\n /** Цветовая тональность заливки. */\n tone?: GrProgressBarTone\n /** Толщина трека. */\n size?: GrProgressBarSize\n /** Убрать рамку трека: внутри карточки вторая рамка только шумит. */\n borderless?: boolean\n}\n\nconst props = withDefaults(defineProps<GrProgressBarProps>(), {\n buffer: undefined,\n indeterminate: false,\n showValue: false,\n formatValue: undefined,\n ariaLabel: undefined,\n tone: 'primary',\n size: undefined,\n // `undefined`, а не `false`: иначе значение из `componentDefaults` не дошло бы.\n borderless: undefined,\n})\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrProgressBar' })\nconst resolvedBorderless = useGrComponentProp('GrProgressBar', 'borderless', () => props.borderless, false)\nconst trackClassName = computed(() => trackSizes[resolvedSize.value])\nconst rowGapClassName = computed(() => rowGaps[resolvedSize.value])\nconst valueClassName = computed(() => valueTextSizes[resolvedSize.value])\n\nfunction clampValue(value: number): number {\n if (Number.isNaN(value))\n return 0\n return Math.min(100, Math.max(0, value))\n}\n\nconst safe = computed(() => clampValue(props.value))\nconst safeBuffer = computed(() => (props.buffer === undefined ? undefined : clampValue(props.buffer)))\n\nconst fillClassName = computed(() => grProgressBarFillClass(props.tone))\n\n/**\n * `aria-valuetext` нужен только со своим форматом: «45» при `aria-valuemax=\"100\"`\n * диктор и так читает как проценты, дублировать это текстом незачем.\n */\nconst valueText = computed(() => (props.formatValue ? props.formatValue(safe.value) : undefined))\nconst valueLabel = computed(() => valueText.value ?? `${Math.round(safe.value)}%`)\n</script>\n\n<template>\n <div\n data-gr-progress-bar\n class=\"w-full flex items-center\"\n :class=\"rowGapClassName\"\n >\n <div\n data-gr-progress-bar-track\n :data-gr-progress-bar-indeterminate=\"indeterminate ? '' : undefined\"\n role=\"progressbar\"\n :aria-label=\"ariaLabel\"\n :aria-valuenow=\"indeterminate ? undefined : safe\"\n :aria-valuetext=\"indeterminate ? undefined : valueText\"\n aria-valuemin=\"0\"\n aria-valuemax=\"100\"\n class=\"relative min-w-0 flex-1 rounded-[var(--gr-radius-full)] bg-[var(--gr-muted)] overflow-hidden\"\n :class=\"[trackClassName, resolvedBorderless ? '' : 'border border-[var(--gr-brd)]']\"\n >\n <div\n v-if=\"safeBuffer !== undefined && !indeterminate\"\n data-gr-progress-bar-buffer\n class=\"absolute inset-y-0 left-0\"\n :class=\"bufferClass\"\n :style=\"{ width: `${safeBuffer}%` }\"\n />\n <div\n data-gr-progress-bar-fill\n class=\"absolute inset-y-0 left-0\"\n :class=\"fillClassName\"\n :style=\"indeterminate ? undefined : { width: `${safe}%` }\"\n />\n </div>\n <span\n v-if=\"showValue && !indeterminate\"\n data-gr-progress-bar-value\n class=\"text-[var(--gr-muted-fg)] [font-variant-numeric:tabular-nums] min-w-[4ch] text-right\"\n :class=\"valueClassName\"\n >{{ valueLabel }}</span>\n </div>\n</template>\n\n<style>\n@keyframes gr-progress-indeterminate {\n from {\n transform: translateX(-100%);\n }\n to {\n transform: translateX(250%);\n }\n}\n\n/* Ширина бегущей полосы задаётся здесь, а не инлайном: инлайн-стиль перебил бы\n раскладку под `reduce` ниже. */\n[data-gr-progress-bar-indeterminate] [data-gr-progress-bar-fill] {\n width: 40%;\n animation-name: gr-progress-indeterminate;\n animation-duration: var(--gr-progress-indeterminate-duration, 1.4s);\n animation-timing-function: linear;\n animation-iteration-count: infinite;\n}\n\n/* Замерев в исходном кадре, полоса легла бы у левого края и читалась как\n «прогресс 40%». Нейтральная заливка на всю ширину не притворяется ни нулём,\n ни завершением: она говорит «работа идёт, значение неизвестно». */\n@media (prefers-reduced-motion: reduce) {\n [data-gr-progress-bar-indeterminate] [data-gr-progress-bar-fill] {\n width: 100%;\n animation: none;\n background-color: var(--gr-progress-indeterminate-bg, var(--gr-muted-fg));\n }\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAyCA,MAAM,QAAQ;EAYd,MAAM,eAAe,yBAAyB,MAAM,MAAM,EAAE,WAAW,gBAAgB,CAAC;EACxF,MAAM,qBAAqB,mBAAmB,iBAAiB,oBAAoB,MAAM,YAAY,KAAK;EAC1G,MAAM,iBAAiB,eAAe,WAAW,aAAa,MAAM;EACpE,MAAM,kBAAkB,eAAe,QAAQ,aAAa,MAAM;EAClE,MAAM,iBAAiB,eAAe,eAAe,aAAa,MAAM;EAExE,SAAS,WAAW,OAAuB;GACzC,IAAI,OAAO,MAAM,KAAK,GACpB,OAAO;GACT,OAAO,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,KAAK,CAAC;EACzC;EAEA,MAAM,OAAO,eAAe,WAAW,MAAM,KAAK,CAAC;EACnD,MAAM,aAAa,eAAgB,MAAM,WAAW,KAAA,IAAY,KAAA,IAAY,WAAW,MAAM,MAAM,CAAE;EAErG,MAAM,gBAAgB,eAAe,uBAAuB,MAAM,IAAI,CAAC;;;;;EAMvE,MAAM,YAAY,eAAgB,MAAM,cAAc,MAAM,YAAY,KAAK,KAAK,IAAI,KAAA,CAAU;EAChG,MAAM,aAAa,eAAe,UAAU,SAAS,GAAG,KAAK,MAAM,KAAK,KAAK,EAAE,EAAE;;uBAI/E,mBAqCM,OAAA;IApCJ,wBAAA;IACA,OAAK,eAAA,CAAC,4BACE,gBAAA,KAAe,CAAA;OAEvB,mBAyBM,OAAA;IAxBJ,8BAAA;IACC,sCAAoC,QAAA,gBAAa,KAAQ,KAAA;IAC1D,MAAK;IACJ,cAAY,QAAA;IACZ,iBAAe,QAAA,gBAAgB,KAAA,IAAY,KAAA;IAC3C,kBAAgB,QAAA,gBAAgB,KAAA,IAAY,UAAA;IAC7C,iBAAc;IACd,iBAAc;IACd,OAAK,eAAA,CAAC,gGAA8F,CAC3F,eAAA,OAAgB,MAAA,kBAAA,IAAkB,KAAA,+BAAA,CAAA,CAAA;OAGnC,WAAA,UAAe,KAAA,KAAS,CAAK,QAAA,iBAAA,UAAA,GADrC,mBAME,OAAA;;IAJA,+BAAA;IACA,OAAK,eAAA,CAAC,6BACE,MAAA,WAAA,CAAW,CAAA;IAClB,OAAK,eAAA,EAAA,OAAA,GAAc,WAAA,MAAU,GAAA,CAAA;gDAEhC,mBAKE,OAAA;IAJA,6BAAA;IACA,OAAK,eAAA,CAAC,6BACE,cAAA,KAAa,CAAA;IACpB,OAAK,eAAE,QAAA,gBAAgB,KAAA,IAAS,EAAA,OAAA,GAAe,KAAA,MAAI,GAAA,CAAA;kCAIhD,QAAA,aAAS,CAAK,QAAA,iBAAA,UAAA,GADtB,mBAKwB,QAAA;;IAHtB,8BAAA;IACA,OAAK,eAAA,CAAC,wFACE,eAAA,KAAc,CAAA;sBACpB,WAAA,KAAU,GAAA,CAAA,KAAA,mBAAA,IAAA,IAAA,CAAA,GAAA,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"GrRadio-DksDro_U.js","names":[],"sources":["../../../../src/components/GrRadio/grRadioGroupContext.ts","../../../../src/components/GrRadio/GrRadio.vue","../../../../src/components/GrRadio/GrRadio.vue"],"sourcesContent":["import type { ComputedRef, InjectionKey } from 'vue'\n\nimport type { GrButtonSize } from '../GrButton/grButtonStyles'\n\n/**\n * Значение переключателя. Не только строка: перечисления в реальных формах —\n * это id числом или флаг. Объекты сюда намеренно не входят: значение уходит в\n * `data-value` и в скрытый input нативной формы, а значит должно иметь\n * однозначное строковое представление.\n */\nexport type GrRadioValue = string | number | boolean\n\nexport type GrRadioEntry = {\n value: () => GrRadioValue\n disabled: () => boolean\n}\n\nexport type GrRadioGroupContext = {\n modelValue: ComputedRef<GrRadioValue>\n name: ComputedRef<string | undefined>\n disabled: ComputedRef<boolean>\n /**\n * Только для чтения: выбор видно, но он не меняется. У роли `radio` нет\n * `aria-readonly` — состояние объявляет сама группа, переключателю остаётся\n * не прикидываться кликабельным.\n */\n readonly: ComputedRef<boolean>\n /** Ошибка группы: до вида переключателя она доходит только через контекст. */\n invalid: ComputedRef<boolean>\n size: ComputedRef<GrButtonSize>\n setValue: (value: GrRadioValue) => void\n /**\n * Регистрация переключателя в группе. Порядок регистрации совпадает с\n * порядком в документе (setup детей идёт сверху вниз), поэтому группа знает\n * состав и в slot-режиме, и на сервере — без обхода DOM.\n */\n register: (entry: GrRadioEntry) => () => void\n /**\n * Значение переключателя, который держит `tabindex=\"0\"`. Roving tabindex\n * WAI-ARIA: группа — одна остановка Tab, внутрь попадают стрелками. Если не\n * выбрано ничего, остановкой становится первый доступный переключатель.\n */\n rovingValue: ComputedRef<GrRadioValue | undefined>\n /** Переводит выбор на соседний доступный переключатель (стрелки). */\n moveSelection: (from: GrRadioValue, direction: 1 | -1) => GrRadioValue | undefined\n /** Переводит выбор на первый или последний доступный (`Home`/`End`). */\n selectEdge: (edge: 'first' | 'last') => GrRadioValue | undefined\n}\n\nexport const GR_RADIO_GROUP_CONTEXT: InjectionKey<GrRadioGroupContext> = Symbol('GR_RADIO_GROUP_CONTEXT')\n","<script setup lang=\"ts\">\nimport { computed, inject, nextTick, onUnmounted, ref, useId, useSlots } from 'vue'\n\nimport { useGrComponentSize } from '../GrConfigProvider/context'\n\nimport type { GrButtonSize, GrButtonTone, GrButtonVariant } from '../GrButton'\n\nimport {\n grRadioButtonClass,\n grRadioControlClass,\n grRadioDescriptionSizes,\n grRadioDotBaseClass,\n grRadioDotClass,\n grRadioDotSizes,\n grRadioLabelClassFor,\n grRadioRootClass,\n type GrRadioVariant,\n} from './grRadioStyles'\nimport { GR_RADIO_GROUP_CONTEXT, type GrRadioValue } from './grRadioGroupContext'\n\nexport type { GrRadioValue } from './grRadioGroupContext'\n\n/**\n * GrRadio — одиночный элемент группы выбора.\n *\n * Может работать автономно (через `v-model`) либо внутри `GrRadioGroup`\n * (тогда `modelValue`/`disabled`/`size`/`name`/`invalid` приходят через `inject`).\n *\n * @prop value — значение этого элемента, сравнивается с `modelValue` группы.\n * @prop variant — визуальное представление: `radiobox` (круг+dot) или `button` (стиль `GrButton`).\n *\n * Доступным контролом является сам элемент с `role=\"radio\"`: он держит фокус,\n * `aria-checked` и клавиатуру. Внутрь него ничего интерактивного не вкладывается —\n * `role=\"radio\"` объявляет потомков презентационными, и вложенный native `<input>`\n * (даже скрытый и с `tabindex=\"-1\"`) ломает виджет для скринридеров и падает\n * в axe на `nested-interactive`. Для отправки нативной формой рядом рендерится\n * `input[type=\"hidden\"]` — он не фокусируется и не является интерактивным.\n */\nexport interface GrRadioProps {\n value: GrRadioValue\n modelValue?: GrRadioValue\n disabled?: boolean\n /** Визуальное и ARIA-состояние ошибки. Складывается с `invalid` группы. */\n invalid?: boolean\n name?: string\n required?: boolean\n form?: string\n id?: string\n size?: GrButtonSize\n variant?: GrRadioVariant\n buttonVariant?: GrButtonVariant\n buttonTone?: GrButtonTone\n selectedButtonVariant?: GrButtonVariant\n selectedButtonTone?: GrButtonTone\n ariaLabel?: string\n}\n\nexport interface GrRadioEmits {\n (e: 'update:modelValue', value: GrRadioValue): void\n}\n\nconst props = withDefaults(defineProps<GrRadioProps>(), {\n modelValue: undefined,\n disabled: undefined,\n invalid: false,\n name: undefined,\n required: false,\n form: undefined,\n id: undefined,\n size: undefined,\n variant: 'radiobox',\n buttonVariant: 'outline',\n buttonTone: 'neutral',\n selectedButtonVariant: 'primary',\n selectedButtonTone: 'primary',\n ariaLabel: undefined,\n})\n\nconst emit = defineEmits<GrRadioEmits>()\n\nconst slots = useSlots()\nconst group = inject(GR_RADIO_GROUP_CONTEXT, null)\n\nconst resolvedModelValue = computed(() => {\n if (props.modelValue !== undefined)\n return props.modelValue\n\n return group?.modelValue.value ?? ''\n})\n\nconst resolvedDisabled = computed(() => {\n if (props.disabled !== undefined)\n return props.disabled\n\n return group?.disabled.value ?? false\n})\n\nconst resolvedReadonly = computed(() => group?.readonly.value ?? false)\n\n// «Или», а не `??`: ошибку может объявить и группа, и сам переключатель.\nconst resolvedInvalid = computed(() => props.invalid || (group?.invalid.value ?? false))\n\nconst resolvedName = computed(() => {\n if (props.name)\n return props.name\n\n return group?.name.value\n})\n\n// Приоритет: локальный проп → размер группы → `GrConfigProvider` → `md`.\n// Группа специфичнее конфига, поэтому её значение подаётся как «локальное».\nconst resolvedSize = useGrComponentSize(\n () => props.size ?? group?.size.value,\n { component: 'GrRadio' },\n)\n\nconst checked = computed(() => resolvedModelValue.value === props.value)\n\n/** В DOM значение живёт строкой: `data-value` и скрытый input иначе не умеют. */\nconst domValue = computed(() => String(props.value))\n\n// Нативная форма получает значение только от выбранного и не-disabled элемента —\n// ровно как поступил бы native radio. Без `name` отправлять нечего.\nconst submitsValue = computed(\n () => checked.value && !resolvedDisabled.value && Boolean(resolvedName.value),\n)\n\nconst descriptionId = useId()\nconst hasDescription = computed(() => Boolean(slots.description))\n\nconst buttonClassName = computed(() => {\n return grRadioButtonClass({\n checked: checked.value,\n disabled: resolvedDisabled.value,\n readonly: resolvedReadonly.value,\n size: resolvedSize.value,\n buttonVariant: props.buttonVariant,\n buttonTone: props.buttonTone,\n selectedButtonVariant: props.selectedButtonVariant,\n selectedButtonTone: props.selectedButtonTone,\n })\n})\n\nconst rootClassName = computed(() => grRadioRootClass({\n disabled: resolvedDisabled.value,\n readonly: resolvedReadonly.value,\n}))\nconst controlClassName = computed(() => grRadioControlClass({\n size: resolvedSize.value,\n checked: checked.value,\n disabled: resolvedDisabled.value,\n invalid: resolvedInvalid.value,\n}))\nconst dotClassName = computed(() => grRadioDotClass({\n checked: checked.value,\n disabled: resolvedDisabled.value,\n}))\nconst labelClassName = computed(() => grRadioLabelClassFor(resolvedSize.value, checked.value))\nconst dotSizeClassName = computed(() => grRadioDotSizes[resolvedSize.value])\nconst descriptionClassName = computed(() => grRadioDescriptionSizes[resolvedSize.value])\n\nfunction setValue(next: GrRadioValue): void {\n if (resolvedDisabled.value)\n return\n\n if (props.modelValue !== undefined) {\n emit('update:modelValue', next)\n return\n }\n\n group?.setValue(next)\n}\n\nfunction onButtonClick(): void {\n setValue(props.value)\n}\n\n// Паттерн radiogroup: группа — одна остановка Tab, внутрь попадают стрелками.\n// Вне группы (одиночный `GrRadio`) переключатель остаётся обычной остановкой.\nconst rovingTabindex = computed(() => {\n if (resolvedDisabled.value) return -1\n if (!group) return 0\n return group.rovingValue.value === props.value ? 0 : -1\n})\n\nconst rootEl = ref<HTMLElement | null>(null)\n\n/**\n * Фокус переезжает вслед за выбором — иначе следующая стрелка отсчитывалась бы\n * от прежнего элемента.\n */\nfunction focusValue(next: GrRadioValue | undefined): void {\n if (next === undefined) return\n\n void nextTick(() => {\n const group = rootEl.value?.closest('[data-gr-radio-group]')\n const target = [...(group?.querySelectorAll<HTMLElement>('[data-gr-radio]') ?? [])]\n .find(el => el.dataset.value === String(next))\n\n target?.focus()\n })\n}\n\nfunction onArrow(direction: 1 | -1, event: KeyboardEvent): void {\n if (!group || resolvedDisabled.value) return\n\n event.preventDefault()\n focusValue(group.moveSelection(props.value, direction))\n}\n\nfunction onEdge(edge: 'first' | 'last', event: KeyboardEvent): void {\n if (!group || resolvedDisabled.value) return\n\n event.preventDefault()\n focusValue(group.selectEdge(edge))\n}\n\nconst unregister = group?.register({\n value: () => props.value,\n disabled: () => resolvedDisabled.value,\n})\n\nonUnmounted(() => unregister?.())\n</script>\n\n<template>\n <div\n v-if=\"variant === 'button'\"\n :id=\"id\"\n ref=\"rootEl\"\n data-gr-button\n data-gr-radio\n role=\"radio\"\n :aria-checked=\"checked ? 'true' : 'false'\"\n :aria-label=\"ariaLabel\"\n :aria-disabled=\"resolvedDisabled ? 'true' : undefined\"\n :aria-invalid=\"resolvedInvalid ? 'true' : undefined\"\n :aria-required=\"required ? 'true' : undefined\"\n :data-value=\"domValue\"\n :tabindex=\"rovingTabindex\"\n :class=\"buttonClassName\"\n @click=\"onButtonClick\"\n @keydown.space.prevent=\"onButtonClick\"\n @keydown.enter.prevent=\"onButtonClick\"\n @keydown.down=\"onArrow(1, $event)\"\n @keydown.right=\"onArrow(1, $event)\"\n @keydown.up=\"onArrow(-1, $event)\"\n @keydown.left=\"onArrow(-1, $event)\"\n @keydown.home=\"onEdge('first', $event)\"\n @keydown.end=\"onEdge('last', $event)\"\n >\n <input\n v-if=\"submitsValue\"\n type=\"hidden\"\n :name=\"resolvedName\"\n :value=\"domValue\"\n :form=\"form\"\n >\n\n <slot />\n </div>\n\n <div\n v-else\n :id=\"id\"\n ref=\"rootEl\"\n data-gr-radio\n role=\"radio\"\n :aria-checked=\"checked ? 'true' : 'false'\"\n :aria-label=\"ariaLabel\"\n :aria-disabled=\"resolvedDisabled ? 'true' : undefined\"\n :aria-invalid=\"resolvedInvalid ? 'true' : undefined\"\n :aria-required=\"required ? 'true' : undefined\"\n :aria-describedby=\"hasDescription ? descriptionId : undefined\"\n :data-value=\"domValue\"\n :tabindex=\"rovingTabindex\"\n class=\"inline-flex items-start gap-2 select-none focus-visible:outline-none focus-visible:rounded-[var(--gr-radius-md)] focus-visible:shadow-[0_0_0_2px_var(--gr-ring),0_0_0_4px_var(--gr-bg)]\"\n :class=\"rootClassName\"\n @click=\"onButtonClick\"\n @keydown.space.prevent=\"onButtonClick\"\n @keydown.enter.prevent=\"onButtonClick\"\n @keydown.down=\"onArrow(1, $event)\"\n @keydown.right=\"onArrow(1, $event)\"\n @keydown.up=\"onArrow(-1, $event)\"\n @keydown.left=\"onArrow(-1, $event)\"\n @keydown.home=\"onEdge('first', $event)\"\n @keydown.end=\"onEdge('last', $event)\"\n >\n <input\n v-if=\"submitsValue\"\n type=\"hidden\"\n :name=\"resolvedName\"\n :value=\"domValue\"\n :form=\"form\"\n >\n\n <span\n data-gr-radio-control\n aria-hidden=\"true\"\n class=\"mt-0.5 shrink-0 rounded-[var(--gr-radius-full)] border flex items-center justify-center transition-colors duration-[var(--gr-duration-fast)]\"\n :class=\"controlClassName\"\n >\n <span\n data-gr-radio-dot\n :class=\"[grRadioDotBaseClass, dotSizeClassName, dotClassName]\"\n />\n </span>\n\n <span class=\"grid gap-0.5\">\n <span data-gr-radio-label :class=\"labelClassName\">\n <slot />\n </span>\n\n <span\n v-if=\"hasDescription\"\n :id=\"descriptionId\"\n data-gr-radio-description\n :class=\"descriptionClassName\"\n >\n <slot name=\"description\" />\n </span>\n </span>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, inject, nextTick, onUnmounted, ref, useId, useSlots } from 'vue'\n\nimport { useGrComponentSize } from '../GrConfigProvider/context'\n\nimport type { GrButtonSize, GrButtonTone, GrButtonVariant } from '../GrButton'\n\nimport {\n grRadioButtonClass,\n grRadioControlClass,\n grRadioDescriptionSizes,\n grRadioDotBaseClass,\n grRadioDotClass,\n grRadioDotSizes,\n grRadioLabelClassFor,\n grRadioRootClass,\n type GrRadioVariant,\n} from './grRadioStyles'\nimport { GR_RADIO_GROUP_CONTEXT, type GrRadioValue } from './grRadioGroupContext'\n\nexport type { GrRadioValue } from './grRadioGroupContext'\n\n/**\n * GrRadio — одиночный элемент группы выбора.\n *\n * Может работать автономно (через `v-model`) либо внутри `GrRadioGroup`\n * (тогда `modelValue`/`disabled`/`size`/`name`/`invalid` приходят через `inject`).\n *\n * @prop value — значение этого элемента, сравнивается с `modelValue` группы.\n * @prop variant — визуальное представление: `radiobox` (круг+dot) или `button` (стиль `GrButton`).\n *\n * Доступным контролом является сам элемент с `role=\"radio\"`: он держит фокус,\n * `aria-checked` и клавиатуру. Внутрь него ничего интерактивного не вкладывается —\n * `role=\"radio\"` объявляет потомков презентационными, и вложенный native `<input>`\n * (даже скрытый и с `tabindex=\"-1\"`) ломает виджет для скринридеров и падает\n * в axe на `nested-interactive`. Для отправки нативной формой рядом рендерится\n * `input[type=\"hidden\"]` — он не фокусируется и не является интерактивным.\n */\nexport interface GrRadioProps {\n value: GrRadioValue\n modelValue?: GrRadioValue\n disabled?: boolean\n /** Визуальное и ARIA-состояние ошибки. Складывается с `invalid` группы. */\n invalid?: boolean\n name?: string\n required?: boolean\n form?: string\n id?: string\n size?: GrButtonSize\n variant?: GrRadioVariant\n buttonVariant?: GrButtonVariant\n buttonTone?: GrButtonTone\n selectedButtonVariant?: GrButtonVariant\n selectedButtonTone?: GrButtonTone\n ariaLabel?: string\n}\n\nexport interface GrRadioEmits {\n (e: 'update:modelValue', value: GrRadioValue): void\n}\n\nconst props = withDefaults(defineProps<GrRadioProps>(), {\n modelValue: undefined,\n disabled: undefined,\n invalid: false,\n name: undefined,\n required: false,\n form: undefined,\n id: undefined,\n size: undefined,\n variant: 'radiobox',\n buttonVariant: 'outline',\n buttonTone: 'neutral',\n selectedButtonVariant: 'primary',\n selectedButtonTone: 'primary',\n ariaLabel: undefined,\n})\n\nconst emit = defineEmits<GrRadioEmits>()\n\nconst slots = useSlots()\nconst group = inject(GR_RADIO_GROUP_CONTEXT, null)\n\nconst resolvedModelValue = computed(() => {\n if (props.modelValue !== undefined)\n return props.modelValue\n\n return group?.modelValue.value ?? ''\n})\n\nconst resolvedDisabled = computed(() => {\n if (props.disabled !== undefined)\n return props.disabled\n\n return group?.disabled.value ?? false\n})\n\nconst resolvedReadonly = computed(() => group?.readonly.value ?? false)\n\n// «Или», а не `??`: ошибку может объявить и группа, и сам переключатель.\nconst resolvedInvalid = computed(() => props.invalid || (group?.invalid.value ?? false))\n\nconst resolvedName = computed(() => {\n if (props.name)\n return props.name\n\n return group?.name.value\n})\n\n// Приоритет: локальный проп → размер группы → `GrConfigProvider` → `md`.\n// Группа специфичнее конфига, поэтому её значение подаётся как «локальное».\nconst resolvedSize = useGrComponentSize(\n () => props.size ?? group?.size.value,\n { component: 'GrRadio' },\n)\n\nconst checked = computed(() => resolvedModelValue.value === props.value)\n\n/** В DOM значение живёт строкой: `data-value` и скрытый input иначе не умеют. */\nconst domValue = computed(() => String(props.value))\n\n// Нативная форма получает значение только от выбранного и не-disabled элемента —\n// ровно как поступил бы native radio. Без `name` отправлять нечего.\nconst submitsValue = computed(\n () => checked.value && !resolvedDisabled.value && Boolean(resolvedName.value),\n)\n\nconst descriptionId = useId()\nconst hasDescription = computed(() => Boolean(slots.description))\n\nconst buttonClassName = computed(() => {\n return grRadioButtonClass({\n checked: checked.value,\n disabled: resolvedDisabled.value,\n readonly: resolvedReadonly.value,\n size: resolvedSize.value,\n buttonVariant: props.buttonVariant,\n buttonTone: props.buttonTone,\n selectedButtonVariant: props.selectedButtonVariant,\n selectedButtonTone: props.selectedButtonTone,\n })\n})\n\nconst rootClassName = computed(() => grRadioRootClass({\n disabled: resolvedDisabled.value,\n readonly: resolvedReadonly.value,\n}))\nconst controlClassName = computed(() => grRadioControlClass({\n size: resolvedSize.value,\n checked: checked.value,\n disabled: resolvedDisabled.value,\n invalid: resolvedInvalid.value,\n}))\nconst dotClassName = computed(() => grRadioDotClass({\n checked: checked.value,\n disabled: resolvedDisabled.value,\n}))\nconst labelClassName = computed(() => grRadioLabelClassFor(resolvedSize.value, checked.value))\nconst dotSizeClassName = computed(() => grRadioDotSizes[resolvedSize.value])\nconst descriptionClassName = computed(() => grRadioDescriptionSizes[resolvedSize.value])\n\nfunction setValue(next: GrRadioValue): void {\n if (resolvedDisabled.value)\n return\n\n if (props.modelValue !== undefined) {\n emit('update:modelValue', next)\n return\n }\n\n group?.setValue(next)\n}\n\nfunction onButtonClick(): void {\n setValue(props.value)\n}\n\n// Паттерн radiogroup: группа — одна остановка Tab, внутрь попадают стрелками.\n// Вне группы (одиночный `GrRadio`) переключатель остаётся обычной остановкой.\nconst rovingTabindex = computed(() => {\n if (resolvedDisabled.value) return -1\n if (!group) return 0\n return group.rovingValue.value === props.value ? 0 : -1\n})\n\nconst rootEl = ref<HTMLElement | null>(null)\n\n/**\n * Фокус переезжает вслед за выбором — иначе следующая стрелка отсчитывалась бы\n * от прежнего элемента.\n */\nfunction focusValue(next: GrRadioValue | undefined): void {\n if (next === undefined) return\n\n void nextTick(() => {\n const group = rootEl.value?.closest('[data-gr-radio-group]')\n const target = [...(group?.querySelectorAll<HTMLElement>('[data-gr-radio]') ?? [])]\n .find(el => el.dataset.value === String(next))\n\n target?.focus()\n })\n}\n\nfunction onArrow(direction: 1 | -1, event: KeyboardEvent): void {\n if (!group || resolvedDisabled.value) return\n\n event.preventDefault()\n focusValue(group.moveSelection(props.value, direction))\n}\n\nfunction onEdge(edge: 'first' | 'last', event: KeyboardEvent): void {\n if (!group || resolvedDisabled.value) return\n\n event.preventDefault()\n focusValue(group.selectEdge(edge))\n}\n\nconst unregister = group?.register({\n value: () => props.value,\n disabled: () => resolvedDisabled.value,\n})\n\nonUnmounted(() => unregister?.())\n</script>\n\n<template>\n <div\n v-if=\"variant === 'button'\"\n :id=\"id\"\n ref=\"rootEl\"\n data-gr-button\n data-gr-radio\n role=\"radio\"\n :aria-checked=\"checked ? 'true' : 'false'\"\n :aria-label=\"ariaLabel\"\n :aria-disabled=\"resolvedDisabled ? 'true' : undefined\"\n :aria-invalid=\"resolvedInvalid ? 'true' : undefined\"\n :aria-required=\"required ? 'true' : undefined\"\n :data-value=\"domValue\"\n :tabindex=\"rovingTabindex\"\n :class=\"buttonClassName\"\n @click=\"onButtonClick\"\n @keydown.space.prevent=\"onButtonClick\"\n @keydown.enter.prevent=\"onButtonClick\"\n @keydown.down=\"onArrow(1, $event)\"\n @keydown.right=\"onArrow(1, $event)\"\n @keydown.up=\"onArrow(-1, $event)\"\n @keydown.left=\"onArrow(-1, $event)\"\n @keydown.home=\"onEdge('first', $event)\"\n @keydown.end=\"onEdge('last', $event)\"\n >\n <input\n v-if=\"submitsValue\"\n type=\"hidden\"\n :name=\"resolvedName\"\n :value=\"domValue\"\n :form=\"form\"\n >\n\n <slot />\n </div>\n\n <div\n v-else\n :id=\"id\"\n ref=\"rootEl\"\n data-gr-radio\n role=\"radio\"\n :aria-checked=\"checked ? 'true' : 'false'\"\n :aria-label=\"ariaLabel\"\n :aria-disabled=\"resolvedDisabled ? 'true' : undefined\"\n :aria-invalid=\"resolvedInvalid ? 'true' : undefined\"\n :aria-required=\"required ? 'true' : undefined\"\n :aria-describedby=\"hasDescription ? descriptionId : undefined\"\n :data-value=\"domValue\"\n :tabindex=\"rovingTabindex\"\n class=\"inline-flex items-start gap-2 select-none focus-visible:outline-none focus-visible:rounded-[var(--gr-radius-md)] focus-visible:shadow-[0_0_0_2px_var(--gr-ring),0_0_0_4px_var(--gr-bg)]\"\n :class=\"rootClassName\"\n @click=\"onButtonClick\"\n @keydown.space.prevent=\"onButtonClick\"\n @keydown.enter.prevent=\"onButtonClick\"\n @keydown.down=\"onArrow(1, $event)\"\n @keydown.right=\"onArrow(1, $event)\"\n @keydown.up=\"onArrow(-1, $event)\"\n @keydown.left=\"onArrow(-1, $event)\"\n @keydown.home=\"onEdge('first', $event)\"\n @keydown.end=\"onEdge('last', $event)\"\n >\n <input\n v-if=\"submitsValue\"\n type=\"hidden\"\n :name=\"resolvedName\"\n :value=\"domValue\"\n :form=\"form\"\n >\n\n <span\n data-gr-radio-control\n aria-hidden=\"true\"\n class=\"mt-0.5 shrink-0 rounded-[var(--gr-radius-full)] border flex items-center justify-center transition-colors duration-[var(--gr-duration-fast)]\"\n :class=\"controlClassName\"\n >\n <span\n data-gr-radio-dot\n :class=\"[grRadioDotBaseClass, dotSizeClassName, dotClassName]\"\n />\n </span>\n\n <span class=\"grid gap-0.5\">\n <span data-gr-radio-label :class=\"labelClassName\">\n <slot />\n </span>\n\n <span\n v-if=\"hasDescription\"\n :id=\"descriptionId\"\n data-gr-radio-description\n :class=\"descriptionClassName\"\n >\n <slot name=\"description\" />\n </span>\n </span>\n </div>\n</template>\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"GrRadioGroup-BOIhejE6.js","names":["$slots"],"sources":["../../../../src/components/GrRadioGroup/GrRadioGroup.vue","../../../../src/components/GrRadioGroup/GrRadioGroup.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, provide, ref } from 'vue'\n\nimport { useGrComponentSize } from '../GrConfigProvider/context'\nimport { useGrFormFieldContext } from '../GrFormField/context'\nimport { useGrFormControl } from '../../composables/useGrFormControl'\nimport { useFocusWithin } from '../../composables/internal/useFocusWithin'\n\nimport type { GrButtonSize } from '../GrButton'\nimport GrButtonGroup from '../GrButtonGroup/GrButtonGroup.vue'\nimport GrRadio from '../GrRadio/GrRadio.vue'\nimport { GR_RADIO_GROUP_CONTEXT } from '../GrRadio'\nimport type { GrRadioEntry, GrRadioValue } from '../GrRadio/grRadioGroupContext'\n\nexport type GrRadioGroupVariant = 'radiobox' | 'button'\nexport type GrRadioGroupOrientation = 'vertical' | 'horizontal'\nexport interface GrRadioGroupOption {\n value: GrRadioValue\n label: string\n /** Отключить одну опцию, не переходя на слот. */\n disabled?: boolean\n /** Пояснение под подписью. Рисуется только в варианте `radiobox`. */\n description?: string\n}\n\n/**\n * GrRadioGroup — контейнер группы `GrRadio`.\n *\n * Может работать как через слот (ручной рендер `GrRadio`), так и через проп `options`.\n * Предоставляет дочерним `GrRadio` общий `modelValue`/`disabled`/`size`/`name` через `inject`.\n */\nexport interface GrRadioGroupProps {\n modelValue: GrRadioValue\n options?: GrRadioGroupOption[]\n name?: string\n disabled?: boolean\n /** Только для чтения: выбор видно, но он не меняется. */\n readonly?: boolean\n /** Визуальное и ARIA-состояние ошибки. */\n invalid?: boolean\n /** Обязательное поле (`aria-required`). */\n required?: boolean\n variant?: GrRadioGroupVariant\n /**\n * Раскладка варианта `radiobox`. Кнопочный вариант всегда горизонтальный —\n * его собирает `GrButtonGroup`.\n */\n orientation?: GrRadioGroupOrientation\n size?: GrButtonSize\n ariaLabel?: string\n}\n\nexport interface GrRadioGroupEmits {\n (e: 'update:modelValue', value: GrRadioValue): void\n (e: 'change', value: GrRadioValue): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n}\n\nconst props = withDefaults(defineProps<GrRadioGroupProps>(), {\n options: undefined,\n name: undefined,\n disabled: false,\n readonly: false,\n invalid: false,\n required: false,\n variant: 'radiobox',\n orientation: 'vertical',\n size: undefined,\n ariaLabel: undefined,\n})\n\n// Эффективный размер группы: локальный проп → `GrConfigProvider` → `md`.\n// Дочерние `GrRadio` получают уже разрешённое значение через контекст, поэтому\n// провайдер работает и для них — без второго чтения конфига.\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrRadioGroup' })\n\n// Контекст `GrFormField`. Группа — не labelable-элемент, поэтому имя приходит\n// через `aria-labelledby` на подпись поля, а не через `<label for>`.\nconst field = useGrFormFieldContext()\nconst fieldId = computed(() => field?.id.value)\nconst describedBy = computed(() => field?.describedById.value)\nconst {\n disabled: isDisabled,\n invalid: isInvalid, required: isRequired, readonly: isReadonly } = useGrFormControl(() => props)\nconst labelledBy = computed(() => (props.ariaLabel ? undefined : field?.labelId.value))\n\nconst emit = defineEmits<GrRadioGroupEmits>()\ndefineSlots<{\n /** Собственная разметка переключателей вместо генерации из `options`. */\n default?: () => any\n}>()\n\n\nfunction setValue(next: GrRadioValue): void {\n if (isDisabled.value || isReadonly.value)\n return\n emit('update:modelValue', next)\n emit('change', next)\n}\n\nconst listClass = computed(() => (props.orientation === 'horizontal'\n ? 'flex flex-wrap items-start gap-x-6 gap-y-2'\n : 'grid gap-2'))\n\nconst rootEl = ref<HTMLElement | null>(null)\n\n// Фокус ходит между радиокнопками: без границы каждая стрелка давала бы\n// потребителю пару `blur` + `focus`.\nconst { onFocusIn, onFocusOut } = useFocusWithin(rootEl, {\n enter: event => emit('focus', event),\n leave: event => emit('blur', event),\n})\n\nfunction focus(): void {\n rootEl.value?.querySelector<HTMLElement>('[role=\"radio\"][tabindex=\"0\"]')?.focus()\n}\n\nfunction blur(): void {\n rootEl.value?.querySelector<HTMLElement>('[role=\"radio\"][tabindex=\"0\"]')?.blur()\n}\n\ndefineExpose({ focus, blur })\n\nconst entries = ref<GrRadioEntry[]>([])\n\nfunction register(entry: GrRadioEntry): () => void {\n entries.value.push(entry)\n return () => {\n const index = entries.value.indexOf(entry)\n if (index >= 0) entries.value.splice(index, 1)\n }\n}\n\nconst enabledValues = computed(() =>\n entries.value.filter(entry => !entry.disabled()).map(entry => entry.value()),\n)\n\nconst rovingValue = computed(() => {\n if (enabledValues.value.includes(props.modelValue)) return props.modelValue\n return enabledValues.value[0]\n})\n\nfunction moveSelection(from: GrRadioValue, direction: 1 | -1): GrRadioValue | undefined {\n const values = enabledValues.value\n if (values.length === 0) return undefined\n\n const current = values.indexOf(from)\n // Стрелки зациклены — это требование паттерна radiogroup.\n const next = values[(current + direction + values.length) % values.length]\n\n if (next !== undefined) setValue(next)\n return next\n}\n\n/** `Home`/`End` — на края набора; как в `GrSegmented`, где паттерн реализован целиком. */\nfunction selectEdge(edge: 'first' | 'last'): GrRadioValue | undefined {\n const values = enabledValues.value\n const next = edge === 'first' ? values[0] : values.at(-1)\n\n if (next !== undefined) setValue(next)\n return next\n}\n\nprovide(GR_RADIO_GROUP_CONTEXT, {\n modelValue: computed(() => props.modelValue),\n name: computed(() => props.name),\n disabled: computed(() => isDisabled.value),\n readonly: isReadonly,\n invalid: isInvalid,\n size: resolvedSize,\n setValue,\n register,\n rovingValue,\n moveSelection,\n selectEdge,\n})\n</script>\n\n<template>\n <div\n :id=\"fieldId\"\n ref=\"rootEl\"\n data-gr-radio-group\n role=\"radiogroup\"\n :aria-label=\"ariaLabel\"\n :aria-labelledby=\"labelledBy\"\n :aria-describedby=\"describedBy\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n :aria-required=\"isRequired ? 'true' : undefined\"\n :aria-readonly=\"isReadonly ? 'true' : undefined\"\n :aria-disabled=\"isDisabled ? 'true' : undefined\"\n @focusin=\"onFocusIn\"\n @focusout=\"onFocusOut\"\n >\n <template v-if=\"$slots.default\">\n <GrButtonGroup v-if=\"variant === 'button'\">\n <slot />\n </GrButtonGroup>\n <div v-else :class=\"listClass\">\n <slot />\n </div>\n </template>\n <template v-else>\n <GrButtonGroup v-if=\"variant === 'button'\">\n <GrRadio\n v-for=\"opt in options ?? []\"\n :key=\"String(opt.value)\"\n :value=\"opt.value\"\n :disabled=\"opt.disabled\"\n variant=\"button\"\n :size=\"resolvedSize\"\n >\n {{ opt.label }}\n </GrRadio>\n </GrButtonGroup>\n <div v-else :class=\"listClass\">\n <GrRadio\n v-for=\"opt in options ?? []\"\n :key=\"String(opt.value)\"\n :value=\"opt.value\"\n :disabled=\"opt.disabled\"\n >\n {{ opt.label }}\n\n <template v-if=\"opt.description\" #description>\n {{ opt.description }}\n </template>\n </GrRadio>\n </div>\n </template>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, provide, ref } from 'vue'\n\nimport { useGrComponentSize } from '../GrConfigProvider/context'\nimport { useGrFormFieldContext } from '../GrFormField/context'\nimport { useGrFormControl } from '../../composables/useGrFormControl'\nimport { useFocusWithin } from '../../composables/internal/useFocusWithin'\n\nimport type { GrButtonSize } from '../GrButton'\nimport GrButtonGroup from '../GrButtonGroup/GrButtonGroup.vue'\nimport GrRadio from '../GrRadio/GrRadio.vue'\nimport { GR_RADIO_GROUP_CONTEXT } from '../GrRadio'\nimport type { GrRadioEntry, GrRadioValue } from '../GrRadio/grRadioGroupContext'\n\nexport type GrRadioGroupVariant = 'radiobox' | 'button'\nexport type GrRadioGroupOrientation = 'vertical' | 'horizontal'\nexport interface GrRadioGroupOption {\n value: GrRadioValue\n label: string\n /** Отключить одну опцию, не переходя на слот. */\n disabled?: boolean\n /** Пояснение под подписью. Рисуется только в варианте `radiobox`. */\n description?: string\n}\n\n/**\n * GrRadioGroup — контейнер группы `GrRadio`.\n *\n * Может работать как через слот (ручной рендер `GrRadio`), так и через проп `options`.\n * Предоставляет дочерним `GrRadio` общий `modelValue`/`disabled`/`size`/`name` через `inject`.\n */\nexport interface GrRadioGroupProps {\n modelValue: GrRadioValue\n options?: GrRadioGroupOption[]\n name?: string\n disabled?: boolean\n /** Только для чтения: выбор видно, но он не меняется. */\n readonly?: boolean\n /** Визуальное и ARIA-состояние ошибки. */\n invalid?: boolean\n /** Обязательное поле (`aria-required`). */\n required?: boolean\n variant?: GrRadioGroupVariant\n /**\n * Раскладка варианта `radiobox`. Кнопочный вариант всегда горизонтальный —\n * его собирает `GrButtonGroup`.\n */\n orientation?: GrRadioGroupOrientation\n size?: GrButtonSize\n ariaLabel?: string\n}\n\nexport interface GrRadioGroupEmits {\n (e: 'update:modelValue', value: GrRadioValue): void\n (e: 'change', value: GrRadioValue): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n}\n\nconst props = withDefaults(defineProps<GrRadioGroupProps>(), {\n options: undefined,\n name: undefined,\n disabled: false,\n readonly: false,\n invalid: false,\n required: false,\n variant: 'radiobox',\n orientation: 'vertical',\n size: undefined,\n ariaLabel: undefined,\n})\n\n// Эффективный размер группы: локальный проп → `GrConfigProvider` → `md`.\n// Дочерние `GrRadio` получают уже разрешённое значение через контекст, поэтому\n// провайдер работает и для них — без второго чтения конфига.\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrRadioGroup' })\n\n// Контекст `GrFormField`. Группа — не labelable-элемент, поэтому имя приходит\n// через `aria-labelledby` на подпись поля, а не через `<label for>`.\nconst field = useGrFormFieldContext()\nconst fieldId = computed(() => field?.id.value)\nconst describedBy = computed(() => field?.describedById.value)\nconst {\n disabled: isDisabled,\n invalid: isInvalid, required: isRequired, readonly: isReadonly } = useGrFormControl(() => props)\nconst labelledBy = computed(() => (props.ariaLabel ? undefined : field?.labelId.value))\n\nconst emit = defineEmits<GrRadioGroupEmits>()\ndefineSlots<{\n /** Собственная разметка переключателей вместо генерации из `options`. */\n default?: () => any\n}>()\n\n\nfunction setValue(next: GrRadioValue): void {\n if (isDisabled.value || isReadonly.value)\n return\n emit('update:modelValue', next)\n emit('change', next)\n}\n\nconst listClass = computed(() => (props.orientation === 'horizontal'\n ? 'flex flex-wrap items-start gap-x-6 gap-y-2'\n : 'grid gap-2'))\n\nconst rootEl = ref<HTMLElement | null>(null)\n\n// Фокус ходит между радиокнопками: без границы каждая стрелка давала бы\n// потребителю пару `blur` + `focus`.\nconst { onFocusIn, onFocusOut } = useFocusWithin(rootEl, {\n enter: event => emit('focus', event),\n leave: event => emit('blur', event),\n})\n\nfunction focus(): void {\n rootEl.value?.querySelector<HTMLElement>('[role=\"radio\"][tabindex=\"0\"]')?.focus()\n}\n\nfunction blur(): void {\n rootEl.value?.querySelector<HTMLElement>('[role=\"radio\"][tabindex=\"0\"]')?.blur()\n}\n\ndefineExpose({ focus, blur })\n\nconst entries = ref<GrRadioEntry[]>([])\n\nfunction register(entry: GrRadioEntry): () => void {\n entries.value.push(entry)\n return () => {\n const index = entries.value.indexOf(entry)\n if (index >= 0) entries.value.splice(index, 1)\n }\n}\n\nconst enabledValues = computed(() =>\n entries.value.filter(entry => !entry.disabled()).map(entry => entry.value()),\n)\n\nconst rovingValue = computed(() => {\n if (enabledValues.value.includes(props.modelValue)) return props.modelValue\n return enabledValues.value[0]\n})\n\nfunction moveSelection(from: GrRadioValue, direction: 1 | -1): GrRadioValue | undefined {\n const values = enabledValues.value\n if (values.length === 0) return undefined\n\n const current = values.indexOf(from)\n // Стрелки зациклены — это требование паттерна radiogroup.\n const next = values[(current + direction + values.length) % values.length]\n\n if (next !== undefined) setValue(next)\n return next\n}\n\n/** `Home`/`End` — на края набора; как в `GrSegmented`, где паттерн реализован целиком. */\nfunction selectEdge(edge: 'first' | 'last'): GrRadioValue | undefined {\n const values = enabledValues.value\n const next = edge === 'first' ? values[0] : values.at(-1)\n\n if (next !== undefined) setValue(next)\n return next\n}\n\nprovide(GR_RADIO_GROUP_CONTEXT, {\n modelValue: computed(() => props.modelValue),\n name: computed(() => props.name),\n disabled: computed(() => isDisabled.value),\n readonly: isReadonly,\n invalid: isInvalid,\n size: resolvedSize,\n setValue,\n register,\n rovingValue,\n moveSelection,\n selectEdge,\n})\n</script>\n\n<template>\n <div\n :id=\"fieldId\"\n ref=\"rootEl\"\n data-gr-radio-group\n role=\"radiogroup\"\n :aria-label=\"ariaLabel\"\n :aria-labelledby=\"labelledBy\"\n :aria-describedby=\"describedBy\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n :aria-required=\"isRequired ? 'true' : undefined\"\n :aria-readonly=\"isReadonly ? 'true' : undefined\"\n :aria-disabled=\"isDisabled ? 'true' : undefined\"\n @focusin=\"onFocusIn\"\n @focusout=\"onFocusOut\"\n >\n <template v-if=\"$slots.default\">\n <GrButtonGroup v-if=\"variant === 'button'\">\n <slot />\n </GrButtonGroup>\n <div v-else :class=\"listClass\">\n <slot />\n </div>\n </template>\n <template v-else>\n <GrButtonGroup v-if=\"variant === 'button'\">\n <GrRadio\n v-for=\"opt in options ?? []\"\n :key=\"String(opt.value)\"\n :value=\"opt.value\"\n :disabled=\"opt.disabled\"\n variant=\"button\"\n :size=\"resolvedSize\"\n >\n {{ opt.label }}\n </GrRadio>\n </GrButtonGroup>\n <div v-else :class=\"listClass\">\n <GrRadio\n v-for=\"opt in options ?? []\"\n :key=\"String(opt.value)\"\n :value=\"opt.value\"\n :disabled=\"opt.disabled\"\n >\n {{ opt.label }}\n\n <template v-if=\"opt.description\" #description>\n {{ opt.description }}\n </template>\n </GrRadio>\n </div>\n </template>\n </div>\n</template>\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"GrRating-JrHXz0Rv.js","names":["$slots"],"sources":["../../../../src/components/GrRating/GrRating.vue","../../../../src/components/GrRating/GrRating.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\n\nimport { useGrComponentSize } from '../GrConfigProvider/context'\n\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { useGrFormFieldContext } from '../GrFormField/context'\nimport { useGrFormControl } from '../../composables/useGrFormControl'\n\nimport {\n ratingFillClassByTone,\n ratingRootClass,\n ratingSymbolClass,\n ratingTextSizeBySize,\n ratingVoidClass,\n type GrRatingSize,\n type GrRatingTone,\n} from './grRatingStyles'\n\nexport type {\n GrRatingSize,\n GrRatingTone,\n} from './grRatingStyles'\n\n/**\n * Публичный GR-примитив «Rating» — шкала оценки символами (по умолчанию звёзды).\n *\n * Интерактивный режим реализует WAI-ARIA slider pattern (`role=\"slider\"`,\n * `aria-valuemin/max/now/valuetext`, стрелки / Home / End), режим только для\n * чтения — `role=\"img\"` с текстовой подписью, чтобы скринридер озвучил оценку\n * одной фразой, а не набором элементов.\n *\n * Символ по умолчанию рисуется инлайновым SVG (звезда с `fill=\"currentColor\"`) —\n * иконочные шрифты-маски `i-lucide-star` дают только контур. Свой символ\n * задаётся пропом `icon` (UnoCSS-класс иконки) или слотом `#symbol`.\n */\nexport interface GrRatingProps {\n /** Текущая оценка. Дробная (`3.5`) поддерживается при `allowHalf`. */\n modelValue: number\n /** Количество символов шкалы. */\n max?: number\n size?: GrRatingSize\n /** Тон заливки; точечно перекрывается переменной `--gr-rating-color`. */\n tone?: GrRatingTone\n /** Половинчатые оценки: клик по левой половине символа даёт `.5`. */\n allowHalf?: boolean\n /** Только показ: без ввода и фокуса. */\n readonly?: boolean\n disabled?: boolean\n /** Визуальное и ARIA-состояние ошибки. */\n invalid?: boolean\n /** Обязательное поле (`aria-required`). */\n required?: boolean\n /** Повторный клик по текущей оценке сбрасывает её в `0`. */\n clearable?: boolean\n /** UnoCSS-класс иконки вместо встроенной звезды (например `i-lucide-heart`). */\n icon?: string\n /** Показывать числовую подпись справа от шкалы. */\n showText?: boolean\n /** Формат подписи. По умолчанию — само значение. Сильнее `texts`. */\n formatText?: (value: number) => string\n /**\n * Подписи по делениям: «3 из 5, нормально» вместо «3 из 5». Уходят и в\n * видимый текст, и в `aria-valuetext` — ради этого рейтинг и существует.\n * Массив короче `max` оставляет верхние деления без подписи.\n */\n texts?: string[]\n /**\n * Компактный вид: рисуются только заполненные символы. Осмысленно вместе с\n * `readonly` — в списках и таблицах пять звёзд в каждой строке съедают ширину.\n */\n compact?: boolean\n ariaLabel?: string\n /** Имя для нативной формы: hidden input со значением; `0` — «не выбрано», input не рендерится. */\n name?: string\n}\n\nexport interface GrRatingEmits {\n (e: 'update:modelValue', value: number): void\n /** Оценка зафиксирована (клик / клавиша). */\n (e: 'change', value: number): void\n /** Значение под курсором; `null` — курсор ушёл со шкалы. */\n (e: 'hoverChange', value: number | null): void\n /** Оценка снята повторным кликом; только при `clearable`. */\n (e: 'clear'): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n}\n\nconst props = withDefaults(\n defineProps<GrRatingProps>(),\n {\n max: 5,\n size: undefined,\n tone: 'warning',\n allowHalf: false,\n readonly: false,\n disabled: false,\n invalid: false,\n required: false,\n clearable: false,\n icon: undefined,\n showText: false,\n formatText: undefined,\n texts: undefined,\n compact: false,\n ariaLabel: undefined,\n name: undefined,\n },\n)\n\n// Эффективный размер: локальный проп → `GrConfigProvider` → дефолт компонента.\nconst resolvedSize = useGrComponentSize(() => props.size, {\n component: 'GrRating',\n supported: ['xs', 'sm', 'md', 'lg'],\n})\n\nconst emit = defineEmits<GrRatingEmits>()\ndefineSlots<{\n /** Свой символ вместо звезды. `filled` — закрашенная половина символа. */\n symbol?: (props: { index: number, filled: boolean }) => any\n /** Подпись рядом с оценкой. */\n text?: (props: { value: number }) => any\n}>()\n\n\nconst { t } = useGranularityTranslations()\n\n// Контекст `GrFormField`: id/aria-describedby/invalid/required как fallback.\nconst field = useGrFormFieldContext()\nconst resolvedId = computed(() => field?.id.value)\nconst {\n disabled: isDisabled,\n invalid: isInvalid, required: isRequired, readonly: isReadonly } = useGrFormControl(() => props)\nconst describedBy = computed(() => field?.describedById.value)\n\nconst rootEl = ref<HTMLElement | null>(null)\n\nfunction focus(): void {\n rootEl.value?.querySelector<HTMLElement>('[data-gr-rating-scale]')?.focus()\n}\n\nfunction blur(): void {\n rootEl.value?.querySelector<HTMLElement>('[data-gr-rating-scale]')?.blur()\n}\n\ndefineExpose({ focus, blur })\n\n/** Ввод возможен: не readonly и не disabled. */\nconst interactive = computed(() => !isReadonly.value && !isDisabled.value)\n\n/**\n * `aria-readonly`, `aria-invalid` и `aria-required` вешаются только в\n * slider-режиме: на `role=\"img\"` они не разрешены и дают critical в axe.\n *\n * Роль контрола (`slider`) сохраняется и в `disabled` — отключённый слайдер это\n * штатный ARIA-паттерн. А вот `readonly` — это уже не контрол, а картинка с\n * подписью: на `role=\"img\"` атрибуты `aria-value*`/`aria-disabled` запрещены\n * (axe: `aria-allowed-attr`), поэтому там их не выводим вовсе.\n */\nconst asSlider = computed(() => !props.readonly)\nconst step = computed(() => (props.allowHalf ? 0.5 : 1))\n\n// Значение под курсором имеет приоритет над моделью — это «предпросмотр» оценки.\nconst hoverValue = ref<number | null>(null)\nconst displayValue = computed(() => hoverValue.value ?? clamp(props.modelValue))\n\nfunction clamp(value: number): number {\n if (!Number.isFinite(value)) return 0\n return Math.min(props.max, Math.max(0, value))\n}\n\n/** Доля заливки символа с индексом `index` (0..1). */\nfunction fillRatio(index: number): number {\n return Math.min(1, Math.max(0, displayValue.value - index))\n}\n\nconst symbols = computed(() => {\n // Компактный вид показывает только заполненную часть шкалы; половинка\n // считается символом — иначе «2.5» потеряло бы половину звезды.\n const length = props.compact ? Math.ceil(clamp(props.modelValue)) : props.max\n return Array.from({ length }, (_, index) => index)\n})\n\n/** Подпись деления под текущим значением: половинки округляются вверх. */\nconst currentLabel = computed(() => {\n if (!props.texts?.length || displayValue.value <= 0) return undefined\n return props.texts[Math.ceil(displayValue.value) - 1]\n})\n\nconst valueText = computed(() => {\n const params = { value: displayValue.value, max: props.max }\n\n // Порядок слов задаёт локаль, а не конкатенация в коде.\n return currentLabel.value\n ? t('gr.rating.valueTextWithLabel', '{value} of {max}, {label}', { ...params, label: currentLabel.value })\n : t('gr.rating.valueText', '{value} of {max}', params)\n})\n\nconst resolvedAriaLabel = computed(() => props.ariaLabel ?? t('gr.rating.label', 'Rating'))\n\nconst text = computed(() => {\n if (props.formatText) return props.formatText(displayValue.value)\n return currentLabel.value ?? String(displayValue.value)\n})\n\n/** Значение, соответствующее позиции курсора внутри символа `index`. */\nfunction valueAt(index: number, event: MouseEvent): number {\n if (!props.allowHalf) return index + 1\n const rect = (event.currentTarget as HTMLElement).getBoundingClientRect()\n return rect.width > 0 && event.clientX - rect.left < rect.width / 2 ? index + 0.5 : index + 1\n}\n\nfunction onScaleBlur(event: FocusEvent): void {\n resetHover()\n emit('blur', event)\n}\n\nfunction commit(value: number): void {\n const next = clamp(value)\n if (next !== props.modelValue) emit('update:modelValue', next)\n emit('change', next)\n}\n\nfunction onSymbolClick(index: number, event: MouseEvent): void {\n if (!interactive.value) return\n const next = valueAt(index, event)\n const cleared = props.clearable && next === props.modelValue\n\n commit(cleared ? 0 : next)\n if (cleared) emit('clear')\n}\n\nfunction onSymbolMouseMove(index: number, event: MouseEvent): void {\n if (!interactive.value) {\n // Режим сменился под курсором — оставлять подсветку от прошлого состояния нельзя.\n resetHover()\n return\n }\n const next = valueAt(index, event)\n if (next === hoverValue.value) return\n hoverValue.value = next\n emit('hoverChange', next)\n}\n\nfunction resetHover(): void {\n if (hoverValue.value === null) return\n hoverValue.value = null\n emit('hoverChange', null)\n}\n\nfunction onKeydown(event: KeyboardEvent): void {\n if (!interactive.value) return\n const current = clamp(props.modelValue)\n let next: number | null = null\n\n switch (event.key) {\n case 'ArrowRight':\n case 'ArrowUp':\n next = current + step.value\n break\n case 'ArrowLeft':\n case 'ArrowDown':\n next = current - step.value\n break\n case 'Home':\n next = 0\n break\n case 'End':\n next = props.max\n break\n default:\n return\n }\n\n event.preventDefault()\n commit(next)\n}\n</script>\n\n<template>\n <div\n ref=\"rootEl\"\n data-gr-rating\n class=\"inline-flex items-center gap-2\"\n >\n <!-- Нативная форма: роль-виджет не labelable и в submit не попадает. -->\n <input\n v-if=\"name && modelValue > 0\"\n type=\"hidden\"\n :name=\"name\"\n :value=\"String(modelValue)\"\n >\n <div\n :id=\"resolvedId\"\n data-gr-rating-scale\n data-testid=\"gr-rating-scale\"\n :class=\"ratingRootClass({ size: resolvedSize, disabled, interactive })\"\n :role=\"asSlider ? 'slider' : 'img'\"\n :tabindex=\"asSlider ? (disabled ? -1 : 0) : undefined\"\n :aria-label=\"asSlider ? resolvedAriaLabel : `${resolvedAriaLabel}: ${valueText}`\"\n :aria-valuemin=\"asSlider ? 0 : undefined\"\n :aria-valuemax=\"asSlider ? max : undefined\"\n :aria-valuenow=\"asSlider ? displayValue : undefined\"\n :aria-valuetext=\"asSlider ? valueText : undefined\"\n :aria-orientation=\"asSlider ? 'horizontal' : undefined\"\n :aria-disabled=\"asSlider && disabled ? 'true' : undefined\"\n :aria-invalid=\"asSlider && isInvalid ? 'true' : undefined\"\n :aria-describedby=\"asSlider ? describedBy : undefined\"\n :aria-required=\"asSlider && isRequired ? 'true' : undefined\"\n :aria-readonly=\"asSlider && isReadonly ? 'true' : undefined\"\n @keydown=\"onKeydown\"\n @mouseleave=\"resetHover\"\n @focus=\"emit('focus', $event)\"\n @blur=\"onScaleBlur\"\n >\n <span\n v-for=\"index in symbols\"\n :key=\"index\"\n data-gr-rating-symbol\n :data-testid=\"`gr-rating-symbol-${index}`\"\n :class=\"ratingSymbolClass(resolvedSize)\"\n @click=\"onSymbolClick(index, $event)\"\n @mousemove=\"onSymbolMouseMove(index, $event)\"\n >\n <!-- Нижний слой — «пустой» символ, поверх него обрезанный по ширине «залитый». -->\n <span class=\"block h-full w-full\" :class=\"ratingVoidClass\" aria-hidden=\"true\">\n <slot name=\"symbol\" :index=\"index\" :filled=\"false\">\n <span v-if=\"icon\" class=\"block h-full w-full\" :class=\"icon\" />\n <svg v-else viewBox=\"0 0 24 24\" fill=\"currentColor\" class=\"block h-full w-full\">\n <path d=\"M12 2.5l2.9 6.1 6.6.9-4.8 4.7 1.2 6.6L12 17.7 6.1 20.8l1.2-6.6L2.5 9.5l6.6-.9z\" />\n </svg>\n </slot>\n </span>\n\n <span\n data-gr-rating-fill\n class=\"pointer-events-none absolute inset-y-0 left-0 overflow-hidden\"\n :class=\"ratingFillClassByTone[tone]\"\n :style=\"{ width: `${fillRatio(index) * 100}%` }\"\n aria-hidden=\"true\"\n >\n <span :class=\"ratingSymbolClass(resolvedSize)\">\n <slot name=\"symbol\" :index=\"index\" :filled=\"true\">\n <span v-if=\"icon\" class=\"block h-full w-full\" :class=\"icon\" />\n <svg v-else viewBox=\"0 0 24 24\" fill=\"currentColor\" class=\"block h-full w-full\">\n <path d=\"M12 2.5l2.9 6.1 6.6.9-4.8 4.7 1.2 6.6L12 17.7 6.1 20.8l1.2-6.6L2.5 9.5l6.6-.9z\" />\n </svg>\n </slot>\n </span>\n </span>\n </span>\n </div>\n\n <span\n v-if=\"showText || $slots.text\"\n data-gr-rating-text\n class=\"text-[var(--gr-muted-fg)] [font-variant-numeric:tabular-nums]\"\n :class=\"ratingTextSizeBySize[resolvedSize]\"\n >\n <slot name=\"text\" :value=\"displayValue\">{{ text }}</slot>\n </span>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\n\nimport { useGrComponentSize } from '../GrConfigProvider/context'\n\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { useGrFormFieldContext } from '../GrFormField/context'\nimport { useGrFormControl } from '../../composables/useGrFormControl'\n\nimport {\n ratingFillClassByTone,\n ratingRootClass,\n ratingSymbolClass,\n ratingTextSizeBySize,\n ratingVoidClass,\n type GrRatingSize,\n type GrRatingTone,\n} from './grRatingStyles'\n\nexport type {\n GrRatingSize,\n GrRatingTone,\n} from './grRatingStyles'\n\n/**\n * Публичный GR-примитив «Rating» — шкала оценки символами (по умолчанию звёзды).\n *\n * Интерактивный режим реализует WAI-ARIA slider pattern (`role=\"slider\"`,\n * `aria-valuemin/max/now/valuetext`, стрелки / Home / End), режим только для\n * чтения — `role=\"img\"` с текстовой подписью, чтобы скринридер озвучил оценку\n * одной фразой, а не набором элементов.\n *\n * Символ по умолчанию рисуется инлайновым SVG (звезда с `fill=\"currentColor\"`) —\n * иконочные шрифты-маски `i-lucide-star` дают только контур. Свой символ\n * задаётся пропом `icon` (UnoCSS-класс иконки) или слотом `#symbol`.\n */\nexport interface GrRatingProps {\n /** Текущая оценка. Дробная (`3.5`) поддерживается при `allowHalf`. */\n modelValue: number\n /** Количество символов шкалы. */\n max?: number\n size?: GrRatingSize\n /** Тон заливки; точечно перекрывается переменной `--gr-rating-color`. */\n tone?: GrRatingTone\n /** Половинчатые оценки: клик по левой половине символа даёт `.5`. */\n allowHalf?: boolean\n /** Только показ: без ввода и фокуса. */\n readonly?: boolean\n disabled?: boolean\n /** Визуальное и ARIA-состояние ошибки. */\n invalid?: boolean\n /** Обязательное поле (`aria-required`). */\n required?: boolean\n /** Повторный клик по текущей оценке сбрасывает её в `0`. */\n clearable?: boolean\n /** UnoCSS-класс иконки вместо встроенной звезды (например `i-lucide-heart`). */\n icon?: string\n /** Показывать числовую подпись справа от шкалы. */\n showText?: boolean\n /** Формат подписи. По умолчанию — само значение. Сильнее `texts`. */\n formatText?: (value: number) => string\n /**\n * Подписи по делениям: «3 из 5, нормально» вместо «3 из 5». Уходят и в\n * видимый текст, и в `aria-valuetext` — ради этого рейтинг и существует.\n * Массив короче `max` оставляет верхние деления без подписи.\n */\n texts?: string[]\n /**\n * Компактный вид: рисуются только заполненные символы. Осмысленно вместе с\n * `readonly` — в списках и таблицах пять звёзд в каждой строке съедают ширину.\n */\n compact?: boolean\n ariaLabel?: string\n /** Имя для нативной формы: hidden input со значением; `0` — «не выбрано», input не рендерится. */\n name?: string\n}\n\nexport interface GrRatingEmits {\n (e: 'update:modelValue', value: number): void\n /** Оценка зафиксирована (клик / клавиша). */\n (e: 'change', value: number): void\n /** Значение под курсором; `null` — курсор ушёл со шкалы. */\n (e: 'hoverChange', value: number | null): void\n /** Оценка снята повторным кликом; только при `clearable`. */\n (e: 'clear'): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n}\n\nconst props = withDefaults(\n defineProps<GrRatingProps>(),\n {\n max: 5,\n size: undefined,\n tone: 'warning',\n allowHalf: false,\n readonly: false,\n disabled: false,\n invalid: false,\n required: false,\n clearable: false,\n icon: undefined,\n showText: false,\n formatText: undefined,\n texts: undefined,\n compact: false,\n ariaLabel: undefined,\n name: undefined,\n },\n)\n\n// Эффективный размер: локальный проп → `GrConfigProvider` → дефолт компонента.\nconst resolvedSize = useGrComponentSize(() => props.size, {\n component: 'GrRating',\n supported: ['xs', 'sm', 'md', 'lg'],\n})\n\nconst emit = defineEmits<GrRatingEmits>()\ndefineSlots<{\n /** Свой символ вместо звезды. `filled` — закрашенная половина символа. */\n symbol?: (props: { index: number, filled: boolean }) => any\n /** Подпись рядом с оценкой. */\n text?: (props: { value: number }) => any\n}>()\n\n\nconst { t } = useGranularityTranslations()\n\n// Контекст `GrFormField`: id/aria-describedby/invalid/required как fallback.\nconst field = useGrFormFieldContext()\nconst resolvedId = computed(() => field?.id.value)\nconst {\n disabled: isDisabled,\n invalid: isInvalid, required: isRequired, readonly: isReadonly } = useGrFormControl(() => props)\nconst describedBy = computed(() => field?.describedById.value)\n\nconst rootEl = ref<HTMLElement | null>(null)\n\nfunction focus(): void {\n rootEl.value?.querySelector<HTMLElement>('[data-gr-rating-scale]')?.focus()\n}\n\nfunction blur(): void {\n rootEl.value?.querySelector<HTMLElement>('[data-gr-rating-scale]')?.blur()\n}\n\ndefineExpose({ focus, blur })\n\n/** Ввод возможен: не readonly и не disabled. */\nconst interactive = computed(() => !isReadonly.value && !isDisabled.value)\n\n/**\n * `aria-readonly`, `aria-invalid` и `aria-required` вешаются только в\n * slider-режиме: на `role=\"img\"` они не разрешены и дают critical в axe.\n *\n * Роль контрола (`slider`) сохраняется и в `disabled` — отключённый слайдер это\n * штатный ARIA-паттерн. А вот `readonly` — это уже не контрол, а картинка с\n * подписью: на `role=\"img\"` атрибуты `aria-value*`/`aria-disabled` запрещены\n * (axe: `aria-allowed-attr`), поэтому там их не выводим вовсе.\n */\nconst asSlider = computed(() => !props.readonly)\nconst step = computed(() => (props.allowHalf ? 0.5 : 1))\n\n// Значение под курсором имеет приоритет над моделью — это «предпросмотр» оценки.\nconst hoverValue = ref<number | null>(null)\nconst displayValue = computed(() => hoverValue.value ?? clamp(props.modelValue))\n\nfunction clamp(value: number): number {\n if (!Number.isFinite(value)) return 0\n return Math.min(props.max, Math.max(0, value))\n}\n\n/** Доля заливки символа с индексом `index` (0..1). */\nfunction fillRatio(index: number): number {\n return Math.min(1, Math.max(0, displayValue.value - index))\n}\n\nconst symbols = computed(() => {\n // Компактный вид показывает только заполненную часть шкалы; половинка\n // считается символом — иначе «2.5» потеряло бы половину звезды.\n const length = props.compact ? Math.ceil(clamp(props.modelValue)) : props.max\n return Array.from({ length }, (_, index) => index)\n})\n\n/** Подпись деления под текущим значением: половинки округляются вверх. */\nconst currentLabel = computed(() => {\n if (!props.texts?.length || displayValue.value <= 0) return undefined\n return props.texts[Math.ceil(displayValue.value) - 1]\n})\n\nconst valueText = computed(() => {\n const params = { value: displayValue.value, max: props.max }\n\n // Порядок слов задаёт локаль, а не конкатенация в коде.\n return currentLabel.value\n ? t('gr.rating.valueTextWithLabel', '{value} of {max}, {label}', { ...params, label: currentLabel.value })\n : t('gr.rating.valueText', '{value} of {max}', params)\n})\n\nconst resolvedAriaLabel = computed(() => props.ariaLabel ?? t('gr.rating.label', 'Rating'))\n\nconst text = computed(() => {\n if (props.formatText) return props.formatText(displayValue.value)\n return currentLabel.value ?? String(displayValue.value)\n})\n\n/** Значение, соответствующее позиции курсора внутри символа `index`. */\nfunction valueAt(index: number, event: MouseEvent): number {\n if (!props.allowHalf) return index + 1\n const rect = (event.currentTarget as HTMLElement).getBoundingClientRect()\n return rect.width > 0 && event.clientX - rect.left < rect.width / 2 ? index + 0.5 : index + 1\n}\n\nfunction onScaleBlur(event: FocusEvent): void {\n resetHover()\n emit('blur', event)\n}\n\nfunction commit(value: number): void {\n const next = clamp(value)\n if (next !== props.modelValue) emit('update:modelValue', next)\n emit('change', next)\n}\n\nfunction onSymbolClick(index: number, event: MouseEvent): void {\n if (!interactive.value) return\n const next = valueAt(index, event)\n const cleared = props.clearable && next === props.modelValue\n\n commit(cleared ? 0 : next)\n if (cleared) emit('clear')\n}\n\nfunction onSymbolMouseMove(index: number, event: MouseEvent): void {\n if (!interactive.value) {\n // Режим сменился под курсором — оставлять подсветку от прошлого состояния нельзя.\n resetHover()\n return\n }\n const next = valueAt(index, event)\n if (next === hoverValue.value) return\n hoverValue.value = next\n emit('hoverChange', next)\n}\n\nfunction resetHover(): void {\n if (hoverValue.value === null) return\n hoverValue.value = null\n emit('hoverChange', null)\n}\n\nfunction onKeydown(event: KeyboardEvent): void {\n if (!interactive.value) return\n const current = clamp(props.modelValue)\n let next: number | null = null\n\n switch (event.key) {\n case 'ArrowRight':\n case 'ArrowUp':\n next = current + step.value\n break\n case 'ArrowLeft':\n case 'ArrowDown':\n next = current - step.value\n break\n case 'Home':\n next = 0\n break\n case 'End':\n next = props.max\n break\n default:\n return\n }\n\n event.preventDefault()\n commit(next)\n}\n</script>\n\n<template>\n <div\n ref=\"rootEl\"\n data-gr-rating\n class=\"inline-flex items-center gap-2\"\n >\n <!-- Нативная форма: роль-виджет не labelable и в submit не попадает. -->\n <input\n v-if=\"name && modelValue > 0\"\n type=\"hidden\"\n :name=\"name\"\n :value=\"String(modelValue)\"\n >\n <div\n :id=\"resolvedId\"\n data-gr-rating-scale\n data-testid=\"gr-rating-scale\"\n :class=\"ratingRootClass({ size: resolvedSize, disabled, interactive })\"\n :role=\"asSlider ? 'slider' : 'img'\"\n :tabindex=\"asSlider ? (disabled ? -1 : 0) : undefined\"\n :aria-label=\"asSlider ? resolvedAriaLabel : `${resolvedAriaLabel}: ${valueText}`\"\n :aria-valuemin=\"asSlider ? 0 : undefined\"\n :aria-valuemax=\"asSlider ? max : undefined\"\n :aria-valuenow=\"asSlider ? displayValue : undefined\"\n :aria-valuetext=\"asSlider ? valueText : undefined\"\n :aria-orientation=\"asSlider ? 'horizontal' : undefined\"\n :aria-disabled=\"asSlider && disabled ? 'true' : undefined\"\n :aria-invalid=\"asSlider && isInvalid ? 'true' : undefined\"\n :aria-describedby=\"asSlider ? describedBy : undefined\"\n :aria-required=\"asSlider && isRequired ? 'true' : undefined\"\n :aria-readonly=\"asSlider && isReadonly ? 'true' : undefined\"\n @keydown=\"onKeydown\"\n @mouseleave=\"resetHover\"\n @focus=\"emit('focus', $event)\"\n @blur=\"onScaleBlur\"\n >\n <span\n v-for=\"index in symbols\"\n :key=\"index\"\n data-gr-rating-symbol\n :data-testid=\"`gr-rating-symbol-${index}`\"\n :class=\"ratingSymbolClass(resolvedSize)\"\n @click=\"onSymbolClick(index, $event)\"\n @mousemove=\"onSymbolMouseMove(index, $event)\"\n >\n <!-- Нижний слой — «пустой» символ, поверх него обрезанный по ширине «залитый». -->\n <span class=\"block h-full w-full\" :class=\"ratingVoidClass\" aria-hidden=\"true\">\n <slot name=\"symbol\" :index=\"index\" :filled=\"false\">\n <span v-if=\"icon\" class=\"block h-full w-full\" :class=\"icon\" />\n <svg v-else viewBox=\"0 0 24 24\" fill=\"currentColor\" class=\"block h-full w-full\">\n <path d=\"M12 2.5l2.9 6.1 6.6.9-4.8 4.7 1.2 6.6L12 17.7 6.1 20.8l1.2-6.6L2.5 9.5l6.6-.9z\" />\n </svg>\n </slot>\n </span>\n\n <span\n data-gr-rating-fill\n class=\"pointer-events-none absolute inset-y-0 left-0 overflow-hidden\"\n :class=\"ratingFillClassByTone[tone]\"\n :style=\"{ width: `${fillRatio(index) * 100}%` }\"\n aria-hidden=\"true\"\n >\n <span :class=\"ratingSymbolClass(resolvedSize)\">\n <slot name=\"symbol\" :index=\"index\" :filled=\"true\">\n <span v-if=\"icon\" class=\"block h-full w-full\" :class=\"icon\" />\n <svg v-else viewBox=\"0 0 24 24\" fill=\"currentColor\" class=\"block h-full w-full\">\n <path d=\"M12 2.5l2.9 6.1 6.6.9-4.8 4.7 1.2 6.6L12 17.7 6.1 20.8l1.2-6.6L2.5 9.5l6.6-.9z\" />\n </svg>\n </slot>\n </span>\n </span>\n </span>\n </div>\n\n <span\n v-if=\"showText || $slots.text\"\n data-gr-rating-text\n class=\"text-[var(--gr-muted-fg)] [font-variant-numeric:tabular-nums]\"\n :class=\"ratingTextSizeBySize[resolvedSize]\"\n >\n <slot name=\"text\" :value=\"displayValue\">{{ text }}</slot>\n </span>\n </div>\n</template>\n"],"mappings":"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"}
|