Skip to content

Compare notes #1

@Jarred-Sumner

Description

@Jarred-Sumner

So I actually also setup Slack's library with my react native app for the bottom sheet a few weeks ago!

Here's the relevant code in case its helpful. I haven't had time to move it into an open source library yet.

  • Mine has a sticky header at the top which is rendered above the pan sheet.
  • Inside the pan sheet, it supports multiple scrollviews via react context (code snippet at the bottom).
  • I expose methods to programmatically transition between short and longform height
  • a react context that lets any child in the pan sheet view can easily change the state. It does this ~synchronously via the JSI.

This is how I use it in JS:

  <View style={{ flex: 1 }}>
        <PanSheetView
          ref={this.panSheet}
          screenOffset={showStart ? TOP_HEADER : LIST_HEADER_HEIGHT + 8}
          headerTag={this.headerTag}
          screenMinY={TOP_Y}
          longHeight={SCREEN_DIMENSIONS.height - TOP_Y}
          defaultPresentationState={
            this.props.initialStep === GalleryStep.searchInput
              ? PanSheetViewSize.tall
              : PanSheetViewSize.short
          }
          shortHeight={
            SCREEN_DIMENSIONS.height -
            TOP_Y -
            (showStart ? TOP_HEADER : LIST_HEADER_HEIGHT)
          }
          onDismiss={this.handleDismiss}
          onWillDismiss={this.handleWillDismiss}
          didAppear={this.showHeader}
        >
          <View style={styles.container}>{this.renderStep()}</View>
        </PanSheetView>
        {showStart && (
          <StartFromHeader ref={this.startFromHeader} scrollY={this.scrollY} />
        )}
      </View>

This is all the relevant code I think:

//
//  PanViewManager.swift
//  yeet
//
//  Created by Jarred WSumner on 2/11/20.
//  Copyright © 2020 Yeet. All rights reserved.
//

import Foundation

protocol PanHostViewInteractor {
  func present(_ modalHostView: PanViewSheet!, with viewController: UIViewController!, animated: Bool)
  func dismiss(_ modalHostView: PanViewSheet!, with viewController: UIViewController!, animated: Bool)
}

@objc(PanViewManager)
class PanViewManager : RCTViewManager, PanHostViewInteractor, RCTInvalidating  {
  func present(_ modalHostView: PanViewSheet!, with viewController: UIViewController!, animated: Bool) {

    let panView: PanViewSheet = modalHostView as! PanViewSheet

    guard let _viewController = panView.reactSuperview()?.reactViewController() else {
      return
    }

    guard !panView.presented else {
      return
    }

    guard panView.reactSubview != nil else {
      return
    }


    guard !(panView.panViewController?.isPanModalPresented ?? false) else {
      return
    }

    panView.panViewController?.panPresentationState = panView._defaultPresentationState


    panView.presented = true

    // Wait a tick.

      _viewController.presentPanModal(panView.panViewController!)
       panView.onPresent(_viewController)
    

  }

  @objc(invalidate) func invalidate() {
    hostViews.allObjects.forEach { view in
      view.invalidate()
    }

    hostViews.removeAllObjects()
  }


  func dismiss(_ modalHostView: PanViewSheet!, with viewController: UIViewController!, animated: Bool) {

    var panView: PanViewSheet = modalHostView as! PanViewSheet

    panView.panViewController?.dismiss(animated: animated)
  }


  
  override func view() -> UIView! {
    Log.debug("BEFORE")
    var _view = PanViewSheet(bridge: bridge)
    Log.debug("HERE")
    let panViewController = PanViewController(bridge: bridge)
    _view.panViewController = panViewController

    _view.delegate = self;


    hostViews.add(_view)

    return _view
  }

 override static func moduleName() -> String! {
   return "PanSheetView";
 }


  @objc(clearPanView:)
  func clearPanView(_ tag: NSNumber) {
    RCTExecuteOnMainQueue {
      guard let panView = self.bridge?.uiManager?.view(forReactTag: tag) as? PanViewSheet else {
        return
      }

      if panView.panViewController?.isPanModalPresented ?? false {
        panView.panViewController?.dismiss(animated: true)
      }
      panView.bridge = nil
    }
  }



