Состояния и стилизация секций

Booking renderer отслеживает четыре состояния для каждой обводки секции: highlighted, selected, unavailable и filtered. Каждое состояние можно стилизовать независимо с помощью темы svgSectionStyles и переключать программно через API рендерера. Состояния работают одинаково для всех типов обводок – SVG-фонов, фигур, созданных в Editor, автоматически сгенерированных обводок и резервных обводок.

Состояния секций

Каждая обводка секции может иметь ноль или более одновременно активных состояний:

State Trigger Typical use
highlighted Наведение мыши или программно Визуальная обратная связь при наведении
selected Клик пользователя по входной зоне (GA) Секция добавлена в корзину
unavailable disableSections() Распроданные, ограниченные или закрытые секции
filtered filterSections() Показать только часть секций (например, ценовой уровень)

Состояния хранятся как атрибуты data-* на элементах SVG-обводок (data-highlighted, data-selected, data-unavailable, data-filtered) и стилизуются через CSS, сгенерированный из конфигурации вашей темы.

Стилизация с помощью svgSectionStyles

Передайте объект svgSectionStyles внутри theme при создании рендерера. Каждый ключ соответствует состоянию, а каждое значение управляет визуальными свойствами для этого состояния.

import { SeatmapBookingRenderer } from '@seatmap.pro/renderer';

const renderer = new SeatmapBookingRenderer(document.getElementById('seatmap'), {
  publicKey: 'your-public-key',
  theme: {
    svgSectionStyles: {
      default: {
        bgColor: 'rgba(200, 200, 200, 0.2)',
        stroke: { color: '#888', width: '2', opacity: 1 },
        cursor: 'pointer',
      },
      hovered: {
        bgColor: 'rgba(33, 150, 243, 0.5)',
        stroke: { color: '#1565C0', width: '3', opacity: 1 },
        cursor: 'pointer',
      },
      selected: {
        bgColor: 'rgba(76, 175, 80, 0.6)',
        stroke: { color: '#2E7D32', width: '3', opacity: 1 },
        cursor: 'pointer',
      },
      unavailable: {
        bgColor: 'rgba(244, 67, 54, 0.4)',
        stroke: { color: '#C62828', width: '3', opacity: 1 },
        cursor: 'not-allowed',
        opacity: 0.5,
      },
      filtered: {
        bgColor: 'rgba(0, 0, 0, 0.15)',
        stroke: { color: '#999', width: '1', opacity: 0.5 },
        cursor: 'default',
        opacity: 0.3,
      },
    },
  },
});
renderer.loadEvent('your-event-id');

Справочник по свойствам стилей

Каждое состояние принимает следующие свойства:

Property Type Description
bgColor string Цвет заливки обводки. Используйте rgba() для полупрозрачных наложений поверх фона площадки.
stroke.color string Цвет границы обводки.
stroke.width string Толщина границы обводки (например, '2').
stroke.opacity number Непрозрачность границы обводки (от 0 до 1).
sectionName.color string Цвет текста подписи секции внутри обводки.
cursor string CSS-курсор при наведении на секцию (например, 'pointer', 'not-allowed').
opacity number Общая непрозрачность элемента обводки (от 0 до 1). По умолчанию: 0.5 для unavailable, 0.3 для filtered. Значения темы переопределяют значения по умолчанию.

Состояние default принимает все перечисленные выше свойства. Другие состояния (hovered, selected, unavailable, filtered) дополнительно поддерживают bgColor для заливки наложения.

Интерфейс OutlineStates

Рендерер отслеживает состояние секции с помощью интерфейса OutlineStates. Каждое поле – необязательное булево значение; задавайте только те поля, которые хотите изменить.

interface OutlineStates {
  highlighted?: boolean; // Mouse hover or programmatic highlight
  selected?: boolean; // Section added to cart (GA sections)
  unavailable?: boolean; // Disabled -- cannot interact
  filtered?: boolean; // Dimmed by active filter
}

При обратном чтении состояния все четыре поля всегда присутствуют:

interface IEntityStates {
  highlighted: boolean;
  selected: boolean;
  unavailable: boolean;
  filtered: boolean;
}

Справочник по API

Отключение секций

Отметьте секции как недоступные, чтобы запретить взаимодействие. Отключённые секции отображаются со стилем unavailable и игнорируют клики.

// Disable specific sections by ID
renderer.disableSections([101, 102, 103]);

// Disable with reset -- re-enable all previously disabled, then disable these
// Useful for category buttons: each click disables a new set without accumulating
renderer.disableSections([201, 202], { resetAll: true });

// Re-enable specific sections
renderer.enableSections([101, 102, 103]);

Также можно отключать секции по имени:

renderer.disableSectionsByNames(['Floor A', 'Floor B']);
renderer.enableSectionsByNames(['Floor A']);

Фильтрация секций

Фильтрация показывает только указанные вами секции, приглушая все остальные. Это удобно для выбора ценового уровня, фильтрации по доступности или просмотра по категориям.

