CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-shopify--draggable

The JavaScript Drag & Drop library your grandparents warned you about.

Pending
Overview
Eval results
Files

droppable.mddocs/

Droppable Zones

Droppable extends Draggable to create designated drop zones where draggable elements can be placed. It provides visual feedback for valid drop targets and handles drop validation.

Capabilities

Droppable Constructor

Creates a droppable instance that requires dropzone configuration to define valid drop targets.

/**
 * Creates a new droppable instance with designated drop zones
 * @param containers - Elements that contain draggable items
 * @param options - Configuration options including required dropzone
 */
class Droppable<T = DroppableEventNames> extends Draggable<T> {
  constructor(containers: DraggableContainer, options: DroppableOptions);
}

interface DroppableOptions extends DraggableOptions {
  dropzone: string | NodeList | HTMLElement[] | (() => NodeList | HTMLElement[]);
  classes?: {[key in DroppableClassNames]: string};
}

type DroppableClassNames =
  | DraggableClassNames
  | 'droppable:active'
  | 'droppable:occupied';

Usage Example:

import { Droppable } from "@shopify/draggable";

const droppable = new Droppable(document.querySelectorAll('.drag-container'), {
  draggable: '.draggable-item',
  dropzone: '.drop-zone',
  classes: {
    'droppable:active': 'drop-zone-active',
    'droppable:occupied': 'drop-zone-occupied'
  }
});

// Listen for drop events
droppable.on('droppable:dropped', (event) => {
  console.log('Item dropped into:', event.dropzone);
  console.log('Dragged item:', event.dragEvent.source);
});

Class Name Management

Specialized class name management for dropzone-specific states.

/**
 * Returns CSS class name for droppable-specific class identifiers
 * @param name - Droppable class identifier
 * @returns CSS class name string
 */
getClassNameFor(name: DroppableClassNames): string;

Drop Events

Droppable-specific events that fire during drop operations.

type DroppableEventNames =
  | 'droppable:start'
  | 'droppable:dropped'
  | 'droppable:returned'
  | 'droppable:stop'
  | DraggableEventNames;

Event Details:

  • droppable:start: Fired when a droppable drag operation begins
  • droppable:dropped: Fired when an item is dropped into a valid dropzone
  • droppable:returned: Fired when an item returns to its original dropzone
  • droppable:stop: Fired when the drop operation ends

Event Handlers Example:

droppable.on('droppable:start', (event) => {
  console.log('Started dragging from dropzone:', event.dropzone);
  // Highlight valid drop zones
  document.querySelectorAll('.drop-zone').forEach(zone => {
    zone.classList.add('available-target');
  });
});

droppable.on('droppable:dropped', (event) => {
  console.log(`Item moved to new dropzone`);
  handleItemDrop(event.dragEvent.source, event.dropzone);
  
  // Optionally prevent the drop
  // event.cancel();
});

droppable.on('droppable:returned', (event) => {
  console.log('Item returned to original position');
  handleItemReturn(event.dragEvent.source, event.dropzone);
});

droppable.on('droppable:stop', (event) => {
  // Clean up highlighting
  document.querySelectorAll('.drop-zone').forEach(zone => {
    zone.classList.remove('available-target');
  });
});

Event Types

class DroppableEvent extends AbstractEvent {
  readonly dragEvent: DragEvent;
}

class DroppableStartEvent extends DroppableEvent {
  dropzone: HTMLElement;
}

class DroppableDroppedEvent extends DroppableEvent {
  dropzone: HTMLElement;
}

class DroppableReturnedEvent extends DroppableEvent {
  dropzone: HTMLElement;
}

class DroppableStopEvent extends DroppableEvent {
  dropzone: HTMLElement;
}

Dropzone Configuration

The dropzone option can be specified in multiple ways:

// CSS selector string
const droppable1 = new Droppable(containers, {
  draggable: '.item',
  dropzone: '.drop-target'
});

// NodeList or HTMLElement array
const dropzones = document.querySelectorAll('.zone');
const droppable2 = new Droppable(containers, {
  draggable: '.item', 
  dropzone: dropzones
});

// Function returning NodeList or HTMLElement array
const droppable3 = new Droppable(containers, {
  draggable: '.item',
  dropzone: () => document.querySelectorAll('.dynamic-zone')
});

Complete Example

import { Droppable } from "@shopify/draggable";

// Create a file upload interface with drag and drop
const fileDroppable = new Droppable(document.querySelector('.file-area'), {
  draggable: '.file-item',
  dropzone: '.upload-zone, .trash-zone',
  classes: {
    'droppable:active': 'zone-highlight',
    'droppable:occupied': 'zone-has-file',
    'source:dragging': 'file-being-dragged'
  }
});

// Handle different drop zones
fileDroppable.on('droppable:dropped', (event) => {
  const file = event.dragEvent.source;
  const zone = event.dropzone;
  
  if (zone.classList.contains('upload-zone')) {
    // Start upload process
    uploadFile(file.dataset.fileId);
    showUploadProgress(file);
  } else if (zone.classList.contains('trash-zone')) {
    // Delete file
    deleteFile(file.dataset.fileId);
    file.remove();
  }
});

// Provide visual feedback
fileDroppable.on('droppable:start', (event) => {
  // Show drop zones when dragging starts
  document.querySelectorAll('.drop-zone').forEach(zone => {
    zone.style.opacity = '1';
    zone.style.transform = 'scale(1.05)';
  });
});

fileDroppable.on('droppable:stop', (event) => {
  // Hide drop zones when dragging ends
  document.querySelectorAll('.drop-zone').forEach(zone => {
    zone.style.opacity = '';
    zone.style.transform = '';
  });
});

// Handle validation
fileDroppable.on('droppable:dropped', (event) => {
  const file = event.dragEvent.source;
  const zone = event.dropzone;
  
  // Check if file type is allowed in this zone
  const allowedTypes = zone.dataset.allowedTypes?.split(',') || [];
  const fileType = file.dataset.type;
  
  if (allowedTypes.length > 0 && !allowedTypes.includes(fileType)) {
    event.cancel();
    showError(`${fileType} files not allowed in this zone`);
  }
});

Advanced Dropzone Patterns

Conditional Drop Zones

const conditionalDroppable = new Droppable(containers, {
  draggable: '.task',
  dropzone: () => {
    // Only return available drop zones based on current state
    return Array.from(document.querySelectorAll('.task-column'))
      .filter(column => !column.classList.contains('locked'));
  }
});

Nested Drop Zones

const nestedDroppable = new Droppable(document.querySelector('.workspace'), {
  draggable: '.widget',
  dropzone: '.panel, .sidebar, .main-area'
});

// Handle different drop contexts
nestedDroppable.on('droppable:dropped', (event) => {
  const widget = event.dragEvent.source;
  const target = event.dropzone;
  
  // Configure widget based on drop target
  if (target.classList.contains('sidebar')) {
    widget.classList.add('sidebar-widget');
    resizeWidget(widget, 'narrow');
  } else if (target.classList.contains('main-area')) {
    widget.classList.add('main-widget');
    resizeWidget(widget, 'wide');
  }
});

Install with Tessl CLI

npx tessl i tessl/npm-shopify--draggable

docs

core-draggable.md

droppable.md

events.md

index.md

plugins.md

sensors.md

sortable.md

swappable.md

tile.json