react-native-focal
Version:
Humble kit to professionally handle the focus/blur experience for controlled components.
165 lines (156 loc) • 6.18 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
var React = require('react');
var reactNative = require('react-native');
var isFunction = require('lodash.isfunction');
var unset = require('lodash.unset');
var get = require('lodash.get');
var set = require('lodash.set');
var reactNativeGestureHandler = require('react-native-gesture-handler');
var uniqueId = require('lodash.uniqueid');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
var isFunction__default = /*#__PURE__*/_interopDefaultLegacy(isFunction);
var unset__default = /*#__PURE__*/_interopDefaultLegacy(unset);
var get__default = /*#__PURE__*/_interopDefaultLegacy(get);
var set__default = /*#__PURE__*/_interopDefaultLegacy(set);
var uniqueId__default = /*#__PURE__*/_interopDefaultLegacy(uniqueId);
/**
* Ref containing the list of all the nodes within all the providers.
*/
const focuses = React.createRef();
/**
* Method responsible for resetting the focal object ref to an empty object.
*/
const resetFocuses = () => {
set__default['default'](focuses, ['current'], {});
};
/**
* Gracefully `blur` the focused node via the `onBlur` method specified in the `Controller` props if it can be `blur`red.
* @param force {boolean} flag to indicate whether to force removing the focused component logically or not.
*/
const blur = (force) => {
const focusedComponent = getFocused();
if (!focusedComponent)
return;
const { node, onBlur } = focusedComponent;
let removeFocus = true;
if (isFunction__default['default'](onBlur))
removeFocus = onBlur(node);
if (force || removeFocus)
unset__default['default'](focuses, ['current', 'focused']);
};
/**
* Method responsible for retrieving the focused component value within the ref.
*/
const getFocused = () => {
const focusedComponentRef = get__default['default'](focuses, ['current', 'focused'], '');
return get__default['default'](focuses, ['current', focusedComponentRef]);
};
/**
* Method responsible for retrieving the focused component id within the ref.
*/
const getFocusedId = () => {
return get__default['default'](focuses, ['current', 'focused'], null);
};
/**
* Method responsible for retrieving the number of actual nodes in the focal object.
*/
const getLength = () => {
return Object.keys(focuses.current || {}).length;
};
/**
* Method responsible for retrieving a certain node within the focal object via index if valid.
*/
const getByIndex = (index) => {
if (index >= getLength())
return null;
return Object.values(focuses.current || {})[index];
};
/**
* Ref containing the list of all the nodes within all the providers.
*/
const handlers = React.createRef();
/**
* Method responsible for subscribing the handler within the handlers ref object.
*/
const subscribeHandler = (id, ref) => {
set__default['default'](handlers, ['current', id], ref);
};
/**
* Method responsible for unsubscribing the handler from the handlers ref object.
*/
const unsubscribeHandler = (id) => {
unset__default['default'](handlers, ['current', id]);
};
/**
* Method responsible for resetting the focal object ref to an empty object.
*/
const resetHandlers = () => {
set__default['default'](handlers, 'current', {});
};
/**
* Method responsible for retrieving the handlers list.
*/
const getHandlers = () => {
return Object.values(handlers.current || {}) || [];
};
const Container = ({ children, onPress, ...props }) => {
const onContainerPress = React.useCallback((_) => {
blur();
if (isFunction__default['default'](onPress))
onPress();
}, [onPress]);
return (React__default['default'].createElement(reactNativeGestureHandler.TapGestureHandler, { waitFor: Object.values(handlers.current || {}) || [], onActivated: onContainerPress },
React__default['default'].createElement(reactNative.Animated.View, { ...props }, children)));
};
const _onBlur = (node) => {
node?.blur?.();
return true;
};
function Controller({ children, isFocusable = true, onBlur = _onBlur, onFocus, ...props }) {
const childRef = React.useRef(null);
const tapRef = React.useRef(null);
const { current: privateId } = React.useRef(uniqueId__default['default']('ctrlr#'));
const componentRefSetter = React.useCallback((node) => {
set__default['default'](childRef, 'current', node);
set__default['default'](focuses, ['current', privateId], {
node,
onBlur
});
}, [onBlur]);
const tapRefSetter = React.useCallback((node) => {
set__default['default'](tapRef, 'current', node);
subscribeHandler(privateId, tapRef);
}, []);
const onPress = React.useCallback((_) => {
childRef?.current?.focus?.();
if (isFocusable)
set__default['default'](focuses, ['current', 'focused'], privateId);
else
blur();
if (isFunction__default['default'](onFocus))
onFocus();
}, [isFocusable, onFocus]);
React.useEffect(() => {
return () => {
unsubscribeHandler(privateId);
};
}, []);
return (React__default['default'].createElement(reactNativeGestureHandler.TapGestureHandler, { ref: tapRefSetter, onActivated: onPress },
React__default['default'].createElement(reactNative.View, { ...props }, React__default['default'].cloneElement(children, { ref: componentRefSetter }))));
}
exports.Container = Container;
exports.Controller = Controller;
exports.blur = blur;
exports.focuses = focuses;
exports.getByIndex = getByIndex;
exports.getFocused = getFocused;
exports.getFocusedId = getFocusedId;
exports.getHandlers = getHandlers;
exports.getLength = getLength;
exports.handlers = handlers;
exports.resetFocuses = resetFocuses;
exports.resetHandlers = resetHandlers;
exports.subscribeHandler = subscribeHandler;
exports.unsubscribeHandler = unsubscribeHandler;