controls

controls

Define which areas can initiate dragging

The controls plugin defines which parts of an element can be used to start dragging. Think modal headers, card handles, or toolbar grips - only specific areas should be draggable while the rest remains interactive.

controls({ allow: ControlFrom.selector('.handle') }); // Only handle can drag
controls({ block: ControlFrom.selector('.button') }); // Buttons can't drag
controls({
  allow: ControlFrom.selector('.header'),
  block: ControlFrom.selector('.close'),
}); // Header drags, close button doesn't

Basic Usage

<script>
  import { controls, ControlFrom } from '@neodrag/svelte';
</script>

<!-- Only header can initiate dragging -->
<div
  {@attach draggable([
    controls({ allow: ControlFrom.selector('.header') }),
  ])}
>
  <div class="header">Drag handle</div>
  <div class="content">Content (not draggable)</div>
</div>

<!-- Anywhere except buttons -->
<div
  {@attach draggable([
    controls({ block: ControlFrom.selector('button') }),
  ])}
>
  <p>Drag anywhere in this area</p>
  <button>Can't drag from here</button>
</div>

ControlFrom Utilities

Selector-Based Controls

<script>
  import { controls, ControlFrom } from '@neodrag/svelte';
</script>

<!-- Single selector -->
<div
  {@attach draggable([
    controls({ allow: ControlFrom.selector('.handle') }),
  ])}
>
  <div class="handle">Drag me</div>
  <div>Not draggable</div>
</div>

<!-- Multiple selectors -->
<div
  {@attach draggable([
    controls({
      allow: ControlFrom.selector('.handle, .title, .grip'),
    }),
  ])}
>
  <div class="title">Title bar</div>
  <div class="handle">Handle</div>
  <div class="grip">⋮⋮</div>
  <div class="content">Content</div>
</div>

<!-- Block specific elements -->
<div
  {@attach draggable([
    controls({
      block: ControlFrom.selector('button, input, select'),
    }),
  ])}
>
  <p>Draggable area</p>
  <button>Button (blocked)</button>
  <input placeholder="Input (blocked)" />
</div>

Element-Based Controls

<script>
  import { controls, ControlFrom } from '@neodrag/svelte';

  let handleElement;
  let blockElement;
</script>

<div
  {@attach draggable([
    controls({
      allow: () => ControlFrom.elements([handleElement]),
    }),
  ])}
>
  <div bind:this={handleElement}>Specific handle element</div>
  <div>Not draggable</div>
</div>

<div
  {@attach draggable([
    controls({
      block: () => ControlFrom.elements([blockElement]),
    }),
  ])}
>
  <p>Draggable area</p>
  <div bind:this={blockElement}>Blocked element</div>
</div>

Advanced Control Logic

Priority Handling

<script>
  import { controls, ControlFrom } from '@neodrag/svelte';
</script>

<!-- Allow wins when zones overlap -->
<div
  {@attach draggable([
    controls({
      allow: ControlFrom.selector('.drag-zone'),
      block: ControlFrom.selector('.no-drag'),
      priority: 'allow',
    }),
  ])}
>
  <div class="drag-zone">
    Drag zone
    <div class="no-drag">Allow wins here</div>
  </div>
</div>

<!-- Block wins when zones overlap -->
<div
  {@attach draggable([
    controls({
      allow: ControlFrom.selector('.drag-zone'),
      block: ControlFrom.selector('.no-drag'),
      priority: 'block',
    }),
  ])}
>
  <div class="drag-zone">
    Drag zone
    <div class="no-drag">Block wins here</div>
  </div>
</div>

Real-World Examples

<script>
  import {
    draggable,
    controls,
    ControlFrom,
    bounds,
    BoundsFrom,
  } from '@neodrag/svelte';
</script>

<div
  {@attach draggable([
    controls({
      allow: ControlFrom.selector('.modal-header'),
      block: ControlFrom.selector('.modal-close, .modal-body button'),
    }),
    bounds(BoundsFrom.viewport()),
  ])}
>
  <div class="modal-header">
    <h3>Modal Title</h3>
    <button class="modal-close">×</button>
  </div>
  <div class="modal-body">
    <p>Modal content</p>
    <button>Action Button</button>
  </div>
</div>

How It Works

The controls plugin manages drag initiation permissions:

  1. Finds control zones on setup by querying the DOM
  2. Checks click position in shouldStart hook
  3. Determines permission based on which zones contain the click point
  4. Returns boolean to allow/prevent drag start

Zone priority logic:

  • If allow zones exist and click is outside all of them → block
  • If click is in both allow and block zones → use priority option
  • Smaller (nested) zones take precedence over larger ones

API Reference

function controls(options?: {
  allow?: ReturnType<
    typeof ControlFrom.selector | typeof ControlFrom.elements
  >;
  block?: ReturnType<
    typeof ControlFrom.selector | typeof ControlFrom.elements
  >;
  priority?: 'allow' | 'block';
}): Plugin;

Options:

  • allow - Control zones that can initiate dragging
  • block - Control zones that cannot initiate dragging
  • priority - Which wins when zones overlap (‘allow’ | ‘block’)

ControlFrom utilities:

  • ControlFrom.selector(cssSelector) - Elements matching CSS selector
  • ControlFrom.elements(nodeList) - Specific DOM elements

Returns: A plugin object for use with draggable.