office-ui-fabric-react
Version:
Reusable React components for building experiences for Microsoft 365.
792 lines • 41.1 kB
JavaScript
import { __assign, __extends } from "tslib";
import * as React from 'react';
import { Async, KeyCodes, css, elementContains, getId, classNamesFunction, styled, initializeComponentRef, } from '../../Utilities';
import { FocusZone, FocusZoneDirection } from '../../FocusZone';
import { Callout, DirectionalHint } from '../../Callout';
import { Selection, SelectionZone, SelectionMode } from '../../utilities/selection/index';
import { Suggestions } from './Suggestions/Suggestions';
import { getStyles as suggestionsStyles } from './Suggestions/Suggestions.styles';
import { SuggestionsController } from './Suggestions/SuggestionsController';
import { ValidationState, } from './BasePicker.types';
import { Autofill } from '../Autofill/index';
import * as stylesImport from './BasePicker.scss';
var legacyStyles = stylesImport;
var getClassNames = classNamesFunction();
/**
* Should be removed once new picker without inheritance is created
*/
function getStyledSuggestions(suggestionsType) {
return styled(suggestionsType, suggestionsStyles, undefined, {
scope: 'Suggestions',
});
}
/**
* {@docCategory Pickers}
*/
var BasePicker = /** @class */ (function (_super) {
__extends(BasePicker, _super);
function BasePicker(basePickerProps) {
var _this = _super.call(this, basePickerProps) || this;
// Refs
_this.root = React.createRef();
_this.input = React.createRef();
_this.focusZone = React.createRef();
_this.suggestionElement = React.createRef();
/**
* @deprecated this is no longer necessary as typescript now supports generic elements
*/
_this.SuggestionOfProperType = Suggestions;
// eslint-disable-next-line deprecation/deprecation
_this._styledSuggestions = getStyledSuggestions(_this.SuggestionOfProperType);
_this.dismissSuggestions = function (ev) {
var selectItemFunction = function () {
var addItemOnDismiss = true;
if (_this.props.onDismiss) {
addItemOnDismiss = _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 and permitted by onDismiss when user leaves.
if (addItemOnDismiss !== false &&
_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 });
};
_this.refocusSuggestions = function (keyCode) {
_this.resetFocus();
if (_this.suggestionStore.suggestions && _this.suggestionStore.suggestions.length > 0) {
if (keyCode === KeyCodes.up) {
_this.suggestionStore.setSelectedSuggestion(_this.suggestionStore.suggestions.length - 1);
}
else if (keyCode === KeyCodes.down) {
_this.suggestionStore.setSelectedSuggestion(0);
}
}
};
_this.onInputChange = function (value) {
_this.updateValue(value);
_this.setState({
moreSuggestionsAvailable: true,
isMostRecentlyUsedVisible: false,
});
};
_this.onSuggestionClick = function (ev, item, index) {
_this.addItemByIndex(index);
};
_this.onSuggestionRemove = function (ev, item, index) {
if (_this.props.onRemoveSuggestion) {
_this.props.onRemoveSuggestion(item);
}
_this.suggestionStore.removeSuggestion(index);
};
_this.onInputFocus = function (ev) {
_this.selection.setAllSelected(false);
// Only trigger all of the focus if this component isn't already focused.
// For example when an item is selected or removed from the selected list it should be treated
// as though the input is still focused.
if (!_this.state.isFocused) {
_this.setState({ isFocused: true });
_this._userTriggeredSuggestions();
if (_this.props.inputProps && _this.props.inputProps.onFocus) {
_this.props.inputProps.onFocus(ev);
}
}
};
_this.onInputBlur = function (ev) {
if (_this.props.inputProps && _this.props.inputProps.onBlur) {
_this.props.inputProps.onBlur(ev);
}
};
_this.onBlur = function (ev) {
if (_this.state.isFocused) {
// Only blur the entire component if an unrelated element gets focus.
// Otherwise treat it as though it still has focus.
// Do nothing if the blur is coming from something
// inside the comboBox root or the comboBox menu since
// it we are not really bluring from the whole comboBox
var relatedTarget = ev.relatedTarget;
if (ev.relatedTarget === null) {
// In IE11, due to lack of support, event.relatedTarget is always
// null making every onBlur call to be "outside" of the ComboBox
// even when it's not. Using document.activeElement is another way
// for us to be able to get what the relatedTarget without relying
// on the event
relatedTarget = document.activeElement;
}
if (relatedTarget && !elementContains(_this.root.current, relatedTarget)) {
_this.setState({ isFocused: false });
if (_this.props.onBlur) {
_this.props.onBlur(ev);
}
}
}
};
/**
* Reveals suggestions any time the user clicks on the input element
* without shifting focus.
*/
_this.onClick = function (ev) {
if (_this.props.inputProps !== undefined && _this.props.inputProps.onClick !== undefined) {
_this.props.inputProps.onClick(ev);
}
// Only primary (left) clicks show suggestions.
if (ev.button === 0) {
_this._userTriggeredSuggestions();
}
};
_this.onKeyDown = function (ev) {
var keyCode = ev.which;
switch (keyCode) {
case KeyCodes.escape:
if (_this.state.suggestionsVisible) {
_this.setState({ suggestionsVisible: false });
ev.preventDefault();
ev.stopPropagation();
}
break;
case KeyCodes.tab:
case KeyCodes.enter:
if (_this.suggestionElement.current && _this.suggestionElement.current.hasSuggestedActionSelected()) {
_this.suggestionElement.current.executeSelectedAction();
}
else if (!ev.shiftKey && _this.suggestionStore.hasSelectedSuggestion() && _this.state.suggestionsVisible) {
_this.completeSuggestion();
ev.preventDefault();
ev.stopPropagation();
}
else {
_this._completeGenericSuggestion();
}
break;
case KeyCodes.backspace:
if (!_this.props.disabled) {
_this.onBackspace(ev);
}
ev.stopPropagation();
break;
case KeyCodes.del:
if (!_this.props.disabled) {
if (_this.input.current &&
ev.target === _this.input.current.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 KeyCodes.up:
if (_this.input.current && ev.target === _this.input.current.inputElement && _this.state.suggestionsVisible) {
if (_this.suggestionElement.current &&
_this.suggestionElement.current.tryHandleKeyDown(keyCode, _this.suggestionStore.currentIndex)) {
ev.preventDefault();
ev.stopPropagation();
_this.forceUpdate();
}
else {
if (_this.suggestionElement.current &&
_this.suggestionElement.current.hasSuggestedAction() &&
_this.suggestionStore.currentIndex === 0) {
ev.preventDefault();
ev.stopPropagation();
_this.suggestionElement.current.focusAboveSuggestions();
_this.suggestionStore.deselectAllSuggestions();
_this.forceUpdate();
}
else {
if (_this.suggestionStore.previousSuggestion()) {
ev.preventDefault();
ev.stopPropagation();
_this.onSuggestionSelect();
}
}
}
}
break;
case KeyCodes.down:
if (_this.input.current && ev.target === _this.input.current.inputElement && _this.state.suggestionsVisible) {
if (_this.suggestionElement.current &&
_this.suggestionElement.current.tryHandleKeyDown(keyCode, _this.suggestionStore.currentIndex)) {
ev.preventDefault();
ev.stopPropagation();
_this.forceUpdate();
}
else {
if (_this.suggestionElement.current &&
_this.suggestionElement.current.hasSuggestedAction() &&
_this.suggestionStore.currentIndex + 1 === _this.suggestionStore.suggestions.length) {
ev.preventDefault();
ev.stopPropagation();
_this.suggestionElement.current.focusBelowSuggestions();
_this.suggestionStore.deselectAllSuggestions();
_this.forceUpdate();
}
else {
if (_this.suggestionStore.nextSuggestion()) {
ev.preventDefault();
ev.stopPropagation();
_this.onSuggestionSelect();
}
}
}
}
break;
}
};
_this.onItemChange = function (changedItem, index) {
var items = _this.state.items;
if (index >= 0) {
var newItems = items;
newItems[index] = changedItem;
_this._updateSelectedItems(newItems);
}
};
_this.onGetMoreResults = function () {
_this.setState({
isSearching: true,
}, function () {
if (_this.props.onGetMoreResults && _this.input.current) {
var suggestions = _this.props.onGetMoreResults(_this.input.current.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 });
}
if (_this.input.current) {
_this.input.current.focus();
}
_this.setState({
moreSuggestionsAvailable: false,
isResultsFooterVisible: true,
});
});
};
_this.completeSelection = function (item) {
_this.addItem(item);
_this.updateValue('');
if (_this.input.current) {
_this.input.current.clear();
}
_this.setState({ suggestionsVisible: false });
};
_this.addItemByIndex = function (index) {
_this.completeSelection(_this.suggestionStore.getSuggestionAtIndex(index).item);
};
_this.addItem = function (item) {
var processedItem = _this.props.onItemSelected
? _this.props.onItemSelected(item)
: item;
if (processedItem === null) {
return;
}
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: '' });
};
_this.removeItem = function (item, focusNextItem) {
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);
}
};
_this.removeItems = function (itemsToRemove) {
var items = _this.state.items;
var newItems = items.filter(function (item) { return itemsToRemove.indexOf(item) === -1; });
_this._updateSelectedItems(newItems);
};
_this._shouldFocusZoneEnterInnerZone = function (ev) {
// If suggestions are shown const up/down keys control them, otherwise allow them through to control the focusZone.
if (_this.state.suggestionsVisible) {
switch (ev.which) {
case KeyCodes.up:
case KeyCodes.down:
return true;
}
}
if (ev.which === KeyCodes.enter) {
return true;
}
return false;
};
_this._onResolveSuggestions = function (updatedValue) {
var suggestions = _this.props.onResolveSuggestions(updatedValue, _this.state.items);
if (suggestions !== null) {
_this.updateSuggestionsList(suggestions, updatedValue);
}
};
_this._completeGenericSuggestion = function () {
if (_this.props.onValidateInput &&
_this.input.current &&
_this.props.onValidateInput(_this.input.current.value) !== ValidationState.invalid &&
_this.props.createGenericItem) {
var itemToConvert = _this.props.createGenericItem(_this.input.current.value, _this.props.onValidateInput(_this.input.current.value));
_this.suggestionStore.createGenericSuggestion(itemToConvert);
_this.completeSuggestion();
}
};
/**
* This should be called when the user does something other than use text entry to trigger suggestions.
*
*/
_this._userTriggeredSuggestions = function () {
if (!_this.state.suggestionsVisible) {
var input = _this.input.current ? _this.input.current.value : '';
if (!input) {
_this.onEmptyInputFocus();
}
else {
if (_this.suggestionStore.suggestions.length === 0) {
_this._onResolveSuggestions(input);
}
else {
_this.setState({
isMostRecentlyUsedVisible: false,
suggestionsVisible: true,
});
}
}
}
};
initializeComponentRef(_this);
_this._async = new Async(_this);
var items = basePickerProps.selectedItems || basePickerProps.defaultSelectedItems || [];
_this._id = getId();
_this._ariaMap = {
selectedItems: "selected-items-" + _this._id,
selectedSuggestionAlert: "selected-suggestion-alert-" + _this._id,
suggestionList: "suggestion-list-" + _this._id,
combobox: "combobox-" + _this._id,
};
_this.suggestionStore = new SuggestionsController();
_this.selection = new Selection({ onSelectionChanged: function () { return _this.onSelectionChange(); } });
_this.selection.setItems(items);
_this.state = {
items: items,
suggestedDisplayValue: '',
isMostRecentlyUsedVisible: false,
moreSuggestionsAvailable: false,
isFocused: false,
isSearching: false,
selectedIndices: [],
};
return _this;
}
BasePicker.getDerivedStateFromProps = function (newProps) {
if (newProps.selectedItems) {
return { items: newProps.selectedItems };
}
return null;
};
Object.defineProperty(BasePicker.prototype, "items", {
get: function () {
return this.state.items;
},
enumerable: true,
configurable: true
});
BasePicker.prototype.componentDidMount = function () {
this.selection.setItems(this.state.items);
this._onResolveSuggestions = this._async.debounce(this._onResolveSuggestions, this.props.resolveDelay);
};
BasePicker.prototype.componentDidUpdate = function (oldProps, oldState) {
if (this.state.items && this.state.items !== oldState.items) {
var currentSelectedIndex = this.selection.getSelectedIndices()[0];
this.selection.setItems(this.state.items);
if (this.state.isFocused) {
// Reset focus and selection so that selected item stays in sync if something
// has been removed
if (this.state.items.length < oldState.items.length) {
this.selection.setIndexSelected(currentSelectedIndex, true, true);
this.resetFocus(currentSelectedIndex);
}
}
}
};
BasePicker.prototype.componentWillUnmount = function () {
if (this.currentPromise) {
this.currentPromise = undefined;
}
this._async.dispose();
};
BasePicker.prototype.focus = function () {
if (this.focusZone.current) {
this.focusZone.current.focus();
}
};
BasePicker.prototype.focusInput = function () {
if (this.input.current) {
this.input.current.focus();
}
};
BasePicker.prototype.completeSuggestion = function (forceComplete) {
if (this.suggestionStore.hasSelectedSuggestion() && this.input.current) {
this.completeSelection(this.suggestionStore.currentSuggestion.item);
}
else if (forceComplete) {
this._completeGenericSuggestion();
}
};
BasePicker.prototype.render = function () {
var _a, _b;
var _c = this.state, suggestedDisplayValue = _c.suggestedDisplayValue, isFocused = _c.isFocused, items = _c.items;
var _d = this.props, className = _d.className, inputProps = _d.inputProps, disabled = _d.disabled, theme = _d.theme, styles = _d.styles;
var suggestionsAvailable = this.state.suggestionsVisible ? this._ariaMap.suggestionList : '';
// TODO
// Clean this up by leaving only the first part after removing support for SASS.
// Currently we can not remove the SASS styles from BasePicker class because it
// might be used by consumers who created custom pickers from extending from
// this base class and have not used the new 'styles' prop.
// We check for 'styles' prop which is going to be injected by the 'styled' HOC
// for every other already existing picker variant (PeoplePicker, TagPicker)
// so that we can use the CSS-in-JS styles. If the check fails (ex: custom picker),
// then we just use the old SASS styles instead.
var classNames = styles
? getClassNames(styles, {
theme: theme,
className: className,
isFocused: isFocused,
disabled: disabled,
inputClassName: inputProps && inputProps.className,
})
: {
root: css('ms-BasePicker', className ? className : ''),
text: css('ms-BasePicker-text', legacyStyles.pickerText, this.state.isFocused && legacyStyles.inputFocused),
itemsWrapper: legacyStyles.pickerItems,
input: css('ms-BasePicker-input', legacyStyles.pickerInput, inputProps && inputProps.className),
screenReaderText: legacyStyles.screenReaderOnly,
};
return (React.createElement("div", { ref: this.root, className: classNames.root, onKeyDown: this.onKeyDown, onBlur: this.onBlur },
React.createElement(FocusZone, { componentRef: this.focusZone, direction: FocusZoneDirection.bidirectional, shouldEnterInnerZone: this._shouldFocusZoneEnterInnerZone },
this.getSuggestionsAlert(classNames.screenReaderText),
React.createElement(SelectionZone, { selection: this.selection, selectionMode: SelectionMode.multiple },
React.createElement("div", { className: classNames.text },
items.length > 0 && (React.createElement("span", { id: this._ariaMap.selectedItems, className: classNames.itemsWrapper, role: 'list' }, this.renderItems())),
this.canAddItems() && (React.createElement(Autofill, __assign({ spellCheck: false }, inputProps, { className: classNames.input, componentRef: this.input, id: ((_a = inputProps) === null || _a === void 0 ? void 0 : _a.id) ? inputProps.id : this._ariaMap.combobox, onClick: this.onClick, onFocus: this.onInputFocus, onBlur: this.onInputBlur, onInputValueChange: this.onInputChange, suggestedDisplayValue: suggestedDisplayValue, "aria-activedescendant": this.getActiveDescendant(), "aria-controls": suggestionsAvailable, "aria-describedby": items.length > 0 ? this._ariaMap.selectedItems : undefined, "aria-expanded": !!this.state.suggestionsVisible, "aria-haspopup": "listbox", "aria-label": this.props['aria-label'] || ((_b = inputProps) === null || _b === void 0 ? void 0 : _b['aria-label']), role: "combobox", disabled: disabled, 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 StyledTypedSuggestions = this._styledSuggestions;
return this.state.suggestionsVisible && this.input ? (React.createElement(Callout, __assign({ isBeakVisible: false, gapSpace: 5, target: this.input.current ? this.input.current.inputElement : undefined, onDismiss: this.dismissSuggestions, directionalHint: DirectionalHint.bottomLeftEdge, directionalHintForRTL: DirectionalHint.bottomRightEdge }, this.props.pickerCalloutProps),
React.createElement(StyledTypedSuggestions
// Assumed to set in derived component's defaultProps
, __assign({
// Assumed to set in derived component's defaultProps
onRenderSuggestion: this.props.onRenderSuggestionsItem, onSuggestionClick: this.onSuggestionClick, onSuggestionRemove: this.onSuggestionRemove, suggestions: this.suggestionStore.getSuggestions(), componentRef: this.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, removeSuggestionAriaLabel: this.props.removeButtonAriaLabel, suggestionsListId: this._ariaMap.suggestionList, createGenericItem: this._completeGenericSuggestion }, 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 _b = this.state, items = _b.items, selectedIndices = _b.selectedIndices;
return items.map(function (item, index) {
return onRenderItem({
item: item,
index: index,
key: item.key ? item.key : index,
selected: selectedIndices.indexOf(index) !== -1,
onRemoveItem: function () { return _this.removeItem(item, true); },
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.current &&
this.root.current.querySelectorAll('[data-selection-index]')[Math.min(index, items.length - 1)];
if (newEl && this.focusZone.current) {
this.focusZone.current.focusElement(newEl);
}
}
else if (!this.canAddItems()) {
this.resetFocus(items.length - 1);
}
else {
if (this.input.current) {
this.input.current.focus();
}
}
};
BasePicker.prototype.onSuggestionSelect = function () {
if (this.suggestionStore.currentSuggestion) {
var currentValue = this.input.current ? this.input.current.value : '';
var itemValue = this._getTextFromItem(this.suggestionStore.currentSuggestion.item, currentValue);
this.setState({ suggestedDisplayValue: itemValue });
}
};
BasePicker.prototype.onSelectionChange = function () {
this.setState({
selectedIndices: this.selection.getSelectedIndices(),
});
};
BasePicker.prototype.updateSuggestions = function (suggestions) {
this.suggestionStore.updateSuggestions(suggestions, 0);
this.forceUpdate();
};
/**
* Only to be called when there is nothing in the input. Checks to see if the consumer has
* provided a function to resolve suggestions
*/
BasePicker.prototype.onEmptyInputFocus = function () {
var emptyResolveSuggestions = this.props.onEmptyResolveSuggestions
? this.props.onEmptyResolveSuggestions
: // eslint-disable-next-line deprecation/deprecation
this.props.onEmptyInputFocus;
// Only attempt to resolve suggestions if it exists
if (emptyResolveSuggestions) {
var suggestions = emptyResolveSuggestions(this.state.items);
this.updateSuggestionsList(suggestions);
this.setState({
isMostRecentlyUsedVisible: true,
suggestionsVisible: true,
moreSuggestionsAvailable: false,
});
}
};
BasePicker.prototype.updateValue = function (updatedValue) {
this._onResolveSuggestions(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)) {
this._updateAndResolveValue(updatedValue, suggestionsArray);
}
else if (suggestionsPromiseLike && suggestionsPromiseLike.then) {
this.setState({
suggestionsLoading: true,
});
// Clear suggestions
this.suggestionStore.updateSuggestions([]);
if (updatedValue !== undefined) {
this.setState({
suggestionsVisible: this._getShowSuggestions(),
});
}
else {
this.setState({
suggestionsVisible: this.input.current && this.input.current.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) {
_this._updateAndResolveValue(updatedValue, newSuggestions);
}
});
}
};
BasePicker.prototype.resolveNewValue = function (updatedValue, suggestions) {
var _this = this;
this.updateSuggestions(suggestions);
var itemValue = undefined;
if (this.suggestionStore.currentSuggestion) {
itemValue = this._getTextFromItem(this.suggestionStore.currentSuggestion.item, updatedValue);
}
// Only set suggestionloading to false after there has been time for the new suggestions to flow
// to the suggestions list. This is to ensure that the suggestions are available before aria-activedescendant
// is set so that screen readers will read out the first selected option.
this.setState({
suggestedDisplayValue: itemValue,
suggestionsVisible: this._getShowSuggestions(),
}, function () { return _this.setState({ suggestionsLoading: false }); });
};
BasePicker.prototype.onChange = function (items) {
if (this.props.onChange) {
this.props.onChange(items);
}
};
// 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.current) ||
(this.input.current && !this.input.current.isValueSelected && this.input.current.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.getActiveDescendant = function () {
if (this.state.suggestionsLoading) {
return undefined;
}
var currentIndex = this.suggestionStore.currentIndex;
// if the suggestions element has actions and the currentIndex does not point to a suggestion, return the action id
if (currentIndex < 0 && this.suggestionElement.current && this.suggestionElement.current.hasSuggestedAction()) {
return 'sug-selectedAction';
}
return currentIndex > -1 && !this.state.suggestionsLoading ? 'sug-' + currentIndex : undefined;
};
BasePicker.prototype.getSuggestionsAlert = function (suggestionAlertClassName) {
if (suggestionAlertClassName === void 0) { suggestionAlertClassName = legacyStyles.screenReaderOnly; }
var currentIndex = this.suggestionStore.currentIndex;
if (this.props.enableSelectedSuggestionAlert) {
var selectedSuggestion = currentIndex > -1 ? this.suggestionStore.getSuggestionAtIndex(this.suggestionStore.currentIndex) : undefined;
var selectedSuggestionAlertText = selectedSuggestion ? selectedSuggestion.ariaLabel : undefined;
return (React.createElement("div", { className: suggestionAlertClassName, role: "alert", id: this._ariaMap.selectedSuggestionAlert, "aria-live": "assertive" },
selectedSuggestionAlertText,
' '));
}
};
/**
* Takes in the current updated value and either resolves it with the new suggestions
* or if updated value is undefined then it clears out currently suggested items
*/
BasePicker.prototype._updateAndResolveValue = function (updatedValue, newSuggestions) {
if (updatedValue !== undefined) {
this.resolveNewValue(updatedValue, newSuggestions);
}
else {
this.suggestionStore.updateSuggestions(newSuggestions, -1);
if (this.state.suggestionsLoading) {
this.setState({
suggestionsLoading: false,
});
}
}
};
/**
* Controls what happens whenever there is an action that impacts the selected items.
* If `selectedItems` is provided, this will act as a controlled component and it will not update its own state.
*/
BasePicker.prototype._updateSelectedItems = function (items) {
var _this = this;
if (this.props.selectedItems) {
// If the component is a controlled component then the controlling component will need to add or remove the items.
this.onChange(items);
}
else {
this.setState({ items: items }, function () {
_this._onSelectedItemsUpdated(items);
});
}
};
BasePicker.prototype._onSelectedItemsUpdated = function (items) {
this.onChange(items);
};
/**
* Suggestions are normally shown after the user updates text and the text
* is non-empty, but also when the user clicks on the input element.
* @returns True if suggestions should be shown.
*/
BasePicker.prototype._getShowSuggestions = function () {
var areSuggestionsVisible = this.input.current !== undefined &&
this.input.current !== null &&
this.input.current.inputElement === document.activeElement &&
this.input.current.value !== '';
return areSuggestionsVisible;
};
BasePicker.prototype._getTextFromItem = function (item, currentValue) {
if (this.props.getTextFromItem) {
return this.props.getTextFromItem(item, currentValue);
}
else {
return '';
}
};
return BasePicker;
}(React.Component));
export { BasePicker };
var BasePickerListBelow = /** @class */ (function (_super) {
__extends(BasePickerListBelow, _super);
function BasePickerListBelow() {
return _super !== null && _super.apply(this, arguments) || this;
}
BasePickerListBelow.prototype.render = function () {
var _a = this.state, suggestedDisplayValue = _a.suggestedDisplayValue, isFocused = _a.isFocused;
var _b = this.props, className = _b.className, inputProps = _b.inputProps, disabled = _b.disabled, theme = _b.theme, styles = _b.styles;
var suggestionsAvailable = this.state.suggestionsVisible ? this._ariaMap.suggestionList : '';
// TODO
// Clean this up by leaving only the first part after removing support for SASS.
// Currently we can not remove the SASS styles from BasePicker class because it
// might be used by consumers who created custom pickers from extending from
// this base class and have not used the new 'styles' prop.
// We check for 'styles' prop which is going to be injected by the 'styled' HOC
// for every other already existing picker variant (PeoplePicker, TagPicker)
// so that we can use the CSS-in-JS styles. If the check fails (ex: custom picker),
// then we just use the old SASS styles instead.
var classNames = styles
? getClassNames(styles, {
theme: theme,
className: className,
isFocused: isFocused,
inputClassName: inputProps && inputProps.className,
})
: {
root: css('ms-BasePicker', className ? className : ''),
text: css('ms-BasePicker-text', legacyStyles.pickerText, this.state.isFocused && legacyStyles.inputFocused, disabled && legacyStyles.inputDisabled),
input: css('ms-BasePicker-input', legacyStyles.pickerInput, inputProps && inputProps.className),
screenReaderText: legacyStyles.screenReaderOnly,
};
return (React.createElement("div", { ref: this.root, onBlur: this.onBlur },
React.createElement("div", { className: classNames.root, onKeyDown: this.onKeyDown },
this.getSuggestionsAlert(classNames.screenReaderText),
React.createElement("div", { className: classNames.text },
React.createElement(Autofill, __assign({}, inputProps, { className: classNames.input, componentRef: this.input, onFocus: this.onInputFocus, onBlur: this.onInputBlur, onClick: this.onClick, onInputValueChange: this.onInputChange, suggestedDisplayValue: suggestedDisplayValue, "aria-activedescendant": this.getActiveDescendant(), "aria-controls": suggestionsAvailable || undefined, "aria-expanded": !!this.state.suggestionsVisible, "aria-haspopup": "listbox", role: "combobox", disabled: disabled, onInputChange: this.props.onInputChange })))),
this.renderSuggestions(),
React.createElement(SelectionZone, { selection: this.selection, selectionMode: SelectionMode.single },
React.createElement(FocusZone, { componentRef: this.focusZone, className: "ms-BasePicker-selectedItems" // just a className hook without any styles applied to it.
, isCircularNavigation: true, direction: FocusZoneDirection.bidirectional, shouldEnterInnerZone: this._shouldFocusZoneEnterInnerZone, id: this._ariaMap.selectedItems, role: 'list' }, 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));
export { BasePickerListBelow };
//# sourceMappingURL=BasePicker.js.map