dsh-plugin-core 0.17.0 → 0.19.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 (68) hide show
  1. package/dist/explorer/open.js +1 -1
  2. package/dist/explorer/open.js.map +1 -1
  3. package/dist/ui/InputGroup.d.ts +84 -0
  4. package/dist/ui/InputGroup.d.ts.map +1 -0
  5. package/dist/ui/InputGroup.js +121 -0
  6. package/dist/ui/InputGroup.js.map +1 -0
  7. package/dist/ui/Modal.d.ts.map +1 -1
  8. package/dist/ui/Modal.js +16 -37
  9. package/dist/ui/Modal.js.map +1 -1
  10. package/dist/ui/PanelDock.d.ts.map +1 -1
  11. package/dist/ui/PanelDock.js +5 -2
  12. package/dist/ui/PanelDock.js.map +1 -1
  13. package/dist/ui/SideSheet.d.ts +56 -0
  14. package/dist/ui/SideSheet.d.ts.map +1 -0
  15. package/dist/ui/SideSheet.js +97 -0
  16. package/dist/ui/SideSheet.js.map +1 -0
  17. package/dist/ui/controls/input-group-css.generated.d.ts +2 -0
  18. package/dist/ui/controls/input-group-css.generated.d.ts.map +1 -0
  19. package/dist/ui/controls/input-group-css.generated.js +4 -0
  20. package/dist/ui/controls/input-group-css.generated.js.map +1 -0
  21. package/dist/ui/controls-css.d.ts +1 -0
  22. package/dist/ui/controls-css.d.ts.map +1 -1
  23. package/dist/ui/controls-css.js +1 -0
  24. package/dist/ui/controls-css.js.map +1 -1
  25. package/dist/ui/feedback-css.generated.d.ts +1 -1
  26. package/dist/ui/feedback-css.generated.d.ts.map +1 -1
  27. package/dist/ui/feedback-css.generated.js +1 -1
  28. package/dist/ui/feedback-css.generated.js.map +1 -1
  29. package/dist/ui/index.d.ts +2 -0
  30. package/dist/ui/index.d.ts.map +1 -1
  31. package/dist/ui/index.js +2 -0
  32. package/dist/ui/index.js.map +1 -1
  33. package/dist/ui/overlay-css.d.ts +2 -0
  34. package/dist/ui/overlay-css.d.ts.map +1 -0
  35. package/dist/ui/overlay-css.generated.d.ts +2 -0
  36. package/dist/ui/overlay-css.generated.d.ts.map +1 -0
  37. package/dist/ui/overlay-css.generated.js +4 -0
  38. package/dist/ui/overlay-css.generated.js.map +1 -0
  39. package/dist/ui/overlay-css.js +2 -0
  40. package/dist/ui/overlay-css.js.map +1 -0
  41. package/dist/ui/overlay.d.ts +40 -0
  42. package/dist/ui/overlay.d.ts.map +1 -0
  43. package/dist/ui/overlay.js +49 -0
  44. package/dist/ui/overlay.js.map +1 -0
  45. package/dist/ui/panel-dock-css.generated.d.ts +1 -1
  46. package/dist/ui/panel-dock-css.generated.d.ts.map +1 -1
  47. package/dist/ui/panel-dock-css.generated.js +1 -1
  48. package/dist/ui/panel-dock-css.generated.js.map +1 -1
  49. package/dist/ui/side-panel-css.d.ts +2 -0
  50. package/dist/ui/side-panel-css.d.ts.map +1 -0
  51. package/dist/ui/side-panel-css.generated.d.ts +2 -0
  52. package/dist/ui/side-panel-css.generated.d.ts.map +1 -0
  53. package/dist/ui/side-panel-css.generated.js +4 -0
  54. package/dist/ui/side-panel-css.generated.js.map +1 -0
  55. package/dist/ui/side-panel-css.js +2 -0
  56. package/dist/ui/side-panel-css.js.map +1 -0
  57. package/dist/ui/side-sheet-css.d.ts +2 -0
  58. package/dist/ui/side-sheet-css.d.ts.map +1 -0
  59. package/dist/ui/side-sheet-css.generated.d.ts +2 -0
  60. package/dist/ui/side-sheet-css.generated.d.ts.map +1 -0
  61. package/dist/ui/side-sheet-css.generated.js +4 -0
  62. package/dist/ui/side-sheet-css.generated.js.map +1 -0
  63. package/dist/ui/side-sheet-css.js +2 -0
  64. package/dist/ui/side-sheet-css.js.map +1 -0
  65. package/docs/design/structure.md +2 -1
  66. package/docs/help/ui.doc.ts +115 -1
  67. package/docs/help/ui.md +106 -1
  68. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"side-panel-css.d.ts","sourceRoot":"","sources":["../../src/ui/side-panel-css.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAA"}
@@ -0,0 +1,2 @@
1
+ export declare const SIDE_PANEL_CSS = "/* \u4FA7\u8FB9\u9762\u677F\u7684\u516C\u5171\u76AE\u80A4 \u2014\u2014 PanelDock\uFF08\u5360\u5BBD\u5206\u680F\uFF09\u4E0E SideSheet\uFF08\u8986\u76D6\u62BD\u5C49\uFF09\u5171\u7528\u8FD9\u4E00\u4EFD\u3002\n *\n * \u5206\u754C\uFF1A**\u76AE\u80A4\u5728\u8FD9\u91CC\uFF0C\u5B9A\u4F4D\u5404\u81EA\u5199**\u3002\u4E24\u8005\u552F\u4E00\u7684\u5F62\u6001\u5DEE\u5F02\u5C31\u662F\u300C\u600E\u4E48\u5360\u4F4D\u300D\u2014\u2014\n * PanelDock \u7528 sticky \u5360\u5BBD\u6324\u7A84\u4E3B\u533A\uFF08panel-dock.css\uFF09\uFF0CSideSheet \u7528 absolute \u76D6\u5728\u9875\u9762\u4E0A\uFF08side-sheet.css\uFF09\u3002\n *\n * \u7C7B\u540D\u6CBF\u7528 dshpc-dock__* \u524D\u7F00\uFF1A\u90A3\u662F\u65E2\u6709\u516C\u5F00\u9762\uFF08\u6587\u6863\u4E0E\u6D88\u8D39\u65B9\u90FD\u5728\u7528\uFF09\uFF0C\n * \u8FD9\u91CC\u53EA\u642C\u6587\u4EF6\u4E0D\u6539\u540D\uFF1B\u65B0\u589E\u7684\u4E2D\u6027\u7C7B\u662F .dshpc-side-surface\uFF08\u5916\u89C2\u57FA\u7C7B\uFF09\u3002 */\n\n/* \u5165\u573A\uFF1A\u5BBD\u5EA6\u4ECE 0 \u957F\u51FA\u6765\uFF08keyframes \u53EA\u5199 from\uFF0C\u7EC8\u70B9\u53D6\u5185\u8054 / \u72B6\u6001\u5BBD\u5EA6\uFF09\u3002\u4E24\u8FB9\u5171\u7528\u3002 */\n@keyframes dshpc-side-in{from{width:0;opacity:0;overflow:hidden}}\n\n/* \u5916\u89C2\u57FA\u7C7B\uFF1A\u80CC\u666F / \u6587\u5B57\u8272 / \u9634\u5F71 / \u7EB5\u5411 flex\u3002**\u4E0D\u542B** position\u3001\u9AD8\u5EA6\u3001\u8FB9\u6846\u65B9\u5411\u3001\u52A8\u753B\u89E6\u53D1\u3002 */\n.dshpc-side-surface{\n box-sizing:border-box;\n display:flex;flex-direction:column;flex:0 0 auto;min-width:0;\n background:var(--dshpc-surface-base);color:var(--dshpc-fg);\n box-shadow:var(--dshpc-shadow-2);\n transition:border-color var(--dshpc-dur-fast) var(--dshpc-ease);\n animation:dshpc-side-in .22s var(--dshpc-ease);\n}\n\n/* \u5934\u90E8\uFF1A\u53F3\u4FA7 34px = 8(\u8FB9\u8DDD) + 20(\u5173\u94AE) + 6(\u547C\u5438)\uFF1A\u7ED9\u7EDD\u5BF9\u5B9A\u4F4D\u7684\u5173\u94AE\u7559\u4F4D\uFF0C\u6807\u9898/extra \u4E0D\u4ECE\u5B83\u5E95\u4E0B\u94BB\u3002 */\n.dshpc-dock__head{\n flex:none;display:flex;align-items:center;gap:8px;\n padding:10px 34px 10px 14px;\n border-bottom:1px solid var(--dshpc-border-faint);\n}\n/* \u56FE\u6807\u4F4D / \u6807\u9898\u53F3\u4FA7\u7684\u5185\u5BB9\u4F4D\uFF1A\u6D88\u8D39\u65B9\u4F20 ReactNode\uFF0C\u53EF\u585E\u591A\u4E2A\uFF1B\u5E93\u53EA\u8D1F\u8D23\u6392\u5E03\uFF0C\u4E0D\u62A2\u5BBD\u5EA6\u3002 */\n.dshpc-dock__lead{display:inline-flex;align-items:center;gap:6px;flex:none;color:var(--dshpc-fg-2)}\n.dshpc-dock__extra{display:inline-flex;align-items:center;gap:6px;flex:none;margin-left:auto}\n/* \u81EA\u5B9A\u4E49\u5934\u90E8\uFF1A\u6574\u884C\u5F52\u8C03\u7528\u65B9 \u2014\u2014 \u4E0D\u52A0\u5185\u8FB9\u8DDD/\u8FB9\u6846/\u80CC\u666F\uFF0C\u4E5F\u4E0D\u7ED9\u5173\u94AE\u7559\u5185\u8054\u7A7A\u95F4\uFF08\u5173\u94AE\u662F\u7EDD\u5BF9\u5B9A\u4F4D\u7684\uFF09\u3002 */\n.dshpc-dock__head--custom{display:block;padding:0;border-bottom:none}\n.dshpc-dock__title{\n flex:1 1 auto;min-width:0;\n overflow:hidden;text-overflow:ellipsis;white-space:nowrap;\n font-size:var(--dshpc-font-md);font-weight:600;\n}\n/* \u5173\u95ED\u94AE\uFF1A20\u00D720 \u65B9\u89D2\u56FE\u6807\u4F4D\uFF08\u4E0E Modal \u7684\u5173\u95ED\u94AE\u540C\u89C4\u683C\uFF0C\u4E0D\u501F Picker \u89E6\u53D1\u5668\u76AE\u80A4\uFF09\u3002 */\n.dshpc-dock__close{\n display:inline-flex;align-items:center;justify-content:center;flex:none;\n width:20px;height:20px;padding:0;border:none;border-radius:var(--dshpc-radius-sm);\n background:transparent;color:var(--dshpc-fg-3);\n font-family:inherit;font-size:13px;line-height:1;cursor:pointer;\n transition:background-color var(--dshpc-dur-fast) var(--dshpc-ease),color var(--dshpc-dur-fast) var(--dshpc-ease);\n}\n.dshpc-dock__close:hover{background:var(--dshpc-hover);color:var(--dshpc-fg)}\n.dshpc-dock__close:focus-visible{outline:2px solid var(--dshpc-focus);outline-offset:1px}\n/* \u5934\u90E8\u5173\u94AE\uFF1A\u7EDD\u5BF9\u5B9A\u4F4D\uFF08top 10 / right 8\uFF09\uFF0C\u951A\u70B9\u662F\u9762\u677F\u672C\u8EAB \u2014\u2014 \u6B63\u6587\u6EDA\u52A8\u5B83\u4E0D\u8DDF\u6EDA\u3002 */\n.dshpc-dock__close--abs{position:absolute;top:10px;right:8px;z-index:8}\n/* \u6D6E\u52A8\u5173\u94AE\uFF1A\u65E0\u5934\u90E8\u9762\u677F\u7528\uFF0C\u7565\u6536\u4E00\u70B9\u8FB9\u8DDD\u3002 */\n.dshpc-dock__close--floating{\n position:absolute;top:6px;right:6px;z-index:8;\n background:var(--dshpc-surface-menu);border:1px solid var(--dshpc-border-faint);\n}\n\n/* \u5185\u5BB9\u533A\u81EA\u5DF1\u6EDA\uFF08\u9762\u677F\u9AD8\u5EA6\u9501\u6B7B\uFF0C\u5185\u5BB9\u4E0D\u8BBE\u5185\u90E8\u6EDA\u52A8\u5C31\u4F1A\u628A\u9875\u9762\u6491\u9AD8\uFF09\u3002 */\n.dshpc-dock__body{flex:1 1 auto;min-height:0;overflow:auto}\n\n/* \u62D6\u62FD\u6761\uFF1A6px \u547D\u4E2D\u533A\uFF0C**\u81EA\u5DF1\u4E0D\u753B\u4EFB\u4F55\u7EBF**\uFF08\u8D34\u5728\u671D\u5185\u90A3\u4E00\u4FA7\uFF0C\u955C\u50CF\u7531\u5404\u81EA\u7684\u5B9A\u4F4D\u6587\u4EF6\u505A\uFF09\u3002\n hover / \u62D6\u52A8\u65F6\u628A\u9762\u677F\u81EA\u5DF1\u7684\u8FB9\u6846\u67D3\u6210\u84DD \u2014\u2014 \u84DD\u7EBF\u53EA\u51C6\u6539\u539F\u7EBF\uFF0C\u4E0D\u51C6\u5728\u65C1\u8FB9\u518D\u52A0\u4E00\u6761\u3002\n z-index 7 \u5FC5\u987B\u9AD8\u4E8E\u9762\u677F\u5185\u5BB9\u91CC\u7684\u6D6E\u5C42\uFF08\u590D\u5236\u94AE\u4E00\u7C7B\uFF09\uFF0C\u5426\u5219\u547D\u4E2D\u533A\u4F1A\u88AB\u5185\u5BB9\u76D6\u4F4F\u3002 */\n.dshpc-dock__resizer{\n position:absolute;left:0;top:0;bottom:0;width:6px;\n z-index:7;cursor:col-resize;background:transparent;\n}\n.dshpc-side-surface:has(> .dshpc-dock__resizer:hover),\n.dshpc-side-surface--resizing{\n /* \u539F\u7EBF\u4FDD\u7559\u300C\u4E0A\u4E0B\u6E10\u9690\u300D\u5DE5\u4F5C\u533A\u6307\u793A\u6761\u8D28\u611F\uFF1A\u628A\u9762\u677F\u81EA\u5DF1\u7684\u90A3\u6761\u8FB9\u6846\u6362\u6210\u540C\u6B3E\u6E10\u53D8\u3002\n \u9762\u677F\u56DB\u8FB9\u91CC\u53EA\u6709\u671D\u5185\u4E00\u4FA7\u6709\u5BBD\u5EA6\uFF0C\u6240\u4EE5\u6E10\u53D8\u53EA\u843D\u5728\u90A3\u4E00\u4FA7\u3002 */\n border-image:linear-gradient(180deg,transparent,var(--dshpc-business) 30% 70%,transparent) 1;\n}\n/* \u62D6\u52A8\u671F\uFF1A\u2460 <iframe>\uFF08PDF / HTML \u9884\u89C8\uFF09\u662F\u72EC\u7ACB\u6587\u6863\uFF0C\u4F1A\u541E\u6389\u7236\u6587\u6863\u7684 pointermove \u2014\u2014\n \u8BA9\u51FA\u6307\u9488\u4E8B\u4EF6\uFF1B\u2461 contain \u628A\u91CD\u6392\u9694\u79BB\u5728\u9762\u677F\u5185\uFF0C\u5426\u5219\u6BCF\u5E27\u6539\u5BBD\u90FD\u4F1A\u7275\u52A8\u6574\u9875\u91CD\u6392\u3002 */\n.dshpc-side-surface--resizing{contain:layout paint;will-change:width}\n.dshpc-side-surface--resizing iframe{pointer-events:none}\n\n/* \u52A8\u6548\u793C\u8C8C\uFF1A\u7CFB\u7EDF\u58F0\u660E\u51CF\u5C11\u52A8\u6548\u65F6\u9000\u5316\u4E3A\u77AC\u65F6\uFF08\u529F\u80FD\u4E0D\u53D7\u5F71\u54CD\uFF09\u3002 */\n@media (prefers-reduced-motion:reduce){\n .dshpc-side-surface{animation:none}\n}\n";
2
+ //# sourceMappingURL=side-panel-css.generated.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"side-panel-css.generated.d.ts","sourceRoot":"","sources":["../../src/ui/side-panel-css.generated.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,cAAc,uwNAAohH,CAAA"}
@@ -0,0 +1,4 @@
1
+ // ⚠️ 生成物,勿手改。真源 = src/ui/side-panel.css
2
+ // 由 scripts/gen-css.mjs 生成(`pnpm run gen:css`):改样式请改那个 .css 文件后重新生成。
3
+ export const SIDE_PANEL_CSS = "/* 侧边面板的公共皮肤 —— PanelDock(占宽分栏)与 SideSheet(覆盖抽屉)共用这一份。\n *\n * 分界:**皮肤在这里,定位各自写**。两者唯一的形态差异就是「怎么占位」——\n * PanelDock 用 sticky 占宽挤窄主区(panel-dock.css),SideSheet 用 absolute 盖在页面上(side-sheet.css)。\n *\n * 类名沿用 dshpc-dock__* 前缀:那是既有公开面(文档与消费方都在用),\n * 这里只搬文件不改名;新增的中性类是 .dshpc-side-surface(外观基类)。 */\n\n/* 入场:宽度从 0 长出来(keyframes 只写 from,终点取内联 / 状态宽度)。两边共用。 */\n@keyframes dshpc-side-in{from{width:0;opacity:0;overflow:hidden}}\n\n/* 外观基类:背景 / 文字色 / 阴影 / 纵向 flex。**不含** position、高度、边框方向、动画触发。 */\n.dshpc-side-surface{\n box-sizing:border-box;\n display:flex;flex-direction:column;flex:0 0 auto;min-width:0;\n background:var(--dshpc-surface-base);color:var(--dshpc-fg);\n box-shadow:var(--dshpc-shadow-2);\n transition:border-color var(--dshpc-dur-fast) var(--dshpc-ease);\n animation:dshpc-side-in .22s var(--dshpc-ease);\n}\n\n/* 头部:右侧 34px = 8(边距) + 20(关钮) + 6(呼吸):给绝对定位的关钮留位,标题/extra 不从它底下钻。 */\n.dshpc-dock__head{\n flex:none;display:flex;align-items:center;gap:8px;\n padding:10px 34px 10px 14px;\n border-bottom:1px solid var(--dshpc-border-faint);\n}\n/* 图标位 / 标题右侧的内容位:消费方传 ReactNode,可塞多个;库只负责排布,不抢宽度。 */\n.dshpc-dock__lead{display:inline-flex;align-items:center;gap:6px;flex:none;color:var(--dshpc-fg-2)}\n.dshpc-dock__extra{display:inline-flex;align-items:center;gap:6px;flex:none;margin-left:auto}\n/* 自定义头部:整行归调用方 —— 不加内边距/边框/背景,也不给关钮留内联空间(关钮是绝对定位的)。 */\n.dshpc-dock__head--custom{display:block;padding:0;border-bottom:none}\n.dshpc-dock__title{\n flex:1 1 auto;min-width:0;\n overflow:hidden;text-overflow:ellipsis;white-space:nowrap;\n font-size:var(--dshpc-font-md);font-weight:600;\n}\n/* 关闭钮:20×20 方角图标位(与 Modal 的关闭钮同规格,不借 Picker 触发器皮肤)。 */\n.dshpc-dock__close{\n display:inline-flex;align-items:center;justify-content:center;flex:none;\n width:20px;height:20px;padding:0;border:none;border-radius:var(--dshpc-radius-sm);\n background:transparent;color:var(--dshpc-fg-3);\n font-family:inherit;font-size:13px;line-height:1;cursor:pointer;\n transition:background-color var(--dshpc-dur-fast) var(--dshpc-ease),color var(--dshpc-dur-fast) var(--dshpc-ease);\n}\n.dshpc-dock__close:hover{background:var(--dshpc-hover);color:var(--dshpc-fg)}\n.dshpc-dock__close:focus-visible{outline:2px solid var(--dshpc-focus);outline-offset:1px}\n/* 头部关钮:绝对定位(top 10 / right 8),锚点是面板本身 —— 正文滚动它不跟滚。 */\n.dshpc-dock__close--abs{position:absolute;top:10px;right:8px;z-index:8}\n/* 浮动关钮:无头部面板用,略收一点边距。 */\n.dshpc-dock__close--floating{\n position:absolute;top:6px;right:6px;z-index:8;\n background:var(--dshpc-surface-menu);border:1px solid var(--dshpc-border-faint);\n}\n\n/* 内容区自己滚(面板高度锁死,内容不设内部滚动就会把页面撑高)。 */\n.dshpc-dock__body{flex:1 1 auto;min-height:0;overflow:auto}\n\n/* 拖拽条:6px 命中区,**自己不画任何线**(贴在朝内那一侧,镜像由各自的定位文件做)。\n hover / 拖动时把面板自己的边框染成蓝 —— 蓝线只准改原线,不准在旁边再加一条。\n z-index 7 必须高于面板内容里的浮层(复制钮一类),否则命中区会被内容盖住。 */\n.dshpc-dock__resizer{\n position:absolute;left:0;top:0;bottom:0;width:6px;\n z-index:7;cursor:col-resize;background:transparent;\n}\n.dshpc-side-surface:has(> .dshpc-dock__resizer:hover),\n.dshpc-side-surface--resizing{\n /* 原线保留「上下渐隐」工作区指示条质感:把面板自己的那条边框换成同款渐变。\n 面板四边里只有朝内一侧有宽度,所以渐变只落在那一侧。 */\n border-image:linear-gradient(180deg,transparent,var(--dshpc-business) 30% 70%,transparent) 1;\n}\n/* 拖动期:① <iframe>(PDF / HTML 预览)是独立文档,会吞掉父文档的 pointermove ——\n 让出指针事件;② contain 把重排隔离在面板内,否则每帧改宽都会牵动整页重排。 */\n.dshpc-side-surface--resizing{contain:layout paint;will-change:width}\n.dshpc-side-surface--resizing iframe{pointer-events:none}\n\n/* 动效礼貌:系统声明减少动效时退化为瞬时(功能不受影响)。 */\n@media (prefers-reduced-motion:reduce){\n .dshpc-side-surface{animation:none}\n}\n";
4
+ //# sourceMappingURL=side-panel-css.generated.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"side-panel-css.generated.js","sourceRoot":"","sources":["../../src/ui/side-panel-css.generated.ts"],"names":[],"mappings":"AAAA,wCAAwC;AACxC,qEAAqE;AACrE,MAAM,CAAC,MAAM,cAAc,GAAG,ihHAAihH,CAAA"}
@@ -0,0 +1,2 @@
1
+ export { SIDE_PANEL_CSS } from './side-panel-css.generated.js';
2
+ //# sourceMappingURL=side-panel-css.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"side-panel-css.js","sourceRoot":"","sources":["../../src/ui/side-panel-css.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAA"}
@@ -0,0 +1,2 @@
1
+ export { SIDE_SHEET_CSS } from './side-sheet-css.generated.js';
2
+ //# sourceMappingURL=side-sheet-css.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"side-sheet-css.d.ts","sourceRoot":"","sources":["../../src/ui/side-sheet-css.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAA"}
@@ -0,0 +1,2 @@
1
+ export declare const SIDE_SHEET_CSS = "/* \u4FA7\u8FB9\u62BD\u5C49\uFF1A\u8986\u76D6\u5F0F\u3002**\u76AE\u80A4\u4E0E PanelDock \u5B8C\u5168\u5171\u7528**\uFF08side-panel.css \u7684 .dshpc-side-surface\n + \u5934\u90E8 / \u62D6\u62FD\u6761 / \u5173\u95ED\u94AE\uFF09\uFF0C\u8FD9\u91CC\u53EA\u7BA1\u300C\u600E\u4E48\u5360\u4F4D\u300D\uFF1Aabsolute \u8D34\u8FB9\u3001\u901A\u9AD8\u3001\u76D6\u5728\u9875\u9762\u4E0A\u3002\n \u4E0E\u5206\u680F\u7684\u552F\u4E00\u5DEE\u522B\uFF1A\u5B83\u4E0D\u6324\u7A84\u4E3B\u533A\uFF08\u6240\u4EE5\u6CA1\u6709 sticky\u3001\u6CA1\u6709\u5360\u5BBD\u3001\u4E5F\u6CA1\u6709\u6536\u8D77\uFF09\u3002\n\n \u6CA1\u6709\u6536\u8D77/\u8033\u6735\uFF08\u7528\u6237 2026-11-02\uFF09\uFF1A\u62BD\u5C49\u5173\u5C31\u662F\u5173\u3002 */\n\n.dshpc-sheet{position:absolute;top:0;bottom:0}\n/* \u8D34\u8FB9\u90A3\u4FA7\u63CF\u8FB9\uFF08\u4E0E\u5206\u680F\u7684 border-left \u540C\u4E00\u5957\u7EBF\u8272\uFF09\uFF0C\u62D6\u62FD\u6761\u6C38\u8FDC\u5728**\u671D\u5185**\u90A3\u4E00\u4FA7\u3002 */\n.dshpc-sheet--left{left:0;border-right:1px solid var(--dshpc-border)}\n.dshpc-sheet--right{right:0;border-left:1px solid var(--dshpc-border)}\n.dshpc-sheet--left .dshpc-dock__resizer{left:auto;right:0}\n/* \u5185\u5BB9\u5185\u8DDD\uFF1A\u5206\u680F\u7684 body \u628A\u5185\u8DDD\u4EA4\u7ED9\u5185\u5BB9\u7EC4\u4EF6\uFF08FileBrowser \u90A3\u7C7B\u81EA\u5DF1\u5E26 17px\uFF09\uFF0C\n \u62BD\u5C49\u662F**\u901A\u7528\u5BB9\u5668**\uFF0C\u5FC5\u987B\u81EA\u5E26 \u2014\u2014 \u5426\u5219\u6587\u5B57\u76F4\u63A5\u8D34\u9762\u677F\u8FB9\uFF08\u7528\u6237 2026-11-02 \u622A\u56FE\uFF09\u3002\n \u5DE6\u53F3 14 \u4E0E\u5934\u90E8\u5DE6\u5185\u8DDD\u5BF9\u9F50\uFF0C\u4E0A\u4E0B\u4E5F\u7559\u540C\u6863\u547C\u5438\u3002 */\n.dshpc-sheet__body{padding:14px}\n/* \u5E95\u680F\uFF08\u53EF\u9009\uFF09\uFF1A\u7EBF\u8272\u4E0E\u5185\u8DDD\u5BF9\u9F50\u9762\u677F\u5934\u90E8\u3002 */\n.dshpc-sheet__foot{\n flex:none;display:flex;justify-content:flex-end;gap:8px;\n padding:10px 14px;border-top:1px solid var(--dshpc-border-faint);\n}\n";
2
+ //# sourceMappingURL=side-sheet-css.generated.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"side-sheet-css.generated.d.ts","sourceRoot":"","sources":["../../src/ui/side-sheet-css.generated.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,cAAc,89DAA+2B,CAAA"}
@@ -0,0 +1,4 @@
1
+ // ⚠️ 生成物,勿手改。真源 = src/ui/side-sheet.css
2
+ // 由 scripts/gen-css.mjs 生成(`pnpm run gen:css`):改样式请改那个 .css 文件后重新生成。
3
+ export const SIDE_SHEET_CSS = "/* 侧边抽屉:覆盖式。**皮肤与 PanelDock 完全共用**(side-panel.css 的 .dshpc-side-surface\n + 头部 / 拖拽条 / 关闭钮),这里只管「怎么占位」:absolute 贴边、通高、盖在页面上。\n 与分栏的唯一差别:它不挤窄主区(所以没有 sticky、没有占宽、也没有收起)。\n\n 没有收起/耳朵(用户 2026-11-02):抽屉关就是关。 */\n\n.dshpc-sheet{position:absolute;top:0;bottom:0}\n/* 贴边那侧描边(与分栏的 border-left 同一套线色),拖拽条永远在**朝内**那一侧。 */\n.dshpc-sheet--left{left:0;border-right:1px solid var(--dshpc-border)}\n.dshpc-sheet--right{right:0;border-left:1px solid var(--dshpc-border)}\n.dshpc-sheet--left .dshpc-dock__resizer{left:auto;right:0}\n/* 内容内距:分栏的 body 把内距交给内容组件(FileBrowser 那类自己带 17px),\n 抽屉是**通用容器**,必须自带 —— 否则文字直接贴面板边(用户 2026-11-02 截图)。\n 左右 14 与头部左内距对齐,上下也留同档呼吸。 */\n.dshpc-sheet__body{padding:14px}\n/* 底栏(可选):线色与内距对齐面板头部。 */\n.dshpc-sheet__foot{\n flex:none;display:flex;justify-content:flex-end;gap:8px;\n padding:10px 14px;border-top:1px solid var(--dshpc-border-faint);\n}\n";
4
+ //# sourceMappingURL=side-sheet-css.generated.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"side-sheet-css.generated.js","sourceRoot":"","sources":["../../src/ui/side-sheet-css.generated.ts"],"names":[],"mappings":"AAAA,wCAAwC;AACxC,qEAAqE;AACrE,MAAM,CAAC,MAAM,cAAc,GAAG,42BAA42B,CAAA"}
@@ -0,0 +1,2 @@
1
+ export { SIDE_SHEET_CSS } from './side-sheet-css.generated.js';
2
+ //# sourceMappingURL=side-sheet-css.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"side-sheet-css.js","sourceRoot":"","sources":["../../src/ui/side-sheet-css.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAA"}
@@ -53,6 +53,7 @@ dsh-plugin-core/
53
53
  │ ├── 选择 ✅ WorkspacePicker(选工作区,自取宿主快照)
