@manohub/ui 0.10.2 → 0.10.3
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/badge/index.d.ts +46 -1
- package/dist/components/badge/index.js +42 -9
- package/dist/components/card/index.d.ts +1 -1
- package/dist/components/drawer/index.d.ts +1 -1
- package/dist/components/form/index.d.ts +50 -1
- package/dist/components/form/index.js +20 -2
- package/dist/components/input/index.js +1 -0
- package/dist/components/nav/index.d.ts +27 -0
- package/dist/components/nav/index.js +12 -0
- package/dist/components/notice/index.d.ts +1 -1
- package/dist/components/notification/index.d.ts +2 -2
- package/dist/components/select/index.d.ts +9 -0
- package/dist/components/select/index.js +57 -12
- package/dist/components/select/select-list.d.ts +42 -8
- package/dist/components/select/select-list.js +36 -11
- package/dist/components/select/select-panel.d.ts +36 -8
- package/dist/components/select/select-panel.js +18 -4
- package/dist/components/select/select-shared.d.ts +51 -10
- package/dist/components/select/select-shared.js +12 -2
- package/dist/components/select/select-tree.d.ts +25 -3
- package/dist/components/select/select-tree.js +17 -6
- package/dist/components/select/select-trigger.d.ts +21 -0
- package/dist/components/select/select-trigger.js +15 -1
- package/dist/components/tag/index.d.ts +32 -153
- package/dist/components/tag/index.js +102 -101
- package/dist/components/text/index.d.ts +1 -1
- package/dist/components/tooltip/index.d.ts +1 -1
- package/dist/components/tree/index.d.ts +65 -0
- package/dist/components/tree/index.js +29 -2
- package/dist/index.d.ts +1 -1
- package/dist/styles/components/avatar.css +2 -2
- package/dist/styles/components/badge.css +21 -1
- package/dist/styles/components/badge.tokens.css +24 -0
- package/dist/styles/components/button.css +7 -6
- package/dist/styles/components/button.tokens.css +5 -2
- package/dist/styles/components/divider.css +8 -4
- package/dist/styles/components/divider.tokens.css +2 -1
- package/dist/styles/components/form.css +51 -0
- package/dist/styles/components/notification.css +1 -1
- package/dist/styles/components/select.css +62 -39
- package/dist/styles/components/select.tokens.css +15 -8
- package/dist/styles/components/tag.css +77 -154
- package/dist/styles/components/tag.tokens.css +13 -22
- package/dist/styles/components/tree.css +37 -3
- package/dist/styles/components/tree.tokens.css +4 -3
- package/dist/styles/components/upload.tokens.css +1 -1
- package/dist/styles/index.css +2 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -62,7 +62,7 @@ import { Card, Form, Tree, Checkbox } from '@manohub/ui'
|
|
|
62
62
|
| 分组 | 组件 |
|
|
63
63
|
|---|---|
|
|
64
64
|
| 容器与分区 | `Page`(页面级骨架:`.Header` / `.Body` / `.Footer` / `.Filter` / `.Toolbar` / `.Split`)、`Panel`(+ `.Header` / `.Body` / `.Footer`)、`Card`(+ 三件)、`Nav`、`Layout`(`Layout.Row` 排「行」/ `Layout.Column` 排「列」:轴向 × 间距档 × 对齐) |
|
|
65
|
-
| 数据展示 | `Tree`、`ListView`(+ `.CardItem`)、`Badge`、`Tag
|
|
65
|
+
| 数据展示 | `Tree`、`ListView`(+ `.CardItem`)、`Badge`、`Tag`(`Badge` 的可关闭 / 可新增封装) |
|
|
66
66
|
| 展示 | `Avatar`(+ `.Group`)、`Skeleton`(行 / 列表 / 块状) |
|
|
67
67
|
| 表单基础(**自绘**) | `Button`(+ `.Group` / `.Divider`;五型 / 小号 / 加载 / 菜单按钮 / 连体切换)、`Input`(+ `.Group` / `.Chip` / `.Display` / `.Textarea`;清除 / 可见性切换 / 校验)、`Search`(搜索框:值 / 搜索 / 清空三条回调)、`Select`(单选 / 多选 / 搜索 / 加载;面板用原生 popover)、`SelectTree`(树形态:仅叶子可选 + 本地过滤 + 命中高亮;与 `Select` 平级导出、共用触发器与面板)、`Switch`(表格档 / 表单档) |
|
|
68
68
|
| 表单与选择 | `Form`(+ `.Header` / `.Item`;标签体系、成员归位、`v-model` 值收集与命令式 `clear()`)、`Filter`(+ `.Item`;`Form` 的壳:条件字段写成子件、控件自放、查询/重置与快照归一)、`Checkbox`、`Radio`(+ `.Group` / `.Card`;卡片式单选)、`Capsule`(胶囊选择器;胶囊 / 方形两档外形,与 `Tabs` 组合即页签) |
|
|
@@ -19,6 +19,13 @@ export type BadgeStatus = 'success' | 'processing' | 'default' | 'warning' | 'er
|
|
|
19
19
|
* - `outline`:浅底 + 同色描边;`tone="default"` 时退化为白底 + 中性描边 + 正文色(状态胶囊)
|
|
20
20
|
* - `plain`:无底无框,靠圆点表达状态
|
|
21
21
|
*
|
|
22
|
+
* **本件是「只读标记」的唯一样式源**:`Tag`(可关闭 / 可新增)只是在本件之上加交互位,
|
|
23
|
+
* 几何与色彩一行都不重写 —— 改本件的档位,两件一起走(见 `components/tag/index.tsx`)。
|
|
24
|
+
*
|
|
25
|
+
* **属性落点**:`class` / `style` 有专口(并入档位类、随档位内联),**其余 attrs 一律透传到根元素**
|
|
26
|
+
* (`id` / `aria-*` / `data-*` / `title` / 原生事件)—— 停在组件上等于丢掉(`Tag` 的「新增」
|
|
27
|
+
* 就靠这条把 `onClick` 落到那颗按钮上),见 `forwardAttrs`。
|
|
28
|
+
*
|
|
22
29
|
* @example 内容徽标
|
|
23
30
|
* <Badge tone="success" shape="outline" size="md" dot="filled">运行中</Badge>
|
|
24
31
|
*
|
|
@@ -28,6 +35,9 @@ export type BadgeStatus = 'success' | 'processing' | 'default' | 'warning' | 'er
|
|
|
28
35
|
*
|
|
29
36
|
* @example 状态点
|
|
30
37
|
* <Badge status="processing" text="运行中" />
|
|
38
|
+
*
|
|
39
|
+
* @example 可点形态(复合件用;`Tag` 的「新增」即此形 + 本件自己的遮罩)
|
|
40
|
+
* <Badge as="button" tone="warning" onClick={() => add()}>新增</Badge>
|
|
31
41
|
*/
|
|
32
42
|
export declare const Badge: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
33
43
|
tone: {
|
|
@@ -73,6 +83,23 @@ export declare const Badge: import("vue").DefineComponent<import("vue").ExtractP
|
|
|
73
83
|
type: StringConstructor;
|
|
74
84
|
default: undefined;
|
|
75
85
|
};
|
|
86
|
+
/**
|
|
87
|
+
* 根元素形态:`span`(缺省,纯展示)/ `button`(可点形态,给**复合件**用)。
|
|
88
|
+
*
|
|
89
|
+
* `Tag` 就是靠它拿到「可点」的:徽标是**只读标记**的唯一样式源,`Tag` 只往上加
|
|
90
|
+
* 关闭位与新增位,不再自己画一遍框(见 `components/tag/index.tsx`)。
|
|
91
|
+
* `as="button"` 会渲染真实 `<button type="button">`(`type` 不能省:缺省是 `submit`,
|
|
92
|
+
* 放进表单会提交),并保留本件全部档位类与几何 —— 按钮的外观缺三项由本件面自带
|
|
93
|
+
* (`border` / `background` / `padding`,见 `contract-reset.spec.ts` 的第三组),
|
|
94
|
+
* `font-family: inherit` 在 `badge.css` 的基块补(UA 会把按钮字形换成 Arial)。
|
|
95
|
+
*
|
|
96
|
+
* **只对「内容徽标」形态生效**:`count` / `status` 两种形态是装饰(角标、状态点 + 文字),
|
|
97
|
+
* 传了它们就忽略本 prop(那时走各自的分支,根仍是 `<span>`)。
|
|
98
|
+
*/
|
|
99
|
+
as: {
|
|
100
|
+
type: PropType<"span" | "button">;
|
|
101
|
+
default: string;
|
|
102
|
+
};
|
|
76
103
|
}>, () => import("vue/jsx-runtime").JSX.Element | null, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
77
104
|
tone: {
|
|
78
105
|
type: PropType<BadgeTone>;
|
|
@@ -117,16 +144,34 @@ export declare const Badge: import("vue").DefineComponent<import("vue").ExtractP
|
|
|
117
144
|
type: StringConstructor;
|
|
118
145
|
default: undefined;
|
|
119
146
|
};
|
|
147
|
+
/**
|
|
148
|
+
* 根元素形态:`span`(缺省,纯展示)/ `button`(可点形态,给**复合件**用)。
|
|
149
|
+
*
|
|
150
|
+
* `Tag` 就是靠它拿到「可点」的:徽标是**只读标记**的唯一样式源,`Tag` 只往上加
|
|
151
|
+
* 关闭位与新增位,不再自己画一遍框(见 `components/tag/index.tsx`)。
|
|
152
|
+
* `as="button"` 会渲染真实 `<button type="button">`(`type` 不能省:缺省是 `submit`,
|
|
153
|
+
* 放进表单会提交),并保留本件全部档位类与几何 —— 按钮的外观缺三项由本件面自带
|
|
154
|
+
* (`border` / `background` / `padding`,见 `contract-reset.spec.ts` 的第三组),
|
|
155
|
+
* `font-family: inherit` 在 `badge.css` 的基块补(UA 会把按钮字形换成 Arial)。
|
|
156
|
+
*
|
|
157
|
+
* **只对「内容徽标」形态生效**:`count` / `status` 两种形态是装饰(角标、状态点 + 文字),
|
|
158
|
+
* 传了它们就忽略本 prop(那时走各自的分支,根仍是 `<span>`)。
|
|
159
|
+
*/
|
|
160
|
+
as: {
|
|
161
|
+
type: PropType<"span" | "button">;
|
|
162
|
+
default: string;
|
|
163
|
+
};
|
|
120
164
|
}>> & Readonly<{}>, {
|
|
121
165
|
text: string;
|
|
122
166
|
size: BadgeSize;
|
|
123
167
|
dot: boolean | BadgeDot;
|
|
124
168
|
shape: BadgeShape;
|
|
125
169
|
count: number;
|
|
126
|
-
status: BadgeStatus;
|
|
127
170
|
tone: BadgeTone;
|
|
128
171
|
overflowCount: number;
|
|
129
172
|
showZero: boolean;
|
|
173
|
+
status: BadgeStatus;
|
|
174
|
+
as: "button" | "span";
|
|
130
175
|
}, SlotsType<{
|
|
131
176
|
default?: () => VNodeChild;
|
|
132
177
|
}>, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineComponent, computed, createVNode } from "vue";
|
|
1
|
+
import { defineComponent, computed, createVNode, mergeProps, Fragment } from "vue";
|
|
2
2
|
const STATUS_TONE = {
|
|
3
3
|
success: "success",
|
|
4
4
|
processing: "primary",
|
|
@@ -52,6 +52,23 @@ const Badge = /* @__PURE__ */ defineComponent({
|
|
|
52
52
|
text: {
|
|
53
53
|
type: String,
|
|
54
54
|
default: void 0
|
|
55
|
+
},
|
|
56
|
+
/**
|
|
57
|
+
* 根元素形态:`span`(缺省,纯展示)/ `button`(可点形态,给**复合件**用)。
|
|
58
|
+
*
|
|
59
|
+
* `Tag` 就是靠它拿到「可点」的:徽标是**只读标记**的唯一样式源,`Tag` 只往上加
|
|
60
|
+
* 关闭位与新增位,不再自己画一遍框(见 `components/tag/index.tsx`)。
|
|
61
|
+
* `as="button"` 会渲染真实 `<button type="button">`(`type` 不能省:缺省是 `submit`,
|
|
62
|
+
* 放进表单会提交),并保留本件全部档位类与几何 —— 按钮的外观缺三项由本件面自带
|
|
63
|
+
* (`border` / `background` / `padding`,见 `contract-reset.spec.ts` 的第三组),
|
|
64
|
+
* `font-family: inherit` 在 `badge.css` 的基块补(UA 会把按钮字形换成 Arial)。
|
|
65
|
+
*
|
|
66
|
+
* **只对「内容徽标」形态生效**:`count` / `status` 两种形态是装饰(角标、状态点 + 文字),
|
|
67
|
+
* 传了它们就忽略本 prop(那时走各自的分支,根仍是 `<span>`)。
|
|
68
|
+
*/
|
|
69
|
+
as: {
|
|
70
|
+
type: String,
|
|
71
|
+
default: "span"
|
|
55
72
|
}
|
|
56
73
|
},
|
|
57
74
|
slots: Object,
|
|
@@ -59,6 +76,13 @@ const Badge = /* @__PURE__ */ defineComponent({
|
|
|
59
76
|
slots,
|
|
60
77
|
attrs
|
|
61
78
|
}) {
|
|
79
|
+
const forwardAttrs = computed(() => {
|
|
80
|
+
const rest = {};
|
|
81
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
82
|
+
if (key !== "class" && key !== "style") rest[key] = value;
|
|
83
|
+
}
|
|
84
|
+
return rest;
|
|
85
|
+
});
|
|
62
86
|
const countText = computed(() => {
|
|
63
87
|
if (props.count === void 0) return null;
|
|
64
88
|
if (props.count === 0 && !props.showZero) return null;
|
|
@@ -69,28 +93,37 @@ const Badge = /* @__PURE__ */ defineComponent({
|
|
|
69
93
|
const style = attrs.style;
|
|
70
94
|
const extra = attrs.class;
|
|
71
95
|
if (props.status) {
|
|
72
|
-
return createVNode("span", {
|
|
96
|
+
return createVNode("span", mergeProps(forwardAttrs.value, {
|
|
73
97
|
"class": ["mh-badge--status", `mh-badge--${STATUS_TONE[props.status]}`, extra].filter(Boolean).join(" "),
|
|
74
98
|
"style": style
|
|
75
|
-
}, [createVNode("span", {
|
|
99
|
+
}), [createVNode("span", {
|
|
76
100
|
"class": "mh-badge__status-dot"
|
|
77
101
|
}, null), props.text ?? slots.default?.()]);
|
|
78
102
|
}
|
|
79
103
|
if (props.count !== void 0) {
|
|
80
104
|
if (countText.value === null && !slots.default) return null;
|
|
81
|
-
return createVNode("span", {
|
|
105
|
+
return createVNode("span", mergeProps(forwardAttrs.value, {
|
|
82
106
|
"class": ["mh-badge--count", extra].filter(Boolean).join(" "),
|
|
83
107
|
"style": style
|
|
84
|
-
}, [slots.default?.(), countText.value !== null ? createVNode("sup", {
|
|
108
|
+
}), [slots.default?.(), countText.value !== null ? createVNode("sup", {
|
|
85
109
|
"class": ["mh-badge__count", `mh-badge--${props.tone}`].join(" ")
|
|
86
110
|
}, [countText.value]) : null]);
|
|
87
111
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
"style": style
|
|
91
|
-
}, [dotKind.value ? createVNode("span", {
|
|
112
|
+
const classes = ["mh-badge", `mh-badge--${props.tone}`, `mh-badge--${props.shape}`, `mh-badge--${props.size}`, dotKind.value ? `mh-badge--dot-${dotKind.value}` : "", extra].filter(Boolean).join(" ");
|
|
113
|
+
const content = createVNode(Fragment, null, [dotKind.value ? createVNode("span", {
|
|
92
114
|
"class": "mh-badge__dot"
|
|
93
115
|
}, null) : null, slots.default?.()]);
|
|
116
|
+
if (props.as === "button") {
|
|
117
|
+
return createVNode("button", mergeProps(forwardAttrs.value, {
|
|
118
|
+
"type": "button",
|
|
119
|
+
"class": classes,
|
|
120
|
+
"style": style
|
|
121
|
+
}), [content]);
|
|
122
|
+
}
|
|
123
|
+
return createVNode("span", mergeProps(forwardAttrs.value, {
|
|
124
|
+
"class": classes,
|
|
125
|
+
"style": style
|
|
126
|
+
}), [content]);
|
|
94
127
|
};
|
|
95
128
|
}
|
|
96
129
|
});
|
|
@@ -76,7 +76,7 @@ export type CardHeaderIcon = string | (() => VNodeChild);
|
|
|
76
76
|
* @example 技能卡(图标 + 标题/副标题 + 右端状态 + 正文 + 操作行)
|
|
77
77
|
* <Card>
|
|
78
78
|
* <Card.Header icon={skill.icon} title={skill.name} subtitle={`V${skill.version}`}>
|
|
79
|
-
* <template #extra><
|
|
79
|
+
* <template #extra><Badge shape="soft" tone="success">已发布</Badge></template>
|
|
80
80
|
* </Card.Header>
|
|
81
81
|
* <Card.Body>{skill.description}</Card.Body>
|
|
82
82
|
* <template #actions><Button size="sm">编辑</Button></template>
|
|
@@ -221,6 +221,7 @@ export declare const Drawer: import("vue").DefineComponent<import("vue").Extract
|
|
|
221
221
|
onClose: () => void;
|
|
222
222
|
width: string | number;
|
|
223
223
|
closable: boolean;
|
|
224
|
+
side: DrawerSide;
|
|
224
225
|
closeOnBackdrop: boolean;
|
|
225
226
|
maskClosable: boolean;
|
|
226
227
|
keyboard: boolean;
|
|
@@ -230,7 +231,6 @@ export declare const Drawer: import("vue").DefineComponent<import("vue").Extract
|
|
|
230
231
|
confirmLoading: boolean;
|
|
231
232
|
showFooter: boolean | undefined;
|
|
232
233
|
onOk: () => unknown;
|
|
233
|
-
side: DrawerSide;
|
|
234
234
|
}, SlotsType<{
|
|
235
235
|
default?: () => VNodeChild;
|
|
236
236
|
title?: () => VNodeChild;
|
|
@@ -133,6 +133,21 @@ export interface FormItemProps {
|
|
|
133
133
|
error?: string;
|
|
134
134
|
/** 字段说明文案(常驻,排在错误之后) */
|
|
135
135
|
hint?: string;
|
|
136
|
+
/**
|
|
137
|
+
* **label 旁的提示**(问号图标 + 悬停气泡,仅文本)。
|
|
138
|
+
*
|
|
139
|
+
* 与 `hint` 分工不同:`hint` 是**常驻**在控件下方的说明文案,`tip` 是**按需展开**的补充解释 ——
|
|
140
|
+
* 不占版面,只在悬停(或键盘聚焦)问号图标时出现,两者可以同时给。
|
|
141
|
+
*
|
|
142
|
+
* 图标排在 label 文本与必填星号**之后**(`服务名称*ⓘ`);只给 `tip` 而不给 `label` 时同样保留
|
|
143
|
+
* label 列(与「只给 required」同一判据)—— 否则图标没有列可落、静默消失。
|
|
144
|
+
*
|
|
145
|
+
* 用的是 `Tooltip` 的 **hover 档**(零 JS):显隐与定位全在 CSS,移入 / 聚焦即显、移开即隐,
|
|
146
|
+
* 因此**不吃点击、不抢焦点**。代价一条:hover 档不进浏览器顶层浮层,会被祖先 `overflow` 裁切
|
|
147
|
+
* —— 弹窗 / 抽屉里贴着裁剪边的字段可能看不全(要顶层浮层就得换 `Tooltip trigger="click"`,
|
|
148
|
+
* 本件有意不暴露该维度,见文件头「label 提示」)。
|
|
149
|
+
*/
|
|
150
|
+
tip?: string;
|
|
136
151
|
/** 本行 label 列宽(px);覆盖 `Form` 的表单级设置 */
|
|
137
152
|
labelWidth?: number;
|
|
138
153
|
/** 本行 label 对齐;覆盖 `Form` 的表单级设置 */
|
|
@@ -205,6 +220,18 @@ export interface FormItemProps {
|
|
|
205
220
|
* `{ y, x }` 上下 / 左右分别给,缺省 0(贴容器边)。页面里的表单在 `Panel.Body` 里、内距由那块给;
|
|
206
221
|
* **弹窗 / 抽屉里没有别的容器**给内距,就用表单自带的 `padding={{ y: 16, x: 20 }}`。
|
|
207
222
|
*
|
|
223
|
+
* ## label 提示(`tip`)
|
|
224
|
+
*
|
|
225
|
+
* 字段名(与必填星号)之后挂一枚问号图标,悬停 / 键盘聚焦弹出说明气泡。它是**按需展开**的补充
|
|
226
|
+
* 解释,不占版面;常驻说明仍归 `hint`(排在控件下方)。两者分工不同,也**不互相替代**。
|
|
227
|
+
*
|
|
228
|
+
* - 图标取 `@manohub/icon` 的 `help`(带圈问号),尺寸随主题图标档(`--ui-font-icon`);
|
|
229
|
+
* - 图标**排在星号之后**(`服务名称*ⓘ`)—— 与「星号跟着字段名走」同一口径;
|
|
230
|
+
* - 只给 `tip`(无 `label`、非必填)时**同样保留 label 列**(与「只给 required」同一判据),
|
|
231
|
+
* 否则图标没有列可落、静默消失;
|
|
232
|
+
* - 气泡用 `Tooltip` 的 **hover 档**(零 JS):不吃点击、不抢焦点;代价是 hover 档不进浏览器
|
|
233
|
+
* 顶层浮层,会被祖先 `overflow` 裁切(弹窗 / 抽屉里贴着裁剪边的字段可能看不全)。
|
|
234
|
+
*
|
|
208
235
|
* ## 键盘事件(`onKeydown` 转发到根节点)
|
|
209
236
|
*
|
|
210
237
|
* `onKeydown` 是本件的**声明式入口**(缺省不绑):传进来的处理器绑在根节点上 ——
|
|
@@ -238,7 +265,7 @@ export interface FormItemProps {
|
|
|
238
265
|
* <Form.Item label="编码" required error={errors.code} hint="保存后不可修改">
|
|
239
266
|
* <Input modelValue={form.code} onChange={(v) => (form.code = v)} />
|
|
240
267
|
* </Form.Item>
|
|
241
|
-
* <Form.Item label="缓存策略" controlWidth={360}>
|
|
268
|
+
* <Form.Item label="缓存策略" controlWidth={360} tip="命中即不请求后端">
|
|
242
269
|
* <Select options={cacheOptions} modelValue={form.cache} onChange={…} />
|
|
243
270
|
* </Form.Item>
|
|
244
271
|
* <Form.Item label="过滤条件" fullWidth>
|
|
@@ -268,6 +295,11 @@ export declare const FormItem: import("vue").DefineComponent<import("vue").Extra
|
|
|
268
295
|
type: StringConstructor;
|
|
269
296
|
default: string;
|
|
270
297
|
};
|
|
298
|
+
/** label 旁的提示文案(问号图标 + 悬停气泡);口径见 `FormItemProps.tip` */
|
|
299
|
+
tip: {
|
|
300
|
+
type: StringConstructor;
|
|
301
|
+
default: string;
|
|
302
|
+
};
|
|
271
303
|
labelWidth: {
|
|
272
304
|
type: NumberConstructor;
|
|
273
305
|
default: undefined;
|
|
@@ -316,6 +348,11 @@ export declare const FormItem: import("vue").DefineComponent<import("vue").Extra
|
|
|
316
348
|
type: StringConstructor;
|
|
317
349
|
default: string;
|
|
318
350
|
};
|
|
351
|
+
/** label 旁的提示文案(问号图标 + 悬停气泡);口径见 `FormItemProps.tip` */
|
|
352
|
+
tip: {
|
|
353
|
+
type: StringConstructor;
|
|
354
|
+
default: string;
|
|
355
|
+
};
|
|
319
356
|
labelWidth: {
|
|
320
357
|
type: NumberConstructor;
|
|
321
358
|
default: undefined;
|
|
@@ -350,6 +387,7 @@ export declare const FormItem: import("vue").DefineComponent<import("vue").Extra
|
|
|
350
387
|
align: FormItemAlign;
|
|
351
388
|
readonly: boolean;
|
|
352
389
|
hint: string;
|
|
390
|
+
tip: string;
|
|
353
391
|
labelWidth: number;
|
|
354
392
|
labelAlign: FormLabelAlign;
|
|
355
393
|
controlWidth: number;
|
|
@@ -730,6 +768,11 @@ export declare const Form: {
|
|
|
730
768
|
type: StringConstructor;
|
|
731
769
|
default: string;
|
|
732
770
|
};
|
|
771
|
+
/** label 旁的提示文案(问号图标 + 悬停气泡);口径见 `FormItemProps.tip` */
|
|
772
|
+
tip: {
|
|
773
|
+
type: StringConstructor;
|
|
774
|
+
default: string;
|
|
775
|
+
};
|
|
733
776
|
labelWidth: {
|
|
734
777
|
type: NumberConstructor;
|
|
735
778
|
default: undefined;
|
|
@@ -778,6 +821,11 @@ export declare const Form: {
|
|
|
778
821
|
type: StringConstructor;
|
|
779
822
|
default: string;
|
|
780
823
|
};
|
|
824
|
+
/** label 旁的提示文案(问号图标 + 悬停气泡);口径见 `FormItemProps.tip` */
|
|
825
|
+
tip: {
|
|
826
|
+
type: StringConstructor;
|
|
827
|
+
default: string;
|
|
828
|
+
};
|
|
781
829
|
labelWidth: {
|
|
782
830
|
type: NumberConstructor;
|
|
783
831
|
default: undefined;
|
|
@@ -812,6 +860,7 @@ export declare const Form: {
|
|
|
812
860
|
align: FormItemAlign;
|
|
813
861
|
readonly: boolean;
|
|
814
862
|
hint: string;
|
|
863
|
+
tip: string;
|
|
815
864
|
labelWidth: number;
|
|
816
865
|
labelAlign: FormLabelAlign;
|
|
817
866
|
controlWidth: number;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { defineComponent, inject, watch, createVNode, createTextVNode, provide, toRef, ref, computed, isVNode } from "vue";
|
|
2
|
+
import { Icon } from "@manohub/icon";
|
|
2
3
|
import { mergePartStyle } from "../part-padding.js";
|
|
4
|
+
import { Tooltip } from "../tooltip/index.js";
|
|
3
5
|
const FORM_LABEL_WIDTH = /* @__PURE__ */ Symbol("mh-form-label-width");
|
|
4
6
|
const FORM_CONTROL_WIDTH = /* @__PURE__ */ Symbol("mh-form-control-width");
|
|
5
7
|
const FORM_LABEL_ALIGN = /* @__PURE__ */ Symbol("mh-form-label-align");
|
|
@@ -29,6 +31,11 @@ const FormItem = /* @__PURE__ */ defineComponent({
|
|
|
29
31
|
type: String,
|
|
30
32
|
default: ""
|
|
31
33
|
},
|
|
34
|
+
/** label 旁的提示文案(问号图标 + 悬停气泡);口径见 `FormItemProps.tip` */
|
|
35
|
+
tip: {
|
|
36
|
+
type: String,
|
|
37
|
+
default: ""
|
|
38
|
+
},
|
|
32
39
|
labelWidth: {
|
|
33
40
|
type: Number,
|
|
34
41
|
default: void 0
|
|
@@ -85,7 +92,7 @@ const FormItem = /* @__PURE__ */ defineComponent({
|
|
|
85
92
|
const styles = [attrs.style, labelWidth ? {
|
|
86
93
|
"--mh-form-label-width": `${labelWidth}px`
|
|
87
94
|
} : null].filter(Boolean);
|
|
88
|
-
const showLabel = !!props.label || props.required;
|
|
95
|
+
const showLabel = !!props.label || props.required || !!props.tip;
|
|
89
96
|
return createVNode("div", {
|
|
90
97
|
"class": [
|
|
91
98
|
"mh-form-item",
|
|
@@ -109,7 +116,18 @@ const FormItem = /* @__PURE__ */ defineComponent({
|
|
|
109
116
|
"class": "mh-form-item__label-text"
|
|
110
117
|
}, [props.label]), props.required ? createVNode("span", {
|
|
111
118
|
"class": "mh-form-item__required"
|
|
112
|
-
}, [createTextVNode("*")]) : null
|
|
119
|
+
}, [createTextVNode("*")]) : null, props.tip ? createVNode(Tooltip, {
|
|
120
|
+
"class": "mh-form-item__tip",
|
|
121
|
+
"content": props.tip
|
|
122
|
+
}, {
|
|
123
|
+
default: () => [createVNode("button", {
|
|
124
|
+
"type": "button",
|
|
125
|
+
"class": "mh-form-item__tip-btn",
|
|
126
|
+
"aria-label": props.tip
|
|
127
|
+
}, [createVNode(Icon, {
|
|
128
|
+
"name": "help"
|
|
129
|
+
}, null)])]
|
|
130
|
+
}) : null]) : null, createVNode("div", {
|
|
113
131
|
"class": "mh-form-item__control",
|
|
114
132
|
"style": controlWidth ? {
|
|
115
133
|
maxWidth: `${controlWidth}px`
|
|
@@ -168,6 +168,7 @@ const Input = /* @__PURE__ */ defineComponent({
|
|
|
168
168
|
if (props.modelValue !== void 0 && props.modelValue !== el.value) el.value = props.modelValue;
|
|
169
169
|
};
|
|
170
170
|
const onKeydown = (event) => {
|
|
171
|
+
if (event.isComposing) return;
|
|
171
172
|
if (event.key === "Enter") props.onEnter?.(current.value ?? "");
|
|
172
173
|
if (event.key === "Delete" && props.clearable) commit("");
|
|
173
174
|
};
|
|
@@ -27,6 +27,11 @@ export type NavVariant = 'tree' | 'list';
|
|
|
27
27
|
* `renderActions` 直接透传给 `Tree`:默认隐藏、行悬浮或行内获得焦点时显形、点击不冒泡(§4.16)。
|
|
28
28
|
* 应用侧只给节点,不要自己写显隐 CSS,也不要把它塞进 `renderNode`(那是常驻附加内容)。
|
|
29
29
|
*
|
|
30
|
+
* ## 图标
|
|
31
|
+
*
|
|
32
|
+
* 导航项图标走 `renderIcon`(标签前的一位图标,口径同 `Tree.renderIcon`)—— **不要**用
|
|
33
|
+
* `renderLabel` 拼「图标 + 文字」:标签槽承担省略号与行内对齐,图标进去会被裁掉,也不成列。
|
|
34
|
+
*
|
|
30
35
|
* @example 分类导航树(选中驱动右侧 + 行尾管理操作)
|
|
31
36
|
* <Nav variant="tree" nodes={categoryNodes.value} rowKey="id" labelKey="name" width={220}
|
|
32
37
|
* selected={categoryId.value} onSelect={(node) => (categoryId.value = node.id)}
|
|
@@ -89,6 +94,16 @@ export declare const Nav: import("vue").DefineComponent<import("vue").ExtractPro
|
|
|
89
94
|
type: PropType<(node: TreeNode) => unknown>;
|
|
90
95
|
default: undefined;
|
|
91
96
|
};
|
|
97
|
+
/** 导航项图标(标签前的一位图标):见 `Tree.renderIcon`(侧栏图标走这一位,不要塞进 `renderLabel`) */
|
|
98
|
+
renderIcon: {
|
|
99
|
+
type: PropType<(node: TreeNode) => unknown>;
|
|
100
|
+
default: undefined;
|
|
101
|
+
};
|
|
102
|
+
/** 展开 / 收起箭头字形:见 `Tree.renderCaret`(`list` 态箭头位被隐藏,传了也不显形) */
|
|
103
|
+
renderCaret: {
|
|
104
|
+
type: PropType<(node: TreeNode, expanded: boolean) => unknown>;
|
|
105
|
+
default: undefined;
|
|
106
|
+
};
|
|
92
107
|
/** 侧栏标题(可选;不传则只有导航体) */
|
|
93
108
|
title: {
|
|
94
109
|
type: StringConstructor;
|
|
@@ -206,6 +221,16 @@ export declare const Nav: import("vue").DefineComponent<import("vue").ExtractPro
|
|
|
206
221
|
type: PropType<(node: TreeNode) => unknown>;
|
|
207
222
|
default: undefined;
|
|
208
223
|
};
|
|
224
|
+
/** 导航项图标(标签前的一位图标):见 `Tree.renderIcon`(侧栏图标走这一位,不要塞进 `renderLabel`) */
|
|
225
|
+
renderIcon: {
|
|
226
|
+
type: PropType<(node: TreeNode) => unknown>;
|
|
227
|
+
default: undefined;
|
|
228
|
+
};
|
|
229
|
+
/** 展开 / 收起箭头字形:见 `Tree.renderCaret`(`list` 态箭头位被隐藏,传了也不显形) */
|
|
230
|
+
renderCaret: {
|
|
231
|
+
type: PropType<(node: TreeNode, expanded: boolean) => unknown>;
|
|
232
|
+
default: undefined;
|
|
233
|
+
};
|
|
209
234
|
/** 侧栏标题(可选;不传则只有导航体) */
|
|
210
235
|
title: {
|
|
211
236
|
type: StringConstructor;
|
|
@@ -285,6 +310,8 @@ export declare const Nav: import("vue").DefineComponent<import("vue").ExtractPro
|
|
|
285
310
|
onErrorAction: () => void;
|
|
286
311
|
labelKey: string;
|
|
287
312
|
childrenKey: string;
|
|
313
|
+
renderCaret: (node: TreeNode, expanded: boolean) => unknown;
|
|
314
|
+
renderIcon: (node: TreeNode) => unknown;
|
|
288
315
|
renderActions: (node: TreeNode) => unknown;
|
|
289
316
|
expandedKeys: string[];
|
|
290
317
|
defaultExpandedKeys: string[];
|
|
@@ -58,6 +58,16 @@ const Nav = /* @__PURE__ */ defineComponent({
|
|
|
58
58
|
type: Function,
|
|
59
59
|
default: void 0
|
|
60
60
|
},
|
|
61
|
+
/** 导航项图标(标签前的一位图标):见 `Tree.renderIcon`(侧栏图标走这一位,不要塞进 `renderLabel`) */
|
|
62
|
+
renderIcon: {
|
|
63
|
+
type: Function,
|
|
64
|
+
default: void 0
|
|
65
|
+
},
|
|
66
|
+
/** 展开 / 收起箭头字形:见 `Tree.renderCaret`(`list` 态箭头位被隐藏,传了也不显形) */
|
|
67
|
+
renderCaret: {
|
|
68
|
+
type: Function,
|
|
69
|
+
default: void 0
|
|
70
|
+
},
|
|
61
71
|
/** 侧栏标题(可选;不传则只有导航体) */
|
|
62
72
|
title: {
|
|
63
73
|
type: String,
|
|
@@ -146,6 +156,8 @@ const Nav = /* @__PURE__ */ defineComponent({
|
|
|
146
156
|
"defaultExpandedKeys": props.defaultExpandedKeys,
|
|
147
157
|
"onExpandChange": props.onExpandChange,
|
|
148
158
|
"renderActions": props.renderActions,
|
|
159
|
+
"renderIcon": props.renderIcon,
|
|
160
|
+
"renderCaret": props.renderCaret,
|
|
149
161
|
"size": props.size,
|
|
150
162
|
"loading": props.loading,
|
|
151
163
|
"empty": props.empty,
|
|
@@ -143,8 +143,8 @@ export declare const Notice: import("vue").DefineComponent<import("vue").Extract
|
|
|
143
143
|
tone: NoticeTone;
|
|
144
144
|
description: string;
|
|
145
145
|
action: string;
|
|
146
|
-
closeText: string;
|
|
147
146
|
onAction: () => void;
|
|
147
|
+
closeText: string;
|
|
148
148
|
}, SlotsType<{
|
|
149
149
|
default?: () => VNodeChild;
|
|
150
150
|
}>, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
@@ -16,7 +16,7 @@ export type NotificationTone = 'info' | 'success' | 'warning' | 'error' | 'submi
|
|
|
16
16
|
*
|
|
17
17
|
* - 图标:`icon` 布尔(按 `tone` 映射)或直接给图标名;
|
|
18
18
|
* - 头像:给 `avatar` 插槽 → 原样渲染(推荐塞 `<Avatar>`);给 `avatar` 字符串 → 渲染字标兜底盒;
|
|
19
|
-
* - 标签:`tags`
|
|
19
|
+
* - 标签:`tags` 插槽里放标记族(只读 `<Badge shape="soft" tone="error">`,要可关闭用 `<Tag>`);
|
|
20
20
|
* - 操作:`actions` 插槽里放按钮族的 `<Button size="sm" variant="primary">`。
|
|
21
21
|
*
|
|
22
22
|
* 关闭是**受控**的:本件只回调 `onClose`,不自己隐藏(可见性归消费方,避免「点了没反应」与
|
|
@@ -27,7 +27,7 @@ export type NotificationTone = 'info' | 'success' | 'warning' | 'error' | 'submi
|
|
|
27
27
|
*
|
|
28
28
|
* @example 图标 + 自动消失 + 头像 + 标签
|
|
29
29
|
* <Notification icon duration={4000} tone="info" title="审批通知" avatar="张" @close="dismiss">
|
|
30
|
-
* <
|
|
30
|
+
* <Badge shape="soft" tone="error">逾期</Badge>
|
|
31
31
|
* {{ actions: () => <Button size="sm" variant="primary">去处理</Button> }}
|
|
32
32
|
* </Notification>
|
|
33
33
|
*/
|
|
@@ -35,6 +35,15 @@
|
|
|
35
35
|
* **面板有高度上限**:`--ui-select-panel-max-height`(默认 320px,钉在 `select.css` 的面板根上)——
|
|
36
36
|
* 超出即面板内容区(`.mh-select__body`)滚动、搜索行常驻;列表与 `SelectTree` 两形态共用这一份。
|
|
37
37
|
*
|
|
38
|
+
* ## 键盘与高亮(焦点**留在触发器上**)
|
|
39
|
+
*
|
|
40
|
+
* 面板里的选项是 `div[role='option']`、**不是 button**、也不可聚焦:高亮位由本件持有
|
|
41
|
+
* (可激活项里的格号),列表体据此下发 `data-active`,触发器上的 `aria-activedescendant`
|
|
42
|
+
* 指到那一行的 id —— 原生 popover 下的「虚拟焦点」标准表达。两条入口共用同一份模型:
|
|
43
|
+
* 触发器(`↑` / `↓` 开面板并平移、`Enter` / `Space` 提交高亮项)与面板内的搜索行
|
|
44
|
+
* (`↑` / `↓` / `Enter` 冒泡到面板)。可激活项 = 摊平 + 跳过禁用项 + (可创建时)末尾那一格
|
|
45
|
+
* 「创建 xxx」行,顺序由 `activatableOptions()` 一处给。
|
|
46
|
+
*
|
|
38
47
|
* ## 状态三源等价
|
|
39
48
|
*
|
|
40
49
|
* 选中行 = `[data-state='selected']`(组件下发)/ 原生 `input:checked`(静态页零 JS 路径)/
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { defineComponent, ref, computed, createVNode, Fragment } from "vue";
|
|
1
|
+
import { defineComponent, ref, computed, watch, createVNode, Fragment } from "vue";
|
|
2
2
|
import { SelectList } from "./select-list.js";
|
|
3
3
|
import { SelectPanel } from "./select-panel.js";
|
|
4
4
|
import { SelectTrigger } from "./select-trigger.js";
|
|
5
|
-
import { createSelectPanelId, isGroup, createTriggerKeydown, showPanel, hidePanel } from "./select-shared.js";
|
|
5
|
+
import { createSelectPanelId, isGroup, activatableOptions, activatableCount, createTriggerKeydown, optionDomId, showPanel, hidePanel } from "./select-shared.js";
|
|
6
6
|
const Select = /* @__PURE__ */ defineComponent({
|
|
7
7
|
name: "Select",
|
|
8
8
|
inheritAttrs: false,
|
|
@@ -167,27 +167,68 @@ const Select = /* @__PURE__ */ defineComponent({
|
|
|
167
167
|
keyword.value = "";
|
|
168
168
|
};
|
|
169
169
|
const clear = () => commit(props.multiple ? [] : "");
|
|
170
|
-
const
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
170
|
+
const activatable = computed(() => activatableOptions(visibleOptions.value));
|
|
171
|
+
const slotCount = computed(() => activatableCount(visibleOptions.value, creatable.value));
|
|
172
|
+
const activeIndex = ref(void 0);
|
|
173
|
+
const activeEl = () => panelRef.value?.querySelector('[data-active="true"]') ?? null;
|
|
174
|
+
const scrollActiveIntoView = () => {
|
|
175
|
+
requestAnimationFrame(() => activeEl()?.scrollIntoView?.({
|
|
176
|
+
block: "nearest"
|
|
177
|
+
}));
|
|
178
|
+
};
|
|
179
|
+
const moveActive = (direction) => {
|
|
180
|
+
const count = slotCount.value;
|
|
181
|
+
if (!count) return;
|
|
182
|
+
const at = activeIndex.value ?? -1;
|
|
183
|
+
activeIndex.value = at < 0 ? direction > 0 ? 0 : count - 1 : (at + direction + count) % count;
|
|
184
|
+
scrollActiveIntoView();
|
|
185
|
+
};
|
|
186
|
+
const commitActive = () => {
|
|
187
|
+
const slot = activeIndex.value;
|
|
188
|
+
if (slot === void 0) return false;
|
|
189
|
+
if (creatable.value && slot === activatable.value.length) {
|
|
190
|
+
createFromKeyword();
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
const option = activatable.value[slot];
|
|
194
|
+
if (!option) return false;
|
|
195
|
+
pick(option);
|
|
196
|
+
if (!props.multiple) activeIndex.value = void 0;
|
|
197
|
+
return true;
|
|
198
|
+
};
|
|
199
|
+
watch(keyword, () => {
|
|
200
|
+
activeIndex.value = void 0;
|
|
201
|
+
});
|
|
202
|
+
const panelOpen = ref(false);
|
|
203
|
+
const isPanelOpen = () => panelOpen.value;
|
|
204
|
+
const openPanel = () => {
|
|
205
|
+
if (!panelOpen.value) activeIndex.value = void 0;
|
|
206
|
+
panelOpen.value = true;
|
|
207
|
+
showPanel(panelRef.value);
|
|
208
|
+
};
|
|
209
|
+
const onPanelToggle = (event) => {
|
|
210
|
+
if (event.newState === "open") return;
|
|
211
|
+
panelOpen.value = false;
|
|
212
|
+
activeIndex.value = void 0;
|
|
177
213
|
};
|
|
178
214
|
const onTriggerKeydown = createTriggerKeydown({
|
|
179
215
|
clearable: props.clearable,
|
|
180
216
|
disabled: props.disabled,
|
|
181
217
|
clear,
|
|
182
|
-
openPanel
|
|
183
|
-
|
|
218
|
+
openPanel,
|
|
219
|
+
isPanelOpen,
|
|
220
|
+
moveActive,
|
|
221
|
+
commitActive
|
|
184
222
|
});
|
|
185
223
|
const onPanelKeydown = (event) => {
|
|
186
224
|
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
187
225
|
event.preventDefault();
|
|
188
|
-
|
|
226
|
+
moveActive(event.key === "ArrowDown" ? 1 : -1);
|
|
227
|
+
return;
|
|
189
228
|
}
|
|
229
|
+
if (event.key === "Enter" && commitActive()) event.preventDefault();
|
|
190
230
|
};
|
|
231
|
+
const activeDescendant = computed(() => activeIndex.value === void 0 ? void 0 : optionDomId(panelId, activeIndex.value));
|
|
191
232
|
return () => {
|
|
192
233
|
const hasValue = selectedValues.value.length > 0;
|
|
193
234
|
return createVNode("div", {
|
|
@@ -203,6 +244,7 @@ const Select = /* @__PURE__ */ defineComponent({
|
|
|
203
244
|
"multiple": props.multiple,
|
|
204
245
|
"placeholder": props.placeholder,
|
|
205
246
|
"label": selectedOptions.value[0]?.label,
|
|
247
|
+
"activeDescendant": activeDescendant.value,
|
|
206
248
|
"onKeydown": onTriggerKeydown,
|
|
207
249
|
"onClear": clear,
|
|
208
250
|
"renderValue": props.multiple ? () => createVNode(Fragment, null, [visibleTags.value.map((option) => createVNode("span", {
|
|
@@ -215,6 +257,7 @@ const Select = /* @__PURE__ */ defineComponent({
|
|
|
215
257
|
"id": panelId,
|
|
216
258
|
"elementRef": panelRef,
|
|
217
259
|
"searchable": props.searchable,
|
|
260
|
+
"onToggle": onPanelToggle,
|
|
218
261
|
"keyword": keyword.value,
|
|
219
262
|
"onKeyword": (value) => {
|
|
220
263
|
keyword.value = value;
|
|
@@ -225,6 +268,8 @@ const Select = /* @__PURE__ */ defineComponent({
|
|
|
225
268
|
default: () => createVNode(SelectList, {
|
|
226
269
|
"options": visibleOptions.value,
|
|
227
270
|
"selectedValues": selectedValues.value,
|
|
271
|
+
"activeIndex": activeIndex.value,
|
|
272
|
+
"idBase": panelId,
|
|
228
273
|
"loading": props.loading,
|
|
229
274
|
"emptyText": props.emptyText,
|
|
230
275
|
"creatable": creatable.value,
|