@veltra/sheet 2.5.0 → 2.5.1

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.
@@ -38,6 +38,10 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
38
38
  const mirroring = ref(false);
39
39
  let mirrorAddr = null;
40
40
  const fxDraft = ref("");
41
+ /** 鼠标是否悬停在公式栏输入区 */
42
+ const fxHovered = ref(false);
43
+ /** 输入框是否处于 focus 状态 */
44
+ const fxFocused = ref(false);
41
45
  /** 输入面板是否因内容增高而浮出(文档流仍保持单行) */
42
46
  const fxExpanded = ref(false);
43
47
  /** textarea 光标(selectionStart);输入/点击/方向键后同步 */
@@ -76,7 +80,13 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
76
80
  }
77
81
  function nameText(state) {
78
82
  const range = state.ranges[0];
79
- if (range) return formatRange(range);
83
+ if (range) {
84
+ if (state.activeCell) {
85
+ const merge = props.sheet.merges.getMergeAt(state.activeCell);
86
+ if (merge && range.start.row === merge.start.row && range.start.col === merge.start.col && range.end.row === merge.end.row && range.end.col === merge.end.col) return formatAddress(state.activeCell);
87
+ }
88
+ return formatRange(range);
89
+ }
80
90
  return state.activeCell ? formatAddress(state.activeCell) : "";
81
91
  }
82
92
  function cellText(addr) {
@@ -93,7 +103,16 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
93
103
  fxDraft.value = active ? cellText(active) : "";
94
104
  fxCursor.value = fxDraft.value.length;
95
105
  closeSuggest();
96
- scheduleAutosize();
106
+ if (fxHovered.value || fxFocused.value || editing.value) scheduleAutosize();
107
+ else collapseFx();
108
+ }
109
+ /** 恢复单行高度并重置展开状态 */
110
+ function collapseFx() {
111
+ const el = fxRef.value;
112
+ if (!el) return;
113
+ el.style.height = "";
114
+ el.scrollTop = 0;
115
+ fxExpanded.value = false;
97
116
  }
98
117
  /** 每帧合并一次的 autosizeFx(批量 cell-change 同帧 N 次 nextTick → 1 次强制布局) */
99
118
  let autosizePending = false;
@@ -106,20 +125,33 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
106
125
  });
107
126
  }
108
127
  /**
109
- * 按内容增高浮出面板,不超过 CSS max-height。
128
+ * 仅在鼠标悬停、获得焦点或编辑时按内容增高浮出面板,不超过 CSS max-height。
110
129
  * 增高走绝对定位,公式栏文档流高度始终单行。
111
130
  */
112
131
  function autosizeFx() {
113
132
  const el = fxRef.value;
114
133
  if (!el) return;
134
+ if (!fxHovered.value && !fxFocused.value && !editing.value) {
135
+ collapseFx();
136
+ return;
137
+ }
115
138
  el.style.height = "0px";
116
139
  const styles = getComputedStyle(el);
117
140
  const max = Number.parseFloat(styles.maxHeight);
118
141
  const min = Number.parseFloat(styles.minHeight);
142
+ const hasMultipleLines = fxDraft.value.includes("\n");
119
143
  const content = el.scrollHeight;
120
144
  const next = Number.isFinite(max) && max > 0 ? Math.min(content, max) : content;
121
145
  el.style.height = `${next}px`;
122
- fxExpanded.value = Number.isFinite(min) ? next > min + 1 : next > 1;
146
+ fxExpanded.value = (Number.isFinite(min) ? next > min + 1 : next > 1) || hasMultipleLines;
147
+ }
148
+ function handleFxMouseEnter() {
149
+ fxHovered.value = true;
150
+ if (fxDraft.value) autosizeFx();
151
+ }
152
+ function handleFxMouseLeave() {
153
+ fxHovered.value = false;
154
+ if (!fxFocused.value && !editing.value) collapseFx();
123
155
  }
124
156
  function syncFxCursor() {
125
157
  const el = fxRef.value;
@@ -193,23 +225,30 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
193
225
  }
194
226
  function handleFxFocus() {
195
227
  if (fxDisabled.value) return;
228
+ fxFocused.value = true;
196
229
  mirroring.value = false;
197
230
  mirrorAddr = null;
198
231
  editing.value = true;
199
232
  editAddr = selection.value.activeCell;
200
233
  syncFxCursor();
234
+ scheduleAutosize();
201
235
  }
