@manohub/ui 0.10.5 → 1.0.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 (70) hide show
  1. package/README.md +155 -155
  2. package/dist/components/form/index.js +8 -1
  3. package/dist/styles/components/avatar.css +131 -131
  4. package/dist/styles/components/avatar.tokens.css +24 -24
  5. package/dist/styles/components/badge.css +266 -266
  6. package/dist/styles/components/badge.tokens.css +24 -24
  7. package/dist/styles/components/button.css +460 -460
  8. package/dist/styles/components/capsule.css +128 -128
  9. package/dist/styles/components/capsule.tokens.css +27 -27
  10. package/dist/styles/components/card.css +198 -198
  11. package/dist/styles/components/checkbox.css +145 -145
  12. package/dist/styles/components/checkbox.tokens.css +26 -26
  13. package/dist/styles/components/code-block.css +216 -216
  14. package/dist/styles/components/code-block.tokens.css +40 -40
  15. package/dist/styles/components/collapse.css +62 -62
  16. package/dist/styles/components/collapse.tokens.css +20 -20
  17. package/dist/styles/components/dialog.css +158 -158
  18. package/dist/styles/components/dialog.tokens.css +24 -24
  19. package/dist/styles/components/divider.css +75 -75
  20. package/dist/styles/components/divider.tokens.css +22 -22
  21. package/dist/styles/components/drawer.css +143 -143
  22. package/dist/styles/components/drawer.tokens.css +22 -22
  23. package/dist/styles/components/filter.css +29 -29
  24. package/dist/styles/components/form.css +442 -425
  25. package/dist/styles/components/form.tokens.css +15 -15
  26. package/dist/styles/components/input.css +538 -538
  27. package/dist/styles/components/input.tokens.css +43 -43
  28. package/dist/styles/components/layout.css +200 -200
  29. package/dist/styles/components/list-view.css +244 -244
  30. package/dist/styles/components/loading.css +76 -76
  31. package/dist/styles/components/loading.tokens.css +23 -23
  32. package/dist/styles/components/nav.css +48 -48
  33. package/dist/styles/components/notice.css +153 -153
  34. package/dist/styles/components/notice.tokens.css +17 -17
  35. package/dist/styles/components/notification.css +159 -159
  36. package/dist/styles/components/notification.tokens.css +37 -37
  37. package/dist/styles/components/page.css +473 -473
  38. package/dist/styles/components/pagination.css +303 -303
  39. package/dist/styles/components/pagination.tokens.css +32 -32
  40. package/dist/styles/components/panel.css +77 -77
  41. package/dist/styles/components/query-state.css +88 -88
  42. package/dist/styles/components/radio.css +271 -271
  43. package/dist/styles/components/radio.tokens.css +26 -26
  44. package/dist/styles/components/search.css +34 -34
  45. package/dist/styles/components/select.css +326 -326
  46. package/dist/styles/components/select.tokens.css +44 -44
  47. package/dist/styles/components/skeleton.css +60 -60
  48. package/dist/styles/components/skeleton.tokens.css +27 -27
  49. package/dist/styles/components/steps.css +205 -205
  50. package/dist/styles/components/steps.tokens.css +44 -44
  51. package/dist/styles/components/switch.css +165 -165
  52. package/dist/styles/components/switch.tokens.css +26 -26
  53. package/dist/styles/components/tabbar.css +235 -235
  54. package/dist/styles/components/tabbar.tokens.css +30 -30
  55. package/dist/styles/components/table.css +504 -504
  56. package/dist/styles/components/table.tokens.css +44 -44
  57. package/dist/styles/components/tag.css +127 -127
  58. package/dist/styles/components/tag.tokens.css +24 -24
  59. package/dist/styles/components/toast.css +185 -185
  60. package/dist/styles/components/toast.tokens.css +26 -26
  61. package/dist/styles/components/tooltip.css +89 -89
  62. package/dist/styles/components/tooltip.tokens.css +25 -25
  63. package/dist/styles/components/tree.css +173 -173
  64. package/dist/styles/components/upload.css +151 -151
  65. package/dist/styles/components/upload.tokens.css +27 -27
  66. package/dist/styles/index.css +153 -153
  67. package/dist/styles/operations/collapse.css +84 -84
  68. package/dist/styles/operations/popup.css +193 -193
  69. package/dist/styles/operations/tabs.css +82 -82
  70. package/package.json +62 -62
