dsh-plugin-core 0.22.1 → 0.24.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 (60) hide show
  1. package/dist/explorer/FileBrowser.d.ts.map +1 -1
  2. package/dist/explorer/FileBrowser.js +3 -0
  3. package/dist/explorer/FileBrowser.js.map +1 -1
  4. package/dist/session/SessionViewModal.d.ts +1 -1
  5. package/dist/session/SessionViewModal.d.ts.map +1 -1
  6. package/dist/session/SessionViewModal.js +4 -3
  7. package/dist/session/SessionViewModal.js.map +1 -1
  8. package/dist/ui/AlertModal.d.ts +7 -0
  9. package/dist/ui/AlertModal.d.ts.map +1 -1
  10. package/dist/ui/AlertModal.js +8 -1
  11. package/dist/ui/AlertModal.js.map +1 -1
  12. package/dist/ui/Button.d.ts +9 -0
  13. package/dist/ui/Button.d.ts.map +1 -1
  14. package/dist/ui/Button.js +67 -11
  15. package/dist/ui/Button.js.map +1 -1
  16. package/dist/ui/ConfirmModal.d.ts +9 -0
  17. package/dist/ui/ConfirmModal.d.ts.map +1 -1
  18. package/dist/ui/ConfirmModal.js +15 -1
  19. package/dist/ui/ConfirmModal.js.map +1 -1
  20. package/dist/ui/DateTimePicker.d.ts.map +1 -1
  21. package/dist/ui/DateTimePicker.js +5 -5
  22. package/dist/ui/DateTimePicker.js.map +1 -1
  23. package/dist/ui/Field.d.ts.map +1 -1
  24. package/dist/ui/Field.js +3 -2
  25. package/dist/ui/Field.js.map +1 -1
  26. package/dist/ui/InputGroup.d.ts +7 -0
  27. package/dist/ui/InputGroup.d.ts.map +1 -1
  28. package/dist/ui/InputGroup.js +5 -2
  29. package/dist/ui/InputGroup.js.map +1 -1
  30. package/dist/ui/datetime-logic.d.ts +2 -0
  31. package/dist/ui/datetime-logic.d.ts.map +1 -1
  32. package/dist/ui/datetime-logic.js +7 -0
  33. package/dist/ui/datetime-logic.js.map +1 -1
  34. package/dist/ui/index.d.ts +2 -1
  35. package/dist/ui/index.d.ts.map +1 -1
  36. package/dist/ui/index.js +2 -1
  37. package/dist/ui/index.js.map +1 -1
  38. package/dist/ui/locale.d.ts +4 -0
  39. package/dist/ui/locale.d.ts.map +1 -0
  40. package/dist/ui/locale.js +17 -0
  41. package/dist/ui/locale.js.map +1 -0
  42. package/dist/ui/modal-host.d.ts +4 -3
  43. package/dist/ui/modal-host.d.ts.map +1 -1
  44. package/dist/ui/modal-host.js +16 -6
  45. package/dist/ui/modal-host.js.map +1 -1
  46. package/dist/ui/thenable.d.ts +6 -0
  47. package/dist/ui/thenable.d.ts.map +1 -1
  48. package/dist/ui/thenable.js +8 -0
  49. package/dist/ui/thenable.js.map +1 -1
  50. package/dist/utils/i18n.d.ts +6 -1
  51. package/dist/utils/i18n.d.ts.map +1 -1
  52. package/dist/utils/i18n.js +33 -4
  53. package/dist/utils/i18n.js.map +1 -1
  54. package/docs/design/external/dsh-host.md +15 -0
  55. package/docs/help/ui.doc.ts +53 -1
  56. package/docs/help/ui.md +47 -1
  57. package/docs/help/utils.doc.ts +8 -4
  58. package/docs/help/utils.md +8 -4
  59. package/docs/requirements.md +11 -0
  60. package/package.json +1 -1
package/docs/help/ui.md CHANGED
@@ -47,6 +47,10 @@ IconButton:variant: plain|outline|danger(默认 plain,**无底色**)、s
47
47
 
48
48
  > 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.
49
49
 
