dsh-plugin-core 0.30.2 → 0.32.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 (71) hide show
  1. package/dist/design/tokens-css.generated.d.ts +1 -1
  2. package/dist/design/tokens-css.generated.d.ts.map +1 -1
  3. package/dist/design/tokens-css.generated.js +1 -1
  4. package/dist/design/tokens-css.generated.js.map +1 -1
  5. package/dist/ui/AlertModal.d.ts +2 -0
  6. package/dist/ui/AlertModal.d.ts.map +1 -1
  7. package/dist/ui/AlertModal.js +1 -0
  8. package/dist/ui/AlertModal.js.map +1 -1
  9. package/dist/ui/Button.d.ts.map +1 -1
  10. package/dist/ui/Button.js +6 -2
  11. package/dist/ui/Button.js.map +1 -1
  12. package/dist/ui/Checkbox.d.ts.map +1 -1
  13. package/dist/ui/Checkbox.js +7 -3
  14. package/dist/ui/Checkbox.js.map +1 -1
  15. package/dist/ui/ConfirmModal.d.ts +2 -0
  16. package/dist/ui/ConfirmModal.d.ts.map +1 -1
  17. package/dist/ui/ConfirmModal.js +1 -0
  18. package/dist/ui/ConfirmModal.js.map +1 -1
  19. package/dist/ui/DateTimePicker.d.ts.map +1 -1
  20. package/dist/ui/DateTimePicker.js +11 -3
  21. package/dist/ui/DateTimePicker.js.map +1 -1
  22. package/dist/ui/Field.d.ts.map +1 -1
  23. package/dist/ui/Field.js +15 -7
  24. package/dist/ui/Field.js.map +1 -1
  25. package/dist/ui/Modal.d.ts +11 -0
  26. package/dist/ui/Modal.d.ts.map +1 -1
  27. package/dist/ui/Modal.js +20 -8
  28. package/dist/ui/Modal.js.map +1 -1
  29. package/dist/ui/Pagination.d.ts.map +1 -1
  30. package/dist/ui/Pagination.js +3 -1
  31. package/dist/ui/Pagination.js.map +1 -1
  32. package/dist/ui/Picker.d.ts.map +1 -1
  33. package/dist/ui/Picker.js +10 -2
  34. package/dist/ui/Picker.js.map +1 -1
  35. package/dist/ui/Segmented.d.ts.map +1 -1
  36. package/dist/ui/Segmented.js +4 -1
  37. package/dist/ui/Segmented.js.map +1 -1
  38. package/dist/ui/SideSheet.d.ts +2 -0
  39. package/dist/ui/SideSheet.d.ts.map +1 -1
  40. package/dist/ui/SideSheet.js +18 -8
  41. package/dist/ui/SideSheet.js.map +1 -1
  42. package/dist/ui/Textarea.d.ts.map +1 -1
  43. package/dist/ui/Textarea.js +4 -1
  44. package/dist/ui/Textarea.js.map +1 -1
  45. package/dist/ui/feedback-css.generated.d.ts +1 -1
  46. package/dist/ui/feedback-css.generated.d.ts.map +1 -1
  47. package/dist/ui/feedback-css.generated.js +1 -1
  48. package/dist/ui/feedback-css.generated.js.map +1 -1
  49. package/dist/ui/index.d.ts +1 -1
  50. package/dist/ui/index.d.ts.map +1 -1
  51. package/dist/ui/index.js.map +1 -1
  52. package/dist/ui/modal-host.d.ts +9 -4
  53. package/dist/ui/modal-host.d.ts.map +1 -1
  54. package/dist/ui/modal-host.js +27 -8
  55. package/dist/ui/modal-host.js.map +1 -1
  56. package/dist/ui/overlay-css.generated.d.ts +1 -1
  57. package/dist/ui/overlay-css.generated.d.ts.map +1 -1
  58. package/dist/ui/overlay-css.generated.js +1 -1
  59. package/dist/ui/overlay-css.generated.js.map +1 -1
  60. package/dist/ui/overlay.d.ts +16 -0
  61. package/dist/ui/overlay.d.ts.map +1 -1
  62. package/dist/ui/overlay.js +45 -1
  63. package/dist/ui/overlay.js.map +1 -1
  64. package/dist/ui/side-sheet-css.generated.d.ts +1 -1
  65. package/dist/ui/side-sheet-css.generated.d.ts.map +1 -1
  66. package/dist/ui/side-sheet-css.generated.js +1 -1
  67. package/dist/ui/side-sheet-css.generated.js.map +1 -1
  68. package/docs/help/ui.doc.ts +6 -0
  69. package/docs/help/ui.md +6 -0
  70. package/docs/requirements.md +22 -0
  71. package/package.json +1 -1
@@ -4,11 +4,55 @@
4
4
  * 抽出来的判据:这两件在两种浮层里完全同构(遮罩让位分栏、头部四槽、Esc 判定),
5
5
  * 差异只在「面板怎么排」(居中 vs 贴边),那部分留在各自的控件里。
6
6
  */
