@manohub/ui 0.9.0 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +13 -7
  2. package/dist/components/checkbox/index.js +5 -1
  3. package/dist/components/code-block/index.js +18 -11
  4. package/dist/components/filter/index.d.ts +8 -2
  5. package/dist/components/filter/index.js +11 -20
  6. package/dist/components/form/index.d.ts +39 -0
  7. package/dist/components/form/index.js +6 -0
  8. package/dist/components/input/index.d.ts +76 -20
  9. package/dist/components/input/index.js +64 -15
  10. package/dist/components/radio/index.js +29 -3
  11. package/dist/components/search/index.js +37 -35
  12. package/dist/components/select/index.d.ts +5 -2
  13. package/dist/components/select/index.js +3 -6
  14. package/dist/components/select/select-list.d.ts +2 -0
  15. package/dist/components/select/select-panel.d.ts +9 -4
  16. package/dist/components/select/select-panel.js +4 -2
  17. package/dist/components/select/select-tree.d.ts +5 -3
  18. package/dist/components/select/select-tree.js +3 -15
  19. package/dist/components/select/select-trigger.d.ts +4 -2
  20. package/dist/components/select/select-trigger.js +9 -4
  21. package/dist/components/table/index.d.ts +19 -8
  22. package/dist/components/table/index.js +3 -3
  23. package/dist/components/text/index.d.ts +4 -3
  24. package/dist/components/toast/index.js +16 -1
  25. package/dist/styles/components/avatar.css +4 -1
  26. package/dist/styles/components/badge.css +3 -1
  27. package/dist/styles/components/button.css +7 -2
  28. package/dist/styles/components/filter.css +22 -34
  29. package/dist/styles/components/input.css +37 -1
  30. package/dist/styles/components/input.tokens.css +5 -3
  31. package/dist/styles/components/pagination.css +8 -2
  32. package/dist/styles/components/select.css +39 -10
  33. package/dist/styles/components/select.tokens.css +4 -3
  34. package/dist/styles/components/steps.tokens.css +5 -3
  35. package/dist/styles/components/table.css +33 -22
  36. package/dist/styles/components/table.tokens.css +6 -6
  37. package/dist/styles/components/tag.css +7 -3
  38. package/dist/styles/components/text.css +10 -0
  39. package/dist/styles/components/toast.css +35 -6
  40. package/dist/styles/components/upload.css +1 -0
  41. package/dist/styles/components/upload.tokens.css +1 -1
  42. package/dist/styles/index.css +5 -4
  43. package/dist/styles/reset.css +52 -167
  44. package/package.json +2 -2
  45. package/dist/styles/components/filter.tokens.css +0 -14
@@ -16,8 +16,19 @@ export interface TableColumn {
16
16
  */
17
17
  width?: string | number;
18
18
  align?: 'left' | 'center' | 'right';
19
- /** 长文本省略(表头仍完整)。`fixed` 下建议给所有长文本列都标上,否则会换行(行高变高) */
20
- ellipsis?: boolean;
19
+ /**
20
+ * 允许该列**换行**(缺省不换行:`nowrap` + 省略号)。
21
+ *
22
+ * <p>缺省不换行是刻意的:换行只会把行高顶高(`td` 的 `height` 只是**下限**),
23
+ * 而且必然伴随从字符间断字(实测序号列窄到装不下两位数时,`10` 被拆成 `1` / `0` 两行、
24
+ * 行高 30 → 49)。真要换行的列(描述 / 备注 / 多行摘要)显式打开它。</p>
25
+ *
26
+ * <p><b>只影响 `layout="fixed"`</b>:`auto` 档列宽由内容计算,表体本来就换行
27
+ * (不换行会让长文本列不再收敛、表格更容易撑破容器),故本档给不给都一样。</p>
28
+ *
29
+ * <p>只作用于表体 `td`;表头始终 `nowrap`(表头字短,换行只会让表头行变高)。</p>
30
+ */
31
+ wrap?: boolean;
21
32
  /**
22
33
  * 列下限(px):防止窄容器把该列压没。**给了才下发 `min-width`** ——
23
34
  * ⚠️ 它**不是硬下限**:实测(Chrome)`<col>` 上的 `min-width` 会被当成**分配权重**参与按比例
@@ -72,13 +83,13 @@ export interface TablePagination {
72
83
  * 组件自己切片会让「服务端分页」与「客户端分页」两套口径藏在同一个 prop 里;
73
84
  * - **错误态**:归 `QueryState`(表格只管列数据)。
74
85
  *
75
- * @example 基本列表(表默认满宽;列宽给权重 + `fixed` 才严格按比例,长文本列配 `ellipsis`)
86
+ * @example 基本列表(表默认满宽;列宽给权重 + `fixed` 才严格按比例)
76
87
  * <Table
77
88
  * layout="fixed" striped row-key="id" :data="rows"
78
89
  * :columns="[
79
90
  * { key: 'name', title: '名称', width: 3 },
80
91
  * { key: 'amount', title: '金额', width: 1, align: 'right' },
81
- * { key: 'description', title: '描述', width: 4, ellipsis: true },
92
+ * { key: 'description', title: '描述', width: 4, wrap: true },
82
93
  * ]"
83
94
  * :pagination="{ total: 120, current: page, onChange: (p) => (page = p) }"
84
95
  * />
@@ -164,8 +175,8 @@ export declare const Table: import("vue").DefineComponent<import("vue").ExtractP
164
175
  * - `auto`(缺省)—— 列宽随内容自适应。`column.width` 是**建议值**,
165
176
  * 长文本(尤其中文长串 / 无空格串)会顶破列宽把表格撑得比容器还宽 ⇒ 包裹层出横向滚动条。
166
177
  * - `fixed` —— `table-layout: fixed`:列宽受声明约束、**不再被内容撑破**,
167
- * 于是百分比 / 数字权重可以「吃满容器 + 严格按比例」。代价:长文本列要显式 `ellipsis: true`,
168
- * 否则会**换行**(行高变高)。
178
+ * 于是百分比 / 数字权重可以「吃满容器 + 严格按比例」。表体**默认不换行**(`nowrap` + 省略号),
179
+ * 要换行的列显式 `wrap: true`。
169
180
  */
170
181
  layout: {
171
182
  type: PropType<"auto" | "fixed">;
@@ -311,8 +322,8 @@ export declare const Table: import("vue").DefineComponent<import("vue").ExtractP
311
322
  * - `auto`(缺省)—— 列宽随内容自适应。`column.width` 是**建议值**,
312
323
  * 长文本(尤其中文长串 / 无空格串)会顶破列宽把表格撑得比容器还宽 ⇒ 包裹层出横向滚动条。
313
324
  * - `fixed` —— `table-layout: fixed`:列宽受声明约束、**不再被内容撑破**,
314
- * 于是百分比 / 数字权重可以「吃满容器 + 严格按比例」。代价:长文本列要显式 `ellipsis: true`,
315
- * 否则会**换行**(行高变高)。
325
+ * 于是百分比 / 数字权重可以「吃满容器 + 严格按比例」。表体**默认不换行**(`nowrap` + 省略号),
326
+ * 要换行的列显式 `wrap: true`。
316
327
  */
317
328
  layout: {
318
329
  type: PropType<"auto" | "fixed">;
@@ -68,8 +68,8 @@ const Table = /* @__PURE__ */ defineComponent({
68
68
  * - `auto`(缺省)—— 列宽随内容自适应。`column.width` 是**建议值**,
69
69
  * 长文本(尤其中文长串 / 无空格串)会顶破列宽把表格撑得比容器还宽 ⇒ 包裹层出横向滚动条。
70
70
  * - `fixed` —— `table-layout: fixed`:列宽受声明约束、**不再被内容撑破**,
71
- * 于是百分比 / 数字权重可以「吃满容器 + 严格按比例」。代价:长文本列要显式 `ellipsis: true`,
72
- * 否则会**换行**(行高变高)。
71
+ * 于是百分比 / 数字权重可以「吃满容器 + 严格按比例」。表体**默认不换行**(`nowrap` + 省略号),
72
+ * 要换行的列显式 `wrap: true`。
73
73
  */
74
74
  layout: {
75
75
  type: String,
@@ -177,7 +177,7 @@ const Table = /* @__PURE__ */ defineComponent({
177
177
  const leadColumns = computed(() => (hasSelectColumn.value ? 1 : 0) + (props.expandable ? 1 : 0));
178
178
  const columnCount = computed(() => leadColumns.value + props.columns.length);
179
179
  const isEmpty = computed(() => props.data.length === 0);
180
- const cellClass = (column) => [column.align === "center" ? "mh-table__cell-center" : "", column.align === "right" ? "mh-table__cell-amount" : "", column.ellipsis ? "mh-table__cell-ellipsis" : ""].filter(Boolean);
180
+ const cellClass = (column) => [column.align === "center" ? "mh-table__cell-center" : "", column.align === "right" ? "mh-table__cell-amount" : "", column.wrap ? "mh-table__cell-wrap" : ""].filter(Boolean);
181
181
  const colStyles = computed(() => {
182
182
  const weightSum = props.columns.reduce((sum, item) => typeof item.width === "number" ? sum + item.width : sum, 0);
183
183
  return props.columns.map((column) => {
@@ -40,9 +40,10 @@ export interface TextProps {
40
40
  *
41
41
  * ## 与 `Title` / 平台默认的关系
42
42
  *
43
- * 浏览器给 `h1`-`h6` 的字阶已被 reset 归零(`font-size: inherit` / `font-weight: inherit`),
44
- * 所以 `as="h2"` 只贡献**语义**,视觉完全由 `size` / `weight` 决定 ——
45
- * 这与「标题的字阶必须跟设计稿」不冲突:要多大显式给 `size`。
43
+ * `as="h2"` 只贡献**语义**,视觉完全由 `size` / `weight` 决定:字号由 `size` 档(缺省 `body`)显式给,
44
+ * 浏览器给 `h1`-`h6` 的那套字阶(`1.5em` + 粗体)由**本件自己的面**归零(`.mh-text:where(h1…h6)`,
45
+ * 只点名标题标签 —— `strong` 的加粗是它唯一的表达,不归零)。元素级基线**不再**代管这条:
46
+ * 它在 Tailwind 的层外,会压住消费方的 `text-*` 工具类(理由见 `styles/reset.css` 文件头)。
46
47
  *
47
48
  * ## 不做什么
48
49
  *
@@ -1,4 +1,4 @@
1
- import { defineComponent, onMounted, onBeforeUnmount, createVNode } from "vue";
1
+ import { defineComponent, onMounted, onBeforeUnmount, createVNode, ref, onUpdated } from "vue";
2
2
  import { Icon } from "@manohub/icon";
3
3
  const TONE_ICON = {
4
4
  info: "info",
@@ -110,7 +110,22 @@ const ToastStack = /* @__PURE__ */ defineComponent({
110
110
  slots,
111
111
  attrs
112
112
  }) {
113
+ const rootRef = ref(null);
114
+ const raise = () => {
115
+ const el = rootRef.value;
116
+ if (!el) return;
117
+ if (!el.hasAttribute("popover")) el.setAttribute("popover", "manual");
118
+ if (typeof el.showPopover !== "function") return;
119
+ try {
120
+ if (el.matches(":popover-open")) el.hidePopover();
121
+ el.showPopover();
122
+ } catch {
123
+ }
124
+ };
125
+ onMounted(raise);
126
+ onUpdated(raise);
113
127
  return () => createVNode("div", {
128
+ "ref": rootRef,
114
129
  "class": ["mh-toast-stack", attrs.class].filter(Boolean).join(" "),
115
130
  "style": attrs.style
116
131
  }, [slots.default?.()]);
@@ -22,8 +22,11 @@
22
22
  color: var(--ui-base-100); /* 占位前景 */
23
23
  }
24
24
 
25
- /* 图片:撑满 + 跟随形态自裁(否则去掉容器 overflow 后长宽不符的图会外溢) */
25
+ /* 图片:撑满 + 跟随形态自裁(否则去掉容器 overflow 后长宽不符的图会外溢)。
26
+ `display: block` 必须自己写:元素级基线不再把 `img` 转块级(那条会压消费方的 `h-*` / `hidden`),
27
+ 否则行内基线间隙会把圆形头像顶偏。 */
26
28
  .mh-avatar > img {
29
+ display: block;
27
30
  width: 100%;
28
31
  height: 100%;
29
32
  border-radius: inherit;
@@ -89,8 +89,10 @@
89
89
  border-color: transparent;
90
90
  }
91
91
 
92
+ /* 中性软底取 `base-200`(中性面)—— 改前是 `base-50`:那档只比内容底低 1.7 L*,
93
+ 软底淡到与背景不可分(同族 `--primary` 软底也走「弱底」档,此处与之等形)。 */
92
94
  .mh-badge--soft.mh-badge--default {
93
- --mh-badge-bg: var(--ui-base-50);
95
+ --mh-badge-bg: var(--ui-base-200);
94
96
  --mh-badge-fg: var(--ui-base-content-muted);
95
97
  }
96
98
 
@@ -40,6 +40,8 @@
40
40
  font-family: inherit;
41
41
  font-size: var(--ui-button-font-size);
42
42
  line-height: var(--ui-line-body);
43
+ /* 链接形态时本体就是 `<a>`(见组件层),基线不再代管 `a { text-decoration: inherit }`,这里自己摘掉下划线 */
44
+ text-decoration: none;
43
45
  cursor: pointer;
44
46
  white-space: nowrap;
45
47
  /* 三态补间(底 / 边 / 字 / 影) */
@@ -163,10 +165,13 @@
163
165
  color: var(--ui-button-accent);
164
166
  }
165
167
 
166
- /* ===== 虚线:区域内添加内容 ===== */
168
+ /* ===== 虚线:区域内添加内容 =====
169
+ * 虚线也是「框」→ 取 `--ui-base-300`(全库唯一的「线 / 框」色,出处 `@manohub/theme` 的
170
+ * `default/colors.css` 头「三条口径」第 2 条;`base-400` 只服务勾选类小控件框体)。
171
+ * 改前取的是 `base-400`("虚线要深一档才看得见"),2026-09-30 按库级口径收敛。 */
167
172
  .mh-button--dashed {
168
173
  background: var(--ui-base-100);
169
- border: 1px dashed var(--ui-base-400);
174
+ border: 1px dashed var(--ui-base-300);
170
175
  color: var(--ui-button-accent);
171
176
  }
172
177
  .mh-button--dashed:hover,
@@ -1,41 +1,29 @@
1
1
  /**
2
- * Filter 筛选条(`Form` 的一层壳:`Form` + `Form.Header` + 字段区)。
2
+ * Filter(筛选条):**本件无自有面 —— 零规则**。
3
3
  *
4
- * ## 一份面只补两件事
4
+ * 它现在是一层**纯壳**:只给 `Form` 传「排布档 + label 列宽(可选)+ 值模型 + 字段区回车」,
5
+ * 字段行的排布与几何**全部沿用 `Form`**:
5
6
  *
6
- * 字段行、label 列、控件列、上行与两颗按钮全部复用 `form.css`(`Form` / `Form.Header` /
7
- * `Form.Item`)——本面**不重画**输入框、label、按钮,只补:
8
- * 1. 字段区的排布(4 字段一行自动换行,列距 24 / 行距 16);字段宽自适应
9
- * (label 列宽与「上行 ↔ 字段区」的行距由 `Filter` 传给 `Form` 的 `labelWidth` / `gap`,
10
- * 它们不是 CSS 面的事 —— 见 `components/filter/index.tsx` 的两个常量)。
7
+ * 1. 字段排布 = `Form` 的多列动态档(本件传 `columns="auto"`)—— 网格轨道**跨行共用**,
8
+ * 因此换行下来的字段与上一行**逐列对齐**。从前本件自建 flex 字段区
9
+ * (`flex: 1 1 240px` + `wrap`),而 flex 的 `grow` 是**按行分配**的:末行只剩一项时
10
+ * 它独占该行剩余宽度,被拉成通栏 —— 那正是本件 2026-09 交出机制的起因;
11
+ * 2. label 列宽 / 行距 / 列距 / 字段最小宽 = `Form` 的默认档(令牌 + `.mh-form` 的 8px 档)。
12
+ * 本件**不设默认值、也不落行内值** —— 写死就是第二份默认值,`Form` 调档时不会跟着变;
13
+ * 消费方要收紧筛选条的几何,在容器上覆盖 `Form` 的那两个令牌即可(继承生效);
14
+ * 3. 表头(`Form.Header`)与字段行(`Form.Item`)一律沿用 `Form`,本件不重画任何一个。
11
15
  *
12
- * 出处:Figma「MH后台 0911」node 0:3355(页面「01.1 技能权限分配」的筛选区),几何 1:1:
13
- * 上行高 32(右侧两颗按钮)、下行字段「标签 + 10±2px + 控件(高 32 / 圆角 6 / 边 --ui-base-300)」。
16
+ * ## 为什么还留着这个文件(而不是删掉)
14
17
  *
15
- * 两处**有意不照抄**(理由写在这里,免得后人当成漏项补上):
16
- * 1. 稿里「预制筛选方案」矩形上挂了一条**绿色**投影 `DROP_SHADOW(10, #4ec87a@30%)` ——
17
- * 与主色无关,判定为设计稿误留;本件不画(那颗按钮也不内建);
18
- * 2. 稿里底部那条 1px 线**宽 1200**(整页宽),而筛选内容只有 1160 —— 它是**页面级分隔**,
19
- * 由页面承担;本件画了就会与页面的线叠成双线。
18
+ * 「组件与面一一配对」是本包的机械护栏(用例 `contract-class-surface` 要求每个组件目录
19
+ * 都有同名面文件)。本件确实没有自己的类与几何,故这里只留说明文字。它对外的唯一类名是
20
+ * `.mh-filter__form` —— **结构锚点**(消费方与用例靠它定位这个壳,几何沿用 `.mh-form`),
21
+ * 已登记在共用台账「只做结构 / 测试锚点、面上没有样式声明的类」里。
20
22
  *
21
- * 同理**不带左右内距**:左右 20 由页面筛选位(`.mh-page__filter` 的 `14px 20px 0`)给,
22
- * 组件里再给一次会变成 40。label 列宽由 `Filter` 传 `Form` 的 `labelWidth`(缺省 56),
23
- * 因此本文件**不覆盖主题的 `--ui-form-label-width`**(面上定义 `--ui-*` 必须以本文件名作命名空间)。
23
+ * ## 与设计稿的关系
24
+ *
25
+ * 稿(Figma「MH后台 0911」node 0:3355,页面「01.1 技能权限分配」的筛选区)只用来确定
26
+ * **筛选条由哪些部件组成**(上行两颗按钮 + 下行若干字段);几何**不再照抄**稿里的
27
+ * 68 / 16 / 24 / 240,自 2026-09 起统一走 `Form` 的默认档 —— 取舍见
28
+ * `components/filter/index.tsx` 的文件头。
24
29
  */
25
-
26
- /* 字段区:列距 24 / 行距 16,窄了整体换行(不压缩控件高度、不截断标签)。
27
- 字段区是 `Form` 的直接子节点(`Form.Header` 归位在最前),以下均为 `Form.Item` 自身。 */
28
- .mh-filter__fields {
29
- display: flex;
30
- flex-wrap: wrap;
31
- align-items: center;
32
- column-gap: var(--ui-space-6);
33
- row-gap: var(--ui-space-4);
34
- }
35
-
36
- /* 字段(`Filter.Item` = 挂了这个类的 `Form.Item`):宽度自适应
37
- —— 够宽均分铺满,不够则换行;label 列宽与控件列取用全部来自 form.css。 */
38
- .mh-filter__item {
39
- flex: 1 1 var(--ui-filter-item-min);
40
- min-width: 0;
41
- }
@@ -109,15 +109,27 @@
109
109
  * 这里只补**作者源没覆盖、会被 `<button>` 的原生 UA 样式漏进来的四项**(实测 Chrome 的 html.css:
110
110
  * `button { align-items: flex-start; text-align: center; appearance: auto; font: 400 13.3333px Arial }`):
111
111
  *
112
- * - `align-items: center` —— 控件是 flex 行,UA 的 `flex-start` 会把值 / 标签顶到上沿;
112
+ * - `align-items: center` —— 消费方把控件开成 flex 行时(如 `Select` 的 `.mh-select`)拦住 UA 的
113
+ * `flex-start`(它会把值 / 标签顶到上沿)。本件缺省**不给 `display`**,控件是 inline-block,此时
114
+ * 垂直居中来自浏览器对 `<button>` 内容的默认居中 —— 这条声明在那个形态下用不到,留着是与复合件同一份规格;
113
115
  * - `text-align: inherit` —— UA 的 `center` 会**继承**给值文本;
114
116
  * - `font-weight` / `line-height: inherit` —— UA 的 `font` 简写会把这两项从继承链上摘下来,
115
117
  * 而作者源只写了 `font-family` 与 `font-size`(对比:`<input>` 的 UA 不设这两项,故输入框形态天然一致);
116
118
  * - `appearance: none` —— 防仍画原生按钮皮肤的引擎(Safari 一类)。
117
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
+ *
118
127
  * 禁写:本块不碰框 / 色 / 尺寸 / 内距 —— 那些归 `.mh-input` 与表壳,重复声明就是又开一份会漂的规格。 */
119
128
  .mh-input--trigger {
120
129
  appearance: none;
130
+ white-space: nowrap;
131
+ overflow: hidden;
132
+ text-overflow: ellipsis;
121
133
  align-items: center;
122
134
  font-weight: inherit;
123
135
  line-height: inherit;
@@ -127,6 +139,30 @@
127
139
  cursor: pointer;
128
140
  }
129
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
+
130
166
  /* ===== 触发器形态 · 整箱热区(命中测试重定向,纯 CSS) =====
131
167
  *
132
168
  * 目的:让**整件**(值 / 标签 + 右侧图标 + 左右内距)都等同于点那个原生触发按钮。
@@ -28,9 +28,11 @@
28
28
  --ui-input-font-size: var(--ui-font-body); /* 14:控件内容走正文档 */
29
29
  --ui-input-font-size-sm: var(--ui-font-caption); /* 13 */
30
30
  --ui-input-font-size-lg: var(--ui-font-heading); /* 16 */
31
- /* 描边取 base-300(= 设计 `0:3946` 搜索框的 `#e6e6e6`),与卡片 / 表格 / 按钮 / 分页同一条线。
32
- ⚠️ 改前是 base-400,而 `filter.css` 的既有裁决早已写明「控件框边 = --ui-base-300」——
33
- 本件与它是同一条规格,之前的 base-400 属自相矛盾。base-400 从此只服务勾选类小控件框体。 */
31
+ /* 描边取 base-300 —— 这**不是本件自选**,是库级口径:`base-300` 是全库唯一的「线 / 框」色
32
+ (输入框、按钮、表格、分页项、卡片、面板、分隔线一律取它),`base-400` 只服务勾选类小控件框体。
33
+ 权威出处是 `@manohub/theme` 的 `default/colors.css` 头「三条口径」第 2 条(改值前先读那三条)。
34
+ 括注的 `#e6e6e6` 是 **default** 套该槽位的值;**farris** 套同槽位取 Ne08(`#e8ebf2`)——
35
+ 两套值不同、槽位语义相同,别按规范原文给输入框的 Ne04 来反推(那正是本件改前的 base-400)。 */
34
36
  --ui-input-line: var(--ui-base-300);
35
37
  --ui-input-line-hover: var(--ui-primary-hover); /* 滑过 / 聚焦转主色(规范 Th08) */
36
38
  --ui-input-textarea-min-height: 56px; /* 多行最小高(两行正文 + 上下内距) */
@@ -253,7 +253,13 @@
253
253
  --ui-pagination-font-size: var(--ui-font-body);
254
254
  }
255
255
 
256
- /* ===== 跳页(showQuickJumper) ===== */
256
+ /* ===== 跳页(showQuickJumper) =====
257
+ *
258
+ * 跳页框是**输入框**,画线取 `--ui-base-300` —— 与 `Input` 同一条口径(`base-300` 是全库唯一的
259
+ * 「线 / 框」色,出处 `@manohub/theme` 的 `default/colors.css` 头「三条口径」第 2 条;
260
+ * `base-400` 只服务勾选类小控件框体)。**改前取的是 `base-400`**:同一个「输入框」在库里因此
261
+ * 有过两种线色,属跨件口径漏改(2026-09-30 收敛)。
262
+ * 禁用档与 `Input` 同构:底转 `base-200`、线仍 `base-300` —— 即**靠底色区分禁用**,不再靠线深。 */
257
263
  .mh-pagination__jump {
258
264
  display: inline-flex;
259
265
  align-items: center;
@@ -265,7 +271,7 @@
265
271
  width: 56px;
266
272
  height: var(--ui-pagination-item-size);
267
273
  padding: 0 var(--ui-space-2);
268
- border: 1px solid var(--ui-base-400);
274
+ border: 1px solid var(--ui-base-300);
269
275
  border-radius: var(--ui-pagination-item-radius);
270
276
  background: var(--ui-base-100);
271
277
  font-family: inherit;
@@ -23,6 +23,9 @@
23
23
  * 表壳挂 `.mh-popup-anchor`(`anchor-name`)、面板挂 `.mh-popup--anchored`(`position-anchor`),
24
24
  * 锚点即整件;非锚定场景回落到 240px 兜底值。
25
25
  *
26
+ * **面板有高度上限**:`--ui-select-panel-max-height`(默认 320px)钉在面板根上,超出即内容区
27
+ * (`.mh-select__body`)滚动、搜索行常驻 —— 列表与树两形态共用这一份,形态里不许各写限高。
28
+ *
26
29
  * **一处有色差异**:规范的选中行是灰底(Ne10),本仓的「选中」统一走**主色浅底**
27
30
  * `--ui-primary-subtle`(与列表 / 树的选中态同一语义槽位),否则悬停(Ne11)与选中(Ne10)两档灰
28
31
  * 在本仓有限的灰阶里挤成同色、看不出选中。
@@ -66,7 +69,9 @@
66
69
  height: calc(var(--ui-select-chip-height) - 4px);
67
70
  }
68
71
 
69
- /* 无选中值时的提示文字(组件把 placeholder 渲染进 `__value` 并挂这个修饰类) */
72
+ /* 值区:有值时的单选那一行(占满剩余宽、超出省略)。
73
+ ⚠️ **无值态不在这里** —— 占位由输入面渲染(`.mh-input__placeholder`,色与截断同档):
74
+ 2026-09-29 收敛,此前本文件还有一个 `--placeholder` 修饰类专门给占位上灰字。 */
70
75
  .mh-select__value {
71
76
  flex: 1;
72
77
  min-width: 0;
@@ -74,9 +79,6 @@
74
79
  text-overflow: ellipsis;
75
80
  white-space: nowrap;
76
81
  }
77
- .mh-select__value--placeholder {
78
- color: var(--ui-base-content-subtle);
79
- }
80
82
  /* 下拉箭头:渲染在输入面的**右侧图标位**(`.mh-input__suffix`,表壳的子元素)里 ——
81
83
  位置由表壳的 flex 排布给(控件 `flex: 1 1 auto` 自然把附加位挤到右端),故不再需要 `margin-left: auto`。
82
84
  字形由 @manohub/icon 给;展开转向由 popup.css 的 `.mh-popup-caret` 承担(含触发器进表壳后的兄弟档);
@@ -107,10 +109,25 @@
107
109
  background: var(--ui-base-200);
108
110
  color: var(--ui-base-content-muted);
109
111
  }
110
- /* ===== 面板皮肤(与操作层并列组合:`class="mh-popup mh-select__panel mh-popup--anchored"`) ===== */
112
+ /* ===== 面板皮肤(与操作层并列组合:`class="mh-popup mh-select__panel mh-popup--anchored"`) =====
113
+ **面板是列容器 + 限高**:搜索行常驻(不滚),下一条的 `.mh-select__body` 承担滚动 ——
114
+ 列表与树两形态因此共用同一份「长内容不撑破面板」的规格。
115
+ (历史症状:限高原先只写在树体 `.mh-select__tree` 上,而列表形态的选项是面板的**直接子节点**,
116
+ 面板会被一路撑高;实测 60 条选项的面板高 2212px、视口只有 925px。) */
111
117
  .mh-select__panel {
112
118
  /* 锚 = `.mh-popup-anchor`(挂在表壳上)→ 面板与**整件**同宽;非锚定场景回落 240 */
113
119
  width: anchor-size(width, 240px);
120
+ /* 列容器:搜索行 `flex: none` + 内容区 `flex: 1 / min-height: 0`。
121
+ ⚠️ 加 `display: flex` 会以作者源压过 UA 给**收起态**定的 `display: none`,收起态靠操作层
122
+ `operations/popup.css` 里那条 `:is()` 兜底(把收起的浮层重新钉成 `display: none`,
123
+ 特异性 0-3-0、高于本行)—— **那条兜底不能删**(与 `dialog.css` 同一处坑)。 */
124
+ display: flex;
125
+ flex-direction: column;
126
+ /* 限高(**含搜索行**):超出即内容区滚动。面板仍贴锚点,只是不再无限长 */
127
+ max-height: var(--ui-select-panel-max-height);
128
+ /* UA 给 `[popover]` 的是 `overflow: auto`,必须归零:内容区已是滚动容器,
129
+ 留一层就会与它叠成两条滚动条(同一个面板上实测会同时出现)。 */
130
+ overflow: hidden;
114
131
  padding: var(--ui-space-1) 0;
115
132
  border-radius: var(--ui-select-radius);
116
133
  background: var(--ui-base-100);
@@ -120,6 +137,15 @@
120
137
  box-shadow: var(--ui-shadow-popover);
121
138
  }
122
139
 
140
+ /* 内容区:**面板里唯一的滚动容器**(列表体的选项行 / 树体的树都装在它里面)。
141
+ `min-height: 0` 是这一档生效的前提:列容器里弹性项的可收缩下限默认取内容高,
142
+ 不给它,内容就永远压不下去、上面的限高形同虚设(与 `panel.css` 的 `.mh-panel__body` 同款)。 */
143
+ .mh-select__body {
144
+ flex: 1;
145
+ min-height: 0;
146
+ overflow-y: auto;
147
+ }
148
+
123
149
  /* 选项行:滑过 / 选中 / 禁用 / 提示 / 空态 */
124
150
  .mh-select__option {
125
151
  position: relative;
@@ -214,21 +240,24 @@
214
240
  cursor: default;
215
241
  }
216
242
 
217
- /* 搜索行(选项多于 8 条时由组件渲染) */
243
+ /* 搜索行(**消费方显式 `searchable` 时才由组件渲染**,件不按选项条数自动决断)。
244
+ `flex: none`:它是面板的**常驻**行 —— 面板限高时收缩与滚动都归内容区,
245
+ 少了它,搜索行会被一起压扁(列容器里它默认是可收缩的)。 */
218
246
  .mh-select__search {
247
+ flex: none;
219
248
  padding: var(--ui-space-2) var(--ui-space-2) var(--ui-space-1);
220
249
  }
221
250
 
222
251
  /* ===== 树体(树选择 `SelectTree` 的面板内容)=====
223
252
  边界要认清楚:**面板皮肤归本文件**(上一节),**树的行态归 tree 面**(`.mh-tree__*` 那一族,
224
253
  本文件一行都不碰 —— 跨族类名须先登记跨面豁免,这里不走那条路)。
225
- 故本件只做「把树安放进面板」这一件事:内距 + 限高 + 溢出滚动。
226
- 行高 / 缩进 / 滑过与选中底色全部沿用 tree 面,两处形态不会漂移。 */
254
+ 故本件只做「把树安放进面板」这一件事:内距。
255
+ 行高 / 缩进 / 滑过与选中底色全部沿用 tree 面,两处形态不会漂移;
256
+ **限高与滚动也不在这里** —— 那是面板(`.mh-select__panel`)与面板内容区(`.mh-select__body`)的事,
257
+ 列表与树两形态共用同一份(形态里各写一条就会漂移,本件此前正是自己写了一条)。 */
227
258
  .mh-select__tree {
228
259
  /* 行底满宽、只有内容缩进(缩进公式在 tree 面),故左右各留一个最小呼吸位 */
229
260
  padding: 0 var(--ui-space-1) var(--ui-space-1);
230
- max-height: var(--ui-select-tree-max-height);
231
- overflow-y: auto;
232
261
  }
233
262
 
234
263
  /* 命中高亮(搜索命中的那段文字,由组件的 `renderLabel` 落进 `.mh-tree__label` 槽)。
@@ -30,7 +30,8 @@
30
30
  --ui-select-option-selected: var(--ui-primary-subtle); /* 见头部「有色差异」 */
31
31
  --ui-select-chip-height: 24px; /* 多选标签(规范复用标签族 24 档) */
32
32
  --ui-select-icon-size: var(--ui-font-icon); /* 下拉箭头:常规图标档 */
33
- /* 树形态(SelectTree):面板内树体的限高 —— 约等于 9 行(行高 34 + 行距 6 的节距 40 见 tree.css),
34
- 超过即滚动,长树不把面板撑成整屏 */
35
- --ui-select-tree-max-height: 320px;
33
+ /* 面板限高(**列表与树两形态共用**):面板整体(含搜索行)不超此高,超出即内容区滚动。
34
+ 320px ≈ 8 行选项(选项行 36 + 面板上下内距 8);树形态可见行数按 tree 面的节距 40 折算
35
+ (行高 34 + 行距 6),有无搜索行会差一行上下。要改高度改这枚令牌,不要在业务侧压 max-height。 */
36
+ --ui-select-panel-max-height: 320px;
36
37
  }
@@ -27,9 +27,11 @@
27
27
  --ui-steps-connector-length: 58.383px; /* 规范实测最小段长(其余由 flex 撑开) */
28
28
  --ui-steps-connector-width: 2px;
29
29
  --ui-steps-connector-arrow: 6px;
30
- /* 连接线沿用 base-400:设计稿里**只有三个向导页**画了连接线(`#cdd2de`),而向导页已判定为旧稿;
31
- 四个品牌页没有步骤条样张 → 按「未覆盖槽位沿用现值并注明」处理,不做推测性改动。 */
32
- --ui-steps-connector-line: var(--ui-base-400);
30
+ /* 连接线取 base-300(全库唯一的「线 / 框」色,见 `@manohub/theme` 的 `default/colors.css`
31
+ 头「三条口径」第 2 条)—— 改前取的是 base-400。设计稿里**只有三个向导页**画了连接线
32
+ (`#cdd2de`)而向导页已判定为旧稿,四个品牌页没有步骤条样张 → 按库级口径取线色,
33
+ 不做推测性改动(这枚令牌的档位语义与连接线宽度 / 燕尾尺寸一样,只在「线」这一族内变)。 */
34
+ --ui-steps-connector-line: var(--ui-base-300);
33
35
  --ui-steps-connector-line-active: var(--ui-primary); /* 规范 #4999fe */
34
36
  --ui-steps-title-gap: var(--ui-space-3); /* 节点与标题间距 12 */
35
37
  --ui-steps-seg-width: 140px;
@@ -79,10 +79,10 @@
79
79
  border: none;
80
80
  border-radius: 0;
81
81
  }
82
- /* 紧凑档 */
82
+ /* 紧凑档(`size="sm"`):三档 30 / 38 / 46 里的最小档,嵌在卡片 / 面板里的表格用它 */
83
83
  .mh-table--xs {
84
- --ui-table-head-height: 36px;
85
- --ui-table-row-height: 38px;
84
+ --ui-table-head-height: 30px;
85
+ --ui-table-row-height: 30px;
86
86
  }
87
87
  /* 窄屏下的最小宽(触发横向滚动,而不是把列压扁) */
88
88
  .mh-table--wide {
@@ -94,14 +94,21 @@
94
94
  * 列宽从此**受声明约束、不再被内容撑破** ⇒ 长文本不会把表格撑得比容器还宽,
95
95
  * 于是「百分比 / 数字权重」可以「吃满容器 + 严格按比例」。
96
96
  *
97
- * 代价(调用方要知道):长文本列必须显式 `ellipsis: true`,否则会**换行**(行高变高,见下条)。 */
97
+ * 调用方要知道的:表体**默认不换行**(nowrap + 省略号,见下条);要换行的列显式标 `wrap: true`。 */
98
98
  .mh-table--fixed {
99
99
  table-layout: fixed;
100
100
  }
101
- /* 换行兜底:fixed 已保证「不撑破容器」,这条只防**无空格长串**(如 500 字符的 token)
102
- 溢出单元格、视觉上压到相邻列。`anywhere` 而不是 `break-word`:后者不参与最小内容宽计算。 */
101
+ /* `fixed` 档表体默认**不换行**(nowrap + 省略号)。两条依据:
102
+ * ① 列宽已被 `<colgroup>` 钉死,换行救不回内容,只会把行高顶高(`td` 的 `height` 只是**下限**);
103
+ * ② 换行必然伴随**从字符间断字** —— 实测序号列窄到装不下两位数时,旧的 `overflow-wrap: anywhere`
104
+ * 把 `10` 拆成 `1` / `0` 两行,行高 30 → **49**。
105
+ * 于是「换行」改成**列级主动选择**:`TableColumn.wrap: true`(见 `.mh-table__cell-wrap`)。
106
+ * ⚠️ **只在本档翻默认**:`auto` 档的列宽由内容计算,td 默认 nowrap 会让长文本列不再靠换行收敛、
107
+ * 表格更容易撑破容器 —— 与本档「表头裁切只加在 fixed」同一条判据(见上一条注释)。 */
103
108
  .mh-table--fixed tbody td {
104
- overflow-wrap: anywhere;
109
+ overflow: hidden;
110
+ text-overflow: ellipsis;
111
+ white-space: nowrap;
105
112
  }
106
113
  /* 前导列(勾选 / 展开)在 auto 下靠内容把 40px 的「建议宽」撑出去;fixed 下是硬宽,
107
114
  而 `thead th` / `tbody td` 的标准左右内距各 16px ⇒ 可用内容只有 40 − 32 = 8px,
@@ -248,8 +255,12 @@
248
255
  z-index: 2; /* 表头既钉顶又钉左,压住表头其余列 */
249
256
  background: var(--ui-table-head-bg);
250
257
  }
251
- /* `--sticky-head`:表头钉顶 */
252
- .mh-table--sticky-head thead th {
258
+ /* `--sticky-head`:表头钉顶。
259
+ * ⚠️ 选择器必须带 `tr`(0-1-3):文件后段「拖拽线的定位上下文」那条
260
+ * `.mh-table thead th { position: relative }` 是 **0-1-2**,与不写 `tr` 的写法**同特异性且在后**
261
+ * ⇒ 会把 `position: sticky` 覆盖掉,`stickyHead` 静默失效(0.6.0 起就存在,0.9.0 复核仍在)。
262
+ * `sticky` 同样是定位元素,照样给表头里的拖拽线当包含块,故加 `tr` 不损失原能力。 */
263
+ .mh-table--sticky-head thead tr th {
253
264
  position: sticky;
254
265
  top: 0;
255
266
  z-index: 1;
@@ -271,16 +282,16 @@
271
282
  .mh-table__cell-link:hover {
272
283
  text-decoration: underline;
273
284
  }
274
- /* 长文本省略(列定义里 `ellipsis: true` 时下发)。
275
- * 基础三件套与档位无关;`max-width` **只在 auto 档兜底**:
276
- * fixed 档的列宽已由 `<colgroup>` 钉死,再来一条 150px 的 max-width 会把宽列又压回 150(自相矛盾)。 */
277
- .mh-table__cell-ellipsis {
278
- overflow: hidden;
279
- text-overflow: ellipsis;
280
- white-space: nowrap;
281
- }
282
- .mh-table:not(.mh-table--fixed) .mh-table__cell-ellipsis {
283
- max-width: var(--ui-table-ellipsis);
285
+ /* 列级**放开换行**(列定义里 `wrap: true` 时下发,只作用于表体 `td`;表头始终 nowrap)。
286
+ * 缺省不换行在面文件的 `.mh-table--fixed tbody td` 里 —— 本档才翻默认,故这里同时把
287
+ * 「换行三件套」与 `overflow-wrap` 兜底一并给回:换来的是「长文本列自己决定换行」。
288
+ * ⚠️ 选择器必须带 `td`(0-1-3):`.mh-table--fixed tbody td` 是 **0-1-2**,
289
+ * 单个类(0-1-0)压不过它(同 §表头对齐那条的先例)。 */
290
+ .mh-table tbody td.mh-table__cell-wrap {
291
+ white-space: normal;
292
+ overflow: visible;
293
+ text-overflow: clip;
294
+ overflow-wrap: anywhere;
284
295
  }
285
296
  .mh-table__cell-icon {
286
297
  display: inline-flex;
@@ -399,10 +410,10 @@
399
410
  color: var(--ui-warning); /* 规范 #ff6c29 */
400
411
  }
401
412
 
402
- /* ===== 尺寸档:大号(详情页 / 展示型表格) ===== */
413
+ /* ===== 尺寸档:大号(详情页 / 展示型表格)—— 三档 30 / 38 / 46 里的最大档 ===== */
403
414
  .mh-table--lg {
404
- --ui-table-head-height: 52px;
405
- --ui-table-row-height: 56px;
415
+ --ui-table-head-height: 46px;
416
+ --ui-table-row-height: 46px;
406
417
  --ui-table-padding-x: var(--ui-space-6);
407
418
  }
408
419
 
@@ -11,8 +11,12 @@
11
11
  [data-manohub-ui] {
12
12
  /* 单件令牌 */
13
13
  --ui-table-font-size: var(--ui-font-caption);
14
- --ui-table-head-height: 34px; /* 设计 1160×34 */
15
- --ui-table-row-height: 30px; /* 设计 919×30,节距 31 */
14
+ /* 尺寸档三档 = 30 / 38 / 46(sm / md / lg),本节是**基础值 = 中档(md)**,
15
+ 与 Button(24 / 32 / 40)、Tree(基础 34、紧凑 28)同一条库内约定:
16
+ 基础令牌承担 md,`.mh-<件>--<档>` 只改写另两档。
17
+ ⚠️ 两个档位改写分别在面文件的 `.mh-table--xs`(sm)与 `.mh-table--lg` 里,别写回本文件。 */
18
+ --ui-table-head-height: 38px; /* 中档(md)表头 = 行高 */
19
+ --ui-table-row-height: 38px; /* 中档(md)行高 */
16
20
  --ui-table-line-height: var(--ui-line-caption); /* 单元格行高 = 正文档的 13px 配对(default 20 / farris 18) */
17
21
  --ui-table-head-bg: var(--ui-base-200); /* 设计 #f1f2f5 */
18
22
  --ui-table-head-text: var(--ui-base-content-muted);
@@ -29,10 +33,6 @@
29
33
  --ui-table-link: var(--ui-primary);
30
34
  --ui-table-padding-x: var(--ui-space-4); /* 单元格左右内距 16 */
31
35
  --ui-table-col-check: 40px;
32
- /* 长文本列的兜底上限 —— **只在 `layout="auto"` 下生效**(`.mh-table:not(.mh-table--fixed)`
33
- 作用域内)。`layout="fixed"` 下列宽由 `<colgroup>` 钉死,本令牌不参与,
34
- 否则「宽列」会被它压回 150px 而自相矛盾。要按列给宽请用 `TableColumn.width`。 */
35
- --ui-table-ellipsis: 150px;
36
36
  --ui-table-drag-line: var(--ui-primary);
37
37
  --ui-table-drag-line-width: 3px;
38
38
  --ui-table-drag-line-height: 40px;
@@ -132,8 +132,10 @@
132
132
  appearance: none;
133
133
  pointer-events: none;
134
134
  }
135
+ /* 悬停底取 `base-200`(中性面)—— 改前是 `base-50`:那档只比内容底低 1.7 L*,
136
+ 在多数屏幕上看不出悬停反馈(`base-50` 不再作为「更轻底」的默认去处,见 colors 口径第 1 条)。 */
135
137
  .mh-tag--select:hover {
136
- background: var(--ui-base-50);
138
+ background: var(--ui-base-200);
137
139
  border: 1px solid var(--ui-base-300);
138
140
  }
139
141
  .mh-tag--select:has(input:focus-visible) {
@@ -160,11 +162,13 @@
160
162
  cursor: not-allowed;
161
163
  }
162
164
 
163
- /* ===== 新增档(虚线框 + 加号由 @manohub/icon 的 plus 渲染)===== */
165
+ /* ===== 新增档(虚线框 + 加号由 @manohub/icon 的 plus 渲染)=====
166
+ * 虚线框取 `--ui-base-300`(全库唯一的「线 / 框」色,见 `@manohub/theme` 的 `default/colors.css`
167
+ * 头「三条口径」第 2 条);改前取 `base-400`,2026-09-30 按口径收敛。 */
164
168
  .mh-tag--add {
165
169
  height: var(--ui-tag-height);
166
170
  padding: 0 var(--ui-space-2);
167
- border: 1px dashed var(--ui-base-400);
171
+ border: 1px dashed var(--ui-base-300);
168
172
  border-radius: var(--ui-radius-full);
169
173
  background: transparent;
170
174
  color: var(--ui-base-content-muted);