@manohub/ui 1.0.5 → 1.0.6

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 (83) hide show
  1. package/README.md +8 -2
  2. package/dist/components/badge/index.d.ts +24 -46
  3. package/dist/components/badge/index.js +7 -11
  4. package/dist/components/button/index.d.ts +11 -31
  5. package/dist/components/capsule/index.d.ts +13 -27
  6. package/dist/components/card/index.d.ts +21 -54
  7. package/dist/components/checkbox/index.d.ts +1 -9
  8. package/dist/components/code-block/index.d.ts +28 -55
  9. package/dist/components/collapse/index.d.ts +2 -0
  10. package/dist/components/dialog/index.d.ts +6 -15
  11. package/dist/components/divider/index.d.ts +1 -12
  12. package/dist/components/drawer/index.d.ts +7 -15
  13. package/dist/components/filter/index.d.ts +27 -69
  14. package/dist/components/filter/index.js +3 -8
  15. package/dist/components/form/index.d.ts +52 -132
  16. package/dist/components/form/index.js +5 -12
  17. package/dist/components/input/bordered.d.ts +4 -6
  18. package/dist/components/input/index.d.ts +31 -63
  19. package/dist/components/input/index.js +5 -7
  20. package/dist/components/input/number-input.d.ts +11 -23
  21. package/dist/components/layout/index.d.ts +21 -28
  22. package/dist/components/layout/index.js +5 -8
  23. package/dist/components/list-view/index.d.ts +10 -43
  24. package/dist/components/loading/index.d.ts +8 -19
  25. package/dist/components/nav/index.d.ts +14 -35
  26. package/dist/components/nav/index.js +1 -3
  27. package/dist/components/notice/index.d.ts +13 -33
  28. package/dist/components/notice/index.js +3 -5
  29. package/dist/components/notification/index.d.ts +0 -9
  30. package/dist/components/page/index.d.ts +8 -43
  31. package/dist/components/page/split-resize.d.ts +9 -18
  32. package/dist/components/pagination/index.d.ts +1 -5
  33. package/dist/components/panel/index.d.ts +27 -62
  34. package/dist/components/panel/index.js +1 -4
  35. package/dist/components/part-padding.d.ts +5 -13
  36. package/dist/components/query-state/index.d.ts +8 -32
  37. package/dist/components/radio/index.d.ts +2 -3
  38. package/dist/components/search/index.d.ts +12 -32
  39. package/dist/components/select/index.d.ts +7 -14
  40. package/dist/components/select/index.js +3 -3
  41. package/dist/components/select/select-panel.d.ts +23 -35
  42. package/dist/components/select/select-panel.js +5 -10
  43. package/dist/components/select/select-shared.d.ts +18 -44
  44. package/dist/components/select/select-tree.d.ts +11 -16
  45. package/dist/components/select/select-trigger.d.ts +6 -16
  46. package/dist/components/select/select-trigger.js +5 -11
  47. package/dist/components/skeleton/index.d.ts +1 -5
  48. package/dist/components/steps/index.d.ts +55 -111
  49. package/dist/components/steps/index.js +8 -16
  50. package/dist/components/switch/index.d.ts +17 -33
  51. package/dist/components/switch/index.js +2 -4
  52. package/dist/components/tabbar/index.d.ts +1 -6
  53. package/dist/components/table/index.d.ts +11 -63
  54. package/dist/components/table/index.js +4 -8
  55. package/dist/components/tabs/index.d.ts +22 -44
  56. package/dist/components/text/index.d.ts +10 -21
  57. package/dist/components/toast/index.d.ts +4 -13
  58. package/dist/components/tooltip/index.d.ts +1 -12
  59. package/dist/components/tree/index.d.ts +35 -77
  60. package/dist/components/tree/index.js +17 -33
  61. package/dist/components/upload/index.d.ts +1 -4
  62. package/dist/index.d.ts +2 -2
  63. package/dist/services/base.d.ts +32 -8
  64. package/dist/services/dialog.d.ts +10 -22
  65. package/dist/services/index.d.ts +2 -2
  66. package/dist/services/loading.d.ts +9 -12
  67. package/dist/services/loading.js +8 -1
  68. package/dist/services/toast.d.ts +4 -14
  69. package/dist/styles/components/button.css +2 -0
  70. package/dist/styles/components/capsule.css +2 -0
  71. package/dist/styles/components/checkbox.css +2 -0
  72. package/dist/styles/components/form.css +23 -28
  73. package/dist/styles/components/input.css +53 -71
  74. package/dist/styles/components/notice.css +2 -0
  75. package/dist/styles/components/page.css +25 -31
  76. package/dist/styles/components/pagination.css +4 -0
  77. package/dist/styles/components/radio.css +2 -0
  78. package/dist/styles/components/search.css +8 -10
  79. package/dist/styles/components/switch.css +2 -0
  80. package/dist/styles/components/tabbar.css +2 -0
  81. package/dist/styles/components/table.css +34 -41
  82. package/dist/styles/components/upload.css +2 -0
  83. package/package.json +2 -2
@@ -1,21 +1,18 @@
1
+ import { type ServiceHandle } from './base';
2
+ /** 加载遮罩的句柄(与 `toast()` 同一形状,见 `ServiceHandle`):关掉自己那一次 */
3
+ export type LoadingHandle = ServiceHandle;
1
4
  /**
2
- * 显示全屏加载遮罩(命令式),返回**幂等的关闭句柄**。
5
+ * 显示全屏加载遮罩(命令式),返回**幂等的关闭句柄**(`{ close() }`,与 `toast()` 同形状)。
3
6
  *
4
7
  * 遮罩落在服务宿主(应用自己的 `[data-manohub-ui]` 容器)内 —— 令牌与应用侧样式都跟着它走。
5
8
  * 层级压在同包的 `Toast` 堆叠之上、模态层(`<dialog>` 的 top layer)之下。
6
9
  *
7
- * @example 配平用法(推荐:句柄只关自己那一次)
8
- * const done = showLoading('保存中…')
9
- * try {
10
- * await save()
11
- * } finally {
12
- * done()
13
- * }
10
+ * 句柄只关**自己那一次**(计数减一);重复 `close()` 不再生效(幂等)。不接句柄时也可用模块级的
11
+ * `hideLoading()` 手动减一(已是 0 时 no-op)。
14
12
  *
15
- * @example 文案可后置(同一批并发里最后一次给的文案生效)
16
- * showLoading()
17
- * showLoading('正在导入,请勿关闭页面')
13
+ * 用法见 `docs/demos/loading/service.vue`(配平与并发样例)与 `docs/components/loading.md` 的
14
+ * 「命令式调用」一节。
18
15
  */
19
- export declare function showLoading(value?: string): () => void;
16
+ export declare function showLoading(value?: string): LoadingHandle;
20
17
  /** 关闭一次(引用计数递减);计数归零时收掉宿主与实例。已是 0 时是 no-op */
21
18
  export declare function hideLoading(): void;
