UNPKG

react-choices

Version:

Component to build simple, flexible, and accessible choice components

715 lines (611 loc) 22.6 kB
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