downshift
Advanced tools
Comparing version 1.0.0-beta.10 to 1.0.0-beta.11
@@ -78,3 +78,3 @@ 'use strict'; | ||
_this.setState({ highlightedIndex }, function () { | ||
_this.internalSetState({ highlightedIndex }, function () { | ||
_this.maybeScrollToHighlightedElement(highlightedIndex); | ||
@@ -85,4 +85,4 @@ }); | ||
_this.highlightSelectedItem = function () { | ||
var highlightedIndex = _this.getIndexFromValue(_this.state.selectedValue) || 0; | ||
_this.setState({ highlightedIndex }, function () { | ||
var highlightedIndex = _this.getIndexFromValue(_this.getState().selectedValue) || 0; | ||
_this.internalSetState({ highlightedIndex }, function () { | ||
_this.maybeScrollToHighlightedElement(highlightedIndex, true); | ||
@@ -99,3 +99,3 @@ }); | ||
_this.moveHighlightedIndex = function (amount) { | ||
if (_this.state.isOpen) { | ||
if (_this.getState().isOpen) { | ||
_this.changeHighlighedIndex(amount); | ||
@@ -112,4 +112,6 @@ } else { | ||
} | ||
var highlightedIndex = _this.state.highlightedIndex; | ||
var _this$getState = _this.getState(), | ||
highlightedIndex = _this$getState.highlightedIndex; | ||
var baseIndex = highlightedIndex; | ||
@@ -120,4 +122,6 @@ if (baseIndex === null) { | ||
var newIndex = baseIndex + moveAmount; | ||
if (newIndex < 0 || newIndex > itemsLastIndex) { | ||
newIndex = null; | ||
if (newIndex < 0) { | ||
newIndex = itemsLastIndex; | ||
} else if (newIndex > itemsLastIndex) { | ||
newIndex = 0; | ||
} | ||
@@ -128,3 +132,3 @@ _this.setHighlightedIndex(newIndex); | ||
_this.clearSelection = function () { | ||
_this.setState({ | ||
_this.internalSetState({ | ||
selectedValue: _this.multiple ? [] : '', | ||
@@ -139,9 +143,10 @@ isOpen: false | ||
_this.selectItem = function (itemValue) { | ||
var previousValue = _this.state.selectedValue; | ||
if (!_this.props.multiple) { | ||
_this.reset(); | ||
} | ||
_this.setState(function (state) { | ||
_this.internalSetState(function (_ref3) { | ||
var previousValue = _ref3.selectedValue; | ||
if (_this.props.multiple) { | ||
var values = [].concat(_toConsumableArray(state.selectedValue)); | ||
var values = [].concat(_toConsumableArray(previousValue)); | ||
var pos = values.indexOf(itemValue); | ||
@@ -165,7 +170,2 @@ if (pos > -1) { | ||
} | ||
}, function () { | ||
_this.props.onChange({ | ||
selectedValue: _this.state.selectedValue, | ||
previousValue | ||
}); | ||
}); | ||
@@ -187,3 +187,3 @@ }; | ||
_this.selectHighlightedItem = function () { | ||
return _this.selectItemAtIndex(_this.state.highlightedIndex); | ||
return _this.selectItemAtIndex(_this.getState().highlightedIndex); | ||
}; | ||
@@ -196,8 +196,8 @@ | ||
_this.getRootProps = function () { | ||
var _ref3 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
var _ref4 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
var _ref3$refKey = _ref3.refKey, | ||
refKey = _ref3$refKey === undefined ? 'ref' : _ref3$refKey, | ||
onClick = _ref3.onClick, | ||
rest = _objectWithoutProperties(_ref3, ['refKey', 'onClick']); | ||
var _ref4$refKey = _ref4.refKey, | ||
refKey = _ref4$refKey === undefined ? 'ref' : _ref4$refKey, | ||
onClick = _ref4.onClick, | ||
rest = _objectWithoutProperties(_ref4, ['refKey', 'onClick']); | ||
@@ -242,3 +242,3 @@ // this is used in the render to know whether the user has called getRootProps. | ||
event.preventDefault(); | ||
if (this.state.isOpen) { | ||
if (this.getState().isOpen) { | ||
this.selectHighlightedItem(); | ||
@@ -257,4 +257,9 @@ } | ||
event.preventDefault(); | ||
if (this.state.isOpen) { | ||
if (this.state.highlightedIndex === null) { | ||
var _getState = this.getState(), | ||
isOpen = _getState.isOpen, | ||
highlightedIndex = _getState.highlightedIndex; | ||
if (isOpen) { | ||
if (highlightedIndex === null) { | ||
this.closeMenu(); | ||
@@ -271,9 +276,10 @@ } else { | ||
_this.getButtonProps = function () { | ||
var _ref4 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
var _ref5 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
var onClick = _ref4.onClick, | ||
onKeyDown = _ref4.onKeyDown, | ||
rest = _objectWithoutProperties(_ref4, ['onClick', 'onKeyDown']); | ||
var onClick = _ref5.onClick, | ||
onKeyDown = _ref5.onKeyDown, | ||
rest = _objectWithoutProperties(_ref5, ['onClick', 'onKeyDown']); | ||
var isOpen = _this.state.isOpen; | ||
var _this$getState2 = _this.getState(), | ||
isOpen = _this$getState2.isOpen; | ||
@@ -306,12 +312,12 @@ return _extends({ | ||
_this.getInputProps = function () { | ||
var _ref5 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
var _ref6 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
var onChange = _ref5.onChange, | ||
onKeyDown = _ref5.onKeyDown, | ||
onBlur = _ref5.onBlur, | ||
rest = _objectWithoutProperties(_ref5, ['onChange', 'onKeyDown', 'onBlur']); | ||
var onChange = _ref6.onChange, | ||
onKeyDown = _ref6.onKeyDown, | ||
onBlur = _ref6.onBlur, | ||
rest = _objectWithoutProperties(_ref6, ['onChange', 'onKeyDown', 'onBlur']); | ||
var _this$state = _this.state, | ||
inputValue = _this$state.inputValue, | ||
isOpen = _this$state.isOpen; | ||
var _this$getState3 = _this.getState(), | ||
inputValue = _this$getState3.inputValue, | ||
isOpen = _this$getState3.isOpen; | ||
@@ -340,3 +346,3 @@ return _extends({ | ||
_this.input_handleChange = function (event) { | ||
_this.setState({ inputValue: event.target.value }); | ||
_this.internalSetState({ inputValue: event.target.value }); | ||
}; | ||
@@ -351,8 +357,8 @@ | ||
_this.getItemProps = function () { | ||
var _ref6 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
var _ref7 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
var onMouseEnter = _ref6.onMouseEnter, | ||
value = _ref6.value, | ||
index = _ref6.index, | ||
rest = _objectWithoutProperties(_ref6, ['onMouseEnter', 'value', 'index']); | ||
var onMouseEnter = _ref7.onMouseEnter, | ||
value = _ref7.value, | ||
index = _ref7.index, | ||
rest = _objectWithoutProperties(_ref7, ['onMouseEnter', 'value', 'index']); | ||
@@ -368,6 +374,7 @@ _this.items.push({ index, value }); | ||
_this.reset = function () { | ||
_this.setState(function (_ref7) { | ||
var selectedValue = _ref7.selectedValue; | ||
_this.reset = function (type) { | ||
_this.internalSetState(function (_ref8) { | ||
var selectedValue = _ref8.selectedValue; | ||
return { | ||
type, | ||
isOpen: false, | ||
@@ -381,4 +388,4 @@ highlightedIndex: null, | ||
_this.toggleMenu = function (newState, cb) { | ||
_this.setState(function (_ref8) { | ||
var isOpen = _ref8.isOpen; | ||
_this.internalSetState(function (_ref9) { | ||
var isOpen = _ref9.isOpen; | ||
@@ -391,4 +398,8 @@ var nextIsOpen = !isOpen; | ||
}, function () { | ||
if (_this.state.isOpen) { | ||
if (_this.state.selectedValue.length > 0) { | ||
var _this$getState4 = _this.getState(), | ||
isOpen = _this$getState4.isOpen, | ||
selectedValue = _this$getState4.selectedValue; | ||
if (isOpen) { | ||
if (selectedValue.length > 0) { | ||
_this.highlightSelectedItem(); | ||
@@ -415,3 +426,3 @@ } else { | ||
} | ||
var item = _this.getItemFromIndex(_this.state.highlightedIndex) || {}; | ||
var item = _this.getItemFromIndex(_this.getState().highlightedIndex) || {}; | ||
var status = _this.props.getA11yStatusMessage({ | ||
@@ -435,3 +446,36 @@ resultCount: _this.items.length, | ||
// this is an experimental feature | ||
// so we're not going to document this yet | ||
_createClass(Autocomplete, [{ | ||
key: 'getState', | ||
/** | ||
* Gets the state based on internal state or props | ||
* If a state value is passed via props, then that | ||
* is the value given, otherwise it's retrieved from | ||
* stateToMerge | ||
* | ||
* This will perform a shallow merge of the given state object | ||
* with the state coming from props | ||
* (for the controlled component scenario) | ||
* This is used in state updater functions so they're referencing | ||
* the right state regardless of where it comes from. | ||
* | ||
* @param {Object} stateToMerge defaults to this.state | ||
* @return {Object} the state | ||
*/ | ||
value: function getState() { | ||
var _this2 = this; | ||
var stateToMerge = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.state; | ||
return Object.keys(stateToMerge).reduce(function (state, key) { | ||
state[key] = _this2.props[key] === undefined ? _this2.state[key] : _this2.props[key]; | ||
return state; | ||
}, {}); | ||
} | ||
}, { | ||
key: 'maybeScrollToHighlightedElement', | ||
@@ -447,9 +491,70 @@ value: function maybeScrollToHighlightedElement(highlightedIndex, alignToTop) { | ||
}, { | ||
key: 'internalSetState', | ||
// any piece of our state can live in two places: | ||
// 1. Uncontrolled: it's internal (this.state) | ||
// We will call this.setState to update that state | ||
// 2. Controlled: it's external (this.props) | ||
// We will call this.props.onChange to update that state | ||
// | ||
// In addition, we'll always call this.props.onChange if the | ||
// selectedValue is changed because that's important whether | ||
// that property is controlled or not. | ||
value: function internalSetState(stateToSet, cb) { | ||
var _this3 = this; | ||
var onChangeArg = {}; | ||
var onStateChangeArg = void 0; | ||
return this.setState(function (state) { | ||
state = _this3.getState(state); | ||
onStateChangeArg = typeof stateToSet === 'function' ? stateToSet(state) : stateToSet; | ||
var nextState = {}; | ||
// we need to call on change if the outside world is controlling any of our state | ||
// and we're trying to update that state. OR if the selection has changed and we're | ||
// trying to update the selection | ||
if (onStateChangeArg.hasOwnProperty('selectedValue')) { | ||
onChangeArg.selectedValue = onStateChangeArg.selectedValue; | ||
onChangeArg.previousValue = state.selectedValue; | ||
} | ||
Object.keys(onStateChangeArg).forEach(function (key) { | ||
// the type is useful for the onStateChangeArg | ||
// but we don't actually want to set it in internal state. | ||
// this is an undocumented feature for now... Not all internalSetState | ||
// calls support it and I'm not certain we want them to yet. | ||
// But it enables users controlling the isOpen state to know when | ||
// the isOpen state changes due to mouseup events which is quite handy. | ||
if (key === 'type') { | ||
return; | ||
} | ||
// if it's coming from props, then we don't want to set it internally | ||
if (!_this3.props.hasOwnProperty(key)) { | ||
nextState[key] = onStateChangeArg[key]; | ||
} | ||
}); | ||
return nextState; | ||
}, function () { | ||
// call the provided callback if it's a callback | ||
(0, _utils.cbToCb)(cb)(); | ||
// if the selectedValue changed | ||
// then let's call onChange! | ||
if (Object.keys(onChangeArg).length) { | ||
_this3.props.onChange(onChangeArg); | ||
} | ||
// We call this function whether we're controlled or not | ||
// It's mostly useful if we're controlled, but it can | ||
// definitely be useful for folks to know when something | ||
// happens internally. | ||
_this3.props.onStateChange(onStateChangeArg); | ||
}); | ||
} | ||
}, { | ||
key: 'getControllerStateAndHelpers', | ||
value: function getControllerStateAndHelpers() { | ||
var _state = this.state, | ||
highlightedIndex = _state.highlightedIndex, | ||
inputValue = _state.inputValue, | ||
isOpen = _state.isOpen, | ||
selectedValue = _state.selectedValue; | ||
var _getState2 = this.getState(), | ||
highlightedIndex = _getState2.highlightedIndex, | ||
inputValue = _getState2.inputValue, | ||
selectedValue = _getState2.selectedValue, | ||
isOpen = _getState2.isOpen; | ||
var getRootProps = this.getRootProps, | ||
@@ -514,3 +619,3 @@ getButtonProps = this.getButtonProps, | ||
value: function componentDidMount() { | ||
var _this2 = this; | ||
var _this4 = this; | ||
@@ -526,10 +631,10 @@ // the _isMounted property is because we have `updateStatus` in a `debounce` | ||
var onMouseDown = function onMouseDown() { | ||
_this2.isMouseDown = true; | ||
_this4.isMouseDown = true; | ||
}; | ||
var onMouseUp = function onMouseUp(event) { | ||
_this2.isMouseDown = false; | ||
_this4.isMouseDown = false; | ||
var target = event.target; | ||
if (!_this2._rootNode.contains(target)) { | ||
_this2.reset(); | ||
if (!_this4._rootNode.contains(target)) { | ||
_this4.reset(Autocomplete.stateChangeTypes.mouseUp); | ||
} | ||
@@ -541,3 +646,3 @@ }; | ||
this.cleanup = function () { | ||
_this2._isMounted = false; | ||
_this4._isMounted = false; | ||
document.body.removeEventListener('mousedown', onMouseDown); | ||
@@ -550,2 +655,4 @@ document.body.removeEventListener('mouseup', onMouseUp); | ||
value: function componentDidUpdate(prevProps, prevState) { | ||
// TODO: what do we need to do for this when the | ||
// autocomplete is a controlled component? | ||
if (prevState.highlightedIndex !== this.state.highlightedIndex || this.state.selectedValue !== prevState.selectedValue) { | ||
@@ -563,4 +670,6 @@ this.updateStatus(); | ||
value: function render() { | ||
var children = this.props.children; | ||
// because the items are rerendered every time we call the children | ||
// we clear this out each render and | ||
this.items = []; | ||
@@ -572,20 +681,17 @@ // we reset this so we know whether the user calls getRootProps during | ||
this.getRootProps.called = false; | ||
var _props = this.props, | ||
children = _props.children, | ||
defaultValue = _props.defaultValue, | ||
getValue = _props.getValue, | ||
getA11yStatusMessage = _props.getA11yStatusMessage, | ||
defaultHighlightedIndex = _props.defaultHighlightedIndex, | ||
multiple = _props.multiple, | ||
onClick = _props.onClick, | ||
onChange = _props.onChange, | ||
rest = _objectWithoutProperties(_props, ['children', 'defaultValue', 'getValue', 'getA11yStatusMessage', 'defaultHighlightedIndex', 'multiple', 'onClick', 'onChange']); | ||
var element = children(this.getControllerStateAndHelpers()); | ||
if (this.getRootProps.called) { | ||
// doing React.Children.only for Preact support ⚛️ | ||
var element = _react2.default.Children.only(children(this.getControllerStateAndHelpers())); | ||
if (!element) { | ||
// returned null or something... | ||
return element; | ||
} else if (this.getRootProps.called) { | ||
// we assumed they applied the root props correctly | ||
return element; | ||
} else if (typeof element.type === 'string') { | ||
return _react2.default.cloneElement(element, this.getRootProps(_extends({}, rest, element.props))); | ||
// they didn't apply the root props, but we can clone | ||
// this and apply the props ourselves | ||
return _react2.default.cloneElement(element, this.getRootProps(element.props)); | ||
} else { | ||
// they didn't apply the root props, but they need to | ||
// otherwise we can't query around the autocomplete | ||
throw new Error('downshift: If you return a non-DOM element, you must use apply the getRootProps function'); | ||
@@ -607,3 +713,12 @@ } | ||
onChange: _propTypes2.default.func.isRequired, | ||
onClick: _propTypes2.default.func | ||
onStateChange: _propTypes2.default.func, | ||
onClick: _propTypes2.default.func, | ||
// things we keep in state for uncontrolled components | ||
// but can accept as props for controlled components | ||
/* eslint-disable react/no-unused-prop-types */ | ||
selectedValue: _propTypes2.default.any, | ||
isOpen: _propTypes2.default.bool, | ||
inputValue: _propTypes2.default.string, | ||
highlightedIndex: _propTypes2.default.number | ||
/* eslint-enable */ | ||
}; | ||
@@ -613,6 +728,6 @@ Autocomplete.defaultProps = { | ||
defaultValue: null, | ||
getA11yStatusMessage(_ref9) { | ||
var resultCount = _ref9.resultCount, | ||
highlightedItem = _ref9.highlightedItem, | ||
getValue = _ref9.getValue; | ||
getA11yStatusMessage(_ref10) { | ||
var resultCount = _ref10.resultCount, | ||
highlightedItem = _ref10.highlightedItem, | ||
getValue = _ref10.getValue; | ||
@@ -628,4 +743,7 @@ if (!resultCount) { | ||
return String(i); | ||
} | ||
}, | ||
onStateChange: function onStateChange() {} }; | ||
Autocomplete.stateChangeTypes = { | ||
mouseUp: '__autocomplete_mouseup__' | ||
}; | ||
exports.default = Autocomplete; |
{ | ||
"name": "downshift", | ||
"version": "1.0.0-beta.10", | ||
"version": "1.0.0-beta.11", | ||
"description": "A set of primitives to build simple, flexible, WAI-ARIA compliant React autocomplete components", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
@@ -125,4 +125,4 @@ <h1 align="center"> | ||
This is the only component. It doesn't render anything itself, it just calls the child function and renders that. Wrap | ||
everything in this. | ||
This is the only component. It doesn't render anything itself, it just calls | ||
the child function and renders that. Wrap everything in this. | ||
@@ -168,2 +168,38 @@ #### getValue | ||
#### onStateChange | ||
> `function({highlightedIndex, inputValue, isOpen, selectedValue})` | not required, no useful default | ||
This function is called anytime the internal state changes. This can be useful | ||
if you're using downshift as a "controlled" component, where you manage some or | ||
all of the state (e.g. isOpen, selectedValue, highlightedIndex, etc) and then | ||
pass it as props, rather than letting downshift control all its state itself. | ||
#### highlightedIndex | ||
> `number` | **state prop** (read more below) | ||
The index that should be highlighted | ||
#### inputValue | ||
> `string` | **state prop** (read more below) | ||
The value the input should have | ||
#### isOpen | ||
> `boolean` | **state prop** (read more below) | ||
Whether the menu should be considered open or closed. Some aspects of the | ||
autocomplete component respond differently based on this value (for example, if | ||
`isOpen` is true when the user hits "Enter" on the input field, then the | ||
item at the `highlightedIndex` item is selected). | ||
#### `selectedValue` | ||
> `any`/`Array(any)` | **state prop** (read more below) | ||
The currently selected value. | ||
#### children | ||
@@ -255,2 +291,13 @@ | ||
### State Props | ||
You can pass some props which normally the `downshift` will manage for you. If | ||
you pass these props, then they become "controlled" props. In this situation, | ||
`downshift` will no longer update them directly and will instead call your | ||
`onStateChange` handler and expect you to update them. This can be useful if | ||
you want to control the component externally (like selecting an item | ||
from another part of the UI). | ||
State Props are labeled above with **state prop** | ||
## Examples | ||
@@ -257,0 +304,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
56221
816
382