@feugene/granularity 0.41.0 → 0.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/LICENSE +13 -162
  2. package/README.md +4 -4
  3. package/dist/chunks/{config-BRESvs9g.js → config-Ca8qr8Q3.js} +1 -1
  4. package/dist/chunks/{config-BRESvs9g.js.map → config-Ca8qr8Q3.js.map} +1 -1
  5. package/dist/chunks/{en-CuuLfNHX.js → en-B9E3jO6m.js} +5 -1
  6. package/dist/chunks/{en-CuuLfNHX.js.map → en-B9E3jO6m.js.map} +1 -1
  7. package/dist/chunks/{es-CsmvfESn.js → es-DCkqHTlU.js} +3 -3
  8. package/dist/chunks/{es-CsmvfESn.js.map → es-DCkqHTlU.js.map} +1 -1
  9. package/dist/chunks/{es-CSnmrcMw.js → es-QpzdIEut.js} +5 -1
  10. package/dist/chunks/{es-CSnmrcMw.js.map → es-QpzdIEut.js.map} +1 -1
  11. package/dist/chunks/{grKbdStyles-DENu8pUK.js → grKbdStyles-ByMsX-lQ.js} +17 -2
  12. package/dist/chunks/grKbdStyles-ByMsX-lQ.js.map +1 -0
  13. package/dist/chunks/{granular-provider-BshNi2la.js → granular-provider-BG_2XqkS.js} +1 -1
  14. package/dist/chunks/{granular-provider-BshNi2la.js.map → granular-provider-BG_2XqkS.js.map} +1 -1
  15. package/dist/chunks/{ru-BKWafECI.js → ru-D_1Ya489.js} +5 -1
  16. package/dist/chunks/{ru-BKWafECI.js.map → ru-D_1Ya489.js.map} +1 -1
  17. package/dist/components/GrBreadcrumbs/chunks/{GrBreadcrumbs-DOpwY2EF.js → GrBreadcrumbs-D0qPry_R.js} +4 -1
  18. package/dist/components/GrBreadcrumbs/chunks/GrBreadcrumbs-D0qPry_R.js.map +1 -0
  19. package/dist/components/GrBreadcrumbs/index.js +1 -1
  20. package/dist/components/GrCommandPalette/chunks/{GrCommandPalette-CnzPuoIM.js → GrCommandPalette-Ha4s3DMT.js} +1 -1
  21. package/dist/components/GrCommandPalette/chunks/{GrCommandPalette-CnzPuoIM.js.map → GrCommandPalette-Ha4s3DMT.js.map} +1 -1
  22. package/dist/components/GrCommandPalette/index.js +1 -1
  23. package/dist/components/GrDataTable/chunks/{GrDataTable-DpyipOFx.js → GrDataTable-BBe7-uUe.js} +5 -1
  24. package/dist/components/GrDataTable/chunks/GrDataTable-BBe7-uUe.js.map +1 -0
  25. package/dist/components/GrDataTable/index.js +1 -1
  26. package/dist/components/GrDelta/chunks/{GrDelta-BpfqebEV.js → GrDelta-CgTvfoIN.js} +0 -0
  27. package/dist/components/GrDelta/chunks/GrDelta-CgTvfoIN.js.map +1 -0
  28. package/dist/components/GrDelta/index.js +1 -1
  29. package/dist/components/GrDescriptionList/chunks/{GrDescriptionList-D8ANFpn_.js → GrDescriptionList-Dz2Jj1if.js} +26 -11
  30. package/dist/components/GrDescriptionList/chunks/GrDescriptionList-Dz2Jj1if.js.map +1 -0
  31. package/dist/components/GrDescriptionList/index.js +1 -1
  32. package/dist/components/GrFilePreview/chunks/{GrFilePreview-PabkLgYX.js → GrFilePreview-2kVOg1C7.js} +40 -5
  33. package/dist/components/GrFilePreview/chunks/GrFilePreview-2kVOg1C7.js.map +1 -0
  34. package/dist/components/GrFilePreview/index.js +1 -1
  35. package/dist/components/GrInputTag/chunks/{GrInputTag-3Au5HWZL.js → GrInputTag-BKzm1PKw.js} +4 -1
  36. package/dist/components/GrInputTag/chunks/GrInputTag-BKzm1PKw.js.map +1 -0
  37. package/dist/components/GrInputTag/index.js +1 -1
  38. package/dist/components/GrJsonViewer/chunks/{GrJsonViewer-CDGOX3C0.js → GrJsonViewer-BLCEWlwg.js} +32 -6
  39. package/dist/components/GrJsonViewer/chunks/GrJsonViewer-BLCEWlwg.js.map +1 -0
  40. package/dist/components/GrJsonViewer/index.js +1 -1
  41. package/dist/components/GrKbd/chunks/{GrKbd-D_a1mPLe.js → GrKbd-BNMeoaLV.js} +1 -1
  42. package/dist/components/GrKbd/chunks/{GrKbd-D_a1mPLe.js.map → GrKbd-BNMeoaLV.js.map} +1 -1
  43. package/dist/components/GrKbd/index.js +2 -2
  44. package/dist/components/GrNumberInput/chunks/{GrNumberInput-BqPydXpK.js → GrNumberInput-Bm-V59TG.js} +4 -1
  45. package/dist/components/GrNumberInput/chunks/GrNumberInput-Bm-V59TG.js.map +1 -0
  46. package/dist/components/GrNumberInput/index.js +1 -1
  47. package/dist/components/GrSegmented/chunks/{GrSegmented-NKTi9bMG.js → GrSegmented-CuDaKA6S.js} +5 -1
  48. package/dist/components/GrSegmented/chunks/GrSegmented-CuDaKA6S.js.map +1 -0
  49. package/dist/components/GrSegmented/index.js +1 -1
  50. package/dist/components/GrSortableList/chunks/{GrSortableList-CQCaWivn.js → GrSortableList-ce1psLL0.js} +4 -1
  51. package/dist/components/GrSortableList/chunks/GrSortableList-ce1psLL0.js.map +1 -0
  52. package/dist/components/GrSortableList/index.js +1 -1
  53. package/dist/components/GrSteps/chunks/{GrSteps-DXj7m-WO.js → GrSteps-Cng-Wrjk.js} +5 -1
  54. package/dist/components/GrSteps/chunks/GrSteps-Cng-Wrjk.js.map +1 -0
  55. package/dist/components/GrSteps/index.js +1 -1
  56. package/dist/components/GrTabs/chunks/{GrTabs-DHfv67cp.js → GrTabs-BArefUFv.js} +5 -1
  57. package/dist/components/GrTabs/chunks/GrTabs-BArefUFv.js.map +1 -0
  58. package/dist/components/GrTabs/index.js +1 -1
  59. package/dist/granular-provider-node.js +1 -1
  60. package/dist/granular-provider.js +1 -1
  61. package/dist/i18n/all.js +1 -1
  62. package/dist/i18n/index.js +1 -1
  63. package/dist/index.js +15 -15
  64. package/dist/types/components/GrFilePreview/GrFilePreview.vue.d.ts +14 -0
  65. package/dist/types/components/GrKbd/grKbdStyles.d.ts +17 -2
  66. package/dist/types/i18n/messages/en.d.ts +12 -0
  67. package/dist/types/i18n/messages/es.d.ts +12 -0
  68. package/dist/types/i18n/messages/ru.d.ts +12 -0
  69. package/dist/web-types.json +19 -1
  70. package/package.json +3 -3
  71. package/dist/chunks/grKbdStyles-DENu8pUK.js.map +0 -1
  72. package/dist/components/GrBreadcrumbs/chunks/GrBreadcrumbs-DOpwY2EF.js.map +0 -1
  73. package/dist/components/GrDataTable/chunks/GrDataTable-DpyipOFx.js.map +0 -1
  74. package/dist/components/GrDelta/chunks/GrDelta-BpfqebEV.js.map +0 -1
  75. package/dist/components/GrDescriptionList/chunks/GrDescriptionList-D8ANFpn_.js.map +0 -1
  76. package/dist/components/GrFilePreview/chunks/GrFilePreview-PabkLgYX.js.map +0 -1
  77. package/dist/components/GrInputTag/chunks/GrInputTag-3Au5HWZL.js.map +0 -1
  78. package/dist/components/GrJsonViewer/chunks/GrJsonViewer-CDGOX3C0.js.map +0 -1
  79. package/dist/components/GrNumberInput/chunks/GrNumberInput-BqPydXpK.js.map +0 -1
  80. package/dist/components/GrSegmented/chunks/GrSegmented-NKTi9bMG.js.map +0 -1
  81. package/dist/components/GrSortableList/chunks/GrSortableList-CQCaWivn.js.map +0 -1
  82. package/dist/components/GrSteps/chunks/GrSteps-DXj7m-WO.js.map +0 -1
  83. package/dist/components/GrTabs/chunks/GrTabs-DHfv67cp.js.map +0 -1
