@manohub/ui 0.7.1 → 0.8.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.
- package/README.md +1 -1
- package/dist/components/button/index.d.ts +6 -0
- package/dist/components/card/index.d.ts +21 -19
- package/dist/components/card/index.js +12 -4
- package/dist/components/form/index.d.ts +28 -46
- package/dist/components/form/index.js +3 -21
- package/dist/components/input/index.d.ts +74 -6
- package/dist/components/input/index.js +13 -1
- package/dist/components/list-view/index.d.ts +1 -1
- package/dist/components/pagination/index.d.ts +2 -2
- package/dist/components/pagination/index.js +1 -1
- package/dist/components/query-state/index.d.ts +18 -2
- package/dist/components/select/index.d.ts +32 -30
- package/dist/components/select/index.js +46 -82
- package/dist/components/select/select-list.d.ts +125 -0
- package/dist/components/select/select-list.js +95 -0
- package/dist/components/select/select-panel.d.ts +106 -0
- package/dist/components/select/select-panel.js +72 -0
- package/dist/components/select/select-shared.d.ts +64 -0
- package/dist/components/select/select-shared.js +24 -0
- package/dist/components/select/select-tree-filter.d.ts +61 -0
- package/dist/components/select/select-tree-filter.js +63 -0
- package/dist/components/select/select-tree.d.ts +274 -0
- package/dist/components/select/select-tree.js +263 -0
- package/dist/components/select/select-trigger.d.ts +132 -0
- package/dist/components/select/select-trigger.js +101 -0
- package/dist/components/steps/index.d.ts +1 -1
- package/dist/components/table/index.d.ts +25 -4
- package/dist/components/text/index.d.ts +7 -2
- package/dist/components/tree/index.d.ts +28 -2
- package/dist/components/tree/index.js +14 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -0
- package/dist/styles/components/card.css +7 -1
- package/dist/styles/components/form.css +0 -21
- package/dist/styles/components/input.css +25 -0
- package/dist/styles/components/select.css +21 -0
- package/dist/styles/components/select.tokens.css +3 -0
- package/package.json +2 -2
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { defineComponent, ref, computed, watch, createVNode, createTextVNode, Fragment } from "vue";
|
|
2
|
+
import { Tree } from "../tree/index.js";
|
|
3
|
+
import { SelectPanel } from "./select-panel.js";
|
|
4
|
+
import { SelectTrigger } from "./select-trigger.js";
|
|
5
|
+
import { createSelectPanelId, createTriggerKeydown, showPanel, hidePanel } from "./select-shared.js";
|
|
6
|
+
import { filterTreeNodes, findTreeNode, treeLabel, splitHighlight, treeChildren, treeNodeKey } from "./select-tree-filter.js";
|
|
7
|
+
const SelectTree = /* @__PURE__ */ defineComponent({
|
|
8
|
+
name: "SelectTree",
|
|
9
|
+
inheritAttrs: false,
|
|
10
|
+
props: {
|
|
11
|
+
/** 树数据(与 `Tree` 同一个 `TreeNode` 类型,不另造节点形状) */
|
|
12
|
+
nodes: {
|
|
13
|
+
type: Array,
|
|
14
|
+
required: true
|
|
15
|
+
},
|
|
16
|
+
/** 节点主键:字段名或取值函数(与 `Tree` 同义) */
|
|
17
|
+
rowKey: {
|
|
18
|
+
type: [String, Function],
|
|
19
|
+
required: true
|
|
20
|
+
},
|
|
21
|
+
/** 标签字段名(默认 'label') */
|
|
22
|
+
labelKey: {
|
|
23
|
+
type: String,
|
|
24
|
+
default: "label"
|
|
25
|
+
},
|
|
26
|
+
/** 子节点字段名(默认 'children') */
|
|
27
|
+
childrenKey: {
|
|
28
|
+
type: String,
|
|
29
|
+
default: "children"
|
|
30
|
+
},
|
|
31
|
+
/** 受控值(选中叶子的主键;树选择只单选) */
|
|
32
|
+
modelValue: {
|
|
33
|
+
type: String,
|
|
34
|
+
default: void 0
|
|
35
|
+
},
|
|
36
|
+
/** 非受控初始值 */
|
|
37
|
+
defaultValue: {
|
|
38
|
+
type: String,
|
|
39
|
+
default: void 0
|
|
40
|
+
},
|
|
41
|
+
placeholder: {
|
|
42
|
+
type: String,
|
|
43
|
+
default: "请选择"
|
|
44
|
+
},
|
|
45
|
+
/** 搜索:`true` 强制开;不给时节点总数多于 8 自动开(与 `Select` 同口径) */
|
|
46
|
+
searchable: {
|
|
47
|
+
type: Boolean,
|
|
48
|
+
default: false
|
|
49
|
+
},
|
|
50
|
+
/** 自定义命中判定(缺省:标签区分大小写地包含关键词) */
|
|
51
|
+
filterNode: {
|
|
52
|
+
type: Function,
|
|
53
|
+
default: void 0
|
|
54
|
+
},
|
|
55
|
+
/** 命中文本是否高亮(默认开) */
|
|
56
|
+
highlight: {
|
|
57
|
+
type: Boolean,
|
|
58
|
+
default: true
|
|
59
|
+
},
|
|
60
|
+
/** 受控展开集(传了就完全受控;**过滤态下组件强制全展**,不受本 prop 约束) */
|
|
61
|
+
expandedKeys: {
|
|
62
|
+
type: Array,
|
|
63
|
+
default: void 0
|
|
64
|
+
},
|
|
65
|
+
/** 非受控展开初值 */
|
|
66
|
+
defaultExpandedKeys: {
|
|
67
|
+
type: Array,
|
|
68
|
+
default: () => []
|
|
69
|
+
},
|
|
70
|
+
/** 初始展开全部(与 `defaultExpandedKeys` 互斥,它优先) */
|
|
71
|
+
defaultExpandAll: {
|
|
72
|
+
type: Boolean,
|
|
73
|
+
default: false
|
|
74
|
+
},
|
|
75
|
+
/** 展开集变化回调 */
|
|
76
|
+
onExpandChange: {
|
|
77
|
+
type: Function,
|
|
78
|
+
default: void 0
|
|
79
|
+
},
|
|
80
|
+
clearable: {
|
|
81
|
+
type: Boolean,
|
|
82
|
+
default: false
|
|
83
|
+
},
|
|
84
|
+
disabled: {
|
|
85
|
+
type: Boolean,
|
|
86
|
+
default: false
|
|
87
|
+
},
|
|
88
|
+
/** 加载中(面板里出一行提示,不渲染树) */
|
|
89
|
+
loading: {
|
|
90
|
+
type: Boolean,
|
|
91
|
+
default: false
|
|
92
|
+
},
|
|
93
|
+
emptyText: {
|
|
94
|
+
type: String,
|
|
95
|
+
default: "暂无数据"
|
|
96
|
+
},
|
|
97
|
+
/** 尺寸档(24 / 28 / 32) */
|
|
98
|
+
size: {
|
|
99
|
+
type: String,
|
|
100
|
+
default: "md"
|
|
101
|
+
},
|
|
102
|
+
/** 校验状态(红 / 黄;与 `Input` 同一词表) */
|
|
103
|
+
status: {
|
|
104
|
+
type: String,
|
|
105
|
+
default: void 0
|
|
106
|
+
},
|
|
107
|
+
/** 值变更回调(选中叶子的主键) */
|
|
108
|
+
onChange: {
|
|
109
|
+
type: Function,
|
|
110
|
+
default: void 0
|
|
111
|
+
},
|
|
112
|
+
/** 搜索词变更(远程换数据用;本地过滤不受它影响) */
|
|
113
|
+
onSearch: {
|
|
114
|
+
type: Function,
|
|
115
|
+
default: void 0
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
emits: {
|
|
119
|
+
"update:modelValue": (v) => true
|
|
120
|
+
},
|
|
121
|
+
slots: Object,
|
|
122
|
+
setup(props, {
|
|
123
|
+
emit,
|
|
124
|
+
slots,
|
|
125
|
+
attrs
|
|
126
|
+
}) {
|
|
127
|
+
const panelId = createSelectPanelId();
|
|
128
|
+
const panelRef = ref(null);
|
|
129
|
+
const inner = ref(props.defaultValue);
|
|
130
|
+
const keyword = ref("");
|
|
131
|
+
const keyOf = (node) => treeNodeKey(node, props.rowKey);
|
|
132
|
+
const childrenOf = (node) => treeChildren(node, props.childrenKey);
|
|
133
|
+
const current = computed(() => props.modelValue ?? inner.value);
|
|
134
|
+
const hasValue = computed(() => current.value !== void 0 && current.value !== "");
|
|
135
|
+
const nodeCount = computed(() => {
|
|
136
|
+
let count = 0;
|
|
137
|
+
const walk = (list) => {
|
|
138
|
+
for (const node of list) {
|
|
139
|
+
count += 1;
|
|
140
|
+
walk(childrenOf(node));
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
walk(props.nodes);
|
|
144
|
+
return count;
|
|
145
|
+
});
|
|
146
|
+
const showSearch = computed(() => props.searchable || nodeCount.value > 8);
|
|
147
|
+
const collectKeys = (list) => list.flatMap((node) => [keyOf(node), ...collectKeys(childrenOf(node))]);
|
|
148
|
+
const innerExpanded = ref(props.defaultExpandAll ? collectKeys(props.nodes) : [...props.defaultExpandedKeys]);
|
|
149
|
+
watch(() => props.defaultExpandedKeys, (next) => {
|
|
150
|
+
if (props.expandedKeys === void 0) innerExpanded.value = [...next];
|
|
151
|
+
});
|
|
152
|
+
const filtered = computed(() => keyword.value ? filterTreeNodes(props.nodes, {
|
|
153
|
+
rowKey: props.rowKey,
|
|
154
|
+
childrenKey: props.childrenKey,
|
|
155
|
+
labelKey: props.labelKey,
|
|
156
|
+
keyword: keyword.value,
|
|
157
|
+
match: props.filterNode
|
|
158
|
+
}) : void 0);
|
|
159
|
+
const visibleNodes = computed(() => filtered.value?.nodes ?? props.nodes);
|
|
160
|
+
const expandedKeys = computed(() => filtered.value ? filtered.value.keys : props.expandedKeys ?? innerExpanded.value);
|
|
161
|
+
const handleExpandChange = (keys) => {
|
|
162
|
+
if (props.expandedKeys === void 0) innerExpanded.value = keys;
|
|
163
|
+
props.onExpandChange?.(keys);
|
|
164
|
+
};
|
|
165
|
+
const displayLabel = computed(() => {
|
|
166
|
+
if (!hasValue.value) return void 0;
|
|
167
|
+
const found = findTreeNode(props.nodes, String(current.value), {
|
|
168
|
+
rowKey: props.rowKey,
|
|
169
|
+
childrenKey: props.childrenKey
|
|
170
|
+
});
|
|
171
|
+
return found ? treeLabel(found, props.labelKey) : String(current.value);
|
|
172
|
+
});
|
|
173
|
+
const commit = (value) => {
|
|
174
|
+
if (props.modelValue === void 0) inner.value = value;
|
|
175
|
+
emit("update:modelValue", value);
|
|
176
|
+
props.onChange?.(value);
|
|
177
|
+
};
|
|
178
|
+
const hide = () => hidePanel(panelRef.value);
|
|
179
|
+
const toggleExpand = (key) => {
|
|
180
|
+
const next = new Set(expandedKeys.value);
|
|
181
|
+
if (next.has(key)) next.delete(key);
|
|
182
|
+
else next.add(key);
|
|
183
|
+
handleExpandChange([...next]);
|
|
184
|
+
};
|
|
185
|
+
const pick = (node) => {
|
|
186
|
+
if (childrenOf(node).length > 0) {
|
|
187
|
+
toggleExpand(keyOf(node));
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
commit(keyOf(node));
|
|
191
|
+
hide();
|
|
192
|
+
};
|
|
193
|
+
const clear = () => commit("");
|
|
194
|
+
const onTriggerKeydown = createTriggerKeydown({
|
|
195
|
+
clearable: props.clearable,
|
|
196
|
+
disabled: props.disabled,
|
|
197
|
+
clear,
|
|
198
|
+
openPanel: () => showPanel(panelRef.value)
|
|
199
|
+
/* 有意不给 `focusFirst`:树的行是 `div`、没有 tabindex,面板内不做 ↑↓ 行导航 */
|
|
200
|
+
});
|
|
201
|
+
const renderHitLabel = (node) => createVNode(Fragment, null, [splitHighlight(treeLabel(node, props.labelKey), keyword.value).map((segment, index) => segment.hit ? createVNode("span", {
|
|
202
|
+
"key": index,
|
|
203
|
+
"class": "mh-select__hit"
|
|
204
|
+
}, [segment.text]) : segment.text)]);
|
|
205
|
+
return () => {
|
|
206
|
+
const highlightLabel = props.highlight && !!keyword.value ? renderHitLabel : void 0;
|
|
207
|
+
return createVNode("div", {
|
|
208
|
+
"class": ["mh-popup-host", attrs.class].filter(Boolean).join(" "),
|
|
209
|
+
"style": attrs.style
|
|
210
|
+
}, [createVNode(SelectTrigger, {
|
|
211
|
+
"panelId": panelId,
|
|
212
|
+
"size": props.size,
|
|
213
|
+
"status": props.status,
|
|
214
|
+
"disabled": props.disabled,
|
|
215
|
+
"clearable": props.clearable,
|
|
216
|
+
"hasValue": hasValue.value,
|
|
217
|
+
"placeholder": props.placeholder,
|
|
218
|
+
"label": displayLabel.value,
|
|
219
|
+
"onKeydown": onTriggerKeydown,
|
|
220
|
+
"onClear": clear
|
|
221
|
+
}, null), createVNode(SelectPanel, {
|
|
222
|
+
"id": panelId,
|
|
223
|
+
"elementRef": panelRef,
|
|
224
|
+
"searchable": showSearch.value,
|
|
225
|
+
"keyword": keyword.value,
|
|
226
|
+
"onKeyword": (value) => {
|
|
227
|
+
keyword.value = value;
|
|
228
|
+
props.onSearch?.(value);
|
|
229
|
+
}
|
|
230
|
+
}, {
|
|
231
|
+
default: () => {
|
|
232
|
+
if (props.loading) {
|
|
233
|
+
return createVNode("span", {
|
|
234
|
+
"class": "mh-select__option mh-select__option--hint"
|
|
235
|
+
}, [createTextVNode("加载中…")]);
|
|
236
|
+
}
|
|
237
|
+
if (visibleNodes.value.length === 0) {
|
|
238
|
+
const custom = slots.empty?.();
|
|
239
|
+
return custom !== void 0 && custom !== null ? custom : createVNode("span", {
|
|
240
|
+
"class": "mh-select__option mh-select__option--empty"
|
|
241
|
+
}, [props.emptyText]);
|
|
242
|
+
}
|
|
243
|
+
return createVNode("div", {
|
|
244
|
+
"class": "mh-select__tree"
|
|
245
|
+
}, [createVNode(Tree, {
|
|
246
|
+
"nodes": visibleNodes.value,
|
|
247
|
+
"rowKey": props.rowKey,
|
|
248
|
+
"labelKey": props.labelKey,
|
|
249
|
+
"childrenKey": props.childrenKey,
|
|
250
|
+
"selected": current.value === "" ? void 0 : current.value,
|
|
251
|
+
"expandedKeys": expandedKeys.value,
|
|
252
|
+
"onExpandChange": handleExpandChange,
|
|
253
|
+
"onSelect": pick,
|
|
254
|
+
"renderLabel": highlightLabel
|
|
255
|
+
}, null)]);
|
|
256
|
+
}
|
|
257
|
+
})]);
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
export {
|
|
262
|
+
SelectTree
|
|
263
|
+
};
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { type PropType, type VNodeChild } from 'vue';
|
|
2
|
+
import { type InputStatus } from '../input';
|
|
3
|
+
import type { ControlSize } from '../control-size';
|
|
4
|
+
export declare const SelectTrigger: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
5
|
+
/** 面板 id(`popovertarget` 的目标,由 `createSelectPanelId()` 产出的全页唯一 id) */
|
|
6
|
+
panelId: {
|
|
7
|
+
type: StringConstructor;
|
|
8
|
+
required: true;
|
|
9
|
+
};
|
|
10
|
+
/** 尺寸档(24 / 28 / 32):框高与字号归输入面,本件只透传 */
|
|
11
|
+
size: {
|
|
12
|
+
type: PropType<ControlSize>;
|
|
13
|
+
default: string;
|
|
14
|
+
};
|
|
15
|
+
/** 校验状态(红 / 黄;与输入框同一词表) */
|
|
16
|
+
status: {
|
|
17
|
+
type: PropType<InputStatus>;
|
|
18
|
+
default: undefined;
|
|
19
|
+
};
|
|
20
|
+
disabled: {
|
|
21
|
+
type: BooleanConstructor;
|
|
22
|
+
default: boolean;
|
|
23
|
+
};
|
|
24
|
+
clearable: {
|
|
25
|
+
type: BooleanConstructor;
|
|
26
|
+
default: boolean;
|
|
27
|
+
};
|
|
28
|
+
/** 显式判定清除位显隐(多选的值是数组、单选显示的是 label,都没有「一个字符串模型」可喂) */
|
|
29
|
+
hasValue: {
|
|
30
|
+
type: BooleanConstructor;
|
|
31
|
+
default: boolean;
|
|
32
|
+
};
|
|
33
|
+
/** 多选形态(控件上多一个 `mh-select--multiple`,标签不换行) */
|
|
34
|
+
multiple: {
|
|
35
|
+
type: BooleanConstructor;
|
|
36
|
+
default: boolean;
|
|
37
|
+
};
|
|
38
|
+
placeholder: {
|
|
39
|
+
type: StringConstructor;
|
|
40
|
+
default: string;
|
|
41
|
+
};
|
|
42
|
+
/** 单选显示文本(无值时不渲染它,改渲染 `placeholder`) */
|
|
43
|
+
label: {
|
|
44
|
+
type: StringConstructor;
|
|
45
|
+
default: undefined;
|
|
46
|
+
};
|
|
47
|
+
/** 触发器上的键盘契约(`createTriggerKeydown()` 产出,两件同源) */
|
|
48
|
+
onKeydown: {
|
|
49
|
+
type: PropType<(event: KeyboardEvent) => void>;
|
|
50
|
+
default: undefined;
|
|
51
|
+
};
|
|
52
|
+
/** 清除位被点击(输入面的内建清除位回调;本件只把它转给消费方) */
|
|
53
|
+
onClear: {
|
|
54
|
+
type: PropType<() => void>;
|
|
55
|
+
default: undefined;
|
|
56
|
+
};
|
|
57
|
+
/** 值区自定义渲染(多选标签收纳用;不给则走单选那一行) */
|
|
58
|
+
renderValue: {
|
|
59
|
+
type: PropType<() => VNodeChild>;
|
|
60
|
+
default: undefined;
|
|
61
|
+
};
|
|
62
|
+
}>, () => import("vue/jsx-runtime").JSX.Element, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
63
|
+
/** 面板 id(`popovertarget` 的目标,由 `createSelectPanelId()` 产出的全页唯一 id) */
|
|
64
|
+
panelId: {
|
|
65
|
+
type: StringConstructor;
|
|
66
|
+
required: true;
|
|
67
|
+
};
|
|
68
|
+
/** 尺寸档(24 / 28 / 32):框高与字号归输入面,本件只透传 */
|
|
69
|
+
size: {
|
|
70
|
+
type: PropType<ControlSize>;
|
|
71
|
+
default: string;
|
|
72
|
+
};
|
|
73
|
+
/** 校验状态(红 / 黄;与输入框同一词表) */
|
|
74
|
+
status: {
|
|
75
|
+
type: PropType<InputStatus>;
|
|
76
|
+
default: undefined;
|
|
77
|
+
};
|
|
78
|
+
disabled: {
|
|
79
|
+
type: BooleanConstructor;
|
|
80
|
+
default: boolean;
|
|
81
|
+
};
|
|
82
|
+
clearable: {
|
|
83
|
+
type: BooleanConstructor;
|
|
84
|
+
default: boolean;
|
|
85
|
+
};
|
|
86
|
+
/** 显式判定清除位显隐(多选的值是数组、单选显示的是 label,都没有「一个字符串模型」可喂) */
|
|
87
|
+
hasValue: {
|
|
88
|
+
type: BooleanConstructor;
|
|
89
|
+
default: boolean;
|
|
90
|
+
};
|
|
91
|
+
/** 多选形态(控件上多一个 `mh-select--multiple`,标签不换行) */
|
|
92
|
+
multiple: {
|
|
93
|
+
type: BooleanConstructor;
|
|
94
|
+
default: boolean;
|
|
95
|
+
};
|
|
96
|
+
placeholder: {
|
|
97
|
+
type: StringConstructor;
|
|
98
|
+
default: string;
|
|
99
|
+
};
|
|
100
|
+
/** 单选显示文本(无值时不渲染它,改渲染 `placeholder`) */
|
|
101
|
+
label: {
|
|
102
|
+
type: StringConstructor;
|
|
103
|
+
default: undefined;
|
|
104
|
+
};
|
|
105
|
+
/** 触发器上的键盘契约(`createTriggerKeydown()` 产出,两件同源) */
|
|
106
|
+
onKeydown: {
|
|
107
|
+
type: PropType<(event: KeyboardEvent) => void>;
|
|
108
|
+
default: undefined;
|
|
109
|
+
};
|
|
110
|
+
/** 清除位被点击(输入面的内建清除位回调;本件只把它转给消费方) */
|
|
111
|
+
onClear: {
|
|
112
|
+
type: PropType<() => void>;
|
|
113
|
+
default: undefined;
|
|
114
|
+
};
|
|
115
|
+
/** 值区自定义渲染(多选标签收纳用;不给则走单选那一行) */
|
|
116
|
+
renderValue: {
|
|
117
|
+
type: PropType<() => VNodeChild>;
|
|
118
|
+
default: undefined;
|
|
119
|
+
};
|
|
120
|
+
}>> & Readonly<{}>, {
|
|
121
|
+
size: ControlSize;
|
|
122
|
+
multiple: boolean;
|
|
123
|
+
disabled: boolean;
|
|
124
|
+
label: string;
|
|
125
|
+
onKeydown: (event: KeyboardEvent) => void;
|
|
126
|
+
placeholder: string;
|
|
127
|
+
status: InputStatus;
|
|
128
|
+
clearable: boolean;
|
|
129
|
+
hasValue: boolean;
|
|
130
|
+
onClear: () => void;
|
|
131
|
+
renderValue: () => VNodeChild;
|
|
132
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { defineComponent, createVNode } from "vue";
|
|
2
|
+
import { Icon } from "@manohub/icon";
|
|
3
|
+
import { Input } from "../input/index.js";
|
|
4
|
+
const SelectTrigger = /* @__PURE__ */ defineComponent({
|
|
5
|
+
name: "SelectTrigger",
|
|
6
|
+
inheritAttrs: false,
|
|
7
|
+
props: {
|
|
8
|
+
/** 面板 id(`popovertarget` 的目标,由 `createSelectPanelId()` 产出的全页唯一 id) */
|
|
9
|
+
panelId: {
|
|
10
|
+
type: String,
|
|
11
|
+
required: true
|
|
12
|
+
},
|
|
13
|
+
/** 尺寸档(24 / 28 / 32):框高与字号归输入面,本件只透传 */
|
|
14
|
+
size: {
|
|
15
|
+
type: String,
|
|
16
|
+
default: "md"
|
|
17
|
+
},
|
|
18
|
+
/** 校验状态(红 / 黄;与输入框同一词表) */
|
|
19
|
+
status: {
|
|
20
|
+
type: String,
|
|
21
|
+
default: void 0
|
|
22
|
+
},
|
|
23
|
+
disabled: {
|
|
24
|
+
type: Boolean,
|
|
25
|
+
default: false
|
|
26
|
+
},
|
|
27
|
+
clearable: {
|
|
28
|
+
type: Boolean,
|
|
29
|
+
default: false
|
|
30
|
+
},
|
|
31
|
+
/** 显式判定清除位显隐(多选的值是数组、单选显示的是 label,都没有「一个字符串模型」可喂) */
|
|
32
|
+
hasValue: {
|
|
33
|
+
type: Boolean,
|
|
34
|
+
default: false
|
|
35
|
+
},
|
|
36
|
+
/** 多选形态(控件上多一个 `mh-select--multiple`,标签不换行) */
|
|
37
|
+
multiple: {
|
|
38
|
+
type: Boolean,
|
|
39
|
+
default: false
|
|
40
|
+
},
|
|
41
|
+
placeholder: {
|
|
42
|
+
type: String,
|
|
43
|
+
default: "请选择"
|
|
44
|
+
},
|
|
45
|
+
/** 单选显示文本(无值时不渲染它,改渲染 `placeholder`) */
|
|
46
|
+
label: {
|
|
47
|
+
type: String,
|
|
48
|
+
default: void 0
|
|
49
|
+
},
|
|
50
|
+
/** 触发器上的键盘契约(`createTriggerKeydown()` 产出,两件同源) */
|
|
51
|
+
onKeydown: {
|
|
52
|
+
type: Function,
|
|
53
|
+
default: void 0
|
|
54
|
+
},
|
|
55
|
+
/** 清除位被点击(输入面的内建清除位回调;本件只把它转给消费方) */
|
|
56
|
+
onClear: {
|
|
57
|
+
type: Function,
|
|
58
|
+
default: void 0
|
|
59
|
+
},
|
|
60
|
+
/** 值区自定义渲染(多选标签收纳用;不给则走单选那一行) */
|
|
61
|
+
renderValue: {
|
|
62
|
+
type: Function,
|
|
63
|
+
default: void 0
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
setup(props) {
|
|
67
|
+
const controlClass = () => ["mh-select", props.multiple ? "mh-select--multiple" : "", props.size === "md" ? "" : `mh-select--${props.size}`].filter(Boolean).join(" ");
|
|
68
|
+
return () => createVNode(Input, {
|
|
69
|
+
"as": "button",
|
|
70
|
+
"size": props.size,
|
|
71
|
+
"status": props.status,
|
|
72
|
+
"disabled": props.disabled,
|
|
73
|
+
"clearable": props.clearable,
|
|
74
|
+
"hasValue": props.hasValue,
|
|
75
|
+
"class": "mh-popup-anchor mh-select-host",
|
|
76
|
+
"controlClass": controlClass(),
|
|
77
|
+
"controlAttrs": {
|
|
78
|
+
popovertarget: props.panelId,
|
|
79
|
+
"aria-haspopup": "listbox",
|
|
80
|
+
/* 开合态由原生 popover 与 popup.css 的箭头转向承担,本件不维护第二份;
|
|
81
|
+
恒定 'false' 是既有口径(输入面的触发器形态不跟踪 popover 的开关状态) */
|
|
82
|
+
"aria-expanded": "false",
|
|
83
|
+
onKeydown: props.onKeydown
|
|
84
|
+
},
|
|
85
|
+
"onChange": () => props.onClear?.()
|
|
86
|
+
}, {
|
|
87
|
+
default: () => props.renderValue ? props.renderValue() : createVNode("span", {
|
|
88
|
+
"class": ["mh-select__value", props.hasValue ? "" : "mh-select__value--placeholder"].filter(Boolean).join(" ")
|
|
89
|
+
}, [props.hasValue ? props.label : props.placeholder]),
|
|
90
|
+
/* 箭头沿用输入框的右侧图标位(槽内尺寸收敛见 `select.css` 的 `.mh-select-host` 一节);
|
|
91
|
+
展开转向由 `popup.css` 的 `.mh-popup-caret` 承担(含触发器进表壳后的兄弟档) */
|
|
92
|
+
suffix: () => createVNode(Icon, {
|
|
93
|
+
"name": "chevron-down",
|
|
94
|
+
"class": "mh-select__caret mh-popup-caret"
|
|
95
|
+
}, null)
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
export {
|
|
100
|
+
SelectTrigger
|
|
101
|
+
};
|
|
@@ -37,7 +37,7 @@ export type CanJump = (index: number, current: number) => boolean;
|
|
|
37
37
|
*
|
|
38
38
|
* ### 「哪几步能跳」由调用方给(`canJump`)
|
|
39
39
|
*
|
|
40
|
-
* 本件**不猜业务规则**:`canJump` 缺省 `true
|
|
40
|
+
* 本件**不猜业务规则**:`canJump` 缺省 `true`(每一步都可跳),要限制就注入一条判定:
|
|
41
41
|
*
|
|
42
42
|
* ```tsx
|
|
43
43
|
* <Steps clickable :can-jump="(index, current) => index <= current" … />
|
|
@@ -72,13 +72,34 @@ export interface TablePagination {
|
|
|
72
72
|
* 组件自己切片会让「服务端分页」与「客户端分页」两套口径藏在同一个 prop 里;
|
|
73
73
|
* - **错误态**:归 `QueryState`(表格只管列数据)。
|
|
74
74
|
*
|
|
75
|
-
* @example
|
|
75
|
+
* @example 基本列表(表默认满宽;列宽给权重 + `fixed` 才严格按比例,长文本列配 `ellipsis`)
|
|
76
76
|
* <Table
|
|
77
|
-
*
|
|
78
|
-
* :
|
|
77
|
+
* layout="fixed" striped row-key="id" :data="rows"
|
|
78
|
+
* :columns="[
|
|
79
|
+
* { key: 'name', title: '名称', width: 3 },
|
|
80
|
+
* { key: 'amount', title: '金额', width: 1, align: 'right' },
|
|
81
|
+
* { key: 'description', title: '描述', width: 4, ellipsis: true },
|
|
82
|
+
* ]"
|
|
79
83
|
* :pagination="{ total: 120, current: page, onChange: (p) => (page = p) }"
|
|
80
|
-
* @sort="({ key, order }) => (sortKey = key, sortOrder = order)"
|
|
81
84
|
* />
|
|
85
|
+
*
|
|
86
|
+
* @example 操作列(用 `actions` 插槽 + 一列 `align:'right'`;行内操作同族,`Button.Group` 收口)
|
|
87
|
+
* <Table :columns="columns" :data="rows" row-key="id">
|
|
88
|
+
* {{
|
|
89
|
+
* actions: ({ row }) => (
|
|
90
|
+
* <Button.Group>
|
|
91
|
+
* <Button variant="text" onClick={() => open(row)}>编辑</Button>
|
|
92
|
+
* <Button variant="text" danger onClick={() => remove(row)}>删除</Button>
|
|
93
|
+
* </Button.Group>
|
|
94
|
+
* ),
|
|
95
|
+
* }}
|
|
96
|
+
* </Table>
|
|
97
|
+
*
|
|
98
|
+
* @example 单元格自定义 + 按行判定可勾选
|
|
99
|
+
* <Table :columns="columns" :data="rows" row-key="id" v-model="selected"
|
|
100
|
+
* :selectable="(row) => row.status === 'draft'">
|
|
101
|
+
* {{ cell: ({ value, column }) => column.key === 'status' ? <Text tone="success">{value}</Text> : value }}
|
|
102
|
+
* </Table>
|
|
82
103
|
*/
|
|
83
104
|
export declare const Table: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
84
105
|
columns: {
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { type PropType, type SlotsType, type VNodeChild } from 'vue';
|
|
2
2
|
/** 字号档(每档都有一枚配套的行高令牌,故**不开放**只有字号的 micro / display) */
|
|
3
3
|
export type TextSize = 'caption' | 'body' | 'heading' | 'title';
|
|
4
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* 语义色档(`success` / `warning` 取 `-strong`,与 `Notice` / `Dialog` 同一张表)。
|
|
6
|
+
*
|
|
7
|
+
* ⚠️ 自绘样式时要字色,一律落 `--ui-<tone>` / `--ui-<tone>-strong`(浅底深字)。
|
|
8
|
+
* `--ui-*-content` 那一族是**实心填充之上的白字**,配浅底(`-weak`)就是白字白底。
|
|
9
|
+
*/
|
|
5
10
|
export type TextTone = 'default' | 'muted' | 'subtle' | 'primary' | 'success' | 'warning' | 'error';
|
|
6
11
|
/** 字重档(缺省**不设**,即继承上下文 —— 这样「在已加粗的区域里放一段普通文字」不必显式写 regular) */
|
|
7
12
|
export type TextWeight = 'regular' | 'medium' | 'semibold';
|
|
@@ -30,7 +35,7 @@ export interface TextProps {
|
|
|
30
35
|
* 但**没有件消费它们**:卡片描述、表单说明、只读值、列表次级信息这些「容器里的文字层级」
|
|
31
36
|
* 于是无处可归 —— `Card` 只管卡片外壳,`Panel` 只管区域,两者都不管内容里的排版;
|
|
32
37
|
* 而契约 L1-5 又禁应用侧写 `font-*` / `color`。**令牌有了、路径没有**,
|
|
33
|
-
*
|
|
38
|
+
* 结果就是每个应用都要为「卡片里的文字」保留一条定版例外登记(首批迁移的登记项里,
|
|
34
39
|
* 绝大多数属于这一类)。本件把这条路径补上。
|
|
35
40
|
*
|
|
36
41
|
* ## 与 `Title` / 平台默认的关系
|
|
@@ -23,8 +23,7 @@ export interface TreeNode {
|
|
|
23
23
|
* ## 为什么自建(对照计划登记的退路)
|
|
24
24
|
*
|
|
25
25
|
* farris `FTreeView` 实测结论:① 是列模板式网格(节点内容可用 `columnTemplate` 承载);
|
|
26
|
-
* ② **只在挂载时填充一次可见行,data
|
|
27
|
-
* `apps/skill/src/features/skill/farris-file-tree.tsx` 正是用 `key` 强制重挂绕行);
|
|
26
|
+
* ② **只在挂载时填充一次可见行,data 变化不刷新**(**唯一**的兜底是 `key` 强制重挂 —— 那会丢展开态);
|
|
28
27
|
* ③ 层级缩进不可控、节点图标走 farris 图标字体类。
|
|
29
28
|
*
|
|
30
29
|
* 而本组件的硬需求是「数据更新时**不丢**用户已展开的节点 / 滚动位置 / 焦点」——
|
|
@@ -80,6 +79,19 @@ export declare const Tree: import("vue").DefineComponent<import("vue").ExtractPr
|
|
|
80
79
|
type: PropType<(node: TreeNode) => void>;
|
|
81
80
|
default: undefined;
|
|
82
81
|
};
|
|
82
|
+
/**
|
|
83
|
+
* 标签体的自定义渲染(**替掉** `labelKey` 的纯文本)。
|
|
84
|
+
*
|
|
85
|
+
* 与 `renderNode` 的分工:本 prop 渲染的是**标签本体**(占位、省略号、行内对齐都由 `.mh-tree__label`
|
|
86
|
+
* 承担,因此必须留在那个槽里);`renderNode` 渲染的是标签**之后**的常驻附加内容(计数徽标一类)。
|
|
87
|
+
* 典型用途是搜索命中的文本高亮(把标签切成多个片段)。
|
|
88
|
+
*
|
|
89
|
+
* 缺省不传时仍渲染 `labelKey` 的纯文本 —— 这是绝大多数消费方的路径,行为零变化。
|
|
90
|
+
*/
|
|
91
|
+
renderLabel: {
|
|
92
|
+
type: PropType<(node: TreeNode) => unknown>;
|
|
93
|
+
default: undefined;
|
|
94
|
+
};
|
|
83
95
|
/** 节点附加内容(计数徽标等),渲染在标签之后 */
|
|
84
96
|
renderNode: {
|
|
85
97
|
type: PropType<(node: TreeNode) => unknown>;
|
|
@@ -200,6 +212,19 @@ export declare const Tree: import("vue").DefineComponent<import("vue").ExtractPr
|
|
|
200
212
|
type: PropType<(node: TreeNode) => void>;
|
|
201
213
|
default: undefined;
|
|
202
214
|
};
|
|
215
|
+
/**
|
|
216
|
+
* 标签体的自定义渲染(**替掉** `labelKey` 的纯文本)。
|
|
217
|
+
*
|
|
218
|
+
* 与 `renderNode` 的分工:本 prop 渲染的是**标签本体**(占位、省略号、行内对齐都由 `.mh-tree__label`
|
|
219
|
+
* 承担,因此必须留在那个槽里);`renderNode` 渲染的是标签**之后**的常驻附加内容(计数徽标一类)。
|
|
220
|
+
* 典型用途是搜索命中的文本高亮(把标签切成多个片段)。
|
|
221
|
+
*
|
|
222
|
+
* 缺省不传时仍渲染 `labelKey` 的纯文本 —— 这是绝大多数消费方的路径,行为零变化。
|
|
223
|
+
*/
|
|
224
|
+
renderLabel: {
|
|
225
|
+
type: PropType<(node: TreeNode) => unknown>;
|
|
226
|
+
default: undefined;
|
|
227
|
+
};
|
|
203
228
|
/** 节点附加内容(计数徽标等),渲染在标签之后 */
|
|
204
229
|
renderNode: {
|
|
205
230
|
type: PropType<(node: TreeNode) => unknown>;
|
|
@@ -305,6 +330,7 @@ export declare const Tree: import("vue").DefineComponent<import("vue").ExtractPr
|
|
|
305
330
|
onErrorAction: () => void;
|
|
306
331
|
labelKey: string;
|
|
307
332
|
childrenKey: string;
|
|
333
|
+
renderLabel: (node: TreeNode) => unknown;
|
|
308
334
|
renderNode: (node: TreeNode) => unknown;
|
|
309
335
|
renderActions: (node: TreeNode) => unknown;
|
|
310
336
|
expandedKeys: string[];
|
|
@@ -33,6 +33,19 @@ const Tree = /* @__PURE__ */ defineComponent({
|
|
|
33
33
|
type: Function,
|
|
34
34
|
default: void 0
|
|
35
35
|
},
|
|
36
|
+
/**
|
|
37
|
+
* 标签体的自定义渲染(**替掉** `labelKey` 的纯文本)。
|
|
38
|
+
*
|
|
39
|
+
* 与 `renderNode` 的分工:本 prop 渲染的是**标签本体**(占位、省略号、行内对齐都由 `.mh-tree__label`
|
|
40
|
+
* 承担,因此必须留在那个槽里);`renderNode` 渲染的是标签**之后**的常驻附加内容(计数徽标一类)。
|
|
41
|
+
* 典型用途是搜索命中的文本高亮(把标签切成多个片段)。
|
|
42
|
+
*
|
|
43
|
+
* 缺省不传时仍渲染 `labelKey` 的纯文本 —— 这是绝大多数消费方的路径,行为零变化。
|
|
44
|
+
*/
|
|
45
|
+
renderLabel: {
|
|
46
|
+
type: Function,
|
|
47
|
+
default: void 0
|
|
48
|
+
},
|
|
36
49
|
/** 节点附加内容(计数徽标等),渲染在标签之后 */
|
|
37
50
|
renderNode: {
|
|
38
51
|
type: Function,
|
|
@@ -231,7 +244,7 @@ const Tree = /* @__PURE__ */ defineComponent({
|
|
|
231
244
|
"class": "mh-tree__caret mh-tree__caret--empty"
|
|
232
245
|
}, null), createVNode("span", {
|
|
233
246
|
"class": "mh-tree__label"
|
|
234
|
-
}, [String(node[props.labelKey] ?? "")]), props.renderNode ? createVNode("span", {
|
|
247
|
+
}, [props.renderLabel ? props.renderLabel(node) : String(node[props.labelKey] ?? "")]), props.renderNode ? createVNode("span", {
|
|
235
248
|
"class": "mh-tree__extra"
|
|
236
249
|
}, [props.renderNode(node)]) : null, props.renderActions ? createVNode("span", {
|
|
237
250
|
"class": "mh-tree__actions",
|
package/dist/index.d.ts
CHANGED
|
@@ -34,7 +34,7 @@ export { Badge, type BadgeTone, type BadgeShape, type BadgeSize, type BadgeDot,
|
|
|
34
34
|
export { Button, type ButtonVariant, type ButtonCompound, type ButtonCompoundType, } from './components/button';
|
|
35
35
|
export { Input, Textarea, type TextareaProps, type InputCompound, type InputCompoundType, } from './components/input';
|
|
36
36
|
export { Search, type SearchProps } from './components/search';
|
|
37
|
-
export { Select, type SelectOption, type SelectValue } from './components/select';
|
|
37
|
+
export { Select, SelectTree, type SelectOption, type SelectValue } from './components/select';
|
|
38
38
|
export { Switch } from './components/switch';
|
|
39
39
|
export { Capsule, type CapsuleOption, type CapsuleShape } from './components/capsule';
|
|
40
40
|
export { Form, type FormProps, type FormHeaderProps, type FormItemProps, type FormValues, type FormItemSlotScope, type FormItemAlign, type FormLabelAlign, type FormColumns, type FormPadding, type FormCompound, } from './components/form';
|
package/dist/index.js
CHANGED
|
@@ -39,6 +39,7 @@ import { Upload, formatFileSize } from "./components/upload/index.js";
|
|
|
39
39
|
import { QueryState } from "./components/query-state/index.js";
|
|
40
40
|
import { Loading } from "./components/loading/index.js";
|
|
41
41
|
import { mergePartStyle, partPaddingStyle } from "./components/part-padding.js";
|
|
42
|
+
import { SelectTree } from "./components/select/select-tree.js";
|
|
42
43
|
import { alert, closeAllDialogs, confirm } from "./services/dialog.js";
|
|
43
44
|
import { clearToasts, toast } from "./services/toast.js";
|
|
44
45
|
import { configureHost, getHostSelector, resolveHost } from "./services/base.js";
|
|
@@ -74,6 +75,7 @@ export {
|
|
|
74
75
|
Radio,
|
|
75
76
|
Search,
|
|
76
77
|
Select,
|
|
78
|
+
SelectTree,
|
|
77
79
|
Skeleton,
|
|
78
80
|
Steps,
|
|
79
81
|
Switch,
|