@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":"useOverlayLayer-BupuP_hs.js","names":[],"sources":["../../src/composables/internal/overlayStack.ts","../../src/composables/useOverlayLayer.ts"],"sourcesContent":["/**\n * Единый стек слоёв оверлеев: модалки, drawer'ы, просмотрщик, панели селектов,\n * дропдауны, подсказки — всё, что открывается поверх страницы.\n *\n * Список один, и порядок регистрации совпадает с порядком открытия, но из него\n * выводятся **две разные вершины**:\n *\n * - **Esc** → последний слой любого рода. Дропдаун, открытый внутри модалки,\n * обязан закрыться первым — пользователь видит верхним именно его.\n * - **`inert`** → ставится на модальные слои ниже последнего **модального**.\n * Немодальный слой сверху модалку не понижает: иначе окно ушло бы в `inert`\n * вместе со своим же открытым дропдауном и перестало отвечать.\n *\n * Вести эти две вершины в двух независимых реестрах нельзя: реестры описывают\n * один и тот же порядок и расходятся молча — достаточно оверлея, который\n * зарегистрировался в одном и забыл про другой.\n *\n * Ловушку фокуса стек не реализует сам, но питает её (`useFocusTrap`): он\n * видит слои из **разных деревьев рендера** (диалоги `useDialogService`\n * монтируются отдельным `render()` в `body`) и знает их корни.\n */\n\nimport { isComposingEvent } from '../../internal/keyboard'\n\nexport interface OverlayLayer {\n /** Уникальный id зарегистрированного слоя. */\n id: number\n /**\n * Модальный слой: блокирует страницу и участвует в вычислении `inert`.\n * Немодальные (поповеры, меню, подсказки) — только в очереди Esc.\n */\n modal: boolean\n /** Закрывать ли слой по Esc (реактивный геттер: `closeOnEsc` и подобные). */\n shouldClose: () => boolean\n /** Закрыть слой. */\n close: () => void\n /** Уведомление «этот модальный слой сейчас верхний». Только для `modal`. */\n setTopmost?: (isTopmost: boolean) => void\n /**\n * Корень слоя. Ловушка фокуса нижнего слоя обязана считать его своим:\n * панель селекта, открытого внутри модалки, телепортирована в `body` и лежит\n * вне DOM-поддерева окна, но для пользователя это тот же слой.\n */\n root?: () => HTMLElement | null\n}\n\nconst stack: OverlayLayer[] = []\nlet listening = false\nlet nextId = 1\n\n/** Пересчитывает верхний **модальный** слой и уведомляет все модальные. */\nfunction syncTopmost(): void {\n const modals = stack.filter(layer => layer.modal)\n const top = modals[modals.length - 1]\n for (const layer of modals) layer.setTopmost?.(layer === top)\n}\n\nfunction handleKeydown(event: KeyboardEvent): void {\n if (event.key !== 'Escape') return\n // Esc во время IME-композиции отменяет её, а не закрывает слой.\n if (isComposingEvent(event)) return\n if (stack.length === 0) return\n\n const top = stack[stack.length - 1]\n\n // Гасим всегда, даже если верхний слой не закрывается по Esc: иначе нажатие\n // «провалится» на слой ниже и закроет не то, что видит пользователь.\n event.preventDefault()\n event.stopPropagation()\n if (typeof event.stopImmediatePropagation === 'function')\n event.stopImmediatePropagation()\n\n if (top.shouldClose()) top.close()\n}\n\nfunction startListening(): void {\n if (listening) return\n if (typeof window === 'undefined') return\n // Capture-фаза опережает любые локальные обработчики: Esc обязан достаться\n // верхнему слою, а не тому, кто первым подписался.\n window.addEventListener('keydown', handleKeydown, true)\n listening = true\n}\n\nfunction stopListening(): void {\n if (!listening) return\n if (typeof window === 'undefined') return\n window.removeEventListener('keydown', handleKeydown, true)\n listening = false\n}\n\n/** Регистрирует слой как верхний и возвращает его id. */\nexport function pushOverlayLayer(layer: Omit<OverlayLayer, 'id'>): number {\n const id = nextId++\n stack.push({ ...layer, id })\n startListening()\n syncTopmost()\n return id\n}\n\n/** Снимает слой со стека (при закрытии или размонтировании). */\nexport function removeOverlayLayer(id: number): void {\n const index = stack.findIndex(item => item.id === id)\n if (index >= 0) stack.splice(index, 1)\n if (stack.length === 0) stopListening()\n syncTopmost()\n}\n\n/**\n * Корни слоёв, открытых **поверх** данного.\n *\n * Ими ловушка фокуса дополняет свой контейнер: всё, что открыто изнутри слоя\n * (панель селекта, меню, подсказка), лежит в `body` отдельным поддеревом, и\n * без этого списка ловушка утаскивала бы из него фокус обратно в окно.\n */\nexport function layerRootsAbove(id: number): HTMLElement[] {\n const index = stack.findIndex(layer => layer.id === id)\n if (index < 0) return []\n\n return stack\n .slice(index + 1)\n .map(layer => layer.root?.() ?? null)\n .filter((root): root is HTMLElement => root !== null)\n}\n\n/** Корни всех зарегистрированных слоёв. */\nexport function allLayerRoots(): HTMLElement[] {\n return stack\n .map(layer => layer.root?.() ?? null)\n .filter((root): root is HTMLElement => root !== null)\n}\n\n/** Сколько слоёв открыто. Для тестов и диагностики. */\nexport function overlayStackSize(): number {\n return stack.length\n}\n\n/** Тестовая/служебная очистка стека. */\nexport function resetOverlayStack(): void {\n stack.splice(0, stack.length)\n stopListening()\n}\n","import type { Ref } from 'vue'\nimport { nextTick, onUnmounted, watch } from 'vue'\n\nimport { layerRootsAbove, pushOverlayLayer, removeOverlayLayer } from './internal/overlayStack'\n\nexport interface UseOverlayLayerOptions {\n /**\n * Модальный слой: блокирует страницу, и модалки под ним получают `inert`.\n * Немодальные (поповеры, меню, подсказки) участвуют только в очереди Esc.\n * По умолчанию `false`.\n *\n * Геттером — там, где модальность включается пропом (`GrDrawer`): значение\n * читается в момент регистрации, то есть при каждом открытии.\n */\n modal?: boolean | (() => boolean)\n /**\n * Закрывать ли слой по Esc. Геттер, а не значение, — чтобы читать реактивный\n * проп (`closeOnEsc`). По умолчанию закрывается всегда.\n *\n * Если геттер вернёт `false`, нажатие всё равно не пройдёт на слой ниже:\n * пользователь видит верхним этот слой, и Esc адресован ему.\n */\n closeOnEscape?: () => boolean\n /**\n * Уведомление «этот модальный слой сейчас верхний». Компонент вешает по нему\n * `inert` на свой корень, когда перестал быть верхним.\n */\n onTopmostChange?: (isTopmost: boolean) => void\n /**\n * Возвращать фокус элементу, который был активен до открытия. По умолчанию —\n * да. Отключать имеет смысл там, где фокус с триггера и не уходил: `GrSelect`\n * ведёт список через `aria-activedescendant`.\n */\n restoreFocus?: boolean\n /**\n * Корень слоя. Нужен дважды: для правила возврата фокуса (если на момент\n * закрытия фокус уже вне слоя, пользователь ушёл сам — забирать нельзя) и\n * для ловушки фокуса слоя под ним, которая обязана считать этот корень\n * своим (панель селекта внутри модалки телепортирована в `body`).\n */\n root?: Ref<HTMLElement | null>\n}\n\nexport interface OverlayLayerHandle {\n /**\n * Корни слоёв, открытых поверх этого. Передаётся в `useFocusTrap` как\n * `containers`: пока панель, открытая изнутри окна, жива, фокус в ней\n * законен.\n */\n rootsAbove: () => HTMLElement[]\n}\n\n/**\n * Регистрирует оверлей в общем стеке слоёв, пока `open` истинно.\n *\n * Один контракт на порядок Esc, `inert` и возврат фокуса — три механизма,\n * которые обязаны видеть один и тот же порядок слоёв. Что берёт на себя:\n *\n * - **Esc** — верхнему слою, вниз не проваливается;\n * - **`inert`** — через `onTopmostChange` у модальных слоёв;\n * - **возврат фокуса** — по единому правилу (см. ниже).\n *\n * Чего **не** делает: фокус-ловушку — это `useFocusTrap`, отдельный примитив.\n * Поповеру ловушка и не нужна: Tab обязан уводить фокус наружу и закрывать его,\n * поэтому связка «слой + ловушка» собирается в компоненте, а не тут.\n *\n * Правило возврата фокуса: восстанавливаем, **только если на момент закрытия\n * фокус всё ещё внутри слоя**. Если пользователь кликнул в другое поле, фокус\n * уже там — забирать его обратно нельзя. Эвристика «возвращать, если открыли с\n * клавиатуры» промахивается в обе стороны: мышью открытый слой фокус не вернёт,\n * а клавиатурный отберёт его у того, куда пользователь ушёл сам.\n */\nexport function useOverlayLayer(\n open: Ref<boolean>,\n onDismiss: () => void,\n options: UseOverlayLayerOptions = {},\n): OverlayLayerHandle {\n let layerId: number | null = null\n let previouslyFocused: HTMLElement | null = null\n\n function register(): void {\n if (layerId !== null) return\n\n // Стек — браузерное понятие: Esc, `inert` и возврат фокуса на сервере\n // бессмысленны. Регистрируйся слой и там — он остался бы в модульном массиве\n // навсегда, потому что `onUnmounted` при `renderToString` не вызывается, и\n // каждый серверный рендер открытого оверлея копил бы замыкание со ссылками\n // на свои компоненты.\n if (typeof window === 'undefined') return\n\n if (options.restoreFocus !== false && typeof document !== 'undefined')\n previouslyFocused = (document.activeElement as HTMLElement) ?? null\n\n layerId = pushOverlayLayer({\n modal: typeof options.modal === 'function' ? options.modal() : options.modal ?? false,\n shouldClose: () => options.closeOnEscape?.() ?? true,\n close: onDismiss,\n setTopmost: options.onTopmostChange,\n root: () => options.root?.value ?? null,\n })\n }\n\n function unregister(): void {\n if (layerId === null) return\n removeOverlayLayer(layerId)\n layerId = null\n\n restoreFocus()\n }\n\n function restoreFocus(): void {\n const target = previouslyFocused\n previouslyFocused = null\n\n if (options.restoreFocus === false || !target) return\n if (typeof document === 'undefined') return\n\n // Фокус уже вне слоя — пользователь ушёл сам, отбирать нельзя. Решение\n // принимаем сейчас, пока слой ещё в DOM, а применяем следующим тиком.\n const active = document.activeElement\n const root = options.root?.value\n if (root && active && active !== document.body && !root.contains(active)) return\n\n // Возврат откладывается намеренно: ловушка фокуса того же слоя снимает свои\n // слушатели в этом же флаше, и сфокусируй мы триггер сразу — она затащила бы\n // фокус обратно в закрывающийся слой.\n void nextTick(() => {\n if (target.isConnected) target.focus?.()\n })\n }\n\n watch(\n open,\n (isOpen) => {\n if (isOpen) register()\n else unregister()\n },\n { immediate: true },\n )\n\n onUnmounted(unregister)\n\n return {\n rootsAbove: () => (layerId === null ? [] : layerRootsAbove(layerId)),\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"GrAutocomplete-CQ9T7Lly.js","names":[],"sources":["../../../../src/components/GrAutocomplete/GrAutocomplete.vue","../../../../src/components/GrAutocomplete/GrAutocomplete.vue"],"sourcesContent":["<script setup lang=\"ts\" generic=\"TValue extends GrAutocompleteValue = string\">\nimport { computed, nextTick, onBeforeUnmount, ref, shallowRef, useId, watch } from 'vue'\n\nimport { usePortalTarget } from '../../composables/usePortalTarget'\n\nimport { useGrComponentProp, useGrComponentSize, useGrThemeAttrs } from '../GrConfigProvider/context'\n\nimport { vClickOutside } from '../../directives'\nimport { useFloating } from '../../composables/useFloating'\nimport { useDismissible } from '../../composables/useDismissible'\nimport { useVirtualList } from '../../composables/useVirtualList'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { isComposingEvent } from '../../internal/keyboard'\nimport { useGrFormFieldContext } from '../GrFormField/context'\nimport { useGrFormControl } from '../../composables/useGrFormControl'\nimport { useFocusWithin } from '../../composables/internal/useFocusWithin'\nimport { useControlledOpen } from '../../composables/internal/useControlledOpen'\nimport { useComboboxNavigation } from '../../composables/internal/useComboboxNavigation'\nimport { filterOptions, resolveSelectedOptions } from '../shared/optionFilter'\nimport { useControlAddons } from '../../composables/internal/useControlAddons'\n\nimport GrBadge from '../GrBadge/GrBadge.vue'\nimport type { GrBadgeRadius, GrBadgeSize, GrBadgeTone } from '../GrBadge/grBadgeStyles'\n\nimport {\n autocompleteOptionClass,\n autocompletePanelClasses,\n autocompleteShellClass,\n autocompleteStateClass,\n type GrAutocompleteModelValue,\n type GrAutocompleteOption,\n type GrAutocompleteValue,\n type GrAutocompleteSize,\n} from './grAutocompleteStyles'\n\nexport type {\n GrAutocompleteModelValue,\n GrAutocompleteOption,\n GrAutocompleteSize,\n GrAutocompleteValue,\n} from './grAutocompleteStyles'\n\n/**\n * Публичный GR-примитив «Autocomplete / Combobox» (WAI-ARIA editable combobox).\n *\n * В отличие от `GrSelect` (select-only combobox с кнопкой-триггером), здесь\n * combobox-ом является сам текстовый `<input>`: набранный текст — это поисковый\n * запрос, а выбор опции заполняет поле. Поддерживает локальную фильтрацию,\n * удалённую (async) загрузку через событие `search` + внешний проп `loading`,\n * произвольные значения (`allowCustomValue`) и multiple с удаляемыми chips.\n */\nexport interface GrAutocompleteProps<TValue extends GrAutocompleteValue = string> {\n /** Выбранное значение (single — строка, multiple — массив строк). */\n modelValue: GrAutocompleteModelValue<TValue>\n /**\n * Доступные опции. Для локального режима — полный список (фильтруется на клиенте).\n * Для remote-режима (`filterable=false`) — список, который родитель обновляет\n * в ответ на событие `search`.\n */\n options?: GrAutocompleteOption<TValue>[]\n multiple?: boolean\n /**\n * Вид чипов выбранных значений в режиме `multiple`. Чип — это `GrBadge`, а не\n * своя плашка: на светлой теме собственная заливка чипа (примесь `--gr-muted`\n * без рамки) давала 1.02:1 к фону поля, то есть была невидима.\n */\n tagTone?: GrBadgeTone\n tagDark?: boolean\n tagSize?: GrBadgeSize\n tagRadius?: GrBadgeRadius\n disabled?: boolean\n /** Только для чтения: значение видно и уходит в форму, но не редактируется. */\n readonly?: boolean\n /** Визуальное и ARIA-состояние ошибки. */\n invalid?: boolean\n /** Обязательное поле (`aria-required`). */\n required?: boolean\n size?: GrAutocompleteSize\n placeholder?: string\n ariaLabel?: string\n /** Кнопка очистки выбранного значения/запроса. */\n clearable?: boolean\n /** Внешне управляемое состояние загрузки (для async-сценариев). */\n loading?: boolean\n /**\n * Локальная фильтрация опций по введённому запросу. Отключите (`false`) для\n * чисто удалённого поиска — тогда `options` показываются как есть, а фильтрацию\n * выполняет сервер по событию `search`.\n */\n filterable?: boolean\n /** Кастомный матчер локальной фильтрации. По умолчанию — подстрока в `label`/`value`. */\n filter?: (option: GrAutocompleteOption<TValue>, query: string) => boolean\n /**\n * Удалённая загрузка опций под управлением компонента: дебаунс, отмена\n * устаревшего запроса и `loading` берёт на себя он. Ответ на отменённый\n * запрос игнорируется — при быстром вводе в списке всегда результат\n * последнего запроса, а не того, который вернулся позже.\n *\n * `signal` пробрасывается в `fetch`. Локальная фильтрация в этом режиме\n * выключена: список фильтрует сервер. Альтернатива — событие `search`, если\n * запрос ведёт само приложение.\n */\n fetchOptions?: (query: string, signal: AbortSignal) => Promise<GrAutocompleteOption<TValue>[]>\n /** Минимальная длина запроса до эмита `search` (для дебаунса remote-загрузки). */\n minQueryLength?: number\n /** Задержка дебаунса события `search`, мс. */\n debounce?: number\n /** Разрешить ввод/коммит значения, которого нет в `options`. */\n allowCustomValue?: boolean\n /** Закрывать панель после выбора (single всегда закрывает). */\n closeOnSelect?: boolean\n /** Максимальная высота панели, px. */\n dropdownMaxHeight?: number\n /**\n * Виртуализация панели: в DOM живёт только окно вокруг вьюпорта.\n *\n * Высоту окна задаёт `dropdownMaxHeight`. Включается осознанно: на сотне\n * опций выигрыша нет, а в разметке остаётся только окно — вместе с ним\n * меняется и то, что находит `querySelector` потребителя. Профильный\n * сценарий — удалённый поиск по справочнику на тысячи позиций.\n */\n virtual?: boolean\n /** i18n-тексты состояний панели / aria. */\n loadingText?: string\n noResultsText?: string\n clearLabel?: string\n /**\n * Контролируемое состояние панели (`v-model:open`). Без пропа панель ведёт\n * себя сама (uncontrolled), с ним — слушайте `update:open` и меняйте проп.\n */\n open?: boolean\n /** Имя для нативной формы: hidden input по значению модели (не по тексту запроса). */\n name?: string\n /**\n * Ширины аддонов `prefix`/`suffix` — общий контракт контролов пакета\n * (`docs/form-controls.md`).\n */\n prefixMinWidth?: string\n prefixMaxWidth?: string\n suffixMinWidth?: string\n suffixMaxWidth?: string\n prefixFixed?: boolean\n suffixFixed?: boolean\n}\n\nexport interface GrAutocompleteEmits<TValue extends GrAutocompleteValue = string> {\n (e: 'update:modelValue', value: GrAutocompleteModelValue<TValue>): void\n /** Дебаунснутый поисковый запрос — точка входа для удалённой загрузки опций. */\n (e: 'search', query: string): void\n /** Запрос `fetchOptions` завершился ошибкой (отмена устаревшего сюда не приходит). */\n (e: 'searchError', error: unknown): void\n /** Значение зафиксировано выбором или снятием опции. */\n (e: 'change', value: GrAutocompleteModelValue<TValue>): void\n /** Панель открылась/закрылась (`v-model:open`). */\n (e: 'update:open', value: boolean): void\n /** Значение снято кнопкой очистки; только при `clearable`. */\n (e: 'clear'): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n}\n\nconst props = withDefaults(\n defineProps<GrAutocompleteProps<TValue>>(),\n {\n options: undefined,\n multiple: false,\n tagTone: 'neutral',\n tagDark: false,\n // `sm`, а не `md` как у `GrInputTag`: чип живёт внутри контрола, а не в тексте.\n tagSize: 'sm',\n tagRadius: 'round',\n disabled: false,\n readonly: false,\n invalid: false,\n required: false,\n size: undefined,\n placeholder: undefined,\n ariaLabel: undefined,\n clearable: undefined,\n loading: false,\n filterable: true,\n filter: undefined,\n fetchOptions: undefined,\n minQueryLength: 0,\n debounce: 250,\n allowCustomValue: false,\n closeOnSelect: true,\n dropdownMaxHeight: 280,\n virtual: false,\n loadingText: undefined,\n noResultsText: undefined,\n clearLabel: undefined,\n open: undefined,\n name: undefined,\n prefixMinWidth: undefined,\n prefixMaxWidth: undefined,\n suffixMinWidth: undefined,\n suffixMaxWidth: undefined,\n prefixFixed: false,\n suffixFixed: false,\n },\n)\n\n/** Ширина аддона по умолчанию — та же лестница, что у `GrInput`. */\nconst ADDON_MIN_WIDTH_BY_SIZE: Record<GrAutocompleteSize, string> = {\n xs: '2rem',\n sm: '2.25rem',\n md: '2.5rem',\n lg: '3rem',\n}\n\n// Эффективный размер: локальный проп → `GrConfigProvider` → дефолт компонента.\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrAutocomplete' })\nconst resolvedClearable = useGrComponentProp('GrAutocomplete', 'clearable', () => props.clearable, false)\n\n/**\n * Аддоны живут флекс-соседями поля, а не поверх него: оболочка автокомплита —\n * флекс-строка с чипами, поэтому резервировать место паддингом не нужно.\n * Контракт ширин остаётся общим (`useControlAddons`).\n */\nconst { hasPrefix, hasSuffix, prefixEl, suffixEl, prefixStyle, suffixStyle } = useControlAddons(() => props, {\n defaultMinWidth: () => ADDON_MIN_WIDTH_BY_SIZE[resolvedSize.value],\n paddingX: () => '0px',\n})\n\nconst emit = defineEmits<GrAutocompleteEmits<TValue>>()\ndefineSlots<{\n /** Аддон слева от поля ввода. */\n prefix?: () => any\n /** Аддон справа, перед спиннером и крестиком. */\n suffix?: () => any\n /** Строка списка вместо подписи опции. */\n option?: (props: { option: GrAutocompleteOption<TValue>, selected: boolean }) => any\n /** Содержимое панели, пока едут опции. */\n loading?: () => any\n /** Содержимое панели, когда подходящих опций нет. */\n empty?: () => any\n}>()\n\n\nconst { t } = useGranularityTranslations()\n\nconst resolvedLoadingText = computed(() => props.loadingText ?? t('gr.autocomplete.loading', 'Loading…'))\nconst resolvedNoResultsText = computed(() => props.noResultsText ?? t('gr.autocomplete.noResults', 'No results'))\nconst resolvedClearLabel = computed(() => props.clearLabel ?? t('gr.common.clear', 'Clear'))\n// Число уходит под двумя общепринятыми именами: `n` читают `@feugene/fint-i18n`\n// и `vue-i18n`, `count` — `i18next`. Формы для каждого языка лежат в словаре,\n// выбирает их переводчик; встроенный fallback — одна английская строка.\nconst resolvedTypeMoreText = computed(() =>\n t('gr.autocomplete.typeMore', 'Type at least {n} characters', {\n n: props.minQueryLength,\n count: props.minQueryLength,\n }),\n)\n\n// Fallback из контекста `GrFormField` (id/aria-describedby/invalid/required).\nconst field = useGrFormFieldContext()\nconst resolvedId = computed(() => field?.id.value)\nconst {\n disabled: isDisabled,\n invalid: isInvalid, required: isRequired, readonly: isReadonly } = useGrFormControl(() => props)\nconst describedBy = computed(() => field?.describedById.value)\n\n// Read-only запирает контрол так же, как disabled: значение видно, но панель не\n// открывается, опции не выбираются и чипы не удаляются. Без этого `readonly`\n// оставался бы ARIA-атрибутом без поведения.\nconst locked = computed(() => isDisabled.value || isReadonly.value)\n\n// Ответ последнего `fetchOptions`. До первого ответа показываем `options` —\n// с ними компонент рисует стартовый список, не дожидаясь сервера.\nconst remoteOptions = shallowRef<GrAutocompleteOption<TValue>[]>([])\nconst remoteAnswered = ref(false)\nconst remoteLoading = ref(false)\n\nconst optionsResolved = computed<GrAutocompleteOption<TValue>[]>(() =>\n props.fetchOptions && remoteAnswered.value ? remoteOptions.value : (props.options ?? []),\n)\n\n/**\n * Подпись состава стартового списка. Сравнивать идентичность массива нельзя:\n * инлайн-литерал `:options=\"[...]\"` пересоздаётся каждым ререндером родителя —\n * в том числе тем, который вызвал сам компонент своим `update:modelValue`, —\n * и remote-результаты исчезали бы прямо посреди выбора.\n */\nfunction optionsSignature(options: GrAutocompleteOption<TValue>[] | undefined): string {\n return (options ?? []).map(o => `${String(o.value)}\\u0000${o.label}`).join('\\u0001')\n}\n\n// Родитель сменил стартовый список — он снова источник до следующего ответа\n// сервера, а летящий запрос относится к прежнему набору данных и отменяется.\nwatch(() => optionsSignature(props.options), () => {\n if (!props.fetchOptions) return\n cancelSearch()\n remoteAnswered.value = false\n remoteOptions.value = []\n})\n\nconst isLoading = computed(() => props.loading || remoteLoading.value)\n\n/** `0` — валидное значение, поэтому «пусто» проверяется явно, а не через falsy. */\nfunction isEmptyValue(value: unknown): boolean {\n return value === undefined || value === null || value === ''\n}\n\nfunction toArray(value: GrAutocompleteModelValue<TValue>): TValue[] {\n if (Array.isArray(value)) return value\n if (isEmptyValue(value)) return []\n return [value]\n}\n\nconst modelSingle = computed<TValue | ''>(() => {\n const raw = Array.isArray(props.modelValue) ? props.modelValue[0] : props.modelValue\n return isEmptyValue(raw) ? '' : (raw as TValue)\n})\nconst selectedValues = computed(() => (props.multiple ? toArray(props.modelValue) : (isEmptyValue(modelSingle.value) ? [] : [modelSingle.value as TValue])))\nconst hasSelection = computed(() => selectedValues.value.length > 0)\n\nfunction labelFor(value: GrAutocompleteValue): string {\n return optionsResolved.value.find(o => o.value === value)?.label ?? String(value)\n}\n\n/** Опции выбранных значений (для chips в multiple). Неизвестные значения показываем как есть. */\nconst selectedOptions = computed<GrAutocompleteOption<TValue>[]>(() =>\n resolveSelectedOptions(selectedValues.value, optionsResolved.value),\n)\n\nconst singleSelectedLabel = computed(() => (props.multiple || isEmptyValue(modelSingle.value) ? '' : labelFor(modelSingle.value)))\n\n// ————— Состояние.\nconst query = ref('')\n// `dirty` — пользователь начал вводить запрос. Нужно, чтобы в single-режиме при\n// открытии заполненного поля показать весь список (а не отфильтровать по метке\n// уже выбранной опции). Сбрасывается при программной установке `query`.\nconst dirty = ref(false)\n// Имя `open` сохранено: читатели работают с computed-Ref.\nconst { open, setOpen } = useControlledOpen(\n () => props.open,\n next => emit('update:open', next),\n)\n\nconst rootEl = ref<HTMLElement | null>(null)\nconst panelEl = ref<HTMLElement | null>(null)\nconst listboxEl = ref<HTMLElement | null>(null)\n\n// Панель телепортирована в `body`, то есть лежит вне корня, но для пользователя\n// она часть контрола: без неё уход фокуса в панель читался бы как `blur`.\nconst { onFocusIn, onFocusOut } = useFocusWithin(rootEl, {\n enter: event => emit('focus', event),\n leave: event => emit('blur', event),\n containers: () => [panelEl.value],\n})\nconst inputEl = ref<HTMLInputElement | null>(null)\n\nfunction focus(): void {\n inputEl.value?.focus()\n}\n\nfunction blur(): void {\n inputEl.value?.blur()\n}\n\ndefineExpose({\n focus,\n blur,\n open: () => openDropdown(),\n close: () => closeDropdown(),\n})\nconst clickOutsideExclude = [() => panelEl.value]\n\nconst { floatingStyle } = useFloating(rootEl, panelEl, open, {\n placement: 'bottom-start',\n matchWidth: true,\n zIndexVar: '--gr-z-dropdown',\n})\n\nuseDismissible(open, closeDropdown)\n\n// ————— Фильтрация.\nconst searchQuery = computed(() => {\n // При `fetchOptions` фильтрует сервер: локальный матчер отсеял бы то, что он\n // уже прислал в ответ на этот же запрос.\n if (!props.filterable || props.fetchOptions) return ''\n // single: пока пользователь не начал вводить — показываем весь список.\n if (!props.multiple && !dirty.value) return ''\n return query.value.trim()\n})\n\nconst filteredOptions = computed<GrAutocompleteOption<TValue>[]>(() =>\n filterOptions(optionsResolved.value, searchQuery.value, props.filter),\n)\n\nconst belowMinQuery = computed(() => props.minQueryLength > 0 && query.value.trim().length < props.minQueryLength)\n\n/**\n * Опции к показу и навигации. Ниже `minQueryLength` — пусто: список ещё\n * относится к прошлому запросу, показывать его под подсказкой «введите ещё N»\n * значит дезинформировать.\n */\nconst effectiveOptions = computed<GrAutocompleteOption<TValue>[]>(() =>\n belowMinQuery.value ? [] : filteredOptions.value,\n)\n\nconst canAddCustom = computed(() => {\n if (!props.allowCustomValue) return false\n // Кастомное значение набирается текстом — оно строковое по природе;\n // при числовом `TValue` эта ветка неприменима (см. docs/components.md).\n const v = query.value.trim() as TValue\n if (!v) return false\n if (props.multiple && selectedValues.value.includes(v)) return false\n if (!props.multiple && v === modelSingle.value) return false\n // Не предлагаем «Add», если такое значение/метка уже есть среди опций.\n return !optionsResolved.value.some(o => o.value === v || o.label === v)\n})\n\nconst showEmpty = computed(() =>\n !isLoading.value && effectiveOptions.value.length === 0 && !canAddCustom.value,\n)\n\n// ————— Панель: id/aria-activedescendant.\nconst listboxId = useId()\n\n/**\n * Id опции строится от позиции, а не от значения: значение с пробелом дало бы\n * невалидный `id`, а `aria-activedescendant` — два токена вместо одной ссылки,\n * и активная опция перестала бы объявляться.\n */\nfunction optionDomId(index: number): string {\n return `${listboxId}-opt-${index}`\n}\nconst addOptionDomId = computed(() => `${listboxId}-add`)\n\n/**\n * Виртуализация панели.\n *\n * Набор — `[«Add …»?] + filteredOptions`, тот же, по которому ходит клавиатура\n * (`navigableItems` ниже). Иначе верхняя распорка вытолкнула бы строку «Add …»\n * вниз: она отрисована внутри listbox'а первой и является полноценной опцией.\n */\n\n/** Оценка высоты опции: `py-2` вокруг строки кегля `sm`. Уточняется замером. */\nconst OPTION_SIZE_ESTIMATE = 36\n\nconst addOffset = computed(() => (canAddCustom.value ? 1 : 0))\nconst virtualCount = computed(() => effectiveOptions.value.length + addOffset.value)\n\nconst virtualizer = useVirtualList({\n container: listboxEl,\n count: () => (props.virtual ? virtualCount.value : 0),\n // Фильтрация/remote-ответ пересобирают набор — замеры прошлого невалидны.\n source: () => effectiveOptions.value,\n itemSize: OPTION_SIZE_ESTIMATE,\n // Панель скрыта `v-show`, пока закрыта, поэтому `clientHeight` контейнера —\n // ноль. Окно считается от объявленной высоты до первого настоящего замера.\n viewportSize: () => props.dropdownMaxHeight,\n})\n\n/** Виден ли «Add …»: вне виртуального окна его рисовать нельзя — он элемент набора. */\nconst showAddOption = computed(() => {\n if (!canAddCustom.value) return false\n return !props.virtual || virtualizer.range.value.start === 0\n})\n\n/** Опции к отрисовке вместе с их абсолютным индексом в `filteredOptions`. */\nconst renderedOptions = computed(() => {\n const all = effectiveOptions.value\n if (!props.virtual) return all.map((option, index) => ({ option, index }))\n\n const { start, end } = virtualizer.range.value\n const from = Math.max(0, start - addOffset.value)\n const to = Math.max(0, end - addOffset.value)\n\n return all.slice(from, to).map((option, offset) => ({ option, index: from + offset }))\n})\n\n/**\n * Размер набора и позиция в нём объявляются только при виртуализации: в обычном\n * режиме диктор выводит их из DOM, а при неполном наборе получил бы «3 из 15»\n * на списке в десять тысяч.\n */\nfunction optionSetProps(virtualIndex: number): Record<string, number> | undefined {\n if (!props.virtual) return undefined\n return { 'aria-setsize': virtualCount.value, 'aria-posinset': virtualIndex + 1 }\n}\n\nconst listboxStyle = computed(() => {\n const base: Record<string, string> = { maxHeight: `${props.dropdownMaxHeight}px` }\n if (!props.virtual) return base\n\n return {\n ...base,\n ...virtualizer.spacerStyle.value,\n }\n})\n\n/**\n * Клавиатурная навигация ходит и по опциям, и по варианту «добавить своё»:\n * иначе при непустом списке Enter всегда уходил бы в активную опцию, и\n * закоммитить произвольное значение с клавиатуры было бы нечем.\n */\ntype NavigableItem =\n | { kind: 'add' }\n | { kind: 'option', option: GrAutocompleteOption<TValue>, index: number }\n\nconst navigableItems = computed<NavigableItem[]>(() => {\n const items: NavigableItem[] = canAddCustom.value ? [{ kind: 'add' }] : []\n effectiveOptions.value.forEach((option, index) => {\n if (!option.disabled) items.push({ kind: 'option', option, index })\n })\n return items\n})\n\nfunction isSelected(value: TValue): boolean {\n return selectedValues.value.includes(value)\n}\n\nfunction navigableIndexOf(value: TValue): number {\n return navigableItems.value.findIndex(item => item.kind === 'option' && item.option.value === value)\n}\n\n/** Прокрутка к активному: сперва окно виртуального списка, затем доводка. */\nasync function scrollActiveIntoView(item: NavigableItem): Promise<void> {\n // Вне окна активной опции в DOM нет: `getElementById` вернул бы `null`,\n // прокрутка не случилась бы, а `aria-activedescendant` указал бы в пустоту.\n if (props.virtual)\n virtualizer.scrollToIndex(item.kind === 'add' ? 0 : item.index + addOffset.value)\n\n await nextTick()\n const id = item.kind === 'add' ? addOptionDomId.value : optionDomId(item.index)\n document.getElementById(id)?.scrollIntoView?.({ block: 'nearest' })\n}\n\nconst {\n activeIndex,\n activeItem,\n activeDescendantId,\n init: initActiveIndex,\n reset: resetActive,\n handleNavigationKeys,\n} = useComboboxNavigation<NavigableItem>({\n items: () => navigableItems.value,\n open: () => open.value,\n idOf: item => (item.kind === 'add' ? addOptionDomId.value : optionDomId(item.index)),\n initialIndex: () => navigableItems.value.findIndex(item => item.kind === 'option' && isSelected(item.option.value)),\n scrollTo: item => scrollActiveIntoView(item),\n})\n\nconst activeValue = computed(() => (activeItem.value?.kind === 'option' ? activeItem.value.option.value : undefined))\n\n// ————— Открытие/закрытие.\nfunction openDropdown(): void {\n if (locked.value || open.value) return\n setOpen(true)\n}\n\nfunction closeDropdown(): void {\n setOpen(false)\n}\n\nfunction focusInput(): void {\n inputEl.value?.focus()\n}\n\n// ————— Дебаунснутый поиск для remote-загрузки.\nlet searchTimer: ReturnType<typeof setTimeout> | null = null\n// Счётчик запросов: ответ, стартовавший раньше, но пришедший позже, обязан\n// проиграть последнему — иначе список показывает результат старого запроса.\nlet searchSeq = 0\nlet inflight: AbortController | null = null\n\nfunction isAbortError(error: unknown): boolean {\n return error instanceof Error && error.name === 'AbortError'\n}\n\nasync function runFetch(query: string): Promise<void> {\n const fetchOptions = props.fetchOptions\n if (!fetchOptions) return\n\n inflight?.abort()\n const controller = new AbortController()\n inflight = controller\n const seq = ++searchSeq\n remoteLoading.value = true\n\n try {\n const result = await fetchOptions(query, controller.signal)\n if (seq !== searchSeq) return\n remoteOptions.value = result\n remoteAnswered.value = true\n }\n catch (error) {\n if (seq !== searchSeq || isAbortError(error)) return\n remoteOptions.value = []\n remoteAnswered.value = true\n emit('searchError', error)\n }\n finally {\n if (seq === searchSeq) remoteLoading.value = false\n }\n}\n\nfunction scheduleSearch(value: string): void {\n if (searchTimer) clearTimeout(searchTimer)\n searchTimer = setTimeout(() => {\n if (props.minQueryLength > 0 && value.trim().length < props.minQueryLength) return\n emit('search', value.trim())\n void runFetch(value.trim())\n }, props.debounce)\n}\n\n/**\n * Снять запланированный и летящий запрос. Инкремент `searchSeq` обязателен\n * вместе со снятием `remoteLoading`: после него `finally` в `runFetch` считает\n * себя устаревшим и флаг не тронет — спиннер остался бы навсегда.\n */\nfunction cancelSearch(): void {\n if (searchTimer) clearTimeout(searchTimer)\n searchTimer = null\n inflight?.abort()\n inflight = null\n searchSeq += 1\n remoteLoading.value = false\n}\n\nonBeforeUnmount(cancelSearch)\n\n// ————— Ввод.\nfunction onInput(event: Event): void {\n const value = (event.target as HTMLInputElement).value\n query.value = value\n dirty.value = true\n openDropdown()\n scheduleSearch(value)\n}\n\nfunction onFocus(): void {\n if (locked.value) return\n openDropdown()\n}\n\nfunction setQuery(value: string): void {\n query.value = value\n dirty.value = false\n}\n\n// ————— Выбор значений.\nfunction selectSingle(value: TValue, label: string): void {\n emit('update:modelValue', value)\n emit('change', value as GrAutocompleteModelValue<TValue>)\n setQuery(label)\n closeDropdown()\n}\n\nfunction toggleMultiple(value: TValue): void {\n const next = selectedValues.value.slice()\n const idx = next.indexOf(value)\n if (idx >= 0) next.splice(idx, 1)\n else next.push(value)\n emit('update:modelValue', next)\n emit('change', next as GrAutocompleteModelValue<TValue>)\n setQuery('')\n if (props.closeOnSelect) closeDropdown()\n else void nextTick(focusInput)\n}\n\nfunction chooseOption(option: GrAutocompleteOption<TValue>): void {\n if (locked.value || option.disabled) return\n if (props.multiple) toggleMultiple(option.value)\n else selectSingle(option.value, option.label)\n}\n\nfunction commitCustom(): void {\n if (locked.value) return\n // Кастомное значение строковое по природе — см. `canAddCustom`.\n const v = query.value.trim() as TValue\n if (!v) return\n if (props.multiple) toggleMultiple(v)\n else selectSingle(v, String(v))\n}\n\nfunction removeValue(value: TValue, focusAfter: () => void = focusInput): void {\n if (locked.value) return\n const next = selectedValues.value.filter(v => v !== value)\n emit('update:modelValue', next)\n emit('change', next as GrAutocompleteModelValue<TValue>)\n void nextTick(focusAfter)\n}\n\nfunction clearSelection(): void {\n if (locked.value) return\n const next = (props.multiple ? [] : '') as GrAutocompleteModelValue<TValue>\n emit('update:modelValue', next)\n emit('change', next)\n emit('clear')\n setQuery('')\n void nextTick(focusInput)\n}\n\nconst showClear = computed(() =>\n resolvedClearable.value && !locked.value && (hasSelection.value || query.value.length > 0),\n)\n\n// ————— Чипы (multiple): стрелки вместо таб-стопов.\n//\n// Крестики намеренно не табируемы: у combobox фокус живёт на `<input>`, и\n// двадцать выбранных значений не должны давать двадцать остановок Tab. Поэтому\n// вход в чипы — ArrowLeft из пустого запроса, а выход — ArrowRight/Esc/печать.\nfunction chipRemoveButtons(): HTMLButtonElement[] {\n return [...(rootEl.value?.querySelectorAll<HTMLButtonElement>('[data-gr-autocomplete-chip-remove]') ?? [])]\n}\n\nfunction focusChip(index: number): void {\n const buttons = chipRemoveButtons()\n if (!buttons.length) {\n focusInput()\n return\n }\n buttons[Math.min(Math.max(index, 0), buttons.length - 1)].focus()\n}\n\nfunction onChipKeydown(event: KeyboardEvent, index: number, value: TValue): void {\n switch (event.key) {\n case 'ArrowLeft':\n event.preventDefault()\n focusChip(index - 1)\n break\n case 'ArrowRight':\n event.preventDefault()\n if (index >= chipRemoveButtons().length - 1) focusInput()\n else focusChip(index + 1)\n break\n case 'Home':\n event.preventDefault()\n focusChip(0)\n break\n case 'End':\n event.preventDefault()\n focusChip(chipRemoveButtons().length - 1)\n break\n case 'Delete':\n case 'Backspace':\n event.preventDefault()\n removeValue(value, () => focusChip(index))\n break\n case 'Escape':\n event.preventDefault()\n focusInput()\n break\n default:\n // Печатный символ возвращает в поле: фокус переезжает синхронно, до\n // вставки текста, поэтому символ попадает в запрос, а не пропадает.\n if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) focusInput()\n }\n}\n\n// ————— Клавиатура.\nfunction onKeydown(event: KeyboardEvent): void {\n // Клавиша во время IME-композиции принадлежит композиции: Enter коммитит её,\n // Esc отменяет, стрелки ходят по кандидатам.\n if (isComposingEvent(event)) return\n if (locked.value) return\n\n // Курсор в начале пустого запроса — стрелка влево уходит к чипам, а не\n // двигает каретку, которой всё равно некуда двигаться.\n if (event.key === 'ArrowLeft' && props.multiple && query.value === '' && selectedValues.value.length) {\n event.preventDefault()\n closeDropdown()\n focusChip(selectedValues.value.length - 1)\n return\n }\n\n if (!open.value && ['ArrowDown', 'ArrowUp'].includes(event.key)) {\n event.preventDefault()\n openDropdown()\n return\n }\n\n // Только при открытой панели: в закрытом инпуте Home/End двигают каретку.\n if (open.value && handleNavigationKeys(event)) return\n\n switch (event.key) {\n case 'Enter': {\n if (!open.value) break\n event.preventDefault()\n const item = activeItem.value\n if (item?.kind === 'option') chooseOption(item.option)\n else if (item?.kind === 'add' || canAddCustom.value) commitCustom()\n break\n }\n case 'Backspace':\n // multiple: пустой запрос + Backspace удаляет последний chip.\n if (props.multiple && query.value === '' && selectedValues.value.length) {\n event.preventDefault()\n removeValue(selectedValues.value[selectedValues.value.length - 1])\n }\n break\n case 'Tab':\n closeDropdown()\n break\n }\n}\n\n// ————— Синхронизация query с выбранным значением (single).\nwatch(\n [singleSelectedLabel, () => props.multiple],\n ([label, multiple]) => {\n if (multiple) return\n // Не перетираем то, что пользователь сейчас набирает.\n if (open.value || dirty.value) return\n setQuery(label)\n },\n { immediate: true },\n)\n\nwatch(open, (isOpen) => {\n if (isOpen) {\n initActiveIndex()\n return\n }\n // Закрытие: сбрасываем активную опцию и «черновик».\n resetActive()\n if (props.multiple) {\n setQuery('')\n }\n else {\n // single: возвращаем метку выбранной опции (revert ввода без выбора).\n setQuery(singleSelectedLabel.value)\n }\n})\n\nconst ariaAutocomplete = computed(() => (props.allowCustomValue ? 'both' : 'list'))\n\n// Телепорт включается только ПОСЛЕ монтирования: иначе первый клиентский\n// рендер не совпадает с серверным и ломается гидрация (см. композабл).\nconst { target: portalTarget, enabled: teleportEnabled } = usePortalTarget()\n\n// Тема поддерева на телепортированную панель: в DOM она уезжает в `body`, то\n// есть вне обёртки провайдера, и `data-theme` с неё не наследуется. В дереве\n// компонентов панель остаётся внутри — `inject` доходит, и тему она ставит себе\n// сама.\nconst themeAttrs = useGrThemeAttrs()\n</script>\n\n<template>\n <div\n ref=\"rootEl\"\n v-click-outside=\"{ handler: closeDropdown, enabled: open, exclude: clickOutsideExclude }\"\n data-gr-autocomplete\n class=\"relative w-full\"\n @focusin=\"onFocusIn\"\n @focusout=\"onFocusOut\"\n >\n <div\n data-gr-autocomplete-shell\n :class=\"autocompleteShellClass({ size: resolvedSize, disabled: isDisabled, invalid: isInvalid })\"\n @mousedown.self.prevent=\"focusInput\"\n >\n <span\n v-if=\"hasPrefix\"\n ref=\"prefixEl\"\n data-gr-autocomplete-prefix\n class=\"inline-flex shrink-0 items-center justify-center text-[var(--gr-muted-fg)]\"\n :style=\"prefixStyle\"\n >\n <slot name=\"prefix\" />\n </span>\n\n <!-- Chips выбранных значений (multiple). -->\n <template v-if=\"multiple\">\n <GrBadge\n v-for=\"(option, chipIndex) in selectedOptions\"\n :key=\"option.value\"\n data-gr-autocomplete-chip\n :tone=\"tagTone\"\n :dark=\"tagDark\"\n :size=\"tagSize\"\n :radius=\"tagRadius\"\n >\n <span class=\"inline-flex max-w-full items-center gap-1 align-middle\">\n <span class=\"truncate\">{{ option.label }}</span>\n <button\n v-if=\"!locked\"\n type=\"button\"\n data-gr-autocomplete-chip-remove\n class=\"inline-flex h-3.5 w-3.5 shrink-0 items-center justify-center rounded-[var(--gr-radius-full)] text-current focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--gr-ring)]\"\n :aria-label=\"t('gr.autocomplete.removeValue', 'Remove {label}', { label: option.label })\"\n tabindex=\"-1\"\n @click=\"removeValue(option.value)\"\n @keydown=\"onChipKeydown($event, chipIndex, option.value)\"\n >\n <span class=\"i-lucide-x block h-3 w-3\" aria-hidden=\"true\" />\n </button>\n </span>\n </GrBadge>\n </template>\n\n <input\n :id=\"resolvedId\"\n ref=\"inputEl\"\n data-gr-autocomplete-input\n data-testid=\"gr-autocomplete-input\"\n type=\"text\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n role=\"combobox\"\n :value=\"query\"\n :disabled=\"isDisabled\"\n :placeholder=\"hasSelection && multiple ? undefined : placeholder\"\n :aria-label=\"ariaLabel\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n :aria-describedby=\"describedBy\"\n :aria-required=\"isRequired ? 'true' : undefined\"\n :aria-readonly=\"isReadonly ? 'true' : undefined\"\n :readonly=\"isReadonly\"\n aria-haspopup=\"listbox\"\n :aria-autocomplete=\"ariaAutocomplete\"\n :aria-controls=\"open ? listboxId : undefined\"\n :aria-activedescendant=\"activeDescendantId\"\n :aria-expanded=\"open ? 'true' : 'false'\"\n class=\"min-w-0 flex-1 bg-transparent text-inherit placeholder:text-[var(--gr-muted-fg)] focus:outline-none disabled:cursor-not-allowed\"\n @input=\"onInput\"\n @focus=\"onFocus\"\n @keydown=\"onKeydown\"\n >\n\n <span\n v-if=\"hasSuffix\"\n ref=\"suffixEl\"\n data-gr-autocomplete-suffix\n class=\"inline-flex shrink-0 items-center justify-center text-[var(--gr-muted-fg)]\"\n :style=\"suffixStyle\"\n >\n <slot name=\"suffix\" />\n </span>\n\n <!-- Trailing: спиннер / очистка / шеврон. -->\n <span\n v-if=\"isLoading\"\n data-gr-autocomplete-spinner\n class=\"shrink-0 flex items-center text-[var(--gr-muted-fg)]\"\n >\n <span class=\"i-lucide-loader-2 block h-4 w-4 animate-spin\" aria-hidden=\"true\" />\n </span>\n\n <button\n v-else-if=\"showClear\"\n type=\"button\"\n data-testid=\"gr-autocomplete-clear\"\n data-gr-autocomplete-clear\n class=\"shrink-0 inline-flex h-5 w-5 items-center justify-center rounded-[var(--gr-radius-control)] text-[var(--gr-muted-fg)] hover:text-[var(--gr-fg)]\"\n :aria-label=\"resolvedClearLabel\"\n @click=\"clearSelection\"\n >\n <span class=\"i-lucide-x block h-4 w-4\" aria-hidden=\"true\" />\n </button>\n\n <span\n v-else\n data-testid=\"gr-autocomplete-chevron\"\n class=\"shrink-0 flex items-center text-[var(--gr-muted-fg)] pointer-events-none\"\n >\n <span class=\"i-lucide-chevron-down block h-4 w-4\" aria-hidden=\"true\" />\n </span>\n </div>\n\n <!-- Нативная форма: сериализуется модель, а не текст запроса. -->\n <template v-if=\"name\">\n <input\n v-for=\"value in selectedValues\"\n :key=\"`hidden-${String(value)}`\"\n type=\"hidden\"\n :name=\"name\"\n :value=\"String(value)\"\n >\n </template>\n\n <teleport :to=\"portalTarget\" :disabled=\"!teleportEnabled\">\n <transition\n enter-active-class=\"transition ease-[var(--gr-ease-out)] duration-[var(--gr-duration-fast)]\"\n enter-from-class=\"transform opacity-0 scale-95\"\n enter-to-class=\"transform opacity-100 scale-100\"\n leave-active-class=\"transition ease-[var(--gr-ease-in)] duration-[var(--gr-duration-fast)]\"\n leave-from-class=\"transform opacity-100 scale-100\"\n leave-to-class=\"transform opacity-0 scale-95\"\n >\n <div\n v-show=\"open\"\n ref=\"panelEl\"\n data-testid=\"gr-autocomplete-panel\"\n v-bind=\"themeAttrs\"\n data-gr-autocomplete-panel\n data-gr-overlay-root\n :style=\"floatingStyle\"\n >\n <div :class=\"autocompletePanelClasses\">\n <!--\n Пустой listbox не рендерим: его `p-1` давал полосу пустоты над\n строкой состояния, и «ничего не найдено» выглядело прижатым к низу.\n -->\n <div\n v-if=\"showAddOption || renderedOptions.length\"\n :id=\"listboxId\"\n ref=\"listboxEl\"\n data-gr-autocomplete-listbox\n :data-gr-virtual=\"virtual ? '' : undefined\"\n class=\"p-1 overflow-auto\"\n :class=\"virtual ? 'flex flex-col' : ''\"\n :style=\"listboxStyle\"\n role=\"listbox\"\n :aria-multiselectable=\"multiple ? 'true' : undefined\"\n >\n <!--\n Прямые потомки listbox — только опции: роль объявляет остальных\n недопустимыми детьми (`aria-required-children`), поэтому строки\n состояний живут ниже, вне списка. `mousedown.prevent` держит\n фокус на `<input>` — контракт combobox с `aria-activedescendant`.\n -->\n <button\n v-if=\"showAddOption\"\n :id=\"addOptionDomId\"\n type=\"button\"\n role=\"option\"\n aria-selected=\"false\"\n tabindex=\"-1\"\n v-bind=\"optionSetProps(0)\"\n data-testid=\"gr-autocomplete-add-option\"\n data-gr-autocomplete-add-option\n :class=\"autocompleteOptionClass({ disabled: false, active: activeItem?.kind === 'add' })\"\n @mousedown.prevent\n @click=\"commitCustom\"\n @mousemove=\"activeIndex = 0\"\n >\n {{ t('gr.autocomplete.addOption', 'Add \"{value}\"', { value: query.trim() }) }}\n </button>\n\n <button\n v-for=\"{ option, index: optionIndex } in renderedOptions\"\n :id=\"optionDomId(optionIndex)\"\n :key=\"option.value\"\n :ref=\"(el) => virtual && virtualizer.measure(optionIndex + addOffset, el as Element | null)\"\n data-gr-autocomplete-option\n type=\"button\"\n role=\"option\"\n tabindex=\"-1\"\n v-bind=\"optionSetProps(optionIndex + addOffset)\"\n :disabled=\"option.disabled\"\n :aria-selected=\"isSelected(option.value) ? 'true' : 'false'\"\n :aria-disabled=\"option.disabled ? 'true' : undefined\"\n :class=\"autocompleteOptionClass({\n disabled: !!option.disabled,\n active: activeValue === option.value,\n })\"\n @mousedown.prevent\n @click=\"chooseOption(option)\"\n @mousemove=\"activeIndex = navigableIndexOf(option.value)\"\n >\n <slot name=\"option\" :option=\"option\" :selected=\"isSelected(option.value)\">\n <span class=\"flex items-center gap-2 min-w-0\">\n <span\n class=\"inline-block h-4 w-4 shrink-0\"\n :class=\"isSelected(option.value) ? 'i-lucide-check text-[var(--gr-primary)]' : ''\"\n aria-hidden=\"true\"\n />\n <span class=\"truncate\">{{ option.label }}</span>\n </span>\n </slot>\n </button>\n </div>\n\n <!--\n Загрузка, «введите ещё N» и «ничего не найдено» меняются молча:\n `aria-label` на generic-элементе AT игнорируют. Один живой регион\n на все три — и вне listbox, чтобы не ломать его состав.\n -->\n <div\n v-if=\"isLoading || belowMinQuery || showEmpty\"\n data-gr-autocomplete-status\n role=\"status\"\n aria-live=\"polite\"\n :class=\"autocompleteStateClass\"\n >\n <template v-if=\"isLoading\">\n <span data-gr-autocomplete-loading class=\"flex items-center gap-2\">\n <slot name=\"loading\">\n <span class=\"i-lucide-loader-2 block h-4 w-4 animate-spin\" aria-hidden=\"true\" />\n <span>{{ resolvedLoadingText }}</span>\n </slot>\n </span>\n </template>\n <template v-else-if=\"belowMinQuery\">\n <span data-gr-autocomplete-hint>{{ resolvedTypeMoreText }}</span>\n </template>\n <template v-else>\n <span data-gr-autocomplete-empty>\n <slot name=\"empty\">\n {{ resolvedNoResultsText }}\n </slot>\n </span>\n </template>\n </div>\n </div>\n </div>\n </transition>\n </teleport>\n </div>\n</template>\n\n<style scoped>\n/*\n * Распорки виртуального списка.\n *\n * Не `padding` контейнера: `max-height` меряет ту же коробку, и распорка в\n * десятки тысяч пикселей раздула бы панель. Не обёртка внутри listbox'а:\n * прямыми потомками роли обязаны быть только опции (`aria-required-children`,\n * гейт — `GrAutocomplete.a11y.test.ts`). Не отступы крайних опций: при прыжке\n * прокрутки опции заменяются целиком, распорка исчезла бы вместе с ними, и\n * браузер обрезал бы `scrollTop`.\n *\n * Псевдоэлементы не узлы DOM: их не за что размонтировать, детьми listbox'а\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\" generic=\"TValue extends GrAutocompleteValue = string\">\nimport { computed, nextTick, onBeforeUnmount, ref, shallowRef, useId, watch } from 'vue'\n\nimport { usePortalTarget } from '../../composables/usePortalTarget'\n\nimport { useGrComponentProp, useGrComponentSize, useGrThemeAttrs } from '../GrConfigProvider/context'\n\nimport { vClickOutside } from '../../directives'\nimport { useFloating } from '../../composables/useFloating'\nimport { useDismissible } from '../../composables/useDismissible'\nimport { useVirtualList } from '../../composables/useVirtualList'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { isComposingEvent } from '../../internal/keyboard'\nimport { useGrFormFieldContext } from '../GrFormField/context'\nimport { useGrFormControl } from '../../composables/useGrFormControl'\nimport { useFocusWithin } from '../../composables/internal/useFocusWithin'\nimport { useControlledOpen } from '../../composables/internal/useControlledOpen'\nimport { useComboboxNavigation } from '../../composables/internal/useComboboxNavigation'\nimport { filterOptions, resolveSelectedOptions } from '../shared/optionFilter'\nimport { useControlAddons } from '../../composables/internal/useControlAddons'\n\nimport GrBadge from '../GrBadge/GrBadge.vue'\nimport type { GrBadgeRadius, GrBadgeSize, GrBadgeTone } from '../GrBadge/grBadgeStyles'\n\nimport {\n autocompleteOptionClass,\n autocompletePanelClasses,\n autocompleteShellClass,\n autocompleteStateClass,\n type GrAutocompleteModelValue,\n type GrAutocompleteOption,\n type GrAutocompleteValue,\n type GrAutocompleteSize,\n} from './grAutocompleteStyles'\n\nexport type {\n GrAutocompleteModelValue,\n GrAutocompleteOption,\n GrAutocompleteSize,\n GrAutocompleteValue,\n} from './grAutocompleteStyles'\n\n/**\n * Публичный GR-примитив «Autocomplete / Combobox» (WAI-ARIA editable combobox).\n *\n * В отличие от `GrSelect` (select-only combobox с кнопкой-триггером), здесь\n * combobox-ом является сам текстовый `<input>`: набранный текст — это поисковый\n * запрос, а выбор опции заполняет поле. Поддерживает локальную фильтрацию,\n * удалённую (async) загрузку через событие `search` + внешний проп `loading`,\n * произвольные значения (`allowCustomValue`) и multiple с удаляемыми chips.\n */\nexport interface GrAutocompleteProps<TValue extends GrAutocompleteValue = string> {\n /** Выбранное значение (single — строка, multiple — массив строк). */\n modelValue: GrAutocompleteModelValue<TValue>\n /**\n * Доступные опции. Для локального режима — полный список (фильтруется на клиенте).\n * Для remote-режима (`filterable=false`) — список, который родитель обновляет\n * в ответ на событие `search`.\n */\n options?: GrAutocompleteOption<TValue>[]\n multiple?: boolean\n /**\n * Вид чипов выбранных значений в режиме `multiple`. Чип — это `GrBadge`, а не\n * своя плашка: на светлой теме собственная заливка чипа (примесь `--gr-muted`\n * без рамки) давала 1.02:1 к фону поля, то есть была невидима.\n */\n tagTone?: GrBadgeTone\n tagDark?: boolean\n tagSize?: GrBadgeSize\n tagRadius?: GrBadgeRadius\n disabled?: boolean\n /** Только для чтения: значение видно и уходит в форму, но не редактируется. */\n readonly?: boolean\n /** Визуальное и ARIA-состояние ошибки. */\n invalid?: boolean\n /** Обязательное поле (`aria-required`). */\n required?: boolean\n size?: GrAutocompleteSize\n placeholder?: string\n ariaLabel?: string\n /** Кнопка очистки выбранного значения/запроса. */\n clearable?: boolean\n /** Внешне управляемое состояние загрузки (для async-сценариев). */\n loading?: boolean\n /**\n * Локальная фильтрация опций по введённому запросу. Отключите (`false`) для\n * чисто удалённого поиска — тогда `options` показываются как есть, а фильтрацию\n * выполняет сервер по событию `search`.\n */\n filterable?: boolean\n /** Кастомный матчер локальной фильтрации. По умолчанию — подстрока в `label`/`value`. */\n filter?: (option: GrAutocompleteOption<TValue>, query: string) => boolean\n /**\n * Удалённая загрузка опций под управлением компонента: дебаунс, отмена\n * устаревшего запроса и `loading` берёт на себя он. Ответ на отменённый\n * запрос игнорируется — при быстром вводе в списке всегда результат\n * последнего запроса, а не того, который вернулся позже.\n *\n * `signal` пробрасывается в `fetch`. Локальная фильтрация в этом режиме\n * выключена: список фильтрует сервер. Альтернатива — событие `search`, если\n * запрос ведёт само приложение.\n */\n fetchOptions?: (query: string, signal: AbortSignal) => Promise<GrAutocompleteOption<TValue>[]>\n /** Минимальная длина запроса до эмита `search` (для дебаунса remote-загрузки). */\n minQueryLength?: number\n /** Задержка дебаунса события `search`, мс. */\n debounce?: number\n /** Разрешить ввод/коммит значения, которого нет в `options`. */\n allowCustomValue?: boolean\n /** Закрывать панель после выбора (single всегда закрывает). */\n closeOnSelect?: boolean\n /** Максимальная высота панели, px. */\n dropdownMaxHeight?: number\n /**\n * Виртуализация панели: в DOM живёт только окно вокруг вьюпорта.\n *\n * Высоту окна задаёт `dropdownMaxHeight`. Включается осознанно: на сотне\n * опций выигрыша нет, а в разметке остаётся только окно — вместе с ним\n * меняется и то, что находит `querySelector` потребителя. Профильный\n * сценарий — удалённый поиск по справочнику на тысячи позиций.\n */\n virtual?: boolean\n /** i18n-тексты состояний панели / aria. */\n loadingText?: string\n noResultsText?: string\n clearLabel?: string\n /**\n * Контролируемое состояние панели (`v-model:open`). Без пропа панель ведёт\n * себя сама (uncontrolled), с ним — слушайте `update:open` и меняйте проп.\n */\n open?: boolean\n /** Имя для нативной формы: hidden input по значению модели (не по тексту запроса). */\n name?: string\n /**\n * Ширины аддонов `prefix`/`suffix` — общий контракт контролов пакета\n * (`docs/form-controls.md`).\n */\n prefixMinWidth?: string\n prefixMaxWidth?: string\n suffixMinWidth?: string\n suffixMaxWidth?: string\n prefixFixed?: boolean\n suffixFixed?: boolean\n}\n\nexport interface GrAutocompleteEmits<TValue extends GrAutocompleteValue = string> {\n (e: 'update:modelValue', value: GrAutocompleteModelValue<TValue>): void\n /** Дебаунснутый поисковый запрос — точка входа для удалённой загрузки опций. */\n (e: 'search', query: string): void\n /** Запрос `fetchOptions` завершился ошибкой (отмена устаревшего сюда не приходит). */\n (e: 'searchError', error: unknown): void\n /** Значение зафиксировано выбором или снятием опции. */\n (e: 'change', value: GrAutocompleteModelValue<TValue>): void\n /** Панель открылась/закрылась (`v-model:open`). */\n (e: 'update:open', value: boolean): void\n /** Значение снято кнопкой очистки; только при `clearable`. */\n (e: 'clear'): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n}\n\nconst props = withDefaults(\n defineProps<GrAutocompleteProps<TValue>>(),\n {\n options: undefined,\n multiple: false,\n tagTone: 'neutral',\n tagDark: false,\n // `sm`, а не `md` как у `GrInputTag`: чип живёт внутри контрола, а не в тексте.\n tagSize: 'sm',\n tagRadius: 'round',\n disabled: false,\n readonly: false,\n invalid: false,\n required: false,\n size: undefined,\n placeholder: undefined,\n ariaLabel: undefined,\n clearable: undefined,\n loading: false,\n filterable: true,\n filter: undefined,\n fetchOptions: undefined,\n minQueryLength: 0,\n debounce: 250,\n allowCustomValue: false,\n closeOnSelect: true,\n dropdownMaxHeight: 280,\n virtual: false,\n loadingText: undefined,\n noResultsText: undefined,\n clearLabel: undefined,\n open: undefined,\n name: undefined,\n prefixMinWidth: undefined,\n prefixMaxWidth: undefined,\n suffixMinWidth: undefined,\n suffixMaxWidth: undefined,\n prefixFixed: false,\n suffixFixed: false,\n },\n)\n\n/** Ширина аддона по умолчанию — та же лестница, что у `GrInput`. */\nconst ADDON_MIN_WIDTH_BY_SIZE: Record<GrAutocompleteSize, string> = {\n xs: '2rem',\n sm: '2.25rem',\n md: '2.5rem',\n lg: '3rem',\n}\n\n// Эффективный размер: локальный проп → `GrConfigProvider` → дефолт компонента.\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrAutocomplete' })\nconst resolvedClearable = useGrComponentProp('GrAutocomplete', 'clearable', () => props.clearable, false)\n\n/**\n * Аддоны живут флекс-соседями поля, а не поверх него: оболочка автокомплита —\n * флекс-строка с чипами, поэтому резервировать место паддингом не нужно.\n * Контракт ширин остаётся общим (`useControlAddons`).\n */\nconst { hasPrefix, hasSuffix, prefixEl, suffixEl, prefixStyle, suffixStyle } = useControlAddons(() => props, {\n defaultMinWidth: () => ADDON_MIN_WIDTH_BY_SIZE[resolvedSize.value],\n paddingX: () => '0px',\n})\n\nconst emit = defineEmits<GrAutocompleteEmits<TValue>>()\ndefineSlots<{\n /** Аддон слева от поля ввода. */\n prefix?: () => any\n /** Аддон справа, перед спиннером и крестиком. */\n suffix?: () => any\n /** Строка списка вместо подписи опции. */\n option?: (props: { option: GrAutocompleteOption<TValue>, selected: boolean }) => any\n /** Содержимое панели, пока едут опции. */\n loading?: () => any\n /** Содержимое панели, когда подходящих опций нет. */\n empty?: () => any\n}>()\n\n\nconst { t } = useGranularityTranslations()\n\nconst resolvedLoadingText = computed(() => props.loadingText ?? t('gr.autocomplete.loading', 'Loading…'))\nconst resolvedNoResultsText = computed(() => props.noResultsText ?? t('gr.autocomplete.noResults', 'No results'))\nconst resolvedClearLabel = computed(() => props.clearLabel ?? t('gr.common.clear', 'Clear'))\n// Число уходит под двумя общепринятыми именами: `n` читают `@feugene/fint-i18n`\n// и `vue-i18n`, `count` — `i18next`. Формы для каждого языка лежат в словаре,\n// выбирает их переводчик; встроенный fallback — одна английская строка.\nconst resolvedTypeMoreText = computed(() =>\n t('gr.autocomplete.typeMore', 'Type at least {n} characters', {\n n: props.minQueryLength,\n count: props.minQueryLength,\n }),\n)\n\n// Fallback из контекста `GrFormField` (id/aria-describedby/invalid/required).\nconst field = useGrFormFieldContext()\nconst resolvedId = computed(() => field?.id.value)\nconst {\n disabled: isDisabled,\n invalid: isInvalid, required: isRequired, readonly: isReadonly } = useGrFormControl(() => props)\nconst describedBy = computed(() => field?.describedById.value)\n\n// Read-only запирает контрол так же, как disabled: значение видно, но панель не\n// открывается, опции не выбираются и чипы не удаляются. Без этого `readonly`\n// оставался бы ARIA-атрибутом без поведения.\nconst locked = computed(() => isDisabled.value || isReadonly.value)\n\n// Ответ последнего `fetchOptions`. До первого ответа показываем `options` —\n// с ними компонент рисует стартовый список, не дожидаясь сервера.\nconst remoteOptions = shallowRef<GrAutocompleteOption<TValue>[]>([])\nconst remoteAnswered = ref(false)\nconst remoteLoading = ref(false)\n\nconst optionsResolved = computed<GrAutocompleteOption<TValue>[]>(() =>\n props.fetchOptions && remoteAnswered.value ? remoteOptions.value : (props.options ?? []),\n)\n\n/**\n * Подпись состава стартового списка. Сравнивать идентичность массива нельзя:\n * инлайн-литерал `:options=\"[...]\"` пересоздаётся каждым ререндером родителя —\n * в том числе тем, который вызвал сам компонент своим `update:modelValue`, —\n * и remote-результаты исчезали бы прямо посреди выбора.\n */\nfunction optionsSignature(options: GrAutocompleteOption<TValue>[] | undefined): string {\n return (options ?? []).map(o => `${String(o.value)}\\u0000${o.label}`).join('\\u0001')\n}\n\n// Родитель сменил стартовый список — он снова источник до следующего ответа\n// сервера, а летящий запрос относится к прежнему набору данных и отменяется.\nwatch(() => optionsSignature(props.options), () => {\n if (!props.fetchOptions) return\n cancelSearch()\n remoteAnswered.value = false\n remoteOptions.value = []\n})\n\nconst isLoading = computed(() => props.loading || remoteLoading.value)\n\n/** `0` — валидное значение, поэтому «пусто» проверяется явно, а не через falsy. */\nfunction isEmptyValue(value: unknown): boolean {\n return value === undefined || value === null || value === ''\n}\n\nfunction toArray(value: GrAutocompleteModelValue<TValue>): TValue[] {\n if (Array.isArray(value)) return value\n if (isEmptyValue(value)) return []\n return [value]\n}\n\nconst modelSingle = computed<TValue | ''>(() => {\n const raw = Array.isArray(props.modelValue) ? props.modelValue[0] : props.modelValue\n return isEmptyValue(raw) ? '' : (raw as TValue)\n})\nconst selectedValues = computed(() => (props.multiple ? toArray(props.modelValue) : (isEmptyValue(modelSingle.value) ? [] : [modelSingle.value as TValue])))\nconst hasSelection = computed(() => selectedValues.value.length > 0)\n\nfunction labelFor(value: GrAutocompleteValue): string {\n return optionsResolved.value.find(o => o.value === value)?.label ?? String(value)\n}\n\n/** Опции выбранных значений (для chips в multiple). Неизвестные значения показываем как есть. */\nconst selectedOptions = computed<GrAutocompleteOption<TValue>[]>(() =>\n resolveSelectedOptions(selectedValues.value, optionsResolved.value),\n)\n\nconst singleSelectedLabel = computed(() => (props.multiple || isEmptyValue(modelSingle.value) ? '' : labelFor(modelSingle.value)))\n\n// ————— Состояние.\nconst query = ref('')\n// `dirty` — пользователь начал вводить запрос. Нужно, чтобы в single-режиме при\n// открытии заполненного поля показать весь список (а не отфильтровать по метке\n// уже выбранной опции). Сбрасывается при программной установке `query`.\nconst dirty = ref(false)\n// Имя `open` сохранено: читатели работают с computed-Ref.\nconst { open, setOpen } = useControlledOpen(\n () => props.open,\n next => emit('update:open', next),\n)\n\nconst rootEl = ref<HTMLElement | null>(null)\nconst panelEl = ref<HTMLElement | null>(null)\nconst listboxEl = ref<HTMLElement | null>(null)\n\n// Панель телепортирована в `body`, то есть лежит вне корня, но для пользователя\n// она часть контрола: без неё уход фокуса в панель читался бы как `blur`.\nconst { onFocusIn, onFocusOut } = useFocusWithin(rootEl, {\n enter: event => emit('focus', event),\n leave: event => emit('blur', event),\n containers: () => [panelEl.value],\n})\nconst inputEl = ref<HTMLInputElement | null>(null)\n\nfunction focus(): void {\n inputEl.value?.focus()\n}\n\nfunction blur(): void {\n inputEl.value?.blur()\n}\n\ndefineExpose({\n focus,\n blur,\n open: () => openDropdown(),\n close: () => closeDropdown(),\n})\nconst clickOutsideExclude = [() => panelEl.value]\n\nconst { floatingStyle } = useFloating(rootEl, panelEl, open, {\n placement: 'bottom-start',\n matchWidth: true,\n zIndexVar: '--gr-z-dropdown',\n})\n\nuseDismissible(open, closeDropdown)\n\n// ————— Фильтрация.\nconst searchQuery = computed(() => {\n // При `fetchOptions` фильтрует сервер: локальный матчер отсеял бы то, что он\n // уже прислал в ответ на этот же запрос.\n if (!props.filterable || props.fetchOptions) return ''\n // single: пока пользователь не начал вводить — показываем весь список.\n if (!props.multiple && !dirty.value) return ''\n return query.value.trim()\n})\n\nconst filteredOptions = computed<GrAutocompleteOption<TValue>[]>(() =>\n filterOptions(optionsResolved.value, searchQuery.value, props.filter),\n)\n\nconst belowMinQuery = computed(() => props.minQueryLength > 0 && query.value.trim().length < props.minQueryLength)\n\n/**\n * Опции к показу и навигации. Ниже `minQueryLength` — пусто: список ещё\n * относится к прошлому запросу, показывать его под подсказкой «введите ещё N»\n * значит дезинформировать.\n */\nconst effectiveOptions = computed<GrAutocompleteOption<TValue>[]>(() =>\n belowMinQuery.value ? [] : filteredOptions.value,\n)\n\nconst canAddCustom = computed(() => {\n if (!props.allowCustomValue) return false\n // Кастомное значение набирается текстом — оно строковое по природе;\n // при числовом `TValue` эта ветка неприменима (см. docs/components.md).\n const v = query.value.trim() as TValue\n if (!v) return false\n if (props.multiple && selectedValues.value.includes(v)) return false\n if (!props.multiple && v === modelSingle.value) return false\n // Не предлагаем «Add», если такое значение/метка уже есть среди опций.\n return !optionsResolved.value.some(o => o.value === v || o.label === v)\n})\n\nconst showEmpty = computed(() =>\n !isLoading.value && effectiveOptions.value.length === 0 && !canAddCustom.value,\n)\n\n// ————— Панель: id/aria-activedescendant.\nconst listboxId = useId()\n\n/**\n * Id опции строится от позиции, а не от значения: значение с пробелом дало бы\n * невалидный `id`, а `aria-activedescendant` — два токена вместо одной ссылки,\n * и активная опция перестала бы объявляться.\n */\nfunction optionDomId(index: number): string {\n return `${listboxId}-opt-${index}`\n}\nconst addOptionDomId = computed(() => `${listboxId}-add`)\n\n/**\n * Виртуализация панели.\n *\n * Набор — `[«Add …»?] + filteredOptions`, тот же, по которому ходит клавиатура\n * (`navigableItems` ниже). Иначе верхняя распорка вытолкнула бы строку «Add …»\n * вниз: она отрисована внутри listbox'а первой и является полноценной опцией.\n */\n\n/** Оценка высоты опции: `py-2` вокруг строки кегля `sm`. Уточняется замером. */\nconst OPTION_SIZE_ESTIMATE = 36\n\nconst addOffset = computed(() => (canAddCustom.value ? 1 : 0))\nconst virtualCount = computed(() => effectiveOptions.value.length + addOffset.value)\n\nconst virtualizer = useVirtualList({\n container: listboxEl,\n count: () => (props.virtual ? virtualCount.value : 0),\n // Фильтрация/remote-ответ пересобирают набор — замеры прошлого невалидны.\n source: () => effectiveOptions.value,\n itemSize: OPTION_SIZE_ESTIMATE,\n // Панель скрыта `v-show`, пока закрыта, поэтому `clientHeight` контейнера —\n // ноль. Окно считается от объявленной высоты до первого настоящего замера.\n viewportSize: () => props.dropdownMaxHeight,\n})\n\n/** Виден ли «Add …»: вне виртуального окна его рисовать нельзя — он элемент набора. */\nconst showAddOption = computed(() => {\n if (!canAddCustom.value) return false\n return !props.virtual || virtualizer.range.value.start === 0\n})\n\n/** Опции к отрисовке вместе с их абсолютным индексом в `filteredOptions`. */\nconst renderedOptions = computed(() => {\n const all = effectiveOptions.value\n if (!props.virtual) return all.map((option, index) => ({ option, index }))\n\n const { start, end } = virtualizer.range.value\n const from = Math.max(0, start - addOffset.value)\n const to = Math.max(0, end - addOffset.value)\n\n return all.slice(from, to).map((option, offset) => ({ option, index: from + offset }))\n})\n\n/**\n * Размер набора и позиция в нём объявляются только при виртуализации: в обычном\n * режиме диктор выводит их из DOM, а при неполном наборе получил бы «3 из 15»\n * на списке в десять тысяч.\n */\nfunction optionSetProps(virtualIndex: number): Record<string, number> | undefined {\n if (!props.virtual) return undefined\n return { 'aria-setsize': virtualCount.value, 'aria-posinset': virtualIndex + 1 }\n}\n\nconst listboxStyle = computed(() => {\n const base: Record<string, string> = { maxHeight: `${props.dropdownMaxHeight}px` }\n if (!props.virtual) return base\n\n return {\n ...base,\n ...virtualizer.spacerStyle.value,\n }\n})\n\n/**\n * Клавиатурная навигация ходит и по опциям, и по варианту «добавить своё»:\n * иначе при непустом списке Enter всегда уходил бы в активную опцию, и\n * закоммитить произвольное значение с клавиатуры было бы нечем.\n */\ntype NavigableItem =\n | { kind: 'add' }\n | { kind: 'option', option: GrAutocompleteOption<TValue>, index: number }\n\nconst navigableItems = computed<NavigableItem[]>(() => {\n const items: NavigableItem[] = canAddCustom.value ? [{ kind: 'add' }] : []\n effectiveOptions.value.forEach((option, index) => {\n if (!option.disabled) items.push({ kind: 'option', option, index })\n })\n return items\n})\n\nfunction isSelected(value: TValue): boolean {\n return selectedValues.value.includes(value)\n}\n\nfunction navigableIndexOf(value: TValue): number {\n return navigableItems.value.findIndex(item => item.kind === 'option' && item.option.value === value)\n}\n\n/** Прокрутка к активному: сперва окно виртуального списка, затем доводка. */\nasync function scrollActiveIntoView(item: NavigableItem): Promise<void> {\n // Вне окна активной опции в DOM нет: `getElementById` вернул бы `null`,\n // прокрутка не случилась бы, а `aria-activedescendant` указал бы в пустоту.\n if (props.virtual)\n virtualizer.scrollToIndex(item.kind === 'add' ? 0 : item.index + addOffset.value)\n\n await nextTick()\n const id = item.kind === 'add' ? addOptionDomId.value : optionDomId(item.index)\n document.getElementById(id)?.scrollIntoView?.({ block: 'nearest' })\n}\n\nconst {\n activeIndex,\n activeItem,\n activeDescendantId,\n init: initActiveIndex,\n reset: resetActive,\n handleNavigationKeys,\n} = useComboboxNavigation<NavigableItem>({\n items: () => navigableItems.value,\n open: () => open.value,\n idOf: item => (item.kind === 'add' ? addOptionDomId.value : optionDomId(item.index)),\n initialIndex: () => navigableItems.value.findIndex(item => item.kind === 'option' && isSelected(item.option.value)),\n scrollTo: item => scrollActiveIntoView(item),\n})\n\nconst activeValue = computed(() => (activeItem.value?.kind === 'option' ? activeItem.value.option.value : undefined))\n\n// ————— Открытие/закрытие.\nfunction openDropdown(): void {\n if (locked.value || open.value) return\n setOpen(true)\n}\n\nfunction closeDropdown(): void {\n setOpen(false)\n}\n\nfunction focusInput(): void {\n inputEl.value?.focus()\n}\n\n// ————— Дебаунснутый поиск для remote-загрузки.\nlet searchTimer: ReturnType<typeof setTimeout> | null = null\n// Счётчик запросов: ответ, стартовавший раньше, но пришедший позже, обязан\n// проиграть последнему — иначе список показывает результат старого запроса.\nlet searchSeq = 0\nlet inflight: AbortController | null = null\n\nfunction isAbortError(error: unknown): boolean {\n return error instanceof Error && error.name === 'AbortError'\n}\n\nasync function runFetch(query: string): Promise<void> {\n const fetchOptions = props.fetchOptions\n if (!fetchOptions) return\n\n inflight?.abort()\n const controller = new AbortController()\n inflight = controller\n const seq = ++searchSeq\n remoteLoading.value = true\n\n try {\n const result = await fetchOptions(query, controller.signal)\n if (seq !== searchSeq) return\n remoteOptions.value = result\n remoteAnswered.value = true\n }\n catch (error) {\n if (seq !== searchSeq || isAbortError(error)) return\n remoteOptions.value = []\n remoteAnswered.value = true\n emit('searchError', error)\n }\n finally {\n if (seq === searchSeq) remoteLoading.value = false\n }\n}\n\nfunction scheduleSearch(value: string): void {\n if (searchTimer) clearTimeout(searchTimer)\n searchTimer = setTimeout(() => {\n if (props.minQueryLength > 0 && value.trim().length < props.minQueryLength) return\n emit('search', value.trim())\n void runFetch(value.trim())\n }, props.debounce)\n}\n\n/**\n * Снять запланированный и летящий запрос. Инкремент `searchSeq` обязателен\n * вместе со снятием `remoteLoading`: после него `finally` в `runFetch` считает\n * себя устаревшим и флаг не тронет — спиннер остался бы навсегда.\n */\nfunction cancelSearch(): void {\n if (searchTimer) clearTimeout(searchTimer)\n searchTimer = null\n inflight?.abort()\n inflight = null\n searchSeq += 1\n remoteLoading.value = false\n}\n\nonBeforeUnmount(cancelSearch)\n\n// ————— Ввод.\nfunction onInput(event: Event): void {\n const value = (event.target as HTMLInputElement).value\n query.value = value\n dirty.value = true\n openDropdown()\n scheduleSearch(value)\n}\n\nfunction onFocus(): void {\n if (locked.value) return\n openDropdown()\n}\n\nfunction setQuery(value: string): void {\n query.value = value\n dirty.value = false\n}\n\n// ————— Выбор значений.\nfunction selectSingle(value: TValue, label: string): void {\n emit('update:modelValue', value)\n emit('change', value as GrAutocompleteModelValue<TValue>)\n setQuery(label)\n closeDropdown()\n}\n\nfunction toggleMultiple(value: TValue): void {\n const next = selectedValues.value.slice()\n const idx = next.indexOf(value)\n if (idx >= 0) next.splice(idx, 1)\n else next.push(value)\n emit('update:modelValue', next)\n emit('change', next as GrAutocompleteModelValue<TValue>)\n setQuery('')\n if (props.closeOnSelect) closeDropdown()\n else void nextTick(focusInput)\n}\n\nfunction chooseOption(option: GrAutocompleteOption<TValue>): void {\n if (locked.value || option.disabled) return\n if (props.multiple) toggleMultiple(option.value)\n else selectSingle(option.value, option.label)\n}\n\nfunction commitCustom(): void {\n if (locked.value) return\n // Кастомное значение строковое по природе — см. `canAddCustom`.\n const v = query.value.trim() as TValue\n if (!v) return\n if (props.multiple) toggleMultiple(v)\n else selectSingle(v, String(v))\n}\n\nfunction removeValue(value: TValue, focusAfter: () => void = focusInput): void {\n if (locked.value) return\n const next = selectedValues.value.filter(v => v !== value)\n emit('update:modelValue', next)\n emit('change', next as GrAutocompleteModelValue<TValue>)\n void nextTick(focusAfter)\n}\n\nfunction clearSelection(): void {\n if (locked.value) return\n const next = (props.multiple ? [] : '') as GrAutocompleteModelValue<TValue>\n emit('update:modelValue', next)\n emit('change', next)\n emit('clear')\n setQuery('')\n void nextTick(focusInput)\n}\n\nconst showClear = computed(() =>\n resolvedClearable.value && !locked.value && (hasSelection.value || query.value.length > 0),\n)\n\n// ————— Чипы (multiple): стрелки вместо таб-стопов.\n//\n// Крестики намеренно не табируемы: у combobox фокус живёт на `<input>`, и\n// двадцать выбранных значений не должны давать двадцать остановок Tab. Поэтому\n// вход в чипы — ArrowLeft из пустого запроса, а выход — ArrowRight/Esc/печать.\nfunction chipRemoveButtons(): HTMLButtonElement[] {\n return [...(rootEl.value?.querySelectorAll<HTMLButtonElement>('[data-gr-autocomplete-chip-remove]') ?? [])]\n}\n\nfunction focusChip(index: number): void {\n const buttons = chipRemoveButtons()\n if (!buttons.length) {\n focusInput()\n return\n }\n buttons[Math.min(Math.max(index, 0), buttons.length - 1)].focus()\n}\n\nfunction onChipKeydown(event: KeyboardEvent, index: number, value: TValue): void {\n switch (event.key) {\n case 'ArrowLeft':\n event.preventDefault()\n focusChip(index - 1)\n break\n case 'ArrowRight':\n event.preventDefault()\n if (index >= chipRemoveButtons().length - 1) focusInput()\n else focusChip(index + 1)\n break\n case 'Home':\n event.preventDefault()\n focusChip(0)\n break\n case 'End':\n event.preventDefault()\n focusChip(chipRemoveButtons().length - 1)\n break\n case 'Delete':\n case 'Backspace':\n event.preventDefault()\n removeValue(value, () => focusChip(index))\n break\n case 'Escape':\n event.preventDefault()\n focusInput()\n break\n default:\n // Печатный символ возвращает в поле: фокус переезжает синхронно, до\n // вставки текста, поэтому символ попадает в запрос, а не пропадает.\n if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) focusInput()\n }\n}\n\n// ————— Клавиатура.\nfunction onKeydown(event: KeyboardEvent): void {\n // Клавиша во время IME-композиции принадлежит композиции: Enter коммитит её,\n // Esc отменяет, стрелки ходят по кандидатам.\n if (isComposingEvent(event)) return\n if (locked.value) return\n\n // Курсор в начале пустого запроса — стрелка влево уходит к чипам, а не\n // двигает каретку, которой всё равно некуда двигаться.\n if (event.key === 'ArrowLeft' && props.multiple && query.value === '' && selectedValues.value.length) {\n event.preventDefault()\n closeDropdown()\n focusChip(selectedValues.value.length - 1)\n return\n }\n\n if (!open.value && ['ArrowDown', 'ArrowUp'].includes(event.key)) {\n event.preventDefault()\n openDropdown()\n return\n }\n\n // Только при открытой панели: в закрытом инпуте Home/End двигают каретку.\n if (open.value && handleNavigationKeys(event)) return\n\n switch (event.key) {\n case 'Enter': {\n if (!open.value) break\n event.preventDefault()\n const item = activeItem.value\n if (item?.kind === 'option') chooseOption(item.option)\n else if (item?.kind === 'add' || canAddCustom.value) commitCustom()\n break\n }\n case 'Backspace':\n // multiple: пустой запрос + Backspace удаляет последний chip.\n if (props.multiple && query.value === '' && selectedValues.value.length) {\n event.preventDefault()\n removeValue(selectedValues.value[selectedValues.value.length - 1])\n }\n break\n case 'Tab':\n closeDropdown()\n break\n }\n}\n\n// ————— Синхронизация query с выбранным значением (single).\nwatch(\n [singleSelectedLabel, () => props.multiple],\n ([label, multiple]) => {\n if (multiple) return\n // Не перетираем то, что пользователь сейчас набирает.\n if (open.value || dirty.value) return\n setQuery(label)\n },\n { immediate: true },\n)\n\nwatch(open, (isOpen) => {\n if (isOpen) {\n initActiveIndex()\n return\n }\n // Закрытие: сбрасываем активную опцию и «черновик».\n resetActive()\n if (props.multiple) {\n setQuery('')\n }\n else {\n // single: возвращаем метку выбранной опции (revert ввода без выбора).\n setQuery(singleSelectedLabel.value)\n }\n})\n\nconst ariaAutocomplete = computed(() => (props.allowCustomValue ? 'both' : 'list'))\n\n// Телепорт включается только ПОСЛЕ монтирования: иначе первый клиентский\n// рендер не совпадает с серверным и ломается гидрация (см. композабл).\nconst { target: portalTarget, enabled: teleportEnabled } = usePortalTarget()\n\n// Тема поддерева на телепортированную панель: в DOM она уезжает в `body`, то\n// есть вне обёртки провайдера, и `data-theme` с неё не наследуется. В дереве\n// компонентов панель остаётся внутри — `inject` доходит, и тему она ставит себе\n// сама.\nconst themeAttrs = useGrThemeAttrs()\n</script>\n\n<template>\n <div\n ref=\"rootEl\"\n v-click-outside=\"{ handler: closeDropdown, enabled: open, exclude: clickOutsideExclude }\"\n data-gr-autocomplete\n class=\"relative w-full\"\n @focusin=\"onFocusIn\"\n @focusout=\"onFocusOut\"\n >\n <div\n data-gr-autocomplete-shell\n :class=\"autocompleteShellClass({ size: resolvedSize, disabled: isDisabled, invalid: isInvalid })\"\n @mousedown.self.prevent=\"focusInput\"\n >\n <span\n v-if=\"hasPrefix\"\n ref=\"prefixEl\"\n data-gr-autocomplete-prefix\n class=\"inline-flex shrink-0 items-center justify-center text-[var(--gr-muted-fg)]\"\n :style=\"prefixStyle\"\n >\n <slot name=\"prefix\" />\n </span>\n\n <!-- Chips выбранных значений (multiple). -->\n <template v-if=\"multiple\">\n <GrBadge\n v-for=\"(option, chipIndex) in selectedOptions\"\n :key=\"option.value\"\n data-gr-autocomplete-chip\n :tone=\"tagTone\"\n :dark=\"tagDark\"\n :size=\"tagSize\"\n :radius=\"tagRadius\"\n >\n <span class=\"inline-flex max-w-full items-center gap-1 align-middle\">\n <span class=\"truncate\">{{ option.label }}</span>\n <button\n v-if=\"!locked\"\n type=\"button\"\n data-gr-autocomplete-chip-remove\n class=\"inline-flex h-3.5 w-3.5 shrink-0 items-center justify-center rounded-[var(--gr-radius-full)] text-current focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--gr-ring)]\"\n :aria-label=\"t('gr.autocomplete.removeValue', 'Remove {label}', { label: option.label })\"\n tabindex=\"-1\"\n @click=\"removeValue(option.value)\"\n @keydown=\"onChipKeydown($event, chipIndex, option.value)\"\n >\n <span class=\"i-lucide-x block h-3 w-3\" aria-hidden=\"true\" />\n </button>\n </span>\n </GrBadge>\n </template>\n\n <input\n :id=\"resolvedId\"\n ref=\"inputEl\"\n data-gr-autocomplete-input\n data-testid=\"gr-autocomplete-input\"\n type=\"text\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n role=\"combobox\"\n :value=\"query\"\n :disabled=\"isDisabled\"\n :placeholder=\"hasSelection && multiple ? undefined : placeholder\"\n :aria-label=\"ariaLabel\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n :aria-describedby=\"describedBy\"\n :aria-required=\"isRequired ? 'true' : undefined\"\n :aria-readonly=\"isReadonly ? 'true' : undefined\"\n :readonly=\"isReadonly\"\n aria-haspopup=\"listbox\"\n :aria-autocomplete=\"ariaAutocomplete\"\n :aria-controls=\"open ? listboxId : undefined\"\n :aria-activedescendant=\"activeDescendantId\"\n :aria-expanded=\"open ? 'true' : 'false'\"\n class=\"min-w-0 flex-1 bg-transparent text-inherit placeholder:text-[var(--gr-muted-fg)] focus:outline-none disabled:cursor-not-allowed\"\n @input=\"onInput\"\n @focus=\"onFocus\"\n @keydown=\"onKeydown\"\n >\n\n <span\n v-if=\"hasSuffix\"\n ref=\"suffixEl\"\n data-gr-autocomplete-suffix\n class=\"inline-flex shrink-0 items-center justify-center text-[var(--gr-muted-fg)]\"\n :style=\"suffixStyle\"\n >\n <slot name=\"suffix\" />\n </span>\n\n <!-- Trailing: спиннер / очистка / шеврон. -->\n <span\n v-if=\"isLoading\"\n data-gr-autocomplete-spinner\n class=\"shrink-0 flex items-center text-[var(--gr-muted-fg)]\"\n >\n <span class=\"i-lucide-loader-2 block h-4 w-4 animate-spin\" aria-hidden=\"true\" />\n </span>\n\n <button\n v-else-if=\"showClear\"\n type=\"button\"\n data-testid=\"gr-autocomplete-clear\"\n data-gr-autocomplete-clear\n class=\"shrink-0 inline-flex h-5 w-5 items-center justify-center rounded-[var(--gr-radius-control)] text-[var(--gr-muted-fg)] hover:text-[var(--gr-fg)]\"\n :aria-label=\"resolvedClearLabel\"\n @click=\"clearSelection\"\n >\n <span class=\"i-lucide-x block h-4 w-4\" aria-hidden=\"true\" />\n </button>\n\n <span\n v-else\n data-testid=\"gr-autocomplete-chevron\"\n class=\"shrink-0 flex items-center text-[var(--gr-muted-fg)] pointer-events-none\"\n >\n <span class=\"i-lucide-chevron-down block h-4 w-4\" aria-hidden=\"true\" />\n </span>\n </div>\n\n <!-- Нативная форма: сериализуется модель, а не текст запроса. -->\n <template v-if=\"name\">\n <input\n v-for=\"value in selectedValues\"\n :key=\"`hidden-${String(value)}`\"\n type=\"hidden\"\n :name=\"name\"\n :value=\"String(value)\"\n >\n </template>\n\n <teleport :to=\"portalTarget\" :disabled=\"!teleportEnabled\">\n <transition\n enter-active-class=\"transition ease-[var(--gr-ease-out)] duration-[var(--gr-duration-fast)]\"\n enter-from-class=\"transform opacity-0 scale-95\"\n enter-to-class=\"transform opacity-100 scale-100\"\n leave-active-class=\"transition ease-[var(--gr-ease-in)] duration-[var(--gr-duration-fast)]\"\n leave-from-class=\"transform opacity-100 scale-100\"\n leave-to-class=\"transform opacity-0 scale-95\"\n >\n <div\n v-show=\"open\"\n ref=\"panelEl\"\n data-testid=\"gr-autocomplete-panel\"\n v-bind=\"themeAttrs\"\n data-gr-autocomplete-panel\n data-gr-overlay-root\n :style=\"floatingStyle\"\n >\n <div :class=\"autocompletePanelClasses\">\n <!--\n Пустой listbox не рендерим: его `p-1` давал полосу пустоты над\n строкой состояния, и «ничего не найдено» выглядело прижатым к низу.\n -->\n <div\n v-if=\"showAddOption || renderedOptions.length\"\n :id=\"listboxId\"\n ref=\"listboxEl\"\n data-gr-autocomplete-listbox\n :data-gr-virtual=\"virtual ? '' : undefined\"\n class=\"p-1 overflow-auto\"\n :class=\"virtual ? 'flex flex-col' : ''\"\n :style=\"listboxStyle\"\n role=\"listbox\"\n :aria-multiselectable=\"multiple ? 'true' : undefined\"\n >\n <!--\n Прямые потомки listbox — только опции: роль объявляет остальных\n недопустимыми детьми (`aria-required-children`), поэтому строки\n состояний живут ниже, вне списка. `mousedown.prevent` держит\n фокус на `<input>` — контракт combobox с `aria-activedescendant`.\n -->\n <button\n v-if=\"showAddOption\"\n :id=\"addOptionDomId\"\n type=\"button\"\n role=\"option\"\n aria-selected=\"false\"\n tabindex=\"-1\"\n v-bind=\"optionSetProps(0)\"\n data-testid=\"gr-autocomplete-add-option\"\n data-gr-autocomplete-add-option\n :class=\"autocompleteOptionClass({ disabled: false, active: activeItem?.kind === 'add' })\"\n @mousedown.prevent\n @click=\"commitCustom\"\n @mousemove=\"activeIndex = 0\"\n >\n {{ t('gr.autocomplete.addOption', 'Add \"{value}\"', { value: query.trim() }) }}\n </button>\n\n <button\n v-for=\"{ option, index: optionIndex } in renderedOptions\"\n :id=\"optionDomId(optionIndex)\"\n :key=\"option.value\"\n :ref=\"(el) => virtual && virtualizer.measure(optionIndex + addOffset, el as Element | null)\"\n data-gr-autocomplete-option\n type=\"button\"\n role=\"option\"\n tabindex=\"-1\"\n v-bind=\"optionSetProps(optionIndex + addOffset)\"\n :disabled=\"option.disabled\"\n :aria-selected=\"isSelected(option.value) ? 'true' : 'false'\"\n :aria-disabled=\"option.disabled ? 'true' : undefined\"\n :class=\"autocompleteOptionClass({\n disabled: !!option.disabled,\n active: activeValue === option.value,\n })\"\n @mousedown.prevent\n @click=\"chooseOption(option)\"\n @mousemove=\"activeIndex = navigableIndexOf(option.value)\"\n >\n <slot name=\"option\" :option=\"option\" :selected=\"isSelected(option.value)\">\n <span class=\"flex items-center gap-2 min-w-0\">\n <span\n class=\"inline-block h-4 w-4 shrink-0\"\n :class=\"isSelected(option.value) ? 'i-lucide-check text-[var(--gr-primary)]' : ''\"\n aria-hidden=\"true\"\n />\n <span class=\"truncate\">{{ option.label }}</span>\n </span>\n </slot>\n </button>\n </div>\n\n <!--\n Загрузка, «введите ещё N» и «ничего не найдено» меняются молча:\n `aria-label` на generic-элементе AT игнорируют. Один живой регион\n на все три — и вне listbox, чтобы не ломать его состав.\n -->\n <div\n v-if=\"isLoading || belowMinQuery || showEmpty\"\n data-gr-autocomplete-status\n role=\"status\"\n aria-live=\"polite\"\n :class=\"autocompleteStateClass\"\n >\n <template v-if=\"isLoading\">\n <span data-gr-autocomplete-loading class=\"flex items-center gap-2\">\n <slot name=\"loading\">\n <span class=\"i-lucide-loader-2 block h-4 w-4 animate-spin\" aria-hidden=\"true\" />\n <span>{{ resolvedLoadingText }}</span>\n </slot>\n </span>\n </template>\n <template v-else-if=\"belowMinQuery\">\n <span data-gr-autocomplete-hint>{{ resolvedTypeMoreText }}</span>\n </template>\n <template v-else>\n <span data-gr-autocomplete-empty>\n <slot name=\"empty\">\n {{ resolvedNoResultsText }}\n </slot>\n </span>\n </template>\n </div>\n </div>\n </div>\n </transition>\n </teleport>\n </div>\n</template>\n\n<style scoped>\n/*\n * Распорки виртуального списка.\n *\n * Не `padding` контейнера: `max-height` меряет ту же коробку, и распорка в\n * десятки тысяч пикселей раздула бы панель. Не обёртка внутри listbox'а:\n * прямыми потомками роли обязаны быть только опции (`aria-required-children`,\n * гейт — `GrAutocomplete.a11y.test.ts`). Не отступы крайних опций: при прыжке\n * прокрутки опции заменяются целиком, распорка исчезла бы вместе с ними, и\n * браузер обрезал бы `scrollTop`.\n *\n * Псевдоэлементы не узлы DOM: их не за что размонтировать, детьми listbox'а\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":"GrBreadcrumbs-BCNGDS6k.js","names":[],"sources":["../../../../src/components/GrBreadcrumbs/GrBreadcrumbs.vue","../../../../src/components/GrBreadcrumbs/GrBreadcrumbs.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'\n\nimport GrLink from '../GrLink/GrLink.vue'\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\n\nimport {\n breadcrumbsCurrentClass,\n breadcrumbsEllipsisClass,\n breadcrumbsItemIconClass,\n breadcrumbsLabelClass,\n breadcrumbsListClass,\n breadcrumbsListNowrapClass,\n breadcrumbsListWrapClass,\n breadcrumbsRootClass,\n breadcrumbsSeparatorClass,\n breadcrumbsSizeClassBySize,\n type GrBreadcrumbItem,\n type GrBreadcrumbsLinkComponent,\n type GrBreadcrumbsSize,\n resolveBreadcrumbsFit,\n resolveBreadcrumbsLayout,\n} from './grBreadcrumbsStyles'\n\nexport type { GrBreadcrumbItem, GrBreadcrumbsSize } from './grBreadcrumbsStyles'\n\n/**\n * Путь до текущей страницы (WAI-ARIA breadcrumb): `<nav>` с именем, внутри\n * упорядоченный список ссылок, последний пункт — текущая страница.\n *\n * Ссылки рисует `GrLink`, поэтому роутер подключается тем же способом, что и\n * везде в пакете: `as` + `to`.\n */\nexport interface GrBreadcrumbsProps {\n /** Путь от корня к текущей странице. Последний пункт — текущая страница. */\n items: GrBreadcrumbItem[]\n /** Компонент ссылки для всех пунктов: `RouterLink`, `NuxtLink`, `Link` Inertia. */\n as?: GrBreadcrumbsLinkComponent\n /** Разделитель между пунктами. Декоративен: диктору не читается. */\n separator?: string\n size?: GrBreadcrumbsSize\n /**\n * С какого числа пунктов схлопывать середину в «…». `0`/не задано — показывать\n * путь целиком.\n */\n maxItems?: number\n /** Сколько пунктов оставить в начале при схлопывании. */\n itemsBeforeCollapse?: number\n /** Сколько пунктов оставить в конце при схлопывании. */\n itemsAfterCollapse?: number\n /** Имя лендмарка. Не задано — берётся из локали. */\n ariaLabel?: string\n /** Последний пункт остаётся ссылкой (текущая страница кликабельна). */\n linkCurrent?: boolean\n /** i18n-метка кнопки раскрытия схлопнутого пути. */\n expandLabel?: string\n /**\n * Схлопывать по доступной ширине, а не по числу пунктов. Путь становится\n * однострочным, середина уходит под «…» ровно настолько, насколько не влезает.\n *\n * Без пропа поведение прежнее: список переносится на вторую строку, а порог\n * задаёт `maxItems`. Вместе пропы совместимы — `maxItems` остаётся жёстким\n * потолком, ширина ужимает дальше.\n */\n autoCollapse?: boolean\n}\n\nconst props = withDefaults(defineProps<GrBreadcrumbsProps>(), {\n as: undefined,\n separator: undefined,\n size: undefined,\n maxItems: undefined,\n itemsBeforeCollapse: 1,\n itemsAfterCollapse: 1,\n ariaLabel: undefined,\n linkCurrent: false,\n expandLabel: undefined,\n autoCollapse: false,\n})\n\ndefineSlots<{\n /** Содержимое пункта целиком (иконка и подпись). */\n item?: (props: { item: GrBreadcrumbItem, index: number, isCurrent: boolean }) => unknown\n /** Разделитель между пунктами. */\n separator?: () => unknown\n /** Кнопка раскрытия схлопнутой середины. */\n ellipsis?: (props: { hiddenCount: number, expand: () => void }) => unknown\n}>()\n\nconst { t } = useGranularityTranslations()\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrBreadcrumbs' })\nconst resolvedSeparator = useGrComponentProp('GrBreadcrumbs', 'separator', () => props.separator, '/')\n\nconst resolvedAriaLabel = computed(() => props.ariaLabel ?? t('gr.breadcrumbs.label', 'Breadcrumb'))\nconst resolvedExpandLabel = computed(() => props.expandLabel ?? t('gr.breadcrumbs.expand', 'Show hidden path'))\n\nconst rootEl = ref<HTMLElement | null>(null)\nconst listEl = ref<HTMLElement | null>(null)\nconst expanded = ref(false)\n\n/**\n * Схлопывание по ширине: сколько пунктов хвоста влезает.\n *\n * `undefined` — ещё не мерили, показываем путь целиком. Первый кадр в режиме\n * `autoCollapse` намеренно полный: ширины снимаются с него, а посчитать их до\n * рендера неоткуда.\n */\nconst fitAfter = ref<number | undefined>(undefined)\n\n/** Ширины не зависят от контейнера, поэтому живут до смены самого пути. */\nlet measured: { items: number[], separator: number } | null = null\n/**\n * Ширина кнопки «…» узнаётся только когда она отрисована, то есть уже после\n * первого схлопывания. До этого считаем её нулевой: поправка может лишь\n * ужать хвост ещё на пункт, а не вернуть спрятанное, поэтому качелей не будет.\n */\nlet ellipsisWidth = 0\n/** Ширина, на которой считали в прошлый раз: по её смене раскрытый путь сворачивается. */\nlet lastAvailable = 0\n\n// Новый путь — новая страница: раскрытая середина прошлого пути к ней отношения\n// не имеет, а измеренные ширины относятся к прошлым подписям.\nwatch(() => props.items, () => {\n expanded.value = false\n measured = null\n ellipsisWidth = 0\n fitAfter.value = undefined\n void nextTick(measure)\n})\n\nconst lastIndex = computed(() => props.items.length - 1)\n\n/**\n * Схлопывание по ширине выражается через тот же `maxItems`: раскладку считает\n * одна функция, а режимы отличаются только тем, откуда взялось число.\n */\nconst autoMaxItems = computed(() => {\n if (!props.autoCollapse || fitAfter.value === undefined) return undefined\n if (fitAfter.value >= props.items.length) return undefined\n\n return props.items.length - 1\n})\n\nconst effectiveMaxItems = computed(() => {\n const limits = [props.maxItems, autoMaxItems.value].filter((value): value is number => typeof value === 'number' && value > 0)\n\n return limits.length > 0 ? Math.min(...limits) : undefined\n})\n\nconst effectiveAfter = computed(() => (\n props.autoCollapse && fitAfter.value !== undefined ? fitAfter.value : props.itemsAfterCollapse\n))\n\nconst entries = computed(() => resolveBreadcrumbsLayout({\n items: props.items,\n maxItems: effectiveMaxItems.value,\n itemsBeforeCollapse: props.itemsBeforeCollapse,\n itemsAfterCollapse: effectiveAfter.value,\n expanded: expanded.value,\n}))\n\nfunction isCurrent(index: number): boolean {\n return index === lastIndex.value\n}\n\n/** Ссылкой пункт становится, только если ведёт куда-то и не выключен. */\nfunction isLink(item: GrBreadcrumbItem, index: number): boolean {\n if (item.disabled) return false\n if (isCurrent(index) && !props.linkCurrent) return false\n return item.href !== undefined || item.to !== undefined || props.as !== undefined\n}\n\nfunction widthOf(el: Element | null | undefined): number {\n // `scrollWidth`, а не прямоугольник: у подписи стоит `truncate`, и в узком\n // контейнере прямоугольник вернул бы уже ужатую ширину, а не собственную.\n return el ? (el as HTMLElement).scrollWidth : 0\n}\n\n/**\n * Снять ширины с полного пути и пересчитать, сколько пунктов хвоста влезает.\n *\n * Замер делается один раз на путь: подписи от ширины контейнера не зависят,\n * поэтому `resize` пересчитывает только арифметику.\n */\nfunction measure(): void {\n if (!props.autoCollapse) return\n\n const list = listEl.value\n if (!list) return\n\n const ellipsis = list.querySelector('[data-gr-breadcrumbs-ellipsis-item]')\n if (ellipsis) ellipsisWidth = widthOf(ellipsis)\n\n if (!measured) {\n // Ширины снимаем только с полного пути: спрятанных пунктов в DOM нет.\n if (fitAfter.value !== undefined) return\n\n const wraps = [...list.querySelectorAll('[data-gr-breadcrumbs-item-wrap]')]\n if (wraps.length !== props.items.length) return\n\n measured = {\n items: wraps.map(widthOf),\n separator: widthOf(list.querySelector('[data-gr-breadcrumbs-separator]')),\n }\n }\n\n const available = list.clientWidth\n // jsdom и скрытый контейнер отдают 0 — решать по такой ширине нечего.\n if (available <= 0) return\n\n lastAvailable = available\n\n fitAfter.value = resolveBreadcrumbsFit({\n itemWidths: measured.items,\n separatorWidth: measured.separator,\n ellipsisWidth,\n available,\n itemsBeforeCollapse: props.itemsBeforeCollapse,\n gapWidth: listGap(list),\n })\n}\n\n/** Зазор списка в пикселях: он задан классом, а не пропом, поэтому спрашиваем стиль. */\nfunction listGap(list: HTMLElement): number {\n if (typeof getComputedStyle !== 'function') return 0\n\n return Number.parseFloat(getComputedStyle(list).columnGap) || 0\n}\n\n/**\n * Ширина кнопки «…» известна только после того, как она отрисована, то есть\n * после первого схлопывания. Поэтому решение пересчитывается ещё раз: поправка\n * либо ничего не меняет, либо ужимает хвост на пункт. Цикла нет — второй проход\n * оставляет `fitAfter` прежним, и наблюдатель больше не срабатывает.\n */\nwatch(fitAfter, () => void nextTick(measure))\n\nlet observer: ResizeObserver | null = null\n\nwatch(() => props.autoCollapse, (enabled) => {\n if (enabled) {\n void nextTick(measure)\n return\n }\n\n measured = null\n ellipsisWidth = 0\n fitAfter.value = undefined\n})\n\nonMounted(() => {\n void nextTick(measure)\n\n // На сервере и в jsdom `ResizeObserver` отсутствует — измерять там нечего.\n if (typeof ResizeObserver === 'undefined') return\n\n observer = new ResizeObserver(() => {\n // Смена ширины — новая раскладка, и раскрытая руками середина относилась к\n // прежней. Свернуть её иначе нечем: в раскрытом пути кнопки «…» нет, то есть\n // поворот телефона оставлял бы путь развёрнутым навсегда. Высота меняется от\n // самого раскрытия (путь переносится на вторую строку) — на неё не реагируем.\n if (expanded.value && listEl.value && listEl.value.clientWidth !== lastAvailable)\n expanded.value = false\n\n measure()\n })\n if (listEl.value) observer.observe(listEl.value)\n})\n\nonBeforeUnmount(() => observer?.disconnect())\n\nasync function expand(): Promise<void> {\n expanded.value = true\n\n // Кнопка исчезает вместе со схлопыванием, и фокус ушёл бы на `<body>`.\n // Переводим его на первый раскрытый пункт — там, где кнопка и стояла. Пункты\n // без ссылки держат `tabindex=\"-1\"` именно ради этого случая.\n await nextTick()\n const before = Math.max(0, Math.min(props.itemsBeforeCollapse, props.items.length))\n const revealed = rootEl.value?.querySelectorAll<HTMLElement>('[data-gr-breadcrumbs-item]')\n revealed?.[before]?.focus()\n}\n</script>\n\n<template>\n <nav\n ref=\"rootEl\"\n data-gr-breadcrumbs\n :class=\"[breadcrumbsRootClass, breadcrumbsSizeClassBySize[resolvedSize]]\"\n :aria-label=\"resolvedAriaLabel\"\n >\n <ol\n ref=\"listEl\"\n data-gr-breadcrumbs-list\n :class=\"[breadcrumbsListClass, autoCollapse && !expanded ? breadcrumbsListNowrapClass : breadcrumbsListWrapClass]\"\n >\n <template v-for=\"(entry, position) in entries\" :key=\"entry.kind === 'item' ? `item-${entry.index}` : 'ellipsis'\">\n <!--\n Разделитель — отдельный элемент списка и помечен `aria-hidden`:\n структуру пути диктору сообщает сам список, а «/» он бы читал вслух.\n -->\n <li\n v-if=\"position > 0\"\n data-gr-breadcrumbs-separator\n :class=\"breadcrumbsSeparatorClass\"\n aria-hidden=\"true\"\n >\n <slot name=\"separator\">\n {{ resolvedSeparator }}\n </slot>\n </li>\n\n <li v-if=\"entry.kind === 'ellipsis'\" data-gr-breadcrumbs-ellipsis-item class=\"min-w-0\">\n <slot name=\"ellipsis\" :hidden-count=\"entry.hiddenCount\" :expand=\"expand\">\n <button\n type=\"button\"\n data-gr-breadcrumbs-ellipsis\n data-testid=\"gr-breadcrumbs-ellipsis\"\n :class=\"breadcrumbsEllipsisClass\"\n :aria-label=\"resolvedExpandLabel\"\n @click=\"expand\"\n >\n …\n </button>\n </slot>\n </li>\n\n <li v-else data-gr-breadcrumbs-item-wrap class=\"min-w-0\">\n <GrLink\n v-if=\"isLink(entry.item, entry.index)\"\n :key=\"`link-${entry.index}`\"\n data-gr-breadcrumbs-item\n :as=\"as\"\n :href=\"entry.item.href\"\n :to=\"entry.item.to\"\n :size=\"resolvedSize\"\n :variant=\"isCurrent(entry.index) ? 'default' : 'muted'\"\n :aria-label=\"entry.item.ariaLabel\"\n :aria-current=\"isCurrent(entry.index) ? 'page' : undefined\"\n >\n <slot name=\"item\" :item=\"entry.item\" :index=\"entry.index\" :is-current=\"isCurrent(entry.index)\">\n <span v-if=\"entry.item.icon\" :class=\"[breadcrumbsItemIconClass, entry.item.icon]\" aria-hidden=\"true\" />\n <span :class=\"breadcrumbsLabelClass\">{{ entry.item.label }}</span>\n </slot>\n </GrLink>\n\n <!--\n Текущая страница и выключенный пункт — не ссылки. `aria-current`\n остаётся на текущем: именно он отвечает на вопрос «где я».\n -->\n <span\n v-else\n data-gr-breadcrumbs-item\n :class=\"breadcrumbsCurrentClass\"\n :aria-label=\"entry.item.ariaLabel\"\n :aria-current=\"isCurrent(entry.index) ? 'page' : undefined\"\n :aria-disabled=\"entry.item.disabled ? 'true' : undefined\"\n tabindex=\"-1\"\n >\n <slot name=\"item\" :item=\"entry.item\" :index=\"entry.index\" :is-current=\"isCurrent(entry.index)\">\n <span v-if=\"entry.item.icon\" :class=\"[breadcrumbsItemIconClass, entry.item.icon]\" aria-hidden=\"true\" />\n <span :class=\"breadcrumbsLabelClass\">{{ entry.item.label }}</span>\n </slot>\n </span>\n </li>\n </template>\n </ol>\n </nav>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'\n\nimport GrLink from '../GrLink/GrLink.vue'\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\n\nimport {\n breadcrumbsCurrentClass,\n breadcrumbsEllipsisClass,\n breadcrumbsItemIconClass,\n breadcrumbsLabelClass,\n breadcrumbsListClass,\n breadcrumbsListNowrapClass,\n breadcrumbsListWrapClass,\n breadcrumbsRootClass,\n breadcrumbsSeparatorClass,\n breadcrumbsSizeClassBySize,\n type GrBreadcrumbItem,\n type GrBreadcrumbsLinkComponent,\n type GrBreadcrumbsSize,\n resolveBreadcrumbsFit,\n resolveBreadcrumbsLayout,\n} from './grBreadcrumbsStyles'\n\nexport type { GrBreadcrumbItem, GrBreadcrumbsSize } from './grBreadcrumbsStyles'\n\n/**\n * Путь до текущей страницы (WAI-ARIA breadcrumb): `<nav>` с именем, внутри\n * упорядоченный список ссылок, последний пункт — текущая страница.\n *\n * Ссылки рисует `GrLink`, поэтому роутер подключается тем же способом, что и\n * везде в пакете: `as` + `to`.\n */\nexport interface GrBreadcrumbsProps {\n /** Путь от корня к текущей странице. Последний пункт — текущая страница. */\n items: GrBreadcrumbItem[]\n /** Компонент ссылки для всех пунктов: `RouterLink`, `NuxtLink`, `Link` Inertia. */\n as?: GrBreadcrumbsLinkComponent\n /** Разделитель между пунктами. Декоративен: диктору не читается. */\n separator?: string\n size?: GrBreadcrumbsSize\n /**\n * С какого числа пунктов схлопывать середину в «…». `0`/не задано — показывать\n * путь целиком.\n */\n maxItems?: number\n /** Сколько пунктов оставить в начале при схлопывании. */\n itemsBeforeCollapse?: number\n /** Сколько пунктов оставить в конце при схлопывании. */\n itemsAfterCollapse?: number\n /** Имя лендмарка. Не задано — берётся из локали. */\n ariaLabel?: string\n /** Последний пункт остаётся ссылкой (текущая страница кликабельна). */\n linkCurrent?: boolean\n /** i18n-метка кнопки раскрытия схлопнутого пути. */\n expandLabel?: string\n /**\n * Схлопывать по доступной ширине, а не по числу пунктов. Путь становится\n * однострочным, середина уходит под «…» ровно настолько, насколько не влезает.\n *\n * Без пропа поведение прежнее: список переносится на вторую строку, а порог\n * задаёт `maxItems`. Вместе пропы совместимы — `maxItems` остаётся жёстким\n * потолком, ширина ужимает дальше.\n */\n autoCollapse?: boolean\n}\n\nconst props = withDefaults(defineProps<GrBreadcrumbsProps>(), {\n as: undefined,\n separator: undefined,\n size: undefined,\n maxItems: undefined,\n itemsBeforeCollapse: 1,\n itemsAfterCollapse: 1,\n ariaLabel: undefined,\n linkCurrent: false,\n expandLabel: undefined,\n autoCollapse: false,\n})\n\ndefineSlots<{\n /** Содержимое пункта целиком (иконка и подпись). */\n item?: (props: { item: GrBreadcrumbItem, index: number, isCurrent: boolean }) => unknown\n /** Разделитель между пунктами. */\n separator?: () => unknown\n /** Кнопка раскрытия схлопнутой середины. */\n ellipsis?: (props: { hiddenCount: number, expand: () => void }) => unknown\n}>()\n\nconst { t } = useGranularityTranslations()\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrBreadcrumbs' })\nconst resolvedSeparator = useGrComponentProp('GrBreadcrumbs', 'separator', () => props.separator, '/')\n\nconst resolvedAriaLabel = computed(() => props.ariaLabel ?? t('gr.breadcrumbs.label', 'Breadcrumb'))\nconst resolvedExpandLabel = computed(() => props.expandLabel ?? t('gr.breadcrumbs.expand', 'Show hidden path'))\n\nconst rootEl = ref<HTMLElement | null>(null)\nconst listEl = ref<HTMLElement | null>(null)\nconst expanded = ref(false)\n\n/**\n * Схлопывание по ширине: сколько пунктов хвоста влезает.\n *\n * `undefined` — ещё не мерили, показываем путь целиком. Первый кадр в режиме\n * `autoCollapse` намеренно полный: ширины снимаются с него, а посчитать их до\n * рендера неоткуда.\n */\nconst fitAfter = ref<number | undefined>(undefined)\n\n/** Ширины не зависят от контейнера, поэтому живут до смены самого пути. */\nlet measured: { items: number[], separator: number } | null = null\n/**\n * Ширина кнопки «…» узнаётся только когда она отрисована, то есть уже после\n * первого схлопывания. До этого считаем её нулевой: поправка может лишь\n * ужать хвост ещё на пункт, а не вернуть спрятанное, поэтому качелей не будет.\n */\nlet ellipsisWidth = 0\n/** Ширина, на которой считали в прошлый раз: по её смене раскрытый путь сворачивается. */\nlet lastAvailable = 0\n\n// Новый путь — новая страница: раскрытая середина прошлого пути к ней отношения\n// не имеет, а измеренные ширины относятся к прошлым подписям.\nwatch(() => props.items, () => {\n expanded.value = false\n measured = null\n ellipsisWidth = 0\n fitAfter.value = undefined\n void nextTick(measure)\n})\n\nconst lastIndex = computed(() => props.items.length - 1)\n\n/**\n * Схлопывание по ширине выражается через тот же `maxItems`: раскладку считает\n * одна функция, а режимы отличаются только тем, откуда взялось число.\n */\nconst autoMaxItems = computed(() => {\n if (!props.autoCollapse || fitAfter.value === undefined) return undefined\n if (fitAfter.value >= props.items.length) return undefined\n\n return props.items.length - 1\n})\n\nconst effectiveMaxItems = computed(() => {\n const limits = [props.maxItems, autoMaxItems.value].filter((value): value is number => typeof value === 'number' && value > 0)\n\n return limits.length > 0 ? Math.min(...limits) : undefined\n})\n\nconst effectiveAfter = computed(() => (\n props.autoCollapse && fitAfter.value !== undefined ? fitAfter.value : props.itemsAfterCollapse\n))\n\nconst entries = computed(() => resolveBreadcrumbsLayout({\n items: props.items,\n maxItems: effectiveMaxItems.value,\n itemsBeforeCollapse: props.itemsBeforeCollapse,\n itemsAfterCollapse: effectiveAfter.value,\n expanded: expanded.value,\n}))\n\nfunction isCurrent(index: number): boolean {\n return index === lastIndex.value\n}\n\n/** Ссылкой пункт становится, только если ведёт куда-то и не выключен. */\nfunction isLink(item: GrBreadcrumbItem, index: number): boolean {\n if (item.disabled) return false\n if (isCurrent(index) && !props.linkCurrent) return false\n return item.href !== undefined || item.to !== undefined || props.as !== undefined\n}\n\nfunction widthOf(el: Element | null | undefined): number {\n // `scrollWidth`, а не прямоугольник: у подписи стоит `truncate`, и в узком\n // контейнере прямоугольник вернул бы уже ужатую ширину, а не собственную.\n return el ? (el as HTMLElement).scrollWidth : 0\n}\n\n/**\n * Снять ширины с полного пути и пересчитать, сколько пунктов хвоста влезает.\n *\n * Замер делается один раз на путь: подписи от ширины контейнера не зависят,\n * поэтому `resize` пересчитывает только арифметику.\n */\nfunction measure(): void {\n if (!props.autoCollapse) return\n\n const list = listEl.value\n if (!list) return\n\n const ellipsis = list.querySelector('[data-gr-breadcrumbs-ellipsis-item]')\n if (ellipsis) ellipsisWidth = widthOf(ellipsis)\n\n if (!measured) {\n // Ширины снимаем только с полного пути: спрятанных пунктов в DOM нет.\n if (fitAfter.value !== undefined) return\n\n const wraps = [...list.querySelectorAll('[data-gr-breadcrumbs-item-wrap]')]\n if (wraps.length !== props.items.length) return\n\n measured = {\n items: wraps.map(widthOf),\n separator: widthOf(list.querySelector('[data-gr-breadcrumbs-separator]')),\n }\n }\n\n const available = list.clientWidth\n // jsdom и скрытый контейнер отдают 0 — решать по такой ширине нечего.\n if (available <= 0) return\n\n lastAvailable = available\n\n fitAfter.value = resolveBreadcrumbsFit({\n itemWidths: measured.items,\n separatorWidth: measured.separator,\n ellipsisWidth,\n available,\n itemsBeforeCollapse: props.itemsBeforeCollapse,\n gapWidth: listGap(list),\n })\n}\n\n/** Зазор списка в пикселях: он задан классом, а не пропом, поэтому спрашиваем стиль. */\nfunction listGap(list: HTMLElement): number {\n if (typeof getComputedStyle !== 'function') return 0\n\n return Number.parseFloat(getComputedStyle(list).columnGap) || 0\n}\n\n/**\n * Ширина кнопки «…» известна только после того, как она отрисована, то есть\n * после первого схлопывания. Поэтому решение пересчитывается ещё раз: поправка\n * либо ничего не меняет, либо ужимает хвост на пункт. Цикла нет — второй проход\n * оставляет `fitAfter` прежним, и наблюдатель больше не срабатывает.\n */\nwatch(fitAfter, () => void nextTick(measure))\n\nlet observer: ResizeObserver | null = null\n\nwatch(() => props.autoCollapse, (enabled) => {\n if (enabled) {\n void nextTick(measure)\n return\n }\n\n measured = null\n ellipsisWidth = 0\n fitAfter.value = undefined\n})\n\nonMounted(() => {\n void nextTick(measure)\n\n // На сервере и в jsdom `ResizeObserver` отсутствует — измерять там нечего.\n if (typeof ResizeObserver === 'undefined') return\n\n observer = new ResizeObserver(() => {\n // Смена ширины — новая раскладка, и раскрытая руками середина относилась к\n // прежней. Свернуть её иначе нечем: в раскрытом пути кнопки «…» нет, то есть\n // поворот телефона оставлял бы путь развёрнутым навсегда. Высота меняется от\n // самого раскрытия (путь переносится на вторую строку) — на неё не реагируем.\n if (expanded.value && listEl.value && listEl.value.clientWidth !== lastAvailable)\n expanded.value = false\n\n measure()\n })\n if (listEl.value) observer.observe(listEl.value)\n})\n\nonBeforeUnmount(() => observer?.disconnect())\n\nasync function expand(): Promise<void> {\n expanded.value = true\n\n // Кнопка исчезает вместе со схлопыванием, и фокус ушёл бы на `<body>`.\n // Переводим его на первый раскрытый пункт — там, где кнопка и стояла. Пункты\n // без ссылки держат `tabindex=\"-1\"` именно ради этого случая.\n await nextTick()\n const before = Math.max(0, Math.min(props.itemsBeforeCollapse, props.items.length))\n const revealed = rootEl.value?.querySelectorAll<HTMLElement>('[data-gr-breadcrumbs-item]')\n revealed?.[before]?.focus()\n}\n</script>\n\n<template>\n <nav\n ref=\"rootEl\"\n data-gr-breadcrumbs\n :class=\"[breadcrumbsRootClass, breadcrumbsSizeClassBySize[resolvedSize]]\"\n :aria-label=\"resolvedAriaLabel\"\n >\n <ol\n ref=\"listEl\"\n data-gr-breadcrumbs-list\n :class=\"[breadcrumbsListClass, autoCollapse && !expanded ? breadcrumbsListNowrapClass : breadcrumbsListWrapClass]\"\n >\n <template v-for=\"(entry, position) in entries\" :key=\"entry.kind === 'item' ? `item-${entry.index}` : 'ellipsis'\">\n <!--\n Разделитель — отдельный элемент списка и помечен `aria-hidden`:\n структуру пути диктору сообщает сам список, а «/» он бы читал вслух.\n -->\n <li\n v-if=\"position > 0\"\n data-gr-breadcrumbs-separator\n :class=\"breadcrumbsSeparatorClass\"\n aria-hidden=\"true\"\n >\n <slot name=\"separator\">\n {{ resolvedSeparator }}\n </slot>\n </li>\n\n <li v-if=\"entry.kind === 'ellipsis'\" data-gr-breadcrumbs-ellipsis-item class=\"min-w-0\">\n <slot name=\"ellipsis\" :hidden-count=\"entry.hiddenCount\" :expand=\"expand\">\n <button\n type=\"button\"\n data-gr-breadcrumbs-ellipsis\n data-testid=\"gr-breadcrumbs-ellipsis\"\n :class=\"breadcrumbsEllipsisClass\"\n :aria-label=\"resolvedExpandLabel\"\n @click=\"expand\"\n >\n …\n </button>\n </slot>\n </li>\n\n <li v-else data-gr-breadcrumbs-item-wrap class=\"min-w-0\">\n <GrLink\n v-if=\"isLink(entry.item, entry.index)\"\n :key=\"`link-${entry.index}`\"\n data-gr-breadcrumbs-item\n :as=\"as\"\n :href=\"entry.item.href\"\n :to=\"entry.item.to\"\n :size=\"resolvedSize\"\n :variant=\"isCurrent(entry.index) ? 'default' : 'muted'\"\n :aria-label=\"entry.item.ariaLabel\"\n :aria-current=\"isCurrent(entry.index) ? 'page' : undefined\"\n >\n <slot name=\"item\" :item=\"entry.item\" :index=\"entry.index\" :is-current=\"isCurrent(entry.index)\">\n <span v-if=\"entry.item.icon\" :class=\"[breadcrumbsItemIconClass, entry.item.icon]\" aria-hidden=\"true\" />\n <span :class=\"breadcrumbsLabelClass\">{{ entry.item.label }}</span>\n </slot>\n </GrLink>\n\n <!--\n Текущая страница и выключенный пункт — не ссылки. `aria-current`\n остаётся на текущем: именно он отвечает на вопрос «где я».\n -->\n <span\n v-else\n data-gr-breadcrumbs-item\n :class=\"breadcrumbsCurrentClass\"\n :aria-label=\"entry.item.ariaLabel\"\n :aria-current=\"isCurrent(entry.index) ? 'page' : undefined\"\n :aria-disabled=\"entry.item.disabled ? 'true' : undefined\"\n tabindex=\"-1\"\n >\n <slot name=\"item\" :item=\"entry.item\" :index=\"entry.index\" :is-current=\"isCurrent(entry.index)\">\n <span v-if=\"entry.item.icon\" :class=\"[breadcrumbsItemIconClass, entry.item.icon]\" aria-hidden=\"true\" />\n <span :class=\"breadcrumbsLabelClass\">{{ entry.item.label }}</span>\n </slot>\n </span>\n </li>\n </template>\n </ol>\n </nav>\n</template>\n"],"mappings":"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"}
|