wix-style-react
Version:
258 lines (218 loc) • 8.84 kB
JavaScript
import _extends from "@babel/runtime/helpers/extends";
import _assertThisInitialized from "@babel/runtime/helpers/assertThisInitialized";
import _inherits from "@babel/runtime/helpers/inherits";
import _possibleConstructorReturn from "@babel/runtime/helpers/possibleConstructorReturn";
import _getPrototypeOf from "@babel/runtime/helpers/getPrototypeOf";
import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
import _createClass from "@babel/runtime/helpers/createClass";
import _defineProperty from "@babel/runtime/helpers/defineProperty";
function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }
function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }
import React from 'react';
import hoistNonReactMethods from 'hoist-non-react-methods';
import PropTypes from 'prop-types';
import { wrapDisplayName, getDisplayName, isStatelessComponent } from '../hocUtils';
/**
* Use this method to spread the focusable focus states onto the a component's root element.
* @param {object} props
*/
export function focusableStates(props) {
if (!props) {
throw new Error('FocusableHOC.focusableStates(props): props must be defined');
}
return {
'data-focus': props.focusableIsFocused,
'data-focus-visible': props.focusableIsFocusVisible
};
}
/**
* NOTE: Technically we should add the focusable proptypes to every component which uses this HOC.
* Currently it is not used since Auto-Docs can not process spreading a plain object into propTypes.
* In any way, these props should not be in the documentation, since we expose only the withFocusable(Component),
* and the withFocusable(...) does not accept these props.
* So... for now we can omit these from the wrapped components, and silence eslint if needed.
*/
export var FocusablePropTypes = {
focusableOnFocus: PropTypes.func,
focusableOnBlur: PropTypes.func,
focusableIsFocused: PropTypes.bool,
focusableIsFocusVisible: PropTypes.bool
};
/**
* Singleton for managing current input method (keyboard or mouse).
*/
var inputMethod = new ( /*#__PURE__*/function () {
// Default is keyboard in case an element is focused programmatically.
function _class2() {
var _this = this;
_classCallCheck(this, _class2);
_defineProperty(this, "method", 'keyboard');
_defineProperty(this, "subscribers", new Map());
if (typeof window !== 'undefined') {
window.addEventListener('mousedown', function () {
return _this.setMethod('mouse');
});
window.addEventListener('keydown', function () {
return _this.setMethod('keyboard');
}); // We need to listen on keyUp, in case a TAB is made from the browser's address-bar,
// so the keyDown is not fired, only the keyUp.
window.addEventListener('keyup', function () {
return _this.setMethod('keyboard');
});
}
}
/**
* Subscribe to inputMethod change events
* @param {*} target used as a key to the subscribers map
* @param {*} callback optional to be called when the input method changes
*/
_createClass(_class2, [{
key: "subscribe",
value: function subscribe(target, callback) {
this.subscribers.set(target, callback);
}
/**
* Unsubscribe to inputMethod change events
* @param {*} target used as a key to the subscribers map
*/
}, {
key: "unsubscribe",
value: function unsubscribe(target) {
this.subscribers["delete"](target);
}
/**
* Is the current input method `keyboard`. if `false` is means it is `mouse`
*/
}, {
key: "isKeyboard",
value: function isKeyboard() {
return this.method === 'keyboard';
}
}, {
key: "setMethod",
value: function setMethod(method) {
if (method !== this.method) {
this.method = method;
this.subscribers.forEach(function (f) {
return f();
});
}
}
}]);
return _class2;
}())();
/*
* TODO: Consider adding 'disabled' state to this HOC, since:
* - When component is focused and then it becomes disabled, then the focus needs to be blurred.
*
* TODO: Consider using [Recompose](https://github.com/acdlite/recompose/tree/master/src/packages/recompose) to do:
* - the static hoisting
* - set displayName
*/
export var withFocusable = function withFocusable(Component) {
if (isStatelessComponent(Component)) {
throw new Error("FocusableHOC does not support stateless components. ".concat(getDisplayName(Component), " is stateless."));
}
var FocusableHOC = /*#__PURE__*/function (_React$PureComponent) {
_inherits(FocusableHOC, _React$PureComponent);
var _super = _createSuper(FocusableHOC);
function FocusableHOC() {
var _this2;
_classCallCheck(this, FocusableHOC);
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
_this2 = _super.call.apply(_super, [this].concat(args));
_defineProperty(_assertThisInitialized(_this2), "wrappedComponentRef", null);
_defineProperty(_assertThisInitialized(_this2), "focusedByMouse", false);
_defineProperty(_assertThisInitialized(_this2), "state", {
focus: false,
focusVisible: false
});
_defineProperty(_assertThisInitialized(_this2), "onFocus", function () {
_this2.setState({
focus: true,
focusVisible: inputMethod.isKeyboard()
});
inputMethod.subscribe(_assertThisInitialized(_this2), function () {
if (inputMethod.isKeyboard()) {
_this2.setState({
focusVisible: true
});
}
});
});
_defineProperty(_assertThisInitialized(_this2), "onBlur", function () {
inputMethod.unsubscribe(_assertThisInitialized(_this2));
_this2.setState({
focus: false,
focusVisible: false
});
});
return _this2;
}
_createClass(FocusableHOC, [{
key: "componentWillUnmount",
value: function componentWillUnmount() {
inputMethod.unsubscribe(this);
}
}, {
key: "componentDidUpdate",
value: function componentDidUpdate(prevProps) {
/*
in case when button was focused and then become disabled,
we need to trigger blur logic and remove all listeners, as disabled button
do not trigger onFocus and onBlur events
*/
var isFocused = this.state.focus || this.state.focusVisible;
var isBecomeDisabled = !prevProps.disabled && this.props.disabled;
if (isFocused && isBecomeDisabled) {
this.onBlur();
}
}
}, {
key: "render",
value: function render() {
var _this3 = this;
return /*#__PURE__*/React.createElement(Component, _extends({
ref: function ref(_ref) {
return _this3.wrappedComponentRef = _ref;
}
}, this.props, {
focusableOnFocus: this.onFocus,
focusableOnBlur: this.onBlur,
focusableIsFocused: this.state.focus || null,
focusableIsFocusVisible: this.state.focusVisible || null
}));
}
}]);
return FocusableHOC;
}(React.PureComponent);
_defineProperty(FocusableHOC, "displayName", wrapDisplayName(Component, 'WithFocusable'));
_defineProperty(FocusableHOC, "defaultProps", Component.defaultProps);
assignPropTypesHack(FocusableHOC, Component.propTypes);
return hoistNonReactMethods(FocusableHOC, Component, {
delegateTo: function delegateTo(c) {
return c.wrappedComponentRef;
},
hoistStatics: true
});
};
/**
* Assigned the given propTypes to the given class.
*
* This is a hack because since Yoshi3, with babel-preset-yoshi,
* the babel-plugin-transform-react-remove-prop-types is enabled and removes propTypes.
*
* So if we simply do FocusableHOC.propTypes = Component.propTypes, it is being stripped away.
*
* This later becomes a problem if another component defines:
* <code>
* Comp.propTypes = {
* prop1: SomeFocusableComp.propTypes.prop1
* }
* </code>
*/
function assignPropTypesHack(targetClass, propTypes) {
targetClass.propTypes = propTypes;
}