UNPKG

office-ui-fabric-react

Version:

Reusable React components for building experiences for Office 365.

591 lines • 32.1 kB
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