UNPKG

@wordpress/undo-manager

Version:
175 lines (165 loc) 4.42 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.createUndoManager = createUndoManager; var _isShallowEqual = _interopRequireDefault(require("@wordpress/is-shallow-equal")); /** * WordPress dependencies */ /** * Internal dependencies */ /** * Represents a single change in history. */ /** * Represents changes for a single item. */ /** * Represents a record of history changes. */ /** * The undo manager interface. */ /** * Merge changes for a single item into a record of changes. * * @param changes1 Previous changes * @param changes2 Next changes * * @return Merged changes */ function mergeHistoryChanges(changes1, changes2) { const newChanges = { ...changes1 }; Object.entries(changes2).forEach(([key, value]) => { if (newChanges[key]) { newChanges[key] = { ...newChanges[key], to: value.to }; } else { newChanges[key] = value; } }); return newChanges; } /** * Adds history changes for a single item into a record of changes. * * @param record The record to merge into. * @param changes The changes to merge. */ const addHistoryChangesIntoRecord = (record, changes) => { const existingChangesIndex = record?.findIndex(({ id: recordIdentifier }) => { return typeof recordIdentifier === 'string' ? recordIdentifier === changes.id : (0, _isShallowEqual.default)(recordIdentifier, changes.id); }); const nextRecord = [...record]; if (existingChangesIndex !== -1) { // If the edit is already in the stack leave the initial "from" value. nextRecord[existingChangesIndex] = { id: changes.id, changes: mergeHistoryChanges(nextRecord[existingChangesIndex].changes, changes.changes) }; } else { nextRecord.push(changes); } return nextRecord; }; /** * Creates an undo manager. * * @return Undo manager. */ function createUndoManager() { let history = []; let stagedRecord = []; let offset = 0; const dropPendingRedos = () => { history = history.slice(0, offset || undefined); offset = 0; }; const appendStagedRecordToLatestHistoryRecord = () => { var _history$index; const index = history.length === 0 ? 0 : history.length - 1; let latestRecord = (_history$index = history[index]) !== null && _history$index !== void 0 ? _history$index : []; stagedRecord.forEach(changes => { latestRecord = addHistoryChangesIntoRecord(latestRecord, changes); }); stagedRecord = []; history[index] = latestRecord; }; /** * Checks whether a record is empty. * A record is considered empty if it the changes keep the same values. * Also updates to function values are ignored. * * @param record The record to check. * @return Whether the record is empty. */ const isRecordEmpty = record => { const filteredRecord = record.filter(({ changes }) => { return Object.values(changes).some(({ from, to }) => typeof from !== 'function' && typeof to !== 'function' && !(0, _isShallowEqual.default)(from, to)); }); return !filteredRecord.length; }; return { addRecord(record, isStaged = false) { const isEmpty = !record || isRecordEmpty(record); if (isStaged) { if (isEmpty) { return; } record.forEach(changes => { stagedRecord = addHistoryChangesIntoRecord(stagedRecord, changes); }); } else { dropPendingRedos(); if (stagedRecord.length) { appendStagedRecordToLatestHistoryRecord(); } if (isEmpty) { return; } history.push(record); } }, undo() { if (stagedRecord.length) { dropPendingRedos(); appendStagedRecordToLatestHistoryRecord(); } const undoRecord = history[history.length - 1 + offset]; if (!undoRecord) { return; } offset -= 1; return undoRecord; }, redo() { const redoRecord = history[history.length + offset]; if (!redoRecord) { return; } offset += 1; return redoRecord; }, hasUndo() { return !!history[history.length - 1 + offset]; }, hasRedo() { return !!history[history.length + offset]; } }; } //# sourceMappingURL=index.js.map