strat-gameplay-webapp/frontend-sba/.claude/MOBILE_TOUCH_PATTERNS.md
Cal Corum 52706bed40 CLAUDE: Mobile drag-drop lineup builder and touch-friendly UI improvements
- 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>
2026-01-17 22:17:16 -06:00

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:

  1. Draggable elements - Any element using vuedraggable, SortableJS, or native drag-and-drop
  2. Interactive cards/list items - Items users tap frequently
  3. Custom sliders/controls - Touch-based UI controls
  4. Bottom sheets/modals - Draggable overlays
  5. Swipeable elements - Carousels, dismissible items

When NOT to Apply

Do NOT apply to:

  1. Form inputs - Text fields, textareas need selection
  2. Read-only content - Articles, documentation, static text
  3. 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