@feugene/granularity 0.42.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 (177) hide show
  1. package/README.md +3 -3
  2. package/dist/chunks/config-BXRmsn3t.js +172 -0
  3. package/dist/chunks/config-BXRmsn3t.js.map +1 -0
  4. package/dist/chunks/config-Dg0bSoGw.js +81 -0
  5. package/dist/chunks/config-Dg0bSoGw.js.map +1 -0
  6. package/dist/chunks/config-DmED6aJV.js +237 -0
  7. package/dist/chunks/config-DmED6aJV.js.map +1 -0
  8. package/dist/chunks/{config-DWL81-aM.js → config-hnoZvcIM.js} +2 -20
  9. package/dist/chunks/config-hnoZvcIM.js.map +1 -0
  10. package/dist/chunks/dragSortGeometry-CY-llfrQ.js +87 -0
  11. package/dist/chunks/dragSortGeometry-CY-llfrQ.js.map +1 -0
  12. package/dist/chunks/{en-B9E3jO6m.js → en-C_QkUTRL.js} +32 -1
  13. package/dist/chunks/en-C_QkUTRL.js.map +1 -0
  14. package/dist/chunks/{es-DCkqHTlU.js → es-B4t_3Wnh.js} +3 -3
  15. package/dist/chunks/{es-DCkqHTlU.js.map → es-B4t_3Wnh.js.map} +1 -1
  16. package/dist/chunks/{es-QpzdIEut.js → es-DemSEhZE.js} +32 -1
  17. package/dist/chunks/es-DemSEhZE.js.map +1 -0
  18. package/dist/chunks/{generated-B95IBXs6.js → generated-VcxJTo_q.js} +210 -0
  19. package/dist/chunks/generated-VcxJTo_q.js.map +1 -0
  20. package/dist/chunks/{granular-provider-BG_2XqkS.js → granular-provider-BohXQcNt.js} +7 -1
  21. package/dist/chunks/granular-provider-BohXQcNt.js.map +1 -0
  22. package/dist/chunks/{ru-D_1Ya489.js → ru-CLUErjhY.js} +32 -1
  23. package/dist/chunks/ru-CLUErjhY.js.map +1 -0
  24. package/dist/chunks/scrollOverflow-B_kEdcol.js +31 -0
  25. package/dist/chunks/scrollOverflow-B_kEdcol.js.map +1 -0
  26. package/dist/chunks/tones-CRJAaYyg.js +21 -0
  27. package/dist/chunks/tones-CRJAaYyg.js.map +1 -0
  28. package/dist/components/GrAffix/chunks/GrAffix-CWtC7yi8.js +272 -0
  29. package/dist/components/GrAffix/chunks/GrAffix-CWtC7yi8.js.map +1 -0
  30. package/dist/components/GrAffix/index.js +3 -0
  31. package/dist/components/GrCarousel/chunks/GrCarousel-ByuughtI.js +761 -0
  32. package/dist/components/GrCarousel/chunks/GrCarousel-ByuughtI.js.map +1 -0
  33. package/dist/components/GrCarousel/index.js +3 -0
  34. package/dist/components/GrCarousel/styles.css +1 -0
  35. package/dist/components/GrColorPicker/chunks/{GrColorPicker-BwzRLs-i.js → GrColorPicker-9mTR-YZ_.js} +1 -1
  36. package/dist/components/GrColorPicker/chunks/{GrColorPicker-BwzRLs-i.js.map → GrColorPicker-9mTR-YZ_.js.map} +1 -1
  37. package/dist/components/GrColorPicker/index.js +1 -1
  38. package/dist/components/GrDataTable/chunks/{GrDataTable-BBe7-uUe.js → GrDataTable-xNSmO_I-.js} +4 -3
  39. package/dist/components/GrDataTable/chunks/{GrDataTable-BBe7-uUe.js.map → GrDataTable-xNSmO_I-.js.map} +1 -1
  40. package/dist/components/GrDataTable/index.js +1 -1
  41. package/dist/components/GrImageViewer/chunks/{GrImageViewer-Dd2gJ-j_.js.map → GrImageViewer-DK-iVHHC.js.map} +1 -1
  42. package/dist/components/GrImageViewer/index.js +1 -1
  43. package/dist/components/GrInputTag/chunks/{GrInputTag-BKzm1PKw.js.map → GrInputTag-Ci8AO1ze.js.map} +1 -1
  44. package/dist/components/GrInputTag/index.js +1 -1
  45. package/dist/components/GrJsonViewer/chunks/{GrJsonViewer-BLCEWlwg.js → GrJsonViewer-CwCBMh4h.js} +2 -2
  46. package/dist/components/GrJsonViewer/chunks/{GrJsonViewer-BLCEWlwg.js.map → GrJsonViewer-CwCBMh4h.js.map} +1 -1
  47. package/dist/components/GrJsonViewer/index.js +1 -1
  48. package/dist/components/GrNumberInput/chunks/{GrNumberInput-Bm-V59TG.js.map → GrNumberInput-CL5QhOmx.js.map} +1 -1
  49. package/dist/components/GrNumberInput/index.js +1 -1
  50. package/dist/components/GrProgressBar/index.js +1 -1
  51. package/dist/components/GrSlider/chunks/{GrSlider-Db-jACXz.js.map → GrSlider-CO8N9SF6.js.map} +1 -1
  52. package/dist/components/GrSlider/index.js +1 -1
  53. package/dist/components/GrSortableList/chunks/{GrSortableList-ce1psLL0.js → GrSortableList-NKriuGm_.js} +2 -1
  54. package/dist/components/GrSortableList/chunks/{GrSortableList-ce1psLL0.js.map → GrSortableList-NKriuGm_.js.map} +1 -1
  55. package/dist/components/GrSortableList/index.js +1 -1
  56. package/dist/components/GrTabs/chunks/{GrTabs-BArefUFv.js → GrTabs-s9RzC1WX.js} +2 -31
  57. package/dist/components/GrTabs/chunks/GrTabs-s9RzC1WX.js.map +1 -0
  58. package/dist/components/GrTabs/index.js +1 -1
  59. package/dist/components/GrTransfer/chunks/GrTransfer-8FA78ny6.js +1368 -0
  60. package/dist/components/GrTransfer/chunks/GrTransfer-8FA78ny6.js.map +1 -0
  61. package/dist/components/GrTransfer/index.js +3 -0
  62. package/dist/components/GrTransfer/styles.css +1 -0
  63. package/dist/components/GrTree/chunks/{GrTree-BFWFc0Bm.js → GrTree-CkHzgYg1.js} +3 -3
  64. package/dist/components/GrTree/chunks/{GrTree-BFWFc0Bm.js.map → GrTree-CkHzgYg1.js.map} +1 -1
  65. package/dist/components/GrTree/index.js +1 -1
  66. package/dist/components/GrTreeSelect/chunks/{GrTreeSelect-DJa2lYGe.js → GrTreeSelect-DUsv3_mF.js} +1 -1
  67. package/dist/components/GrTreeSelect/chunks/{GrTreeSelect-DJa2lYGe.js.map → GrTreeSelect-DUsv3_mF.js.map} +1 -1
  68. package/dist/components/GrTreeSelect/index.js +1 -1
  69. package/dist/composables/useDragSort.js +179 -1
  70. package/dist/composables/useDragSort.js.map +1 -0
  71. package/dist/granular-provider-node.js +1 -1
  72. package/dist/granular-provider.js +1 -1
  73. package/dist/i18n/all.js +1 -1
  74. package/dist/i18n/index.js +1 -1
  75. package/dist/index.js +23 -19
  76. package/dist/theme.js +1 -1
  77. package/dist/tokens.js +1 -1
  78. package/dist/types/components/GrAffix/GrAffix.vue.d.ts +91 -0
  79. package/dist/types/components/GrAffix/affixState.d.ts +81 -0
  80. package/dist/types/components/GrAffix/config.d.ts +1 -0
  81. package/dist/types/components/GrAffix/grAffixStyles.d.ts +46 -0
  82. package/dist/types/components/GrAffix/index.d.ts +9 -0
  83. package/dist/types/components/GrAffix/safelist.d.ts +1 -0
  84. package/dist/types/components/GrAlert/GrAlert.vue.d.ts +1 -1
  85. package/dist/types/components/GrBadgeWrap/GrBadgeWrap.vue.d.ts +2 -2
  86. package/dist/types/components/GrButton/GrButton.vue.d.ts +2 -2
  87. package/dist/types/components/GrCard/GrCard.vue.d.ts +1 -1
  88. package/dist/types/components/GrCarousel/GrCarousel.vue.d.ts +151 -0
  89. package/dist/types/components/GrCarousel/GrCarouselSlide.vue.d.ts +53 -0
  90. package/dist/types/components/GrCarousel/carouselNavigation.d.ts +57 -0
  91. package/dist/types/components/GrCarousel/composables/useCarouselAutoplay.d.ts +33 -0
  92. package/dist/types/components/GrCarousel/composables/useCarouselSwipe.d.ts +20 -0
  93. package/dist/types/components/GrCarousel/config.d.ts +7 -0
  94. package/dist/types/components/GrCarousel/defaults.d.ts +13 -0
  95. package/dist/types/components/GrCarousel/grCarouselContext.d.ts +46 -0
  96. package/dist/types/components/GrCarousel/grCarouselStyles.d.ts +80 -0
  97. package/dist/types/components/GrCarousel/index.d.ts +12 -0
  98. package/dist/types/components/GrCarousel/safelist.d.ts +1 -0
  99. package/dist/types/components/GrCheckbox/GrCheckbox.vue.d.ts +3 -3
  100. package/dist/types/components/GrCheckboxGroup/GrCheckboxGroup.vue.d.ts +2 -2
  101. package/dist/types/components/GrChip/GrChip.vue.d.ts +2 -2
  102. package/dist/types/components/GrChipGroup/GrChipGroup.vue.d.ts +2 -2
  103. package/dist/types/components/GrCollapse/GrCollapse.vue.d.ts +1 -1
  104. package/dist/types/components/GrCollapse/GrCollapseItem.vue.d.ts +1 -1
  105. package/dist/types/components/GrColorPicker/GrColorPicker.vue.d.ts +3 -3
  106. package/dist/types/components/GrCommandPalette/GrCommandPalette.vue.d.ts +1 -1
  107. package/dist/types/components/GrConfirmDialog/GrConfirmDialog.vue.d.ts +1 -1
  108. package/dist/types/components/GrContextMenu/GrContextMenu.vue.d.ts +2 -2
  109. package/dist/types/components/GrDialog/GrDialog.vue.d.ts +1 -1
  110. package/dist/types/components/GrDivider/GrDivider.vue.d.ts +2 -2
  111. package/dist/types/components/GrDrawer/GrDrawer.vue.d.ts +1 -1
  112. package/dist/types/components/GrDropdown/GrDropdown.vue.d.ts +3 -3
  113. package/dist/types/components/GrDropdownMenu/GrDropdownMenu.vue.d.ts +3 -3
  114. package/dist/types/components/GrDropdownMenu/GrDropdownMenuItem.vue.d.ts +1 -1
  115. package/dist/types/components/GrEmptyState/GrEmptyState.vue.d.ts +1 -1
  116. package/dist/types/components/GrFileUpload/GrFileUpload.vue.d.ts +1 -1
  117. package/dist/types/components/GrFormField/GrFormField.vue.d.ts +3 -3
  118. package/dist/types/components/GrFormFile/GrFormFile.vue.d.ts +2 -2
  119. package/dist/types/components/GrFormSection/GrFormSection.vue.d.ts +1 -1
  120. package/dist/types/components/GrIcon/GrIcon.vue.d.ts +1 -1
  121. package/dist/types/components/GrImageViewer/GrImageViewer.vue.d.ts +3 -3
  122. package/dist/types/components/GrInput/GrInput.vue.d.ts +4 -4
  123. package/dist/types/components/GrInputTag/GrInputTag.vue.d.ts +3 -3
  124. package/dist/types/components/GrKbd/GrKbd.vue.d.ts +1 -1
  125. package/dist/types/components/GrLink/GrLink.vue.d.ts +1 -1
  126. package/dist/types/components/GrList/GrListItem.vue.d.ts +2 -2
  127. package/dist/types/components/GrNavbar/GrNavbar.vue.d.ts +2 -2
  128. package/dist/types/components/GrNumberInput/GrNumberInput.vue.d.ts +3 -3
  129. package/dist/types/components/GrPagination/GrPagination.vue.d.ts +1 -1
  130. package/dist/types/components/GrPopover/GrPopover.vue.d.ts +3 -3
  131. package/dist/types/components/GrPromptDialog/GrPromptDialog.vue.d.ts +3 -3
  132. package/dist/types/components/GrRadio/GrRadio.vue.d.ts +2 -2
  133. package/dist/types/components/GrRadioGroup/GrRadioGroup.vue.d.ts +2 -2
  134. package/dist/types/components/GrRating/GrRating.vue.d.ts +2 -2
  135. package/dist/types/components/GrSegmented/GrSegmented.vue.d.ts +2 -2
  136. package/dist/types/components/GrSidebar/GrSidebarItem.vue.d.ts +1 -1
  137. package/dist/types/components/GrSkeleton/GrSkeleton.vue.d.ts +1 -1
  138. package/dist/types/components/GrSlider/GrSlider.vue.d.ts +3 -3
  139. package/dist/types/components/GrSplitter/GrSplitter.vue.d.ts +1 -1
  140. package/dist/types/components/GrStatistic/GrStatistic.vue.d.ts +2 -2
  141. package/dist/types/components/GrSwitch/GrSwitch.vue.d.ts +3 -3
  142. package/dist/types/components/GrTable/GrTable.vue.d.ts +1 -1
  143. package/dist/types/components/GrTabs/GrTabs.vue.d.ts +1 -1
  144. package/dist/types/components/GrTextarea/GrTextarea.vue.d.ts +2 -2
  145. package/dist/types/components/GrTimeline/GrTimelineItem.vue.d.ts +2 -2
  146. package/dist/types/components/GrToaster/GrToaster.vue.d.ts +1 -1
  147. package/dist/types/components/GrTooltip/GrTooltip.vue.d.ts +3 -3
  148. package/dist/types/components/GrTransfer/GrTransfer.vue.d.ts +103 -0
  149. package/dist/types/components/GrTransfer/config.d.ts +1 -0
  150. package/dist/types/components/GrTransfer/defaults.d.ts +15 -0
  151. package/dist/types/components/GrTransfer/grTransferStyles.d.ts +127 -0
  152. package/dist/types/components/GrTransfer/index.d.ts +11 -0
  153. package/dist/types/components/GrTransfer/safelist.d.ts +1 -0
  154. package/dist/types/components/GrTransfer/transferGeometry.d.ts +44 -0
  155. package/dist/types/components/GrTransfer/transferModel.d.ts +68 -0
  156. package/dist/types/components/GrTransfer/transferSelection.d.ts +36 -0
  157. package/dist/types/components/GrTransfer/useTransferDrag.d.ts +48 -0
  158. package/dist/types/granular-provider/shared.d.ts +3 -0
  159. package/dist/types/i18n/messages/en.d.ts +58 -0
  160. package/dist/types/i18n/messages/es.d.ts +58 -0
  161. package/dist/types/i18n/messages/ru.d.ts +58 -0
  162. package/dist/types/index.d.ts +3 -0
  163. package/dist/web-types.json +718 -288
  164. package/package.json +17 -1
  165. package/dist/chunks/config-DWL81-aM.js.map +0 -1
  166. package/dist/chunks/en-B9E3jO6m.js.map +0 -1
  167. package/dist/chunks/es-QpzdIEut.js.map +0 -1
  168. package/dist/chunks/generated-B95IBXs6.js.map +0 -1
  169. package/dist/chunks/granular-provider-BG_2XqkS.js.map +0 -1
  170. package/dist/chunks/ru-D_1Ya489.js.map +0 -1
  171. package/dist/chunks/useDragSort-0l47h4yb.js +0 -265
  172. package/dist/chunks/useDragSort-0l47h4yb.js.map +0 -1
  173. package/dist/components/GrTabs/chunks/GrTabs-BArefUFv.js.map +0 -1
  174. package/dist/components/GrImageViewer/chunks/{GrImageViewer-Dd2gJ-j_.js → GrImageViewer-DK-iVHHC.js} +3 -3
  175. package/dist/components/GrInputTag/chunks/{GrInputTag-BKzm1PKw.js → GrInputTag-Ci8AO1ze.js} +1 -1
  176. package/dist/components/GrNumberInput/chunks/{GrNumberInput-Bm-V59TG.js → GrNumberInput-CL5QhOmx.js} +1 -1
  177. package/dist/components/GrSlider/chunks/{GrSlider-Db-jACXz.js → GrSlider-CO8N9SF6.js} +1 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"ru-D_1Ya489.js","names":[],"sources":["../../src/i18n/locales/ru.json"],"sourcesContent":["{\n \"chip\": {\n \"remove\": \"Убрать\",\n \"removeNamed\": \"Убрать {label}\"\n },\n \"common\": {\n \"confirm\": \"Подтвердить\",\n \"cancel\": \"Отмена\",\n \"add\": \"Добавить\",\n \"clear\": \"Очистить\",\n \"close\": \"Закрыть\"\n },\n \"loading\": {\n \"defaultText\": \"Загрузка...\"\n },\n \"steps\": {\n \"label\": \"Ход выполнения\",\n \"status\": \"Шаг {step} из {count}\",\n \"completed\": \"выполнен\",\n \"error\": \"есть ошибки\"\n },\n \"treeSelect\": {\n \"filterPlaceholder\": \"Поиск…\",\n \"empty\": \"Нет данных\",\n \"loading\": \"Загрузка…\"\n },\n \"badgeWrap\": {\n \"count\": \"{count} новых\"\n },\n \"avatar\": {\n \"group\": \"Участники\",\n \"groupOverflow\": \"и ещё {count}\",\n \"status\": {\n \"online\": \"в сети\",\n \"offline\": \"не в сети\",\n \"busy\": \"занят\",\n \"away\": \"отошёл\"\n }\n },\n \"tree\": {\n \"drag\": \"Перетащить\",\n \"expand\": \"Развернуть\",\n \"collapse\": \"Свернуть\",\n \"moved\": \"{label} перемещён\"\n },\n \"select\": {\n \"customValuePlaceholder\": \"Добавить значение…\",\n \"addOption\": \"Добавить «{value}»\",\n \"clearOption\": \"Не выбрано\",\n \"searchPlaceholder\": \"Поиск…\",\n \"loading\": \"Загрузка…\",\n \"noResults\": \"Ничего не найдено\",\n \"removeTag\": \"Удалить {label}\"\n },\n \"dataTable\": {\n \"loading\": \"Загрузка…\",\n \"empty\": \"Нет данных\",\n \"selectAll\": \"Выбрать все строки\",\n \"selectRow\": \"Выбрать строку\",\n \"sortAsc\": \"Нажмите для сортировки по возрастанию\",\n \"sortDesc\": \"Нажмите для сортировки по убыванию\",\n \"sortNone\": \"Нажмите, чтобы снять сортировку\",\n \"moveColumn\": \"Переставить колонку {label}\",\n \"columnMoved\": \"Колонка {label} перемещена на позицию {position} из {count}\",\n \"resizeColumn\": \"Ширина колонки {label}\",\n \"columnResized\": \"Ширина колонки {label} — {width} пикселей\"\n },\n \"breadcrumbs\": {\n \"label\": \"Навигационная цепочка\",\n \"expand\": \"Показать скрытые уровни\"\n },\n \"autocomplete\": {\n \"loading\": \"Загрузка…\",\n \"noResults\": \"Ничего не найдено\",\n \"addOption\": \"Добавить «{value}»\",\n \"removeValue\": \"Убрать {label}\",\n \"typeMore\": {\n \"one\": \"Введите минимум {n} символ\",\n \"few\": \"Введите минимум {n} символа\",\n \"many\": \"Введите минимум {n} символов\",\n \"other\": \"Введите минимум {n} символа\"\n }\n },\n \"form\": {\n \"required\": \"Обязательное поле\",\n \"min\": \"Минимум {min}\",\n \"max\": \"Максимум {max}\",\n \"len\": \"Длина должна быть {len}\",\n \"pattern\": \"Неверный формат\",\n \"email\": \"Введите корректный e-mail\",\n \"url\": \"Введите корректный URL\",\n \"file\": \"Некорректный файл\",\n \"invalid\": \"Некорректное значение\",\n \"readonly\": \"Только для чтения\",\n \"validating\": \"Проверяем…\"\n },\n \"dialog\": {\n \"confirm\": {\n \"title\": \"Подтверждение\"\n },\n \"prompt\": {\n \"title\": \"Введите значение\",\n \"label\": \"Значение\",\n \"required\": \"Введите значение.\"\n },\n \"ok\": \"ОК\"\n },\n \"switch\": {\n \"loading\": \"Сохраняем…\"\n },\n \"slider\": {\n \"min\": \"минимум\",\n \"max\": \"максимум\"\n },\n \"sidebar\": {\n \"expand\": \"Развернуть панель\",\n \"collapse\": \"Свернуть панель\"\n },\n \"splitter\": {\n \"label\": \"Изменить размер панелей\"\n },\n \"statistic\": {\n \"trendUp\": \"Рост\",\n \"trendDown\": \"Падение\",\n \"trendFlat\": \"Без изменений\"\n },\n \"pagination\": {\n \"label\": \"Пагинация\",\n \"pageSize\": \"Размер страницы\",\n \"prev\": \"Назад\",\n \"next\": \"Вперёд\",\n \"page\": \"Страница {n}\",\n \"first\": \"Первая страница\",\n \"last\": \"Последняя страница\",\n \"jumpTo\": \"Перейти к\",\n \"total\": \"{from}–{to} из {total}\",\n \"status\": \"Страница {page} из {count}\"\n },\n \"navbar\": {\n \"openMenu\": \"Открыть меню\"\n },\n \"responseError\": {\n \"networkTitle\": \"Нет связи с сервером\",\n \"networkMessage\": \"Проверьте подключение к интернету и попробуйте ещё раз.\",\n \"abortedTitle\": \"Запрос отменён\",\n \"abortedMessage\": \"Запрос был отменён.\",\n \"validationTitle\": \"Проверьте данные\",\n \"validationMessage\": \"Сервер вернул ошибку валидации. Исправьте поля и попробуйте снова.\",\n \"clientTitle\": \"Ошибка запроса\",\n \"clientMessage\": \"Сервер отклонил запрос.\",\n \"serverTitle\": \"Ошибка сервера\",\n \"serverMessage\": \"Произошла ошибка на сервере. Попробуйте ещё раз.\",\n \"unknownTitle\": \"Что-то пошло не так\",\n \"unknownMessage\": \"Не удалось выполнить операцию. Попробуйте ещё раз.\",\n \"statusLabel\": \"HTTP {status}\",\n \"retryLabel\": \"Повторить\",\n \"dismissLabel\": \"Скрыть\"\n },\n \"colors\": {\n \"neutral\": \"Нейтральный\",\n \"success\": \"Успех\",\n \"warning\": \"Предупреждение\",\n \"danger\": \"Опасность\",\n \"info\": \"Инфо\",\n \"primary\": \"Primary\",\n \"slate\": \"Сланцевый\",\n \"azure\": \"Лазурный\"\n },\n \"drawer\": {\n \"title\": \"Панель\"\n },\n \"modal\": {\n \"title\": \"Диалог\"\n },\n \"numberInput\": {\n \"increase\": \"Увеличить\",\n \"decrease\": \"Уменьшить\"\n },\n \"toaster\": {\n \"dismiss\": \"Закрыть\",\n \"region\": \"Уведомления\"\n },\n \"inputTag\": {\n \"removeTag\": \"Удалить тег\",\n \"removeTagNamed\": \"Удалить тег {tag}\",\n \"clearAll\": \"Удалить все теги\",\n \"added\": \"Добавлен тег: {tag}\",\n \"addedMany\": \"Добавлено тегов: {count}\",\n \"removed\": \"Удалён тег: {tag}\",\n \"cleared\": \"Все теги удалены\",\n \"limitReached\": \"Достигнут предел тегов\"\n },\n \"fileUpload\": {\n \"placeholder\": \"Перетащите файлы сюда или нажмите для выбора\",\n \"progress\": \"Прогресс загрузки\",\n \"uploading\": \"Загрузка…\",\n \"success\": \"Загрузка завершена\",\n \"error\": \"Не удалось загрузить\",\n \"remove\": \"Убрать {fileName}\",\n \"retry\": \"Повторить загрузку\",\n \"retryFile\": \"Повторить {fileName}\",\n \"abortFile\": \"Отменить загрузку {fileName}\",\n \"statusPending\": \"В очереди\",\n \"statusUploading\": \"Загружается\",\n \"statusSuccess\": \"Загружен\",\n \"statusError\": \"Ошибка\"\n },\n \"formFile\": {\n \"upload\": \"Загрузить файл\",\n \"change\": \"Заменить файл\",\n \"remove\": \"Удалить\",\n \"clearAll\": \"Очистить всё\",\n \"placeholder\": \"Файлы не выбраны\",\n \"validateError\": \"Не удалось проверить перетащенные файлы\",\n \"removeFile\": \"Убрать {fileName}\"\n },\n \"imageViewer\": {\n \"close\": \"Закрыть просмотрщик\",\n \"prev\": \"Предыдущее изображение\",\n \"next\": \"Следующее изображение\",\n \"zoomIn\": \"Увеличить\",\n \"zoomOut\": \"Уменьшить\",\n \"resetZoom\": \"Сбросить масштаб\",\n \"zoomToNatural\": \"Натуральный размер (1:1)\",\n \"rotateLeft\": \"Повернуть влево\",\n \"rotateRight\": \"Повернуть вправо\",\n \"download\": \"Скачать изображение\",\n \"empty\": \"Нет изображения\",\n \"label\": \"Просмотр изображений\",\n \"position\": \"Изображение {index} из {total}\"\n },\n \"formErrorBanner\": {\n \"validationTitle\": \"Проверьте поля формы\",\n \"validationMessage\": \"Сервер отклонил данные. Исправьте выделенные поля и попробуйте снова.\",\n \"clientTitle\": \"Не удалось сохранить\",\n \"serverTitle\": \"Ошибка сохранения\",\n \"serverMessage\": \"Не удалось сохранить данные. Попробуйте позже.\"\n },\n \"uploadErrorBanner\": {\n \"networkTitle\": \"Не удалось загрузить файлы\",\n \"networkMessage\": \"Проверьте подключение и попробуйте снова.\",\n \"validationTitle\": \"Файлы не прошли проверку\",\n \"validationMessage\": \"Некоторые файлы не соответствуют требованиям.\",\n \"serverTitle\": \"Ошибка загрузки на сервере\",\n \"serverMessage\": \"Сервер не смог обработать загрузку. Попробуйте снова.\"\n },\n \"input\": {\n \"clear\": \"Очистить\",\n \"showPassword\": \"Показать пароль\",\n \"hidePassword\": \"Скрыть пароль\",\n \"limitReached\": \"Достигнут предел символов\"\n },\n \"textarea\": {\n \"lines\": {\n \"one\": \"{n} строка\",\n \"few\": \"{n} строки\",\n \"many\": \"{n} строк\",\n \"other\": \"{n} строки\"\n }\n },\n \"rating\": {\n \"label\": \"Оценка\",\n \"valueText\": \"{value} из {max}\",\n \"valueTextWithLabel\": \"{value} из {max}, {label}\"\n },\n \"commandPalette\": {\n \"label\": \"Палитра команд\",\n \"placeholder\": \"Введите команду или запрос…\",\n \"empty\": \"Ничего не найдено\",\n \"recent\": \"Недавние\",\n \"loading\": \"Загрузка…\"\n },\n \"delta\": {\n \"increase\": \"рост\"\n },\n \"filePreview\": {\n \"label\": \"Файл\"\n },\n \"fileValidation\": {\n \"accept\": \"Файл «{fileName}» не подходит под accept=\\\"{accept}\\\"\",\n \"extension\": \"У файла «{fileName}» недопустимое расширение «.{extension}»\",\n \"mimeType\": \"У файла «{fileName}» недопустимый тип «{mimeType}»\",\n \"mimeTypeFallback\": \"У файла «{fileName}» неопределённый тип «{mimeType}»\",\n \"maxFileSize\": \"Файл «{fileName}» слишком большой ({size} байт), максимум {maxBytes}\",\n \"maxCount\": \"Выбрано слишком много файлов ({count}), максимум — {maxCount}\",\n \"maxTotalSize\": \"Суммарный размер файлов слишком большой ({total} байт), максимум {maxBytes}\"\n },\n \"kbd\": {\n \"command\": \"Command\",\n \"option\": \"Option\",\n \"shift\": \"Shift\",\n \"control\": \"Control\",\n \"enter\": \"Enter\",\n \"escape\": \"Escape\",\n \"tab\": \"Tab\",\n \"backspace\": \"Backspace\",\n \"delete\": \"Delete\",\n \"pageUp\": \"Page Up\",\n \"pageDown\": \"Page Down\",\n \"arrowUp\": \"стрелка вверх\",\n \"arrowDown\": \"стрелка вниз\",\n \"arrowLeft\": \"стрелка влево\",\n \"arrowRight\": \"стрелка вправо\",\n \"then\": \"затем\"\n },\n \"link\": {\n \"opensInNewTab\": \"откроется в новой вкладке\"\n },\n \"colorPicker\": {\n \"panelLabel\": \"Выбор цвета\",\n \"hue\": \"Оттенок\",\n \"saturation\": \"Насыщенность\",\n \"lightness\": \"Светлота\",\n \"opacity\": \"Непрозрачность\",\n \"hexLabel\": \"Значение hex\",\n \"presetsLabel\": \"Палитра\",\n \"swatchLabel\": \"Цвет {color}\"\n },\n \"collapse\": {\n \"empty\": \"Пока пусто\"\n },\n \"list\": {\n \"empty\": \"Пока пусто\"\n },\n \"button\": {\n \"loading\": \"Загрузка…\"\n },\n \"table\": {\n \"empty\": \"Пока пусто\"\n },\n \"timeline\": {\n \"empty\": \"Пока пусто\"\n },\n \"tabs\": {\n \"empty\": \"Пока пусто\",\n \"close\": \"Закрыть вкладку\"\n },\n \"bottomNav\": {\n \"label\": \"Нижняя навигация\",\n \"badge\": \"{count} новых\"\n },\n \"emptyState\": {\n \"title\": \"Пока пусто\"\n },\n \"sortable\": {\n \"label\": \"Сортируемый список\",\n \"handle\": \"Переставить\",\n \"empty\": \"Пока нечего сортировать\",\n \"grabbed\": \"Взято. Позиция {position} из {count}\",\n \"moved\": \"Позиция {position} из {count}\",\n \"dropped\": \"Отпущено на позиции {position} из {count}\",\n \"cancelled\": \"Перенос отменён\"\n },\n \"jsonViewer\": {\n \"search\": \"Поиск по ключу или значению\",\n \"expandAll\": \"Раскрыть всё\",\n \"collapseAll\": \"Свернуть всё\",\n \"copy\": \"Скопировать значение\",\n \"copied\": \"Значение скопировано\",\n \"copyFailed\": \"Не удалось скопировать значение\",\n \"moreItems\": \"ещё\"\n },\n \"contextMenu\": {\n \"label\": \"Контекстное меню\"\n }\n}\n"],"mappings":""}
