@feugene/granularity 0.5.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunks/{adapter-K6zZTzBi.js.map → adapter-CRyH4YC8.js.map} +1 -1
- package/dist/chunks/{config-Cgk5ti-b.js.map → config-3SftO0uG.js.map} +1 -1
- package/dist/chunks/{config-BN-B2K4r.js.map → config-B44QVIge.js.map} +1 -1
- package/dist/chunks/{config-C-GVBa19.js.map → config-B8rC_K97.js.map} +1 -1
- package/dist/chunks/{config-DEv4m-ZP.js.map → config-BXffa3ta.js.map} +1 -1
- package/dist/chunks/{config-CUgvsjZx.js.map → config-BhvNm4Pp.js.map} +1 -1
- package/dist/chunks/{config-BNOf_qjD.js.map → config-C0b_3LJV.js.map} +1 -1
- package/dist/chunks/config-C0rHS1-l.js.map +1 -0
- package/dist/chunks/{config-fCcRwjIB.js.map → config-C4qAA0Sb.js.map} +1 -1
- package/dist/chunks/{config-XwI6t8cn.js.map → config-C5TN9J2Z.js.map} +1 -1
- package/dist/chunks/config-CVnluhKO.js.map +1 -0
- package/dist/chunks/{config-DUtUkSzc.js.map → config-CjEplP6D.js.map} +1 -1
- package/dist/chunks/{config-D19pyP4D.js.map → config-CtomKf-k.js.map} +1 -1
- package/dist/chunks/{config-BvuQUBtZ.js.map → config-CxCAfXZ1.js.map} +1 -1
- package/dist/chunks/{config-BDiLiqSk.js.map → config-D1DI3roo.js.map} +1 -1
- package/dist/chunks/{config-CgjOJ3aF.js.map → config-D27BL0H9.js.map} +1 -1
- package/dist/chunks/config-D32znLtV2.js.map +1 -0
- package/dist/chunks/{config-YSOeh6cy.js.map → config-D6Af6WZw.js.map} +1 -1
- package/dist/chunks/{config-BulF0Bf-.js.map → config-D98m8iyz.js.map} +1 -1
- package/dist/chunks/config-DDLoUczC.js.map +1 -0
- package/dist/chunks/{config-CFcv0hjb.js.map → config-DQOSlWvp.js.map} +1 -1
- package/dist/chunks/{config-ndXqsz5A.js.map → config-DbqFk-IW.js.map} +1 -1
- package/dist/chunks/{config-CdHY0Z7L.js.map → config-DnsISjD8.js.map} +1 -1
- package/dist/chunks/{config-BFZN9-vt.js.map → config-DudL02ag.js.map} +1 -1
- package/dist/chunks/{config-DgRJlF00.js.map → config-Dusr9jcE.js.map} +1 -1
- package/dist/chunks/{config-CWvHiaqr2.js.map → config-DxqMXzNV2.js.map} +1 -1
- package/dist/chunks/config-Mp9DJ9Mq2.js.map +1 -0
- package/dist/chunks/{config-R0jaXatX.js → config-XoANT5mU.js} +1 -1
- package/dist/chunks/{config-R0jaXatX.js.map → config-XoANT5mU.js.map} +1 -1
- package/dist/chunks/config-ZJVOVgqR.js +22 -0
- package/dist/chunks/config-ZJVOVgqR.js.map +1 -0
- package/dist/chunks/{config-CM7nbATU2.js.map → config-rxFL-6yF2.js.map} +1 -1
- package/dist/chunks/{config-bpQYM5O-.js.map → config-wo-ncgTH.js.map} +1 -1
- package/dist/chunks/{en-BeJa78lP.js → en-QNZhjmSJ.js} +18 -1
- package/dist/chunks/en-QNZhjmSJ.js.map +1 -0
- package/dist/chunks/{es-axYOwwWD.js → es-C1c5XsIK.js} +18 -1
- package/dist/chunks/es-C1c5XsIK.js.map +1 -0
- package/dist/chunks/{es-BPqm7rLm.js → es-CKLPuE0h.js} +3 -3
- package/dist/chunks/{es-BPqm7rLm.js.map → es-CKLPuE0h.js.map} +1 -1
- package/dist/chunks/{grSelectStyles-daEeDlBn.js.map → grSelectStyles-DTAYNzxn.js.map} +1 -1
- package/dist/chunks/grStyle-5EMYYctb.js +19 -0
- package/dist/chunks/grStyle-5EMYYctb.js.map +1 -0
- package/dist/chunks/granularityI18n-CXa_uN7f.js +46 -0
- package/dist/chunks/granularityI18n-CXa_uN7f.js.map +1 -0
- package/dist/chunks/{ru-Dqh-2C00.js → ru-DcF7ciz0.js} +18 -1
- package/dist/chunks/ru-DcF7ciz0.js.map +1 -0
- package/dist/chunks/{shared-CyCPkimZ.js → shared-BkIIEolF.js} +37 -30
- package/dist/chunks/shared-BkIIEolF.js.map +1 -0
- package/dist/components/GrFileUpload/chunks/{GrFileUpload-u01j9S0t.js → GrFileUpload-DYRERhI2.js} +258 -37
- package/dist/components/GrFileUpload/chunks/GrFileUpload-DYRERhI2.js.map +1 -0
- package/dist/components/GrFileUpload/index.js +3 -3
- package/dist/components/GrFormField/chunks/{GrFormField-C6PfAs4u.js.map → GrFormField-C1oWTT_0.js.map} +1 -1
- package/dist/components/GrFormField/index.js +2 -2
- package/dist/components/GrFormFile/chunks/{GrFormFile-DdM8Bc2B.js.map → GrFormFile-deDPicM2.js.map} +1 -1
- package/dist/components/GrFormFile/index.js +2 -2
- package/dist/components/GrFormSection/chunks/{GrFormSection-5djEaQZQ.js.map → GrFormSection-CWBk2EUm.js.map} +1 -1
- package/dist/components/GrFormSection/index.js +2 -2
- package/dist/components/GrImageViewer/chunks/{GrImageViewer-CjSnohW7.js.map → GrImageViewer-Cpj5zyZQ.js.map} +1 -1
- package/dist/components/GrImageViewer/index.js +2 -2
- package/dist/components/GrInput/chunks/{GrInput-ghxaTf6d.js.map → GrInput-CfO2RsM-.js.map} +1 -1
- package/dist/components/GrInput/index.js +2 -2
- package/dist/components/GrInputTag/chunks/{GrInputTag-B0x4U0Yk.js → GrInputTag-G_VYJPF9.js} +1 -1
- package/dist/components/GrInputTag/chunks/{GrInputTag-B0x4U0Yk.js.map → GrInputTag-G_VYJPF9.js.map} +1 -1
- package/dist/components/GrInputTag/index.js +2 -2
- package/dist/components/GrLink/chunks/{GrLink-DvvNkP0M.js → GrLink-BLPigqij.js} +1 -1
- package/dist/components/GrLink/chunks/{GrLink-DvvNkP0M.js.map → GrLink-BLPigqij.js.map} +1 -1
- package/dist/components/GrLink/index.js +2 -2
- package/dist/components/GrList/chunks/{GrList--Ib4zLNw.js.map → GrList-BPjQf-72.js.map} +1 -1
- package/dist/components/GrList/index.js +2 -2
- package/dist/components/GrLoading/index.js +1 -1
- package/dist/components/GrModal/index.js +1 -1
- package/dist/components/GrNavbar/chunks/{GrNavbar-nIyuiVj1.js → GrNavbar-BoVuSZ-V.js} +1 -1
- package/dist/components/GrNavbar/chunks/{GrNavbar-nIyuiVj1.js.map → GrNavbar-BoVuSZ-V.js.map} +1 -1
- package/dist/components/GrNavbar/index.js +2 -2
- package/dist/components/GrNumberInput/chunks/{GrNumberInput-B0mPLjdv.js → GrNumberInput-ECtFCIKV.js} +1 -1
- package/dist/components/GrNumberInput/chunks/{GrNumberInput-B0mPLjdv.js.map → GrNumberInput-ECtFCIKV.js.map} +1 -1
- package/dist/components/GrNumberInput/index.js +2 -2
- package/dist/components/GrPagination/chunks/{GrPagination-C_rxGwfY.js → GrPagination-DW44c2-a.js} +2 -2
- package/dist/components/GrPagination/chunks/{GrPagination-C_rxGwfY.js.map → GrPagination-DW44c2-a.js.map} +1 -1
- package/dist/components/GrPagination/index.js +2 -2
- package/dist/components/GrProgressBar/chunks/{GrProgressBar-MvMnzsJl.js → GrProgressBar-hQQK8cYh.js} +1 -1
- package/dist/components/GrProgressBar/chunks/{GrProgressBar-MvMnzsJl.js.map → GrProgressBar-hQQK8cYh.js.map} +1 -1
- package/dist/components/GrProgressBar/index.js +2 -2
- package/dist/components/GrPromptDialog/chunks/{GrPromptDialog-DKhbN6ji.js → GrPromptDialog-CUMW6_58.js} +2 -2
- package/dist/components/GrPromptDialog/chunks/{GrPromptDialog-DKhbN6ji.js.map → GrPromptDialog-CUMW6_58.js.map} +1 -1
- package/dist/components/GrPromptDialog/index.js +2 -2
- package/dist/components/GrRadio/chunks/{GrRadio-BxBs3WHq.js → GrRadio-B_t_ZNVl.js} +1 -1
- package/dist/components/GrRadio/chunks/{GrRadio-BxBs3WHq.js.map → GrRadio-B_t_ZNVl.js.map} +1 -1
- package/dist/components/GrRadio/index.js +2 -2
- package/dist/components/GrRadioGroup/chunks/{GrRadioGroup-CEAfJQ6A.js → GrRadioGroup-CVlgpUBr.js} +1 -1
- package/dist/components/GrRadioGroup/chunks/{GrRadioGroup-CEAfJQ6A.js.map → GrRadioGroup-CVlgpUBr.js.map} +1 -1
- package/dist/components/GrRadioGroup/index.js +2 -2
- package/dist/components/GrSegmented/chunks/{GrSegmented-Bwn2qOY4.js → GrSegmented-BPl1koHc.js} +1 -1
- package/dist/components/GrSegmented/chunks/{GrSegmented-Bwn2qOY4.js.map → GrSegmented-BPl1koHc.js.map} +1 -1
- package/dist/components/GrSegmented/index.js +2 -2
- package/dist/components/GrSelect/chunks/{GrSelect-BMMT5MrT.js → GrSelect-C3NAfTqL.js} +2 -2
- package/dist/components/GrSelect/chunks/{GrSelect-BMMT5MrT.js.map → GrSelect-C3NAfTqL.js.map} +1 -1
- package/dist/components/GrSelect/index.js +2 -2
- package/dist/components/GrSidebar/chunks/{GrSidebar-CSZ3RpFB.js.map → GrSidebar-DVucgcfV.js.map} +1 -1
- package/dist/components/GrSidebar/index.js +2 -2
- package/dist/components/GrSkeleton/chunks/{GrSkeleton-DKXkqWDa.js.map → GrSkeleton-CDTvCSoH.js.map} +1 -1
- package/dist/components/GrSkeleton/index.js +2 -2
- package/dist/components/GrSwitch/chunks/{GrSwitch-CdBxwV3k.js → GrSwitch-Bnamgqb2.js} +1 -1
- package/dist/components/GrSwitch/chunks/{GrSwitch-CdBxwV3k.js.map → GrSwitch-Bnamgqb2.js.map} +1 -1
- package/dist/components/GrSwitch/index.js +2 -2
- package/dist/components/GrTable/index.js +1 -1
- package/dist/components/GrTabs/chunks/{GrTabs-BpF8AAUz.js.map → GrTabs-CiGmcmvm.js.map} +1 -1
- package/dist/components/GrTabs/index.js +2 -2
- package/dist/components/GrTextarea/chunks/{GrTextarea-DFS9ZkrA.js → GrTextarea-VBSc6A7D.js} +1 -1
- package/dist/components/GrTextarea/chunks/{GrTextarea-DFS9ZkrA.js.map → GrTextarea-VBSc6A7D.js.map} +1 -1
- package/dist/components/GrTextarea/index.js +2 -2
- package/dist/components/GrToaster/chunks/{GrToaster-3Mf0Moxt.js → GrToaster-I1e-zCpf.js} +1 -1
- package/dist/components/GrToaster/chunks/{GrToaster-3Mf0Moxt.js.map → GrToaster-I1e-zCpf.js.map} +1 -1
- package/dist/components/GrToaster/index.js +2 -2
- package/dist/components/GrTooltip/chunks/{GrTooltip-BvmsukLW.js.map → GrTooltip-CrV1gI_c.js.map} +1 -1
- package/dist/components/GrTooltip/index.js +2 -2
- package/dist/components/GrTree/chunks/{GrTree-C2kN-O4Q.js.map → GrTree-Dlev9oBd.js.map} +1 -1
- package/dist/components/GrTree/index.js +2 -2
- package/dist/components/GrTreeSelect/chunks/{GrTreeSelect-N-oPAOFj.js → GrTreeSelect-DkAIksRW.js} +4 -4
- package/dist/components/GrTreeSelect/chunks/{GrTreeSelect-N-oPAOFj.js.map → GrTreeSelect-DkAIksRW.js.map} +1 -1
- package/dist/components/GrTreeSelect/index.js +2 -2
- package/dist/directives/loading.js +15 -0
- package/dist/directives/loading.js.map +1 -1
- package/dist/granular-provider-node.js +2 -2
- package/dist/granular-provider.js +1 -1
- package/dist/i18n/all.js +1 -1
- package/dist/i18n/index.js +2 -2
- package/dist/index.js +1032 -58
- package/dist/index.js.map +1 -1
- package/dist/types/src/components/GrFileUpload/GrFileUpload.vue.d.ts +58 -5
- package/dist/types/src/components/GrFileUpload/index.d.ts +4 -0
- package/dist/types/src/components/GrFileUpload/uploadState.d.ts +38 -0
- package/dist/types/src/components/GrFileUpload/uploadViaXhr.d.ts +33 -0
- package/dist/types/src/components/GrImageViewer/GrImageViewer.vue.d.ts +1 -1
- package/dist/types/src/components/GrInput/GrInput.vue.d.ts +1 -1
- package/dist/types/src/components/GrNumberInput/GrNumberInput.vue.d.ts +1 -1
- package/dist/types/src/components/GrResponseErrorBanner/GrFormErrorBanner.vue.d.ts +70 -0
- package/dist/types/src/components/GrResponseErrorBanner/GrResponseErrorBanner.vue.d.ts +96 -0
- package/dist/types/src/components/GrResponseErrorBanner/GrUploadErrorBanner.vue.d.ts +73 -0
- package/dist/types/src/components/GrResponseErrorBanner/config.d.ts +1 -0
- package/dist/types/src/components/GrResponseErrorBanner/index.d.ts +10 -0
- package/dist/types/src/components/GrResponseErrorBanner/parsers/abortErrorParser.d.ts +15 -0
- package/dist/types/src/components/GrResponseErrorBanner/parsers/createResponseErrorClassifier.d.ts +28 -0
- package/dist/types/src/components/GrResponseErrorBanner/parsers/fileValidationParser.d.ts +12 -0
- package/dist/types/src/components/GrResponseErrorBanner/parsers/httpStatusParser.d.ts +15 -0
- package/dist/types/src/components/GrResponseErrorBanner/parsers/index.d.ts +11 -0
- package/dist/types/src/components/GrResponseErrorBanner/parsers/jsonApiErrorParser.d.ts +17 -0
- package/dist/types/src/components/GrResponseErrorBanner/parsers/laravelValidationParser.d.ts +15 -0
- package/dist/types/src/components/GrResponseErrorBanner/parsers/networkErrorParser.d.ts +14 -0
- package/dist/types/src/components/GrResponseErrorBanner/parsers/normalizeError.d.ts +19 -0
- package/dist/types/src/components/GrResponseErrorBanner/parsers/plainMessageParser.d.ts +15 -0
- package/dist/types/src/components/GrResponseErrorBanner/parsers/problemDetailsParser.d.ts +17 -0
- package/dist/types/src/components/GrResponseErrorBanner/responseError.defaults.d.ts +18 -0
- package/dist/types/src/components/GrResponseErrorBanner/responseError.types.d.ts +115 -0
- package/dist/types/src/components/GrResponseErrorBanner/useResponseError.d.ts +62 -0
- package/dist/types/src/components/GrTextarea/GrTextarea.vue.d.ts +1 -1
- package/dist/types/src/components/GrToaster/GrToaster.vue.d.ts +1 -1
- package/dist/types/src/granular-provider/shared.d.ts +1 -0
- package/dist/types/src/i18n/messages/en.d.ts +32 -0
- package/dist/types/src/i18n/messages/es.d.ts +32 -0
- package/dist/types/src/i18n/messages/ru.d.ts +32 -0
- package/dist/types/src/index.d.ts +1 -0
- package/package.json +1 -1
- package/dist/chunks/config-BGmiXpR82.js.map +0 -1
- package/dist/chunks/config-CA9XwYnZ2.js.map +0 -1
- package/dist/chunks/config-CsMIhZfM.js.map +0 -1
- package/dist/chunks/config-CwLoozAz2.js.map +0 -1
- package/dist/chunks/config-DCQO6H6h.js.map +0 -1
- package/dist/chunks/config-Da90XGsi.js +0 -38
- package/dist/chunks/config-Da90XGsi.js.map +0 -1
- package/dist/chunks/en-BeJa78lP.js.map +0 -1
- package/dist/chunks/es-axYOwwWD.js.map +0 -1
- package/dist/chunks/granularityI18n-GCqUz_lo.js +0 -26
- package/dist/chunks/granularityI18n-GCqUz_lo.js.map +0 -1
- package/dist/chunks/ru-Dqh-2C00.js.map +0 -1
- package/dist/chunks/shared-CyCPkimZ.js.map +0 -1
- package/dist/components/GrFileUpload/chunks/GrFileUpload-u01j9S0t.js.map +0 -1
- /package/dist/chunks/{adapter-K6zZTzBi.js → adapter-CRyH4YC8.js} +0 -0
- /package/dist/chunks/{config-Cgk5ti-b.js → config-3SftO0uG.js} +0 -0
- /package/dist/chunks/{config-BN-B2K4r.js → config-B44QVIge.js} +0 -0
- /package/dist/chunks/{config-C-GVBa19.js → config-B8rC_K97.js} +0 -0
- /package/dist/chunks/{config-DEv4m-ZP.js → config-BXffa3ta.js} +0 -0
- /package/dist/chunks/{config-CUgvsjZx.js → config-BhvNm4Pp.js} +0 -0
- /package/dist/chunks/{config-BNOf_qjD.js → config-C0b_3LJV.js} +0 -0
- /package/dist/chunks/{config-CA9XwYnZ2.js → config-C0rHS1-l.js} +0 -0
- /package/dist/chunks/{config-fCcRwjIB.js → config-C4qAA0Sb.js} +0 -0
- /package/dist/chunks/{config-XwI6t8cn.js → config-C5TN9J2Z.js} +0 -0
- /package/dist/chunks/{config-BGmiXpR82.js → config-CVnluhKO.js} +0 -0
- /package/dist/chunks/{config-DUtUkSzc.js → config-CjEplP6D.js} +0 -0
- /package/dist/chunks/{config-D19pyP4D.js → config-CtomKf-k.js} +0 -0
- /package/dist/chunks/{config-BvuQUBtZ.js → config-CxCAfXZ1.js} +0 -0
- /package/dist/chunks/{config-BDiLiqSk.js → config-D1DI3roo.js} +0 -0
- /package/dist/chunks/{config-CgjOJ3aF.js → config-D27BL0H9.js} +0 -0
- /package/dist/chunks/{config-DCQO6H6h.js → config-D32znLtV2.js} +0 -0
- /package/dist/chunks/{config-YSOeh6cy.js → config-D6Af6WZw.js} +0 -0
- /package/dist/chunks/{config-BulF0Bf-.js → config-D98m8iyz.js} +0 -0
- /package/dist/chunks/{config-CwLoozAz2.js → config-DDLoUczC.js} +0 -0
- /package/dist/chunks/{config-CFcv0hjb.js → config-DQOSlWvp.js} +0 -0
- /package/dist/chunks/{config-ndXqsz5A.js → config-DbqFk-IW.js} +0 -0
- /package/dist/chunks/{config-CdHY0Z7L.js → config-DnsISjD8.js} +0 -0
- /package/dist/chunks/{config-BFZN9-vt.js → config-DudL02ag.js} +0 -0
- /package/dist/chunks/{config-DgRJlF00.js → config-Dusr9jcE.js} +0 -0
- /package/dist/chunks/{config-CWvHiaqr2.js → config-DxqMXzNV2.js} +0 -0
- /package/dist/chunks/{config-CsMIhZfM.js → config-Mp9DJ9Mq2.js} +0 -0
- /package/dist/chunks/{config-CM7nbATU2.js → config-rxFL-6yF2.js} +0 -0
- /package/dist/chunks/{config-bpQYM5O-.js → config-wo-ncgTH.js} +0 -0
- /package/dist/chunks/{grSelectStyles-daEeDlBn.js → grSelectStyles-DTAYNzxn.js} +0 -0
- /package/dist/components/GrFormField/chunks/{GrFormField-C6PfAs4u.js → GrFormField-C1oWTT_0.js} +0 -0
- /package/dist/components/GrFormFile/chunks/{GrFormFile-DdM8Bc2B.js → GrFormFile-deDPicM2.js} +0 -0
- /package/dist/components/GrFormSection/chunks/{GrFormSection-5djEaQZQ.js → GrFormSection-CWBk2EUm.js} +0 -0
- /package/dist/components/GrImageViewer/chunks/{GrImageViewer-CjSnohW7.js → GrImageViewer-Cpj5zyZQ.js} +0 -0
- /package/dist/components/GrInput/chunks/{GrInput-ghxaTf6d.js → GrInput-CfO2RsM-.js} +0 -0
- /package/dist/components/GrList/chunks/{GrList--Ib4zLNw.js → GrList-BPjQf-72.js} +0 -0
- /package/dist/components/GrSidebar/chunks/{GrSidebar-CSZ3RpFB.js → GrSidebar-DVucgcfV.js} +0 -0
- /package/dist/components/GrSkeleton/chunks/{GrSkeleton-DKXkqWDa.js → GrSkeleton-CDTvCSoH.js} +0 -0
- /package/dist/components/GrTabs/chunks/{GrTabs-BpF8AAUz.js → GrTabs-CiGmcmvm.js} +0 -0
- /package/dist/components/GrTooltip/chunks/{GrTooltip-BvmsukLW.js → GrTooltip-CrV1gI_c.js} +0 -0
- /package/dist/components/GrTree/chunks/{GrTree-C2kN-O4Q.js → GrTree-Dlev9oBd.js} +0 -0
package/dist/components/GrSelect/chunks/{GrSelect-BMMT5MrT.js.map → GrSelect-C3NAfTqL.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GrSelect-BMMT5MrT.js","names":[],"sources":["../../../../src/components/GrSelect/GrSelect.vue","../../../../src/components/GrSelect/GrSelect.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, nextTick, onUnmounted, ref, watch } from 'vue'\n\nimport GrInput from '../GrInput/GrInput.vue'\nimport { vClickOutside } from '../../directives'\n\nimport {\n defaultBaseClass,\n grSelectLinkNativeLabelClass,\n grSelectLinkNativeOverlayClass,\n grSelectNativeClass,\n grSelectPanelClasses,\n grSelectTriggerClass,\n linkBaseClass,\n type GrSelectModelValue,\n type GrSelectOption,\n type GrSelectOptionsView,\n type GrSelectSize,\n type GrSelectUnderline,\n type GrSelectVariant,\n type GrSelectView,\n} from './grSelectStyles'\n\nexport type {\n GrSelectModelValue,\n GrSelectOption,\n GrSelectOptionsView,\n GrSelectSize,\n GrSelectUnderline,\n GrSelectVariant,\n GrSelectView,\n} from './grSelectStyles'\n\n/**\n * Пропсы публичного DS-примитива «Select».\n */\nexport interface GrSelectProps {\n modelValue: GrSelectModelValue\n options?: GrSelectOption[]\n disabled?: boolean\n ariaLabel?: string\n view?: GrSelectView\n size?: GrSelectSize\n /** Placeholder (показывается, когда значение не выбрано). */\n placeholder?: string\n /** Multiple selection. */\n multiple?: boolean\n /** Как отображать список опций: нативный `<select>` или кастомная панель. */\n optionsView?: GrSelectOptionsView\n /** Разрешает ввод/выбор значения, которого нет в `options`. */\n allowCustomValue?: boolean\n /** Placeholder для инпута кастомного значения (только в `optionsView=\"panel\"`). */\n customValuePlaceholder?: string\n /** Максимальная высота панели (только в `optionsView=\"panel\"`). */\n dropdownMaxHeight?: number\n /** Закрывать панель после выбора (только в `optionsView=\"panel\"`). */\n closeOnSelect?: boolean\n /** Разрешает очистку выбранного значения. */\n clearable?: boolean\n /** i18n-label для кнопки очистки (`aria-label`). */\n clearLabel?: string\n /**\n * Цвет/вариант ссылки для `view=\"link\"` (аналогично `GrLink`).\n * В `view=\"default\"` не используется.\n */\n variant?: GrSelectVariant\n /**\n * Подчёркивание для `view=\"link\"` (аналогично `GrLink`).\n * В `view=\"default\"` не используется.\n */\n underline?: GrSelectUnderline\n}\n\nconst props = withDefaults(\n defineProps<GrSelectProps>(),\n {\n options: undefined,\n disabled: false,\n ariaLabel: undefined,\n view: 'default',\n size: 'md',\n\n placeholder: undefined,\n multiple: false,\n\n optionsView: 'native',\n allowCustomValue: false,\n customValuePlaceholder: 'Add value…',\n dropdownMaxHeight: 280,\n closeOnSelect: true,\n clearable: false,\n clearLabel: 'Clear',\n variant: 'primary',\n underline: 'auto',\n },\n)\n\nconst baseClassName = computed(() => props.view === 'link' ? linkBaseClass : defaultBaseClass)\n\nconst rootClass = computed(() => props.view === 'link' ? 'relative inline-block align-baseline' : 'relative w-full')\n\nconst emit = defineEmits<{\n (e: 'update:modelValue', value: GrSelectModelValue): void\n}>()\n\nconst optionsResolved = computed(() => props.options ?? [])\n\nfunction modelToArray(value: GrSelectModelValue): string[] {\n if (Array.isArray(value)) return value\n if (!value) return []\n return [value]\n}\n\nconst modelSingle = computed(() => {\n return Array.isArray(props.modelValue) ? (props.modelValue[0] ?? '') : props.modelValue\n})\n\nconst modelMultiple = computed(() => modelToArray(props.modelValue))\n\nconst selectedValues = computed(() => {\n return props.multiple ? modelMultiple.value : (modelSingle.value ? [modelSingle.value] : [])\n})\n\nconst hasSelection = computed(() => selectedValues.value.length > 0)\n\nconst selectedOptions = computed<GrSelectOption[]>(() => {\n const byValue = new Map(optionsResolved.value.map(o => [o.value, o]))\n return selectedValues.value.map((v) => byValue.get(v) ?? { value: v, label: v })\n})\n\nconst hasModelInOptions = computed(() => {\n if (props.multiple) return false\n return optionsResolved.value.some((o) => o.value === modelSingle.value)\n})\n\nconst displayLabel = computed(() => {\n if (props.multiple) {\n if (!selectedValues.value.length) return ''\n return selectedValues.value\n .map((v) => optionsResolved.value.find((o) => o.value === v)?.label ?? v)\n .join(', ')\n }\n\n return optionsResolved.value.find((o) => o.value === modelSingle.value)?.label ?? modelSingle.value\n})\n\nconst displayText = computed(() => {\n if (hasSelection.value) return displayLabel.value\n return props.placeholder ?? ''\n})\n\nconst nativeCustomOptionVisible = computed(() => {\n if (!props.allowCustomValue) return false\n if (props.optionsView !== 'native') return false\n if (props.multiple) return false\n if (!props.options) return false\n if (modelSingle.value === '') return false\n return !hasModelInOptions.value\n})\n\nconst customValue = ref('')\nconst open = ref(false)\nconst rootEl = ref<HTMLElement | null>(null)\nconst panelEl = ref<HTMLElement | null>(null)\nconst customInputRef = ref<InstanceType<typeof GrInput> | null>(null)\nconst clickOutsideExclude = [() => panelEl.value]\nconst panelStyle = ref<Record<string, string>>({\n left: '0px',\n top: '0px',\n width: '0px',\n zIndex: '2147483647',\n})\n\nfunction syncPanelPosition(): void {\n if (typeof window === 'undefined') return\n\n const root = rootEl.value\n if (!root) return\n\n const rect = root.getBoundingClientRect()\n\n // Для `view='link'` ширина триггера = ширине выбранной опции (`inline-block w-auto`),\n // и без отдельной обработки панель тоже становилась шириной выбранной опции, из-за чего\n // её размер «прыгал» при выборе разных опций. Решение — отдать вычисление ширины браузеру:\n // `width: max-content` (по самому длинному ребёнку — `w-full`-кнопкам с `whitespace-nowrap`),\n // `min-width` равной ширине триггера (чтобы панель не была уже самого триггера).\n // Это чисто CSS-подход — без JS-замеров и ResizeObserver, максимально перформантный.\n if (props.view === 'link') {\n panelStyle.value = {\n left: `${rect.left}px`,\n top: `${rect.bottom + 8}px`,\n width: 'max-content',\n minWidth: `${rect.width}px`,\n zIndex: '2147483647',\n }\n return\n }\n\n panelStyle.value = {\n left: `${rect.left}px`,\n top: `${rect.bottom + 8}px`,\n width: `${rect.width}px`,\n zIndex: '2147483647',\n }\n}\n\nfunction bindPanelPositionListeners(): void {\n if (typeof window === 'undefined') return\n window.addEventListener('resize', syncPanelPosition)\n window.addEventListener('scroll', syncPanelPosition, true)\n}\n\nfunction unbindPanelPositionListeners(): void {\n if (typeof window === 'undefined') return\n window.removeEventListener('resize', syncPanelPosition)\n window.removeEventListener('scroll', syncPanelPosition, true)\n}\n\nfunction closeDropdown(): void {\n open.value = false\n}\n\nfunction toggleDropdown(): void {\n if (props.disabled) return\n open.value = !open.value\n}\n\nfunction closeOnEscape(e: KeyboardEvent): void {\n if (e.key === 'Escape') closeDropdown()\n}\n\nwatch(\n open,\n async (isOpen) => {\n if (typeof document === 'undefined') return\n\n document.removeEventListener('keydown', closeOnEscape)\n unbindPanelPositionListeners()\n\n if (isOpen) document.addEventListener('keydown', closeOnEscape)\n\n if (!isOpen) {\n customValue.value = ''\n return\n }\n\n bindPanelPositionListeners()\n await nextTick()\n syncPanelPosition()\n\n if (props.allowCustomValue) {\n customInputRef.value?.focus()\n }\n },\n { immediate: true },\n)\n\nonUnmounted(() => {\n if (typeof document === 'undefined') return\n document.removeEventListener('keydown', closeOnEscape)\n unbindPanelPositionListeners()\n})\n\nconst panelClasses = computed(() => {\n return grSelectPanelClasses\n})\n\nconst visibleOptions = computed(() => {\n const base = optionsResolved.value\n\n const withCustom = (() => {\n if (!props.allowCustomValue) return base\n if (props.multiple) return base\n if (modelSingle.value === '') return base\n if (hasModelInOptions.value) return base\n return [{ value: modelSingle.value, label: modelSingle.value }, ...base]\n })()\n\n if (!props.allowCustomValue) return withCustom\n\n const q = customValue.value.trim().toLowerCase()\n if (!q) return withCustom\n\n return withCustom.filter((o) => {\n return o.label.toLowerCase().includes(q) || o.value.toLowerCase().includes(q)\n })\n})\n\nconst canAddCustom = computed(() => {\n if (!props.allowCustomValue) return false\n if (props.optionsView !== 'panel') return false\n const v = customValue.value.trim()\n if (!v) return false\n\n if (props.multiple) {\n if (selectedValues.value.includes(v)) return false\n return !optionsResolved.value.some((o) => o.value === v)\n }\n\n if (v === modelSingle.value) return false\n return !optionsResolved.value.some((o) => o.value === v)\n})\n\nfunction emitValue(value: GrSelectModelValue): void {\n emit('update:modelValue', value)\n}\n\nfunction isSelected(value: string): boolean {\n return selectedValues.value.includes(value)\n}\n\nfunction selectValue(value: string): void {\n emitValue(value)\n if (props.closeOnSelect) {\n closeDropdown()\n }\n}\n\nfunction toggleValue(value: string): void {\n if (!props.multiple) {\n selectValue(value)\n return\n }\n\n const next = selectedValues.value.slice()\n const idx = next.indexOf(value)\n if (idx >= 0) {\n next.splice(idx, 1)\n }\n else {\n next.push(value)\n }\n\n emitValue(next)\n if (props.closeOnSelect) {\n closeDropdown()\n }\n}\n\nfunction addCustom(): void {\n const v = customValue.value.trim()\n if (!v) return\n toggleValue(v)\n}\n\nconst showNativeChevron = computed(() => {\n return props.optionsView === 'native' && props.view !== 'link' && !props.multiple\n})\n\nconst nativeClearOptionVisible = computed(() => {\n if (!props.clearable) return false\n if (props.optionsView !== 'native') return false\n if (props.multiple) return false\n if (!props.options) return true\n return !props.options.some(o => o.value === '')\n})\n\nconst panelClearVisible = computed(() => {\n if (!props.clearable) return false\n if (props.optionsView !== 'panel') return false\n if (props.view === 'link') return false\n return hasSelection.value\n})\n\nconst nativeClassName = computed(() => {\n return grSelectNativeClass({\n view: props.view,\n size: props.size,\n disabled: props.disabled,\n variant: props.variant,\n underline: props.underline,\n showNativeChevron: showNativeChevron.value,\n })\n})\n\n/**\n * `view=\"link\"` в native-режиме: ширина обёртки должна определяться выбранной опцией,\n * а не самой длинной (как делает браузер по умолчанию). Поэтому рендерим прозрачный\n * `<select>`-overlay поверх видимого `<span>` с меткой — overlay принимает клики/клавиатуру,\n * span задаёт ширину компонента в закрытом состоянии.\n */\nconst isLinkNative = computed(() => props.view === 'link' && props.optionsView === 'native')\n\nconst linkNativeLabelClassName = computed(() => grSelectLinkNativeLabelClass({\n size: props.size,\n variant: props.variant,\n underline: props.underline,\n disabled: props.disabled,\n}))\n\nconst linkNativeDisplayText = computed(() => {\n return displayText.value || props.placeholder || '\\u00A0'\n})\n\nconst triggerClassName = computed(() => {\n return grSelectTriggerClass({\n view: props.view,\n optionsView: props.optionsView,\n size: props.size,\n disabled: props.disabled,\n variant: props.variant,\n underline: props.underline,\n })\n})\n\nfunction onChange(e: Event): void {\n const el = e.target as HTMLSelectElement\n\n if (props.multiple) {\n const values = Array.from(el.selectedOptions, o => o.value)\n emit('update:modelValue', values)\n return\n }\n\n emit('update:modelValue', el.value)\n}\n\nfunction clearSelection(): void {\n if (props.disabled) return\n emitValue(props.multiple ? [] : '')\n}\n</script>\n\n<template>\n <div\n v-if=\"optionsView === 'native'\"\n data-ds-select\n :class=\"rootClass\"\n >\n <select\n data-ds-select-native\n :value=\"multiple ? modelMultiple : modelSingle\"\n :multiple=\"multiple\"\n :disabled=\"disabled\"\n :aria-label=\"ariaLabel\"\n :class=\"isLinkNative ? grSelectLinkNativeOverlayClass : [baseClassName, nativeClassName]\"\n @change=\"onChange\"\n >\n <option v-if=\"nativeClearOptionVisible\" value=\"\" />\n\n <slot>\n <option v-if=\"nativeCustomOptionVisible\" :value=\"modelSingle\">\n {{ modelSingle }}\n </option>\n <option v-for=\"opt in options\" :key=\"opt.value\" :value=\"opt.value\">\n {{ opt.label }}\n </option>\n </slot>\n </select>\n\n <span\n v-if=\"isLinkNative\"\n data-ds-select-link-label\n aria-hidden=\"true\"\n :class=\"linkNativeLabelClassName\"\n >{{ linkNativeDisplayText }}</span>\n\n <span\n v-if=\"showNativeChevron\"\n data-testid=\"ds-select-chevron\"\n class=\"absolute top-1/2 -translate-y-1/2 right-3 text-[var(--muted-fg)] pointer-events-none\"\n >\n <span class=\"i-lucide-chevron-down inline-block h-4 w-4\" aria-hidden=\"true\" />\n </span>\n </div>\n\n <div\n v-else\n ref=\"rootEl\"\n v-click-outside=\"{ handler: closeDropdown, enabled: open, exclude: clickOutsideExclude }\"\n data-ds-select\n :class=\"rootClass\"\n >\n <button\n data-testid=\"ds-select-trigger\"\n data-ds-select-trigger\n type=\"button\"\n :disabled=\"disabled\"\n :aria-label=\"ariaLabel\"\n role=\"combobox\"\n aria-readonly=\"true\"\n :aria-expanded=\"open ? 'true' : 'false'\"\n :class=\"[baseClassName, triggerClassName]\"\n @click=\"toggleDropdown\"\n >\n <span class=\"min-w-0 flex-1\">\n <slot\n name=\"value\"\n :selected-options=\"selectedOptions\"\n :selected-values=\"selectedValues\"\n :display-label=\"displayLabel\"\n :placeholder=\"placeholder\"\n :has-selection=\"hasSelection\"\n >\n <span\n class=\"block truncate\"\n :class=\"!hasSelection ? 'text-[var(--muted-fg)]' : ''\"\n >\n {{ displayText }}\n </span>\n </slot>\n </span>\n\n <span\n v-if=\"panelClearVisible\"\n class=\"shrink-0 h-4 w-4\"\n aria-hidden=\"true\"\n />\n\n <span\n v-else\n data-testid=\"ds-select-chevron\"\n class=\"shrink-0 text-[var(--muted-fg)] pointer-events-none\"\n >\n <span class=\"i-lucide-chevron-down inline-block h-4 w-4\" aria-hidden=\"true\" />\n </span>\n </button>\n\n <button\n v-if=\"panelClearVisible\"\n data-testid=\"ds-select-clear\"\n data-ds-select-clear\n type=\"button\"\n class=\"absolute top-1/2 -translate-y-1/2 right-3 h-6 w-6 inline-flex items-center justify-center rounded-md text-[var(--muted-fg)] hover:text-[var(--fg)] hover:bg-[color-mix(in_srgb,var(--muted)_25%,transparent)] disabled:opacity-50\"\n :disabled=\"disabled\"\n :aria-label=\"clearLabel\"\n @click.stop=\"clearSelection\"\n >\n <span class=\"i-lucide-x inline-block h-4 w-4\" aria-hidden=\"true\" />\n </button>\n\n <teleport to=\"body\">\n <transition\n enter-active-class=\"transition ease-out duration-150\"\n enter-from-class=\"transform opacity-0 scale-95\"\n enter-to-class=\"transform opacity-100 scale-100\"\n leave-active-class=\"transition ease-in duration-100\"\n leave-from-class=\"transform opacity-100 scale-100\"\n leave-to-class=\"transform opacity-0 scale-95\"\n >\n <div\n v-show=\"open\"\n ref=\"panelEl\"\n data-testid=\"ds-select-panel\"\n data-ds-select-panel\n class=\"fixed w-full\"\n :style=\"panelStyle\"\n >\n <div :class=\"panelClasses\">\n <div v-if=\"allowCustomValue\" class=\"p-2 border-b border-[var(--brd)]\">\n <GrInput\n ref=\"customInputRef\"\n v-model=\"customValue\"\n data-testid=\"ds-select-custom-input\"\n type=\"text\"\n :placeholder=\"customValuePlaceholder\"\n size=\"sm\"\n @keydown.enter.prevent=\"addCustom\"\n />\n </div>\n\n <div\n data-ds-select-listbox\n class=\"p-1 overflow-auto\"\n :style=\"{ maxHeight: `${dropdownMaxHeight}px` }\"\n role=\"listbox\"\n :aria-multiselectable=\"multiple ? 'true' : undefined\"\n >\n <button\n v-if=\"canAddCustom\"\n data-testid=\"ds-select-add-option\"\n data-ds-select-add-option\n type=\"button\"\n class=\"rounded-[10px] px-3 py-2 text-left text-[13px] hover:bg-[color-mix(in_srgb,var(--muted)_30%,transparent)]\" :class=\"[\n view === 'link' ? 'block min-w-full w-max whitespace-nowrap' : 'w-full',\n ]\"\n @click=\"addCustom\"\n >\n Add “{{ customValue.trim() }}”\n </button>\n\n <button\n v-for=\"opt in visibleOptions\"\n :key=\"opt.value\"\n data-ds-select-option\n type=\"button\"\n role=\"option\"\n :aria-selected=\"isSelected(opt.value) ? 'true' : 'false'\"\n class=\"rounded-[10px] px-3 py-2 text-left text-[13px] hover:bg-[color-mix(in_srgb,var(--muted)_30%,transparent)]\" :class=\"[\n view === 'link' ? 'block min-w-full w-max whitespace-nowrap' : 'w-full',\n ]\"\n @click=\"toggleValue(opt.value)\"\n >\n <slot name=\"option\" :option=\"opt\" :selected=\"isSelected(opt.value)\">\n <span class=\"flex items-center gap-2 min-w-0\">\n <span\n class=\"inline-block h-4 w-4 shrink-0\"\n :class=\"isSelected(opt.value) ? 'i-lucide-check text-[var(--primary)]' : ''\"\n aria-hidden=\"true\"\n />\n <span class=\"truncate\">{{ opt.label }}</span>\n </span>\n </slot>\n </button>\n </div>\n </div>\n </div>\n </transition>\n </teleport>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, nextTick, onUnmounted, ref, watch } from 'vue'\n\nimport GrInput from '../GrInput/GrInput.vue'\nimport { vClickOutside } from '../../directives'\n\nimport {\n defaultBaseClass,\n grSelectLinkNativeLabelClass,\n grSelectLinkNativeOverlayClass,\n grSelectNativeClass,\n grSelectPanelClasses,\n grSelectTriggerClass,\n linkBaseClass,\n type GrSelectModelValue,\n type GrSelectOption,\n type GrSelectOptionsView,\n type GrSelectSize,\n type GrSelectUnderline,\n type GrSelectVariant,\n type GrSelectView,\n} from './grSelectStyles'\n\nexport type {\n GrSelectModelValue,\n GrSelectOption,\n GrSelectOptionsView,\n GrSelectSize,\n GrSelectUnderline,\n GrSelectVariant,\n GrSelectView,\n} from './grSelectStyles'\n\n/**\n * Пропсы публичного DS-примитива «Select».\n */\nexport interface GrSelectProps {\n modelValue: GrSelectModelValue\n options?: GrSelectOption[]\n disabled?: boolean\n ariaLabel?: string\n view?: GrSelectView\n size?: GrSelectSize\n /** Placeholder (показывается, когда значение не выбрано). */\n placeholder?: string\n /** Multiple selection. */\n multiple?: boolean\n /** Как отображать список опций: нативный `<select>` или кастомная панель. */\n optionsView?: GrSelectOptionsView\n /** Разрешает ввод/выбор значения, которого нет в `options`. */\n allowCustomValue?: boolean\n /** Placeholder для инпута кастомного значения (только в `optionsView=\"panel\"`). */\n customValuePlaceholder?: string\n /** Максимальная высота панели (только в `optionsView=\"panel\"`). */\n dropdownMaxHeight?: number\n /** Закрывать панель после выбора (только в `optionsView=\"panel\"`). */\n closeOnSelect?: boolean\n /** Разрешает очистку выбранного значения. */\n clearable?: boolean\n /** i18n-label для кнопки очистки (`aria-label`). */\n clearLabel?: string\n /**\n * Цвет/вариант ссылки для `view=\"link\"` (аналогично `GrLink`).\n * В `view=\"default\"` не используется.\n */\n variant?: GrSelectVariant\n /**\n * Подчёркивание для `view=\"link\"` (аналогично `GrLink`).\n * В `view=\"default\"` не используется.\n */\n underline?: GrSelectUnderline\n}\n\nconst props = withDefaults(\n defineProps<GrSelectProps>(),\n {\n options: undefined,\n disabled: false,\n ariaLabel: undefined,\n view: 'default',\n size: 'md',\n\n placeholder: undefined,\n multiple: false,\n\n optionsView: 'native',\n allowCustomValue: false,\n customValuePlaceholder: 'Add value…',\n dropdownMaxHeight: 280,\n closeOnSelect: true,\n clearable: false,\n clearLabel: 'Clear',\n variant: 'primary',\n underline: 'auto',\n },\n)\n\nconst baseClassName = computed(() => props.view === 'link' ? linkBaseClass : defaultBaseClass)\n\nconst rootClass = computed(() => props.view === 'link' ? 'relative inline-block align-baseline' : 'relative w-full')\n\nconst emit = defineEmits<{\n (e: 'update:modelValue', value: GrSelectModelValue): void\n}>()\n\nconst optionsResolved = computed(() => props.options ?? [])\n\nfunction modelToArray(value: GrSelectModelValue): string[] {\n if (Array.isArray(value)) return value\n if (!value) return []\n return [value]\n}\n\nconst modelSingle = computed(() => {\n return Array.isArray(props.modelValue) ? (props.modelValue[0] ?? '') : props.modelValue\n})\n\nconst modelMultiple = computed(() => modelToArray(props.modelValue))\n\nconst selectedValues = computed(() => {\n return props.multiple ? modelMultiple.value : (modelSingle.value ? [modelSingle.value] : [])\n})\n\nconst hasSelection = computed(() => selectedValues.value.length > 0)\n\nconst selectedOptions = computed<GrSelectOption[]>(() => {\n const byValue = new Map(optionsResolved.value.map(o => [o.value, o]))\n return selectedValues.value.map((v) => byValue.get(v) ?? { value: v, label: v })\n})\n\nconst hasModelInOptions = computed(() => {\n if (props.multiple) return false\n return optionsResolved.value.some((o) => o.value === modelSingle.value)\n})\n\nconst displayLabel = computed(() => {\n if (props.multiple) {\n if (!selectedValues.value.length) return ''\n return selectedValues.value\n .map((v) => optionsResolved.value.find((o) => o.value === v)?.label ?? v)\n .join(', ')\n }\n\n return optionsResolved.value.find((o) => o.value === modelSingle.value)?.label ?? modelSingle.value\n})\n\nconst displayText = computed(() => {\n if (hasSelection.value) return displayLabel.value\n return props.placeholder ?? ''\n})\n\nconst nativeCustomOptionVisible = computed(() => {\n if (!props.allowCustomValue) return false\n if (props.optionsView !== 'native') return false\n if (props.multiple) return false\n if (!props.options) return false\n if (modelSingle.value === '') return false\n return !hasModelInOptions.value\n})\n\nconst customValue = ref('')\nconst open = ref(false)\nconst rootEl = ref<HTMLElement | null>(null)\nconst panelEl = ref<HTMLElement | null>(null)\nconst customInputRef = ref<InstanceType<typeof GrInput> | null>(null)\nconst clickOutsideExclude = [() => panelEl.value]\nconst panelStyle = ref<Record<string, string>>({\n left: '0px',\n top: '0px',\n width: '0px',\n zIndex: '2147483647',\n})\n\nfunction syncPanelPosition(): void {\n if (typeof window === 'undefined') return\n\n const root = rootEl.value\n if (!root) return\n\n const rect = root.getBoundingClientRect()\n\n // Для `view='link'` ширина триггера = ширине выбранной опции (`inline-block w-auto`),\n // и без отдельной обработки панель тоже становилась шириной выбранной опции, из-за чего\n // её размер «прыгал» при выборе разных опций. Решение — отдать вычисление ширины браузеру:\n // `width: max-content` (по самому длинному ребёнку — `w-full`-кнопкам с `whitespace-nowrap`),\n // `min-width` равной ширине триггера (чтобы панель не была уже самого триггера).\n // Это чисто CSS-подход — без JS-замеров и ResizeObserver, максимально перформантный.\n if (props.view === 'link') {\n panelStyle.value = {\n left: `${rect.left}px`,\n top: `${rect.bottom + 8}px`,\n width: 'max-content',\n minWidth: `${rect.width}px`,\n zIndex: '2147483647',\n }\n return\n }\n\n panelStyle.value = {\n left: `${rect.left}px`,\n top: `${rect.bottom + 8}px`,\n width: `${rect.width}px`,\n zIndex: '2147483647',\n }\n}\n\nfunction bindPanelPositionListeners(): void {\n if (typeof window === 'undefined') return\n window.addEventListener('resize', syncPanelPosition)\n window.addEventListener('scroll', syncPanelPosition, true)\n}\n\nfunction unbindPanelPositionListeners(): void {\n if (typeof window === 'undefined') return\n window.removeEventListener('resize', syncPanelPosition)\n window.removeEventListener('scroll', syncPanelPosition, true)\n}\n\nfunction closeDropdown(): void {\n open.value = false\n}\n\nfunction toggleDropdown(): void {\n if (props.disabled) return\n open.value = !open.value\n}\n\nfunction closeOnEscape(e: KeyboardEvent): void {\n if (e.key === 'Escape') closeDropdown()\n}\n\nwatch(\n open,\n async (isOpen) => {\n if (typeof document === 'undefined') return\n\n document.removeEventListener('keydown', closeOnEscape)\n unbindPanelPositionListeners()\n\n if (isOpen) document.addEventListener('keydown', closeOnEscape)\n\n if (!isOpen) {\n customValue.value = ''\n return\n }\n\n bindPanelPositionListeners()\n await nextTick()\n syncPanelPosition()\n\n if (props.allowCustomValue) {\n customInputRef.value?.focus()\n }\n },\n { immediate: true },\n)\n\nonUnmounted(() => {\n if (typeof document === 'undefined') return\n document.removeEventListener('keydown', closeOnEscape)\n unbindPanelPositionListeners()\n})\n\nconst panelClasses = computed(() => {\n return grSelectPanelClasses\n})\n\nconst visibleOptions = computed(() => {\n const base = optionsResolved.value\n\n const withCustom = (() => {\n if (!props.allowCustomValue) return base\n if (props.multiple) return base\n if (modelSingle.value === '') return base\n if (hasModelInOptions.value) return base\n return [{ value: modelSingle.value, label: modelSingle.value }, ...base]\n })()\n\n if (!props.allowCustomValue) return withCustom\n\n const q = customValue.value.trim().toLowerCase()\n if (!q) return withCustom\n\n return withCustom.filter((o) => {\n return o.label.toLowerCase().includes(q) || o.value.toLowerCase().includes(q)\n })\n})\n\nconst canAddCustom = computed(() => {\n if (!props.allowCustomValue) return false\n if (props.optionsView !== 'panel') return false\n const v = customValue.value.trim()\n if (!v) return false\n\n if (props.multiple) {\n if (selectedValues.value.includes(v)) return false\n return !optionsResolved.value.some((o) => o.value === v)\n }\n\n if (v === modelSingle.value) return false\n return !optionsResolved.value.some((o) => o.value === v)\n})\n\nfunction emitValue(value: GrSelectModelValue): void {\n emit('update:modelValue', value)\n}\n\nfunction isSelected(value: string): boolean {\n return selectedValues.value.includes(value)\n}\n\nfunction selectValue(value: string): void {\n emitValue(value)\n if (props.closeOnSelect) {\n closeDropdown()\n }\n}\n\nfunction toggleValue(value: string): void {\n if (!props.multiple) {\n selectValue(value)\n return\n }\n\n const next = selectedValues.value.slice()\n const idx = next.indexOf(value)\n if (idx >= 0) {\n next.splice(idx, 1)\n }\n else {\n next.push(value)\n }\n\n emitValue(next)\n if (props.closeOnSelect) {\n closeDropdown()\n }\n}\n\nfunction addCustom(): void {\n const v = customValue.value.trim()\n if (!v) return\n toggleValue(v)\n}\n\nconst showNativeChevron = computed(() => {\n return props.optionsView === 'native' && props.view !== 'link' && !props.multiple\n})\n\nconst nativeClearOptionVisible = computed(() => {\n if (!props.clearable) return false\n if (props.optionsView !== 'native') return false\n if (props.multiple) return false\n if (!props.options) return true\n return !props.options.some(o => o.value === '')\n})\n\nconst panelClearVisible = computed(() => {\n if (!props.clearable) return false\n if (props.optionsView !== 'panel') return false\n if (props.view === 'link') return false\n return hasSelection.value\n})\n\nconst nativeClassName = computed(() => {\n return grSelectNativeClass({\n view: props.view,\n size: props.size,\n disabled: props.disabled,\n variant: props.variant,\n underline: props.underline,\n showNativeChevron: showNativeChevron.value,\n })\n})\n\n/**\n * `view=\"link\"` в native-режиме: ширина обёртки должна определяться выбранной опцией,\n * а не самой длинной (как делает браузер по умолчанию). Поэтому рендерим прозрачный\n * `<select>`-overlay поверх видимого `<span>` с меткой — overlay принимает клики/клавиатуру,\n * span задаёт ширину компонента в закрытом состоянии.\n */\nconst isLinkNative = computed(() => props.view === 'link' && props.optionsView === 'native')\n\nconst linkNativeLabelClassName = computed(() => grSelectLinkNativeLabelClass({\n size: props.size,\n variant: props.variant,\n underline: props.underline,\n disabled: props.disabled,\n}))\n\nconst linkNativeDisplayText = computed(() => {\n return displayText.value || props.placeholder || '\\u00A0'\n})\n\nconst triggerClassName = computed(() => {\n return grSelectTriggerClass({\n view: props.view,\n optionsView: props.optionsView,\n size: props.size,\n disabled: props.disabled,\n variant: props.variant,\n underline: props.underline,\n })\n})\n\nfunction onChange(e: Event): void {\n const el = e.target as HTMLSelectElement\n\n if (props.multiple) {\n const values = Array.from(el.selectedOptions, o => o.value)\n emit('update:modelValue', values)\n return\n }\n\n emit('update:modelValue', el.value)\n}\n\nfunction clearSelection(): void {\n if (props.disabled) return\n emitValue(props.multiple ? [] : '')\n}\n</script>\n\n<template>\n <div\n v-if=\"optionsView === 'native'\"\n data-ds-select\n :class=\"rootClass\"\n >\n <select\n data-ds-select-native\n :value=\"multiple ? modelMultiple : modelSingle\"\n :multiple=\"multiple\"\n :disabled=\"disabled\"\n :aria-label=\"ariaLabel\"\n :class=\"isLinkNative ? grSelectLinkNativeOverlayClass : [baseClassName, nativeClassName]\"\n @change=\"onChange\"\n >\n <option v-if=\"nativeClearOptionVisible\" value=\"\" />\n\n <slot>\n <option v-if=\"nativeCustomOptionVisible\" :value=\"modelSingle\">\n {{ modelSingle }}\n </option>\n <option v-for=\"opt in options\" :key=\"opt.value\" :value=\"opt.value\">\n {{ opt.label }}\n </option>\n </slot>\n </select>\n\n <span\n v-if=\"isLinkNative\"\n data-ds-select-link-label\n aria-hidden=\"true\"\n :class=\"linkNativeLabelClassName\"\n >{{ linkNativeDisplayText }}</span>\n\n <span\n v-if=\"showNativeChevron\"\n data-testid=\"ds-select-chevron\"\n class=\"absolute top-1/2 -translate-y-1/2 right-3 text-[var(--muted-fg)] pointer-events-none\"\n >\n <span class=\"i-lucide-chevron-down inline-block h-4 w-4\" aria-hidden=\"true\" />\n </span>\n </div>\n\n <div\n v-else\n ref=\"rootEl\"\n v-click-outside=\"{ handler: closeDropdown, enabled: open, exclude: clickOutsideExclude }\"\n data-ds-select\n :class=\"rootClass\"\n >\n <button\n data-testid=\"ds-select-trigger\"\n data-ds-select-trigger\n type=\"button\"\n :disabled=\"disabled\"\n :aria-label=\"ariaLabel\"\n role=\"combobox\"\n aria-readonly=\"true\"\n :aria-expanded=\"open ? 'true' : 'false'\"\n :class=\"[baseClassName, triggerClassName]\"\n @click=\"toggleDropdown\"\n >\n <span class=\"min-w-0 flex-1\">\n <slot\n name=\"value\"\n :selected-options=\"selectedOptions\"\n :selected-values=\"selectedValues\"\n :display-label=\"displayLabel\"\n :placeholder=\"placeholder\"\n :has-selection=\"hasSelection\"\n >\n <span\n class=\"block truncate\"\n :class=\"!hasSelection ? 'text-[var(--muted-fg)]' : ''\"\n >\n {{ displayText }}\n </span>\n </slot>\n </span>\n\n <span\n v-if=\"panelClearVisible\"\n class=\"shrink-0 h-4 w-4\"\n aria-hidden=\"true\"\n />\n\n <span\n v-else\n data-testid=\"ds-select-chevron\"\n class=\"shrink-0 text-[var(--muted-fg)] pointer-events-none\"\n >\n <span class=\"i-lucide-chevron-down inline-block h-4 w-4\" aria-hidden=\"true\" />\n </span>\n </button>\n\n <button\n v-if=\"panelClearVisible\"\n data-testid=\"ds-select-clear\"\n data-ds-select-clear\n type=\"button\"\n class=\"absolute top-1/2 -translate-y-1/2 right-3 h-6 w-6 inline-flex items-center justify-center rounded-md text-[var(--muted-fg)] hover:text-[var(--fg)] hover:bg-[color-mix(in_srgb,var(--muted)_25%,transparent)] disabled:opacity-50\"\n :disabled=\"disabled\"\n :aria-label=\"clearLabel\"\n @click.stop=\"clearSelection\"\n >\n <span class=\"i-lucide-x inline-block h-4 w-4\" aria-hidden=\"true\" />\n </button>\n\n <teleport to=\"body\">\n <transition\n enter-active-class=\"transition ease-out duration-150\"\n enter-from-class=\"transform opacity-0 scale-95\"\n enter-to-class=\"transform opacity-100 scale-100\"\n leave-active-class=\"transition ease-in duration-100\"\n leave-from-class=\"transform opacity-100 scale-100\"\n leave-to-class=\"transform opacity-0 scale-95\"\n >\n <div\n v-show=\"open\"\n ref=\"panelEl\"\n data-testid=\"ds-select-panel\"\n data-ds-select-panel\n class=\"fixed w-full\"\n :style=\"panelStyle\"\n >\n <div :class=\"panelClasses\">\n <div v-if=\"allowCustomValue\" class=\"p-2 border-b border-[var(--brd)]\">\n <GrInput\n ref=\"customInputRef\"\n v-model=\"customValue\"\n data-testid=\"ds-select-custom-input\"\n type=\"text\"\n :placeholder=\"customValuePlaceholder\"\n size=\"sm\"\n @keydown.enter.prevent=\"addCustom\"\n />\n </div>\n\n <div\n data-ds-select-listbox\n class=\"p-1 overflow-auto\"\n :style=\"{ maxHeight: `${dropdownMaxHeight}px` }\"\n role=\"listbox\"\n :aria-multiselectable=\"multiple ? 'true' : undefined\"\n >\n <button\n v-if=\"canAddCustom\"\n data-testid=\"ds-select-add-option\"\n data-ds-select-add-option\n type=\"button\"\n class=\"rounded-[10px] px-3 py-2 text-left text-[13px] hover:bg-[color-mix(in_srgb,var(--muted)_30%,transparent)]\" :class=\"[\n view === 'link' ? 'block min-w-full w-max whitespace-nowrap' : 'w-full',\n ]\"\n @click=\"addCustom\"\n >\n Add “{{ customValue.trim() }}”\n </button>\n\n <button\n v-for=\"opt in visibleOptions\"\n :key=\"opt.value\"\n data-ds-select-option\n type=\"button\"\n role=\"option\"\n :aria-selected=\"isSelected(opt.value) ? 'true' : 'false'\"\n class=\"rounded-[10px] px-3 py-2 text-left text-[13px] hover:bg-[color-mix(in_srgb,var(--muted)_30%,transparent)]\" :class=\"[\n view === 'link' ? 'block min-w-full w-max whitespace-nowrap' : 'w-full',\n ]\"\n @click=\"toggleValue(opt.value)\"\n >\n <slot name=\"option\" :option=\"opt\" :selected=\"isSelected(opt.value)\">\n <span class=\"flex items-center gap-2 min-w-0\">\n <span\n class=\"inline-block h-4 w-4 shrink-0\"\n :class=\"isSelected(opt.value) ? 'i-lucide-check text-[var(--primary)]' : ''\"\n aria-hidden=\"true\"\n />\n <span class=\"truncate\">{{ opt.label }}</span>\n </span>\n </slot>\n </button>\n </div>\n </div>\n </div>\n </transition>\n </teleport>\n </div>\n</template>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"GrSelect-C3NAfTqL.js","names":[],"sources":["../../../../src/components/GrSelect/GrSelect.vue","../../../../src/components/GrSelect/GrSelect.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, nextTick, onUnmounted, ref, watch } from 'vue'\n\nimport GrInput from '../GrInput/GrInput.vue'\nimport { vClickOutside } from '../../directives'\n\nimport {\n defaultBaseClass,\n grSelectLinkNativeLabelClass,\n grSelectLinkNativeOverlayClass,\n grSelectNativeClass,\n grSelectPanelClasses,\n grSelectTriggerClass,\n linkBaseClass,\n type GrSelectModelValue,\n type GrSelectOption,\n type GrSelectOptionsView,\n type GrSelectSize,\n type GrSelectUnderline,\n type GrSelectVariant,\n type GrSelectView,\n} from './grSelectStyles'\n\nexport type {\n GrSelectModelValue,\n GrSelectOption,\n GrSelectOptionsView,\n GrSelectSize,\n GrSelectUnderline,\n GrSelectVariant,\n GrSelectView,\n} from './grSelectStyles'\n\n/**\n * Пропсы публичного DS-примитива «Select».\n */\nexport interface GrSelectProps {\n modelValue: GrSelectModelValue\n options?: GrSelectOption[]\n disabled?: boolean\n ariaLabel?: string\n view?: GrSelectView\n size?: GrSelectSize\n /** Placeholder (показывается, когда значение не выбрано). */\n placeholder?: string\n /** Multiple selection. */\n multiple?: boolean\n /** Как отображать список опций: нативный `<select>` или кастомная панель. */\n optionsView?: GrSelectOptionsView\n /** Разрешает ввод/выбор значения, которого нет в `options`. */\n allowCustomValue?: boolean\n /** Placeholder для инпута кастомного значения (только в `optionsView=\"panel\"`). */\n customValuePlaceholder?: string\n /** Максимальная высота панели (только в `optionsView=\"panel\"`). */\n dropdownMaxHeight?: number\n /** Закрывать панель после выбора (только в `optionsView=\"panel\"`). */\n closeOnSelect?: boolean\n /** Разрешает очистку выбранного значения. */\n clearable?: boolean\n /** i18n-label для кнопки очистки (`aria-label`). */\n clearLabel?: string\n /**\n * Цвет/вариант ссылки для `view=\"link\"` (аналогично `GrLink`).\n * В `view=\"default\"` не используется.\n */\n variant?: GrSelectVariant\n /**\n * Подчёркивание для `view=\"link\"` (аналогично `GrLink`).\n * В `view=\"default\"` не используется.\n */\n underline?: GrSelectUnderline\n}\n\nconst props = withDefaults(\n defineProps<GrSelectProps>(),\n {\n options: undefined,\n disabled: false,\n ariaLabel: undefined,\n view: 'default',\n size: 'md',\n\n placeholder: undefined,\n multiple: false,\n\n optionsView: 'native',\n allowCustomValue: false,\n customValuePlaceholder: 'Add value…',\n dropdownMaxHeight: 280,\n closeOnSelect: true,\n clearable: false,\n clearLabel: 'Clear',\n variant: 'primary',\n underline: 'auto',\n },\n)\n\nconst baseClassName = computed(() => props.view === 'link' ? linkBaseClass : defaultBaseClass)\n\nconst rootClass = computed(() => props.view === 'link' ? 'relative inline-block align-baseline' : 'relative w-full')\n\nconst emit = defineEmits<{\n (e: 'update:modelValue', value: GrSelectModelValue): void\n}>()\n\nconst optionsResolved = computed(() => props.options ?? [])\n\nfunction modelToArray(value: GrSelectModelValue): string[] {\n if (Array.isArray(value)) return value\n if (!value) return []\n return [value]\n}\n\nconst modelSingle = computed(() => {\n return Array.isArray(props.modelValue) ? (props.modelValue[0] ?? '') : props.modelValue\n})\n\nconst modelMultiple = computed(() => modelToArray(props.modelValue))\n\nconst selectedValues = computed(() => {\n return props.multiple ? modelMultiple.value : (modelSingle.value ? [modelSingle.value] : [])\n})\n\nconst hasSelection = computed(() => selectedValues.value.length > 0)\n\nconst selectedOptions = computed<GrSelectOption[]>(() => {\n const byValue = new Map(optionsResolved.value.map(o => [o.value, o]))\n return selectedValues.value.map((v) => byValue.get(v) ?? { value: v, label: v })\n})\n\nconst hasModelInOptions = computed(() => {\n if (props.multiple) return false\n return optionsResolved.value.some((o) => o.value === modelSingle.value)\n})\n\nconst displayLabel = computed(() => {\n if (props.multiple) {\n if (!selectedValues.value.length) return ''\n return selectedValues.value\n .map((v) => optionsResolved.value.find((o) => o.value === v)?.label ?? v)\n .join(', ')\n }\n\n return optionsResolved.value.find((o) => o.value === modelSingle.value)?.label ?? modelSingle.value\n})\n\nconst displayText = computed(() => {\n if (hasSelection.value) return displayLabel.value\n return props.placeholder ?? ''\n})\n\nconst nativeCustomOptionVisible = computed(() => {\n if (!props.allowCustomValue) return false\n if (props.optionsView !== 'native') return false\n if (props.multiple) return false\n if (!props.options) return false\n if (modelSingle.value === '') return false\n return !hasModelInOptions.value\n})\n\nconst customValue = ref('')\nconst open = ref(false)\nconst rootEl = ref<HTMLElement | null>(null)\nconst panelEl = ref<HTMLElement | null>(null)\nconst customInputRef = ref<InstanceType<typeof GrInput> | null>(null)\nconst clickOutsideExclude = [() => panelEl.value]\nconst panelStyle = ref<Record<string, string>>({\n left: '0px',\n top: '0px',\n width: '0px',\n zIndex: '2147483647',\n})\n\nfunction syncPanelPosition(): void {\n if (typeof window === 'undefined') return\n\n const root = rootEl.value\n if (!root) return\n\n const rect = root.getBoundingClientRect()\n\n // Для `view='link'` ширина триггера = ширине выбранной опции (`inline-block w-auto`),\n // и без отдельной обработки панель тоже становилась шириной выбранной опции, из-за чего\n // её размер «прыгал» при выборе разных опций. Решение — отдать вычисление ширины браузеру:\n // `width: max-content` (по самому длинному ребёнку — `w-full`-кнопкам с `whitespace-nowrap`),\n // `min-width` равной ширине триггера (чтобы панель не была уже самого триггера).\n // Это чисто CSS-подход — без JS-замеров и ResizeObserver, максимально перформантный.\n if (props.view === 'link') {\n panelStyle.value = {\n left: `${rect.left}px`,\n top: `${rect.bottom + 8}px`,\n width: 'max-content',\n minWidth: `${rect.width}px`,\n zIndex: '2147483647',\n }\n return\n }\n\n panelStyle.value = {\n left: `${rect.left}px`,\n top: `${rect.bottom + 8}px`,\n width: `${rect.width}px`,\n zIndex: '2147483647',\n }\n}\n\nfunction bindPanelPositionListeners(): void {\n if (typeof window === 'undefined') return\n window.addEventListener('resize', syncPanelPosition)\n window.addEventListener('scroll', syncPanelPosition, true)\n}\n\nfunction unbindPanelPositionListeners(): void {\n if (typeof window === 'undefined') return\n window.removeEventListener('resize', syncPanelPosition)\n window.removeEventListener('scroll', syncPanelPosition, true)\n}\n\nfunction closeDropdown(): void {\n open.value = false\n}\n\nfunction toggleDropdown(): void {\n if (props.disabled) return\n open.value = !open.value\n}\n\nfunction closeOnEscape(e: KeyboardEvent): void {\n if (e.key === 'Escape') closeDropdown()\n}\n\nwatch(\n open,\n async (isOpen) => {\n if (typeof document === 'undefined') return\n\n document.removeEventListener('keydown', closeOnEscape)\n unbindPanelPositionListeners()\n\n if (isOpen) document.addEventListener('keydown', closeOnEscape)\n\n if (!isOpen) {\n customValue.value = ''\n return\n }\n\n bindPanelPositionListeners()\n await nextTick()\n syncPanelPosition()\n\n if (props.allowCustomValue) {\n customInputRef.value?.focus()\n }\n },\n { immediate: true },\n)\n\nonUnmounted(() => {\n if (typeof document === 'undefined') return\n document.removeEventListener('keydown', closeOnEscape)\n unbindPanelPositionListeners()\n})\n\nconst panelClasses = computed(() => {\n return grSelectPanelClasses\n})\n\nconst visibleOptions = computed(() => {\n const base = optionsResolved.value\n\n const withCustom = (() => {\n if (!props.allowCustomValue) return base\n if (props.multiple) return base\n if (modelSingle.value === '') return base\n if (hasModelInOptions.value) return base\n return [{ value: modelSingle.value, label: modelSingle.value }, ...base]\n })()\n\n if (!props.allowCustomValue) return withCustom\n\n const q = customValue.value.trim().toLowerCase()\n if (!q) return withCustom\n\n return withCustom.filter((o) => {\n return o.label.toLowerCase().includes(q) || o.value.toLowerCase().includes(q)\n })\n})\n\nconst canAddCustom = computed(() => {\n if (!props.allowCustomValue) return false\n if (props.optionsView !== 'panel') return false\n const v = customValue.value.trim()\n if (!v) return false\n\n if (props.multiple) {\n if (selectedValues.value.includes(v)) return false\n return !optionsResolved.value.some((o) => o.value === v)\n }\n\n if (v === modelSingle.value) return false\n return !optionsResolved.value.some((o) => o.value === v)\n})\n\nfunction emitValue(value: GrSelectModelValue): void {\n emit('update:modelValue', value)\n}\n\nfunction isSelected(value: string): boolean {\n return selectedValues.value.includes(value)\n}\n\nfunction selectValue(value: string): void {\n emitValue(value)\n if (props.closeOnSelect) {\n closeDropdown()\n }\n}\n\nfunction toggleValue(value: string): void {\n if (!props.multiple) {\n selectValue(value)\n return\n }\n\n const next = selectedValues.value.slice()\n const idx = next.indexOf(value)\n if (idx >= 0) {\n next.splice(idx, 1)\n }\n else {\n next.push(value)\n }\n\n emitValue(next)\n if (props.closeOnSelect) {\n closeDropdown()\n }\n}\n\nfunction addCustom(): void {\n const v = customValue.value.trim()\n if (!v) return\n toggleValue(v)\n}\n\nconst showNativeChevron = computed(() => {\n return props.optionsView === 'native' && props.view !== 'link' && !props.multiple\n})\n\nconst nativeClearOptionVisible = computed(() => {\n if (!props.clearable) return false\n if (props.optionsView !== 'native') return false\n if (props.multiple) return false\n if (!props.options) return true\n return !props.options.some(o => o.value === '')\n})\n\nconst panelClearVisible = computed(() => {\n if (!props.clearable) return false\n if (props.optionsView !== 'panel') return false\n if (props.view === 'link') return false\n return hasSelection.value\n})\n\nconst nativeClassName = computed(() => {\n return grSelectNativeClass({\n view: props.view,\n size: props.size,\n disabled: props.disabled,\n variant: props.variant,\n underline: props.underline,\n showNativeChevron: showNativeChevron.value,\n })\n})\n\n/**\n * `view=\"link\"` в native-режиме: ширина обёртки должна определяться выбранной опцией,\n * а не самой длинной (как делает браузер по умолчанию). Поэтому рендерим прозрачный\n * `<select>`-overlay поверх видимого `<span>` с меткой — overlay принимает клики/клавиатуру,\n * span задаёт ширину компонента в закрытом состоянии.\n */\nconst isLinkNative = computed(() => props.view === 'link' && props.optionsView === 'native')\n\nconst linkNativeLabelClassName = computed(() => grSelectLinkNativeLabelClass({\n size: props.size,\n variant: props.variant,\n underline: props.underline,\n disabled: props.disabled,\n}))\n\nconst linkNativeDisplayText = computed(() => {\n return displayText.value || props.placeholder || '\\u00A0'\n})\n\nconst triggerClassName = computed(() => {\n return grSelectTriggerClass({\n view: props.view,\n optionsView: props.optionsView,\n size: props.size,\n disabled: props.disabled,\n variant: props.variant,\n underline: props.underline,\n })\n})\n\nfunction onChange(e: Event): void {\n const el = e.target as HTMLSelectElement\n\n if (props.multiple) {\n const values = Array.from(el.selectedOptions, o => o.value)\n emit('update:modelValue', values)\n return\n }\n\n emit('update:modelValue', el.value)\n}\n\nfunction clearSelection(): void {\n if (props.disabled) return\n emitValue(props.multiple ? [] : '')\n}\n</script>\n\n<template>\n <div\n v-if=\"optionsView === 'native'\"\n data-ds-select\n :class=\"rootClass\"\n >\n <select\n data-ds-select-native\n :value=\"multiple ? modelMultiple : modelSingle\"\n :multiple=\"multiple\"\n :disabled=\"disabled\"\n :aria-label=\"ariaLabel\"\n :class=\"isLinkNative ? grSelectLinkNativeOverlayClass : [baseClassName, nativeClassName]\"\n @change=\"onChange\"\n >\n <option v-if=\"nativeClearOptionVisible\" value=\"\" />\n\n <slot>\n <option v-if=\"nativeCustomOptionVisible\" :value=\"modelSingle\">\n {{ modelSingle }}\n </option>\n <option v-for=\"opt in options\" :key=\"opt.value\" :value=\"opt.value\">\n {{ opt.label }}\n </option>\n </slot>\n </select>\n\n <span\n v-if=\"isLinkNative\"\n data-ds-select-link-label\n aria-hidden=\"true\"\n :class=\"linkNativeLabelClassName\"\n >{{ linkNativeDisplayText }}</span>\n\n <span\n v-if=\"showNativeChevron\"\n data-testid=\"ds-select-chevron\"\n class=\"absolute top-1/2 -translate-y-1/2 right-3 text-[var(--muted-fg)] pointer-events-none\"\n >\n <span class=\"i-lucide-chevron-down inline-block h-4 w-4\" aria-hidden=\"true\" />\n </span>\n </div>\n\n <div\n v-else\n ref=\"rootEl\"\n v-click-outside=\"{ handler: closeDropdown, enabled: open, exclude: clickOutsideExclude }\"\n data-ds-select\n :class=\"rootClass\"\n >\n <button\n data-testid=\"ds-select-trigger\"\n data-ds-select-trigger\n type=\"button\"\n :disabled=\"disabled\"\n :aria-label=\"ariaLabel\"\n role=\"combobox\"\n aria-readonly=\"true\"\n :aria-expanded=\"open ? 'true' : 'false'\"\n :class=\"[baseClassName, triggerClassName]\"\n @click=\"toggleDropdown\"\n >\n <span class=\"min-w-0 flex-1\">\n <slot\n name=\"value\"\n :selected-options=\"selectedOptions\"\n :selected-values=\"selectedValues\"\n :display-label=\"displayLabel\"\n :placeholder=\"placeholder\"\n :has-selection=\"hasSelection\"\n >\n <span\n class=\"block truncate\"\n :class=\"!hasSelection ? 'text-[var(--muted-fg)]' : ''\"\n >\n {{ displayText }}\n </span>\n </slot>\n </span>\n\n <span\n v-if=\"panelClearVisible\"\n class=\"shrink-0 h-4 w-4\"\n aria-hidden=\"true\"\n />\n\n <span\n v-else\n data-testid=\"ds-select-chevron\"\n class=\"shrink-0 text-[var(--muted-fg)] pointer-events-none\"\n >\n <span class=\"i-lucide-chevron-down inline-block h-4 w-4\" aria-hidden=\"true\" />\n </span>\n </button>\n\n <button\n v-if=\"panelClearVisible\"\n data-testid=\"ds-select-clear\"\n data-ds-select-clear\n type=\"button\"\n class=\"absolute top-1/2 -translate-y-1/2 right-3 h-6 w-6 inline-flex items-center justify-center rounded-md text-[var(--muted-fg)] hover:text-[var(--fg)] hover:bg-[color-mix(in_srgb,var(--muted)_25%,transparent)] disabled:opacity-50\"\n :disabled=\"disabled\"\n :aria-label=\"clearLabel\"\n @click.stop=\"clearSelection\"\n >\n <span class=\"i-lucide-x inline-block h-4 w-4\" aria-hidden=\"true\" />\n </button>\n\n <teleport to=\"body\">\n <transition\n enter-active-class=\"transition ease-out duration-150\"\n enter-from-class=\"transform opacity-0 scale-95\"\n enter-to-class=\"transform opacity-100 scale-100\"\n leave-active-class=\"transition ease-in duration-100\"\n leave-from-class=\"transform opacity-100 scale-100\"\n leave-to-class=\"transform opacity-0 scale-95\"\n >\n <div\n v-show=\"open\"\n ref=\"panelEl\"\n data-testid=\"ds-select-panel\"\n data-ds-select-panel\n class=\"fixed w-full\"\n :style=\"panelStyle\"\n >\n <div :class=\"panelClasses\">\n <div v-if=\"allowCustomValue\" class=\"p-2 border-b border-[var(--brd)]\">\n <GrInput\n ref=\"customInputRef\"\n v-model=\"customValue\"\n data-testid=\"ds-select-custom-input\"\n type=\"text\"\n :placeholder=\"customValuePlaceholder\"\n size=\"sm\"\n @keydown.enter.prevent=\"addCustom\"\n />\n </div>\n\n <div\n data-ds-select-listbox\n class=\"p-1 overflow-auto\"\n :style=\"{ maxHeight: `${dropdownMaxHeight}px` }\"\n role=\"listbox\"\n :aria-multiselectable=\"multiple ? 'true' : undefined\"\n >\n <button\n v-if=\"canAddCustom\"\n data-testid=\"ds-select-add-option\"\n data-ds-select-add-option\n type=\"button\"\n class=\"rounded-[10px] px-3 py-2 text-left text-[13px] hover:bg-[color-mix(in_srgb,var(--muted)_30%,transparent)]\" :class=\"[\n view === 'link' ? 'block min-w-full w-max whitespace-nowrap' : 'w-full',\n ]\"\n @click=\"addCustom\"\n >\n Add “{{ customValue.trim() }}”\n </button>\n\n <button\n v-for=\"opt in visibleOptions\"\n :key=\"opt.value\"\n data-ds-select-option\n type=\"button\"\n role=\"option\"\n :aria-selected=\"isSelected(opt.value) ? 'true' : 'false'\"\n class=\"rounded-[10px] px-3 py-2 text-left text-[13px] hover:bg-[color-mix(in_srgb,var(--muted)_30%,transparent)]\" :class=\"[\n view === 'link' ? 'block min-w-full w-max whitespace-nowrap' : 'w-full',\n ]\"\n @click=\"toggleValue(opt.value)\"\n >\n <slot name=\"option\" :option=\"opt\" :selected=\"isSelected(opt.value)\">\n <span class=\"flex items-center gap-2 min-w-0\">\n <span\n class=\"inline-block h-4 w-4 shrink-0\"\n :class=\"isSelected(opt.value) ? 'i-lucide-check text-[var(--primary)]' : ''\"\n aria-hidden=\"true\"\n />\n <span class=\"truncate\">{{ opt.label }}</span>\n </span>\n </slot>\n </button>\n </div>\n </div>\n </div>\n </transition>\n </teleport>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, nextTick, onUnmounted, ref, watch } from 'vue'\n\nimport GrInput from '../GrInput/GrInput.vue'\nimport { vClickOutside } from '../../directives'\n\nimport {\n defaultBaseClass,\n grSelectLinkNativeLabelClass,\n grSelectLinkNativeOverlayClass,\n grSelectNativeClass,\n grSelectPanelClasses,\n grSelectTriggerClass,\n linkBaseClass,\n type GrSelectModelValue,\n type GrSelectOption,\n type GrSelectOptionsView,\n type GrSelectSize,\n type GrSelectUnderline,\n type GrSelectVariant,\n type GrSelectView,\n} from './grSelectStyles'\n\nexport type {\n GrSelectModelValue,\n GrSelectOption,\n GrSelectOptionsView,\n GrSelectSize,\n GrSelectUnderline,\n GrSelectVariant,\n GrSelectView,\n} from './grSelectStyles'\n\n/**\n * Пропсы публичного DS-примитива «Select».\n */\nexport interface GrSelectProps {\n modelValue: GrSelectModelValue\n options?: GrSelectOption[]\n disabled?: boolean\n ariaLabel?: string\n view?: GrSelectView\n size?: GrSelectSize\n /** Placeholder (показывается, когда значение не выбрано). */\n placeholder?: string\n /** Multiple selection. */\n multiple?: boolean\n /** Как отображать список опций: нативный `<select>` или кастомная панель. */\n optionsView?: GrSelectOptionsView\n /** Разрешает ввод/выбор значения, которого нет в `options`. */\n allowCustomValue?: boolean\n /** Placeholder для инпута кастомного значения (только в `optionsView=\"panel\"`). */\n customValuePlaceholder?: string\n /** Максимальная высота панели (только в `optionsView=\"panel\"`). */\n dropdownMaxHeight?: number\n /** Закрывать панель после выбора (только в `optionsView=\"panel\"`). */\n closeOnSelect?: boolean\n /** Разрешает очистку выбранного значения. */\n clearable?: boolean\n /** i18n-label для кнопки очистки (`aria-label`). */\n clearLabel?: string\n /**\n * Цвет/вариант ссылки для `view=\"link\"` (аналогично `GrLink`).\n * В `view=\"default\"` не используется.\n */\n variant?: GrSelectVariant\n /**\n * Подчёркивание для `view=\"link\"` (аналогично `GrLink`).\n * В `view=\"default\"` не используется.\n */\n underline?: GrSelectUnderline\n}\n\nconst props = withDefaults(\n defineProps<GrSelectProps>(),\n {\n options: undefined,\n disabled: false,\n ariaLabel: undefined,\n view: 'default',\n size: 'md',\n\n placeholder: undefined,\n multiple: false,\n\n optionsView: 'native',\n allowCustomValue: false,\n customValuePlaceholder: 'Add value…',\n dropdownMaxHeight: 280,\n closeOnSelect: true,\n clearable: false,\n clearLabel: 'Clear',\n variant: 'primary',\n underline: 'auto',\n },\n)\n\nconst baseClassName = computed(() => props.view === 'link' ? linkBaseClass : defaultBaseClass)\n\nconst rootClass = computed(() => props.view === 'link' ? 'relative inline-block align-baseline' : 'relative w-full')\n\nconst emit = defineEmits<{\n (e: 'update:modelValue', value: GrSelectModelValue): void\n}>()\n\nconst optionsResolved = computed(() => props.options ?? [])\n\nfunction modelToArray(value: GrSelectModelValue): string[] {\n if (Array.isArray(value)) return value\n if (!value) return []\n return [value]\n}\n\nconst modelSingle = computed(() => {\n return Array.isArray(props.modelValue) ? (props.modelValue[0] ?? '') : props.modelValue\n})\n\nconst modelMultiple = computed(() => modelToArray(props.modelValue))\n\nconst selectedValues = computed(() => {\n return props.multiple ? modelMultiple.value : (modelSingle.value ? [modelSingle.value] : [])\n})\n\nconst hasSelection = computed(() => selectedValues.value.length > 0)\n\nconst selectedOptions = computed<GrSelectOption[]>(() => {\n const byValue = new Map(optionsResolved.value.map(o => [o.value, o]))\n return selectedValues.value.map((v) => byValue.get(v) ?? { value: v, label: v })\n})\n\nconst hasModelInOptions = computed(() => {\n if (props.multiple) return false\n return optionsResolved.value.some((o) => o.value === modelSingle.value)\n})\n\nconst displayLabel = computed(() => {\n if (props.multiple) {\n if (!selectedValues.value.length) return ''\n return selectedValues.value\n .map((v) => optionsResolved.value.find((o) => o.value === v)?.label ?? v)\n .join(', ')\n }\n\n return optionsResolved.value.find((o) => o.value === modelSingle.value)?.label ?? modelSingle.value\n})\n\nconst displayText = computed(() => {\n if (hasSelection.value) return displayLabel.value\n return props.placeholder ?? ''\n})\n\nconst nativeCustomOptionVisible = computed(() => {\n if (!props.allowCustomValue) return false\n if (props.optionsView !== 'native') return false\n if (props.multiple) return false\n if (!props.options) return false\n if (modelSingle.value === '') return false\n return !hasModelInOptions.value\n})\n\nconst customValue = ref('')\nconst open = ref(false)\nconst rootEl = ref<HTMLElement | null>(null)\nconst panelEl = ref<HTMLElement | null>(null)\nconst customInputRef = ref<InstanceType<typeof GrInput> | null>(null)\nconst clickOutsideExclude = [() => panelEl.value]\nconst panelStyle = ref<Record<string, string>>({\n left: '0px',\n top: '0px',\n width: '0px',\n zIndex: '2147483647',\n})\n\nfunction syncPanelPosition(): void {\n if (typeof window === 'undefined') return\n\n const root = rootEl.value\n if (!root) return\n\n const rect = root.getBoundingClientRect()\n\n // Для `view='link'` ширина триггера = ширине выбранной опции (`inline-block w-auto`),\n // и без отдельной обработки панель тоже становилась шириной выбранной опции, из-за чего\n // её размер «прыгал» при выборе разных опций. Решение — отдать вычисление ширины браузеру:\n // `width: max-content` (по самому длинному ребёнку — `w-full`-кнопкам с `whitespace-nowrap`),\n // `min-width` равной ширине триггера (чтобы панель не была уже самого триггера).\n // Это чисто CSS-подход — без JS-замеров и ResizeObserver, максимально перформантный.\n if (props.view === 'link') {\n panelStyle.value = {\n left: `${rect.left}px`,\n top: `${rect.bottom + 8}px`,\n width: 'max-content',\n minWidth: `${rect.width}px`,\n zIndex: '2147483647',\n }\n return\n }\n\n panelStyle.value = {\n left: `${rect.left}px`,\n top: `${rect.bottom + 8}px`,\n width: `${rect.width}px`,\n zIndex: '2147483647',\n }\n}\n\nfunction bindPanelPositionListeners(): void {\n if (typeof window === 'undefined') return\n window.addEventListener('resize', syncPanelPosition)\n window.addEventListener('scroll', syncPanelPosition, true)\n}\n\nfunction unbindPanelPositionListeners(): void {\n if (typeof window === 'undefined') return\n window.removeEventListener('resize', syncPanelPosition)\n window.removeEventListener('scroll', syncPanelPosition, true)\n}\n\nfunction closeDropdown(): void {\n open.value = false\n}\n\nfunction toggleDropdown(): void {\n if (props.disabled) return\n open.value = !open.value\n}\n\nfunction closeOnEscape(e: KeyboardEvent): void {\n if (e.key === 'Escape') closeDropdown()\n}\n\nwatch(\n open,\n async (isOpen) => {\n if (typeof document === 'undefined') return\n\n document.removeEventListener('keydown', closeOnEscape)\n unbindPanelPositionListeners()\n\n if (isOpen) document.addEventListener('keydown', closeOnEscape)\n\n if (!isOpen) {\n customValue.value = ''\n return\n }\n\n bindPanelPositionListeners()\n await nextTick()\n syncPanelPosition()\n\n if (props.allowCustomValue) {\n customInputRef.value?.focus()\n }\n },\n { immediate: true },\n)\n\nonUnmounted(() => {\n if (typeof document === 'undefined') return\n document.removeEventListener('keydown', closeOnEscape)\n unbindPanelPositionListeners()\n})\n\nconst panelClasses = computed(() => {\n return grSelectPanelClasses\n})\n\nconst visibleOptions = computed(() => {\n const base = optionsResolved.value\n\n const withCustom = (() => {\n if (!props.allowCustomValue) return base\n if (props.multiple) return base\n if (modelSingle.value === '') return base\n if (hasModelInOptions.value) return base\n return [{ value: modelSingle.value, label: modelSingle.value }, ...base]\n })()\n\n if (!props.allowCustomValue) return withCustom\n\n const q = customValue.value.trim().toLowerCase()\n if (!q) return withCustom\n\n return withCustom.filter((o) => {\n return o.label.toLowerCase().includes(q) || o.value.toLowerCase().includes(q)\n })\n})\n\nconst canAddCustom = computed(() => {\n if (!props.allowCustomValue) return false\n if (props.optionsView !== 'panel') return false\n const v = customValue.value.trim()\n if (!v) return false\n\n if (props.multiple) {\n if (selectedValues.value.includes(v)) return false\n return !optionsResolved.value.some((o) => o.value === v)\n }\n\n if (v === modelSingle.value) return false\n return !optionsResolved.value.some((o) => o.value === v)\n})\n\nfunction emitValue(value: GrSelectModelValue): void {\n emit('update:modelValue', value)\n}\n\nfunction isSelected(value: string): boolean {\n return selectedValues.value.includes(value)\n}\n\nfunction selectValue(value: string): void {\n emitValue(value)\n if (props.closeOnSelect) {\n closeDropdown()\n }\n}\n\nfunction toggleValue(value: string): void {\n if (!props.multiple) {\n selectValue(value)\n return\n }\n\n const next = selectedValues.value.slice()\n const idx = next.indexOf(value)\n if (idx >= 0) {\n next.splice(idx, 1)\n }\n else {\n next.push(value)\n }\n\n emitValue(next)\n if (props.closeOnSelect) {\n closeDropdown()\n }\n}\n\nfunction addCustom(): void {\n const v = customValue.value.trim()\n if (!v) return\n toggleValue(v)\n}\n\nconst showNativeChevron = computed(() => {\n return props.optionsView === 'native' && props.view !== 'link' && !props.multiple\n})\n\nconst nativeClearOptionVisible = computed(() => {\n if (!props.clearable) return false\n if (props.optionsView !== 'native') return false\n if (props.multiple) return false\n if (!props.options) return true\n return !props.options.some(o => o.value === '')\n})\n\nconst panelClearVisible = computed(() => {\n if (!props.clearable) return false\n if (props.optionsView !== 'panel') return false\n if (props.view === 'link') return false\n return hasSelection.value\n})\n\nconst nativeClassName = computed(() => {\n return grSelectNativeClass({\n view: props.view,\n size: props.size,\n disabled: props.disabled,\n variant: props.variant,\n underline: props.underline,\n showNativeChevron: showNativeChevron.value,\n })\n})\n\n/**\n * `view=\"link\"` в native-режиме: ширина обёртки должна определяться выбранной опцией,\n * а не самой длинной (как делает браузер по умолчанию). Поэтому рендерим прозрачный\n * `<select>`-overlay поверх видимого `<span>` с меткой — overlay принимает клики/клавиатуру,\n * span задаёт ширину компонента в закрытом состоянии.\n */\nconst isLinkNative = computed(() => props.view === 'link' && props.optionsView === 'native')\n\nconst linkNativeLabelClassName = computed(() => grSelectLinkNativeLabelClass({\n size: props.size,\n variant: props.variant,\n underline: props.underline,\n disabled: props.disabled,\n}))\n\nconst linkNativeDisplayText = computed(() => {\n return displayText.value || props.placeholder || '\\u00A0'\n})\n\nconst triggerClassName = computed(() => {\n return grSelectTriggerClass({\n view: props.view,\n optionsView: props.optionsView,\n size: props.size,\n disabled: props.disabled,\n variant: props.variant,\n underline: props.underline,\n })\n})\n\nfunction onChange(e: Event): void {\n const el = e.target as HTMLSelectElement\n\n if (props.multiple) {\n const values = Array.from(el.selectedOptions, o => o.value)\n emit('update:modelValue', values)\n return\n }\n\n emit('update:modelValue', el.value)\n}\n\nfunction clearSelection(): void {\n if (props.disabled) return\n emitValue(props.multiple ? [] : '')\n}\n</script>\n\n<template>\n <div\n v-if=\"optionsView === 'native'\"\n data-ds-select\n :class=\"rootClass\"\n >\n <select\n data-ds-select-native\n :value=\"multiple ? modelMultiple : modelSingle\"\n :multiple=\"multiple\"\n :disabled=\"disabled\"\n :aria-label=\"ariaLabel\"\n :class=\"isLinkNative ? grSelectLinkNativeOverlayClass : [baseClassName, nativeClassName]\"\n @change=\"onChange\"\n >\n <option v-if=\"nativeClearOptionVisible\" value=\"\" />\n\n <slot>\n <option v-if=\"nativeCustomOptionVisible\" :value=\"modelSingle\">\n {{ modelSingle }}\n </option>\n <option v-for=\"opt in options\" :key=\"opt.value\" :value=\"opt.value\">\n {{ opt.label }}\n </option>\n </slot>\n </select>\n\n <span\n v-if=\"isLinkNative\"\n data-ds-select-link-label\n aria-hidden=\"true\"\n :class=\"linkNativeLabelClassName\"\n >{{ linkNativeDisplayText }}</span>\n\n <span\n v-if=\"showNativeChevron\"\n data-testid=\"ds-select-chevron\"\n class=\"absolute top-1/2 -translate-y-1/2 right-3 text-[var(--muted-fg)] pointer-events-none\"\n >\n <span class=\"i-lucide-chevron-down inline-block h-4 w-4\" aria-hidden=\"true\" />\n </span>\n </div>\n\n <div\n v-else\n ref=\"rootEl\"\n v-click-outside=\"{ handler: closeDropdown, enabled: open, exclude: clickOutsideExclude }\"\n data-ds-select\n :class=\"rootClass\"\n >\n <button\n data-testid=\"ds-select-trigger\"\n data-ds-select-trigger\n type=\"button\"\n :disabled=\"disabled\"\n :aria-label=\"ariaLabel\"\n role=\"combobox\"\n aria-readonly=\"true\"\n :aria-expanded=\"open ? 'true' : 'false'\"\n :class=\"[baseClassName, triggerClassName]\"\n @click=\"toggleDropdown\"\n >\n <span class=\"min-w-0 flex-1\">\n <slot\n name=\"value\"\n :selected-options=\"selectedOptions\"\n :selected-values=\"selectedValues\"\n :display-label=\"displayLabel\"\n :placeholder=\"placeholder\"\n :has-selection=\"hasSelection\"\n >\n <span\n class=\"block truncate\"\n :class=\"!hasSelection ? 'text-[var(--muted-fg)]' : ''\"\n >\n {{ displayText }}\n </span>\n </slot>\n </span>\n\n <span\n v-if=\"panelClearVisible\"\n class=\"shrink-0 h-4 w-4\"\n aria-hidden=\"true\"\n />\n\n <span\n v-else\n data-testid=\"ds-select-chevron\"\n class=\"shrink-0 text-[var(--muted-fg)] pointer-events-none\"\n >\n <span class=\"i-lucide-chevron-down inline-block h-4 w-4\" aria-hidden=\"true\" />\n </span>\n </button>\n\n <button\n v-if=\"panelClearVisible\"\n data-testid=\"ds-select-clear\"\n data-ds-select-clear\n type=\"button\"\n class=\"absolute top-1/2 -translate-y-1/2 right-3 h-6 w-6 inline-flex items-center justify-center rounded-md text-[var(--muted-fg)] hover:text-[var(--fg)] hover:bg-[color-mix(in_srgb,var(--muted)_25%,transparent)] disabled:opacity-50\"\n :disabled=\"disabled\"\n :aria-label=\"clearLabel\"\n @click.stop=\"clearSelection\"\n >\n <span class=\"i-lucide-x inline-block h-4 w-4\" aria-hidden=\"true\" />\n </button>\n\n <teleport to=\"body\">\n <transition\n enter-active-class=\"transition ease-out duration-150\"\n enter-from-class=\"transform opacity-0 scale-95\"\n enter-to-class=\"transform opacity-100 scale-100\"\n leave-active-class=\"transition ease-in duration-100\"\n leave-from-class=\"transform opacity-100 scale-100\"\n leave-to-class=\"transform opacity-0 scale-95\"\n >\n <div\n v-show=\"open\"\n ref=\"panelEl\"\n data-testid=\"ds-select-panel\"\n data-ds-select-panel\n class=\"fixed w-full\"\n :style=\"panelStyle\"\n >\n <div :class=\"panelClasses\">\n <div v-if=\"allowCustomValue\" class=\"p-2 border-b border-[var(--brd)]\">\n <GrInput\n ref=\"customInputRef\"\n v-model=\"customValue\"\n data-testid=\"ds-select-custom-input\"\n type=\"text\"\n :placeholder=\"customValuePlaceholder\"\n size=\"sm\"\n @keydown.enter.prevent=\"addCustom\"\n />\n </div>\n\n <div\n data-ds-select-listbox\n class=\"p-1 overflow-auto\"\n :style=\"{ maxHeight: `${dropdownMaxHeight}px` }\"\n role=\"listbox\"\n :aria-multiselectable=\"multiple ? 'true' : undefined\"\n >\n <button\n v-if=\"canAddCustom\"\n data-testid=\"ds-select-add-option\"\n data-ds-select-add-option\n type=\"button\"\n class=\"rounded-[10px] px-3 py-2 text-left text-[13px] hover:bg-[color-mix(in_srgb,var(--muted)_30%,transparent)]\" :class=\"[\n view === 'link' ? 'block min-w-full w-max whitespace-nowrap' : 'w-full',\n ]\"\n @click=\"addCustom\"\n >\n Add “{{ customValue.trim() }}”\n </button>\n\n <button\n v-for=\"opt in visibleOptions\"\n :key=\"opt.value\"\n data-ds-select-option\n type=\"button\"\n role=\"option\"\n :aria-selected=\"isSelected(opt.value) ? 'true' : 'false'\"\n class=\"rounded-[10px] px-3 py-2 text-left text-[13px] hover:bg-[color-mix(in_srgb,var(--muted)_30%,transparent)]\" :class=\"[\n view === 'link' ? 'block min-w-full w-max whitespace-nowrap' : 'w-full',\n ]\"\n @click=\"toggleValue(opt.value)\"\n >\n <slot name=\"option\" :option=\"opt\" :selected=\"isSelected(opt.value)\">\n <span class=\"flex items-center gap-2 min-w-0\">\n <span\n class=\"inline-block h-4 w-4 shrink-0\"\n :class=\"isSelected(opt.value) ? 'i-lucide-check text-[var(--primary)]' : ''\"\n aria-hidden=\"true\"\n />\n <span class=\"truncate\">{{ opt.label }}</span>\n </span>\n </slot>\n </button>\n </div>\n </div>\n </div>\n </transition>\n </teleport>\n </div>\n</template>\n"],"mappings":"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"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { t as GrSelect_default } from "./chunks/GrSelect-
|
|
2
|
-
import { n as grSelectSafelist, t as grSelectConfig } from "../../chunks/config-
|
|
1
|
+
import { t as GrSelect_default } from "./chunks/GrSelect-C3NAfTqL.js";
|
|
2
|
+
import { n as grSelectSafelist, t as grSelectConfig } from "../../chunks/config-XoANT5mU.js";
|
|
3
3
|
export { GrSelect_default as GrSelect, GrSelect_default as default, grSelectConfig, grSelectSafelist };
|
package/dist/components/GrSidebar/chunks/{GrSidebar-CSZ3RpFB.js.map → GrSidebar-DVucgcfV.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GrSidebar-
|
|
1
|
+
{"version":3,"file":"GrSidebar-DVucgcfV.js","names":["$slots"],"sources":["../../../../src/components/GrSidebar/GrSidebar.vue","../../../../src/components/GrSidebar/GrSidebar.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * GrSidebar — боковая панель приложения с заголовком и слотом контента.\n *\n * Слоты `#title` / `#subtitle` имеют приоритет над одноимёнными пропами\n * и позволяют передать произвольную разметку (иконки, бейджи, действия).\n */\nexport interface GrSidebarProps {\n title?: string\n subtitle?: string\n}\n\nwithDefaults(defineProps<GrSidebarProps>(), {\n title: undefined,\n subtitle: undefined,\n})\n</script>\n\n<template>\n <aside\n data-ds-sidebar\n class=\"border-r border-[var(--sidebar-brd)] bg-[var(--sidebar)] text-[var(--sidebar-fg)]\"\n >\n <div\n v-if=\"$slots.title || title || $slots.subtitle || subtitle\"\n data-ds-sidebar-header\n class=\"border-b border-[var(--sidebar-brd)] px-4 py-4\"\n >\n <div\n v-if=\"$slots.subtitle || subtitle\"\n data-ds-sidebar-subtitle\n class=\"text-[14px] text-[var(--muted-fg)]\"\n >\n <slot name=\"subtitle\">\n {{ subtitle }}\n </slot>\n </div>\n <div\n v-if=\"$slots.title || title\"\n data-ds-sidebar-title\n class=\"text-[18px] font-700\"\n >\n <slot name=\"title\">\n {{ title }}\n </slot>\n </div>\n </div>\n <div class=\"p-3\">\n <slot />\n </div>\n </aside>\n</template>\n","<script setup lang=\"ts\">\n/**\n * GrSidebar — боковая панель приложения с заголовком и слотом контента.\n *\n * Слоты `#title` / `#subtitle` имеют приоритет над одноимёнными пропами\n * и позволяют передать произвольную разметку (иконки, бейджи, действия).\n */\nexport interface GrSidebarProps {\n title?: string\n subtitle?: string\n}\n\nwithDefaults(defineProps<GrSidebarProps>(), {\n title: undefined,\n subtitle: undefined,\n})\n</script>\n\n<template>\n <aside\n data-ds-sidebar\n class=\"border-r border-[var(--sidebar-brd)] bg-[var(--sidebar)] text-[var(--sidebar-fg)]\"\n >\n <div\n v-if=\"$slots.title || title || $slots.subtitle || subtitle\"\n data-ds-sidebar-header\n class=\"border-b border-[var(--sidebar-brd)] px-4 py-4\"\n >\n <div\n v-if=\"$slots.subtitle || subtitle\"\n data-ds-sidebar-subtitle\n class=\"text-[14px] text-[var(--muted-fg)]\"\n >\n <slot name=\"subtitle\">\n {{ subtitle }}\n </slot>\n </div>\n <div\n v-if=\"$slots.title || title\"\n data-ds-sidebar-title\n class=\"text-[18px] font-700\"\n >\n <slot name=\"title\">\n {{ title }}\n </slot>\n </div>\n </div>\n <div class=\"p-3\">\n <slot />\n </div>\n </aside>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uBAmBE,mBA+BQ,SA/BR,YA+BQ,CA1BEA,KAAAA,OAAO,SAAS,QAAA,SAASA,KAAAA,OAAO,YAAY,QAAA,YAAA,WAAA,EADpD,mBAuBM,OAvBN,YAuBM,CAjBIA,KAAAA,OAAO,YAAY,QAAA,YAAA,WAAA,EAD3B,mBAQM,OARN,YAQM,CAHJ,WAEO,KAAA,QAAA,YAAA,EAAA,QAAA,CAAA,gBAAA,gBADF,QAAA,SAAQ,EAAA,EAAA,CAAA,CAAA,CAAA,CAAA,IAAA,mBAAA,IAAA,KAAA,EAIPA,KAAAA,OAAO,SAAS,QAAA,SAAA,WAAA,EADxB,mBAQM,OARN,YAQM,CAHJ,WAEO,KAAA,QAAA,SAAA,EAAA,QAAA,CAAA,gBAAA,gBADF,QAAA,MAAK,EAAA,EAAA,CAAA,CAAA,CAAA,CAAA,IAAA,mBAAA,IAAA,KAAA,CAAA,CAAA,IAAA,mBAAA,IAAA,KAAA,EAId,mBAEM,OAFN,YAEM,CADJ,WAAQ,KAAA,QAAA,UAAA,CAAA,CAAA,CAAA,CAAA"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { t as GrSidebar_default } from "./chunks/GrSidebar-
|
|
2
|
-
import { t as grSidebarConfig } from "../../chunks/config-
|
|
1
|
+
import { t as GrSidebar_default } from "./chunks/GrSidebar-DVucgcfV.js";
|
|
2
|
+
import { t as grSidebarConfig } from "../../chunks/config-DnsISjD8.js";
|
|
3
3
|
export { GrSidebar_default as GrSidebar, GrSidebar_default as default, grSidebarConfig };
|
package/dist/components/GrSkeleton/chunks/{GrSkeleton-DKXkqWDa.js.map → GrSkeleton-CDTvCSoH.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GrSkeleton-
|
|
1
|
+
{"version":3,"file":"GrSkeleton-CDTvCSoH.js","names":[],"sources":["../../../../src/components/GrSkeleton/GrSkeleton.vue","../../../../src/components/GrSkeleton/GrSkeleton.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * GrSkeleton — заглушка-плейсхолдер для загружающегося контента.\n *\n * Помечен `aria-hidden=\"true\"`, чтобы скринридер не озвучивал «пустой» узел;\n * внешний контейнер обычно имеет `aria-busy=\"true\"` + `aria-live`.\n */\nexport interface GrSkeletonProps {\n height?: string\n width?: string\n rounded?: string\n}\n\nwithDefaults(defineProps<GrSkeletonProps>(), {\n height: '12px',\n width: '100%',\n rounded: '9999px',\n})\n</script>\n\n<template>\n <div\n data-ds-skeleton\n aria-hidden=\"true\"\n class=\"bg-[var(--muted)] border border-[var(--brd)]\"\n :style=\"{ height, width, borderRadius: rounded }\"\n />\n</template>\n\n<style>\n@keyframes ds-skeleton-pulse {\n 0%, 100% {\n opacity: 1;\n }\n 50% {\n opacity: 0.5;\n }\n}\n\n[data-ds-skeleton] {\n animation: ds-skeleton-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;\n}\n\n@media (prefers-reduced-motion: reduce) {\n [data-ds-skeleton] {\n animation: none;\n }\n}\n</style>\n","<script setup lang=\"ts\">\n/**\n * GrSkeleton — заглушка-плейсхолдер для загружающегося контента.\n *\n * Помечен `aria-hidden=\"true\"`, чтобы скринридер не озвучивал «пустой» узел;\n * внешний контейнер обычно имеет `aria-busy=\"true\"` + `aria-live`.\n */\nexport interface GrSkeletonProps {\n height?: string\n width?: string\n rounded?: string\n}\n\nwithDefaults(defineProps<GrSkeletonProps>(), {\n height: '12px',\n width: '100%',\n rounded: '9999px',\n})\n</script>\n\n<template>\n <div\n data-ds-skeleton\n aria-hidden=\"true\"\n class=\"bg-[var(--muted)] border border-[var(--brd)]\"\n :style=\"{ height, width, borderRadius: rounded }\"\n />\n</template>\n\n<style>\n@keyframes ds-skeleton-pulse {\n 0%, 100% {\n opacity: 1;\n }\n 50% {\n opacity: 0.5;\n }\n}\n\n[data-ds-skeleton] {\n animation: ds-skeleton-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;\n}\n\n@media (prefers-reduced-motion: reduce) {\n [data-ds-skeleton] {\n animation: none;\n }\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;uBAqBE,mBAKE,OAAA;IAJE,oBAAA;IACA,eAAY;IACZ,OAAM;IACL,OAAK,eAAA;KAAA,QAAI,QAAA;KAAM,OAAE,QAAA;KAAK,cAAgB,QAAA;KAAO,CAAA"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { t as GrSkeleton_default } from "./chunks/GrSkeleton-
|
|
2
|
-
import { t as grSkeletonConfig } from "../../chunks/config-
|
|
1
|
+
import { t as GrSkeleton_default } from "./chunks/GrSkeleton-CDTvCSoH.js";
|
|
2
|
+
import { t as grSkeletonConfig } from "../../chunks/config-Mp9DJ9Mq2.js";
|
|
3
3
|
export { GrSkeleton_default as GrSkeleton, GrSkeleton_default as default, grSkeletonConfig };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as grSwitchTrackClass, i as grSwitchThumbClass, r as grSwitchLabelClass } from "../../../chunks/config-
|
|
1
|
+
import { a as grSwitchTrackClass, i as grSwitchThumbClass, r as grSwitchLabelClass } from "../../../chunks/config-rxFL-6yF2.js";
|
|
2
2
|
import { computed, createCommentVNode, createElementBlock, createElementVNode, defineComponent, normalizeClass, normalizeStyle, openBlock, renderSlot } from "vue";
|
|
3
3
|
//#region src/components/GrSwitch/GrSwitch.vue?vue&type=script&setup=true&lang.ts
|
|
4
4
|
var _hoisted_1 = [
|
package/dist/components/GrSwitch/chunks/{GrSwitch-CdBxwV3k.js.map → GrSwitch-Bnamgqb2.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GrSwitch-
|
|
1
|
+
{"version":3,"file":"GrSwitch-Bnamgqb2.js","names":["$slots"],"sources":["../../../../src/components/GrSwitch/GrSwitch.vue","../../../../src/components/GrSwitch/GrSwitch.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport {computed} from 'vue'\n\nimport {\n grSwitchLabelClass,\n grSwitchThumbClass,\n grSwitchTrackClass,\n type GrSwitchSize,\n} from './grSwitchStyles'\n\nexport type {GrSwitchSize} from './grSwitchStyles'\n\n/**\n * Пропсы публичного DS-примитива «Switch».\n */\nexport interface GrSwitchProps {\n modelValue: boolean\n disabled?: boolean\n ariaLabel?: string\n size?: GrSwitchSize\n /** Кастомный цвет фона в активном состоянии. Если не задан — `var(--primary)`. */\n activeBackgroundColor?: string\n /** Кастомный цвет фона в неактивном состоянии. Если не задан — `var(--muted)`. */\n inactiveBackgroundColor?: string\n}\n\nconst getCustomColor = (value?: string) => value?.trim() || undefined\n\nconst props = withDefaults(\n defineProps<GrSwitchProps>(),\n {\n disabled: false,\n ariaLabel: undefined,\n size: 'md',\n activeBackgroundColor: undefined,\n inactiveBackgroundColor: undefined,\n },\n)\n\nconst emit = defineEmits<{\n (e: 'update:modelValue', value: boolean): void\n}>()\n\nconst trackClass = computed(() => grSwitchTrackClass(props.size))\n\nconst trackStyle = computed(() => {\n const isChecked = props.modelValue\n const defaultBackgroundColor = isChecked ? 'var(--primary)' : 'var(--muted)'\n const customBackgroundColor = getCustomColor(\n isChecked ? props.activeBackgroundColor : props.inactiveBackgroundColor,\n )\n const backgroundColor = customBackgroundColor ?? defaultBackgroundColor\n\n return {\n '--ds-switch-track-bg': backgroundColor,\n '--ds-switch-track-brd': customBackgroundColor\n ? backgroundColor\n : isChecked\n ? 'var(--primary)'\n : 'var(--brd)',\n backgroundColor: 'var(--ds-switch-track-bg)',\n }\n})\n\nconst thumbClass = computed(() => grSwitchThumbClass({size: props.size, checked: props.modelValue}))\n\nconst labelClass = computed(() => grSwitchLabelClass(props.size))\n\nfunction toggle(): void {\n if (props.disabled) {\n return\n }\n\n emit('update:modelValue', !props.modelValue)\n}\n</script>\n\n<template>\n <button\n type=\"button\"\n role=\"switch\"\n data-ds-switch\n :aria-checked=\"modelValue ? 'true' : 'false'\"\n :aria-label=\"ariaLabel\"\n :disabled=\"disabled\"\n class=\"inline-flex items-center gap-2 select-none disabled:opacity-50 disabled:cursor-not-allowed\"\n @click=\"toggle\"\n >\n <span\n data-testid=\"ds-switch-track\"\n data-ds-switch-track\n :class=\"trackClass\"\n :style=\"trackStyle\"\n >\n <span\n data-testid=\"ds-switch-thumb\"\n data-ds-switch-thumb\n :class=\"thumbClass\"\n aria-hidden=\"true\"\n />\n </span>\n <span\n v-if=\"$slots.default\"\n data-ds-switch-label\n :class=\"labelClass\"\n >\n <slot />\n </span>\n </button>\n</template>\n","<script setup lang=\"ts\">\nimport {computed} from 'vue'\n\nimport {\n grSwitchLabelClass,\n grSwitchThumbClass,\n grSwitchTrackClass,\n type GrSwitchSize,\n} from './grSwitchStyles'\n\nexport type {GrSwitchSize} from './grSwitchStyles'\n\n/**\n * Пропсы публичного DS-примитива «Switch».\n */\nexport interface GrSwitchProps {\n modelValue: boolean\n disabled?: boolean\n ariaLabel?: string\n size?: GrSwitchSize\n /** Кастомный цвет фона в активном состоянии. Если не задан — `var(--primary)`. */\n activeBackgroundColor?: string\n /** Кастомный цвет фона в неактивном состоянии. Если не задан — `var(--muted)`. */\n inactiveBackgroundColor?: string\n}\n\nconst getCustomColor = (value?: string) => value?.trim() || undefined\n\nconst props = withDefaults(\n defineProps<GrSwitchProps>(),\n {\n disabled: false,\n ariaLabel: undefined,\n size: 'md',\n activeBackgroundColor: undefined,\n inactiveBackgroundColor: undefined,\n },\n)\n\nconst emit = defineEmits<{\n (e: 'update:modelValue', value: boolean): void\n}>()\n\nconst trackClass = computed(() => grSwitchTrackClass(props.size))\n\nconst trackStyle = computed(() => {\n const isChecked = props.modelValue\n const defaultBackgroundColor = isChecked ? 'var(--primary)' : 'var(--muted)'\n const customBackgroundColor = getCustomColor(\n isChecked ? props.activeBackgroundColor : props.inactiveBackgroundColor,\n )\n const backgroundColor = customBackgroundColor ?? defaultBackgroundColor\n\n return {\n '--ds-switch-track-bg': backgroundColor,\n '--ds-switch-track-brd': customBackgroundColor\n ? backgroundColor\n : isChecked\n ? 'var(--primary)'\n : 'var(--brd)',\n backgroundColor: 'var(--ds-switch-track-bg)',\n }\n})\n\nconst thumbClass = computed(() => grSwitchThumbClass({size: props.size, checked: props.modelValue}))\n\nconst labelClass = computed(() => grSwitchLabelClass(props.size))\n\nfunction toggle(): void {\n if (props.disabled) {\n return\n }\n\n emit('update:modelValue', !props.modelValue)\n}\n</script>\n\n<template>\n <button\n type=\"button\"\n role=\"switch\"\n data-ds-switch\n :aria-checked=\"modelValue ? 'true' : 'false'\"\n :aria-label=\"ariaLabel\"\n :disabled=\"disabled\"\n class=\"inline-flex items-center gap-2 select-none disabled:opacity-50 disabled:cursor-not-allowed\"\n @click=\"toggle\"\n >\n <span\n data-testid=\"ds-switch-track\"\n data-ds-switch-track\n :class=\"trackClass\"\n :style=\"trackStyle\"\n >\n <span\n data-testid=\"ds-switch-thumb\"\n data-ds-switch-thumb\n :class=\"thumbClass\"\n aria-hidden=\"true\"\n />\n </span>\n <span\n v-if=\"$slots.default\"\n data-ds-switch-label\n :class=\"labelClass\"\n >\n <slot />\n </span>\n </button>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;EA0BA,MAAM,kBAAkB,UAAmB,OAAO,MAAM,IAAI,KAAA;EAE5D,MAAM,QAAQ;EAWd,MAAM,OAAO;EAIb,MAAM,aAAa,eAAe,mBAAmB,MAAM,KAAK,CAAA;EAEhE,MAAM,aAAa,eAAe;GAChC,MAAM,YAAY,MAAM;GACxB,MAAM,yBAAyB,YAAY,mBAAmB;GAC9D,MAAM,wBAAwB,eAC1B,YAAY,MAAM,wBAAwB,MAAM,wBACpD;GACA,MAAM,kBAAkB,yBAAyB;AAEjD,UAAO;IACL,wBAAwB;IACxB,yBAAyB,wBACnB,kBACA,YACI,mBACA;IACV,iBAAiB;IACnB;IACD;EAED,MAAM,aAAa,eAAe,mBAAmB;GAAC,MAAM,MAAM;GAAM,SAAS,MAAM;GAAW,CAAC,CAAA;EAEnG,MAAM,aAAa,eAAe,mBAAmB,MAAM,KAAK,CAAA;EAEhE,SAAS,SAAe;AACtB,OAAI,MAAM,SACR;AAGF,QAAK,qBAAqB,CAAC,MAAM,WAAU;;;uBAK3C,mBA8BS,UAAA;IA7BL,MAAK;IACL,MAAK;IACL,kBAAA;IACC,gBAAc,QAAA,aAAU,SAAA;IACxB,cAAY,QAAA;IACZ,UAAU,QAAA;IACX,OAAM;IACL,SAAO;OAEV,mBAYO,QAAA;IAXH,eAAY;IACZ,wBAAA;IACC,OAAK,eAAE,WAAA,MAAU;IACjB,OAAK,eAAE,WAAA,MAAU;OAEpB,mBAKE,QAAA;IAJE,eAAY;IACZ,wBAAA;IACC,OAAK,eAAE,WAAA,MAAU;IAClB,eAAY;qBAIRA,KAAAA,OAAO,WAAA,WAAA,EADjB,mBAMO,QAAA;;IAJH,wBAAA;IACC,OAAK,eAAE,WAAA,MAAU;OAEpB,WAAQ,KAAA,QAAA,UAAA,CAAA,EAAA,EAAA,IAAA,mBAAA,IAAA,KAAA,CAAA,EAAA,GAAA,WAAA"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { n as grSwitchSafelist, t as grSwitchConfig } from "../../chunks/config-
|
|
2
|
-
import { t as GrSwitch_default } from "./chunks/GrSwitch-
|
|
1
|
+
import { n as grSwitchSafelist, t as grSwitchConfig } from "../../chunks/config-rxFL-6yF2.js";
|
|
2
|
+
import { t as GrSwitch_default } from "./chunks/GrSwitch-Bnamgqb2.js";
|
|
3
3
|
export { GrSwitch_default as GrSwitch, GrSwitch_default as default, grSwitchConfig, grSwitchSafelist };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { t as GrTable_default } from "./chunks/GrTable-CyQ9uw8A.js";
|
|
2
|
-
import { t as grTableConfig } from "../../chunks/config-
|
|
2
|
+
import { t as grTableConfig } from "../../chunks/config-DQOSlWvp.js";
|
|
3
3
|
export { GrTable_default as GrTable, GrTable_default as default, grTableConfig };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GrTabs-BpF8AAUz.js","names":[],"sources":["../../../../src/components/GrTabs/GrTabs.vue","../../../../src/components/GrTabs/GrTabs.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, nextTick, ref } from 'vue'\n\nexport type GrTab = {\n value: string\n label: string\n badge?: string\n disabled?: boolean\n}\n\nexport type GrTabsProps = {\n modelValue: string\n tabs: GrTab[]\n}\n\n/**\n * GrTabs — горизонтальная группа вкладок с паттерном WAI-ARIA `tablist`.\n *\n * A11y:\n * - `role=\"tablist\"` на корне, `role=\"tab\"` на каждой вкладке.\n * - Roving `tabindex`: активная вкладка `0`, остальные `-1`.\n * - Клавиатура: `ArrowLeft`/`ArrowRight` — цикличный переход, `Home`/`End` — к первой/последней.\n * - При переключении стрелками DOM-фокус программно переносится на новую вкладку.\n *\n * Сам компонент не рендерит `tabpanel` — это ответственность консьюмера (по `aria-controls`/внешней разметке).\n */\nconst props = defineProps<GrTabsProps>()\n\nconst emit = defineEmits<{\n (e: 'update:modelValue', value: string): void\n}>()\n\nconst buttonRefs = ref<HTMLButtonElement[]>([])\n\nfunction setButtonRef(el: unknown, index: number): void {\n if (el instanceof HTMLButtonElement)\n buttonRefs.value[index] = el\n}\n\nconst activeIndex = computed(() => props.tabs.findIndex(t => t.value === props.modelValue))\n\nfunction isEnabled(tab: GrTab): boolean {\n return !tab.disabled\n}\n\nasync function selectByIndex(index: number, focus = false): Promise<void> {\n const tab = props.tabs[index]\n if (!tab || !isEnabled(tab))\n return\n if (tab.value !== props.modelValue)\n emit('update:modelValue', tab.value)\n if (focus) {\n await nextTick()\n buttonRefs.value[index]?.focus()\n }\n}\n\nfunction findNextEnabled(from: number, direction: 1 | -1): number {\n const length = props.tabs.length\n if (length === 0)\n return -1\n let index = from\n for (let i = 0; i < length; i++) {\n index = (index + direction + length) % length\n const tab = props.tabs[index]\n if (tab && isEnabled(tab))\n return index\n }\n return -1\n}\n\nfunction firstEnabled(): number {\n return props.tabs.findIndex(isEnabled)\n}\n\nfunction lastEnabled(): number {\n for (let i = props.tabs.length - 1; i >= 0; i--) {\n const tab = props.tabs[i]\n if (tab && isEnabled(tab))\n return i\n }\n return -1\n}\n\nfunction onKeydown(event: KeyboardEvent): void {\n if (props.tabs.length === 0)\n return\n\n const currentIndex = activeIndex.value < 0 ? 0 : activeIndex.value\n let nextIndex = -1\n\n switch (event.key) {\n case 'ArrowRight':\n nextIndex = findNextEnabled(currentIndex, 1)\n break\n case 'ArrowLeft':\n nextIndex = findNextEnabled(currentIndex, -1)\n break\n case 'Home':\n nextIndex = firstEnabled()\n break\n case 'End':\n nextIndex = lastEnabled()\n break\n default:\n return\n }\n\n if (nextIndex < 0)\n return\n\n event.preventDefault()\n void selectByIndex(nextIndex, true)\n}\n\nfunction onClick(tab: GrTab): void {\n if (!isEnabled(tab))\n return\n if (tab.value !== props.modelValue)\n emit('update:modelValue', tab.value)\n}\n</script>\n\n<template>\n <div\n role=\"tablist\"\n data-ds-tabs\n class=\"inline-flex flex-wrap gap-1 rounded-[var(--ds-radius-lg)] border border-[var(--brd)] bg-[var(--muted)] p-1\"\n @keydown=\"onKeydown\"\n >\n <button\n v-for=\"(tab, index) in tabs\"\n :key=\"tab.value\"\n :ref=\"el => setButtonRef(el, index)\"\n type=\"button\"\n role=\"tab\"\n data-ds-tab\n :aria-selected=\"tab.value === modelValue ? 'true' : 'false'\"\n :aria-disabled=\"tab.disabled ? 'true' : undefined\"\n :disabled=\"tab.disabled\"\n :tabindex=\"tab.value === modelValue ? 0 : -1\"\n class=\"h-9 px-3 rounded-[10px] text-sm font-600 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ring)] disabled:opacity-60 disabled:cursor-not-allowed\"\n :class=\"tab.value === modelValue\n ? 'bg-[var(--card)] text-[var(--fg)] border border-[var(--brd)]'\n : 'text-[var(--muted-fg)] hover:text-[var(--fg)] hover:bg-[color-mix(in_srgb,var(--card)_70%,transparent)]'\"\n @click=\"onClick(tab)\"\n >\n <span class=\"inline-flex items-center gap-2\">\n <span>{{ tab.label }}</span>\n <span\n v-if=\"tab.badge\"\n class=\"text-[11px] px-1.5 py-0.5 rounded-full bg-[var(--secondary)] text-[var(--secondary-fg)]\"\n >\n {{ tab.badge }}\n </span>\n </span>\n </button>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, nextTick, ref } from 'vue'\n\nexport type GrTab = {\n value: string\n label: string\n badge?: string\n disabled?: boolean\n}\n\nexport type GrTabsProps = {\n modelValue: string\n tabs: GrTab[]\n}\n\n/**\n * GrTabs — горизонтальная группа вкладок с паттерном WAI-ARIA `tablist`.\n *\n * A11y:\n * - `role=\"tablist\"` на корне, `role=\"tab\"` на каждой вкладке.\n * - Roving `tabindex`: активная вкладка `0`, остальные `-1`.\n * - Клавиатура: `ArrowLeft`/`ArrowRight` — цикличный переход, `Home`/`End` — к первой/последней.\n * - При переключении стрелками DOM-фокус программно переносится на новую вкладку.\n *\n * Сам компонент не рендерит `tabpanel` — это ответственность консьюмера (по `aria-controls`/внешней разметке).\n */\nconst props = defineProps<GrTabsProps>()\n\nconst emit = defineEmits<{\n (e: 'update:modelValue', value: string): void\n}>()\n\nconst buttonRefs = ref<HTMLButtonElement[]>([])\n\nfunction setButtonRef(el: unknown, index: number): void {\n if (el instanceof HTMLButtonElement)\n buttonRefs.value[index] = el\n}\n\nconst activeIndex = computed(() => props.tabs.findIndex(t => t.value === props.modelValue))\n\nfunction isEnabled(tab: GrTab): boolean {\n return !tab.disabled\n}\n\nasync function selectByIndex(index: number, focus = false): Promise<void> {\n const tab = props.tabs[index]\n if (!tab || !isEnabled(tab))\n return\n if (tab.value !== props.modelValue)\n emit('update:modelValue', tab.value)\n if (focus) {\n await nextTick()\n buttonRefs.value[index]?.focus()\n }\n}\n\nfunction findNextEnabled(from: number, direction: 1 | -1): number {\n const length = props.tabs.length\n if (length === 0)\n return -1\n let index = from\n for (let i = 0; i < length; i++) {\n index = (index + direction + length) % length\n const tab = props.tabs[index]\n if (tab && isEnabled(tab))\n return index\n }\n return -1\n}\n\nfunction firstEnabled(): number {\n return props.tabs.findIndex(isEnabled)\n}\n\nfunction lastEnabled(): number {\n for (let i = props.tabs.length - 1; i >= 0; i--) {\n const tab = props.tabs[i]\n if (tab && isEnabled(tab))\n return i\n }\n return -1\n}\n\nfunction onKeydown(event: KeyboardEvent): void {\n if (props.tabs.length === 0)\n return\n\n const currentIndex = activeIndex.value < 0 ? 0 : activeIndex.value\n let nextIndex = -1\n\n switch (event.key) {\n case 'ArrowRight':\n nextIndex = findNextEnabled(currentIndex, 1)\n break\n case 'ArrowLeft':\n nextIndex = findNextEnabled(currentIndex, -1)\n break\n case 'Home':\n nextIndex = firstEnabled()\n break\n case 'End':\n nextIndex = lastEnabled()\n break\n default:\n return\n }\n\n if (nextIndex < 0)\n return\n\n event.preventDefault()\n void selectByIndex(nextIndex, true)\n}\n\nfunction onClick(tab: GrTab): void {\n if (!isEnabled(tab))\n return\n if (tab.value !== props.modelValue)\n emit('update:modelValue', tab.value)\n}\n</script>\n\n<template>\n <div\n role=\"tablist\"\n data-ds-tabs\n class=\"inline-flex flex-wrap gap-1 rounded-[var(--ds-radius-lg)] border border-[var(--brd)] bg-[var(--muted)] p-1\"\n @keydown=\"onKeydown\"\n >\n <button\n v-for=\"(tab, index) in tabs\"\n :key=\"tab.value\"\n :ref=\"el => setButtonRef(el, index)\"\n type=\"button\"\n role=\"tab\"\n data-ds-tab\n :aria-selected=\"tab.value === modelValue ? 'true' : 'false'\"\n :aria-disabled=\"tab.disabled ? 'true' : undefined\"\n :disabled=\"tab.disabled\"\n :tabindex=\"tab.value === modelValue ? 0 : -1\"\n class=\"h-9 px-3 rounded-[10px] text-sm font-600 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ring)] disabled:opacity-60 disabled:cursor-not-allowed\"\n :class=\"tab.value === modelValue\n ? 'bg-[var(--card)] text-[var(--fg)] border border-[var(--brd)]'\n : 'text-[var(--muted-fg)] hover:text-[var(--fg)] hover:bg-[color-mix(in_srgb,var(--card)_70%,transparent)]'\"\n @click=\"onClick(tab)\"\n >\n <span class=\"inline-flex items-center gap-2\">\n <span>{{ tab.label }}</span>\n <span\n v-if=\"tab.badge\"\n class=\"text-[11px] px-1.5 py-0.5 rounded-full bg-[var(--secondary)] text-[var(--secondary-fg)]\"\n >\n {{ tab.badge }}\n </span>\n </span>\n </button>\n </div>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;EA0BA,MAAM,QAAQ;EAEd,MAAM,OAAO;EAIb,MAAM,aAAa,IAAyB,EAAE,CAAA;EAE9C,SAAS,aAAa,IAAa,OAAqB;AACtD,OAAI,cAAc,kBAChB,YAAW,MAAM,SAAS;;EAG9B,MAAM,cAAc,eAAe,MAAM,KAAK,WAAU,MAAK,EAAE,UAAU,MAAM,WAAW,CAAA;EAE1F,SAAS,UAAU,KAAqB;AACtC,UAAO,CAAC,IAAI;;EAGd,eAAe,cAAc,OAAe,QAAQ,OAAsB;GACxE,MAAM,MAAM,MAAM,KAAK;AACvB,OAAI,CAAC,OAAO,CAAC,UAAU,IAAI,CACzB;AACF,OAAI,IAAI,UAAU,MAAM,WACtB,MAAK,qBAAqB,IAAI,MAAK;AACrC,OAAI,OAAO;AACT,UAAM,UAAS;AACf,eAAW,MAAM,QAAQ,OAAM;;;EAInC,SAAS,gBAAgB,MAAc,WAA2B;GAChE,MAAM,SAAS,MAAM,KAAK;AAC1B,OAAI,WAAW,EACb,QAAO;GACT,IAAI,QAAQ;AACZ,QAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,KAAK;AAC/B,aAAS,QAAQ,YAAY,UAAU;IACvC,MAAM,MAAM,MAAM,KAAK;AACvB,QAAI,OAAO,UAAU,IAAI,CACvB,QAAO;;AAEX,UAAO;;EAGT,SAAS,eAAuB;AAC9B,UAAO,MAAM,KAAK,UAAU,UAAS;;EAGvC,SAAS,cAAsB;AAC7B,QAAK,IAAI,IAAI,MAAM,KAAK,SAAS,GAAG,KAAK,GAAG,KAAK;IAC/C,MAAM,MAAM,MAAM,KAAK;AACvB,QAAI,OAAO,UAAU,IAAI,CACvB,QAAO;;AAEX,UAAO;;EAGT,SAAS,UAAU,OAA4B;AAC7C,OAAI,MAAM,KAAK,WAAW,EACxB;GAEF,MAAM,eAAe,YAAY,QAAQ,IAAI,IAAI,YAAY;GAC7D,IAAI,YAAY;AAEhB,WAAQ,MAAM,KAAd;IACE,KAAK;AACH,iBAAY,gBAAgB,cAAc,EAAC;AAC3C;IACF,KAAK;AACH,iBAAY,gBAAgB,cAAc,GAAE;AAC5C;IACF,KAAK;AACH,iBAAY,cAAa;AACzB;IACF,KAAK;AACH,iBAAY,aAAY;AACxB;IACF,QACE;;AAGJ,OAAI,YAAY,EACd;AAEF,SAAM,gBAAe;AAChB,iBAAc,WAAW,KAAI;;EAGpC,SAAS,QAAQ,KAAkB;AACjC,OAAI,CAAC,UAAU,IAAI,CACjB;AACF,OAAI,IAAI,UAAU,MAAM,WACtB,MAAK,qBAAqB,IAAI,MAAK;;;uBAKrC,mBAiCM,OAAA;IAhCJ,MAAK;IACL,gBAAA;IACA,OAAM;IACI;yBAEV,mBA0BS,UAAA,MAAA,WAzBgB,QAAA,OAAf,KAAK,UAAK;wBADpB,mBA0BS,UAAA;KAxBN,KAAK,IAAI;;KACT,MAAK,OAAM,aAAa,IAAI,MAAK;KAClC,MAAK;KACL,MAAK;KACL,eAAA;KACC,iBAAe,IAAI,UAAU,QAAA,aAAU,SAAA;KACvC,iBAAe,IAAI,WAAQ,SAAY,KAAA;KACvC,UAAU,IAAI;KACd,UAAU,IAAI,UAAU,QAAA,aAAU,IAAA;KACnC,OAAK,eAAA,CAAC,uLACE,IAAI,UAAU,QAAA,aAAA,iEAAA,0GAAA,CAAA;KAGrB,UAAK,WAAE,QAAQ,IAAG;QAEnB,mBAQO,QARP,YAQO,CAPL,mBAA4B,QAAA,MAAA,gBAAnB,IAAI,MAAK,EAAA,EAAA,EAEV,IAAI,SAAA,WAAA,EADZ,mBAKO,QALP,YAKO,gBADF,IAAI,MAAK,EAAA,EAAA,IAAA,mBAAA,IAAA,KAAA,CAAA,CAAA,CAAA,EAAA,IAAA,WAAA"}
|
|
1
|
+
{"version":3,"file":"GrTabs-CiGmcmvm.js","names":[],"sources":["../../../../src/components/GrTabs/GrTabs.vue","../../../../src/components/GrTabs/GrTabs.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, nextTick, ref } from 'vue'\n\nexport type GrTab = {\n value: string\n label: string\n badge?: string\n disabled?: boolean\n}\n\nexport type GrTabsProps = {\n modelValue: string\n tabs: GrTab[]\n}\n\n/**\n * GrTabs — горизонтальная группа вкладок с паттерном WAI-ARIA `tablist`.\n *\n * A11y:\n * - `role=\"tablist\"` на корне, `role=\"tab\"` на каждой вкладке.\n * - Roving `tabindex`: активная вкладка `0`, остальные `-1`.\n * - Клавиатура: `ArrowLeft`/`ArrowRight` — цикличный переход, `Home`/`End` — к первой/последней.\n * - При переключении стрелками DOM-фокус программно переносится на новую вкладку.\n *\n * Сам компонент не рендерит `tabpanel` — это ответственность консьюмера (по `aria-controls`/внешней разметке).\n */\nconst props = defineProps<GrTabsProps>()\n\nconst emit = defineEmits<{\n (e: 'update:modelValue', value: string): void\n}>()\n\nconst buttonRefs = ref<HTMLButtonElement[]>([])\n\nfunction setButtonRef(el: unknown, index: number): void {\n if (el instanceof HTMLButtonElement)\n buttonRefs.value[index] = el\n}\n\nconst activeIndex = computed(() => props.tabs.findIndex(t => t.value === props.modelValue))\n\nfunction isEnabled(tab: GrTab): boolean {\n return !tab.disabled\n}\n\nasync function selectByIndex(index: number, focus = false): Promise<void> {\n const tab = props.tabs[index]\n if (!tab || !isEnabled(tab))\n return\n if (tab.value !== props.modelValue)\n emit('update:modelValue', tab.value)\n if (focus) {\n await nextTick()\n buttonRefs.value[index]?.focus()\n }\n}\n\nfunction findNextEnabled(from: number, direction: 1 | -1): number {\n const length = props.tabs.length\n if (length === 0)\n return -1\n let index = from\n for (let i = 0; i < length; i++) {\n index = (index + direction + length) % length\n const tab = props.tabs[index]\n if (tab && isEnabled(tab))\n return index\n }\n return -1\n}\n\nfunction firstEnabled(): number {\n return props.tabs.findIndex(isEnabled)\n}\n\nfunction lastEnabled(): number {\n for (let i = props.tabs.length - 1; i >= 0; i--) {\n const tab = props.tabs[i]\n if (tab && isEnabled(tab))\n return i\n }\n return -1\n}\n\nfunction onKeydown(event: KeyboardEvent): void {\n if (props.tabs.length === 0)\n return\n\n const currentIndex = activeIndex.value < 0 ? 0 : activeIndex.value\n let nextIndex = -1\n\n switch (event.key) {\n case 'ArrowRight':\n nextIndex = findNextEnabled(currentIndex, 1)\n break\n case 'ArrowLeft':\n nextIndex = findNextEnabled(currentIndex, -1)\n break\n case 'Home':\n nextIndex = firstEnabled()\n break\n case 'End':\n nextIndex = lastEnabled()\n break\n default:\n return\n }\n\n if (nextIndex < 0)\n return\n\n event.preventDefault()\n void selectByIndex(nextIndex, true)\n}\n\nfunction onClick(tab: GrTab): void {\n if (!isEnabled(tab))\n return\n if (tab.value !== props.modelValue)\n emit('update:modelValue', tab.value)\n}\n</script>\n\n<template>\n <div\n role=\"tablist\"\n data-ds-tabs\n class=\"inline-flex flex-wrap gap-1 rounded-[var(--ds-radius-lg)] border border-[var(--brd)] bg-[var(--muted)] p-1\"\n @keydown=\"onKeydown\"\n >\n <button\n v-for=\"(tab, index) in tabs\"\n :key=\"tab.value\"\n :ref=\"el => setButtonRef(el, index)\"\n type=\"button\"\n role=\"tab\"\n data-ds-tab\n :aria-selected=\"tab.value === modelValue ? 'true' : 'false'\"\n :aria-disabled=\"tab.disabled ? 'true' : undefined\"\n :disabled=\"tab.disabled\"\n :tabindex=\"tab.value === modelValue ? 0 : -1\"\n class=\"h-9 px-3 rounded-[10px] text-sm font-600 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ring)] disabled:opacity-60 disabled:cursor-not-allowed\"\n :class=\"tab.value === modelValue\n ? 'bg-[var(--card)] text-[var(--fg)] border border-[var(--brd)]'\n : 'text-[var(--muted-fg)] hover:text-[var(--fg)] hover:bg-[color-mix(in_srgb,var(--card)_70%,transparent)]'\"\n @click=\"onClick(tab)\"\n >\n <span class=\"inline-flex items-center gap-2\">\n <span>{{ tab.label }}</span>\n <span\n v-if=\"tab.badge\"\n class=\"text-[11px] px-1.5 py-0.5 rounded-full bg-[var(--secondary)] text-[var(--secondary-fg)]\"\n >\n {{ tab.badge }}\n </span>\n </span>\n </button>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, nextTick, ref } from 'vue'\n\nexport type GrTab = {\n value: string\n label: string\n badge?: string\n disabled?: boolean\n}\n\nexport type GrTabsProps = {\n modelValue: string\n tabs: GrTab[]\n}\n\n/**\n * GrTabs — горизонтальная группа вкладок с паттерном WAI-ARIA `tablist`.\n *\n * A11y:\n * - `role=\"tablist\"` на корне, `role=\"tab\"` на каждой вкладке.\n * - Roving `tabindex`: активная вкладка `0`, остальные `-1`.\n * - Клавиатура: `ArrowLeft`/`ArrowRight` — цикличный переход, `Home`/`End` — к первой/последней.\n * - При переключении стрелками DOM-фокус программно переносится на новую вкладку.\n *\n * Сам компонент не рендерит `tabpanel` — это ответственность консьюмера (по `aria-controls`/внешней разметке).\n */\nconst props = defineProps<GrTabsProps>()\n\nconst emit = defineEmits<{\n (e: 'update:modelValue', value: string): void\n}>()\n\nconst buttonRefs = ref<HTMLButtonElement[]>([])\n\nfunction setButtonRef(el: unknown, index: number): void {\n if (el instanceof HTMLButtonElement)\n buttonRefs.value[index] = el\n}\n\nconst activeIndex = computed(() => props.tabs.findIndex(t => t.value === props.modelValue))\n\nfunction isEnabled(tab: GrTab): boolean {\n return !tab.disabled\n}\n\nasync function selectByIndex(index: number, focus = false): Promise<void> {\n const tab = props.tabs[index]\n if (!tab || !isEnabled(tab))\n return\n if (tab.value !== props.modelValue)\n emit('update:modelValue', tab.value)\n if (focus) {\n await nextTick()\n buttonRefs.value[index]?.focus()\n }\n}\n\nfunction findNextEnabled(from: number, direction: 1 | -1): number {\n const length = props.tabs.length\n if (length === 0)\n return -1\n let index = from\n for (let i = 0; i < length; i++) {\n index = (index + direction + length) % length\n const tab = props.tabs[index]\n if (tab && isEnabled(tab))\n return index\n }\n return -1\n}\n\nfunction firstEnabled(): number {\n return props.tabs.findIndex(isEnabled)\n}\n\nfunction lastEnabled(): number {\n for (let i = props.tabs.length - 1; i >= 0; i--) {\n const tab = props.tabs[i]\n if (tab && isEnabled(tab))\n return i\n }\n return -1\n}\n\nfunction onKeydown(event: KeyboardEvent): void {\n if (props.tabs.length === 0)\n return\n\n const currentIndex = activeIndex.value < 0 ? 0 : activeIndex.value\n let nextIndex = -1\n\n switch (event.key) {\n case 'ArrowRight':\n nextIndex = findNextEnabled(currentIndex, 1)\n break\n case 'ArrowLeft':\n nextIndex = findNextEnabled(currentIndex, -1)\n break\n case 'Home':\n nextIndex = firstEnabled()\n break\n case 'End':\n nextIndex = lastEnabled()\n break\n default:\n return\n }\n\n if (nextIndex < 0)\n return\n\n event.preventDefault()\n void selectByIndex(nextIndex, true)\n}\n\nfunction onClick(tab: GrTab): void {\n if (!isEnabled(tab))\n return\n if (tab.value !== props.modelValue)\n emit('update:modelValue', tab.value)\n}\n</script>\n\n<template>\n <div\n role=\"tablist\"\n data-ds-tabs\n class=\"inline-flex flex-wrap gap-1 rounded-[var(--ds-radius-lg)] border border-[var(--brd)] bg-[var(--muted)] p-1\"\n @keydown=\"onKeydown\"\n >\n <button\n v-for=\"(tab, index) in tabs\"\n :key=\"tab.value\"\n :ref=\"el => setButtonRef(el, index)\"\n type=\"button\"\n role=\"tab\"\n data-ds-tab\n :aria-selected=\"tab.value === modelValue ? 'true' : 'false'\"\n :aria-disabled=\"tab.disabled ? 'true' : undefined\"\n :disabled=\"tab.disabled\"\n :tabindex=\"tab.value === modelValue ? 0 : -1\"\n class=\"h-9 px-3 rounded-[10px] text-sm font-600 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ring)] disabled:opacity-60 disabled:cursor-not-allowed\"\n :class=\"tab.value === modelValue\n ? 'bg-[var(--card)] text-[var(--fg)] border border-[var(--brd)]'\n : 'text-[var(--muted-fg)] hover:text-[var(--fg)] hover:bg-[color-mix(in_srgb,var(--card)_70%,transparent)]'\"\n @click=\"onClick(tab)\"\n >\n <span class=\"inline-flex items-center gap-2\">\n <span>{{ tab.label }}</span>\n <span\n v-if=\"tab.badge\"\n class=\"text-[11px] px-1.5 py-0.5 rounded-full bg-[var(--secondary)] text-[var(--secondary-fg)]\"\n >\n {{ tab.badge }}\n </span>\n </span>\n </button>\n </div>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;EA0BA,MAAM,QAAQ;EAEd,MAAM,OAAO;EAIb,MAAM,aAAa,IAAyB,EAAE,CAAA;EAE9C,SAAS,aAAa,IAAa,OAAqB;AACtD,OAAI,cAAc,kBAChB,YAAW,MAAM,SAAS;;EAG9B,MAAM,cAAc,eAAe,MAAM,KAAK,WAAU,MAAK,EAAE,UAAU,MAAM,WAAW,CAAA;EAE1F,SAAS,UAAU,KAAqB;AACtC,UAAO,CAAC,IAAI;;EAGd,eAAe,cAAc,OAAe,QAAQ,OAAsB;GACxE,MAAM,MAAM,MAAM,KAAK;AACvB,OAAI,CAAC,OAAO,CAAC,UAAU,IAAI,CACzB;AACF,OAAI,IAAI,UAAU,MAAM,WACtB,MAAK,qBAAqB,IAAI,MAAK;AACrC,OAAI,OAAO;AACT,UAAM,UAAS;AACf,eAAW,MAAM,QAAQ,OAAM;;;EAInC,SAAS,gBAAgB,MAAc,WAA2B;GAChE,MAAM,SAAS,MAAM,KAAK;AAC1B,OAAI,WAAW,EACb,QAAO;GACT,IAAI,QAAQ;AACZ,QAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,KAAK;AAC/B,aAAS,QAAQ,YAAY,UAAU;IACvC,MAAM,MAAM,MAAM,KAAK;AACvB,QAAI,OAAO,UAAU,IAAI,CACvB,QAAO;;AAEX,UAAO;;EAGT,SAAS,eAAuB;AAC9B,UAAO,MAAM,KAAK,UAAU,UAAS;;EAGvC,SAAS,cAAsB;AAC7B,QAAK,IAAI,IAAI,MAAM,KAAK,SAAS,GAAG,KAAK,GAAG,KAAK;IAC/C,MAAM,MAAM,MAAM,KAAK;AACvB,QAAI,OAAO,UAAU,IAAI,CACvB,QAAO;;AAEX,UAAO;;EAGT,SAAS,UAAU,OAA4B;AAC7C,OAAI,MAAM,KAAK,WAAW,EACxB;GAEF,MAAM,eAAe,YAAY,QAAQ,IAAI,IAAI,YAAY;GAC7D,IAAI,YAAY;AAEhB,WAAQ,MAAM,KAAd;IACE,KAAK;AACH,iBAAY,gBAAgB,cAAc,EAAC;AAC3C;IACF,KAAK;AACH,iBAAY,gBAAgB,cAAc,GAAE;AAC5C;IACF,KAAK;AACH,iBAAY,cAAa;AACzB;IACF,KAAK;AACH,iBAAY,aAAY;AACxB;IACF,QACE;;AAGJ,OAAI,YAAY,EACd;AAEF,SAAM,gBAAe;AAChB,iBAAc,WAAW,KAAI;;EAGpC,SAAS,QAAQ,KAAkB;AACjC,OAAI,CAAC,UAAU,IAAI,CACjB;AACF,OAAI,IAAI,UAAU,MAAM,WACtB,MAAK,qBAAqB,IAAI,MAAK;;;uBAKrC,mBAiCM,OAAA;IAhCJ,MAAK;IACL,gBAAA;IACA,OAAM;IACI;yBAEV,mBA0BS,UAAA,MAAA,WAzBgB,QAAA,OAAf,KAAK,UAAK;wBADpB,mBA0BS,UAAA;KAxBN,KAAK,IAAI;;KACT,MAAK,OAAM,aAAa,IAAI,MAAK;KAClC,MAAK;KACL,MAAK;KACL,eAAA;KACC,iBAAe,IAAI,UAAU,QAAA,aAAU,SAAA;KACvC,iBAAe,IAAI,WAAQ,SAAY,KAAA;KACvC,UAAU,IAAI;KACd,UAAU,IAAI,UAAU,QAAA,aAAU,IAAA;KACnC,OAAK,eAAA,CAAC,uLACE,IAAI,UAAU,QAAA,aAAA,iEAAA,0GAAA,CAAA;KAGrB,UAAK,WAAE,QAAQ,IAAG;QAEnB,mBAQO,QARP,YAQO,CAPL,mBAA4B,QAAA,MAAA,gBAAnB,IAAI,MAAK,EAAA,EAAA,EAEV,IAAI,SAAA,WAAA,EADZ,mBAKO,QALP,YAKO,gBADF,IAAI,MAAK,EAAA,EAAA,IAAA,mBAAA,IAAA,KAAA,CAAA,CAAA,CAAA,EAAA,IAAA,WAAA"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { t as GrTabs_default } from "./chunks/GrTabs-
|
|
2
|
-
import { t as grTabsConfig } from "../../chunks/config-
|
|
1
|
+
import { t as GrTabs_default } from "./chunks/GrTabs-CiGmcmvm.js";
|
|
2
|
+
import { t as grTabsConfig } from "../../chunks/config-DDLoUczC.js";
|
|
3
3
|
export { GrTabs_default as GrTabs, GrTabs_default as default, grTabsConfig };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as grTextareaClass } from "../../../chunks/config-
|
|
1
|
+
import { r as grTextareaClass } from "../../../chunks/config-DxqMXzNV2.js";
|
|
2
2
|
import { computed, createElementBlock, defineComponent, normalizeClass, openBlock } from "vue";
|
|
3
3
|
//#region src/components/GrTextarea/GrTextarea.vue?vue&type=script&setup=true&lang.ts
|
|
4
4
|
var _hoisted_1 = [
|
package/dist/components/GrTextarea/chunks/{GrTextarea-DFS9ZkrA.js.map → GrTextarea-VBSc6A7D.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GrTextarea-
|
|
1
|
+
{"version":3,"file":"GrTextarea-VBSc6A7D.js","names":[],"sources":["../../../../src/components/GrTextarea/GrTextarea.vue","../../../../src/components/GrTextarea/GrTextarea.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * GrTextarea — многострочное поле ввода DS-примитива.\n *\n * Состояния:\n * - `state`: визуальный оттенок рамки (`default | success | warning | danger`).\n * - `invalid`: форсирует `danger`-состояние и проставляет `aria-invalid=\"true\"`.\n */\nimport { computed } from 'vue'\nimport { type GrTextareaState, grTextareaClass } from './grTextareaStyles'\n\nconst props = withDefaults(defineProps<{\n modelValue: string\n placeholder?: string\n autocomplete?: string\n disabled?: boolean\n invalid?: boolean\n state?: GrTextareaState\n name?: string\n id?: string\n rows?: number\n}>(), {\n placeholder: undefined,\n autocomplete: undefined,\n disabled: false,\n invalid: false,\n state: 'default',\n name: undefined,\n id: undefined,\n rows: 4,\n})\n\nconst emit = defineEmits<{\n (e: 'update:modelValue', value: string): void\n}>()\n\nexport type GrTextareaProps = typeof props\n\nconst className = computed(() => grTextareaClass({\n state: props.state,\n invalid: props.invalid,\n}))\n\nfunction onInput(e: Event): void {\n emit('update:modelValue', (e.target as HTMLTextAreaElement).value)\n}\n</script>\n\n<template>\n <textarea\n :id=\"id\"\n data-ds-textarea\n :name=\"name\"\n :rows=\"rows\"\n :autocomplete=\"autocomplete\"\n :placeholder=\"placeholder\"\n :disabled=\"disabled\"\n :value=\"modelValue\"\n :aria-invalid=\"invalid ? 'true' : undefined\"\n class=\"w-full rounded-md border bg-[var(--bg)] px-3 py-2 text-[14px] text-[var(--fg)] placeholder:text-[var(--muted-fg)] transition-colors duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ring)] disabled:opacity-50 disabled:cursor-not-allowed\"\n :class=\"className\"\n @input=\"onInput\"\n />\n</template>\n","<script setup lang=\"ts\">\n/**\n * GrTextarea — многострочное поле ввода DS-примитива.\n *\n * Состояния:\n * - `state`: визуальный оттенок рамки (`default | success | warning | danger`).\n * - `invalid`: форсирует `danger`-состояние и проставляет `aria-invalid=\"true\"`.\n */\nimport { computed } from 'vue'\nimport { type GrTextareaState, grTextareaClass } from './grTextareaStyles'\n\nconst props = withDefaults(defineProps<{\n modelValue: string\n placeholder?: string\n autocomplete?: string\n disabled?: boolean\n invalid?: boolean\n state?: GrTextareaState\n name?: string\n id?: string\n rows?: number\n}>(), {\n placeholder: undefined,\n autocomplete: undefined,\n disabled: false,\n invalid: false,\n state: 'default',\n name: undefined,\n id: undefined,\n rows: 4,\n})\n\nconst emit = defineEmits<{\n (e: 'update:modelValue', value: string): void\n}>()\n\nexport type GrTextareaProps = typeof props\n\nconst className = computed(() => grTextareaClass({\n state: props.state,\n invalid: props.invalid,\n}))\n\nfunction onInput(e: Event): void {\n emit('update:modelValue', (e.target as HTMLTextAreaElement).value)\n}\n</script>\n\n<template>\n <textarea\n :id=\"id\"\n data-ds-textarea\n :name=\"name\"\n :rows=\"rows\"\n :autocomplete=\"autocomplete\"\n :placeholder=\"placeholder\"\n :disabled=\"disabled\"\n :value=\"modelValue\"\n :aria-invalid=\"invalid ? 'true' : undefined\"\n class=\"w-full rounded-md border bg-[var(--bg)] px-3 py-2 text-[14px] text-[var(--fg)] placeholder:text-[var(--muted-fg)] transition-colors duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ring)] disabled:opacity-50 disabled:cursor-not-allowed\"\n :class=\"className\"\n @input=\"onInput\"\n />\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAWA,MAAM,QAAQ;EAqBd,MAAM,OAAO;EAMb,MAAM,YAAY,eAAe,gBAAgB;GAC/C,OAAO,MAAM;GACb,SAAS,MAAM;GAChB,CAAC,CAAA;EAEF,SAAS,QAAQ,GAAgB;AAC/B,QAAK,qBAAsB,EAAE,OAA+B,MAAK;;;uBAKjE,mBAaE,YAAA;IAZC,IAAI,QAAA;IACL,oBAAA;IACC,MAAM,QAAA;IACN,MAAM,QAAA;IACN,cAAc,QAAA;IACd,aAAa,QAAA;IACb,UAAU,QAAA;IACV,OAAO,QAAA;IACP,gBAAc,QAAA,UAAO,SAAY,KAAA;IAClC,OAAK,eAAA,CAAC,6QACE,UAAA,MAAS,CAAA;IACT"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { n as grTextareaSafelist, t as grTextareaConfig } from "../../chunks/config-
|
|
2
|
-
import { t as GrTextarea_default } from "./chunks/GrTextarea-
|
|
1
|
+
import { n as grTextareaSafelist, t as grTextareaConfig } from "../../chunks/config-DxqMXzNV2.js";
|
|
2
|
+
import { t as GrTextarea_default } from "./chunks/GrTextarea-VBSc6A7D.js";
|
|
3
3
|
export { GrTextarea_default as GrTextarea, GrTextarea_default as default, grTextareaConfig, grTextareaSafelist };
|
|
@@ -4,7 +4,7 @@ import { t as x_default } from "../../../chunks/x-Pa6C2hwi.js";
|
|
|
4
4
|
import { t as GrButton_default } from "../../GrButton/chunks/GrButton-BmGdp8-4.js";
|
|
5
5
|
import { t as GrIcon_default } from "../../GrIcon/chunks/GrIcon-BV7RBU_0.js";
|
|
6
6
|
import { useToast } from "../../../composables/useToast.js";
|
|
7
|
-
import { r as PLACEMENT_CLASS } from "../../../chunks/config-
|
|
7
|
+
import { r as PLACEMENT_CLASS } from "../../../chunks/config-CxCAfXZ1.js";
|
|
8
8
|
import { Fragment, Teleport, TransitionGroup, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, normalizeClass, normalizeStyle, openBlock, renderList, resolveDynamicComponent, toDisplayString, unref, withCtx } from "vue";
|
|
9
9
|
//#region src/components/GrToaster/GrToaster.vue?vue&type=script&setup=true&lang.ts
|
|
10
10
|
var _hoisted_1 = ["aria-label"];
|
package/dist/components/GrToaster/chunks/{GrToaster-3Mf0Moxt.js.map → GrToaster-I1e-zCpf.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GrToaster-3Mf0Moxt.js","names":[],"sources":["../../../../src/components/GrToaster/GrToaster.vue","../../../../src/components/GrToaster/GrToaster.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * GrToaster — DS-примитив области уведомлений (toast).\n *\n * Модель состояния берётся из `useToast` (модульный синглтон), поэтому\n * в приложении предполагается **один активный `GrToaster`** на корень.\n *\n * A11y:\n * - контейнер — `role=\"region\"` с `aria-label` (i18n-проп `regionLabel`);\n * - каждый toast — `role=\"status\"` + `aria-live=\"polite\"` для `info`/`success`\n * и `role=\"alert\"` + `aria-live=\"assertive\"` для `warning`/`danger`\n * (критичные сообщения должны прерывать скринридер).\n *\n * UX:\n * - появление/исчезновение анимируется `TransitionGroup`;\n * - `placement` настраивает угол экрана;\n * - `z-[60]` — выше, чем `GrModal` (`z-50`), чтобы уведомления не перекрывались диалогом.\n */\nimport {computed} from 'vue'\nimport type {Component} from 'vue'\n\nimport {useToast} from '../../composables/useToast'\nimport type {GrToastTone} from '../../composables/useToast'\nimport GrButton from '../GrButton'\nimport GrIcon from '../GrIcon'\nimport {PLACEMENT_CLASS, type GrToasterPlacement} from './grToasterStyles'\n\nimport IconCheck from '~icons/lucide/check-circle'\nimport IconWarning from '~icons/lucide/alert-triangle'\nimport IconError from '~icons/lucide/x-circle'\nimport IconInfo from '~icons/lucide/info'\nimport IconClose from '~icons/lucide/x'\n\nexport type {GrToasterPlacement}\n\ninterface ToneMeta {\n icon: Component\n color: string\n role: 'status' | 'alert'\n live: 'polite' | 'assertive'\n}\n\n// Единый маппинг по tone'у: иконка, цвет, a11y-роль и «напор» live-региона.\n// На уровне модуля — чтобы не пересоздавать объект на каждый рендер.\nconst TONE_META: Record<GrToastTone, ToneMeta> = {\n primary: {icon: IconInfo, color: 'var(--primary)', role: 'status', live: 'polite'},\n neutral: {icon: IconInfo, color: 'var(--muted-fg)', role: 'status', live: 'polite'},\n info: {icon: IconInfo, color: 'var(--ds-info)', role: 'status', live: 'polite'},\n success: {icon: IconCheck, color: 'var(--ds-success)', role: 'status', live: 'polite'},\n warning: {icon: IconWarning, color: 'var(--ds-warning)', role: 'alert', live: 'assertive'},\n danger: {icon: IconError, color: 'var(--ds-danger)', role: 'alert', live: 'assertive'},\n slate: {icon: IconInfo, color: 'var(--ds-slate)', role: 'status', live: 'polite'},\n azure: {icon: IconInfo, color: 'var(--ds-azure)', role: 'status', live: 'polite'},\n}\n\n\nexport interface GrToasterProps {\n /** Угол экрана для стека уведомлений. */\n placement?: GrToasterPlacement\n /** A11y-лейбл кнопки закрытия (i18n). */\n dismissLabel?: string\n /** A11y-лейбл контейнера-региона (i18n). */\n regionLabel?: string\n}\n\nconst props = withDefaults(defineProps<GrToasterProps>(), {\n placement: 'top-right',\n dismissLabel: 'Dismiss',\n regionLabel: 'Notifications',\n})\n\nconst {list, dismiss} = useToast()\n\n// SSR-guard: на сервере `document.body` недоступен — отключаем `teleport`.\nconst isClient = typeof window !== 'undefined'\n\nfunction metaFor(tone: GrToastTone): ToneMeta {\n return TONE_META[tone] ?? TONE_META.info\n}\n\nconst containerClass = computed(() => PLACEMENT_CLASS[props.placement])\n</script>\n\n<template>\n <teleport to=\"body\" :disabled=\"!isClient\">\n <div\n data-ds-toaster\n role=\"region\"\n :aria-label=\"regionLabel\"\n class=\"fixed z-[60] grid w-[360px] max-w-[calc(100vw-2rem)] gap-3\"\n :class=\"containerClass\"\n >\n <TransitionGroup\n tag=\"div\"\n class=\"grid gap-3\"\n enter-active-class=\"transition duration-200 ease-out\"\n enter-from-class=\"opacity-0 translate-y-2\"\n enter-to-class=\"opacity-100 translate-y-0\"\n leave-active-class=\"transition duration-150 ease-in absolute\"\n leave-from-class=\"opacity-100 translate-y-0\"\n leave-to-class=\"opacity-0 translate-y-2\"\n move-class=\"transition-transform duration-200 ease-out\"\n >\n <div\n v-for=\"toast in list\"\n :key=\"toast.id\"\n data-ds-toast\n :data-tone=\"toast.tone\"\n :role=\"metaFor(toast.tone).role\"\n :aria-live=\"metaFor(toast.tone).live\"\n aria-atomic=\"true\"\n class=\"rounded-[var(--ds-radius-lg)] border border-[var(--brd)] bg-[var(--card)] px-4 py-3 shadow-[var(--ds-shadow-2)]\"\n >\n <div class=\"flex items-start gap-3\">\n <GrIcon size=\"md\" class=\"mt-0.5\" :style=\"{ color: metaFor(toast.tone).color }\" aria-hidden=\"true\">\n <component :is=\"metaFor(toast.tone).icon\" />\n </GrIcon>\n <div class=\"min-w-0 flex-1\">\n <div class=\"text-[13px] font-700\">\n {{ toast.title }}\n </div>\n <div v-if=\"toast.message\" class=\"mt-0.5 text-[13px] text-[var(--muted-fg)]\">\n {{ toast.message }}\n </div>\n </div>\n <GrButton\n variant=\"ghost\"\n size=\"sm\"\n square\n :aria-label=\"dismissLabel\"\n @click=\"dismiss(toast.id)\"\n >\n <GrIcon size=\"sm\" aria-hidden=\"true\">\n <IconClose />\n </GrIcon>\n </GrButton>\n </div>\n </div>\n </TransitionGroup>\n </div>\n </teleport>\n</template>\n","<script setup lang=\"ts\">\n/**\n * GrToaster — DS-примитив области уведомлений (toast).\n *\n * Модель состояния берётся из `useToast` (модульный синглтон), поэтому\n * в приложении предполагается **один активный `GrToaster`** на корень.\n *\n * A11y:\n * - контейнер — `role=\"region\"` с `aria-label` (i18n-проп `regionLabel`);\n * - каждый toast — `role=\"status\"` + `aria-live=\"polite\"` для `info`/`success`\n * и `role=\"alert\"` + `aria-live=\"assertive\"` для `warning`/`danger`\n * (критичные сообщения должны прерывать скринридер).\n *\n * UX:\n * - появление/исчезновение анимируется `TransitionGroup`;\n * - `placement` настраивает угол экрана;\n * - `z-[60]` — выше, чем `GrModal` (`z-50`), чтобы уведомления не перекрывались диалогом.\n */\nimport {computed} from 'vue'\nimport type {Component} from 'vue'\n\nimport {useToast} from '../../composables/useToast'\nimport type {GrToastTone} from '../../composables/useToast'\nimport GrButton from '../GrButton'\nimport GrIcon from '../GrIcon'\nimport {PLACEMENT_CLASS, type GrToasterPlacement} from './grToasterStyles'\n\nimport IconCheck from '~icons/lucide/check-circle'\nimport IconWarning from '~icons/lucide/alert-triangle'\nimport IconError from '~icons/lucide/x-circle'\nimport IconInfo from '~icons/lucide/info'\nimport IconClose from '~icons/lucide/x'\n\nexport type {GrToasterPlacement}\n\ninterface ToneMeta {\n icon: Component\n color: string\n role: 'status' | 'alert'\n live: 'polite' | 'assertive'\n}\n\n// Единый маппинг по tone'у: иконка, цвет, a11y-роль и «напор» live-региона.\n// На уровне модуля — чтобы не пересоздавать объект на каждый рендер.\nconst TONE_META: Record<GrToastTone, ToneMeta> = {\n primary: {icon: IconInfo, color: 'var(--primary)', role: 'status', live: 'polite'},\n neutral: {icon: IconInfo, color: 'var(--muted-fg)', role: 'status', live: 'polite'},\n info: {icon: IconInfo, color: 'var(--ds-info)', role: 'status', live: 'polite'},\n success: {icon: IconCheck, color: 'var(--ds-success)', role: 'status', live: 'polite'},\n warning: {icon: IconWarning, color: 'var(--ds-warning)', role: 'alert', live: 'assertive'},\n danger: {icon: IconError, color: 'var(--ds-danger)', role: 'alert', live: 'assertive'},\n slate: {icon: IconInfo, color: 'var(--ds-slate)', role: 'status', live: 'polite'},\n azure: {icon: IconInfo, color: 'var(--ds-azure)', role: 'status', live: 'polite'},\n}\n\n\nexport interface GrToasterProps {\n /** Угол экрана для стека уведомлений. */\n placement?: GrToasterPlacement\n /** A11y-лейбл кнопки закрытия (i18n). */\n dismissLabel?: string\n /** A11y-лейбл контейнера-региона (i18n). */\n regionLabel?: string\n}\n\nconst props = withDefaults(defineProps<GrToasterProps>(), {\n placement: 'top-right',\n dismissLabel: 'Dismiss',\n regionLabel: 'Notifications',\n})\n\nconst {list, dismiss} = useToast()\n\n// SSR-guard: на сервере `document.body` недоступен — отключаем `teleport`.\nconst isClient = typeof window !== 'undefined'\n\nfunction metaFor(tone: GrToastTone): ToneMeta {\n return TONE_META[tone] ?? TONE_META.info\n}\n\nconst containerClass = computed(() => PLACEMENT_CLASS[props.placement])\n</script>\n\n<template>\n <teleport to=\"body\" :disabled=\"!isClient\">\n <div\n data-ds-toaster\n role=\"region\"\n :aria-label=\"regionLabel\"\n class=\"fixed z-[60] grid w-[360px] max-w-[calc(100vw-2rem)] gap-3\"\n :class=\"containerClass\"\n >\n <TransitionGroup\n tag=\"div\"\n class=\"grid gap-3\"\n enter-active-class=\"transition duration-200 ease-out\"\n enter-from-class=\"opacity-0 translate-y-2\"\n enter-to-class=\"opacity-100 translate-y-0\"\n leave-active-class=\"transition duration-150 ease-in absolute\"\n leave-from-class=\"opacity-100 translate-y-0\"\n leave-to-class=\"opacity-0 translate-y-2\"\n move-class=\"transition-transform duration-200 ease-out\"\n >\n <div\n v-for=\"toast in list\"\n :key=\"toast.id\"\n data-ds-toast\n :data-tone=\"toast.tone\"\n :role=\"metaFor(toast.tone).role\"\n :aria-live=\"metaFor(toast.tone).live\"\n aria-atomic=\"true\"\n class=\"rounded-[var(--ds-radius-lg)] border border-[var(--brd)] bg-[var(--card)] px-4 py-3 shadow-[var(--ds-shadow-2)]\"\n >\n <div class=\"flex items-start gap-3\">\n <GrIcon size=\"md\" class=\"mt-0.5\" :style=\"{ color: metaFor(toast.tone).color }\" aria-hidden=\"true\">\n <component :is=\"metaFor(toast.tone).icon\" />\n </GrIcon>\n <div class=\"min-w-0 flex-1\">\n <div class=\"text-[13px] font-700\">\n {{ toast.title }}\n </div>\n <div v-if=\"toast.message\" class=\"mt-0.5 text-[13px] text-[var(--muted-fg)]\">\n {{ toast.message }}\n </div>\n </div>\n <GrButton\n variant=\"ghost\"\n size=\"sm\"\n square\n :aria-label=\"dismissLabel\"\n @click=\"dismiss(toast.id)\"\n >\n <GrIcon size=\"sm\" aria-hidden=\"true\">\n <IconClose />\n </GrIcon>\n </GrButton>\n </div>\n </div>\n </TransitionGroup>\n </div>\n </teleport>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4CA,MAAM,YAA2C;GAC/C,SAAS;IAAC,MAAM;IAAU,OAAO;IAAkB,MAAM;IAAU,MAAM;IAAS;GAClF,SAAS;IAAC,MAAM;IAAU,OAAO;IAAmB,MAAM;IAAU,MAAM;IAAS;GACnF,MAAM;IAAC,MAAM;IAAU,OAAO;IAAkB,MAAM;IAAU,MAAM;IAAS;GAC/E,SAAS;IAAC,MAAM;IAAW,OAAO;IAAqB,MAAM;IAAU,MAAM;IAAS;GACtF,SAAS;IAAC,MAAM;IAAa,OAAO;IAAqB,MAAM;IAAS,MAAM;IAAY;GAC1F,QAAQ;IAAC,MAAM;IAAW,OAAO;IAAoB,MAAM;IAAS,MAAM;IAAY;GACtF,OAAO;IAAC,MAAM;IAAU,OAAO;IAAmB,MAAM;IAAU,MAAM;IAAS;GACjF,OAAO;IAAC,MAAM;IAAU,OAAO;IAAmB,MAAM;IAAU,MAAM;IAAS;GACnF;EAYA,MAAM,QAAQ;EAMd,MAAM,EAAC,MAAM,YAAW,UAAS;EAGjC,MAAM,WAAW,OAAO,WAAW;EAEnC,SAAS,QAAQ,MAA6B;AAC5C,UAAO,UAAU,SAAS,UAAU;;EAGtC,MAAM,iBAAiB,eAAe,gBAAgB,MAAM,WAAU;;uBAIpE,YAwDW,UAAA;IAxDD,IAAG;IAAQ,UAAQ,CAAG;OAC9B,mBAsDM,OAAA;IArDF,mBAAA;IACA,MAAK;IACJ,cAAY,QAAA;IACb,OAAK,eAAA,CAAC,8DACE,eAAA,MAAc,CAAA;OAExB,YA8CkB,iBAAA;IA7Cd,KAAI;IACJ,OAAM;IACN,sBAAmB;IACnB,oBAAiB;IACjB,kBAAe;IACf,sBAAmB;IACnB,oBAAiB;IACjB,kBAAe;IACf,cAAW;;2BAGY,EAAA,UAAA,KAAA,EADzB,mBAkCM,UAAA,MAAA,WAjCc,MAAA,KAAI,GAAb,UAAK;yBADhB,mBAkCM,OAAA;MAhCD,KAAK,MAAM;MACZ,iBAAA;MACC,aAAW,MAAM;MACjB,MAAM,QAAQ,MAAM,KAAI,CAAE;MAC1B,aAAW,QAAQ,MAAM,KAAI,CAAE;MAChC,eAAY;MACZ,OAAM;SAER,mBAuBM,OAvBN,YAuBM;MAtBJ,YAES,MAAA,eAAA,EAAA;OAFD,MAAK;OAAK,OAAM;OAAU,OAAK,eAAA,EAAA,OAAW,QAAQ,MAAM,KAAI,CAAE,OAAK,CAAA;OAAI,eAAY;;8BAC7C,EAAA,WAAA,EAA5C,YAA4C,wBAA5B,QAAQ,MAAM,KAAI,CAAE,KAAI,CAAA,EAAA,CAAA;;;MAE1C,mBAOM,OAPN,YAOM,CANJ,mBAEM,OAFN,YAEM,gBADD,MAAM,MAAK,EAAA,EAAA,EAEL,MAAM,WAAA,WAAA,EAAjB,mBAEM,OAFN,YAEM,gBADD,MAAM,QAAO,EAAA,EAAA,IAAA,mBAAA,IAAA,KAAA,CAAA,CAAA;MAGpB,YAUW,MAAA,iBAAA,EAAA;OATP,SAAQ;OACR,MAAK;OACL,QAAA;OACC,cAAY,QAAA;OACZ,UAAK,WAAE,MAAA,QAAO,CAAC,MAAM,GAAE;;8BAIjB,CAFT,YAES,MAAA,eAAA,EAAA;QAFD,MAAK;QAAK,eAAY;;+BACf,CAAb,YAAa,MAAA,UAAA,CAAA,CAAA,CAAA"}
|
|
1
|
+
{"version":3,"file":"GrToaster-I1e-zCpf.js","names":[],"sources":["../../../../src/components/GrToaster/GrToaster.vue","../../../../src/components/GrToaster/GrToaster.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * GrToaster — DS-примитив области уведомлений (toast).\n *\n * Модель состояния берётся из `useToast` (модульный синглтон), поэтому\n * в приложении предполагается **один активный `GrToaster`** на корень.\n *\n * A11y:\n * - контейнер — `role=\"region\"` с `aria-label` (i18n-проп `regionLabel`);\n * - каждый toast — `role=\"status\"` + `aria-live=\"polite\"` для `info`/`success`\n * и `role=\"alert\"` + `aria-live=\"assertive\"` для `warning`/`danger`\n * (критичные сообщения должны прерывать скринридер).\n *\n * UX:\n * - появление/исчезновение анимируется `TransitionGroup`;\n * - `placement` настраивает угол экрана;\n * - `z-[60]` — выше, чем `GrModal` (`z-50`), чтобы уведомления не перекрывались диалогом.\n */\nimport {computed} from 'vue'\nimport type {Component} from 'vue'\n\nimport {useToast} from '../../composables/useToast'\nimport type {GrToastTone} from '../../composables/useToast'\nimport GrButton from '../GrButton'\nimport GrIcon from '../GrIcon'\nimport {PLACEMENT_CLASS, type GrToasterPlacement} from './grToasterStyles'\n\nimport IconCheck from '~icons/lucide/check-circle'\nimport IconWarning from '~icons/lucide/alert-triangle'\nimport IconError from '~icons/lucide/x-circle'\nimport IconInfo from '~icons/lucide/info'\nimport IconClose from '~icons/lucide/x'\n\nexport type {GrToasterPlacement}\n\ninterface ToneMeta {\n icon: Component\n color: string\n role: 'status' | 'alert'\n live: 'polite' | 'assertive'\n}\n\n// Единый маппинг по tone'у: иконка, цвет, a11y-роль и «напор» live-региона.\n// На уровне модуля — чтобы не пересоздавать объект на каждый рендер.\nconst TONE_META: Record<GrToastTone, ToneMeta> = {\n primary: {icon: IconInfo, color: 'var(--primary)', role: 'status', live: 'polite'},\n neutral: {icon: IconInfo, color: 'var(--muted-fg)', role: 'status', live: 'polite'},\n info: {icon: IconInfo, color: 'var(--ds-info)', role: 'status', live: 'polite'},\n success: {icon: IconCheck, color: 'var(--ds-success)', role: 'status', live: 'polite'},\n warning: {icon: IconWarning, color: 'var(--ds-warning)', role: 'alert', live: 'assertive'},\n danger: {icon: IconError, color: 'var(--ds-danger)', role: 'alert', live: 'assertive'},\n slate: {icon: IconInfo, color: 'var(--ds-slate)', role: 'status', live: 'polite'},\n azure: {icon: IconInfo, color: 'var(--ds-azure)', role: 'status', live: 'polite'},\n}\n\n\nexport interface GrToasterProps {\n /** Угол экрана для стека уведомлений. */\n placement?: GrToasterPlacement\n /** A11y-лейбл кнопки закрытия (i18n). */\n dismissLabel?: string\n /** A11y-лейбл контейнера-региона (i18n). */\n regionLabel?: string\n}\n\nconst props = withDefaults(defineProps<GrToasterProps>(), {\n placement: 'top-right',\n dismissLabel: 'Dismiss',\n regionLabel: 'Notifications',\n})\n\nconst {list, dismiss} = useToast()\n\n// SSR-guard: на сервере `document.body` недоступен — отключаем `teleport`.\nconst isClient = typeof window !== 'undefined'\n\nfunction metaFor(tone: GrToastTone): ToneMeta {\n return TONE_META[tone] ?? TONE_META.info\n}\n\nconst containerClass = computed(() => PLACEMENT_CLASS[props.placement])\n</script>\n\n<template>\n <teleport to=\"body\" :disabled=\"!isClient\">\n <div\n data-ds-toaster\n role=\"region\"\n :aria-label=\"regionLabel\"\n class=\"fixed z-[60] grid w-[360px] max-w-[calc(100vw-2rem)] gap-3\"\n :class=\"containerClass\"\n >\n <TransitionGroup\n tag=\"div\"\n class=\"grid gap-3\"\n enter-active-class=\"transition duration-200 ease-out\"\n enter-from-class=\"opacity-0 translate-y-2\"\n enter-to-class=\"opacity-100 translate-y-0\"\n leave-active-class=\"transition duration-150 ease-in absolute\"\n leave-from-class=\"opacity-100 translate-y-0\"\n leave-to-class=\"opacity-0 translate-y-2\"\n move-class=\"transition-transform duration-200 ease-out\"\n >\n <div\n v-for=\"toast in list\"\n :key=\"toast.id\"\n data-ds-toast\n :data-tone=\"toast.tone\"\n :role=\"metaFor(toast.tone).role\"\n :aria-live=\"metaFor(toast.tone).live\"\n aria-atomic=\"true\"\n class=\"rounded-[var(--ds-radius-lg)] border border-[var(--brd)] bg-[var(--card)] px-4 py-3 shadow-[var(--ds-shadow-2)]\"\n >\n <div class=\"flex items-start gap-3\">\n <GrIcon size=\"md\" class=\"mt-0.5\" :style=\"{ color: metaFor(toast.tone).color }\" aria-hidden=\"true\">\n <component :is=\"metaFor(toast.tone).icon\" />\n </GrIcon>\n <div class=\"min-w-0 flex-1\">\n <div class=\"text-[13px] font-700\">\n {{ toast.title }}\n </div>\n <div v-if=\"toast.message\" class=\"mt-0.5 text-[13px] text-[var(--muted-fg)]\">\n {{ toast.message }}\n </div>\n </div>\n <GrButton\n variant=\"ghost\"\n size=\"sm\"\n square\n :aria-label=\"dismissLabel\"\n @click=\"dismiss(toast.id)\"\n >\n <GrIcon size=\"sm\" aria-hidden=\"true\">\n <IconClose />\n </GrIcon>\n </GrButton>\n </div>\n </div>\n </TransitionGroup>\n </div>\n </teleport>\n</template>\n","<script setup lang=\"ts\">\n/**\n * GrToaster — DS-примитив области уведомлений (toast).\n *\n * Модель состояния берётся из `useToast` (модульный синглтон), поэтому\n * в приложении предполагается **один активный `GrToaster`** на корень.\n *\n * A11y:\n * - контейнер — `role=\"region\"` с `aria-label` (i18n-проп `regionLabel`);\n * - каждый toast — `role=\"status\"` + `aria-live=\"polite\"` для `info`/`success`\n * и `role=\"alert\"` + `aria-live=\"assertive\"` для `warning`/`danger`\n * (критичные сообщения должны прерывать скринридер).\n *\n * UX:\n * - появление/исчезновение анимируется `TransitionGroup`;\n * - `placement` настраивает угол экрана;\n * - `z-[60]` — выше, чем `GrModal` (`z-50`), чтобы уведомления не перекрывались диалогом.\n */\nimport {computed} from 'vue'\nimport type {Component} from 'vue'\n\nimport {useToast} from '../../composables/useToast'\nimport type {GrToastTone} from '../../composables/useToast'\nimport GrButton from '../GrButton'\nimport GrIcon from '../GrIcon'\nimport {PLACEMENT_CLASS, type GrToasterPlacement} from './grToasterStyles'\n\nimport IconCheck from '~icons/lucide/check-circle'\nimport IconWarning from '~icons/lucide/alert-triangle'\nimport IconError from '~icons/lucide/x-circle'\nimport IconInfo from '~icons/lucide/info'\nimport IconClose from '~icons/lucide/x'\n\nexport type {GrToasterPlacement}\n\ninterface ToneMeta {\n icon: Component\n color: string\n role: 'status' | 'alert'\n live: 'polite' | 'assertive'\n}\n\n// Единый маппинг по tone'у: иконка, цвет, a11y-роль и «напор» live-региона.\n// На уровне модуля — чтобы не пересоздавать объект на каждый рендер.\nconst TONE_META: Record<GrToastTone, ToneMeta> = {\n primary: {icon: IconInfo, color: 'var(--primary)', role: 'status', live: 'polite'},\n neutral: {icon: IconInfo, color: 'var(--muted-fg)', role: 'status', live: 'polite'},\n info: {icon: IconInfo, color: 'var(--ds-info)', role: 'status', live: 'polite'},\n success: {icon: IconCheck, color: 'var(--ds-success)', role: 'status', live: 'polite'},\n warning: {icon: IconWarning, color: 'var(--ds-warning)', role: 'alert', live: 'assertive'},\n danger: {icon: IconError, color: 'var(--ds-danger)', role: 'alert', live: 'assertive'},\n slate: {icon: IconInfo, color: 'var(--ds-slate)', role: 'status', live: 'polite'},\n azure: {icon: IconInfo, color: 'var(--ds-azure)', role: 'status', live: 'polite'},\n}\n\n\nexport interface GrToasterProps {\n /** Угол экрана для стека уведомлений. */\n placement?: GrToasterPlacement\n /** A11y-лейбл кнопки закрытия (i18n). */\n dismissLabel?: string\n /** A11y-лейбл контейнера-региона (i18n). */\n regionLabel?: string\n}\n\nconst props = withDefaults(defineProps<GrToasterProps>(), {\n placement: 'top-right',\n dismissLabel: 'Dismiss',\n regionLabel: 'Notifications',\n})\n\nconst {list, dismiss} = useToast()\n\n// SSR-guard: на сервере `document.body` недоступен — отключаем `teleport`.\nconst isClient = typeof window !== 'undefined'\n\nfunction metaFor(tone: GrToastTone): ToneMeta {\n return TONE_META[tone] ?? TONE_META.info\n}\n\nconst containerClass = computed(() => PLACEMENT_CLASS[props.placement])\n</script>\n\n<template>\n <teleport to=\"body\" :disabled=\"!isClient\">\n <div\n data-ds-toaster\n role=\"region\"\n :aria-label=\"regionLabel\"\n class=\"fixed z-[60] grid w-[360px] max-w-[calc(100vw-2rem)] gap-3\"\n :class=\"containerClass\"\n >\n <TransitionGroup\n tag=\"div\"\n class=\"grid gap-3\"\n enter-active-class=\"transition duration-200 ease-out\"\n enter-from-class=\"opacity-0 translate-y-2\"\n enter-to-class=\"opacity-100 translate-y-0\"\n leave-active-class=\"transition duration-150 ease-in absolute\"\n leave-from-class=\"opacity-100 translate-y-0\"\n leave-to-class=\"opacity-0 translate-y-2\"\n move-class=\"transition-transform duration-200 ease-out\"\n >\n <div\n v-for=\"toast in list\"\n :key=\"toast.id\"\n data-ds-toast\n :data-tone=\"toast.tone\"\n :role=\"metaFor(toast.tone).role\"\n :aria-live=\"metaFor(toast.tone).live\"\n aria-atomic=\"true\"\n class=\"rounded-[var(--ds-radius-lg)] border border-[var(--brd)] bg-[var(--card)] px-4 py-3 shadow-[var(--ds-shadow-2)]\"\n >\n <div class=\"flex items-start gap-3\">\n <GrIcon size=\"md\" class=\"mt-0.5\" :style=\"{ color: metaFor(toast.tone).color }\" aria-hidden=\"true\">\n <component :is=\"metaFor(toast.tone).icon\" />\n </GrIcon>\n <div class=\"min-w-0 flex-1\">\n <div class=\"text-[13px] font-700\">\n {{ toast.title }}\n </div>\n <div v-if=\"toast.message\" class=\"mt-0.5 text-[13px] text-[var(--muted-fg)]\">\n {{ toast.message }}\n </div>\n </div>\n <GrButton\n variant=\"ghost\"\n size=\"sm\"\n square\n :aria-label=\"dismissLabel\"\n @click=\"dismiss(toast.id)\"\n >\n <GrIcon size=\"sm\" aria-hidden=\"true\">\n <IconClose />\n </GrIcon>\n </GrButton>\n </div>\n </div>\n </TransitionGroup>\n </div>\n </teleport>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4CA,MAAM,YAA2C;GAC/C,SAAS;IAAC,MAAM;IAAU,OAAO;IAAkB,MAAM;IAAU,MAAM;IAAS;GAClF,SAAS;IAAC,MAAM;IAAU,OAAO;IAAmB,MAAM;IAAU,MAAM;IAAS;GACnF,MAAM;IAAC,MAAM;IAAU,OAAO;IAAkB,MAAM;IAAU,MAAM;IAAS;GAC/E,SAAS;IAAC,MAAM;IAAW,OAAO;IAAqB,MAAM;IAAU,MAAM;IAAS;GACtF,SAAS;IAAC,MAAM;IAAa,OAAO;IAAqB,MAAM;IAAS,MAAM;IAAY;GAC1F,QAAQ;IAAC,MAAM;IAAW,OAAO;IAAoB,MAAM;IAAS,MAAM;IAAY;GACtF,OAAO;IAAC,MAAM;IAAU,OAAO;IAAmB,MAAM;IAAU,MAAM;IAAS;GACjF,OAAO;IAAC,MAAM;IAAU,OAAO;IAAmB,MAAM;IAAU,MAAM;IAAS;GACnF;EAYA,MAAM,QAAQ;EAMd,MAAM,EAAC,MAAM,YAAW,UAAS;EAGjC,MAAM,WAAW,OAAO,WAAW;EAEnC,SAAS,QAAQ,MAA6B;AAC5C,UAAO,UAAU,SAAS,UAAU;;EAGtC,MAAM,iBAAiB,eAAe,gBAAgB,MAAM,WAAU;;uBAIpE,YAwDW,UAAA;IAxDD,IAAG;IAAQ,UAAQ,CAAG;OAC9B,mBAsDM,OAAA;IArDF,mBAAA;IACA,MAAK;IACJ,cAAY,QAAA;IACb,OAAK,eAAA,CAAC,8DACE,eAAA,MAAc,CAAA;OAExB,YA8CkB,iBAAA;IA7Cd,KAAI;IACJ,OAAM;IACN,sBAAmB;IACnB,oBAAiB;IACjB,kBAAe;IACf,sBAAmB;IACnB,oBAAiB;IACjB,kBAAe;IACf,cAAW;;2BAGY,EAAA,UAAA,KAAA,EADzB,mBAkCM,UAAA,MAAA,WAjCc,MAAA,KAAI,GAAb,UAAK;yBADhB,mBAkCM,OAAA;MAhCD,KAAK,MAAM;MACZ,iBAAA;MACC,aAAW,MAAM;MACjB,MAAM,QAAQ,MAAM,KAAI,CAAE;MAC1B,aAAW,QAAQ,MAAM,KAAI,CAAE;MAChC,eAAY;MACZ,OAAM;SAER,mBAuBM,OAvBN,YAuBM;MAtBJ,YAES,MAAA,eAAA,EAAA;OAFD,MAAK;OAAK,OAAM;OAAU,OAAK,eAAA,EAAA,OAAW,QAAQ,MAAM,KAAI,CAAE,OAAK,CAAA;OAAI,eAAY;;8BAC7C,EAAA,WAAA,EAA5C,YAA4C,wBAA5B,QAAQ,MAAM,KAAI,CAAE,KAAI,CAAA,EAAA,CAAA;;;MAE1C,mBAOM,OAPN,YAOM,CANJ,mBAEM,OAFN,YAEM,gBADD,MAAM,MAAK,EAAA,EAAA,EAEL,MAAM,WAAA,WAAA,EAAjB,mBAEM,OAFN,YAEM,gBADD,MAAM,QAAO,EAAA,EAAA,IAAA,mBAAA,IAAA,KAAA,CAAA,CAAA;MAGpB,YAUW,MAAA,iBAAA,EAAA;OATP,SAAQ;OACR,MAAK;OACL,QAAA;OACC,cAAY,QAAA;OACZ,UAAK,WAAE,MAAA,QAAO,CAAC,MAAM,GAAE;;8BAIjB,CAFT,YAES,MAAA,eAAA,EAAA;QAFD,MAAK;QAAK,eAAY;;+BACf,CAAb,YAAa,MAAA,UAAA,CAAA,CAAA,CAAA"}
|