@feugene/granularity 0.11.0 → 0.12.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.
Files changed (189) hide show
  1. package/dist/GrTree.css +1 -1
  2. package/dist/chunks/config-1uSYl98Y2.js.map +1 -0
  3. package/dist/chunks/config-BIRO0vDJ.js.map +1 -0
  4. package/dist/chunks/config-BWiPnygb.js +5 -0
  5. package/dist/chunks/config-BWiPnygb.js.map +1 -0
  6. package/dist/chunks/config-BXtAmU3s2.js.map +1 -0
  7. package/dist/chunks/config-CGHLoBlH.js.map +1 -0
  8. package/dist/chunks/config-CrG1XiE72.js.map +1 -0
  9. package/dist/chunks/config-DUcu1D682.js.map +1 -0
  10. package/dist/chunks/config-Dlm49Jp5.js +5 -0
  11. package/dist/chunks/config-Dlm49Jp5.js.map +1 -0
  12. package/dist/chunks/config-DnvPqu4g2.js.map +1 -0
  13. package/dist/chunks/config-DpqOnJiz.js.map +1 -0
  14. package/dist/chunks/config-JpHA4Xx02.js.map +1 -0
  15. package/dist/chunks/config-LGrZIdaZ.js.map +1 -0
  16. package/dist/chunks/config-eXB87DOi2.js +9 -0
  17. package/dist/chunks/config-eXB87DOi2.js.map +1 -0
  18. package/dist/chunks/context-DdNIxa1w.js +9 -0
  19. package/dist/chunks/context-DdNIxa1w.js.map +1 -0
  20. package/dist/chunks/{en-BdtiS2d3.js → en-CfkDa4GX.js} +8 -2
  21. package/dist/chunks/{en-BdtiS2d3.js.map → en-CfkDa4GX.js.map} +1 -1
  22. package/dist/chunks/{es-DBhctWg-.js → es-CgaLnFYx.js} +3 -3
  23. package/dist/chunks/{es-DBhctWg-.js.map → es-CgaLnFYx.js.map} +1 -1
  24. package/dist/chunks/{es-BGRMZbe9.js → es-FkgECuCm.js} +8 -2
  25. package/dist/chunks/{es-BGRMZbe9.js.map → es-FkgECuCm.js.map} +1 -1
  26. package/dist/chunks/grModalEscStack-BXk-9mv7.js +43 -0
  27. package/dist/chunks/grModalEscStack-BXk-9mv7.js.map +1 -0
  28. package/dist/chunks/{ru-BFCnOiML.js → ru-Bl8CxpEa.js} +8 -2
  29. package/dist/chunks/{ru-BFCnOiML.js.map → ru-Bl8CxpEa.js.map} +1 -1
  30. package/dist/chunks/{shared-ChvcQgDl.js → shared-B6Ay2081.js} +17 -11
  31. package/dist/chunks/shared-B6Ay2081.js.map +1 -0
  32. package/dist/chunks/{transition-TLFw4KBn.js → useScrollLock-K7kaP2Ud.js} +52 -1
  33. package/dist/chunks/useScrollLock-K7kaP2Ud.js.map +1 -0
  34. package/dist/components/GrConfirmDialog/chunks/{GrConfirmDialog-BWYKCsHd.js → GrConfirmDialog-D8CkOtqv.js} +1 -1
  35. package/dist/components/GrConfirmDialog/chunks/{GrConfirmDialog-BWYKCsHd.js.map → GrConfirmDialog-D8CkOtqv.js.map} +1 -1
  36. package/dist/components/GrConfirmDialog/index.js +2 -2
  37. package/dist/components/GrDataTable/chunks/{GrDataTable-ptbARmJ3.js → GrDataTable-Ds5OeGat.js} +42 -16
  38. package/dist/components/GrDataTable/chunks/GrDataTable-Ds5OeGat.js.map +1 -0
  39. package/dist/components/GrDataTable/index.js +1 -1
  40. package/dist/components/GrDialog/chunks/{GrDialog-5DAS-1Lb.js → GrDialog-Ctucxju9.js} +2 -2
  41. package/dist/components/GrDialog/chunks/{GrDialog-5DAS-1Lb.js.map → GrDialog-Ctucxju9.js.map} +1 -1
  42. package/dist/components/GrDialog/index.js +1 -1
  43. package/dist/components/GrDialogService/chunks/{GrDialogService-DY_fmaMu.js → GrDialogService-CU1Fbonc.js} +2 -2
  44. package/dist/components/GrDialogService/chunks/{GrDialogService-DY_fmaMu.js.map → GrDialogService-CU1Fbonc.js.map} +1 -1
  45. package/dist/components/GrDialogService/index.js +1 -1
  46. package/dist/components/GrDivider/chunks/GrDivider-5bcE-BB4.js +54 -0
  47. package/dist/components/GrDivider/chunks/GrDivider-5bcE-BB4.js.map +1 -0
  48. package/dist/components/GrDivider/index.js +3 -0
  49. package/dist/components/GrDrawer/chunks/{GrDrawer-D1kzsjXN.js → GrDrawer-SLGKCY2A.js} +37 -13
  50. package/dist/components/GrDrawer/chunks/GrDrawer-SLGKCY2A.js.map +1 -0
  51. package/dist/components/GrDrawer/index.js +2 -2
  52. package/dist/components/GrDropdown/chunks/{GrDropdown-BXb5Cxxf.js → GrDropdown-BnDMYP8v.js} +90 -4
  53. package/dist/components/GrDropdown/chunks/GrDropdown-BnDMYP8v.js.map +1 -0
  54. package/dist/components/GrDropdown/index.js +1 -1
  55. package/dist/components/GrDropdownMenu/chunks/{GrDropdownMenu-DFEJLPcV.js → GrDropdownMenu--g9Vofcq.js} +1 -1
  56. package/dist/components/GrDropdownMenu/chunks/{GrDropdownMenu-DFEJLPcV.js.map → GrDropdownMenu--g9Vofcq.js.map} +1 -1
  57. package/dist/components/GrDropdownMenu/index.js +1 -1
  58. package/dist/components/GrFormField/chunks/GrFormField-DnfMHx_U.js +73 -0
  59. package/dist/components/GrFormField/chunks/GrFormField-DnfMHx_U.js.map +1 -0
  60. package/dist/components/GrFormField/index.js +3 -2
  61. package/dist/components/GrImageViewer/chunks/{GrImageViewer-OCUIrNh2.js → GrImageViewer-Dla9IBkd.js} +311 -222
  62. package/dist/components/GrImageViewer/chunks/GrImageViewer-Dla9IBkd.js.map +1 -0
  63. package/dist/components/GrImageViewer/index.js +1 -1
  64. package/dist/components/GrInput/chunks/GrInput-B8dd3JaA.js +334 -0
  65. package/dist/components/GrInput/chunks/GrInput-B8dd3JaA.js.map +1 -0
  66. package/dist/components/GrInput/index.js +1 -1
  67. package/dist/components/GrKbd/chunks/GrKbd-BEoYxWhE.js +22 -0
  68. package/dist/components/GrKbd/chunks/GrKbd-BEoYxWhE.js.map +1 -0
  69. package/dist/components/GrKbd/index.js +3 -0
  70. package/dist/components/GrModal/chunks/{GrModal-BkjW9jZS.js → GrModal-C0GZrHN3.js} +3 -56
  71. package/dist/components/GrModal/chunks/GrModal-C0GZrHN3.js.map +1 -0
  72. package/dist/components/GrModal/index.js +1 -1
  73. package/dist/components/GrNumberInput/chunks/{GrNumberInput-CldPIWQx.js → GrNumberInput-DxCEvL7V.js} +93 -11
  74. package/dist/components/GrNumberInput/chunks/GrNumberInput-DxCEvL7V.js.map +1 -0
  75. package/dist/components/GrNumberInput/index.js +1 -1
  76. package/dist/components/GrPagination/chunks/{GrPagination-Bu9wv6it.js → GrPagination-mlyfLjQY.js} +1 -1
  77. package/dist/components/GrPagination/chunks/{GrPagination-Bu9wv6it.js.map → GrPagination-mlyfLjQY.js.map} +1 -1
  78. package/dist/components/GrPagination/index.js +2 -2
  79. package/dist/components/GrProgressBar/index.js +1 -1
  80. package/dist/components/GrPromptDialog/chunks/{GrPromptDialog-TmWXbpEP.js → GrPromptDialog-C-_BMNuY.js} +3 -3
  81. package/dist/components/GrPromptDialog/chunks/{GrPromptDialog-TmWXbpEP.js.map → GrPromptDialog-C-_BMNuY.js.map} +1 -1
  82. package/dist/components/GrPromptDialog/index.js +1 -1
  83. package/dist/components/GrRadio/chunks/{GrRadio-CPTixXaY.js → GrRadio-Dr-pvs_9.js} +1 -1
  84. package/dist/components/GrRadio/chunks/{GrRadio-CPTixXaY.js.map → GrRadio-Dr-pvs_9.js.map} +1 -1
  85. package/dist/components/GrRadio/index.js +2 -2
  86. package/dist/components/GrRadioGroup/chunks/{GrRadioGroup-vKZCbkCp.js → GrRadioGroup-nOdvfV_g.js} +1 -1
  87. package/dist/components/GrRadioGroup/chunks/{GrRadioGroup-vKZCbkCp.js.map → GrRadioGroup-nOdvfV_g.js.map} +1 -1
  88. package/dist/components/GrRadioGroup/index.js +1 -1
  89. package/dist/components/GrSelect/chunks/{GrSelect-RShK21lK.js → GrSelect-BVCmitqa.js} +147 -27
  90. package/dist/components/GrSelect/chunks/GrSelect-BVCmitqa.js.map +1 -0
  91. package/dist/components/GrSelect/index.js +1 -1
  92. package/dist/components/GrSidebar/index.js +1 -1
  93. package/dist/components/GrTabPanels/chunks/GrTabPanels-DbKybwe-.js +68 -0
  94. package/dist/components/GrTabPanels/chunks/GrTabPanels-DbKybwe-.js.map +1 -0
  95. package/dist/components/GrTabPanels/index.js +3 -0
  96. package/dist/components/GrTabs/chunks/{GrTabs-Z-FDJUr_.js → GrTabs-DsLwWoRz.js} +6 -1
  97. package/dist/components/GrTabs/chunks/GrTabs-DsLwWoRz.js.map +1 -0
  98. package/dist/components/GrTabs/index.js +1 -1
  99. package/dist/components/GrTextarea/chunks/{GrTextarea-D56YxhKq.js → GrTextarea-CKukGlNl.js} +15 -5
  100. package/dist/components/GrTextarea/chunks/GrTextarea-CKukGlNl.js.map +1 -0
  101. package/dist/components/GrTextarea/index.js +2 -2
  102. package/dist/components/GrToaster/chunks/{GrToaster-eI8YvnIo.js → GrToaster-L2cu6cSR.js} +1 -1
  103. package/dist/components/GrToaster/chunks/{GrToaster-eI8YvnIo.js.map → GrToaster-L2cu6cSR.js.map} +1 -1
  104. package/dist/components/GrToaster/index.js +2 -2
  105. package/dist/components/GrTree/chunks/{GrTree-D3ODe2dB.js → GrTree-B2_yW6g1.js} +107 -8
  106. package/dist/components/GrTree/chunks/GrTree-B2_yW6g1.js.map +1 -0
  107. package/dist/components/GrTree/index.js +2 -2
  108. package/dist/components/GrTreeSelect/chunks/{GrTreeSelect-B2xBKs5Q.js → GrTreeSelect-0aM62xyu.js} +3 -3
  109. package/dist/components/GrTreeSelect/chunks/{GrTreeSelect-B2xBKs5Q.js.map → GrTreeSelect-0aM62xyu.js.map} +1 -1
  110. package/dist/components/GrTreeSelect/index.js +2 -2
  111. package/dist/granular-provider-node.js +2 -2
  112. package/dist/granular-provider.js +1 -1
  113. package/dist/i18n/all.js +1 -1
  114. package/dist/i18n/index.js +1 -1
  115. package/dist/index.js +40 -33
  116. package/dist/index.js.map +1 -1
  117. package/dist/styles/base.css +30 -8
  118. package/dist/styles/preflight.css +5 -1
  119. package/dist/types/src/components/GrDataTable/GrDataTable.vue.d.ts +28 -4
  120. package/dist/types/src/components/GrDivider/GrDivider.vue.d.ts +58 -0
  121. package/dist/types/src/components/GrDivider/config.d.ts +1 -0
  122. package/dist/types/src/components/GrDivider/index.d.ts +4 -0
  123. package/dist/types/src/components/GrDrawer/GrDrawer.vue.d.ts +4 -4
  124. package/dist/types/src/components/GrDropdown/GrDropdown.vue.d.ts +7 -0
  125. package/dist/types/src/components/GrDropdownMenu/GrDropdownMenu.vue.d.ts +6 -0
  126. package/dist/types/src/components/GrFormField/GrFormField.vue.d.ts +15 -2
  127. package/dist/types/src/components/GrFormField/context.d.ts +20 -0
  128. package/dist/types/src/components/GrFormField/index.d.ts +1 -0
  129. package/dist/types/src/components/GrImageViewer/GrImageViewer.vue.d.ts +4 -0
  130. package/dist/types/src/components/GrImageViewer/composables/useViewerKeyboard.d.ts +20 -0
  131. package/dist/types/src/components/GrImageViewer/composables/useWheelGesture.d.ts +16 -0
  132. package/dist/types/src/components/GrImageViewer/composables/useZoomPan.d.ts +50 -0
  133. package/dist/types/src/components/GrInput/GrInput.vue.d.ts +41 -2
  134. package/dist/types/src/components/GrKbd/GrKbd.vue.d.ts +46 -0
  135. package/dist/types/src/components/GrKbd/config.d.ts +1 -0
  136. package/dist/types/src/components/GrKbd/index.d.ts +4 -0
  137. package/dist/types/src/components/GrNumberInput/GrNumberInput.vue.d.ts +16 -0
  138. package/dist/types/src/components/GrSelect/GrSelect.vue.d.ts +2 -2
  139. package/dist/types/src/components/GrTabPanels/GrTabPanel.vue.d.ts +46 -0
  140. package/dist/types/src/components/GrTabPanels/GrTabPanels.vue.d.ts +51 -0
  141. package/dist/types/src/components/GrTabPanels/config.d.ts +5 -0
  142. package/dist/types/src/components/GrTabPanels/context.d.ts +9 -0
  143. package/dist/types/src/components/GrTabPanels/index.d.ts +6 -0
  144. package/dist/types/src/components/GrTabs/GrTabs.vue.d.ts +6 -0
  145. package/dist/types/src/components/GrTree/grTreeInteractionContext.d.ts +2 -0
  146. package/dist/types/src/composables/internal/useScrollLock.d.ts +24 -0
  147. package/dist/types/src/granular-provider/shared.d.ts +3 -0
  148. package/dist/types/src/i18n/messages/en.d.ts +10 -0
  149. package/dist/types/src/i18n/messages/es.d.ts +10 -0
  150. package/dist/types/src/i18n/messages/ru.d.ts +10 -0
  151. package/dist/types/src/index.d.ts +3 -0
  152. package/dist/web-types.json +275 -26
  153. package/package.json +13 -1
  154. package/dist/chunks/config-1uSYl98Y.js.map +0 -1
  155. package/dist/chunks/config-BIRO0vDJ2.js.map +0 -1
  156. package/dist/chunks/config-BXtAmU3s.js.map +0 -1
  157. package/dist/chunks/config-CGHLoBlH2.js.map +0 -1
  158. package/dist/chunks/config-CrG1XiE7.js.map +0 -1
  159. package/dist/chunks/config-DUcu1D68.js.map +0 -1
  160. package/dist/chunks/config-DnvPqu4g.js.map +0 -1
  161. package/dist/chunks/config-DpqOnJiz2.js.map +0 -1
  162. package/dist/chunks/config-JpHA4Xx0.js.map +0 -1
  163. package/dist/chunks/config-LGrZIdaZ2.js.map +0 -1
  164. package/dist/chunks/shared-ChvcQgDl.js.map +0 -1
  165. package/dist/chunks/transition-TLFw4KBn.js.map +0 -1
  166. package/dist/components/GrDataTable/chunks/GrDataTable-ptbARmJ3.js.map +0 -1
  167. package/dist/components/GrDrawer/chunks/GrDrawer-D1kzsjXN.js.map +0 -1
  168. package/dist/components/GrDropdown/chunks/GrDropdown-BXb5Cxxf.js.map +0 -1
  169. package/dist/components/GrFormField/chunks/GrFormField-BujNQrw8.js +0 -35
  170. package/dist/components/GrFormField/chunks/GrFormField-BujNQrw8.js.map +0 -1
  171. package/dist/components/GrImageViewer/chunks/GrImageViewer-OCUIrNh2.js.map +0 -1
  172. package/dist/components/GrInput/chunks/GrInput-C8pOJgCi.js +0 -188
  173. package/dist/components/GrInput/chunks/GrInput-C8pOJgCi.js.map +0 -1
  174. package/dist/components/GrModal/chunks/GrModal-BkjW9jZS.js.map +0 -1
  175. package/dist/components/GrNumberInput/chunks/GrNumberInput-CldPIWQx.js.map +0 -1
  176. package/dist/components/GrSelect/chunks/GrSelect-RShK21lK.js.map +0 -1
  177. package/dist/components/GrTabs/chunks/GrTabs-Z-FDJUr_.js.map +0 -1
  178. package/dist/components/GrTextarea/chunks/GrTextarea-D56YxhKq.js.map +0 -1
  179. package/dist/components/GrTree/chunks/GrTree-D3ODe2dB.js.map +0 -1
  180. /package/dist/chunks/{config-1uSYl98Y.js → config-1uSYl98Y2.js} +0 -0
  181. /package/dist/chunks/{config-BIRO0vDJ2.js → config-BIRO0vDJ.js} +0 -0
  182. /package/dist/chunks/{config-BXtAmU3s.js → config-BXtAmU3s2.js} +0 -0
  183. /package/dist/chunks/{config-CGHLoBlH2.js → config-CGHLoBlH.js} +0 -0
  184. /package/dist/chunks/{config-CrG1XiE7.js → config-CrG1XiE72.js} +0 -0
  185. /package/dist/chunks/{config-DUcu1D68.js → config-DUcu1D682.js} +0 -0
  186. /package/dist/chunks/{config-DnvPqu4g.js → config-DnvPqu4g2.js} +0 -0
  187. /package/dist/chunks/{config-DpqOnJiz2.js → config-DpqOnJiz.js} +0 -0
  188. /package/dist/chunks/{config-JpHA4Xx0.js → config-JpHA4Xx02.js} +0 -0
  189. /package/dist/chunks/{config-LGrZIdaZ2.js → config-LGrZIdaZ.js} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GrSelect-BVCmitqa.js","names":[],"sources":["../../../../src/components/GrSelect/GrSelect.vue","../../../../src/components/GrSelect/GrSelect.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, nextTick, ref, useId, watch } from 'vue'\n\nimport GrInput from '../GrInput/GrInput.vue'\nimport { vClickOutside } from '../../directives'\nimport { useFloating } from '../../composables/internal/useFloating'\nimport { useEscapeToClose } from '../../composables/internal/useEscapeToClose'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { useGrFormFieldContext } from '../GrFormField/context'\n\nimport {\n defaultBaseClass,\n grSelectLinkNativeLabelClass,\n grSelectLinkNativeOverlayClass,\n grSelectNativeClass,\n grSelectPanelClasses,\n grSelectTriggerClass,\n linkBaseClass,\n type GrSelectModelValue,\n type GrSelectOption,\n type GrSelectOptionGroup,\n type GrSelectOptionOrGroup,\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 GrSelectOptionGroup,\n GrSelectOptionOrGroup,\n GrSelectOptionsView,\n GrSelectSize,\n GrSelectUnderline,\n GrSelectVariant,\n GrSelectView,\n} from './grSelectStyles'\n\n/**\n * Пропсы публичного GR-примитива «Select».\n */\nexport interface GrSelectProps {\n modelValue: GrSelectModelValue\n /** Список опций. Поддерживает плоский массив опций и группы опций (`{ label, options }`). */\n options?: GrSelectOptionOrGroup[]\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\"`). i18n-friendly: если не задан — берётся из адаптера перевода (`gr.select.customValuePlaceholder`), иначе — встроенный fallback. */\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: undefined,\n dropdownMaxHeight: 280,\n closeOnSelect: true,\n clearable: false,\n clearLabel: undefined,\n variant: 'primary',\n underline: 'auto',\n },\n)\n\nconst { t } = useGranularityTranslations()\n\nconst resolvedCustomValuePlaceholder = computed(() => {\n return props.customValuePlaceholder ?? t('gr.select.customValuePlaceholder', 'Add value…')\n})\n\nconst resolvedClearLabel = computed(() => props.clearLabel ?? t('gr.common.clear', 'Clear'))\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\n// Fallback из контекста `GrFormField` (id/aria-describedby/invalid/required)\n// для связки с лейблом и сообщением об ошибке.\nconst field = useGrFormFieldContext()\nconst resolvedId = computed(() => field?.id.value)\nconst isInvalid = computed(() => Boolean(field?.invalid.value))\nconst describedBy = computed(() => field?.describedById.value)\nconst isRequired = computed(() => Boolean(field?.required.value))\n\nconst optionsResolved = computed<GrSelectOptionOrGroup[]>(() => props.options ?? [])\n\nfunction isOptionGroup(item: GrSelectOptionOrGroup): item is GrSelectOptionGroup {\n return Array.isArray((item as GrSelectOptionGroup).options)\n}\n\n/** Плоский список всех опций (группы «развёрнуты»). Используется для всех вычислений по значениям. */\nconst flatOptions = computed<GrSelectOption[]>(() => {\n const result: GrSelectOption[] = []\n for (const item of optionsResolved.value) {\n if (isOptionGroup(item)) result.push(...item.options)\n else result.push(item)\n }\n return result\n})\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(flatOptions.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 flatOptions.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) => flatOptions.value.find((o) => o.value === v)?.label ?? v)\n .join(', ')\n }\n\n return flatOptions.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\n// Нативный `<select multiple>` — это многострочный listbox, а не поповер. В режиме\n// `view=\"link\"` он рендерится невидимым overlay'ем, из-за чего multiple-вариант «не\n// открывается» (клик по невидимому listbox'у ничего не показывает). Для этой\n// неподдерживаемой комбинации форсим кастомную панель, которая корректно открывается.\nconst effectiveOptionsView = computed<GrSelectOptionsView>(() => {\n if (props.view === 'link' && props.multiple && props.optionsView === 'native')\n return 'panel'\n return props.optionsView\n})\n\nconst nativeCustomOptionVisible = computed(() => {\n if (!props.allowCustomValue) return false\n if (effectiveOptionsView.value !== '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]\n\n// Для `view='link'` ширина триггера = ширине выбранной опции (`inline-block w-auto`),\n// поэтому панель растёт по контенту (`matchWidth: 'min'` → `width: max-content` +\n// `min-width` от триггера), а не сжимается до неё. Для `view='default'` панель точно\n// повторяет ширину триггера (`matchWidth: true`).\nconst { floatingStyle, update: updateFloatingPosition } = useFloating(rootEl, panelEl, open, {\n placement: 'bottom-start',\n matchWidth: () => (props.view === 'link' ? 'min' : true),\n zIndexVar: '--gr-z-dropdown',\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\nuseEscapeToClose(open, closeDropdown)\n\n// `view` определяет режим `matchWidth` (см. выше) — пересчитываем позицию/ширину\n// панели, если он меняется, пока панель открыта.\nwatch(\n () => props.view,\n () => {\n if (open.value) updateFloatingPosition()\n },\n)\n\nconst panelClasses = computed(() => {\n return grSelectPanelClasses\n})\n\nfunction matchesQuery(option: GrSelectOption, query: string): boolean {\n if (!query) return true\n return option.label.toLowerCase().includes(query) || option.value.toLowerCase().includes(query)\n}\n\n/**\n * Элемент рендера панели: либо заголовок группы, либо опция.\n * Группировка сохраняется, фильтрация по `customValue` скрывает пустые группы.\n */\ntype GrSelectPanelItem =\n | { kind: 'group', label: string, key: string }\n | { kind: 'option', option: GrSelectOption, key: string }\n\nconst panelItems = computed<GrSelectPanelItem[]>(() => {\n const q = props.allowCustomValue ? customValue.value.trim().toLowerCase() : ''\n const items: GrSelectPanelItem[] = []\n\n // Опция для кастомного значения, которого нет в options (single).\n if (props.allowCustomValue && !props.multiple && modelSingle.value !== '' && !hasModelInOptions.value) {\n const custom: GrSelectOption = { value: modelSingle.value, label: modelSingle.value }\n if (matchesQuery(custom, q)) {\n items.push({ kind: 'option', option: custom, key: `__custom__${custom.value}` })\n }\n }\n\n optionsResolved.value.forEach((item, index) => {\n if (isOptionGroup(item)) {\n const matched = item.options.filter((o) => matchesQuery(o, q))\n if (!matched.length) return\n items.push({ kind: 'group', label: item.label, key: `__group__${index}` })\n for (const option of matched) {\n // Ключ с индексом группы — одинаковое `value` в разных группах больше не даёт дубликат.\n items.push({ kind: 'option', option, key: `${index}:${option.value}` })\n }\n return\n }\n\n if (matchesQuery(item, q)) {\n items.push({ kind: 'option', option: item, key: `${index}:${item.value}` })\n }\n })\n\n return items\n})\n\nconst canAddCustom = computed(() => {\n if (!props.allowCustomValue) return false\n if (effectiveOptionsView.value !== '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 !flatOptions.value.some((o) => o.value === v)\n }\n\n if (v === modelSingle.value) return false\n return !flatOptions.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 isOptionDisabled(value: string): boolean {\n return flatOptions.value.find(o => o.value === value)?.disabled === true\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 (isOptionDisabled(value)) return\n\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\n// ————— Клавиатурная навигация комбобокса (WAI-ARIA, aria-activedescendant).\nconst listboxId = useId()\nconst activeIndex = ref(-1)\n\nfunction optionDomId(value: string): string {\n return `${listboxId}-opt-${value}`\n}\n\n// Навигируемые (видимые, не-disabled) опции панели в порядке рендера.\nconst navigableValues = computed<string[]>(() =>\n panelItems.value\n .filter((item): item is Extract<GrSelectPanelItem, { kind: 'option' }> => item.kind === 'option' && !item.option.disabled)\n .map(item => item.option.value),\n)\n\nconst activeValue = computed(() => (activeIndex.value >= 0 ? navigableValues.value[activeIndex.value] : undefined))\nconst activeDescendantId = computed(() =>\n open.value && activeValue.value !== undefined ? optionDomId(activeValue.value) : undefined,\n)\n\nfunction clampActive(index: number): number {\n const len = navigableValues.value.length\n if (len === 0) return -1\n return ((index % len) + len) % len\n}\n\nasync function scrollActiveIntoView(): Promise<void> {\n await nextTick()\n const value = activeValue.value\n if (value === undefined) return\n const el = document.getElementById(optionDomId(value))\n el?.scrollIntoView?.({ block: 'nearest' })\n}\n\nfunction setActive(index: number): void {\n activeIndex.value = clampActive(index)\n void scrollActiveIntoView()\n}\n\nfunction initActiveIndex(): void {\n const selectedIdx = navigableValues.value.findIndex(v => isSelected(v))\n activeIndex.value = selectedIdx >= 0 ? selectedIdx : (navigableValues.value.length ? 0 : -1)\n}\n\nfunction openDropdown(): void {\n if (props.disabled || open.value) return\n open.value = true\n}\n\nlet typeaheadBuffer = ''\nlet typeaheadTimer: ReturnType<typeof setTimeout> | null = null\nfunction typeahead(char: string): void {\n typeaheadBuffer += char.toLowerCase()\n if (typeaheadTimer) clearTimeout(typeaheadTimer)\n typeaheadTimer = setTimeout(() => { typeaheadBuffer = '' }, 600)\n\n const idx = navigableValues.value.findIndex((v) => {\n const opt = flatOptions.value.find(o => o.value === v)\n return opt?.label.toLowerCase().startsWith(typeaheadBuffer)\n })\n if (idx >= 0) setActive(idx)\n}\n\nfunction onComboKeydown(event: KeyboardEvent): void {\n if (props.disabled) return\n\n if (!open.value) {\n if (['ArrowDown', 'ArrowUp', 'Enter', ' '].includes(event.key)) {\n event.preventDefault()\n openDropdown()\n }\n return\n }\n\n switch (event.key) {\n case 'ArrowDown':\n event.preventDefault()\n setActive(activeIndex.value + 1)\n break\n case 'ArrowUp':\n event.preventDefault()\n setActive(activeIndex.value - 1)\n break\n case 'Home':\n event.preventDefault()\n setActive(0)\n break\n case 'End':\n event.preventDefault()\n setActive(navigableValues.value.length - 1)\n break\n case 'Enter':\n event.preventDefault()\n if (props.allowCustomValue && canAddCustom.value)\n addCustom()\n else if (activeValue.value !== undefined)\n toggleValue(activeValue.value)\n break\n case 'Tab':\n closeDropdown()\n break\n default:\n // typeahead — только без allowCustomValue (иначе мешает вводу в custom-инпут).\n if (!props.allowCustomValue && event.key.length === 1 && !event.metaKey && !event.ctrlKey && !event.altKey)\n typeahead(event.key)\n }\n}\n\nwatch(\n open,\n async (isOpen) => {\n if (!isOpen) {\n customValue.value = ''\n activeIndex.value = -1\n return\n }\n\n initActiveIndex()\n\n if (props.allowCustomValue) {\n await nextTick()\n customInputRef.value?.focus()\n }\n },\n { immediate: true },\n)\n\nconst showNativeChevron = computed(() => {\n return effectiveOptionsView.value === 'native' && props.view !== 'link' && !props.multiple\n})\n\nconst nativeClearOptionVisible = computed(() => {\n if (!props.clearable) return false\n if (effectiveOptionsView.value !== 'native') return false\n if (props.multiple) return false\n if (!props.options) return true\n return !flatOptions.value.some(o => o.value === '')\n})\n\nconst panelClearVisible = computed(() => {\n if (!props.clearable) return false\n if (effectiveOptionsView.value !== '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' && effectiveOptionsView.value === '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: effectiveOptionsView.value,\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=\"effectiveOptionsView === 'native'\"\n data-gr-select\n :class=\"rootClass\"\n >\n <!--\n Выбор задаём per-option через `:selected` (а не `:value` на `<select>`):\n для `<select multiple>` биндинг `:value` массивом не работает — DOM не\n отражает программное изменение модели. `onChange` читает выбор из DOM.\n -->\n <select\n :id=\"resolvedId\"\n data-gr-select-native\n :multiple=\"multiple\"\n :disabled=\"disabled\"\n :aria-label=\"ariaLabel\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n :aria-describedby=\"describedBy\"\n :aria-required=\"isRequired ? 'true' : undefined\"\n :class=\"isLinkNative ? grSelectLinkNativeOverlayClass : [baseClassName, nativeClassName]\"\n @change=\"onChange\"\n >\n <option v-if=\"nativeClearOptionVisible\" value=\"\" :selected=\"!multiple && modelSingle === ''\">\n {{ placeholder || t('gr.select.clearOption', 'None') }}\n </option>\n\n <slot>\n <option v-if=\"nativeCustomOptionVisible\" :value=\"modelSingle\" :selected=\"!multiple\">\n {{ modelSingle }}\n </option>\n <template v-for=\"(item, index) in optionsResolved\" :key=\"index\">\n <optgroup v-if=\"isOptionGroup(item)\" :label=\"item.label\">\n <option\n v-for=\"opt in item.options\"\n :key=\"`${index}:${opt.value}`\"\n :value=\"opt.value\"\n :selected=\"multiple ? modelMultiple.includes(opt.value) : modelSingle === opt.value\"\n :disabled=\"opt.disabled\"\n >\n {{ opt.label }}\n </option>\n </optgroup>\n <option\n v-else\n :value=\"item.value\"\n :selected=\"multiple ? modelMultiple.includes(item.value) : modelSingle === item.value\"\n :disabled=\"item.disabled\"\n >\n {{ item.label }}\n </option>\n </template>\n </slot>\n </select>\n\n <span\n v-if=\"isLinkNative\"\n data-gr-select-link-label\n aria-hidden=\"true\"\n :class=\"linkNativeLabelClassName\"\n >{{ linkNativeDisplayText }}</span>\n\n <span\n v-if=\"showNativeChevron\"\n data-testid=\"gr-select-chevron\"\n class=\"absolute top-1/2 -translate-y-1/2 right-3 flex items-center text-[var(--muted-fg)] pointer-events-none\"\n >\n <span class=\"i-lucide-chevron-down block h-4 w-4\" aria-hidden=\"true\" />\n </span>\n </div>\n\n <div\n v-else\n ref=\"rootEl\"\n v-click-outside=\"{ handler: closeDropdown, enabled: open, exclude: clickOutsideExclude }\"\n data-gr-select\n :class=\"rootClass\"\n >\n <button\n :id=\"resolvedId\"\n data-testid=\"gr-select-trigger\"\n data-gr-select-trigger\n type=\"button\"\n :disabled=\"disabled\"\n :aria-label=\"ariaLabel\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n :aria-describedby=\"describedBy\"\n :aria-required=\"isRequired ? 'true' : undefined\"\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n :aria-controls=\"open ? listboxId : undefined\"\n :aria-activedescendant=\"activeDescendantId\"\n :aria-expanded=\"open ? 'true' : 'false'\"\n :class=\"[baseClassName, triggerClassName]\"\n @click=\"toggleDropdown\"\n @keydown=\"onComboKeydown\"\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=\"gr-select-chevron\"\n class=\"shrink-0 flex items-center text-[var(--muted-fg)] pointer-events-none\"\n >\n <span class=\"i-lucide-chevron-down block h-4 w-4\" aria-hidden=\"true\" />\n </span>\n </button>\n\n <button\n v-if=\"panelClearVisible\"\n data-testid=\"gr-select-clear\"\n data-gr-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=\"resolvedClearLabel\"\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=\"gr-select-panel\"\n data-gr-select-panel\n :style=\"floatingStyle\"\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=\"gr-select-custom-input\"\n type=\"text\"\n :placeholder=\"resolvedCustomValuePlaceholder\"\n size=\"sm\"\n @keydown=\"onComboKeydown\"\n />\n </div>\n\n <div\n :id=\"listboxId\"\n data-gr-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=\"gr-select-add-option\"\n data-gr-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 {{ t('gr.select.addOption', 'Add \"{value}\"', { value: customValue.trim() }) }}\n </button>\n\n <template v-for=\"item in panelItems\" :key=\"item.key\">\n <div\n v-if=\"item.kind === 'group'\"\n data-gr-select-group-label\n role=\"presentation\"\n class=\"px-3 pt-2 pb-1 text-[11px] font-semibold uppercase tracking-wide text-[var(--muted-fg)]\" :class=\"[\n view === 'link' ? 'block min-w-full w-max whitespace-nowrap' : '',\n ]\"\n >\n {{ item.label }}\n </div>\n\n <button\n v-else\n :id=\"optionDomId(item.option.value)\"\n data-gr-select-option\n type=\"button\"\n role=\"option\"\n :disabled=\"item.option.disabled\"\n :aria-selected=\"isSelected(item.option.value) ? 'true' : 'false'\"\n :aria-disabled=\"item.option.disabled ? 'true' : undefined\"\n class=\"rounded-[10px] px-3 py-2 text-left text-[13px]\" :class=\"[\n view === 'link' ? 'block min-w-full w-max whitespace-nowrap' : 'w-full',\n item.option.disabled ? 'cursor-not-allowed opacity-50' : 'hover:bg-[color-mix(in_srgb,var(--muted)_30%,transparent)]',\n activeValue === item.option.value && !item.option.disabled ? 'bg-[color-mix(in_srgb,var(--muted)_30%,transparent)]' : '',\n ]\"\n @click=\"toggleValue(item.option.value)\"\n @mousemove=\"activeIndex = navigableValues.indexOf(item.option.value)\"\n >\n <slot name=\"option\" :option=\"item.option\" :selected=\"isSelected(item.option.value)\">\n <span class=\"flex items-center gap-2 min-w-0\">\n <span\n class=\"inline-block h-4 w-4 shrink-0\"\n :class=\"isSelected(item.option.value) ? 'i-lucide-check text-[var(--primary)]' : ''\"\n aria-hidden=\"true\"\n />\n <span class=\"truncate\">{{ item.option.label }}</span>\n </span>\n </slot>\n </button>\n </template>\n </div>\n </div>\n </div>\n </transition>\n </teleport>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, nextTick, ref, useId, watch } from 'vue'\n\nimport GrInput from '../GrInput/GrInput.vue'\nimport { vClickOutside } from '../../directives'\nimport { useFloating } from '../../composables/internal/useFloating'\nimport { useEscapeToClose } from '../../composables/internal/useEscapeToClose'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { useGrFormFieldContext } from '../GrFormField/context'\n\nimport {\n defaultBaseClass,\n grSelectLinkNativeLabelClass,\n grSelectLinkNativeOverlayClass,\n grSelectNativeClass,\n grSelectPanelClasses,\n grSelectTriggerClass,\n linkBaseClass,\n type GrSelectModelValue,\n type GrSelectOption,\n type GrSelectOptionGroup,\n type GrSelectOptionOrGroup,\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 GrSelectOptionGroup,\n GrSelectOptionOrGroup,\n GrSelectOptionsView,\n GrSelectSize,\n GrSelectUnderline,\n GrSelectVariant,\n GrSelectView,\n} from './grSelectStyles'\n\n/**\n * Пропсы публичного GR-примитива «Select».\n */\nexport interface GrSelectProps {\n modelValue: GrSelectModelValue\n /** Список опций. Поддерживает плоский массив опций и группы опций (`{ label, options }`). */\n options?: GrSelectOptionOrGroup[]\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\"`). i18n-friendly: если не задан — берётся из адаптера перевода (`gr.select.customValuePlaceholder`), иначе — встроенный fallback. */\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: undefined,\n dropdownMaxHeight: 280,\n closeOnSelect: true,\n clearable: false,\n clearLabel: undefined,\n variant: 'primary',\n underline: 'auto',\n },\n)\n\nconst { t } = useGranularityTranslations()\n\nconst resolvedCustomValuePlaceholder = computed(() => {\n return props.customValuePlaceholder ?? t('gr.select.customValuePlaceholder', 'Add value…')\n})\n\nconst resolvedClearLabel = computed(() => props.clearLabel ?? t('gr.common.clear', 'Clear'))\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\n// Fallback из контекста `GrFormField` (id/aria-describedby/invalid/required)\n// для связки с лейблом и сообщением об ошибке.\nconst field = useGrFormFieldContext()\nconst resolvedId = computed(() => field?.id.value)\nconst isInvalid = computed(() => Boolean(field?.invalid.value))\nconst describedBy = computed(() => field?.describedById.value)\nconst isRequired = computed(() => Boolean(field?.required.value))\n\nconst optionsResolved = computed<GrSelectOptionOrGroup[]>(() => props.options ?? [])\n\nfunction isOptionGroup(item: GrSelectOptionOrGroup): item is GrSelectOptionGroup {\n return Array.isArray((item as GrSelectOptionGroup).options)\n}\n\n/** Плоский список всех опций (группы «развёрнуты»). Используется для всех вычислений по значениям. */\nconst flatOptions = computed<GrSelectOption[]>(() => {\n const result: GrSelectOption[] = []\n for (const item of optionsResolved.value) {\n if (isOptionGroup(item)) result.push(...item.options)\n else result.push(item)\n }\n return result\n})\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(flatOptions.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 flatOptions.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) => flatOptions.value.find((o) => o.value === v)?.label ?? v)\n .join(', ')\n }\n\n return flatOptions.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\n// Нативный `<select multiple>` — это многострочный listbox, а не поповер. В режиме\n// `view=\"link\"` он рендерится невидимым overlay'ем, из-за чего multiple-вариант «не\n// открывается» (клик по невидимому listbox'у ничего не показывает). Для этой\n// неподдерживаемой комбинации форсим кастомную панель, которая корректно открывается.\nconst effectiveOptionsView = computed<GrSelectOptionsView>(() => {\n if (props.view === 'link' && props.multiple && props.optionsView === 'native')\n return 'panel'\n return props.optionsView\n})\n\nconst nativeCustomOptionVisible = computed(() => {\n if (!props.allowCustomValue) return false\n if (effectiveOptionsView.value !== '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]\n\n// Для `view='link'` ширина триггера = ширине выбранной опции (`inline-block w-auto`),\n// поэтому панель растёт по контенту (`matchWidth: 'min'` → `width: max-content` +\n// `min-width` от триггера), а не сжимается до неё. Для `view='default'` панель точно\n// повторяет ширину триггера (`matchWidth: true`).\nconst { floatingStyle, update: updateFloatingPosition } = useFloating(rootEl, panelEl, open, {\n placement: 'bottom-start',\n matchWidth: () => (props.view === 'link' ? 'min' : true),\n zIndexVar: '--gr-z-dropdown',\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\nuseEscapeToClose(open, closeDropdown)\n\n// `view` определяет режим `matchWidth` (см. выше) — пересчитываем позицию/ширину\n// панели, если он меняется, пока панель открыта.\nwatch(\n () => props.view,\n () => {\n if (open.value) updateFloatingPosition()\n },\n)\n\nconst panelClasses = computed(() => {\n return grSelectPanelClasses\n})\n\nfunction matchesQuery(option: GrSelectOption, query: string): boolean {\n if (!query) return true\n return option.label.toLowerCase().includes(query) || option.value.toLowerCase().includes(query)\n}\n\n/**\n * Элемент рендера панели: либо заголовок группы, либо опция.\n * Группировка сохраняется, фильтрация по `customValue` скрывает пустые группы.\n */\ntype GrSelectPanelItem =\n | { kind: 'group', label: string, key: string }\n | { kind: 'option', option: GrSelectOption, key: string }\n\nconst panelItems = computed<GrSelectPanelItem[]>(() => {\n const q = props.allowCustomValue ? customValue.value.trim().toLowerCase() : ''\n const items: GrSelectPanelItem[] = []\n\n // Опция для кастомного значения, которого нет в options (single).\n if (props.allowCustomValue && !props.multiple && modelSingle.value !== '' && !hasModelInOptions.value) {\n const custom: GrSelectOption = { value: modelSingle.value, label: modelSingle.value }\n if (matchesQuery(custom, q)) {\n items.push({ kind: 'option', option: custom, key: `__custom__${custom.value}` })\n }\n }\n\n optionsResolved.value.forEach((item, index) => {\n if (isOptionGroup(item)) {\n const matched = item.options.filter((o) => matchesQuery(o, q))\n if (!matched.length) return\n items.push({ kind: 'group', label: item.label, key: `__group__${index}` })\n for (const option of matched) {\n // Ключ с индексом группы — одинаковое `value` в разных группах больше не даёт дубликат.\n items.push({ kind: 'option', option, key: `${index}:${option.value}` })\n }\n return\n }\n\n if (matchesQuery(item, q)) {\n items.push({ kind: 'option', option: item, key: `${index}:${item.value}` })\n }\n })\n\n return items\n})\n\nconst canAddCustom = computed(() => {\n if (!props.allowCustomValue) return false\n if (effectiveOptionsView.value !== '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 !flatOptions.value.some((o) => o.value === v)\n }\n\n if (v === modelSingle.value) return false\n return !flatOptions.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 isOptionDisabled(value: string): boolean {\n return flatOptions.value.find(o => o.value === value)?.disabled === true\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 (isOptionDisabled(value)) return\n\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\n// ————— Клавиатурная навигация комбобокса (WAI-ARIA, aria-activedescendant).\nconst listboxId = useId()\nconst activeIndex = ref(-1)\n\nfunction optionDomId(value: string): string {\n return `${listboxId}-opt-${value}`\n}\n\n// Навигируемые (видимые, не-disabled) опции панели в порядке рендера.\nconst navigableValues = computed<string[]>(() =>\n panelItems.value\n .filter((item): item is Extract<GrSelectPanelItem, { kind: 'option' }> => item.kind === 'option' && !item.option.disabled)\n .map(item => item.option.value),\n)\n\nconst activeValue = computed(() => (activeIndex.value >= 0 ? navigableValues.value[activeIndex.value] : undefined))\nconst activeDescendantId = computed(() =>\n open.value && activeValue.value !== undefined ? optionDomId(activeValue.value) : undefined,\n)\n\nfunction clampActive(index: number): number {\n const len = navigableValues.value.length\n if (len === 0) return -1\n return ((index % len) + len) % len\n}\n\nasync function scrollActiveIntoView(): Promise<void> {\n await nextTick()\n const value = activeValue.value\n if (value === undefined) return\n const el = document.getElementById(optionDomId(value))\n el?.scrollIntoView?.({ block: 'nearest' })\n}\n\nfunction setActive(index: number): void {\n activeIndex.value = clampActive(index)\n void scrollActiveIntoView()\n}\n\nfunction initActiveIndex(): void {\n const selectedIdx = navigableValues.value.findIndex(v => isSelected(v))\n activeIndex.value = selectedIdx >= 0 ? selectedIdx : (navigableValues.value.length ? 0 : -1)\n}\n\nfunction openDropdown(): void {\n if (props.disabled || open.value) return\n open.value = true\n}\n\nlet typeaheadBuffer = ''\nlet typeaheadTimer: ReturnType<typeof setTimeout> | null = null\nfunction typeahead(char: string): void {\n typeaheadBuffer += char.toLowerCase()\n if (typeaheadTimer) clearTimeout(typeaheadTimer)\n typeaheadTimer = setTimeout(() => { typeaheadBuffer = '' }, 600)\n\n const idx = navigableValues.value.findIndex((v) => {\n const opt = flatOptions.value.find(o => o.value === v)\n return opt?.label.toLowerCase().startsWith(typeaheadBuffer)\n })\n if (idx >= 0) setActive(idx)\n}\n\nfunction onComboKeydown(event: KeyboardEvent): void {\n if (props.disabled) return\n\n if (!open.value) {\n if (['ArrowDown', 'ArrowUp', 'Enter', ' '].includes(event.key)) {\n event.preventDefault()\n openDropdown()\n }\n return\n }\n\n switch (event.key) {\n case 'ArrowDown':\n event.preventDefault()\n setActive(activeIndex.value + 1)\n break\n case 'ArrowUp':\n event.preventDefault()\n setActive(activeIndex.value - 1)\n break\n case 'Home':\n event.preventDefault()\n setActive(0)\n break\n case 'End':\n event.preventDefault()\n setActive(navigableValues.value.length - 1)\n break\n case 'Enter':\n event.preventDefault()\n if (props.allowCustomValue && canAddCustom.value)\n addCustom()\n else if (activeValue.value !== undefined)\n toggleValue(activeValue.value)\n break\n case 'Tab':\n closeDropdown()\n break\n default:\n // typeahead — только без allowCustomValue (иначе мешает вводу в custom-инпут).\n if (!props.allowCustomValue && event.key.length === 1 && !event.metaKey && !event.ctrlKey && !event.altKey)\n typeahead(event.key)\n }\n}\n\nwatch(\n open,\n async (isOpen) => {\n if (!isOpen) {\n customValue.value = ''\n activeIndex.value = -1\n return\n }\n\n initActiveIndex()\n\n if (props.allowCustomValue) {\n await nextTick()\n customInputRef.value?.focus()\n }\n },\n { immediate: true },\n)\n\nconst showNativeChevron = computed(() => {\n return effectiveOptionsView.value === 'native' && props.view !== 'link' && !props.multiple\n})\n\nconst nativeClearOptionVisible = computed(() => {\n if (!props.clearable) return false\n if (effectiveOptionsView.value !== 'native') return false\n if (props.multiple) return false\n if (!props.options) return true\n return !flatOptions.value.some(o => o.value === '')\n})\n\nconst panelClearVisible = computed(() => {\n if (!props.clearable) return false\n if (effectiveOptionsView.value !== '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' && effectiveOptionsView.value === '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: effectiveOptionsView.value,\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=\"effectiveOptionsView === 'native'\"\n data-gr-select\n :class=\"rootClass\"\n >\n <!--\n Выбор задаём per-option через `:selected` (а не `:value` на `<select>`):\n для `<select multiple>` биндинг `:value` массивом не работает — DOM не\n отражает программное изменение модели. `onChange` читает выбор из DOM.\n -->\n <select\n :id=\"resolvedId\"\n data-gr-select-native\n :multiple=\"multiple\"\n :disabled=\"disabled\"\n :aria-label=\"ariaLabel\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n :aria-describedby=\"describedBy\"\n :aria-required=\"isRequired ? 'true' : undefined\"\n :class=\"isLinkNative ? grSelectLinkNativeOverlayClass : [baseClassName, nativeClassName]\"\n @change=\"onChange\"\n >\n <option v-if=\"nativeClearOptionVisible\" value=\"\" :selected=\"!multiple && modelSingle === ''\">\n {{ placeholder || t('gr.select.clearOption', 'None') }}\n </option>\n\n <slot>\n <option v-if=\"nativeCustomOptionVisible\" :value=\"modelSingle\" :selected=\"!multiple\">\n {{ modelSingle }}\n </option>\n <template v-for=\"(item, index) in optionsResolved\" :key=\"index\">\n <optgroup v-if=\"isOptionGroup(item)\" :label=\"item.label\">\n <option\n v-for=\"opt in item.options\"\n :key=\"`${index}:${opt.value}`\"\n :value=\"opt.value\"\n :selected=\"multiple ? modelMultiple.includes(opt.value) : modelSingle === opt.value\"\n :disabled=\"opt.disabled\"\n >\n {{ opt.label }}\n </option>\n </optgroup>\n <option\n v-else\n :value=\"item.value\"\n :selected=\"multiple ? modelMultiple.includes(item.value) : modelSingle === item.value\"\n :disabled=\"item.disabled\"\n >\n {{ item.label }}\n </option>\n </template>\n </slot>\n </select>\n\n <span\n v-if=\"isLinkNative\"\n data-gr-select-link-label\n aria-hidden=\"true\"\n :class=\"linkNativeLabelClassName\"\n >{{ linkNativeDisplayText }}</span>\n\n <span\n v-if=\"showNativeChevron\"\n data-testid=\"gr-select-chevron\"\n class=\"absolute top-1/2 -translate-y-1/2 right-3 flex items-center text-[var(--muted-fg)] pointer-events-none\"\n >\n <span class=\"i-lucide-chevron-down block h-4 w-4\" aria-hidden=\"true\" />\n </span>\n </div>\n\n <div\n v-else\n ref=\"rootEl\"\n v-click-outside=\"{ handler: closeDropdown, enabled: open, exclude: clickOutsideExclude }\"\n data-gr-select\n :class=\"rootClass\"\n >\n <button\n :id=\"resolvedId\"\n data-testid=\"gr-select-trigger\"\n data-gr-select-trigger\n type=\"button\"\n :disabled=\"disabled\"\n :aria-label=\"ariaLabel\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n :aria-describedby=\"describedBy\"\n :aria-required=\"isRequired ? 'true' : undefined\"\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n :aria-controls=\"open ? listboxId : undefined\"\n :aria-activedescendant=\"activeDescendantId\"\n :aria-expanded=\"open ? 'true' : 'false'\"\n :class=\"[baseClassName, triggerClassName]\"\n @click=\"toggleDropdown\"\n @keydown=\"onComboKeydown\"\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=\"gr-select-chevron\"\n class=\"shrink-0 flex items-center text-[var(--muted-fg)] pointer-events-none\"\n >\n <span class=\"i-lucide-chevron-down block h-4 w-4\" aria-hidden=\"true\" />\n </span>\n </button>\n\n <button\n v-if=\"panelClearVisible\"\n data-testid=\"gr-select-clear\"\n data-gr-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=\"resolvedClearLabel\"\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=\"gr-select-panel\"\n data-gr-select-panel\n :style=\"floatingStyle\"\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=\"gr-select-custom-input\"\n type=\"text\"\n :placeholder=\"resolvedCustomValuePlaceholder\"\n size=\"sm\"\n @keydown=\"onComboKeydown\"\n />\n </div>\n\n <div\n :id=\"listboxId\"\n data-gr-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=\"gr-select-add-option\"\n data-gr-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 {{ t('gr.select.addOption', 'Add \"{value}\"', { value: customValue.trim() }) }}\n </button>\n\n <template v-for=\"item in panelItems\" :key=\"item.key\">\n <div\n v-if=\"item.kind === 'group'\"\n data-gr-select-group-label\n role=\"presentation\"\n class=\"px-3 pt-2 pb-1 text-[11px] font-semibold uppercase tracking-wide text-[var(--muted-fg)]\" :class=\"[\n view === 'link' ? 'block min-w-full w-max whitespace-nowrap' : '',\n ]\"\n >\n {{ item.label }}\n </div>\n\n <button\n v-else\n :id=\"optionDomId(item.option.value)\"\n data-gr-select-option\n type=\"button\"\n role=\"option\"\n :disabled=\"item.option.disabled\"\n :aria-selected=\"isSelected(item.option.value) ? 'true' : 'false'\"\n :aria-disabled=\"item.option.disabled ? 'true' : undefined\"\n class=\"rounded-[10px] px-3 py-2 text-left text-[13px]\" :class=\"[\n view === 'link' ? 'block min-w-full w-max whitespace-nowrap' : 'w-full',\n item.option.disabled ? 'cursor-not-allowed opacity-50' : 'hover:bg-[color-mix(in_srgb,var(--muted)_30%,transparent)]',\n activeValue === item.option.value && !item.option.disabled ? 'bg-[color-mix(in_srgb,var(--muted)_30%,transparent)]' : '',\n ]\"\n @click=\"toggleValue(item.option.value)\"\n @mousemove=\"activeIndex = navigableValues.indexOf(item.option.value)\"\n >\n <slot name=\"option\" :option=\"item.option\" :selected=\"isSelected(item.option.value)\">\n <span class=\"flex items-center gap-2 min-w-0\">\n <span\n class=\"inline-block h-4 w-4 shrink-0\"\n :class=\"isSelected(item.option.value) ? 'i-lucide-check text-[var(--primary)]' : ''\"\n aria-hidden=\"true\"\n />\n <span class=\"truncate\">{{ item.option.label }}</span>\n </span>\n </slot>\n </button>\n </template>\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-RShK21lK.js";
1
+ import { t as GrSelect_default } from "./chunks/GrSelect-BVCmitqa.js";
2
2
  import { n as grSelectSafelist, t as grSelectConfig } from "../../chunks/config-B7IIWGt_2.js";
3
3
  export { GrSelect_default as GrSelect, GrSelect_default as default, grSelectConfig, grSelectSafelist };
@@ -1,3 +1,3 @@
1
1
  import { n as GrSidebar_default, t as GrSidebarItem_default } from "./chunks/GrSidebar-DkWozzwA.js";
2
- import { t as grSidebarConfig } from "../../chunks/config-CGHLoBlH2.js";
2
+ import { t as grSidebarConfig } from "../../chunks/config-CGHLoBlH.js";
3
3
  export { GrSidebar_default as GrSidebar, GrSidebar_default as default, GrSidebarItem_default as GrSidebarItem, grSidebarConfig };
@@ -0,0 +1,68 @@
1
+ import { computed, createCommentVNode, createElementBlock, defineComponent, inject, openBlock, provide, renderSlot, useId } from "vue";
2
+ //#region src/components/GrTabPanels/context.ts
3
+ var GR_TAB_PANELS_KEY = Symbol("gr-tab-panels");
4
+ //#endregion
5
+ //#region src/components/GrTabPanels/GrTabPanels.vue?vue&type=script&setup=true&lang.ts
6
+ var _hoisted_1$1 = { "data-gr-tab-panels": "" };
7
+ //#endregion
8
+ //#region src/components/GrTabPanels/GrTabPanels.vue
9
+ var GrTabPanels_default = /* @__PURE__ */ defineComponent({
10
+ __name: "GrTabPanels",
11
+ props: {
12
+ modelValue: {},
13
+ idBase: { default: void 0 }
14
+ },
15
+ setup(__props) {
16
+ const props = __props;
17
+ const generatedIdBase = useId();
18
+ const resolvedIdBase = props.idBase ?? generatedIdBase;
19
+ provide(GR_TAB_PANELS_KEY, {
20
+ activeValue: computed(() => props.modelValue),
21
+ idBase: resolvedIdBase
22
+ });
23
+ return (_ctx, _cache) => {
24
+ return openBlock(), createElementBlock("div", _hoisted_1$1, [renderSlot(_ctx.$slots, "default")]);
25
+ };
26
+ }
27
+ });
28
+ //#endregion
29
+ //#region src/components/GrTabPanels/GrTabPanel.vue?vue&type=script&setup=true&lang.ts
30
+ var _hoisted_1 = [
31
+ "id",
32
+ "aria-labelledby",
33
+ "hidden",
34
+ "tabindex"
35
+ ];
36
+ //#endregion
37
+ //#region src/components/GrTabPanels/GrTabPanel.vue
38
+ var GrTabPanel_default = /* @__PURE__ */ defineComponent({
39
+ __name: "GrTabPanel",
40
+ props: {
41
+ value: {},
42
+ keepAlive: {
43
+ type: Boolean,
44
+ default: false
45
+ }
46
+ },
47
+ setup(__props) {
48
+ const props = __props;
49
+ const ctx = inject(GR_TAB_PANELS_KEY, null);
50
+ const isActive = computed(() => ctx ? ctx.activeValue.value === props.value : true);
51
+ const panelId = computed(() => ctx ? `${ctx.idBase}-panel-${props.value}` : void 0);
52
+ const tabId = computed(() => ctx ? `${ctx.idBase}-tab-${props.value}` : void 0);
53
+ return (_ctx, _cache) => {
54
+ return isActive.value || __props.keepAlive ? (openBlock(), createElementBlock("div", {
55
+ key: 0,
56
+ id: panelId.value,
57
+ role: "tabpanel",
58
+ "data-gr-tab-panel": "",
59
+ "aria-labelledby": tabId.value,
60
+ hidden: __props.keepAlive && !isActive.value ? true : void 0,
61
+ tabindex: isActive.value ? 0 : void 0,
62
+ class: "rounded-[var(--gr-radius-md)] focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ring)]"
63
+ }, [renderSlot(_ctx.$slots, "default")], 8, _hoisted_1)) : createCommentVNode("", true);
64
+ };
65
+ }
66
+ });
67
+ //#endregion
68
+ export { GrTabPanels_default as n, GR_TAB_PANELS_KEY as r, GrTabPanel_default as t };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GrTabPanels-DbKybwe-.js","names":[],"sources":["../../../../src/components/GrTabPanels/context.ts","../../../../src/components/GrTabPanels/GrTabPanels.vue","../../../../src/components/GrTabPanels/GrTabPanels.vue","../../../../src/components/GrTabPanels/GrTabPanel.vue","../../../../src/components/GrTabPanels/GrTabPanel.vue"],"sourcesContent":["import type { ComputedRef, InjectionKey } from 'vue'\n\n/** Контекст, который `GrTabPanels` предоставляет своим `GrTabPanel`. */\nexport interface GrTabPanelsContext {\n /** Значение активной вкладки (совпадает с `modelValue` у `GrTabs`). */\n activeValue: ComputedRef<string>\n /** Общая база id для связки `tab`↔`tabpanel` (см. проп `idBase` у `GrTabs`). */\n idBase: string\n}\n\nexport const GR_TAB_PANELS_KEY: InjectionKey<GrTabPanelsContext> = Symbol('gr-tab-panels')\n","<script setup lang=\"ts\">\nimport { computed, provide, useId } from 'vue'\n\nimport { GR_TAB_PANELS_KEY } from './context'\n\n/**\n * GrTabPanels — контейнер `tabpanel`-ов, companion к `GrTabs`.\n *\n * Показывает панель, чьё `value` совпадает с `modelValue`, и связывает её с\n * соответствующей вкладкой по ARIA (`role=\"tabpanel\"`, `aria-labelledby`).\n *\n * Для полной связки `tab`↔`tabpanel` передайте **одинаковый `idBase`** и в\n * `GrTabs`, и в `GrTabPanels` — тогда id вкладок и панелей совпадут\n * (`aria-controls` ↔ `aria-labelledby`).\n */\nconst props = withDefaults(\n defineProps<{\n modelValue: string\n idBase?: string\n }>(),\n {\n idBase: undefined,\n },\n)\n\nconst generatedIdBase = useId()\nconst resolvedIdBase = props.idBase ?? generatedIdBase\n\nprovide(GR_TAB_PANELS_KEY, {\n activeValue: computed(() => props.modelValue),\n idBase: resolvedIdBase,\n})\n</script>\n\n<template>\n <div data-gr-tab-panels>\n <slot />\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, provide, useId } from 'vue'\n\nimport { GR_TAB_PANELS_KEY } from './context'\n\n/**\n * GrTabPanels — контейнер `tabpanel`-ов, companion к `GrTabs`.\n *\n * Показывает панель, чьё `value` совпадает с `modelValue`, и связывает её с\n * соответствующей вкладкой по ARIA (`role=\"tabpanel\"`, `aria-labelledby`).\n *\n * Для полной связки `tab`↔`tabpanel` передайте **одинаковый `idBase`** и в\n * `GrTabs`, и в `GrTabPanels` — тогда id вкладок и панелей совпадут\n * (`aria-controls` ↔ `aria-labelledby`).\n */\nconst props = withDefaults(\n defineProps<{\n modelValue: string\n idBase?: string\n }>(),\n {\n idBase: undefined,\n },\n)\n\nconst generatedIdBase = useId()\nconst resolvedIdBase = props.idBase ?? generatedIdBase\n\nprovide(GR_TAB_PANELS_KEY, {\n activeValue: computed(() => props.modelValue),\n idBase: resolvedIdBase,\n})\n</script>\n\n<template>\n <div data-gr-tab-panels>\n <slot />\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, inject } from 'vue'\n\nimport { GR_TAB_PANELS_KEY } from './context'\n\n/**\n * GrTabPanel — одна панель внутри `GrTabPanels`. Показывается, когда её `value`\n * равно активной вкладке. `keepAlive` оставляет неактивные панели в DOM\n * (скрытыми через `hidden`) — полезно, чтобы не терять состояние форм.\n */\nconst props = withDefaults(\n defineProps<{\n value: string\n keepAlive?: boolean\n }>(),\n {\n keepAlive: false,\n },\n)\n\nconst ctx = inject(GR_TAB_PANELS_KEY, null)\n\nconst isActive = computed(() => (ctx ? ctx.activeValue.value === props.value : true))\n// id совпадают с id вкладок `GrTabs` при одинаковом `idBase` → корректная ARIA-связка.\nconst panelId = computed(() => (ctx ? `${ctx.idBase}-panel-${props.value}` : undefined))\nconst tabId = computed(() => (ctx ? `${ctx.idBase}-tab-${props.value}` : undefined))\n</script>\n\n<template>\n <div\n v-if=\"isActive || keepAlive\"\n :id=\"panelId\"\n role=\"tabpanel\"\n data-gr-tab-panel\n :aria-labelledby=\"tabId\"\n :hidden=\"keepAlive && !isActive ? true : undefined\"\n :tabindex=\"isActive ? 0 : undefined\"\n class=\"rounded-[var(--gr-radius-md)] focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ring)]\"\n >\n <slot />\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, inject } from 'vue'\n\nimport { GR_TAB_PANELS_KEY } from './context'\n\n/**\n * GrTabPanel — одна панель внутри `GrTabPanels`. Показывается, когда её `value`\n * равно активной вкладке. `keepAlive` оставляет неактивные панели в DOM\n * (скрытыми через `hidden`) — полезно, чтобы не терять состояние форм.\n */\nconst props = withDefaults(\n defineProps<{\n value: string\n keepAlive?: boolean\n }>(),\n {\n keepAlive: false,\n },\n)\n\nconst ctx = inject(GR_TAB_PANELS_KEY, null)\n\nconst isActive = computed(() => (ctx ? ctx.activeValue.value === props.value : true))\n// id совпадают с id вкладок `GrTabs` при одинаковом `idBase` → корректная ARIA-связка.\nconst panelId = computed(() => (ctx ? `${ctx.idBase}-panel-${props.value}` : undefined))\nconst tabId = computed(() => (ctx ? `${ctx.idBase}-tab-${props.value}` : undefined))\n</script>\n\n<template>\n <div\n v-if=\"isActive || keepAlive\"\n :id=\"panelId\"\n role=\"tabpanel\"\n data-gr-tab-panel\n :aria-labelledby=\"tabId\"\n :hidden=\"keepAlive && !isActive ? true : undefined\"\n :tabindex=\"isActive ? 0 : undefined\"\n class=\"rounded-[var(--gr-radius-md)] focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ring)]\"\n >\n <slot />\n </div>\n</template>\n"],"mappings":";;AAUA,IAAa,oBAAsD,OAAO,eAAe;;;;;;;;;;;;;ECKzF,MAAM,QAAQ;EAUd,MAAM,kBAAkB,MAAM;EAC9B,MAAM,iBAAiB,MAAM,UAAU;EAEvC,QAAQ,mBAAmB;GACzB,aAAa,eAAe,MAAM,UAAU;GAC5C,QAAQ;EACV,CAAC;;uBAIC,mBAEM,OAFN,cAEM,CADJ,WAAQ,KAAA,QAAA,SAAA,CAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;;;EE1BZ,MAAM,QAAQ;EAUd,MAAM,MAAM,OAAO,mBAAmB,IAAI;EAE1C,MAAM,WAAW,eAAgB,MAAM,IAAI,YAAY,UAAU,MAAM,QAAQ,IAAK;EAEpF,MAAM,UAAU,eAAgB,MAAM,GAAG,IAAI,OAAO,SAAS,MAAM,UAAU,KAAA,CAAU;EACvF,MAAM,QAAQ,eAAgB,MAAM,GAAG,IAAI,OAAO,OAAO,MAAM,UAAU,KAAA,CAAU;;UAKzE,SAAA,SAAY,QAAA,aAAA,UAAA,GADpB,mBAWM,OAAA;;IATH,IAAI,QAAA;IACL,MAAK;IACL,qBAAA;IACC,mBAAiB,MAAA;IACjB,QAAQ,QAAA,aAAS,CAAK,SAAA,QAAQ,OAAU,KAAA;IACxC,UAAU,SAAA,QAAQ,IAAO,KAAA;IAC1B,OAAM;OAEN,WAAQ,KAAA,QAAA,SAAA,CAAA,GAAA,GAAA,UAAA,KAAA,mBAAA,IAAA,IAAA"}
@@ -0,0 +1,3 @@
1
+ import { n as GrTabPanels_default, r as GR_TAB_PANELS_KEY, t as GrTabPanel_default } from "./chunks/GrTabPanels-DbKybwe-.js";
2
+ import { t as grTabPanelsConfig } from "../../chunks/config-eXB87DOi2.js";
3
+ export { GR_TAB_PANELS_KEY, GrTabPanel_default as GrTabPanel, GrTabPanels_default as GrTabPanels, GrTabPanels_default as default, grTabPanelsConfig };
@@ -1,6 +1,8 @@
1
1
  import { Fragment, computed, createCommentVNode, createElementBlock, createElementVNode, defineComponent, nextTick, normalizeClass, openBlock, ref, renderList, toDisplayString } from "vue";
