初始化模版工程
This commit is contained in:
202
components/html-editor/lib/core/eventManager/index.ts
Normal file
202
components/html-editor/lib/core/eventManager/index.ts
Normal file
@@ -0,0 +1,202 @@
|
||||
/**
|
||||
* Event Manager
|
||||
* 事件管理器,处理所有DOM事件
|
||||
*/
|
||||
|
||||
import { type HTMLEditor } from '../editor';
|
||||
import { getElementType } from '../utils';
|
||||
|
||||
type EventHandler = (e: Event) => void;
|
||||
|
||||
export class EventManager {
|
||||
private editor: HTMLEditor;
|
||||
private boundHandlers: Map<string, EventHandler>;
|
||||
|
||||
|
||||
constructor(editor: HTMLEditor) {
|
||||
this.editor = editor;
|
||||
this.boundHandlers = new Map<string, EventHandler>();
|
||||
}
|
||||
|
||||
bindAll(): void {
|
||||
this.bindHoverEvents();
|
||||
this.bindClickEvents();
|
||||
this.bindDocumentEvents();
|
||||
}
|
||||
|
||||
|
||||
|
||||
bindHoverEvents(): void {
|
||||
const highlightTracker = this.editor.helperBoxManager!.createHighlightTracker();
|
||||
const handleMouseOver = (e: Event) => {
|
||||
if (this.editor.globalEditable?.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
if (this.editor.isInsertMode) {
|
||||
return;
|
||||
}
|
||||
// 如果正在进行拖动、缩放等操作,不处理 hover
|
||||
if (this.editor.isOperating()) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.stopPropagation();
|
||||
const target = e.target as HTMLElement;
|
||||
const targetTagName = target.tagName.toLowerCase();
|
||||
// 如果包含在忽略的标签中,不处理
|
||||
if ((this.editor.options.ignoreSelectTags || []).includes(targetTagName)) return;
|
||||
|
||||
if (target.classList.contains('selected-element') || target.classList.contains('moveable-line')) return;
|
||||
|
||||
// 先清除容器内所有非选中元素的hover样式
|
||||
if (this.editor.container) {
|
||||
const doc = this.editor.getDoc().document;
|
||||
doc.querySelectorAll('.hover-highlight').forEach((el: Element) => {
|
||||
if (!el.classList.contains('selected-element')) {
|
||||
el.classList.remove('hover-highlight');
|
||||
el.removeAttribute('data-element-type');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
target.classList.add('hover-highlight');
|
||||
target.setAttribute('data-element-type', getElementType(target));
|
||||
|
||||
if (this.editor.options.helperBox && this.editor.helperBoxManager && this.editor.container) {
|
||||
highlightTracker.start(target);
|
||||
// const position = this.editor.getBoundPostion(target);
|
||||
// this.editor.helperBoxManager.updatePostion(position);
|
||||
this.editor.helperBoxManager.visible(!(this.editor.selectedElement === target));
|
||||
}
|
||||
|
||||
this.editor.emit('hover', target);
|
||||
};
|
||||
|
||||
const handleMouseOut = (e: Event) => {
|
||||
if (this.editor.globalEditable?.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
if (this.editor.isInsertMode) {
|
||||
return;
|
||||
}
|
||||
const target = e.target as HTMLElement;
|
||||
if (!target.classList.contains('selected-element')) {
|
||||
target.classList.remove('hover-highlight');
|
||||
target.removeAttribute('data-element-type');
|
||||
}
|
||||
|
||||
// 如果启用了 helperBox,则隐藏
|
||||
if (this.editor.options.helperBox && this.editor.helperBoxManager) {
|
||||
highlightTracker.stop(target);
|
||||
this.editor.helperBoxManager.visible(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
if (this.editor.container) {
|
||||
this.editor.container.addEventListener('mouseover', handleMouseOver);
|
||||
this.editor.container.addEventListener('mouseout', handleMouseOut);
|
||||
|
||||
this.boundHandlers.set('mouseover', handleMouseOver);
|
||||
this.boundHandlers.set('mouseout', handleMouseOut);
|
||||
}
|
||||
}
|
||||
|
||||
bindClickEvents(): void {
|
||||
const handleClick = (e: Event) => {
|
||||
if (this.editor.globalEditable?.isEnabled()) {
|
||||
// 在全局模式下不进行元素选择,让浏览器原生选择/caret工作
|
||||
return;
|
||||
}
|
||||
const target = e.target as HTMLElement;
|
||||
if (this.editor.isInsertMode) {
|
||||
e.stopPropagation();
|
||||
const me = e as MouseEvent;
|
||||
this.editor.insertTextAtPosition(me.clientX, me.clientY);
|
||||
return;
|
||||
}
|
||||
|
||||
const targetTagName = target.tagName.toLowerCase();
|
||||
// 如果包含在忽略的标签中,不处理
|
||||
if ((this.editor.options.ignoreSelectTags || []).includes(targetTagName)) return;
|
||||
|
||||
// 如果正在进行拖动或缩放操作,不处理点击
|
||||
if (this.editor.isDragging || this.editor.isResizing) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 当点击当前容器时,清空其他编辑器的选中样式
|
||||
this.editor.EditorRegistry.clearOthers(this.editor);
|
||||
|
||||
// 如果点击的元素已经被选中且可编辑,不要stopPropagation,让contenteditable正常工作
|
||||
if (target === this.editor.selectedElement && target.getAttribute('contenteditable') === 'true') {
|
||||
// 不阻止事件,让用户可以在元素内部点击定位光标
|
||||
return;
|
||||
}
|
||||
|
||||
// 选中元素
|
||||
e.stopPropagation();
|
||||
this.editor.selectElement(target);
|
||||
};
|
||||
|
||||
if (this.editor.container) {
|
||||
this.editor.container.addEventListener('click', handleClick);
|
||||
this.boundHandlers.set('click', handleClick);
|
||||
}
|
||||
}
|
||||
|
||||
bindDocumentEvents(): void {
|
||||
const handleDocumentMouseDown = (e: Event) => {
|
||||
// 使用 composedPath 处理可能的 DOM 节点在事件处理过程中被移除或在 portal 中的情况
|
||||
const path = e.composedPath?.() || [];
|
||||
const target = (path[0] || e.target) as HTMLElement;
|
||||
|
||||
if (!target || !target.closest) return;
|
||||
|
||||
// 如果正在进行操作,不清除选择
|
||||
if (this.editor.isOperating()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isInEditorUI = path.some(node => {
|
||||
if (!(node instanceof HTMLElement)) return false;
|
||||
return (
|
||||
node.classList.contains('html-editor-toolbar') ||
|
||||
node.classList.contains('html-editor-heading-dropdown') ||
|
||||
node.classList.contains('html-editor-popover') ||
|
||||
node.classList.contains('ant-color-picker-inner') ||
|
||||
node.hasAttribute('data-radix-popper-content-wrapper') ||
|
||||
node.hasAttribute('data-radix-portal') ||
|
||||
node.hasAttribute('data-html-editor-ui')
|
||||
);
|
||||
});
|
||||
|
||||
if (this.editor.container &&
|
||||
!this.editor.container.contains(target) &&
|
||||
!isInEditorUI
|
||||
) {
|
||||
this.editor.clearSelection();
|
||||
}
|
||||
};
|
||||
|
||||
// 使用 mousedown 且开启 capture: true 确保在任何组件阻止冒泡前进行检查
|
||||
document.addEventListener('mousedown', handleDocumentMouseDown, true);
|
||||
this.boundHandlers.set('documentMouseDown', handleDocumentMouseDown);
|
||||
}
|
||||
|
||||
unbindAll(): void {
|
||||
this.boundHandlers.forEach((handler, event) => {
|
||||
if (event === 'documentMouseDown') {
|
||||
document.removeEventListener('mousedown', handler, true);
|
||||
} else if (event === 'documentClick') {
|
||||
document.removeEventListener('click', handler);
|
||||
} else if (this.editor.container) {
|
||||
this.editor.container.removeEventListener(event, handler);
|
||||
}
|
||||
});
|
||||
this.boundHandlers.clear();
|
||||
}
|
||||
}
|
||||
|
||||
export default EventManager;
|
||||
Reference in New Issue
Block a user