@manohub/ui 0.8.2 → 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.
Files changed (56) hide show
  1. package/README.md +14 -8
  2. package/dist/components/button/index.d.ts +8 -8
  3. package/dist/components/checkbox/index.js +5 -1
  4. package/dist/components/code-block/index.d.ts +776 -0
  5. package/dist/components/code-block/index.js +335 -0
  6. package/dist/components/dialog/index.d.ts +3 -3
  7. package/dist/components/dialog/index.js +5 -5
  8. package/dist/components/filter/index.d.ts +8 -2
  9. package/dist/components/filter/index.js +11 -20
  10. package/dist/components/form/index.d.ts +39 -0
  11. package/dist/components/form/index.js +6 -0
  12. package/dist/components/input/index.d.ts +48 -7
  13. package/dist/components/input/index.js +23 -3
  14. package/dist/components/notice/index.d.ts +1 -1
  15. package/dist/components/notice/index.js +4 -4
  16. package/dist/components/notification/index.d.ts +1 -1
  17. package/dist/components/notification/index.js +5 -5
  18. package/dist/components/radio/index.js +29 -3
  19. package/dist/components/search/index.js +37 -35
  20. package/dist/components/select/index.js +1 -3
  21. package/dist/components/select/select-trigger.d.ts +4 -2
  22. package/dist/components/select/select-trigger.js +9 -4
  23. package/dist/components/table/index.d.ts +19 -8
  24. package/dist/components/table/index.js +3 -3
  25. package/dist/components/tag/index.d.ts +1 -1
  26. package/dist/components/text/index.d.ts +4 -3
  27. package/dist/components/toast/index.d.ts +4 -4
  28. package/dist/components/toast/index.js +16 -1
  29. package/dist/index.d.ts +1 -2
  30. package/dist/index.js +2 -4
  31. package/dist/services/dialog.d.ts +1 -1
  32. package/dist/styles/components/avatar.css +4 -1
  33. package/dist/styles/components/button.css +2 -0
  34. package/dist/styles/components/code-block.css +216 -0
  35. package/dist/styles/components/code-block.tokens.css +40 -0
  36. package/dist/styles/components/dialog.css +3 -3
  37. package/dist/styles/components/filter.css +22 -34
  38. package/dist/styles/components/input.css +24 -0
  39. package/dist/styles/components/select.css +3 -4
  40. package/dist/styles/components/table.css +33 -22
  41. package/dist/styles/components/table.tokens.css +6 -6
  42. package/dist/styles/components/text.css +10 -0
  43. package/dist/styles/components/toast.css +35 -6
  44. package/dist/styles/components/upload.css +1 -0
  45. package/dist/styles/index.css +7 -8
  46. package/dist/styles/reset.css +52 -167
  47. package/package.json +2 -2
  48. package/dist/components/code/index.d.ts +0 -134
  49. package/dist/components/code/index.js +0 -117
  50. package/dist/components/code-editor/index.d.ts +0 -163
  51. package/dist/components/code-editor/index.js +0 -98
  52. package/dist/styles/components/code-editor.css +0 -91
  53. package/dist/styles/components/code-editor.tokens.css +0 -24
  54. package/dist/styles/components/code.css +0 -153
  55. package/dist/styles/components/code.tokens.css +0 -29
  56. package/dist/styles/components/filter.tokens.css +0 -14
