@feugene/granularity 0.41.0 → 0.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +13 -162
- package/README.md +5 -5
- package/dist/chunks/config-BXRmsn3t.js +172 -0
- package/dist/chunks/config-BXRmsn3t.js.map +1 -0
- package/dist/chunks/{config-BRESvs9g.js → config-Ca8qr8Q3.js} +1 -1
- package/dist/chunks/{config-BRESvs9g.js.map → config-Ca8qr8Q3.js.map} +1 -1
- package/dist/chunks/config-Dg0bSoGw.js +81 -0
- package/dist/chunks/config-Dg0bSoGw.js.map +1 -0
- package/dist/chunks/config-DmED6aJV.js +237 -0
- package/dist/chunks/config-DmED6aJV.js.map +1 -0
- package/dist/chunks/{config-DWL81-aM.js → config-hnoZvcIM.js} +2 -20
- package/dist/chunks/config-hnoZvcIM.js.map +1 -0
- package/dist/chunks/dragSortGeometry-CY-llfrQ.js +87 -0
- package/dist/chunks/dragSortGeometry-CY-llfrQ.js.map +1 -0
- package/dist/chunks/{en-CuuLfNHX.js → en-C_QkUTRL.js} +36 -1
- package/dist/chunks/en-C_QkUTRL.js.map +1 -0
- package/dist/chunks/{es-CsmvfESn.js → es-B4t_3Wnh.js} +3 -3
- package/dist/chunks/{es-CsmvfESn.js.map → es-B4t_3Wnh.js.map} +1 -1
- package/dist/chunks/{es-CSnmrcMw.js → es-DemSEhZE.js} +36 -1
- package/dist/chunks/es-DemSEhZE.js.map +1 -0
- package/dist/chunks/{generated-B95IBXs6.js → generated-VcxJTo_q.js} +210 -0
- package/dist/chunks/generated-VcxJTo_q.js.map +1 -0
- package/dist/chunks/{grKbdStyles-DENu8pUK.js → grKbdStyles-ByMsX-lQ.js} +17 -2
- package/dist/chunks/grKbdStyles-ByMsX-lQ.js.map +1 -0
- package/dist/chunks/{granular-provider-BshNi2la.js → granular-provider-BohXQcNt.js} +8 -2
- package/dist/chunks/granular-provider-BohXQcNt.js.map +1 -0
- package/dist/chunks/{ru-BKWafECI.js → ru-CLUErjhY.js} +36 -1
- package/dist/chunks/ru-CLUErjhY.js.map +1 -0
- package/dist/chunks/scrollOverflow-B_kEdcol.js +31 -0
- package/dist/chunks/scrollOverflow-B_kEdcol.js.map +1 -0
- package/dist/chunks/tones-CRJAaYyg.js +21 -0
- package/dist/chunks/tones-CRJAaYyg.js.map +1 -0
- package/dist/components/GrAffix/chunks/GrAffix-CWtC7yi8.js +272 -0
- package/dist/components/GrAffix/chunks/GrAffix-CWtC7yi8.js.map +1 -0
- package/dist/components/GrAffix/index.js +3 -0
- package/dist/components/GrBreadcrumbs/chunks/{GrBreadcrumbs-DOpwY2EF.js → GrBreadcrumbs-D0qPry_R.js} +4 -1
- package/dist/components/GrBreadcrumbs/chunks/GrBreadcrumbs-D0qPry_R.js.map +1 -0
- package/dist/components/GrBreadcrumbs/index.js +1 -1
- package/dist/components/GrCarousel/chunks/GrCarousel-ByuughtI.js +761 -0
- package/dist/components/GrCarousel/chunks/GrCarousel-ByuughtI.js.map +1 -0
- package/dist/components/GrCarousel/index.js +3 -0
- package/dist/components/GrCarousel/styles.css +1 -0
- package/dist/components/GrColorPicker/chunks/{GrColorPicker-BwzRLs-i.js → GrColorPicker-9mTR-YZ_.js} +1 -1
- package/dist/components/GrColorPicker/chunks/{GrColorPicker-BwzRLs-i.js.map → GrColorPicker-9mTR-YZ_.js.map} +1 -1
- package/dist/components/GrColorPicker/index.js +1 -1
- package/dist/components/GrCommandPalette/chunks/{GrCommandPalette-CnzPuoIM.js → GrCommandPalette-Ha4s3DMT.js} +1 -1
- package/dist/components/GrCommandPalette/chunks/{GrCommandPalette-CnzPuoIM.js.map → GrCommandPalette-Ha4s3DMT.js.map} +1 -1
- package/dist/components/GrCommandPalette/index.js +1 -1
- package/dist/components/GrDataTable/chunks/{GrDataTable-DpyipOFx.js → GrDataTable-xNSmO_I-.js} +9 -4
- package/dist/components/GrDataTable/chunks/GrDataTable-xNSmO_I-.js.map +1 -0
- package/dist/components/GrDataTable/index.js +1 -1
- package/dist/components/GrDelta/chunks/{GrDelta-BpfqebEV.js → GrDelta-CgTvfoIN.js} +0 -0
- package/dist/components/GrDelta/chunks/GrDelta-CgTvfoIN.js.map +1 -0
- package/dist/components/GrDelta/index.js +1 -1
- package/dist/components/GrDescriptionList/chunks/{GrDescriptionList-D8ANFpn_.js → GrDescriptionList-Dz2Jj1if.js} +26 -11
- package/dist/components/GrDescriptionList/chunks/GrDescriptionList-Dz2Jj1if.js.map +1 -0
- package/dist/components/GrDescriptionList/index.js +1 -1
- package/dist/components/GrFilePreview/chunks/{GrFilePreview-PabkLgYX.js → GrFilePreview-2kVOg1C7.js} +40 -5
- package/dist/components/GrFilePreview/chunks/GrFilePreview-2kVOg1C7.js.map +1 -0
- package/dist/components/GrFilePreview/index.js +1 -1
- package/dist/components/GrImageViewer/chunks/{GrImageViewer-Dd2gJ-j_.js.map → GrImageViewer-DK-iVHHC.js.map} +1 -1
- package/dist/components/GrImageViewer/index.js +1 -1
- package/dist/components/GrInputTag/chunks/{GrInputTag-3Au5HWZL.js → GrInputTag-Ci8AO1ze.js} +5 -2
- package/dist/components/GrInputTag/chunks/GrInputTag-Ci8AO1ze.js.map +1 -0
- package/dist/components/GrInputTag/index.js +1 -1
- package/dist/components/GrJsonViewer/chunks/{GrJsonViewer-CDGOX3C0.js → GrJsonViewer-CwCBMh4h.js} +34 -8
- package/dist/components/GrJsonViewer/chunks/GrJsonViewer-CwCBMh4h.js.map +1 -0
- package/dist/components/GrJsonViewer/index.js +1 -1
- package/dist/components/GrKbd/chunks/{GrKbd-D_a1mPLe.js → GrKbd-BNMeoaLV.js} +1 -1
- package/dist/components/GrKbd/chunks/{GrKbd-D_a1mPLe.js.map → GrKbd-BNMeoaLV.js.map} +1 -1
- package/dist/components/GrKbd/index.js +2 -2
- package/dist/components/GrNumberInput/chunks/{GrNumberInput-BqPydXpK.js → GrNumberInput-CL5QhOmx.js} +5 -2
- package/dist/components/GrNumberInput/chunks/GrNumberInput-CL5QhOmx.js.map +1 -0
- package/dist/components/GrNumberInput/index.js +1 -1
- package/dist/components/GrProgressBar/index.js +1 -1
- package/dist/components/GrSegmented/chunks/{GrSegmented-NKTi9bMG.js → GrSegmented-CuDaKA6S.js} +5 -1
- package/dist/components/GrSegmented/chunks/GrSegmented-CuDaKA6S.js.map +1 -0
- package/dist/components/GrSegmented/index.js +1 -1
- package/dist/components/GrSlider/chunks/{GrSlider-Db-jACXz.js.map → GrSlider-CO8N9SF6.js.map} +1 -1
- package/dist/components/GrSlider/index.js +1 -1
- package/dist/components/GrSortableList/chunks/{GrSortableList-CQCaWivn.js → GrSortableList-NKriuGm_.js} +6 -2
- package/dist/components/GrSortableList/chunks/GrSortableList-NKriuGm_.js.map +1 -0
- package/dist/components/GrSortableList/index.js +1 -1
- package/dist/components/GrSteps/chunks/{GrSteps-DXj7m-WO.js → GrSteps-Cng-Wrjk.js} +5 -1
- package/dist/components/GrSteps/chunks/GrSteps-Cng-Wrjk.js.map +1 -0
- package/dist/components/GrSteps/index.js +1 -1
- package/dist/components/GrTabs/chunks/{GrTabs-DHfv67cp.js → GrTabs-s9RzC1WX.js} +7 -32
- package/dist/components/GrTabs/chunks/GrTabs-s9RzC1WX.js.map +1 -0
- package/dist/components/GrTabs/index.js +1 -1
- package/dist/components/GrTransfer/chunks/GrTransfer-8FA78ny6.js +1368 -0
- package/dist/components/GrTransfer/chunks/GrTransfer-8FA78ny6.js.map +1 -0
- package/dist/components/GrTransfer/index.js +3 -0
- package/dist/components/GrTransfer/styles.css +1 -0
- package/dist/components/GrTree/chunks/{GrTree-BFWFc0Bm.js → GrTree-CkHzgYg1.js} +3 -3
- package/dist/components/GrTree/chunks/{GrTree-BFWFc0Bm.js.map → GrTree-CkHzgYg1.js.map} +1 -1
- package/dist/components/GrTree/index.js +1 -1
- package/dist/components/GrTreeSelect/chunks/{GrTreeSelect-DJa2lYGe.js → GrTreeSelect-DUsv3_mF.js} +1 -1
- package/dist/components/GrTreeSelect/chunks/{GrTreeSelect-DJa2lYGe.js.map → GrTreeSelect-DUsv3_mF.js.map} +1 -1
- package/dist/components/GrTreeSelect/index.js +1 -1
- package/dist/composables/useDragSort.js +179 -1
- package/dist/composables/useDragSort.js.map +1 -0
- package/dist/granular-provider-node.js +1 -1
- package/dist/granular-provider.js +1 -1
- package/dist/i18n/all.js +1 -1
- package/dist/i18n/index.js +1 -1
- package/dist/index.js +32 -28
- package/dist/theme.js +1 -1
- package/dist/tokens.js +1 -1
- package/dist/types/components/GrAffix/GrAffix.vue.d.ts +91 -0
- package/dist/types/components/GrAffix/affixState.d.ts +81 -0
- package/dist/types/components/GrAffix/config.d.ts +1 -0
- package/dist/types/components/GrAffix/grAffixStyles.d.ts +46 -0
- package/dist/types/components/GrAffix/index.d.ts +9 -0
- package/dist/types/components/GrAffix/safelist.d.ts +1 -0
- package/dist/types/components/GrAlert/GrAlert.vue.d.ts +1 -1
- package/dist/types/components/GrBadgeWrap/GrBadgeWrap.vue.d.ts +2 -2
- package/dist/types/components/GrButton/GrButton.vue.d.ts +2 -2
- package/dist/types/components/GrCard/GrCard.vue.d.ts +1 -1
- package/dist/types/components/GrCarousel/GrCarousel.vue.d.ts +151 -0
- package/dist/types/components/GrCarousel/GrCarouselSlide.vue.d.ts +53 -0
- package/dist/types/components/GrCarousel/carouselNavigation.d.ts +57 -0
- package/dist/types/components/GrCarousel/composables/useCarouselAutoplay.d.ts +33 -0
- package/dist/types/components/GrCarousel/composables/useCarouselSwipe.d.ts +20 -0
- package/dist/types/components/GrCarousel/config.d.ts +7 -0
- package/dist/types/components/GrCarousel/defaults.d.ts +13 -0
- package/dist/types/components/GrCarousel/grCarouselContext.d.ts +46 -0
- package/dist/types/components/GrCarousel/grCarouselStyles.d.ts +80 -0
- package/dist/types/components/GrCarousel/index.d.ts +12 -0
- package/dist/types/components/GrCarousel/safelist.d.ts +1 -0
- package/dist/types/components/GrCheckbox/GrCheckbox.vue.d.ts +3 -3
- package/dist/types/components/GrCheckboxGroup/GrCheckboxGroup.vue.d.ts +2 -2
- package/dist/types/components/GrChip/GrChip.vue.d.ts +2 -2
- package/dist/types/components/GrChipGroup/GrChipGroup.vue.d.ts +2 -2
- package/dist/types/components/GrCollapse/GrCollapse.vue.d.ts +1 -1
- package/dist/types/components/GrCollapse/GrCollapseItem.vue.d.ts +1 -1
- package/dist/types/components/GrColorPicker/GrColorPicker.vue.d.ts +3 -3
- package/dist/types/components/GrCommandPalette/GrCommandPalette.vue.d.ts +1 -1
- package/dist/types/components/GrConfirmDialog/GrConfirmDialog.vue.d.ts +1 -1
- package/dist/types/components/GrContextMenu/GrContextMenu.vue.d.ts +2 -2
- package/dist/types/components/GrDialog/GrDialog.vue.d.ts +1 -1
- package/dist/types/components/GrDivider/GrDivider.vue.d.ts +2 -2
- package/dist/types/components/GrDrawer/GrDrawer.vue.d.ts +1 -1
- package/dist/types/components/GrDropdown/GrDropdown.vue.d.ts +3 -3
- package/dist/types/components/GrDropdownMenu/GrDropdownMenu.vue.d.ts +3 -3
- package/dist/types/components/GrDropdownMenu/GrDropdownMenuItem.vue.d.ts +1 -1
- package/dist/types/components/GrEmptyState/GrEmptyState.vue.d.ts +1 -1
- package/dist/types/components/GrFilePreview/GrFilePreview.vue.d.ts +14 -0
- package/dist/types/components/GrFileUpload/GrFileUpload.vue.d.ts +1 -1
- package/dist/types/components/GrFormField/GrFormField.vue.d.ts +3 -3
- package/dist/types/components/GrFormFile/GrFormFile.vue.d.ts +2 -2
- package/dist/types/components/GrFormSection/GrFormSection.vue.d.ts +1 -1
- package/dist/types/components/GrIcon/GrIcon.vue.d.ts +1 -1
- package/dist/types/components/GrImageViewer/GrImageViewer.vue.d.ts +3 -3
- package/dist/types/components/GrInput/GrInput.vue.d.ts +4 -4
- package/dist/types/components/GrInputTag/GrInputTag.vue.d.ts +3 -3
- package/dist/types/components/GrKbd/GrKbd.vue.d.ts +1 -1
- package/dist/types/components/GrKbd/grKbdStyles.d.ts +17 -2
- package/dist/types/components/GrLink/GrLink.vue.d.ts +1 -1
- package/dist/types/components/GrList/GrListItem.vue.d.ts +2 -2
- package/dist/types/components/GrNavbar/GrNavbar.vue.d.ts +2 -2
- package/dist/types/components/GrNumberInput/GrNumberInput.vue.d.ts +3 -3
- package/dist/types/components/GrPagination/GrPagination.vue.d.ts +1 -1
- package/dist/types/components/GrPopover/GrPopover.vue.d.ts +3 -3
- package/dist/types/components/GrPromptDialog/GrPromptDialog.vue.d.ts +3 -3
- package/dist/types/components/GrRadio/GrRadio.vue.d.ts +2 -2
- package/dist/types/components/GrRadioGroup/GrRadioGroup.vue.d.ts +2 -2
- package/dist/types/components/GrRating/GrRating.vue.d.ts +2 -2
- package/dist/types/components/GrSegmented/GrSegmented.vue.d.ts +2 -2
- package/dist/types/components/GrSidebar/GrSidebarItem.vue.d.ts +1 -1
- package/dist/types/components/GrSkeleton/GrSkeleton.vue.d.ts +1 -1
- package/dist/types/components/GrSlider/GrSlider.vue.d.ts +3 -3
- package/dist/types/components/GrSplitter/GrSplitter.vue.d.ts +1 -1
- package/dist/types/components/GrStatistic/GrStatistic.vue.d.ts +2 -2
- package/dist/types/components/GrSwitch/GrSwitch.vue.d.ts +3 -3
- package/dist/types/components/GrTable/GrTable.vue.d.ts +1 -1
- package/dist/types/components/GrTabs/GrTabs.vue.d.ts +1 -1
- package/dist/types/components/GrTextarea/GrTextarea.vue.d.ts +2 -2
- package/dist/types/components/GrTimeline/GrTimelineItem.vue.d.ts +2 -2
- package/dist/types/components/GrToaster/GrToaster.vue.d.ts +1 -1
- package/dist/types/components/GrTooltip/GrTooltip.vue.d.ts +3 -3
- package/dist/types/components/GrTransfer/GrTransfer.vue.d.ts +103 -0
- package/dist/types/components/GrTransfer/config.d.ts +1 -0
- package/dist/types/components/GrTransfer/defaults.d.ts +15 -0
- package/dist/types/components/GrTransfer/grTransferStyles.d.ts +127 -0
- package/dist/types/components/GrTransfer/index.d.ts +11 -0
- package/dist/types/components/GrTransfer/safelist.d.ts +1 -0
- package/dist/types/components/GrTransfer/transferGeometry.d.ts +44 -0
- package/dist/types/components/GrTransfer/transferModel.d.ts +68 -0
- package/dist/types/components/GrTransfer/transferSelection.d.ts +36 -0
- package/dist/types/components/GrTransfer/useTransferDrag.d.ts +48 -0
- package/dist/types/granular-provider/shared.d.ts +3 -0
- package/dist/types/i18n/messages/en.d.ts +70 -0
- package/dist/types/i18n/messages/es.d.ts +70 -0
- package/dist/types/i18n/messages/ru.d.ts +70 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/web-types.json +735 -287
- package/package.json +19 -3
- package/dist/chunks/config-DWL81-aM.js.map +0 -1
- package/dist/chunks/en-CuuLfNHX.js.map +0 -1
- package/dist/chunks/es-CSnmrcMw.js.map +0 -1
- package/dist/chunks/generated-B95IBXs6.js.map +0 -1
- package/dist/chunks/grKbdStyles-DENu8pUK.js.map +0 -1
- package/dist/chunks/granular-provider-BshNi2la.js.map +0 -1
- package/dist/chunks/ru-BKWafECI.js.map +0 -1
- package/dist/chunks/useDragSort-0l47h4yb.js +0 -265
- package/dist/chunks/useDragSort-0l47h4yb.js.map +0 -1
- package/dist/components/GrBreadcrumbs/chunks/GrBreadcrumbs-DOpwY2EF.js.map +0 -1
- package/dist/components/GrDataTable/chunks/GrDataTable-DpyipOFx.js.map +0 -1
- package/dist/components/GrDelta/chunks/GrDelta-BpfqebEV.js.map +0 -1
- package/dist/components/GrDescriptionList/chunks/GrDescriptionList-D8ANFpn_.js.map +0 -1
- package/dist/components/GrFilePreview/chunks/GrFilePreview-PabkLgYX.js.map +0 -1
- package/dist/components/GrInputTag/chunks/GrInputTag-3Au5HWZL.js.map +0 -1
- package/dist/components/GrJsonViewer/chunks/GrJsonViewer-CDGOX3C0.js.map +0 -1
- package/dist/components/GrNumberInput/chunks/GrNumberInput-BqPydXpK.js.map +0 -1
- package/dist/components/GrSegmented/chunks/GrSegmented-NKTi9bMG.js.map +0 -1
- package/dist/components/GrSortableList/chunks/GrSortableList-CQCaWivn.js.map +0 -1
- package/dist/components/GrSteps/chunks/GrSteps-DXj7m-WO.js.map +0 -1
- package/dist/components/GrTabs/chunks/GrTabs-DHfv67cp.js.map +0 -1
- package/dist/components/GrImageViewer/chunks/{GrImageViewer-Dd2gJ-j_.js → GrImageViewer-DK-iVHHC.js} +3 -3
- package/dist/components/GrSlider/chunks/{GrSlider-Db-jACXz.js → GrSlider-CO8N9SF6.js} +1 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ru-BKWafECI.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 \"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":"GrBreadcrumbs-DOpwY2EF.js","names":[],"sources":["../../../../src/components/GrBreadcrumbs/GrBreadcrumbs.vue","../../../../src/components/GrBreadcrumbs/GrBreadcrumbs.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'\n\nimport GrLink from '../GrLink/GrLink.vue'\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { iconClass, iconTag } from '../shared/icon'\n\nimport {\n breadcrumbsCurrentClass,\n breadcrumbsEllipsisClass,\n breadcrumbsItemIconClass,\n breadcrumbsItemWrapClass,\n breadcrumbsLabelClass,\n breadcrumbsLabelHiddenClass,\n breadcrumbsListClass,\n breadcrumbsListNowrapClass,\n breadcrumbsListWrapClass,\n breadcrumbsRootClass,\n breadcrumbsSeparatorClass,\n breadcrumbsSizeClassBySize,\n type GrBreadcrumbItem,\n type GrBreadcrumbsLinkComponent,\n type GrBreadcrumbsSize,\n resolveBreadcrumbsFit,\n resolveBreadcrumbsLayout,\n} from './grBreadcrumbsStyles'\n\nexport type { GrBreadcrumbItem, GrBreadcrumbsSize } from './grBreadcrumbsStyles'\n\n/**\n * Путь до текущей страницы (WAI-ARIA breadcrumb): `<nav>` с именем, внутри\n * упорядоченный список ссылок, последний пункт — текущая страница.\n *\n * Ссылки рисует `GrLink`, поэтому роутер подключается тем же способом, что и\n * везде в пакете: `as` + `to`.\n */\nexport interface GrBreadcrumbsProps {\n /** Путь от корня к текущей странице. Последний пункт — текущая страница. */\n items: GrBreadcrumbItem[]\n /** Компонент ссылки для всех пунктов: `RouterLink`, `NuxtLink`, `Link` Inertia. */\n as?: GrBreadcrumbsLinkComponent\n /** Разделитель между пунктами. Декоративен: диктору не читается. */\n separator?: string\n size?: GrBreadcrumbsSize\n /**\n * С какого числа пунктов схлопывать середину в «…». `0`/не задано — показывать\n * путь целиком.\n */\n maxItems?: number\n /** Сколько пунктов оставить в начале при схлопывании. */\n itemsBeforeCollapse?: number\n /** Сколько пунктов оставить в конце при схлопывании. */\n itemsAfterCollapse?: number\n /** Имя лендмарка. Не задано — берётся из локали. */\n ariaLabel?: string\n /** Последний пункт остаётся ссылкой (текущая страница кликабельна). */\n linkCurrent?: boolean\n /**\n * Какой пункт — текущая страница. Не задан — последний.\n *\n * `-1` означает, что текущей страницы в пути нет: её имя стоит в `h1`, а\n * путь показывает только родителей. Тогда `aria-current` не получает никто,\n * и последний пункт остаётся обычной ссылкой.\n */\n currentIndex?: number\n /** i18n-метка кнопки раскрытия схлопнутого пути. */\n expandLabel?: string\n /**\n * Схлопывать по доступной ширине, а не по числу пунктов. Путь становится\n * однострочным, середина уходит под «…» ровно настолько, насколько не влезает.\n *\n * Без пропа поведение прежнее: список переносится на вторую строку, а порог\n * задаёт `maxItems`. Вместе пропы совместимы — `maxItems` остаётся жёстким\n * потолком, ширина ужимает дальше.\n */\n autoCollapse?: boolean\n}\n\nconst props = withDefaults(defineProps<GrBreadcrumbsProps>(), {\n as: undefined,\n separator: undefined,\n size: undefined,\n maxItems: undefined,\n itemsBeforeCollapse: 1,\n itemsAfterCollapse: 1,\n ariaLabel: undefined,\n linkCurrent: false,\n currentIndex: undefined,\n expandLabel: undefined,\n autoCollapse: false,\n})\n\ndefineSlots<{\n /** Содержимое пункта целиком (иконка и подпись). */\n item?: (props: { item: GrBreadcrumbItem, index: number, isCurrent: boolean }) => unknown\n /** Разделитель между пунктами. */\n separator?: () => unknown\n /** Кнопка раскрытия схлопнутой середины. */\n ellipsis?: (props: { hiddenCount: number, expand: () => void }) => unknown\n}>()\n\nconst { t } = useGranularityTranslations()\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrBreadcrumbs' })\nconst resolvedSeparator = useGrComponentProp('GrBreadcrumbs', 'separator', () => props.separator, '/')\n\nconst resolvedAriaLabel = computed(() => props.ariaLabel ?? t('gr.breadcrumbs.label', 'Breadcrumb'))\nconst resolvedExpandLabel = computed(() => props.expandLabel ?? t('gr.breadcrumbs.expand', 'Show hidden path'))\n\nconst rootEl = ref<HTMLElement | null>(null)\nconst listEl = ref<HTMLElement | null>(null)\nconst expanded = ref(false)\n\n/**\n * Схлопывание по ширине: сколько пунктов хвоста влезает.\n *\n * `undefined` — ещё не мерили, показываем путь целиком. Первый кадр в режиме\n * `autoCollapse` намеренно полный: ширины снимаются с него, а посчитать их до\n * рендера неоткуда.\n */\nconst fitAfter = ref<number | undefined>(undefined)\n\n/** Ширины не зависят от контейнера, поэтому живут до смены самого пути. */\nlet measured: { items: number[], separator: number } | null = null\n/**\n * Ширина кнопки «…» узнаётся только когда она отрисована, то есть уже после\n * первого схлопывания. До этого считаем её нулевой: поправка может лишь\n * ужать хвост ещё на пункт, а не вернуть спрятанное, поэтому качелей не будет.\n */\nlet ellipsisWidth = 0\n/** Ширина, на которой считали в прошлый раз: по её смене раскрытый путь сворачивается. */\nlet lastAvailable = 0\n\n// Новый путь — новая страница: раскрытая середина прошлого пути к ней отношения\n// не имеет, а измеренные ширины относятся к прошлым подписям.\nwatch(() => props.items, () => {\n expanded.value = false\n measured = null\n ellipsisWidth = 0\n fitAfter.value = undefined\n void nextTick(measure)\n})\n\nconst lastIndex = computed(() => props.items.length - 1)\n\n/**\n * Схлопывание по ширине выражается через тот же `maxItems`: раскладку считает\n * одна функция, а режимы отличаются только тем, откуда взялось число.\n */\nconst autoMaxItems = computed(() => {\n if (!props.autoCollapse || fitAfter.value === undefined)\n return undefined\n if (fitAfter.value >= props.items.length)\n return undefined\n\n return props.items.length - 1\n})\n\nconst effectiveMaxItems = computed(() => {\n const limits = [props.maxItems, autoMaxItems.value].filter((value): value is number => typeof value === 'number' && value > 0)\n\n return limits.length > 0 ? Math.min(...limits) : undefined\n})\n\nconst effectiveAfter = computed(() => (\n props.autoCollapse && fitAfter.value !== undefined ? fitAfter.value : props.itemsAfterCollapse\n))\n\nconst entries = computed(() => resolveBreadcrumbsLayout({\n items: props.items,\n maxItems: effectiveMaxItems.value,\n itemsBeforeCollapse: props.itemsBeforeCollapse,\n itemsAfterCollapse: effectiveAfter.value,\n expanded: expanded.value,\n}))\n\nconst resolvedCurrentIndex = computed(() => props.currentIndex ?? lastIndex.value)\n\nfunction isCurrent(index: number): boolean {\n return index === resolvedCurrentIndex.value\n}\n\n/**\n * Класс подписи: у пункта-иконки она прячется визуально, но остаётся в\n * разметке — путь читают и поиском, и диктором.\n *\n * Без иконки поле игнорируется: спрятать подпись, не показав ничего взамен,\n * значит стереть пункт. Молчать тут нельзя — в разметке всё на месте, и\n * пропажу видно только глазами.\n */\nfunction labelClassFor(item: GrBreadcrumbItem): string {\n if (!item.iconOnly)\n return breadcrumbsLabelClass\n\n if (!item.icon) {\n if (__GR_DEV__) {\n console.warn(\n `[granularity] GrBreadcrumbs: у пункта «${item.label}» стоит \\`iconOnly\\` без \\`icon\\`. `\n + 'Подпись осталась видимой: прятать её, не показав иконку, значит стереть пункт.',\n )\n }\n\n return breadcrumbsLabelClass\n }\n\n return breadcrumbsLabelHiddenClass\n}\n\n/**\n * Ссылкой пункт становится, только если ведёт куда-то и не выключен.\n */\nfunction isLink(item: GrBreadcrumbItem, index: number): boolean {\n if (item.disabled)\n return false\n if (isCurrent(index) && !props.linkCurrent)\n return false\n return item.href !== undefined || item.to !== undefined\n}\n\nfunction widthOf(el: Element | null | undefined): number {\n // `scrollWidth`, а не прямоугольник: у подписи стоит `truncate`, и в узком\n // контейнере прямоугольник вернул бы уже ужатую ширину, а не собственную.\n return el ? (el as HTMLElement).scrollWidth : 0\n}\n\n/**\n * Снять ширины с полного пути и пересчитать, сколько пунктов хвоста влезает.\n *\n * Замер делается один раз на путь: подписи от ширины контейнера не зависят,\n * поэтому `resize` пересчитывает только арифметику.\n */\nfunction measure(): void {\n if (!props.autoCollapse)\n return\n\n const list = listEl.value\n if (!list)\n return\n\n const ellipsis = list.querySelector('[data-gr-breadcrumbs-ellipsis-item]')\n if (ellipsis)\n ellipsisWidth = widthOf(ellipsis)\n\n if (!measured) {\n // Ширины снимаем только с полного пути: спрятанных пунктов в DOM нет.\n if (fitAfter.value !== undefined)\n return\n\n const wraps = [...list.querySelectorAll('[data-gr-breadcrumbs-item-wrap]')]\n if (wraps.length !== props.items.length)\n return\n\n measured = {\n items: wraps.map(widthOf),\n separator: widthOf(list.querySelector('[data-gr-breadcrumbs-separator]')),\n }\n }\n\n const available = list.clientWidth\n // jsdom и скрытый контейнер отдают 0 — решать по такой ширине нечего.\n if (available <= 0)\n return\n\n lastAvailable = available\n\n fitAfter.value = resolveBreadcrumbsFit({\n itemWidths: measured.items,\n separatorWidth: measured.separator,\n ellipsisWidth,\n available,\n itemsBeforeCollapse: props.itemsBeforeCollapse,\n gapWidth: listGap(list),\n })\n}\n\n/** Зазор списка в пикселях: он задан классом, а не пропом, поэтому спрашиваем стиль. */\nfunction listGap(list: HTMLElement): number {\n if (typeof getComputedStyle !== 'function')\n return 0\n\n return Number.parseFloat(getComputedStyle(list).columnGap) || 0\n}\n\n/**\n * Ширина кнопки «…» известна только после того, как она отрисована, то есть\n * после первого схлопывания. Поэтому решение пересчитывается ещё раз: поправка\n * либо ничего не меняет, либо ужимает хвост на пункт. Цикла нет — второй проход\n * оставляет `fitAfter` прежним, и наблюдатель больше не срабатывает.\n */\nwatch(fitAfter, () => void nextTick(measure))\n\nlet observer: ResizeObserver | null = null\n\nwatch(() => props.autoCollapse, (enabled) => {\n if (enabled) {\n void nextTick(measure)\n return\n }\n\n measured = null\n ellipsisWidth = 0\n fitAfter.value = undefined\n})\n\nonMounted(() => {\n void nextTick(measure)\n\n // На сервере и в jsdom `ResizeObserver` отсутствует — измерять там нечего.\n if (typeof ResizeObserver === 'undefined')\n return\n\n observer = new ResizeObserver(() => {\n // Смена ширины — новая раскладка, и раскрытая руками середина относилась к\n // прежней. Свернуть её иначе нечем: в раскрытом пути кнопки «…» нет, то есть\n // поворот телефона оставлял бы путь развёрнутым навсегда. Высота меняется от\n // самого раскрытия (путь переносится на вторую строку) — на неё не реагируем.\n if (expanded.value && listEl.value && listEl.value.clientWidth !== lastAvailable)\n expanded.value = false\n\n measure()\n })\n if (listEl.value)\n observer.observe(listEl.value)\n})\n\nonBeforeUnmount(() => observer?.disconnect())\n\nasync function expand(): Promise<void> {\n expanded.value = true\n\n // Кнопка исчезает вместе со схлопыванием, и фокус ушёл бы на `<body>`.\n // Переводим его на первый раскрытый пункт — там, где кнопка и стояла. Пункты\n // без ссылки держат `tabindex=\"-1\"` именно ради этого случая.\n await nextTick()\n const before = Math.max(0, Math.min(props.itemsBeforeCollapse, props.items.length))\n const revealed = rootEl.value?.querySelectorAll<HTMLElement>('[data-gr-breadcrumbs-item]')\n revealed?.[before]?.focus()\n}\n</script>\n\n<template>\n <nav\n ref=\"rootEl\"\n data-gr-breadcrumbs\n :class=\"[breadcrumbsRootClass, breadcrumbsSizeClassBySize[resolvedSize]]\"\n :aria-label=\"resolvedAriaLabel\"\n >\n <ol\n ref=\"listEl\"\n data-gr-breadcrumbs-list\n :class=\"[breadcrumbsListClass, autoCollapse && !expanded ? breadcrumbsListNowrapClass : breadcrumbsListWrapClass]\"\n >\n <template v-for=\"(entry, position) in entries\" :key=\"entry.kind === 'item' ? `item-${entry.index}` : 'ellipsis'\">\n <!--\n Разделитель — отдельный элемент списка и помечен `aria-hidden`:\n структуру пути диктору сообщает сам список, а «/» он бы читал вслух.\n -->\n <li\n v-if=\"position > 0\"\n data-gr-breadcrumbs-separator\n :class=\"breadcrumbsSeparatorClass\"\n aria-hidden=\"true\"\n >\n <slot name=\"separator\">\n {{ resolvedSeparator }}\n </slot>\n </li>\n\n <li v-if=\"entry.kind === 'ellipsis'\" data-gr-breadcrumbs-ellipsis-item class=\"min-w-0\">\n <slot name=\"ellipsis\" :hidden-count=\"entry.hiddenCount\" :expand=\"expand\">\n <button\n type=\"button\"\n data-gr-breadcrumbs-ellipsis\n data-testid=\"gr-breadcrumbs-ellipsis\"\n :class=\"breadcrumbsEllipsisClass\"\n :aria-label=\"resolvedExpandLabel\"\n @click=\"expand\"\n >\n …\n </button>\n </slot>\n </li>\n\n <li v-else data-gr-breadcrumbs-item-wrap :class=\"breadcrumbsItemWrapClass\">\n <GrLink\n v-if=\"isLink(entry.item, entry.index)\"\n :key=\"`link-${entry.index}`\"\n data-gr-breadcrumbs-item\n :as=\"as\"\n :href=\"entry.item.href\"\n :to=\"entry.item.to\"\n :size=\"resolvedSize\"\n :variant=\"isCurrent(entry.index) ? 'default' : 'muted'\"\n :aria-label=\"entry.item.ariaLabel\"\n :aria-current=\"isCurrent(entry.index) ? 'page' : undefined\"\n >\n <slot name=\"item\" :item=\"entry.item\" :index=\"entry.index\" :is-current=\"isCurrent(entry.index)\">\n <component\n :is=\"iconTag(entry.item.icon)\"\n v-if=\"entry.item.icon\"\n :class=\"[breadcrumbsItemIconClass, iconClass(entry.item.icon)]\"\n aria-hidden=\"true\"\n />\n <span :class=\"labelClassFor(entry.item)\">{{ entry.item.label }}</span>\n </slot>\n </GrLink>\n\n <!--\n Текущая страница и выключенный пункт — не ссылки. `aria-current`\n остаётся на текущем: именно он отвечает на вопрос «где я».\n -->\n <span\n v-else\n data-gr-breadcrumbs-item\n :class=\"breadcrumbsCurrentClass\"\n :aria-label=\"entry.item.ariaLabel\"\n :aria-current=\"isCurrent(entry.index) ? 'page' : undefined\"\n :aria-disabled=\"entry.item.disabled ? 'true' : undefined\"\n tabindex=\"-1\"\n >\n <slot name=\"item\" :item=\"entry.item\" :index=\"entry.index\" :is-current=\"isCurrent(entry.index)\">\n <component\n :is=\"iconTag(entry.item.icon)\"\n v-if=\"entry.item.icon\"\n :class=\"[breadcrumbsItemIconClass, iconClass(entry.item.icon)]\"\n aria-hidden=\"true\"\n />\n <span :class=\"labelClassFor(entry.item)\">{{ entry.item.label }}</span>\n </slot>\n </span>\n </li>\n </template>\n </ol>\n </nav>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'\n\nimport GrLink from '../GrLink/GrLink.vue'\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { iconClass, iconTag } from '../shared/icon'\n\nimport {\n breadcrumbsCurrentClass,\n breadcrumbsEllipsisClass,\n breadcrumbsItemIconClass,\n breadcrumbsItemWrapClass,\n breadcrumbsLabelClass,\n breadcrumbsLabelHiddenClass,\n breadcrumbsListClass,\n breadcrumbsListNowrapClass,\n breadcrumbsListWrapClass,\n breadcrumbsRootClass,\n breadcrumbsSeparatorClass,\n breadcrumbsSizeClassBySize,\n type GrBreadcrumbItem,\n type GrBreadcrumbsLinkComponent,\n type GrBreadcrumbsSize,\n resolveBreadcrumbsFit,\n resolveBreadcrumbsLayout,\n} from './grBreadcrumbsStyles'\n\nexport type { GrBreadcrumbItem, GrBreadcrumbsSize } from './grBreadcrumbsStyles'\n\n/**\n * Путь до текущей страницы (WAI-ARIA breadcrumb): `<nav>` с именем, внутри\n * упорядоченный список ссылок, последний пункт — текущая страница.\n *\n * Ссылки рисует `GrLink`, поэтому роутер подключается тем же способом, что и\n * везде в пакете: `as` + `to`.\n */\nexport interface GrBreadcrumbsProps {\n /** Путь от корня к текущей странице. Последний пункт — текущая страница. */\n items: GrBreadcrumbItem[]\n /** Компонент ссылки для всех пунктов: `RouterLink`, `NuxtLink`, `Link` Inertia. */\n as?: GrBreadcrumbsLinkComponent\n /** Разделитель между пунктами. Декоративен: диктору не читается. */\n separator?: string\n size?: GrBreadcrumbsSize\n /**\n * С какого числа пунктов схлопывать середину в «…». `0`/не задано — показывать\n * путь целиком.\n */\n maxItems?: number\n /** Сколько пунктов оставить в начале при схлопывании. */\n itemsBeforeCollapse?: number\n /** Сколько пунктов оставить в конце при схлопывании. */\n itemsAfterCollapse?: number\n /** Имя лендмарка. Не задано — берётся из локали. */\n ariaLabel?: string\n /** Последний пункт остаётся ссылкой (текущая страница кликабельна). */\n linkCurrent?: boolean\n /**\n * Какой пункт — текущая страница. Не задан — последний.\n *\n * `-1` означает, что текущей страницы в пути нет: её имя стоит в `h1`, а\n * путь показывает только родителей. Тогда `aria-current` не получает никто,\n * и последний пункт остаётся обычной ссылкой.\n */\n currentIndex?: number\n /** i18n-метка кнопки раскрытия схлопнутого пути. */\n expandLabel?: string\n /**\n * Схлопывать по доступной ширине, а не по числу пунктов. Путь становится\n * однострочным, середина уходит под «…» ровно настолько, насколько не влезает.\n *\n * Без пропа поведение прежнее: список переносится на вторую строку, а порог\n * задаёт `maxItems`. Вместе пропы совместимы — `maxItems` остаётся жёстким\n * потолком, ширина ужимает дальше.\n */\n autoCollapse?: boolean\n}\n\nconst props = withDefaults(defineProps<GrBreadcrumbsProps>(), {\n as: undefined,\n separator: undefined,\n size: undefined,\n maxItems: undefined,\n itemsBeforeCollapse: 1,\n itemsAfterCollapse: 1,\n ariaLabel: undefined,\n linkCurrent: false,\n currentIndex: undefined,\n expandLabel: undefined,\n autoCollapse: false,\n})\n\ndefineSlots<{\n /** Содержимое пункта целиком (иконка и подпись). */\n item?: (props: { item: GrBreadcrumbItem, index: number, isCurrent: boolean }) => unknown\n /** Разделитель между пунктами. */\n separator?: () => unknown\n /** Кнопка раскрытия схлопнутой середины. */\n ellipsis?: (props: { hiddenCount: number, expand: () => void }) => unknown\n}>()\n\nconst { t } = useGranularityTranslations()\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrBreadcrumbs' })\nconst resolvedSeparator = useGrComponentProp('GrBreadcrumbs', 'separator', () => props.separator, '/')\n\nconst resolvedAriaLabel = computed(() => props.ariaLabel ?? t('gr.breadcrumbs.label', 'Breadcrumb'))\nconst resolvedExpandLabel = computed(() => props.expandLabel ?? t('gr.breadcrumbs.expand', 'Show hidden path'))\n\nconst rootEl = ref<HTMLElement | null>(null)\nconst listEl = ref<HTMLElement | null>(null)\nconst expanded = ref(false)\n\n/**\n * Схлопывание по ширине: сколько пунктов хвоста влезает.\n *\n * `undefined` — ещё не мерили, показываем путь целиком. Первый кадр в режиме\n * `autoCollapse` намеренно полный: ширины снимаются с него, а посчитать их до\n * рендера неоткуда.\n */\nconst fitAfter = ref<number | undefined>(undefined)\n\n/** Ширины не зависят от контейнера, поэтому живут до смены самого пути. */\nlet measured: { items: number[], separator: number } | null = null\n/**\n * Ширина кнопки «…» узнаётся только когда она отрисована, то есть уже после\n * первого схлопывания. До этого считаем её нулевой: поправка может лишь\n * ужать хвост ещё на пункт, а не вернуть спрятанное, поэтому качелей не будет.\n */\nlet ellipsisWidth = 0\n/** Ширина, на которой считали в прошлый раз: по её смене раскрытый путь сворачивается. */\nlet lastAvailable = 0\n\n// Новый путь — новая страница: раскрытая середина прошлого пути к ней отношения\n// не имеет, а измеренные ширины относятся к прошлым подписям.\nwatch(() => props.items, () => {\n expanded.value = false\n measured = null\n ellipsisWidth = 0\n fitAfter.value = undefined\n void nextTick(measure)\n})\n\nconst lastIndex = computed(() => props.items.length - 1)\n\n/**\n * Схлопывание по ширине выражается через тот же `maxItems`: раскладку считает\n * одна функция, а режимы отличаются только тем, откуда взялось число.\n */\nconst autoMaxItems = computed(() => {\n if (!props.autoCollapse || fitAfter.value === undefined)\n return undefined\n if (fitAfter.value >= props.items.length)\n return undefined\n\n return props.items.length - 1\n})\n\nconst effectiveMaxItems = computed(() => {\n const limits = [props.maxItems, autoMaxItems.value].filter((value): value is number => typeof value === 'number' && value > 0)\n\n return limits.length > 0 ? Math.min(...limits) : undefined\n})\n\nconst effectiveAfter = computed(() => (\n props.autoCollapse && fitAfter.value !== undefined ? fitAfter.value : props.itemsAfterCollapse\n))\n\nconst entries = computed(() => resolveBreadcrumbsLayout({\n items: props.items,\n maxItems: effectiveMaxItems.value,\n itemsBeforeCollapse: props.itemsBeforeCollapse,\n itemsAfterCollapse: effectiveAfter.value,\n expanded: expanded.value,\n}))\n\nconst resolvedCurrentIndex = computed(() => props.currentIndex ?? lastIndex.value)\n\nfunction isCurrent(index: number): boolean {\n return index === resolvedCurrentIndex.value\n}\n\n/**\n * Класс подписи: у пункта-иконки она прячется визуально, но остаётся в\n * разметке — путь читают и поиском, и диктором.\n *\n * Без иконки поле игнорируется: спрятать подпись, не показав ничего взамен,\n * значит стереть пункт. Молчать тут нельзя — в разметке всё на месте, и\n * пропажу видно только глазами.\n */\nfunction labelClassFor(item: GrBreadcrumbItem): string {\n if (!item.iconOnly)\n return breadcrumbsLabelClass\n\n if (!item.icon) {\n if (__GR_DEV__) {\n console.warn(\n `[granularity] GrBreadcrumbs: у пункта «${item.label}» стоит \\`iconOnly\\` без \\`icon\\`. `\n + 'Подпись осталась видимой: прятать её, не показав иконку, значит стереть пункт.',\n )\n }\n\n return breadcrumbsLabelClass\n }\n\n return breadcrumbsLabelHiddenClass\n}\n\n/**\n * Ссылкой пункт становится, только если ведёт куда-то и не выключен.\n */\nfunction isLink(item: GrBreadcrumbItem, index: number): boolean {\n if (item.disabled)\n return false\n if (isCurrent(index) && !props.linkCurrent)\n return false\n return item.href !== undefined || item.to !== undefined\n}\n\nfunction widthOf(el: Element | null | undefined): number {\n // `scrollWidth`, а не прямоугольник: у подписи стоит `truncate`, и в узком\n // контейнере прямоугольник вернул бы уже ужатую ширину, а не собственную.\n return el ? (el as HTMLElement).scrollWidth : 0\n}\n\n/**\n * Снять ширины с полного пути и пересчитать, сколько пунктов хвоста влезает.\n *\n * Замер делается один раз на путь: подписи от ширины контейнера не зависят,\n * поэтому `resize` пересчитывает только арифметику.\n */\nfunction measure(): void {\n if (!props.autoCollapse)\n return\n\n const list = listEl.value\n if (!list)\n return\n\n const ellipsis = list.querySelector('[data-gr-breadcrumbs-ellipsis-item]')\n if (ellipsis)\n ellipsisWidth = widthOf(ellipsis)\n\n if (!measured) {\n // Ширины снимаем только с полного пути: спрятанных пунктов в DOM нет.\n if (fitAfter.value !== undefined)\n return\n\n const wraps = [...list.querySelectorAll('[data-gr-breadcrumbs-item-wrap]')]\n if (wraps.length !== props.items.length)\n return\n\n measured = {\n items: wraps.map(widthOf),\n separator: widthOf(list.querySelector('[data-gr-breadcrumbs-separator]')),\n }\n }\n\n const available = list.clientWidth\n // jsdom и скрытый контейнер отдают 0 — решать по такой ширине нечего.\n if (available <= 0)\n return\n\n lastAvailable = available\n\n fitAfter.value = resolveBreadcrumbsFit({\n itemWidths: measured.items,\n separatorWidth: measured.separator,\n ellipsisWidth,\n available,\n itemsBeforeCollapse: props.itemsBeforeCollapse,\n gapWidth: listGap(list),\n })\n}\n\n/** Зазор списка в пикселях: он задан классом, а не пропом, поэтому спрашиваем стиль. */\nfunction listGap(list: HTMLElement): number {\n if (typeof getComputedStyle !== 'function')\n return 0\n\n return Number.parseFloat(getComputedStyle(list).columnGap) || 0\n}\n\n/**\n * Ширина кнопки «…» известна только после того, как она отрисована, то есть\n * после первого схлопывания. Поэтому решение пересчитывается ещё раз: поправка\n * либо ничего не меняет, либо ужимает хвост на пункт. Цикла нет — второй проход\n * оставляет `fitAfter` прежним, и наблюдатель больше не срабатывает.\n */\nwatch(fitAfter, () => void nextTick(measure))\n\nlet observer: ResizeObserver | null = null\n\nwatch(() => props.autoCollapse, (enabled) => {\n if (enabled) {\n void nextTick(measure)\n return\n }\n\n measured = null\n ellipsisWidth = 0\n fitAfter.value = undefined\n})\n\nonMounted(() => {\n void nextTick(measure)\n\n // На сервере и в jsdom `ResizeObserver` отсутствует — измерять там нечего.\n if (typeof ResizeObserver === 'undefined')\n return\n\n observer = new ResizeObserver(() => {\n // Смена ширины — новая раскладка, и раскрытая руками середина относилась к\n // прежней. Свернуть её иначе нечем: в раскрытом пути кнопки «…» нет, то есть\n // поворот телефона оставлял бы путь развёрнутым навсегда. Высота меняется от\n // самого раскрытия (путь переносится на вторую строку) — на неё не реагируем.\n if (expanded.value && listEl.value && listEl.value.clientWidth !== lastAvailable)\n expanded.value = false\n\n measure()\n })\n if (listEl.value)\n observer.observe(listEl.value)\n})\n\nonBeforeUnmount(() => observer?.disconnect())\n\nasync function expand(): Promise<void> {\n expanded.value = true\n\n // Кнопка исчезает вместе со схлопыванием, и фокус ушёл бы на `<body>`.\n // Переводим его на первый раскрытый пункт — там, где кнопка и стояла. Пункты\n // без ссылки держат `tabindex=\"-1\"` именно ради этого случая.\n await nextTick()\n const before = Math.max(0, Math.min(props.itemsBeforeCollapse, props.items.length))\n const revealed = rootEl.value?.querySelectorAll<HTMLElement>('[data-gr-breadcrumbs-item]')\n revealed?.[before]?.focus()\n}\n</script>\n\n<template>\n <nav\n ref=\"rootEl\"\n data-gr-breadcrumbs\n :class=\"[breadcrumbsRootClass, breadcrumbsSizeClassBySize[resolvedSize]]\"\n :aria-label=\"resolvedAriaLabel\"\n >\n <ol\n ref=\"listEl\"\n data-gr-breadcrumbs-list\n :class=\"[breadcrumbsListClass, autoCollapse && !expanded ? breadcrumbsListNowrapClass : breadcrumbsListWrapClass]\"\n >\n <template v-for=\"(entry, position) in entries\" :key=\"entry.kind === 'item' ? `item-${entry.index}` : 'ellipsis'\">\n <!--\n Разделитель — отдельный элемент списка и помечен `aria-hidden`:\n структуру пути диктору сообщает сам список, а «/» он бы читал вслух.\n -->\n <li\n v-if=\"position > 0\"\n data-gr-breadcrumbs-separator\n :class=\"breadcrumbsSeparatorClass\"\n aria-hidden=\"true\"\n >\n <slot name=\"separator\">\n {{ resolvedSeparator }}\n </slot>\n </li>\n\n <li v-if=\"entry.kind === 'ellipsis'\" data-gr-breadcrumbs-ellipsis-item class=\"min-w-0\">\n <slot name=\"ellipsis\" :hidden-count=\"entry.hiddenCount\" :expand=\"expand\">\n <button\n type=\"button\"\n data-gr-breadcrumbs-ellipsis\n data-testid=\"gr-breadcrumbs-ellipsis\"\n :class=\"breadcrumbsEllipsisClass\"\n :aria-label=\"resolvedExpandLabel\"\n @click=\"expand\"\n >\n …\n </button>\n </slot>\n </li>\n\n <li v-else data-gr-breadcrumbs-item-wrap :class=\"breadcrumbsItemWrapClass\">\n <GrLink\n v-if=\"isLink(entry.item, entry.index)\"\n :key=\"`link-${entry.index}`\"\n data-gr-breadcrumbs-item\n :as=\"as\"\n :href=\"entry.item.href\"\n :to=\"entry.item.to\"\n :size=\"resolvedSize\"\n :variant=\"isCurrent(entry.index) ? 'default' : 'muted'\"\n :aria-label=\"entry.item.ariaLabel\"\n :aria-current=\"isCurrent(entry.index) ? 'page' : undefined\"\n >\n <slot name=\"item\" :item=\"entry.item\" :index=\"entry.index\" :is-current=\"isCurrent(entry.index)\">\n <component\n :is=\"iconTag(entry.item.icon)\"\n v-if=\"entry.item.icon\"\n :class=\"[breadcrumbsItemIconClass, iconClass(entry.item.icon)]\"\n aria-hidden=\"true\"\n />\n <span :class=\"labelClassFor(entry.item)\">{{ entry.item.label }}</span>\n </slot>\n </GrLink>\n\n <!--\n Текущая страница и выключенный пункт — не ссылки. `aria-current`\n остаётся на текущем: именно он отвечает на вопрос «где я».\n -->\n <span\n v-else\n data-gr-breadcrumbs-item\n :class=\"breadcrumbsCurrentClass\"\n :aria-label=\"entry.item.ariaLabel\"\n :aria-current=\"isCurrent(entry.index) ? 'page' : undefined\"\n :aria-disabled=\"entry.item.disabled ? 'true' : undefined\"\n tabindex=\"-1\"\n >\n <slot name=\"item\" :item=\"entry.item\" :index=\"entry.index\" :is-current=\"isCurrent(entry.index)\">\n <component\n :is=\"iconTag(entry.item.icon)\"\n v-if=\"entry.item.icon\"\n :class=\"[breadcrumbsItemIconClass, iconClass(entry.item.icon)]\"\n aria-hidden=\"true\"\n />\n <span :class=\"labelClassFor(entry.item)\">{{ entry.item.label }}</span>\n </slot>\n </span>\n </li>\n </template>\n </ol>\n </nav>\n</template>\n"],"mappings":"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"}
|