@uifabric/utilities
Version:
Fluent UI React utilities for building components.
425 lines • 17.6 kB
JavaScript
import { SELECTION_CHANGE, SELECTION_ITEMS_CHANGE, SelectionMode } from './Selection.types';
import { EventGroup } from '../EventGroup';
/**
* {@docCategory Selection}
*/
var Selection = /** @class */ (function () {
/**
* Create a new Selection. If `TItem` does not have a `key` property, you must provide an options
* object with a `getKey` implementation. Providing options is optional otherwise.
* (At most one `options` object is accepted.)
*/
function Selection() {
var options = []; // Otherwise, arguments require options with `getKey`.
for (var _i = 0 // Otherwise, arguments require options with `getKey`.
; _i < arguments.length // Otherwise, arguments require options with `getKey`.
; _i++ // Otherwise, arguments require options with `getKey`.
) {
options[_i] = arguments[_i]; // Otherwise, arguments require options with `getKey`.
}
var _a = options[0] || {}, onSelectionChanged = _a.onSelectionChanged, onItemsChanged = _a.onItemsChanged, getKey = _a.getKey, _b = _a.canSelectItem, canSelectItem = _b === void 0 ? function () { return true; } : _b, items = _a.items, _c = _a.selectionMode, selectionMode = _c === void 0 ? SelectionMode.multiple : _c;
this.mode = selectionMode;
this._getKey = getKey || defaultGetKey;
this._changeEventSuppressionCount = 0;
this._exemptedCount = 0;
this._anchoredIndex = 0;
this._unselectableCount = 0;
this._onSelectionChanged = onSelectionChanged;
this._onItemsChanged = onItemsChanged;
this._canSelectItem = canSelectItem;
this._keyToIndexMap = {};
this._isModal = false;
this.setItems(items || [], true);
this.count = this.getSelectedCount();
}
Selection.prototype.canSelectItem = function (item, index) {
if (typeof index === 'number' && index < 0) {
return false;
}
return this._canSelectItem(item, index);
};
Selection.prototype.getKey = function (item, index) {
var key = this._getKey(item, index);
return typeof key === 'number' || key ? "" + key : '';
};
Selection.prototype.setChangeEvents = function (isEnabled, suppressChange) {
this._changeEventSuppressionCount += isEnabled ? -1 : 1;
if (this._changeEventSuppressionCount === 0 && this._hasChanged) {
this._hasChanged = false;
if (!suppressChange) {
this._change();
}
}
};
Selection.prototype.isModal = function () {
return this._isModal;
};
Selection.prototype.setModal = function (isModal) {
if (this._isModal !== isModal) {
this.setChangeEvents(false);
this._isModal = isModal;
if (!isModal) {
this.setAllSelected(false);
}
this._change();
this.setChangeEvents(true);
}
};
/**
* Selection needs the items, call this method to set them. If the set
* of items is the same, this will re-evaluate selection and index maps.
* Otherwise, shouldClear should be set to true, so that selection is
* cleared.
*/
Selection.prototype.setItems = function (items, shouldClear) {
if (shouldClear === void 0) { shouldClear = true; }
var newKeyToIndexMap = {};
var newUnselectableIndices = {};
var hasSelectionChanged = false;
this.setChangeEvents(false);
// Reset the unselectable count.
this._unselectableCount = 0;
var haveItemsChanged = false;
// Build lookup table for quick selection evaluation.
for (var i = 0; i < items.length; i++) {
var item = items[i];
if (item) {
var key = this.getKey(item, i);
if (key) {
if (!haveItemsChanged && (!(key in this._keyToIndexMap) || this._keyToIndexMap[key] !== i)) {
haveItemsChanged = true;
}
newKeyToIndexMap[key] = i;
}
}
newUnselectableIndices[i] = item && !this.canSelectItem(item);
if (newUnselectableIndices[i]) {
this._unselectableCount++;
}
}
if (shouldClear || items.length === 0) {
this._setAllSelected(false, true);
}
// Check the exemption list for discrepencies.
var newExemptedIndicies = {};
var newExemptedCount = 0;
for (var indexProperty in this._exemptedIndices) {
if (this._exemptedIndices.hasOwnProperty(indexProperty)) {
var index = Number(indexProperty);
var item = this._items[index];
var exemptKey = item ? this.getKey(item, Number(index)) : undefined;
var newIndex = exemptKey ? newKeyToIndexMap[exemptKey] : index;
if (newIndex === undefined) {
// The item has likely been replaced or removed.
hasSelectionChanged = true;
}
else {
// We know the new index of the item. update the existing exemption table.
newExemptedIndicies[newIndex] = true;
newExemptedCount++;
hasSelectionChanged = hasSelectionChanged || newIndex !== index;
}
}
}
if (this._items && this._exemptedCount === 0 && items.length !== this._items.length && this._isAllSelected) {
// If everything was selected but the number of items has changed, selection has changed.
hasSelectionChanged = true;
}
if (!haveItemsChanged) {
for (var _i = 0, _a = Object.keys(this._keyToIndexMap); _i < _a.length; _i++) {
var key = _a[_i];
if (!(key in newKeyToIndexMap)) {
haveItemsChanged = true;
break;
}
}
}
this._exemptedIndices = newExemptedIndicies;
this._exemptedCount = newExemptedCount;
this._keyToIndexMap = newKeyToIndexMap;
this._unselectableIndices = newUnselectableIndices;
this._items = items;
this._selectedItems = null;
if (hasSelectionChanged) {
this._updateCount();
}
if (haveItemsChanged) {
EventGroup.raise(this, SELECTION_ITEMS_CHANGE);
if (this._onItemsChanged) {
this._onItemsChanged();
}
}
if (hasSelectionChanged) {
this._change();
}
this.setChangeEvents(true);
};
Selection.prototype.getItems = function () {
return this._items;
};
Selection.prototype.getSelection = function () {
if (!this._selectedItems) {
this._selectedItems = [];
var items = this._items;
if (items) {
for (var i = 0; i < items.length; i++) {
if (this.isIndexSelected(i)) {
this._selectedItems.push(items[i]);
}
}
}
}
return this._selectedItems;
};
Selection.prototype.getSelectedCount = function () {
return this._isAllSelected
? this._items.length - this._exemptedCount - this._unselectableCount
: this._exemptedCount;
};
Selection.prototype.getSelectedIndices = function () {
if (!this._selectedIndices) {
this._selectedIndices = [];
var items = this._items;
if (items) {
for (var i = 0; i < items.length; i++) {
if (this.isIndexSelected(i)) {
this._selectedIndices.push(i);
}
}
}
}
return this._selectedIndices;
};
Selection.prototype.getItemIndex = function (key) {
var index = this._keyToIndexMap[key];
return (index !== null && index !== void 0 ? index : -1);
};
Selection.prototype.isRangeSelected = function (fromIndex, count) {
if (count === 0) {
return false;
}
var endIndex = fromIndex + count;
for (var i = fromIndex; i < endIndex; i++) {
if (!this.isIndexSelected(i)) {
return false;
}
}
return true;
};
Selection.prototype.isAllSelected = function () {
var selectableCount = this._items.length - this._unselectableCount;
// In single mode, we can only have a max of 1 item.
if (this.mode === SelectionMode.single) {
selectableCount = Math.min(selectableCount, 1);
}
return ((this.count > 0 && this._isAllSelected && this._exemptedCount === 0) ||
(!this._isAllSelected && this._exemptedCount === selectableCount && selectableCount > 0));
};
Selection.prototype.isKeySelected = function (key) {
var index = this._keyToIndexMap[key];
return this.isIndexSelected(index);
};
Selection.prototype.isIndexSelected = function (index) {
return !!((this.count > 0 && this._isAllSelected && !this._exemptedIndices[index] && !this._unselectableIndices[index]) ||
(!this._isAllSelected && this._exemptedIndices[index]));
};
Selection.prototype.setAllSelected = function (isAllSelected) {
if (isAllSelected && this.mode !== SelectionMode.multiple) {
return;
}
var selectableCount = this._items ? this._items.length - this._unselectableCount : 0;
this.setChangeEvents(false);
if (selectableCount > 0 && (this._exemptedCount > 0 || isAllSelected !== this._isAllSelected)) {
this._exemptedIndices = {};
if (isAllSelected !== this._isAllSelected || this._exemptedCount > 0) {
this._exemptedCount = 0;
this._isAllSelected = isAllSelected;
this._change();
}
this._updateCount();
}
this.setChangeEvents(true);
};
Selection.prototype.setKeySelected = function (key, isSelected, shouldAnchor) {
var index = this._keyToIndexMap[key];
if (index >= 0) {
this.setIndexSelected(index, isSelected, shouldAnchor);
}
};
Selection.prototype.setIndexSelected = function (index, isSelected, shouldAnchor) {
if (this.mode === SelectionMode.none) {
return;
}
// Clamp the index.
index = Math.min(Math.max(0, index), this._items.length - 1);
// No-op on out of bounds selections.
if (index < 0 || index >= this._items.length) {
return;
}
this.setChangeEvents(false);
var isExempt = this._exemptedIndices[index];
var canSelect = !this._unselectableIndices[index];
if (canSelect) {
if (isSelected && this.mode === SelectionMode.single) {
// If this is single-select, the previous selection should be removed.
this._setAllSelected(false, true);
}
// Determine if we need to remove the exemption.
if (isExempt && ((isSelected && this._isAllSelected) || (!isSelected && !this._isAllSelected))) {
delete this._exemptedIndices[index];
this._exemptedCount--;
}
// Determine if we need to add the exemption.
if (!isExempt && ((isSelected && !this._isAllSelected) || (!isSelected && this._isAllSelected))) {
this._exemptedIndices[index] = true;
this._exemptedCount++;
}
if (shouldAnchor) {
this._anchoredIndex = index;
}
}
this._updateCount();
this.setChangeEvents(true);
};
Selection.prototype.setRangeSelected = function (fromIndex, count, isSelected, shouldAnchor) {
if (this.mode === SelectionMode.none) {
return;
}
// Clamp the index.
fromIndex = Math.min(Math.max(0, fromIndex), this._items.length - 1);
// Clamp the range.
count = Math.min(Math.max(0, count), this._items.length - fromIndex);
// No-op on out of bounds selections.
if (fromIndex < 0 || fromIndex >= this._items.length || count === 0) {
return;
}
this.setChangeEvents(false);
var anchorIndex = this._anchoredIndex || 0;
var startIndex = fromIndex;
var endIndex = fromIndex + count - 1;
var newAnchorIndex = anchorIndex >= endIndex ? startIndex : endIndex;
for (; startIndex <= endIndex; startIndex++) {
this.setIndexSelected(startIndex, isSelected, shouldAnchor ? startIndex === newAnchorIndex : false);
}
this.setChangeEvents(true);
};
Selection.prototype.selectToKey = function (key, clearSelection) {
this.selectToIndex(this._keyToIndexMap[key], clearSelection);
};
Selection.prototype.selectToRange = function (fromIndex, count, clearSelection) {
if (this.mode === SelectionMode.none) {
return;
}
if (this.mode === SelectionMode.single) {
if (count === 1) {
this.setIndexSelected(fromIndex, true, true);
}
return;
}
var anchorIndex = this._anchoredIndex || 0;
var startIndex = Math.min(fromIndex, anchorIndex);
var endIndex = Math.max(fromIndex + count - 1, anchorIndex);
this.setChangeEvents(false);
if (clearSelection) {
this._setAllSelected(false, true);
}
for (; startIndex <= endIndex; startIndex++) {
this.setIndexSelected(startIndex, true, false);
}
this.setChangeEvents(true);
};
Selection.prototype.selectToIndex = function (index, clearSelection) {
if (this.mode === SelectionMode.none) {
return;
}
if (this.mode === SelectionMode.single) {
this.setIndexSelected(index, true, true);
return;
}
var anchorIndex = this._anchoredIndex || 0;
var startIndex = Math.min(index, anchorIndex);
var endIndex = Math.max(index, anchorIndex);
this.setChangeEvents(false);
if (clearSelection) {
this._setAllSelected(false, true);
}
for (; startIndex <= endIndex; startIndex++) {
this.setIndexSelected(startIndex, true, false);
}
this.setChangeEvents(true);
};
Selection.prototype.toggleAllSelected = function () {
this.setAllSelected(!this.isAllSelected());
};
Selection.prototype.toggleKeySelected = function (key) {
this.setKeySelected(key, !this.isKeySelected(key), true);
};
Selection.prototype.toggleIndexSelected = function (index) {
this.setIndexSelected(index, !this.isIndexSelected(index), true);
};
Selection.prototype.toggleRangeSelected = function (fromIndex, count) {
if (this.mode === SelectionMode.none) {
return;
}
var isRangeSelected = this.isRangeSelected(fromIndex, count);
var endIndex = fromIndex + count;
if (this.mode === SelectionMode.single && count > 1) {
return;
}
this.setChangeEvents(false);
for (var i = fromIndex; i < endIndex; i++) {
this.setIndexSelected(i, !isRangeSelected, false);
}
this.setChangeEvents(true);
};
Selection.prototype._updateCount = function (preserveModalState) {
if (preserveModalState === void 0) { preserveModalState = false; }
var count = this.getSelectedCount();
if (count !== this.count) {
this.count = count;
this._change();
}
if (!this.count && !preserveModalState) {
this.setModal(false);
}
};
Selection.prototype._setAllSelected = function (isAllSelected, preserveModalState) {
if (preserveModalState === void 0) { preserveModalState = false; }
if (isAllSelected && this.mode !== SelectionMode.multiple) {
return;
}
var selectableCount = this._items ? this._items.length - this._unselectableCount : 0;
this.setChangeEvents(false);
if (selectableCount > 0 && (this._exemptedCount > 0 || isAllSelected !== this._isAllSelected)) {
this._exemptedIndices = {};
if (isAllSelected !== this._isAllSelected || this._exemptedCount > 0) {
this._exemptedCount = 0;
this._isAllSelected = isAllSelected;
this._change();
}
this._updateCount(preserveModalState);
}
this.setChangeEvents(true);
};
Selection.prototype._change = function () {
if (this._changeEventSuppressionCount === 0) {
this._selectedItems = null;
this._selectedIndices = undefined;
EventGroup.raise(this, SELECTION_CHANGE);
if (this._onSelectionChanged) {
this._onSelectionChanged();
}
}
else {
this._hasChanged = true;
}
};
return Selection;
}());
export { Selection };
function defaultGetKey(item, index) {
// 0 may be used as a key
var _a = (item || {}).key, key = _a === void 0 ? "" + index : _a;
return key;
}
//# sourceMappingURL=Selection.js.map