  var hostViews = NSHashTable<PanViewSheet>(options: .weakMemory)


  override func shadowView() -> RCTShadowView! {
    return PanShadowView()
  }

 
  @objc(presentViewManagerFrom:to:) func presentViewManager(_ from: NSNumber, _ to: NSNumber) {
    guard var bridge = self.bridge else {
      return
    }


    RCTExecuteOnMainQueue {
      guard let panView = bridge.uiManager.view(forReactTag: to) as? PanViewSheet else {
        return
      }

      panView.presentIt()
    }

  }

  @objc(transition:to:) func transition(_ tag: NSNumber, _ to: String) {
    guard var bridge = self.bridge else {
      return
    }


    guard bridge.isValid && !bridge.isLoading else {
      return
    }

    let presentationState = PanViewSheet.PAN_PRESENTATION_STATE_CONVERTER[to] ?? .longForm

    RCTExecuteOnMainQueue { [weak bridge] in
      guard let panView = bridge?.uiManager?.view(forReactTag: tag) as? PanViewSheet else {
        return
      }

      guard let panViewController = panView.panViewController else {
        return
      }

      if to == "dismiss" {
        panViewController.dismiss(animated: true, completion: nil)
      } else {
        panViewController.transition(to: presentationState)
      }

    }

  }

  override static func requiresMainQueueSetup() -> Bool {
    return true
  }
}
//
//  PanShadowView.m
//  yeet
//
//  Created by Jarred WSumner on 2/11/20.
//  Copyright © 2020 Yeet. All rights reserved.
//

#import "PanShadowView.h"
#import <React/RCTUtils.h>

@implementation PanShadowView

- (void)insertReactSubview:(id<RCTComponent>)subview atIndex:(NSInteger)atIndex
{
  [super insertReactSubview:subview atIndex:atIndex];
  if ([subview isKindOfClass:[RCTShadowView class]]) {
    ((RCTShadowView *)subview).size = RCTScreenSize();
  }
}

@end
//
//  ReactNativePanViewController.swift
//  yeet
//
//  Created by Jarred WSumner on 2/11/20.
//  Copyright © 2020 Yeet. All rights reserved.
//

import Foundation
import PanModal

@objc(PanViewController)
class PanViewController : UIViewController, PanModalPresentable {
  weak var bridge: RCTBridge? = nil
  var panViewTag: NSNumber? = nil
  var panView: PanViewSheet? {
    guard panViewTag != nil else {
      return nil
    }

    return bridge?.uiManager?.view(forReactTag: panViewTag) as? PanViewSheet
  }
  init(bridge: RCTBridge?) {
    self.bridge = bridge


    super.init(nibName: nil, bundle: nil)
    self.view = UIView(frame: UIScreen.main.bounds)
    self.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]

    self.modalPresentationStyle = .custom

  }

  override func loadView() {
    super.loadView()

    self.view.clipsToBounds = false
  }


  override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation { get { return .portrait } }


  func supportedInterfaceOrientations(for window: UIWindow?) -> UIInterfaceOrientationMask {
    return .portrait
  }


  required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }

  override var supportedInterfaceOrientations : UIInterfaceOrientationMask {
    get {
      return .portrait
    }

    set {

    }
  }



  @objc(longHeight) var longHeight : CGFloat = UIScreen.main.bounds.height {
    didSet {
      self.panModalSetNeedsLayoutUpdate()
    }
  }
  @objc(shortHeight) var shortHeight : CGFloat = UIScreen.main.bounds.height - 100 {
     didSet {
       self.panModalSetNeedsLayoutUpdate()
     }
  }


  var panScrollView : UIScrollView? {
    get {
      let view = bridge?.uiManager.view(forReactTag: panScrollTag)

      if let _view = view as? RCTScrollView {
        return _view.scrollView
      } else if type(of: view) == UIScrollView.self {
        return view as! UIScrollView
      } else {
        return nil
      }
    }
  }

  var panScrollTag: NSNumber? = nil
  var panScrollable: UIScrollView? {
    get {
      if let scrollView = self.panScrollView {
        return scrollView
      } else {
        return nil
      }
    }
  }

  var hasLoaded = false

  override func viewDidLoad() {
    super.viewDidLoad()
    hasLoaded = true


//    panModalSetNeedsLayoutUpdate()
//    panModalTransition(to: panPresentationState)
  }



  var _topOffset = CGFloat(0)

  var topOffset: CGFloat {
    get {
      return _topOffset
    }

    set (newValue) {
      _topOffset = newValue

      panModalSetNeedsLayoutUpdate()
    }
  }





