A drag & drop Mithril component. Stream-based, pointer-agnostic, view-agnostic.
npm i -s dragm
import Drag from 'dragm'
// or
const Drag = require('dragm')
or
<script src="//unkpg.com/dragm"></script>
<script>
m.mount(document.body, {
view: () => [
Drag({
// Bind functions to drag & drop streams!
drop: () =>
alert('You got the drop on me'),
// Bring your own view!
view: ({state: {drag}}) =>
m('.draggable', {
// Interpolate drag streams internally!
style: {color: drag() ? 'green' : 'inherit'}
},
drag() ? 'Dragging!' : 'Drag me?'
)
})
]
})
</script>
Dragm is a Mithril interface to @JAForbes' excellent pointer-stream library. It provides streams which update when drag & drop events occur on the component. You don't need to bring any tooling to make use of this - it's all included and as simple as you like. The underlying stream implementation is Flyd, which is fully interoperable with Mithril streams thanks to fantasy-land applicative compliance.
Works with touch and mouse, bypassing the inconsistent HTML5 drag & drop API. Currently, dragm provides streams for:
drag: stream Boolean
- which tells you whether the component is being dragged or not. It emits when dragging starts and ends.drop: stream undefined
- which tells you when the component has been dropped onto. It emits whenever the drop occurs. Note that the value of this stream is useless - the significance is if & when it fires.
No view constraints, no DOM structure impositions. Dragm allows you to provide the view
as an attribute, allowing you to define your own virtual DOM logic using the provided vnode state's drag
& drop
streams; alternatively / additionally, you can pass in drag
& drop
streams as attributes to update your upstream model, and provide children as you would with a regular component.
This mostly depends on whether you have a centralised application state model like Redux and keep your components pure - in which case you'll be wanting to define actions to bind to Dragm's streams and provide children based on your pre-existing model; or if you separate your application model into its constituent components - in which case you can provide a view which can access the component's state, thereby allowing you to interpolate the drag & drop streams there.
// For centralised application models & stateless views
const CustomDragItem = {
// Where drag and drop are your action triggers, and dragging is your data
view : ({attrs: {drag, drop, dragging, text}}) =>
// These drag components bind to your actions
m(Drag, {drop},
m('.drop-area',
m(Drag, {drag},
// Seeing as all our state data is already available from our store,
// we can interpolate it ahead of time & pass the subtree in as children
m('.drag-handle',{
class: dragging ? 'dragging' : 'resting'
}
m('.title', text),
'Drag here'
)
),
'Drop here'
)
)
}
// For stateful, decentralised component architecture
const CustomDragItem = {
// Our model is unaware of Dragm, and doesn't provide custom actions:
// We just get text & determine the import of Dragm's API internally.
view : ({state, attrs: {text}}) =>
m(Drag, {
drop(){
state.dropped = true
}
},
m('.drop-area',
m(Drag, {
drag(dragging){
state.dropped = (
dragging
? undefined
: false
)
},
// Instead of passing the subtree as children,
// We provide a view to read from Dragm's internal state
view: ({state: {drag}}) =>
m('.drag-handle',{
class: drag() ? 'dragging' : 'resting'
},
m('.title', text),
'Drag here'
)
}),
this.dropped === true
? 'Dropped here!'
: this.dropped === false
? 'Missed!'
: 'Drop here...'
)
)
}