54
54
  │ │ 通用 Picker 是纯零件;领域选择器经 connectHost 自取数(2026-10-20)
55
55
  │ ├── 组合 ✅ SplitGroup(两格焊成一体:只画外框 / 分隔 / 聚焦圈,不持状态)
56
+ │ │ InputGroup(输入框 + 按钮组焊成一体:可选前置标题 / 撤销 / 自定义高度)
56
57
  │ └── 布局 ✅ PanelDock / createPanelDock(可停靠分栏:多面板 + 拖拽拉栏)
57
58
  │ 组名 = 窗口一对一、同名覆盖、默认上限 3 超限拒绝;
58
59
  │ 不做 tab 切换 / 自动淘汰 / 浮动层(用户 2026-10-20 定案)
@@ -120,7 +121,7 @@ utils(谁都不依赖,谁都可以用)
120
121
  │ ├─ 动作 Button / IconButton
121
122
  │ ├─ 反馈 Modal / Toast
122
123
  │ ├─ 展示 Card / StripCard / Chip / Table / MarqueeText / SectionHead / FieldHint / LiveText
123
- │ ├─ 组合 SplitGroup(展示在选择器页)
124
+ │ ├─ 组合 SplitGroup(展示在选择器页)/ InputGroup(展示在表单页)
124
125
  │ └─ 布局 PanelDock / 拉栏 / QuickToolbar
