@manohub/ui 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) 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 +48 -7
  9. package/dist/components/input/index.js +23 -3
  10. package/dist/components/radio/index.js +29 -3
  11. package/dist/components/search/index.js +37 -35
  12. package/dist/components/select/index.js +1 -3
  13. package/dist/components/select/select-trigger.d.ts +4 -2
  14. package/dist/components/select/select-trigger.js +9 -4
  15. package/dist/components/table/index.d.ts +19 -8
  16. package/dist/components/table/index.js +3 -3
  17. package/dist/components/text/index.d.ts +4 -3
  18. package/dist/components/toast/index.js +16 -1
  19. package/dist/styles/components/avatar.css +4 -1
  20. package/dist/styles/components/button.css +2 -0
  21. package/dist/styles/components/filter.css +22 -34
  22. package/dist/styles/components/input.css +24 -0
  23. package/dist/styles/components/select.css +3 -4
  24. package/dist/styles/components/table.css +33 -22
  25. package/dist/styles/components/table.tokens.css +6 -6
  26. package/dist/styles/components/text.css +10 -0
  27. package/dist/styles/components/toast.css +35 -6
  28. package/dist/styles/components/upload.css +1 -0
  29. package/dist/styles/index.css +5 -4
  30. package/dist/styles/reset.css +52 -167
  31. package/package.json +2 -2
  32. package/dist/styles/components/filter.tokens.css +0 -14
@@ -211,9 +211,7 @@ const Select = /* @__PURE__ */ defineComponent({
211
211
  "class": "mh-select__chip"
212
212
  }, [option.label])), foldedCount.value > 0 ? createVNode("span", {
213
213
  "class": "mh-select__chip mh-select__chip--fold"
214
- }, [foldText.value]) : null, !hasValue ? createVNode("span", {
215
- "class": "mh-select__value mh-select__value--placeholder"
216
- }, [props.placeholder]) : null]) : void 0
214
+ }, [foldText.value]) : null]) : void 0
217
215
  }, null), createVNode(SelectPanel, {
218
216
  "id": panelId,
219
217
  "elementRef": panelRef,
@@ -35,11 +35,12 @@ export declare const SelectTrigger: import("vue").DefineComponent<import("vue").
35
35
  type: BooleanConstructor;
36
36
  default: boolean;
37
37
  };
38
+ /** 无值时的占位文案(**透传给输入面**:`hasValue === false` 时由它渲染并取代 `default` 插槽) */
38
39
  placeholder: {
39
40
  type: StringConstructor;
40
41
  default: string;
41
42
  };
42
- /** 单选显示文本(无值时不渲染它,改渲染 `placeholder`) */
43
+ /** 单选显示文本(只在**有值**时渲染;无值态由输入面的占位承担) */
43
44
  label: {
44
45
  type: StringConstructor;
45
46
  default: undefined;
@@ -93,11 +94,12 @@ export declare const SelectTrigger: import("vue").DefineComponent<import("vue").
93
94
  type: BooleanConstructor;
94
95
  default: boolean;
95
96
  };
97
+ /** 无值时的占位文案(**透传给输入面**:`hasValue === false` 时由它渲染并取代 `default` 插槽) */
96
98
  placeholder: {
97
99
  type: StringConstructor;
98
100
  default: string;
99
101
  };
100
- /** 单选显示文本(无值时不渲染它,改渲染 `placeholder`) */
102
+ /** 单选显示文本(只在**有值**时渲染;无值态由输入面的占位承担) */
101
103
  label: {
102
104
  type: StringConstructor;
103
105
  default: undefined;
@@ -38,11 +38,12 @@ const SelectTrigger = /* @__PURE__ */ defineComponent({
38
38
  type: Boolean,
39
39
  default: false
40
40
  },
41
+ /** 无值时的占位文案(**透传给输入面**:`hasValue === false` 时由它渲染并取代 `default` 插槽) */
41
42
  placeholder: {
42
43
  type: String,
43
44
  default: "请选择"
44
45
  },
45
- /** 单选显示文本(无值时不渲染它,改渲染 `placeholder`) */
46
+ /** 单选显示文本(只在**有值**时渲染;无值态由输入面的占位承担) */
46
47
  label: {
47
48
  type: String,
48
49
  default: void 0
@@ -72,6 +73,7 @@ const SelectTrigger = /* @__PURE__ */ defineComponent({
72
73
  "disabled": props.disabled,
73
74
  "clearable": props.clearable,
74
75
  "hasValue": props.hasValue,
76
+ "placeholder": props.placeholder,
75
77
  "class": "mh-popup-anchor mh-select-host",
76
78
  "controlClass": controlClass(),
77
79
  "controlAttrs": {
@@ -84,9 +86,12 @@ const SelectTrigger = /* @__PURE__ */ defineComponent({
84
86
  },
85
87
  "onChange": () => props.onClear?.()
86
88
  }, {
87
- default: () => props.renderValue ? props.renderValue() : createVNode("span", {
88
- "class": ["mh-select__value", props.hasValue ? "" : "mh-select__value--placeholder"].filter(Boolean).join(" ")
89
- }, [props.hasValue ? props.label : props.placeholder]),
89
+ default: () => props.renderValue ? props.renderValue() : (
90
+ /* 只画**有值**时的那一行;无值态在上面被输入面的占位取代(`default` 不会被调用) */
91
+ createVNode("span", {
92
+ "class": "mh-select__value"
93
+ }, [props.label])
94
+ ),
90
95
  /* 箭头沿用输入框的右侧图标位(槽内尺寸收敛见 `select.css` 的 `.mh-select-host` 一节);
91
96
  展开转向由 `popup.css` 的 `.mh-popup-caret` 承担(含触发器进表壳后的兄弟档) */
92
97
  suffix: () => createVNode(Icon, {
@@ -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;
@@ -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
  /* 三态补间(底 / 边 / 字 / 影) */
@@ -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
- }
@@ -127,6 +127,30 @@
127
127
  cursor: pointer;
128
128
  }
129
129
 
130
+ /* ===== 触发器形态 · 占位文案 =====
131
+ *
132
+ * `<button>` **没有 `::placeholder` 伪元素**,故触发器形态的占位由组件渲染一个 span 承载
133
+ * (判据见 `components/input/index.tsx` 的 `showPlaceholder`)。这里只给它的排版与色。
134
+ *
135
+ * `display: block` 是**必要的**(实测四种父级 × 长/短文案):span 默认是 inline,而 `overflow`
136
+ * 对行内盒不生效 ⇒ 长占位会直接撑破控件;`block` 之后它撑满控件的内容宽、超长走省略号,
137
+ * 且在**父级是 flex**(如 `Select` 的触发器)时会被 blockify,行为与 inline 一致
138
+ * —— 两种父级下都对,故不要求消费方给控件开 flex。`min-width: 0` 供 flex 父级下允许收缩。
139
+ * `flex: 1` 是给**父级是 flex** 的复合件用的(`Select` 的值区`.mh-select__value` 就是这个配方):
140
+ * 实测同样的「占位 + 右侧箭头」在 flex 父级下,不给 `flex: 1` 时占位只有**内容宽**(48px)、
141
+ * 给了才撑满到箭头前(207 / 可用 220)—— 少了它会让选择器的占位区窄成一截。
142
+ * 父级不是 flex 时 `flex` 属性被忽略,故对单件用法无副作用。
143
+ * 色与原生 `.mh-input::placeholder` 同档(弱文本),选择器值区的 `--placeholder` 也是这一档。 */
144
+ .mh-input__placeholder {
145
+ display: block;
146
+ flex: 1;
147
+ min-width: 0;
148
+ overflow: hidden;
149
+ text-overflow: ellipsis;
150
+ white-space: nowrap;
151
+ color: var(--ui-base-content-subtle);
152
+ }
153
+
130
154
  /* ===== 触发器形态 · 整箱热区(命中测试重定向,纯 CSS) =====
131
155
  *
132
156
  * 目的:让**整件**(值 / 标签 + 右侧图标 + 左右内距)都等同于点那个原生触发按钮。
@@ -66,7 +66,9 @@
66
66
  height: calc(var(--ui-select-chip-height) - 4px);
67
67
  }
68
68
 
69
- /* 无选中值时的提示文字(组件把 placeholder 渲染进 `__value` 并挂这个修饰类) */
69
+ /* 值区:有值时的单选那一行(占满剩余宽、超出省略)。
70
+ ⚠️ **无值态不在这里** —— 占位由输入面渲染(`.mh-input__placeholder`,色与截断同档):
71
+ 2026-09-29 收敛,此前本文件还有一个 `--placeholder` 修饰类专门给占位上灰字。 */
70
72
  .mh-select__value {
71
73
  flex: 1;
72
74
  min-width: 0;
@@ -74,9 +76,6 @@
74
76
  text-overflow: ellipsis;
75
77
  white-space: nowrap;
76
78
  }
77
- .mh-select__value--placeholder {
78
- color: var(--ui-base-content-subtle);
79
- }
80
79
  /* 下拉箭头:渲染在输入面的**右侧图标位**(`.mh-input__suffix`,表壳的子元素)里 ——
81
80
  位置由表壳的 flex 排布给(控件 `flex: 1 1 auto` 自然把附加位挤到右端),故不再需要 `margin-left: auto`。
82
81
  字形由 @manohub/icon 给;展开转向由 popup.css 的 `.mh-popup-caret` 承担(含触发器进表壳后的兄弟档);
@@ -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;
@@ -27,6 +27,16 @@
27
27
  color: var(--mh-text-tone-color, inherit);
28
28
  }
29
29
 
30
+ /* 浏览器给 `h1`-`h6` 的那套字阶(`1.5em` + 粗体)只该是**语义**,视觉由 `size` / `weight` 定:
31
+ 归零搬到了本件(元素级基线不再代管 —— 那条在 Tailwind 的层外,会压住消费方的 `text-*` 工具类)。
32
+ 只点名标题标签:`strong` / `b` 的加粗是它们唯一的表达,不能被归零。
33
+ 档位类在本文件里更靠后、且同特异性,故 `--heading` / `--semibold` 一类照样覆盖它
34
+ (静态页里手写 `<h2 class="mh-text">` 且不给档位时,也仍是「正文字号 + 继承字重」)。 */
35
+ .mh-text:where(h1, h2, h3, h4, h5, h6) {
36
+ font-size: inherit;
37
+ font-weight: inherit;
38
+ }
39
+
30
40
  /* ===== 字号档(每档配一枚行高令牌,两者成对出现才不会出现「字号变了行距没变」) ===== */
31
41
  .mh-text--caption {
32
42
  font-size: var(--ui-font-caption);
@@ -26,7 +26,12 @@
26
26
  display: flex;
27
27
  align-items: center;
28
28
  gap: var(--ui-toast-gap);
29
- width: var(--ui-toast-width);
29
+ /* 宽度:**内容自适应,240 起、最长 560** —— 即规范原文的「宽 240 / 最大 560」。
30
+ 原写法 `width: var(--ui-toast-width)` 是**固定宽**:文本被锁在 240−40=200px 里折行,
31
+ 而 `max-width` 永远轮不到(内容只会折行,撑不宽元素)⇒ 23 字提示被硬折成两行。
32
+ 改 min/max 夹住的 fit-content 后:短文案仍是 240(样张宽不变),长文案按内容展开、到 560 才折行。 */
33
+ width: fit-content;
34
+ min-width: var(--ui-toast-width);
30
35
  max-width: var(--ui-toast-width-max);
31
36
  min-height: var(--ui-toast-min-height);
32
37
  padding: var(--ui-space-3) var(--ui-space-4) var(--ui-space-3) var(--ui-space-6);
@@ -37,9 +42,9 @@
37
42
  }
38
43
 
39
44
  /* loading:**不另设样张高**(高度与其它档一致,见文件头的「有意偏差」)—— 本档只差图标(转圈) */
40
- /* 带动作:更宽的样张宽 */
45
+ /* 带动作:**更宽的样张宽**(360 是下限,长文案仍可展开到 560 上限) */
41
46
  .mh-toast--action {
42
- width: 360px;
47
+ min-width: 360px;
43
48
  }
44
49
 
45
50
  /* ===== 图标:圆形色底 + 白色字形(18×18) ===== */
@@ -91,7 +96,10 @@
91
96
  /* 占满剩余宽度:动作位 / 关闭位都被顶到右端。
92
97
  不写成「每个右侧位各给 margin-left: auto」—— 两个 auto 会平分剩余空间,
93
98
  而本族的常驻提示可能同时带动作与关闭位(那样动作钮会被推到卡片中间)。 */
94
- flex: 1;
99
+ /* ⚠️ 必须是 `flex: 1 1 auto`,不能写成 `flex: 1`(= basis 0%):父级现在是 `width: fit-content`,
100
+ 容器的固有宽按子项的**内容宽**算,basis 为 0 会让文本对容器宽度的贡献退化
101
+ ⇒ 容器停在 min-width(240)、长文案照旧折行(改宽度就白改了)。 */
102
+ flex: 1 1 auto;
95
103
  min-width: 0;
96
104
  margin: 0;
97
105
  font-size: var(--ui-toast-text-size);
@@ -115,19 +123,40 @@
115
123
  outline-offset: 2px;
116
124
  }
117
125
 
118
- /* ===== 堆叠容器(顶部居中、纵向 8) ===== */
126
+ /* ===== 堆叠容器(顶部居中、纵向 8) =====
127
+ ⚠️ `z-index` 只对**普通层**有效:原生 `<dialog>.showModal()` 的模态层与 `::backdrop` 在 top layer,
128
+ 对它 z-index 再大也没用(toast 会被遮罩压暗)。故本容器带 `popover="manual"` 进 top layer
129
+ —— `showPopover()` 由组件层负责(见 `ToastStack` 的说明)。 */
119
130
  .mh-toast-stack {
120
131
  position: fixed;
121
132
  top: var(--ui-space-6);
122
133
  left: 50%;
123
134
  transform: translateX(-50%);
124
- z-index: 1080; /* 高于页面内容、低于模态层(原生 <dialog> 的 top layer 不受影响) */
135
+ z-index: 1080; /* 普通层里仍压过页面内容;popover 不可用的浏览器靠它兜底 */
125
136
  display: flex;
126
137
  flex-direction: column;
127
138
  gap: var(--ui-space-2);
128
139
  align-items: center;
129
140
  pointer-events: none; /* 容器不挡点击;每条自己恢复 */
130
141
  }
142
+
143
+ /* `[popover]` 档:清掉 UA 给 popover 的默认,否则会变成「全屏居中、带边框的方块」
144
+ (UA 给的是 `inset: 0; margin: auto; border: solid; padding: .25em; background: Canvas; overflow: auto`)。
145
+ ⚠️ `inset: auto` 与 `top` / `left` **必须写在同一条规则里**:只写 `inset: auto` 会把上面那条规则里的
146
+ `left: 50%` 一并压成 auto(同层冲突按顺序/特异性决胜,本块更高),元素就跑到视口左边缘去了。 */
147
+ .mh-toast-stack[popover] {
148
+ inset: auto;
149
+ top: var(--ui-space-6);
150
+ left: 50%;
151
+ transform: translateX(-50%);
152
+ margin: 0;
153
+ border: 0;
154
+ padding: 0;
155
+ background: transparent;
156
+ overflow: visible;
157
+ max-width: none;
158
+ max-height: none;
159
+ }
131
160
  .mh-toast-stack > .mh-toast {
132
161
  pointer-events: auto;
133
162
  }
@@ -138,6 +138,7 @@
138
138
  overflow: hidden;
139
139
  }
140
140
  .mh-upload__thumb-img {
141
+ display: block; /* 元素级基线不再把 `img` 转块级(那条会压消费方的 `h-*` / `hidden`),本件自己写 */
141
142
  width: var(--ui-upload-thumb-inner);
142
143
  height: var(--ui-upload-thumb-inner);
143
144
  object-fit: cover;
@@ -43,10 +43,11 @@
43
43
  * (护栏会拦「面里出现不含修饰符类的 `--ui-*` 声明」)。
44
44
  *
45
45
  * 层序:**基线(`./reset.css`,元素级、零特异性)→ 值 → 面**;面内部**操作层在前**(零 JS 原语、规则走 `:where()` 零特异性,
46
- * 永远给皮肤让位),组件面在后。
46
+ * 永远给皮肤让位),组件面在后。基线只有两面(`box-sizing` + 容器基座)—— 其余元素级声明一律归各组件面:
47
+ * 本文件**在 Tailwind 的层外**(未分层 > 分层),多留一条就会压住消费方的工具类(理由见 `reset.css` 文件头)。
47
48
  */
48
49
 
49
- /* ===== 基线:元素级 reset(特异性最低;先于值 → 面,消费方写任何元素选择器都能盖过它) ===== */
50
+ /* ===== 基线:元素级 reset(只有盒模型 + 容器基座;先于值 → 面,消费方写任何元素选择器都能盖过它) ===== */
50
51
  @import './reset.css';
51
52
 
52
53
  /* ===== 值:组件令牌基础值(锚 `[data-manohub-ui]`,与主题令牌同层序) ===== */
@@ -70,7 +71,7 @@
70
71
  @import './components/checkbox.tokens.css';
71
72
  @import './components/radio.tokens.css';
72
73
  @import './components/capsule.tokens.css';
73
- @import './components/filter.tokens.css';
74
+ /* 筛选条没有自己的令牌:字段区几何一律继承 Form 的默认档 */
74
75
 
75
76
  /* 反馈与三态 */
76
77
  @import './components/notice.tokens.css';
@@ -127,7 +128,7 @@
127
128
  @import './components/checkbox.css';
128
129
  @import './components/radio.css'; /* 单选一件两形态(`.Group` / `.Card`)同一个面文件 */
129
130
  @import './components/capsule.css';
130
- @import './components/filter.css'; /* 筛选条:条件字段区 + 查询/重置(控件由消费方自放) */
131
+ @import './components/filter.css'; /* 筛选条:**零规则**(字段排布与几何全部沿用 Form,本文件只留说明;见其文件头) */
131
132
 
132
133
  /* 反馈与三态 */
133
134
  @import './components/notice.css';