@feugene/granularity 0.27.1 → 0.28.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.
@@ -25,6 +25,15 @@ var breadcrumbsSizeClassBySize = {
25
25
  var breadcrumbsSeparatorClass = "select-none text-[var(--gr-muted-fg)]";
26
26
  /** Текущая страница: не ссылка, но и не «выключенный» текст — обычный акцент подписи. */
27
27
  var breadcrumbsCurrentClass = "inline-flex items-center gap-1 min-w-0 font-600 text-[var(--gr-fg)]";
28
+ /**
29
+ * Обёртка пункта — flex, а не блок с линейным боксом.
30
+ *
31
+ * Базовая линия `inline-flex` берётся от первого flex-элемента: у пункта
32
+ * с иконкой это блочная иконка, и линейный бокс раздувается на высоту
33
+ * подстрочных выносных. Пункт с иконкой выходил на 3px выше соседей, а при
34
+ * `align-items: center` списка это смещало его содержимое на полтора пикселя.
35
+ */
36
+ var breadcrumbsItemWrapClass = "flex items-center min-w-0";
28
37
  var breadcrumbsItemIconClass = "inline-block h-4 w-4 shrink-0";
29
38
  var breadcrumbsLabelClass = "truncate";
30
39
  /**
@@ -95,6 +104,7 @@ var grBreadcrumbsSafelist = [.../* @__PURE__ */ new Set([
95
104
  ...splitClassTokens(breadcrumbsSeparatorClass),
96
105
  ...splitClassTokens(breadcrumbsCurrentClass),
97
106
  ...splitClassTokens(breadcrumbsItemIconClass),
107
+ ...splitClassTokens(breadcrumbsItemWrapClass),
98
108
  ...splitClassTokens(breadcrumbsLabelClass),
99
109
  ...splitClassTokens(breadcrumbsEllipsisClass)
100
110
  ])];
@@ -106,4 +116,4 @@ var grBreadcrumbsConfig = defineGranularComponent(import.meta.url, {
106
116
  dependencies: ["GrLink"]
107
117
  });
108
118
  //#endregion
