UNPKG

travels

Version:

A fast, framework-agnostic undo/redo core library powered by Mutative JSON Patch

3 lines (2 loc) 10.2 kB
!function(t,s){"object"==typeof exports&&"undefined"!=typeof module?s(exports,require("mutative")):"function"==typeof define&&define.amd?define(["exports","mutative"],s):s((t="undefined"!=typeof globalThis?globalThis:t||self).Travels={},t.Mutative)}(this,function(t,s){"use strict";"function"==typeof SuppressedError&&SuppressedError;const e=t=>"object"==typeof t&&null!==t,i=t=>{if(!e(t))return!1;const s=Object.getPrototypeOf(t);return null===s||s===Object.prototype},a=t=>({patches:t?t.patches.map(t=>[...t]):[],inversePatches:t?t.inversePatches.map(t=>[...t]):[]}),h=t=>{if(null===t||"object"!=typeof t)return t;if(Array.isArray(t))return t.map(h);const s={};for(const e in t)Object.prototype.hasOwnProperty.call(t,e)&&(s[e]=h(t[e]));return s},n=(t,s)=>{if(s&&t&&"object"==typeof t){for(const e in t)Object.prototype.hasOwnProperty.call(t,e)&&(s[e]=h(t[e]));return s}return h(t)},o=t=>!!Array.isArray(t)&&Reflect.ownKeys(t).every(t=>{if("length"===t)return!0;if("symbol"==typeof t)return!1;const s=Number(t);return Number.isInteger(s)&&s>=0&&String(s)===t});class r{constructor(t,s={}){this.listeners=new Set,this.pendingState=null,this.historyCache=null,this.historyVersion=0,this.mutableFallbackWarned=!1,this.subscribe=t=>(this.listeners.add(t),()=>{this.listeners.delete(t)}),this.getState=()=>this.state;const{maxHistory:e=10,initialPatches:i,initialPosition:h=0,autoArchive:o=!0,mutable:r=!1,patchesOptions:c}=s,l=function(t,s){var e={};for(var i in t)Object.prototype.hasOwnProperty.call(t,i)&&s.indexOf(i)<0&&(e[i]=t[i]);if(null!=t&&"function"==typeof Object.getOwnPropertySymbols){var a=0;for(i=Object.getOwnPropertySymbols(t);a<i.length;a++)s.indexOf(i[a])<0&&Object.prototype.propertyIsEnumerable.call(t,i[a])&&(e[i[a]]=t[i[a]])}return e}(s,["maxHistory","initialPatches","initialPosition","autoArchive","mutable","patchesOptions"]);if(e<0)throw new Error(`Travels: maxHistory must be non-negative, but got ${e}`);0===e&&"production"!==process.env.NODE_ENV&&console.warn("Travels: maxHistory is 0, which disables undo/redo history. This is rarely intended."),"production"!==process.env.NODE_ENV&&i&&(Array.isArray(i.patches)&&Array.isArray(i.inversePatches)?i.patches.length!==i.inversePatches.length&&console.error("Travels: initialPatches.patches and initialPatches.inversePatches must have the same length"):console.error("Travels: initialPatches must have 'patches' and 'inversePatches' arrays")),this.state=t,this.initialState=r?n(t):t,this.maxHistory=e,this.autoArchive=o,this.mutable=r,this.options=Object.assign(Object.assign({},l),{enablePatches:null==c||c});const{patches:p,position:P}=this.normalizeInitialHistory(i,h);this.allPatches=p,this.initialPatches=i?a(p):void 0,this.position=P,this.initialPosition=P,this.tempPatches=a()}normalizeInitialHistory(t,s){const e=a(t),i=e.patches.length,h=this.maxHistory>0?this.maxHistory:0,n="number"!=typeof s||!Number.isFinite(s);let o=n?0:s;const r=Math.max(0,Math.min(o,i));if("production"===process.env.NODE_ENV||!n&&r===o||console.warn(`Travels: initialPosition (${s}) is invalid for available patches (${i}). Using ${r} instead.`),o=r,0===i)return{patches:e,position:0};if(0===h)return"production"!==process.env.NODE_ENV&&console.warn(`Travels: maxHistory (${this.maxHistory}) discards persisted history.`),{patches:a(),position:0};if(h>=i)return{patches:e,position:o};const c=i-h,l={patches:e.patches.slice(-h),inversePatches:e.inversePatches.slice(-h)},p=a(l),P=Math.max(0,Math.min(h,o-c));return"production"!==process.env.NODE_ENV&&console.warn(`Travels: initialPatches length (${i}) exceeds maxHistory (${h}). Trimmed to last ${h} steps. Position adjusted to ${P}.`),{patches:p,position:P}}invalidateHistoryCache(){this.historyVersion+=1,this.historyCache=null}notify(){this.listeners.forEach(t=>t(this.state,this.getPatches(),this.position))}hasRootReplacement(t){return t.some(t=>Array.isArray(t.path)&&0===t.path.length&&"replace"===t.op)}setState(t){let a,h;const n=this.mutable&&e(this.state),r="function"==typeof t,c=Array.isArray(this.state),l=Array.isArray(t),p=!c&&!l&&i(this.state)&&i(t),P=c&&l&&o(this.state)&&o(t),u=r&&n||n&&!r&&(P||p);if(!this.mutable||n||this.mutableFallbackWarned||(this.mutableFallbackWarned=!0,"production"!==process.env.NODE_ENV&&console.warn("Travels: mutable mode requires the state root to be an object. Falling back to immutable updates.")),u)[,a,h]=s.create(this.state,r?t:s=>{((t,s)=>{const e=Array.isArray(t),i=Array.isArray(s),a=Reflect.ownKeys(t);for(const i of a)e&&"length"===i||Object.prototype.hasOwnProperty.call(s,i)||delete t[i];e&&i&&(t.length=s.length),Object.assign(t,s)})(s,t)},this.options),s.apply(this.state,a,{mutable:!0}),this.pendingState=this.state;else{const[i,n,o]="function"==typeof t?s.create(this.state,t,this.options):s.create(this.state,()=>e(t)?s.rawReturn(t):t,this.options);a=n,h=o,this.state=i,this.pendingState=i}Promise.resolve().then(()=>{this.pendingState=null});if(!(0===a.length&&0===h.length)){if(this.autoArchive){this.position<this.allPatches.patches.length&&(this.allPatches.patches.splice(this.position,this.allPatches.patches.length-this.position),this.allPatches.inversePatches.splice(this.position,this.allPatches.inversePatches.length-this.position)),this.allPatches.patches.push(a),this.allPatches.inversePatches.push(h),this.position=this.maxHistory<this.allPatches.patches.length?this.maxHistory:this.position+1,this.maxHistory<this.allPatches.patches.length&&(0===this.maxHistory?(this.allPatches.patches=[],this.allPatches.inversePatches=[]):(this.allPatches.patches=this.allPatches.patches.slice(-this.maxHistory),this.allPatches.inversePatches=this.allPatches.inversePatches.slice(-this.maxHistory)))}else{const t=this.position<this.allPatches.patches.length+Number(!!this.tempPatches.patches.length);t&&(this.allPatches.patches.splice(this.position,this.allPatches.patches.length-this.position),this.allPatches.inversePatches.splice(this.position,this.allPatches.inversePatches.length-this.position)),this.tempPatches.patches.length&&!t||(this.position=this.maxHistory<this.allPatches.patches.length+1?this.maxHistory:this.position+1),t&&(this.tempPatches.patches.length=0,this.tempPatches.inversePatches.length=0),this.tempPatches.patches.push(a),this.tempPatches.inversePatches.push(h)}this.invalidateHistoryCache(),this.notify()}}archive(){var t;if(this.autoArchive)return void console.warn("Auto archive is enabled, no need to archive manually");if(!this.tempPatches.patches.length)return;const e=null!==(t=this.pendingState)&&void 0!==t?t:this.state,[,i,a]=s.create(e,t=>s.apply(t,this.tempPatches.inversePatches.flat().reverse()),this.options);this.allPatches.patches.push(a),this.allPatches.inversePatches.push(i),this.maxHistory<this.allPatches.patches.length&&(0===this.maxHistory?(this.allPatches.patches=[],this.allPatches.inversePatches=[]):(this.allPatches.patches=this.allPatches.patches.slice(-this.maxHistory),this.allPatches.inversePatches=this.allPatches.inversePatches.slice(-this.maxHistory))),this.tempPatches.patches.length=0,this.tempPatches.inversePatches.length=0,this.invalidateHistoryCache(),this.notify()}getAllPatches(){return!this.autoArchive&&!!this.tempPatches.patches.length?{patches:this.allPatches.patches.concat([this.tempPatches.patches.flat()]),inversePatches:this.allPatches.inversePatches.concat([this.tempPatches.inversePatches.flat().reverse()])}:this.allPatches}getHistory(){if(this.historyCache&&this.historyCache.version===this.historyVersion)return this.historyCache.history;const t=[this.state];let e=this.state;const i=this.getAllPatches(),a=!this.autoArchive&&i.patches.length>this.maxHistory?i.patches.slice(i.patches.length-this.maxHistory):i.patches,h=!this.autoArchive&&i.inversePatches.length>this.maxHistory?i.inversePatches.slice(i.inversePatches.length-this.maxHistory):i.inversePatches;for(let i=this.position;i<a.length;i++)e=s.apply(e,a[i]),t.push(e);e=this.state;for(let i=this.position-1;i>-1;i--)e=s.apply(e,h[i]),t.unshift(e);return this.historyCache={version:this.historyVersion,history:t},"production"!==process.env.NODE_ENV&&Object.freeze(t),t}go(t){const i=!this.autoArchive&&!!this.tempPatches.patches.length;i&&this.archive();const a=this.getAllPatches(),h=t<this.position;if(t>a.patches.length&&(console.warn(`Can't go forward to position ${t}`),t=a.patches.length),t<0&&(console.warn(`Can't go back to position ${t}`),t=0),t===this.position)return;if(i){const t=a.inversePatches.slice(-1)[0];a.inversePatches[a.inversePatches.length-1]=[...t].reverse()}const n=h?a.inversePatches.slice(-this.maxHistory).slice(t,this.position).flat().reverse():a.patches.slice(-this.maxHistory).slice(this.position,t).flat();this.mutable&&e(this.state)&&!this.hasRootReplacement(n)?s.apply(this.state,n,{mutable:!0}):this.state=s.apply(this.state,n),this.position=t,this.invalidateHistoryCache(),this.notify()}back(t=1){this.go(this.position-t)}forward(t=1){this.go(this.position+t)}reset(){if(this.mutable&&e(this.state)&&e(this.initialState)){const[,t]=s.create(this.state,t=>{for(const s of Object.keys(t))delete t[s];n(this.initialState,t)},this.options);s.apply(this.state,t,{mutable:!0})}else this.state=this.initialState;this.position=this.initialPosition,this.allPatches=a(this.initialPatches),this.tempPatches=a(),this.invalidateHistoryCache(),this.notify()}canBack(){return this.position>0}canForward(){const t=!this.autoArchive&&!!this.tempPatches.patches.length,s=this.getAllPatches();return t?this.position<s.patches.length-1:this.position<s.patches.length}canArchive(){return!this.autoArchive&&!!this.tempPatches.patches.length}getPosition(){return this.position}getPatches(){return!this.autoArchive&&!!this.tempPatches.patches.length?this.getAllPatches():this.allPatches}getControls(){const t=this,s={get position(){return t.getPosition()},getHistory:()=>t.getHistory(),get patches(){return t.getPatches()},back:s=>t.back(s),forward:s=>t.forward(s),reset:()=>t.reset(),go:s=>t.go(s),canBack:()=>t.canBack(),canForward:()=>t.canForward()};return this.autoArchive||(s.archive=()=>t.archive(),s.canArchive=()=>t.canArchive()),s}}t.Travels=r,t.createTravels=function(t,s={}){return new r(t,s)}}); //# sourceMappingURL=index.umd.js.map