@@ -27,7 +27,14 @@ function showLoading(value) {
27
27
  ensureApp();
28
28
  count += 1;
29
29
  text.value = value;
30
- return () => hideLoading();
30
+ let closed = false;
31
+ return {
32
+ close: () => {
33
+ if (closed) return;
34
+ closed = true;
35
+ hideLoading();
36
+ }
37
+ };
31
38
  }
32
39
  function hideLoading() {
33
40
  if (count === 0) return;
@@ -1,4 +1,5 @@
1
1
  import { type ToastTone } from '../components/toast';
2
+ import { type ServiceHandle } from './base';
2
3
  export interface ToastServiceOptions {
3
4
  /** 自动消失毫秒数(缺省 3000;`0` = 常驻,必须由调用方或用户自行关掉) */
4
5
  duration?: number;
@@ -10,10 +11,8 @@ export interface ToastServiceOptions {
10
11
  /** 手动关闭位(缺省:常驻提示自动开、会自己消失的不开) */
11
12
  closable?: boolean;
12
13
  }
13
- /** 单条提示的句柄:提前关掉它(如「保存中」完成后立刻收掉) */
14
- export interface ToastHandle {
15
- close: () => void;
16
- }
14
+ /** 单条提示的句柄(与 `showLoading()` 同一形状,见 `ServiceHandle`):提前关掉它那一条 */
15
+ export type ToastHandle = ServiceHandle;
17
16
  /**
18
17
  * 弹一条消息提示(命令式)。
19
18
  *
@@ -24,16 +23,7 @@ export interface ToastHandle {
24
23
  * ③ 同屏最多 5 条,超出挤掉最早的;
25
24
  * ④ 返回句柄可提前关掉它自己(其余各条互不影响)。
26
25
  *
27
- * @example 成功提示(3 秒自动消失)
28
- * toast('success', '保存成功')
29
- *
30
- * @example 失败 + 重试动作
31
- * toast('error', '导出失败', { action: { text: '重试', onClick: retry } })
32
- *
33
- * @example 常驻的「进行中」,完成后自己收掉
34
- * const pending = toast('loading', '正在导入…', { duration: 0 })
35
- * // …导入完成
36
- * pending.close()
26
+ * 用法见 `docs/demos/toast/service.vue` 与 `docs/components/toast.md` 的「命令式调用」一节。
37
27
  */
38
28
  export declare function toast(tone: ToastTone, text: string, options?: ToastServiceOptions): ToastHandle;
39
29
  /** 清空全部提示并收掉宿主(换应用 / 卸载时调;幂等) */
@@ -39,6 +39,8 @@
39
39
  transition-property: background-color, border-color, color, box-shadow;
40
40
  transition-duration: 0.15s;
41
41
  transition-timing-function: ease;
42
+ /* 定位上下文:隐身 file input 的包含块必须落在按钮内,否则逃出滚动容器裁剪、撑大文档滚动高度 */
43
+ position: relative;
42
44
  }
43
45
 
44
46
  /* 危险色修饰:换槽位即可,六型与各自 hover / active 一并跟随 */
@@ -27,6 +27,8 @@
27
27
  padding: var(--ui-capsule-inset);
28
28
  border-radius: var(--ui-capsule-radius);
29
29
  background: var(--ui-capsule-track-bg);
30
+ /* 定位上下文:隐身 input 的包含块必须落在控件内,否则逃出滚动容器裁剪、撑大文档滚动高度 */
31
+ position: relative;
30
32
  }
31
33
 
32
34
  /* 隐身开关:视觉隐藏但保持可聚焦(1px 盒 + clip-path,同 checkbox / radio 配方) */
@@ -22,6 +22,8 @@
22
22
  color: var(--ui-base-content);
23
23
  cursor: pointer;
24
24
  user-select: none;
25
+ /* 定位上下文:隐身 input 的包含块必须落在开关内,否则逃出滚动容器裁剪、撑大文档滚动高度 */
26
+ position: relative;
25
27
  }
26
28
 
27
29
  .mh-checkbox__input {
@@ -19,10 +19,9 @@
19
19
  display: flex;
20
20
  flex-direction: column;
21
21
  gap: var(--ui-space-2);
22
- /* 多列公式用的三个变量(**都在本件**,不进 theme 令牌文件):
23
- - `--mh-form-columns`:数字档的**列数上限**(档位类只覆盖它,不复制几何);
24
- - `--mh-form-column-min`:一列的**最小宽度**(`columnMinWidth` 行内覆盖,缺省取令牌 320);
25
- - `--mh-form-gap`:行距 —— 公式里必须用**与实体 gap 同一个值**算轨道下限。 */
22
+ /* 多列公式用的三个变量(**都在本件**,不进 theme 令牌文件):`--mh-form-columns`(数字档的**列数上限**,档位类
23
+ 只覆盖它、不复制几何)、`--mh-form-column-min`(一列**最小宽度**,`columnMinWidth` 行内可覆盖,缺省令牌 320)、
24
+ `--mh-form-gap`(行距 —— 公式里必须用**与实体 gap 同一个值**算轨道下限)。 */
26
25
  --mh-form-columns: 2;
27
26
  --mh-form-column-min: var(--ui-form-column-min);
28
27
  --mh-form-gap: var(--ui-space-2);
@@ -72,10 +71,9 @@
72
71
  }
73
72
 
74
73
  /* ===== 动态列数(`columns="auto"`)=====
75
- * `auto-fill` + `minmax(最小列宽, 1fr)`:列数随容器宽度变。实测(最小宽 320 / 行距 8):
76
- * 640px → 1 列、660px → 2 列、1000px → 3 列、1320px → 4 列。
77
- * 为什么是 `auto-fill` 而不是 `auto-fit`:两者只差在「子项少于轨道数」时 —— `auto-fit` 会**折叠空轨道**,
78
- * 宽容器里 2 个字段被拉成两条长条(实测 1400px 下每项 696px);`auto-fill` 保留轨道,列宽稳定在区间内
74
+ * `auto-fill` + `minmax(最小列宽, 1fr)`:列数随容器宽度变(实测 最小宽 320 / 行距 8:640 → 1 列、660 → 2 列、
75
+ * 1000 → 3 列、1320 → 4 列)。为什么不是 `auto-fit`:两者只差在「子项少于轨道数」时 —— `auto-fit` 会**折叠空
76
+ * 轨道**,宽容器里 2 个字段被拉成两条长条(实测 1400px 下每项 696px);`auto-fill` 保留轨道、列宽稳定在区间内
79
77
  * (1400px 下每项 344px)。表单要的是**列宽稳定**,不是「把剩余宽度填满」。 */
80
78
 
81
79
  .mh-form--columns-auto {
@@ -130,19 +128,18 @@
130
128
  }
131
129
 