//   var panScrollable: UIScrollView? {
//         return nil
//     }

 var longFormHeight: PanModalHeight {
  return .contentHeightIgnoringSafeArea(longHeight)
 }

  var shortFormHeight: PanModalHeight {
   return .contentHeightIgnoringSafeArea(shortHeight)
  }

  var transitionAnimationOptions: UIView.AnimationOptions {
      return [.allowUserInteraction, .beginFromCurrentState]
  }

 var anchorModalToLongForm: Bool {
  get { return panView?._defaultPresentationState == .longForm }
 }

 var shouldRoundTopCorners: Bool {
     return false
 }

  func reloadHeader() {
    RCTExecuteOnMainQueue {
      guard let headerView = self.headerView else {
         return
      }

      guard let reactSubview = self.panView?.reactSubview ?? nil else {
        return
      }

      self.view.insertSubview(headerView, belowSubview: reactSubview)
    }
  }

  var minY: CGFloat = 0

 var allowsExtendedPanScrolling: Bool { true }

 /**
  A flag to determine if dismissal should be initiated when swiping down on the presented view.

  Return false to fallback to the short form state instead of dismissing.

  Default value is true.
  */
 var allowsDragToDismiss: Bool { true }

 /**
  A flag to determine if dismissal should be initiated when tapping on the dimmed background view.

  Default value is true.
  */
 var allowsTapToDismiss: Bool { true }

  /**
   Notifies the delegate when the pan modal gesture recognizer state is either
   `began` or `changed`. This method gives the delegate a chance to prepare
   for the gesture recognizer state change.

   For example, when the pan modal view is about to scroll.

   Default value is an empty implementation.
   */
  func willRespond(to panModalGestureRecognizer: UIPanGestureRecognizer) {
    guard let reactSubview = panView?.reactSubview else {
          return
        }

        guard let headerView = self.headerView else {
          return
        }

    guard let superview  =  view.superview else {
      return
    }

    guard let window = UIApplication.shared.keyWindow else {
      return
    }

    let dist = panModalGestureRecognizer.translation(in: window)


    let totalDistance = dist.y + (headerView.frame.size.height - superview.frame.y)

    let endY = totalDistance > 0 ? totalDistance : 0

    if let dragIndicatorView = self.dragIndicatorView {
      let endValue: Float = endY == 0 ? 1 : 0

      if endValue != dragIndicatorView.layer.opacity {
        panModalAnimate({
          dragIndicatorView.layer.opacity = endValue
        })
      }
    }

    headerView.transform = CGAffineTransform.identity.translatedBy(x: .zero, y: endY)
  }


  /**
   Asks the delegate if the pan modal gesture recognizer should be prioritized.

   For example, you can use this to define a region
   where you would like to restrict where the pan gesture can start.

   If false, then we rely solely on the internal conditions of when a pan gesture
   should succeed or fail, such as, if we're actively scrolling on the scrollView.

   Default return value is false.
   */
  func shouldPrioritize(panModalGestureRecognizer: UIPanGestureRecognizer) -> Bool {


    return false
  }

  /**
   Asks the delegate if the pan modal should transition to a new state.

   Default value is true.
   */
  func shouldTransition(to state: PanModalPresentationController.PresentationState) -> Bool {
    return true
  }

  var panPresentationState = PanModalPresentationController.PresentationState.shortForm
  /**
   Notifies the delegate that the pan modal is about to transition to a new state.

   Default value is an empty implementation.
   */
  func adjustHeaderPosition(state: PanModalPresentationController.PresentationState) {
    if let headerView = self.headerView {
      if state == .longForm {
        panModalAnimate({
          headerView.transform = CGAffineTransform.init(translationX: 0, y: (self.longHeight - self.shortHeight) )
        })
      } else {
        panModalAnimate({
          headerView.transform = .identity
        })
      }
    }
  }
  func willTransition(to state: PanModalPresentationController.PresentationState) {
     adjustHeaderPosition(state: state)
    panView?.willTransition(to: state)
    panPresentationState = state
    panModalSetNeedsLayoutUpdate()


    if let dragIndicatorView = self.dragIndicatorView {
      let _alpha: Float = state == .longForm ? 0.0 : 1.0
      if _alpha != dragIndicatorView.layer.opacity {
        panModalAnimate({
          dragIndicatorView.layer.opacity = _alpha
        })
      }
    }
  }

  var dragIndicatorView: UIView? {
    guard let panPresentationController = self.panPresentationController else {
      return nil
    }

    return view.superview?.subviews.first(where: { view -> Bool in
      return view.backgroundColor == self.dragIndicatorBackgroundColor
    })
  }

  var panPresentationController: PanModalPresentationController?  { return presentationController as? PanModalPresentationController }
  var showDragIndicator: Bool {
    return headerTag == nil
  }

  var headerTag: NSNumber? = nil  {
    didSet {
      reloadHeader()
    }
  }
  var headerView: UIView? {
    guard let headerTag = self.headerTag else {
      return nil
    }

    guard bridge?.isValid ?? false else {
      return nil
    }

    guard let uiManager = bridge?.uiManager else {
      return nil
    }

    return uiManager.view(forReactTag: headerTag)
  }
  /**
   Notifies the delegate that the pan modal is about to be dismissed.

   Default value is an empty implementation.
   */
  func panModalWillDismiss() {
    panView?.resetTouch()
    panView?.onWillDismiss?([:])

    if let headerView = self.headerView {
        panModalAnimate({
          self.headerView?.layer.opacity = 0
        })
      }
  }



  override func viewWillAppear(_ animated: Bool) {
     super.viewWillAppear(animated)

    reloadHeader()

    if let headerView = self.headerView {
      headerView.layer.opacity = 0
      panModalAnimate({
        self.headerView?.layer.opacity = 1
      })
    }

    // Force it to start in the long form
    if panView?._defaultPresentationState == .longForm {
      DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
        self.transition(to: .longForm)
      }
    }

   }

  override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    panView?.didAppear?([:])
    reloadHeader()
    headerView?.layer.opacity = 1
  }


  override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)

    if let headerView = self.headerView {
       panModalAnimate({
         self.headerView?.layer.opacity = 0
       })
     }
  }


  func transition(to: PanModalPresentationController.PresentationState) {
    panModalTransition(to: to)
  }

  override func viewDidDisappear(_ animated: Bool) {
    super.viewDidDisappear(animated)
    panView?.presented = false
    panView?.resetTouch()
  }


  /**
   Notifies the delegate after the pan modal is dismissed.

   Default value is an empty implementation.
   */
  func panModalDidDismiss() {

    panView?.onDismiss?([:])
  }

}
  //
