События и hit-тестирование
Сбор отделён от определения попаданий
Главный поток слушает pointer/wheel/touch с { passive: true }. События, связанные с прокруткой, лишь записывают дельту и метку времени в общий канал: никакого hit-тестирования и никакого setState.
Определение попаданий происходит в ядре: BVH по мировым AABB инкрементально поддерживается вместе со Scene (при изменении топологии перестраивается, при изменении только геометрии выполняется refit). После попадания строится путь root→target и возвращается оболочке по обратному потоку.
Property-тесты гарантируют, что BVH и наивная линейная реализация дают одинаковый результат: у оптимизированного пути всегда есть оракул для сравнения.
Трёхфазное распространение
Модель событий выровнена с DOM: перехват → цель → всплытие.
<container onClickCapture={(event) => log("outer capture", event.eventPhase)}>
<container
onPointerDown={(event) => {
event.preventDefault();
event.stopPropagation();
}}
/>
</container>Доступные обработчики: onPointerDown, onPointerUp, onPointerMove, onPointerCancel, onClick, onWheel, у каждого есть версия *Capture.
DoperEvent предоставляет target, currentTarget, eventPhase, логические координаты внутри canvas x/y, deltaX/deltaY, buttons, клавиши-модификаторы, preventDefault(), stopPropagation() и stopImmediatePropagation().
Проблема времени вызова preventDefault
Пассивный слушатель не может вызвать preventDefault(). Это вопрос корректности, который нужно решать явно, а не деталь, которую можно обойти.
Решение: области, где нужно подавить поведение по умолчанию (например, внутренняя прокручиваемая зона), ядро вычисляет заранее и синхронизирует в главный поток «прямоугольники непассивных областей». Главный поток переводит именно эти области на непассивные слушатели и вызывает preventDefault()синхронно, когда событие попадает внутрь. Поэтому нет гонки, зависящей от асинхронного ответа.
Границы семантики попаданий
Текущая семантика намеренно сужена, чтобы избежать неявного поведения:
- При перекрывающихся попаданиях целью считается «нарисованный последним». Ни z-order, ни отключение попаданий через
pointer-events, ни пропуск невидимых узлов пока не предоставляются. Введение любого из этих пунктов требует явного проектного решения. - Попадания по снимку кадра: все события одной пачки разрешаются относительно геометрии последнего закоммиченного кадра. Прокрутка внутри пачки, меняющая геометрию, повлияет на попадания только со следующего кадра — это обеспечивает атомарный откат пачки событий и детерминированное воспроизведение.
- Ввод с клавиатуры идёт через протокол ввода редактирования и не маскируется под событие попадания.
В демонстрации событий в Playground видно журнал трёхфазного распространения в реальном времени.