125
126
  │
126
127
  ├─ ③ 高级空间
@@ -49,6 +49,18 @@ h('div', { style: { display: 'flex' } },
49
49
  )
50
50
  // 之后任何地方:dock.open('files') / dock.close('files')`
51
51
 
52
+ const sideSheetExample = `import { SideSheet } from 'dsh-plugin-core/ui'
53
+
54
+ // 页面里某一处(关闭就是卸载,和 Modal 一样)
55
+ open ? h(SideSheet, {
56
+ title: '文件详情',
57
+ extra: h(Chip, null, '只读'), // 标题右侧(与 PanelDock 同一个槽)
58
+ onClose: () => setOpen(false),
59
+ footer: h(Button, { variant: 'primary', onClick: () => setOpen(false) }, '知道了'),
60
+ }, h(FileDetail)) : null
61
+
62
+ // 从右侧滑入、初始 720 宽、不许拖:side="right" / width={720} / resizable={false}`
63
+
52
64
  const workspacePickerExample = `import { WorkspacePicker, connectHost } from 'dsh-plugin-core/ui'
53
65
 
54
66
  // apply(ctx) 里:connectHost(ctx)
@@ -82,6 +94,34 @@ h(SplitGroup, null,
82
94
  h(Picker, { size: 'md', options: membersOf(scope), value: member, onChange: setMember, searchable: true }),
83
95
  )`