109
- export { breadcrumbsItemIconClass as a, breadcrumbsListNowrapClass as c, breadcrumbsSeparatorClass as d, breadcrumbsSizeClassBySize as f, breadcrumbsEllipsisClass as i, breadcrumbsListWrapClass as l, resolveBreadcrumbsLayout as m, grBreadcrumbsSafelist as n, breadcrumbsLabelClass as o, resolveBreadcrumbsFit as p, breadcrumbsCurrentClass as r, breadcrumbsListClass as s, grBreadcrumbsConfig as t, breadcrumbsRootClass as u };
119
+ export { breadcrumbsItemIconClass as a, breadcrumbsListClass as c, breadcrumbsRootClass as d, breadcrumbsSeparatorClass as f, resolveBreadcrumbsLayout as h, breadcrumbsEllipsisClass as i, breadcrumbsListNowrapClass as l, resolveBreadcrumbsFit as m, grBreadcrumbsSafelist as n, breadcrumbsItemWrapClass as o, breadcrumbsSizeClassBySize as p, breadcrumbsCurrentClass as r, breadcrumbsLabelClass as s, grBreadcrumbsConfig as t, breadcrumbsListWrapClass as u };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-DONFPTFg.js","names":[],"sources":["../../src/components/GrBreadcrumbs/grBreadcrumbsStyles.ts","../../src/components/GrBreadcrumbs/safelist.ts","../../src/components/GrBreadcrumbs/config.ts"],"sourcesContent":["import type { Component } from 'vue'\n\nimport type { GrComponentSize } from '../shared/sizes'\n\nexport type GrBreadcrumbsSize = GrComponentSize\n\n/** Пункт пути. Кроме `label` всё опционально: последний пункт обычно без ссылки. */\nexport type GrBreadcrumbItem = {\n label: string\n href?: string\n /** Цель роутерной ссылки: уезжает в `GrLink` через `attrs` вместе с `as`. */\n to?: unknown\n /**\n * Иконка перед подписью — декоративна. Vue-компонент либо класс иконки вашей\n * UnoCSS-сборки (`i-lucide-*` требует вашего `presetIcons`).\n */\n icon?: string | Component\n disabled?: boolean\n /** Доступное имя пункта, когда подписи недостаточно («Настройки» → «Настройки проекта»). */\n ariaLabel?: string\n}\n\nexport const breadcrumbsRootClass = 'w-full min-w-0'\nexport const breadcrumbsListClass = 'flex items-center gap-1 m-0 p-0 [list-style:none]'\n\n/**\n * Перенос — умолчание: длинный путь уезжает на вторую строку и остаётся читаемым.\n * В режиме `autoCollapse` строка одна, иначе переполнения не случится никогда и\n * мерить будет нечего: список просто перенесётся.\n *\n * Исключение — раскрытый вручную путь: в одну строку он бы не влез (её нехватка и\n * вызвала схлопывание), `overflow: hidden` срезал бы хвост вместе с текущей\n * страницей, а фокус на раскрытом пункте молча уводил бы скролл контейнера.\n * Поэтому раскрытие возвращает перенос.\n */\nexport const breadcrumbsListWrapClass = 'flex-wrap'\nexport const breadcrumbsListNowrapClass = 'flex-nowrap overflow-hidden'\n\n/** Кегль строки пути. Шкала общая с `GrLink`: путь — это ряд ссылок. */\nexport const breadcrumbsSizeClassBySize: Record<GrBreadcrumbsSize, string> = {\n xs: 'text-[length:var(--gr-text-xs)] leading-[var(--gr-leading-xs)]',\n sm: 'text-[length:var(--gr-text-sm)] leading-[var(--gr-leading-sm)]',\n md: 'text-[length:var(--gr-text-sm)] leading-[var(--gr-leading-sm)]',\n lg: 'text-[length:var(--gr-text-base)] leading-[var(--gr-leading-base)]',\n}\n\nexport const breadcrumbsSeparatorClass = 'select-none text-[var(--gr-muted-fg)]'\n\n/** Текущая страница: не ссылка, но и не «выключенный» текст — обычный акцент подписи. */\nexport const breadcrumbsCurrentClass = 'inline-flex items-center gap-1 min-w-0 font-600 text-[var(--gr-fg)]'\n\n/**\n * Обёртка пункта — flex, а не блок с линейным боксом.\n *\n * Базовая линия `inline-flex` берётся от первого flex-элемента: у пункта\n * с иконкой это блочная иконка, и линейный бокс раздувается на высоту\n * подстрочных выносных. Пункт с иконкой выходил на 3px выше соседей, а при\n * `align-items: center` списка это смещало его содержимое на полтора пикселя.\n */\nexport const breadcrumbsItemWrapClass = 'flex items-center min-w-0'\n\nexport const breadcrumbsItemIconClass = 'inline-block h-4 w-4 shrink-0'\n\nexport const breadcrumbsLabelClass = 'truncate'\n\n/**\n * Кнопка «…»: раскрывает схлопнутую середину пути на месте. Оформлена как\n * приглушённая ссылка, чтобы не выбиваться из ряда.\n */\nexport const breadcrumbsEllipsisClass = 'inline-flex items-center rounded-[var(--gr-radius-sm)] px-1 text-[var(--gr-muted-fg)] hover:text-[var(--gr-fg)] focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--gr-ring)]'\n\nexport type GrBreadcrumbsLinkComponent = string | Component\n\n/**\n * Раскладка пути: какие пункты видны и где стоит кнопка раскрытия.\n *\n * Чистая функция, потому что вся её сложность — арифметика границ, и проверять\n * её монтированием компонента дороже, чем таблицей входов.\n */\nexport type GrBreadcrumbsLayoutEntry =\n | { kind: 'item', item: GrBreadcrumbItem, index: number }\n | { kind: 'ellipsis', hiddenCount: number }\n\n/**\n * Сколько пунктов хвоста влезает в доступную ширину.\n *\n * Порог по числу пунктов (`maxItems`) на узком экране предсказывает плохо: три\n * коротких пункта влезают, два длинных — нет. Здесь решение принимается по\n * измеренным ширинам.\n *\n * Голова не ужимается: `itemsBeforeCollapse` — обычно один корневой пункт, и он\n * дёшев. Хвост ужимается до упора, но **не меньше одного**: последний пункт\n * отвечает на вопрос «где я сейчас», и прятать его бессмысленно — путь\n * превратился бы в «Главная / …».\n *\n * Возврат — число пунктов хвоста; `items.length` означает «влезло всё».\n */\nexport function resolveBreadcrumbsFit(options: {\n itemWidths: number[]\n separatorWidth: number\n ellipsisWidth: number\n available: number\n itemsBeforeCollapse: number\n /** Зазор списка (`gap`). Стоит между каждой парой соседей, включая разделители. */\n gapWidth?: number\n}): number {\n const { itemWidths, separatorWidth, ellipsisWidth, available, itemsBeforeCollapse, gapWidth = 0 } = options\n const total = itemWidths.length\n\n if (total === 0) return 0\n\n const sum = (from: number, to: number): number =>\n itemWidths.slice(from, to).reduce((acc, width) => acc + width, 0)\n\n /**\n * Ширина строки из `parts` видимых блоков: сами блоки, разделители между ними\n * и зазоры. Элементов в списке `2 * parts - 1` (блоки и разделители вперемешку),\n * значит зазоров на один меньше — без них расчёт систематически оптимистичен и\n * путь подрезается уже после схлопывания.\n */\n const rowWidth = (content: number, parts: number): number =>\n content\n + Math.max(0, parts - 1) * separatorWidth\n + Math.max(0, 2 * parts - 2) * gapWidth\n\n if (rowWidth(sum(0, total), total) <= available) return total\n\n const before = Math.max(0, Math.min(itemsBeforeCollapse, total))\n\n for (let after = total - before - 1; after > 1; after--) {\n const content = sum(0, before) + ellipsisWidth + sum(total - after, total)\n\n if (rowWidth(content, before + 1 + after) <= available) return after\n }\n\n return 1\n}\n\nexport function resolveBreadcrumbsLayout(options: {\n items: GrBreadcrumbItem[]\n maxItems?: number\n itemsBeforeCollapse: number\n itemsAfterCollapse: number\n expanded: boolean\n}): GrBreadcrumbsLayoutEntry[] {\n const { items, maxItems, itemsBeforeCollapse, itemsAfterCollapse, expanded } = options\n const all: GrBreadcrumbsLayoutEntry[] = items.map((item, index) => ({ kind: 'item', item, index }))\n\n if (expanded || !maxItems || maxItems < 1 || items.length <= maxItems)\n return all\n\n // Хвост важнее головы: «где я сейчас» читается справа, поэтому при нехватке\n // места сначала жертвуем серединой, а не концом.\n const before = Math.max(0, Math.min(itemsBeforeCollapse, items.length))\n const after = Math.max(0, Math.min(itemsAfterCollapse, items.length - before))\n const hiddenCount = items.length - before - after\n\n if (hiddenCount <= 1)\n return all\n\n return [\n ...all.slice(0, before),\n { kind: 'ellipsis', hiddenCount },\n ...all.slice(items.length - after),\n ]\n}\n","import { splitClassTokens } from '../shared/classTokens'\nimport {\n breadcrumbsCurrentClass,\n breadcrumbsEllipsisClass,\n breadcrumbsItemIconClass,\n breadcrumbsItemWrapClass,\n breadcrumbsLabelClass,\n breadcrumbsListClass,\n breadcrumbsListNowrapClass,\n breadcrumbsListWrapClass,\n breadcrumbsRootClass,\n breadcrumbsSeparatorClass,\n breadcrumbsSizeClassBySize,\n} from './grBreadcrumbsStyles'\n\n// Всё, что живёт в `grBreadcrumbsStyles.ts`: и мапа размеров, и строковые\n// литералы. Хелпер уезжает в общий `dist/chunks/`, вне области скана\n// компонента — гейт `src/__tests__/safelist.test.ts`.\nexport const grBreadcrumbsSafelist = [...new Set([\n ...Object.values(breadcrumbsSizeClassBySize).flatMap(splitClassTokens),\n ...splitClassTokens(breadcrumbsRootClass),\n ...splitClassTokens(breadcrumbsListClass),\n ...splitClassTokens(breadcrumbsListWrapClass),\n ...splitClassTokens(breadcrumbsListNowrapClass),\n ...splitClassTokens(breadcrumbsSeparatorClass),\n ...splitClassTokens(breadcrumbsCurrentClass),\n ...splitClassTokens(breadcrumbsItemIconClass),\n ...splitClassTokens(breadcrumbsItemWrapClass),\n ...splitClassTokens(breadcrumbsLabelClass),\n ...splitClassTokens(breadcrumbsEllipsisClass),\n])]\n","import { defineGranularComponent } from '@feugene/unocss-preset-granular/contract'\n\nimport { grBreadcrumbsSafelist } from './safelist'\n\nexport const grBreadcrumbsConfig = defineGranularComponent(import.meta.url, {\n name: 'GrBreadcrumbs',\n safelist: grBreadcrumbsSafelist,\n dependencies: ['GrLink'],\n})\n"],"mappings":";;;AAsBA,IAAa,uBAAuB;AACpC,IAAa,uBAAuB;;;;;;;;;;;AAYpC,IAAa,2BAA2B;AACxC,IAAa,6BAA6B;;AAG1C,IAAa,6BAAgE;CAC3E,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;AACN;AAEA,IAAa,4BAA4B;;AAGzC,IAAa,0BAA0B;;;;;;;;;AAUvC,IAAa,2BAA2B;AAExC,IAAa,2BAA2B;AAExC,IAAa,wBAAwB;;;;;AAMrC,IAAa,2BAA2B;;;;;;;;;;;;;;;AA4BxC,SAAgB,sBAAsB,SAQ3B;CACT,MAAM,EAAE,YAAY,gBAAgB,eAAe,WAAW,qBAAqB,WAAW,MAAM;CACpG,MAAM,QAAQ,WAAW;CAEzB,IAAI,UAAU,GAAG,OAAO;CAExB,MAAM,OAAO,MAAc,OACzB,WAAW,MAAM,MAAM,EAAE,CAAC,CAAC,QAAQ,KAAK,UAAU,MAAM,OAAO,CAAC;;;;;;;CAQlE,MAAM,YAAY,SAAiB,UACjC,UACE,KAAK,IAAI,GAAG,QAAQ,CAAC,IAAI,iBACzB,KAAK,IAAI,GAAG,IAAI,QAAQ,CAAC,IAAI;CAEjC,IAAI,SAAS,IAAI,GAAG,KAAK,GAAG,KAAK,KAAK,WAAW,OAAO;CAExD,MAAM,SAAS,KAAK,IAAI,GAAG,KAAK,IAAI,qBAAqB,KAAK,CAAC;CAE/D,KAAK,IAAI,QAAQ,QAAQ,SAAS,GAAG,QAAQ,GAAG,SAG9C,IAAI,SAFY,IAAI,GAAG,MAAM,IAAI,gBAAgB,IAAI,QAAQ,OAAO,KAAK,GAEnD,SAAS,IAAI,KAAK,KAAK,WAAW,OAAO;CAGjE,OAAO;AACT;AAEA,SAAgB,yBAAyB,SAMV;CAC7B,MAAM,EAAE,OAAO,UAAU,qBAAqB,oBAAoB,aAAa;CAC/E,MAAM,MAAkC,MAAM,KAAK,MAAM,WAAW;EAAE,MAAM;EAAQ;EAAM;CAAM,EAAE;CAElG,IAAI,YAAY,CAAC,YAAY,WAAW,KAAK,MAAM,UAAU,UAC3D,OAAO;CAIT,MAAM,SAAS,KAAK,IAAI,GAAG,KAAK,IAAI,qBAAqB,MAAM,MAAM,CAAC;CACtE,MAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,IAAI,oBAAoB,MAAM,SAAS,MAAM,CAAC;CAC7E,MAAM,cAAc,MAAM,SAAS,SAAS;CAE5C,IAAI,eAAe,GACjB,OAAO;CAET,OAAO;EACL,GAAG,IAAI,MAAM,GAAG,MAAM;EACtB;GAAE,MAAM;GAAY;EAAY;EAChC,GAAG,IAAI,MAAM,MAAM,SAAS,KAAK;CACnC;AACF;;;ACnJA,IAAa,wBAAwB,CAAC,mBAAG,IAAI,IAAI;CAC/C,GAAG,OAAO,OAAO,0BAA0B,CAAC,CAAC,QAAQ,gBAAgB;CACrE,GAAG,iBAAiB,oBAAoB;CACxC,GAAG,iBAAiB,oBAAoB;CACxC,GAAG,iBAAiB,wBAAwB;CAC5C,GAAG,iBAAiB,0BAA0B;CAC9C,GAAG,iBAAiB,yBAAyB;CAC7C,GAAG,iBAAiB,uBAAuB;CAC3C,GAAG,iBAAiB,wBAAwB;CAC5C,GAAG,iBAAiB,wBAAwB;CAC5C,GAAG,iBAAiB,qBAAqB;CACzC,GAAG,iBAAiB,wBAAwB;AAC9C,CAAC,CAAC;;;AC1BF,IAAa,sBAAsB,wBAAwB,OAAO,KAAK,KAAK;CAC1E,MAAM;CACN,UAAU;CACV,cAAc,CAAC,QAAQ;AACzB,CAAC"}
@@ -4,7 +4,7 @@ import { t as grAvatarConfig } from "./config-B3J0alyY.js";
4
4
  import { t as grBadgeConfig } from "./config-g8-JGZ_Y.js";
5
5
  import { t as grBadgeWrapConfig } from "./config-DwABJHEo.js";
6
6
  import { t as grBottomNavConfig } from "./config-DM0lsduD.js";
7
- import { t as grBreadcrumbsConfig } from "./config-DcfWZOi5.js";
7
+ import { t as grBreadcrumbsConfig } from "./config-DONFPTFg.js";
8
8
  import { t as grButtonConfig } from "./config-BHcS5Cwm.js";
9
9
  import { t as grButtonGroupConfig } from "./config-DBmdgoVf2.js";
10
10
  import { t as grCardConfig } from "./config-Dpk-eVkS.js";
@@ -1 +1 @@
1
- {"version":3,"file":"granular-provider-DvvRayEh.js","names":[],"sources":["../../src/components/GrDialog/config.ts","../../src/components/GrResponseErrorBanner/config.ts","../../src/granular-provider/shared.ts","../../src/granular-provider/index.ts"],"sourcesContent":["import { defineGranularComponent } from '@feugene/unocss-preset-granular/contract'\n\nimport { grDialogSafelist } from './safelist'\n\nexport const grDialogConfig = defineGranularComponent(import.meta.url, {\n name: 'GrDialog',\n dependencies: ['GrButton', 'GrModal'],\n safelist: grDialogSafelist,\n})\n","import { defineGranularComponent } from '@feugene/unocss-preset-granular/contract'\n\nexport const grResponseErrorBannerConfig = defineGranularComponent(import.meta.url, {\n name: 'GrResponseErrorBanner',\n dependencies: ['GrAlert', 'GrButton'],\n})\n","// `id`, `theme.*`, `packageBaseUrl` и реестр компонентов провайдера.\n//\n// Все `new URL('../styles/...', import.meta.url)` обязаны лежать именно здесь:\n// `shared.ts` в одной директории с `index.ts` / `node.ts`, поэтому\n// относительные пути совпадают. Для браузера безопасно — бандлеры\n// транслируют такие литералы в статические asset-URL.\n//\n// `apps/showcase/scripts/generate-component-api.mjs` читает список компонентов\n// из `Object.keys(granularityComponentConfigs)` через vite SSR.\nimport {\n defineGranularProvider,\n type GranularComponentDescriptor,\n type GranularProvider,\n resolvePackageBaseUrl,\n} from '@feugene/unocss-preset-granular/contract'\n// <granularity:components:imports> — блок генерируется `yarn generate:registry`\nimport { grAlertConfig } from '../components/GrAlert/config'\nimport { grAutocompleteConfig } from '../components/GrAutocomplete/config'\nimport { grAvatarConfig } from '../components/GrAvatar/config'\nimport { grBadgeConfig } from '../components/GrBadge/config'\nimport { grBadgeWrapConfig } from '../components/GrBadgeWrap/config'\nimport { grBottomNavConfig } from '../components/GrBottomNav/config'\nimport { grBreadcrumbsConfig } from '../components/GrBreadcrumbs/config'\nimport { grButtonConfig } from '../components/GrButton/config'\nimport { grButtonGroupConfig } from '../components/GrButtonGroup/config'\nimport { grCardConfig } from '../components/GrCard/config'\nimport { grCheckboxConfig } from '../components/GrCheckbox/config'\nimport { grCheckboxGroupConfig } from '../components/GrCheckboxGroup/config'\nimport { grChipConfig } from '../components/GrChip/config'\nimport { grChipGroupConfig } from '../components/GrChipGroup/config'\nimport { grCodeBlockConfig } from '../components/GrCodeBlock/config'\nimport { grCollapseConfig } from '../components/GrCollapse/config'\nimport { grColorPickerConfig } from '../components/GrColorPicker/config'\nimport { grCommandPaletteConfig } from '../components/GrCommandPalette/config'\nimport { grConfigProviderConfig } from '../components/GrConfigProvider/config'\nimport { grConfirmDialogConfig } from '../components/GrConfirmDialog/config'\nimport { grContextMenuConfig } from '../components/GrContextMenu/config'\nimport { grDataTableConfig } from '../components/GrDataTable/config'\nimport { grDeltaConfig } from '../components/GrDelta/config'\nimport { grDescriptionListConfig } from '../components/GrDescriptionList/config'\nimport { grDialogConfig } from '../components/GrDialog/config'\nimport { grDialogServiceConfig } from '../components/GrDialogService/config'\nimport { grDividerConfig } from '../components/GrDivider/config'\nimport { grDrawerConfig } from '../components/GrDrawer/config'\nimport { grDropdownConfig } from '../components/GrDropdown/config'\nimport { grDropdownMenuConfig } from '../components/GrDropdownMenu/config'\nimport { grEmptyStateConfig } from '../components/GrEmptyState/config'\nimport { grFilePreviewConfig } from '../components/GrFilePreview/config'\nimport { grFileUploadConfig } from '../components/GrFileUpload/config'\nimport { grFormConfig } from '../components/GrForm/config'\nimport { grFormFieldConfig } from '../components/GrFormField/config'\nimport { grFormFileConfig } from '../components/GrFormFile/config'\nimport { grFormSectionConfig } from '../components/GrFormSection/config'\nimport { grIconConfig } from '../components/GrIcon/config'\nimport { grImageViewerConfig } from '../components/GrImageViewer/config'\nimport { grInputConfig } from '../components/GrInput/config'\nimport { grInputTagConfig } from '../components/GrInputTag/config'\nimport { grJsonViewerConfig } from '../components/GrJsonViewer/config'\nimport { grKbdConfig } from '../components/GrKbd/config'\nimport { grLinkConfig } from '../components/GrLink/config'\nimport { grListConfig } from '../components/GrList/config'\nimport { grLoadingConfig } from '../components/GrLoading/config'\nimport { grModalConfig } from '../components/GrModal/config'\nimport { grNavbarConfig } from '../components/GrNavbar/config'\nimport { grNumberInputConfig } from '../components/GrNumberInput/config'\nimport { grPaginationConfig } from '../components/GrPagination/config'\nimport { grPopoverConfig } from '../components/GrPopover/config'\nimport { grProgressBarConfig } from '../components/GrProgressBar/config'\nimport { grProgressCircleConfig } from '../components/GrProgressCircle/config'\nimport { grPromptDialogConfig } from '../components/GrPromptDialog/config'\nimport { grRadioConfig } from '../components/GrRadio/config'\nimport { grRadioGroupConfig } from '../components/GrRadioGroup/config'\nimport { grRatingConfig } from '../components/GrRating/config'\nimport { grResponseErrorBannerConfig } from '../components/GrResponseErrorBanner/config'\nimport { grSegmentedConfig } from '../components/GrSegmented/config'\nimport { grSelectConfig } from '../components/GrSelect/config'\nimport { grSidebarConfig } from '../components/GrSidebar/config'\nimport { grSkeletonConfig } from '../components/GrSkeleton/config'\nimport { grSliderConfig } from '../components/GrSlider/config'\nimport { grSortableListConfig } from '../components/GrSortableList/config'\nimport { grSplitterConfig } from '../components/GrSplitter/config'\nimport { grStatisticConfig } from '../components/GrStatistic/config'\nimport { grStepsConfig } from '../components/GrSteps/config'\nimport { grSwitchConfig } from '../components/GrSwitch/config'\nimport { grTableConfig } from '../components/GrTable/config'\nimport { grTabPanelsConfig } from '../components/GrTabPanels/config'\nimport { grTabsConfig } from '../components/GrTabs/config'\nimport { grTextareaConfig } from '../components/GrTextarea/config'\nimport { grTimelineConfig } from '../components/GrTimeline/config'\nimport { grToasterConfig } from '../components/GrToaster/config'\nimport { grTooltipConfig } from '../components/GrTooltip/config'\nimport { grTreeConfig } from '../components/GrTree/config'\nimport { grTreeSelectConfig } from '../components/GrTreeSelect/config'\nimport { grValueConfig } from '../components/GrValue/config'\n// </granularity:components:imports>\n\n/** Идентификатор провайдера — совпадает с именем пакета. */\nexport const GRANULARITY_PROVIDER_ID = '@feugene/granularity'\n\n// Не заменять на `new URL('..', import.meta.url)`: Vite и rolldown распознают\n// этот литерал и подставляют `data:`-URL, после чего scan-директории пустеют.\nconst packageBaseUrl = resolvePackageBaseUrl(import.meta.url)\n\n/** Встроенные темы пакета. Единственный источник правды о списке тем. */\nexport const granularityThemeNames = ['light', 'dark'] as const\nexport type GranularityThemeName = (typeof granularityThemeNames)[number]\n\n/** Темы, активные по умолчанию, если окружение не переопределяет выбор. */\nexport const granularityDefaultThemes: readonly GranularityThemeName[] = ['light']\n\nconst theme = {\n baseCssUrl: new URL('../styles/base.css', import.meta.url).href,\n tokensCssUrl: new URL('../styles/tokens.css', import.meta.url).href,\n themes: {\n light: new URL('../styles/themes/light.css', import.meta.url).href,\n dark: new URL('../styles/themes/dark.css', import.meta.url).href,\n },\n defaultThemes: granularityDefaultThemes,\n} as const\n\n/**\n * Реестр публичных компонентов. Ключи совпадают с subpath-экспортом\n * `@feugene/granularity/components/<Name>`.\n */\nexport const granularityComponentConfigs = {\n // <granularity:components:registry> — блок генерируется `yarn generate:registry`\n GrAlert: grAlertConfig,\n GrAutocomplete: grAutocompleteConfig,\n GrAvatar: grAvatarConfig,\n GrBadge: grBadgeConfig,\n GrBadgeWrap: grBadgeWrapConfig,\n GrBottomNav: grBottomNavConfig,\n GrBreadcrumbs: grBreadcrumbsConfig,\n GrButton: grButtonConfig,\n GrButtonGroup: grButtonGroupConfig,\n GrCard: grCardConfig,\n GrCheckbox: grCheckboxConfig,\n GrCheckboxGroup: grCheckboxGroupConfig,\n GrChip: grChipConfig,\n GrChipGroup: grChipGroupConfig,\n GrCodeBlock: grCodeBlockConfig,\n GrCollapse: grCollapseConfig,\n GrColorPicker: grColorPickerConfig,\n GrCommandPalette: grCommandPaletteConfig,\n GrConfigProvider: grConfigProviderConfig,\n GrConfirmDialog: grConfirmDialogConfig,\n GrContextMenu: grContextMenuConfig,\n GrDataTable: grDataTableConfig,\n GrDelta: grDeltaConfig,\n GrDescriptionList: grDescriptionListConfig,\n GrDialog: grDialogConfig,\n GrDialogService: grDialogServiceConfig,\n GrDivider: grDividerConfig,\n GrDrawer: grDrawerConfig,\n GrDropdown: grDropdownConfig,\n GrDropdownMenu: grDropdownMenuConfig,\n GrEmptyState: grEmptyStateConfig,\n GrFilePreview: grFilePreviewConfig,\n GrFileUpload: grFileUploadConfig,\n GrForm: grFormConfig,\n GrFormField: grFormFieldConfig,\n GrFormFile: grFormFileConfig,\n GrFormSection: grFormSectionConfig,\n GrIcon: grIconConfig,\n GrImageViewer: grImageViewerConfig,\n GrInput: grInputConfig,\n GrInputTag: grInputTagConfig,\n GrJsonViewer: grJsonViewerConfig,\n GrKbd: grKbdConfig,\n GrLink: grLinkConfig,\n GrList: grListConfig,\n GrLoading: grLoadingConfig,\n GrModal: grModalConfig,\n GrNavbar: grNavbarConfig,\n GrNumberInput: grNumberInputConfig,\n GrPagination: grPaginationConfig,\n GrPopover: grPopoverConfig,\n GrProgressBar: grProgressBarConfig,\n GrProgressCircle: grProgressCircleConfig,\n GrPromptDialog: grPromptDialogConfig,\n GrRadio: grRadioConfig,\n GrRadioGroup: grRadioGroupConfig,\n GrRating: grRatingConfig,\n GrResponseErrorBanner: grResponseErrorBannerConfig,\n GrSegmented: grSegmentedConfig,\n GrSelect: grSelectConfig,\n GrSidebar: grSidebarConfig,\n GrSkeleton: grSkeletonConfig,\n GrSlider: grSliderConfig,\n GrSortableList: grSortableListConfig,\n GrSplitter: grSplitterConfig,\n GrStatistic: grStatisticConfig,\n GrSteps: grStepsConfig,\n GrSwitch: grSwitchConfig,\n GrTable: grTableConfig,\n GrTabPanels: grTabPanelsConfig,\n GrTabs: grTabsConfig,\n GrTextarea: grTextareaConfig,\n GrTimeline: grTimelineConfig,\n GrToaster: grToasterConfig,\n GrTooltip: grTooltipConfig,\n GrTree: grTreeConfig,\n GrTreeSelect: grTreeSelectConfig,\n GrValue: grValueConfig,\n // </granularity:components:registry>\n}\n\nexport type GranularityComponentName = keyof typeof granularityComponentConfigs\n\n/** Базовый набор компонентов в порядке реестра. */\nconst baseComponents: readonly GranularComponentDescriptor[] = Object.values(\n granularityComponentConfigs,\n)\n\n/**\n * Собирает granular-provider пакета.\n *\n * `overrides` — точка расширения для потребителя: дескриптор с именем из\n * базового реестра заменяет его, остальные дописываются в конец.\n */\nexport function createGranularityProvider(\n overrides: readonly GranularComponentDescriptor[] = [],\n): GranularProvider {\n const overrideByName = new Map(\n overrides.map(component => [component.name, component]),\n )\n const components: GranularComponentDescriptor[] = [\n ...baseComponents.map(component => overrideByName.get(component.name) ?? component),\n ...overrides.filter(component => !baseComponents.some(base => base.name === component.name)),\n ]\n\n return defineGranularProvider({\n id: GRANULARITY_PROVIDER_ID,\n contractVersion: 1,\n packageBaseUrl,\n components,\n theme,\n })\n}\n","import { createGranularityProvider } from './shared'\n\nexport * from './shared'\n\nexport const granularityProvider = createGranularityProvider()\n\nexport default granularityProvider\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAIA,IAAa,iBAAiB,wBAAwB,OAAO,KAAK,KAAK;CACrE,MAAM;CACN,cAAc,CAAC,YAAY,SAAS;CACpC,UAAU;AACZ,CAAC;;;ACND,IAAa,8BAA8B,wBAAwB,OAAO,KAAK,KAAK;CAClF,MAAM;CACN,cAAc,CAAC,WAAW,UAAU;AACtC,CAAC;;;;AC4FD,IAAa,0BAA0B;AAIvC,IAAM,iBAAiB,sBAAsB,OAAO,KAAK,GAAG;;AAG5D,IAAa,wBAAwB,CAAC,SAAS,MAAM;;AAIrD,IAAa,2BAA4D,CAAC,OAAO;AAEjF,IAAM,QAAQ;CACZ,YAAY,IAAA,IAAA,q5KAAA,KAAA,OAAA,KAAA,GAAA,CAA8C,CAAC;CAC3D,cAAc,IAAA,IAAA,65YAAA,KAAA,OAAA,KAAA,GAAA,CAAgD,CAAC;CAC/D,QAAQ;EACN,OAAO,IAAA,IAAA,iyYAAA,KAAA,OAAA,KAAA,GAAA,CAAsD,CAAC;EAC9D,MAAM,IAAA,IAAA,y/SAAA,KAAA,OAAA,KAAA,GAAA,CAAqD,CAAC;CAC9D;CACA,eAAe;AACjB;;;;;AAMA,IAAa,8BAA8B;CAEzC,SAAS;CACT,gBAAgB;CAChB,UAAU;CACV,SAAS;CACT,aAAa;CACb,aAAa;CACb,eAAe;CACf,UAAU;CACV,eAAe;CACf,QAAQ;CACR,YAAY;CACZ,iBAAiB;CACjB,QAAQ;CACR,aAAa;CACb,aAAa;CACb,YAAY;CACZ,eAAe;CACf,kBAAkB;CAClB,kBAAkB;CAClB,iBAAiB;CACjB,eAAe;CACf,aAAa;CACb,SAAS;CACT,mBAAmB;CACnB,UAAU;CACV,iBAAiB;CACjB,WAAW;CACX,UAAU;CACV,YAAY;CACZ,gBAAgB;CAChB,cAAc;CACd,eAAe;CACf,cAAc;CACd,QAAQ;CACR,aAAa;CACb,YAAY;CACZ,eAAe;CACf,QAAQ;CACR,eAAe;CACf,SAAS;CACT,YAAY;CACZ,cAAc;CACd,OAAO;CACP,QAAQ;CACR,QAAQ;CACR,WAAW;CACX,SAAS;CACT,UAAU;CACV,eAAe;CACf,cAAc;CACd,WAAW;CACX,eAAe;CACf,kBAAkB;CAClB,gBAAgB;CAChB,SAAS;CACT,cAAc;CACd,UAAU;CACV,uBAAuB;CACvB,aAAa;CACb,UAAU;CACV,WAAW;CACX,YAAY;CACZ,UAAU;CACV,gBAAgB;CAChB,YAAY;CACZ,aAAa;CACb,SAAS;CACT,UAAU;CACV,SAAS;CACT,aAAa;CACb,QAAQ;CACR,YAAY;CACZ,YAAY;CACZ,WAAW;CACX,WAAW;CACX,QAAQ;CACR,cAAc;CACd,SAAS;AAEX;;AAKA,IAAM,iBAAyD,OAAO,OACpE,2BACF;;;;;;;AAQA,SAAgB,0BACd,YAAoD,CAAC,GACnC;CAClB,MAAM,iBAAiB,IAAI,IACzB,UAAU,KAAI,cAAa,CAAC,UAAU,MAAM,SAAS,CAAC,CACxD;CAMA,OAAO,uBAAuB;EAC5B,IAAI;EACJ,iBAAiB;EACjB;EACA,aARA,GAAG,eAAe,KAAI,cAAa,eAAe,IAAI,UAAU,IAAI,KAAK,SAAS,GAClF,GAAG,UAAU,QAAO,cAAa,CAAC,eAAe,MAAK,SAAQ,KAAK,SAAS,UAAU,IAAI,CAAC,CAO3F;EACA;CACF,CAAC;AACH;;;AC1OA,IAAa,sBAAsB,0BAA0B"}
1
+ {"version":3,"file":"granular-provider-BhBBIAl7.js","names":[],"sources":["../../src/components/GrDialog/config.ts","../../src/components/GrResponseErrorBanner/config.ts","../../src/granular-provider/shared.ts","../../src/granular-provider/index.ts"],"sourcesContent":["import { defineGranularComponent } from '@feugene/unocss-preset-granular/contract'\n\nimport { grDialogSafelist } from './safelist'\n\nexport const grDialogConfig = defineGranularComponent(import.meta.url, {\n name: 'GrDialog',\n dependencies: ['GrButton', 'GrModal'],\n safelist: grDialogSafelist,\n})\n","import { defineGranularComponent } from '@feugene/unocss-preset-granular/contract'\n\nexport const grResponseErrorBannerConfig = defineGranularComponent(import.meta.url, {\n name: 'GrResponseErrorBanner',\n dependencies: ['GrAlert', 'GrButton'],\n})\n","// `id`, `theme.*`, `packageBaseUrl` и реестр компонентов провайдера.\n//\n// Все `new URL('../styles/...', import.meta.url)` обязаны лежать именно здесь:\n// `shared.ts` в одной директории с `index.ts` / `node.ts`, поэтому\n// относительные пути совпадают. Для браузера безопасно — бандлеры\n// транслируют такие литералы в статические asset-URL.\n//\n// `apps/showcase/scripts/generate-component-api.mjs` читает список компонентов\n// из `Object.keys(granularityComponentConfigs)` через vite SSR.\nimport {\n defineGranularProvider,\n type GranularComponentDescriptor,\n type GranularProvider,\n resolvePackageBaseUrl,\n} from '@feugene/unocss-preset-granular/contract'\n// <granularity:components:imports> — блок генерируется `yarn generate:registry`\nimport { grAlertConfig } from '../components/GrAlert/config'\nimport { grAutocompleteConfig } from '../components/GrAutocomplete/config'\nimport { grAvatarConfig } from '../components/GrAvatar/config'\nimport { grBadgeConfig } from '../components/GrBadge/config'\nimport { grBadgeWrapConfig } from '../components/GrBadgeWrap/config'\nimport { grBottomNavConfig } from '../components/GrBottomNav/config'\nimport { grBreadcrumbsConfig } from '../components/GrBreadcrumbs/config'\nimport { grButtonConfig } from '../components/GrButton/config'\nimport { grButtonGroupConfig } from '../components/GrButtonGroup/config'\nimport { grCardConfig } from '../components/GrCard/config'\nimport { grCheckboxConfig } from '../components/GrCheckbox/config'\nimport { grCheckboxGroupConfig } from '../components/GrCheckboxGroup/config'\nimport { grChipConfig } from '../components/GrChip/config'\nimport { grChipGroupConfig } from '../components/GrChipGroup/config'\nimport { grCodeBlockConfig } from '../components/GrCodeBlock/config'\nimport { grCollapseConfig } from '../components/GrCollapse/config'\nimport { grColorPickerConfig } from '../components/GrColorPicker/config'\nimport { grCommandPaletteConfig } from '../components/GrCommandPalette/config'\nimport { grConfigProviderConfig } from '../components/GrConfigProvider/config'\nimport { grConfirmDialogConfig } from '../components/GrConfirmDialog/config'\nimport { grContextMenuConfig } from '../components/GrContextMenu/config'\nimport { grDataTableConfig } from '../components/GrDataTable/config'\nimport { grDeltaConfig } from '../components/GrDelta/config'\nimport { grDescriptionListConfig } from '../components/GrDescriptionList/config'\nimport { grDialogConfig } from '../components/GrDialog/config'\nimport { grDialogServiceConfig } from '../components/GrDialogService/config'\nimport { grDividerConfig } from '../components/GrDivider/config'\nimport { grDrawerConfig } from '../components/GrDrawer/config'\nimport { grDropdownConfig } from '../components/GrDropdown/config'\nimport { grDropdownMenuConfig } from '../components/GrDropdownMenu/config'\nimport { grEmptyStateConfig } from '../components/GrEmptyState/config'\nimport { grFilePreviewConfig } from '../components/GrFilePreview/config'\nimport { grFileUploadConfig } from '../components/GrFileUpload/config'\nimport { grFormConfig } from '../components/GrForm/config'\nimport { grFormFieldConfig } from '../components/GrFormField/config'\nimport { grFormFileConfig } from '../components/GrFormFile/config'\nimport { grFormSectionConfig } from '../components/GrFormSection/config'\nimport { grIconConfig } from '../components/GrIcon/config'\nimport { grImageViewerConfig } from '../components/GrImageViewer/config'\nimport { grInputConfig } from '../components/GrInput/config'\nimport { grInputTagConfig } from '../components/GrInputTag/config'\nimport { grJsonViewerConfig } from '../components/GrJsonViewer/config'\nimport { grKbdConfig } from '../components/GrKbd/config'\nimport { grLinkConfig } from '../components/GrLink/config'\nimport { grListConfig } from '../components/GrList/config'\nimport { grLoadingConfig } from '../components/GrLoading/config'\nimport { grModalConfig } from '../components/GrModal/config'\nimport { grNavbarConfig } from '../components/GrNavbar/config'\nimport { grNumberInputConfig } from '../components/GrNumberInput/config'\nimport { grPaginationConfig } from '../components/GrPagination/config'\nimport { grPopoverConfig } from '../components/GrPopover/config'\nimport { grProgressBarConfig } from '../components/GrProgressBar/config'\nimport { grProgressCircleConfig } from '../components/GrProgressCircle/config'\nimport { grPromptDialogConfig } from '../components/GrPromptDialog/config'\nimport { grRadioConfig } from '../components/GrRadio/config'\nimport { grRadioGroupConfig } from '../components/GrRadioGroup/config'\nimport { grRatingConfig } from '../components/GrRating/config'\nimport { grResponseErrorBannerConfig } from '../components/GrResponseErrorBanner/config'\nimport { grSegmentedConfig } from '../components/GrSegmented/config'\nimport { grSelectConfig } from '../components/GrSelect/config'\nimport { grSidebarConfig } from '../components/GrSidebar/config'\nimport { grSkeletonConfig } from '../components/GrSkeleton/config'\nimport { grSliderConfig } from '../components/GrSlider/config'\nimport { grSortableListConfig } from '../components/GrSortableList/config'\nimport { grSplitterConfig } from '../components/GrSplitter/config'\nimport { grStatisticConfig } from '../components/GrStatistic/config'\nimport { grStepsConfig } from '../components/GrSteps/config'\nimport { grSwitchConfig } from '../components/GrSwitch/config'\nimport { grTableConfig } from '../components/GrTable/config'\nimport { grTabPanelsConfig } from '../components/GrTabPanels/config'\nimport { grTabsConfig } from '../components/GrTabs/config'\nimport { grTextareaConfig } from '../components/GrTextarea/config'\nimport { grTimelineConfig } from '../components/GrTimeline/config'\nimport { grToasterConfig } from '../components/GrToaster/config'\nimport { grTooltipConfig } from '../components/GrTooltip/config'\nimport { grTreeConfig } from '../components/GrTree/config'\nimport { grTreeSelectConfig } from '../components/GrTreeSelect/config'\nimport { grValueConfig } from '../components/GrValue/config'\n// </granularity:components:imports>\n\n/** Идентификатор провайдера — совпадает с именем пакета. */\nexport const GRANULARITY_PROVIDER_ID = '@feugene/granularity'\n\n// Не заменять на `new URL('..', import.meta.url)`: Vite и rolldown распознают\n// этот литерал и подставляют `data:`-URL, после чего scan-директории пустеют.\nconst packageBaseUrl = resolvePackageBaseUrl(import.meta.url)\n\n/** Встроенные темы пакета. Единственный источник правды о списке тем. */\nexport const granularityThemeNames = ['light', 'dark'] as const\nexport type GranularityThemeName = (typeof granularityThemeNames)[number]\n\n/** Темы, активные по умолчанию, если окружение не переопределяет выбор. */\nexport const granularityDefaultThemes: readonly GranularityThemeName[] = ['light']\n\nconst theme = {\n baseCssUrl: new URL('../styles/base.css', import.meta.url).href,\n tokensCssUrl: new URL('../styles/tokens.css', import.meta.url).href,\n themes: {\n light: new URL('../styles/themes/light.css', import.meta.url).href,\n dark: new URL('../styles/themes/dark.css', import.meta.url).href,\n },\n defaultThemes: granularityDefaultThemes,\n} as const\n\n/**\n * Реестр публичных компонентов. Ключи совпадают с subpath-экспортом\n * `@feugene/granularity/components/<Name>`.\n */\nexport const granularityComponentConfigs = {\n // <granularity:components:registry> — блок генерируется `yarn generate:registry`\n GrAlert: grAlertConfig,\n GrAutocomplete: grAutocompleteConfig,\n GrAvatar: grAvatarConfig,\n GrBadge: grBadgeConfig,\n GrBadgeWrap: grBadgeWrapConfig,\n GrBottomNav: grBottomNavConfig,\n GrBreadcrumbs: grBreadcrumbsConfig,\n GrButton: grButtonConfig,\n GrButtonGroup: grButtonGroupConfig,\n GrCard: grCardConfig,\n GrCheckbox: grCheckboxConfig,\n GrCheckboxGroup: grCheckboxGroupConfig,\n GrChip: grChipConfig,\n GrChipGroup: grChipGroupConfig,\n GrCodeBlock: grCodeBlockConfig,\n GrCollapse: grCollapseConfig,\n GrColorPicker: grColorPickerConfig,\n GrCommandPalette: grCommandPaletteConfig,\n GrConfigProvider: grConfigProviderConfig,\n GrConfirmDialog: grConfirmDialogConfig,\n GrContextMenu: grContextMenuConfig,\n GrDataTable: grDataTableConfig,\n GrDelta: grDeltaConfig,\n GrDescriptionList: grDescriptionListConfig,\n GrDialog: grDialogConfig,\n GrDialogService: grDialogServiceConfig,\n GrDivider: grDividerConfig,\n GrDrawer: grDrawerConfig,\n GrDropdown: grDropdownConfig,\n GrDropdownMenu: grDropdownMenuConfig,\n GrEmptyState: grEmptyStateConfig,\n GrFilePreview: grFilePreviewConfig,\n GrFileUpload: grFileUploadConfig,\n GrForm: grFormConfig,\n GrFormField: grFormFieldConfig,\n GrFormFile: grFormFileConfig,\n GrFormSection: grFormSectionConfig,\n GrIcon: grIconConfig,\n GrImageViewer: grImageViewerConfig,\n GrInput: grInputConfig,\n GrInputTag: grInputTagConfig,\n GrJsonViewer: grJsonViewerConfig,\n GrKbd: grKbdConfig,\n GrLink: grLinkConfig,\n GrList: grListConfig,\n GrLoading: grLoadingConfig,\n GrModal: grModalConfig,\n GrNavbar: grNavbarConfig,\n GrNumberInput: grNumberInputConfig,\n GrPagination: grPaginationConfig,\n GrPopover: grPopoverConfig,\n GrProgressBar: grProgressBarConfig,\n GrProgressCircle: grProgressCircleConfig,\n GrPromptDialog: grPromptDialogConfig,\n GrRadio: grRadioConfig,\n GrRadioGroup: grRadioGroupConfig,\n GrRating: grRatingConfig,\n GrResponseErrorBanner: grResponseErrorBannerConfig,\n GrSegmented: grSegmentedConfig,\n GrSelect: grSelectConfig,\n GrSidebar: grSidebarConfig,\n GrSkeleton: grSkeletonConfig,\n GrSlider: grSliderConfig,\n GrSortableList: grSortableListConfig,\n GrSplitter: grSplitterConfig,\n GrStatistic: grStatisticConfig,\n GrSteps: grStepsConfig,\n GrSwitch: grSwitchConfig,\n GrTable: grTableConfig,\n GrTabPanels: grTabPanelsConfig,\n GrTabs: grTabsConfig,\n GrTextarea: grTextareaConfig,\n GrTimeline: grTimelineConfig,\n GrToaster: grToasterConfig,\n GrTooltip: grTooltipConfig,\n GrTree: grTreeConfig,\n GrTreeSelect: grTreeSelectConfig,\n GrValue: grValueConfig,\n // </granularity:components:registry>\n}\n\nexport type GranularityComponentName = keyof typeof granularityComponentConfigs\n\n/** Базовый набор компонентов в порядке реестра. */\nconst baseComponents: readonly GranularComponentDescriptor[] = Object.values(\n granularityComponentConfigs,\n)\n\n/**\n * Собирает granular-provider пакета.\n *\n * `overrides` — точка расширения для потребителя: дескриптор с именем из\n * базового реестра заменяет его, остальные дописываются в конец.\n */\nexport function createGranularityProvider(\n overrides: readonly GranularComponentDescriptor[] = [],\n): GranularProvider {\n const overrideByName = new Map(\n overrides.map(component => [component.name, component]),\n )\n const components: GranularComponentDescriptor[] = [\n ...baseComponents.map(component => overrideByName.get(component.name) ?? component),\n ...overrides.filter(component => !baseComponents.some(base => base.name === component.name)),\n ]\n\n return defineGranularProvider({\n id: GRANULARITY_PROVIDER_ID,\n contractVersion: 1,\n packageBaseUrl,\n components,\n theme,\n })\n}\n","import { createGranularityProvider } from './shared'\n\nexport * from './shared'\n\nexport const granularityProvider = createGranularityProvider()\n\nexport default granularityProvider\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAIA,IAAa,iBAAiB,wBAAwB,OAAO,KAAK,KAAK;CACrE,MAAM;CACN,cAAc,CAAC,YAAY,SAAS;CACpC,UAAU;AACZ,CAAC;;;ACND,IAAa,8BAA8B,wBAAwB,OAAO,KAAK,KAAK;CAClF,MAAM;CACN,cAAc,CAAC,WAAW,UAAU;AACtC,CAAC;;;;AC4FD,IAAa,0BAA0B;AAIvC,IAAM,iBAAiB,sBAAsB,OAAO,KAAK,GAAG;;AAG5D,IAAa,wBAAwB,CAAC,SAAS,MAAM;;AAIrD,IAAa,2BAA4D,CAAC,OAAO;AAEjF,IAAM,QAAQ;CACZ,YAAY,IAAA,IAAA,q5KAAA,KAAA,OAAA,KAAA,GAAA,CAA8C,CAAC;CAC3D,cAAc,IAAA,IAAA,65YAAA,KAAA,OAAA,KAAA,GAAA,CAAgD,CAAC;CAC/D,QAAQ;EACN,OAAO,IAAA,IAAA,iyYAAA,KAAA,OAAA,KAAA,GAAA,CAAsD,CAAC;EAC9D,MAAM,IAAA,IAAA,y/SAAA,KAAA,OAAA,KAAA,GAAA,CAAqD,CAAC;CAC9D;CACA,eAAe;AACjB;;;;;AAMA,IAAa,8BAA8B;CAEzC,SAAS;CACT,gBAAgB;CAChB,UAAU;CACV,SAAS;CACT,aAAa;CACb,aAAa;CACb,eAAe;CACf,UAAU;CACV,eAAe;CACf,QAAQ;CACR,YAAY;CACZ,iBAAiB;CACjB,QAAQ;CACR,aAAa;CACb,aAAa;CACb,YAAY;CACZ,eAAe;CACf,kBAAkB;CAClB,kBAAkB;CAClB,iBAAiB;CACjB,eAAe;CACf,aAAa;CACb,SAAS;CACT,mBAAmB;CACnB,UAAU;CACV,iBAAiB;CACjB,WAAW;CACX,UAAU;CACV,YAAY;CACZ,gBAAgB;CAChB,cAAc;CACd,eAAe;CACf,cAAc;CACd,QAAQ;CACR,aAAa;CACb,YAAY;CACZ,eAAe;CACf,QAAQ;CACR,eAAe;CACf,SAAS;CACT,YAAY;CACZ,cAAc;CACd,OAAO;CACP,QAAQ;CACR,QAAQ;CACR,WAAW;CACX,SAAS;CACT,UAAU;CACV,eAAe;CACf,cAAc;CACd,WAAW;CACX,eAAe;CACf,kBAAkB;CAClB,gBAAgB;CAChB,SAAS;CACT,cAAc;CACd,UAAU;CACV,uBAAuB;CACvB,aAAa;CACb,UAAU;CACV,WAAW;CACX,YAAY;CACZ,UAAU;CACV,gBAAgB;CAChB,YAAY;CACZ,aAAa;CACb,SAAS;CACT,UAAU;CACV,SAAS;CACT,aAAa;CACb,QAAQ;CACR,YAAY;CACZ,YAAY;CACZ,WAAW;CACX,WAAW;CACX,QAAQ;CACR,cAAc;CACd,SAAS;AAEX;;AAKA,IAAM,iBAAyD,OAAO,OACpE,2BACF;;;;;;;AAQA,SAAgB,0BACd,YAAoD,CAAC,GACnC;CAClB,MAAM,iBAAiB,IAAI,IACzB,UAAU,KAAI,cAAa,CAAC,UAAU,MAAM,SAAS,CAAC,CACxD;CAMA,OAAO,uBAAuB;EAC5B,IAAI;EACJ,iBAAiB;EACjB;EACA,aARA,GAAG,eAAe,KAAI,cAAa,eAAe,IAAI,UAAU,IAAI,KAAK,SAAS,GAClF,GAAG,UAAU,QAAO,cAAa,CAAC,eAAe,MAAK,SAAQ,KAAK,SAAS,UAAU,IAAI,CAAC,CAO3F;EACA;CACF,CAAC;AACH;;;AC1OA,IAAa,sBAAsB,0BAA0B"}
@@ -2,7 +2,7 @@ import { a as useGrComponentSize, i as useGrComponentProp } from "../../../chunk
2
2
  import { n as useGranularityTranslations } from "../../../chunks/granularityI18n-BC5zsj_d.js";
3
3
  import { t as GrLink_default } from "../../GrLink/chunks/GrLink-DAGCFYO7.js";
4
4
  import { n as iconTag, t as iconClass } from "../../../chunks/icon-Dag2tIMs.js";
5
- import { a as breadcrumbsItemIconClass, c as breadcrumbsListNowrapClass, d as breadcrumbsSeparatorClass, f as breadcrumbsSizeClassBySize, i as breadcrumbsEllipsisClass, l as breadcrumbsListWrapClass, m as resolveBreadcrumbsLayout, o as breadcrumbsLabelClass, p as resolveBreadcrumbsFit, r as breadcrumbsCurrentClass, s as breadcrumbsListClass, u as breadcrumbsRootClass } from "../../../chunks/config-DcfWZOi5.js";
5
+ import { a as breadcrumbsItemIconClass, c as breadcrumbsListClass, d as breadcrumbsRootClass, f as breadcrumbsSeparatorClass, h as resolveBreadcrumbsLayout, i as breadcrumbsEllipsisClass, l as breadcrumbsListNowrapClass, m as resolveBreadcrumbsFit, o as breadcrumbsItemWrapClass, p as breadcrumbsSizeClassBySize, r as breadcrumbsCurrentClass, s as breadcrumbsLabelClass, u as breadcrumbsListWrapClass } from "../../../chunks/config-DONFPTFg.js";
6
6
  import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, defineComponent, nextTick, normalizeClass, onBeforeUnmount, onMounted, openBlock, ref, renderList, renderSlot, resolveDynamicComponent, toDisplayString, unref, watch, withCtx } from "vue";
7
7
  //#region src/components/GrBreadcrumbs/GrBreadcrumbs.vue?vue&type=script&setup=true&lang.ts
8
8
  var _hoisted_1 = ["aria-label"];
@@ -12,12 +12,7 @@ var _hoisted_2 = {
12
12
  class: "min-w-0"
13
13
  };
14
14
  var _hoisted_3 = ["aria-label"];
15
- var _hoisted_4 = {
16
- key: 2,
17
- "data-gr-breadcrumbs-item-wrap": "",
18
- class: "min-w-0"
19
- };
20
- var _hoisted_5 = [
15
+ var _hoisted_4 = [
21
16
  "aria-label",
22
17
  "aria-current",
23
18
  "aria-disabled"
@@ -39,6 +34,7 @@ var GrBreadcrumbs_default = /* @__PURE__ */ defineComponent({
39
34
  type: Boolean,
40
35
  default: false
41
36
  },
37
+ currentIndex: { default: void 0 },
42
38
  expandLabel: { default: void 0 },
43
39
  autoCollapse: {
44
40
  type: Boolean,
@@ -102,14 +98,17 @@ var GrBreadcrumbs_default = /* @__PURE__ */ defineComponent({
102
98
  itemsAfterCollapse: effectiveAfter.value,
103
99
  expanded: expanded.value
104
100
  }));
101
+ const resolvedCurrentIndex = computed(() => props.currentIndex ?? lastIndex.value);
105
102
  function isCurrent(index) {
106
- return index === lastIndex.value;
103
+ return index === resolvedCurrentIndex.value;
107
104
  }
108
- /** Ссылкой пункт становится, только если ведёт куда-то и не выключен. */
105
+ /**
106
+ * Ссылкой пункт становится, только если ведёт куда-то и не выключен.
107
+ */
109
108
  function isLink(item, index) {
110
109
  if (item.disabled) return false;
111
110
  if (isCurrent(index) && !props.linkCurrent) return false;
112
- return item.href !== void 0 || item.to !== void 0 || props.as !== void 0;
111
+ return item.href !== void 0 || item.to !== void 0;
113
112
  }
114
113
  function widthOf(el) {
115
114
  return el ? el.scrollWidth : 0;
@@ -213,7 +212,11 @@ var GrBreadcrumbs_default = /* @__PURE__ */ defineComponent({
213
212
  class: normalizeClass(unref(breadcrumbsEllipsisClass)),
214
213
  "aria-label": resolvedExpandLabel.value,
215
214
  onClick: expand
216
- }, " … ", 10, _hoisted_3)])])) : (openBlock(), createElementBlock("li", _hoisted_4, [isLink(entry.item, entry.index) ? (openBlock(), createBlock(GrLink_default, {
215
+ }, " … ", 10, _hoisted_3)])])) : (openBlock(), createElementBlock("li", {
216
+ key: 2,
217
+ "data-gr-breadcrumbs-item-wrap": "",
218
+ class: normalizeClass(unref(breadcrumbsItemWrapClass))
219
+ }, [isLink(entry.item, entry.index) ? (openBlock(), createBlock(GrLink_default, {
217
220
  key: `link-${entry.index}`,
218
221
  "data-gr-breadcrumbs-item": "",
219
222
  as: __props.as,
@@ -258,7 +261,7 @@ var GrBreadcrumbs_default = /* @__PURE__ */ defineComponent({
258
261
  key: 0,
259
262
  class: normalizeClass([unref(breadcrumbsItemIconClass), unref(iconClass)(entry.item.icon)]),
260
263
  "aria-hidden": "true"
261
- }, null, 8, ["class"])) : createCommentVNode("", true), createElementVNode("span", { class: normalizeClass(unref(breadcrumbsLabelClass)) }, toDisplayString(entry.item.label), 3)])], 10, _hoisted_5))]))], 64);
264
+ }, null, 8, ["class"])) : createCommentVNode("", true), createElementVNode("span", { class: normalizeClass(unref(breadcrumbsLabelClass)) }, toDisplayString(entry.item.label), 3)])], 10, _hoisted_4))], 2))], 64);
262
265
  }), 128))], 2)], 10, _hoisted_1);
