@manohub/ui 0.10.5 → 1.0.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 +155 -155
- package/dist/components/form/index.js +8 -1
- package/dist/components/input/bordered.d.ts +15 -0
- package/dist/components/input/bordered.js +8 -0
- package/dist/components/input/index.d.ts +82 -31
- package/dist/components/input/index.js +16 -6
- package/dist/components/input/number-input.d.ts +239 -0
- package/dist/components/input/number-input.js +240 -0
- package/dist/components/select/index.d.ts +19 -0
- package/dist/components/select/index.js +10 -0
- package/dist/components/select/select-tree.d.ts +11 -0
- package/dist/components/select/select-tree.js +6 -0
- package/dist/components/select/select-trigger.d.ts +17 -0
- package/dist/components/select/select-trigger.js +9 -0
- package/dist/components/table/index.d.ts +85 -10
- package/dist/components/table/index.js +38 -9
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -0
- package/dist/styles/components/avatar.css +131 -131
- package/dist/styles/components/avatar.tokens.css +24 -24
- package/dist/styles/components/badge.css +266 -266
- package/dist/styles/components/badge.tokens.css +24 -24
- package/dist/styles/components/button.css +460 -460
- package/dist/styles/components/capsule.css +128 -128
- package/dist/styles/components/capsule.tokens.css +27 -27
- package/dist/styles/components/card.css +198 -198
- package/dist/styles/components/checkbox.css +145 -145
- package/dist/styles/components/checkbox.tokens.css +26 -26
- package/dist/styles/components/code-block.css +216 -216
- package/dist/styles/components/code-block.tokens.css +40 -40
- package/dist/styles/components/collapse.css +62 -62
- package/dist/styles/components/collapse.tokens.css +20 -20
- package/dist/styles/components/dialog.css +158 -158
- package/dist/styles/components/dialog.tokens.css +24 -24
- package/dist/styles/components/divider.css +75 -75
- package/dist/styles/components/divider.tokens.css +22 -22
- package/dist/styles/components/drawer.css +143 -143
- package/dist/styles/components/drawer.tokens.css +22 -22
- package/dist/styles/components/filter.css +29 -29
- package/dist/styles/components/form.css +456 -425
- package/dist/styles/components/form.tokens.css +19 -15
- package/dist/styles/components/input.css +659 -538
- package/dist/styles/components/input.tokens.css +43 -43
- package/dist/styles/components/layout.css +200 -200
- package/dist/styles/components/list-view.css +244 -244
- package/dist/styles/components/loading.css +76 -76
- package/dist/styles/components/loading.tokens.css +23 -23
- package/dist/styles/components/nav.css +48 -48
- package/dist/styles/components/notice.css +153 -153
- package/dist/styles/components/notice.tokens.css +17 -17
- package/dist/styles/components/notification.css +159 -159
- package/dist/styles/components/notification.tokens.css +37 -37
- package/dist/styles/components/page.css +473 -473
- package/dist/styles/components/pagination.css +303 -303
- package/dist/styles/components/pagination.tokens.css +32 -32
- package/dist/styles/components/panel.css +77 -77
- package/dist/styles/components/query-state.css +88 -88
- package/dist/styles/components/radio.css +271 -271
- package/dist/styles/components/radio.tokens.css +26 -26
- package/dist/styles/components/search.css +34 -34
- package/dist/styles/components/select.css +326 -326
- package/dist/styles/components/select.tokens.css +44 -44
- package/dist/styles/components/skeleton.css +60 -60
- package/dist/styles/components/skeleton.tokens.css +27 -27
- package/dist/styles/components/steps.css +205 -205
- package/dist/styles/components/steps.tokens.css +44 -44
- package/dist/styles/components/switch.css +165 -165
- package/dist/styles/components/switch.tokens.css +26 -26
- package/dist/styles/components/tabbar.css +235 -235
- package/dist/styles/components/tabbar.tokens.css +30 -30
- package/dist/styles/components/table.css +510 -504
- package/dist/styles/components/table.tokens.css +44 -44
- package/dist/styles/components/tag.css +127 -127
- package/dist/styles/components/tag.tokens.css +24 -24
- package/dist/styles/components/toast.css +185 -185
- package/dist/styles/components/toast.tokens.css +26 -26
- package/dist/styles/components/tooltip.css +89 -89
- package/dist/styles/components/tooltip.tokens.css +25 -25
- package/dist/styles/components/tree.css +173 -173
- package/dist/styles/components/upload.css +151 -151
- package/dist/styles/components/upload.tokens.css +27 -27
- package/dist/styles/index.css +153 -153
- package/dist/styles/operations/collapse.css +84 -84
- package/dist/styles/operations/popup.css +193 -193
- package/dist/styles/operations/tabs.css +82 -82
- package/package.json +62 -62
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { defineComponent, ref, computed, createVNode, mergeProps, createTextVNode } from "vue";
|
|
2
2
|
import { Icon } from "@manohub/icon";
|
|
3
|
+
import { borderedClassOf } from "./bordered.js";
|
|
3
4
|
const Input = /* @__PURE__ */ defineComponent({
|
|
4
5
|
name: "Input",
|
|
5
6
|
inheritAttrs: false,
|
|
@@ -67,11 +68,19 @@ const Input = /* @__PURE__ */ defineComponent({
|
|
|
67
68
|
default: false
|
|
68
69
|
},
|
|
69
70
|
/**
|
|
70
|
-
*
|
|
71
|
-
*
|
|
71
|
+
* 有无边框,三档(判定表见 `./bordered.ts`,输入族三件共用):
|
|
72
|
+
*
|
|
73
|
+
* - `true`(缺省)**常有框**;
|
|
74
|
+
* - `false` **无框** —— 常态、悬停、聚焦都不出现(本档的效果只有一种);
|
|
75
|
+
* - `'focus'` **聚焦(与悬停)才显出框** —— 行内编辑那档:常态看着像「值」,点进去才亮框;
|
|
76
|
+
* 悬停 / 聚焦的表现**复用有框档那两条**,不另写一套。
|
|
77
|
+
*
|
|
78
|
+
* 配 `readonly` 即**只读值档**(摘要 / 详情里回显业务值的推荐写法,也是 `Form.Item` 的
|
|
79
|
+
* `readonly` 行配的那一档):几何与可编辑输入框完全一致,但不给任何「可编辑」线索。
|
|
80
|
+
* 只读值档请写 `readonly` + `bordered={false}`;`readonly` + `'focus'` 会被中和(兜底,不推荐)。
|
|
72
81
|
*/
|
|
73
82
|
bordered: {
|
|
74
|
-
type: Boolean,
|
|
83
|
+
type: [Boolean, String],
|
|
75
84
|
default: true
|
|
76
85
|
},
|
|
77
86
|
maxlength: {
|
|
@@ -174,7 +183,7 @@ const Input = /* @__PURE__ */ defineComponent({
|
|
|
174
183
|
};
|
|
175
184
|
const inputType = computed(() => props.type === "password" && revealed.value ? "text" : props.type);
|
|
176
185
|
const isTrigger = computed(() => props.as === "button");
|
|
177
|
-
const controlClasses = computed(() => ["mh-input", isTrigger.value ? "mh-input--trigger" : "", props.size === "md" ? "" : `mh-input--${props.size}`, props.bordered
|
|
186
|
+
const controlClasses = computed(() => ["mh-input", isTrigger.value ? "mh-input--trigger" : "", props.size === "md" ? "" : `mh-input--${props.size}`, borderedClassOf(props.bordered), visualStatus.value ? `is-${visualStatus.value}` : "", props.disabled ? "is-disabled" : "", props.controlClass].filter(Boolean).join(" "));
|
|
178
187
|
const showClear = computed(() => props.clearable && (props.hasValue ?? !!current.value));
|
|
179
188
|
const showCounter = computed(() => !isTrigger.value && props.showCount && props.maxlength !== void 0);
|
|
180
189
|
const showPlaceholder = computed(() => isTrigger.value && props.hasValue === false && !!props.placeholder);
|
|
@@ -283,8 +292,9 @@ const Textarea = /* @__PURE__ */ defineComponent({
|
|
|
283
292
|
type: String,
|
|
284
293
|
default: void 0
|
|
285
294
|
},
|
|
295
|
+
/** 是否带边框(三档口径见 `Input` 的同名 prop:`true` 常有框 / `false` 无框 / `'focus'` 聚焦才显框) */
|
|
286
296
|
bordered: {
|
|
287
|
-
type: Boolean,
|
|
297
|
+
type: [Boolean, String],
|
|
288
298
|
default: true
|
|
289
299
|
},
|
|
290
300
|
maxlength: {
|
|
@@ -344,7 +354,7 @@ const Textarea = /* @__PURE__ */ defineComponent({
|
|
|
344
354
|
"class": ["mh-input__wrap", attrs.class].filter(Boolean).join(" "),
|
|
345
355
|
"style": attrs.style
|
|
346
356
|
}, [createVNode("textarea", mergeProps(forwardAttrs.value, {
|
|
347
|
-
"class": ["mh-input", "mh-input--textarea", props.size === "md" ? "" : `mh-input--${props.size}`, props.bordered
|
|
357
|
+
"class": ["mh-input", "mh-input--textarea", props.size === "md" ? "" : `mh-input--${props.size}`, borderedClassOf(props.bordered), visualStatus.value ? `is-${visualStatus.value}` : "", props.disabled ? "is-disabled" : ""].filter(Boolean).join(" "),
|
|
348
358
|
"rows": autoRows.value,
|
|
349
359
|
"value": current.value,
|
|
350
360
|
"placeholder": props.placeholder,
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { type PropType, type SlotsType, type VNodeChild } from 'vue';
|
|
2
|
+
import type { ControlSize } from '../control-size';
|
|
3
|
+
import type { InputStatus } from './index';
|
|
4
|
+
/**
|
|
5
|
+
* 数字输入(**自绘**)。
|
|
6
|
+
*
|
|
7
|
+
* 与 `Input` **同族同面**(同一个表壳 / 内距 / 状态协议,样式归 `styles/components/input.css`),
|
|
8
|
+
* 按 `AGENTS.md` 硬约束 1 的「同族两件可并排平级导出、共用同一目录与同一份面」处置
|
|
9
|
+
* —— 与 `Input` / `Textarea` 的关系同款,故不另开组件目录、不新增面文件。
|
|
10
|
+
*
|
|
11
|
+
* 为什么单列一个件而不是加进 `Input`:`Input` 的 `modelValue` 是 **string**,且已串起
|
|
12
|
+
* 「密码可见切换 / 清除位 / 字数统计 / 触发器形态」一条链;把数值语义(`min` / `max` / `step` /
|
|
13
|
+
* `precision` / 钳制 / 舍入 / 步进按钮)塞进去会污染统一词表(`AGENTS.md` 硬约束 8)。
|
|
14
|
+
* 本件也**不接** `Input` 的 `as="button"` 触发器形态 —— 那是复合件(`Select` 一族)的口子。
|
|
15
|
+
*
|
|
16
|
+
* 统一词表(与 `Checkbox` / `Switch` / `Input` 同款):`modelValue` / `onChange` / `disabled` / `size`;
|
|
17
|
+
* 值变更时 `emit('update:modelValue')` 与 `onChange` **双走**。
|
|
18
|
+
*
|
|
19
|
+
* ## 值域口径(三条,都是「原生数字框最被讨厌的点」的反面)
|
|
20
|
+
*
|
|
21
|
+
* 1. **空值是 `null`,不是 `0`**:留空与「填了 0」是两件事,前者不提交、后者要提交。
|
|
22
|
+
* 2. **输入中途不钳制**:`-` / `1.` / 空串这些中间态一律原样留着(打一半就被改掉会打断输入);
|
|
23
|
+
* 钳制与舍入发生在**归一时刻** —— `blur` / `Enter` / 步进按钮 / `min`·`max`·`precision` 变化。
|
|
24
|
+
* 3. **非法输入回滚**:解析不出来(如 `1a`)就退回上一个合法值,**不静默变 0**。
|
|
25
|
+
*
|
|
26
|
+
* 属性落点与 `Input` 完全同款:`class` / `style` → 表壳 `.mh-input__wrap`;其余 attrs 一律透传到
|
|
27
|
+
* 控件(`id` / `name` / `aria-*` / `data-*` / 原生事件),见 `forwardAttrs`。
|
|
28
|
+
*
|
|
29
|
+
* @example 受控 + 区间
|
|
30
|
+
* <NumberInput modelValue={timeout} min={0} max={43200} onChange={setTimeout} />
|
|
31
|
+
*
|
|
32
|
+
* @example 表格内 / 行内编辑档(无框)
|
|
33
|
+
* <NumberInput modelValue={row.value} bordered={false} precision={0} onChange={set} />
|
|
34
|
+
*
|
|
35
|
+
* @example 带单位后缀
|
|
36
|
+
* <NumberInput modelValue={size} min={1}>
|
|
37
|
+
* {{ suffix: () => 'MB' }}
|
|
38
|
+
* </NumberInput>
|
|
39
|
+
*/
|
|
40
|
+
export interface NumberInputProps {
|
|
41
|
+
/** 受控值(空值为 `null`,不是 `0`) */
|
|
42
|
+
modelValue?: number | null;
|
|
43
|
+
/** 非受控初值(缺省 `null`) */
|
|
44
|
+
defaultValue?: number | null;
|
|
45
|
+
/** 下界(含):归一时刻钳住 */
|
|
46
|
+
min?: number;
|
|
47
|
+
/** 上界(含):归一时刻钳住 */
|
|
48
|
+
max?: number;
|
|
49
|
+
/** 步进量(缺省 1);同时决定 `precision` 的缺省推断(整数步长 → 0 位小数) */
|
|
50
|
+
step?: number;
|
|
51
|
+
/** 小数位;显式给出时优先于按 `step` 推断 */
|
|
52
|
+
precision?: number;
|
|
53
|
+
placeholder?: string;
|
|
54
|
+
/** 尺寸档(与全库 `ControlSize` 同表:24 / 28 / 32) */
|
|
55
|
+
size?: ControlSize;
|
|
56
|
+
disabled?: boolean;
|
|
57
|
+
/** 只读(口径同 `Input`:值可选中复制,但不能改;步进按钮一并收掉) */
|
|
58
|
+
readonly?: boolean;
|
|
59
|
+
/** 校验态(下发 `aria-invalid`,样式走协议不看 data-*);等价于 `status="error"` */
|
|
60
|
+
invalid?: boolean;
|
|
61
|
+
/** 校验状态(`invalid` 的显式写法;两者同时给以 `status` 为准) */
|
|
62
|
+
status?: InputStatus;
|
|
63
|
+
/** 是否带边框(三档口径见 `Input` 的同名 prop:`true` / `false` / `'focus'`) */
|
|
64
|
+
bordered?: boolean | 'focus';
|
|
65
|
+
/** 是否渲染右端步进按钮(缺省 true;`readonly` / `disabled` 时自动不渲染) */
|
|
66
|
+
controls?: boolean;
|
|
67
|
+
/** 失焦时钳到 `[min, max]` 并按 `precision` 舍入(缺省 true) */
|
|
68
|
+
clampOnBlur?: boolean;
|
|
69
|
+
/** 值变更回调(模型与 `emit` 双走) */
|
|
70
|
+
onChange?: (v: number | null) => void;
|
|
71
|
+
/** 回车回调(回车会先做一次归一) */
|
|
72
|
+
onEnter?: (v: number | null) => void;
|
|
73
|
+
}
|
|
74
|
+
export declare const NumberInput: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
75
|
+
modelValue: {
|
|
76
|
+
type: PropType<number | null>;
|
|
77
|
+
default: undefined;
|
|
78
|
+
};
|
|
79
|
+
defaultValue: {
|
|
80
|
+
type: PropType<number | null>;
|
|
81
|
+
default: null;
|
|
82
|
+
};
|
|
83
|
+
min: {
|
|
84
|
+
type: NumberConstructor;
|
|
85
|
+
default: undefined;
|
|
86
|
+
};
|
|
87
|
+
max: {
|
|
88
|
+
type: NumberConstructor;
|
|
89
|
+
default: undefined;
|
|
90
|
+
};
|
|
91
|
+
step: {
|
|
92
|
+
type: NumberConstructor;
|
|
93
|
+
default: undefined;
|
|
94
|
+
};
|
|
95
|
+
precision: {
|
|
96
|
+
type: NumberConstructor;
|
|
97
|
+
default: undefined;
|
|
98
|
+
};
|
|
99
|
+
placeholder: {
|
|
100
|
+
type: StringConstructor;
|
|
101
|
+
default: undefined;
|
|
102
|
+
};
|
|
103
|
+
size: {
|
|
104
|
+
type: PropType<ControlSize>;
|
|
105
|
+
default: string;
|
|
106
|
+
};
|
|
107
|
+
disabled: {
|
|
108
|
+
type: BooleanConstructor;
|
|
109
|
+
default: boolean;
|
|
110
|
+
};
|
|
111
|
+
readonly: {
|
|
112
|
+
type: BooleanConstructor;
|
|
113
|
+
default: boolean;
|
|
114
|
+
};
|
|
115
|
+
invalid: {
|
|
116
|
+
type: BooleanConstructor;
|
|
117
|
+
default: boolean;
|
|
118
|
+
};
|
|
119
|
+
status: {
|
|
120
|
+
type: PropType<InputStatus>;
|
|
121
|
+
default: undefined;
|
|
122
|
+
};
|
|
123
|
+
/** 是否带边框(三档口径见 `Input` 的同名 prop:`true` 常有框 / `false` 无框 / `'focus'` 聚焦才显框) */
|
|
124
|
+
bordered: {
|
|
125
|
+
type: PropType<boolean | "focus">;
|
|
126
|
+
default: boolean;
|
|
127
|
+
};
|
|
128
|
+
controls: {
|
|
129
|
+
type: BooleanConstructor;
|
|
130
|
+
default: boolean;
|
|
131
|
+
};
|
|
132
|
+
clampOnBlur: {
|
|
133
|
+
type: BooleanConstructor;
|
|
134
|
+
default: boolean;
|
|
135
|
+
};
|
|
136
|
+
onChange: {
|
|
137
|
+
type: PropType<(v: number | null) => void>;
|
|
138
|
+
default: undefined;
|
|
139
|
+
};
|
|
140
|
+
onEnter: {
|
|
141
|
+
type: PropType<(v: number | null) => void>;
|
|
142
|
+
default: undefined;
|
|
143
|
+
};
|
|
144
|
+
}>, () => import("vue/jsx-runtime").JSX.Element, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
145
|
+
'update:modelValue': (v: number | null) => true;
|
|
146
|
+
}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
147
|
+
modelValue: {
|
|
148
|
+
type: PropType<number | null>;
|
|
149
|
+
default: undefined;
|
|
150
|
+
};
|
|
151
|
+
defaultValue: {
|
|
152
|
+
type: PropType<number | null>;
|
|
153
|
+
default: null;
|
|
154
|
+
};
|
|
155
|
+
min: {
|
|
156
|
+
type: NumberConstructor;
|
|
157
|
+
default: undefined;
|
|
158
|
+
};
|
|
159
|
+
max: {
|
|
160
|
+
type: NumberConstructor;
|
|
161
|
+
default: undefined;
|
|
162
|
+
};
|
|
163
|
+
step: {
|
|
164
|
+
type: NumberConstructor;
|
|
165
|
+
default: undefined;
|
|
166
|
+
};
|
|
167
|
+
precision: {
|
|
168
|
+
type: NumberConstructor;
|
|
169
|
+
default: undefined;
|
|
170
|
+
};
|
|
171
|
+
placeholder: {
|
|
172
|
+
type: StringConstructor;
|
|
173
|
+
default: undefined;
|
|
174
|
+
};
|
|
175
|
+
size: {
|
|
176
|
+
type: PropType<ControlSize>;
|
|
177
|
+
default: string;
|
|
178
|
+
};
|
|
179
|
+
disabled: {
|
|
180
|
+
type: BooleanConstructor;
|
|
181
|
+
default: boolean;
|
|
182
|
+
};
|
|
183
|
+
readonly: {
|
|
184
|
+
type: BooleanConstructor;
|
|
185
|
+
default: boolean;
|
|
186
|
+
};
|
|
187
|
+
invalid: {
|
|
188
|
+
type: BooleanConstructor;
|
|
189
|
+
default: boolean;
|
|
190
|
+
};
|
|
191
|
+
status: {
|
|
192
|
+
type: PropType<InputStatus>;
|
|
193
|
+
default: undefined;
|
|
194
|
+
};
|
|
195
|
+
/** 是否带边框(三档口径见 `Input` 的同名 prop:`true` 常有框 / `false` 无框 / `'focus'` 聚焦才显框) */
|
|
196
|
+
bordered: {
|
|
197
|
+
type: PropType<boolean | "focus">;
|
|
198
|
+
default: boolean;
|
|
199
|
+
};
|
|
200
|
+
controls: {
|
|
201
|
+
type: BooleanConstructor;
|
|
202
|
+
default: boolean;
|
|
203
|
+
};
|
|
204
|
+
clampOnBlur: {
|
|
205
|
+
type: BooleanConstructor;
|
|
206
|
+
default: boolean;
|
|
207
|
+
};
|
|
208
|
+
onChange: {
|
|
209
|
+
type: PropType<(v: number | null) => void>;
|
|
210
|
+
default: undefined;
|
|
211
|
+
};
|
|
212
|
+
onEnter: {
|
|
213
|
+
type: PropType<(v: number | null) => void>;
|
|
214
|
+
default: undefined;
|
|
215
|
+
};
|
|
216
|
+
}>> & Readonly<{
|
|
217
|
+
"onUpdate:modelValue"?: ((v: number | null) => any) | undefined;
|
|
218
|
+
}>, {
|
|
219
|
+
size: ControlSize;
|
|
220
|
+
disabled: boolean;
|
|
221
|
+
min: number;
|
|
222
|
+
max: number;
|
|
223
|
+
onChange: (v: number | null) => void;
|
|
224
|
+
invalid: boolean;
|
|
225
|
+
bordered: boolean | "focus";
|
|
226
|
+
defaultValue: number | null;
|
|
227
|
+
placeholder: string;
|
|
228
|
+
modelValue: number | null;
|
|
229
|
+
status: InputStatus;
|
|
230
|
+
step: number;
|
|
231
|
+
precision: number;
|
|
232
|
+
readonly: boolean;
|
|
233
|
+
controls: boolean;
|
|
234
|
+
clampOnBlur: boolean;
|
|
235
|
+
onEnter: (v: number | null) => void;
|
|
236
|
+
}, SlotsType<{
|
|
237
|
+
prefix?: () => VNodeChild;
|
|
238
|
+
suffix?: () => VNodeChild;
|
|
239
|
+
}>, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { defineComponent, ref, computed, watch, createVNode, mergeProps } from "vue";
|
|
2
|
+
import { Icon } from "@manohub/icon";
|
|
3
|
+
import { borderedClassOf } from "./bordered.js";
|
|
4
|
+
const NumberInput = /* @__PURE__ */ defineComponent({
|
|
5
|
+
name: "NumberInput",
|
|
6
|
+
inheritAttrs: false,
|
|
7
|
+
props: {
|
|
8
|
+
modelValue: {
|
|
9
|
+
type: Number,
|
|
10
|
+
default: void 0
|
|
11
|
+
},
|
|
12
|
+
defaultValue: {
|
|
13
|
+
type: Number,
|
|
14
|
+
default: null
|
|
15
|
+
},
|
|
16
|
+
min: {
|
|
17
|
+
type: Number,
|
|
18
|
+
default: void 0
|
|
19
|
+
},
|
|
20
|
+
max: {
|
|
21
|
+
type: Number,
|
|
22
|
+
default: void 0
|
|
23
|
+
},
|
|
24
|
+
step: {
|
|
25
|
+
type: Number,
|
|
26
|
+
default: void 0
|
|
27
|
+
},
|
|
28
|
+
precision: {
|
|
29
|
+
type: Number,
|
|
30
|
+
default: void 0
|
|
31
|
+
},
|
|
32
|
+
placeholder: {
|
|
33
|
+
type: String,
|
|
34
|
+
default: void 0
|
|
35
|
+
},
|
|
36
|
+
size: {
|
|
37
|
+
type: String,
|
|
38
|
+
default: "md"
|
|
39
|
+
},
|
|
40
|
+
disabled: {
|
|
41
|
+
type: Boolean,
|
|
42
|
+
default: false
|
|
43
|
+
},
|
|
44
|
+
readonly: {
|
|
45
|
+
type: Boolean,
|
|
46
|
+
default: false
|
|
47
|
+
},
|
|
48
|
+
invalid: {
|
|
49
|
+
type: Boolean,
|
|
50
|
+
default: false
|
|
51
|
+
},
|
|
52
|
+
status: {
|
|
53
|
+
type: String,
|
|
54
|
+
default: void 0
|
|
55
|
+
},
|
|
56
|
+
/** 是否带边框(三档口径见 `Input` 的同名 prop:`true` 常有框 / `false` 无框 / `'focus'` 聚焦才显框) */
|
|
57
|
+
bordered: {
|
|
58
|
+
type: [Boolean, String],
|
|
59
|
+
default: true
|
|
60
|
+
},
|
|
61
|
+
controls: {
|
|
62
|
+
type: Boolean,
|
|
63
|
+
default: true
|
|
64
|
+
},
|
|
65
|
+
clampOnBlur: {
|
|
66
|
+
type: Boolean,
|
|
67
|
+
default: true
|
|
68
|
+
},
|
|
69
|
+
onChange: {
|
|
70
|
+
type: Function,
|
|
71
|
+
default: void 0
|
|
72
|
+
},
|
|
73
|
+
onEnter: {
|
|
74
|
+
type: Function,
|
|
75
|
+
default: void 0
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
emits: {
|
|
79
|
+
"update:modelValue": (v) => true
|
|
80
|
+
},
|
|
81
|
+
slots: Object,
|
|
82
|
+
setup(props, {
|
|
83
|
+
emit,
|
|
84
|
+
slots,
|
|
85
|
+
attrs
|
|
86
|
+
}) {
|
|
87
|
+
const inner = ref(props.defaultValue ?? null);
|
|
88
|
+
const current = computed(() => props.modelValue === void 0 ? inner.value : props.modelValue);
|
|
89
|
+
const focused = ref(false);
|
|
90
|
+
const resolvedStep = computed(() => props.step !== void 0 && props.step > 0 ? props.step : 1);
|
|
91
|
+
const resolvedPrecision = computed(() => {
|
|
92
|
+
if (props.precision !== void 0 && props.precision >= 0) return Math.floor(props.precision);
|
|
93
|
+
const text = String(resolvedStep.value);
|
|
94
|
+
const dot = text.indexOf(".");
|
|
95
|
+
return dot < 0 ? 0 : text.length - dot - 1;
|
|
96
|
+
});
|
|
97
|
+
const roundTo = (value, precision) => {
|
|
98
|
+
const factor = 10 ** precision;
|
|
99
|
+
return Math.round(value * factor) / factor;
|
|
100
|
+
};
|
|
101
|
+
const clampValue = (value) => {
|
|
102
|
+
let next = value;
|
|
103
|
+
if (props.min !== void 0 && next < props.min) next = props.min;
|
|
104
|
+
if (props.max !== void 0 && next > props.max) next = props.max;
|
|
105
|
+
return next;
|
|
106
|
+
};
|
|
107
|
+
const normalize = (value) => clampValue(roundTo(value, resolvedPrecision.value));
|
|
108
|
+
const textOf = (value) => value === null || value === void 0 ? "" : String(roundTo(value, resolvedPrecision.value));
|
|
109
|
+
const parseInput = (raw) => {
|
|
110
|
+
const text = raw.trim();
|
|
111
|
+
if (!text) return null;
|
|
112
|
+
if (!/^[+-]?(\d+\.?\d*|\.\d+)$/.test(text)) return "invalid";
|
|
113
|
+
const value = Number(text);
|
|
114
|
+
return Number.isFinite(value) ? value : "invalid";
|
|
115
|
+
};
|
|
116
|
+
const draft = ref(textOf(current.value));
|
|
117
|
+
const display = computed(() => props.modelValue === void 0 ? draft.value : textOf(props.modelValue));
|
|
118
|
+
const inputRef = ref(null);
|
|
119
|
+
const syncDom = () => {
|
|
120
|
+
const el = inputRef.value;
|
|
121
|
+
if (el && el.value !== display.value) el.value = display.value;
|
|
122
|
+
};
|
|
123
|
+
const setModel = (value) => {
|
|
124
|
+
if (props.modelValue === void 0) inner.value = value;
|
|
125
|
+
emit("update:modelValue", value);
|
|
126
|
+
props.onChange?.(value);
|
|
127
|
+
};
|
|
128
|
+
const commit = (value) => {
|
|
129
|
+
setModel(value);
|
|
130
|
+
draft.value = textOf(value);
|
|
131
|
+
syncDom();
|
|
132
|
+
};
|
|
133
|
+
watch(current, (value) => {
|
|
134
|
+
if (!focused.value) draft.value = textOf(value);
|
|
135
|
+
});
|
|
136
|
+
watch(() => [props.min, props.max, props.precision, props.step], () => {
|
|
137
|
+
if (current.value === null) return;
|
|
138
|
+
const next = normalize(current.value);
|
|
139
|
+
if (next !== current.value) commit(next);
|
|
140
|
+
});
|
|
141
|
+
const forwardAttrs = computed(() => {
|
|
142
|
+
const rest = {};
|
|
143
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
144
|
+
if (key !== "class" && key !== "style") rest[key] = value;
|
|
145
|
+
}
|
|
146
|
+
return rest;
|
|
147
|
+
});
|
|
148
|
+
const visualStatus = computed(() => props.status ?? (props.invalid ? "error" : void 0));
|
|
149
|
+
const isInvalid = computed(() => visualStatus.value === "error");
|
|
150
|
+
const onInput = (event) => {
|
|
151
|
+
const el = event.target;
|
|
152
|
+
draft.value = el.value;
|
|
153
|
+
const parsed = parseInput(el.value);
|
|
154
|
+
if (parsed === "invalid") return;
|
|
155
|
+
setModel(parsed);
|
|
156
|
+
if (props.modelValue !== void 0 && props.modelValue !== parsed) syncDom();
|
|
157
|
+
};
|
|
158
|
+
const settle = () => {
|
|
159
|
+
const parsed = parseInput(draft.value);
|
|
160
|
+
if (parsed === "invalid") {
|
|
161
|
+
draft.value = textOf(current.value);
|
|
162
|
+
syncDom();
|
|
163
|
+
return current.value;
|
|
164
|
+
}
|
|
165
|
+
if (parsed === null) {
|
|
166
|
+
commit(null);
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
const next = props.clampOnBlur ? normalize(parsed) : roundTo(parsed, resolvedPrecision.value);
|
|
170
|
+
commit(next);
|
|
171
|
+
return next;
|
|
172
|
+
};
|
|
173
|
+
const onFocus = () => {
|
|
174
|
+
focused.value = true;
|
|
175
|
+
};
|
|
176
|
+
const onBlur = () => {
|
|
177
|
+
focused.value = false;
|
|
178
|
+
settle();
|
|
179
|
+
};
|
|
180
|
+
const onKeydown = (event) => {
|
|
181
|
+
if (event.isComposing) return;
|
|
182
|
+
if (event.key === "Enter") {
|
|
183
|
+
const value = settle();
|
|
184
|
+
props.onEnter?.(value);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (event.key === "ArrowUp" || event.key === "ArrowDown") {
|
|
188
|
+
if (props.disabled || props.readonly) return;
|
|
189
|
+
event.preventDefault();
|
|
190
|
+
stepBy(event.key === "ArrowUp" ? 1 : -1);
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
const stepBy = (direction) => {
|
|
194
|
+
if (props.disabled || props.readonly) return;
|
|
195
|
+
const base = current.value ?? (props.min !== void 0 ? props.min : 0);
|
|
196
|
+
commit(normalize(base + direction * resolvedStep.value));
|
|
197
|
+
};
|
|
198
|
+
const atMax = computed(() => props.max !== void 0 && current.value !== null && current.value >= props.max);
|
|
199
|
+
const atMin = computed(() => props.min !== void 0 && current.value !== null && current.value <= props.min);
|
|
200
|
+
const showControls = computed(() => props.controls && !props.disabled && !props.readonly);
|
|
201
|
+
const controlClasses = computed(() => ["mh-input", "mh-input--number", props.size === "md" ? "" : `mh-input--${props.size}`, borderedClassOf(props.bordered), visualStatus.value ? `is-${visualStatus.value}` : "", props.disabled ? "is-disabled" : ""].filter(Boolean).join(" "));
|
|
202
|
+
const stepButton = (direction) => createVNode("button", {
|
|
203
|
+
"type": "button",
|
|
204
|
+
"class": "mh-input__step-btn",
|
|
205
|
+
"aria-label": direction === 1 ? "增加" : "减少",
|
|
206
|
+
"tabindex": -1,
|
|
207
|
+
"disabled": direction === 1 ? atMax.value : atMin.value,
|
|
208
|
+
"onClick": () => stepBy(direction)
|
|
209
|
+
}, [createVNode(Icon, {
|
|
210
|
+
"name": direction === 1 ? "chevron-up" : "chevron-down"
|
|
211
|
+
}, null)]);
|
|
212
|
+
return () => createVNode("div", {
|
|
213
|
+
"class": ["mh-input__wrap", attrs.class].filter(Boolean).join(" "),
|
|
214
|
+
"style": attrs.style
|
|
215
|
+
}, [slots.prefix ? createVNode("span", {
|
|
216
|
+
"class": "mh-input__prefix"
|
|
217
|
+
}, [slots.prefix()]) : null, createVNode("input", mergeProps(forwardAttrs.value, {
|
|
218
|
+
"ref": inputRef,
|
|
219
|
+
"class": controlClasses.value,
|
|
220
|
+
"type": "text",
|
|
221
|
+
"inputmode": "decimal",
|
|
222
|
+
"value": display.value,
|
|
223
|
+
"placeholder": props.placeholder,
|
|
224
|
+
"disabled": props.disabled,
|
|
225
|
+
"readonly": props.readonly,
|
|
226
|
+
"aria-invalid": isInvalid.value ? "true" : void 0,
|
|
227
|
+
"onInput": onInput,
|
|
228
|
+
"onKeydown": onKeydown,
|
|
229
|
+
"onFocus": onFocus,
|
|
230
|
+
"onBlur": onBlur
|
|
231
|
+
}), null), showControls.value ? createVNode("span", {
|
|
232
|
+
"class": "mh-input__step"
|
|
233
|
+
}, [stepButton(1), stepButton(-1)]) : null, slots.suffix ? createVNode("span", {
|
|
234
|
+
"class": "mh-input__suffix"
|
|
235
|
+
}, [slots.suffix()]) : null]);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
export {
|
|
239
|
+
NumberInput
|
|
240
|
+
};
|
|
@@ -132,6 +132,15 @@ export declare const Select: import("vue").DefineComponent<import("vue").Extract
|
|
|
132
132
|
type: PropType<InputStatus>;
|
|
133
133
|
default: undefined;
|
|
134
134
|
};
|
|
135
|
+
/**
|
|
136
|
+
* 有无边框(框由触发器 `<Input as="button">` 带,三档口径与 `Input` 的同名 prop 完全一致):
|
|
137
|
+
* `true`(缺省)常有框 / `false` 无框(常态、悬停、聚焦都不显)/ `'focus'` 聚焦(与悬停)才显框。
|
|
138
|
+
* 表格单元格里想要「常态像值、点进去才亮框」就用 `'focus'`。
|
|
139
|
+
*/
|
|
140
|
+
bordered: {
|
|
141
|
+
type: PropType<boolean | "focus">;
|
|
142
|
+
default: boolean;
|
|
143
|
+
};
|
|
135
144
|
/** 加载中(面板里出一行提示,不渲染选项) */
|
|
136
145
|
loading: {
|
|
137
146
|
type: BooleanConstructor;
|
|
@@ -221,6 +230,15 @@ export declare const Select: import("vue").DefineComponent<import("vue").Extract
|
|
|
221
230
|
type: PropType<InputStatus>;
|
|
222
231
|
default: undefined;
|
|
223
232
|
};
|
|
233
|
+
/**
|
|
234
|
+
* 有无边框(框由触发器 `<Input as="button">` 带,三档口径与 `Input` 的同名 prop 完全一致):
|
|
235
|
+
* `true`(缺省)常有框 / `false` 无框(常态、悬停、聚焦都不显)/ `'focus'` 聚焦(与悬停)才显框。
|
|
236
|
+
* 表格单元格里想要「常态像值、点进去才亮框」就用 `'focus'`。
|
|
237
|
+
*/
|
|
238
|
+
bordered: {
|
|
239
|
+
type: PropType<boolean | "focus">;
|
|
240
|
+
default: boolean;
|
|
241
|
+
};
|
|
224
242
|
/** 加载中(面板里出一行提示,不渲染选项) */
|
|
225
243
|
loading: {
|
|
226
244
|
type: BooleanConstructor;
|
|
@@ -248,6 +266,7 @@ export declare const Select: import("vue").DefineComponent<import("vue").Extract
|
|
|
248
266
|
disabled: boolean;
|
|
249
267
|
onChange: (v: SelectValue) => void;
|
|
250
268
|
loading: boolean;
|
|
269
|
+
bordered: boolean | "focus";
|
|
251
270
|
options: SelectItem[];
|
|
252
271
|
defaultValue: SelectValue | undefined;
|
|
253
272
|
placeholder: string;
|
|
@@ -74,6 +74,15 @@ const Select = /* @__PURE__ */ defineComponent({
|
|
|
74
74
|
type: String,
|
|
75
75
|
default: void 0
|
|
76
76
|
},
|
|
77
|
+
/**
|
|
78
|
+
* 有无边框(框由触发器 `<Input as="button">` 带,三档口径与 `Input` 的同名 prop 完全一致):
|
|
79
|
+
* `true`(缺省)常有框 / `false` 无框(常态、悬停、聚焦都不显)/ `'focus'` 聚焦(与悬停)才显框。
|
|
80
|
+
* 表格单元格里想要「常态像值、点进去才亮框」就用 `'focus'`。
|
|
81
|
+
*/
|
|
82
|
+
bordered: {
|
|
83
|
+
type: [Boolean, String],
|
|
84
|
+
default: true
|
|
85
|
+
},
|
|
77
86
|
/** 加载中(面板里出一行提示,不渲染选项) */
|
|
78
87
|
loading: {
|
|
79
88
|
type: Boolean,
|
|
@@ -239,6 +248,7 @@ const Select = /* @__PURE__ */ defineComponent({
|
|
|
239
248
|
"size": props.size,
|
|
240
249
|
"status": props.status,
|
|
241
250
|
"disabled": props.disabled,
|
|
251
|
+
"bordered": props.bordered,
|
|
242
252
|
"clearable": props.clearable,
|
|
243
253
|
"hasValue": hasValue,
|
|
244
254
|
"multiple": props.multiple,
|
|
@@ -129,6 +129,11 @@ export declare const SelectTree: import("vue").DefineComponent<import("vue").Ext
|
|
|
129
129
|
type: PropType<InputStatus>;
|
|
130
130
|
default: undefined;
|
|
131
131
|
};
|
|
132
|
+
/** 有无边框(框由触发器 `<Input as="button">` 带,三档口径与 `Input` 的同名 prop 一致) */
|
|
133
|
+
bordered: {
|
|
134
|
+
type: PropType<boolean | "focus">;
|
|
135
|
+
default: boolean;
|
|
136
|
+
};
|
|
132
137
|
/** 值变更回调(选中叶子的主键) */
|
|
133
138
|
onChange: {
|
|
134
139
|
type: PropType<(v: string) => void>;
|
|
@@ -248,6 +253,11 @@ export declare const SelectTree: import("vue").DefineComponent<import("vue").Ext
|
|
|
248
253
|
type: PropType<InputStatus>;
|
|
249
254
|
default: undefined;
|
|
250
255
|
};
|
|
256
|
+
/** 有无边框(框由触发器 `<Input as="button">` 带,三档口径与 `Input` 的同名 prop 一致) */
|
|
257
|
+
bordered: {
|
|
258
|
+
type: PropType<boolean | "focus">;
|
|
259
|
+
default: boolean;
|
|
260
|
+
};
|
|
251
261
|
/** 值变更回调(选中叶子的主键) */
|
|
252
262
|
onChange: {
|
|
253
263
|
type: PropType<(v: string) => void>;
|
|
@@ -283,6 +293,7 @@ export declare const SelectTree: import("vue").DefineComponent<import("vue").Ext
|
|
|
283
293
|
defaultExpandedKeys: string[];
|
|
284
294
|
defaultExpandAll: boolean;
|
|
285
295
|
onExpandChange: (keys: string[]) => void;
|
|
296
|
+
bordered: boolean | "focus";
|
|
286
297
|
defaultValue: string;
|
|
287
298
|
placeholder: string;
|
|
288
299
|
modelValue: string;
|
|
@@ -104,6 +104,11 @@ const SelectTree = /* @__PURE__ */ defineComponent({
|
|
|
104
104
|
type: String,
|
|
105
105
|
default: void 0
|
|
106
106
|
},
|
|
107
|
+
/** 有无边框(框由触发器 `<Input as="button">` 带,三档口径与 `Input` 的同名 prop 一致) */
|
|
108
|
+
bordered: {
|
|
109
|
+
type: [Boolean, String],
|
|
110
|
+
default: true
|
|
111
|
+
},
|
|
107
112
|
/** 值变更回调(选中叶子的主键) */
|
|
108
113
|
onChange: {
|
|
109
114
|
type: Function,
|
|
@@ -211,6 +216,7 @@ const SelectTree = /* @__PURE__ */ defineComponent({
|
|
|
211
216
|
"size": props.size,
|
|
212
217
|
"status": props.status,
|
|
213
218
|
"disabled": props.disabled,
|
|
219
|
+
"bordered": props.bordered,
|
|
214
220
|
"clearable": props.clearable,
|
|
215
221
|
"hasValue": hasValue.value,
|
|
216
222
|
"placeholder": props.placeholder,
|