84
96
 
97
+ const inputGroupExample = `import { InputGroup } from 'dsh-plugin-core/ui'
98
+
99
+ // 输入框 + 按钮组焊成一体;label 是前置标题,undoable 在最前面加撤销钮
100
+ h(InputGroup, {
101
+ label: '任务名',
102
+ value: name, onChange: setName,
103
+ undoable: true,
104
+ onSubmit: save, // Enter 提交(提交点也会进撤销栈)
105
+ actions: [
106
+ { label: '返回', variant: 'ghost', onClick: back },
107
+ { label: '确定', variant: 'success', onClick: save }, // 语义色直接传档,不自己调色
108
+ ],
109
+ })
110
+
111
+ // 按钮三形:纯图标(正方形,ariaLabel 给读屏)/ 图标 + 文字 / 纯文字;size 三档
112
+ h(InputGroup, {
113
+ size: 'md', value: kw, onChange: setKw,
114
+ actions: [
115
+ { icon: h(IconRefreshOutlineRegular, { size: 14 }), ariaLabel: '重置', onClick: reset },
116
+ { icon: h(IconSearchOutlineRegular, { size: 14 }), label: '搜索', onClick: search },
117
+ ],
118
+ })
119
+
120
+ // 高度自定义:height 直接给 px,压过 size 档(内件跟着撑满)
121
+ h(InputGroup, {
122
+ value: kw, onChange: setKw, height: 44,
123
+ actions: [{ label: '确定', onClick: save }],
124
+ })`
85
125
  const tableExample = `import { Table, Chip } from 'dsh-plugin-core/ui'
86
126
 
87
127
  const columns = [
@@ -228,7 +268,7 @@ const doc: DocTopic = {
228
268
  exports: [
229
269
  'Button', 'IconButton', 'Segmented', 'SplitGroup', 'Input', 'PrefixedInput', 'NumberInput', 'Textarea',
230
270
  'Checkbox', 'fieldMetricsOf', 'Picker', 'WorkspacePicker', 'ModelPicker', 'DateTimePicker', 'Surface', 'Card', 'StripCard',
231
- 'Chip', 'Table', 'Pagination', 'Modal', 'FieldHint', 'setTooltipRenderer', 'showToast', 'notify', 'showHostToast',
271
+ 'Chip', 'Table', 'Pagination', 'Modal', 'SideSheet', 'FieldHint', 'setTooltipRenderer', 'showToast', 'notify', 'showHostToast',
232
272
  'Loader', 'LoaderBar', 'Loading', 'RunningBlocks', 'SectionHead', 'MarqueeText', 'LiveText',
233
273
  'QuickToolbar', 'startResizeLayoutWidth', 'PanelDock', 'createPanelDock', 'DockPanel',
234
274
  'ensureControls', 'setStyleKit',
@@ -403,6 +443,41 @@ const doc: DocTopic = {
403
443
  ],
404
444
  code: [{ id: 'ui-splitgroup-example', lang: 'tsx', src: splitGroupExample }],
405
445
  },
446
+ {
447
+ id: 'input-group',
448
+ heading: { zh: 'InputGroup(组合输入框)', en: 'InputGroup (input with actions)' },
449
+ prose: [
450
+ {
451
+ zh: '输入框 + 后置按钮组焊成一体,外观与 SplitGroup 同一套口径(外框 / 圆角 / 相邻 1px 分隔 / 整体聚焦圈都由外壳画,内件把自己的边框、圆角交出来)。区别是这里**持状态**:值受控于调用方,提交点与撤销栈在控件内部。',
452
+ en: 'An input and a trailing button group welded into one shell, using the same skin rules as SplitGroup (frame, radius, 1px dividers and the focus ring all come from the shell; inner controls hand over their border and radius). The difference: this one **holds state** — the value is controlled by the caller, while commit points and the undo stack live inside.',
453
+ },
454
+ {
455
+ zh: '按钮可以是任意多枚(返回 + 确定、图标 / 图文 / 纯文字);`label` 是前置标题,与输入框同格灰底。`size` 三档(sm 24 / md 28 / lg 32),要别的就传 `height`(px),内件跟着撑满。',
456
+ en: 'Any number of buttons (back + confirm; icon / icon+text / text only); `label` is a leading title sharing the cell with a sunken background. Three sizes (sm 24 / md 28 / lg 32); pass `height` in px for anything else and the inner controls fill it.',
457
+ },
458
+ {
459
+ zh: '撤销口径:Enter / 失焦 / 点任一动作 = 一次提交,提交前的值进栈;点撤销弹回栈顶、栈空时按钮禁用。父级直接改 `value`(如外部清空)不算编辑,不进栈。',
460
+ en: 'Undo semantics: Enter / blur / clicking any action is one commit, and the value before it is pushed; the undo button pops the top and is disabled while the stack is empty. A `value` change made directly by the parent (such as an external clear) is not an edit and never enters the stack.',
461
+ },
462
+ ],
463
+ api: [
464
+ {
465
+ id: 'ui-input-group',
466
+ rows: [
467
+ { name: 'value / onChange(v)', type: 'string / (v: string) => void', def: '—', desc: { zh: '受控值', en: 'Controlled value' } },
468
+ { name: 'label', type: 'string', def: '—', desc: { zh: '前置标题(同格灰底);同时作为整组无障碍名的兜底', en: 'Leading title in the same cell (sunken background); also the fallback accessible name for the group' } },
469
+ { name: 'actions', type: 'InputGroupAction[]', def: '—', desc: { zh: '{ icon?, label?, variant?, disabled?, ariaLabel?, onClick };只给 icon 退化成正方形图标钮(ariaLabel 必给),给 label 走 Button;variant 默认 info,语义色就用 Button 现成的档(success / info / warning / danger),不另调色', en: '{ icon?, label?, variant?, disabled?, ariaLabel?, onClick }; icon-only degrades to a square icon button (pass ariaLabel), a label renders a Button; variant defaults to info — for a semantic colour pass Button’s own variant (success / info / warning / danger) instead of a custom colour' } },
470
+ { name: 'undoable / undoLabel', type: 'boolean / string', def: "false / '撤销'", desc: { zh: '最前面加撤销钮,回到上一次提交前的值', en: 'Prepends an undo button returning to the value before the last commit' } },
471
+ { name: 'size / height', type: "'sm' | 'md' | 'lg' / number(px)", def: "'lg' / —", desc: { zh: 'height 给了就压过 size 档', en: 'height overrides the size preset' } },
472
+ { name: 'error / disabled', type: 'boolean', def: '—', desc: { zh: 'error 描红整组;disabled 禁用输入与所有按钮', en: 'error turns the whole shell red; disabled disables the input and every button' } },
473
+ { name: 'onSubmit(v)', type: '(v: string) => void', def: '—', desc: { zh: 'Enter 提交(同时记一次撤销点)', en: 'Enter submit (also records an undo point)' } },
474
+ { name: 'hint', type: 'string', def: '—', desc: { zh: '整组后面挂标准 FieldHint', en: 'Attaches a standard FieldHint after the whole group' } },
475
+ { name: 'placeholder / aria-label / id / className / style', type: 'string', def: '—', desc: { zh: '占位 / 无障碍名 / 锚点 / 只补布局', en: 'Placeholder / accessible name / anchor / layout-only additions' } },
476
+ ],
477
+ },
478
+ ],
479
+ code: [{ id: 'ui-inputgroup-example', lang: 'tsx', src: inputGroupExample }],
480
+ },
406
481
  {
407
482
  id: 'datetime-picker',
408
483
  heading: { zh: 'DateTimePicker(日期时间选择)', en: 'DateTimePicker (date & time)' },
@@ -717,6 +792,45 @@ const doc: DocTopic = {
717
792
  },
718
793
  ],
719
794
  },
795
+ {
796
+ id: 'ui-sidesheet',
797
+ heading: { zh: 'SideSheet(侧边抽屉)', en: 'SideSheet (side drawer)' },
798
+ prose: [
799
+ {
800
+ zh: '从屏幕左 / 右缘滑入的**覆盖式**浮层。皮肤与 `PanelDock` **一模一样**(同一份 `side-panel.css`:头部排版、拖拽条、关闭钮、边框、阴影、入场动画),宽度也能拖;差别只在**怎么占位**:分栏是 sticky 占宽、把主区挤窄(调用方必须给挂载点),抽屉是 absolute 盖上去(自铺遮罩,不需要挂载点)。',
801
+ en: 'An **overlay** sliding in from the left or right edge. It shares the **exact same skin** as `PanelDock` (one `side-panel.css`: header layout, resize handle, close button, border, shadow, entrance animation) and resizes by dragging too; the only difference is **how it occupies space**: a dock is sticky and narrows the main area (caller must supply a mount), a sheet is absolute and covers it (lays its own mask, no mount needed).',
802
+ },
803
+ {
804
+ zh: '**没有收起**:抽屉没有收起钮、也没有收进去之后贴在边上的耳朵 —— 关就是关(用户 2026-11-02)。要「收起来还留着入口」那个形态,用 `PanelDock`。',
805
+ en: '**No collapse**: there is no collapse button and no edge ear to reopen from — closing means closing (decided 2026-11-02). For “collapse but keep an entry point”, use `PanelDock`.',
806
+ },
807
+ {
808
+ zh: '开关行为与 `Modal` 一致:**渲染即显示**(`{open && <SideSheet … />}`)、点遮罩 / Esc / ✕ 三条关闭路径都在,且可分别关掉(`closeOnMask` / `escClose` / `closable`)。遮罩右边界照样让开右侧分栏(读 `--dshpc-page-inset-right`),分级走 drawer 档(低于 modal、高于 dock)。',
809
+ en: 'Closing behaves exactly like `Modal`: **rendered means visible** (`{open && <SideSheet … />}`), with mask click / Esc / ✕ all wired and individually switchable (`closeOnMask` / `escClose` / `closable`). The mask still yields to the right column (`--dshpc-page-inset-right`) and sits on the drawer z-tier (below modal, above dock).',
810
+ },
811
+ {
812
+ zh: '宽度:初始 460px(与 PanelDock 同档)、下限 320、上限默认只受遮罩盒限制,都可调。**默认就能拖**(`resizable={false}` 关掉);拖动期把面板自己那条边框染成蓝色渐变,与分栏同款(不在旁边另加一条线)。拖出来的宽度只活在组件内,下次打开回到初始值。',
813
+ en: 'Width: starts at 460px (same as PanelDock), floor 320, ceiling defaults to the mask box — all adjustable. **Draggable by default** (`resizable={false}` to turn it off); while dragging, the panel’s own border becomes the blue gradient, same as the dock (never a second line beside it). A dragged width lives inside the component only — reopening starts from the initial value again.',
814
+ },
815
+ ],
816
+ api: [
817
+ {
818
+ id: 'ui-sidesheet',
819
+ rows: [
820
+ { name: 'side', type: "'left' | 'right'", def: "'left'", desc: { zh: '贴哪条边、从哪边滑入;拖拽条自动落到朝内那一侧', en: 'Which edge it hugs and slides from; the resize handle moves to the inner edge' } },
821
+ { name: 'title / icon / extra', type: 'ReactNode', def: '—', desc: { zh: '内置头的标题 / 标题前图标 / 标题右侧任意内容(与 PanelDock 同槽)', en: 'Built-in header: title / icon before it / anything to the right (same slots as PanelDock)' } },
822
+ { name: 'header', type: 'ReactNode | null', def: '—', desc: { zh: '整块替换头部;`null` = 不要头部(关闭钮浮到右上角)', en: 'Replace the whole header; `null` = no header (close button floats top-right)' } },
823
+ { name: 'onClose / closable / closeIcon', type: '() => void / boolean / ReactNode', def: '— / 跟随 onClose / ✕', desc: { zh: '关闭回调;不传则不出关闭钮', en: 'Close handler; no close button if omitted' } },
824
+ { name: 'closeOnMask / escClose', type: 'boolean', def: "'true' / 'true'", desc: { zh: '点遮罩关闭 / Esc 关闭', en: 'Close on mask click / on Esc' } },
825
+ { name: 'width / minWidth / maxWidth', type: 'number / number / number', def: '460 / 320 / —', desc: { zh: '初始宽度 / 拖拽下限 / 拖拽上限(px;上限缺省 = 遮罩盒宽)', en: 'Initial width / resize floor / resize ceiling (px; ceiling defaults to the mask box)' } },
826
+ { name: 'resizable', type: 'boolean', def: 'true', desc: { zh: '能否拖宽(关掉就没有拖拽条)', en: 'Whether it resizes by dragging (off = no handle)' } },
827
+ { name: 'footer / children', type: 'ReactNode / ReactNode', def: '— / —', desc: { zh: '底栏 / 主体(主体是唯一滚动容器)', en: 'Footer / body (the body is the only scroll container)' } },
828
+ { name: 'translate', type: 'Translate', def: '内置字典', desc: { zh: '覆盖关闭 / 拖拽条的文案', en: 'Override close / resize copy' } },
829
+ ],
830
+ },
831
+ ],
832
+ code: [{ id: 'ui-sidesheet-example', lang: 'tsx', src: sideSheetExample }],
833
+ },
720
834
  {
721
835
  id: 'layout',
722
836
  heading: { zh: 'PanelDock(停靠分栏)', en: 'PanelDock (dock)' },
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、FieldHint、setTooltipRenderer、showToast、notify、showHostToast、Loader、LoaderBar、Loading、RunningBlocks、SectionHead、MarqueeText、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、Pagination、Modal、SideSheet、FieldHint、setTooltipRenderer、showToast、notify、showHostToast、Loader、LoaderBar、Loading、RunningBlocks、SectionHead、MarqueeText、LiveText、QuickToolbar、startResizeLayoutWidth、PanelDock、createPanelDock、DockPanel、ensureControls、setStyleKit
8
8
 
9
9
  ## 概览
10
10
 
@@ -177,6 +177,65 @@ h(SplitGroup, null,
177
177
  )
178
178
  ```
