Rewrite Features config drag-drop with pointer/touch events

Replace HTML5 Drag and Drop API with pointer/touch event handling
for better mobile support:

- Add pointer event handlers for desktop/stylus drag
- Add global touch event listeners for mobile drag
- Track drag state with refs for consistent behavior
- Calculate drop position based on Y coordinate
- Improve Cancel to restore from initial snapshot

Also improve SwipeTabs to ignore interactive elements (buttons,
links, inputs) during swipe detection, and add touch-action: none
to order cards for proper touch handling.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-22 18:41:25 +01:00
parent 6adcf75ef1
commit 1ff1103c67
3 changed files with 337 additions and 92 deletions

View File

@@ -35,7 +35,15 @@ const findTouchById = (touches: React.TouchList, id: number): React.Touch | null
return touch;
}
}
return null;
return null;
};
const resolveSwipeTarget = (target: EventTarget | null): Element | null => {
if (!target) return null;
const element = target as Element;
if (typeof element.closest === 'function') return element;
const node = target as Node & { parentElement?: Element | null };
return node.parentElement ?? null;
};
export function SwipeTabs<T extends string | number>({
@@ -80,8 +88,18 @@ export function SwipeTabs<T extends string | number>({
});
const shouldIgnoreSwipe = useCallback((target: EventTarget | null) => {
if (!target || typeof (target as Element).closest !== 'function') return false;
return Boolean((target as Element).closest('[data-swipe-ignore="true"]'));
const element = resolveSwipeTarget(target);
if (!element) return false;
if (element.closest(
'[data-swipe-ignore="true"], button, a, input, select, textarea, [role="button"], [role="link"], [role="switch"], [contenteditable="true"]'
)) {
return true;
}
const label = element.closest('label');
if (label && label.querySelector('input, select, textarea, button')) {
return true;
}
return false;
}, []);
const applyTransform = useCallback((offset: number, animate: boolean) => {