@@ -1,538 +1,538 @@
1
- /**
2
- * Input / Textarea —— 输入框族(三档尺寸 + 单行 + 多行 + 展示态 + 前后缀 + 清除/眼睛 + 前置后置标签)。
3
- *
4
- * 出处:设计稿《MH后台-0911》的搜索框(`0:3945`:高 32 / 圆角 6 / 内距 12 / 图标 16 + 距 8 /
5
- * 占位 `#999999` / 边框 `#e6e6e6`)与 `@aihub/theme` 的 `src/components/input.css`
6
- * (控件高 32 / 内距 12 / 圆角 6 / 边框 base-300;聚焦 = 主色描边 + 主色环;禁用 = base-200 底 + base-300 边 + subtle 字;
7
- * 多行最小高 44 + 右下计数;前置后置标签嵌在同一控件内,标签底 Ne11、高 24)。
8
- *
9
- * **一处有意差异**:规范把控件最大宽钉在 300px(300px),本仓的控件宽度**由表单列宽驱动**
10
- * (`Form.Item` 的控件列 `flex: 1`),故这里 `width: 100%`、不设上限 —— 与 form.css 里
11
- * 「不受 farris 字段 max-width 约束」的既有结论同口径。需要定宽的消费方在容器上收敛。
12
- * **第二处有意差异(图标档)**:设计注记的「图标 16」按**整库口径**改为**常规图标档**
13
- * `--ui-input-affix-size = var(--ui-font-icon)`(14);前后缀 / 眼睛与全库其它常规图标同径。
14
- * 清除圆点内的叉(10)不属于常规图标,仍按设计取小字形。
15
- *
16
- * **表壳是 flex 行**(`__wrap`):前置 → 控件 → 清除 → 眼睛 → 后缀 → 计数依次排列,
17
- * 间隔统一吃 `--ui-input-affix-gap`。壳内控件 `flex: 1 1 auto + min-width: 0`,
18
- * 故任何附加位出现/消失时输入区自动伸缩,**不需要为每个附加组合各写一条让位规则**
19
- * (绝对定位 + padding 让位的旧法在「前后缀 + 清除 + 眼睛」同时出现时必然要两两枚举)。
20
- *
21
- * **高度三档全部吃主题令牌**(`--ui-control-height-sm` / `-md` / `-lg`,default 24 / 32 / 40),
22
- * 换主题即换控件尺寸;档位只覆盖令牌,几何规则只有一份。
23
- *
24
- * **触发器形态(`as="button"`)**:控件元素可以是 `<button>`(`Select` 一类复合件靠它复用整件的
25
- * 框 / 内距 / 清除位 / 右侧图标位,同时保留 `<button>` 才有的原生语义,如 `popovertarget`)。
26
- * 面层只需两条 —— `.mh-input--trigger` 只归零 `<button>` 的 UA 残留,
27
- * `.mh-input__wrap > .mh-input--trigger::after` 是**整箱热区**(见该节),
28
- * 两条都**不重写框、不重写状态**:框与状态本来就挂在 `.mh-input` 与表壳上,与元素类型无关。
29
- *
30
- * **聚焦环不新造阴影令牌**:规范的聚焦阴影槽位映射为本仓的「主色弱底」令牌
31
- * `--ui-primary-weak`(同一用途:主色低透明度环)。
32
- *
33
- * 状态三源等价:原生 `:hover` / `:focus` / `:disabled` / `[aria-invalid]` ⟷ 组件下发 `data-state`
34
- * ⟷ 静态档 `.is-*`,同名面共用一份声明。
35
- *
36
- * 禁写:本件不消费 farris 类;跨件令牌一律进 `theme/`。
37
- */
38
-
39
- /* 控件本体:**裸用(不带壳)时由控件自己画框** —— Select 的搜索行、静态页写法都靠这一条。
40
- * 带壳 / 进组时,后面「壳」「组」两节会把框逐层收归外层(见各节注释)。 */
41
- .mh-input {
42
- width: 100%;
43
- height: var(--ui-input-height);
44
- padding: 0 var(--ui-input-padding-x);
45
- border: 1px solid var(--ui-input-line);
46
- border-radius: var(--ui-input-radius);
47
- background: var(--ui-base-100);
48
- font-family: inherit;
49
- font-size: var(--ui-input-font-size);
50
- color: var(--ui-base-content);
51
- outline: none;
52
- transition-property: border-color, box-shadow, background-color;
53
- transition-duration: 0.15s;
54
- transition-timing-function: ease;
55
- }
56
- .mh-input::placeholder {
57
- color: var(--ui-base-content-subtle);
58
- }
59
- .mh-input:hover,
60
- .mh-input.is-hover {
61
- border-color: var(--ui-input-line-hover);
62
- }
63
- .mh-input:focus,
64
- .mh-input.is-focus {
65
- border-color: var(--ui-input-line-hover);
66
- box-shadow: 0 0 0 2px var(--ui-primary-weak);
67
- }
68
- /* 校验态:走 `aria-invalid`(本仓协议),静态档 `.is-error` 供规格冻结与读图 */
69
- .mh-input[aria-invalid='true'],
70
- .mh-input.is-error {
71
- border-color: var(--ui-error);
72
- }
73
- .mh-input.is-error:focus,
74
- .mh-input.is-error.is-focus {
75
- box-shadow: 0 0 0 2px var(--ui-error-weak);
76
- }
77
- /* 警告态(尚未阻断提交,但需要用户注意) */
78
- .mh-input.is-warning {
79
- border-color: var(--ui-warning);
80
- }
81
- .mh-input.is-warning:focus,
82
- .mh-input.is-warning.is-focus {
83
- box-shadow: 0 0 0 2px var(--ui-warning-weak);
84
- }
85
- .mh-input:disabled,
86
- .mh-input.is-disabled {
87
- background: var(--ui-base-200); /* Ne10 */
88
- border-color: var(--ui-base-300); /* Ne05 */
89
- color: var(--ui-base-content-subtle); /* Te07 */
90
- cursor: not-allowed;
91
- }
92
-
93
- /* ===== 尺寸档 ===== */
94
- .mh-input--sm {
95
- height: var(--ui-input-height-sm);
96
- padding: 0 var(--ui-input-padding-x-sm);
97
- font-size: var(--ui-input-font-size-sm);
98
- }
99
- .mh-input--lg {
100
- height: var(--ui-input-height-lg);
101
- padding: 0 var(--ui-input-padding-x-lg);
102
- font-size: var(--ui-input-font-size-lg);
103
- }
104
-
105
- /* ===== 触发器形态(`as="button"`:控件是 `<button>`,框与状态仍归表壳) =====
106
- *
107
- * 复用面(框 / 内距 / 附加位 / 三态)**不需要为按钮重写一行**:那些规则要么挂在 `.mh-input` 上
108
- * (本身与元素类型无关),要么挂在表壳上(`:has(> .mh-input…)` 对 button 同样命中)。
109
- * 这里只补**作者源没覆盖、会被 `<button>` 的原生 UA 样式漏进来的四项**(实测 Chrome 的 html.css:
110
- * `button { align-items: flex-start; text-align: center; appearance: auto; font: 400 13.3333px Arial }`):
111
- *
112
- * - `align-items: center` —— 消费方把控件开成 flex 行时(如 `Select` 的 `.mh-select`)拦住 UA 的
113
- * `flex-start`(它会把值 / 标签顶到上沿)。本件缺省**不给 `display`**,控件是 inline-block,此时
114
- * 垂直居中来自浏览器对 `<button>` 内容的默认居中 —— 这条声明在那个形态下用不到,留着是与复合件同一份规格;
115
- * - `text-align: inherit` —— UA 的 `center` 会**继承**给值文本;
116
- * - `font-weight` / `line-height: inherit` —— UA 的 `font` 简写会把这两项从继承链上摘下来,
117
- * 而作者源只写了 `font-family` 与 `font-size`(对比:`<input>` 的 UA 不设这两项,故输入框形态天然一致);
118
- * - `appearance: none` —— 防仍画原生按钮皮肤的引擎(Safari 一类)。
119
- *
120
- * 外加一条**单行契约**(`white-space: nowrap` + `overflow: hidden` + `text-overflow: ellipsis`):
121
- * 触发器与输入框同为**单行**控件,值再长也不换行。少了它,长值会在定高框里折成两行 ——
122
- * 第二行溢出到框的下沿之外、右端图标也被挤走(实测:消费方把值当**裸文本**子节点渲染时必现,
123
- * 如 `<Input as="button">{{ 部门名 }}</Input>` 放进窄表格单元格)。截断口径与
124
- * `.mh-input__placeholder` / `.mh-select__value` 同源,这里补的是**控件本身**那一层
125
- * —— 那两处只管各自的子节点,落在控件上的裸文本没有它们兜。
126
- *
127
- * 禁写:本块不碰框 / 色 / 尺寸 / 内距 —— 那些归 `.mh-input` 与表壳,重复声明就是又开一份会漂的规格。 */
128
- .mh-input--trigger {
129
- appearance: none;
130
- white-space: nowrap;
131
- overflow: hidden;
132
- text-overflow: ellipsis;
133
- align-items: center;
134
- font-weight: inherit;
135
- line-height: inherit;
136
- text-align: inherit;
137
- /* 命中被下面的覆盖层重定向到本控件后,整箱(含内距与附加位)都取这一条光标;
138
- 禁用态由 `.mh-input:disabled { cursor: not-allowed }`(0-2-0)压过(本条约 0-1-0)。 */
139
- cursor: pointer;
140
- }
141
-
142
- /* ===== 触发器形态 · 占位文案 =====
143
- *
144
- * `<button>` **没有 `::placeholder` 伪元素**,故触发器形态的占位由组件渲染一个 span 承载
145
- * (判据见 `components/input/index.tsx` 的 `showPlaceholder`)。这里只给它的排版与色。
146
- *
147
- * `display: block` 是**必要的**(实测四种父级 × 长/短文案):span 默认是 inline,而 `overflow`
148
- * 对行内盒不生效 ⇒ 长占位会直接撑破控件;`block` 之后它撑满控件的内容宽、超长走省略号,
149
- * 且在**父级是 flex**(如 `Select` 的触发器)时会被 blockify,行为与 inline 一致
150
- * —— 两种父级下都对,故不要求消费方给控件开 flex。`min-width: 0` 供 flex 父级下允许收缩。
151
- * `flex: 1` 是给**父级是 flex** 的复合件用的(`Select` 的值区`.mh-select__value` 就是这个配方):
152
- * 实测同样的「占位 + 右侧箭头」在 flex 父级下,不给 `flex: 1` 时占位只有**内容宽**(48px)、
153
- * 给了才撑满到箭头前(207 / 可用 220)—— 少了它会让选择器的占位区窄成一截。
154
- * 父级不是 flex 时 `flex` 属性被忽略,故对单件用法无副作用。
155
- * 色与原生 `.mh-input::placeholder` 同档(弱文本),选择器值区的 `--placeholder` 也是这一档。 */
156
- .mh-input__placeholder {
157
- display: block;
158
- flex: 1;
159
- min-width: 0;
160
- overflow: hidden;
161
- text-overflow: ellipsis;
162
- white-space: nowrap;
163
- color: var(--ui-base-content-subtle);
164
- }
165
-
166
- /* ===== 触发器形态 · 整箱热区(命中测试重定向,纯 CSS) =====
167
- *
168
- * 目的:让**整件**(值 / 标签 + 右侧图标 + 左右内距)都等同于点那个原生触发按钮。
169
- * 触发器形态的附加位(如右侧箭头)是控件的**兄弟**而不是子孙,点它们本来永远到不了按钮
170
- * (实测:只有按钮本体那一格能开面板)。
171
- *
172
- * 做法:给控件挂一张**透明的绝对定位覆盖层**。伪元素**不产生独立事件目标** —— 命中它即命中
173
- * 它的宿主元素,而 `inset: 0` 的包含块是最近的定位祖先,也就是**表壳**(`.mh-input__wrap`
174
- * 自带 `position: relative`,见「壳」一节)。于是整个箱子的像素点都归控件:
175
- * `popovertarget` 的原生激活、`:active`、mousedown 聚焦一律照旧,**不需要任何 JS 代理**
176
- * (JS 代理会丢掉 `:active` 与按下聚焦的原生时序)。
177
- *
178
- * 层叠:覆盖层是定位元素 → 盖住静态的兄弟(`.mh-input__suffix` 里的箭头等)→ 正是所需;
179
- * **清除位必须自己升到覆盖层之上**(它是唯一不该被热区截走的兄弟),故 `.mh-input__clear` 带
180
- * `position: relative`,并在 spec 里有断言锁住 —— 漏了点清除会变成开面板。
181
- * 消费方若在 `prefix` / `suffix` 里放**可点元素**,同样要自行 `position: relative`。
182
- *
183
- * 必须写成「表壳 > 控件」:裸写 `.mh-input--trigger::after` 时,无壳场景的包含块会落到页面级
184
- * 定位祖先上 —— 那块透明层会铺满一大片并吞掉别人的点击。 */
185
- .mh-input__wrap > .mh-input--trigger::after {
186
- content: '';
187
- position: absolute;
188
- inset: 0;
189
- }
190
-
191
- /* ===== 无边框档(表格内编辑 / 行内编辑:没有框也没有底,靠悬停给反馈) ===== */
192
- .mh-input--borderless {
193
- border-color: transparent;
194
- background: transparent;
195
- border-radius: 0;
196
- }
197
- .mh-input--borderless:hover,
198
- .mh-input--borderless.is-hover {
199
- border-color: transparent;
200
- background: var(--ui-base-200);
201
- }
202
- .mh-input--borderless:focus,
203
- .mh-input--borderless.is-focus {
204
- border-color: var(--ui-input-line-hover);
205
- background: var(--ui-base-100);
206
- box-shadow: 0 0 0 2px var(--ui-primary-weak);
207
- }
208
- /* **只读值档**(`readonly` + `bordered={false}`):几何照旧(与可编辑输入框同高、同内距),
209
- 但把「可编辑」的线索全退掉 —— 只读值不该在滑过时亮起浅底、被点中时亮出边框与主色环。
210
- 仍然可聚焦、可选中复制(只读 ≠ 禁用:要的就是能选、能复制)。
211
- 它是摘要 / 详情里值回显的推荐写法,也是 `Form.Item` 的 `readonly` 行配的那一档。
212
- 特异性 0-3-0,压过上面两条(0-2-0);带壳时框主是壳,同款规则见文件下半部分。 */
213
- .mh-input--borderless[readonly]:hover,
214
- .mh-input--borderless[readonly].is-hover,
215
- .mh-input--borderless[readonly]:focus,
216
- .mh-input--borderless[readonly].is-focus {
217
- border-color: transparent;
218
- background: transparent;
219
- box-shadow: none;
220
- }
221
-
222
- /* ===== 展示态(下划线,只读值回显) ===== */
223
- .mh-input--display {
224
- display: inline-flex;
225
- align-items: center;
226
- height: var(--ui-input-height);
227
- padding: 0;
228
- border: none;
229
- border-bottom: 1px solid var(--ui-base-300);
230
- border-radius: 0;
231
- background: transparent;
232
- font-size: var(--ui-input-font-size);
233
- color: var(--ui-base-content);
234
- white-space: pre-wrap;
235
- word-break: break-word;
236
- }
237
-
238
- /* ===== 多行(右下计数同容器) ===== */
239
- .mh-input--textarea {
240
- min-height: var(--ui-input-textarea-min-height);
241
- height: auto;
242
- padding: var(--ui-space-2) var(--ui-input-padding-x);
243
- line-height: var(--ui-line-body);
244
- resize: vertical;
245
- }
246
- .mh-input--textarea.mh-input--sm {
247
- min-height: calc(var(--ui-input-textarea-min-height) - 4px);
248
- }
249
- .mh-input--textarea.mh-input--lg {
250
- min-height: calc(var(--ui-input-textarea-min-height) + 8px);
251
- }
252
- /* 计数在位时给多行留出底部空间,避免遮住最后一行 */
253
- .mh-input__wrap:has(> .mh-input__counter) > .mh-input--textarea {
254
- padding-bottom: calc(var(--ui-space-2) + var(--ui-input-counter-size) + 2px);
255
- }
256
-
257
- /* ===== 壳(附加位的排布容器,**整件的框主**) =====
258
- *
259
- * 框画在壳上、不画在控件上:壳是一个 flex 行,前缀 / 清除 / 眼睛 / 后缀都在**控件之外**,
260
- * 框挂在控件上时附加位就落在框外 —— 带眼睛的密码框实测:壳 220px、带框的控件只到 196px,
261
- * 眼睛图标飘在框外。壳定高 + 控件 `height: 100%`,整件仍是可点区域。
262
- * **裸 `.mh-input`(不带壳)仍由控件自己画框**(Select 的搜索行就靠它),见上一节。
263
- */
264
- .mh-input__wrap {
265
- position: relative;
266
- display: inline-flex;
267
- align-items: center;
268
- gap: var(--ui-input-affix-gap);
269
- width: 100%;
270
- height: var(--ui-input-height);
271
- padding: 0 var(--ui-input-padding-x);
272
- border: 1px solid var(--ui-input-line);
273
- border-radius: var(--ui-input-radius);
274
- background: var(--ui-base-100);
275
- transition-property: border-color, box-shadow, background-color;
276
- transition-duration: 0.15s;
277
- transition-timing-function: ease;
278
- }
279
- .mh-input__wrap:has(> .mh-input--sm) {
280
- height: var(--ui-input-height-sm);
281
- }
282
- .mh-input__wrap:has(> .mh-input--lg) {
283
- height: var(--ui-input-height-lg);
284
- }
285
- /* 多行:高归内容(`resize` 要能改高),框仍归壳 */
286
- .mh-input__wrap:has(> .mh-input--textarea) {
287
- height: auto;
288
- padding: var(--ui-space-2) var(--ui-input-padding-x);
289
- }
290
- /* 壳内控件占满剩余宽度,且允许被压缩(min-width:0);附加位增减时输入区自动伸缩 */
291
- .mh-input__wrap > .mh-input {
292
- flex: 1 1 auto;
293
- min-width: 0;
294
- /* 框 / 圆角 / 底色 / 环一并退掉:整件只留壳那一道(与组内同一套口径) */
295
- border: none;
296
- border-radius: 0;
297
- padding: 0;
298
- background: transparent;
299
- }
300
- /* 高度只给单行;多行的 `min-height` 与 `resize` 归控件自己 */
301
- .mh-input__wrap > .mh-input:not(.mh-input--textarea) {
302
- height: 100%;
303
- }
304
- .mh-input__wrap > .mh-input--textarea {
305
- width: 100%;
306
- display: block;
307
- }
308
- .mh-input__wrap > .mh-input:focus,
309
- .mh-input__wrap > .mh-input.is-focus {
310
- box-shadow: none;
311
- }
312
- /* 悬停:反馈画在壳上(壳铺满整件,附加位区域也一起响应) */
313
- .mh-input__wrap:hover {
314
- border-color: var(--ui-input-line-hover);
315
- }
316
- /* 聚焦:环画在壳上 —— 壳是框主,环自然跟着整件走 */
317
- .mh-input__wrap:focus-within,
318
- .mh-input__wrap:has(> .mh-input.is-focus) {
319
- border-color: var(--ui-input-line-hover);
320
- box-shadow: 0 0 0 2px var(--ui-primary-weak);
321
- }
322
- /* 校验态:色同上提到壳(error 走 `aria-invalid` 协议,另给静态档 `.is-error`) */
323
- .mh-input__wrap:has(> .mh-input[aria-invalid='true']),
324
- .mh-input__wrap:has(> .mh-input.is-error) {
325
- border-color: var(--ui-error);
326
- }
327
- .mh-input__wrap:has(> .mh-input[aria-invalid='true']:focus),
328
- .mh-input__wrap:has(> .mh-input.is-error:focus),
329
- .mh-input__wrap:has(> .mh-input.is-error.is-focus) {
330
- border-color: var(--ui-error);
331
- box-shadow: 0 0 0 2px var(--ui-error-weak);
332
- }
333
- .mh-input__wrap:has(> .mh-input.is-warning) {
334
- border-color: var(--ui-warning);
335
- }
336
- .mh-input__wrap:has(> .mh-input.is-warning:focus),
337
- .mh-input__wrap:has(> .mh-input.is-warning.is-focus) {
338
- border-color: var(--ui-warning);
339
- box-shadow: 0 0 0 2px var(--ui-warning-weak);
340
- }
341
- /* 禁用:底与边归壳(字色仍在控件上,见 `.mh-input:disabled`) */
342
- .mh-input__wrap:has(> .mh-input:disabled),
343
- .mh-input__wrap:has(> .mh-input.is-disabled) {
344
- background: var(--ui-base-200);
345
- border-color: var(--ui-base-300);
346
- }
347
- /* 无边框档(表格内编辑 / 行内编辑):框与底全退,悬停给浅底、聚焦才显出框 */
348
- .mh-input__wrap:has(> .mh-input--borderless) {
349
- border-color: transparent;
350
- border-radius: 0;
351
- background: transparent;
352
- }
353
- .mh-input__wrap:has(> .mh-input--borderless):hover {
354
- border-color: transparent;
355
- background: var(--ui-base-200);
356
- }
357
- .mh-input__wrap:has(> .mh-input--borderless):focus-within {
358
- border-color: var(--ui-input-line-hover);
359
- background: var(--ui-base-100);
360
- box-shadow: 0 0 0 2px var(--ui-primary-weak);
361
- }
362
- /* **只读值档**同款(带壳时框主是壳):退掉悬停底与聚焦的边框 / 底色 / 主色环。
363
- 特异性 0-4-0,压过上面两条(0-3-0),并且写在它们之后。 */
364
- .mh-input__wrap:has(> .mh-input--borderless[readonly]):hover,
365
- .mh-input__wrap:has(> .mh-input--borderless[readonly]).is-hover {
366
- border-color: transparent;
367
- background: transparent;
368
- }
369
- .mh-input__wrap:has(> .mh-input--borderless[readonly]):focus-within {
370
- border-color: transparent;
371
- background: transparent;
372
- box-shadow: none;
373
- }
374
-
375
- /* ===== 前缀 / 后缀插槽(纯装饰:图标、单位、域名;无底色、不进 Tab 序列) ===== */
376
- .mh-input__prefix,
377
- .mh-input__suffix {
378
- flex: none;
379
- display: inline-flex;
380
- align-items: center;
381
- color: var(--ui-base-content-subtle);
382
- }
383
- /* 槽内图标(消费方直接放 <Icon> 时也给到统一尺寸) */
384
- .mh-input__prefix > svg,
385
- .mh-input__suffix > svg {
386
- flex: none;
387
- width: var(--ui-input-affix-size);
388
- height: var(--ui-input-affix-size);
389
- }
390
-
391
- /* ===== 清除(有内容才由组件渲染;圆形 + close 字形)=====
392
- *
393
- * **默认隐形,滑过表壳或聚焦时才显形**:常显会在每一个有内容的输入框右端永远挂一颗深色实心圆点 ——
394
- * 密集场景(表格内编辑、筛选条、多选触发器、带前后缀的搜索行)里那一列圆点比字段本身还抢眼,
395
- * 也与「清除是低频动作」的层级不符。
396
- *
397
- * 为什么用 `opacity` 而不是 `display` / `visibility`:
398
- * ① 它仍在流中**占位** —— 显隐时输入区宽度不变;换 `display` 会让文字在鼠标移入的一瞬间横跳;
399
- * ② 它仍是可命中的**定位元素** —— 触发器形态的整箱热区靠 `position: relative` + DOM 顺序压在
400
- * 覆盖层之上(见上一节),换 `visibility: hidden` 会连命中一起关掉,那一块反被热区接管。
401
- *
402
- * `:focus-within` 是**触屏与键盘**的兜底:没有 hover 的设备上,点进输入框同样要能点到清除
403
- * (键盘另有一条 `Delete` 路径,见组件)。禁用档不显形:那时本来就不该清,圆点落在灰底上还会
404
- * 被误读成可用。 */
405
- .mh-input__clear {
406
- flex: none;
407
- display: inline-flex;
408
- align-items: center;
409
- justify-content: center;
410
- /* `position: relative` 是**触发器形态热区**的必要条件:热区覆盖层是定位元素、会盖住静态兄弟,
411
- 清除位必须自己也是定位元素才能凭 DOM 顺序压在它之上 —— 否则点清除会被热区截走变成开面板。 */
412
- position: relative;
413
- width: var(--ui-input-icon-size);
414
- height: var(--ui-input-icon-size);
415
- padding: 0;
416
- border: none;
417
- border-radius: var(--ui-radius-full);
418
- background: var(--ui-base-300); /* 规范 Te10 圆点底 */
419
- color: var(--ui-base-100);
420
- cursor: pointer;
421
- opacity: 0;
422
- transition-property: opacity;
423
- transition-duration: 0.15s;
424
- transition-timing-function: ease;
425
- }
426
- /* 显形:滑过**整件**(内距与附加位区域都算)或任一控件拿到焦点 */
427
- .mh-input__wrap:hover > .mh-input__clear,
428
- .mh-input__wrap:focus-within > .mh-input__clear {
429
- opacity: 1;
430
- }
431
- /* 禁用:整件不可交互,清除位一并收掉(`:has()` 的特异性高于上面那条,故同时压过 hover) */
432
- .mh-input__wrap:has(> .mh-input:disabled) > .mh-input__clear,
433
- .mh-input__wrap:has(> .mh-input.is-disabled) > .mh-input__clear {
434
- opacity: 0;
435
- }
436
- .mh-input__clear:hover,
437
- .mh-input__clear:active {
438
- background: var(--ui-base-400); /* 规范 Te08 */
439
- }
440
- .mh-input__clear-icon {
441
- flex: none;
442
- }
443
-
444
- /* ===== 密码可见性切换(右端图标位;明文态转主色,状态一眼可辨) ===== */
445
- .mh-input__eye {
446
- flex: none;
447
- display: inline-flex;
448
- align-items: center;
449
- justify-content: center;
450
- width: var(--ui-input-affix-size);
451
- height: var(--ui-input-affix-size);
452
- padding: 0;
453
- border: none;
454
- background: transparent;
455
- color: var(--ui-base-content-subtle);
456
- cursor: pointer;
457
- }
458
- .mh-input__eye:hover {
459
- color: var(--ui-primary);
460
- }
461
- /* 明文显示中:图标转主色(同一个 `preview` 字形,靠状态色表达"当前是明文") */
462
- .mh-input__eye.is-on {
463
- color: var(--ui-primary);
464
- }
465
- .mh-input__eye-icon {
466
- flex: none;
467
- width: var(--ui-input-affix-size);
468
- height: var(--ui-input-affix-size);
469
- }
470
-
471
- /* ===== 字数计数 =====
472
- * 单行:壳内的普通成员(跟在后缀之后,不遮输入区);
473
- * 多行:脱流钉在右下角(`textarea` 与它同容器,见上方的底部留白规则)。 */
474
- .mh-input__counter {
475
- flex: none;
476
- font-size: var(--ui-input-counter-size);
477
- line-height: var(--ui-line-caption);
478
- color: var(--ui-base-content-subtle);
479
- }
480
- .mh-input__wrap:has(> .mh-input--textarea) > .mh-input__counter {
481
- position: absolute;
482
- /* 右让位一个「拖拽角」:多行是 `resize: vertical`(`.mh-input--textarea`),浏览器于是在框的
483
- 右下角画一枚原生拖拽柄(Chromium 约 16px 见方),与计数器**同角重叠**——实测截图里
484
- `0/1024` 与手柄叠在一起。故计数器在同一行内右移整一档令牌(`--ui-space-4` = 16px):
485
- `bottom` 不动,底部留白口径(见上方 padding-bottom 规则)不受影响。
486
- 不同平台的手柄宽略有差异(Firefox 略窄),此处取整档、不按浏览器分支。 */
487
- right: calc(var(--ui-input-padding-x) + var(--ui-space-4));
488
- bottom: var(--ui-space-1);
489
- }
490
-
491
- /* 附加位里下拉件的面板皮肤(与操作层并列组合:`class="mh-popup mh-input__menu"`) */
492
- .mh-input__menu {
493
- width: 96px;
494
- margin-top: var(--ui-space-1);
495
- padding: var(--ui-space-1) 0;
496
- border-radius: var(--ui-input-radius);
497
- background: var(--ui-base-100);
498
- box-shadow: var(--ui-shadow-popover); /* 锚定浮层(下拉菜单),不是居中浮层 */
499
- }
500
- .mh-input__menu-item {
501
- height: 30px;
502
- padding: 0 var(--ui-space-3);
503
- font-size: var(--ui-input-font-size);
504
- line-height: 30px;
505
- color: var(--ui-base-content);
506
- cursor: pointer;
507
- }
508
- .mh-input__menu-item:hover,
509
- .mh-input__menu-item.is-hover {
510
- background: var(--ui-base-200);
511
- }
512
-
513
- /* ===== 输入说明(说明位 + 气泡皮肤) ===== */
514
- .mh-input__help {
515
- display: inline-flex;
516
- align-items: center;
517
- justify-content: center;
518
- flex: none;
519
- width: 14px;
520
- height: 14px;
521
- margin-left: var(--ui-space-1);
522
- border: 1px solid var(--ui-base-content-muted);
523
- border-radius: var(--ui-radius-full);
524
- color: var(--ui-base-content-muted);
525
- font-size: var(--ui-input-counter-size);
526
- line-height: 1;
527
- cursor: help;
528
- }
529
- /* 气泡:深底浅字(规范 #303c53 底 + 反白字),出血内距按规范 8/24 */
530
- .mh-input__help-tip {
531
- padding: var(--ui-space-2) var(--ui-space-6);
532
- border-radius: var(--ui-input-radius);
533
- background: var(--ui-base-content);
534
- color: var(--ui-base-100);
535
- font-size: var(--ui-input-font-size);
536
- line-height: var(--ui-line-body);
537
- box-shadow: var(--ui-shadow-popover);
538
- }
1
+ /**
2
+ * Input / Textarea —— 输入框族(三档尺寸 + 单行 + 多行 + 展示态 + 前后缀 + 清除/眼睛 + 前置后置标签)。
3
+ *
4
+ * 出处:设计稿《MH后台-0911》的搜索框(`0:3945`:高 32 / 圆角 6 / 内距 12 / 图标 16 + 距 8 /
5
+ * 占位 `#999999` / 边框 `#e6e6e6`)与 `@aihub/theme` 的 `src/components/input.css`
6
+ * (控件高 32 / 内距 12 / 圆角 6 / 边框 base-300;聚焦 = 主色描边 + 主色环;禁用 = base-200 底 + base-300 边 + subtle 字;
7
+ * 多行最小高 44 + 右下计数;前置后置标签嵌在同一控件内,标签底 Ne11、高 24)。
8
+ *
9
+ * **一处有意差异**:规范把控件最大宽钉在 300px(300px),本仓的控件宽度**由表单列宽驱动**
10
+ * (`Form.Item` 的控件列 `flex: 1`),故这里 `width: 100%`、不设上限 —— 与 form.css 里
11
+ * 「不受 farris 字段 max-width 约束」的既有结论同口径。需要定宽的消费方在容器上收敛。
12
+ * **第二处有意差异(图标档)**:设计注记的「图标 16」按**整库口径**改为**常规图标档**
13
+ * `--ui-input-affix-size = var(--ui-font-icon)`(14);前后缀 / 眼睛与全库其它常规图标同径。
14
+ * 清除圆点内的叉(10)不属于常规图标,仍按设计取小字形。
15
+ *
16
+ * **表壳是 flex 行**(`__wrap`):前置 → 控件 → 清除 → 眼睛 → 后缀 → 计数依次排列,
17
+ * 间隔统一吃 `--ui-input-affix-gap`。壳内控件 `flex: 1 1 auto + min-width: 0`,
18
+ * 故任何附加位出现/消失时输入区自动伸缩,**不需要为每个附加组合各写一条让位规则**
19
+ * (绝对定位 + padding 让位的旧法在「前后缀 + 清除 + 眼睛」同时出现时必然要两两枚举)。
20
+ *
21
+ * **高度三档全部吃主题令牌**(`--ui-control-height-sm` / `-md` / `-lg`,default 24 / 32 / 40),
22
+ * 换主题即换控件尺寸;档位只覆盖令牌,几何规则只有一份。
23
+ *
24
+ * **触发器形态(`as="button"`)**:控件元素可以是 `<button>`(`Select` 一类复合件靠它复用整件的
25
+ * 框 / 内距 / 清除位 / 右侧图标位,同时保留 `<button>` 才有的原生语义,如 `popovertarget`)。
26
+ * 面层只需两条 —— `.mh-input--trigger` 只归零 `<button>` 的 UA 残留,
27
+ * `.mh-input__wrap > .mh-input--trigger::after` 是**整箱热区**(见该节),
28
+ * 两条都**不重写框、不重写状态**:框与状态本来就挂在 `.mh-input` 与表壳上,与元素类型无关。
29
+ *
30
+ * **聚焦环不新造阴影令牌**:规范的聚焦阴影槽位映射为本仓的「主色弱底」令牌
31
+ * `--ui-primary-weak`(同一用途:主色低透明度环)。
32
+ *
33
+ * 状态三源等价:原生 `:hover` / `:focus` / `:disabled` / `[aria-invalid]` ⟷ 组件下发 `data-state`
34
+ * ⟷ 静态档 `.is-*`,同名面共用一份声明。
35
+ *
36
+ * 禁写:本件不消费 farris 类;跨件令牌一律进 `theme/`。
37
+ */
38
+
39
+ /* 控件本体:**裸用(不带壳)时由控件自己画框** —— Select 的搜索行、静态页写法都靠这一条。
40
+ * 带壳 / 进组时,后面「壳」「组」两节会把框逐层收归外层(见各节注释)。 */
41
+ .mh-input {
42
+ width: 100%;
43
+ height: var(--ui-input-height);
44
+ padding: 0 var(--ui-input-padding-x);
45
+ border: 1px solid var(--ui-input-line);
46
+ border-radius: var(--ui-input-radius);
47
+ background: var(--ui-base-100);
48
+ font-family: inherit;
49
+ font-size: var(--ui-input-font-size);
50
+ color: var(--ui-base-content);
51
+ outline: none;
52
+ transition-property: border-color, box-shadow, background-color;
53
+ transition-duration: 0.15s;
54
+ transition-timing-function: ease;
55
+ }
56
+ .mh-input::placeholder {
57
+ color: var(--ui-base-content-subtle);
58
+ }
59
+ .mh-input:hover,
60
+ .mh-input.is-hover {
61
+ border-color: var(--ui-input-line-hover);
62
+ }
63
+ .mh-input:focus,
64
+ .mh-input.is-focus {
65
+ border-color: var(--ui-input-line-hover);
66
+ box-shadow: 0 0 0 2px var(--ui-primary-weak);
67
+ }
68
+ /* 校验态:走 `aria-invalid`(本仓协议),静态档 `.is-error` 供规格冻结与读图 */
69
+ .mh-input[aria-invalid='true'],
70
+ .mh-input.is-error {
71
+ border-color: var(--ui-error);
72
+ }
73
+ .mh-input.is-error:focus,
74
+ .mh-input.is-error.is-focus {
75
+ box-shadow: 0 0 0 2px var(--ui-error-weak);
76
+ }
77
+ /* 警告态(尚未阻断提交,但需要用户注意) */
78
+ .mh-input.is-warning {
79
+ border-color: var(--ui-warning);
80
+ }
81
+ .mh-input.is-warning:focus,
82
+ .mh-input.is-warning.is-focus {
83
+ box-shadow: 0 0 0 2px var(--ui-warning-weak);
84
+ }
85
+ .mh-input:disabled,
86
+ .mh-input.is-disabled {
87
+ background: var(--ui-base-200); /* Ne10 */
88
+ border-color: var(--ui-base-300); /* Ne05 */
89
+ color: var(--ui-base-content-subtle); /* Te07 */
90
+ cursor: not-allowed;
91
+ }
92
+
93
+ /* ===== 尺寸档 ===== */
94
+ .mh-input--sm {
95
+ height: var(--ui-input-height-sm);
96
+ padding: 0 var(--ui-input-padding-x-sm);
97
+ font-size: var(--ui-input-font-size-sm);
98
+ }
99
+ .mh-input--lg {
100
+ height: var(--ui-input-height-lg);
101
+ padding: 0 var(--ui-input-padding-x-lg);
102
+ font-size: var(--ui-input-font-size-lg);
103
+ }
104
+
105
+ /* ===== 触发器形态(`as="button"`:控件是 `<button>`,框与状态仍归表壳) =====
106
+ *
107
+ * 复用面(框 / 内距 / 附加位 / 三态)**不需要为按钮重写一行**:那些规则要么挂在 `.mh-input` 上
108
+ * (本身与元素类型无关),要么挂在表壳上(`:has(> .mh-input…)` 对 button 同样命中)。
109
+ * 这里只补**作者源没覆盖、会被 `<button>` 的原生 UA 样式漏进来的四项**(实测 Chrome 的 html.css:
110
+ * `button { align-items: flex-start; text-align: center; appearance: auto; font: 400 13.3333px Arial }`):
111
+ *
112
+ * - `align-items: center` —— 消费方把控件开成 flex 行时(如 `Select` 的 `.mh-select`)拦住 UA 的
113
+ * `flex-start`(它会把值 / 标签顶到上沿)。本件缺省**不给 `display`**,控件是 inline-block,此时
114
+ * 垂直居中来自浏览器对 `<button>` 内容的默认居中 —— 这条声明在那个形态下用不到,留着是与复合件同一份规格;
115
+ * - `text-align: inherit` —— UA 的 `center` 会**继承**给值文本;
116
+ * - `font-weight` / `line-height: inherit` —— UA 的 `font` 简写会把这两项从继承链上摘下来,
117
+ * 而作者源只写了 `font-family` 与 `font-size`(对比:`<input>` 的 UA 不设这两项,故输入框形态天然一致);
118
+ * - `appearance: none` —— 防仍画原生按钮皮肤的引擎(Safari 一类)。
119
+ *
120
+ * 外加一条**单行契约**(`white-space: nowrap` + `overflow: hidden` + `text-overflow: ellipsis`):
121
+ * 触发器与输入框同为**单行**控件,值再长也不换行。少了它,长值会在定高框里折成两行 ——
122
+ * 第二行溢出到框的下沿之外、右端图标也被挤走(实测:消费方把值当**裸文本**子节点渲染时必现,
123
+ * 如 `<Input as="button">{{ 部门名 }}</Input>` 放进窄表格单元格)。截断口径与
124
+ * `.mh-input__placeholder` / `.mh-select__value` 同源,这里补的是**控件本身**那一层
125
+ * —— 那两处只管各自的子节点,落在控件上的裸文本没有它们兜。
126
+ *
127
+ * 禁写:本块不碰框 / 色 / 尺寸 / 内距 —— 那些归 `.mh-input` 与表壳,重复声明就是又开一份会漂的规格。 */
128
+ .mh-input--trigger {
129
+ appearance: none;
130
+ white-space: nowrap;
131
+ overflow: hidden;
132
+ text-overflow: ellipsis;
133
+ align-items: center;
134
+ font-weight: inherit;
135
+ line-height: inherit;
136
+ text-align: inherit;
137
+ /* 命中被下面的覆盖层重定向到本控件后,整箱(含内距与附加位)都取这一条光标;
138
+ 禁用态由 `.mh-input:disabled { cursor: not-allowed }`(0-2-0)压过(本条约 0-1-0)。 */
139
+ cursor: pointer;
140
+ }
141
+
142
+ /* ===== 触发器形态 · 占位文案 =====
143
+ *
144
+ * `<button>` **没有 `::placeholder` 伪元素**,故触发器形态的占位由组件渲染一个 span 承载
145
+ * (判据见 `components/input/index.tsx` 的 `showPlaceholder`)。这里只给它的排版与色。
146
+ *
147
+ * `display: block` 是**必要的**(实测四种父级 × 长/短文案):span 默认是 inline,而 `overflow`
148
+ * 对行内盒不生效 ⇒ 长占位会直接撑破控件;`block` 之后它撑满控件的内容宽、超长走省略号,
149
+ * 且在**父级是 flex**(如 `Select` 的触发器)时会被 blockify,行为与 inline 一致
150
+ * —— 两种父级下都对,故不要求消费方给控件开 flex。`min-width: 0` 供 flex 父级下允许收缩。
151
+ * `flex: 1` 是给**父级是 flex** 的复合件用的(`Select` 的值区`.mh-select__value` 就是这个配方):
152
+ * 实测同样的「占位 + 右侧箭头」在 flex 父级下,不给 `flex: 1` 时占位只有**内容宽**(48px)、
153
+ * 给了才撑满到箭头前(207 / 可用 220)—— 少了它会让选择器的占位区窄成一截。
154
+ * 父级不是 flex 时 `flex` 属性被忽略,故对单件用法无副作用。
155
+ * 色与原生 `.mh-input::placeholder` 同档(弱文本),选择器值区的 `--placeholder` 也是这一档。 */
156
+ .mh-input__placeholder {
157
+ display: block;
158
+ flex: 1;
159
+ min-width: 0;
160
+ overflow: hidden;
161
+ text-overflow: ellipsis;
162
+ white-space: nowrap;
163
+ color: var(--ui-base-content-subtle);
164
+ }
165
+
166
+ /* ===== 触发器形态 · 整箱热区(命中测试重定向,纯 CSS) =====
167
+ *
168
+ * 目的:让**整件**(值 / 标签 + 右侧图标 + 左右内距)都等同于点那个原生触发按钮。
169
+ * 触发器形态的附加位(如右侧箭头)是控件的**兄弟**而不是子孙,点它们本来永远到不了按钮
170
+ * (实测:只有按钮本体那一格能开面板)。
171
+ *
172
+ * 做法:给控件挂一张**透明的绝对定位覆盖层**。伪元素**不产生独立事件目标** —— 命中它即命中
173
+ * 它的宿主元素,而 `inset: 0` 的包含块是最近的定位祖先,也就是**表壳**(`.mh-input__wrap`
174
+ * 自带 `position: relative`,见「壳」一节)。于是整个箱子的像素点都归控件:
175
+ * `popovertarget` 的原生激活、`:active`、mousedown 聚焦一律照旧,**不需要任何 JS 代理**
176
+ * (JS 代理会丢掉 `:active` 与按下聚焦的原生时序)。
177
+ *
178
+ * 层叠:覆盖层是定位元素 → 盖住静态的兄弟(`.mh-input__suffix` 里的箭头等)→ 正是所需;
179
+ * **清除位必须自己升到覆盖层之上**(它是唯一不该被热区截走的兄弟),故 `.mh-input__clear` 带
180
+ * `position: relative`,并在 spec 里有断言锁住 —— 漏了点清除会变成开面板。
181
+ * 消费方若在 `prefix` / `suffix` 里放**可点元素**,同样要自行 `position: relative`。
182
+ *
183
+ * 必须写成「表壳 > 控件」:裸写 `.mh-input--trigger::after` 时,无壳场景的包含块会落到页面级
184
+ * 定位祖先上 —— 那块透明层会铺满一大片并吞掉别人的点击。 */
185
+ .mh-input__wrap > .mh-input--trigger::after {
186
+ content: '';
187
+ position: absolute;
188
+ inset: 0;
189
+ }
190
+
191
+ /* ===== 无边框档(表格内编辑 / 行内编辑:没有框也没有底,靠悬停给反馈) ===== */
192
+ .mh-input--borderless {
193
+ border-color: transparent;
194
+ background: transparent;
195
+ border-radius: 0;
196
+ }
197
+ .mh-input--borderless:hover,
198
+ .mh-input--borderless.is-hover {
199
+ border-color: transparent;
200
+ background: var(--ui-base-200);
201
+ }
202
+ .mh-input--borderless:focus,
203
+ .mh-input--borderless.is-focus {
204
+ border-color: var(--ui-input-line-hover);
205
+ background: var(--ui-base-100);
206
+ box-shadow: 0 0 0 2px var(--ui-primary-weak);
207
+ }
208
+ /* **只读值档**(`readonly` + `bordered={false}`):几何照旧(与可编辑输入框同高、同内距),
209
+ 但把「可编辑」的线索全退掉 —— 只读值不该在滑过时亮起浅底、被点中时亮出边框与主色环。
210
+ 仍然可聚焦、可选中复制(只读 ≠ 禁用:要的就是能选、能复制)。
211
+ 它是摘要 / 详情里值回显的推荐写法,也是 `Form.Item` 的 `readonly` 行配的那一档。
212
+ 特异性 0-3-0,压过上面两条(0-2-0);带壳时框主是壳,同款规则见文件下半部分。 */
213
+ .mh-input--borderless[readonly]:hover,
214
+ .mh-input--borderless[readonly].is-hover,
215
+ .mh-input--borderless[readonly]:focus,
216
+ .mh-input--borderless[readonly].is-focus {
217
+ border-color: transparent;
218
+ background: transparent;
219
+ box-shadow: none;
220
+ }
221
+
222
+ /* ===== 展示态(下划线,只读值回显) ===== */
223
+ .mh-input--display {
224
+ display: inline-flex;
225
+ align-items: center;
226
+ height: var(--ui-input-height);
227
+ padding: 0;
228
+ border: none;
229
+ border-bottom: 1px solid var(--ui-base-300);
230
+ border-radius: 0;
231
+ background: transparent;
232
+ font-size: var(--ui-input-font-size);
233
+ color: var(--ui-base-content);
234
+ white-space: pre-wrap;
235
+ word-break: break-word;
236
+ }
237
+
238
+ /* ===== 多行(右下计数同容器) ===== */
239
+ .mh-input--textarea {
240
+ min-height: var(--ui-input-textarea-min-height);
241
+ height: auto;
242
+ padding: var(--ui-space-2) var(--ui-input-padding-x);
243
+ line-height: var(--ui-line-body);
244
+ resize: vertical;
245
+ }
246
+ .mh-input--textarea.mh-input--sm {
247
+ min-height: calc(var(--ui-input-textarea-min-height) - 4px);
248
+ }
249
+ .mh-input--textarea.mh-input--lg {
250
+ min-height: calc(var(--ui-input-textarea-min-height) + 8px);
251
+ }
252
+ /* 计数在位时给多行留出底部空间,避免遮住最后一行 */
253
+ .mh-input__wrap:has(> .mh-input__counter) > .mh-input--textarea {
254
+ padding-bottom: calc(var(--ui-space-2) + var(--ui-input-counter-size) + 2px);
255
+ }
256
+
257
+ /* ===== 壳(附加位的排布容器,**整件的框主**) =====
258
+ *
259
+ * 框画在壳上、不画在控件上:壳是一个 flex 行,前缀 / 清除 / 眼睛 / 后缀都在**控件之外**,
260
+ * 框挂在控件上时附加位就落在框外 —— 带眼睛的密码框实测:壳 220px、带框的控件只到 196px,
261
+ * 眼睛图标飘在框外。壳定高 + 控件 `height: 100%`,整件仍是可点区域。
262
+ * **裸 `.mh-input`(不带壳)仍由控件自己画框**(Select 的搜索行就靠它),见上一节。
263
+ */
264
+ .mh-input__wrap {
265
+ position: relative;
266
+ display: inline-flex;
267
+ align-items: center;
268
+ gap: var(--ui-input-affix-gap);
269
+ width: 100%;
270
+ height: var(--ui-input-height);
271
+ padding: 0 var(--ui-input-padding-x);
272
+ border: 1px solid var(--ui-input-line);
273
+ border-radius: var(--ui-input-radius);
274
+ background: var(--ui-base-100);
275
+ transition-property: border-color, box-shadow, background-color;
276
+ transition-duration: 0.15s;
277
+ transition-timing-function: ease;
278
+ }
279
+ .mh-input__wrap:has(> .mh-input--sm) {
280
+ height: var(--ui-input-height-sm);
281
+ }
282
+ .mh-input__wrap:has(> .mh-input--lg) {
283
+ height: var(--ui-input-height-lg);
284
+ }
285
+ /* 多行:高归内容(`resize` 要能改高),框仍归壳 */
286
+ .mh-input__wrap:has(> .mh-input--textarea) {
287
+ height: auto;
288
+ padding: var(--ui-space-2) var(--ui-input-padding-x);
289
+ }
290
+ /* 壳内控件占满剩余宽度,且允许被压缩(min-width:0);附加位增减时输入区自动伸缩 */
291
+ .mh-input__wrap > .mh-input {
292
+ flex: 1 1 auto;
293
+ min-width: 0;
294
+ /* 框 / 圆角 / 底色 / 环一并退掉:整件只留壳那一道(与组内同一套口径) */
295
+ border: none;
296
+ border-radius: 0;
297
+ padding: 0;
298
+ background: transparent;
299
+ }
300
+ /* 高度只给单行;多行的 `min-height` 与 `resize` 归控件自己 */
301
+ .mh-input__wrap > .mh-input:not(.mh-input--textarea) {
302
+ height: 100%;
303
+ }
304
+ .mh-input__wrap > .mh-input--textarea {
305
+ width: 100%;
306
+ display: block;
307
+ }
308
+ .mh-input__wrap > .mh-input:focus,
309
+ .mh-input__wrap > .mh-input.is-focus {
310
+ box-shadow: none;
311
+ }
312
+ /* 悬停:反馈画在壳上(壳铺满整件,附加位区域也一起响应) */
313
+ .mh-input__wrap:hover {
314
+ border-color: var(--ui-input-line-hover);
315
+ }
316
+ /* 聚焦:环画在壳上 —— 壳是框主,环自然跟着整件走 */
317
+ .mh-input__wrap:focus-within,
318
+ .mh-input__wrap:has(> .mh-input.is-focus) {
319
+ border-color: var(--ui-input-line-hover);
320
+ box-shadow: 0 0 0 2px var(--ui-primary-weak);
321
+ }
322
+ /* 校验态:色同上提到壳(error 走 `aria-invalid` 协议,另给静态档 `.is-error`) */
323
+ .mh-input__wrap:has(> .mh-input[aria-invalid='true']),
324
+ .mh-input__wrap:has(> .mh-input.is-error) {
325
+ border-color: var(--ui-error);
326
+ }
327
+ .mh-input__wrap:has(> .mh-input[aria-invalid='true']:focus),
328
+ .mh-input__wrap:has(> .mh-input.is-error:focus),
329
+ .mh-input__wrap:has(> .mh-input.is-error.is-focus) {
330
+ border-color: var(--ui-error);
331
+ box-shadow: 0 0 0 2px var(--ui-error-weak);
332
+ }
333
+ .mh-input__wrap:has(> .mh-input.is-warning) {
334
+ border-color: var(--ui-warning);
335
+ }
336
+ .mh-input__wrap:has(> .mh-input.is-warning:focus),
337
+ .mh-input__wrap:has(> .mh-input.is-warning.is-focus) {
338
+ border-color: var(--ui-warning);
339
+ box-shadow: 0 0 0 2px var(--ui-warning-weak);
340
+ }
341
+ /* 禁用:底与边归壳(字色仍在控件上,见 `.mh-input:disabled`) */
342
+ .mh-input__wrap:has(> .mh-input:disabled),
343
+ .mh-input__wrap:has(> .mh-input.is-disabled) {
344
+ background: var(--ui-base-200);
345
+ border-color: var(--ui-base-300);
346
+ }
347
+ /* 无边框档(表格内编辑 / 行内编辑):框与底全退,悬停给浅底、聚焦才显出框 */
348
+ .mh-input__wrap:has(> .mh-input--borderless) {
349
+ border-color: transparent;
350
+ border-radius: 0;
351
+ background: transparent;
352
+ }
353
+ .mh-input__wrap:has(> .mh-input--borderless):hover {
354
+ border-color: transparent;
355
+ background: var(--ui-base-200);
356
+ }
357
+ .mh-input__wrap:has(> .mh-input--borderless):focus-within {
358
+ border-color: var(--ui-input-line-hover);
359
+ background: var(--ui-base-100);
360
+ box-shadow: 0 0 0 2px var(--ui-primary-weak);
361
+ }
362
+ /* **只读值档**同款(带壳时框主是壳):退掉悬停底与聚焦的边框 / 底色 / 主色环。
363
+ 特异性 0-4-0,压过上面两条(0-3-0),并且写在它们之后。 */
364
+ .mh-input__wrap:has(> .mh-input--borderless[readonly]):hover,
365
+ .mh-input__wrap:has(> .mh-input--borderless[readonly]).is-hover {
366
+ border-color: transparent;
367
+ background: transparent;
368
+ }
369
+ .mh-input__wrap:has(> .mh-input--borderless[readonly]):focus-within {
370
+ border-color: transparent;
371
+ background: transparent;
372
+ box-shadow: none;
373
+ }
374
+
375
+ /* ===== 前缀 / 后缀插槽(纯装饰:图标、单位、域名;无底色、不进 Tab 序列) ===== */
376
+ .mh-input__prefix,
377
+ .mh-input__suffix {
378
+ flex: none;
379
+ display: inline-flex;
380
+ align-items: center;
381
+ color: var(--ui-base-content-subtle);
382
+ }
383
+ /* 槽内图标(消费方直接放 <Icon> 时也给到统一尺寸) */
384
+ .mh-input__prefix > svg,
385
+ .mh-input__suffix > svg {
386
+ flex: none;
387
+ width: var(--ui-input-affix-size);
388
+ height: var(--ui-input-affix-size);
389
+ }
390
+
391
+ /* ===== 清除(有内容才由组件渲染;圆形 + close 字形)=====
392
+ *
393
+ * **默认隐形,滑过表壳或聚焦时才显形**:常显会在每一个有内容的输入框右端永远挂一颗深色实心圆点 ——
394
+ * 密集场景(表格内编辑、筛选条、多选触发器、带前后缀的搜索行)里那一列圆点比字段本身还抢眼,
395
+ * 也与「清除是低频动作」的层级不符。
396
+ *
397
+ * 为什么用 `opacity` 而不是 `display` / `visibility`:
398
+ * ① 它仍在流中**占位** —— 显隐时输入区宽度不变;换 `display` 会让文字在鼠标移入的一瞬间横跳;
399
+ * ② 它仍是可命中的**定位元素** —— 触发器形态的整箱热区靠 `position: relative` + DOM 顺序压在
400
+ * 覆盖层之上(见上一节),换 `visibility: hidden` 会连命中一起关掉,那一块反被热区接管。
401
+ *
402
+ * `:focus-within` 是**触屏与键盘**的兜底:没有 hover 的设备上,点进输入框同样要能点到清除
403
+ * (键盘另有一条 `Delete` 路径,见组件)。禁用档不显形:那时本来就不该清,圆点落在灰底上还会
404
+ * 被误读成可用。 */
405
+ .mh-input__clear {
406
+ flex: none;
407
+ display: inline-flex;
408
+ align-items: center;
409
+ justify-content: center;
410
+ /* `position: relative` 是**触发器形态热区**的必要条件:热区覆盖层是定位元素、会盖住静态兄弟,
411
+ 清除位必须自己也是定位元素才能凭 DOM 顺序压在它之上 —— 否则点清除会被热区截走变成开面板。 */
412
+ position: relative;
413
+ width: var(--ui-input-icon-size);
414
+ height: var(--ui-input-icon-size);
415
+ padding: 0;
416
+ border: none;
417
+ border-radius: var(--ui-radius-full);
418
+ background: var(--ui-base-300); /* 规范 Te10 圆点底 */
419
+ color: var(--ui-base-100);
420
+ cursor: pointer;
421
+ opacity: 0;
422
+ transition-property: opacity;
423
+ transition-duration: 0.15s;
424
+ transition-timing-function: ease;
425
+ }
426
+ /* 显形:滑过**整件**(内距与附加位区域都算)或任一控件拿到焦点 */
427
+ .mh-input__wrap:hover > .mh-input__clear,
428
+ .mh-input__wrap:focus-within > .mh-input__clear {
429
+ opacity: 1;
430
+ }
431
+ /* 禁用:整件不可交互,清除位一并收掉(`:has()` 的特异性高于上面那条,故同时压过 hover) */
432
+ .mh-input__wrap:has(> .mh-input:disabled) > .mh-input__clear,
433
+ .mh-input__wrap:has(> .mh-input.is-disabled) > .mh-input__clear {
434
+ opacity: 0;
435
+ }
436
+ .mh-input__clear:hover,
437
+ .mh-input__clear:active {
438
+ background: var(--ui-base-400); /* 规范 Te08 */
439
+ }
440
+ .mh-input__clear-icon {
441
+ flex: none;
442
+ }
443
+
444
+ /* ===== 密码可见性切换(右端图标位;明文态转主色,状态一眼可辨) ===== */
445
+ .mh-input__eye {
446
+ flex: none;
447
+ display: inline-flex;
448
+ align-items: center;
449
+ justify-content: center;
450
+ width: var(--ui-input-affix-size);
451
+ height: var(--ui-input-affix-size);
452
+ padding: 0;
453
+ border: none;
454
+ background: transparent;
455
+ color: var(--ui-base-content-subtle);
456
+ cursor: pointer;
457
+ }
458
+ .mh-input__eye:hover {
459
+ color: var(--ui-primary);
460
+ }
461
+ /* 明文显示中:图标转主色(同一个 `preview` 字形,靠状态色表达"当前是明文") */
462
+ .mh-input__eye.is-on {
463
+ color: var(--ui-primary);
464
+ }
465
+ .mh-input__eye-icon {
466
+ flex: none;
467
+ width: var(--ui-input-affix-size);
468
+ height: var(--ui-input-affix-size);
469
+ }
470
+
471
+ /* ===== 字数计数 =====
472
+ * 单行:壳内的普通成员(跟在后缀之后,不遮输入区);
473
+ * 多行:脱流钉在右下角(`textarea` 与它同容器,见上方的底部留白规则)。 */
474
+ .mh-input__counter {
475
+ flex: none;
476
+ font-size: var(--ui-input-counter-size);
477
+ line-height: var(--ui-line-caption);
478
+ color: var(--ui-base-content-subtle);
479
+ }
480
+ .mh-input__wrap:has(> .mh-input--textarea) > .mh-input__counter {
481
+ position: absolute;
482
+ /* 右让位一个「拖拽角」:多行是 `resize: vertical`(`.mh-input--textarea`),浏览器于是在框的
483
+ 右下角画一枚原生拖拽柄(Chromium 约 16px 见方),与计数器**同角重叠**——实测截图里
484
+ `0/1024` 与手柄叠在一起。故计数器在同一行内右移整一档令牌(`--ui-space-4` = 16px):
485
+ `bottom` 不动,底部留白口径(见上方 padding-bottom 规则)不受影响。
486
+ 不同平台的手柄宽略有差异(Firefox 略窄),此处取整档、不按浏览器分支。 */
487
+ right: calc(var(--ui-input-padding-x) + var(--ui-space-4));
488
+ bottom: var(--ui-space-1);
489
+ }
490
+
491
+ /* 附加位里下拉件的面板皮肤(与操作层并列组合:`class="mh-popup mh-input__menu"`) */
492
+ .mh-input__menu {
493
+ width: 96px;
494
+ margin-top: var(--ui-space-1);
495
+ padding: var(--ui-space-1) 0;
496
+ border-radius: var(--ui-input-radius);
497
+ background: var(--ui-base-100);
498
+ box-shadow: var(--ui-shadow-popover); /* 锚定浮层(下拉菜单),不是居中浮层 */
499
+ }
500
+ .mh-input__menu-item {
501
+ height: 30px;
502
+ padding: 0 var(--ui-space-3);
503
+ font-size: var(--ui-input-font-size);
504
+ line-height: 30px;
505
+ color: var(--ui-base-content);
506
+ cursor: pointer;
507
+ }
508
+ .mh-input__menu-item:hover,
509
+ .mh-input__menu-item.is-hover {
510
+ background: var(--ui-base-200);
511
+ }
512
+
513
+ /* ===== 输入说明(说明位 + 气泡皮肤) ===== */
514
+ .mh-input__help {
515
+ display: inline-flex;
516
+ align-items: center;
517
+ justify-content: center;
518
+ flex: none;
519
+ width: 14px;
520
+ height: 14px;
521
+ margin-left: var(--ui-space-1);
522
+ border: 1px solid var(--ui-base-content-muted);
523
+ border-radius: var(--ui-radius-full);
524
+ color: var(--ui-base-content-muted);
525
+ font-size: var(--ui-input-counter-size);
526
+ line-height: 1;
527
+ cursor: help;
528
+ }
529
+ /* 气泡:深底浅字(规范 #303c53 底 + 反白字),出血内距按规范 8/24 */
530
+ .mh-input__help-tip {
531
+ padding: var(--ui-space-2) var(--ui-space-6);
532
+ border-radius: var(--ui-input-radius);
533
+ background: var(--ui-base-content);
534
+ color: var(--ui-base-100);
535
+ font-size: var(--ui-input-font-size);
536
+ line-height: var(--ui-line-body);
537
+ box-shadow: var(--ui-shadow-popover);
538
+ }