179
179
 
180
+ ## InputGroup(组合输入框)
181
+
182
+ > EN: InputGroup (input with actions)
183
+
184
+ 输入框 + 后置按钮组焊成一体,外观与 SplitGroup 同一套口径(外框 / 圆角 / 相邻 1px 分隔 / 整体聚焦圈都由外壳画,内件把自己的边框、圆角交出来)。区别是这里**持状态**:值受控于调用方,提交点与撤销栈在控件内部。
185
+
186
+ > EN: An input and a trailing button group welded into one shell, using the same skin rules as SplitGroup (frame, radius, 1px dividers and the focus ring all come from the shell; inner controls hand over their border and radius). The difference: this one **holds state** — the value is controlled by the caller, while commit points and the undo stack live inside.
187
+
188
+ 按钮可以是任意多枚(返回 + 确定、图标 / 图文 / 纯文字);`label` 是前置标题,与输入框同格灰底。`size` 三档(sm 24 / md 28 / lg 32),要别的就传 `height`(px),内件跟着撑满。
189
+
190
+ > EN: Any number of buttons (back + confirm; icon / icon+text / text only); `label` is a leading title sharing the cell with a sunken background. Three sizes (sm 24 / md 28 / lg 32); pass `height` in px for anything else and the inner controls fill it.
191
+
192
+ 撤销口径:Enter / 失焦 / 点任一动作 = 一次提交,提交前的值进栈;点撤销弹回栈顶、栈空时按钮禁用。父级直接改 `value`(如外部清空)不算编辑,不进栈。
193
+
194
+ > EN: Undo semantics: Enter / blur / clicking any action is one commit, and the value before it is pushed; the undo button pops the top and is disabled while the stack is empty. A `value` change made directly by the parent (such as an external clear) is not an edit and never enters the stack.
195
+
196
+ | 参数 | 类型 | 默认 | 说明(zh) | 说明(en) |
197
+ |---|---|---|---|---|
198
+ | `value / onChange(v)` | `string / (v: string) => void` | — | 受控值 | Controlled value |
199
+ | `label` | `string` | — | 前置标题(同格灰底);同时作为整组无障碍名的兜底 | Leading title in the same cell (sunken background); also the fallback accessible name for the group |
200
+ | `actions` | `InputGroupAction[]` | — | { icon?, label?, variant?, disabled?, ariaLabel?, onClick };只给 icon 退化成正方形图标钮(ariaLabel 必给),给 label 走 Button;variant 默认 info,语义色就用 Button 现成的档(success / info / warning / danger),不另调色 | { icon?, label?, variant?, disabled?, ariaLabel?, onClick }; icon-only degrades to a square icon button (pass ariaLabel), a label renders a Button; variant defaults to info — for a semantic colour pass Button’s own variant (success / info / warning / danger) instead of a custom colour |
201
+ | `undoable / undoLabel` | `boolean / string` | false / '撤销' | 最前面加撤销钮,回到上一次提交前的值 | Prepends an undo button returning to the value before the last commit |
202
+ | `size / height` | `'sm' \| 'md' \| 'lg' / number(px)` | 'lg' / — | height 给了就压过 size 档 | height overrides the size preset |
203
+ | `error / disabled` | `boolean` | — | error 描红整组;disabled 禁用输入与所有按钮 | error turns the whole shell red; disabled disables the input and every button |
204
+ | `onSubmit(v)` | `(v: string) => void` | — | Enter 提交(同时记一次撤销点) | Enter submit (also records an undo point) |
205
+ | `hint` | `string` | — | 整组后面挂标准 FieldHint | Attaches a standard FieldHint after the whole group |
206
+ | `placeholder / aria-label / id / className / style` | `string` | — | 占位 / 无障碍名 / 锚点 / 只补布局 | Placeholder / accessible name / anchor / layout-only additions |
207
+
208
+ ```tsx id="ui-inputgroup-example"
209
+ import { InputGroup } from 'dsh-plugin-core/ui'
210
+
211
+ // 输入框 + 按钮组焊成一体;label 是前置标题,undoable 在最前面加撤销钮
212
+ h(InputGroup, {
213
+ label: '任务名',
214
+ value: name, onChange: setName,
215
+ undoable: true,
216
+ onSubmit: save, // Enter 提交(提交点也会进撤销栈)
217
+ actions: [
218
+ { label: '返回', variant: 'ghost', onClick: back },
219
+ { label: '确定', variant: 'success', onClick: save }, // 语义色直接传档,不自己调色
220
+ ],
221
+ })
222
+
223
+ // 按钮三形:纯图标(正方形,ariaLabel 给读屏)/ 图标 + 文字 / 纯文字;size 三档
224
+ h(InputGroup, {
225
+ size: 'md', value: kw, onChange: setKw,
226
+ actions: [
227
+ { icon: h(IconRefreshOutlineRegular, { size: 14 }), ariaLabel: '重置', onClick: reset },
228
+ { icon: h(IconSearchOutlineRegular, { size: 14 }), label: '搜索', onClick: search },
229
+ ],
230
+ })
231
+
232
+ // 高度自定义:height 直接给 px,压过 size 档(内件跟着撑满)
233
+ h(InputGroup, {
234
+ value: kw, onChange: setKw, height: 44,
235
+ actions: [{ label: '确定', onClick: save }],
236
+ })
237
+ ```
238
+
180
239
  ## DateTimePicker(日期时间选择)
181
240
 
182
241
  > EN: DateTimePicker (date & time)
@@ -603,6 +662,52 @@ Modal 是自绘 overlay,不走 react-dom portal。
603
662
  | `footer` | `ReactNode` | — | 底栏(按钮区) | Footer (button area) |
604
663
  | `children` | `ReactNode` | — | 主体 | Body |
605
664
 
665
+ ## SideSheet(侧边抽屉)
666
+
667
+ > EN: SideSheet (side drawer)
668
+
669
+ 从屏幕左 / 右缘滑入的**覆盖式**浮层。皮肤与 `PanelDock` **一模一样**(同一份 `side-panel.css`:头部排版、拖拽条、关闭钮、边框、阴影、入场动画),宽度也能拖;差别只在**怎么占位**:分栏是 sticky 占宽、把主区挤窄(调用方必须给挂载点),抽屉是 absolute 盖上去(自铺遮罩,不需要挂载点)。
670
+
671
+ > EN: An **overlay** sliding in from the left or right edge. It shares the **exact same skin** as `PanelDock` (one `side-panel.css`: header layout, resize handle, close button, border, shadow, entrance animation) and resizes by dragging too; the only difference is **how it occupies space**: a dock is sticky and narrows the main area (caller must supply a mount), a sheet is absolute and covers it (lays its own mask, no mount needed).
672
+
673
+ **没有收起**:抽屉没有收起钮、也没有收进去之后贴在边上的耳朵 —— 关就是关(用户 2026-11-02)。要「收起来还留着入口」那个形态,用 `PanelDock`。
674
+
675
+ > EN: **No collapse**: there is no collapse button and no edge ear to reopen from — closing means closing (decided 2026-11-02). For “collapse but keep an entry point”, use `PanelDock`.
676
+
677
+ 开关行为与 `Modal` 一致:**渲染即显示**(`{open && <SideSheet … />}`)、点遮罩 / Esc / ✕ 三条关闭路径都在,且可分别关掉(`closeOnMask` / `escClose` / `closable`)。遮罩右边界照样让开右侧分栏(读 `--dshpc-page-inset-right`),分级走 drawer 档(低于 modal、高于 dock)。
678
+
679
+ > EN: Closing behaves exactly like `Modal`: **rendered means visible** (`{open && <SideSheet … />}`), with mask click / Esc / ✕ all wired and individually switchable (`closeOnMask` / `escClose` / `closable`). The mask still yields to the right column (`--dshpc-page-inset-right`) and sits on the drawer z-tier (below modal, above dock).
680
+
681
+ 宽度:初始 460px(与 PanelDock 同档)、下限 320、上限默认只受遮罩盒限制,都可调。**默认就能拖**(`resizable={false}` 关掉);拖动期把面板自己那条边框染成蓝色渐变,与分栏同款(不在旁边另加一条线)。拖出来的宽度只活在组件内,下次打开回到初始值。
682
+
683
+ > EN: Width: starts at 460px (same as PanelDock), floor 320, ceiling defaults to the mask box — all adjustable. **Draggable by default** (`resizable={false}` to turn it off); while dragging, the panel’s own border becomes the blue gradient, same as the dock (never a second line beside it). A dragged width lives inside the component only — reopening starts from the initial value again.
684
+
685
+ | 参数 | 类型 | 默认 | 说明(zh) | 说明(en) |
686
+ |---|---|---|---|---|
687
+ | `side` | `'left' \| 'right'` | 'left' | 贴哪条边、从哪边滑入;拖拽条自动落到朝内那一侧 | Which edge it hugs and slides from; the resize handle moves to the inner edge |
688
+ | `title / icon / extra` | `ReactNode` | — | 内置头的标题 / 标题前图标 / 标题右侧任意内容(与 PanelDock 同槽) | Built-in header: title / icon before it / anything to the right (same slots as PanelDock) |
689
+ | `header` | `ReactNode \| null` | — | 整块替换头部;`null` = 不要头部(关闭钮浮到右上角) | Replace the whole header; `null` = no header (close button floats top-right) |
690
+ | `onClose / closable / closeIcon` | `() => void / boolean / ReactNode` | — / 跟随 onClose / ✕ | 关闭回调;不传则不出关闭钮 | Close handler; no close button if omitted |
691
+ | `closeOnMask / escClose` | `boolean` | 'true' / 'true' | 点遮罩关闭 / Esc 关闭 | Close on mask click / on Esc |
692
+ | `width / minWidth / maxWidth` | `number / number / number` | 460 / 320 / — | 初始宽度 / 拖拽下限 / 拖拽上限(px;上限缺省 = 遮罩盒宽) | Initial width / resize floor / resize ceiling (px; ceiling defaults to the mask box) |
693
+ | `resizable` | `boolean` | true | 能否拖宽(关掉就没有拖拽条) | Whether it resizes by dragging (off = no handle) |
694
+ | `footer / children` | `ReactNode / ReactNode` | — / — | 底栏 / 主体(主体是唯一滚动容器) | Footer / body (the body is the only scroll container) |
695
+ | `translate` | `Translate` | 内置字典 | 覆盖关闭 / 拖拽条的文案 | Override close / resize copy |
696
+
697
+ ```tsx id="ui-sidesheet-example"
698
+ import { SideSheet } from 'dsh-plugin-core/ui'
699
+
700
+ // 页面里某一处(关闭就是卸载,和 Modal 一样)
701
+ open ? h(SideSheet, {
702
+ title: '文件详情',
703
+ extra: h(Chip, null, '只读'), // 标题右侧(与 PanelDock 同一个槽)
704
+ onClose: () => setOpen(false),
705
+ footer: h(Button, { variant: 'primary', onClick: () => setOpen(false) }, '知道了'),
706
+ }, h(FileDetail)) : null
707
+
708
+ // 从右侧滑入、初始 720 宽、不许拖:side="right" / width={720} / resizable={false}
709
+ ```
710
+
606
711
  ## PanelDock(停靠分栏)
607
712
 
608
713
  > EN: PanelDock (dock)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-core",
3
- "version": "0.17.0",
3
+ "version": "0.19.0",
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": [