@feugene/granularity 0.31.0 → 0.31.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -1
- package/dist/chunks/adapter-CAOxmn2G.js.map +1 -1
- package/dist/chunks/{config-BHcS5Cwm.js → config-87fszqnD.js} +1 -1
- package/dist/chunks/{config-BHcS5Cwm.js.map → config-87fszqnD.js.map} +1 -1
- package/dist/chunks/config-A-bBiotF.js.map +1 -1
- package/dist/chunks/config-B3J0alyY.js.map +1 -1
- package/dist/chunks/config-BhfNgyWL.js.map +1 -1
- package/dist/chunks/config-Bjn30smH.js.map +1 -1
- package/dist/chunks/config-BqT6PMzO.js.map +1 -1
- package/dist/chunks/config-C0Ea9ePP.js.map +1 -1
- package/dist/chunks/config-C0Eu8jpG.js.map +1 -1
- package/dist/chunks/config-CDat1wXf.js.map +1 -1
- package/dist/chunks/config-CQLNjc9X2.js.map +1 -1
- package/dist/chunks/config-CbEbLyDE.js.map +1 -1
- package/dist/chunks/{config-jyr6AIZt2.js → config-CuQmUkMw2.js} +2 -2
- package/dist/chunks/{config-jyr6AIZt2.js.map → config-CuQmUkMw2.js.map} +1 -1
- package/dist/chunks/config-DeyT9dJl.js.map +1 -1
- package/dist/chunks/config-DnF472Ox.js.map +1 -1
- package/dist/chunks/config-DwABJHEo.js.map +1 -1
- package/dist/chunks/{config-tiUtEn4Q.js → config-E1cTyRVV.js} +1 -1
- package/dist/chunks/{config-tiUtEn4Q.js.map → config-E1cTyRVV.js.map} +1 -1
- package/dist/chunks/config-MmPym-Z72.js.map +1 -1
- package/dist/chunks/config-T3-DMXYm.js.map +1 -1
- package/dist/chunks/config-Yy67uWHY.js.map +1 -1
- package/dist/chunks/config-_jbgsFpw.js.map +1 -1
- package/dist/chunks/config-g8-JGZ_Y.js.map +1 -1
- package/dist/chunks/config-mlheIMtV.js.map +1 -1
- package/dist/chunks/config-rLkc9Zn2.js.map +1 -1
- package/dist/chunks/config-uzUuosv7.js.map +1 -1
- package/dist/chunks/context-CTbuwcYs.js.map +1 -1
- package/dist/chunks/deltaTone-B5OoS1vo.js.map +1 -1
- package/dist/chunks/es-Do2CNrC2.js.map +1 -1
- package/dist/chunks/fileValidation-D5TFBhbv.js.map +1 -1
- package/dist/chunks/focusables-BkzK42SQ.js.map +1 -1
- package/dist/chunks/{generated-DJ2dJ__C.js → generated-CSyPsWtM.js} +1 -1
- package/dist/chunks/{generated-DJ2dJ__C.js.map → generated-CSyPsWtM.js.map} +1 -1
- package/dist/chunks/grBadgeStyles-D8Mz73B5.js.map +1 -1
- package/dist/chunks/{grButtonStyles-B_-YRE__.js → grButtonStyles-B3wRWW_i.js} +4 -4
- package/dist/chunks/grButtonStyles-B3wRWW_i.js.map +1 -0
- package/dist/chunks/grLinkStyles-FME_qMpi.js.map +1 -1
- package/dist/chunks/grLoadingStyles-CnQaf5ry.js.map +1 -1
- package/dist/chunks/{grRadioStyles-D4gnU65n.js → grRadioStyles-DGL2QLy6.js} +1 -1
- package/dist/chunks/{grRadioStyles-D4gnU65n.js.map → grRadioStyles-DGL2QLy6.js.map} +1 -1
- package/dist/chunks/grSelectStyles-Cdtd10eY.js.map +1 -1
- package/dist/chunks/{grSliderStyles-B5uNxNJq.js → grSliderStyles-kaz6VXZ3.js} +9 -1
- package/dist/chunks/grSliderStyles-kaz6VXZ3.js.map +1 -0
- package/dist/chunks/{granular-provider-yJNX1g1G.js → granular-provider-Da2CJO6j.js} +3 -3
- package/dist/chunks/{granular-provider-yJNX1g1G.js.map → granular-provider-Da2CJO6j.js.map} +1 -1
- package/dist/chunks/granularityI18n-BC5zsj_d.js.map +1 -1
- package/dist/chunks/icon-Dag2tIMs.js.map +1 -1
- package/dist/chunks/inert-DIHCCn_1.js.map +1 -1
- package/dist/chunks/keyboard-DE2yhz_b.js.map +1 -1
- package/dist/chunks/numericStep-Bq2lj4ew.js.map +1 -1
- package/dist/chunks/optionFilter-DYw9XPPx.js.map +1 -1
- package/dist/chunks/overlayStack-C8cfsqXa.js.map +1 -1
- package/dist/chunks/portalRoot-CVhCgdG6.js.map +1 -1
- package/dist/chunks/renamedProp-BwcN-SJg.js.map +1 -1
- package/dist/chunks/{safelist-BOq5Li7p.js → safelist-DxkMj5IW.js} +1 -1
- package/dist/chunks/{safelist-BOq5Li7p.js.map → safelist-DxkMj5IW.js.map} +1 -1
- package/dist/chunks/slotNodes-DxNXpC7v.js.map +1 -1
- package/dist/chunks/useControlAddons-asUr10Pf.js.map +1 -1
- package/dist/chunks/useControlledOpen-DV3SqLTP.js.map +1 -1
- package/dist/chunks/useDragSort-0l47h4yb.js.map +1 -1
- package/dist/chunks/useFilePreviews-DnwgdyBs.js.map +1 -1
- package/dist/chunks/useFocusWithin-BwHE0rzm.js.map +1 -1
- package/dist/chunks/useModalOverlay-Cv5hF63W.js.map +1 -1
- package/dist/chunks/usePortalTarget-C0p-mhaM.js.map +1 -1
- package/dist/chunks/validation-5HhpanPP.js.map +1 -1
- package/dist/components/GrAlert/chunks/GrAlert-FAVf-ksH.js.map +1 -1
- package/dist/components/GrAutocomplete/chunks/{GrAutocomplete-X6RlmiJQ.js → GrAutocomplete-rYEY15UI.js} +1 -1
- package/dist/components/GrAutocomplete/chunks/GrAutocomplete-rYEY15UI.js.map +1 -0
- package/dist/components/GrAutocomplete/index.js +1 -1
- package/dist/components/GrAutocomplete/styles.css +1 -1
- package/dist/components/GrAvatar/chunks/GrAvatar-CNmLmgcr.js.map +1 -1
- package/dist/components/GrBadge/chunks/{GrBadge-BsoocpF-.js → GrBadge-D2MfThPz.js} +1 -1
- package/dist/components/GrBadge/chunks/GrBadge-D2MfThPz.js.map +1 -0
- package/dist/components/GrBadge/index.js +1 -1
- package/dist/components/GrBadge/styles.css +1 -1
- package/dist/components/GrBadgeWrap/chunks/GrBadgeWrap-B-A3PRkE.js.map +1 -1
- package/dist/components/GrBottomNav/chunks/GrBottomNav-wtFatLjB.js.map +1 -1
- package/dist/components/GrBreadcrumbs/chunks/GrBreadcrumbs-DRDNan_n.js.map +1 -1
- package/dist/components/GrButton/chunks/{GrButton-Bw2AH4uX.js → GrButton-7GP0zObj.js} +1 -1
- package/dist/components/GrButton/chunks/GrButton-7GP0zObj.js.map +1 -0
- package/dist/components/GrButton/index.js +3 -3
- package/dist/components/GrButtonGroup/chunks/GrButtonGroup-CKyiAV8K.js.map +1 -1
- package/dist/components/GrCard/chunks/GrCard-BttXz1pQ.js.map +1 -1
- package/dist/components/GrCheckbox/chunks/GrCheckbox-CEcHIsXM.js.map +1 -1
- package/dist/components/GrCheckboxGroup/chunks/GrCheckboxGroup-BOwrVcFr.js.map +1 -1
- package/dist/components/GrChip/chunks/GrChip-C0vU69tf.js.map +1 -1
- package/dist/components/GrChipGroup/chunks/GrChipGroup-C78Vcwos.js.map +1 -1
- package/dist/components/GrCodeBlock/chunks/{GrCodeBlock-Dkvkr91t.js → GrCodeBlock-Dp0eEBTZ.js} +1 -1
- package/dist/components/GrCodeBlock/chunks/GrCodeBlock-Dp0eEBTZ.js.map +1 -0
- package/dist/components/GrCodeBlock/index.js +1 -1
- package/dist/components/GrCollapse/chunks/GrCollapse-BlW6ybdL.js.map +1 -1
- package/dist/components/GrColorPicker/chunks/{GrColorPicker-C7ifyQ0R.js → GrColorPicker-B20gQ6AR.js} +1 -1
- package/dist/components/GrColorPicker/chunks/GrColorPicker-B20gQ6AR.js.map +1 -0
- package/dist/components/GrColorPicker/index.js +1 -1
- package/dist/components/GrCommandPalette/chunks/{GrCommandPalette-C5abL2Y7.js → GrCommandPalette-Cd0Yo3Dw.js} +1 -1
- package/dist/components/GrCommandPalette/chunks/GrCommandPalette-Cd0Yo3Dw.js.map +1 -0
- package/dist/components/GrCommandPalette/index.js +1 -1
- package/dist/components/GrCommandPalette/styles.css +1 -1
- package/dist/components/GrConfigProvider/chunks/GrConfigProvider-BF7R3hgg.js.map +1 -1
- package/dist/components/GrConfirmDialog/chunks/{GrConfirmDialog-B9mIU78E.js → GrConfirmDialog-DOsbRhoZ.js} +3 -3
- package/dist/components/GrConfirmDialog/chunks/GrConfirmDialog-DOsbRhoZ.js.map +1 -0
- package/dist/components/GrConfirmDialog/index.js +1 -1
- package/dist/components/GrContextMenu/chunks/GrContextMenu-_FKwQvuQ.js.map +1 -1
- package/dist/components/GrDataTable/chunks/{GrDataTable-C8a9_nCg.js → GrDataTable-CAGgRk27.js} +5 -3
- package/dist/components/GrDataTable/chunks/GrDataTable-CAGgRk27.js.map +1 -0
- package/dist/components/GrDataTable/index.js +1 -1
- package/dist/components/GrDelta/chunks/GrDelta-BZwkgQW-.js.map +1 -1
- package/dist/components/GrDescriptionList/chunks/GrDescriptionList-BMeCgFV3.js.map +1 -1
- package/dist/components/GrDialog/chunks/{GrDialog-bOUDYfmh.js → GrDialog-S1Vl-OhX.js} +1 -1
- package/dist/components/GrDialog/chunks/{GrDialog-bOUDYfmh.js.map → GrDialog-S1Vl-OhX.js.map} +1 -1
- package/dist/components/GrDialog/index.js +1 -1
- package/dist/components/GrDialogService/chunks/{GrDialogService-6s2cQ-_6.js → GrDialogService-Cuhc4YLp.js} +4 -4
- package/dist/components/GrDialogService/chunks/GrDialogService-Cuhc4YLp.js.map +1 -0
- package/dist/components/GrDialogService/index.js +1 -1
- package/dist/components/GrDivider/chunks/GrDivider-BXavGcMX.js.map +1 -1
- package/dist/components/GrDrawer/chunks/{GrDrawer-B7PiAcbU.js → GrDrawer-C0YqVE9e.js} +1 -1
- package/dist/components/GrDrawer/chunks/GrDrawer-C0YqVE9e.js.map +1 -0
- package/dist/components/GrDrawer/index.js +1 -1
- package/dist/components/GrDropdown/chunks/GrDropdown-13Wt9eB_.js.map +1 -1
- package/dist/components/GrDropdownMenu/chunks/GrDropdownMenu-GOzuAXsi.js.map +1 -1
- package/dist/components/GrDropdownMenu/chunks/GrDropdownMenuList-B33ZzwfB.js.map +1 -1
- package/dist/components/GrFilePreview/chunks/GrFilePreview-C9Z7hMK8.js.map +1 -1
- package/dist/components/GrFileUpload/chunks/GrFileUpload-NsHkRRIa.js.map +1 -1
- package/dist/components/GrForm/chunks/GrForm-CrnfCsgj.js.map +1 -1
- package/dist/components/GrFormField/chunks/GrFormField-DSylU7IU.js.map +1 -1
- package/dist/components/GrFormFile/chunks/{GrFormFile-CYtXU9HV.js → GrFormFile-B1jGZyIj.js} +2 -2
- package/dist/components/GrFormFile/chunks/GrFormFile-B1jGZyIj.js.map +1 -0
- package/dist/components/GrFormFile/index.js +1 -1
- package/dist/components/GrFormFile/styles.css +1 -1
- package/dist/components/GrIcon/chunks/GrIcon-Dmf0c9ba.js.map +1 -1
- package/dist/components/GrImageViewer/chunks/GrImageViewer-CyVN6Zpc.js.map +1 -1
- package/dist/components/GrInput/chunks/GrInput-BLywRZUN.js.map +1 -1
- package/dist/components/GrInputTag/chunks/GrInputTag-Cj4A9Xgs.js.map +1 -1
- package/dist/components/GrJsonViewer/chunks/{GrJsonViewer-Cy2bknHO.js → GrJsonViewer-BCZ7lE2k.js} +2 -2
- package/dist/components/GrJsonViewer/chunks/GrJsonViewer-BCZ7lE2k.js.map +1 -0
- package/dist/components/GrJsonViewer/index.js +1 -1
- package/dist/components/GrKbd/chunks/GrKbd-D2ga1H9i.js.map +1 -1
- package/dist/components/GrLink/chunks/GrLink-DYXw3W9w.js.map +1 -1
- package/dist/components/GrList/chunks/{GrList-DmT4qzLO.js → GrList-B_CRJB4c.js} +1 -1
- package/dist/components/GrList/chunks/GrList-B_CRJB4c.js.map +1 -0
- package/dist/components/GrList/index.js +1 -1
- package/dist/components/GrList/styles.css +1 -1
- package/dist/components/GrModal/chunks/GrModal-CCoWZuIl.js.map +1 -1
- package/dist/components/GrNavbar/chunks/{GrNavbar-C5EjMKaC.js → GrNavbar-_rtHKwTf.js} +1 -1
- package/dist/components/GrNavbar/chunks/{GrNavbar-C5EjMKaC.js.map → GrNavbar-_rtHKwTf.js.map} +1 -1
- package/dist/components/GrNavbar/index.js +1 -1
- package/dist/components/GrNumberInput/chunks/GrNumberInput-DQ59YICu.js.map +1 -1
- package/dist/components/GrPagination/chunks/{GrPagination-Cw3R21dp.js → GrPagination-B_kAhoAV.js} +2 -2
- package/dist/components/GrPagination/chunks/{GrPagination-Cw3R21dp.js.map → GrPagination-B_kAhoAV.js.map} +1 -1
- package/dist/components/GrPagination/index.js +1 -1
- package/dist/components/GrPopover/chunks/GrPopover-CofG-Tfe.js.map +1 -1
- package/dist/components/GrPromptDialog/chunks/{GrPromptDialog-DLcl1xiq.js → GrPromptDialog-BuJQMZLM.js} +4 -4
- package/dist/components/GrPromptDialog/chunks/GrPromptDialog-BuJQMZLM.js.map +1 -0
- package/dist/components/GrPromptDialog/index.js +1 -1
- package/dist/components/GrRadio/chunks/{GrRadio-C_rESc5g.js → GrRadio-Aq3v8XgG.js} +1 -1
- package/dist/components/GrRadio/chunks/GrRadio-Aq3v8XgG.js.map +1 -0
- package/dist/components/GrRadio/index.js +2 -2
- package/dist/components/GrRadioGroup/chunks/{GrRadioGroup-CdQO4G-U.js → GrRadioGroup-y9LVdTWh.js} +1 -1
- package/dist/components/GrRadioGroup/chunks/GrRadioGroup-y9LVdTWh.js.map +1 -0
- package/dist/components/GrRadioGroup/index.js +1 -1
- package/dist/components/GrRating/chunks/GrRating-IEiTOdnF.js.map +1 -1
- package/dist/components/GrResponseErrorBanner/chunks/{GrResponseErrorBanner-C_skObtv.js → GrResponseErrorBanner-CBVRpgIG.js} +1 -1
- package/dist/components/GrResponseErrorBanner/chunks/{GrResponseErrorBanner-C_skObtv.js.map → GrResponseErrorBanner-CBVRpgIG.js.map} +1 -1
- package/dist/components/GrResponseErrorBanner/chunks/{GrResponseErrorBanner-DudGc9LO.js → GrResponseErrorBanner-DMeOWlzj.js} +1 -1
- package/dist/components/GrResponseErrorBanner/chunks/GrResponseErrorBanner-DMeOWlzj.js.map +1 -0
- package/dist/components/GrResponseErrorBanner/index.js +2 -2
- package/dist/components/GrSegmented/chunks/GrSegmented-CB-VR-yL.js.map +1 -1
- package/dist/components/GrSelect/chunks/{GrSelect-BiUrSSxc.js → GrSelect-gCeRFgQo.js} +1 -1
- package/dist/components/GrSelect/chunks/GrSelect-gCeRFgQo.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-B8EQIlfW.js → GrSidebar-alkwBUzL.js} +1 -1
- package/dist/components/GrSidebar/chunks/GrSidebar-alkwBUzL.js.map +1 -0
- package/dist/components/GrSidebar/index.js +1 -1
- package/dist/components/GrSkeleton/chunks/GrSkeleton-CgfXxPQ2.js.map +1 -1
- package/dist/components/GrSlider/chunks/{GrSlider-DJfeEl4h.js → GrSlider-DTTrZTHK.js} +3 -3
- package/dist/components/GrSlider/chunks/GrSlider-DTTrZTHK.js.map +1 -0
- package/dist/components/GrSlider/index.js +2 -2
- package/dist/components/GrSortableList/chunks/GrSortableList-HyKX--rO.js.map +1 -1
- package/dist/components/GrSplitter/chunks/GrSplitter-MltjaADn.js.map +1 -1
- package/dist/components/GrStatistic/chunks/GrStatistic-qHVasK37.js.map +1 -1
- package/dist/components/GrSteps/chunks/GrSteps-C2jxpZoN.js.map +1 -1
- package/dist/components/GrSwitch/chunks/{GrSwitch-g_OOSkh7.js → GrSwitch-7Oe5QfQZ.js} +1 -1
- package/dist/components/GrSwitch/chunks/GrSwitch-7Oe5QfQZ.js.map +1 -0
- package/dist/components/GrSwitch/index.js +1 -1
- package/dist/components/GrTabPanels/chunks/GrTabPanels-BZQS8-JM.js.map +1 -1
- package/dist/components/GrTable/chunks/GrTable-Zcdz2St_.js.map +1 -1
- package/dist/components/GrTextarea/chunks/{GrTextarea-KREK75Bu.js → GrTextarea-DinXG12-.js} +10 -10
- package/dist/components/GrTextarea/chunks/GrTextarea-DinXG12-.js.map +1 -0
- package/dist/components/GrTextarea/index.js +1 -1
- package/dist/components/GrTimeline/chunks/GrTimeline-BiHpArsg.js.map +1 -1
- package/dist/components/GrToaster/chunks/{GrToaster-Dp7-4B8k.js → GrToaster-COXIBKzq.js} +2 -2
- package/dist/components/GrToaster/chunks/GrToaster-COXIBKzq.js.map +1 -0
- package/dist/components/GrToaster/index.js +1 -1
- package/dist/components/GrToaster/styles.css +1 -1
- package/dist/components/GrTooltip/chunks/GrTooltip-uYETdbCD.js.map +1 -1
- package/dist/components/GrTree/chunks/{GrTree-DC89I4AP.js → GrTree-Ci6CZfKR.js} +5 -5
- package/dist/components/GrTree/chunks/GrTree-Ci6CZfKR.js.map +1 -0
- package/dist/components/GrTree/index.js +1 -1
- package/dist/components/GrTree/styles.css +1 -1
- package/dist/components/GrTreeSelect/chunks/{GrTreeSelect-iJnMl2t9.js → GrTreeSelect-BHwgGJcT.js} +1 -1
- package/dist/components/GrTreeSelect/chunks/GrTreeSelect-BHwgGJcT.js.map +1 -0
- package/dist/components/GrTreeSelect/index.js +1 -1
- package/dist/composables/useAnnouncer.js.map +1 -1
- package/dist/composables/useComboboxNavigation.js.map +1 -1
- package/dist/composables/useDragGesture.js.map +1 -1
- package/dist/composables/useFocusTrap.js.map +1 -1
- package/dist/composables/useOverlayLayer.js.map +1 -1
- package/dist/composables/useRovingFocus.js.map +1 -1
- package/dist/composables/useTheme.js.map +1 -1
- package/dist/composables/useToast.js.map +1 -1
- package/dist/composables/useVirtualList.js.map +1 -1
- package/dist/directives/autofocus.js.map +1 -1
- package/dist/directives/autosize.js.map +1 -1
- package/dist/directives/clickOutside.js.map +1 -1
- package/dist/directives/dropzone.js.map +1 -1
- package/dist/directives/hotkey.js.map +1 -1
- package/dist/directives/loading.js.map +1 -1
- package/dist/granular-provider-node.js +1 -1
- package/dist/granular-provider.js +1 -1
- package/dist/index.js +33 -33
- package/dist/testing.js +2 -2
- package/dist/testing.js.map +1 -1
- package/dist/theme-apply.js.map +1 -1
- package/dist/theme.js +1 -1
- package/dist/theme.js.map +1 -1
- package/dist/tokens.js +1 -1
- package/dist/types/components/GrCodeBlock/tokenizeJson.d.ts +1 -3
- package/dist/types/components/GrImageViewer/GrImageViewer.vue.d.ts +1 -1
- package/dist/types/components/GrInput/GrInput.vue.d.ts +24 -24
- package/dist/types/components/GrSlider/grSliderStyles.d.ts +9 -1
- package/dist/types/components/GrTabPanels/GrTabPanel.vue.d.ts +3 -3
- package/dist/vue/index.js.map +1 -1
- package/dist/web-types.json +1 -1
- package/package.json +3 -3
- package/dist/chunks/grButtonStyles-B_-YRE__.js.map +0 -1
- package/dist/chunks/grSliderStyles-B5uNxNJq.js.map +0 -1
- package/dist/components/GrAutocomplete/chunks/GrAutocomplete-X6RlmiJQ.js.map +0 -1
- package/dist/components/GrBadge/chunks/GrBadge-BsoocpF-.js.map +0 -1
- package/dist/components/GrButton/chunks/GrButton-Bw2AH4uX.js.map +0 -1
- package/dist/components/GrCodeBlock/chunks/GrCodeBlock-Dkvkr91t.js.map +0 -1
- package/dist/components/GrColorPicker/chunks/GrColorPicker-C7ifyQ0R.js.map +0 -1
- package/dist/components/GrCommandPalette/chunks/GrCommandPalette-C5abL2Y7.js.map +0 -1
- package/dist/components/GrConfirmDialog/chunks/GrConfirmDialog-B9mIU78E.js.map +0 -1
- package/dist/components/GrDataTable/chunks/GrDataTable-C8a9_nCg.js.map +0 -1
- package/dist/components/GrDialogService/chunks/GrDialogService-6s2cQ-_6.js.map +0 -1
- package/dist/components/GrDrawer/chunks/GrDrawer-B7PiAcbU.js.map +0 -1
- package/dist/components/GrFormFile/chunks/GrFormFile-CYtXU9HV.js.map +0 -1
- package/dist/components/GrJsonViewer/chunks/GrJsonViewer-Cy2bknHO.js.map +0 -1
- package/dist/components/GrList/chunks/GrList-DmT4qzLO.js.map +0 -1
- package/dist/components/GrPromptDialog/chunks/GrPromptDialog-DLcl1xiq.js.map +0 -1
- package/dist/components/GrRadio/chunks/GrRadio-C_rESc5g.js.map +0 -1
- package/dist/components/GrRadioGroup/chunks/GrRadioGroup-CdQO4G-U.js.map +0 -1
- package/dist/components/GrResponseErrorBanner/chunks/GrResponseErrorBanner-DudGc9LO.js.map +0 -1
- package/dist/components/GrSelect/chunks/GrSelect-BiUrSSxc.js.map +0 -1
- package/dist/components/GrSidebar/chunks/GrSidebar-B8EQIlfW.js.map +0 -1
- package/dist/components/GrSlider/chunks/GrSlider-DJfeEl4h.js.map +0 -1
- package/dist/components/GrSwitch/chunks/GrSwitch-g_OOSkh7.js.map +0 -1
- package/dist/components/GrTextarea/chunks/GrTextarea-KREK75Bu.js.map +0 -1
- package/dist/components/GrToaster/chunks/GrToaster-Dp7-4B8k.js.map +0 -1
- package/dist/components/GrTree/chunks/GrTree-DC89I4AP.js.map +0 -1
- package/dist/components/GrTreeSelect/chunks/GrTreeSelect-iJnMl2t9.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation-5HhpanPP.js","names":[],"sources":["../../src/components/GrForm/fileRule.ts","../../src/components/GrForm/validation.ts"],"sourcesContent":["/**\n * Файловое правило `GrForm` — мост между декларативными `rules` и валидаторами\n * из `src/fileValidation`.\n *\n * Своих проверок здесь нет ни одной: правило собирает те же валидаторы, которые\n * `GrFormFile` и `v-dropzone` запускают на выборе и на drop. Поэтому ограничение,\n * переехавшее из пропов поля в правило формы, не меняет ни поведения, ни текста\n * ошибки — меняется только момент проверки и то, кто о ней сообщает.\n *\n * Модуль чистый (без Vue): его гоняют юнит-тесты без монтирования.\n */\n\nimport {\n acceptValidator,\n allowedExtensionsValidator,\n allowedMimeTypesValidator,\n maxCountValidator,\n maxFileSize,\n maxTotalSizeBytesValidator,\n runFileValidators,\n type FileValidationIssue,\n type FileValidator,\n} from '../../fileValidation'\n\nexport interface GrFormFileRule {\n /** W3C `accept`: `'image/*,.pdf'`. */\n accept?: string\n /** Белый список расширений (с точкой или без). */\n extensions?: string[]\n /** Белый список MIME-типов. */\n mimeTypes?: string[]\n /** Предел размера одного файла в мегабайтах. */\n maxSizeMb?: number\n /** Предел размера одного файла в байтах. Задан вместе с `maxSizeMb` — берётся меньший. */\n maxSizeBytes?: number\n /** Максимум файлов в наборе. */\n maxCount?: number\n /** Суммарный размер набора в мегабайтах. */\n maxTotalSizeMb?: number\n /** Свои валидаторы — тот же тип, что у `GrFormFile` и `v-dropzone`. Идут после встроенных. */\n validators?: FileValidator[]\n}\n\nconst BYTES_IN_MB = 1024 * 1024\n\nfunction mbToBytes(mb: number | undefined): number | undefined {\n return typeof mb === 'number' && Number.isFinite(mb) ? mb * BYTES_IN_MB : undefined\n}\n\nfunction isFile(value: unknown): value is File {\n return typeof File !== 'undefined' && value instanceof File\n}\n\n/**\n * Значение поля как набор файлов. Всё, что файлами не является, даёт пустой\n * набор: правило молчит, а пустоту разбирает `required` движка.\n */\nexport function fileRuleFiles(value: unknown): File[] {\n if (isFile(value)) return [value]\n if (Array.isArray(value)) return value.filter(isFile)\n return []\n}\n\n/** Порядок повторяет `GrFormFile`: встроенные проверки, затем валидаторы потребителя. */\nexport function fileRuleValidators(rule: GrFormFileRule): FileValidator[] {\n return [\n acceptValidator(rule.accept),\n ...(rule.extensions ? [allowedExtensionsValidator(rule.extensions)] : []),\n ...(rule.mimeTypes ? [allowedMimeTypesValidator(rule.mimeTypes)] : []),\n maxFileSize({ bytes: rule.maxSizeBytes, mb: rule.maxSizeMb }),\n maxCountValidator(rule.maxCount),\n maxTotalSizeBytesValidator(mbToBytes(rule.maxTotalSizeMb)),\n ...(rule.validators ?? []),\n ]\n}\n\n/**\n * Первая проблема набора или `undefined`. Первая, а не все: у поля формы одна\n * строка ошибки, и файлы тут не исключение — подробный разбор по каждому файлу\n * остаётся за `GrFormFile`.\n */\nexport async function runFileRule(\n value: unknown,\n rule: GrFormFileRule,\n): Promise<FileValidationIssue | undefined> {\n const files = fileRuleFiles(value)\n if (!files.length) return undefined\n\n const { issues } = await runFileValidators(files, fileRuleValidators(rule), {\n source: 'form',\n multiple: Array.isArray(value),\n accept: rule.accept,\n })\n\n return issues[0]\n}\n","/**\n * Движок валидации `GrForm` — чистый (без Vue/i18n), тестируется изолированно.\n *\n * Правило описывает набор проверок значения поля; `runFieldRules` прогоняет их по\n * порядку и возвращает первое сообщение об ошибке (или `undefined`, если валидно).\n * Сообщения резолвит потребитель (компонент) через `resolveMessage` — так i18n и\n * дефолтные тексты остаются в компоненте, а движок ничего о них не знает.\n */\n\nimport { resolveFileValidationMessage, type FileValidationIssue } from '../../fileValidation'\nimport { runFileRule, type GrFormFileRule } from './fileRule'\n\nexport type { GrFormFileRule } from './fileRule'\n\nexport type GrFormTrigger = 'blur' | 'change' | 'submit'\n\n/** Результат кастомного валидатора: `true`/void — ок; `false` — ошибка (дефолт-текст); строка — текст ошибки. */\nexport type GrFormValidatorResult = boolean | string | void\n\nexport interface GrFormRule {\n /** Значение обязательно (не пустое). */\n required?: boolean\n /** Готовый текст ошибки — перекрывает дефолтное сообщение любой проверки этого правила. */\n message?: string\n /** Минимум: длина для строки/массива, значение для числа. */\n min?: number\n /** Максимум: длина для строки/массива, значение для числа. */\n max?: number\n /** Точная длина/значение. */\n len?: number\n /** Регулярное выражение (проверяется по `String(value)`). */\n pattern?: RegExp\n /** Встроенный тип-валидатор. */\n type?: 'email' | 'url'\n /** Ограничения для значения `File | File[]`: тип, размер, количество. */\n file?: GrFormFileRule\n /** Кастомный (в т.ч. async) валидатор. Второй аргумент — весь `model`. */\n validator?: (value: unknown, model: Record<string, unknown>) => GrFormValidatorResult | Promise<GrFormValidatorResult>\n /** Когда правило срабатывает. По умолчанию — на любом триггере (blur/change/submit). */\n trigger?: GrFormTrigger | GrFormTrigger[]\n}\n\nexport type GrFormRules = Record<string, GrFormRule | GrFormRule[]>\n\n/** Вид сработавшей проверки — ключ для дефолтного i18n-сообщения. */\nexport type GrFormRuleFailure = 'required' | 'min' | 'max' | 'len' | 'pattern' | 'email' | 'url' | 'file' | 'invalid'\n\nexport type GrFormMessageResolver = (\n kind: GrFormRuleFailure,\n rule: GrFormRule,\n params: Record<string, unknown>,\n) => string\n\n/** Подпись `t()` из `useGranularityTranslations()`: ключ, фолбэк, параметры. */\nexport type GrFormMessageTranslate = (\n key: string,\n fallback: string,\n params?: Record<string, unknown>,\n) => string\n\nconst DEFAULT_MESSAGES: Record<GrFormRuleFailure, string> = {\n required: 'This field is required',\n min: 'Minimum is {min}',\n max: 'Maximum is {max}',\n len: 'Length must be {len}',\n pattern: 'Invalid format',\n email: 'Enter a valid email',\n url: 'Enter a valid URL',\n file: 'Invalid file',\n invalid: 'Invalid value',\n}\n\n/**\n * Дефолтный резолвер сообщений: `rule.message` сильнее всего, иначе ключ\n * `gr.form.<kind>` из локали, иначе английский фолбэк.\n *\n * Вынесен из `GrForm.vue` и экспортируется наружу не «на всякий случай»:\n * `runFieldRules` публичен, но без резолвера бесполезен — любой, кто хочет\n * прогнать те же правила вне формы, обязан был написать свой.\n */\nexport function createGrFormMessageResolver(t: GrFormMessageTranslate): GrFormMessageResolver {\n return (kind, rule, params) => {\n if (rule.message) return rule.message\n\n // Текст файловой ошибки приходит от самого валидатора и локализуется его же\n // ключом (`gr.fileValidation.*`): иначе одно и то же «файл не того типа»\n // звучало бы по-разному в поле и в форме. `gr.form.file` — фолбэк на случай\n // вида `file` без issue: сюда движок не приходит, но резолвер публичный.\n const issue = params.issue as FileValidationIssue | undefined\n if (kind === 'file' && issue) return resolveFileValidationMessage(issue, t)\n\n return t(`gr.form.${kind}`, DEFAULT_MESSAGES[kind], params)\n }\n}\n\nconst EMAIL_RE = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\n\nfunction isUrl(value: string): boolean {\n return URL.canParse(value)\n}\n\n/** Пустое значение: `null`/`undefined`/пустая (или пробельная) строка/пустой массив. */\nexport function isEmpty(value: unknown): boolean {\n if (value == null) return true\n if (typeof value === 'string') return value.trim() === ''\n if (Array.isArray(value)) return value.length === 0\n return false\n}\n\n/** Мера значения для min/max/len: число → само значение, строка/массив → длина. */\nfunction measure(value: unknown): number {\n if (typeof value === 'number') return value\n if (typeof value === 'string') return value.length\n if (Array.isArray(value)) return value.length\n return Number(value)\n}\n\n/** Достаёт значение из объекта по dot-path (`address.city`). */\nexport function getByPath(obj: Record<string, unknown>, path: string): unknown {\n if (!path.includes('.')) return obj[path]\n return path.split('.').reduce<unknown>((acc, key) => {\n if (acc && typeof acc === 'object') return (acc as Record<string, unknown>)[key]\n return undefined\n }, obj)\n}\n\n/** Записывает значение в объект по dot-path (создаёт промежуточные объекты). */\nexport function setByPath(obj: Record<string, unknown>, path: string, value: unknown): void {\n const keys = path.split('.')\n const last = keys.pop() as string\n let target = obj\n for (const key of keys) {\n if (!target[key] || typeof target[key] !== 'object') target[key] = {}\n target = target[key] as Record<string, unknown>\n }\n target[last] = value\n}\n\n/** Нормализует правило(а) поля в массив. */\nexport function toRuleArray(rules: GrFormRule | GrFormRule[] | undefined): GrFormRule[] {\n if (!rules) return []\n return Array.isArray(rules) ? rules : [rules]\n}\n\nfunction ruleMatchesTrigger(rule: GrFormRule, trigger?: GrFormTrigger): boolean {\n if (!trigger) return true // полная валидация (validate()) — все правила\n if (!rule.trigger) return true // правило без триггера срабатывает на любом\n const triggers = Array.isArray(rule.trigger) ? rule.trigger : [rule.trigger]\n return triggers.includes(trigger)\n}\n\n/** Правила поля, применимые для данного триггера (undefined = все). */\nexport function rulesForTrigger(rules: GrFormRule[], trigger?: GrFormTrigger): GrFormRule[] {\n return rules.filter(rule => ruleMatchesTrigger(rule, trigger))\n}\n\n/** Есть ли среди правил `required` (для маркера обязательности в поле). */\nexport function rulesRequired(rules: GrFormRule[]): boolean {\n return rules.some(rule => rule.required === true)\n}\n\n/**\n * Прогоняет правила по порядку, возвращает первое сообщение об ошибке или `undefined`.\n * Пустое необязательное значение проходит все проверки (кроме `required`).\n */\nexport async function runFieldRules(\n value: unknown,\n rules: GrFormRule[],\n model: Record<string, unknown>,\n resolveMessage: GrFormMessageResolver,\n): Promise<string | undefined> {\n for (const rule of rules) {\n const empty = isEmpty(value)\n\n if (rule.required && empty) return resolveMessage('required', rule, {})\n // Необязательное пустое значение — остальные проверки этого правила пропускаем.\n if (empty) continue\n\n if (rule.type === 'email' && !EMAIL_RE.test(String(value))) return resolveMessage('email', rule, {})\n if (rule.type === 'url' && !isUrl(String(value))) return resolveMessage('url', rule, {})\n\n if (rule.len != null && measure(value) !== rule.len) return resolveMessage('len', rule, { len: rule.len })\n if (rule.min != null && measure(value) < rule.min) return resolveMessage('min', rule, { min: rule.min })\n if (rule.max != null && measure(value) > rule.max) return resolveMessage('max', rule, { max: rule.max })\n\n if (rule.pattern && !rule.pattern.test(String(value))) return resolveMessage('pattern', rule, {})\n\n if (rule.file) {\n const issue = await runFileRule(value, rule.file)\n if (issue) return resolveMessage('file', rule, { issue })\n }\n\n if (rule.validator) {\n const result = await rule.validator(value, model)\n if (result === false) return rule.message ?? resolveMessage('invalid', rule, {})\n if (typeof result === 'string') return result\n }\n }\n\n return undefined\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"validation-5HhpanPP.js","names":[],"sources":["../../src/components/GrForm/fileRule.ts","../../src/components/GrForm/validation.ts"],"sourcesContent":["/**\n * Файловое правило `GrForm` — мост между декларативными `rules` и валидаторами\n * из `src/fileValidation`.\n *\n * Своих проверок здесь нет ни одной: правило собирает те же валидаторы, которые\n * `GrFormFile` и `v-dropzone` запускают на выборе и на drop. Поэтому ограничение,\n * переехавшее из пропов поля в правило формы, не меняет ни поведения, ни текста\n * ошибки — меняется только момент проверки и то, кто о ней сообщает.\n *\n * Модуль чистый (без Vue): его гоняют юнит-тесты без монтирования.\n */\n\nimport {\n acceptValidator,\n allowedExtensionsValidator,\n allowedMimeTypesValidator,\n maxCountValidator,\n maxFileSize,\n maxTotalSizeBytesValidator,\n runFileValidators,\n type FileValidationIssue,\n type FileValidator,\n} from '../../fileValidation'\n\nexport interface GrFormFileRule {\n /** W3C `accept`: `'image/*,.pdf'`. */\n accept?: string\n /** Белый список расширений (с точкой или без). */\n extensions?: string[]\n /** Белый список MIME-типов. */\n mimeTypes?: string[]\n /** Предел размера одного файла в мегабайтах. */\n maxSizeMb?: number\n /** Предел размера одного файла в байтах. Задан вместе с `maxSizeMb` — берётся меньший. */\n maxSizeBytes?: number\n /** Максимум файлов в наборе. */\n maxCount?: number\n /** Суммарный размер набора в мегабайтах. */\n maxTotalSizeMb?: number\n /** Свои валидаторы — тот же тип, что у `GrFormFile` и `v-dropzone`. Идут после встроенных. */\n validators?: FileValidator[]\n}\n\nconst BYTES_IN_MB = 1024 * 1024\n\nfunction mbToBytes(mb: number | undefined): number | undefined {\n return typeof mb === 'number' && Number.isFinite(mb) ? mb * BYTES_IN_MB : undefined\n}\n\nfunction isFile(value: unknown): value is File {\n return typeof File !== 'undefined' && value instanceof File\n}\n\n/**\n * Значение поля как набор файлов. Всё, что файлами не является, даёт пустой\n * набор: правило молчит, а пустоту разбирает `required` движка.\n */\nexport function fileRuleFiles(value: unknown): File[] {\n if (isFile(value))\n return [value]\n if (Array.isArray(value))\n return value.filter(isFile)\n return []\n}\n\n/** Порядок повторяет `GrFormFile`: встроенные проверки, затем валидаторы потребителя. */\nexport function fileRuleValidators(rule: GrFormFileRule): FileValidator[] {\n return [\n acceptValidator(rule.accept),\n ...(rule.extensions ? [allowedExtensionsValidator(rule.extensions)] : []),\n ...(rule.mimeTypes ? [allowedMimeTypesValidator(rule.mimeTypes)] : []),\n maxFileSize({ bytes: rule.maxSizeBytes, mb: rule.maxSizeMb }),\n maxCountValidator(rule.maxCount),\n maxTotalSizeBytesValidator(mbToBytes(rule.maxTotalSizeMb)),\n ...(rule.validators ?? []),\n ]\n}\n\n/**\n * Первая проблема набора или `undefined`. Первая, а не все: у поля формы одна\n * строка ошибки, и файлы тут не исключение — подробный разбор по каждому файлу\n * остаётся за `GrFormFile`.\n */\nexport async function runFileRule(\n value: unknown,\n rule: GrFormFileRule,\n): Promise<FileValidationIssue | undefined> {\n const files = fileRuleFiles(value)\n if (!files.length)\n return undefined\n\n const { issues } = await runFileValidators(files, fileRuleValidators(rule), {\n source: 'form',\n multiple: Array.isArray(value),\n accept: rule.accept,\n })\n\n return issues[0]\n}\n","/**\n * Движок валидации `GrForm` — чистый (без Vue/i18n), тестируется изолированно.\n *\n * Правило описывает набор проверок значения поля; `runFieldRules` прогоняет их по\n * порядку и возвращает первое сообщение об ошибке (или `undefined`, если валидно).\n * Сообщения резолвит потребитель (компонент) через `resolveMessage` — так i18n и\n * дефолтные тексты остаются в компоненте, а движок ничего о них не знает.\n */\n\nimport { resolveFileValidationMessage, type FileValidationIssue } from '../../fileValidation'\nimport { runFileRule, type GrFormFileRule } from './fileRule'\n\nexport type { GrFormFileRule } from './fileRule'\n\nexport type GrFormTrigger = 'blur' | 'change' | 'submit'\n\n/** Результат кастомного валидатора: `true`/void — ок; `false` — ошибка (дефолт-текст); строка — текст ошибки. */\nexport type GrFormValidatorResult = boolean | string | void\n\nexport interface GrFormRule {\n /** Значение обязательно (не пустое). */\n required?: boolean\n /** Готовый текст ошибки — перекрывает дефолтное сообщение любой проверки этого правила. */\n message?: string\n /** Минимум: длина для строки/массива, значение для числа. */\n min?: number\n /** Максимум: длина для строки/массива, значение для числа. */\n max?: number\n /** Точная длина/значение. */\n len?: number\n /** Регулярное выражение (проверяется по `String(value)`). */\n pattern?: RegExp\n /** Встроенный тип-валидатор. */\n type?: 'email' | 'url'\n /** Ограничения для значения `File | File[]`: тип, размер, количество. */\n file?: GrFormFileRule\n /** Кастомный (в т.ч. async) валидатор. Второй аргумент — весь `model`. */\n validator?: (value: unknown, model: Record<string, unknown>) => GrFormValidatorResult | Promise<GrFormValidatorResult>\n /** Когда правило срабатывает. По умолчанию — на любом триггере (blur/change/submit). */\n trigger?: GrFormTrigger | GrFormTrigger[]\n}\n\nexport type GrFormRules = Record<string, GrFormRule | GrFormRule[]>\n\n/** Вид сработавшей проверки — ключ для дефолтного i18n-сообщения. */\nexport type GrFormRuleFailure = 'required' | 'min' | 'max' | 'len' | 'pattern' | 'email' | 'url' | 'file' | 'invalid'\n\nexport type GrFormMessageResolver = (\n kind: GrFormRuleFailure,\n rule: GrFormRule,\n params: Record<string, unknown>,\n) => string\n\n/** Подпись `t()` из `useGranularityTranslations()`: ключ, фолбэк, параметры. */\nexport type GrFormMessageTranslate = (\n key: string,\n fallback: string,\n params?: Record<string, unknown>,\n) => string\n\nconst DEFAULT_MESSAGES: Record<GrFormRuleFailure, string> = {\n required: 'This field is required',\n min: 'Minimum is {min}',\n max: 'Maximum is {max}',\n len: 'Length must be {len}',\n pattern: 'Invalid format',\n email: 'Enter a valid email',\n url: 'Enter a valid URL',\n file: 'Invalid file',\n invalid: 'Invalid value',\n}\n\n/**\n * Дефолтный резолвер сообщений: `rule.message` сильнее всего, иначе ключ\n * `gr.form.<kind>` из локали, иначе английский фолбэк.\n *\n * Вынесен из `GrForm.vue` и экспортируется наружу не «на всякий случай»:\n * `runFieldRules` публичен, но без резолвера бесполезен — любой, кто хочет\n * прогнать те же правила вне формы, обязан был написать свой.\n */\nexport function createGrFormMessageResolver(t: GrFormMessageTranslate): GrFormMessageResolver {\n return (kind, rule, params) => {\n if (rule.message)\n return rule.message\n\n // Текст файловой ошибки приходит от самого валидатора и локализуется его же\n // ключом (`gr.fileValidation.*`): иначе одно и то же «файл не того типа»\n // звучало бы по-разному в поле и в форме. `gr.form.file` — фолбэк на случай\n // вида `file` без issue: сюда движок не приходит, но резолвер публичный.\n const issue = params.issue as FileValidationIssue | undefined\n if (kind === 'file' && issue)\n return resolveFileValidationMessage(issue, t)\n\n return t(`gr.form.${kind}`, DEFAULT_MESSAGES[kind], params)\n }\n}\n\nconst EMAIL_RE = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\n\nfunction isUrl(value: string): boolean {\n return URL.canParse(value)\n}\n\n/** Пустое значение: `null`/`undefined`/пустая (или пробельная) строка/пустой массив. */\nexport function isEmpty(value: unknown): boolean {\n if (value == null)\n return true\n if (typeof value === 'string')\n return value.trim() === ''\n if (Array.isArray(value))\n return value.length === 0\n return false\n}\n\n/** Мера значения для min/max/len: число → само значение, строка/массив → длина. */\nfunction measure(value: unknown): number {\n if (typeof value === 'number')\n return value\n if (typeof value === 'string')\n return value.length\n if (Array.isArray(value))\n return value.length\n return Number(value)\n}\n\n/** Достаёт значение из объекта по dot-path (`address.city`). */\nexport function getByPath(obj: Record<string, unknown>, path: string): unknown {\n if (!path.includes('.'))\n return obj[path]\n return path.split('.').reduce<unknown>((acc, key) => {\n if (acc && typeof acc === 'object')\n return (acc as Record<string, unknown>)[key]\n return undefined\n }, obj)\n}\n\n/** Записывает значение в объект по dot-path (создаёт промежуточные объекты). */\nexport function setByPath(obj: Record<string, unknown>, path: string, value: unknown): void {\n const keys = path.split('.')\n const last = keys.pop() as string\n let target = obj\n for (const key of keys) {\n if (!target[key] || typeof target[key] !== 'object')\n target[key] = {}\n target = target[key] as Record<string, unknown>\n }\n target[last] = value\n}\n\n/** Нормализует правило(а) поля в массив. */\nexport function toRuleArray(rules: GrFormRule | GrFormRule[] | undefined): GrFormRule[] {\n if (!rules)\n return []\n return Array.isArray(rules) ? rules : [rules]\n}\n\nfunction ruleMatchesTrigger(rule: GrFormRule, trigger?: GrFormTrigger): boolean {\n if (!trigger)\n return true // полная валидация (validate()) — все правила\n if (!rule.trigger)\n return true // правило без триггера срабатывает на любом\n const triggers = Array.isArray(rule.trigger) ? rule.trigger : [rule.trigger]\n return triggers.includes(trigger)\n}\n\n/** Правила поля, применимые для данного триггера (undefined = все). */\nexport function rulesForTrigger(rules: GrFormRule[], trigger?: GrFormTrigger): GrFormRule[] {\n return rules.filter(rule => ruleMatchesTrigger(rule, trigger))\n}\n\n/** Есть ли среди правил `required` (для маркера обязательности в поле). */\nexport function rulesRequired(rules: GrFormRule[]): boolean {\n return rules.some(rule => rule.required === true)\n}\n\n/**\n * Прогоняет правила по порядку, возвращает первое сообщение об ошибке или `undefined`.\n * Пустое необязательное значение проходит все проверки (кроме `required`).\n */\nexport async function runFieldRules(\n value: unknown,\n rules: GrFormRule[],\n model: Record<string, unknown>,\n resolveMessage: GrFormMessageResolver,\n): Promise<string | undefined> {\n for (const rule of rules) {\n const empty = isEmpty(value)\n\n if (rule.required && empty)\n return resolveMessage('required', rule, {})\n // Необязательное пустое значение — остальные проверки этого правила пропускаем.\n if (empty)\n continue\n\n if (rule.type === 'email' && !EMAIL_RE.test(String(value)))\n return resolveMessage('email', rule, {})\n if (rule.type === 'url' && !isUrl(String(value)))\n return resolveMessage('url', rule, {})\n\n if (rule.len != null && measure(value) !== rule.len)\n return resolveMessage('len', rule, { len: rule.len })\n if (rule.min != null && measure(value) < rule.min)\n return resolveMessage('min', rule, { min: rule.min })\n if (rule.max != null && measure(value) > rule.max)\n return resolveMessage('max', rule, { max: rule.max })\n\n if (rule.pattern && !rule.pattern.test(String(value)))\n return resolveMessage('pattern', rule, {})\n\n if (rule.file) {\n const issue = await runFileRule(value, rule.file)\n if (issue)\n return resolveMessage('file', rule, { issue })\n }\n\n if (rule.validator) {\n const result = await rule.validator(value, model)\n if (result === false)\n return rule.message ?? resolveMessage('invalid', rule, {})\n if (typeof result === 'string')\n return result\n }\n }\n\n return undefined\n}\n"],"mappings":";;;;;;;;;;;;;AA2CA,IAAM,cAAc,OAAO;AAE3B,SAAS,UAAU,IAA4C;CAC7D,OAAO,OAAO,OAAO,YAAY,OAAO,SAAS,EAAE,IAAI,KAAK,cAAc,KAAA;AAC5E;AAEA,SAAS,OAAO,OAA+B;CAC7C,OAAO,OAAO,SAAS,eAAe,iBAAiB;AACzD;;;;;AAMA,SAAgB,cAAc,OAAwB;CACpD,IAAI,OAAO,KAAK,GACd,OAAO,CAAC,KAAK;CACf,IAAI,MAAM,QAAQ,KAAK,GACrB,OAAO,MAAM,OAAO,MAAM;CAC5B,OAAO,CAAC;AACV;;AAGA,SAAgB,mBAAmB,MAAuC;CACxE,OAAO;EACL,gBAAgB,KAAK,MAAM;EAC3B,GAAI,KAAK,aAAa,CAAC,2BAA2B,KAAK,UAAU,CAAC,IAAI,CAAC;EACvE,GAAI,KAAK,YAAY,CAAC,0BAA0B,KAAK,SAAS,CAAC,IAAI,CAAC;EACpE,YAAY;GAAE,OAAO,KAAK;GAAc,IAAI,KAAK;EAAU,CAAC;EAC5D,kBAAkB,KAAK,QAAQ;EAC/B,2BAA2B,UAAU,KAAK,cAAc,CAAC;EACzD,GAAI,KAAK,cAAc,CAAC;CAC1B;AACF;;;;;;AAOA,eAAsB,YACpB,OACA,MAC0C;CAC1C,MAAM,QAAQ,cAAc,KAAK;CACjC,IAAI,CAAC,MAAM,QACT,OAAO,KAAA;CAET,MAAM,EAAE,WAAW,MAAM,kBAAkB,OAAO,mBAAmB,IAAI,GAAG;EAC1E,QAAQ;EACR,UAAU,MAAM,QAAQ,KAAK;EAC7B,QAAQ,KAAK;CACf,CAAC;CAED,OAAO,OAAO;AAChB;;;;;;;;;;;ACtCA,IAAM,mBAAsD;CAC1D,UAAU;CACV,KAAK;CACL,KAAK;CACL,KAAK;CACL,SAAS;CACT,OAAO;CACP,KAAK;CACL,MAAM;CACN,SAAS;AACX;;;;;;;;;AAUA,SAAgB,4BAA4B,GAAkD;CAC5F,QAAQ,MAAM,MAAM,WAAW;EAC7B,IAAI,KAAK,SACP,OAAO,KAAK;EAMd,MAAM,QAAQ,OAAO;EACrB,IAAI,SAAS,UAAU,OACrB,OAAO,6BAA6B,OAAO,CAAC;EAE9C,OAAO,EAAE,WAAW,QAAQ,iBAAiB,OAAO,MAAM;CAC5D;AACF;AAEA,IAAM,WAAW;AAEjB,SAAS,MAAM,OAAwB;CACrC,OAAO,IAAI,SAAS,KAAK;AAC3B;;AAGA,SAAgB,QAAQ,OAAyB;CAC/C,IAAI,SAAS,MACX,OAAO;CACT,IAAI,OAAO,UAAU,UACnB,OAAO,MAAM,KAAK,MAAM;CAC1B,IAAI,MAAM,QAAQ,KAAK,GACrB,OAAO,MAAM,WAAW;CAC1B,OAAO;AACT;;AAGA,SAAS,QAAQ,OAAwB;CACvC,IAAI,OAAO,UAAU,UACnB,OAAO;CACT,IAAI,OAAO,UAAU,UACnB,OAAO,MAAM;CACf,IAAI,MAAM,QAAQ,KAAK,GACrB,OAAO,MAAM;CACf,OAAO,OAAO,KAAK;AACrB;;AAGA,SAAgB,UAAU,KAA8B,MAAuB;CAC7E,IAAI,CAAC,KAAK,SAAS,GAAG,GACpB,OAAO,IAAI;CACb,OAAO,KAAK,MAAM,GAAG,CAAC,CAAC,QAAiB,KAAK,QAAQ;EACnD,IAAI,OAAO,OAAO,QAAQ,UACxB,OAAQ,IAAgC;CAE5C,GAAG,GAAG;AACR;;AAGA,SAAgB,UAAU,KAA8B,MAAc,OAAsB;CAC1F,MAAM,OAAO,KAAK,MAAM,GAAG;CAC3B,MAAM,OAAO,KAAK,IAAI;CACtB,IAAI,SAAS;CACb,KAAK,MAAM,OAAO,MAAM;EACtB,IAAI,CAAC,OAAO,QAAQ,OAAO,OAAO,SAAS,UACzC,OAAO,OAAO,CAAC;EACjB,SAAS,OAAO;CAClB;CACA,OAAO,QAAQ;AACjB;;AAGA,SAAgB,YAAY,OAA4D;CACtF,IAAI,CAAC,OACH,OAAO,CAAC;CACV,OAAO,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK;AAC9C;AAEA,SAAS,mBAAmB,MAAkB,SAAkC;CAC9E,IAAI,CAAC,SACH,OAAO;CACT,IAAI,CAAC,KAAK,SACR,OAAO;CAET,QADiB,MAAM,QAAQ,KAAK,OAAO,IAAI,KAAK,UAAU,CAAC,KAAK,OAAO,EAAA,CAC3D,SAAS,OAAO;AAClC;;AAGA,SAAgB,gBAAgB,OAAqB,SAAuC;CAC1F,OAAO,MAAM,QAAO,SAAQ,mBAAmB,MAAM,OAAO,CAAC;AAC/D;;AAGA,SAAgB,cAAc,OAA8B;CAC1D,OAAO,MAAM,MAAK,SAAQ,KAAK,aAAa,IAAI;AAClD;;;;;AAMA,eAAsB,cACpB,OACA,OACA,OACA,gBAC6B;CAC7B,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,QAAQ,QAAQ,KAAK;EAE3B,IAAI,KAAK,YAAY,OACnB,OAAO,eAAe,YAAY,MAAM,CAAC,CAAC;EAE5C,IAAI,OACF;EAEF,IAAI,KAAK,SAAS,WAAW,CAAC,SAAS,KAAK,OAAO,KAAK,CAAC,GACvD,OAAO,eAAe,SAAS,MAAM,CAAC,CAAC;EACzC,IAAI,KAAK,SAAS,SAAS,CAAC,MAAM,OAAO,KAAK,CAAC,GAC7C,OAAO,eAAe,OAAO,MAAM,CAAC,CAAC;EAEvC,IAAI,KAAK,OAAO,QAAQ,QAAQ,KAAK,MAAM,KAAK,KAC9C,OAAO,eAAe,OAAO,MAAM,EAAE,KAAK,KAAK,IAAI,CAAC;EACtD,IAAI,KAAK,OAAO,QAAQ,QAAQ,KAAK,IAAI,KAAK,KAC5C,OAAO,eAAe,OAAO,MAAM,EAAE,KAAK,KAAK,IAAI,CAAC;EACtD,IAAI,KAAK,OAAO,QAAQ,QAAQ,KAAK,IAAI,KAAK,KAC5C,OAAO,eAAe,OAAO,MAAM,EAAE,KAAK,KAAK,IAAI,CAAC;EAEtD,IAAI,KAAK,WAAW,CAAC,KAAK,QAAQ,KAAK,OAAO,KAAK,CAAC,GAClD,OAAO,eAAe,WAAW,MAAM,CAAC,CAAC;EAE3C,IAAI,KAAK,MAAM;GACb,MAAM,QAAQ,MAAM,YAAY,OAAO,KAAK,IAAI;GAChD,IAAI,OACF,OAAO,eAAe,QAAQ,MAAM,EAAE,MAAM,CAAC;EACjD;EAEA,IAAI,KAAK,WAAW;GAClB,MAAM,SAAS,MAAM,KAAK,UAAU,OAAO,KAAK;GAChD,IAAI,WAAW,OACb,OAAO,KAAK,WAAW,eAAe,WAAW,MAAM,CAAC,CAAC;GAC3D,IAAI,OAAO,WAAW,UACpB,OAAO;EACX;CACF;AAGF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GrAlert-FAVf-ksH.js","names":["$slots"],"sources":["../../../../src/components/GrAlert/grAlertStyles.ts","../../../../src/components/GrAlert/GrAlert.vue","../../../../src/components/GrAlert/GrAlert.vue"],"sourcesContent":["import type { GrTone } from '../shared/tones'\n\n/**\n * Цветовая модель `GrAlert`.\n *\n * Модуль намеренно чистый: он оперирует **строками CSS-значений**, а не\n * utility-классами UnoCSS. Классы, живущие только строковыми литералами в\n * `.ts`, не сканируются `granularContent` и требуют ручного safelist — здесь\n * этой проблемы нет по построению, а логику цветов можно тестировать без\n * монтирования компонента.\n */\n\n/**\n * `soft` — залитая тоном подложка, тональный текст. Основной вариант.\n * `outline` — фон страницы, тон несут рамка и иконка; текст нейтральный.\n */\nexport const GR_ALERT_VARIANTS = ['soft', 'outline'] as const\n\nexport type GrAlertVariant = typeof GR_ALERT_VARIANTS[number]\n\n/** Ключ иконки тона. Сама иконка резолвится в SFC — модуль остаётся чистым. */\nexport type GrAlertIconKey = 'info' | 'success' | 'warning' | 'danger'\n\ntype GrAlertToneTokens = {\n /** Подложка варианта `soft`. */\n surface: string\n /** Насыщенный цвет тона: база для рамки. */\n accent: string\n /**\n * Тональный цвет текста — рассчитан на подложку `surface`.\n *\n * Именно он, а не `accent`, красит иконку и текст: на светлой теме\n * `--gr-success` (#10b981) на `--gr-success-light` (#d1fae5) даёт 2.24:1 —\n * ниже порога 3:1 даже для нетекстовой графики. `--gr-success-text`\n * (#065f46) на той же подложке даёт 6.78:1.\n */\n text: string\n icon: GrAlertIconKey\n}\n\n/**\n * Тон → тройка токенов.\n *\n * `primary` и `neutral` не имеют пары `-light`/`-text` и отображаются на\n * ближайшие семантические роли: акцентную поверхность и нейтральную muted.\n */\nconst TONE_TOKENS: Record<GrTone, GrAlertToneTokens> = {\n primary: { surface: 'var(--gr-accent)', accent: 'var(--gr-primary)', text: 'var(--gr-accent-fg)', icon: 'info' },\n neutral: { surface: 'var(--gr-muted)', accent: 'var(--gr-muted-fg)', text: 'var(--gr-fg)', icon: 'info' },\n success: { surface: 'var(--gr-success-light)', accent: 'var(--gr-success)', text: 'var(--gr-success-text)', icon: 'success' },\n warning: { surface: 'var(--gr-warning-light)', accent: 'var(--gr-warning)', text: 'var(--gr-warning-text)', icon: 'warning' },\n danger: { surface: 'var(--gr-danger-light)', accent: 'var(--gr-danger)', text: 'var(--gr-danger-text)', icon: 'danger' },\n info: { surface: 'var(--gr-info-light)', accent: 'var(--gr-info)', text: 'var(--gr-info-text)', icon: 'info' },\n slate: { surface: 'var(--gr-slate-light)', accent: 'var(--gr-slate)', text: 'var(--gr-slate-text)', icon: 'info' },\n azure: { surface: 'var(--gr-azure-light)', accent: 'var(--gr-azure)', text: 'var(--gr-azure-text)', icon: 'info' },\n}\n\n/** Разрешённые цвета одного алерта — ровно то, что уходит в CSS-переменные. */\nexport type GrAlertColors = {\n bg: string\n border: string\n icon: string\n title: string\n text: string\n close: string\n closeHover: string\n closeHoverBg: string\n}\n\n/** Пользовательские переопределения цвета (пропы-эскейпы). */\nexport type GrAlertColorOverrides = {\n backgroundColor?: string\n textColor?: string\n borderColor?: string\n}\n\nexport function grAlertIconKey(tone: GrTone): GrAlertIconKey {\n return TONE_TOKENS[tone].icon\n}\n\n/**\n * Рамка выводится из тона формулой, а не задаётся отдельным токеном на каждый\n * тон: доля тона в смеси с `--gr-brd` — единственное, что отличает рамки между\n * собой. 22% (как было) давало 1.27–1.51:1 к собственной подложке — край\n * практически не читался; 35/45% поднимают его до 1.42–2.07:1.\n */\nfunction border(accent: string, ratio: number): string {\n return `color-mix(in srgb, ${accent} ${ratio}%, var(--gr-brd))`\n}\n\nexport function resolveGrAlertColors(tone: GrTone, variant: GrAlertVariant): GrAlertColors {\n const t = TONE_TOKENS[tone]\n\n if (variant === 'outline') {\n return {\n bg: 'var(--gr-bg)',\n border: border(t.accent, 45),\n icon: t.text,\n title: 'var(--gr-fg)',\n text: 'var(--gr-muted-fg)',\n close: 'var(--gr-muted-fg)',\n closeHover: 'var(--gr-fg)',\n closeHoverBg: 'var(--gr-muted)',\n }\n }\n\n return {\n bg: t.surface,\n border: border(t.accent, 35),\n // Иерархия «заголовок / текст» держится на размере и насыщении шрифта, а не\n // на цвете: приглушать тональный текст подмешиванием подложки нельзя —\n // на тёмной теме warning и danger проваливают 4.5:1 уже при 15% подмеса.\n icon: t.text,\n title: t.text,\n text: t.text,\n close: t.text,\n closeHover: t.text,\n closeHoverBg: `color-mix(in srgb, ${t.accent} 16%, transparent)`,\n }\n}\n\n/**\n * Пропы-эскейпы поверх тона. `textColor` красит весь текстовый слой целиком\n * (иконка, заголовок, текст, кнопка закрытия) — иначе пользовательский цвет\n * соседствовал бы с тональным и давал грязь.\n */\nexport function applyGrAlertOverrides(colors: GrAlertColors, overrides: GrAlertColorOverrides): GrAlertColors {\n const custom = (value?: string) => value?.trim() || undefined\n const text = custom(overrides.textColor)\n\n return {\n ...colors,\n bg: custom(overrides.backgroundColor) ?? colors.bg,\n border: custom(overrides.borderColor) ?? colors.border,\n icon: text ?? colors.icon,\n title: text ?? colors.title,\n text: text ?? colors.text,\n close: text ?? colors.close,\n closeHover: text ?? colors.closeHover,\n closeHoverBg: text ? `color-mix(in srgb, ${text} 12%, transparent)` : colors.closeHoverBg,\n }\n}\n\n/**\n * Публичный контракт кастомизации: переменные `--gr-alert-*` можно\n * переопределить снаружи так же, как их задаёт компонент.\n */\nexport function grAlertCssVars(colors: GrAlertColors): Record<string, string> {\n return {\n '--gr-alert-bg': colors.bg,\n '--gr-alert-brd': colors.border,\n '--gr-alert-icon-color': colors.icon,\n '--gr-alert-title-color': colors.title,\n '--gr-alert-text-color': colors.text,\n '--gr-alert-close-color': colors.close,\n '--gr-alert-close-hover-color': colors.closeHover,\n '--gr-alert-close-hover-bg': colors.closeHoverBg,\n }\n}\n","<script setup lang=\"ts\">\n/**\n * GrAlert — блочное сообщение с тоном, иконкой и опциональным закрытием.\n *\n * Цветовая модель вынесена в `grAlertStyles.ts` и выражена **только токенами**\n * `--gr-*`: компонент одинаково корректен в light и dark без единого hex-литерала.\n *\n * Точки кастомизации — переменные `--gr-alert-*` (см. `grAlertCssVars`); пропы\n * `backgroundColor`/`textColor`/`borderColor` — тот же контракт, но точечно.\n */\nimport { computed } from 'vue'\n\nimport IconWarning from '~icons/lucide/alert-triangle'\nimport IconCheck from '~icons/lucide/check-circle'\nimport IconInfo from '~icons/lucide/info'\nimport IconClose from '~icons/lucide/x'\nimport IconError from '~icons/lucide/x-circle'\n\nimport type { Component } from 'vue'\nimport type { GrTone } from '../shared/tones'\nimport type { GrAlertIconKey, GrAlertVariant } from './grAlertStyles'\n\nimport { useGrComponentProp } from '../GrConfigProvider/context'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport {\n applyGrAlertOverrides,\n grAlertCssVars,\n grAlertIconKey,\n resolveGrAlertColors,\n} from './grAlertStyles'\n\nimport './defaults'\n\nexport type GrAlertTone = GrTone\nexport type { GrAlertVariant } from './grAlertStyles'\n\n/**\n * Режим объявления сообщения скринридеру.\n * `auto` — по тону: `warning`/`danger` перебивают речь (`role=\"alert\"`),\n * остальные ждут паузы (`role=\"status\"`).\n */\nexport type GrAlertLive = 'auto' | 'assertive' | 'polite' | 'off'\n\nexport interface GrAlertProps {\n tone?: GrAlertTone\n variant?: GrAlertVariant\n title?: string\n closable?: boolean\n live?: GrAlertLive\n /**\n * Показывать ли иконку тона. Своя иконка — слотом `#icon`; проп нужен для\n * случая «иконка мешает»: узкая колонка, плотный список, сообщение в форме.\n */\n icon?: boolean\n /**\n * Видимость сообщения — **только контролируемая**: без пропа алерт виден\n * всегда, и закрытие остаётся заботой потребителя (`@close`). С\n * `v-model:visible` компонент прячет себя сам.\n *\n * Собственного состояния у пропа нет намеренно. Оно превратило бы кнопку\n * закрытия в необратимое действие у всех, кто уже живёт на `@close` и,\n * например, спрашивает по нему подтверждение.\n */\n visible?: boolean\n backgroundColor?: string\n textColor?: string\n borderColor?: string\n}\n\nexport interface GrAlertEmits {\n (e: 'close'): void\n /** Пользователь закрыл сообщение (`v-model:visible`). */\n (e: 'update:visible', value: boolean): void\n}\n\nconst emit = defineEmits<GrAlertEmits>()\n\ndefineSlots<{\n /** Текст сообщения. */\n default?: () => any\n /** Иконка вместо глифа по тону. Декоративна: остаётся `aria-hidden`. */\n icon?: () => any\n /** Действия по сообщению: «Повторить», «Подробнее». Ложатся под текст. */\n actions?: () => any\n}>()\n\n// Дефолты tone/variant/closable/live намеренно `undefined`: «настоящий» дефолт\n// переехал в `useGrComponentProp`, иначе `GrConfigProvider` не смог бы отличить\n// заданный пользователем проп от подставленного Vue.\nconst props = withDefaults(defineProps<GrAlertProps>(), {\n tone: undefined,\n variant: undefined,\n title: undefined,\n closable: undefined,\n live: undefined,\n icon: undefined,\n visible: undefined,\n backgroundColor: undefined,\n textColor: undefined,\n borderColor: undefined,\n})\n\nconst { t } = useGranularityTranslations()\n\nconst tone = useGrComponentProp('GrAlert', 'tone', () => props.tone, 'info')\nconst variantInput = useGrComponentProp('GrAlert', 'variant', () => props.variant, 'soft')\nconst closable = useGrComponentProp('GrAlert', 'closable', () => props.closable, false)\nconst live = useGrComponentProp('GrAlert', 'live', () => props.live, 'auto')\n\nconst variant = variantInput\n\nconst ICONS: Record<GrAlertIconKey, Component> = {\n info: IconInfo,\n success: IconCheck,\n warning: IconWarning,\n danger: IconError,\n}\n\nconst toneIcon = computed<Component>(() => ICONS[grAlertIconKey(tone.value)])\n\nconst showIcon = computed(() => props.icon !== false)\nconst isVisible = computed(() => props.visible !== false)\n\nfunction close(): void {\n emit('update:visible', false)\n emit('close')\n}\n\nconst colors = computed(() => applyGrAlertOverrides(\n resolveGrAlertColors(tone.value, variant.value),\n props,\n))\n\nconst rootStyle = computed(() => ({\n ...grAlertCssVars(colors.value),\n background: 'var(--gr-alert-bg)',\n borderColor: 'var(--gr-alert-brd)',\n}))\n\n/**\n * `role=\"alert\"` — assertive-регион: он прерывает чтение. Вешать его на любое\n * информационное сообщение значит превращать спокойную подсказку в тревогу,\n * поэтому по умолчанию его получают только `warning` и `danger`.\n */\nconst role = computed(() => {\n const mode = live.value === 'auto'\n ? (tone.value === 'danger' || tone.value === 'warning' ? 'assertive' : 'polite')\n : live.value\n\n if (mode === 'off') return undefined\n return mode === 'assertive' ? 'alert' : 'status'\n})\n</script>\n\n<template>\n <div\n v-if=\"isVisible\"\n :role=\"role\"\n data-gr-alert\n class=\"rounded-[var(--gr-radius-lg)] border px-4 py-3\"\n :style=\"rootStyle\"\n >\n <div class=\"flex items-start gap-3\">\n <span\n v-if=\"showIcon\"\n data-gr-alert-icon\n class=\"mt-0.5 h-5 w-5 shrink-0 text-[var(--gr-alert-icon-color)]\"\n aria-hidden=\"true\"\n >\n <slot name=\"icon\">\n <component :is=\"toneIcon\" class=\"h-5 w-5\" />\n </slot>\n </span>\n\n <div class=\"min-w-0 flex-1\">\n <div\n v-if=\"props.title\"\n class=\"text-[length:var(--gr-text-sm)] leading-[var(--gr-leading-sm)] font-600 text-[var(--gr-alert-title-color)]\"\n >\n {{ props.title }}\n </div>\n <div\n class=\"text-[length:var(--gr-text-sm)] leading-relaxed text-[var(--gr-alert-text-color)]\"\n :class=\"props.title ? 'mt-1' : ''\"\n >\n <slot />\n </div>\n\n <!-- Действия идут под текстом, а не рядом с ним: на узкой колонке\n кнопки в одной строке с сообщением отжимают его до одного слова. -->\n <div\n v-if=\"$slots.actions\"\n data-gr-alert-actions\n class=\"mt-2 flex flex-wrap gap-2\"\n >\n <slot name=\"actions\" />\n </div>\n </div>\n\n <button\n v-if=\"closable\"\n type=\"button\"\n class=\"-mr-1.5 -mt-1.5 shrink-0 rounded-[var(--gr-radius-md)] p-1.5 text-[var(--gr-alert-close-color)] transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--gr-ring)] hover:bg-[var(--gr-alert-close-hover-bg)] hover:text-[var(--gr-alert-close-hover-color)]\"\n :aria-label=\"t('gr.common.close', 'Close')\"\n @click=\"close\"\n >\n <IconClose class=\"h-4 w-4\" aria-hidden=\"true\" />\n </button>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * GrAlert — блочное сообщение с тоном, иконкой и опциональным закрытием.\n *\n * Цветовая модель вынесена в `grAlertStyles.ts` и выражена **только токенами**\n * `--gr-*`: компонент одинаково корректен в light и dark без единого hex-литерала.\n *\n * Точки кастомизации — переменные `--gr-alert-*` (см. `grAlertCssVars`); пропы\n * `backgroundColor`/`textColor`/`borderColor` — тот же контракт, но точечно.\n */\nimport { computed } from 'vue'\n\nimport IconWarning from '~icons/lucide/alert-triangle'\nimport IconCheck from '~icons/lucide/check-circle'\nimport IconInfo from '~icons/lucide/info'\nimport IconClose from '~icons/lucide/x'\nimport IconError from '~icons/lucide/x-circle'\n\nimport type { Component } from 'vue'\nimport type { GrTone } from '../shared/tones'\nimport type { GrAlertIconKey, GrAlertVariant } from './grAlertStyles'\n\nimport { useGrComponentProp } from '../GrConfigProvider/context'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport {\n applyGrAlertOverrides,\n grAlertCssVars,\n grAlertIconKey,\n resolveGrAlertColors,\n} from './grAlertStyles'\n\nimport './defaults'\n\nexport type GrAlertTone = GrTone\nexport type { GrAlertVariant } from './grAlertStyles'\n\n/**\n * Режим объявления сообщения скринридеру.\n * `auto` — по тону: `warning`/`danger` перебивают речь (`role=\"alert\"`),\n * остальные ждут паузы (`role=\"status\"`).\n */\nexport type GrAlertLive = 'auto' | 'assertive' | 'polite' | 'off'\n\nexport interface GrAlertProps {\n tone?: GrAlertTone\n variant?: GrAlertVariant\n title?: string\n closable?: boolean\n live?: GrAlertLive\n /**\n * Показывать ли иконку тона. Своя иконка — слотом `#icon`; проп нужен для\n * случая «иконка мешает»: узкая колонка, плотный список, сообщение в форме.\n */\n icon?: boolean\n /**\n * Видимость сообщения — **только контролируемая**: без пропа алерт виден\n * всегда, и закрытие остаётся заботой потребителя (`@close`). С\n * `v-model:visible` компонент прячет себя сам.\n *\n * Собственного состояния у пропа нет намеренно. Оно превратило бы кнопку\n * закрытия в необратимое действие у всех, кто уже живёт на `@close` и,\n * например, спрашивает по нему подтверждение.\n */\n visible?: boolean\n backgroundColor?: string\n textColor?: string\n borderColor?: string\n}\n\nexport interface GrAlertEmits {\n (e: 'close'): void\n /** Пользователь закрыл сообщение (`v-model:visible`). */\n (e: 'update:visible', value: boolean): void\n}\n\nconst emit = defineEmits<GrAlertEmits>()\n\ndefineSlots<{\n /** Текст сообщения. */\n default?: () => any\n /** Иконка вместо глифа по тону. Декоративна: остаётся `aria-hidden`. */\n icon?: () => any\n /** Действия по сообщению: «Повторить», «Подробнее». Ложатся под текст. */\n actions?: () => any\n}>()\n\n// Дефолты tone/variant/closable/live намеренно `undefined`: «настоящий» дефолт\n// переехал в `useGrComponentProp`, иначе `GrConfigProvider` не смог бы отличить\n// заданный пользователем проп от подставленного Vue.\nconst props = withDefaults(defineProps<GrAlertProps>(), {\n tone: undefined,\n variant: undefined,\n title: undefined,\n closable: undefined,\n live: undefined,\n icon: undefined,\n visible: undefined,\n backgroundColor: undefined,\n textColor: undefined,\n borderColor: undefined,\n})\n\nconst { t } = useGranularityTranslations()\n\nconst tone = useGrComponentProp('GrAlert', 'tone', () => props.tone, 'info')\nconst variantInput = useGrComponentProp('GrAlert', 'variant', () => props.variant, 'soft')\nconst closable = useGrComponentProp('GrAlert', 'closable', () => props.closable, false)\nconst live = useGrComponentProp('GrAlert', 'live', () => props.live, 'auto')\n\nconst variant = variantInput\n\nconst ICONS: Record<GrAlertIconKey, Component> = {\n info: IconInfo,\n success: IconCheck,\n warning: IconWarning,\n danger: IconError,\n}\n\nconst toneIcon = computed<Component>(() => ICONS[grAlertIconKey(tone.value)])\n\nconst showIcon = computed(() => props.icon !== false)\nconst isVisible = computed(() => props.visible !== false)\n\nfunction close(): void {\n emit('update:visible', false)\n emit('close')\n}\n\nconst colors = computed(() => applyGrAlertOverrides(\n resolveGrAlertColors(tone.value, variant.value),\n props,\n))\n\nconst rootStyle = computed(() => ({\n ...grAlertCssVars(colors.value),\n background: 'var(--gr-alert-bg)',\n borderColor: 'var(--gr-alert-brd)',\n}))\n\n/**\n * `role=\"alert\"` — assertive-регион: он прерывает чтение. Вешать его на любое\n * информационное сообщение значит превращать спокойную подсказку в тревогу,\n * поэтому по умолчанию его получают только `warning` и `danger`.\n */\nconst role = computed(() => {\n const mode = live.value === 'auto'\n ? (tone.value === 'danger' || tone.value === 'warning' ? 'assertive' : 'polite')\n : live.value\n\n if (mode === 'off') return undefined\n return mode === 'assertive' ? 'alert' : 'status'\n})\n</script>\n\n<template>\n <div\n v-if=\"isVisible\"\n :role=\"role\"\n data-gr-alert\n class=\"rounded-[var(--gr-radius-lg)] border px-4 py-3\"\n :style=\"rootStyle\"\n >\n <div class=\"flex items-start gap-3\">\n <span\n v-if=\"showIcon\"\n data-gr-alert-icon\n class=\"mt-0.5 h-5 w-5 shrink-0 text-[var(--gr-alert-icon-color)]\"\n aria-hidden=\"true\"\n >\n <slot name=\"icon\">\n <component :is=\"toneIcon\" class=\"h-5 w-5\" />\n </slot>\n </span>\n\n <div class=\"min-w-0 flex-1\">\n <div\n v-if=\"props.title\"\n class=\"text-[length:var(--gr-text-sm)] leading-[var(--gr-leading-sm)] font-600 text-[var(--gr-alert-title-color)]\"\n >\n {{ props.title }}\n </div>\n <div\n class=\"text-[length:var(--gr-text-sm)] leading-relaxed text-[var(--gr-alert-text-color)]\"\n :class=\"props.title ? 'mt-1' : ''\"\n >\n <slot />\n </div>\n\n <!-- Действия идут под текстом, а не рядом с ним: на узкой колонке\n кнопки в одной строке с сообщением отжимают его до одного слова. -->\n <div\n v-if=\"$slots.actions\"\n data-gr-alert-actions\n class=\"mt-2 flex flex-wrap gap-2\"\n >\n <slot name=\"actions\" />\n </div>\n </div>\n\n <button\n v-if=\"closable\"\n type=\"button\"\n class=\"-mr-1.5 -mt-1.5 shrink-0 rounded-[var(--gr-radius-md)] p-1.5 text-[var(--gr-alert-close-color)] transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--gr-ring)] hover:bg-[var(--gr-alert-close-hover-bg)] hover:text-[var(--gr-alert-close-hover-color)]\"\n :aria-label=\"t('gr.common.close', 'Close')\"\n @click=\"close\"\n >\n <IconClose class=\"h-4 w-4\" aria-hidden=\"true\" />\n </button>\n </div>\n </div>\n</template>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"GrAlert-FAVf-ksH.js","names":["$slots"],"sources":["../../../../src/components/GrAlert/grAlertStyles.ts","../../../../src/components/GrAlert/GrAlert.vue","../../../../src/components/GrAlert/GrAlert.vue"],"sourcesContent":["import type { GrTone } from '../shared/tones'\n\n/**\n * Цветовая модель `GrAlert`.\n *\n * Модуль намеренно чистый: он оперирует **строками CSS-значений**, а не\n * utility-классами UnoCSS. Классы, живущие только строковыми литералами в\n * `.ts`, не сканируются `granularContent` и требуют ручного safelist — здесь\n * этой проблемы нет по построению, а логику цветов можно тестировать без\n * монтирования компонента.\n */\n\n/**\n * `soft` — залитая тоном подложка, тональный текст. Основной вариант.\n * `outline` — фон страницы, тон несут рамка и иконка; текст нейтральный.\n */\nexport const GR_ALERT_VARIANTS = ['soft', 'outline'] as const\n\nexport type GrAlertVariant = typeof GR_ALERT_VARIANTS[number]\n\n/** Ключ иконки тона. Сама иконка резолвится в SFC — модуль остаётся чистым. */\nexport type GrAlertIconKey = 'info' | 'success' | 'warning' | 'danger'\n\ntype GrAlertToneTokens = {\n /** Подложка варианта `soft`. */\n surface: string\n /** Насыщенный цвет тона: база для рамки. */\n accent: string\n /**\n * Тональный цвет текста — рассчитан на подложку `surface`.\n *\n * Именно он, а не `accent`, красит иконку и текст: на светлой теме\n * `--gr-success` (#10b981) на `--gr-success-light` (#d1fae5) даёт 2.24:1 —\n * ниже порога 3:1 даже для нетекстовой графики. `--gr-success-text`\n * (#065f46) на той же подложке даёт 6.78:1.\n */\n text: string\n icon: GrAlertIconKey\n}\n\n/**\n * Тон → тройка токенов.\n *\n * `primary` и `neutral` не имеют пары `-light`/`-text` и отображаются на\n * ближайшие семантические роли: акцентную поверхность и нейтральную muted.\n */\nconst TONE_TOKENS: Record<GrTone, GrAlertToneTokens> = {\n primary: { surface: 'var(--gr-accent)', accent: 'var(--gr-primary)', text: 'var(--gr-accent-fg)', icon: 'info' },\n neutral: { surface: 'var(--gr-muted)', accent: 'var(--gr-muted-fg)', text: 'var(--gr-fg)', icon: 'info' },\n success: { surface: 'var(--gr-success-light)', accent: 'var(--gr-success)', text: 'var(--gr-success-text)', icon: 'success' },\n warning: { surface: 'var(--gr-warning-light)', accent: 'var(--gr-warning)', text: 'var(--gr-warning-text)', icon: 'warning' },\n danger: { surface: 'var(--gr-danger-light)', accent: 'var(--gr-danger)', text: 'var(--gr-danger-text)', icon: 'danger' },\n info: { surface: 'var(--gr-info-light)', accent: 'var(--gr-info)', text: 'var(--gr-info-text)', icon: 'info' },\n slate: { surface: 'var(--gr-slate-light)', accent: 'var(--gr-slate)', text: 'var(--gr-slate-text)', icon: 'info' },\n azure: { surface: 'var(--gr-azure-light)', accent: 'var(--gr-azure)', text: 'var(--gr-azure-text)', icon: 'info' },\n}\n\n/** Разрешённые цвета одного алерта — ровно то, что уходит в CSS-переменные. */\nexport type GrAlertColors = {\n bg: string\n border: string\n icon: string\n title: string\n text: string\n close: string\n closeHover: string\n closeHoverBg: string\n}\n\n/** Пользовательские переопределения цвета (пропы-эскейпы). */\nexport type GrAlertColorOverrides = {\n backgroundColor?: string\n textColor?: string\n borderColor?: string\n}\n\nexport function grAlertIconKey(tone: GrTone): GrAlertIconKey {\n return TONE_TOKENS[tone].icon\n}\n\n/**\n * Рамка выводится из тона формулой, а не задаётся отдельным токеном на каждый\n * тон: доля тона в смеси с `--gr-brd` — единственное, что отличает рамки между\n * собой. 22% (как было) давало 1.27–1.51:1 к собственной подложке — край\n * практически не читался; 35/45% поднимают его до 1.42–2.07:1.\n */\nfunction border(accent: string, ratio: number): string {\n return `color-mix(in srgb, ${accent} ${ratio}%, var(--gr-brd))`\n}\n\nexport function resolveGrAlertColors(tone: GrTone, variant: GrAlertVariant): GrAlertColors {\n const t = TONE_TOKENS[tone]\n\n if (variant === 'outline') {\n return {\n bg: 'var(--gr-bg)',\n border: border(t.accent, 45),\n icon: t.text,\n title: 'var(--gr-fg)',\n text: 'var(--gr-muted-fg)',\n close: 'var(--gr-muted-fg)',\n closeHover: 'var(--gr-fg)',\n closeHoverBg: 'var(--gr-muted)',\n }\n }\n\n return {\n bg: t.surface,\n border: border(t.accent, 35),\n // Иерархия «заголовок / текст» держится на размере и насыщении шрифта, а не\n // на цвете: приглушать тональный текст подмешиванием подложки нельзя —\n // на тёмной теме warning и danger проваливают 4.5:1 уже при 15% подмеса.\n icon: t.text,\n title: t.text,\n text: t.text,\n close: t.text,\n closeHover: t.text,\n closeHoverBg: `color-mix(in srgb, ${t.accent} 16%, transparent)`,\n }\n}\n\n/**\n * Пропы-эскейпы поверх тона. `textColor` красит весь текстовый слой целиком\n * (иконка, заголовок, текст, кнопка закрытия) — иначе пользовательский цвет\n * соседствовал бы с тональным и давал грязь.\n */\nexport function applyGrAlertOverrides(colors: GrAlertColors, overrides: GrAlertColorOverrides): GrAlertColors {\n const custom = (value?: string) => value?.trim() || undefined\n const text = custom(overrides.textColor)\n\n return {\n ...colors,\n bg: custom(overrides.backgroundColor) ?? colors.bg,\n border: custom(overrides.borderColor) ?? colors.border,\n icon: text ?? colors.icon,\n title: text ?? colors.title,\n text: text ?? colors.text,\n close: text ?? colors.close,\n closeHover: text ?? colors.closeHover,\n closeHoverBg: text ? `color-mix(in srgb, ${text} 12%, transparent)` : colors.closeHoverBg,\n }\n}\n\n/**\n * Публичный контракт кастомизации: переменные `--gr-alert-*` можно\n * переопределить снаружи так же, как их задаёт компонент.\n */\nexport function grAlertCssVars(colors: GrAlertColors): Record<string, string> {\n return {\n '--gr-alert-bg': colors.bg,\n '--gr-alert-brd': colors.border,\n '--gr-alert-icon-color': colors.icon,\n '--gr-alert-title-color': colors.title,\n '--gr-alert-text-color': colors.text,\n '--gr-alert-close-color': colors.close,\n '--gr-alert-close-hover-color': colors.closeHover,\n '--gr-alert-close-hover-bg': colors.closeHoverBg,\n }\n}\n","<script setup lang=\"ts\">\n/**\n * GrAlert — блочное сообщение с тоном, иконкой и опциональным закрытием.\n *\n * Цветовая модель вынесена в `grAlertStyles.ts` и выражена **только токенами**\n * `--gr-*`: компонент одинаково корректен в light и dark без единого hex-литерала.\n *\n * Точки кастомизации — переменные `--gr-alert-*` (см. `grAlertCssVars`); пропы\n * `backgroundColor`/`textColor`/`borderColor` — тот же контракт, но точечно.\n */\nimport { computed } from 'vue'\n\nimport IconWarning from '~icons/lucide/alert-triangle'\nimport IconCheck from '~icons/lucide/check-circle'\nimport IconInfo from '~icons/lucide/info'\nimport IconClose from '~icons/lucide/x'\nimport IconError from '~icons/lucide/x-circle'\n\nimport type { Component } from 'vue'\nimport type { GrTone } from '../shared/tones'\nimport type { GrAlertIconKey, GrAlertVariant } from './grAlertStyles'\n\nimport { useGrComponentProp } from '../GrConfigProvider/context'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport {\n applyGrAlertOverrides,\n grAlertCssVars,\n grAlertIconKey,\n resolveGrAlertColors,\n} from './grAlertStyles'\n\nimport './defaults'\n\nexport type GrAlertTone = GrTone\nexport type { GrAlertVariant } from './grAlertStyles'\n\n/**\n * Режим объявления сообщения скринридеру.\n * `auto` — по тону: `warning`/`danger` перебивают речь (`role=\"alert\"`),\n * остальные ждут паузы (`role=\"status\"`).\n */\nexport type GrAlertLive = 'auto' | 'assertive' | 'polite' | 'off'\n\nexport interface GrAlertProps {\n tone?: GrAlertTone\n variant?: GrAlertVariant\n title?: string\n closable?: boolean\n live?: GrAlertLive\n /**\n * Показывать ли иконку тона. Своя иконка — слотом `#icon`; проп нужен для\n * случая «иконка мешает»: узкая колонка, плотный список, сообщение в форме.\n */\n icon?: boolean\n /**\n * Видимость сообщения — **только контролируемая**: без пропа алерт виден\n * всегда, и закрытие остаётся заботой потребителя (`@close`). С\n * `v-model:visible` компонент прячет себя сам.\n *\n * Собственного состояния у пропа нет намеренно. Оно превратило бы кнопку\n * закрытия в необратимое действие у всех, кто уже живёт на `@close` и,\n * например, спрашивает по нему подтверждение.\n */\n visible?: boolean\n backgroundColor?: string\n textColor?: string\n borderColor?: string\n}\n\nexport interface GrAlertEmits {\n (e: 'close'): void\n /** Пользователь закрыл сообщение (`v-model:visible`). */\n (e: 'update:visible', value: boolean): void\n}\n\nconst emit = defineEmits<GrAlertEmits>()\n\ndefineSlots<{\n /** Текст сообщения. */\n default?: () => any\n /** Иконка вместо глифа по тону. Декоративна: остаётся `aria-hidden`. */\n icon?: () => any\n /** Действия по сообщению: «Повторить», «Подробнее». Ложатся под текст. */\n actions?: () => any\n}>()\n\n// Дефолты tone/variant/closable/live намеренно `undefined`: «настоящий» дефолт\n// переехал в `useGrComponentProp`, иначе `GrConfigProvider` не смог бы отличить\n// заданный пользователем проп от подставленного Vue.\nconst props = withDefaults(defineProps<GrAlertProps>(), {\n tone: undefined,\n variant: undefined,\n title: undefined,\n closable: undefined,\n live: undefined,\n icon: undefined,\n visible: undefined,\n backgroundColor: undefined,\n textColor: undefined,\n borderColor: undefined,\n})\n\nconst { t } = useGranularityTranslations()\n\nconst tone = useGrComponentProp('GrAlert', 'tone', () => props.tone, 'info')\nconst variantInput = useGrComponentProp('GrAlert', 'variant', () => props.variant, 'soft')\nconst closable = useGrComponentProp('GrAlert', 'closable', () => props.closable, false)\nconst live = useGrComponentProp('GrAlert', 'live', () => props.live, 'auto')\n\nconst variant = variantInput\n\nconst ICONS: Record<GrAlertIconKey, Component> = {\n info: IconInfo,\n success: IconCheck,\n warning: IconWarning,\n danger: IconError,\n}\n\nconst toneIcon = computed<Component>(() => ICONS[grAlertIconKey(tone.value)])\n\nconst showIcon = computed(() => props.icon !== false)\nconst isVisible = computed(() => props.visible !== false)\n\nfunction close(): void {\n emit('update:visible', false)\n emit('close')\n}\n\nconst colors = computed(() => applyGrAlertOverrides(\n resolveGrAlertColors(tone.value, variant.value),\n props,\n))\n\nconst rootStyle = computed(() => ({\n ...grAlertCssVars(colors.value),\n background: 'var(--gr-alert-bg)',\n borderColor: 'var(--gr-alert-brd)',\n}))\n\n/**\n * `role=\"alert\"` — assertive-регион: он прерывает чтение. Вешать его на любое\n * информационное сообщение значит превращать спокойную подсказку в тревогу,\n * поэтому по умолчанию его получают только `warning` и `danger`.\n */\nconst role = computed(() => {\n const mode = live.value === 'auto'\n ? (tone.value === 'danger' || tone.value === 'warning' ? 'assertive' : 'polite')\n : live.value\n\n if (mode === 'off')\n return undefined\n return mode === 'assertive' ? 'alert' : 'status'\n})\n</script>\n\n<template>\n <div\n v-if=\"isVisible\"\n :role=\"role\"\n data-gr-alert\n class=\"rounded-[var(--gr-radius-lg)] border px-4 py-3\"\n :style=\"rootStyle\"\n >\n <div class=\"flex items-start gap-3\">\n <span\n v-if=\"showIcon\"\n data-gr-alert-icon\n class=\"mt-0.5 h-5 w-5 shrink-0 text-[var(--gr-alert-icon-color)]\"\n aria-hidden=\"true\"\n >\n <slot name=\"icon\">\n <component :is=\"toneIcon\" class=\"h-5 w-5\" />\n </slot>\n </span>\n\n <div class=\"min-w-0 flex-1\">\n <div\n v-if=\"props.title\"\n class=\"text-[length:var(--gr-text-sm)] leading-[var(--gr-leading-sm)] font-600 text-[var(--gr-alert-title-color)]\"\n >\n {{ props.title }}\n </div>\n <div\n class=\"text-[length:var(--gr-text-sm)] leading-relaxed text-[var(--gr-alert-text-color)]\"\n :class=\"props.title ? 'mt-1' : ''\"\n >\n <slot />\n </div>\n\n <!-- Действия идут под текстом, а не рядом с ним: на узкой колонке\n кнопки в одной строке с сообщением отжимают его до одного слова. -->\n <div\n v-if=\"$slots.actions\"\n data-gr-alert-actions\n class=\"mt-2 flex flex-wrap gap-2\"\n >\n <slot name=\"actions\" />\n </div>\n </div>\n\n <button\n v-if=\"closable\"\n type=\"button\"\n class=\"-mr-1.5 -mt-1.5 shrink-0 rounded-[var(--gr-radius-md)] p-1.5 text-[var(--gr-alert-close-color)] transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--gr-ring)] hover:bg-[var(--gr-alert-close-hover-bg)] hover:text-[var(--gr-alert-close-hover-color)]\"\n :aria-label=\"t('gr.common.close', 'Close')\"\n @click=\"close\"\n >\n <IconClose class=\"h-4 w-4\" aria-hidden=\"true\" />\n </button>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * GrAlert — блочное сообщение с тоном, иконкой и опциональным закрытием.\n *\n * Цветовая модель вынесена в `grAlertStyles.ts` и выражена **только токенами**\n * `--gr-*`: компонент одинаково корректен в light и dark без единого hex-литерала.\n *\n * Точки кастомизации — переменные `--gr-alert-*` (см. `grAlertCssVars`); пропы\n * `backgroundColor`/`textColor`/`borderColor` — тот же контракт, но точечно.\n */\nimport { computed } from 'vue'\n\nimport IconWarning from '~icons/lucide/alert-triangle'\nimport IconCheck from '~icons/lucide/check-circle'\nimport IconInfo from '~icons/lucide/info'\nimport IconClose from '~icons/lucide/x'\nimport IconError from '~icons/lucide/x-circle'\n\nimport type { Component } from 'vue'\nimport type { GrTone } from '../shared/tones'\nimport type { GrAlertIconKey, GrAlertVariant } from './grAlertStyles'\n\nimport { useGrComponentProp } from '../GrConfigProvider/context'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport {\n applyGrAlertOverrides,\n grAlertCssVars,\n grAlertIconKey,\n resolveGrAlertColors,\n} from './grAlertStyles'\n\nimport './defaults'\n\nexport type GrAlertTone = GrTone\nexport type { GrAlertVariant } from './grAlertStyles'\n\n/**\n * Режим объявления сообщения скринридеру.\n * `auto` — по тону: `warning`/`danger` перебивают речь (`role=\"alert\"`),\n * остальные ждут паузы (`role=\"status\"`).\n */\nexport type GrAlertLive = 'auto' | 'assertive' | 'polite' | 'off'\n\nexport interface GrAlertProps {\n tone?: GrAlertTone\n variant?: GrAlertVariant\n title?: string\n closable?: boolean\n live?: GrAlertLive\n /**\n * Показывать ли иконку тона. Своя иконка — слотом `#icon`; проп нужен для\n * случая «иконка мешает»: узкая колонка, плотный список, сообщение в форме.\n */\n icon?: boolean\n /**\n * Видимость сообщения — **только контролируемая**: без пропа алерт виден\n * всегда, и закрытие остаётся заботой потребителя (`@close`). С\n * `v-model:visible` компонент прячет себя сам.\n *\n * Собственного состояния у пропа нет намеренно. Оно превратило бы кнопку\n * закрытия в необратимое действие у всех, кто уже живёт на `@close` и,\n * например, спрашивает по нему подтверждение.\n */\n visible?: boolean\n backgroundColor?: string\n textColor?: string\n borderColor?: string\n}\n\nexport interface GrAlertEmits {\n (e: 'close'): void\n /** Пользователь закрыл сообщение (`v-model:visible`). */\n (e: 'update:visible', value: boolean): void\n}\n\nconst emit = defineEmits<GrAlertEmits>()\n\ndefineSlots<{\n /** Текст сообщения. */\n default?: () => any\n /** Иконка вместо глифа по тону. Декоративна: остаётся `aria-hidden`. */\n icon?: () => any\n /** Действия по сообщению: «Повторить», «Подробнее». Ложатся под текст. */\n actions?: () => any\n}>()\n\n// Дефолты tone/variant/closable/live намеренно `undefined`: «настоящий» дефолт\n// переехал в `useGrComponentProp`, иначе `GrConfigProvider` не смог бы отличить\n// заданный пользователем проп от подставленного Vue.\nconst props = withDefaults(defineProps<GrAlertProps>(), {\n tone: undefined,\n variant: undefined,\n title: undefined,\n closable: undefined,\n live: undefined,\n icon: undefined,\n visible: undefined,\n backgroundColor: undefined,\n textColor: undefined,\n borderColor: undefined,\n})\n\nconst { t } = useGranularityTranslations()\n\nconst tone = useGrComponentProp('GrAlert', 'tone', () => props.tone, 'info')\nconst variantInput = useGrComponentProp('GrAlert', 'variant', () => props.variant, 'soft')\nconst closable = useGrComponentProp('GrAlert', 'closable', () => props.closable, false)\nconst live = useGrComponentProp('GrAlert', 'live', () => props.live, 'auto')\n\nconst variant = variantInput\n\nconst ICONS: Record<GrAlertIconKey, Component> = {\n info: IconInfo,\n success: IconCheck,\n warning: IconWarning,\n danger: IconError,\n}\n\nconst toneIcon = computed<Component>(() => ICONS[grAlertIconKey(tone.value)])\n\nconst showIcon = computed(() => props.icon !== false)\nconst isVisible = computed(() => props.visible !== false)\n\nfunction close(): void {\n emit('update:visible', false)\n emit('close')\n}\n\nconst colors = computed(() => applyGrAlertOverrides(\n resolveGrAlertColors(tone.value, variant.value),\n props,\n))\n\nconst rootStyle = computed(() => ({\n ...grAlertCssVars(colors.value),\n background: 'var(--gr-alert-bg)',\n borderColor: 'var(--gr-alert-brd)',\n}))\n\n/**\n * `role=\"alert\"` — assertive-регион: он прерывает чтение. Вешать его на любое\n * информационное сообщение значит превращать спокойную подсказку в тревогу,\n * поэтому по умолчанию его получают только `warning` и `danger`.\n */\nconst role = computed(() => {\n const mode = live.value === 'auto'\n ? (tone.value === 'danger' || tone.value === 'warning' ? 'assertive' : 'polite')\n : live.value\n\n if (mode === 'off')\n return undefined\n return mode === 'assertive' ? 'alert' : 'status'\n})\n</script>\n\n<template>\n <div\n v-if=\"isVisible\"\n :role=\"role\"\n data-gr-alert\n class=\"rounded-[var(--gr-radius-lg)] border px-4 py-3\"\n :style=\"rootStyle\"\n >\n <div class=\"flex items-start gap-3\">\n <span\n v-if=\"showIcon\"\n data-gr-alert-icon\n class=\"mt-0.5 h-5 w-5 shrink-0 text-[var(--gr-alert-icon-color)]\"\n aria-hidden=\"true\"\n >\n <slot name=\"icon\">\n <component :is=\"toneIcon\" class=\"h-5 w-5\" />\n </slot>\n </span>\n\n <div class=\"min-w-0 flex-1\">\n <div\n v-if=\"props.title\"\n class=\"text-[length:var(--gr-text-sm)] leading-[var(--gr-leading-sm)] font-600 text-[var(--gr-alert-title-color)]\"\n >\n {{ props.title }}\n </div>\n <div\n class=\"text-[length:var(--gr-text-sm)] leading-relaxed text-[var(--gr-alert-text-color)]\"\n :class=\"props.title ? 'mt-1' : ''\"\n >\n <slot />\n </div>\n\n <!-- Действия идут под текстом, а не рядом с ним: на узкой колонке\n кнопки в одной строке с сообщением отжимают его до одного слова. -->\n <div\n v-if=\"$slots.actions\"\n data-gr-alert-actions\n class=\"mt-2 flex flex-wrap gap-2\"\n >\n <slot name=\"actions\" />\n </div>\n </div>\n\n <button\n v-if=\"closable\"\n type=\"button\"\n class=\"-mr-1.5 -mt-1.5 shrink-0 rounded-[var(--gr-radius-md)] p-1.5 text-[var(--gr-alert-close-color)] transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--gr-ring)] hover:bg-[var(--gr-alert-close-hover-bg)] hover:text-[var(--gr-alert-close-hover-color)]\"\n :aria-label=\"t('gr.common.close', 'Close')\"\n @click=\"close\"\n >\n <IconClose class=\"h-4 w-4\" aria-hidden=\"true\" />\n </button>\n </div>\n </div>\n</template>\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GrAutocomplete-rYEY15UI.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/useComboboxNavigation'\nimport { useRovingFocus } from '../../composables/useRovingFocus'\nimport { filterOptions, resolveSelectedOptions } from '../shared/optionFilter'\nimport { useControlAddons } from '../../composables/internal/useControlAddons'\n\nimport type { GrBadgeRadius, GrBadgeSize, GrBadgeTone } from '../GrBadge/grBadgeStyles'\nimport GrChip from '../GrChip/GrChip.vue'\nimport { chipSizeForBadgeScale } from '../GrChip/grChipStyles'\nimport { panelPopTransition } from '../shared/overlayTransition'\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\nimport IconCheck from '~icons/lucide/check'\nimport IconChevronDown from '~icons/lucide/chevron-down'\nimport IconLoaderCircle from '~icons/lucide/loader-circle'\nimport IconX from '~icons/lucide/x'\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`. Рисует их `GrChip`, но\n * шкала пропа осталась бейджевой: `tagSize` публичен, и его значения обязаны\n * означать тот же кегль, что и раньше. Перевод ступеней — в\n * `chipSizeForBadgeScale`.\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\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,\n required: isRequired,\n readonly: isReadonly,\n} = 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)\n 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))\n return value\n if (isEmptyValue(value))\n 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)\n return ''\n // single: пока пользователь не начал вводить — показываем весь список.\n if (!props.multiple && !dirty.value)\n 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)\n return false\n // Кастомное значение набирается текстом — оно строковое по природе;\n // при числовом `TValue` эта ветка неприменима (см. docs/components.md).\n const v = query.value.trim() as TValue\n if (!v)\n return false\n if (props.multiple && selectedValues.value.includes(v))\n return false\n if (!props.multiple && v === modelSingle.value)\n 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)\n 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)\n 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)\n 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)\n 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)\n 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)\n 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)\n 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)\n return\n remoteOptions.value = result\n remoteAnswered.value = true\n }\n catch (error) {\n if (seq !== searchSeq || isAbortError(error))\n return\n remoteOptions.value = []\n remoteAnswered.value = true\n emit('searchError', error)\n }\n finally {\n if (seq === searchSeq)\n remoteLoading.value = false\n }\n}\n\nfunction scheduleSearch(value: string): void {\n if (searchTimer)\n clearTimeout(searchTimer)\n searchTimer = setTimeout(() => {\n if (props.minQueryLength > 0 && value.trim().length < props.minQueryLength)\n 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)\n 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)\n 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)\n 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)\n closeDropdown()\n else void nextTick(focusInput)\n}\n\nfunction chooseOption(option: GrAutocompleteOption<TValue>): void {\n if (locked.value || option.disabled)\n return\n if (props.multiple)\n toggleMultiple(option.value)\n else selectSingle(option.value, option.label)\n}\n\nfunction commitCustom(): void {\n if (locked.value)\n return\n // Кастомное значение строковое по природе — см. `canAddCustom`.\n const v = query.value.trim() as TValue\n if (!v)\n return\n if (props.multiple)\n toggleMultiple(v)\n else selectSingle(v, String(v))\n}\n\nfunction removeValue(value: TValue, focusAfter: () => void = focusInput): void {\n if (locked.value)\n 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)\n 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/печать.\n//\n// Отсюда же единственное отличие от `GrInputTag`: `tabindexFor` здесь не\n// используется. Кольцо нужно как движок навигации, а таб-стоп у ряда чипов\n// отсутствует вовсе — он принадлежит полю ввода.\nconst chipRefs = ref<Array<{ removeEl: HTMLButtonElement | null } | null>>([])\n\nfunction setChipRef(index: number) {\n return (el: unknown): void => {\n chipRefs.value[index] = el as { removeEl: HTMLButtonElement | null } | null\n }\n}\n\n/**\n * Проп `tagSize` объявлен по шкале бейджа и остаётся публичным контрактом:\n * дефолт `sm` обязан означать для потребителя тот же кегль, что и раньше.\n */\nconst chipSize = computed(() => chipSizeForBadgeScale(props.tagSize))\n\n// Адресация индексом, а не значением: значения дженерик-типа, и `TValue`\n// сравнивался бы по ссылке там, где опции пересоздаются.\nconst chipIndexes = computed(() => selectedOptions.value.map((_, index) => index))\n\nconst chipRoving = useRovingFocus<number>({\n items: () => chipIndexes.value,\n elementFor: index => chipRefs.value[index]?.removeEl,\n orientation: () => 'horizontal',\n // Ряд чипов — не кольцо: за правым краем стоит поле ввода, за левым ничего.\n wrap: () => false,\n onOverflow: (edge) => {\n if (edge === 'end')\n focusInput()\n return true\n },\n // Удаление чипа перерисовывает ряд: без ожидания фокус уехал бы на узел,\n // которого уже нет.\n beforeFocus: () => nextTick(),\n})\n\nfunction focusChip(index: number): void {\n const last = selectedOptions.value.length - 1\n if (last < 0) {\n focusInput()\n return\n }\n\n void chipRoving.focusKey(Math.min(Math.max(index, 0), last))\n}\n\nfunction onChipKeydown(event: KeyboardEvent, index: number, value: TValue): void {\n if (chipRoving.handleNavigationKeys(event))\n return\n\n switch (event.key) {\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)\n focusInput()\n }\n}\n\n// ————— Клавиатура.\nfunction onKeydown(event: KeyboardEvent): void {\n // Клавиша во время IME-композиции принадлежит композиции: Enter коммитит её,\n // Esc отменяет, стрелки ходят по кандидатам.\n if (isComposingEvent(event))\n return\n if (locked.value)\n 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))\n return\n\n switch (event.key) {\n case 'Enter': {\n if (!open.value)\n break\n event.preventDefault()\n const item = activeItem.value\n if (item?.kind === 'option')\n chooseOption(item.option)\n else if (item?.kind === 'add' || canAddCustom.value)\n 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)\n return\n // Не перетираем то, что пользователь сейчас набирает.\n if (open.value || dirty.value)\n 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 <GrChip\n v-for=\"(option, chipIndex) in selectedOptions\"\n :key=\"option.value\"\n :ref=\"setChipRef(chipIndex)\"\n data-gr-autocomplete-chip\n :tone=\"tagTone\"\n :dark=\"tagDark\"\n :size=\"chipSize\"\n :radius=\"tagRadius\"\n :closable=\"!locked\"\n :remove-label=\"t('gr.autocomplete.removeValue', 'Remove {label}', { label: option.label })\"\n :remove-tabindex=\"-1\"\n @remove=\"removeValue(option.value)\"\n @keydown=\"onChipKeydown($event, chipIndex, option.value)\"\n >\n <span class=\"truncate\">{{ option.label }}</span>\n </GrChip>\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 <IconLoaderCircle class=\"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 <IconX class=\"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 <IconChevronDown class=\"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=\"panelPopTransition.enter\"\n :enter-from-class=\"panelPopTransition.enterFrom\"\n :enter-to-class=\"panelPopTransition.enterTo\"\n :leave-active-class=\"panelPopTransition.leave\"\n :leave-from-class=\"panelPopTransition.leaveFrom\"\n :leave-to-class=\"panelPopTransition.leaveTo\"\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 class=\"inline-block h-4 w-4 shrink-0\" aria-hidden=\"true\">\n <IconCheck v-if=\"isSelected(option.value)\" class=\"block h-4 w-4 text-[var(--gr-primary-text)]\" />\n </span>\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 <IconLoaderCircle class=\"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/useComboboxNavigation'\nimport { useRovingFocus } from '../../composables/useRovingFocus'\nimport { filterOptions, resolveSelectedOptions } from '../shared/optionFilter'\nimport { useControlAddons } from '../../composables/internal/useControlAddons'\n\nimport type { GrBadgeRadius, GrBadgeSize, GrBadgeTone } from '../GrBadge/grBadgeStyles'\nimport GrChip from '../GrChip/GrChip.vue'\nimport { chipSizeForBadgeScale } from '../GrChip/grChipStyles'\nimport { panelPopTransition } from '../shared/overlayTransition'\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\nimport IconCheck from '~icons/lucide/check'\nimport IconChevronDown from '~icons/lucide/chevron-down'\nimport IconLoaderCircle from '~icons/lucide/loader-circle'\nimport IconX from '~icons/lucide/x'\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`. Рисует их `GrChip`, но\n * шкала пропа осталась бейджевой: `tagSize` публичен, и его значения обязаны\n * означать тот же кегль, что и раньше. Перевод ступеней — в\n * `chipSizeForBadgeScale`.\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\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,\n required: isRequired,\n readonly: isReadonly,\n} = 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)\n 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))\n return value\n if (isEmptyValue(value))\n 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)\n return ''\n // single: пока пользователь не начал вводить — показываем весь список.\n if (!props.multiple && !dirty.value)\n 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)\n return false\n // Кастомное значение набирается текстом — оно строковое по природе;\n // при числовом `TValue` эта ветка неприменима (см. docs/components.md).\n const v = query.value.trim() as TValue\n if (!v)\n return false\n if (props.multiple && selectedValues.value.includes(v))\n return false\n if (!props.multiple && v === modelSingle.value)\n 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)\n 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)\n 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)\n 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)\n 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)\n 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)\n 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)\n 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)\n return\n remoteOptions.value = result\n remoteAnswered.value = true\n }\n catch (error) {\n if (seq !== searchSeq || isAbortError(error))\n return\n remoteOptions.value = []\n remoteAnswered.value = true\n emit('searchError', error)\n }\n finally {\n if (seq === searchSeq)\n remoteLoading.value = false\n }\n}\n\nfunction scheduleSearch(value: string): void {\n if (searchTimer)\n clearTimeout(searchTimer)\n searchTimer = setTimeout(() => {\n if (props.minQueryLength > 0 && value.trim().length < props.minQueryLength)\n 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)\n 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)\n 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)\n 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)\n closeDropdown()\n else void nextTick(focusInput)\n}\n\nfunction chooseOption(option: GrAutocompleteOption<TValue>): void {\n if (locked.value || option.disabled)\n return\n if (props.multiple)\n toggleMultiple(option.value)\n else selectSingle(option.value, option.label)\n}\n\nfunction commitCustom(): void {\n if (locked.value)\n return\n // Кастомное значение строковое по природе — см. `canAddCustom`.\n const v = query.value.trim() as TValue\n if (!v)\n return\n if (props.multiple)\n toggleMultiple(v)\n else selectSingle(v, String(v))\n}\n\nfunction removeValue(value: TValue, focusAfter: () => void = focusInput): void {\n if (locked.value)\n 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)\n 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/печать.\n//\n// Отсюда же единственное отличие от `GrInputTag`: `tabindexFor` здесь не\n// используется. Кольцо нужно как движок навигации, а таб-стоп у ряда чипов\n// отсутствует вовсе — он принадлежит полю ввода.\nconst chipRefs = ref<Array<{ removeEl: HTMLButtonElement | null } | null>>([])\n\nfunction setChipRef(index: number) {\n return (el: unknown): void => {\n chipRefs.value[index] = el as { removeEl: HTMLButtonElement | null } | null\n }\n}\n\n/**\n * Проп `tagSize` объявлен по шкале бейджа и остаётся публичным контрактом:\n * дефолт `sm` обязан означать для потребителя тот же кегль, что и раньше.\n */\nconst chipSize = computed(() => chipSizeForBadgeScale(props.tagSize))\n\n// Адресация индексом, а не значением: значения дженерик-типа, и `TValue`\n// сравнивался бы по ссылке там, где опции пересоздаются.\nconst chipIndexes = computed(() => selectedOptions.value.map((_, index) => index))\n\nconst chipRoving = useRovingFocus<number>({\n items: () => chipIndexes.value,\n elementFor: index => chipRefs.value[index]?.removeEl,\n orientation: () => 'horizontal',\n // Ряд чипов — не кольцо: за правым краем стоит поле ввода, за левым ничего.\n wrap: () => false,\n onOverflow: (edge) => {\n if (edge === 'end')\n focusInput()\n return true\n },\n // Удаление чипа перерисовывает ряд: без ожидания фокус уехал бы на узел,\n // которого уже нет.\n beforeFocus: () => nextTick(),\n})\n\nfunction focusChip(index: number): void {\n const last = selectedOptions.value.length - 1\n if (last < 0) {\n focusInput()\n return\n }\n\n void chipRoving.focusKey(Math.min(Math.max(index, 0), last))\n}\n\nfunction onChipKeydown(event: KeyboardEvent, index: number, value: TValue): void {\n if (chipRoving.handleNavigationKeys(event))\n return\n\n switch (event.key) {\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)\n focusInput()\n }\n}\n\n// ————— Клавиатура.\nfunction onKeydown(event: KeyboardEvent): void {\n // Клавиша во время IME-композиции принадлежит композиции: Enter коммитит её,\n // Esc отменяет, стрелки ходят по кандидатам.\n if (isComposingEvent(event))\n return\n if (locked.value)\n 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))\n return\n\n switch (event.key) {\n case 'Enter': {\n if (!open.value)\n break\n event.preventDefault()\n const item = activeItem.value\n if (item?.kind === 'option')\n chooseOption(item.option)\n else if (item?.kind === 'add' || canAddCustom.value)\n 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)\n return\n // Не перетираем то, что пользователь сейчас набирает.\n if (open.value || dirty.value)\n 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 <GrChip\n v-for=\"(option, chipIndex) in selectedOptions\"\n :key=\"option.value\"\n :ref=\"setChipRef(chipIndex)\"\n data-gr-autocomplete-chip\n :tone=\"tagTone\"\n :dark=\"tagDark\"\n :size=\"chipSize\"\n :radius=\"tagRadius\"\n :closable=\"!locked\"\n :remove-label=\"t('gr.autocomplete.removeValue', 'Remove {label}', { label: option.label })\"\n :remove-tabindex=\"-1\"\n @remove=\"removeValue(option.value)\"\n @keydown=\"onChipKeydown($event, chipIndex, option.value)\"\n >\n <span class=\"truncate\">{{ option.label }}</span>\n </GrChip>\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 <IconLoaderCircle class=\"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 <IconX class=\"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 <IconChevronDown class=\"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=\"panelPopTransition.enter\"\n :enter-from-class=\"panelPopTransition.enterFrom\"\n :enter-to-class=\"panelPopTransition.enterTo\"\n :leave-active-class=\"panelPopTransition.leave\"\n :leave-from-class=\"panelPopTransition.leaveFrom\"\n :leave-to-class=\"panelPopTransition.leaveTo\"\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 class=\"inline-block h-4 w-4 shrink-0\" aria-hidden=\"true\">\n <IconCheck v-if=\"isSelected(option.value)\" class=\"block h-4 w-4 text-[var(--gr-primary-text)]\" />\n </span>\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 <IconLoaderCircle class=\"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,3 +1,3 @@
|
|
|
1
1
|
import { n as grAutocompleteSafelist, t as grAutocompleteConfig } from "../../chunks/config-vYMJ3Cr1.js";
|
|
2
|
-
import { t as GrAutocomplete_default } from "./chunks/GrAutocomplete-
|
|
2
|
+
import { t as GrAutocomplete_default } from "./chunks/GrAutocomplete-rYEY15UI.js";
|
|
3
3
|
export { GrAutocomplete_default as GrAutocomplete, GrAutocomplete_default as default, grAutocompleteConfig, grAutocompleteSafelist };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
[data-gr-virtual][data-v-
|
|
1
|
+
[data-gr-virtual][data-v-34ca5aa5]:before,[data-gr-virtual][data-v-34ca5aa5]:after{content:"";flex:none;display:block}[data-gr-virtual][data-v-34ca5aa5]:before{height:var(--gr-virtual-before,0px)}[data-gr-virtual][data-v-34ca5aa5]:after{height:var(--gr-virtual-after,0px)}
|