7
- import { createElement as h, useEffect } from 'react';
7
+ import { createContext, createElement as h, useContext, useEffect } from 'react';
8
8
  import { ensureComponentStyles } from './css.js';
9
9
  import { OVERLAY_CSS } from './overlay-css.js';
10
10
  import { PANEL_CSS } from './panel-css.js';
11
11
  import { PanelClose } from './panel.js';
12
+ /**
13
+ * 锁定广播:Modal / SideSheet `locked` 时把 true 传下去。
14
+ * 为什么要有它:面板挂 `inert` 管不到 **portal 到 body** 的那几个浮层(Picker / 日期选择的菜单
15
+ * 不在面板 DOM 里),锁定时它们还浮在屏幕上能点;那几个组件自己订阅这里、锁上就收起。
16
+ */
17
+ const LockedContext = createContext(false);
18
+ /** 外层浮层是否锁死(不在浮层里 = false)。 */
19
+ export function useOverlayLocked() {
20
+ return useContext(LockedContext);
21
+ }
22
+ export const OverlayLockedProvider = LockedContext.Provider;
23
+ /**
24
+ * 锁定期间的两道保险(消费方自绘的控件不是我们的,管不到它们的 `disabled`):
25
+ * ① Tab 整个吃掉 —— 否则能 Tab 进他们的 input 打字;
26
+ * ② 把面板里没被禁用的活动元素 `blur` 掉 —— 否则锁定前已经停在里面的光标还能继续打字。
27
+ * 只认 `.dshpc-locked` 子树里的元素,不碰面板外面的焦点。
28
+ */
29
+ export function useLockGuards(enabled) {
30
+ useEffect(() => {
31
+ if (!enabled || typeof document === 'undefined')
32
+ return;
33
+ const active = document.activeElement;
34
+ if (active instanceof Element && active.closest('.dshpc-locked') !== null)
35
+ active.blur();
36
+ const onKey = (event) => {
37
+ if (event.key === 'Tab')
38
+ event.preventDefault();
39
+ };
40
+ document.addEventListener('keydown', onKey, true);
41
+ return () => document.removeEventListener('keydown', onKey, true);
42
+ }, [enabled]);
43
+ }
44
+ /**
45
+ * 锁定时的**透明点击层**:盖住整块面板、不遮任何像素,只吃掉指针事件
46
+ * (消费方自绘的元素也跟着点不动)。变灰由各控件自己的 `:disabled` 负责,这里不管外观。
47
+ * 不做焦点接管:核心控件此刻已是真 disabled,Tab 自己就跳过它们。
48
+ */
49
+ export function OverlayLockCatcher() {
50
+ return h('div', {
51
+ className: 'dshpc-lock-catcher',
52
+ // 点它不能再冒到遮罩:否则等于点了外部,会顺带把弹窗关掉。
53
+ onClick: (event) => event.stopPropagation(),
54
+ });
55
+ }
12
56
  /**
13
57
  * Esc 关闭。不传 `onClose` 或 `enabled=false` 时完全不挂监听
14
58
  * (无回调还挂监听,等于让 Esc 被白白吃掉)。
@@ -1 +1 @@
1
- {"version":3,"file":"overlay.js","sourceRoot":"","sources":["../../src/ui/overlay.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"overlay.js","sourceRoot":"","sources":["../../src/ui/overlay.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,aAAa,EAAE,aAAa,IAAI,CAAC,EAAE,UAAU,EAAE,SAAS,EAAkB,MAAM,OAAO,CAAA;AAChG,OAAO,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAA;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAEvC;;;;GAIG;AACH,MAAM,aAAa,GAAG,aAAa,CAAC,KAAK,CAAC,CAAA;AAE1C,+BAA+B;AAC/B,MAAM,UAAU,gBAAgB;IAC9B,OAAO,UAAU,CAAC,aAAa,CAAC,CAAA;AAClC,CAAC;AAED,MAAM,CAAC,MAAM,qBAAqB,GAAG,aAAa,CAAC,QAAQ,CAAA;AAE3D;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,OAAgB;IAC5C,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,OAAO,IAAI,OAAO,QAAQ,KAAK,WAAW;YAAE,OAAM;QACvD,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAA;QACrC,IAAI,MAAM,YAAY,OAAO,IAAI,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC,KAAK,IAAI;YAAG,MAAsB,CAAC,IAAI,EAAE,CAAA;QACzG,MAAM,KAAK,GAAG,CAAC,KAAoB,EAAQ,EAAE;YAC3C,IAAI,KAAK,CAAC,GAAG,KAAK,KAAK;gBAAE,KAAK,CAAC,cAAc,EAAE,CAAA;QACjD,CAAC,CAAA;QACD,QAAQ,CAAC,gBAAgB,CAAC,SAAS,EAAE,KAAK,EAAE,IAAI,CAAC,CAAA;QACjD,OAAO,GAAG,EAAE,CAAC,QAAQ,CAAC,mBAAmB,CAAC,SAAS,EAAE,KAAK,EAAE,IAAI,CAAC,CAAA;IACnE,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAA;AACf,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB;IAChC,OAAO,CAAC,CAAC,KAAK,EAAE;QACd,SAAS,EAAE,oBAAoB;QAC/B,+BAA+B;QAC/B,OAAO,EAAE,CAAC,KAAsC,EAAE,EAAE,CAAC,KAAK,CAAC,eAAe,EAAE;KAC7E,CAAC,CAAA;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,OAAgB,EAAE,OAAiC;IAChF,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,OAAO,IAAI,OAAO,KAAK,SAAS;YAAE,OAAM;QAC7C,MAAM,KAAK,GAAG,CAAC,KAAoB,EAAQ,EAAE;YAC3C,IAAI,KAAK,CAAC,GAAG,KAAK,QAAQ;gBAAE,OAAO,EAAE,CAAA;QACvC,CAAC,CAAA;QACD,QAAQ,CAAC,gBAAgB,CAAC,SAAS,EAAE,KAAK,CAAC,CAAA;QAC3C,OAAO,GAAG,EAAE,CAAC,QAAQ,CAAC,mBAAmB,CAAC,SAAS,EAAE,KAAK,CAAC,CAAA;IAC7D,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAA;AACxB,CAAC;AAmBD,gCAAgC;AAChC,MAAM,UAAU,WAAW,CAAC,KAAuB;IACjD,qBAAqB,CAAC,SAAS,EAAE,WAAW,CAAC,CAAA;IAC7C,OAAO,CAAC,CACN,KAAK,EACL;QACE,GAAG,KAAK,CAAC,KAAK;QACd,SAAS,EAAE,gCAAgC,KAAK,CAAC,IAAI,EAAE;QACvD,OAAO,EAAE,GAAG,EAAE;YACZ,IAAI,KAAK,CAAC,WAAW;gBAAE,KAAK,CAAC,OAAO,EAAE,EAAE,CAAA;QAC1C,CAAC;KACF,EACD,KAAK,CAAC,QAAQ,IAAI,IAAI,CACvB,CAAA;AACH,CAAC;AAiBD,oCAAoC;AACpC,MAAM,UAAU,WAAW,CAAC,KAAuB;IACjD,qBAAqB,CAAC,SAAS,EAAE,WAAW,CAAC,CAAA;IAC7C,oEAAoE;IACpE,qBAAqB,CAAC,OAAO,EAAE,SAAS,CAAC,CAAA;IACzC,OAAO,CAAC,CACN,KAAK,EACL,EAAE,SAAS,EAAE,oBAAoB,EAAE,EACnC,CAAC,CACC,KAAK,EACL,EAAE,SAAS,EAAE,6BAA6B,EAAE,EAC5C,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,0BAA0B,EAAE,EAAE,KAAK,CAAC,IAAI,CAAC,EAClG,CAAC,CACC,KAAK,EACL,EAAE,SAAS,EAAE,gCAAgC,EAAE,EAC/C,CAAC,CACC,KAAK,EACL,EAAE,SAAS,EAAE,8BAA8B,EAAE,EAC7C,CAAC,CAAC,KAAK,EAAE,EAAE,SAAS,EAAE,2BAA2B,EAAE,EAAE,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC,EACvE,KAAK,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE,SAAS,EAAE,2BAA2B,EAAE,EAAE,KAAK,CAAC,KAAK,CAAC,CACrG,EACD,KAAK,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE,SAAS,EAAE,yBAAyB,EAAE,EAAE,KAAK,CAAC,QAAQ,CAAC,CACzG,CACF,EACD,CAAC,CAAC,KAAK,EAAE,EAAE,SAAS,EAAE,4BAA4B,EAAE,CAAC,EACrD,KAAK,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE,SAAS,EAAE,6BAA6B,EAAE,EAAE,KAAK,CAAC,OAAO,CAAC,EAC1G,KAAK,CAAC,OAAO,KAAK,SAAS;QACzB,CAAC,CAAC,IAAI;QACN,gDAAgD;QAChD,CAAC,CAAC,CAAC,CAAC,UAAU,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,UAAU,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,SAAS,EAAE,2BAA2B,EAAE,CAAC,CAC/G,CAAA;AACH,CAAC"}
@@ -1,2 +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/* \u6536\u8D77\u51FA\u573A\uFF08\u7528\u6237 2026-11-02\uFF09\uFF1A\u5BBD\u5EA6\u6536\u5230 0 + \u6DE1\u51FA\uFF0C\u64AD\u5B8C\u7531\u7EC4\u4EF6\u5378\u8F7D\u3002\u53CC\u7C7B\u538B\u8FC7 .dshpc-side-surface \u7684\u5165\u573A\u52A8\u753B\u3002 */\n@keyframes dshpc-side-out{to{width:0;opacity:0;border-width:0;overflow:hidden}}\n.dshpc-side-surface.dshpc-sheet--leaving{animation:dshpc-side-out .2s var(--dshpc-ease) forwards;pointer-events:none;}\n/* \u9762\u4E0E Modal \u540C\u6863\uFF08bg-layer-1\uFF09\uFF0CPanelDock \u4ECD\u7528\u9875\u9762\u5E95\uFF1A\u53CC\u7C7B\u538B\u8FC7\u5171\u7528\u7684 .dshpc-side-surface\uFF0C\n \u4E0D\u78B0\u8FB9\u6846\uFF08\u7528\u6237 2026-11-02\uFF1A\u53EA\u6539\u80CC\u666F\uFF09\u3002 */\n.dshpc-side-surface.dshpc-sheet{background:var(--dshpc-surface-1)}\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/* \u5E95\u680F\u5DE6\u4FA7\u7684\u8FC7\u7A0B\u63D0\u793A\uFF1A\u5403\u6389\u6309\u94AE\u4E4B\u5916\u7684\u5269\u4F59\u5BBD\uFF0C\u8D85\u957F\u4EA4\u7ED9 FlipText \u7701\u7565 / hover \u8DD1\u9A6C\u706F\u3002 */\n.dshpc-sheet__notice{flex:1 1 auto;min-width:0;font-size:var(--dshpc-font-sm);line-height:var(--dshpc-line-sm);color:var(--dshpc-fg-3)}\n";
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/* \u6536\u8D77\u51FA\u573A\uFF08\u7528\u6237 2026-11-02\uFF09\uFF1A\u5BBD\u5EA6\u6536\u5230 0 + \u6DE1\u51FA\uFF0C\u64AD\u5B8C\u7531\u7EC4\u4EF6\u5378\u8F7D\u3002\u53CC\u7C7B\u538B\u8FC7 .dshpc-side-surface \u7684\u5165\u573A\u52A8\u753B\u3002 */\n@keyframes dshpc-side-out{to{width:0;opacity:0;border-width:0;overflow:hidden}}\n.dshpc-side-surface.dshpc-sheet--leaving{animation:dshpc-side-out .2s var(--dshpc-ease) forwards;pointer-events:none;}\n/* \u9762\u4E0E Modal \u540C\u6863\uFF08bg-layer-1\uFF09\uFF0CPanelDock \u4ECD\u7528\u9875\u9762\u5E95\uFF1A\u53CC\u7C7B\u538B\u8FC7\u5171\u7528\u7684 .dshpc-side-surface\uFF0C\n \u4E0D\u78B0\u8FB9\u6846\uFF08\u7528\u6237 2026-11-02\uFF1A\u53EA\u6539\u80CC\u666F\uFF09\u3002 */\n.dshpc-side-surface.dshpc-sheet{background:var(--dshpc-surface-1)}\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\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/* \u5E95\u680F\u5DE6\u4FA7\u7684\u8FC7\u7A0B\u63D0\u793A\uFF1A\u5403\u6389\u6309\u94AE\u4E4B\u5916\u7684\u5269\u4F59\u5BBD\uFF0C\u8D85\u957F\u4EA4\u7ED9 FlipText \u7701\u7565 / hover \u8DD1\u9A6C\u706F\u3002 */\n.dshpc-sheet__notice{flex:1 1 auto;min-width:0;font-size:var(--dshpc-font-sm);line-height:var(--dshpc-line-sm);color:var(--dshpc-fg-3)}\n";
2
2
  //# sourceMappingURL=side-sheet-css.generated.d.ts.map