//  PanViewSheet.swift
//  yeet
//
//  Created by Jarred WSumner on 2/11/20.
//  Copyright © 2020 Yeet. All rights reserved.
//
import Foundation
import UIKit
import PanModal
import SwiftyJSON

@objc(PanViewSheet)
class PanViewSheet : UIView {



  override func didSetProps(_ changedProps: [String]!) {
    if self.reactTag != nil {
      self.panViewController?.panViewTag = reactTag
    }

    self._defaultPresentationState = PanViewSheet.PAN_PRESENTATION_STATE_CONVERTER[defaultPresentationState as String? ?? "shortForm"] ?? .shortForm


    RCTExecuteOnMainQueue {
      self.panViewController?.panModalSetNeedsLayoutUpdate()
    }
  }

  override func didMoveToWindow() {
    super.didMoveToWindow()

    if superview == nil && window == nil {

      return
    }

    guard superview != nil else {
      return
    }

    guard !presented else {
      return
    }

    if isDetached {
      return
    }


    guard panViewController != nil else {
      return
    }


    if (!self.isUserInteractionEnabled && !(superview?.reactSubviews()?.contains(self) ?? false)) {
       return;
   }


    self.panViewController?.panViewTag = reactTag
    self.panViewController?.topOffset = self.screenOffset
    self.panViewController?.minY = self.screenMinY
    self.panViewController?.longHeight = self.longHeight
    self.panViewController?.shortHeight = self.shortHeight


    if let reactSubview = self.reactSubview {
      self.panViewController?.view.insertSubview(reactSubview, at: 0)
      self.panViewController?.reloadHeader()
      panViewController?.panModalSetNeedsLayoutUpdate()

    }


    delegate?.present(self, with: reactViewController(), animated: true)
    presented = true
  }

