Ensaimada
A drag-and-drop sortable list library for Gleam/Lustre with full support for both desktop and mobile devices.
Features
- Desktop Support: HTML5 drag and drop API
- Mobile Support: Touch events for drag and drop on mobile devices
- Customizable: Configurable CSS classes for styling
- Type Safe: Fully typed with Gleam’s type system
- Framework Agnostic: No built-in CSS framework dependencies
- Cross-Container Support: Move items between multiple sortable containers
Installation
Add ensaimada to your Gleam project:
gleam add ensaimada
Quick Start
import gleam/list
import lustre
import lustre/element.{type Element}
import lustre/element/html
import ensaimada
pub type Model {
Model(items: List(String), drag_state: ensaimada.DragState)
}
pub type Msg {
SortableMsg(ensaimada.Msg(Msg))
Reorder(Int, Int)
}
pub fn update(model: Model, msg: Msg) -> Model {
case msg {
SortableMsg(sortable_msg) -> {
let config = ensaimada.default_config(Reorder, "my-list")
let #(new_drag_state, maybe_action) =
ensaimada.update(sortable_msg, model.drag_state, config)
case maybe_action {
option.Some(ensaimada.SameContainer(from, to)) -> {
let new_items = ensaimada.reorder(model.items, from, to)
Model(..model, items: new_items, drag_state: new_drag_state)
}
_ -> Model(..model, drag_state: new_drag_state)
}
}
Reorder(_, _) -> model
}
}
pub fn view(model: Model) -> Element(Msg) {
let config = ensaimada.default_config(Reorder, "my-list")
let sortable_items =
list.index_map(model.items, fn(item, i) {
ensaimada.item("item-" <> int.to_string(i), item)
})
ensaimada.container(
config,
model.drag_state,
sortable_items,
fn(item, _index, _drag_state) {
html.div([], [html.text(ensaimada.item_data(item))])
},
)
|> element.map(SortableMsg)
}
Styling
The library uses CSS classes for styling. Here’s an example CSS setup:
.sortable-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1rem;
padding: 1rem;
}
.sortable-item {
padding: 1rem;
background: white;
border: 2px solid #e5e7eb;
border-radius: 0.5rem;
transition: all 0.2s ease;
}
.sortable-dragging {
opacity: 0.5;
transform: scale(1.05) rotate(3deg);
}
.sortable-drag-over {
border-color: #3b82f6;
background: #eff6ff;
}
.sortable-active {
user-select: none;
}
Cross-Container Drag and Drop
To enable dragging between multiple containers:
let config1 = ensaimada.Config(
..ensaimada.default_config(Reorder, "container-1"),
accept_from: ["container-2"]
)
let config2 = ensaimada.Config(
..ensaimada.default_config(Reorder, "container-2"),
accept_from: ["container-1"]
)
Then handle CrossContainer actions in your update function:
case maybe_action {
Some(ensaimada.CrossContainer(from_cont, from_idx, to_cont, to_idx)) -> {
// Remove from source container and add to target container
...
}
...
}
Testing
Run tests with:
gleam test
License
This project is available under the MIT license. See LICENSE for details.
Contributing
Contributions are welcome! Please feel free to submit issues or pull requests.