@@ -1 +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,2lGAAw/C,CAAA"}
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,6lGAA0/C,CAAA"}
@@ -1,4 +1,4 @@
1
1
  // ⚠️ 生成物,勿手改。真源 = src/ui/side-sheet.css
2
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/* 收起出场(用户 2026-11-02):宽度收到 0 + 淡出,播完由组件卸载。双类压过 .dshpc-side-surface 的入场动画。 */\n@keyframes dshpc-side-out{to{width:0;opacity:0;border-width:0;overflow:hidden}}\n.dshpc-side-surface.dshpc-sheet--leaving{animation:dshpc-side-out .2s var(--dshpc-ease) forwards;pointer-events:none;}\n/* 面与 Modal 同档(bg-layer-1),PanelDock 仍用页面底:双类压过共用的 .dshpc-side-surface,\n 不碰边框(用户 2026-11-02:只改背景)。 */\n.dshpc-side-surface.dshpc-sheet{background:var(--dshpc-surface-1)}\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/* 底栏左侧的过程提示:吃掉按钮之外的剩余宽,超长交给 FlipText 省略 / hover 跑马灯。 */\n.dshpc-sheet__notice{flex:1 1 auto;min-width:0;font-size:var(--dshpc-font-sm);line-height:var(--dshpc-line-sm);color:var(--dshpc-fg-3)}\n";
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/* 收起出场(用户 2026-11-02):宽度收到 0 + 淡出,播完由组件卸载。双类压过 .dshpc-side-surface 的入场动画。 */\n@keyframes dshpc-side-out{to{width:0;opacity:0;border-width:0;overflow:hidden}}\n.dshpc-side-surface.dshpc-sheet--leaving{animation:dshpc-side-out .2s var(--dshpc-ease) forwards;pointer-events:none;}\n/* 面与 Modal 同档(bg-layer-1),PanelDock 仍用页面底:双类压过共用的 .dshpc-side-surface,\n 不碰边框(用户 2026-11-02:只改背景)。 */\n.dshpc-side-surface.dshpc-sheet{background:var(--dshpc-surface-1)}\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/* 底栏(可选):线色与内距对齐面板头部。 */\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/* 底栏左侧的过程提示:吃掉按钮之外的剩余宽,超长交给 FlipText 省略 / hover 跑马灯。 */\n.dshpc-sheet__notice{flex:1 1 auto;min-width:0;font-size:var(--dshpc-font-sm);line-height:var(--dshpc-line-sm);color:var(--dshpc-fg-3)}\n";
4
4
  //# sourceMappingURL=side-sheet-css.generated.js.map