@@ -0,0 +1,335 @@
1
+ import { defineComponent, createVNode, mergeProps, ref, computed, onBeforeUnmount, isVNode } from "vue";
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
+ }
6
+ const CodeView = /* @__PURE__ */ defineComponent({
7
+ name: "CodeView",
8
+ inheritAttrs: false,
9
+ props: {
10
+ code: {
11
+ type: String,
12
+ default: ""
13
+ },
14
+ inline: {
15
+ type: Boolean,
16
+ default: false
17
+ },
18
+ plain: {
19
+ type: Boolean,
20
+ default: false
21
+ },
22
+ language: {
23
+ type: String,
24
+ default: ""
25
+ },
26
+ copyable: {
27
+ type: Boolean,
28
+ default: false
29
+ },
30
+ maxHeight: {
31
+ type: [String, Number],
32
+ default: void 0
33
+ },
34
+ wrap: {
35
+ type: Boolean,
36
+ default: false
37
+ },
38
+ onCopy: {
39
+ type: Function,
40
+ default: void 0
41
+ }
42
+ },
43
+ slots: Object,
44
+ setup(props, {
45
+ slots,
46
+ attrs
47
+ }) {
48
+ const copied = ref(false);
49
+ let timer;
50
+ onBeforeUnmount(() => {
51
+ if (timer) clearTimeout(timer);
52
+ });
53
+ const copy = async () => {
54
+ const text = props.code;
55
+ let ok = false;
56
+ try {
57
+ if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
58
+ await navigator.clipboard.writeText(text);
59
+ ok = true;
60
+ }
61
+ } catch {
62
+ ok = false;
63
+ }
64
+ if (!ok) {
65
+ console.warn("[ui] CodeBlock.View:当前环境无法写入剪贴板,复制未生效");
66
+ }
67
+ copied.value = ok;
68
+ if (ok) {
69
+ if (timer) clearTimeout(timer);
70
+ timer = setTimeout(() => copied.value = false, 1500);
71
+ }
72
+ props.onCopy?.(ok);
73
+ };
74
+ return () => {
75
+ const extra = attrs.class;
76
+ const style = attrs.style;
77
+ const content = slots.default ? slots.default() : props.code;
78
+ if (props.inline) {
79
+ return createVNode("code", {
80
+ "class": ["mh-code-block", "mh-code-block--inline", props.plain ? "mh-code-block--plain" : "", extra].filter(Boolean).join(" "),
81
+ "style": style
82
+ }, [content]);
83
+ }
84
+ const copyLabel = copied.value ? "已复制" : "复制代码";
85
+ const limited = props.maxHeight === void 0 ? void 0 : {
86
+ maxHeight: typeof props.maxHeight === "number" ? `${props.maxHeight}px` : props.maxHeight,
87
+ overflow: "auto"
88
+ };
89
+ return createVNode("div", {
90
+ "class": ["mh-code-block", props.wrap ? "mh-code-block--wrap" : "", extra].filter(Boolean).join(" "),
91
+ "style": style
92
+ }, [createVNode("pre", {
93
+ "class": "mh-code-block__view",
94
+ "style": limited
95
+ }, [content]), props.language ? createVNode("span", {
96
+ "class": "mh-code-block__lang"
97
+ }, [props.language]) : null, props.copyable ? createVNode("button", {
98
+ "type": "button",
99
+ "class": ["mh-button", "mh-button--icon", "mh-code-block__copy", copied.value ? "is-copied" : ""].filter(Boolean).join(" "),
100
+ "data-state": copied.value ? "copied" : "idle",
101
+ "aria-label": copyLabel,
102
+ "title": copyLabel,
103
+ "onClick": copy
104
+ }, [createVNode(Icon, {
105
+ "name": copied.value ? "check" : "copy",
106
+ "class": "mh-button__icon"
107
+ }, null)]) : null]);
108
+ };
109
+ }
110
+ });
111
+ const CodeEditor = /* @__PURE__ */ defineComponent({
112
+ name: "CodeEditor",
113
+ inheritAttrs: false,
114
+ props: {
115
+ modelValue: {
116
+ type: String,
117
+ default: void 0
118
+ },
119
+ defaultValue: {
120
+ type: String,
121
+ default: ""
122
+ },
123
+ placeholder: {
124
+ type: String,
125
+ default: void 0
126
+ },
127
+ rows: {
128
+ type: Number,
129
+ default: void 0
130
+ },
131
+ autoSize: {
132
+ type: [Boolean, Object],
133
+ default: true
134
+ },
135
+ maxHeight: {
136
+ type: [String, Number],
137
+ default: void 0
138
+ },
139
+ readonly: {
140
+ type: Boolean,
141
+ default: false
142
+ },
143
+ disabled: {
144
+ type: Boolean,
145
+ default: false
146
+ },
147
+ language: {
148
+ type: String,
149
+ default: ""
150
+ },
151
+ onChange: {
152
+ type: Function,
153
+ default: void 0
154
+ }
155
+ },
156
+ emits: {
157
+ "update:modelValue": (v) => true
158
+ },
159
+ setup(props, {
160
+ emit,
161
+ attrs
162
+ }) {
163
+ const inner = ref(props.defaultValue);
164
+ const current = computed(() => props.modelValue ?? inner.value);
165
+ const visibleRows = computed(() => {
166
+ if (props.autoSize === false) return props.rows ?? 4;
167
+ const option = typeof props.autoSize === "object" ? props.autoSize : {};
168
+ const min = option.minRows ?? 3;
169
+ const max = option.maxRows ?? 12;
170
+ const lines = (current.value ?? "").split("\n").length;
171
+ return Math.min(Math.max(lines, min), max);
172
+ });
173
+ const areaStyle = computed(() => props.maxHeight === void 0 ? void 0 : {
174
+ maxHeight: typeof props.maxHeight === "number" ? `${props.maxHeight}px` : props.maxHeight,
175
+ overflow: "auto"
176
+ });
177
+ const onInput = (event) => {
178
+ const el = event.target;
179
+ const value = el.value;
180
+ if (props.modelValue === void 0) inner.value = value;
181
+ emit("update:modelValue", value);
182
+ props.onChange?.(value);
183
+ if (props.modelValue !== void 0 && props.modelValue !== value) el.value = props.modelValue;
184
+ };
185
+ return () => createVNode("div", {
186
+ "class": ["mh-code-block", "mh-code-block--editable", attrs.class].filter(Boolean).join(" "),
187
+ "style": attrs.style
188
+ }, [createVNode("textarea", {
189
+ "class": "mh-code-block__editor",
190
+ "rows": visibleRows.value,
191
+ "value": current.value,
192
+ "placeholder": props.placeholder,
193
+ "readonly": props.readonly,
194
+ "disabled": props.disabled,
195
+ "style": areaStyle.value,
196
+ "spellcheck": false,
197
+ "autocapitalize": "off",
198
+ "autocomplete": "off",
199
+ "onInput": onInput
200
+ }, null), props.language ? createVNode("span", {
201
+ "class": "mh-code-block__lang"
202
+ }, [props.language]) : null]);
203
+ }
204
+ });
205
+ const CodeBlockBase = /* @__PURE__ */ defineComponent({
206
+ name: "CodeBlock",
207
+ inheritAttrs: false,
208
+ props: {
209
+ editable: {
210
+ type: Boolean,
211
+ default: false
212
+ },
213
+ /* —— 只读档(`editable` 为假时生效) —— */
214
+ code: {
215
+ type: String,
216
+ default: ""
217
+ },
218
+ inline: {
219
+ type: Boolean,
220
+ default: false
221
+ },
222
+ plain: {
223
+ type: Boolean,
224
+ default: false
225
+ },
226
+ copyable: {
227
+ type: Boolean,
228
+ default: false
229
+ },
230
+ wrap: {
231
+ type: Boolean,
232
+ default: false
233
+ },
234
+ onCopy: {
235
+ type: Function,
236
+ default: void 0
237
+ },
238
+ /* —— 编辑档(`editable` 为真时生效) —— */
239
+ modelValue: {
240
+ type: String,
241
+ default: void 0
242
+ },
243
+ defaultValue: {
244
+ type: String,
245
+ default: ""
246
+ },
247
+ placeholder: {
248
+ type: String,
249
+ default: void 0
250
+ },
251
+ rows: {
252
+ type: Number,
253
+ default: void 0
254
+ },
255
+ autoSize: {
256
+ type: [Boolean, Object],
257
+ default: true
258
+ },
259
+ readonly: {
260
+ type: Boolean,
261
+ default: false
262
+ },
263
+ disabled: {
264
+ type: Boolean,
265
+ default: false
266
+ },
267
+ onChange: {
268
+ type: Function,
269
+ default: void 0
270
+ },
271
+ /* —— 两档共用 —— */
272
+ language: {
273
+ type: String,
274
+ default: ""
275
+ },
276
+ maxHeight: {
277
+ type: [String, Number],
278
+ default: void 0
279
+ }
280
+ },
281
+ emits: {
282
+ "update:modelValue": (v) => true
283
+ },
284
+ slots: Object,
285
+ setup(props, {
286
+ emit,
287
+ slots,
288
+ attrs
289
+ }) {
290
+ return () => {
291
+ let _slot;
292
+ const shared = {
293
+ language: props.language,
294
+ maxHeight: props.maxHeight,
295
+ class: attrs.class,
296
+ style: attrs.style
297
+ };
298
+ if (props.editable) {
299
+ return createVNode(CodeEditor, mergeProps(shared, {
300
+ "modelValue": props.modelValue,
301
+ "defaultValue": props.defaultValue,
302
+ "placeholder": props.placeholder,
303
+ "rows": props.rows,
304
+ "autoSize": props.autoSize,
305
+ "readonly": props.readonly,
306
+ "disabled": props.disabled,
307
+ "onChange": props.onChange,
308
+ "onUpdate:modelValue": (v) => emit("update:modelValue", v)
309
+ }), null);
310
+ }
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);
324
+ };
325
+ }
326
+ });
327
+ const CodeBlock = Object.assign(CodeBlockBase, {
328
+ View: CodeView,
329
+ Editor: CodeEditor
330
+ });
331
+ export {
332
+ CodeBlock,
333
+ CodeEditor,
334
+ CodeView
335
+ };
@@ -1,6 +1,6 @@
1
1
  import { type PropType, type SlotsType, type VNodeChild } from 'vue';
2
2
  export type DialogWidth = 'md' | 'lg';
3
- /** 语义档:决定标题左侧裸符号的图形与颜色(缺省不渲染图标) */
3
+ /** 语义档:决定标题左侧图标的图形与颜色(缺省不渲染图标) */
4
4
  export type DialogTone = 'info' | 'success' | 'warning' | 'error';
5
5
  /**
6
6
  * 对话框(**自绘**;模态语义与遮罩全由原生 `<dialog>` 给)。
@@ -50,7 +50,7 @@ export declare const Dialog: import("vue").DefineComponent<import("vue").Extract
50
50
  /**
51
51
  * 语义档(缺省不渲染图标):`warning` / `error` 用于「这一步有风险」的确认 ——
52
52
  * 服务层的 `confirm()` / `alert()` 就是靠它把「删除确认」与「保存成功」区分开的。
53
- * 它**只加一个裸符号与配色**,不改标题文案、不动底栏按钮的语义(危险色仍由 `okDanger` 定)。
53
+ * 它**只加一个带圈语义图标与配色**,不改标题文案、不动底栏按钮的语义(危险色仍由 `okDanger` 定)。
54
54
  */
55
55
  tone: {
56
56
  type: PropType<DialogTone>;
@@ -157,7 +157,7 @@ export declare const Dialog: import("vue").DefineComponent<import("vue").Extract
157
157
  /**
158
158
  * 语义档(缺省不渲染图标):`warning` / `error` 用于「这一步有风险」的确认 ——
159
159
  * 服务层的 `confirm()` / `alert()` 就是靠它把「删除确认」与「保存成功」区分开的。
160
- * 它**只加一个裸符号与配色**,不改标题文案、不动底栏按钮的语义(危险色仍由 `okDanger` 定)。
160
+ * 它**只加一个带圈语义图标与配色**,不改标题文案、不动底栏按钮的语义(危险色仍由 `okDanger` 定)。
161
161
  */
162
162
  tone: {
163
163
  type: PropType<DialogTone>;
@@ -2,10 +2,10 @@ import { defineComponent, ref, watch, onMounted, onBeforeUnmount, computed, crea
2
2
  import { Icon } from "@manohub/icon";
3
3
  import { Button } from "../button/index.js";
4
4
  const TONE_ICON = {
5
- info: "info",
6
- success: "check",
7
- warning: "alert",
8
- error: "close"
5
+ info: "info-circle",
6
+ success: "check-circle",
7
+ warning: "alert-circle",
8
+ error: "close-circle"
9
9
  };
10
10
  const Dialog = /* @__PURE__ */ defineComponent({
11
11
  name: "Dialog",
@@ -24,7 +24,7 @@ const Dialog = /* @__PURE__ */ defineComponent({
24
24
  /**
25
25
  * 语义档(缺省不渲染图标):`warning` / `error` 用于「这一步有风险」的确认 ——
26
26
  * 服务层的 `confirm()` / `alert()` 就是靠它把「删除确认」与「保存成功」区分开的。
27
- * 它**只加一个裸符号与配色**,不改标题文案、不动底栏按钮的语义(危险色仍由 `okDanger` 定)。
27
+ * 它**只加一个带圈语义图标与配色**,不改标题文案、不动底栏按钮的语义(危险色仍由 `okDanger` 定)。
28
28
  */
29
29
  tone: {
30
30
  type: String,
@@ -41,7 +41,12 @@ export interface FilterProps {
41
41
  title?: string;
42
42
  /** 标题下的说明小字(透传给 `Form.Header`) */
43
43
  description?: string;
44
- /** label 列宽(px);缺省 `56`(见 `FILTER_LABEL_WIDTH`),透传给 `Form` */
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
- * 字段宽**自适应**:容器够宽一行排下,窄了整体换行(列距 24 / 行距 16)。
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 onFieldsKeydown = (event) => {
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 ?? FILTER_LABEL_WIDTH,
122
- "gap": FILTER_GAP,
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
- /* 插槽一律走对象形态:TSX 的 `v-slots={{…}}` 会被当**普通属性**丢掉
129
- (见 `list-view/index.tsx` 同款记录),而多子节点直传会触发
130
- 「Non-function value encountered for default slot」告警(同 `page/index.tsx`)。 */
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
- // 字段区也用插槽对象形态传子节点:`h()` 的第三参不收 `null`(`slots.default?.()` 的返回可能是 null)
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": ["mh-filter__item", attrs.class].filter(Boolean).join(" "),
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` 与变量:多列的轨道下限公式要用**同一个**行距值算 ——