  func presentIt() {



  }
  var canPresent = false

  var delegate: PanHostViewInteractor? = nil


  var invalidated = false

  @objc(invalidate) func invalidate() {
    guard !invalidated else {
      return
    }

    self.invalidated = true
    if presented {
      dismissController()
    }

    resetTouch()
  }

  func resetTouch() {
    guard let touchHandler = self.touchHandler else {
         return
       }

    if RCTIsMainQueue() {
      if let reactSubview = self.reactSubview {
          if touchHandler.view == reactSubview {
            touchHandler.detach(from: reactSubview)
          }
        }
    }


     touchHandler.cancel()
     touchHandler.reset()
     touchHandler.isEnabled = false
     self.touchHandler = nil
  }
  var presented = false
  override func didMoveToSuperview() {
    super.didMoveToSuperview()

    if (presented && superview == nil) {
      self.dismissController()
    }
  }

  func dismissController() {


    presented = false
    RCTExecuteOnMainQueue {
      self.delegate?.dismiss(self, with: self.reactViewController(), animated: true)
    }
  }

  @objc(containerTag) var containerTag: NSNumber? = nil
  var touchHandler: RCTTouchHandler?
  init(bridge: RCTBridge) {
    self.bridge = bridge
    touchHandler = RCTTouchHandler(bridge: bridge)
    super.init(frame: .zero)

    self.backgroundColor = .clear
  }

  var reactSubview : UIView? = nil
  override func insertReactSubview(_ subview: UIView!, at atIndex: Int) {
    super.insertReactSubview(subview, at: atIndex)
    touchHandler?.attach(to: subview)
   reactSubview = subview
  }

  @objc(headerTag) var headerTag: NSNumber? = nil {
     didSet {
       self.panViewController?.headerTag = self.headerTag
     }
   }

  override func removeReactSubview(_ subview: UIView!) {
    super.removeReactSubview(subview)
    touchHandler?.detach(from: subview)

     reactSubview = nil
  }

  @objc(longHeight) var longHeight : CGFloat = UIScreen.main.bounds.height {


    didSet (newValue) {
      self.panViewController?.longHeight = newValue
    }
  }

  @objc(shortHeight) var shortHeight : CGFloat = UIScreen.main.bounds.height - 200 {

     didSet (newValue) {
       self.panViewController?.shortHeight = newValue
     }
  }


  func onPresent(_ parent: UIViewController) {
//    panViewController?.didMove(toParent: parent)
  }

  override func didUpdateReactSubviews() {

  }

  @objc(onWillDismiss)
  var onWillDismiss: RCTDirectEventBlock? = nil
  @objc(onDismiss)
  var onDismiss: RCTDirectEventBlock? = nil

  @objc(screenMinY)
  var screenMinY : CGFloat = .zero {
    didSet (newValue) {
      self.panViewController?.minY = newValue
    }
  }

  @objc(screenOffset)
  var screenOffset : CGFloat = .zero {
    didSet (newValue) {
      self.panViewController?.topOffset = newValue
    }
  }

