@feugene/granularity 0.34.1 → 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/dist/chunks/{config-_QF0O1vq.js → config-CMNfZO7S.js} +2 -2
  2. package/dist/chunks/config-CMNfZO7S.js.map +1 -0
  3. package/dist/chunks/{generated-Bk-f6NsN.js → generated-BcfXQY4j.js} +16 -2
  4. package/dist/chunks/{generated-Bk-f6NsN.js.map → generated-BcfXQY4j.js.map} +1 -1
  5. package/dist/chunks/{granular-provider-BAy4APOX.js → granular-provider-DCn6fgEs.js} +1 -1
  6. package/dist/chunks/{granular-provider-BAy4APOX.js.map → granular-provider-DCn6fgEs.js.map} +1 -1
  7. package/dist/components/GrNavbar/chunks/{GrNavbar-_rtHKwTf.js → GrNavbar-Dr8e5kW-.js} +14 -4
  8. package/dist/components/GrNavbar/chunks/GrNavbar-Dr8e5kW-.js.map +1 -0
  9. package/dist/components/GrNavbar/index.js +1 -1
  10. package/dist/components/GrTabs/chunks/{GrTabs-UsO4j8zv.js → GrTabs-CT2nk_HV.js} +79 -4
  11. package/dist/components/GrTabs/chunks/GrTabs-CT2nk_HV.js.map +1 -0
  12. package/dist/components/GrTabs/index.js +2 -2
  13. package/dist/components/GrTabs/styles.css +1 -0
  14. package/dist/components/GrTimeline/chunks/GrTimeline-BiHpArsg.js.map +1 -1
  15. package/dist/components/GrTimeline/styles.css +1 -1
  16. package/dist/granular-provider-node.js +1 -1
  17. package/dist/granular-provider.js +1 -1
  18. package/dist/index.js +3 -3
  19. package/dist/theme.js +1 -1
  20. package/dist/tokens.js +1 -1
  21. package/dist/types/components/GrNavbar/GrNavbar.vue.d.ts +14 -0
  22. package/dist/types/components/GrNavbar/defaults.d.ts +10 -0
  23. package/dist/types/components/GrNavbar/index.d.ts +1 -0
  24. package/dist/types/components/shared/scrollOverflow.d.ts +20 -0
  25. package/dist/web-types.json +10 -1
  26. package/package.json +1 -1
  27. package/dist/chunks/config-_QF0O1vq.js.map +0 -1
  28. package/dist/components/GrNavbar/chunks/GrNavbar-_rtHKwTf.js.map +0 -1
  29. package/dist/components/GrTabs/chunks/GrTabs-UsO4j8zv.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"GrTimeline-BiHpArsg.js","names":["$slots"],"sources":["../../../../src/components/GrTimeline/grTimelineContext.ts","../../../../src/components/GrTimeline/timelineGroups.ts","../../../../src/components/GrTimeline/GrTimeline.vue","../../../../src/components/GrTimeline/GrTimeline.vue","../../../../src/components/GrTimeline/GrTimelineItem.vue","../../../../src/components/GrTimeline/GrTimelineItem.vue"],"sourcesContent":["import type { ComputedRef, InjectionKey } from 'vue'\n\nimport type { GrTimelineDensity, GrTimelineLayout, GrTimelineOrientation } from './grTimelineStyles'\n\n/**\n * Уровень заголовка группы. Тот же довод, что у `GrCollapse`: лента в разделе\n * `<h4>` с захардкоженным `<h3>` рвёт навигацию по заголовкам.\n */\nexport const GR_TIMELINE_HEADING_LEVELS = [2, 3, 4, 5, 6] as const\n\nexport type GrTimelineHeadingLevel = typeof GR_TIMELINE_HEADING_LEVELS[number]\n\n/**\n * Пункт читает раскладку из контейнера, а не получает её пропом: иначе\n * `layout` пришлось бы дублировать на каждом `GrTimelineItem`.\n */\nexport interface GrTimelineContext {\n layout: ComputedRef<GrTimelineLayout>\n orientation: ComputedRef<GrTimelineOrientation>\n density: ComputedRef<GrTimelineDensity>\n}\n\nexport const GR_TIMELINE_CONTEXT: InjectionKey<GrTimelineContext> = Symbol('GR_TIMELINE_CONTEXT')\n","/**\n * Группировка ленты событий. Без Vue — чтобы проверялась без монтирования.\n */\n\nexport type GrTimelineGroupBy<T> = string | ((item: T, index: number) => string)\n\n/** Пункт вместе с его позицией в исходном наборе: ключи `v-for` считаются от неё. */\nexport interface GrTimelineEntry<T> {\n item: T\n index: number\n}\n\nexport interface GrTimelineGroup<T> {\n /** Ключ группы. Пустая строка — заголовка у группы нет. */\n key: string\n entries: GrTimelineEntry<T>[]\n}\n\nfunction keyOf<T>(item: T, index: number, groupBy: GrTimelineGroupBy<T>): string {\n if (typeof groupBy === 'function')\n return String(groupBy(item, index) ?? '')\n\n const value = (item as Record<string, unknown> | null | undefined)?.[groupBy]\n\n // `undefined` в поле — это «группы нет», а не группа с надписью «undefined».\n return value == null ? '' : String(value)\n}\n\n/**\n * Режет набор на группы, **не меняя порядок**: лента хронологична, и\n * пересортировать её компонент не вправе. Порядок групп — порядок первого\n * вхождения ключа; разорванная группа (`A B A`) собирается в одну.\n *\n * Без `groupBy` — одна безымянная группа: плоская лента остаётся частным\n * случаем, и второй ветки рендера у компонента не появляется.\n */\nexport function groupTimelineItems<T>(\n items: readonly T[],\n groupBy?: GrTimelineGroupBy<T>,\n): GrTimelineGroup<T>[] {\n const entries = items.map((item, index) => ({ item, index }))\n\n if (groupBy === undefined)\n return entries.length === 0 ? [] : [{ key: '', entries }]\n\n const byKey = new Map<string, GrTimelineGroup<T>>()\n\n for (const entry of entries) {\n const key = keyOf(entry.item, entry.index, groupBy)\n const group = byKey.get(key)\n\n if (group)\n group.entries.push(entry)\n else\n byKey.set(key, { key, entries: [entry] })\n }\n\n return [...byKey.values()]\n}\n","<script setup lang=\"ts\" generic=\"T = unknown\">\nimport { computed, provide, useSlots, watch } from 'vue'\n\nimport GrSkeleton from '../GrSkeleton/GrSkeleton.vue'\nimport { useGrComponentProp } from '../GrConfigProvider/context'\nimport { hasMeaningfulSlotContent } from '../shared/slotNodes'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\n\nimport { GR_TIMELINE_CONTEXT, type GrTimelineHeadingLevel } from './grTimelineContext'\nimport {\n emptyClass,\n groupTitleClass,\n loadingRowClass,\n type GrTimelineDensity,\n type GrTimelineLayout,\n type GrTimelineOrientation,\n} from './grTimelineStyles'\nimport { groupTimelineItems, type GrTimelineGroupBy } from './timelineGroups'\n\nexport type {\n GrTimelineDensity,\n GrTimelineLayout,\n GrTimelineOrientation,\n} from './grTimelineStyles'\nexport type { GrTimelineGroupBy } from './timelineGroups'\n\nexport interface GrTimelineProps<TItem = unknown> {\n /**\n * Данные ленты. С ними пункты рисует слот `#item` — и только так лента знает\n * состав набора, то есть может его группировать.\n */\n items?: TItem[]\n /** Ключ пункта для `v-for`: имя поля или функция. Без него — индекс. */\n itemKey?: string | ((item: TItem, index: number) => string | number)\n /** Группировка: имя поля или функция от пункта. Заголовок группы — слот `#group`. */\n groupBy?: GrTimelineGroupBy<TItem>\n /** Уровень заголовка группы: лента обязана вписаться в структуру заголовков страницы. */\n groupHeadingLevel?: GrTimelineHeadingLevel\n /** Раскладка: одна ось, колонка времени слева или чередование сторон. */\n layout?: GrTimelineLayout\n /** Направление оси. Горизонтальная лента — скроллер. */\n orientation?: GrTimelineOrientation\n /** Плотность вертикальных отступов пункта. */\n density?: GrTimelineDensity\n /** Идёт загрузка: вместо пунктов — заглушки, контейнер помечен `aria-busy`. */\n loading?: boolean\n /** Сколько строк-заглушек показать при `loading`. */\n loadingRows?: number\n /**\n * Лента пуста. По умолчанию определяется сама — по данным или по слоту; проп\n * нужен там, где потребитель знает лучше.\n */\n empty?: boolean\n /** Текст пустого состояния. Слот `#empty` сильнее. */\n emptyText?: string\n}\n\nconst props = withDefaults(defineProps<GrTimelineProps<T>>(), {\n items: undefined,\n itemKey: undefined,\n groupBy: undefined,\n // Настоящие дефолты живут в резолверах `useGrComponentProp`: объявленный здесь\n // дефолт неотличим от значения, переданного потребителем, и перебивал бы конфиг.\n groupHeadingLevel: undefined,\n layout: undefined,\n orientation: undefined,\n density: undefined,\n loading: false,\n loadingRows: 3,\n empty: undefined,\n emptyText: undefined,\n})\n\ndefineSlots<{\n /** Пункты ленты (`GrTimelineItem`), когда данные не переданы пропом. */\n default?: () => any\n /** Пункт в data-режиме. */\n item?: (props: { item: T, index: number }) => any\n /** Заголовок группы. По умолчанию — сам ключ. */\n group?: (props: { group: string, items: T[] }) => any\n /** Содержимое пустого состояния вместо текста по умолчанию. */\n empty?: () => any\n /** Заглушки загрузки вместо строк по умолчанию. */\n loading?: () => any\n}>()\n\nconst slots = useSlots()\nconst { t } = useGranularityTranslations()\n\nconst density = useGrComponentProp('GrTimeline', 'density', () => props.density, 'regular')\nconst orientation = useGrComponentProp('GrTimeline', 'orientation', () => props.orientation, 'vertical')\nconst headingLevel = useGrComponentProp('GrTimeline', 'groupHeadingLevel', () => props.groupHeadingLevel, 3)\nconst requestedLayout = useGrComponentProp('GrTimeline', 'layout', () => props.layout, 'stacked')\n\n/**\n * Горизонтальная лента знает одну раскладку: колонка времени и чередование\n * сторон — это вертикальные приёмы, у горизонтальной оси сторон нет.\n */\nconst layout = computed<GrTimelineLayout>(() => (\n orientation.value === 'horizontal' ? 'stacked' : requestedLayout.value\n))\n\nconst headingTag = computed(() => `h${headingLevel.value}`)\n\nconst dataMode = computed(() => props.items !== undefined)\n\nconst grouped = computed(() => dataMode.value && props.groupBy !== undefined)\n\nconst groups = computed(() => groupTimelineItems(props.items ?? [], props.groupBy))\n\n/** Единственная группа плоской ленты: тот же набор, что и в data-режиме без группировки. */\nconst entries = computed(() => groups.value[0]?.entries ?? [])\n\nconst resolvedEmptyText = computed(() => props.emptyText ?? t('gr.timeline.empty', 'Nothing here yet'))\n\nconst isEmpty = computed(() => {\n if (props.empty !== undefined)\n return props.empty\n\n if (dataMode.value)\n return props.items!.length === 0\n\n // `v-for` по пустому массиву оставляет фрагмент без узлов, `v-if` — комментарий,\n // и ни то ни другое пунктом ленты не является.\n return !hasMeaningfulSlotContent(slots.default?.() ?? [])\n})\n\nconst loadingRowCount = computed(() => Math.max(1, Math.trunc(props.loadingRows)))\n\nfunction keyOf(item: T, index: number): string | number {\n const key = props.itemKey\n if (typeof key === 'function')\n return key(item, index)\n if (typeof key === 'string')\n return (item as Record<string, unknown>)[key] as string | number\n\n return index\n}\n\nfunction itemsOf(group: { entries: { item: T }[] }): T[] {\n return group.entries.map(entry => entry.item)\n}\n\nprovide(GR_TIMELINE_CONTEXT, { layout, orientation, density })\n\nif (__GR_DEV__) {\n watch(\n () => [dataMode.value, Boolean(slots.item)] as const,\n ([hasItems, hasSlot]) => {\n if (hasItems && !hasSlot) {\n console.warn(\n '[granularity] GrTimeline: с `items` пункты рисует слот `#item` — без него лента пуста.',\n )\n }\n },\n { immediate: true },\n )\n\n watch(\n () => [dataMode.value, props.groupBy !== undefined] as const,\n ([hasItems, hasGroupBy]) => {\n if (hasGroupBy && !hasItems) {\n console.warn(\n '[granularity] GrTimeline: `groupBy` работает только с `items` — пункты из слота по '\n + 'умолчанию лента не разбирает и сгруппировать их ей не из чего.',\n )\n }\n },\n { immediate: true },\n )\n\n watch(\n () => [orientation.value, requestedLayout.value] as const,\n ([axis, requested]) => {\n if (axis === 'horizontal' && requested !== 'stacked') {\n console.warn(\n `[granularity] GrTimeline: layout=\"${requested}\" — вертикальная раскладка, `\n + 'в горизонтальной ленте она игнорируется.',\n )\n }\n },\n { immediate: true },\n )\n}\n</script>\n\n<template>\n <div\n data-gr-timeline\n :data-layout=\"layout\"\n :data-orientation=\"orientation\"\n :aria-busy=\"loading ? 'true' : undefined\"\n >\n <template v-if=\"loading\">\n <slot name=\"loading\">\n <div\n v-for=\"row in loadingRowCount\"\n :key=\"row\"\n data-gr-timeline-loading-row\n :class=\"loadingRowClass\"\n >\n <GrSkeleton variant=\"circle\" width=\"var(--gr-timeline-marker-size, 10px)\" />\n <GrSkeleton />\n </div>\n </slot>\n </template>\n\n <div\n v-else-if=\"isEmpty\"\n data-gr-timeline-empty\n :class=\"emptyClass\"\n >\n <slot name=\"empty\">\n {{ resolvedEmptyText }}\n </slot>\n </div>\n\n <template v-else-if=\"grouped\">\n <!--\n Группа — секция с заголовком, а не пункт-заголовок внутри списка:\n `<li>` с датой попал бы в набор событий и был бы объявлен как одно из них.\n -->\n <section\n v-for=\"group in groups\"\n :key=\"group.key\"\n data-gr-timeline-group\n >\n <div\n v-if=\"group.key\"\n data-gr-timeline-group-header\n >\n <div data-gr-timeline-rail aria-hidden=\"true\">\n <span data-gr-timeline-line />\n </div>\n\n <component\n :is=\"headingTag\"\n data-gr-timeline-group-title\n :class=\"groupTitleClass\"\n >\n <slot name=\"group\" :group=\"group.key\" :items=\"itemsOf(group)\">\n {{ group.key }}\n </slot>\n </component>\n </div>\n\n <ol\n data-gr-timeline-list\n :tabindex=\"orientation === 'horizontal' ? 0 : undefined\"\n >\n <template\n v-for=\"entry in group.entries\"\n :key=\"keyOf(entry.item, entry.index)\"\n >\n <slot name=\"item\" :item=\"entry.item\" :index=\"entry.index\" />\n </template>\n </ol>\n </section>\n </template>\n\n <ol\n v-else\n data-gr-timeline-list\n :tabindex=\"orientation === 'horizontal' ? 0 : undefined\"\n >\n <template v-if=\"dataMode\">\n <template\n v-for=\"entry in entries\"\n :key=\"keyOf(entry.item, entry.index)\"\n >\n <slot name=\"item\" :item=\"entry.item\" :index=\"entry.index\" />\n </template>\n </template>\n\n <slot v-else />\n </ol>\n </div>\n</template>\n\n<style>\n/*\n * Геометрия ленты живёт здесь, а не в утилитах, по одной причине: сторона\n * пункта в `alternate` — это `nth-child`, а обрыв оси — `last-child`, и оба\n * вопроса утилитами не выражаются. Альтернатива — регистрация пунктов в\n * контексте на `onMounted` — дала бы порядок, зависящий от порядка монтирования,\n * и разъезжалась бы на `v-if` посреди ленты.\n */\n\n[data-gr-timeline] {\n --gr-timeline-columns: auto 1fr;\n}\n\n[data-gr-timeline][data-layout='time'] {\n --gr-timeline-columns: var(--gr-timeline-time-width, 5.5rem) auto 1fr;\n}\n\n[data-gr-timeline][data-layout='alternate'] {\n --gr-timeline-columns: 1fr auto 1fr;\n}\n\n[data-gr-timeline-list] {\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n[data-gr-timeline-item],\n[data-gr-timeline-group-header] {\n display: grid;\n grid-template-columns: var(--gr-timeline-columns);\n column-gap: var(--gr-timeline-gap, 0.75rem);\n align-items: start;\n}\n\n[data-gr-timeline-aside] {\n text-align: end;\n}\n\n/*\n * Ось — отрезок у каждого пункта, а не одна длинная линия: соседние отрезки\n * смыкаются сами, и мерить высоту списка не приходится.\n */\n[data-gr-timeline-rail] {\n display: flex;\n flex-direction: column;\n align-items: center;\n align-self: stretch;\n padding-top: var(--gr-timeline-marker-top, 0.35rem);\n min-width: var(--gr-timeline-marker-size, 10px);\n}\n\n[data-gr-timeline-rail] > :not([data-gr-timeline-line]) {\n flex: none;\n}\n\n[data-gr-timeline-marker] {\n box-sizing: border-box;\n width: var(--gr-timeline-marker-size, 10px);\n height: var(--gr-timeline-marker-size, 10px);\n border-width: var(--gr-timeline-marker-ring, 2px);\n border-style: solid;\n border-color: transparent;\n}\n\n[data-gr-timeline-line] {\n flex: 1 1 auto;\n width: var(--gr-timeline-axis-width, 2px);\n min-height: var(--gr-timeline-line-min, 0.75rem);\n margin-top: 0.25rem;\n /* Ровно на столько следующая точка отступает от верха своей строки: без этого\n отрезок не доходит до неё, и ось выглядит пунктиром из длинных штрихов. */\n margin-bottom: calc(-1 * var(--gr-timeline-marker-top, 0.35rem));\n background-color: var(--gr-timeline-axis-color, var(--gr-brd));\n}\n\n/*\n * Пунктиром идёт участок, ведущий **к** незавершённому событию, и всё, что за\n * ним: непройденный путь. Отрезок после точки для этого не годится — обычно\n * незавершённое событие последнее, и его отрезка не видно вовсе.\n */\n[data-gr-timeline-item]:has(+ [data-gr-timeline-item][data-pending]) [data-gr-timeline-line],\n[data-gr-timeline-item][data-pending] [data-gr-timeline-line] {\n background-color: transparent;\n background-image: repeating-linear-gradient(\n to bottom,\n var(--gr-timeline-axis-color, var(--gr-brd)) 0 var(--gr-timeline-dash, 4px),\n transparent var(--gr-timeline-dash, 4px) calc(var(--gr-timeline-dash, 4px) * 2)\n );\n}\n\n/* Хвост после последнего события — линия в никуда. */\n[data-gr-timeline-item]:last-child [data-gr-timeline-line] {\n visibility: hidden;\n}\n\n/* Между группами ось не рвётся: разрыв читался бы как две разные ленты. */\n[data-gr-timeline-group]:not(:last-of-type) [data-gr-timeline-item]:last-child [data-gr-timeline-line] {\n visibility: visible;\n}\n\n[data-gr-timeline-group-header] [data-gr-timeline-rail] {\n padding-top: 0;\n}\n\n[data-gr-timeline-group] + [data-gr-timeline-group] [data-gr-timeline-group-title] {\n padding-top: 0.5rem;\n}\n\n[data-gr-timeline-group-header] [data-gr-timeline-line] {\n margin-top: 0;\n}\n\n/* Над первым заголовком оси ещё нет — линия висела бы в воздухе. */\n[data-gr-timeline-group]:first-of-type [data-gr-timeline-group-header] [data-gr-timeline-line] {\n visibility: hidden;\n}\n\n[data-gr-timeline][data-layout='time'] [data-gr-timeline-group-header] [data-gr-timeline-rail],\n[data-gr-timeline][data-layout='alternate'] [data-gr-timeline-group-header] [data-gr-timeline-rail] {\n grid-column: 2;\n}\n\n[data-gr-timeline][data-layout='time'] [data-gr-timeline-group-title],\n[data-gr-timeline][data-layout='alternate'] [data-gr-timeline-group-title] {\n grid-column: 3;\n}\n\n[data-gr-timeline][data-layout='alternate'] [data-gr-timeline-rail] {\n grid-column: 2;\n}\n\n[data-gr-timeline][data-layout='alternate'] [data-gr-timeline-content] {\n grid-column: 3;\n}\n\n[data-gr-timeline][data-layout='alternate'] [data-gr-timeline-item]:nth-child(even) [data-gr-timeline-content] {\n grid-column: 1;\n grid-row: 1;\n text-align: end;\n}\n\n/* Двухсторонняя лента в узкой колонке нечитаема — там она односторонняя. */\n@media (max-width: 640px) {\n [data-gr-timeline][data-layout='alternate'] {\n --gr-timeline-columns: auto 1fr;\n }\n\n [data-gr-timeline][data-layout='alternate'] [data-gr-timeline-rail],\n [data-gr-timeline][data-layout='alternate'] [data-gr-timeline-group-header] [data-gr-timeline-rail] {\n grid-column: 1;\n }\n\n [data-gr-timeline][data-layout='alternate'] [data-gr-timeline-content],\n [data-gr-timeline][data-layout='alternate'] [data-gr-timeline-item]:nth-child(even) [data-gr-timeline-content],\n [data-gr-timeline][data-layout='alternate'] [data-gr-timeline-group-title] {\n grid-column: 2;\n text-align: start;\n }\n}\n\n[data-gr-timeline][data-orientation='horizontal'] [data-gr-timeline-list] {\n display: flex;\n flex-direction: row;\n overflow-x: auto;\n}\n\n[data-gr-timeline][data-orientation='horizontal'] [data-gr-timeline-item] {\n grid-template-columns: 1fr;\n min-width: var(--gr-timeline-item-min-width, 12rem);\n padding-inline-end: var(--gr-timeline-gap, 0.75rem);\n}\n\n[data-gr-timeline][data-orientation='horizontal'] [data-gr-timeline-content] {\n padding-bottom: 0;\n}\n\n[data-gr-timeline][data-orientation='horizontal'] [data-gr-timeline-rail] {\n flex-direction: row;\n align-items: center;\n align-self: auto;\n padding-top: 0;\n padding-bottom: 0.25rem;\n}\n\n[data-gr-timeline][data-orientation='horizontal'] [data-gr-timeline-line] {\n width: auto;\n height: var(--gr-timeline-axis-width, 2px);\n min-width: var(--gr-timeline-line-min, 0.75rem);\n min-height: 0;\n margin-top: 0;\n margin-inline-start: 0.25rem;\n}\n\n[data-gr-timeline][data-orientation='horizontal'] [data-gr-timeline-item]:has(+ [data-gr-timeline-item][data-pending]) [data-gr-timeline-line],\n[data-gr-timeline][data-orientation='horizontal'] [data-gr-timeline-item][data-pending] [data-gr-timeline-line] {\n background-image: repeating-linear-gradient(\n to right,\n var(--gr-timeline-axis-color, var(--gr-brd)) 0 var(--gr-timeline-dash, 4px),\n transparent var(--gr-timeline-dash, 4px) calc(var(--gr-timeline-dash, 4px) * 2)\n );\n}\n</style>\n","<script setup lang=\"ts\" generic=\"T = unknown\">\nimport { computed, provide, useSlots, watch } from 'vue'\n\nimport GrSkeleton from '../GrSkeleton/GrSkeleton.vue'\nimport { useGrComponentProp } from '../GrConfigProvider/context'\nimport { hasMeaningfulSlotContent } from '../shared/slotNodes'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\n\nimport { GR_TIMELINE_CONTEXT, type GrTimelineHeadingLevel } from './grTimelineContext'\nimport {\n emptyClass,\n groupTitleClass,\n loadingRowClass,\n type GrTimelineDensity,\n type GrTimelineLayout,\n type GrTimelineOrientation,\n} from './grTimelineStyles'\nimport { groupTimelineItems, type GrTimelineGroupBy } from './timelineGroups'\n\nexport type {\n GrTimelineDensity,\n GrTimelineLayout,\n GrTimelineOrientation,\n} from './grTimelineStyles'\nexport type { GrTimelineGroupBy } from './timelineGroups'\n\nexport interface GrTimelineProps<TItem = unknown> {\n /**\n * Данные ленты. С ними пункты рисует слот `#item` — и только так лента знает\n * состав набора, то есть может его группировать.\n */\n items?: TItem[]\n /** Ключ пункта для `v-for`: имя поля или функция. Без него — индекс. */\n itemKey?: string | ((item: TItem, index: number) => string | number)\n /** Группировка: имя поля или функция от пункта. Заголовок группы — слот `#group`. */\n groupBy?: GrTimelineGroupBy<TItem>\n /** Уровень заголовка группы: лента обязана вписаться в структуру заголовков страницы. */\n groupHeadingLevel?: GrTimelineHeadingLevel\n /** Раскладка: одна ось, колонка времени слева или чередование сторон. */\n layout?: GrTimelineLayout\n /** Направление оси. Горизонтальная лента — скроллер. */\n orientation?: GrTimelineOrientation\n /** Плотность вертикальных отступов пункта. */\n density?: GrTimelineDensity\n /** Идёт загрузка: вместо пунктов — заглушки, контейнер помечен `aria-busy`. */\n loading?: boolean\n /** Сколько строк-заглушек показать при `loading`. */\n loadingRows?: number\n /**\n * Лента пуста. По умолчанию определяется сама — по данным или по слоту; проп\n * нужен там, где потребитель знает лучше.\n */\n empty?: boolean\n /** Текст пустого состояния. Слот `#empty` сильнее. */\n emptyText?: string\n}\n\nconst props = withDefaults(defineProps<GrTimelineProps<T>>(), {\n items: undefined,\n itemKey: undefined,\n groupBy: undefined,\n // Настоящие дефолты живут в резолверах `useGrComponentProp`: объявленный здесь\n // дефолт неотличим от значения, переданного потребителем, и перебивал бы конфиг.\n groupHeadingLevel: undefined,\n layout: undefined,\n orientation: undefined,\n density: undefined,\n loading: false,\n loadingRows: 3,\n empty: undefined,\n emptyText: undefined,\n})\n\ndefineSlots<{\n /** Пункты ленты (`GrTimelineItem`), когда данные не переданы пропом. */\n default?: () => any\n /** Пункт в data-режиме. */\n item?: (props: { item: T, index: number }) => any\n /** Заголовок группы. По умолчанию — сам ключ. */\n group?: (props: { group: string, items: T[] }) => any\n /** Содержимое пустого состояния вместо текста по умолчанию. */\n empty?: () => any\n /** Заглушки загрузки вместо строк по умолчанию. */\n loading?: () => any\n}>()\n\nconst slots = useSlots()\nconst { t } = useGranularityTranslations()\n\nconst density = useGrComponentProp('GrTimeline', 'density', () => props.density, 'regular')\nconst orientation = useGrComponentProp('GrTimeline', 'orientation', () => props.orientation, 'vertical')\nconst headingLevel = useGrComponentProp('GrTimeline', 'groupHeadingLevel', () => props.groupHeadingLevel, 3)\nconst requestedLayout = useGrComponentProp('GrTimeline', 'layout', () => props.layout, 'stacked')\n\n/**\n * Горизонтальная лента знает одну раскладку: колонка времени и чередование\n * сторон — это вертикальные приёмы, у горизонтальной оси сторон нет.\n */\nconst layout = computed<GrTimelineLayout>(() => (\n orientation.value === 'horizontal' ? 'stacked' : requestedLayout.value\n))\n\nconst headingTag = computed(() => `h${headingLevel.value}`)\n\nconst dataMode = computed(() => props.items !== undefined)\n\nconst grouped = computed(() => dataMode.value && props.groupBy !== undefined)\n\nconst groups = computed(() => groupTimelineItems(props.items ?? [], props.groupBy))\n\n/** Единственная группа плоской ленты: тот же набор, что и в data-режиме без группировки. */\nconst entries = computed(() => groups.value[0]?.entries ?? [])\n\nconst resolvedEmptyText = computed(() => props.emptyText ?? t('gr.timeline.empty', 'Nothing here yet'))\n\nconst isEmpty = computed(() => {\n if (props.empty !== undefined)\n return props.empty\n\n if (dataMode.value)\n return props.items!.length === 0\n\n // `v-for` по пустому массиву оставляет фрагмент без узлов, `v-if` — комментарий,\n // и ни то ни другое пунктом ленты не является.\n return !hasMeaningfulSlotContent(slots.default?.() ?? [])\n})\n\nconst loadingRowCount = computed(() => Math.max(1, Math.trunc(props.loadingRows)))\n\nfunction keyOf(item: T, index: number): string | number {\n const key = props.itemKey\n if (typeof key === 'function')\n return key(item, index)\n if (typeof key === 'string')\n return (item as Record<string, unknown>)[key] as string | number\n\n return index\n}\n\nfunction itemsOf(group: { entries: { item: T }[] }): T[] {\n return group.entries.map(entry => entry.item)\n}\n\nprovide(GR_TIMELINE_CONTEXT, { layout, orientation, density })\n\nif (__GR_DEV__) {\n watch(\n () => [dataMode.value, Boolean(slots.item)] as const,\n ([hasItems, hasSlot]) => {\n if (hasItems && !hasSlot) {\n console.warn(\n '[granularity] GrTimeline: с `items` пункты рисует слот `#item` — без него лента пуста.',\n )\n }\n },\n { immediate: true },\n )\n\n watch(\n () => [dataMode.value, props.groupBy !== undefined] as const,\n ([hasItems, hasGroupBy]) => {\n if (hasGroupBy && !hasItems) {\n console.warn(\n '[granularity] GrTimeline: `groupBy` работает только с `items` — пункты из слота по '\n + 'умолчанию лента не разбирает и сгруппировать их ей не из чего.',\n )\n }\n },\n { immediate: true },\n )\n\n watch(\n () => [orientation.value, requestedLayout.value] as const,\n ([axis, requested]) => {\n if (axis === 'horizontal' && requested !== 'stacked') {\n console.warn(\n `[granularity] GrTimeline: layout=\"${requested}\" — вертикальная раскладка, `\n + 'в горизонтальной ленте она игнорируется.',\n )\n }\n },\n { immediate: true },\n )\n}\n</script>\n\n<template>\n <div\n data-gr-timeline\n :data-layout=\"layout\"\n :data-orientation=\"orientation\"\n :aria-busy=\"loading ? 'true' : undefined\"\n >\n <template v-if=\"loading\">\n <slot name=\"loading\">\n <div\n v-for=\"row in loadingRowCount\"\n :key=\"row\"\n data-gr-timeline-loading-row\n :class=\"loadingRowClass\"\n >\n <GrSkeleton variant=\"circle\" width=\"var(--gr-timeline-marker-size, 10px)\" />\n <GrSkeleton />\n </div>\n </slot>\n </template>\n\n <div\n v-else-if=\"isEmpty\"\n data-gr-timeline-empty\n :class=\"emptyClass\"\n >\n <slot name=\"empty\">\n {{ resolvedEmptyText }}\n </slot>\n </div>\n\n <template v-else-if=\"grouped\">\n <!--\n Группа — секция с заголовком, а не пункт-заголовок внутри списка:\n `<li>` с датой попал бы в набор событий и был бы объявлен как одно из них.\n -->\n <section\n v-for=\"group in groups\"\n :key=\"group.key\"\n data-gr-timeline-group\n >\n <div\n v-if=\"group.key\"\n data-gr-timeline-group-header\n >\n <div data-gr-timeline-rail aria-hidden=\"true\">\n <span data-gr-timeline-line />\n </div>\n\n <component\n :is=\"headingTag\"\n data-gr-timeline-group-title\n :class=\"groupTitleClass\"\n >\n <slot name=\"group\" :group=\"group.key\" :items=\"itemsOf(group)\">\n {{ group.key }}\n </slot>\n </component>\n </div>\n\n <ol\n data-gr-timeline-list\n :tabindex=\"orientation === 'horizontal' ? 0 : undefined\"\n >\n <template\n v-for=\"entry in group.entries\"\n :key=\"keyOf(entry.item, entry.index)\"\n >\n <slot name=\"item\" :item=\"entry.item\" :index=\"entry.index\" />\n </template>\n </ol>\n </section>\n </template>\n\n <ol\n v-else\n data-gr-timeline-list\n :tabindex=\"orientation === 'horizontal' ? 0 : undefined\"\n >\n <template v-if=\"dataMode\">\n <template\n v-for=\"entry in entries\"\n :key=\"keyOf(entry.item, entry.index)\"\n >\n <slot name=\"item\" :item=\"entry.item\" :index=\"entry.index\" />\n </template>\n </template>\n\n <slot v-else />\n </ol>\n </div>\n</template>\n\n<style>\n/*\n * Геометрия ленты живёт здесь, а не в утилитах, по одной причине: сторона\n * пункта в `alternate` — это `nth-child`, а обрыв оси — `last-child`, и оба\n * вопроса утилитами не выражаются. Альтернатива — регистрация пунктов в\n * контексте на `onMounted` — дала бы порядок, зависящий от порядка монтирования,\n * и разъезжалась бы на `v-if` посреди ленты.\n */\n\n[data-gr-timeline] {\n --gr-timeline-columns: auto 1fr;\n}\n\n[data-gr-timeline][data-layout='time'] {\n --gr-timeline-columns: var(--gr-timeline-time-width, 5.5rem) auto 1fr;\n}\n\n[data-gr-timeline][data-layout='alternate'] {\n --gr-timeline-columns: 1fr auto 1fr;\n}\n\n[data-gr-timeline-list] {\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n[data-gr-timeline-item],\n[data-gr-timeline-group-header] {\n display: grid;\n grid-template-columns: var(--gr-timeline-columns);\n column-gap: var(--gr-timeline-gap, 0.75rem);\n align-items: start;\n}\n\n[data-gr-timeline-aside] {\n text-align: end;\n}\n\n/*\n * Ось — отрезок у каждого пункта, а не одна длинная линия: соседние отрезки\n * смыкаются сами, и мерить высоту списка не приходится.\n */\n[data-gr-timeline-rail] {\n display: flex;\n flex-direction: column;\n align-items: center;\n align-self: stretch;\n padding-top: var(--gr-timeline-marker-top, 0.35rem);\n min-width: var(--gr-timeline-marker-size, 10px);\n}\n\n[data-gr-timeline-rail] > :not([data-gr-timeline-line]) {\n flex: none;\n}\n\n[data-gr-timeline-marker] {\n box-sizing: border-box;\n width: var(--gr-timeline-marker-size, 10px);\n height: var(--gr-timeline-marker-size, 10px);\n border-width: var(--gr-timeline-marker-ring, 2px);\n border-style: solid;\n border-color: transparent;\n}\n\n[data-gr-timeline-line] {\n flex: 1 1 auto;\n width: var(--gr-timeline-axis-width, 2px);\n min-height: var(--gr-timeline-line-min, 0.75rem);\n margin-top: 0.25rem;\n /* Ровно на столько следующая точка отступает от верха своей строки: без этого\n отрезок не доходит до неё, и ось выглядит пунктиром из длинных штрихов. */\n margin-bottom: calc(-1 * var(--gr-timeline-marker-top, 0.35rem));\n background-color: var(--gr-timeline-axis-color, var(--gr-brd));\n}\n\n/*\n * Пунктиром идёт участок, ведущий **к** незавершённому событию, и всё, что за\n * ним: непройденный путь. Отрезок после точки для этого не годится — обычно\n * незавершённое событие последнее, и его отрезка не видно вовсе.\n */\n[data-gr-timeline-item]:has(+ [data-gr-timeline-item][data-pending]) [data-gr-timeline-line],\n[data-gr-timeline-item][data-pending] [data-gr-timeline-line] {\n background-color: transparent;\n background-image: repeating-linear-gradient(\n to bottom,\n var(--gr-timeline-axis-color, var(--gr-brd)) 0 var(--gr-timeline-dash, 4px),\n transparent var(--gr-timeline-dash, 4px) calc(var(--gr-timeline-dash, 4px) * 2)\n );\n}\n\n/* Хвост после последнего события — линия в никуда. */\n[data-gr-timeline-item]:last-child [data-gr-timeline-line] {\n visibility: hidden;\n}\n\n/* Между группами ось не рвётся: разрыв читался бы как две разные ленты. */\n[data-gr-timeline-group]:not(:last-of-type) [data-gr-timeline-item]:last-child [data-gr-timeline-line] {\n visibility: visible;\n}\n\n[data-gr-timeline-group-header] [data-gr-timeline-rail] {\n padding-top: 0;\n}\n\n[data-gr-timeline-group] + [data-gr-timeline-group] [data-gr-timeline-group-title] {\n padding-top: 0.5rem;\n}\n\n[data-gr-timeline-group-header] [data-gr-timeline-line] {\n margin-top: 0;\n}\n\n/* Над первым заголовком оси ещё нет — линия висела бы в воздухе. */\n[data-gr-timeline-group]:first-of-type [data-gr-timeline-group-header] [data-gr-timeline-line] {\n visibility: hidden;\n}\n\n[data-gr-timeline][data-layout='time'] [data-gr-timeline-group-header] [data-gr-timeline-rail],\n[data-gr-timeline][data-layout='alternate'] [data-gr-timeline-group-header] [data-gr-timeline-rail] {\n grid-column: 2;\n}\n\n[data-gr-timeline][data-layout='time'] [data-gr-timeline-group-title],\n[data-gr-timeline][data-layout='alternate'] [data-gr-timeline-group-title] {\n grid-column: 3;\n}\n\n[data-gr-timeline][data-layout='alternate'] [data-gr-timeline-rail] {\n grid-column: 2;\n}\n\n[data-gr-timeline][data-layout='alternate'] [data-gr-timeline-content] {\n grid-column: 3;\n}\n\n[data-gr-timeline][data-layout='alternate'] [data-gr-timeline-item]:nth-child(even) [data-gr-timeline-content] {\n grid-column: 1;\n grid-row: 1;\n text-align: end;\n}\n\n/* Двухсторонняя лента в узкой колонке нечитаема — там она односторонняя. */\n@media (max-width: 640px) {\n [data-gr-timeline][data-layout='alternate'] {\n --gr-timeline-columns: auto 1fr;\n }\n\n [data-gr-timeline][data-layout='alternate'] [data-gr-timeline-rail],\n [data-gr-timeline][data-layout='alternate'] [data-gr-timeline-group-header] [data-gr-timeline-rail] {\n grid-column: 1;\n }\n\n [data-gr-timeline][data-layout='alternate'] [data-gr-timeline-content],\n [data-gr-timeline][data-layout='alternate'] [data-gr-timeline-item]:nth-child(even) [data-gr-timeline-content],\n [data-gr-timeline][data-layout='alternate'] [data-gr-timeline-group-title] {\n grid-column: 2;\n text-align: start;\n }\n}\n\n[data-gr-timeline][data-orientation='horizontal'] [data-gr-timeline-list] {\n display: flex;\n flex-direction: row;\n overflow-x: auto;\n}\n\n[data-gr-timeline][data-orientation='horizontal'] [data-gr-timeline-item] {\n grid-template-columns: 1fr;\n min-width: var(--gr-timeline-item-min-width, 12rem);\n padding-inline-end: var(--gr-timeline-gap, 0.75rem);\n}\n\n[data-gr-timeline][data-orientation='horizontal'] [data-gr-timeline-content] {\n padding-bottom: 0;\n}\n\n[data-gr-timeline][data-orientation='horizontal'] [data-gr-timeline-rail] {\n flex-direction: row;\n align-items: center;\n align-self: auto;\n padding-top: 0;\n padding-bottom: 0.25rem;\n}\n\n[data-gr-timeline][data-orientation='horizontal'] [data-gr-timeline-line] {\n width: auto;\n height: var(--gr-timeline-axis-width, 2px);\n min-width: var(--gr-timeline-line-min, 0.75rem);\n min-height: 0;\n margin-top: 0;\n margin-inline-start: 0.25rem;\n}\n\n[data-gr-timeline][data-orientation='horizontal'] [data-gr-timeline-item]:has(+ [data-gr-timeline-item][data-pending]) [data-gr-timeline-line],\n[data-gr-timeline][data-orientation='horizontal'] [data-gr-timeline-item][data-pending] [data-gr-timeline-line] {\n background-image: repeating-linear-gradient(\n to right,\n var(--gr-timeline-axis-color, var(--gr-brd)) 0 var(--gr-timeline-dash, 4px),\n transparent var(--gr-timeline-dash, 4px) calc(var(--gr-timeline-dash, 4px) * 2)\n );\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, inject, useSlots } from 'vue'\n\nimport { GR_TIMELINE_CONTEXT } from './grTimelineContext'\nimport {\n descriptionClass,\n grTimelineDensityClass,\n grTimelineMarkerClass,\n timeClass,\n titleClass,\n type GrTimelineMarkerVariant,\n type GrTimelineTone,\n} from './grTimelineStyles'\n\nexport type { GrTimelineMarkerVariant, GrTimelineTone } from './grTimelineStyles'\n\nexport interface GrTimelineItemProps {\n /** Метка времени. Слот `#time` сильнее. */\n time?: string\n /** Машиночитаемое значение метки: попадает в `datetime` тега `<time>`. */\n datetime?: string\n /** Заголовок события. Слот `#title` сильнее. */\n title?: string\n /** Пояснение под заголовком. Слот `#description` сильнее. */\n description?: string\n /** Цвет маркера из палитры пакета. */\n tone?: GrTimelineTone\n /** Заполненная точка — событие случилось, полая — запланировано. */\n variant?: GrTimelineMarkerVariant\n /**\n * Событие ещё не произошло: точка полая, а ось пунктирна начиная с участка,\n * **ведущего** к нему, — непройденный путь. Перебивает `variant`.\n */\n pending?: boolean\n}\n\nconst props = withDefaults(defineProps<GrTimelineItemProps>(), {\n time: undefined,\n datetime: undefined,\n title: undefined,\n description: undefined,\n tone: 'neutral',\n variant: 'filled',\n pending: false,\n})\n\ndefineSlots<{\n /** Содержимое события под заголовком и описанием. */\n default?: () => any\n /** Маркер вместо точки: иконка статуса, аватар автора, номер шага. */\n marker?: () => any\n /** Метка времени. */\n time?: () => any\n /** Заголовок события. */\n title?: () => any\n /** Пояснение под заголовком. */\n description?: () => any\n}>()\n\nconst slots = useSlots()\n\n/**\n * Раскладку пункт читает из контейнера. Вне `GrTimeline` — дефолты: пункт\n * остаётся рабочей строкой, а не падает.\n */\nconst timeline = inject(GR_TIMELINE_CONTEXT, null)\n\nconst layout = computed(() => timeline?.layout.value ?? 'stacked')\nconst density = computed(() => timeline?.density.value ?? 'regular')\n\nconst markerClass = computed(() => grTimelineMarkerClass(\n props.tone,\n props.pending ? 'outlined' : props.variant,\n))\n\nconst hasTime = computed(() => !!slots.time || !!props.time)\nconst hasTitle = computed(() => !!slots.title || !!props.title)\nconst hasDescription = computed(() => !!slots.description || !!props.description)\n\n/** В `layout=\"time\"` метка уезжает в свою колонку, во всех остальных — в содержимое. */\nconst timeInAside = computed(() => layout.value === 'time')\n</script>\n\n<template>\n <li\n data-gr-timeline-item\n :data-tone=\"tone\"\n :data-pending=\"pending ? '' : undefined\"\n >\n <div\n v-if=\"timeInAside\"\n data-gr-timeline-aside\n >\n <time\n v-if=\"hasTime\"\n :datetime=\"datetime\"\n :class=\"timeClass\"\n >\n <slot name=\"time\">{{ time }}</slot>\n </time>\n </div>\n\n <!-- Ось и точка декоративны: смысл события несёт текст рядом. -->\n <div\n data-gr-timeline-rail\n aria-hidden=\"true\"\n >\n <slot name=\"marker\">\n <span data-gr-timeline-marker :class=\"markerClass\" />\n </slot>\n <span data-gr-timeline-line />\n </div>\n\n <!--\n Вертикальный отступ живёт на содержимом, а не на строке: ось растянута по\n строке грид-ячейкой, и её поля в высоту ячейки не входят — линия обрывалась\n бы, не доходя до следующей точки.\n -->\n <div data-gr-timeline-content :class=\"grTimelineDensityClass(density)\">\n <time\n v-if=\"hasTime && !timeInAside\"\n data-gr-timeline-time\n :datetime=\"datetime\"\n :class=\"timeClass\"\n >\n <slot name=\"time\">{{ time }}</slot>\n </time>\n\n <div v-if=\"hasTitle\" data-gr-timeline-title :class=\"titleClass\">\n <slot name=\"title\">\n{{ title }}\n</slot>\n </div>\n\n <div v-if=\"hasDescription\" :class=\"descriptionClass\">\n <slot name=\"description\">\n{{ description }}\n</slot>\n </div>\n\n <div v-if=\"$slots.default\" data-gr-timeline-body>\n <slot />\n </div>\n </div>\n </li>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, inject, useSlots } from 'vue'\n\nimport { GR_TIMELINE_CONTEXT } from './grTimelineContext'\nimport {\n descriptionClass,\n grTimelineDensityClass,\n grTimelineMarkerClass,\n timeClass,\n titleClass,\n type GrTimelineMarkerVariant,\n type GrTimelineTone,\n} from './grTimelineStyles'\n\nexport type { GrTimelineMarkerVariant, GrTimelineTone } from './grTimelineStyles'\n\nexport interface GrTimelineItemProps {\n /** Метка времени. Слот `#time` сильнее. */\n time?: string\n /** Машиночитаемое значение метки: попадает в `datetime` тега `<time>`. */\n datetime?: string\n /** Заголовок события. Слот `#title` сильнее. */\n title?: string\n /** Пояснение под заголовком. Слот `#description` сильнее. */\n description?: string\n /** Цвет маркера из палитры пакета. */\n tone?: GrTimelineTone\n /** Заполненная точка — событие случилось, полая — запланировано. */\n variant?: GrTimelineMarkerVariant\n /**\n * Событие ещё не произошло: точка полая, а ось пунктирна начиная с участка,\n * **ведущего** к нему, — непройденный путь. Перебивает `variant`.\n */\n pending?: boolean\n}\n\nconst props = withDefaults(defineProps<GrTimelineItemProps>(), {\n time: undefined,\n datetime: undefined,\n title: undefined,\n description: undefined,\n tone: 'neutral',\n variant: 'filled',\n pending: false,\n})\n\ndefineSlots<{\n /** Содержимое события под заголовком и описанием. */\n default?: () => any\n /** Маркер вместо точки: иконка статуса, аватар автора, номер шага. */\n marker?: () => any\n /** Метка времени. */\n time?: () => any\n /** Заголовок события. */\n title?: () => any\n /** Пояснение под заголовком. */\n description?: () => any\n}>()\n\nconst slots = useSlots()\n\n/**\n * Раскладку пункт читает из контейнера. Вне `GrTimeline` — дефолты: пункт\n * остаётся рабочей строкой, а не падает.\n */\nconst timeline = inject(GR_TIMELINE_CONTEXT, null)\n\nconst layout = computed(() => timeline?.layout.value ?? 'stacked')\nconst density = computed(() => timeline?.density.value ?? 'regular')\n\nconst markerClass = computed(() => grTimelineMarkerClass(\n props.tone,\n props.pending ? 'outlined' : props.variant,\n))\n\nconst hasTime = computed(() => !!slots.time || !!props.time)\nconst hasTitle = computed(() => !!slots.title || !!props.title)\nconst hasDescription = computed(() => !!slots.description || !!props.description)\n\n/** В `layout=\"time\"` метка уезжает в свою колонку, во всех остальных — в содержимое. */\nconst timeInAside = computed(() => layout.value === 'time')\n</script>\n\n<template>\n <li\n data-gr-timeline-item\n :data-tone=\"tone\"\n :data-pending=\"pending ? '' : undefined\"\n >\n <div\n v-if=\"timeInAside\"\n data-gr-timeline-aside\n >\n <time\n v-if=\"hasTime\"\n :datetime=\"datetime\"\n :class=\"timeClass\"\n >\n <slot name=\"time\">{{ time }}</slot>\n </time>\n </div>\n\n <!-- Ось и точка декоративны: смысл события несёт текст рядом. -->\n <div\n data-gr-timeline-rail\n aria-hidden=\"true\"\n >\n <slot name=\"marker\">\n <span data-gr-timeline-marker :class=\"markerClass\" />\n </slot>\n <span data-gr-timeline-line />\n </div>\n\n <!--\n Вертикальный отступ живёт на содержимом, а не на строке: ось растянута по\n строке грид-ячейкой, и её поля в высоту ячейки не входят — линия обрывалась\n бы, не доходя до следующей точки.\n -->\n <div data-gr-timeline-content :class=\"grTimelineDensityClass(density)\">\n <time\n v-if=\"hasTime && !timeInAside\"\n data-gr-timeline-time\n :datetime=\"datetime\"\n :class=\"timeClass\"\n >\n <slot name=\"time\">{{ time }}</slot>\n </time>\n\n <div v-if=\"hasTitle\" data-gr-timeline-title :class=\"titleClass\">\n <slot name=\"title\">\n{{ title }}\n</slot>\n </div>\n\n <div v-if=\"hasDescription\" :class=\"descriptionClass\">\n <slot name=\"description\">\n{{ description }}\n</slot>\n </div>\n\n <div v-if=\"$slots.default\" data-gr-timeline-body>\n <slot />\n </div>\n </div>\n </li>\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"GrTimeline-BiHpArsg.js","names":["$slots"],"sources":["../../../../src/components/GrTimeline/grTimelineContext.ts","../../../../src/components/GrTimeline/timelineGroups.ts","../../../../src/components/GrTimeline/GrTimeline.vue","../../../../src/components/GrTimeline/GrTimeline.vue","../../../../src/components/GrTimeline/GrTimelineItem.vue","../../../../src/components/GrTimeline/GrTimelineItem.vue"],"sourcesContent":["import type { ComputedRef, InjectionKey } from 'vue'\n\nimport type { GrTimelineDensity, GrTimelineLayout, GrTimelineOrientation } from './grTimelineStyles'\n\n/**\n * Уровень заголовка группы. Тот же довод, что у `GrCollapse`: лента в разделе\n * `<h4>` с захардкоженным `<h3>` рвёт навигацию по заголовкам.\n */\nexport const GR_TIMELINE_HEADING_LEVELS = [2, 3, 4, 5, 6] as const\n\nexport type GrTimelineHeadingLevel = typeof GR_TIMELINE_HEADING_LEVELS[number]\n\n/**\n * Пункт читает раскладку из контейнера, а не получает её пропом: иначе\n * `layout` пришлось бы дублировать на каждом `GrTimelineItem`.\n */\nexport interface GrTimelineContext {\n layout: ComputedRef<GrTimelineLayout>\n orientation: ComputedRef<GrTimelineOrientation>\n density: ComputedRef<GrTimelineDensity>\n}\n\nexport const GR_TIMELINE_CONTEXT: InjectionKey<GrTimelineContext> = Symbol('GR_TIMELINE_CONTEXT')\n","/**\n * Группировка ленты событий. Без Vue — чтобы проверялась без монтирования.\n */\n\nexport type GrTimelineGroupBy<T> = string | ((item: T, index: number) => string)\n\n/** Пункт вместе с его позицией в исходном наборе: ключи `v-for` считаются от неё. */\nexport interface GrTimelineEntry<T> {\n item: T\n index: number\n}\n\nexport interface GrTimelineGroup<T> {\n /** Ключ группы. Пустая строка — заголовка у группы нет. */\n key: string\n entries: GrTimelineEntry<T>[]\n}\n\nfunction keyOf<T>(item: T, index: number, groupBy: GrTimelineGroupBy<T>): string {\n if (typeof groupBy === 'function')\n return String(groupBy(item, index) ?? '')\n\n const value = (item as Record<string, unknown> | null | undefined)?.[groupBy]\n\n // `undefined` в поле — это «группы нет», а не группа с надписью «undefined».\n return value == null ? '' : String(value)\n}\n\n/**\n * Режет набор на группы, **не меняя порядок**: лента хронологична, и\n * пересортировать её компонент не вправе. Порядок групп — порядок первого\n * вхождения ключа; разорванная группа (`A B A`) собирается в одну.\n *\n * Без `groupBy` — одна безымянная группа: плоская лента остаётся частным\n * случаем, и второй ветки рендера у компонента не появляется.\n */\nexport function groupTimelineItems<T>(\n items: readonly T[],\n groupBy?: GrTimelineGroupBy<T>,\n): GrTimelineGroup<T>[] {\n const entries = items.map((item, index) => ({ item, index }))\n\n if (groupBy === undefined)\n return entries.length === 0 ? [] : [{ key: '', entries }]\n\n const byKey = new Map<string, GrTimelineGroup<T>>()\n\n for (const entry of entries) {\n const key = keyOf(entry.item, entry.index, groupBy)\n const group = byKey.get(key)\n\n if (group)\n group.entries.push(entry)\n else\n byKey.set(key, { key, entries: [entry] })\n }\n\n return [...byKey.values()]\n}\n","<script setup lang=\"ts\" generic=\"T = unknown\">\nimport { computed, provide, useSlots, watch } from 'vue'\n\nimport GrSkeleton from '../GrSkeleton/GrSkeleton.vue'\nimport { useGrComponentProp } from '../GrConfigProvider/context'\nimport { hasMeaningfulSlotContent } from '../shared/slotNodes'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\n\nimport { GR_TIMELINE_CONTEXT, type GrTimelineHeadingLevel } from './grTimelineContext'\nimport {\n emptyClass,\n groupTitleClass,\n loadingRowClass,\n type GrTimelineDensity,\n type GrTimelineLayout,\n type GrTimelineOrientation,\n} from './grTimelineStyles'\nimport { groupTimelineItems, type GrTimelineGroupBy } from './timelineGroups'\n\nexport type {\n GrTimelineDensity,\n GrTimelineLayout,\n GrTimelineOrientation,\n} from './grTimelineStyles'\nexport type { GrTimelineGroupBy } from './timelineGroups'\n\nexport interface GrTimelineProps<TItem = unknown> {\n /**\n * Данные ленты. С ними пункты рисует слот `#item` — и только так лента знает\n * состав набора, то есть может его группировать.\n */\n items?: TItem[]\n /** Ключ пункта для `v-for`: имя поля или функция. Без него — индекс. */\n itemKey?: string | ((item: TItem, index: number) => string | number)\n /** Группировка: имя поля или функция от пункта. Заголовок группы — слот `#group`. */\n groupBy?: GrTimelineGroupBy<TItem>\n /** Уровень заголовка группы: лента обязана вписаться в структуру заголовков страницы. */\n groupHeadingLevel?: GrTimelineHeadingLevel\n /** Раскладка: одна ось, колонка времени слева или чередование сторон. */\n layout?: GrTimelineLayout\n /** Направление оси. Горизонтальная лента — скроллер. */\n orientation?: GrTimelineOrientation\n /** Плотность вертикальных отступов пункта. */\n density?: GrTimelineDensity\n /** Идёт загрузка: вместо пунктов — заглушки, контейнер помечен `aria-busy`. */\n loading?: boolean\n /** Сколько строк-заглушек показать при `loading`. */\n loadingRows?: number\n /**\n * Лента пуста. По умолчанию определяется сама — по данным или по слоту; проп\n * нужен там, где потребитель знает лучше.\n */\n empty?: boolean\n /** Текст пустого состояния. Слот `#empty` сильнее. */\n emptyText?: string\n}\n\nconst props = withDefaults(defineProps<GrTimelineProps<T>>(), {\n items: undefined,\n itemKey: undefined,\n groupBy: undefined,\n // Настоящие дефолты живут в резолверах `useGrComponentProp`: объявленный здесь\n // дефолт неотличим от значения, переданного потребителем, и перебивал бы конфиг.\n groupHeadingLevel: undefined,\n layout: undefined,\n orientation: undefined,\n density: undefined,\n loading: false,\n loadingRows: 3,\n empty: undefined,\n emptyText: undefined,\n})\n\ndefineSlots<{\n /** Пункты ленты (`GrTimelineItem`), когда данные не переданы пропом. */\n default?: () => any\n /** Пункт в data-режиме. */\n item?: (props: { item: T, index: number }) => any\n /** Заголовок группы. По умолчанию — сам ключ. */\n group?: (props: { group: string, items: T[] }) => any\n /** Содержимое пустого состояния вместо текста по умолчанию. */\n empty?: () => any\n /** Заглушки загрузки вместо строк по умолчанию. */\n loading?: () => any\n}>()\n\nconst slots = useSlots()\nconst { t } = useGranularityTranslations()\n\nconst density = useGrComponentProp('GrTimeline', 'density', () => props.density, 'regular')\nconst orientation = useGrComponentProp('GrTimeline', 'orientation', () => props.orientation, 'vertical')\nconst headingLevel = useGrComponentProp('GrTimeline', 'groupHeadingLevel', () => props.groupHeadingLevel, 3)\nconst requestedLayout = useGrComponentProp('GrTimeline', 'layout', () => props.layout, 'stacked')\n\n/**\n * Горизонтальная лента знает одну раскладку: колонка времени и чередование\n * сторон — это вертикальные приёмы, у горизонтальной оси сторон нет.\n */\nconst layout = computed<GrTimelineLayout>(() => (\n orientation.value === 'horizontal' ? 'stacked' : requestedLayout.value\n))\n\nconst headingTag = computed(() => `h${headingLevel.value}`)\n\nconst dataMode = computed(() => props.items !== undefined)\n\nconst grouped = computed(() => dataMode.value && props.groupBy !== undefined)\n\nconst groups = computed(() => groupTimelineItems(props.items ?? [], props.groupBy))\n\n/** Единственная группа плоской ленты: тот же набор, что и в data-режиме без группировки. */\nconst entries = computed(() => groups.value[0]?.entries ?? [])\n\nconst resolvedEmptyText = computed(() => props.emptyText ?? t('gr.timeline.empty', 'Nothing here yet'))\n\nconst isEmpty = computed(() => {\n if (props.empty !== undefined)\n return props.empty\n\n if (dataMode.value)\n return props.items!.length === 0\n\n // `v-for` по пустому массиву оставляет фрагмент без узлов, `v-if` — комментарий,\n // и ни то ни другое пунктом ленты не является.\n return !hasMeaningfulSlotContent(slots.default?.() ?? [])\n})\n\nconst loadingRowCount = computed(() => Math.max(1, Math.trunc(props.loadingRows)))\n\nfunction keyOf(item: T, index: number): string | number {\n const key = props.itemKey\n if (typeof key === 'function')\n return key(item, index)\n if (typeof key === 'string')\n return (item as Record<string, unknown>)[key] as string | number\n\n return index\n}\n\nfunction itemsOf(group: { entries: { item: T }[] }): T[] {\n return group.entries.map(entry => entry.item)\n}\n\nprovide(GR_TIMELINE_CONTEXT, { layout, orientation, density })\n\nif (__GR_DEV__) {\n watch(\n () => [dataMode.value, Boolean(slots.item)] as const,\n ([hasItems, hasSlot]) => {\n if (hasItems && !hasSlot) {\n console.warn(\n '[granularity] GrTimeline: с `items` пункты рисует слот `#item` — без него лента пуста.',\n )\n }\n },\n { immediate: true },\n )\n\n watch(\n () => [dataMode.value, props.groupBy !== undefined] as const,\n ([hasItems, hasGroupBy]) => {\n if (hasGroupBy && !hasItems) {\n console.warn(\n '[granularity] GrTimeline: `groupBy` работает только с `items` — пункты из слота по '\n + 'умолчанию лента не разбирает и сгруппировать их ей не из чего.',\n )\n }\n },\n { immediate: true },\n )\n\n watch(\n () => [orientation.value, requestedLayout.value] as const,\n ([axis, requested]) => {\n if (axis === 'horizontal' && requested !== 'stacked') {\n console.warn(\n `[granularity] GrTimeline: layout=\"${requested}\" — вертикальная раскладка, `\n + 'в горизонтальной ленте она игнорируется.',\n )\n }\n },\n { immediate: true },\n )\n}\n</script>\n\n<template>\n <div\n data-gr-timeline\n :data-layout=\"layout\"\n :data-orientation=\"orientation\"\n :aria-busy=\"loading ? 'true' : undefined\"\n >\n <template v-if=\"loading\">\n <slot name=\"loading\">\n <div\n v-for=\"row in loadingRowCount\"\n :key=\"row\"\n data-gr-timeline-loading-row\n :class=\"loadingRowClass\"\n >\n <GrSkeleton variant=\"circle\" width=\"var(--gr-timeline-marker-size, 10px)\" />\n <GrSkeleton />\n </div>\n </slot>\n </template>\n\n <div\n v-else-if=\"isEmpty\"\n data-gr-timeline-empty\n :class=\"emptyClass\"\n >\n <slot name=\"empty\">\n {{ resolvedEmptyText }}\n </slot>\n </div>\n\n <template v-else-if=\"grouped\">\n <!--\n Группа — секция с заголовком, а не пункт-заголовок внутри списка:\n `<li>` с датой попал бы в набор событий и был бы объявлен как одно из них.\n -->\n <section\n v-for=\"group in groups\"\n :key=\"group.key\"\n data-gr-timeline-group\n >\n <div\n v-if=\"group.key\"\n data-gr-timeline-group-header\n >\n <div data-gr-timeline-rail aria-hidden=\"true\">\n <span data-gr-timeline-line />\n </div>\n\n <component\n :is=\"headingTag\"\n data-gr-timeline-group-title\n :class=\"groupTitleClass\"\n >\n <slot name=\"group\" :group=\"group.key\" :items=\"itemsOf(group)\">\n {{ group.key }}\n </slot>\n </component>\n </div>\n\n <ol\n data-gr-timeline-list\n :tabindex=\"orientation === 'horizontal' ? 0 : undefined\"\n >\n <template\n v-for=\"entry in group.entries\"\n :key=\"keyOf(entry.item, entry.index)\"\n >\n <slot name=\"item\" :item=\"entry.item\" :index=\"entry.index\" />\n </template>\n </ol>\n </section>\n </template>\n\n <ol\n v-else\n data-gr-timeline-list\n :tabindex=\"orientation === 'horizontal' ? 0 : undefined\"\n >\n <template v-if=\"dataMode\">\n <template\n v-for=\"entry in entries\"\n :key=\"keyOf(entry.item, entry.index)\"\n >\n <slot name=\"item\" :item=\"entry.item\" :index=\"entry.index\" />\n </template>\n </template>\n\n <slot v-else />\n </ol>\n </div>\n</template>\n\n<style>\n/*\n * Геометрия ленты живёт здесь, а не в утилитах, по одной причине: сторона\n * пункта в `alternate` — это `nth-child`, а обрыв оси — `last-child`, и оба\n * вопроса утилитами не выражаются. Альтернатива — регистрация пунктов в\n * контексте на `onMounted` — дала бы порядок, зависящий от порядка монтирования,\n * и разъезжалась бы на `v-if` посреди ленты.\n */\n\n[data-gr-timeline] {\n --gr-timeline-columns: auto minmax(0, 1fr);\n}\n\n[data-gr-timeline][data-layout='time'] {\n --gr-timeline-columns: var(--gr-timeline-time-width, 5.5rem) auto minmax(0, 1fr);\n}\n\n[data-gr-timeline][data-layout='alternate'] {\n --gr-timeline-columns: minmax(0, 1fr) auto minmax(0, 1fr);\n}\n\n[data-gr-timeline-list] {\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n[data-gr-timeline-item],\n[data-gr-timeline-group-header] {\n display: grid;\n grid-template-columns: var(--gr-timeline-columns);\n column-gap: var(--gr-timeline-gap, 0.75rem);\n align-items: start;\n}\n\n/*\n * Сжатие строки.\n *\n * Трек `1fr` — это `minmax(auto, 1fr)`, а его минимум равен наибольшему\n * минимальному вкладу элементов. У грид-элемента с `overflow: visible` этот\n * вклад равен min-content, то есть для строки с `white-space: nowrap` — её\n * полной ширине. Поэтому лента не сжималась, а `truncate` потребителя не\n * срабатывал: усекать было нечего, трек раздавался под текст. Явные\n * `minmax(0, …)` у треков и `min-width: 0` у элементов снимают оба минимума.\n *\n * Метка времени стоит в треке фиксированной длины, и её трек от этого не\n * меняется — но без `min-width: 0` длинная метка из слота `#time` раздаёт\n * min-content всей строки.\n */\n\n[data-gr-timeline-aside],\n[data-gr-timeline-content],\n[data-gr-timeline-group-title] {\n min-width: 0;\n}\n\n[data-gr-timeline-aside] {\n text-align: end;\n}\n\n/*\n * Ось — отрезок у каждого пункта, а не одна длинная линия: соседние отрезки\n * смыкаются сами, и мерить высоту списка не приходится.\n */\n[data-gr-timeline-rail] {\n display: flex;\n flex-direction: column;\n align-items: center;\n align-self: stretch;\n padding-top: var(--gr-timeline-marker-top, 0.35rem);\n min-width: var(--gr-timeline-marker-size, 10px);\n}\n\n[data-gr-timeline-rail] > :not([data-gr-timeline-line]) {\n flex: none;\n}\n\n[data-gr-timeline-marker] {\n box-sizing: border-box;\n width: var(--gr-timeline-marker-size, 10px);\n height: var(--gr-timeline-marker-size, 10px);\n border-width: var(--gr-timeline-marker-ring, 2px);\n border-style: solid;\n border-color: transparent;\n}\n\n[data-gr-timeline-line] {\n flex: 1 1 auto;\n width: var(--gr-timeline-axis-width, 2px);\n min-height: var(--gr-timeline-line-min, 0.75rem);\n margin-top: 0.25rem;\n /* Ровно на столько следующая точка отступает от верха своей строки: без этого\n отрезок не доходит до неё, и ось выглядит пунктиром из длинных штрихов. */\n margin-bottom: calc(-1 * var(--gr-timeline-marker-top, 0.35rem));\n background-color: var(--gr-timeline-axis-color, var(--gr-brd));\n}\n\n/*\n * Пунктиром идёт участок, ведущий **к** незавершённому событию, и всё, что за\n * ним: непройденный путь. Отрезок после точки для этого не годится — обычно\n * незавершённое событие последнее, и его отрезка не видно вовсе.\n */\n[data-gr-timeline-item]:has(+ [data-gr-timeline-item][data-pending]) [data-gr-timeline-line],\n[data-gr-timeline-item][data-pending] [data-gr-timeline-line] {\n background-color: transparent;\n background-image: repeating-linear-gradient(\n to bottom,\n var(--gr-timeline-axis-color, var(--gr-brd)) 0 var(--gr-timeline-dash, 4px),\n transparent var(--gr-timeline-dash, 4px) calc(var(--gr-timeline-dash, 4px) * 2)\n );\n}\n\n/* Хвост после последнего события — линия в никуда. */\n[data-gr-timeline-item]:last-child [data-gr-timeline-line] {\n visibility: hidden;\n}\n\n/* Между группами ось не рвётся: разрыв читался бы как две разные ленты. */\n[data-gr-timeline-group]:not(:last-of-type) [data-gr-timeline-item]:last-child [data-gr-timeline-line] {\n visibility: visible;\n}\n\n[data-gr-timeline-group-header] [data-gr-timeline-rail] {\n padding-top: 0;\n}\n\n[data-gr-timeline-group] + [data-gr-timeline-group] [data-gr-timeline-group-title] {\n padding-top: 0.5rem;\n}\n\n[data-gr-timeline-group-header] [data-gr-timeline-line] {\n margin-top: 0;\n}\n\n/* Над первым заголовком оси ещё нет — линия висела бы в воздухе. */\n[data-gr-timeline-group]:first-of-type [data-gr-timeline-group-header] [data-gr-timeline-line] {\n visibility: hidden;\n}\n\n[data-gr-timeline][data-layout='time'] [data-gr-timeline-group-header] [data-gr-timeline-rail],\n[data-gr-timeline][data-layout='alternate'] [data-gr-timeline-group-header] [data-gr-timeline-rail] {\n grid-column: 2;\n}\n\n[data-gr-timeline][data-layout='time'] [data-gr-timeline-group-title],\n[data-gr-timeline][data-layout='alternate'] [data-gr-timeline-group-title] {\n grid-column: 3;\n}\n\n[data-gr-timeline][data-layout='alternate'] [data-gr-timeline-rail] {\n grid-column: 2;\n}\n\n[data-gr-timeline][data-layout='alternate'] [data-gr-timeline-content] {\n grid-column: 3;\n}\n\n[data-gr-timeline][data-layout='alternate'] [data-gr-timeline-item]:nth-child(even) [data-gr-timeline-content] {\n grid-column: 1;\n grid-row: 1;\n text-align: end;\n}\n\n/* Двухсторонняя лента в узкой колонке нечитаема — там она односторонняя. */\n@media (max-width: 640px) {\n [data-gr-timeline][data-layout='alternate'] {\n --gr-timeline-columns: auto minmax(0, 1fr);\n }\n\n [data-gr-timeline][data-layout='alternate'] [data-gr-timeline-rail],\n [data-gr-timeline][data-layout='alternate'] [data-gr-timeline-group-header] [data-gr-timeline-rail] {\n grid-column: 1;\n }\n\n [data-gr-timeline][data-layout='alternate'] [data-gr-timeline-content],\n [data-gr-timeline][data-layout='alternate'] [data-gr-timeline-item]:nth-child(even) [data-gr-timeline-content],\n [data-gr-timeline][data-layout='alternate'] [data-gr-timeline-group-title] {\n grid-column: 2;\n text-align: start;\n }\n}\n\n[data-gr-timeline][data-orientation='horizontal'] [data-gr-timeline-list] {\n display: flex;\n flex-direction: row;\n overflow-x: auto;\n}\n\n[data-gr-timeline][data-orientation='horizontal'] [data-gr-timeline-item] {\n grid-template-columns: minmax(0, 1fr);\n min-width: var(--gr-timeline-item-min-width, 12rem);\n padding-inline-end: var(--gr-timeline-gap, 0.75rem);\n}\n\n[data-gr-timeline][data-orientation='horizontal'] [data-gr-timeline-content] {\n padding-bottom: 0;\n}\n\n[data-gr-timeline][data-orientation='horizontal'] [data-gr-timeline-rail] {\n flex-direction: row;\n align-items: center;\n align-self: auto;\n padding-top: 0;\n padding-bottom: 0.25rem;\n}\n\n[data-gr-timeline][data-orientation='horizontal'] [data-gr-timeline-line] {\n width: auto;\n height: var(--gr-timeline-axis-width, 2px);\n min-width: var(--gr-timeline-line-min, 0.75rem);\n min-height: 0;\n margin-top: 0;\n margin-inline-start: 0.25rem;\n}\n\n[data-gr-timeline][data-orientation='horizontal'] [data-gr-timeline-item]:has(+ [data-gr-timeline-item][data-pending]) [data-gr-timeline-line],\n[data-gr-timeline][data-orientation='horizontal'] [data-gr-timeline-item][data-pending] [data-gr-timeline-line] {\n background-image: repeating-linear-gradient(\n to right,\n var(--gr-timeline-axis-color, var(--gr-brd)) 0 var(--gr-timeline-dash, 4px),\n transparent var(--gr-timeline-dash, 4px) calc(var(--gr-timeline-dash, 4px) * 2)\n );\n}\n</style>\n","<script setup lang=\"ts\" generic=\"T = unknown\">\nimport { computed, provide, useSlots, watch } from 'vue'\n\nimport GrSkeleton from '../GrSkeleton/GrSkeleton.vue'\nimport { useGrComponentProp } from '../GrConfigProvider/context'\nimport { hasMeaningfulSlotContent } from '../shared/slotNodes'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\n\nimport { GR_TIMELINE_CONTEXT, type GrTimelineHeadingLevel } from './grTimelineContext'\nimport {\n emptyClass,\n groupTitleClass,\n loadingRowClass,\n type GrTimelineDensity,\n type GrTimelineLayout,\n type GrTimelineOrientation,\n} from './grTimelineStyles'\nimport { groupTimelineItems, type GrTimelineGroupBy } from './timelineGroups'\n\nexport type {\n GrTimelineDensity,\n GrTimelineLayout,\n GrTimelineOrientation,\n} from './grTimelineStyles'\nexport type { GrTimelineGroupBy } from './timelineGroups'\n\nexport interface GrTimelineProps<TItem = unknown> {\n /**\n * Данные ленты. С ними пункты рисует слот `#item` — и только так лента знает\n * состав набора, то есть может его группировать.\n */\n items?: TItem[]\n /** Ключ пункта для `v-for`: имя поля или функция. Без него — индекс. */\n itemKey?: string | ((item: TItem, index: number) => string | number)\n /** Группировка: имя поля или функция от пункта. Заголовок группы — слот `#group`. */\n groupBy?: GrTimelineGroupBy<TItem>\n /** Уровень заголовка группы: лента обязана вписаться в структуру заголовков страницы. */\n groupHeadingLevel?: GrTimelineHeadingLevel\n /** Раскладка: одна ось, колонка времени слева или чередование сторон. */\n layout?: GrTimelineLayout\n /** Направление оси. Горизонтальная лента — скроллер. */\n orientation?: GrTimelineOrientation\n /** Плотность вертикальных отступов пункта. */\n density?: GrTimelineDensity\n /** Идёт загрузка: вместо пунктов — заглушки, контейнер помечен `aria-busy`. */\n loading?: boolean\n /** Сколько строк-заглушек показать при `loading`. */\n loadingRows?: number\n /**\n * Лента пуста. По умолчанию определяется сама — по данным или по слоту; проп\n * нужен там, где потребитель знает лучше.\n */\n empty?: boolean\n /** Текст пустого состояния. Слот `#empty` сильнее. */\n emptyText?: string\n}\n\nconst props = withDefaults(defineProps<GrTimelineProps<T>>(), {\n items: undefined,\n itemKey: undefined,\n groupBy: undefined,\n // Настоящие дефолты живут в резолверах `useGrComponentProp`: объявленный здесь\n // дефолт неотличим от значения, переданного потребителем, и перебивал бы конфиг.\n groupHeadingLevel: undefined,\n layout: undefined,\n orientation: undefined,\n density: undefined,\n loading: false,\n loadingRows: 3,\n empty: undefined,\n emptyText: undefined,\n})\n\ndefineSlots<{\n /** Пункты ленты (`GrTimelineItem`), когда данные не переданы пропом. */\n default?: () => any\n /** Пункт в data-режиме. */\n item?: (props: { item: T, index: number }) => any\n /** Заголовок группы. По умолчанию — сам ключ. */\n group?: (props: { group: string, items: T[] }) => any\n /** Содержимое пустого состояния вместо текста по умолчанию. */\n empty?: () => any\n /** Заглушки загрузки вместо строк по умолчанию. */\n loading?: () => any\n}>()\n\nconst slots = useSlots()\nconst { t } = useGranularityTranslations()\n\nconst density = useGrComponentProp('GrTimeline', 'density', () => props.density, 'regular')\nconst orientation = useGrComponentProp('GrTimeline', 'orientation', () => props.orientation, 'vertical')\nconst headingLevel = useGrComponentProp('GrTimeline', 'groupHeadingLevel', () => props.groupHeadingLevel, 3)\nconst requestedLayout = useGrComponentProp('GrTimeline', 'layout', () => props.layout, 'stacked')\n\n/**\n * Горизонтальная лента знает одну раскладку: колонка времени и чередование\n * сторон — это вертикальные приёмы, у горизонтальной оси сторон нет.\n */\nconst layout = computed<GrTimelineLayout>(() => (\n orientation.value === 'horizontal' ? 'stacked' : requestedLayout.value\n))\n\nconst headingTag = computed(() => `h${headingLevel.value}`)\n\nconst dataMode = computed(() => props.items !== undefined)\n\nconst grouped = computed(() => dataMode.value && props.groupBy !== undefined)\n\nconst groups = computed(() => groupTimelineItems(props.items ?? [], props.groupBy))\n\n/** Единственная группа плоской ленты: тот же набор, что и в data-режиме без группировки. */\nconst entries = computed(() => groups.value[0]?.entries ?? [])\n\nconst resolvedEmptyText = computed(() => props.emptyText ?? t('gr.timeline.empty', 'Nothing here yet'))\n\nconst isEmpty = computed(() => {\n if (props.empty !== undefined)\n return props.empty\n\n if (dataMode.value)\n return props.items!.length === 0\n\n // `v-for` по пустому массиву оставляет фрагмент без узлов, `v-if` — комментарий,\n // и ни то ни другое пунктом ленты не является.\n return !hasMeaningfulSlotContent(slots.default?.() ?? [])\n})\n\nconst loadingRowCount = computed(() => Math.max(1, Math.trunc(props.loadingRows)))\n\nfunction keyOf(item: T, index: number): string | number {\n const key = props.itemKey\n if (typeof key === 'function')\n return key(item, index)\n if (typeof key === 'string')\n return (item as Record<string, unknown>)[key] as string | number\n\n return index\n}\n\nfunction itemsOf(group: { entries: { item: T }[] }): T[] {\n return group.entries.map(entry => entry.item)\n}\n\nprovide(GR_TIMELINE_CONTEXT, { layout, orientation, density })\n\nif (__GR_DEV__) {\n watch(\n () => [dataMode.value, Boolean(slots.item)] as const,\n ([hasItems, hasSlot]) => {\n if (hasItems && !hasSlot) {\n console.warn(\n '[granularity] GrTimeline: с `items` пункты рисует слот `#item` — без него лента пуста.',\n )\n }\n },\n { immediate: true },\n )\n\n watch(\n () => [dataMode.value, props.groupBy !== undefined] as const,\n ([hasItems, hasGroupBy]) => {\n if (hasGroupBy && !hasItems) {\n console.warn(\n '[granularity] GrTimeline: `groupBy` работает только с `items` — пункты из слота по '\n + 'умолчанию лента не разбирает и сгруппировать их ей не из чего.',\n )\n }\n },\n { immediate: true },\n )\n\n watch(\n () => [orientation.value, requestedLayout.value] as const,\n ([axis, requested]) => {\n if (axis === 'horizontal' && requested !== 'stacked') {\n console.warn(\n `[granularity] GrTimeline: layout=\"${requested}\" — вертикальная раскладка, `\n + 'в горизонтальной ленте она игнорируется.',\n )\n }\n },\n { immediate: true },\n )\n}\n</script>\n\n<template>\n <div\n data-gr-timeline\n :data-layout=\"layout\"\n :data-orientation=\"orientation\"\n :aria-busy=\"loading ? 'true' : undefined\"\n >\n <template v-if=\"loading\">\n <slot name=\"loading\">\n <div\n v-for=\"row in loadingRowCount\"\n :key=\"row\"\n data-gr-timeline-loading-row\n :class=\"loadingRowClass\"\n >\n <GrSkeleton variant=\"circle\" width=\"var(--gr-timeline-marker-size, 10px)\" />\n <GrSkeleton />\n </div>\n </slot>\n </template>\n\n <div\n v-else-if=\"isEmpty\"\n data-gr-timeline-empty\n :class=\"emptyClass\"\n >\n <slot name=\"empty\">\n {{ resolvedEmptyText }}\n </slot>\n </div>\n\n <template v-else-if=\"grouped\">\n <!--\n Группа — секция с заголовком, а не пункт-заголовок внутри списка:\n `<li>` с датой попал бы в набор событий и был бы объявлен как одно из них.\n -->\n <section\n v-for=\"group in groups\"\n :key=\"group.key\"\n data-gr-timeline-group\n >\n <div\n v-if=\"group.key\"\n data-gr-timeline-group-header\n >\n <div data-gr-timeline-rail aria-hidden=\"true\">\n <span data-gr-timeline-line />\n </div>\n\n <component\n :is=\"headingTag\"\n data-gr-timeline-group-title\n :class=\"groupTitleClass\"\n >\n <slot name=\"group\" :group=\"group.key\" :items=\"itemsOf(group)\">\n {{ group.key }}\n </slot>\n </component>\n </div>\n\n <ol\n data-gr-timeline-list\n :tabindex=\"orientation === 'horizontal' ? 0 : undefined\"\n >\n <template\n v-for=\"entry in group.entries\"\n :key=\"keyOf(entry.item, entry.index)\"\n >\n <slot name=\"item\" :item=\"entry.item\" :index=\"entry.index\" />\n </template>\n </ol>\n </section>\n </template>\n\n <ol\n v-else\n data-gr-timeline-list\n :tabindex=\"orientation === 'horizontal' ? 0 : undefined\"\n >\n <template v-if=\"dataMode\">\n <template\n v-for=\"entry in entries\"\n :key=\"keyOf(entry.item, entry.index)\"\n >\n <slot name=\"item\" :item=\"entry.item\" :index=\"entry.index\" />\n </template>\n </template>\n\n <slot v-else />\n </ol>\n </div>\n</template>\n\n<style>\n/*\n * Геометрия ленты живёт здесь, а не в утилитах, по одной причине: сторона\n * пункта в `alternate` — это `nth-child`, а обрыв оси — `last-child`, и оба\n * вопроса утилитами не выражаются. Альтернатива — регистрация пунктов в\n * контексте на `onMounted` — дала бы порядок, зависящий от порядка монтирования,\n * и разъезжалась бы на `v-if` посреди ленты.\n */\n\n[data-gr-timeline] {\n --gr-timeline-columns: auto minmax(0, 1fr);\n}\n\n[data-gr-timeline][data-layout='time'] {\n --gr-timeline-columns: var(--gr-timeline-time-width, 5.5rem) auto minmax(0, 1fr);\n}\n\n[data-gr-timeline][data-layout='alternate'] {\n --gr-timeline-columns: minmax(0, 1fr) auto minmax(0, 1fr);\n}\n\n[data-gr-timeline-list] {\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n[data-gr-timeline-item],\n[data-gr-timeline-group-header] {\n display: grid;\n grid-template-columns: var(--gr-timeline-columns);\n column-gap: var(--gr-timeline-gap, 0.75rem);\n align-items: start;\n}\n\n/*\n * Сжатие строки.\n *\n * Трек `1fr` — это `minmax(auto, 1fr)`, а его минимум равен наибольшему\n * минимальному вкладу элементов. У грид-элемента с `overflow: visible` этот\n * вклад равен min-content, то есть для строки с `white-space: nowrap` — её\n * полной ширине. Поэтому лента не сжималась, а `truncate` потребителя не\n * срабатывал: усекать было нечего, трек раздавался под текст. Явные\n * `minmax(0, …)` у треков и `min-width: 0` у элементов снимают оба минимума.\n *\n * Метка времени стоит в треке фиксированной длины, и её трек от этого не\n * меняется — но без `min-width: 0` длинная метка из слота `#time` раздаёт\n * min-content всей строки.\n */\n\n[data-gr-timeline-aside],\n[data-gr-timeline-content],\n[data-gr-timeline-group-title] {\n min-width: 0;\n}\n\n[data-gr-timeline-aside] {\n text-align: end;\n}\n\n/*\n * Ось — отрезок у каждого пункта, а не одна длинная линия: соседние отрезки\n * смыкаются сами, и мерить высоту списка не приходится.\n */\n[data-gr-timeline-rail] {\n display: flex;\n flex-direction: column;\n align-items: center;\n align-self: stretch;\n padding-top: var(--gr-timeline-marker-top, 0.35rem);\n min-width: var(--gr-timeline-marker-size, 10px);\n}\n\n[data-gr-timeline-rail] > :not([data-gr-timeline-line]) {\n flex: none;\n}\n\n[data-gr-timeline-marker] {\n box-sizing: border-box;\n width: var(--gr-timeline-marker-size, 10px);\n height: var(--gr-timeline-marker-size, 10px);\n border-width: var(--gr-timeline-marker-ring, 2px);\n border-style: solid;\n border-color: transparent;\n}\n\n[data-gr-timeline-line] {\n flex: 1 1 auto;\n width: var(--gr-timeline-axis-width, 2px);\n min-height: var(--gr-timeline-line-min, 0.75rem);\n margin-top: 0.25rem;\n /* Ровно на столько следующая точка отступает от верха своей строки: без этого\n отрезок не доходит до неё, и ось выглядит пунктиром из длинных штрихов. */\n margin-bottom: calc(-1 * var(--gr-timeline-marker-top, 0.35rem));\n background-color: var(--gr-timeline-axis-color, var(--gr-brd));\n}\n\n/*\n * Пунктиром идёт участок, ведущий **к** незавершённому событию, и всё, что за\n * ним: непройденный путь. Отрезок после точки для этого не годится — обычно\n * незавершённое событие последнее, и его отрезка не видно вовсе.\n */\n[data-gr-timeline-item]:has(+ [data-gr-timeline-item][data-pending]) [data-gr-timeline-line],\n[data-gr-timeline-item][data-pending] [data-gr-timeline-line] {\n background-color: transparent;\n background-image: repeating-linear-gradient(\n to bottom,\n var(--gr-timeline-axis-color, var(--gr-brd)) 0 var(--gr-timeline-dash, 4px),\n transparent var(--gr-timeline-dash, 4px) calc(var(--gr-timeline-dash, 4px) * 2)\n );\n}\n\n/* Хвост после последнего события — линия в никуда. */\n[data-gr-timeline-item]:last-child [data-gr-timeline-line] {\n visibility: hidden;\n}\n\n/* Между группами ось не рвётся: разрыв читался бы как две разные ленты. */\n[data-gr-timeline-group]:not(:last-of-type) [data-gr-timeline-item]:last-child [data-gr-timeline-line] {\n visibility: visible;\n}\n\n[data-gr-timeline-group-header] [data-gr-timeline-rail] {\n padding-top: 0;\n}\n\n[data-gr-timeline-group] + [data-gr-timeline-group] [data-gr-timeline-group-title] {\n padding-top: 0.5rem;\n}\n\n[data-gr-timeline-group-header] [data-gr-timeline-line] {\n margin-top: 0;\n}\n\n/* Над первым заголовком оси ещё нет — линия висела бы в воздухе. */\n[data-gr-timeline-group]:first-of-type [data-gr-timeline-group-header] [data-gr-timeline-line] {\n visibility: hidden;\n}\n\n[data-gr-timeline][data-layout='time'] [data-gr-timeline-group-header] [data-gr-timeline-rail],\n[data-gr-timeline][data-layout='alternate'] [data-gr-timeline-group-header] [data-gr-timeline-rail] {\n grid-column: 2;\n}\n\n[data-gr-timeline][data-layout='time'] [data-gr-timeline-group-title],\n[data-gr-timeline][data-layout='alternate'] [data-gr-timeline-group-title] {\n grid-column: 3;\n}\n\n[data-gr-timeline][data-layout='alternate'] [data-gr-timeline-rail] {\n grid-column: 2;\n}\n\n[data-gr-timeline][data-layout='alternate'] [data-gr-timeline-content] {\n grid-column: 3;\n}\n\n[data-gr-timeline][data-layout='alternate'] [data-gr-timeline-item]:nth-child(even) [data-gr-timeline-content] {\n grid-column: 1;\n grid-row: 1;\n text-align: end;\n}\n\n/* Двухсторонняя лента в узкой колонке нечитаема — там она односторонняя. */\n@media (max-width: 640px) {\n [data-gr-timeline][data-layout='alternate'] {\n --gr-timeline-columns: auto minmax(0, 1fr);\n }\n\n [data-gr-timeline][data-layout='alternate'] [data-gr-timeline-rail],\n [data-gr-timeline][data-layout='alternate'] [data-gr-timeline-group-header] [data-gr-timeline-rail] {\n grid-column: 1;\n }\n\n [data-gr-timeline][data-layout='alternate'] [data-gr-timeline-content],\n [data-gr-timeline][data-layout='alternate'] [data-gr-timeline-item]:nth-child(even) [data-gr-timeline-content],\n [data-gr-timeline][data-layout='alternate'] [data-gr-timeline-group-title] {\n grid-column: 2;\n text-align: start;\n }\n}\n\n[data-gr-timeline][data-orientation='horizontal'] [data-gr-timeline-list] {\n display: flex;\n flex-direction: row;\n overflow-x: auto;\n}\n\n[data-gr-timeline][data-orientation='horizontal'] [data-gr-timeline-item] {\n grid-template-columns: minmax(0, 1fr);\n min-width: var(--gr-timeline-item-min-width, 12rem);\n padding-inline-end: var(--gr-timeline-gap, 0.75rem);\n}\n\n[data-gr-timeline][data-orientation='horizontal'] [data-gr-timeline-content] {\n padding-bottom: 0;\n}\n\n[data-gr-timeline][data-orientation='horizontal'] [data-gr-timeline-rail] {\n flex-direction: row;\n align-items: center;\n align-self: auto;\n padding-top: 0;\n padding-bottom: 0.25rem;\n}\n\n[data-gr-timeline][data-orientation='horizontal'] [data-gr-timeline-line] {\n width: auto;\n height: var(--gr-timeline-axis-width, 2px);\n min-width: var(--gr-timeline-line-min, 0.75rem);\n min-height: 0;\n margin-top: 0;\n margin-inline-start: 0.25rem;\n}\n\n[data-gr-timeline][data-orientation='horizontal'] [data-gr-timeline-item]:has(+ [data-gr-timeline-item][data-pending]) [data-gr-timeline-line],\n[data-gr-timeline][data-orientation='horizontal'] [data-gr-timeline-item][data-pending] [data-gr-timeline-line] {\n background-image: repeating-linear-gradient(\n to right,\n var(--gr-timeline-axis-color, var(--gr-brd)) 0 var(--gr-timeline-dash, 4px),\n transparent var(--gr-timeline-dash, 4px) calc(var(--gr-timeline-dash, 4px) * 2)\n );\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, inject, useSlots } from 'vue'\n\nimport { GR_TIMELINE_CONTEXT } from './grTimelineContext'\nimport {\n descriptionClass,\n grTimelineDensityClass,\n grTimelineMarkerClass,\n timeClass,\n titleClass,\n type GrTimelineMarkerVariant,\n type GrTimelineTone,\n} from './grTimelineStyles'\n\nexport type { GrTimelineMarkerVariant, GrTimelineTone } from './grTimelineStyles'\n\nexport interface GrTimelineItemProps {\n /** Метка времени. Слот `#time` сильнее. */\n time?: string\n /** Машиночитаемое значение метки: попадает в `datetime` тега `<time>`. */\n datetime?: string\n /** Заголовок события. Слот `#title` сильнее. */\n title?: string\n /** Пояснение под заголовком. Слот `#description` сильнее. */\n description?: string\n /** Цвет маркера из палитры пакета. */\n tone?: GrTimelineTone\n /** Заполненная точка — событие случилось, полая — запланировано. */\n variant?: GrTimelineMarkerVariant\n /**\n * Событие ещё не произошло: точка полая, а ось пунктирна начиная с участка,\n * **ведущего** к нему, — непройденный путь. Перебивает `variant`.\n */\n pending?: boolean\n}\n\nconst props = withDefaults(defineProps<GrTimelineItemProps>(), {\n time: undefined,\n datetime: undefined,\n title: undefined,\n description: undefined,\n tone: 'neutral',\n variant: 'filled',\n pending: false,\n})\n\ndefineSlots<{\n /** Содержимое события под заголовком и описанием. */\n default?: () => any\n /** Маркер вместо точки: иконка статуса, аватар автора, номер шага. */\n marker?: () => any\n /** Метка времени. */\n time?: () => any\n /** Заголовок события. */\n title?: () => any\n /** Пояснение под заголовком. */\n description?: () => any\n}>()\n\nconst slots = useSlots()\n\n/**\n * Раскладку пункт читает из контейнера. Вне `GrTimeline` — дефолты: пункт\n * остаётся рабочей строкой, а не падает.\n */\nconst timeline = inject(GR_TIMELINE_CONTEXT, null)\n\nconst layout = computed(() => timeline?.layout.value ?? 'stacked')\nconst density = computed(() => timeline?.density.value ?? 'regular')\n\nconst markerClass = computed(() => grTimelineMarkerClass(\n props.tone,\n props.pending ? 'outlined' : props.variant,\n))\n\nconst hasTime = computed(() => !!slots.time || !!props.time)\nconst hasTitle = computed(() => !!slots.title || !!props.title)\nconst hasDescription = computed(() => !!slots.description || !!props.description)\n\n/** В `layout=\"time\"` метка уезжает в свою колонку, во всех остальных — в содержимое. */\nconst timeInAside = computed(() => layout.value === 'time')\n</script>\n\n<template>\n <li\n data-gr-timeline-item\n :data-tone=\"tone\"\n :data-pending=\"pending ? '' : undefined\"\n >\n <div\n v-if=\"timeInAside\"\n data-gr-timeline-aside\n >\n <time\n v-if=\"hasTime\"\n :datetime=\"datetime\"\n :class=\"timeClass\"\n >\n <slot name=\"time\">{{ time }}</slot>\n </time>\n </div>\n\n <!-- Ось и точка декоративны: смысл события несёт текст рядом. -->\n <div\n data-gr-timeline-rail\n aria-hidden=\"true\"\n >\n <slot name=\"marker\">\n <span data-gr-timeline-marker :class=\"markerClass\" />\n </slot>\n <span data-gr-timeline-line />\n </div>\n\n <!--\n Вертикальный отступ живёт на содержимом, а не на строке: ось растянута по\n строке грид-ячейкой, и её поля в высоту ячейки не входят — линия обрывалась\n бы, не доходя до следующей точки.\n -->\n <div data-gr-timeline-content :class=\"grTimelineDensityClass(density)\">\n <time\n v-if=\"hasTime && !timeInAside\"\n data-gr-timeline-time\n :datetime=\"datetime\"\n :class=\"timeClass\"\n >\n <slot name=\"time\">{{ time }}</slot>\n </time>\n\n <div v-if=\"hasTitle\" data-gr-timeline-title :class=\"titleClass\">\n <slot name=\"title\">\n{{ title }}\n</slot>\n </div>\n\n <div v-if=\"hasDescription\" :class=\"descriptionClass\">\n <slot name=\"description\">\n{{ description }}\n</slot>\n </div>\n\n <div v-if=\"$slots.default\" data-gr-timeline-body>\n <slot />\n </div>\n </div>\n </li>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, inject, useSlots } from 'vue'\n\nimport { GR_TIMELINE_CONTEXT } from './grTimelineContext'\nimport {\n descriptionClass,\n grTimelineDensityClass,\n grTimelineMarkerClass,\n timeClass,\n titleClass,\n type GrTimelineMarkerVariant,\n type GrTimelineTone,\n} from './grTimelineStyles'\n\nexport type { GrTimelineMarkerVariant, GrTimelineTone } from './grTimelineStyles'\n\nexport interface GrTimelineItemProps {\n /** Метка времени. Слот `#time` сильнее. */\n time?: string\n /** Машиночитаемое значение метки: попадает в `datetime` тега `<time>`. */\n datetime?: string\n /** Заголовок события. Слот `#title` сильнее. */\n title?: string\n /** Пояснение под заголовком. Слот `#description` сильнее. */\n description?: string\n /** Цвет маркера из палитры пакета. */\n tone?: GrTimelineTone\n /** Заполненная точка — событие случилось, полая — запланировано. */\n variant?: GrTimelineMarkerVariant\n /**\n * Событие ещё не произошло: точка полая, а ось пунктирна начиная с участка,\n * **ведущего** к нему, — непройденный путь. Перебивает `variant`.\n */\n pending?: boolean\n}\n\nconst props = withDefaults(defineProps<GrTimelineItemProps>(), {\n time: undefined,\n datetime: undefined,\n title: undefined,\n description: undefined,\n tone: 'neutral',\n variant: 'filled',\n pending: false,\n})\n\ndefineSlots<{\n /** Содержимое события под заголовком и описанием. */\n default?: () => any\n /** Маркер вместо точки: иконка статуса, аватар автора, номер шага. */\n marker?: () => any\n /** Метка времени. */\n time?: () => any\n /** Заголовок события. */\n title?: () => any\n /** Пояснение под заголовком. */\n description?: () => any\n}>()\n\nconst slots = useSlots()\n\n/**\n * Раскладку пункт читает из контейнера. Вне `GrTimeline` — дефолты: пункт\n * остаётся рабочей строкой, а не падает.\n */\nconst timeline = inject(GR_TIMELINE_CONTEXT, null)\n\nconst layout = computed(() => timeline?.layout.value ?? 'stacked')\nconst density = computed(() => timeline?.density.value ?? 'regular')\n\nconst markerClass = computed(() => grTimelineMarkerClass(\n props.tone,\n props.pending ? 'outlined' : props.variant,\n))\n\nconst hasTime = computed(() => !!slots.time || !!props.time)\nconst hasTitle = computed(() => !!slots.title || !!props.title)\nconst hasDescription = computed(() => !!slots.description || !!props.description)\n\n/** В `layout=\"time\"` метка уезжает в свою колонку, во всех остальных — в содержимое. */\nconst timeInAside = computed(() => layout.value === 'time')\n</script>\n\n<template>\n <li\n data-gr-timeline-item\n :data-tone=\"tone\"\n :data-pending=\"pending ? '' : undefined\"\n >\n <div\n v-if=\"timeInAside\"\n data-gr-timeline-aside\n >\n <time\n v-if=\"hasTime\"\n :datetime=\"datetime\"\n :class=\"timeClass\"\n >\n <slot name=\"time\">{{ time }}</slot>\n </time>\n </div>\n\n <!-- Ось и точка декоративны: смысл события несёт текст рядом. -->\n <div\n data-gr-timeline-rail\n aria-hidden=\"true\"\n >\n <slot name=\"marker\">\n <span data-gr-timeline-marker :class=\"markerClass\" />\n </slot>\n <span data-gr-timeline-line />\n </div>\n\n <!--\n Вертикальный отступ живёт на содержимом, а не на строке: ось растянута по\n строке грид-ячейкой, и её поля в высоту ячейки не входят — линия обрывалась\n бы, не доходя до следующей точки.\n -->\n <div data-gr-timeline-content :class=\"grTimelineDensityClass(density)\">\n <time\n v-if=\"hasTime && !timeInAside\"\n data-gr-timeline-time\n :datetime=\"datetime\"\n :class=\"timeClass\"\n >\n <slot name=\"time\">{{ time }}</slot>\n </time>\n\n <div v-if=\"hasTitle\" data-gr-timeline-title :class=\"titleClass\">\n <slot name=\"title\">\n{{ title }}\n</slot>\n </div>\n\n <div v-if=\"hasDescription\" :class=\"descriptionClass\">\n <slot name=\"description\">\n{{ description }}\n</slot>\n </div>\n\n <div v-if=\"$slots.default\" data-gr-timeline-body>\n <slot />\n </div>\n </div>\n </li>\n</template>\n"],"mappings":"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"}
@@ -1 +1 @@
1
- [data-gr-timeline]{--gr-timeline-columns:auto 1fr}[data-gr-timeline][data-layout=time]{--gr-timeline-columns:var(--gr-timeline-time-width,5.5rem) auto 1fr}[data-gr-timeline][data-layout=alternate]{--gr-timeline-columns:1fr auto 1fr}[data-gr-timeline-list]{margin:0;padding:0;list-style:none}[data-gr-timeline-item],[data-gr-timeline-group-header]{grid-template-columns:var(--gr-timeline-columns);column-gap:var(--gr-timeline-gap,.75rem);align-items:start;display:grid}[data-gr-timeline-aside]{text-align:end}[data-gr-timeline-rail]{padding-top:var(--gr-timeline-marker-top,.35rem);min-width:var(--gr-timeline-marker-size,10px);flex-direction:column;align-self:stretch;align-items:center;display:flex}[data-gr-timeline-rail]>:not([data-gr-timeline-line]){flex:none}[data-gr-timeline-marker]{box-sizing:border-box;width:var(--gr-timeline-marker-size,10px);height:var(--gr-timeline-marker-size,10px);border-width:var(--gr-timeline-marker-ring,2px);border-style:solid;border-color:#0000}[data-gr-timeline-line]{width:var(--gr-timeline-axis-width,2px);min-height:var(--gr-timeline-line-min,.75rem);margin-top:.25rem;margin-bottom:calc(-1 * var(--gr-timeline-marker-top,.35rem));background-color:var(--gr-timeline-axis-color,var(--gr-brd));flex:auto}[data-gr-timeline-item]:has(+[data-gr-timeline-item][data-pending]) [data-gr-timeline-line],[data-gr-timeline-item][data-pending] [data-gr-timeline-line]{background-color:#0000;background-image:repeating-linear-gradient(to bottom, var(--gr-timeline-axis-color,var(--gr-brd)) 0 var(--gr-timeline-dash,4px), transparent var(--gr-timeline-dash,4px) calc(var(--gr-timeline-dash,4px) * 2))}[data-gr-timeline-item]:last-child [data-gr-timeline-line]{visibility:hidden}[data-gr-timeline-group]:not(:last-of-type) [data-gr-timeline-item]:last-child [data-gr-timeline-line]{visibility:visible}[data-gr-timeline-group-header] [data-gr-timeline-rail]{padding-top:0}[data-gr-timeline-group]+[data-gr-timeline-group] [data-gr-timeline-group-title]{padding-top:.5rem}[data-gr-timeline-group-header] [data-gr-timeline-line]{margin-top:0}[data-gr-timeline-group]:first-of-type [data-gr-timeline-group-header] [data-gr-timeline-line]{visibility:hidden}[data-gr-timeline][data-layout=time] [data-gr-timeline-group-header] [data-gr-timeline-rail],[data-gr-timeline][data-layout=alternate] [data-gr-timeline-group-header] [data-gr-timeline-rail]{grid-column:2}[data-gr-timeline][data-layout=time] [data-gr-timeline-group-title],[data-gr-timeline][data-layout=alternate] [data-gr-timeline-group-title]{grid-column:3}[data-gr-timeline][data-layout=alternate] [data-gr-timeline-rail]{grid-column:2}[data-gr-timeline][data-layout=alternate] [data-gr-timeline-content]{grid-column:3}[data-gr-timeline][data-layout=alternate] [data-gr-timeline-item]:nth-child(2n) [data-gr-timeline-content]{text-align:end;grid-area:1/1}@media (width<=640px){[data-gr-timeline][data-layout=alternate]{--gr-timeline-columns:auto 1fr}[data-gr-timeline][data-layout=alternate] [data-gr-timeline-rail],[data-gr-timeline][data-layout=alternate] [data-gr-timeline-group-header] [data-gr-timeline-rail]{grid-column:1}[data-gr-timeline][data-layout=alternate] [data-gr-timeline-content],[data-gr-timeline][data-layout=alternate] [data-gr-timeline-item]:nth-child(2n) [data-gr-timeline-content],[data-gr-timeline][data-layout=alternate] [data-gr-timeline-group-title]{text-align:start;grid-column:2}}[data-gr-timeline][data-orientation=horizontal] [data-gr-timeline-list]{flex-direction:row;display:flex;overflow-x:auto}[data-gr-timeline][data-orientation=horizontal] [data-gr-timeline-item]{min-width:var(--gr-timeline-item-min-width,12rem);grid-template-columns:1fr;padding-inline-end:var(--gr-timeline-gap,.75rem)}[data-gr-timeline][data-orientation=horizontal] [data-gr-timeline-content]{padding-bottom:0}[data-gr-timeline][data-orientation=horizontal] [data-gr-timeline-rail]{flex-direction:row;align-self:auto;align-items:center;padding-top:0;padding-bottom:.25rem}[data-gr-timeline][data-orientation=horizontal] [data-gr-timeline-line]{width:auto;height:var(--gr-timeline-axis-width,2px);min-width:var(--gr-timeline-line-min,.75rem);margin-top:0;min-height:0;margin-inline-start:.25rem}[data-gr-timeline][data-orientation=horizontal] [data-gr-timeline-item]:has(+[data-gr-timeline-item][data-pending]) [data-gr-timeline-line],[data-gr-timeline][data-orientation=horizontal] [data-gr-timeline-item][data-pending] [data-gr-timeline-line]{background-image:repeating-linear-gradient(to right, var(--gr-timeline-axis-color,var(--gr-brd)) 0 var(--gr-timeline-dash,4px), transparent var(--gr-timeline-dash,4px) calc(var(--gr-timeline-dash,4px) * 2))}
1
+ [data-gr-timeline]{--gr-timeline-columns:auto minmax(0, 1fr)}[data-gr-timeline][data-layout=time]{--gr-timeline-columns:var(--gr-timeline-time-width,5.5rem) auto minmax(0, 1fr)}[data-gr-timeline][data-layout=alternate]{--gr-timeline-columns:minmax(0, 1fr) auto minmax(0, 1fr)}[data-gr-timeline-list]{margin:0;padding:0;list-style:none}[data-gr-timeline-item],[data-gr-timeline-group-header]{grid-template-columns:var(--gr-timeline-columns);column-gap:var(--gr-timeline-gap,.75rem);align-items:start;display:grid}[data-gr-timeline-aside],[data-gr-timeline-content],[data-gr-timeline-group-title]{min-width:0}[data-gr-timeline-aside]{text-align:end}[data-gr-timeline-rail]{padding-top:var(--gr-timeline-marker-top,.35rem);min-width:var(--gr-timeline-marker-size,10px);flex-direction:column;align-self:stretch;align-items:center;display:flex}[data-gr-timeline-rail]>:not([data-gr-timeline-line]){flex:none}[data-gr-timeline-marker]{box-sizing:border-box;width:var(--gr-timeline-marker-size,10px);height:var(--gr-timeline-marker-size,10px);border-width:var(--gr-timeline-marker-ring,2px);border-style:solid;border-color:#0000}[data-gr-timeline-line]{width:var(--gr-timeline-axis-width,2px);min-height:var(--gr-timeline-line-min,.75rem);margin-top:.25rem;margin-bottom:calc(-1 * var(--gr-timeline-marker-top,.35rem));background-color:var(--gr-timeline-axis-color,var(--gr-brd));flex:auto}[data-gr-timeline-item]:has(+[data-gr-timeline-item][data-pending]) [data-gr-timeline-line],[data-gr-timeline-item][data-pending] [data-gr-timeline-line]{background-color:#0000;background-image:repeating-linear-gradient(to bottom, var(--gr-timeline-axis-color,var(--gr-brd)) 0 var(--gr-timeline-dash,4px), transparent var(--gr-timeline-dash,4px) calc(var(--gr-timeline-dash,4px) * 2))}[data-gr-timeline-item]:last-child [data-gr-timeline-line]{visibility:hidden}[data-gr-timeline-group]:not(:last-of-type) [data-gr-timeline-item]:last-child [data-gr-timeline-line]{visibility:visible}[data-gr-timeline-group-header] [data-gr-timeline-rail]{padding-top:0}[data-gr-timeline-group]+[data-gr-timeline-group] [data-gr-timeline-group-title]{padding-top:.5rem}[data-gr-timeline-group-header] [data-gr-timeline-line]{margin-top:0}[data-gr-timeline-group]:first-of-type [data-gr-timeline-group-header] [data-gr-timeline-line]{visibility:hidden}[data-gr-timeline][data-layout=time] [data-gr-timeline-group-header] [data-gr-timeline-rail],[data-gr-timeline][data-layout=alternate] [data-gr-timeline-group-header] [data-gr-timeline-rail]{grid-column:2}[data-gr-timeline][data-layout=time] [data-gr-timeline-group-title],[data-gr-timeline][data-layout=alternate] [data-gr-timeline-group-title]{grid-column:3}[data-gr-timeline][data-layout=alternate] [data-gr-timeline-rail]{grid-column:2}[data-gr-timeline][data-layout=alternate] [data-gr-timeline-content]{grid-column:3}[data-gr-timeline][data-layout=alternate] [data-gr-timeline-item]:nth-child(2n) [data-gr-timeline-content]{text-align:end;grid-area:1/1}@media (width<=640px){[data-gr-timeline][data-layout=alternate]{--gr-timeline-columns:auto minmax(0, 1fr)}[data-gr-timeline][data-layout=alternate] [data-gr-timeline-rail],[data-gr-timeline][data-layout=alternate] [data-gr-timeline-group-header] [data-gr-timeline-rail]{grid-column:1}[data-gr-timeline][data-layout=alternate] [data-gr-timeline-content],[data-gr-timeline][data-layout=alternate] [data-gr-timeline-item]:nth-child(2n) [data-gr-timeline-content],[data-gr-timeline][data-layout=alternate] [data-gr-timeline-group-title]{text-align:start;grid-column:2}}[data-gr-timeline][data-orientation=horizontal] [data-gr-timeline-list]{flex-direction:row;display:flex;overflow-x:auto}[data-gr-timeline][data-orientation=horizontal] [data-gr-timeline-item]{min-width:var(--gr-timeline-item-min-width,12rem);grid-template-columns:minmax(0,1fr);padding-inline-end:var(--gr-timeline-gap,.75rem)}[data-gr-timeline][data-orientation=horizontal] [data-gr-timeline-content]{padding-bottom:0}[data-gr-timeline][data-orientation=horizontal] [data-gr-timeline-rail]{flex-direction:row;align-self:auto;align-items:center;padding-top:0;padding-bottom:.25rem}[data-gr-timeline][data-orientation=horizontal] [data-gr-timeline-line]{width:auto;height:var(--gr-timeline-axis-width,2px);min-width:var(--gr-timeline-line-min,.75rem);margin-top:0;min-height:0;margin-inline-start:.25rem}[data-gr-timeline][data-orientation=horizontal] [data-gr-timeline-item]:has(+[data-gr-timeline-item][data-pending]) [data-gr-timeline-line],[data-gr-timeline][data-orientation=horizontal] [data-gr-timeline-item][data-pending] [data-gr-timeline-line]{background-image:repeating-linear-gradient(to right, var(--gr-timeline-axis-color,var(--gr-brd)) 0 var(--gr-timeline-dash,4px), transparent var(--gr-timeline-dash,4px) calc(var(--gr-timeline-dash,4px) * 2))}
@@ -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-BAy4APOX.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-DCn6fgEs.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-BAy4APOX.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-DCn6fgEs.js";
2
2
  export { GRANULARITY_PROVIDER_ID, createGranularityProvider, granularityProvider as default, granularityProvider, granularityComponentConfigs, granularityDefaultThemes, granularityThemeNames };
package/dist/index.js CHANGED
@@ -145,7 +145,7 @@ import { n as grLoadingSafelist, t as grLoadingConfig } from "./chunks/config-Bj
145
145
  import "./components/GrLoading/index.js";
146
146
  import { n as grModalSafelist, t as grModalConfig } from "./chunks/config-mlheIMtV.js";
147
147
  import "./components/GrModal/index.js";
148
- import { t as GrNavbar_default } from "./components/GrNavbar/chunks/GrNavbar-_rtHKwTf.js";
148
+ import { t as GrNavbar_default } from "./components/GrNavbar/chunks/GrNavbar-Dr8e5kW-.js";
149
149
  import { n as grNavbarSafelist, t as grNavbarConfig } from "./chunks/config-2ZHRsdOc.js";
150
150
  import { n as grNumberInputSafelist, t as grNumberInputConfig } from "./chunks/config-MmPym-Z72.js";
151
151
  import { t as GrNumberInput_default } from "./components/GrNumberInput/chunks/GrNumberInput-DQ59YICu.js";
@@ -192,8 +192,8 @@ import { n as grTableSafelist, t as grTableConfig } from "./chunks/config-DCq4Jy
192
192
  import "./components/GrTable/index.js";
193
193
  import { n as GrTabPanels_default, t as GrTabPanel_default } from "./components/GrTabPanels/chunks/GrTabPanels-BZQS8-JM.js";
194
194
  import { t as grTabPanelsConfig } from "./chunks/config-eXB87DOi.js";
195
- import { n as grTabsSafelist, t as grTabsConfig } from "./chunks/config-_QF0O1vq.js";
196
- import { t as GrTabs_default } from "./components/GrTabs/chunks/GrTabs-UsO4j8zv.js";
195
+ import { t as GrTabs_default } from "./components/GrTabs/chunks/GrTabs-CT2nk_HV.js";
196
+ import { n as grTabsSafelist, t as grTabsConfig } from "./chunks/config-CMNfZO7S.js";
197
197
  import { n as grTextareaSafelist, t as grTextareaConfig } from "./chunks/config-BhfNgyWL.js";
198
198
  import "./components/GrTextarea/index.js";
199
199
  import { n as GrTimeline_default, t as GrTimelineItem_default } from "./components/GrTimeline/chunks/GrTimeline-BiHpArsg.js";
package/dist/theme.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as grThemeTokens, n as grDerivedTokens, t as grComponentTokens } from "./chunks/generated-Bk-f6NsN.js";
1
+ import { a as grThemeTokens, n as grDerivedTokens, t as grComponentTokens } from "./chunks/generated-BcfXQY4j.js";
2
2
  //#region src/theme/color.ts
3
3
  function parseHex(value) {
4
4
  const match = value.trim().match(/^#([\da-f]{3}|[\da-f]{6})$/i);
package/dist/tokens.js CHANGED
@@ -1,2 +1,2 @@
1
- import { a as grThemeTokens, i as grThemeNames, n as grDerivedTokens, r as grFoundationTokens, t as grComponentTokens } from "./chunks/generated-Bk-f6NsN.js";
1
+ import { a as grThemeTokens, i as grThemeNames, n as grDerivedTokens, r as grFoundationTokens, t as grComponentTokens } from "./chunks/generated-BcfXQY4j.js";
2
2
  export { grComponentTokens, grDerivedTokens, grFoundationTokens, grThemeNames, grThemeTokens };
@@ -1,3 +1,4 @@
1
+ import { type GrComponentSize } from '../GrConfigProvider/context';
1
2
  /**
2
3
  * GrNavbar — верхняя панель приложения (header) с тремя зонами: слева заголовок
3
4
  * и кнопка меню, по центру — свободная зона (поиск, хлебные крошки), справа —
@@ -10,6 +11,16 @@ export interface GrNavbarProps {
10
11
  /** Заголовок строкой. Слот `#title` сильнее и позволяет обойтись без пропа. */
11
12
  title?: string;
12
13
  showMenuButton?: boolean;
14
+ /**
15
+ * Ступень кнопки меню — и только её: остальное в шапке принадлежит
16
+ * потребителю, а высоту ряда задаёт `--gr-navbar-height`.
17
+ *
18
+ * Фолбэк — `sm` (32px), а не общий для пакета `md`: кнопка была этого размера
19
+ * с самого начала, и подъём по умолчанию сдвинул бы шапку у всех. Нужен
20
+ * тач-таргет — `size="lg"` даёт 44px, ровно то, чего требуют WCAG 2.5.5 и
21
+ * Apple HIG; в 56px ряда он помещается.
22
+ */
23
+ size?: GrComponentSize;
13
24
  /** Extra classes applied to the menu button wrapper (e.g. `sm:hidden`). */
14
25
  menuButtonClass?: string;
15
26
  /**
@@ -34,6 +45,7 @@ type __VLS_Slots = {
34
45
  declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPropTypes<__VLS_WithDefaults<__VLS_TypePropsToOption<GrNavbarProps>, {
35
46
  title: undefined;
36
47
  showMenuButton: boolean;
48
+ size: undefined;
37
49
  menuButtonClass: string;
38
50
  sticky: boolean;
39
51
  }>>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
@@ -41,11 +53,13 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
41
53
  }, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<__VLS_WithDefaults<__VLS_TypePropsToOption<GrNavbarProps>, {
42
54
  title: undefined;
43
55
  showMenuButton: boolean;
56
+ size: undefined;
44
57
  menuButtonClass: string;
45
58
  sticky: boolean;
46
59
  }>>> & Readonly<{
47
60
  onMenu?: (() => any) | undefined;
48
61
  }>, {
62
+ size: GrComponentSize;
49
63
  title: string;
50
64
  sticky: boolean;
51
65
  showMenuButton: boolean;
@@ -0,0 +1,10 @@
1
+ import type { GrComponentSize } from '../GrConfigProvider/context';
2
+ /** Пропы `GrNavbar`, настраиваемые глобально через `componentDefaults`. */
3
+ export interface GrNavbarConfigurableProps {
4
+ size: GrComponentSize;
5
+ }
6
+ declare module '../../composables/useGrComponentConfig' {
7
+ interface GrComponentDefaultsRegistry {
8
+ GrNavbar: GrNavbarConfigurableProps;
9
+ }
10
+ }
@@ -1,6 +1,7 @@
1
1
  export { default } from './GrNavbar.vue';
2
2
  export { default as GrNavbar } from './GrNavbar.vue';
3
3
  export type { GrNavbarProps } from './GrNavbar.vue';
4
+ export type { GrNavbarConfigurableProps } from './defaults';
4
5
  export { grNavbarConfig } from './config';
5
6
  export { grNavbarSafelist } from './safelist';
6
7
  export type { GrNavbarEmits } from './GrNavbar.vue';
@@ -0,0 +1,20 @@
1
+ /** С какой стороны у скроллера осталось невидимое содержимое. */
2
+ export type GrScrollOverflow = 'none' | 'start' | 'end' | 'both';
3
+ /**
4
+ * Состояние переполнения скроллера — по трём числам, без DOM.
5
+ *
6
+ * Отдельным модулем, потому что в jsdom раскладки нет: `scrollWidth` и
7
+ * `clientWidth` там нули, и через смонтированный компонент ни одна ветка не
8
+ * проверяется. Здесь она проверяется вся.
9
+ *
10
+ * `scrollStart` — расстояние от начала ряда, а не `scrollLeft`. Это не
11
+ * педантизм: в RTL современные браузеры отсчитывают `scrollLeft` от правого
12
+ * края и уводят его в минус, поэтому физическое «слева» и логическое «в начале»
13
+ * там не совпадают. Модуль знает только про начало и конец, а какой это край
14
+ * экрана — дело CSS.
15
+ *
16
+ * Запас в пиксель — как в {@link titleWhenTruncated}: браузеры округляют
17
+ * размеры вверх, и у ряда, влезающего целиком, разница бывает единичной.
18
+ * Без запаса затухание висело бы на ровном месте.
19
+ */
20
+ export declare function resolveScrollOverflow(scrollStart: number, scrollSize: number, clientSize: number): GrScrollOverflow;
@@ -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.34.1",
4
+ "version": "0.35.0",
5
5
  "framework": "vue",
6
6
  "js-types-syntax": "typescript",
7
7
  "description-markup": "markdown",
@@ -6565,6 +6565,15 @@
6565
6565
  "type": "string | undefined"
6566
6566
  }
6567
6567
  },
6568
+ {
6569
+ "name": "size",
6570
+ "description": "Ступень кнопки меню — и только её: остальное в шапке принадлежит потребителю, а высоту ряда задаёт `--gr-navbar-height`. Фолбэк — `sm` (32px), а не общий для пакета `md`: кнопка была этого размера с самого начала, и подъём по умолчанию сдвинул бы шапку у всех. Нужен тач-таргет — `size=\"lg\"` даёт 44px, ровно то, чего требуют WCAG 2.5.5 и Apple HIG; в 56px ряда он помещается.",
6571
+ "default": "undefined",
6572
+ "value": {
6573
+ "kind": "expression",
6574
+ "type": "\"xs\" | \"sm\" | \"md\" | \"lg\" | undefined"
6575
+ }
6576
+ },
6568
6577
  {
6569
6578
  "name": "showMenuButton",
6570
6579
  "default": "false",
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.34.1",
4
+ "version": "0.35.0",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": {
7
7
  "name": "Evgeniy Fureev",
@@ -1 +0,0 @@
1
- {"version":3,"file":"config-_QF0O1vq.js","names":[],"sources":["../../src/components/GrTabs/grTabsStyles.ts","../../src/components/GrTabs/safelist.ts","../../src/components/GrTabs/config.ts"],"sourcesContent":["import type { GrComponentSize } from '../shared/sizes'\n\nexport type GrTabsSize = GrComponentSize\n\n/**\n * `pills` — вкладки-таблетки в общей обойме, `line` — классический ряд с\n * подчёркиванием активной. Имя `pills` общее с `GrSegmented`: у компонентов\n * одна роль, и вид с таким названием у них совпадает.\n */\nexport const GR_TABS_VARIANTS = ['pills', 'line'] as const\nexport type GrTabsVariant = typeof GR_TABS_VARIANTS[number]\n\nexport type GrTabsOrientation = 'horizontal' | 'vertical'\n\n/** Высота вкладки повторяет шкалу `GrButton`: вкладки часто стоят с ним в один ряд. */\nexport const tabSizes: Record<GrTabsSize, string> = {\n xs: 'h-7 px-2 text-[length:var(--gr-text-xs)] leading-[var(--gr-leading-xs)]',\n sm: 'h-8 px-2.5 text-[length:var(--gr-text-xs)] leading-[var(--gr-leading-xs)]',\n md: 'h-9 px-3 text-[length:var(--gr-text-sm)] leading-[var(--gr-leading-sm)]',\n lg: 'h-10 px-4 text-[length:var(--gr-text-base)] leading-[var(--gr-leading-base)]',\n}\n\n/** Счётчик у вкладки: на ступень мельче подписи. */\nexport const tabBadgeSizes: Record<GrTabsSize, string> = {\n xs: 'text-[length:var(--gr-text-2xs)] leading-[var(--gr-leading-2xs)] px-1 py-0.5',\n sm: 'text-[length:var(--gr-text-2xs)] leading-[var(--gr-leading-2xs)] px-1.5 py-0.5',\n md: 'text-[length:var(--gr-text-2xs)] leading-[var(--gr-leading-2xs)] px-1.5 py-0.5',\n lg: 'text-[length:var(--gr-text-xs)] leading-[var(--gr-leading-xs)] px-2 py-0.5',\n}\n\n/** Обойма вокруг вкладок: на мелких ступенях рамка съедала бы высоту. */\nexport const tablistSizes: Record<GrTabsSize, string> = {\n xs: 'gap-0.5 p-0.5',\n sm: 'gap-1 p-1',\n md: 'gap-1 p-1',\n lg: 'gap-1 p-1.5',\n}\n\n/** `line` живёт без обоймы, поэтому и без внутренних отступов. */\nexport const tablistLineSizes: Record<GrTabsSize, string> = {\n xs: 'gap-1',\n sm: 'gap-2',\n md: 'gap-2',\n lg: 'gap-3',\n}\n\nexport const tablistBase = 'inline-flex max-w-full'\n\nexport const tablistVariants: Record<GrTabsVariant, string> = {\n pills: 'rounded-[var(--gr-radius-lg)] border border-[var(--gr-brd)] bg-[var(--gr-muted)]',\n line: 'border-b border-[var(--gr-brd)]',\n}\n\n/**\n * Вертикальный `line` подчёркивает вкладку сбоку, а не снизу: линия должна идти\n * вдоль списка, иначе она отделяет вкладки друг от друга вместо ряда целиком.\n */\nexport const tablistVerticalVariants: Record<GrTabsVariant, string> = {\n pills: '',\n line: 'border-b-0 border-r border-[var(--gr-brd)]',\n}\n\n/**\n * Переполнение прокручивается, а не переносится строкой: перенос ломает ряд и\n * уводит вкладки под панель. Полоса прокрутки скрыта — ряд ведёт клавиатура и\n * автоскролл к активной вкладке.\n */\nexport const tablistScrollClass = 'overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden'\n\n/** Вертикальный ряд — колонка; прокрутки ему не нужно: он растёт вниз. */\nexport const tablistColumnClass = 'flex-col'\n\nexport const tabBase = 'shrink-0 font-600 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--gr-ring)]'\n\n// Цвет подчёркивания задаёт только состояние: `border-transparent` в базе имеет\n// ту же специфичность, что и цвет активной вкладки, и побеждал бы через раз —\n// по порядку правил в сгенерированном CSS, а не по порядку классов в атрибуте.\nexport const tabVariantBase: Record<GrTabsVariant, string> = {\n pills: 'rounded-[var(--gr-radius-md)]',\n line: 'rounded-[var(--gr-radius-md)] rounded-b-[var(--gr-radius-none)] border-b-2 -mb-px',\n}\n\nexport const tabStateClasses: Record<GrTabsVariant, { active: string, idle: string, disabled: string }> = {\n pills: {\n active: 'bg-[var(--gr-card)] text-[var(--gr-fg)] border border-[var(--gr-brd)]',\n idle: 'text-[var(--gr-muted-fg)] hover:text-[var(--gr-fg)] hover:bg-[color-mix(in_srgb,var(--gr-card)_70%,transparent)]',\n disabled: 'cursor-not-allowed text-[var(--gr-disabled-fg)]',\n },\n line: {\n active: 'border-[var(--gr-primary)] text-[var(--gr-fg)]',\n idle: 'border-transparent text-[var(--gr-muted-fg)] hover:text-[var(--gr-fg)] hover:border-[var(--gr-brd)]',\n disabled: 'border-transparent cursor-not-allowed text-[var(--gr-disabled-fg)]',\n },\n}\n\nexport const tabIconClass = 'h-4 w-4 shrink-0'\n\nexport const tabContentClass = 'inline-flex items-center gap-2'\n\n/**\n * Крестик закрытия. Кнопкой он быть не может: `role=\"tab\"` объявляет потомков\n * презентационными, и вложенный интерактив теряется у скринридера (axe:\n * `nested-interactive`). Поэтому это `<span>`, а клик по нему разбирает\n * обработчик самой вкладки.\n *\n * Своего фокус-кольца у него нет по той же причине — в таб-порядке он не стоит,\n * с клавиатуры закрытие идёт по `Delete`/`Backspace`.\n */\nexport const tabCloseBase = 'inline-flex items-center justify-center shrink-0 rounded-[var(--gr-radius-sm)] text-[var(--gr-muted-fg)] transition-colors hover:bg-[var(--gr-muted)] hover:text-[var(--gr-fg)]'\n\nexport const tabCloseSizes: Record<GrTabsSize, string> = {\n xs: 'h-4 w-4',\n sm: 'h-4 w-4',\n md: 'h-5 w-5',\n lg: 'h-5 w-5',\n}\n\nexport const tabCloseIconSizes: Record<GrTabsSize, string> = {\n xs: 'h-3 w-3',\n sm: 'h-3 w-3',\n md: 'h-4 w-4',\n lg: 'h-4 w-4',\n}\n\n/** Пустой ряд держит высоту вкладки: иначе соседние блоки прыгают при закрытии последней. */\nexport const tabsEmptyBase = 'inline-flex items-center text-[var(--gr-muted-fg)]'\n\nexport const tabBadgeBase = 'rounded-[var(--gr-radius-full)] bg-[var(--gr-secondary)] text-[var(--gr-secondary-fg)]'\n\nexport function grTabsListClass(options: {\n variant: GrTabsVariant\n size: GrTabsSize\n orientation: GrTabsOrientation\n}): string {\n const vertical = options.orientation === 'vertical'\n\n return [\n tablistBase,\n tablistVariants[options.variant],\n vertical ? tablistVerticalVariants[options.variant] : '',\n options.variant === 'pills' ? tablistSizes[options.size] : tablistLineSizes[options.size],\n vertical ? tablistColumnClass : tablistScrollClass,\n ].filter(Boolean).join(' ')\n}\n\nexport function grTabsTabClass(options: {\n variant: GrTabsVariant\n size: GrTabsSize\n active: boolean\n disabled: boolean\n}): string {\n const state = tabStateClasses[options.variant]\n\n return [\n tabBase,\n tabVariantBase[options.variant],\n tabSizes[options.size],\n options.disabled ? state.disabled : options.active ? state.active : state.idle,\n ].join(' ')\n}\n\nexport function grTabsBadgeClass(size: GrTabsSize): string {\n return [tabBadgeBase, tabBadgeSizes[size]].join(' ')\n}\n\nexport function grTabsCloseClass(size: GrTabsSize): string {\n return [tabCloseBase, tabCloseSizes[size]].join(' ')\n}\n\nexport function grTabsEmptyClass(size: GrTabsSize): string {\n return [tabsEmptyBase, tabSizes[size]].join(' ')\n}\n","import { splitClassTokens } from '../shared/classTokens'\nimport {\n tabBadgeBase,\n tabBadgeSizes,\n tabBase,\n tabCloseBase,\n tabCloseIconSizes,\n tabCloseSizes,\n tabContentClass,\n tabIconClass,\n tablistBase,\n tablistColumnClass,\n tablistLineSizes,\n tablistScrollClass,\n tablistSizes,\n tablistVariants,\n tablistVerticalVariants,\n tabsEmptyBase,\n tabSizes,\n tabStateClasses,\n tabVariantBase,\n} from './grTabsStyles'\n\nexport const grTabsSafelist = [...new Set([\n ...Object.values(tabSizes).flatMap(splitClassTokens),\n ...Object.values(tabBadgeSizes).flatMap(splitClassTokens),\n ...Object.values(tabCloseSizes).flatMap(splitClassTokens),\n ...Object.values(tabCloseIconSizes).flatMap(splitClassTokens),\n ...Object.values(tablistSizes).flatMap(splitClassTokens),\n ...Object.values(tablistLineSizes).flatMap(splitClassTokens),\n ...Object.values(tablistVariants).flatMap(splitClassTokens),\n ...Object.values(tablistVerticalVariants).flatMap(splitClassTokens),\n ...Object.values(tabVariantBase).flatMap(splitClassTokens),\n ...Object.values(tabStateClasses).flatMap(state => [\n ...splitClassTokens(state.active),\n ...splitClassTokens(state.idle),\n ...splitClassTokens(state.disabled),\n ]),\n ...splitClassTokens(tablistBase),\n ...splitClassTokens(tablistScrollClass),\n ...splitClassTokens(tablistColumnClass),\n ...splitClassTokens(tabBase),\n ...splitClassTokens(tabBadgeBase),\n ...splitClassTokens(tabContentClass),\n ...splitClassTokens(tabIconClass),\n ...splitClassTokens(tabCloseBase),\n ...splitClassTokens(tabsEmptyBase),\n])]\n","import { defineGranularComponent } from '@feugene/unocss-preset-granular/contract'\n\nimport { grTabsSafelist } from './safelist'\n\nexport const grTabsConfig = defineGranularComponent(import.meta.url, {\n name: 'GrTabs',\n safelist: grTabsSafelist,\n})\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"GrNavbar-_rtHKwTf.js","names":[],"sources":["../../../../src/components/GrNavbar/GrNavbar.vue","../../../../src/components/GrNavbar/GrNavbar.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nimport IconMenu from '~icons/lucide/menu'\n\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport GrButton from '../GrButton/GrButton.vue'\nimport GrIcon from '../GrIcon/GrIcon.vue'\n\nimport {\n grNavbarRootClass,\n navbarCenterClass,\n navbarRightAlignClass,\n navbarSideClass,\n navbarSideGrowClass,\n navbarTitleClass,\n} from './grNavbarStyles'\n\n/**\n * GrNavbar — верхняя панель приложения (header) с тремя зонами: слева заголовок\n * и кнопка меню, по центру — свободная зона (поиск, хлебные крошки), справа —\n * действия и аватар.\n *\n * - Корень — `<header>` (landmark), без собственного `role`.\n * - `aria-label` кнопки меню локализуется через `useGranularityTranslations`.\n */\nexport interface GrNavbarProps {\n /** Заголовок строкой. Слот `#title` сильнее и позволяет обойтись без пропа. */\n title?: string\n showMenuButton?: boolean\n /** Extra classes applied to the menu button wrapper (e.g. `sm:hidden`). */\n menuButtonClass?: string\n /**\n * Панель прилипает к верху при прокрутке. Слой — `--gr-z-navbar`: он ниже\n * якорных панелей, чтобы открытый список не уезжал под шапку.\n */\n sticky?: boolean\n}\n\nexport interface GrNavbarEmits {\n (e: 'menu'): void\n}\n\nconst props = withDefaults(defineProps<GrNavbarProps>(), {\n title: undefined,\n showMenuButton: false,\n menuButtonClass: '',\n sticky: false,\n})\n\nconst slots = defineSlots<{\n /** Заголовок целиком — вместо строки `title`. */\n title?: () => unknown\n /** Зона сразу после заголовка: вкладки, переключатель раздела. */\n left?: () => unknown\n /** Центральная зона: поиск, хлебные крошки. */\n center?: () => unknown\n /** Правая зона: действия, аватар. */\n default?: () => unknown\n}>()\n\nconst emit = defineEmits<GrNavbarEmits>()\n\nconst { t } = useGranularityTranslations()\n\nconst menuAriaLabel = computed(() => t('gr.navbar.openMenu', 'Open menu'))\n\n// Пустой блок заголовка съедал бы отступ ряда, поэтому рендерим его только при\n// наличии содержимого — проп `title` для этого не обязателен.\nconst hasTitle = computed(() => Boolean(props.title || slots.title))\n\nconst rootClass = computed(() => grNavbarRootClass(props.sticky))\n\n// С центральной зоной боковые делят остаток поровну — иначе «центр» уезжает\n// вслед за более широким боком. Без неё правая просто прижата к краю.\nconst hasCenter = computed(() => Boolean(slots.center))\n\nconst sideClass = computed(() => [navbarSideClass, hasCenter.value ? navbarSideGrowClass : ''])\nconst rightClass = computed(() => [\n navbarSideClass,\n hasCenter.value ? `${navbarSideGrowClass} ${navbarRightAlignClass}` : 'ml-auto',\n])\n</script>\n\n<template>\n <header\n data-gr-navbar\n :data-sticky=\"sticky ? 'true' : undefined\"\n :class=\"rootClass\"\n >\n <div data-gr-navbar-left :class=\"sideClass\">\n <GrButton\n v-if=\"showMenuButton\"\n data-gr-navbar-menu\n variant=\"ghost\"\n size=\"sm\"\n square\n :aria-label=\"menuAriaLabel\"\n :class=\"menuButtonClass\"\n @click=\"emit('menu')\"\n >\n <GrIcon :size=\"16\">\n <IconMenu aria-hidden=\"true\" />\n </GrIcon>\n </GrButton>\n\n <div v-if=\"hasTitle\" data-gr-navbar-title :class=\"navbarTitleClass\">\n <slot name=\"title\">\n {{ title }}\n </slot>\n </div>\n\n <slot name=\"left\" />\n </div>\n\n <div v-if=\"hasCenter\" data-gr-navbar-center :class=\"navbarCenterClass\">\n <slot name=\"center\" />\n </div>\n\n <div data-gr-navbar-right :class=\"rightClass\">\n <slot />\n </div>\n </header>\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nimport IconMenu from '~icons/lucide/menu'\n\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport GrButton from '../GrButton/GrButton.vue'\nimport GrIcon from '../GrIcon/GrIcon.vue'\n\nimport {\n grNavbarRootClass,\n navbarCenterClass,\n navbarRightAlignClass,\n navbarSideClass,\n navbarSideGrowClass,\n navbarTitleClass,\n} from './grNavbarStyles'\n\n/**\n * GrNavbar — верхняя панель приложения (header) с тремя зонами: слева заголовок\n * и кнопка меню, по центру — свободная зона (поиск, хлебные крошки), справа —\n * действия и аватар.\n *\n * - Корень — `<header>` (landmark), без собственного `role`.\n * - `aria-label` кнопки меню локализуется через `useGranularityTranslations`.\n */\nexport interface GrNavbarProps {\n /** Заголовок строкой. Слот `#title` сильнее и позволяет обойтись без пропа. */\n title?: string\n showMenuButton?: boolean\n /** Extra classes applied to the menu button wrapper (e.g. `sm:hidden`). */\n menuButtonClass?: string\n /**\n * Панель прилипает к верху при прокрутке. Слой — `--gr-z-navbar`: он ниже\n * якорных панелей, чтобы открытый список не уезжал под шапку.\n */\n sticky?: boolean\n}\n\nexport interface GrNavbarEmits {\n (e: 'menu'): void\n}\n\nconst props = withDefaults(defineProps<GrNavbarProps>(), {\n title: undefined,\n showMenuButton: false,\n menuButtonClass: '',\n sticky: false,\n})\n\nconst slots = defineSlots<{\n /** Заголовок целиком — вместо строки `title`. */\n title?: () => unknown\n /** Зона сразу после заголовка: вкладки, переключатель раздела. */\n left?: () => unknown\n /** Центральная зона: поиск, хлебные крошки. */\n center?: () => unknown\n /** Правая зона: действия, аватар. */\n default?: () => unknown\n}>()\n\nconst emit = defineEmits<GrNavbarEmits>()\n\nconst { t } = useGranularityTranslations()\n\nconst menuAriaLabel = computed(() => t('gr.navbar.openMenu', 'Open menu'))\n\n// Пустой блок заголовка съедал бы отступ ряда, поэтому рендерим его только при\n// наличии содержимого — проп `title` для этого не обязателен.\nconst hasTitle = computed(() => Boolean(props.title || slots.title))\n\nconst rootClass = computed(() => grNavbarRootClass(props.sticky))\n\n// С центральной зоной боковые делят остаток поровну — иначе «центр» уезжает\n// вслед за более широким боком. Без неё правая просто прижата к краю.\nconst hasCenter = computed(() => Boolean(slots.center))\n\nconst sideClass = computed(() => [navbarSideClass, hasCenter.value ? navbarSideGrowClass : ''])\nconst rightClass = computed(() => [\n navbarSideClass,\n hasCenter.value ? `${navbarSideGrowClass} ${navbarRightAlignClass}` : 'ml-auto',\n])\n</script>\n\n<template>\n <header\n data-gr-navbar\n :data-sticky=\"sticky ? 'true' : undefined\"\n :class=\"rootClass\"\n >\n <div data-gr-navbar-left :class=\"sideClass\">\n <GrButton\n v-if=\"showMenuButton\"\n data-gr-navbar-menu\n variant=\"ghost\"\n size=\"sm\"\n square\n :aria-label=\"menuAriaLabel\"\n :class=\"menuButtonClass\"\n @click=\"emit('menu')\"\n >\n <GrIcon :size=\"16\">\n <IconMenu aria-hidden=\"true\" />\n </GrIcon>\n </GrButton>\n\n <div v-if=\"hasTitle\" data-gr-navbar-title :class=\"navbarTitleClass\">\n <slot name=\"title\">\n {{ title }}\n </slot>\n </div>\n\n <slot name=\"left\" />\n </div>\n\n <div v-if=\"hasCenter\" data-gr-navbar-center :class=\"navbarCenterClass\">\n <slot name=\"center\" />\n </div>\n\n <div data-gr-navbar-right :class=\"rightClass\">\n <slot />\n </div>\n </header>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2CA,MAAM,QAAQ;EAOd,MAAM,QAAQ,SAAA;EAWd,MAAM,OAAO;EAEb,MAAM,EAAE,MAAM,2BAA2B;EAEzC,MAAM,gBAAgB,eAAe,EAAE,sBAAsB,WAAW,CAAC;EAIzE,MAAM,WAAW,eAAe,QAAQ,MAAM,SAAS,MAAM,KAAK,CAAC;EAEnE,MAAM,YAAY,eAAe,kBAAkB,MAAM,MAAM,CAAC;EAIhE,MAAM,YAAY,eAAe,QAAQ,MAAM,MAAM,CAAC;EAEtD,MAAM,YAAY,eAAe,CAAC,iBAAiB,UAAU,QAAQ,sBAAsB,EAAE,CAAC;EAC9F,MAAM,aAAa,eAAe,CAChC,iBACA,UAAU,QAAQ,GAAG,oBAAoB,GAAG,0BAA0B,SACxE,CAAC;;uBAIC,mBAqCS,UAAA;IApCP,kBAAA;IACC,eAAa,QAAA,SAAM,SAAY,KAAA;IAC/B,OAAK,eAAE,UAAA,KAAS;;IAEjB,mBAuBM,OAAA;KAvBD,uBAAA;KAAqB,OAAK,eAAE,UAAA,KAAS;;KAEhC,QAAA,kBAAA,UAAA,GADR,YAaW,kBAAA;;MAXT,uBAAA;MACA,SAAQ;MACR,MAAK;MACL,QAAA;MACC,cAAY,cAAA;MACZ,OAAK,eAAE,QAAA,eAAe;MACtB,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,KAAI,MAAA;;6BAIH,CAFT,YAES,gBAAA,EAFA,MAAM,GAAE,GAAA;8BACgB,CAA/B,YAA+B,MAAA,YAAA,GAAA,EAArB,eAAY,OAAM,CAAA,CAAA,CAAA;;;;;KAIrB,SAAA,SAAA,UAAA,GAAX,mBAIM,OAAA;;MAJe,wBAAA;MAAsB,OAAK,eAAE,MAAA,gBAAA,CAAgB;SAChE,WAEO,KAAA,QAAA,SAAA,CAAA,SAAA,CAAA,gBAAA,gBADF,QAAA,KAAK,GAAA,CAAA,CAAA,CAAA,CAAA,GAAA,CAAA,KAAA,mBAAA,IAAA,IAAA;KAIZ,WAAoB,KAAA,QAAA,MAAA;;IAGX,UAAA,SAAA,UAAA,GAAX,mBAEM,OAAA;;KAFgB,yBAAA;KAAuB,OAAK,eAAE,MAAA,iBAAA,CAAiB;QACnE,WAAsB,KAAA,QAAA,QAAA,CAAA,GAAA,CAAA,KAAA,mBAAA,IAAA,IAAA;IAGxB,mBAEM,OAAA;KAFD,wBAAA;KAAsB,OAAK,eAAE,WAAA,KAAU;QAC1C,WAAQ,KAAA,QAAA,SAAA,CAAA,GAAA,CAAA"}