VGSpy

Анимированный скроллинг шпион

Вложенная навигация

Родительский пункт, вложенные секции и VGDropdown.

VGSpy добавляет класс active ссылке текущей секции. Прокрутите область примера или выберите пункт навигации. Каждый пример независим; оформление активных ссылок задаёт приложение.

Группа разделов и выпадающее меню

При выборе вложенной секции активируется и родительская .nav-link. Пункт .vg-dropdown-item активирует переключатель VGDropdown. Вернитесь к «Обзору» — предыдущие выделения должны исчезнуть.

Обзор

Обычная ссылка активируется без родительской группы.

Руководство

Родительский пункт стоит перед вложенной .nav.

Установка

Одновременно выделяются «Установка» и «Руководство».

Конфигурация

Родитель остаётся активным при переходе между подразделами.

Вопросы

Пункт .vg-dropdown-item активирует кнопку «Справка».

Поддержка

Вернитесь к обзору и проверьте очистку выделения выпадающего меню.

HTML
<div class="spy-demo">
    <nav class="spy-demo__nav" id="spy-nested-nav" aria-label="Структура руководства"
         data-vg-toggle="spy" data-target="#spy-nested-scroll">
        <a href="#spy-nested-overview">Обзор</a>
        <div>
            <a class="nav-link" href="#spy-nested-guide">Руководство</a>
            <div class="nav">
                <a class="nav-link" href="#spy-nested-install">Установка</a>
                <a class="nav-link" href="#spy-nested-config">Конфигурация</a>
            </div>
        </div>
        <div class="vg-dropdown">
            <button class="btn btn-surface btn-sm" type="button" data-vg-toggle="dropdown"
                    data-vg-target="#spy-nested-menu" aria-expanded="false">Справка</button>
            <div class="vg-dropdown-content" id="spy-nested-menu">
                <div class="vg-dropdown-container">
                    <a class="vg-dropdown-item" href="#spy-nested-faq">Вопросы</a>
                    <a class="vg-dropdown-item" href="#spy-nested-support">Поддержка</a>
                </div>
            </div>
        </div>
    </nav>
    <div class="spy-demo__scroll" id="spy-nested-scroll" tabindex="0" role="region" aria-label="Руководство: содержимое">
        <section class="spy-demo__section" id="spy-nested-overview"><h4>Обзор</h4><p>Обычная ссылка активируется без родительской группы.</p></section>
        <section class="spy-demo__section" id="spy-nested-guide"><h4>Руководство</h4><p>Родительский пункт стоит перед вложенной .nav.</p></section>
        <section class="spy-demo__section" id="spy-nested-install"><h4>Установка</h4><p>Одновременно выделяются «Установка» и «Руководство».</p></section>
        <section class="spy-demo__section" id="spy-nested-config"><h4>Конфигурация</h4><p>Родитель остаётся активным при переходе между подразделами.</p></section>
        <section class="spy-demo__section" id="spy-nested-faq"><h4>Вопросы</h4><p>Пункт .vg-dropdown-item активирует кнопку «Справка».</p></section>
        <section class="spy-demo__section" id="spy-nested-support"><h4>Поддержка</h4><p>Вернитесь к обзору и проверьте очистку выделения выпадающего меню.</p></section>
    </div>
</div>
JavaScript
/**
 * Описание: вложенная навигация VGSpy с VGDropdown.
 * Возможности: активация родительских ссылок и переключателя меню.
 */
// Дополнительная инициализация не требуется: используется Data API.
// Общий JS vgapp подключает VGSpy и VGDropdown; стили — vgapp/scss.
CSS
/* Композиция демо; семантические токены OKAUX на сайте используют префикс vg. */
.spy-demo {
    display: grid;
    gap: 1rem;
    min-width: 0;
}
.spy-demo__nav, .spy-demo__controls {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: .5rem;
}
.spy-demo__nav a, .spy-demo__nav button {
    display: inline-flex;
    padding: .5rem .75rem;
    border: 1px solid var(--vg-border-default);
    border-radius: .5rem;
    background: var(--vg-background-primary);
    color: var(--vg-text-primary);
    text-decoration: none;
    font: inherit;
}
.spy-demo__nav .active {
    background: var(--vg-background-tertiary);
    border-color: var(--vg-border-active);
    box-shadow: inset 0 -3px var(--vg-border-active);
    font-weight: 700;
}
.spy-demo__nav :is(a, button):focus-visible {
    outline: 2px solid var(--vg-border-active);
    outline-offset: 2px;
}
.spy-demo__nav .nav {
    display: grid;
    gap: .25rem;
    padding: .5rem 0 0 1rem;
}
.spy-demo__nav .vg-dropdown-container {
    display: grid;
    gap: .25rem;
    padding: .5rem;
}
.spy-demo__scroll {
    height: 20rem;
    overflow-y: auto;
    overscroll-behavior: contain;
    border: 1px solid var(--vg-border-default);
    border-radius: .75rem;
    background: var(--vg-background-primary);
    color: var(--vg-text-primary);
}
.spy-demo__section {
    min-height: 18rem;
    padding: 1.25rem;
    border-bottom: 1px solid var(--vg-border-default);
    overflow-wrap: anywhere;
}
.spy-demo__section:nth-child(even) { background: var(--vg-background-secondary); }
.spy-demo__section h4 { margin: 0 0 .75rem; }
.spy-demo__log {
    max-height: 10rem;
    overflow: auto;
    margin: 0;
    padding-left: 1.5rem;
    color: var(--vg-text-secondary);
    overflow-wrap: anywhere;
}
.spy-demo__empty { padding: 1.25rem; color: var(--vg-text-secondary); }
@media (max-width: 575px) {
    .spy-demo__scroll { height: 17rem; }
    .spy-demo__section { min-height: 16rem; padding: 1rem; }
}

Разметка и оформление

href="#id" / data-vg-target="#id"
Секция ищется по уникальному id в документе. data-vg-target имеет приоритет над href. Не используйте несколько ссылок на один id в одной навигации: карта хранит одну ссылку на секцию.
.nav / .list-group
Для подсветки родителя вложенная группа располагается после его .nav-link или .list-group-item. Произвольная вложенность без этих классов не создаёт родительское выделение.
.vg-dropdown-item
Пункт внутри .vg-dropdown подсвечивает элемент [data-vg-toggle="dropdown"]. Меню открывает VGDropdown; VGSpy управляет только активным состоянием.
active
У VGSpy нет собственного SCSS-входа и CSS-переменных. Стили примеров используют семантические токены OKAUX 1.0.2. Состояние темы остаётся в OKAUX; адаптер vgapp/theme уже подключён к тому же data-theme.
Видимость
Обычные секции наблюдаются через IntersectionObserver. Для скрытых вкладок и панелей сначала покажите содержимое, затем вызовите refresh(). Не добавляйте искусственную высоту скрытым панелям ради навигации.