  required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }

  @objc(panScrollTag) var panScrollTag: NSNumber? {
    get {
      return panViewController?.panScrollTag
    }

    set (newValue) {
      Log.debug("PAN SCROL TAG \(newValue)")
      panViewController?.panScrollTag = newValue
    }
  }

  static let PAN_PRESENTATION_STATE_CONVERTER = [
    "longForm": PanModalPresentationController.PresentationState.longForm,
    "shortForm": PanModalPresentationController.PresentationState.shortForm
  ]

  static let PAN_PRESENTATION_TO_STRING = Dictionary(grouping: PAN_PRESENTATION_STATE_CONVERTER.keys.sorted(), by: { PAN_PRESENTATION_STATE_CONVERTER[$0]! })

  @objc(defaultPresentationState)
  var defaultPresentationState : NSString? = nil

  var _defaultPresentationState: PanModalPresentationController.PresentationState = .shortForm

  @objc(onTransition)
  var onTransition: RCTDirectEventBlock? = nil

  func willTransition(to: PanModalPresentationController.PresentationState) {
    guard let _onTransition = self.onTransition else {
      return
    }

    

    let from: PanModalPresentationController.PresentationState = to == .longForm ? .shortForm : .longForm
    _onTransition([
      "from": PanViewSheet.PAN_PRESENTATION_TO_STRING[from]?.first,
      "to": PanViewSheet.PAN_PRESENTATION_TO_STRING[to]?.first
    ])


  }


  @objc(didAppear)
  var didAppear: RCTDirectEventBlock? = nil




  @objc(bridge)
  var bridge: RCTBridge? = nil
  @objc(panViewController)
  var panViewController: PanViewController? = nil {
    didSet {
//      self.panViewController?.boundsDidChangeBlock = { rect in
//        guard let bridge = self.bridge else {
//          return
//        }
//
//        guard !bridge.isLoading && bridge.isValid else {
//          return
//        }
//
//        self.bridge?.uiManager.setSize(rect.size, for: self)
//      }
    }
  }

  override func layoutSubviews() {
    super.layoutSubviews()
  }


  deinit {
    resetTouch()
    Log.debug("DEINIT PAN VIEW SHEET")
    
  }


}
import * as React from "react";
import {
  requireNativeComponent,
  NativeSyntheticEvent,
  findNodeHandle,
  View,
  StyleSheet,
  ScrollView as ScrollViewType,
  LayoutAnimation
} from "react-native";
import FastList from "../FastList";
import {
  presentPanSheetView,
  PanSheetViewSize,
  transitionPanSheetView
} from "../../lib/Yeet";
const ScrollView = require("react-native/Libraries/Components/ScrollView/ScrollView");
// const AppContainer = require("react-native/AppContainer");

type Props = {
  screenOffset: number;
  screenMinY: number;
  panScrollTag: number | null;
  longHeight: number;

  shortHeight: number;

  onDismiss: (event: NativeSyntheticEvent<any>) => void;
  onWillDismiss: (event: NativeSyntheticEvent<any>) => void;
};
const NativePanSheetView = requireNativeComponent(
  "PanView"
) as React.ComponentType<Props>;

type PanSheetContextValue = {
  setActiveScrollView: (ref) => void;
  setSize: (size: PanSheetViewSize) => void;
  size: PanSheetViewSize;
  panScrollTag: number | null;
};
export const PanSheetContext = React.createContext<PanSheetContextValue>({
  setActiveScrollView: ref => null,
  setSize: (size: PanSheetViewSize) => null,
  panScrollTag: 0,
  size: PanSheetViewSize.short
});

export class PanSheetView extends React.Component<
  Props,
  { panSheetValue: PanSheetContextValue }
