@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.
@@ -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
- borderless: {
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
- borderless: {
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
- borderless: boolean;
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
- /** 单元格:`{ row, column, value, index }`(不给则直接输出原值) */
433
- cell?: (context: {
434
- row: TableRow;
435
- column: TableColumn;
436
- value: unknown;
437
- index: number;
438
- }) => VNodeChild;
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
- borderless: {
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: false
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.borderless ? "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(" ");
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 ? slots.cell({
307
- row,
308
- column,
309
- value: row[column.key],
310
- index
311
- }) : slots.actions && column.align === "right" ? slots.actions({
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';