@manohub/kit 1.0.0 → 1.0.2

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 (2) hide show
  1. package/CONTRACT.md +99 -9
  2. package/package.json +1 -1
package/CONTRACT.md CHANGED
@@ -50,8 +50,8 @@
50
50
  | prop 的**存在性与取值** | 该件 `dist/components/<件>/index.d.ts` | 传 prop 前打开确认 |
51
51
  | **语义词表**(改外观的唯一正规通道) | 同上,`.d.ts` 里的联合类型 | 26 个维度,`tone` / `variant` / `shape` / `size` / `status` / `gap` / `align` / `justify` / `padding` … |
52
52
  | **骨架怎么用**(归位 / 滚动 / 页头 / 操作位) | `@manohub/ui/dist/components/page/index.d.ts`、`dist/components/panel/index.d.ts` 的头注释 | §5 已把判据写全;这两份是更细的展开 |
53
- | 图标**名清单** | `@manohub/icon` 的 `dist/glyphs.d.ts`(`IconNameList`) | `name` 必须在清单里搜得到 |
54
- | 图标**字形与出处** | `node_modules/@manohub/icon/dist/glyphs.js`(源码形态:`src/glyphs.ts`) | 缺图标改这里 + 重 build |
53
+ | 图标**名清单** | `@manohub/icon` 的 `dist/glyphs.d.ts`(`IconNameList`) | 传 `name` 时必须在清单里搜得到(L1.5-6) |
54
+ | 图标**字形与出处** | `node_modules/@manohub/icon/dist/glyphs.js` / `business-glyphs.js`(源码在包的 `src/glyphs.ts` / `business-glyphs.ts`);本项目自带字形见本仓 `icons.ts`(可用 `icon-from-svg` 从 SVG 生成) | 通用字形缺了改中央包 + 重 build;业务字形缺了写进 `<Icon>` 插槽并集中一处(L1.5-6 第 ③ 类) |
55
55
  | **类名与档位类** | `@manohub/ui/dist/styles/index.css` 及其 `components/` | 应用侧**不写** `.mh-*` |
56
56
  | 本仓**类名命名空间** | 本应用自己的 `docs/kit-namespaces.md`(若还没有,建一个) | 类名形状必须匹配表内某一格 |
57
57
 
@@ -182,7 +182,7 @@ configureHost(hostEl)
182
182
  | --- | --- | --- |
183
183
  | **L1-1 应用侧不持有值** | 应用侧的 `--*` 自定义属性**一处都不该有**。只有两种合法写法:① **重设已存在的令牌**(局部换肤)② 不写 | 值的唯一来源是 theme。局部换肤是合法且推荐的(见下) |
184
184
  | **L1-2 引用必须存在** | 每个 `var(--xxx)` 的 `--xxx` 必须能在 theme 六片或 `<件>.tokens.css` 里**搜到**。**新增令牌名一律违规** | 写错名字不会报错,只会静默回退成默认值 |
185
- | **L1-3 尺寸取令牌** | 尺寸取 `--ui-space-*` / `--ui-radius-*` / `--ui-control-height-*` / `--ui-row-height` / `--ui-grid-column-min`;**几何是设计基准、不跟根字号**,故禁 `rem`;内距给 **px 数字**(与 `Padding` 同口径) | 设计基准不随宿主根字号分叉 |
185
+ | **L1-3 尺寸取令牌** | 尺寸取 `--ui-space-*` / `--ui-radius-*` / `--ui-control-height-*` / `--ui-control-width` / `--ui-control-min-width` / `--ui-row-height` / `--ui-grid-column-min`;**几何是设计基准、不跟根字号**,故禁 `rem`;内距给 **px 数字**(与 `Padding` 同口径) | 设计基准不随宿主根字号分叉 |
186
186
  | **L1-4 只写布局属性** | `<style>` 与 `style={{ }}` 里的属性名必须在**下面的白名单**内 | theme 是视觉的唯一来源 |
187
187
  | **L1-5 类名形状** | 类名一律 `<本仓命名空间>-<kebab-case>`。落点 A:CSS 选择器**首段**;落点 B:`class=` 的**每个 token** | 两个应用装进同一页面时不能撞名 |
188
188
  | **L1-6 不碰库的类** | 选择器里**不得出现 `.mh-`**;不得用 `!important`;不得用裸元素选择器与 `*`。**唯一例外:入口基线**(`html` / `body` / `#app` 三个选择器及其高度链,见 §1.2)| `.mh-*` 与档位类归 ui;覆写升版即静默失效 |
@@ -228,6 +228,27 @@ configureHost(hostEl)
228
228
  区别在**「新名字」还是「已有名字」**:`--ui-primary` 在 theme 里有定义 → 合法(重设):
229
229
  `--ui-my-brand` 在 theme 里搜不到 → 违规(发明)。想加新名字去 `@manohub/theme` 提。
230
230
 
231
+ ### 控件定宽:重设 `--ui-control-width`(同一通道的用法)
232
+
233
+ 可输入控件(`Input` / `Textarea` / `NumberInput` / `Select` / `SelectTree` / `Search`)的宽度由**两枚主题令牌**驱动,
234
+ 缺省是「撑满所在列 / 容器」+ 120 的收缩下限:
235
+
236
+ ```css
237
+ /* 一行筛选位:整片控件一句话定宽(不必逐个挂类) */
238
+ .vm-filter-bar { --ui-control-width: 240px; }
239
+ /* 密集工具条里确实要更窄:局部放行下限 */
240
+ .vm-dense-bar { --ui-control-min-width: 0; }
241
+ ```
242
+
243
+ - **什么时候要用它**:控件落在**宽度由内容决定**的容器里时(典型是页头右位那类 flex 的 shrink-to-fit 槽),
244
+ 百分比宽度解析不出确定值,会回退成**内容宽** —— 症状是下拉只剩占位文字那一小截、同排两件还不等宽;
245
+ 行被 flex 挤压时同理(下限就是防「无限缩」)。
246
+ - **不要用 `width` 覆写**:那会绕过口径,值只落在你写的那**一个件**上(同排的第二件还得再写一次),
247
+ 下一版若调整了控件宽度的来源又得再改一遍。
248
+ - **没有自己的容器时**(如页头右位),把类挂在**控件自身**上写这两枚令牌 —— 各件都把消费方的 `class`
249
+ 落在自己的**可见箱**上(`Input` 是表壳 `.mh-input__wrap`、`Select` / `SelectTree` 是触发器表壳、
250
+ `Search` 是整行那个根)。两枚令牌都**向下继承**,故挂在容器的任意祖先上同样成立。
251
+
231
252
  ### 正误对照
232
253
 
233
254
  ```css
@@ -253,13 +274,13 @@ configureHost(hostEl)
253
274
 
254
275
  | 条款 | 要什么(闭集) | 为什么 |
255
276
  | --- | --- | --- |
256
- | **L1.5-1 唯一来源** | 需要图形处一律 `<Icon name="…" />`(非 Vue 场景用 `renderIconSvg()`)。**不得**自绘(`<svg` / `<path` / `<circle`);**不得**用文字符号(`✓` `⌄` `×` `▶` `←`)冒充 | 一套线重、一个视觉族;文字符号在不同字体下高矮不一 |
277
+ | **L1.5-1 唯一来源** | 需要图形处一律 `<Icon name="…" />`(非 Vue 场景用 `renderIconSvg()`)。**不得**自绘(`<svg` / `<path` / `<circle`);**不得**用文字符号(`✓` `⌄` `×` `▶` `←`)冒充。**唯一例外**:本项目自带的业务字形 —— 写进 `<Icon>` 的**默认插槽**,且图形集中在一处定义(L1.5-6 第 ③ 类) | 一套线重、一个视觉族;文字符号在不同字体下高矮不一 |
257
278
  | **L1.5-2 尺寸在面层给** | 图标槽**只写 `width` / `height`** 两个属性,值取 `var(--ui-font-icon)`(要档距用 `calc()`);件内令牌写成它的**别名**。**不传 `size`** | `size` 默认 16,不写就静默落到 16;写死了也不跟主题缩放。**消费侧没有例外** |
258
279
  | **L1.5-3 只继承色、不承载色** | 不给图标写任何颜色(`color` / `stroke` / `fill` / `stroke-width`) | 图标随文案色;theme 里**有意没有 icons 片**,不要想着去补一片 |
259
280
  | **L1.5-4 方位在字形里** | 方位一律 `chevron-*`(四向齐备);**不得**为「翻向」写 `transform: rotate()`。面层出现 `rotate(` 的唯一合法情形是 `loading` 的转圈动效;`back` 只作「返回」语义 | 旋转过的字形在视觉上不是同一个族 |
260
281
  | **L1.5-5 语义图标按容器挑族** | 图标位**有**圆形色底 → 取裸字形(`info` / `check` / `alert` / `close`),色底那一圈由**组件**给(字形再自带一圈会叠成双圈);图标位**没有**色底 → 取带圈字形(`info-circle` / `check-circle` / `alert-circle` / `close-circle`),否则裸符号孤零零撑不住状态的分量 | 两族各自成立,混用必错一边 |
261
- | **L1.5-6 名字取自清单,缺图标补包** | `name` 必须能在 `IconNameList` 里搜到;缺图标 → 在 `@manohub/icon` 加一条并重 build,**不在应用仓画路,也不在 ui 画路** | 就地画一个 = 第二次分叉 |
262
- | **L1.5-7 入口分栈** | Vue 应用只认主入口 `@manohub/icon`;`@manohub/icon/glyphs` **只在没有 Vue 组件可用处**(静态页 / 模板串 / 非 Vue 栈) | 两个入口的产物类型不同 |
282
+ | **L1.5-6 缺图形时按三类走** | **① 通用 UI 字形**(`chevron-*` / `edit` / `search` / `info` …)→ `name` 必须能在 `IconNameList` 里搜到;缺了就补 `@manohub/icon` 并重 build,**不得**在应用仓画。**② 产品专属图形**(多色 / 渐变 / 品牌 logo / 插画)→ **不进字形表**,留应用侧 assets。**③ 本项目业务字形**(本菜单 / 页头专属的**单色**字形)→ 走 `<Icon>` 的**默认插槽**:`<Icon><path …/></Icon>`(整段 SVG 用 `icon-from-svg` 归一成片段)。硬要求:图形**集中在项目自己的一个文件里**,不得在页面里就地画裸 `<svg>` | 判据:**散装内联 `<svg>` = 第二次分叉;`<Icon>` 插槽 + 集中定义 ≠ 分叉**。防的是「同一图形两处各画一遍、改一处漏一处」,不是「字形必须出自中央包」—— 中央包发版要走完整个流程,业务字形不该排那个队 |
283
+ | **L1.5-7 入口分栈** | Vue 应用只认主入口 `@manohub/icon`;`@manohub/icon/glyphs` **只在没有 Vue 组件可用处**(静态页 / 模板串 / 非 Vue 栈);`@manohub/icon/svg` 是**归一工具**(构建期 `icon-from-svg` 用,也可在运行期应急归一),不做渲染 | 三个入口的产物职责不同 |
263
284
 
264
285
  ### 正误对照
265
286
 
@@ -274,6 +295,24 @@ configureHost(hostEl)
274
295
  .vm-select-caret { width: var(--ui-font-icon); height: var(--ui-font-icon); }
275
296
  ```
276
297
 
298
+ 业务字形(L1.5-6 第 ③ 类)的合法姿势 —— 图形集中在一处、经 `<Icon>` 的插槽渲染:
299
+
300
+ ```tsx
301
+ // icons.ts —— 项目唯一一处字形定义;整段 SVG 可先用 icon-from-svg 归一成片段(产物进 git 可 review)
302
+ export const MY_GLYPHS = { 'aip-foo': '<g transform="scale(1.5)" fill="currentColor" stroke="none">…</g>' }
303
+
304
+ // 调用点
305
+ import { Icon } from '@manohub/icon'
306
+ import { MY_GLYPHS } from './icons'
307
+ <Icon>{MY_GLYPHS['aip-foo']}</Icon>
308
+ <Icon><path d="M2 2h6v6H2z" /></Icon> {/* 现写现用也行,前提是这一处就是唯一出处 */}
309
+ ```
310
+
311
+ ```tsx
312
+ // ✗ 散装内联:这一处画一个 <svg>,另一处又画一个 —— 同一图形两处各一份,改一处必漏一处
313
+ <svg viewBox="0 0 24 24"><path d="…" /></svg>
314
+ ```
315
+
277
316
  自检见 §7 第 9–12 问。
278
317
 
279
318
  ---
@@ -460,10 +499,10 @@ import Sortable from 'sortablejs' // 行为库:不产
460
499
 
461
500
  **L1.5**
462
501
 
463
- 9. 我这次用到的每个图形都是 `<Icon name="…">` 吗?有没有手绘 `<svg` / 用 `✓ ⌄ ×` 一类文字符号冒充?
502
+ 9. 我这次用到的每个图形都是 `<Icon>` 吗(内置走 `name`、业务字形走默认插槽)?有没有手绘裸 `<svg` / 用 `✓ ⌄ ×` 一类文字符号冒充?**业务字形若是我自己加的**:是否集中在唯一的 `icons.ts` 里、别处只是引用?
464
503
  10. 我有没有给 `<Icon>` 传 `size`?尺寸是在 CSS 里写 `var(--ui-font-icon)` 吗?
465
504
  11. 我有没有给图标写颜色(`color` / `stroke` / `fill` / `stroke-width`)?面层出现 `rotate(` 了吗(只允许 `loading` 的转圈)?
466
- 12. 我用的 `name` 在 `IconNameList` 里搜得到吗?方位是 `chevron-*` 吗?有没有把 `back` 当方位?
505
+ 12. 我用的 `name` 在 `IconNameList` 里搜得到吗(业务字形走插槽,本就不该出现在 `name` 里)?方位是 `chevron-*` 吗?有没有把 `back` 当方位?
467
506
 
468
507
  **L2**
469
508
 
@@ -497,7 +536,10 @@ import Sortable from 'sortablejs' // 行为库:不产
497
536
  4. **提给 `@manohub/ui` 建件**:新件按「组件 + 面 CSS + 登记 + 导出 + 文档页」**五处齐备**,
498
537
  少一处会静默失效。建成后把 `docs/kit-gaps.md` 里那条划掉。
499
538
 
500
- 已知缺件(截至 0.6.0):`DatePicker`(日期选择)、`Number`(数字输入)。
539
+ 已知缺件(截至 0.6.0):`DatePicker`(日期选择)。
540
+ (`Number`(数字输入)已于 **1.0.1** 由新件 **`NumberInput`** 关闭:与 `Input` 同族同面、类名同根
541
+ `mh-input--number`,`modelValue` 是 `number | null`(空值为 `null` 不是 `0`),含 `min` / `max` /
542
+ `step` / `precision` 与右端步进位;**输入期不钳制、归一时刻(失焦 / 回车 / 步进)才钳制与舍入**。)
501
543
  **不要**因此回去直连底层组件库(§6 L3-3 会拦)。
502
544
 
503
545
  ---
@@ -638,6 +680,23 @@ import Sortable from 'sortablejs' // 行为库:不产
638
680
  **消费 theme 一直有、却没有件消费的那套 `--ui-font-*` 档位令牌**。此前「容器里的文字层级」
639
681
  (卡片描述、只读值、次级信息)在应用侧无合规落点,每个应用都要为它留一条定版例外登记 ——
640
682
  这条路径至此补上。
683
+ - **`NumberInput`**(`@manohub/ui`,1.0.1):数字输入件,关闭 §8 登记已久的缺件 `Number`。
684
+ 与 `Input` **同族同面**(共用 `components/input/` 目录与 `styles/components/input.css`,
685
+ 类名同根 `mh-input--number` —— 与 `Textarea` 的 `mh-input--textarea` 同款),
686
+ `modelValue: number | null` + `onChange`;`min` / `max` / `step` / `precision` / `controls` /
687
+ `clampOnBlur`;**输入期不钳制**(`-` / `1.` 这类中间态留在框里),钳制与舍入发生在归一时刻
688
+ (失焦 / 回车 / 步进按钮 / 边界变化),非法输入回滚为上一个合法值。
689
+ 出处是 aip-web 的「连接器配置内化」:34 个连接器插件里的 `FNumberSpinner` 迁到骨架层时无对应件
690
+ (方案见 `docs/plans/2026-10-08-connector-config-parts.md`)。
691
+ - **`Table` 的单元格编辑**(`@manohub/ui`,1.0.1):走 **`#cell` 插槽 + `setValue`** ——
692
+ 作用域 `{ row, column, value, index, setValue }`;`setValue(next)` 只发一次
693
+ `onCellChange(row, column.key, next, index)`,**本件从不改 `data`**(与它一贯的受控口径一致)。
694
+ **表格不渲染编辑控件**:格子里摆什么(`Input` / `Select` / `NumberInput`、要不要套 `Form.Item`
695
+ 拿 label 与错误位、只读态摆文本还是禁用控件、要不要按行判)全归调用方 ——
696
+ 口径与 `Form.Item` 的默认插槽同款(「你自己放控件」),表格面里因此**没有**编辑控件的几何规则。
697
+ ⚠️ 与 farris `FDataGrid` 的差异:**不做「点击进入编辑」**(格子里摆的就是常驻控件);
698
+ **序号列仍自加一列**(§10 的取舍不变);**不做列级格式化**(`value` 是原值,折文案是调用方的事)。
699
+ 出处同 `NumberInput`(连接器配置内化)。
641
700
  - **`Dialog.tone` 与服务层 `tone`**:`alert()` / `confirm()` 现在能表达 `info` / `success` / `warning` / `error`
642
701
  四档(标题左侧一个裸符号 + 配色),`confirm({ tone: 'error' })` 还会把确定按钮自动染成危险色 ——
643
702
  「删除确认」与「保存成功」不再长得一样。
@@ -673,6 +732,37 @@ import Sortable from 'sortablejs' // 行为库:不产
673
732
  否则回落「按顺序配对」旧写法,两条路不叠加;`selector` 插槽拿到的 `options` 就是提取出来的项。
674
733
  **本仓内唯一消费点**是 `apps/mcp` 的配置抽屉(`mcp-config-drawer.tsx`),已随迁移同步。
675
734
 
735
+ #### 五、2026-10 的破坏性变更:`Table` 改名 + 输入族 `bordered` 拆档(**版本口径破例:落 1.0.1**)
736
+
737
+ ⚠️ **破例声明**:改名与**值集 / 语义**变更都属破坏性变更(`AGENTS.md` 硬约束 6),按规矩该升**次版本**
738
+ (`1.0.0` → `2.0.0`)。本项目裁定:本次随 **1.0.1** 一并发出 —— 依据是**消费方为零**(全仓核对,见下),
739
+ 且同批还有两条加性变更(`NumberInput`、`Table` 单元格编辑 `#cell` + `setValue`)要与它一起发。
740
+ `borderless` **不保留别名**,旧写法在 1.0.1 里直接失效。
741
+ **下次同类改动请先核消费方:已有消费方就按硬约束升次版本,不要再破例。**
742
+
743
+ | 变更 | 1.0.1 之前 | 1.0.1 | 为什么要改 / 怎么迁 |
744
+ | --- | --- | --- | --- |
745
+ | `Table.borderless` 更名 **`Table.bordered`** | `borderless`(默认 `false`,含义是「有框」) | **`bordered`**(默认 `true`,含义是「有框」) | 「有无外框」这一维度在库内有**两种极性**:`Input` / `Textarea` / `Nav` 用肯定式 `bordered`,只有 `Table` 用否定式。统一到肯定式后,调用方不必再记「哪件用否定式」。**默认行为不变**(缺省都有框),变的只是去框的写法 |
746
+ | **输入族与选择器族的 `bordered` 值集扩为 `boolean \| 'focus'`**(`Input.bordered` / `Textarea.bordered` / `NumberInput.bordered` / **新增** `Select.bordered` / `SelectTree.bordered`),且 **`false` 的语义收紧** | `bordered={false}` = 无框档,但**悬停亮浅底、聚焦显出框与主色环**(一档两效果);`Select` 一族**没有**这个 prop(只能恒有框) | `false` = **无框**(常态 / 悬停 / 聚焦都不显,效果只有一种);`'focus'` = **聚焦(与悬停)才显框**(承接旧的 `false` 视觉);`Select` / `SelectTree` 多了 `bordered`,透传给共用的触发器 | 「无框」不该在聚焦时变成「有框」—— 那是另一档。拆开后:`false` 用于「要可编辑但一个框都不冒」,`'focus'` 用于行内编辑(常态像值、点进去才亮框)。`'focus'` 档**不另写状态描写**,悬停 / 聚焦复用有框档那两条。表格里 Input / Select / NumberInput 要有同一档表现,就必须让选择器也认这个 prop |
747
+
748
+ **怎么迁**:
749
+
750
+ 1. `<Table borderless />` → `<Table :bordered="false" />`。⚠️ `borderless` 不再被识别,留在模板里会
751
+ **静默失效**(表格照旧有外框,不报错)。
752
+ 2. `<Input bordered={false} />`(以及 `Textarea` / `NumberInput` 的同名写法)**语义收紧了**:
753
+ 旧视觉是「悬停亮浅底 + 聚焦显框 + 主色环」,现在那是 `bordered="focus"`。
754
+ 要旧视觉就把 `false` 改成 `'focus'`;**要继续「一个框都不冒」就保持 `false` 不动**(新的正确用法)。
755
+ ⚠️ `bordered` 现在接字符串,`bordered="false"`(**带引号**)在 Vue 里是**字符串** `'false'`,
756
+ 与 `false` / `'focus'` 都不同 —— 本包把它当**未命中**处理(走缺省 `true`,仍有框),不吃静默双解。
757
+
758
+ **类名**:`.mh-table--borderless`(`Table` 去框)、`.mh-input--borderless`(输入族无框)不变 ——
759
+ 面层词根统一用**否定式**,故「prop 肯定式 / 类名否定式」是**预期**,不是笔误。新增的 `'focus'` 档用
760
+ `.mh-input--border-on-focus`(**肯定式**:它说的不是「关框」,而是「聚焦时给框」),
761
+ 口径见 `packages/ui/docs/guide/naming-and-defaults.md` §2.1。
762
+
763
+ **消费点核对**(全仓,非估计):`frontend-infra` 四包 + `aip-web` 全部应用里 **零消费方** ——
764
+ 只有 `packages/ui` 自己的文档页与单测用到,已随本次一并改(`__tests__/table.spec.ts` 的开关组用例)。
765
+
676
766
  ### 12.3 存量应用怎么迁
677
767
 
678
768
  按**层**推进,每步单独可跑通(这也是最优顺序):
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@manohub/kit",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "子应用入口编排层:createSubApp(作用域容器与宿主锚点、pinia/路由/vue-query 装配、宿主挂载协议)、i18n 单实例与语言探测,外加随包分发的接入契约(CONTRACT.md,五层闭集条款 + 自检清单)与三个 AI 技能包。本包**零样式产物**:设计令牌(值)在 @manohub/theme,组件(类与行为)在 @manohub/ui,两者由消费方直接引入。",