50
+ loading 会把前置圈插进按钮:无 icon 的按钮因此宽出 18px(圈 12 + 间距 6),把旁边的按钮挤开。要完全不跳有两条路:给 `width` 定宽(按最长文案 + 18px 留),或传 `loadingText={false}` 让 loading 时只剩一圈。
51
+
52
+ > EN: Loading inserts the spinner into the button, so a button without an icon grows 18px (12px spinner + 6px gap) and shoves its neighbours. To keep it rock-steady, either set `width` (longest label + 18px) or pass `loadingText={false}` so only the spinner remains while loading.
53
+
50
54
  Segmented:`items`(必填)支持单选 / 多选,`size`(默认 lg;三档总高 24 / 28 / 32)、`bordered`(1px 外框,总高不变)、`multiple`(多选)。外观只有一套:灰底轨道 + 白色选中片,选中片的描边环与阴影由宿主 elevation token 提供(离线兜底值没有那圈环)。选中片配色只经 `tone`:传内置色名(与 Chip 同名的 business / success / warning / danger / purple / pink / cyan / teal / orange / indigo / rose / slate)时底与字由核心定、深浅两主题各自成立;传 `{ bg, fg }` 时两个主题都用调用方这一对颜色(对比度由调用方负责)。段内 `icon` 是前置图标槽(官方图标组件 / 数组 / 自绘节点均可),`label` 也可传节点;顺序固定为 icon → label → count。只有图标(无文字 / 计数 / 角标)时该段自动变正方形(宽 = 段高),读屏名用 `ariaLabel`,缺省退回 `tooltip`。段内 `count`(弱化灰字,选中段内自动跟着选中片字色提亮)/ `badge`(红角标)接受 `number | string`:数字 ≤0 不显示,字符串原样显示(如 `1K` / `99+`)。
51
55
 
52
56
  > EN: Segmented: `items` (required) supports single or multiple selection; `size` (default lg; totals 24 / 28 / 32), `bordered` (1px outer frame, same total height), `multiple` (multi-select). One look only: grey track + white selected pill; its stroke ring and shadow come from the host elevation token (the offline fallback has no ring). The selected pill’s colors go through `tone` only: a built-in color name (business / success / warning / danger / purple / pink / cyan / teal / orange / indigo / rose / slate, same names as Chip) lets the core pick background and text per theme; `{ bg, fg }` uses that pair verbatim in both themes (contrast is the caller’s call). An item’s leading `icon` takes any node (official icon component, array, or custom); `label` also accepts a node; order is always icon → label → count. With only an icon (no label / count / badge) the segment squares itself (width = segment height), and the accessible name comes from `ariaLabel`, falling back to `tooltip`. An item’s `count` (muted text; auto-lightened to follow the selected pill’s text color while selected) and `badge` (red pill) take `number | string`: numbers ≤0 render nothing, strings render as given (e.g. `'1K'` / `'99+'`).
