@feugene/granularity 0.41.0 → 0.43.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 (220) hide show
  1. package/LICENSE +13 -162
  2. package/README.md +5 -5
  3. package/dist/chunks/config-BXRmsn3t.js +172 -0
  4. package/dist/chunks/config-BXRmsn3t.js.map +1 -0
  5. package/dist/chunks/{config-BRESvs9g.js → config-Ca8qr8Q3.js} +1 -1
  6. package/dist/chunks/{config-BRESvs9g.js.map → config-Ca8qr8Q3.js.map} +1 -1
  7. package/dist/chunks/config-Dg0bSoGw.js +81 -0
  8. package/dist/chunks/config-Dg0bSoGw.js.map +1 -0
  9. package/dist/chunks/config-DmED6aJV.js +237 -0
  10. package/dist/chunks/config-DmED6aJV.js.map +1 -0
  11. package/dist/chunks/{config-DWL81-aM.js → config-hnoZvcIM.js} +2 -20
  12. package/dist/chunks/config-hnoZvcIM.js.map +1 -0
  13. package/dist/chunks/dragSortGeometry-CY-llfrQ.js +87 -0
  14. package/dist/chunks/dragSortGeometry-CY-llfrQ.js.map +1 -0
  15. package/dist/chunks/{en-CuuLfNHX.js → en-C_QkUTRL.js} +36 -1
  16. package/dist/chunks/en-C_QkUTRL.js.map +1 -0
  17. package/dist/chunks/{es-CsmvfESn.js → es-B4t_3Wnh.js} +3 -3
  18. package/dist/chunks/{es-CsmvfESn.js.map → es-B4t_3Wnh.js.map} +1 -1
  19. package/dist/chunks/{es-CSnmrcMw.js → es-DemSEhZE.js} +36 -1
  20. package/dist/chunks/es-DemSEhZE.js.map +1 -0
  21. package/dist/chunks/{generated-B95IBXs6.js → generated-VcxJTo_q.js} +210 -0
  22. package/dist/chunks/generated-VcxJTo_q.js.map +1 -0
  23. package/dist/chunks/{grKbdStyles-DENu8pUK.js → grKbdStyles-ByMsX-lQ.js} +17 -2
  24. package/dist/chunks/grKbdStyles-ByMsX-lQ.js.map +1 -0
  25. package/dist/chunks/{granular-provider-BshNi2la.js → granular-provider-BohXQcNt.js} +8 -2
  26. package/dist/chunks/granular-provider-BohXQcNt.js.map +1 -0
  27. package/dist/chunks/{ru-BKWafECI.js → ru-CLUErjhY.js} +36 -1
  28. package/dist/chunks/ru-CLUErjhY.js.map +1 -0
  29. package/dist/chunks/scrollOverflow-B_kEdcol.js +31 -0
  30. package/dist/chunks/scrollOverflow-B_kEdcol.js.map +1 -0
  31. package/dist/chunks/tones-CRJAaYyg.js +21 -0
  32. package/dist/chunks/tones-CRJAaYyg.js.map +1 -0
  33. package/dist/components/GrAffix/chunks/GrAffix-CWtC7yi8.js +272 -0
  34. package/dist/components/GrAffix/chunks/GrAffix-CWtC7yi8.js.map +1 -0
  35. package/dist/components/GrAffix/index.js +3 -0
  36. package/dist/components/GrBreadcrumbs/chunks/{GrBreadcrumbs-DOpwY2EF.js → GrBreadcrumbs-D0qPry_R.js} +4 -1
  37. package/dist/components/GrBreadcrumbs/chunks/GrBreadcrumbs-D0qPry_R.js.map +1 -0
  38. package/dist/components/GrBreadcrumbs/index.js +1 -1
  39. package/dist/components/GrCarousel/chunks/GrCarousel-ByuughtI.js +761 -0
  40. package/dist/components/GrCarousel/chunks/GrCarousel-ByuughtI.js.map +1 -0
  41. package/dist/components/GrCarousel/index.js +3 -0
  42. package/dist/components/GrCarousel/styles.css +1 -0
  43. package/dist/components/GrColorPicker/chunks/{GrColorPicker-BwzRLs-i.js → GrColorPicker-9mTR-YZ_.js} +1 -1
  44. package/dist/components/GrColorPicker/chunks/{GrColorPicker-BwzRLs-i.js.map → GrColorPicker-9mTR-YZ_.js.map} +1 -1
  45. package/dist/components/GrColorPicker/index.js +1 -1
  46. package/dist/components/GrCommandPalette/chunks/{GrCommandPalette-CnzPuoIM.js → GrCommandPalette-Ha4s3DMT.js} +1 -1
  47. package/dist/components/GrCommandPalette/chunks/{GrCommandPalette-CnzPuoIM.js.map → GrCommandPalette-Ha4s3DMT.js.map} +1 -1
  48. package/dist/components/GrCommandPalette/index.js +1 -1
  49. package/dist/components/GrDataTable/chunks/{GrDataTable-DpyipOFx.js → GrDataTable-xNSmO_I-.js} +9 -4
  50. package/dist/components/GrDataTable/chunks/GrDataTable-xNSmO_I-.js.map +1 -0
  51. package/dist/components/GrDataTable/index.js +1 -1
  52. package/dist/components/GrDelta/chunks/{GrDelta-BpfqebEV.js → GrDelta-CgTvfoIN.js} +0 -0
  53. package/dist/components/GrDelta/chunks/GrDelta-CgTvfoIN.js.map +1 -0
  54. package/dist/components/GrDelta/index.js +1 -1
  55. package/dist/components/GrDescriptionList/chunks/{GrDescriptionList-D8ANFpn_.js → GrDescriptionList-Dz2Jj1if.js} +26 -11
  56. package/dist/components/GrDescriptionList/chunks/GrDescriptionList-Dz2Jj1if.js.map +1 -0
  57. package/dist/components/GrDescriptionList/index.js +1 -1
  58. package/dist/components/GrFilePreview/chunks/{GrFilePreview-PabkLgYX.js → GrFilePreview-2kVOg1C7.js} +40 -5
  59. package/dist/components/GrFilePreview/chunks/GrFilePreview-2kVOg1C7.js.map +1 -0
  60. package/dist/components/GrFilePreview/index.js +1 -1
  61. package/dist/components/GrImageViewer/chunks/{GrImageViewer-Dd2gJ-j_.js.map → GrImageViewer-DK-iVHHC.js.map} +1 -1
  62. package/dist/components/GrImageViewer/index.js +1 -1
  63. package/dist/components/GrInputTag/chunks/{GrInputTag-3Au5HWZL.js → GrInputTag-Ci8AO1ze.js} +5 -2
  64. package/dist/components/GrInputTag/chunks/GrInputTag-Ci8AO1ze.js.map +1 -0
  65. package/dist/components/GrInputTag/index.js +1 -1
  66. package/dist/components/GrJsonViewer/chunks/{GrJsonViewer-CDGOX3C0.js → GrJsonViewer-CwCBMh4h.js} +34 -8
  67. package/dist/components/GrJsonViewer/chunks/GrJsonViewer-CwCBMh4h.js.map +1 -0
  68. package/dist/components/GrJsonViewer/index.js +1 -1
  69. package/dist/components/GrKbd/chunks/{GrKbd-D_a1mPLe.js → GrKbd-BNMeoaLV.js} +1 -1
  70. package/dist/components/GrKbd/chunks/{GrKbd-D_a1mPLe.js.map → GrKbd-BNMeoaLV.js.map} +1 -1
  71. package/dist/components/GrKbd/index.js +2 -2
  72. package/dist/components/GrNumberInput/chunks/{GrNumberInput-BqPydXpK.js → GrNumberInput-CL5QhOmx.js} +5 -2
  73. package/dist/components/GrNumberInput/chunks/GrNumberInput-CL5QhOmx.js.map +1 -0
  74. package/dist/components/GrNumberInput/index.js +1 -1
  75. package/dist/components/GrProgressBar/index.js +1 -1
  76. package/dist/components/GrSegmented/chunks/{GrSegmented-NKTi9bMG.js → GrSegmented-CuDaKA6S.js} +5 -1
  77. package/dist/components/GrSegmented/chunks/GrSegmented-CuDaKA6S.js.map +1 -0
  78. package/dist/components/GrSegmented/index.js +1 -1
  79. package/dist/components/GrSlider/chunks/{GrSlider-Db-jACXz.js.map → GrSlider-CO8N9SF6.js.map} +1 -1
  80. package/dist/components/GrSlider/index.js +1 -1
  81. package/dist/components/GrSortableList/chunks/{GrSortableList-CQCaWivn.js → GrSortableList-NKriuGm_.js} +6 -2
  82. package/dist/components/GrSortableList/chunks/GrSortableList-NKriuGm_.js.map +1 -0
  83. package/dist/components/GrSortableList/index.js +1 -1
  84. package/dist/components/GrSteps/chunks/{GrSteps-DXj7m-WO.js → GrSteps-Cng-Wrjk.js} +5 -1
  85. package/dist/components/GrSteps/chunks/GrSteps-Cng-Wrjk.js.map +1 -0
  86. package/dist/components/GrSteps/index.js +1 -1
  87. package/dist/components/GrTabs/chunks/{GrTabs-DHfv67cp.js → GrTabs-s9RzC1WX.js} +7 -32
  88. package/dist/components/GrTabs/chunks/GrTabs-s9RzC1WX.js.map +1 -0
  89. package/dist/components/GrTabs/index.js +1 -1
  90. package/dist/components/GrTransfer/chunks/GrTransfer-8FA78ny6.js +1368 -0
  91. package/dist/components/GrTransfer/chunks/GrTransfer-8FA78ny6.js.map +1 -0
  92. package/dist/components/GrTransfer/index.js +3 -0
  93. package/dist/components/GrTransfer/styles.css +1 -0
  94. package/dist/components/GrTree/chunks/{GrTree-BFWFc0Bm.js → GrTree-CkHzgYg1.js} +3 -3
  95. package/dist/components/GrTree/chunks/{GrTree-BFWFc0Bm.js.map → GrTree-CkHzgYg1.js.map} +1 -1
  96. package/dist/components/GrTree/index.js +1 -1
  97. package/dist/components/GrTreeSelect/chunks/{GrTreeSelect-DJa2lYGe.js → GrTreeSelect-DUsv3_mF.js} +1 -1
  98. package/dist/components/GrTreeSelect/chunks/{GrTreeSelect-DJa2lYGe.js.map → GrTreeSelect-DUsv3_mF.js.map} +1 -1
  99. package/dist/components/GrTreeSelect/index.js +1 -1
  100. package/dist/composables/useDragSort.js +179 -1
  101. package/dist/composables/useDragSort.js.map +1 -0
  102. package/dist/granular-provider-node.js +1 -1
  103. package/dist/granular-provider.js +1 -1
  104. package/dist/i18n/all.js +1 -1
  105. package/dist/i18n/index.js +1 -1
  106. package/dist/index.js +32 -28
  107. package/dist/theme.js +1 -1
  108. package/dist/tokens.js +1 -1
  109. package/dist/types/components/GrAffix/GrAffix.vue.d.ts +91 -0
  110. package/dist/types/components/GrAffix/affixState.d.ts +81 -0
  111. package/dist/types/components/GrAffix/config.d.ts +1 -0
  112. package/dist/types/components/GrAffix/grAffixStyles.d.ts +46 -0
  113. package/dist/types/components/GrAffix/index.d.ts +9 -0
  114. package/dist/types/components/GrAffix/safelist.d.ts +1 -0
  115. package/dist/types/components/GrAlert/GrAlert.vue.d.ts +1 -1
  116. package/dist/types/components/GrBadgeWrap/GrBadgeWrap.vue.d.ts +2 -2
  117. package/dist/types/components/GrButton/GrButton.vue.d.ts +2 -2
  118. package/dist/types/components/GrCard/GrCard.vue.d.ts +1 -1
  119. package/dist/types/components/GrCarousel/GrCarousel.vue.d.ts +151 -0
  120. package/dist/types/components/GrCarousel/GrCarouselSlide.vue.d.ts +53 -0
  121. package/dist/types/components/GrCarousel/carouselNavigation.d.ts +57 -0
  122. package/dist/types/components/GrCarousel/composables/useCarouselAutoplay.d.ts +33 -0
  123. package/dist/types/components/GrCarousel/composables/useCarouselSwipe.d.ts +20 -0
  124. package/dist/types/components/GrCarousel/config.d.ts +7 -0
  125. package/dist/types/components/GrCarousel/defaults.d.ts +13 -0
  126. package/dist/types/components/GrCarousel/grCarouselContext.d.ts +46 -0
  127. package/dist/types/components/GrCarousel/grCarouselStyles.d.ts +80 -0
  128. package/dist/types/components/GrCarousel/index.d.ts +12 -0
  129. package/dist/types/components/GrCarousel/safelist.d.ts +1 -0
  130. package/dist/types/components/GrCheckbox/GrCheckbox.vue.d.ts +3 -3
  131. package/dist/types/components/GrCheckboxGroup/GrCheckboxGroup.vue.d.ts +2 -2
  132. package/dist/types/components/GrChip/GrChip.vue.d.ts +2 -2
  133. package/dist/types/components/GrChipGroup/GrChipGroup.vue.d.ts +2 -2
  134. package/dist/types/components/GrCollapse/GrCollapse.vue.d.ts +1 -1
  135. package/dist/types/components/GrCollapse/GrCollapseItem.vue.d.ts +1 -1
  136. package/dist/types/components/GrColorPicker/GrColorPicker.vue.d.ts +3 -3
  137. package/dist/types/components/GrCommandPalette/GrCommandPalette.vue.d.ts +1 -1
  138. package/dist/types/components/GrConfirmDialog/GrConfirmDialog.vue.d.ts +1 -1
  139. package/dist/types/components/GrContextMenu/GrContextMenu.vue.d.ts +2 -2
  140. package/dist/types/components/GrDialog/GrDialog.vue.d.ts +1 -1
  141. package/dist/types/components/GrDivider/GrDivider.vue.d.ts +2 -2
  142. package/dist/types/components/GrDrawer/GrDrawer.vue.d.ts +1 -1
  143. package/dist/types/components/GrDropdown/GrDropdown.vue.d.ts +3 -3
  144. package/dist/types/components/GrDropdownMenu/GrDropdownMenu.vue.d.ts +3 -3
  145. package/dist/types/components/GrDropdownMenu/GrDropdownMenuItem.vue.d.ts +1 -1
  146. package/dist/types/components/GrEmptyState/GrEmptyState.vue.d.ts +1 -1
  147. package/dist/types/components/GrFilePreview/GrFilePreview.vue.d.ts +14 -0
  148. package/dist/types/components/GrFileUpload/GrFileUpload.vue.d.ts +1 -1
  149. package/dist/types/components/GrFormField/GrFormField.vue.d.ts +3 -3
  150. package/dist/types/components/GrFormFile/GrFormFile.vue.d.ts +2 -2
  151. package/dist/types/components/GrFormSection/GrFormSection.vue.d.ts +1 -1
  152. package/dist/types/components/GrIcon/GrIcon.vue.d.ts +1 -1
  153. package/dist/types/components/GrImageViewer/GrImageViewer.vue.d.ts +3 -3
  154. package/dist/types/components/GrInput/GrInput.vue.d.ts +4 -4
  155. package/dist/types/components/GrInputTag/GrInputTag.vue.d.ts +3 -3
  156. package/dist/types/components/GrKbd/GrKbd.vue.d.ts +1 -1
  157. package/dist/types/components/GrKbd/grKbdStyles.d.ts +17 -2
  158. package/dist/types/components/GrLink/GrLink.vue.d.ts +1 -1
  159. package/dist/types/components/GrList/GrListItem.vue.d.ts +2 -2
  160. package/dist/types/components/GrNavbar/GrNavbar.vue.d.ts +2 -2
  161. package/dist/types/components/GrNumberInput/GrNumberInput.vue.d.ts +3 -3
  162. package/dist/types/components/GrPagination/GrPagination.vue.d.ts +1 -1
  163. package/dist/types/components/GrPopover/GrPopover.vue.d.ts +3 -3
  164. package/dist/types/components/GrPromptDialog/GrPromptDialog.vue.d.ts +3 -3
  165. package/dist/types/components/GrRadio/GrRadio.vue.d.ts +2 -2
  166. package/dist/types/components/GrRadioGroup/GrRadioGroup.vue.d.ts +2 -2
  167. package/dist/types/components/GrRating/GrRating.vue.d.ts +2 -2
  168. package/dist/types/components/GrSegmented/GrSegmented.vue.d.ts +2 -2
  169. package/dist/types/components/GrSidebar/GrSidebarItem.vue.d.ts +1 -1
  170. package/dist/types/components/GrSkeleton/GrSkeleton.vue.d.ts +1 -1
  171. package/dist/types/components/GrSlider/GrSlider.vue.d.ts +3 -3
  172. package/dist/types/components/GrSplitter/GrSplitter.vue.d.ts +1 -1
  173. package/dist/types/components/GrStatistic/GrStatistic.vue.d.ts +2 -2
  174. package/dist/types/components/GrSwitch/GrSwitch.vue.d.ts +3 -3
  175. package/dist/types/components/GrTable/GrTable.vue.d.ts +1 -1
  176. package/dist/types/components/GrTabs/GrTabs.vue.d.ts +1 -1
  177. package/dist/types/components/GrTextarea/GrTextarea.vue.d.ts +2 -2
  178. package/dist/types/components/GrTimeline/GrTimelineItem.vue.d.ts +2 -2
  179. package/dist/types/components/GrToaster/GrToaster.vue.d.ts +1 -1
  180. package/dist/types/components/GrTooltip/GrTooltip.vue.d.ts +3 -3
  181. package/dist/types/components/GrTransfer/GrTransfer.vue.d.ts +103 -0
  182. package/dist/types/components/GrTransfer/config.d.ts +1 -0
  183. package/dist/types/components/GrTransfer/defaults.d.ts +15 -0
  184. package/dist/types/components/GrTransfer/grTransferStyles.d.ts +127 -0
  185. package/dist/types/components/GrTransfer/index.d.ts +11 -0
  186. package/dist/types/components/GrTransfer/safelist.d.ts +1 -0
  187. package/dist/types/components/GrTransfer/transferGeometry.d.ts +44 -0
  188. package/dist/types/components/GrTransfer/transferModel.d.ts +68 -0
  189. package/dist/types/components/GrTransfer/transferSelection.d.ts +36 -0
  190. package/dist/types/components/GrTransfer/useTransferDrag.d.ts +48 -0
  191. package/dist/types/granular-provider/shared.d.ts +3 -0
  192. package/dist/types/i18n/messages/en.d.ts +70 -0
  193. package/dist/types/i18n/messages/es.d.ts +70 -0
  194. package/dist/types/i18n/messages/ru.d.ts +70 -0
  195. package/dist/types/index.d.ts +3 -0
  196. package/dist/web-types.json +735 -287
  197. package/package.json +19 -3
  198. package/dist/chunks/config-DWL81-aM.js.map +0 -1
  199. package/dist/chunks/en-CuuLfNHX.js.map +0 -1
  200. package/dist/chunks/es-CSnmrcMw.js.map +0 -1
  201. package/dist/chunks/generated-B95IBXs6.js.map +0 -1
  202. package/dist/chunks/grKbdStyles-DENu8pUK.js.map +0 -1
  203. package/dist/chunks/granular-provider-BshNi2la.js.map +0 -1
  204. package/dist/chunks/ru-BKWafECI.js.map +0 -1
  205. package/dist/chunks/useDragSort-0l47h4yb.js +0 -265
  206. package/dist/chunks/useDragSort-0l47h4yb.js.map +0 -1
  207. package/dist/components/GrBreadcrumbs/chunks/GrBreadcrumbs-DOpwY2EF.js.map +0 -1
  208. package/dist/components/GrDataTable/chunks/GrDataTable-DpyipOFx.js.map +0 -1
  209. package/dist/components/GrDelta/chunks/GrDelta-BpfqebEV.js.map +0 -1
  210. package/dist/components/GrDescriptionList/chunks/GrDescriptionList-D8ANFpn_.js.map +0 -1
  211. package/dist/components/GrFilePreview/chunks/GrFilePreview-PabkLgYX.js.map +0 -1
  212. package/dist/components/GrInputTag/chunks/GrInputTag-3Au5HWZL.js.map +0 -1
  213. package/dist/components/GrJsonViewer/chunks/GrJsonViewer-CDGOX3C0.js.map +0 -1
  214. package/dist/components/GrNumberInput/chunks/GrNumberInput-BqPydXpK.js.map +0 -1
  215. package/dist/components/GrSegmented/chunks/GrSegmented-NKTi9bMG.js.map +0 -1
  216. package/dist/components/GrSortableList/chunks/GrSortableList-CQCaWivn.js.map +0 -1
  217. package/dist/components/GrSteps/chunks/GrSteps-DXj7m-WO.js.map +0 -1
  218. package/dist/components/GrTabs/chunks/GrTabs-DHfv67cp.js.map +0 -1
  219. package/dist/components/GrImageViewer/chunks/{GrImageViewer-Dd2gJ-j_.js → GrImageViewer-DK-iVHHC.js} +3 -3
  220. package/dist/components/GrSlider/chunks/{GrSlider-Db-jACXz.js → GrSlider-CO8N9SF6.js} +1 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"GrSortableList-CQCaWivn.js","names":[],"sources":["../../../../src/components/GrSortableList/GrSortableList.vue","../../../../src/components/GrSortableList/GrSortableList.vue"],"sourcesContent":["<script setup lang=\"ts\" generic=\"T\">\nimport { computed, nextTick, ref } from 'vue'\n\nimport GrCard, { type GrCardVariant } from '../GrCard'\nimport { useAnnouncer } from '../../composables/useAnnouncer'\nimport { useDragSort } from '../../composables/useDragSort'\nimport { useRovingFocus } from '../../composables/useRovingFocus'\nimport { insertionIndex, moveItem } from '../../composables/internal/dragSortGeometry'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\n\nimport type { GrSortableOrientation } from './grSortableListStyles'\nimport {\n contentClass,\n dividedClass,\n emptyClass,\n handleClass,\n handleDisabledClass,\n indicatorAfterClass,\n indicatorBeforeClass,\n listClass,\n rowDisabledClass,\n rowDraggingClass,\n rowFocusClass,\n rowGrabbedClass,\n rowLayoutClass,\n} from './grSortableListStyles'\n\nimport IconGripVertical from '~icons/lucide/grip-vertical'\n\nexport interface GrSortableListProps<TItem = unknown> {\n /** Набор в текущем порядке. `v-model`: наружу уходит новый массив, вход не мутируется. */\n modelValue: TItem[]\n /** Ключ элемента: имя поля или функция. Без него — индекс. */\n itemKey?: string | ((item: TItem, index: number) => string | number)\n /** Список только для чтения: ни указателем, ни с клавиатуры. */\n disabled?: boolean\n /** Ось переноса. `horizontal` — ряд с переносом по ширине. */\n orientation?: GrSortableOrientation\n /** Поверхность под списком — вариант карточки. */\n variant?: GrCardVariant\n /** Разделители между строками. */\n divided?: boolean\n /** Тянуть можно только за ручку. Выключено — тянется вся строка. */\n handleOnly?: boolean\n /** Высота видимой части: список становится скроллером с автопрокруткой при переносе. */\n maxHeight?: number | string\n /** Текст пустого состояния. Слот `#empty` сильнее. */\n emptyText?: string\n /** Имя списка для скринридера. Не задано — берётся из локали. */\n ariaLabel?: string\n}\n\nexport interface GrSortableListEmits<TItem = unknown> {\n (e: 'update:modelValue', value: TItem[]): void\n (e: 'move', from: number, to: number): void\n (e: 'change', value: TItem[]): void\n}\n\nconst props = withDefaults(defineProps<GrSortableListProps<T>>(), {\n itemKey: undefined,\n disabled: false,\n orientation: 'vertical',\n variant: undefined,\n divided: true,\n handleOnly: true,\n maxHeight: undefined,\n emptyText: undefined,\n ariaLabel: undefined,\n})\n\nconst emit = defineEmits<GrSortableListEmits<T>>()\n\ndefineSlots<{\n item?: (props: { item: T, index: number, dragging: boolean, grabbed: boolean }) => any\n handle?: (props: { item: T, index: number, disabled: boolean }) => any\n empty?: () => any\n}>()\n\nconst { t } = useGranularityTranslations()\nconst { announce } = useAnnouncer()\n\nconst listEl = ref<HTMLElement | null>(null)\nconst rowEls = new Map<string, HTMLElement>()\n\nconst count = computed(() => props.modelValue.length)\nconst isEmpty = computed(() => count.value === 0)\nconst isVertical = computed(() => props.orientation === 'vertical')\n\nfunction keyOf(item: T, index: number): string {\n if (typeof props.itemKey === 'function')\n return String(props.itemKey(item, index))\n if (typeof props.itemKey === 'string')\n return String((item as Record<string, unknown>)[props.itemKey])\n\n return String(index)\n}\n\nconst keys = computed(() => props.modelValue.map((item, index) => keyOf(item, index)))\n\nfunction indexOfKey(key: string): number {\n return keys.value.indexOf(key)\n}\n\nfunction registerRow(key: string, el: unknown): void {\n if (el instanceof HTMLElement) {\n rowEls.set(key, el)\n return\n }\n\n // Перестановка размонтирует старый узел уже после того, как встал новый:\n // снимаем запись, только если она протухла (приём из `GrTree`).\n const previous = rowEls.get(key)\n if (previous && !previous.isConnected)\n rowEls.delete(key)\n}\n\nconst roving = useRovingFocus<string>({\n items: () => keys.value,\n elementFor: key => rowEls.get(key) ?? null,\n orientation: () => (isVertical.value ? 'vertical' : 'horizontal'),\n wrap: () => false,\n})\n\nfunction applyMove(from: number, to: number): void {\n if (from < 0 || to < 0 || from === to)\n return\n\n const next = moveItem(props.modelValue, from, to)\n emit('update:modelValue', next)\n emit('move', from, to)\n emit('change', next)\n}\n\nconst sort = useDragSort<string, number>({\n items: () => keys.value,\n elementFor: key => rowEls.get(key) ?? null,\n orientation: () => props.orientation,\n scroller: () => (props.maxHeight === undefined ? null : listEl.value),\n disabled: () => props.disabled,\n resolveTarget: (hit, source) => insertionIndex(hit, indexOfKey(source), count.value),\n onDrop: (source, to) => applyMove(indexOfKey(source), to),\n onUpdate: (source, target, mode) => {\n // Объявляется только клавиатурный перенос: у мыши всё видно на экране, а\n // поток объявлений на каждом движении указателя делает диктор бесполезным.\n if (mode !== 'keyboard' || source === null)\n return\n\n const position = (target ?? indexOfKey(source)) + 1\n announce(t('gr.sortable.moved', 'Position {position} of {count}', { position, count: count.value }))\n },\n})\n\nconst activeIndex = computed(() => (sort.source.value === null ? -1 : indexOfKey(sort.source.value)))\n\n/** Куда встанет строка: индикатор рисуется на строке-соседе, а не отдельным узлом. */\nfunction indicatorFor(index: number): 'before' | 'after' | null {\n const target = sort.target.value\n if (target === null || !sort.isActive.value || index === activeIndex.value)\n return null\n\n if (target === index)\n return target < activeIndex.value || activeIndex.value === -1 ? 'before' : 'after'\n\n return null\n}\n\nfunction rowClass(index: number, key: string): string[] {\n const grabbed = sort.mode.value === 'keyboard' && sort.source.value === key\n const dragging = sort.mode.value === 'pointer' && sort.source.value === key\n const indicator = indicatorFor(index)\n\n return [\n rowLayoutClass,\n rowFocusClass,\n props.disabled ? rowDisabledClass : '',\n grabbed ? rowGrabbedClass : '',\n dragging ? rowDraggingClass : '',\n indicator === 'before' ? indicatorBeforeClass : '',\n indicator === 'after' ? indicatorAfterClass : '',\n ]\n}\n\nfunction startPointer(key: string, event: PointerEvent): void {\n if (props.disabled)\n return\n\n sort.startFrom(key)(event)\n}\n\nfunction grab(key: string): void {\n sort.grab(key)\n if (sort.mode.value !== 'keyboard')\n return\n\n // Цель ставится до объявления: `setTarget` сам объявляет позицию, и обратный\n // порядок стёр бы более полное сообщение о захвате.\n sort.setTarget(indexOfKey(key))\n announce(t('gr.sortable.grabbed', 'Grabbed. Position {position} of {count}', {\n position: indexOfKey(key) + 1,\n count: count.value,\n }))\n}\n\nasync function drop(key: string): Promise<void> {\n const target = sort.target.value\n sort.drop()\n\n announce(t('gr.sortable.dropped', 'Dropped at position {position} of {count}', {\n position: (target ?? 0) + 1,\n count: count.value,\n }))\n\n // Фокус остаётся на перенесённой строке: иначе после переноса он падает на\n // тело документа и клавиатурный сценарий обрывается на первом же шаге.\n await nextTick()\n await roving.focusKey(key)\n}\n\nfunction cancel(): void {\n if (!sort.isActive.value)\n return\n\n sort.cancel()\n announce(t('gr.sortable.cancelled', 'Move cancelled'))\n}\n\nfunction moveTargetBy(delta: number): void {\n const current = sort.target.value ?? activeIndex.value\n const next = Math.min(count.value - 1, Math.max(0, current + delta))\n\n if (next !== current)\n sort.setTarget(next)\n}\n\nfunction onRowKeydown(event: KeyboardEvent, key: string): void {\n if (props.disabled)\n return\n\n const forward = isVertical.value ? 'ArrowDown' : 'ArrowRight'\n const backward = isVertical.value ? 'ArrowUp' : 'ArrowLeft'\n const grabbed = sort.mode.value === 'keyboard'\n\n if (event.key === 'Escape') {\n if (!grabbed)\n return\n\n event.preventDefault()\n cancel()\n return\n }\n\n if (event.key === ' ' || event.key === 'Enter') {\n event.preventDefault()\n if (grabbed)\n void drop(key)\n else grab(key)\n return\n }\n\n if (grabbed) {\n // Взятая строка забирает стрелки себе: двигается она, а не фокус.\n if (event.key === forward || event.key === backward) {\n event.preventDefault()\n moveTargetBy(event.key === forward ? 1 : -1)\n }\n return\n }\n\n roving.handleNavigationKeys(event)\n}\n\n/** Фокус ушёл из списка — взятая строка не может остаться взятой навсегда. */\nfunction onFocusout(event: FocusEvent): void {\n if (sort.mode.value !== 'keyboard')\n return\n\n const next = event.relatedTarget as Node | null\n if (next && listEl.value?.contains(next))\n return\n\n cancel()\n}\n\nconst resolvedEmptyText = computed(() => props.emptyText ?? t('gr.sortable.empty', 'Nothing to sort yet'))\nconst resolvedLabel = computed(() => props.ariaLabel ?? t('gr.sortable.label', 'Sortable list'))\nconst handleLabel = computed(() => t('gr.sortable.handle', 'Reorder'))\n\nconst listStyle = computed(() => (props.maxHeight === undefined\n ? undefined\n : { maxHeight: typeof props.maxHeight === 'number' ? `${props.maxHeight}px` : props.maxHeight, overflow: 'auto' }))\n\ndefineExpose({\n /** Программная перестановка — та же, что делает перенос. */\n move: (from: number, to: number) => applyMove(from, to),\n focusItem: (index: number) => roving.focusKey(keys.value[index]),\n})\n</script>\n\n<template>\n <GrCard :variant=\"variant\">\n <div\n ref=\"listEl\"\n role=\"list\"\n data-gr-sortable\n :aria-label=\"resolvedLabel\"\n :aria-disabled=\"disabled ? 'true' : undefined\"\n :class=\"[listClass[orientation], divided ? dividedClass : '']\"\n :style=\"listStyle\"\n @focusout=\"onFocusout\"\n >\n <div\n v-if=\"isEmpty\"\n data-gr-sortable-empty\n :class=\"emptyClass\"\n >\n <slot name=\"empty\">\n {{ resolvedEmptyText }}\n </slot>\n </div>\n\n <div\n v-for=\"(item, index) in modelValue\"\n v-else\n :key=\"keys[index]\"\n :ref=\"el => registerRow(keys[index], el)\"\n role=\"listitem\"\n data-gr-sortable-item\n :tabindex=\"disabled ? -1 : roving.tabindexFor(keys[index])\"\n :aria-roledescription=\"handleLabel\"\n :aria-posinset=\"index + 1\"\n :aria-setsize=\"count\"\n :aria-grabbed=\"sort.mode.value === 'keyboard' && sort.source.value === keys[index] ? 'true' : undefined\"\n :class=\"rowClass(index, keys[index])\"\n @keydown=\"onRowKeydown($event, keys[index])\"\n @focus=\"roving.setActive(keys[index])\"\n @pointerdown=\"handleOnly ? undefined : startPointer(keys[index], $event)\"\n >\n <button\n type=\"button\"\n tabindex=\"-1\"\n data-gr-sortable-handle\n :class=\"[handleClass, disabled ? handleDisabledClass : '']\"\n :aria-label=\"handleLabel\"\n :disabled=\"disabled\"\n @pointerdown=\"startPointer(keys[index], $event)\"\n @click.stop\n >\n <slot\n name=\"handle\"\n :item=\"item\"\n :index=\"index\"\n :disabled=\"disabled\"\n >\n <IconGripVertical class=\"block\" aria-hidden=\"true\" />\n </slot>\n </button>\n\n <div :class=\"contentClass\">\n <slot\n name=\"item\"\n :item=\"item\"\n :index=\"index\"\n :dragging=\"sort.mode.value === 'pointer' && sort.source.value === keys[index]\"\n :grabbed=\"sort.mode.value === 'keyboard' && sort.source.value === keys[index]\"\n >\n {{ item }}\n </slot>\n </div>\n </div>\n </div>\n </GrCard>\n</template>\n","<script setup lang=\"ts\" generic=\"T\">\nimport { computed, nextTick, ref } from 'vue'\n\nimport GrCard, { type GrCardVariant } from '../GrCard'\nimport { useAnnouncer } from '../../composables/useAnnouncer'\nimport { useDragSort } from '../../composables/useDragSort'\nimport { useRovingFocus } from '../../composables/useRovingFocus'\nimport { insertionIndex, moveItem } from '../../composables/internal/dragSortGeometry'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\n\nimport type { GrSortableOrientation } from './grSortableListStyles'\nimport {\n contentClass,\n dividedClass,\n emptyClass,\n handleClass,\n handleDisabledClass,\n indicatorAfterClass,\n indicatorBeforeClass,\n listClass,\n rowDisabledClass,\n rowDraggingClass,\n rowFocusClass,\n rowGrabbedClass,\n rowLayoutClass,\n} from './grSortableListStyles'\n\nimport IconGripVertical from '~icons/lucide/grip-vertical'\n\nexport interface GrSortableListProps<TItem = unknown> {\n /** Набор в текущем порядке. `v-model`: наружу уходит новый массив, вход не мутируется. */\n modelValue: TItem[]\n /** Ключ элемента: имя поля или функция. Без него — индекс. */\n itemKey?: string | ((item: TItem, index: number) => string | number)\n /** Список только для чтения: ни указателем, ни с клавиатуры. */\n disabled?: boolean\n /** Ось переноса. `horizontal` — ряд с переносом по ширине. */\n orientation?: GrSortableOrientation\n /** Поверхность под списком — вариант карточки. */\n variant?: GrCardVariant\n /** Разделители между строками. */\n divided?: boolean\n /** Тянуть можно только за ручку. Выключено — тянется вся строка. */\n handleOnly?: boolean\n /** Высота видимой части: список становится скроллером с автопрокруткой при переносе. */\n maxHeight?: number | string\n /** Текст пустого состояния. Слот `#empty` сильнее. */\n emptyText?: string\n /** Имя списка для скринридера. Не задано — берётся из локали. */\n ariaLabel?: string\n}\n\nexport interface GrSortableListEmits<TItem = unknown> {\n (e: 'update:modelValue', value: TItem[]): void\n (e: 'move', from: number, to: number): void\n (e: 'change', value: TItem[]): void\n}\n\nconst props = withDefaults(defineProps<GrSortableListProps<T>>(), {\n itemKey: undefined,\n disabled: false,\n orientation: 'vertical',\n variant: undefined,\n divided: true,\n handleOnly: true,\n maxHeight: undefined,\n emptyText: undefined,\n ariaLabel: undefined,\n})\n\nconst emit = defineEmits<GrSortableListEmits<T>>()\n\ndefineSlots<{\n item?: (props: { item: T, index: number, dragging: boolean, grabbed: boolean }) => any\n handle?: (props: { item: T, index: number, disabled: boolean }) => any\n empty?: () => any\n}>()\n\nconst { t } = useGranularityTranslations()\nconst { announce } = useAnnouncer()\n\nconst listEl = ref<HTMLElement | null>(null)\nconst rowEls = new Map<string, HTMLElement>()\n\nconst count = computed(() => props.modelValue.length)\nconst isEmpty = computed(() => count.value === 0)\nconst isVertical = computed(() => props.orientation === 'vertical')\n\nfunction keyOf(item: T, index: number): string {\n if (typeof props.itemKey === 'function')\n return String(props.itemKey(item, index))\n if (typeof props.itemKey === 'string')\n return String((item as Record<string, unknown>)[props.itemKey])\n\n return String(index)\n}\n\nconst keys = computed(() => props.modelValue.map((item, index) => keyOf(item, index)))\n\nfunction indexOfKey(key: string): number {\n return keys.value.indexOf(key)\n}\n\nfunction registerRow(key: string, el: unknown): void {\n if (el instanceof HTMLElement) {\n rowEls.set(key, el)\n return\n }\n\n // Перестановка размонтирует старый узел уже после того, как встал новый:\n // снимаем запись, только если она протухла (приём из `GrTree`).\n const previous = rowEls.get(key)\n if (previous && !previous.isConnected)\n rowEls.delete(key)\n}\n\nconst roving = useRovingFocus<string>({\n items: () => keys.value,\n elementFor: key => rowEls.get(key) ?? null,\n orientation: () => (isVertical.value ? 'vertical' : 'horizontal'),\n wrap: () => false,\n})\n\nfunction applyMove(from: number, to: number): void {\n if (from < 0 || to < 0 || from === to)\n return\n\n const next = moveItem(props.modelValue, from, to)\n emit('update:modelValue', next)\n emit('move', from, to)\n emit('change', next)\n}\n\nconst sort = useDragSort<string, number>({\n items: () => keys.value,\n elementFor: key => rowEls.get(key) ?? null,\n orientation: () => props.orientation,\n scroller: () => (props.maxHeight === undefined ? null : listEl.value),\n disabled: () => props.disabled,\n resolveTarget: (hit, source) => insertionIndex(hit, indexOfKey(source), count.value),\n onDrop: (source, to) => applyMove(indexOfKey(source), to),\n onUpdate: (source, target, mode) => {\n // Объявляется только клавиатурный перенос: у мыши всё видно на экране, а\n // поток объявлений на каждом движении указателя делает диктор бесполезным.\n if (mode !== 'keyboard' || source === null)\n return\n\n const position = (target ?? indexOfKey(source)) + 1\n announce(t('gr.sortable.moved', 'Position {position} of {count}', { position, count: count.value }))\n },\n})\n\nconst activeIndex = computed(() => (sort.source.value === null ? -1 : indexOfKey(sort.source.value)))\n\n/** Куда встанет строка: индикатор рисуется на строке-соседе, а не отдельным узлом. */\nfunction indicatorFor(index: number): 'before' | 'after' | null {\n const target = sort.target.value\n if (target === null || !sort.isActive.value || index === activeIndex.value)\n return null\n\n if (target === index)\n return target < activeIndex.value || activeIndex.value === -1 ? 'before' : 'after'\n\n return null\n}\n\nfunction rowClass(index: number, key: string): string[] {\n const grabbed = sort.mode.value === 'keyboard' && sort.source.value === key\n const dragging = sort.mode.value === 'pointer' && sort.source.value === key\n const indicator = indicatorFor(index)\n\n return [\n rowLayoutClass,\n rowFocusClass,\n props.disabled ? rowDisabledClass : '',\n grabbed ? rowGrabbedClass : '',\n dragging ? rowDraggingClass : '',\n indicator === 'before' ? indicatorBeforeClass : '',\n indicator === 'after' ? indicatorAfterClass : '',\n ]\n}\n\nfunction startPointer(key: string, event: PointerEvent): void {\n if (props.disabled)\n return\n\n sort.startFrom(key)(event)\n}\n\nfunction grab(key: string): void {\n sort.grab(key)\n if (sort.mode.value !== 'keyboard')\n return\n\n // Цель ставится до объявления: `setTarget` сам объявляет позицию, и обратный\n // порядок стёр бы более полное сообщение о захвате.\n sort.setTarget(indexOfKey(key))\n announce(t('gr.sortable.grabbed', 'Grabbed. Position {position} of {count}', {\n position: indexOfKey(key) + 1,\n count: count.value,\n }))\n}\n\nasync function drop(key: string): Promise<void> {\n const target = sort.target.value\n sort.drop()\n\n announce(t('gr.sortable.dropped', 'Dropped at position {position} of {count}', {\n position: (target ?? 0) + 1,\n count: count.value,\n }))\n\n // Фокус остаётся на перенесённой строке: иначе после переноса он падает на\n // тело документа и клавиатурный сценарий обрывается на первом же шаге.\n await nextTick()\n await roving.focusKey(key)\n}\n\nfunction cancel(): void {\n if (!sort.isActive.value)\n return\n\n sort.cancel()\n announce(t('gr.sortable.cancelled', 'Move cancelled'))\n}\n\nfunction moveTargetBy(delta: number): void {\n const current = sort.target.value ?? activeIndex.value\n const next = Math.min(count.value - 1, Math.max(0, current + delta))\n\n if (next !== current)\n sort.setTarget(next)\n}\n\nfunction onRowKeydown(event: KeyboardEvent, key: string): void {\n if (props.disabled)\n return\n\n const forward = isVertical.value ? 'ArrowDown' : 'ArrowRight'\n const backward = isVertical.value ? 'ArrowUp' : 'ArrowLeft'\n const grabbed = sort.mode.value === 'keyboard'\n\n if (event.key === 'Escape') {\n if (!grabbed)\n return\n\n event.preventDefault()\n cancel()\n return\n }\n\n if (event.key === ' ' || event.key === 'Enter') {\n event.preventDefault()\n if (grabbed)\n void drop(key)\n else grab(key)\n return\n }\n\n if (grabbed) {\n // Взятая строка забирает стрелки себе: двигается она, а не фокус.\n if (event.key === forward || event.key === backward) {\n event.preventDefault()\n moveTargetBy(event.key === forward ? 1 : -1)\n }\n return\n }\n\n roving.handleNavigationKeys(event)\n}\n\n/** Фокус ушёл из списка — взятая строка не может остаться взятой навсегда. */\nfunction onFocusout(event: FocusEvent): void {\n if (sort.mode.value !== 'keyboard')\n return\n\n const next = event.relatedTarget as Node | null\n if (next && listEl.value?.contains(next))\n return\n\n cancel()\n}\n\nconst resolvedEmptyText = computed(() => props.emptyText ?? t('gr.sortable.empty', 'Nothing to sort yet'))\nconst resolvedLabel = computed(() => props.ariaLabel ?? t('gr.sortable.label', 'Sortable list'))\nconst handleLabel = computed(() => t('gr.sortable.handle', 'Reorder'))\n\nconst listStyle = computed(() => (props.maxHeight === undefined\n ? undefined\n : { maxHeight: typeof props.maxHeight === 'number' ? `${props.maxHeight}px` : props.maxHeight, overflow: 'auto' }))\n\ndefineExpose({\n /** Программная перестановка — та же, что делает перенос. */\n move: (from: number, to: number) => applyMove(from, to),\n focusItem: (index: number) => roving.focusKey(keys.value[index]),\n})\n</script>\n\n<template>\n <GrCard :variant=\"variant\">\n <div\n ref=\"listEl\"\n role=\"list\"\n data-gr-sortable\n :aria-label=\"resolvedLabel\"\n :aria-disabled=\"disabled ? 'true' : undefined\"\n :class=\"[listClass[orientation], divided ? dividedClass : '']\"\n :style=\"listStyle\"\n @focusout=\"onFocusout\"\n >\n <div\n v-if=\"isEmpty\"\n data-gr-sortable-empty\n :class=\"emptyClass\"\n >\n <slot name=\"empty\">\n {{ resolvedEmptyText }}\n </slot>\n </div>\n\n <div\n v-for=\"(item, index) in modelValue\"\n v-else\n :key=\"keys[index]\"\n :ref=\"el => registerRow(keys[index], el)\"\n role=\"listitem\"\n data-gr-sortable-item\n :tabindex=\"disabled ? -1 : roving.tabindexFor(keys[index])\"\n :aria-roledescription=\"handleLabel\"\n :aria-posinset=\"index + 1\"\n :aria-setsize=\"count\"\n :aria-grabbed=\"sort.mode.value === 'keyboard' && sort.source.value === keys[index] ? 'true' : undefined\"\n :class=\"rowClass(index, keys[index])\"\n @keydown=\"onRowKeydown($event, keys[index])\"\n @focus=\"roving.setActive(keys[index])\"\n @pointerdown=\"handleOnly ? undefined : startPointer(keys[index], $event)\"\n >\n <button\n type=\"button\"\n tabindex=\"-1\"\n data-gr-sortable-handle\n :class=\"[handleClass, disabled ? handleDisabledClass : '']\"\n :aria-label=\"handleLabel\"\n :disabled=\"disabled\"\n @pointerdown=\"startPointer(keys[index], $event)\"\n @click.stop\n >\n <slot\n name=\"handle\"\n :item=\"item\"\n :index=\"index\"\n :disabled=\"disabled\"\n >\n <IconGripVertical class=\"block\" aria-hidden=\"true\" />\n </slot>\n </button>\n\n <div :class=\"contentClass\">\n <slot\n name=\"item\"\n :item=\"item\"\n :index=\"index\"\n :dragging=\"sort.mode.value === 'pointer' && sort.source.value === keys[index]\"\n :grabbed=\"sort.mode.value === 'keyboard' && sort.source.value === keys[index]\"\n >\n {{ item }}\n </slot>\n </div>\n </div>\n </div>\n </GrCard>\n</template>\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"GrSteps-DXj7m-WO.js","names":[],"sources":["../../../../src/components/GrSteps/stepsModel.ts","../../../../src/components/GrSteps/GrSteps.vue","../../../../src/components/GrSteps/GrSteps.vue"],"sourcesContent":["import type { Component } from 'vue'\n\n/**\n * Правила шагового мастера — чистая арифметика без Vue.\n *\n * Вынесено отдельным модулем и тестируется без монтирования: «можно ли уйти на\n * шаг N» и «какого он статуса» — вся содержательная логика компонента, и\n * проверять её через отрендеренный DOM было бы дороже и слабее (эталон —\n * `GrForm/validation.ts`).\n */\n\n/**\n * Состояние шага.\n *\n * `error` не выводится из позиции — его ставит приложение, когда шаг пройден,\n * но не прошёл проверку. Без него мастер не умеет сказать «на втором шаге\n * остались ошибки», ради чего гейт перехода и заводится.\n */\nexport type GrStepStatus = 'complete' | 'current' | 'upcoming' | 'error'\n\nexport type GrStep = {\n value: string\n label: string\n /** Вторая строка под подписью: что от пользователя ждут на этом шаге. */\n description?: string\n /**\n * Иконка вместо номера — декоративна. Vue-компонент либо класс иконки вашей\n * UnoCSS-сборки (`i-lucide-*` требует вашего `presetIcons`).\n */\n icon?: string | Component\n /** Явный статус. Не задан — выводится из позиции относительно текущего шага. */\n status?: GrStepStatus\n disabled?: boolean\n}\n\nexport function stepIndexOf(steps: readonly GrStep[], value: string): number {\n return steps.findIndex(step => step.value === value)\n}\n\n/**\n * Статус шага: явный побеждает позиционный.\n *\n * Позиция даёт три состояния из четырёх, и только `error` приходит снаружи —\n * вывести его неоткуда, ошибка живёт в форме, а не в порядке шагов.\n */\nexport function stepStatusAt(steps: readonly GrStep[], index: number, currentIndex: number): GrStepStatus {\n const explicit = steps[index]?.status\n if (explicit)\n return explicit\n\n if (index < currentIndex)\n return 'complete'\n if (index === currentIndex)\n return 'current'\n return 'upcoming'\n}\n\n/**\n * Можно ли уйти на шаг по его индексу.\n *\n * `linear` ограничивает **только движение вперёд**, и не одним шагом, а первым\n * непройденным: вернуться назад и снова прыгнуть на тот же шаг, до которого уже\n * дошёл, — нормальный сценарий правки заполненного мастера. Запрещать его\n * значило бы заставлять пользователя проходить всё заново ради одной опечатки.\n */\nexport function canEnterStep(options: {\n steps: readonly GrStep[]\n index: number\n currentIndex: number\n linear: boolean\n}): boolean {\n const { steps, index, currentIndex, linear } = options\n const step = steps[index]\n\n if (!step || step.disabled)\n return false\n if (index === currentIndex)\n return false\n if (!linear || index < currentIndex)\n return true\n\n return index <= furthestReachableIndex(steps, currentIndex)\n}\n\n/**\n * Дальний край доступного в линейном режиме: текущий шаг плюс идущие за ним\n * подряд помеченные пройденными. Шаг с ошибкой краем не является — он и есть\n * причина, по которой дальше идти нельзя.\n */\nfunction furthestReachableIndex(steps: readonly GrStep[], currentIndex: number): number {\n let furthest = currentIndex + 1\n\n for (let index = currentIndex + 1; index < steps.length; index += 1) {\n if (steps[index]?.status !== 'complete')\n break\n furthest = index + 1\n }\n\n return Math.min(furthest, steps.length - 1)\n}\n\n/** Индекс следующего доступного шага, или `-1`, если дальше идти некуда. */\nexport function nextEnterableIndex(steps: readonly GrStep[], currentIndex: number): number {\n for (let index = currentIndex + 1; index < steps.length; index += 1) {\n if (!steps[index]?.disabled)\n return index\n }\n\n return -1\n}\n\n/** Индекс предыдущего доступного шага, или `-1`. */\nexport function previousEnterableIndex(steps: readonly GrStep[], currentIndex: number): number {\n for (let index = currentIndex - 1; index >= 0; index -= 1) {\n if (!steps[index]?.disabled)\n return index\n }\n\n return -1\n}\n","<script setup lang=\"ts\">\nimport type { Component } from 'vue'\nimport { computed } from 'vue'\n\nimport IconCheck from '~icons/lucide/check'\n\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\nimport GrProgressBar from '../GrProgressBar/GrProgressBar.vue'\n\nimport type { GrStepsOrientation, GrStepsSize, GrStepsVariant } from './grStepsStyles'\nimport {\n grStepsLabelClass,\n grStepsMarkerClass,\n stepsCompactBarClass,\n stepsCompactCounterClass,\n stepsCompactLabelClass,\n stepsCompactRowClass,\n stepsConnectorClass,\n stepsConnectorDoneClass,\n stepsConnectorPendingClass,\n stepsDescriptionClass,\n stepsItemClass,\n stepsItemConnectedClass,\n stepsListClass,\n stepsRootClass,\n stepsSizeClassBySize,\n stepsTriggerClass,\n stepsTriggerClassByOrientation,\n stepsTriggerEnabledClass,\n} from './grStepsStyles'\nimport type { GrStep, GrStepStatus } from './stepsModel'\nimport {\n canEnterStep,\n nextEnterableIndex,\n previousEnterableIndex,\n stepIndexOf,\n stepStatusAt,\n} from './stepsModel'\n\n/**\n * Индикатор шагов многошагового мастера.\n *\n * По семантике это **навигация**, а не составной виджет выбора: `<nav>` со\n * списком, где пройденный шаг — кнопка со своей остановкой `Tab`, текущий —\n * `aria-current=\"step\"`, будущий — вне таб-порядка. Той же формы держатся\n * `GrBreadcrumbs` и `GrBottomNav`; `role=\"tab\"` здесь неприменим — tablist без\n * `tabpanel` это сломанный паттерн, а панель шага никакой роли не несёт.\n *\n * Контент шага компонент не рисует: его ставит потребитель обычным `v-if`.\n */\nexport interface GrStepsProps {\n modelValue: string\n steps: GrStep[]\n orientation?: GrStepsOrientation\n size?: GrStepsSize\n /** Компактный вид для узкой колонки: подпись текущего шага и полоса прогресса. */\n variant?: GrStepsVariant\n /**\n * Вперёд — не дальше первого непройденного. Назад свободно всегда: вернуться\n * и поправить заполненное — обычный сценарий, а не обход правила.\n */\n linear?: boolean\n /** Переход кликом по шагу. Выключен — лента становится только индикатором. */\n clickable?: boolean\n ariaLabel?: string\n /**\n * Гейт перехода. Вернул `false` — перехода нет.\n *\n * Сюда потребитель кладёт валидацию шага: `GrSteps` про `GrForm` ничего не\n * знает и знать не должен, а форма уже отдаёт `validate()`/`validateField()`.\n */\n beforeLeave?: (from: string, to: string) => boolean | Promise<boolean>\n}\n\nexport interface GrStepsEmits {\n (e: 'update:modelValue', value: string): void\n (e: 'change', value: string): void\n}\n\nconst props = withDefaults(defineProps<GrStepsProps>(), {\n // Дефолты живут в резолверах ниже: Vue подставил бы свой раньше, чем\n // компонент заглянет в `GrConfigProvider`.\n orientation: undefined,\n size: undefined,\n variant: undefined,\n linear: undefined,\n clickable: undefined,\n ariaLabel: undefined,\n beforeLeave: undefined,\n})\n\nconst emit = defineEmits<GrStepsEmits>()\n\ndefineSlots<{\n /**\n * Своя разметка пункта. Корневой тег, `aria-current` и клик остаются за\n * компонентом — иначе доступность шага пришлось бы собирать заново.\n */\n step?: (props: { step: GrStep, index: number, status: GrStepStatus, enterable: boolean }) => unknown\n}>()\n\nconst { t } = useGranularityTranslations()\n\nconst resolvedSize = useGrComponentSize<GrStepsSize>(() => props.size, { component: 'GrSteps' })\nconst resolvedOrientation = useGrComponentProp('GrSteps', 'orientation', () => props.orientation, 'horizontal')\nconst resolvedVariant = useGrComponentProp('GrSteps', 'variant', () => props.variant, 'steps')\nconst resolvedLinear = useGrComponentProp('GrSteps', 'linear', () => props.linear, true)\nconst resolvedClickable = useGrComponentProp('GrSteps', 'clickable', () => props.clickable, true)\n\nconst currentIndex = computed(() => stepIndexOf(props.steps, props.modelValue))\nconst currentStep = computed(() => props.steps[currentIndex.value])\n\nconst resolvedAriaLabel = computed(() => props.ariaLabel ?? t('gr.steps.label', 'Progress'))\n\nfunction statusAt(index: number): GrStepStatus {\n return stepStatusAt(props.steps, index, currentIndex.value)\n}\n\nfunction isEnterable(index: number): boolean {\n if (!resolvedClickable.value)\n return false\n return canEnterStep({\n steps: props.steps,\n index,\n currentIndex: currentIndex.value,\n linear: resolvedLinear.value,\n })\n}\n\n/**\n * Пройденный шаг печатает галку вместо номера, шаг с ошибкой — восклицание.\n * Оба декоративны: смысл несёт скрытая подпись состояния рядом с меткой.\n */\nfunction markerText(index: number): string {\n return statusAt(index) === 'error' ? '!' : String(index + 1)\n}\n\nfunction stateText(status: GrStepStatus): string {\n if (status === 'complete')\n return t('gr.steps.completed', 'completed')\n if (status === 'error')\n return t('gr.steps.error', 'has errors')\n return ''\n}\n\nconst statusText = computed(() => t('gr.steps.status', 'Step {step} of {count}', {\n step: currentIndex.value + 1,\n count: props.steps.length,\n}))\n\n/** Полоса компактного вида: доля пройденного, а не номер шага. */\nconst progressValue = computed(() => {\n if (props.steps.length <= 1)\n return 100\n return Math.round((currentIndex.value / (props.steps.length - 1)) * 100)\n})\n\nfunction tagFor(index: number): 'button' | 'span' {\n return isEnterable(index) ? 'button' : 'span'\n}\n\nfunction triggerClass(index: number): string {\n const base = `${stepsTriggerClass} ${stepsTriggerClassByOrientation[resolvedOrientation.value]}`\n return isEnterable(index) ? `${base} ${stepsTriggerEnabledClass}` : base\n}\n\n/** Место под соединитель нужно только тем пунктам, за которыми ещё есть шаг. */\nfunction itemClass(index: number): string {\n const base = stepsItemClass[resolvedOrientation.value]\n if (index >= props.steps.length - 1)\n return base\n return `${base} ${stepsItemConnectedClass[resolvedOrientation.value]}`.trim()\n}\n\nfunction connectorClass(index: number): string {\n const done = statusAt(index) === 'complete'\n return `${stepsConnectorClass[resolvedOrientation.value]} ${done ? stepsConnectorDoneClass : stepsConnectorPendingClass}`\n}\n\nfunction iconOf(step: GrStep): string | Component | undefined {\n return step.icon\n}\n\nasync function goToIndex(index: number): Promise<boolean> {\n const target = props.steps[index]\n if (!target || target.value === props.modelValue)\n return false\n\n if (props.beforeLeave) {\n const allowed = await props.beforeLeave(props.modelValue, target.value)\n if (!allowed)\n return false\n }\n\n emit('update:modelValue', target.value)\n emit('change', target.value)\n\n return true\n}\n\nasync function onStepClick(index: number): Promise<void> {\n if (!isEnterable(index))\n return\n await goToIndex(index)\n}\n\nasync function next(): Promise<boolean> {\n const index = nextEnterableIndex(props.steps, currentIndex.value)\n return index === -1 ? false : goToIndex(index)\n}\n\nasync function back(): Promise<boolean> {\n const index = previousEnterableIndex(props.steps, currentIndex.value)\n return index === -1 ? false : goToIndex(index)\n}\n\nasync function goTo(value: string): Promise<boolean> {\n return goToIndex(stepIndexOf(props.steps, value))\n}\n\ndefineExpose({\n next,\n back,\n goTo,\n isFirst: computed(() => previousEnterableIndex(props.steps, currentIndex.value) === -1),\n isLast: computed(() => nextEnterableIndex(props.steps, currentIndex.value) === -1),\n})\n</script>\n\n<template>\n <nav\n data-gr-steps\n :data-orientation=\"resolvedOrientation\"\n :class=\"[stepsRootClass, stepsSizeClassBySize[resolvedSize]]\"\n :aria-label=\"resolvedAriaLabel\"\n >\n <div v-if=\"resolvedVariant === 'compact'\" data-gr-steps-compact>\n <div :class=\"stepsCompactRowClass\">\n <span :class=\"stepsCompactLabelClass\">{{ currentStep?.label }}</span>\n <span :class=\"stepsCompactCounterClass\">{{ currentIndex + 1 }} / {{ steps.length }}</span>\n </div>\n <!--\n Полоса декоративна: то же самое уже сказано текстом рядом и скрытым\n живым регионом ниже, а второй `progressbar` в дереве заставил бы\n диктора читать прогресс дважды.\n -->\n <GrProgressBar\n :value=\"progressValue\"\n :size=\"resolvedSize === 'lg' ? 'md' : 'sm'\"\n :class=\"stepsCompactBarClass\"\n aria-hidden=\"true\"\n />\n </div>\n\n <ol v-else data-gr-steps-list :class=\"stepsListClass[resolvedOrientation]\">\n <li\n v-for=\"(step, index) in steps\"\n :key=\"step.value\"\n data-gr-step\n :data-value=\"step.value\"\n :data-status=\"statusAt(index)\"\n :class=\"itemClass(index)\"\n >\n <component\n :is=\"tagFor(index)\"\n data-gr-step-trigger\n :type=\"tagFor(index) === 'button' ? 'button' : undefined\"\n :class=\"triggerClass(index)\"\n :aria-current=\"statusAt(index) === 'current' ? 'step' : undefined\"\n :aria-disabled=\"step.disabled ? 'true' : undefined\"\n @click=\"tagFor(index) === 'button' ? onStepClick(index) : undefined\"\n >\n <slot\n name=\"step\"\n :step=\"step\"\n :index=\"index\"\n :status=\"statusAt(index)\"\n :enterable=\"isEnterable(index)\"\n >\n <span data-gr-step-marker :class=\"grStepsMarkerClass(statusAt(index))\" aria-hidden=\"true\">\n <component :is=\"iconOf(step)\" v-if=\"step.icon\" class=\"h-[60%] w-[60%]\" />\n <IconCheck v-else-if=\"statusAt(index) === 'complete'\" class=\"h-[60%] w-[60%]\" />\n <template v-else>{{ markerText(index) }}</template>\n </span>\n\n <span class=\"block min-w-0\">\n <span data-gr-step-label :class=\"grStepsLabelClass(statusAt(index))\">{{ step.label }}</span>\n <!--\n Состояние шага словом: пройденный и шаг с ошибкой иначе звучат\n одинаково — цвет маркера диктору недоступен.\n -->\n <span v-if=\"stateText(statusAt(index))\" class=\"sr-only\"> — {{ stateText(statusAt(index)) }}</span>\n <span v-if=\"step.description\" data-gr-step-description :class=\"stepsDescriptionClass\">\n {{ step.description }}\n </span>\n </span>\n </slot>\n </component>\n\n <span\n v-if=\"index < steps.length - 1\"\n data-gr-step-connector\n :class=\"connectorClass(index)\"\n aria-hidden=\"true\"\n />\n </li>\n </ol>\n\n <!--\n «Шаг 2 из 5» — состояние виджета, а не событие, поэтому живёт своим\n регионом в разметке, а не уходит в общий объявитель (`docs/announcer.md`).\n Образец — компактный индикатор `GrPagination`.\n -->\n <div data-gr-steps-status role=\"status\" class=\"sr-only\">\n{{ statusText }}\n</div>\n </nav>\n</template>\n","<script setup lang=\"ts\">\nimport type { Component } from 'vue'\nimport { computed } from 'vue'\n\nimport IconCheck from '~icons/lucide/check'\n\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\nimport GrProgressBar from '../GrProgressBar/GrProgressBar.vue'\n\nimport type { GrStepsOrientation, GrStepsSize, GrStepsVariant } from './grStepsStyles'\nimport {\n grStepsLabelClass,\n grStepsMarkerClass,\n stepsCompactBarClass,\n stepsCompactCounterClass,\n stepsCompactLabelClass,\n stepsCompactRowClass,\n stepsConnectorClass,\n stepsConnectorDoneClass,\n stepsConnectorPendingClass,\n stepsDescriptionClass,\n stepsItemClass,\n stepsItemConnectedClass,\n stepsListClass,\n stepsRootClass,\n stepsSizeClassBySize,\n stepsTriggerClass,\n stepsTriggerClassByOrientation,\n stepsTriggerEnabledClass,\n} from './grStepsStyles'\nimport type { GrStep, GrStepStatus } from './stepsModel'\nimport {\n canEnterStep,\n nextEnterableIndex,\n previousEnterableIndex,\n stepIndexOf,\n stepStatusAt,\n} from './stepsModel'\n\n/**\n * Индикатор шагов многошагового мастера.\n *\n * По семантике это **навигация**, а не составной виджет выбора: `<nav>` со\n * списком, где пройденный шаг — кнопка со своей остановкой `Tab`, текущий —\n * `aria-current=\"step\"`, будущий — вне таб-порядка. Той же формы держатся\n * `GrBreadcrumbs` и `GrBottomNav`; `role=\"tab\"` здесь неприменим — tablist без\n * `tabpanel` это сломанный паттерн, а панель шага никакой роли не несёт.\n *\n * Контент шага компонент не рисует: его ставит потребитель обычным `v-if`.\n */\nexport interface GrStepsProps {\n modelValue: string\n steps: GrStep[]\n orientation?: GrStepsOrientation\n size?: GrStepsSize\n /** Компактный вид для узкой колонки: подпись текущего шага и полоса прогресса. */\n variant?: GrStepsVariant\n /**\n * Вперёд — не дальше первого непройденного. Назад свободно всегда: вернуться\n * и поправить заполненное — обычный сценарий, а не обход правила.\n */\n linear?: boolean\n /** Переход кликом по шагу. Выключен — лента становится только индикатором. */\n clickable?: boolean\n ariaLabel?: string\n /**\n * Гейт перехода. Вернул `false` — перехода нет.\n *\n * Сюда потребитель кладёт валидацию шага: `GrSteps` про `GrForm` ничего не\n * знает и знать не должен, а форма уже отдаёт `validate()`/`validateField()`.\n */\n beforeLeave?: (from: string, to: string) => boolean | Promise<boolean>\n}\n\nexport interface GrStepsEmits {\n (e: 'update:modelValue', value: string): void\n (e: 'change', value: string): void\n}\n\nconst props = withDefaults(defineProps<GrStepsProps>(), {\n // Дефолты живут в резолверах ниже: Vue подставил бы свой раньше, чем\n // компонент заглянет в `GrConfigProvider`.\n orientation: undefined,\n size: undefined,\n variant: undefined,\n linear: undefined,\n clickable: undefined,\n ariaLabel: undefined,\n beforeLeave: undefined,\n})\n\nconst emit = defineEmits<GrStepsEmits>()\n\ndefineSlots<{\n /**\n * Своя разметка пункта. Корневой тег, `aria-current` и клик остаются за\n * компонентом — иначе доступность шага пришлось бы собирать заново.\n */\n step?: (props: { step: GrStep, index: number, status: GrStepStatus, enterable: boolean }) => unknown\n}>()\n\nconst { t } = useGranularityTranslations()\n\nconst resolvedSize = useGrComponentSize<GrStepsSize>(() => props.size, { component: 'GrSteps' })\nconst resolvedOrientation = useGrComponentProp('GrSteps', 'orientation', () => props.orientation, 'horizontal')\nconst resolvedVariant = useGrComponentProp('GrSteps', 'variant', () => props.variant, 'steps')\nconst resolvedLinear = useGrComponentProp('GrSteps', 'linear', () => props.linear, true)\nconst resolvedClickable = useGrComponentProp('GrSteps', 'clickable', () => props.clickable, true)\n\nconst currentIndex = computed(() => stepIndexOf(props.steps, props.modelValue))\nconst currentStep = computed(() => props.steps[currentIndex.value])\n\nconst resolvedAriaLabel = computed(() => props.ariaLabel ?? t('gr.steps.label', 'Progress'))\n\nfunction statusAt(index: number): GrStepStatus {\n return stepStatusAt(props.steps, index, currentIndex.value)\n}\n\nfunction isEnterable(index: number): boolean {\n if (!resolvedClickable.value)\n return false\n return canEnterStep({\n steps: props.steps,\n index,\n currentIndex: currentIndex.value,\n linear: resolvedLinear.value,\n })\n}\n\n/**\n * Пройденный шаг печатает галку вместо номера, шаг с ошибкой — восклицание.\n * Оба декоративны: смысл несёт скрытая подпись состояния рядом с меткой.\n */\nfunction markerText(index: number): string {\n return statusAt(index) === 'error' ? '!' : String(index + 1)\n}\n\nfunction stateText(status: GrStepStatus): string {\n if (status === 'complete')\n return t('gr.steps.completed', 'completed')\n if (status === 'error')\n return t('gr.steps.error', 'has errors')\n return ''\n}\n\nconst statusText = computed(() => t('gr.steps.status', 'Step {step} of {count}', {\n step: currentIndex.value + 1,\n count: props.steps.length,\n}))\n\n/** Полоса компактного вида: доля пройденного, а не номер шага. */\nconst progressValue = computed(() => {\n if (props.steps.length <= 1)\n return 100\n return Math.round((currentIndex.value / (props.steps.length - 1)) * 100)\n})\n\nfunction tagFor(index: number): 'button' | 'span' {\n return isEnterable(index) ? 'button' : 'span'\n}\n\nfunction triggerClass(index: number): string {\n const base = `${stepsTriggerClass} ${stepsTriggerClassByOrientation[resolvedOrientation.value]}`\n return isEnterable(index) ? `${base} ${stepsTriggerEnabledClass}` : base\n}\n\n/** Место под соединитель нужно только тем пунктам, за которыми ещё есть шаг. */\nfunction itemClass(index: number): string {\n const base = stepsItemClass[resolvedOrientation.value]\n if (index >= props.steps.length - 1)\n return base\n return `${base} ${stepsItemConnectedClass[resolvedOrientation.value]}`.trim()\n}\n\nfunction connectorClass(index: number): string {\n const done = statusAt(index) === 'complete'\n return `${stepsConnectorClass[resolvedOrientation.value]} ${done ? stepsConnectorDoneClass : stepsConnectorPendingClass}`\n}\n\nfunction iconOf(step: GrStep): string | Component | undefined {\n return step.icon\n}\n\nasync function goToIndex(index: number): Promise<boolean> {\n const target = props.steps[index]\n if (!target || target.value === props.modelValue)\n return false\n\n if (props.beforeLeave) {\n const allowed = await props.beforeLeave(props.modelValue, target.value)\n if (!allowed)\n return false\n }\n\n emit('update:modelValue', target.value)\n emit('change', target.value)\n\n return true\n}\n\nasync function onStepClick(index: number): Promise<void> {\n if (!isEnterable(index))\n return\n await goToIndex(index)\n}\n\nasync function next(): Promise<boolean> {\n const index = nextEnterableIndex(props.steps, currentIndex.value)\n return index === -1 ? false : goToIndex(index)\n}\n\nasync function back(): Promise<boolean> {\n const index = previousEnterableIndex(props.steps, currentIndex.value)\n return index === -1 ? false : goToIndex(index)\n}\n\nasync function goTo(value: string): Promise<boolean> {\n return goToIndex(stepIndexOf(props.steps, value))\n}\n\ndefineExpose({\n next,\n back,\n goTo,\n isFirst: computed(() => previousEnterableIndex(props.steps, currentIndex.value) === -1),\n isLast: computed(() => nextEnterableIndex(props.steps, currentIndex.value) === -1),\n})\n</script>\n\n<template>\n <nav\n data-gr-steps\n :data-orientation=\"resolvedOrientation\"\n :class=\"[stepsRootClass, stepsSizeClassBySize[resolvedSize]]\"\n :aria-label=\"resolvedAriaLabel\"\n >\n <div v-if=\"resolvedVariant === 'compact'\" data-gr-steps-compact>\n <div :class=\"stepsCompactRowClass\">\n <span :class=\"stepsCompactLabelClass\">{{ currentStep?.label }}</span>\n <span :class=\"stepsCompactCounterClass\">{{ currentIndex + 1 }} / {{ steps.length }}</span>\n </div>\n <!--\n Полоса декоративна: то же самое уже сказано текстом рядом и скрытым\n живым регионом ниже, а второй `progressbar` в дереве заставил бы\n диктора читать прогресс дважды.\n -->\n <GrProgressBar\n :value=\"progressValue\"\n :size=\"resolvedSize === 'lg' ? 'md' : 'sm'\"\n :class=\"stepsCompactBarClass\"\n aria-hidden=\"true\"\n />\n </div>\n\n <ol v-else data-gr-steps-list :class=\"stepsListClass[resolvedOrientation]\">\n <li\n v-for=\"(step, index) in steps\"\n :key=\"step.value\"\n data-gr-step\n :data-value=\"step.value\"\n :data-status=\"statusAt(index)\"\n :class=\"itemClass(index)\"\n >\n <component\n :is=\"tagFor(index)\"\n data-gr-step-trigger\n :type=\"tagFor(index) === 'button' ? 'button' : undefined\"\n :class=\"triggerClass(index)\"\n :aria-current=\"statusAt(index) === 'current' ? 'step' : undefined\"\n :aria-disabled=\"step.disabled ? 'true' : undefined\"\n @click=\"tagFor(index) === 'button' ? onStepClick(index) : undefined\"\n >\n <slot\n name=\"step\"\n :step=\"step\"\n :index=\"index\"\n :status=\"statusAt(index)\"\n :enterable=\"isEnterable(index)\"\n >\n <span data-gr-step-marker :class=\"grStepsMarkerClass(statusAt(index))\" aria-hidden=\"true\">\n <component :is=\"iconOf(step)\" v-if=\"step.icon\" class=\"h-[60%] w-[60%]\" />\n <IconCheck v-else-if=\"statusAt(index) === 'complete'\" class=\"h-[60%] w-[60%]\" />\n <template v-else>{{ markerText(index) }}</template>\n </span>\n\n <span class=\"block min-w-0\">\n <span data-gr-step-label :class=\"grStepsLabelClass(statusAt(index))\">{{ step.label }}</span>\n <!--\n Состояние шага словом: пройденный и шаг с ошибкой иначе звучат\n одинаково — цвет маркера диктору недоступен.\n -->\n <span v-if=\"stateText(statusAt(index))\" class=\"sr-only\"> — {{ stateText(statusAt(index)) }}</span>\n <span v-if=\"step.description\" data-gr-step-description :class=\"stepsDescriptionClass\">\n {{ step.description }}\n </span>\n </span>\n </slot>\n </component>\n\n <span\n v-if=\"index < steps.length - 1\"\n data-gr-step-connector\n :class=\"connectorClass(index)\"\n aria-hidden=\"true\"\n />\n </li>\n </ol>\n\n <!--\n «Шаг 2 из 5» — состояние виджета, а не событие, поэтому живёт своим\n регионом в разметке, а не уходит в общий объявитель (`docs/announcer.md`).\n Образец — компактный индикатор `GrPagination`.\n -->\n <div data-gr-steps-status role=\"status\" class=\"sr-only\">\n{{ statusText }}\n</div>\n </nav>\n</template>\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"GrTabs-DHfv67cp.js","names":[],"sources":["../../../../src/components/shared/scrollOverflow.ts","../../../../src/components/GrTabs/GrTabs.vue","../../../../src/components/GrTabs/GrTabs.vue"],"sourcesContent":["/** С какой стороны у скроллера осталось невидимое содержимое. */\nexport type GrScrollOverflow = 'none' | 'start' | 'end' | 'both'\n\n/**\n * Состояние переполнения скроллера — по трём числам, без DOM.\n *\n * Отдельным модулем, потому что в jsdom раскладки нет: `scrollWidth` и\n * `clientWidth` там нули, и через смонтированный компонент ни одна ветка не\n * проверяется. Здесь она проверяется вся.\n *\n * `scrollStart` — расстояние от начала ряда, а не `scrollLeft`. Это не\n * педантизм: в RTL современные браузеры отсчитывают `scrollLeft` от правого\n * края и уводят его в минус, поэтому физическое «слева» и логическое «в начале»\n * там не совпадают. Модуль знает только про начало и конец, а какой это край\n * экрана — дело CSS.\n *\n * Запас в пиксель — как в {@link titleWhenTruncated}: браузеры округляют\n * размеры вверх, и у ряда, влезающего целиком, разница бывает единичной.\n * Без запаса затухание висело бы на ровном месте.\n */\nexport function resolveScrollOverflow(\n scrollStart: number,\n scrollSize: number,\n clientSize: number,\n): GrScrollOverflow {\n const hidden = scrollSize - clientSize\n\n if (hidden <= 1)\n return 'none'\n\n const start = Math.abs(scrollStart)\n const atStart = start <= 1\n const atEnd = hidden - start <= 1\n\n if (atStart && atEnd)\n return 'none'\n\n if (atStart)\n return 'end'\n\n if (atEnd)\n return 'start'\n\n return 'both'\n}\n","<script setup lang=\"ts\">\nimport { computed, nextTick, onBeforeUnmount, onMounted, ref, watch, type Component } from 'vue'\n\nimport IconX from '~icons/lucide/x'\n\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { useRovingFocus } from '../../composables/useRovingFocus'\nimport { iconClass, iconTag } from '../shared/icon'\nimport { resolveScrollOverflow, type GrScrollOverflow } from '../shared/scrollOverflow'\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\nimport {\n grTabsBadgeClass,\n grTabsCloseClass,\n grTabsEmptyClass,\n grTabsListClass,\n grTabsTabClass,\n tabCloseIconSizes,\n tabContentClass,\n tabIconClass,\n type GrTabsOrientation,\n type GrTabsSize,\n type GrTabsVariant,\n} from './grTabsStyles'\n\nexport interface GrTab {\n value: string\n label: string\n badge?: string\n /**\n * Иконка слева от подписи: Vue-компонент либо класс иконки вашей UnoCSS-сборки\n * (`'i-lucide-user'` — тогда нужен ваш `presetIcons`, см. `docs/installation.md`).\n */\n icon?: string | Component\n disabled?: boolean\n /** Перекрывает `closable` ряда в обе стороны: закрепить вкладку или закрыть одну её. */\n closable?: boolean\n}\n\nexport interface 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 size?: GrTabsSize\n /** Вид ряда: обойма с таблетками или ряд с подчёркиванием. */\n variant?: GrTabsVariant\n /**\n * `automatic` (по умолчанию) — стрелка сразу переключает вкладку;\n * `manual` — стрелка двигает только фокус, выбор подтверждается\n * `Enter`/`Space`. Второй режим для вкладок с тяжёлой загрузкой: перебор\n * стрелками иначе тянет каждую панель.\n */\n activationMode?: GrTabsActivationMode\n /** Горизонтальный (по умолчанию) или вертикальный список вкладок. */\n orientation?: GrTabsOrientation\n /**\n * Крестик на вкладках и закрытие по `Delete`/`Backspace`. Точечно снимается\n * `closable: false` у самой вкладки.\n */\n closable?: boolean\n /** Текст, когда список вкладок пуст. Слот `#empty` сильнее. */\n emptyText?: string\n}\n\nexport interface GrTabsEmits {\n (e: 'update:modelValue', value: string): void\n /**\n * Просьба закрыть вкладку. Компонент список не трогает: `tabs` — проп, и\n * закрытие может не состояться («сохранить изменения?»).\n */\n (e: 'close', value: string): void\n}\n\nexport type GrTabsActivationMode = 'automatic' | 'manual'\n\nexport type { GrTabsOrientation, GrTabsVariant } from './grTabsStyles'\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 = withDefaults(defineProps<GrTabsProps>(), {\n idBase: undefined,\n size: undefined,\n // Дефолт живёт в резолвере: Vue подставил бы свой раньше, чем компонент\n // заглянет в `GrConfigProvider`.\n variant: undefined,\n activationMode: 'automatic',\n orientation: 'horizontal',\n closable: false,\n emptyText: undefined,\n})\n\ndefineSlots<{\n /** Содержимое вкладки целиком — вместо подписи, иконки и счётчика. Крестик остаётся. */\n tab?: (props: { tab: GrTab, active: boolean, disabled: boolean }) => unknown\n /** Пустой ряд — вместо текста из локали. */\n empty?: () => unknown\n}>()\n\nconst emit = defineEmits<GrTabsEmits>()\n\nconst { t } = useGranularityTranslations()\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrTabs' })\nconst resolvedVariant = useGrComponentProp('GrTabs', 'variant', () => props.variant, 'pills')\n\nconst tablistClass = computed(() => grTabsListClass({\n variant: resolvedVariant.value,\n size: resolvedSize.value,\n orientation: props.orientation,\n}))\n\nfunction tabClass(tab: GrTab): string {\n return grTabsTabClass({\n variant: resolvedVariant.value,\n size: resolvedSize.value,\n active: tab.value === props.modelValue,\n disabled: Boolean(tab.disabled),\n })\n}\n\nconst badgeClass = computed(() => grTabsBadgeClass(resolvedSize.value))\nconst closeClass = computed(() => grTabsCloseClass(resolvedSize.value))\nconst closeIconClass = computed(() => tabCloseIconSizes[resolvedSize.value])\nconst emptyClass = computed(() => grTabsEmptyClass(resolvedSize.value))\n\n/**\n * Пустой ряд — не пустой `tablist`: роль обязана владеть потомками `tab`,\n * и текст внутри неё был бы нарушением, а не пустым состоянием. Поэтому\n * пустое состояние рисуется отдельной веткой, а не содержимым `tablist`.\n */\nconst isEmpty = computed(() => props.tabs.length === 0)\nconst resolvedEmptyText = computed(() => props.emptyText ?? t('gr.tabs.empty', 'Nothing here yet'))\nconst closeTitle = computed(() => t('gr.tabs.close', 'Close tab'))\n\n/** Отключённая вкладка не принимает взаимодействия, а закрытие — тоже взаимодействие. */\nfunction isClosable(tab: GrTab): boolean {\n return !tab.disabled && (tab.closable ?? props.closable)\n}\n\nconst buttonRefs = ref<HTMLButtonElement[]>([])\n\n/**\n * Vue зовёт ref-функцию с `null`, когда узел исчез. Игнорировать такой вызов\n * нельзя: в массиве оставались отсоединённые от DOM кнопки, и `focus()` по\n * сократившемуся списку молча проваливался в `<body>`.\n */\nfunction setButtonRef(el: unknown, index: number): void {\n if (el instanceof HTMLButtonElement)\n buttonRefs.value[index] = el\n else\n delete buttonRefs.value[index]\n\n if (buttonRefs.value.length > props.tabs.length)\n buttonRefs.value.length = props.tabs.length\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\nconst tabIndexes = computed(() => props.tabs.map((_, index) => index))\n\n/**\n * Кольцо roving-фокуса. При `activationMode: 'manual'` фокус уезжает вперёд\n * выбора — это и есть разница между `activeKey` примитива и `initialKey`:\n * первый ведёт фокус, второй подставляет выбранную вкладку, пока фокус не\n * трогали.\n *\n * Выбор стрелка **не** переносит: этим заведует `onKeydown` по\n * `activationMode`. Через `onMove` это выразить нельзя — клик тоже зовёт\n * `focusKey`, и выбор эмитился бы дважды.\n */\nconst roving = useRovingFocus<number>({\n items: () => tabIndexes.value,\n elementFor: index => buttonRefs.value[index],\n isDisabled: index => Boolean(props.tabs[index]?.disabled),\n orientation: () => (props.orientation === 'vertical' ? 'vertical' : 'horizontal'),\n skipDisabled: () => true,\n /**\n * Пока фокус не трогали, остановку `Tab` держит выбранная вкладка. Если\n * `modelValue` не совпал ни с одной (пустое начальное значение, асинхронный\n * список, удалённая активная вкладка), примитив ставит её на первую\n * доступную — иначе tablist выпал бы из обхода целиком и молча.\n *\n * Выключенность здесь не проверяется намеренно: на кнопке `aria-disabled`, а\n * не нативный `disabled`, поэтому выключенная вкладка фокусируется и держать\n * остановку вправе.\n */\n initialKey: () => (activeIndex.value >= 0 ? activeIndex.value : undefined),\n /**\n * Прокрутка до фокуса, а не после: иначе браузер доскроллит сам по-своему, и\n * ряд дёрнется дважды. Синхронно — фокус обязан остаться в том же такте, что\n * и раньше: тик, где он нужен, добавляет `focusIndex`.\n */\n beforeFocus: index => scrollIntoView(index),\n})\n\n/**\n * Фокус после перерисовки — для путей, где список только что изменился\n * (закрытие вкладки). Клавиатура идёт через примитив напрямую: там ряд уже\n * отрисован, и лишний тик только отложил бы фокус.\n */\nasync function focusIndex(index: number): Promise<void> {\n await nextTick()\n await roving.focusKey(index)\n}\n\n/**\n * Ряд прокручивается, а не переносится, поэтому активная вкладка может уехать\n * за край — например, когда её выбрали снаружи. `scrollIntoView` нет в jsdom и\n * в старых движках, поэтому вызов необязательный.\n */\nfunction scrollIntoView(index: number): void {\n buttonRefs.value[index]?.scrollIntoView?.({ block: 'nearest', inline: 'nearest' })\n}\n\nwatch(() => props.modelValue, async () => {\n await nextTick()\n if (activeIndex.value >= 0)\n scrollIntoView(activeIndex.value)\n})\n\n/**\n * Признак того, что ряд продолжается за краем.\n *\n * Полоса прокрутки у ряда скрыта намеренно, и без этого признака продолжение\n * не выдаёт себя ничем: вкладки за краем достижимы стрелками, но о том, что они\n * там есть, догадаться нечем. Затухание рисует CSS по `data-overflow`.\n *\n * Вертикальный ряд не скроллится вовсе — он растёт вниз, — поэтому состояние\n * считается только для горизонтального.\n */\nconst tablistEl = ref<HTMLElement | null>(null)\nconst scrollOverflow = ref<GrScrollOverflow>('none')\n\nlet resizeObserver: ResizeObserver | null = null\nlet scheduled = false\n\nfunction measureOverflow(): void {\n const el = tablistEl.value\n\n if (!el || props.orientation === 'vertical') {\n scrollOverflow.value = 'none'\n return\n }\n\n scrollOverflow.value = resolveScrollOverflow(el.scrollLeft, el.scrollWidth, el.clientWidth)\n}\n\nfunction scheduleMeasure(): void {\n if (scheduled)\n return\n\n scheduled = true\n void nextTick(() => {\n scheduled = false\n measureOverflow()\n })\n}\n\nonMounted(() => {\n scheduleMeasure()\n\n // На сервере и в jsdom `ResizeObserver` отсутствует — измерять там нечего.\n if (typeof ResizeObserver === 'undefined')\n return\n\n resizeObserver = new ResizeObserver(scheduleMeasure)\n if (tablistEl.value)\n resizeObserver.observe(tablistEl.value)\n})\n\nonBeforeUnmount(() => {\n resizeObserver?.disconnect()\n resizeObserver = null\n})\n\n// Состав ряда меняет его ширину, ориентация — само наличие прокрутки.\nwatch(() => [props.tabs.length, props.orientation], scheduleMeasure)\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 focusIndex(index)\n}\n\nfunction onKeydown(event: KeyboardEvent): void {\n if (props.tabs.length === 0)\n return\n\n const currentIndex = roving.rovingKey.value ?? 0\n\n switch (event.key) {\n case 'Enter':\n case ' ':\n // В ручном режиме выбор подтверждается явно — на той вкладке, где фокус.\n if (props.activationMode === 'manual') {\n event.preventDefault()\n void selectByIndex(currentIndex, false)\n }\n return\n case 'Delete':\n case 'Backspace': {\n // Единственный клавиатурный путь к закрытию: крестик в таб-порядке не стоит.\n const tab = props.tabs[currentIndex]\n if (tab && isClosable(tab)) {\n event.preventDefault()\n requestClose(currentIndex)\n }\n return\n }\n }\n\n // Стрелки, `Home`, `End` — фокус ведёт примитив по объявленной ориентации.\n if (!roving.handleNavigationKeys(event))\n return\n\n // `automatic`: выбор едет за фокусом. `manual`: ждёт `Enter`/`Space`.\n if (props.activationMode !== 'manual')\n void selectByIndex(roving.rovingKey.value ?? currentIndex, false)\n}\n\n/**\n * Закрытая вкладка, которая держала фокус. Компонент список не укорачивает —\n * это делает потребитель, — поэтому фокус возвращается только после того, как\n * `tabs` действительно стал короче.\n */\nlet pendingClose: { index: number, value: string } | null = null\n\nfunction requestClose(index: number): void {\n const tab = props.tabs[index]\n if (!tab || !isClosable(tab))\n return\n\n const held = buttonRefs.value[index] === document.activeElement\n pendingClose = held ? { index, value: tab.value } : null\n\n emit('close', tab.value)\n}\n\n// Без этого клавиатурное закрытие роняет фокус в `<body>`: кнопка, на которой\n// он стоял, исчезает из DOM вместе со вкладкой.\nwatch(() => props.tabs.length, async (next, prev) => {\n const pending = pendingClose\n pendingClose = null\n\n if (!pending || next >= prev)\n return\n // Ушла не та вкладка — потребитель поменял список по своей причине.\n if (props.tabs.some(tab => tab.value === pending.value))\n return\n\n await nextTick()\n\n const target = Math.min(pending.index, next - 1)\n if (target >= 0)\n void focusIndex(target)\n else\n roving.reset()\n})\n\nfunction onClick(event: MouseEvent, tab: GrTab, index: number): void {\n if (!isEnabled(tab))\n return\n\n // Крестик — не кнопка, поэтому его клик приходит сюда же и разбирается по цели.\n if (event.target instanceof Element && event.target.closest('[data-gr-tab-close]')) {\n requestClose(index)\n return\n }\n\n roving.setActive(index)\n\n if (tab.value !== props.modelValue)\n emit('update:modelValue', tab.value)\n}\n</script>\n\n<template>\n <div v-if=\"isEmpty\" data-gr-tabs-empty :class=\"emptyClass\">\n <slot name=\"empty\">\n {{ resolvedEmptyText }}\n </slot>\n </div>\n\n <div\n v-else\n ref=\"tablistEl\"\n role=\"tablist\"\n data-gr-tabs\n :aria-orientation=\"orientation\"\n :data-overflow=\"orientation === 'vertical' ? undefined : scrollOverflow\"\n :class=\"tablistClass\"\n @keydown=\"onKeydown\"\n @scroll.passive=\"scheduleMeasure\"\n >\n <button\n v-for=\"(tab, index) in tabs\"\n :id=\"idBase ? `${idBase}-tab-${tab.value}` : undefined\"\n :key=\"tab.value\"\n :ref=\"el => setButtonRef(el, index)\"\n type=\"button\"\n role=\"tab\"\n data-gr-tab\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 :aria-keyshortcuts=\"isClosable(tab) ? 'Delete' : undefined\"\n :tabindex=\"roving.tabindexFor(index)\"\n :class=\"tabClass(tab)\"\n @click=\"onClick($event, tab, index)\"\n >\n <span :class=\"tabContentClass\">\n <slot\n name=\"tab\"\n :tab=\"tab\"\n :active=\"tab.value === modelValue\"\n :disabled=\"Boolean(tab.disabled)\"\n >\n <component\n :is=\"iconTag(tab.icon)\"\n v-if=\"tab.icon\"\n :class=\"[tabIconClass, iconClass(tab.icon)]\"\n aria-hidden=\"true\"\n />\n <span>{{ tab.label }}</span>\n <span v-if=\"tab.badge\" :class=\"badgeClass\">\n {{ tab.badge }}\n </span>\n </slot>\n\n <!-- Снаружи слота: своя разметка вкладки не должна отнимать закрытие. -->\n <span\n v-if=\"isClosable(tab)\"\n data-gr-tab-close\n :class=\"closeClass\"\n :title=\"closeTitle\"\n aria-hidden=\"true\"\n >\n <IconX :class=\"closeIconClass\" />\n </span>\n </span>\n </button>\n </div>\n</template>\n\n<style>\n/*\n * Затухание у края, за которым ряд продолжается.\n *\n * Здесь, а не утилитами, по двум причинам сразу. `mask-image` в `presetMini` не\n * выражается — это уже зафиксировано отказом в `GrImageCrop`. И маской, а не\n * градиентом-подложкой, потому что фон полосы у вариантов разный: `pills` несёт\n * свой непрозрачный `--gr-muted`, а `line` прозрачен и лежит на фоне родителя,\n * которого компонент не знает. Градиенту пришлось бы красить в цвет подложки —\n * для `line` его неоткуда взять. Маска гасит содержимое независимо от того, что\n * под ним.\n *\n * Состояние считается логически (начало/конец ряда), а направление градиента в\n * CSS выражается только физически — отсюда зеркальные правила для RTL.\n */\n\n[data-gr-tabs] {\n /*\n * `scrollIntoView({ inline: 'nearest' })` из кольца фокуса прижимает вкладку\n * вплотную к краю. Без отступа прокрутки кольцо фокуса у клавиатурного\n * пользователя оказалось бы ровно под затуханием.\n */\n scroll-padding-inline: var(--gr-tabs-scroll-fade, 1.5rem);\n}\n\n[data-gr-tabs][data-overflow='start'] {\n --gr-tabs-mask: linear-gradient(to right, transparent 0, #000 var(--gr-tabs-scroll-fade, 1.5rem));\n}\n\n[data-gr-tabs][data-overflow='end'] {\n --gr-tabs-mask: linear-gradient(to right, #000 calc(100% - var(--gr-tabs-scroll-fade, 1.5rem)), transparent 100%);\n}\n\n[data-gr-tabs][data-overflow='both'] {\n --gr-tabs-mask: linear-gradient(\n to right,\n transparent 0,\n #000 var(--gr-tabs-scroll-fade, 1.5rem),\n #000 calc(100% - var(--gr-tabs-scroll-fade, 1.5rem)),\n transparent 100%\n );\n}\n\n[dir='rtl'] [data-gr-tabs][data-overflow='start'] {\n --gr-tabs-mask: linear-gradient(to left, transparent 0, #000 var(--gr-tabs-scroll-fade, 1.5rem));\n}\n\n[dir='rtl'] [data-gr-tabs][data-overflow='end'] {\n --gr-tabs-mask: linear-gradient(to left, #000 calc(100% - var(--gr-tabs-scroll-fade, 1.5rem)), transparent 100%);\n}\n\n[data-gr-tabs]:is([data-overflow='start'], [data-overflow='end'], [data-overflow='both']) {\n -webkit-mask-image: var(--gr-tabs-mask);\n mask-image: var(--gr-tabs-mask);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, nextTick, onBeforeUnmount, onMounted, ref, watch, type Component } from 'vue'\n\nimport IconX from '~icons/lucide/x'\n\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { useRovingFocus } from '../../composables/useRovingFocus'\nimport { iconClass, iconTag } from '../shared/icon'\nimport { resolveScrollOverflow, type GrScrollOverflow } from '../shared/scrollOverflow'\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\nimport {\n grTabsBadgeClass,\n grTabsCloseClass,\n grTabsEmptyClass,\n grTabsListClass,\n grTabsTabClass,\n tabCloseIconSizes,\n tabContentClass,\n tabIconClass,\n type GrTabsOrientation,\n type GrTabsSize,\n type GrTabsVariant,\n} from './grTabsStyles'\n\nexport interface GrTab {\n value: string\n label: string\n badge?: string\n /**\n * Иконка слева от подписи: Vue-компонент либо класс иконки вашей UnoCSS-сборки\n * (`'i-lucide-user'` — тогда нужен ваш `presetIcons`, см. `docs/installation.md`).\n */\n icon?: string | Component\n disabled?: boolean\n /** Перекрывает `closable` ряда в обе стороны: закрепить вкладку или закрыть одну её. */\n closable?: boolean\n}\n\nexport interface 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 size?: GrTabsSize\n /** Вид ряда: обойма с таблетками или ряд с подчёркиванием. */\n variant?: GrTabsVariant\n /**\n * `automatic` (по умолчанию) — стрелка сразу переключает вкладку;\n * `manual` — стрелка двигает только фокус, выбор подтверждается\n * `Enter`/`Space`. Второй режим для вкладок с тяжёлой загрузкой: перебор\n * стрелками иначе тянет каждую панель.\n */\n activationMode?: GrTabsActivationMode\n /** Горизонтальный (по умолчанию) или вертикальный список вкладок. */\n orientation?: GrTabsOrientation\n /**\n * Крестик на вкладках и закрытие по `Delete`/`Backspace`. Точечно снимается\n * `closable: false` у самой вкладки.\n */\n closable?: boolean\n /** Текст, когда список вкладок пуст. Слот `#empty` сильнее. */\n emptyText?: string\n}\n\nexport interface GrTabsEmits {\n (e: 'update:modelValue', value: string): void\n /**\n * Просьба закрыть вкладку. Компонент список не трогает: `tabs` — проп, и\n * закрытие может не состояться («сохранить изменения?»).\n */\n (e: 'close', value: string): void\n}\n\nexport type GrTabsActivationMode = 'automatic' | 'manual'\n\nexport type { GrTabsOrientation, GrTabsVariant } from './grTabsStyles'\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 = withDefaults(defineProps<GrTabsProps>(), {\n idBase: undefined,\n size: undefined,\n // Дефолт живёт в резолвере: Vue подставил бы свой раньше, чем компонент\n // заглянет в `GrConfigProvider`.\n variant: undefined,\n activationMode: 'automatic',\n orientation: 'horizontal',\n closable: false,\n emptyText: undefined,\n})\n\ndefineSlots<{\n /** Содержимое вкладки целиком — вместо подписи, иконки и счётчика. Крестик остаётся. */\n tab?: (props: { tab: GrTab, active: boolean, disabled: boolean }) => unknown\n /** Пустой ряд — вместо текста из локали. */\n empty?: () => unknown\n}>()\n\nconst emit = defineEmits<GrTabsEmits>()\n\nconst { t } = useGranularityTranslations()\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrTabs' })\nconst resolvedVariant = useGrComponentProp('GrTabs', 'variant', () => props.variant, 'pills')\n\nconst tablistClass = computed(() => grTabsListClass({\n variant: resolvedVariant.value,\n size: resolvedSize.value,\n orientation: props.orientation,\n}))\n\nfunction tabClass(tab: GrTab): string {\n return grTabsTabClass({\n variant: resolvedVariant.value,\n size: resolvedSize.value,\n active: tab.value === props.modelValue,\n disabled: Boolean(tab.disabled),\n })\n}\n\nconst badgeClass = computed(() => grTabsBadgeClass(resolvedSize.value))\nconst closeClass = computed(() => grTabsCloseClass(resolvedSize.value))\nconst closeIconClass = computed(() => tabCloseIconSizes[resolvedSize.value])\nconst emptyClass = computed(() => grTabsEmptyClass(resolvedSize.value))\n\n/**\n * Пустой ряд — не пустой `tablist`: роль обязана владеть потомками `tab`,\n * и текст внутри неё был бы нарушением, а не пустым состоянием. Поэтому\n * пустое состояние рисуется отдельной веткой, а не содержимым `tablist`.\n */\nconst isEmpty = computed(() => props.tabs.length === 0)\nconst resolvedEmptyText = computed(() => props.emptyText ?? t('gr.tabs.empty', 'Nothing here yet'))\nconst closeTitle = computed(() => t('gr.tabs.close', 'Close tab'))\n\n/** Отключённая вкладка не принимает взаимодействия, а закрытие — тоже взаимодействие. */\nfunction isClosable(tab: GrTab): boolean {\n return !tab.disabled && (tab.closable ?? props.closable)\n}\n\nconst buttonRefs = ref<HTMLButtonElement[]>([])\n\n/**\n * Vue зовёт ref-функцию с `null`, когда узел исчез. Игнорировать такой вызов\n * нельзя: в массиве оставались отсоединённые от DOM кнопки, и `focus()` по\n * сократившемуся списку молча проваливался в `<body>`.\n */\nfunction setButtonRef(el: unknown, index: number): void {\n if (el instanceof HTMLButtonElement)\n buttonRefs.value[index] = el\n else\n delete buttonRefs.value[index]\n\n if (buttonRefs.value.length > props.tabs.length)\n buttonRefs.value.length = props.tabs.length\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\nconst tabIndexes = computed(() => props.tabs.map((_, index) => index))\n\n/**\n * Кольцо roving-фокуса. При `activationMode: 'manual'` фокус уезжает вперёд\n * выбора — это и есть разница между `activeKey` примитива и `initialKey`:\n * первый ведёт фокус, второй подставляет выбранную вкладку, пока фокус не\n * трогали.\n *\n * Выбор стрелка **не** переносит: этим заведует `onKeydown` по\n * `activationMode`. Через `onMove` это выразить нельзя — клик тоже зовёт\n * `focusKey`, и выбор эмитился бы дважды.\n */\nconst roving = useRovingFocus<number>({\n items: () => tabIndexes.value,\n elementFor: index => buttonRefs.value[index],\n isDisabled: index => Boolean(props.tabs[index]?.disabled),\n orientation: () => (props.orientation === 'vertical' ? 'vertical' : 'horizontal'),\n skipDisabled: () => true,\n /**\n * Пока фокус не трогали, остановку `Tab` держит выбранная вкладка. Если\n * `modelValue` не совпал ни с одной (пустое начальное значение, асинхронный\n * список, удалённая активная вкладка), примитив ставит её на первую\n * доступную — иначе tablist выпал бы из обхода целиком и молча.\n *\n * Выключенность здесь не проверяется намеренно: на кнопке `aria-disabled`, а\n * не нативный `disabled`, поэтому выключенная вкладка фокусируется и держать\n * остановку вправе.\n */\n initialKey: () => (activeIndex.value >= 0 ? activeIndex.value : undefined),\n /**\n * Прокрутка до фокуса, а не после: иначе браузер доскроллит сам по-своему, и\n * ряд дёрнется дважды. Синхронно — фокус обязан остаться в том же такте, что\n * и раньше: тик, где он нужен, добавляет `focusIndex`.\n */\n beforeFocus: index => scrollIntoView(index),\n})\n\n/**\n * Фокус после перерисовки — для путей, где список только что изменился\n * (закрытие вкладки). Клавиатура идёт через примитив напрямую: там ряд уже\n * отрисован, и лишний тик только отложил бы фокус.\n */\nasync function focusIndex(index: number): Promise<void> {\n await nextTick()\n await roving.focusKey(index)\n}\n\n/**\n * Ряд прокручивается, а не переносится, поэтому активная вкладка может уехать\n * за край — например, когда её выбрали снаружи. `scrollIntoView` нет в jsdom и\n * в старых движках, поэтому вызов необязательный.\n */\nfunction scrollIntoView(index: number): void {\n buttonRefs.value[index]?.scrollIntoView?.({ block: 'nearest', inline: 'nearest' })\n}\n\nwatch(() => props.modelValue, async () => {\n await nextTick()\n if (activeIndex.value >= 0)\n scrollIntoView(activeIndex.value)\n})\n\n/**\n * Признак того, что ряд продолжается за краем.\n *\n * Полоса прокрутки у ряда скрыта намеренно, и без этого признака продолжение\n * не выдаёт себя ничем: вкладки за краем достижимы стрелками, но о том, что они\n * там есть, догадаться нечем. Затухание рисует CSS по `data-overflow`.\n *\n * Вертикальный ряд не скроллится вовсе — он растёт вниз, — поэтому состояние\n * считается только для горизонтального.\n */\nconst tablistEl = ref<HTMLElement | null>(null)\nconst scrollOverflow = ref<GrScrollOverflow>('none')\n\nlet resizeObserver: ResizeObserver | null = null\nlet scheduled = false\n\nfunction measureOverflow(): void {\n const el = tablistEl.value\n\n if (!el || props.orientation === 'vertical') {\n scrollOverflow.value = 'none'\n return\n }\n\n scrollOverflow.value = resolveScrollOverflow(el.scrollLeft, el.scrollWidth, el.clientWidth)\n}\n\nfunction scheduleMeasure(): void {\n if (scheduled)\n return\n\n scheduled = true\n void nextTick(() => {\n scheduled = false\n measureOverflow()\n })\n}\n\nonMounted(() => {\n scheduleMeasure()\n\n // На сервере и в jsdom `ResizeObserver` отсутствует — измерять там нечего.\n if (typeof ResizeObserver === 'undefined')\n return\n\n resizeObserver = new ResizeObserver(scheduleMeasure)\n if (tablistEl.value)\n resizeObserver.observe(tablistEl.value)\n})\n\nonBeforeUnmount(() => {\n resizeObserver?.disconnect()\n resizeObserver = null\n})\n\n// Состав ряда меняет его ширину, ориентация — само наличие прокрутки.\nwatch(() => [props.tabs.length, props.orientation], scheduleMeasure)\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 focusIndex(index)\n}\n\nfunction onKeydown(event: KeyboardEvent): void {\n if (props.tabs.length === 0)\n return\n\n const currentIndex = roving.rovingKey.value ?? 0\n\n switch (event.key) {\n case 'Enter':\n case ' ':\n // В ручном режиме выбор подтверждается явно — на той вкладке, где фокус.\n if (props.activationMode === 'manual') {\n event.preventDefault()\n void selectByIndex(currentIndex, false)\n }\n return\n case 'Delete':\n case 'Backspace': {\n // Единственный клавиатурный путь к закрытию: крестик в таб-порядке не стоит.\n const tab = props.tabs[currentIndex]\n if (tab && isClosable(tab)) {\n event.preventDefault()\n requestClose(currentIndex)\n }\n return\n }\n }\n\n // Стрелки, `Home`, `End` — фокус ведёт примитив по объявленной ориентации.\n if (!roving.handleNavigationKeys(event))\n return\n\n // `automatic`: выбор едет за фокусом. `manual`: ждёт `Enter`/`Space`.\n if (props.activationMode !== 'manual')\n void selectByIndex(roving.rovingKey.value ?? currentIndex, false)\n}\n\n/**\n * Закрытая вкладка, которая держала фокус. Компонент список не укорачивает —\n * это делает потребитель, — поэтому фокус возвращается только после того, как\n * `tabs` действительно стал короче.\n */\nlet pendingClose: { index: number, value: string } | null = null\n\nfunction requestClose(index: number): void {\n const tab = props.tabs[index]\n if (!tab || !isClosable(tab))\n return\n\n const held = buttonRefs.value[index] === document.activeElement\n pendingClose = held ? { index, value: tab.value } : null\n\n emit('close', tab.value)\n}\n\n// Без этого клавиатурное закрытие роняет фокус в `<body>`: кнопка, на которой\n// он стоял, исчезает из DOM вместе со вкладкой.\nwatch(() => props.tabs.length, async (next, prev) => {\n const pending = pendingClose\n pendingClose = null\n\n if (!pending || next >= prev)\n return\n // Ушла не та вкладка — потребитель поменял список по своей причине.\n if (props.tabs.some(tab => tab.value === pending.value))\n return\n\n await nextTick()\n\n const target = Math.min(pending.index, next - 1)\n if (target >= 0)\n void focusIndex(target)\n else\n roving.reset()\n})\n\nfunction onClick(event: MouseEvent, tab: GrTab, index: number): void {\n if (!isEnabled(tab))\n return\n\n // Крестик — не кнопка, поэтому его клик приходит сюда же и разбирается по цели.\n if (event.target instanceof Element && event.target.closest('[data-gr-tab-close]')) {\n requestClose(index)\n return\n }\n\n roving.setActive(index)\n\n if (tab.value !== props.modelValue)\n emit('update:modelValue', tab.value)\n}\n</script>\n\n<template>\n <div v-if=\"isEmpty\" data-gr-tabs-empty :class=\"emptyClass\">\n <slot name=\"empty\">\n {{ resolvedEmptyText }}\n </slot>\n </div>\n\n <div\n v-else\n ref=\"tablistEl\"\n role=\"tablist\"\n data-gr-tabs\n :aria-orientation=\"orientation\"\n :data-overflow=\"orientation === 'vertical' ? undefined : scrollOverflow\"\n :class=\"tablistClass\"\n @keydown=\"onKeydown\"\n @scroll.passive=\"scheduleMeasure\"\n >\n <button\n v-for=\"(tab, index) in tabs\"\n :id=\"idBase ? `${idBase}-tab-${tab.value}` : undefined\"\n :key=\"tab.value\"\n :ref=\"el => setButtonRef(el, index)\"\n type=\"button\"\n role=\"tab\"\n data-gr-tab\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 :aria-keyshortcuts=\"isClosable(tab) ? 'Delete' : undefined\"\n :tabindex=\"roving.tabindexFor(index)\"\n :class=\"tabClass(tab)\"\n @click=\"onClick($event, tab, index)\"\n >\n <span :class=\"tabContentClass\">\n <slot\n name=\"tab\"\n :tab=\"tab\"\n :active=\"tab.value === modelValue\"\n :disabled=\"Boolean(tab.disabled)\"\n >\n <component\n :is=\"iconTag(tab.icon)\"\n v-if=\"tab.icon\"\n :class=\"[tabIconClass, iconClass(tab.icon)]\"\n aria-hidden=\"true\"\n />\n <span>{{ tab.label }}</span>\n <span v-if=\"tab.badge\" :class=\"badgeClass\">\n {{ tab.badge }}\n </span>\n </slot>\n\n <!-- Снаружи слота: своя разметка вкладки не должна отнимать закрытие. -->\n <span\n v-if=\"isClosable(tab)\"\n data-gr-tab-close\n :class=\"closeClass\"\n :title=\"closeTitle\"\n aria-hidden=\"true\"\n >\n <IconX :class=\"closeIconClass\" />\n </span>\n </span>\n </button>\n </div>\n</template>\n\n<style>\n/*\n * Затухание у края, за которым ряд продолжается.\n *\n * Здесь, а не утилитами, по двум причинам сразу. `mask-image` в `presetMini` не\n * выражается — это уже зафиксировано отказом в `GrImageCrop`. И маской, а не\n * градиентом-подложкой, потому что фон полосы у вариантов разный: `pills` несёт\n * свой непрозрачный `--gr-muted`, а `line` прозрачен и лежит на фоне родителя,\n * которого компонент не знает. Градиенту пришлось бы красить в цвет подложки —\n * для `line` его неоткуда взять. Маска гасит содержимое независимо от того, что\n * под ним.\n *\n * Состояние считается логически (начало/конец ряда), а направление градиента в\n * CSS выражается только физически — отсюда зеркальные правила для RTL.\n */\n\n[data-gr-tabs] {\n /*\n * `scrollIntoView({ inline: 'nearest' })` из кольца фокуса прижимает вкладку\n * вплотную к краю. Без отступа прокрутки кольцо фокуса у клавиатурного\n * пользователя оказалось бы ровно под затуханием.\n */\n scroll-padding-inline: var(--gr-tabs-scroll-fade, 1.5rem);\n}\n\n[data-gr-tabs][data-overflow='start'] {\n --gr-tabs-mask: linear-gradient(to right, transparent 0, #000 var(--gr-tabs-scroll-fade, 1.5rem));\n}\n\n[data-gr-tabs][data-overflow='end'] {\n --gr-tabs-mask: linear-gradient(to right, #000 calc(100% - var(--gr-tabs-scroll-fade, 1.5rem)), transparent 100%);\n}\n\n[data-gr-tabs][data-overflow='both'] {\n --gr-tabs-mask: linear-gradient(\n to right,\n transparent 0,\n #000 var(--gr-tabs-scroll-fade, 1.5rem),\n #000 calc(100% - var(--gr-tabs-scroll-fade, 1.5rem)),\n transparent 100%\n );\n}\n\n[dir='rtl'] [data-gr-tabs][data-overflow='start'] {\n --gr-tabs-mask: linear-gradient(to left, transparent 0, #000 var(--gr-tabs-scroll-fade, 1.5rem));\n}\n\n[dir='rtl'] [data-gr-tabs][data-overflow='end'] {\n --gr-tabs-mask: linear-gradient(to left, #000 calc(100% - var(--gr-tabs-scroll-fade, 1.5rem)), transparent 100%);\n}\n\n[data-gr-tabs]:is([data-overflow='start'], [data-overflow='end'], [data-overflow='both']) {\n -webkit-mask-image: var(--gr-tabs-mask);\n mask-image: var(--gr-tabs-mask);\n}\n</style>\n"],"mappings":"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"}
