初始化模版工程

This commit is contained in:
Cloud Bot
2026-03-20 07:33:46 +00:00
commit 23717e0ecd
386 changed files with 51675 additions and 0 deletions

View 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;