react-choices
Version:
Component to build simple, flexible, and accessible choice components
715 lines (611 loc) • 22.6 kB
JavaScript
import { INIT, RECEIVE_NEXT_PROPS, connectToState, withActionIdentity, withLifecycleStateLogic, withRenderProp } from 'conventional-component';
import React, { Component } from 'react';
import cx from 'classnames-es';
var KEYCODE = {
NUMPAD_2: 98,
NUMPAD_4: 100,
NUMPAD_6: 102,
NUMPAD_8: 104,
DOWN: 40,
LEFT: 37,
RIGHT: 39,
UP: 38,
ESC: 27
};
function compose() {
for (var _len = arguments.length, funcs = Array(_len), _key = 0; _key < _len; _key++) {
funcs[_key] = arguments[_key];
}
if (funcs.length === 0) {
return function (arg) {
return arg;
};
}
if (funcs.length === 1) {
return funcs[0];
}
return funcs.reduce(function (a, b) {
return function () {
return a(b.apply(undefined, arguments));
};
});
}
var whenDifferent = function whenDifferent() {
var connectableProps = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
return function (props, nextProps) {
return connectableProps.some(function (prop) {
return props[prop] !== nextProps[prop];
});
};
};
var classCallCheck = function (instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
};
var createClass = function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
}();
var defineProperty = function (obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
};
var _extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
var inherits = function (subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
enumerable: false,
writable: true,
configurable: true
}
});
if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
};
var possibleConstructorReturn = function (self, call) {
if (!self) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return call && (typeof call === "object" || typeof call === "function") ? call : self;
};
var ChoicesDisplay = function ChoicesDisplay(_ref) {
var label = _ref.label,
states = _ref.states,
disabled = _ref.disabled,
readOnly = _ref.readOnly,
setValue = _ref.setValue,
hoverValue = _ref.hoverValue,
previousValue = _ref.previousValue,
nextValue = _ref.nextValue,
getContainerProps = _ref.getContainerProps,
getContainerLabelProps = _ref.getContainerLabelProps,
getItemProps = _ref.getItemProps,
getItemInputProps = _ref.getItemInputProps;
return React.createElement(
'div',
_extends({}, getContainerProps(), {
className: cx('Choices__container', {
'Choices__container--disabled': disabled,
'Choices__container--readonly': readOnly
})
}),
React.createElement(
'div',
_extends({}, getContainerLabelProps(), { className: 'Choices__label' }),
label
),
React.createElement(
'div',
{ className: 'Choices__items' },
React.createElement(
'button',
{ onClick: previousValue, disabled: disabled, readOnly: readOnly },
'<'
),
states.map(function (state) {
return React.createElement(
'button',
_extends({
key: state.key
}, getItemProps(state), getItemInputProps(state), {
className: cx('Choices__item', state.inputClassName, {
'Choices__item--focused': state.focused,
'Choices__item--hovered': state.hovered,
'Choices__item--selected': state.selected
}),
onMouseOver: hoverValue.bind(null, state.value),
onClick: setValue.bind(null, state.value)
}),
state.label
);
}),
React.createElement(
'button',
{ onClick: nextValue, disabled: disabled, readOnly: readOnly },
'>'
)
)
);
};
var toInputClassName = function toInputClassName(state) {
return [state.blockName, state.name].join('-') + '__input-' + state.value;
};
var defaultGetKeyCodeHandler = function defaultGetKeyCodeHandler(keyCode, parentInstance) {
switch (keyCode) {
case KEYCODE.NUMPAD_2:
case KEYCODE.NUMPAD_4:
case KEYCODE.DOWN:
case KEYCODE.LEFT:
return parentInstance.focusPreviousValue;
case KEYCODE.NUMPAD_8:
case KEYCODE.NUMPAD_6:
case KEYCODE.UP:
case KEYCODE.RIGHT:
return parentInstance.focusNextValue;
case KEYCODE.ESC:
return parentInstance.resetValue;
default:
return function () {
return undefined;
};
}
};
var createStates = function createStates() {
var props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var availableStates = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
return availableStates.map(function (availableState) {
return _extends({}, availableState, {
key: props.blockName + '-' + props.name + '__item-' + availableState.value,
label: typeof availableState.label !== 'undefined' ? availableState.label : availableState.value,
notSettable: availableState.settable === false,
focused: availableState.value === props.focusedValue,
hovered: availableState.value === props.hoveredValue,
selected: availableState.value === (props.selectedValue || props.defaultValue),
inputClassName: toInputClassName(_extends({}, availableState, {
blockName: props.blockName,
name: props.name
}))
});
});
};
var EventDetectionContainer = function (_Component) {
inherits(EventDetectionContainer, _Component);
function EventDetectionContainer() {
var _ref;
var _temp, _this, _ret;
classCallCheck(this, EventDetectionContainer);
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
return _ret = (_temp = (_this = possibleConstructorReturn(this, (_ref = EventDetectionContainer.__proto__ || Object.getPrototypeOf(EventDetectionContainer)).call.apply(_ref, [this].concat(args))), _this), _this.setContainer = function (ref) {
return _this.container = ref;
}, _this.keyboardListener = function (event) {
var isActiveControl = _this.container.contains(document.activeElement);
var handle = function handle() {
return undefined;
};
if (isActiveControl) {
handle = _this.props.getKeyCodeHandler(event.keyCode, _this.props.parentInstance);
}
return handle(event);
}, _this.unfocusWhenOutside = function (event) {
var isActiveControl = _this.container.contains(document.activeElement);
if (!isActiveControl) {
var unfocusValue = _this.props.unfocusValue || _this.props.parentInstance.unfocusValue;
if (unfocusValue) {
return unfocusValue();
}
}
}, _this.unhoverWhenOutside = function (event) {
var unhoverValue = _this.props.unhoverValue || _this.props.parentInstance.unhoverValue;
if (unhoverValue) {
return unhoverValue();
}
}, _temp), possibleConstructorReturn(_this, _ret);
}
createClass(EventDetectionContainer, [{
key: 'componentDidMount',
value: function componentDidMount() {
document.addEventListener('keydown', this.keyboardListener);
document.addEventListener('focusin', this.unfocusWhenOutside);
}
}, {
key: 'componentWillUnmount',
value: function componentWillUnmount() {
document.removeEventListener('keydown', this.keyboardListener);
document.removeEventListener('focusin', this.unfocusWhenOutside);
}
}, {
key: 'render',
value: function render() {
var children = this.props.children;
return React.createElement(
'div',
{
className: 'EventDetection-container',
ref: this.setContainer,
onMouseOut: this.unhoverWhenOutside
},
children
);
}
}]);
return EventDetectionContainer;
}(Component);
function withLogic() {
var Template = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ChoicesDisplay;
var Choices = function (_Component2) {
inherits(Choices, _Component2);
function Choices() {
var _ref2;
var _temp2, _this2, _ret2;
classCallCheck(this, Choices);
for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
args[_key2] = arguments[_key2];
}
return _ret2 = (_temp2 = (_this2 = possibleConstructorReturn(this, (_ref2 = Choices.__proto__ || Object.getPrototypeOf(Choices)).call.apply(_ref2, [this].concat(args))), _this2), _this2.createClassName = function (element) {
var modifier = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : undefined;
return _this2.props.blockName + '__' + element + (modifier ? '--' + modifier : '');
}, _this2.focusInputElement = function (value) {
var el = (document.getElementsByClassName(toInputClassName({
blockName: _this2.props.blockName,
name: _this2.props.name,
value: value
})) || [])[0];
if (el && _this2.props.disabled !== true) {
requestAnimationFrame(function () {
return el.focus();
});
}
}, _this2.getContainerProps = function () {
var _cx;
return {
className: cx(_this2.createClassName('container'), (_cx = {}, defineProperty(_cx, _this2.createClassName('container', 'disabled'), _this2.props.disabled), defineProperty(_cx, _this2.createClassName('container', 'readonly'), _this2.props.readOnly), _cx)),
role: 'radiogroup',
'aria-labelledby': _this2.createClassName('label-' + _this2.props.name),
'aria-activedescendant': _this2.createClassName('item-' + _this2.props.selectedValue),
'aria-disabled': _this2.props.disabled ? 'true' : 'false',
'aria-readonly': _this2.props.readOnly ? 'true' : 'false'
};
}, _this2.getContainerLabelProps = function () {
return {
id: _this2.createClassName('label-' + _this2.props.name),
className: _this2.createClassName('label')
};
}, _this2.getItemProps = function (state) {
return {
key: state.key,
id: _this2.createClassName('item-' + state.value),
className: _this2.createClassName('item'),
role: 'radio',
'aria-checked': state.selected ? 'true' : 'false'
};
}, _this2.getItemInputProps = function (state) {
return {
tabIndex: state.selected && _this2.props.disabled !== true ? 0 : -1,
className: cx(_this2.createClassName('item__input'), state.inputClassName),
disabled: _this2.props.disabled || false,
readOnly: _this2.props.readOnly || false
};
}, _this2.resetValue = function (event) {
event && event.preventDefault();
if (_this2.props.readOnly || _this2.props.disabled) return;
_this2.props.setValue(_this2.props.defaultValue);
}, _this2.setValue = function (value, event) {
event && event.preventDefault();
if (_this2.props.readOnly || _this2.props.disabled) return;
_this2.props.setValue(value);
}, _this2.unfocusValue = _this2.setValue.bind(null, undefined), _this2.hoverValue = function (value, event) {
event && event.preventDefault();
if (_this2.props.disabled) return;
_this2.props.hoverValue(value);
}, _this2.unhoverValue = _this2.hoverValue.bind(null, undefined), _this2.focusPreviousValue = function (event) {
event && event.preventDefault();
if (_this2.props.disabled) return;
_this2.props.previousValue(false);
}, _this2.focusNextValue = function (event) {
event && event.preventDefault();
if (_this2.props.disabled) return;
_this2.props.nextValue(false);
}, _this2.previousValue = function (event) {
event && event.preventDefault();
if (_this2.props.readOnly || _this2.props.disabled) return;
_this2.props.previousValue(true);
}, _this2.nextValue = function (event) {
event && event.preventDefault();
if (_this2.props.readOnly || _this2.props.disabled) return;
_this2.props.nextValue(true);
}, _temp2), possibleConstructorReturn(_this2, _ret2);
}
createClass(Choices, [{
key: 'componentWillReceiveProps',
value: function componentWillReceiveProps(nextProps) {
var hasNewSelectedValue = this.props.selectedValue && nextProps.selectedValue !== this.props.selectedValue;
var hasNewFocusedValue = !!nextProps.focusedValue;
if (hasNewSelectedValue) {
this.focusInputElement(nextProps.selectedValue);
} else if (hasNewFocusedValue) {
this.focusInputElement(nextProps.focusedValue);
}
}
}, {
key: 'render',
value: function render() {
var getKeyCodeHandler = this.props.getKeyCodeHandler || defaultGetKeyCodeHandler;
var templateProps = {
createClassName: this.createClassName,
getContainerProps: this.getContainerProps,
getContainerLabelProps: this.getContainerLabelProps,
getItemProps: this.getItemProps,
getItemInputProps: this.getItemInputProps,
name: this.props.name,
label: this.props.label,
blockName: this.props.blockName,
states: createStates(this.props, this.props.availableStates),
defaultValue: this.props.defaultValue,
focusedValue: this.props.focusedValue,
hoveredValue: this.props.hoveredValue,
selectedValue: this.props.selectedValue,
disabled: this.props.disabled || false,
readOnly: this.props.readOnly || false,
resetValue: this.resetValue,
setValue: this.setValue,
hoverValue: this.hoverValue,
previousValue: this.previousValue,
nextValue: this.nextValue
};
var children = null;
if (typeof this.props.render === 'function' || typeof this.props.children === 'function') {
children = withRenderProp(_extends({}, templateProps, {
render: this.props.render,
children: this.props.children
}));
} else if (Template) {
children = React.createElement(Template, templateProps);
}
return React.createElement(
EventDetectionContainer,
{
parentInstance: this,
getKeyCodeHandler: getKeyCodeHandler,
unfocusValue: this.unfocusValue,
unhoverValue: this.unhoverValue
},
children
);
}
}]);
return Choices;
}(Component);
Choices.defaultProps = {
blockName: COMPONENT_NAME
};
return withLifecycleStateLogic({
shouldDispatchReceiveNextProps: whenDifferent(['name', 'label', 'blockName', 'availableStates', 'defaultValue', 'selectedValue'])
})(Choices);
}
var prefix = 'react-choices';
var SET_VALUE = prefix + '/SET_VALUE';
var HOVER_VALUE = prefix + '/HOVER_VALUE';
var PREVIOUS_VALUE = prefix + '/PREVIOUS_VALUE';
var NEXT_VALUE = prefix + '/NEXT_VALUE';
var setValue = withActionIdentity(function (value) {
return {
type: SET_VALUE,
payload: value
};
});
var hoverValue = withActionIdentity(function (value) {
return {
type: HOVER_VALUE,
payload: value
};
});
var previousValue = withActionIdentity(function () {
var shouldSelect = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
return {
type: PREVIOUS_VALUE,
payload: { shouldSelect: shouldSelect }
};
});
var nextValue = withActionIdentity(function () {
var shouldSelect = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
return {
type: NEXT_VALUE,
payload: { shouldSelect: shouldSelect }
};
});
var actions = Object.freeze({
SET_VALUE: SET_VALUE,
HOVER_VALUE: HOVER_VALUE,
PREVIOUS_VALUE: PREVIOUS_VALUE,
NEXT_VALUE: NEXT_VALUE,
setValue: setValue,
hoverValue: hoverValue,
previousValue: previousValue,
nextValue: nextValue
});
var REDUCER_NAME = 'choices';
var initialState = {
name: undefined,
label: undefined,
blockName: undefined,
availableStates: [],
defaultValue: undefined,
focusedValue: undefined,
hoveredValue: undefined,
selectedValue: undefined
};
var firstValue = function firstValue() {
var availableStates = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
return (availableStates[0] || {}).value;
};
var isSettable = function isSettable(state) {
return state.settable !== false;
};
var getPreviousValue = function getPreviousValue(state) {
var currentValue = state.focusedValue || state.selectedValue;
var currentStateIndex = state.availableStates.findIndex(function (as) {
return as.value === currentValue;
});
if (currentStateIndex > 0) {
var previousSettableIndex = -1;
for (var idx = state.availableStates.length; idx >= 0; idx--) {
if (idx < currentStateIndex && isSettable(state.availableStates[idx])) {
previousSettableIndex = idx;
break;
}
}
if (previousSettableIndex !== -1) {
return state.availableStates[previousSettableIndex].value;
}
}
return undefined;
};
var getNextValue = function getNextValue(state) {
var currentValue = state.focusedValue || state.selectedValue;
var currentStateIndex = state.availableStates.findIndex(function (as) {
return as.value === currentValue;
});
if (currentStateIndex < state.availableStates.length - 1) {
var nextSettableIndex = -1;
for (var idx = 0; idx < state.availableStates.length; idx++) {
if (idx > currentStateIndex && isSettable(state.availableStates[idx])) {
nextSettableIndex = idx;
break;
}
}
if (nextSettableIndex !== -1) {
return state.availableStates[nextSettableIndex].value;
}
}
return undefined;
};
var reducer = function reducer() {
var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : initialState;
var action = arguments[1];
switch (action.type) {
case INIT:
{
var availableStates = action.payload.availableStates;
return _extends({}, state, {
name: action.payload.name,
label: action.payload.label,
blockName: action.payload.blockName,
availableStates: availableStates,
defaultValue: action.payload.defaultValue,
selectedValue: action.payload.defaultValue || firstValue(availableStates)
});
}
case RECEIVE_NEXT_PROPS:
{
return _extends({}, state, {
name: action.payload.name,
label: action.payload.label,
blockName: action.payload.blockName,
availableStates: action.payload.availableStates,
defaultValue: action.payload.defaultValue,
selectedValue: action.payload.selectedValue
});
}
case PREVIOUS_VALUE:
{
var shouldSelect = action.payload.shouldSelect;
var previousValue$$1 = getPreviousValue(state);
if (previousValue$$1) {
if (shouldSelect) {
return _extends({}, state, {
selectedValue: previousValue$$1,
focusedValue: previousValue$$1
});
} else {
return _extends({}, state, { focusedValue: previousValue$$1 });
}
}
return state;
}
case NEXT_VALUE:
{
var _shouldSelect = action.payload.shouldSelect;
var nextValue$$1 = getNextValue(state);
if (nextValue$$1) {
if (_shouldSelect) {
return _extends({}, state, { selectedValue: nextValue$$1, focusedValue: nextValue$$1 });
} else {
return _extends({}, state, { focusedValue: nextValue$$1 });
}
}
return state;
}
case SET_VALUE:
{
var value = action.payload;
var availableState = state.availableStates.find(function (as) {
return as.value === value;
});
if (availableState && isSettable(availableState)) {
return _extends({}, state, { selectedValue: value, focusedValue: value });
} else if (value === undefined) {
return _extends({}, state, { focusedValue: undefined });
}
return state;
}
case HOVER_VALUE:
{
var _value = action.payload;
var _availableState = state.availableStates.find(function (as) {
return as.value === _value;
});
if (_availableState) {
return _extends({}, state, { hoveredValue: action.payload });
} else if (_value === undefined) {
return _extends({}, state, { hoveredValue: undefined });
}
return state;
}
default:
return state;
}
};
var COMPONENT_NAME = 'Choices';
var COMPONENT_KEY = 'name';
var enhance = compose(connectToState(reducer, actions), withLogic);
var Choices$1 = enhance(ChoicesDisplay);
export { actions, reducer, ChoicesDisplay as Template, withLogic, Choices$1 as Choices, REDUCER_NAME, COMPONENT_NAME, COMPONENT_KEY };
export default Choices$1;
//# sourceMappingURL=index.mjs.map