132
130
  /* ===== label 提示位(`Form.Item` 的 `tip`)=====
133
- * 图标取 `@manohub/icon` 的 `help`,气泡复用 `Tooltip` 的面(`.mh-tooltip`)与操作层 `popup.css` 的
134
- * hover 档;本件只声明**提示位在 label 行里怎么排**。宿主位两件事必做:① `flex: 0 0 auto`(label
135
- * 文本过长走省略号时不挤压图标);② 行高归 1(图标比一行文字矮,留在 label 行盒里会按基线排、
136
- * 中线与文字错开)。气泡是固定定位(操作层 hover 档),不参与本行排布。 */
131
+ * 图标取 `help`,气泡复用 `Tooltip` 的面(`.mh-tooltip`)与操作层 `popup.css` 的 hover 档;本件只声明
132
+ * **提示位在 label 行里怎么排**。宿主位两件事必做:① `flex: 0 0 auto`(label 文本过长走省略号时不挤压图标);
133
+ * ② 行高归 1(图标比一行文字矮,留在 label 行盒里会按基线排、中线与文字错开)。气泡是固定定位,不参与本行排布。 */
137
134
 
138
135
  .mh-form-item__tip {
139
136
  display: inline-flex;
140
137
  align-items: center;
141
138
  flex: 0 0 auto;
142
139
  /* 行盒归零(`font-size: 0` + `line-height: 0`):`Tooltip` 宿主里还夹着一层 **inline 的锚点 span**
143
- (`.mh-popup-anchor`),它会给内含的 `inline-flex` 触发器生成行盒 —— 宿主因此比图标高出一个
144
- descender 间隙(实测 16px 图标 ⇒ 宿主 17px),在 label 行里居中后图标中线比文字高 0.5px。
145
- 归零后宿主高度 === 图标高度。图标 svg 自带尺寸,不读字号。 */
140
+ (`.mh-popup-anchor`),它会给内含的 `inline-flex` 触发器生成行盒 —— 宿主因此比图标高出一个 descender 间隙
141
+ (实测 16px 图标 ⇒ 宿主 17px),在 label 行里居中后图标中线比文字高 0.5px。归零后宿主高度 === 图标高度
142
+ (图标 svg 自带尺寸,不读字号)。 */
146
143
  font-size: 0;
147
144
  line-height: 0;
148
145
  }
@@ -267,12 +264,11 @@
267
264
  }
268
265
 
269
266
  /* ===== 行内垂直对齐档 =====
270
- * 默认(基类)`center`:label 与控件**中线**对齐。需要顶对齐的是**多行与整块内容**:label 该与首行
271
- * 文字齐,而不是落到整块高度的中间。
272
- * 自动判定(按控件形态,消费方零配置):认**原生元素** `textarea`,不认别件的类名(面之间不互指,
273
- * 是本仓的既有裁决;认元素还顺带覆盖了代码编辑区这类自绘多行件)。文本域的壳带 8px 上内距 + 1px 边框
274
- * (控件自身内距被壳归零,见 input.css),label 补上同样偏移才与**首行文字**同线 —— 不补偏高 9px(实测)。
275
- * 整条包 `:where()`:把特异性压回 0-1-0,显式修饰类(0-2-0)才稳定压过它,不必靠「谁写在后面」。 */
267
+ * 默认(基类)`center`:label 与控件**中线**对齐;需要顶对齐的是**多行与整块内容**(label 该与首行文字齐,而不是
268
+ * 落到整块高度的中间)。自动判定按控件形态、消费方零配置:认**原生元素** `textarea`、不认别件的类名(面之间不互指
269
+ * 是本仓的既有裁决;认元素还顺带覆盖了代码编辑区这类自绘多行件)。文本域的壳带 8px 上内距 + 1px 边框(控件自身
270
+ * 内距被壳归零,见 input.css),label 补上同样偏移才与**首行文字**同线 —— 不补偏高 9px(实测)。整条包
271
+ * `:where()`:特异性压回 0-1-0,显式修饰类(0-2-0)才稳定压过它,不必靠「谁写在后面」。 */
276
272
  .mh-form-item:where(:has(.mh-form-item__control textarea)) {
277
273
  align-items: start;
278
274
  }
@@ -297,9 +293,9 @@
297
293
  }
298
294
 
299
295
  /* ===== 纵向排布(`layout="vertical"`:label 在控件上方) =====
300
- * 窄容器(抽屉 / 移动端)与「字段名很长」的场景用纵向 —— 横向时 label 列宽会把控件压得很窄。
301
- * 单列三行:label / 控件 / 提示依次往下。选择器带 `>` 且写成 0-3-0:纵向档必须压过上面的顶对齐档
302
- * 与显式档 —— 纵向时 label 本来就在上方,那 9px 上内距只会多出一截空隙。 */
296
+ * 窄容器(抽屉 / 移动端)与「字段名很长」的场景用纵向 —— 横向时 label 列宽会把控件压得很窄。单列三行:label /
297
+ * 控件 / 提示依次往下。选择器带 `>` 且写成 0-3-0:纵向档必须压过上面的顶对齐档与显式档 —— 纵向时 label 本来就在
298
+ * 上方,那 9px 上内距只会多出一截空隙。 */
303
299
  .mh-form--vertical .mh-form-item {
304
300
  grid-template-columns: minmax(0, 1fr);
305
301
  }
@@ -351,10 +347,9 @@
351
347
  color: var(--ui-base-content);
352
348
  }
353
349
 
