travels
Version:
A fast, framework-agnostic undo/redo core library powered by Mutative JSON Patch
3 lines (2 loc) • 9.85 kB
JavaScript
import{create as t,apply as s,rawReturn as e}from"mutative";"function"==typeof SuppressedError&&SuppressedError;const i=t=>"object"==typeof t&&null!==t,h=t=>{if(!i(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]):[]}),o=t=>{if(null===t||"object"!=typeof t)return t;if(Array.isArray(t))return t.map(o);const s={};for(const e in t)Object.prototype.hasOwnProperty.call(t,e)&&(s[e]=o(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]=o(t[e]));return s}return o(t)},r=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 c{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 h=0;for(i=Object.getOwnPropertySymbols(t);h<i.length;h++)s.indexOf(i[h])<0&&Object.prototype.propertyIsEnumerable.call(t,i[h])&&(e[i[h]]=t[i[h]])}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,o="number"!=typeof s||!Number.isFinite(s);let n=o?0:s;const r=Math.max(0,Math.min(n,i));if("production"===process.env.NODE_ENV||!o&&r===n||console.warn(`Travels: initialPosition (${s}) is invalid for available patches (${i}). Using ${r} instead.`),n=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:n};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,n-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(a){let o,n;const c=this.mutable&&i(this.state),l="function"==typeof a,p=Array.isArray(this.state),P=Array.isArray(a),u=!p&&!P&&h(this.state)&&h(a),y=p&&P&&r(this.state)&&r(a),v=l&&c||c&&!l&&(y||u);if(!this.mutable||c||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.")),v)[,o,n]=t(this.state,l?a:t=>{((t,s)=>{const e=Array.isArray(t),i=Array.isArray(s),h=Reflect.ownKeys(t);for(const i of h)e&&"length"===i||Object.prototype.hasOwnProperty.call(s,i)||delete t[i];e&&i&&(t.length=s.length),Object.assign(t,s)})(t,a)},this.options),s(this.state,o,{mutable:!0}),this.pendingState=this.state;else{const[s,h,r]=t(this.state,"function"==typeof a?a:()=>i(a)?e(a):a,this.options);o=h,n=r,this.state=s,this.pendingState=s}Promise.resolve().then(()=>{this.pendingState=null});if(!(0===o.length&&0===n.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(o),this.allPatches.inversePatches.push(n),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(o),this.tempPatches.inversePatches.push(n)}this.invalidateHistoryCache(),this.notify()}}archive(){var e;if(this.autoArchive)return void console.warn("Auto archive is enabled, no need to archive manually");if(!this.tempPatches.patches.length)return;const i=null!==(e=this.pendingState)&&void 0!==e?e:this.state,[,h,a]=t(i,t=>s(t,this.tempPatches.inversePatches.flat().reverse()),this.options);this.allPatches.patches.push(a),this.allPatches.inversePatches.push(h),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(),h=!this.autoArchive&&i.patches.length>this.maxHistory?i.patches.slice(i.patches.length-this.maxHistory):i.patches,a=!this.autoArchive&&i.inversePatches.length>this.maxHistory?i.inversePatches.slice(i.inversePatches.length-this.maxHistory):i.inversePatches;for(let i=this.position;i<h.length;i++)e=s(e,h[i]),t.push(e);e=this.state;for(let i=this.position-1;i>-1;i--)e=s(e,a[i]),t.unshift(e);return this.historyCache={version:this.historyVersion,history:t},"production"!==process.env.NODE_ENV&&Object.freeze(t),t}go(t){const e=!this.autoArchive&&!!this.tempPatches.patches.length;e&&this.archive();const h=this.getAllPatches(),a=t<this.position;if(t>h.patches.length&&(console.warn(`Can't go forward to position ${t}`),t=h.patches.length),t<0&&(console.warn(`Can't go back to position ${t}`),t=0),t===this.position)return;if(e){const t=h.inversePatches.slice(-1)[0];h.inversePatches[h.inversePatches.length-1]=[...t].reverse()}const o=a?h.inversePatches.slice(-this.maxHistory).slice(t,this.position).flat().reverse():h.patches.slice(-this.maxHistory).slice(this.position,t).flat();this.mutable&&i(this.state)&&!this.hasRootReplacement(o)?s(this.state,o,{mutable:!0}):this.state=s(this.state,o),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&&i(this.state)&&i(this.initialState)){const[,e]=t(this.state,t=>{for(const s of Object.keys(t))delete t[s];n(this.initialState,t)},this.options);s(this.state,e,{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}}function l(t,s={}){return new c(t,s)}export{c as Travels,l as createTravels};
//# sourceMappingURL=index.esm.js.map