Компонент VisualLinker
Рисует соединения между элементами, которые вы размещаете сами. Поместите любой шаблон в default-слот — блоки могут находиться на любой глубине, внутри любых компонентов-обёрток — и пометьте их директивами v-vl-block/v-vl-port, обычными data-vl-* атрибутами или передайте через проп blocks. Компонент не добавляет обёрток на блоки и не трогает вашу разметку или раскладку: он только измеряет помеченные элементы и рисует линии в собственном слое.
<script setup lang="ts">
import { VisualLinker, vVlBlock } from '@macrulez/visual-linker-vue'
const connections = [{ id: 'a-b', from: { blockId: 'a' }, to: { blockId: 'b' } }]
</script>
<template>
<VisualLinker :connections="connections">
<MyLayout>
<MyCard v-vl-block="'a'" />
<SidePanel>
<div data-vl-block="b">Обычный HTML тоже работает</div>
</SidePanel>
</MyLayout>
</VisualLinker>
</template>Как помечать блоки и порты и какому <VisualLinker> принадлежит элемент — смотрите на странице Разметка блоков и портов.
Пропсы
connections
ConnectionDescriptor[] · обязателен
Смотрите Соединения. Отслеживается глубоко — изменение style существующего соединения на месте тоже вызывает перерисовку.
blocks
VisualLinkerBlock[] · по умолчанию: []
Явно переданные блоки — для элементов, которые хранятся рефами в <script>, или элементов, на которые нельзя добавить атрибуты. Та же форма, что у BlockDescriptor ядра (смотрите Блоки и порты), причём каждое поле-элемент принимает также Vue template ref или геттер:
el— template ref, геттер, обычный элемент или CSS-селектор. Селектор ищется внутри области компонента, а приscope="page"— по всему документу. Безelзапись только добавляет конфигурацию (порты, настройки перетаскивания) блоку, уже помеченному в шаблоне с тем жеid.ports—target/anchorElпорта тоже принимают template ref.draggable,dragHandle,dragBounds— как в ядре.
Для одного и того же id поле, заданное здесь, побеждает опции директивы, а те — data-атрибуты.
config
VisualLinkerConfig · по умолчанию: {}
Конфигурация диаграммы — см. Структура конфигурации. Она реактивна: замените объект или измените реактивный, и диаграмма перерисуется с новыми значениями; смена темы или включение lines.jumps не требуют повторного монтирования. Незаданные поля берутся из общей конфигурации приложения (её предоставляют модуль Nuxt и VisualLinkerPlugin), которая сливается с этим config по полям, причём побеждает он, а затем из встроенных значений.
selected
string[] · по умолчанию: — (не задано, выделением владеет движок)
Id выделенных соединений, для v-model:selected — нужен config.interaction.selectable. См. Выделение и доступность.
scope
'container' | 'page' · по умолчанию: 'container'
'container'— блоки находятся где угодно внутри default-слота компонента; линии рисуются в слое внутри собственного бокса компонента.'page'— блоки могут быть в любом месте документа; линии рисуются в слое размером с viewport сposition: fixed, телепортированном в<body>, поэтомуoverflow: hiddenилиtransformу предков не обрезают и не сдвигают его.dragBounds: 'container'в этом случае означает viewport.
name
string · по умолчанию: — (не задано)
Позволяет элементам в других местах страницы закрепиться за этим инстансом через data-vl-linker="<name>" или опцию linker у директив. Если на странице несколько инстансов с scope="page", дайте каждому свой name.
zIndex
number | string · по умолчанию: — (не задано)
z-index слоя, в котором рисуются линии (и слоты-оверлеи).
Любые другие атрибуты (class, style, id, …) попадают на корневой элемент компонента.
События
block-dragstart
Payload: { blockId: string }
block-drag
Payload: { blockId: string; x: number; y: number }
block-dragend
Payload: { blockId: string; x: number; y: number }
block-mouseenter
Payload: { blockId: string }
block-mouseleave
Payload: { blockId: string }
connection-click
Payload: ConnectionDescriptor
connection-mouseenter
Payload: ConnectionDescriptor
connection-mouseleave
Payload: ConnectionDescriptor
connection-selectionchange
Payload: string[] — id выделенных соединений.
connection-delete-request
Payload: ConnectionDescriptor[] — соединения для удаления. Ничего не удаляется, решаете вы.
update:selected
Payload: string[] — для v-model:selected.
Первые восемь один в один повторяют собственные события движка (смотрите API движка), только с именами событий в kebab-case вместо префиксов движка block:/connection:.
Слоты
default
Scope: без scope
Ваша собственная разметка — помеченные в ней блоки и порты находятся на любой глубине. Элементы, добавленные, удалённые или перепомеченные позже (v-if, v-for, сторонняя разметка, выставляющая data-vl-* атрибуты), подхватываются автоматически.
connection-label
Scope: { connection: ConnectionDescriptor; label?: ConnectionLabel; point: Point; angle?: number; rotation?: number; from: Point; to: Point }
Рендерится по одному разу на каждое соединение, абсолютно позиционирован по фактической середине соединения (с учётом формы кривой, а не просто по середине между конечными точками). Для соединения с labels он рендерится вместо этого по одному разу на каждую подпись без text — в её point, повёрнутый на rotation; подписи с text рисует движок — HTML-оверлей внутри слоя отрисовки, так как произвольное содержимое Vue не может рендериться внутри <svg> без особенностей кроссбраузерности <foreignObject>. Строится, только если этот слот реально используется.
port
Scope: { blockId: string; portId?: string; point: Point }
Рендерится по одному разу на каждый посчитанный порт — для кастомного содержимого порта вместо (или, вместе с ports.show, дополнительно к) встроенной точки. Строится, только если этот слот реально используется.
marker
Scope: { connection: ConnectionDescriptor; position: 'start' | 'end'; point: Point; angle: number }
Рендерится для каждой конечной точки соединения, у которой не настроен маркер — ни в config.markers, ни в style.markers соединения — и которая не прижата к краю прокручиваемого контейнера (см. Прокручиваемые контейнеры) — настроенный маркер всё равно побеждает и рендерится как нативный SVG-маркер. Строится, только если этот слот реально используется.
Пример — кастомный маркер и подпись соединения:
<VisualLinker :connections="connections">
<div v-for="item in items" :key="item.id" v-vl-block="item.id" class="card">{{ item.id }}</div>
<template #connection-label="{ connection }">
<span class="badge">{{ connection.id }}</span>
</template>
<template #marker="{ position }">
<span v-if="position === 'end'" class="dot" />
</template>
</VisualLinker>Область страницы
Пример — связь двух списков, отрендеренных разными компонентами:
<template>
<VisualLinker scope="page" name="assign" :connections="connections" :z-index="10" />
<TaskList>
<li v-vl-block="{ id: 't1', linker: 'assign' }">Задача 1</li>
</TaskList>
<OwnerList>
<li data-vl-block="ann" data-vl-linker="assign">Анна</li>
</OwnerList>
</template>Сам <VisualLinker> здесь может оставаться пустым — со scope="page" ему не нужно оборачивать блоки, которые он соединяет.
Итоговая разметка
Компонент рендерит один корневой <div class="vl-container"> вокруг содержимого слота (с position: relative при scope="container"). Слой отрисовки — <div class="vl-layer">, плюс vl-layer--page в режиме страницы — появляется только на клиенте после монтирования, поэтому серверный рендер и проход гидратации совпадают. При scope="container" слой — последний дочерний элемент корня, так что он рисуется поверх содержимого слота без всякой возни с z-index.