@manohub/ui 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -7
- package/dist/components/checkbox/index.js +5 -1
- package/dist/components/code-block/index.js +18 -11
- package/dist/components/filter/index.d.ts +8 -2
- package/dist/components/filter/index.js +11 -20
- package/dist/components/form/index.d.ts +39 -0
- package/dist/components/form/index.js +6 -0
- package/dist/components/input/index.d.ts +48 -7
- package/dist/components/input/index.js +23 -3
- package/dist/components/radio/index.js +29 -3
- package/dist/components/search/index.js +37 -35
- package/dist/components/select/index.js +1 -3
- package/dist/components/select/select-trigger.d.ts +4 -2
- package/dist/components/select/select-trigger.js +9 -4
- package/dist/components/table/index.d.ts +19 -8
- package/dist/components/table/index.js +3 -3
- package/dist/components/text/index.d.ts +4 -3
- package/dist/components/toast/index.js +16 -1
- package/dist/styles/components/avatar.css +4 -1
- package/dist/styles/components/button.css +2 -0
- package/dist/styles/components/filter.css +22 -34
- package/dist/styles/components/input.css +24 -0
- package/dist/styles/components/select.css +3 -4
- package/dist/styles/components/table.css +33 -22
- package/dist/styles/components/table.tokens.css +6 -6
- package/dist/styles/components/text.css +10 -0
- package/dist/styles/components/toast.css +35 -6
- package/dist/styles/components/upload.css +1 -0
- package/dist/styles/index.css +5 -4
- package/dist/styles/reset.css +52 -167
- package/package.json +2 -2
- package/dist/styles/components/filter.tokens.css +0 -14
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ AIHub **自绘组件层**:不依赖 farris 的手写 Vue TSX 组件 + 自带
|
|
|
7
7
|
| 层 | 入口 | 内容 |
|
|
8
8
|
|---|---|---|
|
|
9
9
|
| ① 主题 | `@manohub/theme/default.css`(兜底)/ `@manohub/theme/farris.css`(Farris 规范) | **全局**令牌(值)—— 随主题包独立发布,换主题只换这一行 |
|
|
10
|
-
| ② 组件面 | `@manohub/ui/styles.css` | **元素级 reset
|
|
10
|
+
| ② 组件面 | `@manohub/ui/styles.css` | **元素级 reset**(只有盒模型 + 容器基座两面,见下)+ 类与状态协议 + 组件令牌基础值,**无需 JS** 即可使用 |
|
|
11
11
|
| ③ Vue 层 | `@manohub/ui` | Vue 组件:渲染 ② 的类 + 行为增强 |
|
|
12
12
|
| ④ 服务层 | `@manohub/ui` | 命令式反馈:`toast()` / `confirm()` / `alert()` / `showLoading()`(宿主 + 队列 + 生命周期,共用 ③ 的组件) |
|
|
13
13
|
|
|
@@ -37,13 +37,19 @@ pnpm add @manohub/ui @manohub/theme
|
|
|
37
37
|
|
|
38
38
|
只写静态页(不装 Vue)时引这两行即可 —— 类名与 `data-*` 状态协议直接可用。
|
|
39
39
|
|
|
40
|
-
**② 的第一条 import 就是元素级 reset
|
|
41
|
-
`ul` / `ol` 的序号与缩进、`a` 的下划线、标题的默认字阶一并归零。作用域锚 `[data-manohub-ui]`
|
|
42
|
-
且特异性为 0 —— 应用侧写任何元素选择器都能盖过它,所以**不必再自己写一份全局 reset**。
|
|
40
|
+
**② 的第一条 import 就是元素级 reset**(随包内置,不需要第三行),它只有两面:
|
|
43
41
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
1. **盒模型**:`box-sizing: border-box` 覆盖容器、全部后代与伪元素(本包所有面的几何前提);
|
|
43
|
+
2. **容器基座**:`[data-manohub-ui]` 上的字族 / 字号 / 行高 / 字色 / 底色 / 整链高度(令牌的落点)。
|
|
44
|
+
|
|
45
|
+
作用域锚 `[data-manohub-ui]` 且特异性为 0 —— 应用侧写任何元素选择器都能盖过它。
|
|
46
|
+
**它有意不再归零标题字阶、列表序号、链接下划线、控件外观**:那些规则在 Tailwind 的层外
|
|
47
|
+
(未分层 > 分层),会把消费方的工具类整片压住(`border*`、`img` 上的 `h-*`、`hidden` …);
|
|
48
|
+
需要归零的元素各自归到渲染它的组件面上(`img` 在 Avatar / Upload、`a` 在 Button、
|
|
49
|
+
`textarea` 的 `resize` 在 `.mh-input--textarea`、`h1`-`h6` 在 `.mh-text`)。
|
|
50
|
+
Tailwind 消费方另有 preflight 兜底,两者不冲突。
|
|
51
|
+
|
|
52
|
+
所以**不必再自己写一份全局 reset**;也**不要**用元素级选择器去补(那正是被压住的成因)。
|
|
47
53
|
|
|
48
54
|
本包**可独立发布**:组件与类型一律从 `@manohub/ui` 直接引;下游(如 `@manohub/kit`)若转出口,是下游自己的选择。
|
|
49
55
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineComponent, ref, computed, watchEffect, createVNode, mergeProps } from "vue";
|
|
1
|
+
import { defineComponent, ref, computed, watchEffect, createVNode, mergeProps, nextTick } from "vue";
|
|
2
2
|
import { Icon } from "@manohub/icon";
|
|
3
3
|
const Checkbox = /* @__PURE__ */ defineComponent({
|
|
4
4
|
name: "Checkbox",
|
|
@@ -58,6 +58,10 @@ const Checkbox = /* @__PURE__ */ defineComponent({
|
|
|
58
58
|
const next = event.target.checked;
|
|
59
59
|
emit("update:modelValue", next);
|
|
60
60
|
props.onChange?.(next);
|
|
61
|
+
void nextTick(() => {
|
|
62
|
+
const el = inputRef.value;
|
|
63
|
+
if (el && el.checked !== !!props.modelValue) el.checked = !!props.modelValue;
|
|
64
|
+
});
|
|
61
65
|
};
|
|
62
66
|
return () => {
|
|
63
67
|
const state = props.indeterminate ? "indeterminate" : props.modelValue ? "checked" : "unchecked";
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import { defineComponent, createVNode, mergeProps, ref, computed, onBeforeUnmount } from "vue";
|
|
1
|
+
import { defineComponent, createVNode, mergeProps, ref, computed, onBeforeUnmount, isVNode } from "vue";
|
|
2
2
|
import { Icon } from "@manohub/icon";
|
|
3
|
+
function _isSlot(s) {
|
|
4
|
+
return typeof s === "function" || Object.prototype.toString.call(s) === "[object Object]" && !isVNode(s);
|
|
5
|
+
}
|
|
3
6
|
const CodeView = /* @__PURE__ */ defineComponent({
|
|
4
7
|
name: "CodeView",
|
|
5
8
|
inheritAttrs: false,
|
|
@@ -285,6 +288,7 @@ const CodeBlockBase = /* @__PURE__ */ defineComponent({
|
|
|
285
288
|
attrs
|
|
286
289
|
}) {
|
|
287
290
|
return () => {
|
|
291
|
+
let _slot;
|
|
288
292
|
const shared = {
|
|
289
293
|
language: props.language,
|
|
290
294
|
maxHeight: props.maxHeight,
|
|
@@ -304,16 +308,19 @@ const CodeBlockBase = /* @__PURE__ */ defineComponent({
|
|
|
304
308
|
"onUpdate:modelValue": (v) => emit("update:modelValue", v)
|
|
305
309
|
}), null);
|
|
306
310
|
}
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
311
|
+
const view = {
|
|
312
|
+
...shared,
|
|
313
|
+
code: props.code,
|
|
314
|
+
inline: props.inline,
|
|
315
|
+
plain: props.plain,
|
|
316
|
+
copyable: props.copyable,
|
|
317
|
+
wrap: props.wrap,
|
|
318
|
+
onCopy: props.onCopy
|
|
319
|
+
};
|
|
320
|
+
const body = slots.default;
|
|
321
|
+
return body ? createVNode(CodeView, view, _isSlot(_slot = body()) ? _slot : {
|
|
322
|
+
default: () => [_slot]
|
|
323
|
+
}) : createVNode(CodeView, view, null);
|
|
317
324
|
};
|
|
318
325
|
}
|
|
319
326
|
});
|
|
@@ -41,7 +41,12 @@ export interface FilterProps {
|
|
|
41
41
|
title?: string;
|
|
42
42
|
/** 标题下的说明小字(透传给 `Form.Header`) */
|
|
43
43
|
description?: string;
|
|
44
|
-
/**
|
|
44
|
+
/**
|
|
45
|
+
* label 列宽(px),透传给 `Form`。
|
|
46
|
+
*
|
|
47
|
+
* **缺省不设**:不传即与 `Form` 一致(取令牌 `--ui-form-label-width`,120)。
|
|
48
|
+
* 想收窄(筛选条的标签多为四字)就显式传,例如 `labelWidth={68}`。
|
|
49
|
+
*/
|
|
45
50
|
labelWidth?: number;
|
|
46
51
|
/** 受控条件快照(`条件名 → 值`);不传即非受控(由 `defaultValue` 起) */
|
|
47
52
|
modelValue?: FilterValues;
|
|
@@ -79,7 +84,8 @@ export interface FilterItemProps {
|
|
|
79
84
|
* 增量只有一处 —— **读写的值都是归一后的**(`'all'` / 空数组 / 空值 → 空串),
|
|
80
85
|
* 于是消费方直接把 `value` 绑给控件,不必自己处理哨兵。
|
|
81
86
|
*
|
|
82
|
-
*
|
|
87
|
+
* 字段宽**自适应**:字段区是 `Form` 的多列动态档(`Filter` 传 `columns="auto"`),
|
|
88
|
+
* 故本件只输出 `Form.Item` 自己的类 —— 列宽由那条网格轨道算,换行下来的字段与上一行逐列对齐。
|
|
83
89
|
*
|
|
84
90
|
* @example
|
|
85
91
|
* <Filter.Item code="status" label="授权状态">
|
|
@@ -2,8 +2,6 @@ import { defineComponent, createVNode, ref, h, Fragment } from "vue";
|
|
|
2
2
|
import { Button } from "../button/index.js";
|
|
3
3
|
import { FormItem, Form } from "../form/index.js";
|
|
4
4
|
const FILTER_ALL = "all";
|
|
5
|
-
const FILTER_LABEL_WIDTH = 68;
|
|
6
|
-
const FILTER_GAP = 16;
|
|
7
5
|
function normalizeFilterValue(value) {
|
|
8
6
|
if (Array.isArray(value)) {
|
|
9
7
|
const kept = value.map((item) => normalizeFilterValue(item)).filter((item) => item !== "");
|
|
@@ -96,7 +94,7 @@ const FilterRoot = /* @__PURE__ */ defineComponent({
|
|
|
96
94
|
emit("update:modelValue", next);
|
|
97
95
|
props.onReset?.(next);
|
|
98
96
|
};
|
|
99
|
-
const
|
|
97
|
+
const onFormKeydown = (event) => {
|
|
100
98
|
if (event.key !== "Enter" || event.isComposing) return;
|
|
101
99
|
const target = event.target;
|
|
102
100
|
if (target?.tagName !== "INPUT" || target.closest("[popover]")) return;
|
|
@@ -118,30 +116,23 @@ const FilterRoot = /* @__PURE__ */ defineComponent({
|
|
|
118
116
|
"ref": formRef,
|
|
119
117
|
"class": ["mh-filter__form", attrs.class].filter(Boolean).join(" "),
|
|
120
118
|
"style": attrs.style,
|
|
121
|
-
"labelWidth": props.labelWidth
|
|
122
|
-
"
|
|
119
|
+
"labelWidth": props.labelWidth,
|
|
120
|
+
"columns": "auto",
|
|
121
|
+
"onKeydown": onFormKeydown,
|
|
123
122
|
"modelValue": props.modelValue,
|
|
124
123
|
"defaultValue": props.defaultValue,
|
|
125
124
|
"onChange": onFormChange
|
|
126
125
|
}, {
|
|
127
|
-
default: () =>
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
h(Form.Header, {
|
|
126
|
+
default: () => {
|
|
127
|
+
const raw = slots.default?.();
|
|
128
|
+
const fields = Array.isArray(raw) ? raw : raw == null ? [] : [raw];
|
|
129
|
+
return [h(Form.Header, {
|
|
132
130
|
title: props.title,
|
|
133
131
|
description: props.description
|
|
134
132
|
}, {
|
|
135
133
|
extra: renderActions
|
|
136
|
-
}),
|
|
137
|
-
|
|
138
|
-
h("div", {
|
|
139
|
-
class: "mh-filter__fields",
|
|
140
|
-
onKeydown: onFieldsKeydown
|
|
141
|
-
}, {
|
|
142
|
-
default: () => slots.default?.()
|
|
143
|
-
})
|
|
144
|
-
]
|
|
134
|
+
}), ...fields];
|
|
135
|
+
}
|
|
145
136
|
});
|
|
146
137
|
}
|
|
147
138
|
});
|
|
@@ -181,7 +172,7 @@ const FilterItem = /* @__PURE__ */ defineComponent({
|
|
|
181
172
|
"required": props.required,
|
|
182
173
|
"error": props.error,
|
|
183
174
|
"hint": props.hint,
|
|
184
|
-
"class":
|
|
175
|
+
"class": attrs.class,
|
|
185
176
|
"style": attrs.style
|
|
186
177
|
}, {
|
|
187
178
|
default: (scope) => slots.default?.({
|
|
@@ -99,6 +99,18 @@ export interface FormProps {
|
|
|
99
99
|
defaultValue?: FormValues;
|
|
100
100
|
/** 值变更(快照含**全部已登记字段**,未设值的字段为 `undefined`) */
|
|
101
101
|
onChange?: (values: FormValues) => void;
|
|
102
|
+
/**
|
|
103
|
+
* 根节点的键盘事件(**转发到表单根**)。
|
|
104
|
+
*
|
|
105
|
+
* 本件是 `inheritAttrs: false`,根节点只显式绑 `class` / `style` —— 不声明的监听器会被**静默丢掉**
|
|
106
|
+
* (与该文件里已修过的「消费方 style 被静默丢弃」同款前科)。而根节点就是「全部字段行的容器」,
|
|
107
|
+
* 故它是「字段区层面的委托」的落点:本包 `Filter` 的「字段区回车 = 查询」就搭在这条通道上
|
|
108
|
+
* (构造 `Filter` 时因此不必再套一层只为挂事件的壳 —— 那会改变 `class` / `style` 的落点)。
|
|
109
|
+
*
|
|
110
|
+
* **有意只开这一条**:其它监听器(如 `onClick`)不转发;要把某一条也接进来,先当一次公开行为变更裁决。
|
|
111
|
+
* 要不要拦、拦哪些按键(本包 `Filter` 收窄到「`<input>` 上的回车」)由调用方自己判断,本件只转发。
|
|
112
|
+
*/
|
|
113
|
+
onKeydown?: (event: KeyboardEvent) => void;
|
|
102
114
|
}
|
|
103
115
|
export interface FormItemProps {
|
|
104
116
|
/**
|
|
@@ -193,6 +205,15 @@ export interface FormItemProps {
|
|
|
193
205
|
* `{ y, x }` 上下 / 左右分别给,缺省 0(贴容器边)。页面里的表单在 `Panel.Body` 里、内距由那块给;
|
|
194
206
|
* **弹窗 / 抽屉里没有别的容器**给内距,就用表单自带的 `padding={{ y: 16, x: 20 }}`。
|
|
195
207
|
*
|
|
208
|
+
* ## 键盘事件(`onKeydown` 转发到根节点)
|
|
209
|
+
*
|
|
210
|
+
* `onKeydown` 是本件的**声明式入口**(缺省不绑):传进来的处理器绑在根节点上 ——
|
|
211
|
+
* 本件是 `inheritAttrs: false`,根节点只显式绑 `class` / `style`,不声明的监听器会被静默丢掉。
|
|
212
|
+
* 根节点即「全部字段行的容器」,因此它是「字段区层面的委托」的落点:本包 `Filter` 的
|
|
213
|
+
* 「字段区回车 = 查询」就搭在这里,构造 `Filter` 时**不必**再套一层只为挂事件的壳
|
|
214
|
+
* (那会改变 `class` / `style` 的落点)。要不要拦、拦哪些按键由调用方自己判断,本件只转发。
|
|
215
|
+
* **有意只开这一条**:其它监听器不转发,扩张要先当一次公开行为变更裁决。
|
|
216
|
+
*
|
|
196
217
|
* ## 只读值(摘要 / 详情)
|
|
197
218
|
*
|
|
198
219
|
* 「向导最后一步的确认摘要」「详情弹窗」不需要另起一套描述列表件 —— 只用表单行本身,
|
|
@@ -455,6 +476,11 @@ export declare const Form: {
|
|
|
455
476
|
type: PropType<(values: FormValues) => void>;
|
|
456
477
|
default: undefined;
|
|
457
478
|
};
|
|
479
|
+
/** 根节点的键盘事件(转发到表单根;缺省不绑)—— 用途与边界见 `FormProps` 的同名注释 */
|
|
480
|
+
onKeydown: {
|
|
481
|
+
type: PropType<(event: KeyboardEvent) => void>;
|
|
482
|
+
default: undefined;
|
|
483
|
+
};
|
|
458
484
|
}>> & Readonly<{
|
|
459
485
|
"onUpdate:modelValue"?: ((values: FormValues) => any) | undefined;
|
|
460
486
|
}>, () => import("vue/jsx-runtime").JSX.Element, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
@@ -463,6 +489,7 @@ export declare const Form: {
|
|
|
463
489
|
padding: Padding;
|
|
464
490
|
layout: "horizontal" | "vertical";
|
|
465
491
|
onChange: (values: FormValues) => void;
|
|
492
|
+
onKeydown: (event: KeyboardEvent) => void;
|
|
466
493
|
defaultValue: FormValues;
|
|
467
494
|
gap: number;
|
|
468
495
|
columns: FormColumns;
|
|
@@ -544,12 +571,18 @@ export declare const Form: {
|
|
|
544
571
|
type: PropType<(values: FormValues) => void>;
|
|
545
572
|
default: undefined;
|
|
546
573
|
};
|
|
574
|
+
/** 根节点的键盘事件(转发到表单根;缺省不绑)—— 用途与边界见 `FormProps` 的同名注释 */
|
|
575
|
+
onKeydown: {
|
|
576
|
+
type: PropType<(event: KeyboardEvent) => void>;
|
|
577
|
+
default: undefined;
|
|
578
|
+
};
|
|
547
579
|
}>> & Readonly<{
|
|
548
580
|
"onUpdate:modelValue"?: ((values: FormValues) => any) | undefined;
|
|
549
581
|
}>, () => import("vue/jsx-runtime").JSX.Element, {}, {}, {}, {
|
|
550
582
|
padding: Padding;
|
|
551
583
|
layout: "horizontal" | "vertical";
|
|
552
584
|
onChange: (values: FormValues) => void;
|
|
585
|
+
onKeydown: (event: KeyboardEvent) => void;
|
|
553
586
|
defaultValue: FormValues;
|
|
554
587
|
gap: number;
|
|
555
588
|
columns: FormColumns;
|
|
@@ -626,6 +659,11 @@ export declare const Form: {
|
|
|
626
659
|
type: PropType<(values: FormValues) => void>;
|
|
627
660
|
default: undefined;
|
|
628
661
|
};
|
|
662
|
+
/** 根节点的键盘事件(转发到表单根;缺省不绑)—— 用途与边界见 `FormProps` 的同名注释 */
|
|
663
|
+
onKeydown: {
|
|
664
|
+
type: PropType<(event: KeyboardEvent) => void>;
|
|
665
|
+
default: undefined;
|
|
666
|
+
};
|
|
629
667
|
}>> & Readonly<{
|
|
630
668
|
"onUpdate:modelValue"?: ((values: FormValues) => any) | undefined;
|
|
631
669
|
}>, () => import("vue/jsx-runtime").JSX.Element, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
@@ -634,6 +672,7 @@ export declare const Form: {
|
|
|
634
672
|
padding: Padding;
|
|
635
673
|
layout: "horizontal" | "vertical";
|
|
636
674
|
onChange: (values: FormValues) => void;
|
|
675
|
+
onKeydown: (event: KeyboardEvent) => void;
|
|
637
676
|
defaultValue: FormValues;
|
|
638
677
|
gap: number;
|
|
639
678
|
columns: FormColumns;
|
|
@@ -236,6 +236,11 @@ const FormRoot = /* @__PURE__ */ defineComponent({
|
|
|
236
236
|
onChange: {
|
|
237
237
|
type: Function,
|
|
238
238
|
default: void 0
|
|
239
|
+
},
|
|
240
|
+
/** 根节点的键盘事件(转发到表单根;缺省不绑)—— 用途与边界见 `FormProps` 的同名注释 */
|
|
241
|
+
onKeydown: {
|
|
242
|
+
type: Function,
|
|
243
|
+
default: void 0
|
|
239
244
|
}
|
|
240
245
|
},
|
|
241
246
|
emits: {
|
|
@@ -313,6 +318,7 @@ const FormRoot = /* @__PURE__ */ defineComponent({
|
|
|
313
318
|
} = splitFormHeader(list);
|
|
314
319
|
return createVNode("div", {
|
|
315
320
|
"class": ["mh-form", columnsClass(), props.layout === "vertical" ? "mh-form--vertical" : "", attrs.class].filter(Boolean).join(" "),
|
|
321
|
+
"onKeydown": props.onKeydown,
|
|
316
322
|
"style": [
|
|
317
323
|
mergePartStyle(attrs.style, props.padding),
|
|
318
324
|
/* 行距同时下发实体 `gap` 与变量:多列的轨道下限公式要用**同一个**行距值算 ——
|
|
@@ -35,8 +35,9 @@ export interface TextareaAutoSize {
|
|
|
35
35
|
* <Textarea modelValue={note} autoSize={{ minRows: 2, maxRows: 6 }} maxlength={500} showCount onChange={setNote} />
|
|
36
36
|
*
|
|
37
37
|
* @example 触发器形态(复合件用:控件换成 `<button>`,表壳 / 清除位 / 右侧图标位全部复用)
|
|
38
|
-
* <Input as="button" clearable hasValue={!!value} controlClass="my-picker"
|
|
39
|
-
* {{
|
|
38
|
+
* <Input as="button" clearable hasValue={!!value} placeholder="请选择" controlClass="my-picker"
|
|
39
|
+
* controlAttrs={{ popovertarget: id }}>
|
|
40
|
+
* {{ default: () => value, suffix: () => <Icon name="chevron-down" /> }}
|
|
40
41
|
* </Input>
|
|
41
42
|
*/
|
|
42
43
|
export declare const Input: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
@@ -50,6 +51,13 @@ export declare const Input: import("vue").DefineComponent<import("vue").ExtractP
|
|
|
50
51
|
type: StringConstructor;
|
|
51
52
|
default: string;
|
|
52
53
|
};
|
|
54
|
+
/**
|
|
55
|
+
* 占位文案。两种形态都消费它:
|
|
56
|
+
* - 缺省(`as="input"`)走原生 `::placeholder` 伪元素;
|
|
57
|
+
* - 触发器形态(`as="button"`)**没有 `::placeholder` 可用**,改由一个 span 承载
|
|
58
|
+
* (`.mh-input__placeholder`)—— 当且仅当 `hasValue === false` 且本 prop 有值时渲染它,
|
|
59
|
+
* 并**取代** `default` 插槽(见上面触发器一节)。没传 `hasValue` 的调用方行为不变。
|
|
60
|
+
*/
|
|
53
61
|
placeholder: {
|
|
54
62
|
type: StringConstructor;
|
|
55
63
|
default: undefined;
|
|
@@ -142,7 +150,8 @@ export declare const Input: import("vue").DefineComponent<import("vue").ExtractP
|
|
|
142
150
|
type: PropType<Record<string, unknown>>;
|
|
143
151
|
default: undefined;
|
|
144
152
|
};
|
|
145
|
-
/**
|
|
153
|
+
/** 显式覆盖清除位显隐;不给时仍按「有内容」判定(多选类复合件没有单值可判)。
|
|
154
|
+
* 同时是触发器形态「显示值还是显示占位」的判据(仅当它为 `false` 且传了 `placeholder` 时接管) */
|
|
146
155
|
hasValue: {
|
|
147
156
|
type: BooleanConstructor;
|
|
148
157
|
default: undefined;
|
|
@@ -160,6 +169,13 @@ export declare const Input: import("vue").DefineComponent<import("vue").ExtractP
|
|
|
160
169
|
type: StringConstructor;
|
|
161
170
|
default: string;
|
|
162
171
|
};
|
|
172
|
+
/**
|
|
173
|
+
* 占位文案。两种形态都消费它:
|
|
174
|
+
* - 缺省(`as="input"`)走原生 `::placeholder` 伪元素;
|
|
175
|
+
* - 触发器形态(`as="button"`)**没有 `::placeholder` 可用**,改由一个 span 承载
|
|
176
|
+
* (`.mh-input__placeholder`)—— 当且仅当 `hasValue === false` 且本 prop 有值时渲染它,
|
|
177
|
+
* 并**取代** `default` 插槽(见上面触发器一节)。没传 `hasValue` 的调用方行为不变。
|
|
178
|
+
*/
|
|
163
179
|
placeholder: {
|
|
164
180
|
type: StringConstructor;
|
|
165
181
|
default: undefined;
|
|
@@ -252,7 +268,8 @@ export declare const Input: import("vue").DefineComponent<import("vue").ExtractP
|
|
|
252
268
|
type: PropType<Record<string, unknown>>;
|
|
253
269
|
default: undefined;
|
|
254
270
|
};
|
|
255
|
-
/**
|
|
271
|
+
/** 显式覆盖清除位显隐;不给时仍按「有内容」判定(多选类复合件没有单值可判)。
|
|
272
|
+
* 同时是触发器形态「显示值还是显示占位」的判据(仅当它为 `false` 且传了 `placeholder` 时接管) */
|
|
256
273
|
hasValue: {
|
|
257
274
|
type: BooleanConstructor;
|
|
258
275
|
default: undefined;
|
|
@@ -478,6 +495,13 @@ export declare const InputCompound: {
|
|
|
478
495
|
type: StringConstructor;
|
|
479
496
|
default: string;
|
|
480
497
|
};
|
|
498
|
+
/**
|
|
499
|
+
* 占位文案。两种形态都消费它:
|
|
500
|
+
* - 缺省(`as="input"`)走原生 `::placeholder` 伪元素;
|
|
501
|
+
* - 触发器形态(`as="button"`)**没有 `::placeholder` 可用**,改由一个 span 承载
|
|
502
|
+
* (`.mh-input__placeholder`)—— 当且仅当 `hasValue === false` 且本 prop 有值时渲染它,
|
|
503
|
+
* 并**取代** `default` 插槽(见上面触发器一节)。没传 `hasValue` 的调用方行为不变。
|
|
504
|
+
*/
|
|
481
505
|
placeholder: {
|
|
482
506
|
type: StringConstructor;
|
|
483
507
|
default: undefined;
|
|
@@ -570,7 +594,8 @@ export declare const InputCompound: {
|
|
|
570
594
|
type: PropType<Record<string, unknown>>;
|
|
571
595
|
default: undefined;
|
|
572
596
|
};
|
|
573
|
-
/**
|
|
597
|
+
/** 显式覆盖清除位显隐;不给时仍按「有内容」判定(多选类复合件没有单值可判)。
|
|
598
|
+
* 同时是触发器形态「显示值还是显示占位」的判据(仅当它为 `false` 且传了 `placeholder` 时接管) */
|
|
574
599
|
hasValue: {
|
|
575
600
|
type: BooleanConstructor;
|
|
576
601
|
default: undefined;
|
|
@@ -622,6 +647,13 @@ export declare const InputCompound: {
|
|
|
622
647
|
type: StringConstructor;
|
|
623
648
|
default: string;
|
|
624
649
|
};
|
|
650
|
+
/**
|
|
651
|
+
* 占位文案。两种形态都消费它:
|
|
652
|
+
* - 缺省(`as="input"`)走原生 `::placeholder` 伪元素;
|
|
653
|
+
* - 触发器形态(`as="button"`)**没有 `::placeholder` 可用**,改由一个 span 承载
|
|
654
|
+
* (`.mh-input__placeholder`)—— 当且仅当 `hasValue === false` 且本 prop 有值时渲染它,
|
|
655
|
+
* 并**取代** `default` 插槽(见上面触发器一节)。没传 `hasValue` 的调用方行为不变。
|
|
656
|
+
*/
|
|
625
657
|
placeholder: {
|
|
626
658
|
type: StringConstructor;
|
|
627
659
|
default: undefined;
|
|
@@ -714,7 +746,8 @@ export declare const InputCompound: {
|
|
|
714
746
|
type: PropType<Record<string, unknown>>;
|
|
715
747
|
default: undefined;
|
|
716
748
|
};
|
|
717
|
-
/**
|
|
749
|
+
/** 显式覆盖清除位显隐;不给时仍按「有内容」判定(多选类复合件没有单值可判)。
|
|
750
|
+
* 同时是触发器形态「显示值还是显示占位」的判据(仅当它为 `false` 且传了 `placeholder` 时接管) */
|
|
718
751
|
hasValue: {
|
|
719
752
|
type: BooleanConstructor;
|
|
720
753
|
default: undefined;
|
|
@@ -757,6 +790,13 @@ export declare const InputCompound: {
|
|
|
757
790
|
type: StringConstructor;
|
|
758
791
|
default: string;
|
|
759
792
|
};
|
|
793
|
+
/**
|
|
794
|
+
* 占位文案。两种形态都消费它:
|
|
795
|
+
* - 缺省(`as="input"`)走原生 `::placeholder` 伪元素;
|
|
796
|
+
* - 触发器形态(`as="button"`)**没有 `::placeholder` 可用**,改由一个 span 承载
|
|
797
|
+
* (`.mh-input__placeholder`)—— 当且仅当 `hasValue === false` 且本 prop 有值时渲染它,
|
|
798
|
+
* 并**取代** `default` 插槽(见上面触发器一节)。没传 `hasValue` 的调用方行为不变。
|
|
799
|
+
*/
|
|
760
800
|
placeholder: {
|
|
761
801
|
type: StringConstructor;
|
|
762
802
|
default: undefined;
|
|
@@ -849,7 +889,8 @@ export declare const InputCompound: {
|
|
|
849
889
|
type: PropType<Record<string, unknown>>;
|
|
850
890
|
default: undefined;
|
|
851
891
|
};
|
|
852
|
-
/**
|
|
892
|
+
/** 显式覆盖清除位显隐;不给时仍按「有内容」判定(多选类复合件没有单值可判)。
|
|
893
|
+
* 同时是触发器形态「显示值还是显示占位」的判据(仅当它为 `false` 且传了 `placeholder` 时接管) */
|
|
853
894
|
hasValue: {
|
|
854
895
|
type: BooleanConstructor;
|
|
855
896
|
default: undefined;
|
|
@@ -14,6 +14,13 @@ const Input = /* @__PURE__ */ defineComponent({
|
|
|
14
14
|
type: String,
|
|
15
15
|
default: ""
|
|
16
16
|
},
|
|
17
|
+
/**
|
|
18
|
+
* 占位文案。两种形态都消费它:
|
|
19
|
+
* - 缺省(`as="input"`)走原生 `::placeholder` 伪元素;
|
|
20
|
+
* - 触发器形态(`as="button"`)**没有 `::placeholder` 可用**,改由一个 span 承载
|
|
21
|
+
* (`.mh-input__placeholder`)—— 当且仅当 `hasValue === false` 且本 prop 有值时渲染它,
|
|
22
|
+
* 并**取代** `default` 插槽(见上面触发器一节)。没传 `hasValue` 的调用方行为不变。
|
|
23
|
+
*/
|
|
17
24
|
placeholder: {
|
|
18
25
|
type: String,
|
|
19
26
|
default: void 0
|
|
@@ -95,7 +102,13 @@ const Input = /* @__PURE__ */ defineComponent({
|
|
|
95
102
|
* 整件的**表壳、附加位与状态规则一并复用**,只把「控件元素」从 `<input>` 换成 `<button>`:
|
|
96
103
|
* 于是 `Select` 这类件能拿到输入框的框与内距、内建清除位、右侧图标位与三态反馈,同时保留
|
|
97
104
|
* 原生 `<button>` 才有的语义(如 `popovertarget` 触发 popover —— 该属性只认 button)。
|
|
98
|
-
* 缺省 `as="input"` 的渲染路径除本组 prop 外零变化。
|
|
105
|
+
* 缺省 `as="input"` 的渲染路径除本组 prop 外零变化。
|
|
106
|
+
*
|
|
107
|
+
* **无值时的占位**:`<button>` 没有 `::placeholder` 伪元素可用,故 `placeholder` 在这个形态下
|
|
108
|
+
* 由本件渲染成一个 span(`.mh-input__placeholder`,色档与原生占位一致);判据是
|
|
109
|
+
* `hasValue === false && placeholder` **同时**成立 —— 两者缺一即沿用消费方给的 `default` 插槽。
|
|
110
|
+
* 这样「有值渲染值、无值渲染占位」这件例行事的文案与切换都由输入面统一,复合件不必各画一遍
|
|
111
|
+
* (`Select` 早期为自己的值区单画过一份,见 `select-trigger.tsx`)。 */
|
|
99
112
|
/** 控件元素形态:`input` 文本输入(缺省)/ `button` 触发器 */
|
|
100
113
|
as: {
|
|
101
114
|
type: String,
|
|
@@ -111,7 +124,8 @@ const Input = /* @__PURE__ */ defineComponent({
|
|
|
111
124
|
type: Object,
|
|
112
125
|
default: void 0
|
|
113
126
|
},
|
|
114
|
-
/**
|
|
127
|
+
/** 显式覆盖清除位显隐;不给时仍按「有内容」判定(多选类复合件没有单值可判)。
|
|
128
|
+
* 同时是触发器形态「显示值还是显示占位」的判据(仅当它为 `false` 且传了 `placeholder` 时接管) */
|
|
115
129
|
hasValue: {
|
|
116
130
|
type: Boolean,
|
|
117
131
|
default: void 0
|
|
@@ -149,6 +163,7 @@ const Input = /* @__PURE__ */ defineComponent({
|
|
|
149
163
|
const isTrigger = computed(() => props.as === "button");
|
|
150
164
|
const controlClasses = computed(() => ["mh-input", isTrigger.value ? "mh-input--trigger" : "", props.size === "md" ? "" : `mh-input--${props.size}`, props.bordered ? "" : "mh-input--borderless", visualStatus.value ? `is-${visualStatus.value}` : "", props.disabled ? "is-disabled" : "", props.controlClass].filter(Boolean).join(" "));
|
|
151
165
|
const showClear = computed(() => props.clearable && (props.hasValue ?? !!current.value));
|
|
166
|
+
const showPlaceholder = computed(() => isTrigger.value && props.hasValue === false && !!props.placeholder);
|
|
152
167
|
return () => createVNode("div", {
|
|
153
168
|
"class": ["mh-input__wrap", attrs.class].filter(Boolean).join(" "),
|
|
154
169
|
"style": attrs.style
|
|
@@ -164,7 +179,12 @@ const Input = /* @__PURE__ */ defineComponent({
|
|
|
164
179
|
"class": controlClasses.value,
|
|
165
180
|
"disabled": props.disabled,
|
|
166
181
|
"aria-invalid": isInvalid.value ? "true" : void 0
|
|
167
|
-
}), [
|
|
182
|
+
}), [showPlaceholder.value ? (
|
|
183
|
+
/* 触发器没有 `::placeholder`,占位文案由本件渲染(色见 `.mh-input__placeholder`) */
|
|
184
|
+
createVNode("span", {
|
|
185
|
+
"class": "mh-input__placeholder"
|
|
186
|
+
}, [props.placeholder])
|
|
187
|
+
) : slots.default ? slots.default() : null])
|
|
168
188
|
) : createVNode("input", {
|
|
169
189
|
"class": controlClasses.value,
|
|
170
190
|
"type": inputType.value,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineComponent, createVNode, ref, computed } from "vue";
|
|
1
|
+
import { defineComponent, createVNode, ref, nextTick, computed } from "vue";
|
|
2
2
|
const Radio = /* @__PURE__ */ defineComponent({
|
|
3
3
|
name: "Radio",
|
|
4
4
|
inheritAttrs: false,
|
|
@@ -136,7 +136,17 @@ const RadioGroup = /* @__PURE__ */ defineComponent({
|
|
|
136
136
|
props.onChange?.(value);
|
|
137
137
|
}
|
|
138
138
|
);
|
|
139
|
+
const rootRef = ref(null);
|
|
140
|
+
const syncNative = () => {
|
|
141
|
+
const root = rootRef.value;
|
|
142
|
+
if (!root) return;
|
|
143
|
+
root.querySelectorAll(".mh-radio__input").forEach((el) => {
|
|
144
|
+
const want = String(current.value) === el.value;
|
|
145
|
+
if (el.checked !== want) el.checked = want;
|
|
146
|
+
});
|
|
147
|
+
};
|
|
139
148
|
return () => createVNode("div", {
|
|
149
|
+
"ref": rootRef,
|
|
140
150
|
"class": ["mh-radio-group", props.vertical ? "mh-radio-group--vertical" : void 0, attrs.class].filter(Boolean).join(" "),
|
|
141
151
|
"style": attrs.style
|
|
142
152
|
}, [slots.default ? slots.default({
|
|
@@ -150,7 +160,10 @@ const RadioGroup = /* @__PURE__ */ defineComponent({
|
|
|
150
160
|
"checked": option.value === current.value,
|
|
151
161
|
"disabled": props.disabled || option.disabled,
|
|
152
162
|
"size": props.size,
|
|
153
|
-
"onChange":
|
|
163
|
+
"onChange": (value) => {
|
|
164
|
+
select(value);
|
|
165
|
+
void nextTick(syncNative);
|
|
166
|
+
}
|
|
154
167
|
}, null))]);
|
|
155
168
|
}
|
|
156
169
|
});
|
|
@@ -206,7 +219,17 @@ const RadioCard = /* @__PURE__ */ defineComponent({
|
|
|
206
219
|
props.onChange?.(value);
|
|
207
220
|
}
|
|
208
221
|
);
|
|
222
|
+
const rootRef = ref(null);
|
|
223
|
+
const syncNative = () => {
|
|
224
|
+
const root = rootRef.value;
|
|
225
|
+
if (!root) return;
|
|
226
|
+
root.querySelectorAll(".mh-radio-card__input").forEach((el) => {
|
|
227
|
+
const want = current.value === el.value;
|
|
228
|
+
if (el.checked !== want) el.checked = want;
|
|
229
|
+
});
|
|
230
|
+
};
|
|
209
231
|
return () => createVNode("div", {
|
|
232
|
+
"ref": rootRef,
|
|
210
233
|
"class": ["mh-radio-card", props.columns > 1 ? "mh-radio-card--grid" : "", attrs.class].filter(Boolean).join(" "),
|
|
211
234
|
"style": props.columns > 1 ? {
|
|
212
235
|
...attrs.style,
|
|
@@ -228,7 +251,10 @@ const RadioCard = /* @__PURE__ */ defineComponent({
|
|
|
228
251
|
"value": String(item.value),
|
|
229
252
|
"checked": isChecked,
|
|
230
253
|
"disabled": isDisabled,
|
|
231
|
-
"onChange": () =>
|
|
254
|
+
"onChange": () => {
|
|
255
|
+
select(item.value);
|
|
256
|
+
void nextTick(syncNative);
|
|
257
|
+
}
|
|
232
258
|
}, null), createVNode("span", {
|
|
233
259
|
"class": "mh-radio-card__radio",
|
|
234
260
|
"aria-hidden": "true"
|
|
@@ -76,44 +76,46 @@ const Search = /* @__PURE__ */ defineComponent({
|
|
|
76
76
|
const iconAction = computed(() => props.action === "icon");
|
|
77
77
|
const showAction = computed(() => iconAction.value || props.searchText !== void 0);
|
|
78
78
|
const actionLabel = computed(() => props.searchText ?? "搜索");
|
|
79
|
-
return () =>
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}, {
|
|
92
|
-
default: () => [iconAction.value ? {} : {
|
|
79
|
+
return () => {
|
|
80
|
+
const field = {
|
|
81
|
+
class: "mh-search__input",
|
|
82
|
+
modelValue: current.value,
|
|
83
|
+
size: props.size,
|
|
84
|
+
disabled: props.disabled,
|
|
85
|
+
placeholder: props.placeholder,
|
|
86
|
+
clearable: props.clearable,
|
|
87
|
+
onChange: commit,
|
|
88
|
+
onEnter: search
|
|
89
|
+
};
|
|
90
|
+
const fieldNode = iconAction.value ? createVNode(Input, field, null) : createVNode(Input, field, {
|
|
93
91
|
prefix: () => createVNode(Icon, {
|
|
94
92
|
"name": "search"
|
|
95
93
|
}, null)
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
94
|
+
});
|
|
95
|
+
return createVNode("div", {
|
|
96
|
+
"class": ["mh-search", attrs.class].filter(Boolean).join(" "),
|
|
97
|
+
"style": attrs.style
|
|
98
|
+
}, [fieldNode, showAction.value ? iconAction.value ? createVNode(Button, {
|
|
99
|
+
"class": "mh-search__btn",
|
|
100
|
+
"variant": "primary",
|
|
101
|
+
"size": props.size,
|
|
102
|
+
"icon": "search",
|
|
103
|
+
"title": actionLabel.value,
|
|
104
|
+
"disabled": props.disabled,
|
|
105
|
+
"loading": props.loading,
|
|
106
|
+
"onClick": search
|
|
107
|
+
}, null) : createVNode(Button, {
|
|
108
|
+
"class": "mh-search__btn",
|
|
109
|
+
"variant": "primary",
|
|
110
|
+
"size": props.size,
|
|
111
|
+
"icon": "search",
|
|
112
|
+
"disabled": props.disabled,
|
|
113
|
+
"loading": props.loading,
|
|
114
|
+
"onClick": search
|
|
115
|
+
}, {
|
|
116
|
+
default: () => props.searchText
|
|
117
|
+
}) : null]);
|
|
118
|
+
};
|
|
117
119
|
}
|
|
118
120
|
});
|
|
119
121
|
export {
|