@veltra/desktop 1.7.19 → 1.8.0

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.
Files changed (71) hide show
  1. package/dist/components/batch-edit/batch-edit-form.js +40 -26
  2. package/dist/components/batch-edit/batch-edit-form.js.map +1 -1
  3. package/dist/components/batch-edit/use-handlers.js +3 -3
  4. package/dist/components/batch-edit/use-handlers.js.map +1 -1
  5. package/dist/components/batch-edit/use-shortcut-key.js +4 -2
  6. package/dist/components/batch-edit/use-shortcut-key.js.map +1 -1
  7. package/dist/components/card/style2.css +0 -2
  8. package/dist/components/dropdown/dropdown.js +5 -3
  9. package/dist/components/dropdown/dropdown.js.map +1 -1
  10. package/dist/components/expression-editor/components/path-preview.js +3 -6
  11. package/dist/components/expression-editor/components/path-preview.js.map +1 -1
  12. package/dist/components/expression-editor/components/variable-picker.js +49 -12
  13. package/dist/components/expression-editor/components/variable-picker.js.map +1 -1
  14. package/dist/components/expression-editor/core/editor.js +20 -0
  15. package/dist/components/expression-editor/core/editor.js.map +1 -1
  16. package/dist/components/expression-editor/expression-editor.js +53 -18
  17. package/dist/components/expression-editor/expression-editor.js.map +1 -1
  18. package/dist/components/expression-editor/style2.css +135 -73
  19. package/dist/components/form/form.js.map +1 -1
  20. package/dist/components/form/form.vue_vue_type_script_setup_true_lang.js +0 -1
  21. package/dist/components/form/form.vue_vue_type_script_setup_true_lang.js.map +1 -1
  22. package/dist/components/form/style2.css +8 -3
  23. package/dist/components/index.d.ts +26 -26
  24. package/dist/components/index.js +52 -52
  25. package/dist/components/multi-select/multi-select.js +16 -12
  26. package/dist/components/multi-select/multi-select.js.map +1 -1
  27. package/dist/components/multi-select/style2.css +7 -0
  28. package/dist/components/multi-tree-select/multi-tree-select.js +82 -56
  29. package/dist/components/multi-tree-select/multi-tree-select.js.map +1 -1
  30. package/dist/components/multi-tree-select/style2.css +7 -0
  31. package/dist/components/select/select.js +7 -3
  32. package/dist/components/select/select.js.map +1 -1
  33. package/dist/components/select/style2.css +7 -0
  34. package/dist/components/select/use-options.js +14 -3
  35. package/dist/components/select/use-options.js.map +1 -1
  36. package/dist/components/tree-select/style2.css +7 -0
  37. package/dist/components/tree-select/tree-select.js +49 -17
  38. package/dist/components/tree-select/tree-select.js.map +1 -1
  39. package/dist/index.d.ts +1 -1
  40. package/dist/index.js +2 -2
  41. package/dist/types/dropdown.d.ts +10 -0
  42. package/dist/types/multi-tree-select.d.ts +7 -1
  43. package/dist/types/tree-select.d.ts +7 -1
  44. package/package.json +1 -1
  45. package/src/components/batch-edit/__test__/batch-edit.test.ts +112 -1
  46. package/src/components/batch-edit/batch-edit-form.vue +21 -2
  47. package/src/components/batch-edit/use-handlers.ts +6 -5
  48. package/src/components/batch-edit/use-shortcut-key.ts +5 -2
  49. package/src/components/card/style.scss +0 -2
  50. package/src/components/dropdown/dropdown.vue +9 -4
  51. package/src/components/expression-editor/components/path-preview.vue +1 -2
  52. package/src/components/expression-editor/components/variable-picker.vue +54 -13
  53. package/src/components/expression-editor/core/editor.ts +28 -0
  54. package/src/components/expression-editor/expression-editor.vue +47 -17
  55. package/src/components/expression-editor/style.scss +167 -82
  56. package/src/components/form/form.vue +0 -1
  57. package/src/components/form/style.scss +11 -4
  58. package/src/components/multi-select/multi-select.vue +11 -2
  59. package/src/components/multi-select/style.scss +6 -0
  60. package/src/components/multi-tree-select/multi-tree-select.vue +58 -9
  61. package/src/components/multi-tree-select/style.scss +6 -0
  62. package/src/components/select/__test__/select.test.ts +78 -0
  63. package/src/components/select/select.vue +13 -3
  64. package/src/components/select/style.scss +6 -0
  65. package/src/components/select/use-options.ts +25 -4
  66. package/src/components/tree-select/__test__/tree-select.test.ts +116 -0
  67. package/src/components/tree-select/style.scss +6 -0
  68. package/src/components/tree-select/tree-select.vue +63 -13
  69. package/src/types/dropdown.ts +10 -0
  70. package/src/types/multi-tree-select.ts +12 -1
  71. package/src/types/tree-select.ts +12 -1