354
- /* 标题左端的品牌色竖标(主色胶囊,几何见 `form.tokens.css` 的两枚令牌 —— 库内唯一来源)。
355
- * **只在有标题文本时出现**:这一行也会在「只给 `extra`」时渲染(筛选条那种),那时凭空多一条竖标
356
- 就成了坏的排版 —— 用 `:has()` 守住真正有标题的那一种。
357
- * `flex: none`:标题换行时竖标不许被挤扁;竖标与文本之间的间距由标题行既有的 `gap` 给。 */
350
+ /* 标题左端的品牌色竖标(主色胶囊,几何见 `form.tokens.css` 的两枚令牌 —— 库内唯一来源)。**只在有标题文本时出现**:
351
+ * 这一行也会在「只给 `extra`」时渲染(筛选条那种),那时凭空多一条竖标就成了坏的排版 —— 用 `:has()` 守住真正有
352
+ * 标题的那一种。`flex: none`:标题换行时竖标不许被挤扁;竖标与文本之间的间距由标题行既有的 `gap` 给。 */
358
353
  .mh-form__title:has(> .mh-form__title-text)::before {
359
354
  content: '';
360
355
  flex: none;
@@ -1,18 +1,13 @@
1
1
  /**
2
2
  * Input / Textarea —— 输入框族(三档尺寸 / 单行 / 多行 / 展示态 / 前后缀 / 清除·眼睛 / 前置后置标签)。
3
3
  *
4
- * 表壳是 flex 行(`__wrap`):前置 → 控件 → 清除 → 眼睛 → 后缀 → 计数依次排列,间隔统一吃
5
- * `--ui-input-affix-gap`;壳内控件 `flex: 1 1 auto + min-width: 0`,故附加位出现/消失时输入区自动
6
- * 伸缩,**不需要为每种附加组合各写一条让位规则**(绝对定位 + padding 让位的旧法必然要两两枚举)。
7
- *
4
+ * 表壳是 flex 行(`__wrap`):前置 → 控件 → 清除 → 眼睛 → 后缀 → 计数依次排列,间隔统一吃 `--ui-input-affix-gap`;
5
+ * 壳内控件 `flex: 1 1 auto + min-width: 0`,故附加位出现 / 消失时输入区自动伸缩,**不需要为每种附加组合各写一条让位
6
+ * 规则**(绝对定位 + padding 让位的旧法必然要两两枚举)。
8
7
  * 高度三档全部吃主题令牌(`--ui-control-height-sm|-md|-lg`),档位只覆盖令牌、几何规则只有一份。
9
- *
10
- * 状态三源等价:原生 `:hover` / `:focus` / `:disabled` / `[aria-invalid]` ⟷ 组件下发 `data-state`
11
- * ⟷ 静态档 `.is-*`,同名面共用一份声明。
12
- *
13
- * 有意差异(对齐设计源时定下的):控件宽度由表单列宽驱动(`width: 100%`,不设 300 上限);
14
- * 常规图标取 14(`--ui-input-affix-size = var(--ui-font-icon)`),非设计注记的 16。
15
- * 禁写:本件不消费 farris 类;跨件令牌一律进 `theme/`。
8
+ * 状态三源等价:`:hover` / `:focus` / `:disabled` / `[aria-invalid]` ⟷ `data-state` ⟷ 静态档 `.is-*`,同一份声明。
9
+ * 有意差异(对齐设计源时定下的):控件宽度由表单列宽驱动(`width: 100%`,不设 300 上限);常规图标取 14
10
+ * (`--ui-input-affix-size = var(--ui-font-icon)`),非设计注记的 16。禁写:本件不消费 farris 类;跨件令牌一律进 `theme/`。
16
11
  */
17
12
 
18
13
  /* 控件本体:**裸用(不带壳)时由控件自己画框** —— Select 的搜索行、静态页写法都靠这一条。
@@ -82,17 +77,14 @@
82
77
  }
83
78
 
84
79
  /* ===== 触发器形态(`as="button"`:控件是 `<button>`,框与状态仍归表壳) =====
85
- * 复用面(框 / 内距 / 附加位 / 三态)不需要为按钮重写一行(那些规则挂在 `.mh-input` 或表壳上,
86
- * 与元素类型无关),这里只补 **UA 样式会漏进来的四项**(实测 Chrome 的 html.css 给 `<button>`
87
- * 铺了 `align-items: flex-start; text-align: center; appearance: auto; font: 400 13.3333px Arial`):
88
- * `align-items: center`(消费方开 flex 行时拦住 UA 的 flex-start)、`text-align: inherit`
89
- * (UA 的 center 会继承给值文本)、`font-weight` / `line-height: inherit`(UA 的 `font` 简写会把这两项
90
- * 从继承链上摘下来)、`appearance: none`(防仍画原生按钮皮肤的引擎)。
91
- *
92
- * 外加一条**单行契约**(nowrap + overflow hidden + ellipsis):触发器与输入框同为单行控件,少了它,
93
- * 消费方把值当**裸文本**子节点渲染时长值会在定高框里折成两行(第二行溢出到框外、右端图标被挤走)。
94
- * 截断口径与 `.mh-input__placeholder` / `.mh-select__value` 同源,这里补的是控件本身那一层。
95
- *
80
+ * 复用面(框 / 内距 / 附加位 / 三态)不需要为按钮重写一行(那些规则挂在 `.mh-input` 或表壳上,与元素类型无关),这里
81
+ * 只补 **UA 样式会漏进来的四项**(实测 Chrome 的 html.css 给 `<button>` 铺了 `align-items: flex-start; text-align:
82
+ * center; appearance: auto; font: 400 13.3333px Arial`):`align-items: center`(拦住 UA 的 flex-start)、
83
+ * `text-align: inherit`(UA 的 center 会继承给值文本)、`font-weight` / `line-height: inherit`(UA 的 `font` 简写会
84
+ * 把这两项从继承链上摘下来)、`appearance: none`。
85
+ * 外加一条**单行契约**(nowrap + overflow hidden + ellipsis):触发器与输入框同为单行控件,少了它,消费方把值当
86
+ * **裸文本**子节点渲染时长值会在定高框里折成两行(第二行溢出到框外、右端图标被挤走);截断口径与
87
+ * `.mh-input__placeholder` / `.mh-select__value` 同源。
96
88
  * 禁写:本块不碰框 / 色 / 尺寸 / 内距 —— 那些归 `.mh-input` 与表壳,重复声明就是又开一份会漂的规格。 */
97
89
  .mh-input--trigger {
98
90
  appearance: none;
@@ -109,11 +101,10 @@
109
101
  }
110
102
 
111
103
  /* ===== 触发器形态 · 占位文案 =====
112
- * `<button>` **没有 `::placeholder` 伪元素**,故触发器形态的占位由组件渲染一个 span 承载
113
- * (判据见 `components/input/index.tsx` 的 `showPlaceholder`)。
114
- * `display: block` 是必要的:span 默认 inline 而 `overflow` 对行内盒不生效(长占位会撑破控件);
115
- * `flex: 1` 是给**父级是 flex** 的复合件用的 —— 实测「占位 + 右侧箭头」在 flex 父级下不给 `flex: 1`
116
- * 时占位只有内容宽(48px),给了才撑满到箭头前(207 / 可用 220)。 */
104
+ * `<button>` **没有 `::placeholder` 伪元素**,故触发器形态的占位由组件渲染一个 span 承载(判据见组件里的
105
+ * `showPlaceholder`)。`display: block` 是必要的:span 默认 inline 而 `overflow` 对行内盒不生效(长占位会撑破
106
+ * 控件);`flex: 1` 是给**父级是 flex** 的复合件用的 —— 实测「占位 + 右侧箭头」不给它时占位只有内容宽(48px),
107
+ * 给了才撑满到箭头前(207 / 可用 220)。 */
117
108
  .mh-input__placeholder {
118
109
  display: block;
119
110
  flex: 1;
@@ -125,14 +116,13 @@
125
116
  }
126
117
 
127
118
  /* ===== 触发器形态 · 整箱热区(命中测试重定向,纯 CSS) =====
128
- * 目的:整件(值 / 标签 + 右侧图标 + 左右内距)都等同于点那个原生触发按钮 —— 触发器形态的附加位是
129
- * 控件的**兄弟**而非子孙,点它们本来永远到不了按钮。
130
- * 做法:给控件挂一张透明绝对定位覆盖层;伪元素**不产生独立事件目标**(命中它即命中宿主),
131
- * `inset: 0` 的包含块是最近的定位祖先 = **表壳** ⇒ 整个箱子的像素点都归控件,且不需要 JS 代理
132
- * (代理会丢掉 `:active` 与按下聚焦的原生时序)。
133
- * 层叠:覆盖层是定位元素、盖住静态兄弟(正是所需);**清除位必须自己升到它之上**
134
- * (`.mh-input__clear` 带 `position: relative`,spec 有断言锁住 —— 漏了点清除会变成开面板)。
135
- * 必须写成「表壳 > 控件」:裸写时无壳场景的包含块会落到页面级定位祖先上,那块透明层会铺满一大片。 */
119
+ * 目的:整件(值 / 标签 + 右侧图标 + 左右内距)都等同于点那个原生触发按钮 —— 触发器形态的附加位是控件的**兄弟**而非
120
+ * 子孙,点它们本来永远到不了按钮。
121
+ * 做法:给控件挂一张透明绝对定位覆盖层;伪元素**不产生独立事件目标**(命中它即命中宿主),`inset: 0` 的包含块是最近
122
+ * 的定位祖先 = **表壳** ⇒ 整个箱子的像素点都归控件,且不需要 JS 代理(代理会丢掉 `:active` 与按下聚焦的原生时序)。
123
+ * 层叠:覆盖层是定位元素、盖住静态兄弟(正是所需);**清除位必须自己升到它之上**(`.mh-input__clear` 带
124
+ * `position: relative`,spec 有断言锁住 —— 漏了点清除会变成开面板)。必须写成「表壳 > 控件」:裸写时无壳场景的包含块
125
+ * 会落到页面级定位祖先上,那块透明层会铺满一大片。 */
136
126
  .mh-input__wrap > .mh-input--trigger::after {
137
127
  content: '';
138
128
  position: absolute;
@@ -140,11 +130,10 @@
140
130
  }
141
131
 
142
132
  /* ===== 无边框档(`bordered={false}`:框与底全退,**任何状态都不再出现**) =====
143
- * 本档的效果**只有一种**:无框(悬停不亮底、聚焦不显框也不留环)—— 「常态无框、聚焦才显框」是
144
- * **另一档** `bordered="focus"`,不要把两种效果叠在同一档上。
145
- * 配 `readonly` 即**只读值档**:几何与可编辑输入框完全一致、仍可聚焦可选中复制(只读 ≠ 禁用),
146
- * 但「可编辑」的线索一条都没有 —— 摘要 / 详情里值回显的推荐写法。
147
- * 中和句与通用状态规则同为 0-2-0,靠写在它们之后取胜。 */
133
+ * 本档的效果**只有一种**:无框(悬停不亮底、聚焦不显框也不留环)——「常态无框、聚焦才显框」是**另一档**
134
+ * `bordered="focus"`,别把两种效果叠在同一档。配 `readonly` 即**只读值档**:几何与可编辑输入框完全一致、仍可聚焦
135
+ * 可选中复制(只读 ≠ 禁用),但「可编辑」的线索一条都没有 —— 摘要 / 详情里值回显的推荐写法;中和句与通用状态
136
+ * 规则同为 0-2-0,靠写在它们之后取胜。 */
148
137
  .mh-input--borderless {
149
138
  border-color: transparent;
150
139
  background: transparent;
@@ -160,9 +149,8 @@
160
149
  }
161
150
 
162
151
  /* ===== 聚焦显框档(`bordered="focus"`:行内编辑那档)=====
163
- * 常态无框无底;**悬停与聚焦复用有框档那两条**(框转 hover 线色、聚焦再加主色弱底环)——
164
- * 本档不另写任何状态描写,「聚焦 = 有框档的样子」这句话在面上只有一份。
165
- * 那两条是 0-2-0、本档常态 0-1-0 ⇒ 悬停 / 聚焦必定压过常态,与顺序无关(裸控件这一层)。 */
152
+ * 常态无框无底;**悬停与聚焦复用有框档那两条**(框转 hover 线色、聚焦再加主色弱底环)—— 本档不另写任何状态描写,
153
+ * 「聚焦 = 有框档的样子」这句话在面上只有一份。那两条是 0-2-0、本档常态 0-1-0 ⇒ 悬停 / 聚焦必定压过常态,与顺序无关。 */
166
154
  .mh-input--border-on-focus {
167
155
  border-color: transparent;
168
156
  background: transparent;
@@ -211,12 +199,10 @@
211
199
  }
212
200
 
213
201
  /* ===== 壳(附加位的排布容器,**整件的框主**) =====
214
- * 框画在壳上、不画在控件上:壳是 flex 行,附加位都在控件之外,框挂在控件上时附加位会落到框外
215
- * (实测带眼睛的密码框:壳 220px,带框的控件只到 196px,眼睛飘在框外)。
216
- * **裸 `.mh-input`(不带壳)仍由控件自己画框**(Select 的搜索行就靠它)。
217
- * **宽度口径归壳**(`--ui-control-width` / `--ui-control-min-width` 都在主题层):壳就是消费方看见的
218
- * 那个控件箱,定宽与收缩下限都设在这里;控件本体仍 `width: 100%`(把令牌也设到本体会让它在壳的
219
- * 内距之外多出 24px)。 */
202
+ * 框画在壳上、不画在控件上:壳是 flex 行、附加位都在控件之外,框挂在控件上时附加位会落到框外(实测带眼睛的
203
+ * 密码框:壳 220px,带框的控件只到 196px,眼睛飘在框外)。**裸 `.mh-input`(不带壳)仍由控件自己画框**
204
+ * (`Select` 的搜索行靠它)。**宽度口径归壳**(两枚 `--ui-control-*` 都在主题层):壳就是消费方看见的那个控件箱
205
+ * (定宽与收缩下限都设在这里),控件本体仍 `width: 100%`(把令牌也设到本体会让它在壳的内距之外多出 24px)。 */
220
206
  .mh-input__wrap {
221
207
  /* 左右内距走**内部变量**再吃令牌:壳是框与内距的主,而档位类挂在控件上(壳是它的父),
222
208
  档位规则要能改到壳的内距 —— 见下面两条 `:has()`。写成 `--mh-*` 是刻意的(内部中转),
@@ -356,13 +342,12 @@
356
342
  }
357
343
 
358
344
  /* ===== 清除(有内容才由组件渲染;圆形 + close 字形)=====
359
- * **默认隐形,滑过表壳或聚焦时才显形**:常显会在每个有内容的输入框右端永远挂一颗深色圆点,
360
- * 密集场景(表格内编辑、筛选条、多选触发器)里那一列圆点比字段本身还抢眼,也与「清除是低频动作」
361
- * 的层级不符。
362
- * 为什么用 `opacity` 而不是 `display` / `visibility`:① 它仍在流中**占位**(显隐时输入区宽度不变,
363
- * 换 `display` 会让文字在鼠标移入的一瞬间横跳);② 它仍是可命中的**定位元素** —— 触发器形态的整箱
364
- * 热区靠 `position: relative` + DOM 顺序压在覆盖层之上,换 `visibility: hidden` 会连命中一起关掉。
365
- * `:focus-within` 是触屏与键盘的兜底(键盘另有 `Delete` 路径,见组件);禁用档不显形。 */
345
+ * **默认隐形,滑过表壳或聚焦时才显形**:常显会在每个有内容的输入框右端永远挂一颗深色圆点,密集场景(表格内编辑、
346
+ * 筛选条、多选触发器)里那一列圆点比字段本身还抢眼,也与「清除是低频动作」的层级不符。为什么用 `opacity` 而不是
347
+ * `display` / `visibility`:① 它仍在流中**占位**(显隐时输入区宽度不变,换 `display` 会让文字在鼠标移入的一瞬间
348
+ * 横跳);② 它仍是可命中的**定位元素** —— 触发器形态的整箱热区靠 `position: relative` + DOM 顺序压在覆盖层之上,
349
+ * 换 `visibility: hidden` 会连命中一起关掉。`:focus-within` 是触屏与键盘的兜底(键盘另有 `Delete` 路径);禁用档
350
+ * 不显形。 */
366
351
  .mh-input__clear {
367
352
  flex: none;
368
353
  display: inline-flex;
@@ -447,21 +432,19 @@
447
432
  }
448
433
 
449
434
  /* ===== 数字输入(NumberInput;与 Input 同族同面) =====
450
- * 表壳 / 内距 / 状态协议全部复用上面几节 —— 本件只多两样:控件上的 `.mh-input--number` 与右端的
451
- * 一对步进按钮,故**没有**独立的面文件、也不进 `styles/index.css` 的登记清单。
452
- * 类名**与面文件同根**(`mh-input*`),与 `Textarea` 的 `.mh-input--textarea` 一致 ——
453
- * 另开 `mh-number-*` 会被 `contract-class-surface.spec.ts` 的「与面文件名同根」判据拦下。
454
- * `.mh-input--number` 只声明 `font-variant-numeric`:数值等宽,编辑时数字不因字形宽度变化而左右跳
455
- * (比例数字下 `1111` 比 `0000` 窄一截);它**不承载几何** —— 框与内距归壳与控件。 */
435
+ * 表壳 / 内距 / 状态协议全部复用上面几节 —— 本件只多两样:控件上的 `.mh-input--number` 与右端一对步进按钮,故
436
+ * **没有**独立的面文件、也不进 `styles/index.css` 的登记清单。类名**与面文件同根**(`mh-input*`,与 `Textarea` 的
437
+ * `.mh-input--textarea` 一致)—— 另开 `mh-number-*` 会被「与面文件名同根」的用例拦下。`.mh-input--number` 只声明
438
+ * `font-variant-numeric`(数值等宽,编辑时数字不因字形宽度变化而左右跳:比例数字下 `1111` 比 `0000` 窄一截);
439
+ * 它**不承载几何** —— 框与内距归壳与控件。 */
456
440
  .mh-input--number {
457
441
  font-variant-numeric: tabular-nums;
458
442
  }
459
443
 
460
- /* 步进位:右端的一对上下箭头(列排、整条居中)。
461
- * 取值(两条约束一起解出来的):md 16 / 压 4(32 − 4 = 28 ≤ 30)、lg 16 / 压 0(32 ≤ 38)、
462
- * sm 13 / 压 4(26 − 4 = 22 ≤ 22;13 是「放得下 且 压不过字形」的最大值 —— 14 的两条约束互相打架)。
463
- * 尺寸声明落在**步进位自己**上(基准),两档改写靠表壳的 `:has()` 选到它 —— 档位类挂在控件上,
464
- * 而箭头是控件的**兄弟**:自定义属性只向下继承,声明在控件上箭头取不到值(同 `input.tokens.css` 的坑)。 */
444
+ /* 步进位:右端的一对上下箭头(列排、整条居中)。取值(两条约束一起解出来的):md 16 / 压 4(32 − 4 = 28 ≤ 30)、
445
+ * lg 16 / 压 0(32 ≤ 38)、sm 13 / 压 4(26 − 4 = 22 ≤ 22;13 是「放得下且压不过字形」的最大值 —— 14 的两条约束
446
+ * 互相打架)。尺寸声明落在**步进位自己**上(基准),两档改写靠表壳的 `:has()` 选到它 —— 档位类挂在控件上、而箭头是
447
+ * 控件的**兄弟**:自定义属性只向下继承,声明在控件上箭头取不到值(同 `input.tokens.css` 的坑)。 */
465
448
  .mh-input__step {
466
449
  /* md 基准:箭头盒取**常规图标档 16**(一对 32 比内容高 30 多 2,故两枚之间压 4 → 一对 28 落进 30) */
467
450
  --mh-input-step-icon: 16px;
@@ -471,10 +454,9 @@
471
454
  flex-direction: column;
472
455
  align-self: stretch;
473
456
  justify-content: center;
474
- /* **默认隐形且不可点**:步进位是「聚焦后调值」的辅助位,常显会让每个数字框右端都挂着一对箭头。
475
- 用 `opacity` 而非 `display`:① 仍在流中**占位**(否则聚焦的一瞬间数字横向跳一格);
476
- ② `pointer-events: none` 让隐形时的热区一并失效,避免「点了看不见的箭头、值莫名其妙变了」。
477
- 读屏仍能读到这两个按钮(不丢可达性)。 */
457
+ /* **默认隐形且不可点**:步进位是「聚焦后调值」的辅助位,常显会让每个数字框右端都挂着一对箭头。用 `opacity` 而非
458
+ `display`:① 仍在流中**占位**(否则聚焦的一瞬间数字横向跳一格);② `pointer-events: none` 让隐形时的热区一并
459
+ 失效,避免「点了看不见的箭头、值莫名其妙变了」。读屏仍能读到这两个按钮。 */
478
460
  opacity: 0;
479
461
  pointer-events: none;
480
462
  transition-property: opacity;
@@ -25,6 +25,8 @@
25
25
  box-shadow: var(--ui-notice-shadow);
26
26
  font-size: var(--ui-notice-font);
27
27
  line-height: var(--ui-line-body);
28
+ /* 定位上下文:隐身说明文本的包含块必须落在控件内,否则逃出滚动容器裁剪、撑大文档滚动高度 */
29
+ position: relative;
28
30
  }
29
31
 
30
32
  .mh-notice__body {
@@ -54,11 +54,11 @@
54
54
  height: 26px;
55
55
  }
56
56
 
57
- /* 页头右侧自由位(Header 唯一可自定义位,对应 `extra` 属性 / 插槽):搜索框 / 按钮 / 下拉等水平排列。
58
- `align-self: stretch` 是关键的一条:父级 `__header-inner` 是 `align-items: flex-end`(右侧位与文本块
59
- **底边对齐**的既有口径),而 flex-end **只定位、不拉伸** —— 少了这条,本盒高度就等于内容高度
60
- (内容比标题行矮时会短一截),自己画背景 / 分隔线就会露白边。拉满后盒高 = 内容行高,内部仍垂直居中。
61
- 注意这里只到「内容行」的上下边:页头条上下的 14 / 13px 留白是 `.mh-page__header` 的 padding。 */
57
+ /* 页头右侧自由位(Header 唯一可自定义位,对应 `extra`):搜索框 / 按钮 / 下拉等水平排列。`align-self: stretch`
58
+ 是关键的一条:父级 `__header-inner` 是 `align-items: flex-end`(右侧位与文本块**底边对齐**的既有口径),而
59
+ flex-end **只定位、不拉伸** —— 少了这条,本盒高度就等于内容高度(比标题行矮时短一截),自己画背景 / 分隔线会露
60
+ 白边。拉满后盒高 = 内容行高、内部仍垂直居中;注意这里只到「内容行」的上下边:页头条上下的 14 / 13px 留白是
61
+ `.mh-page__header` 的 padding。 */
62
62
  .mh-page__header-extra {
63
63
  flex-shrink: 0;
64
64
  align-self: stretch;
@@ -75,13 +75,11 @@
75
75
  color: var(--ui-base-content);
76
76
  }
77
77
 
78
- /* 页头副标题(Header 的 subTitle):标题下一行的次要说明,取 caption 档字号与次要色。
79
- 间距只用 margin-top 4:页头条的上下留白由 .mh-page__header 的 padding 提供,
80
- 这里只负责标题行与副标题行之间的距离 —— 有副标题时条目高度自然变高(56 → 80),
81
- 不再另调 padding(避免「有/无副标题」两套页头规格)。
82
- 文本块与右侧位的**底边对齐**仍是 .mh-page__header-inner 的既有口径(flex-end),
83
- 只不过右位自身被拉伸占满内容行(见 .mh-page__header-extra 的 align-self: stretch),
84
- 所以有副标题时它跟着变高、内容在盒内垂直居中,不在本项里改。 */
78
+ /* 页头副标题(`subTitle`):标题下一行的次要说明,取 caption 档字号与次要色。间距只用 `margin-top: 4` —— 页头条
79
+ 的上下留白由 `.mh-page__header` 的 padding 提供,这里只负责标题行与副标题行的距离,有副标题时条目高度自然变高
80
+ (56 → 80),不再另调 padding(避免「有 / 无副标题」两套页头规格)。文本块与右侧位的**底边对齐**仍归
81
+ `__header-inner` 的 flex-end,右位自身被拉伸占满内容行(见 `__header-extra` 的 `align-self: stretch`),故有
82
+ 副标题时它跟着变高、内容在盒内垂直居中,不在本项里改。 */
85
83
  .mh-page__header-subtitle {
86
84
  margin-top: var(--ui-space-1);
87
85
  font-size: var(--ui-font-caption);
@@ -195,10 +193,9 @@
195
193
  width: 16px;
196
194
  }
197
195
 
198
- /* 展开按钮:**占满整条把手**(全高全宽)—— 收起态里它是整块侧栏唯一看得见的东西,
199
- 所以它自己就是那条「带边框的竖条」,不是里面再嵌一个小按钮。
200
- 盒模型走 border-box:1px 描边要算在 16px 里,否则右侧那条边会被把手的 overflow:hidden 裁掉。
201
- 与收起按钮共用同一套描边 / 底 / 圆角(尺寸不同是有意的:收起按钮钉在分隔线上,只能是小药丸)。 */
196
+ /* 展开按钮:**占满整条把手**(全高全宽)—— 收起态里它是整块侧栏唯一看得见的东西,所以它自己就是那条「带边框的竖条」,
197
+ 不是里面再嵌一个小按钮。盒模型走 border-box:1px 描边要算在 16px 里,否则右侧那条边会被把手的 `overflow: hidden`
198
+ 裁掉。与收起按钮共用同一套描边 / 底 / 圆角(尺寸不同是有意的:收起按钮钉在分隔线上,只能是小药丸)。 */
202
199
  .mh-page__split-rail-btn {
203
200
  box-sizing: border-box;
204
201
  display: flex;
@@ -238,11 +235,10 @@
238
235
  }
239
236
 
240
237
  /* --- 宽度过渡期间**内容不跟着重排**(收起 / 展开都算)---
241
- 内容按 `--mh-page-split-frozen`(该侧展开时的宽度,组件在收起那一刻实测后下发)定宽排布,
242
- 多出来的部分由本盒子的 overflow:hidden 裁掉 —— 观感还是「滑出去 / 滑进来」,
243
- 但栏内的滚动容器(Panel.Body、表格包裹层这些 overflow:auto 的件)不会因为宽度被压缩而现出滚动条。
244
- 取不到值时退回 100%:那种情形是「没给 width 的自适应侧栏」,容器宽度本身不可插值(auto 不能动画),
245
- 收起/展开是瞬间跳变,也就无所谓过渡期间的重排。 */
238
+ 内容按 `--mh-page-split-frozen`(该侧展开时的宽度,组件在收起那一刻实测后下发)定宽排布,多出来的部分由本盒子的
239
+ `overflow: hidden` 裁掉 —— 观感还是「滑出去 / 滑进来」,但栏内的滚动容器(`Panel.Body`、表格包裹层这些
240
+ `overflow: auto` 的件)不会因宽度被压缩而现出滚动条。取不到值时退回 100%(自适应侧栏的容器宽度不可插值,收起 /
241
+ 展开本就是瞬间跳变,也就无所谓过渡期重排)。 */
246
242
  .mh-page__split-sidebar > * {
247
243
  width: var(--mh-page-split-frozen, 100%);
248
244
  }
@@ -375,11 +371,10 @@
375
371
  opacity: 1;
376
372
  }
377
373
 
378
- /* --- 收起按钮:**扁高条**(宽 12 / 高 28,跨在分隔线上);默认不可见也不拦事件。
379
- 两条位置关系都是踩过的坑:
380
- ① 它比 9px 命中带宽,常驻可点会吞掉栏边内容的点击 ⇒ 默认 `opacity: 0` + `pointer-events: none`;
381
- ② 高亮线是手柄的**伪元素**,而伪元素默认绘制在子元素(本按钮)**之上** ⇒ 必须给按钮 `z-index`
382
- (同层叠上下文内只有定位元素的 z-index 参与比较,`auto` 视作 0),否则那条 1px 线会横穿按钮中央。 */
374
+ /* --- 收起按钮:**扁高条**(宽 12 / 高 28,跨在分隔线上);默认不可见也不拦事件。两条位置关系都是踩过的坑:
375
+ ① 它比 9px 命中带宽,常驻可点会吞掉栏边内容的点击 ⇒ 默认 `opacity: 0` + `pointer-events: none`;② 高亮线是手柄的
376
+ **伪元素**,而伪元素默认绘制在子元素(本按钮)**之上** ⇒ 必须给按钮 `z-index`(同层叠上下文内只有定位元素的
377
+ z-index 参与比较,`auto` 视作 0),否则那条 1px 线会横穿按钮中央。 */
383
378
  .mh-page__split-handle-btn {
384
379
  position: absolute;
385
380
  top: 50%;
@@ -416,11 +411,10 @@
416
411
  pointer-events: auto;
417
412
  }
418
413
 
419
- /* 显式清掉 `:focus` 态的轮廓:上游 farris 有 `button:focus { outline: -webkit-focus-ring-color auto 5px }`,
420
- 特异性 (0,1,1) 高于本按钮基础态的 (0,1,0) —— 只写基础态 `outline: none` 压不住它,
421
- 鼠标点击(`:focus-visible` 不匹配)就会露出那道 5px 深色环。本规则 (0,2,0) 压过上游;
422
- 焦点反馈只留给紧随其后的 `:focus-visible`(同特异性、顺序在后 ⇒ 它赢)。
423
- 注:自绘层零 farris,本包之外的样式链若给全局按钮上过焦点环,本规则同样兜住。 */
414
+ /* 显式清掉 `:focus` 态的轮廓:上游 farris 有 `button:focus { outline: -webkit-focus-ring-color auto 5px }`,特异性
415
+ (0,1,1) 高于本按钮基础态的 (0,1,0) —— 只写基础态 `outline: none` 压不住它,鼠标点击(`:focus-visible` 不匹配)
416
+ 就会露出那道 5px 深色环。本规则 (0,2,0) 压过上游;焦点反馈只留给紧随其后的 `:focus-visible`(同特异性、顺序在后
417
+ ⇒ 它赢)。注:本包之外的样式链若给全局按钮上过焦点环,本规则同样兜住。 */
424
418
  .mh-page__split-handle-btn:focus {
425
419
  outline: none;
426
420
  }
@@ -89,6 +89,8 @@
89
89
  color: var(--ui-pagination-text);
90
90
  cursor: pointer;
91
91
  white-space: nowrap;
92
+ /* 定位上下文:隐身 input 的包含块必须落在控件内,否则逃出滚动容器裁剪、撑大文档滚动高度 */
93
+ position: relative;
92
94
  }
