@zhenliang/sheet 0.2.5-beta.10 → 0.2.5-beta.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/editor/InputOptionsEditor/DropMenu.d.ts +10 -0
- package/dist/core/editor/InputOptionsEditor/DropMenu.js +52 -0
- package/dist/core/editor/InputOptionsEditor/index.d.ts +1 -1
- package/dist/core/editor/InputOptionsEditor/index.js +32 -379
- package/dist/core/editor/InputOptionsEditor/index.less +1 -0
- package/dist/core/editor/InputOptionsEditor/keyEventEffect.d.ts +16 -0
- package/dist/core/editor/InputOptionsEditor/keyEventEffect.js +305 -0
- package/dist/core/editor/InputOptionsEditor/utils.d.ts +5 -0
- package/dist/core/editor/InputOptionsEditor/utils.js +17 -0
- package/dist/example/basic.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import type { OptionItem } from './utils';
|
|
3
|
+
import './index.less';
|
|
4
|
+
interface DropMenuProps {
|
|
5
|
+
options: OptionItem[];
|
|
6
|
+
onOptionClick: (opt: OptionItem) => void;
|
|
7
|
+
style?: React.CSSProperties;
|
|
8
|
+
}
|
|
9
|
+
declare const DropMenu: ({ options, onOptionClick, style }: DropMenuProps) => JSX.Element;
|
|
10
|
+
export default DropMenu;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { InfoCircleOutlined } from '@ant-design/icons';
|
|
2
|
+
import { Menu, Tooltip } from 'antd';
|
|
3
|
+
import "./index.less";
|
|
4
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
5
|
+
import { jsxs as _jsxs } from "react/jsx-runtime";
|
|
6
|
+
var renderMenuItems = function renderMenuItems(items, onOptionClick) {
|
|
7
|
+
return items.map(function (opt) {
|
|
8
|
+
if (opt.children && opt.children.length > 0) {
|
|
9
|
+
return /*#__PURE__*/_jsx(Menu.SubMenu, {
|
|
10
|
+
title: opt.label,
|
|
11
|
+
className: "formula-editor-dropdown-submenu",
|
|
12
|
+
children: renderMenuItems(opt.children, onOptionClick)
|
|
13
|
+
}, opt.value);
|
|
14
|
+
}
|
|
15
|
+
return /*#__PURE__*/_jsxs(Menu.Item, {
|
|
16
|
+
className: "formula-editor-dropdown-item",
|
|
17
|
+
onClick: function onClick() {
|
|
18
|
+
return onOptionClick(opt);
|
|
19
|
+
},
|
|
20
|
+
children: [opt.label, opt.info ? /*#__PURE__*/_jsx(Tooltip, {
|
|
21
|
+
title: opt.info,
|
|
22
|
+
children: /*#__PURE__*/_jsx(InfoCircleOutlined, {
|
|
23
|
+
style: {
|
|
24
|
+
color: '#FF9900',
|
|
25
|
+
marginLeft: 4
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
}) : null]
|
|
29
|
+
}, opt.value);
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
var DropMenu = function DropMenu(_ref) {
|
|
33
|
+
var options = _ref.options,
|
|
34
|
+
onOptionClick = _ref.onOptionClick,
|
|
35
|
+
style = _ref.style;
|
|
36
|
+
return /*#__PURE__*/_jsx(Menu, {
|
|
37
|
+
className: "formula-editor-dropdown",
|
|
38
|
+
style: style,
|
|
39
|
+
onMouseDown: function onMouseDown(e) {
|
|
40
|
+
e.preventDefault();
|
|
41
|
+
e.stopPropagation();
|
|
42
|
+
},
|
|
43
|
+
children: options.length === 0 ? /*#__PURE__*/_jsx(Menu.Item, {
|
|
44
|
+
disabled: true,
|
|
45
|
+
children: /*#__PURE__*/_jsx("span", {
|
|
46
|
+
className: "formula-editor-dropdown-no-data",
|
|
47
|
+
children: "\u6682\u65E0\u6570\u636E"
|
|
48
|
+
})
|
|
49
|
+
}, "no-data") : renderMenuItems(options, onOptionClick)
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
export default DropMenu;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { SheetType } from "../../../type";
|
|
2
2
|
import './index.less';
|
|
3
|
-
import {
|
|
3
|
+
import { inputProps, OptionItem } from './utils';
|
|
4
4
|
export declare const getFormulaInputEditor: (options: OptionItem[], replaceIndex?: string, extraProps?: inputProps, getExtraProps?: ((props: SheetType.CellEditorProps) => inputProps) | undefined, choseEditor?: ((record: any) => boolean) | undefined) => SheetType.CellEditor;
|
|
5
5
|
export default getFormulaInputEditor;
|
|
@@ -4,7 +4,6 @@ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t =
|
|
|
4
4
|
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
|
5
5
|
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : String(i); }
|
|
6
6
|
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
7
|
-
function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
|
|
8
7
|
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
|
|
9
8
|
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
|
|
10
9
|
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
|
|
@@ -12,34 +11,19 @@ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len
|
|
|
12
11
|
function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
|
|
13
12
|
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
|
|
14
13
|
function _objectDestructuringEmpty(obj) { if (obj == null) throw new TypeError("Cannot destructure " + obj); }
|
|
15
|
-
import {
|
|
16
|
-
import { Dropdown, Input, Menu, Tooltip } from 'antd';
|
|
14
|
+
import { Dropdown, Input } from 'antd';
|
|
17
15
|
import { get, head, isEmpty, isNil } from 'lodash';
|
|
18
16
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
19
17
|
import { formulaString } from "../numberEditor";
|
|
18
|
+
import DropMenu from "./DropMenu";
|
|
20
19
|
import "./index.less";
|
|
21
|
-
import {
|
|
20
|
+
import { useKeyEventEffect } from "./keyEventEffect";
|
|
21
|
+
import { createSpan, flattenOptions, getStringDiff, moveCursorToSpan, replaceLabelsWithValues, replaceLongestDiff, replaceValuesWithLabels, SpanType, tokenize, validateVariables } from "./utils";
|
|
22
22
|
import validateCalculationExpr from "./vaildFormula";
|
|
23
23
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
24
|
-
import { jsxs as _jsxs } from "react/jsx-runtime";
|
|
25
24
|
var OPERATORS = ['=', '+', '-', '*', '/', '('];
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
// const createLabelSpan = (label: string): HTMLElement => {
|
|
29
|
-
// const span = document.createElement('span');
|
|
30
|
-
// span.className = 'formula-editor-token-label';
|
|
31
|
-
// span.textContent = label;
|
|
32
|
-
// return span;
|
|
33
|
-
// };
|
|
34
|
-
|
|
35
|
-
// // 创建 empty other span
|
|
36
|
-
// const createEmptyOtherSpan = (): HTMLElement => {
|
|
37
|
-
// const span = document.createElement('span');
|
|
38
|
-
// span.className = 'formula-editor-token-other';
|
|
39
|
-
// span.textContent = '';
|
|
40
|
-
// return span;
|
|
41
|
-
// };
|
|
42
|
-
|
|
25
|
+
var LABEL = SpanType.LABEL,
|
|
26
|
+
OTHER = SpanType.OTHER;
|
|
43
27
|
export var getFormulaInputEditor = function getFormulaInputEditor(options, replaceIndex, extraProps, getExtraProps, choseEditor) {
|
|
44
28
|
var _ref = extraProps || {},
|
|
45
29
|
warnMethod = _ref.warnMethod,
|
|
@@ -59,6 +43,7 @@ export var getFormulaInputEditor = function getFormulaInputEditor(options, repla
|
|
|
59
43
|
inputArgs = Object.assign({}, (_objectDestructuringEmpty(_ref2), _ref2));
|
|
60
44
|
var replaceValue = replaceIndex ? (_get = get(record, [replaceIndex])) !== null && _get !== void 0 ? _get : '' : originValue;
|
|
61
45
|
var calcValue = replaceValue.startsWith('=') ? replaceValue : "=".concat(replaceValue);
|
|
46
|
+
var originContainerValue = replaceValue === '' || replaceValue === null ? '' : calcValue;
|
|
62
47
|
var _useState = useState(calcValue),
|
|
63
48
|
_useState2 = _slicedToArray(_useState, 1),
|
|
64
49
|
inputValue = _useState2[0];
|
|
@@ -67,7 +52,7 @@ export var getFormulaInputEditor = function getFormulaInputEditor(options, repla
|
|
|
67
52
|
offsetX = _useState4[0],
|
|
68
53
|
setOffsetX = _useState4[1];
|
|
69
54
|
// 为了不重新render,再写一个
|
|
70
|
-
var _useState5 = useState(
|
|
55
|
+
var _useState5 = useState(originContainerValue),
|
|
71
56
|
_useState6 = _slicedToArray(_useState5, 2),
|
|
72
57
|
containerValue = _useState6[0],
|
|
73
58
|
setContainerValue = _useState6[1];
|
|
@@ -94,39 +79,7 @@ export var getFormulaInputEditor = function getFormulaInputEditor(options, repla
|
|
|
94
79
|
return tokenize(inputValue, flatOptions);
|
|
95
80
|
}, [inputValue, flatOptions]);
|
|
96
81
|
// 根据搜索文本获取筛选后的层级选项(保持层级结构)
|
|
97
|
-
var filteredHierarchyOptions = useMemo(function () {
|
|
98
|
-
var searchText = inputAfterDropDownShow.trim();
|
|
99
|
-
// 如果搜索文本为空,显示所有选项(保持层级)
|
|
100
|
-
if (!searchText) {
|
|
101
|
-
return options;
|
|
102
|
-
}
|
|
103
82
|
|
|
104
|
-
// 根据搜索文本筛选,保持层级结构
|
|
105
|
-
var filterWithHierarchy = function filterWithHierarchy(items) {
|
|
106
|
-
var result = [];
|
|
107
|
-
var _iterator = _createForOfIteratorHelper(items),
|
|
108
|
-
_step;
|
|
109
|
-
try {
|
|
110
|
-
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
111
|
-
var item = _step.value;
|
|
112
|
-
var labelMatch = item.label.includes(searchText);
|
|
113
|
-
// 筛选 children
|
|
114
|
-
var filteredChildren = item.children ? filterWithHierarchy(item.children) : [];
|
|
115
|
-
if (labelMatch || filteredChildren.length > 0) {
|
|
116
|
-
result.push(_objectSpread(_objectSpread({}, item), {}, {
|
|
117
|
-
children: filteredChildren.length > 0 ? filteredChildren : item.children
|
|
118
|
-
}));
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
} catch (err) {
|
|
122
|
-
_iterator.e(err);
|
|
123
|
-
} finally {
|
|
124
|
-
_iterator.f();
|
|
125
|
-
}
|
|
126
|
-
return result;
|
|
127
|
-
};
|
|
128
|
-
return filterWithHierarchy(options);
|
|
129
|
-
}, [inputAfterDropDownShow]);
|
|
130
83
|
useEffect(function () {
|
|
131
84
|
if (!open) {
|
|
132
85
|
containerValueSnapShot.current = '';
|
|
@@ -159,9 +112,7 @@ export var getFormulaInputEditor = function getFormulaInputEditor(options, repla
|
|
|
159
112
|
var replacedIndex = _allSpans.indexOf(replacedSpan);
|
|
160
113
|
var _targetSpan = _allSpans[replacedIndex + 1];
|
|
161
114
|
if (!_targetSpan || _targetSpan.classList.contains('formula-editor-token-label')) {
|
|
162
|
-
_targetSpan =
|
|
163
|
-
_targetSpan.className = 'formula-editor-token-other';
|
|
164
|
-
_targetSpan.textContent = '';
|
|
115
|
+
_targetSpan = createSpan(OTHER, '');
|
|
165
116
|
replacedSpan.after(_targetSpan);
|
|
166
117
|
}
|
|
167
118
|
moveCursorToSpan(_targetSpan, true, selection);
|
|
@@ -196,23 +147,17 @@ export var getFormulaInputEditor = function getFormulaInputEditor(options, repla
|
|
|
196
147
|
var afterPart = spanText.slice(deleteIndex + deleteText.length);
|
|
197
148
|
|
|
198
149
|
// 删除当前 span,用三个 span 替换:other(前)、label、other(后)
|
|
199
|
-
var labelSpan =
|
|
200
|
-
labelSpan.className = 'formula-editor-token-label';
|
|
201
|
-
labelSpan.textContent = opt.label;
|
|
150
|
+
var labelSpan = createSpan(LABEL, opt.label);
|
|
202
151
|
|
|
203
152
|
// 创建待插入的 span 列表
|
|
204
153
|
var spansToInsert = [];
|
|
205
154
|
if (beforePart) {
|
|
206
|
-
var beforeSpan =
|
|
207
|
-
beforeSpan.className = 'formula-editor-token-other';
|
|
208
|
-
beforeSpan.textContent = beforePart;
|
|
155
|
+
var beforeSpan = createSpan(OTHER, beforePart);
|
|
209
156
|
spansToInsert.push(beforeSpan);
|
|
210
157
|
}
|
|
211
158
|
spansToInsert.push(labelSpan);
|
|
212
159
|
if (afterPart) {
|
|
213
|
-
var afterSpan =
|
|
214
|
-
afterSpan.className = 'formula-editor-token-other';
|
|
215
|
-
afterSpan.textContent = afterPart;
|
|
160
|
+
var afterSpan = createSpan(OTHER, afterPart);
|
|
216
161
|
spansToInsert.push(afterSpan);
|
|
217
162
|
}
|
|
218
163
|
// 插入所有 span
|
|
@@ -226,9 +171,7 @@ export var getFormulaInputEditor = function getFormulaInputEditor(options, repla
|
|
|
226
171
|
|
|
227
172
|
// 如果下一个 span 是 label 类型或没有下一个 span,则插入一个空 span
|
|
228
173
|
if (!targetSpan || targetSpan.classList.contains('formula-editor-token-label')) {
|
|
229
|
-
targetSpan =
|
|
230
|
-
targetSpan.className = 'formula-editor-token-other';
|
|
231
|
-
targetSpan.textContent = '';
|
|
174
|
+
targetSpan = createSpan(OTHER, '');
|
|
232
175
|
labelSpan.after(targetSpan);
|
|
233
176
|
}
|
|
234
177
|
moveCursorToSpan(targetSpan, true, selection);
|
|
@@ -294,6 +237,18 @@ export var getFormulaInputEditor = function getFormulaInputEditor(options, repla
|
|
|
294
237
|
setInputAfterDropDownShow(inputStringAfterDropDownShow);
|
|
295
238
|
onChange(valueExpr);
|
|
296
239
|
}, [containerValue, open]);
|
|
240
|
+
var _useKeyEventEffect = useKeyEventEffect({
|
|
241
|
+
closeDorpDown: closeDorpDown,
|
|
242
|
+
setOffsetX: setOffsetX,
|
|
243
|
+
setOpen: setOpen,
|
|
244
|
+
containerRef: containerRef,
|
|
245
|
+
tempSpanRef: tempSpanRef,
|
|
246
|
+
inputAfterDropDownShow: inputAfterDropDownShow,
|
|
247
|
+
options: options
|
|
248
|
+
}),
|
|
249
|
+
handleMouseUp = _useKeyEventEffect.handleMouseUp,
|
|
250
|
+
handleKeyDown = _useKeyEventEffect.handleKeyDown,
|
|
251
|
+
filteredHierarchyOptions = _useKeyEventEffect.filteredHierarchyOptions;
|
|
297
252
|
|
|
298
253
|
// 编辑状态时自动聚焦
|
|
299
254
|
useEffect(function () {
|
|
@@ -302,314 +257,6 @@ export var getFormulaInputEditor = function getFormulaInputEditor(options, repla
|
|
|
302
257
|
(_containerRef$current2 = containerRef.current) === null || _containerRef$current2 === void 0 || _containerRef$current2.focus();
|
|
303
258
|
}
|
|
304
259
|
}, [isEditing]);
|
|
305
|
-
|
|
306
|
-
// 鼠标释放时,如果光标在 label span 内,打开下拉框并记录 tempSpan
|
|
307
|
-
var handleMouseUp = useCallback(function () {
|
|
308
|
-
var div = containerRef.current;
|
|
309
|
-
if (!div) return;
|
|
310
|
-
closeDorpDown();
|
|
311
|
-
var selection = window.getSelection();
|
|
312
|
-
if (!selection || selection.rangeCount === 0) return;
|
|
313
|
-
var range = selection.getRangeAt(0);
|
|
314
|
-
var startContainer = range.startContainer;
|
|
315
|
-
var currentSpan = getSpanAtCursor(startContainer);
|
|
316
|
-
if (!currentSpan || currentSpan === div) return;
|
|
317
|
-
var isLabelSpan = currentSpan.classList.contains('formula-editor-token-label');
|
|
318
|
-
|
|
319
|
-
// 如果在 label span 内,打开下拉框并记录 tempSpan
|
|
320
|
-
if (isLabelSpan) {
|
|
321
|
-
// 更新 offsetX 位置
|
|
322
|
-
var containerRect = div.getBoundingClientRect();
|
|
323
|
-
var cursorRect = range.getBoundingClientRect();
|
|
324
|
-
var cursorDistance = cursorRect ? cursorRect.left - containerRect.left : 0;
|
|
325
|
-
setOffsetX(cursorDistance < 150 ? 0 : cursorDistance - 8);
|
|
326
|
-
|
|
327
|
-
// 记录当前 label span 作为 tempSpan
|
|
328
|
-
tempSpanRef.current = currentSpan;
|
|
329
|
-
|
|
330
|
-
// 打开下拉框
|
|
331
|
-
setOpen(true);
|
|
332
|
-
// 移动到下一个 span 的开头
|
|
333
|
-
var allSpans = Array.from(div.querySelectorAll('span'));
|
|
334
|
-
var currentIndex = allSpans.indexOf(currentSpan);
|
|
335
|
-
if (currentIndex < allSpans.length - 1) {
|
|
336
|
-
var nextSpan = allSpans[currentIndex + 1];
|
|
337
|
-
var newRange = document.createRange();
|
|
338
|
-
newRange.selectNodeContents(nextSpan);
|
|
339
|
-
newRange.collapse(true);
|
|
340
|
-
selection.removeAllRanges();
|
|
341
|
-
selection.addRange(newRange);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
}, [closeDorpDown, setOffsetX, setOpen]);
|
|
345
|
-
var handleKeyDown = useCallback(function (e) {
|
|
346
|
-
var _currentSpan$textCont, _currentSpan$textCont2;
|
|
347
|
-
if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
|
|
348
|
-
e.preventDefault();
|
|
349
|
-
e.stopPropagation();
|
|
350
|
-
return;
|
|
351
|
-
}
|
|
352
|
-
var div = containerRef.current;
|
|
353
|
-
if (!div) return;
|
|
354
|
-
var selection = window.getSelection();
|
|
355
|
-
if (!selection || selection.rangeCount === 0) return;
|
|
356
|
-
var range = selection.getRangeAt(0);
|
|
357
|
-
var startContainer = range.startContainer,
|
|
358
|
-
startOffset = range.startOffset;
|
|
359
|
-
var currentSpan = getSpanAtCursor(startContainer);
|
|
360
|
-
if (!currentSpan || currentSpan === div) return;
|
|
361
|
-
var allSpans = Array.from(div.querySelectorAll('span'));
|
|
362
|
-
var currentIndex = allSpans.indexOf(currentSpan);
|
|
363
|
-
var textLen = (_currentSpan$textCont = (_currentSpan$textCont2 = currentSpan.textContent) === null || _currentSpan$textCont2 === void 0 ? void 0 : _currentSpan$textCont2.length) !== null && _currentSpan$textCont !== void 0 ? _currentSpan$textCont : 0;
|
|
364
|
-
|
|
365
|
-
// 方向键:跳过 label span
|
|
366
|
-
if (e.key === 'ArrowRight' || e.key === 'ArrowLeft') {
|
|
367
|
-
var position = getCursorPositionInSpan(currentSpan);
|
|
368
|
-
var isAtEnd = startOffset >= textLen;
|
|
369
|
-
var isAtStart = startOffset === 0;
|
|
370
|
-
var isAlmostEnd = startOffset >= textLen - 1;
|
|
371
|
-
var isAlmostStart = position === 1;
|
|
372
|
-
if (e.key === 'ArrowRight' && isAtEnd) {
|
|
373
|
-
closeDorpDown();
|
|
374
|
-
// 向右 + 在末尾 -> 跳到下一个 span 的开头(跳过 label span)
|
|
375
|
-
var targetIndex = currentIndex + 1;
|
|
376
|
-
while (targetIndex < allSpans.length && allSpans[targetIndex].classList.contains('formula-editor-token-label')) {
|
|
377
|
-
targetIndex++;
|
|
378
|
-
}
|
|
379
|
-
if (targetIndex < allSpans.length && targetIndex !== currentIndex + 1) {
|
|
380
|
-
e.preventDefault();
|
|
381
|
-
var targetSpan = allSpans[targetIndex];
|
|
382
|
-
moveCursorToSpan(targetSpan, true, selection);
|
|
383
|
-
}
|
|
384
|
-
} else if (e.key === 'ArrowRight' && isAlmostEnd) {
|
|
385
|
-
closeDorpDown();
|
|
386
|
-
var _targetIndex = currentIndex + 1;
|
|
387
|
-
if (_targetIndex < allSpans.length && !allSpans[_targetIndex].classList.contains('formula-editor-token-label')) {
|
|
388
|
-
e.preventDefault();
|
|
389
|
-
var _targetSpan2 = allSpans[_targetIndex];
|
|
390
|
-
moveCursorToSpan(_targetSpan2, true, selection);
|
|
391
|
-
}
|
|
392
|
-
} else if (e.key === 'ArrowLeft' && isAtStart) {
|
|
393
|
-
closeDorpDown();
|
|
394
|
-
// 向左 + 在开头 -> 跳到上一个 span 的末尾(跳过 label span)
|
|
395
|
-
var _targetIndex2 = currentIndex - 1;
|
|
396
|
-
while (_targetIndex2 >= 0 && allSpans[_targetIndex2].classList.contains('formula-editor-token-label')) {
|
|
397
|
-
_targetIndex2--;
|
|
398
|
-
}
|
|
399
|
-
if (_targetIndex2 >= 0 && _targetIndex2 !== currentIndex - 1) {
|
|
400
|
-
e.preventDefault();
|
|
401
|
-
var _targetSpan3 = allSpans[_targetIndex2];
|
|
402
|
-
moveCursorToSpan(_targetSpan3, false, selection);
|
|
403
|
-
}
|
|
404
|
-
} else if (e.key === 'ArrowLeft' && isAlmostStart) {
|
|
405
|
-
closeDorpDown();
|
|
406
|
-
var _targetIndex3 = currentIndex - 1;
|
|
407
|
-
if (_targetIndex3 >= 0 && !allSpans[_targetIndex3].classList.contains('formula-editor-token-label')) {
|
|
408
|
-
e.preventDefault();
|
|
409
|
-
var _targetSpan4 = allSpans[_targetIndex3];
|
|
410
|
-
moveCursorToSpan(_targetSpan4, false, selection);
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
return;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
// 删除键逻辑
|
|
417
|
-
if (e.key !== 'Delete' && e.key !== 'Backspace') return;
|
|
418
|
-
if (e.key === 'Backspace' || e.key === 'Delete') {
|
|
419
|
-
if (tempSpanRef.current) {
|
|
420
|
-
closeDorpDown();
|
|
421
|
-
}
|
|
422
|
-
// 检查删除后是否只剩一个空的 span
|
|
423
|
-
var allSpansNow = Array.from(div.querySelectorAll('span'));
|
|
424
|
-
var isOnlyOneEmptySpan = allSpansNow.length === 1 && allSpansNow[0].textContent === '';
|
|
425
|
-
if (isOnlyOneEmptySpan) {
|
|
426
|
-
e.preventDefault();
|
|
427
|
-
e.stopPropagation();
|
|
428
|
-
return;
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
// 不在 label span 内,在 span 开头且前一个是 label 时,删除整个 label span
|
|
433
|
-
if (startOffset === 0 && e.key === 'Backspace' && currentIndex > 0) {
|
|
434
|
-
var prevSpan = allSpans[currentIndex - 1];
|
|
435
|
-
if (prevSpan.classList.contains('formula-editor-token-label')) {
|
|
436
|
-
e.preventDefault();
|
|
437
|
-
e.stopPropagation();
|
|
438
|
-
prevSpan.remove();
|
|
439
|
-
var newRange = document.createRange();
|
|
440
|
-
|
|
441
|
-
// 如果当前 span 有内容,光标放在开头;否则找最近的有内容的 span
|
|
442
|
-
if (currentSpan.textContent !== '') {
|
|
443
|
-
newRange.selectNodeContents(currentSpan);
|
|
444
|
-
newRange.collapse(true);
|
|
445
|
-
} else {
|
|
446
|
-
// 找前一个最近的有内容的 span
|
|
447
|
-
var _targetSpan5;
|
|
448
|
-
for (var i = currentIndex - 2; i >= 0; i--) {
|
|
449
|
-
if (allSpans[i].textContent !== '') {
|
|
450
|
-
_targetSpan5 = allSpans[i];
|
|
451
|
-
break;
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
if (_targetSpan5) {
|
|
455
|
-
newRange.selectNodeContents(_targetSpan5);
|
|
456
|
-
newRange.collapse(false);
|
|
457
|
-
} else {
|
|
458
|
-
newRange.selectNodeContents(div);
|
|
459
|
-
newRange.collapse(true);
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
selection.removeAllRanges();
|
|
463
|
-
selection.addRange(newRange);
|
|
464
|
-
return;
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
// 在 span 开头 + Delete 键,光标移到前一个 span 的末尾
|
|
469
|
-
if (startOffset === 0 && e.key === 'Backspace' && currentIndex > 0) {
|
|
470
|
-
var _prevSpan = allSpans[currentIndex - 1];
|
|
471
|
-
// 前一个 span 是非 label span 时,光标移到其末尾
|
|
472
|
-
if (!_prevSpan.classList.contains('formula-editor-token-label')) {
|
|
473
|
-
e.preventDefault();
|
|
474
|
-
e.stopPropagation();
|
|
475
|
-
var prevText = _prevSpan.textContent || '';
|
|
476
|
-
if (prevText.length > 0) {
|
|
477
|
-
_prevSpan.textContent = prevText.slice(0, -1);
|
|
478
|
-
}
|
|
479
|
-
var _newRange = document.createRange();
|
|
480
|
-
_newRange.selectNodeContents(_prevSpan);
|
|
481
|
-
_newRange.collapse(false);
|
|
482
|
-
selection.removeAllRanges();
|
|
483
|
-
selection.addRange(_newRange);
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
// 其他情况:等待浏览器完成默认删除后,检查当前 span 是否为空
|
|
488
|
-
setTimeout(function () {
|
|
489
|
-
var currentSpanNow = getSpanAtCursor(startContainer);
|
|
490
|
-
var allSpansNow = Array.from(div.querySelectorAll('span'));
|
|
491
|
-
|
|
492
|
-
// 如果 currentSpanNow 不在 allSpansNow 里(被浏览器删除了)
|
|
493
|
-
// 且还有 span 存在,用按下删除键时的 currentSpan 位置往前找
|
|
494
|
-
if (allSpansNow.length > 0 && !allSpansNow.includes(currentSpanNow)) {
|
|
495
|
-
// 如果光标在 span 开头 + Delete 键,currentIndex 指向的是被删 span 的后一个,所以要 -1
|
|
496
|
-
var searchIndex = startOffset === 0 ? currentIndex - 1 : currentIndex;
|
|
497
|
-
// 从 searchIndex 位置往前找
|
|
498
|
-
var _targetSpan6;
|
|
499
|
-
for (var _i = searchIndex; _i >= 0; _i--) {
|
|
500
|
-
var span = allSpansNow[_i];
|
|
501
|
-
if (span.textContent !== '') {
|
|
502
|
-
_targetSpan6 = span;
|
|
503
|
-
break;
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
// 如果找到的有内容 span 是 label,则把光标放在 label span 的后一个 span 的开头
|
|
507
|
-
if (_targetSpan6 && _targetSpan6.classList.contains('formula-editor-token-label')) {
|
|
508
|
-
var labelIndex = allSpansNow.indexOf(_targetSpan6);
|
|
509
|
-
if (labelIndex < allSpansNow.length - 1) {
|
|
510
|
-
var nextSpan = allSpansNow[labelIndex + 1];
|
|
511
|
-
var _newRange2 = document.createRange();
|
|
512
|
-
_newRange2.selectNodeContents(nextSpan);
|
|
513
|
-
_newRange2.collapse(true);
|
|
514
|
-
selection.removeAllRanges();
|
|
515
|
-
selection.addRange(_newRange2);
|
|
516
|
-
return;
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
// 否则把光标放在找到的 span 的末尾
|
|
521
|
-
if (_targetSpan6) {
|
|
522
|
-
var _newRange3 = document.createRange();
|
|
523
|
-
_newRange3.selectNodeContents(_targetSpan6);
|
|
524
|
-
_newRange3.collapse(false);
|
|
525
|
-
selection.removeAllRanges();
|
|
526
|
-
selection.addRange(_newRange3);
|
|
527
|
-
return;
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
if (!currentSpanNow || currentSpanNow === div) return;
|
|
531
|
-
if (currentSpanNow.textContent === '') {
|
|
532
|
-
var currentIndexNow = allSpansNow.indexOf(currentSpanNow);
|
|
533
|
-
var _newRange4 = document.createRange();
|
|
534
|
-
if (currentIndexNow > 0) {
|
|
535
|
-
// 移到前一个 span 的末尾,但如果前一个 span 是 label,则移到后一个 span 的开头
|
|
536
|
-
var _targetSpan7 = allSpansNow[currentIndexNow - 1];
|
|
537
|
-
if (_targetSpan7.classList.contains('formula-editor-token-label')) {
|
|
538
|
-
// 移到 label span 的后一个 span 的开头
|
|
539
|
-
if (currentIndexNow < allSpansNow.length - 1) {
|
|
540
|
-
var _nextSpan = allSpansNow[currentIndexNow + 1];
|
|
541
|
-
_newRange4.selectNodeContents(_nextSpan);
|
|
542
|
-
_newRange4.collapse(true);
|
|
543
|
-
} else {
|
|
544
|
-
_newRange4.selectNodeContents(div);
|
|
545
|
-
_newRange4.collapse(false);
|
|
546
|
-
}
|
|
547
|
-
} else {
|
|
548
|
-
_newRange4.selectNodeContents(_targetSpan7);
|
|
549
|
-
_newRange4.collapse(false);
|
|
550
|
-
}
|
|
551
|
-
currentSpanNow.remove();
|
|
552
|
-
} else if (allSpansNow.length > 1) {
|
|
553
|
-
// 第一个 span 为空,移到下一个 span 的开头
|
|
554
|
-
var _nextSpan2 = allSpansNow[currentIndexNow + 1];
|
|
555
|
-
_newRange4.selectNodeContents(_nextSpan2);
|
|
556
|
-
_newRange4.collapse(true);
|
|
557
|
-
currentSpanNow.remove();
|
|
558
|
-
} else {
|
|
559
|
-
// 只有一个空 span,保持空
|
|
560
|
-
_newRange4.selectNodeContents(div);
|
|
561
|
-
_newRange4.collapse(true);
|
|
562
|
-
}
|
|
563
|
-
selection.removeAllRanges();
|
|
564
|
-
selection.addRange(_newRange4);
|
|
565
|
-
}
|
|
566
|
-
}, 0);
|
|
567
|
-
}, []);
|
|
568
|
-
|
|
569
|
-
// 递归渲染多级菜单
|
|
570
|
-
var renderMenuItems = function renderMenuItems(items) {
|
|
571
|
-
return items.map(function (opt) {
|
|
572
|
-
if (opt.children && opt.children.length > 0) {
|
|
573
|
-
return /*#__PURE__*/_jsx(Menu.SubMenu, {
|
|
574
|
-
title: opt.label,
|
|
575
|
-
className: "formula-editor-dropdown-submenu",
|
|
576
|
-
children: renderMenuItems(opt.children)
|
|
577
|
-
}, opt.value);
|
|
578
|
-
}
|
|
579
|
-
return /*#__PURE__*/_jsxs(Menu.Item, {
|
|
580
|
-
className: "formula-editor-dropdown-item",
|
|
581
|
-
onClick: function onClick() {
|
|
582
|
-
return handleOptionClick(opt);
|
|
583
|
-
},
|
|
584
|
-
children: [opt.label, opt.info ? /*#__PURE__*/_jsx(Tooltip, {
|
|
585
|
-
title: opt.info,
|
|
586
|
-
children: /*#__PURE__*/_jsx(InfoCircleOutlined, {
|
|
587
|
-
style: {
|
|
588
|
-
color: '#FF9900',
|
|
589
|
-
marginLeft: 4
|
|
590
|
-
}
|
|
591
|
-
})
|
|
592
|
-
}) : null]
|
|
593
|
-
}, opt.value);
|
|
594
|
-
});
|
|
595
|
-
};
|
|
596
|
-
var dropdownContent = /*#__PURE__*/_jsx(Menu, {
|
|
597
|
-
className: "formula-editor-dropdown",
|
|
598
|
-
style: {
|
|
599
|
-
transform: "translateX(".concat(offsetX, "px)")
|
|
600
|
-
},
|
|
601
|
-
onMouseDown: function onMouseDown(e) {
|
|
602
|
-
e.preventDefault();
|
|
603
|
-
e.stopPropagation();
|
|
604
|
-
},
|
|
605
|
-
children: filteredHierarchyOptions.length === 0 ? /*#__PURE__*/_jsx(Menu.Item, {
|
|
606
|
-
disabled: true,
|
|
607
|
-
children: /*#__PURE__*/_jsx("span", {
|
|
608
|
-
className: "formula-editor-dropdown-no-data",
|
|
609
|
-
children: "\u6682\u65E0\u6570\u636E"
|
|
610
|
-
})
|
|
611
|
-
}, "no-data") : renderMenuItems(filteredHierarchyOptions)
|
|
612
|
-
});
|
|
613
260
|
var handleChange = useCallback(function (value) {
|
|
614
261
|
onChange(!isNil(value) ? value : null);
|
|
615
262
|
}, [onChange]);
|
|
@@ -643,7 +290,13 @@ export var getFormulaInputEditor = function getFormulaInputEditor(options, repla
|
|
|
643
290
|
}
|
|
644
291
|
return /*#__PURE__*/_jsx(Dropdown, {
|
|
645
292
|
overlayClassName: "dropDownOffset",
|
|
646
|
-
overlay:
|
|
293
|
+
overlay: /*#__PURE__*/_jsx(DropMenu, {
|
|
294
|
+
options: filteredHierarchyOptions,
|
|
295
|
+
onOptionClick: handleOptionClick,
|
|
296
|
+
style: {
|
|
297
|
+
transform: "translateX(".concat(offsetX, "px)")
|
|
298
|
+
}
|
|
299
|
+
}),
|
|
647
300
|
open: open,
|
|
648
301
|
children: /*#__PURE__*/_jsx("div", {
|
|
649
302
|
ref: containerRef,
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { KeyboardEvent as ReactKeyboardEvent } from 'react';
|
|
2
|
+
import { OptionItem } from './utils';
|
|
3
|
+
declare const useKeyEventEffect: ({ closeDorpDown, setOffsetX, setOpen, containerRef, tempSpanRef, inputAfterDropDownShow, options }: {
|
|
4
|
+
closeDorpDown: () => void;
|
|
5
|
+
setOffsetX: (value: React.SetStateAction<number>) => void;
|
|
6
|
+
setOpen: (value: React.SetStateAction<boolean>) => void;
|
|
7
|
+
containerRef: React.RefObject<HTMLDivElement>;
|
|
8
|
+
tempSpanRef: React.MutableRefObject<HTMLElement | null>;
|
|
9
|
+
inputAfterDropDownShow: string;
|
|
10
|
+
options: OptionItem[];
|
|
11
|
+
}) => {
|
|
12
|
+
handleMouseUp: () => void;
|
|
13
|
+
handleKeyDown: (e: ReactKeyboardEvent<HTMLDivElement>) => void;
|
|
14
|
+
filteredHierarchyOptions: OptionItem[];
|
|
15
|
+
};
|
|
16
|
+
export { useKeyEventEffect };
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
|
|
2
|
+
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
3
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
4
|
+
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
|
5
|
+
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : String(i); }
|
|
6
|
+
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
7
|
+
function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
|
|
8
|
+
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
|
|
9
|
+
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
|
|
10
|
+
import { useCallback, useMemo } from 'react';
|
|
11
|
+
import { getCursorPositionInSpan, getSpanAtCursor, moveCursorToSpan } from "./utils";
|
|
12
|
+
var useKeyEventEffect = function useKeyEventEffect(_ref) {
|
|
13
|
+
var closeDorpDown = _ref.closeDorpDown,
|
|
14
|
+
setOffsetX = _ref.setOffsetX,
|
|
15
|
+
setOpen = _ref.setOpen,
|
|
16
|
+
containerRef = _ref.containerRef,
|
|
17
|
+
tempSpanRef = _ref.tempSpanRef,
|
|
18
|
+
inputAfterDropDownShow = _ref.inputAfterDropDownShow,
|
|
19
|
+
options = _ref.options;
|
|
20
|
+
// 鼠标释放时,如果光标在 label span 内,打开下拉框并记录 tempSpan
|
|
21
|
+
var handleMouseUp = useCallback(function () {
|
|
22
|
+
var div = containerRef.current;
|
|
23
|
+
if (!div) return;
|
|
24
|
+
closeDorpDown();
|
|
25
|
+
var selection = window.getSelection();
|
|
26
|
+
if (!selection || selection.rangeCount === 0) return;
|
|
27
|
+
var range = selection.getRangeAt(0);
|
|
28
|
+
var startContainer = range.startContainer;
|
|
29
|
+
var currentSpan = getSpanAtCursor(startContainer);
|
|
30
|
+
if (!currentSpan || currentSpan === div) return;
|
|
31
|
+
var isLabelSpan = currentSpan.classList.contains('formula-editor-token-label');
|
|
32
|
+
|
|
33
|
+
// 如果在 label span 内,打开下拉框并记录 tempSpan
|
|
34
|
+
if (isLabelSpan) {
|
|
35
|
+
// 更新 offsetX 位置
|
|
36
|
+
var containerRect = div.getBoundingClientRect();
|
|
37
|
+
var cursorRect = range.getBoundingClientRect();
|
|
38
|
+
var cursorDistance = cursorRect ? cursorRect.left - containerRect.left : 0;
|
|
39
|
+
setOffsetX(cursorDistance < 150 ? 0 : cursorDistance - 8);
|
|
40
|
+
|
|
41
|
+
// 记录当前 label span 作为 tempSpan
|
|
42
|
+
tempSpanRef.current = currentSpan;
|
|
43
|
+
|
|
44
|
+
// 打开下拉框
|
|
45
|
+
setOpen(true);
|
|
46
|
+
// 移动到下一个 span 的开头
|
|
47
|
+
var allSpans = Array.from(div.querySelectorAll('span'));
|
|
48
|
+
var currentIndex = allSpans.indexOf(currentSpan);
|
|
49
|
+
if (currentIndex < allSpans.length - 1) {
|
|
50
|
+
var nextSpan = allSpans[currentIndex + 1];
|
|
51
|
+
moveCursorToSpan(nextSpan, true, selection);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}, [closeDorpDown, setOffsetX, setOpen]);
|
|
55
|
+
var handleKeyDown = useCallback(function (e) {
|
|
56
|
+
var _currentSpan$textCont, _currentSpan$textCont2;
|
|
57
|
+
if (e.key === 'ArrowUp' || e.key === 'ArrowDown' || e.key === 'Delete') {
|
|
58
|
+
e.preventDefault();
|
|
59
|
+
e.stopPropagation();
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
var div = containerRef.current;
|
|
63
|
+
if (!div) return;
|
|
64
|
+
var selection = window.getSelection();
|
|
65
|
+
if (!selection || selection.rangeCount === 0) return;
|
|
66
|
+
var range = selection.getRangeAt(0);
|
|
67
|
+
var startContainer = range.startContainer,
|
|
68
|
+
startOffset = range.startOffset;
|
|
69
|
+
var currentSpan = getSpanAtCursor(startContainer);
|
|
70
|
+
if (!currentSpan || currentSpan === div) return;
|
|
71
|
+
var allSpans = Array.from(div.querySelectorAll('span'));
|
|
72
|
+
var currentIndex = allSpans.indexOf(currentSpan);
|
|
73
|
+
var textLen = (_currentSpan$textCont = (_currentSpan$textCont2 = currentSpan.textContent) === null || _currentSpan$textCont2 === void 0 ? void 0 : _currentSpan$textCont2.length) !== null && _currentSpan$textCont !== void 0 ? _currentSpan$textCont : 0;
|
|
74
|
+
|
|
75
|
+
// 方向键:跳过 label span
|
|
76
|
+
if (e.key === 'ArrowRight' || e.key === 'ArrowLeft') {
|
|
77
|
+
var position = getCursorPositionInSpan(currentSpan);
|
|
78
|
+
var isAtEnd = startOffset >= textLen;
|
|
79
|
+
var isAtStart = startOffset === 0;
|
|
80
|
+
var isAlmostEnd = startOffset >= textLen - 1;
|
|
81
|
+
var isAlmostStart = position === 1;
|
|
82
|
+
if (e.key === 'ArrowRight' && isAtEnd) {
|
|
83
|
+
closeDorpDown();
|
|
84
|
+
// 向右 + 在末尾 -> 跳到下一个 span 的开头(跳过 label span)
|
|
85
|
+
var targetIndex = currentIndex + 1;
|
|
86
|
+
while (targetIndex < allSpans.length && allSpans[targetIndex].classList.contains('formula-editor-token-label')) {
|
|
87
|
+
targetIndex++;
|
|
88
|
+
}
|
|
89
|
+
if (targetIndex < allSpans.length && targetIndex !== currentIndex + 1) {
|
|
90
|
+
e.preventDefault();
|
|
91
|
+
var targetSpan = allSpans[targetIndex];
|
|
92
|
+
moveCursorToSpan(targetSpan, true, selection);
|
|
93
|
+
}
|
|
94
|
+
} else if (e.key === 'ArrowRight' && isAlmostEnd) {
|
|
95
|
+
closeDorpDown();
|
|
96
|
+
var _targetIndex = currentIndex + 1;
|
|
97
|
+
if (_targetIndex < allSpans.length && !allSpans[_targetIndex].classList.contains('formula-editor-token-label')) {
|
|
98
|
+
e.preventDefault();
|
|
99
|
+
var _targetSpan = allSpans[_targetIndex];
|
|
100
|
+
moveCursorToSpan(_targetSpan, true, selection);
|
|
101
|
+
}
|
|
102
|
+
} else if (e.key === 'ArrowLeft' && isAtStart) {
|
|
103
|
+
closeDorpDown();
|
|
104
|
+
// 向左 + 在开头 -> 跳到上一个 span 的末尾(跳过 label span)
|
|
105
|
+
var _targetIndex2 = currentIndex - 1;
|
|
106
|
+
while (_targetIndex2 >= 0 && allSpans[_targetIndex2].classList.contains('formula-editor-token-label')) {
|
|
107
|
+
_targetIndex2--;
|
|
108
|
+
}
|
|
109
|
+
if (_targetIndex2 >= 0 && _targetIndex2 !== currentIndex - 1) {
|
|
110
|
+
e.preventDefault();
|
|
111
|
+
var _targetSpan2 = allSpans[_targetIndex2];
|
|
112
|
+
moveCursorToSpan(_targetSpan2, false, selection);
|
|
113
|
+
}
|
|
114
|
+
} else if (e.key === 'ArrowLeft' && isAlmostStart) {
|
|
115
|
+
closeDorpDown();
|
|
116
|
+
var _targetIndex3 = currentIndex - 1;
|
|
117
|
+
if (_targetIndex3 >= 0 && !allSpans[_targetIndex3].classList.contains('formula-editor-token-label')) {
|
|
118
|
+
e.preventDefault();
|
|
119
|
+
var _targetSpan3 = allSpans[_targetIndex3];
|
|
120
|
+
moveCursorToSpan(_targetSpan3, false, selection);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 删除键逻辑
|
|
127
|
+
if (e.key !== 'Delete' && e.key !== 'Backspace') return;
|
|
128
|
+
if (e.key === 'Backspace' || e.key === 'Delete') {
|
|
129
|
+
if (tempSpanRef.current) {
|
|
130
|
+
closeDorpDown();
|
|
131
|
+
}
|
|
132
|
+
// 检查删除后是否只剩一个空的 span
|
|
133
|
+
var allSpansNow = Array.from(div.querySelectorAll('span'));
|
|
134
|
+
var isOnlyOneEmptySpan = allSpansNow.length === 1 && allSpansNow[0].textContent === '';
|
|
135
|
+
if (isOnlyOneEmptySpan) {
|
|
136
|
+
e.preventDefault();
|
|
137
|
+
e.stopPropagation();
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// 不在 label span 内,在 span 开头且前一个是 label 时,删除整个 label span
|
|
143
|
+
if (startOffset === 0 && e.key === 'Backspace' && currentIndex > 0) {
|
|
144
|
+
var prevSpan = allSpans[currentIndex - 1];
|
|
145
|
+
if (prevSpan.classList.contains('formula-editor-token-label')) {
|
|
146
|
+
e.preventDefault();
|
|
147
|
+
e.stopPropagation();
|
|
148
|
+
prevSpan.remove();
|
|
149
|
+
var newRange = document.createRange();
|
|
150
|
+
|
|
151
|
+
// 如果当前 span 有内容,光标放在开头;否则找最近的有内容的 span
|
|
152
|
+
if (currentSpan.textContent !== '') {
|
|
153
|
+
newRange.selectNodeContents(currentSpan);
|
|
154
|
+
newRange.collapse(true);
|
|
155
|
+
} else {
|
|
156
|
+
// 找前一个最近的有内容的 span
|
|
157
|
+
var _targetSpan4;
|
|
158
|
+
for (var i = currentIndex - 2; i >= 0; i--) {
|
|
159
|
+
if (allSpans[i].textContent !== '') {
|
|
160
|
+
_targetSpan4 = allSpans[i];
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (_targetSpan4) {
|
|
165
|
+
newRange.selectNodeContents(_targetSpan4);
|
|
166
|
+
newRange.collapse(false);
|
|
167
|
+
} else {
|
|
168
|
+
newRange.selectNodeContents(div);
|
|
169
|
+
newRange.collapse(true);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
selection.removeAllRanges();
|
|
173
|
+
selection.addRange(newRange);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// 在 span 开头 + Delete 键,光标移到前一个 span 的末尾
|
|
179
|
+
if (startOffset === 0 && e.key === 'Backspace' && currentIndex > 0) {
|
|
180
|
+
var _prevSpan = allSpans[currentIndex - 1];
|
|
181
|
+
// 前一个 span 是非 label span 时,光标移到其末尾
|
|
182
|
+
if (!_prevSpan.classList.contains('formula-editor-token-label')) {
|
|
183
|
+
e.preventDefault();
|
|
184
|
+
e.stopPropagation();
|
|
185
|
+
var prevText = _prevSpan.textContent || '';
|
|
186
|
+
if (prevText.length > 0) {
|
|
187
|
+
_prevSpan.textContent = prevText.slice(0, -1);
|
|
188
|
+
}
|
|
189
|
+
moveCursorToSpan(_prevSpan, false, selection);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// 其他情况:等待浏览器完成默认删除后,检查当前 span 是否为空
|
|
194
|
+
setTimeout(function () {
|
|
195
|
+
var currentSpanNow = getSpanAtCursor(startContainer);
|
|
196
|
+
var allSpansNow = Array.from(div.querySelectorAll('span'));
|
|
197
|
+
|
|
198
|
+
// 如果 currentSpanNow 不在 allSpansNow 里(被浏览器删除了)
|
|
199
|
+
// 且还有 span 存在,用按下删除键时的 currentSpan 位置往前找
|
|
200
|
+
if (allSpansNow.length > 0 && !allSpansNow.includes(currentSpanNow)) {
|
|
201
|
+
// 如果光标在 span 开头 + Delete 键,currentIndex 指向的是被删 span 的后一个,所以要 -1
|
|
202
|
+
var searchIndex = startOffset === 0 ? currentIndex - 1 : currentIndex;
|
|
203
|
+
// 从 searchIndex 位置往前找
|
|
204
|
+
var _targetSpan5;
|
|
205
|
+
for (var _i = searchIndex; _i >= 0; _i--) {
|
|
206
|
+
var span = allSpansNow[_i];
|
|
207
|
+
if (span.textContent !== '') {
|
|
208
|
+
_targetSpan5 = span;
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// 如果找到的有内容 span 是 label,则把光标放在 label span 的后一个 span 的开头
|
|
213
|
+
if (_targetSpan5 && _targetSpan5.classList.contains('formula-editor-token-label')) {
|
|
214
|
+
var labelIndex = allSpansNow.indexOf(_targetSpan5);
|
|
215
|
+
if (labelIndex < allSpansNow.length - 1) {
|
|
216
|
+
var nextSpan = allSpansNow[labelIndex + 1];
|
|
217
|
+
moveCursorToSpan(nextSpan, true, selection);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// 否则把光标放在找到的 span 的末尾
|
|
223
|
+
if (_targetSpan5) {
|
|
224
|
+
moveCursorToSpan(_targetSpan5, false, selection);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (!currentSpanNow || currentSpanNow === div) return;
|
|
229
|
+
if (currentSpanNow.textContent === '') {
|
|
230
|
+
var currentIndexNow = allSpansNow.indexOf(currentSpanNow);
|
|
231
|
+
var _newRange = document.createRange();
|
|
232
|
+
if (currentIndexNow > 0) {
|
|
233
|
+
// 移到前一个 span 的末尾,但如果前一个 span 是 label,则移到后一个 span 的开头
|
|
234
|
+
var _targetSpan6 = allSpansNow[currentIndexNow - 1];
|
|
235
|
+
if (_targetSpan6.classList.contains('formula-editor-token-label')) {
|
|
236
|
+
// 移到 label span 的后一个 span 的开头
|
|
237
|
+
if (currentIndexNow < allSpansNow.length - 1) {
|
|
238
|
+
var _nextSpan = allSpansNow[currentIndexNow + 1];
|
|
239
|
+
_newRange.selectNodeContents(_nextSpan);
|
|
240
|
+
_newRange.collapse(true);
|
|
241
|
+
} else {
|
|
242
|
+
_newRange.selectNodeContents(div);
|
|
243
|
+
_newRange.collapse(false);
|
|
244
|
+
}
|
|
245
|
+
} else {
|
|
246
|
+
_newRange.selectNodeContents(_targetSpan6);
|
|
247
|
+
_newRange.collapse(false);
|
|
248
|
+
}
|
|
249
|
+
currentSpanNow.remove();
|
|
250
|
+
} else if (allSpansNow.length > 1) {
|
|
251
|
+
// 第一个 span 为空,移到下一个 span 的开头
|
|
252
|
+
var _nextSpan2 = allSpansNow[currentIndexNow + 1];
|
|
253
|
+
_newRange.selectNodeContents(_nextSpan2);
|
|
254
|
+
_newRange.collapse(true);
|
|
255
|
+
currentSpanNow.remove();
|
|
256
|
+
} else {
|
|
257
|
+
// 只有一个空 span,保持空
|
|
258
|
+
_newRange.selectNodeContents(div);
|
|
259
|
+
_newRange.collapse(true);
|
|
260
|
+
}
|
|
261
|
+
selection.removeAllRanges();
|
|
262
|
+
selection.addRange(_newRange);
|
|
263
|
+
}
|
|
264
|
+
}, 0);
|
|
265
|
+
}, [closeDorpDown, containerRef.current]);
|
|
266
|
+
var filteredHierarchyOptions = useMemo(function () {
|
|
267
|
+
var searchText = inputAfterDropDownShow.trim();
|
|
268
|
+
// 如果搜索文本为空,显示所有选项(保持层级)
|
|
269
|
+
if (!searchText) {
|
|
270
|
+
return options;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// 根据搜索文本筛选,保持层级结构
|
|
274
|
+
var filterWithHierarchy = function filterWithHierarchy(items) {
|
|
275
|
+
var result = [];
|
|
276
|
+
var _iterator = _createForOfIteratorHelper(items),
|
|
277
|
+
_step;
|
|
278
|
+
try {
|
|
279
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
280
|
+
var item = _step.value;
|
|
281
|
+
var labelMatch = item.label.includes(searchText);
|
|
282
|
+
// 筛选 children
|
|
283
|
+
var filteredChildren = item.children ? filterWithHierarchy(item.children) : [];
|
|
284
|
+
if (labelMatch || filteredChildren.length > 0) {
|
|
285
|
+
result.push(_objectSpread(_objectSpread({}, item), {}, {
|
|
286
|
+
children: filteredChildren.length > 0 ? filteredChildren : item.children
|
|
287
|
+
}));
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
} catch (err) {
|
|
291
|
+
_iterator.e(err);
|
|
292
|
+
} finally {
|
|
293
|
+
_iterator.f();
|
|
294
|
+
}
|
|
295
|
+
return result;
|
|
296
|
+
};
|
|
297
|
+
return filterWithHierarchy(options);
|
|
298
|
+
}, [inputAfterDropDownShow]);
|
|
299
|
+
return {
|
|
300
|
+
handleMouseUp: handleMouseUp,
|
|
301
|
+
handleKeyDown: handleKeyDown,
|
|
302
|
+
filteredHierarchyOptions: filteredHierarchyOptions
|
|
303
|
+
};
|
|
304
|
+
};
|
|
305
|
+
export { useKeyEventEffect };
|
|
@@ -73,3 +73,8 @@ export type inputProps = Partial<Pick<InputNumberProps, 'max' | 'min' | 'addonBe
|
|
|
73
73
|
}>;
|
|
74
74
|
export declare const getSpanAtCursor: (startContainer: any) => HTMLElement | null;
|
|
75
75
|
export declare const moveCursorToSpan: (targetSpan: HTMLElement, atStart: boolean | undefined, selection: any) => void;
|
|
76
|
+
export declare enum SpanType {
|
|
77
|
+
LABEL = "label",
|
|
78
|
+
OTHER = "other"
|
|
79
|
+
}
|
|
80
|
+
export declare const createSpan: (type: string, content: string) => HTMLElement;
|
|
@@ -5,6 +5,8 @@ function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o =
|
|
|
5
5
|
function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
|
|
6
6
|
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
|
|
7
7
|
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
|
|
8
|
+
import { get } from "lodash";
|
|
9
|
+
|
|
8
10
|
// 运算符列表
|
|
9
11
|
export var OPERATORS = ['+', '-', '*', '/'];
|
|
10
12
|
|
|
@@ -360,4 +362,19 @@ export var moveCursorToSpan = function moveCursorToSpan(targetSpan) {
|
|
|
360
362
|
newRange.collapse(atStart);
|
|
361
363
|
selection.removeAllRanges();
|
|
362
364
|
selection.addRange(newRange);
|
|
365
|
+
};
|
|
366
|
+
export var SpanType = /*#__PURE__*/function (SpanType) {
|
|
367
|
+
SpanType["LABEL"] = "label";
|
|
368
|
+
SpanType["OTHER"] = "other";
|
|
369
|
+
return SpanType;
|
|
370
|
+
}({});
|
|
371
|
+
var spanTypeStyle = {
|
|
372
|
+
label: 'formula-editor-token-label',
|
|
373
|
+
other: 'formula-editor-token-other'
|
|
374
|
+
};
|
|
375
|
+
export var createSpan = function createSpan(type, content) {
|
|
376
|
+
var span = document.createElement('span');
|
|
377
|
+
span.className = get(spanTypeStyle, [type]);
|
|
378
|
+
span.textContent = content;
|
|
379
|
+
return span;
|
|
363
380
|
};
|
package/dist/example/basic.js
CHANGED
|
@@ -197,7 +197,7 @@ var App = function App() {
|
|
|
197
197
|
var row = _ref4.row,
|
|
198
198
|
key = _ref4.key,
|
|
199
199
|
value = _ref4.value;
|
|
200
|
-
newData[row] = _objectSpread(_objectSpread({}, newData[row]), {}, _defineProperty({}, key, key === 'tags' ? (_String = String(value)) === null || _String === void 0 ? void 0 : _String.split(',') : value));
|
|
200
|
+
newData[row] = _objectSpread(_objectSpread({}, newData[row]), {}, _defineProperty({}, key === 'amount' ? 'formula' : key, key === 'tags' ? (_String = String(value)) === null || _String === void 0 ? void 0 : _String.split(',') : value));
|
|
201
201
|
});
|
|
202
202
|
setData(newData);
|
|
203
203
|
}, [dataSource]);
|