Skip to content

Сложные события через :on

Аннотация :on связывает событие visual SFC-тега с одной или несколькими реакциями. Она использует тот же trigger-контракт, что и edit-on, поэтому комбинации клавиш, удерживаемые клавиши и pointer button описываются одинаково.

Базовый пример

vue
<Text
  value="Открыть"
  :on="{
    event: 'click',
    reaction: action({
      identity: 'order.open',
      input: { id: rowId },
    }),
  }"
/>

В обычной форме event задаётся статической строкой. В ссылочной форме compiler получает допустимую event-поверхность тега, а активные triggers вычисляются из effective configuration во время рендера. reaction является Source-синтаксисом, а не JavaScript callback.

Trigger-контракт

ПолеТипНазначение
eventstringИмя intrinsic или объявленного semantic Event.
keystring или string[]Допустимые KeyboardEvent.key, без учёта регистра.
codestring или string[]Физические KeyboardEvent.code, не зависящие от раскладки.
held.key / held.codestring[]Обычные клавиши, удерживаемые во время события.
held.matchall или anyТребовать все клавиши или хотя бы одну. По умолчанию all.
held.exactbooleanЗапретить дополнительные удерживаемые обычные клавиши.
modifiersobjectctrl, shift, alt, meta, mod, altGraph, exact.
buttonnumberPointer/mouse button. Основная кнопка — 0, правая — 2.
repeatbooleanРазрешить или запретить повторный keydown.
composingbooleanСостояние IME composition.

mod означает Command на macOS и Control на Windows/Linux. Значение false является явным условием. Например, shift: false запрещает Shift. exact запрещает физические modifiers, не упомянутые в объекте.

vue
<Box
  :on.stop.prevent="{
    event: 'contextmenu',
    button: 2,
    held: { code: ['KeyW'], exact: true },
    modifiers: { shift: true, exact: true },
    reaction: action({
      identity: 'cell.mark',
      input: { rowId, columnKey, event: event() },
    }),
  }"
/>

Modifiers аннотации

ModifierСемантика
.stopСинхронно вызывает stopPropagation() и прекращает semantic forwarding occurrence.
.preventСинхронно вызывает preventDefault() до запуска reactions.
.selfСрабатывает только когда target === currentTarget.
.onceВыполняет совпавшее правило один раз за mount этого узла.
.captureУстанавливает listener на capture-фазе.
.passiveУстанавливает passive listener.

Эти флаги можно записывать и boolean-полями правила. Суффикс применяется ко всем правилам аннотации и имеет приоритет над false внутри правила. passive нельзя объединять с prevent: compiler выдаст sfc-template-on-passive-prevent. Поля capture и passive внутри объекта должны быть статическими boolean.

self проверяется до once. once потребляется после совпадения и до запуска reaction, поэтому ошибка Action не активирует правило повторно.

Несколько правил и reactions

Массив правил использует first-match-wins. После первого совпадения остальные правила этой аннотации не проверяются для данного occurrence.

vue
<Text
  :value="row.name"
  :on="[
    {
      event: 'keydown',
      code: ['Enter'],
      reaction: action({ identity: 'row.open', input: { id: rowId } }),
    },
    {
      event: 'click',
      reaction: [
        action({ identity: 'selection.set', input: { id: rowId } }),
        query({ identity: 'details.refresh', input: { id: rowId } }),
        emit('rowSelected', { id: rowId }),
      ],
    },
  ]"
/>

Reactions выполняются последовательно и ожидаются через await. Ошибка останавливает оставшуюся последовательность. Результат одной reaction не становится input следующей автоматически. Поддерживаются action(), query(), emit() и typescript().

TriggerSet из effective configuration

Когда между окружениями должен меняться только способ запуска, используйте ссылочную форму { triggers, reaction }:

vue
<Cell
  :on="{
    triggers: $context.config.groundHandling.actualTimeTriggers,
    reaction: query({
      identity: 'groundHandling.actualTime.update',
      input: {
        legId: row.arrivalLeg.id,
        station: row.arrivalLeg.latestArrivalStationIataCode,
        code: 'Bridge On',
        point: 'value',
        value: now(),
        comment: null,
      },
    }),
  }"
>
  <GroundHandlingProcess
    :process="row.arrivalLeg.groundHandling[code = 'Bridge On']"
  />
</Cell>

triggers принимает runtime expression, возвращающий TriggerSet. Каждый элемент массива является самостоятельным правилом, а порядок сохраняет first-match-wins. Event должен входить в event-поверхность текущего тега.

Configuration может определить для одного Environment contextmenu, для другого click, а для третьего keydown с modifiers и held keys. Reaction и Query identity при этом не меняются. Пустой TriggerSet не устанавливает listener-ы и отключает реакцию.

