office-ui-fabric-react
Version:
Reusable React components for building experiences for Office 365.
372 lines (370 loc) • 18.3 kB
JavaScript
"use strict";
var __extends = (this && this.__extends) || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
var __assign = (this && this.__assign) || Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var React = require('react');
var FocusZone_1 = require('../../FocusZone');
var Callout_1 = require('../../Callout');
var KeyCodes_1 = require('../../utilities/KeyCodes');
var index_1 = require('../../utilities/selection/index');
var Suggestions_1 = require('./Suggestions/Suggestions');
var SuggestionsController_1 = require('./Suggestions/SuggestionsController');
var BaseComponent_1 = require('../../common/BaseComponent');
var css_1 = require('../../utilities/css');
var autobind_1 = require('../../utilities/autobind');
require('./BasePicker.scss');
var BasePicker = (function (_super) {
__extends(BasePicker, _super);
function BasePicker(basePickerProps) {
var _this = this;
_super.call(this, basePickerProps);
this.SuggestionOfProperType = Suggestions_1.Suggestions;
var items = 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,
displayValue: '',
value: '',
moreSuggestionsAvailable: false
};
}
BasePicker.prototype.componentWillReceiveProps = 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.focus = function () {
this.focusZone.focus();
};
BasePicker.prototype.dismissSuggestions = function () {
this.setState({ suggestionsVisible: false });
};
BasePicker.prototype.completeSuggestion = function () {
if (this.suggestionStore.hasSelectedSuggestion()) {
this.addItem(this.suggestionStore.currentSuggestion.item);
this.updateValue('');
}
};
BasePicker.prototype.render = function () {
var displayValue = this.state.displayValue;
return (React.createElement("div", {ref: this._resolveRef('root'), className: css_1.css('ms-BasePicker', this.props.className ? this.props.className : ''), onKeyDown: this.onKeyDown},
React.createElement(index_1.SelectionZone, {selection: this.selection, selectionMode: index_1.SelectionMode.multiple},
React.createElement(FocusZone_1.FocusZone, {ref: this._resolveRef('focusZone'), className: 'ms-BasePicker-text'},
this.renderItems(),
React.createElement("input", {ref: this._resolveRef('input'), className: 'ms-BasePicker-input', onFocus: this.onInputFocus, onChange: this.onInputChange, value: displayValue, "aria-activedescendant": 'sug-' + this.suggestionStore.currentIndex, "aria-owns": 'suggestion-list', "aria-expanded": 'true', "aria-haspopup": 'true', autoCapitalize: 'off', autoComplete: 'off'}))
),
this.renderSuggestions()));
};
BasePicker.prototype.renderSuggestions = function () {
var TypedSuggestion = this.SuggestionOfProperType;
return this.state.suggestionsVisible ? (React.createElement(Callout_1.Callout, {isBeakVisible: false, gapSpace: 0, targetElement: this.root, onDismiss: this.dismissSuggestions},
React.createElement(TypedSuggestion, __assign({onRenderSuggestion: this.props.onRenderSuggestionsItem, onSuggestionClick: this.onSuggestionClick, suggestions: this.suggestionStore.getSuggestions(), ref: this._resolveRef('suggestionElement'), onGetMoreResults: this.onGetMoreResults, moreSuggestionsAvailable: this.state.moreSuggestionsAvailable, isLoading: this.state.suggestionsLoading}, this.props.pickerSuggestionsProps))
)) : (null);
};
BasePicker.prototype.renderItems = function () {
var _this = this;
var onRenderItem = this.props.onRenderItem;
var items = this.state.items;
return items.map(function (item, index) { return onRenderItem({
item: item,
index: index,
isSelected: _this.selection.isIndexSelected(index),
onRemoveItem: function () { return _this.removeItem(item); }
}); });
};
BasePicker.prototype.resetFocus = function (index) {
var items = this.state.items;
if (items.length) {
var newEl = this.root.querySelectorAll('[data-selection-index]')[Math.min(index, items.length - 1)];
if (newEl) {
this.focusZone.focusElement(newEl);
}
}
else {
this.input.focus();
}
};
BasePicker.prototype.onSuggestionSelect = function () {
var _this = this;
if (this.suggestionStore.currentSuggestion) {
var currentValue = this.state.value;
var itemValue = this.props.getTextFromItem(this.suggestionStore.currentSuggestion.item, currentValue);
this.updateDisplayValue(currentValue, itemValue, function () { return _this.suggestionElement.scrollSelected(); });
}
};
BasePicker.prototype.onSelectionChange = function () {
this.forceUpdate();
};
BasePicker.prototype.updateSuggestions = function (suggestions) {
this.suggestionStore.updateSuggestions(suggestions);
this.forceUpdate();
};
BasePicker.prototype.updateValue = function (updatedValue) {
var _this = this;
var value = this.state.value;
if (!this.suggestionStore.currentIndex || updatedValue !== value) {
var suggestions = this.props.onResolveSuggestions(updatedValue, this.state.items);
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.resolveNewValue(updatedValue, suggestionsArray);
}
else if (suggestionsPromiseLike.then) {
if (!this.loadingTimer) {
this.loadingTimer = this._async.setTimeout(function () { return _this.setState({
suggestionsLoading: true
}); }, 500);
}
this.updateDisplayValue(updatedValue);
// Ensure that the promise will only use the callback if it was the most recent one.
var promise_1 = this.currentPromise = suggestionsPromiseLike.then(function (newSuggestions) {
if (promise_1 === _this.currentPromise) {
_this.resolveNewValue(updatedValue, newSuggestions);
if (_this.loadingTimer) {
_this._async.clearTimeout(_this.loadingTimer);
_this.loadingTimer = undefined;
}
}
});
}
}
};
BasePicker.prototype.resolveNewValue = function (updatedValue, suggestions) {
this.suggestionStore.updateSuggestions(suggestions);
var itemValue = undefined;
if (this.suggestionStore.currentSuggestion) {
itemValue = this.props.getTextFromItem(this.suggestionStore.currentSuggestion.item, updatedValue);
}
this.setState({ suggestionsLoading: false });
this.updateDisplayValue(updatedValue, itemValue);
};
BasePicker.prototype.updateDisplayValue = function (updatedValue, itemValue, afterUpdateCallback) {
var _this = this;
var differenceIndex = 0;
if (updatedValue && itemValue) {
while (differenceIndex < updatedValue.length && updatedValue[differenceIndex].toLocaleLowerCase() === itemValue[differenceIndex].toLocaleLowerCase()) {
differenceIndex++;
}
}
this.setState({
displayValue: itemValue || updatedValue,
value: updatedValue,
suggestionsVisible: updatedValue && updatedValue !== ''
}, function () {
if (afterUpdateCallback) {
afterUpdateCallback();
}
if (itemValue && differenceIndex < itemValue.length && differenceIndex === updatedValue.length) {
_this.input.setSelectionRange(differenceIndex, itemValue.length, 'backward');
}
});
};
BasePicker.prototype.onChange = function () {
if (this.props.onChange) {
this.props.onChange(this.state.items);
}
};
BasePicker.prototype.onInputChange = function (ev) {
var value = ev.target.value;
this.updateValue(value);
this.setState({ moreSuggestionsAvailable: true });
};
BasePicker.prototype.onSuggestionClick = function (ev, item, index) {
this.addItemByIndex(index);
};
BasePicker.prototype.onInputFocus = function (ev) {
this.selection.setAllSelected(false);
if (this.state.value) {
this.setState({ suggestionsVisible: true });
}
};
BasePicker.prototype.onKeyDown = function (ev) {
var value = this.state.value;
switch (ev.which) {
case KeyCodes_1.KeyCodes.escape:
this.dismissSuggestions();
break;
case KeyCodes_1.KeyCodes.tab:
case KeyCodes_1.KeyCodes.enter:
if (value && this.suggestionStore.hasSelectedSuggestion()) {
this.completeSuggestion();
ev.preventDefault();
ev.stopPropagation();
}
break;
case KeyCodes_1.KeyCodes.backspace:
this.onBackspace(ev);
break;
case KeyCodes_1.KeyCodes.up:
if (ev.target === this.input && this.suggestionStore.previousSuggestion()) {
ev.preventDefault();
ev.stopPropagation();
this.onSuggestionSelect();
}
break;
case KeyCodes_1.KeyCodes.down:
if (ev.target === this.input) {
if (this.suggestionStore.nextSuggestion()) {
ev.preventDefault();
ev.stopPropagation();
this.onSuggestionSelect();
}
}
break;
}
};
BasePicker.prototype.onGetMoreResults = function () {
var _this = this;
if (this.props.onGetMoreResults) {
var suggestions = this.props.onGetMoreResults(this.state.value, this.state.items);
var suggestionsArray = suggestions;
var suggestionsPromiseLike = suggestions;
if (Array.isArray(suggestionsArray)) {
this.updateSuggestions(suggestionsArray);
}
else if (suggestionsPromiseLike.then) {
suggestionsPromiseLike.then(function (newSuggestions) { return _this.updateSuggestions(newSuggestions); });
}
}
this.input.focus();
this.setState({ moreSuggestionsAvailable: false });
};
BasePicker.prototype.addItemByIndex = function (index) {
this.addItem(this.suggestionStore.getSuggestionAtIndex(index).item);
this.updateValue('');
};
BasePicker.prototype.addItem = function (item) {
var _this = this;
var newItems = this.state.items.concat([item]);
this.selection.setItems(newItems);
this.setState({ items: newItems }, function () { return _this.onChange(); });
};
BasePicker.prototype.removeItem = function (item) {
var _this = this;
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.selection.setItems(newItems);
this.setState({ items: newItems }, function () { return _this.onChange(); });
}
};
BasePicker.prototype.removeItems = function (itemsToRemove) {
var _this = this;
var items = this.state.items;
var newItems = items.filter(function (item) { return itemsToRemove.indexOf(item) === -1; });
var firstItemToRemove = this.selection.getSelection()[0];
var index = items.indexOf(firstItemToRemove);
this.selection.setItems(newItems);
this.setState({ items: newItems }, function () { return _this.resetFocus(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) {
var displayValue = this.state.displayValue;
if (ev.target === this.input) {
if (displayValue && this.suggestionStore.hasSelectedSuggestion() && this.input.selectionStart !== this.input.selectionEnd) {
this.updateValue(displayValue.substr(0, this.input.selectionStart - 1));
// Since this effectively deletes a letter from the string we need to preventDefault so that
// the backspace doesn't try to delete a letter that's already been deleted. If a letter is deleted
// it can trigger the onChange event again which can have unintended consequences.
ev.preventDefault();
}
else if (!displayValue && this.state.items.length) {
this.removeItem(this.state.items[this.state.items.length - 1]);
}
}
else if (this.selection.getSelectedCount() > 0) {
this.removeItems(this.selection.getSelection());
}
};
__decorate([
autobind_1.autobind
], BasePicker.prototype, "dismissSuggestions", null);
__decorate([
autobind_1.autobind
], BasePicker.prototype, "onInputChange", null);
__decorate([
autobind_1.autobind
], BasePicker.prototype, "onSuggestionClick", null);
__decorate([
autobind_1.autobind
], BasePicker.prototype, "onInputFocus", null);
__decorate([
autobind_1.autobind
], BasePicker.prototype, "onKeyDown", null);
__decorate([
autobind_1.autobind
], BasePicker.prototype, "onGetMoreResults", null);
__decorate([
autobind_1.autobind
], BasePicker.prototype, "addItemByIndex", null);
__decorate([
autobind_1.autobind
], BasePicker.prototype, "addItem", null);
__decorate([
autobind_1.autobind
], BasePicker.prototype, "removeItem", null);
__decorate([
autobind_1.autobind
], BasePicker.prototype, "removeItems", null);
return BasePicker;
}(BaseComponent_1.BaseComponent));
exports.BasePicker = BasePicker;
var BasePickerListBelow = (function (_super) {
__extends(BasePickerListBelow, _super);
function BasePickerListBelow() {
_super.apply(this, arguments);
}
BasePickerListBelow.prototype.render = function () {
var displayValue = this.state.displayValue;
return (React.createElement("div", null,
React.createElement("div", {ref: this._resolveRef('root'), className: css_1.css('ms-BasePicker', this.props.className ? this.props.className : ''), onKeyDown: this.onKeyDown},
React.createElement(index_1.SelectionZone, {selection: this.selection, selectionMode: index_1.SelectionMode.multiple},
React.createElement("div", {className: 'ms-BasePicker-text'},
React.createElement("input", {ref: this._resolveRef('input'), onFocus: this.onInputFocus, onChange: this.onInputChange, value: displayValue, className: 'ms-BasePicker-input'})
)
)
),
this.renderSuggestions(),
React.createElement(FocusZone_1.FocusZone, {ref: this._resolveRef('focusZone'), className: 'ms-BasePicker-selectedItems'}, this.renderItems())));
};
BasePickerListBelow.prototype.onBackspace = function (ev) {
var value = this.state.value;
if (ev.target === this.input) {
if (value && this.input.selectionStart !== this.input.selectionEnd) {
this.updateValue(value.substring(0, this.input.selectionStart));
// Since this effectively deletes a letter from the string we need to preventDefault so that
// the backspace doesn't try to delete a letter that's already been deleted. If a letter is deleted
// it can trigger the onChange event again which can have unintended consequences.
ev.preventDefault();
}
}
};
return BasePickerListBelow;
}(BasePicker));
exports.BasePickerListBelow = BasePickerListBelow;
//# sourceMappingURL=BasePicker.js.map