- Add vuedraggable for mobile-friendly lineup building - Add touch delay and threshold settings for better mobile UX - Add drag ghost/chosen/dragging visual states - Add replacement mode visual feedback when dragging over occupied slots - Add getBench action to useGameActions for substitution panel - Add BN (bench) to valid positions in LineupPlayerState - Update lineup service to load full lineup (active + bench) - Add touch-manipulation CSS to UI components (ActionButton, ButtonGroup, ToggleSwitch) - Add select-none to prevent text selection during touch interactions - Add mobile touch patterns documentation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
4.3 KiB
4.3 KiB
Mobile Touch Patterns
Text Selection Prevention for Touch Interactions
When building components with drag-and-drop, touch gestures, or interactive elements on mobile, text selection can interfere with the user experience. Users attempting to drag items may accidentally select text instead.
The Problem
On mobile devices:
- Touch-and-hold triggers text selection
- Dragging while text is selected feels broken
- iOS shows callout menus on long press
- Double-tap zoom can interfere with interactions
The Solution
Apply a combination of CSS rules and Tailwind classes to prevent text selection on interactive elements.
CSS Pattern
Add this to your component's <style scoped> section:
/* Prevent text selection on all draggable/interactive elements AND their children */
:deep([draggable="true"]),
:deep([draggable="true"] *),
:deep(.sortable-item),
:deep(.sortable-item *),
.interactive-item,
.interactive-item * {
-webkit-user-select: none !important;
-moz-user-select: none !important;
-ms-user-select: none !important;
user-select: none !important;
-webkit-touch-callout: none !important; /* Prevent iOS callout menu */
}
/* Touch action on containers only (not children) */
:deep([draggable="true"]),
:deep(.sortable-item) {
touch-action: manipulation; /* Allow pan/zoom but optimize for touch */
}
/* Apply to chosen/active drag states */
:deep(.sortable-chosen),
:deep(.sortable-chosen *) {
-webkit-user-select: none !important;
user-select: none !important;
}
Tailwind Classes
Add these classes to interactive container elements:
<div class="select-none touch-manipulation">
<!-- Interactive content -->
</div>
select-none- Prevents text selection (user-select: none)touch-manipulation- Optimizes touch handling (touch-action: manipulation)
When to Apply
Apply this pattern to:
- Draggable elements - Any element using vuedraggable, SortableJS, or native drag-and-drop
- Interactive cards/list items - Items users tap frequently
- Custom sliders/controls - Touch-based UI controls
- Bottom sheets/modals - Draggable overlays
- Swipeable elements - Carousels, dismissible items
When NOT to Apply
Do NOT apply to:
- Form inputs - Text fields, textareas need selection
- Read-only content - Articles, documentation, static text
- Copyable content - Code blocks, IDs, URLs users might copy
Vuedraggable Configuration
When using vuedraggable, also configure touch-friendly options:
const dragOptions = {
animation: 200,
ghostClass: 'drag-ghost',
chosenClass: 'drag-chosen',
dragClass: 'drag-dragging',
// Touch settings for mobile
delay: 50, // Small delay before drag starts
delayOnTouchOnly: true, // Only apply delay on touch devices
touchStartThreshold: 3, // Pixels of movement before drag starts
}
Example Implementation
<template>
<div class="select-none">
<draggable
:list="items"
item-key="id"
v-bind="dragOptions"
class="space-y-2"
>
<template #item="{ element }">
<div class="item-card select-none touch-manipulation cursor-grab active:cursor-grabbing">
<span class="font-medium">{{ element.name }}</span>
</div>
</template>
</draggable>
</div>
</template>
<style scoped>
/* Full pattern from above */
:deep([draggable="true"]),
:deep([draggable="true"] *) {
-webkit-user-select: none !important;
user-select: none !important;
-webkit-touch-callout: none !important;
}
:deep([draggable="true"]) {
touch-action: manipulation;
}
</style>
Testing Checklist
When testing on mobile:
- Can drag items without text selection appearing
- No iOS callout menu on long press
- Drag feels responsive (not delayed)
- Can still scroll the page normally
- Form inputs still allow text selection
- No double-tap zoom interference
References
Created: 2025-01-17 Pattern Source: LineupBuilder.vue mobile drag-and-drop implementation