@feugene/granularity 0.20.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunks/{config-BJ7Bv_G2.js → config-BC8d_smz.js} +10 -2
- package/dist/chunks/config-BC8d_smz.js.map +1 -0
- package/dist/chunks/config-BDfTXHo1.js.map +1 -0
- package/dist/chunks/config-BEVBsvni2.js.map +1 -0
- package/dist/chunks/config-BTCOJO5z2.js.map +1 -0
- package/dist/chunks/config-B_qi4Fma.js +92 -0
- package/dist/chunks/config-B_qi4Fma.js.map +1 -0
- package/dist/chunks/{config-gwro_7yn.js → config-Bm_Sovla.js} +6 -2
- package/dist/chunks/config-Bm_Sovla.js.map +1 -0
- package/dist/chunks/{config-BKvX0q10.js → config-C5mO5DYZ.js} +10 -1
- package/dist/chunks/config-C5mO5DYZ.js.map +1 -0
- package/dist/chunks/config-CIcynHGr.js +96 -0
- package/dist/chunks/config-CIcynHGr.js.map +1 -0
- package/dist/chunks/{config-BsuDM3Hb.js → config-CKULiuvc.js} +15 -1
- package/dist/chunks/config-CKULiuvc.js.map +1 -0
- package/dist/chunks/config-COpuWGr_.js.map +1 -0
- package/dist/chunks/config-DBQQT-WP.js +66 -0
- package/dist/chunks/config-DBQQT-WP.js.map +1 -0
- package/dist/chunks/config-DU2DNSRx.js.map +1 -0
- package/dist/chunks/config-QJ54_fcM.js +61 -0
- package/dist/chunks/config-QJ54_fcM.js.map +1 -0
- package/dist/chunks/config-ScuvHw6k.js.map +1 -0
- package/dist/chunks/config-eXB87DOi.js.map +1 -0
- package/dist/chunks/config-jyZq8XKL.js +58 -0
- package/dist/chunks/config-jyZq8XKL.js.map +1 -0
- package/dist/chunks/copy-DChChcq-.js +29 -0
- package/dist/chunks/copy-DChChcq-.js.map +1 -0
- package/dist/chunks/{en-COdzxgAX.js → en-DCCFP4qu.js} +18 -2
- package/dist/chunks/{en-COdzxgAX.js.map → en-DCCFP4qu.js.map} +1 -1
- package/dist/chunks/{es-ClqJ-4hr.js → es-CT34HBVM.js} +18 -2
- package/dist/chunks/{es-ClqJ-4hr.js.map → es-CT34HBVM.js.map} +1 -1
- package/dist/chunks/{es-DGMHwknJ.js → es-DdJsxlUr.js} +3 -3
- package/dist/chunks/{es-DGMHwknJ.js.map → es-DdJsxlUr.js.map} +1 -1
- package/dist/chunks/{generated-BRkbhyxk.js → generated-CiCrGDQO.js} +147 -0
- package/dist/chunks/{generated-BRkbhyxk.js.map → generated-CiCrGDQO.js.map} +1 -1
- package/dist/chunks/{granular-provider-CZYm70Pm.js → granular-provider-CYroWMjO.js} +21 -11
- package/dist/chunks/granular-provider-CYroWMjO.js.map +1 -0
- package/dist/chunks/{ru-9z8ix45c.js → ru-CLiAMwrx.js} +18 -2
- package/dist/chunks/{ru-9z8ix45c.js.map → ru-CLiAMwrx.js.map} +1 -1
- package/dist/chunks/trending-up-Bf9AQmoq.js +42 -0
- package/dist/chunks/trending-up-Bf9AQmoq.js.map +1 -0
- package/dist/components/GrCard/chunks/GrCard-Bc7nRfbe.js +144 -0
- package/dist/components/GrCard/chunks/GrCard-Bc7nRfbe.js.map +1 -0
- package/dist/components/GrCard/index.js +2 -2
- package/dist/components/GrCodeBlock/chunks/GrCodeBlock-BmXCLgLS.js +315 -0
- package/dist/components/GrCodeBlock/chunks/GrCodeBlock-BmXCLgLS.js.map +1 -0
- package/dist/components/GrCodeBlock/index.js +3 -0
- package/dist/components/GrCodeBlock/styles.css +1 -0
- package/dist/components/GrCollapse/chunks/{GrCollapse-KFcrji1L.js → GrCollapse-C1rCfbc0.js} +2 -2
- package/dist/components/GrCollapse/chunks/{GrCollapse-KFcrji1L.js.map → GrCollapse-C1rCfbc0.js.map} +1 -1
- package/dist/components/GrCollapse/index.js +2 -2
- package/dist/components/GrDataTable/chunks/{GrDataTable-CNvZg2HZ.js → GrDataTable-B5MmZFUg.js} +67 -5
- package/dist/components/GrDataTable/chunks/GrDataTable-B5MmZFUg.js.map +1 -0
- package/dist/components/GrDataTable/index.js +2 -2
- package/dist/components/GrDelta/chunks/GrDelta-BMmpYz8K.js +0 -0
- package/dist/components/GrDelta/chunks/GrDelta-BMmpYz8K.js.map +1 -0
- package/dist/components/GrDelta/index.js +3 -0
- package/dist/components/GrDescriptionList/chunks/GrDescriptionList-pXh1LZEe.js +103 -0
- package/dist/components/GrDescriptionList/chunks/GrDescriptionList-pXh1LZEe.js.map +1 -0
- package/dist/components/GrDescriptionList/index.js +3 -0
- package/dist/components/GrDropdownMenu/chunks/{GrDropdownMenu-LOaFSCyd.js → GrDropdownMenu-DyS6dWDp.js} +2 -2
- package/dist/components/GrDropdownMenu/chunks/{GrDropdownMenu-LOaFSCyd.js.map → GrDropdownMenu-DyS6dWDp.js.map} +1 -1
- package/dist/components/GrDropdownMenu/index.js +2 -2
- package/dist/components/GrFilePreview/chunks/GrFilePreview-rbE3zL9w.js +315 -0
- package/dist/components/GrFilePreview/chunks/GrFilePreview-rbE3zL9w.js.map +1 -0
- package/dist/components/GrFilePreview/index.js +3 -0
- package/dist/components/GrForm/chunks/{GrForm-DdJw23Pz.js → GrForm-B_Qq_13k.js} +14 -7
- package/dist/components/GrForm/chunks/GrForm-B_Qq_13k.js.map +1 -0
- package/dist/components/GrForm/index.js +1 -1
- package/dist/components/GrImageViewer/chunks/{GrImageViewer-CMO10TS7.js.map → GrImageViewer-ng7el4wn.js.map} +1 -1
- package/dist/components/GrImageViewer/index.js +1 -1
- package/dist/components/GrInputTag/chunks/{GrInputTag-Dghhg03-.js.map → GrInputTag-C7QVPijj.js.map} +1 -1
- package/dist/components/GrInputTag/index.js +1 -1
- package/dist/components/GrJsonViewer/chunks/GrJsonViewer-DFUppRNb.js +389 -0
- package/dist/components/GrJsonViewer/chunks/GrJsonViewer-DFUppRNb.js.map +1 -0
- package/dist/components/GrJsonViewer/index.js +3 -0
- package/dist/components/GrList/chunks/{GrList-CHibO0xA.js → GrList-BRYhpqIe.js} +4 -4
- package/dist/components/GrList/chunks/GrList-BRYhpqIe.js.map +1 -0
- package/dist/components/GrList/index.js +2 -2
- package/dist/components/GrList/styles.css +1 -1
- package/dist/components/GrRadio/index.js +1 -1
- package/dist/components/GrSegmented/chunks/{GrSegmented-DP9WMAbm.js → GrSegmented-D1mjxF_O.js} +1 -1
- package/dist/components/GrSegmented/chunks/GrSegmented-D1mjxF_O.js.map +1 -0
- package/dist/components/GrSegmented/index.js +2 -2
- package/dist/components/GrSidebar/chunks/{GrSidebar-DG_t4aPE.js → GrSidebar-D7WxRtIu.js} +7 -1
- package/dist/components/GrSidebar/chunks/{GrSidebar-DG_t4aPE.js.map → GrSidebar-D7WxRtIu.js.map} +1 -1
- package/dist/components/GrSidebar/index.js +1 -1
- package/dist/components/GrSortableList/chunks/{GrSortableList-DJFPdUVv.js → GrSortableList-DPjxilmN.js} +1 -1
- package/dist/components/GrSortableList/chunks/{GrSortableList-DJFPdUVv.js.map → GrSortableList-DPjxilmN.js.map} +1 -1
- package/dist/components/GrSortableList/index.js +1 -1
- package/dist/components/GrStatistic/chunks/{GrStatistic-C3VpykHo.js → GrStatistic-DkzWUAw1.js} +8 -41
- package/dist/components/GrStatistic/chunks/GrStatistic-DkzWUAw1.js.map +1 -0
- package/dist/components/GrStatistic/index.js +1 -1
- package/dist/components/GrTabPanels/index.js +1 -1
- package/dist/components/GrTextarea/index.js +1 -1
- package/dist/components/GrTimeline/chunks/{GrTimeline-DlTraRn5.js → GrTimeline-Ch9sHzwL.js} +1 -1
- package/dist/components/GrTimeline/chunks/GrTimeline-Ch9sHzwL.js.map +1 -0
- package/dist/components/GrTimeline/index.js +2 -2
- package/dist/components/GrTimeline/styles.css +1 -1
- package/dist/components/GrToaster/chunks/{GrToaster-Db2NgrXu.js → GrToaster-DJUzx3sR.js} +1 -1
- package/dist/components/GrToaster/chunks/{GrToaster-Db2NgrXu.js.map → GrToaster-DJUzx3sR.js.map} +1 -1
- package/dist/components/GrToaster/index.js +2 -2
- package/dist/composables/useToast.js +14 -2
- package/dist/composables/useToast.js.map +1 -1
- 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 +38 -28
- package/dist/theme.js +1 -1
- package/dist/tokens.js +1 -1
- package/dist/types/components/GrButton/GrButton.vue.d.ts +1 -1
- package/dist/types/components/GrCard/GrCard.vue.d.ts +25 -6
- package/dist/types/components/GrCard/defaults.d.ts +2 -1
- package/dist/types/components/GrCard/grCardStyles.d.ts +8 -0
- package/dist/types/components/GrCard/index.d.ts +1 -1
- package/dist/types/components/GrCodeBlock/GrCodeBlock.vue.d.ts +79 -0
- package/dist/types/components/GrCodeBlock/config.d.ts +1 -0
- package/dist/types/components/GrCodeBlock/defaults.d.ts +21 -0
- package/dist/types/components/GrCodeBlock/grCodeBlockStyles.d.ts +53 -0
- package/dist/types/components/GrCodeBlock/index.d.ts +6 -0
- package/dist/types/components/GrCodeBlock/safelist.d.ts +1 -0
- package/dist/types/components/GrCodeBlock/serializeCode.d.ts +19 -0
- package/dist/types/components/GrCodeBlock/tokenizeJson.d.ts +24 -0
- package/dist/types/components/GrCollapse/GrCollapse.vue.d.ts +2 -2
- package/dist/types/components/GrCommandPalette/GrCommandPalette.vue.d.ts +2 -2
- package/dist/types/components/GrDataTable/GrDataTable.vue.d.ts +38 -1
- package/dist/types/components/GrDataTable/grDataTableStyles.d.ts +7 -0
- package/dist/types/components/GrDataTable/index.d.ts +1 -1
- package/dist/types/components/GrDelta/GrDelta.vue.d.ts +84 -0
- package/dist/types/components/GrDelta/config.d.ts +1 -0
- package/dist/types/components/GrDelta/defaults.d.ts +22 -0
- package/dist/types/components/GrDelta/deltaTone.d.ts +24 -0
- package/dist/types/components/GrDelta/grDeltaStyles.d.ts +38 -0
- package/dist/types/components/GrDelta/index.d.ts +6 -0
- package/dist/types/components/GrDelta/safelist.d.ts +1 -0
- package/dist/types/components/GrDescriptionList/GrDescriptionList.vue.d.ts +107 -0
- package/dist/types/components/GrDescriptionList/config.d.ts +1 -0
- package/dist/types/components/GrDescriptionList/defaults.d.ts +21 -0
- package/dist/types/components/GrDescriptionList/grDescriptionListStyles.d.ts +25 -0
- package/dist/types/components/GrDescriptionList/index.d.ts +6 -0
- package/dist/types/components/GrDescriptionList/safelist.d.ts +1 -0
- package/dist/types/components/GrDropdownMenu/GrDropdownMenu.vue.d.ts +1 -1
- package/dist/types/components/GrDropdownMenu/grDropdownMenuStyles.d.ts +10 -1
- package/dist/types/components/GrFilePreview/GrFilePreview.vue.d.ts +100 -0
- package/dist/types/components/GrFilePreview/config.d.ts +1 -0
- package/dist/types/components/GrFilePreview/defaults.d.ts +20 -0
- package/dist/types/components/GrFilePreview/fileKindOf.d.ts +14 -0
- package/dist/types/components/GrFilePreview/grFilePreviewStyles.d.ts +37 -0
- package/dist/types/components/GrFilePreview/index.d.ts +6 -0
- package/dist/types/components/GrFilePreview/safelist.d.ts +1 -0
- package/dist/types/components/GrForm/GrForm.vue.d.ts +47 -86
- package/dist/types/components/GrFormField/GrFormField.vue.d.ts +2 -2
- package/dist/types/components/GrImageViewer/GrImageViewer.vue.d.ts +1 -1
- package/dist/types/components/GrInput/GrInput.vue.d.ts +1 -1
- package/dist/types/components/GrInputTag/GrInputTag.vue.d.ts +1 -1
- package/dist/types/components/GrJsonViewer/GrJsonViewer.vue.d.ts +129 -0
- package/dist/types/components/GrJsonViewer/config.d.ts +1 -0
- package/dist/types/components/GrJsonViewer/defaults.d.ts +23 -0
- package/dist/types/components/GrJsonViewer/grJsonViewerStyles.d.ts +57 -0
- package/dist/types/components/GrJsonViewer/index.d.ts +9 -0
- package/dist/types/components/GrJsonViewer/jsonToNodes.d.ts +47 -0
- package/dist/types/components/GrJsonViewer/safelist.d.ts +1 -0
- package/dist/types/components/GrList/grListStyles.d.ts +13 -0
- package/dist/types/components/GrNumberInput/GrNumberInput.vue.d.ts +1 -1
- package/dist/types/components/GrRating/GrRating.vue.d.ts +1 -1
- package/dist/types/components/GrSegmented/GrSegmented.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 +1 -1
- package/dist/types/components/GrTable/GrTable.vue.d.ts +2 -2
- package/dist/types/components/GrTabs/GrTabs.vue.d.ts +1 -1
- package/dist/types/components/GrTimeline/GrTimelineItem.vue.d.ts +3 -3
- package/dist/types/components/shared/sizes.d.ts +5 -1
- package/dist/types/composables/useToast.d.ts +11 -0
- package/dist/types/granular-provider/shared.d.ts +5 -0
- package/dist/types/i18n/messages/en.d.ts +28 -0
- package/dist/types/i18n/messages/es.d.ts +28 -0
- package/dist/types/i18n/messages/ru.d.ts +28 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/web-types.json +661 -124
- package/package.json +22 -1
- package/dist/chunks/config-BDfTXHo12.js.map +0 -1
- package/dist/chunks/config-BEVBsvni.js.map +0 -1
- package/dist/chunks/config-BJ7Bv_G2.js.map +0 -1
- package/dist/chunks/config-BKvX0q10.js.map +0 -1
- package/dist/chunks/config-BTCOJO5z.js.map +0 -1
- package/dist/chunks/config-BsuDM3Hb.js.map +0 -1
- package/dist/chunks/config-COpuWGr_2.js.map +0 -1
- package/dist/chunks/config-DU2DNSRx2.js.map +0 -1
- package/dist/chunks/config-ScuvHw6k2.js.map +0 -1
- package/dist/chunks/config-eXB87DOi2.js.map +0 -1
- package/dist/chunks/config-gwro_7yn.js.map +0 -1
- package/dist/chunks/granular-provider-CZYm70Pm.js.map +0 -1
- package/dist/components/GrCard/chunks/GrCard-DfIZOP5N.js +0 -89
- package/dist/components/GrCard/chunks/GrCard-DfIZOP5N.js.map +0 -1
- package/dist/components/GrDataTable/chunks/GrDataTable-CNvZg2HZ.js.map +0 -1
- package/dist/components/GrForm/chunks/GrForm-DdJw23Pz.js.map +0 -1
- package/dist/components/GrList/chunks/GrList-CHibO0xA.js.map +0 -1
- package/dist/components/GrSegmented/chunks/GrSegmented-DP9WMAbm.js.map +0 -1
- package/dist/components/GrStatistic/chunks/GrStatistic-C3VpykHo.js.map +0 -1
- package/dist/components/GrTimeline/chunks/GrTimeline-DlTraRn5.js.map +0 -1
- package/dist/chunks/{config-BDfTXHo12.js → config-BDfTXHo1.js} +0 -0
- package/dist/chunks/{config-BEVBsvni.js → config-BEVBsvni2.js} +0 -0
- package/dist/chunks/{config-BTCOJO5z.js → config-BTCOJO5z2.js} +0 -0
- package/dist/chunks/{config-COpuWGr_2.js → config-COpuWGr_.js} +0 -0
- package/dist/chunks/{config-DU2DNSRx2.js → config-DU2DNSRx.js} +0 -0
- package/dist/chunks/{config-ScuvHw6k2.js → config-ScuvHw6k.js} +0 -0
- package/dist/chunks/{config-eXB87DOi2.js → config-eXB87DOi.js} +0 -0
- package/dist/components/GrImageViewer/chunks/{GrImageViewer-CMO10TS7.js → GrImageViewer-ng7el4wn.js} +1 -1
- package/dist/components/GrInputTag/chunks/{GrInputTag-Dghhg03-.js → GrInputTag-C7QVPijj.js} +1 -1
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { t as GrRadio_default } from "./chunks/GrRadio-BdhknbO8.js";
|
|
2
|
-
import { n as grRadioSafelist, t as grRadioConfig } from "../../chunks/config-
|
|
2
|
+
import { n as grRadioSafelist, t as grRadioConfig } from "../../chunks/config-BTCOJO5z2.js";
|
|
3
3
|
export { GrRadio_default as GrRadio, GrRadio_default as default, grRadioConfig, grRadioSafelist };
|
package/dist/components/GrSegmented/chunks/{GrSegmented-DP9WMAbm.js → GrSegmented-D1mjxF_O.js}
RENAMED
|
@@ -4,7 +4,7 @@ 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
6
|
import { useRovingFocus } from "../../../composables/useRovingFocus.js";
|
|
7
|
-
import { a as grSegmentedItemIconClass, c as grSegmentedRootClass, i as grSegmentedItemClass, l as grSegmentedRootStyle, o as grSegmentedItemLabelClass, r as grSegmentedIndicatorClass, s as grSegmentedItemSpinnerClass } from "../../../chunks/config-
|
|
7
|
+
import { a as grSegmentedItemIconClass, c as grSegmentedRootClass, i as grSegmentedItemClass, l as grSegmentedRootStyle, o as grSegmentedItemLabelClass, r as grSegmentedIndicatorClass, s as grSegmentedItemSpinnerClass } from "../../../chunks/config-BEVBsvni2.js";
|
|
8
8
|
import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, defineComponent, nextTick, normalizeClass, normalizeStyle, onBeforeUnmount, onMounted, openBlock, ref, renderList, renderSlot, resolveDynamicComponent, toDisplayString, unref, useId, watch } from "vue";
|
|
9
9
|
//#region src/components/GrSegmented/GrSegmented.vue?vue&type=script&setup=true&lang.ts
|
|
10
10
|
var _hoisted_1 = [
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GrSegmented-D1mjxF_O.js","names":[],"sources":["../../../../src/components/GrSegmented/GrSegmented.vue","../../../../src/components/GrSegmented/GrSegmented.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, nextTick, onBeforeUnmount, onMounted, ref, useId, watch } from 'vue'\n\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\nimport { useGrFormFieldContext } from '../GrFormField/context'\nimport { useGrFormControl } from '../../composables/useGrFormControl'\nimport { useRovingFocus } from '../../composables/useRovingFocus'\nimport { useFocusWithin } from '../../composables/internal/useFocusWithin'\n\nimport type { ComponentPublicInstance } from 'vue'\nimport IconLoader from '~icons/lucide/loader-circle'\n\nimport {\n grSegmentedIndicatorClass,\n grSegmentedItemClass,\n grSegmentedItemIconClass,\n grSegmentedItemLabelClass,\n grSegmentedItemSpinnerClass,\n grSegmentedRootClass,\n grSegmentedRootStyle,\n type GrSegmentedOption,\n type GrSegmentedOrientation,\n type GrSegmentedSize,\n type GrSegmentedValue,\n type GrSegmentedVariant,\n} from './grSegmentedStyles'\n\ntype IndicatorGeometry = {\n width: number\n height: number\n x: number\n y: number\n}\n\n/**\n * Пропсы публичного GR-примитива «Segmented».\n */\nexport interface GrSegmentedProps {\n modelValue: GrSegmentedValue\n options: GrSegmentedOption[]\n variant?: GrSegmentedVariant\n size?: GrSegmentedSize\n /**\n * Направление ряда. Вертикаль — боковые фильтры; индикатор к ней готов по\n * построению, он двумерный.\n */\n orientation?: GrSegmentedOrientation\n /** Длительность анимации индикатора в мс. */\n indicatorDuration?: number\n /** Растягивать сегмент на всю ширину контейнера. */\n block?: boolean\n disabled?: boolean\n /** Только для чтения: выбор видно, но он не меняется. */\n readonly?: boolean\n /** Визуальное и ARIA-состояние ошибки. */\n invalid?: boolean\n /** Обязательное поле (`aria-required`). */\n required?: boolean\n /** Имя скрытого поля, которым выбранное значение уходит в нативную форму. */\n name?: string\n ariaLabel?: string\n}\n\nexport interface GrSegmentedEmits {\n (e: 'update:modelValue', value: GrSegmentedValue): void\n (e: 'change', value: GrSegmentedValue, option: GrSegmentedOption): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n}\n\nconst props = withDefaults(\n defineProps<GrSegmentedProps>(),\n {\n variant: undefined,\n size: undefined,\n orientation: 'horizontal',\n indicatorDuration: 300,\n block: false,\n disabled: false,\n readonly: false,\n invalid: false,\n required: false,\n name: undefined,\n ariaLabel: undefined,\n },\n)\n\n// Эффективный размер: локальный проп → `GrConfigProvider` → дефолт компонента.\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrSegmented' })\nconst resolvedVariant = useGrComponentProp('GrSegmented', 'variant', () => props.variant, 'pills')\n\nconst emit = defineEmits<GrSegmentedEmits>()\ndefineSlots<{\n /** Содержимое сегмента вместо подписи из `options`. */\n default?: (props: {\n option: GrSegmentedOption\n selected: boolean\n disabled: boolean\n loading: boolean\n }) => any\n}>()\n\n\n// `useId()` стабилен между сервером и клиентом, в отличие от `instance.uid`.\nconst fallbackName = `gr-segmented-${useId()}`\n\n// Контекст `GrFormField` + общий контракт форм-контрола.\nconst field = useGrFormFieldContext()\nconst fieldId = computed(() => field?.id.value)\nconst describedBy = computed(() => field?.describedById.value)\nconst labelledBy = computed(() => (props.ariaLabel ? undefined : field?.labelId.value))\nconst {\n disabled: isDisabled,\n invalid: isInvalid, required: isRequired, readonly: isReadonly } = useGrFormControl(() => props)\n\nconst rootRef = ref<HTMLElement | null>(null)\n\n// Фокус ходит между сегментами: без границы каждая стрелка давала бы\n// потребителю пару `blur` + `focus`.\nconst { onFocusIn, onFocusOut } = useFocusWithin(rootRef, {\n enter: event => emit('focus', event),\n leave: event => emit('blur', event),\n})\n\ndefineExpose({ focus, blur })\nconst itemRefs = ref(new Map<string, HTMLElement>())\nconst indicatorGeometry = ref<IndicatorGeometry | null>(null)\nconst indicatorReady = ref(false)\n\nlet resizeObserver: ResizeObserver | null = null\nlet observedRoot: HTMLElement | null = null\nlet observedItem: HTMLElement | null = null\nlet scheduled = false\n\nconst selectedIndex = computed(() => props.options.findIndex(option => option.value === props.modelValue))\nconst selectedOption = computed(() => props.options[selectedIndex.value] ?? null)\nconst indicatorDuration = computed(() => Math.max(0, Math.round(props.indicatorDuration)))\nconst isVertical = computed(() => props.orientation === 'vertical')\nconst rootClassName = computed(() => grSegmentedRootClass({\n variant: resolvedVariant.value,\n orientation: props.orientation,\n block: props.block,\n disabled: isDisabled.value,\n}))\n\n/**\n * Раскладка задаётся треками, а не классами: число сегментов известно только в\n * рантайме. По вертикали колонка одна — сегменты одинаковой ширины по\n * построению, и `block` решает лишь, занимать ли ширину контейнера.\n */\nconst rootStyle = computed<Record<string, string>>(() => {\n const empty = props.options.length === 0\n const track = () => props.block && !isVertical.value ? 'minmax(0,1fr)' : 'minmax(0,max-content)'\n\n return {\n ...grSegmentedRootStyle({\n variant: resolvedVariant.value,\n size: resolvedSize.value,\n orientation: props.orientation,\n }),\n gridTemplateColumns: isVertical.value\n ? 'minmax(0,1fr)'\n : empty ? 'none' : props.options.map(track).join(' '),\n ...(isVertical.value\n ? { gridTemplateRows: empty ? 'none' : props.options.map(track).join(' ') }\n : {}),\n }\n})\nconst indicatorClassName = computed(() => grSegmentedIndicatorClass(resolvedVariant.value))\nconst resolvedName = computed(() => props.name ?? fallbackName)\nconst hasIndicator = computed(() => selectedIndex.value !== -1 && indicatorGeometry.value !== null)\n\nconst indicatorStyle = computed<Record<string, string>>(() => {\n const geometry = indicatorGeometry.value\n if (!geometry) {\n return {\n opacity: '0',\n width: '0px',\n height: '0px',\n transitionDuration: `${indicatorDuration.value}ms`,\n transform: 'translate3d(0px, 0px, 0px)',\n }\n }\n\n return {\n opacity: indicatorReady.value ? '1' : '0',\n width: `${geometry.width}px`,\n height: `${geometry.height}px`,\n transitionDuration: `${indicatorDuration.value}ms`,\n transform: `translate3d(${geometry.x}px, ${geometry.y}px, 0)`,\n }\n})\n\nfunction getOptionKey(value: GrSegmentedValue): string {\n return String(value)\n}\n\nfunction resolveOptionDisabled(option: GrSegmentedOption): boolean {\n return isDisabled.value || Boolean(option.disabled)\n}\n\n/**\n * Занятый сегмент не «недоступен», а работает: нативный `disabled` и\n * `aria-disabled` ему не ставим — состояние несёт `aria-busy`. Но выбор он не\n * принимает, поэтому блокировка считается отдельно от `disabled`.\n */\nfunction isOptionBusy(option: GrSegmentedOption): boolean {\n return Boolean(option.loading)\n}\n\nfunction isOptionBlocked(option: GrSegmentedOption): boolean {\n return resolveOptionDisabled(option) || isOptionBusy(option)\n}\n\nfunction isOptionSelected(option: GrSegmentedOption): boolean {\n return option.value === props.modelValue\n}\n\nfunction isIconOnlyOption(option: GrSegmentedOption): boolean {\n return Boolean(option.icon) && !option.label\n}\n\nfunction setItemRef(value: GrSegmentedValue, element: Element | ComponentPublicInstance | null): void {\n const key = getOptionKey(value)\n\n if (!(element instanceof HTMLElement)) {\n itemRefs.value.delete(key)\n return\n }\n\n itemRefs.value.set(key, element)\n}\n\nfunction isSameGeometry(left: IndicatorGeometry | null, right: IndicatorGeometry | null): boolean {\n if (left === right) {\n return true\n }\n\n if (!left || !right) {\n return false\n }\n\n return left.width === right.width\n && left.height === right.height\n && left.x === right.x\n && left.y === right.y\n}\n\nfunction measureIndicator(): void {\n const root = rootRef.value\n const option = selectedOption.value\n if (!root || !option) {\n if (indicatorGeometry.value !== null)\n indicatorGeometry.value = null\n\n if (indicatorReady.value)\n indicatorReady.value = false\n\n return\n }\n\n const selectedItem = itemRefs.value.get(getOptionKey(option.value))\n if (!selectedItem) {\n return\n }\n\n const rootRect = root.getBoundingClientRect()\n const itemRect = selectedItem.getBoundingClientRect()\n\n const nextGeometry = {\n width: Math.max(Math.round(itemRect.width), 0),\n height: Math.max(Math.round(itemRect.height), 0),\n x: Math.round(itemRect.left - rootRect.left),\n y: Math.max(Math.round(itemRect.top - rootRect.top) - 1, 0),\n } satisfies IndicatorGeometry\n\n if (!isSameGeometry(indicatorGeometry.value, nextGeometry)) {\n indicatorGeometry.value = nextGeometry\n }\n\n const nextReady = nextGeometry.width > 0 && nextGeometry.height > 0\n if (indicatorReady.value !== nextReady) {\n indicatorReady.value = nextReady\n }\n}\n\nfunction refreshObserver(): void {\n if (typeof ResizeObserver === 'undefined') {\n return\n }\n\n const root = rootRef.value\n const option = selectedOption.value\n const selectedItem = option\n ? itemRefs.value.get(getOptionKey(option.value)) ?? null\n : null\n\n if (!resizeObserver) {\n resizeObserver = new ResizeObserver(() => scheduleMeasure())\n }\n\n if (!root) {\n resizeObserver.disconnect()\n observedRoot = null\n observedItem = null\n return\n }\n\n if (observedRoot === root && observedItem === selectedItem) {\n return\n }\n\n resizeObserver.disconnect()\n resizeObserver.observe(root)\n\n if (selectedItem) {\n resizeObserver.observe(selectedItem)\n }\n\n observedRoot = root\n observedItem = selectedItem\n}\n\nfunction scheduleMeasure(): void {\n if (scheduled) {\n return\n }\n\n scheduled = true\n void nextTick(() => {\n scheduled = false\n measureIndicator()\n refreshObserver()\n })\n}\n\nfunction emitValue(option: GrSegmentedOption): void {\n // Один guard на все пути: проверяй `readonly` только клавиатура, и клик по\n // сегменту менял бы значение вопреки заявленному контракту.\n if (isReadonly.value || isOptionBlocked(option) || option.value === props.modelValue) {\n return\n }\n\n emit('update:modelValue', option.value)\n emit('change', option.value, option)\n}\n\nfunction optionOf(value: GrSegmentedValue): GrSegmentedOption | undefined {\n return props.options.find(option => option.value === value)\n}\n\n/**\n * Кольцо roving-фокуса. Обе оси работают всегда: `orientation` у сегмента\n * описывает раскладку и уходит в `aria-orientation`, а не сужает клавиатуру —\n * вертикальный набор листается и стрелками влево/вправо.\n */\nconst roving = useRovingFocus<GrSegmentedValue>({\n items: () => props.options.map(option => option.value),\n elementFor: value => itemRefs.value.get(getOptionKey(value)),\n isDisabled: (value) => {\n const option = optionOf(value)\n return option ? isOptionBlocked(option) : true\n },\n orientation: () => 'both',\n skipDisabled: () => true,\n /**\n * Выбранный сегмент держит остановку `Tab` — кроме выключенного: на нём\n * стоит нативный `disabled`, и фокус на него не встанет вовсе. Загружающийся\n * остановку сохраняет: он не «недоступен», а занят.\n */\n initialKey: () => {\n const selected = selectedOption.value\n return selected && !resolveOptionDisabled(selected) ? selected.value : undefined\n },\n // Стрелка в `radiogroup` переносит и выбор, а не только фокус.\n onMove: (value) => {\n const option = optionOf(value)\n if (option) emitValue(option)\n },\n})\n\nfunction onItemClick(option: GrSegmentedOption): void {\n emitValue(option)\n // Клик тоже переносит остановку `Tab`: иначе она осталась бы там, куда её\n // увела последняя стрелка.\n if (!isOptionBlocked(option)) roving.setActive(option.value)\n}\n\n/** Императивный фокус ведёт на текущую остановку `Tab`, а не на первый сегмент. */\nfunction rovingElement(): HTMLElement | undefined {\n const key = roving.rovingKey.value\n return key === undefined ? undefined : itemRefs.value.get(getOptionKey(key))\n}\n\nfunction focus(): void {\n rovingElement()?.focus()\n}\n\nfunction blur(): void {\n rovingElement()?.blur()\n}\n\nfunction onKeydown(event: KeyboardEvent, index: number): void {\n if (isDisabled.value || isReadonly.value) {\n return\n }\n\n if (roving.handleNavigationKeys(event)) {\n return\n }\n\n if (event.key === ' ' || event.key === 'Enter') {\n event.preventDefault()\n const option = props.options[index]\n if (option) {\n emitValue(option)\n }\n }\n}\n\nwatch(() => props.modelValue, () => scheduleMeasure())\nwatch(() => props.options, () => scheduleMeasure(), { deep: true })\nwatch(resolvedVariant, () => scheduleMeasure())\nwatch(resolvedSize, () => scheduleMeasure())\nwatch(() => props.block, () => scheduleMeasure())\n// Смена ориентации переставляет сегменты: без пересчёта индикатор остался бы в\n// координатах прежней раскладки.\nwatch(() => props.orientation, () => scheduleMeasure())\n\nonMounted(() => scheduleMeasure())\nonBeforeUnmount(() => {\n resizeObserver?.disconnect()\n observedRoot = null\n observedItem = null\n})\n</script>\n\n<template>\n <div\n :id=\"fieldId\"\n ref=\"rootRef\"\n data-gr-segmented\n :data-variant=\"resolvedVariant\"\n role=\"radiogroup\"\n :aria-orientation=\"orientation\"\n :aria-label=\"ariaLabel\"\n :aria-labelledby=\"labelledBy\"\n :aria-describedby=\"describedBy\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n :aria-required=\"isRequired ? 'true' : undefined\"\n :aria-readonly=\"isReadonly ? 'true' : undefined\"\n :aria-disabled=\"isDisabled ? 'true' : undefined\"\n :class=\"rootClassName\"\n :style=\"rootStyle\"\n @focusin=\"onFocusIn\"\n @focusout=\"onFocusOut\"\n >\n <!-- Значение для нативной формы уходит одним скрытым полем рядом с\n сегментами, а не вложенным в каждый `role=\"radio\"`: роль объявляет своих\n потомков презентационными, и вложенный интерактивный контрол ломает\n виджет для скринридеров (axe: `nested-interactive`). Скрытое поле не\n фокусируется и интерактивным не считается. -->\n <input\n v-if=\"selectedOption && !resolveOptionDisabled(selectedOption)\"\n type=\"hidden\"\n :name=\"resolvedName\"\n :value=\"String(selectedOption.value)\"\n >\n\n <span\n v-if=\"hasIndicator\"\n data-gr-segmented-indicator\n aria-hidden=\"true\"\n :class=\"indicatorClassName\"\n :style=\"indicatorStyle\"\n />\n\n <button\n v-for=\"(option, index) in options\"\n :key=\"option.value\"\n :ref=\"element => setItemRef(option.value, element)\"\n data-gr-segmented-item\n :data-value=\"String(option.value)\"\n type=\"button\"\n role=\"radio\"\n :aria-label=\"option.ariaLabel\"\n :aria-checked=\"isOptionSelected(option) ? 'true' : 'false'\"\n :aria-disabled=\"resolveOptionDisabled(option) ? 'true' : undefined\"\n :aria-busy=\"isOptionBusy(option) ? 'true' : undefined\"\n :disabled=\"resolveOptionDisabled(option)\"\n :tabindex=\"roving.tabindexFor(option.value)\"\n :class=\"grSegmentedItemClass({\n variant: resolvedVariant,\n selected: isOptionSelected(option),\n disabled: resolveOptionDisabled(option),\n iconOnly: isIconOnlyOption(option),\n })\"\n @click=\"onItemClick(option)\"\n @keydown=\"onKeydown($event, index)\"\n >\n <slot\n :option=\"option\"\n :selected=\"isOptionSelected(option)\"\n :disabled=\"resolveOptionDisabled(option)\"\n :loading=\"isOptionBusy(option)\"\n >\n <!-- Спиннер занимает место иконки: две крутилки рядом читались бы как ошибка. -->\n <IconLoader\n v-if=\"isOptionBusy(option)\"\n data-gr-segmented-spinner\n aria-hidden=\"true\"\n :class=\"grSegmentedItemSpinnerClass\"\n />\n <component\n :is=\"option.icon\"\n v-else-if=\"option.icon\"\n aria-hidden=\"true\"\n :class=\"grSegmentedItemIconClass\"\n />\n <span v-if=\"option.label\" :class=\"grSegmentedItemLabelClass\">\n {{ option.label }}\n </span>\n </slot>\n </button>\n </div>\n</template>","<script setup lang=\"ts\">\nimport { computed, nextTick, onBeforeUnmount, onMounted, ref, useId, watch } from 'vue'\n\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\nimport { useGrFormFieldContext } from '../GrFormField/context'\nimport { useGrFormControl } from '../../composables/useGrFormControl'\nimport { useRovingFocus } from '../../composables/useRovingFocus'\nimport { useFocusWithin } from '../../composables/internal/useFocusWithin'\n\nimport type { ComponentPublicInstance } from 'vue'\nimport IconLoader from '~icons/lucide/loader-circle'\n\nimport {\n grSegmentedIndicatorClass,\n grSegmentedItemClass,\n grSegmentedItemIconClass,\n grSegmentedItemLabelClass,\n grSegmentedItemSpinnerClass,\n grSegmentedRootClass,\n grSegmentedRootStyle,\n type GrSegmentedOption,\n type GrSegmentedOrientation,\n type GrSegmentedSize,\n type GrSegmentedValue,\n type GrSegmentedVariant,\n} from './grSegmentedStyles'\n\ntype IndicatorGeometry = {\n width: number\n height: number\n x: number\n y: number\n}\n\n/**\n * Пропсы публичного GR-примитива «Segmented».\n */\nexport interface GrSegmentedProps {\n modelValue: GrSegmentedValue\n options: GrSegmentedOption[]\n variant?: GrSegmentedVariant\n size?: GrSegmentedSize\n /**\n * Направление ряда. Вертикаль — боковые фильтры; индикатор к ней готов по\n * построению, он двумерный.\n */\n orientation?: GrSegmentedOrientation\n /** Длительность анимации индикатора в мс. */\n indicatorDuration?: number\n /** Растягивать сегмент на всю ширину контейнера. */\n block?: boolean\n disabled?: boolean\n /** Только для чтения: выбор видно, но он не меняется. */\n readonly?: boolean\n /** Визуальное и ARIA-состояние ошибки. */\n invalid?: boolean\n /** Обязательное поле (`aria-required`). */\n required?: boolean\n /** Имя скрытого поля, которым выбранное значение уходит в нативную форму. */\n name?: string\n ariaLabel?: string\n}\n\nexport interface GrSegmentedEmits {\n (e: 'update:modelValue', value: GrSegmentedValue): void\n (e: 'change', value: GrSegmentedValue, option: GrSegmentedOption): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n}\n\nconst props = withDefaults(\n defineProps<GrSegmentedProps>(),\n {\n variant: undefined,\n size: undefined,\n orientation: 'horizontal',\n indicatorDuration: 300,\n block: false,\n disabled: false,\n readonly: false,\n invalid: false,\n required: false,\n name: undefined,\n ariaLabel: undefined,\n },\n)\n\n// Эффективный размер: локальный проп → `GrConfigProvider` → дефолт компонента.\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrSegmented' })\nconst resolvedVariant = useGrComponentProp('GrSegmented', 'variant', () => props.variant, 'pills')\n\nconst emit = defineEmits<GrSegmentedEmits>()\ndefineSlots<{\n /** Содержимое сегмента вместо подписи из `options`. */\n default?: (props: {\n option: GrSegmentedOption\n selected: boolean\n disabled: boolean\n loading: boolean\n }) => any\n}>()\n\n\n// `useId()` стабилен между сервером и клиентом, в отличие от `instance.uid`.\nconst fallbackName = `gr-segmented-${useId()}`\n\n// Контекст `GrFormField` + общий контракт форм-контрола.\nconst field = useGrFormFieldContext()\nconst fieldId = computed(() => field?.id.value)\nconst describedBy = computed(() => field?.describedById.value)\nconst labelledBy = computed(() => (props.ariaLabel ? undefined : field?.labelId.value))\nconst {\n disabled: isDisabled,\n invalid: isInvalid, required: isRequired, readonly: isReadonly } = useGrFormControl(() => props)\n\nconst rootRef = ref<HTMLElement | null>(null)\n\n// Фокус ходит между сегментами: без границы каждая стрелка давала бы\n// потребителю пару `blur` + `focus`.\nconst { onFocusIn, onFocusOut } = useFocusWithin(rootRef, {\n enter: event => emit('focus', event),\n leave: event => emit('blur', event),\n})\n\ndefineExpose({ focus, blur })\nconst itemRefs = ref(new Map<string, HTMLElement>())\nconst indicatorGeometry = ref<IndicatorGeometry | null>(null)\nconst indicatorReady = ref(false)\n\nlet resizeObserver: ResizeObserver | null = null\nlet observedRoot: HTMLElement | null = null\nlet observedItem: HTMLElement | null = null\nlet scheduled = false\n\nconst selectedIndex = computed(() => props.options.findIndex(option => option.value === props.modelValue))\nconst selectedOption = computed(() => props.options[selectedIndex.value] ?? null)\nconst indicatorDuration = computed(() => Math.max(0, Math.round(props.indicatorDuration)))\nconst isVertical = computed(() => props.orientation === 'vertical')\nconst rootClassName = computed(() => grSegmentedRootClass({\n variant: resolvedVariant.value,\n orientation: props.orientation,\n block: props.block,\n disabled: isDisabled.value,\n}))\n\n/**\n * Раскладка задаётся треками, а не классами: число сегментов известно только в\n * рантайме. По вертикали колонка одна — сегменты одинаковой ширины по\n * построению, и `block` решает лишь, занимать ли ширину контейнера.\n */\nconst rootStyle = computed<Record<string, string>>(() => {\n const empty = props.options.length === 0\n const track = () => props.block && !isVertical.value ? 'minmax(0,1fr)' : 'minmax(0,max-content)'\n\n return {\n ...grSegmentedRootStyle({\n variant: resolvedVariant.value,\n size: resolvedSize.value,\n orientation: props.orientation,\n }),\n gridTemplateColumns: isVertical.value\n ? 'minmax(0,1fr)'\n : empty ? 'none' : props.options.map(track).join(' '),\n ...(isVertical.value\n ? { gridTemplateRows: empty ? 'none' : props.options.map(track).join(' ') }\n : {}),\n }\n})\nconst indicatorClassName = computed(() => grSegmentedIndicatorClass(resolvedVariant.value))\nconst resolvedName = computed(() => props.name ?? fallbackName)\nconst hasIndicator = computed(() => selectedIndex.value !== -1 && indicatorGeometry.value !== null)\n\nconst indicatorStyle = computed<Record<string, string>>(() => {\n const geometry = indicatorGeometry.value\n if (!geometry) {\n return {\n opacity: '0',\n width: '0px',\n height: '0px',\n transitionDuration: `${indicatorDuration.value}ms`,\n transform: 'translate3d(0px, 0px, 0px)',\n }\n }\n\n return {\n opacity: indicatorReady.value ? '1' : '0',\n width: `${geometry.width}px`,\n height: `${geometry.height}px`,\n transitionDuration: `${indicatorDuration.value}ms`,\n transform: `translate3d(${geometry.x}px, ${geometry.y}px, 0)`,\n }\n})\n\nfunction getOptionKey(value: GrSegmentedValue): string {\n return String(value)\n}\n\nfunction resolveOptionDisabled(option: GrSegmentedOption): boolean {\n return isDisabled.value || Boolean(option.disabled)\n}\n\n/**\n * Занятый сегмент не «недоступен», а работает: нативный `disabled` и\n * `aria-disabled` ему не ставим — состояние несёт `aria-busy`. Но выбор он не\n * принимает, поэтому блокировка считается отдельно от `disabled`.\n */\nfunction isOptionBusy(option: GrSegmentedOption): boolean {\n return Boolean(option.loading)\n}\n\nfunction isOptionBlocked(option: GrSegmentedOption): boolean {\n return resolveOptionDisabled(option) || isOptionBusy(option)\n}\n\nfunction isOptionSelected(option: GrSegmentedOption): boolean {\n return option.value === props.modelValue\n}\n\nfunction isIconOnlyOption(option: GrSegmentedOption): boolean {\n return Boolean(option.icon) && !option.label\n}\n\nfunction setItemRef(value: GrSegmentedValue, element: Element | ComponentPublicInstance | null): void {\n const key = getOptionKey(value)\n\n if (!(element instanceof HTMLElement)) {\n itemRefs.value.delete(key)\n return\n }\n\n itemRefs.value.set(key, element)\n}\n\nfunction isSameGeometry(left: IndicatorGeometry | null, right: IndicatorGeometry | null): boolean {\n if (left === right) {\n return true\n }\n\n if (!left || !right) {\n return false\n }\n\n return left.width === right.width\n && left.height === right.height\n && left.x === right.x\n && left.y === right.y\n}\n\nfunction measureIndicator(): void {\n const root = rootRef.value\n const option = selectedOption.value\n if (!root || !option) {\n if (indicatorGeometry.value !== null)\n indicatorGeometry.value = null\n\n if (indicatorReady.value)\n indicatorReady.value = false\n\n return\n }\n\n const selectedItem = itemRefs.value.get(getOptionKey(option.value))\n if (!selectedItem) {\n return\n }\n\n const rootRect = root.getBoundingClientRect()\n const itemRect = selectedItem.getBoundingClientRect()\n\n const nextGeometry = {\n width: Math.max(Math.round(itemRect.width), 0),\n height: Math.max(Math.round(itemRect.height), 0),\n x: Math.round(itemRect.left - rootRect.left),\n y: Math.max(Math.round(itemRect.top - rootRect.top) - 1, 0),\n } satisfies IndicatorGeometry\n\n if (!isSameGeometry(indicatorGeometry.value, nextGeometry)) {\n indicatorGeometry.value = nextGeometry\n }\n\n const nextReady = nextGeometry.width > 0 && nextGeometry.height > 0\n if (indicatorReady.value !== nextReady) {\n indicatorReady.value = nextReady\n }\n}\n\nfunction refreshObserver(): void {\n if (typeof ResizeObserver === 'undefined') {\n return\n }\n\n const root = rootRef.value\n const option = selectedOption.value\n const selectedItem = option\n ? itemRefs.value.get(getOptionKey(option.value)) ?? null\n : null\n\n if (!resizeObserver) {\n resizeObserver = new ResizeObserver(() => scheduleMeasure())\n }\n\n if (!root) {\n resizeObserver.disconnect()\n observedRoot = null\n observedItem = null\n return\n }\n\n if (observedRoot === root && observedItem === selectedItem) {\n return\n }\n\n resizeObserver.disconnect()\n resizeObserver.observe(root)\n\n if (selectedItem) {\n resizeObserver.observe(selectedItem)\n }\n\n observedRoot = root\n observedItem = selectedItem\n}\n\nfunction scheduleMeasure(): void {\n if (scheduled) {\n return\n }\n\n scheduled = true\n void nextTick(() => {\n scheduled = false\n measureIndicator()\n refreshObserver()\n })\n}\n\nfunction emitValue(option: GrSegmentedOption): void {\n // Один guard на все пути: проверяй `readonly` только клавиатура, и клик по\n // сегменту менял бы значение вопреки заявленному контракту.\n if (isReadonly.value || isOptionBlocked(option) || option.value === props.modelValue) {\n return\n }\n\n emit('update:modelValue', option.value)\n emit('change', option.value, option)\n}\n\nfunction optionOf(value: GrSegmentedValue): GrSegmentedOption | undefined {\n return props.options.find(option => option.value === value)\n}\n\n/**\n * Кольцо roving-фокуса. Обе оси работают всегда: `orientation` у сегмента\n * описывает раскладку и уходит в `aria-orientation`, а не сужает клавиатуру —\n * вертикальный набор листается и стрелками влево/вправо.\n */\nconst roving = useRovingFocus<GrSegmentedValue>({\n items: () => props.options.map(option => option.value),\n elementFor: value => itemRefs.value.get(getOptionKey(value)),\n isDisabled: (value) => {\n const option = optionOf(value)\n return option ? isOptionBlocked(option) : true\n },\n orientation: () => 'both',\n skipDisabled: () => true,\n /**\n * Выбранный сегмент держит остановку `Tab` — кроме выключенного: на нём\n * стоит нативный `disabled`, и фокус на него не встанет вовсе. Загружающийся\n * остановку сохраняет: он не «недоступен», а занят.\n */\n initialKey: () => {\n const selected = selectedOption.value\n return selected && !resolveOptionDisabled(selected) ? selected.value : undefined\n },\n // Стрелка в `radiogroup` переносит и выбор, а не только фокус.\n onMove: (value) => {\n const option = optionOf(value)\n if (option) emitValue(option)\n },\n})\n\nfunction onItemClick(option: GrSegmentedOption): void {\n emitValue(option)\n // Клик тоже переносит остановку `Tab`: иначе она осталась бы там, куда её\n // увела последняя стрелка.\n if (!isOptionBlocked(option)) roving.setActive(option.value)\n}\n\n/** Императивный фокус ведёт на текущую остановку `Tab`, а не на первый сегмент. */\nfunction rovingElement(): HTMLElement | undefined {\n const key = roving.rovingKey.value\n return key === undefined ? undefined : itemRefs.value.get(getOptionKey(key))\n}\n\nfunction focus(): void {\n rovingElement()?.focus()\n}\n\nfunction blur(): void {\n rovingElement()?.blur()\n}\n\nfunction onKeydown(event: KeyboardEvent, index: number): void {\n if (isDisabled.value || isReadonly.value) {\n return\n }\n\n if (roving.handleNavigationKeys(event)) {\n return\n }\n\n if (event.key === ' ' || event.key === 'Enter') {\n event.preventDefault()\n const option = props.options[index]\n if (option) {\n emitValue(option)\n }\n }\n}\n\nwatch(() => props.modelValue, () => scheduleMeasure())\nwatch(() => props.options, () => scheduleMeasure(), { deep: true })\nwatch(resolvedVariant, () => scheduleMeasure())\nwatch(resolvedSize, () => scheduleMeasure())\nwatch(() => props.block, () => scheduleMeasure())\n// Смена ориентации переставляет сегменты: без пересчёта индикатор остался бы в\n// координатах прежней раскладки.\nwatch(() => props.orientation, () => scheduleMeasure())\n\nonMounted(() => scheduleMeasure())\nonBeforeUnmount(() => {\n resizeObserver?.disconnect()\n observedRoot = null\n observedItem = null\n})\n</script>\n\n<template>\n <div\n :id=\"fieldId\"\n ref=\"rootRef\"\n data-gr-segmented\n :data-variant=\"resolvedVariant\"\n role=\"radiogroup\"\n :aria-orientation=\"orientation\"\n :aria-label=\"ariaLabel\"\n :aria-labelledby=\"labelledBy\"\n :aria-describedby=\"describedBy\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n :aria-required=\"isRequired ? 'true' : undefined\"\n :aria-readonly=\"isReadonly ? 'true' : undefined\"\n :aria-disabled=\"isDisabled ? 'true' : undefined\"\n :class=\"rootClassName\"\n :style=\"rootStyle\"\n @focusin=\"onFocusIn\"\n @focusout=\"onFocusOut\"\n >\n <!-- Значение для нативной формы уходит одним скрытым полем рядом с\n сегментами, а не вложенным в каждый `role=\"radio\"`: роль объявляет своих\n потомков презентационными, и вложенный интерактивный контрол ломает\n виджет для скринридеров (axe: `nested-interactive`). Скрытое поле не\n фокусируется и интерактивным не считается. -->\n <input\n v-if=\"selectedOption && !resolveOptionDisabled(selectedOption)\"\n type=\"hidden\"\n :name=\"resolvedName\"\n :value=\"String(selectedOption.value)\"\n >\n\n <span\n v-if=\"hasIndicator\"\n data-gr-segmented-indicator\n aria-hidden=\"true\"\n :class=\"indicatorClassName\"\n :style=\"indicatorStyle\"\n />\n\n <button\n v-for=\"(option, index) in options\"\n :key=\"option.value\"\n :ref=\"element => setItemRef(option.value, element)\"\n data-gr-segmented-item\n :data-value=\"String(option.value)\"\n type=\"button\"\n role=\"radio\"\n :aria-label=\"option.ariaLabel\"\n :aria-checked=\"isOptionSelected(option) ? 'true' : 'false'\"\n :aria-disabled=\"resolveOptionDisabled(option) ? 'true' : undefined\"\n :aria-busy=\"isOptionBusy(option) ? 'true' : undefined\"\n :disabled=\"resolveOptionDisabled(option)\"\n :tabindex=\"roving.tabindexFor(option.value)\"\n :class=\"grSegmentedItemClass({\n variant: resolvedVariant,\n selected: isOptionSelected(option),\n disabled: resolveOptionDisabled(option),\n iconOnly: isIconOnlyOption(option),\n })\"\n @click=\"onItemClick(option)\"\n @keydown=\"onKeydown($event, index)\"\n >\n <slot\n :option=\"option\"\n :selected=\"isOptionSelected(option)\"\n :disabled=\"resolveOptionDisabled(option)\"\n :loading=\"isOptionBusy(option)\"\n >\n <!-- Спиннер занимает место иконки: две крутилки рядом читались бы как ошибка. -->\n <IconLoader\n v-if=\"isOptionBusy(option)\"\n data-gr-segmented-spinner\n aria-hidden=\"true\"\n :class=\"grSegmentedItemSpinnerClass\"\n />\n <component\n :is=\"option.icon\"\n v-else-if=\"option.icon\"\n aria-hidden=\"true\"\n :class=\"grSegmentedItemIconClass\"\n />\n <span v-if=\"option.label\" :class=\"grSegmentedItemLabelClass\">\n {{ option.label }}\n </span>\n </slot>\n </button>\n </div>\n</template>"],"mappings":"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"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { n as grSegmentedSafelist, t as grSegmentedConfig } from "../../chunks/config-
|
|
2
|
-
import { t as GrSegmented_default } from "./chunks/GrSegmented-
|
|
1
|
+
import { n as grSegmentedSafelist, t as grSegmentedConfig } from "../../chunks/config-BEVBsvni2.js";
|
|
2
|
+
import { t as GrSegmented_default } from "./chunks/GrSegmented-D1mjxF_O.js";
|
|
3
3
|
export { GrSegmented_default as GrSegmented, GrSegmented_default as default, grSegmentedConfig, grSegmentedSafelist };
|
|
@@ -171,6 +171,12 @@ var GrSidebarItem_default = /* @__PURE__ */ defineComponent({
|
|
|
171
171
|
if (props.as) return typeof props.as === "string" ? props.as : markRaw(props.as);
|
|
172
172
|
return props.href ? "a" : "button";
|
|
173
173
|
});
|
|
174
|
+
/**
|
|
175
|
+
* Компонент-ссылка (`Link` от Inertia, `RouterLink`) рендерит `<a>` сам, и без
|
|
176
|
+
* `href` он ведёт в никуда. Строковый тег, кроме `a`, атрибут не понимает —
|
|
177
|
+
* там он и гасится.
|
|
178
|
+
*/
|
|
179
|
+
const rootHref = computed(() => typeof rootTag.value === "string" && rootTag.value !== "a" ? void 0 : props.href);
|
|
174
180
|
const rootClass = computed(() => grSidebarItemClass({
|
|
175
181
|
collapsed: collapsed.value,
|
|
176
182
|
disabled: props.disabled,
|
|
@@ -180,7 +186,7 @@ var GrSidebarItem_default = /* @__PURE__ */ defineComponent({
|
|
|
180
186
|
return openBlock(), createBlock(resolveDynamicComponent(rootTag.value), {
|
|
181
187
|
"data-gr-sidebar-item": "",
|
|
182
188
|
type: rootTag.value === "button" ? "button" : void 0,
|
|
183
|
-
href:
|
|
189
|
+
href: rootHref.value,
|
|
184
190
|
"aria-current": __props.active ? "page" : void 0,
|
|
185
191
|
"aria-disabled": __props.disabled ? "true" : void 0,
|
|
186
192
|
title: collapsed.value ? __props.label : void 0,
|
package/dist/components/GrSidebar/chunks/{GrSidebar-DG_t4aPE.js.map → GrSidebar-D7WxRtIu.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GrSidebar-DG_t4aPE.js","names":["$slots"],"sources":["../../../../src/components/GrSidebar/sidebarContext.ts","../../../../src/components/GrSidebar/GrSidebar.vue","../../../../src/components/GrSidebar/GrSidebar.vue","../../../../src/components/GrSidebar/GrSidebarItem.vue","../../../../src/components/GrSidebar/GrSidebarItem.vue","../../../../src/components/GrSidebar/GrSidebarGroup.vue","../../../../src/components/GrSidebar/GrSidebarGroup.vue"],"sourcesContent":["import type { InjectionKey, Ref } from 'vue'\n\n/**\n * Контекст `GrSidebar`, который потребляют `GrSidebarItem`'ы: свёрнута ли панель.\n * В свёрнутом состоянии пункты показывают только иконку (или первую букву метки).\n */\nexport interface GrSidebarContext {\n collapsed: Ref<boolean>\n}\n\nexport const GR_SIDEBAR_KEY: InjectionKey<GrSidebarContext> = Symbol.for('@feugene/granularity/sidebar')\n","<script setup lang=\"ts\">\n/**\n * GrSidebar — боковая панель приложения с опциональным заголовком, кнопкой\n * сворачивания и слотом контента (обычно — список `GrSidebarItem`).\n *\n * - Свёрнутое состояние (`collapsed`, поддерживает `v-model:collapsed`) сужает\n * панель до иконочной ширины; `GrSidebarItem`'ы через inject показывают только\n * иконку (или первую букву метки).\n * - Кнопка тогла (`show-toggle-button`) переключает состояние.\n * - Хедер рендерится только если есть `title`/`subtitle` (или кнопка тогла) —\n * иначе панель начинается сразу с контента.\n * - Слоты `#title` / `#subtitle` имеют приоритет над одноимёнными пропами.\n */\nimport { computed, provide, ref, watch } from 'vue'\n\nimport GrButton from '../GrButton/GrButton.vue'\nimport GrIcon from '../GrIcon/GrIcon.vue'\nimport IconChevronLeft from '~icons/lucide/chevron-left'\nimport IconChevronRight from '~icons/lucide/chevron-right'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\n\nimport {\n contentBase,\n grSidebarCollapseDirection,\n grSidebarRootClass,\n headerBase,\n subtitleClass,\n titleClass,\n type GrSidebarLandmark,\n type GrSidebarPosition,\n} from './grSidebarStyles'\nimport { GR_SIDEBAR_KEY } from './sidebarContext'\n\nexport type { GrSidebarLandmark, GrSidebarPosition } from './grSidebarStyles'\n\nexport interface GrSidebarProps {\n title?: string\n subtitle?: string\n /** Свёрнутое состояние. Поддерживает `v-model:collapsed`. */\n collapsed?: boolean\n /** Показать кнопку сворачивания/разворачивания в хедере. */\n showToggleButton?: boolean\n /** Ширина в развёрнутом состоянии. */\n width?: string\n /** Ширина в свёрнутом состоянии. */\n collapsedWidth?: string\n /** A11y-лейбл кнопки тогла. Не задан — берётся из локали (`gr.sidebar.*`). */\n toggleLabel?: string\n /** Сторона экрана: граница и направление шеврона зеркалятся. */\n position?: GrSidebarPosition\n /**\n * Лендмарк корня. `complementary` (по умолчанию) — `<aside>`; `navigation` —\n * `<nav>` для панели, которая действительно является навигацией. Вложенный\n * `<nav>` внутрь `<aside>` не заводим: два лендмарка на одну панель засоряют\n * обзор, а панель фильтров навигацией не является вовсе.\n */\n landmark?: GrSidebarLandmark\n /** Имя лендмарка: без него две панели на странице неразличимы. */\n ariaLabel?: string\n}\n\nexport interface GrSidebarEmits {\n (e: 'update:collapsed', value: boolean): void\n}\n\nconst props = withDefaults(defineProps<GrSidebarProps>(), {\n title: undefined,\n subtitle: undefined,\n collapsed: false,\n showToggleButton: false,\n width: '240px',\n collapsedWidth: '64px',\n toggleLabel: undefined,\n position: 'left',\n landmark: 'complementary',\n ariaLabel: undefined,\n})\n\nconst { t } = useGranularityTranslations()\n\nconst emit = defineEmits<GrSidebarEmits>()\n\n// Локальное состояние с синхронизацией из пропа — поддерживает и controlled\n// (`v-model:collapsed`), и uncontrolled (панель сама помнит состояние).\nconst collapsedState = ref(props.collapsed)\nwatch(() => props.collapsed, value => (collapsedState.value = value))\n\nfunction toggle(): void {\n collapsedState.value = !collapsedState.value\n emit('update:collapsed', collapsedState.value)\n}\n\n// Даём `GrSidebarItem`'ам знать про свёрнутость.\nprovide(GR_SIDEBAR_KEY, { collapsed: collapsedState })\n\nconst hasTitle = computed(() => Boolean(props.title))\nconst hasSubtitle = computed(() => Boolean(props.subtitle))\nconst showHeader = computed(() => Boolean(hasTitle.value || hasSubtitle.value || props.showToggleButton))\n\nconst asideStyle = computed(() => ({\n width: collapsedState.value ? props.collapsedWidth : props.width,\n}))\n\nconst resolvedToggleLabel = computed(() => props.toggleLabel ?? (collapsedState.value\n ? t('gr.sidebar.expand', 'Expand sidebar')\n : t('gr.sidebar.collapse', 'Collapse sidebar')))\n\nconst rootTag = computed(() => (props.landmark === 'navigation' ? 'nav' : 'aside'))\nconst rootClass = computed(() => grSidebarRootClass(props.position))\n\nconst collapseDirection = computed(() => grSidebarCollapseDirection(props.position, collapsedState.value))\nconst collapseIcon = computed(() => (collapseDirection.value === 'right' ? IconChevronRight : IconChevronLeft))\n</script>\n\n<template>\n <component\n :is=\"rootTag\"\n data-gr-sidebar\n :data-collapsed=\"collapsedState ? 'true' : undefined\"\n :data-position=\"position\"\n :aria-label=\"ariaLabel\"\n :class=\"rootClass\"\n :style=\"asideStyle\"\n >\n <div\n v-if=\"showHeader\"\n data-gr-sidebar-header\n :class=\"[headerBase, collapsedState ? 'justify-center' : 'justify-between']\"\n >\n <div\n v-if=\"!collapsedState && (hasTitle || hasSubtitle || $slots.title || $slots.subtitle)\"\n class=\"min-w-0\"\n >\n <div\n v-if=\"$slots.subtitle || hasSubtitle\"\n data-gr-sidebar-subtitle\n :class=\"subtitleClass\"\n >\n <slot name=\"subtitle\">\n {{ subtitle }}\n </slot>\n </div>\n <div\n v-if=\"$slots.title || hasTitle\"\n data-gr-sidebar-title\n :class=\"titleClass\"\n >\n <slot name=\"title\">\n {{ title }}\n </slot>\n </div>\n </div>\n\n <GrButton\n v-if=\"showToggleButton\"\n data-gr-sidebar-toggle\n variant=\"ghost\"\n size=\"sm\"\n square\n :aria-label=\"resolvedToggleLabel\"\n :aria-expanded=\"collapsedState ? 'false' : 'true'\"\n :data-direction=\"collapseDirection\"\n @click=\"toggle\"\n >\n <GrIcon :size=\"16\">\n <component :is=\"collapseIcon\" />\n </GrIcon>\n </GrButton>\n </div>\n\n <div\n data-gr-sidebar-content\n tabindex=\"0\"\n :class=\"[contentBase, collapsedState ? 'p-2' : 'p-3']\"\n >\n <slot />\n </div>\n </component>\n</template>\n","<script setup lang=\"ts\">\n/**\n * GrSidebar — боковая панель приложения с опциональным заголовком, кнопкой\n * сворачивания и слотом контента (обычно — список `GrSidebarItem`).\n *\n * - Свёрнутое состояние (`collapsed`, поддерживает `v-model:collapsed`) сужает\n * панель до иконочной ширины; `GrSidebarItem`'ы через inject показывают только\n * иконку (или первую букву метки).\n * - Кнопка тогла (`show-toggle-button`) переключает состояние.\n * - Хедер рендерится только если есть `title`/`subtitle` (или кнопка тогла) —\n * иначе панель начинается сразу с контента.\n * - Слоты `#title` / `#subtitle` имеют приоритет над одноимёнными пропами.\n */\nimport { computed, provide, ref, watch } from 'vue'\n\nimport GrButton from '../GrButton/GrButton.vue'\nimport GrIcon from '../GrIcon/GrIcon.vue'\nimport IconChevronLeft from '~icons/lucide/chevron-left'\nimport IconChevronRight from '~icons/lucide/chevron-right'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\n\nimport {\n contentBase,\n grSidebarCollapseDirection,\n grSidebarRootClass,\n headerBase,\n subtitleClass,\n titleClass,\n type GrSidebarLandmark,\n type GrSidebarPosition,\n} from './grSidebarStyles'\nimport { GR_SIDEBAR_KEY } from './sidebarContext'\n\nexport type { GrSidebarLandmark, GrSidebarPosition } from './grSidebarStyles'\n\nexport interface GrSidebarProps {\n title?: string\n subtitle?: string\n /** Свёрнутое состояние. Поддерживает `v-model:collapsed`. */\n collapsed?: boolean\n /** Показать кнопку сворачивания/разворачивания в хедере. */\n showToggleButton?: boolean\n /** Ширина в развёрнутом состоянии. */\n width?: string\n /** Ширина в свёрнутом состоянии. */\n collapsedWidth?: string\n /** A11y-лейбл кнопки тогла. Не задан — берётся из локали (`gr.sidebar.*`). */\n toggleLabel?: string\n /** Сторона экрана: граница и направление шеврона зеркалятся. */\n position?: GrSidebarPosition\n /**\n * Лендмарк корня. `complementary` (по умолчанию) — `<aside>`; `navigation` —\n * `<nav>` для панели, которая действительно является навигацией. Вложенный\n * `<nav>` внутрь `<aside>` не заводим: два лендмарка на одну панель засоряют\n * обзор, а панель фильтров навигацией не является вовсе.\n */\n landmark?: GrSidebarLandmark\n /** Имя лендмарка: без него две панели на странице неразличимы. */\n ariaLabel?: string\n}\n\nexport interface GrSidebarEmits {\n (e: 'update:collapsed', value: boolean): void\n}\n\nconst props = withDefaults(defineProps<GrSidebarProps>(), {\n title: undefined,\n subtitle: undefined,\n collapsed: false,\n showToggleButton: false,\n width: '240px',\n collapsedWidth: '64px',\n toggleLabel: undefined,\n position: 'left',\n landmark: 'complementary',\n ariaLabel: undefined,\n})\n\nconst { t } = useGranularityTranslations()\n\nconst emit = defineEmits<GrSidebarEmits>()\n\n// Локальное состояние с синхронизацией из пропа — поддерживает и controlled\n// (`v-model:collapsed`), и uncontrolled (панель сама помнит состояние).\nconst collapsedState = ref(props.collapsed)\nwatch(() => props.collapsed, value => (collapsedState.value = value))\n\nfunction toggle(): void {\n collapsedState.value = !collapsedState.value\n emit('update:collapsed', collapsedState.value)\n}\n\n// Даём `GrSidebarItem`'ам знать про свёрнутость.\nprovide(GR_SIDEBAR_KEY, { collapsed: collapsedState })\n\nconst hasTitle = computed(() => Boolean(props.title))\nconst hasSubtitle = computed(() => Boolean(props.subtitle))\nconst showHeader = computed(() => Boolean(hasTitle.value || hasSubtitle.value || props.showToggleButton))\n\nconst asideStyle = computed(() => ({\n width: collapsedState.value ? props.collapsedWidth : props.width,\n}))\n\nconst resolvedToggleLabel = computed(() => props.toggleLabel ?? (collapsedState.value\n ? t('gr.sidebar.expand', 'Expand sidebar')\n : t('gr.sidebar.collapse', 'Collapse sidebar')))\n\nconst rootTag = computed(() => (props.landmark === 'navigation' ? 'nav' : 'aside'))\nconst rootClass = computed(() => grSidebarRootClass(props.position))\n\nconst collapseDirection = computed(() => grSidebarCollapseDirection(props.position, collapsedState.value))\nconst collapseIcon = computed(() => (collapseDirection.value === 'right' ? IconChevronRight : IconChevronLeft))\n</script>\n\n<template>\n <component\n :is=\"rootTag\"\n data-gr-sidebar\n :data-collapsed=\"collapsedState ? 'true' : undefined\"\n :data-position=\"position\"\n :aria-label=\"ariaLabel\"\n :class=\"rootClass\"\n :style=\"asideStyle\"\n >\n <div\n v-if=\"showHeader\"\n data-gr-sidebar-header\n :class=\"[headerBase, collapsedState ? 'justify-center' : 'justify-between']\"\n >\n <div\n v-if=\"!collapsedState && (hasTitle || hasSubtitle || $slots.title || $slots.subtitle)\"\n class=\"min-w-0\"\n >\n <div\n v-if=\"$slots.subtitle || hasSubtitle\"\n data-gr-sidebar-subtitle\n :class=\"subtitleClass\"\n >\n <slot name=\"subtitle\">\n {{ subtitle }}\n </slot>\n </div>\n <div\n v-if=\"$slots.title || hasTitle\"\n data-gr-sidebar-title\n :class=\"titleClass\"\n >\n <slot name=\"title\">\n {{ title }}\n </slot>\n </div>\n </div>\n\n <GrButton\n v-if=\"showToggleButton\"\n data-gr-sidebar-toggle\n variant=\"ghost\"\n size=\"sm\"\n square\n :aria-label=\"resolvedToggleLabel\"\n :aria-expanded=\"collapsedState ? 'false' : 'true'\"\n :data-direction=\"collapseDirection\"\n @click=\"toggle\"\n >\n <GrIcon :size=\"16\">\n <component :is=\"collapseIcon\" />\n </GrIcon>\n </GrButton>\n </div>\n\n <div\n data-gr-sidebar-content\n tabindex=\"0\"\n :class=\"[contentBase, collapsedState ? 'p-2' : 'p-3']\"\n >\n <slot />\n </div>\n </component>\n</template>\n","<script setup lang=\"ts\">\n/**\n * GrSidebarItem — пункт навигации боковой панели `GrSidebar`.\n *\n * Контракт:\n * - развёрнутая панель — `[иконка] метка [бейдж]`;\n * - свёрнутая панель — только иконка; если иконки нет, показывается ПЕРВАЯ БУКВА\n * метки (uppercase). В свёрнутом виде метка уходит в `title`/`aria-label` для\n * доступности и tooltip.\n *\n * Корневой тег: `as` → `<a href>` → `<button>` (в этом порядке).\n */\nimport { computed, inject, markRaw, type Component } from 'vue'\n\nimport { grSidebarItemClass, itemBadgeClass, itemLetterClass } from './grSidebarStyles'\nimport { GR_SIDEBAR_KEY } from './sidebarContext'\n\nexport interface GrSidebarItemProps {\n label: string\n /** Иконка: класс UnoCSS-иконки (`'i-lucide-home'`) или Vue-компонент. */\n icon?: string | Component\n href?: string\n as?: string | Component\n active?: boolean\n disabled?: boolean\n badge?: string | number\n}\n\nconst props = withDefaults(defineProps<GrSidebarItemProps>(), {\n icon: undefined,\n href: undefined,\n as: undefined,\n active: false,\n disabled: false,\n badge: undefined,\n})\n\nconst sidebar = inject(GR_SIDEBAR_KEY, null)\nconst collapsed = computed(() => sidebar?.collapsed.value ?? false)\n\nconst isStringIcon = computed(() => typeof props.icon === 'string')\nconst iconComponent = computed(() => (props.icon && typeof props.icon !== 'string' ? markRaw(props.icon as Component) : null))\nconst firstLetter = computed(() => props.label.trim().charAt(0).toUpperCase() || '•')\n\nconst rootTag = computed<string | Component>(() => {\n if (props.disabled) return 'span'\n if (props.as) return typeof props.as === 'string' ? props.as : markRaw(props.as)\n return props.href ? 'a' : 'button'\n})\n\nconst rootClass = computed(() => grSidebarItemClass({\n collapsed: collapsed.value,\n disabled: props.disabled,\n active: props.active,\n}))\n</script>\n\n<template>\n <component\n :is=\"rootTag\"\n data-gr-sidebar-item\n :type=\"rootTag === 'button' ? 'button' : undefined\"\n :href=\"rootTag === 'a' ? href : undefined\"\n :aria-current=\"active ? 'page' : undefined\"\n :aria-disabled=\"disabled ? 'true' : undefined\"\n :title=\"collapsed ? label : undefined\"\n :aria-label=\"collapsed ? label : undefined\"\n :class=\"rootClass\"\n >\n <span class=\"flex h-5 w-5 shrink-0 items-center justify-center\">\n <component :is=\"iconComponent\" v-if=\"iconComponent\" class=\"h-5 w-5\" aria-hidden=\"true\" />\n <span v-else-if=\"isStringIcon\" :class=\"icon\" class=\"block h-5 w-5\" aria-hidden=\"true\" />\n <span v-else-if=\"collapsed\" :class=\"itemLetterClass\" aria-hidden=\"true\">{{ firstLetter }}</span>\n </span>\n\n <template v-if=\"!collapsed\">\n <span class=\"min-w-0 flex-1 truncate text-left\">{{ label }}</span>\n <span v-if=\"badge != null\" :class=\"itemBadgeClass\">{{ badge }}</span>\n </template>\n </component>\n</template>\n","<script setup lang=\"ts\">\n/**\n * GrSidebarItem — пункт навигации боковой панели `GrSidebar`.\n *\n * Контракт:\n * - развёрнутая панель — `[иконка] метка [бейдж]`;\n * - свёрнутая панель — только иконка; если иконки нет, показывается ПЕРВАЯ БУКВА\n * метки (uppercase). В свёрнутом виде метка уходит в `title`/`aria-label` для\n * доступности и tooltip.\n *\n * Корневой тег: `as` → `<a href>` → `<button>` (в этом порядке).\n */\nimport { computed, inject, markRaw, type Component } from 'vue'\n\nimport { grSidebarItemClass, itemBadgeClass, itemLetterClass } from './grSidebarStyles'\nimport { GR_SIDEBAR_KEY } from './sidebarContext'\n\nexport interface GrSidebarItemProps {\n label: string\n /** Иконка: класс UnoCSS-иконки (`'i-lucide-home'`) или Vue-компонент. */\n icon?: string | Component\n href?: string\n as?: string | Component\n active?: boolean\n disabled?: boolean\n badge?: string | number\n}\n\nconst props = withDefaults(defineProps<GrSidebarItemProps>(), {\n icon: undefined,\n href: undefined,\n as: undefined,\n active: false,\n disabled: false,\n badge: undefined,\n})\n\nconst sidebar = inject(GR_SIDEBAR_KEY, null)\nconst collapsed = computed(() => sidebar?.collapsed.value ?? false)\n\nconst isStringIcon = computed(() => typeof props.icon === 'string')\nconst iconComponent = computed(() => (props.icon && typeof props.icon !== 'string' ? markRaw(props.icon as Component) : null))\nconst firstLetter = computed(() => props.label.trim().charAt(0).toUpperCase() || '•')\n\nconst rootTag = computed<string | Component>(() => {\n if (props.disabled) return 'span'\n if (props.as) return typeof props.as === 'string' ? props.as : markRaw(props.as)\n return props.href ? 'a' : 'button'\n})\n\nconst rootClass = computed(() => grSidebarItemClass({\n collapsed: collapsed.value,\n disabled: props.disabled,\n active: props.active,\n}))\n</script>\n\n<template>\n <component\n :is=\"rootTag\"\n data-gr-sidebar-item\n :type=\"rootTag === 'button' ? 'button' : undefined\"\n :href=\"rootTag === 'a' ? href : undefined\"\n :aria-current=\"active ? 'page' : undefined\"\n :aria-disabled=\"disabled ? 'true' : undefined\"\n :title=\"collapsed ? label : undefined\"\n :aria-label=\"collapsed ? label : undefined\"\n :class=\"rootClass\"\n >\n <span class=\"flex h-5 w-5 shrink-0 items-center justify-center\">\n <component :is=\"iconComponent\" v-if=\"iconComponent\" class=\"h-5 w-5\" aria-hidden=\"true\" />\n <span v-else-if=\"isStringIcon\" :class=\"icon\" class=\"block h-5 w-5\" aria-hidden=\"true\" />\n <span v-else-if=\"collapsed\" :class=\"itemLetterClass\" aria-hidden=\"true\">{{ firstLetter }}</span>\n </span>\n\n <template v-if=\"!collapsed\">\n <span class=\"min-w-0 flex-1 truncate text-left\">{{ label }}</span>\n <span v-if=\"badge != null\" :class=\"itemBadgeClass\">{{ badge }}</span>\n </template>\n </component>\n</template>\n","<script setup lang=\"ts\">\n/**\n * GrSidebarGroup — секция пунктов `GrSidebar` с заголовком.\n *\n * В свёрнутой панели заголовку негде поместиться: он скрывается, а секции\n * разделяет линия — без неё иконки соседних секций сливаются в один столбец.\n */\nimport { computed, inject, useId } from 'vue'\n\nimport { groupCollapsedClass, groupLabelClass } from './grSidebarStyles'\nimport { GR_SIDEBAR_KEY } from './sidebarContext'\n\nexport interface GrSidebarGroupProps {\n /** Заголовок секции. Слот `#label` сильнее. */\n label?: string\n}\n\nconst props = withDefaults(defineProps<GrSidebarGroupProps>(), {\n label: undefined,\n})\n\nconst slots = defineSlots<{\n /** Пункты секции. */\n default?: () => unknown\n /** Заголовок целиком — вместо строки `label`. */\n label?: () => unknown\n}>()\n\nconst sidebar = inject(GR_SIDEBAR_KEY, null)\nconst collapsed = computed(() => sidebar?.collapsed.value ?? false)\n\nconst labelId = useId()\nconst hasLabel = computed(() => Boolean(props.label || slots.label))\n\n// Имя группе даёт только видимый заголовок: в свёрнутом виде его нет, и\n// `aria-labelledby` указывал бы в пустоту.\nconst showLabel = computed(() => hasLabel.value && !collapsed.value)\n</script>\n\n<template>\n <div\n data-gr-sidebar-group\n role=\"group\"\n :aria-labelledby=\"showLabel ? labelId : undefined\"\n :class=\"collapsed ? groupCollapsedClass : undefined\"\n >\n <div\n v-if=\"showLabel\"\n :id=\"labelId\"\n data-gr-sidebar-group-label\n :class=\"groupLabelClass\"\n >\n <slot name=\"label\">\n {{ label }}\n </slot>\n </div>\n\n <slot />\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * GrSidebarGroup — секция пунктов `GrSidebar` с заголовком.\n *\n * В свёрнутой панели заголовку негде поместиться: он скрывается, а секции\n * разделяет линия — без неё иконки соседних секций сливаются в один столбец.\n */\nimport { computed, inject, useId } from 'vue'\n\nimport { groupCollapsedClass, groupLabelClass } from './grSidebarStyles'\nimport { GR_SIDEBAR_KEY } from './sidebarContext'\n\nexport interface GrSidebarGroupProps {\n /** Заголовок секции. Слот `#label` сильнее. */\n label?: string\n}\n\nconst props = withDefaults(defineProps<GrSidebarGroupProps>(), {\n label: undefined,\n})\n\nconst slots = defineSlots<{\n /** Пункты секции. */\n default?: () => unknown\n /** Заголовок целиком — вместо строки `label`. */\n label?: () => unknown\n}>()\n\nconst sidebar = inject(GR_SIDEBAR_KEY, null)\nconst collapsed = computed(() => sidebar?.collapsed.value ?? false)\n\nconst labelId = useId()\nconst hasLabel = computed(() => Boolean(props.label || slots.label))\n\n// Имя группе даёт только видимый заголовок: в свёрнутом виде его нет, и\n// `aria-labelledby` указывал бы в пустоту.\nconst showLabel = computed(() => hasLabel.value && !collapsed.value)\n</script>\n\n<template>\n <div\n data-gr-sidebar-group\n role=\"group\"\n :aria-labelledby=\"showLabel ? labelId : undefined\"\n :class=\"collapsed ? groupCollapsedClass : undefined\"\n >\n <div\n v-if=\"showLabel\"\n :id=\"labelId\"\n data-gr-sidebar-group-label\n :class=\"groupLabelClass\"\n >\n <slot name=\"label\">\n {{ label }}\n </slot>\n </div>\n\n <slot />\n </div>\n</template>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"GrSidebar-D7WxRtIu.js","names":["$slots"],"sources":["../../../../src/components/GrSidebar/sidebarContext.ts","../../../../src/components/GrSidebar/GrSidebar.vue","../../../../src/components/GrSidebar/GrSidebar.vue","../../../../src/components/GrSidebar/GrSidebarItem.vue","../../../../src/components/GrSidebar/GrSidebarItem.vue","../../../../src/components/GrSidebar/GrSidebarGroup.vue","../../../../src/components/GrSidebar/GrSidebarGroup.vue"],"sourcesContent":["import type { InjectionKey, Ref } from 'vue'\n\n/**\n * Контекст `GrSidebar`, который потребляют `GrSidebarItem`'ы: свёрнута ли панель.\n * В свёрнутом состоянии пункты показывают только иконку (или первую букву метки).\n */\nexport interface GrSidebarContext {\n collapsed: Ref<boolean>\n}\n\nexport const GR_SIDEBAR_KEY: InjectionKey<GrSidebarContext> = Symbol.for('@feugene/granularity/sidebar')\n","<script setup lang=\"ts\">\n/**\n * GrSidebar — боковая панель приложения с опциональным заголовком, кнопкой\n * сворачивания и слотом контента (обычно — список `GrSidebarItem`).\n *\n * - Свёрнутое состояние (`collapsed`, поддерживает `v-model:collapsed`) сужает\n * панель до иконочной ширины; `GrSidebarItem`'ы через inject показывают только\n * иконку (или первую букву метки).\n * - Кнопка тогла (`show-toggle-button`) переключает состояние.\n * - Хедер рендерится только если есть `title`/`subtitle` (или кнопка тогла) —\n * иначе панель начинается сразу с контента.\n * - Слоты `#title` / `#subtitle` имеют приоритет над одноимёнными пропами.\n */\nimport { computed, provide, ref, watch } from 'vue'\n\nimport GrButton from '../GrButton/GrButton.vue'\nimport GrIcon from '../GrIcon/GrIcon.vue'\nimport IconChevronLeft from '~icons/lucide/chevron-left'\nimport IconChevronRight from '~icons/lucide/chevron-right'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\n\nimport {\n contentBase,\n grSidebarCollapseDirection,\n grSidebarRootClass,\n headerBase,\n subtitleClass,\n titleClass,\n type GrSidebarLandmark,\n type GrSidebarPosition,\n} from './grSidebarStyles'\nimport { GR_SIDEBAR_KEY } from './sidebarContext'\n\nexport type { GrSidebarLandmark, GrSidebarPosition } from './grSidebarStyles'\n\nexport interface GrSidebarProps {\n title?: string\n subtitle?: string\n /** Свёрнутое состояние. Поддерживает `v-model:collapsed`. */\n collapsed?: boolean\n /** Показать кнопку сворачивания/разворачивания в хедере. */\n showToggleButton?: boolean\n /** Ширина в развёрнутом состоянии. */\n width?: string\n /** Ширина в свёрнутом состоянии. */\n collapsedWidth?: string\n /** A11y-лейбл кнопки тогла. Не задан — берётся из локали (`gr.sidebar.*`). */\n toggleLabel?: string\n /** Сторона экрана: граница и направление шеврона зеркалятся. */\n position?: GrSidebarPosition\n /**\n * Лендмарк корня. `complementary` (по умолчанию) — `<aside>`; `navigation` —\n * `<nav>` для панели, которая действительно является навигацией. Вложенный\n * `<nav>` внутрь `<aside>` не заводим: два лендмарка на одну панель засоряют\n * обзор, а панель фильтров навигацией не является вовсе.\n */\n landmark?: GrSidebarLandmark\n /** Имя лендмарка: без него две панели на странице неразличимы. */\n ariaLabel?: string\n}\n\nexport interface GrSidebarEmits {\n (e: 'update:collapsed', value: boolean): void\n}\n\nconst props = withDefaults(defineProps<GrSidebarProps>(), {\n title: undefined,\n subtitle: undefined,\n collapsed: false,\n showToggleButton: false,\n width: '240px',\n collapsedWidth: '64px',\n toggleLabel: undefined,\n position: 'left',\n landmark: 'complementary',\n ariaLabel: undefined,\n})\n\nconst { t } = useGranularityTranslations()\n\nconst emit = defineEmits<GrSidebarEmits>()\n\n// Локальное состояние с синхронизацией из пропа — поддерживает и controlled\n// (`v-model:collapsed`), и uncontrolled (панель сама помнит состояние).\nconst collapsedState = ref(props.collapsed)\nwatch(() => props.collapsed, value => (collapsedState.value = value))\n\nfunction toggle(): void {\n collapsedState.value = !collapsedState.value\n emit('update:collapsed', collapsedState.value)\n}\n\n// Даём `GrSidebarItem`'ам знать про свёрнутость.\nprovide(GR_SIDEBAR_KEY, { collapsed: collapsedState })\n\nconst hasTitle = computed(() => Boolean(props.title))\nconst hasSubtitle = computed(() => Boolean(props.subtitle))\nconst showHeader = computed(() => Boolean(hasTitle.value || hasSubtitle.value || props.showToggleButton))\n\nconst asideStyle = computed(() => ({\n width: collapsedState.value ? props.collapsedWidth : props.width,\n}))\n\nconst resolvedToggleLabel = computed(() => props.toggleLabel ?? (collapsedState.value\n ? t('gr.sidebar.expand', 'Expand sidebar')\n : t('gr.sidebar.collapse', 'Collapse sidebar')))\n\nconst rootTag = computed(() => (props.landmark === 'navigation' ? 'nav' : 'aside'))\nconst rootClass = computed(() => grSidebarRootClass(props.position))\n\nconst collapseDirection = computed(() => grSidebarCollapseDirection(props.position, collapsedState.value))\nconst collapseIcon = computed(() => (collapseDirection.value === 'right' ? IconChevronRight : IconChevronLeft))\n</script>\n\n<template>\n <component\n :is=\"rootTag\"\n data-gr-sidebar\n :data-collapsed=\"collapsedState ? 'true' : undefined\"\n :data-position=\"position\"\n :aria-label=\"ariaLabel\"\n :class=\"rootClass\"\n :style=\"asideStyle\"\n >\n <div\n v-if=\"showHeader\"\n data-gr-sidebar-header\n :class=\"[headerBase, collapsedState ? 'justify-center' : 'justify-between']\"\n >\n <div\n v-if=\"!collapsedState && (hasTitle || hasSubtitle || $slots.title || $slots.subtitle)\"\n class=\"min-w-0\"\n >\n <div\n v-if=\"$slots.subtitle || hasSubtitle\"\n data-gr-sidebar-subtitle\n :class=\"subtitleClass\"\n >\n <slot name=\"subtitle\">\n {{ subtitle }}\n </slot>\n </div>\n <div\n v-if=\"$slots.title || hasTitle\"\n data-gr-sidebar-title\n :class=\"titleClass\"\n >\n <slot name=\"title\">\n {{ title }}\n </slot>\n </div>\n </div>\n\n <GrButton\n v-if=\"showToggleButton\"\n data-gr-sidebar-toggle\n variant=\"ghost\"\n size=\"sm\"\n square\n :aria-label=\"resolvedToggleLabel\"\n :aria-expanded=\"collapsedState ? 'false' : 'true'\"\n :data-direction=\"collapseDirection\"\n @click=\"toggle\"\n >\n <GrIcon :size=\"16\">\n <component :is=\"collapseIcon\" />\n </GrIcon>\n </GrButton>\n </div>\n\n <div\n data-gr-sidebar-content\n tabindex=\"0\"\n :class=\"[contentBase, collapsedState ? 'p-2' : 'p-3']\"\n >\n <slot />\n </div>\n </component>\n</template>\n","<script setup lang=\"ts\">\n/**\n * GrSidebar — боковая панель приложения с опциональным заголовком, кнопкой\n * сворачивания и слотом контента (обычно — список `GrSidebarItem`).\n *\n * - Свёрнутое состояние (`collapsed`, поддерживает `v-model:collapsed`) сужает\n * панель до иконочной ширины; `GrSidebarItem`'ы через inject показывают только\n * иконку (или первую букву метки).\n * - Кнопка тогла (`show-toggle-button`) переключает состояние.\n * - Хедер рендерится только если есть `title`/`subtitle` (или кнопка тогла) —\n * иначе панель начинается сразу с контента.\n * - Слоты `#title` / `#subtitle` имеют приоритет над одноимёнными пропами.\n */\nimport { computed, provide, ref, watch } from 'vue'\n\nimport GrButton from '../GrButton/GrButton.vue'\nimport GrIcon from '../GrIcon/GrIcon.vue'\nimport IconChevronLeft from '~icons/lucide/chevron-left'\nimport IconChevronRight from '~icons/lucide/chevron-right'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\n\nimport {\n contentBase,\n grSidebarCollapseDirection,\n grSidebarRootClass,\n headerBase,\n subtitleClass,\n titleClass,\n type GrSidebarLandmark,\n type GrSidebarPosition,\n} from './grSidebarStyles'\nimport { GR_SIDEBAR_KEY } from './sidebarContext'\n\nexport type { GrSidebarLandmark, GrSidebarPosition } from './grSidebarStyles'\n\nexport interface GrSidebarProps {\n title?: string\n subtitle?: string\n /** Свёрнутое состояние. Поддерживает `v-model:collapsed`. */\n collapsed?: boolean\n /** Показать кнопку сворачивания/разворачивания в хедере. */\n showToggleButton?: boolean\n /** Ширина в развёрнутом состоянии. */\n width?: string\n /** Ширина в свёрнутом состоянии. */\n collapsedWidth?: string\n /** A11y-лейбл кнопки тогла. Не задан — берётся из локали (`gr.sidebar.*`). */\n toggleLabel?: string\n /** Сторона экрана: граница и направление шеврона зеркалятся. */\n position?: GrSidebarPosition\n /**\n * Лендмарк корня. `complementary` (по умолчанию) — `<aside>`; `navigation` —\n * `<nav>` для панели, которая действительно является навигацией. Вложенный\n * `<nav>` внутрь `<aside>` не заводим: два лендмарка на одну панель засоряют\n * обзор, а панель фильтров навигацией не является вовсе.\n */\n landmark?: GrSidebarLandmark\n /** Имя лендмарка: без него две панели на странице неразличимы. */\n ariaLabel?: string\n}\n\nexport interface GrSidebarEmits {\n (e: 'update:collapsed', value: boolean): void\n}\n\nconst props = withDefaults(defineProps<GrSidebarProps>(), {\n title: undefined,\n subtitle: undefined,\n collapsed: false,\n showToggleButton: false,\n width: '240px',\n collapsedWidth: '64px',\n toggleLabel: undefined,\n position: 'left',\n landmark: 'complementary',\n ariaLabel: undefined,\n})\n\nconst { t } = useGranularityTranslations()\n\nconst emit = defineEmits<GrSidebarEmits>()\n\n// Локальное состояние с синхронизацией из пропа — поддерживает и controlled\n// (`v-model:collapsed`), и uncontrolled (панель сама помнит состояние).\nconst collapsedState = ref(props.collapsed)\nwatch(() => props.collapsed, value => (collapsedState.value = value))\n\nfunction toggle(): void {\n collapsedState.value = !collapsedState.value\n emit('update:collapsed', collapsedState.value)\n}\n\n// Даём `GrSidebarItem`'ам знать про свёрнутость.\nprovide(GR_SIDEBAR_KEY, { collapsed: collapsedState })\n\nconst hasTitle = computed(() => Boolean(props.title))\nconst hasSubtitle = computed(() => Boolean(props.subtitle))\nconst showHeader = computed(() => Boolean(hasTitle.value || hasSubtitle.value || props.showToggleButton))\n\nconst asideStyle = computed(() => ({\n width: collapsedState.value ? props.collapsedWidth : props.width,\n}))\n\nconst resolvedToggleLabel = computed(() => props.toggleLabel ?? (collapsedState.value\n ? t('gr.sidebar.expand', 'Expand sidebar')\n : t('gr.sidebar.collapse', 'Collapse sidebar')))\n\nconst rootTag = computed(() => (props.landmark === 'navigation' ? 'nav' : 'aside'))\nconst rootClass = computed(() => grSidebarRootClass(props.position))\n\nconst collapseDirection = computed(() => grSidebarCollapseDirection(props.position, collapsedState.value))\nconst collapseIcon = computed(() => (collapseDirection.value === 'right' ? IconChevronRight : IconChevronLeft))\n</script>\n\n<template>\n <component\n :is=\"rootTag\"\n data-gr-sidebar\n :data-collapsed=\"collapsedState ? 'true' : undefined\"\n :data-position=\"position\"\n :aria-label=\"ariaLabel\"\n :class=\"rootClass\"\n :style=\"asideStyle\"\n >\n <div\n v-if=\"showHeader\"\n data-gr-sidebar-header\n :class=\"[headerBase, collapsedState ? 'justify-center' : 'justify-between']\"\n >\n <div\n v-if=\"!collapsedState && (hasTitle || hasSubtitle || $slots.title || $slots.subtitle)\"\n class=\"min-w-0\"\n >\n <div\n v-if=\"$slots.subtitle || hasSubtitle\"\n data-gr-sidebar-subtitle\n :class=\"subtitleClass\"\n >\n <slot name=\"subtitle\">\n {{ subtitle }}\n </slot>\n </div>\n <div\n v-if=\"$slots.title || hasTitle\"\n data-gr-sidebar-title\n :class=\"titleClass\"\n >\n <slot name=\"title\">\n {{ title }}\n </slot>\n </div>\n </div>\n\n <GrButton\n v-if=\"showToggleButton\"\n data-gr-sidebar-toggle\n variant=\"ghost\"\n size=\"sm\"\n square\n :aria-label=\"resolvedToggleLabel\"\n :aria-expanded=\"collapsedState ? 'false' : 'true'\"\n :data-direction=\"collapseDirection\"\n @click=\"toggle\"\n >\n <GrIcon :size=\"16\">\n <component :is=\"collapseIcon\" />\n </GrIcon>\n </GrButton>\n </div>\n\n <div\n data-gr-sidebar-content\n tabindex=\"0\"\n :class=\"[contentBase, collapsedState ? 'p-2' : 'p-3']\"\n >\n <slot />\n </div>\n </component>\n</template>\n","<script setup lang=\"ts\">\n/**\n * GrSidebarItem — пункт навигации боковой панели `GrSidebar`.\n *\n * Контракт:\n * - развёрнутая панель — `[иконка] метка [бейдж]`;\n * - свёрнутая панель — только иконка; если иконки нет, показывается ПЕРВАЯ БУКВА\n * метки (uppercase). В свёрнутом виде метка уходит в `title`/`aria-label` для\n * доступности и tooltip.\n *\n * Корневой тег: `as` → `<a href>` → `<button>` (в этом порядке).\n */\nimport { computed, inject, markRaw, type Component } from 'vue'\n\nimport { grSidebarItemClass, itemBadgeClass, itemLetterClass } from './grSidebarStyles'\nimport { GR_SIDEBAR_KEY } from './sidebarContext'\n\nexport interface GrSidebarItemProps {\n label: string\n /** Иконка: класс UnoCSS-иконки (`'i-lucide-home'`) или Vue-компонент. */\n icon?: string | Component\n href?: string\n as?: string | Component\n active?: boolean\n disabled?: boolean\n badge?: string | number\n}\n\nconst props = withDefaults(defineProps<GrSidebarItemProps>(), {\n icon: undefined,\n href: undefined,\n as: undefined,\n active: false,\n disabled: false,\n badge: undefined,\n})\n\nconst sidebar = inject(GR_SIDEBAR_KEY, null)\nconst collapsed = computed(() => sidebar?.collapsed.value ?? false)\n\nconst isStringIcon = computed(() => typeof props.icon === 'string')\nconst iconComponent = computed(() => (props.icon && typeof props.icon !== 'string' ? markRaw(props.icon as Component) : null))\nconst firstLetter = computed(() => props.label.trim().charAt(0).toUpperCase() || '•')\n\nconst rootTag = computed<string | Component>(() => {\n if (props.disabled) return 'span'\n if (props.as) return typeof props.as === 'string' ? props.as : markRaw(props.as)\n return props.href ? 'a' : 'button'\n})\n\n/**\n * Компонент-ссылка (`Link` от Inertia, `RouterLink`) рендерит `<a>` сам, и без\n * `href` он ведёт в никуда. Строковый тег, кроме `a`, атрибут не понимает —\n * там он и гасится.\n */\nconst rootHref = computed(() => (\n typeof rootTag.value === 'string' && rootTag.value !== 'a' ? undefined : props.href\n))\n\nconst rootClass = computed(() => grSidebarItemClass({\n collapsed: collapsed.value,\n disabled: props.disabled,\n active: props.active,\n}))\n</script>\n\n<template>\n <component\n :is=\"rootTag\"\n data-gr-sidebar-item\n :type=\"rootTag === 'button' ? 'button' : undefined\"\n :href=\"rootHref\"\n :aria-current=\"active ? 'page' : undefined\"\n :aria-disabled=\"disabled ? 'true' : undefined\"\n :title=\"collapsed ? label : undefined\"\n :aria-label=\"collapsed ? label : undefined\"\n :class=\"rootClass\"\n >\n <span class=\"flex h-5 w-5 shrink-0 items-center justify-center\">\n <component :is=\"iconComponent\" v-if=\"iconComponent\" class=\"h-5 w-5\" aria-hidden=\"true\" />\n <span v-else-if=\"isStringIcon\" :class=\"icon\" class=\"block h-5 w-5\" aria-hidden=\"true\" />\n <span v-else-if=\"collapsed\" :class=\"itemLetterClass\" aria-hidden=\"true\">{{ firstLetter }}</span>\n </span>\n\n <template v-if=\"!collapsed\">\n <span class=\"min-w-0 flex-1 truncate text-left\">{{ label }}</span>\n <span v-if=\"badge != null\" :class=\"itemBadgeClass\">{{ badge }}</span>\n </template>\n </component>\n</template>\n","<script setup lang=\"ts\">\n/**\n * GrSidebarItem — пункт навигации боковой панели `GrSidebar`.\n *\n * Контракт:\n * - развёрнутая панель — `[иконка] метка [бейдж]`;\n * - свёрнутая панель — только иконка; если иконки нет, показывается ПЕРВАЯ БУКВА\n * метки (uppercase). В свёрнутом виде метка уходит в `title`/`aria-label` для\n * доступности и tooltip.\n *\n * Корневой тег: `as` → `<a href>` → `<button>` (в этом порядке).\n */\nimport { computed, inject, markRaw, type Component } from 'vue'\n\nimport { grSidebarItemClass, itemBadgeClass, itemLetterClass } from './grSidebarStyles'\nimport { GR_SIDEBAR_KEY } from './sidebarContext'\n\nexport interface GrSidebarItemProps {\n label: string\n /** Иконка: класс UnoCSS-иконки (`'i-lucide-home'`) или Vue-компонент. */\n icon?: string | Component\n href?: string\n as?: string | Component\n active?: boolean\n disabled?: boolean\n badge?: string | number\n}\n\nconst props = withDefaults(defineProps<GrSidebarItemProps>(), {\n icon: undefined,\n href: undefined,\n as: undefined,\n active: false,\n disabled: false,\n badge: undefined,\n})\n\nconst sidebar = inject(GR_SIDEBAR_KEY, null)\nconst collapsed = computed(() => sidebar?.collapsed.value ?? false)\n\nconst isStringIcon = computed(() => typeof props.icon === 'string')\nconst iconComponent = computed(() => (props.icon && typeof props.icon !== 'string' ? markRaw(props.icon as Component) : null))\nconst firstLetter = computed(() => props.label.trim().charAt(0).toUpperCase() || '•')\n\nconst rootTag = computed<string | Component>(() => {\n if (props.disabled) return 'span'\n if (props.as) return typeof props.as === 'string' ? props.as : markRaw(props.as)\n return props.href ? 'a' : 'button'\n})\n\n/**\n * Компонент-ссылка (`Link` от Inertia, `RouterLink`) рендерит `<a>` сам, и без\n * `href` он ведёт в никуда. Строковый тег, кроме `a`, атрибут не понимает —\n * там он и гасится.\n */\nconst rootHref = computed(() => (\n typeof rootTag.value === 'string' && rootTag.value !== 'a' ? undefined : props.href\n))\n\nconst rootClass = computed(() => grSidebarItemClass({\n collapsed: collapsed.value,\n disabled: props.disabled,\n active: props.active,\n}))\n</script>\n\n<template>\n <component\n :is=\"rootTag\"\n data-gr-sidebar-item\n :type=\"rootTag === 'button' ? 'button' : undefined\"\n :href=\"rootHref\"\n :aria-current=\"active ? 'page' : undefined\"\n :aria-disabled=\"disabled ? 'true' : undefined\"\n :title=\"collapsed ? label : undefined\"\n :aria-label=\"collapsed ? label : undefined\"\n :class=\"rootClass\"\n >\n <span class=\"flex h-5 w-5 shrink-0 items-center justify-center\">\n <component :is=\"iconComponent\" v-if=\"iconComponent\" class=\"h-5 w-5\" aria-hidden=\"true\" />\n <span v-else-if=\"isStringIcon\" :class=\"icon\" class=\"block h-5 w-5\" aria-hidden=\"true\" />\n <span v-else-if=\"collapsed\" :class=\"itemLetterClass\" aria-hidden=\"true\">{{ firstLetter }}</span>\n </span>\n\n <template v-if=\"!collapsed\">\n <span class=\"min-w-0 flex-1 truncate text-left\">{{ label }}</span>\n <span v-if=\"badge != null\" :class=\"itemBadgeClass\">{{ badge }}</span>\n </template>\n </component>\n</template>\n","<script setup lang=\"ts\">\n/**\n * GrSidebarGroup — секция пунктов `GrSidebar` с заголовком.\n *\n * В свёрнутой панели заголовку негде поместиться: он скрывается, а секции\n * разделяет линия — без неё иконки соседних секций сливаются в один столбец.\n */\nimport { computed, inject, useId } from 'vue'\n\nimport { groupCollapsedClass, groupLabelClass } from './grSidebarStyles'\nimport { GR_SIDEBAR_KEY } from './sidebarContext'\n\nexport interface GrSidebarGroupProps {\n /** Заголовок секции. Слот `#label` сильнее. */\n label?: string\n}\n\nconst props = withDefaults(defineProps<GrSidebarGroupProps>(), {\n label: undefined,\n})\n\nconst slots = defineSlots<{\n /** Пункты секции. */\n default?: () => unknown\n /** Заголовок целиком — вместо строки `label`. */\n label?: () => unknown\n}>()\n\nconst sidebar = inject(GR_SIDEBAR_KEY, null)\nconst collapsed = computed(() => sidebar?.collapsed.value ?? false)\n\nconst labelId = useId()\nconst hasLabel = computed(() => Boolean(props.label || slots.label))\n\n// Имя группе даёт только видимый заголовок: в свёрнутом виде его нет, и\n// `aria-labelledby` указывал бы в пустоту.\nconst showLabel = computed(() => hasLabel.value && !collapsed.value)\n</script>\n\n<template>\n <div\n data-gr-sidebar-group\n role=\"group\"\n :aria-labelledby=\"showLabel ? labelId : undefined\"\n :class=\"collapsed ? groupCollapsedClass : undefined\"\n >\n <div\n v-if=\"showLabel\"\n :id=\"labelId\"\n data-gr-sidebar-group-label\n :class=\"groupLabelClass\"\n >\n <slot name=\"label\">\n {{ label }}\n </slot>\n </div>\n\n <slot />\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * GrSidebarGroup — секция пунктов `GrSidebar` с заголовком.\n *\n * В свёрнутой панели заголовку негде поместиться: он скрывается, а секции\n * разделяет линия — без неё иконки соседних секций сливаются в один столбец.\n */\nimport { computed, inject, useId } from 'vue'\n\nimport { groupCollapsedClass, groupLabelClass } from './grSidebarStyles'\nimport { GR_SIDEBAR_KEY } from './sidebarContext'\n\nexport interface GrSidebarGroupProps {\n /** Заголовок секции. Слот `#label` сильнее. */\n label?: string\n}\n\nconst props = withDefaults(defineProps<GrSidebarGroupProps>(), {\n label: undefined,\n})\n\nconst slots = defineSlots<{\n /** Пункты секции. */\n default?: () => unknown\n /** Заголовок целиком — вместо строки `label`. */\n label?: () => unknown\n}>()\n\nconst sidebar = inject(GR_SIDEBAR_KEY, null)\nconst collapsed = computed(() => sidebar?.collapsed.value ?? false)\n\nconst labelId = useId()\nconst hasLabel = computed(() => Boolean(props.label || slots.label))\n\n// Имя группе даёт только видимый заголовок: в свёрнутом виде его нет, и\n// `aria-labelledby` указывал бы в пустоту.\nconst showLabel = computed(() => hasLabel.value && !collapsed.value)\n</script>\n\n<template>\n <div\n data-gr-sidebar-group\n role=\"group\"\n :aria-labelledby=\"showLabel ? labelId : undefined\"\n :class=\"collapsed ? groupCollapsedClass : undefined\"\n >\n <div\n v-if=\"showLabel\"\n :id=\"labelId\"\n data-gr-sidebar-group-label\n :class=\"groupLabelClass\"\n >\n <slot name=\"label\">\n {{ label }}\n </slot>\n </div>\n\n <slot />\n </div>\n</template>\n"],"mappings":"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"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { n as grSidebarSafelist, t as grSidebarConfig } from "../../chunks/config-BfsBepkH2.js";
|
|
2
|
-
import { n as GrSidebarItem_default, r as GrSidebar_default, t as GrSidebarGroup_default } from "./chunks/GrSidebar-
|
|
2
|
+
import { n as GrSidebarItem_default, r as GrSidebar_default, t as GrSidebarGroup_default } from "./chunks/GrSidebar-D7WxRtIu.js";
|
|
3
3
|
export { GrSidebar_default as GrSidebar, GrSidebar_default as default, GrSidebarGroup_default as GrSidebarGroup, GrSidebarItem_default as GrSidebarItem, grSidebarConfig, grSidebarSafelist };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { n as useGranularityTranslations } from "../../../chunks/granularityI18n-BC5zsj_d.js";
|
|
2
|
-
import { t as GrCard_default } from "../../GrCard/chunks/GrCard-
|
|
2
|
+
import { t as GrCard_default } from "../../GrCard/chunks/GrCard-Bc7nRfbe.js";
|
|
3
3
|
import { useAnnouncer } from "../../../composables/useAnnouncer.js";
|
|
4
4
|
import { n as insertionIndex, r as moveItem, t as useDragSort } from "../../../chunks/useDragSort-0l47h4yb.js";
|
|
5
5
|
import { useRovingFocus } from "../../../composables/useRovingFocus.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GrSortableList-DJFPdUVv.js","names":[],"sources":["../../../../src/components/GrSortableList/GrSortableList.vue","../../../../src/components/GrSortableList/GrSortableList.vue"],"sourcesContent":["<script setup lang=\"ts\" generic=\"T\">\nimport { computed, nextTick, ref } from 'vue'\n\nimport GrCard, { type GrCardVariant } from '../GrCard'\nimport { useAnnouncer } from '../../composables/useAnnouncer'\nimport { useDragSort } from '../../composables/useDragSort'\nimport { useRovingFocus } from '../../composables/useRovingFocus'\nimport { insertionIndex, moveItem } from '../../composables/internal/dragSortGeometry'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\n\nimport type { GrSortableOrientation } from './grSortableListStyles'\nimport {\n contentClass,\n dividedClass,\n emptyClass,\n handleClass,\n handleDisabledClass,\n indicatorAfterClass,\n indicatorBeforeClass,\n listClass,\n rowDisabledClass,\n rowDraggingClass,\n rowFocusClass,\n rowGrabbedClass,\n rowLayoutClass,\n} from './grSortableListStyles'\n\nimport IconGripVertical from '~icons/lucide/grip-vertical'\n\nexport interface GrSortableListProps<TItem = unknown> {\n /** Набор в текущем порядке. `v-model`: наружу уходит новый массив, вход не мутируется. */\n modelValue: TItem[]\n /** Ключ элемента: имя поля или функция. Без него — индекс. */\n itemKey?: string | ((item: TItem, index: number) => string | number)\n /** Список только для чтения: ни указателем, ни с клавиатуры. */\n disabled?: boolean\n /** Ось переноса. `horizontal` — ряд с переносом по ширине. */\n orientation?: GrSortableOrientation\n /** Поверхность под списком — вариант карточки. */\n variant?: GrCardVariant\n /** Разделители между строками. */\n divided?: boolean\n /** Тянуть можно только за ручку. Выключено — тянется вся строка. */\n handleOnly?: boolean\n /** Высота видимой части: список становится скроллером с автопрокруткой при переносе. */\n maxHeight?: number | string\n /** Текст пустого состояния. Слот `#empty` сильнее. */\n emptyText?: string\n /** Имя списка для скринридера. Не задано — берётся из локали. */\n ariaLabel?: string\n}\n\nexport interface GrSortableListEmits<TItem = unknown> {\n (e: 'update:modelValue', value: TItem[]): void\n (e: 'move', from: number, to: number): void\n (e: 'change', value: TItem[]): void\n}\n\nconst props = withDefaults(defineProps<GrSortableListProps<T>>(), {\n itemKey: undefined,\n disabled: false,\n orientation: 'vertical',\n variant: undefined,\n divided: true,\n handleOnly: true,\n maxHeight: undefined,\n emptyText: undefined,\n ariaLabel: undefined,\n})\n\nconst emit = defineEmits<GrSortableListEmits<T>>()\n\ndefineSlots<{\n item?: (props: { item: T, index: number, dragging: boolean, grabbed: boolean }) => any\n handle?: (props: { item: T, index: number, disabled: boolean }) => any\n empty?: () => any\n}>()\n\nconst { t } = useGranularityTranslations()\nconst { announce } = useAnnouncer()\n\nconst listEl = ref<HTMLElement | null>(null)\nconst rowEls = new Map<string, HTMLElement>()\n\nconst count = computed(() => props.modelValue.length)\nconst isEmpty = computed(() => count.value === 0)\nconst isVertical = computed(() => props.orientation === 'vertical')\n\nfunction keyOf(item: T, index: number): string {\n if (typeof props.itemKey === 'function') return String(props.itemKey(item, index))\n if (typeof props.itemKey === 'string') return String((item as Record<string, unknown>)[props.itemKey])\n\n return String(index)\n}\n\nconst keys = computed(() => props.modelValue.map((item, index) => keyOf(item, index)))\n\nfunction indexOfKey(key: string): number {\n return keys.value.indexOf(key)\n}\n\nfunction registerRow(key: string, el: unknown): void {\n if (el instanceof HTMLElement) {\n rowEls.set(key, el)\n return\n }\n\n // Перестановка размонтирует старый узел уже после того, как встал новый:\n // снимаем запись, только если она протухла (приём из `GrTree`).\n const previous = rowEls.get(key)\n if (previous && !previous.isConnected) rowEls.delete(key)\n}\n\nconst roving = useRovingFocus<string>({\n items: () => keys.value,\n elementFor: key => rowEls.get(key) ?? null,\n orientation: () => (isVertical.value ? 'vertical' : 'horizontal'),\n wrap: () => false,\n})\n\nfunction applyMove(from: number, to: number): void {\n if (from < 0 || to < 0 || from === to) return\n\n const next = moveItem(props.modelValue, from, to)\n emit('update:modelValue', next)\n emit('move', from, to)\n emit('change', next)\n}\n\nconst sort = useDragSort<string, number>({\n items: () => keys.value,\n elementFor: key => rowEls.get(key) ?? null,\n orientation: () => props.orientation,\n scroller: () => (props.maxHeight === undefined ? null : listEl.value),\n disabled: () => props.disabled,\n resolveTarget: (hit, source) => insertionIndex(hit, indexOfKey(source), count.value),\n onDrop: (source, to) => applyMove(indexOfKey(source), to),\n onUpdate: (source, target, mode) => {\n // Объявляется только клавиатурный перенос: у мыши всё видно на экране, а\n // поток объявлений на каждом движении указателя делает диктор бесполезным.\n if (mode !== 'keyboard' || source === null) return\n\n const position = (target ?? indexOfKey(source)) + 1\n announce(t('gr.sortable.moved', 'Position {position} of {count}', { position, count: count.value }))\n },\n})\n\nconst activeIndex = computed(() => (sort.source.value === null ? -1 : indexOfKey(sort.source.value)))\n\n/** Куда встанет строка: индикатор рисуется на строке-соседе, а не отдельным узлом. */\nfunction indicatorFor(index: number): 'before' | 'after' | null {\n const target = sort.target.value\n if (target === null || !sort.isActive.value || index === activeIndex.value) return null\n\n if (target === index) return target < activeIndex.value || activeIndex.value === -1 ? 'before' : 'after'\n\n return null\n}\n\nfunction rowClass(index: number, key: string): string[] {\n const grabbed = sort.mode.value === 'keyboard' && sort.source.value === key\n const dragging = sort.mode.value === 'pointer' && sort.source.value === key\n const indicator = indicatorFor(index)\n\n return [\n rowLayoutClass,\n rowFocusClass,\n props.disabled ? rowDisabledClass : '',\n grabbed ? rowGrabbedClass : '',\n dragging ? rowDraggingClass : '',\n indicator === 'before' ? indicatorBeforeClass : '',\n indicator === 'after' ? indicatorAfterClass : '',\n ]\n}\n\nfunction startPointer(key: string, event: PointerEvent): void {\n if (props.disabled) return\n\n sort.startFrom(key)(event)\n}\n\nfunction grab(key: string): void {\n sort.grab(key)\n if (sort.mode.value !== 'keyboard') return\n\n // Цель ставится до объявления: `setTarget` сам объявляет позицию, и обратный\n // порядок стёр бы более полное сообщение о захвате.\n sort.setTarget(indexOfKey(key))\n announce(t('gr.sortable.grabbed', 'Grabbed. Position {position} of {count}', {\n position: indexOfKey(key) + 1,\n count: count.value,\n }))\n}\n\nasync function drop(key: string): Promise<void> {\n const target = sort.target.value\n sort.drop()\n\n announce(t('gr.sortable.dropped', 'Dropped at position {position} of {count}', {\n position: (target ?? 0) + 1,\n count: count.value,\n }))\n\n // Фокус остаётся на перенесённой строке: иначе после переноса он падает на\n // тело документа и клавиатурный сценарий обрывается на первом же шаге.\n await nextTick()\n await roving.focusKey(key)\n}\n\nfunction cancel(): void {\n if (!sort.isActive.value) return\n\n sort.cancel()\n announce(t('gr.sortable.cancelled', 'Move cancelled'))\n}\n\nfunction moveTargetBy(delta: number): void {\n const current = sort.target.value ?? activeIndex.value\n const next = Math.min(count.value - 1, Math.max(0, current + delta))\n\n if (next !== current) sort.setTarget(next)\n}\n\nfunction onRowKeydown(event: KeyboardEvent, key: string): void {\n if (props.disabled) return\n\n const forward = isVertical.value ? 'ArrowDown' : 'ArrowRight'\n const backward = isVertical.value ? 'ArrowUp' : 'ArrowLeft'\n const grabbed = sort.mode.value === 'keyboard'\n\n if (event.key === 'Escape') {\n if (!grabbed) return\n\n event.preventDefault()\n cancel()\n return\n }\n\n if (event.key === ' ' || event.key === 'Enter') {\n event.preventDefault()\n if (grabbed) void drop(key)\n else grab(key)\n return\n }\n\n if (grabbed) {\n // Взятая строка забирает стрелки себе: двигается она, а не фокус.\n if (event.key === forward || event.key === backward) {\n event.preventDefault()\n moveTargetBy(event.key === forward ? 1 : -1)\n }\n return\n }\n\n roving.handleNavigationKeys(event)\n}\n\n/** Фокус ушёл из списка — взятая строка не может остаться взятой навсегда. */\nfunction onFocusout(event: FocusEvent): void {\n if (sort.mode.value !== 'keyboard') return\n\n const next = event.relatedTarget as Node | null\n if (next && listEl.value?.contains(next)) return\n\n cancel()\n}\n\nconst resolvedEmptyText = computed(() => props.emptyText ?? t('gr.sortable.empty', 'Nothing to sort yet'))\nconst resolvedLabel = computed(() => props.ariaLabel ?? t('gr.sortable.label', 'Sortable list'))\nconst handleLabel = computed(() => t('gr.sortable.handle', 'Reorder'))\n\nconst listStyle = computed(() => (props.maxHeight === undefined\n ? undefined\n : { maxHeight: typeof props.maxHeight === 'number' ? `${props.maxHeight}px` : props.maxHeight, overflow: 'auto' }))\n\ndefineExpose({\n /** Программная перестановка — та же, что делает перенос. */\n move: (from: number, to: number) => applyMove(from, to),\n focusItem: (index: number) => roving.focusKey(keys.value[index]),\n})\n</script>\n\n<template>\n <GrCard :variant=\"variant\">\n <div\n ref=\"listEl\"\n role=\"list\"\n data-gr-sortable\n :aria-label=\"resolvedLabel\"\n :aria-disabled=\"disabled ? 'true' : undefined\"\n :class=\"[listClass[orientation], divided ? dividedClass : '']\"\n :style=\"listStyle\"\n @focusout=\"onFocusout\"\n >\n <div\n v-if=\"isEmpty\"\n data-gr-sortable-empty\n :class=\"emptyClass\"\n >\n <slot name=\"empty\">\n {{ resolvedEmptyText }}\n </slot>\n </div>\n\n <div\n v-for=\"(item, index) in modelValue\"\n v-else\n :key=\"keys[index]\"\n :ref=\"el => registerRow(keys[index], el)\"\n role=\"listitem\"\n data-gr-sortable-item\n :tabindex=\"disabled ? -1 : roving.tabindexFor(keys[index])\"\n :aria-roledescription=\"handleLabel\"\n :aria-posinset=\"index + 1\"\n :aria-setsize=\"count\"\n :aria-grabbed=\"sort.mode.value === 'keyboard' && sort.source.value === keys[index] ? 'true' : undefined\"\n :class=\"rowClass(index, keys[index])\"\n @keydown=\"onRowKeydown($event, keys[index])\"\n @focus=\"roving.setActive(keys[index])\"\n @pointerdown=\"handleOnly ? undefined : startPointer(keys[index], $event)\"\n >\n <button\n type=\"button\"\n tabindex=\"-1\"\n data-gr-sortable-handle\n :class=\"[handleClass, disabled ? handleDisabledClass : '']\"\n :aria-label=\"handleLabel\"\n :disabled=\"disabled\"\n @pointerdown=\"startPointer(keys[index], $event)\"\n @click.stop\n >\n <slot\n name=\"handle\"\n :item=\"item\"\n :index=\"index\"\n :disabled=\"disabled\"\n >\n <IconGripVertical class=\"block\" aria-hidden=\"true\" />\n </slot>\n </button>\n\n <div :class=\"contentClass\">\n <slot\n name=\"item\"\n :item=\"item\"\n :index=\"index\"\n :dragging=\"sort.mode.value === 'pointer' && sort.source.value === keys[index]\"\n :grabbed=\"sort.mode.value === 'keyboard' && sort.source.value === keys[index]\"\n >\n {{ item }}\n </slot>\n </div>\n </div>\n </div>\n </GrCard>\n</template>\n","<script setup lang=\"ts\" generic=\"T\">\nimport { computed, nextTick, ref } from 'vue'\n\nimport GrCard, { type GrCardVariant } from '../GrCard'\nimport { useAnnouncer } from '../../composables/useAnnouncer'\nimport { useDragSort } from '../../composables/useDragSort'\nimport { useRovingFocus } from '../../composables/useRovingFocus'\nimport { insertionIndex, moveItem } from '../../composables/internal/dragSortGeometry'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\n\nimport type { GrSortableOrientation } from './grSortableListStyles'\nimport {\n contentClass,\n dividedClass,\n emptyClass,\n handleClass,\n handleDisabledClass,\n indicatorAfterClass,\n indicatorBeforeClass,\n listClass,\n rowDisabledClass,\n rowDraggingClass,\n rowFocusClass,\n rowGrabbedClass,\n rowLayoutClass,\n} from './grSortableListStyles'\n\nimport IconGripVertical from '~icons/lucide/grip-vertical'\n\nexport interface GrSortableListProps<TItem = unknown> {\n /** Набор в текущем порядке. `v-model`: наружу уходит новый массив, вход не мутируется. */\n modelValue: TItem[]\n /** Ключ элемента: имя поля или функция. Без него — индекс. */\n itemKey?: string | ((item: TItem, index: number) => string | number)\n /** Список только для чтения: ни указателем, ни с клавиатуры. */\n disabled?: boolean\n /** Ось переноса. `horizontal` — ряд с переносом по ширине. */\n orientation?: GrSortableOrientation\n /** Поверхность под списком — вариант карточки. */\n variant?: GrCardVariant\n /** Разделители между строками. */\n divided?: boolean\n /** Тянуть можно только за ручку. Выключено — тянется вся строка. */\n handleOnly?: boolean\n /** Высота видимой части: список становится скроллером с автопрокруткой при переносе. */\n maxHeight?: number | string\n /** Текст пустого состояния. Слот `#empty` сильнее. */\n emptyText?: string\n /** Имя списка для скринридера. Не задано — берётся из локали. */\n ariaLabel?: string\n}\n\nexport interface GrSortableListEmits<TItem = unknown> {\n (e: 'update:modelValue', value: TItem[]): void\n (e: 'move', from: number, to: number): void\n (e: 'change', value: TItem[]): void\n}\n\nconst props = withDefaults(defineProps<GrSortableListProps<T>>(), {\n itemKey: undefined,\n disabled: false,\n orientation: 'vertical',\n variant: undefined,\n divided: true,\n handleOnly: true,\n maxHeight: undefined,\n emptyText: undefined,\n ariaLabel: undefined,\n})\n\nconst emit = defineEmits<GrSortableListEmits<T>>()\n\ndefineSlots<{\n item?: (props: { item: T, index: number, dragging: boolean, grabbed: boolean }) => any\n handle?: (props: { item: T, index: number, disabled: boolean }) => any\n empty?: () => any\n}>()\n\nconst { t } = useGranularityTranslations()\nconst { announce } = useAnnouncer()\n\nconst listEl = ref<HTMLElement | null>(null)\nconst rowEls = new Map<string, HTMLElement>()\n\nconst count = computed(() => props.modelValue.length)\nconst isEmpty = computed(() => count.value === 0)\nconst isVertical = computed(() => props.orientation === 'vertical')\n\nfunction keyOf(item: T, index: number): string {\n if (typeof props.itemKey === 'function') return String(props.itemKey(item, index))\n if (typeof props.itemKey === 'string') return String((item as Record<string, unknown>)[props.itemKey])\n\n return String(index)\n}\n\nconst keys = computed(() => props.modelValue.map((item, index) => keyOf(item, index)))\n\nfunction indexOfKey(key: string): number {\n return keys.value.indexOf(key)\n}\n\nfunction registerRow(key: string, el: unknown): void {\n if (el instanceof HTMLElement) {\n rowEls.set(key, el)\n return\n }\n\n // Перестановка размонтирует старый узел уже после того, как встал новый:\n // снимаем запись, только если она протухла (приём из `GrTree`).\n const previous = rowEls.get(key)\n if (previous && !previous.isConnected) rowEls.delete(key)\n}\n\nconst roving = useRovingFocus<string>({\n items: () => keys.value,\n elementFor: key => rowEls.get(key) ?? null,\n orientation: () => (isVertical.value ? 'vertical' : 'horizontal'),\n wrap: () => false,\n})\n\nfunction applyMove(from: number, to: number): void {\n if (from < 0 || to < 0 || from === to) return\n\n const next = moveItem(props.modelValue, from, to)\n emit('update:modelValue', next)\n emit('move', from, to)\n emit('change', next)\n}\n\nconst sort = useDragSort<string, number>({\n items: () => keys.value,\n elementFor: key => rowEls.get(key) ?? null,\n orientation: () => props.orientation,\n scroller: () => (props.maxHeight === undefined ? null : listEl.value),\n disabled: () => props.disabled,\n resolveTarget: (hit, source) => insertionIndex(hit, indexOfKey(source), count.value),\n onDrop: (source, to) => applyMove(indexOfKey(source), to),\n onUpdate: (source, target, mode) => {\n // Объявляется только клавиатурный перенос: у мыши всё видно на экране, а\n // поток объявлений на каждом движении указателя делает диктор бесполезным.\n if (mode !== 'keyboard' || source === null) return\n\n const position = (target ?? indexOfKey(source)) + 1\n announce(t('gr.sortable.moved', 'Position {position} of {count}', { position, count: count.value }))\n },\n})\n\nconst activeIndex = computed(() => (sort.source.value === null ? -1 : indexOfKey(sort.source.value)))\n\n/** Куда встанет строка: индикатор рисуется на строке-соседе, а не отдельным узлом. */\nfunction indicatorFor(index: number): 'before' | 'after' | null {\n const target = sort.target.value\n if (target === null || !sort.isActive.value || index === activeIndex.value) return null\n\n if (target === index) return target < activeIndex.value || activeIndex.value === -1 ? 'before' : 'after'\n\n return null\n}\n\nfunction rowClass(index: number, key: string): string[] {\n const grabbed = sort.mode.value === 'keyboard' && sort.source.value === key\n const dragging = sort.mode.value === 'pointer' && sort.source.value === key\n const indicator = indicatorFor(index)\n\n return [\n rowLayoutClass,\n rowFocusClass,\n props.disabled ? rowDisabledClass : '',\n grabbed ? rowGrabbedClass : '',\n dragging ? rowDraggingClass : '',\n indicator === 'before' ? indicatorBeforeClass : '',\n indicator === 'after' ? indicatorAfterClass : '',\n ]\n}\n\nfunction startPointer(key: string, event: PointerEvent): void {\n if (props.disabled) return\n\n sort.startFrom(key)(event)\n}\n\nfunction grab(key: string): void {\n sort.grab(key)\n if (sort.mode.value !== 'keyboard') return\n\n // Цель ставится до объявления: `setTarget` сам объявляет позицию, и обратный\n // порядок стёр бы более полное сообщение о захвате.\n sort.setTarget(indexOfKey(key))\n announce(t('gr.sortable.grabbed', 'Grabbed. Position {position} of {count}', {\n position: indexOfKey(key) + 1,\n count: count.value,\n }))\n}\n\nasync function drop(key: string): Promise<void> {\n const target = sort.target.value\n sort.drop()\n\n announce(t('gr.sortable.dropped', 'Dropped at position {position} of {count}', {\n position: (target ?? 0) + 1,\n count: count.value,\n }))\n\n // Фокус остаётся на перенесённой строке: иначе после переноса он падает на\n // тело документа и клавиатурный сценарий обрывается на первом же шаге.\n await nextTick()\n await roving.focusKey(key)\n}\n\nfunction cancel(): void {\n if (!sort.isActive.value) return\n\n sort.cancel()\n announce(t('gr.sortable.cancelled', 'Move cancelled'))\n}\n\nfunction moveTargetBy(delta: number): void {\n const current = sort.target.value ?? activeIndex.value\n const next = Math.min(count.value - 1, Math.max(0, current + delta))\n\n if (next !== current) sort.setTarget(next)\n}\n\nfunction onRowKeydown(event: KeyboardEvent, key: string): void {\n if (props.disabled) return\n\n const forward = isVertical.value ? 'ArrowDown' : 'ArrowRight'\n const backward = isVertical.value ? 'ArrowUp' : 'ArrowLeft'\n const grabbed = sort.mode.value === 'keyboard'\n\n if (event.key === 'Escape') {\n if (!grabbed) return\n\n event.preventDefault()\n cancel()\n return\n }\n\n if (event.key === ' ' || event.key === 'Enter') {\n event.preventDefault()\n if (grabbed) void drop(key)\n else grab(key)\n return\n }\n\n if (grabbed) {\n // Взятая строка забирает стрелки себе: двигается она, а не фокус.\n if (event.key === forward || event.key === backward) {\n event.preventDefault()\n moveTargetBy(event.key === forward ? 1 : -1)\n }\n return\n }\n\n roving.handleNavigationKeys(event)\n}\n\n/** Фокус ушёл из списка — взятая строка не может остаться взятой навсегда. */\nfunction onFocusout(event: FocusEvent): void {\n if (sort.mode.value !== 'keyboard') return\n\n const next = event.relatedTarget as Node | null\n if (next && listEl.value?.contains(next)) return\n\n cancel()\n}\n\nconst resolvedEmptyText = computed(() => props.emptyText ?? t('gr.sortable.empty', 'Nothing to sort yet'))\nconst resolvedLabel = computed(() => props.ariaLabel ?? t('gr.sortable.label', 'Sortable list'))\nconst handleLabel = computed(() => t('gr.sortable.handle', 'Reorder'))\n\nconst listStyle = computed(() => (props.maxHeight === undefined\n ? undefined\n : { maxHeight: typeof props.maxHeight === 'number' ? `${props.maxHeight}px` : props.maxHeight, overflow: 'auto' }))\n\ndefineExpose({\n /** Программная перестановка — та же, что делает перенос. */\n move: (from: number, to: number) => applyMove(from, to),\n focusItem: (index: number) => roving.focusKey(keys.value[index]),\n})\n</script>\n\n<template>\n <GrCard :variant=\"variant\">\n <div\n ref=\"listEl\"\n role=\"list\"\n data-gr-sortable\n :aria-label=\"resolvedLabel\"\n :aria-disabled=\"disabled ? 'true' : undefined\"\n :class=\"[listClass[orientation], divided ? dividedClass : '']\"\n :style=\"listStyle\"\n @focusout=\"onFocusout\"\n >\n <div\n v-if=\"isEmpty\"\n data-gr-sortable-empty\n :class=\"emptyClass\"\n >\n <slot name=\"empty\">\n {{ resolvedEmptyText }}\n </slot>\n </div>\n\n <div\n v-for=\"(item, index) in modelValue\"\n v-else\n :key=\"keys[index]\"\n :ref=\"el => registerRow(keys[index], el)\"\n role=\"listitem\"\n data-gr-sortable-item\n :tabindex=\"disabled ? -1 : roving.tabindexFor(keys[index])\"\n :aria-roledescription=\"handleLabel\"\n :aria-posinset=\"index + 1\"\n :aria-setsize=\"count\"\n :aria-grabbed=\"sort.mode.value === 'keyboard' && sort.source.value === keys[index] ? 'true' : undefined\"\n :class=\"rowClass(index, keys[index])\"\n @keydown=\"onRowKeydown($event, keys[index])\"\n @focus=\"roving.setActive(keys[index])\"\n @pointerdown=\"handleOnly ? undefined : startPointer(keys[index], $event)\"\n >\n <button\n type=\"button\"\n tabindex=\"-1\"\n data-gr-sortable-handle\n :class=\"[handleClass, disabled ? handleDisabledClass : '']\"\n :aria-label=\"handleLabel\"\n :disabled=\"disabled\"\n @pointerdown=\"startPointer(keys[index], $event)\"\n @click.stop\n >\n <slot\n name=\"handle\"\n :item=\"item\"\n :index=\"index\"\n :disabled=\"disabled\"\n >\n <IconGripVertical class=\"block\" aria-hidden=\"true\" />\n </slot>\n </button>\n\n <div :class=\"contentClass\">\n <slot\n name=\"item\"\n :item=\"item\"\n :index=\"index\"\n :dragging=\"sort.mode.value === 'pointer' && sort.source.value === keys[index]\"\n :grabbed=\"sort.mode.value === 'keyboard' && sort.source.value === keys[index]\"\n >\n {{ item }}\n </slot>\n </div>\n </div>\n </div>\n </GrCard>\n</template>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"GrSortableList-DPjxilmN.js","names":[],"sources":["../../../../src/components/GrSortableList/GrSortableList.vue","../../../../src/components/GrSortableList/GrSortableList.vue"],"sourcesContent":["<script setup lang=\"ts\" generic=\"T\">\nimport { computed, nextTick, ref } from 'vue'\n\nimport GrCard, { type GrCardVariant } from '../GrCard'\nimport { useAnnouncer } from '../../composables/useAnnouncer'\nimport { useDragSort } from '../../composables/useDragSort'\nimport { useRovingFocus } from '../../composables/useRovingFocus'\nimport { insertionIndex, moveItem } from '../../composables/internal/dragSortGeometry'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\n\nimport type { GrSortableOrientation } from './grSortableListStyles'\nimport {\n contentClass,\n dividedClass,\n emptyClass,\n handleClass,\n handleDisabledClass,\n indicatorAfterClass,\n indicatorBeforeClass,\n listClass,\n rowDisabledClass,\n rowDraggingClass,\n rowFocusClass,\n rowGrabbedClass,\n rowLayoutClass,\n} from './grSortableListStyles'\n\nimport IconGripVertical from '~icons/lucide/grip-vertical'\n\nexport interface GrSortableListProps<TItem = unknown> {\n /** Набор в текущем порядке. `v-model`: наружу уходит новый массив, вход не мутируется. */\n modelValue: TItem[]\n /** Ключ элемента: имя поля или функция. Без него — индекс. */\n itemKey?: string | ((item: TItem, index: number) => string | number)\n /** Список только для чтения: ни указателем, ни с клавиатуры. */\n disabled?: boolean\n /** Ось переноса. `horizontal` — ряд с переносом по ширине. */\n orientation?: GrSortableOrientation\n /** Поверхность под списком — вариант карточки. */\n variant?: GrCardVariant\n /** Разделители между строками. */\n divided?: boolean\n /** Тянуть можно только за ручку. Выключено — тянется вся строка. */\n handleOnly?: boolean\n /** Высота видимой части: список становится скроллером с автопрокруткой при переносе. */\n maxHeight?: number | string\n /** Текст пустого состояния. Слот `#empty` сильнее. */\n emptyText?: string\n /** Имя списка для скринридера. Не задано — берётся из локали. */\n ariaLabel?: string\n}\n\nexport interface GrSortableListEmits<TItem = unknown> {\n (e: 'update:modelValue', value: TItem[]): void\n (e: 'move', from: number, to: number): void\n (e: 'change', value: TItem[]): void\n}\n\nconst props = withDefaults(defineProps<GrSortableListProps<T>>(), {\n itemKey: undefined,\n disabled: false,\n orientation: 'vertical',\n variant: undefined,\n divided: true,\n handleOnly: true,\n maxHeight: undefined,\n emptyText: undefined,\n ariaLabel: undefined,\n})\n\nconst emit = defineEmits<GrSortableListEmits<T>>()\n\ndefineSlots<{\n item?: (props: { item: T, index: number, dragging: boolean, grabbed: boolean }) => any\n handle?: (props: { item: T, index: number, disabled: boolean }) => any\n empty?: () => any\n}>()\n\nconst { t } = useGranularityTranslations()\nconst { announce } = useAnnouncer()\n\nconst listEl = ref<HTMLElement | null>(null)\nconst rowEls = new Map<string, HTMLElement>()\n\nconst count = computed(() => props.modelValue.length)\nconst isEmpty = computed(() => count.value === 0)\nconst isVertical = computed(() => props.orientation === 'vertical')\n\nfunction keyOf(item: T, index: number): string {\n if (typeof props.itemKey === 'function') return String(props.itemKey(item, index))\n if (typeof props.itemKey === 'string') return String((item as Record<string, unknown>)[props.itemKey])\n\n return String(index)\n}\n\nconst keys = computed(() => props.modelValue.map((item, index) => keyOf(item, index)))\n\nfunction indexOfKey(key: string): number {\n return keys.value.indexOf(key)\n}\n\nfunction registerRow(key: string, el: unknown): void {\n if (el instanceof HTMLElement) {\n rowEls.set(key, el)\n return\n }\n\n // Перестановка размонтирует старый узел уже после того, как встал новый:\n // снимаем запись, только если она протухла (приём из `GrTree`).\n const previous = rowEls.get(key)\n if (previous && !previous.isConnected) rowEls.delete(key)\n}\n\nconst roving = useRovingFocus<string>({\n items: () => keys.value,\n elementFor: key => rowEls.get(key) ?? null,\n orientation: () => (isVertical.value ? 'vertical' : 'horizontal'),\n wrap: () => false,\n})\n\nfunction applyMove(from: number, to: number): void {\n if (from < 0 || to < 0 || from === to) return\n\n const next = moveItem(props.modelValue, from, to)\n emit('update:modelValue', next)\n emit('move', from, to)\n emit('change', next)\n}\n\nconst sort = useDragSort<string, number>({\n items: () => keys.value,\n elementFor: key => rowEls.get(key) ?? null,\n orientation: () => props.orientation,\n scroller: () => (props.maxHeight === undefined ? null : listEl.value),\n disabled: () => props.disabled,\n resolveTarget: (hit, source) => insertionIndex(hit, indexOfKey(source), count.value),\n onDrop: (source, to) => applyMove(indexOfKey(source), to),\n onUpdate: (source, target, mode) => {\n // Объявляется только клавиатурный перенос: у мыши всё видно на экране, а\n // поток объявлений на каждом движении указателя делает диктор бесполезным.\n if (mode !== 'keyboard' || source === null) return\n\n const position = (target ?? indexOfKey(source)) + 1\n announce(t('gr.sortable.moved', 'Position {position} of {count}', { position, count: count.value }))\n },\n})\n\nconst activeIndex = computed(() => (sort.source.value === null ? -1 : indexOfKey(sort.source.value)))\n\n/** Куда встанет строка: индикатор рисуется на строке-соседе, а не отдельным узлом. */\nfunction indicatorFor(index: number): 'before' | 'after' | null {\n const target = sort.target.value\n if (target === null || !sort.isActive.value || index === activeIndex.value) return null\n\n if (target === index) return target < activeIndex.value || activeIndex.value === -1 ? 'before' : 'after'\n\n return null\n}\n\nfunction rowClass(index: number, key: string): string[] {\n const grabbed = sort.mode.value === 'keyboard' && sort.source.value === key\n const dragging = sort.mode.value === 'pointer' && sort.source.value === key\n const indicator = indicatorFor(index)\n\n return [\n rowLayoutClass,\n rowFocusClass,\n props.disabled ? rowDisabledClass : '',\n grabbed ? rowGrabbedClass : '',\n dragging ? rowDraggingClass : '',\n indicator === 'before' ? indicatorBeforeClass : '',\n indicator === 'after' ? indicatorAfterClass : '',\n ]\n}\n\nfunction startPointer(key: string, event: PointerEvent): void {\n if (props.disabled) return\n\n sort.startFrom(key)(event)\n}\n\nfunction grab(key: string): void {\n sort.grab(key)\n if (sort.mode.value !== 'keyboard') return\n\n // Цель ставится до объявления: `setTarget` сам объявляет позицию, и обратный\n // порядок стёр бы более полное сообщение о захвате.\n sort.setTarget(indexOfKey(key))\n announce(t('gr.sortable.grabbed', 'Grabbed. Position {position} of {count}', {\n position: indexOfKey(key) + 1,\n count: count.value,\n }))\n}\n\nasync function drop(key: string): Promise<void> {\n const target = sort.target.value\n sort.drop()\n\n announce(t('gr.sortable.dropped', 'Dropped at position {position} of {count}', {\n position: (target ?? 0) + 1,\n count: count.value,\n }))\n\n // Фокус остаётся на перенесённой строке: иначе после переноса он падает на\n // тело документа и клавиатурный сценарий обрывается на первом же шаге.\n await nextTick()\n await roving.focusKey(key)\n}\n\nfunction cancel(): void {\n if (!sort.isActive.value) return\n\n sort.cancel()\n announce(t('gr.sortable.cancelled', 'Move cancelled'))\n}\n\nfunction moveTargetBy(delta: number): void {\n const current = sort.target.value ?? activeIndex.value\n const next = Math.min(count.value - 1, Math.max(0, current + delta))\n\n if (next !== current) sort.setTarget(next)\n}\n\nfunction onRowKeydown(event: KeyboardEvent, key: string): void {\n if (props.disabled) return\n\n const forward = isVertical.value ? 'ArrowDown' : 'ArrowRight'\n const backward = isVertical.value ? 'ArrowUp' : 'ArrowLeft'\n const grabbed = sort.mode.value === 'keyboard'\n\n if (event.key === 'Escape') {\n if (!grabbed) return\n\n event.preventDefault()\n cancel()\n return\n }\n\n if (event.key === ' ' || event.key === 'Enter') {\n event.preventDefault()\n if (grabbed) void drop(key)\n else grab(key)\n return\n }\n\n if (grabbed) {\n // Взятая строка забирает стрелки себе: двигается она, а не фокус.\n if (event.key === forward || event.key === backward) {\n event.preventDefault()\n moveTargetBy(event.key === forward ? 1 : -1)\n }\n return\n }\n\n roving.handleNavigationKeys(event)\n}\n\n/** Фокус ушёл из списка — взятая строка не может остаться взятой навсегда. */\nfunction onFocusout(event: FocusEvent): void {\n if (sort.mode.value !== 'keyboard') return\n\n const next = event.relatedTarget as Node | null\n if (next && listEl.value?.contains(next)) return\n\n cancel()\n}\n\nconst resolvedEmptyText = computed(() => props.emptyText ?? t('gr.sortable.empty', 'Nothing to sort yet'))\nconst resolvedLabel = computed(() => props.ariaLabel ?? t('gr.sortable.label', 'Sortable list'))\nconst handleLabel = computed(() => t('gr.sortable.handle', 'Reorder'))\n\nconst listStyle = computed(() => (props.maxHeight === undefined\n ? undefined\n : { maxHeight: typeof props.maxHeight === 'number' ? `${props.maxHeight}px` : props.maxHeight, overflow: 'auto' }))\n\ndefineExpose({\n /** Программная перестановка — та же, что делает перенос. */\n move: (from: number, to: number) => applyMove(from, to),\n focusItem: (index: number) => roving.focusKey(keys.value[index]),\n})\n</script>\n\n<template>\n <GrCard :variant=\"variant\">\n <div\n ref=\"listEl\"\n role=\"list\"\n data-gr-sortable\n :aria-label=\"resolvedLabel\"\n :aria-disabled=\"disabled ? 'true' : undefined\"\n :class=\"[listClass[orientation], divided ? dividedClass : '']\"\n :style=\"listStyle\"\n @focusout=\"onFocusout\"\n >\n <div\n v-if=\"isEmpty\"\n data-gr-sortable-empty\n :class=\"emptyClass\"\n >\n <slot name=\"empty\">\n {{ resolvedEmptyText }}\n </slot>\n </div>\n\n <div\n v-for=\"(item, index) in modelValue\"\n v-else\n :key=\"keys[index]\"\n :ref=\"el => registerRow(keys[index], el)\"\n role=\"listitem\"\n data-gr-sortable-item\n :tabindex=\"disabled ? -1 : roving.tabindexFor(keys[index])\"\n :aria-roledescription=\"handleLabel\"\n :aria-posinset=\"index + 1\"\n :aria-setsize=\"count\"\n :aria-grabbed=\"sort.mode.value === 'keyboard' && sort.source.value === keys[index] ? 'true' : undefined\"\n :class=\"rowClass(index, keys[index])\"\n @keydown=\"onRowKeydown($event, keys[index])\"\n @focus=\"roving.setActive(keys[index])\"\n @pointerdown=\"handleOnly ? undefined : startPointer(keys[index], $event)\"\n >\n <button\n type=\"button\"\n tabindex=\"-1\"\n data-gr-sortable-handle\n :class=\"[handleClass, disabled ? handleDisabledClass : '']\"\n :aria-label=\"handleLabel\"\n :disabled=\"disabled\"\n @pointerdown=\"startPointer(keys[index], $event)\"\n @click.stop\n >\n <slot\n name=\"handle\"\n :item=\"item\"\n :index=\"index\"\n :disabled=\"disabled\"\n >\n <IconGripVertical class=\"block\" aria-hidden=\"true\" />\n </slot>\n </button>\n\n <div :class=\"contentClass\">\n <slot\n name=\"item\"\n :item=\"item\"\n :index=\"index\"\n :dragging=\"sort.mode.value === 'pointer' && sort.source.value === keys[index]\"\n :grabbed=\"sort.mode.value === 'keyboard' && sort.source.value === keys[index]\"\n >\n {{ item }}\n </slot>\n </div>\n </div>\n </div>\n </GrCard>\n</template>\n","<script setup lang=\"ts\" generic=\"T\">\nimport { computed, nextTick, ref } from 'vue'\n\nimport GrCard, { type GrCardVariant } from '../GrCard'\nimport { useAnnouncer } from '../../composables/useAnnouncer'\nimport { useDragSort } from '../../composables/useDragSort'\nimport { useRovingFocus } from '../../composables/useRovingFocus'\nimport { insertionIndex, moveItem } from '../../composables/internal/dragSortGeometry'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\n\nimport type { GrSortableOrientation } from './grSortableListStyles'\nimport {\n contentClass,\n dividedClass,\n emptyClass,\n handleClass,\n handleDisabledClass,\n indicatorAfterClass,\n indicatorBeforeClass,\n listClass,\n rowDisabledClass,\n rowDraggingClass,\n rowFocusClass,\n rowGrabbedClass,\n rowLayoutClass,\n} from './grSortableListStyles'\n\nimport IconGripVertical from '~icons/lucide/grip-vertical'\n\nexport interface GrSortableListProps<TItem = unknown> {\n /** Набор в текущем порядке. `v-model`: наружу уходит новый массив, вход не мутируется. */\n modelValue: TItem[]\n /** Ключ элемента: имя поля или функция. Без него — индекс. */\n itemKey?: string | ((item: TItem, index: number) => string | number)\n /** Список только для чтения: ни указателем, ни с клавиатуры. */\n disabled?: boolean\n /** Ось переноса. `horizontal` — ряд с переносом по ширине. */\n orientation?: GrSortableOrientation\n /** Поверхность под списком — вариант карточки. */\n variant?: GrCardVariant\n /** Разделители между строками. */\n divided?: boolean\n /** Тянуть можно только за ручку. Выключено — тянется вся строка. */\n handleOnly?: boolean\n /** Высота видимой части: список становится скроллером с автопрокруткой при переносе. */\n maxHeight?: number | string\n /** Текст пустого состояния. Слот `#empty` сильнее. */\n emptyText?: string\n /** Имя списка для скринридера. Не задано — берётся из локали. */\n ariaLabel?: string\n}\n\nexport interface GrSortableListEmits<TItem = unknown> {\n (e: 'update:modelValue', value: TItem[]): void\n (e: 'move', from: number, to: number): void\n (e: 'change', value: TItem[]): void\n}\n\nconst props = withDefaults(defineProps<GrSortableListProps<T>>(), {\n itemKey: undefined,\n disabled: false,\n orientation: 'vertical',\n variant: undefined,\n divided: true,\n handleOnly: true,\n maxHeight: undefined,\n emptyText: undefined,\n ariaLabel: undefined,\n})\n\nconst emit = defineEmits<GrSortableListEmits<T>>()\n\ndefineSlots<{\n item?: (props: { item: T, index: number, dragging: boolean, grabbed: boolean }) => any\n handle?: (props: { item: T, index: number, disabled: boolean }) => any\n empty?: () => any\n}>()\n\nconst { t } = useGranularityTranslations()\nconst { announce } = useAnnouncer()\n\nconst listEl = ref<HTMLElement | null>(null)\nconst rowEls = new Map<string, HTMLElement>()\n\nconst count = computed(() => props.modelValue.length)\nconst isEmpty = computed(() => count.value === 0)\nconst isVertical = computed(() => props.orientation === 'vertical')\n\nfunction keyOf(item: T, index: number): string {\n if (typeof props.itemKey === 'function') return String(props.itemKey(item, index))\n if (typeof props.itemKey === 'string') return String((item as Record<string, unknown>)[props.itemKey])\n\n return String(index)\n}\n\nconst keys = computed(() => props.modelValue.map((item, index) => keyOf(item, index)))\n\nfunction indexOfKey(key: string): number {\n return keys.value.indexOf(key)\n}\n\nfunction registerRow(key: string, el: unknown): void {\n if (el instanceof HTMLElement) {\n rowEls.set(key, el)\n return\n }\n\n // Перестановка размонтирует старый узел уже после того, как встал новый:\n // снимаем запись, только если она протухла (приём из `GrTree`).\n const previous = rowEls.get(key)\n if (previous && !previous.isConnected) rowEls.delete(key)\n}\n\nconst roving = useRovingFocus<string>({\n items: () => keys.value,\n elementFor: key => rowEls.get(key) ?? null,\n orientation: () => (isVertical.value ? 'vertical' : 'horizontal'),\n wrap: () => false,\n})\n\nfunction applyMove(from: number, to: number): void {\n if (from < 0 || to < 0 || from === to) return\n\n const next = moveItem(props.modelValue, from, to)\n emit('update:modelValue', next)\n emit('move', from, to)\n emit('change', next)\n}\n\nconst sort = useDragSort<string, number>({\n items: () => keys.value,\n elementFor: key => rowEls.get(key) ?? null,\n orientation: () => props.orientation,\n scroller: () => (props.maxHeight === undefined ? null : listEl.value),\n disabled: () => props.disabled,\n resolveTarget: (hit, source) => insertionIndex(hit, indexOfKey(source), count.value),\n onDrop: (source, to) => applyMove(indexOfKey(source), to),\n onUpdate: (source, target, mode) => {\n // Объявляется только клавиатурный перенос: у мыши всё видно на экране, а\n // поток объявлений на каждом движении указателя делает диктор бесполезным.\n if (mode !== 'keyboard' || source === null) return\n\n const position = (target ?? indexOfKey(source)) + 1\n announce(t('gr.sortable.moved', 'Position {position} of {count}', { position, count: count.value }))\n },\n})\n\nconst activeIndex = computed(() => (sort.source.value === null ? -1 : indexOfKey(sort.source.value)))\n\n/** Куда встанет строка: индикатор рисуется на строке-соседе, а не отдельным узлом. */\nfunction indicatorFor(index: number): 'before' | 'after' | null {\n const target = sort.target.value\n if (target === null || !sort.isActive.value || index === activeIndex.value) return null\n\n if (target === index) return target < activeIndex.value || activeIndex.value === -1 ? 'before' : 'after'\n\n return null\n}\n\nfunction rowClass(index: number, key: string): string[] {\n const grabbed = sort.mode.value === 'keyboard' && sort.source.value === key\n const dragging = sort.mode.value === 'pointer' && sort.source.value === key\n const indicator = indicatorFor(index)\n\n return [\n rowLayoutClass,\n rowFocusClass,\n props.disabled ? rowDisabledClass : '',\n grabbed ? rowGrabbedClass : '',\n dragging ? rowDraggingClass : '',\n indicator === 'before' ? indicatorBeforeClass : '',\n indicator === 'after' ? indicatorAfterClass : '',\n ]\n}\n\nfunction startPointer(key: string, event: PointerEvent): void {\n if (props.disabled) return\n\n sort.startFrom(key)(event)\n}\n\nfunction grab(key: string): void {\n sort.grab(key)\n if (sort.mode.value !== 'keyboard') return\n\n // Цель ставится до объявления: `setTarget` сам объявляет позицию, и обратный\n // порядок стёр бы более полное сообщение о захвате.\n sort.setTarget(indexOfKey(key))\n announce(t('gr.sortable.grabbed', 'Grabbed. Position {position} of {count}', {\n position: indexOfKey(key) + 1,\n count: count.value,\n }))\n}\n\nasync function drop(key: string): Promise<void> {\n const target = sort.target.value\n sort.drop()\n\n announce(t('gr.sortable.dropped', 'Dropped at position {position} of {count}', {\n position: (target ?? 0) + 1,\n count: count.value,\n }))\n\n // Фокус остаётся на перенесённой строке: иначе после переноса он падает на\n // тело документа и клавиатурный сценарий обрывается на первом же шаге.\n await nextTick()\n await roving.focusKey(key)\n}\n\nfunction cancel(): void {\n if (!sort.isActive.value) return\n\n sort.cancel()\n announce(t('gr.sortable.cancelled', 'Move cancelled'))\n}\n\nfunction moveTargetBy(delta: number): void {\n const current = sort.target.value ?? activeIndex.value\n const next = Math.min(count.value - 1, Math.max(0, current + delta))\n\n if (next !== current) sort.setTarget(next)\n}\n\nfunction onRowKeydown(event: KeyboardEvent, key: string): void {\n if (props.disabled) return\n\n const forward = isVertical.value ? 'ArrowDown' : 'ArrowRight'\n const backward = isVertical.value ? 'ArrowUp' : 'ArrowLeft'\n const grabbed = sort.mode.value === 'keyboard'\n\n if (event.key === 'Escape') {\n if (!grabbed) return\n\n event.preventDefault()\n cancel()\n return\n }\n\n if (event.key === ' ' || event.key === 'Enter') {\n event.preventDefault()\n if (grabbed) void drop(key)\n else grab(key)\n return\n }\n\n if (grabbed) {\n // Взятая строка забирает стрелки себе: двигается она, а не фокус.\n if (event.key === forward || event.key === backward) {\n event.preventDefault()\n moveTargetBy(event.key === forward ? 1 : -1)\n }\n return\n }\n\n roving.handleNavigationKeys(event)\n}\n\n/** Фокус ушёл из списка — взятая строка не может остаться взятой навсегда. */\nfunction onFocusout(event: FocusEvent): void {\n if (sort.mode.value !== 'keyboard') return\n\n const next = event.relatedTarget as Node | null\n if (next && listEl.value?.contains(next)) return\n\n cancel()\n}\n\nconst resolvedEmptyText = computed(() => props.emptyText ?? t('gr.sortable.empty', 'Nothing to sort yet'))\nconst resolvedLabel = computed(() => props.ariaLabel ?? t('gr.sortable.label', 'Sortable list'))\nconst handleLabel = computed(() => t('gr.sortable.handle', 'Reorder'))\n\nconst listStyle = computed(() => (props.maxHeight === undefined\n ? undefined\n : { maxHeight: typeof props.maxHeight === 'number' ? `${props.maxHeight}px` : props.maxHeight, overflow: 'auto' }))\n\ndefineExpose({\n /** Программная перестановка — та же, что делает перенос. */\n move: (from: number, to: number) => applyMove(from, to),\n focusItem: (index: number) => roving.focusKey(keys.value[index]),\n})\n</script>\n\n<template>\n <GrCard :variant=\"variant\">\n <div\n ref=\"listEl\"\n role=\"list\"\n data-gr-sortable\n :aria-label=\"resolvedLabel\"\n :aria-disabled=\"disabled ? 'true' : undefined\"\n :class=\"[listClass[orientation], divided ? dividedClass : '']\"\n :style=\"listStyle\"\n @focusout=\"onFocusout\"\n >\n <div\n v-if=\"isEmpty\"\n data-gr-sortable-empty\n :class=\"emptyClass\"\n >\n <slot name=\"empty\">\n {{ resolvedEmptyText }}\n </slot>\n </div>\n\n <div\n v-for=\"(item, index) in modelValue\"\n v-else\n :key=\"keys[index]\"\n :ref=\"el => registerRow(keys[index], el)\"\n role=\"listitem\"\n data-gr-sortable-item\n :tabindex=\"disabled ? -1 : roving.tabindexFor(keys[index])\"\n :aria-roledescription=\"handleLabel\"\n :aria-posinset=\"index + 1\"\n :aria-setsize=\"count\"\n :aria-grabbed=\"sort.mode.value === 'keyboard' && sort.source.value === keys[index] ? 'true' : undefined\"\n :class=\"rowClass(index, keys[index])\"\n @keydown=\"onRowKeydown($event, keys[index])\"\n @focus=\"roving.setActive(keys[index])\"\n @pointerdown=\"handleOnly ? undefined : startPointer(keys[index], $event)\"\n >\n <button\n type=\"button\"\n tabindex=\"-1\"\n data-gr-sortable-handle\n :class=\"[handleClass, disabled ? handleDisabledClass : '']\"\n :aria-label=\"handleLabel\"\n :disabled=\"disabled\"\n @pointerdown=\"startPointer(keys[index], $event)\"\n @click.stop\n >\n <slot\n name=\"handle\"\n :item=\"item\"\n :index=\"index\"\n :disabled=\"disabled\"\n >\n <IconGripVertical class=\"block\" aria-hidden=\"true\" />\n </slot>\n </button>\n\n <div :class=\"contentClass\">\n <slot\n name=\"item\"\n :item=\"item\"\n :index=\"index\"\n :dragging=\"sort.mode.value === 'pointer' && sort.source.value === keys[index]\"\n :grabbed=\"sort.mode.value === 'keyboard' && sort.source.value === keys[index]\"\n >\n {{ item }}\n </slot>\n </div>\n </div>\n </div>\n </GrCard>\n</template>\n"],"mappings":"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"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { n as grSortableListSafelist, t as grSortableListConfig } from "../../chunks/config-DAX8Deh12.js";
|
|
2
|
-
import { t as GrSortableList_default } from "./chunks/GrSortableList-
|
|
2
|
+
import { t as GrSortableList_default } from "./chunks/GrSortableList-DPjxilmN.js";
|
|
3
3
|
export { GrSortableList_default as GrSortableList, GrSortableList_default as default, grSortableListConfig, grSortableListSafelist };
|
package/dist/components/GrStatistic/chunks/{GrStatistic-C3VpykHo.js → GrStatistic-DkzWUAw1.js}
RENAMED
|
@@ -3,48 +3,9 @@ import { n as useGranularityTranslations } from "../../../chunks/granularityI18n
|
|
|
3
3
|
import { t as GrSkeleton_default } from "../../GrSkeleton/chunks/GrSkeleton-CgfXxPQ2.js";
|
|
4
4
|
import { n as iconTag, t as iconClass } from "../../../chunks/icon-Dag2tIMs.js";
|
|
5
5
|
import { t as minus_default } from "../../../chunks/minus-oxdbHF33.js";
|
|
6
|
+
import { n as trending_down_default, t as trending_up_default } from "../../../chunks/trending-up-Bf9AQmoq.js";
|
|
6
7
|
import { a as statisticRootClass, c as statisticTrendClassByTrend, i as statisticPlaceholderHeightBySize, l as statisticTrendSizeBySize, o as statisticTitleClass, r as statisticAffixSizeBySize, s as statisticTitleSizeBySize, u as statisticValueClass } from "../../../chunks/config-Be9k9bOf.js";
|
|
7
8
|
import { computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, markRaw, normalizeClass, onBeforeUnmount, onMounted, openBlock, ref, renderSlot, resolveDynamicComponent, toDisplayString, unref, useSlots, watch, withCtx } from "vue";
|
|
8
|
-
//#region ~icons/lucide/trending-down
|
|
9
|
-
var _hoisted_1$2 = {
|
|
10
|
-
viewBox: "0 0 24 24",
|
|
11
|
-
width: "1.2em",
|
|
12
|
-
height: "1.2em"
|
|
13
|
-
};
|
|
14
|
-
function render$2(_ctx, _cache) {
|
|
15
|
-
return openBlock(), createElementBlock("svg", _hoisted_1$2, [..._cache[0] || (_cache[0] = [createElementVNode("g", {
|
|
16
|
-
fill: "none",
|
|
17
|
-
stroke: "currentColor",
|
|
18
|
-
"stroke-linecap": "round",
|
|
19
|
-
"stroke-linejoin": "round",
|
|
20
|
-
"stroke-width": "2"
|
|
21
|
-
}, [createElementVNode("path", { d: "M16 17h6v-6" }), createElementVNode("path", { d: "m22 17l-8.5-8.5l-5 5L2 7" })], -1)])]);
|
|
22
|
-
}
|
|
23
|
-
var trending_down_default = markRaw({
|
|
24
|
-
name: "lucide-trending-down",
|
|
25
|
-
render: render$2
|
|
26
|
-
});
|
|
27
|
-
//#endregion
|
|
28
|
-
//#region ~icons/lucide/trending-up
|
|
29
|
-
var _hoisted_1$1 = {
|
|
30
|
-
viewBox: "0 0 24 24",
|
|
31
|
-
width: "1.2em",
|
|
32
|
-
height: "1.2em"
|
|
33
|
-
};
|
|
34
|
-
function render$1(_ctx, _cache) {
|
|
35
|
-
return openBlock(), createElementBlock("svg", _hoisted_1$1, [..._cache[0] || (_cache[0] = [createElementVNode("g", {
|
|
36
|
-
fill: "none",
|
|
37
|
-
stroke: "currentColor",
|
|
38
|
-
"stroke-linecap": "round",
|
|
39
|
-
"stroke-linejoin": "round",
|
|
40
|
-
"stroke-width": "2"
|
|
41
|
-
}, [createElementVNode("path", { d: "M16 7h6v6" }), createElementVNode("path", { d: "m22 7l-8.5 8.5l-5-5L2 17" })], -1)])]);
|
|
42
|
-
}
|
|
43
|
-
var trending_up_default = markRaw({
|
|
44
|
-
name: "lucide-trending-up",
|
|
45
|
-
render: render$1
|
|
46
|
-
});
|
|
47
|
-
//#endregion
|
|
48
9
|
//#region src/components/GrStatistic/countUp.ts
|
|
49
10
|
/**
|
|
50
11
|
* Перебор чисел для `GrStatistic`: чистая арифметика кадра без Vue и таймеров.
|
|
@@ -209,6 +170,12 @@ var GrStatistic_default = /* @__PURE__ */ defineComponent({
|
|
|
209
170
|
if (props.href) return "a";
|
|
210
171
|
return props.clickable ? "button" : "div";
|
|
211
172
|
});
|
|
173
|
+
/**
|
|
174
|
+
* Компонент-ссылка (`Link` от Inertia, `RouterLink`) рендерит `<a>` сам, и без
|
|
175
|
+
* `href` он ведёт в никуда. Строковый тег, кроме `a`, атрибут не понимает —
|
|
176
|
+
* там он и гасится.
|
|
177
|
+
*/
|
|
178
|
+
const rootHref = computed(() => typeof rootTag.value === "string" && rootTag.value !== "a" ? void 0 : props.href);
|
|
212
179
|
const rootClass = computed(() => statisticRootClass({ interactive: isInteractive.value }));
|
|
213
180
|
function toNumber(value) {
|
|
214
181
|
const numeric = typeof value === "number" ? value : Number(value);
|
|
@@ -287,7 +254,7 @@ var GrStatistic_default = /* @__PURE__ */ defineComponent({
|
|
|
287
254
|
return openBlock(), createBlock(resolveDynamicComponent(rootTag.value), {
|
|
288
255
|
"data-gr-statistic": "",
|
|
289
256
|
type: rootTag.value === "button" ? "button" : void 0,
|
|
290
|
-
href:
|
|
257
|
+
href: rootHref.value,
|
|
291
258
|
class: normalizeClass(rootClass.value),
|
|
292
259
|
onClick: _cache[0] || (_cache[0] = ($event) => emit("click", $event))
|
|
293
260
|
}, {
|