npm package: @pantograph/sortable
Description: JavaScript library for reorderable drag-and-drop lists on modern browsers and touch devices. No jQuery required. Supports Meteor, AngularJS, React, Polymer, Vue, Knockout and any CSS library, e.g. Bootstrap.
Author: sedmedgh
Latest version: 2.2.4

# Sortable.js

A modern JavaScript library for reorderable drag-and-drop lists with touch support.

## Features

- **Touch Support**: Works on mobile devices and touch screens
- **Modern Browsers**: Full support for all modern browsers (including IE9+)
- **Cross-List Dragging**: Drag items between different lists
- **Smooth Animations**: CSS animations and transitions
- **Drag Handles**: Support for drag handles and selectable text
- **Auto-Scrolling**: Smart auto-scroll during drag operations
- **Plugin System**: Extensible with plugins
- **Framework Support**: Works with React, Vue, Angular, and more
- **No Dependencies**: Pure JavaScript, no jQuery required
- **TypeScript**: Full TypeScript definitions available

## Quick Start

### Installation

```bash
npm install sortablejs --save
```

### Basic Usage

```html
<ul id="items">
    <li>Item 1</li>
    <li>Item 2</li>
    <li>Item 3</li>
</ul>
```

```javascript
import Sortable from 'sortablejs';

const el = document.getElementById('items');
const sortable = Sortable.create(el);
```

## Installation Options

### NPM
```bash
npm install sortablejs --save
```

### CDN
```html
<script src="sortable.min.js"></script>
```

### Import Options

```javascript
// Default SortableJS (with default plugins)
import Sortable from 'sortablejs';

// Core SortableJS (without plugins)
import Sortable from 'sortablejs/modular/sortable.core.esm.js';

// Complete SortableJS (with all plugins)
import Sortable from 'sortablejs/modular/sortable.complete.esm.js';

// Cherry-pick plugins
import Sortable, { Swap, AutoScroll } from 'sortablejs';
```

## Configuration

### Basic Options

```javascript
const sortable = Sortable.create(el, {
    group: "name",           // Group name for cross-list dragging
    sort: true,              // Enable sorting within list
    delay: 0,                // Delay before drag starts (ms)
    disabled: false,         // Disable the sortable
    animation: 150,          // Animation duration (ms)
    handle: ".drag-handle",  // Drag handle selector
    filter: ".ignore",       // Elements to ignore
    draggable: ".item",      // Draggable elements
    ghostClass: "sortable-ghost",    // Class for drop placeholder
    chosenClass: "sortable-chosen",  // Class for chosen item
    dragClass: "sortable-drag",      // Class for dragging item
    swapThreshold: 0.65,     // Swap zone threshold
    direction: 'vertical',   // Sort direction
    forceFallback: false,    // Force fallback mode
});
```

### Advanced Options

```javascript
const sortable = Sortable.create(el, {
    // Group configuration
    group: {
        name: "shared",
        pull: true,    // Can pull from this group
        put: true,     // Can put into this group
        revertClone: true  // Revert cloned element
    },
    
    // Animation settings
    animation: 150,
    easing: "cubic-bezier(1, 0, 0, 1)",
    
    // Touch settings
    delayOnTouchOnly: false,
    touchStartThreshold: 3,
    
    // Fallback settings
    fallbackClass: "sortable-fallback",
    fallbackOnBody: false,
    fallbackTolerance: 0,
    
    // Scroll settings
    scroll: true,
    scrollSensitivity: 30,
    scrollSpeed: 10,
    
    // Event handlers
    onStart: function(evt) {
        console.log('Drag started');
    },
    onEnd: function(evt) {
        console.log('Drag ended');
    }
});
```

## Event Handlers

