@feugene/granularity 0.16.0 → 0.19.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/chevron-left-D8AUBAM7.js +23 -0
- package/dist/chunks/chevron-left-D8AUBAM7.js.map +1 -0
- package/dist/chunks/{chevron-right-BfqMVpbB.js → chevron-right-C6Z01h0X.js} +1 -22
- package/dist/chunks/chevron-right-C6Z01h0X.js.map +1 -0
- package/dist/chunks/config-8Pcutu1j2.js.map +1 -0
- package/dist/chunks/{config-K0C2tcLB.js → config-9PYRZlKE.js} +1 -1
- package/dist/chunks/{config-K0C2tcLB.js.map → config-9PYRZlKE.js.map} +1 -1
- 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-BJ7Bv_G2.js +148 -0
- package/dist/chunks/config-BJ7Bv_G2.js.map +1 -0
- package/dist/chunks/config-BKvX0q102.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-D8tSR2uS.js → config-Be9k9bOf.js} +2 -8
- package/dist/chunks/config-Be9k9bOf.js.map +1 -0
- 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-DpqOnJiz2.js → config-CDat1wXf2.js} +1 -1
- package/dist/chunks/config-CDat1wXf2.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-CY0_6cMi2.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-DAX8Deh1.js +68 -0
- package/dist/chunks/config-DAX8Deh1.js.map +1 -0
- package/dist/chunks/config-DKdlqXiY.js.map +1 -1
- 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-Rw9OyW702.js.map +1 -0
- 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/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/generated-BRkbhyxk.js +3270 -0
- package/dist/chunks/generated-BRkbhyxk.js.map +1 -0
- 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-C6uAujdJ.js} +23 -21
- package/dist/chunks/granular-provider-C6uAujdJ.js.map +1 -0
- package/dist/chunks/grip-vertical-lRha2S7q.js +16 -0
- package/dist/chunks/grip-vertical-lRha2S7q.js.map +1 -0
- package/dist/chunks/icon-Dag2tIMs.js +21 -0
- package/dist/chunks/icon-Dag2tIMs.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/{portalRoot-CWD4zHBm.js → portalRoot-CVhCgdG6.js} +10 -1
- package/dist/chunks/{portalRoot-CWD4zHBm.js.map → portalRoot-CVhCgdG6.js.map} +1 -1
- 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-DrQVYwwA.js} +2 -2
- package/dist/chunks/{useModalOverlay-DCM4d3yA.js.map → useModalOverlay-DrQVYwwA.js.map} +1 -1
- package/dist/chunks/{usePortalTarget-RyzEgNTb.js → usePortalTarget-Cn-Wiz5d.js} +1 -1
- package/dist/chunks/{usePortalTarget-RyzEgNTb.js.map → usePortalTarget-Cn-Wiz5d.js.map} +1 -1
- package/dist/components/GrAutocomplete/chunks/{GrAutocomplete-CQ9T7Lly.js → GrAutocomplete-gY4Xopza.js} +35 -26
- package/dist/components/GrAutocomplete/chunks/GrAutocomplete-gY4Xopza.js.map +1 -0
- package/dist/components/GrAutocomplete/index.js +1 -1
- package/dist/components/GrAutocomplete/styles.css +1 -1
- package/dist/components/GrBreadcrumbs/chunks/{GrBreadcrumbs-BCNGDS6k.js → GrBreadcrumbs-DS_-PBVt.js} +8 -7
- package/dist/components/GrBreadcrumbs/chunks/GrBreadcrumbs-DS_-PBVt.js.map +1 -0
- package/dist/components/GrBreadcrumbs/index.js +1 -1
- package/dist/components/GrCollapse/chunks/{GrCollapse-B2Fc6A9r.js.map → GrCollapse-Kk0DE8tb.js.map} +1 -1
- package/dist/components/GrCollapse/index.js +1 -1
- package/dist/components/GrColorPicker/chunks/{GrColorPicker-bnYBRGk8.js → GrColorPicker-CkBTzkqn.js} +4 -4
- package/dist/components/GrColorPicker/chunks/{GrColorPicker-bnYBRGk8.js.map → GrColorPicker-CkBTzkqn.js.map} +1 -1
- package/dist/components/GrColorPicker/index.js +2 -2
- package/dist/components/GrCommandPalette/chunks/{GrCommandPalette-MWDYDPNC.js → GrCommandPalette-BugTlGdj.js} +41 -16
- package/dist/components/GrCommandPalette/chunks/GrCommandPalette-BugTlGdj.js.map +1 -0
- package/dist/components/GrCommandPalette/index.js +2 -2
- 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-Ds5T0GiR.js} +1 -1
- package/dist/components/GrConfirmDialog/chunks/{GrConfirmDialog-DaQmlp1_.js.map → GrConfirmDialog-Ds5T0GiR.js.map} +1 -1
- package/dist/components/GrConfirmDialog/index.js +1 -1
- package/dist/components/GrDataTable/chunks/{GrDataTable-DVFho9Cj.js → GrDataTable-CYe2A2WA.js} +498 -48
- package/dist/components/GrDataTable/chunks/GrDataTable-CYe2A2WA.js.map +1 -0
- package/dist/components/GrDataTable/index.js +2 -2
- package/dist/components/GrDialog/chunks/{GrDialog-AE1gIZuz.js → GrDialog-C3tYaA8v.js} +1 -1
- package/dist/components/GrDialog/chunks/{GrDialog-AE1gIZuz.js.map → GrDialog-C3tYaA8v.js.map} +1 -1
- package/dist/components/GrDialog/index.js +1 -1
- package/dist/components/GrDialogService/chunks/{GrDialogService-B4DYWDkA.js → GrDialogService-CTBiiJDI.js} +3 -3
- package/dist/components/GrDialogService/chunks/{GrDialogService-B4DYWDkA.js.map → GrDialogService-CTBiiJDI.js.map} +1 -1
- package/dist/components/GrDialogService/index.js +1 -1
- package/dist/components/GrDivider/chunks/{GrDivider-B2Xwc_QO.js → GrDivider-xxq1_fIl.js} +1 -1
- package/dist/components/GrDivider/chunks/{GrDivider-B2Xwc_QO.js.map → GrDivider-xxq1_fIl.js.map} +1 -1
- package/dist/components/GrDivider/index.js +2 -2
- package/dist/components/GrDrawer/chunks/{GrDrawer-DQwi6aRw.js → GrDrawer-BSSuiNHU.js} +1 -1
- package/dist/components/GrDrawer/chunks/{GrDrawer-DQwi6aRw.js.map → GrDrawer-BSSuiNHU.js.map} +1 -1
- package/dist/components/GrDrawer/index.js +1 -1
- package/dist/components/GrDropdown/chunks/{GrDropdown-BLng6V1m.js → GrDropdown-Bh6UK2M5.js} +3 -3
- package/dist/components/GrDropdown/chunks/{GrDropdown-BLng6V1m.js.map → GrDropdown-Bh6UK2M5.js.map} +1 -1
- package/dist/components/GrDropdown/index.js +1 -1
- package/dist/components/GrDropdownMenu/chunks/{GrDropdownMenu-BImk2Vs7.js → GrDropdownMenu-LyfEFUbg.js} +2 -2
- package/dist/components/GrDropdownMenu/chunks/{GrDropdownMenu-BImk2Vs7.js.map → GrDropdownMenu-LyfEFUbg.js.map} +1 -1
- package/dist/components/GrDropdownMenu/index.js +2 -2
- 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-DfD4euH5.js} +48 -23
- package/dist/components/GrImageViewer/chunks/GrImageViewer-DfD4euH5.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-CbMMr6W1.js} +1 -1
- package/dist/components/GrModal/chunks/{GrModal-B8I78T0N.js.map → GrModal-CbMMr6W1.js.map} +1 -1
- package/dist/components/GrModal/index.js +1 -1
- package/dist/components/GrNumberInput/chunks/{GrNumberInput-CZOyPz4J.js → GrNumberInput-B5CHZDBj.js} +2 -1
- package/dist/components/GrNumberInput/chunks/{GrNumberInput-CZOyPz4J.js.map → GrNumberInput-B5CHZDBj.js.map} +1 -1
- package/dist/components/GrNumberInput/index.js +1 -1
- package/dist/components/GrPagination/chunks/{GrPagination-B6N0QZBf.js → GrPagination-BM4FrD6Q.js} +4 -4
- package/dist/components/GrPagination/chunks/GrPagination-BM4FrD6Q.js.map +1 -0
- package/dist/components/GrPagination/index.js +1 -1
- package/dist/components/GrPopover/chunks/{GrPopover-B1eRCOoO.js → GrPopover-DMH4Jjst.js} +1 -1
- package/dist/components/GrPopover/chunks/{GrPopover-B1eRCOoO.js.map → GrPopover-DMH4Jjst.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-Pwr9NPxK.js} +3 -3
- package/dist/components/GrPromptDialog/chunks/{GrPromptDialog-Ct174NVD.js.map → GrPromptDialog-Pwr9NPxK.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-DVFH3lYu.js} +10 -9
- package/dist/components/GrRating/chunks/GrRating-DVFH3lYu.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-B-Voni7D.js} +69 -55
- package/dist/components/GrSelect/chunks/GrSelect-B-Voni7D.js.map +1 -0
- package/dist/components/GrSelect/index.js +1 -1
- package/dist/components/GrSelect/styles.css +1 -1
- package/dist/components/GrSidebar/chunks/{GrSidebar-DRGnnViw.js → GrSidebar-i7AxjhY_.js} +3 -2
- package/dist/components/GrSidebar/chunks/{GrSidebar-DRGnnViw.js.map → GrSidebar-i7AxjhY_.js.map} +1 -1
- package/dist/components/GrSidebar/index.js +2 -2
- package/dist/components/GrSkeleton/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-XB7mXM1W.js +277 -0
- package/dist/components/GrSortableList/chunks/GrSortableList-XB7mXM1W.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-DOsM6s5V.js} +54 -7
- package/dist/components/GrStatistic/chunks/GrStatistic-DOsM6s5V.js.map +1 -0
- 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-f3-oQenu.js} +55 -65
- package/dist/components/GrTabs/chunks/GrTabs-f3-oQenu.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-Dqja0Lf9.js} +114 -13
- package/dist/components/GrToaster/chunks/GrToaster-Dqja0Lf9.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-Cn08heWz.js} +2 -2
- package/dist/components/GrTooltip/chunks/{GrTooltip-DEbejfhA.js.map → GrTooltip-Cn08heWz.js.map} +1 -1
- package/dist/components/GrTooltip/index.js +2 -2
- package/dist/components/GrTree/chunks/{GrTree-wfdbvygw.js → GrTree-BR9a2pay.js} +148 -111
- package/dist/components/GrTree/chunks/GrTree-BR9a2pay.js.map +1 -0
- package/dist/components/GrTree/index.js +2 -2
- package/dist/components/GrTree/styles.css +1 -1
- package/dist/components/GrTreeSelect/chunks/{GrTreeSelect-lLWmAMtI.js → GrTreeSelect--muUeB0v.js} +28 -14
- package/dist/components/GrTreeSelect/chunks/GrTreeSelect--muUeB0v.js.map +1 -0
- package/dist/components/GrTreeSelect/index.js +1 -1
- package/dist/composables/useAnnouncer.js +1 -1
- 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/usePortalTarget.js +1 -1
- 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/directives/loading.js +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 +77 -68
- package/dist/styles/themes/dark.css +6 -1
- package/dist/testing.js +199 -0
- package/dist/testing.js.map +1 -0
- package/dist/theme-apply.js +33 -0
- package/dist/theme-apply.js.map +1 -0
- package/dist/theme.js +501 -0
- package/dist/theme.js.map +1 -0
- package/dist/tokens.js +1 -3131
- 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/GrBreadcrumbs/grBreadcrumbsStyles.d.ts +5 -2
- 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/GrCommandPalette.vue.d.ts +6 -6
- package/dist/types/src/components/GrCommandPalette/defaults.d.ts +1 -1
- package/dist/types/src/components/GrCommandPalette/filtering.d.ts +6 -2
- 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 +38 -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/GrRating.vue.d.ts +9 -4
- 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 +37 -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/GrStatistic.vue.d.ts +12 -9
- package/dist/types/src/components/GrStatistic/defaults.d.ts +1 -1
- package/dist/types/src/components/GrStatistic/grStatisticStyles.d.ts +1 -2
- 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/GrTabs.vue.d.ts +6 -2
- 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/GrTree/grTreeProps.d.ts +11 -4
- package/dist/types/src/components/GrTree/safelist.d.ts +1 -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/components/shared/icon.d.ts +13 -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/types/src/testing/announcer.d.ts +10 -0
- package/dist/types/src/testing/context.d.ts +36 -0
- package/dist/types/src/testing/env.d.ts +25 -0
- package/dist/types/src/testing/geometry.d.ts +23 -0
- package/dist/types/src/testing/index.d.ts +12 -0
- package/dist/types/src/testing/pointer.d.ts +31 -0
- package/dist/types/src/theme/apply.d.ts +27 -0
- package/dist/types/src/theme/color.d.ts +35 -0
- package/dist/types/src/theme/extendTheme.d.ts +63 -0
- package/dist/types/src/theme/index.d.ts +22 -0
- package/dist/types/src/theme/roles.d.ts +16 -0
- package/dist/types/src/theme/tone.d.ts +32 -0
- package/dist/types/src/theme/validate.d.ts +16 -0
- package/dist/types/src/tokens/types.d.ts +8 -0
- package/dist/web-types.json +214 -13
- package/package.json +47 -4
- package/tokens/themes/dark.json +2 -1
- package/dist/chunks/chevron-right-BfqMVpbB.js.map +0 -1
- package/dist/chunks/config-8Pcutu1j.js.map +0 -1
- package/dist/chunks/config-B-g6lT19.js.map +0 -1
- package/dist/chunks/config-BE6n20np.js.map +0 -1
- package/dist/chunks/config-BKvX0q10.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-CY0_6cMi.js.map +0 -1
- package/dist/chunks/config-Cw6TDzWv2.js.map +0 -1
- package/dist/chunks/config-D8tSR2uS.js.map +0 -1
- package/dist/chunks/config-DpqOnJiz2.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-Rw9OyW70.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/GrBreadcrumbs/chunks/GrBreadcrumbs-BCNGDS6k.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/GrStatistic/chunks/GrStatistic-CttyoYPT.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/components/GrTreeSelect/chunks/GrTreeSelect-lLWmAMtI.js.map +0 -1
- package/dist/tokens.js.map +0 -1
- package/dist/chunks/{config-8Pcutu1j.js → config-8Pcutu1j2.js} +0 -0
- package/dist/chunks/{config-BE6n20np.js → config-BE6n20np2.js} +0 -0
- package/dist/chunks/{config-BKvX0q10.js → config-BKvX0q102.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-CY0_6cMi.js → config-CY0_6cMi2.js} +0 -0
- package/dist/chunks/{config-Rw9OyW70.js → config-Rw9OyW702.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/components/GrCollapse/chunks/{GrCollapse-B2Fc6A9r.js → GrCollapse-Kk0DE8tb.js} +1 -1
- /package/dist/types/src/composables/{internal/useComboboxNavigation.d.ts → useComboboxNavigation.d.ts} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"GrCommandPalette-MWDYDPNC.js","names":["$emit","$slots"],"sources":["../../../../src/components/GrCommandPalette/filtering.ts","../../../../src/components/GrCommandPalette/GrCommandPalette.vue","../../../../src/components/GrCommandPalette/GrCommandPalette.vue"],"sourcesContent":["import { matchesQueryParts, normalizeOptionQuery } from '../shared/optionFilter'\n\n/** Команда палитры. `id` обязателен — он же ключ рендера и цель `aria-activedescendant`. */\nexport type GrCommandItem = {\n id: string\n label: string\n /** Пояснение под меткой. */\n description?: string\n /** UnoCSS-класс иконки (например `i-lucide-file-plus`). */\n icon?: string\n /** Подсказка сочетания клавиш — массив клавиш, каждая рисуется отдельным `GrKbd`. */\n shortcut?: string[]\n /** Заголовок группы, под которой команда показывается в списке. */\n group?: string\n /** Дополнительные слова для поиска (синонимы, транслитерация). */\n keywords?: string[]\n disabled?: boolean\n}\n\n/** Команды, сгруппированные для рендера: порядок групп — порядок первого вхождения. */\nexport type GrCommandGroup = {\n /** `undefined` — команды без группы (рендерятся без заголовка). */\n name: string | undefined\n items: GrCommandItem[]\n}\n\nexport type GrCommandFilter = (item: GrCommandItem, query: string) => boolean\n\n/**\n * Матчер по умолчанию: подстрока (без учёта регистра) в метке, описании,\n * названии группы или ключевых словах.\n *\n * Набор полей у команды шире, чем у опции списка, а сам подстрочный матч — тот\n * же самый на весь пакет (`shared/optionFilter.ts`).\n */\nexport function matchCommandItem(item: GrCommandItem, query: string): boolean {\n const parts = [item.label, item.description, item.group, ...(item.keywords ?? [])]\n\n return matchesQueryParts(parts, normalizeOptionQuery(query))\n}\n\nexport function filterCommandItems(\n items: GrCommandItem[],\n query: string,\n filter: GrCommandFilter = matchCommandItem,\n): GrCommandItem[] {\n if (!query.trim()) return items\n return items.filter(item => filter(item, query))\n}\n\n/**\n * Раскладывает плоский список по группам, сохраняя порядок первого появления\n * группы. Команды без `group` собираются в единственную безымянную группу,\n * которая остаётся на своём месте в общем порядке.\n */\nexport function groupCommandItems(items: GrCommandItem[]): GrCommandGroup[] {\n const groups: GrCommandGroup[] = []\n const byName = new Map<string | undefined, GrCommandGroup>()\n\n for (const item of items) {\n const name = item.group || undefined\n let group = byName.get(name)\n if (!group) {\n group = { name, items: [] }\n byName.set(name, group)\n groups.push(group)\n }\n group.items.push(item)\n }\n\n return groups\n}\n\n/** Кусок текста для подсветки: `match` — попал в запрос. */\nexport type GrCommandMatchSegment = {\n text: string\n match: boolean\n}\n\n/**\n * Режет текст на сегменты по вхождению запроса (без учёта регистра).\n *\n * Совпадения нет — один сегмент целиком: свой `filter` вправе матчить по\n * `keywords`, и тогда в метке подсвечивать нечего. Регистр исходного текста\n * сохраняется — подсветка не должна менять то, что читает пользователь.\n */\nexport function splitCommandMatch(text: string, query: string): GrCommandMatchSegment[] {\n const needle = query.trim().toLowerCase()\n if (!needle || !text) return [{ text, match: false }]\n\n const segments: GrCommandMatchSegment[] = []\n const haystack = text.toLowerCase()\n let from = 0\n\n while (from < text.length) {\n const at = haystack.indexOf(needle, from)\n if (at < 0) break\n\n if (at > from) segments.push({ text: text.slice(from, at), match: false })\n segments.push({ text: text.slice(at, at + needle.length), match: true })\n from = at + needle.length\n }\n\n if (!segments.length) return [{ text, match: false }]\n if (from < text.length) segments.push({ text: text.slice(from), match: false })\n\n return segments\n}\n\n/**\n * Поднимает «недавние» команды в отдельную группу наверх, сохраняя порядок\n * `recentIds`, и убирает их из остального списка — иначе одна и та же команда\n * оказалась бы в списке дважды, а `id` у неё один.\n */\nexport function withRecentCommands(\n groups: GrCommandGroup[],\n items: GrCommandItem[],\n recentIds: string[],\n title: string,\n): GrCommandGroup[] {\n if (!recentIds.length) return groups\n\n const byId = new Map(items.map(item => [item.id, item]))\n const recent: GrCommandItem[] = []\n const taken = new Set<string>()\n\n for (const id of recentIds) {\n const item = byId.get(id)\n if (!item || taken.has(id)) continue\n recent.push(item)\n taken.add(id)\n }\n\n if (!recent.length) return groups\n\n const rest = groups\n .map(group => ({ ...group, items: group.items.filter(item => !taken.has(item.id)) }))\n .filter(group => group.items.length > 0)\n\n return [{ name: title, items: recent }, ...rest]\n}\n\n/** Идентификаторы, встретившиеся больше одного раза. */\nexport function findDuplicateCommandIds(items: GrCommandItem[]): string[] {\n const seen = new Set<string>()\n const duplicates = new Set<string>()\n\n for (const item of items) {\n if (seen.has(item.id)) duplicates.add(item.id)\n else seen.add(item.id)\n }\n\n return [...duplicates]\n}\n","<script setup lang=\"ts\">\nimport { useGrComponentProp } from '../GrConfigProvider/context'\nimport { computed, nextTick, onBeforeUnmount, onMounted, ref, useId, watch } from 'vue'\n\nimport { useVirtualList } from '../../composables/useVirtualList'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { isComposingEvent } from '../../internal/keyboard'\nimport { useComboboxNavigation } from '../../composables/internal/useComboboxNavigation'\nimport GrKbd from '../GrKbd/GrKbd.vue'\nimport GrModal from '../GrModal/GrModal.vue'\n\nimport {\n filterCommandItems,\n findDuplicateCommandIds,\n groupCommandItems,\n matchCommandItem,\n splitCommandMatch,\n withRecentCommands,\n type GrCommandFilter,\n type GrCommandItem,\n} from './filtering'\nimport {\n formatCommandHotkey,\n isAppleDevice,\n matchesCommandHotkey,\n parseCommandHotkey,\n} from '../shared/hotkey'\nimport {\n commandEmptyClass,\n commandFooterClass,\n commandGroupLabelClass,\n commandItemClass,\n commandItemDescriptionClass,\n commandItemMutedClass,\n commandMatchClass,\n commandPaletteModalSizeBySize,\n commandSearchInputClass,\n commandSearchRowClass,\n type GrCommandPaletteSize,\n} from './grCommandPaletteStyles'\n\nexport type { GrCommandFilter, GrCommandGroup, GrCommandItem } from './filtering'\nexport type { GrCommandPaletteSize } from './grCommandPaletteStyles'\n\n/**\n * Публичный GR-примитив «Command palette» (⌘K) — модальный поиск по командам\n * приложения с группировкой, подсказками сочетаний и клавиатурной навигацией.\n *\n * A11y: WAI-ARIA combobox поверх модалки — `role=\"combobox\"` на поле ввода,\n * `role=\"listbox\"` на списке, `aria-activedescendant` на активной команде\n * (фокус остаётся в поле). Esc, фокус-ловушка и блокировка скролла приходят\n * из `GrModal`.\n *\n * Открытие по глобальному сочетанию (`hotkey`, по умолчанию `mod+k` — Cmd на\n * macOS, Ctrl на прочих) компонент берёт на себя; передайте `hotkey: null`,\n * чтобы управлять открытием только через `v-model`.\n */\nexport interface GrCommandPaletteProps {\n /** Открыта ли палитра. */\n modelValue: boolean\n /** Плоский список команд; группировка — по полю `group` самой команды. */\n items?: GrCommandItem[]\n /**\n * Недавние команды: пока запрос пуст, они поднимаются отдельной группой\n * наверх в этом порядке и не дублируются ниже.\n */\n recentIds?: string[]\n placeholder?: string\n size?: GrCommandPaletteSize\n /** Глобальное сочетание открытия. `null` — не вешать слушатель. */\n hotkey?: string | null\n /** Локальная фильтрация по запросу. `false` — фильтрует владелец по событию `search`. */\n filterable?: boolean\n /** Кастомный матчер локальной фильтрации. */\n filter?: GrCommandFilter\n /** Внешне управляемое состояние загрузки (для remote-поиска). */\n loading?: boolean\n /** Закрывать палитру после выбора команды. */\n closeOnSelect?: boolean\n /** Максимальная высота списка, px. */\n maxHeight?: number\n /**\n * Виртуализация списка: в DOM живёт только окно вокруг вьюпорта. Высоту окна\n * задаёт `maxHeight`.\n *\n * Включается осознанно: на сотне команд выигрыша нет, а в разметке остаётся\n * только окно — вместе с ним меняется и то, что находит `querySelector`\n * потребителя. Профильный сценарий — палитра приложения на тысячи команд.\n */\n virtual?: boolean\n /** Показывать подсказку сочетания в поле ввода. */\n showHotkeyHint?: boolean\n emptyText?: string\n ariaLabel?: string\n}\n\nexport interface GrCommandPaletteEmits {\n (e: 'update:modelValue', value: boolean): void\n /** Команда выбрана (клик или Enter). */\n (e: 'select', item: GrCommandItem): void\n /** Поисковый запрос изменился — точка входа для remote-поиска. */\n (e: 'search', query: string): void\n}\n\nimport './defaults'\n\nconst props = withDefaults(\n defineProps<GrCommandPaletteProps>(),\n {\n items: undefined,\n recentIds: undefined,\n placeholder: undefined,\n // Дефолт `size` живёт в резолвере ниже, а не здесь: Vue подставил бы его\n // до того, как компонент заглянет в `GrConfigProvider`.\n size: undefined,\n hotkey: 'mod+k',\n filterable: true,\n filter: undefined,\n loading: false,\n closeOnSelect: true,\n maxHeight: 360,\n virtual: false,\n showHotkeyHint: true,\n emptyText: undefined,\n ariaLabel: undefined,\n },\n)\n\nconst emit = defineEmits<GrCommandPaletteEmits>()\n\nconst { t } = useGranularityTranslations()\n\nconst resolvedPlaceholder = computed(() => props.placeholder ?? t('gr.commandPalette.placeholder', 'Type a command or search…'))\nconst resolvedEmptyText = computed(() => props.emptyText ?? t('gr.commandPalette.empty', 'Nothing found'))\nconst resolvedAriaLabel = computed(() => props.ariaLabel ?? t('gr.commandPalette.label', 'Command palette'))\nconst loadingText = computed(() => t('gr.commandPalette.loading', 'Loading…'))\nconst recentTitle = computed(() => t('gr.commandPalette.recent', 'Recent'))\n\nconst query = ref('')\nconst inputEl = ref<HTMLInputElement | null>(null)\nconst listboxId = useId()\n\nconst itemsResolved = computed<GrCommandItem[]>(() => props.items ?? [])\n\nconst filteredItems = computed<GrCommandItem[]>(() =>\n props.filterable\n ? filterCommandItems(itemsResolved.value, query.value, props.filter ?? matchCommandItem)\n : itemsResolved.value,\n)\n\nconst groups = computed(() => {\n const grouped = groupCommandItems(filteredItems.value)\n\n // С непустым запросом секции «недавние» нет: там правит релевантность, а не\n // история — иначе первая же буква уводила бы взгляд не туда.\n if (query.value.trim()) return grouped\n\n return withRecentCommands(grouped, filteredItems.value, props.recentIds ?? [], recentTitle.value)\n})\n\n/**\n * Порядок обхода стрелками совпадает с порядком на экране: «недавние» стоят\n * первыми, значит и первая стрелка ведёт туда же.\n */\nconst orderedItems = computed(() => groups.value.flatMap(group => group.items))\n\n/** Плоский список того, что реально выбирается — по нему ходят стрелки. */\nconst navigableItems = computed(() => orderedItems.value.filter(item => !item.disabled))\n\nfunction itemDomId(id: string): string {\n return `${listboxId}-item-${id}`\n}\n\n/**\n * Виртуализация списка.\n *\n * Набор — плоский поток строк экрана: заголовок группы занимает такую же\n * строку, как команда, поэтому входит в него наравне. Вложенную структуру\n * рендер пересобирает уже из окна.\n */\n\n/** Оценки высоты строк. Команда крупнее заголовка; дальше уточняются замером. */\nconst ITEM_SIZE_ESTIMATE = 44\nconst GROUP_LABEL_SIZE_ESTIMATE = 28\n\ntype PaletteRow =\n | { kind: 'label', groupIndex: number }\n | { kind: 'item', groupIndex: number, item: GrCommandItem, posInSet: number, setSize: number }\n\nconst listEl = ref<HTMLElement | null>(null)\n\nconst paletteRows = computed<PaletteRow[]>(() => {\n const rows: PaletteRow[] = []\n\n groups.value.forEach((group, groupIndex) => {\n if (group.name) rows.push({ kind: 'label', groupIndex })\n\n group.items.forEach((item, index) => {\n rows.push({\n kind: 'item',\n groupIndex,\n item,\n // Набор — группа, а не весь список: по ARIA позиция отсчитывается от неё.\n posInSet: index + 1,\n setSize: group.items.length,\n })\n })\n })\n\n return rows\n})\n\nconst virtualizer = useVirtualList({\n container: listEl,\n count: () => (props.virtual ? paletteRows.value.length : 0),\n // Запрос пересобирает набор строк — замеры прошлого набора невалидны.\n source: () => paletteRows.value,\n itemSize: index => (paletteRows.value[index]?.kind === 'label' ? GROUP_LABEL_SIZE_ESTIMATE : ITEM_SIZE_ESTIMATE),\n // Список закрытой палитры не смонтирован вовсе: `clientHeight` взять неоткуда.\n // `maxHeight` можно перебить `--gr-command-list-max-height` — тогда точная\n // высота приедет с первым замером, а это останется оценкой первого рендера.\n viewportSize: () => props.maxHeight,\n})\n\n/**\n * Группы к отрисовке. При виртуализации пересобираются из среза `paletteRows`:\n * группа, начатая выше окна, всё равно открывается — иначе её команды остались\n * бы прямыми потомками listbox'а, что роль запрещает.\n */\ntype RenderedGroup = {\n key: string\n groupIndex: number\n name: string | undefined\n labelVisible: boolean\n /** Позиция строки заголовка в `paletteRows` — по ней его замеряет виртуализатор. */\n labelRowIndex?: number\n items: { item: GrCommandItem, rowIndex: number, posInSet: number, setSize: number }[]\n}\n\nconst renderedGroups = computed<RenderedGroup[]>(() => {\n const rows = paletteRows.value\n\n const from = props.virtual ? virtualizer.range.value.start : 0\n const to = props.virtual ? Math.min(rows.length, virtualizer.range.value.end) : rows.length\n\n const result: RenderedGroup[] = []\n let current: RenderedGroup | undefined\n\n for (let rowIndex = from; rowIndex < to; rowIndex++) {\n const row = rows[rowIndex]!\n const group = groups.value[row.groupIndex]\n\n if (!current || current.groupIndex !== row.groupIndex) {\n current = {\n key: group?.name ?? `__ungrouped-${row.groupIndex}`,\n groupIndex: row.groupIndex,\n name: group?.name,\n labelVisible: false,\n items: [],\n }\n result.push(current)\n }\n\n if (row.kind === 'label') {\n current.labelVisible = true\n current.labelRowIndex = rowIndex\n continue\n }\n\n current.items.push({ item: row.item, rowIndex, posInSet: row.posInSet, setSize: row.setSize })\n }\n\n return result\n})\n\n/** ARIA набора: объявляем только при виртуализации — иначе набор виден по DOM. */\nfunction itemSetProps(entry: { posInSet: number, setSize: number }): Record<string, number> | undefined {\n if (!props.virtual) return undefined\n return { 'aria-setsize': entry.setSize, 'aria-posinset': entry.posInSet }\n}\n\n\n/** Сегменты подсветки: совпавшее с запросом рисуется `<mark>`. */\nfunction matchSegments(text: string): ReturnType<typeof splitCommandMatch> {\n return splitCommandMatch(text, props.filterable ? query.value : '')\n}\n\nasync function scrollActiveIntoView(item: GrCommandItem): Promise<void> {\n // Вне окна активной команды в DOM нет: `getElementById` вернул бы `null`,\n // прокрутка не случилась бы, а `aria-activedescendant` указал бы в пустоту.\n if (props.virtual) {\n const rowIndex = paletteRows.value.findIndex(row => row.kind === 'item' && row.item.id === item.id)\n if (rowIndex >= 0) virtualizer.scrollToIndex(rowIndex)\n }\n\n await nextTick()\n document.getElementById(itemDomId(item.id))?.scrollIntoView?.({ block: 'nearest' })\n}\n\nconst {\n activeItem,\n activeDescendantId,\n setActive,\n reset: resetActive,\n handleNavigationKeys,\n} = useComboboxNavigation<GrCommandItem>({\n items: () => navigableItems.value,\n open: () => props.modelValue,\n idOf: item => itemDomId(item.id),\n scrollTo: item => scrollActiveIntoView(item),\n})\n\n// Палитра всегда стартует с первой команды — и при монтировании открытой тоже.\nresetActive(0)\n\nfunction isActive(item: GrCommandItem): boolean {\n return activeItem.value?.id === item.id\n}\n\n// Активной становится первая команда, когда список изменился **по содержимому**.\n// Следить за идентичностью массива нельзя: родитель, отдающий `:items` инлайн-\n// выражением, пересоздаёт его на каждый посторонний ререндер — и выбранная\n// стрелками команда прыгала бы в начало.\n// Разделитель — пробел, а не запятая: `id` вправе её содержать, и «a,b» + «c»\n// дало бы тот же ключ, что «a» + «b,c».\nwatch(\n () => orderedItems.value.map(item => item.id).join(' '),\n () => resetActive(0),\n)\n\n// `id` — ключ рендера и цель `aria-activedescendant`: дубли дают одинаковые\n// DOM-id, и фокус уезжает не на ту команду.\nwatch(\n itemsResolved,\n (items) => {\n if (process.env.NODE_ENV === 'production') return\n const duplicates = findDuplicateCommandIds(items)\n if (!duplicates.length) return\n\n console.warn(\n `[GrCommandPalette] повторяющиеся id команд: ${duplicates.join(', ')}. `\n + 'Из-за одинаковых DOM-id `aria-activedescendant` укажет не на ту команду.',\n )\n },\n { immediate: true },\n)\n\nfunction open(): void {\n emit('update:modelValue', true)\n}\n\nfunction close(): void {\n emit('update:modelValue', false)\n}\n\nfunction toggle(): void {\n emit('update:modelValue', !props.modelValue)\n}\n\nfunction selectItem(item: GrCommandItem): void {\n if (item.disabled) return\n emit('select', item)\n if (props.closeOnSelect) close()\n}\n\nfunction onItemHover(item: GrCommandItem): void {\n const index = navigableItems.value.indexOf(item)\n if (index >= 0) setActive(index)\n}\n\nfunction onInput(event: Event): void {\n query.value = (event.target as HTMLInputElement).value\n emit('search', query.value.trim())\n}\n\nfunction onKeydown(event: KeyboardEvent): void {\n // Клавиша во время IME-композиции принадлежит композиции: Enter коммитит её,\n // стрелки ходят по кандидатам.\n if (isComposingEvent(event)) return\n\n if (handleNavigationKeys(event)) return\n\n switch (event.key) {\n case 'Enter':\n if (!activeItem.value) break\n event.preventDefault()\n selectItem(activeItem.value)\n break\n }\n}\n\n// ————— Открытие: сброс запроса и выбора.\nwatch(\n () => props.modelValue,\n (open) => {\n if (!open) return\n\n query.value = ''\n resetActive(0)\n },\n)\n\n// Фокус в поле — отдельным наблюдателем, и по появлению самого поля: содержимое\n// панели рождается вместе с ней, и по одному лишь пропу фокусировать нечего.\n// Ловушка фокуса `GrModal` этот выбор не перебивает — фокус, уже стоящий внутри\n// слоя, она считает осознанным.\nwatch(\n [() => props.modelValue, inputEl],\n ([open]) => {\n if (open) inputEl.value?.focus()\n },\n { flush: 'post' },\n)\n\n// ————— Глобальное сочетание открытия.\nconst parsedHotkey = computed(() => (props.hotkey ? parseCommandHotkey(props.hotkey) : null))\n\n// Платформа определяется только после монтирования: на сервере `navigator` нет,\n// и первый рендер обязан совпасть с серверным. Иначе сервер отдаёт `Ctrl`,\n// клиент на macOS рисует `⌘`, и гидрация расходится по тексту подсказки.\nconst isApple = ref(false)\n\nconst hotkeyHint = computed(() =>\n props.showHotkeyHint && parsedHotkey.value ? formatCommandHotkey(parsedHotkey.value, isApple.value) : [],\n)\n\nfunction onWindowKeydown(event: KeyboardEvent): void {\n const hotkey = parsedHotkey.value\n if (!hotkey || !matchesCommandHotkey(event, hotkey, isApple.value)) return\n event.preventDefault()\n toggle()\n}\n\nonMounted(() => {\n if (typeof window === 'undefined') return\n isApple.value = isAppleDevice()\n window.addEventListener('keydown', onWindowKeydown)\n})\n\nonBeforeUnmount(() => {\n if (typeof window !== 'undefined') window.removeEventListener('keydown', onWindowKeydown)\n})\n\n// Эффективный размер: локальный проп → `GrConfigProvider` → дефолт компонента.\nconst resolvedSize = useGrComponentProp('GrCommandPalette', 'size', () => props.size, 'lg')\n\nconst modalSize = computed(() => commandPaletteModalSizeBySize[resolvedSize.value])\nconst listStyle = computed(() => ({ maxHeight: `var(--gr-command-list-max-height, ${props.maxHeight}px)` }))\n\nconst listStyleWithSpacers = computed(() => {\n const base = listStyle.value\n if (!props.virtual) return base\n\n return {\n ...base,\n ...virtualizer.spacerStyle.value,\n }\n})\n\ndefineExpose({ open, close, toggle })\n</script>\n\n<template>\n <GrModal\n :model-value=\"modelValue\"\n :size=\"modalSize\"\n @update:model-value=\"$emit('update:modelValue', $event)\"\n >\n <div data-gr-command-palette :aria-label=\"resolvedAriaLabel\">\n <div data-gr-command-palette-search :class=\"commandSearchRowClass\">\n <span class=\"i-lucide-search block h-4 w-4 shrink-0 text-[var(--gr-muted-fg)]\" aria-hidden=\"true\" />\n\n <input\n ref=\"inputEl\"\n data-gr-command-palette-input\n data-testid=\"gr-command-palette-input\"\n type=\"text\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n aria-autocomplete=\"list\"\n aria-expanded=\"true\"\n :aria-label=\"resolvedAriaLabel\"\n :aria-controls=\"listboxId\"\n :aria-activedescendant=\"activeDescendantId\"\n :value=\"query\"\n :placeholder=\"resolvedPlaceholder\"\n :class=\"commandSearchInputClass\"\n @input=\"onInput\"\n @keydown=\"onKeydown\"\n >\n\n <span v-if=\"loading\" class=\"shrink-0 text-[var(--gr-muted-fg)]\" aria-hidden=\"true\">\n <span class=\"i-lucide-loader-2 block h-4 w-4 animate-spin\" />\n </span>\n <span v-else-if=\"hotkeyHint.length\" class=\"shrink-0\" aria-hidden=\"true\">\n <GrKbd :keys=\"hotkeyHint\" separator=\"\" size=\"sm\" />\n </span>\n </div>\n\n <div\n :id=\"listboxId\"\n ref=\"listEl\"\n data-gr-command-palette-list\n data-testid=\"gr-command-palette-list\"\n :data-gr-virtual=\"virtual ? '' : undefined\"\n class=\"overflow-y-auto p-2\"\n :style=\"listStyleWithSpacers\"\n role=\"listbox\"\n :aria-label=\"resolvedAriaLabel\"\n >\n <!-- Прямыми потомками listbox могут быть только `role=\"group\"`: заголовок\n группы лежит внутри неё и объявлен презентационным (имя группе он\n даёт через `aria-labelledby`), иначе ломается `aria-required-children`.\n\n При виртуализации окно может начаться ниже заголовка — тогда его в\n DOM нет, и имя группы идёт напрямую в `aria-label`. -->\n <div\n v-for=\"group in renderedGroups\"\n :key=\"group.key\"\n role=\"group\"\n :aria-labelledby=\"group.name && group.labelVisible ? `${listboxId}-group-${group.groupIndex}` : undefined\"\n :aria-label=\"group.name && !group.labelVisible ? group.name : undefined\"\n >\n <div\n v-if=\"group.name && group.labelVisible\"\n :id=\"`${listboxId}-group-${group.groupIndex}`\"\n :ref=\"(el) => virtual && group.labelRowIndex !== undefined && virtualizer.measure(group.labelRowIndex, el as Element | null)\"\n data-gr-command-palette-group\n role=\"presentation\"\n :class=\"commandGroupLabelClass\"\n >\n {{ group.name }}\n </div>\n\n <div>\n <div\n v-for=\"entry in group.items\"\n :id=\"itemDomId(entry.item.id)\"\n :key=\"entry.item.id\"\n :ref=\"(el) => virtual && virtualizer.measure(entry.rowIndex, el as Element | null)\"\n data-gr-command-palette-item\n :data-testid=\"`gr-command-palette-item-${entry.item.id}`\"\n role=\"option\"\n v-bind=\"itemSetProps(entry)\"\n :aria-selected=\"isActive(entry.item) ? 'true' : 'false'\"\n :aria-disabled=\"entry.item.disabled ? 'true' : undefined\"\n :class=\"commandItemClass({ active: isActive(entry.item), disabled: Boolean(entry.item.disabled) })\"\n @click=\"selectItem(entry.item)\"\n @mousemove=\"onItemHover(entry.item)\"\n >\n <slot name=\"item\" :item=\"entry.item\" :active=\"isActive(entry.item)\">\n <span\n v-if=\"entry.item.icon\"\n class=\"block h-4 w-4 shrink-0\"\n :class=\"[entry.item.icon, commandItemMutedClass(Boolean(entry.item.disabled))]\"\n aria-hidden=\"true\"\n />\n <span class=\"min-w-0 flex-1\">\n <span class=\"block truncate\">\n <template v-for=\"(segment, index) in matchSegments(entry.item.label)\" :key=\"index\">\n <mark v-if=\"segment.match\" :class=\"commandMatchClass\">{{ segment.text }}</mark>\n <template v-else>{{ segment.text }}</template>\n </template>\n </span>\n <span\n v-if=\"entry.item.description\"\n :class=\"[commandItemDescriptionClass, commandItemMutedClass(Boolean(entry.item.disabled))]\"\n >\n <template v-for=\"(segment, index) in matchSegments(entry.item.description)\" :key=\"index\">\n <mark v-if=\"segment.match\" :class=\"commandMatchClass\">{{ segment.text }}</mark>\n <template v-else>{{ segment.text }}</template>\n </template>\n </span>\n </span>\n <span v-if=\"entry.item.shortcut?.length\" class=\"shrink-0\">\n <GrKbd :keys=\"entry.item.shortcut\" separator=\"\" size=\"sm\" />\n </span>\n </slot>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Состояния объявляются живым регионом: `aria-label` на generic-элементе\n большинство AT игнорируют, и загрузка осталась бы неозвученной. Регион\n один на оба состояния и лежит вне listbox — внутри он стал бы его\n недопустимым потомком. -->\n <div\n v-if=\"loading || !filteredItems.length\"\n data-gr-command-palette-empty\n data-testid=\"gr-command-palette-empty\"\n role=\"status\"\n aria-live=\"polite\"\n :class=\"commandEmptyClass\"\n >\n <template v-if=\"loading\">\n {{ loadingText }}\n </template>\n <slot v-else name=\"empty\" :query=\"query\">\n {{ resolvedEmptyText }}\n </slot>\n </div>\n\n <div v-if=\"$slots.footer\" data-gr-command-palette-footer :class=\"commandFooterClass\">\n <slot name=\"footer\" />\n </div>\n </div>\n </GrModal>\n</template>\n\n<style scoped>\n/*\n * Распорки виртуального списка.\n *\n * Не `padding` контейнера: `max-height` меряет ту же коробку. Не обёртка внутри\n * listbox'а: прямыми потомками роли обязаны быть только группы. Не отступы\n * крайних строк: при прыжке прокрутки строки заменяются целиком, и распорка\n * исчезла бы вместе с ними.\n *\n * `display: block` обязателен: список палитры — блочный поток, а псевдоэлемент\n * по умолчанию строчный, и строчная коробка игнорирует `height` — распорка\n * молча не создалась бы вовсе.\n */\n[data-gr-virtual]::before,\n[data-gr-virtual]::after {\n content: '';\n display: block;\n flex: none;\n}\n\n[data-gr-virtual]::before {\n height: var(--gr-virtual-before, 0px);\n}\n\n[data-gr-virtual]::after {\n height: var(--gr-virtual-after, 0px);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { useGrComponentProp } from '../GrConfigProvider/context'\nimport { computed, nextTick, onBeforeUnmount, onMounted, ref, useId, watch } from 'vue'\n\nimport { useVirtualList } from '../../composables/useVirtualList'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { isComposingEvent } from '../../internal/keyboard'\nimport { useComboboxNavigation } from '../../composables/internal/useComboboxNavigation'\nimport GrKbd from '../GrKbd/GrKbd.vue'\nimport GrModal from '../GrModal/GrModal.vue'\n\nimport {\n filterCommandItems,\n findDuplicateCommandIds,\n groupCommandItems,\n matchCommandItem,\n splitCommandMatch,\n withRecentCommands,\n type GrCommandFilter,\n type GrCommandItem,\n} from './filtering'\nimport {\n formatCommandHotkey,\n isAppleDevice,\n matchesCommandHotkey,\n parseCommandHotkey,\n} from '../shared/hotkey'\nimport {\n commandEmptyClass,\n commandFooterClass,\n commandGroupLabelClass,\n commandItemClass,\n commandItemDescriptionClass,\n commandItemMutedClass,\n commandMatchClass,\n commandPaletteModalSizeBySize,\n commandSearchInputClass,\n commandSearchRowClass,\n type GrCommandPaletteSize,\n} from './grCommandPaletteStyles'\n\nexport type { GrCommandFilter, GrCommandGroup, GrCommandItem } from './filtering'\nexport type { GrCommandPaletteSize } from './grCommandPaletteStyles'\n\n/**\n * Публичный GR-примитив «Command palette» (⌘K) — модальный поиск по командам\n * приложения с группировкой, подсказками сочетаний и клавиатурной навигацией.\n *\n * A11y: WAI-ARIA combobox поверх модалки — `role=\"combobox\"` на поле ввода,\n * `role=\"listbox\"` на списке, `aria-activedescendant` на активной команде\n * (фокус остаётся в поле). Esc, фокус-ловушка и блокировка скролла приходят\n * из `GrModal`.\n *\n * Открытие по глобальному сочетанию (`hotkey`, по умолчанию `mod+k` — Cmd на\n * macOS, Ctrl на прочих) компонент берёт на себя; передайте `hotkey: null`,\n * чтобы управлять открытием только через `v-model`.\n */\nexport interface GrCommandPaletteProps {\n /** Открыта ли палитра. */\n modelValue: boolean\n /** Плоский список команд; группировка — по полю `group` самой команды. */\n items?: GrCommandItem[]\n /**\n * Недавние команды: пока запрос пуст, они поднимаются отдельной группой\n * наверх в этом порядке и не дублируются ниже.\n */\n recentIds?: string[]\n placeholder?: string\n size?: GrCommandPaletteSize\n /** Глобальное сочетание открытия. `null` — не вешать слушатель. */\n hotkey?: string | null\n /** Локальная фильтрация по запросу. `false` — фильтрует владелец по событию `search`. */\n filterable?: boolean\n /** Кастомный матчер локальной фильтрации. */\n filter?: GrCommandFilter\n /** Внешне управляемое состояние загрузки (для remote-поиска). */\n loading?: boolean\n /** Закрывать палитру после выбора команды. */\n closeOnSelect?: boolean\n /** Максимальная высота списка, px. */\n maxHeight?: number\n /**\n * Виртуализация списка: в DOM живёт только окно вокруг вьюпорта. Высоту окна\n * задаёт `maxHeight`.\n *\n * Включается осознанно: на сотне команд выигрыша нет, а в разметке остаётся\n * только окно — вместе с ним меняется и то, что находит `querySelector`\n * потребителя. Профильный сценарий — палитра приложения на тысячи команд.\n */\n virtual?: boolean\n /** Показывать подсказку сочетания в поле ввода. */\n showHotkeyHint?: boolean\n emptyText?: string\n ariaLabel?: string\n}\n\nexport interface GrCommandPaletteEmits {\n (e: 'update:modelValue', value: boolean): void\n /** Команда выбрана (клик или Enter). */\n (e: 'select', item: GrCommandItem): void\n /** Поисковый запрос изменился — точка входа для remote-поиска. */\n (e: 'search', query: string): void\n}\n\nimport './defaults'\n\nconst props = withDefaults(\n defineProps<GrCommandPaletteProps>(),\n {\n items: undefined,\n recentIds: undefined,\n placeholder: undefined,\n // Дефолт `size` живёт в резолвере ниже, а не здесь: Vue подставил бы его\n // до того, как компонент заглянет в `GrConfigProvider`.\n size: undefined,\n hotkey: 'mod+k',\n filterable: true,\n filter: undefined,\n loading: false,\n closeOnSelect: true,\n maxHeight: 360,\n virtual: false,\n showHotkeyHint: true,\n emptyText: undefined,\n ariaLabel: undefined,\n },\n)\n\nconst emit = defineEmits<GrCommandPaletteEmits>()\n\nconst { t } = useGranularityTranslations()\n\nconst resolvedPlaceholder = computed(() => props.placeholder ?? t('gr.commandPalette.placeholder', 'Type a command or search…'))\nconst resolvedEmptyText = computed(() => props.emptyText ?? t('gr.commandPalette.empty', 'Nothing found'))\nconst resolvedAriaLabel = computed(() => props.ariaLabel ?? t('gr.commandPalette.label', 'Command palette'))\nconst loadingText = computed(() => t('gr.commandPalette.loading', 'Loading…'))\nconst recentTitle = computed(() => t('gr.commandPalette.recent', 'Recent'))\n\nconst query = ref('')\nconst inputEl = ref<HTMLInputElement | null>(null)\nconst listboxId = useId()\n\nconst itemsResolved = computed<GrCommandItem[]>(() => props.items ?? [])\n\nconst filteredItems = computed<GrCommandItem[]>(() =>\n props.filterable\n ? filterCommandItems(itemsResolved.value, query.value, props.filter ?? matchCommandItem)\n : itemsResolved.value,\n)\n\nconst groups = computed(() => {\n const grouped = groupCommandItems(filteredItems.value)\n\n // С непустым запросом секции «недавние» нет: там правит релевантность, а не\n // история — иначе первая же буква уводила бы взгляд не туда.\n if (query.value.trim()) return grouped\n\n return withRecentCommands(grouped, filteredItems.value, props.recentIds ?? [], recentTitle.value)\n})\n\n/**\n * Порядок обхода стрелками совпадает с порядком на экране: «недавние» стоят\n * первыми, значит и первая стрелка ведёт туда же.\n */\nconst orderedItems = computed(() => groups.value.flatMap(group => group.items))\n\n/** Плоский список того, что реально выбирается — по нему ходят стрелки. */\nconst navigableItems = computed(() => orderedItems.value.filter(item => !item.disabled))\n\nfunction itemDomId(id: string): string {\n return `${listboxId}-item-${id}`\n}\n\n/**\n * Виртуализация списка.\n *\n * Набор — плоский поток строк экрана: заголовок группы занимает такую же\n * строку, как команда, поэтому входит в него наравне. Вложенную структуру\n * рендер пересобирает уже из окна.\n */\n\n/** Оценки высоты строк. Команда крупнее заголовка; дальше уточняются замером. */\nconst ITEM_SIZE_ESTIMATE = 44\nconst GROUP_LABEL_SIZE_ESTIMATE = 28\n\ntype PaletteRow =\n | { kind: 'label', groupIndex: number }\n | { kind: 'item', groupIndex: number, item: GrCommandItem, posInSet: number, setSize: number }\n\nconst listEl = ref<HTMLElement | null>(null)\n\nconst paletteRows = computed<PaletteRow[]>(() => {\n const rows: PaletteRow[] = []\n\n groups.value.forEach((group, groupIndex) => {\n if (group.name) rows.push({ kind: 'label', groupIndex })\n\n group.items.forEach((item, index) => {\n rows.push({\n kind: 'item',\n groupIndex,\n item,\n // Набор — группа, а не весь список: по ARIA позиция отсчитывается от неё.\n posInSet: index + 1,\n setSize: group.items.length,\n })\n })\n })\n\n return rows\n})\n\nconst virtualizer = useVirtualList({\n container: listEl,\n count: () => (props.virtual ? paletteRows.value.length : 0),\n // Запрос пересобирает набор строк — замеры прошлого набора невалидны.\n source: () => paletteRows.value,\n itemSize: index => (paletteRows.value[index]?.kind === 'label' ? GROUP_LABEL_SIZE_ESTIMATE : ITEM_SIZE_ESTIMATE),\n // Список закрытой палитры не смонтирован вовсе: `clientHeight` взять неоткуда.\n // `maxHeight` можно перебить `--gr-command-list-max-height` — тогда точная\n // высота приедет с первым замером, а это останется оценкой первого рендера.\n viewportSize: () => props.maxHeight,\n})\n\n/**\n * Группы к отрисовке. При виртуализации пересобираются из среза `paletteRows`:\n * группа, начатая выше окна, всё равно открывается — иначе её команды остались\n * бы прямыми потомками listbox'а, что роль запрещает.\n */\ntype RenderedGroup = {\n key: string\n groupIndex: number\n name: string | undefined\n labelVisible: boolean\n /** Позиция строки заголовка в `paletteRows` — по ней его замеряет виртуализатор. */\n labelRowIndex?: number\n items: { item: GrCommandItem, rowIndex: number, posInSet: number, setSize: number }[]\n}\n\nconst renderedGroups = computed<RenderedGroup[]>(() => {\n const rows = paletteRows.value\n\n const from = props.virtual ? virtualizer.range.value.start : 0\n const to = props.virtual ? Math.min(rows.length, virtualizer.range.value.end) : rows.length\n\n const result: RenderedGroup[] = []\n let current: RenderedGroup | undefined\n\n for (let rowIndex = from; rowIndex < to; rowIndex++) {\n const row = rows[rowIndex]!\n const group = groups.value[row.groupIndex]\n\n if (!current || current.groupIndex !== row.groupIndex) {\n current = {\n key: group?.name ?? `__ungrouped-${row.groupIndex}`,\n groupIndex: row.groupIndex,\n name: group?.name,\n labelVisible: false,\n items: [],\n }\n result.push(current)\n }\n\n if (row.kind === 'label') {\n current.labelVisible = true\n current.labelRowIndex = rowIndex\n continue\n }\n\n current.items.push({ item: row.item, rowIndex, posInSet: row.posInSet, setSize: row.setSize })\n }\n\n return result\n})\n\n/** ARIA набора: объявляем только при виртуализации — иначе набор виден по DOM. */\nfunction itemSetProps(entry: { posInSet: number, setSize: number }): Record<string, number> | undefined {\n if (!props.virtual) return undefined\n return { 'aria-setsize': entry.setSize, 'aria-posinset': entry.posInSet }\n}\n\n\n/** Сегменты подсветки: совпавшее с запросом рисуется `<mark>`. */\nfunction matchSegments(text: string): ReturnType<typeof splitCommandMatch> {\n return splitCommandMatch(text, props.filterable ? query.value : '')\n}\n\nasync function scrollActiveIntoView(item: GrCommandItem): Promise<void> {\n // Вне окна активной команды в DOM нет: `getElementById` вернул бы `null`,\n // прокрутка не случилась бы, а `aria-activedescendant` указал бы в пустоту.\n if (props.virtual) {\n const rowIndex = paletteRows.value.findIndex(row => row.kind === 'item' && row.item.id === item.id)\n if (rowIndex >= 0) virtualizer.scrollToIndex(rowIndex)\n }\n\n await nextTick()\n document.getElementById(itemDomId(item.id))?.scrollIntoView?.({ block: 'nearest' })\n}\n\nconst {\n activeItem,\n activeDescendantId,\n setActive,\n reset: resetActive,\n handleNavigationKeys,\n} = useComboboxNavigation<GrCommandItem>({\n items: () => navigableItems.value,\n open: () => props.modelValue,\n idOf: item => itemDomId(item.id),\n scrollTo: item => scrollActiveIntoView(item),\n})\n\n// Палитра всегда стартует с первой команды — и при монтировании открытой тоже.\nresetActive(0)\n\nfunction isActive(item: GrCommandItem): boolean {\n return activeItem.value?.id === item.id\n}\n\n// Активной становится первая команда, когда список изменился **по содержимому**.\n// Следить за идентичностью массива нельзя: родитель, отдающий `:items` инлайн-\n// выражением, пересоздаёт его на каждый посторонний ререндер — и выбранная\n// стрелками команда прыгала бы в начало.\n// Разделитель — пробел, а не запятая: `id` вправе её содержать, и «a,b» + «c»\n// дало бы тот же ключ, что «a» + «b,c».\nwatch(\n () => orderedItems.value.map(item => item.id).join(' '),\n () => resetActive(0),\n)\n\n// `id` — ключ рендера и цель `aria-activedescendant`: дубли дают одинаковые\n// DOM-id, и фокус уезжает не на ту команду.\nwatch(\n itemsResolved,\n (items) => {\n if (process.env.NODE_ENV === 'production') return\n const duplicates = findDuplicateCommandIds(items)\n if (!duplicates.length) return\n\n console.warn(\n `[GrCommandPalette] повторяющиеся id команд: ${duplicates.join(', ')}. `\n + 'Из-за одинаковых DOM-id `aria-activedescendant` укажет не на ту команду.',\n )\n },\n { immediate: true },\n)\n\nfunction open(): void {\n emit('update:modelValue', true)\n}\n\nfunction close(): void {\n emit('update:modelValue', false)\n}\n\nfunction toggle(): void {\n emit('update:modelValue', !props.modelValue)\n}\n\nfunction selectItem(item: GrCommandItem): void {\n if (item.disabled) return\n emit('select', item)\n if (props.closeOnSelect) close()\n}\n\nfunction onItemHover(item: GrCommandItem): void {\n const index = navigableItems.value.indexOf(item)\n if (index >= 0) setActive(index)\n}\n\nfunction onInput(event: Event): void {\n query.value = (event.target as HTMLInputElement).value\n emit('search', query.value.trim())\n}\n\nfunction onKeydown(event: KeyboardEvent): void {\n // Клавиша во время IME-композиции принадлежит композиции: Enter коммитит её,\n // стрелки ходят по кандидатам.\n if (isComposingEvent(event)) return\n\n if (handleNavigationKeys(event)) return\n\n switch (event.key) {\n case 'Enter':\n if (!activeItem.value) break\n event.preventDefault()\n selectItem(activeItem.value)\n break\n }\n}\n\n// ————— Открытие: сброс запроса и выбора.\nwatch(\n () => props.modelValue,\n (open) => {\n if (!open) return\n\n query.value = ''\n resetActive(0)\n },\n)\n\n// Фокус в поле — отдельным наблюдателем, и по появлению самого поля: содержимое\n// панели рождается вместе с ней, и по одному лишь пропу фокусировать нечего.\n// Ловушка фокуса `GrModal` этот выбор не перебивает — фокус, уже стоящий внутри\n// слоя, она считает осознанным.\nwatch(\n [() => props.modelValue, inputEl],\n ([open]) => {\n if (open) inputEl.value?.focus()\n },\n { flush: 'post' },\n)\n\n// ————— Глобальное сочетание открытия.\nconst parsedHotkey = computed(() => (props.hotkey ? parseCommandHotkey(props.hotkey) : null))\n\n// Платформа определяется только после монтирования: на сервере `navigator` нет,\n// и первый рендер обязан совпасть с серверным. Иначе сервер отдаёт `Ctrl`,\n// клиент на macOS рисует `⌘`, и гидрация расходится по тексту подсказки.\nconst isApple = ref(false)\n\nconst hotkeyHint = computed(() =>\n props.showHotkeyHint && parsedHotkey.value ? formatCommandHotkey(parsedHotkey.value, isApple.value) : [],\n)\n\nfunction onWindowKeydown(event: KeyboardEvent): void {\n const hotkey = parsedHotkey.value\n if (!hotkey || !matchesCommandHotkey(event, hotkey, isApple.value)) return\n event.preventDefault()\n toggle()\n}\n\nonMounted(() => {\n if (typeof window === 'undefined') return\n isApple.value = isAppleDevice()\n window.addEventListener('keydown', onWindowKeydown)\n})\n\nonBeforeUnmount(() => {\n if (typeof window !== 'undefined') window.removeEventListener('keydown', onWindowKeydown)\n})\n\n// Эффективный размер: локальный проп → `GrConfigProvider` → дефолт компонента.\nconst resolvedSize = useGrComponentProp('GrCommandPalette', 'size', () => props.size, 'lg')\n\nconst modalSize = computed(() => commandPaletteModalSizeBySize[resolvedSize.value])\nconst listStyle = computed(() => ({ maxHeight: `var(--gr-command-list-max-height, ${props.maxHeight}px)` }))\n\nconst listStyleWithSpacers = computed(() => {\n const base = listStyle.value\n if (!props.virtual) return base\n\n return {\n ...base,\n ...virtualizer.spacerStyle.value,\n }\n})\n\ndefineExpose({ open, close, toggle })\n</script>\n\n<template>\n <GrModal\n :model-value=\"modelValue\"\n :size=\"modalSize\"\n @update:model-value=\"$emit('update:modelValue', $event)\"\n >\n <div data-gr-command-palette :aria-label=\"resolvedAriaLabel\">\n <div data-gr-command-palette-search :class=\"commandSearchRowClass\">\n <span class=\"i-lucide-search block h-4 w-4 shrink-0 text-[var(--gr-muted-fg)]\" aria-hidden=\"true\" />\n\n <input\n ref=\"inputEl\"\n data-gr-command-palette-input\n data-testid=\"gr-command-palette-input\"\n type=\"text\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n aria-autocomplete=\"list\"\n aria-expanded=\"true\"\n :aria-label=\"resolvedAriaLabel\"\n :aria-controls=\"listboxId\"\n :aria-activedescendant=\"activeDescendantId\"\n :value=\"query\"\n :placeholder=\"resolvedPlaceholder\"\n :class=\"commandSearchInputClass\"\n @input=\"onInput\"\n @keydown=\"onKeydown\"\n >\n\n <span v-if=\"loading\" class=\"shrink-0 text-[var(--gr-muted-fg)]\" aria-hidden=\"true\">\n <span class=\"i-lucide-loader-2 block h-4 w-4 animate-spin\" />\n </span>\n <span v-else-if=\"hotkeyHint.length\" class=\"shrink-0\" aria-hidden=\"true\">\n <GrKbd :keys=\"hotkeyHint\" separator=\"\" size=\"sm\" />\n </span>\n </div>\n\n <div\n :id=\"listboxId\"\n ref=\"listEl\"\n data-gr-command-palette-list\n data-testid=\"gr-command-palette-list\"\n :data-gr-virtual=\"virtual ? '' : undefined\"\n class=\"overflow-y-auto p-2\"\n :style=\"listStyleWithSpacers\"\n role=\"listbox\"\n :aria-label=\"resolvedAriaLabel\"\n >\n <!-- Прямыми потомками listbox могут быть только `role=\"group\"`: заголовок\n группы лежит внутри неё и объявлен презентационным (имя группе он\n даёт через `aria-labelledby`), иначе ломается `aria-required-children`.\n\n При виртуализации окно может начаться ниже заголовка — тогда его в\n DOM нет, и имя группы идёт напрямую в `aria-label`. -->\n <div\n v-for=\"group in renderedGroups\"\n :key=\"group.key\"\n role=\"group\"\n :aria-labelledby=\"group.name && group.labelVisible ? `${listboxId}-group-${group.groupIndex}` : undefined\"\n :aria-label=\"group.name && !group.labelVisible ? group.name : undefined\"\n >\n <div\n v-if=\"group.name && group.labelVisible\"\n :id=\"`${listboxId}-group-${group.groupIndex}`\"\n :ref=\"(el) => virtual && group.labelRowIndex !== undefined && virtualizer.measure(group.labelRowIndex, el as Element | null)\"\n data-gr-command-palette-group\n role=\"presentation\"\n :class=\"commandGroupLabelClass\"\n >\n {{ group.name }}\n </div>\n\n <div>\n <div\n v-for=\"entry in group.items\"\n :id=\"itemDomId(entry.item.id)\"\n :key=\"entry.item.id\"\n :ref=\"(el) => virtual && virtualizer.measure(entry.rowIndex, el as Element | null)\"\n data-gr-command-palette-item\n :data-testid=\"`gr-command-palette-item-${entry.item.id}`\"\n role=\"option\"\n v-bind=\"itemSetProps(entry)\"\n :aria-selected=\"isActive(entry.item) ? 'true' : 'false'\"\n :aria-disabled=\"entry.item.disabled ? 'true' : undefined\"\n :class=\"commandItemClass({ active: isActive(entry.item), disabled: Boolean(entry.item.disabled) })\"\n @click=\"selectItem(entry.item)\"\n @mousemove=\"onItemHover(entry.item)\"\n >\n <slot name=\"item\" :item=\"entry.item\" :active=\"isActive(entry.item)\">\n <span\n v-if=\"entry.item.icon\"\n class=\"block h-4 w-4 shrink-0\"\n :class=\"[entry.item.icon, commandItemMutedClass(Boolean(entry.item.disabled))]\"\n aria-hidden=\"true\"\n />\n <span class=\"min-w-0 flex-1\">\n <span class=\"block truncate\">\n <template v-for=\"(segment, index) in matchSegments(entry.item.label)\" :key=\"index\">\n <mark v-if=\"segment.match\" :class=\"commandMatchClass\">{{ segment.text }}</mark>\n <template v-else>{{ segment.text }}</template>\n </template>\n </span>\n <span\n v-if=\"entry.item.description\"\n :class=\"[commandItemDescriptionClass, commandItemMutedClass(Boolean(entry.item.disabled))]\"\n >\n <template v-for=\"(segment, index) in matchSegments(entry.item.description)\" :key=\"index\">\n <mark v-if=\"segment.match\" :class=\"commandMatchClass\">{{ segment.text }}</mark>\n <template v-else>{{ segment.text }}</template>\n </template>\n </span>\n </span>\n <span v-if=\"entry.item.shortcut?.length\" class=\"shrink-0\">\n <GrKbd :keys=\"entry.item.shortcut\" separator=\"\" size=\"sm\" />\n </span>\n </slot>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Состояния объявляются живым регионом: `aria-label` на generic-элементе\n большинство AT игнорируют, и загрузка осталась бы неозвученной. Регион\n один на оба состояния и лежит вне listbox — внутри он стал бы его\n недопустимым потомком. -->\n <div\n v-if=\"loading || !filteredItems.length\"\n data-gr-command-palette-empty\n data-testid=\"gr-command-palette-empty\"\n role=\"status\"\n aria-live=\"polite\"\n :class=\"commandEmptyClass\"\n >\n <template v-if=\"loading\">\n {{ loadingText }}\n </template>\n <slot v-else name=\"empty\" :query=\"query\">\n {{ resolvedEmptyText }}\n </slot>\n </div>\n\n <div v-if=\"$slots.footer\" data-gr-command-palette-footer :class=\"commandFooterClass\">\n <slot name=\"footer\" />\n </div>\n </div>\n </GrModal>\n</template>\n\n<style scoped>\n/*\n * Распорки виртуального списка.\n *\n * Не `padding` контейнера: `max-height` меряет ту же коробку. Не обёртка внутри\n * listbox'а: прямыми потомками роли обязаны быть только группы. Не отступы\n * крайних строк: при прыжке прокрутки строки заменяются целиком, и распорка\n * исчезла бы вместе с ними.\n *\n * `display: block` обязателен: список палитры — блочный поток, а псевдоэлемент\n * по умолчанию строчный, и строчная коробка игнорирует `height` — распорка\n * молча не создалась бы вовсе.\n */\n[data-gr-virtual]::before,\n[data-gr-virtual]::after {\n content: '';\n display: block;\n flex: none;\n}\n\n[data-gr-virtual]::before {\n height: var(--gr-virtual-before, 0px);\n}\n\n[data-gr-virtual]::after {\n height: var(--gr-virtual-after, 0px);\n}\n</style>\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"GrDataTable-DVFho9Cj.js","names":["$slots"],"sources":["../../../../src/components/GrDataTable/grDataTableSort.ts","../../../../src/components/GrDataTable/GrDataTable.vue","../../../../src/components/GrDataTable/GrDataTable.vue"],"sourcesContent":["/**\n * Сравнение значений ячеек для сортировки `GrDataTable`.\n *\n * Логика вынесена из SFC: у неё есть крайние случаи, которые дешевле проверить\n * прямым вызовом, чем через монтирование таблицы.\n *\n * Прежняя реализация приводила значение к числу через `Number(value)`, а он\n * считает нулём и `null`, и пустую строку: пустые ячейки оседали в середине\n * числового ряда, между отрицательными и положительными. Здесь пустое значение —\n * отдельная категория, и она всегда в конце, независимо от направления.\n */\n\nexport type GrDataTableSortDir = 'asc' | 'desc'\n\n/** Пустое значение ячейки: нечего сравнивать, место таким — в конце списка. */\nexport function isEmptyCell(value: unknown): boolean {\n if (value === null || value === undefined)\n return true\n\n return typeof value === 'string' && value.trim() === ''\n}\n\n/**\n * Число, с которым имеет смысл сравнивать: сам `number`, числовая строка, `Date`\n * (по таймстампу), `boolean` (`false` < `true`). Иначе — `null`, и сравнение\n * уходит в строковую ветку.\n */\nfunction toComparableNumber(value: unknown): number | null {\n if (typeof value === 'number')\n return Number.isFinite(value) ? value : null\n\n if (typeof value === 'boolean')\n return value ? 1 : 0\n\n if (typeof value === 'bigint')\n return Number(value)\n\n if (value instanceof Date) {\n const time = value.getTime()\n return Number.isNaN(time) ? null : time\n }\n\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (trimmed === '')\n return null\n\n const parsed = Number(trimmed)\n return Number.isFinite(parsed) ? parsed : null\n }\n\n return null\n}\n\n/**\n * Сравнивает два значения ячейки. Возвращает готовую разницу с учётом `dir` —\n * кроме пустых значений: они всегда в конце, иначе «пустые» в `desc` уезжали бы\n * наверх и таблица выглядела бы сломанной.\n */\nexport function compareCells(\n a: unknown,\n b: unknown,\n dir: GrDataTableSortDir,\n locale?: string,\n): number {\n const aEmpty = isEmptyCell(a)\n const bEmpty = isEmptyCell(b)\n if (aEmpty || bEmpty)\n return aEmpty && bEmpty ? 0 : (aEmpty ? 1 : -1)\n\n const aNum = toComparableNumber(a)\n const bNum = toComparableNumber(b)\n\n const result = aNum !== null && bNum !== null\n ? aNum - bNum\n : String(a).localeCompare(String(b), locale, { sensitivity: 'base', numeric: true })\n\n return dir === 'asc' ? result : -result\n}\n\n/**\n * Новый отсортированный массив. Исходный не мутируется: `rows` приходят пропом,\n * сортировка на месте меняла бы данные потребителя.\n */\nexport function sortRows<TRow extends Record<string, unknown>>(\n rows: readonly TRow[],\n key: string,\n dir: GrDataTableSortDir,\n locale?: string,\n): TRow[] {\n return [...rows].sort((a, b) => compareCells(a[key], b[key], dir, locale))\n}\n","\n<script setup lang=\"ts\" generic=\"TRow extends Record<string, unknown> = Record<string, unknown>\">\nimport { computed, nextTick, onMounted, ref, useId } from 'vue'\n\nimport GrTable from '../GrTable/GrTable.vue'\nimport GrIcon from '../GrIcon/GrIcon.vue'\nimport GrCheckbox from '../GrCheckbox/GrCheckbox.vue'\nimport { useGrComponentSize } from '../GrConfigProvider/context'\nimport { useVirtualList } from '../../composables/useVirtualList'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { sortRows, type GrDataTableSortDir } from './grDataTableSort'\nimport {\n type GrDataTableSize,\n cellPaddings,\n headerGaps,\n headerTextSizes,\n placeholderPaddings,\n rowHeightEstimates,\n selectCheckboxSizes,\n selectColumnWidths,\n sortIconSizes,\n spinnerSizes,\n} from './grDataTableStyles'\n\nimport IconArrowUp from '~icons/lucide/arrow-up'\nimport IconArrowDown from '~icons/lucide/arrow-down'\n\n/**\n * Ключ колонки. Собственные поля строки подсказываются автодополнением, но\n * произвольная строка тоже допустима: колонка может быть вычисляемой и жить\n * только в слоте `#cell-<key>`.\n */\nexport type GrDataColumnKey<TRow extends Record<string, unknown> = Record<string, unknown>> =\n | Extract<keyof TRow, string>\n | (string & {})\n\nexport type GrDataColumn<TRow extends Record<string, unknown> = Record<string, unknown>> = {\n key: GrDataColumnKey<TRow>\n label: string\n sortable?: boolean\n align?: 'left' | 'center' | 'right'\n /**\n * Ширина колонки: число — пиксели, строка — как есть (`'40%'`, `'12rem'`).\n *\n * Уезжает в заголовочную ячейку: при фиксированной раскладке ширины колонок\n * задаёт первая строка таблицы, а это и есть `<thead>`.\n */\n width?: string | number\n}\n\nexport type GrDataTableRowKey<TRow extends Record<string, unknown> = Record<string, unknown>> =\n | string\n | ((row: TRow) => string | number)\n\n/** Порядок обхода состояний сортировки по клику на заголовок. */\nexport type GrDataTableSortCycle = 'asc-desc' | 'asc-desc-none'\n\nexport interface GrDataTableProps<TRow extends Record<string, unknown> = Record<string, unknown>> {\n rows: TRow[]\n columns: GrDataColumn<TRow>[]\n /** Ключ строки или функция-резолвер. По умолчанию — поле `'id'`. */\n rowKey?: GrDataTableRowKey<TRow>\n /** Ключ колонки для начальной сортировки (uncontrolled-режим). */\n initialSortKey?: string\n /** Направление начальной сортировки (uncontrolled-режим). */\n initialSortDir?: GrDataTableSortDir\n /** Контролируемый ключ сортировки: `v-model:sortKey`. Задаёт controlled-режим. */\n sortKey?: string\n /** Контролируемое направление сортировки: `v-model:sortDir`. */\n sortDir?: GrDataTableSortDir\n /** Клик по заголовку: `asc → desc` или `asc → desc → без сортировки`. */\n sortCycle?: GrDataTableSortCycle\n /**\n * Внешняя сортировка (например серверная): компонент НЕ сортирует `rows`\n * сам, а только сообщает о смене через `update:sortKey`/`update:sortDir`/\n * `sortChange`. `rows` при этом должны приходить уже отсортированными.\n */\n externalSort?: boolean\n /**\n * Выбор строк: добавляет ведущую колонку с чекбоксами (+ «выбрать все» в шапке).\n * Выбранные ключи строк — через `v-model:selected`.\n */\n selectable?: boolean\n /** Контролируемый список выбранных ключей строк: `v-model:selected`. */\n selected?: Array<string | number>\n /** Предикат «строку можно выбрать». Невыбираемые строки не попадают и в «выбрать все». */\n selectableRow?: (row: TRow) => boolean\n /**\n * Состояние загрузки: тело таблицы заменяется строкой-индикатором.\n * `empty`-состояние при этом не показывается.\n */\n loading?: boolean\n /** Текст индикатора загрузки. i18n: fallback `gr.dataTable.loading`. */\n loadingText?: string\n /** Текст пустого состояния. i18n: fallback `gr.dataTable.empty`; слот `#empty` сильнее. */\n emptyText?: string\n /** Класс строки: общий для всех либо вычисляемый по строке. */\n rowClass?: string | ((row: TRow, index: number) => string | undefined)\n /** Произвольные атрибуты строки (`data-*`, `title`, …). */\n rowProps?: (row: TRow, index: number) => Record<string, unknown> | undefined\n /**\n * Размер таблицы: кегль, паддинги ячеек, стрелки сортировки и чекбоксы.\n * Прокидывается в `GrTable`.\n */\n size?: GrDataTableSize\n // Прокси к GrTable:\n caption?: string\n ariaLabel?: string\n ariaLabelledby?: string\n regionLabel?: string\n /** Прилипающий заголовок при вертикальном скролле (нужен `maxHeight`). */\n stickyHeader?: boolean\n /**\n * Виртуализация строк: в DOM живёт только окно вокруг вьюпорта. Высоту окна\n * задаёт `maxHeight`, без него окна прокрутки не существует.\n *\n * Включает фиксированную раскладку таблицы: ширина колонки считается по\n * содержимому всех строк, а в DOM их только окно — без фиксации колонки\n * прыгали бы на каждой прокрутке. Ширины стоит задать через `width` у\n * колонок, иначе фиксированная раскладка делит место поровну.\n */\n virtual?: boolean\n /** Максимальная высота таблицы (вертикальный скролл). Число — в пикселях. */\n maxHeight?: string | number\n}\n\nexport interface GrDataTableEmits<TRow extends Record<string, unknown> = Record<string, unknown>> {\n (e: 'update:sortKey', value: string): void\n (e: 'update:sortDir', value: GrDataTableSortDir): void\n (e: 'sortChange', value: { key: string, dir: GrDataTableSortDir }): void\n (e: 'update:selected', value: Array<string | number>): void\n (e: 'rowClick', payload: { row: TRow, index: number, event: MouseEvent }): void\n}\n\n/**\n * `GrDataTable` — data-таблица поверх `GrTable` с сортировкой по клику\n * на заголовок и scoped-слотами ячеек (`#cell-<key>`), `#header-<key>`,\n * `#caption`, `#footer`, `#empty`, `#loading`.\n *\n * Сортировка, включая крайние случаи с пустыми и смешанными значениями, живёт\n * в `grDataTableSort.ts` — см. `docs/components/GrDataTable.md`.\n */\nconst props = withDefaults(defineProps<GrDataTableProps<TRow>>(), {\n rowKey: 'id' as GrDataTableRowKey<TRow>,\n initialSortKey: undefined,\n initialSortDir: 'asc',\n sortKey: undefined,\n sortDir: undefined,\n sortCycle: 'asc-desc',\n externalSort: false,\n selectable: false,\n selected: undefined,\n selectableRow: undefined,\n loading: false,\n loadingText: undefined,\n emptyText: undefined,\n rowClass: undefined,\n rowProps: undefined,\n size: undefined,\n caption: undefined,\n ariaLabel: undefined,\n ariaLabelledby: undefined,\n regionLabel: undefined,\n stickyHeader: false,\n maxHeight: undefined,\n virtual: false,\n})\n\nconst emit = defineEmits<GrDataTableEmits<TRow>>()\n\nconst { t, locale } = useGranularityTranslations()\nconst resolvedLoadingText = computed(() => props.loadingText ?? t('gr.dataTable.loading', 'Loading…'))\nconst resolvedEmptyText = computed(() => props.emptyText ?? t('gr.dataTable.empty', 'No data'))\n\n// Uncontrolled-состояние; в controlled-режиме перекрывается пропами `sortKey`/`sortDir`.\nconst internalSortKey = ref<string>(props.initialSortKey ?? '')\nconst internalSortDir = ref<GrDataTableSortDir>(props.initialSortDir)\n\nconst isSortKeyControlled = computed(() => props.sortKey !== undefined)\nconst isSortDirControlled = computed(() => props.sortDir !== undefined)\n\nconst currentSortKey = computed(() => props.sortKey ?? internalSortKey.value)\nconst currentSortDir = computed<GrDataTableSortDir>(() => props.sortDir ?? internalSortDir.value)\n\nfunction applySort(key: string, dir: GrDataTableSortDir): void {\n if (!isSortKeyControlled.value)\n internalSortKey.value = key\n if (!isSortDirControlled.value)\n internalSortDir.value = dir\n\n emit('update:sortKey', key)\n emit('update:sortDir', dir)\n emit('sortChange', { key, dir })\n}\n\nconst sortedRows = computed(() => {\n // Внешняя сортировка: `rows` уже отсортированы потребителем — не трогаем.\n if (props.externalSort)\n return [...props.rows]\n\n const key = currentSortKey.value\n if (!key)\n return [...props.rows]\n\n return sortRows(props.rows, key, currentSortDir.value, locale.value)\n})\n\nfunction toggleSort(col: GrDataColumn<TRow>): void {\n if (!col.sortable)\n return\n\n if (currentSortKey.value !== col.key) {\n applySort(col.key, 'asc')\n return\n }\n\n if (currentSortDir.value === 'asc') {\n applySort(col.key, 'desc')\n return\n }\n\n // Третье состояние: сортировка снимается, порядок возвращается к исходному.\n if (props.sortCycle === 'asc-desc-none') {\n applySort('', 'asc')\n return\n }\n\n applySort(col.key, 'asc')\n}\n\nfunction cellAlign(col: GrDataColumn<TRow>): string {\n if (col.align === 'right')\n return 'text-right'\n if (col.align === 'center')\n return 'text-center'\n return 'text-left'\n}\n\n/**\n * Синтетические ключи для строк, у которых поле `rowKey` пустое.\n *\n * Без них строка получила бы `String(undefined ?? '')` — **один и тот же** ключ\n * на всю таблицу: Vue переиспользовал бы DOM не по назначению, а выбор одной\n * строки помечал бы выбранными все. Ключ привязан к идентичности объекта,\n * поэтому переживает сортировку и не зависит от индекса.\n */\nconst syntheticKeys = new WeakMap<object, string>()\nlet syntheticKeyCounter = 0\nconst missingKeyWarned = ref(false)\n\nfunction syntheticRowKey(row: TRow): string {\n const existing = syntheticKeys.get(row)\n if (existing !== undefined)\n return existing\n\n syntheticKeyCounter += 1\n const generated = `gr-row-${syntheticKeyCounter}`\n syntheticKeys.set(row, generated)\n\n if (!missingKeyWarned.value && process.env.NODE_ENV !== 'production') {\n missingKeyWarned.value = true\n console.warn(\n `[GrDataTable] У строки нет значения по ключу \"${String(props.rowKey)}\". `\n + 'Задайте `rowKey` (поле или функцию) — иначе выбор строк и переиспользование '\n + 'DOM работают по синтетическому ключу, который не переживёт перезагрузку данных.',\n )\n }\n\n return generated\n}\n\nfunction rowKeyValue(row: TRow): string | number {\n const rk = props.rowKey\n if (typeof rk === 'function')\n return rk(row)\n\n const value = (row as Record<string, unknown>)[rk as string]\n if (typeof value === 'string' && value !== '')\n return value\n if (typeof value === 'number')\n return value\n\n return syntheticRowKey(row)\n}\n\nfunction ariaSortFor(col: GrDataColumn<TRow>): 'ascending' | 'descending' | 'none' | undefined {\n if (!col.sortable)\n return undefined\n if (currentSortKey.value !== col.key)\n return 'none'\n return currentSortDir.value === 'asc' ? 'ascending' : 'descending'\n}\n\n/**\n * Подсказка «что сделает нажатие» — visually hidden текстом **после** подписи\n * колонки, а не `aria-label`'ом на кнопке. `aria-label` подменял бы собой имя\n * `<th>`, и AT при переходе по ячейкам вместо «Название» читала бы\n * «Отсортировано по Название по возрастанию, нажмите…». Текущее состояние\n * сортировки при этом объявляет `aria-sort` на самом `<th>`.\n */\nfunction sortHint(col: GrDataColumn<TRow>): string {\n if (currentSortKey.value !== col.key)\n return t('gr.dataTable.sortAsc', 'Activate to sort ascending')\n\n if (currentSortDir.value === 'asc')\n return t('gr.dataTable.sortDesc', 'Activate to sort descending')\n\n return props.sortCycle === 'asc-desc-none'\n ? t('gr.dataTable.sortNone', 'Activate to remove sorting')\n : t('gr.dataTable.sortAsc', 'Activate to sort ascending')\n}\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrDataTable' })\n\nconst cellClass = computed(() => cellPaddings[resolvedSize.value])\nconst placeholderClass = computed(() => placeholderPaddings[resolvedSize.value])\nconst headerTextClass = computed(() => headerTextSizes[resolvedSize.value])\nconst headerGapClass = computed(() => headerGaps[resolvedSize.value])\nconst selectColumnClass = computed(() => selectColumnWidths[resolvedSize.value])\nconst spinnerClass = computed(() => spinnerSizes[resolvedSize.value])\nconst sortIconSize = computed(() => sortIconSizes[resolvedSize.value])\nconst checkboxSize = computed(() => selectCheckboxSizes[resolvedSize.value])\n\n/**\n * Номер строки и полное их число объявляем только при виртуализации: иначе\n * диктор считает строки по разметке, а там всего окно. Заголовок — строка\n * первая, поэтому строки набора начинаются со второй.\n */\nconst ariaRowCount = computed(() => (props.virtual ? sortedRows.value.length + 1 : undefined))\n\nfunction ariaRowIndex(index: number): number | undefined {\n return props.virtual ? index + 2 : undefined\n}\n\nconst tableProps = computed(() => ({\n size: resolvedSize.value,\n ariaLabel: props.ariaLabel,\n ariaLabelledby: props.ariaLabelledby,\n regionLabel: props.regionLabel,\n stickyHeader: props.stickyHeader,\n maxHeight: props.maxHeight,\n rowCount: ariaRowCount.value,\n fixedLayout: props.virtual,\n}))\n\nconst isEmpty = computed(() => sortedRows.value.length === 0)\n\n// Общее число колонок с учётом ведущей чекбокс-колонки — для `colspan`\n// строк loading/empty.\nconst totalColumns = computed(() => props.columns.length + (props.selectable ? 1 : 0))\n\n/**\n * Живой регион существует с первого рендера и пуст, пока объявлять нечего.\n * Регион, который появляется уже с текстом, часть AT не объявляет вовсе —\n * а именно так вело себя `role=\"status\"` внутри строки загрузки.\n */\nconst liveMessage = computed(() => {\n if (props.loading)\n return resolvedLoadingText.value\n if (isEmpty.value)\n return resolvedEmptyText.value\n return ''\n})\n\nfunction cellValue(row: TRow, key: string): unknown {\n return (row as Record<string, unknown>)[key]\n}\n\nfunction rowClassName(row: TRow, index: number): string | undefined {\n const source = props.rowClass\n return typeof source === 'function' ? source(row, index) : source\n}\n\nfunction onRowClick(row: TRow, index: number, event: MouseEvent): void {\n emit('rowClick', { row, index, event })\n}\n\n// ————— Выбор строк.\n// Uncontrolled-режим, как у сортировки выше: без `v-model:selected` чекбоксы\n// рисовались, кликались и никогда не отмечались — состояние выбора целиком\n// выводилось из пропа, а внутреннего не было вовсе.\nconst internalSelected = ref<Array<string | number>>([])\nconst isSelectedControlled = computed(() => props.selected !== undefined)\n\nconst selectedKeys = computed<Set<string | number>>(\n () => new Set(props.selected ?? internalSelected.value),\n)\n\nfunction isRowSelectable(row: TRow): boolean {\n return props.selectableRow ? props.selectableRow(row) : true\n}\n\nfunction isRowSelected(row: TRow): boolean {\n return selectedKeys.value.has(rowKeyValue(row))\n}\n\n/** «Выбрать все» работает по видимым и выбираемым строкам — они же считают состояние шапки. */\nconst selectableRows = computed(() => sortedRows.value.filter(isRowSelectable))\n\nconst allSelected = computed(() =>\n selectableRows.value.length > 0 && selectableRows.value.every(isRowSelected),\n)\nconst someSelected = computed(() =>\n selectableRows.value.some(isRowSelected) && !allSelected.value,\n)\n\nfunction emitSelected(next: Set<string | number>): void {\n if (!isSelectedControlled.value)\n internalSelected.value = [...next]\n\n emit('update:selected', [...next])\n}\n\nfunction toggleRow(row: TRow): void {\n if (!isRowSelectable(row))\n return\n\n const key = rowKeyValue(row)\n const next = new Set(selectedKeys.value)\n if (next.has(key))\n next.delete(key)\n else\n next.add(key)\n emitSelected(next)\n}\n\nfunction toggleAll(): void {\n const next = new Set(selectedKeys.value)\n\n if (allSelected.value) {\n // Снимаем выбор только с видимых строк, сохраняя внешние ключи.\n for (const row of selectableRows.value) next.delete(rowKeyValue(row))\n emitSelected(next)\n return\n }\n\n for (const row of selectableRows.value) next.add(rowKeyValue(row))\n emitSelected(next)\n}\n\n// ————— Императивный API.\nconst tableRef = ref<InstanceType<typeof GrTable> | null>(null)\nconst rootId = useId()\n\nfunction rootEl(): HTMLElement | null {\n return (tableRef.value?.$el as HTMLElement | undefined) ?? null\n}\n\n/** Скролл-контейнер таблицы — он же элемент с `maxHeight`. */\nfunction scrollEl(): HTMLElement | null {\n const root = rootEl()\n return root?.matches('[data-gr-table-scroll]') ? root : root?.querySelector('[data-gr-table-scroll]') ?? null\n}\n\n/**\n * Виртуализация строк.\n *\n * Распорки здесь не псевдоэлементы, как у списочных компонентов: `<tbody>`\n * игнорирует `padding`, а псевдоэлемент внутри группы строк не образует строку\n * с управляемой высотой. Поэтому срезанное держат две служебные `<tr>` — той же\n * формы, что строки загрузки и пустоты у `GrTable`.\n */\nconst scrollContainer = ref<HTMLElement | null>(null)\n\nonMounted(() => {\n scrollContainer.value = scrollEl()\n})\n\nconst virtualizer = useVirtualList({\n container: scrollContainer,\n count: () => (props.virtual ? sortedRows.value.length : 0),\n // Сортировка и замена rows раздают индексы заново — замеры сбрасываются.\n source: () => sortedRows.value,\n itemSize: () => rowHeightEstimates[resolvedSize.value],\n // Скролл-контейнер живёт в `GrTable` и до монтирования недоступен: окно\n // первого рендера считается от объявленной высоты.\n viewportSize: () => (typeof props.maxHeight === 'number' ? props.maxHeight : undefined),\n})\n\n/** Строки к отрисовке вместе с их абсолютной позицией в наборе. */\nconst renderedRows = computed(() => {\n const rows = sortedRows.value\n if (!props.virtual) return rows.map((row, index) => ({ row, index }))\n\n const { start, end } = virtualizer.range.value\n return rows.slice(start, end).map((row, offset) => ({ row, index: start + offset }))\n})\n\nconst spacerBefore = computed(() => (props.virtual ? virtualizer.offset.value : 0))\nconst spacerAfter = computed(() => (props.virtual ? virtualizer.offsetEnd.value : 0))\n\n/** Ширина колонки: число трактуем как пиксели. */\nfunction columnWidthStyle(width: string | number | undefined): Record<string, string> | undefined {\n if (width === undefined) return undefined\n return { width: typeof width === 'number' ? `${width}px` : width }\n}\n\nfunction scrollToRow(key: string | number, options?: ScrollIntoViewOptions): boolean {\n // Ключ строки — произвольная строка от потребителя, в селектор её не подставить:\n // сравниваем через `dataset`, а не собираем `[data-row-key=\"…\"]` конкатенацией.\n const target = String(key)\n\n // Строки вне окна в DOM нет: без прокрутки виртуального списка поиск по\n // `dataset` вернул бы `undefined`, и метод молча отвечал бы «не нашёл».\n if (props.virtual) {\n const index = sortedRows.value.findIndex(row => String(rowKeyValue(row)) === target)\n if (index < 0) return false\n\n virtualizer.scrollToIndex(index)\n void nextTick(() => {\n // Вызов опциональный: отложенный `scrollIntoView` некому поймать, а в\n // среде без него (jsdom) это дало бы необработанный отказ промиса.\n findRowEl(target)?.scrollIntoView?.(options ?? { block: 'nearest' })\n })\n return true\n }\n\n const row = findRowEl(target)\n if (!row)\n return false\n\n row.scrollIntoView(options ?? { block: 'nearest' })\n return true\n}\n\nfunction findRowEl(rowKey: string): HTMLElement | undefined {\n // Ключ строки — произвольная строка от потребителя, в селектор её не\n // подставить: сравниваем через `dataset`, а не собираем селектор конкатенацией.\n const rows = rootEl()?.querySelectorAll<HTMLElement>('[data-gr-datatable-row]') ?? []\n return Array.prototype.find.call(rows, (el: HTMLElement) => el.dataset.rowKey === rowKey) as HTMLElement | undefined\n}\n\nconst scrollTo = (options: ScrollToOptions): void => {\n scrollEl()?.scrollTo(options)\n}\n\nfunction clearSort(): void {\n applySort('', 'asc')\n}\n\ndefineExpose({\n /** Прокрутить к строке по её ключу. `false` — строки нет в DOM. */\n scrollToRow,\n /** Прокрутить скролл-контейнер таблицы. */\n scrollTo,\n /** Снять сортировку (эквивалент третьего состояния). */\n clearSort,\n /** Отметить/снять все выбираемые строки. */\n toggleAll,\n})\n</script>\n\n<template>\n <GrTable\n v-bind=\"tableProps\"\n ref=\"tableRef\"\n data-gr-datatable\n >\n <!-- Caption рендерится всегда (у `GrTable` он `sr-only`): в нём живёт\n постоянный live-регион, иначе объявлять загрузку было бы нечему. -->\n <template #caption>\n <slot name=\"caption\">\n {{ caption }}\n </slot>\n <span\n :id=\"`${rootId}-live`\"\n data-gr-datatable-live\n role=\"status\"\n aria-live=\"polite\"\n >{{ liveMessage }}</span>\n </template>\n\n <template #header>\n <tr data-gr-datatable-header :aria-rowindex=\"virtual ? 1 : undefined\">\n <th\n v-if=\"selectable\"\n class=\"text-left\"\n :class=\"[selectColumnClass, cellClass]\"\n scope=\"col\"\n >\n <GrCheckbox\n data-gr-datatable-select-all\n :model-value=\"allSelected\"\n :indeterminate=\"someSelected\"\n :size=\"checkboxSize\"\n :disabled=\"loading || selectableRows.length === 0\"\n :aria-label=\"t('gr.dataTable.selectAll', 'Select all rows')\"\n @update:model-value=\"toggleAll\"\n />\n </th>\n <th\n v-for=\"col in columns\"\n :key=\"col.key\"\n class=\"font-700\"\n :class=\"[headerTextClass, cellClass, cellAlign(col)]\"\n :style=\"columnWidthStyle(col.width)\"\n :aria-sort=\"ariaSortFor(col)\"\n scope=\"col\"\n >\n <div class=\"inline-flex items-center\" :class=\"headerGapClass\">\n <button\n v-if=\"col.sortable\"\n type=\"button\"\n data-gr-datatable-sort\n class=\"inline-flex items-center text-[var(--gr-muted-fg)] hover:text-[var(--gr-fg)] transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--gr-ring)] rounded-[var(--gr-radius-sm)]\"\n :class=\"headerGapClass\"\n @click=\"toggleSort(col)\"\n >\n <span>\n <slot :name=\"`header-${col.key}`\" :column=\"col\">\n {{ col.label }}\n </slot>\n </span>\n <span class=\"sr-only\">{{ sortHint(col) }}</span>\n <span class=\"inline-flex\" aria-hidden=\"true\">\n <GrIcon v-if=\"currentSortKey === col.key && currentSortDir === 'asc'\" :size=\"sortIconSize\">\n <IconArrowUp />\n </GrIcon>\n <GrIcon v-else-if=\"currentSortKey === col.key && currentSortDir === 'desc'\" :size=\"sortIconSize\">\n <IconArrowDown />\n </GrIcon>\n </span>\n </button>\n <span v-else class=\"text-[var(--gr-muted-fg)]\">\n <slot :name=\"`header-${col.key}`\" :column=\"col\">\n {{ col.label }}\n </slot>\n </span>\n </div>\n </th>\n </tr>\n </template>\n\n <template v-if=\"loading\">\n <tr data-gr-datatable-loading>\n <td :colspan=\"totalColumns\" class=\"text-center text-[var(--gr-muted-fg)]\" :class=\"placeholderClass\">\n <slot name=\"loading\">\n <span class=\"inline-flex items-center gap-2\">\n <span class=\"i-lucide-loader-circle block animate-spin\" :class=\"spinnerClass\" aria-hidden=\"true\" />\n <span>{{ resolvedLoadingText }}</span>\n </span>\n </slot>\n </td>\n </tr>\n </template>\n <template v-else-if=\"isEmpty\">\n <tr data-gr-datatable-empty>\n <td :colspan=\"totalColumns\" class=\"text-center text-[var(--gr-muted-fg)]\" :class=\"placeholderClass\">\n <slot name=\"empty\">\n {{ resolvedEmptyText }}\n </slot>\n </td>\n </tr>\n </template>\n <template v-else>\n <!--\n Распорки виртуального списка. У таблицы они строки, а не псевдоэлементы:\n `<tbody>` игнорирует `padding`, а псевдоэлемент внутри группы строк не\n образует строку с управляемой высотой. Форма та же, что у служебных\n строк загрузки и пустоты.\n -->\n <tr\n v-if=\"virtual && spacerBefore > 0\"\n aria-hidden=\"true\"\n data-gr-datatable-spacer=\"before\"\n :style=\"{ pointerEvents: 'none' }\"\n >\n <td :colspan=\"totalColumns\" :style=\"{ height: `${spacerBefore}px`, padding: '0', border: '0' }\" />\n </tr>\n\n <tr\n v-for=\"{ row, index } in renderedRows\"\n :key=\"rowKeyValue(row)\"\n :ref=\"(el) => virtual && virtualizer.measure(index, el as Element | null)\"\n class=\"border-t border-[var(--gr-brd)]\"\n :class=\"[\n isRowSelected(row) ? 'bg-[color-mix(in_srgb,var(--gr-primary)_8%,transparent)]' : '',\n rowClassName(row, index),\n ]\"\n data-gr-datatable-row\n :data-row-key=\"rowKeyValue(row)\"\n :data-selected=\"selectable && isRowSelected(row) ? 'true' : undefined\"\n :aria-rowindex=\"ariaRowIndex(index)\"\n v-bind=\"rowProps?.(row, index)\"\n @click=\"onRowClick(row, index, $event)\"\n >\n <td v-if=\"selectable\" class=\"text-left\" :class=\"[selectColumnClass, cellClass]\">\n <!-- `.stop` на самом чекбоксе, а не на ячейке: гасить навигационный\n клик есть смысл только там, где стоит контрол выбора. Иначе\n служебная ячейка невыбираемой строки становится мёртвой зоной. -->\n <GrCheckbox\n v-if=\"isRowSelectable(row)\"\n data-gr-datatable-select-row\n :model-value=\"isRowSelected(row)\"\n :size=\"checkboxSize\"\n :aria-label=\"t('gr.dataTable.selectRow', 'Select row')\"\n @click.stop\n @update:model-value=\"toggleRow(row)\"\n />\n </td>\n <td\n v-for=\"col in columns\"\n :key=\"col.key\"\n :class=\"[cellClass, cellAlign(col)]\"\n >\n <slot :name=\"`cell-${col.key}`\" :row=\"row\" :index=\"index\">\n <span>{{ cellValue(row, col.key) }}</span>\n </slot>\n </td>\n </tr>\n\n <tr\n v-if=\"virtual && spacerAfter > 0\"\n aria-hidden=\"true\"\n data-gr-datatable-spacer=\"after\"\n :style=\"{ pointerEvents: 'none' }\"\n >\n <td :colspan=\"totalColumns\" :style=\"{ height: `${spacerAfter}px`, padding: '0', border: '0' }\" />\n </tr>\n </template>\n\n <template v-if=\"$slots.footer\" #footer>\n <slot name=\"footer\" />\n </template>\n </GrTable>\n</template>\n","\n<script setup lang=\"ts\" generic=\"TRow extends Record<string, unknown> = Record<string, unknown>\">\nimport { computed, nextTick, onMounted, ref, useId } from 'vue'\n\nimport GrTable from '../GrTable/GrTable.vue'\nimport GrIcon from '../GrIcon/GrIcon.vue'\nimport GrCheckbox from '../GrCheckbox/GrCheckbox.vue'\nimport { useGrComponentSize } from '../GrConfigProvider/context'\nimport { useVirtualList } from '../../composables/useVirtualList'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { sortRows, type GrDataTableSortDir } from './grDataTableSort'\nimport {\n type GrDataTableSize,\n cellPaddings,\n headerGaps,\n headerTextSizes,\n placeholderPaddings,\n rowHeightEstimates,\n selectCheckboxSizes,\n selectColumnWidths,\n sortIconSizes,\n spinnerSizes,\n} from './grDataTableStyles'\n\nimport IconArrowUp from '~icons/lucide/arrow-up'\nimport IconArrowDown from '~icons/lucide/arrow-down'\n\n/**\n * Ключ колонки. Собственные поля строки подсказываются автодополнением, но\n * произвольная строка тоже допустима: колонка может быть вычисляемой и жить\n * только в слоте `#cell-<key>`.\n */\nexport type GrDataColumnKey<TRow extends Record<string, unknown> = Record<string, unknown>> =\n | Extract<keyof TRow, string>\n | (string & {})\n\nexport type GrDataColumn<TRow extends Record<string, unknown> = Record<string, unknown>> = {\n key: GrDataColumnKey<TRow>\n label: string\n sortable?: boolean\n align?: 'left' | 'center' | 'right'\n /**\n * Ширина колонки: число — пиксели, строка — как есть (`'40%'`, `'12rem'`).\n *\n * Уезжает в заголовочную ячейку: при фиксированной раскладке ширины колонок\n * задаёт первая строка таблицы, а это и есть `<thead>`.\n */\n width?: string | number\n}\n\nexport type GrDataTableRowKey<TRow extends Record<string, unknown> = Record<string, unknown>> =\n | string\n | ((row: TRow) => string | number)\n\n/** Порядок обхода состояний сортировки по клику на заголовок. */\nexport type GrDataTableSortCycle = 'asc-desc' | 'asc-desc-none'\n\nexport interface GrDataTableProps<TRow extends Record<string, unknown> = Record<string, unknown>> {\n rows: TRow[]\n columns: GrDataColumn<TRow>[]\n /** Ключ строки или функция-резолвер. По умолчанию — поле `'id'`. */\n rowKey?: GrDataTableRowKey<TRow>\n /** Ключ колонки для начальной сортировки (uncontrolled-режим). */\n initialSortKey?: string\n /** Направление начальной сортировки (uncontrolled-режим). */\n initialSortDir?: GrDataTableSortDir\n /** Контролируемый ключ сортировки: `v-model:sortKey`. Задаёт controlled-режим. */\n sortKey?: string\n /** Контролируемое направление сортировки: `v-model:sortDir`. */\n sortDir?: GrDataTableSortDir\n /** Клик по заголовку: `asc → desc` или `asc → desc → без сортировки`. */\n sortCycle?: GrDataTableSortCycle\n /**\n * Внешняя сортировка (например серверная): компонент НЕ сортирует `rows`\n * сам, а только сообщает о смене через `update:sortKey`/`update:sortDir`/\n * `sortChange`. `rows` при этом должны приходить уже отсортированными.\n */\n externalSort?: boolean\n /**\n * Выбор строк: добавляет ведущую колонку с чекбоксами (+ «выбрать все» в шапке).\n * Выбранные ключи строк — через `v-model:selected`.\n */\n selectable?: boolean\n /** Контролируемый список выбранных ключей строк: `v-model:selected`. */\n selected?: Array<string | number>\n /** Предикат «строку можно выбрать». Невыбираемые строки не попадают и в «выбрать все». */\n selectableRow?: (row: TRow) => boolean\n /**\n * Состояние загрузки: тело таблицы заменяется строкой-индикатором.\n * `empty`-состояние при этом не показывается.\n */\n loading?: boolean\n /** Текст индикатора загрузки. i18n: fallback `gr.dataTable.loading`. */\n loadingText?: string\n /** Текст пустого состояния. i18n: fallback `gr.dataTable.empty`; слот `#empty` сильнее. */\n emptyText?: string\n /** Класс строки: общий для всех либо вычисляемый по строке. */\n rowClass?: string | ((row: TRow, index: number) => string | undefined)\n /** Произвольные атрибуты строки (`data-*`, `title`, …). */\n rowProps?: (row: TRow, index: number) => Record<string, unknown> | undefined\n /**\n * Размер таблицы: кегль, паддинги ячеек, стрелки сортировки и чекбоксы.\n * Прокидывается в `GrTable`.\n */\n size?: GrDataTableSize\n // Прокси к GrTable:\n caption?: string\n ariaLabel?: string\n ariaLabelledby?: string\n regionLabel?: string\n /** Прилипающий заголовок при вертикальном скролле (нужен `maxHeight`). */\n stickyHeader?: boolean\n /**\n * Виртуализация строк: в DOM живёт только окно вокруг вьюпорта. Высоту окна\n * задаёт `maxHeight`, без него окна прокрутки не существует.\n *\n * Включает фиксированную раскладку таблицы: ширина колонки считается по\n * содержимому всех строк, а в DOM их только окно — без фиксации колонки\n * прыгали бы на каждой прокрутке. Ширины стоит задать через `width` у\n * колонок, иначе фиксированная раскладка делит место поровну.\n */\n virtual?: boolean\n /** Максимальная высота таблицы (вертикальный скролл). Число — в пикселях. */\n maxHeight?: string | number\n}\n\nexport interface GrDataTableEmits<TRow extends Record<string, unknown> = Record<string, unknown>> {\n (e: 'update:sortKey', value: string): void\n (e: 'update:sortDir', value: GrDataTableSortDir): void\n (e: 'sortChange', value: { key: string, dir: GrDataTableSortDir }): void\n (e: 'update:selected', value: Array<string | number>): void\n (e: 'rowClick', payload: { row: TRow, index: number, event: MouseEvent }): void\n}\n\n/**\n * `GrDataTable` — data-таблица поверх `GrTable` с сортировкой по клику\n * на заголовок и scoped-слотами ячеек (`#cell-<key>`), `#header-<key>`,\n * `#caption`, `#footer`, `#empty`, `#loading`.\n *\n * Сортировка, включая крайние случаи с пустыми и смешанными значениями, живёт\n * в `grDataTableSort.ts` — см. `docs/components/GrDataTable.md`.\n */\nconst props = withDefaults(defineProps<GrDataTableProps<TRow>>(), {\n rowKey: 'id' as GrDataTableRowKey<TRow>,\n initialSortKey: undefined,\n initialSortDir: 'asc',\n sortKey: undefined,\n sortDir: undefined,\n sortCycle: 'asc-desc',\n externalSort: false,\n selectable: false,\n selected: undefined,\n selectableRow: undefined,\n loading: false,\n loadingText: undefined,\n emptyText: undefined,\n rowClass: undefined,\n rowProps: undefined,\n size: undefined,\n caption: undefined,\n ariaLabel: undefined,\n ariaLabelledby: undefined,\n regionLabel: undefined,\n stickyHeader: false,\n maxHeight: undefined,\n virtual: false,\n})\n\nconst emit = defineEmits<GrDataTableEmits<TRow>>()\n\nconst { t, locale } = useGranularityTranslations()\nconst resolvedLoadingText = computed(() => props.loadingText ?? t('gr.dataTable.loading', 'Loading…'))\nconst resolvedEmptyText = computed(() => props.emptyText ?? t('gr.dataTable.empty', 'No data'))\n\n// Uncontrolled-состояние; в controlled-режиме перекрывается пропами `sortKey`/`sortDir`.\nconst internalSortKey = ref<string>(props.initialSortKey ?? '')\nconst internalSortDir = ref<GrDataTableSortDir>(props.initialSortDir)\n\nconst isSortKeyControlled = computed(() => props.sortKey !== undefined)\nconst isSortDirControlled = computed(() => props.sortDir !== undefined)\n\nconst currentSortKey = computed(() => props.sortKey ?? internalSortKey.value)\nconst currentSortDir = computed<GrDataTableSortDir>(() => props.sortDir ?? internalSortDir.value)\n\nfunction applySort(key: string, dir: GrDataTableSortDir): void {\n if (!isSortKeyControlled.value)\n internalSortKey.value = key\n if (!isSortDirControlled.value)\n internalSortDir.value = dir\n\n emit('update:sortKey', key)\n emit('update:sortDir', dir)\n emit('sortChange', { key, dir })\n}\n\nconst sortedRows = computed(() => {\n // Внешняя сортировка: `rows` уже отсортированы потребителем — не трогаем.\n if (props.externalSort)\n return [...props.rows]\n\n const key = currentSortKey.value\n if (!key)\n return [...props.rows]\n\n return sortRows(props.rows, key, currentSortDir.value, locale.value)\n})\n\nfunction toggleSort(col: GrDataColumn<TRow>): void {\n if (!col.sortable)\n return\n\n if (currentSortKey.value !== col.key) {\n applySort(col.key, 'asc')\n return\n }\n\n if (currentSortDir.value === 'asc') {\n applySort(col.key, 'desc')\n return\n }\n\n // Третье состояние: сортировка снимается, порядок возвращается к исходному.\n if (props.sortCycle === 'asc-desc-none') {\n applySort('', 'asc')\n return\n }\n\n applySort(col.key, 'asc')\n}\n\nfunction cellAlign(col: GrDataColumn<TRow>): string {\n if (col.align === 'right')\n return 'text-right'\n if (col.align === 'center')\n return 'text-center'\n return 'text-left'\n}\n\n/**\n * Синтетические ключи для строк, у которых поле `rowKey` пустое.\n *\n * Без них строка получила бы `String(undefined ?? '')` — **один и тот же** ключ\n * на всю таблицу: Vue переиспользовал бы DOM не по назначению, а выбор одной\n * строки помечал бы выбранными все. Ключ привязан к идентичности объекта,\n * поэтому переживает сортировку и не зависит от индекса.\n */\nconst syntheticKeys = new WeakMap<object, string>()\nlet syntheticKeyCounter = 0\nconst missingKeyWarned = ref(false)\n\nfunction syntheticRowKey(row: TRow): string {\n const existing = syntheticKeys.get(row)\n if (existing !== undefined)\n return existing\n\n syntheticKeyCounter += 1\n const generated = `gr-row-${syntheticKeyCounter}`\n syntheticKeys.set(row, generated)\n\n if (!missingKeyWarned.value && process.env.NODE_ENV !== 'production') {\n missingKeyWarned.value = true\n console.warn(\n `[GrDataTable] У строки нет значения по ключу \"${String(props.rowKey)}\". `\n + 'Задайте `rowKey` (поле или функцию) — иначе выбор строк и переиспользование '\n + 'DOM работают по синтетическому ключу, который не переживёт перезагрузку данных.',\n )\n }\n\n return generated\n}\n\nfunction rowKeyValue(row: TRow): string | number {\n const rk = props.rowKey\n if (typeof rk === 'function')\n return rk(row)\n\n const value = (row as Record<string, unknown>)[rk as string]\n if (typeof value === 'string' && value !== '')\n return value\n if (typeof value === 'number')\n return value\n\n return syntheticRowKey(row)\n}\n\nfunction ariaSortFor(col: GrDataColumn<TRow>): 'ascending' | 'descending' | 'none' | undefined {\n if (!col.sortable)\n return undefined\n if (currentSortKey.value !== col.key)\n return 'none'\n return currentSortDir.value === 'asc' ? 'ascending' : 'descending'\n}\n\n/**\n * Подсказка «что сделает нажатие» — visually hidden текстом **после** подписи\n * колонки, а не `aria-label`'ом на кнопке. `aria-label` подменял бы собой имя\n * `<th>`, и AT при переходе по ячейкам вместо «Название» читала бы\n * «Отсортировано по Название по возрастанию, нажмите…». Текущее состояние\n * сортировки при этом объявляет `aria-sort` на самом `<th>`.\n */\nfunction sortHint(col: GrDataColumn<TRow>): string {\n if (currentSortKey.value !== col.key)\n return t('gr.dataTable.sortAsc', 'Activate to sort ascending')\n\n if (currentSortDir.value === 'asc')\n return t('gr.dataTable.sortDesc', 'Activate to sort descending')\n\n return props.sortCycle === 'asc-desc-none'\n ? t('gr.dataTable.sortNone', 'Activate to remove sorting')\n : t('gr.dataTable.sortAsc', 'Activate to sort ascending')\n}\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrDataTable' })\n\nconst cellClass = computed(() => cellPaddings[resolvedSize.value])\nconst placeholderClass = computed(() => placeholderPaddings[resolvedSize.value])\nconst headerTextClass = computed(() => headerTextSizes[resolvedSize.value])\nconst headerGapClass = computed(() => headerGaps[resolvedSize.value])\nconst selectColumnClass = computed(() => selectColumnWidths[resolvedSize.value])\nconst spinnerClass = computed(() => spinnerSizes[resolvedSize.value])\nconst sortIconSize = computed(() => sortIconSizes[resolvedSize.value])\nconst checkboxSize = computed(() => selectCheckboxSizes[resolvedSize.value])\n\n/**\n * Номер строки и полное их число объявляем только при виртуализации: иначе\n * диктор считает строки по разметке, а там всего окно. Заголовок — строка\n * первая, поэтому строки набора начинаются со второй.\n */\nconst ariaRowCount = computed(() => (props.virtual ? sortedRows.value.length + 1 : undefined))\n\nfunction ariaRowIndex(index: number): number | undefined {\n return props.virtual ? index + 2 : undefined\n}\n\nconst tableProps = computed(() => ({\n size: resolvedSize.value,\n ariaLabel: props.ariaLabel,\n ariaLabelledby: props.ariaLabelledby,\n regionLabel: props.regionLabel,\n stickyHeader: props.stickyHeader,\n maxHeight: props.maxHeight,\n rowCount: ariaRowCount.value,\n fixedLayout: props.virtual,\n}))\n\nconst isEmpty = computed(() => sortedRows.value.length === 0)\n\n// Общее число колонок с учётом ведущей чекбокс-колонки — для `colspan`\n// строк loading/empty.\nconst totalColumns = computed(() => props.columns.length + (props.selectable ? 1 : 0))\n\n/**\n * Живой регион существует с первого рендера и пуст, пока объявлять нечего.\n * Регион, который появляется уже с текстом, часть AT не объявляет вовсе —\n * а именно так вело себя `role=\"status\"` внутри строки загрузки.\n */\nconst liveMessage = computed(() => {\n if (props.loading)\n return resolvedLoadingText.value\n if (isEmpty.value)\n return resolvedEmptyText.value\n return ''\n})\n\nfunction cellValue(row: TRow, key: string): unknown {\n return (row as Record<string, unknown>)[key]\n}\n\nfunction rowClassName(row: TRow, index: number): string | undefined {\n const source = props.rowClass\n return typeof source === 'function' ? source(row, index) : source\n}\n\nfunction onRowClick(row: TRow, index: number, event: MouseEvent): void {\n emit('rowClick', { row, index, event })\n}\n\n// ————— Выбор строк.\n// Uncontrolled-режим, как у сортировки выше: без `v-model:selected` чекбоксы\n// рисовались, кликались и никогда не отмечались — состояние выбора целиком\n// выводилось из пропа, а внутреннего не было вовсе.\nconst internalSelected = ref<Array<string | number>>([])\nconst isSelectedControlled = computed(() => props.selected !== undefined)\n\nconst selectedKeys = computed<Set<string | number>>(\n () => new Set(props.selected ?? internalSelected.value),\n)\n\nfunction isRowSelectable(row: TRow): boolean {\n return props.selectableRow ? props.selectableRow(row) : true\n}\n\nfunction isRowSelected(row: TRow): boolean {\n return selectedKeys.value.has(rowKeyValue(row))\n}\n\n/** «Выбрать все» работает по видимым и выбираемым строкам — они же считают состояние шапки. */\nconst selectableRows = computed(() => sortedRows.value.filter(isRowSelectable))\n\nconst allSelected = computed(() =>\n selectableRows.value.length > 0 && selectableRows.value.every(isRowSelected),\n)\nconst someSelected = computed(() =>\n selectableRows.value.some(isRowSelected) && !allSelected.value,\n)\n\nfunction emitSelected(next: Set<string | number>): void {\n if (!isSelectedControlled.value)\n internalSelected.value = [...next]\n\n emit('update:selected', [...next])\n}\n\nfunction toggleRow(row: TRow): void {\n if (!isRowSelectable(row))\n return\n\n const key = rowKeyValue(row)\n const next = new Set(selectedKeys.value)\n if (next.has(key))\n next.delete(key)\n else\n next.add(key)\n emitSelected(next)\n}\n\nfunction toggleAll(): void {\n const next = new Set(selectedKeys.value)\n\n if (allSelected.value) {\n // Снимаем выбор только с видимых строк, сохраняя внешние ключи.\n for (const row of selectableRows.value) next.delete(rowKeyValue(row))\n emitSelected(next)\n return\n }\n\n for (const row of selectableRows.value) next.add(rowKeyValue(row))\n emitSelected(next)\n}\n\n// ————— Императивный API.\nconst tableRef = ref<InstanceType<typeof GrTable> | null>(null)\nconst rootId = useId()\n\nfunction rootEl(): HTMLElement | null {\n return (tableRef.value?.$el as HTMLElement | undefined) ?? null\n}\n\n/** Скролл-контейнер таблицы — он же элемент с `maxHeight`. */\nfunction scrollEl(): HTMLElement | null {\n const root = rootEl()\n return root?.matches('[data-gr-table-scroll]') ? root : root?.querySelector('[data-gr-table-scroll]') ?? null\n}\n\n/**\n * Виртуализация строк.\n *\n * Распорки здесь не псевдоэлементы, как у списочных компонентов: `<tbody>`\n * игнорирует `padding`, а псевдоэлемент внутри группы строк не образует строку\n * с управляемой высотой. Поэтому срезанное держат две служебные `<tr>` — той же\n * формы, что строки загрузки и пустоты у `GrTable`.\n */\nconst scrollContainer = ref<HTMLElement | null>(null)\n\nonMounted(() => {\n scrollContainer.value = scrollEl()\n})\n\nconst virtualizer = useVirtualList({\n container: scrollContainer,\n count: () => (props.virtual ? sortedRows.value.length : 0),\n // Сортировка и замена rows раздают индексы заново — замеры сбрасываются.\n source: () => sortedRows.value,\n itemSize: () => rowHeightEstimates[resolvedSize.value],\n // Скролл-контейнер живёт в `GrTable` и до монтирования недоступен: окно\n // первого рендера считается от объявленной высоты.\n viewportSize: () => (typeof props.maxHeight === 'number' ? props.maxHeight : undefined),\n})\n\n/** Строки к отрисовке вместе с их абсолютной позицией в наборе. */\nconst renderedRows = computed(() => {\n const rows = sortedRows.value\n if (!props.virtual) return rows.map((row, index) => ({ row, index }))\n\n const { start, end } = virtualizer.range.value\n return rows.slice(start, end).map((row, offset) => ({ row, index: start + offset }))\n})\n\nconst spacerBefore = computed(() => (props.virtual ? virtualizer.offset.value : 0))\nconst spacerAfter = computed(() => (props.virtual ? virtualizer.offsetEnd.value : 0))\n\n/** Ширина колонки: число трактуем как пиксели. */\nfunction columnWidthStyle(width: string | number | undefined): Record<string, string> | undefined {\n if (width === undefined) return undefined\n return { width: typeof width === 'number' ? `${width}px` : width }\n}\n\nfunction scrollToRow(key: string | number, options?: ScrollIntoViewOptions): boolean {\n // Ключ строки — произвольная строка от потребителя, в селектор её не подставить:\n // сравниваем через `dataset`, а не собираем `[data-row-key=\"…\"]` конкатенацией.\n const target = String(key)\n\n // Строки вне окна в DOM нет: без прокрутки виртуального списка поиск по\n // `dataset` вернул бы `undefined`, и метод молча отвечал бы «не нашёл».\n if (props.virtual) {\n const index = sortedRows.value.findIndex(row => String(rowKeyValue(row)) === target)\n if (index < 0) return false\n\n virtualizer.scrollToIndex(index)\n void nextTick(() => {\n // Вызов опциональный: отложенный `scrollIntoView` некому поймать, а в\n // среде без него (jsdom) это дало бы необработанный отказ промиса.\n findRowEl(target)?.scrollIntoView?.(options ?? { block: 'nearest' })\n })\n return true\n }\n\n const row = findRowEl(target)\n if (!row)\n return false\n\n row.scrollIntoView(options ?? { block: 'nearest' })\n return true\n}\n\nfunction findRowEl(rowKey: string): HTMLElement | undefined {\n // Ключ строки — произвольная строка от потребителя, в селектор её не\n // подставить: сравниваем через `dataset`, а не собираем селектор конкатенацией.\n const rows = rootEl()?.querySelectorAll<HTMLElement>('[data-gr-datatable-row]') ?? []\n return Array.prototype.find.call(rows, (el: HTMLElement) => el.dataset.rowKey === rowKey) as HTMLElement | undefined\n}\n\nconst scrollTo = (options: ScrollToOptions): void => {\n scrollEl()?.scrollTo(options)\n}\n\nfunction clearSort(): void {\n applySort('', 'asc')\n}\n\ndefineExpose({\n /** Прокрутить к строке по её ключу. `false` — строки нет в DOM. */\n scrollToRow,\n /** Прокрутить скролл-контейнер таблицы. */\n scrollTo,\n /** Снять сортировку (эквивалент третьего состояния). */\n clearSort,\n /** Отметить/снять все выбираемые строки. */\n toggleAll,\n})\n</script>\n\n<template>\n <GrTable\n v-bind=\"tableProps\"\n ref=\"tableRef\"\n data-gr-datatable\n >\n <!-- Caption рендерится всегда (у `GrTable` он `sr-only`): в нём живёт\n постоянный live-регион, иначе объявлять загрузку было бы нечему. -->\n <template #caption>\n <slot name=\"caption\">\n {{ caption }}\n </slot>\n <span\n :id=\"`${rootId}-live`\"\n data-gr-datatable-live\n role=\"status\"\n aria-live=\"polite\"\n >{{ liveMessage }}</span>\n </template>\n\n <template #header>\n <tr data-gr-datatable-header :aria-rowindex=\"virtual ? 1 : undefined\">\n <th\n v-if=\"selectable\"\n class=\"text-left\"\n :class=\"[selectColumnClass, cellClass]\"\n scope=\"col\"\n >\n <GrCheckbox\n data-gr-datatable-select-all\n :model-value=\"allSelected\"\n :indeterminate=\"someSelected\"\n :size=\"checkboxSize\"\n :disabled=\"loading || selectableRows.length === 0\"\n :aria-label=\"t('gr.dataTable.selectAll', 'Select all rows')\"\n @update:model-value=\"toggleAll\"\n />\n </th>\n <th\n v-for=\"col in columns\"\n :key=\"col.key\"\n class=\"font-700\"\n :class=\"[headerTextClass, cellClass, cellAlign(col)]\"\n :style=\"columnWidthStyle(col.width)\"\n :aria-sort=\"ariaSortFor(col)\"\n scope=\"col\"\n >\n <div class=\"inline-flex items-center\" :class=\"headerGapClass\">\n <button\n v-if=\"col.sortable\"\n type=\"button\"\n data-gr-datatable-sort\n class=\"inline-flex items-center text-[var(--gr-muted-fg)] hover:text-[var(--gr-fg)] transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--gr-ring)] rounded-[var(--gr-radius-sm)]\"\n :class=\"headerGapClass\"\n @click=\"toggleSort(col)\"\n >\n <span>\n <slot :name=\"`header-${col.key}`\" :column=\"col\">\n {{ col.label }}\n </slot>\n </span>\n <span class=\"sr-only\">{{ sortHint(col) }}</span>\n <span class=\"inline-flex\" aria-hidden=\"true\">\n <GrIcon v-if=\"currentSortKey === col.key && currentSortDir === 'asc'\" :size=\"sortIconSize\">\n <IconArrowUp />\n </GrIcon>\n <GrIcon v-else-if=\"currentSortKey === col.key && currentSortDir === 'desc'\" :size=\"sortIconSize\">\n <IconArrowDown />\n </GrIcon>\n </span>\n </button>\n <span v-else class=\"text-[var(--gr-muted-fg)]\">\n <slot :name=\"`header-${col.key}`\" :column=\"col\">\n {{ col.label }}\n </slot>\n </span>\n </div>\n </th>\n </tr>\n </template>\n\n <template v-if=\"loading\">\n <tr data-gr-datatable-loading>\n <td :colspan=\"totalColumns\" class=\"text-center text-[var(--gr-muted-fg)]\" :class=\"placeholderClass\">\n <slot name=\"loading\">\n <span class=\"inline-flex items-center gap-2\">\n <span class=\"i-lucide-loader-circle block animate-spin\" :class=\"spinnerClass\" aria-hidden=\"true\" />\n <span>{{ resolvedLoadingText }}</span>\n </span>\n </slot>\n </td>\n </tr>\n </template>\n <template v-else-if=\"isEmpty\">\n <tr data-gr-datatable-empty>\n <td :colspan=\"totalColumns\" class=\"text-center text-[var(--gr-muted-fg)]\" :class=\"placeholderClass\">\n <slot name=\"empty\">\n {{ resolvedEmptyText }}\n </slot>\n </td>\n </tr>\n </template>\n <template v-else>\n <!--\n Распорки виртуального списка. У таблицы они строки, а не псевдоэлементы:\n `<tbody>` игнорирует `padding`, а псевдоэлемент внутри группы строк не\n образует строку с управляемой высотой. Форма та же, что у служебных\n строк загрузки и пустоты.\n -->\n <tr\n v-if=\"virtual && spacerBefore > 0\"\n aria-hidden=\"true\"\n data-gr-datatable-spacer=\"before\"\n :style=\"{ pointerEvents: 'none' }\"\n >\n <td :colspan=\"totalColumns\" :style=\"{ height: `${spacerBefore}px`, padding: '0', border: '0' }\" />\n </tr>\n\n <tr\n v-for=\"{ row, index } in renderedRows\"\n :key=\"rowKeyValue(row)\"\n :ref=\"(el) => virtual && virtualizer.measure(index, el as Element | null)\"\n class=\"border-t border-[var(--gr-brd)]\"\n :class=\"[\n isRowSelected(row) ? 'bg-[color-mix(in_srgb,var(--gr-primary)_8%,transparent)]' : '',\n rowClassName(row, index),\n ]\"\n data-gr-datatable-row\n :data-row-key=\"rowKeyValue(row)\"\n :data-selected=\"selectable && isRowSelected(row) ? 'true' : undefined\"\n :aria-rowindex=\"ariaRowIndex(index)\"\n v-bind=\"rowProps?.(row, index)\"\n @click=\"onRowClick(row, index, $event)\"\n >\n <td v-if=\"selectable\" class=\"text-left\" :class=\"[selectColumnClass, cellClass]\">\n <!-- `.stop` на самом чекбоксе, а не на ячейке: гасить навигационный\n клик есть смысл только там, где стоит контрол выбора. Иначе\n служебная ячейка невыбираемой строки становится мёртвой зоной. -->\n <GrCheckbox\n v-if=\"isRowSelectable(row)\"\n data-gr-datatable-select-row\n :model-value=\"isRowSelected(row)\"\n :size=\"checkboxSize\"\n :aria-label=\"t('gr.dataTable.selectRow', 'Select row')\"\n @click.stop\n @update:model-value=\"toggleRow(row)\"\n />\n </td>\n <td\n v-for=\"col in columns\"\n :key=\"col.key\"\n :class=\"[cellClass, cellAlign(col)]\"\n >\n <slot :name=\"`cell-${col.key}`\" :row=\"row\" :index=\"index\">\n <span>{{ cellValue(row, col.key) }}</span>\n </slot>\n </td>\n </tr>\n\n <tr\n v-if=\"virtual && spacerAfter > 0\"\n aria-hidden=\"true\"\n data-gr-datatable-spacer=\"after\"\n :style=\"{ pointerEvents: 'none' }\"\n >\n <td :colspan=\"totalColumns\" :style=\"{ height: `${spacerAfter}px`, padding: '0', border: '0' }\" />\n </tr>\n </template>\n\n <template v-if=\"$slots.footer\" #footer>\n <slot name=\"footer\" />\n </template>\n </GrTable>\n</template>\n"],"mappings":"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"}
|