@@ -9,7 +9,7 @@
9
9
  @update:visible="handleVisibleChange($event)"
10
10
  >
11
11
  <template #content>
12
- <div :class="cls.e('picker')" @mousedown.prevent>
12
+ <div ref="root" :class="cls.e('picker')" @mousedown.prevent>
13
13
  <!-- 面包屑:仅逐级模式且非根级显示 -->
14
14
  <div v-if="!isFlat && navPath.length > 0" :class="cls.e('breadcrumbs')">
15
15
  <span :class="cls.e('breadcrumb-item')">
@@ -39,7 +39,12 @@
39
39
 
40
40
  <div :class="cls.e('picker-body')">
41
41
  <div :class="cls.e('panel')" v-if="currentList.length > 0">
42
- <u-scroll tag="ul" :class="cls.e('panel-list')" :content-class="cls.e('panel-content')">
42
+ <u-scroll
43
+ ref="listScroll"
44
+ tag="ul"
45
+ :class="cls.e('panel-list')"
46
+ :content-class="cls.e('panel-content')"
47
+ >
43
48
  <li
44
49
  v-for="(item, idx) in currentList"
45
50
  :key="`${idx}-${item.value}`"
@@ -49,7 +54,12 @@
49
54
  @mouseenter="activeIndex = idx"
50
55
  >
51
56
  <span :class="cls.e('item-label')">{{ item.label }}</span>
52
- <u-icon v-if="!isFlat && hasChildren(item)" :class="cls.e('item-expand')">
57
+ <u-icon
58
+ v-if="!isFlat && hasChildren(item)"
59
+ :class="cls.e('item-expand')"
60
+ title="展开下一级"
61
+ @click.stop="expandInto(item)"
62
+ >
53
63
  <ArrowRight />
54
64
  </u-icon>
55
65
  </li>
@@ -61,6 +71,21 @@
61
71
 
62
72
  <PathPreview v-if="isFlat && activePath.length > 0" :path="activePath" />
63
73
  </div>
74
+
75
+ <div v-if="currentList.length > 0" :class="cls.e('picker-hint')">
76
+ <template v-if="isFlat">
77
+ <span><kbd>↑</kbd><kbd>↓</kbd> 选择</span>
78
+ <span><kbd>Enter</kbd> 确认</span>
79
+ <span><kbd>Esc</kbd> 关闭</span>
80
+ </template>
81
+ <template v-else>
82
+ <span><kbd>↑</kbd><kbd>↓</kbd> 选择</span>
83
+ <span><kbd>→</kbd> 展开</span>
84
+ <span><kbd>←</kbd> 返回</span>
85
+ <span><kbd>Enter</kbd> 确认</span>
86
+ <span><kbd>Esc</kbd> 关闭</span>
87
+ </template>
88
+ </div>
64
89
  </div>
65
90
  </template>
66
91
  </u-tip>
@@ -69,7 +94,7 @@
69
94
  <script lang="ts" setup>
70
95
  import { ArrowRight } from '@veltra/icons/normal'
71
96
  import { bem } from '@veltra/utils'
72
- import { computed, nextTick, ref, watch } from 'vue'
97
+ import { computed, nextTick, ref, useTemplateRef, watch } from 'vue'
73
98
 
74
99
  import type { VariableItem } from '../../../types'
75
100
  import { UEmpty } from '../../empty'
@@ -107,6 +132,8 @@ const cls = bem('expression-editor')
107
132
 
108
133
  const navPath = ref<VariableItem[]>([])
109
134
  const activeIndex = ref(0)
135
+ const root = useTemplateRef('root')
136
+ const listScroll = useTemplateRef<InstanceType<typeof UScroll>>('listScroll')
110
137
 
111
138
  const isFlat = computed(() => props.filter !== '')
112
139
 
@@ -192,13 +219,19 @@ function selectActive() {
192
219
  return
193
220
  }
194
221
  // 默认 'leaf':进入下一级
195
- navPath.value = [...navPath.value, item]
196
- activeIndex.value = 0
222
+ expandInto(item)
197
223
  return
198
224
  }
199
225
  emit('select', item)
200
226
  }
201
227
 
228
+ /** 进入 item 的下一级(点击展开图标 / leaf 模式点击分支项共用) */
229
+ function expandInto(item: VariableItem) {
230
+ if (!hasChildren(item)) return
231
+ navPath.value = [...navPath.value, item]
232
+ activeIndex.value = 0
233
+ }
234
+
202
235
  function flattenSourceFor(it: VariableItem): VariableItem {
203
236
  const flat = it as FlatItem
204
237
  // 还原为「不带 __path 的纯 VariableItem」,避免泄漏内部字段
@@ -220,11 +253,20 @@ function moveActive(delta: -1 | 1) {
220
253
 
221
254
  function scrollActiveIntoView() {
222
255
  void nextTick(() => {
223
- const list = document.querySelector(`.${cls.e('panel-list')}`)
224
- if (!list) return
225
- const item = list.querySelector(`[data-idx="${activeIndex.value}"]`)
226
- if (item && 'scrollIntoView' in item) {
227
- ;(item as HTMLElement).scrollIntoView({ block: 'nearest' })
256
+ // 在面板根节点内查找,避免同页多个编辑器时滚错列表
257
+ const item = root.value?.querySelector(`[data-idx="${activeIndex.value}"]`)
258
+ // expose 代理运行时会解包 ref,类型上按 unknown 收窄
259
+ const scroller = listScroll.value?.containerRef as unknown
260
+ if (!(item instanceof HTMLElement) || !(scroller instanceof HTMLElement)) return
261
+ // 只滚面板列表自身:原生 scrollIntoView 会沿所有滚动祖先链滚动,
262
+ // 面板贴近视口边缘时会连带滚动页面,导致面板与触发器错位抖动
263
+ const top =
264
+ item.getBoundingClientRect().top - scroller.getBoundingClientRect().top + scroller.scrollTop
265
+ const bottom = top + item.offsetHeight
266
+ if (top < scroller.scrollTop) {
267
+ scroller.scrollTop = top
268
+ } else if (bottom > scroller.scrollTop + scroller.clientHeight) {
269
+ scroller.scrollTop = bottom - scroller.clientHeight
228
270
  }
229
271
  })
230
272
  }
@@ -287,8 +329,7 @@ function handleKeydown(e: KeyboardEvent): boolean {
287
329
  if (isFlat.value) return false
288
330
  const item = currentList.value[activeIndex.value]
289
331
  if (item && hasChildren(item)) {
290
- navPath.value = [...navPath.value, item]
291
- activeIndex.value = 0
332
+ expandInto(item)
292
333
  return true
293
334
  }
294
335
  return false
@@ -43,6 +43,11 @@ export interface EditorAPI {
43
43
  getCaretOffset(): number | null
44
44
  /** 把光标设置到指定偏移(不会修改模型) */
45
45
  setCaretOffset(offset: number): void
46
+ /**
47
+ * 取序列化偏移处字符的视口矩形(mention 锚点定位用)。
48
+ * 偏移落在 text 段上返回该字符矩形;落在 chip 上返回 chip 元素矩形;越界返回 null。
49
+ */
50
+ getRectAtOffset(offset: number): DOMRect | null
46
51
  /**
47
52
  * 用一个 var chip 替换 [start, end) 区间。
48
53
  * 调用方通常在 mention 选中时用:start = `@` 起点、end = 当前光标。
@@ -321,6 +326,28 @@ export function createEditor(opts: EditorOptions): EditorAPI {
321
326
  return null
322
327
  }
323
328
 
329
+ function getRectAtOffset(offset: number): DOMRect | null {
330
+ let pos = 0
331
+ for (const child of Array.from(opts.container.childNodes)) {
332
+ const len = childSerialLength(child)
333
+ if (offset >= pos && offset < pos + len) {
334
+ if (isChipElement(child)) return (child as HTMLElement).getBoundingClientRect()
335
+ const textNode = firstTextDescendant(child)
336
+ if (!textNode) return (child as HTMLElement).getBoundingClientRect()
337
+ const local = Math.min(offset - pos, textNode.data.length)
338
+ const range = document.createRange()
339
+ range.setStart(textNode, local)
340
+ range.setEnd(textNode, Math.min(local + 1, textNode.data.length))
341
+ const rects = range.getClientRects()
342
+ return rects.length > 0
343
+ ? (rects[0] as DOMRect)
344
+ : (child as HTMLElement).getBoundingClientRect()
345
+ }
346
+ pos += len
347
+ }
348
+ return null
349
+ }
350
+
324
351
  /** input 后:从 DOM 反推 doc,并触发 onChange(如有变化)。 */
325
352
  function syncFromDOM(): void {
326
353
  if (composing) return
@@ -478,6 +505,7 @@ export function createEditor(opts: EditorOptions): EditorAPI {
478
505
  },
479
506
  getCaretOffset,
480
507
  setCaretOffset: setCaret,
508
+ getRectAtOffset,
481
509
  replaceRangeWithVar,
482
510
  replaceVarAt,
483
511
  rerender: () => render(),
@@ -1,17 +1,27 @@
1
1
  <template>
2
- <div :class="className">
3
- <div
4
- ref="container"
5
- :class="cls.e('container')"
6
- :contenteditable="!readonly && !disabled"
7
- :data-empty="isEmpty || undefined"
8
- :data-placeholder="props.placeholder"
9
- @keydown="onKeydown"
10
- @blur="onBlur"
11
- ></div>
12
-
13
- <div v-if="showPlaceholder" :class="cls.e('placeholder')">
14
- {{ props.placeholder }}
2
+ <div ref="rootEl" :class="cls.b">
3
+ <!-- 提示行独立在边框盒(shell)之外 -->
4
+ <div v-if="showHint" :class="cls.e('hint')">输入 <kbd>@</kbd> 唤起变量面板</div>
5
+
6
+ <div :class="shellClass">
7
+ <div :class="cls.e('body')">
8
+ <div
9
+ ref="container"
10
+ :class="cls.e('container')"
11
+ :contenteditable="!readonly && !disabled"
12
+ :data-empty="isEmpty || undefined"
13
+ :data-placeholder="props.placeholder"
14
+ @keydown="onKeydown"
15
+ @blur="onBlur"
16
+ ></div>
17
+
18
+ <div v-if="showPlaceholder" :class="cls.e('placeholder')">
19
+ {{ props.placeholder }}
20
+ </div>
21
+ </div>
22
+
23
+ <!-- mention 面板的定位锚点:跟随 `@` 字符位置(见 positionMentionAnchor) -->
24
+ <span ref="mentionAnchor" :class="cls.e('mention-anchor')" aria-hidden="true"></span>
15
25
  </div>
16
26
 
17
27
  <VariablePicker
@@ -63,11 +73,14 @@ const { formProps } = injectFormContext()
63
73
  const { size, disabled, readonly } = useFormFallbackProps([formProps ?? {}, props])
64
74
 
65
75
  const containerRef = useTemplateRef<HTMLDivElement>('container')
76
+ const rootElRef = useTemplateRef<HTMLDivElement>('rootEl')
77
+ const mentionAnchorRef = useTemplateRef<HTMLSpanElement>('mentionAnchor')
66
78
  const pickerRef = useTemplateRef<{ handleKeydown: (e: KeyboardEvent) => boolean }>('pickerRef')
67
79
 
68
- const className = computed(() => [
69
- cls.b,
70
- cls.m(size.value),
80
+ /** 视觉盒(边框 / 背景 / 聚焦光晕 / 尺寸态)挂在 shell 上,根节点只做布局 */
81
+ const shellClass = computed(() => [
82
+ cls.e('shell'),
83
+ cls.em('shell', size.value),
71
84
  bem.is('disabled', disabled.value),
72
85
  bem.is('readonly', readonly.value)
73
86
  ])
@@ -80,6 +93,7 @@ const mention: MentionAPI = createMention()
80
93
  const currentDoc = shallowRef<Doc>([])
81
94
  const isEmpty = computed(() => currentDoc.value.length === 0)
82
95
  const showPlaceholder = computed(() => isEmpty.value && !disabled.value)
96
+ const showHint = computed(() => isEmpty.value && !disabled.value && !readonly.value)
83
97
 
84
98
  const pickerMode = shallowRef<'mention' | 'reselect' | null>(null)
85
99
  const pickerTriggerDom = shallowRef<HTMLElement | undefined>(undefined)
@@ -88,6 +102,18 @@ const pickerFilter = shallowRef('')
88
102
 
89
103
  const pickerVisible = computed(() => pickerMode.value !== null)
90
104
 
105
+ /** 把 mention 锚点移到 `@` 字符下方,面板据此定位 */
106
+ function positionMentionAnchor(anchorOffset: number) {
107
+ const rootEl = rootElRef.value
108
+ const anchor = mentionAnchorRef.value
109
+ const rect = editorRef.value?.getRectAtOffset(anchorOffset)
110
+ if (!rootEl || !anchor || !rect) return
111
+ // absolute 定位以 padding box 为基准,需扣除根节点边框宽度
112
+ const rootRect = rootEl.getBoundingClientRect()
113
+ anchor.style.left = `${rect.left - rootRect.left - rootEl.clientLeft}px`
114
+ anchor.style.top = `${rect.bottom - rootRect.top - rootEl.clientTop}px`
115
+ }
116
+
91
117
  function syncMention() {
92
118
  const editor = editorRef.value
93
119
  if (!editor) return
@@ -97,7 +123,8 @@ function syncMention() {
97
123
  if (state) {
98
124
  pickerMode.value = 'mention'
99
125
  pickerFilter.value = state.filter
100
- pickerTriggerDom.value = containerRef.value ?? undefined
126
+ positionMentionAnchor(state.anchorOffset)
127
+ pickerTriggerDom.value = mentionAnchorRef.value ?? containerRef.value ?? undefined
101
128
  } else if (pickerMode.value === 'mention') {
102
129
  closePicker()
103
130
  }
@@ -135,6 +162,9 @@ onMounted(() => {
135
162
  pickerFilter.value = ''
136
163
  pickerTriggerDom.value = chipEl
137
164
  reselectingSegIdx.value = segIndex
165
+ // chip 的 mousedown 被 preventDefault,编辑器没有焦点时点击 chip 焦点不会转移,
166
+ // 键盘导航依赖 container 的 keydown,这里主动把焦点拉回编辑器
167
+ containerRef.value?.focus()
138
168
  }
139
169
  })
140
170
  editorRef.value = editor
@@ -5,48 +5,71 @@
5
5
  $root-name: expression-editor;
6
6
 
7
7
  @include m.b($root-name) {
8
+ // 根节点只做布局(提示行在视觉盒之外),视觉盒由 __shell 承担
8
9
  position: relative;
9
- background-color: fn.use-var(bg-color, top);
10
- border-width: fn.use-var(border, width);
11
- border-style: fn.use-var(border, style);
12
- border-color: fn.use-var(border, muted-color);
13
- border-radius: fn.use-var(radius, default);
14
10
  width: 100%;
15
11
 
16
- @include m.is-not(disabled, readonly) {
17
- &:hover {
18
- border-color: fn.use-var(color, primary);
19
- }
12
+ @include m.e(shell) {
13
+ background-color: fn.use-var(bg-color, top);
14
+ border-width: fn.use-var(border, width);
15
+ border-style: fn.use-var(border, style);
16
+ border-color: fn.use-var(border, muted-color);
17
+ border-radius: fn.use-var(radius, default);
18
+ transition:
19
+ border-color fn.use-var(transition, fast) fn.use-var(transition, ease),
20
+ box-shadow fn.use-var(transition, fast) fn.use-var(transition, ease);
21
+
22
+ @include m.is-not(disabled, readonly) {
23
+ &:hover {
24
+ border-color: fn.use-var(color, primary);
25
+ }
20
26
 
21
- &:focus-within {
22
- border-color: fn.use-var(color, primary);
23
- outline: none;
27
+ &:focus-within {
28
+ border-color: fn.use-var(color, primary);
29
+ box-shadow: fn.use-var(focus-ring);
30
+ outline: none;
31
+ }
24
32
  }
25
- }
26
33
 
27
- @include m.is('disabled') {
28
- background-color: fn.use-var(color, disabled);
29
- cursor: not-allowed;
30
- border-color: fn.use-var(border, muted-color);
34
+ @include m.is('disabled') {
35
+ background-color: fn.use-var(color, disabled);
36
+ cursor: not-allowed;
37
+ border-color: fn.use-var(border, muted-color);
31
38
 
32
- &:hover {
39
+ &:hover {
40
+ border-color: fn.use-var(border, muted-color);
41
+ }
42
+ }
43
+
44
+ @include m.is('readonly') {
45
+ background-color: fn.use-var(bg-color, middle);
46
+ cursor: default;
33
47
  border-color: fn.use-var(border, muted-color);
48
+ user-select: text;
49
+
50
+ &:hover {
51
+ border-color: fn.use-var(border, muted-color);
52
+ }
34
53
  }
35
- }
36
54
 
37
- @include m.is('readonly') {
38
- background-color: fn.use-var(bg-color, middle);
39
- cursor: default;
40
- border-color: fn.use-var(border, muted-color);
41
- user-select: text;
55
+ @include m.size using ($size) {
56
+ min-height: fn.use-var(form-component-height, $size);
57
+ padding: fn.use-var(gap, $size);
42
58
 
43
- &:hover {
44
- border-color: fn.use-var(border, muted-color);
59
+ @include m.bem($root-name, placeholder) {
60
+ font-size: fn.use-var(font-size-main, $size);
61
+ }
62
+
63
+ @include m.bem($root-name, container) {
64
+ font-size: fn.use-var(font-size-main, $size);
65
+ }
45
66
  }
46
67
  }
47
68
 
48
69
  @include m.e(placeholder) {
49
70
  position: absolute;
71
+ top: 0;
72
+ left: 0;
50
73
  color: fn.use-var(text-color, placeholder);
51
74
  pointer-events: none;
52
75
  user-select: none;
@@ -54,6 +77,43 @@ $root-name: expression-editor;
54
77
  font-weight: 400;
55
78
  }
56
79
 
80
+ // 提示 + 输入区的定位层:placeholder 相对它对齐,hint 出现时不至于错位
81
+ @include m.e(body) {
82
+ position: relative;
83
+ }
84
+
85
+ // 编辑器上方的 `@` 使用提示(空内容时显示),位于边框盒之外
86
+ @include m.e(hint) {
87
+ font-size: fn.use-var(font-size-main, small);
88
+ color: fn.use-var(text-color, placeholder);
89
+ user-select: none;
90
+ margin-bottom: 4px;
91
+ }
92
+
93
+ // mention 面板定位锚点:跟随 `@` 字符,零尺寸不参与绘制
94
+ @include m.e(mention-anchor) {
95
+ position: absolute;
96
+ width: 0;
97
+ height: 0;
98
+ pointer-events: none;
99
+ }
100
+
101
+ // 键位提示(编辑器 hint 与面板 footer 共用)
102
+ @include m.e(hint, picker-hint) {
103
+ kbd {
104
+ display: inline-block;
105
+ min-width: 18px;
106
+ padding: 1px 4px;
107
+ border: fn.use-var(border);
108
+ border-radius: fn.use-var(radius, small);
109
+ font-size: 11px;
110
+ line-height: 1.4;
111
+ text-align: center;
112
+ color: fn.use-var(text-color, second);
113
+ background-color: fn.use-var(bg-color, top);
114
+ }
115
+ }
116
+
57
117
  @include m.e(container) {
58
118
  position: relative;
59
119
  outline: none;
@@ -117,70 +177,92 @@ $root-name: expression-editor;
117
177
  }
118
178
 
119
179
  // ─── 变量选择面板 ──────────────────────────────────────────
180
+ // 面板以 tip 为浮层载体,但按库内标准下拉面板形态渲染:
181
+ // 负 margin 抵消 tip 内容自带的 gap 内边距,由 picker 自绘边框卡片
120
182
  @include m.e(picker) {
121
- min-width: 220px;
183
+ margin: calc(#{fn.use-var(gap, default)} * -1);
184
+ min-width: 240px;
185
+ display: flex;
186
+ flex-direction: column;
187
+ overflow: hidden;
122
188
  background-color: fn.use-var(bg-color, top);
189
+ border: fn.use-var(border);
190
+ border-radius: fn.use-var(radius, default);
191
+ backdrop-filter: fn.use-var(bg-filter);
192
+ -webkit-backdrop-filter: fn.use-var(bg-filter);
123
193
  }
124
194
 
125
195
  @include m.e(picker-body) {
196
+ flex: 1 1 auto;
197
+ min-height: 0;
126
198
  display: flex;
127
199
  align-items: stretch;
128
- min-height: 200px;
129
200
  }
130
201
 
131
202
  @include m.e(panel) {
132
203
  flex: 1 1 auto;
133
204
  min-width: 0;
134
205
  max-height: 320px;
206
+ display: flex;
207
+ flex-direction: column;
135
208
  }
136
209
 
137
210
  @include m.e(panel-list) {
138
- height: 100%;
139
- min-height: 200px;
211
+ flex: 1 1 auto;
212
+ min-height: 0;
140
213
  }
141
214
 
142
215
  @include m.e(panel-content) {
143
- padding: 4px 0;
216
+ padding: 4px;
144
217
  }
145
218
 
146
219
  @include m.e(item) {
147
- padding: 6px 12px;
148
- cursor: pointer;
149
- user-select: none;
150
220
  display: flex;
151
221
  align-items: center;
152
222
  justify-content: space-between;
153
- gap: 8px;
154
- transition:
155
- background-color 0.12s ease,
156
- color 0.12s ease;
157
- color: fn.use-var(text-color, main);
158
- font-size: 13px;
159
- line-height: 1.4;
223
+ gap: fn.use-var(gap, default);
224
+ height: fn.use-var(form-component-height, default);
225
+ padding: 0 fn.use-var(gap, default);
160
226
  border-radius: fn.use-var(radius, small);
161
- margin: 1px 4px;
227
+ font-size: fn.use-var(font-size-main, default);
228
+ color: fn.use-var(text-color, main);
229
+ cursor: pointer;
230
+ user-select: none;
231
+ transition:
232
+ background-color 0.15s ease,
233
+ color 0.15s ease;
162
234
 
163
235
  @include m.is(active) {
164
- background-color: fn.use-var(color, primary);
165
- color: fn.use-var(text-color, white);
166
-
167
- @include m.bem($root-name, item-expand) {
168
- color: fn.use-var(text-color, white);
169
- }
236
+ background-color: fn.use-var(bg-color, hover);
237
+ color: fn.use-var(text-color, title);
170
238
  }
171
239
  }
172
240
 
173
241
  @include m.e(item-label) {
174
242
  flex: 1;
175
- overflow: hidden;
176
- text-overflow: ellipsis;
177
- white-space: nowrap;
243
+ min-width: 0;
244
+ @include m.ellipsis;
178
245
  }
179
246
 
180
247
  @include m.e(item-expand) {
181
248
  flex-shrink: 0;
182
249
  color: fn.use-var(text-color, placeholder);
183
250
  font-size: 12px;
251
+ // 扩大点击热区:u-icon 是固定 1em 盒(全局 border-box),padding 会把 svg 挤成 0 宽,
252
+ // 这里改回 content-box 让字形保持原尺寸,再用负 margin 抵消布局占位
253
+ box-sizing: content-box;
254
+ padding: 6px;
255
+ margin: -6px;
256
+ border-radius: fn.use-var(radius, small);
257
+ cursor: pointer;
258
+ transition:
259
+ color 0.15s ease,
260
+ background-color 0.15s ease;
261
+
262
+ &:hover {
263
+ color: fn.use-var(color, primary);
264
+ background-color: fn.use-var(bg-color, hover);
265
+ }
184
266
  }
185
267
 
186
268
  @include m.e(empty) {
@@ -189,30 +271,51 @@ $root-name: expression-editor;
189
271
  text-align: center;
190
272
  }
191
273
 
274
+ // 面板底部键盘操作提示
275
+ @include m.e(picker-hint) {
276
+ display: flex;
277
+ align-items: center;
278
+ flex-wrap: wrap;
279
+ gap: 4px 12px;
280
+ padding: 6px 10px;
281
+ border-top: fn.use-var(border);
282
+ font-size: fn.use-var(font-size-main, small);
283
+ color: fn.use-var(text-color, placeholder);
284
+ user-select: none;
285
+
286
+ span {
287
+ display: inline-flex;
288
+ align-items: center;
289
+ gap: 3px;
290
+ }
291
+ }
292
+
192
293
  // ─── 面包屑 ───────────────────────────────────────────────
193
294
  @include m.e(breadcrumbs) {
194
295
  display: flex;
195
296
  align-items: center;
196
297
  flex-wrap: wrap;
197
- padding: 6px 10px;
298
+ gap: 2px;
299
+ padding: 6px 8px;
300
+ background-color: fn.use-var(bg-color, middle);
198
301
  border-bottom: fn.use-var(border);
199
- font-size: 12px;
302
+ font-size: fn.use-var(font-size-main, small);
200
303
  color: fn.use-var(text-color, second);
201
304
  }
202
305
 
203
306
  @include m.e(breadcrumb-item) {
204
307
  display: inline-flex;
205
308
  align-items: center;
206
- gap: 4px;
309
+ gap: 2px;
207
310
  }
208
311
 
209
312
  @include m.e(breadcrumb-link) {
313
+ padding: 3px 6px;
314
+ border-radius: fn.use-var(radius, small);
210
315
  cursor: pointer;
211
316
  transition:
212
- color 0.12s ease,
213
- background-color 0.12s ease;
214
- padding: 2px 6px;
215
- border-radius: fn.use-var(radius, small);
317
+ color 0.15s ease,
318
+ background-color 0.15s ease;
216
319
 
217
320
  &:hover {
218
321
  color: fn.use-var(color, primary);
@@ -226,7 +329,7 @@ $root-name: expression-editor;
226
329
  }
227
330
 
228
331
  @include m.e(breadcrumb-separator) {
229
- font-size: 10px;
332
+ font-size: 12px;
230
333
  color: fn.use-var(text-color, placeholder);
231
334
  }
232
335
 
@@ -236,7 +339,7 @@ $root-name: expression-editor;
236
339
  border-left: fn.use-var(border);
237
340
  padding: 8px 12px;
238
341
  background-color: fn.use-var(bg-color, middle);
239
- font-size: 12px;
342
+ font-size: fn.use-var(font-size-main, small);
240
343
  }
241
344
 
242
345
  @include m.e(path-preview-title) {
@@ -257,6 +360,8 @@ $root-name: expression-editor;
257
360
  gap: 4px;
258
361
  line-height: 1.6;
259
362
  color: fn.use-var(text-color, main);
363
+ // 层级缩进由模板传入的 --depth 驱动
364
+ padding-left: calc(var(--depth, 0) * 12px);
260
365
 
261
366
  @include m.is(active) {
262
367
  color: fn.use-var(color, primary);
@@ -264,28 +369,8 @@ $root-name: expression-editor;
264
369
  }
265
370
  }
266
371
 
267
- @include m.e(path-tree-branch) {
268
- color: fn.use-var(text-color, placeholder);
269
- }
270
-
271
372
  @include m.e(path-tree-label) {
272
- overflow: hidden;
273
- text-overflow: ellipsis;
274
- white-space: nowrap;
275
- }
276
-
277
- @include m.size using ($size) {
278
- min-height: fn.use-var(form-component-height, $size);
279
- padding: fn.use-var(gap, $size);
280
-
281
- @include m.bem($root-name, placeholder) {
282
- top: fn.use-var(gap, $size);
283
- left: fn.use-var(gap, $size);
284
- font-size: fn.use-var(font-size-main, $size);
285
- }
286
-
287
- @include m.bem($root-name, container) {
288
- font-size: fn.use-var(font-size-main, $size);
289
- }
373
+ min-width: 0;
374
+ @include m.ellipsis;
290
375
  }
291
376
  }
@@ -3,7 +3,6 @@
3
3
  tag="form"
4
4
  ref="grid"
5
5
  @submit.prevent
6
- gap="0 12px"
7
6
  :cols="cols || breakpointCols"
8
7
  :class="[cls.b, bem.is('readonly', readonly)]"
9
8
  >