I'm experimenting with drag-and-drop using cyclejs in a codepen. The standard drag methods supported by HTML 5 don't seem to support constraints on the movement of the dragged object so I went with standard mousedown/mousemove/mouseup. It works, but not consistently. The combine() operation doesn't seem to trigger even when the debug() calls show that mousedown and mousemove events have been received and sometimes the mouseup is missed. Perhaps my understanding of the operation is incomplete or incorrect. A direct link to the codepen is provided at the bottom of this post. Any help appreciated!
const xs = xstream.default;
const { run } = Cycle;
const { div, svg, makeDOMDriver } = CycleDOM;
function DragBox(sources) {
const COMPONENT_NAME = `DragBox`;
const intent = function({ DOM }) {
return {
mousedown$: DOM.select(`#${COMPONENT_NAME}`)
.events("mousedown")
.map(function(ev) {
return ev;
})
.debug("mousedown"),
mousemove$: DOM.select(`#${COMPONENT_NAME}`)
.events("mousemove")
.map(function(ev) {
return ev;
})
.debug("mousemove"),
mouseup$: DOM.select("#container")
.events("mouseup")
.map(function(ev) {
return ev;
})
.debug("mouseup")
};
};
const between = (first, second) => {
return source => first.mapTo(source.endWhen(second)).flatten();
};
const model = function({ mousedown$, mousemove$, mouseup$ }) {
return xs
.combine(mousedown$, mousemove$)
.debug("combine")
.map(([mousedown, mousemove]) => ({
x: mousemove.pageX - mousedown.layerX,
y: mousemove.pageY - mousedown.layerY
}))
.compose(between(mousedown$, mouseup$))
.startWith({
x: 0,
y: 0
})
.debug("model");
};
const getStyle = left => top => {
return {
style: {
position: "absolute",
left: left + "px",
top: top + "px",
backgroundColor: "#333",
cursor: "move"
}
};
};
const view = function(state$) {
return state$.map(value =>
div("#container", { style: { height: "100vh" } }, [
div(`#${COMPONENT_NAME}`, getStyle(value.x)(value.y), "Move Me!")
])
);
};
const actions = intent(sources);
const state$ = model(actions);
const vTree$ = view(state$);
return {
DOM: vTree$
};
}
function main(sources) {
const dragBox = DragBox(sources);
const sinks = {
DOM: dragBox.DOM
};
return sinks;
}
Cycle.run(main, {
DOM: makeDOMDriver("#app")
});
Testing your code shows that combine
is not getting the first mousedown
event, apparently due to the between
operator subscribing to mousedown$
after the first mousedown event. Adding remember
to the mousedown$
sends that first mousedown event to the between operator subscription.
mousedown$: DOM.select(`#${COMPONENT_NAME}`)
.events("mousedown").remember()
Codesandbox.io testing between
Here's another CycleJS/xstream Drag and Drop approach (taking inspiration from this RxJS Drag and Drop example) I think is more straight forward. Everything else in your code is essentially the same but the model
function is this:
const model = function({ mousedown$, mousemove$, mouseup$ }) {
return mousedown$.map(md => {
let startX = md.offsetX, startY = md.offsetY
return mousemove$.map(mm => {
mm.preventDefault()
return {
x: mm.clientX - startX,
y: mm.clientY - startY
}
}).endWhen(mouseup$)
}).flatten().startWith({x:0,y:0})
};
Here's a Codepen.io example.