mixins.cljs 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. (ns frontend.mixins
  2. (:require [rum.core :as rum]
  3. [goog.dom :as dom]
  4. [frontend.util :refer [profile] :as util]
  5. [frontend.state :as state])
  6. (:import [goog.events EventHandler]))
  7. (defn detach
  8. "Detach all event listeners."
  9. [state]
  10. (some-> state ::event-handler .removeAll))
  11. (defn listen
  12. "Register an event `handler` for events of `type` on `target`."
  13. [state target type handler & [opts]]
  14. (when-let [^EventHandler event-handler (::event-handler state)]
  15. (.listen event-handler target (name type) handler (clj->js opts))))
  16. (def event-handler-mixin
  17. "The event handler mixin."
  18. {:will-mount
  19. (fn [state]
  20. (assoc state ::event-handler (EventHandler.)))
  21. :will-unmount
  22. (fn [state]
  23. (detach state)
  24. (dissoc state ::event-handler))})
  25. (defn hide-when-esc-or-outside
  26. [state & {:keys [on-hide node visibilitychange? outside?]}]
  27. (try
  28. (let [dom-node (rum/dom-node state)]
  29. (when-let [dom-node (or node dom-node)]
  30. (let [click-fn (fn [e]
  31. (let [target (.. e -target)]
  32. ;; If the click target is outside of current node
  33. (when (and
  34. (not (dom/contains dom-node target))
  35. (not (.contains (.-classList target) "ignore-outside-event")))
  36. (on-hide state e :click))))]
  37. (when-not (false? outside?)
  38. (listen state js/window "mousedown" click-fn)))
  39. (listen state js/window "keydown"
  40. (fn [e]
  41. (case (.-keyCode e)
  42. ;; Esc
  43. 27 (on-hide state e :esc)
  44. nil)))
  45. (when visibilitychange?
  46. (listen state js/window "visibilitychange"
  47. (fn [e]
  48. (on-hide state e :visibilitychange))))))
  49. (catch :default _e
  50. ;; TODO: Unable to find node on an unmounted component.
  51. nil)))
  52. (defn on-enter
  53. [state & {:keys [on-enter node]}]
  54. (let [node (or node (rum/dom-node state))]
  55. (listen state node "keyup"
  56. (fn [e]
  57. (case (.-keyCode e)
  58. ;; Enter
  59. 13 (on-enter e)
  60. nil)))))
  61. (defn on-key-up
  62. [state keycode-map all-handler]
  63. (listen state js/window "keyup"
  64. (fn [e]
  65. (let [key-code (.-keyCode e)]
  66. (when-let [f (get keycode-map key-code)]
  67. (f state e))
  68. (when all-handler (all-handler e key-code))))))
  69. (defn on-key-down
  70. ([state keycode-map]
  71. (on-key-down state keycode-map {}))
  72. ([state keycode-map {:keys [not-matched-handler all-handler target]}]
  73. (listen state (or target js/window) "keydown"
  74. (fn [e]
  75. (let [key-code (.-keyCode e)]
  76. (if-let [f (get keycode-map key-code)]
  77. (f state e)
  78. (when (and not-matched-handler (fn? not-matched-handler))
  79. (not-matched-handler e key-code)))
  80. (when (and all-handler (fn? all-handler))
  81. (all-handler e key-code)))))))
  82. (defn event-mixin
  83. ([attach-listeners]
  84. (event-mixin attach-listeners identity))
  85. ([attach-listeners init-callback]
  86. (merge
  87. event-handler-mixin
  88. {:init (fn [state _props]
  89. (init-callback state))
  90. :did-mount (fn [state]
  91. (attach-listeners state)
  92. state)
  93. :did-remount (fn [old-state new-state]
  94. (detach old-state)
  95. (attach-listeners new-state)
  96. new-state)})))
  97. (defn modal
  98. [k]
  99. (event-mixin
  100. (fn [state]
  101. (let [open? (get state k)]
  102. (hide-when-esc-or-outside
  103. state
  104. :on-hide (fn []
  105. (when (and open? @open?)
  106. (reset! open? false))))))
  107. (fn [state]
  108. (let [open? (atom false)
  109. component (:rum/react-component state)]
  110. (add-watch open? ::open
  111. (fn [_ _ _ _]
  112. (rum/request-render component)))
  113. (assoc state
  114. :open? open?
  115. :close-fn (fn []
  116. (reset! open? false))
  117. :open-fn (fn []
  118. (reset! open? true))
  119. :toggle-fn (fn []
  120. (swap! open? not)))))))
  121. (def component-editing-mode
  122. {:will-mount
  123. (fn [state]
  124. (state/set-block-component-editing-mode! true)
  125. state)
  126. :will-unmount
  127. (fn [state]
  128. (state/set-block-component-editing-mode! false)
  129. state)})
  130. (defn perf-measure-mixin
  131. "Does performance measurements in development."
  132. [desc]
  133. {:wrap-render
  134. (fn wrap-render [render-fn]
  135. (fn [state]
  136. (profile
  137. (str "Render " desc)
  138. (render-fn state))))})