SNAP_IMPROVEMENTS.md 4.3 KB

Grid Snapping Improvements - More Forgiving Algorithm

Problem

The snapping was too strict - it would only snap when the widget was almost perfectly positioned over a cell. Users had to be very precise, making it difficult to snap widgets into cells.

Solution

1. Increased Snap Threshold

// Before
threshold: 30  // Only 30 pixels - too strict

// After  
threshold: 500  // Much larger area - very forgiving

The threshold is now 500 pixels, which means the widget can be anywhere reasonably near a cell and it will consider snapping.

2. Overlap-Based Snapping

The algorithm now prioritizes overlap over distance:

Old Algorithm:

  • Primary metric: Distance from widget center to cell center
  • Secondary metric: Overlap as a tiebreaker
  • Problem: Would snap to closest cell even with minimal overlap

New Algorithm:

  • Primary metric: Overlap ratio (how much of the widget overlaps the cell)
  • Secondary metric: Distance as a tiebreaker
  • Minimum overlap required: 15% (minOverlap: 0.15)
  • Result: Only snaps when widget is actually ON the cell

3. How It Works Now

const SNAP_CONFIG = {
  threshold: 500,      // Consider cells within 500px
  minOverlap: 0.15,    // Widget must overlap cell by at least 15%
  magneticSnap: true,  // Real-time visual feedback
  resizeOnSnap: true,  // Auto-resize to fit cell
};

Snap Selection Logic:

  1. Find all cells within 500px of widget center
  2. Calculate overlap ratio for each cell (0.0 to 1.0)
  3. Exclude cells with less than 15% overlap
  4. Choose cell with highest overlap
  5. If overlap is tied, choose the one with closer center

4. Visual Behavior

Before:

  • Widget near cell → Snaps immediately (annoying)
  • Widget partially on cell → Might snap to wrong cell
  • Hard to position freely

After:

  • Widget near cell but not overlapping → No snap, free movement ✅
  • Widget partially on cell (15%+) → Highlights that cell ✅
  • Widget mostly on cell → Snaps to that cell ✅
  • Widget overlapping multiple cells → Snaps to cell with most overlap ✅

Configuration

You can tune the behavior by adjusting these values in WidgetRenderer.tsx:

const SNAP_CONFIG = {
  // Larger threshold = consider more cells (current: 500px is very forgiving)
  threshold: 500,
  
  // Higher minOverlap = need more widget on cell to snap
  // 0.15 = 15% overlap required
  // 0.25 = 25% overlap required (stricter)
  // 0.05 = 5% overlap required (more forgiving)
  minOverlap: 0.15,
  
  // Disable magnetic snapping for only-on-drop behavior
  magneticSnap: true,
  
  // Disable auto-resize if you want to preserve widget size
  resizeOnSnap: true,
};

Examples

Example 1: Small Overlap

Widget overlaps cell by 10%
→ No snap (below 15% threshold)
→ Widget moves freely

Example 2: Good Overlap

Widget overlaps cell by 40%
→ Cell highlights
→ On release, snaps to cell
→ Widget resizes to fit cell

Example 3: Multiple Cells

Widget overlaps cell-1 by 20%
Widget overlaps cell-2 by 60%
→ cell-2 highlights (higher overlap)
→ Snaps to cell-2

Testing

  1. Free Movement: Drag widget near (but not on) a cell

    • Expected: No highlighting, no snapping ✅
  2. Partial Overlap: Drag widget so 20% overlaps a cell

    • Expected: Cell highlights, snaps on release ✅
  3. Multiple Cells: Drag widget over corner between two cells

    • Expected: Only the cell with most overlap highlights ✅
  4. Full Overlap: Drag widget completely over a cell

    • Expected: Strong highlight, definite snap ✅

Console Debug Output

You'll now see more detailed logging:

[Snap Debug] Checking cell: {
  cellId: "cell-1",
  distance: "245.3",
  overlap: "0.342",          // 34.2% overlap
  withinThreshold: true,
  meetsMinOverlap: true      // Above 15% minimum
}

[Snap Debug] Final best cell: {
  cellId: "cell-1",
  overlap: "0.342"           // Chosen due to highest overlap
}

Files Modified

  • /src/components/WidgetRenderer.tsx:
    • Increased threshold from 30 to 500
    • Added minOverlap config (0.15 = 15%)
    • Changed algorithm to prioritize overlap over distance
    • Improved debug logging

Result: Snapping now feels natural and forgiving. You have to actually position the widget on the cell for it to snap, rather than having it snap too eagerly when just passing nearby.