93
95
  .mh-pagination__size-option:hover {
94
96
  background: var(--ui-pagination-bg-selected);
@@ -135,6 +137,8 @@
135
137
  transition-property: background-color, border-color, color;
136
138
  transition-duration: 0.15s;
137
139
  transition-timing-function: ease;
140
+ /* 定位上下文:隐身 input 的包含块必须落在控件内,否则逃出滚动容器裁剪、撑大文档滚动高度 */
141
+ position: relative;
138
142
  }
139
143
  /* 滑过 / 按下:只描边转色(底仍透明,规范如此) */
140
144
  .mh-pagination__btn:hover,
@@ -23,6 +23,8 @@
23
23
  color: var(--ui-base-content);
24
24
  cursor: pointer;
25
25
  user-select: none;
26
+ /* 定位上下文:隐身 input 的包含块必须落在开关内,否则逃出滚动容器裁剪、撑大文档滚动高度 */
27
+ position: relative;
26
28
  }
27
29
 
28
30
  /* 组容器:横向 16 / 纵向 12 */
@@ -1,18 +1,16 @@
1
1
  /**
2
2
  * Search —— 搜索框(输入框 + 可选搜索按钮的**组合件面**)。
3
3
  *
4
- * 本面**只声明「一行里怎么摆」**:输入框那一段的面归 `input.css`(框 / 内距 / 清除位 / 聚焦 / 禁用全套
5
- * 复用),搜索按钮的面归 `button.css`。本件没有自己的几何令牌,也不重画任何控件的框。
4
+ * 本面**只声明「一行里怎么摆」**:输入框那一段的面归 `input.css`(框 / 内距 / 清除位 / 聚焦 / 禁用全套复用),搜索按钮
5
+ * 的面归 `button.css`。本件没有自己的几何令牌,也不重画任何控件的框。
6
6
  *
7
- * 两条摆法:输入框 `flex: 1 1 auto` + `min-width: 0`(必须允许收缩,否则会把同行的搜索按钮挤出容器,
8
- * 窄栏里尤其明显);搜索按钮 `flex: none`(永远「那么大」,宽度让给输入框)。
9
- * **按钮是可选的**:不传 `searchText` 时只渲染输入框(框内的放大镜已占住「搜索」语义,再出一个同字形
10
- * 的图标按钮就是同一个意思画两遍);此时 `__btn` 那条摆法无对象,输入框自然占满整行。
7
+ * 两条摆法:输入框 `flex: 1 1 auto` + `min-width: 0`(必须允许收缩,否则会把同行的搜索按钮挤出容器,窄栏里尤其明显);
8
+ * 搜索按钮 `flex: none`(永远「那么大」,宽度让给输入框)。**按钮是可选的**:不传 `searchText` 时只渲染输入框(框内的
9
+ * 放大镜已占住「搜索」语义,再出一个同字形的图标按钮就是同一个意思画两遍),此时 `__btn` 那条摆法无对象。
11
10
  *
12
- * **宽度口径**:整行吃控件那对令牌(`--ui-control-width` / `--ui-control-min-width`,见 theme 的
13
- * spacing.css)。行内那个输入框是 `Input` 的表壳,它在 `input.css` 里同样吃了这两枚,故这里按导入顺序
14
- * (本文件在 `input.css` 之后)把它**收回**:宽度归这一行,重复吃会让「240 的输入框 + 右侧按钮」溢出。
15
- * 类名与面文件名同根(`.mh-search*`)。
11
+ * **宽度口径**:整行吃控件那对令牌(`--ui-control-width` / `--ui-control-min-width`)。行内那个输入框是 `Input` 的
12
+ * 表壳、它在 `input.css` 里同样吃了这两枚,故这里按导入顺序(本文件在 `input.css` 之后)把它**收回**:宽度归这一行,
13
+ * 重复吃会让「240 的输入框 + 右侧按钮」溢出。类名与面文件名同根(`.mh-search*`)。
16
14
  */
17
15
  .mh-search {
18
16
  display: flex;
@@ -14,6 +14,8 @@
14
14
  align-items: center;
15
15
  gap: var(--ui-switch-label-gap);
16
16
  cursor: pointer;
17
+ /* 定位上下文:隐身 input 的包含块必须落在开关内,否则逃出滚动容器裁剪、撑大文档滚动高度 */
18
+ position: relative;
17
19
  }
18
20
  /* 隐身开关(1px 盒 + clip-path + appearance:none,与 checkbox / radio 同配方):
19
21
  不用 display:none / visibility:hidden —— 那会把键盘与读屏一起关掉。 */
@@ -22,6 +22,8 @@
22
22
  gap: var(--ui-tabbar-gap);
23
23
  height: var(--ui-tabbar-height);
24
24
  flex-shrink: 0;
25
+ /* 定位上下文:隐身 input 的包含块必须落在控件内,否则逃出滚动容器裁剪、撑大文档滚动高度 */
26
+ position: relative;
25
27
  }
26
28
 
27
29
  .mh-tabbar__tab {