🐛 Fixes more glitches of dnd sortable trees

This commit is contained in:
Andrés Moya 2020-06-19 12:06:22 +02:00 committed by Andrey Antukh
parent 1119ffffcf
commit 763f1ee13d
4 changed files with 110 additions and 44 deletions

View file

@ -87,11 +87,52 @@
(dissoc state :timer))
state)))
(def sortable-ctx (mf/create-context nil))
(mf/defc sortable-container
[{:keys [children] :as props}]
(let [global-drag-end (mf/use-memo #(rx/subject))]
[:& (mf/provider sortable-ctx) {:value global-drag-end}
children]))
;; The dnd API is problematic for nested elements, such a sortable items tree.
;; The approach used here to solve bad situations is:
;; - Capture all events in the leaf draggable elements, and stop propagation.
;; - Ignore events originated in non-draggable children.
;; - At drag operation end, all elements that have received some enter/over
;; event and have not received the corresponding leave event, are notified
;; so they can clean up.
;;
;; Do not remove commented out lines, they are useful to debug events when
;; things go weird.
(defn use-sortable
[& {:keys [data-type data on-drop on-drag on-hold detect-center?] :as opts}]
(let [ref (mf/use-ref)
state (mf/use-state {:over nil
:timer nil})
:timer nil
:subscr nil})
global-drag-end (mf/use-ctx sortable-ctx)
cleanup
(fn []
;; (js/console.log "cleanup" (:name data))
(when-let [subscr (:subscr @state)]
;; (js/console.log "unsubscribing" (:name data))
(rx/unsub! (:subscr @state)))
(swap! state (fn [state]
(-> state
(cancel-timer)
(dissoc :over :subscr)))))
subscribe-to-drag-end
(fn []
(when (nil? (:subscr @state))
;; (js/console.log "subscribing" (:name data))
(swap! state
#(assoc % :subscr (rx/sub! global-drag-end cleanup)))))
on-drag-start
(fn [event]
@ -108,6 +149,7 @@
(dom/prevent-default event) ;; prevent default to allow drag enter
(when-not (dnd/from-child? event)
(dom/stop-propagation event)
(subscribe-to-drag-end)
;; (dnd/trace event data "drag-enter")
(when (fn? on-hold)
(swap! state (fn [state]
@ -121,6 +163,7 @@
(dom/prevent-default event) ;; prevent default to allow drag over
(when-not (dnd/from-child? event)
(dom/stop-propagation event)
(subscribe-to-drag-end)
;; (dnd/trace event data "drag-over")
(let [side (dnd/drop-side event detect-center?)]
(swap! state assoc :over side)))))
@ -130,10 +173,7 @@
(when-not (dnd/from-child? event)
(dom/stop-propagation event)
;; (dnd/trace event data "drag-leave")
(swap! state (fn [state]
(-> state
(cancel-timer)
(dissoc :over))))))
(cleanup)))
on-drop'
(fn [event]
@ -141,30 +181,30 @@
;; (dnd/trace event data "drop")
(let [side (dnd/drop-side event detect-center?)
drop-data (dnd/get-data event data-type)]
(swap! state (fn [state]
(-> state
(cancel-timer)
(dissoc :over))))
(cleanup)
(rx/push! global-drag-end nil)
(when (fn? on-drop)
(on-drop side drop-data))))
on-drag-end
(fn [event]
(dom/stop-propagation event)
;; (dnd/trace event data "drag-end")
(swap! state (fn [state]
(-> state
(cancel-timer)
(dissoc :over)))))
(rx/push! global-drag-end nil)
(cleanup))
on-mount
(fn []
(let [dom (mf/ref-val ref)]
(.setAttribute dom "draggable" true)
;; Register all events in the (default) bubble mode, so that they
;; are captured by the most leaf item. The handler will stop
;; propagation, so they will not go up in the containment tree.
(.addEventListener dom "dragstart" on-drag-start false)
(.addEventListener dom "dragenter" on-drag-enter false)
(.addEventListener dom "dragover" on-drag-over false)
(.addEventListener dom "dragleave" on-drag-leave true)
(.addEventListener dom "dragleave" on-drag-leave false)
(.addEventListener dom "drop" on-drop' false)
(.addEventListener dom "dragend" on-drag-end false)
#(do