Ссылочная форма не включает editable и не публикует edited: совпавший trigger немедленно запускает указанную reaction.

Значения event() и now()

event() возвращает сериализуемый snapshot, а не DOM Event. В нём доступны:

ГруппаДоступные пути event('…')Значение
Все intrinsic eventstypeИмя события, например click или keydown.
Все intrinsic eventsoccurredAtISO 8601 UTC timestamp исходного occurrence.
Modifiersmodifiers.alt, modifiers.altGraph, modifiers.ctrl, modifiers.meta, modifiers.shiftСостояние клавиш-модификаторов.
Held keysheld.key, held.codeМассивы удерживаемых логических клавиш и физических кодов.
Pointer/mouse/dragx, y, button, buttons, pointerTypeКоординаты и состояние указателя. Правая кнопка имеет button: 2.
WheeldeltaX, deltaYГоризонтальное и вертикальное смещение колеса/trackpad.
Keyboardkey, code, repeat, composingДанные клавиатурного события.
Input controlsvalue, checkedТекущее значение; для multiple Select value является массивом строк.

Примеры чтения:

ts
event()                    // весь snapshot
event('type')              // 'contextmenu'
event('occurredAt')        // время исходного события
event('modifiers.shift')   // true или false
event('held.code')         // ['Space']
event('button')            // 2
event('key')               // 't'
event('value')             // значение Input/Select

Dot-path может читать и поля semantic/custom Event. Например, для payload { row: { id: 'leg-1' } } доступно event('row.id'). Если путь отсутствует в конкретном payload, результатом будет undefined.

Встроенные semantic Events Table имеют собственные typed payload:

Table EventДоступные пути
Все Table EventstableId
rowActivatedrowId, rowIndex, row, columnKey, activation
rowContextMenuRequestedrowId, rowIndex, row, columnKey, anchor.x, anchor.y
selectionChangedmode, selectedRowIds, selectedRows, addedRowIds, removedRowIds
sortChangedsort и вложенные sort.<index>.columnKey, sort.<index>.direction
columnVisibilityChangedvisibility, hiddenColumnKeys
columnPinChangedleft, right
columnOrderChangedcolumnKeys
columnSizeChangedsizes, changedColumnKey
pageChangedpageIndex, pageSize, pageCount

Например: event('rowId'), event('anchor.x') или event('selectedRowIds'). Пользовательские Events предоставляют поля своего payloadType; event(path) не ограничивает их заранее общим DOM-каталогом.

now() не читает Event. Это безопасный DSL-примитив, который возвращает текущее время начала выполнения reaction как ISO 8601 UTC-строку:

ts
query({
  identity: 'groundHandling.actualTime.update',
  input: {
    value: now(),
    sourceEventAt: event('occurredAt'),
  },
})

Все вложенные использования now() внутри одного input получают одно значение. Используйте now() для команды «установить текущее время», а event('occurredAt') — когда важно сохранить именно время исходного browser occurrence.

Помимо event() и now(), input поддерживает JSON literals, массивы, объекты и lexical SFC scope: row, rowKey, rowIndex, columnKey, value и другие доступные в текущем узле locals.

now() относится только к безопасному DSL input для Event reactions (action, query, emit и Composition event effects). Это не глобальная функция ValueExpression и не добавляет скрытые часы в defineComputation.

Совместимость с @event и editable

Простая запись остаётся доступна:

vue
<Text @click="action({ identity: 'order.open' })" />

Она использует тот же runtime Event-механизм без дополнительных условий. @click и :on на одном узле являются независимыми локальными обработчиками.

edit-on использует тот же trigger descriptor:

vue
<Text
  :value="row.name"
  editable
  :edit-on="{ event: 'click', held: { code: ['KeyW'] } }"
  :on="{ event: 'click', held: { code: ['KeyW'] }, reaction: action({ identity: 'audit.edit-opened' }) }"
/>

Один жест может запустить reaction и открыть editable. .stop не отменяет обработчик того же VNode.

Где доступен :on

Аннотация работает на visual tags с собственной event-поверхностью: Text, DateTime, Number, Icon, Badge, Dot, Box, Flex, Grid, Divider, Input, Textarea, Checkbox, Select, Table и Cell.

Cell является специальной структурной границей: адаптер связывает его :on с реальной renderer-owned поверхностью табличной ячейки. Остальные структурные Column, Variant, ColumnMenu, RowMenu, MenuItem и MenuSeparator не создают самостоятельный event target; compiler выдаёт диагностику.

У вложенного пользовательского SFC можно обрабатывать Events его typed manifest. Для raw DOM-жеста используйте visual wrapper: Vue fallthrough и форма корневого узла дочернего компонента не являются частью renderer-neutral контракта.