@manohub/ui 1.0.0 → 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/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/form.css +14 -0
- package/dist/styles/components/form.tokens.css +4 -0
- package/dist/styles/components/input.css +159 -38
- package/dist/styles/components/table.css +6 -0
- package/package.json +2 -2
|
@@ -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,
|
|
@@ -21,6 +21,14 @@ export declare const SelectTrigger: import("vue").DefineComponent<import("vue").
|
|
|
21
21
|
type: BooleanConstructor;
|
|
22
22
|
default: boolean;
|
|
23
23
|
};
|
|
24
|
+
/**
|
|
25
|
+
* 有无边框(**直接透传给输入面**,三档口径与 `Input` 的同名 prop 完全一致):
|
|
26
|
+
* `true` 常有框 / `false` 无框(任何状态都不显)/ `'focus'` 聚焦(与悬停)才显框。
|
|
27
|
+
*/
|
|
28
|
+
bordered: {
|
|
29
|
+
type: PropType<boolean | "focus">;
|
|
30
|
+
default: boolean;
|
|
31
|
+
};
|
|
24
32
|
clearable: {
|
|
25
33
|
type: BooleanConstructor;
|
|
26
34
|
default: boolean;
|
|
@@ -90,6 +98,14 @@ export declare const SelectTrigger: import("vue").DefineComponent<import("vue").
|
|
|
90
98
|
type: BooleanConstructor;
|
|
91
99
|
default: boolean;
|
|
92
100
|
};
|
|
101
|
+
/**
|
|
102
|
+
* 有无边框(**直接透传给输入面**,三档口径与 `Input` 的同名 prop 完全一致):
|
|
103
|
+
* `true` 常有框 / `false` 无框(任何状态都不显)/ `'focus'` 聚焦(与悬停)才显框。
|
|
104
|
+
*/
|
|
105
|
+
bordered: {
|
|
106
|
+
type: PropType<boolean | "focus">;
|
|
107
|
+
default: boolean;
|
|
108
|
+
};
|
|
93
109
|
clearable: {
|
|
94
110
|
type: BooleanConstructor;
|
|
95
111
|
default: boolean;
|
|
@@ -145,6 +161,7 @@ export declare const SelectTrigger: import("vue").DefineComponent<import("vue").
|
|
|
145
161
|
disabled: boolean;
|
|
146
162
|
label: string;
|
|
147
163
|
onKeydown: (event: KeyboardEvent) => void;
|
|
164
|
+
bordered: boolean | "focus";
|
|
148
165
|
placeholder: string;
|
|
149
166
|
status: InputStatus;
|
|
150
167
|
clearable: boolean;
|
|
@@ -24,6 +24,14 @@ const SelectTrigger = /* @__PURE__ */ defineComponent({
|
|
|
24
24
|
type: Boolean,
|
|
25
25
|
default: false
|
|
26
26
|
},
|
|
27
|
+
/**
|
|
28
|
+
* 有无边框(**直接透传给输入面**,三档口径与 `Input` 的同名 prop 完全一致):
|
|
29
|
+
* `true` 常有框 / `false` 无框(任何状态都不显)/ `'focus'` 聚焦(与悬停)才显框。
|
|
30
|
+
*/
|
|
31
|
+
bordered: {
|
|
32
|
+
type: [Boolean, String],
|
|
33
|
+
default: true
|
|
34
|
+
},
|
|
27
35
|
clearable: {
|
|
28
36
|
type: Boolean,
|
|
29
37
|
default: false
|
|
@@ -81,6 +89,7 @@ const SelectTrigger = /* @__PURE__ */ defineComponent({
|
|
|
81
89
|
"size": props.size,
|
|
82
90
|
"status": props.status,
|
|
83
91
|
"disabled": props.disabled,
|
|
92
|
+
"bordered": props.bordered,
|
|
84
93
|
"clearable": props.clearable,
|
|
85
94
|
"hasValue": props.hasValue,
|
|
86
95
|
"placeholder": props.placeholder,
|
|
@@ -50,6 +50,36 @@ export interface TableRowProps {
|
|
|
50
50
|
class?: string;
|
|
51
51
|
onClick?: (row: TableRow, index: number, event: MouseEvent) => void;
|
|
52
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* `#cell` 插槽的作用域 —— **单元格编辑走这条**。
|
|
55
|
+
*
|
|
56
|
+
* 表格**不替调用方渲染控件**:编辑格要摆什么(`Input` / `Select` / `NumberInput`、外层要不要套
|
|
57
|
+
* `Form.Item` 拿 label 位与错误文案位、只读态摆什么)全由调用方在插槽里决定。口径与 `Form.Item`
|
|
58
|
+
* 同款 —— 它的默认插槽也是「你自己放控件」;骨架层因此不必猜每种业务格子的排版,
|
|
59
|
+
* 也不会把「哪一格长什么样」的开关堆成一张越来越长的 prop 表。
|
|
60
|
+
*
|
|
61
|
+
* **回写是受控的**:`setValue(next)` 只把「这一格变成了什么」上报出去
|
|
62
|
+
* (= 发一次 `onCellChange(row, column.key, next, index)`)。本件**从不改 `data`** ——
|
|
63
|
+
* 新数组与「这次改动该不该被接受」都由调用方决定,与行选中同一套受控口径。
|
|
64
|
+
*
|
|
65
|
+
* @example 编辑格:套 `Form.Item` 拿错误位,控件自己绑值、回写走 `setValue`
|
|
66
|
+
* <Table :columns="columns" :data="rows" row-key="id" layout="fixed" :on-cell-change="write">
|
|
67
|
+
* <template #cell="{ row, column, value, index, setValue }">
|
|
68
|
+
* <!-- 格子里的框由控件自己带:默认有框 / `:bordered="false"` 无框 / `bordered="focus"` 聚焦才显框 -->
|
|
69
|
+
* <Input v-if="column.key === 'name'" :model-value="String(value ?? '')" @change="setValue" />
|
|
70
|
+
* <template v-else>{{ value }}</template>
|
|
71
|
+
* </template>
|
|
72
|
+
* </Table>
|
|
73
|
+
*/
|
|
74
|
+
export interface TableCellSlotScope {
|
|
75
|
+
row: TableRow;
|
|
76
|
+
column: TableColumn;
|
|
77
|
+
/** 该格当前值(`row[column.key]` 的原值;表格不改写它,也不做任何格式化) */
|
|
78
|
+
value: unknown;
|
|
79
|
+
index: number;
|
|
80
|
+
/** 上报该格的新值(= 发一次 `onCellChange`);调用方没接 `onCellChange` 时是空操作 */
|
|
81
|
+
setValue: (next: unknown) => void;
|
|
82
|
+
}
|
|
53
83
|
/**
|
|
54
84
|
* 行级可勾选判定(`selectable` 的函数形态):返回 `false` 的行渲染**禁用态**勾选框,
|
|
55
85
|
* 且不进表头全选的增删范围。签名与 `onRow` / `rowClass` 一致(都给 `index`)。
|
|
@@ -150,6 +180,17 @@ export declare const Table: import("vue").DefineComponent<import("vue").ExtractP
|
|
|
150
180
|
type: BooleanConstructor;
|
|
151
181
|
default: boolean;
|
|
152
182
|
};
|
|
183
|
+
/**
|
|
184
|
+
* 单元格值变更 —— 由 `#cell` 插槽里的 `setValue` 触发,是**唯一的回写通道**。
|
|
185
|
+
*
|
|
186
|
+
* 本件**不改 `data`**(与本仓表格一贯的克制口径一致:全受控、只发事件、不排序、不切片);
|
|
187
|
+
* 要不要接受、以及新数组怎么产生,都由调用方决定。
|
|
188
|
+
* 没接这个回调时 `setValue` 是空操作(插槽里的控件照旧可绑,不会报错)。
|
|
189
|
+
*/
|
|
190
|
+
onCellChange: {
|
|
191
|
+
type: PropType<(row: TableRow, key: string, value: unknown, index: number) => void>;
|
|
192
|
+
default: undefined;
|
|
193
|
+
};
|
|
153
194
|
/** 尺寸档:`sm` 紧凑(表格嵌在卡片里)/ `md` 标准 / `lg` 宽松(详情页) */
|
|
154
195
|
size: {
|
|
155
196
|
type: PropType<ControlSize>;
|
|
@@ -160,7 +201,18 @@ export declare const Table: import("vue").DefineComponent<import("vue").ExtractP
|
|
|
160
201
|
type: BooleanConstructor;
|
|
161
202
|
default: boolean;
|
|
162
203
|
};
|
|
163
|
-
|
|
204
|
+
/**
|
|
205
|
+
* 有无外框(缺省**有**框):去外框写 `:bordered="false"`。
|
|
206
|
+
*
|
|
207
|
+
* **极性口径**:本库的「有无外框」一律用**肯定式** `bordered`(`Input` / `Textarea` /
|
|
208
|
+
* `NumberInput` / `Nav` 同款,见 `docs/guide/naming-and-defaults.md` §2.1)。本件原名
|
|
209
|
+
* `borderless`(否定式、默认 `false`),2026-10 按同名同义统一改成 `bordered` —— 默认行为不变
|
|
210
|
+
* (都是「缺省有框」),只是名字的极性换到与其余件一致,调用方不必再记「哪件用否定式」。
|
|
211
|
+
*
|
|
212
|
+
* 类名仍是 `.mh-table--borderless`:面层词根**统一为否定式**(输入族关框时也输出它),
|
|
213
|
+
* 与 prop 的极性相反 —— 两套各自内部一致,`Input` 的文档页记着同一处口径。
|
|
214
|
+
*/
|
|
215
|
+
bordered: {
|
|
164
216
|
type: BooleanConstructor;
|
|
165
217
|
default: boolean;
|
|
166
218
|
};
|
|
@@ -297,6 +349,17 @@ export declare const Table: import("vue").DefineComponent<import("vue").ExtractP
|
|
|
297
349
|
type: BooleanConstructor;
|
|
298
350
|
default: boolean;
|
|
299
351
|
};
|
|
352
|
+
/**
|
|
353
|
+
* 单元格值变更 —— 由 `#cell` 插槽里的 `setValue` 触发,是**唯一的回写通道**。
|
|
354
|
+
*
|
|
355
|
+
* 本件**不改 `data`**(与本仓表格一贯的克制口径一致:全受控、只发事件、不排序、不切片);
|
|
356
|
+
* 要不要接受、以及新数组怎么产生,都由调用方决定。
|
|
357
|
+
* 没接这个回调时 `setValue` 是空操作(插槽里的控件照旧可绑,不会报错)。
|
|
358
|
+
*/
|
|
359
|
+
onCellChange: {
|
|
360
|
+
type: PropType<(row: TableRow, key: string, value: unknown, index: number) => void>;
|
|
361
|
+
default: undefined;
|
|
362
|
+
};
|
|
300
363
|
/** 尺寸档:`sm` 紧凑(表格嵌在卡片里)/ `md` 标准 / `lg` 宽松(详情页) */
|
|
301
364
|
size: {
|
|
302
365
|
type: PropType<ControlSize>;
|
|
@@ -307,7 +370,18 @@ export declare const Table: import("vue").DefineComponent<import("vue").ExtractP
|
|
|
307
370
|
type: BooleanConstructor;
|
|
308
371
|
default: boolean;
|
|
309
372
|
};
|
|
310
|
-
|
|
373
|
+
/**
|
|
374
|
+
* 有无外框(缺省**有**框):去外框写 `:bordered="false"`。
|
|
375
|
+
*
|
|
376
|
+
* **极性口径**:本库的「有无外框」一律用**肯定式** `bordered`(`Input` / `Textarea` /
|
|
377
|
+
* `NumberInput` / `Nav` 同款,见 `docs/guide/naming-and-defaults.md` §2.1)。本件原名
|
|
378
|
+
* `borderless`(否定式、默认 `false`),2026-10 按同名同义统一改成 `bordered` —— 默认行为不变
|
|
379
|
+
* (都是「缺省有框」),只是名字的极性换到与其余件一致,调用方不必再记「哪件用否定式」。
|
|
380
|
+
*
|
|
381
|
+
* 类名仍是 `.mh-table--borderless`:面层词根**统一为否定式**(输入族关框时也输出它),
|
|
382
|
+
* 与 prop 的极性相反 —— 两套各自内部一致,`Input` 的文档页记着同一处口径。
|
|
383
|
+
*/
|
|
384
|
+
bordered: {
|
|
311
385
|
type: BooleanConstructor;
|
|
312
386
|
default: boolean;
|
|
313
387
|
};
|
|
@@ -414,11 +488,12 @@ export declare const Table: import("vue").DefineComponent<import("vue").ExtractP
|
|
|
414
488
|
expandedKeys: (string | number)[];
|
|
415
489
|
defaultExpandedKeys: (string | number)[];
|
|
416
490
|
onExpandChange: (keys: Array<string | number>) => void;
|
|
491
|
+
bordered: boolean;
|
|
417
492
|
modelValue: (string | number)[];
|
|
418
493
|
emptyText: string;
|
|
419
494
|
selectable: boolean | TableRowSelectable;
|
|
420
495
|
striped: boolean;
|
|
421
|
-
|
|
496
|
+
onCellChange: (row: TableRow, key: string, value: unknown, index: number) => void;
|
|
422
497
|
stickyHead: boolean;
|
|
423
498
|
stickyCol: boolean;
|
|
424
499
|
showHeader: boolean;
|
|
@@ -429,13 +504,13 @@ export declare const Table: import("vue").DefineComponent<import("vue").ExtractP
|
|
|
429
504
|
expandable: (row: TableRow, index: number) => VNodeChild;
|
|
430
505
|
pagination: false | TablePagination;
|
|
431
506
|
}, SlotsType<{
|
|
432
|
-
/**
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
507
|
+
/**
|
|
508
|
+
* 单元格:`{ row, column, value, index, setValue }`(不给则直接输出原值)。
|
|
509
|
+
*
|
|
510
|
+
* **编辑格也走这里**(见 `TableCellSlotScope` 的说明):本件不替调用方渲染控件,
|
|
511
|
+
* 只给值、给位置、给一个受控的回写口 `setValue`。
|
|
512
|
+
*/
|
|
513
|
+
cell?: (context: TableCellSlotScope) => VNodeChild;
|
|
439
514
|
/** 行尾操作区:`{ row, index }`(面里用 `align: 'right'` 的列承载) */
|
|
440
515
|
actions?: (context: {
|
|
441
516
|
row: TableRow;
|
|
@@ -43,6 +43,17 @@ const Table = /* @__PURE__ */ defineComponent({
|
|
|
43
43
|
type: Boolean,
|
|
44
44
|
default: false
|
|
45
45
|
},
|
|
46
|
+
/**
|
|
47
|
+
* 单元格值变更 —— 由 `#cell` 插槽里的 `setValue` 触发,是**唯一的回写通道**。
|
|
48
|
+
*
|
|
49
|
+
* 本件**不改 `data`**(与本仓表格一贯的克制口径一致:全受控、只发事件、不排序、不切片);
|
|
50
|
+
* 要不要接受、以及新数组怎么产生,都由调用方决定。
|
|
51
|
+
* 没接这个回调时 `setValue` 是空操作(插槽里的控件照旧可绑,不会报错)。
|
|
52
|
+
*/
|
|
53
|
+
onCellChange: {
|
|
54
|
+
type: Function,
|
|
55
|
+
default: void 0
|
|
56
|
+
},
|
|
46
57
|
/** 尺寸档:`sm` 紧凑(表格嵌在卡片里)/ `md` 标准 / `lg` 宽松(详情页) */
|
|
47
58
|
size: {
|
|
48
59
|
type: String,
|
|
@@ -53,9 +64,20 @@ const Table = /* @__PURE__ */ defineComponent({
|
|
|
53
64
|
type: Boolean,
|
|
54
65
|
default: false
|
|
55
66
|
},
|
|
56
|
-
|
|
67
|
+
/**
|
|
68
|
+
* 有无外框(缺省**有**框):去外框写 `:bordered="false"`。
|
|
69
|
+
*
|
|
70
|
+
* **极性口径**:本库的「有无外框」一律用**肯定式** `bordered`(`Input` / `Textarea` /
|
|
71
|
+
* `NumberInput` / `Nav` 同款,见 `docs/guide/naming-and-defaults.md` §2.1)。本件原名
|
|
72
|
+
* `borderless`(否定式、默认 `false`),2026-10 按同名同义统一改成 `bordered` —— 默认行为不变
|
|
73
|
+
* (都是「缺省有框」),只是名字的极性换到与其余件一致,调用方不必再记「哪件用否定式」。
|
|
74
|
+
*
|
|
75
|
+
* 类名仍是 `.mh-table--borderless`:面层词根**统一为否定式**(输入族关框时也输出它),
|
|
76
|
+
* 与 prop 的极性相反 —— 两套各自内部一致,`Input` 的文档页记着同一处口径。
|
|
77
|
+
*/
|
|
78
|
+
bordered: {
|
|
57
79
|
type: Boolean,
|
|
58
|
-
default:
|
|
80
|
+
default: true
|
|
59
81
|
},
|
|
60
82
|
/** 外框交给包裹层(满高表格用) */
|
|
61
83
|
fill: {
|
|
@@ -178,6 +200,9 @@ const Table = /* @__PURE__ */ defineComponent({
|
|
|
178
200
|
const columnCount = computed(() => leadColumns.value + props.columns.length);
|
|
179
201
|
const isEmpty = computed(() => props.data.length === 0);
|
|
180
202
|
const cellClass = (column) => [column.align === "center" ? "mh-table__cell-center" : "", column.align === "right" ? "mh-table__cell-amount" : "", column.wrap ? "mh-table__cell-wrap" : ""].filter(Boolean);
|
|
203
|
+
const cellSetValue = (row, column, index) => (next) => {
|
|
204
|
+
props.onCellChange?.(row, column.key, next, index);
|
|
205
|
+
};
|
|
181
206
|
const colStyles = computed(() => {
|
|
182
207
|
const weightSum = props.columns.reduce((sum, item) => typeof item.width === "number" ? sum + item.width : sum, 0);
|
|
183
208
|
return props.columns.map((column) => {
|
|
@@ -221,7 +246,7 @@ const Table = /* @__PURE__ */ defineComponent({
|
|
|
221
246
|
};
|
|
222
247
|
const sortGlyph = (key) => props.sortKey === key && props.sortOrder === "asc" ? "chevron-up" : "chevron-down";
|
|
223
248
|
return () => {
|
|
224
|
-
const tableClass = ["mh-table", resolvedSize.value === "sm" ? "mh-table--xs" : "", resolvedSize.value === "lg" ? "mh-table--lg" : "", props.
|
|
249
|
+
const tableClass = ["mh-table", resolvedSize.value === "sm" ? "mh-table--xs" : "", resolvedSize.value === "lg" ? "mh-table--lg" : "", props.bordered ? "" : "mh-table--borderless", props.fill ? "mh-table--fill" : "", props.layout === "fixed" ? "mh-table--fixed" : "", props.stickyHead ? "mh-table--sticky-head" : "", props.stickyCol ? "mh-table--sticky-col" : "", attrs.class].filter(Boolean).join(" ");
|
|
225
250
|
const pager = typeof props.pagination === "object" ? props.pagination : null;
|
|
226
251
|
return createVNode("div", {
|
|
227
252
|
"class": "mh-table-wrapper",
|
|
@@ -303,12 +328,16 @@ const Table = /* @__PURE__ */ defineComponent({
|
|
|
303
328
|
) : null, props.columns.map((column) => createVNode("td", {
|
|
304
329
|
"key": column.key,
|
|
305
330
|
"class": cellClass(column).join(" ")
|
|
306
|
-
}, [slots.cell ?
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
331
|
+
}, [slots.cell ? (
|
|
332
|
+
/* 编辑格与展示格都走这条:值、位置、回写口一并交给调用方 */
|
|
333
|
+
slots.cell({
|
|
334
|
+
row,
|
|
335
|
+
column,
|
|
336
|
+
value: row[column.key],
|
|
337
|
+
index,
|
|
338
|
+
setValue: cellSetValue(row, column, index)
|
|
339
|
+
})
|
|
340
|
+
) : slots.actions && column.align === "right" ? slots.actions({
|
|
312
341
|
row,
|
|
313
342
|
index
|
|
314
343
|
}) : String(row[column.key] ?? "")]))]), isExpanded ? createVNode("tr", {
|
package/dist/index.d.ts
CHANGED
|
@@ -32,7 +32,7 @@ export { Tree, type TreeNode } from './components/tree';
|
|
|
32
32
|
export { ListView, type ListViewProps, type ListViewStatus, type ListViewStatusTone, type ListViewCardItemProps, type ListViewCompound, } from './components/list-view';
|
|
33
33
|
export { Badge, type BadgeTone, type BadgeShape, type BadgeSize, type BadgeDot, type BadgeStatus, } from './components/badge';
|
|
34
34
|
export { Button, type ButtonVariant, type ButtonCompound, type ButtonCompoundType, } from './components/button';
|
|
35
|
-
export { Input, Textarea, type TextareaProps, type InputCompound, type InputCompoundType, } from './components/input';
|
|
35
|
+
export { Input, Textarea, NumberInput, type TextareaProps, type NumberInputProps, type InputCompound, type InputCompoundType, } from './components/input';
|
|
36
36
|
export { Search, type SearchProps } from './components/search';
|
|
37
37
|
export { Select, SelectTree, type SelectOption, type SelectValue } from './components/select';
|
|
38
38
|
export { Switch } from './components/switch';
|
|
@@ -45,7 +45,7 @@ export { Notification, type NotificationTone, } from './components/notification'
|
|
|
45
45
|
export { Dialog, type DialogWidth } from './components/dialog';
|
|
46
46
|
export { Drawer, type DrawerSide } from './components/drawer';
|
|
47
47
|
export { Tooltip, type TooltipSide, type TooltipAlign, } from './components/tooltip';
|
|
48
|
-
export { Table, type TableColumn, type TableRow, type TableRowSelectable, type TableSortOrder, } from './components/table';
|
|
48
|
+
export { Table, type TableColumn, type TableCellSlotScope, type TableRow, type TableRowSelectable, type TableSortOrder, } from './components/table';
|
|
49
49
|
export { Pagination, type PaginationPageItem } from './components/pagination';
|
|
50
50
|
export { TabBar, type TabBarOption, type TabBarValue } from './components/tabbar';
|
|
51
51
|
export { Steps, type CanJump, type StepsItem, type StepsVariant, type StepsCompound, type StepsCompoundType, } from './components/steps';
|