diff --git a/assets/js/phoenix_live_view/js.js b/assets/js/phoenix_live_view/js.js index 766fdd32d..47df38e47 100644 --- a/assets/js/phoenix_live_view/js.js +++ b/assets/js/phoenix_live_view/js.js @@ -54,9 +54,15 @@ let JS = { }) }, - exec_dispatch(e, eventType, phxEvent, view, sourceEl, el, {event, detail, bubbles}){ + exec_dispatch(e, eventType, phxEvent, view, sourceEl, el, {event, detail, bubbles, blocking}){ detail = detail || {} detail.dispatcher = sourceEl + if(blocking){ + const promise = new Promise((resolve, _reject) => { + detail.done = resolve + }) + liveSocket.asyncTransition(promise) + } DOM.dispatchEvent(el, event, {detail, bubbles}) }, diff --git a/assets/js/phoenix_live_view/live_socket.js b/assets/js/phoenix_live_view/live_socket.js index f09cb3b15..8f18c04df 100644 --- a/assets/js/phoenix_live_view/live_socket.js +++ b/assets/js/phoenix_live_view/live_socket.js @@ -300,6 +300,10 @@ export default class LiveSocket { this.transitions.after(callback) } + asyncTransition(promise){ + this.transitions.addAsyncTransition(promise) + } + transition(time, onStart, onDone = function(){}){ this.transitions.addTransition(time, onStart, onDone) } @@ -993,6 +997,7 @@ export default class LiveSocket { class TransitionSet { constructor(){ this.transitions = new Set() + this.promises = new Set() this.pendingOps = [] } @@ -1001,6 +1006,7 @@ class TransitionSet { clearTimeout(timer) this.transitions.delete(timer) }) + this.promises.clear() this.flushPendingOps() } @@ -1022,9 +1028,17 @@ class TransitionSet { this.transitions.add(timer) } + addAsyncTransition(promise){ + this.promises.add(promise) + promise.then(() => { + this.promises.delete(promise) + this.flushPendingOps() + }) + } + pushPendingOp(op){ this.pendingOps.push(op) } - size(){ return this.transitions.size } + size(){ return this.transitions.size + this.promises.size } flushPendingOps(){ if(this.size() > 0){ return } diff --git a/lib/phoenix_live_view/js.ex b/lib/phoenix_live_view/js.ex index 7d8afca72..7d2ce8033 100644 --- a/lib/phoenix_live_view/js.ex +++ b/lib/phoenix_live_view/js.ex @@ -228,6 +228,9 @@ defmodule Phoenix.LiveView.JS do with the client event. The details will be available in the `event.detail` attribute for event listeners. * `:bubbles` – A boolean flag to bubble the event or not. Defaults to `true`. + * `:blocking` - A boolean flag to block the UI until the event handler calls `event.detail.done()`. + The done function is injected by LiveView and *must* be called eventually to unblock the UI. + This is useful to integrate with third party JavaScript based animation libraries. ## Examples @@ -245,7 +248,7 @@ defmodule Phoenix.LiveView.JS do @doc "See `dispatch/2`." def dispatch(%JS{} = js, event, opts) do - opts = validate_keys(opts, :dispatch, [:to, :detail, :bubbles]) + opts = validate_keys(opts, :dispatch, [:to, :detail, :bubbles, :blocking]) args = [event: event, to: opts[:to]] args = @@ -284,6 +287,15 @@ defmodule Phoenix.LiveView.JS do args end + args = + case Keyword.get(opts, :blocking) do + true -> + Keyword.put(args, :blocking, opts[:blocking]) + + _ -> + args + end + put_op(js, "dispatch", args) end