@@ -1,265 +0,0 @@
1
- import { useDragGesture } from "../composables/useDragGesture.js";
2
- import { computed, onScopeDispose, shallowRef } from "vue";
3
- //#region src/composables/internal/dragSortGeometry.ts
4
- /**
5
- * Какой элемент под указателем и в каком месте его.
6
- *
7
- * Промах не отбрасывается: между строками есть зазоры, а курсор уезжает за
8
- * край списка — и в обоих случаях перенос обязан продолжать показывать
9
- * ближайшую цель, иначе индикатор мигает на каждом пикселе между строками.
10
- *
11
- * Вырожденный отрезок (нулевая высота строки) даёт `fraction: 0` — цель
12
- * остаётся детерминированной, а не зависящей от направления подхода.
13
- */
14
- function hitTest(spans, position) {
15
- if (spans.length === 0) return null;
16
- let nearestIndex = 0;
17
- let nearestDistance = Number.POSITIVE_INFINITY;
18
- for (let index = 0; index < spans.length; index += 1) {
19
- const span = spans[index];
20
- if (position >= span.start && position <= span.end) return {
21
- index,
22
- fraction: fractionIn(span, position)
23
- };
24
- const distance = position < span.start ? span.start - position : position - span.end;
25
- if (distance < nearestDistance) {
26
- nearestDistance = distance;
27
- nearestIndex = index;
28
- }
29
- }
30
- const nearest = spans[nearestIndex];
31
- return {
32
- index: nearestIndex,
33
- fraction: fractionIn(nearest, position)
34
- };
35
- }
36
- function fractionIn(span, position) {
37
- const size = span.end - span.start;
38
- if (size <= 0) return 0;
39
- return clamp((position - span.start) / size, 0, 1);
40
- }
41
- /**
42
- * Куда встанет элемент, если отпустить сейчас.
43
- *
44
- * Верхняя половина строки — «до неё», нижняя — «после». Индекс возвращается
45
- * **в терминах итогового массива**, то есть уже с поправкой на то, что
46
- * перетаскиваемый элемент из него временно вынут: без поправки перенос вниз
47
- * на одну позицию не двигал бы ничего.
48
- */
49
- function insertionIndex(hit, from, count) {
50
- if (count <= 0) return 0;
51
- const base = hit.fraction < .5 ? hit.index : hit.index + 1;
52
- return clamp(from < base ? base - 1 : base, 0, count - 1);
53
- }
54
- /**
55
- * На сколько прокрутить контейнер за кадр: отрицательное — к началу.
56
- *
57
- * Скорость растёт линейно по мере приближения к краю, а не включается
58
- * ступенькой: ступенька на длинном списке ощущается как рывок и промахивается
59
- * мимо цели.
60
- */
61
- function autoScrollDelta(viewport, position, options = {}) {
62
- const zone = options.zone ?? 32;
63
- const speed = options.speed ?? 12;
64
- if (zone <= 0 || speed <= 0) return 0;
65
- if (viewport.end - viewport.start <= zone * 2) return 0;
66
- if (position < viewport.start || position > viewport.end) return 0;
67
- const fromStart = position - viewport.start;
68
- if (fromStart < zone) return -Math.ceil((zone - fromStart) / zone * speed);
69
- const fromEnd = viewport.end - position;
70
- if (fromEnd < zone) return Math.ceil((zone - fromEnd) / zone * speed);
71
- return 0;
72
- }
73
- /**
74
- * Перестановка без мутации входа: массив потребителя остаётся нетронутым,
75
- * наружу уходит новый. `v-model` с мутацией исходного набора ломает и
76
- * сравнение «было — стало», и историю в сторе потребителя.
77
- */
78
- function moveItem(items, from, to) {
79
- const next = items.slice();
80
- if (from === to || from < 0 || from >= items.length) return next;
81
- const [moved] = next.splice(from, 1);
82
- next.splice(clamp(to, 0, next.length), 0, moved);
83
- return next;
84
- }
85
- function clamp(value, min, max) {
86
- return Math.min(max, Math.max(min, value));
87
- }
88
- //#endregion
89
- //#region src/composables/useDragSort.ts
90
- function useDragSort(options) {
91
- const source = shallowRef(null);
92
- const target = shallowRef(null);
93
- const mode = shallowRef(null);
94
- const isActive = computed(() => mode.value !== null);
95
- let pendingKey = null;
96
- let originX = 0;
97
- let originY = 0;
98
- let lastPoint = null;
99
- let scrollFrame = null;
100
- const gesture = useDragGesture({
101
- disabled: () => options.disabled?.() ?? false,
102
- onStart: (event) => {
103
- if (pendingKey === null) return false;
104
- if (options.canDrag?.(pendingKey) === false) return false;
105
- originX = event.clientX;
106
- originY = event.clientY;
107
- return true;
108
- },
109
- onMove: (event) => {
110
- if (mode.value === null) {
111
- if (Math.hypot(event.clientX - originX, event.clientY - originY) < (options.threshold?.() ?? 4)) return;
112
- source.value = pendingKey;
113
- mode.value = "pointer";
114
- window.addEventListener("keydown", onWindowKeydown, true);
115
- startScrolling();
116
- notify();
117
- }
118
- event.preventDefault();
119
- lastPoint = {
120
- x: event.clientX,
121
- y: event.clientY
122
- };
123
- updateTarget(lastPoint);
124
- },
125
- onEnd: () => {
126
- if (mode.value === "pointer") commit();
127
- else reset();
128
- },
129
- onCancel: reset
130
- });
131
- function isVertical() {
132
- return (options.orientation?.() ?? "vertical") === "vertical";
133
- }
134
- function spanOf(rect) {
135
- return isVertical() ? {
136
- start: rect.top,
137
- end: rect.bottom
138
- } : {
139
- start: rect.left,
140
- end: rect.right
141
- };
142
- }
143
- function notify() {
144
- options.onUpdate?.(source.value, target.value, mode.value);
145
- }
146
- function updateTarget(point) {
147
- const current = source.value;
148
- if (current === null) return;
149
- const rendered = [];
150
- options.items().forEach((key, index) => {
151
- const el = options.elementFor(key);
152
- if (el) rendered.push({
153
- key,
154
- index,
155
- span: spanOf(el.getBoundingClientRect())
156
- });
157
- });
158
- const hit = hitTest(rendered.map((entry) => entry.span), isVertical() ? point.y : point.x);
159
- const entry = hit === null ? null : rendered[hit.index];
160
- const next = entry === null || hit === null ? null : options.resolveTarget({
161
- key: entry.key,
162
- index: entry.index,
163
- fraction: hit.fraction
164
- }, current, target.value);
165
- if (next === target.value) return;
166
- target.value = next;
167
- notify();
168
- }
169
- /**
170
- * Автопрокрутка живёт в своём кадре, а не в обработчике движения: у края
171
- * указатель обычно стоит неподвижно, событий больше нет — а список должен
172
- * продолжать ехать.
173
- */
174
- function tickScroll() {
175
- scrollFrame = null;
176
- const scroller = options.scroller?.();
177
- const point = lastPoint;
178
- if (!scroller || !point || mode.value !== "pointer") return;
179
- const delta = autoScrollDelta(spanOf(scroller.getBoundingClientRect()), isVertical() ? point.y : point.x);
180
- if (delta !== 0) {
181
- if (isVertical()) scroller.scrollTop += delta;
182
- else scroller.scrollLeft += delta;
183
- updateTarget(point);
184
- }
185
- scrollFrame = requestAnimationFrame(tickScroll);
186
- }
187
- function startScrolling() {
188
- if (scrollFrame !== null || !options.scroller?.()) return;
189
- scrollFrame = requestAnimationFrame(tickScroll);
190
- }
191
- function stopScrolling() {
192
- if (scrollFrame === null) return;
193
- cancelAnimationFrame(scrollFrame);
194
- scrollFrame = null;
195
- }
196
- function onWindowKeydown(event) {
197
- if (event.key !== "Escape") return;
198
- event.preventDefault();
199
- gesture.stop(false);
200
- }
201
- function reset() {
202
- stopScrolling();
203
- window.removeEventListener("keydown", onWindowKeydown, true);
204
- pendingKey = null;
205
- lastPoint = null;
206
- source.value = null;
207
- target.value = null;
208
- mode.value = null;
209
- notify();
210
- }
211
- function commit() {
212
- const from = source.value;
213
- const to = target.value;
214
- if (from !== null && to !== null) options.onDrop(from, to);
215
- reset();
216
- }
217
- function startFrom(key) {
218
- return (event) => {
219
- if (mode.value === "keyboard") return;
220
- pendingKey = key;
221
- gesture.start(event);
222
- };
223
- }
224
- function grab(key) {
225
- if (options.disabled?.() || options.canDrag?.(key) === false) return;
226
- if (mode.value !== null) return;
227
- source.value = key;
228
- target.value = null;
229
- mode.value = "keyboard";
230
- notify();
231
- }
232
- function setTarget(next) {
233
- if (mode.value !== "keyboard") return;
234
- target.value = next;
235
- notify();
236
- }
237
- function drop() {
238
- if (mode.value !== "keyboard") return;
239
- commit();
240
- }
241
- function cancel() {
242
- if (mode.value === "pointer") {
243
- gesture.stop(false);
244
- return;
245
- }
246
- if (mode.value === "keyboard") reset();
247
- }
248
- onScopeDispose(() => {
249
- stopScrolling();
250
- window.removeEventListener("keydown", onWindowKeydown, true);
251
- });
252
- return {
253
- source,
254
- target,
255
- mode,
256
- isActive,
257
- startFrom,
258
- grab,
259
- setTarget,
260
- drop,
261
- cancel
262
- };
263
- }
264
- //#endregion
265
- export { insertionIndex as n, moveItem as r, useDragSort as t };
@@ -1 +0,0 @@
1
- {"version":3,"file":"useDragSort-0l47h4yb.js","names":[],"sources":["../../src/composables/internal/dragSortGeometry.ts","../../src/composables/useDragSort.ts"],"sourcesContent":["/**\n * Геометрия перестановки: чистая арифметика без DOM и без реактивности.\n *\n * Вынесена отдельно, потому что ошибка здесь проявляется не исключением, а\n * «элемент встал не туда» — поймать это можно только тестом на числах.\n * Композабл `useDragSort` сводит `DOMRect` к отрезкам вдоль оси и зовёт эти\n * функции; какой смысл у попадания, решает уже компонент.\n */\n\n/** Отрезок, занимаемый элементом вдоль оси переноса. */\nexport interface DragSortSpan {\n start: number\n end: number\n}\n\nexport interface DragSortHitResult {\n /** Индекс элемента в переданном наборе. */\n index: number\n /** Доля вдоль элемента: 0 — его начало, 1 — конец. */\n fraction: number\n}\n\n/**\n * Какой элемент под указателем и в каком месте его.\n *\n * Промах не отбрасывается: между строками есть зазоры, а курсор уезжает за\n * край списка — и в обоих случаях перенос обязан продолжать показывать\n * ближайшую цель, иначе индикатор мигает на каждом пикселе между строками.\n *\n * Вырожденный отрезок (нулевая высота строки) даёт `fraction: 0` — цель\n * остаётся детерминированной, а не зависящей от направления подхода.\n */\nexport function hitTest(spans: readonly DragSortSpan[], position: number): DragSortHitResult | null {\n if (spans.length === 0)\n return null\n\n let nearestIndex = 0\n let nearestDistance = Number.POSITIVE_INFINITY\n\n for (let index = 0; index < spans.length; index += 1) {\n const span = spans[index]\n\n if (position >= span.start && position <= span.end)\n return { index, fraction: fractionIn(span, position) }\n\n const distance = position < span.start ? span.start - position : position - span.end\n if (distance < nearestDistance) {\n nearestDistance = distance\n nearestIndex = index\n }\n }\n\n const nearest = spans[nearestIndex]\n\n return { index: nearestIndex, fraction: fractionIn(nearest, position) }\n}\n\nfunction fractionIn(span: DragSortSpan, position: number): number {\n const size = span.end - span.start\n if (size <= 0)\n return 0\n\n return clamp((position - span.start) / size, 0, 1)\n}\n\n/**\n * Куда встанет элемент, если отпустить сейчас.\n *\n * Верхняя половина строки — «до неё», нижняя — «после». Индекс возвращается\n * **в терминах итогового массива**, то есть уже с поправкой на то, что\n * перетаскиваемый элемент из него временно вынут: без поправки перенос вниз\n * на одну позицию не двигал бы ничего.\n */\nexport function insertionIndex(hit: DragSortHitResult, from: number, count: number): number {\n if (count <= 0)\n return 0\n\n const base = hit.fraction < 0.5 ? hit.index : hit.index + 1\n const adjusted = from < base ? base - 1 : base\n\n return clamp(adjusted, 0, count - 1)\n}\n\nexport interface AutoScrollOptions {\n /** Ширина чувствительной полосы у каждого края, px. По умолчанию 32. */\n zone?: number\n /** Скорость на самом краю, px за кадр. По умолчанию 12. */\n speed?: number\n}\n\n/**\n * На сколько прокрутить контейнер за кадр: отрицательное — к началу.\n *\n * Скорость растёт линейно по мере приближения к краю, а не включается\n * ступенькой: ступенька на длинном списке ощущается как рывок и промахивается\n * мимо цели.\n */\nexport function autoScrollDelta(viewport: DragSortSpan, position: number, options: AutoScrollOptions = {}): number {\n const zone = options.zone ?? 32\n const speed = options.speed ?? 12\n\n if (zone <= 0 || speed <= 0)\n return 0\n // Вьюпорт короче двух зон: полосы наложились бы и тянули список в обе стороны.\n if (viewport.end - viewport.start <= zone * 2)\n return 0\n\n if (position < viewport.start || position > viewport.end)\n return 0\n\n const fromStart = position - viewport.start\n if (fromStart < zone)\n return -Math.ceil(((zone - fromStart) / zone) * speed)\n\n const fromEnd = viewport.end - position\n if (fromEnd < zone)\n return Math.ceil(((zone - fromEnd) / zone) * speed)\n\n return 0\n}\n\n/**\n * Перестановка без мутации входа: массив потребителя остаётся нетронутым,\n * наружу уходит новый. `v-model` с мутацией исходного набора ломает и\n * сравнение «было — стало», и историю в сторе потребителя.\n */\nexport function moveItem<T>(items: readonly T[], from: number, to: number): T[] {\n const next = items.slice()\n if (from === to || from < 0 || from >= items.length)\n return next\n\n const [moved] = next.splice(from, 1)\n next.splice(clamp(to, 0, next.length), 0, moved)\n\n return next\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, value))\n}\n","import type { Ref } from 'vue'\nimport { computed, onScopeDispose, shallowRef } from 'vue'\n\nimport type { DragSortSpan } from './internal/dragSortGeometry'\nimport { autoScrollDelta, hitTest } from './internal/dragSortGeometry'\nimport { useDragGesture } from './useDragGesture'\n\n/**\n * Модель перестановки: перенос элемента указателем и с клавиатуры.\n *\n * Композабл владеет **механикой** — сессией жеста поверх `useDragGesture`,\n * порогом начала, попаданием в элемент, автопрокруткой у краёв и состоянием\n * переноса. **Смысл** остаётся у компонента: что считать целью (индекс вставки\n * у списка, «до / внутрь / после» у дерева), какими клавишами это делается и\n * что сказать в живой регион.\n *\n * Граница проведена так не из вкуса: у списка цель переноса — число, у дерева —\n * пара «узел и зона», а `Space` в дереве уже занят чекбоксом. Общая у них\n * механика, а не семантика; обобщать семантику значило бы завести опцию на\n * каждое расхождение.\n *\n * Данные композабл не меняет: он сообщает «источник и цель», перестановку\n * делает потребитель (`moveItem` из `internal/dragSortGeometry` — если она\n * обычная).\n *\n * ```ts\n * const sort = useDragSort<string, number>({\n * items: () => keys.value,\n * elementFor: key => rowEls.get(key),\n * scroller: () => scrollerEl.value,\n * resolveTarget: (hit, source) => insertionIndex(hit, indexOf(source), keys.value.length),\n * onDrop: (source, to) => emit('move', indexOf(source), to),\n * })\n * ```\n */\n\nexport type DragSortOrientation = 'vertical' | 'horizontal'\n\n/** Чем ведётся перенос: указателем или с клавиатуры. */\nexport type DragSortMode = 'pointer' | 'keyboard'\n\nexport interface DragSortHit<TKey> {\n key: TKey\n index: number\n /** Доля вдоль элемента: 0 — начало, 1 — конец. Зоны нарезает компонент. */\n fraction: number\n}\n\nexport interface UseDragSortOptions<TKey, TTarget> {\n items: () => readonly TKey[]\n elementFor: (key: TKey) => HTMLElement | null | undefined\n /** По умолчанию `vertical`. */\n orientation?: () => DragSortOrientation\n /** Скроллер для автопрокрутки у краёв. Не задан — автопрокрутки нет. */\n scroller?: () => HTMLElement | null | undefined\n disabled?: () => boolean\n canDrag?: (key: TKey) => boolean\n /** Сдвиг, после которого нажатие становится переносом. По умолчанию 4px. */\n threshold?: () => number\n /**\n * Попадание указателя → цель переноса. `null` — сюда положить нельзя.\n *\n * Третьим аргументом приходит прежняя цель: цели сравниваются по ссылке, и\n * объектной цели (как «узел плюс зона» у дерева) стоит вернуть **ту же**\n * ссылку, пока смысл не изменился, — иначе подсветка и объявления будут\n * дёргаться на каждом движении указателя.\n */\n resolveTarget: (hit: DragSortHit<TKey>, source: TKey, previous: TTarget | null) => TTarget | null\n /** Перенос состоялся. */\n onDrop: (source: TKey, target: TTarget) => void\n /** Состояние изменилось: подсветка строк и объявления — на этом. */\n onUpdate?: (source: TKey | null, target: TTarget | null, mode: DragSortMode | null) => void\n}\n\nexport interface UseDragSortReturn<TKey, TTarget> {\n source: Ref<TKey | null>\n target: Ref<TTarget | null>\n mode: Ref<DragSortMode | null>\n isActive: Ref<boolean>\n /** Вешается на `@pointerdown` ручки или строки. */\n startFrom: (key: TKey) => (event: PointerEvent) => void\n /** Клавиатурный перенос: взять элемент. */\n grab: (key: TKey) => void\n /** Клавиатурный перенос: назначить цель (двигать ведёт компонент). */\n setTarget: (target: TTarget | null) => void\n /** Положить: если цель есть — состоится `onDrop`. */\n drop: () => void\n /** Отменить: ничего не переносится. */\n cancel: () => void\n}\n\nexport function useDragSort<TKey, TTarget>(\n options: UseDragSortOptions<TKey, TTarget>,\n): UseDragSortReturn<TKey, TTarget> {\n const source = shallowRef<TKey | null>(null)\n const target = shallowRef<TTarget | null>(null)\n const mode = shallowRef<DragSortMode | null>(null)\n const isActive = computed(() => mode.value !== null)\n\n // Нажатая, но ещё не потянутая строка: между `pointerdown` и порогом это\n // остаётся обычным кликом, и объявлять перенос рано.\n let pendingKey: TKey | null = null\n let originX = 0\n let originY = 0\n let lastPoint: { x: number, y: number } | null = null\n let scrollFrame: number | null = null\n\n const gesture = useDragGesture({\n disabled: () => options.disabled?.() ?? false,\n onStart: (event) => {\n if (pendingKey === null)\n return false\n if (options.canDrag?.(pendingKey) === false)\n return false\n\n originX = event.clientX\n originY = event.clientY\n\n return true\n },\n onMove: (event) => {\n if (mode.value === null) {\n const moved = Math.hypot(event.clientX - originX, event.clientY - originY)\n if (moved < (options.threshold?.() ?? 4))\n return\n\n source.value = pendingKey\n mode.value = 'pointer'\n // Esc отменяет перенос, но `pointercancel` браузер при этом не шлёт —\n // клавишу приходится слушать самим, в capture, до слоёв приложения.\n window.addEventListener('keydown', onWindowKeydown, true)\n startScrolling()\n notify()\n }\n\n // Против выделения текста под курсором во время переноса.\n event.preventDefault()\n\n lastPoint = { x: event.clientX, y: event.clientY }\n updateTarget(lastPoint)\n },\n onEnd: () => {\n if (mode.value === 'pointer')\n commit()\n else reset()\n },\n onCancel: reset,\n })\n\n function isVertical(): boolean {\n return (options.orientation?.() ?? 'vertical') === 'vertical'\n }\n\n function spanOf(rect: DOMRect): DragSortSpan {\n return isVertical() ? { start: rect.top, end: rect.bottom } : { start: rect.left, end: rect.right }\n }\n\n function notify(): void {\n options.onUpdate?.(source.value, target.value, mode.value)\n }\n\n function updateTarget(point: { x: number, y: number }): void {\n const current = source.value\n if (current === null)\n return\n\n // Отрисованы не все: список может быть под окном отсечения, ветка дерева —\n // свёрнута. Попадание считается по тому, что на экране есть.\n const rendered: { key: TKey, index: number, span: DragSortSpan }[] = []\n\n options.items().forEach((key, index) => {\n const el = options.elementFor(key)\n if (el)\n rendered.push({ key, index, span: spanOf(el.getBoundingClientRect()) })\n })\n\n const hit = hitTest(rendered.map(entry => entry.span), isVertical() ? point.y : point.x)\n const entry = hit === null ? null : rendered[hit.index]\n const next = entry === null || hit === null\n ? null\n : options.resolveTarget({ key: entry.key, index: entry.index, fraction: hit.fraction }, current, target.value)\n\n if (next === target.value)\n return\n\n target.value = next\n notify()\n }\n\n /**\n * Автопрокрутка живёт в своём кадре, а не в обработчике движения: у края\n * указатель обычно стоит неподвижно, событий больше нет — а список должен\n * продолжать ехать.\n */\n function tickScroll(): void {\n scrollFrame = null\n\n const scroller = options.scroller?.()\n const point = lastPoint\n if (!scroller || !point || mode.value !== 'pointer')\n return\n\n const delta = autoScrollDelta(spanOf(scroller.getBoundingClientRect()), isVertical() ? point.y : point.x)\n\n if (delta !== 0) {\n if (isVertical())\n scroller.scrollTop += delta\n else scroller.scrollLeft += delta\n\n updateTarget(point)\n }\n\n scrollFrame = requestAnimationFrame(tickScroll)\n }\n\n function startScrolling(): void {\n if (scrollFrame !== null || !options.scroller?.())\n return\n\n scrollFrame = requestAnimationFrame(tickScroll)\n }\n\n function stopScrolling(): void {\n if (scrollFrame === null)\n return\n\n cancelAnimationFrame(scrollFrame)\n scrollFrame = null\n }\n\n function onWindowKeydown(event: KeyboardEvent): void {\n if (event.key !== 'Escape')\n return\n\n event.preventDefault()\n gesture.stop(false)\n }\n\n function reset(): void {\n stopScrolling()\n window.removeEventListener('keydown', onWindowKeydown, true)\n\n pendingKey = null\n lastPoint = null\n source.value = null\n target.value = null\n mode.value = null\n notify()\n }\n\n function commit(): void {\n const from = source.value\n const to = target.value\n\n if (from !== null && to !== null)\n options.onDrop(from, to)\n\n reset()\n }\n\n function startFrom(key: TKey) {\n return (event: PointerEvent): void => {\n // Клавиатурный перенос идёт своим чередом: указатель его не перебивает.\n if (mode.value === 'keyboard')\n return\n\n pendingKey = key\n gesture.start(event)\n }\n }\n\n function grab(key: TKey): void {\n if (options.disabled?.() || options.canDrag?.(key) === false)\n return\n if (mode.value !== null)\n return\n\n source.value = key\n target.value = null\n mode.value = 'keyboard'\n notify()\n }\n\n function setTarget(next: TTarget | null): void {\n if (mode.value !== 'keyboard')\n return\n\n target.value = next\n notify()\n }\n\n function drop(): void {\n if (mode.value !== 'keyboard')\n return\n\n commit()\n }\n\n function cancel(): void {\n if (mode.value === 'pointer') {\n gesture.stop(false)\n return\n }\n\n if (mode.value === 'keyboard')\n reset()\n }\n\n onScopeDispose(() => {\n stopScrolling()\n window.removeEventListener('keydown', onWindowKeydown, true)\n })\n\n return { source, target, mode, isActive, startFrom, grab, setTarget, drop, cancel }\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"GrTabs-BArefUFv.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, watchEffect, 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\nif (__GR_DEV__) {\n watchEffect(() => {\n if (props.modelValue === undefined) {\n console.warn(\n `[granularity] GrTabs: обязательный проп \\`modelValue\\` не передан — получено ${String(props.modelValue)}.`,\n )\n }\n\n if (!Array.isArray(props.tabs)) {\n console.warn(\n `[granularity] GrTabs: обязательный проп \\`tabs\\` должен быть массивом — получено ${String(props.tabs)}.`,\n )\n }\n })\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, watchEffect, 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\nif (__GR_DEV__) {\n watchEffect(() => {\n if (props.modelValue === undefined) {\n console.warn(\n `[granularity] GrTabs: обязательный проп \\`modelValue\\` не передан — получено ${String(props.modelValue)}.`,\n )\n }\n\n if (!Array.isArray(props.tabs)) {\n console.warn(\n `[granularity] GrTabs: обязательный проп \\`tabs\\` должен быть массивом — получено ${String(props.tabs)}.`,\n )\n }\n })\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":";;;;;;;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,sBACd,aACA,YACA,YACkB;CAClB,MAAM,SAAS,aAAa;CAE5B,IAAI,UAAU,GACZ,OAAO;CAET,MAAM,QAAQ,KAAK,IAAI,WAAW;CAClC,MAAM,UAAU,SAAS;CACzB,MAAM,QAAQ,SAAS,SAAS;CAEhC,IAAI,WAAW,OACb,OAAO;CAET,IAAI,SACF,OAAO;CAET,IAAI,OACF,OAAO;CAET,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ECgDA,MAAM,QAAQ;EAmBd,MAAM,OAAO;EAEb,MAAM,EAAE,MAAM,2BAA2B;EAEzC,MAAM,eAAe,yBAAyB,MAAM,MAAM,EAAE,WAAW,SAAS,CAAC;EACjF,MAAM,kBAAkB,mBAAmB,UAAU,iBAAiB,MAAM,SAAS,OAAO;EAE5F,MAAM,eAAe,eAAe,gBAAgB;GAClD,SAAS,gBAAgB;GACzB,MAAM,aAAa;GACnB,aAAa,MAAM;EACrB,CAAC,CAAC;EAEF,SAAS,SAAS,KAAoB;GACpC,OAAO,eAAe;IACpB,SAAS,gBAAgB;IACzB,MAAM,aAAa;IACnB,QAAQ,IAAI,UAAU,MAAM;IAC5B,UAAU,QAAQ,IAAI,QAAQ;GAChC,CAAC;EACH;EAEA,MAAM,aAAa,eAAe,iBAAiB,aAAa,KAAK,CAAC;EACtE,MAAM,aAAa,eAAe,iBAAiB,aAAa,KAAK,CAAC;EACtE,MAAM,iBAAiB,eAAe,kBAAkB,aAAa,MAAM;EAC3E,MAAM,aAAa,eAAe,iBAAiB,aAAa,KAAK,CAAC;;;;;;EAOtE,MAAM,UAAU,eAAe,MAAM,KAAK,WAAW,CAAC;EACtD,MAAM,oBAAoB,eAAe,MAAM,aAAa,EAAE,iBAAiB,kBAAkB,CAAC;EAClG,MAAM,aAAa,eAAe,EAAE,iBAAiB,WAAW,CAAC;;EAGjE,SAAS,WAAW,KAAqB;GACvC,OAAO,CAAC,IAAI,aAAa,IAAI,YAAY,MAAM;EACjD;EAEA,MAAM,aAAa,IAAyB,CAAC,CAAC;;;;;;EAO9C,SAAS,aAAa,IAAa,OAAqB;GACtD,IAAI,cAAc,mBAChB,WAAW,MAAM,SAAS;QAE1B,OAAO,WAAW,MAAM;GAE1B,IAAI,WAAW,MAAM,SAAS,MAAM,KAAK,QACvC,WAAW,MAAM,SAAS,MAAM,KAAK;EACzC;EAEA,MAAM,cAAc,eAAe,MAAM,KAAK,WAAU,MAAK,EAAE,UAAU,MAAM,UAAU,CAAC;EAE1F,SAAS,UAAU,KAAqB;GACtC,OAAO,CAAC,IAAI;EACd;EAEA,MAAM,aAAa,eAAe,MAAM,KAAK,KAAK,GAAG,UAAU,KAAK,CAAC;;;;;;;;;;;EAYrE,MAAM,SAAS,eAAuB;GACpC,aAAa,WAAW;GACxB,aAAY,UAAS,WAAW,MAAM;GACtC,aAAY,UAAS,QAAQ,MAAM,KAAK,MAAM,EAAE,QAAQ;GACxD,mBAAoB,MAAM,gBAAgB,aAAa,aAAa;GACpE,oBAAoB;;;;;;;;;;;GAWpB,kBAAmB,YAAY,SAAS,IAAI,YAAY,QAAQ,KAAA;;;;;;GAMhE,cAAa,UAAS,eAAe,KAAK;EAC5C,CAAC;;;;;;EAOD,eAAe,WAAW,OAA8B;GACtD,MAAM,SAAS;GACf,MAAM,OAAO,SAAS,KAAK;EAC7B;;;;;;EAOA,SAAS,eAAe,OAAqB;GAC3C,WAAW,MAAM,MAAM,EAAE,iBAAiB;IAAE,OAAO;IAAW,QAAQ;GAAU,CAAC;EACnF;EAEA,YAAY,MAAM,YAAY,YAAY;GACxC,MAAM,SAAS;GACf,IAAI,YAAY,SAAS,GACvB,eAAe,YAAY,KAAK;EACpC,CAAC;;;;;;;;;;;EAYD,MAAM,YAAY,IAAwB,IAAI;EAC9C,MAAM,iBAAiB,IAAsB,MAAM;EAEnD,IAAI,iBAAwC;EAC5C,IAAI,YAAY;EAEhB,SAAS,kBAAwB;GAC/B,MAAM,KAAK,UAAU;GAErB,IAAI,CAAC,MAAM,MAAM,gBAAgB,YAAY;IAC3C,eAAe,QAAQ;IACvB;GACF;GAEA,eAAe,QAAQ,sBAAsB,GAAG,YAAY,GAAG,aAAa,GAAG,WAAW;EAC5F;EAEA,SAAS,kBAAwB;GAC/B,IAAI,WACF;GAEF,YAAY;GACZ,eAAoB;IAClB,YAAY;IACZ,gBAAgB;GAClB,CAAC;EACH;EAEA,gBAAgB;GACd,gBAAgB;GAGhB,IAAI,OAAO,mBAAmB,aAC5B;GAEF,iBAAiB,IAAI,eAAe,eAAe;GACnD,IAAI,UAAU,OACZ,eAAe,QAAQ,UAAU,KAAK;EAC1C,CAAC;EAED,sBAAsB;GACpB,gBAAgB,WAAW;GAC3B,iBAAiB;EACnB,CAAC;EAGD,YAAY,CAAC,MAAM,KAAK,QAAQ,MAAM,WAAW,GAAG,eAAe;EAEnE,eAAe,cAAc,OAAe,QAAQ,OAAsB;GACxE,MAAM,MAAM,MAAM,KAAK;GACvB,IAAI,CAAC,OAAO,CAAC,UAAU,GAAG,GACxB;GACF,IAAI,IAAI,UAAU,MAAM,YACtB,KAAK,qBAAqB,IAAI,KAAK;GACrC,IAAI,OACF,MAAM,WAAW,KAAK;EAC1B;EAEA,SAAS,UAAU,OAA4B;GAC7C,IAAI,MAAM,KAAK,WAAW,GACxB;GAEF,MAAM,eAAe,OAAO,UAAU,SAAS;GAE/C,QAAQ,MAAM,KAAd;IACE,KAAK;IACL,KAAK;KAEH,IAAI,MAAM,mBAAmB,UAAU;MACrC,MAAM,eAAe;MACrB,cAAmB,cAAc,KAAK;KACxC;KACA;IACF,KAAK;IACL,KAAK,aAAa;KAEhB,MAAM,MAAM,MAAM,KAAK;KACvB,IAAI,OAAO,WAAW,GAAG,GAAG;MAC1B,MAAM,eAAe;MACrB,aAAa,YAAY;KAC3B;KACA;IACF;GACF;GAGA,IAAI,CAAC,OAAO,qBAAqB,KAAK,GACpC;GAGF,IAAI,MAAM,mBAAmB,UAC3B,cAAmB,OAAO,UAAU,SAAS,cAAc,KAAK;EACpE;;;;;;EAOA,IAAI,eAAwD;EAE5D,SAAS,aAAa,OAAqB;GACzC,MAAM,MAAM,MAAM,KAAK;GACvB,IAAI,CAAC,OAAO,CAAC,WAAW,GAAG,GACzB;GAGF,eADa,WAAW,MAAM,WAAW,SAAS,gBAC5B;IAAE;IAAO,OAAO,IAAI;GAAM,IAAI;GAEpD,KAAK,SAAS,IAAI,KAAK;EACzB;EAIA,YAAY,MAAM,KAAK,QAAQ,OAAO,MAAM,SAAS;GACnD,MAAM,UAAU;GAChB,eAAe;GAEf,IAAI,CAAC,WAAW,QAAQ,MACtB;GAEF,IAAI,MAAM,KAAK,MAAK,QAAO,IAAI,UAAU,QAAQ,KAAK,GACpD;GAEF,MAAM,SAAS;GAEf,MAAM,SAAS,KAAK,IAAI,QAAQ,OAAO,OAAO,CAAC;GAC/C,IAAI,UAAU,GACZ,WAAgB,MAAM;QAEtB,OAAO,MAAM;EACjB,CAAC;EAED,SAAS,QAAQ,OAAmB,KAAY,OAAqB;GACnE,IAAI,CAAC,UAAU,GAAG,GAChB;GAGF,IAAI,MAAM,kBAAkB,WAAW,MAAM,OAAO,QAAQ,qBAAqB,GAAG;IAClF,aAAa,KAAK;IAClB;GACF;GAEA,OAAO,UAAU,KAAK;GAEtB,IAAI,IAAI,UAAU,MAAM,YACtB,KAAK,qBAAqB,IAAI,KAAK;EACvC;EAEA,IAAA,QAAA,IAAA,aAAA,cACE,kBAAkB;GAChB,IAAI,MAAM,eAAe,KAAA,GACvB,QAAQ,KACN,gFAAgF,OAAO,MAAM,UAAU,EAAE,EAC3G;GAGF,IAAI,CAAC,MAAM,QAAQ,MAAM,IAAI,GAC3B,QAAQ,KACN,oFAAoF,OAAO,MAAM,IAAI,EAAE,EACzG;EAEJ,CAAC;;GAKU,OAAA,QAAA,SAAX,UAAA,GAAA,mBAIM,OAAA;;IAJc,sBAAA;IAAoB,OAAK,eAAE,WAAA,KAAU;GACvD,GAAA,CAAA,WAEO,KAAA,QAAA,SAAA,CAAA,SAAA,CADF,gBAAA,gBAAA,kBAAA,KAAiB,GAAA,CAAA,CAAA,CAAA,CAAA,GAAA,CAAA,MAIxB,UAAA,GAAA,mBA0DM,OAAA;;IAxDA,SAAA;IAAJ,KAAI;IACJ,MAAK;IACL,gBAAA;IACC,oBAAkB,QAAA;IAClB,iBAAe,QAAA,gBAAW,aAAkB,KAAA,IAAY,eAAA;IACxD,OAAK,eAAE,aAAA,KAAY;IACV;IACO,iBAAA;GAEjB,GAAA,EAAA,UAAA,IAAA,GAAA,mBA8CS,UAAA,MAAA,WA7CgB,QAAA,OAAf,KAAK,UAAK;IADpB,OAAA,UAAA,GAAA,mBA8CS,UAAA;KA5CN,IAAI,QAAA,SAAM,GAAM,QAAA,OAAM,OAAQ,IAAI,UAAU,KAAA;KAC5C,KAAK,IAAI;;KACT,MAAK,OAAM,aAAa,IAAI,KAAK;KAClC,MAAK;KACL,MAAK;KACL,eAAA;KACC,iBAAe,QAAA,SAAM,GAAM,QAAA,OAAM,SAAU,IAAI,UAAU,KAAA;KACzD,iBAAe,IAAI,UAAU,QAAA,aAAU,SAAA;KACvC,iBAAe,IAAI,WAAQ,SAAY,KAAA;KACvC,qBAAmB,WAAW,GAAG,IAAA,WAAe,KAAA;KAChD,UAAU,MAAA,MAAA,CAAM,CAAC,YAAY,KAAK;KAClC,OAAK,eAAE,SAAS,GAAG,CAAA;KACnB,UAAK,WAAE,QAAQ,QAAQ,KAAK,KAAK;IAElC,GAAA,CAAA,mBA6BO,QAAA,EA7BA,OAAK,eAAE,MAAA,eAAA,CAAe,EAAA,GAAA,CAC3B,WAgBO,KAAA,QAAA,OAAA;KAdC;KACL,QAAQ,IAAI,UAAU,QAAA;KACtB,UAAU,QAAQ,IAAI,QAAQ;IAJjC,SAgBO;KARG,IAAI,QAFZ,UAAA,GAAA,YAKE,wBAJK,MAAA,OAAA,CAAO,CAAC,IAAI,IAAI,CAAA,GAAA;;MAEpB,OAAK,eAAA,CAAG,MAAA,YAAA,GAAc,MAAA,SAAA,CAAS,CAAC,IAAI,IAAI,CAAA,CAAA;MACzC,eAAY;;KAEd,mBAA4B,QAAA,MAAA,gBAAnB,IAAI,KAAK,GAAA,CAAA;KACN,IAAI,SAAhB,UAAA,GAAA,mBAEO,QAAA;;MAFiB,OAAK,eAAE,WAAA,KAAU;KACpC,GAAA,gBAAA,IAAI,KAAK,GAAA,CAAA,KAAA,mBAAA,IAAA,IAAA;IAMR,CAAA,GAAA,WAAW,GAAG,KADtB,UAAA,GAAA,mBAQO,QAAA;;KANL,qBAAA;KACC,OAAK,eAAE,WAAA,KAAU;KACjB,OAAO,WAAA;KACR,eAAY;IAEZ,GAAA,CAAA,YAAiC,MAAA,SAAA,GAAA,EAAzB,OAAK,eAAE,eAAA,KAAc,EAAA,GAAA,MAAA,GAAA,CAAA,OAAA,CAAA,CAAA,GAAA,IAAA,UAAA,KAAA,mBAAA,IAAA,IAAA,CAAA,GAAA,CAAA,CAAA,GAAA,IAAA,UAAA"}
@@ -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
@@ -10,8 +10,8 @@ import { useRovingFocus } from "../../../composables/useRovingFocus.js";
10
10
  import { t as useControlAddons } from "../../../chunks/useControlAddons-asUr10Pf.js";
11
11
  import { d as chipSizeForBadgeScale } from "../../../chunks/grChipStyles-DcDyxAL0.js";
12
12
  import { t as GrChip_default } from "../../GrChip/chunks/GrChip-CKqyzYcv.js";
13
- import { t as loader_2_default } from "../../../chunks/loader-2-B1fdsdWW.js";
14
13
  import { useAnnouncer } from "../../../composables/useAnnouncer.js";
14
+ import { t as loader_2_default } from "../../../chunks/loader-2-B1fdsdWW.js";
15
15
  import { a as grInputTagWrapperClass, i as grInputTagInputClass, o as spinnerClass, r as clearButtonClass } from "../../../chunks/config-BqT6PMzO.js";
16
16
  import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, nextTick, normalizeClass, normalizeStyle, openBlock, ref, renderList, renderSlot, toDisplayString, unref, watchEffect, withCtx, withModifiers } from "vue";
17
17
  //#region src/components/GrInputTag/GrInputTag.vue?vue&type=script&setup=true&lang.ts
@@ -6,9 +6,9 @@ import { n as useGrFormFieldContext } from "../../../chunks/context-DdNIxa1w.js"
6
6
  import { useGrFormControl } from "../../../composables/useGrFormControl.js";
7
7
  import { n as addLen, t as useControlAddons } from "../../../chunks/useControlAddons-asUr10Pf.js";
8
8
  import { t as chevron_down_default } from "../../../chunks/chevron-down-CQS2OG_s.js";
9
- import { t as bigStep } from "../../../chunks/numericStep-Bq2lj4ew.js";
10
9
  import { t as chevron_left_default } from "../../../chunks/chevron-left-D8AUBAM7.js";
11
10
  import { t as chevron_right_default } from "../../../chunks/chevron-right-C6Z01h0X.js";
11
+ import { t as bigStep } from "../../../chunks/numericStep-Bq2lj4ew.js";
12
12
  import { a as grNumberInputShellClass, i as grNumberInputInputClass, o as stepperCompactClass, r as clearButtonClass, s as stepperWideClass } from "../../../chunks/config-MmPym-Z7.js";
13
13
  import { computed, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, markRaw, mergeProps, normalizeClass, normalizeStyle, onBeforeUnmount, openBlock, ref, renderSlot, unref, watchEffect, withCtx, withModifiers } from "vue";
14
14
  //#region src/components/GrNumberInput/numberInputMath.ts
@@ -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