@@ -59,6 +63,7 @@ Segmented:`items`(必填)支持单选 / 多选,`size`(默认 lg;三
59
63
  | `iconRight` | `ReactNode` | — | 后置图标(展开箭头、外链) | Trailing icon (expand arrow, external link) |
60
64
  | `color` | `string` | — | 自定义主色:实心档作底色,线框 / 无底档作主色 | Custom main color: background for solid tiers, main color for outline/bare |
61
65
  | `loading` | `boolean` | 'false' | 受控 loading:文案前显示同色小圈并自动 disabled(别处刷新也能点亮它);与「点击自动 loading」取或 | Controlled loading: same-color spinner before the label plus auto-disable (an external refresh can light it up too); OR-ed with automatic click loading |
66
+ | `loadingText` | `boolean` | true | 传 false:loading 时清掉文案、把按钮锁在「有文案时」的宽度上、只留一枚居中的圈;settle 后文案与自适应宽度一起还原。文案清掉后读屏靠 aria-label(字符串 children 自动补),首帧就是 loading 时锁不到宽,改用 width | Pass false to drop the label while loading, freeze the button at its labelled width and show a single centred spinner; label and auto width come back on settle. Screen readers fall back to aria-label (auto-filled for string children); a button that starts out loading has no labelled width to freeze — set width instead |
62
67
  | `onClick` | `(...) => unknown` | — | **返回 Promise 即自动 loading**:转圈 + 禁用 + 防连点,settle 后自己恢复;reject 也恢复并把错误打到控制台。同步返回值 = 老行为 | **Returning a Promise enables loading automatically**: spinner, disabled, no double-clicks, restored on settle; a rejection also restores and logs the error. Sync return values behave as before |
63
68
  | `onBusyChange` | `(busy: boolean) => void` | — | 把「这个按钮正在跑」报出去(配合 `Modal` / `SideSheet` 的 `busy` 锁死关闭),不改变按钮自身行为 | Reports "this button is running" outward (pair with `busy` on `Modal` / `SideSheet` to lock closing); does not change the button itself |
64
69
  | `block` | `boolean` | 'false' | 撑满父宽 | Fill parent width |
@@ -203,7 +208,7 @@ h(SplitGroup, null,
203
208
  |---|---|---|---|---|
204
209
  | `value / onChange(v)` | `string / (v: string) => void` | — | 受控值 | Controlled value |
205
210
  | `label` | `string` | — | 前置标题(同格灰底);同时作为整组无障碍名的兜底 | Leading title in the same cell (sunken background); also the fallback accessible name for the group |
206
- | `actions` | `InputGroupAction[]` | — | { icon?, label?, variant?, disabled?, ariaLabel?, onClick };只给 icon 退化成正方形图标钮(ariaLabel 必给),给 label 走 Button;variant 默认 info,语义色就用 Button 现成的档(success / info / warning / danger),不另调色;**onClick 返回 Promise 时那枚按钮自动转圈禁用**,不用自己传 loading | { 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; **a Promise-returning onClick spins and disables that button** — no loading prop to manage |
211
+ | `actions` | `InputGroupAction[]` | — | { icon?, label?, variant?, disabled?, width?, loadingText?, ariaLabel?, onClick };只给 icon 退化成正方形图标钮(ariaLabel 必给),给 label 走 Button;variant 默认 info,语义色就用 Button 现成的档(success / info / warning / danger),不另调色;**onClick 返回 Promise 时那枚按钮自动转圈禁用**,不用自己传 loading;转圈会把按钮撑宽并挤窄输入框,用 width 或 loadingText={false} 压住 | { icon?, label?, variant?, disabled?, width?, loadingText?, 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; **a Promise-returning onClick spins and disables that button** — no loading prop to manage; spinning widens the button and squeezes the input, so hold it with width or loadingText={false} |
207
212
  | `undoable / undoLabel` | `boolean / string` | false / '撤销' | 最前面加撤销钮,回到上一次提交前的值 | Prepends an undo button returning to the value before the last commit |
208
213
  | `size / height` | `'sm' \| 'md' \| 'lg' / number(px)` | 'lg' / — | height 给了就压过 size 档 | height overrides the size preset |
209
214
  | `error / disabled` | `boolean` | — | error 描红整组;disabled 禁用输入与所有按钮 | error turns the whole shell red; disabled disables the input and every button |
@@ -637,6 +642,14 @@ Modal 是自绘 overlay,不走 react-dom portal。
637
642
 
638
643
  > EN: The mask’s right edge reads `--dshpc-page-inset-right` (summed across docks) and yields to the right column, centering the dialog in the **remaining** area. The dialog needs no mask props; never measure the column yourself (mounting is async, it always reads 0).
639
644
 
645
+ 命令式 `confirmModal(opts)` / `alertModal(opts)` 返回 Promise:前者 resolve `true`(确定)或 `false`(取消 / 遮罩 / Esc),后者关闭即 resolve。回调返回 Promise 时对应按钮自己转圈、整框锁死,settle 后才关。
646
+
647
+ > EN: The imperative `confirmModal(opts)` / `alertModal(opts)` return a Promise: the former resolves `true` (confirmed) or `false` (cancel / mask / Esc), the latter resolves on close. When a callback returns a Promise, its button spins and the dialog locks until it settles.
648
+
649
+ 回调的返回值决定关不关:**只有 `false`**(同步返回,或 resolve 成 `false`)表示这次没成 —— 框留着让人重试;`true` / `undefined` / 其他返回值照常关;reject 也不关(错误打到控制台)。失败提示由回调自己出,调用点不必包 `try/catch`。
650
+
651
+ > EN: The callback’s return value decides whether it closes: **only `false`** (returned directly, or resolved to `false`) means it did not succeed — the dialog stays for a retry; `true` / `undefined` / anything else closes as usual; a rejection also keeps it open (the error is logged). The callback owns its failure message, so call sites need no `try/catch`.
652
+
640
653
  | 参数 | 类型 | 默认 | 说明(zh) | 说明(en) |
641
654
  |---|---|---|---|---|
642
655
  | `showToast(message, opts?)` | `` | | 官方 Sonner 原味(轻量原生观感;demo 统一用 notify)。opts: `{ variant?, durationMs?, color?, onDismiss?, position? }`,6 向定位,明暗跟随宿主 | Raw official Sonner flavour (lightweight, native look; the demo standardises on notify). opts `{ variant?, durationMs?, color?, onDismiss?, position? }`, 6-way placement, theme follows host |
@@ -652,6 +665,7 @@ Modal 是自绘 overlay,不走 react-dom portal。
652
665
  | `SectionHead({ icon?, title, right?, accent?, flush? })` | `` | | 分区标题:标签(可选,画出来的短竖线)/ icon + 标题 + 右侧控件。accent = 标签颜色(内置色名 / 任意色值 / default / false 不画);flush = 去底边距 | Section heading: optional drawn bar / icon + title + right controls. accent = bar colour (built-in name / raw value / default / false for none); flush = no bottom margin |
653
666
  | `MarqueeText({ text, style?, title?, className? })` | `` | | 超长省略、hover 滚动 | Ellipsis on overflow, scroll on hover |
654
667
  | `LiveText({ render, style? })` | `` | | 定时重渲染(相对时间这类) | Periodic re-render (for relative time etc.) |
668
+ | `useLocale()` | `` | | 当前语言,并在宿主切语言(`<html lang>` 变化)时重渲;内置文案本来就跟随宿主,这个 hook 只补「切完刷新」 | Current locale, re-rendering when the host switches language (`<html lang>` change); built-in copy already follows the host, this hook only adds the post-switch refresh |
655
669
  | `startResizeLayoutWidth(opts)` | `` | | 拖拽调整布局宽度(root 不传则回落 #dshpc-root) | Drag to resize layout width (falls back to #dshpc-root if root omitted) |
656
670
 
657
671
  | 参数 | 类型 | 默认 | 说明(zh) | 说明(en) |
@@ -671,6 +685,38 @@ Modal 是自绘 overlay,不走 react-dom portal。
671
685
  | `className` | `string` | — | 追加 class(只补布局 / 视觉修饰,别覆盖皮肤) | Extra class (layout / visual tweaks only; do not override the skin) |
672
686
  | `children` | `ReactNode` | — | 主体 | Body |
673
687
 
688
+ | 参数 | 类型 | 默认 | 说明(zh) | 说明(en) |
689
+ |---|---|---|---|---|
690
+ | `title / message` | `ReactNode` | — | 标题 / 说明文字或任意内容;无标题时整条头部消失 | Title / body text or any content; no title means no header row |
691
+ | `tone` | `'primary' \| 'danger' \| 'warning'` | 'primary' | 确认钮语义档(决定按钮颜色) | Confirm button tone (sets its colour) |
692
+ | `confirmText / cancelText` | `string` | 内置字典「确定」/「取消」 | 按钮文案 | Button labels |
693
+ | `confirmWidth / cancelWidth` | `number \| string` | — | 两颗钮各自定宽(px 或任意 CSS 宽度);loading 转圈会把按钮撑宽,定宽后不跳 | Per-button fixed width (px or any CSS width); loading would otherwise widen the button and shift its neighbour |
694
+ | `loadingText` | `boolean` | true | 传 false:loading 时两颗钮清文案、锁住有文案的宽度、只留居中的圈(见 `Button.loadingText`) | Pass false: while loading, both buttons drop their labels, freeze at the labelled width and show a centred spinner (see `Button.loadingText`) |
695
+ | `onConfirm / onCancel` | `() => unknown` | — | 确认 / 取消回调;返回 Promise 时按钮转圈、整框锁死,settle 后恢复;返回 `false` 不关 | Confirm / cancel callback; a Promise makes its button spin and locks the dialog until it settles; `false` keeps it open |
696
+ | `confirmLoading / cancelLoading` | `boolean` | false | 外部驱动对应按钮 loading,与按钮自身异步状态取或 | External loading for that button, OR-ed with its own async state |
697
+ | `closeOnMask` | `boolean` | 'true' | 点遮罩关闭(等于取消) | Close on mask click (treated as cancel) |
698
+ | `size` | `ModalSize` | 'sm' | 尺寸档 | Size tier |
699
+ | `translate` | `Translate` | 内置字典 | 覆盖确定 / 取消文案 | Override confirm / cancel copy |
700
+
701
+ | 参数 | 类型 | 默认 | 说明(zh) | 说明(en) |
702
+ |---|---|---|---|---|
703
+ | `title / message` | `ReactNode` | — | 标题 / 说明文字或任意内容;无标题时整条头部消失 | Title / body text or any content; no title means no header row |
704
+ | `severity` | `'info' \| 'success' \| 'warning' \| 'error'` | 'info' | 信号档:头部图标与配色由它决定 | Signal tier: sets the header glyph and colour |
705
+ | `confirmText` | `string` | 内置字典「知道了」 | 按钮文案 | Button label |
706
+ | `confirmWidth` | `number \| string` | — | 确认钮定宽(px 或任意 CSS 宽度);loading 转圈会把按钮撑宽,定宽后不跳 | Fixed width for the confirm button (px or any CSS width); loading would otherwise widen it |
707
+ | `loadingText` | `boolean` | true | 传 false:loading 时清文案、锁住有文案的宽度、只留居中的圈(见 `Button.loadingText`) | Pass false: while loading, drop the label, freeze at the labelled width and show a centred spinner (see `Button.loadingText`) |
708
+ | `onConfirm` | `() => unknown` | — | 确认 / 遮罩 / Esc 的统一出口;返回 Promise 时转圈锁框,返回 `false` 不关 | Single exit for confirm / mask / Esc; a Promise spins and locks it, `false` keeps it open |
709
+ | `confirmLoading` | `boolean` | false | 外部驱动按钮 loading,与按钮自身异步状态取或 | External button loading, OR-ed with its own async state |
710
+ | `closeOnMask` | `boolean` | 'true' | 点遮罩关闭 | Close on mask click |
711
+ | `size` | `ModalSize` | 'sm' | 尺寸档 | Size tier |
712
+ | `translate` | `Translate` | 内置字典 | 覆盖按钮文案 | Override the button copy |
713
+
714
+ | 参数 | 类型 | 默认 | 说明(zh) | 说明(en) |
715
+ |---|---|---|---|---|
716
+ | `confirmModal(opts)` | `Promise<boolean>` | — | 命令式确认框;opts 同 `ConfirmModal`,另有 `skin`;`true` = 确定 | Imperative confirm; opts match `ConfirmModal` plus `skin`; `true` = confirmed |
717
+ | `alertModal(opts)` | `Promise<void>` | — | 命令式警告框;opts 同 `AlertModal`,另有 `skin`;关闭即 resolve | Imperative alert; opts match `AlertModal` plus `skin`; resolves on close |
718
+ | `skin` | `'core' \| 'host'` | 'core' | host 走宿主原生 Modal(需先引入适配层 `official.js`,未注册则回落 core 并告警) | host uses the host’s native Modal (requires the `official.js` adapter; falls back to core with a warning when unregistered) |
719
+
674
720
  ## SideSheet(侧边抽屉)
675
721
 
676
722
  > EN: SideSheet (side drawer)
@@ -111,16 +111,20 @@ const doc: DocTopic = {
111
111
  en: 'Boundary: a plugin’s own UI copy is the plugin’s responsibility; this package offers no plugin-level i18n framework. To override a string: pass a `translate` prop, or use `t` with your own dictionary.',
112
112
  },
113
113
  {
114
- zh: '可用 key 全集见 src/utils/i18n.ts 的 UtilMessageKey。',
115
- en: 'Full key list: `UtilMessageKey` in `src/utils/i18n.ts`.',
114
+ zh: '语言默认跟随宿主:官方 locale 插件把生效语言写进 `<html lang>`(zh → `zh-CN`),本包直接读它 ⇒ 插件不必再接 locale 服务、也不必自己同步。`setLocale` 只是手动覆盖(测试 / 非宿主环境用),调过就不再跟随宿主。',
115
+ en: 'The language follows the host by default: the official locale plugin writes the active language into `<html lang>` (zh → `zh-CN`) and this package reads it directly, so a plugin needs neither the locale service nor its own sync. `setLocale` is only a manual override (tests / non-host environments); once called it stops following the host.',
116
+ },
117
+ {
118
+ zh: '可用 key 全集见 src/utils/i18n.ts 的 UtilMessageKey;要在宿主切语言时重渲组件,顶层调一次 `useLocale()`(ui 导出)。',
119
+ en: 'Full key list: `UtilMessageKey` in `src/utils/i18n.ts`; to re-render on a host language switch, call `useLocale()` (exported from ui) once at the top of a component.',
116
120
  },
117
121
  ],
118
122
  api: [
119
123
  {
120
124
  id: 'utils-i18n',
121
125
  rows: [
122
- { name: 'setLocale(locale)', type: "('zh' | 'en') => void", def: '—', desc: { zh: "'zh'(默认)/ 'en'", en: "'zh' (default) / 'en'" } },
123
- { name: 'getLocale()', type: '() => string', def: '—', desc: { zh: '当前语言', en: 'Current locale' } },
126
+ { name: 'setLocale(locale)', type: "('zh' | 'en') => void", def: '不调则跟随宿主', desc: { zh: '手动覆盖语言(测试 / 非宿主);不调就跟随宿主 `<html lang>`', en: 'Manual override (tests / non-host); otherwise follows the host `<html lang>`' } },
127
+ { name: 'getLocale()', type: '() => string', def: '—', desc: { zh: '当前语言(默认跟随宿主)', en: 'Current locale (follows the host by default)' } },
124
128
  { name: 't(key, vars?)', type: '(key, vars?) => string', def: '—', desc: { zh: '取文案;{n} 占位由 vars 填', en: 'Get copy; {n} placeholders filled by vars' } },
125
129
  { name: 'type Translate', type: '(key, vars?) => string', def: '—', desc: { zh: '组件都收这个参数供覆盖', en: 'Components accept this to override copy' } },
126
130
  ],
@@ -84,13 +84,17 @@ RequestOptions:{ timeoutMs?, signal?, method?, body?, headers? }。
84
84
 
85
85
  > EN: Boundary: a plugin’s own UI copy is the plugin’s responsibility; this package offers no plugin-level i18n framework. To override a string: pass a `translate` prop, or use `t` with your own dictionary.
86
86
 
87
- 可用 key 全集见 src/utils/i18n.ts 的 UtilMessageKey。
87
+ 语言默认跟随宿主:官方 locale 插件把生效语言写进 `<html lang>`(zh → `zh-CN`),本包直接读它 ⇒ 插件不必再接 locale 服务、也不必自己同步。`setLocale` 只是手动覆盖(测试 / 非宿主环境用),调过就不再跟随宿主。
88
88
 
89
- > EN: Full key list: `UtilMessageKey` in `src/utils/i18n.ts`.
89
+ > EN: The language follows the host by default: the official locale plugin writes the active language into `<html lang>` (zh → `zh-CN`) and this package reads it directly, so a plugin needs neither the locale service nor its own sync. `setLocale` is only a manual override (tests / non-host environments); once called it stops following the host.
90
+
91
+ 可用 key 全集见 src/utils/i18n.ts 的 UtilMessageKey;要在宿主切语言时重渲组件,顶层调一次 `useLocale()`(ui 导出)。
92
+
93
+ > EN: Full key list: `UtilMessageKey` in `src/utils/i18n.ts`; to re-render on a host language switch, call `useLocale()` (exported from ui) once at the top of a component.
90
94
 
91
95
  | 参数 | 类型 | 默认 | 说明(zh) | 说明(en) |
92
96
  |---|---|---|---|---|
93
- | `setLocale(locale)` | `('zh' \| 'en') => void` | — | 'zh'(默认)/ 'en' | 'zh' (default) / 'en' |
94
- | `getLocale()` | `() => string` | — | 当前语言 | Current locale |
97
+ | `setLocale(locale)` | `('zh' \| 'en') => void` | 不调则跟随宿主 | 手动覆盖语言(测试 / 非宿主);不调就跟随宿主 `<html lang>` | Manual override (tests / non-host); otherwise follows the host `<html lang>` |
98
+ | `getLocale()` | `() => string` | — | 当前语言(默认跟随宿主) | Current locale (follows the host by default) |
95
99
  | `t(key, vars?)` | `(key, vars?) => string` | — | 取文案;{n} 占位由 vars 填 | Get copy; {n} placeholders filled by vars |
96
100
  | `type Translate` | `(key, vars?) => string` | — | 组件都收这个参数供覆盖 | Components accept this to override copy |
@@ -114,7 +114,18 @@ dsh-plugin-core/
114
114
  受控 `loading` 与之取或(**别处刷新也能点亮同一颗按钮**);`onBusyChange` 把「在跑」报出去。
115
115
  `Modal` / `SideSheet` 只认 `busy`(遮罩 / Esc / ✕ 一并失效,footer 皮肤各弹窗自管),
116
116
  `ConfirmModal` / `AlertModal` 退化成薄壳,命令式 `confirmModal` / `alertModal` 只负责「跑完关不关」。
117
+ 这个「关不关」由回调返回值决定(2026-11-03 用户拍板):**只有明确的 `false`**(同步返回,或 Promise
118
+ resolve 成 `false`)算「这次没成」,弹窗留着让人重试;`true` / `undefined` / 其它照常关。失败怎么提示
119
+ 由回调自己决定(核心不弹 Toast),这样调用点就不必在外面包 `try/catch`。同步与异步两条路径同一份判据。
117
120
  转圈期间按钮自己的 `icon` / `iconRight` 一律让位,只留转圈(+ 文字)。内测期不设兼容层。
121
+ - 转圈会插一枚 12px 圈 + 6px 间距 ⇒ 无 icon 的按钮宽出 18px(2026-11-03 用户拍板的两条通道):
122
+ ① `ConfirmModal`(`confirmWidth` / `cancelWidth`)、`AlertModal`(`confirmWidth`)、
123
+ `InputGroupAction`(`width`)透传 `Button.width`,调用方按「最长文案 + 18px」定宽;
124
+ ② `Button.loadingText={false}`(ConfirmModal / AlertModal / InputGroupAction 同名透传):
125
+ loading 时**清掉文案、锁住「有文案时」的宽度、只留一枚居中的圈**,settle 后文案与自适应宽度一起还原。
126
+ 清文案后读屏名称靠 `aria-label`(字符串 children 自动补,复合 children 由调用方给);
127
+ 首帧就是 loading(没有「有文案的宽度」可量)时锁不到宽,走 ① 的 `width`。
128
+ loading 圈包进 `.dshpc-btn__icon`(svg 被 1em 规则接管)⇒ 有 icon 的按钮切 loading 宽度零差。
118
129
  - `Loading` 是旧的「自带定位 + 自带 pill 背景」复合件,**保留不删**但标 `@deprecated`,新代码用 `Loader`。
119
130
  - 布局基础:`PanelDock` / `DockPanel` / `createPanelDock`(**可停靠分栏容器**:多面板并存、按组名寻址、拖拽拉栏、宽度记忆;2026-10-20 拍板用此名,替代早先的 `SidebarLayout` / `SplitPane`)。只做右侧;组名与窗口一对一;默认上限 3,超限拒绝新开;不做 tab 切换 / 自动淘汰 / 多份同名;**面板本身始终是布局成员,不做面板浮层**(2026-10-28 补:**收起** = 面板退出布局、另出右侧一只独立的 `position: fixed` 耳朵,点耳朵展开并还原宽度;关闭不留耳朵——耳朵是独立浮层,不是把面板变浮层)。**结构照成熟库口径写 children,`open/close` 只传 id,不把页面树塞进调用参数**
120
131
  - 浮动快捷工具栏:`QuickToolbar`(2026-10-31 用户拍板):右侧一列独立圆钮,纵向**恒贴屏幕底边**(默认 16px);横向默认贴屏幕右缘,传 `anchorId` / `anchor()` 时贴到指定控件右缘外侧,右侧放不下则退回屏幕右缘(控件找不到按跟随窗口处理)。内置回顶部默认 `when:'scroll'`、阈值 320,`backToTop:false` 可关掉,对象可调 `order` / `label` / `threshold`;自定义项 `items` 支持 `icon` / `label` / `onClick` / `visible` / `when` / `order` / `threshold` / `color` / `fgColor`,统一 `onAction`。旧的 `BackToTop` **全部删除**,只保留这一份,不做迁移。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-core",
3
- "version": "0.22.1",
3
+ "version": "0.24.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": [