grommet
Version:
The most advanced UX framework for enterprise applications.
625 lines (544 loc) • 22.5 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
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 _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
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 _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _propTypes = require('prop-types');
var _propTypes2 = _interopRequireDefault(_propTypes);
var _reactDom = require('react-dom');
var _classnames4 = require('classnames');
var _classnames5 = _interopRequireDefault(_classnames4);
var _KeyboardAccelerators = require('../utils/KeyboardAccelerators');
var _KeyboardAccelerators2 = _interopRequireDefault(_KeyboardAccelerators);
var _Drop = require('../utils/Drop');
var _Drop2 = _interopRequireDefault(_Drop);
var _Props = require('../utils/Props');
var _Props2 = _interopRequireDefault(_Props);
var _Responsive = require('../utils/Responsive');
var _Responsive2 = _interopRequireDefault(_Responsive);
var _Button = require('./Button');
var _Button2 = _interopRequireDefault(_Button);
var _Search = require('./icons/base/Search');
var _Search2 = _interopRequireDefault(_Search);
var _CSSClassnames = require('../utils/CSSClassnames');
var _CSSClassnames2 = _interopRequireDefault(_CSSClassnames);
var _Intl = require('../utils/Intl');
var _Intl2 = _interopRequireDefault(_Intl);
var _Announcer = require('../utils/Announcer');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _defineProperty(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; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(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; }
function _inherits(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; } // (C) Copyright 2014-2016 Hewlett Packard Enterprise Development LP
var CLASS_ROOT = _CSSClassnames2.default.SEARCH;
var INPUT = _CSSClassnames2.default.INPUT;
var BACKGROUND_COLOR_INDEX = _CSSClassnames2.default.BACKGROUND_COLOR_INDEX;
var Search = function (_Component) {
_inherits(Search, _Component);
function Search(props, context) {
_classCallCheck(this, Search);
var _this = _possibleConstructorReturn(this, (Search.__proto__ || Object.getPrototypeOf(Search)).call(this, props, context));
_this._onAddDrop = _this._onAddDrop.bind(_this);
_this._onRemoveDrop = _this._onRemoveDrop.bind(_this);
_this._onFocusInput = _this._onFocusInput.bind(_this);
_this._onChangeInput = _this._onChangeInput.bind(_this);
_this._onClickBody = _this._onClickBody.bind(_this);
_this._onNextSuggestion = _this._onNextSuggestion.bind(_this);
_this._onPreviousSuggestion = _this._onPreviousSuggestion.bind(_this);
_this._announceSuggestion = _this._announceSuggestion.bind(_this);
_this._onEnter = _this._onEnter.bind(_this);
_this._onClickSuggestion = _this._onClickSuggestion.bind(_this);
_this._onMouseUp = _this._onMouseUp.bind(_this);
_this._onInputKeyDown = _this._onInputKeyDown.bind(_this);
_this._onSink = _this._onSink.bind(_this);
_this._onResponsive = _this._onResponsive.bind(_this);
_this._stopPropagation = _this._stopPropagation.bind(_this);
_this.state = {
announceChange: false,
activeSuggestionIndex: -1,
align: 'left',
dropActive: false,
inline: props.inline,
small: false
};
return _this;
}
_createClass(Search, [{
key: 'componentDidMount',
value: function componentDidMount() {
var _props = this.props,
initialFocus = _props.initialFocus,
inline = _props.inline,
responsive = _props.responsive;
if (inline && responsive) {
this._responsive = _Responsive2.default.start(this._onResponsive);
}
if (initialFocus) {
(0, _reactDom.findDOMNode)(this._inputRef).focus();
}
}
}, {
key: 'componentWillReceiveProps',
value: function componentWillReceiveProps(nextProps) {
var _state = this.state,
dropActive = _state.dropActive,
inline = _state.inline,
small = _state.small;
if (nextProps.suggestions && nextProps.suggestions.length > 0 && !dropActive && this._inputRef === document.activeElement) {
this.setState({ dropActive: true });
} else if ((!nextProps.suggestions || nextProps.suggestions.length === 0) && inline) {
this.setState({ dropActive: false });
}
if (!small && nextProps.inline !== this.props.inline) {
this.setState({ inline: nextProps.inline });
}
}
}, {
key: 'componentDidUpdate',
value: function componentDidUpdate(prevProps, prevState) {
var _props2 = this.props,
dropAlign = _props2.dropAlign,
suggestions = _props2.suggestions;
var _state2 = this.state,
announceChange = _state2.announceChange,
dropActive = _state2.dropActive,
inline = _state2.inline;
var intl = this.context.intl;
// Set up keyboard listeners appropriate to the current state.
var activeKeyboardHandlers = {
esc: this._onRemoveDrop,
tab: this._onRemoveDrop,
up: this._onPreviousSuggestion,
down: this._onNextSuggestion,
enter: this._onEnter,
left: this._stopPropagation,
right: this._stopPropagation
};
if (!dropActive && prevState.dropActive) {
document.removeEventListener('click', this._onClickBody);
_KeyboardAccelerators2.default.stopListeningToKeyboard(this, activeKeyboardHandlers);
if (this._drop) {
this._drop.remove();
this._drop = undefined;
}
}
if (dropActive && !prevState.dropActive) {
document.addEventListener('click', this._onClickBody);
_KeyboardAccelerators2.default.startListeningToKeyboard(this, activeKeyboardHandlers);
var baseElement = void 0;
if (this._controlRef) {
baseElement = (0, _reactDom.findDOMNode)(this._controlRef);
} else {
baseElement = this._inputRef;
}
var align = dropAlign || {
top: inline ? 'bottom' : 'top',
left: 'left'
};
this._drop = new _Drop2.default(baseElement, this._renderDropContent(), {
align: align,
focusControl: !inline,
responsive: false // so suggestion changes don't re-align
});
if (this._inputRef) {
this._inputRef.focus();
}
} else if (this._drop) {
this._drop.render(this._renderDropContent());
}
if (announceChange && suggestions) {
var matchResultsMessage = _Intl2.default.getMessage(intl, 'Match Results', {
count: suggestions.length
});
var navigationHelpMessage = '';
if (suggestions.length) {
navigationHelpMessage = '(' + _Intl2.default.getMessage(intl, 'Navigation Help') + ')';
}
(0, _Announcer.announce)(matchResultsMessage + ' ' + navigationHelpMessage);
this.setState({ announceChange: false });
}
}
}, {
key: 'componentWillUnmount',
value: function componentWillUnmount() {
document.removeEventListener('click', this._onClickBody);
_KeyboardAccelerators2.default.stopListeningToKeyboard(this);
if (this._responsive) {
this._responsive.stop();
}
if (this._drop) {
this._drop.remove();
}
}
}, {
key: 'focus',
value: function focus() {
var input = this._inputRef;
if (input) {
(0, _reactDom.findDOMNode)(input).focus();
}
}
}, {
key: '_stopPropagation',
value: function _stopPropagation() {
if (document.activeElement === this._inputRef) {
return true;
}
}
}, {
key: '_onInputKeyDown',
value: function _onInputKeyDown(event) {
var _props3 = this.props,
inline = _props3.inline,
onSelect = _props3.onSelect,
suggestions = _props3.suggestions,
onKeyDown = _props3.onKeyDown;
var enter = 13;
var dropActive = this.state.dropActive;
if (suggestions) {
var up = 38;
var down = 40;
if (event.keyCode === up || event.keyCode === down) {
// stop the input to move the cursor when suggestions are present
event.preventDefault();
if (event.keyCode === down && !dropActive && inline) {
this._onAddDrop();
}
}
}
if (!dropActive && onSelect && event.keyCode === enter) {
onSelect({
target: this._inputRef || this._controlRef
}, false);
}
if (onKeyDown) {
onKeyDown(event);
}
}
}, {
key: '_onClickBody',
value: function _onClickBody(event) {
// don't close drop when clicking on input
if (event.target !== this._inputRef) {
this._onRemoveDrop();
}
}
}, {
key: '_onAddDrop',
value: function _onAddDrop() {
this.setState({ dropActive: true, activeSuggestionIndex: -1 });
}
}, {
key: '_onRemoveDrop',
value: function _onRemoveDrop() {
this.setState({ dropActive: false });
}
}, {
key: '_onFocusInput',
value: function _onFocusInput(event) {
var _props4 = this.props,
onFocus = _props4.onFocus,
suggestions = _props4.suggestions;
if (onFocus) {
onFocus(event);
}
if (suggestions && suggestions.length > 0) {
this._onAddDrop();
}
}
}, {
key: '_fireDOMChange',
value: function _fireDOMChange() {
var onDOMChange = this.props.onDOMChange;
var event = void 0;
try {
event = new Event('change', {
'bubbles': true,
'cancelable': true
});
} catch (e) {
// IE11 workaround.
event = document.createEvent('Event');
event.initEvent('change', true, true);
}
var target = this._inputRef;
target.dispatchEvent(event);
onDOMChange(event);
}
}, {
key: '_onChangeInput',
value: function _onChangeInput(event) {
var onDOMChange = this.props.onDOMChange;
this.setState({ activeSuggestionIndex: -1, announceChange: true });
if (onDOMChange) {
this._fireDOMChange();
}
}
}, {
key: '_announceSuggestion',
value: function _announceSuggestion(index) {
var intl = this.context.intl;
var labelMessage = this._renderLabel(this.props.suggestions[index]);
var enterSelectMessage = _Intl2.default.getMessage(intl, 'Enter Select');
(0, _Announcer.announce)(labelMessage + ' ' + enterSelectMessage);
}
}, {
key: '_onNextSuggestion',
value: function _onNextSuggestion() {
var suggestions = this.props.suggestions;
if (suggestions) {
var index = this.state.activeSuggestionIndex;
index = Math.min(index + 1, suggestions.length - 1);
this.setState({ activeSuggestionIndex: index }, this._announceSuggestion.bind(this, index));
}
}
}, {
key: '_onPreviousSuggestion',
value: function _onPreviousSuggestion() {
var suggestions = this.props.suggestions;
if (suggestions) {
var index = this.state.activeSuggestionIndex;
index = Math.max(index - 1, 0);
this.setState({ activeSuggestionIndex: index }, this._announceSuggestion.bind(this, index));
}
}
}, {
key: '_onEnter',
value: function _onEnter(event) {
var _this2 = this;
var _props5 = this.props,
inline = _props5.inline,
onSelect = _props5.onSelect,
suggestions = _props5.suggestions;
var activeSuggestionIndex = this.state.activeSuggestionIndex;
var intl = this.context.intl;
// for not inline search the enter should NOT submit the form
// in this case double enter is required
if (!inline) {
event.preventDefault(); // prevent submitting forms
}
if (activeSuggestionIndex >= 0) {
var suggestion = suggestions[activeSuggestionIndex];
this.setState({ value: suggestion }, function () {
var suggestionMessage = _this2._renderLabel(suggestion);
var selectedMessage = _Intl2.default.getMessage(intl, 'Selected');
(0, _Announcer.announce)(suggestionMessage + ' ' + selectedMessage);
});
if (onSelect) {
onSelect({
target: this._inputRef || this._controlRef,
suggestion: suggestion
}, true);
}
} else if (onSelect) {
onSelect({
target: this._inputRef || this._controlRef
}, false);
}
this._onRemoveDrop();
}
}, {
key: '_onClickSuggestion',
value: function _onClickSuggestion(suggestion) {
var onSelect = this.props.onSelect;
this._onRemoveDrop();
if (onSelect) {
onSelect({
target: this._inputRef || this._controlRef,
suggestion: suggestion
}, true);
}
}
}, {
key: '_onMouseUp',
value: function _onMouseUp(event) {
var onMouseUp = this.props.onMouseUp;
// This fixes a Safari bug which prevents the input
// text from being selected on focus.
event.preventDefault();
if (onMouseUp) {
onMouseUp(event);
}
}
}, {
key: '_onSink',
value: function _onSink(event) {
event.stopPropagation();
event.nativeEvent.stopImmediatePropagation();
}
}, {
key: '_onResponsive',
value: function _onResponsive(small) {
var inline = this.props.inline;
if (small) {
this.setState({ inline: false, small: small });
} else {
this.setState({ inline: inline, small: small });
}
}
}, {
key: '_renderLabel',
value: function _renderLabel(suggestion) {
if ((typeof suggestion === 'undefined' ? 'undefined' : _typeof(suggestion)) === 'object') {
return suggestion.label || suggestion.value;
} else {
return suggestion;
}
}
}, {
key: '_renderDropContent',
value: function _renderDropContent() {
var _classnames,
_this3 = this;
var _props6 = this.props,
defaultValue = _props6.defaultValue,
dropAlign = _props6.dropAlign,
dropColorIndex = _props6.dropColorIndex,
suggestions = _props6.suggestions,
value = _props6.value;
var _state3 = this.state,
inline = _state3.inline,
activeSuggestionIndex = _state3.activeSuggestionIndex;
var restProps = _Props2.default.omit(this.props, Object.keys(Search.propTypes));
var classes = (0, _classnames5.default)(CLASS_ROOT + '__drop', (_classnames = {}, _defineProperty(_classnames, BACKGROUND_COLOR_INDEX + '-' + dropColorIndex, dropColorIndex), _defineProperty(_classnames, CLASS_ROOT + '__drop--controlled', !inline), _classnames));
var input = void 0;
if (!inline) {
input = _react2.default.createElement('input', _extends({}, restProps, { key: 'input', ref: function ref(_ref) {
return _this3._inputRef = _ref;
},
type: 'search', autoComplete: 'off', value: value,
defaultValue: defaultValue, onChange: this._onChangeInput,
className: INPUT + ' ' + CLASS_ROOT + '__input',
onKeyDown: this._onInputKeyDown }));
}
var suggestionsNode = void 0;
if (suggestions) {
suggestionsNode = suggestions.map(function (suggestion, index) {
var classes = (0, _classnames5.default)(CLASS_ROOT + '__suggestion', _defineProperty({}, CLASS_ROOT + '__suggestion--active', index === activeSuggestionIndex));
return _react2.default.createElement(
'div',
{ key: index, className: classes, tabIndex: '-1', role: 'button',
onClick: _this3._onClickSuggestion.bind(_this3, suggestion),
onFocus: function onFocus() {
return _this3.setState({ activeSuggestionIndex: index });
} },
_this3._renderLabel(suggestion)
);
}, this);
suggestionsNode = _react2.default.createElement(
'div',
{ key: 'suggestions', className: CLASS_ROOT + '__suggestions' },
suggestionsNode
);
}
var contents = [input, suggestionsNode];
if (!inline) {
contents = [_react2.default.createElement(
'div',
{ key: 'contents', className: CLASS_ROOT + '__drop-contents',
onClick: this._onSink },
contents
)];
if (!dropAlign || !dropAlign.top && !dropAlign.bottom) {
var control = _react2.default.createElement(_Button2.default, { key: 'icon', icon: _react2.default.createElement(_Search2.default, null),
className: CLASS_ROOT + '__drop-control',
onClick: this._onRemoveDrop });
if (!dropAlign || dropAlign.left === 'left') {
contents.unshift(control);
} else if (dropAlign.right === 'right') {
contents.push(control);
}
}
}
return _react2.default.createElement(
'div',
{ className: classes },
contents
);
}
}, {
key: 'render',
value: function render() {
var _classnames3,
_this4 = this;
var _props7 = this.props,
className = _props7.className,
defaultValue = _props7.defaultValue,
iconAlign = _props7.iconAlign,
id = _props7.id,
fill = _props7.fill,
pad = _props7.pad,
placeHolder = _props7.placeHolder,
size = _props7.size,
value = _props7.value;
var inline = this.state.inline;
var restProps = _Props2.default.omit(this.props, Object.keys(Search.propTypes));
var classes = (0, _classnames5.default)(CLASS_ROOT, (_classnames3 = {}, _defineProperty(_classnames3, CLASS_ROOT + '--controlled', !inline), _defineProperty(_classnames3, CLASS_ROOT + '--fill', fill), _defineProperty(_classnames3, CLASS_ROOT + '--icon-align-' + iconAlign, iconAlign), _defineProperty(_classnames3, CLASS_ROOT + '--pad-' + pad, pad), _defineProperty(_classnames3, CLASS_ROOT + '--inline', inline), _defineProperty(_classnames3, CLASS_ROOT + '--' + size, size), _classnames3), className);
if (inline) {
return _react2.default.createElement(
'div',
{ className: classes },
_react2.default.createElement('input', _extends({}, restProps, { ref: function ref(_ref2) {
return _this4._inputRef = _ref2;
},
type: 'search', id: id, placeholder: placeHolder,
autoComplete: 'off',
defaultValue: this._renderLabel(defaultValue),
value: this._renderLabel(value),
className: INPUT + ' ' + CLASS_ROOT + '__input',
onFocus: this._onFocusInput,
onChange: this._onChangeInput,
onMouseUp: this._onMouseUp,
onKeyDown: this._onInputKeyDown })),
_react2.default.createElement(_Search2.default, null)
);
} else {
return _react2.default.createElement(_Button2.default, { ref: function ref(_ref3) {
return _this4._controlRef = _ref3;
},
id: id, className: className, icon: _react2.default.createElement(_Search2.default, null),
onClick: this._onAddDrop });
}
}
}]);
return Search;
}(_react.Component);
Search.displayName = 'Search';
exports.default = Search;
Search.contextTypes = {
intl: _propTypes2.default.object
};
Search.defaultProps = {
align: 'left',
iconAlign: 'end',
inline: false,
responsive: true
};
Search.propTypes = {
align: _propTypes2.default.string,
defaultValue: _propTypes2.default.string,
dropAlign: _Drop.dropAlignPropType,
dropColorIndex: _propTypes2.default.string,
fill: _propTypes2.default.bool,
iconAlign: _propTypes2.default.oneOf(['start', 'end']),
id: _propTypes2.default.string,
initialFocus: _propTypes2.default.bool,
inline: _propTypes2.default.bool,
onDOMChange: _propTypes2.default.func,
onSelect: _propTypes2.default.func,
onKeyDown: _propTypes2.default.func,
pad: _propTypes2.default.oneOf(['small', 'medium']),
placeHolder: _propTypes2.default.string,
responsive: _propTypes2.default.bool,
size: _propTypes2.default.oneOf(['small', 'medium', 'large']),
suggestions: _propTypes2.default.arrayOf(_propTypes2.default.oneOfType([_propTypes2.default.shape({
label: _propTypes2.default.node,
value: _propTypes2.default.any
}), _propTypes2.default.string])),
value: _propTypes2.default.string
};
module.exports = exports['default'];