@@ -1 +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,q/CAAq/C,CAAA"}
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,u/CAAu/C,CAAA"}
@@ -824,6 +824,10 @@ const doc: DocTopic = {
824
824
  zh: '回调的返回值决定关不关:**只有 `false`**(同步返回,或 resolve 成 `false`)表示这次没成 —— 框留着让人重试;`true` / `undefined` / 其他返回值照常关;reject 也不关(错误打到控制台)。失败提示由回调自己出,调用点不必包 `try/catch`。',
825
825
  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`.',
826
826
  },
827
+ {
828
+ zh: '**要「整框谁都别动」就传 `locked`**,别拿按钮 loading 顶替 —— 两者是两件事:loading 只管那一颗按钮转圈,`locked` 管整块面板。做法是**控件各自按自己的禁用态渲染**(Button / Input / Textarea / Checkbox / Segmented / Picker / 日期选择 / 分页 都会自己变灰并真的禁用,Tab 自然跳过),再盖一层**全透明**点击层兜住自绘元素与鼠标 —— 所以界面不会整体蒙灰(蒙灰会让人以为坏了),只有控件自己变灰 + 禁止手势。它**只做「或」、从不写回**任何 `disabled`:本来禁用的解锁后照样禁用,锁定期间你把某个控件改成能用 / 禁用,解锁后也照你的。声明式传 `locked={saving}` 即可;受控项,不传 = 未锁,关掉重开自然复位。命令式那两个函数的回调会收到 `ctl`(`{ setLocked(bool) }`):`onConfirm: async (ctl) => { ctl.setLocked(true); await save(); ctl.setLocked(false) }`;框关掉之后再调是空操作。',
829
+ en: '**When the whole dialog must be untouchable, pass `locked`** — do not fake it with button loading: loading only spins that one button, while `locked` covers the whole panel. It works by letting **each control render its own disabled state** (Button / Input / Textarea / Checkbox / Segmented / Picker / date picker / pagination all dim themselves and are genuinely disabled, so Tab skips them), plus a **fully transparent** click layer that catches self-drawn elements and the pointer — so the surface is never dimmed as a whole (a veil reads as "the app broke"); only the controls dim and the cursor turns not-allowed. It **only ORs and never writes back** any `disabled`: already-disabled controls stay disabled after unlocking, and anything you flip mid-lock stays as you left it. Declaratively pass `locked={saving}`; it is controlled and stateless — omitted means unlocked, reopening starts clean. The imperative dialogs hand their callbacks a `ctl` (`{ setLocked(bool) }`): `onConfirm: async (ctl) => { ctl.setLocked(true); await save(); ctl.setLocked(false) }`. Calling it after the dialog closed is a no-op.',
830
+ },
827
831
  ],
828
832
  api: [
829
833
  {
@@ -857,6 +861,7 @@ const doc: DocTopic = {
857
861
  { name: 'closeOnMask', type: 'boolean', def: "'true'", desc: { zh: '点遮罩关闭;误触代价大的场景设 false', en: 'Close on mask click; set false where mis-clicks are costly' } },
858
862
  { name: 'escClose', type: 'boolean', def: "'true'", desc: { zh: 'Esc 关闭', en: 'Close on Esc' } },
859
863
  { name: 'busy', type: 'boolean', def: 'false', desc: { zh: '有异步在跑(footer 里的 `Button` 正在 loading):遮罩 / Esc / ✕ 三条关闭路径一起失效,内容照常。否决项,不替代 closeOnMask / escClose', en: 'Async work in flight (a footer `Button` is loading): mask / Esc / ✕ all stop closing while content keeps rendering. A veto, not a replacement for closeOnMask / escClose' } },
864
+ { name: 'locked', type: 'boolean', def: 'false', desc: { zh: '**锁死全部交互**(与 loading 解耦):面板里的核心控件各自按**自己的禁用态**渲染(灰掉、点不动、Tab 跳过),再盖一层**全透明**点击层兜住自绘元素与鼠标(禁止手势)—— **不压任何灰色遮罩**;同时 Tab 键整个吃掉、锁定瞬间把面板里的活动焦点 blur 掉(自绘的 input 也打不了字);三条关闭路径同时失效。自绘的原生表单元素会顺带**近似变灰**(一条特异性为 0 的规则,只写 opacity / cursor,被你自己任何一条规则或 `--dshpc-lock-dim: 1` 覆盖掉)。只做「或」、不写回调用方的 `disabled`:本来禁用的解锁后照样禁用,锁定期间改的也不被覆盖。受控项、不落内部 state(关掉重开自然回到未锁)', en: '**Locks every interaction** (decoupled from loading): core controls inside render in **their own disabled state** (dimmed, unclickable, skipped by Tab), plus a **fully transparent** click layer that catches self-drawn elements and the pointer (not-allowed cursor) — **no dim veil at all**; Tab is swallowed outright and whatever was focused inside is blurred on lock (so a self-drawn input cannot be typed into); all three close paths stop working. Self-drawn native form elements get an **approximate dim** too (one zero-specificity rule writing only opacity / cursor, beaten by any rule of yours or by `--dshpc-lock-dim: 1`). It only ORs and never writes back a caller’s `disabled`: already-disabled controls stay disabled after unlocking, and mid-lock changes are not overwritten. Controlled and stateless (reopening starts unlocked)' } },
860
865
  { name: 'maxWidth', type: 'string', def: '按 size', desc: { zh: '任意 CSS 值,覆盖 size 的宽度上限;不要用 vw(遮罩已按右侧分栏让位)', en: 'Any CSS value, overriding the size’s max width; avoid vw (the mask already yielded to the right column)' } },
861
866
  { name: 'hideHeader', type: 'boolean', def: "'false'", desc: { zh: '不渲染头部(沉浸式)', en: 'Skip header (immersive)' } },
862
867
  { name: 'footer', type: 'ReactNode', def: '—', desc: { zh: '底栏(按钮区;上下内距相等、内容垂直居中)', en: 'Footer (button area; symmetric padding, vertically centred)' } },
@@ -940,6 +945,7 @@ const doc: DocTopic = {
940
945
  { name: 'open', type: 'boolean', def: 'true', desc: { zh: '受控开关:传 false 时先播收起动画再卸载(想有收起效果就常驻渲染它、只切这个布尔)', en: 'Controlled flag: false plays the collapse animation before unmounting (keep it mounted and toggle this to get the exit motion)' } },
941
946
  { name: 'closeOnMask / escClose', type: 'boolean', def: "'true' / 'true'", desc: { zh: '点遮罩关闭 / Esc 关闭', en: 'Close on mask click / on Esc' } },
942
947
  { name: 'busy', type: 'boolean', def: 'false', desc: { zh: '有异步在跑(footer 里的 `Button` 正在 loading):遮罩 / Esc / ✕ 三条关闭路径一起失效,内容照常。否决项,不替代 closeOnMask / escClose', en: 'Async work in flight (a footer `Button` is loading): mask / Esc / ✕ all stop closing while content keeps rendering. A veto, not a replacement for closeOnMask / escClose' } },
948
+ { name: 'locked', type: 'boolean', def: 'false', desc: { zh: '锁死全部交互(含义同 `Modal.locked`):核心控件各自按自己的禁用态渲染 + 全透明点击层 + 三条关闭路径失效;不压灰色遮罩、不写回任何 `disabled`', en: 'Lock every interaction (same as `Modal.locked`): core controls render in their own disabled state, a fully transparent click layer catches the rest, all three close paths stop working; no dim veil and no writing back to any `disabled`' } },
943
949
  { 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)' } },
944
950
  { name: 'resizable', type: 'boolean', def: 'true', desc: { zh: '能否拖宽(关掉就没有拖拽条)', en: 'Whether it resizes by dragging (off = no handle)' } },
945
951
  { name: 'footer / children', type: 'ReactNode / ReactNode', def: '— / —', desc: { zh: '底栏 / 主体(主体是唯一滚动容器)', en: 'Footer / body (the body is the only scroll container)' } },
package/docs/help/ui.md CHANGED
@@ -696,6 +696,10 @@ Modal 是自绘 overlay,不走 react-dom portal。
696
696
 
697
697
  > 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`.
698
698
 
699
+ **要「整框谁都别动」就传 `locked`**,别拿按钮 loading 顶替 —— 两者是两件事:loading 只管那一颗按钮转圈,`locked` 管整块面板。做法是**控件各自按自己的禁用态渲染**(Button / Input / Textarea / Checkbox / Segmented / Picker / 日期选择 / 分页 都会自己变灰并真的禁用,Tab 自然跳过),再盖一层**全透明**点击层兜住自绘元素与鼠标 —— 所以界面不会整体蒙灰(蒙灰会让人以为坏了),只有控件自己变灰 + 禁止手势。它**只做「或」、从不写回**任何 `disabled`:本来禁用的解锁后照样禁用,锁定期间你把某个控件改成能用 / 禁用,解锁后也照你的。声明式传 `locked={saving}` 即可;受控项,不传 = 未锁,关掉重开自然复位。命令式那两个函数的回调会收到 `ctl`(`{ setLocked(bool) }`):`onConfirm: async (ctl) => { ctl.setLocked(true); await save(); ctl.setLocked(false) }`;框关掉之后再调是空操作。
700
+
701
+ > EN: **When the whole dialog must be untouchable, pass `locked`** — do not fake it with button loading: loading only spins that one button, while `locked` covers the whole panel. It works by letting **each control render its own disabled state** (Button / Input / Textarea / Checkbox / Segmented / Picker / date picker / pagination all dim themselves and are genuinely disabled, so Tab skips them), plus a **fully transparent** click layer that catches self-drawn elements and the pointer — so the surface is never dimmed as a whole (a veil reads as "the app broke"); only the controls dim and the cursor turns not-allowed. It **only ORs and never writes back** any `disabled`: already-disabled controls stay disabled after unlocking, and anything you flip mid-lock stays as you left it. Declaratively pass `locked={saving}`; it is controlled and stateless — omitted means unlocked, reopening starts clean. The imperative dialogs hand their callbacks a `ctl` (`{ setLocked(bool) }`): `onConfirm: async (ctl) => { ctl.setLocked(true); await save(); ctl.setLocked(false) }`. Calling it after the dialog closed is a no-op.
702
+
699
703
  | 参数 | 类型 | 默认 | 说明(zh) | 说明(en) |
700
704
  |---|---|---|---|---|
701
705
  | `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 |
@@ -724,6 +728,7 @@ Modal 是自绘 overlay,不走 react-dom portal。
724
728
  | `closeOnMask` | `boolean` | 'true' | 点遮罩关闭;误触代价大的场景设 false | Close on mask click; set false where mis-clicks are costly |
725
729
  | `escClose` | `boolean` | 'true' | Esc 关闭 | Close on Esc |
726
730
  | `busy` | `boolean` | false | 有异步在跑(footer 里的 `Button` 正在 loading):遮罩 / Esc / ✕ 三条关闭路径一起失效,内容照常。否决项,不替代 closeOnMask / escClose | Async work in flight (a footer `Button` is loading): mask / Esc / ✕ all stop closing while content keeps rendering. A veto, not a replacement for closeOnMask / escClose |
731
+ | `locked` | `boolean` | false | **锁死全部交互**(与 loading 解耦):面板里的核心控件各自按**自己的禁用态**渲染(灰掉、点不动、Tab 跳过),再盖一层**全透明**点击层兜住自绘元素与鼠标(禁止手势)—— **不压任何灰色遮罩**;同时 Tab 键整个吃掉、锁定瞬间把面板里的活动焦点 blur 掉(自绘的 input 也打不了字);三条关闭路径同时失效。自绘的原生表单元素会顺带**近似变灰**(一条特异性为 0 的规则,只写 opacity / cursor,被你自己任何一条规则或 `--dshpc-lock-dim: 1` 覆盖掉)。只做「或」、不写回调用方的 `disabled`:本来禁用的解锁后照样禁用,锁定期间改的也不被覆盖。受控项、不落内部 state(关掉重开自然回到未锁) | **Locks every interaction** (decoupled from loading): core controls inside render in **their own disabled state** (dimmed, unclickable, skipped by Tab), plus a **fully transparent** click layer that catches self-drawn elements and the pointer (not-allowed cursor) — **no dim veil at all**; Tab is swallowed outright and whatever was focused inside is blurred on lock (so a self-drawn input cannot be typed into); all three close paths stop working. Self-drawn native form elements get an **approximate dim** too (one zero-specificity rule writing only opacity / cursor, beaten by any rule of yours or by `--dshpc-lock-dim: 1`). It only ORs and never writes back a caller’s `disabled`: already-disabled controls stay disabled after unlocking, and mid-lock changes are not overwritten. Controlled and stateless (reopening starts unlocked) |
727
732
  | `maxWidth` | `string` | 按 size | 任意 CSS 值,覆盖 size 的宽度上限;不要用 vw(遮罩已按右侧分栏让位) | Any CSS value, overriding the size’s max width; avoid vw (the mask already yielded to the right column) |
728
733
  | `hideHeader` | `boolean` | 'false' | 不渲染头部(沉浸式) | Skip header (immersive) |
729
734
  | `footer` | `ReactNode` | — | 底栏(按钮区;上下内距相等、内容垂直居中) | Footer (button area; symmetric padding, vertically centred) |
@@ -795,6 +800,7 @@ Modal 是自绘 overlay,不走 react-dom portal。
795
800
  | `open` | `boolean` | true | 受控开关:传 false 时先播收起动画再卸载(想有收起效果就常驻渲染它、只切这个布尔) | Controlled flag: false plays the collapse animation before unmounting (keep it mounted and toggle this to get the exit motion) |
796
801
  | `closeOnMask / escClose` | `boolean` | 'true' / 'true' | 点遮罩关闭 / Esc 关闭 | Close on mask click / on Esc |
797
802
  | `busy` | `boolean` | false | 有异步在跑(footer 里的 `Button` 正在 loading):遮罩 / Esc / ✕ 三条关闭路径一起失效,内容照常。否决项,不替代 closeOnMask / escClose | Async work in flight (a footer `Button` is loading): mask / Esc / ✕ all stop closing while content keeps rendering. A veto, not a replacement for closeOnMask / escClose |
803
+ | `locked` | `boolean` | false | 锁死全部交互(含义同 `Modal.locked`):核心控件各自按自己的禁用态渲染 + 全透明点击层 + 三条关闭路径失效;不压灰色遮罩、不写回任何 `disabled` | Lock every interaction (same as `Modal.locked`): core controls render in their own disabled state, a fully transparent click layer catches the rest, all three close paths stop working; no dim veil and no writing back to any `disabled` |
798
804
  | `width / minWidth / maxWidth` | `number / number / number` | 460 / 320 / — | 初始宽度 / 拖拽下限 / 拖拽上限(px;上限缺省 = 遮罩盒宽) | Initial width / resize floor / resize ceiling (px; ceiling defaults to the mask box) |
799
805
  | `resizable` | `boolean` | true | 能否拖宽(关掉就没有拖拽条) | Whether it resizes by dragging (off = no handle) |
800
806
  | `footer / children` | `ReactNode / ReactNode` | — / — | 底栏 / 主体(主体是唯一滚动容器) | Footer / body (the body is the only scroll container) |
@@ -114,6 +114,28 @@ dsh-plugin-core/
114
114
  受控 `loading` 与之取或(**别处刷新也能点亮同一颗按钮**);`onBusyChange` 把「在跑」报出去。
115
115
  `Modal` / `SideSheet` 只认 `busy`(遮罩 / Esc / ✕ 一并失效,footer 皮肤各弹窗自管),
116
116
  `ConfirmModal` / `AlertModal` 退化成薄壳,命令式 `confirmModal` / `alertModal` 只负责「跑完关不关」。
117
+ - **锁定与 loading 是两件事**(2026-11-05 用户拍板,新增 `locked`,推翻「`busy` 一把抓」):
118
+ `busy` 只封三条关闭路径、内容照常;`locked` 封**全部交互**并同时封关闭路径。实现口径当天被用户
119
+ 打回一次、二次拍板后才是最终版:
120
+ - **不用 `inert`、不压任何灰色遮罩**(整块蒙灰用户以为界面坏了,这是被否掉的第一版)。
121
+ - 做法是核心控件**各自按自己的禁用态渲染**(Button / IconButton / Input / PrefixedInput /
122
+ NumberInput / Textarea / Checkbox / Segmented / Picker / DateTimePicker / Pagination 在渲染时
123
+ 多算一个 `|| locked`),Tab 自然跳过;再盖一层**全透明**点击层兜住消费方自绘的元素与指针,
124
+ 光标为 `not-allowed`。
125
+ - **只做「或」,永不写回**调用方的 `disabled` —— 这是「不与用户已禁用的状态冲突」「记住初始状态」
126
+ 的答案:没有快照也就没有还原,本来禁用的解锁后照样禁用,锁定期间被改成能用 / 禁用的也不被覆盖。
127
+ - `locked` 是受控项、组件内不存 state,不传即初始态、关掉重开自动复位;命令式的两个弹窗不给静态
128
+ `locked`,改由回调参数 `ctl.setLocked(bool)` 在异步过程中开关(弹窗关掉后调用是空操作)。
129
+ - 自绘元素的三道补充(2026-11-05 用户追加拍板):**Tab 整个吃掉**(否则能 Tab 进自绘 input 打字)、
130
+ **锁定瞬间把 `.dshpc-locked` 里的活动元素 blur 掉**(否则锁定前已聚焦的还能继续打)、以及给自绘的
131
+ 原生 `button / input / select / textarea` 一条**近似变灰**规则。
132
+ - 那条变灰规则的口径是不许盖消费方样式:整条选择器包进 `:where()`(特异性 **0**,消费方任意一条
133
+ 自己的规则都能盖回来)、**只写 `opacity` + `cursor`**、值走 token `--dshpc-lock-dim`(写 1 即退出)。
134
+ 绝不走「JS 改内联 style 再还原」——那才会真把人家写的样式冲掉;CSS 只是「条件成立时生效」,
135
+ 解锁即不再匹配,不存在还原动作。
136
+ - `skin:'host'` 不支持(宿主弹窗内部不归我们管)。
137
+ - 剩余缺口:用内联 `style="opacity:…"` 的自绘控件灰不到它(内联本来就赢过任何规则),
138
+ 但点击与键盘早已被挡住 —— 用户接受。
117
139
  这个「关不关」由回调返回值决定(2026-11-03 用户拍板):**只有明确的 `false`**(同步返回,或 Promise
118
140
  resolve 成 `false`)算「这次没成」,弹窗留着让人重试;`true` / `undefined` / 其它照常关。失败怎么提示
119
141
  由回调自己决定(核心不弹 Toast),这样调用点就不必在外面包 `try/catch`。同步与异步两条路径同一份判据。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-core",
3
- "version": "0.30.2",
3
+ "version": "0.32.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": [