office-ui-fabric-react
Version:
Reusable React components for building experiences for Office 365.
591 lines • 32.1 kB
JavaScript
define(["require", "exports", "tslib", "react", "../../Utilities", "../../FocusZone", "../../Callout", "../../utilities/selection/index", "./Suggestions/Suggestions", "./Suggestions/SuggestionsController", "./BasePicker.types", "./AutoFill/BaseAutoFill", "./BasePicker.scss"], function (require, exports, tslib_1, React, Utilities_1, FocusZone_1, Callout_1, index_1, Suggestions_1, SuggestionsController_1, BasePicker_types_1, BaseAutoFill_1, stylesImport) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var styles = stylesImport;
var BasePicker = /** @class */ (function (_super) {
tslib_1.__extends(BasePicker, _super);
function BasePicker(basePickerProps) {
var _this = _super.call(this, basePickerProps) || this;
_this.SuggestionOfProperType = Suggestions_1.Suggestions;
var items = basePickerProps.selectedItems || basePickerProps.defaultSelectedItems || [];
_this.suggestionStore = new SuggestionsController_1.SuggestionsController();
_this.selection = new index_1.Selection({ onSelectionChanged: function () { return _this.onSelectionChange(); } });
_this.selection.setItems(items);
_this.state = {
items: items,
suggestedDisplayValue: '',
isMostRecentlyUsedVisible: false,
moreSuggestionsAvailable: false,
isFocused: false,
isSearching: false
};
return _this;
}
Object.defineProperty(BasePicker.prototype, "items", {
get: function () {
return this.state.items;
},
enumerable: true,
configurable: true
});
BasePicker.prototype.componentWillUpdate = function (newProps, newState) {
if (newState.items && newState.items !== this.state.items) {
this.selection.setItems(newState.items);
}
};
BasePicker.prototype.componentDidMount = function () {
this.selection.setItems(this.state.items);
};
BasePicker.prototype.componentWillReceiveProps = function (newProps) {
var _this = this;
var newItems = newProps.selectedItems;
if (newItems) {
var focusIndex_1;
// If there are less new items than old items then something was removed and we
// should try to keep focus consistent
if (newItems.length < this.state.items.length) {
focusIndex_1 = this.state.items.indexOf(this.selection.getSelection()[0]);
}
this.setState({
items: newProps.selectedItems
}, function () {
if (focusIndex_1 >= 0) {
_this.resetFocus(focusIndex_1);
}
});
}
};
BasePicker.prototype.componentWillUnmount = function () {
_super.prototype.componentWillUnmount.call(this);
if (this.loadingTimer) {
this._async.clearTimeout(this.loadingTimer);
}
if (this.currentPromise) {
this.currentPromise = undefined;
}
};
BasePicker.prototype.focus = function () {
this.focusZone.focus();
};
BasePicker.prototype.focusInput = function () {
if (this.input) {
this.input.focus();
}
};
BasePicker.prototype.dismissSuggestions = function (ev) {
var _this = this;
var selectItemFunction = function () {
if (_this.props.onDismiss) {
_this.props.onDismiss(ev, _this.suggestionStore.currentSuggestion ? _this.suggestionStore.currentSuggestion.item : undefined);
}
if (!ev || (ev && !ev.defaultPrevented)) {
// Select the first suggestion if one is available when user leaves.
if (_this.canAddItems() && _this.suggestionStore.hasSelectedSuggestion() && _this.state.suggestedDisplayValue) {
_this.addItemByIndex(0);
}
}
};
if (this.currentPromise) {
this.currentPromise.then(function () { return selectItemFunction(); });
}
else {
selectItemFunction();
}
this.setState({ suggestionsVisible: false });
};
BasePicker.prototype.completeSuggestion = function () {
if (this.suggestionStore.hasSelectedSuggestion()) {
this.addItem(this.suggestionStore.currentSuggestion.item);
this.updateValue('');
this.input.clear();
}
};
BasePicker.prototype.refocusSuggestions = function (keyCode) {
this.resetFocus();
if (keyCode === 38 /* up */) {
this.suggestionStore.setSelectedSuggestion(this.suggestionStore.suggestions.length - 1);
}
else if (keyCode === 40 /* down */) {
this.suggestionStore.setSelectedSuggestion(0);
}
};
BasePicker.prototype.render = function () {
var suggestedDisplayValue = this.state.suggestedDisplayValue;
var _a = this.props, className = _a.className, inputProps = _a.inputProps, disabled = _a.disabled;
var currentIndex = this.suggestionStore.currentIndex;
var selectedSuggestion = currentIndex > -1 ? this.suggestionStore.getSuggestionAtIndex(this.suggestionStore.currentIndex) : undefined;
var selectedSuggestionAlert = selectedSuggestion ? selectedSuggestion.ariaLabel : undefined;
return (React.createElement("div", { ref: this._resolveRef('root'), className: Utilities_1.css('ms-BasePicker', className ? className : ''), onKeyDown: this.onKeyDown },
React.createElement(FocusZone_1.FocusZone, { ref: this._resolveRef('focusZone'), direction: FocusZone_1.FocusZoneDirection.bidirectional, isInnerZoneKeystroke: this._isFocusZoneInnerKeystroke },
React.createElement("div", { className: styles.screenReaderOnly, role: 'alert', id: 'selected-suggestion-alert', "aria-live": 'assertive' },
selectedSuggestionAlert,
" "),
React.createElement(index_1.SelectionZone, { selection: this.selection, selectionMode: index_1.SelectionMode.multiple },
React.createElement("div", { className: Utilities_1.css('ms-BasePicker-text', styles.pickerText, this.state.isFocused && styles.inputFocused), role: 'list' },
this.renderItems(),
this.canAddItems() && (React.createElement(BaseAutoFill_1.BaseAutoFill, tslib_1.__assign({}, inputProps, { className: Utilities_1.css('ms-BasePicker-input', styles.pickerInput), ref: this._resolveRef('input'), onFocus: this.onInputFocus, onBlur: this.onInputBlur, onInputValueChange: this.onInputChange, suggestedDisplayValue: suggestedDisplayValue, "aria-activedescendant": 'sug-' + this.suggestionStore.currentIndex, "aria-owns": 'suggestion-list', "aria-expanded": !!this.state.suggestionsVisible, "aria-haspopup": 'true', autoCapitalize: 'off', autoComplete: 'off', role: 'combobox', disabled: disabled, "aria-controls": 'selected-suggestion-alert', onInputChange: this.props.onInputChange })))))),
this.renderSuggestions()));
};
BasePicker.prototype.canAddItems = function () {
var items = this.state.items;
var itemLimit = this.props.itemLimit;
return itemLimit === undefined || items.length < itemLimit;
};
BasePicker.prototype.renderSuggestions = function () {
var TypedSuggestion = this.SuggestionOfProperType;
return this.state.suggestionsVisible ? (React.createElement(Callout_1.Callout, { isBeakVisible: false, gapSpace: 5, target: this.input.inputElement, onDismiss: this.dismissSuggestions, directionalHint: 4 /* bottomLeftEdge */, directionalHintForRTL: 6 /* bottomRightEdge */ },
React.createElement(TypedSuggestion, tslib_1.__assign({ onRenderSuggestion: this.props.onRenderSuggestionsItem, onSuggestionClick: this.onSuggestionClick, onSuggestionRemove: this.onSuggestionRemove, suggestions: this.suggestionStore.getSuggestions(), ref: this._resolveRef('suggestionElement'), onGetMoreResults: this.onGetMoreResults, moreSuggestionsAvailable: this.state.moreSuggestionsAvailable, isLoading: this.state.suggestionsLoading, isSearching: this.state.isSearching, isMostRecentlyUsedVisible: this.state.isMostRecentlyUsedVisible, isResultsFooterVisible: this.state.isResultsFooterVisible, refocusSuggestions: this.refocusSuggestions }, this.props.pickerSuggestionsProps)))) : (null);
};
BasePicker.prototype.renderItems = function () {
var _this = this;
var _a = this.props, disabled = _a.disabled, removeButtonAriaLabel = _a.removeButtonAriaLabel;
var onRenderItem = this.props.onRenderItem;
var items = this.state.items;
return items.map(function (item, index) { return onRenderItem({
item: item,
index: index,
key: item.key ? item.key : index,
selected: _this.selection.isIndexSelected(index),
onRemoveItem: function () { return _this.removeItem(item); },
disabled: disabled,
onItemChange: _this.onItemChange,
removeButtonAriaLabel: removeButtonAriaLabel
}); });
};
BasePicker.prototype.resetFocus = function (index) {
var items = this.state.items;
if (items.length && index >= 0) {
var newEl = this.root.querySelectorAll('[data-selection-index]')[Math.min(index, items.length - 1)];
if (newEl) {
this.focusZone.focusElement(newEl);
}
}
else if (!this.canAddItems()) {
items[items.length - 1].selected = true;
this.resetFocus(items.length - 1);
}
else {
this.input.focus();
}
};
BasePicker.prototype.onSuggestionSelect = function () {
if (this.suggestionStore.currentSuggestion) {
var currentValue = this.input.value;
var itemValue = this._getTextFromItem(this.suggestionStore.currentSuggestion.item, currentValue);
this.setState({ suggestedDisplayValue: itemValue });
}
};
BasePicker.prototype.onSelectionChange = function () {
this.forceUpdate();
};
BasePicker.prototype.updateSuggestions = function (suggestions) {
this.suggestionStore.updateSuggestions(suggestions, 0);
this.forceUpdate();
};
BasePicker.prototype.onEmptyInputFocus = function () {
var onEmptyInputFocus = this.props.onEmptyInputFocus;
var suggestions = onEmptyInputFocus(this.state.items);
this.updateSuggestionsList(suggestions);
};
BasePicker.prototype.updateValue = function (updatedValue) {
var suggestions = this.props.onResolveSuggestions(updatedValue, this.state.items);
this.updateSuggestionsList(suggestions, updatedValue);
};
BasePicker.prototype.updateSuggestionsList = function (suggestions, updatedValue) {
var _this = this;
var suggestionsArray = suggestions;
var suggestionsPromiseLike = suggestions;
// Check to see if the returned value is an array, if it is then just pass it into the next function.
// If the returned value is not an array then check to see if it's a promise or PromiseLike. If it is then resolve it asynchronously.
if (Array.isArray(suggestionsArray)) {
if (updatedValue !== undefined) {
this.resolveNewValue(updatedValue, suggestionsArray);
}
else {
this.suggestionStore.updateSuggestions(suggestionsArray, 0);
}
}
else if (suggestionsPromiseLike && suggestionsPromiseLike.then) {
if (!this.loadingTimer) {
this.loadingTimer = this._async.setTimeout(function () {
_this.setState({
suggestionsLoading: true
});
}, 500);
}
// Clear suggestions
this.suggestionStore.updateSuggestions([]);
if (updatedValue !== undefined) {
this.setState({
suggestionsVisible: this.input && this.input.value !== '' && this.input.inputElement === document.activeElement
});
}
else {
this.setState({
suggestionsVisible: this.input && this.input.inputElement === document.activeElement
});
}
// Ensure that the promise will only use the callback if it was the most recent one.
var promise_1 = this.currentPromise = suggestionsPromiseLike;
promise_1.then(function (newSuggestions) {
if (promise_1 === _this.currentPromise) {
if (updatedValue !== undefined) {
_this.resolveNewValue(updatedValue, newSuggestions);
}
else {
_this.suggestionStore.updateSuggestions(newSuggestions);
_this.setState({
suggestionsLoading: false
});
}
if (_this.loadingTimer) {
_this._async.clearTimeout(_this.loadingTimer);
_this.loadingTimer = undefined;
}
}
});
}
};
BasePicker.prototype.resolveNewValue = function (updatedValue, suggestions) {
this.suggestionStore.updateSuggestions(suggestions, 0);
var itemValue = undefined;
if (this.suggestionStore.currentSuggestion) {
itemValue = this._getTextFromItem(this.suggestionStore.currentSuggestion.item, updatedValue);
}
this.setState({
suggestionsLoading: false,
suggestedDisplayValue: itemValue,
suggestionsVisible: this.input && this.input.value !== '' && this.input.inputElement === document.activeElement
});
};
BasePicker.prototype.onChange = function (items) {
if (this.props.onChange) {
this.props.onChange(items);
}
};
BasePicker.prototype.onInputChange = function (value) {
this.updateValue(value);
this.setState({
moreSuggestionsAvailable: true,
isMostRecentlyUsedVisible: false
});
};
BasePicker.prototype.onSuggestionClick = function (ev, item, index) {
this.addItemByIndex(index);
this.setState({ suggestionsVisible: false });
};
BasePicker.prototype.onSuggestionRemove = function (ev, item, index) {
if (this.props.onRemoveSuggestion) {
this.props.onRemoveSuggestion(item);
}
this.suggestionStore.removeSuggestion(index);
};
BasePicker.prototype.onInputFocus = function (ev) {
this.setState({ isFocused: true });
this.selection.setAllSelected(false);
if (this.input && this.input.value === '' && this.props.onEmptyInputFocus) {
this.onEmptyInputFocus();
this.setState({
isMostRecentlyUsedVisible: true,
moreSuggestionsAvailable: false,
suggestionsVisible: true
});
}
else if (this.input && this.input.value) {
this.setState({
isMostRecentlyUsedVisible: false,
suggestionsVisible: true
});
}
if (this.props.inputProps && this.props.inputProps.onFocus) {
this.props.inputProps.onFocus(ev);
}
};
BasePicker.prototype.onInputBlur = function (ev) {
this.setState({ isFocused: false });
};
BasePicker.prototype.onKeyDown = function (ev) {
switch (ev.which) {
case 27 /* escape */:
if (this.state.suggestionsVisible) {
this.setState({ suggestionsVisible: false });
ev.preventDefault();
ev.stopPropagation();
}
break;
case 9 /* tab */:
case 13 /* enter */:
if (!ev.shiftKey && this.suggestionStore.hasSelectedSuggestion() && this.state.suggestionsVisible) {
this.completeSuggestion();
ev.preventDefault();
ev.stopPropagation();
}
else {
this._onValidateInput();
}
break;
case 8 /* backspace */:
if (!this.props.disabled) {
this.onBackspace(ev);
}
ev.stopPropagation();
break;
case 46 /* del */:
if (!this.props.disabled) {
if (this.input && ev.target === this.input.inputElement && this.state.suggestionsVisible && this.suggestionStore.currentIndex !== -1) {
if (this.props.onRemoveSuggestion) {
this.props.onRemoveSuggestion(this.suggestionStore.currentSuggestion.item);
}
this.suggestionStore.removeSuggestion(this.suggestionStore.currentIndex);
this.forceUpdate();
}
else {
this.onBackspace(ev);
}
}
ev.stopPropagation();
break;
case 38 /* up */:
if (ev.target === this.input.inputElement && this.state.suggestionsVisible) {
if (this.state.moreSuggestionsAvailable && this.suggestionElement.props.searchForMoreText && this.suggestionStore.currentIndex === 0) {
this.suggestionElement.focusSearchForMoreButton();
this.suggestionStore.deselectAllSuggestions();
this.forceUpdate();
}
else {
if (this.suggestionStore.previousSuggestion()) {
ev.preventDefault();
ev.stopPropagation();
this.onSuggestionSelect();
}
}
}
break;
case 40 /* down */:
if (ev.target === this.input.inputElement && this.state.suggestionsVisible) {
if (this.state.moreSuggestionsAvailable && this.suggestionElement.props.searchForMoreText && (this.suggestionStore.currentIndex + 1) === this.suggestionStore.suggestions.length) {
this.suggestionElement.focusSearchForMoreButton();
this.suggestionStore.deselectAllSuggestions();
this.forceUpdate();
}
else {
if (this.suggestionStore.nextSuggestion()) {
ev.preventDefault();
ev.stopPropagation();
this.onSuggestionSelect();
}
}
}
break;
}
};
BasePicker.prototype.onItemChange = function (changedItem, index) {
var items = this.state.items;
if (index >= 0) {
var newItems = items;
newItems[index] = changedItem;
this._updateSelectedItems(newItems);
}
};
BasePicker.prototype.onGetMoreResults = function () {
var _this = this;
this.setState({
isSearching: true
}, function () {
if (_this.props.onGetMoreResults) {
var suggestions = _this.props.onGetMoreResults(_this.input.value, _this.state.items);
var suggestionsArray = suggestions;
var suggestionsPromiseLike = suggestions;
if (Array.isArray(suggestionsArray)) {
_this.updateSuggestions(suggestionsArray);
_this.setState({ isSearching: false });
}
else if (suggestionsPromiseLike.then) {
suggestionsPromiseLike.then(function (newSuggestions) {
_this.updateSuggestions(newSuggestions);
_this.setState({ isSearching: false });
});
}
}
else {
_this.setState({ isSearching: false });
}
_this.input.focus();
_this.setState({
moreSuggestionsAvailable: false,
isResultsFooterVisible: true
});
});
};
BasePicker.prototype.addItemByIndex = function (index) {
this.addItem(this.suggestionStore.getSuggestionAtIndex(index).item);
if (this.input) {
this.input.clear();
}
this.updateValue('');
};
BasePicker.prototype.addItem = function (item) {
var _this = this;
var processedItem = this.props.onItemSelected ? this.props.onItemSelected(item) : item;
var processedItemObject = processedItem;
var processedItemPromiseLike = processedItem;
if (processedItemPromiseLike && processedItemPromiseLike.then) {
processedItemPromiseLike.then(function (resolvedProcessedItem) {
var newItems = _this.state.items.concat([resolvedProcessedItem]);
_this._updateSelectedItems(newItems);
});
}
else {
var newItems = this.state.items.concat([processedItemObject]);
this._updateSelectedItems(newItems);
}
this.setState({ suggestedDisplayValue: '' });
};
BasePicker.prototype.removeItem = function (item) {
var items = this.state.items;
var index = items.indexOf(item);
if (index >= 0) {
var newItems = items.slice(0, index).concat(items.slice(index + 1));
this._updateSelectedItems(newItems);
}
};
BasePicker.prototype.removeItems = function (itemsToRemove) {
var items = this.state.items;
var newItems = items.filter(function (item) { return itemsToRemove.indexOf(item) === -1; });
var firstItemToRemove = itemsToRemove[0];
var index = items.indexOf(firstItemToRemove);
this._updateSelectedItems(newItems, index);
};
// This is protected because we may expect the backspace key to work differently in a different kind of picker.
// This lets the subclass override it and provide it's own onBackspace. For an example see the BasePickerListBelow
BasePicker.prototype.onBackspace = function (ev) {
if (this.state.items.length && !this.input || (!this.input.isValueSelected && this.input.cursorLocation === 0)) {
if (this.selection.getSelectedCount() > 0) {
this.removeItems(this.selection.getSelection());
}
else {
this.removeItem(this.state.items[this.state.items.length - 1]);
}
}
};
BasePicker.prototype._isFocusZoneInnerKeystroke = function (ev) {
// If suggestions are shown let up/down keys control them, otherwise allow them through to control the focusZone.
if (this.state.suggestionsVisible) {
switch (ev.which) {
case 38 /* up */:
case 40 /* down */:
return true;
}
}
if (ev.which === 13 /* enter */) {
return true;
}
return false;
};
/**
* Controls what happens whenever there is an action that impacts the selected items.
* If selectedItems is provided as a property then this will act as a controlled component and it will not update it's own state.
*/
BasePicker.prototype._updateSelectedItems = function (items, focusIndex) {
var _this = this;
if (this.props.selectedItems) {
// If the component is a controlled component then the controlling component will need
this.onChange(items);
}
else {
this.setState({ items: items }, function () {
_this._onSelectedItemsUpdated(items, focusIndex);
});
}
};
BasePicker.prototype._onSelectedItemsUpdated = function (items, focusIndex) {
this.resetFocus(focusIndex);
this.onChange(items);
};
BasePicker.prototype._onValidateInput = function () {
if (this.props.onValidateInput && this.props.onValidateInput(this.input.value) !== BasePicker_types_1.ValidationState.invalid && this.props.createGenericItem) {
var itemToConvert = this.props.createGenericItem(this.input.value, this.props.onValidateInput(this.input.value));
this.suggestionStore.createGenericSuggestion(itemToConvert);
this.completeSuggestion();
}
};
BasePicker.prototype._getTextFromItem = function (item, currentValue) {
if (this.props.getTextFromItem) {
return this.props.getTextFromItem(item, currentValue);
}
else {
return '';
}
};
tslib_1.__decorate([
Utilities_1.autobind
], BasePicker.prototype, "dismissSuggestions", null);
tslib_1.__decorate([
Utilities_1.autobind
], BasePicker.prototype, "refocusSuggestions", null);
tslib_1.__decorate([
Utilities_1.autobind
], BasePicker.prototype, "onInputChange", null);
tslib_1.__decorate([
Utilities_1.autobind
], BasePicker.prototype, "onSuggestionClick", null);
tslib_1.__decorate([
Utilities_1.autobind
], BasePicker.prototype, "onSuggestionRemove", null);
tslib_1.__decorate([
Utilities_1.autobind
], BasePicker.prototype, "onInputFocus", null);
tslib_1.__decorate([
Utilities_1.autobind
], BasePicker.prototype, "onInputBlur", null);
tslib_1.__decorate([
Utilities_1.autobind
], BasePicker.prototype, "onKeyDown", null);
tslib_1.__decorate([
Utilities_1.autobind
], BasePicker.prototype, "onItemChange", null);
tslib_1.__decorate([
Utilities_1.autobind
], BasePicker.prototype, "onGetMoreResults", null);
tslib_1.__decorate([
Utilities_1.autobind
], BasePicker.prototype, "addItemByIndex", null);
tslib_1.__decorate([
Utilities_1.autobind
], BasePicker.prototype, "addItem", null);
tslib_1.__decorate([
Utilities_1.autobind
], BasePicker.prototype, "removeItem", null);
tslib_1.__decorate([
Utilities_1.autobind
], BasePicker.prototype, "removeItems", null);
tslib_1.__decorate([
Utilities_1.autobind
], BasePicker.prototype, "_isFocusZoneInnerKeystroke", null);
return BasePicker;
}(Utilities_1.BaseComponent));
exports.BasePicker = BasePicker;
var BasePickerListBelow = /** @class */ (function (_super) {
tslib_1.__extends(BasePickerListBelow, _super);
function BasePickerListBelow() {
return _super !== null && _super.apply(this, arguments) || this;
}
BasePickerListBelow.prototype.render = function () {
var suggestedDisplayValue = this.state.suggestedDisplayValue;
var _a = this.props, className = _a.className, inputProps = _a.inputProps, disabled = _a.disabled;
return (React.createElement("div", null,
React.createElement("div", { ref: this._resolveRef('root'), className: Utilities_1.css('ms-BasePicker', className ? className : ''), onKeyDown: this.onKeyDown },
React.createElement(index_1.SelectionZone, { selection: this.selection, selectionMode: index_1.SelectionMode.multiple },
React.createElement("div", { className: Utilities_1.css('ms-BasePicker-text', styles.pickerText, this.state.isFocused && styles.inputFocused) },
React.createElement(BaseAutoFill_1.BaseAutoFill, tslib_1.__assign({}, inputProps, { className: Utilities_1.css('ms-BasePicker-input', styles.pickerInput), ref: this._resolveRef('input'), onFocus: this.onInputFocus, onBlur: this.onInputBlur, onInputValueChange: this.onInputChange, suggestedDisplayValue: suggestedDisplayValue, "aria-activedescendant": 'sug-' + this.suggestionStore.currentIndex, "aria-owns": 'suggestion-list', "aria-expanded": !!this.state.suggestionsVisible, "aria-haspopup": 'true', autoCapitalize: 'off', autoComplete: 'off', role: 'combobox', disabled: disabled }))))),
this.renderSuggestions(),
React.createElement(FocusZone_1.FocusZone, { ref: this._resolveRef('focusZone'), className: 'ms-BasePicker-selectedItems', isCircularNavigation: true, direction: FocusZone_1.FocusZoneDirection.bidirectional, isInnerZoneKeystroke: this._isFocusZoneInnerKeystroke }, this.renderItems())));
};
BasePickerListBelow.prototype.onBackspace = function (ev) {
// override the existing backspace method to not do anything because the list items appear below.
};
return BasePickerListBelow;
}(BasePicker));
exports.BasePickerListBelow = BasePickerListBelow;
});
//# sourceMappingURL=BasePicker.js.map