|
| 1 | +module Lumi.Components2.ScrollObserver where |
| 2 | + |
| 3 | +import Prelude |
| 4 | +import Data.Newtype (class Newtype) |
| 5 | +import Data.Nullable (Nullable) |
| 6 | +import Data.Nullable as Nullable |
| 7 | +import Effect (Effect) |
| 8 | +import Effect.Unsafe (unsafePerformEffect) |
| 9 | +import Lumi.Components (LumiComponent, lumiComponent) |
| 10 | +import React.Basic (JSX) |
| 11 | +import React.Basic.Hooks (Hook, UnsafeReference(..), UseEffect, UseState, coerceHook, useEffect, useState, (/\)) |
| 12 | +import React.Basic.Hooks as React |
| 13 | +import Web.DOM (Element, Node) |
| 14 | +import Web.DOM.Element (scrollLeft, scrollTop) |
| 15 | +import Web.DOM.Element as Element |
| 16 | +import Web.Event.Event (EventType(..)) |
| 17 | +import Web.Event.EventTarget (EventListener, EventTarget, eventListener) |
| 18 | + |
| 19 | +newtype UseScrollObserver hooks |
| 20 | + = UseScrollObserver (UseEffect (UnsafeReference (Nullable Node)) (UseState Boolean (UseState Boolean hooks))) |
| 21 | + |
| 22 | +derive instance ntUseScrollObserver :: Newtype (UseScrollObserver hooks) _ |
| 23 | + |
| 24 | +useScrollObserver :: Nullable Node -> Hook UseScrollObserver { hasScrolledX :: Boolean, hasScrolledY :: Boolean } |
| 25 | +useScrollObserver root = |
| 26 | + coerceHook React.do |
| 27 | + hasScrolledY /\ setHasScrolledY <- useState false |
| 28 | + hasScrolledX /\ setHasScrolledX <- useState false |
| 29 | + useEffect (UnsafeReference root) do |
| 30 | + scrollParent <- getScrollParent root |
| 31 | + let |
| 32 | + onScroll = do |
| 33 | + top <- scrollTop scrollParent |
| 34 | + left <- scrollLeft scrollParent |
| 35 | + setHasScrolledY \_ -> top > 0.0 |
| 36 | + setHasScrolledX \_ -> left > 0.0 |
| 37 | + onScrollListener <- eventListener \_ -> onScroll |
| 38 | + onScroll |
| 39 | + Element.toEventTarget scrollParent # addPassiveEventListener (EventType "scroll") onScrollListener false |
| 40 | + pure do |
| 41 | + Element.toEventTarget scrollParent # removePassiveEventListener (EventType "scroll") onScrollListener false |
| 42 | + pure { hasScrolledY, hasScrolledX } |
| 43 | + |
| 44 | +type ScrollObserverProps |
| 45 | + = ( node :: Nullable Node |
| 46 | + , content :: { hasScrolledX :: Boolean, hasScrolledY :: Boolean } -> JSX |
| 47 | + ) |
| 48 | + |
| 49 | +scrollObserver :: LumiComponent ScrollObserverProps |
| 50 | +scrollObserver = |
| 51 | + unsafePerformEffect do |
| 52 | + lumiComponent "ScrollObserver" defaults \props -> React.do |
| 53 | + hasScrolled <- useScrollObserver props.node |
| 54 | + pure $ props.content hasScrolled |
| 55 | + where |
| 56 | + defaults = { node: Nullable.null, content: \_ -> mempty } |
| 57 | + |
| 58 | +foreign import getScrollParent :: Nullable Node -> Effect Element |
| 59 | + |
| 60 | +-- | Adds a listener to an event target. The boolean argument indicates whether |
| 61 | +-- | the listener should be added for the "capture" phase. |
| 62 | +foreign import addPassiveEventListener :: |
| 63 | + EventType -> |
| 64 | + EventListener -> |
| 65 | + Boolean -> |
| 66 | + EventTarget -> |
| 67 | + Effect Unit |
| 68 | + |
| 69 | +-- | Removes a listener to an event target. The boolean argument indicates |
| 70 | +-- | whether the listener should be removed for the "capture" phase. |
| 71 | +foreign import removePassiveEventListener :: |
| 72 | + EventType -> |
| 73 | + EventListener -> |
| 74 | + Boolean -> |
| 75 | + EventTarget -> |
| 76 | + Effect Unit |
0 commit comments