// Show only these sections -- all others get the "filtered" style
const premiumIds = [101, 102, 103];
renderer.filterSections(premiumIds);

// Remove all filters -- restore all sections to normal
renderer.removeFilterSections();

Логика фильтра инвертирует переданные вами ID: секции, присутствующие в массиве, остаются обычными, а секции, отсутствующие в массиве, получают состояние filtered.

Запрос секций

// Get all sections with their current state
const sections = renderer.getSections();
sections.forEach((section) => {
  console.log(section.id, section.name, section.isGa, section.disabled, section.filtered);
});

Полный список методов

Method Description
disableSections(ids, options?) Отключить секции по числовым ID. Передайте { resetAll: true }, чтобы перед отключением заново включить все.
enableSections(ids) Заново включить ранее отключённые секции.
disableSectionsByNames(names, options?) Отключить секции по строке имени.
enableSectionsByNames(names) Заново включить секции по строке имени.
filterSections(ids, options?) Показать только указанные секции; приглушить все остальные.
removeFilterSections(ids?) Снять фильтр с конкретных секций или со всех, если ID не переданы.
getSections() Возвращает все секции с текущим состоянием (id, name, isGa, disabled, filtered).
setOutlineStates(id, states) Низкоуровневый: задать конкретные состояния секции. Передайте null для сброса. Передайте undefined в качестве id, чтобы применить ко всем.
getOutlineStates(id) Низкоуровневый: прочитать текущее состояние секции. Возвращает все четыре булевых поля.
onSectionStateChange(callback) Зарегистрировать колбэк, вызываемый при изменении состояния любой секции. Получает ID секции.

Миграция со старых имён методов

Прежние имена методов *SvgSections* устарели, но всё ещё работают. Обновите их, когда будет удобно:

Deprecated Replacement
disableSvgSectionsByIds() disableSections()
enableSvgSectionsByIds() enableSections()
disableSvgSectionsByNames() disableSectionsByNames()
enableSvgSectionsByNames() enableSectionsByNames()
filterSvgSectionsByIds() filterSections()
removeFilterSvgSectionsByIds() removeFilterSections()

Новые имена отражают то, что эти методы работают одинаково для всех типов секций, а не только для секций, привязанных к SVG.

При вызове устаревших методов рендерер один раз выводит в консоль предупреждение с рекомендуемой заменой. Чтобы подавить эти предупреждения во время миграции:

const renderer = new SeatmapBookingRenderer(element, {
  publicKey: 'your-public-key',
  suppressDeprecationWarnings: true,
});

Низкоуровневый API состояний

Для продвинутых сценариев – кастомных UI-фреймворков, внешней синхронизации состояния или одновременного применения нескольких состояний – используйте низкоуровневый API состояний напрямую.

// Set multiple states on a single section
renderer.setOutlineStates(101, {
  highlighted: true,
  unavailable: true,
});

// Read current state
const state = renderer.getOutlineStates(101);
// Returns: { highlighted: true, selected: false, unavailable: true, filtered: false }

// Clear all states on a section
renderer.setOutlineStates(101, null);

// Clear all states on all sections (pass undefined as id)
renderer.setOutlineStates(undefined, null);

Низкоуровневый API задаёт состояния напрямую без побочных эффектов (без пересчёта цен, без обновления модели данных). Используйте высокоуровневые методы (disableSections, filterSections) для стандартных сценариев, которые также обновляют модель данных.

Колбэк изменения состояния

Зарегистрируйте колбэк, чтобы получать уведомления при изменении состояния любой секции. Это удобно для поддержания синхронизации внешнего UI (боковой панели, легенды, всплывающей подсказки) с состоянием рендерера.

renderer.onSectionStateChange((sectionId) => {
  const state = renderer.getOutlineStates(sectionId);
  updateSidebar(sectionId, state);
});

Колбэк срабатывает при всех изменениях состояния – наведении, выборе, отключении, фильтрации и программных обновлениях.

Опция resetAll

И disableSections, и filterSections принимают необязательный параметр { resetAll: true }. Когда он установлен, рендерер сначала очищает все ранее применённые состояния этого типа, прежде чем применить новые.

Это удобно для сценариев эксклюзивного выбора, например для кнопок-фильтров по категориям:

// Category filter buttons -- each click shows only that category
document.getElementById('btn-vip').addEventListener('click', () => {
  renderer.filterSections(vipSectionIds, { resetAll: true });
});

document.getElementById('btn-standard').addEventListener('click', () => {
  renderer.filterSections(standardSectionIds, { resetAll: true });
});

document.getElementById('btn-all').addEventListener('click', () => {
  renderer.removeFilterSections();
});

Без resetAll вам пришлось бы вызывать removeFilterSections() перед каждым вызовом filterSections(), что может вызвать заметное мерцание.

Наведение и выбор секций

Подсветка при наведении и выбор секций включаются через настройки рендерера:

