UNPKG

@starzhuimeng/formula-editor

Version:

A configurable formula editor with customizable symbols

1,383 lines (1,382 loc) 57.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.FormulaEditor = void 0; const types_1 = require("../types"); const dom_1 = require("../utils/dom"); const parser_1 = require("../utils/parser"); const validator_1 = require("../utils/validator"); const SymbolPanel_1 = require("./SymbolPanel"); /** * 默认符号配置 */ const DEFAULT_SYMBOLS = { basic: [ { value: '=', type: types_1.ElementType.OPERATOR, description: '等号' }, { value: '+', type: types_1.ElementType.OPERATOR, description: '加号' }, { value: '-', type: types_1.ElementType.OPERATOR, description: '减号' }, { value: '×', type: types_1.ElementType.OPERATOR, description: '乘号', insertValue: '*' }, { value: '÷', type: types_1.ElementType.OPERATOR, description: '除号', insertValue: '/' }, { value: '±', type: types_1.ElementType.OPERATOR, description: '正负号' }, { value: '(', type: types_1.ElementType.BRACKET, description: '左圆括号' }, { value: ')', type: types_1.ElementType.BRACKET, description: '右圆括号' }, { value: '[', type: types_1.ElementType.BRACKET, description: '左方括号' }, { value: ']', type: types_1.ElementType.BRACKET, description: '右方括号' }, { value: '{', type: types_1.ElementType.BRACKET, description: '左花括号' }, { value: '}', type: types_1.ElementType.BRACKET, description: '右花括号' }, { value: ',', type: types_1.ElementType.SYMBOL, description: '逗号' }, { value: '.', type: types_1.ElementType.NUMBER, description: '小数点' } ], greek: [ { value: 'α', type: types_1.ElementType.VARIABLE, description: 'Alpha' }, { value: 'β', type: types_1.ElementType.VARIABLE, description: 'Beta' }, { value: 'γ', type: types_1.ElementType.VARIABLE, description: 'Gamma' }, { value: 'δ', type: types_1.ElementType.VARIABLE, description: 'Delta' }, { value: 'ε', type: types_1.ElementType.VARIABLE, description: 'Epsilon' }, { value: 'η', type: types_1.ElementType.VARIABLE, description: 'Eta' }, { value: 'θ', type: types_1.ElementType.VARIABLE, description: 'Theta' }, { value: 'λ', type: types_1.ElementType.VARIABLE, description: 'Lambda' }, { value: 'μ', type: types_1.ElementType.VARIABLE, description: 'Mu' }, { value: 'π', type: types_1.ElementType.VARIABLE, description: 'Pi' }, { value: 'ρ', type: types_1.ElementType.VARIABLE, description: 'Rho' }, { value: 'σ', type: types_1.ElementType.VARIABLE, description: 'Sigma' }, { value: 'τ', type: types_1.ElementType.VARIABLE, description: 'Tau' }, { value: 'φ', type: types_1.ElementType.VARIABLE, description: 'Phi' }, { value: 'ω', type: types_1.ElementType.VARIABLE, description: 'Omega' }, { value: 'Γ', type: types_1.ElementType.VARIABLE, description: 'Gamma(大写)' }, { value: 'Δ', type: types_1.ElementType.VARIABLE, description: 'Delta(大写)' }, { value: 'Θ', type: types_1.ElementType.VARIABLE, description: 'Theta(大写)' }, { value: 'Λ', type: types_1.ElementType.VARIABLE, description: 'Lambda(大写)' }, { value: 'Π', type: types_1.ElementType.VARIABLE, description: 'Pi(大写)' }, { value: 'Σ', type: types_1.ElementType.VARIABLE, description: 'Sigma(大写)' }, { value: 'Φ', type: types_1.ElementType.VARIABLE, description: 'Phi(大写)' }, { value: 'Ψ', type: types_1.ElementType.VARIABLE, description: 'Psi(大写)' }, { value: 'Ω', type: types_1.ElementType.VARIABLE, description: 'Omega(大写)' } ], functions: [ { value: 'sin', type: types_1.ElementType.FUNCTION, description: '正弦函数' }, { value: 'cos', type: types_1.ElementType.FUNCTION, description: '余弦函数' }, { value: 'tan', type: types_1.ElementType.FUNCTION, description: '正切函数' }, { value: 'log', type: types_1.ElementType.FUNCTION, description: '对数函数' }, { value: 'ln', type: types_1.ElementType.FUNCTION, description: '自然对数' }, { value: 'lim', type: types_1.ElementType.FUNCTION, description: '极限' }, { value: 'max', type: types_1.ElementType.FUNCTION, description: '最大值' }, { value: 'min', type: types_1.ElementType.FUNCTION, description: '最小值' } ], special: [ { value: '∞', type: types_1.ElementType.SYMBOL, description: '无穷大' }, { value: '∫', type: types_1.ElementType.INTEGRAL, description: '积分' }, { value: '∬', type: types_1.ElementType.INTEGRAL, description: '二重积分' }, { value: '∭', type: types_1.ElementType.INTEGRAL, description: '三重积分' }, { value: '∮', type: types_1.ElementType.INTEGRAL, description: '曲线积分' }, { value: '∇', type: types_1.ElementType.SYMBOL, description: 'Nabla算子' }, { value: '∂', type: types_1.ElementType.SYMBOL, description: '偏导数' }, { value: '∑', type: types_1.ElementType.SYMBOL, description: '求和' }, { value: '∏', type: types_1.ElementType.SYMBOL, description: '求积' }, { value: '√', type: types_1.ElementType.ROOT, description: '平方根' }, { value: '∛', type: types_1.ElementType.ROOT, description: '立方根' }, { value: '∜', type: types_1.ElementType.ROOT, description: '4次方根' }, { value: '≠', type: types_1.ElementType.OPERATOR, description: '不等于' }, { value: '≈', type: types_1.ElementType.OPERATOR, description: '约等于' }, { value: '≤', type: types_1.ElementType.OPERATOR, description: '小于等于' }, { value: '≥', type: types_1.ElementType.OPERATOR, description: '大于等于' }, { value: '∈', type: types_1.ElementType.SYMBOL, description: '属于' }, { value: '∉', type: types_1.ElementType.SYMBOL, description: '不属于' }, { value: '⊂', type: types_1.ElementType.SYMBOL, description: '子集' }, { value: '⊃', type: types_1.ElementType.SYMBOL, description: '超集' } ] }; /** * 默认样式配置 */ const DEFAULT_STYLES = { fontSize: '16px', color: '#333', backgroundColor: '#ffffff', border: '1px solid #ddd', height: 'auto', width: '100%', symbolPanel: { backgroundColor: '#fff', symbolColor: '#333', borderColor: '#ddd' } }; /** * 公式编辑器类 */ class FormulaEditor { /** * 构造函数 * @param options 编辑器选项 */ constructor(options) { var _a; this.formulaElements = []; this.elementTags = []; this.currentFocus = -1; this.textInputElement = null; this.eventListeners = {}; this.errorMessageElement = null; this.lastValidationResult = null; this.options = this.mergeWithDefaults(options); this.container = options.container; // 创建编辑器容器 this.editorContainer = (0, dom_1.createElement)('div', 'formula-editor-container'); // 初始化symbolButton,会在createToolbar中被赋值 this.symbolButton = (0, dom_1.createElement)('button'); // 创建工具栏 const toolbar = this.createToolbar(); // 创建编辑区域 this.editArea = this.createEditArea(); // 组合编辑器 (0, dom_1.appendChildren)(this.editorContainer, toolbar, this.editArea); // 添加到容器 this.container.appendChild(this.editorContainer); // 创建文本输入框 - 确保在编辑区域初始化后创建 this.createTextInput(); // 添加键盘和点击事件监听 (0, dom_1.addEventListeners)(this.editArea, { keydown: (e) => this.handleKeyDown(e), click: (e) => this.handleAreaClick(e) }); // 创建符号面板 this.symbolPanel = new SymbolPanel_1.SymbolPanel({ symbols: this.options.symbols || DEFAULT_SYMBOLS, onSymbolClick: (symbol) => this.insertSymbol(symbol), container: this.editorContainer, styles: (_a = this.options.styles) === null || _a === void 0 ? void 0 : _a.symbolPanel }); // 初始化编辑器 this.init(); } /** * 合并默认配置 */ mergeWithDefaults(options) { return Object.assign(Object.assign({}, options), { symbols: options.symbols || DEFAULT_SYMBOLS, styles: Object.assign(Object.assign({}, DEFAULT_STYLES), (options.styles || {})) }); } /** * 初始化编辑器 */ init() { var _a; try { // 设置初始公式 if (this.options.initialFormula) { this.setFormula(this.options.initialFormula); } // 设置只读模式 if (this.options.readOnly) { this.setReadOnly(true); } // 隐藏符号面板 this.symbolPanel.hide(); // 确保文本输入框隐藏 this.hideTextInput(); // 创建错误信息显示元素 this.createErrorMessageElement(); // 如果配置了自动验证,验证初始公式 if ((_a = this.options.validation) === null || _a === void 0 ? void 0 : _a.autoValidate) { this.validateFormula(); } } catch (error) { console.error('Error initializing editor:', error); } } /** * 处理区域点击事件 */ handleAreaClick(e) { try { // 确保编辑区域获得焦点 this.editArea.focus(); // 获取点击事件的坐标位置 const clickX = e.clientX; const clickY = e.clientY; // 如果点击的是编辑区域而不是标签 if (e.target === this.editArea) { // 找到最近的标签位置 let closestIndex = 0; let minDistance = Number.MAX_VALUE; // 处理空编辑区域的情况 if (this.elementTags.length === 0) { this.currentFocus = 0; this.updateFocusIndicator(); return; } // 遍历所有标签,找到水平距离最近的位置 this.elementTags.forEach((tag, index) => { const rect = tag.getBoundingClientRect(); const tagCenter = rect.left + (rect.width / 2); const distance = Math.abs(clickX - tagCenter); // 如果这个标签是点击位置右侧的第一个标签,且距离比当前记录的最小距离小 if (distance < minDistance) { minDistance = distance; // 如果点击位置在标签的左半部分,则光标应在标签之前 if (clickX < tagCenter) { closestIndex = index; } else { // 否则光标应在标签之后 closestIndex = index + 1; } } }); // 设置焦点到最近的位置 this.currentFocus = closestIndex; this.updateFocusIndicator(); // 不再自动显示文本输入框,等待用户开始输入 } } catch (error) { console.error('Error in handleAreaClick:', error); } } /** * 将焦点设置到末尾 */ setFocusToEnd() { this.currentFocus = this.elementTags.length; this.updateFocusIndicator(); } /** * 处理键盘事件 */ handleKeyDown(e) { if (this.options.readOnly) { e.preventDefault(); return; } try { // 输出调试信息 console.log('Keyboard event detected:', e.key); switch (e.key) { case 'Backspace': case 'Delete': e.preventDefault(); this.deleteElement(); break; case 'ArrowLeft': e.preventDefault(); this.moveFocusLeft(); this.hideTextInput(); // 导航时隐藏输入框 break; case 'ArrowRight': e.preventDefault(); this.moveFocusRight(); this.hideTextInput(); // 导航时隐藏输入框 break; case 'Home': e.preventDefault(); this.currentFocus = 0; this.updateFocusIndicator(); this.hideTextInput(); // 导航时隐藏输入框 break; case 'End': e.preventDefault(); this.setFocusToEnd(); this.hideTextInput(); // 导航时隐藏输入框 break; case 'Enter': e.preventDefault(); if (this.textInputElement && this.textInputElement.style.display === 'block') { // 如果输入框已显示,则提交内容 this.commitTextInput(); } else { // 否则显示空白输入框 console.log('Enter key pressed, showing text input'); this.forceShowTextInput(); } break; case 'Escape': e.preventDefault(); this.hideTextInput(); break; case ' ': // 空格键 e.preventDefault(); // 空格键直接插入空格符号 this.insertSymbol(' '); break; // 直接处理常用运算符 case '+': case '-': case '*': case '/': case '=': e.preventDefault(); console.log('Operator key detected, inserting directly:', e.key); this.insertSymbol(e.key); break; // 直接处理括号 case '(': case ')': case '[': case ']': case '{': case '}': e.preventDefault(); console.log('Bracket key detected, inserting directly:', e.key); this.insertSymbol(e.key); break; case '.': // 小数点 case ',': // 逗号 e.preventDefault(); console.log('Punctuation key detected, inserting directly:', e.key); this.insertSymbol(e.key); break; default: // 检查是否为数字 if (/^[0-9]$/.test(e.key)) { e.preventDefault(); // 数字直接插入,不显示文本框 console.log('Number key detected, inserting directly:', e.key); this.insertSymbol(e.key); } // 对于其他可打印字符,显示文本输入框并预填充该字符 else if (e.key.length === 1 && !e.ctrlKey && !e.altKey && !e.metaKey) { e.preventDefault(); console.log('Printable character detected:', e.key); this.forceShowTextInput(e.key); } break; } } catch (error) { console.error('Error in handleKeyDown:', error); } } /** * 强制显示文本输入框(跳过常规检查) */ forceShowTextInput(initialText = '') { console.log('Force showing text input with:', initialText); try { // 确保文本输入框存在 if (!this.textInputElement) { console.warn('Text input element is null, creating a new one'); this.createTextInput(); if (!this.textInputElement) { console.error('Failed to create text input element'); return; } } // 获取光标元素和位置 const cursorElement = document.getElementById('formula-cursor-active'); console.log('Cursor element found:', !!cursorElement); if (!cursorElement) { console.warn('No cursor found, using default positioning'); // 如果没有找到光标,就在编辑区域中间显示 const editAreaRect = this.editArea.getBoundingClientRect(); // 使用fixed定位,相对于视口 this.textInputElement.style.position = 'fixed'; this.textInputElement.style.left = `${editAreaRect.left + editAreaRect.width / 2 - 75}px`; this.textInputElement.style.top = `${editAreaRect.top + 30}px`; } else { // 获取光标位置 const cursorRect = cursorElement.getBoundingClientRect(); // 定位到光标位置,确保输入框出现在光标下方 this.textInputElement.style.position = 'fixed'; this.textInputElement.style.left = `${cursorRect.left}px`; this.textInputElement.style.top = `${cursorRect.bottom + 2}px`; } // 设置输入框样式和内容 this.textInputElement.style.width = '150px'; this.textInputElement.style.padding = '5px'; this.textInputElement.style.display = 'block'; this.textInputElement.value = initialText; this.textInputElement.placeholder = '输入公式内容...'; // 确保输入框有很高的z-index值 this.textInputElement.style.zIndex = '9999'; // 确保输入框样式明显但不过分干扰 this.textInputElement.style.border = '1px solid #2196f3'; this.textInputElement.style.backgroundColor = '#e3f2fd'; this.textInputElement.style.borderRadius = '3px'; this.textInputElement.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)'; // 移到body中,确保不受编辑器DOM操作影响 if (this.textInputElement.parentNode !== document.body) { document.body.appendChild(this.textInputElement); } // 设置文本输入框的事件监听 this.textInputElement.onkeydown = (e) => { console.log('Text input keydown:', e.key); this.handleTextInputKeyDown(e); }; this.textInputElement.onblur = () => { console.log('Text input blur event'); setTimeout(() => { this.commitTextInput(); }, 200); }; // 聚焦并设置光标到文本末尾 this.textInputElement.focus(); if (initialText && this.textInputElement.selectionStart !== null) { this.textInputElement.selectionStart = initialText.length; } } catch (error) { console.error('Error in forceShowTextInput:', error); } } /** * 显示文本输入框 */ showTextInput(initialText = '') { console.log('showTextInput called with:', initialText); if (!this.textInputElement || this.options.readOnly) { console.warn('Cannot show text input: input is null or editor is readonly'); return; } try { // 防止重复调用,如果已经显示,则更新值 if (this.textInputElement.style.display === 'block') { console.log('Text input already visible, updating value'); this.textInputElement.value += initialText; this.textInputElement.focus(); return; } // 获取光标位置 const cursorElement = this.editArea.querySelector('.formula-cursor'); const editAreaRect = this.editArea.getBoundingClientRect(); // 将输入框移到body中,确保不受编辑器DOM操作影响 document.body.appendChild(this.textInputElement); // 设置为固定定位 this.textInputElement.style.position = 'fixed'; if (!cursorElement) { console.warn('No cursor found, using default positioning'); // 基本定位 - 放在编辑器顶部中央 this.textInputElement.style.left = `${editAreaRect.left + 20}px`; this.textInputElement.style.top = `${editAreaRect.top + 20}px`; } else { console.log('Cursor found, positioning relative to cursor'); const cursorRect = cursorElement.getBoundingClientRect(); // 定位到光标位置 this.textInputElement.style.left = `${cursorRect.left}px`; this.textInputElement.style.top = `${cursorRect.bottom + 5}px`; } // 显示输入框 this.textInputElement.style.width = '150px'; this.textInputElement.style.margin = '0'; this.textInputElement.style.display = 'block'; // 设置输入框样式确保可见 this.textInputElement.style.backgroundColor = '#e3f2fd'; this.textInputElement.style.zIndex = '9999'; this.textInputElement.style.padding = '5px'; this.textInputElement.style.border = '2px solid #2196f3'; // 设置输入框值 this.textInputElement.value = initialText; console.log('Input value set to:', initialText); // 设置事件监听 this.textInputElement.onkeydown = (e) => { console.log('Text input keydown:', e.key); this.handleTextInputKeyDown(e); }; this.textInputElement.onblur = () => { console.log('Text input blur event'); setTimeout(() => { this.commitTextInput(); }, 200); }; // 聚焦 setTimeout(() => { if (this.textInputElement) { console.log('Focusing text input (delayed)'); this.textInputElement.focus(); if (initialText && this.textInputElement.selectionStart !== null) { this.textInputElement.selectionStart = initialText.length; } } }, 50); } catch (error) { console.error('Error showing text input:', error); this.forceShowTextInput(initialText); } } /** * 隐藏文本输入框 */ hideTextInput() { try { if (this.textInputElement) { this.textInputElement.style.display = 'none'; this.textInputElement.value = ''; // 如果文本输入框被移到了body中,需要移回编辑区域 if (this.textInputElement.parentNode === document.body) { document.body.removeChild(this.textInputElement); this.editArea.appendChild(this.textInputElement); } } } catch (error) { console.error('Error hiding text input:', error); } } /** * 处理文本输入框的键盘事件 */ handleTextInputKeyDown(e) { console.log('Text input keydown handler called with key:', e.key); switch (e.key) { case 'Enter': e.preventDefault(); // Enter键提交输入 this.commitTextInput(); break; case 'Escape': e.preventDefault(); // Escape键取消输入 this.hideTextInput(); this.editArea.focus(); break; case 'Tab': e.preventDefault(); // Tab键提交输入并移动到下一位置 this.commitTextInput(); this.moveFocusRight(); break; case 'ArrowUp': case 'ArrowDown': case 'ArrowLeft': case 'ArrowRight': // 输入框为空时允许方向键移动光标 if (this.textInputElement && this.textInputElement.value.length === 0) { e.preventDefault(); this.hideTextInput(); switch (e.key) { case 'ArrowLeft': this.moveFocusLeft(); break; case 'ArrowRight': this.moveFocusRight(); break; } this.editArea.focus(); } break; case 'Backspace': // 如果输入框为空并且按下Backspace,则删除前一个元素 if (this.textInputElement && this.textInputElement.value.length === 0) { e.preventDefault(); this.hideTextInput(); this.deleteElement(); // 在删除后重新显示光标 this.updateFocusIndicator(); } break; default: // 对于其他按键,使用默认处理 break; } } /** * 提交文本输入框的内容 */ commitTextInput() { if (!this.textInputElement) return; const text = this.textInputElement.value.trim(); if (text) { // 作为单个文本标签插入 this.insertText(text); } this.hideTextInput(); this.editArea.focus(); } /** * 插入整个文本作为一个标签 * @param text 要插入的文本 */ insertText(text) { if (this.options.readOnly) return; // 确定元素类型 let type = types_1.ElementType.VARIABLE; // 默认为变量类型 // 检查是否为数字 if (/^[0-9.]+$/.test(text)) { type = types_1.ElementType.NUMBER; } // 检查是否为运算符 else if (/^[+\-*/=÷×±]+$/.test(text)) { type = types_1.ElementType.OPERATOR; } // 检查是否为函数 else if (['sin', 'cos', 'tan', 'log', 'ln', 'max', 'min'].includes(text)) { type = types_1.ElementType.FUNCTION; } // 检查是否为括号 else if (/^[\(\)\[\]\{\}]+$/.test(text)) { type = types_1.ElementType.BRACKET; } // 检查是否为特殊符号 else if (!/^[a-zA-Z0-9]+$/.test(text)) { type = types_1.ElementType.SYMBOL; } const newElement = { type, value: text, id: (0, dom_1.generateId)() }; // 在当前焦点位置插入元素 if (this.currentFocus === -1) { this.currentFocus = this.formulaElements.length; } this.formulaElements.splice(this.currentFocus, 0, newElement); // 创建元素标签 const elementTag = this.createElementTag(newElement); // 插入到DOM if (this.currentFocus === this.elementTags.length) { this.editArea.appendChild(elementTag); this.elementTags.push(elementTag); } else { this.editArea.insertBefore(elementTag, this.elementTags[this.currentFocus]); this.elementTags.splice(this.currentFocus, 0, elementTag); } // 更新焦点位置 this.currentFocus++; this.updateFocusIndicator(); // 触发变更事件 this.triggerChangeEvent(); } /** * 插入符号 */ insertSymbol(symbolInput) { // 检查是否为只读模式 if (this.options.readOnly) { return; } // 处理符号对象或字符串 let symbol; let type = types_1.ElementType.SYMBOL; if (typeof symbolInput === 'string') { symbol = symbolInput; // 根据符号类型判断 if (/[0-9.]/.test(symbol)) { type = types_1.ElementType.NUMBER; } else if (/[a-zA-Z]/.test(symbol)) { type = types_1.ElementType.VARIABLE; } else if (['+', '-', '×', '÷', '*', '/', '='].includes(symbol)) { type = types_1.ElementType.OPERATOR; } else if (['(', ')', '[', ']', '{', '}'].includes(symbol)) { type = types_1.ElementType.BRACKET; } } else { // 使用传入的插入值或显示值 symbol = symbolInput.insertValue || symbolInput.value; // 使用传入的类型或默认类型 type = symbolInput.type || types_1.ElementType.SYMBOL; } const newElement = { type, value: symbol, id: (0, dom_1.generateId)() }; // 在当前焦点位置插入元素 if (this.currentFocus === -1) { this.currentFocus = this.formulaElements.length; } this.formulaElements.splice(this.currentFocus, 0, newElement); // 创建元素标签 const elementTag = this.createElementTag(newElement); // 添加插入动画类 elementTag.classList.add('formula-element-inserted'); // 动画结束后移除类 setTimeout(() => { elementTag.classList.remove('formula-element-inserted'); }, 300); // 插入到DOM if (this.currentFocus === this.elementTags.length) { this.editArea.appendChild(elementTag); this.elementTags.push(elementTag); } else { this.editArea.insertBefore(elementTag, this.elementTags[this.currentFocus]); this.elementTags.splice(this.currentFocus, 0, elementTag); } // 更新焦点位置 this.currentFocus++; this.updateFocusIndicator(); // 触发事件 this.triggerChangeEvent(); this.triggerEvent(types_1.EventType.SYMBOL_CLICK, { symbol }); } /** * 创建元素标签 */ createElementTag(element) { const tag = (0, dom_1.createElement)('span', `formula-element formula-element-${element.type.toLowerCase()}`); tag.textContent = element.value; tag.dataset.id = element.id; tag.dataset.type = element.type; // 添加点击事件 (0, dom_1.addEventListeners)(tag, { click: (e) => { e.stopPropagation(); // 找到当前标签的索引 const index = this.elementTags.indexOf(tag); if (index !== -1) { this.currentFocus = index + 1; // 将焦点设置到该元素后面 this.updateFocusIndicator(); } } }); return tag; } /** * 触发变更事件 */ triggerChangeEvent() { var _a; const formula = parser_1.FormulaParser.stringify(this.formulaElements); this.triggerEvent(types_1.EventType.CHANGE, { formula, elements: [...this.formulaElements] }); // 如果配置了自动验证,则在变更时验证 if ((_a = this.options.validation) === null || _a === void 0 ? void 0 : _a.autoValidate) { this.validateFormula(); } } /** * 设置公式 * @param formula 公式字符串 */ setFormula(formula) { // 清空现有内容 this.clear(); // 解析公式 this.formulaElements = parser_1.FormulaParser.parse(formula); // 创建元素标签 this.formulaElements.forEach(element => { const tag = this.createElementTag(element); this.editArea.appendChild(tag); this.elementTags.push(tag); }); // 更新焦点 this.setFocusToEnd(); // 触发变更事件 this.triggerChangeEvent(); } /** * 获取公式字符串 * @returns 公式字符串 */ getFormula() { return parser_1.FormulaParser.stringify(this.formulaElements); } /** * 获取公式元素数组 * @returns 公式元素数组 */ getFormulaElements() { return [...this.formulaElements]; } /** * 获取指定索引位置的公式元素 * @param index 元素索引 * @returns 公式元素,如果索引无效则返回null */ getElementAt(index) { if (index >= 0 && index < this.formulaElements.length) { return Object.assign({}, this.formulaElements[index]); } return null; } /** * 在指定位置插入新元素 * @param element 要插入的元素 * @param index 插入位置索引,默认为当前焦点位置 * @returns 成功插入的元素 */ insertElement(element, index = this.currentFocus) { var _a; const newElement = Object.assign(Object.assign({}, element), { id: (0, dom_1.generateId)() }); // 确保索引在有效范围内 const insertIndex = Math.max(0, Math.min(index, this.formulaElements.length)); // 插入元素 this.formulaElements.splice(insertIndex, 0, newElement); // 创建并插入新标签 const newTag = this.createElementTag(newElement); if (insertIndex >= this.elementTags.length) { this.editArea.appendChild(newTag); } else { this.editArea.insertBefore(newTag, this.elementTags[insertIndex]); } // 更新标签数组 this.elementTags.splice(insertIndex, 0, newTag); // 更新焦点 this.currentFocus = insertIndex + 1; this.updateFocusIndicator(); // 触发变更事件 this.triggerChangeEvent(); // 自动验证 if ((_a = this.options.validation) === null || _a === void 0 ? void 0 : _a.autoValidate) { this.validateFormula(); } return newElement; } /** * 更新指定索引位置的元素 * @param index 元素索引 * @param elementUpdate 元素更新数据 * @returns 成功返回true,失败返回false */ updateElement(index, elementUpdate) { var _a; if (index < 0 || index >= this.formulaElements.length) { return false; } // 更新元素 const updatedElement = Object.assign(Object.assign({}, this.formulaElements[index]), elementUpdate); this.formulaElements[index] = updatedElement; // 更新DOM标签 const oldTag = this.elementTags[index]; const newTag = this.createElementTag(updatedElement); this.editArea.replaceChild(newTag, oldTag); this.elementTags[index] = newTag; // 触发变更事件 this.triggerChangeEvent(); // 自动验证 if ((_a = this.options.validation) === null || _a === void 0 ? void 0 : _a.autoValidate) { this.validateFormula(); } return true; } /** * 从指定位置删除元素 * @param index 要删除的元素索引 * @returns 成功返回true,失败返回false */ removeElement(index) { var _a; if (index < 0 || index >= this.formulaElements.length) { return false; } // 从数组中删除元素 this.formulaElements.splice(index, 1); // 从DOM中删除标签 const tag = this.elementTags[index]; this.editArea.removeChild(tag); // 从标签数组中删除 this.elementTags.splice(index, 1); // 调整焦点 this.currentFocus = Math.min(index, this.formulaElements.length); this.updateFocusIndicator(); // 触发变更事件 this.triggerChangeEvent(); // 自动验证 if ((_a = this.options.validation) === null || _a === void 0 ? void 0 : _a.autoValidate) { this.validateFormula(); } return true; } /** * 添加元素到公式末尾 * @param element 要添加的元素 * @returns 添加的元素 */ appendElement(element) { return this.insertElement(element, this.formulaElements.length); } /** * 查找符合条件的元素 * @param predicate 查找条件函数 * @returns 满足条件的元素索引数组 */ findElements(predicate) { return this.formulaElements .map((element, index) => ({ element, index })) .filter(({ element, index }) => predicate(element, index)) .map(({ index }) => index); } /** * 移动焦点到指定索引位置 * @param index 目标索引 * @returns 成功返回true,失败返回false */ setFocusToIndex(index) { if (index < 0 || index > this.formulaElements.length) { return false; } this.currentFocus = index; this.updateFocusIndicator(); return true; } /** * 获取当前焦点位置 * @returns 当前焦点索引 */ getCurrentFocusIndex() { return this.currentFocus; } /** * 批量更新公式元素 * @param elements 新的元素数组 */ setElements(elements) { // 清空当前元素 this.clear(); // 添加新元素 elements.forEach(element => { this.appendElement(element); }); } /** * 复制指定元素到剪贴板 * @param index 要复制的元素索引 * @returns 成功返回true,失败返回false */ copyElement(index) { if (index < 0 || index >= this.formulaElements.length) { return false; } try { const element = this.formulaElements[index]; navigator.clipboard.writeText(element.value); return true; } catch (error) { console.error('Failed to copy element:', error); return false; } } /** * 获取公式的纯文本表示 * @returns 公式的纯文本 */ getFormulaText() { return this.formulaElements.map(el => el.value).join(''); } /** * 公开的删除当前选中元素的方法 * @returns 成功返回true,失败返回false */ deleteCurrentElement() { if (this.currentFocus > 0 && this.currentFocus <= this.formulaElements.length) { this.deleteElement(); return true; } return false; } /** * 设置只读模式 */ setReadOnly(readOnly) { this.options.readOnly = readOnly; this.symbolButton.style.display = readOnly ? 'none' : 'inline-block'; // 更新tabindex if (readOnly) { this.editArea.removeAttribute('tabindex'); } else { this.editArea.setAttribute('tabindex', '0'); } } /** * 清空编辑器 */ clear() { this.formulaElements = []; this.elementTags = []; this.editArea.innerHTML = ''; this.currentFocus = -1; this.triggerEvent(types_1.EventType.CHANGE, { formula: '', elements: [] }); } /** * 添加事件监听器 */ addEventListener(event, callback) { var _a; if (!this.eventListeners[event]) { this.eventListeners[event] = []; } (_a = this.eventListeners[event]) === null || _a === void 0 ? void 0 : _a.push(callback); } /** * 移除事件监听器 */ removeEventListener(event, callback) { const listeners = this.eventListeners[event]; if (listeners) { const index = listeners.indexOf(callback); if (index !== -1) { listeners.splice(index, 1); } } } /** * 触发事件 */ triggerEvent(event, data) { const listeners = this.eventListeners[event]; if (listeners) { listeners.forEach(callback => callback(data)); } } /** * 移动焦点到左边 */ moveFocusLeft() { if (this.currentFocus > 0) { this.currentFocus--; this.updateFocusIndicator(); } } /** * 移动焦点到右边 */ moveFocusRight() { if (this.currentFocus < this.elementTags.length) { this.currentFocus++; this.updateFocusIndicator(); } } /** * 更新焦点指示器 */ updateFocusIndicator() { try { // 移除所有标签的焦点样式 this.elementTags.forEach(tag => { tag.classList.remove('formula-element-focused'); }); // 清除可能存在的光标指示器 const existingCursor = this.editArea.querySelector('.formula-cursor'); if (existingCursor) { this.editArea.removeChild(existingCursor); } // 当焦点在标签之间时,插入光标指示器 if (this.currentFocus >= 0 && this.currentFocus <= this.elementTags.length) { const cursor = (0, dom_1.createElement)('div', 'formula-cursor'); (0, dom_1.setStyles)(cursor, { display: 'inline-block', width: '2px', height: '1.5em', // 稍微增加高度使其更明显 backgroundColor: '#ff0000', // 使用红色使其更加明显,便于调试 verticalAlign: 'middle', animation: 'blink 1s infinite', position: 'relative', // 相对定位,便于文本输入框定位参考 marginLeft: '2px', marginRight: '2px' }); // 在当前位置插入光标 if (this.currentFocus === this.elementTags.length) { this.editArea.appendChild(cursor); } else { this.editArea.insertBefore(cursor, this.elementTags[this.currentFocus]); } // 添加ID以便更容易找到 cursor.id = 'formula-cursor-active'; // 确保光标可见 - 滚动到视图 setTimeout(() => { cursor.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); }, 10); } } catch (error) { console.error('Error updating focus indicator:', error); } } /** * 删除元素 */ deleteElement() { if (this.currentFocus > 0 && this.currentFocus <= this.formulaElements.length) { // 删除当前位置前的元素 const index = this.currentFocus - 1; this.formulaElements.splice(index, 1); // 移除对应的标签 this.editArea.removeChild(this.elementTags[index]); this.elementTags.splice(index, 1); // 更新焦点位置 this.currentFocus--; this.updateFocusIndicator(); // 触发变更事件 this.triggerChangeEvent(); } } /** * 创建工具栏 */ createToolbar() { var _a; const toolbar = (0, dom_1.createElement)('div', 'formula-editor-toolbar'); (0, dom_1.setStyles)(toolbar, { display: 'flex', padding: '8px', borderBottom: ((_a = this.options.styles) === null || _a === void 0 ? void 0 : _a.border) || DEFAULT_STYLES.border, backgroundColor: '#f5f5f5' }); // 创建符号按钮 this.symbolButton = (0, dom_1.createElement)('button', 'formula-editor-symbol-btn'); this.symbolButton.innerHTML = '&#931;'; // Sigma symbol (0, dom_1.setStyles)(this.symbolButton, { padding: '4px 8px', border: '1px solid #ccc', borderRadius: '4px', background: 'white', cursor: 'pointer', fontWeight: 'bold' }); (0, dom_1.addEventListeners)(this.symbolButton, { click: () => this.symbolPanel.toggle() }); // 添加按钮到工具栏 toolbar.appendChild(this.symbolButton); return toolbar; } /** * 创建编辑区域 */ createEditArea() { var _a, _b, _c, _d; const editArea = (0, dom_1.createElement)('div', 'formula-editor-edit-area'); // 设置样式 (0, dom_1.setStyles)(editArea, { padding: '10px', minHeight: '100px', outline: 'none', fontSize: ((_a = this.options.styles) === null || _a === void 0 ? void 0 : _a.fontSize) || DEFAULT_STYLES.fontSize, color: ((_b = this.options.styles) === null || _b === void 0 ? void 0 : _b.color) || DEFAULT_STYLES.color, backgroundColor: ((_c = this.options.styles) === null || _c === void 0 ? void 0 : _c.backgroundColor) || DEFAULT_STYLES.backgroundColor, width: ((_d = this.options.styles) === null || _d === void 0 ? void 0 : _d.width) || DEFAULT_STYLES.width, boxSizing: 'border-box', fontFamily: 'Arial, sans-serif', cursor: 'text', position: 'relative', // 添加相对定位,便于放置绝对定位的文本输入框 overflow: 'auto' // 确保滚动时位置计算正确 }); // 添加tabindex以便能够获取焦点 editArea.setAttribute('tabindex', '0'); // 添加事件监听 (0, dom_1.addEventListeners)(editArea, { focus: () => { this.triggerEvent(types_1.EventType.FOCUS); if (this.currentFocus === -1) { this.setFocusToEnd(); } }, blur: (e) => { // 如果不是点击了文本输入框,才触发失焦事件 const relatedTarget = e.relatedTarget; if (!relatedTarget || !relatedTarget.classList.contains('formula-text-input')) { this.hideTextInput(); this.triggerEvent(types_1.EventType.BLUR); } } }); // 添加CSS样式支持光标动画和文本输入框 const style = document.createElement('style'); style.textContent = ` @keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } } .formula-element { display: inline-block; padding: 3px 6px; margin: 3px; border-radius: 4px; background-color: #f0f0f0; border: 1px solid #ddd; user-select: none; transition: all 0.2s ease; font-family: 'Arial', sans-serif; } .formula-element:hover { box-shadow: 0 1px 3px rgba(0,0,0,0.1); transform: translateY(-1px); } .formula-element-focused { background-color: #e3f2fd; border-color: #2196f3; box-shadow: 0 0 0 2px rgba(33, 150, 243, 0.3); } .formula-element-error { background-color: #ffebee; border-color: #f44336; box-shadow: 0 0 0 2px rgba(244, 67, 54, 0.3); } .formula-element-symbol { background-color: #e3f2fd; color: #1565c0; } .formula-element-number { background-color: #e8f5e9; color: #2e7d32; font-weight: 500; } .formula-element-variable { background-color: #fff8e1; color: #ff8f00; font-style: italic; } .formula-element-operator { background-color: #fce4ec; color: #c2185b; font-weight: bold; } .formula-element-function { background-color: #f3e5f5; color: #7b1fa2; font-weight: 500; } .formula-element-bracket { background-color: #ede7f6; color: #512da8; font-weight: bold; } .formula-text-input { position: absolute; min-width: 50px; max-width: 150px; z-index: 10; padding: 4px 8px; margin: 2px; border-radius: 4px; background-color: #e3f2fd; border: 1px solid #2196f3; font-size: inherit; font-family: inherit; box-shadow: 0 2px 5px rgba(0,0,0,0.2); outline: none; animation: fadeIn 0.2s ease; } @keyframes fadeIn { from { opacity: 0; transform: translateY(-5px); } to { opacity: 1; transform: translateY(0); } } .formula-cursor { display: inline-block; width: 2px; height: 1.5em; background-color: #ff0000; vertical-align: middle; animation: blink 1s infinite; position: relative; margin-left: 2px; margin-right: 2px; } /* 添加插入反馈动画 */ @keyframes elementInserted { 0% { transform: scale(0.8); opacity: 0.5; } 50% { transform: scale(1.1); opacity: 1; } 100% { transform: scale(1); opacity: 1; } } .formula-element-inserted { animation: elementInserted 0.3s ease; } .formula-error-message { background-color: #ffebee; color: #b71c1c; padding: 8px 12px; margin-top: 8px; border-radius: 4px; border-left: 4px solid #f44336; font-size: 14px; line-height: 1.5; display: none; animation: fadeIn 0.3s ease; } `; document.head.appendChild(style); return editArea; } /** * 创建文本输入组件 */ createTextInput() { console.log('Creating text input element'); // 确保编辑区域已经初始化 if (!this.editArea) { console.error('Cannot create text input: editArea not initialized'); return; } try { // 如果已经存在,先移除它 if (this.textInputElement) { if (this.textInputElement.parentNode) { this.textInputElement.parentNode.removeChild(this.textInputElement); } } const input = (0, dom_1.createElement)('input', 'formula-text-input'); input.type = 'text'; input.style.display = 'none'; input.placeholder = '输入文本...'; // 确保输入框样式明显 input.style.fontSize = '16px'; input.style.padding = '5px'; input.style.borderRadius = '4px'; input.style.border = '2px solid #2196f3'; input.style.backgroundColor = '#e3f2fd'; input.style.zIndex = '9999'; this.textInputElement = input; // 添加到编辑区域的末尾 this.editArea.appendChild(input); console.log('Text input element created and appended to edit area'); } catch (error) { console.error('Error creating text input:', error); } } /** * 创建错误信息显示元素 */ createErrorMessageElement() { this.errorMessageElement = (0, dom_1.createElement)('div', 'formula-error-message'); this.errorMessageElement.style.display = 'none'; this.editorContainer.appendChild(this.errorMessageElement); } /** * 显示错误信息 */ showErrorMessage(message) { if (!this.errorMessageElement) { this.createErrorMessageElement(); } if (this.errorMessageElement) { this.errorMessageElement.textContent = message; this.errorMessageElement.style.display = 'block'; } } /** * 隐藏错误信息 */ hideErrorMessage() { if (this.errorMessageElement) { this.errorMessageElement.style.display = 'none'; } } /** * 验证公式 */ validateFormula() { var _a; // 清除之前的错误标记 this.clearErrorHighlights(); // 如果没有配置验证规则,则始终视为有效 if (!this.options.validation || (!((_a = this.options.validation.rules) === null || _a === void 0 ? void 0 : _a.length) && !this.options.validation.customValidator)) { return { valid: true, errors: [] }; } const formula = this.getFormula(); let result = { valid: true, errors: [] }; // 使用自定义验证器 if (this.options.validation.customValidator) { result = this.options.validation.customValidator(formula, [...this.formulaElements]); } // 使用内置验证规则