multi-list-selection
Version:
Library for managing selections across multiple lists
201 lines (185 loc) • 8.46 kB
JavaScript
'use strict';
'use babel';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function valueEqualityIndexOf(list, targetItem, equalityPredicate) {
for (var index = 0; index < list.length; index++) {
var item = list[index];
if (equalityPredicate(item, targetItem)) {
return index;
}
}
return -1;
}
var MultiList = function () {
function MultiList(lists, equalityPredicate) {
_classCallCheck(this, MultiList);
this.lists = lists;
this.equalityPredicate = equalityPredicate || function (a, b) {
return a === b;
};
this.selectedListIndex = 0;
this.selectedItemIndexesForList = Array(lists.length).fill(0);
}
_createClass(MultiList, [{
key: 'getSelectedListIndex',
value: function getSelectedListIndex() {
return this.selectedListIndex;
}
}, {
key: 'getSelectedItemIndexForList',
value: function getSelectedItemIndexForList(listIndex) {
if (listIndex === undefined) throw new RangeError();
return this.selectedItemIndexesForList[listIndex];
}
}, {
key: 'getSelectedItemForList',
value: function getSelectedItemForList(listIndex) {
if (listIndex === undefined) throw new RangeError();
return this.lists[listIndex][this.getSelectedItemIndexForList(listIndex)];
}
}, {
key: 'getSelectedItem',
value: function getSelectedItem() {
var itemIndex = this.getSelectedItemIndexForList(this.selectedListIndex);
return this.lists[this.selectedListIndex][itemIndex];
}
}, {
key: 'getListAtIndex',
value: function getListAtIndex(listIndex) {
return this.lists[listIndex];
}
}, {
key: 'selectListAtIndex',
value: function selectListAtIndex(listIndex) {
this.selectedListIndex = listIndex;
}
}, {
key: 'selectItemAtLocation',
value: function selectItemAtLocation(_ref) {
var _ref2 = _slicedToArray(_ref, 2);
var listIndex = _ref2[0];
var itemIndex = _ref2[1];
this.selectListAtIndex(listIndex);
this.selectedItemIndexesForList[listIndex] = itemIndex;
}
}, {
key: 'getGlobalItemIndex',
value: function getGlobalItemIndex(_ref3) {
var _ref4 = _slicedToArray(_ref3, 2);
var localListIndex = _ref4[0];
var localItemIndex = _ref4[1];
var globalIndex = 0;
for (var i = 0; i < localListIndex; i++) {
globalIndex += this.lists[i].length;
}
return globalIndex + localItemIndex;
}
}, {
key: 'getLocalItemLocation',
value: function getLocalItemLocation(globalIndex) {
if (globalIndex < 0) throw new RangeError('Index ' + globalIndex + ' out of range');
var totalLength = this.lists.reduce(function (totalLength, list) {
return totalLength + list.length;
}, 0);
if (totalLength < globalIndex) throw new RangeError('Index ' + globalIndex + ' out of range');
var listLengths = this.lists.map(function (list) {
return list.length;
});
var count = 0;
var currentListIndex = 0;
while (count + listLengths[currentListIndex] <= globalIndex) {
count += listLengths[currentListIndex];
currentListIndex++;
}
var localItemIndex = globalIndex - count;
return [currentListIndex, localItemIndex];
}
}, {
key: 'selectItem',
value: function selectItem(item) {
var flattenedList = this.lists.reduce(function (acc, list) {
return acc.concat(list);
}, []);
var globalIndex = valueEqualityIndexOf(flattenedList, item, this.equalityPredicate);
this.selectItemAtLocation(this.getLocalItemLocation(globalIndex));
}
}, {
key: 'moveListSelection',
value: function moveListSelection(steps) {
var newListIndex = this.selectedListIndex + steps;
var listCount = this.lists.length;
newListIndex = newListIndex % listCount;
if (newListIndex < 0) {
this.selectedListIndex = listCount - 1;
this.moveListSelection(newListIndex + 1);
} else {
this.selectListAtIndex(newListIndex);
}
}
}, {
key: 'moveItemSelection',
value: function moveItemSelection(steps) {
var listIndex = this.selectedListIndex;
var localItemIndex = this.getSelectedItemIndexForList(listIndex);
var globalIndex = this.getGlobalItemIndex([listIndex, localItemIndex]);
var totalLength = this.lists.reduce(function (totalLength, list) {
return totalLength + list.length;
}, 0);
var newGlobalIndex = (globalIndex + steps) % totalLength;
if (newGlobalIndex < 0) {
newGlobalIndex = totalLength + newGlobalIndex;
}
this.selectItemAtLocation(this.getLocalItemLocation(newGlobalIndex));
}
}, {
key: 'updateLists',
value: function updateLists(newLists) {
if (newLists.length !== this.lists.length) {
throw new Error('Number or lists not equal');
}
for (var listIndex = 0; listIndex < newLists.length; listIndex++) {
var itemIndex = this.selectedItemIndexesForList[listIndex];
var item = this.lists[listIndex][itemIndex];
var newIndex = valueEqualityIndexOf(newLists[listIndex], item, this.equalityPredicate);
if (newIndex > -1) {
// if item is still present in list, return its new index
this.selectedItemIndexesForList[listIndex] = newIndex;
} else if (newLists[listIndex].length > itemIndex) {
// else item not present, but if there is another item in the same location, return current index
this.selectedItemIndexesForList[listIndex] = itemIndex;
} else if (newLists[listIndex].length) {
// else nothing at location, if there are still items in the current list, return last item
this.selectedItemIndexesForList[listIndex] = newLists[listIndex].length - 1;
} else {
if (this.getSelectedListIndex() === listIndex) {
// else list is empty, and if current list is the selected list...
if (newLists[listIndex + 1] && newLists[listIndex + 1].length) {
// ... and there is an element in the next list, set the first item as selected
this.selectItemAtLocation([listIndex + 1, 0]);
} else if (newLists[listIndex - 1] && newLists[listIndex - 1].length) {
// if the next list is empty but the previous list is non-empty, set the last item as selected
this.selectItemAtLocation([listIndex - 1, newLists[listIndex - 1].length - 1]);
}
this.selectedItemIndexesForList[listIndex] = 0;
}
}
}
this.lists = newLists;
}
}, {
key: 'toObject',
value: function toObject() {
var lists = this.lists;
var selectedListIndex = this.selectedListIndex;
var selectedItemIndexesForList = this.selectedItemIndexesForList;
return { lists: lists, selectedListIndex: selectedListIndex, selectedItemIndexesForList: selectedItemIndexesForList };
}
}]);
return MultiList;
}();
exports.default = MultiList;