Improve routing rule drag handling and controls
Build and Deploy Gateway / build-and-push (push) Successful in 34s
Build and Deploy Gateway / deploy (push) Successful in 6s

This commit is contained in:
2026-08-17 16:48:46 +03:00
parent f3be0b2fd0
commit 286a89051a
9 changed files with 112 additions and 92 deletions
+32 -19
View File
@@ -83,6 +83,7 @@ interface RuleDragSession extends RuleReorderLifecycle {
pointerOffsetY: number;
translateY: number;
raf: number | null;
capture: HTMLElement;
handle: HTMLButtonElement;
}
@@ -447,9 +448,10 @@ export function useRoutingFeature({
const lifecycle = beginRuleReorder(dragRef.current, ruleKey, 'pointer');
if (!lifecycle) return;
const row = event.currentTarget.closest<HTMLElement>('[data-rule-key]');
if (!row) return;
const capture = event.currentTarget.closest<HTMLElement>('.client-local-rules-list');
if (!row || !capture) return;
const rect = row.getBoundingClientRect();
event.currentTarget.setPointerCapture(event.pointerId);
capture.setPointerCapture(event.pointerId);
dragRef.current = {
...lifecycle,
originRules: [...rulesRef.current],
@@ -459,11 +461,12 @@ export function useRoutingFeature({
pointerOffsetY: event.clientY - (rect.top + rect.height / 2),
translateY: 0,
raf: null,
capture,
handle: event.currentTarget,
};
}
function movePointerReorder(event: ReactPointerEvent<HTMLButtonElement>) {
function movePointerReorder(event: ReactPointerEvent<HTMLElement>) {
const session = dragRef.current;
if (!session || session.pointerId !== event.pointerId) return;
session.latestY = event.clientY;
@@ -514,9 +517,9 @@ export function useRoutingFeature({
if (
outcome.releasePointerCapture
&& session.pointerId !== null
&& session.handle.hasPointerCapture(session.pointerId)
&& session.capture.hasPointerCapture(session.pointerId)
) {
session.handle.releasePointerCapture(session.pointerId);
session.capture.releasePointerCapture(session.pointerId);
}
if (session.lifted) {
if (reason === 'drop' && session.input === 'pointer') suppressHandleClickRef.current = true;
@@ -536,17 +539,17 @@ export function useRoutingFeature({
return finishReorder('cancel', announce);
}
function finishPointerReorder(event: ReactPointerEvent<HTMLButtonElement>) {
function finishPointerReorder(event: ReactPointerEvent<HTMLElement>) {
if (dragRef.current?.pointerId !== event.pointerId) return;
dropReorder();
}
function cancelPointerReorder(event: ReactPointerEvent<HTMLButtonElement>) {
function cancelPointerReorder(event: ReactPointerEvent<HTMLElement>) {
if (dragRef.current?.pointerId !== event.pointerId) return;
cancelReorder();
}
function losePointerReorder(event: ReactPointerEvent<HTMLButtonElement>) {
function losePointerReorder(event: ReactPointerEvent<HTMLElement>) {
if (dragRef.current?.pointerId !== event.pointerId) return;
finishReorder('lost-capture');
}
@@ -569,6 +572,7 @@ export function useRoutingFeature({
pointerOffsetY: 0,
translateY: 0,
raf: null,
capture: handle,
handle,
};
setLiftedKey(ruleKey);
@@ -929,6 +933,10 @@ export function RoutingPanel({ feature, statusSlot }: { feature: RoutingFeature;
className={`client-local-rules-list${draftRules.length > 1 ? ' has-order-flow' : ''}`}
role="list"
aria-label="Правила применяются сверху вниз"
onPointerMove={feature.movePointerReorder}
onPointerUp={feature.finishPointerReorder}
onPointerCancel={feature.cancelPointerReorder}
onLostPointerCapture={feature.losePointerReorder}
>
{feature.rules.map((rule, index) => {
const [status, statusLabel] = localRuleStatus(
@@ -959,18 +967,19 @@ export function RoutingPanel({ feature, statusSlot }: { feature: RoutingFeature;
aria-pressed={lifted}
disabled={handleDisabled}
onPointerDown={(event) => feature.startPointerReorder(event, rule._key)}
onPointerMove={feature.movePointerReorder}
onPointerUp={feature.finishPointerReorder}
onPointerCancel={feature.cancelPointerReorder}
onLostPointerCapture={feature.losePointerReorder}
onKeyDown={(event) => feature.handleReorderKey(event, rule._key)}
onClick={(event) => feature.handleReorderClick(event, rule._key)}
onBlur={(event) => feature.handleReorderBlur(event, rule._key)}
>
<svg viewBox="0 0 12 28" aria-hidden="true">
<circle cx="6" cy="6" r="1.6" />
<circle cx="6" cy="14" r="1.6" />
<circle cx="6" cy="22" r="1.6" />
<svg viewBox="0 0 16 28" aria-hidden="true">
<circle cx="5" cy="5" r="1.25" />
<circle cx="11" cy="5" r="1.25" />
<circle cx="5" cy="11" r="1.25" />
<circle cx="11" cy="11" r="1.25" />
<circle cx="5" cy="17" r="1.25" />
<circle cx="11" cy="17" r="1.25" />
<circle cx="5" cy="23" r="1.25" />
<circle cx="11" cy="23" r="1.25" />
</svg>
</button>
<span className="client-local-rule-enabled-wrap client-tooltip-anchor">
@@ -984,11 +993,15 @@ export function RoutingPanel({ feature, statusSlot }: { feature: RoutingFeature;
onClick={() => feature.change(index, 'enabled', !rule.enabled)}
>
<svg viewBox="0 0 20 20" aria-hidden="true">
<rect className="client-rule-switch-track" x="2" y="6" width="16" height="8" rx="4" />
<circle className="client-rule-switch-thumb" cx="6" cy="10" r="2.5" />
<circle cx="10" cy="10" r="6" />
<path className="client-rule-check" d="m6.8 10.1 2.1 2.2 4.5-5" />
</svg>
</button>
<Tooltip>{rule.enabled ? 'Отключить правило' : 'Включить правило'}</Tooltip>
<Tooltip>
{rule.enabled
? 'Отключить: правило перестанет участвовать в маршрутизации'
: 'Включить: правило снова будет участвовать в маршрутизации'}
</Tooltip>
</span>
<RuleTypePicker
value={rule.type}