202
236
  /**
203
237
  * 失焦提交;引用选择期间(画布 pointerdown 已挂起)跳过。
204
238
  * ✓/✗ / 候选列表用 mousedown.prevent 拦截失焦。
205
239
  */
206
240
  function handleFxBlur() {
207
- if (!editing.value) return;
241
+ fxFocused.value = false;
242
+ if (!editing.value) {
243
+ if (!fxHovered.value) collapseFx();
244
+ return;
245
+ }
208
246
  if (suppressBlurCommit) {
209
247
  suppressBlurCommit = false;
210
248
  return;
211
249
  }
212
250
  commitEdit();
251
+ if (!fxHovered.value) collapseFx();
213
252
  }
214
253
  function handleFxKeydown(event) {
215
254
  if (suggestOpen.value) {
@@ -345,7 +384,11 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
345
384
  onKeydown: handleNameKeydown
346
385
  }, null, 34), [[vModelText, nameDraft.value]]),
347
386
  createElementVNode("span", { class: normalizeClass(unref(cls).e("fx-label")) }, "fx", 2),
348
- createElementVNode("div", { class: normalizeClass(unref(cls).e("fx-editor")) }, [createCommentVNode(" 文档流只占单行;面板绝对定位向下浮起,不撑开公式栏 "), createElementVNode("div", { class: normalizeClass([unref(cls).e("fx-input-panel"), unref(bem).is("expanded", fxExpanded.value)]) }, [withDirectives(createElementVNode("textarea", {
387
+ createElementVNode("div", {
388
+ class: normalizeClass(unref(cls).e("fx-editor")),
389
+ onMouseenter: handleFxMouseEnter,
390
+ onMouseleave: handleFxMouseLeave
391
+ }, [createCommentVNode(" 文档流只占单行;面板绝对定位向下浮起,不撑开公式栏 "), createElementVNode("div", { class: normalizeClass([unref(cls).e("fx-input-panel"), unref(bem).is("expanded", fxExpanded.value)]) }, [withDirectives(createElementVNode("textarea", {
349
392
  ref_key: "fxRef",
350
393
  ref: fxRef,
351
394
  "onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => fxDraft.value = $event),
@@ -368,7 +411,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
368
411
  "active-index": suggestIndex.value,
369
412
  onSelect: confirmSuggest,
370
413
  onHover: _cache[3] || (_cache[3] = (i) => suggestIndex.value = i)
371
- }, null, 8, ["items", "active-index"])], 2)], 2),
414
+ }, null, 8, ["items", "active-index"])], 2)], 34),
372
415
  editing.value ? (openBlock(), createElementBlock("button", {
373
416
  key: 0,
374
417
  type: "button",
@@ -14,7 +14,7 @@
14
14
  * --u-height-default: 32px;
15
15
  * --u-height-small: 24px;
16
16
  */
17
- /** 暗色:data-theme 与系统偏好,与 UITheme.injectBuiltInThemes 选择器策略一致 */
17
+ /** 暗色:`UITheme.render()` 按主题系列写入 html[data-theme] */
18
18
  .u-sheet {
19
19
  position: relative;
20
20
  display: flex;
@@ -246,7 +246,7 @@
246
246
  line-height: 1.4;
247
247
  resize: none;
248
248
  overflow-x: hidden;
249
- overflow-y: auto;
249
+ overflow-y: hidden;
250
250
  outline: none;
251
251
  }
252
252
  .u-sheet__fx-input:focus {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veltra/sheet",
3
- "version": "2.5.0",
3
+ "version": "2.5.1",
4
4
  "homepage": "https://github.com/cabinet-fe/ultra-ui#readme",
5
5
  "bugs": {
6
6
  "url": "https://github.com/cabinet-fe/ultra-ui/issues"
@@ -36,11 +36,11 @@
36
36
  "peerDependencies": {
37
37
  "@cat-kit/core": ">=1.1.8",
38
38
  "@cat-kit/fe": ">=1.1.8",
39
- "@veltra/desktop": "^1.6.5",
39
+ "@veltra/desktop": "^1.7.0",
40
40
  "@veltra/icons": "^1.5.0",
41
- "@veltra/sheet-core": "^2.5.0",
42
- "@veltra/styles": "^1.6.5",
43
- "@veltra/utils": "^1.6.5",
41
+ "@veltra/sheet-core": "^2.5.1",
42
+ "@veltra/styles": "^1.7.0",
43
+ "@veltra/utils": "^1.7.0",
44
44
  "vue": ">=3.5.41"
45
45
  }
46
46
  }
@@ -471,4 +471,33 @@ describe('USheet 公式栏:函数补全与引用选择', () => {
471
471
  await nextTick()
472
472
  expect(fxInput(el).value).toBe('=A1+B1')
473
473
  })
474
+
475
+ it('多行文本:默认保持单行高度,鼠标进入展开,移出后收起', async () => {
476
+ const workbook = new Workbook()
477
+ const sheet = workbook.activeSheet
478
+ sheet.setCellValue({ row: 0, col: 0 }, 'Line1\nLine2\nLine3\nLine4\nLine5')
479
+ const { el } = mount(() => ({ workbook, rows: 20, cols: 8 }))
480
+ await nextTick()
481
+ sheet.selectCell({ row: 0, col: 0 })
482
+ await nextTick()
483
+
484
+ const input = fxInput(el)
485
+ const panel = el.querySelector('.u-sheet__fx-input-panel')!
486
+ const editor = el.querySelector('.u-sheet__fx-editor')!
487
+
488
+ // 默认单行未展开,无 is-expanded 类,height 为空(由 CSS min-height 承载)
489
+ expect(panel.classList.contains('is-expanded')).toBe(false)
490
+ expect(input.style.height).toBe('')
491
+
492
+ // 鼠标移入公式栏编辑区:触发展开
493
+ editor.dispatchEvent(new MouseEvent('mouseenter'))
494
+ await nextTick()
495
+ expect(panel.classList.contains('is-expanded')).toBe(true)
496
+
497
+ // 鼠标移出公式栏编辑区:收起为单行
498
+ editor.dispatchEvent(new MouseEvent('mouseleave'))
499
+ await nextTick()
500
+ expect(panel.classList.contains('is-expanded')).toBe(false)
501
+ expect(input.style.height).toBe('')
502
+ })
474
503
  })
@@ -11,7 +11,11 @@
11
11
  @keydown="handleNameKeydown"
12
12
  />
13
13
  <span :class="cls.e('fx-label')">fx</span>
14
- <div :class="cls.e('fx-editor')">
14
+ <div
15
+ :class="cls.e('fx-editor')"
16
+ @mouseenter="handleFxMouseEnter"
17
+ @mouseleave="handleFxMouseLeave"
18
+ >
15
19
  <!-- 文档流只占单行;面板绝对定位向下浮起,不撑开公式栏 -->
16
20
  <div :class="[cls.e('fx-input-panel'), bem.is('expanded', fxExpanded)]">
17
21
  <textarea
@@ -122,6 +126,10 @@ let editAddr: CellAddress | null = null
122
126
  const mirroring = ref(false)
123
127
  let mirrorAddr: CellAddress | null = null
124
128
  const fxDraft = ref('')
129
+ /** 鼠标是否悬停在公式栏输入区 */
130
+ const fxHovered = ref(false)
131
+ /** 输入框是否处于 focus 状态 */
132
+ const fxFocused = ref(false)
125
133
  /** 输入面板是否因内容增高而浮出(文档流仍保持单行) */
126
134
  const fxExpanded = ref(false)
127
135
  /** textarea 光标(selectionStart);输入/点击/方向键后同步 */
@@ -170,7 +178,21 @@ function handleRefSelect(range: CellRange): void {
170
178
 
171
179
  function nameText(state: SelectionState): string {
172
180
  const range = state.ranges[0]
173
- if (range) return formatRange(range)
181
+ if (range) {
182
+ if (state.activeCell) {
183
+ const merge = props.sheet.merges.getMergeAt(state.activeCell)
184
+ if (
185
+ merge &&
186
+ range.start.row === merge.start.row &&
187
+ range.start.col === merge.start.col &&
188
+ range.end.row === merge.end.row &&
189
+ range.end.col === merge.end.col
190
+ ) {
191
+ return formatAddress(state.activeCell)
192
+ }
193
+ }
194
+ return formatRange(range)
195
+ }
174
196
  return state.activeCell ? formatAddress(state.activeCell) : ''
175
197
  }
176
198
 
@@ -190,7 +212,20 @@ function refreshFx(): void {
190
212
  fxDraft.value = active ? cellText(active) : ''
191
213
  fxCursor.value = fxDraft.value.length
192
214
  closeSuggest()
193
- scheduleAutosize()
215
+ if (fxHovered.value || fxFocused.value || editing.value) {
216
+ scheduleAutosize()
217
+ } else {
218
+ collapseFx()
219
+ }
220
+ }
221
+
222
+ /** 恢复单行高度并重置展开状态 */
223
+ function collapseFx(): void {
224
+ const el = fxRef.value
225
+ if (!el) return
226
+ el.style.height = ''
227
+ el.scrollTop = 0
228
+ fxExpanded.value = false
194
229
  }
195
230
 
196
231
  /** 每帧合并一次的 autosizeFx(批量 cell-change 同帧 N 次 nextTick → 1 次强制布局) */
@@ -205,20 +240,39 @@ function scheduleAutosize(): void {
205
240
  }
206
241
 
207
242
  /**
208
- * 按内容增高浮出面板,不超过 CSS max-height。
243
+ * 仅在鼠标悬停、获得焦点或编辑时按内容增高浮出面板,不超过 CSS max-height。
209
244
  * 增高走绝对定位,公式栏文档流高度始终单行。
210
245
  */
211
246
  function autosizeFx(): void {
212
247
  const el = fxRef.value
213
248
  if (!el) return
249
+ if (!fxHovered.value && !fxFocused.value && !editing.value) {
250
+ collapseFx()
251
+ return
252
+ }
214
253
  el.style.height = '0px'
215
254
  const styles = getComputedStyle(el)
216
255
  const max = Number.parseFloat(styles.maxHeight)
217
256
  const min = Number.parseFloat(styles.minHeight)
257
+ const hasMultipleLines = fxDraft.value.includes('\n')
218
258
  const content = el.scrollHeight
219
259
  const next = Number.isFinite(max) && max > 0 ? Math.min(content, max) : content
220
260
  el.style.height = `${next}px`
221
- fxExpanded.value = Number.isFinite(min) ? next > min + 1 : next > 1
261
+ fxExpanded.value = (Number.isFinite(min) ? next > min + 1 : next > 1) || hasMultipleLines
262
+ }
263
+
264
+ function handleFxMouseEnter(): void {
265
+ fxHovered.value = true
266
+ if (fxDraft.value) {
267
+ autosizeFx()
268
+ }
269
+ }
270
+
271
+ function handleFxMouseLeave(): void {
272
+ fxHovered.value = false
273
+ if (!fxFocused.value && !editing.value) {
274
+ collapseFx()
275
+ }
222
276
  }
223
277
 
224
278
  function syncFxCursor(): void {
@@ -311,11 +365,13 @@ function commitName(): void {
311
365
 
312
366
  function handleFxFocus(): void {
313
367
  if (fxDisabled.value) return
368
+ fxFocused.value = true
314
369
  mirroring.value = false
315
370
  mirrorAddr = null
316
371
  editing.value = true
317
372
  editAddr = selection.value.activeCell
318
373
  syncFxCursor()
374
+ scheduleAutosize()
319
375
  }
320
376
 
321
377
  /**
@@ -323,12 +379,17 @@ function handleFxFocus(): void {
323
379
  * ✓/✗ / 候选列表用 mousedown.prevent 拦截失焦。
324
380
  */
325
381
  function handleFxBlur(): void {
326
- if (!editing.value) return
382
+ fxFocused.value = false
383
+ if (!editing.value) {
384
+ if (!fxHovered.value) collapseFx()
385
+ return
386
+ }
327
387
  if (suppressBlurCommit) {
328
388
  suppressBlurCommit = false
329
389
  return
330
390
  }
331
391
  commitEdit()
392
+ if (!fxHovered.value) collapseFx()
332
393
  }
333
394
 
334
395
  function handleFxKeydown(event: KeyboardEvent): void {
@@ -269,7 +269,7 @@
269
269
  line-height: 1.4;
270
270
  resize: none;
271
271
  overflow-x: hidden;
272
- overflow-y: auto;
272
+ overflow-y: hidden;
273
273
  outline: none;
274
274
 
275
275
  &:focus {