263
266
  };
264
267
  }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GrBreadcrumbs-BYbfo-49.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 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) return undefined\n if (fitAfter.value >= props.items.length) 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 */\nfunction isLink(item: GrBreadcrumbItem, index: number): boolean {\n if (item.disabled) return false\n if (isCurrent(index) && !props.linkCurrent) 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) return\n\n const list = listEl.value\n if (!list) return\n\n const ellipsis = list.querySelector('[data-gr-breadcrumbs-ellipsis-item]')\n if (ellipsis) ellipsisWidth = widthOf(ellipsis)\n\n if (!measured) {\n // Ширины снимаем только с полного пути: спрятанных пунктов в DOM нет.\n if (fitAfter.value !== undefined) return\n\n const wraps = [...list.querySelectorAll('[data-gr-breadcrumbs-item-wrap]')]\n if (wraps.length !== props.items.length) 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) 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') 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') 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) 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=\"breadcrumbsLabelClass\">{{ 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=\"breadcrumbsLabelClass\">{{ 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 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) return undefined\n if (fitAfter.value >= props.items.length) 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 */\nfunction isLink(item: GrBreadcrumbItem, index: number): boolean {\n if (item.disabled) return false\n if (isCurrent(index) && !props.linkCurrent) 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) return\n\n const list = listEl.value\n if (!list) return\n\n const ellipsis = list.querySelector('[data-gr-breadcrumbs-ellipsis-item]')\n if (ellipsis) ellipsisWidth = widthOf(ellipsis)\n\n if (!measured) {\n // Ширины снимаем только с полного пути: спрятанных пунктов в DOM нет.\n if (fitAfter.value !== undefined) return\n\n const wraps = [...list.querySelectorAll('[data-gr-breadcrumbs-item-wrap]')]\n if (wraps.length !== props.items.length) 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) 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') 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') 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) 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=\"breadcrumbsLabelClass\">{{ 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=\"breadcrumbsLabelClass\">{{ entry.item.label }}</span>\n </slot>\n </span>\n </li>\n </template>\n </ol>\n </nav>\n</template>\n"],"mappings":"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"}
@@ -1,3 +1,3 @@
1
- import { m as resolveBreadcrumbsLayout, n as grBreadcrumbsSafelist, p as resolveBreadcrumbsFit, t as grBreadcrumbsConfig } from "../../chunks/config-DcfWZOi5.js";
2
- import { t as GrBreadcrumbs_default } from "./chunks/GrBreadcrumbs-jtSnMEtg.js";
1
+ import { h as resolveBreadcrumbsLayout, m as resolveBreadcrumbsFit, n as grBreadcrumbsSafelist, t as grBreadcrumbsConfig } from "../../chunks/config-DONFPTFg.js";
2
+ import { t as GrBreadcrumbs_default } from "./chunks/GrBreadcrumbs-BYbfo-49.js";
3
3
  export { GrBreadcrumbs_default as GrBreadcrumbs, GrBreadcrumbs_default as default, grBreadcrumbsConfig, grBreadcrumbsSafelist, resolveBreadcrumbsFit, resolveBreadcrumbsLayout };
@@ -1,2 +1,2 @@
1
- import { a as granularityDefaultThemes, i as granularityComponentConfigs, n as GRANULARITY_PROVIDER_ID, o as granularityThemeNames, r as createGranularityProvider, t as granularityProvider } from "./chunks/granular-provider-DvvRayEh.js";
1
+ import { a as granularityDefaultThemes, i as granularityComponentConfigs, n as GRANULARITY_PROVIDER_ID, o as granularityThemeNames, r as createGranularityProvider, t as granularityProvider } from "./chunks/granular-provider-BhBBIAl7.js";
2
2
  export { GRANULARITY_PROVIDER_ID, createGranularityProvider, granularityProvider as default, granularityProvider, granularityComponentConfigs, granularityDefaultThemes, granularityThemeNames };
@@ -1,2 +1,2 @@
1
- import { a as granularityDefaultThemes, i as granularityComponentConfigs, n as GRANULARITY_PROVIDER_ID, o as granularityThemeNames, r as createGranularityProvider, t as granularityProvider } from "./chunks/granular-provider-DvvRayEh.js";
1
+ import { a as granularityDefaultThemes, i as granularityComponentConfigs, n as GRANULARITY_PROVIDER_ID, o as granularityThemeNames, r as createGranularityProvider, t as granularityProvider } from "./chunks/granular-provider-BhBBIAl7.js";
2
2
  export { GRANULARITY_PROVIDER_ID, createGranularityProvider, granularityProvider as default, granularityProvider, granularityComponentConfigs, granularityDefaultThemes, granularityThemeNames };
package/dist/index.js CHANGED
@@ -35,8 +35,8 @@ import { t as GrBadgeWrap_default } from "./components/GrBadgeWrap/chunks/GrBadg
35
35
  import { n as grBottomNavSafelist, t as grBottomNavConfig } from "./chunks/config-DM0lsduD.js";
36
36
  import { t as GrBottomNav_default } from "./components/GrBottomNav/chunks/GrBottomNav-wtFatLjB.js";
37
37
  import { t as GrLink_default } from "./components/GrLink/chunks/GrLink-DAGCFYO7.js";
38
- import { m as resolveBreadcrumbsLayout, n as grBreadcrumbsSafelist, p as resolveBreadcrumbsFit, t as grBreadcrumbsConfig } from "./chunks/config-DcfWZOi5.js";
39
- import { t as GrBreadcrumbs_default } from "./components/GrBreadcrumbs/chunks/GrBreadcrumbs-jtSnMEtg.js";
38
+ import { h as resolveBreadcrumbsLayout, m as resolveBreadcrumbsFit, n as grBreadcrumbsSafelist, t as grBreadcrumbsConfig } from "./chunks/config-DONFPTFg.js";
39
+ import { t as GrBreadcrumbs_default } from "./components/GrBreadcrumbs/chunks/GrBreadcrumbs-BYbfo-49.js";
40
40
  import { n as useGrButtonGroup } from "./chunks/context-BfIQv7m1.js";
41
41
  import { t as GrButton_default } from "./components/GrButton/chunks/GrButton-Bw2AH4uX.js";
42
42
  import { t as grButtonSafelist } from "./chunks/safelist-BOq5Li7p.js";
@@ -28,6 +28,14 @@ export interface GrBreadcrumbsProps {
28
28
  ariaLabel?: string;
29
29
  /** Последний пункт остаётся ссылкой (текущая страница кликабельна). */
30
30
  linkCurrent?: boolean;
31
+ /**
32
+ * Какой пункт — текущая страница. Не задан — последний.
33
+ *
34
+ * `-1` означает, что текущей страницы в пути нет: её имя стоит в `h1`, а
35
+ * путь показывает только родителей. Тогда `aria-current` не получает никто,
36
+ * и последний пункт остаётся обычной ссылкой.
37
+ */
38
+ currentIndex?: number;
31
39
  /** i18n-метка кнопки раскрытия схлопнутого пути. */
32
40
  expandLabel?: string;
33
41
  /**
@@ -64,6 +72,7 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
64
72
  itemsAfterCollapse: number;
65
73
  ariaLabel: undefined;
66
74
  linkCurrent: boolean;
75
+ currentIndex: undefined;
67
76
  expandLabel: undefined;
68
77
  autoCollapse: boolean;
69
78
  }>>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<__VLS_WithDefaults<__VLS_TypePropsToOption<GrBreadcrumbsProps>, {
@@ -75,6 +84,7 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
75
84
  itemsAfterCollapse: number;
76
85
  ariaLabel: undefined;
77
86
  linkCurrent: boolean;
87
+ currentIndex: undefined;
78
88
  expandLabel: undefined;
79
89
  autoCollapse: boolean;
80
90
  }>>> & Readonly<{}>, {
@@ -86,6 +96,7 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
86
96
  maxItems: number;
87
97
  itemsAfterCollapse: number;
88
98
  linkCurrent: boolean;
99
+ currentIndex: number;
89
100
  expandLabel: string;
90
101
  autoCollapse: boolean;
91
102
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
@@ -35,6 +35,15 @@ export declare const breadcrumbsSizeClassBySize: Record<GrBreadcrumbsSize, strin
35
35
  export declare const breadcrumbsSeparatorClass = "select-none text-[var(--gr-muted-fg)]";
36
36
  /** Текущая страница: не ссылка, но и не «выключенный» текст — обычный акцент подписи. */
37
37
  export declare const breadcrumbsCurrentClass = "inline-flex items-center gap-1 min-w-0 font-600 text-[var(--gr-fg)]";
38
+ /**
39
+ * Обёртка пункта — flex, а не блок с линейным боксом.
40
+ *
41
+ * Базовая линия `inline-flex` берётся от первого flex-элемента: у пункта
42
+ * с иконкой это блочная иконка, и линейный бокс раздувается на высоту
43
+ * подстрочных выносных. Пункт с иконкой выходил на 3px выше соседей, а при
44
+ * `align-items: center` списка это смещало его содержимое на полтора пикселя.
45
+ */
46
+ export declare const breadcrumbsItemWrapClass = "flex items-center min-w-0";
38
47
  export declare const breadcrumbsItemIconClass = "inline-block h-4 w-4 shrink-0";
39
48
  export declare const breadcrumbsLabelClass = "truncate";
40
49
  /**
package/dist/vue/index.js CHANGED
@@ -30,6 +30,7 @@ function installComponent(app, c) {
30
30
  app.component(resolvedName, c);
31
31
  return;
32
32
  }
33
+ if (!(typeof process !== "undefined" && process.env.NODE_ENV !== "production")) return;
33
34
  console.warn("[granularity] component passed to createGranularity has no `install`, `name`, or `__name` — skipped.");
34
35
  }
35
36
  function installDirective(app, d) {
@@ -41,6 +42,7 @@ function installDirective(app, d) {
41
42
  app.directive(d.name, d.directive);
42
43
  return;
43
44
  }
45
+ if (!(typeof process !== "undefined" && process.env.NODE_ENV !== "production")) return;
44
46
  console.warn("[granularity] directive passed to createGranularity has no `install` or `{ name, directive }` — skipped.");
45
47
  }
46
48
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/vue/plugin.ts"],"sourcesContent":["import type { App, Component, Directive, Plugin } from 'vue'\n\n/**\n * Лёгкий контейнер, который может быть зарегистрирован в Vue:\n * - SFC-компонент (Vue сам компилирует `__name` из имени файла) — достаточно просто передать сам компонент,\n * - SFC-компонент с опциональным `install` (по аналогии с Element Plus / Naive UI),\n * - объект с собственным `install`,\n * - либо объект `{ name, component }`, если имя нужно задать явно.\n */\nexport type GranularityInstallableComponent =\n | (Component & { name?: string; __name?: string; install?: (app: App) => void })\n | { install: (app: App) => void }\n | { name: string; component: Component }\n\n/**\n * Директива — либо пара `{ name, directive }`, либо объект с `install`.\n */\nexport type GranularityInstallableDirective =\n | { name: string; directive: Directive }\n | { install: (app: App) => void }\n\nexport interface CreateGranularityOptions {\n /**\n * Список компонентов для глобальной регистрации.\n * Пакет сам по себе ничего не импортирует — пользователь передаёт сюда только те компоненты,\n * которые ему действительно нужны. За счёт этого tree-shaking остаётся полным.\n */\n components?: GranularityInstallableComponent[]\n /**\n * Список директив. Передавайте ровно те, что нужны, по аналогии с компонентами.\n */\n directives?: GranularityInstallableDirective[]\n /**\n * Произвольные `provide`-значения. Ключи могут быть `symbol` или строкой.\n * Пакет не навязывает конкретные ключи (например, i18n) — выбор за пользователем.\n */\n provides?: Array<{ key: string | symbol; value: unknown }>\n /**\n * Произвольные `app.config.globalProperties` — по умолчанию ничего не ставим.\n */\n globalProperties?: Record<string, unknown>\n}\n\n/**\n * Вспомогательная обёртка: превращает произвольный компонент в `GranularityInstallableComponent`\n * с явным именем регистрации.\n *\n * ```ts\n * import { GrButton } from '@feugene/granularity/components/GrButton'\n * import { defineInstallable } from '@feugene/granularity/vue'\n *\n * const installable = defineInstallable(GrButton, 'GrButton')\n * ```\n */\nexport function defineInstallable<T extends Component>(component: T, name: string): GranularityInstallableComponent {\n return {\n install(app: App) {\n app.component(name, component)\n },\n }\n}\n\nfunction installComponent(app: App, c: GranularityInstallableComponent): void {\n if (typeof (c as any).install === 'function') {\n ;(c as any).install(app)\n return\n }\n\n // Форма `{ name, component }` — явное указание имени для произвольного компонента.\n // Допускаем как object-компоненты (SFC / options), так и functional-компоненты.\n if (\n typeof (c as any).name === 'string'\n && (c as any).component\n && (typeof (c as any).component === 'object' || typeof (c as any).component === 'function')\n ) {\n const {name, component} = c as { name: string; component: Component }\n app.component(name, component)\n return\n }\n\n // SFC: Vue 3 компилирует имя компонента из имени файла в поле `__name`\n // (например, `GrButton.vue` → `__name: 'GrButton'`). Также поддерживаем явное `name`.\n const resolvedName\n = (c as Component & { name?: string }).name\n ?? (c as { __name?: string }).__name\n if (resolvedName) {\n app.component(resolvedName, c as Component)\n return\n }\n\n // Сознательно без throw — просто сообщаем в dev-режиме.\n \n console.warn('[granularity] component passed to createGranularity has no `install`, `name`, or `__name` — skipped.')\n}\n\nfunction installDirective(app: App, d: GranularityInstallableDirective): void {\n if ('install' in d && typeof d.install === 'function') {\n d.install(app)\n return\n }\n\n if ('name' in d && 'directive' in d) {\n app.directive(d.name, d.directive)\n return\n }\n\n // Сознательно без throw — согласовано с `installComponent`.\n \n console.warn('[granularity] directive passed to createGranularity has no `install` or `{ name, directive }` — skipped.')\n}\n\n/**\n * Применяет опции к переданному приложению. Экспортируется отдельно от фабрики —\n * удобно, когда плагин не нужен (например, в тестах).\n */\nexport function installGranularity(app: App, options: CreateGranularityOptions = {}): void {\n for (const c of options.components ?? [])\n installComponent(app, c)\n\n for (const d of options.directives ?? [])\n installDirective(app, d)\n\n for (const {key, value} of options.provides ?? [])\n app.provide(key as any, value)\n\n if (options.globalProperties) {\n for (const [key, value] of Object.entries(options.globalProperties))\n app.config.globalProperties[key] = value\n }\n}\n\n/**\n * Создаёт Vue-плагин `{ install }`, который при `app.use(...)` зарегистрирует переданные\n * компоненты, директивы и provide-значения. Сам модуль НЕ импортирует ни компонентов,\n * ни директив из пакета — всё приходит снаружи. Это сохраняет гранулярность:\n * бандлер увидит только те символы, которые вы реально передали.\n *\n * ```ts\n * import { createApp } from 'vue'\n * import { createGranularity } from '@feugene/granularity/vue'\n * import { GrButton } from '@feugene/granularity/components/GrButton'\n * import { GrInput } from '@feugene/granularity/components/GrInput'\n * import { vHotkey } from '@feugene/granularity/directives/hotkey'\n *\n * createApp(App)\n * .use(createGranularity({\n * // Достаточно передать сами компоненты — имя будет взято из `__name` (Vue SFC)\n * // или из `name`; можно также указать явно через `{ name, component }`.\n * components: [GrButton, GrInput],\n * directives: [{ name: 'hotkey', directive: vHotkey }],\n * }))\n * .mount('#app')\n * ```\n */\nexport function createGranularity(options: CreateGranularityOptions = {}): Plugin {\n return {\n install(app: App) {\n installGranularity(app, options)\n },\n }\n}\n"],"mappings":";;;;;;;;;;;;AAsDA,SAAgB,kBAAuC,WAAc,MAA+C;CAChH,OAAO,EACH,QAAQ,KAAU;EACd,IAAI,UAAU,MAAM,SAAS;CACjC,EACJ;AACJ;AAEA,SAAS,iBAAiB,KAAU,GAA0C;CAC1E,IAAI,OAAQ,EAAU,YAAY,YAAY;EACzC,EAAW,QAAQ,GAAG;EACvB;CACJ;CAIA,IACI,OAAQ,EAAU,SAAS,YACvB,EAAU,cACV,OAAQ,EAAU,cAAc,YAAY,OAAQ,EAAU,cAAc,aAClF;EACE,MAAM,EAAC,MAAM,cAAa;EAC1B,IAAI,UAAU,MAAM,SAAS;EAC7B;CACJ;CAIA,MAAM,eACC,EAAoC,QAC/B,EAA0B;CACtC,IAAI,cAAc;EACd,IAAI,UAAU,cAAc,CAAc;EAC1C;CACJ;CAIA,QAAQ,KAAK,sGAAsG;AACvH;AAEA,SAAS,iBAAiB,KAAU,GAA0C;CAC1E,IAAI,aAAa,KAAK,OAAO,EAAE,YAAY,YAAY;EACnD,EAAE,QAAQ,GAAG;EACb;CACJ;CAEA,IAAI,UAAU,KAAK,eAAe,GAAG;EACjC,IAAI,UAAU,EAAE,MAAM,EAAE,SAAS;EACjC;CACJ;CAIA,QAAQ,KAAK,0GAA0G;AAC3H;;;;;AAMA,SAAgB,mBAAmB,KAAU,UAAoC,CAAC,GAAS;CACvF,KAAK,MAAM,KAAK,QAAQ,cAAc,CAAC,GACnC,iBAAiB,KAAK,CAAC;CAE3B,KAAK,MAAM,KAAK,QAAQ,cAAc,CAAC,GACnC,iBAAiB,KAAK,CAAC;CAE3B,KAAK,MAAM,EAAC,KAAK,WAAU,QAAQ,YAAY,CAAC,GAC5C,IAAI,QAAQ,KAAY,KAAK;CAEjC,IAAI,QAAQ,kBACR,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,QAAQ,gBAAgB,GAC9D,IAAI,OAAO,iBAAiB,OAAO;AAE/C;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,SAAgB,kBAAkB,UAAoC,CAAC,GAAW;CAC9E,OAAO,EACH,QAAQ,KAAU;EACd,mBAAmB,KAAK,OAAO;CACnC,EACJ;AACJ"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/vue/plugin.ts"],"sourcesContent":["import type { App, Component, Directive, Plugin } from 'vue'\n\n/**\n * Лёгкий контейнер, который может быть зарегистрирован в Vue:\n * - SFC-компонент (Vue сам компилирует `__name` из имени файла) — достаточно просто передать сам компонент,\n * - SFC-компонент с опциональным `install` (по аналогии с Element Plus / Naive UI),\n * - объект с собственным `install`,\n * - либо объект `{ name, component }`, если имя нужно задать явно.\n */\nexport type GranularityInstallableComponent =\n | (Component & { name?: string; __name?: string; install?: (app: App) => void })\n | { install: (app: App) => void }\n | { name: string; component: Component }\n\n/**\n * Директива — либо пара `{ name, directive }`, либо объект с `install`.\n */\nexport type GranularityInstallableDirective =\n | { name: string; directive: Directive }\n | { install: (app: App) => void }\n\nexport interface CreateGranularityOptions {\n /**\n * Список компонентов для глобальной регистрации.\n * Пакет сам по себе ничего не импортирует — пользователь передаёт сюда только те компоненты,\n * которые ему действительно нужны. За счёт этого tree-shaking остаётся полным.\n */\n components?: GranularityInstallableComponent[]\n /**\n * Список директив. Передавайте ровно те, что нужны, по аналогии с компонентами.\n */\n directives?: GranularityInstallableDirective[]\n /**\n * Произвольные `provide`-значения. Ключи могут быть `symbol` или строкой.\n * Пакет не навязывает конкретные ключи (например, i18n) — выбор за пользователем.\n */\n provides?: Array<{ key: string | symbol; value: unknown }>\n /**\n * Произвольные `app.config.globalProperties` — по умолчанию ничего не ставим.\n */\n globalProperties?: Record<string, unknown>\n}\n\n/**\n * Вспомогательная обёртка: превращает произвольный компонент в `GranularityInstallableComponent`\n * с явным именем регистрации.\n *\n * ```ts\n * import { GrButton } from '@feugene/granularity/components/GrButton'\n * import { defineInstallable } from '@feugene/granularity/vue'\n *\n * const installable = defineInstallable(GrButton, 'GrButton')\n * ```\n */\nexport function defineInstallable<T extends Component>(component: T, name: string): GranularityInstallableComponent {\n return {\n install(app: App) {\n app.component(name, component)\n },\n }\n}\n\nfunction installComponent(app: App, c: GranularityInstallableComponent): void {\n if (typeof (c as any).install === 'function') {\n ;(c as any).install(app)\n return\n }\n\n // Форма `{ name, component }` — явное указание имени для произвольного компонента.\n // Допускаем как object-компоненты (SFC / options), так и functional-компоненты.\n if (\n typeof (c as any).name === 'string'\n && (c as any).component\n && (typeof (c as any).component === 'object' || typeof (c as any).component === 'function')\n ) {\n const {name, component} = c as { name: string; component: Component }\n app.component(name, component)\n return\n }\n\n // SFC: Vue 3 компилирует имя компонента из имени файла в поле `__name`\n // (например, `GrButton.vue` → `__name: 'GrButton'`). Также поддерживаем явное `name`.\n const resolvedName\n = (c as Component & { name?: string }).name\n ?? (c as { __name?: string }).__name\n if (resolvedName) {\n app.component(resolvedName, c as Component)\n return\n }\n\n // Сознательно без throw — просто сообщаем в dev-режиме.\n if (!__GR_DEV__) return\n\n console.warn('[granularity] component passed to createGranularity has no `install`, `name`, or `__name` — skipped.')\n}\n\nfunction installDirective(app: App, d: GranularityInstallableDirective): void {\n if ('install' in d && typeof d.install === 'function') {\n d.install(app)\n return\n }\n\n if ('name' in d && 'directive' in d) {\n app.directive(d.name, d.directive)\n return\n }\n\n // Сознательно без throw — согласовано с `installComponent`.\n if (!__GR_DEV__) return\n\n console.warn('[granularity] directive passed to createGranularity has no `install` or `{ name, directive }` — skipped.')\n}\n\n/**\n * Применяет опции к переданному приложению. Экспортируется отдельно от фабрики —\n * удобно, когда плагин не нужен (например, в тестах).\n */\nexport function installGranularity(app: App, options: CreateGranularityOptions = {}): void {\n for (const c of options.components ?? [])\n installComponent(app, c)\n\n for (const d of options.directives ?? [])\n installDirective(app, d)\n\n for (const {key, value} of options.provides ?? [])\n app.provide(key as any, value)\n\n if (options.globalProperties) {\n for (const [key, value] of Object.entries(options.globalProperties))\n app.config.globalProperties[key] = value\n }\n}\n\n/**\n * Создаёт Vue-плагин `{ install }`, который при `app.use(...)` зарегистрирует переданные\n * компоненты, директивы и provide-значения. Сам модуль НЕ импортирует ни компонентов,\n * ни директив из пакета — всё приходит снаружи. Это сохраняет гранулярность:\n * бандлер увидит только те символы, которые вы реально передали.\n *\n * ```ts\n * import { createApp } from 'vue'\n * import { createGranularity } from '@feugene/granularity/vue'\n * import { GrButton } from '@feugene/granularity/components/GrButton'\n * import { GrInput } from '@feugene/granularity/components/GrInput'\n * import { vHotkey } from '@feugene/granularity/directives/hotkey'\n *\n * createApp(App)\n * .use(createGranularity({\n * // Достаточно передать сами компоненты — имя будет взято из `__name` (Vue SFC)\n * // или из `name`; можно также указать явно через `{ name, component }`.\n * components: [GrButton, GrInput],\n * directives: [{ name: 'hotkey', directive: vHotkey }],\n * }))\n * .mount('#app')\n * ```\n */\nexport function createGranularity(options: CreateGranularityOptions = {}): Plugin {\n return {\n install(app: App) {\n installGranularity(app, options)\n },\n }\n}\n"],"mappings":";;;;;;;;;;;;AAsDA,SAAgB,kBAAuC,WAAc,MAA+C;CAChH,OAAO,EACH,QAAQ,KAAU;EACd,IAAI,UAAU,MAAM,SAAS;CACjC,EACJ;AACJ;AAEA,SAAS,iBAAiB,KAAU,GAA0C;CAC1E,IAAI,OAAQ,EAAU,YAAY,YAAY;EACzC,EAAW,QAAQ,GAAG;EACvB;CACJ;CAIA,IACI,OAAQ,EAAU,SAAS,YACvB,EAAU,cACV,OAAQ,EAAU,cAAc,YAAY,OAAQ,EAAU,cAAc,aAClF;EACE,MAAM,EAAC,MAAM,cAAa;EAC1B,IAAI,UAAU,MAAM,SAAS;EAC7B;CACJ;CAIA,MAAM,eACC,EAAoC,QAC/B,EAA0B;CACtC,IAAI,cAAc;EACd,IAAI,UAAU,cAAc,CAAc;EAC1C;CACJ;CAGA,IAAI,EAAA,OAAA,YAAA,eAAA,QAAA,IAAA,aAAA,eAAa;CAEjB,QAAQ,KAAK,sGAAsG;AACvH;AAEA,SAAS,iBAAiB,KAAU,GAA0C;CAC1E,IAAI,aAAa,KAAK,OAAO,EAAE,YAAY,YAAY;EACnD,EAAE,QAAQ,GAAG;EACb;CACJ;CAEA,IAAI,UAAU,KAAK,eAAe,GAAG;EACjC,IAAI,UAAU,EAAE,MAAM,EAAE,SAAS;EACjC;CACJ;CAGA,IAAI,EAAA,OAAA,YAAA,eAAA,QAAA,IAAA,aAAA,eAAa;CAEjB,QAAQ,KAAK,0GAA0G;AAC3H;;;;;AAMA,SAAgB,mBAAmB,KAAU,UAAoC,CAAC,GAAS;CACvF,KAAK,MAAM,KAAK,QAAQ,cAAc,CAAC,GACnC,iBAAiB,KAAK,CAAC;CAE3B,KAAK,MAAM,KAAK,QAAQ,cAAc,CAAC,GACnC,iBAAiB,KAAK,CAAC;CAE3B,KAAK,MAAM,EAAC,KAAK,WAAU,QAAQ,YAAY,CAAC,GAC5C,IAAI,QAAQ,KAAY,KAAK;CAEjC,IAAI,QAAQ,kBACR,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,QAAQ,gBAAgB,GAC9D,IAAI,OAAO,iBAAiB,OAAO;AAE/C;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,SAAgB,kBAAkB,UAAoC,CAAC,GAAW;CAC9E,OAAO,EACH,QAAQ,KAAU;EACd,mBAAmB,KAAK,OAAO;CACnC,EACJ;AACJ"}
@@ -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.27.1",
4
+ "version": "0.28.0",
5
5
  "framework": "vue",
6
6
  "js-types-syntax": "typescript",
7
7
  "description-markup": "markdown",
@@ -863,6 +863,15 @@
863
863
  "type": "boolean | undefined"
864
864
  }
865
865
  },
866
+ {
867
+ "name": "currentIndex",
868
+ "description": "Какой пункт — текущая страница. Не задан — последний. `-1` означает, что текущей страницы в пути нет: её имя стоит в `h1`, а путь показывает только родителей. Тогда `aria-current` не получает никто, и последний пункт остаётся обычной ссылкой.",
869
+ "default": "undefined",
870
+ "value": {
871
+ "kind": "expression",
872
+ "type": "number | undefined"
873
+ }
874
+ },
866
875
  {
867
876
  "name": "expandLabel",
868
877
  "description": "i18n-метка кнопки раскрытия схлопнутого пути.",
package/package.json CHANGED
@@ -1,7 +1,7 @@
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.27.1",
4
+ "version": "0.28.0",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": {
7
7
  "name": "Evgeniy Fureev",
@@ -523,7 +523,7 @@
523
523
  "devDependencies": {
524
524
  "@antfu/eslint-config": "^9.1.0",
525
525
  "@feugene/fint-i18n": "^0.6.0",
526
- "@feugene/granularity-test-kit": "^0.2.0",
526
+ "@feugene/granularity-test-kit": "^0.4.0",
527
527
  "@feugene/unocss-preset-granular": "^0.9.1",
528
528
  "@floating-ui/dom": "^1.8.0",
529
529
  "@iconify-json/lucide": "^1.2.117",
@@ -545,7 +545,7 @@
545
545
  "vue-tsc": "^3.3.7"
546
546
  },
547
547
  "scripts": {
548
- "build": "vite build && node scripts/check-dist-dev-guard.mjs && node scripts/check-dist-no-tests.mjs && node scripts/generate-web-types.mjs && vue-tsc -p tsconfig.build.json",
548
+ "build": "vite build && gr-check-dist-dev-guard && node scripts/check-dist-no-tests.mjs && node scripts/generate-web-types.mjs && vue-tsc -p tsconfig.build.json",
549
549
  "check:messages": "fint-i18n-check-messages src/i18n/locales",
550
550
  "dev": "vite build --watch",
551
551
  "doctor": "granular doctor ./granular.options.mjs --strict",
@@ -1 +0,0 @@
1
- {"version":3,"file":"config-DcfWZOi5.js","names":[],"sources":["../../src/components/GrBreadcrumbs/grBreadcrumbsStyles.ts","../../src/components/GrBreadcrumbs/safelist.ts","../../src/components/GrBreadcrumbs/config.ts"],"sourcesContent":["import type { Component } from 'vue'\n\nimport type { GrComponentSize } from '../shared/sizes'\n\nexport type GrBreadcrumbsSize = GrComponentSize\n\n/** Пункт пути. Кроме `label` всё опционально: последний пункт обычно без ссылки. */\nexport type GrBreadcrumbItem = {\n label: string\n href?: string\n /** Цель роутерной ссылки: уезжает в `GrLink` через `attrs` вместе с `as`. */\n to?: unknown\n /**\n * Иконка перед подписью — декоративна. Vue-компонент либо класс иконки вашей\n * UnoCSS-сборки (`i-lucide-*` требует вашего `presetIcons`).\n */\n icon?: string | Component\n disabled?: boolean\n /** Доступное имя пункта, когда подписи недостаточно («Настройки» → «Настройки проекта»). */\n ariaLabel?: string\n}\n\nexport const breadcrumbsRootClass = 'w-full min-w-0'\nexport const breadcrumbsListClass = 'flex items-center gap-1 m-0 p-0 [list-style:none]'\n\n/**\n * Перенос — умолчание: длинный путь уезжает на вторую строку и остаётся читаемым.\n * В режиме `autoCollapse` строка одна, иначе переполнения не случится никогда и\n * мерить будет нечего: список просто перенесётся.\n *\n * Исключение — раскрытый вручную путь: в одну строку он бы не влез (её нехватка и\n * вызвала схлопывание), `overflow: hidden` срезал бы хвост вместе с текущей\n * страницей, а фокус на раскрытом пункте молча уводил бы скролл контейнера.\n * Поэтому раскрытие возвращает перенос.\n */\nexport const breadcrumbsListWrapClass = 'flex-wrap'\nexport const breadcrumbsListNowrapClass = 'flex-nowrap overflow-hidden'\n\n/** Кегль строки пути. Шкала общая с `GrLink`: путь — это ряд ссылок. */\nexport const breadcrumbsSizeClassBySize: Record<GrBreadcrumbsSize, string> = {\n xs: 'text-[length:var(--gr-text-xs)] leading-[var(--gr-leading-xs)]',\n sm: 'text-[length:var(--gr-text-sm)] leading-[var(--gr-leading-sm)]',\n md: 'text-[length:var(--gr-text-sm)] leading-[var(--gr-leading-sm)]',\n lg: 'text-[length:var(--gr-text-base)] leading-[var(--gr-leading-base)]',\n}\n\nexport const breadcrumbsSeparatorClass = 'select-none text-[var(--gr-muted-fg)]'\n\n/** Текущая страница: не ссылка, но и не «выключенный» текст — обычный акцент подписи. */\nexport const breadcrumbsCurrentClass = 'inline-flex items-center gap-1 min-w-0 font-600 text-[var(--gr-fg)]'\n\nexport const breadcrumbsItemIconClass = 'inline-block h-4 w-4 shrink-0'\n\nexport const breadcrumbsLabelClass = 'truncate'\n\n/**\n * Кнопка «…»: раскрывает схлопнутую середину пути на месте. Оформлена как\n * приглушённая ссылка, чтобы не выбиваться из ряда.\n */\nexport const breadcrumbsEllipsisClass = 'inline-flex items-center rounded-[var(--gr-radius-sm)] px-1 text-[var(--gr-muted-fg)] hover:text-[var(--gr-fg)] focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--gr-ring)]'\n\nexport type GrBreadcrumbsLinkComponent = string | Component\n\n/**\n * Раскладка пути: какие пункты видны и где стоит кнопка раскрытия.\n *\n * Чистая функция, потому что вся её сложность — арифметика границ, и проверять\n * её монтированием компонента дороже, чем таблицей входов.\n */\nexport type GrBreadcrumbsLayoutEntry =\n | { kind: 'item', item: GrBreadcrumbItem, index: number }\n | { kind: 'ellipsis', hiddenCount: number }\n\n/**\n * Сколько пунктов хвоста влезает в доступную ширину.\n *\n * Порог по числу пунктов (`maxItems`) на узком экране предсказывает плохо: три\n * коротких пункта влезают, два длинных — нет. Здесь решение принимается по\n * измеренным ширинам.\n *\n * Голова не ужимается: `itemsBeforeCollapse` — обычно один корневой пункт, и он\n * дёшев. Хвост ужимается до упора, но **не меньше одного**: последний пункт\n * отвечает на вопрос «где я сейчас», и прятать его бессмысленно — путь\n * превратился бы в «Главная / …».\n *\n * Возврат — число пунктов хвоста; `items.length` означает «влезло всё».\n */\nexport function resolveBreadcrumbsFit(options: {\n itemWidths: number[]\n separatorWidth: number\n ellipsisWidth: number\n available: number\n itemsBeforeCollapse: number\n /** Зазор списка (`gap`). Стоит между каждой парой соседей, включая разделители. */\n gapWidth?: number\n}): number {\n const { itemWidths, separatorWidth, ellipsisWidth, available, itemsBeforeCollapse, gapWidth = 0 } = options\n const total = itemWidths.length\n\n if (total === 0) return 0\n\n const sum = (from: number, to: number): number =>\n itemWidths.slice(from, to).reduce((acc, width) => acc + width, 0)\n\n /**\n * Ширина строки из `parts` видимых блоков: сами блоки, разделители между ними\n * и зазоры. Элементов в списке `2 * parts - 1` (блоки и разделители вперемешку),\n * значит зазоров на один меньше — без них расчёт систематически оптимистичен и\n * путь подрезается уже после схлопывания.\n */\n const rowWidth = (content: number, parts: number): number =>\n content\n + Math.max(0, parts - 1) * separatorWidth\n + Math.max(0, 2 * parts - 2) * gapWidth\n\n if (rowWidth(sum(0, total), total) <= available) return total\n\n const before = Math.max(0, Math.min(itemsBeforeCollapse, total))\n\n for (let after = total - before - 1; after > 1; after--) {\n const content = sum(0, before) + ellipsisWidth + sum(total - after, total)\n\n if (rowWidth(content, before + 1 + after) <= available) return after\n }\n\n return 1\n}\n\nexport function resolveBreadcrumbsLayout(options: {\n items: GrBreadcrumbItem[]\n maxItems?: number\n itemsBeforeCollapse: number\n itemsAfterCollapse: number\n expanded: boolean\n}): GrBreadcrumbsLayoutEntry[] {\n const { items, maxItems, itemsBeforeCollapse, itemsAfterCollapse, expanded } = options\n const all: GrBreadcrumbsLayoutEntry[] = items.map((item, index) => ({ kind: 'item', item, index }))\n\n if (expanded || !maxItems || maxItems < 1 || items.length <= maxItems)\n return all\n\n // Хвост важнее головы: «где я сейчас» читается справа, поэтому при нехватке\n // места сначала жертвуем серединой, а не концом.\n const before = Math.max(0, Math.min(itemsBeforeCollapse, items.length))\n const after = Math.max(0, Math.min(itemsAfterCollapse, items.length - before))\n const hiddenCount = items.length - before - after\n\n if (hiddenCount <= 1)\n return all\n\n return [\n ...all.slice(0, before),\n { kind: 'ellipsis', hiddenCount },\n ...all.slice(items.length - after),\n ]\n}\n","import { splitClassTokens } from '../shared/classTokens'\nimport {\n breadcrumbsCurrentClass,\n breadcrumbsEllipsisClass,\n breadcrumbsItemIconClass,\n breadcrumbsLabelClass,\n breadcrumbsListClass,\n breadcrumbsListNowrapClass,\n breadcrumbsListWrapClass,\n breadcrumbsRootClass,\n breadcrumbsSeparatorClass,\n breadcrumbsSizeClassBySize,\n} from './grBreadcrumbsStyles'\n\n// Всё, что живёт в `grBreadcrumbsStyles.ts`: и мапа размеров, и строковые\n// литералы. Хелпер уезжает в общий `dist/chunks/`, вне области скана\n// компонента — гейт `src/__tests__/safelist.test.ts`.\nexport const grBreadcrumbsSafelist = [...new Set([\n ...Object.values(breadcrumbsSizeClassBySize).flatMap(splitClassTokens),\n ...splitClassTokens(breadcrumbsRootClass),\n ...splitClassTokens(breadcrumbsListClass),\n ...splitClassTokens(breadcrumbsListWrapClass),\n ...splitClassTokens(breadcrumbsListNowrapClass),\n ...splitClassTokens(breadcrumbsSeparatorClass),\n ...splitClassTokens(breadcrumbsCurrentClass),\n ...splitClassTokens(breadcrumbsItemIconClass),\n ...splitClassTokens(breadcrumbsLabelClass),\n ...splitClassTokens(breadcrumbsEllipsisClass),\n])]\n","import { defineGranularComponent } from '@feugene/unocss-preset-granular/contract'\n\nimport { grBreadcrumbsSafelist } from './safelist'\n\nexport const grBreadcrumbsConfig = defineGranularComponent(import.meta.url, {\n name: 'GrBreadcrumbs',\n safelist: grBreadcrumbsSafelist,\n dependencies: ['GrLink'],\n})\n"],"mappings":";;;AAsBA,IAAa,uBAAuB;AACpC,IAAa,uBAAuB;;;;;;;;;;;AAYpC,IAAa,2BAA2B;AACxC,IAAa,6BAA6B;;AAG1C,IAAa,6BAAgE;CAC3E,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;AACN;AAEA,IAAa,4BAA4B;;AAGzC,IAAa,0BAA0B;AAEvC,IAAa,2BAA2B;AAExC,IAAa,wBAAwB;;;;;AAMrC,IAAa,2BAA2B;;;;;;;;;;;;;;;AA4BxC,SAAgB,sBAAsB,SAQ3B;CACT,MAAM,EAAE,YAAY,gBAAgB,eAAe,WAAW,qBAAqB,WAAW,MAAM;CACpG,MAAM,QAAQ,WAAW;CAEzB,IAAI,UAAU,GAAG,OAAO;CAExB,MAAM,OAAO,MAAc,OACzB,WAAW,MAAM,MAAM,EAAE,CAAC,CAAC,QAAQ,KAAK,UAAU,MAAM,OAAO,CAAC;;;;;;;CAQlE,MAAM,YAAY,SAAiB,UACjC,UACE,KAAK,IAAI,GAAG,QAAQ,CAAC,IAAI,iBACzB,KAAK,IAAI,GAAG,IAAI,QAAQ,CAAC,IAAI;CAEjC,IAAI,SAAS,IAAI,GAAG,KAAK,GAAG,KAAK,KAAK,WAAW,OAAO;CAExD,MAAM,SAAS,KAAK,IAAI,GAAG,KAAK,IAAI,qBAAqB,KAAK,CAAC;CAE/D,KAAK,IAAI,QAAQ,QAAQ,SAAS,GAAG,QAAQ,GAAG,SAG9C,IAAI,SAFY,IAAI,GAAG,MAAM,IAAI,gBAAgB,IAAI,QAAQ,OAAO,KAAK,GAEnD,SAAS,IAAI,KAAK,KAAK,WAAW,OAAO;CAGjE,OAAO;AACT;AAEA,SAAgB,yBAAyB,SAMV;CAC7B,MAAM,EAAE,OAAO,UAAU,qBAAqB,oBAAoB,aAAa;CAC/E,MAAM,MAAkC,MAAM,KAAK,MAAM,WAAW;EAAE,MAAM;EAAQ;EAAM;CAAM,EAAE;CAElG,IAAI,YAAY,CAAC,YAAY,WAAW,KAAK,MAAM,UAAU,UAC3D,OAAO;CAIT,MAAM,SAAS,KAAK,IAAI,GAAG,KAAK,IAAI,qBAAqB,MAAM,MAAM,CAAC;CACtE,MAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,IAAI,oBAAoB,MAAM,SAAS,MAAM,CAAC;CAC7E,MAAM,cAAc,MAAM,SAAS,SAAS;CAE5C,IAAI,eAAe,GACjB,OAAO;CAET,OAAO;EACL,GAAG,IAAI,MAAM,GAAG,MAAM;EACtB;GAAE,MAAM;GAAY;EAAY;EAChC,GAAG,IAAI,MAAM,MAAM,SAAS,KAAK;CACnC;AACF;;;AC1IA,IAAa,wBAAwB,CAAC,mBAAG,IAAI,IAAI;CAC/C,GAAG,OAAO,OAAO,0BAA0B,CAAC,CAAC,QAAQ,gBAAgB;CACrE,GAAG,iBAAiB,oBAAoB;CACxC,GAAG,iBAAiB,oBAAoB;CACxC,GAAG,iBAAiB,wBAAwB;CAC5C,GAAG,iBAAiB,0BAA0B;CAC9C,GAAG,iBAAiB,yBAAyB;CAC7C,GAAG,iBAAiB,uBAAuB;CAC3C,GAAG,iBAAiB,wBAAwB;CAC5C,GAAG,iBAAiB,qBAAqB;CACzC,GAAG,iBAAiB,wBAAwB;AAC9C,CAAC,CAAC;;;ACxBF,IAAa,sBAAsB,wBAAwB,OAAO,KAAK,KAAK;CAC1E,MAAM;CACN,UAAU;CACV,cAAc,CAAC,QAAQ;AACzB,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"GrBreadcrumbs-jtSnMEtg.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 breadcrumbsLabelClass,\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 /** 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 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) return undefined\n if (fitAfter.value >= props.items.length) 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\nfunction isCurrent(index: number): boolean {\n return index === lastIndex.value\n}\n\n/** Ссылкой пункт становится, только если ведёт куда-то и не выключен. */\nfunction isLink(item: GrBreadcrumbItem, index: number): boolean {\n if (item.disabled) return false\n if (isCurrent(index) && !props.linkCurrent) return false\n return item.href !== undefined || item.to !== undefined || props.as !== 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) return\n\n const list = listEl.value\n if (!list) return\n\n const ellipsis = list.querySelector('[data-gr-breadcrumbs-ellipsis-item]')\n if (ellipsis) ellipsisWidth = widthOf(ellipsis)\n\n if (!measured) {\n // Ширины снимаем только с полного пути: спрятанных пунктов в DOM нет.\n if (fitAfter.value !== undefined) return\n\n const wraps = [...list.querySelectorAll('[data-gr-breadcrumbs-item-wrap]')]\n if (wraps.length !== props.items.length) 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) 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') 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') 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) 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=\"min-w-0\">\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=\"breadcrumbsLabelClass\">{{ 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=\"breadcrumbsLabelClass\">{{ 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 breadcrumbsLabelClass,\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 /** 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 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) return undefined\n if (fitAfter.value >= props.items.length) 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\nfunction isCurrent(index: number): boolean {\n return index === lastIndex.value\n}\n\n/** Ссылкой пункт становится, только если ведёт куда-то и не выключен. */\nfunction isLink(item: GrBreadcrumbItem, index: number): boolean {\n if (item.disabled) return false\n if (isCurrent(index) && !props.linkCurrent) return false\n return item.href !== undefined || item.to !== undefined || props.as !== 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) return\n\n const list = listEl.value\n if (!list) return\n\n const ellipsis = list.querySelector('[data-gr-breadcrumbs-ellipsis-item]')\n if (ellipsis) ellipsisWidth = widthOf(ellipsis)\n\n if (!measured) {\n // Ширины снимаем только с полного пути: спрятанных пунктов в DOM нет.\n if (fitAfter.value !== undefined) return\n\n const wraps = [...list.querySelectorAll('[data-gr-breadcrumbs-item-wrap]')]\n if (wraps.length !== props.items.length) 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) 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') 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') 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) 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=\"min-w-0\">\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=\"breadcrumbsLabelClass\">{{ 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=\"breadcrumbsLabelClass\">{{ entry.item.label }}</span>\n </slot>\n </span>\n </li>\n </template>\n </ol>\n </nav>\n</template>\n"],"mappings":"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"}