@@ -1,12 +1,12 @@
1
1
  import { t as x_default } from "../../../chunks/x-2KXnqxiD.js";
2
2
  import { n as useGranularityTranslations } from "../../../chunks/granularityI18n-BC5zsj_d.js";
3
3
  import { t as GrIcon_default } from "../../GrIcon/chunks/GrIcon-DTZIgmje.js";
4
- import { t as minus_default } from "../../../chunks/minus-oxdbHF33.js";
5
- import { t as useModalOverlay } from "../../../chunks/useModalOverlay-ACRkflzx.js";
6
- import { useDragGesture } from "../../../composables/useDragGesture.js";
7
4
  import { useAnnouncer } from "../../../composables/useAnnouncer.js";
5
+ import { useDragGesture } from "../../../composables/useDragGesture.js";
8
6
  import { t as chevron_left_default } from "../../../chunks/chevron-left-D8AUBAM7.js";
9
7
  import { t as chevron_right_default } from "../../../chunks/chevron-right-C6Z01h0X.js";
8
+ import { t as minus_default } from "../../../chunks/minus-oxdbHF33.js";
9
+ import { t as useModalOverlay } from "../../../chunks/useModalOverlay-ACRkflzx.js";
10
10
  import { a as emptyStateClass, c as toolbarSeparatorClass, i as chromeButtonClass, l as toolbarShellClass, o as scrimClass, r as badgeClass, s as toolbarButtonClass } from "../../../chunks/config-DjRhIB5g.js";
