@feugene/granularity 0.43.0 → 0.44.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 (47) hide show
  1. package/README.md +3 -3
  2. package/dist/chunks/config-BkQ2jvXv.js +49 -0
  3. package/dist/chunks/config-BkQ2jvXv.js.map +1 -0
  4. package/dist/chunks/{en-C_QkUTRL.js → en-DiHzFact.js} +3 -1
  5. package/dist/chunks/en-DiHzFact.js.map +1 -0
  6. package/dist/chunks/{es-DemSEhZE.js → es-1ZL6NtkB.js} +3 -1
  7. package/dist/chunks/es-1ZL6NtkB.js.map +1 -0
  8. package/dist/chunks/{es-B4t_3Wnh.js → es-CeKkMxZ_.js} +3 -3
  9. package/dist/chunks/{es-B4t_3Wnh.js.map → es-CeKkMxZ_.js.map} +1 -1
  10. package/dist/chunks/{generated-VcxJTo_q.js → generated-Hxu_sWPs.js} +28 -0
  11. package/dist/chunks/{generated-VcxJTo_q.js.map → generated-Hxu_sWPs.js.map} +1 -1
  12. package/dist/chunks/{granular-provider-BohXQcNt.js → granular-provider-CCNbQa5U.js} +2 -0
  13. package/dist/chunks/granular-provider-CCNbQa5U.js.map +1 -0
  14. package/dist/chunks/{ru-CLUErjhY.js → ru-Dr2RYn0P.js} +3 -1
  15. package/dist/chunks/ru-Dr2RYn0P.js.map +1 -0
  16. package/dist/chunks/useScrollSpy-CIJpw0RI.js +360 -0
  17. package/dist/chunks/useScrollSpy-CIJpw0RI.js.map +1 -0
  18. package/dist/components/GrScrollSpy/chunks/GrScrollSpy-CQCH3zLC.js +207 -0
  19. package/dist/components/GrScrollSpy/chunks/GrScrollSpy-CQCH3zLC.js.map +1 -0
  20. package/dist/components/GrScrollSpy/index.js +3 -0
  21. package/dist/composables/useScrollSpy.js +2 -0
  22. package/dist/granular-provider-node.js +1 -1
  23. package/dist/granular-provider.js +1 -1
  24. package/dist/i18n/all.js +1 -1
  25. package/dist/i18n/index.js +1 -1
  26. package/dist/index.js +4 -1
  27. package/dist/theme.js +1 -1
  28. package/dist/tokens.js +1 -1
  29. package/dist/types/components/GrScrollSpy/GrScrollSpy.vue.d.ts +105 -0
  30. package/dist/types/components/GrScrollSpy/config.d.ts +1 -0
  31. package/dist/types/components/GrScrollSpy/grScrollSpyStyles.d.ts +26 -0
  32. package/dist/types/components/GrScrollSpy/index.d.ts +9 -0
  33. package/dist/types/components/GrScrollSpy/safelist.d.ts +1 -0
  34. package/dist/types/components/GrScrollSpy/scrollSpyItems.d.ts +44 -0
  35. package/dist/types/composables/internal/scrollSpyGeometry.d.ts +67 -0
  36. package/dist/types/composables/useScrollSpy.d.ts +59 -0
  37. package/dist/types/granular-provider/shared.d.ts +1 -0
  38. package/dist/types/i18n/messages/en.d.ts +6 -0
  39. package/dist/types/i18n/messages/es.d.ts +6 -0
  40. package/dist/types/i18n/messages/ru.d.ts +6 -0
  41. package/dist/types/index.d.ts +3 -0
  42. package/dist/web-types.json +96 -1
  43. package/package.json +9 -1
  44. package/dist/chunks/en-C_QkUTRL.js.map +0 -1
  45. package/dist/chunks/es-DemSEhZE.js.map +0 -1
  46. package/dist/chunks/granular-provider-BohXQcNt.js.map +0 -1
  47. package/dist/chunks/ru-CLUErjhY.js.map +0 -1
