Remove redux store in replace for hand-rolled replacement design for in-place updates of the state. Avoids a lot of data copying, and reduces bundle size

This commit is contained in:
Xon 2024-08-12 17:34:43 +08:00
commit 88db9639b0
51 changed files with 1301 additions and 3686 deletions

View file

@ -1,6 +1,6 @@
# Choices.js [![Actions Status](https://github.com/jshjohnson/Choices/workflows/Build%20and%20test/badge.svg)](https://github.com/jshjohnson/Choices/actions) [![Actions Status](https://github.com/jshjohnson/Choices/workflows/Bundle%20size%20checks/badge.svg)](https://github.com/jshjohnson/Choices/actions) [![npm](https://img.shields.io/npm/v/choices.js.svg)](https://www.npmjs.com/package/choices.js)
A vanilla, lightweight (~20.8kb gzipped 🎉), configurable select box/text input plugin. Similar to Select2 and Selectize but without the jQuery dependency.
A vanilla, lightweight (~19.9kb gzipped 🎉), configurable select box/text input plugin. Similar to Select2 and Selectize but without the jQuery dependency.
[Demo](https://choices-js.github.io/Choices/)

14
package-lock.json generated
View file

@ -9,8 +9,7 @@
"version": "11.0.0-rc5",
"license": "MIT",
"dependencies": {
"fuse.js": "^7.0.0",
"redux": "^4.2.0"
"fuse.js": "^7.0.0"
},
"devDependencies": {
"@babel/cli": "^7.24.8",
@ -1887,6 +1886,7 @@
"version": "7.25.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz",
"integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==",
"dev": true,
"license": "MIT",
"dependencies": {
"regenerator-runtime": "^0.14.0"
@ -12241,15 +12241,6 @@
"node": ">=8.10.0"
}
},
"node_modules/redux": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
"integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.9.2"
}
},
"node_modules/regenerate": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
@ -12274,6 +12265,7 @@
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
"dev": true,
"license": "MIT"
},
"node_modules/regenerator-transform": {

View file

@ -131,8 +131,7 @@
"vitest": "^2.0.5"
},
"dependencies": {
"fuse.js": "^7.0.0",
"redux": "^4.2.0"
"fuse.js": "^7.0.0"
},
"npmName": "choices.js",
"npmFileMap": [

View file

@ -105,14 +105,6 @@
highlighted: highlighted,
}); };
var clearAll = function () { return ({
type: "CLEAR_ALL" /* ActionType.CLEAR_ALL */,
}); };
var setTxn = function (txn) { return ({
type: "SET_TRANSACTION" /* ActionType.SET_TRANSACTION */,
txn: txn,
}); };
/* eslint-disable @typescript-eslint/no-explicit-any */
var getRandomNumber = function (min, max) {
return Math.floor(Math.random() * (max - min) + min);
@ -240,9 +232,6 @@
});
return element.dispatchEvent(event);
};
var cloneObject = function (obj) {
return obj !== undefined ? JSON.parse(JSON.stringify(obj)) : undefined;
};
/**
* Returns an array of keys present on the first but missing on the second object
*/
@ -980,447 +969,85 @@
var ObjectsInConfig = ['fuseOptions', 'classNames'];
/**
* Adapted from React: https://github.com/facebook/react/blob/master/packages/shared/formatProdErrorMessage.js
*
* Do not require this module directly! Use normal throw error calls. These messages will be replaced with error codes
* during build.
* @param {number} code
*/
function formatProdErrorMessage(code) {
return "Minified Redux error #" + code + "; visit https://redux.js.org/Errors?code=" + code + " for the full message or " + 'use the non-minified dev environment for full errors. ';
}
// Inlined version of the `symbol-observable` polyfill
var $$observable = function () {
return typeof Symbol === 'function' && Symbol.observable || '@@observable';
}();
/**
* These are private action types reserved by Redux.
* For any unknown actions, you must return the current state.
* If the current state is undefined, you must return the initial state.
* Do not reference these action types directly in your code.
*/
var randomString = function randomString() {
return Math.random().toString(36).substring(7).split('').join('.');
};
var ActionTypes = {
INIT: "@@redux/INIT" + randomString(),
REPLACE: "@@redux/REPLACE" + randomString(),
PROBE_UNKNOWN_ACTION: function PROBE_UNKNOWN_ACTION() {
return "@@redux/PROBE_UNKNOWN_ACTION" + randomString();
}
};
/**
* @param {any} obj The object to inspect.
* @returns {boolean} True if the argument appears to be a plain object.
*/
function isPlainObject(obj) {
if (typeof obj !== 'object' || obj === null) return false;
var proto = obj;
while (Object.getPrototypeOf(proto) !== null) {
proto = Object.getPrototypeOf(proto);
}
return Object.getPrototypeOf(obj) === proto;
}
/**
* @deprecated
*
* **We recommend using the `configureStore` method
* of the `@reduxjs/toolkit` package**, which replaces `createStore`.
*
* Redux Toolkit is our recommended approach for writing Redux logic today,
* including store setup, reducers, data fetching, and more.
*
* **For more details, please read this Redux docs page:**
* **https://redux.js.org/introduction/why-rtk-is-redux-today**
*
* `configureStore` from Redux Toolkit is an improved version of `createStore` that
* simplifies setup and helps avoid common bugs.
*
* You should not be using the `redux` core package by itself today, except for learning purposes.
* The `createStore` method from the core `redux` package will not be removed, but we encourage
* all users to migrate to using Redux Toolkit for all Redux code.
*
* If you want to use `createStore` without this visual deprecation warning, use
* the `legacy_createStore` import instead:
*
* `import { legacy_createStore as createStore} from 'redux'`
*
*/
function createStore(reducer, preloadedState, enhancer) {
var _ref2;
if (typeof preloadedState === 'function' && typeof enhancer === 'function' || typeof enhancer === 'function' && typeof arguments[3] === 'function') {
throw new Error(formatProdErrorMessage(0) );
}
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState;
preloadedState = undefined;
}
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error(formatProdErrorMessage(1) );
}
return enhancer(createStore)(reducer, preloadedState);
}
if (typeof reducer !== 'function') {
throw new Error(formatProdErrorMessage(2) );
}
var currentReducer = reducer;
var currentState = preloadedState;
var currentListeners = [];
var nextListeners = currentListeners;
var isDispatching = false;
/**
* This makes a shallow copy of currentListeners so we can use
* nextListeners as a temporary list while dispatching.
*
* This prevents any bugs around consumers calling
* subscribe/unsubscribe in the middle of a dispatch.
*/
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice();
}
}
/**
* Reads the state tree managed by the store.
*
* @returns {any} The current state tree of your application.
*/
function getState() {
if (isDispatching) {
throw new Error(formatProdErrorMessage(3) );
}
return currentState;
}
/**
* Adds a change listener. It will be called any time an action is dispatched,
* and some part of the state tree may potentially have changed. You may then
* call `getState()` to read the current state tree inside the callback.
*
* You may call `dispatch()` from a change listener, with the following
* caveats:
*
* 1. The subscriptions are snapshotted just before every `dispatch()` call.
* If you subscribe or unsubscribe while the listeners are being invoked, this
* will not have any effect on the `dispatch()` that is currently in progress.
* However, the next `dispatch()` call, whether nested or not, will use a more
* recent snapshot of the subscription list.
*
* 2. The listener should not expect to see all state changes, as the state
* might have been updated multiple times during a nested `dispatch()` before
* the listener is called. It is, however, guaranteed that all subscribers
* registered before the `dispatch()` started will be called with the latest
* state by the time it exits.
*
* @param {Function} listener A callback to be invoked on every dispatch.
* @returns {Function} A function to remove this change listener.
*/
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error(formatProdErrorMessage(4) );
}
if (isDispatching) {
throw new Error(formatProdErrorMessage(5) );
}
var isSubscribed = true;
ensureCanMutateNextListeners();
nextListeners.push(listener);
return function unsubscribe() {
if (!isSubscribed) {
return;
}
if (isDispatching) {
throw new Error(formatProdErrorMessage(6) );
}
isSubscribed = false;
ensureCanMutateNextListeners();
var index = nextListeners.indexOf(listener);
nextListeners.splice(index, 1);
currentListeners = null;
};
}
/**
* Dispatches an action. It is the only way to trigger a state change.
*
* The `reducer` function, used to create the store, will be called with the
* current state tree and the given `action`. Its return value will
* be considered the **next** state of the tree, and the change listeners
* will be notified.
*
* The base implementation only supports plain object actions. If you want to
* dispatch a Promise, an Observable, a thunk, or something else, you need to
* wrap your store creating function into the corresponding middleware. For
* example, see the documentation for the `redux-thunk` package. Even the
* middleware will eventually dispatch plain object actions using this method.
*
* @param {Object} action A plain object representing what changed. It is
* a good idea to keep actions serializable so you can record and replay user
* sessions, or use the time travelling `redux-devtools`. An action must have
* a `type` property which may not be `undefined`. It is a good idea to use
* string constants for action types.
*
* @returns {Object} For convenience, the same action object you dispatched.
*
* Note that, if you use a custom middleware, it may wrap `dispatch()` to
* return something else (for example, a Promise you can await).
*/
function dispatch(action) {
if (!isPlainObject(action)) {
throw new Error(formatProdErrorMessage(7) );
}
if (typeof action.type === 'undefined') {
throw new Error(formatProdErrorMessage(8) );
}
if (isDispatching) {
throw new Error(formatProdErrorMessage(9) );
}
try {
isDispatching = true;
currentState = currentReducer(currentState, action);
} finally {
isDispatching = false;
}
var listeners = currentListeners = nextListeners;
for (var i = 0; i < listeners.length; i++) {
var listener = listeners[i];
listener();
}
return action;
}
/**
* Replaces the reducer currently used by the store to calculate the state.
*
* You might need this if your app implements code splitting and you want to
* load some of the reducers dynamically. You might also need this if you
* implement a hot reloading mechanism for Redux.
*
* @param {Function} nextReducer The reducer for the store to use instead.
* @returns {void}
*/
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error(formatProdErrorMessage(10) );
}
currentReducer = nextReducer; // This action has a similiar effect to ActionTypes.INIT.
// Any reducers that existed in both the new and old rootReducer
// will receive the previous state. This effectively populates
// the new state tree with any relevant data from the old one.
dispatch({
type: ActionTypes.REPLACE
});
}
/**
* Interoperability point for observable/reactive libraries.
* @returns {observable} A minimal observable of state changes.
* For more information, see the observable proposal:
* https://github.com/tc39/proposal-observable
*/
function observable() {
var _ref;
var outerSubscribe = subscribe;
return _ref = {
/**
* The minimal observable subscription method.
* @param {Object} observer Any object that can be used as an observer.
* The observer object should have a `next` method.
* @returns {subscription} An object with an `unsubscribe` method that can
* be used to unsubscribe the observable from the store, and prevent further
* emission of values from the observable.
*/
subscribe: function subscribe(observer) {
if (typeof observer !== 'object' || observer === null) {
throw new Error(formatProdErrorMessage(11) );
}
function observeState() {
if (observer.next) {
observer.next(getState());
}
}
observeState();
var unsubscribe = outerSubscribe(observeState);
return {
unsubscribe: unsubscribe
};
}
}, _ref[$$observable] = function () {
return this;
}, _ref;
} // When a store is created, an "INIT" action is dispatched so that every
// reducer returns their initial state. This effectively populates
// the initial state tree.
dispatch({
type: ActionTypes.INIT
});
return _ref2 = {
dispatch: dispatch,
subscribe: subscribe,
getState: getState,
replaceReducer: replaceReducer
}, _ref2[$$observable] = observable, _ref2;
}
function assertReducerShape(reducers) {
Object.keys(reducers).forEach(function (key) {
var reducer = reducers[key];
var initialState = reducer(undefined, {
type: ActionTypes.INIT
});
if (typeof initialState === 'undefined') {
throw new Error(formatProdErrorMessage(12) );
}
if (typeof reducer(undefined, {
type: ActionTypes.PROBE_UNKNOWN_ACTION()
}) === 'undefined') {
throw new Error(formatProdErrorMessage(13) );
}
});
}
/**
* Turns an object whose values are different reducer functions, into a single
* reducer function. It will call every child reducer, and gather their results
* into a single state object, whose keys correspond to the keys of the passed
* reducer functions.
*
* @param {Object} reducers An object whose values correspond to different
* reducer functions that need to be combined into one. One handy way to obtain
* it is to use ES6 `import * as reducers` syntax. The reducers may never return
* undefined for any action. Instead, they should return their initial state
* if the state passed to them was undefined, and the current state for any
* unrecognized action.
*
* @returns {Function} A reducer function that invokes every reducer inside the
* passed object, and builds a state object with the same shape.
*/
function combineReducers(reducers) {
var reducerKeys = Object.keys(reducers);
var finalReducers = {};
for (var i = 0; i < reducerKeys.length; i++) {
var key = reducerKeys[i];
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key];
}
}
var finalReducerKeys = Object.keys(finalReducers); // This is used to make sure we don't warn about the same
var shapeAssertionError;
try {
assertReducerShape(finalReducers);
} catch (e) {
shapeAssertionError = e;
}
return function combination(state, action) {
if (state === void 0) {
state = {};
}
if (shapeAssertionError) {
throw shapeAssertionError;
}
var hasChanged = false;
var nextState = {};
for (var _i = 0; _i < finalReducerKeys.length; _i++) {
var _key = finalReducerKeys[_i];
var reducer = finalReducers[_key];
var previousStateForKey = state[_key];
var nextStateForKey = reducer(previousStateForKey, action);
if (typeof nextStateForKey === 'undefined') {
action && action.type;
throw new Error(formatProdErrorMessage(14) );
}
nextState[_key] = nextStateForKey;
hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
}
hasChanged = hasChanged || finalReducerKeys.length !== Object.keys(state).length;
return hasChanged ? nextState : state;
};
}
function items(state, action) {
if (state === void 0) { state = []; }
if (action === void 0) { action = {}; }
function items(s, action) {
var state = s;
var update = false;
switch (action.type) {
case "ADD_ITEM" /* ActionType.ADD_ITEM */: {
var item = action.item;
if (!item.id) {
return state;
if (item.id) {
item.selected = true;
var el = item.element;
if (el) {
el.selected = true;
el.setAttribute('selected', '');
}
update = true;
state.push(item);
state.forEach(function (obj) {
// eslint-disable-next-line no-param-reassign
obj.highlighted = false;
});
}
item.selected = true;
var el = item.element;
if (el) {
el.selected = true;
el.setAttribute('selected', '');
}
return __spreadArray(__spreadArray([], state, true), [item], false).map(function (obj) {
var choice = obj;
choice.highlighted = false;
return choice;
});
break;
}
case "REMOVE_ITEM" /* ActionType.REMOVE_ITEM */: {
var item_1 = action.item;
if (!item_1.id) {
return state;
if (item_1.id) {
item_1.selected = false;
var el = item_1.element;
if (el) {
el.selected = false;
el.removeAttribute('selected');
}
update = true;
state = state.filter(function (choice) { return choice.id !== item_1.id; });
}
item_1.selected = false;
var el = item_1.element;
if (el) {
el.selected = false;
el.removeAttribute('selected');
}
return state.filter(function (choice) { return choice.id !== item_1.id; });
break;
}
case "REMOVE_CHOICE" /* ActionType.REMOVE_CHOICE */: {
var choice_1 = action.choice;
return state.filter(function (item) { return item.id !== choice_1.id; });
update = true;
state = state.filter(function (item) { return item.id !== choice_1.id; });
break;
}
case "HIGHLIGHT_ITEM" /* ActionType.HIGHLIGHT_ITEM */: {
var highlightItemAction_1 = action;
return state.map(function (obj) {
update = true;
state.forEach(function (obj) {
var item = obj;
if (item.id === highlightItemAction_1.item.id) {
item.highlighted = highlightItemAction_1.highlighted;
}
return item;
});
}
default: {
return state;
break;
}
}
return { state: state, update: update };
}
function groups(state, action) {
if (state === void 0) { state = []; }
if (action === void 0) { action = {}; }
function groups(s, action) {
var state = s;
var update = false;
switch (action.type) {
case "ADD_GROUP" /* ActionType.ADD_GROUP */: {
var addGroupAction = action;
return __spreadArray(__spreadArray([], state, true), [addGroupAction.group], false);
update = true;
state.push(addGroupAction.group);
break;
}
case "CLEAR_CHOICES" /* ActionType.CLEAR_CHOICES */: {
return [];
}
default: {
return state;
update = true;
state = [];
break;
}
}
return { state: state, update: update };
}
function choices(state, action) {
if (state === void 0) { state = []; }
if (action === void 0) { action = {}; }
function choices(s, action) {
var state = s;
var update = false;
switch (action.type) {
case "ADD_CHOICE" /* ActionType.ADD_CHOICE */: {
var choice = action.choice;
@ -1429,36 +1056,41 @@
A selected choice has been added to the passed input's value (added as an item)
An active choice appears within the choice dropdown
*/
return __spreadArray(__spreadArray([], state, true), [choice], false);
state.push(choice);
update = true;
break;
}
case "REMOVE_CHOICE" /* ActionType.REMOVE_CHOICE */: {
var choice_1 = action.choice;
return state.filter(function (obj) { return obj.id !== choice_1.id; });
update = true;
state = state.filter(function (obj) { return obj.id !== choice_1.id; });
break;
}
case "ADD_ITEM" /* ActionType.ADD_ITEM */: {
var item = action.item;
// trigger a rebuild of the choices list as the item can not be added multiple times
if (item.id && item.selected) {
return __spreadArray([], state, true);
update = true;
}
return state;
break;
}
case "REMOVE_ITEM" /* ActionType.REMOVE_ITEM */: {
var item = action.item;
// trigger a rebuild of the choices list as the item can be added
if (item.id && !item.selected) {
return __spreadArray([], state, true);
update = true;
}
return state;
break;
}
case "FILTER_CHOICES" /* ActionType.FILTER_CHOICES */: {
var results = action.results;
update = true;
// avoid O(n^2) algorithm complexity when searching/filtering choices
var scoreLookup_1 = [];
results.forEach(function (result) {
scoreLookup_1[result.item.id] = result;
});
return state.map(function (obj) {
state.forEach(function (obj) {
var choice = obj;
var result = scoreLookup_1[choice.id];
if (result !== undefined) {
@ -1471,102 +1103,110 @@
choice.rank = 0;
choice.active = false;
}
return choice;
});
break;
}
case "ACTIVATE_CHOICES" /* ActionType.ACTIVATE_CHOICES */: {
var active_1 = action.active;
return state.map(function (obj) {
update = true;
state.forEach(function (obj) {
var choice = obj;
choice.active = active_1;
return choice;
});
break;
}
case "CLEAR_CHOICES" /* ActionType.CLEAR_CHOICES */: {
return [];
}
default: {
return state;
update = true;
state = [];
break;
}
}
return { state: state, update: update };
}
var general = function (state, action) {
if (state === void 0) { state = 0; }
if (action === void 0) { action = {}; }
switch (action.type) {
case "SET_TRANSACTION" /* ActionType.SET_TRANSACTION */: {
if (action.txn) {
return state + 1;
}
return Math.max(0, state - 1);
}
default: {
return state;
}
}
};
var defaultState = {
groups: [],
items: [],
choices: [],
txn: 0,
};
var appReducer = combineReducers({
items: items,
var reducers = {
groups: groups,
items: items,
choices: choices,
txn: general,
});
var rootReducer = function (passedState, action) {
var state = passedState;
// If we are clearing all items, groups and options we reassign
// state and then pass that state to our proper reducer. This isn't
// mutating our actual state
// See: http://stackoverflow.com/a/35641992
if (action.type === "CLEAR_ALL" /* ActionType.CLEAR_ALL */) {
// preserve the txn state as to allow withTxn to work
var paused = state.txn;
state = cloneObject(defaultState);
state.txn = paused;
}
return appReducer(state, action);
};
/* eslint-disable @typescript-eslint/no-explicit-any */
var Store = /** @class */ (function () {
function Store() {
this._store = createStore(rootReducer, window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__());
this._store = this.defaultState;
this._listeners = [];
this._txn = 0;
}
/**
* Subscribe store to function call (wrapped Redux method)
*/
Store.prototype.subscribe = function (onChange) {
this._store.subscribe(onChange);
Object.defineProperty(Store.prototype, "defaultState", {
// eslint-disable-next-line class-methods-use-this
get: function () {
return {
groups: [],
items: [],
choices: [],
};
},
enumerable: false,
configurable: true
});
// eslint-disable-next-line class-methods-use-this
Store.prototype.changeSet = function (init) {
return {
groups: init,
items: init,
choices: init,
};
};
Store.prototype.resetStore = function () {
this._store = this.defaultState;
var changes = this.changeSet(true);
this._listeners.forEach(function (l) { return l(changes); });
};
Store.prototype.subscribe = function (onChange) {
this._listeners.push(onChange);
};
/**
* Dispatch event to store (wrapped Redux method)
*/
Store.prototype.dispatch = function (action) {
this._store.dispatch(action);
var state = this._store;
var hasChanges = false;
var changes = this._outstandingChanges || this.changeSet(false);
Object.keys(reducers).forEach(function (key) {
var stateUpdate = reducers[key](state[key], action);
if (stateUpdate.update) {
hasChanges = true;
changes[key] = true;
state[key] = stateUpdate.state;
}
});
if (hasChanges) {
if (this._txn) {
this._outstandingChanges = changes;
}
else {
this._listeners.forEach(function (l) { return l(changes); });
}
}
};
Store.prototype.withTxn = function (func) {
this._store.dispatch(setTxn(true));
this._txn++;
try {
func();
}
finally {
this._store.dispatch(setTxn(false));
this._txn = Math.max(0, this._txn - 1);
if (!this._txn) {
var changeSet_1 = this._outstandingChanges;
if (changeSet_1) {
this._outstandingChanges = undefined;
this._listeners.forEach(function (l) { return l(changeSet_1); });
}
}
}
};
Object.defineProperty(Store.prototype, "state", {
/**
* Get store object (wrapping Redux method)
* Get store object
*/
get: function () {
return this._store.getState();
return this._store;
},
enumerable: false,
configurable: true
@ -1636,10 +1276,10 @@
* Get active groups from store
*/
get: function () {
var _a = this, groups = _a.groups, choices = _a.choices;
return groups.filter(function (group) {
var _this = this;
return this.state.groups.filter(function (group) {
var isActive = group.active && !group.disabled;
var hasActiveOptions = choices.some(function (choice) { return choice.active && !choice.disabled; });
var hasActiveOptions = _this.state.choices.some(function (choice) { return choice.active && !choice.disabled; });
return isActive && hasActiveOptions;
}, []);
},
@ -1647,7 +1287,7 @@
configurable: true
});
Store.prototype.inTxn = function () {
return this.state.txn > 0;
return this._txn > 0;
};
/**
* Get single choice by it's ID
@ -3734,9 +3374,6 @@
}
this.initialised = false;
this._store = new Store();
this._initialState = defaultState;
this._currentState = defaultState;
this._prevState = defaultState;
this._currentValue = '';
this.config.searchEnabled =
(!this._isTextElement && this.config.searchEnabled) ||
@ -3819,8 +3456,7 @@
this._createTemplates();
this._createElements();
this._createStructure();
this._store.subscribe(this._render);
this._render();
this._initStore();
this._addEventListeners();
var shouldDisable = (this._isTextElement && !this.config.addItems) ||
this.passedElement.element.hasAttribute('disabled') ||
@ -3844,6 +3480,7 @@
this.passedElement.reveal();
this.containerOuter.unwrap(this.passedElement.element);
this.clearStore();
this._store._listeners = [];
this._stopSearch();
this._templates = templates;
this.initialised = false;
@ -4249,7 +3886,7 @@
return this;
};
Choices.prototype.clearStore = function () {
this._store.dispatch(clearAll());
this._store.resetStore();
this._lastAddedChoiceId = 0;
this._lastAddedGroupId = 0;
// @todo integrate with Store
@ -4278,15 +3915,12 @@
}
}
};
Choices.prototype._render = function () {
Choices.prototype._render = function (changes) {
if (this._store.inTxn()) {
return;
}
this._currentState = this._store.state;
var shouldRenderItems = this._currentState.items !== this._prevState.items;
var stateChanged = this._currentState.choices !== this._prevState.choices ||
this._currentState.groups !== this._prevState.groups ||
shouldRenderItems;
var shouldRenderItems = changes === null || changes === void 0 ? void 0 : changes.items;
var stateChanged = (changes === null || changes === void 0 ? void 0 : changes.choices) || (changes === null || changes === void 0 ? void 0 : changes.groups) || shouldRenderItems;
if (!stateChanged) {
return;
}
@ -4296,7 +3930,6 @@
if (shouldRenderItems) {
this._renderItems();
}
this._prevState = this._currentState;
};
Choices.prototype._renderChoices = function () {
var _this = this;
@ -5515,7 +5148,6 @@
});
};
Choices.prototype._createStructure = function () {
var _this = this;
// Hide original element
this.passedElement.conceal();
// Wrap input in container preserving DOM ordering
@ -5543,6 +5175,10 @@
}
this._highlightPosition = 0;
this._isSearching = false;
};
Choices.prototype._initStore = function () {
var _this = this;
this._store.subscribe(this._render);
this._store.withTxn(function () {
_this._addPredefinedChoices(_this._presetChoices, _this._isSelectOneElement && !_this._hasNonChoicePlaceholder, false);
});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -99,14 +99,6 @@ var highlightItem = function (item, highlighted) { return ({
highlighted: highlighted,
}); };
var clearAll = function () { return ({
type: "CLEAR_ALL" /* ActionType.CLEAR_ALL */,
}); };
var setTxn = function (txn) { return ({
type: "SET_TRANSACTION" /* ActionType.SET_TRANSACTION */,
txn: txn,
}); };
/* eslint-disable @typescript-eslint/no-explicit-any */
var getRandomNumber = function (min, max) {
return Math.floor(Math.random() * (max - min) + min);
@ -234,9 +226,6 @@ var dispatchEvent = function (element, type, customArgs) {
});
return element.dispatchEvent(event);
};
var cloneObject = function (obj) {
return obj !== undefined ? JSON.parse(JSON.stringify(obj)) : undefined;
};
/**
* Returns an array of keys present on the first but missing on the second object
*/
@ -974,447 +963,85 @@ var DEFAULT_CONFIG = {
var ObjectsInConfig = ['fuseOptions', 'classNames'];
/**
* Adapted from React: https://github.com/facebook/react/blob/master/packages/shared/formatProdErrorMessage.js
*
* Do not require this module directly! Use normal throw error calls. These messages will be replaced with error codes
* during build.
* @param {number} code
*/
function formatProdErrorMessage(code) {
return "Minified Redux error #" + code + "; visit https://redux.js.org/Errors?code=" + code + " for the full message or " + 'use the non-minified dev environment for full errors. ';
}
// Inlined version of the `symbol-observable` polyfill
var $$observable = function () {
return typeof Symbol === 'function' && Symbol.observable || '@@observable';
}();
/**
* These are private action types reserved by Redux.
* For any unknown actions, you must return the current state.
* If the current state is undefined, you must return the initial state.
* Do not reference these action types directly in your code.
*/
var randomString = function randomString() {
return Math.random().toString(36).substring(7).split('').join('.');
};
var ActionTypes = {
INIT: "@@redux/INIT" + randomString(),
REPLACE: "@@redux/REPLACE" + randomString(),
PROBE_UNKNOWN_ACTION: function PROBE_UNKNOWN_ACTION() {
return "@@redux/PROBE_UNKNOWN_ACTION" + randomString();
}
};
/**
* @param {any} obj The object to inspect.
* @returns {boolean} True if the argument appears to be a plain object.
*/
function isPlainObject(obj) {
if (typeof obj !== 'object' || obj === null) return false;
var proto = obj;
while (Object.getPrototypeOf(proto) !== null) {
proto = Object.getPrototypeOf(proto);
}
return Object.getPrototypeOf(obj) === proto;
}
/**
* @deprecated
*
* **We recommend using the `configureStore` method
* of the `@reduxjs/toolkit` package**, which replaces `createStore`.
*
* Redux Toolkit is our recommended approach for writing Redux logic today,
* including store setup, reducers, data fetching, and more.
*
* **For more details, please read this Redux docs page:**
* **https://redux.js.org/introduction/why-rtk-is-redux-today**
*
* `configureStore` from Redux Toolkit is an improved version of `createStore` that
* simplifies setup and helps avoid common bugs.
*
* You should not be using the `redux` core package by itself today, except for learning purposes.
* The `createStore` method from the core `redux` package will not be removed, but we encourage
* all users to migrate to using Redux Toolkit for all Redux code.
*
* If you want to use `createStore` without this visual deprecation warning, use
* the `legacy_createStore` import instead:
*
* `import { legacy_createStore as createStore} from 'redux'`
*
*/
function createStore(reducer, preloadedState, enhancer) {
var _ref2;
if (typeof preloadedState === 'function' && typeof enhancer === 'function' || typeof enhancer === 'function' && typeof arguments[3] === 'function') {
throw new Error(formatProdErrorMessage(0) );
}
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState;
preloadedState = undefined;
}
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error(formatProdErrorMessage(1) );
}
return enhancer(createStore)(reducer, preloadedState);
}
if (typeof reducer !== 'function') {
throw new Error(formatProdErrorMessage(2) );
}
var currentReducer = reducer;
var currentState = preloadedState;
var currentListeners = [];
var nextListeners = currentListeners;
var isDispatching = false;
/**
* This makes a shallow copy of currentListeners so we can use
* nextListeners as a temporary list while dispatching.
*
* This prevents any bugs around consumers calling
* subscribe/unsubscribe in the middle of a dispatch.
*/
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice();
}
}
/**
* Reads the state tree managed by the store.
*
* @returns {any} The current state tree of your application.
*/
function getState() {
if (isDispatching) {
throw new Error(formatProdErrorMessage(3) );
}
return currentState;
}
/**
* Adds a change listener. It will be called any time an action is dispatched,
* and some part of the state tree may potentially have changed. You may then
* call `getState()` to read the current state tree inside the callback.
*
* You may call `dispatch()` from a change listener, with the following
* caveats:
*
* 1. The subscriptions are snapshotted just before every `dispatch()` call.
* If you subscribe or unsubscribe while the listeners are being invoked, this
* will not have any effect on the `dispatch()` that is currently in progress.
* However, the next `dispatch()` call, whether nested or not, will use a more
* recent snapshot of the subscription list.
*
* 2. The listener should not expect to see all state changes, as the state
* might have been updated multiple times during a nested `dispatch()` before
* the listener is called. It is, however, guaranteed that all subscribers
* registered before the `dispatch()` started will be called with the latest
* state by the time it exits.
*
* @param {Function} listener A callback to be invoked on every dispatch.
* @returns {Function} A function to remove this change listener.
*/
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error(formatProdErrorMessage(4) );
}
if (isDispatching) {
throw new Error(formatProdErrorMessage(5) );
}
var isSubscribed = true;
ensureCanMutateNextListeners();
nextListeners.push(listener);
return function unsubscribe() {
if (!isSubscribed) {
return;
}
if (isDispatching) {
throw new Error(formatProdErrorMessage(6) );
}
isSubscribed = false;
ensureCanMutateNextListeners();
var index = nextListeners.indexOf(listener);
nextListeners.splice(index, 1);
currentListeners = null;
};
}
/**
* Dispatches an action. It is the only way to trigger a state change.
*
* The `reducer` function, used to create the store, will be called with the
* current state tree and the given `action`. Its return value will
* be considered the **next** state of the tree, and the change listeners
* will be notified.
*
* The base implementation only supports plain object actions. If you want to
* dispatch a Promise, an Observable, a thunk, or something else, you need to
* wrap your store creating function into the corresponding middleware. For
* example, see the documentation for the `redux-thunk` package. Even the
* middleware will eventually dispatch plain object actions using this method.
*
* @param {Object} action A plain object representing what changed. It is
* a good idea to keep actions serializable so you can record and replay user
* sessions, or use the time travelling `redux-devtools`. An action must have
* a `type` property which may not be `undefined`. It is a good idea to use
* string constants for action types.
*
* @returns {Object} For convenience, the same action object you dispatched.
*
* Note that, if you use a custom middleware, it may wrap `dispatch()` to
* return something else (for example, a Promise you can await).
*/
function dispatch(action) {
if (!isPlainObject(action)) {
throw new Error(formatProdErrorMessage(7) );
}
if (typeof action.type === 'undefined') {
throw new Error(formatProdErrorMessage(8) );
}
if (isDispatching) {
throw new Error(formatProdErrorMessage(9) );
}
try {
isDispatching = true;
currentState = currentReducer(currentState, action);
} finally {
isDispatching = false;
}
var listeners = currentListeners = nextListeners;
for (var i = 0; i < listeners.length; i++) {
var listener = listeners[i];
listener();
}
return action;
}
/**
* Replaces the reducer currently used by the store to calculate the state.
*
* You might need this if your app implements code splitting and you want to
* load some of the reducers dynamically. You might also need this if you
* implement a hot reloading mechanism for Redux.
*
* @param {Function} nextReducer The reducer for the store to use instead.
* @returns {void}
*/
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error(formatProdErrorMessage(10) );
}
currentReducer = nextReducer; // This action has a similiar effect to ActionTypes.INIT.
// Any reducers that existed in both the new and old rootReducer
// will receive the previous state. This effectively populates
// the new state tree with any relevant data from the old one.
dispatch({
type: ActionTypes.REPLACE
});
}
/**
* Interoperability point for observable/reactive libraries.
* @returns {observable} A minimal observable of state changes.
* For more information, see the observable proposal:
* https://github.com/tc39/proposal-observable
*/
function observable() {
var _ref;
var outerSubscribe = subscribe;
return _ref = {
/**
* The minimal observable subscription method.
* @param {Object} observer Any object that can be used as an observer.
* The observer object should have a `next` method.
* @returns {subscription} An object with an `unsubscribe` method that can
* be used to unsubscribe the observable from the store, and prevent further
* emission of values from the observable.
*/
subscribe: function subscribe(observer) {
if (typeof observer !== 'object' || observer === null) {
throw new Error(formatProdErrorMessage(11) );
}
function observeState() {
if (observer.next) {
observer.next(getState());
}
}
observeState();
var unsubscribe = outerSubscribe(observeState);
return {
unsubscribe: unsubscribe
};
}
}, _ref[$$observable] = function () {
return this;
}, _ref;
} // When a store is created, an "INIT" action is dispatched so that every
// reducer returns their initial state. This effectively populates
// the initial state tree.
dispatch({
type: ActionTypes.INIT
});
return _ref2 = {
dispatch: dispatch,
subscribe: subscribe,
getState: getState,
replaceReducer: replaceReducer
}, _ref2[$$observable] = observable, _ref2;
}
function assertReducerShape(reducers) {
Object.keys(reducers).forEach(function (key) {
var reducer = reducers[key];
var initialState = reducer(undefined, {
type: ActionTypes.INIT
});
if (typeof initialState === 'undefined') {
throw new Error(formatProdErrorMessage(12) );
}
if (typeof reducer(undefined, {
type: ActionTypes.PROBE_UNKNOWN_ACTION()
}) === 'undefined') {
throw new Error(formatProdErrorMessage(13) );
}
});
}
/**
* Turns an object whose values are different reducer functions, into a single
* reducer function. It will call every child reducer, and gather their results
* into a single state object, whose keys correspond to the keys of the passed
* reducer functions.
*
* @param {Object} reducers An object whose values correspond to different
* reducer functions that need to be combined into one. One handy way to obtain
* it is to use ES6 `import * as reducers` syntax. The reducers may never return
* undefined for any action. Instead, they should return their initial state
* if the state passed to them was undefined, and the current state for any
* unrecognized action.
*
* @returns {Function} A reducer function that invokes every reducer inside the
* passed object, and builds a state object with the same shape.
*/
function combineReducers(reducers) {
var reducerKeys = Object.keys(reducers);
var finalReducers = {};
for (var i = 0; i < reducerKeys.length; i++) {
var key = reducerKeys[i];
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key];
}
}
var finalReducerKeys = Object.keys(finalReducers); // This is used to make sure we don't warn about the same
var shapeAssertionError;
try {
assertReducerShape(finalReducers);
} catch (e) {
shapeAssertionError = e;
}
return function combination(state, action) {
if (state === void 0) {
state = {};
}
if (shapeAssertionError) {
throw shapeAssertionError;
}
var hasChanged = false;
var nextState = {};
for (var _i = 0; _i < finalReducerKeys.length; _i++) {
var _key = finalReducerKeys[_i];
var reducer = finalReducers[_key];
var previousStateForKey = state[_key];
var nextStateForKey = reducer(previousStateForKey, action);
if (typeof nextStateForKey === 'undefined') {
action && action.type;
throw new Error(formatProdErrorMessage(14) );
}
nextState[_key] = nextStateForKey;
hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
}
hasChanged = hasChanged || finalReducerKeys.length !== Object.keys(state).length;
return hasChanged ? nextState : state;
};
}
function items(state, action) {
if (state === void 0) { state = []; }
if (action === void 0) { action = {}; }
function items(s, action) {
var state = s;
var update = false;
switch (action.type) {
case "ADD_ITEM" /* ActionType.ADD_ITEM */: {
var item = action.item;
if (!item.id) {
return state;
if (item.id) {
item.selected = true;
var el = item.element;
if (el) {
el.selected = true;
el.setAttribute('selected', '');
}
update = true;
state.push(item);
state.forEach(function (obj) {
// eslint-disable-next-line no-param-reassign
obj.highlighted = false;
});
}
item.selected = true;
var el = item.element;
if (el) {
el.selected = true;
el.setAttribute('selected', '');
}
return __spreadArray(__spreadArray([], state, true), [item], false).map(function (obj) {
var choice = obj;
choice.highlighted = false;
return choice;
});
break;
}
case "REMOVE_ITEM" /* ActionType.REMOVE_ITEM */: {
var item_1 = action.item;
if (!item_1.id) {
return state;
if (item_1.id) {
item_1.selected = false;
var el = item_1.element;
if (el) {
el.selected = false;
el.removeAttribute('selected');
}
update = true;
state = state.filter(function (choice) { return choice.id !== item_1.id; });
}
item_1.selected = false;
var el = item_1.element;
if (el) {
el.selected = false;
el.removeAttribute('selected');
}
return state.filter(function (choice) { return choice.id !== item_1.id; });
break;
}
case "REMOVE_CHOICE" /* ActionType.REMOVE_CHOICE */: {
var choice_1 = action.choice;
return state.filter(function (item) { return item.id !== choice_1.id; });
update = true;
state = state.filter(function (item) { return item.id !== choice_1.id; });
break;
}
case "HIGHLIGHT_ITEM" /* ActionType.HIGHLIGHT_ITEM */: {
var highlightItemAction_1 = action;
return state.map(function (obj) {
update = true;
state.forEach(function (obj) {
var item = obj;
if (item.id === highlightItemAction_1.item.id) {
item.highlighted = highlightItemAction_1.highlighted;
}
return item;
});
}
default: {
return state;
break;
}
}
return { state: state, update: update };
}
function groups(state, action) {
if (state === void 0) { state = []; }
if (action === void 0) { action = {}; }
function groups(s, action) {
var state = s;
var update = false;
switch (action.type) {
case "ADD_GROUP" /* ActionType.ADD_GROUP */: {
var addGroupAction = action;
return __spreadArray(__spreadArray([], state, true), [addGroupAction.group], false);
update = true;
state.push(addGroupAction.group);
break;
}
case "CLEAR_CHOICES" /* ActionType.CLEAR_CHOICES */: {
return [];
}
default: {
return state;
update = true;
state = [];
break;
}
}
return { state: state, update: update };
}
function choices(state, action) {
if (state === void 0) { state = []; }
if (action === void 0) { action = {}; }
function choices(s, action) {
var state = s;
var update = false;
switch (action.type) {
case "ADD_CHOICE" /* ActionType.ADD_CHOICE */: {
var choice = action.choice;
@ -1423,36 +1050,41 @@ function choices(state, action) {
A selected choice has been added to the passed input's value (added as an item)
An active choice appears within the choice dropdown
*/
return __spreadArray(__spreadArray([], state, true), [choice], false);
state.push(choice);
update = true;
break;
}
case "REMOVE_CHOICE" /* ActionType.REMOVE_CHOICE */: {
var choice_1 = action.choice;
return state.filter(function (obj) { return obj.id !== choice_1.id; });
update = true;
state = state.filter(function (obj) { return obj.id !== choice_1.id; });
break;
}
case "ADD_ITEM" /* ActionType.ADD_ITEM */: {
var item = action.item;
// trigger a rebuild of the choices list as the item can not be added multiple times
if (item.id && item.selected) {
return __spreadArray([], state, true);
update = true;
}
return state;
break;
}
case "REMOVE_ITEM" /* ActionType.REMOVE_ITEM */: {
var item = action.item;
// trigger a rebuild of the choices list as the item can be added
if (item.id && !item.selected) {
return __spreadArray([], state, true);
update = true;
}
return state;
break;
}
case "FILTER_CHOICES" /* ActionType.FILTER_CHOICES */: {
var results = action.results;
update = true;
// avoid O(n^2) algorithm complexity when searching/filtering choices
var scoreLookup_1 = [];
results.forEach(function (result) {
scoreLookup_1[result.item.id] = result;
});
return state.map(function (obj) {
state.forEach(function (obj) {
var choice = obj;
var result = scoreLookup_1[choice.id];
if (result !== undefined) {
@ -1465,102 +1097,110 @@ function choices(state, action) {
choice.rank = 0;
choice.active = false;
}
return choice;
});
break;
}
case "ACTIVATE_CHOICES" /* ActionType.ACTIVATE_CHOICES */: {
var active_1 = action.active;
return state.map(function (obj) {
update = true;
state.forEach(function (obj) {
var choice = obj;
choice.active = active_1;
return choice;
});
break;
}
case "CLEAR_CHOICES" /* ActionType.CLEAR_CHOICES */: {
return [];
}
default: {
return state;
update = true;
state = [];
break;
}
}
return { state: state, update: update };
}
var general = function (state, action) {
if (state === void 0) { state = 0; }
if (action === void 0) { action = {}; }
switch (action.type) {
case "SET_TRANSACTION" /* ActionType.SET_TRANSACTION */: {
if (action.txn) {
return state + 1;
}
return Math.max(0, state - 1);
}
default: {
return state;
}
}
};
var defaultState = {
groups: [],
items: [],
choices: [],
txn: 0,
};
var appReducer = combineReducers({
items: items,
var reducers = {
groups: groups,
items: items,
choices: choices,
txn: general,
});
var rootReducer = function (passedState, action) {
var state = passedState;
// If we are clearing all items, groups and options we reassign
// state and then pass that state to our proper reducer. This isn't
// mutating our actual state
// See: http://stackoverflow.com/a/35641992
if (action.type === "CLEAR_ALL" /* ActionType.CLEAR_ALL */) {
// preserve the txn state as to allow withTxn to work
var paused = state.txn;
state = cloneObject(defaultState);
state.txn = paused;
}
return appReducer(state, action);
};
/* eslint-disable @typescript-eslint/no-explicit-any */
var Store = /** @class */ (function () {
function Store() {
this._store = createStore(rootReducer, window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__());
this._store = this.defaultState;
this._listeners = [];
this._txn = 0;
}
/**
* Subscribe store to function call (wrapped Redux method)
*/
Store.prototype.subscribe = function (onChange) {
this._store.subscribe(onChange);
Object.defineProperty(Store.prototype, "defaultState", {
// eslint-disable-next-line class-methods-use-this
get: function () {
return {
groups: [],
items: [],
choices: [],
};
},
enumerable: false,
configurable: true
});
// eslint-disable-next-line class-methods-use-this
Store.prototype.changeSet = function (init) {
return {
groups: init,
items: init,
choices: init,
};
};
Store.prototype.resetStore = function () {
this._store = this.defaultState;
var changes = this.changeSet(true);
this._listeners.forEach(function (l) { return l(changes); });
};
Store.prototype.subscribe = function (onChange) {
this._listeners.push(onChange);
};
/**
* Dispatch event to store (wrapped Redux method)
*/
Store.prototype.dispatch = function (action) {
this._store.dispatch(action);
var state = this._store;
var hasChanges = false;
var changes = this._outstandingChanges || this.changeSet(false);
Object.keys(reducers).forEach(function (key) {
var stateUpdate = reducers[key](state[key], action);
if (stateUpdate.update) {
hasChanges = true;
changes[key] = true;
state[key] = stateUpdate.state;
}
});
if (hasChanges) {
if (this._txn) {
this._outstandingChanges = changes;
}
else {
this._listeners.forEach(function (l) { return l(changes); });
}
}
};
Store.prototype.withTxn = function (func) {
this._store.dispatch(setTxn(true));
this._txn++;
try {
func();
}
finally {
this._store.dispatch(setTxn(false));
this._txn = Math.max(0, this._txn - 1);
if (!this._txn) {
var changeSet_1 = this._outstandingChanges;
if (changeSet_1) {
this._outstandingChanges = undefined;
this._listeners.forEach(function (l) { return l(changeSet_1); });
}
}
}
};
Object.defineProperty(Store.prototype, "state", {
/**
* Get store object (wrapping Redux method)
* Get store object
*/
get: function () {
return this._store.getState();
return this._store;
},
enumerable: false,
configurable: true
@ -1630,10 +1270,10 @@ var Store = /** @class */ (function () {
* Get active groups from store
*/
get: function () {
var _a = this, groups = _a.groups, choices = _a.choices;
return groups.filter(function (group) {
var _this = this;
return this.state.groups.filter(function (group) {
var isActive = group.active && !group.disabled;
var hasActiveOptions = choices.some(function (choice) { return choice.active && !choice.disabled; });
var hasActiveOptions = _this.state.choices.some(function (choice) { return choice.active && !choice.disabled; });
return isActive && hasActiveOptions;
}, []);
},
@ -1641,7 +1281,7 @@ var Store = /** @class */ (function () {
configurable: true
});
Store.prototype.inTxn = function () {
return this.state.txn > 0;
return this._txn > 0;
};
/**
* Get single choice by it's ID
@ -3728,9 +3368,6 @@ var Choices = /** @class */ (function () {
}
this.initialised = false;
this._store = new Store();
this._initialState = defaultState;
this._currentState = defaultState;
this._prevState = defaultState;
this._currentValue = '';
this.config.searchEnabled =
(!this._isTextElement && this.config.searchEnabled) ||
@ -3813,8 +3450,7 @@ var Choices = /** @class */ (function () {
this._createTemplates();
this._createElements();
this._createStructure();
this._store.subscribe(this._render);
this._render();
this._initStore();
this._addEventListeners();
var shouldDisable = (this._isTextElement && !this.config.addItems) ||
this.passedElement.element.hasAttribute('disabled') ||
@ -3838,6 +3474,7 @@ var Choices = /** @class */ (function () {
this.passedElement.reveal();
this.containerOuter.unwrap(this.passedElement.element);
this.clearStore();
this._store._listeners = [];
this._stopSearch();
this._templates = templates;
this.initialised = false;
@ -4243,7 +3880,7 @@ var Choices = /** @class */ (function () {
return this;
};
Choices.prototype.clearStore = function () {
this._store.dispatch(clearAll());
this._store.resetStore();
this._lastAddedChoiceId = 0;
this._lastAddedGroupId = 0;
// @todo integrate with Store
@ -4272,15 +3909,12 @@ var Choices = /** @class */ (function () {
}
}
};
Choices.prototype._render = function () {
Choices.prototype._render = function (changes) {
if (this._store.inTxn()) {
return;
}
this._currentState = this._store.state;
var shouldRenderItems = this._currentState.items !== this._prevState.items;
var stateChanged = this._currentState.choices !== this._prevState.choices ||
this._currentState.groups !== this._prevState.groups ||
shouldRenderItems;
var shouldRenderItems = changes === null || changes === void 0 ? void 0 : changes.items;
var stateChanged = (changes === null || changes === void 0 ? void 0 : changes.choices) || (changes === null || changes === void 0 ? void 0 : changes.groups) || shouldRenderItems;
if (!stateChanged) {
return;
}
@ -4290,7 +3924,6 @@ var Choices = /** @class */ (function () {
if (shouldRenderItems) {
this._renderItems();
}
this._prevState = this._currentState;
};
Choices.prototype._renderChoices = function () {
var _this = this;
@ -5509,7 +5142,6 @@ var Choices = /** @class */ (function () {
});
};
Choices.prototype._createStructure = function () {
var _this = this;
// Hide original element
this.passedElement.conceal();
// Wrap input in container preserving DOM ordering
@ -5537,6 +5169,10 @@ var Choices = /** @class */ (function () {
}
this._highlightPosition = 0;
this._isSearching = false;
};
Choices.prototype._initStore = function () {
var _this = this;
this._store.subscribe(this._render);
this._store.withTxn(function () {
_this._addPredefinedChoices(_this._presetChoices, _this._isSelectOneElement && !_this._hasNonChoicePlaceholder, false);
});

View file

@ -105,14 +105,6 @@
highlighted: highlighted,
}); };
var clearAll = function () { return ({
type: "CLEAR_ALL" /* ActionType.CLEAR_ALL */,
}); };
var setTxn = function (txn) { return ({
type: "SET_TRANSACTION" /* ActionType.SET_TRANSACTION */,
txn: txn,
}); };
/* eslint-disable @typescript-eslint/no-explicit-any */
var getRandomNumber = function (min, max) {
return Math.floor(Math.random() * (max - min) + min);
@ -240,9 +232,6 @@
});
return element.dispatchEvent(event);
};
var cloneObject = function (obj) {
return obj !== undefined ? JSON.parse(JSON.stringify(obj)) : undefined;
};
/**
* Returns an array of keys present on the first but missing on the second object
*/
@ -980,447 +969,85 @@
var ObjectsInConfig = ['fuseOptions', 'classNames'];
/**
* Adapted from React: https://github.com/facebook/react/blob/master/packages/shared/formatProdErrorMessage.js
*
* Do not require this module directly! Use normal throw error calls. These messages will be replaced with error codes
* during build.
* @param {number} code
*/
function formatProdErrorMessage(code) {
return "Minified Redux error #" + code + "; visit https://redux.js.org/Errors?code=" + code + " for the full message or " + 'use the non-minified dev environment for full errors. ';
}
// Inlined version of the `symbol-observable` polyfill
var $$observable = function () {
return typeof Symbol === 'function' && Symbol.observable || '@@observable';
}();
/**
* These are private action types reserved by Redux.
* For any unknown actions, you must return the current state.
* If the current state is undefined, you must return the initial state.
* Do not reference these action types directly in your code.
*/
var randomString = function randomString() {
return Math.random().toString(36).substring(7).split('').join('.');
};
var ActionTypes = {
INIT: "@@redux/INIT" + randomString(),
REPLACE: "@@redux/REPLACE" + randomString(),
PROBE_UNKNOWN_ACTION: function PROBE_UNKNOWN_ACTION() {
return "@@redux/PROBE_UNKNOWN_ACTION" + randomString();
}
};
/**
* @param {any} obj The object to inspect.
* @returns {boolean} True if the argument appears to be a plain object.
*/
function isPlainObject(obj) {
if (typeof obj !== 'object' || obj === null) return false;
var proto = obj;
while (Object.getPrototypeOf(proto) !== null) {
proto = Object.getPrototypeOf(proto);
}
return Object.getPrototypeOf(obj) === proto;
}
/**
* @deprecated
*
* **We recommend using the `configureStore` method
* of the `@reduxjs/toolkit` package**, which replaces `createStore`.
*
* Redux Toolkit is our recommended approach for writing Redux logic today,
* including store setup, reducers, data fetching, and more.
*
* **For more details, please read this Redux docs page:**
* **https://redux.js.org/introduction/why-rtk-is-redux-today**
*
* `configureStore` from Redux Toolkit is an improved version of `createStore` that
* simplifies setup and helps avoid common bugs.
*
* You should not be using the `redux` core package by itself today, except for learning purposes.
* The `createStore` method from the core `redux` package will not be removed, but we encourage
* all users to migrate to using Redux Toolkit for all Redux code.
*
* If you want to use `createStore` without this visual deprecation warning, use
* the `legacy_createStore` import instead:
*
* `import { legacy_createStore as createStore} from 'redux'`
*
*/
function createStore(reducer, preloadedState, enhancer) {
var _ref2;
if (typeof preloadedState === 'function' && typeof enhancer === 'function' || typeof enhancer === 'function' && typeof arguments[3] === 'function') {
throw new Error(formatProdErrorMessage(0) );
}
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState;
preloadedState = undefined;
}
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error(formatProdErrorMessage(1) );
}
return enhancer(createStore)(reducer, preloadedState);
}
if (typeof reducer !== 'function') {
throw new Error(formatProdErrorMessage(2) );
}
var currentReducer = reducer;
var currentState = preloadedState;
var currentListeners = [];
var nextListeners = currentListeners;
var isDispatching = false;
/**
* This makes a shallow copy of currentListeners so we can use
* nextListeners as a temporary list while dispatching.
*
* This prevents any bugs around consumers calling
* subscribe/unsubscribe in the middle of a dispatch.
*/
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice();
}
}
/**
* Reads the state tree managed by the store.
*
* @returns {any} The current state tree of your application.
*/
function getState() {
if (isDispatching) {
throw new Error(formatProdErrorMessage(3) );
}
return currentState;
}
/**
* Adds a change listener. It will be called any time an action is dispatched,
* and some part of the state tree may potentially have changed. You may then
* call `getState()` to read the current state tree inside the callback.
*
* You may call `dispatch()` from a change listener, with the following
* caveats:
*
* 1. The subscriptions are snapshotted just before every `dispatch()` call.
* If you subscribe or unsubscribe while the listeners are being invoked, this
* will not have any effect on the `dispatch()` that is currently in progress.
* However, the next `dispatch()` call, whether nested or not, will use a more
* recent snapshot of the subscription list.
*
* 2. The listener should not expect to see all state changes, as the state
* might have been updated multiple times during a nested `dispatch()` before
* the listener is called. It is, however, guaranteed that all subscribers
* registered before the `dispatch()` started will be called with the latest
* state by the time it exits.
*
* @param {Function} listener A callback to be invoked on every dispatch.
* @returns {Function} A function to remove this change listener.
*/
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error(formatProdErrorMessage(4) );
}
if (isDispatching) {
throw new Error(formatProdErrorMessage(5) );
}
var isSubscribed = true;
ensureCanMutateNextListeners();
nextListeners.push(listener);
return function unsubscribe() {
if (!isSubscribed) {
return;
}
if (isDispatching) {
throw new Error(formatProdErrorMessage(6) );
}
isSubscribed = false;
ensureCanMutateNextListeners();
var index = nextListeners.indexOf(listener);
nextListeners.splice(index, 1);
currentListeners = null;
};
}
/**
* Dispatches an action. It is the only way to trigger a state change.
*
* The `reducer` function, used to create the store, will be called with the
* current state tree and the given `action`. Its return value will
* be considered the **next** state of the tree, and the change listeners
* will be notified.
*
* The base implementation only supports plain object actions. If you want to
* dispatch a Promise, an Observable, a thunk, or something else, you need to
* wrap your store creating function into the corresponding middleware. For
* example, see the documentation for the `redux-thunk` package. Even the
* middleware will eventually dispatch plain object actions using this method.
*
* @param {Object} action A plain object representing what changed. It is
* a good idea to keep actions serializable so you can record and replay user
* sessions, or use the time travelling `redux-devtools`. An action must have
* a `type` property which may not be `undefined`. It is a good idea to use
* string constants for action types.
*
* @returns {Object} For convenience, the same action object you dispatched.
*
* Note that, if you use a custom middleware, it may wrap `dispatch()` to
* return something else (for example, a Promise you can await).
*/
function dispatch(action) {
if (!isPlainObject(action)) {
throw new Error(formatProdErrorMessage(7) );
}
if (typeof action.type === 'undefined') {
throw new Error(formatProdErrorMessage(8) );
}
if (isDispatching) {
throw new Error(formatProdErrorMessage(9) );
}
try {
isDispatching = true;
currentState = currentReducer(currentState, action);
} finally {
isDispatching = false;
}
var listeners = currentListeners = nextListeners;
for (var i = 0; i < listeners.length; i++) {
var listener = listeners[i];
listener();
}
return action;
}
/**
* Replaces the reducer currently used by the store to calculate the state.
*
* You might need this if your app implements code splitting and you want to
* load some of the reducers dynamically. You might also need this if you
* implement a hot reloading mechanism for Redux.
*
* @param {Function} nextReducer The reducer for the store to use instead.
* @returns {void}
*/
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error(formatProdErrorMessage(10) );
}
currentReducer = nextReducer; // This action has a similiar effect to ActionTypes.INIT.
// Any reducers that existed in both the new and old rootReducer
// will receive the previous state. This effectively populates
// the new state tree with any relevant data from the old one.
dispatch({
type: ActionTypes.REPLACE
});
}
/**
* Interoperability point for observable/reactive libraries.
* @returns {observable} A minimal observable of state changes.
* For more information, see the observable proposal:
* https://github.com/tc39/proposal-observable
*/
function observable() {
var _ref;
var outerSubscribe = subscribe;
return _ref = {
/**
* The minimal observable subscription method.
* @param {Object} observer Any object that can be used as an observer.
* The observer object should have a `next` method.
* @returns {subscription} An object with an `unsubscribe` method that can
* be used to unsubscribe the observable from the store, and prevent further
* emission of values from the observable.
*/
subscribe: function subscribe(observer) {
if (typeof observer !== 'object' || observer === null) {
throw new Error(formatProdErrorMessage(11) );
}
function observeState() {
if (observer.next) {
observer.next(getState());
}
}
observeState();
var unsubscribe = outerSubscribe(observeState);
return {
unsubscribe: unsubscribe
};
}
}, _ref[$$observable] = function () {
return this;
}, _ref;
} // When a store is created, an "INIT" action is dispatched so that every
// reducer returns their initial state. This effectively populates
// the initial state tree.
dispatch({
type: ActionTypes.INIT
});
return _ref2 = {
dispatch: dispatch,
subscribe: subscribe,
getState: getState,
replaceReducer: replaceReducer
}, _ref2[$$observable] = observable, _ref2;
}
function assertReducerShape(reducers) {
Object.keys(reducers).forEach(function (key) {
var reducer = reducers[key];
var initialState = reducer(undefined, {
type: ActionTypes.INIT
});
if (typeof initialState === 'undefined') {
throw new Error(formatProdErrorMessage(12) );
}
if (typeof reducer(undefined, {
type: ActionTypes.PROBE_UNKNOWN_ACTION()
}) === 'undefined') {
throw new Error(formatProdErrorMessage(13) );
}
});
}
/**
* Turns an object whose values are different reducer functions, into a single
* reducer function. It will call every child reducer, and gather their results
* into a single state object, whose keys correspond to the keys of the passed
* reducer functions.
*
* @param {Object} reducers An object whose values correspond to different
* reducer functions that need to be combined into one. One handy way to obtain
* it is to use ES6 `import * as reducers` syntax. The reducers may never return
* undefined for any action. Instead, they should return their initial state
* if the state passed to them was undefined, and the current state for any
* unrecognized action.
*
* @returns {Function} A reducer function that invokes every reducer inside the
* passed object, and builds a state object with the same shape.
*/
function combineReducers(reducers) {
var reducerKeys = Object.keys(reducers);
var finalReducers = {};
for (var i = 0; i < reducerKeys.length; i++) {
var key = reducerKeys[i];
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key];
}
}
var finalReducerKeys = Object.keys(finalReducers); // This is used to make sure we don't warn about the same
var shapeAssertionError;
try {
assertReducerShape(finalReducers);
} catch (e) {
shapeAssertionError = e;
}
return function combination(state, action) {
if (state === void 0) {
state = {};
}
if (shapeAssertionError) {
throw shapeAssertionError;
}
var hasChanged = false;
var nextState = {};
for (var _i = 0; _i < finalReducerKeys.length; _i++) {
var _key = finalReducerKeys[_i];
var reducer = finalReducers[_key];
var previousStateForKey = state[_key];
var nextStateForKey = reducer(previousStateForKey, action);
if (typeof nextStateForKey === 'undefined') {
action && action.type;
throw new Error(formatProdErrorMessage(14) );
}
nextState[_key] = nextStateForKey;
hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
}
hasChanged = hasChanged || finalReducerKeys.length !== Object.keys(state).length;
return hasChanged ? nextState : state;
};
}
function items(state, action) {
if (state === void 0) { state = []; }
if (action === void 0) { action = {}; }
function items(s, action) {
var state = s;
var update = false;
switch (action.type) {
case "ADD_ITEM" /* ActionType.ADD_ITEM */: {
var item = action.item;
if (!item.id) {
return state;
if (item.id) {
item.selected = true;
var el = item.element;
if (el) {
el.selected = true;
el.setAttribute('selected', '');
}
update = true;
state.push(item);
state.forEach(function (obj) {
// eslint-disable-next-line no-param-reassign
obj.highlighted = false;
});
}
item.selected = true;
var el = item.element;
if (el) {
el.selected = true;
el.setAttribute('selected', '');
}
return __spreadArray(__spreadArray([], state, true), [item], false).map(function (obj) {
var choice = obj;
choice.highlighted = false;
return choice;
});
break;
}
case "REMOVE_ITEM" /* ActionType.REMOVE_ITEM */: {
var item_1 = action.item;
if (!item_1.id) {
return state;
if (item_1.id) {
item_1.selected = false;
var el = item_1.element;
if (el) {
el.selected = false;
el.removeAttribute('selected');
}
update = true;
state = state.filter(function (choice) { return choice.id !== item_1.id; });
}
item_1.selected = false;
var el = item_1.element;
if (el) {
el.selected = false;
el.removeAttribute('selected');
}
return state.filter(function (choice) { return choice.id !== item_1.id; });
break;
}
case "REMOVE_CHOICE" /* ActionType.REMOVE_CHOICE */: {
var choice_1 = action.choice;
return state.filter(function (item) { return item.id !== choice_1.id; });
update = true;
state = state.filter(function (item) { return item.id !== choice_1.id; });
break;
}
case "HIGHLIGHT_ITEM" /* ActionType.HIGHLIGHT_ITEM */: {
var highlightItemAction_1 = action;
return state.map(function (obj) {
update = true;
state.forEach(function (obj) {
var item = obj;
if (item.id === highlightItemAction_1.item.id) {
item.highlighted = highlightItemAction_1.highlighted;
}
return item;
});
}
default: {
return state;
break;
}
}
return { state: state, update: update };
}
function groups(state, action) {
if (state === void 0) { state = []; }
if (action === void 0) { action = {}; }
function groups(s, action) {
var state = s;
var update = false;
switch (action.type) {
case "ADD_GROUP" /* ActionType.ADD_GROUP */: {
var addGroupAction = action;
return __spreadArray(__spreadArray([], state, true), [addGroupAction.group], false);
update = true;
state.push(addGroupAction.group);
break;
}
case "CLEAR_CHOICES" /* ActionType.CLEAR_CHOICES */: {
return [];
}
default: {
return state;
update = true;
state = [];
break;
}
}
return { state: state, update: update };
}
function choices(state, action) {
if (state === void 0) { state = []; }
if (action === void 0) { action = {}; }
function choices(s, action) {
var state = s;
var update = false;
switch (action.type) {
case "ADD_CHOICE" /* ActionType.ADD_CHOICE */: {
var choice = action.choice;
@ -1429,36 +1056,41 @@
A selected choice has been added to the passed input's value (added as an item)
An active choice appears within the choice dropdown
*/
return __spreadArray(__spreadArray([], state, true), [choice], false);
state.push(choice);
update = true;
break;
}
case "REMOVE_CHOICE" /* ActionType.REMOVE_CHOICE */: {
var choice_1 = action.choice;
return state.filter(function (obj) { return obj.id !== choice_1.id; });
update = true;
state = state.filter(function (obj) { return obj.id !== choice_1.id; });
break;
}
case "ADD_ITEM" /* ActionType.ADD_ITEM */: {
var item = action.item;
// trigger a rebuild of the choices list as the item can not be added multiple times
if (item.id && item.selected) {
return __spreadArray([], state, true);
update = true;
}
return state;
break;
}
case "REMOVE_ITEM" /* ActionType.REMOVE_ITEM */: {
var item = action.item;
// trigger a rebuild of the choices list as the item can be added
if (item.id && !item.selected) {
return __spreadArray([], state, true);
update = true;
}
return state;
break;
}
case "FILTER_CHOICES" /* ActionType.FILTER_CHOICES */: {
var results = action.results;
update = true;
// avoid O(n^2) algorithm complexity when searching/filtering choices
var scoreLookup_1 = [];
results.forEach(function (result) {
scoreLookup_1[result.item.id] = result;
});
return state.map(function (obj) {
state.forEach(function (obj) {
var choice = obj;
var result = scoreLookup_1[choice.id];
if (result !== undefined) {
@ -1471,102 +1103,110 @@
choice.rank = 0;
choice.active = false;
}
return choice;
});
break;
}
case "ACTIVATE_CHOICES" /* ActionType.ACTIVATE_CHOICES */: {
var active_1 = action.active;
return state.map(function (obj) {
update = true;
state.forEach(function (obj) {
var choice = obj;
choice.active = active_1;
return choice;
});
break;
}
case "CLEAR_CHOICES" /* ActionType.CLEAR_CHOICES */: {
return [];
}
default: {
return state;
update = true;
state = [];
break;
}
}
return { state: state, update: update };
}
var general = function (state, action) {
if (state === void 0) { state = 0; }
if (action === void 0) { action = {}; }
switch (action.type) {
case "SET_TRANSACTION" /* ActionType.SET_TRANSACTION */: {
if (action.txn) {
return state + 1;
}
return Math.max(0, state - 1);
}
default: {
return state;
}
}
};
var defaultState = {
groups: [],
items: [],
choices: [],
txn: 0,
};
var appReducer = combineReducers({
items: items,
var reducers = {
groups: groups,
items: items,
choices: choices,
txn: general,
});
var rootReducer = function (passedState, action) {
var state = passedState;
// If we are clearing all items, groups and options we reassign
// state and then pass that state to our proper reducer. This isn't
// mutating our actual state
// See: http://stackoverflow.com/a/35641992
if (action.type === "CLEAR_ALL" /* ActionType.CLEAR_ALL */) {
// preserve the txn state as to allow withTxn to work
var paused = state.txn;
state = cloneObject(defaultState);
state.txn = paused;
}
return appReducer(state, action);
};
/* eslint-disable @typescript-eslint/no-explicit-any */
var Store = /** @class */ (function () {
function Store() {
this._store = createStore(rootReducer, window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__());
this._store = this.defaultState;
this._listeners = [];
this._txn = 0;
}
/**
* Subscribe store to function call (wrapped Redux method)
*/
Store.prototype.subscribe = function (onChange) {
this._store.subscribe(onChange);
Object.defineProperty(Store.prototype, "defaultState", {
// eslint-disable-next-line class-methods-use-this
get: function () {
return {
groups: [],
items: [],
choices: [],
};
},
enumerable: false,
configurable: true
});
// eslint-disable-next-line class-methods-use-this
Store.prototype.changeSet = function (init) {
return {
groups: init,
items: init,
choices: init,
};
};
Store.prototype.resetStore = function () {
this._store = this.defaultState;
var changes = this.changeSet(true);
this._listeners.forEach(function (l) { return l(changes); });
};
Store.prototype.subscribe = function (onChange) {
this._listeners.push(onChange);
};
/**
* Dispatch event to store (wrapped Redux method)
*/
Store.prototype.dispatch = function (action) {
this._store.dispatch(action);
var state = this._store;
var hasChanges = false;
var changes = this._outstandingChanges || this.changeSet(false);
Object.keys(reducers).forEach(function (key) {
var stateUpdate = reducers[key](state[key], action);
if (stateUpdate.update) {
hasChanges = true;
changes[key] = true;
state[key] = stateUpdate.state;
}
});
if (hasChanges) {
if (this._txn) {
this._outstandingChanges = changes;
}
else {
this._listeners.forEach(function (l) { return l(changes); });
}
}
};
Store.prototype.withTxn = function (func) {
this._store.dispatch(setTxn(true));
this._txn++;
try {
func();
}
finally {
this._store.dispatch(setTxn(false));
this._txn = Math.max(0, this._txn - 1);
if (!this._txn) {
var changeSet_1 = this._outstandingChanges;
if (changeSet_1) {
this._outstandingChanges = undefined;
this._listeners.forEach(function (l) { return l(changeSet_1); });
}
}
}
};
Object.defineProperty(Store.prototype, "state", {
/**
* Get store object (wrapping Redux method)
* Get store object
*/
get: function () {
return this._store.getState();
return this._store;
},
enumerable: false,
configurable: true
@ -1636,10 +1276,10 @@
* Get active groups from store
*/
get: function () {
var _a = this, groups = _a.groups, choices = _a.choices;
return groups.filter(function (group) {
var _this = this;
return this.state.groups.filter(function (group) {
var isActive = group.active && !group.disabled;
var hasActiveOptions = choices.some(function (choice) { return choice.active && !choice.disabled; });
var hasActiveOptions = _this.state.choices.some(function (choice) { return choice.active && !choice.disabled; });
return isActive && hasActiveOptions;
}, []);
},
@ -1647,7 +1287,7 @@
configurable: true
});
Store.prototype.inTxn = function () {
return this.state.txn > 0;
return this._txn > 0;
};
/**
* Get single choice by it's ID
@ -3252,9 +2892,6 @@
}
this.initialised = false;
this._store = new Store();
this._initialState = defaultState;
this._currentState = defaultState;
this._prevState = defaultState;
this._currentValue = '';
this.config.searchEnabled =
(!this._isTextElement && this.config.searchEnabled) ||
@ -3337,8 +2974,7 @@
this._createTemplates();
this._createElements();
this._createStructure();
this._store.subscribe(this._render);
this._render();
this._initStore();
this._addEventListeners();
var shouldDisable = (this._isTextElement && !this.config.addItems) ||
this.passedElement.element.hasAttribute('disabled') ||
@ -3362,6 +2998,7 @@
this.passedElement.reveal();
this.containerOuter.unwrap(this.passedElement.element);
this.clearStore();
this._store._listeners = [];
this._stopSearch();
this._templates = templates;
this.initialised = false;
@ -3767,7 +3404,7 @@
return this;
};
Choices.prototype.clearStore = function () {
this._store.dispatch(clearAll());
this._store.resetStore();
this._lastAddedChoiceId = 0;
this._lastAddedGroupId = 0;
// @todo integrate with Store
@ -3796,15 +3433,12 @@
}
}
};
Choices.prototype._render = function () {
Choices.prototype._render = function (changes) {
if (this._store.inTxn()) {
return;
}
this._currentState = this._store.state;
var shouldRenderItems = this._currentState.items !== this._prevState.items;
var stateChanged = this._currentState.choices !== this._prevState.choices ||
this._currentState.groups !== this._prevState.groups ||
shouldRenderItems;
var shouldRenderItems = changes === null || changes === void 0 ? void 0 : changes.items;
var stateChanged = (changes === null || changes === void 0 ? void 0 : changes.choices) || (changes === null || changes === void 0 ? void 0 : changes.groups) || shouldRenderItems;
if (!stateChanged) {
return;
}
@ -3814,7 +3448,6 @@
if (shouldRenderItems) {
this._renderItems();
}
this._prevState = this._currentState;
};
Choices.prototype._renderChoices = function () {
var _this = this;
@ -5033,7 +4666,6 @@
});
};
Choices.prototype._createStructure = function () {
var _this = this;
// Hide original element
this.passedElement.conceal();
// Wrap input in container preserving DOM ordering
@ -5061,6 +4693,10 @@
}
this._highlightPosition = 0;
this._isSearching = false;
};
Choices.prototype._initStore = function () {
var _this = this;
this._store.subscribe(this._render);
this._store.withTxn(function () {
_this._addPredefinedChoices(_this._presetChoices, _this._isSelectOneElement && !_this._hasNonChoicePlaceholder, false);
});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -99,14 +99,6 @@ var highlightItem = function (item, highlighted) { return ({
highlighted: highlighted,
}); };
var clearAll = function () { return ({
type: "CLEAR_ALL" /* ActionType.CLEAR_ALL */,
}); };
var setTxn = function (txn) { return ({
type: "SET_TRANSACTION" /* ActionType.SET_TRANSACTION */,
txn: txn,
}); };
/* eslint-disable @typescript-eslint/no-explicit-any */
var getRandomNumber = function (min, max) {
return Math.floor(Math.random() * (max - min) + min);
@ -234,9 +226,6 @@ var dispatchEvent = function (element, type, customArgs) {
});
return element.dispatchEvent(event);
};
var cloneObject = function (obj) {
return obj !== undefined ? JSON.parse(JSON.stringify(obj)) : undefined;
};
/**
* Returns an array of keys present on the first but missing on the second object
*/
@ -974,447 +963,85 @@ var DEFAULT_CONFIG = {
var ObjectsInConfig = ['fuseOptions', 'classNames'];
/**
* Adapted from React: https://github.com/facebook/react/blob/master/packages/shared/formatProdErrorMessage.js
*
* Do not require this module directly! Use normal throw error calls. These messages will be replaced with error codes
* during build.
* @param {number} code
*/
function formatProdErrorMessage(code) {
return "Minified Redux error #" + code + "; visit https://redux.js.org/Errors?code=" + code + " for the full message or " + 'use the non-minified dev environment for full errors. ';
}
// Inlined version of the `symbol-observable` polyfill
var $$observable = function () {
return typeof Symbol === 'function' && Symbol.observable || '@@observable';
}();
/**
* These are private action types reserved by Redux.
* For any unknown actions, you must return the current state.
* If the current state is undefined, you must return the initial state.
* Do not reference these action types directly in your code.
*/
var randomString = function randomString() {
return Math.random().toString(36).substring(7).split('').join('.');
};
var ActionTypes = {
INIT: "@@redux/INIT" + randomString(),
REPLACE: "@@redux/REPLACE" + randomString(),
PROBE_UNKNOWN_ACTION: function PROBE_UNKNOWN_ACTION() {
return "@@redux/PROBE_UNKNOWN_ACTION" + randomString();
}
};
/**
* @param {any} obj The object to inspect.
* @returns {boolean} True if the argument appears to be a plain object.
*/
function isPlainObject(obj) {
if (typeof obj !== 'object' || obj === null) return false;
var proto = obj;
while (Object.getPrototypeOf(proto) !== null) {
proto = Object.getPrototypeOf(proto);
}
return Object.getPrototypeOf(obj) === proto;
}
/**
* @deprecated
*
* **We recommend using the `configureStore` method
* of the `@reduxjs/toolkit` package**, which replaces `createStore`.
*
* Redux Toolkit is our recommended approach for writing Redux logic today,
* including store setup, reducers, data fetching, and more.
*
* **For more details, please read this Redux docs page:**
* **https://redux.js.org/introduction/why-rtk-is-redux-today**
*
* `configureStore` from Redux Toolkit is an improved version of `createStore` that
* simplifies setup and helps avoid common bugs.
*
* You should not be using the `redux` core package by itself today, except for learning purposes.
* The `createStore` method from the core `redux` package will not be removed, but we encourage
* all users to migrate to using Redux Toolkit for all Redux code.
*
* If you want to use `createStore` without this visual deprecation warning, use
* the `legacy_createStore` import instead:
*
* `import { legacy_createStore as createStore} from 'redux'`
*
*/
function createStore(reducer, preloadedState, enhancer) {
var _ref2;
if (typeof preloadedState === 'function' && typeof enhancer === 'function' || typeof enhancer === 'function' && typeof arguments[3] === 'function') {
throw new Error(formatProdErrorMessage(0) );
}
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState;
preloadedState = undefined;
}
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error(formatProdErrorMessage(1) );
}
return enhancer(createStore)(reducer, preloadedState);
}
if (typeof reducer !== 'function') {
throw new Error(formatProdErrorMessage(2) );
}
var currentReducer = reducer;
var currentState = preloadedState;
var currentListeners = [];
var nextListeners = currentListeners;
var isDispatching = false;
/**
* This makes a shallow copy of currentListeners so we can use
* nextListeners as a temporary list while dispatching.
*
* This prevents any bugs around consumers calling
* subscribe/unsubscribe in the middle of a dispatch.
*/
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice();
}
}
/**
* Reads the state tree managed by the store.
*
* @returns {any} The current state tree of your application.
*/
function getState() {
if (isDispatching) {
throw new Error(formatProdErrorMessage(3) );
}
return currentState;
}
/**
* Adds a change listener. It will be called any time an action is dispatched,
* and some part of the state tree may potentially have changed. You may then
* call `getState()` to read the current state tree inside the callback.
*
* You may call `dispatch()` from a change listener, with the following
* caveats:
*
* 1. The subscriptions are snapshotted just before every `dispatch()` call.
* If you subscribe or unsubscribe while the listeners are being invoked, this
* will not have any effect on the `dispatch()` that is currently in progress.
* However, the next `dispatch()` call, whether nested or not, will use a more
* recent snapshot of the subscription list.
*
* 2. The listener should not expect to see all state changes, as the state
* might have been updated multiple times during a nested `dispatch()` before
* the listener is called. It is, however, guaranteed that all subscribers
* registered before the `dispatch()` started will be called with the latest
* state by the time it exits.
*
* @param {Function} listener A callback to be invoked on every dispatch.
* @returns {Function} A function to remove this change listener.
*/
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error(formatProdErrorMessage(4) );
}
if (isDispatching) {
throw new Error(formatProdErrorMessage(5) );
}
var isSubscribed = true;
ensureCanMutateNextListeners();
nextListeners.push(listener);
return function unsubscribe() {
if (!isSubscribed) {
return;
}
if (isDispatching) {
throw new Error(formatProdErrorMessage(6) );
}
isSubscribed = false;
ensureCanMutateNextListeners();
var index = nextListeners.indexOf(listener);
nextListeners.splice(index, 1);
currentListeners = null;
};
}
/**
* Dispatches an action. It is the only way to trigger a state change.
*
* The `reducer` function, used to create the store, will be called with the
* current state tree and the given `action`. Its return value will
* be considered the **next** state of the tree, and the change listeners
* will be notified.
*
* The base implementation only supports plain object actions. If you want to
* dispatch a Promise, an Observable, a thunk, or something else, you need to
* wrap your store creating function into the corresponding middleware. For
* example, see the documentation for the `redux-thunk` package. Even the
* middleware will eventually dispatch plain object actions using this method.
*
* @param {Object} action A plain object representing what changed. It is
* a good idea to keep actions serializable so you can record and replay user
* sessions, or use the time travelling `redux-devtools`. An action must have
* a `type` property which may not be `undefined`. It is a good idea to use
* string constants for action types.
*
* @returns {Object} For convenience, the same action object you dispatched.
*
* Note that, if you use a custom middleware, it may wrap `dispatch()` to
* return something else (for example, a Promise you can await).
*/
function dispatch(action) {
if (!isPlainObject(action)) {
throw new Error(formatProdErrorMessage(7) );
}
if (typeof action.type === 'undefined') {
throw new Error(formatProdErrorMessage(8) );
}
if (isDispatching) {
throw new Error(formatProdErrorMessage(9) );
}
try {
isDispatching = true;
currentState = currentReducer(currentState, action);
} finally {
isDispatching = false;
}
var listeners = currentListeners = nextListeners;
for (var i = 0; i < listeners.length; i++) {
var listener = listeners[i];
listener();
}
return action;
}
/**
* Replaces the reducer currently used by the store to calculate the state.
*
* You might need this if your app implements code splitting and you want to
* load some of the reducers dynamically. You might also need this if you
* implement a hot reloading mechanism for Redux.
*
* @param {Function} nextReducer The reducer for the store to use instead.
* @returns {void}
*/
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error(formatProdErrorMessage(10) );
}
currentReducer = nextReducer; // This action has a similiar effect to ActionTypes.INIT.
// Any reducers that existed in both the new and old rootReducer
// will receive the previous state. This effectively populates
// the new state tree with any relevant data from the old one.
dispatch({
type: ActionTypes.REPLACE
});
}
/**
* Interoperability point for observable/reactive libraries.
* @returns {observable} A minimal observable of state changes.
* For more information, see the observable proposal:
* https://github.com/tc39/proposal-observable
*/
function observable() {
var _ref;
var outerSubscribe = subscribe;
return _ref = {
/**
* The minimal observable subscription method.
* @param {Object} observer Any object that can be used as an observer.
* The observer object should have a `next` method.
* @returns {subscription} An object with an `unsubscribe` method that can
* be used to unsubscribe the observable from the store, and prevent further
* emission of values from the observable.
*/
subscribe: function subscribe(observer) {
if (typeof observer !== 'object' || observer === null) {
throw new Error(formatProdErrorMessage(11) );
}
function observeState() {
if (observer.next) {
observer.next(getState());
}
}
observeState();
var unsubscribe = outerSubscribe(observeState);
return {
unsubscribe: unsubscribe
};
}
}, _ref[$$observable] = function () {
return this;
}, _ref;
} // When a store is created, an "INIT" action is dispatched so that every
// reducer returns their initial state. This effectively populates
// the initial state tree.
dispatch({
type: ActionTypes.INIT
});
return _ref2 = {
dispatch: dispatch,
subscribe: subscribe,
getState: getState,
replaceReducer: replaceReducer
}, _ref2[$$observable] = observable, _ref2;
}
function assertReducerShape(reducers) {
Object.keys(reducers).forEach(function (key) {
var reducer = reducers[key];
var initialState = reducer(undefined, {
type: ActionTypes.INIT
});
if (typeof initialState === 'undefined') {
throw new Error(formatProdErrorMessage(12) );
}
if (typeof reducer(undefined, {
type: ActionTypes.PROBE_UNKNOWN_ACTION()
}) === 'undefined') {
throw new Error(formatProdErrorMessage(13) );
}
});
}
/**
* Turns an object whose values are different reducer functions, into a single
* reducer function. It will call every child reducer, and gather their results
* into a single state object, whose keys correspond to the keys of the passed
* reducer functions.
*
* @param {Object} reducers An object whose values correspond to different
* reducer functions that need to be combined into one. One handy way to obtain
* it is to use ES6 `import * as reducers` syntax. The reducers may never return
* undefined for any action. Instead, they should return their initial state
* if the state passed to them was undefined, and the current state for any
* unrecognized action.
*
* @returns {Function} A reducer function that invokes every reducer inside the
* passed object, and builds a state object with the same shape.
*/
function combineReducers(reducers) {
var reducerKeys = Object.keys(reducers);
var finalReducers = {};
for (var i = 0; i < reducerKeys.length; i++) {
var key = reducerKeys[i];
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key];
}
}
var finalReducerKeys = Object.keys(finalReducers); // This is used to make sure we don't warn about the same
var shapeAssertionError;
try {
assertReducerShape(finalReducers);
} catch (e) {
shapeAssertionError = e;
}
return function combination(state, action) {
if (state === void 0) {
state = {};
}
if (shapeAssertionError) {
throw shapeAssertionError;
}
var hasChanged = false;
var nextState = {};
for (var _i = 0; _i < finalReducerKeys.length; _i++) {
var _key = finalReducerKeys[_i];
var reducer = finalReducers[_key];
var previousStateForKey = state[_key];
var nextStateForKey = reducer(previousStateForKey, action);
if (typeof nextStateForKey === 'undefined') {
action && action.type;
throw new Error(formatProdErrorMessage(14) );
}
nextState[_key] = nextStateForKey;
hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
}
hasChanged = hasChanged || finalReducerKeys.length !== Object.keys(state).length;
return hasChanged ? nextState : state;
};
}
function items(state, action) {
if (state === void 0) { state = []; }
if (action === void 0) { action = {}; }
function items(s, action) {
var state = s;
var update = false;
switch (action.type) {
case "ADD_ITEM" /* ActionType.ADD_ITEM */: {
var item = action.item;
if (!item.id) {
return state;
if (item.id) {
item.selected = true;
var el = item.element;
if (el) {
el.selected = true;
el.setAttribute('selected', '');
}
update = true;
state.push(item);
state.forEach(function (obj) {
// eslint-disable-next-line no-param-reassign
obj.highlighted = false;
});
}
item.selected = true;
var el = item.element;
if (el) {
el.selected = true;
el.setAttribute('selected', '');
}
return __spreadArray(__spreadArray([], state, true), [item], false).map(function (obj) {
var choice = obj;
choice.highlighted = false;
return choice;
});
break;
}
case "REMOVE_ITEM" /* ActionType.REMOVE_ITEM */: {
var item_1 = action.item;
if (!item_1.id) {
return state;
if (item_1.id) {
item_1.selected = false;
var el = item_1.element;
if (el) {
el.selected = false;
el.removeAttribute('selected');
}
update = true;
state = state.filter(function (choice) { return choice.id !== item_1.id; });
}
item_1.selected = false;
var el = item_1.element;
if (el) {
el.selected = false;
el.removeAttribute('selected');
}
return state.filter(function (choice) { return choice.id !== item_1.id; });
break;
}
case "REMOVE_CHOICE" /* ActionType.REMOVE_CHOICE */: {
var choice_1 = action.choice;
return state.filter(function (item) { return item.id !== choice_1.id; });
update = true;
state = state.filter(function (item) { return item.id !== choice_1.id; });
break;
}
case "HIGHLIGHT_ITEM" /* ActionType.HIGHLIGHT_ITEM */: {
var highlightItemAction_1 = action;
return state.map(function (obj) {
update = true;
state.forEach(function (obj) {
var item = obj;
if (item.id === highlightItemAction_1.item.id) {
item.highlighted = highlightItemAction_1.highlighted;
}
return item;
});
}
default: {
return state;
break;
}
}
return { state: state, update: update };
}
function groups(state, action) {
if (state === void 0) { state = []; }
if (action === void 0) { action = {}; }
function groups(s, action) {
var state = s;
var update = false;
switch (action.type) {
case "ADD_GROUP" /* ActionType.ADD_GROUP */: {
var addGroupAction = action;
return __spreadArray(__spreadArray([], state, true), [addGroupAction.group], false);
update = true;
state.push(addGroupAction.group);
break;
}
case "CLEAR_CHOICES" /* ActionType.CLEAR_CHOICES */: {
return [];
}
default: {
return state;
update = true;
state = [];
break;
}
}
return { state: state, update: update };
}
function choices(state, action) {
if (state === void 0) { state = []; }
if (action === void 0) { action = {}; }
function choices(s, action) {
var state = s;
var update = false;
switch (action.type) {
case "ADD_CHOICE" /* ActionType.ADD_CHOICE */: {
var choice = action.choice;
@ -1423,36 +1050,41 @@ function choices(state, action) {
A selected choice has been added to the passed input's value (added as an item)
An active choice appears within the choice dropdown
*/
return __spreadArray(__spreadArray([], state, true), [choice], false);
state.push(choice);
update = true;
break;
}
case "REMOVE_CHOICE" /* ActionType.REMOVE_CHOICE */: {
var choice_1 = action.choice;
return state.filter(function (obj) { return obj.id !== choice_1.id; });
update = true;
state = state.filter(function (obj) { return obj.id !== choice_1.id; });
break;
}
case "ADD_ITEM" /* ActionType.ADD_ITEM */: {
var item = action.item;
// trigger a rebuild of the choices list as the item can not be added multiple times
if (item.id && item.selected) {
return __spreadArray([], state, true);
update = true;
}
return state;
break;
}
case "REMOVE_ITEM" /* ActionType.REMOVE_ITEM */: {
var item = action.item;
// trigger a rebuild of the choices list as the item can be added
if (item.id && !item.selected) {
return __spreadArray([], state, true);
update = true;
}
return state;
break;
}
case "FILTER_CHOICES" /* ActionType.FILTER_CHOICES */: {
var results = action.results;
update = true;
// avoid O(n^2) algorithm complexity when searching/filtering choices
var scoreLookup_1 = [];
results.forEach(function (result) {
scoreLookup_1[result.item.id] = result;
});
return state.map(function (obj) {
state.forEach(function (obj) {
var choice = obj;
var result = scoreLookup_1[choice.id];
if (result !== undefined) {
@ -1465,102 +1097,110 @@ function choices(state, action) {
choice.rank = 0;
choice.active = false;
}
return choice;
});
break;
}
case "ACTIVATE_CHOICES" /* ActionType.ACTIVATE_CHOICES */: {
var active_1 = action.active;
return state.map(function (obj) {
update = true;
state.forEach(function (obj) {
var choice = obj;
choice.active = active_1;
return choice;
});
break;
}
case "CLEAR_CHOICES" /* ActionType.CLEAR_CHOICES */: {
return [];
}
default: {
return state;
update = true;
state = [];
break;
}
}
return { state: state, update: update };
}
var general = function (state, action) {
if (state === void 0) { state = 0; }
if (action === void 0) { action = {}; }
switch (action.type) {
case "SET_TRANSACTION" /* ActionType.SET_TRANSACTION */: {
if (action.txn) {
return state + 1;
}
return Math.max(0, state - 1);
}
default: {
return state;
}
}
};
var defaultState = {
groups: [],
items: [],
choices: [],
txn: 0,
};
var appReducer = combineReducers({
items: items,
var reducers = {
groups: groups,
items: items,
choices: choices,
txn: general,
});
var rootReducer = function (passedState, action) {
var state = passedState;
// If we are clearing all items, groups and options we reassign
// state and then pass that state to our proper reducer. This isn't
// mutating our actual state
// See: http://stackoverflow.com/a/35641992
if (action.type === "CLEAR_ALL" /* ActionType.CLEAR_ALL */) {
// preserve the txn state as to allow withTxn to work
var paused = state.txn;
state = cloneObject(defaultState);
state.txn = paused;
}
return appReducer(state, action);
};
/* eslint-disable @typescript-eslint/no-explicit-any */
var Store = /** @class */ (function () {
function Store() {
this._store = createStore(rootReducer, window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__());
this._store = this.defaultState;
this._listeners = [];
this._txn = 0;
}
/**
* Subscribe store to function call (wrapped Redux method)
*/
Store.prototype.subscribe = function (onChange) {
this._store.subscribe(onChange);
Object.defineProperty(Store.prototype, "defaultState", {
// eslint-disable-next-line class-methods-use-this
get: function () {
return {
groups: [],
items: [],
choices: [],
};
},
enumerable: false,
configurable: true
});
// eslint-disable-next-line class-methods-use-this
Store.prototype.changeSet = function (init) {
return {
groups: init,
items: init,
choices: init,
};
};
Store.prototype.resetStore = function () {
this._store = this.defaultState;
var changes = this.changeSet(true);
this._listeners.forEach(function (l) { return l(changes); });
};
Store.prototype.subscribe = function (onChange) {
this._listeners.push(onChange);
};
/**
* Dispatch event to store (wrapped Redux method)
*/
Store.prototype.dispatch = function (action) {
this._store.dispatch(action);
var state = this._store;
var hasChanges = false;
var changes = this._outstandingChanges || this.changeSet(false);
Object.keys(reducers).forEach(function (key) {
var stateUpdate = reducers[key](state[key], action);
if (stateUpdate.update) {
hasChanges = true;
changes[key] = true;
state[key] = stateUpdate.state;
}
});
if (hasChanges) {
if (this._txn) {
this._outstandingChanges = changes;
}
else {
this._listeners.forEach(function (l) { return l(changes); });
}
}
};
Store.prototype.withTxn = function (func) {
this._store.dispatch(setTxn(true));
this._txn++;
try {
func();
}
finally {
this._store.dispatch(setTxn(false));
this._txn = Math.max(0, this._txn - 1);
if (!this._txn) {
var changeSet_1 = this._outstandingChanges;
if (changeSet_1) {
this._outstandingChanges = undefined;
this._listeners.forEach(function (l) { return l(changeSet_1); });
}
}
}
};
Object.defineProperty(Store.prototype, "state", {
/**
* Get store object (wrapping Redux method)
* Get store object
*/
get: function () {
return this._store.getState();
return this._store;
},
enumerable: false,
configurable: true
@ -1630,10 +1270,10 @@ var Store = /** @class */ (function () {
* Get active groups from store
*/
get: function () {
var _a = this, groups = _a.groups, choices = _a.choices;
return groups.filter(function (group) {
var _this = this;
return this.state.groups.filter(function (group) {
var isActive = group.active && !group.disabled;
var hasActiveOptions = choices.some(function (choice) { return choice.active && !choice.disabled; });
var hasActiveOptions = _this.state.choices.some(function (choice) { return choice.active && !choice.disabled; });
return isActive && hasActiveOptions;
}, []);
},
@ -1641,7 +1281,7 @@ var Store = /** @class */ (function () {
configurable: true
});
Store.prototype.inTxn = function () {
return this.state.txn > 0;
return this._txn > 0;
};
/**
* Get single choice by it's ID
@ -3246,9 +2886,6 @@ var Choices = /** @class */ (function () {
}
this.initialised = false;
this._store = new Store();
this._initialState = defaultState;
this._currentState = defaultState;
this._prevState = defaultState;
this._currentValue = '';
this.config.searchEnabled =
(!this._isTextElement && this.config.searchEnabled) ||
@ -3331,8 +2968,7 @@ var Choices = /** @class */ (function () {
this._createTemplates();
this._createElements();
this._createStructure();
this._store.subscribe(this._render);
this._render();
this._initStore();
this._addEventListeners();
var shouldDisable = (this._isTextElement && !this.config.addItems) ||
this.passedElement.element.hasAttribute('disabled') ||
@ -3356,6 +2992,7 @@ var Choices = /** @class */ (function () {
this.passedElement.reveal();
this.containerOuter.unwrap(this.passedElement.element);
this.clearStore();
this._store._listeners = [];
this._stopSearch();
this._templates = templates;
this.initialised = false;
@ -3761,7 +3398,7 @@ var Choices = /** @class */ (function () {
return this;
};
Choices.prototype.clearStore = function () {
this._store.dispatch(clearAll());
this._store.resetStore();
this._lastAddedChoiceId = 0;
this._lastAddedGroupId = 0;
// @todo integrate with Store
@ -3790,15 +3427,12 @@ var Choices = /** @class */ (function () {
}
}
};
Choices.prototype._render = function () {
Choices.prototype._render = function (changes) {
if (this._store.inTxn()) {
return;
}
this._currentState = this._store.state;
var shouldRenderItems = this._currentState.items !== this._prevState.items;
var stateChanged = this._currentState.choices !== this._prevState.choices ||
this._currentState.groups !== this._prevState.groups ||
shouldRenderItems;
var shouldRenderItems = changes === null || changes === void 0 ? void 0 : changes.items;
var stateChanged = (changes === null || changes === void 0 ? void 0 : changes.choices) || (changes === null || changes === void 0 ? void 0 : changes.groups) || shouldRenderItems;
if (!stateChanged) {
return;
}
@ -3808,7 +3442,6 @@ var Choices = /** @class */ (function () {
if (shouldRenderItems) {
this._renderItems();
}
this._prevState = this._currentState;
};
Choices.prototype._renderChoices = function () {
var _this = this;
@ -5027,7 +4660,6 @@ var Choices = /** @class */ (function () {
});
};
Choices.prototype._createStructure = function () {
var _this = this;
// Hide original element
this.passedElement.conceal();
// Wrap input in container preserving DOM ordering
@ -5055,6 +4687,10 @@ var Choices = /** @class */ (function () {
}
this._highlightPosition = 0;
this._isSearching = false;
};
Choices.prototype._initStore = function () {
var _this = this;
this._store.subscribe(this._render);
this._store.withTxn(function () {
_this._addPredefinedChoices(_this._presetChoices, _this._isSelectOneElement && !_this._hasNonChoicePlaceholder, false);
});

View file

@ -105,14 +105,6 @@
highlighted: highlighted,
}); };
var clearAll = function () { return ({
type: "CLEAR_ALL" /* ActionType.CLEAR_ALL */,
}); };
var setTxn = function (txn) { return ({
type: "SET_TRANSACTION" /* ActionType.SET_TRANSACTION */,
txn: txn,
}); };
/* eslint-disable @typescript-eslint/no-explicit-any */
var getRandomNumber = function (min, max) {
return Math.floor(Math.random() * (max - min) + min);
@ -240,9 +232,6 @@
});
return element.dispatchEvent(event);
};
var cloneObject = function (obj) {
return obj !== undefined ? JSON.parse(JSON.stringify(obj)) : undefined;
};
/**
* Returns an array of keys present on the first but missing on the second object
*/
@ -980,447 +969,85 @@
var ObjectsInConfig = ['fuseOptions', 'classNames'];
/**
* Adapted from React: https://github.com/facebook/react/blob/master/packages/shared/formatProdErrorMessage.js
*
* Do not require this module directly! Use normal throw error calls. These messages will be replaced with error codes
* during build.
* @param {number} code
*/
function formatProdErrorMessage(code) {
return "Minified Redux error #" + code + "; visit https://redux.js.org/Errors?code=" + code + " for the full message or " + 'use the non-minified dev environment for full errors. ';
}
// Inlined version of the `symbol-observable` polyfill
var $$observable = function () {
return typeof Symbol === 'function' && Symbol.observable || '@@observable';
}();
/**
* These are private action types reserved by Redux.
* For any unknown actions, you must return the current state.
* If the current state is undefined, you must return the initial state.
* Do not reference these action types directly in your code.
*/
var randomString = function randomString() {
return Math.random().toString(36).substring(7).split('').join('.');
};
var ActionTypes = {
INIT: "@@redux/INIT" + randomString(),
REPLACE: "@@redux/REPLACE" + randomString(),
PROBE_UNKNOWN_ACTION: function PROBE_UNKNOWN_ACTION() {
return "@@redux/PROBE_UNKNOWN_ACTION" + randomString();
}
};
/**
* @param {any} obj The object to inspect.
* @returns {boolean} True if the argument appears to be a plain object.
*/
function isPlainObject(obj) {
if (typeof obj !== 'object' || obj === null) return false;
var proto = obj;
while (Object.getPrototypeOf(proto) !== null) {
proto = Object.getPrototypeOf(proto);
}
return Object.getPrototypeOf(obj) === proto;
}
/**
* @deprecated
*
* **We recommend using the `configureStore` method
* of the `@reduxjs/toolkit` package**, which replaces `createStore`.
*
* Redux Toolkit is our recommended approach for writing Redux logic today,
* including store setup, reducers, data fetching, and more.
*
* **For more details, please read this Redux docs page:**
* **https://redux.js.org/introduction/why-rtk-is-redux-today**
*
* `configureStore` from Redux Toolkit is an improved version of `createStore` that
* simplifies setup and helps avoid common bugs.
*
* You should not be using the `redux` core package by itself today, except for learning purposes.
* The `createStore` method from the core `redux` package will not be removed, but we encourage
* all users to migrate to using Redux Toolkit for all Redux code.
*
* If you want to use `createStore` without this visual deprecation warning, use
* the `legacy_createStore` import instead:
*
* `import { legacy_createStore as createStore} from 'redux'`
*
*/
function createStore(reducer, preloadedState, enhancer) {
var _ref2;
if (typeof preloadedState === 'function' && typeof enhancer === 'function' || typeof enhancer === 'function' && typeof arguments[3] === 'function') {
throw new Error(formatProdErrorMessage(0) );
}
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState;
preloadedState = undefined;
}
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error(formatProdErrorMessage(1) );
}
return enhancer(createStore)(reducer, preloadedState);
}
if (typeof reducer !== 'function') {
throw new Error(formatProdErrorMessage(2) );
}
var currentReducer = reducer;
var currentState = preloadedState;
var currentListeners = [];
var nextListeners = currentListeners;
var isDispatching = false;
/**
* This makes a shallow copy of currentListeners so we can use
* nextListeners as a temporary list while dispatching.
*
* This prevents any bugs around consumers calling
* subscribe/unsubscribe in the middle of a dispatch.
*/
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice();
}
}
/**
* Reads the state tree managed by the store.
*
* @returns {any} The current state tree of your application.
*/
function getState() {
if (isDispatching) {
throw new Error(formatProdErrorMessage(3) );
}
return currentState;
}
/**
* Adds a change listener. It will be called any time an action is dispatched,
* and some part of the state tree may potentially have changed. You may then
* call `getState()` to read the current state tree inside the callback.
*
* You may call `dispatch()` from a change listener, with the following
* caveats:
*
* 1. The subscriptions are snapshotted just before every `dispatch()` call.
* If you subscribe or unsubscribe while the listeners are being invoked, this
* will not have any effect on the `dispatch()` that is currently in progress.
* However, the next `dispatch()` call, whether nested or not, will use a more
* recent snapshot of the subscription list.
*
* 2. The listener should not expect to see all state changes, as the state
* might have been updated multiple times during a nested `dispatch()` before
* the listener is called. It is, however, guaranteed that all subscribers
* registered before the `dispatch()` started will be called with the latest
* state by the time it exits.
*
* @param {Function} listener A callback to be invoked on every dispatch.
* @returns {Function} A function to remove this change listener.
*/
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error(formatProdErrorMessage(4) );
}
if (isDispatching) {
throw new Error(formatProdErrorMessage(5) );
}
var isSubscribed = true;
ensureCanMutateNextListeners();
nextListeners.push(listener);
return function unsubscribe() {
if (!isSubscribed) {
return;
}
if (isDispatching) {
throw new Error(formatProdErrorMessage(6) );
}
isSubscribed = false;
ensureCanMutateNextListeners();
var index = nextListeners.indexOf(listener);
nextListeners.splice(index, 1);
currentListeners = null;
};
}
/**
* Dispatches an action. It is the only way to trigger a state change.
*
* The `reducer` function, used to create the store, will be called with the
* current state tree and the given `action`. Its return value will
* be considered the **next** state of the tree, and the change listeners
* will be notified.
*
* The base implementation only supports plain object actions. If you want to
* dispatch a Promise, an Observable, a thunk, or something else, you need to
* wrap your store creating function into the corresponding middleware. For
* example, see the documentation for the `redux-thunk` package. Even the
* middleware will eventually dispatch plain object actions using this method.
*
* @param {Object} action A plain object representing what changed. It is
* a good idea to keep actions serializable so you can record and replay user
* sessions, or use the time travelling `redux-devtools`. An action must have
* a `type` property which may not be `undefined`. It is a good idea to use
* string constants for action types.
*
* @returns {Object} For convenience, the same action object you dispatched.
*
* Note that, if you use a custom middleware, it may wrap `dispatch()` to
* return something else (for example, a Promise you can await).
*/
function dispatch(action) {
if (!isPlainObject(action)) {
throw new Error(formatProdErrorMessage(7) );
}
if (typeof action.type === 'undefined') {
throw new Error(formatProdErrorMessage(8) );
}
if (isDispatching) {
throw new Error(formatProdErrorMessage(9) );
}
try {
isDispatching = true;
currentState = currentReducer(currentState, action);
} finally {
isDispatching = false;
}
var listeners = currentListeners = nextListeners;
for (var i = 0; i < listeners.length; i++) {
var listener = listeners[i];
listener();
}
return action;
}
/**
* Replaces the reducer currently used by the store to calculate the state.
*
* You might need this if your app implements code splitting and you want to
* load some of the reducers dynamically. You might also need this if you
* implement a hot reloading mechanism for Redux.
*
* @param {Function} nextReducer The reducer for the store to use instead.
* @returns {void}
*/
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error(formatProdErrorMessage(10) );
}
currentReducer = nextReducer; // This action has a similiar effect to ActionTypes.INIT.
// Any reducers that existed in both the new and old rootReducer
// will receive the previous state. This effectively populates
// the new state tree with any relevant data from the old one.
dispatch({
type: ActionTypes.REPLACE
});
}
/**
* Interoperability point for observable/reactive libraries.
* @returns {observable} A minimal observable of state changes.
* For more information, see the observable proposal:
* https://github.com/tc39/proposal-observable
*/
function observable() {
var _ref;
var outerSubscribe = subscribe;
return _ref = {
/**
* The minimal observable subscription method.
* @param {Object} observer Any object that can be used as an observer.
* The observer object should have a `next` method.
* @returns {subscription} An object with an `unsubscribe` method that can
* be used to unsubscribe the observable from the store, and prevent further
* emission of values from the observable.
*/
subscribe: function subscribe(observer) {
if (typeof observer !== 'object' || observer === null) {
throw new Error(formatProdErrorMessage(11) );
}
function observeState() {
if (observer.next) {
observer.next(getState());
}
}
observeState();
var unsubscribe = outerSubscribe(observeState);
return {
unsubscribe: unsubscribe
};
}
}, _ref[$$observable] = function () {
return this;
}, _ref;
} // When a store is created, an "INIT" action is dispatched so that every
// reducer returns their initial state. This effectively populates
// the initial state tree.
dispatch({
type: ActionTypes.INIT
});
return _ref2 = {
dispatch: dispatch,
subscribe: subscribe,
getState: getState,
replaceReducer: replaceReducer
}, _ref2[$$observable] = observable, _ref2;
}
function assertReducerShape(reducers) {
Object.keys(reducers).forEach(function (key) {
var reducer = reducers[key];
var initialState = reducer(undefined, {
type: ActionTypes.INIT
});
if (typeof initialState === 'undefined') {
throw new Error(formatProdErrorMessage(12) );
}
if (typeof reducer(undefined, {
type: ActionTypes.PROBE_UNKNOWN_ACTION()
}) === 'undefined') {
throw new Error(formatProdErrorMessage(13) );
}
});
}
/**
* Turns an object whose values are different reducer functions, into a single
* reducer function. It will call every child reducer, and gather their results
* into a single state object, whose keys correspond to the keys of the passed
* reducer functions.
*
* @param {Object} reducers An object whose values correspond to different
* reducer functions that need to be combined into one. One handy way to obtain
* it is to use ES6 `import * as reducers` syntax. The reducers may never return
* undefined for any action. Instead, they should return their initial state
* if the state passed to them was undefined, and the current state for any
* unrecognized action.
*
* @returns {Function} A reducer function that invokes every reducer inside the
* passed object, and builds a state object with the same shape.
*/
function combineReducers(reducers) {
var reducerKeys = Object.keys(reducers);
var finalReducers = {};
for (var i = 0; i < reducerKeys.length; i++) {
var key = reducerKeys[i];
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key];
}
}
var finalReducerKeys = Object.keys(finalReducers); // This is used to make sure we don't warn about the same
var shapeAssertionError;
try {
assertReducerShape(finalReducers);
} catch (e) {
shapeAssertionError = e;
}
return function combination(state, action) {
if (state === void 0) {
state = {};
}
if (shapeAssertionError) {
throw shapeAssertionError;
}
var hasChanged = false;
var nextState = {};
for (var _i = 0; _i < finalReducerKeys.length; _i++) {
var _key = finalReducerKeys[_i];
var reducer = finalReducers[_key];
var previousStateForKey = state[_key];
var nextStateForKey = reducer(previousStateForKey, action);
if (typeof nextStateForKey === 'undefined') {
action && action.type;
throw new Error(formatProdErrorMessage(14) );
}
nextState[_key] = nextStateForKey;
hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
}
hasChanged = hasChanged || finalReducerKeys.length !== Object.keys(state).length;
return hasChanged ? nextState : state;
};
}
function items(state, action) {
if (state === void 0) { state = []; }
if (action === void 0) { action = {}; }
function items(s, action) {
var state = s;
var update = false;
switch (action.type) {
case "ADD_ITEM" /* ActionType.ADD_ITEM */: {
var item = action.item;
if (!item.id) {
return state;
if (item.id) {
item.selected = true;
var el = item.element;
if (el) {
el.selected = true;
el.setAttribute('selected', '');
}
update = true;
state.push(item);
state.forEach(function (obj) {
// eslint-disable-next-line no-param-reassign
obj.highlighted = false;
});
}
item.selected = true;
var el = item.element;
if (el) {
el.selected = true;
el.setAttribute('selected', '');
}
return __spreadArray(__spreadArray([], state, true), [item], false).map(function (obj) {
var choice = obj;
choice.highlighted = false;
return choice;
});
break;
}
case "REMOVE_ITEM" /* ActionType.REMOVE_ITEM */: {
var item_1 = action.item;
if (!item_1.id) {
return state;
if (item_1.id) {
item_1.selected = false;
var el = item_1.element;
if (el) {
el.selected = false;
el.removeAttribute('selected');
}
update = true;
state = state.filter(function (choice) { return choice.id !== item_1.id; });
}
item_1.selected = false;
var el = item_1.element;
if (el) {
el.selected = false;
el.removeAttribute('selected');
}
return state.filter(function (choice) { return choice.id !== item_1.id; });
break;
}
case "REMOVE_CHOICE" /* ActionType.REMOVE_CHOICE */: {
var choice_1 = action.choice;
return state.filter(function (item) { return item.id !== choice_1.id; });
update = true;
state = state.filter(function (item) { return item.id !== choice_1.id; });
break;
}
case "HIGHLIGHT_ITEM" /* ActionType.HIGHLIGHT_ITEM */: {
var highlightItemAction_1 = action;
return state.map(function (obj) {
update = true;
state.forEach(function (obj) {
var item = obj;
if (item.id === highlightItemAction_1.item.id) {
item.highlighted = highlightItemAction_1.highlighted;
}
return item;
});
}
default: {
return state;
break;
}
}
return { state: state, update: update };
}
function groups(state, action) {
if (state === void 0) { state = []; }
if (action === void 0) { action = {}; }
function groups(s, action) {
var state = s;
var update = false;
switch (action.type) {
case "ADD_GROUP" /* ActionType.ADD_GROUP */: {
var addGroupAction = action;
return __spreadArray(__spreadArray([], state, true), [addGroupAction.group], false);
update = true;
state.push(addGroupAction.group);
break;
}
case "CLEAR_CHOICES" /* ActionType.CLEAR_CHOICES */: {
return [];
}
default: {
return state;
update = true;
state = [];
break;
}
}
return { state: state, update: update };
}
function choices(state, action) {
if (state === void 0) { state = []; }
if (action === void 0) { action = {}; }
function choices(s, action) {
var state = s;
var update = false;
switch (action.type) {
case "ADD_CHOICE" /* ActionType.ADD_CHOICE */: {
var choice = action.choice;
@ -1429,36 +1056,41 @@
A selected choice has been added to the passed input's value (added as an item)
An active choice appears within the choice dropdown
*/
return __spreadArray(__spreadArray([], state, true), [choice], false);
state.push(choice);
update = true;
break;
}
case "REMOVE_CHOICE" /* ActionType.REMOVE_CHOICE */: {
var choice_1 = action.choice;
return state.filter(function (obj) { return obj.id !== choice_1.id; });
update = true;
state = state.filter(function (obj) { return obj.id !== choice_1.id; });
break;
}
case "ADD_ITEM" /* ActionType.ADD_ITEM */: {
var item = action.item;
// trigger a rebuild of the choices list as the item can not be added multiple times
if (item.id && item.selected) {
return __spreadArray([], state, true);
update = true;
}
return state;
break;
}
case "REMOVE_ITEM" /* ActionType.REMOVE_ITEM */: {
var item = action.item;
// trigger a rebuild of the choices list as the item can be added
if (item.id && !item.selected) {
return __spreadArray([], state, true);
update = true;
}
return state;
break;
}
case "FILTER_CHOICES" /* ActionType.FILTER_CHOICES */: {
var results = action.results;
update = true;
// avoid O(n^2) algorithm complexity when searching/filtering choices
var scoreLookup_1 = [];
results.forEach(function (result) {
scoreLookup_1[result.item.id] = result;
});
return state.map(function (obj) {
state.forEach(function (obj) {
var choice = obj;
var result = scoreLookup_1[choice.id];
if (result !== undefined) {
@ -1471,102 +1103,110 @@
choice.rank = 0;
choice.active = false;
}
return choice;
});
break;
}
case "ACTIVATE_CHOICES" /* ActionType.ACTIVATE_CHOICES */: {
var active_1 = action.active;
return state.map(function (obj) {
update = true;
state.forEach(function (obj) {
var choice = obj;
choice.active = active_1;
return choice;
});
break;
}
case "CLEAR_CHOICES" /* ActionType.CLEAR_CHOICES */: {
return [];
}
default: {
return state;
update = true;
state = [];
break;
}
}
return { state: state, update: update };
}
var general = function (state, action) {
if (state === void 0) { state = 0; }
if (action === void 0) { action = {}; }
switch (action.type) {
case "SET_TRANSACTION" /* ActionType.SET_TRANSACTION */: {
if (action.txn) {
return state + 1;
}
return Math.max(0, state - 1);
}
default: {
return state;
}
}
};
var defaultState = {
groups: [],
items: [],
choices: [],
txn: 0,
};
var appReducer = combineReducers({
items: items,
var reducers = {
groups: groups,
items: items,
choices: choices,
txn: general,
});
var rootReducer = function (passedState, action) {
var state = passedState;
// If we are clearing all items, groups and options we reassign
// state and then pass that state to our proper reducer. This isn't
// mutating our actual state
// See: http://stackoverflow.com/a/35641992
if (action.type === "CLEAR_ALL" /* ActionType.CLEAR_ALL */) {
// preserve the txn state as to allow withTxn to work
var paused = state.txn;
state = cloneObject(defaultState);
state.txn = paused;
}
return appReducer(state, action);
};
/* eslint-disable @typescript-eslint/no-explicit-any */
var Store = /** @class */ (function () {
function Store() {
this._store = createStore(rootReducer, window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__());
this._store = this.defaultState;
this._listeners = [];
this._txn = 0;
}
/**
* Subscribe store to function call (wrapped Redux method)
*/
Store.prototype.subscribe = function (onChange) {
this._store.subscribe(onChange);
Object.defineProperty(Store.prototype, "defaultState", {
// eslint-disable-next-line class-methods-use-this
get: function () {
return {
groups: [],
items: [],
choices: [],
};
},
enumerable: false,
configurable: true
});
// eslint-disable-next-line class-methods-use-this
Store.prototype.changeSet = function (init) {
return {
groups: init,
items: init,
choices: init,
};
};
Store.prototype.resetStore = function () {
this._store = this.defaultState;
var changes = this.changeSet(true);
this._listeners.forEach(function (l) { return l(changes); });
};
Store.prototype.subscribe = function (onChange) {
this._listeners.push(onChange);
};
/**
* Dispatch event to store (wrapped Redux method)
*/
Store.prototype.dispatch = function (action) {
this._store.dispatch(action);
var state = this._store;
var hasChanges = false;
var changes = this._outstandingChanges || this.changeSet(false);
Object.keys(reducers).forEach(function (key) {
var stateUpdate = reducers[key](state[key], action);
if (stateUpdate.update) {
hasChanges = true;
changes[key] = true;
state[key] = stateUpdate.state;
}
});
if (hasChanges) {
if (this._txn) {
this._outstandingChanges = changes;
}
else {
this._listeners.forEach(function (l) { return l(changes); });
}
}
};
Store.prototype.withTxn = function (func) {
this._store.dispatch(setTxn(true));
this._txn++;
try {
func();
}
finally {
this._store.dispatch(setTxn(false));
this._txn = Math.max(0, this._txn - 1);
if (!this._txn) {
var changeSet_1 = this._outstandingChanges;
if (changeSet_1) {
this._outstandingChanges = undefined;
this._listeners.forEach(function (l) { return l(changeSet_1); });
}
}
}
};
Object.defineProperty(Store.prototype, "state", {
/**
* Get store object (wrapping Redux method)
* Get store object
*/
get: function () {
return this._store.getState();
return this._store;
},
enumerable: false,
configurable: true
@ -1636,10 +1276,10 @@
* Get active groups from store
*/
get: function () {
var _a = this, groups = _a.groups, choices = _a.choices;
return groups.filter(function (group) {
var _this = this;
return this.state.groups.filter(function (group) {
var isActive = group.active && !group.disabled;
var hasActiveOptions = choices.some(function (choice) { return choice.active && !choice.disabled; });
var hasActiveOptions = _this.state.choices.some(function (choice) { return choice.active && !choice.disabled; });
return isActive && hasActiveOptions;
}, []);
},
@ -1647,7 +1287,7 @@
configurable: true
});
Store.prototype.inTxn = function () {
return this.state.txn > 0;
return this._txn > 0;
};
/**
* Get single choice by it's ID
@ -2108,9 +1748,6 @@
}
this.initialised = false;
this._store = new Store();
this._initialState = defaultState;
this._currentState = defaultState;
this._prevState = defaultState;
this._currentValue = '';
this.config.searchEnabled =
(!this._isTextElement && this.config.searchEnabled) ||
@ -2193,8 +1830,7 @@
this._createTemplates();
this._createElements();
this._createStructure();
this._store.subscribe(this._render);
this._render();
this._initStore();
this._addEventListeners();
var shouldDisable = (this._isTextElement && !this.config.addItems) ||
this.passedElement.element.hasAttribute('disabled') ||
@ -2218,6 +1854,7 @@
this.passedElement.reveal();
this.containerOuter.unwrap(this.passedElement.element);
this.clearStore();
this._store._listeners = [];
this._stopSearch();
this._templates = templates;
this.initialised = false;
@ -2623,7 +2260,7 @@
return this;
};
Choices.prototype.clearStore = function () {
this._store.dispatch(clearAll());
this._store.resetStore();
this._lastAddedChoiceId = 0;
this._lastAddedGroupId = 0;
// @todo integrate with Store
@ -2652,15 +2289,12 @@
}
}
};
Choices.prototype._render = function () {
Choices.prototype._render = function (changes) {
if (this._store.inTxn()) {
return;
}
this._currentState = this._store.state;
var shouldRenderItems = this._currentState.items !== this._prevState.items;
var stateChanged = this._currentState.choices !== this._prevState.choices ||
this._currentState.groups !== this._prevState.groups ||
shouldRenderItems;
var shouldRenderItems = changes === null || changes === void 0 ? void 0 : changes.items;
var stateChanged = (changes === null || changes === void 0 ? void 0 : changes.choices) || (changes === null || changes === void 0 ? void 0 : changes.groups) || shouldRenderItems;
if (!stateChanged) {
return;
}
@ -2670,7 +2304,6 @@
if (shouldRenderItems) {
this._renderItems();
}
this._prevState = this._currentState;
};
Choices.prototype._renderChoices = function () {
var _this = this;
@ -3889,7 +3522,6 @@
});
};
Choices.prototype._createStructure = function () {
var _this = this;
// Hide original element
this.passedElement.conceal();
// Wrap input in container preserving DOM ordering
@ -3917,6 +3549,10 @@
}
this._highlightPosition = 0;
this._isSearching = false;
};
Choices.prototype._initStore = function () {
var _this = this;
this._store.subscribe(this._render);
this._store.withTxn(function () {
_this._addPredefinedChoices(_this._presetChoices, _this._isSelectOneElement && !_this._hasNonChoicePlaceholder, false);
});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -99,14 +99,6 @@ var highlightItem = function (item, highlighted) { return ({
highlighted: highlighted,
}); };
var clearAll = function () { return ({
type: "CLEAR_ALL" /* ActionType.CLEAR_ALL */,
}); };
var setTxn = function (txn) { return ({
type: "SET_TRANSACTION" /* ActionType.SET_TRANSACTION */,
txn: txn,
}); };
/* eslint-disable @typescript-eslint/no-explicit-any */
var getRandomNumber = function (min, max) {
return Math.floor(Math.random() * (max - min) + min);
@ -234,9 +226,6 @@ var dispatchEvent = function (element, type, customArgs) {
});
return element.dispatchEvent(event);
};
var cloneObject = function (obj) {
return obj !== undefined ? JSON.parse(JSON.stringify(obj)) : undefined;
};
/**
* Returns an array of keys present on the first but missing on the second object
*/
@ -974,447 +963,85 @@ var DEFAULT_CONFIG = {
var ObjectsInConfig = ['fuseOptions', 'classNames'];
/**
* Adapted from React: https://github.com/facebook/react/blob/master/packages/shared/formatProdErrorMessage.js
*
* Do not require this module directly! Use normal throw error calls. These messages will be replaced with error codes
* during build.
* @param {number} code
*/
function formatProdErrorMessage(code) {
return "Minified Redux error #" + code + "; visit https://redux.js.org/Errors?code=" + code + " for the full message or " + 'use the non-minified dev environment for full errors. ';
}
// Inlined version of the `symbol-observable` polyfill
var $$observable = function () {
return typeof Symbol === 'function' && Symbol.observable || '@@observable';
}();
/**
* These are private action types reserved by Redux.
* For any unknown actions, you must return the current state.
* If the current state is undefined, you must return the initial state.
* Do not reference these action types directly in your code.
*/
var randomString = function randomString() {
return Math.random().toString(36).substring(7).split('').join('.');
};
var ActionTypes = {
INIT: "@@redux/INIT" + randomString(),
REPLACE: "@@redux/REPLACE" + randomString(),
PROBE_UNKNOWN_ACTION: function PROBE_UNKNOWN_ACTION() {
return "@@redux/PROBE_UNKNOWN_ACTION" + randomString();
}
};
/**
* @param {any} obj The object to inspect.
* @returns {boolean} True if the argument appears to be a plain object.
*/
function isPlainObject(obj) {
if (typeof obj !== 'object' || obj === null) return false;
var proto = obj;
while (Object.getPrototypeOf(proto) !== null) {
proto = Object.getPrototypeOf(proto);
}
return Object.getPrototypeOf(obj) === proto;
}
/**
* @deprecated
*
* **We recommend using the `configureStore` method
* of the `@reduxjs/toolkit` package**, which replaces `createStore`.
*
* Redux Toolkit is our recommended approach for writing Redux logic today,
* including store setup, reducers, data fetching, and more.
*
* **For more details, please read this Redux docs page:**
* **https://redux.js.org/introduction/why-rtk-is-redux-today**
*
* `configureStore` from Redux Toolkit is an improved version of `createStore` that
* simplifies setup and helps avoid common bugs.
*
* You should not be using the `redux` core package by itself today, except for learning purposes.
* The `createStore` method from the core `redux` package will not be removed, but we encourage
* all users to migrate to using Redux Toolkit for all Redux code.
*
* If you want to use `createStore` without this visual deprecation warning, use
* the `legacy_createStore` import instead:
*
* `import { legacy_createStore as createStore} from 'redux'`
*
*/
function createStore(reducer, preloadedState, enhancer) {
var _ref2;
if (typeof preloadedState === 'function' && typeof enhancer === 'function' || typeof enhancer === 'function' && typeof arguments[3] === 'function') {
throw new Error(formatProdErrorMessage(0) );
}
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState;
preloadedState = undefined;
}
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error(formatProdErrorMessage(1) );
}
return enhancer(createStore)(reducer, preloadedState);
}
if (typeof reducer !== 'function') {
throw new Error(formatProdErrorMessage(2) );
}
var currentReducer = reducer;
var currentState = preloadedState;
var currentListeners = [];
var nextListeners = currentListeners;
var isDispatching = false;
/**
* This makes a shallow copy of currentListeners so we can use
* nextListeners as a temporary list while dispatching.
*
* This prevents any bugs around consumers calling
* subscribe/unsubscribe in the middle of a dispatch.
*/
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice();
}
}
/**
* Reads the state tree managed by the store.
*
* @returns {any} The current state tree of your application.
*/
function getState() {
if (isDispatching) {
throw new Error(formatProdErrorMessage(3) );
}
return currentState;
}
/**
* Adds a change listener. It will be called any time an action is dispatched,
* and some part of the state tree may potentially have changed. You may then
* call `getState()` to read the current state tree inside the callback.
*
* You may call `dispatch()` from a change listener, with the following
* caveats:
*
* 1. The subscriptions are snapshotted just before every `dispatch()` call.
* If you subscribe or unsubscribe while the listeners are being invoked, this
* will not have any effect on the `dispatch()` that is currently in progress.
* However, the next `dispatch()` call, whether nested or not, will use a more
* recent snapshot of the subscription list.
*
* 2. The listener should not expect to see all state changes, as the state
* might have been updated multiple times during a nested `dispatch()` before
* the listener is called. It is, however, guaranteed that all subscribers
* registered before the `dispatch()` started will be called with the latest
* state by the time it exits.
*
* @param {Function} listener A callback to be invoked on every dispatch.
* @returns {Function} A function to remove this change listener.
*/
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error(formatProdErrorMessage(4) );
}
if (isDispatching) {
throw new Error(formatProdErrorMessage(5) );
}
var isSubscribed = true;
ensureCanMutateNextListeners();
nextListeners.push(listener);
return function unsubscribe() {
if (!isSubscribed) {
return;
}
if (isDispatching) {
throw new Error(formatProdErrorMessage(6) );
}
isSubscribed = false;
ensureCanMutateNextListeners();
var index = nextListeners.indexOf(listener);
nextListeners.splice(index, 1);
currentListeners = null;
};
}
/**
* Dispatches an action. It is the only way to trigger a state change.
*
* The `reducer` function, used to create the store, will be called with the
* current state tree and the given `action`. Its return value will
* be considered the **next** state of the tree, and the change listeners
* will be notified.
*
* The base implementation only supports plain object actions. If you want to
* dispatch a Promise, an Observable, a thunk, or something else, you need to
* wrap your store creating function into the corresponding middleware. For
* example, see the documentation for the `redux-thunk` package. Even the
* middleware will eventually dispatch plain object actions using this method.
*
* @param {Object} action A plain object representing what changed. It is
* a good idea to keep actions serializable so you can record and replay user
* sessions, or use the time travelling `redux-devtools`. An action must have
* a `type` property which may not be `undefined`. It is a good idea to use
* string constants for action types.
*
* @returns {Object} For convenience, the same action object you dispatched.
*
* Note that, if you use a custom middleware, it may wrap `dispatch()` to
* return something else (for example, a Promise you can await).
*/
function dispatch(action) {
if (!isPlainObject(action)) {
throw new Error(formatProdErrorMessage(7) );
}
if (typeof action.type === 'undefined') {
throw new Error(formatProdErrorMessage(8) );
}
if (isDispatching) {
throw new Error(formatProdErrorMessage(9) );
}
try {
isDispatching = true;
currentState = currentReducer(currentState, action);
} finally {
isDispatching = false;
}
var listeners = currentListeners = nextListeners;
for (var i = 0; i < listeners.length; i++) {
var listener = listeners[i];
listener();
}
return action;
}
/**
* Replaces the reducer currently used by the store to calculate the state.
*
* You might need this if your app implements code splitting and you want to
* load some of the reducers dynamically. You might also need this if you
* implement a hot reloading mechanism for Redux.
*
* @param {Function} nextReducer The reducer for the store to use instead.
* @returns {void}
*/
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error(formatProdErrorMessage(10) );
}
currentReducer = nextReducer; // This action has a similiar effect to ActionTypes.INIT.
// Any reducers that existed in both the new and old rootReducer
// will receive the previous state. This effectively populates
// the new state tree with any relevant data from the old one.
dispatch({
type: ActionTypes.REPLACE
});
}
/**
* Interoperability point for observable/reactive libraries.
* @returns {observable} A minimal observable of state changes.
* For more information, see the observable proposal:
* https://github.com/tc39/proposal-observable
*/
function observable() {
var _ref;
var outerSubscribe = subscribe;
return _ref = {
/**
* The minimal observable subscription method.
* @param {Object} observer Any object that can be used as an observer.
* The observer object should have a `next` method.
* @returns {subscription} An object with an `unsubscribe` method that can
* be used to unsubscribe the observable from the store, and prevent further
* emission of values from the observable.
*/
subscribe: function subscribe(observer) {
if (typeof observer !== 'object' || observer === null) {
throw new Error(formatProdErrorMessage(11) );
}
function observeState() {
if (observer.next) {
observer.next(getState());
}
}
observeState();
var unsubscribe = outerSubscribe(observeState);
return {
unsubscribe: unsubscribe
};
}
}, _ref[$$observable] = function () {
return this;
}, _ref;
} // When a store is created, an "INIT" action is dispatched so that every
// reducer returns their initial state. This effectively populates
// the initial state tree.
dispatch({
type: ActionTypes.INIT
});
return _ref2 = {
dispatch: dispatch,
subscribe: subscribe,
getState: getState,
replaceReducer: replaceReducer
}, _ref2[$$observable] = observable, _ref2;
}
function assertReducerShape(reducers) {
Object.keys(reducers).forEach(function (key) {
var reducer = reducers[key];
var initialState = reducer(undefined, {
type: ActionTypes.INIT
});
if (typeof initialState === 'undefined') {
throw new Error(formatProdErrorMessage(12) );
}
if (typeof reducer(undefined, {
type: ActionTypes.PROBE_UNKNOWN_ACTION()
}) === 'undefined') {
throw new Error(formatProdErrorMessage(13) );
}
});
}
/**
* Turns an object whose values are different reducer functions, into a single
* reducer function. It will call every child reducer, and gather their results
* into a single state object, whose keys correspond to the keys of the passed
* reducer functions.
*
* @param {Object} reducers An object whose values correspond to different
* reducer functions that need to be combined into one. One handy way to obtain
* it is to use ES6 `import * as reducers` syntax. The reducers may never return
* undefined for any action. Instead, they should return their initial state
* if the state passed to them was undefined, and the current state for any
* unrecognized action.
*
* @returns {Function} A reducer function that invokes every reducer inside the
* passed object, and builds a state object with the same shape.
*/
function combineReducers(reducers) {
var reducerKeys = Object.keys(reducers);
var finalReducers = {};
for (var i = 0; i < reducerKeys.length; i++) {
var key = reducerKeys[i];
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key];
}
}
var finalReducerKeys = Object.keys(finalReducers); // This is used to make sure we don't warn about the same
var shapeAssertionError;
try {
assertReducerShape(finalReducers);
} catch (e) {
shapeAssertionError = e;
}
return function combination(state, action) {
if (state === void 0) {
state = {};
}
if (shapeAssertionError) {
throw shapeAssertionError;
}
var hasChanged = false;
var nextState = {};
for (var _i = 0; _i < finalReducerKeys.length; _i++) {
var _key = finalReducerKeys[_i];
var reducer = finalReducers[_key];
var previousStateForKey = state[_key];
var nextStateForKey = reducer(previousStateForKey, action);
if (typeof nextStateForKey === 'undefined') {
action && action.type;
throw new Error(formatProdErrorMessage(14) );
}
nextState[_key] = nextStateForKey;
hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
}
hasChanged = hasChanged || finalReducerKeys.length !== Object.keys(state).length;
return hasChanged ? nextState : state;
};
}
function items(state, action) {
if (state === void 0) { state = []; }
if (action === void 0) { action = {}; }
function items(s, action) {
var state = s;
var update = false;
switch (action.type) {
case "ADD_ITEM" /* ActionType.ADD_ITEM */: {
var item = action.item;
if (!item.id) {
return state;
if (item.id) {
item.selected = true;
var el = item.element;
if (el) {
el.selected = true;
el.setAttribute('selected', '');
}
update = true;
state.push(item);
state.forEach(function (obj) {
// eslint-disable-next-line no-param-reassign
obj.highlighted = false;
});
}
item.selected = true;
var el = item.element;
if (el) {
el.selected = true;
el.setAttribute('selected', '');
}
return __spreadArray(__spreadArray([], state, true), [item], false).map(function (obj) {
var choice = obj;
choice.highlighted = false;
return choice;
});
break;
}
case "REMOVE_ITEM" /* ActionType.REMOVE_ITEM */: {
var item_1 = action.item;
if (!item_1.id) {
return state;
if (item_1.id) {
item_1.selected = false;
var el = item_1.element;
if (el) {
el.selected = false;
el.removeAttribute('selected');
}
update = true;
state = state.filter(function (choice) { return choice.id !== item_1.id; });
}
item_1.selected = false;
var el = item_1.element;
if (el) {
el.selected = false;
el.removeAttribute('selected');
}
return state.filter(function (choice) { return choice.id !== item_1.id; });
break;
}
case "REMOVE_CHOICE" /* ActionType.REMOVE_CHOICE */: {
var choice_1 = action.choice;
return state.filter(function (item) { return item.id !== choice_1.id; });
update = true;
state = state.filter(function (item) { return item.id !== choice_1.id; });
break;
}
case "HIGHLIGHT_ITEM" /* ActionType.HIGHLIGHT_ITEM */: {
var highlightItemAction_1 = action;
return state.map(function (obj) {
update = true;
state.forEach(function (obj) {
var item = obj;
if (item.id === highlightItemAction_1.item.id) {
item.highlighted = highlightItemAction_1.highlighted;
}
return item;
});
}
default: {
return state;
break;
}
}
return { state: state, update: update };
}
function groups(state, action) {
if (state === void 0) { state = []; }
if (action === void 0) { action = {}; }
function groups(s, action) {
var state = s;
var update = false;
switch (action.type) {
case "ADD_GROUP" /* ActionType.ADD_GROUP */: {
var addGroupAction = action;
return __spreadArray(__spreadArray([], state, true), [addGroupAction.group], false);
update = true;
state.push(addGroupAction.group);
break;
}
case "CLEAR_CHOICES" /* ActionType.CLEAR_CHOICES */: {
return [];
}
default: {
return state;
update = true;
state = [];
break;
}
}
return { state: state, update: update };
}
function choices(state, action) {
if (state === void 0) { state = []; }
if (action === void 0) { action = {}; }
function choices(s, action) {
var state = s;
var update = false;
switch (action.type) {
case "ADD_CHOICE" /* ActionType.ADD_CHOICE */: {
var choice = action.choice;
@ -1423,36 +1050,41 @@ function choices(state, action) {
A selected choice has been added to the passed input's value (added as an item)
An active choice appears within the choice dropdown
*/
return __spreadArray(__spreadArray([], state, true), [choice], false);
state.push(choice);
update = true;
break;
}
case "REMOVE_CHOICE" /* ActionType.REMOVE_CHOICE */: {
var choice_1 = action.choice;
return state.filter(function (obj) { return obj.id !== choice_1.id; });
update = true;
state = state.filter(function (obj) { return obj.id !== choice_1.id; });
break;
}
case "ADD_ITEM" /* ActionType.ADD_ITEM */: {
var item = action.item;
// trigger a rebuild of the choices list as the item can not be added multiple times
if (item.id && item.selected) {
return __spreadArray([], state, true);
update = true;
}
return state;
break;
}
case "REMOVE_ITEM" /* ActionType.REMOVE_ITEM */: {
var item = action.item;
// trigger a rebuild of the choices list as the item can be added
if (item.id && !item.selected) {
return __spreadArray([], state, true);
update = true;
}
return state;
break;
}
case "FILTER_CHOICES" /* ActionType.FILTER_CHOICES */: {
var results = action.results;
update = true;
// avoid O(n^2) algorithm complexity when searching/filtering choices
var scoreLookup_1 = [];
results.forEach(function (result) {
scoreLookup_1[result.item.id] = result;
});
return state.map(function (obj) {
state.forEach(function (obj) {
var choice = obj;
var result = scoreLookup_1[choice.id];
if (result !== undefined) {
@ -1465,102 +1097,110 @@ function choices(state, action) {
choice.rank = 0;
choice.active = false;
}
return choice;
});
break;
}
case "ACTIVATE_CHOICES" /* ActionType.ACTIVATE_CHOICES */: {
var active_1 = action.active;
return state.map(function (obj) {
update = true;
state.forEach(function (obj) {
var choice = obj;
choice.active = active_1;
return choice;
});
break;
}
case "CLEAR_CHOICES" /* ActionType.CLEAR_CHOICES */: {
return [];
}
default: {
return state;
update = true;
state = [];
break;
}
}
return { state: state, update: update };
}
var general = function (state, action) {
if (state === void 0) { state = 0; }
if (action === void 0) { action = {}; }
switch (action.type) {
case "SET_TRANSACTION" /* ActionType.SET_TRANSACTION */: {
if (action.txn) {
return state + 1;
}
return Math.max(0, state - 1);
}
default: {
return state;
}
}
};
var defaultState = {
groups: [],
items: [],
choices: [],
txn: 0,
};
var appReducer = combineReducers({
items: items,
var reducers = {
groups: groups,
items: items,
choices: choices,
txn: general,
});
var rootReducer = function (passedState, action) {
var state = passedState;
// If we are clearing all items, groups and options we reassign
// state and then pass that state to our proper reducer. This isn't
// mutating our actual state
// See: http://stackoverflow.com/a/35641992
if (action.type === "CLEAR_ALL" /* ActionType.CLEAR_ALL */) {
// preserve the txn state as to allow withTxn to work
var paused = state.txn;
state = cloneObject(defaultState);
state.txn = paused;
}
return appReducer(state, action);
};
/* eslint-disable @typescript-eslint/no-explicit-any */
var Store = /** @class */ (function () {
function Store() {
this._store = createStore(rootReducer, window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__());
this._store = this.defaultState;
this._listeners = [];
this._txn = 0;
}
/**
* Subscribe store to function call (wrapped Redux method)
*/
Store.prototype.subscribe = function (onChange) {
this._store.subscribe(onChange);
Object.defineProperty(Store.prototype, "defaultState", {
// eslint-disable-next-line class-methods-use-this
get: function () {
return {
groups: [],
items: [],
choices: [],
};
},
enumerable: false,
configurable: true
});
// eslint-disable-next-line class-methods-use-this
Store.prototype.changeSet = function (init) {
return {
groups: init,
items: init,
choices: init,
};
};
Store.prototype.resetStore = function () {
this._store = this.defaultState;
var changes = this.changeSet(true);
this._listeners.forEach(function (l) { return l(changes); });
};
Store.prototype.subscribe = function (onChange) {
this._listeners.push(onChange);
};
/**
* Dispatch event to store (wrapped Redux method)
*/
Store.prototype.dispatch = function (action) {
this._store.dispatch(action);
var state = this._store;
var hasChanges = false;
var changes = this._outstandingChanges || this.changeSet(false);
Object.keys(reducers).forEach(function (key) {
var stateUpdate = reducers[key](state[key], action);
if (stateUpdate.update) {
hasChanges = true;
changes[key] = true;
state[key] = stateUpdate.state;
}
});
if (hasChanges) {
if (this._txn) {
this._outstandingChanges = changes;
}
else {
this._listeners.forEach(function (l) { return l(changes); });
}
}
};
Store.prototype.withTxn = function (func) {
this._store.dispatch(setTxn(true));
this._txn++;
try {
func();
}
finally {
this._store.dispatch(setTxn(false));
this._txn = Math.max(0, this._txn - 1);
if (!this._txn) {
var changeSet_1 = this._outstandingChanges;
if (changeSet_1) {
this._outstandingChanges = undefined;
this._listeners.forEach(function (l) { return l(changeSet_1); });
}
}
}
};
Object.defineProperty(Store.prototype, "state", {
/**
* Get store object (wrapping Redux method)
* Get store object
*/
get: function () {
return this._store.getState();
return this._store;
},
enumerable: false,
configurable: true
@ -1630,10 +1270,10 @@ var Store = /** @class */ (function () {
* Get active groups from store
*/
get: function () {
var _a = this, groups = _a.groups, choices = _a.choices;
return groups.filter(function (group) {
var _this = this;
return this.state.groups.filter(function (group) {
var isActive = group.active && !group.disabled;
var hasActiveOptions = choices.some(function (choice) { return choice.active && !choice.disabled; });
var hasActiveOptions = _this.state.choices.some(function (choice) { return choice.active && !choice.disabled; });
return isActive && hasActiveOptions;
}, []);
},
@ -1641,7 +1281,7 @@ var Store = /** @class */ (function () {
configurable: true
});
Store.prototype.inTxn = function () {
return this.state.txn > 0;
return this._txn > 0;
};
/**
* Get single choice by it's ID
@ -2102,9 +1742,6 @@ var Choices = /** @class */ (function () {
}
this.initialised = false;
this._store = new Store();
this._initialState = defaultState;
this._currentState = defaultState;
this._prevState = defaultState;
this._currentValue = '';
this.config.searchEnabled =
(!this._isTextElement && this.config.searchEnabled) ||
@ -2187,8 +1824,7 @@ var Choices = /** @class */ (function () {
this._createTemplates();
this._createElements();
this._createStructure();
this._store.subscribe(this._render);
this._render();
this._initStore();
this._addEventListeners();
var shouldDisable = (this._isTextElement && !this.config.addItems) ||
this.passedElement.element.hasAttribute('disabled') ||
@ -2212,6 +1848,7 @@ var Choices = /** @class */ (function () {
this.passedElement.reveal();
this.containerOuter.unwrap(this.passedElement.element);
this.clearStore();
this._store._listeners = [];
this._stopSearch();
this._templates = templates;
this.initialised = false;
@ -2617,7 +2254,7 @@ var Choices = /** @class */ (function () {
return this;
};
Choices.prototype.clearStore = function () {
this._store.dispatch(clearAll());
this._store.resetStore();
this._lastAddedChoiceId = 0;
this._lastAddedGroupId = 0;
// @todo integrate with Store
@ -2646,15 +2283,12 @@ var Choices = /** @class */ (function () {
}
}
};
Choices.prototype._render = function () {
Choices.prototype._render = function (changes) {
if (this._store.inTxn()) {
return;
}
this._currentState = this._store.state;
var shouldRenderItems = this._currentState.items !== this._prevState.items;
var stateChanged = this._currentState.choices !== this._prevState.choices ||
this._currentState.groups !== this._prevState.groups ||
shouldRenderItems;
var shouldRenderItems = changes === null || changes === void 0 ? void 0 : changes.items;
var stateChanged = (changes === null || changes === void 0 ? void 0 : changes.choices) || (changes === null || changes === void 0 ? void 0 : changes.groups) || shouldRenderItems;
if (!stateChanged) {
return;
}
@ -2664,7 +2298,6 @@ var Choices = /** @class */ (function () {
if (shouldRenderItems) {
this._renderItems();
}
this._prevState = this._currentState;
};
Choices.prototype._renderChoices = function () {
var _this = this;
@ -3883,7 +3516,6 @@ var Choices = /** @class */ (function () {
});
};
Choices.prototype._createStructure = function () {
var _this = this;
// Hide original element
this.passedElement.conceal();
// Wrap input in container preserving DOM ordering
@ -3911,6 +3543,10 @@ var Choices = /** @class */ (function () {
}
this._highlightPosition = 0;
this._isSearching = false;
};
Choices.prototype._initStore = function () {
var _this = this;
this._store.subscribe(this._render);
this._store.withTxn(function () {
_this._addPredefinedChoices(_this._presetChoices, _this._isSelectOneElement && !_this._hasNonChoicePlaceholder, false);
});

View file

@ -1,24 +1,21 @@
import { ChoiceFull } from '../interfaces/choice-full';
import { ActionType } from '../interfaces';
import { SearchResult } from '../interfaces/search';
export interface AddChoiceAction {
type: ActionType.ADD_CHOICE;
import { AnyAction } from '../interfaces/store';
export type ChoiceActions = AddChoiceAction | RemoveChoiceAction | FilterChoicesAction | ActivateChoicesAction | ClearChoicesAction;
export interface AddChoiceAction extends AnyAction<ActionType.ADD_CHOICE> {
choice: ChoiceFull;
}
export interface RemoveChoiceAction {
type: ActionType.REMOVE_CHOICE;
export interface RemoveChoiceAction extends AnyAction<ActionType.REMOVE_CHOICE> {
choice: ChoiceFull;
}
export interface FilterChoicesAction {
type: ActionType.FILTER_CHOICES;
export interface FilterChoicesAction extends AnyAction<ActionType.FILTER_CHOICES> {
results: SearchResult<ChoiceFull>[];
}
export interface ActivateChoicesAction {
type: ActionType.ACTIVATE_CHOICES;
export interface ActivateChoicesAction extends AnyAction<ActionType.ACTIVATE_CHOICES> {
active: boolean;
}
export interface ClearChoicesAction {
type: ActionType.CLEAR_CHOICES;
export interface ClearChoicesAction extends AnyAction<ActionType.CLEAR_CHOICES> {
}
export declare const addChoice: (choice: ChoiceFull) => AddChoiceAction;
export declare const removeChoice: (choice: ChoiceFull) => RemoveChoiceAction;

View file

@ -1,7 +1,8 @@
import { GroupFull } from '../interfaces/group-full';
import { ActionType } from '../interfaces';
export interface AddGroupAction {
type: ActionType.ADD_GROUP;
import { AnyAction } from '../interfaces/store';
export type GroupActions = AddGroupAction;
export interface AddGroupAction extends AnyAction<ActionType.ADD_GROUP> {
group: GroupFull;
}
export declare const addGroup: (group: GroupFull) => AddGroupAction;

View file

@ -1,15 +1,14 @@
import { ChoiceFull } from '../interfaces/choice-full';
import { ActionType } from '../interfaces';
export interface AddItemAction {
type: ActionType.ADD_ITEM;
import { AnyAction } from '../interfaces/store';
export type ItemActions = AddItemAction | RemoveItemAction | HighlightItemAction;
export interface AddItemAction extends AnyAction<ActionType.ADD_ITEM> {
item: ChoiceFull;
}
export interface RemoveItemAction {
type: ActionType.REMOVE_ITEM;
export interface RemoveItemAction extends AnyAction<ActionType.REMOVE_ITEM> {
item: ChoiceFull;
}
export interface HighlightItemAction {
type: ActionType.HIGHLIGHT_ITEM;
export interface HighlightItemAction extends AnyAction<ActionType.HIGHLIGHT_ITEM> {
item: ChoiceFull;
highlighted: boolean;
}

View file

@ -1,10 +0,0 @@
import { ActionType } from '../interfaces';
export interface ClearAllAction {
type: ActionType.CLEAR_ALL;
}
export interface SetTransactionStateAction {
type: ActionType.SET_TRANSACTION;
txn: boolean;
}
export declare const clearAll: () => ClearAllAction;
export declare const setTxn: (txn: boolean) => SetTransactionStateAction;

View file

@ -3,7 +3,7 @@ import { InputChoice } from './interfaces/input-choice';
import { InputGroup } from './interfaces/input-group';
import { Notice } from './interfaces/notice';
import { Options } from './interfaces/options';
import { State } from './interfaces/state';
import { StateChangeSet } from './interfaces/state';
import Store from './store/store';
import { ChoiceFull } from './interfaces/choice-full';
import { GroupFull } from './interfaces/group-full';
@ -41,9 +41,6 @@ declare class Choices {
_canAddUserChoices: boolean;
_store: Store;
_templates: Templates;
_initialState: State;
_currentState: State;
_prevState: State;
_lastAddedChoiceId: number;
_lastAddedGroupId: number;
_currentValue: string;
@ -148,7 +145,7 @@ declare class Choices {
clearStore(): this;
clearInput(): this;
_validateConfig(): void;
_render(): void;
_render(changes?: StateChangeSet): void;
_renderChoices(): void;
_renderItems(): void;
_createGroupsFragment(groups: GroupFull[], choices: ChoiceFull[], fragment?: DocumentFragment): DocumentFragment;
@ -200,6 +197,7 @@ declare class Choices {
_createTemplates(): void;
_createElements(): void;
_createStructure(): void;
_initStore(): void;
_addPredefinedChoices(choices: (ChoiceFull | GroupFull)[], selectFirstOption?: boolean, withEvents?: boolean): void;
_findAndSelectChoiceByValue(value: string): boolean;
_generatePlaceholderValue(): string | null;

View file

@ -7,7 +7,5 @@ export declare const enum ActionType {
ADD_GROUP = "ADD_GROUP",
ADD_ITEM = "ADD_ITEM",
REMOVE_ITEM = "REMOVE_ITEM",
HIGHLIGHT_ITEM = "HIGHLIGHT_ITEM",
CLEAR_ALL = "CLEAR_ALL",
SET_TRANSACTION = "SET_TRANSACTION"
HIGHLIGHT_ITEM = "HIGHLIGHT_ITEM"
}

View file

@ -4,5 +4,7 @@ export interface State {
choices: ChoiceFull[];
groups: GroupFull[];
items: ChoiceFull[];
txn: number;
}
export type StateChangeSet = {
[K in keyof State]: boolean;
};

View file

@ -1,10 +1,23 @@
import { State } from './state';
import { StateChangeSet, State } from './state';
import { ChoiceFull } from './choice-full';
import { GroupFull } from './group-full';
import { ActionType } from './action-type';
export interface AnyAction<A extends ActionType = ActionType> {
type: A;
}
export interface StateUpdate<T> {
update: boolean;
state: T;
}
export type Reducer<T> = (state: T, action: AnyAction) => StateUpdate<T>;
export type StoreListener = (changes: StateChangeSet) => void;
export interface Store {
dispatch(action: AnyAction): void;
subscribe(onChange: StoreListener): void;
withTxn(func: () => void): void;
get defaultState(): State;
/**
* Get store object (wrapping Redux method)
* Get store object
*/
get state(): State;
/**

View file

@ -1,6 +1,8 @@
import { AddChoiceAction, RemoveChoiceAction, FilterChoicesAction, ActivateChoicesAction, ClearChoicesAction } from '../actions/choices';
import { AddItemAction, RemoveItemAction } from '../actions/items';
import { ChoiceFull } from '../interfaces/choice-full';
type ActionTypes = AddChoiceAction | RemoveChoiceAction | FilterChoicesAction | ActivateChoicesAction | ClearChoicesAction | AddItemAction | RemoveItemAction | Record<string, never>;
export default function choices(state?: ChoiceFull[], action?: ActionTypes): ChoiceFull[];
import { State } from '../interfaces';
import { StateUpdate } from '../interfaces/store';
import { ChoiceActions } from '../actions/choices';
import { ItemActions } from '../actions/items';
type ActionTypes = ChoiceActions | ItemActions;
type StateType = State['choices'];
export default function choices(s: StateType, action: ActionTypes): StateUpdate<StateType>;
export {};

View file

@ -1,7 +1,8 @@
import { AddGroupAction } from '../actions/groups';
import { ClearChoicesAction } from '../actions/choices';
import { GroupActions } from '../actions/groups';
import { State } from '../interfaces/state';
import { GroupFull } from '../interfaces/group-full';
type ActionTypes = AddGroupAction | ClearChoicesAction | Record<string, never>;
export default function groups(state?: GroupFull[], action?: ActionTypes): State['groups'];
import { StateUpdate } from '../interfaces/store';
import { ChoiceActions } from '../actions/choices';
type ActionTypes = ChoiceActions | GroupActions;
type StateType = State['groups'];
export default function groups(s: StateType, action: ActionTypes): StateUpdate<StateType>;
export {};

View file

@ -1,5 +0,0 @@
import { AnyAction } from 'redux';
import { State } from '../interfaces';
export declare const defaultState: State;
declare const rootReducer: (passedState: State, action: AnyAction) => object;
export default rootReducer;

View file

@ -1,7 +1,8 @@
import { AddItemAction, RemoveItemAction, HighlightItemAction } from '../actions/items';
import { ItemActions } from '../actions/items';
import { State } from '../interfaces/state';
import { RemoveChoiceAction } from '../actions/choices';
import { ChoiceFull } from '../interfaces/choice-full';
type ActionTypes = AddItemAction | RemoveChoiceAction | RemoveItemAction | HighlightItemAction | Record<string, never>;
export default function items(state?: ChoiceFull[], action?: ActionTypes): State['items'];
import { ChoiceActions } from '../actions/choices';
import { StateUpdate } from '../interfaces/store';
type ActionTypes = ChoiceActions | ItemActions;
type StateType = State['items'];
export default function items(s: StateType, action: ActionTypes): StateUpdate<StateType>;
export {};

View file

@ -1,5 +0,0 @@
import { SetTransactionStateAction } from '../actions/misc';
import { State } from '../interfaces/state';
type ActionTypes = SetTransactionStateAction | Record<string, never>;
declare const general: (state?: number, action?: ActionTypes) => State["txn"];
export default general;

View file

@ -1,22 +1,20 @@
import { Store as ReduxStore, AnyAction } from 'redux';
import { Store as IStore } from '../interfaces/store';
import { State } from '../interfaces/state';
import { AnyAction, Store as IStore, StoreListener } from '../interfaces/store';
import { StateChangeSet, State } from '../interfaces/state';
import { ChoiceFull } from '../interfaces/choice-full';
import { GroupFull } from '../interfaces/group-full';
export default class Store implements IStore {
_store: ReduxStore;
constructor();
/**
* Subscribe store to function call (wrapped Redux method)
*/
subscribe(onChange: () => void): void;
/**
* Dispatch event to store (wrapped Redux method)
*/
_store: State;
_listeners: StoreListener[];
_txn: number;
_outstandingChanges?: StateChangeSet;
get defaultState(): State;
changeSet(init: boolean): StateChangeSet;
resetStore(): void;
subscribe(onChange: StoreListener): void;
dispatch(action: AnyAction): void;
withTxn(func: () => void): void;
/**
* Get store object (wrapping Redux method)
* Get store object
*/
get state(): State;
/**

View file

@ -1,30 +1,36 @@
import { ChoiceFull } from '../interfaces/choice-full';
import { ActionType } from '../interfaces';
import { SearchResult } from '../interfaces/search';
import { AnyAction } from '../interfaces/store';
export interface AddChoiceAction {
type: ActionType.ADD_CHOICE;
export type ChoiceActions =
| AddChoiceAction
| RemoveChoiceAction
| FilterChoicesAction
| ActivateChoicesAction
| ClearChoicesAction;
export interface AddChoiceAction extends AnyAction<ActionType.ADD_CHOICE> {
choice: ChoiceFull;
}
export interface RemoveChoiceAction {
type: ActionType.REMOVE_CHOICE;
export interface RemoveChoiceAction
extends AnyAction<ActionType.REMOVE_CHOICE> {
choice: ChoiceFull;
}
export interface FilterChoicesAction {
type: ActionType.FILTER_CHOICES;
export interface FilterChoicesAction
extends AnyAction<ActionType.FILTER_CHOICES> {
results: SearchResult<ChoiceFull>[];
}
export interface ActivateChoicesAction {
type: ActionType.ACTIVATE_CHOICES;
export interface ActivateChoicesAction
extends AnyAction<ActionType.ACTIVATE_CHOICES> {
active: boolean;
}
export interface ClearChoicesAction {
type: ActionType.CLEAR_CHOICES;
}
export interface ClearChoicesAction
extends AnyAction<ActionType.CLEAR_CHOICES> {}
export const addChoice = (choice: ChoiceFull): AddChoiceAction => ({
type: ActionType.ADD_CHOICE,

View file

@ -1,8 +1,10 @@
import { GroupFull } from '../interfaces/group-full';
import { ActionType } from '../interfaces';
import { AnyAction } from '../interfaces/store';
export interface AddGroupAction {
type: ActionType.ADD_GROUP;
export type GroupActions = AddGroupAction;
export interface AddGroupAction extends AnyAction<ActionType.ADD_GROUP> {
group: GroupFull;
}

View file

@ -1,18 +1,22 @@
import { ChoiceFull } from '../interfaces/choice-full';
import { ActionType } from '../interfaces';
import { AnyAction } from '../interfaces/store';
export interface AddItemAction {
type: ActionType.ADD_ITEM;
export type ItemActions =
| AddItemAction
| RemoveItemAction
| HighlightItemAction;
export interface AddItemAction extends AnyAction<ActionType.ADD_ITEM> {
item: ChoiceFull;
}
export interface RemoveItemAction {
type: ActionType.REMOVE_ITEM;
export interface RemoveItemAction extends AnyAction<ActionType.REMOVE_ITEM> {
item: ChoiceFull;
}
export interface HighlightItemAction {
type: ActionType.HIGHLIGHT_ITEM;
export interface HighlightItemAction
extends AnyAction<ActionType.HIGHLIGHT_ITEM> {
item: ChoiceFull;
highlighted: boolean;
}

View file

@ -1,19 +0,0 @@
import { ActionType } from '../interfaces';
export interface ClearAllAction {
type: ActionType.CLEAR_ALL;
}
export interface SetTransactionStateAction {
type: ActionType.SET_TRANSACTION;
txn: boolean;
}
export const clearAll = (): ClearAllAction => ({
type: ActionType.CLEAR_ALL,
});
export const setTxn = (txn: boolean): SetTransactionStateAction => ({
type: ActionType.SET_TRANSACTION,
txn,
});

View file

@ -8,7 +8,6 @@ import {
} from './actions/choices';
import { addGroup } from './actions/groups';
import { addItem, highlightItem, removeItem } from './actions/items';
import { clearAll } from './actions/misc';
import {
Container,
Dropdown,
@ -23,7 +22,7 @@ import { InputChoice } from './interfaces/input-choice';
import { InputGroup } from './interfaces/input-group';
import { Notice } from './interfaces/notice';
import { Options, ObjectsInConfig } from './interfaces/options';
import { State } from './interfaces/state';
import { StateChangeSet } from './interfaces/state';
import {
diff,
generateId,
@ -35,7 +34,6 @@ import {
sortByRank,
strToEl,
} from './lib/utils';
import { defaultState } from './reducers';
import Store from './store/store';
import templates, { escapeForTemplate } from './templates';
import { mapInputToChoice } from './lib/choice-input';
@ -131,12 +129,6 @@ class Choices {
_templates: Templates;
_initialState: State;
_currentState: State;
_prevState: State;
_lastAddedChoiceId: number = 0;
_lastAddedGroupId: number = 0;
@ -273,9 +265,6 @@ class Choices {
this.initialised = false;
this._store = new Store();
this._initialState = defaultState;
this._currentState = defaultState;
this._prevState = defaultState;
this._currentValue = '';
this.config.searchEnabled =
(!this._isTextElement && this.config.searchEnabled) ||
@ -358,10 +347,7 @@ class Choices {
this._createTemplates();
this._createElements();
this._createStructure();
this._store.subscribe(this._render);
this._render();
this._initStore();
this._addEventListeners();
const shouldDisable =
@ -393,6 +379,7 @@ class Choices {
this.containerOuter.unwrap(this.passedElement.element);
this.clearStore();
this._store._listeners = [];
this._stopSearch();
this._templates = templates;
@ -898,7 +885,7 @@ class Choices {
}
clearStore(): this {
this._store.dispatch(clearAll());
this._store.resetStore();
this._lastAddedChoiceId = 0;
this._lastAddedGroupId = 0;
// @todo integrate with Store
@ -941,19 +928,14 @@ class Choices {
}
}
_render(): void {
_render(changes?: StateChangeSet): void {
if (this._store.inTxn()) {
return;
}
this._currentState = this._store.state;
const shouldRenderItems =
this._currentState.items !== this._prevState.items;
const shouldRenderItems = changes?.items;
const stateChanged =
this._currentState.choices !== this._prevState.choices ||
this._currentState.groups !== this._prevState.groups ||
shouldRenderItems;
changes?.choices || changes?.groups || shouldRenderItems;
if (!stateChanged) {
return;
@ -966,8 +948,6 @@ class Choices {
if (shouldRenderItems) {
this._renderItems();
}
this._prevState = this._currentState;
}
_renderChoices(): void {
@ -2570,6 +2550,11 @@ class Choices {
this._highlightPosition = 0;
this._isSearching = false;
}
_initStore(): void {
this._store.subscribe(this._render);
this._store.withTxn(() => {
this._addPredefinedChoices(
this._presetChoices,

View file

@ -8,6 +8,4 @@ export const enum ActionType {
ADD_ITEM = 'ADD_ITEM',
REMOVE_ITEM = 'REMOVE_ITEM',
HIGHLIGHT_ITEM = 'HIGHLIGHT_ITEM',
CLEAR_ALL = 'CLEAR_ALL',
SET_TRANSACTION = 'SET_TRANSACTION',
}

View file

@ -5,5 +5,8 @@ export interface State {
choices: ChoiceFull[];
groups: GroupFull[];
items: ChoiceFull[];
txn: number;
}
export type StateChangeSet = {
[K in keyof State]: boolean;
};

View file

@ -1,12 +1,32 @@
import { State } from './state';
import { StateChangeSet, State } from './state';
import { ChoiceFull } from './choice-full';
import { GroupFull } from './group-full';
import { ActionType } from './action-type';
export interface AnyAction<A extends ActionType = ActionType> {
type: A;
}
export interface StateUpdate<T> {
update: boolean;
state: T;
}
export type Reducer<T> = (state: T, action: AnyAction) => StateUpdate<T>;
export type StoreListener = (changes: StateChangeSet) => void;
export interface Store {
dispatch(action: AnyAction): void;
subscribe(onChange: StoreListener): void;
withTxn(func: () => void): void;
get defaultState(): State;
/**
* Get store object (wrapping Redux method)
* Get store object
*/
get state(): State;

View file

@ -1,77 +1,73 @@
import {
AddChoiceAction,
RemoveChoiceAction,
FilterChoicesAction,
ActivateChoicesAction,
ClearChoicesAction,
} from '../actions/choices';
import { AddItemAction, RemoveItemAction } from '../actions/items';
import { ChoiceFull } from '../interfaces/choice-full';
import { ActionType } from '../interfaces';
import { ActionType, State } from '../interfaces';
import { StateUpdate } from '../interfaces/store';
import { ChoiceActions } from '../actions/choices';
import { ItemActions } from '../actions/items';
import { SearchResult } from '../interfaces/search';
import { ChoiceFull } from '../interfaces/choice-full';
type ActionTypes =
| AddChoiceAction
| RemoveChoiceAction
| FilterChoicesAction
| ActivateChoicesAction
| ClearChoicesAction
| AddItemAction
| RemoveItemAction
| Record<string, never>;
type ActionTypes = ChoiceActions | ItemActions;
type StateType = State['choices'];
export default function choices(
state: ChoiceFull[] = [],
action: ActionTypes = {},
): ChoiceFull[] {
s: StateType,
action: ActionTypes,
): StateUpdate<StateType> {
let state = s;
let update = false;
switch (action.type) {
case ActionType.ADD_CHOICE: {
const { choice } = action as AddChoiceAction;
const { choice } = action;
/*
A disabled choice appears in the choice dropdown but cannot be selected
A selected choice has been added to the passed input's value (added as an item)
An active choice appears within the choice dropdown
*/
return [...state, choice];
state.push(choice);
update = true;
break;
}
case ActionType.REMOVE_CHOICE: {
const { choice } = action as RemoveChoiceAction;
const { choice } = action;
return state.filter((obj) => obj.id !== choice.id);
update = true;
state = state.filter((obj) => obj.id !== choice.id);
break;
}
case ActionType.ADD_ITEM: {
const { item } = action as AddItemAction;
const { item } = action;
// trigger a rebuild of the choices list as the item can not be added multiple times
if (item.id && item.selected) {
return [...state];
update = true;
}
return state;
break;
}
case ActionType.REMOVE_ITEM: {
const { item } = action as RemoveItemAction;
const { item } = action;
// trigger a rebuild of the choices list as the item can be added
if (item.id && !item.selected) {
return [...state];
update = true;
}
return state;
break;
}
case ActionType.FILTER_CHOICES: {
const { results } = action as FilterChoicesAction;
const { results } = action;
update = true;
// avoid O(n^2) algorithm complexity when searching/filtering choices
const scoreLookup: SearchResult<ChoiceFull>[] = [];
results.forEach((result) => {
scoreLookup[result.item.id] = result;
});
return state.map((obj) => {
state.forEach((obj) => {
const choice = obj;
const result = scoreLookup[choice.id];
if (result !== undefined) {
@ -83,28 +79,33 @@ export default function choices(
choice.rank = 0;
choice.active = false;
}
return choice;
});
break;
}
case ActionType.ACTIVATE_CHOICES: {
const { active } = action as ActivateChoicesAction;
const { active } = action;
return state.map((obj) => {
update = true;
state.forEach((obj) => {
const choice = obj;
choice.active = active;
return choice;
});
break;
}
case ActionType.CLEAR_CHOICES: {
return [];
update = true;
state = [];
break;
}
default: {
return state;
}
default:
break;
}
return { state, update };
}

View file

@ -1,28 +1,37 @@
import { AddGroupAction } from '../actions/groups';
import { ClearChoicesAction } from '../actions/choices';
import { GroupActions } from '../actions/groups';
import { State } from '../interfaces/state';
import { GroupFull } from '../interfaces/group-full';
import { ActionType } from '../interfaces';
import { StateUpdate } from '../interfaces/store';
import { ChoiceActions } from '../actions/choices';
type ActionTypes = AddGroupAction | ClearChoicesAction | Record<string, never>;
type ActionTypes = ChoiceActions | GroupActions;
type StateType = State['groups'];
export default function groups(
state: GroupFull[] = [],
action: ActionTypes = {},
): State['groups'] {
s: StateType,
action: ActionTypes,
): StateUpdate<StateType> {
let state = s;
let update = false;
switch (action.type) {
case ActionType.ADD_GROUP: {
const addGroupAction = action as AddGroupAction;
const addGroupAction = action;
return [...state, addGroupAction.group];
update = true;
state.push(addGroupAction.group);
break;
}
case ActionType.CLEAR_CHOICES: {
return [];
update = true;
state = [];
break;
}
default: {
return state;
}
default:
break;
}
return { state, update };
}

View file

@ -1,39 +0,0 @@
import { AnyAction, combineReducers } from 'redux';
import items from './items';
import groups from './groups';
import choices from './choices';
import txn from './txn';
import { cloneObject } from '../lib/utils';
import { ActionType, State } from '../interfaces';
export const defaultState: State = {
groups: [],
items: [],
choices: [],
txn: 0,
};
const appReducer = combineReducers({
items,
groups,
choices,
txn,
});
const rootReducer = (passedState: State, action: AnyAction): object => {
let state = passedState;
// If we are clearing all items, groups and options we reassign
// state and then pass that state to our proper reducer. This isn't
// mutating our actual state
// See: http://stackoverflow.com/a/35641992
if (action.type === ActionType.CLEAR_ALL) {
// preserve the txn state as to allow withTxn to work
const paused = state.txn;
state = cloneObject(defaultState);
state.txn = paused;
}
return appReducer(state, action);
};
export default rootReducer;

View file

@ -1,83 +1,82 @@
import {
AddItemAction,
RemoveItemAction,
HighlightItemAction,
} from '../actions/items';
import { ItemActions } from '../actions/items';
import { State } from '../interfaces/state';
import { RemoveChoiceAction } from '../actions/choices';
import { ChoiceFull } from '../interfaces/choice-full';
import { ChoiceActions } from '../actions/choices';
import { ActionType } from '../interfaces';
import { StateUpdate } from '../interfaces/store';
type ActionTypes =
| AddItemAction
| RemoveChoiceAction
| RemoveItemAction
| HighlightItemAction
| Record<string, never>;
type ActionTypes = ChoiceActions | ItemActions;
type StateType = State['items'];
export default function items(
state: ChoiceFull[] = [],
action: ActionTypes = {},
): State['items'] {
s: StateType,
action: ActionTypes,
): StateUpdate<StateType> {
let state = s;
let update = false;
switch (action.type) {
case ActionType.ADD_ITEM: {
const { item } = action as AddItemAction;
if (!item.id) {
return state;
const { item } = action;
if (item.id) {
item.selected = true;
const el = item.element as HTMLOptionElement | undefined;
if (el) {
el.selected = true;
el.setAttribute('selected', '');
}
update = true;
state.push(item);
state.forEach((obj) => {
// eslint-disable-next-line no-param-reassign
obj.highlighted = false;
});
}
item.selected = true;
const el = item.element as HTMLOptionElement;
if (el) {
el.selected = true;
el.setAttribute('selected', '');
}
return [...state, item].map((obj) => {
const choice = obj;
choice.highlighted = false;
return choice;
});
break;
}
case ActionType.REMOVE_ITEM: {
const { item } = action as RemoveItemAction;
if (!item.id) {
return state;
}
const { item } = action;
if (item.id) {
item.selected = false;
const el = item.element as HTMLOptionElement | undefined;
if (el) {
el.selected = false;
el.removeAttribute('selected');
}
item.selected = false;
const el = item.element as HTMLOptionElement;
if (el) {
el.selected = false;
el.removeAttribute('selected');
update = true;
state = state.filter((choice) => choice.id !== item.id);
}
return state.filter((choice) => choice.id !== item.id);
break;
}
case ActionType.REMOVE_CHOICE: {
const { choice } = action as RemoveChoiceAction;
const { choice } = action;
return state.filter((item) => item.id !== choice.id);
update = true;
state = state.filter((item) => item.id !== choice.id);
break;
}
case ActionType.HIGHLIGHT_ITEM: {
const highlightItemAction = action as HighlightItemAction;
const highlightItemAction = action;
return state.map((obj) => {
update = true;
state.forEach((obj) => {
const item = obj;
if (item.id === highlightItemAction.item.id) {
item.highlighted = highlightItemAction.highlighted;
}
return item;
});
break;
}
default: {
return state;
}
default:
break;
}
return { state, update };
}

View file

@ -1,23 +0,0 @@
import { SetTransactionStateAction } from '../actions/misc';
import { State } from '../interfaces/state';
import { ActionType } from '../interfaces';
type ActionTypes = SetTransactionStateAction | Record<string, never>;
const general = (state: number = 0, action: ActionTypes = {}): State['txn'] => {
switch (action.type) {
case ActionType.SET_TRANSACTION: {
if (action.txn) {
return state + 1;
}
return Math.max(0, state - 1);
}
default: {
return state;
}
}
};
export default general;

View file

@ -1,51 +1,109 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { createStore, Store as ReduxStore, AnyAction } from 'redux';
import { Store as IStore } from '../interfaces/store';
import { State } from '../interfaces/state';
import rootReducer from '../reducers/index';
import { setTxn } from '../actions/misc';
import {
AnyAction,
Reducer,
Store as IStore,
StoreListener,
} from '../interfaces/store';
import { StateChangeSet, State } from '../interfaces/state';
import { ChoiceFull } from '../interfaces/choice-full';
import { GroupFull } from '../interfaces/group-full';
import items from '../reducers/items';
import groups from '../reducers/groups';
import choices from '../reducers/choices';
type ReducerList = { [K in keyof State]: Reducer<State[K]> };
const reducers: ReducerList = {
groups,
items,
choices,
} as const;
export default class Store implements IStore {
_store: ReduxStore;
_store: State = this.defaultState;
constructor() {
this._store = createStore(
rootReducer,
(window as any).__REDUX_DEVTOOLS_EXTENSION__ &&
(window as any).__REDUX_DEVTOOLS_EXTENSION__(),
);
_listeners: StoreListener[] = [];
_txn: number = 0;
_outstandingChanges?: StateChangeSet;
// eslint-disable-next-line class-methods-use-this
get defaultState(): State {
return {
groups: [],
items: [],
choices: [],
};
}
/**
* Subscribe store to function call (wrapped Redux method)
*/
subscribe(onChange: () => void): void {
this._store.subscribe(onChange);
// eslint-disable-next-line class-methods-use-this
changeSet(init: boolean): StateChangeSet {
return {
groups: init,
items: init,
choices: init,
};
}
resetStore(): void {
this._store = this.defaultState;
const changes = this.changeSet(true);
this._listeners.forEach((l) => l(changes));
}
subscribe(onChange: StoreListener): void {
this._listeners.push(onChange);
}
/**
* Dispatch event to store (wrapped Redux method)
*/
dispatch(action: AnyAction): void {
this._store.dispatch(action);
const state = this._store;
let hasChanges = false;
const changes = this._outstandingChanges || this.changeSet(false);
Object.keys(reducers).forEach((key: string) => {
const stateUpdate = (reducers[key] as Reducer<unknown>)(
state[key],
action,
);
if (stateUpdate.update) {
hasChanges = true;
changes[key] = true;
state[key] = stateUpdate.state;
}
});
if (hasChanges) {
if (this._txn) {
this._outstandingChanges = changes;
} else {
this._listeners.forEach((l) => l(changes));
}
}
}
withTxn(func: () => void): void {
this._store.dispatch(setTxn(true));
this._txn++;
try {
func();
} finally {
this._store.dispatch(setTxn(false));
this._txn = Math.max(0, this._txn - 1);
if (!this._txn) {
const changeSet = this._outstandingChanges;
if (changeSet) {
this._outstandingChanges = undefined;
this._listeners.forEach((l) => l(changeSet));
}
}
}
}
/**
* Get store object (wrapping Redux method)
* Get store object
*/
get state(): State {
return this._store.getState();
return this._store;
}
/**
@ -98,11 +156,9 @@ export default class Store implements IStore {
* Get active groups from store
*/
get activeGroups(): GroupFull[] {
const { groups, choices } = this;
return groups.filter((group) => {
return this.state.groups.filter((group) => {
const isActive = group.active && !group.disabled;
const hasActiveOptions = choices.some(
const hasActiveOptions = this.state.choices.some(
(choice) => choice.active && !choice.disabled,
);
@ -111,7 +167,7 @@ export default class Store implements IStore {
}
inTxn(): boolean {
return this.state.txn > 0;
return this._txn > 0;
}
/**

View file

@ -1,39 +0,0 @@
import { expect } from 'chai';
import * as actions from '../../../src/scripts/actions/misc';
import { ActionType } from '../../../src';
describe('actions/misc', () => {
describe('clearAll action', () => {
it('returns CLEAR_ALL action', () => {
const expectedAction: actions.ClearAllAction = {
type: ActionType.CLEAR_ALL,
};
expect(actions.clearAll()).to.deep.equal(expectedAction);
});
});
describe('setTxn action', () => {
describe('setting paused state to true', () => {
it('returns expected action', () => {
const expectedAction: actions.SetTransactionStateAction = {
type: ActionType.SET_TRANSACTION,
txn: true,
};
expect(actions.setTxn(true)).to.deep.equal(expectedAction);
});
});
describe('setting paused state to false', () => {
it('returns expected action', () => {
const expectedAction: actions.SetTransactionStateAction = {
type: ActionType.SET_TRANSACTION,
txn: false,
};
expect(actions.setTxn(false)).to.deep.equal(expectedAction);
});
});
});
});

View file

@ -352,8 +352,8 @@ describe('choices', () => {
expect(storeSubscribeSpy.lastCall.args[0]).to.equal(instance._render);
});
it('fires initial render', () => {
expect(renderSpy.called).to.equal(true);
it('does not fire initial render with no items or choices', () => {
expect(renderSpy.called).to.equal(false);
});
it('adds event listeners', () => {
@ -633,9 +633,8 @@ describe('choices', () => {
expect(
passedElementTriggerEventStub.lastCall.args[0],
).to.deep.equal(EventType.showDropdown);
expect(
passedElementTriggerEventStub.lastCall.args[1],
).to.undefined;
expect(passedElementTriggerEventStub.lastCall.args[1]).to
.undefined;
done(true);
});
}));
@ -737,9 +736,8 @@ describe('choices', () => {
expect(
passedElementTriggerEventStub.lastCall.args[0],
).to.deep.equal(EventType.hideDropdown);
expect(
passedElementTriggerEventStub.lastCall.args[1],
).to.undefined;
expect(passedElementTriggerEventStub.lastCall.args[1]).to
.undefined;
done(true);
});
}));
@ -1137,31 +1135,6 @@ describe('choices', () => {
});
});
describe('clearStore', () => {
let storeDispatchStub;
beforeEach(() => {
storeDispatchStub = stub();
instance._store.dispatch = storeDispatchStub;
output = instance.clearStore();
});
afterEach(() => {
instance._store.dispatch.reset();
});
it('returns this', () => {
expect(output).to.deep.equal(instance);
});
it('dispatches clearAll action', () => {
expect(storeDispatchStub.lastCall.args[0]).to.deep.equal({
type: ActionType.CLEAR_ALL,
});
});
});
describe('clearInput', () => {
let inputClearSpy;
let storeDispatchStub;

View file

@ -3,15 +3,9 @@ import choices from '../../../src/scripts/reducers/choices';
import { cloneObject } from '../../../src/scripts/lib/utils';
import { ChoiceFull } from '../../../src/scripts/interfaces/choice-full';
import { ActionType } from '../../../src';
import { defaultState } from '../../../src/scripts/reducers';
import { StateUpdate } from '../../../src/scripts/interfaces/store';
describe('reducers/choices', () => {
it('should return same state when no action matches', () => {
expect(choices(defaultState.choices, {} as any)).to.equal(
defaultState.choices,
);
});
describe('when choices do not exist', () => {
describe('ADD_CHOICE', () => {
const choice: ChoiceFull = {
@ -34,9 +28,12 @@ describe('reducers/choices', () => {
describe('passing expected values', () => {
it('adds choice', () => {
const expectedResponse = [choice];
const expectedResponse: StateUpdate<ChoiceFull[]> = {
update: true,
state: [choice],
};
const actualResponse = choices(undefined, {
const actualResponse = choices([], {
type: ActionType.ADD_CHOICE,
choice: cloneObject(choice),
});
@ -51,9 +48,12 @@ describe('reducers/choices', () => {
const item = Object.assign(cloneObject(choice), {
placeholder: false,
});
const expectedResponse = [item];
const expectedResponse: StateUpdate<ChoiceFull[]> = {
update: true,
state: [item],
};
const actualResponse = choices(undefined, {
const actualResponse = choices([], {
type: ActionType.ADD_CHOICE,
choice: cloneObject(item),
});
@ -126,7 +126,7 @@ describe('reducers/choices', () => {
rank,
},
],
}).find((choice) => choice.id === id);
}).state.find((choice) => choice.id === id);
expect(actualResponse).to.deep.equal(expectedResponse);
});
@ -134,16 +134,19 @@ describe('reducers/choices', () => {
describe('ACTIVATE_CHOICES', () => {
it('sets active flag to passed value', () => {
const expectedResponse = [
{
...state[0],
active: true,
},
{
...state[1],
active: true,
},
] as ChoiceFull[];
const expectedResponse: StateUpdate<ChoiceFull[]> = {
update: true,
state: [
{
...state[0],
active: true,
},
{
...state[1],
active: true,
},
],
};
const actualResponse = choices(cloneObject(state), {
type: ActionType.ACTIVATE_CHOICES,
@ -154,43 +157,21 @@ describe('reducers/choices', () => {
});
});
describe('REMOVE_CHOICE', () => {
it('the choice is removed', () => {
const choice = state[0];
const expectedResponse = state.filter((s) => s.id !== choice.id);
const actualResponse = choices(cloneObject(state), {
type: ActionType.REMOVE_CHOICE,
choice: cloneObject(choice),
});
expect(actualResponse).to.deep.equal(expectedResponse);
});
});
describe('CLEAR_CHOICES', () => {
it('restores to defaultState', () => {
const expectedResponse = defaultState.choices;
const actualResponse = choices(cloneObject(state), {
type: ActionType.CLEAR_CHOICES,
});
expect(actualResponse).to.deep.equal(expectedResponse);
});
});
describe('ADD_ITEM', () => {
describe('when action has a choice id', () => {
it('disables choice corresponding with id', () => {
const expectedResponse = [
{
...state[0],
},
{
...state[1],
selected: true,
},
] as ChoiceFull[];
const expectedResponse: StateUpdate<ChoiceFull[]> = {
update: true,
state: [
{
...state[0],
},
{
...state[1],
selected: true,
},
],
};
const actualResponse = choices(cloneObject(state), {
type: ActionType.ADD_ITEM,

View file

@ -3,15 +3,9 @@ import groups from '../../../src/scripts/reducers/groups';
import { cloneObject } from '../../../src/scripts/lib/utils';
import { GroupFull } from '../../../src/scripts/interfaces/group-full';
import { ActionType } from '../../../src';
import { defaultState } from '../../../src/scripts/reducers';
import { StateUpdate } from '../../../src/scripts/interfaces/store';
describe('reducers/groups', () => {
it('should return same state when no action matches', () => {
expect(groups(defaultState.groups, {} as any)).to.equal(
defaultState.groups,
);
});
describe('when groups do not exist', () => {
describe('ADD_GROUP', () => {
it('adds group', () => {
@ -23,9 +17,12 @@ describe('reducers/groups', () => {
choices: [],
};
const expectedResponse = [group];
const expectedResponse: StateUpdate<GroupFull[]> = {
update: true,
state: [group],
};
const actualResponse = groups(undefined, {
const actualResponse = groups([], {
type: ActionType.ADD_GROUP,
group: cloneObject(group),
});
@ -34,38 +31,4 @@ describe('reducers/groups', () => {
});
});
});
describe('when groups exist', () => {
let state: GroupFull[];
beforeEach(() => {
state = [
{
id: 1,
label: 'Group one',
active: true,
disabled: false,
choices: [],
},
{
id: 2,
label: 'Group two',
active: true,
disabled: false,
choices: [],
},
];
});
describe('CLEAR_CHOICES', () => {
it('restores to defaultState', () => {
const expectedResponse = defaultState.groups;
const actualResponse = groups(cloneObject(state), {
type: ActionType.CLEAR_CHOICES,
});
expect(actualResponse).to.deep.equal(expectedResponse);
});
});
});
});

View file

@ -1,43 +0,0 @@
import { createStore } from 'redux';
import { expect } from 'chai';
import rootReducer from '../../../src/scripts/reducers';
import groups from '../../../src/scripts/reducers/groups';
import choices from '../../../src/scripts/reducers/choices';
import items from '../../../src/scripts/reducers/items';
import txn from '../../../src/scripts/reducers/txn';
import { ActionType } from '../../../src';
describe('reducers/rootReducer', () => {
const store = createStore(rootReducer);
it('returns expected reducers', () => {
const state = store.getState();
expect(state.groups).to.deep.equal(groups(undefined, {} as any));
expect(state.choices).to.deep.equal(choices(undefined, {} as any));
expect(state.items).to.deep.equal(items(undefined, {} as any));
expect(state.txn).to.deep.equal(txn(undefined, {} as any));
});
describe('CLEAR_ALL', () => {
it('resets state', () => {
const output = rootReducer(
{
items: [1, 2, 3],
groups: [1, 2, 3],
choices: [1, 2, 3],
},
{
type: ActionType.CLEAR_ALL,
},
);
expect(output).to.deep.equal({
items: [],
groups: [],
choices: [],
txn: 0,
});
});
});
});

View file

@ -4,15 +4,9 @@ import { RemoveItemAction } from '../../../src/scripts/actions/items';
import { cloneObject } from '../../../src/scripts/lib/utils';
import { ChoiceFull } from '../../../src/scripts/interfaces/choice-full';
import { ActionType } from '../../../src';
import { defaultState } from '../../../src/scripts/reducers';
import { StateUpdate } from '../../../src/scripts/interfaces/store';
describe('reducers/items', () => {
it('should return same state when no action matches', () => {
expect(items(defaultState.items, {} as any)).to.deep.equal(
defaultState.items,
);
});
describe('when items do not exist', () => {
describe('ADD_ITEM', () => {
const choice: ChoiceFull = {
@ -36,10 +30,10 @@ describe('reducers/items', () => {
let actualResponse: ChoiceFull[];
beforeEach(() => {
actualResponse = items(undefined, {
actualResponse = items([], {
type: ActionType.ADD_ITEM,
item: cloneObject(choice),
});
}).state;
});
it('adds item', () => {
@ -61,9 +55,12 @@ describe('reducers/items', () => {
placeholder: false,
});
it('adds item with placeholder set to false', () => {
const expectedResponse = [item];
const expectedResponse: StateUpdate<ChoiceFull[]> = {
update: true,
state: [item],
};
const actualResponse = items(undefined, {
const actualResponse = items([], {
type: ActionType.ADD_ITEM,
item: cloneObject(item),
});
@ -113,11 +110,14 @@ describe('reducers/items', () => {
describe('REMOVE_ITEM', () => {
it('sets an item to be inactive based on passed ID', () => {
const expectedResponse = [
{
...state[0],
},
] as ChoiceFull[];
const expectedResponse: StateUpdate<ChoiceFull[]> = {
update: true,
state: [
{
...state[0],
},
],
};
const actualResponse = items(cloneObject(state), {
type: ActionType.REMOVE_ITEM,
@ -131,7 +131,10 @@ describe('reducers/items', () => {
describe('REMOVE_CHOICE', () => {
it('the item is removed', () => {
const choice = state[0];
const expectedResponse = state.filter((s) => s.id !== choice.id);
const expectedResponse: StateUpdate<ChoiceFull[]> = {
update: true,
state: state.filter((s) => s.id !== choice.id),
};
const actualResponse = items(cloneObject(state), {
type: ActionType.REMOVE_CHOICE,
@ -144,15 +147,18 @@ describe('reducers/items', () => {
describe('HIGHLIGHT_ITEM', () => {
it('sets an item to be inactive based on passed ID', () => {
const expectedResponse = [
{
...state[0],
},
{
...state[1],
highlighted: true,
},
] as ChoiceFull[];
const expectedResponse: StateUpdate<ChoiceFull[]> = {
update: true,
state: [
{
...state[0],
},
{
...state[1],
highlighted: true,
},
],
};
const actualResponse = items(cloneObject(state), {
type: ActionType.HIGHLIGHT_ITEM,

View file

@ -1,23 +0,0 @@
import { expect } from 'chai';
import general from '../../../src/scripts/reducers/txn';
import { ActionType, State } from '../../../src';
import { defaultState } from '../../../src/scripts/reducers';
describe('reducers/txn', () => {
it('should return same state when no action matches', () => {
expect(general(defaultState.txn, {} as any)).to.equal(defaultState.txn);
});
describe('SET_TRANSACTION', () => {
it('sets transaction state', () => {
const expectedState: State['txn'] = 1;
const actualState = general(undefined, {
type: ActionType.SET_TRANSACTION,
txn: true,
});
expect(expectedState).to.deep.equal(actualState);
});
});
});

View file

@ -1,21 +1,24 @@
import { expect } from 'chai';
import sinon from 'sinon';
import { AnyAction, Unsubscribe } from 'redux';
import Store from '../../../src/scripts/store/store';
import { State } from '../../../src';
import { ActionType, State } from '../../../src';
import { cloneObject } from '../../../src/scripts/lib/utils';
import {
AnyAction,
StoreListener,
} from '../../../src/scripts/interfaces/store';
describe('reducers/store', () => {
let instance: Store;
let subscribeStub: sinon.SinonStub<[listener: () => void], Unsubscribe>;
let dispatchStub: sinon.SinonStub<[action: AnyAction], AnyAction>;
let getStateStub: sinon.SinonStub<[], State>;
let subscribeStub: sinon.SinonStub<[listener: StoreListener], void>;
let dispatchStub: sinon.SinonStub<[action: AnyAction], void>;
let getStateStub: sinon.SinonStub<any[], State>;
beforeEach(() => {
instance = new Store();
subscribeStub = sinon.stub(instance._store, 'subscribe');
dispatchStub = sinon.stub(instance._store, 'dispatch');
getStateStub = sinon.stub(instance._store, 'getState');
subscribeStub = sinon.stub(instance, 'subscribe');
dispatchStub = sinon.stub(instance, 'dispatch');
getStateStub = sinon.stub(instance, 'state');
});
afterEach(() => {
@ -25,17 +28,13 @@ describe('reducers/store', () => {
});
describe('constructor', () => {
it('creates redux store', () => {
expect(instance._store).to.contain.keys([
'subscribe',
'dispatch',
'getState',
]);
it('creates redux-like store', () => {
expect(instance).to.contain.keys(['_store', '_listeners', '_txn']);
});
});
describe('subscribe', () => {
it('wraps redux subscribe method', () => {
it('wraps redux-like subscribe method', () => {
const onChange = (): void => {};
expect(subscribeStub.callCount).to.equal(0);
instance.subscribe(onChange);
@ -45,8 +44,8 @@ describe('reducers/store', () => {
});
describe('dispatch', () => {
it('wraps redux dispatch method', () => {
const action = { type: 'TEST_ACTION' };
it('wraps redux-like dispatch method', () => {
const action: AnyAction = { type: ActionType.CLEAR_CHOICES };
expect(dispatchStub.callCount).to.equal(0);
instance.dispatch(action);
expect(dispatchStub.callCount).to.equal(1);
@ -56,8 +55,8 @@ describe('reducers/store', () => {
describe('state getter', () => {
it('returns state', () => {
const state: State = { items: [], choices: [], groups: [], txn: 0 };
getStateStub.returns(cloneObject(state));
const state: State = { items: [], choices: [], groups: [] };
getStateStub.value(cloneObject(state));
expect(instance.state).to.deep.equal(state);
});
@ -68,7 +67,6 @@ describe('reducers/store', () => {
beforeEach(() => {
state = {
txn: 0,
items: [
{
id: 1,
@ -163,7 +161,7 @@ describe('reducers/store', () => {
],
};
getStateStub.returns(state);
getStateStub.value(state);
});
describe('items getter', () => {