@quidone/react-native-wheel-picker
Version:
Picker is a UI component for selecting an item from a list of options.
152 lines (151 loc) • 6.92 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _react = _interopRequireWildcard(require("react"));
var _reactUsefulHooks = require("@rozhkov/react-useful-hooks");
var _debounce = _interopRequireDefault(require("../debounce"));
var _jsxRuntime = require("react/jsx-runtime");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
const withScrollStartEndEvent = Component => {
const Wrapper = ({
onScrollStart: onScrollStartProp,
onScrollEnd: onScrollEndProp,
onScrollBeginDrag: onScrollBeginDragProp,
onScrollEndDrag: onScrollEndDragProp,
onMomentumScrollBegin: onMomentumScrollBeginProp,
onMomentumScrollEnd: onMomentumScrollEndProp,
scrollOffset,
...rest
}, forwardedRef) => {
const onScrollStartStable = (0, _reactUsefulHooks.useStableCallback)(onScrollStartProp);
const isOnScrollStartCalledRef = (0, _react.useRef)(false);
/*
* `isImplicitScrollRef` marks a scroll session that was detected from
* `contentOffset` updates only, without a native start callback.
*
* Why this exists:
* Android may perform a programmatic animated scroll
* (`scrollTo` / `scrollToIndex`) and emit several offset updates while
* skipping the normal callback pair that we usually rely on:
* `onScrollBeginDrag` / `onScrollEndDrag` and
* `onMomentumScrollBegin` / `onMomentumScrollEnd`.
*
* Relevant React Native issues:
* https://github.com/facebook/react-native/issues/11693
* https://github.com/facebook/react-native/issues/19246
* https://github.com/facebook/react-native/issues/25672
* https://github.com/facebook/react-native/issues/26661
*
* In this library it shows up when one wheel triggers a synchronized
* animated scroll in another wheel. Example sequence on Android:
* - offset changes: 48 -> 47.6 -> 46.0 -> 43.4 -> ... -> 0
* - but no native "begin/end" events are dispatched for that movement
*
* Old failure mode:
* - the first offset update inferred scroll start and scheduled a debounced
* scroll end
* - the next offset updates only cleared that debounced end
* - because Android never sent a native end event, the session stayed
* "started" forever
* - `PickerControl` then kept one picker in `isStopped = false`, so the
* aggregated DatePicker `onDateChanged` stopped firing
*
* We still emit `onScrollStart` for such a session, but we must finish it
* differently: if the session is implicit, every offset update should
* re-arm the debounced end so that the last offset update wins and the
* scroll eventually ends even when Android never gives us a native end
* callback.
*
* Related library issues:
* https://github.com/quidone/react-native-wheel-picker/issues/56
* https://github.com/quidone/react-native-wheel-picker/issues/71
*/
const isImplicitScrollRef = (0, _react.useRef)(false);
const deactivateOnScrollStart = (0, _reactUsefulHooks.useStableCallback)(() => {
isOnScrollStartCalledRef.current = false;
isImplicitScrollRef.current = false;
});
const maybeCallOnScrollStart = (0, _reactUsefulHooks.useStableCallback)(({
implicit
}) => {
const shouldActivate = !isOnScrollStartCalledRef.current;
if (shouldActivate) {
onScrollStartStable();
isOnScrollStartCalledRef.current = true;
isImplicitScrollRef.current = implicit;
return;
}
if (!implicit) {
isImplicitScrollRef.current = false;
}
});
const maybeCallOnNativeScrollStart = (0, _reactUsefulHooks.useStableCallback)(() => {
maybeCallOnScrollStart({
implicit: false
});
});
const maybeCallOnImplicitScrollStart = (0, _reactUsefulHooks.useStableCallback)(() => {
maybeCallOnScrollStart({
implicit: true
});
});
const onScrollEndStable = (0, _reactUsefulHooks.useStableCallback)(() => {
maybeCallOnNativeScrollStart();
onScrollEndProp?.();
deactivateOnScrollStart();
});
const onScrollEnd = (0, _react.useMemo)(() => (0, _debounce.default)(onScrollEndStable, 100),
// A small delay is needed so that onScrollEnd doesn't trigger prematurely.
[onScrollEndStable]);
const onScrollBeginDrag = (0, _reactUsefulHooks.useStableCallback)(args => {
maybeCallOnNativeScrollStart();
onScrollBeginDragProp?.(args);
});
const onScrollEndDrag = (0, _reactUsefulHooks.useStableCallback)(args => {
onScrollEndDragProp?.(args);
onScrollEnd();
});
const onMomentumScrollBegin = (0, _reactUsefulHooks.useStableCallback)(args => {
maybeCallOnNativeScrollStart();
onScrollEnd.clear();
onMomentumScrollBeginProp?.(args);
});
const onMomentumScrollEnd = (0, _reactUsefulHooks.useStableCallback)(args => {
onMomentumScrollEndProp?.(args);
onScrollEnd();
});
(0, _react.useEffect)(() => {
const sub = scrollOffset.addListener(() => {
if (!isOnScrollStartCalledRef.current) {
maybeCallOnImplicitScrollStart();
onScrollEnd();
return;
}
if (isImplicitScrollRef.current) {
onScrollEnd();
} else {
onScrollEnd.clear();
}
});
return () => {
scrollOffset.removeListener(sub);
};
}, [maybeCallOnImplicitScrollStart, onScrollEnd, scrollOffset]);
return /*#__PURE__*/(0, _jsxRuntime.jsx)(Component, {
...rest,
ref: forwardedRef,
onScrollBeginDrag: onScrollBeginDrag,
onScrollEndDrag: onScrollEndDrag,
onMomentumScrollBegin: onMomentumScrollBegin,
onMomentumScrollEnd: onMomentumScrollEnd
});
};
Wrapper.displayName = `withScrollStartEndEvent(${Component.displayName || 'Component'})`;
return /*#__PURE__*/(0, _react.memo)(/*#__PURE__*/(0, _react.forwardRef)(Wrapper));
};
var _default = exports.default = withScrollStartEndEvent;
//# sourceMappingURL=withScrollStartEndEvent.js.map