@macfja/svelte-undoable
Version:
Memento design pattern in Svelte
236 lines (229 loc) • 8.54 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Undoable = {}));
}(this, (function (exports) { 'use strict';
function noop() { }
function safe_not_equal(a, b) {
return a != a ? b == b : a !== b || ((a && typeof a === 'object') || typeof a === 'function');
}
const subscriber_queue = [];
/**
* Create a `Writable` store that allows both updating and reading by subscription.
* @param {*=}value initial value
* @param {StartStopNotifier=}start start and stop notifications for subscriptions
*/
function writable(value, start = noop) {
let stop;
const subscribers = [];
function set(new_value) {
if (safe_not_equal(value, new_value)) {
value = new_value;
if (stop) { // store is ready
const run_queue = !subscriber_queue.length;
for (let i = 0; i < subscribers.length; i += 1) {
const s = subscribers[i];
s[1]();
subscriber_queue.push(s, value);
}
if (run_queue) {
for (let i = 0; i < subscriber_queue.length; i += 2) {
subscriber_queue[i][0](subscriber_queue[i + 1]);
}
subscriber_queue.length = 0;
}
}
}
}
function update(fn) {
set(fn(value));
}
function subscribe(run, invalidate = noop) {
const subscriber = [run, invalidate];
subscribers.push(subscriber);
if (subscribers.length === 1) {
stop = start(set) || noop;
}
run(value);
return () => {
const index = subscribers.indexOf(subscriber);
if (index !== -1) {
subscribers.splice(index, 1);
}
if (subscribers.length === 0) {
stop();
stop = null;
}
};
}
return { set, update, subscribe };
}
/*
* Copyright MacFJA
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/**
* Create a store with undo/redo feature
* @param {*} initial The initial value of the store
* @param {number?} capacity The maximum number of entry to remember (any number lower than 2 is considered as infinite size)
* @param {function(*: newValue): boolean} accept The validation function to accept a value to be save in memory.<br/>
* Take the new store value as parameter, should return a boolean (`true` to save the value, `false` to dismiss it).<br/>
* If ignore, it will accept all value
* @return {UndoableStore<*>}
*/
function undoable(initial, capacity, accept) {
let states = [initial];
let inAction = true;
let index = 0;
const store = writable(initial);
if (typeof accept !== "function") {
accept = () => true;
}
store.subscribe((newValue) => {
if (!inAction && accept(newValue)) {
if (states.length > index + 1) {
states = states.slice(0, index + 1);
}
states.push(newValue);
index++;
if (capacity > 1 && states.length > capacity) {
states.shift();
index = states.length;
}
}
});
inAction = false;
/**
* @internal
* Change the value to the previous saved state
*/
const undo = () => {
inAction = true;
if (index > 0) {
index--;
}
store.set(states[index]);
inAction = false;
};
/**
* @internal
* Change the value to the next saved state
*/
const redo = () => {
inAction = true;
if (index < states.length - 1) {
index++;
}
store.set(states[index]);
inAction = false;
};
/**
* @internal
* Indicate if the store value can be revert to a previous state
* @return {boolean}
*/
const canUndo = () => {
return index > 0;
};
/**
* @internal
* Indicate if the store value can be change to a next state
* @return {boolean}
*/
const canRedo = () => {
return index < states.length - 1;
};
/**
* @internal
* Revert the value of the store to the oldest state.
* If the parameter is `true`, then the store state history is cleared
* @param {boolean?} clear If `true` the history is cleared
*/
const reset = (clear) => {
inAction = true;
index = 0;
store.set(states[index]);
if (clear) {
states = states.slice(0, 1);
}
inAction = false;
};
/**
* @internal
* Get the number of saved states
* @return {number}
*/
const length = () => {
return states.length;
};
return {
subscribe: store.subscribe,
set: store.set,
update: store.update,
undo,
redo,
canUndo,
canRedo,
reset,
length,
};
}
/**
* Change the value to the previous saved state
* @param {UndoableStore<*>} undoableStore The store to use
*/
function undo(undoableStore) {
undoableStore.undo();
}
/**
* Change the value to the next saved state
* @param {UndoableStore<*>} undoableStore The store to use
*/
function redo(undoableStore) {
undoableStore.redo();
}
/**
* Indicate if the store value can be revert to a previous state
* @param {UndoableStore<*>} undoableStore The store to use
* @return {boolean}
*/
function canUndo(undoableStore) {
return undoableStore.canUndo();
}
/**
* Indicate if the store value can be change to a next state
* @param {UndoableStore<*>} undoableStore The store to use
* @return {boolean}
*/
function canRedo(undoableStore) {
return undoableStore.canRedo();
}
/**
* Revert the value of the store to the oldest state.
* If the second parameter is `true`, then the store state history is cleared
* @param {UndoableStore<*>} undoableStore The store to use
* @param {boolean} clear If `true` the history is cleared
*/
function reset(undoableStore, clear) {
undoableStore.reset(clear);
}
exports.canRedo = canRedo;
exports.canUndo = canUndo;
exports.redo = redo;
exports.reset = reset;
exports.undo = undo;
exports.undoable = undoable;
Object.defineProperty(exports, '__esModule', { value: true });
})));