wix-style-react
Version:
271 lines (227 loc) • 10 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.focusableStates = focusableStates;
exports.withFocusable = exports.FocusablePropTypes = void 0;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _assertThisInitialized2 = _interopRequireDefault(require("@babel/runtime/helpers/assertThisInitialized"));
var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits"));
var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn"));
var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf"));
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _react = _interopRequireDefault(require("react"));
var _hoistNonReactMethods = _interopRequireDefault(require("hoist-non-react-methods"));
var _propTypes = _interopRequireDefault(require("prop-types"));
var _hocUtils = require("../hocUtils");
function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = (0, _getPrototypeOf2["default"])(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = (0, _getPrototypeOf2["default"])(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return (0, _possibleConstructorReturn2["default"])(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; } }
/**
* Use this method to spread the focusable focus states onto the a component's root element.
* @param {object} props
*/
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.
*/
var FocusablePropTypes = {
focusableOnFocus: _propTypes["default"].func,
focusableOnBlur: _propTypes["default"].func,
focusableIsFocused: _propTypes["default"].bool,
focusableIsFocusVisible: _propTypes["default"].bool
};
/**
* Singleton for managing current input method (keyboard or mouse).
*/
exports.FocusablePropTypes = FocusablePropTypes;
var inputMethod = new ( /*#__PURE__*/function () {
// Default is keyboard in case an element is focused programmatically.
function _class2() {
var _this = this;
(0, _classCallCheck2["default"])(this, _class2);
(0, _defineProperty2["default"])(this, "method", 'keyboard');
(0, _defineProperty2["default"])(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
*/
(0, _createClass2["default"])(_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
*/
var withFocusable = function withFocusable(Component) {
if ((0, _hocUtils.isStatelessComponent)(Component)) {
throw new Error("FocusableHOC does not support stateless components. ".concat((0, _hocUtils.getDisplayName)(Component), " is stateless."));
}
var FocusableHOC = /*#__PURE__*/function (_React$PureComponent) {
(0, _inherits2["default"])(FocusableHOC, _React$PureComponent);
var _super = _createSuper(FocusableHOC);
function FocusableHOC() {
var _this2;
(0, _classCallCheck2["default"])(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));
(0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this2), "wrappedComponentRef", null);
(0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this2), "focusedByMouse", false);
(0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this2), "state", {
focus: false,
focusVisible: false
});
(0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this2), "onFocus", function () {
_this2.setState({
focus: true,
focusVisible: inputMethod.isKeyboard()
});
inputMethod.subscribe((0, _assertThisInitialized2["default"])(_this2), function () {
if (inputMethod.isKeyboard()) {
_this2.setState({
focusVisible: true
});
}
});
});
(0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this2), "onBlur", function () {
inputMethod.unsubscribe((0, _assertThisInitialized2["default"])(_this2));
_this2.setState({
focus: false,
focusVisible: false
});
});
return _this2;
}
(0, _createClass2["default"])(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["default"].createElement(Component, (0, _extends2["default"])({
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["default"].PureComponent);
(0, _defineProperty2["default"])(FocusableHOC, "displayName", (0, _hocUtils.wrapDisplayName)(Component, 'WithFocusable'));
(0, _defineProperty2["default"])(FocusableHOC, "defaultProps", Component.defaultProps);
assignPropTypesHack(FocusableHOC, Component.propTypes);
return (0, _hoistNonReactMethods["default"])(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>
*/
exports.withFocusable = withFocusable;
function assignPropTypesHack(targetClass, propTypes) {
targetClass.propTypes = propTypes;
}