11
11
  import { Fragment, Teleport, Transition, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, guardReactiveProps, markRaw, mergeProps, nextTick, normalizeClass, normalizeProps, normalizeStyle, onBeforeUnmount, openBlock, ref, renderSlot, toDisplayString, toHandlers, unref, watch, withCtx } from "vue";
12
12
  //#region ~icons/lucide/download
@@ -3,8 +3,8 @@ import { n as useGranularityTranslations } from "../../../chunks/granularityI18n
3
3
  import { n as useGrFormFieldContext } from "../../../chunks/context-DdNIxa1w.js";
4
4
  import { useGrFormControl } from "../../../composables/useGrFormControl.js";
5
5
  import { t as useFocusWithin } from "../../../chunks/useFocusWithin-BwHE0rzm.js";
6
- import { t as bigStep } from "../../../chunks/numericStep-Bq2lj4ew.js";
7
6
  import { useDragGesture } from "../../../composables/useDragGesture.js";
7
+ import { t as bigStep } from "../../../chunks/numericStep-Bq2lj4ew.js";
8
8
  import { A as sliderTrackVerticalLengthClass, D as sliderTooltipClass, a as sliderFillOrientationClass, f as sliderMarkTickClass, g as sliderRailClass, j as sliderTrackWidthBySize, k as sliderTrackHeightBySize, n as sliderFillClass, s as sliderMarkLabelClassFor, v as sliderRootClass, x as sliderThumbClass } from "../../../chunks/grSliderStyles-kaz6VXZ3.js";
9
9
  import { Fragment, computed, createCommentVNode, createElementBlock, createElementVNode, defineComponent, normalizeClass, normalizeStyle, openBlock, ref, renderList, toDisplayString, unref, watchEffect } from "vue";
10
10
  //#region src/components/GrSlider/GrSlider.vue?vue&type=script&setup=true&lang.ts