@@ -0,0 +1,207 @@
1
+ import { n as useGranularityTranslations } from "../../../chunks/granularityI18n-BC5zsj_d.js";
2
+ import { t as useScrollSpy } from "../../../chunks/useScrollSpy-CIJpw0RI.js";
3
+ import { a as scrollSpyRootClass, i as scrollSpyListClass, r as grScrollSpyItemClass } from "../../../chunks/config-BkQ2jvXv.js";
4
+ import { Fragment, computed, createCommentVNode, createElementBlock, createElementVNode, defineComponent, normalizeClass, normalizeStyle, openBlock, ref, renderList, renderSlot, toDisplayString, unref, watchEffect } from "vue";
5
+ //#region src/components/GrScrollSpy/scrollSpyItems.ts
6
+ /**
7
+ * Значение пропа `offset` в CSS-длину для инлайновой переменной.
8
+ *
9
+ * Строка уходит как есть и намеренно не валидируется: `4rem`,
10
+ * `var(--gr-navbar-height)`, `calc(…)` обязаны работать — иначе проп проигрывает
11
+ * голому `style`. Отрицательное зажимается в ноль: линия активации выше верха
12
+ * скроллпорта смысла не имеет.
13
+ */
14
+ function scrollSpyOffsetLength(value) {
15
+ if (value == null) return void 0;
16
+ if (typeof value === "number") {
17
+ if (!Number.isFinite(value)) return void 0;
18
+ return `${Math.max(0, value)}px`;
19
+ }
20
+ const trimmed = value.trim();
21
+ return trimmed === "" ? void 0 : trimmed;
22
+ }
23
+ /**
24
+ * Наш ли это клик.
25
+ *
26
+ * Пункт остаётся `<a href="#id">` ровно ради того, что браузер умеет с ссылкой
27
+ * сам: `Cmd`-клик открывает в новой вкладке, средняя кнопка — тоже. Перехватив
28
+ * их, компонент отнял бы у пользователя работающее поведение и ничего не дал
29
+ * взамен.
30
+ */
31
+ function anchorClickHandled(event) {
32
+ if (event.defaultPrevented || event.button !== 0) return false;
33
+ return !(event.metaKey || event.ctrlKey || event.shiftKey || event.altKey);
34
+ }
35
+ /**
36
+ * Предки активного пункта — те разделы выше по списку, чей уровень мельче.
37
+ *
38
+ * Считается по документному порядку массива, а не по дереву: дерево позволило
39
+ * бы объявить вложенность, противоречащую порядку разделов на странице.
40
+ */
41
+ function ancestorSectionIds(sections, activeId) {
42
+ const ancestors = /* @__PURE__ */ new Set();
43
+ if (activeId === null) return ancestors;
44
+ const index = sections.findIndex((section) => section.id === activeId);
45
+ if (index < 0) return ancestors;
46
+ let level = sections[index].level ?? 1;
47
+ for (let cursor = index - 1; cursor >= 0; cursor -= 1) {
48
+ const candidate = sections[cursor].level ?? 1;
49
+ if (candidate < level) {
50
+ ancestors.add(sections[cursor].id);
51
+ level = candidate;
52
+ }
53
+ }
54
+ return ancestors;
55
+ }
56
+ //#endregion
57
+ //#region src/components/GrScrollSpy/GrScrollSpy.vue?vue&type=script&setup=true&lang.ts
58
+ var _hoisted_1 = ["aria-label"];
59
+ var _hoisted_2 = ["aria-level", "data-level"];
60
+ var _hoisted_3 = [
61
+ "href",
62
+ "data-active",
63
+ "data-ancestor",
64
+ "aria-current",
65
+ "onClick"
66
+ ];
67
+ //#endregion
68
+ //#region src/components/GrScrollSpy/GrScrollSpy.vue
69
+ var GrScrollSpy_default = /* @__PURE__ */ defineComponent({
70
+ __name: "GrScrollSpy",
71
+ props: {
72
+ sections: {},
73
+ offset: { default: void 0 },
74
+ scroller: { default: void 0 },
75
+ ariaLabel: { default: void 0 },
76
+ updateHash: {
77
+ type: Boolean,
78
+ default: true
79
+ },
80
+ behavior: { default: void 0 },
81
+ focusTarget: {
82
+ type: Boolean,
83
+ default: true
84
+ },
85
+ disabled: {
86
+ type: Boolean,
87
+ default: false
88
+ }
89
+ },
90
+ emits: ["activeChange", "select"],
91
+ setup(__props, { expose: __expose, emit: __emit }) {
92
+ const props = __props;
93
+ const emit = __emit;
94
+ const { t } = useGranularityTranslations();
95
+ const rootEl = ref(null);
96
+ const label = computed(() => props.ariaLabel ?? t("gr.scrollSpy.label", "On this page"));
97
+ const rootStyle = computed(() => {
98
+ const length = scrollSpyOffsetLength(props.offset);
99
+ return length === void 0 ? void 0 : { "--gr-scroll-spy-offset": length };
100
+ });
101
+ /**
102
+ * Отступ берётся из вычисленного стиля, а не из пропа: браузер уже разрешил
103
+ * `4rem`, `var(...)` и `calc(...)` в used value, и свой парсер CSS не нужен.
104
+ * Заодно работает отступ, заданный каскадом, — а он и есть общий дефолт группы.
105
+ *
106
+ * Носитель — `scroll-margin-top` на корне: вычисленное значение этого свойства
107
+ * всегда абсолютная длина, а на само оглавление оно влияет ровно там, где нужно,
108
+ * и в ту же сторону — отодвигает его от края при переходе по якорю.
109
+ */
110
+ function measureOffsetPx() {
111
+ const el = rootEl.value;
112
+ if (!el || typeof getComputedStyle !== "function") return 0;
113
+ const parsed = Number.parseFloat(getComputedStyle(el).scrollMarginTop);
114
+ return Number.isFinite(parsed) ? Math.max(0, parsed) : 0;
115
+ }
116
+ function resolveScroller() {
117
+ const value = props.scroller;
118
+ if (typeof value === "string") return typeof document === "undefined" ? null : document.querySelector(value);
119
+ return value ?? null;
120
+ }
121
+ const spy = useScrollSpy({
122
+ sections: () => Array.isArray(props.sections) ? props.sections.map((section) => section.id) : [],
123
+ scroller: resolveScroller,
124
+ offset: measureOffsetPx,
125
+ disabled: () => props.disabled,
126
+ onChange: (id) => emit("activeChange", id)
127
+ });
128
+ const ancestors = computed(() => ancestorSectionIds(props.sections ?? [], spy.active.value));
129
+ function itemState(id) {
130
+ if (spy.active.value === id) return "active";
131
+ return ancestors.value.has(id) ? "ancestor" : "idle";
132
+ }
133
+ /**
134
+ * `preventDefault()` отнимает у ссылки то, что браузер делает сам при переходе
135
+ * по якорю, — перенос точки последовательной навигации. Без компенсации
136
+ * клавиатурный пользователь после активации пункта продолжает `Tab` внутри
137
+ * оглавления, а не с начала раздела.
138
+ *
139
+ * `preventScroll` обязателен: иначе фокус телепортирует прокрутку и подерётся с
140
+ * плавной. Временный `tabindex` снимается по `blur` — чужой DOM остаётся чужим.
141
+ */
142
+ function focusSection(id) {
143
+ const el = typeof document === "undefined" ? null : document.getElementById(id);
144
+ if (!el) return;
145
+ if (!el.hasAttribute("tabindex")) {
146
+ el.setAttribute("tabindex", "-1");
147
+ el.addEventListener("blur", () => el.removeAttribute("tabindex"), { once: true });
148
+ }
149
+ el.focus({ preventScroll: true });
150
+ }
151
+ function activate(id, event) {
152
+ spy.scrollTo(id, props.behavior === void 0 ? void 0 : { behavior: props.behavior });
153
+ if (props.updateHash && typeof history !== "undefined") history.replaceState(null, "", `#${id}`);
154
+ if (props.focusTarget) focusSection(id);
155
+ if (event) emit("select", id, event);
156
+ }
157
+ function onItemClick(id, event) {
158
+ if (!anchorClickHandled(event)) return;
159
+ event.preventDefault();
160
+ activate(id, event);
161
+ }
162
+ __expose({
163
+ active: spy.active,
164
+ scrollTo: spy.scrollTo,
165
+ refresh: spy.refresh
166
+ });
167
+ if (process.env.NODE_ENV !== "production") watchEffect(() => {
168
+ if (!Array.isArray(props.sections)) console.warn(`[granularity] GrScrollSpy: обязательный проп \`sections\` должен быть массивом — получено ${String(props.sections)}.`);
169
+ });
170
+ return (_ctx, _cache) => {
171
+ return Array.isArray(__props.sections) && __props.sections.length > 0 ? (openBlock(), createElementBlock("nav", {
172
+ key: 0,
173
+ ref_key: "rootEl",
174
+ ref: rootEl,
175
+ "data-gr-scroll-spy": "",
176
+ "aria-label": label.value,
177
+ class: normalizeClass(unref(scrollSpyRootClass)),
178
+ style: normalizeStyle(rootStyle.value)
179
+ }, [createElementVNode("ul", {
180
+ role: "list",
181
+ class: normalizeClass(unref(scrollSpyListClass))
182
+ }, [(openBlock(true), createElementBlock(Fragment, null, renderList(__props.sections, (section) => {
183
+ return openBlock(), createElementBlock("li", {
184
+ key: section.id,
185
+ "aria-level": section.level ?? 1,
186
+ "data-level": section.level ?? 1,
187
+ style: normalizeStyle({ paddingInlineStart: `calc(var(--gr-scroll-spy-indent, 0.75rem) * ${(section.level ?? 1) - 1})` })
188
+ }, [renderSlot(_ctx.$slots, "item", {
189
+ section,
190
+ active: unref(spy).active.value === section.id,
191
+ ancestor: ancestors.value.has(section.id),
192
+ activate: () => activate(section.id)
193
+ }, () => [createElementVNode("a", {
194
+ href: `#${section.id}`,
195
+ "data-gr-scroll-spy-item": "",
196
+ "data-active": unref(spy).active.value === section.id ? "true" : void 0,
197
+ "data-ancestor": ancestors.value.has(section.id) ? "true" : void 0,
198
+ "aria-current": unref(spy).active.value === section.id ? "location" : void 0,
199
+ class: normalizeClass(unref(grScrollSpyItemClass)(itemState(section.id))),
200
+ onClick: ($event) => onItemClick(section.id, $event)
201
+ }, toDisplayString(section.label), 11, _hoisted_3)])], 12, _hoisted_2);
202
+ }), 128))], 2)], 14, _hoisted_1)) : createCommentVNode("", true);
203
+ };
204
+ }
205
+ });
206
+ //#endregion
207
+ export { GrScrollSpy_default as t };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GrScrollSpy-CQCH3zLC.js","names":[],"sources":["../../../../src/components/GrScrollSpy/scrollSpyItems.ts","../../../../src/components/GrScrollSpy/GrScrollSpy.vue","../../../../src/components/GrScrollSpy/GrScrollSpy.vue"],"sourcesContent":["/**\n * Арифметика пунктов оглавления: всё, что следует из того, что пункт —\n * **ссылка**, а не из положения прокрутки. Положение живёт в\n * `composables/internal/scrollSpyGeometry.ts`.\n */\n\n/** Раздел в оглавлении. */\nexport interface GrScrollSpySection {\n id: string\n label: string\n /** Уровень вложенности, 1 — верхний. Влияет на отступ и цепочку предков. */\n level?: number\n}\n\n/**\n * Значение пропа `offset` в CSS-длину для инлайновой переменной.\n *\n * Строка уходит как есть и намеренно не валидируется: `4rem`,\n * `var(--gr-navbar-height)`, `calc(…)` обязаны работать — иначе проп проигрывает\n * голому `style`. Отрицательное зажимается в ноль: линия активации выше верха\n * скроллпорта смысла не имеет.\n */\nexport function scrollSpyOffsetLength(value: number | string | undefined): string | undefined {\n if (value == null)\n return undefined\n\n if (typeof value === 'number') {\n if (!Number.isFinite(value))\n return undefined\n\n // Число выносится в переменную, чтобы литерал остался одним токеном: гейт\n // safelist разбирает шаблонные строки по пробелам и обломок вида `value)}px`\n // принимает за утилиту UnoCSS — которой он, по совпадению, и оказывается.\n const px = Math.max(0, value)\n\n return `${px}px`\n }\n\n const trimmed = value.trim()\n\n return trimmed === '' ? undefined : trimmed\n}\n\n/**\n * Наш ли это клик.\n *\n * Пункт остаётся `<a href=\"#id\">` ровно ради того, что браузер умеет с ссылкой\n * сам: `Cmd`-клик открывает в новой вкладке, средняя кнопка — тоже. Перехватив\n * их, компонент отнял бы у пользователя работающее поведение и ничего не дал\n * взамен.\n */\nexport function anchorClickHandled(event: {\n button: number\n metaKey: boolean\n ctrlKey: boolean\n shiftKey: boolean\n altKey: boolean\n defaultPrevented: boolean\n}): boolean {\n if (event.defaultPrevented || event.button !== 0)\n return false\n\n return !(event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)\n}\n\n/**\n * Предки активного пункта — те разделы выше по списку, чей уровень мельче.\n *\n * Считается по документному порядку массива, а не по дереву: дерево позволило\n * бы объявить вложенность, противоречащую порядку разделов на странице.\n */\nexport function ancestorSectionIds(\n sections: readonly GrScrollSpySection[],\n activeId: string | null,\n): Set<string> {\n const ancestors = new Set<string>()\n\n if (activeId === null)\n return ancestors\n\n const index = sections.findIndex(section => section.id === activeId)\n\n if (index < 0)\n return ancestors\n\n let level = sections[index].level ?? 1\n\n for (let cursor = index - 1; cursor >= 0; cursor -= 1) {\n const candidate = sections[cursor].level ?? 1\n\n if (candidate < level) {\n ancestors.add(sections[cursor].id)\n level = candidate\n }\n }\n\n return ancestors\n}\n","<script setup lang=\"ts\">\nimport { computed, ref, watchEffect } from 'vue'\n\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { useScrollSpy } from '../../composables/useScrollSpy'\n\nimport type { GrScrollSpySection } from './scrollSpyItems'\nimport { anchorClickHandled, ancestorSectionIds, scrollSpyOffsetLength } from './scrollSpyItems'\nimport { grScrollSpyItemClass, scrollSpyListClass, scrollSpyRootClass } from './grScrollSpyStyles'\n\n/**\n * GrScrollSpy — оглавление длинной страницы, которое знает, до какого раздела\n * дочитали: активный пункт подсвечивается по прокрутке, клик по пункту ведёт к\n * разделу.\n *\n * Логика положения живёт в композабле `useScrollSpy` и доступна отдельно — этот\n * компонент добавляет к ней разметку, `aria-current` и всё, что следует из того,\n * что пункт является ссылкой.\n */\nexport interface GrScrollSpyProps {\n /** Разделы в порядке их следования на странице. */\n sections: GrScrollSpySection[]\n /**\n * Отступ линии активации от верха скроллпорта. Число — пиксели, строка — как\n * есть. Почти всегда равен отступу липкой шапки (`--gr-affix-offset`).\n */\n offset?: number | string\n /** Скроллпорт: элемент или селектор. Не задан — ищется от первого раздела. */\n scroller?: HTMLElement | string | null\n ariaLabel?: string\n /** Обновлять `#hash` адреса при переходе. */\n updateHash?: boolean\n /** Плавность прокрутки. Под `prefers-reduced-motion` всегда мгновенная. */\n behavior?: ScrollBehavior\n /** Переносить фокус на раздел, как это делает переход по якорю. */\n focusTarget?: boolean\n disabled?: boolean\n}\n\nexport interface GrScrollSpyEmits {\n (e: 'activeChange', id: string | null): void\n (e: 'select', id: string, event: MouseEvent): void\n}\n\nconst props = withDefaults(defineProps<GrScrollSpyProps>(), {\n offset: undefined,\n scroller: undefined,\n ariaLabel: undefined,\n updateHash: true,\n behavior: undefined,\n focusTarget: true,\n disabled: false,\n})\n\nconst emit = defineEmits<GrScrollSpyEmits>()\n\ndefineSlots<{\n /** Пункт целиком. `activate` повторяет клик по нему. */\n item?: (props: {\n section: GrScrollSpySection\n active: boolean\n ancestor: boolean\n activate: () => void\n }) => unknown\n}>()\n\nconst { t } = useGranularityTranslations()\n\nconst rootEl = ref<HTMLElement | null>(null)\n\nconst label = computed(() => props.ariaLabel ?? t('gr.scrollSpy.label', 'On this page'))\n\nconst rootStyle = computed(() => {\n const length = scrollSpyOffsetLength(props.offset)\n\n return length === undefined ? undefined : { '--gr-scroll-spy-offset': length }\n})\n\n/**\n * Отступ берётся из вычисленного стиля, а не из пропа: браузер уже разрешил\n * `4rem`, `var(...)` и `calc(...)` в used value, и свой парсер CSS не нужен.\n * Заодно работает отступ, заданный каскадом, — а он и есть общий дефолт группы.\n *\n * Носитель — `scroll-margin-top` на корне: вычисленное значение этого свойства\n * всегда абсолютная длина, а на само оглавление оно влияет ровно там, где нужно,\n * и в ту же сторону — отодвигает его от края при переходе по якорю.\n */\nfunction measureOffsetPx(): number {\n const el = rootEl.value\n\n if (!el || typeof getComputedStyle !== 'function')\n return 0\n\n const parsed = Number.parseFloat(getComputedStyle(el).scrollMarginTop)\n\n return Number.isFinite(parsed) ? Math.max(0, parsed) : 0\n}\n\nfunction resolveScroller(): HTMLElement | null {\n const value = props.scroller\n\n if (typeof value === 'string')\n return typeof document === 'undefined' ? null : document.querySelector<HTMLElement>(value)\n\n return value ?? null\n}\n\nconst spy = useScrollSpy({\n sections: () => (Array.isArray(props.sections) ? props.sections.map(section => section.id) : []),\n scroller: resolveScroller,\n offset: measureOffsetPx,\n disabled: () => props.disabled,\n onChange: id => emit('activeChange', id),\n})\n\nconst ancestors = computed(() => ancestorSectionIds(props.sections ?? [], spy.active.value))\n\nfunction itemState(id: string): 'active' | 'ancestor' | 'idle' {\n if (spy.active.value === id)\n return 'active'\n\n return ancestors.value.has(id) ? 'ancestor' : 'idle'\n}\n\n/**\n * `preventDefault()` отнимает у ссылки то, что браузер делает сам при переходе\n * по якорю, — перенос точки последовательной навигации. Без компенсации\n * клавиатурный пользователь после активации пункта продолжает `Tab` внутри\n * оглавления, а не с начала раздела.\n *\n * `preventScroll` обязателен: иначе фокус телепортирует прокрутку и подерётся с\n * плавной. Временный `tabindex` снимается по `blur` — чужой DOM остаётся чужим.\n */\nfunction focusSection(id: string): void {\n const el = typeof document === 'undefined' ? null : document.getElementById(id)\n\n if (!el)\n return\n\n if (!el.hasAttribute('tabindex')) {\n el.setAttribute('tabindex', '-1')\n el.addEventListener('blur', () => el.removeAttribute('tabindex'), { once: true })\n }\n\n el.focus({ preventScroll: true })\n}\n\nfunction activate(id: string, event?: MouseEvent): void {\n spy.scrollTo(id, props.behavior === undefined ? undefined : { behavior: props.behavior })\n\n if (props.updateHash && typeof history !== 'undefined') {\n // `replaceState`, а не `pushState`: запись истории на каждый пункт\n // превратила бы «Назад» в отмену прокрутки вместо возврата на страницу.\n history.replaceState(null, '', `#${id}`)\n }\n\n if (props.focusTarget)\n focusSection(id)\n\n if (event)\n emit('select', id, event)\n}\n\nfunction onItemClick(id: string, event: MouseEvent): void {\n if (!anchorClickHandled(event))\n return\n\n event.preventDefault()\n activate(id, event)\n}\n\ndefineExpose({\n active: spy.active,\n scrollTo: spy.scrollTo,\n refresh: spy.refresh,\n})\n\nif (__GR_DEV__) {\n watchEffect(() => {\n if (!Array.isArray(props.sections)) {\n console.warn(\n `[granularity] GrScrollSpy: обязательный проп \\`sections\\` должен быть массивом — получено ${String(props.sections)}.`,\n )\n }\n })\n}\n</script>\n\n<template>\n <!--\n Пустой список не рендерит ничего: пустой лендмарк `navigation` шумит в\n списке ориентиров у диктора, не давая взамен ни одной цели.\n -->\n <nav\n v-if=\"Array.isArray(sections) && sections.length > 0\"\n ref=\"rootEl\"\n data-gr-scroll-spy\n :aria-label=\"label\"\n :class=\"scrollSpyRootClass\"\n :style=\"rootStyle\"\n >\n <!--\n `role=\"list\"` обязателен: снятый `list-style` отнимает у списка роль в\n WebKit, и оглавление перестаёт объявляться списком.\n -->\n <ul role=\"list\" :class=\"scrollSpyListClass\">\n <li\n v-for=\"section in sections\"\n :key=\"section.id\"\n :aria-level=\"section.level ?? 1\"\n :data-level=\"section.level ?? 1\"\n :style=\"{ paddingInlineStart: `calc(var(--gr-scroll-spy-indent, 0.75rem) * ${(section.level ?? 1) - 1})` }\"\n >\n <slot\n name=\"item\"\n :section=\"section\"\n :active=\"spy.active.value === section.id\"\n :ancestor=\"ancestors.has(section.id)\"\n :activate=\"() => activate(section.id)\"\n >\n <a\n :href=\"`#${section.id}`\"\n data-gr-scroll-spy-item\n :data-active=\"spy.active.value === section.id ? 'true' : undefined\"\n :data-ancestor=\"ancestors.has(section.id) ? 'true' : undefined\"\n :aria-current=\"spy.active.value === section.id ? 'location' : undefined\"\n :class=\"grScrollSpyItemClass(itemState(section.id))\"\n @click=\"onItemClick(section.id, $event)\"\n >\n {{ section.label }}\n </a>\n </slot>\n </li>\n </ul>\n </nav>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, ref, watchEffect } from 'vue'\n\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { useScrollSpy } from '../../composables/useScrollSpy'\n\nimport type { GrScrollSpySection } from './scrollSpyItems'\nimport { anchorClickHandled, ancestorSectionIds, scrollSpyOffsetLength } from './scrollSpyItems'\nimport { grScrollSpyItemClass, scrollSpyListClass, scrollSpyRootClass } from './grScrollSpyStyles'\n\n/**\n * GrScrollSpy — оглавление длинной страницы, которое знает, до какого раздела\n * дочитали: активный пункт подсвечивается по прокрутке, клик по пункту ведёт к\n * разделу.\n *\n * Логика положения живёт в композабле `useScrollSpy` и доступна отдельно — этот\n * компонент добавляет к ней разметку, `aria-current` и всё, что следует из того,\n * что пункт является ссылкой.\n */\nexport interface GrScrollSpyProps {\n /** Разделы в порядке их следования на странице. */\n sections: GrScrollSpySection[]\n /**\n * Отступ линии активации от верха скроллпорта. Число — пиксели, строка — как\n * есть. Почти всегда равен отступу липкой шапки (`--gr-affix-offset`).\n */\n offset?: number | string\n /** Скроллпорт: элемент или селектор. Не задан — ищется от первого раздела. */\n scroller?: HTMLElement | string | null\n ariaLabel?: string\n /** Обновлять `#hash` адреса при переходе. */\n updateHash?: boolean\n /** Плавность прокрутки. Под `prefers-reduced-motion` всегда мгновенная. */\n behavior?: ScrollBehavior\n /** Переносить фокус на раздел, как это делает переход по якорю. */\n focusTarget?: boolean\n disabled?: boolean\n}\n\nexport interface GrScrollSpyEmits {\n (e: 'activeChange', id: string | null): void\n (e: 'select', id: string, event: MouseEvent): void\n}\n\nconst props = withDefaults(defineProps<GrScrollSpyProps>(), {\n offset: undefined,\n scroller: undefined,\n ariaLabel: undefined,\n updateHash: true,\n behavior: undefined,\n focusTarget: true,\n disabled: false,\n})\n\nconst emit = defineEmits<GrScrollSpyEmits>()\n\ndefineSlots<{\n /** Пункт целиком. `activate` повторяет клик по нему. */\n item?: (props: {\n section: GrScrollSpySection\n active: boolean\n ancestor: boolean\n activate: () => void\n }) => unknown\n}>()\n\nconst { t } = useGranularityTranslations()\n\nconst rootEl = ref<HTMLElement | null>(null)\n\nconst label = computed(() => props.ariaLabel ?? t('gr.scrollSpy.label', 'On this page'))\n\nconst rootStyle = computed(() => {\n const length = scrollSpyOffsetLength(props.offset)\n\n return length === undefined ? undefined : { '--gr-scroll-spy-offset': length }\n})\n\n/**\n * Отступ берётся из вычисленного стиля, а не из пропа: браузер уже разрешил\n * `4rem`, `var(...)` и `calc(...)` в used value, и свой парсер CSS не нужен.\n * Заодно работает отступ, заданный каскадом, — а он и есть общий дефолт группы.\n *\n * Носитель — `scroll-margin-top` на корне: вычисленное значение этого свойства\n * всегда абсолютная длина, а на само оглавление оно влияет ровно там, где нужно,\n * и в ту же сторону — отодвигает его от края при переходе по якорю.\n */\nfunction measureOffsetPx(): number {\n const el = rootEl.value\n\n if (!el || typeof getComputedStyle !== 'function')\n return 0\n\n const parsed = Number.parseFloat(getComputedStyle(el).scrollMarginTop)\n\n return Number.isFinite(parsed) ? Math.max(0, parsed) : 0\n}\n\nfunction resolveScroller(): HTMLElement | null {\n const value = props.scroller\n\n if (typeof value === 'string')\n return typeof document === 'undefined' ? null : document.querySelector<HTMLElement>(value)\n\n return value ?? null\n}\n\nconst spy = useScrollSpy({\n sections: () => (Array.isArray(props.sections) ? props.sections.map(section => section.id) : []),\n scroller: resolveScroller,\n offset: measureOffsetPx,\n disabled: () => props.disabled,\n onChange: id => emit('activeChange', id),\n})\n\nconst ancestors = computed(() => ancestorSectionIds(props.sections ?? [], spy.active.value))\n\nfunction itemState(id: string): 'active' | 'ancestor' | 'idle' {\n if (spy.active.value === id)\n return 'active'\n\n return ancestors.value.has(id) ? 'ancestor' : 'idle'\n}\n\n/**\n * `preventDefault()` отнимает у ссылки то, что браузер делает сам при переходе\n * по якорю, — перенос точки последовательной навигации. Без компенсации\n * клавиатурный пользователь после активации пункта продолжает `Tab` внутри\n * оглавления, а не с начала раздела.\n *\n * `preventScroll` обязателен: иначе фокус телепортирует прокрутку и подерётся с\n * плавной. Временный `tabindex` снимается по `blur` — чужой DOM остаётся чужим.\n */\nfunction focusSection(id: string): void {\n const el = typeof document === 'undefined' ? null : document.getElementById(id)\n\n if (!el)\n return\n\n if (!el.hasAttribute('tabindex')) {\n el.setAttribute('tabindex', '-1')\n el.addEventListener('blur', () => el.removeAttribute('tabindex'), { once: true })\n }\n\n el.focus({ preventScroll: true })\n}\n\nfunction activate(id: string, event?: MouseEvent): void {\n spy.scrollTo(id, props.behavior === undefined ? undefined : { behavior: props.behavior })\n\n if (props.updateHash && typeof history !== 'undefined') {\n // `replaceState`, а не `pushState`: запись истории на каждый пункт\n // превратила бы «Назад» в отмену прокрутки вместо возврата на страницу.\n history.replaceState(null, '', `#${id}`)\n }\n\n if (props.focusTarget)\n focusSection(id)\n\n if (event)\n emit('select', id, event)\n}\n\nfunction onItemClick(id: string, event: MouseEvent): void {\n if (!anchorClickHandled(event))\n return\n\n event.preventDefault()\n activate(id, event)\n}\n\ndefineExpose({\n active: spy.active,\n scrollTo: spy.scrollTo,\n refresh: spy.refresh,\n})\n\nif (__GR_DEV__) {\n watchEffect(() => {\n if (!Array.isArray(props.sections)) {\n console.warn(\n `[granularity] GrScrollSpy: обязательный проп \\`sections\\` должен быть массивом — получено ${String(props.sections)}.`,\n )\n }\n })\n}\n</script>\n\n<template>\n <!--\n Пустой список не рендерит ничего: пустой лендмарк `navigation` шумит в\n списке ориентиров у диктора, не давая взамен ни одной цели.\n -->\n <nav\n v-if=\"Array.isArray(sections) && sections.length > 0\"\n ref=\"rootEl\"\n data-gr-scroll-spy\n :aria-label=\"label\"\n :class=\"scrollSpyRootClass\"\n :style=\"rootStyle\"\n >\n <!--\n `role=\"list\"` обязателен: снятый `list-style` отнимает у списка роль в\n WebKit, и оглавление перестаёт объявляться списком.\n -->\n <ul role=\"list\" :class=\"scrollSpyListClass\">\n <li\n v-for=\"section in sections\"\n :key=\"section.id\"\n :aria-level=\"section.level ?? 1\"\n :data-level=\"section.level ?? 1\"\n :style=\"{ paddingInlineStart: `calc(var(--gr-scroll-spy-indent, 0.75rem) * ${(section.level ?? 1) - 1})` }\"\n >\n <slot\n name=\"item\"\n :section=\"section\"\n :active=\"spy.active.value === section.id\"\n :ancestor=\"ancestors.has(section.id)\"\n :activate=\"() => activate(section.id)\"\n >\n <a\n :href=\"`#${section.id}`\"\n data-gr-scroll-spy-item\n :data-active=\"spy.active.value === section.id ? 'true' : undefined\"\n :data-ancestor=\"ancestors.has(section.id) ? 'true' : undefined\"\n :aria-current=\"spy.active.value === section.id ? 'location' : undefined\"\n :class=\"grScrollSpyItemClass(itemState(section.id))\"\n @click=\"onItemClick(section.id, $event)\"\n >\n {{ section.label }}\n </a>\n </slot>\n </li>\n </ul>\n </nav>\n</template>\n"],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { t as GrScrollSpy_default } from "./chunks/GrScrollSpy-CQCH3zLC.js";
2
+ import { n as grScrollSpySafelist, t as grScrollSpyConfig } from "../../chunks/config-BkQ2jvXv.js";
3
+ export { GrScrollSpy_default as GrScrollSpy, GrScrollSpy_default as default, grScrollSpyConfig, grScrollSpySafelist };
@@ -0,0 +1,2 @@
1
+ import { t as useScrollSpy } from "../chunks/useScrollSpy-CIJpw0RI.js";
2
+ export { useScrollSpy };
@@ -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-BohXQcNt.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-CCNbQa5U.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-BohXQcNt.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-CCNbQa5U.js";
2
2
  export { GRANULARITY_PROVIDER_ID, createGranularityProvider, granularityProvider as default, granularityProvider, granularityComponentConfigs, granularityDefaultThemes, granularityThemeNames };
package/dist/i18n/all.js CHANGED
@@ -1,4 +1,4 @@
1
- import { n as ru, r as en, t as es } from "../chunks/es-B4t_3Wnh.js";
1
+ import { n as ru, r as en, t as es } from "../chunks/es-CeKkMxZ_.js";
2
2
  //#region src/i18n/all.ts
3
3
  var all = [
4
4
  en,
@@ -1,3 +1,3 @@
1
1
  import { t as GRANULARITY_I18N_KEY } from "../chunks/adapter-CAOxmn2G.js";
2
- import { i as GRANULARITY_I18N_BLOCK, n as ru, r as en, t as es } from "../chunks/es-B4t_3Wnh.js";
2
+ import { i as GRANULARITY_I18N_BLOCK, n as ru, r as en, t as es } from "../chunks/es-CeKkMxZ_.js";
3
3
  export { GRANULARITY_I18N_BLOCK, GRANULARITY_I18N_KEY, en, es, ru };
package/dist/index.js CHANGED
@@ -168,6 +168,9 @@ import { t as grRadioGroupConfig } from "./chunks/config-QaIc_gRa.js";
168
168
  import { n as grRatingSafelist, t as grRatingConfig } from "./chunks/config-DRbojEzO.js";
169
169
  import { t as GrRating_default } from "./components/GrRating/chunks/GrRating-DTriyyqG.js";
170
170
  import { n as GrFormErrorBanner_default, r as GrUploadErrorBanner_default, t as useResponseError } from "./components/GrResponseErrorBanner/chunks/GrResponseErrorBanner-ZvBvEQDZ.js";
171
+ import { t as useScrollSpy } from "./chunks/useScrollSpy-CIJpw0RI.js";
172
+ import { t as GrScrollSpy_default } from "./components/GrScrollSpy/chunks/GrScrollSpy-CQCH3zLC.js";
173
+ import { n as grScrollSpySafelist, t as grScrollSpyConfig } from "./chunks/config-BkQ2jvXv.js";
171
174
  import { n as titleWhenTruncated, t as GrSegmented_default } from "./components/GrSegmented/chunks/GrSegmented-CuDaKA6S.js";
172
175
  import { n as grSegmentedSafelist, t as grSegmentedConfig } from "./chunks/config-rLkc9Zn2.js";
173
176
  import { n as grSelectSafelist, t as grSelectConfig } from "./chunks/config--t2dxj0t.js";
@@ -211,4 +214,4 @@ import { t as GrTreeSelect_default } from "./components/GrTreeSelect/chunks/GrTr
211
214
  import "./composables/useGrComponentConfig.js";
212
215
  import "./composables/useGranularityTranslations.js";
213
216
  import { GRANULARITY_THEME_STATE, granularityThemePlugin, initThemeEarly, useTheme } from "./composables/useTheme.js";
214
- export { FileValidationError, GRANULARITY_THEME_STATE, GRANULARITY_TOAST_STATE, GR_KBD_TOKENS, GR_TONES, GrAffix_default as GrAffix, GrAlert_default as GrAlert, GrAutocomplete_default as GrAutocomplete, GrAvatar_default as GrAvatar, GrAvatarGroup_default as GrAvatarGroup, GrBadge_default as GrBadge, GrBadgeWrap_default as GrBadgeWrap, GrBottomNav_default as GrBottomNav, GrBreadcrumbs_default as GrBreadcrumbs, GrButton_default as GrButton, GrButtonGroup_default as GrButtonGroup, GrCard_default as GrCard, GrCarousel_default as GrCarousel, GrCarouselSlide_default as GrCarouselSlide, GrCheckbox_default as GrCheckbox, GrCheckboxGroup_default as GrCheckboxGroup, GrChip_default as GrChip, GrChipGroup_default as GrChipGroup, GrCollapse_default as GrCollapse, GrCollapseItem_default as GrCollapseItem, GrColorPicker_default as GrColorPicker, GrCommandPalette_default as GrCommandPalette, GrConfigProvider_default as GrConfigProvider, GrConfirmDialog_default as GrConfirmDialog, GrContextMenu_default as GrContextMenu, GrDataTable_default as GrDataTable, GrDelta_default as GrDelta, GrDescriptionList_default as GrDescriptionList, GrDialog_default as GrDialog, GrDialogCloseButton_default as GrDialogCloseButton, GrDialogFooter_default as GrDialogFooter, GrDialogHeader_default as GrDialogHeader, GrDialogServiceHost_default as GrDialogServiceHost, GrDivider_default as GrDivider, GrDrawer_default as GrDrawer, GrDropdown_default as GrDropdown, GrDropdownMenu_default as GrDropdownMenu, GrDropdownMenuColumn_default as GrDropdownMenuColumn, GrDropdownMenuColumns_default as GrDropdownMenuColumns, GrDropdownMenuDivider_default as GrDropdownMenuDivider, GrDropdownMenuGroup_default as GrDropdownMenuGroup, GrDropdownMenuHeader_default as GrDropdownMenuHeader, GrDropdownMenuItem_default as GrDropdownMenuItem, GrDropdownMenuList_default as GrDropdownMenuList, GrEmptyState_default as GrEmptyState, GrFilePreview_default as GrFilePreview, GrFileUpload_default as GrFileUpload, GrForm_default as GrForm, GrFormErrorBanner_default as GrFormErrorBanner, GrFormField_default as GrFormField, GrFormFile_default as GrFormFile, GrFormSection_default as GrFormSection, GrIcon_default as GrIcon, GrImageViewer_default as GrImageViewer, GrInput_default as GrInput, GrInputTag_default as GrInputTag, GrJsonViewer_default as GrJsonViewer, GrKbd_default as GrKbd, GrLink_default as GrLink, GrList_default as GrList, GrListItem_default as GrListItem, GrLoading_default as GrLoading, GrModal_default as GrModal, GrNavbar_default as GrNavbar, GrNumberInput_default as GrNumberInput, GrPagination_default as GrPagination, GrPopover_default as GrPopover, GrProgressBar_default as GrProgressBar, GrProgressCircle_default as GrProgressCircle, GrPromptDialog_default as GrPromptDialog, GrRadio_default as GrRadio, GrRadioGroup_default as GrRadioGroup, GrRating_default as GrRating, GrResponseErrorBanner_default as GrResponseErrorBanner, GrSegmented_default as GrSegmented, GrSelect_default as GrSelect, GrSidebar_default as GrSidebar, GrSidebarGroup_default as GrSidebarGroup, GrSidebarItem_default as GrSidebarItem, GrSkeleton_default as GrSkeleton, GrSlider_default as GrSlider, GrSortableList_default as GrSortableList, GrSplitter_default as GrSplitter, GrStatistic_default as GrStatistic, GrSteps_default as GrSteps, GrSwitch_default as GrSwitch, GrTabPanel_default as GrTabPanel, GrTabPanels_default as GrTabPanels, GrTable_default as GrTable, GrTabs_default as GrTabs, GrTextarea_default as GrTextarea, GrTimeline_default as GrTimeline, GrTimelineItem_default as GrTimelineItem, GrToaster_default as GrToaster, GrTooltip_default as GrTooltip, GrTransfer_default as GrTransfer, GrTree_default as GrTree, GrTreeSelect_default as GrTreeSelect, GrUploadAbortError, GrUploadErrorBanner_default as GrUploadErrorBanner, GrUploadHttpError, GrValue_default as GrValue, coreResponseErrorParsers, createGrFormMessageResolver, createLoading, createResponseErrorClassifier, deltaDirection, deltaTone, dialogService, extendDefaultParsers, fileKindOf, findKbdToken, formatBadgeValue, formatStatisticValue, getByPath, grAffixConfig, grAffixSafelist, grAlertConfig, grAutocompleteConfig, grAutocompleteSafelist, grAvatarConfig, grAvatarSafelist, grBadgeConfig, grBadgeSafelist, grBadgeWrapConfig, grBadgeWrapSafelist, grBottomNavConfig, grBottomNavSafelist, grBreadcrumbsConfig, grBreadcrumbsSafelist, grButtonConfig, grButtonGroupConfig, grButtonSafelist, grCardConfig, grCardSafelist, grCarouselConfig, grCarouselSafelist, grCheckboxConfig, grCheckboxGroupConfig, grCheckboxSafelist, grChipConfig, grChipGroupConfig, grChipGroupSafelist, grChipSafelist, grCollapseConfig, grCollapseSafelist, grColorPickerConfig, grColorPickerSafelist, grCommandPaletteConfig, grCommandPaletteSafelist, grConfigProviderConfig, grConfirmDialogConfig, grContextMenuConfig, grDataTableConfig, grDataTableSafelist, grDeltaConfig, grDeltaSafelist, grDescriptionListConfig, grDescriptionListSafelist, grDialogSafelist, grDialogServiceConfig, grDividerConfig, grDividerSafelist, grDrawerConfig, grDrawerSafelist, grDropdownConfig, grDropdownMenuConfig, grDropdownMenuSafelist, grDropdownSafelist, grEmptyStateConfig, grEmptyStateSafelist, grFilePreviewConfig, grFilePreviewSafelist, grFileUploadConfig, grFileUploadSafelist, grFormConfig, grFormFieldConfig, grFormFieldSafelist, grFormFileConfig, grFormFileSafelist, grFormSectionConfig, grFormSectionSafelist, grIconConfig, grIconSafelist, grImageViewerConfig, grImageViewerSafelist, grInputConfig, grInputSafelist, grInputTagConfig, grInputTagSafelist, grJsonViewerConfig, grJsonViewerSafelist, grKbdConfig, grKbdSafelist, grLinkConfig, grLinkSafelist, grListConfig, grListSafelist, grLoadingConfig, grLoadingSafelist, grModalConfig, grModalSafelist, grNavbarConfig, grNavbarSafelist, grNumberInputConfig, grNumberInputSafelist, grPaginationConfig, grPaginationSafelist, grPopoverConfig, grPopoverSafelist, grProgressBarConfig, grProgressBarSafelist, grProgressCircleConfig, grProgressCircleSafelist, grPromptDialogConfig, grRadioConfig, grRadioGroupConfig, grRadioSafelist, grRatingConfig, grRatingSafelist, grSegmentedConfig, grSegmentedSafelist, grSelectConfig, grSelectSafelist, grSidebarConfig, grSidebarSafelist, grSkeletonConfig, grSliderConfig, grSliderSafelist, grSortableListConfig, grSortableListSafelist, grSplitterConfig, grSplitterSafelist, grStatisticConfig, grStatisticSafelist, grStepsConfig, grStepsSafelist, grSwitchConfig, grSwitchSafelist, grTabPanelsConfig, grTableConfig, grTableSafelist, grTabsConfig, grTabsSafelist, grTextareaConfig, grTextareaSafelist, grTimelineConfig, grTimelineSafelist, grToasterConfig, grToasterSafelist, grTooltipConfig, grTooltipSafelist, grTransferConfig, grTransferSafelist, grTreeConfig, grTreeSafelist, grTreeSelectConfig, grTreeSelectSafelist, grValueConfig, grValueSafelist, granularityDialogServicePlugin, granularityThemePlugin, granularityToastPlugin, initThemeEarly, initialsFrom, isEmpty, isPreviewableKind, normalizeError, resolveBreadcrumbsFit, resolveBreadcrumbsLayout, resolveGrConfig, runFieldRules, setByPath, sizeFromPointer, teardownDialogService, titleWhenTruncated, useAnnouncer, useComboboxNavigation, useDialogService, useDismissible, useDragGesture, useDragSort, useFloating, useFocusTrap, useGrButtonGroup, useGrComponentDefaults, useGrComponentProp, useGrComponentSize, useGrConfig, useGrFormContext, useGrFormControl, useGrFormFieldContext, useGrThemeAttrs, useGranularityTranslations, useOverlayLayer, usePortalTarget, useResponseError, useRovingFocus, useTheme, useToast, useVirtualList, vAutofocus, vAutosize, vClickOutside, vDropzone, vHotkey, vLoading };
217
+ export { FileValidationError, GRANULARITY_THEME_STATE, GRANULARITY_TOAST_STATE, GR_KBD_TOKENS, GR_TONES, GrAffix_default as GrAffix, GrAlert_default as GrAlert, GrAutocomplete_default as GrAutocomplete, GrAvatar_default as GrAvatar, GrAvatarGroup_default as GrAvatarGroup, GrBadge_default as GrBadge, GrBadgeWrap_default as GrBadgeWrap, GrBottomNav_default as GrBottomNav, GrBreadcrumbs_default as GrBreadcrumbs, GrButton_default as GrButton, GrButtonGroup_default as GrButtonGroup, GrCard_default as GrCard, GrCarousel_default as GrCarousel, GrCarouselSlide_default as GrCarouselSlide, GrCheckbox_default as GrCheckbox, GrCheckboxGroup_default as GrCheckboxGroup, GrChip_default as GrChip, GrChipGroup_default as GrChipGroup, GrCollapse_default as GrCollapse, GrCollapseItem_default as GrCollapseItem, GrColorPicker_default as GrColorPicker, GrCommandPalette_default as GrCommandPalette, GrConfigProvider_default as GrConfigProvider, GrConfirmDialog_default as GrConfirmDialog, GrContextMenu_default as GrContextMenu, GrDataTable_default as GrDataTable, GrDelta_default as GrDelta, GrDescriptionList_default as GrDescriptionList, GrDialog_default as GrDialog, GrDialogCloseButton_default as GrDialogCloseButton, GrDialogFooter_default as GrDialogFooter, GrDialogHeader_default as GrDialogHeader, GrDialogServiceHost_default as GrDialogServiceHost, GrDivider_default as GrDivider, GrDrawer_default as GrDrawer, GrDropdown_default as GrDropdown, GrDropdownMenu_default as GrDropdownMenu, GrDropdownMenuColumn_default as GrDropdownMenuColumn, GrDropdownMenuColumns_default as GrDropdownMenuColumns, GrDropdownMenuDivider_default as GrDropdownMenuDivider, GrDropdownMenuGroup_default as GrDropdownMenuGroup, GrDropdownMenuHeader_default as GrDropdownMenuHeader, GrDropdownMenuItem_default as GrDropdownMenuItem, GrDropdownMenuList_default as GrDropdownMenuList, GrEmptyState_default as GrEmptyState, GrFilePreview_default as GrFilePreview, GrFileUpload_default as GrFileUpload, GrForm_default as GrForm, GrFormErrorBanner_default as GrFormErrorBanner, GrFormField_default as GrFormField, GrFormFile_default as GrFormFile, GrFormSection_default as GrFormSection, GrIcon_default as GrIcon, GrImageViewer_default as GrImageViewer, GrInput_default as GrInput, GrInputTag_default as GrInputTag, GrJsonViewer_default as GrJsonViewer, GrKbd_default as GrKbd, GrLink_default as GrLink, GrList_default as GrList, GrListItem_default as GrListItem, GrLoading_default as GrLoading, GrModal_default as GrModal, GrNavbar_default as GrNavbar, GrNumberInput_default as GrNumberInput, GrPagination_default as GrPagination, GrPopover_default as GrPopover, GrProgressBar_default as GrProgressBar, GrProgressCircle_default as GrProgressCircle, GrPromptDialog_default as GrPromptDialog, GrRadio_default as GrRadio, GrRadioGroup_default as GrRadioGroup, GrRating_default as GrRating, GrResponseErrorBanner_default as GrResponseErrorBanner, GrScrollSpy_default as GrScrollSpy, GrSegmented_default as GrSegmented, GrSelect_default as GrSelect, GrSidebar_default as GrSidebar, GrSidebarGroup_default as GrSidebarGroup, GrSidebarItem_default as GrSidebarItem, GrSkeleton_default as GrSkeleton, GrSlider_default as GrSlider, GrSortableList_default as GrSortableList, GrSplitter_default as GrSplitter, GrStatistic_default as GrStatistic, GrSteps_default as GrSteps, GrSwitch_default as GrSwitch, GrTabPanel_default as GrTabPanel, GrTabPanels_default as GrTabPanels, GrTable_default as GrTable, GrTabs_default as GrTabs, GrTextarea_default as GrTextarea, GrTimeline_default as GrTimeline, GrTimelineItem_default as GrTimelineItem, GrToaster_default as GrToaster, GrTooltip_default as GrTooltip, GrTransfer_default as GrTransfer, GrTree_default as GrTree, GrTreeSelect_default as GrTreeSelect, GrUploadAbortError, GrUploadErrorBanner_default as GrUploadErrorBanner, GrUploadHttpError, GrValue_default as GrValue, coreResponseErrorParsers, createGrFormMessageResolver, createLoading, createResponseErrorClassifier, deltaDirection, deltaTone, dialogService, extendDefaultParsers, fileKindOf, findKbdToken, formatBadgeValue, formatStatisticValue, getByPath, grAffixConfig, grAffixSafelist, grAlertConfig, grAutocompleteConfig, grAutocompleteSafelist, grAvatarConfig, grAvatarSafelist, grBadgeConfig, grBadgeSafelist, grBadgeWrapConfig, grBadgeWrapSafelist, grBottomNavConfig, grBottomNavSafelist, grBreadcrumbsConfig, grBreadcrumbsSafelist, grButtonConfig, grButtonGroupConfig, grButtonSafelist, grCardConfig, grCardSafelist, grCarouselConfig, grCarouselSafelist, grCheckboxConfig, grCheckboxGroupConfig, grCheckboxSafelist, grChipConfig, grChipGroupConfig, grChipGroupSafelist, grChipSafelist, grCollapseConfig, grCollapseSafelist, grColorPickerConfig, grColorPickerSafelist, grCommandPaletteConfig, grCommandPaletteSafelist, grConfigProviderConfig, grConfirmDialogConfig, grContextMenuConfig, grDataTableConfig, grDataTableSafelist, grDeltaConfig, grDeltaSafelist, grDescriptionListConfig, grDescriptionListSafelist, grDialogSafelist, grDialogServiceConfig, grDividerConfig, grDividerSafelist, grDrawerConfig, grDrawerSafelist, grDropdownConfig, grDropdownMenuConfig, grDropdownMenuSafelist, grDropdownSafelist, grEmptyStateConfig, grEmptyStateSafelist, grFilePreviewConfig, grFilePreviewSafelist, grFileUploadConfig, grFileUploadSafelist, grFormConfig, grFormFieldConfig, grFormFieldSafelist, grFormFileConfig, grFormFileSafelist, grFormSectionConfig, grFormSectionSafelist, grIconConfig, grIconSafelist, grImageViewerConfig, grImageViewerSafelist, grInputConfig, grInputSafelist, grInputTagConfig, grInputTagSafelist, grJsonViewerConfig, grJsonViewerSafelist, grKbdConfig, grKbdSafelist, grLinkConfig, grLinkSafelist, grListConfig, grListSafelist, grLoadingConfig, grLoadingSafelist, grModalConfig, grModalSafelist, grNavbarConfig, grNavbarSafelist, grNumberInputConfig, grNumberInputSafelist, grPaginationConfig, grPaginationSafelist, grPopoverConfig, grPopoverSafelist, grProgressBarConfig, grProgressBarSafelist, grProgressCircleConfig, grProgressCircleSafelist, grPromptDialogConfig, grRadioConfig, grRadioGroupConfig, grRadioSafelist, grRatingConfig, grRatingSafelist, grScrollSpyConfig, grScrollSpySafelist, grSegmentedConfig, grSegmentedSafelist, grSelectConfig, grSelectSafelist, grSidebarConfig, grSidebarSafelist, grSkeletonConfig, grSliderConfig, grSliderSafelist, grSortableListConfig, grSortableListSafelist, grSplitterConfig, grSplitterSafelist, grStatisticConfig, grStatisticSafelist, grStepsConfig, grStepsSafelist, grSwitchConfig, grSwitchSafelist, grTabPanelsConfig, grTableConfig, grTableSafelist, grTabsConfig, grTabsSafelist, grTextareaConfig, grTextareaSafelist, grTimelineConfig, grTimelineSafelist, grToasterConfig, grToasterSafelist, grTooltipConfig, grTooltipSafelist, grTransferConfig, grTransferSafelist, grTreeConfig, grTreeSafelist, grTreeSelectConfig, grTreeSelectSafelist, grValueConfig, grValueSafelist, granularityDialogServicePlugin, granularityThemePlugin, granularityToastPlugin, initThemeEarly, initialsFrom, isEmpty, isPreviewableKind, normalizeError, resolveBreadcrumbsFit, resolveBreadcrumbsLayout, resolveGrConfig, runFieldRules, setByPath, sizeFromPointer, teardownDialogService, titleWhenTruncated, useAnnouncer, useComboboxNavigation, useDialogService, useDismissible, useDragGesture, useDragSort, useFloating, useFocusTrap, useGrButtonGroup, useGrComponentDefaults, useGrComponentProp, useGrComponentSize, useGrConfig, useGrFormContext, useGrFormControl, useGrFormFieldContext, useGrThemeAttrs, useGranularityTranslations, useOverlayLayer, usePortalTarget, useResponseError, useRovingFocus, useScrollSpy, useTheme, useToast, useVirtualList, vAutofocus, vAutosize, vClickOutside, vDropzone, vHotkey, vLoading };
package/dist/theme.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as grThemeTokens, n as grDerivedTokens, t as grComponentTokens } from "./chunks/generated-VcxJTo_q.js";
1
+ import { a as grThemeTokens, n as grDerivedTokens, t as grComponentTokens } from "./chunks/generated-Hxu_sWPs.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-VcxJTo_q.js";
1
+ import { a as grThemeTokens, i as grThemeNames, n as grDerivedTokens, r as grFoundationTokens, t as grComponentTokens } from "./chunks/generated-Hxu_sWPs.js";
2
2
  export { grComponentTokens, grDerivedTokens, grFoundationTokens, grThemeNames, grThemeTokens };
@@ -0,0 +1,105 @@
1
+ import type { GrScrollSpySection } from './scrollSpyItems';
2
+ /**
3
+ * GrScrollSpy — оглавление длинной страницы, которое знает, до какого раздела
4
+ * дочитали: активный пункт подсвечивается по прокрутке, клик по пункту ведёт к
5
+ * разделу.
6
+ *
7
+ * Логика положения живёт в композабле `useScrollSpy` и доступна отдельно — этот
8
+ * компонент добавляет к ней разметку, `aria-current` и всё, что следует из того,
9
+ * что пункт является ссылкой.
10
+ */
11
+ export interface GrScrollSpyProps {
12
+ /** Разделы в порядке их следования на странице. */
13
+ sections: GrScrollSpySection[];
14
+ /**
15
+ * Отступ линии активации от верха скроллпорта. Число — пиксели, строка — как
16
+ * есть. Почти всегда равен отступу липкой шапки (`--gr-affix-offset`).
17
+ */
18
+ offset?: number | string;
19
+ /** Скроллпорт: элемент или селектор. Не задан — ищется от первого раздела. */
20
+ scroller?: HTMLElement | string | null;
21
+ ariaLabel?: string;
22
+ /** Обновлять `#hash` адреса при переходе. */
23
+ updateHash?: boolean;
24
+ /** Плавность прокрутки. Под `prefers-reduced-motion` всегда мгновенная. */
25
+ behavior?: ScrollBehavior;
26
+ /** Переносить фокус на раздел, как это делает переход по якорю. */
27
+ focusTarget?: boolean;
28
+ disabled?: boolean;
29
+ }
30
+ export interface GrScrollSpyEmits {
31
+ (e: 'activeChange', id: string | null): void;
32
+ (e: 'select', id: string, event: MouseEvent): void;
33
+ }
34
+ type __VLS_Slots = {
35
+ /** Пункт целиком. `activate` повторяет клик по нему. */
36
+ item?: (props: {
37
+ section: GrScrollSpySection;
38
+ active: boolean;
39
+ ancestor: boolean;
40
+ activate: () => void;
41
+ }) => unknown;
42
+ };
43
+ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPropTypes<__VLS_WithDefaults<__VLS_TypePropsToOption<GrScrollSpyProps>, {
44
+ offset: undefined;
45
+ scroller: undefined;
46
+ ariaLabel: undefined;
47
+ updateHash: boolean;
48
+ behavior: undefined;
49
+ focusTarget: boolean;
50
+ disabled: boolean;
51
+ }>>, {
52
+ active: Readonly<import("vue").Ref<string | null, string | null>>;
53
+ scrollTo: (id: string, options?: {
54
+ behavior?: ScrollBehavior;
55
+ }) => void;
56
+ refresh: () => void;
57
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
58
+ activeChange: (id: string | null) => void;
59
+ select: (id: string, event: MouseEvent) => void;
60
+ }, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<__VLS_WithDefaults<__VLS_TypePropsToOption<GrScrollSpyProps>, {
61
+ offset: undefined;
62
+ scroller: undefined;
63
+ ariaLabel: undefined;
64
+ updateHash: boolean;
65
+ behavior: undefined;
66
+ focusTarget: boolean;
67
+ disabled: boolean;
68
+ }>>> & Readonly<{
69
+ onSelect?: ((id: string, event: MouseEvent) => any) | undefined;
70
+ onActiveChange?: ((id: string | null) => any) | undefined;
71
+ }>, {
72
+ offset: number | string;
73
+ disabled: boolean;
74
+ ariaLabel: string;
75
+ behavior: ScrollBehavior;
76
+ scroller: HTMLElement | string | null;
77
+ updateHash: boolean;
78
+ focusTarget: boolean;
79
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
80
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
81
+ declare const _default: typeof __VLS_export;
82
+ export default _default;
83
+ type __VLS_TypePropsToOption<T> = {
84
+ [K in keyof T]-?: {} extends Pick<T, K> ? {
85
+ type: import('vue').PropType<Required<T>[K]>;
86
+ } : {
87
+ type: import('vue').PropType<T[K]>;
88
+ required: true;
89
+ };
90
+ };
91
+ type __VLS_WithDefaults<P, D> = {
92
+ [K in keyof Pick<P, keyof P>]: K extends keyof D ? __VLS_PrettifyLocal<P[K] & {
93
+ default: D[K];
94
+ }> : P[K];
95
+ };
96
+ type __VLS_WithSlots<T, S> = T & {
97
+ new (): {
98
+ $slots: S;
99
+ };
100
+ };
101
+ type __VLS_PrettifyLocal<T> = (T extends any ? {
102
+ [K in keyof T]: T[K];
103
+ } : {
104
+ [K in keyof T as K]: T[K];
105
+ }) & {};
@@ -0,0 +1 @@
1
+ export declare const grScrollSpyConfig: import("@feugene/unocss-preset-granular/contract").GranularComponentDescriptor<"GrScrollSpy">;
@@ -0,0 +1,26 @@
1
+ export declare const scrollSpyRootClass = "text-[length:var(--gr-control-text-sm)] leading-[var(--gr-leading-sm)] [scroll-margin-top:var(--gr-scroll-spy-offset,0px)]";
2
+ export declare const scrollSpyListClass = "flex flex-col [list-style:none]";
3
+ /**
4
+ * Пункт — ссылка, поэтому `no-underline` и своё фокус-кольцо: `focus:outline-none`
5
+ * снимает системное, `focus-visible:ring-*` возвращает кольцо пакета.
6
+ *
7
+ * `ease-*` обязателен рядом с `duration-*`: без него кривую подставит `presetMini`,
8
+ * то есть движение задавал бы пресет, а не дизайн-система.
9
+ */
10
+ export declare const scrollSpyItemBaseClass = "block truncate no-underline border-s-2 ps-2 pe-2 py-1 rounded-e-[var(--gr-radius-control)] transition-colors duration-[var(--gr-duration-fast)] ease-[var(--gr-ease-out)] focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--gr-ring)]";
11
+ /**
12
+ * Активный пункт отличается **тремя** признаками — цветом рельса, весом и
13
+ * цветом текста. Одного цвета мало: он не виден при монохромном зрении и не
14
+ * существует для диктора, которому состояние приходит через `aria-current`.
15
+ *
16
+ * Приглушение неактивных — токеном текста, а не `opacity`: прозрачность
17
+ * разбавляет выверенные на AA цвета и роняет контраст.
18
+ */
19
+ export declare const scrollSpyItemIdleClass = "border-s-[var(--gr-scroll-spy-rail,var(--gr-brd))] text-[var(--gr-muted-fg)] hover:text-[var(--gr-fg)]";
20
+ /**
21
+ * Предок активного пункта. Без него оглавление с двумя уровнями читается как
22
+ * сломанное: активен подраздел, а его раздел выглядит выключенным.
23
+ */
24
+ export declare const scrollSpyItemAncestorClass = "border-s-[var(--gr-scroll-spy-rail,var(--gr-brd))] text-[var(--gr-fg)]";
25
+ export declare const scrollSpyItemActiveClass = "border-s-[var(--gr-scroll-spy-marker,var(--gr-primary))] font-600 text-[var(--gr-primary-text)]";
26
+ export declare function grScrollSpyItemClass(state: 'active' | 'ancestor' | 'idle'): string;
@@ -0,0 +1,9 @@
1
+ import type { ComponentExposed } from '../shared/instance';
2
+ import type GrScrollSpyComponent from './GrScrollSpy.vue';
3
+ export { default } from './GrScrollSpy.vue';
4
+ export { default as GrScrollSpy } from './GrScrollSpy.vue';
5
+ export type { GrScrollSpyEmits, GrScrollSpyProps } from './GrScrollSpy.vue';
6
+ export type { GrScrollSpySection } from './scrollSpyItems';
7
+ export { grScrollSpyConfig } from './config';
8
+ export { grScrollSpySafelist } from './safelist';
9
+ export type GrScrollSpyInstance = ComponentExposed<typeof GrScrollSpyComponent>;
@@ -0,0 +1 @@
1
+ export declare const grScrollSpySafelist: string[];
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Арифметика пунктов оглавления: всё, что следует из того, что пункт —
3
+ * **ссылка**, а не из положения прокрутки. Положение живёт в
4
+ * `composables/internal/scrollSpyGeometry.ts`.
5
+ */
6
+ /** Раздел в оглавлении. */
7
+ export interface GrScrollSpySection {
8
+ id: string;
9
+ label: string;
10
+ /** Уровень вложенности, 1 — верхний. Влияет на отступ и цепочку предков. */
11
+ level?: number;
12
+ }
13
+ /**
14
+ * Значение пропа `offset` в CSS-длину для инлайновой переменной.
15
+ *
16
+ * Строка уходит как есть и намеренно не валидируется: `4rem`,
17
+ * `var(--gr-navbar-height)`, `calc(…)` обязаны работать — иначе проп проигрывает
18
+ * голому `style`. Отрицательное зажимается в ноль: линия активации выше верха
19
+ * скроллпорта смысла не имеет.
20
+ */
21
+ export declare function scrollSpyOffsetLength(value: number | string | undefined): string | undefined;
22
+ /**
23
+ * Наш ли это клик.
24
+ *
25
+ * Пункт остаётся `<a href="#id">` ровно ради того, что браузер умеет с ссылкой
26
+ * сам: `Cmd`-клик открывает в новой вкладке, средняя кнопка — тоже. Перехватив
27
+ * их, компонент отнял бы у пользователя работающее поведение и ничего не дал
28
+ * взамен.
29
+ */
30
+ export declare function anchorClickHandled(event: {
31
+ button: number;
32
+ metaKey: boolean;
33
+ ctrlKey: boolean;
34
+ shiftKey: boolean;
35
+ altKey: boolean;
36
+ defaultPrevented: boolean;
37
+ }): boolean;
38
+ /**
39
+ * Предки активного пункта — те разделы выше по списку, чей уровень мельче.
40
+ *
41
+ * Считается по документному порядку массива, а не по дереву: дерево позволило
42
+ * бы объявить вложенность, противоречащую порядку разделов на странице.
43
+ */
44
+ export declare function ancestorSectionIds(sections: readonly GrScrollSpySection[], activeId: string | null): Set<string>;
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Арифметика положения чтения: чистые функции без DOM и без реактивности.
3
+ *
4
+ * Вынесена отдельно по той же причине, что и геометрия перестановки в
5
+ * `dragSortGeometry.ts`: ошибка здесь проявляется не исключением, а
6
+ * «подсветился не тот раздел», и поймать это можно только тестом на числах. В
7
+ * jsdom проверить нечем вовсе — там нет ни прокрутки, ни раскладки.
8
+ *
9
+ * Композабл `useScrollSpy` сводит `DOMRect` к числам и зовёт эти функции; что
10
+ * значит активный раздел для разметки, решает уже компонент.
11
+ */
12
+ /** Верх раздела в координатах вьюпорта. */
13
+ export interface ScrollSpyRect {
14
+ id: string;
15
+ top: number;
16
+ }
17
+ /**
18
+ * Какой раздел читают сейчас.
19
+ *
20
+ * Правило — «последний, чей верх выше линии», а не «самый видимый». Максимум
21
+ * доли пересечения ошибается двумя способами: длинный раздел выигрывает у
22
+ * короткого независимо от того, что читают, и подсветка отскакивает назад,
23
+ * когда следующий раздел только показался снизу. Здесь ответ — заголовок,
24
+ * ближайший сверху к линии чтения.
25
+ *
26
+ * Порядок массива на ответ не влияет: выбор идёт по геометрии. Это снимает
27
+ * класс дефектов, где объявленный список разошёлся с порядком разделов на
28
+ * странице — подсветку в таком случае невозможно отладить.
29
+ *
30
+ * `atEnd` побеждает правило линии, и без него последний раздел недостижим:
31
+ * раздел короче остатка скроллпорта никогда не доводит свой верх до линии,
32
+ * потому что прокрутка кончается раньше.
33
+ */
34
+ export declare function activeSectionId(rects: readonly ScrollSpyRect[], line: number, atEnd: boolean): string | null;
35
+ /**
36
+ * `rootMargin` наблюдателя: верх корня поджимается на отступ, и тогда его край
37
+ * совпадает с линией активации.
38
+ *
39
+ * Единицы только `px`: `rootMargin` произвольных длин не принимает, поэтому
40
+ * отступ приходит сюда уже замеренным.
41
+ */
42
+ export declare function scrollSpyRootMargin(offsetPx: number): string;
43
+ /**
44
+ * Куда прокрутить скроллпорт, чтобы верх раздела встал на линию активации.
45
+ *
46
+ * Своя арифметика вместо `scrollIntoView` — по трём независимым причинам:
47
+ * тот двигает всех предков (у карусели это стоило прыжка страницы на каждом
48
+ * шаге автопрокрутки), не умеет наш отступ, и в jsdom отсутствует вовсе, то
49
+ * есть клик стал бы непроверяемым.
50
+ */
51
+ export declare function scrollSpyTargetTop(scrollTop: number, sectionTop: number, portTop: number, offset: number, maxScrollTop: number): number;
52
+ /**
53
+ * Прокручивается ли предок.
54
+ *
55
+ * Читается вычисленный `overflow-y`, а не объявленный: при
56
+ * `overflow-x: hidden; overflow-y: visible` спецификация превращает вычисленный
57
+ * `overflow-y` в `auto`, то есть предок действительно становится скроллпортом.
58
+ */
59
+ export declare function isScrollableOverflow(overflowY: string): boolean;
60
+ /**
61
+ * Доехали ли до дна.
62
+ *
63
+ * `maxScrollTop > 1` — обязательное условие, а не перестраховка: содержимое
64
+ * короче скроллпорта даёт «дно» на нулевой прокрутке, и правило `atEnd`
65
+ * подсветило бы последний раздел на странице, где позиции чтения нет вовсе.
66
+ */
67
+ export declare function isScrolledToEnd(scrollTop: number, maxScrollTop: number): boolean;