Repository: bokeh/bokeh

Issue:
[FEATURE] Provide mouse press up event
bokeh provides the option to capture many mouse event types from plots in the browser, taking advantage of the [HammerJS](https://hammerjs.github.io/) library. In particular, bokehjs passes events like Tap, Press, and Pan on to the python backend (see demonstration in bokeh documentation [here](https://docs.bokeh.org/en/latest/docs/user_guide/interaction/callbacks.html#customjs-for-user-interaction-events)).

When a user long presses or holds their mouse button for longer than 0.251 seconds, a [press event](https://hammerjs.github.io/recognizer-press/) is activated in HammerJS and picked up by bokehjs. However, the corresponding mouse release, or "press up" is not picked up by bokehjs. It is still activated in HammerJS though (again see [here](https://hammerjs.github.io/recognizer-press/) for the `pressup` event).

A "press up" event can be added by extending the events bokehjs captures that are already available from HammerJS. To that end, I have a feature branch that accomplishes this if a PR is welcomed.


Discussion:
@ethan-nelson If it can be added in a straightforward, non-disruptive manner (sounds like?) then I wouldn't have any issues around adding a new event. I'd suggest submitting the PR so we can review the actual code and go from there
Great! Yes, just a few minor additions--PR coming soon.

Resolving patch:
diff --git a/bokeh/events.py b/bokeh/events.py
--- a/bokeh/events.py
+++ b/bokeh/events.py
@@ -99,6 +99,7 @@ def callback(event):
     'PlotEvent',
     'PointEvent',
     'Press',
+    'PressUp',
     'Reset',
     'SelectionGeometry',
     'Tap',
@@ -332,6 +333,18 @@ class Press(PointEvent):
     '''
     event_name = 'press'
 
+class PressUp(PointEvent):
+    ''' Announce a pressup event on a Bokeh plot.
+
+    Attributes:
+        sx (float) : x-coordinate of the event in *screen* space
+        sy (float) : y-coordinate of the event in *screen* space
+        x (float) : x-coordinate of the event in *data* space
+        y (float) : y-coordinate of the event in *data* space
+
+    '''
+    event_name = 'pressup'
+
 class MouseEnter(PointEvent):
     ''' Announce a mouse enter event onto a Bokeh plot.
 
diff --git a/examples/howto/js_events.py b/examples/howto/js_events.py
--- a/examples/howto/js_events.py
+++ b/examples/howto/js_events.py
@@ -71,6 +71,7 @@ def display_event(div, attributes=[]):
 p.js_on_event(events.Tap,       display_event(div, attributes=point_attributes))
 p.js_on_event(events.DoubleTap, display_event(div, attributes=point_attributes))
 p.js_on_event(events.Press,     display_event(div, attributes=point_attributes))
+p.js_on_event(events.PressUp,   display_event(div, attributes=point_attributes))
 
 # Mouse wheel event
 p.js_on_event(events.MouseWheel, display_event(div,attributes=point_attributes+['delta']))
diff --git a/sphinx/source/docs/user_guide/examples/js_events.py b/sphinx/source/docs/user_guide/examples/js_events.py
--- a/sphinx/source/docs/user_guide/examples/js_events.py
+++ b/sphinx/source/docs/user_guide/examples/js_events.py
@@ -48,7 +48,7 @@ def display_event(div, attributes=[], style = 'float:left;clear:left;font_size=1
 pinch_attributes = point_attributes + ['scale']            # Pinch event
 
 point_events = [
-    events.Tap, events.DoubleTap, events.Press,
+    events.Tap, events.DoubleTap, events.Press, events.PressUp,
     events.MouseMove, events.MouseEnter, events.MouseLeave,
     events.PanStart, events.PanEnd, events.PinchStart, events.PinchEnd,
 ]


Test patch:
diff --git a/bokehjs/test/core/ui_events.ts b/bokehjs/test/core/ui_events.ts
--- a/bokehjs/test/core/ui_events.ts
+++ b/bokehjs/test/core/ui_events.ts
@@ -370,6 +370,16 @@ describe("ui_events module", () => {
       // assert(spy_uievent.calledOnce)
     })
 
+    it("_pressup method should handle pressup event", () => {
+      const e: any = new Event("pressup") // XXX: not a hammerjs event
+      e.pointerType = "mouse"
+      e.srcEvent = {pageX: 100, pageY: 200}
+
+      ANY_ui_events._pressup(e)
+
+      assert(spy_plot.calledOnce)
+    })
+
     it("_pan_start method should handle panstart event", () => {
       const e: any = new Event("panstart") // XXX: not a hammerjs event
       e.pointerType = "mouse"