> {
  setActiveScrollView = ref => {
    if (ref === null) {
      this.setState({
        panSheetValue: {
          ...this.state.panSheetValue,
          panScrollTag: null
        }
      });
    } else if (ref?.current instanceof FastList) {
      this.setActiveScrollView(ref.current.getScrollView());
    } else if (typeof ref?.current?.getNode === "function") {
      this.setActiveScrollView(ref.current.getNode());
    } else if (typeof ref?.current !== "undefined") {
      console.log(
        "GOT",
        (ref.current as ScrollViewType)?.scrollResponderGetScrollableNode()
      );
      this.setState({
        panSheetValue: {
          ...this.state.panSheetValue,
          panScrollTag: (ref.current as ScrollViewType)?.scrollResponderGetScrollableNode()
        }
      });
    } else if ((ref as ScrollViewType)?.scrollResponderGetScrollableNode()) {
      console.log(
        "GOT IT!",
        (ref as ScrollViewType)?.scrollResponderGetScrollableNode()
      );
      this.setState({
        panSheetValue: {
          ...this.state.panSheetValue,
          panScrollTag: (ref as ScrollViewType)?.scrollResponderGetScrollableNode()
        }
      });
    } else {
      this.setState({
        panSheetValue: {
          ...this.state.panSheetValue,

          panScrollTag: null
        }
      });
    }
  };

  static defaultProps = {
    defaultPresentationState: PanSheetViewSize.short
  };

  constructor(props) {
    super(props);

    this.state = {
      panSheetValue: {
        size: props.defaultPresentationState,
        setActiveScrollView: this.setActiveScrollView,
        panScrollTag: null,
        setSize: this.setSize
      }
    };
  }

  containerRef = React.createRef();
  containerTag: number;
  nativePanSheetView = React.createRef<View>();

  _shouldSetResponder = () => false;

  componentDidMount() {}

  handleTransition = (
    event: NativeSyntheticEvent<{
      from: PanSheetViewSize;
      to: PanSheetViewSize;
    }>
  ) => {
    if (event.nativeEvent.to === this.state.panSheetValue.size) {
      return;
    }
    this.setState({
      panSheetValue: {
        ...this.state.panSheetValue,
        size: event.nativeEvent.to
      }
    });
  };

  setSize = (size: PanSheetViewSize) => {
    if (!this.nativePanSheetView.current) {
      return;
    }

    if (this.state.panSheetValue.size !== size) {
      this.setState({
        panSheetValue: {
          ...this.state.panSheetValue,
          size
        }
      });
    }

    transitionPanSheetView(
      findNodeHandle(this.nativePanSheetView.current),
      size
    );
  };

  dismiss = () => {
    transitionPanSheetView(
      findNodeHandle(this.nativePanSheetView.current),
      "dismiss"
    );
  };

  setNativeProps = props => {
    this.nativePanSheetView.current.setNativeProps(props);
  };

  render() {
    return (
      <NativePanSheetView
        screenOffset={this.props.screenOffset}
        screenMinY={this.props.screenMinY}
        ref={this.nativePanSheetView}
        longHeight={this.props.longHeight}
        shortHeight={this.props.shortHeight}
        headerTag={this.props.headerTag}
        // onStartShouldSetResponder={this._shouldSetResponder}
        onTransition={this.handleTransition}
        didAppear={this.props.didAppear}
        defaultPresentationState={this.props.defaultPresentationState}
        onDismiss={this.props.onDismiss}
        onTransition={this.handleTransition}
        style={styles.modal}
        onWillDismiss={this.props.onWillDismiss}
        panScrollTag={this.state.panSheetValue.panScrollTag}
      >
        <PanSheetContext.Provider value={this.state.panSheetValue}>
          <ScrollView.Context.Provider value={null}>
            <View style={styles.container}>{this.props.children}</View>
          </ScrollView.Context.Provider>
        </PanSheetContext.Provider>
      </NativePanSheetView>
    );
  }
}

const styles = StyleSheet.create({
  modal: {
    position: "absolute",
    overflow: "visible"
  },
  container: {
    /* $FlowFixMe(>=0.111.0 site=react_native_fb) This comment suppresses an
     * error found when Flow v0.111 was deployed. To see the error, delete this
     * comment and run Flow. */
    left: 0,
    top: 0,
    overflow: "visible",
    width: "100%",
    height: "100%"
  }
});

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions