@veltra/desktop 1.0.13 → 1.0.15

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 (166) hide show
  1. package/dist/components/action/action-group.js +110 -3
  2. package/dist/components/action/action-group.js.map +1 -1
  3. package/dist/components/action/action-group.vue.d.ts +16 -3
  4. package/dist/components/action/action.js +26 -13
  5. package/dist/components/action/action.js.map +1 -1
  6. package/dist/components/action/style.js +2 -1
  7. package/dist/components/action/style2.css +35 -7
  8. package/dist/components/batch-edit/batch-edit-form.js +106 -42
  9. package/dist/components/batch-edit/batch-edit-form.js.map +1 -1
  10. package/dist/components/batch-edit/batch-edit-list.js +84 -47
  11. package/dist/components/batch-edit/batch-edit-list.js.map +1 -1
  12. package/dist/components/batch-edit/batch-edit.js +42 -10
  13. package/dist/components/batch-edit/batch-edit.js.map +1 -1
  14. package/dist/components/batch-edit/style.js +2 -4
  15. package/dist/components/batch-edit/style2.css +202 -30
  16. package/dist/components/batch-edit/use-edit.js +45 -9
  17. package/dist/components/batch-edit/use-edit.js.map +1 -1
  18. package/dist/components/cascade/cascade.js +3 -1
  19. package/dist/components/cascade/cascade.js.map +1 -1
  20. package/dist/components/code-editor/code-editor.js +145 -49
  21. package/dist/components/code-editor/code-editor.js.map +1 -1
  22. package/dist/components/code-editor/code-editor.vue.d.ts +2 -0
  23. package/dist/components/code-editor/style2.css +87 -19
  24. package/dist/components/dialog/dialog.js +6 -2
  25. package/dist/components/dialog/dialog.js.map +1 -1
  26. package/dist/components/dialog/style2.css +2 -3
  27. package/dist/components/expression-editor/components/path-preview.js +27 -0
  28. package/dist/components/expression-editor/components/path-preview.js.map +1 -0
  29. package/dist/components/expression-editor/components/variable-picker.js +200 -153
  30. package/dist/components/expression-editor/components/variable-picker.js.map +1 -1
  31. package/dist/components/expression-editor/core/chip.js +54 -0
  32. package/dist/components/expression-editor/core/chip.js.map +1 -0
  33. package/dist/components/expression-editor/core/editor.js +425 -0
  34. package/dist/components/expression-editor/core/editor.js.map +1 -0
  35. package/dist/components/expression-editor/core/mention.js +91 -0
  36. package/dist/components/expression-editor/core/mention.js.map +1 -0
  37. package/dist/components/expression-editor/core/model.js +64 -0
  38. package/dist/components/expression-editor/core/model.js.map +1 -0
  39. package/dist/components/expression-editor/di.js +4 -10
  40. package/dist/components/expression-editor/di.js.map +1 -1
  41. package/dist/components/expression-editor/expression-editor.js +168 -114
  42. package/dist/components/expression-editor/expression-editor.js.map +1 -1
  43. package/dist/components/expression-editor/expression-editor.vue.d.ts +2 -1
  44. package/dist/components/expression-editor/style.js +0 -2
  45. package/dist/components/expression-editor/style2.css +145 -218
  46. package/dist/components/index.js +54 -54
  47. package/dist/components/layout/layout.js +12 -11
  48. package/dist/components/layout/layout.js.map +1 -1
  49. package/dist/components/multi-select/multi-select.vue.d.ts +1 -1
  50. package/dist/components/multi-tree-select/multi-tree-select.vue.d.ts +1 -1
  51. package/dist/components/number-input/number-input.js +4 -4
  52. package/dist/components/number-input/number-input.js.map +1 -1
  53. package/dist/components/pop-confirm/pop-confirm.vue.d.ts +1 -1
  54. package/dist/components/rich-text-editor/rich-text-editor.js +1 -1
  55. package/dist/components/rich-text-editor/toolbar.js +2 -2
  56. package/dist/components/steps/steps.vue.d.ts +1 -1
  57. package/dist/components/table/use-table.js +16 -0
  58. package/dist/components/table/use-table.js.map +1 -1
  59. package/dist/components/tabs/tabs-horizontal.vue.d.ts +3 -3
  60. package/dist/components/tabs/tabs-vertical.vue.d.ts +2 -2
  61. package/dist/components/tabs/tabs.vue.d.ts +3 -3
  62. package/dist/components/tip/tip.js +4 -5
  63. package/dist/components/tip/tip.js.map +1 -1
  64. package/dist/components/tip/tip.vue.d.ts +1 -1
  65. package/dist/components/tree/style2.css +18 -12
  66. package/dist/components/tree/tree-node.js +7 -4
  67. package/dist/components/tree/tree-node.js.map +1 -1
  68. package/dist/components/tree-select/tree-select.vue.d.ts +1 -1
  69. package/dist/index.d.ts +3 -3
  70. package/dist/index.js +83 -83
  71. package/dist/style.js +3 -3
  72. package/dist/types/action.d.ts +38 -4
  73. package/dist/types/cascade.d.ts +2 -3
  74. package/dist/types/code-editor.d.ts +9 -2
  75. package/dist/types/expression-editor.d.ts +9 -1
  76. package/dist/types/index.d.ts +2 -2
  77. package/package.json +11 -12
  78. package/src/components/action/action-group.vue +126 -56
  79. package/src/components/action/action.vue +35 -12
  80. package/src/components/action/style.scss +46 -10
  81. package/src/components/action/style.ts +2 -1
  82. package/src/components/batch-edit/batch-edit-form.vue +121 -45
  83. package/src/components/batch-edit/batch-edit-list.vue +56 -39
  84. package/src/components/batch-edit/batch-edit.vue +57 -6
  85. package/src/components/batch-edit/style.scss +251 -37
  86. package/src/components/batch-edit/style.ts +5 -8
  87. package/src/components/batch-edit/use-edit.ts +58 -11
  88. package/src/components/cascade/cascade.vue +3 -1
  89. package/src/components/code-editor/code-editor.vue +198 -63
  90. package/src/components/code-editor/style.scss +110 -12
  91. package/src/components/dialog/dialog.vue +6 -2
  92. package/src/components/dialog/style.scss +4 -4
  93. package/src/components/expression-editor/__test__/chip.test.ts +86 -0
  94. package/src/components/expression-editor/__test__/editor.test.ts +47 -0
  95. package/src/components/expression-editor/__test__/mention.test.ts +122 -0
  96. package/src/components/expression-editor/__test__/model.test.ts +127 -0
  97. package/src/components/expression-editor/components/path-preview.vue +32 -0
  98. package/src/components/expression-editor/components/variable-picker.vue +225 -219
  99. package/src/components/expression-editor/core/chip.ts +101 -0
  100. package/src/components/expression-editor/core/editor.ts +514 -0
  101. package/src/components/expression-editor/core/mention.ts +121 -0
  102. package/src/components/expression-editor/core/model.ts +109 -0
  103. package/src/components/expression-editor/di.ts +4 -32
  104. package/src/components/expression-editor/expression-editor.vue +184 -151
  105. package/src/components/expression-editor/style.scss +148 -219
  106. package/src/components/expression-editor/style.ts +0 -2
  107. package/src/components/layout/layout.vue +17 -19
  108. package/src/components/number-input/number-input.vue +4 -4
  109. package/src/components/table/use-table.ts +16 -0
  110. package/src/components/tip/tip.vue +4 -3
  111. package/src/components/tree/style.scss +30 -33
  112. package/src/components/tree/tree-node.vue +4 -2
  113. package/src/types/action.ts +42 -4
  114. package/src/types/cascade.ts +4 -3
  115. package/src/types/code-editor.ts +9 -2
  116. package/src/types/expression-editor.ts +9 -0
  117. package/dist/components/action/action-group.vue_vue_type_script_setup_true_lang.js +0 -65
  118. package/dist/components/action/action-group.vue_vue_type_script_setup_true_lang.js.map +0 -1
  119. package/dist/components/expression-editor/constants.js +0 -9
  120. package/dist/components/expression-editor/constants.js.map +0 -1
  121. package/dist/components/expression-editor/internal/editor-runtime/create-runtime.js +0 -47
  122. package/dist/components/expression-editor/internal/editor-runtime/create-runtime.js.map +0 -1
  123. package/dist/components/expression-editor/internal/editor-runtime/model-sync.js +0 -49
  124. package/dist/components/expression-editor/internal/editor-runtime/model-sync.js.map +0 -1
  125. package/dist/components/expression-editor/internal/features/commands/register-command-packs.js +0 -215
  126. package/dist/components/expression-editor/internal/features/commands/register-command-packs.js.map +0 -1
  127. package/dist/components/expression-editor/internal/features/drag-drop/drag-drop-service.js +0 -33
  128. package/dist/components/expression-editor/internal/features/drag-drop/drag-drop-service.js.map +0 -1
  129. package/dist/components/expression-editor/internal/features/insertion/insertion-service.js +0 -33
  130. package/dist/components/expression-editor/internal/features/insertion/insertion-service.js.map +0 -1
  131. package/dist/components/expression-editor/nodes/variable-node.js +0 -94
  132. package/dist/components/expression-editor/nodes/variable-node.js.map +0 -1
  133. package/dist/components/expression-editor/parser.js +0 -32
  134. package/dist/components/expression-editor/parser.js.map +0 -1
  135. package/dist/components/expression-editor/plain-text.js +0 -19
  136. package/dist/components/expression-editor/plain-text.js.map +0 -1
  137. package/dist/components/expression-editor/use-context.js +0 -69
  138. package/dist/components/expression-editor/use-context.js.map +0 -1
  139. package/dist/components/expression-editor/use-decorators.js +0 -27
  140. package/dist/components/expression-editor/use-decorators.js.map +0 -1
  141. package/dist/components/expression-editor/use-expression-drag-drop.js +0 -275
  142. package/dist/components/expression-editor/use-expression-drag-drop.js.map +0 -1
  143. package/src/components/batch-edit/batch-edit-new.vue +0 -168
  144. package/src/components/batch-edit/style-new.scss +0 -35
  145. package/src/components/batch-edit/use-edit-new.ts +0 -68
  146. package/src/components/batch-edit/use-tip.ts +0 -47
  147. package/src/components/expression-editor/__test__/architecture-boundaries.test.ts +0 -103
  148. package/src/components/expression-editor/__test__/architecture-compatibility.test.ts +0 -52
  149. package/src/components/expression-editor/__test__/drag-drop.test.ts +0 -171
  150. package/src/components/expression-editor/__test__/parser.test.ts +0 -62
  151. package/src/components/expression-editor/constants.ts +0 -6
  152. package/src/components/expression-editor/internal/contracts/editor-runtime.ts +0 -11
  153. package/src/components/expression-editor/internal/editor-runtime/create-runtime.ts +0 -64
  154. package/src/components/expression-editor/internal/editor-runtime/index.ts +0 -1
  155. package/src/components/expression-editor/internal/editor-runtime/model-sync.ts +0 -68
  156. package/src/components/expression-editor/internal/features/commands/register-command-packs.ts +0 -517
  157. package/src/components/expression-editor/internal/features/drag-drop/drag-drop-service.ts +0 -51
  158. package/src/components/expression-editor/internal/features/insertion/insertion-service.ts +0 -56
  159. package/src/components/expression-editor/nodes/variable-node.tsx +0 -118
  160. package/src/components/expression-editor/parser.ts +0 -46
  161. package/src/components/expression-editor/plain-text.ts +0 -25
  162. package/src/components/expression-editor/plan.md +0 -22
  163. package/src/components/expression-editor/use-context.ts +0 -134
  164. package/src/components/expression-editor/use-decorators.tsx +0 -29
  165. package/src/components/expression-editor/use-editor.ts +0 -12
  166. package/src/components/expression-editor/use-expression-drag-drop.ts +0 -396
@@ -1,194 +1,235 @@
1
1
  import _sfc_main$1 from "../../scroll/scroll.js";
2
2
  import _sfc_main$2 from "../../icon/icon.js";
3
3
  import _sfc_main$3 from "../../empty/empty.js";
4
- import input_default from "../../input/input.js";
5
4
  import _sfc_main$4 from "../../tip/tip.js";
6
- import { ExpressionEditorDIKey } from "../di.js";
7
- import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, inject, normalizeClass, onBeforeUnmount, onMounted, openBlock, ref, renderList, toDisplayString, unref, watch, withCtx } from "vue";
5
+ import _sfc_main$5 from "./path-preview.js";
6
+ import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, nextTick, normalizeClass, openBlock, ref, renderList, toDisplayString, unref, watch, withCtx, withModifiers } from "vue";
8
7
  import { bem } from "@veltra/utils";
9
- import { ArrowRight, Search } from "@veltra/icons/normal";
8
+ import { ArrowRight } from "@veltra/icons/normal";
10
9
  //#region src/components/expression-editor/components/variable-picker.vue
11
10
  const _hoisted_1 = ["onClick"];
12
- const _hoisted_2 = ["onClick", "onMouseenter"];
11
+ const _hoisted_2 = [
12
+ "data-idx",
13
+ "onClick",
14
+ "onMouseenter"
15
+ ];
13
16
  const _sfc_main = /* @__PURE__ */ defineComponent({
14
17
  name: "VariablePicker",
15
18
  __name: "variable-picker",
16
19
  props: {
17
20
  visible: { type: Boolean },
18
21
  triggerDom: {},
19
- filterable: { type: Boolean },
20
- registerPickerKeyHandler: { type: Function }
22
+ variables: {},
23
+ filter: {},
24
+ selectableLevels: {}
21
25
  },
22
- emits: ["select", "update:visible"],
23
- setup(__props, { emit: __emit }) {
26
+ emits: [
27
+ "select",
28
+ "update:visible",
29
+ "dismiss"
30
+ ],
31
+ setup(__props, { expose: __expose, emit: __emit }) {
24
32
  const props = __props;
25
33
  const emit = __emit;
26
- const { cls, editorProps, variableMap } = inject(ExpressionEditorDIKey);
27
- const searchQuery = ref("");
28
- const activeIndex = ref(-1);
29
- const navigationPath = ref([]);
30
- function flattenVariables(items, parentPath = []) {
31
- if (!items) return [];
32
- const result = [];
33
- for (const item of items) {
34
- const currentPath = [...parentPath, item];
35
- if (!item.children || item.children.length === 0) result.push({
36
- ...item,
37
- label: currentPath.map((p) => p.label).join(" / ")
38
- });
39
- else result.push(...flattenVariables(item.children, currentPath));
40
- }
41
- return result;
34
+ const cls = bem("expression-editor");
35
+ const navPath = ref([]);
36
+ const activeIndex = ref(0);
37
+ const isFlat = computed(() => props.filter !== "");
38
+ function hasChildren(item) {
39
+ return !!item.children && item.children.length > 0;
42
40
  }
43
- const filteredVariables = computed(() => {
44
- const { variables } = editorProps;
45
- if (!variables || variables.length === 0) return [];
46
- if (!searchQuery.value.trim()) return variables;
47
- const flat = flattenVariables(variables);
48
- const query = searchQuery.value.toLowerCase();
49
- return flat.filter((item) => item.label.toLowerCase().includes(query));
50
- });
41
+ function isLeaf(item) {
42
+ return !hasChildren(item);
43
+ }
44
+ /** 当前层级展示的列表(来源依模式而定) */
51
45
  const currentList = computed(() => {
52
- if (searchQuery.value.trim()) return filteredVariables.value;
53
- else {
54
- let current = filteredVariables.value;
55
- for (const pathItem of navigationPath.value) {
56
- const found = current.find((item) => item.value === pathItem.value);
57
- if (found?.children) current = found.children;
58
- else break;
46
+ const variables = props.variables ?? [];
47
+ if (isFlat.value) return flatList.value;
48
+ let level = variables;
49
+ for (const node of navPath.value) {
50
+ const found = level.find((it) => it.value === node.value);
51
+ if (!found || !found.children) break;
52
+ level = found.children;
53
+ }
54
+ return level;
55
+ });
56
+ /** 扁平模式下:根据 selectableLevels 拍平 + 过滤 */
57
+ const flatList = computed(() => {
58
+ const variables = props.variables ?? [];
59
+ const out = [];
60
+ const includeBranches = props.selectableLevels === "any";
61
+ function walk(items, path) {
62
+ for (const it of items) {
63
+ const nextPath = [...path, it];
64
+ if (isLeaf(it)) out.push({
65
+ ...it,
66
+ __path: nextPath
67
+ });
68
+ else {
69
+ if (includeBranches) out.push({
70
+ ...it,
71
+ __path: nextPath
72
+ });
73
+ walk(it.children, nextPath);
74
+ }
59
75
  }
60
- return current;
61
76
  }
77
+ walk(variables, []);
78
+ const q = props.filter.toLowerCase();
79
+ return out.filter((it) => it.label.toLowerCase().includes(q));
62
80
  });
63
- const breadcrumbs = computed(() => {
64
- return [{
65
- label: "全部变量",
66
- value: ""
67
- }, ...navigationPath.value];
81
+ /** 当前 active 项;扁平模式下用于驱动路径预览 */
82
+ const activePath = computed(() => {
83
+ if (!isFlat.value) return [];
84
+ const item = flatList.value[activeIndex.value];
85
+ return item ? item.__path : [];
68
86
  });
69
- function getItemCls(item, index) {
70
- const isActive = activeIndex.value === index;
71
- const isLeaf = !item.children || item.children.length === 0;
72
- return [
73
- cls.e("item"),
74
- bem.is("active", isActive),
75
- bem.is("leaf", isLeaf)
76
- ];
77
- }
78
- function handleItemClick(item) {
79
- if (item.children && item.children.length > 0) {
80
- navigationPath.value.push(item);
81
- activeIndex.value = -1;
82
- return;
83
- }
84
- const fullItem = variableMap.value.get(item.value);
85
- if (fullItem) emit("select", fullItem);
86
- else emit("select", item);
87
- updateVisible(false);
87
+ function getItemCls(_item, idx) {
88
+ return [cls.e("item"), bem.is("active", idx === activeIndex.value)];
88
89
  }
89
- function navigateToBreadcrumb(index) {
90
- if (index === 0) navigationPath.value = [];
91
- else navigationPath.value = navigationPath.value.slice(0, index);
92
- activeIndex.value = -1;
90
+ function navigateTo(i) {
91
+ navPath.value = navPath.value.slice(0, i);
92
+ activeIndex.value = 0;
93
93
  }
94
- function handleSearch() {
95
- navigationPath.value = [];
96
- activeIndex.value = -1;
94
+ function handleItemClick(item, idx) {
95
+ activeIndex.value = idx;
96
+ selectActive();
97
97
  }
98
- function updateVisible(visible) {
99
- emit("update:visible", visible);
100
- if (!visible) {
101
- navigationPath.value = [];
102
- searchQuery.value = "";
103
- activeIndex.value = -1;
104
- }
105
- }
106
- function handleKeydown(e) {
107
- if (!props.visible) return;
108
- if (e.key === "Escape") {
109
- e.preventDefault();
110
- updateVisible(false);
98
+ function selectActive() {
99
+ const item = currentList.value[activeIndex.value];
100
+ if (!item) return;
101
+ if (isFlat.value) {
102
+ emit("select", flattenSourceFor(item));
111
103
  return;
112
104
  }
113
- if (currentList.value.length === 0) return;
114
- if (activeIndex.value === -1 && currentList.value.length > 0) activeIndex.value = 0;
115
- const currentItem = currentList.value[activeIndex.value];
116
- if (e.key === "ArrowDown") {
117
- e.preventDefault();
118
- activeIndex.value = Math.min(currentList.value.length - 1, activeIndex.value + 1);
119
- } else if (e.key === "ArrowUp") {
120
- e.preventDefault();
121
- activeIndex.value = Math.max(0, activeIndex.value - 1);
122
- } else if (e.key === " " || e.key === "Spacebar") {
123
- e.preventDefault();
124
- if (currentItem?.children && currentItem.children.length > 0) handleItemClick(currentItem);
125
- } else if (e.key === "ArrowRight") {
126
- e.preventDefault();
127
- if (currentItem?.children && currentItem.children.length > 0) handleItemClick(currentItem);
128
- } else if (e.key === "ArrowLeft" || e.key === "Backspace") {
129
- e.preventDefault();
130
- if (navigationPath.value.length > 0) {
131
- navigationPath.value.pop();
132
- activeIndex.value = 0;
133
- }
134
- } else if (e.key === "Enter") {
135
- e.preventDefault();
136
- if (currentItem) {
137
- if (!currentItem.children || currentItem.children.length === 0) handleItemClick(currentItem);
105
+ if (hasChildren(item)) {
106
+ if (props.selectableLevels === "any") {
107
+ emit("select", item);
108
+ return;
138
109
  }
110
+ navPath.value = [...navPath.value, item];
111
+ activeIndex.value = 0;
112
+ return;
139
113
  }
114
+ emit("select", item);
115
+ }
116
+ function flattenSourceFor(it) {
117
+ const flat = it;
118
+ const out = {
119
+ value: flat.value,
120
+ label: flat.label
121
+ };
122
+ if (flat.type) out.type = flat.type;
123
+ if (flat.children) out.children = flat.children;
124
+ return out;
125
+ }
126
+ function moveActive(delta) {
127
+ const max = currentList.value.length - 1;
128
+ if (max < 0) return;
129
+ let next = activeIndex.value + delta;
130
+ if (next < 0) next = 0;
131
+ if (next > max) next = max;
132
+ activeIndex.value = next;
133
+ scrollActiveIntoView();
134
+ }
135
+ function scrollActiveIntoView() {
136
+ nextTick(() => {
137
+ const list = document.querySelector(`.${cls.e("panel-list")}`);
138
+ if (!list) return;
139
+ const item = list.querySelector(`[data-idx="${activeIndex.value}"]`);
140
+ if (item && "scrollIntoView" in item) item.scrollIntoView({ block: "nearest" });
141
+ });
142
+ }
143
+ function handleVisibleChange(v) {
144
+ emit("update:visible", v);
145
+ if (!v) reset();
146
+ }
147
+ function reset() {
148
+ navPath.value = [];
149
+ activeIndex.value = 0;
140
150
  }
141
151
  watch(() => props.visible, (v) => {
142
- if (v) document.addEventListener("keydown", handleKeydown);
143
- else document.removeEventListener("keydown", handleKeydown);
152
+ if (!v) reset();
153
+ else activeIndex.value = 0;
144
154
  });
145
- onMounted(() => {
146
- props.registerPickerKeyHandler?.(handleKeydown);
147
- });
148
- onBeforeUnmount(() => {
149
- document.removeEventListener("keydown", handleKeydown);
150
- props.registerPickerKeyHandler?.(null);
155
+ watch(() => props.filter, () => {
156
+ activeIndex.value = 0;
157
+ if (isFlat.value) navPath.value = [];
151
158
  });
159
+ /**
160
+ * 由父级 forward 进来的键盘事件。
161
+ * 返回 true 表示已处理(外部应 preventDefault)。
162
+ */
163
+ function handleKeydown(e) {
164
+ if (!props.visible) return false;
165
+ switch (e.key) {
166
+ case "Escape":
167
+ emit("dismiss");
168
+ emit("update:visible", false);
169
+ reset();
170
+ return true;
171
+ case "ArrowDown":
172
+ moveActive(1);
173
+ return true;
174
+ case "ArrowUp":
175
+ moveActive(-1);
176
+ return true;
177
+ case "ArrowLeft":
178
+ if (!isFlat.value && navPath.value.length > 0) {
179
+ navPath.value = navPath.value.slice(0, -1);
180
+ activeIndex.value = 0;
181
+ return true;
182
+ }
183
+ return false;
184
+ case "ArrowRight": {
185
+ if (isFlat.value) return false;
186
+ const item = currentList.value[activeIndex.value];
187
+ if (item && hasChildren(item)) {
188
+ navPath.value = [...navPath.value, item];
189
+ activeIndex.value = 0;
190
+ return true;
191
+ }
192
+ return false;
193
+ }
194
+ case "Enter":
195
+ selectActive();
196
+ return true;
197
+ default: return false;
198
+ }
199
+ }
200
+ __expose({ handleKeydown });
152
201
  return (_ctx, _cache) => {
153
202
  return openBlock(), createBlock(unref(_sfc_main$4), {
154
203
  "hide-arrow": "",
155
204
  alignment: "start",
156
205
  direction: "bottom",
206
+ trigger: "click",
157
207
  "trigger-dom": __props.triggerDom,
158
208
  visible: __props.visible,
159
- "onUpdate:visible": _cache[1] || (_cache[1] = ($event) => updateVisible($event))
209
+ "onUpdate:visible": _cache[2] || (_cache[2] = ($event) => handleVisibleChange($event))
160
210
  }, {
161
- content: withCtx(() => [
162
- createCommentVNode(" 搜索框 "),
163
- __props.filterable ? (openBlock(), createElementBlock("div", {
211
+ content: withCtx(() => [createElementVNode("div", {
212
+ class: normalizeClass(unref(cls).e("picker")),
213
+ onMousedown: _cache[1] || (_cache[1] = withModifiers(() => {}, ["prevent"]))
214
+ }, [
215
+ createCommentVNode(" 面包屑:仅逐级模式且非根级显示 "),
216
+ !isFlat.value && navPath.value.length > 0 ? (openBlock(), createElementBlock("div", {
164
217
  key: 0,
165
- class: normalizeClass(unref(cls).e("filter"))
166
- }, [createVNode(unref(input_default), {
167
- placeholder: "输入关键字进行过滤",
168
- modelValue: searchQuery.value,
169
- "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => searchQuery.value = $event),
170
- size: "small",
171
- clearable: "",
172
- onInput: handleSearch
173
- }, {
174
- suffix: withCtx(() => [createVNode(unref(_sfc_main$2), null, {
175
- default: withCtx(() => [createVNode(unref(Search))]),
176
- _: 1
177
- })]),
178
- _: 1
179
- }, 8, ["modelValue"])], 2)) : createCommentVNode("v-if", true),
180
- createCommentVNode(" 面包屑导航 "),
181
- breadcrumbs.value.length > 1 && !searchQuery.value ? (openBlock(), createElementBlock("div", {
182
- key: 1,
183
218
  class: normalizeClass(unref(cls).e("breadcrumbs"))
184
- }, [(openBlock(true), createElementBlock(Fragment, null, renderList(breadcrumbs.value, (crumb, index) => {
219
+ }, [createElementVNode("span", { class: normalizeClass(unref(cls).e("breadcrumb-item")) }, [createElementVNode("span", {
220
+ class: normalizeClass(unref(cls).e("breadcrumb-link")),
221
+ onClick: _cache[0] || (_cache[0] = ($event) => navigateTo(0))
222
+ }, "全部变量", 2), createVNode(unref(_sfc_main$2), { class: normalizeClass(unref(cls).e("breadcrumb-separator")) }, {
223
+ default: withCtx(() => [createVNode(unref(ArrowRight))]),
224
+ _: 1
225
+ }, 8, ["class"])], 2), (openBlock(true), createElementBlock(Fragment, null, renderList(navPath.value, (crumb, i) => {
185
226
  return openBlock(), createElementBlock("span", {
186
- key: index,
227
+ key: `${i}-${crumb.value}`,
187
228
  class: normalizeClass(unref(cls).e("breadcrumb-item"))
188
229
  }, [createElementVNode("span", {
189
- class: normalizeClass([unref(cls).e("breadcrumb-link"), { [unref(cls).e("breadcrumb-link--active")]: index === breadcrumbs.value.length - 1 }]),
190
- onClick: ($event) => navigateToBreadcrumb(index)
191
- }, toDisplayString(crumb.label), 11, _hoisted_1), index < breadcrumbs.value.length - 1 ? (openBlock(), createBlock(unref(_sfc_main$2), {
230
+ class: normalizeClass([unref(cls).e("breadcrumb-link"), { [unref(cls).em("breadcrumb-link", "active")]: i === navPath.value.length - 1 }]),
231
+ onClick: ($event) => navigateTo(i + 1)
232
+ }, toDisplayString(crumb.label), 11, _hoisted_1), i < navPath.value.length - 1 ? (openBlock(), createBlock(unref(_sfc_main$2), {
192
233
  key: 0,
193
234
  class: normalizeClass(unref(cls).e("breadcrumb-separator"))
194
235
  }, {
@@ -196,9 +237,8 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
196
237
  _: 1
197
238
  }, 8, ["class"])) : createCommentVNode("v-if", true)], 2);
198
239
  }), 128))], 2)) : createCommentVNode("v-if", true),
199
- createCommentVNode(" 单面板 "),
200
- currentList.value.length ? (openBlock(), createElementBlock("div", {
201
- key: 2,
240
+ createElementVNode("div", { class: normalizeClass(unref(cls).e("picker-body")) }, [currentList.value.length > 0 ? (openBlock(), createElementBlock("div", {
241
+ key: 0,
202
242
  class: normalizeClass(unref(cls).e("panel"))
203
243
  }, [createVNode(unref(_sfc_main$1), {
204
244
  tag: "ul",
@@ -207,11 +247,12 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
207
247
  }, {
208
248
  default: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(currentList.value, (item, idx) => {
209
249
  return openBlock(), createElementBlock("li", {
210
- key: item.value ?? idx,
250
+ key: `${idx}-${item.value}`,
251
+ "data-idx": idx,
211
252
  class: normalizeClass(getItemCls(item, idx)),
212
- onClick: ($event) => handleItemClick(item),
253
+ onClick: ($event) => handleItemClick(item, idx),
213
254
  onMouseenter: ($event) => activeIndex.value = idx
214
- }, [createElementVNode("span", { class: normalizeClass(unref(cls).e("item-label")) }, toDisplayString(item.label), 3), item.children?.length ? (openBlock(), createBlock(unref(_sfc_main$2), {
255
+ }, [createElementVNode("span", { class: normalizeClass(unref(cls).e("item-label")) }, toDisplayString(item.label), 3), !isFlat.value && hasChildren(item) ? (openBlock(), createBlock(unref(_sfc_main$2), {
215
256
  key: 0,
216
257
  class: normalizeClass(unref(cls).e("item-expand"))
217
258
  }, {
@@ -220,8 +261,14 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
220
261
  }, 8, ["class"])) : createCommentVNode("v-if", true)], 42, _hoisted_2);
221
262
  }), 128))]),
222
263
  _: 1
223
- }, 8, ["class", "content-class"])], 2)) : (openBlock(), createElementBlock(Fragment, { key: 3 }, [createCommentVNode(" 空状态 "), createElementVNode("div", { class: normalizeClass(unref(cls).e("empty")) }, [createVNode(unref(_sfc_main$3), { description: "暂无可用变量" })], 2)], 2112))
224
- ]),
264
+ }, 8, ["class", "content-class"])], 2)) : (openBlock(), createElementBlock("div", {
265
+ key: 1,
266
+ class: normalizeClass(unref(cls).e("empty"))
267
+ }, [createVNode(unref(_sfc_main$3), { description: "暂无可用变量" })], 2)), isFlat.value && activePath.value.length > 0 ? (openBlock(), createBlock(_sfc_main$5, {
268
+ key: 2,
269
+ path: activePath.value
270
+ }, null, 8, ["path"])) : createCommentVNode("v-if", true)], 2)
271
+ ], 34)]),
225
272
  _: 1
226
273
  }, 8, ["trigger-dom", "visible"]);
227
274
  };
@@ -1 +1 @@
1
- {"version":3,"file":"variable-picker.js","names":[],"sources":["../../../../src/components/expression-editor/components/variable-picker.vue"],"sourcesContent":["<template>\n <u-tip\n hide-arrow\n alignment=\"start\"\n direction=\"bottom\"\n :trigger-dom=\"triggerDom\"\n :visible=\"visible\"\n @update:visible=\"updateVisible($event)\"\n >\n <template #content>\n <!-- 搜索框 -->\n <div v-if=\"filterable\" :class=\"cls.e('filter')\">\n <u-input\n placeholder=\"输入关键字进行过滤\"\n v-model=\"searchQuery\"\n :size=\"'small'\"\n clearable\n @input=\"handleSearch\"\n >\n <template #suffix>\n <u-icon><Search /></u-icon>\n </template>\n </u-input>\n </div>\n\n <!-- 面包屑导航 -->\n <div v-if=\"breadcrumbs.length > 1 && !searchQuery\" :class=\"cls.e('breadcrumbs')\">\n <span v-for=\"(crumb, index) in breadcrumbs\" :key=\"index\" :class=\"cls.e('breadcrumb-item')\">\n <span\n :class=\"[\n cls.e('breadcrumb-link'),\n { [cls.e('breadcrumb-link--active')]: index === breadcrumbs.length - 1 }\n ]\"\n @click=\"navigateToBreadcrumb(index)\"\n >\n {{ crumb.label }}\n </span>\n <u-icon v-if=\"index < breadcrumbs.length - 1\" :class=\"cls.e('breadcrumb-separator')\">\n <ArrowRight />\n </u-icon>\n </span>\n </div>\n\n <!-- 单面板 -->\n <div :class=\"cls.e('panel')\" v-if=\"currentList.length\">\n <u-scroll tag=\"ul\" :class=\"cls.e('panel-list')\" :content-class=\"cls.e('panel-content')\">\n <li\n v-for=\"(item, idx) in currentList\"\n :key=\"item.value ?? idx\"\n :class=\"getItemCls(item, idx)\"\n @click=\"handleItemClick(item)\"\n @mouseenter=\"activeIndex = idx\"\n >\n <span :class=\"cls.e('item-label')\">\n {{ item.label }}\n </span>\n <u-icon v-if=\"item.children?.length\" :class=\"cls.e('item-expand')\">\n <ArrowRight />\n </u-icon>\n </li>\n </u-scroll>\n </div>\n\n <!-- 空状态 -->\n <div v-else :class=\"cls.e('empty')\">\n <UEmpty description=\"暂无可用变量\" />\n </div>\n </template>\n </u-tip>\n</template>\n\n<script lang=\"ts\" setup>\nimport { ArrowRight, Search } from '@veltra/icons/normal'\nimport { bem } from '@veltra/utils'\nimport { inject, ref, computed, watch, onMounted, onBeforeUnmount } from 'vue'\n\nimport type { VariableItem } from '../../../types'\nimport { UEmpty } from '../../empty'\nimport { UIcon } from '../../icon'\nimport { UInput } from '../../input'\nimport { UScroll } from '../../scroll'\nimport { UTip } from '../../tip'\nimport { ExpressionEditorDIKey } from '../di'\n\ndefineOptions({\n name: 'VariablePicker'\n})\n\nconst props = defineProps<{\n visible: boolean\n triggerDom?: HTMLElement\n filterable?: boolean\n registerPickerKeyHandler?: (handler: ((e: KeyboardEvent) => void) | null) => void\n}>()\n\nconst emit = defineEmits<{\n (e: 'select', variable: VariableItem): void\n (e: 'update:visible', visible: boolean): void\n}>()\n\nconst { cls, editorProps, variableMap } = inject(ExpressionEditorDIKey)!\n\nconst searchQuery = ref('')\nconst activeIndex = ref<number>(-1)\nconst navigationPath = ref<VariableItem[]>([]) // 当前导航路径\n\n// 扁平化变量树,用于搜索\nfunction flattenVariables(\n items: VariableItem[] | undefined,\n parentPath: VariableItem[] = []\n): VariableItem[] {\n if (!items) return []\n const result: VariableItem[] = []\n\n for (const item of items) {\n const currentPath = [...parentPath, item]\n // 如果是叶子节点,添加到结果中\n if (!item.children || item.children.length === 0) {\n result.push({\n ...item,\n label: currentPath.map((p) => p.label).join(' / ')\n })\n } else {\n // 递归处理子节点\n result.push(...flattenVariables(item.children, currentPath))\n }\n }\n\n return result\n}\n\n// 过滤后的变量列表\nconst filteredVariables = computed(() => {\n const { variables } = editorProps\n if (!variables || variables.length === 0) return []\n\n if (!searchQuery.value.trim()) {\n return variables\n }\n\n // 搜索模式:扁平化并过滤\n const flat = flattenVariables(variables)\n const query = searchQuery.value.toLowerCase()\n return flat.filter((item) => item.label.toLowerCase().includes(query))\n})\n\n// 当前显示的列表(单面板)\nconst currentList = computed(() => {\n if (searchQuery.value.trim()) {\n // 搜索模式:显示过滤后的扁平列表\n return filteredVariables.value\n } else {\n // 正常模式:根据导航路径显示当前层级\n let current: VariableItem[] = filteredVariables.value\n for (const pathItem of navigationPath.value) {\n const found = current.find((item) => item.value === pathItem.value)\n if (found?.children) {\n current = found.children\n } else {\n break\n }\n }\n return current\n }\n})\n\n// 面包屑导航\nconst breadcrumbs = computed(() => {\n return [{ label: '全部变量', value: '' }, ...navigationPath.value]\n})\n\nfunction getItemCls(item: VariableItem, index: number): string[] {\n const isActive = activeIndex.value === index\n const isLeaf = !item.children || item.children.length === 0\n\n return [cls.e('item'), bem.is('active', isActive), bem.is('leaf', isLeaf)]\n}\n\nfunction handleItemClick(item: VariableItem) {\n // 如果有子节点,导航到下一级\n if (item.children && item.children.length > 0) {\n navigationPath.value.push(item)\n activeIndex.value = -1\n return\n }\n\n // 叶子节点:选择并关闭\n const fullItem = variableMap.value.get(item.value)\n if (fullItem) {\n emit('select', fullItem)\n } else {\n emit('select', item)\n }\n updateVisible(false)\n}\n\n// 面包屑导航\nfunction navigateToBreadcrumb(index: number) {\n if (index === 0) {\n // 点击\"全部变量\",返回根级\n navigationPath.value = []\n } else {\n // 导航到指定层级\n navigationPath.value = navigationPath.value.slice(0, index)\n }\n activeIndex.value = -1\n}\n\nfunction handleSearch() {\n // 搜索时重置导航路径\n navigationPath.value = []\n activeIndex.value = -1\n}\n\nfunction updateVisible(visible: boolean) {\n emit('update:visible', visible)\n if (!visible) {\n // 关闭时重置状态\n navigationPath.value = []\n searchQuery.value = ''\n activeIndex.value = -1\n }\n}\n\n// 处理键盘事件\nfunction handleKeydown(e: KeyboardEvent) {\n if (!props.visible) return\n\n if (e.key === 'Escape') {\n e.preventDefault()\n updateVisible(false)\n return\n }\n\n if (currentList.value.length === 0) return\n\n // 初始化 activeIndex\n if (activeIndex.value === -1 && currentList.value.length > 0) {\n activeIndex.value = 0\n }\n\n const currentItem = currentList.value[activeIndex.value]\n\n if (e.key === 'ArrowDown') {\n e.preventDefault()\n activeIndex.value = Math.min(currentList.value.length - 1, activeIndex.value + 1)\n } else if (e.key === 'ArrowUp') {\n e.preventDefault()\n activeIndex.value = Math.max(0, activeIndex.value - 1)\n } else if (e.key === ' ' || e.key === 'Spacebar') {\n // 空格键:导航到下一级(仅对有子项的项有效)\n e.preventDefault()\n if (currentItem?.children && currentItem.children.length > 0) {\n handleItemClick(currentItem)\n }\n } else if (e.key === 'ArrowRight') {\n // 右箭头:导航到下一级(仅对有子项的项有效)\n e.preventDefault()\n if (currentItem?.children && currentItem.children.length > 0) {\n handleItemClick(currentItem)\n }\n } else if (e.key === 'ArrowLeft' || e.key === 'Backspace') {\n // 左箭头/退格:返回上一级\n e.preventDefault()\n if (navigationPath.value.length > 0) {\n navigationPath.value.pop()\n activeIndex.value = 0\n }\n } else if (e.key === 'Enter') {\n // 回车键:确定选择(仅对最末级项有效)\n e.preventDefault()\n if (currentItem) {\n if (!currentItem.children || currentItem.children.length === 0) {\n // 只有叶子节点才能选择\n handleItemClick(currentItem)\n }\n }\n }\n}\n\n// 监听键盘事件\nwatch(\n () => props.visible,\n (v) => {\n if (v) {\n document.addEventListener('keydown', handleKeydown)\n } else {\n document.removeEventListener('keydown', handleKeydown)\n }\n }\n)\n\nonMounted(() => {\n props.registerPickerKeyHandler?.(handleKeydown)\n})\n\nonBeforeUnmount(() => {\n document.removeEventListener('keydown', handleKeydown)\n props.registerPickerKeyHandler?.(null)\n})\n</script>\n"],"mappings":"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"}
1
+ {"version":3,"file":"variable-picker.js","names":[],"sources":["../../../../src/components/expression-editor/components/variable-picker.vue"],"sourcesContent":["<template>\n <u-tip\n hide-arrow\n alignment=\"start\"\n direction=\"bottom\"\n trigger=\"click\"\n :trigger-dom=\"triggerDom\"\n :visible=\"visible\"\n @update:visible=\"handleVisibleChange($event)\"\n >\n <template #content>\n <div :class=\"cls.e('picker')\" @mousedown.prevent>\n <!-- 面包屑:仅逐级模式且非根级显示 -->\n <div v-if=\"!isFlat && navPath.length > 0\" :class=\"cls.e('breadcrumbs')\">\n <span :class=\"cls.e('breadcrumb-item')\">\n <span :class=\"cls.e('breadcrumb-link')\" @click=\"navigateTo(0)\">全部变量</span>\n <u-icon :class=\"cls.e('breadcrumb-separator')\">\n <ArrowRight />\n </u-icon>\n </span>\n <span\n v-for=\"(crumb, i) in navPath\"\n :key=\"`${i}-${crumb.value}`\"\n :class=\"cls.e('breadcrumb-item')\"\n >\n <span\n :class=\"[\n cls.e('breadcrumb-link'),\n { [cls.em('breadcrumb-link', 'active')]: i === navPath.length - 1 }\n ]\"\n @click=\"navigateTo(i + 1)\"\n >{{ crumb.label }}</span\n >\n <u-icon v-if=\"i < navPath.length - 1\" :class=\"cls.e('breadcrumb-separator')\">\n <ArrowRight />\n </u-icon>\n </span>\n </div>\n\n <div :class=\"cls.e('picker-body')\">\n <div :class=\"cls.e('panel')\" v-if=\"currentList.length > 0\">\n <u-scroll tag=\"ul\" :class=\"cls.e('panel-list')\" :content-class=\"cls.e('panel-content')\">\n <li\n v-for=\"(item, idx) in currentList\"\n :key=\"`${idx}-${item.value}`\"\n :data-idx=\"idx\"\n :class=\"getItemCls(item, idx)\"\n @click=\"handleItemClick(item, idx)\"\n @mouseenter=\"activeIndex = idx\"\n >\n <span :class=\"cls.e('item-label')\">{{ item.label }}</span>\n <u-icon v-if=\"!isFlat && hasChildren(item)\" :class=\"cls.e('item-expand')\">\n <ArrowRight />\n </u-icon>\n </li>\n </u-scroll>\n </div>\n <div v-else :class=\"cls.e('empty')\">\n <UEmpty description=\"暂无可用变量\" />\n </div>\n\n <PathPreview v-if=\"isFlat && activePath.length > 0\" :path=\"activePath\" />\n </div>\n </div>\n </template>\n </u-tip>\n</template>\n\n<script lang=\"ts\" setup>\nimport { ArrowRight } from '@veltra/icons/normal'\nimport { bem } from '@veltra/utils'\nimport { computed, nextTick, ref, watch } from 'vue'\n\nimport type { VariableItem } from '../../../types'\nimport { UEmpty } from '../../empty'\nimport { UIcon } from '../../icon'\nimport { UScroll } from '../../scroll'\nimport { UTip } from '../../tip'\nimport PathPreview from './path-preview.vue'\n\ndefineOptions({\n name: 'VariablePicker'\n})\n\ninterface FlatItem extends VariableItem {\n /** 仅扁平模式使用:从根到当前节点的路径 */\n __path: VariableItem[]\n}\n\nconst props = defineProps<{\n visible: boolean\n triggerDom?: HTMLElement\n /** 顶层变量树 */\n variables?: VariableItem[]\n /** 来自编辑器 mention 状态的过滤字符串。空 = 逐级模式;非空 = 扁平模式。 */\n filter: string\n /** 是否允许选中分支节点 */\n selectableLevels: 'leaf' | 'any'\n}>()\n\nconst emit = defineEmits<{\n (e: 'select', item: VariableItem): void\n (e: 'update:visible', visible: boolean): void\n /** 用户主动取消(按 Esc)。编辑器据此把 mention 状态置为 dismissed。 */\n (e: 'dismiss'): void\n}>()\n\nconst cls = bem('expression-editor')\n\nconst navPath = ref<VariableItem[]>([])\nconst activeIndex = ref(0)\n\nconst isFlat = computed(() => props.filter !== '')\n\nfunction hasChildren(item: VariableItem): boolean {\n return !!item.children && item.children.length > 0\n}\n\nfunction isLeaf(item: VariableItem): boolean {\n return !hasChildren(item)\n}\n\n/** 当前层级展示的列表(来源依模式而定) */\nconst currentList = computed<VariableItem[]>(() => {\n const variables = props.variables ?? []\n if (isFlat.value) return flatList.value\n let level: VariableItem[] = variables\n for (const node of navPath.value) {\n const found = level.find((it) => it.value === node.value)\n if (!found || !found.children) break\n level = found.children\n }\n return level\n})\n\n/** 扁平模式下:根据 selectableLevels 拍平 + 过滤 */\nconst flatList = computed<FlatItem[]>(() => {\n const variables = props.variables ?? []\n const out: FlatItem[] = []\n const includeBranches = props.selectableLevels === 'any'\n function walk(items: VariableItem[], path: VariableItem[]) {\n for (const it of items) {\n const nextPath = [...path, it]\n if (isLeaf(it)) {\n out.push({ ...it, __path: nextPath })\n } else {\n if (includeBranches) out.push({ ...it, __path: nextPath })\n walk(it.children!, nextPath)\n }\n }\n }\n walk(variables, [])\n const q = props.filter.toLowerCase()\n return out.filter((it) => it.label.toLowerCase().includes(q))\n})\n\n/** 当前 active 项;扁平模式下用于驱动路径预览 */\nconst activePath = computed<VariableItem[]>(() => {\n if (!isFlat.value) return []\n const item = flatList.value[activeIndex.value] as FlatItem | undefined\n return item ? item.__path : []\n})\n\nfunction getItemCls(_item: VariableItem, idx: number) {\n return [cls.e('item'), bem.is('active', idx === activeIndex.value)]\n}\n\nfunction navigateTo(i: number) {\n // 0 = 全部变量;i 之后表示进入到 navPath[i-1]\n navPath.value = navPath.value.slice(0, i)\n activeIndex.value = 0\n}\n\nfunction handleItemClick(item: VariableItem, idx: number) {\n activeIndex.value = idx\n selectActive()\n}\n\nfunction selectActive() {\n const item = currentList.value[activeIndex.value]\n if (!item) return\n\n if (isFlat.value) {\n // 扁平模式始终是「选中并替换」\n emit('select', flattenSourceFor(item))\n return\n }\n\n // 逐级模式\n if (hasChildren(item)) {\n if (props.selectableLevels === 'any') {\n // Enter 在分支项上:选中分支本身\n emit('select', item)\n return\n }\n // 默认 'leaf':进入下一级\n navPath.value = [...navPath.value, item]\n activeIndex.value = 0\n return\n }\n emit('select', item)\n}\n\nfunction flattenSourceFor(it: VariableItem): VariableItem {\n const flat = it as FlatItem\n // 还原为「不带 __path 的纯 VariableItem」,避免泄漏内部字段\n const out: VariableItem = { value: flat.value, label: flat.label }\n if (flat.type) out.type = flat.type\n if (flat.children) out.children = flat.children\n return out\n}\n\nfunction moveActive(delta: -1 | 1) {\n const max = currentList.value.length - 1\n if (max < 0) return\n let next = activeIndex.value + delta\n if (next < 0) next = 0\n if (next > max) next = max\n activeIndex.value = next\n scrollActiveIntoView()\n}\n\nfunction scrollActiveIntoView() {\n void nextTick(() => {\n const list = document.querySelector(`.${cls.e('panel-list')}`)\n if (!list) return\n const item = list.querySelector(`[data-idx=\"${activeIndex.value}\"]`)\n if (item && 'scrollIntoView' in item) {\n ;(item as HTMLElement).scrollIntoView({ block: 'nearest' })\n }\n })\n}\n\nfunction handleVisibleChange(v: boolean) {\n emit('update:visible', v)\n if (!v) reset()\n}\n\nfunction reset() {\n navPath.value = []\n activeIndex.value = 0\n}\n\nwatch(\n () => props.visible,\n (v) => {\n if (!v) reset()\n else activeIndex.value = 0\n }\n)\n\nwatch(\n () => props.filter,\n () => {\n // 过滤词变化:重置 active;逐级 → 扁平时清空 navPath\n activeIndex.value = 0\n if (isFlat.value) navPath.value = []\n }\n)\n\n/**\n * 由父级 forward 进来的键盘事件。\n * 返回 true 表示已处理(外部应 preventDefault)。\n */\nfunction handleKeydown(e: KeyboardEvent): boolean {\n if (!props.visible) return false\n\n switch (e.key) {\n case 'Escape':\n emit('dismiss')\n emit('update:visible', false)\n reset()\n return true\n case 'ArrowDown':\n moveActive(1)\n return true\n case 'ArrowUp':\n moveActive(-1)\n return true\n case 'ArrowLeft':\n // 仅逐级模式:返回上一级;扁平模式下不拦截,让光标正常移动并触发 mention dismiss\n if (!isFlat.value && navPath.value.length > 0) {\n navPath.value = navPath.value.slice(0, -1)\n activeIndex.value = 0\n return true\n }\n return false\n case 'ArrowRight': {\n if (isFlat.value) return false\n const item = currentList.value[activeIndex.value]\n if (item && hasChildren(item)) {\n navPath.value = [...navPath.value, item]\n activeIndex.value = 0\n return true\n }\n return false\n }\n case 'Enter':\n selectActive()\n return true\n default:\n return false\n }\n}\n\ndefineExpose({ handleKeydown })\n</script>\n"],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ //#region src/components/expression-editor/core/chip.ts
2
+ const CHIP_DATA_SEG = "var";
3
+ const CHIP_VALUE_ATTR = "data-value";
4
+ const CHIP_TYPE_ATTR = "data-var-type";
5
+ const CHIP_CLOSE_ATTR = "data-chip-close";
6
+ /** 判断一个 DOM 节点是否为 chip 元素(var 段渲染体) */
7
+ function isChipElement(node) {
8
+ return !!node && node.nodeType === 1 && node.getAttribute("data-seg") === CHIP_DATA_SEG;
9
+ }
10
+ /** 判断点击目标是否落在 × 删除区。 */
11
+ function isCloseTarget(target) {
12
+ if (!(target instanceof Element)) return false;
13
+ return !!target.closest(`[${CHIP_CLOSE_ATTR}]`);
14
+ }
15
+ /** 从给定 segment 创建一个 chip 元素(var 段渲染体)。 */
16
+ function createChip(segment, opts) {
17
+ const { cls } = opts;
18
+ const chip = document.createElement("span");
19
+ chip.setAttribute("data-seg", CHIP_DATA_SEG);
20
+ chip.setAttribute(CHIP_VALUE_ATTR, segment.value);
21
+ if (segment.type) chip.setAttribute(CHIP_TYPE_ATTR, segment.type);
22
+ chip.setAttribute("contenteditable", "false");
23
+ chip.className = cls.e("chip");
24
+ chip.title = segment.label;
25
+ const label = document.createElement("span");
26
+ label.className = cls.e("chip-label");
27
+ label.textContent = segment.type ? `${segment.label} (${segment.type})` : segment.label;
28
+ const close = document.createElement("span");
29
+ close.className = cls.e("chip-close");
30
+ close.setAttribute(CHIP_CLOSE_ATTR, "true");
31
+ close.setAttribute("aria-label", "删除");
32
+ close.setAttribute("role", "button");
33
+ close.textContent = "×";
34
+ chip.appendChild(label);
35
+ chip.appendChild(close);
36
+ chip.addEventListener("mousedown", (e) => {
37
+ e.preventDefault();
38
+ if (isCloseTarget(e.target)) opts.onRemove(chip);
39
+ else opts.onReselect(chip);
40
+ });
41
+ return chip;
42
+ }
43
+ /** 读取 chip 元素绑定的变量 value。 */
44
+ function readChipValue(el) {
45
+ return el.getAttribute(CHIP_VALUE_ATTR) ?? "";
46
+ }
47
+ /** 读取 chip 元素绑定的可选 type。 */
48
+ function readChipType(el) {
49
+ return el.getAttribute(CHIP_TYPE_ATTR) ?? void 0;
50
+ }
51
+ //#endregion
52
+ export { createChip, isChipElement, readChipType, readChipValue };
53
+
54
+ //# sourceMappingURL=chip.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chip.js","names":[],"sources":["../../../../src/components/expression-editor/core/chip.ts"],"sourcesContent":["import type { BEM } from '@veltra/utils'\n\nimport type { Segment } from './model'\n\nexport interface ChipDeps {\n cls: BEM<'expression-editor'>\n}\n\nexport interface ChipFactoryOptions extends ChipDeps {\n /**\n * 当用户点击 chip 主体(不是 ×)时回调,由调用方决定如何打开重选面板。\n * 收到的 chipEl 当前在 DOM 中的位置即为重选锚点。\n */\n onReselect: (chipEl: HTMLElement) => void\n /** 当用户点击 × 时回调,调用方应将该 chip 从模型 / DOM 中移除。 */\n onRemove: (chipEl: HTMLElement) => void\n}\n\nconst CHIP_DATA_SEG = 'var'\nconst CHIP_VALUE_ATTR = 'data-value'\nconst CHIP_TYPE_ATTR = 'data-var-type'\nconst CHIP_CLOSE_ATTR = 'data-chip-close'\n\n/** 判断一个 DOM 节点是否为 chip 元素(var 段渲染体) */\nexport function isChipElement(node: Node | null): node is HTMLElement {\n return (\n !!node &&\n node.nodeType === 1 &&\n (node as HTMLElement).getAttribute('data-seg') === CHIP_DATA_SEG\n )\n}\n\n/** 判断点击目标是否落在 × 删除区。 */\nfunction isCloseTarget(target: EventTarget | null): boolean {\n if (!(target instanceof Element)) return false\n return !!target.closest(`[${CHIP_CLOSE_ATTR}]`)\n}\n\n/** 从给定 segment 创建一个 chip 元素(var 段渲染体)。 */\nexport function createChip(\n segment: Extract<Segment, { kind: 'var' }>,\n opts: ChipFactoryOptions\n): HTMLElement {\n const { cls } = opts\n\n const chip = document.createElement('span')\n chip.setAttribute('data-seg', CHIP_DATA_SEG)\n chip.setAttribute(CHIP_VALUE_ATTR, segment.value)\n if (segment.type) chip.setAttribute(CHIP_TYPE_ATTR, segment.type)\n chip.setAttribute('contenteditable', 'false')\n chip.className = cls.e('chip')\n chip.title = segment.label\n\n const label = document.createElement('span')\n label.className = cls.e('chip-label')\n label.textContent = segment.type ? `${segment.label} (${segment.type})` : segment.label\n\n const close = document.createElement('span')\n close.className = cls.e('chip-close')\n close.setAttribute(CHIP_CLOSE_ATTR, 'true')\n close.setAttribute('aria-label', '删除')\n close.setAttribute('role', 'button')\n close.textContent = '×'\n\n chip.appendChild(label)\n chip.appendChild(close)\n\n chip.addEventListener('mousedown', (e) => {\n e.preventDefault()\n if (isCloseTarget(e.target)) {\n opts.onRemove(chip)\n } else {\n opts.onReselect(chip)\n }\n })\n\n return chip\n}\n\n/** 读取 chip 元素绑定的变量 value。 */\nexport function readChipValue(el: HTMLElement): string {\n return el.getAttribute(CHIP_VALUE_ATTR) ?? ''\n}\n\n/** 读取 chip 元素绑定的可选 type。 */\nexport function readChipType(el: HTMLElement): string | undefined {\n return el.getAttribute(CHIP_TYPE_ATTR) ?? undefined\n}\n\n/** 在 chip 上设置 / 取消选中(焦点)样式。 */\nexport function setChipFocused(el: HTMLElement, focused: boolean): void {\n el.classList.toggle('is-focused', focused)\n}\n\n/** 测试支持:暴露用于 DOM 选择器的属性常量。 */\nexport const ChipAttrs = {\n seg: 'data-seg',\n value: CHIP_VALUE_ATTR,\n type: CHIP_TYPE_ATTR,\n close: CHIP_CLOSE_ATTR\n} as const\n"],"mappings":";AAkBA,MAAM,gBAAgB;AACtB,MAAM,kBAAkB;AACxB,MAAM,iBAAiB;AACvB,MAAM,kBAAkB;;AAGxB,SAAgB,cAAc,MAAwC;AACpE,QACE,CAAC,CAAC,QACF,KAAK,aAAa,KACjB,KAAqB,aAAa,WAAW,KAAK;;;AAKvD,SAAS,cAAc,QAAqC;AAC1D,KAAI,EAAE,kBAAkB,SAAU,QAAO;AACzC,QAAO,CAAC,CAAC,OAAO,QAAQ,IAAI,gBAAgB,GAAG;;;AAIjD,SAAgB,WACd,SACA,MACa;CACb,MAAM,EAAE,QAAQ;CAEhB,MAAM,OAAO,SAAS,cAAc,OAAO;AAC3C,MAAK,aAAa,YAAY,cAAc;AAC5C,MAAK,aAAa,iBAAiB,QAAQ,MAAM;AACjD,KAAI,QAAQ,KAAM,MAAK,aAAa,gBAAgB,QAAQ,KAAK;AACjE,MAAK,aAAa,mBAAmB,QAAQ;AAC7C,MAAK,YAAY,IAAI,EAAE,OAAO;AAC9B,MAAK,QAAQ,QAAQ;CAErB,MAAM,QAAQ,SAAS,cAAc,OAAO;AAC5C,OAAM,YAAY,IAAI,EAAE,aAAa;AACrC,OAAM,cAAc,QAAQ,OAAO,GAAG,QAAQ,MAAM,IAAI,QAAQ,KAAK,KAAK,QAAQ;CAElF,MAAM,QAAQ,SAAS,cAAc,OAAO;AAC5C,OAAM,YAAY,IAAI,EAAE,aAAa;AACrC,OAAM,aAAa,iBAAiB,OAAO;AAC3C,OAAM,aAAa,cAAc,KAAK;AACtC,OAAM,aAAa,QAAQ,SAAS;AACpC,OAAM,cAAc;AAEpB,MAAK,YAAY,MAAM;AACvB,MAAK,YAAY,MAAM;AAEvB,MAAK,iBAAiB,cAAc,MAAM;AACxC,IAAE,gBAAgB;AAClB,MAAI,cAAc,EAAE,OAAO,CACzB,MAAK,SAAS,KAAK;MAEnB,MAAK,WAAW,KAAK;GAEvB;AAEF,QAAO;;;AAIT,SAAgB,cAAc,IAAyB;AACrD,QAAO,GAAG,aAAa,gBAAgB,IAAI;;;AAI7C,SAAgB,aAAa,IAAqC;AAChE,QAAO,GAAG,aAAa,eAAe,IAAI,KAAA"}