2
2
  //#region src/components/GrTabs/GrTabs.vue?vue&type=script&setup=true&lang.ts
3
3
  var _hoisted_1 = [
4
+ "id",
5
+ "aria-controls",
4
6
  "aria-selected",
5
7
  "aria-disabled",
6
8
  "disabled",
@@ -18,7 +20,8 @@ var GrTabs_default = /* @__PURE__ */ defineComponent({
18
20
  __name: "GrTabs",
19
21
  props: {
20
22
  modelValue: {},
21
- tabs: {}
23
+ tabs: {},
24
+ idBase: {}
22
25
  },
23
26
  emits: ["update:modelValue"],
24
27
  setup(__props, { emit: __emit }) {
@@ -103,6 +106,8 @@ var GrTabs_default = /* @__PURE__ */ defineComponent({
103
106
  type: "button",
104
107
  role: "tab",
105
108
  "data-gr-tab": "",
109
+ id: __props.idBase ? `${__props.idBase}-tab-${tab.value}` : void 0,
110
+ "aria-controls": __props.idBase ? `${__props.idBase}-panel-${tab.value}` : void 0,
106
111
  "aria-selected": tab.value === __props.modelValue ? "true" : "false",
107
112
  "aria-disabled": tab.disabled ? "true" : void 0,
108
113
  disabled: tab.disabled,
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GrTabs-DsLwWoRz.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 * База id для ARIA-связки с `GrTabPanels`. Если задана, каждая вкладка\n * получает `id=\"<idBase>-tab-<value>\"` и `aria-controls=\"<idBase>-panel-<value>\"`.\n * Передайте тот же `idBase` в `GrTabPanels`, чтобы связать `tab`↔`tabpanel`.\n */\n idBase?: string\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-gr-tabs\n class=\"inline-flex flex-wrap gap-1 rounded-[var(--gr-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-gr-tab\n :id=\"idBase ? `${idBase}-tab-${tab.value}` : undefined\"\n :aria-controls=\"idBase ? `${idBase}-panel-${tab.value}` : undefined\"\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 * База id для ARIA-связки с `GrTabPanels`. Если задана, каждая вкладка\n * получает `id=\"<idBase>-tab-<value>\"` и `aria-controls=\"<idBase>-panel-<value>\"`.\n * Передайте тот же `idBase` в `GrTabPanels`, чтобы связать `tab`↔`tabpanel`.\n */\n idBase?: string\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-gr-tabs\n class=\"inline-flex flex-wrap gap-1 rounded-[var(--gr-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-gr-tab\n :id=\"idBase ? `${idBase}-tab-${tab.value}` : undefined\"\n :aria-controls=\"idBase ? `${idBase}-panel-${tab.value}` : undefined\"\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":"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"}
@@ -1,3 +1,3 @@
1
- import { t as GrTabs_default } from "./chunks/GrTabs-Z-FDJUr_.js";
1
+ import { t as GrTabs_default } from "./chunks/GrTabs-DsLwWoRz.js";
2
2
  import { t as grTabsConfig } from "../../chunks/config-BLHqZhy-2.js";
3
3
  export { GrTabs_default as GrTabs, GrTabs_default as default, grTabsConfig };
@@ -1,4 +1,5 @@
1
- import { r as grTextareaClass } from "../../../chunks/config-1uSYl98Y.js";
1
+ import { n as useGrFormFieldContext } from "../../../chunks/context-DdNIxa1w.js";
2
+ import { r as grTextareaClass } from "../../../chunks/config-1uSYl98Y2.js";
2
3
  import { computed, createElementBlock, defineComponent, normalizeClass, openBlock } from "vue";
3
4
  //#region src/components/GrTextarea/GrTextarea.vue?vue&type=script&setup=true&lang.ts
4
5
  var _hoisted_1 = [
@@ -9,7 +10,9 @@ var _hoisted_1 = [
9
10
  "placeholder",
10
11
  "disabled",
11
12
  "value",
12
- "aria-invalid"
13
+ "aria-invalid",
14
+ "aria-describedby",
15
+ "aria-required"
13
16
  ];
14
17
  //#endregion
15
18
  //#region src/components/GrTextarea/GrTextarea.vue
@@ -43,16 +46,21 @@ var GrTextarea_default = /* @__PURE__ */ defineComponent({
43
46
  */
44
47
  const props = __props;
45
48
  const emit = __emit;
49
+ const field = useGrFormFieldContext();
50
+ const resolvedId = computed(() => props.id ?? field?.id.value);
51
+ const isInvalid = computed(() => props.invalid || Boolean(field?.invalid.value));
52
+ const describedBy = computed(() => field?.describedById.value);
53
+ const isRequired = computed(() => Boolean(field?.required.value));
46
54
  const className = computed(() => grTextareaClass({
47
55
  state: props.state,
48
- invalid: props.invalid
56
+ invalid: isInvalid.value
49
57
  }));
50
58
  function onInput(e) {
51
59
  emit("update:modelValue", e.target.value);
52
60
  }
53
61
  return (_ctx, _cache) => {
54
62
  return openBlock(), createElementBlock("textarea", {
55
- id: __props.id,
63
+ id: resolvedId.value,
56
64
  "data-gr-textarea": "",
57
65
  name: __props.name,
58
66
  rows: __props.rows,
@@ -60,7 +68,9 @@ var GrTextarea_default = /* @__PURE__ */ defineComponent({
60
68
  placeholder: __props.placeholder,
61
69
  disabled: __props.disabled,
62
70
  value: __props.modelValue,
63
- "aria-invalid": __props.invalid ? "true" : void 0,
71
+ "aria-invalid": isInvalid.value ? "true" : void 0,
72
+ "aria-describedby": describedBy.value,
73
+ "aria-required": isRequired.value ? "true" : void 0,
64
74
  class: normalizeClass(["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", className.value]),
65
75
  onInput
66
76
  }, null, 42, _hoisted_1);
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GrTextarea-CKukGlNl.js","names":[],"sources":["../../../../src/components/GrTextarea/GrTextarea.vue","../../../../src/components/GrTextarea/GrTextarea.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * GrTextarea — многострочное поле ввода GR-примитива.\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'\nimport { useGrFormFieldContext } from '../GrFormField/context'\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\n// Fallback из контекста `GrFormField` (id/aria-describedby/invalid/required).\nconst field = useGrFormFieldContext()\nconst resolvedId = computed(() => props.id ?? field?.id.value)\nconst isInvalid = computed(() => props.invalid || Boolean(field?.invalid.value))\nconst describedBy = computed(() => field?.describedById.value)\nconst isRequired = computed(() => Boolean(field?.required.value))\n\nconst className = computed(() => grTextareaClass({\n state: props.state,\n invalid: isInvalid.value,\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=\"resolvedId\"\n data-gr-textarea\n :name=\"name\"\n :rows=\"rows\"\n :autocomplete=\"autocomplete\"\n :placeholder=\"placeholder\"\n :disabled=\"disabled\"\n :value=\"modelValue\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n :aria-describedby=\"describedBy\"\n :aria-required=\"isRequired ? '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 — многострочное поле ввода GR-примитива.\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'\nimport { useGrFormFieldContext } from '../GrFormField/context'\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\n// Fallback из контекста `GrFormField` (id/aria-describedby/invalid/required).\nconst field = useGrFormFieldContext()\nconst resolvedId = computed(() => props.id ?? field?.id.value)\nconst isInvalid = computed(() => props.invalid || Boolean(field?.invalid.value))\nconst describedBy = computed(() => field?.describedById.value)\nconst isRequired = computed(() => Boolean(field?.required.value))\n\nconst className = computed(() => grTextareaClass({\n state: props.state,\n invalid: isInvalid.value,\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=\"resolvedId\"\n data-gr-textarea\n :name=\"name\"\n :rows=\"rows\"\n :autocomplete=\"autocomplete\"\n :placeholder=\"placeholder\"\n :disabled=\"disabled\"\n :value=\"modelValue\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n :aria-describedby=\"describedBy\"\n :aria-required=\"isRequired ? '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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAYA,MAAM,QAAQ;EAqBd,MAAM,OAAO;EAOb,MAAM,QAAQ,sBAAsB;EACpC,MAAM,aAAa,eAAe,MAAM,MAAM,OAAO,GAAG,KAAK;EAC7D,MAAM,YAAY,eAAe,MAAM,WAAW,QAAQ,OAAO,QAAQ,KAAK,CAAC;EAC/E,MAAM,cAAc,eAAe,OAAO,cAAc,KAAK;EAC7D,MAAM,aAAa,eAAe,QAAQ,OAAO,SAAS,KAAK,CAAC;EAEhE,MAAM,YAAY,eAAe,gBAAgB;GAC/C,OAAO,MAAM;GACb,SAAS,UAAU;EACrB,CAAC,CAAC;EAEF,SAAS,QAAQ,GAAgB;GAC/B,KAAK,qBAAsB,EAAE,OAA+B,KAAK;EACnE;;uBAIE,mBAeE,YAAA;IAdC,IAAI,WAAA;IACL,oBAAA;IACC,MAAM,QAAA;IACN,MAAM,QAAA;IACN,cAAc,QAAA;IACd,aAAa,QAAA;IACb,UAAU,QAAA;IACV,OAAO,QAAA;IACP,gBAAc,UAAA,QAAS,SAAY,KAAA;IACnC,oBAAkB,YAAA;IAClB,iBAAe,WAAA,QAAU,SAAY,KAAA;IACtC,OAAK,eAAA,CAAC,6QACE,UAAA,KAAS,CAAA;IACT"}
@@ -1,3 +1,3 @@
1
- import { n as grTextareaSafelist, t as grTextareaConfig } from "../../chunks/config-1uSYl98Y.js";
2
- import { t as GrTextarea_default } from "./chunks/GrTextarea-D56YxhKq.js";
1
+ import { n as grTextareaSafelist, t as grTextareaConfig } from "../../chunks/config-1uSYl98Y2.js";
2
+ import { t as GrTextarea_default } from "./chunks/GrTextarea-CKukGlNl.js";
3
3
  export { GrTextarea_default as GrTextarea, GrTextarea_default as default, grTextareaConfig, grTextareaSafelist };
@@ -6,7 +6,7 @@ import { t as _plugin_vue_export_helper_default } from "../../../chunks/_plugin-
6
6
  import { t as GrButton_default } from "../../GrButton/chunks/GrButton-BlffdeJr.js";
7
7
  import { t as GrIcon_default } from "../../GrIcon/chunks/GrIcon-D9AWQLk2.js";
8
8
  import { useToast } from "../../../composables/useToast.js";
9
- import { r as PLACEMENT_CLASS } from "../../../chunks/config-LGrZIdaZ2.js";
9
+ import { r as PLACEMENT_CLASS } from "../../../chunks/config-LGrZIdaZ.js";
10
10
  import { Fragment, Teleport, TransitionGroup, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, normalizeClass, normalizeStyle, openBlock, ref, renderList, resolveDynamicComponent, toDisplayString, unref, watchEffect, withCtx } from "vue";
11
11
  import '../../../GrToaster.css';//#region src/components/GrToaster/GrToaster.vue?vue&type=script&setup=true&lang.ts
12
12
  var _hoisted_1 = ["aria-label"];
@@ -1 +1 @@
1
- {"version":3,"file":"GrToaster-eI8YvnIo.js","names":[],"sources":["../../../../src/components/GrToaster/GrToaster.vue","../../../../src/components/GrToaster/GrToaster.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * GrToaster — GR-примитив области уведомлений (toast).\n *\n * Модель состояния берётся из `useToast`, поэтому в приложении предполагается\n * **один активный `GrToaster`** на корень (или app-scoped через плагин).\n *\n * A11y:\n * - контейнер — `role=\"region\"` с `aria-label` (i18n-проп `regionLabel`);\n * - список обёрнут в ПОСТОЯННЫЙ live-region (`aria-live=\"polite\"`), который\n * существует до вставки тостов — иначе SR часто не озвучивают целиком\n * вставленный узел с `aria-live`;\n * - критичные тосты (`warning`/`danger`) несут `role=\"alert\"` (ассертивно).\n *\n * UX:\n * - `maxVisible` ограничивает стек; лишние ждут в очереди (их таймеры на паузе);\n * - автозакрытие **останавливается под курсором/фокусом** (WCAG 2.2.1) и\n * визуализируется прогресс-баром до закрытия;\n * - `placement` настраивает угол экрана; слой — `--gr-z-toast`.\n */\nimport { computed, ref, watchEffect } from 'vue'\nimport type { Component } from 'vue'\n\nimport { useToast } from '../../composables/useToast'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\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}\n\n// Единый маппинг по tone'у: иконка, цвет, a11y-роль. На уровне модуля —\n// чтобы не пересоздавать объект на каждый рендер.\nconst TONE_META: Record<GrToastTone, ToneMeta> = {\n primary: { icon: IconInfo, color: 'var(--primary)', role: 'status' },\n neutral: { icon: IconInfo, color: 'var(--muted-fg)', role: 'status' },\n info: { icon: IconInfo, color: 'var(--gr-info)', role: 'status' },\n success: { icon: IconCheck, color: 'var(--gr-success)', role: 'status' },\n warning: { icon: IconWarning, color: 'var(--gr-warning)', role: 'alert' },\n danger: { icon: IconError, color: 'var(--gr-danger)', role: 'alert' },\n slate: { icon: IconInfo, color: 'var(--gr-slate)', role: 'status' },\n azure: { icon: IconInfo, color: 'var(--gr-azure)', role: 'status' },\n}\n\nexport interface GrToasterProps {\n /** Угол экрана для стека уведомлений. */\n placement?: GrToasterPlacement\n /** Максимум одновременно видимых тостов; остальные ждут в очереди. По умолчанию `4`. */\n maxVisible?: number\n /** A11y-лейбл кнопки закрытия (i18n). */\n dismissLabel?: string\n /** A11y-лейбл контейнера-региона (i18n). */\n regionLabel?: string\n}\n\nconst props = withDefaults(defineProps<GrToasterProps>(), {\n placement: 'top-right',\n maxVisible: 4,\n dismissLabel: undefined,\n regionLabel: undefined,\n})\n\nconst { t } = useGranularityTranslations()\nconst resolvedDismissLabel = computed(() => props.dismissLabel ?? t('gr.toaster.dismiss', 'Dismiss'))\nconst resolvedRegionLabel = computed(() => props.regionLabel ?? t('gr.toaster.region', 'Notifications'))\n\nconst { list, dismiss, pause, resume } = useToast()\n\n// SSR-guard: на сервере `document.body` недоступен — отключаем `teleport`.\nconst isClient = typeof window !== 'undefined'\n\n// Видимые тосты (не больше `maxVisible`); остальные — в очереди.\nconst visibleToasts = computed(() => list.value.slice(0, Math.max(1, props.maxVisible)))\nconst visibleIds = computed(() => new Set(visibleToasts.value.map(toast => toast.id)))\n\n// Пауза под курсором/фокусом (WCAG 2.2.1). Один флаг на весь стек.\nconst paused = ref(false)\n\n// Единый источник правды по таймерам: тост тикает, только если он видим И стек не на\n// паузе. Очередь и hover — на паузе. Реагирует на изменения списка/hover.\nwatchEffect(() => {\n for (const toast of list.value) {\n if (visibleIds.value.has(toast.id) && !paused.value)\n resume(toast.id)\n else\n pause(toast.id)\n }\n})\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-gr-toaster\n role=\"region\"\n :aria-label=\"resolvedRegionLabel\"\n class=\"fixed z-[var(--gr-z-toast)] w-[360px] max-w-[calc(100vw-2rem)]\"\n :class=\"containerClass\"\n @mouseenter=\"paused = true\"\n @mouseleave=\"paused = false\"\n @focusin=\"paused = true\"\n @focusout=\"paused = false\"\n >\n <!-- Постоянный live-region: существует всегда, поэтому вставленные тосты озвучиваются. -->\n <TransitionGroup\n tag=\"div\"\n aria-live=\"polite\"\n aria-atomic=\"false\"\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 visibleToasts\"\n :key=\"toast.id\"\n data-gr-toast\n :data-tone=\"toast.tone\"\n :role=\"metaFor(toast.tone).role\"\n aria-atomic=\"true\"\n class=\"relative overflow-hidden rounded-[var(--gr-radius-lg)] border border-[var(--brd)] bg-[var(--card)] px-4 py-3 shadow-[var(--gr-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=\"resolvedDismissLabel\"\n @click=\"dismiss(toast.id)\"\n >\n <GrIcon size=\"sm\" aria-hidden=\"true\">\n <IconClose />\n </GrIcon>\n </GrButton>\n </div>\n\n <!-- Прогресс до автозакрытия; замирает вместе с таймером на паузе. -->\n <div\n v-if=\"toast.timeoutMs > 0\"\n data-gr-toast-progress\n aria-hidden=\"true\"\n class=\"gr-toast-progress absolute bottom-0 left-0 h-[2px] w-full origin-left\"\n :style=\"{\n backgroundColor: metaFor(toast.tone).color,\n animationDuration: `${toast.timeoutMs}ms`,\n animationPlayState: paused ? 'paused' : 'running',\n }\"\n />\n </div>\n </TransitionGroup>\n </div>\n </teleport>\n</template>\n\n<style scoped>\n.gr-toast-progress {\n animation-name: gr-toast-progress;\n animation-timing-function: linear;\n animation-fill-mode: forwards;\n}\n\n@keyframes gr-toast-progress {\n from {\n transform: scaleX(1);\n }\n to {\n transform: scaleX(0);\n }\n}\n</style>\n","<script setup lang=\"ts\">\n/**\n * GrToaster — GR-примитив области уведомлений (toast).\n *\n * Модель состояния берётся из `useToast`, поэтому в приложении предполагается\n * **один активный `GrToaster`** на корень (или app-scoped через плагин).\n *\n * A11y:\n * - контейнер — `role=\"region\"` с `aria-label` (i18n-проп `regionLabel`);\n * - список обёрнут в ПОСТОЯННЫЙ live-region (`aria-live=\"polite\"`), который\n * существует до вставки тостов — иначе SR часто не озвучивают целиком\n * вставленный узел с `aria-live`;\n * - критичные тосты (`warning`/`danger`) несут `role=\"alert\"` (ассертивно).\n *\n * UX:\n * - `maxVisible` ограничивает стек; лишние ждут в очереди (их таймеры на паузе);\n * - автозакрытие **останавливается под курсором/фокусом** (WCAG 2.2.1) и\n * визуализируется прогресс-баром до закрытия;\n * - `placement` настраивает угол экрана; слой — `--gr-z-toast`.\n */\nimport { computed, ref, watchEffect } from 'vue'\nimport type { Component } from 'vue'\n\nimport { useToast } from '../../composables/useToast'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\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}\n\n// Единый маппинг по tone'у: иконка, цвет, a11y-роль. На уровне модуля —\n// чтобы не пересоздавать объект на каждый рендер.\nconst TONE_META: Record<GrToastTone, ToneMeta> = {\n primary: { icon: IconInfo, color: 'var(--primary)', role: 'status' },\n neutral: { icon: IconInfo, color: 'var(--muted-fg)', role: 'status' },\n info: { icon: IconInfo, color: 'var(--gr-info)', role: 'status' },\n success: { icon: IconCheck, color: 'var(--gr-success)', role: 'status' },\n warning: { icon: IconWarning, color: 'var(--gr-warning)', role: 'alert' },\n danger: { icon: IconError, color: 'var(--gr-danger)', role: 'alert' },\n slate: { icon: IconInfo, color: 'var(--gr-slate)', role: 'status' },\n azure: { icon: IconInfo, color: 'var(--gr-azure)', role: 'status' },\n}\n\nexport interface GrToasterProps {\n /** Угол экрана для стека уведомлений. */\n placement?: GrToasterPlacement\n /** Максимум одновременно видимых тостов; остальные ждут в очереди. По умолчанию `4`. */\n maxVisible?: number\n /** A11y-лейбл кнопки закрытия (i18n). */\n dismissLabel?: string\n /** A11y-лейбл контейнера-региона (i18n). */\n regionLabel?: string\n}\n\nconst props = withDefaults(defineProps<GrToasterProps>(), {\n placement: 'top-right',\n maxVisible: 4,\n dismissLabel: undefined,\n regionLabel: undefined,\n})\n\nconst { t } = useGranularityTranslations()\nconst resolvedDismissLabel = computed(() => props.dismissLabel ?? t('gr.toaster.dismiss', 'Dismiss'))\nconst resolvedRegionLabel = computed(() => props.regionLabel ?? t('gr.toaster.region', 'Notifications'))\n\nconst { list, dismiss, pause, resume } = useToast()\n\n// SSR-guard: на сервере `document.body` недоступен — отключаем `teleport`.\nconst isClient = typeof window !== 'undefined'\n\n// Видимые тосты (не больше `maxVisible`); остальные — в очереди.\nconst visibleToasts = computed(() => list.value.slice(0, Math.max(1, props.maxVisible)))\nconst visibleIds = computed(() => new Set(visibleToasts.value.map(toast => toast.id)))\n\n// Пауза под курсором/фокусом (WCAG 2.2.1). Один флаг на весь стек.\nconst paused = ref(false)\n\n// Единый источник правды по таймерам: тост тикает, только если он видим И стек не на\n// паузе. Очередь и hover — на паузе. Реагирует на изменения списка/hover.\nwatchEffect(() => {\n for (const toast of list.value) {\n if (visibleIds.value.has(toast.id) && !paused.value)\n resume(toast.id)\n else\n pause(toast.id)\n }\n})\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-gr-toaster\n role=\"region\"\n :aria-label=\"resolvedRegionLabel\"\n class=\"fixed z-[var(--gr-z-toast)] w-[360px] max-w-[calc(100vw-2rem)]\"\n :class=\"containerClass\"\n @mouseenter=\"paused = true\"\n @mouseleave=\"paused = false\"\n @focusin=\"paused = true\"\n @focusout=\"paused = false\"\n >\n <!-- Постоянный live-region: существует всегда, поэтому вставленные тосты озвучиваются. -->\n <TransitionGroup\n tag=\"div\"\n aria-live=\"polite\"\n aria-atomic=\"false\"\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 visibleToasts\"\n :key=\"toast.id\"\n data-gr-toast\n :data-tone=\"toast.tone\"\n :role=\"metaFor(toast.tone).role\"\n aria-atomic=\"true\"\n class=\"relative overflow-hidden rounded-[var(--gr-radius-lg)] border border-[var(--brd)] bg-[var(--card)] px-4 py-3 shadow-[var(--gr-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=\"resolvedDismissLabel\"\n @click=\"dismiss(toast.id)\"\n >\n <GrIcon size=\"sm\" aria-hidden=\"true\">\n <IconClose />\n </GrIcon>\n </GrButton>\n </div>\n\n <!-- Прогресс до автозакрытия; замирает вместе с таймером на паузе. -->\n <div\n v-if=\"toast.timeoutMs > 0\"\n data-gr-toast-progress\n aria-hidden=\"true\"\n class=\"gr-toast-progress absolute bottom-0 left-0 h-[2px] w-full origin-left\"\n :style=\"{\n backgroundColor: metaFor(toast.tone).color,\n animationDuration: `${toast.timeoutMs}ms`,\n animationPlayState: paused ? 'paused' : 'running',\n }\"\n />\n </div>\n </TransitionGroup>\n </div>\n </teleport>\n</template>\n\n<style scoped>\n.gr-toast-progress {\n animation-name: gr-toast-progress;\n animation-timing-function: linear;\n animation-fill-mode: forwards;\n}\n\n@keyframes gr-toast-progress {\n from {\n transform: scaleX(1);\n }\n to {\n transform: scaleX(0);\n }\n}\n</style>\n"],"mappings":"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"}
1
+ {"version":3,"file":"GrToaster-L2cu6cSR.js","names":[],"sources":["../../../../src/components/GrToaster/GrToaster.vue","../../../../src/components/GrToaster/GrToaster.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * GrToaster — GR-примитив области уведомлений (toast).\n *\n * Модель состояния берётся из `useToast`, поэтому в приложении предполагается\n * **один активный `GrToaster`** на корень (или app-scoped через плагин).\n *\n * A11y:\n * - контейнер — `role=\"region\"` с `aria-label` (i18n-проп `regionLabel`);\n * - список обёрнут в ПОСТОЯННЫЙ live-region (`aria-live=\"polite\"`), который\n * существует до вставки тостов — иначе SR часто не озвучивают целиком\n * вставленный узел с `aria-live`;\n * - критичные тосты (`warning`/`danger`) несут `role=\"alert\"` (ассертивно).\n *\n * UX:\n * - `maxVisible` ограничивает стек; лишние ждут в очереди (их таймеры на паузе);\n * - автозакрытие **останавливается под курсором/фокусом** (WCAG 2.2.1) и\n * визуализируется прогресс-баром до закрытия;\n * - `placement` настраивает угол экрана; слой — `--gr-z-toast`.\n */\nimport { computed, ref, watchEffect } from 'vue'\nimport type { Component } from 'vue'\n\nimport { useToast } from '../../composables/useToast'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\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}\n\n// Единый маппинг по tone'у: иконка, цвет, a11y-роль. На уровне модуля —\n// чтобы не пересоздавать объект на каждый рендер.\nconst TONE_META: Record<GrToastTone, ToneMeta> = {\n primary: { icon: IconInfo, color: 'var(--primary)', role: 'status' },\n neutral: { icon: IconInfo, color: 'var(--muted-fg)', role: 'status' },\n info: { icon: IconInfo, color: 'var(--gr-info)', role: 'status' },\n success: { icon: IconCheck, color: 'var(--gr-success)', role: 'status' },\n warning: { icon: IconWarning, color: 'var(--gr-warning)', role: 'alert' },\n danger: { icon: IconError, color: 'var(--gr-danger)', role: 'alert' },\n slate: { icon: IconInfo, color: 'var(--gr-slate)', role: 'status' },\n azure: { icon: IconInfo, color: 'var(--gr-azure)', role: 'status' },\n}\n\nexport interface GrToasterProps {\n /** Угол экрана для стека уведомлений. */\n placement?: GrToasterPlacement\n /** Максимум одновременно видимых тостов; остальные ждут в очереди. По умолчанию `4`. */\n maxVisible?: number\n /** A11y-лейбл кнопки закрытия (i18n). */\n dismissLabel?: string\n /** A11y-лейбл контейнера-региона (i18n). */\n regionLabel?: string\n}\n\nconst props = withDefaults(defineProps<GrToasterProps>(), {\n placement: 'top-right',\n maxVisible: 4,\n dismissLabel: undefined,\n regionLabel: undefined,\n})\n\nconst { t } = useGranularityTranslations()\nconst resolvedDismissLabel = computed(() => props.dismissLabel ?? t('gr.toaster.dismiss', 'Dismiss'))\nconst resolvedRegionLabel = computed(() => props.regionLabel ?? t('gr.toaster.region', 'Notifications'))\n\nconst { list, dismiss, pause, resume } = useToast()\n\n// SSR-guard: на сервере `document.body` недоступен — отключаем `teleport`.\nconst isClient = typeof window !== 'undefined'\n\n// Видимые тосты (не больше `maxVisible`); остальные — в очереди.\nconst visibleToasts = computed(() => list.value.slice(0, Math.max(1, props.maxVisible)))\nconst visibleIds = computed(() => new Set(visibleToasts.value.map(toast => toast.id)))\n\n// Пауза под курсором/фокусом (WCAG 2.2.1). Один флаг на весь стек.\nconst paused = ref(false)\n\n// Единый источник правды по таймерам: тост тикает, только если он видим И стек не на\n// паузе. Очередь и hover — на паузе. Реагирует на изменения списка/hover.\nwatchEffect(() => {\n for (const toast of list.value) {\n if (visibleIds.value.has(toast.id) && !paused.value)\n resume(toast.id)\n else\n pause(toast.id)\n }\n})\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-gr-toaster\n role=\"region\"\n :aria-label=\"resolvedRegionLabel\"\n class=\"fixed z-[var(--gr-z-toast)] w-[360px] max-w-[calc(100vw-2rem)]\"\n :class=\"containerClass\"\n @mouseenter=\"paused = true\"\n @mouseleave=\"paused = false\"\n @focusin=\"paused = true\"\n @focusout=\"paused = false\"\n >\n <!-- Постоянный live-region: существует всегда, поэтому вставленные тосты озвучиваются. -->\n <TransitionGroup\n tag=\"div\"\n aria-live=\"polite\"\n aria-atomic=\"false\"\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 visibleToasts\"\n :key=\"toast.id\"\n data-gr-toast\n :data-tone=\"toast.tone\"\n :role=\"metaFor(toast.tone).role\"\n aria-atomic=\"true\"\n class=\"relative overflow-hidden rounded-[var(--gr-radius-lg)] border border-[var(--brd)] bg-[var(--card)] px-4 py-3 shadow-[var(--gr-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=\"resolvedDismissLabel\"\n @click=\"dismiss(toast.id)\"\n >\n <GrIcon size=\"sm\" aria-hidden=\"true\">\n <IconClose />\n </GrIcon>\n </GrButton>\n </div>\n\n <!-- Прогресс до автозакрытия; замирает вместе с таймером на паузе. -->\n <div\n v-if=\"toast.timeoutMs > 0\"\n data-gr-toast-progress\n aria-hidden=\"true\"\n class=\"gr-toast-progress absolute bottom-0 left-0 h-[2px] w-full origin-left\"\n :style=\"{\n backgroundColor: metaFor(toast.tone).color,\n animationDuration: `${toast.timeoutMs}ms`,\n animationPlayState: paused ? 'paused' : 'running',\n }\"\n />\n </div>\n </TransitionGroup>\n </div>\n </teleport>\n</template>\n\n<style scoped>\n.gr-toast-progress {\n animation-name: gr-toast-progress;\n animation-timing-function: linear;\n animation-fill-mode: forwards;\n}\n\n@keyframes gr-toast-progress {\n from {\n transform: scaleX(1);\n }\n to {\n transform: scaleX(0);\n }\n}\n</style>\n","<script setup lang=\"ts\">\n/**\n * GrToaster — GR-примитив области уведомлений (toast).\n *\n * Модель состояния берётся из `useToast`, поэтому в приложении предполагается\n * **один активный `GrToaster`** на корень (или app-scoped через плагин).\n *\n * A11y:\n * - контейнер — `role=\"region\"` с `aria-label` (i18n-проп `regionLabel`);\n * - список обёрнут в ПОСТОЯННЫЙ live-region (`aria-live=\"polite\"`), который\n * существует до вставки тостов — иначе SR часто не озвучивают целиком\n * вставленный узел с `aria-live`;\n * - критичные тосты (`warning`/`danger`) несут `role=\"alert\"` (ассертивно).\n *\n * UX:\n * - `maxVisible` ограничивает стек; лишние ждут в очереди (их таймеры на паузе);\n * - автозакрытие **останавливается под курсором/фокусом** (WCAG 2.2.1) и\n * визуализируется прогресс-баром до закрытия;\n * - `placement` настраивает угол экрана; слой — `--gr-z-toast`.\n */\nimport { computed, ref, watchEffect } from 'vue'\nimport type { Component } from 'vue'\n\nimport { useToast } from '../../composables/useToast'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\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}\n\n// Единый маппинг по tone'у: иконка, цвет, a11y-роль. На уровне модуля —\n// чтобы не пересоздавать объект на каждый рендер.\nconst TONE_META: Record<GrToastTone, ToneMeta> = {\n primary: { icon: IconInfo, color: 'var(--primary)', role: 'status' },\n neutral: { icon: IconInfo, color: 'var(--muted-fg)', role: 'status' },\n info: { icon: IconInfo, color: 'var(--gr-info)', role: 'status' },\n success: { icon: IconCheck, color: 'var(--gr-success)', role: 'status' },\n warning: { icon: IconWarning, color: 'var(--gr-warning)', role: 'alert' },\n danger: { icon: IconError, color: 'var(--gr-danger)', role: 'alert' },\n slate: { icon: IconInfo, color: 'var(--gr-slate)', role: 'status' },\n azure: { icon: IconInfo, color: 'var(--gr-azure)', role: 'status' },\n}\n\nexport interface GrToasterProps {\n /** Угол экрана для стека уведомлений. */\n placement?: GrToasterPlacement\n /** Максимум одновременно видимых тостов; остальные ждут в очереди. По умолчанию `4`. */\n maxVisible?: number\n /** A11y-лейбл кнопки закрытия (i18n). */\n dismissLabel?: string\n /** A11y-лейбл контейнера-региона (i18n). */\n regionLabel?: string\n}\n\nconst props = withDefaults(defineProps<GrToasterProps>(), {\n placement: 'top-right',\n maxVisible: 4,\n dismissLabel: undefined,\n regionLabel: undefined,\n})\n\nconst { t } = useGranularityTranslations()\nconst resolvedDismissLabel = computed(() => props.dismissLabel ?? t('gr.toaster.dismiss', 'Dismiss'))\nconst resolvedRegionLabel = computed(() => props.regionLabel ?? t('gr.toaster.region', 'Notifications'))\n\nconst { list, dismiss, pause, resume } = useToast()\n\n// SSR-guard: на сервере `document.body` недоступен — отключаем `teleport`.\nconst isClient = typeof window !== 'undefined'\n\n// Видимые тосты (не больше `maxVisible`); остальные — в очереди.\nconst visibleToasts = computed(() => list.value.slice(0, Math.max(1, props.maxVisible)))\nconst visibleIds = computed(() => new Set(visibleToasts.value.map(toast => toast.id)))\n\n// Пауза под курсором/фокусом (WCAG 2.2.1). Один флаг на весь стек.\nconst paused = ref(false)\n\n// Единый источник правды по таймерам: тост тикает, только если он видим И стек не на\n// паузе. Очередь и hover — на паузе. Реагирует на изменения списка/hover.\nwatchEffect(() => {\n for (const toast of list.value) {\n if (visibleIds.value.has(toast.id) && !paused.value)\n resume(toast.id)\n else\n pause(toast.id)\n }\n})\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-gr-toaster\n role=\"region\"\n :aria-label=\"resolvedRegionLabel\"\n class=\"fixed z-[var(--gr-z-toast)] w-[360px] max-w-[calc(100vw-2rem)]\"\n :class=\"containerClass\"\n @mouseenter=\"paused = true\"\n @mouseleave=\"paused = false\"\n @focusin=\"paused = true\"\n @focusout=\"paused = false\"\n >\n <!-- Постоянный live-region: существует всегда, поэтому вставленные тосты озвучиваются. -->\n <TransitionGroup\n tag=\"div\"\n aria-live=\"polite\"\n aria-atomic=\"false\"\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 visibleToasts\"\n :key=\"toast.id\"\n data-gr-toast\n :data-tone=\"toast.tone\"\n :role=\"metaFor(toast.tone).role\"\n aria-atomic=\"true\"\n class=\"relative overflow-hidden rounded-[var(--gr-radius-lg)] border border-[var(--brd)] bg-[var(--card)] px-4 py-3 shadow-[var(--gr-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=\"resolvedDismissLabel\"\n @click=\"dismiss(toast.id)\"\n >\n <GrIcon size=\"sm\" aria-hidden=\"true\">\n <IconClose />\n </GrIcon>\n </GrButton>\n </div>\n\n <!-- Прогресс до автозакрытия; замирает вместе с таймером на паузе. -->\n <div\n v-if=\"toast.timeoutMs > 0\"\n data-gr-toast-progress\n aria-hidden=\"true\"\n class=\"gr-toast-progress absolute bottom-0 left-0 h-[2px] w-full origin-left\"\n :style=\"{\n backgroundColor: metaFor(toast.tone).color,\n animationDuration: `${toast.timeoutMs}ms`,\n animationPlayState: paused ? 'paused' : 'running',\n }\"\n />\n </div>\n </TransitionGroup>\n </div>\n </teleport>\n</template>\n\n<style scoped>\n.gr-toast-progress {\n animation-name: gr-toast-progress;\n animation-timing-function: linear;\n animation-fill-mode: forwards;\n}\n\n@keyframes gr-toast-progress {\n from {\n transform: scaleX(1);\n }\n to {\n transform: scaleX(0);\n }\n}\n</style>\n"],"mappings":"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"}
@@ -1,3 +1,3 @@
1
- import { n as grToasterSafelist, t as grToasterConfig } from "../../chunks/config-LGrZIdaZ2.js";
2
- import { t as GrToaster_default } from "./chunks/GrToaster-eI8YvnIo.js";
1
+ import { n as grToasterSafelist, t as grToasterConfig } from "../../chunks/config-LGrZIdaZ.js";
2
+ import { t as GrToaster_default } from "./chunks/GrToaster-L2cu6cSR.js";
3
3
  export { GrToaster_default as GrToaster, GrToaster_default as default, grToasterConfig, grToasterSafelist };