@@ -276,6 +276,12 @@ export declare const en: {
276
276
  recent: string;
277
277
  loading: string;
278
278
  };
279
+ delta: {
280
+ increase: string;
281
+ };
282
+ filePreview: {
283
+ label: string;
284
+ };
279
285
  fileValidation: {
280
286
  accept: string;
281
287
  extension: string;
@@ -631,6 +637,12 @@ export declare const en: {
631
637
  recent: string;
632
638
  loading: string;
633
639
  };
640
+ delta: {
641
+ increase: string;
642
+ };
643
+ filePreview: {
644
+ label: string;
645
+ };
634
646
  fileValidation: {
635
647
  accept: string;
636
648
  extension: string;
@@ -271,6 +271,12 @@ export declare const es: {
271
271
  recent: string;
272
272
  loading: string;
273
273
  };
274
+ delta: {
275
+ increase: string;
276
+ };
277
+ filePreview: {
278
+ label: string;
279
+ };
274
280
  fileValidation: {
275
281
  accept: string;
276
282
  extension: string;
@@ -628,6 +634,12 @@ export declare const es: {
628
634
  recent: string;
629
635
  loading: string;
630
636
  };
637
+ delta: {
638
+ increase: string;
639
+ };
640
+ filePreview: {
641
+ label: string;
642
+ };
631
643
  fileValidation: {
632
644
  accept: string;
633
645
  extension: string;
@@ -273,6 +273,12 @@ export declare const ru: {
273
273
  recent: string;
274
274
  loading: string;
275
275
  };
276
+ delta: {
277
+ increase: string;
278
+ };
279
+ filePreview: {
280
+ label: string;
281
+ };
276
282
  fileValidation: {
277
283
  accept: string;
278
284
  extension: string;
@@ -632,6 +638,12 @@ export declare const ru: {
632
638
  recent: string;
633
639
  loading: string;
634
640
  };
641
+ delta: {
642
+ increase: string;
643
+ };
644
+ filePreview: {
645
+ label: string;
646
+ };
635
647
  fileValidation: {
636
648
  accept: string;
637
649
  extension: string;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://raw.githubusercontent.com/JetBrains/web-types/master/schema/web-types.json",
3
3
  "name": "@feugene/granularity",
4
- "version": "0.41.0",
4
+ "version": "0.42.0",
5
5
  "framework": "vue",
6
6
  "js-types-syntax": "typescript",
7
7
  "description-markup": "markdown",
@@ -4050,6 +4050,24 @@
4050
4050
  "type": "string | undefined"
4051
4051
  }
4052
4052
  },
4053
+ {
4054
+ "name": "target",
4055
+ "description": "Куда открывать ссылку. Объявлен пропом, а не оставлен на fallthrough: иначе `_blank` уезжал бы на `<a>` мимо компонента, и защитный `rel` подставить было бы негде.",
4056
+ "default": "undefined",
4057
+ "value": {
4058
+ "kind": "expression",
4059
+ "type": "string | undefined"
4060
+ }
4061
+ },
4062
+ {
4063
+ "name": "rel",
4064
+ "description": "Своё значение `rel`. Задано — отменяет автоподстановку.",
4065
+ "default": "undefined",
4066
+ "value": {
4067
+ "kind": "expression",
4068
+ "type": "string | undefined"
4069
+ }
4070
+ },
4053
4071
  {
4054
4072
  "name": "clickable",
4055
4073
  "description": "Плитка кликабельна и эмитит `click` — обычно чтобы открыть просмотрщик.",
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@feugene/granularity",
3
3
  "description": "Granularity design system package with Vue 3 components, consumed via the `@feugene/unocss-preset-granular` preset.",
4
- "version": "0.41.0",
5
- "license": "SEE LICENSE IN LICENSE",
4
+ "version": "0.42.0",
5
+ "license": "LicenseRef-Granularity-EUC-1.1",
6
6
  "author": {
7
7
  "name": "Evgeniy Fureev",
8
8
  "url": "https://github.com/efureev"
@@ -603,7 +603,7 @@
603
603
  "devDependencies": {
604
604
  "@antfu/eslint-config": "^9.3.0",
605
605
  "@feugene/fint-i18n": "^0.7.0",
606
- "@feugene/granularity-test-kit": "^0.8.1",
606
+ "@feugene/granularity-test-kit": "^0.10.0",
607
607
  "@feugene/unocss-preset-granular": "^0.16.0",
608
608
  "@floating-ui/dom": "^1.8.0",
609
609
  "@iconify-json/lucide": "^1.2.126",
@@ -1 +0,0 @@
1
- {"version":3,"file":"grKbdStyles-DENu8pUK.js","names":[],"sources":["../../src/components/GrKbd/grKbdStyles.ts"],"sourcesContent":["import type { GrComponentSize } from '../shared/sizes'\n\n/**\n * Классы `GrKbd`.\n *\n * Шкала полная (`xs…lg`), а не «sm и всё остальное»: тип пропа обещает четыре\n * ступени, и `xs`/`lg` не должны молча рендериться как `md`.\n */\n\nexport const GR_KBD_VARIANTS = ['merged', 'split', 'sequence'] as const\n\n/**\n * `merged` — сочетание одной плашкой (`⌘K`), как его пишут сами системы;\n * `split` — по плашке на клавишу; `sequence` — аккорд «G затем I».\n */\nexport type GrKbdVariant = typeof GR_KBD_VARIANTS[number]\n\nexport const keyBaseClass = 'inline-flex items-center justify-center rounded-[var(--gr-radius-control)] border border-[var(--gr-brd)] bg-[var(--gr-muted)] font-medium leading-none text-[var(--gr-muted-fg)] shadow-[var(--gr-shadow-1)] tabular-nums'\n\n/**\n * Межстрочный здесь не парный: у клавиши он задан осознанно (`leading-none` в\n * `keyBaseClass`), и вторая декларация того же веса подралась бы с ним за\n * порядок в стилях. Правило — `docs/sizes.md`.\n */\nexport const keySizes: Record<GrComponentSize, string> = {\n xs: 'min-w-[1.25rem] h-5 px-1 text-[length:var(--gr-control-text-3xs)]',\n sm: 'min-w-[1.5rem] h-6 px-1.5 text-[length:var(--gr-control-text-xs)]',\n md: 'min-w-[1.75rem] h-7 px-2 text-[length:var(--gr-control-text-md)]',\n lg: 'min-w-[2rem] h-8 px-2.5 text-[length:var(--gr-control-text-lg)]',\n}\n\n/**\n * Клавиша внутри общей плашки: ни рамки, ни фона, ни собственной ширины —\n * иначе `⌘K` разъехалось бы зазорами внутри одного чипа.\n */\nexport const mergedKeyClass = 'inline-flex items-center justify-center'\n\n/** Обёртка `split`: сама рамки не имеет — её рисуют вложенные клавиши. */\nexport const comboBaseClass = 'inline-flex items-center align-middle'\n\nexport const comboGaps: Record<GrComponentSize, string> = {\n xs: 'gap-0.5',\n sm: 'gap-1',\n md: 'gap-1',\n lg: 'gap-1.5',\n}\n\n/** Зазор внутри общей плашки: между глифами он меньше, чем между плашками. */\nexport const mergedGaps: Record<GrComponentSize, string> = {\n xs: 'gap-0',\n sm: 'gap-0.5',\n md: 'gap-0.5',\n lg: 'gap-1',\n}\n\nexport const separatorClass = 'select-none text-[var(--gr-muted-fg)]'\n\n/** Слово-разделитель аккорда стоит между плашками и не липнет к ним. */\nexport const sequenceSeparatorClass = 'select-none px-1 text-[var(--gr-muted-fg)]'\n\nexport function grKbdKeyClass(size: GrComponentSize): string {\n return `${keyBaseClass} ${keySizes[size]}`\n}\n\nexport function grKbdComboClass(size: GrComponentSize, variant: GrKbdVariant): string {\n return variant === 'merged'\n ? `${keyBaseClass} ${keySizes[size]} ${mergedGaps[size]}`\n : `${comboBaseClass} ${comboGaps[size]}`\n}\n\n/** Класс клавиши внутри сочетания: в общей плашке она без собственной рамки. */\nexport function grKbdInnerKeyClass(size: GrComponentSize, variant: GrKbdVariant): string {\n return variant === 'merged' ? mergedKeyClass : grKbdKeyClass(size)\n}\n"],"mappings":";AAiBA,IAAa,eAAe;;;;;;AAO5B,IAAa,WAA4C;CACvD,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;AACN;;;;;AAMA,IAAa,iBAAiB;;AAG9B,IAAa,iBAAiB;AAE9B,IAAa,YAA6C;CACxD,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;AACN;;AAGA,IAAa,aAA8C;CACzD,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;AACN;AAEA,IAAa,iBAAiB;;AAG9B,IAAa,yBAAyB;AAEtC,SAAgB,cAAc,MAA+B;CAC3D,OAAO,GAAG,aAAa,GAAG,SAAS;AACrC;AAEA,SAAgB,gBAAgB,MAAuB,SAA+B;CACpF,OAAO,YAAY,WACf,GAAG,aAAa,GAAG,SAAS,MAAM,GAAG,WAAW,UAChD,GAAG,eAAe,GAAG,UAAU;AACrC;;AAGA,SAAgB,mBAAmB,MAAuB,SAA+B;CACvF,OAAO,YAAY,WAAW,iBAAiB,cAAc,IAAI;AACnE"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"GrBreadcrumbs-DOpwY2EF.js","names":[],"sources":["../../../../src/components/GrBreadcrumbs/GrBreadcrumbs.vue","../../../../src/components/GrBreadcrumbs/GrBreadcrumbs.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'\n\nimport GrLink from '../GrLink/GrLink.vue'\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { iconClass, iconTag } from '../shared/icon'\n\nimport {\n breadcrumbsCurrentClass,\n breadcrumbsEllipsisClass,\n breadcrumbsItemIconClass,\n breadcrumbsItemWrapClass,\n breadcrumbsLabelClass,\n breadcrumbsLabelHiddenClass,\n breadcrumbsListClass,\n breadcrumbsListNowrapClass,\n breadcrumbsListWrapClass,\n breadcrumbsRootClass,\n breadcrumbsSeparatorClass,\n breadcrumbsSizeClassBySize,\n type GrBreadcrumbItem,\n type GrBreadcrumbsLinkComponent,\n type GrBreadcrumbsSize,\n resolveBreadcrumbsFit,\n resolveBreadcrumbsLayout,\n} from './grBreadcrumbsStyles'\n\nexport type { GrBreadcrumbItem, GrBreadcrumbsSize } from './grBreadcrumbsStyles'\n\n/**\n * Путь до текущей страницы (WAI-ARIA breadcrumb): `<nav>` с именем, внутри\n * упорядоченный список ссылок, последний пункт — текущая страница.\n *\n * Ссылки рисует `GrLink`, поэтому роутер подключается тем же способом, что и\n * везде в пакете: `as` + `to`.\n */\nexport interface GrBreadcrumbsProps {\n /** Путь от корня к текущей странице. Последний пункт — текущая страница. */\n items: GrBreadcrumbItem[]\n /** Компонент ссылки для всех пунктов: `RouterLink`, `NuxtLink`, `Link` Inertia. */\n as?: GrBreadcrumbsLinkComponent\n /** Разделитель между пунктами. Декоративен: диктору не читается. */\n separator?: string\n size?: GrBreadcrumbsSize\n /**\n * С какого числа пунктов схлопывать середину в «…». `0`/не задано — показывать\n * путь целиком.\n */\n maxItems?: number\n /** Сколько пунктов оставить в начале при схлопывании. */\n itemsBeforeCollapse?: number\n /** Сколько пунктов оставить в конце при схлопывании. */\n itemsAfterCollapse?: number\n /** Имя лендмарка. Не задано — берётся из локали. */\n ariaLabel?: string\n /** Последний пункт остаётся ссылкой (текущая страница кликабельна). */\n linkCurrent?: boolean\n /**\n * Какой пункт — текущая страница. Не задан — последний.\n *\n * `-1` означает, что текущей страницы в пути нет: её имя стоит в `h1`, а\n * путь показывает только родителей. Тогда `aria-current` не получает никто,\n * и последний пункт остаётся обычной ссылкой.\n */\n currentIndex?: number\n /** i18n-метка кнопки раскрытия схлопнутого пути. */\n expandLabel?: string\n /**\n * Схлопывать по доступной ширине, а не по числу пунктов. Путь становится\n * однострочным, середина уходит под «…» ровно настолько, насколько не влезает.\n *\n * Без пропа поведение прежнее: список переносится на вторую строку, а порог\n * задаёт `maxItems`. Вместе пропы совместимы — `maxItems` остаётся жёстким\n * потолком, ширина ужимает дальше.\n */\n autoCollapse?: boolean\n}\n\nconst props = withDefaults(defineProps<GrBreadcrumbsProps>(), {\n as: undefined,\n separator: undefined,\n size: undefined,\n maxItems: undefined,\n itemsBeforeCollapse: 1,\n itemsAfterCollapse: 1,\n ariaLabel: undefined,\n linkCurrent: false,\n currentIndex: undefined,\n expandLabel: undefined,\n autoCollapse: false,\n})\n\ndefineSlots<{\n /** Содержимое пункта целиком (иконка и подпись). */\n item?: (props: { item: GrBreadcrumbItem, index: number, isCurrent: boolean }) => unknown\n /** Разделитель между пунктами. */\n separator?: () => unknown\n /** Кнопка раскрытия схлопнутой середины. */\n ellipsis?: (props: { hiddenCount: number, expand: () => void }) => unknown\n}>()\n\nconst { t } = useGranularityTranslations()\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrBreadcrumbs' })\nconst resolvedSeparator = useGrComponentProp('GrBreadcrumbs', 'separator', () => props.separator, '/')\n\nconst resolvedAriaLabel = computed(() => props.ariaLabel ?? t('gr.breadcrumbs.label', 'Breadcrumb'))\nconst resolvedExpandLabel = computed(() => props.expandLabel ?? t('gr.breadcrumbs.expand', 'Show hidden path'))\n\nconst rootEl = ref<HTMLElement | null>(null)\nconst listEl = ref<HTMLElement | null>(null)\nconst expanded = ref(false)\n\n/**\n * Схлопывание по ширине: сколько пунктов хвоста влезает.\n *\n * `undefined` — ещё не мерили, показываем путь целиком. Первый кадр в режиме\n * `autoCollapse` намеренно полный: ширины снимаются с него, а посчитать их до\n * рендера неоткуда.\n */\nconst fitAfter = ref<number | undefined>(undefined)\n\n/** Ширины не зависят от контейнера, поэтому живут до смены самого пути. */\nlet measured: { items: number[], separator: number } | null = null\n/**\n * Ширина кнопки «…» узнаётся только когда она отрисована, то есть уже после\n * первого схлопывания. До этого считаем её нулевой: поправка может лишь\n * ужать хвост ещё на пункт, а не вернуть спрятанное, поэтому качелей не будет.\n */\nlet ellipsisWidth = 0\n/** Ширина, на которой считали в прошлый раз: по её смене раскрытый путь сворачивается. */\nlet lastAvailable = 0\n\n// Новый путь — новая страница: раскрытая середина прошлого пути к ней отношения\n// не имеет, а измеренные ширины относятся к прошлым подписям.\nwatch(() => props.items, () => {\n expanded.value = false\n measured = null\n ellipsisWidth = 0\n fitAfter.value = undefined\n void nextTick(measure)\n})\n\nconst lastIndex = computed(() => props.items.length - 1)\n\n/**\n * Схлопывание по ширине выражается через тот же `maxItems`: раскладку считает\n * одна функция, а режимы отличаются только тем, откуда взялось число.\n */\nconst autoMaxItems = computed(() => {\n if (!props.autoCollapse || fitAfter.value === undefined)\n return undefined\n if (fitAfter.value >= props.items.length)\n return undefined\n\n return props.items.length - 1\n})\n\nconst effectiveMaxItems = computed(() => {\n const limits = [props.maxItems, autoMaxItems.value].filter((value): value is number => typeof value === 'number' && value > 0)\n\n return limits.length > 0 ? Math.min(...limits) : undefined\n})\n\nconst effectiveAfter = computed(() => (\n props.autoCollapse && fitAfter.value !== undefined ? fitAfter.value : props.itemsAfterCollapse\n))\n\nconst entries = computed(() => resolveBreadcrumbsLayout({\n items: props.items,\n maxItems: effectiveMaxItems.value,\n itemsBeforeCollapse: props.itemsBeforeCollapse,\n itemsAfterCollapse: effectiveAfter.value,\n expanded: expanded.value,\n}))\n\nconst resolvedCurrentIndex = computed(() => props.currentIndex ?? lastIndex.value)\n\nfunction isCurrent(index: number): boolean {\n return index === resolvedCurrentIndex.value\n}\n\n/**\n * Класс подписи: у пункта-иконки она прячется визуально, но остаётся в\n * разметке — путь читают и поиском, и диктором.\n *\n * Без иконки поле игнорируется: спрятать подпись, не показав ничего взамен,\n * значит стереть пункт. Молчать тут нельзя — в разметке всё на месте, и\n * пропажу видно только глазами.\n */\nfunction labelClassFor(item: GrBreadcrumbItem): string {\n if (!item.iconOnly)\n return breadcrumbsLabelClass\n\n if (!item.icon) {\n if (__GR_DEV__) {\n console.warn(\n `[granularity] GrBreadcrumbs: у пункта «${item.label}» стоит \\`iconOnly\\` без \\`icon\\`. `\n + 'Подпись осталась видимой: прятать её, не показав иконку, значит стереть пункт.',\n )\n }\n\n return breadcrumbsLabelClass\n }\n\n return breadcrumbsLabelHiddenClass\n}\n\n/**\n * Ссылкой пункт становится, только если ведёт куда-то и не выключен.\n */\nfunction isLink(item: GrBreadcrumbItem, index: number): boolean {\n if (item.disabled)\n return false\n if (isCurrent(index) && !props.linkCurrent)\n return false\n return item.href !== undefined || item.to !== undefined\n}\n\nfunction widthOf(el: Element | null | undefined): number {\n // `scrollWidth`, а не прямоугольник: у подписи стоит `truncate`, и в узком\n // контейнере прямоугольник вернул бы уже ужатую ширину, а не собственную.\n return el ? (el as HTMLElement).scrollWidth : 0\n}\n\n/**\n * Снять ширины с полного пути и пересчитать, сколько пунктов хвоста влезает.\n *\n * Замер делается один раз на путь: подписи от ширины контейнера не зависят,\n * поэтому `resize` пересчитывает только арифметику.\n */\nfunction measure(): void {\n if (!props.autoCollapse)\n return\n\n const list = listEl.value\n if (!list)\n return\n\n const ellipsis = list.querySelector('[data-gr-breadcrumbs-ellipsis-item]')\n if (ellipsis)\n ellipsisWidth = widthOf(ellipsis)\n\n if (!measured) {\n // Ширины снимаем только с полного пути: спрятанных пунктов в DOM нет.\n if (fitAfter.value !== undefined)\n return\n\n const wraps = [...list.querySelectorAll('[data-gr-breadcrumbs-item-wrap]')]\n if (wraps.length !== props.items.length)\n return\n\n measured = {\n items: wraps.map(widthOf),\n separator: widthOf(list.querySelector('[data-gr-breadcrumbs-separator]')),\n }\n }\n\n const available = list.clientWidth\n // jsdom и скрытый контейнер отдают 0 — решать по такой ширине нечего.\n if (available <= 0)\n return\n\n lastAvailable = available\n\n fitAfter.value = resolveBreadcrumbsFit({\n itemWidths: measured.items,\n separatorWidth: measured.separator,\n ellipsisWidth,\n available,\n itemsBeforeCollapse: props.itemsBeforeCollapse,\n gapWidth: listGap(list),\n })\n}\n\n/** Зазор списка в пикселях: он задан классом, а не пропом, поэтому спрашиваем стиль. */\nfunction listGap(list: HTMLElement): number {\n if (typeof getComputedStyle !== 'function')\n return 0\n\n return Number.parseFloat(getComputedStyle(list).columnGap) || 0\n}\n\n/**\n * Ширина кнопки «…» известна только после того, как она отрисована, то есть\n * после первого схлопывания. Поэтому решение пересчитывается ещё раз: поправка\n * либо ничего не меняет, либо ужимает хвост на пункт. Цикла нет — второй проход\n * оставляет `fitAfter` прежним, и наблюдатель больше не срабатывает.\n */\nwatch(fitAfter, () => void nextTick(measure))\n\nlet observer: ResizeObserver | null = null\n\nwatch(() => props.autoCollapse, (enabled) => {\n if (enabled) {\n void nextTick(measure)\n return\n }\n\n measured = null\n ellipsisWidth = 0\n fitAfter.value = undefined\n})\n\nonMounted(() => {\n void nextTick(measure)\n\n // На сервере и в jsdom `ResizeObserver` отсутствует — измерять там нечего.\n if (typeof ResizeObserver === 'undefined')\n return\n\n observer = new ResizeObserver(() => {\n // Смена ширины — новая раскладка, и раскрытая руками середина относилась к\n // прежней. Свернуть её иначе нечем: в раскрытом пути кнопки «…» нет, то есть\n // поворот телефона оставлял бы путь развёрнутым навсегда. Высота меняется от\n // самого раскрытия (путь переносится на вторую строку) — на неё не реагируем.\n if (expanded.value && listEl.value && listEl.value.clientWidth !== lastAvailable)\n expanded.value = false\n\n measure()\n })\n if (listEl.value)\n observer.observe(listEl.value)\n})\n\nonBeforeUnmount(() => observer?.disconnect())\n\nasync function expand(): Promise<void> {\n expanded.value = true\n\n // Кнопка исчезает вместе со схлопыванием, и фокус ушёл бы на `<body>`.\n // Переводим его на первый раскрытый пункт — там, где кнопка и стояла. Пункты\n // без ссылки держат `tabindex=\"-1\"` именно ради этого случая.\n await nextTick()\n const before = Math.max(0, Math.min(props.itemsBeforeCollapse, props.items.length))\n const revealed = rootEl.value?.querySelectorAll<HTMLElement>('[data-gr-breadcrumbs-item]')\n revealed?.[before]?.focus()\n}\n</script>\n\n<template>\n <nav\n ref=\"rootEl\"\n data-gr-breadcrumbs\n :class=\"[breadcrumbsRootClass, breadcrumbsSizeClassBySize[resolvedSize]]\"\n :aria-label=\"resolvedAriaLabel\"\n >\n <ol\n ref=\"listEl\"\n data-gr-breadcrumbs-list\n :class=\"[breadcrumbsListClass, autoCollapse && !expanded ? breadcrumbsListNowrapClass : breadcrumbsListWrapClass]\"\n >\n <template v-for=\"(entry, position) in entries\" :key=\"entry.kind === 'item' ? `item-${entry.index}` : 'ellipsis'\">\n <!--\n Разделитель — отдельный элемент списка и помечен `aria-hidden`:\n структуру пути диктору сообщает сам список, а «/» он бы читал вслух.\n -->\n <li\n v-if=\"position > 0\"\n data-gr-breadcrumbs-separator\n :class=\"breadcrumbsSeparatorClass\"\n aria-hidden=\"true\"\n >\n <slot name=\"separator\">\n {{ resolvedSeparator }}\n </slot>\n </li>\n\n <li v-if=\"entry.kind === 'ellipsis'\" data-gr-breadcrumbs-ellipsis-item class=\"min-w-0\">\n <slot name=\"ellipsis\" :hidden-count=\"entry.hiddenCount\" :expand=\"expand\">\n <button\n type=\"button\"\n data-gr-breadcrumbs-ellipsis\n data-testid=\"gr-breadcrumbs-ellipsis\"\n :class=\"breadcrumbsEllipsisClass\"\n :aria-label=\"resolvedExpandLabel\"\n @click=\"expand\"\n >\n …\n </button>\n </slot>\n </li>\n\n <li v-else data-gr-breadcrumbs-item-wrap :class=\"breadcrumbsItemWrapClass\">\n <GrLink\n v-if=\"isLink(entry.item, entry.index)\"\n :key=\"`link-${entry.index}`\"\n data-gr-breadcrumbs-item\n :as=\"as\"\n :href=\"entry.item.href\"\n :to=\"entry.item.to\"\n :size=\"resolvedSize\"\n :variant=\"isCurrent(entry.index) ? 'default' : 'muted'\"\n :aria-label=\"entry.item.ariaLabel\"\n :aria-current=\"isCurrent(entry.index) ? 'page' : undefined\"\n >\n <slot name=\"item\" :item=\"entry.item\" :index=\"entry.index\" :is-current=\"isCurrent(entry.index)\">\n <component\n :is=\"iconTag(entry.item.icon)\"\n v-if=\"entry.item.icon\"\n :class=\"[breadcrumbsItemIconClass, iconClass(entry.item.icon)]\"\n aria-hidden=\"true\"\n />\n <span :class=\"labelClassFor(entry.item)\">{{ entry.item.label }}</span>\n </slot>\n </GrLink>\n\n <!--\n Текущая страница и выключенный пункт — не ссылки. `aria-current`\n остаётся на текущем: именно он отвечает на вопрос «где я».\n -->\n <span\n v-else\n data-gr-breadcrumbs-item\n :class=\"breadcrumbsCurrentClass\"\n :aria-label=\"entry.item.ariaLabel\"\n :aria-current=\"isCurrent(entry.index) ? 'page' : undefined\"\n :aria-disabled=\"entry.item.disabled ? 'true' : undefined\"\n tabindex=\"-1\"\n >\n <slot name=\"item\" :item=\"entry.item\" :index=\"entry.index\" :is-current=\"isCurrent(entry.index)\">\n <component\n :is=\"iconTag(entry.item.icon)\"\n v-if=\"entry.item.icon\"\n :class=\"[breadcrumbsItemIconClass, iconClass(entry.item.icon)]\"\n aria-hidden=\"true\"\n />\n <span :class=\"labelClassFor(entry.item)\">{{ entry.item.label }}</span>\n </slot>\n </span>\n </li>\n </template>\n </ol>\n </nav>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'\n\nimport GrLink from '../GrLink/GrLink.vue'\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { iconClass, iconTag } from '../shared/icon'\n\nimport {\n breadcrumbsCurrentClass,\n breadcrumbsEllipsisClass,\n breadcrumbsItemIconClass,\n breadcrumbsItemWrapClass,\n breadcrumbsLabelClass,\n breadcrumbsLabelHiddenClass,\n breadcrumbsListClass,\n breadcrumbsListNowrapClass,\n breadcrumbsListWrapClass,\n breadcrumbsRootClass,\n breadcrumbsSeparatorClass,\n breadcrumbsSizeClassBySize,\n type GrBreadcrumbItem,\n type GrBreadcrumbsLinkComponent,\n type GrBreadcrumbsSize,\n resolveBreadcrumbsFit,\n resolveBreadcrumbsLayout,\n} from './grBreadcrumbsStyles'\n\nexport type { GrBreadcrumbItem, GrBreadcrumbsSize } from './grBreadcrumbsStyles'\n\n/**\n * Путь до текущей страницы (WAI-ARIA breadcrumb): `<nav>` с именем, внутри\n * упорядоченный список ссылок, последний пункт — текущая страница.\n *\n * Ссылки рисует `GrLink`, поэтому роутер подключается тем же способом, что и\n * везде в пакете: `as` + `to`.\n */\nexport interface GrBreadcrumbsProps {\n /** Путь от корня к текущей странице. Последний пункт — текущая страница. */\n items: GrBreadcrumbItem[]\n /** Компонент ссылки для всех пунктов: `RouterLink`, `NuxtLink`, `Link` Inertia. */\n as?: GrBreadcrumbsLinkComponent\n /** Разделитель между пунктами. Декоративен: диктору не читается. */\n separator?: string\n size?: GrBreadcrumbsSize\n /**\n * С какого числа пунктов схлопывать середину в «…». `0`/не задано — показывать\n * путь целиком.\n */\n maxItems?: number\n /** Сколько пунктов оставить в начале при схлопывании. */\n itemsBeforeCollapse?: number\n /** Сколько пунктов оставить в конце при схлопывании. */\n itemsAfterCollapse?: number\n /** Имя лендмарка. Не задано — берётся из локали. */\n ariaLabel?: string\n /** Последний пункт остаётся ссылкой (текущая страница кликабельна). */\n linkCurrent?: boolean\n /**\n * Какой пункт — текущая страница. Не задан — последний.\n *\n * `-1` означает, что текущей страницы в пути нет: её имя стоит в `h1`, а\n * путь показывает только родителей. Тогда `aria-current` не получает никто,\n * и последний пункт остаётся обычной ссылкой.\n */\n currentIndex?: number\n /** i18n-метка кнопки раскрытия схлопнутого пути. */\n expandLabel?: string\n /**\n * Схлопывать по доступной ширине, а не по числу пунктов. Путь становится\n * однострочным, середина уходит под «…» ровно настолько, насколько не влезает.\n *\n * Без пропа поведение прежнее: список переносится на вторую строку, а порог\n * задаёт `maxItems`. Вместе пропы совместимы — `maxItems` остаётся жёстким\n * потолком, ширина ужимает дальше.\n */\n autoCollapse?: boolean\n}\n\nconst props = withDefaults(defineProps<GrBreadcrumbsProps>(), {\n as: undefined,\n separator: undefined,\n size: undefined,\n maxItems: undefined,\n itemsBeforeCollapse: 1,\n itemsAfterCollapse: 1,\n ariaLabel: undefined,\n linkCurrent: false,\n currentIndex: undefined,\n expandLabel: undefined,\n autoCollapse: false,\n})\n\ndefineSlots<{\n /** Содержимое пункта целиком (иконка и подпись). */\n item?: (props: { item: GrBreadcrumbItem, index: number, isCurrent: boolean }) => unknown\n /** Разделитель между пунктами. */\n separator?: () => unknown\n /** Кнопка раскрытия схлопнутой середины. */\n ellipsis?: (props: { hiddenCount: number, expand: () => void }) => unknown\n}>()\n\nconst { t } = useGranularityTranslations()\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrBreadcrumbs' })\nconst resolvedSeparator = useGrComponentProp('GrBreadcrumbs', 'separator', () => props.separator, '/')\n\nconst resolvedAriaLabel = computed(() => props.ariaLabel ?? t('gr.breadcrumbs.label', 'Breadcrumb'))\nconst resolvedExpandLabel = computed(() => props.expandLabel ?? t('gr.breadcrumbs.expand', 'Show hidden path'))\n\nconst rootEl = ref<HTMLElement | null>(null)\nconst listEl = ref<HTMLElement | null>(null)\nconst expanded = ref(false)\n\n/**\n * Схлопывание по ширине: сколько пунктов хвоста влезает.\n *\n * `undefined` — ещё не мерили, показываем путь целиком. Первый кадр в режиме\n * `autoCollapse` намеренно полный: ширины снимаются с него, а посчитать их до\n * рендера неоткуда.\n */\nconst fitAfter = ref<number | undefined>(undefined)\n\n/** Ширины не зависят от контейнера, поэтому живут до смены самого пути. */\nlet measured: { items: number[], separator: number } | null = null\n/**\n * Ширина кнопки «…» узнаётся только когда она отрисована, то есть уже после\n * первого схлопывания. До этого считаем её нулевой: поправка может лишь\n * ужать хвост ещё на пункт, а не вернуть спрятанное, поэтому качелей не будет.\n */\nlet ellipsisWidth = 0\n/** Ширина, на которой считали в прошлый раз: по её смене раскрытый путь сворачивается. */\nlet lastAvailable = 0\n\n// Новый путь — новая страница: раскрытая середина прошлого пути к ней отношения\n// не имеет, а измеренные ширины относятся к прошлым подписям.\nwatch(() => props.items, () => {\n expanded.value = false\n measured = null\n ellipsisWidth = 0\n fitAfter.value = undefined\n void nextTick(measure)\n})\n\nconst lastIndex = computed(() => props.items.length - 1)\n\n/**\n * Схлопывание по ширине выражается через тот же `maxItems`: раскладку считает\n * одна функция, а режимы отличаются только тем, откуда взялось число.\n */\nconst autoMaxItems = computed(() => {\n if (!props.autoCollapse || fitAfter.value === undefined)\n return undefined\n if (fitAfter.value >= props.items.length)\n return undefined\n\n return props.items.length - 1\n})\n\nconst effectiveMaxItems = computed(() => {\n const limits = [props.maxItems, autoMaxItems.value].filter((value): value is number => typeof value === 'number' && value > 0)\n\n return limits.length > 0 ? Math.min(...limits) : undefined\n})\n\nconst effectiveAfter = computed(() => (\n props.autoCollapse && fitAfter.value !== undefined ? fitAfter.value : props.itemsAfterCollapse\n))\n\nconst entries = computed(() => resolveBreadcrumbsLayout({\n items: props.items,\n maxItems: effectiveMaxItems.value,\n itemsBeforeCollapse: props.itemsBeforeCollapse,\n itemsAfterCollapse: effectiveAfter.value,\n expanded: expanded.value,\n}))\n\nconst resolvedCurrentIndex = computed(() => props.currentIndex ?? lastIndex.value)\n\nfunction isCurrent(index: number): boolean {\n return index === resolvedCurrentIndex.value\n}\n\n/**\n * Класс подписи: у пункта-иконки она прячется визуально, но остаётся в\n * разметке — путь читают и поиском, и диктором.\n *\n * Без иконки поле игнорируется: спрятать подпись, не показав ничего взамен,\n * значит стереть пункт. Молчать тут нельзя — в разметке всё на месте, и\n * пропажу видно только глазами.\n */\nfunction labelClassFor(item: GrBreadcrumbItem): string {\n if (!item.iconOnly)\n return breadcrumbsLabelClass\n\n if (!item.icon) {\n if (__GR_DEV__) {\n console.warn(\n `[granularity] GrBreadcrumbs: у пункта «${item.label}» стоит \\`iconOnly\\` без \\`icon\\`. `\n + 'Подпись осталась видимой: прятать её, не показав иконку, значит стереть пункт.',\n )\n }\n\n return breadcrumbsLabelClass\n }\n\n return breadcrumbsLabelHiddenClass\n}\n\n/**\n * Ссылкой пункт становится, только если ведёт куда-то и не выключен.\n */\nfunction isLink(item: GrBreadcrumbItem, index: number): boolean {\n if (item.disabled)\n return false\n if (isCurrent(index) && !props.linkCurrent)\n return false\n return item.href !== undefined || item.to !== undefined\n}\n\nfunction widthOf(el: Element | null | undefined): number {\n // `scrollWidth`, а не прямоугольник: у подписи стоит `truncate`, и в узком\n // контейнере прямоугольник вернул бы уже ужатую ширину, а не собственную.\n return el ? (el as HTMLElement).scrollWidth : 0\n}\n\n/**\n * Снять ширины с полного пути и пересчитать, сколько пунктов хвоста влезает.\n *\n * Замер делается один раз на путь: подписи от ширины контейнера не зависят,\n * поэтому `resize` пересчитывает только арифметику.\n */\nfunction measure(): void {\n if (!props.autoCollapse)\n return\n\n const list = listEl.value\n if (!list)\n return\n\n const ellipsis = list.querySelector('[data-gr-breadcrumbs-ellipsis-item]')\n if (ellipsis)\n ellipsisWidth = widthOf(ellipsis)\n\n if (!measured) {\n // Ширины снимаем только с полного пути: спрятанных пунктов в DOM нет.\n if (fitAfter.value !== undefined)\n return\n\n const wraps = [...list.querySelectorAll('[data-gr-breadcrumbs-item-wrap]')]\n if (wraps.length !== props.items.length)\n return\n\n measured = {\n items: wraps.map(widthOf),\n separator: widthOf(list.querySelector('[data-gr-breadcrumbs-separator]')),\n }\n }\n\n const available = list.clientWidth\n // jsdom и скрытый контейнер отдают 0 — решать по такой ширине нечего.\n if (available <= 0)\n return\n\n lastAvailable = available\n\n fitAfter.value = resolveBreadcrumbsFit({\n itemWidths: measured.items,\n separatorWidth: measured.separator,\n ellipsisWidth,\n available,\n itemsBeforeCollapse: props.itemsBeforeCollapse,\n gapWidth: listGap(list),\n })\n}\n\n/** Зазор списка в пикселях: он задан классом, а не пропом, поэтому спрашиваем стиль. */\nfunction listGap(list: HTMLElement): number {\n if (typeof getComputedStyle !== 'function')\n return 0\n\n return Number.parseFloat(getComputedStyle(list).columnGap) || 0\n}\n\n/**\n * Ширина кнопки «…» известна только после того, как она отрисована, то есть\n * после первого схлопывания. Поэтому решение пересчитывается ещё раз: поправка\n * либо ничего не меняет, либо ужимает хвост на пункт. Цикла нет — второй проход\n * оставляет `fitAfter` прежним, и наблюдатель больше не срабатывает.\n */\nwatch(fitAfter, () => void nextTick(measure))\n\nlet observer: ResizeObserver | null = null\n\nwatch(() => props.autoCollapse, (enabled) => {\n if (enabled) {\n void nextTick(measure)\n return\n }\n\n measured = null\n ellipsisWidth = 0\n fitAfter.value = undefined\n})\n\nonMounted(() => {\n void nextTick(measure)\n\n // На сервере и в jsdom `ResizeObserver` отсутствует — измерять там нечего.\n if (typeof ResizeObserver === 'undefined')\n return\n\n observer = new ResizeObserver(() => {\n // Смена ширины — новая раскладка, и раскрытая руками середина относилась к\n // прежней. Свернуть её иначе нечем: в раскрытом пути кнопки «…» нет, то есть\n // поворот телефона оставлял бы путь развёрнутым навсегда. Высота меняется от\n // самого раскрытия (путь переносится на вторую строку) — на неё не реагируем.\n if (expanded.value && listEl.value && listEl.value.clientWidth !== lastAvailable)\n expanded.value = false\n\n measure()\n })\n if (listEl.value)\n observer.observe(listEl.value)\n})\n\nonBeforeUnmount(() => observer?.disconnect())\n\nasync function expand(): Promise<void> {\n expanded.value = true\n\n // Кнопка исчезает вместе со схлопыванием, и фокус ушёл бы на `<body>`.\n // Переводим его на первый раскрытый пункт — там, где кнопка и стояла. Пункты\n // без ссылки держат `tabindex=\"-1\"` именно ради этого случая.\n await nextTick()\n const before = Math.max(0, Math.min(props.itemsBeforeCollapse, props.items.length))\n const revealed = rootEl.value?.querySelectorAll<HTMLElement>('[data-gr-breadcrumbs-item]')\n revealed?.[before]?.focus()\n}\n</script>\n\n<template>\n <nav\n ref=\"rootEl\"\n data-gr-breadcrumbs\n :class=\"[breadcrumbsRootClass, breadcrumbsSizeClassBySize[resolvedSize]]\"\n :aria-label=\"resolvedAriaLabel\"\n >\n <ol\n ref=\"listEl\"\n data-gr-breadcrumbs-list\n :class=\"[breadcrumbsListClass, autoCollapse && !expanded ? breadcrumbsListNowrapClass : breadcrumbsListWrapClass]\"\n >\n <template v-for=\"(entry, position) in entries\" :key=\"entry.kind === 'item' ? `item-${entry.index}` : 'ellipsis'\">\n <!--\n Разделитель — отдельный элемент списка и помечен `aria-hidden`:\n структуру пути диктору сообщает сам список, а «/» он бы читал вслух.\n -->\n <li\n v-if=\"position > 0\"\n data-gr-breadcrumbs-separator\n :class=\"breadcrumbsSeparatorClass\"\n aria-hidden=\"true\"\n >\n <slot name=\"separator\">\n {{ resolvedSeparator }}\n </slot>\n </li>\n\n <li v-if=\"entry.kind === 'ellipsis'\" data-gr-breadcrumbs-ellipsis-item class=\"min-w-0\">\n <slot name=\"ellipsis\" :hidden-count=\"entry.hiddenCount\" :expand=\"expand\">\n <button\n type=\"button\"\n data-gr-breadcrumbs-ellipsis\n data-testid=\"gr-breadcrumbs-ellipsis\"\n :class=\"breadcrumbsEllipsisClass\"\n :aria-label=\"resolvedExpandLabel\"\n @click=\"expand\"\n >\n …\n </button>\n </slot>\n </li>\n\n <li v-else data-gr-breadcrumbs-item-wrap :class=\"breadcrumbsItemWrapClass\">\n <GrLink\n v-if=\"isLink(entry.item, entry.index)\"\n :key=\"`link-${entry.index}`\"\n data-gr-breadcrumbs-item\n :as=\"as\"\n :href=\"entry.item.href\"\n :to=\"entry.item.to\"\n :size=\"resolvedSize\"\n :variant=\"isCurrent(entry.index) ? 'default' : 'muted'\"\n :aria-label=\"entry.item.ariaLabel\"\n :aria-current=\"isCurrent(entry.index) ? 'page' : undefined\"\n >\n <slot name=\"item\" :item=\"entry.item\" :index=\"entry.index\" :is-current=\"isCurrent(entry.index)\">\n <component\n :is=\"iconTag(entry.item.icon)\"\n v-if=\"entry.item.icon\"\n :class=\"[breadcrumbsItemIconClass, iconClass(entry.item.icon)]\"\n aria-hidden=\"true\"\n />\n <span :class=\"labelClassFor(entry.item)\">{{ entry.item.label }}</span>\n </slot>\n </GrLink>\n\n <!--\n Текущая страница и выключенный пункт — не ссылки. `aria-current`\n остаётся на текущем: именно он отвечает на вопрос «где я».\n -->\n <span\n v-else\n data-gr-breadcrumbs-item\n :class=\"breadcrumbsCurrentClass\"\n :aria-label=\"entry.item.ariaLabel\"\n :aria-current=\"isCurrent(entry.index) ? 'page' : undefined\"\n :aria-disabled=\"entry.item.disabled ? 'true' : undefined\"\n tabindex=\"-1\"\n >\n <slot name=\"item\" :item=\"entry.item\" :index=\"entry.index\" :is-current=\"isCurrent(entry.index)\">\n <component\n :is=\"iconTag(entry.item.icon)\"\n v-if=\"entry.item.icon\"\n :class=\"[breadcrumbsItemIconClass, iconClass(entry.item.icon)]\"\n aria-hidden=\"true\"\n />\n <span :class=\"labelClassFor(entry.item)\">{{ entry.item.label }}</span>\n </slot>\n </span>\n </li>\n </template>\n </ol>\n </nav>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA+EA,MAAM,QAAQ;EAuBd,MAAM,EAAE,MAAM,2BAA2B;EAEzC,MAAM,eAAe,yBAAyB,MAAM,MAAM,EAAE,WAAW,gBAAgB,CAAC;EACxF,MAAM,oBAAoB,mBAAmB,iBAAiB,mBAAmB,MAAM,WAAW,GAAG;EAErG,MAAM,oBAAoB,eAAe,MAAM,aAAa,EAAE,wBAAwB,YAAY,CAAC;EACnG,MAAM,sBAAsB,eAAe,MAAM,eAAe,EAAE,yBAAyB,kBAAkB,CAAC;EAE9G,MAAM,SAAS,IAAwB,IAAI;EAC3C,MAAM,SAAS,IAAwB,IAAI;EAC3C,MAAM,WAAW,IAAI,KAAK;;;;;;;;EAS1B,MAAM,WAAW,IAAwB,KAAA,CAAS;;EAGlD,IAAI,WAA0D;;;;;;EAM9D,IAAI,gBAAgB;;EAEpB,IAAI,gBAAgB;EAIpB,YAAY,MAAM,aAAa;GAC7B,SAAS,QAAQ;GACjB,WAAW;GACX,gBAAgB;GAChB,SAAS,QAAQ,KAAA;GACjB,SAAc,OAAO;EACvB,CAAC;EAED,MAAM,YAAY,eAAe,MAAM,MAAM,SAAS,CAAC;;;;;EAMvD,MAAM,eAAe,eAAe;GAClC,IAAI,CAAC,MAAM,gBAAgB,SAAS,UAAU,KAAA,GAC5C,OAAO,KAAA;GACT,IAAI,SAAS,SAAS,MAAM,MAAM,QAChC,OAAO,KAAA;GAET,OAAO,MAAM,MAAM,SAAS;EAC9B,CAAC;EAED,MAAM,oBAAoB,eAAe;GACvC,MAAM,SAAS,CAAC,MAAM,UAAU,aAAa,KAAK,CAAC,CAAC,QAAQ,UAA2B,OAAO,UAAU,YAAY,QAAQ,CAAC;GAE7H,OAAO,OAAO,SAAS,IAAI,KAAK,IAAI,GAAG,MAAM,IAAI,KAAA;EACnD,CAAC;EAED,MAAM,iBAAiB,eACrB,MAAM,gBAAgB,SAAS,UAAU,KAAA,IAAY,SAAS,QAAQ,MAAM,kBAC7E;EAED,MAAM,UAAU,eAAe,yBAAyB;GACtD,OAAO,MAAM;GACb,UAAU,kBAAkB;GAC5B,qBAAqB,MAAM;GAC3B,oBAAoB,eAAe;GACnC,UAAU,SAAS;EACrB,CAAC,CAAC;EAEF,MAAM,uBAAuB,eAAe,MAAM,gBAAgB,UAAU,KAAK;EAEjF,SAAS,UAAU,OAAwB;GACzC,OAAO,UAAU,qBAAqB;EACxC;;;;;;;;;EAUA,SAAS,cAAc,MAAgC;GACrD,IAAI,CAAC,KAAK,UACR,OAAO;GAET,IAAI,CAAC,KAAK,MAAM;IACd,IAAA,QAAA,IAAA,aAAA,cACE,QAAQ,KACN,0CAA0C,KAAK,MAAM,kHAEvD;IAGF,OAAO;GACT;GAEA,OAAO;EACT;;;;EAKA,SAAS,OAAO,MAAwB,OAAwB;GAC9D,IAAI,KAAK,UACP,OAAO;GACT,IAAI,UAAU,KAAK,KAAK,CAAC,MAAM,aAC7B,OAAO;GACT,OAAO,KAAK,SAAS,KAAA,KAAa,KAAK,OAAO,KAAA;EAChD;EAEA,SAAS,QAAQ,IAAwC;GAGvD,OAAO,KAAM,GAAmB,cAAc;EAChD;;;;;;;EAQA,SAAS,UAAgB;GACvB,IAAI,CAAC,MAAM,cACT;GAEF,MAAM,OAAO,OAAO;GACpB,IAAI,CAAC,MACH;GAEF,MAAM,WAAW,KAAK,cAAc,qCAAqC;GACzE,IAAI,UACF,gBAAgB,QAAQ,QAAQ;GAElC,IAAI,CAAC,UAAU;IAEb,IAAI,SAAS,UAAU,KAAA,GACrB;IAEF,MAAM,QAAQ,CAAC,GAAG,KAAK,iBAAiB,iCAAiC,CAAC;IAC1E,IAAI,MAAM,WAAW,MAAM,MAAM,QAC/B;IAEF,WAAW;KACT,OAAO,MAAM,IAAI,OAAO;KACxB,WAAW,QAAQ,KAAK,cAAc,iCAAiC,CAAC;IAC1E;GACF;GAEA,MAAM,YAAY,KAAK;GAEvB,IAAI,aAAa,GACf;GAEF,gBAAgB;GAEhB,SAAS,QAAQ,sBAAsB;IACrC,YAAY,SAAS;IACrB,gBAAgB,SAAS;IACzB;IACA;IACA,qBAAqB,MAAM;IAC3B,UAAU,QAAQ,IAAI;GACxB,CAAC;EACH;;EAGA,SAAS,QAAQ,MAA2B;GAC1C,IAAI,OAAO,qBAAqB,YAC9B,OAAO;GAET,OAAO,OAAO,WAAW,iBAAiB,IAAI,CAAC,CAAC,SAAS,KAAK;EAChE;;;;;;;EAQA,MAAM,gBAAgB,KAAK,SAAS,OAAO,CAAC;EAE5C,IAAI,WAAkC;EAEtC,YAAY,MAAM,eAAe,YAAY;GAC3C,IAAI,SAAS;IACX,SAAc,OAAO;IACrB;GACF;GAEA,WAAW;GACX,gBAAgB;GAChB,SAAS,QAAQ,KAAA;EACnB,CAAC;EAED,gBAAgB;GACd,SAAc,OAAO;GAGrB,IAAI,OAAO,mBAAmB,aAC5B;GAEF,WAAW,IAAI,qBAAqB;IAKlC,IAAI,SAAS,SAAS,OAAO,SAAS,OAAO,MAAM,gBAAgB,eACjE,SAAS,QAAQ;IAEnB,QAAQ;GACV,CAAC;GACD,IAAI,OAAO,OACT,SAAS,QAAQ,OAAO,KAAK;EACjC,CAAC;EAED,sBAAsB,UAAU,WAAW,CAAC;EAE5C,eAAe,SAAwB;GACrC,SAAS,QAAQ;GAKjB,MAAM,SAAS;GACf,MAAM,SAAS,KAAK,IAAI,GAAG,KAAK,IAAI,MAAM,qBAAqB,MAAM,MAAM,MAAM,CAAC;GAElF,CADiB,OAAO,OAAO,iBAA8B,4BAA4B,EAAA,GAC9E,OAAO,EAAE,MAAM;EAC5B;;GAIE,OAAA,UAAA,GAAA,mBA4FM,OAAA;IA3FA,SAAA;IAAJ,KAAI;IACJ,uBAAA;IACC,OAAK,eAAA,CAAG,MAAA,oBAAA,GAAsB,MAAA,0BAAA,CAA0B,CAAC,MAAA,YAAA,EAAY,CAAA;IACrE,cAAY,kBAAA;GAEb,GAAA,CAAA,mBAqFK,MAAA;IApFC,SAAA;IAAJ,KAAI;IACJ,4BAAA;IACC,OAAK,eAAA,CAAG,MAAA,oBAAA,GAAsB,QAAA,gBAAY,CAAK,SAAA,QAAW,MAAA,0BAAA,IAA6B,MAAA,wBAAA,CAAwB,CAAA;GAEhH,GAAA,EAAA,UAAA,IAAA,GAAA,mBA+EW,UAAA,MAAA,WA/E2B,QAAA,QAApB,OAAO,aAAQ;IAAoB,OAAA,UAAA,GAAA,mBAAA,UAAA,EAAA,KAAA,MAAM,SAAI,SAAA,QAAsB,MAAM,UAAK,WAAA,GAAA,CAMtF,WAAQ,KADhB,UAAA,GAAA,mBASK,MAAA;;KAPH,iCAAA;KACC,OAAK,eAAE,MAAA,yBAAA,CAAyB;KACjC,eAAY;IAEZ,GAAA,CAAA,WAEO,KAAA,QAAA,aAAA,CAAA,SAAA,CADF,gBAAA,gBAAA,MAAA,iBAAA,CAAiB,GAAA,CAAA,CAAA,CAAA,CAAA,GAAA,CAAA,KAAA,mBAAA,IAAA,IAAA,GAId,MAAM,SAAI,cAApB,UAAA,GAAA,mBAaK,MAbL,YAaK,CAZH,WAWO,KAAA,QAAA,YAAA;KAXgB,aAAc,MAAM;KAAsB;IAAjE,SAWO,CAVL,mBASS,UAAA;KARP,MAAK;KACL,gCAAA;KACA,eAAY;KACX,OAAK,eAAE,MAAA,wBAAA,CAAwB;KAC/B,cAAY,oBAAA;KACZ,SAAO;IACT,GAAA,OAED,IAAA,UAAA,CAAA,CAAA,CAAA,CAAA,MAIJ,UAAA,GAAA,mBA+CK,MAAA;;KA/CM,iCAAA;KAA+B,OAAK,eAAE,MAAA,wBAAA,CAAwB;IAE/D,GAAA,CAAA,OAAO,MAAM,MAAM,MAAM,KAAK,KADtC,UAAA,GAAA,YAqBS,gBAAA;KAnBN,KAAG,QAAU,MAAM;KACpB,4BAAA;KACC,IAAI,QAAA;KACJ,MAAM,MAAM,KAAK;KACjB,IAAI,MAAM,KAAK;KACf,MAAM,MAAA,YAAA;KACN,SAAS,UAAU,MAAM,KAAK,IAAA,YAAA;KAC9B,cAAY,MAAM,KAAK;KACvB,gBAAc,UAAU,MAAM,KAAK,IAAA,SAAa,KAAA;;KAEjD,SAAA,cAQO,CARP,WAQO,KAAA,QAAA,QAAA;MARY,MAAM,MAAM;MAAO,OAAO,MAAM;MAAQ,WAAY,UAAU,MAAM,KAAK;KAA5F,SAQO,CALG,MAAM,KAAK,QAFnB,UAAA,GAAA,YAKE,wBAJK,MAAA,OAAA,CAAO,CAAC,MAAM,KAAK,IAAI,CAAA,GAAA;;MAE3B,OAAK,eAAA,CAAG,MAAA,wBAAA,GAA0B,MAAA,SAAA,CAAS,CAAC,MAAM,KAAK,IAAI,CAAA,CAAA;MAC5D,eAAY;KAEd,GAAA,MAAA,GAAA,CAAA,OAAA,CAAA,KAAA,mBAAA,IAAA,IAAA,GAAA,mBAAsE,QAAA,EAA/D,OAAK,eAAE,cAAc,MAAM,IAAI,CAAA,EAAM,GAAA,gBAAA,MAAM,KAAK,KAAK,GAAA,CAAA,CAAA,CAAA,CAAA,CAAA;;;;;;;;;;IAQhE,CAAA,MAAA,UAAA,GAAA,mBAkBO,QAAA;;KAhBL,4BAAA;KACC,OAAK,eAAE,MAAA,uBAAA,CAAuB;KAC9B,cAAY,MAAM,KAAK;KACvB,gBAAc,UAAU,MAAM,KAAK,IAAA,SAAa,KAAA;KAChD,iBAAe,MAAM,KAAK,WAAQ,SAAY,KAAA;KAC/C,UAAS;IAET,GAAA,CAAA,WAQO,KAAA,QAAA,QAAA;KARY,MAAM,MAAM;KAAO,OAAO,MAAM;KAAQ,WAAY,UAAU,MAAM,KAAK;IAA5F,SAQO,CALG,MAAM,KAAK,QAFnB,UAAA,GAAA,YAKE,wBAJK,MAAA,OAAA,CAAO,CAAC,MAAM,KAAK,IAAI,CAAA,GAAA;;KAE3B,OAAK,eAAA,CAAG,MAAA,wBAAA,GAA0B,MAAA,SAAA,CAAS,CAAC,MAAM,KAAK,IAAI,CAAA,CAAA;KAC5D,eAAY;IAEd,GAAA,MAAA,GAAA,CAAA,OAAA,CAAA,KAAA,mBAAA,IAAA,IAAA,GAAA,mBAAsE,QAAA,EAA/D,OAAK,eAAE,cAAc,MAAM,IAAI,CAAA,EAAM,GAAA,gBAAA,MAAM,KAAK,KAAK,GAAA,CAAA,CAAA,CAAA,CAAA,GAAA,IAAA,UAAA,EAAA,GAAA,CAAA,EAAA,GAAA,EAAA"}