```javascript
const sortable = Sortable.create(el, {
    // Element is chosen
    onChoose: function(evt) {
        console.log('Element chosen:', evt.item);
    },
    
    // Element is unchosen
    onUnchoose: function(evt) {
        console.log('Element unchosen:', evt.item);
    },
    
    // Element dragging started
    onStart: function(evt) {
        console.log('Drag started:', evt.item);
        console.log('Old index:', evt.oldIndex);
    },
    
    // Element dragging ended
    onEnd: function(evt) {
        console.log('Drag ended');
        console.log('Item:', evt.item);
        console.log('From:', evt.from);
        console.log('To:', evt.to);
        console.log('Old index:', evt.oldIndex);
        console.log('New index:', evt.newIndex);
    },
    
    // Element is added to list
    onAdd: function(evt) {
        console.log('Element added:', evt.item);
    },
    
    // Element is removed from list
    onRemove: function(evt) {
        console.log('Element removed:', evt.item);
    },
    
    // List order changed
    onUpdate: function(evt) {
        console.log('List updated');
    },
    
    // Any change to list
    onSort: function(evt) {
        console.log('List sorted');
    },
    
    // Element is filtered
    onFilter: function(evt) {
        console.log('Element filtered:', evt.item);
    },
    
    // Element is moved
    onMove: function(evt, originalEvent) {
        console.log('Element moved');
        // Return false to cancel
        // Return -1 to insert before
        // Return 1 to insert after
        return true;
    },
    
    // Clone is created
    onClone: function(evt) {
        console.log('Clone created:', evt.clone);
    },
    
    // Element position changed
    onChange: function(evt) {
        console.log('Element changed position');
    }
});
```

## Methods

### Instance Methods

```javascript
const sortable = Sortable.create(el);

// Get/set options
sortable.option('disabled', true);
const disabled = sortable.option('disabled');

// Get array of item IDs
const order = sortable.toArray();

// Sort items by array
sortable.sort(['item3', 'item1', 'item2']);

// Save current order
sortable.save();

// Destroy sortable
sortable.destroy();
```

### Static Methods

```javascript
// Create new instance
const sortable = Sortable.create(el, options);

// Get instance from element
const sortable = Sortable.get(element);

// Mount plugin
Sortable.mount(Plugin);

// Get active instance
const active = Sortable.active;

// Get dragged element
const dragged = Sortable.dragged;
```

## Plugins

### Default Plugins (Included)

- **AutoScroll**: Automatic scrolling during drag operations
- **OnSpill**: Handle items dropped outside valid targets
  - `RemoveOnSpill`: Remove spilled items
  - `RevertOnSpill`: Revert spilled items to original position

### Extra Plugins

- **Swap**: Swap items instead of reordering
- **FlatTree**: Hierarchical tree-like drag and drop

### Plugin Usage

```javascript
import Sortable, { Swap, AutoScroll } from 'sortablejs';

// Mount plugins
Sortable.mount(Swap);
Sortable.mount(AutoScroll);

// Use plugins
const sortable = Sortable.create(el, {
    swap: true,
    scroll: true
});
```

## Examples

### Basic List
```html
<ul id="basic-list">
    <li>Item 1</li>
    <li>Item 2</li>
    <li>Item 3</li>
</ul>
```

```javascript
Sortable.create(document.getElementById('basic-list'));
```

### Drag Handles
```html
<ul id="handle-list">
    <li><span class="handle">⋮⋮</span> Item 1</li>
    <li><span class="handle">⋮⋮</span> Item 2</li>
    <li><span class="handle">⋮⋮</span> Item 3</li>
</ul>
```

```javascript
Sortable.create(document.getElementById('handle-list'), {
    handle: '.handle'
});
```

### Cross-List Dragging
```html
<div id="list1" class="list">
    <div class="item">Item 1</div>
    <div class="item">Item 2</div>
</div>
<div id="list2" class="list">
    <div class="item">Item 3</div>
    <div class="item">Item 4</div>
</div>
```

```javascript
Sortable.create(document.getElementById('list1'), {
    group: 'shared'
});

Sortable.create(document.getElementById('list2'), {
    group: 'shared'
});
```

### Bootstrap Integration
```html
<ul class="list-group" id="bootstrap-list">
    <li class="list-group-item">Item 1</li>
    <li class="list-group-item">Item 2</li>
    <li class="list-group-item">Item 3</li>
</ul>
```

```javascript
Sortable.create(document.getElementById('bootstrap-list'));
```

## Browser Support

- **Modern Browsers**: Chrome, Firefox, Safari, Edge
- **Mobile**: iOS Safari, Chrome Mobile, Firefox Mobile
- **Legacy**: Internet Explorer 9+
- **Touch Devices**: Full touch support

## Performance Tips

1. **Use Drag Handles**: Reduces accidental drags
2. **Limit Animation Duration**: Keep animations under 200ms
3. **Use Efficient Selectors**: Avoid complex CSS selectors
4. **Disable When Not Needed**: Use `disabled` option
5. **Optimize Event Handlers**: Keep handlers lightweight

## Contributing

Please read our Contributing Guide before submitting issues or pull requests.

## License

MIT License - see LICENSE file for details.