const renderer = new SeatmapBookingRenderer(document.getElementById('seatmap'), {
  publicKey: 'your-public-key',
  highlightSectionOnHover: true,
  enableSectionSelection: true,
  onSectionSelect: (section) => {
    console.log('Selected:', section.id, section.name);
    return true; // return true to confirm selection
  },
  onSectionDeselect: (section) => {
    console.log('Deselected:', section.id, section.name);
    return true;
  },
  onSectionMouseEnter: (section) => {
    // Update external UI (tooltip, sidebar, etc.)
  },
  onSectionMouseLeave: () => {
    // Clear external UI
  },
});

Когда highlightSectionOnHover равно true, наведение на секцию применяет стиль hovered. Когда enableSectionSelection равно true, клик по входной зоне (GA) переключает стиль selected и вызывает колбэки выбора/снятия выбора.

Комбинации состояний

Несколько состояний могут быть активны для одной секции. Рендерер применяет CSS с правилами специфичности:

Combination Visual result
selected + наведение Более толстая обводка, полная непрозрачность
unavailable + наведение Слегка увеличенная непрозрачность (по-прежнему показывает недоступность)
selected + unavailable Отображается с пониженной непрозрачностью (визуально приоритет у unavailable)

Типы обводок

Рендерер создаёт обводки секций из нескольких источников. Все четыре типа реагируют на состояния и стилизацию одинаково:

Source Origin Attribute
SVG-фон SVG плана зала с привязками секций data-outline-source="svg"
Фигура Прямоугольники, полигоны, пути, созданные в Editor data-outline-source="shape"
Авто Обводки, сгенерированные Editor из групп мест data-outline-source="auto"
Резервная Сгенерированная во время выполнения из позиций мест data-outline-source="fallback"

Вам не нужно обрабатывать эти типы по-разному. Все публичные методы API работают единообразно для всех типов. Атрибут data-outline-source – это внутренняя деталь, доступная только для отладки.

Видимость обводок во время анимации

Во время анимаций панорамирования и масштабирования слой SVG-обводок по умолчанию скрывается ради производительности. Обводки появляются снова после завершения анимации. Чтобы оставлять обводки видимыми во время анимаций (ценой производительности), установите:

const renderer = new SeatmapBookingRenderer(element, {
  publicKey: 'your-public-key',
  showOutlineLayerOnAnimation: true,
});

Полный пример

Этот пример создаёт рендерер со стилизованными состояниями, отключает распроданные секции и фильтрует до конкретного ценового уровня по действию пользователя:

import { SeatmapBookingRenderer } from '@seatmap.pro/renderer';

const renderer = new SeatmapBookingRenderer(document.getElementById('seatmap'), {
  publicKey: 'your-public-key',
  highlightSectionOnHover: true,
  enableSectionSelection: true,
  theme: {
    svgSectionStyles: {
      default: {
        bgColor: 'rgba(100, 100, 100, 0.1)',
        stroke: { color: '#666', width: '1', opacity: 0.8 },
        cursor: 'pointer',
      },
      hovered: {
        bgColor: 'rgba(33, 150, 243, 0.4)',
        stroke: { color: '#1976D2', width: '2', opacity: 1 },
      },
      selected: {
        bgColor: 'rgba(76, 175, 80, 0.5)',
        stroke: { color: '#388E3C', width: '2', opacity: 1 },
      },
      unavailable: {
        bgColor: 'rgba(158, 158, 158, 0.3)',
        stroke: { color: '#9E9E9E', width: '1', opacity: 0.5 },
        cursor: 'not-allowed',
        opacity: 0.4,
      },
      filtered: {
        bgColor: 'rgba(0, 0, 0, 0.08)',
        stroke: { color: '#ccc', width: '1', opacity: 0.3 },
        cursor: 'default',
        opacity: 0.2,
      },
    },
  },
  onSectionSelect: (section) => {
    console.log('Added to cart:', section.name);
    return true;
  },
  onSectionDeselect: (section) => {
    console.log('Removed from cart:', section.name);
    return true;
  },
});

renderer.loadEvent('your-event-id').then(() => {
  // Disable sold-out sections after event loads
  const soldOutIds = [301, 302];
  renderer.disableSections(soldOutIds);
});

// Filter to a price tier when user clicks a button
document.getElementById('filter-premium').addEventListener('click', () => {
  const premiumIds = [101, 102, 103];
  renderer.filterSections(premiumIds);
});

document.getElementById('clear-filter').addEventListener('click', () => {
  renderer.removeFilterSections();
});

Playground

Используйте интерактивный playground для тестирования всех состояний секций в реальном времени. Загрузите пресет unifiedSectionStates по адресу /playground/?preset=unifiedSectionStates, чтобы увидеть:

  • Стилизованные обводки для всех типов обводок (SVG, фигура, резервная)
  • Кнопки для отключения, включения, фильтрации, снятия фильтра, выбора и сброса
  • DOM-инспектор, показывающий в реальном времени атрибуты data-*, вычисленные стили и значения курсора
  • Логирование в консоль для всех событий секций