mirror of
https://github.com/codex-team/editor.js
synced 2024-05-18 14:26:48 +02:00
* fix isMutationBelongsToElement function: make it return true if the whole text node is deleted inside of some descendant of the passed element * isMutationBelongsToElement function shouldn't return true if some of the ancestors of the passed element were added or deleted, only if the element itself * add test case verifying that 'onChange' is fired when the whole text inside some nested descendant of the block is removed * replace introduced dependency with ToolMock * add comment explaining isMutationBelongsToElement behaviour in case of adding/removing the passed element itself * fix formatting * added some more explanation * added record to the changelog --------- Co-authored-by: Peter Savchenko <specc.dev@gmail.com>
This commit is contained in:
parent
7ff5faa46f
commit
8138ce95b2
|
@ -1,5 +1,9 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
### 2.30.0
|
||||||
|
|
||||||
|
- `Fix` — `onChange` will be called when removing the entire text within a descendant element of a block.
|
||||||
|
|
||||||
### 2.29.1
|
### 2.29.1
|
||||||
|
|
||||||
- `Fix` — Toolbox wont be shown when Slash pressed with along with Shift or Alt
|
- `Fix` — Toolbox wont be shown when Slash pressed with along with Shift or Alt
|
||||||
|
|
|
@ -8,28 +8,28 @@ export function isMutationBelongsToElement(mutationRecord: MutationRecord, eleme
|
||||||
const { type, target, addedNodes, removedNodes } = mutationRecord;
|
const { type, target, addedNodes, removedNodes } = mutationRecord;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* In case of removing the whole text in element, mutation type will be 'childList',
|
* Covers all types of mutations happened to the element or it's descendants with the only one exception - removing/adding the element itself;
|
||||||
* 'removedNodes' will contain text node that is not existed anymore, so we can't check it with 'contains' method
|
|
||||||
* But Target will be the element itself, so we can detect it.
|
|
||||||
*/
|
*/
|
||||||
if (target === element) {
|
if (element.contains(target)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check typing and attributes changes
|
* In case of removing/adding the element itself, mutation type will be 'childList' and 'removedNodes'/'addedNodes' will contain the element.
|
||||||
*/
|
*/
|
||||||
if (['characterData', 'attributes'].includes(type)) {
|
if (type === 'childList') {
|
||||||
const targetElement = target.nodeType === Node.TEXT_NODE ? target.parentNode : target;
|
const elementAddedItself = Array.from(addedNodes).some(node => node === element);
|
||||||
|
|
||||||
return element.contains(targetElement);
|
if (elementAddedItself) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const elementRemovedItself = Array.from(removedNodes).some(node => node === element);
|
||||||
|
|
||||||
|
if (elementRemovedItself) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
return false;
|
||||||
* Check new/removed nodes
|
|
||||||
*/
|
|
||||||
const addedNodesBelongsToBlock = Array.from(addedNodes).some(node => element.contains(node));
|
|
||||||
const removedNodesBelongsToBlock = Array.from(removedNodes).some(node => element.contains(node));
|
|
||||||
|
|
||||||
return addedNodesBelongsToBlock || removedNodesBelongsToBlock;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import Header from '@editorjs/header';
|
import Header from '@editorjs/header';
|
||||||
import Code from '@editorjs/code';
|
import Code from '@editorjs/code';
|
||||||
|
import ToolMock from '../fixtures/tools/ToolMock';
|
||||||
import Delimiter from '@editorjs/delimiter';
|
import Delimiter from '@editorjs/delimiter';
|
||||||
import { BlockAddedMutationType } from '../../../types/events/block/BlockAdded';
|
import { BlockAddedMutationType } from '../../../types/events/block/BlockAdded';
|
||||||
import { BlockChangedMutationType } from '../../../types/events/block/BlockChanged';
|
import { BlockChangedMutationType } from '../../../types/events/block/BlockChanged';
|
||||||
|
@ -787,4 +788,61 @@ describe('onChange callback', () => {
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should be fired when the whole text inside some descendant of the block is removed', () => {
|
||||||
|
/**
|
||||||
|
* Mock of Tool with nested contenteditable element
|
||||||
|
*/
|
||||||
|
class ToolWithContentEditableDescendant extends ToolMock {
|
||||||
|
/**
|
||||||
|
* Creates element with nested contenteditable element
|
||||||
|
*/
|
||||||
|
public render(): HTMLElement {
|
||||||
|
const contenteditable = document.createElement('div');
|
||||||
|
|
||||||
|
contenteditable.contentEditable = 'true';
|
||||||
|
contenteditable.innerText = 'a';
|
||||||
|
contenteditable.setAttribute('data-cy', 'nested-contenteditable');
|
||||||
|
|
||||||
|
const wrapper = document.createElement('div');
|
||||||
|
|
||||||
|
wrapper.appendChild(contenteditable);
|
||||||
|
|
||||||
|
return wrapper;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
tools: {
|
||||||
|
testTool: {
|
||||||
|
class: ToolWithContentEditableDescendant,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
blocks: [
|
||||||
|
{
|
||||||
|
type: 'testTool',
|
||||||
|
data: 'a',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
onChange: (): void => {
|
||||||
|
console.log('something changed');
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
cy.spy(config, 'onChange').as('onChange');
|
||||||
|
cy.createEditor(config).as('editorInstance');
|
||||||
|
|
||||||
|
cy.get('[data-cy=nested-contenteditable]')
|
||||||
|
.click()
|
||||||
|
.clear();
|
||||||
|
|
||||||
|
cy.get('@onChange').should('be.calledWithMatch', EditorJSApiMock, Cypress.sinon.match({
|
||||||
|
type: BlockChangedMutationType,
|
||||||
|
detail: {
|
||||||
|
index: 0,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue