mirror of
https://github.com/penpot/penpot.git
synced 2025-07-16 03:35:13 +02:00
✨ Add internal performance oriented changes to tooltip*
This commit is contained in:
parent
fc655224af
commit
f7e94accc3
7 changed files with 180 additions and 113 deletions
|
@ -15,21 +15,48 @@
|
|||
[app.util.timers :as ts]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(defn- calculate-tooltip-rect [tooltip trigger-rect placement offset]
|
||||
(def ^:private ^:const arrow-height 12)
|
||||
(def ^:private ^:const half-arrow-height (/ arrow-height 2))
|
||||
(def ^:private ^:const overlay-offset 32)
|
||||
|
||||
(defn- clear-schedule
|
||||
[ref]
|
||||
(when-let [schedule (mf/ref-val ref)]
|
||||
(ts/dispose! schedule)
|
||||
(mf/set-ref-val! ref nil)))
|
||||
|
||||
(defn- add-schedule
|
||||
[ref delay f]
|
||||
(mf/set-ref-val! ref (ts/schedule delay f)))
|
||||
|
||||
(defn- show-popover
|
||||
[node]
|
||||
(when (.-isConnected ^js node)
|
||||
(.showPopover ^js node)))
|
||||
|
||||
(defn- hide-popover
|
||||
[node]
|
||||
(dom/unset-css-property! node "display")
|
||||
(.hidePopover ^js node))
|
||||
|
||||
(defn- calculate-placement-bounding-rect
|
||||
"Given a placement, calcultates the bounding rect for it taking in
|
||||
account provided tooltip bounding rect and the origin bounding
|
||||
rect."
|
||||
[placement tooltip-brect origin-brect offset]
|
||||
(let [{trigger-top :top
|
||||
trigger-left :left
|
||||
trigger-right :right
|
||||
trigger-bottom :bottom
|
||||
trigger-width :width
|
||||
trigger-height :height} trigger-rect
|
||||
trigger-height :height}
|
||||
origin-brect
|
||||
|
||||
{tooltip-width :width
|
||||
tooltip-height :height} (dom/get-bounding-rect tooltip)
|
||||
tooltip-height :height}
|
||||
tooltip-brect
|
||||
|
||||
offset (d/nilv offset 2)
|
||||
arrow-height 12
|
||||
half-arrow-height (/ arrow-height 2)
|
||||
overlay-offset 32]
|
||||
offset (d/nilv offset 2)]
|
||||
|
||||
(case placement
|
||||
"bottom"
|
||||
|
@ -95,7 +122,10 @@
|
|||
:width tooltip-width
|
||||
:height tooltip-height})))
|
||||
|
||||
(defn- get-fallback-order [placement]
|
||||
(defn- get-fallback-order
|
||||
"Get a vector of placement followed with ordered fallback pacements
|
||||
for the specified placement"
|
||||
[placement]
|
||||
(case placement
|
||||
"top" ["top" "right" "bottom" "left" "top-right" "bottom-right" "bottom-left" "top-left"]
|
||||
"bottom" ["bottom" "left" "top" "right" "bottom-right" "bottom-left" "top-left" "top-right"]
|
||||
|
@ -106,65 +136,88 @@
|
|||
"bottom-left" ["bottom-left" "left" "top" "right" "bottom" "top-left" "top-right" "bottom-right"]
|
||||
"top-left" ["top-left" "top" "right" "bottom" "left" "bottom-left" "top-right" "bottom-right"]))
|
||||
|
||||
(defn- find-matching-placement
|
||||
"Algorithm for find a correct placement and placement-brect for the
|
||||
provided placement, if the current placement does not matches, it
|
||||
uses the predefined fallbacks. Returns an array of matched placement
|
||||
and its bounding rect."
|
||||
[placement tooltip-brect origin-brect window-size offset]
|
||||
(loop [placements (seq (get-fallback-order placement))]
|
||||
(when-let [placement (first placements)]
|
||||
(let [placement-brect (calculate-placement-bounding-rect placement tooltip-brect origin-brect offset)]
|
||||
(if (dom/is-bounding-rect-outside? placement-brect window-size)
|
||||
(recur (rest placements))
|
||||
#js [placement placement-brect])))))
|
||||
|
||||
(defn- update-tooltip-position
|
||||
"Update the tooltip position having in account the current window
|
||||
size, placement. It calculates the appropriate placement and updates
|
||||
the dom with the result."
|
||||
[tooltip placement origin-brect offset]
|
||||
(show-popover tooltip)
|
||||
(let [tooltip-brect (dom/get-bounding-rect tooltip)
|
||||
window-size (dom/get-window-size)]
|
||||
(when-let [[placement placement-rect] (find-matching-placement placement tooltip-brect origin-brect window-size offset)]
|
||||
(dom/set-css-property! tooltip "display" "grid")
|
||||
(dom/set-css-property! tooltip "inset-block-start" (dm/str (:top placement-rect) "px"))
|
||||
(dom/set-css-property! tooltip "inset-inline-start" (dm/str (:left placement-rect) "px"))
|
||||
placement)))
|
||||
|
||||
(def ^:private schema:tooltip
|
||||
[:map
|
||||
[:class {:optional true} :string]
|
||||
[:id {:optional true} :string]
|
||||
[:offset {:optional true} :int]
|
||||
[:delay {:optional true} :int]
|
||||
[:content [:or fn? :string [:fn mf/element?]]]
|
||||
[:placement {:optional true}
|
||||
[:maybe [:enum "top" "bottom" "left" "right" "top-right" "bottom-right" "bottom-left" "top-left"]]]])
|
||||
|
||||
(mf/defc tooltip*
|
||||
{::mf/schema schema:tooltip}
|
||||
[{:keys [class id children tooltip-content placement offset delay] :rest props}]
|
||||
(let [id (or id (mf/use-id))
|
||||
placement* (mf/use-state #(d/nilv placement "top"))
|
||||
placement (deref placement*)
|
||||
delay (d/nilv delay 300)
|
||||
[{:keys [class id children content placement offset delay] :rest props}]
|
||||
(let [internal-id
|
||||
(mf/use-id)
|
||||
|
||||
schedule-ref (mf/use-ref nil)
|
||||
id
|
||||
(d/nilv id internal-id)
|
||||
|
||||
position-tooltip
|
||||
(fn [^js tooltip trigger-rect]
|
||||
(let [all-placements (get-fallback-order placement)]
|
||||
(when (.-isConnected tooltip)
|
||||
(.showPopover ^js tooltip))
|
||||
(loop [[current-placement & remaining-placements] all-placements]
|
||||
(when current-placement
|
||||
(reset! placement* current-placement)
|
||||
(let [tooltip-rect (calculate-tooltip-rect tooltip trigger-rect current-placement offset)]
|
||||
(if (dom/is-bounding-rect-outside? tooltip-rect)
|
||||
(recur remaining-placements)
|
||||
(do (dom/set-css-property! tooltip "display" "grid")
|
||||
(dom/set-css-property! tooltip "inset-block-start" (dm/str (:top tooltip-rect) "px"))
|
||||
(dom/set-css-property! tooltip "inset-inline-start" (dm/str (:left tooltip-rect) "px")))))))))
|
||||
placement*
|
||||
(mf/use-state #(d/nilv placement "top"))
|
||||
|
||||
placement
|
||||
(deref placement*)
|
||||
|
||||
delay
|
||||
(d/nilv delay 300)
|
||||
|
||||
schedule-ref
|
||||
(mf/use-ref nil)
|
||||
|
||||
on-show
|
||||
(mf/use-fn
|
||||
(mf/deps id placement)
|
||||
(mf/deps id placement offset)
|
||||
(fn [event]
|
||||
(when-let [schedule (mf/ref-val schedule-ref)]
|
||||
(ts/dispose! schedule)
|
||||
(mf/set-ref-val! schedule-ref nil))
|
||||
(clear-schedule schedule-ref)
|
||||
(when-let [tooltip (dom/get-element id)]
|
||||
(let [trigger-rect (->> (dom/get-target event)
|
||||
(dom/get-bounding-rect))]
|
||||
(mf/set-ref-val!
|
||||
schedule-ref
|
||||
(ts/schedule
|
||||
delay
|
||||
#(position-tooltip tooltip trigger-rect)))))))
|
||||
(let [origin-brect
|
||||
(->> (dom/get-target event)
|
||||
(dom/get-bounding-rect))
|
||||
|
||||
update-position
|
||||
(fn []
|
||||
(let [placement (update-tooltip-position tooltip placement origin-brect offset)]
|
||||
(reset! placement* placement)))]
|
||||
|
||||
(add-schedule schedule-ref delay update-position)))))
|
||||
|
||||
on-hide
|
||||
(mf/use-fn
|
||||
(mf/deps id)
|
||||
(fn [] (when-let [tooltip (dom/get-element id)]
|
||||
(when-let [schedule (mf/ref-val schedule-ref)]
|
||||
(ts/dispose! schedule)
|
||||
(mf/set-ref-val! schedule-ref nil))
|
||||
(dom/unset-css-property! tooltip "display")
|
||||
(.hidePopover ^js tooltip))))
|
||||
(fn []
|
||||
(when-let [tooltip (dom/get-element id)]
|
||||
(clear-schedule schedule-ref)
|
||||
(hide-popover tooltip))))
|
||||
|
||||
handle-key-down
|
||||
(mf/use-fn
|
||||
|
@ -173,33 +226,38 @@
|
|||
(when (kbd/esc? event)
|
||||
(on-hide))))
|
||||
|
||||
class (d/append-class class (stl/css-case
|
||||
:tooltip true
|
||||
:tooltip-top (= placement "top")
|
||||
:tooltip-bottom (= placement "bottom")
|
||||
:tooltip-left (= placement "left")
|
||||
:tooltip-right (= placement "right")
|
||||
:tooltip-top-right (= placement "top-right")
|
||||
:tooltip-bottom-right (= placement "bottom-right")
|
||||
:tooltip-bottom-left (= placement "bottom-left")
|
||||
:tooltip-top-left (= placement "top-left")))
|
||||
tooltip-class
|
||||
(stl/css-case
|
||||
:tooltip true
|
||||
:tooltip-top (identical? placement "top")
|
||||
:tooltip-bottom (identical? placement "bottom")
|
||||
:tooltip-left (identical? placement "left")
|
||||
:tooltip-right (identical? placement "right")
|
||||
:tooltip-top-right (identical? placement "top-right")
|
||||
:tooltip-bottom-right (identical? placement "bottom-right")
|
||||
:tooltip-bottom-left (identical? placement "bottom-left")
|
||||
:tooltip-top-left (identical? placement "top-left"))
|
||||
|
||||
props
|
||||
(mf/spread-props props
|
||||
{:on-mouse-enter on-show
|
||||
:on-mouse-leave on-hide
|
||||
:on-focus on-show
|
||||
:on-blur on-hide
|
||||
:on-key-down handle-key-down
|
||||
:class (stl/css :tooltip-trigger)
|
||||
:aria-describedby id})
|
||||
content
|
||||
(if (fn? content)
|
||||
(content)
|
||||
content)]
|
||||
|
||||
props (mf/spread-props props {:on-mouse-enter on-show
|
||||
:on-mouse-leave on-hide
|
||||
:on-focus on-show
|
||||
:on-blur on-hide
|
||||
:on-key-down handle-key-down
|
||||
:class (stl/css :tooltip-trigger)
|
||||
:aria-describedby id})]
|
||||
[:> :div props
|
||||
children
|
||||
[:div {:class class
|
||||
[:div {:class [class tooltip-class]
|
||||
:id id
|
||||
:popover "auto"
|
||||
:role "tooltip"}
|
||||
[:div {:class (stl/css :tooltip-content)}
|
||||
(if (fn? tooltip-content)
|
||||
(tooltip-content)
|
||||
tooltip-content)]
|
||||
[:div {:class (stl/css :tooltip-content)} content]
|
||||
[:div {:class (stl/css :tooltip-arrow)
|
||||
:id "tooltip-arrow"}]]]))
|
||||
:id "tooltip-arrow"}]]]))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue