dsh-plugin-core 0.35.2 → 0.35.4

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 (88) hide show
  1. package/dist/adv/explorer/FileBrowser.js +4 -4
  2. package/dist/adv/explorer/FileBrowser.js.map +1 -1
  3. package/dist/adv/explorer/crumb-nav.js +1 -1
  4. package/dist/adv/explorer/crumb-nav.js.map +1 -1
  5. package/dist/adv/explorer/explorer-css.generated.d.ts +1 -1
  6. package/dist/adv/explorer/explorer-css.generated.d.ts.map +1 -1
  7. package/dist/adv/explorer/explorer-css.generated.js +1 -1
  8. package/dist/adv/explorer/explorer-css.generated.js.map +1 -1
  9. package/dist/adv/explorer/landing.js +1 -1
  10. package/dist/adv/explorer/landing.js.map +1 -1
  11. package/dist/adv/explorer/preview/TextPreview.js +1 -1
  12. package/dist/adv/explorer/preview/TextPreview.js.map +1 -1
  13. package/dist/design/tokens-css.generated.d.ts +1 -1
  14. package/dist/design/tokens-css.generated.d.ts.map +1 -1
  15. package/dist/design/tokens-css.generated.js +1 -1
  16. package/dist/design/tokens-css.generated.js.map +1 -1
  17. package/dist/ui/actions/Button.d.ts +4 -0
  18. package/dist/ui/actions/Button.d.ts.map +1 -1
  19. package/dist/ui/actions/Button.js +7 -5
  20. package/dist/ui/actions/Button.js.map +1 -1
  21. package/dist/ui/actions/button-css.generated.d.ts +1 -1
  22. package/dist/ui/actions/button-css.generated.d.ts.map +1 -1
  23. package/dist/ui/actions/button-css.generated.js +1 -1
  24. package/dist/ui/actions/button-css.generated.js.map +1 -1
  25. package/dist/ui/display/Card.d.ts +4 -0
  26. package/dist/ui/display/Card.d.ts.map +1 -1
  27. package/dist/ui/display/Card.js +2 -0
  28. package/dist/ui/display/Card.js.map +1 -1
  29. package/dist/ui/display/CodeViewer.js +1 -1
  30. package/dist/ui/display/CodeViewer.js.map +1 -1
  31. package/dist/ui/display/ListRow.d.ts +38 -0
  32. package/dist/ui/display/ListRow.d.ts.map +1 -0
  33. package/dist/ui/display/ListRow.js +69 -0
  34. package/dist/ui/display/ListRow.js.map +1 -0
  35. package/dist/ui/display/card-css.generated.d.ts +1 -1
  36. package/dist/ui/display/card-css.generated.d.ts.map +1 -1
  37. package/dist/ui/display/card-css.generated.js +1 -1
  38. package/dist/ui/display/card-css.generated.js.map +1 -1
  39. package/dist/ui/display/code-viewer-css.generated.d.ts +1 -1
  40. package/dist/ui/display/code-viewer-css.generated.d.ts.map +1 -1
  41. package/dist/ui/display/code-viewer-css.generated.js +1 -1
  42. package/dist/ui/display/code-viewer-css.generated.js.map +1 -1
  43. package/dist/ui/display/list-row-css.d.ts +2 -0
  44. package/dist/ui/display/list-row-css.d.ts.map +1 -0
  45. package/dist/ui/display/list-row-css.generated.d.ts +2 -0
  46. package/dist/ui/display/list-row-css.generated.d.ts.map +1 -0
  47. package/dist/ui/display/list-row-css.generated.js +4 -0
  48. package/dist/ui/display/list-row-css.generated.js.map +1 -0
  49. package/dist/ui/display/list-row-css.js +7 -0
  50. package/dist/ui/display/list-row-css.js.map +1 -0
  51. package/dist/ui/feedback/Modal.js +1 -1
  52. package/dist/ui/feedback/Modal.js.map +1 -1
  53. package/dist/ui/feedback/SideSheet.js +1 -1
  54. package/dist/ui/feedback/SideSheet.js.map +1 -1
  55. package/dist/ui/feedback/Toast.d.ts.map +1 -1
  56. package/dist/ui/feedback/Toast.js +15 -8
  57. package/dist/ui/feedback/Toast.js.map +1 -1
  58. package/dist/ui/feedback/feedback-css.generated.d.ts +1 -1
  59. package/dist/ui/feedback/feedback-css.generated.d.ts.map +1 -1
  60. package/dist/ui/feedback/feedback-css.generated.js +1 -1
  61. package/dist/ui/feedback/feedback-css.generated.js.map +1 -1
  62. package/dist/ui/index.d.ts +1 -0
  63. package/dist/ui/index.d.ts.map +1 -1
  64. package/dist/ui/index.js +2 -0
  65. package/dist/ui/index.js.map +1 -1
  66. package/dist/ui/infra/panel-css.generated.d.ts +1 -1
  67. package/dist/ui/infra/panel-css.generated.d.ts.map +1 -1
  68. package/dist/ui/infra/panel-css.generated.js +1 -1
  69. package/dist/ui/infra/panel-css.generated.js.map +1 -1
  70. package/dist/ui/infra/side-panel-css.generated.d.ts +1 -1
  71. package/dist/ui/infra/side-panel-css.generated.d.ts.map +1 -1
  72. package/dist/ui/infra/side-panel-css.generated.js +1 -1
  73. package/dist/ui/infra/side-panel-css.generated.js.map +1 -1
  74. package/dist/ui/inputs/datetime-css.generated.d.ts +1 -1
  75. package/dist/ui/inputs/datetime-css.generated.d.ts.map +1 -1
  76. package/dist/ui/inputs/datetime-css.generated.js +1 -1
  77. package/dist/ui/inputs/datetime-css.generated.js.map +1 -1
  78. package/dist/ui/inputs/picker-css.generated.d.ts +1 -1
  79. package/dist/ui/inputs/picker-css.generated.d.ts.map +1 -1
  80. package/dist/ui/inputs/picker-css.generated.js +1 -1
  81. package/dist/ui/inputs/picker-css.generated.js.map +1 -1
  82. package/dist/ui/layout/PanelDock.js +2 -2
  83. package/dist/ui/layout/PanelDock.js.map +1 -1
  84. package/docs/design/structure.md +3 -3
  85. package/docs/design/style.md +102 -45
  86. package/docs/help/ui.doc.ts +66 -1
  87. package/docs/help/ui.md +61 -1
  88. package/package.json +1 -1
@@ -156,58 +156,83 @@ EN: Welding controls into one visual unit has exactly one recipe: the shell owns
156
156
  `SplitGroup` overrides inner classes with descendant selectors, so those class names are the coupling point —
157
157
  rename them and you must update `src/ui/layout/split-group.css`, or the weld fails silently.
158
158
 
159
- ## 滚动条让位:一律离边缘 1px
159
+ ## 滚动条让位:只挂 `.dshpc-scroll-thin`
160
160
 
161
- > EN: Scrollbars always sit 1px off the edge
161
+ > EN: Scrollbar inset: just hang `.dshpc-scroll-thin`
162
162
 
163
- 本仓每个滚动容器(`overflow:auto` / `overflow-y:auto` / `overflow-x:auto`)都要让滚动条与容器的**可见边框**
164
- 之间留出 1px 缝,由容器自己的透明边框提供(横向滚用 `border-bottom`,双向两条):
163
+ **唯一真源**:`src/design/tokens.css` 的 tokens 域(`ensureComponentStyles` 每次必注册、且排在
164
+ 所有控件域之前 ⇒ 任何组件挂类即生效,不必额外注入 `panel` 域)。这个类**同时**给两样东西:
165
165
 
166
- ```css
167
- border-right: 1px solid transparent;
168
- ```
166
+ | 它给什么 | 唯一可调值 |
167
+ |---|---|
168
+ | 细滚动条:线宽、thumb 上下端圆角、track 透明 | 字面量三处同值(`width` / `height` / `border-radius`,现行值见 `tokens.css`) |
169
+ | 离边 1px 让位(容器自己的透明边框) | `--dshpc-scroll-inset`(现 1px) |
169
170
 
170
- **不要叠加**:容器本身已有实色边框的就不要再加(否则线变粗)。两个具体落点:
171
+ ⚠️ **线宽不能放进 CSS 变量**:WebKit 的 `::-webkit-scrollbar` 系列伪元素不解析 `var()`,带变量的
172
+ 声明会被静默丢弃 ⇒ 宽度整条作废、退回默认值。2026-11-06 把 `width:2px` 改成
173
+ `var(--dshpc-scroll-w)` 后真机当场打回,用户发现。所以线宽与圆角**必须写字面量**,改宽度只改
174
+ 那一个规则块的三处数字;让位在普通元素上,仍走 `--dshpc-scroll-inset` 一行。
175
+ **不在任何别的 CSS 里抄规则、也不在组件里另写透明边框**。
171
176
 
172
- - 浮层面板里贴满内容区的滚动层(Picker 列表、日期快捷档列):**必须**自己写那条透明边框。
173
- 不写就贴在面板边框内壁上,缝隙是 0,看着就是贴死(2026-11-06 用户实测)。
174
- - 容器本身就是带边框的面板(文件浏览器 ▾ 选层 `dshpc-fb__crumbs-menu`):同一元素不能既保留
175
- 实色边框又让位,只能接受贴边,走 `test/quality.test.mjs` 的 `PANEL_SCROLLERS` 白名单。
176
- - 消费方自己的滚动容器同理,自己加那条边框。
177
+ 原理:原生滚动条画在 padding box 之外、border 之内 —— 只有 border 能把它推离边缘,加 padding 无效。
178
+ 实现只写 `::-webkit-scrollbar`,**不要写 `scrollbar-width`**:Chrome 一见它就切到规范化滚动条、
179
+ 忽略这里设的宽度(2026-11-06 实测宽度回到 ~10px)。2026-11-06 用户口径:1px 太细 ⇒ 线宽起步
180
+ 2px、thumb 上下端圆角;后续宽度调整只改 `tokens.css` 那三处,自检不写死数字。
177
181
 
178
- **为什么没有一条全局规则**:CSS 没有 `:scrollable` 这类选择器,写不出通配所有滚动容器的规则,
179
- 所以只能每个容器自己声明,由 `test/quality.test.mjs` 扫源码兜住漏写。
182
+ ### 方向档(CSS 没有 `:scrollable`,推不出滚动方向,只能显式声明)
180
183
 
181
- 原理:原生滚动条画在 padding box 之外、border 之内 —— 只有 border 能把它推离边缘,加 padding 无效。
182
- **滚动条默认用浏览器原生样式。** 2026-11-02 试过 `::-webkit-scrollbar` 改 thumb(留 3px
183
- 缝 + 圆角)当场被否:要的就是「让出 1 像素」,不是换一套滚动条外观。
184
- 2026-11-06 口径更新:浮层内的滚动区(下拉菜单、日期快捷档列)显式挂 `.dshpc-scroll-thin`
185
- (定义在 panel.css)——**宽度也是 1px 的一条细线**,离边仍是 1px。实现上只写
186
- `::-webkit-scrollbar`,**不要写 `scrollbar-width`**:Chrome 一见它就切到规范化滚动条、忽略
187
- `::-webkit-scrollbar` 的 1px(2026-11-06 实测宽度回到 ~10px)。其余容器仍是原生样式。
188
-
189
- - 已有实色边框的容器(如 Picker 菜单的 `border:1px solid`,或挂 `.dshpc-panel` 后由面板提供的边框)
190
- 滚动条贴的就是那条线,算已让位,**不要叠加**。
191
- - 消费方自己的滚动容器(主区 / 侧栏)不在本仓代码里,配方相同:自己加那条 `border-right`。
192
- 2026-11-02 起 `--dshpc-dock-gap` 已删,dock 不再替主区让 margin,改由主区容器自己让。
193
- - 自检:`test/quality.test.mjs` 扫 `src/**/*.css`,滚动规则缺边框即红。
194
-
195
- EN: every scroll container in this repo keeps a 1px gap between its scrollbar and its visible border, and the
196
- container owns that gap via its own transparent `border-right` (`border-bottom` when scrolling horizontally).
197
- Never stack it on an existing solid border. Scroll layers filling a floating panel's content box (Picker
198
- list, date-preset column) **must** write it themselves — without it the scrollbar sits flush against the
199
- panel border, zero gap (user-verified 2026-11-06). The one exception is a container that is itself the
200
- bordered panel (the file-browser crumb menu), whitelisted as `PANEL_SCROLLERS` in
201
- `test/quality.test.mjs`. Native scrollbars are painted between the padding box and the border, so only a
202
- border moves them — padding does nothing. There is no global rule to inherit: CSS has no `:scrollable`
203
- selector, so every container declares it itself and the test catches omissions. **Keep the native scrollbar by default**: a `::-webkit-scrollbar`
204
- thumb (3px gap + radius) was tried on 2026-11-02 and rejected — the ask is exactly "give up 1 pixel", not a
205
- different scrollbar look. Updated 2026-11-06: floating-layer scrollers (dropdown menus, the date-preset
206
- column) opt into a 1px-wide hairline via `.dshpc-scroll-thin` (defined in panel.css) — width 1px, inset still
207
- 1px. Implement with `::-webkit-scrollbar` only: adding `scrollbar-width` makes Chrome switch to the
208
- standardized scrollbar and ignore the 1px (measured ~10px on 2026-11-06). Everything else stays native.
209
- Containers that already have a solid border count as done. Consumer-owned scroll containers use the same
210
- one-liner; `--dshpc-dock-gap` was removed on 2026-11-02. A test enforces the border.
184
+ | 滚动方向 | 挂什么 | 让位落在 |
185
+ |---|---|---|
186
+ | 竖向(`overflow-y:auto`) | `dshpc-scroll-thin` | 右 1px |
187
+ | 横向(`overflow-x:auto`) | `dshpc-scroll-thin dshpc-scroll-thin--x` | 底 1px、右归零 |
188
+ | 双向(`overflow:auto`)且底部也要让 | `dshpc-scroll-thin dshpc-scroll-thin--both` | 右 + 底各 1px |
189
+
190
+ 方向档**必须与基类同挂**(线宽规则只认基类),`test/quality.test.mjs` 盯着。只要右让位的双向容器
191
+ (贴边面板 body、弹窗 body)挂基类即可,不必加 `--both`。
192
+
193
+ ### 四处例外
194
+
195
+ - **本身已有实色边框**(▾ 选层菜单挂 `.dshpc-panel`):`:not(.dshpc-panel)` 跳过让位,滚动条贴
196
+ 那条实色线即算让位;无条件叠透明边框会把实色右边框覆盖成透明。挂基类仍拿细滚动条。
197
+ - **CodeMirror**:`.cm-scroller` 由官方生成、挂不上类 ⇒ 类挂外层 `.dshpc-cv__editor`,tokens.css
198
+ 用后代选择器一并命中线宽与**右**让位;只让右不让底(给底会改 scroller 高度,CodeMirror 的
199
+ 测量循环会多算一条横滚条)。外层自己 `overflow:hidden` 不滚 ⇒ 在 `code-viewer.css` 把
200
+ `--dshpc-scroll-inset-right` 设 `0px` 关掉自身让位,免得两层各 1px 叠成 2px。
201
+ - **消费方自己的滚动容器**(demo 左导航 / main 主区 / 表格横滚区):配方相同,挂类即可,
202
+ **不再自己写 `border-right: 1px solid transparent`**。2026-11-02 起 `--dshpc-dock-gap` 已删,
203
+ dock 不替主区让 margin。
204
+ - **面板边框本身**:`test/quality.test.mjs` 仍断言 `.dshpc-panel` 带边框(上一条例外的前提)。
205
+
206
+ ### 自检
207
+
208
+ `test/quality.test.mjs` 三条:① 所有滚动容器都挂基类(含 demo 的内联 `overflowY/X` —— 旧自检
209
+ 只扫 CSS 文件,漏过 demo);② 1px 让位边框不许在 tokens.css 之外出现;③ `::-webkit-scrollbar`
210
+ 只许在 tokens.css 出现一份,且线宽 / 圆角必须是同一个字面量(防这次的 var 化回潮)。旧口径
211
+ 「CSS 没有 `:scrollable`,只能每个容器自己声明」已被类取代。
212
+
213
+ EN: `.dshpc-scroll-thin` in `src/design/tokens.css` (tokens domain, registered on every
214
+ `ensureComponentStyles` call and sorted first) is the single source of truth for **both** the hairline
215
+ (a literal, identical value in `width` / `height` / `border-radius`) and the 1px inset
216
+ (`--dshpc-scroll-inset`, 1px). Never put the line width in a CSS variable: WebKit scrollbar
217
+ pseudo-elements do not resolve `var()`, so such declarations are silently dropped and the width falls
218
+ back to the default (hit in a real client on 2026-11-06). Hang the class; never duplicate the rules.
219
+ Native scrollbars sit between the padding box and the border, so only a border can push them off the
220
+ edge — padding does nothing. Use `::-webkit-scrollbar` only: adding `scrollbar-width` makes Chrome
221
+ switch to the standardized scrollbar and ignore the width (measured ~10px on 2026-11-06).
222
+
223
+ Direction variants (no `:scrollable` selector exists, so direction must be stated explicitly):
224
+ `dshpc-scroll-thin` for vertical (right inset), `+ dshpc-scroll-thin--x` for horizontal (bottom inset,
225
+ right zeroed), `+ dshpc-scroll-thin--both` for two-axis (right + bottom). Variants must be hung together
226
+ with the base class.
227
+
228
+ Exceptions: containers already carrying a solid border (`.dshpc-panel`) are skipped via
229
+ `:not(.dshpc-panel)` — their border is the inset. CodeMirror generates `.cm-scroller` and cannot take a
230
+ class, so the class goes on `.dshpc-cv__editor` and a descendant selector in tokens.css covers line width
231
+ and the **right** inset only (a bottom border changes scroller height and makes CodeMirror's measurement
232
+ loop add a phantom horizontal scrollbar); the non-scrolling outer layer sets
233
+ `--dshpc-scroll-inset-right:0px` in `code-viewer.css` to avoid stacking two 1px gaps. Consumer-owned
234
+ scroll containers (demo sidebar / main / table wrappers) just hang the class. `test/quality.test.mjs`
235
+ checks all three, including inline `overflowY/X` in the demo.
211
236
 
212
237
  ## 浮层三档:模态 / 锚点面板 / 菜单
213
238
 
@@ -249,3 +274,35 @@ one-liner; `--dshpc-dock-gap` was removed on 2026-11-02. A test enforces the bor
249
274
 
250
275
  EN: No second `document.createElement('style')` — always go through the kit. No second style tag id — one kit
251
276
  per plugin. Rules inside a domain consume `var(--dshpc-*)` only; never hard-code colors, sizes or fonts.
277
+
278
+ ## 控件默认字号
279
+
280
+ > EN: Default type scale for controls
281
+
282
+ `Surface` 是纯容器,只给外框,`font:inherit` 刻意不接管内容排版;**但承载文字的控件必须在自己的域内给默认字号**
283
+ (`font-size:var(--dshpc-font-md)` + `line-height:var(--dshpc-line-md)`,与 `Table` / `Input md` 同档)。
284
+ 不许把这件事推给每个消费者重复补一遍 —— 2026-11-07 用户拍板:`ListRow` 曾漏设,会话列表标题继承到宿主默认
285
+ 16px,消费者被迫自己写 `fontSize` 压回去。调用方仍可用内联 style 覆盖这一档。
286
+
287
+ EN: `Surface` is a pure frame and deliberately inherits typography, but any control that renders text must set its
288
+ own default `font-size` / `line-height` from tokens (same tier as `Table` / `Input md`). Do not make every consumer
289
+ repeat it; callers can still override that tier inline.
290
+
291
+ ## 卡片整卡 hover 与底色轴
292
+
293
+ > EN: Whole-card hover and fill switch
294
+
295
+ `Card` / `StripCard` 的整卡悬停统一吃 `--dshpc-card-hover`(亮 `rgba(37,99,235,.07)` / 暗
296
+ `rgba(96,165,250,.13)`),由 `hoverable` 开启、**默认关**;底色由 `filled` 控制、**默认有底**。
297
+ 两者都只改 `--surface-bg` 一个变量,不许写 `background` / `background-image` 属性去覆盖底色 ——
298
+ `StripCard` 的浅底也必须走这个变量,否则覆盖顺序会打架(兄弟项目曾用渐变叠加绕过,2026-11-07 收编)。
299
+
300
+ EN: Whole-card hover on `Card` / `StripCard` uses `--dshpc-card-hover` only; it is opt-in via `hoverable`
301
+ (default off). The fill is controlled by `filled` (default on). Both only rewrite `--surface-bg`; never
302
+ override the fill with `background` / `background-image` properties.
303
+
304
+ 开启 `hoverable` 时**同时**给 `cursor:pointer` —— 既然声明整卡可交互,手势不能只换背景不变;
305
+ 没开这根轴的卡片保持默认光标(2026-11-07 用户反馈)。
306
+
307
+ EN: Enabling `hoverable` also sets `cursor:pointer`: a card that declares itself interactive must change
308
+ the pointer too, not just its background. Cards without the axis keep the default cursor.
@@ -28,6 +28,7 @@ const cardExample = `import { Card, Chip, StripCard } from 'dsh-plugin-core/ui'
28
28
  h(Card, {
29
29
  accent: 'warning',
30
30
  bar: { side: 'left', inset: 12, gap: 12 }, // 色条可放 left / right / top / bottom
31
+ hoverable: true, // 整卡 hover 变色(默认关)
31
32
  action: h(Button, { size: 'sm', variant: 'ghost' }, '跳过'),
32
33
  },
33
34
  h('div', null, '标题'),
@@ -36,6 +37,7 @@ h(Card, {
36
37
 
37
38
  h(StripCard, {
38
39
  accent: 'success',
40
+ hoverable: true, filled: false, // 底色关掉 = 透明(默认有底)
39
41
  expanded: open, onToggle: () => setOpen(!open),
40
42
  panel: h('div', null, '展开详情'),
41
43
  }, h('div', null, '标题 + 元信息'))`
@@ -51,6 +53,25 @@ h(Surface, { border: 'faint', padding: 'lg' }, h(Card, null, '框里再放卡'))
51
53
  // 虚线占位框也走它,不要自己写 1px dashed:
52
54
  h(Surface, { tone: 'sunken', border: 'dashed', padding: 'lg' }, '还没做的东西')`
53
55
 
56
+ const listRowExample = `import { ListRow, Button, Chip } from 'dsh-plugin-core/ui'
57
+
58
+ // 一行一条:常态带底,点整行打开 / 选中,右侧小操作不连带触发整行
59
+ h(ListRow, {
60
+ onClick: () => openFile(entry),
61
+ selected: entry.path === currentPath,
62
+ actions: h(Button, {
63
+ size: 'sm', variant: 'ghost', onClick: () => removeEntry(entry),
64
+ }, '移除'),
65
+ },
66
+ h('div', { style: { display: 'flex', alignItems: 'center', gap: 8 } },
67
+ h(Chip, null, 'TS'),
68
+ h('span', null, entry.name),
69
+ ),
70
+ )
71
+
72
+ // 带边 / 关掉 hover / 纯展示(不可点)
73
+ h(ListRow, { bordered: true, hoverable: false }, '只读的一行')`
74
+
54
75
  const panelDockExample = `const dock = createPanelDock({ maxPanels: 3 }) // 一次,挂在插件级
55
76
 
56
77
  // 页面里:结构就在这儿写,不塞进任何调用参数
@@ -284,7 +305,7 @@ const doc: DocTopic = {
284
305
  exports: [
285
306
  'Button', 'IconButton', 'Segmented', 'SplitGroup', 'Input', 'PrefixedInput', 'NumberInput', 'Textarea',
286
307
  'Checkbox', 'fieldMetricsOf', 'Picker', 'WorkspacePicker', 'ModelPicker', 'DateTimePicker', 'Surface', 'Card', 'StripCard',
287
- 'Chip', 'Table', 'Pagination', 'Modal', 'SideSheet', 'FieldHint', 'setTooltipRenderer', 'showToast', 'notify', 'showHostToast',
308
+ 'Chip', 'Table', 'ListRow', 'Pagination', 'Modal', 'SideSheet', 'FieldHint', 'setTooltipRenderer', 'showToast', 'notify', 'showHostToast',
288
309
  'Loader', 'LoaderBar', 'Loading', 'RunningBlocks', 'SectionHead', 'MarqueeText', 'FlipText', 'LiveText',
289
310
  'QuickToolbar', 'startResizeLayoutWidth', 'PanelDock', 'createPanelDock', 'DockPanel',
290
311
  'ensureControls', 'setStyleKit',
@@ -321,6 +342,10 @@ const doc: DocTopic = {
321
342
  zh: 'IconButton:variant: plain|outline|danger(默认 plain,**无底色**)、size(默认 lg)、icon(必填)、label(必填,无障碍名兼 title)、color(图标色)、background(底色,不传即无底色)。',
322
343
  en: 'IconButton: variant plain|outline|danger (default plain, **no background**), size (default lg), icon (required), label (required, doubles as a11y name and title), color (icon colour), background (fill; omitted = no fill).',
323
344
  },
345
+ {
346
+ zh: '`dense` 是两者共用的紧凑图标档:`size` / 高度一律不变,图标放大一档、内距收紧(Button 的 `--btn-pad` 从 10/12/14 收到 6,图标容器从 0.875em 到 1.25em;IconButton 把图标 `<svg>` 的 `width/height` 从 1em 提到 1.25em,正方形宽高不变、四周留白变小)。两者 dense 下图标都是 sm 15px / md·lg 16.25px。IconButton 的按钮显式 `padding:0`、图标 `flex:none`:svg 是 flex item,不禁止收缩会被内距压成窄条,此时改 `width` 或加 `!important` 都无效。给描边细、缩到 sm 看不清的图标用(如文件类型 / 语言图标);默认 false,原有按钮外观不变。',
347
+ en: '`dense` is a shared compact-icon tier for both: `size` and height stay put while the icon grows one step and padding tightens (Button: `--btn-pad` 10/12/14 → 6, icon container 0.875em → 1.25em; IconButton: the icon `<svg>` goes from `width/height:1em` to `1.25em`, same square box, less breathing room). Both give sm 15px / md·lg 16.25px icons. An IconButton sets `padding:0` on itself and `flex:none` on the icon: the svg is a flex item, and without disabling shrinking the padding squeezes it into a narrow strip — changing `width` or adding `!important` cannot fix that. Use it for thin-stroke icons that vanish at `sm` (file-type / language glyphs). Defaults to false, so existing buttons are unchanged.',
348
+ },
324
349
  {
325
350
  zh: '两者的 loading 语义一致:`onClick` 返回 Promise 就转圈(纯图标钮把图标位换成小圈,`aria-label` 不变、补 `aria-busy`),`onBusyChange` 把「在跑」报出去;受控 `loading` 与它取或 —— 别处刷新也能点亮同一颗按钮。转圈期间按钮自己的图标位(`icon` / `iconRight`)全部让位,只留转圈 + 文字。',
326
351
  en: 'Both share one loading semantics: a Promise-returning `onClick` spins (an icon button swaps its icon for the spinner, keeps `aria-label` and adds `aria-busy`), `onBusyChange` reports "running", and the controlled `loading` prop is OR-ed in — an external refresh lights the same button too. While spinning, the button’s own icon slots (`icon` / `iconRight`) all step aside: spinner plus label only.',
@@ -340,6 +365,7 @@ const doc: DocTopic = {
340
365
  rows: [
341
366
  { name: 'variant', type: "上表 18 档(primary/secondary/danger/success/warning/info/outline/danger-outline/ghost/link + 扩展 purple/pink/cyan/teal/orange/indigo/rose/slate)", def: "'outline'", desc: { zh: '外观档', en: 'Appearance tier' } },
342
367
  { name: 'size', type: "'sm' | 'md' | 'lg'", def: "'lg'", desc: { zh: '高度档(24 / 28 / 32,有边无边同高)', en: 'Height tier (24 / 28 / 32, same height with or without border)' } },
368
+ { name: 'dense', type: 'boolean', def: 'false', desc: { zh: '紧凑图标档:高度不变,图标放大、内距收紧(Button / IconButton 同名同义)', en: 'Compact-icon tier: same height, larger icon and tighter padding (same prop on Button and IconButton)' } },
343
369
  { name: 'icon', type: 'ReactNode', def: '—', desc: { zh: '前置图标(官方图标组件 / 字符串均可)', en: 'Leading icon (official icon component or string)' } },
344
370
  { name: 'iconRight', type: 'ReactNode', def: '—', desc: { zh: '后置图标(展开箭头、外链)', en: 'Trailing icon (expand arrow, external link)' } },
345
371
  { name: 'color', type: 'string', def: '—', desc: { zh: '自定义主色:实心档作底色,线框 / 无底档作主色', en: 'Custom main color: background for solid tiers, main color for outline/bare' } },
@@ -606,6 +632,10 @@ const doc: DocTopic = {
606
632
  zh: '头部整体垂直居中,`action` 里裸放一个元素即居中。颜色全走 token,明暗跟随宿主。',
607
633
  en: 'The header centers vertically as a whole; drop a bare element into `action` and it centers. Colors come from tokens, so light/dark follows the host.',
608
634
  },
635
+ {
636
+ zh: '`hoverable` 开启后**整卡**悬停变色、同时把光标改成 `pointer`,统一吃 `--dshpc-card-hover`(亮 7% 蓝 / 暗 13% 蓝),默认关;`filled` 切底色,默认有底(Card = surface-1,StripCard = accent 浅底),`false` 时底透明但边框仍在。两根轴都只改 `--surface-bg`,插件不用再自己写 `:hover` 规则;透明底 + hover 也照常变色。',
637
+ en: '`hoverable` (default off) changes the **whole card** on hover and switches the cursor to `pointer`, using `--dshpc-card-hover` (7% blue light / 13% blue dark). `filled` (default on) controls the background — Card uses surface-1, StripCard the accent tint; `false` makes it transparent but keeps the border. Both axes only rewrite `--surface-bg`, so plugins no longer need their own `:hover` rules; a transparent card still changes color on hover.',
638
+ },
609
639
  ],
610
640
  api: [
611
641
  {
@@ -618,12 +648,47 @@ const doc: DocTopic = {
618
648
  { name: 'action', type: 'ReactNode', def: '—', desc: { zh: '右上角操作区', en: 'Top-right action area' } },
619
649
  { name: 'children', type: 'ReactNode', def: '—', desc: { zh: '主内容,排版随你', en: 'Main content, layout is yours' } },
620
650
  { name: 'panel', type: 'ReactNode', def: '—', desc: { zh: '展开后才显示的额外内容', en: 'Extra content shown only when expanded' } },
651
+ { name: 'hoverable', type: 'boolean', def: "'false'", desc: { zh: '整卡 hover 变色 + 光标变 pointer,统一 --dshpc-card-hover;默认关', en: 'Whole-card hover plus a pointer cursor, using --dshpc-card-hover; default off' } },
652
+ { name: 'filled', type: 'boolean', def: "'true'", desc: { zh: '是否带底色;false 时底透明、边框仍在。默认有底(Card = surface-1 / StripCard = accent 浅底)', en: 'Background fill; false makes it transparent while keeping the border. Default on (Card = surface-1 / StripCard = accent tint)' } },
621
653
  { name: 'className / style', type: '—', def: '—', desc: { zh: '追加类 / 样式', en: 'Append class / style' } },
622
654
  ],
623
655
  },
624
656
  ],
625
657
  code: [{ id: 'ui-card-example', lang: 'tsx', src: cardExample }],
626
658
  },
659
+ {
660
+ id: 'list-row',
661
+ heading: { zh: 'ListRow(一行一条)', en: 'ListRow (one item per line)' },
662
+ prose: [
663
+ {
664
+ zh: '一行代表一个可选 / 可点的对象(文件、任务、候选项):常态就带底色,鼠标移上去按 Button 灰色档加深一档;选中换成 slate 的极浅蓝灰单色背景,不描边、不画竖条。左主内容、右侧一组小操作,点右侧不连带触发整行。底 / 边 / 圆角复用 Surface,颜色全走 token,插件不写第二套皮肤。',
665
+ en: 'One line per selectable / clickable object (file, task, candidate): a visible background at rest that darkens one step on hover using the same grey tokens as Button; selected rows swap to a single pale slate-blue-grey background — no border, no rail. Main content on the left, a group of small actions on the right that never trigger the row. Background / border / radius come from Surface and colors from tokens, so plugins write no second skin.',
666
+ },
667
+ {
668
+ zh: '默认中号(min-height 28,与 Button md 同档),不设 size 轴;四边内距 8px 等距;默认字号 13px / 行高 20px 由核心给(`--dshpc-font-md` / `--dshpc-line-md`,与 Table 同档),调用方不必自己设、可用内联 style 覆盖。`bordered` 切带边 / 不带边,无边档仍是 1px 透明占位,两档同高。`selected` 只改外观,状态由调用方管。',
669
+ en: 'One medium tier by default (min-height 28, same as Button md); no size axis; equal 8px padding on all four sides. The core sets a default 13px / 20px type scale (`--dshpc-font-md` / `--dshpc-line-md`, same as Table) — callers need not set it and can override it inline. `bordered` switches the edge on/off — the borderless tier keeps a transparent 1px placeholder so heights stay equal. `selected` only changes appearance; the caller owns the state.',
670
+ },
671
+ {
672
+ zh: '`hoverable` 默认跟随「整行可点」(可点行开、纯展示行关,与 Table 的 hover 口径一致),可显式开关;hover / 选中色取 `--dshpc-active` / `--dshpc-listrow-selected-bg`(由 `--dshpc-slate` 派生,亮色 14% / 暗色 26% 两档,暗色不发黑)。整行不是 `<button>`:调用方要往里放 Button / IconButton,嵌 button 是非法 HTML;键盘走 Enter / Space,右侧操作槽挡住冒泡。',
673
+ en: '`hoverable` follows row clickability by default (on for clickable rows, off for static ones, matching Table), and can be forced on/off; hover / selected use `--dshpc-active` / `--dshpc-listrow-selected-bg` (derived from `--dshpc-slate`, 14% light / 26% dark, so the dark theme never goes black). The row is not a `<button>`: callers put Buttons inside it and nested buttons are invalid HTML. Keyboard uses Enter / Space; the actions slot stops propagation.',
674
+ },
675
+ ],
676
+ api: [
677
+ {
678
+ id: 'ui-list-row',
679
+ rows: [
680
+ { name: 'children', type: 'ReactNode', def: '—', desc: { zh: '主内容:图标 / 名字 / 标签 / 左右分区自己排', en: 'Main content: icons, labels, tags, left/right layout are yours' } },
681
+ { name: 'actions', type: 'ReactNode', def: '—', desc: { zh: '右侧操作槽;核心挡住事件冒泡,不连带整行', en: 'Right-side actions; the core stops propagation so the row is not triggered' } },
682
+ { name: 'onClick', type: '() => void', def: '—', desc: { zh: '给了才让整行可点(role=button / tabIndex / Enter·Space)', en: 'Only makes the row clickable when provided (role=button / tabIndex / Enter·Space)' } },
683
+ { name: 'selected', type: 'boolean', def: "'false'", desc: { zh: '选中态:slate 极浅蓝灰单色背景(明暗各一档),不描边 / 不画竖条,压在 hover 之上', en: 'Selected: a single pale slate-blue-grey background (separate light / dark tiers), no border or rail, above hover' } },
684
+ { name: 'bordered', type: 'boolean', def: "'false'", desc: { zh: '带边 / 不带边,两档同高', en: 'Bordered / borderless; both tiers share the same height' } },
685
+ { name: 'hoverable', type: 'boolean', def: '自动', desc: { zh: 'hover 灰态加深开关;默认跟随整行可点', en: 'Grey-state hover switch; defaults to row clickability' } },
686
+ { name: 'className / style', type: '—', def: '—', desc: { zh: '追加类 / 样式,只允许补布局', en: 'Append class / style, layout patches only' } },
687
+ ],
688
+ },
689
+ ],
690
+ code: [{ id: 'ui-listrow-example', lang: 'tsx', src: listRowExample }],
691
+ },
627
692
  {
628
693
  id: 'chip',
629
694
  heading: { zh: 'Chip', en: 'Chip' },
package/docs/help/ui.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  > EN: ui — base controls
6
6
 
7
- 导出:Button、IconButton、Segmented、SplitGroup、Input、PrefixedInput、NumberInput、Textarea、Checkbox、fieldMetricsOf、Picker、WorkspacePicker、ModelPicker、DateTimePicker、Surface、Card、StripCard、Chip、Table、Pagination、Modal、SideSheet、FieldHint、setTooltipRenderer、showToast、notify、showHostToast、Loader、LoaderBar、Loading、RunningBlocks、SectionHead、MarqueeText、FlipText、LiveText、QuickToolbar、startResizeLayoutWidth、PanelDock、createPanelDock、DockPanel、ensureControls、setStyleKit
7
+ 导出:Button、IconButton、Segmented、SplitGroup、Input、PrefixedInput、NumberInput、Textarea、Checkbox、fieldMetricsOf、Picker、WorkspacePicker、ModelPicker、DateTimePicker、Surface、Card、StripCard、Chip、Table、ListRow、Pagination、Modal、SideSheet、FieldHint、setTooltipRenderer、showToast、notify、showHostToast、Loader、LoaderBar、Loading、RunningBlocks、SectionHead、MarqueeText、FlipText、LiveText、QuickToolbar、startResizeLayoutWidth、PanelDock、createPanelDock、DockPanel、ensureControls、setStyleKit
8
8
 
9
9
  ## 概览
10
10
 
@@ -43,6 +43,10 @@ IconButton:variant: plain|outline|danger(默认 plain,**无底色**)、s
43
43
 
44
44
  > EN: IconButton: variant plain|outline|danger (default plain, **no background**), size (default lg), icon (required), label (required, doubles as a11y name and title), color (icon colour), background (fill; omitted = no fill).
45
45
 
46
+ `dense` 是两者共用的紧凑图标档:`size` / 高度一律不变,图标放大一档、内距收紧(Button 的 `--btn-pad` 从 10/12/14 收到 6,图标容器从 0.875em 到 1.25em;IconButton 把图标 `<svg>` 的 `width/height` 从 1em 提到 1.25em,正方形宽高不变、四周留白变小)。两者 dense 下图标都是 sm 15px / md·lg 16.25px。IconButton 的按钮显式 `padding:0`、图标 `flex:none`:svg 是 flex item,不禁止收缩会被内距压成窄条,此时改 `width` 或加 `!important` 都无效。给描边细、缩到 sm 看不清的图标用(如文件类型 / 语言图标);默认 false,原有按钮外观不变。
47
+
48
+ > EN: `dense` is a shared compact-icon tier for both: `size` and height stay put while the icon grows one step and padding tightens (Button: `--btn-pad` 10/12/14 → 6, icon container 0.875em → 1.25em; IconButton: the icon `<svg>` goes from `width/height:1em` to `1.25em`, same square box, less breathing room). Both give sm 15px / md·lg 16.25px icons. An IconButton sets `padding:0` on itself and `flex:none` on the icon: the svg is a flex item, and without disabling shrinking the padding squeezes it into a narrow strip — changing `width` or adding `!important` cannot fix that. Use it for thin-stroke icons that vanish at `sm` (file-type / language glyphs). Defaults to false, so existing buttons are unchanged.
49
+
46
50
  两者的 loading 语义一致:`onClick` 返回 Promise 就转圈(纯图标钮把图标位换成小圈,`aria-label` 不变、补 `aria-busy`),`onBusyChange` 把「在跑」报出去;受控 `loading` 与它取或 —— 别处刷新也能点亮同一颗按钮。转圈期间按钮自己的图标位(`icon` / `iconRight`)全部让位,只留转圈 + 文字。
47
51
 
48
52
  > EN: Both share one loading semantics: a Promise-returning `onClick` spins (an icon button swaps its icon for the spinner, keeps `aria-label` and adds `aria-busy`), `onBusyChange` reports "running", and the controlled `loading` prop is OR-ed in — an external refresh lights the same button too. While spinning, the button’s own icon slots (`icon` / `iconRight`) all step aside: spinner plus label only.
@@ -59,6 +63,7 @@ Segmented:`items`(必填)支持单选 / 多选,`size`(默认 lg;三
59
63
  |---|---|---|---|---|
60
64
  | `variant` | `上表 18 档(primary/secondary/danger/success/warning/info/outline/danger-outline/ghost/link + 扩展 purple/pink/cyan/teal/orange/indigo/rose/slate)` | 'outline' | 外观档 | Appearance tier |
61
65
  | `size` | `'sm' \| 'md' \| 'lg'` | 'lg' | 高度档(24 / 28 / 32,有边无边同高) | Height tier (24 / 28 / 32, same height with or without border) |
66
+ | `dense` | `boolean` | false | 紧凑图标档:高度不变,图标放大、内距收紧(Button / IconButton 同名同义) | Compact-icon tier: same height, larger icon and tighter padding (same prop on Button and IconButton) |
62
67
  | `icon` | `ReactNode` | — | 前置图标(官方图标组件 / 字符串均可) | Leading icon (official icon component or string) |
63
68
  | `iconRight` | `ReactNode` | — | 后置图标(展开箭头、外链) | Trailing icon (expand arrow, external link) |
64
69
  | `color` | `string` | — | 自定义主色:实心档作底色,线框 / 无底档作主色 | Custom main color: background for solid tiers, main color for outline/bare |
@@ -360,6 +365,10 @@ h(Surface, { tone: 'sunken', border: 'dashed', padding: 'lg' }, '还没做的东
360
365
 
361
366
  > EN: The header centers vertically as a whole; drop a bare element into `action` and it centers. Colors come from tokens, so light/dark follows the host.
362
367
 
368
+ `hoverable` 开启后**整卡**悬停变色、同时把光标改成 `pointer`,统一吃 `--dshpc-card-hover`(亮 7% 蓝 / 暗 13% 蓝),默认关;`filled` 切底色,默认有底(Card = surface-1,StripCard = accent 浅底),`false` 时底透明但边框仍在。两根轴都只改 `--surface-bg`,插件不用再自己写 `:hover` 规则;透明底 + hover 也照常变色。
369
+
370
+ > EN: `hoverable` (default off) changes the **whole card** on hover and switches the cursor to `pointer`, using `--dshpc-card-hover` (7% blue light / 13% blue dark). `filled` (default on) controls the background — Card uses surface-1, StripCard the accent tint; `false` makes it transparent but keeps the border. Both axes only rewrite `--surface-bg`, so plugins no longer need their own `:hover` rules; a transparent card still changes color on hover.
371
+
363
372
  | 参数 | 类型 | 默认 | 说明(zh) | 说明(en) |
364
373
  |---|---|---|---|---|
365
374
  | `accent` | `13 档 or 任意 CSS 色值(business/success/warning/danger/neutral + 扩展 purple/pink/cyan/teal/orange/indigo/rose/slate)` | 'business' | 档位映射到 design 的 token;也可传 '#f59e0b' 这类自定义值 | Tiers map to design tokens; also accepts custom values like '#f59e0b' |
@@ -369,6 +378,8 @@ h(Surface, { tone: 'sunken', border: 'dashed', padding: 'lg' }, '还没做的东
369
378
  | `action` | `ReactNode` | — | 右上角操作区 | Top-right action area |
370
379
  | `children` | `ReactNode` | — | 主内容,排版随你 | Main content, layout is yours |
371
380
  | `panel` | `ReactNode` | — | 展开后才显示的额外内容 | Extra content shown only when expanded |
381
+ | `hoverable` | `boolean` | 'false' | 整卡 hover 变色 + 光标变 pointer,统一 --dshpc-card-hover;默认关 | Whole-card hover plus a pointer cursor, using --dshpc-card-hover; default off |
382
+ | `filled` | `boolean` | 'true' | 是否带底色;false 时底透明、边框仍在。默认有底(Card = surface-1 / StripCard = accent 浅底) | Background fill; false makes it transparent while keeping the border. Default on (Card = surface-1 / StripCard = accent tint) |
372
383
  | `className / style` | `—` | — | 追加类 / 样式 | Append class / style |
373
384
 
374
385
  ```tsx id="ui-card-example"
@@ -377,6 +388,7 @@ import { Card, Chip, StripCard } from 'dsh-plugin-core/ui'
377
388
  h(Card, {
378
389
  accent: 'warning',
379
390
  bar: { side: 'left', inset: 12, gap: 12 }, // 色条可放 left / right / top / bottom
391
+ hoverable: true, // 整卡 hover 变色(默认关)
380
392
  action: h(Button, { size: 'sm', variant: 'ghost' }, '跳过'),
381
393
  },
382
394
  h('div', null, '标题'),
@@ -385,11 +397,59 @@ h(Card, {
385
397
 
386
398
  h(StripCard, {
387
399
  accent: 'success',
400
+ hoverable: true, filled: false, // 底色关掉 = 透明(默认有底)
388
401
  expanded: open, onToggle: () => setOpen(!open),
389
402
  panel: h('div', null, '展开详情'),
390
403
  }, h('div', null, '标题 + 元信息'))
391
404
  ```
392
405
 
406
+ ## ListRow(一行一条)
407
+
408
+ > EN: ListRow (one item per line)
409
+
410
+ 一行代表一个可选 / 可点的对象(文件、任务、候选项):常态就带底色,鼠标移上去按 Button 灰色档加深一档;选中换成 slate 的极浅蓝灰单色背景,不描边、不画竖条。左主内容、右侧一组小操作,点右侧不连带触发整行。底 / 边 / 圆角复用 Surface,颜色全走 token,插件不写第二套皮肤。
411
+
412
+ > EN: One line per selectable / clickable object (file, task, candidate): a visible background at rest that darkens one step on hover using the same grey tokens as Button; selected rows swap to a single pale slate-blue-grey background — no border, no rail. Main content on the left, a group of small actions on the right that never trigger the row. Background / border / radius come from Surface and colors from tokens, so plugins write no second skin.
413
+
414
+ 默认中号(min-height 28,与 Button md 同档),不设 size 轴;四边内距 8px 等距;默认字号 13px / 行高 20px 由核心给(`--dshpc-font-md` / `--dshpc-line-md`,与 Table 同档),调用方不必自己设、可用内联 style 覆盖。`bordered` 切带边 / 不带边,无边档仍是 1px 透明占位,两档同高。`selected` 只改外观,状态由调用方管。
415
+
416
+ > EN: One medium tier by default (min-height 28, same as Button md); no size axis; equal 8px padding on all four sides. The core sets a default 13px / 20px type scale (`--dshpc-font-md` / `--dshpc-line-md`, same as Table) — callers need not set it and can override it inline. `bordered` switches the edge on/off — the borderless tier keeps a transparent 1px placeholder so heights stay equal. `selected` only changes appearance; the caller owns the state.
417
+
418
+ `hoverable` 默认跟随「整行可点」(可点行开、纯展示行关,与 Table 的 hover 口径一致),可显式开关;hover / 选中色取 `--dshpc-active` / `--dshpc-listrow-selected-bg`(由 `--dshpc-slate` 派生,亮色 14% / 暗色 26% 两档,暗色不发黑)。整行不是 `<button>`:调用方要往里放 Button / IconButton,嵌 button 是非法 HTML;键盘走 Enter / Space,右侧操作槽挡住冒泡。
419
+
420
+ > EN: `hoverable` follows row clickability by default (on for clickable rows, off for static ones, matching Table), and can be forced on/off; hover / selected use `--dshpc-active` / `--dshpc-listrow-selected-bg` (derived from `--dshpc-slate`, 14% light / 26% dark, so the dark theme never goes black). The row is not a `<button>`: callers put Buttons inside it and nested buttons are invalid HTML. Keyboard uses Enter / Space; the actions slot stops propagation.
421
+
422
+ | 参数 | 类型 | 默认 | 说明(zh) | 说明(en) |
423
+ |---|---|---|---|---|
424
+ | `children` | `ReactNode` | — | 主内容:图标 / 名字 / 标签 / 左右分区自己排 | Main content: icons, labels, tags, left/right layout are yours |
425
+ | `actions` | `ReactNode` | — | 右侧操作槽;核心挡住事件冒泡,不连带整行 | Right-side actions; the core stops propagation so the row is not triggered |
426
+ | `onClick` | `() => void` | — | 给了才让整行可点(role=button / tabIndex / Enter·Space) | Only makes the row clickable when provided (role=button / tabIndex / Enter·Space) |
427
+ | `selected` | `boolean` | 'false' | 选中态:slate 极浅蓝灰单色背景(明暗各一档),不描边 / 不画竖条,压在 hover 之上 | Selected: a single pale slate-blue-grey background (separate light / dark tiers), no border or rail, above hover |
428
+ | `bordered` | `boolean` | 'false' | 带边 / 不带边,两档同高 | Bordered / borderless; both tiers share the same height |
429
+ | `hoverable` | `boolean` | 自动 | hover 灰态加深开关;默认跟随整行可点 | Grey-state hover switch; defaults to row clickability |
430
+ | `className / style` | `—` | — | 追加类 / 样式,只允许补布局 | Append class / style, layout patches only |
431
+
432
+ ```tsx id="ui-listrow-example"
433
+ import { ListRow, Button, Chip } from 'dsh-plugin-core/ui'
434
+
435
+ // 一行一条:常态带底,点整行打开 / 选中,右侧小操作不连带触发整行
436
+ h(ListRow, {
437
+ onClick: () => openFile(entry),
438
+ selected: entry.path === currentPath,
439
+ actions: h(Button, {
440
+ size: 'sm', variant: 'ghost', onClick: () => removeEntry(entry),
441
+ }, '移除'),
442
+ },
443
+ h('div', { style: { display: 'flex', alignItems: 'center', gap: 8 } },
444
+ h(Chip, null, 'TS'),
445
+ h('span', null, entry.name),
446
+ ),
447
+ )
448
+
449
+ // 带边 / 关掉 hover / 纯展示(不可点)
450
+ h(ListRow, { bordered: true, hoverable: false }, '只读的一行')
451
+ ```
452
+
393
453
  ## Chip
394
454
 
395
455
  > EN: Chip
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-core",
3
- "version": "0.35.2",
3
+ "version": "0.35.4",
4
4
  "packageManager": "pnpm@12.3.4",
5
5
  "description": "Shared base for DSH plugins: design tokens, UI controls, utils, workspace explorer, session preview",
6
6
  "keywords": [