Блоки и порты
Блок — это любой DOM-элемент, который уже находится под вашим контролем — движок только измеряет его (и его порты) и никогда не трогает содержимое или раскладку, кроме опциональной трансформации при перетаскивании.
Определение блока
BlockDescriptor
id
string
Уникальный id блока, на который ссылаются from.blockId/to.blockId соединения.
el
HTMLElement
Собственный элемент блока.
ports
PortDescriptor[] · по умолчанию: — (не задано)
Явные порты — смотрите «Определение порта» ниже. Если не задано, соединения крепятся прямо к границе блока, с side: 'auto'.
draggable
boolean · по умолчанию: берётся из blocks.draggable
Переопределяет дефолт уровня инстанса для этого конкретного блока.
dragHandle
string | HTMLElement · по умолчанию: — (перетаскивание начинается с любой точки блока)
CSS-селектор или элемент внутри el, с которого начинается перетаскивание вместо всего блока.
dragBounds
DragBounds · по умолчанию: берётся из blocks.drag.bounds
Переопределяет дефолт уровня инстанса для этого конкретного блока. Три допустимые формы — смотрите «Перетаскивание» ниже.
portSpread
boolean | { gap?: number; padding?: number } · по умолчанию: берётся из ports.spread
Разводит соединения, делящие сторону порта этого блока, по отдельным виртуальным портам. spread самого порта переопределяет его. См. Разведение соединений по порту.
highlightable
boolean · по умолчанию: берётся из interaction.highlight
Включает или выключает состояние highlight соединений этого блока, пока курсор над блоком; локальное значение сильнее глобального. См. Визуальные состояния.
Пример — все поля сразу:
linker.setBlocks([
{
id: 'task-1', // уникальный id — соединения ссылаются на него через from.blockId/to.blockId
el: taskCardEl, // собственный элемент блока
ports: [{ id: 'output', side: 'right', offset: 0.5 }], // опционально, см. «Определение порта» ниже — можно не задавать совсем, тогда соединения крепятся к границе блока
draggable: true, // этот блок можно перетаскивать, даже если дефолт уровня инстанса — false
dragHandle: '.card-header', // перетаскивание начинается только с этой части блока — без этого поля начинает весь блок
dragBounds: 'container', // этот блок нельзя перетащить за пределы собственного контейнера диаграммы
portSpread: { gap: 20 }, // соединения, делящие сторону этого блока, получают каждое свой виртуальный порт на расстоянии 20px
highlightable: true, // наведение на этот блок подсвечивает его соединения, даже если interaction.highlight равен false
},
])Определение порта
PortDescriptor
Массив ports у блока позволяет соединению крепиться к чему-то более конкретному, чем граница блока — к определённой строке списка, к конкретному полю формы.
id
string
Id порта, на который ссылается portId конечной точки соединения.
target
string | HTMLElement · по умолчанию: — (собственный элемент блока)
CSS-селектор (резолвится относительно el блока) или прямой элемент — куда на самом деле садится точка подключения порта.
side
PortSide | FixedSide[] · по умолчанию: 'auto'
'auto' определяет сторону входа/выхода из положения второго конца соединения при каждом рендере, выбирая ту сторону, что лучше всего смотрит на неё. FixedSide[] (подмножество 'top' | 'right' | 'bottom' | 'left') сужает этот автоматический выбор — например, ['left', 'right'], чтобы полностью исключить выход сверху/снизу. Одна фиксированная сторона закрепляет её жёстко.
offset
number · по умолчанию: 0.5
Позиция вдоль выбранной стороны, 0..1 (0.5 — по центру). Игнорируется, если задан anchorBlockId/anchorEl.
anchorBlockId
string · по умолчанию: — (не задано)
Рисует точку подключения этого порта на границе другого зарегистрированного блока вместо границы target — полезно, когда target — это дочерний элемент, вложенный глубоко внутрь контейнера (например, строка внутри блока-группы), но соединение визуально должно выходить с края самого контейнера. Точка при этом всё равно отслеживает реальное положение target, спроецированное на границу блока-якоря, так что соседние порты, закреплённые на одном и том же блоке, сохраняют свой относительный порядок, а не схлопываются в одну точку. Определение стороны (включая 'auto') идёт по блоку-якорю, а не по target. Игнорируется, если также задан anchorEl.
anchorEl
HTMLElement · по умолчанию: — (не задано)
Как anchorBlockId, но крепится прямо к заданному элементу вместо el зарегистрированного блока — для привязки к элементу, который ещё не (или вообще не) является одним из зарегистрированных блоков движка. Имеет приоритет над anchorBlockId, если заданы оба.
spread
boolean | { gap?: number; padding?: number } · по умолчанию: берётся из portSpread блока
Разводит соединения, делящие сторону этого порта. false исключает порт, даже если его блок разводит соединения. См. Разведение соединений по порту.
Пример — все поля сразу:
{
id: 'output', // на него ссылается from.portId/to.portId соединения
target: '.output-row', // CSS-селектор внутри блока — без него используется собственный элемент блока
side: ['right', 'bottom'], // 'auto' здесь может выбрать только right или bottom, никогда top/left
offset: 0.25, // в этом примере игнорируется, так как anchorBlockId ниже переопределяет, где на самом деле сидит точка
anchorBlockId: 'group-1', // точка рисуется на границе блока "group-1", но всё равно отслеживает реальное положение target
spread: true, // соединения, делящие сторону этого порта, получают виртуальные порты
}Если вместо anchorBlockId задать anchorEl: someElement, порт крепится прямо к этому элементу, а не к зарегистрированному блоку — и побеждает, если заданы оба поля одновременно.
Перетаскивание
Любой блок можно сделать перетаскиваемым — задайте blocks.draggable: true в конфигурации для всех блоков либо draggable у одного блока (BlockDescriptor.draggable, побеждает, если задано). Перетаскивание управляется указателем, двигает блок через отдельное CSS-свойство translate — поэтому сдвиг складывается с уже имеющейся у блока transform, а не затирает её, — а каждое связанное соединение перестраивается в реальном времени по мере движения блока — без ручного вызова повторного рендера.
const linker = createVisualLinker(diagramEl, {
blocks: {
draggable: true, // любой блок можно перетаскивать, если сам не задаёт draggable: false
drag: {
grid: 20, // перетаскиваемые блоки привязываются к общей сетке 20px, так что независимо передвинутые блоки всё равно совпадают по линиям
bounds: 'container', // ни один блок нельзя перетащить за пределы собственного контейнера диаграммы
},
},
})
linker.setBlocks([
{
id: 'task-1', // id этого блока
el: taskCardEl, // собственный элемент этого блока
dragHandle: '.card-header', // перетаскивание начинает только заголовок — остальная часть карточки инертна
},
{
id: 'task-2', // id этого блока
el: otherCardEl, // собственный элемент этого блока
draggable: false, // этот блок исключён, даже если дефолт инстанса выше — true
},
])dragBounds (для всех блоков через blocks.drag.bounds или для конкретного блока через BlockDescriptor.dragBounds) ограничивает позицию перетаскиваемого блока так, что его собственный прямоугольник никогда не выходит за границы:
'container'— собственный элемент-контейнер движка, как в примере выше.HTMLElement— произвольная область элемента (например, отдельная drop-зона в другом месте раскладки, не обязательно сам контейнер).- объект
DragBoundsInset({ top?, right?, bottom?, left? }, в px, незаданные стороны по умолчанию0) — собственная область контейнера, уменьшенная на эти отступы, например{ top: 16, bottom: 16 }, чтобы перетаскиваемые блоки не заходили под перекрывающие контейнер шапку/подвал.
Не задано ни на одном из уровней — без ограничений, поведение по умолчанию.
События перетаскивания (block:dragstart/block:drag/block:dragend) описаны на странице API движка.