|
#?(:cljs (defn sample-scroll-state! [scrollable] |
|
[(.. scrollable -scrollTop) |
|
(.. scrollable -scrollHeight) |
|
(.. scrollable -clientHeight)])) |
|
|
|
#?(:cljs (defn scroll-state> [scrollable] |
|
(m/observe |
|
(fn [!] |
|
(let [sample (fn [] (! (sample-scroll-state! scrollable)))] |
|
(.addEventListener scrollable "scroll" sample #js {"passive" true}) |
|
#(.removeEventListener scrollable "scroll" sample)))))) |
|
|
|
#?(:cljs (defn scroll-state< [scrollable] |
|
(->> (scroll-state> scrollable) |
|
(m/reductions {} [0 0 0]) |
|
(m/latest identity)))) |
|
|
|
; counted collection, fixed/known row height, absolute "jump scroll" |
|
(p/client |
|
(dom/div {:style {:overflowX "hidden" :overflowY "auto"}} |
|
(let [[scrollTop] (new (scroll-state< dom/node)) |
|
max-height (* row-count row-height) |
|
clamped-scroll-top (js/Math.min scrollTop max-height) |
|
start (/ clamped-scroll-top row-height)] |
|
(dom/div {:style {:height (str (* row-height row-count) "px") ; allow absolute jumps when height and count are known |
|
:padding-top (str clamped-scroll-top "px") ; seen elements are replaced with padding |
|
:padding-bottom (str (- max-height clamped-scroll-top) "px")}} |
|
|
|
(p/server |
|
(p/for [x (->> xs (drop start) (take page-size))] |
|
(p/client (dom/div x)))))))) |
|
|
|
; infinite seq, variable row height, uses natural layout at cost of leaving seen elements mounted |
|
(p/client |
|
(dom/div {:style {:overflowX "hidden" :overflowY "auto"}} |
|
(let [!pages (atom 1) pages (p/watch !pages) |
|
[scrollTop scrollHeight clientHeight] (new (scroll-state< dom/node))] |
|
(when (>= scrollTop (- scrollHeight clientHeight |
|
clientHeight)) ; scrollThresholdPx = clientHeight |
|
(swap! !pages inc)) |
|
(dom/div ; content is unstyled, uses natural layout |
|
(p/server |
|
(p/for [x (->> xs (take (* pages page-size)))] ; leave dom |
|
(p/client (dom/div x)))))))) |