@tamagui/react-native-web-lite
Version:
React Native for Web
389 lines • 17.3 kB
JavaScript
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf,
__hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all) __defProp(target, name, {
get: all[name],
enumerable: !0
});
},
__copyProps = (to, from, except, desc) => {
if (from && typeof from == "object" || typeof from == "function") for (let key of __getOwnPropNames(from)) !__hasOwnProp.call(to, key) && key !== except && __defProp(to, key, {
get: () => from[key],
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
});
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
value: mod,
enumerable: !0
}) : target, mod)),
__toCommonJS = mod => __copyProps(__defProp({}, "__esModule", {
value: !0
}), mod);
var ScrollView_exports = {};
__export(ScrollView_exports, {
default: () => ScrollView_default
});
module.exports = __toCommonJS(ScrollView_exports);
var import_react = __toESM(require("react")),
import_react_native_web_internals = require("@tamagui/react-native-web-internals"),
import_View = __toESM(require("../View/index.cjs")),
import_ScrollViewBase = __toESM(require("./ScrollViewBase.cjs")),
import_jsx_runtime = require("react/jsx-runtime");
const IS_ANIMATING_TOUCH_START_THRESHOLD_MS = 16;
class ScrollView extends import_react.default.Component {
_scrollNodeRef;
_innerViewRef;
keyboardWillOpenTo = null;
additionalScrollOffset = 0;
isTouching = !1;
lastMomentumScrollBeginTime = 0;
lastMomentumScrollEndTime = 0;
// Reset to false every time becomes responder. This is used to:
// - Determine if the scroll view has been scrolled and therefore should
// refuse to give up its responder lock.
// - Determine if releasing should dismiss the keyboard when we are in
// tap-to-dismiss mode (!this.props.keyboardShouldPersistTaps).
observedScrollSinceBecomingResponder = !1;
becameResponderWhileAnimating = !1;
/**
* Returns a reference to the underlying scroll responder, which supports
* operations like `scrollTo`. All ScrollView-like components should
* implement this method so that they can be composed while providing access
* to the underlying scroll responder's methods.
*/
getScrollResponder() {
return this.mixin;
}
getScrollableNode() {
return this._scrollNodeRef;
}
getInnerViewRef() {
return this._innerViewRef;
}
getInnerViewNode() {
return this._innerViewRef;
}
getNativeScrollRef() {
return this._scrollNodeRef;
}
render() {
const {
contentContainerStyle,
horizontal,
onContentSizeChange,
refreshControl,
stickyHeaderIndices,
pagingEnabled,
/* eslint-disable */
forwardedRef,
keyboardDismissMode,
onScroll,
centerContent,
/* eslint-enable */
...other
} = this.props;
if (process.env.NODE_ENV !== "production" && this.props.style) {
const style = import_react_native_web_internals.StyleSheet.flatten(this.props.style),
childLayoutProps = ["alignItems", "justifyContent"].filter(prop => style && style[prop] !== void 0);
(0, import_react_native_web_internals.invariant)(childLayoutProps.length === 0, `ScrollView child layout (${JSON.stringify(childLayoutProps)}) must be applied through the contentContainerStyle prop.`);
}
let contentSizeChangeProps = {};
onContentSizeChange && (contentSizeChangeProps = {
onLayout: this._handleContentOnLayout.bind(this)
});
const hasStickyHeaderIndices = !horizontal && Array.isArray(stickyHeaderIndices),
children = hasStickyHeaderIndices || pagingEnabled ? import_react.default.Children.map(this.props.children, (child, i) => {
const isSticky = hasStickyHeaderIndices && stickyHeaderIndices.indexOf(i) > -1;
return child != null && (isSticky || pagingEnabled) ? /* @__PURE__ */(0, import_jsx_runtime.jsx)(import_View.default, {
style: import_react_native_web_internals.StyleSheet.compose(isSticky && styles.stickyHeader, pagingEnabled && styles.pagingEnabledChild),
children: child
}) : child;
}) : this.props.children,
contentContainer = /* @__PURE__ */(0, import_jsx_runtime.jsx)(import_View.default, {
...contentSizeChangeProps,
collapsable: !1,
ref: this._setInnerViewRef.bind(this),
style: [horizontal && styles.contentContainerHorizontal, centerContent && styles.contentContainerCenterContent, contentContainerStyle],
children
}),
baseStyle = horizontal ? styles.baseHorizontal : styles.baseVertical,
pagingEnabledStyle = horizontal ? styles.pagingEnabledHorizontal : styles.pagingEnabledVertical,
props = {
...other,
style: [baseStyle, pagingEnabled && pagingEnabledStyle, this.props.style],
onTouchStart: this.scrollResponderHandleTouchStart.bind(this),
onTouchMove: this.scrollResponderHandleTouchMove.bind(this),
onTouchEnd: this.scrollResponderHandleTouchEnd.bind(this),
onScrollBeginDrag: this.scrollResponderHandleScrollBeginDrag.bind(this),
onScrollEndDrag: this.scrollResponderHandleScrollEndDrag.bind(this),
onMomentumScrollBegin: this.scrollResponderHandleMomentumScrollBegin.bind(this),
onMomentumScrollEnd: this.scrollResponderHandleMomentumScrollEnd.bind(this),
onStartShouldSetResponder: this.scrollResponderHandleStartShouldSetResponder.bind(this),
onStartShouldSetResponderCapture: this.scrollResponderHandleStartShouldSetResponderCapture.bind(this),
onScrollShouldSetResponder: this.scrollResponderHandleScrollShouldSetResponder.bind(this),
onScroll: this._handleScroll.bind(this),
onResponderGrant: this.scrollResponderHandleResponderGrant.bind(this),
onResponderTerminationRequest: this.scrollResponderHandleTerminationRequest.bind(this),
onResponderRelease: this.scrollResponderHandleResponderRelease.bind(this),
onResponderReject: this.scrollResponderHandleResponderReject.bind(this)
},
ScrollViewClass = import_ScrollViewBase.default;
(0, import_react_native_web_internals.invariant)(ScrollViewClass !== void 0, "ScrollViewClass must not be undefined");
const scrollView = /* @__PURE__ */(0, import_jsx_runtime.jsx)(ScrollViewClass, {
...props,
ref: this._setScrollNodeRef.bind(this),
children: contentContainer
});
return refreshControl ? import_react.default.cloneElement(refreshControl, {
style: props.style
}, scrollView) : scrollView;
}
_handleContentOnLayout(e) {
const {
width,
height
} = e.nativeEvent.layout;
this.props.onContentSizeChange(width, height);
}
_handleScroll(e) {
process.env.NODE_ENV !== "production" && this.props.onScroll && this.props.scrollEventThrottle == null && console.info("You specified `onScroll` on a <ScrollView> but not `scrollEventThrottle`. You will only receive one event. Using `16` you get all the events but be aware that it may cause frame drops, use a bigger number if you don't need as much precision."), this.props.keyboardDismissMode === "on-drag" && (0, import_react_native_web_internals.dismissKeyboard)(), this.scrollResponderHandleScroll(e);
}
_setInnerViewRef(node) {
this._innerViewRef = node;
}
_setScrollNodeRef(node) {
this._scrollNodeRef = node, node != null && (node.getScrollResponder = this.getScrollResponder, node.getInnerViewNode = this.getInnerViewNode, node.getInnerViewRef = this.getInnerViewRef, node.getNativeScrollRef = this.getNativeScrollRef, node.getScrollableNode = this.getScrollableNode, node.scrollTo = this.scrollTo, node.scrollToEnd = this.scrollToEnd, node.scrollResponderZoomTo = this.scrollResponderZoomTo, node.scrollResponderScrollNativeHandleToKeyboard = this.scrollResponderScrollNativeHandleToKeyboard), (0, import_react_native_web_internals.mergeRefs)(this.props.forwardedRef)(node);
}
/**
* Invoke this from an `onScroll` event.
*/
scrollResponderHandleScrollShouldSetResponder() {
return this.isTouching;
}
/**
* Merely touch starting is not sufficient for a scroll view to become the
* responder. Being the "responder" means that the very next touch move/end
* event will result in an action/movement.
*
* Invoke this from an `onStartShouldSetResponder` event.
*
* `onStartShouldSetResponder` is used when the next move/end will trigger
* some UI movement/action, but when you want to yield priority to views
* nested inside of the view.
*
* There may be some cases where scroll views actually should return `true`
* from `onStartShouldSetResponder`: Any time we are detecting a standard tap
* that gives priority to nested views.
*
* - If a single tap on the scroll view triggers an action such as
* recentering a map style view yet wants to give priority to interaction
* views inside (such as dropped pins or labels), then we would return true
* from this method when there is a single touch.
*
* - Similar to the previous case, if a two finger "tap" should trigger a
* zoom, we would check the `touches` count, and if `>= 2`, we would return
* true.
*
*/
scrollResponderHandleStartShouldSetResponder() {
return !1;
}
/**
* There are times when the scroll view wants to become the responder
* (meaning respond to the next immediate `touchStart/touchEnd`), in a way
* that *doesn't* give priority to nested views (hence the capture phase):
*
* - Currently animating.
* - Tapping anywhere that is not the focused input, while the keyboard is
* up (which should dismiss the keyboard).
*
* Invoke this from an `onStartShouldSetResponderCapture` event.
*/
scrollResponderHandleStartShouldSetResponderCapture(e) {
return this.scrollResponderIsAnimating();
}
/**
* Invoke this from an `onResponderReject` event.
*
* Some other element is not yielding its role as responder. Normally, we'd
* just disable the `UIScrollView`, but a touch has already began on it, the
* `UIScrollView` will not accept being disabled after that. The easiest
* solution for now is to accept the limitation of disallowing this
* altogether. To improve this, find a way to disable the `UIScrollView` after
* a touch has already started.
*/
scrollResponderHandleResponderReject() {
(0, import_react_native_web_internals.warning)(!1, "ScrollView doesn't take rejection well - scrolls anyway");
}
/**
* We will allow the scroll view to give up its lock iff it acquired the lock
* during an animation. This is a very useful default that happens to satisfy
* many common user experiences.
*
* - Stop a scroll on the left edge, then turn that into an outer view's
* backswipe.
* - Stop a scroll mid-bounce at the top, continue pulling to have the outer
* view dismiss.
* - However, without catching the scroll view mid-bounce (while it is
* motionless), if you drag far enough for the scroll view to become
* responder (and therefore drag the scroll view a bit), any backswipe
* navigation of a swipe gesture higher in the view hierarchy, should be
* rejected.
*/
scrollResponderHandleTerminationRequest() {
return !this.observedScrollSinceBecomingResponder;
}
/**
* Invoke this from an `onTouchEnd` event.
*
* @param {SyntheticEvent} e Event.
*/
scrollResponderHandleTouchEnd(e) {
const nativeEvent = e.nativeEvent;
this.isTouching = nativeEvent.touches.length !== 0, this.props.onTouchEnd && this.props.onTouchEnd(e);
}
/**
* Invoke this from an `onResponderRelease` event.
*/
scrollResponderHandleResponderRelease(e) {
this.props.onResponderRelease && this.props.onResponderRelease(e);
const currentlyFocusedTextInput = import_react_native_web_internals.TextInputState.currentlyFocusedField();
!this.props.keyboardShouldPersistTaps && currentlyFocusedTextInput != null && e.target !== currentlyFocusedTextInput && !this.observedScrollSinceBecomingResponder && !this.becameResponderWhileAnimating && (this.props.onScrollResponderKeyboardDismissed && this.props.onScrollResponderKeyboardDismissed(e), import_react_native_web_internals.TextInputState.blurTextInput(currentlyFocusedTextInput));
}
scrollResponderHandleScroll(e) {
this.observedScrollSinceBecomingResponder = !0, this.props.onScroll && this.props.onScroll(e);
}
/**
* Invoke this from an `onResponderGrant` event.
*/
scrollResponderHandleResponderGrant(e) {
this.observedScrollSinceBecomingResponder = !1, this.props.onResponderGrant && this.props.onResponderGrant(e), this.becameResponderWhileAnimating = this.scrollResponderIsAnimating();
}
/**
* Unfortunately, `onScrollBeginDrag` also fires when *stopping* the scroll
* animation, and there's not an easy way to distinguish a drag vs. stopping
* momentum.
*
* Invoke this from an `onScrollBeginDrag` event.
*/
scrollResponderHandleScrollBeginDrag(e) {
this.props.onScrollBeginDrag && this.props.onScrollBeginDrag(e);
}
/**
* Invoke this from an `onScrollEndDrag` event.
*/
scrollResponderHandleScrollEndDrag(e) {
this.props.onScrollEndDrag && this.props.onScrollEndDrag(e);
}
/**
* Invoke this from an `onMomentumScrollBegin` event.
*/
scrollResponderHandleMomentumScrollBegin(e) {
this.lastMomentumScrollBeginTime = Date.now(), this.props.onMomentumScrollBegin && this.props.onMomentumScrollBegin(e);
}
/**
* Invoke this from an `onMomentumScrollEnd` event.
*/
scrollResponderHandleMomentumScrollEnd(e) {
this.lastMomentumScrollEndTime = Date.now(), this.props.onMomentumScrollEnd && this.props.onMomentumScrollEnd(e);
}
/**
* Invoke this from an `onTouchStart` event.
*
* Since we know that the `SimpleEventPlugin` occurs later in the plugin
* order, after `ResponderEventPlugin`, we can detect that we were *not*
* permitted to be the responder (presumably because a contained view became
* responder). The `onResponderReject` won't fire in that case - it only
* fires when a *current* responder rejects our request.
*
* @param {SyntheticEvent} e Touch Start event.
*/
scrollResponderHandleTouchStart(e) {
this.isTouching = !0, this.props.onTouchStart && this.props.onTouchStart(e);
}
/**
* Invoke this from an `onTouchMove` event.
*
* Since we know that the `SimpleEventPlugin` occurs later in the plugin
* order, after `ResponderEventPlugin`, we can detect that we were *not*
* permitted to be the responder (presumably because a contained view became
* responder). The `onResponderReject` won't fire in that case - it only
* fires when a *current* responder rejects our request.
*
* @param {SyntheticEvent} e Touch Start event.
*/
scrollResponderHandleTouchMove(e) {
this.props.onTouchMove && this.props.onTouchMove(e);
}
/**
* A helper function for this class that lets us quickly determine if the
* view is currently animating. This is particularly useful to know when
* a touch has just started or ended.
*/
scrollResponderIsAnimating() {
return Date.now() - this.lastMomentumScrollEndTime < IS_ANIMATING_TOUCH_START_THRESHOLD_MS || this.lastMomentumScrollEndTime < this.lastMomentumScrollBeginTime;
}
}
const commonStyle = {
flexGrow: 1,
flexShrink: 1,
// Enable hardware compositing in modern browsers.
// Creates a new layer with its own backing surface that can significantly
// improve scroll performance.
transform: [{
translateZ: 0
}],
// iOS native scrolling
WebkitOverflowScrolling: "touch"
},
styles = {
baseVertical: {
...commonStyle,
flexDirection: "column",
overflowX: "hidden",
overflowY: "auto"
},
baseHorizontal: {
...commonStyle,
flexDirection: "row",
overflowX: "auto",
overflowY: "hidden"
},
contentContainerHorizontal: {
flexDirection: "row"
},
contentContainerCenterContent: {
justifyContent: "center",
flexGrow: 1
},
stickyHeader: {
position: "sticky",
top: 0,
zIndex: 10
},
pagingEnabledHorizontal: {
scrollSnapType: "x mandatory"
},
pagingEnabledVertical: {
scrollSnapType: "y mandatory"
},
pagingEnabledChild: {
scrollSnapAlign: "start"
}
},
ForwardedScrollView = import_react.default.forwardRef((props, forwardedRef) => /* @__PURE__ */(0, import_jsx_runtime.jsx)(ScrollView, {
...props,
forwardedRef
}));
ForwardedScrollView.displayName = "ScrollView";
var ScrollView_default = ForwardedScrollView;