Skip to content

Instantly share code, notes, and snippets.

@shaunlebron
Last active January 15, 2026 01:16
Show Gist options
  • Select an option

  • Save shaunlebron/e3c64e5ab956bc2bff971157b37f27d8 to your computer and use it in GitHub Desktop.

Select an option

Save shaunlebron/e3c64e5ab956bc2bff971157b37f27d8 to your computer and use it in GitHub Desktop.
(ns example.touch-surface
(:require
[applied-science.js-interop :as j]
[reagent.core :as r]
[reagent.hooks :as hooks]))
(defn with-nonpassive-event-listeners
"TODO: remove this when React supports non-passive event listeners:
https://github.com/facebook/react/issues/22794
Add event listeners that allow `e.preventDefault()` (e.g. for touch and scroll event listeners).
Usage:
1. Pass the returned Ref prop to the target component.
2. This function must be called inside a function component (i.e. r/defc).
"
[listeners]
(returning-let [node-ref (hooks/use-ref nil)]
(let [[add-events remove-events]
(for [action [:addEventListener :removeEventListener]]
(fn [node]
(doseq [[k f] listeners]
(j/call node action k f #js{:passive false}))))]
(hooks/use-effect
(fn []
(let [node (j/get node-ref :current)]
(returning #(remove-events node)
(add-events node))))))))
(r/defc touch-surface [{:keys [on-start on-move on-end on-cancel]}]
(let [node-ref (with-nonpassive-event-listeners {"touchstart" on-start
"touchmove" on-move})]
[:<>
[:style (garden/css
[:.touch-surface {:position "absolute"
:inset 0
:user-select "none" ;; try to disable selection
:touch-action "none" ;; try to disable builtin browser behavior like zoom/pan
;; reagent/react can’t seem to handle vendor prefixes in inline styles,
;; so we have to attach it as a class rule in a <style> element here:
:-webkit-touch-callout "none" ;; <-- try to prevent long tap on iOS
}])]
[:div.touch-surface {:ref node-ref
:on-touch-end on-end
:on-touch-cancel on-cancel}]]))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment