@greypan/web-ui 10.2.0 → 11.0.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 (133) hide show
  1. package/README.CN.md +68 -68
  2. package/README.md +43 -43
  3. package/dist/assets/glass.js.map +1 -1
  4. package/dist/assets/menu-portal.js.map +1 -1
  5. package/dist/assets/modal-surface.js.map +1 -1
  6. package/dist/assets/overlay-motion.js.map +1 -1
  7. package/dist/assets/selection-control.js.map +1 -1
  8. package/dist/components/autocomplete/index.d.ts +6 -6
  9. package/dist/components/autocomplete/index.js +1 -1
  10. package/dist/components/autocomplete/index.js.map +1 -1
  11. package/dist/components/autocomplete/style.js.map +1 -1
  12. package/dist/components/avatar/index.js.map +1 -1
  13. package/dist/components/back-top/index.js.map +1 -1
  14. package/dist/components/back-top/style.js.map +1 -1
  15. package/dist/components/badge/index.js +1 -1
  16. package/dist/components/badge/index.js.map +1 -1
  17. package/dist/components/button/index.js.map +1 -1
  18. package/dist/components/button/style.js.map +1 -1
  19. package/dist/components/checkbox/index.d.ts +1 -1
  20. package/dist/components/checkbox/index.js +1 -1
  21. package/dist/components/checkbox/index.js.map +1 -1
  22. package/dist/components/checkbox/style.js.map +1 -1
  23. package/dist/components/checkbox-group/index.d.ts +9 -0
  24. package/dist/components/checkbox-group/index.js +8 -2
  25. package/dist/components/checkbox-group/index.js.map +1 -1
  26. package/dist/components/collapse/index.d.ts +5 -5
  27. package/dist/components/collapse/index.js.map +1 -1
  28. package/dist/components/collapse/style.js.map +1 -1
  29. package/dist/components/context-menu/index.d.ts +60 -60
  30. package/dist/components/context-menu/index.js +2 -2
  31. package/dist/components/context-menu/index.js.map +1 -1
  32. package/dist/components/context-menu/scrim.js.map +1 -1
  33. package/dist/components/context-menu/style.js.map +1 -1
  34. package/dist/components/dialog/index.d.ts +4 -4
  35. package/dist/components/dialog/index.js +1 -1
  36. package/dist/components/dialog/index.js.map +1 -1
  37. package/dist/components/dialog/style.js.map +1 -1
  38. package/dist/components/drawer/index.d.ts +8 -8
  39. package/dist/components/drawer/index.js +1 -1
  40. package/dist/components/drawer/index.js.map +1 -1
  41. package/dist/components/drawer/style.js.map +1 -1
  42. package/dist/components/dropdown/index.d.ts +6 -6
  43. package/dist/components/dropdown/index.js.map +1 -1
  44. package/dist/components/dropdown-item/index.js.map +1 -1
  45. package/dist/components/editable-text/index.d.ts +2 -2
  46. package/dist/components/editable-text/index.js.map +1 -1
  47. package/dist/components/editable-text/style.js.map +1 -1
  48. package/dist/components/empty/index.d.ts +3 -3
  49. package/dist/components/empty/index.js.map +1 -1
  50. package/dist/components/image-preview/index.d.ts +5 -5
  51. package/dist/components/image-preview/index.js +7 -7
  52. package/dist/components/image-preview/index.js.map +1 -1
  53. package/dist/components/image-preview/style.js.map +1 -1
  54. package/dist/components/input/index.js +4 -1
  55. package/dist/components/input/index.js.map +1 -1
  56. package/dist/components/input/style.js.map +1 -1
  57. package/dist/components/input-number/index.d.ts +6 -0
  58. package/dist/components/input-number/index.js +27 -21
  59. package/dist/components/input-number/index.js.map +1 -1
  60. package/dist/components/layout/index.d.ts +7 -7
  61. package/dist/components/layout/index.js +4 -4
  62. package/dist/components/layout/index.js.map +1 -1
  63. package/dist/components/layout/style.js.map +1 -1
  64. package/dist/components/middle-ellipsis/index.d.ts +1 -1
  65. package/dist/components/middle-ellipsis/index.js.map +1 -1
  66. package/dist/components/middle-ellipsis/truncate.d.ts +1 -1
  67. package/dist/components/middle-ellipsis/truncate.js.map +1 -1
  68. package/dist/components/popover/index.d.ts +1 -1
  69. package/dist/components/popover/index.js.map +1 -1
  70. package/dist/components/popover/style.js.map +1 -1
  71. package/dist/components/radio/index.d.ts +1 -1
  72. package/dist/components/radio/index.js +1 -1
  73. package/dist/components/radio/index.js.map +1 -1
  74. package/dist/components/radio-group/index.d.ts +7 -0
  75. package/dist/components/radio-group/index.js +21 -3
  76. package/dist/components/radio-group/index.js.map +1 -1
  77. package/dist/components/segmented/index.js.map +1 -1
  78. package/dist/components/segmented/style.js.map +1 -1
  79. package/dist/components/segmented-trigger/style.js.map +1 -1
  80. package/dist/components/select/index.js +1 -1
  81. package/dist/components/select/index.js.map +1 -1
  82. package/dist/components/select/style.js.map +1 -1
  83. package/dist/components/slider/index.js +3 -3
  84. package/dist/components/slider/index.js.map +1 -1
  85. package/dist/components/slider/style.js +1 -1
  86. package/dist/components/slider/style.js.map +1 -1
  87. package/dist/components/spinner/index.js +1 -1
  88. package/dist/components/spinner/index.js.map +1 -1
  89. package/dist/components/switch/index.d.ts +3 -0
  90. package/dist/components/switch/index.js +26 -8
  91. package/dist/components/switch/index.js.map +1 -1
  92. package/dist/components/switch/style.js.map +1 -1
  93. package/dist/components/textarea/index.js +1 -1
  94. package/dist/components/textarea/index.js.map +1 -1
  95. package/dist/components/textarea/style.js.map +1 -1
  96. package/dist/components/theme/index.js.map +1 -1
  97. package/dist/components/theme/style.js.map +1 -1
  98. package/dist/components/toast/style.js.map +1 -1
  99. package/dist/components/toast/toast.js +1 -1
  100. package/dist/components/toast/toast.js.map +1 -1
  101. package/dist/components/tooltip/index.d.ts +1 -1
  102. package/dist/components/tooltip/index.js.map +1 -1
  103. package/dist/components/tooltip/style.js.map +1 -1
  104. package/dist/scrollbars/index.d.ts +8 -8
  105. package/dist/scrollbars/index.js.map +1 -1
  106. package/dist/scrollbars/internal.js.map +1 -1
  107. package/dist/shared/element-height/index.js.map +1 -1
  108. package/dist/shared/focus/pointer-focus.js.map +1 -1
  109. package/dist/shared/group-management/index.js +5 -1
  110. package/dist/shared/group-management/index.js.map +1 -1
  111. package/dist/shared/label-emptiness/index.js.map +1 -1
  112. package/dist/shared/menu-behavior/index.js.map +1 -1
  113. package/dist/shared/menu-portal/menu-portal.js.map +1 -1
  114. package/dist/shared/menu-portal/menu-tree.js.map +1 -1
  115. package/dist/shared/option-portal/combobox-shell.js.map +1 -1
  116. package/dist/shared/option-portal/index.js.map +1 -1
  117. package/dist/shared/overlay/anchored-panel.js.map +1 -1
  118. package/dist/shared/overlay/native-dialog-presence.js.map +1 -1
  119. package/dist/shared/overlay/nested-drawer-layers.js.map +1 -1
  120. package/dist/shared/overlay/open-overlay.js.map +1 -1
  121. package/dist/shared/overlay/overlay.js.map +1 -1
  122. package/dist/shared/overlay/portal.js.map +1 -1
  123. package/dist/shared/overlay/presence.js.map +1 -1
  124. package/dist/shared/scroll-lock/scroll-lock.js.map +1 -1
  125. package/dist/shared/theme/root-sync.js.map +1 -1
  126. package/package.json +2 -2
  127. package/dist/icons/generated/heroicons-arrow-down-16-solid.d.ts +0 -6
  128. package/dist/icons/generated/heroicons-arrow-up-16-solid.d.ts +0 -6
  129. package/dist/icons/generated/jam-volume.d.ts +0 -8
  130. package/dist/icons/generated/jam-watch-f.d.ts +0 -8
  131. package/dist/icons/generated/ooui-check.d.ts +0 -6
  132. package/dist/icons/generated/radix-icons-check.d.ts +0 -6
  133. package/dist/icons/generated/si-check-alt-fill.d.ts +0 -6
@@ -1 +1 @@
1
- {"version":3,"file":"style.js","names":[],"sources":["../../../src/components/dialog/style.css?inline"],"sourcesContent":["*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\n:host {\n display: contents;\n}\n\ndialog {\n /* 必须显式 position: fixed:UA 的 dialog:modal 默认就是 fixed,但作者样式若出现\n position: relative(本轮单层重构曾引入)会把它覆盖回文档流——top layer 里的\n modal dialog 变成按文档流定位:渲染在页面顶部(用户看到的“残影”)且跟随页面\n 滚动(“没有固定居中”)。显式写死 fixed 防御未来任何作者类再覆盖。 */\n position: fixed;\n\n /* 本元素不参与 opacity 过渡:opacity < 1 会让 dialog 成为 backdrop root,后代\n backdrop-filter 在该过渡期间被整体禁用。玻璃卡片(.wui-dialog-body)自身\n opacity + backdrop-filter 插值过渡(own opacity 不禁用自身 blur,见下)。 */\n\n /* 进场由大到小收缩(1.1 → 1),退场反向。取消 .is-visible 后回落到本条起点值,\n enter/exit 共用同一 token。方向与浮动面板的 --wui-scale-enter 相反,故单独成 token。 */\n transform: scale(var(--wui-dialog-scale-enter, 1.1));\n\n overflow: visible;\n\n box-sizing: border-box;\n width: min(90vw, var(--wui-dialog-width, 360px));\n\n /* 整卡兜底:防卡片高过视口。真正的内容区上限在 .desc(见下),这里只接住\n 「视口本身很矮、chrome + 内容区上限也放不下」的情况。字面量而非 token,\n 免得与 .desc 抢同一个数值——共用时外层必然先触发(它是整卡约束),\n .desc 的 max-height 就成了永远轮不到的死约束。dvh 增强见文件末尾。 */\n max-height: 100vh;\n padding: 0;\n border: none;\n\n font-family: inherit;\n color: var(--wui-color-text, #1b1b1b);\n\n background: transparent;\n\n transition: transform var(--wui-duration-dialog-enter, 320ms) var(--wui-ease-dialog, cubic-bezier(0.2, 0, 0, 1));\n\n &::backdrop {\n opacity: 0;\n background: var(--wui-dialog-overlay-bg, var(--wui-color-backdrop, rgb(0 0 0 / 0.12)));\n\n /* 与卡片共用同一条 dialog 缓动:用内置 ease 会让遮罩在卡片落位后才走完大半透明度。 */\n transition: opacity var(--wui-duration-dialog-enter, 320ms) var(--wui-ease-dialog, cubic-bezier(0.2, 0, 0, 1));\n }\n}\n\n/* 动态视口高度兜底:移动端工具栏收放时不让 dialog 超出可见区域。\n 放在 @supports 内而非与上一条并列,是因为 var() 的回退值要到计算值阶段才替换;\n 不支持 dvh 的引擎会在该阶段拿到非法值,使整条声明变成 invalid-at-computed-value-time\n 而丢失 max-height。并列两条普通声明则由解析期丢弃,行为不同。dialog 这条是字面量,\n 并列也不会丢,但统一放在 @supports 内,免得两条规则看起来像两种不同的兜底策略。\n\n `.desc` 的 dvh 增强不在这里:它必须排在文件后面那条 `.desc` 规则之后才生效,\n 同特异度下靠后者胜。放在这里会被后面那条的 `90vh` 回退静默覆盖。 */\n@supports (height: 100dvh) {\n dialog {\n max-height: 100dvh;\n }\n}\n\n/* ===== 高度约束下移到内容区(chrome 由内容撑开) =====\n\n 宿主过去必须复述一个会变的 chrome 高度:AddDialog / RestoreDialog 都写\n `height: min(calc(82vh - 108px), calc(var(--wui-dialog-max-height, 560px) - 108px))`\n (改动前的写法,见 base b6b7eb34;这两处宿主已按新语义转换),\n 而那 108 里的 footer 一项就是 --wui-control-size——触摸端\n `@media (pointer: coarse)` 把它从 36px 抬到 40px,chrome 随之长高,宿主的常数立刻腐坏。\n\n 现在 header / footer 完全由内容撑开,上限只落在 .desc。修掉的是「宿主必须知道 chrome\n 才能让内容区滚起来」:改动前 .desc 既没有上限也没有 overflow,内容超高时直接把卡片\n 顶出 dialog 盒子,唯一能滚的地方是宿主自己用 `min(calc(82vh - 108px), …)` 之类的\n 内层 height 撑出来的那一块。现在上限与滚动都归 .desc,宿主不必再为「让它滚」而算。\n\n 但「宿主不再需要知道 chrome 多高」并不成立,改成这样写会误导后人。--wui-dialog-max-height\n 的语义是**内容区**上限:宿主若要给整卡定上限,仍然得自己量出 chrome 再从 token 里减掉\n (AddDialog / RestoreDialog 减 142,SettingsDialog 减 106,三处不能互相照抄)。把整卡\n 上限原样写进 token,卡片会比预期高出一个 chrome。\n\n 实测(Chromium + WebKit,见 __tests__/dialog-content-height.browser.spec.ts):本组 CSS\n 只对 **默认模式**生效——headless 模式(默认槽渲染进 .wui-dialog-content、没有 .desc)的**卡片尺寸**不变。\n 但位置不保证:`dialog` 的整卡兜底从 min(90vh, 90dvh) 收紧到字面量 100vh,modal dialog\n 按自身盒子在视口里居中、卡片从盒子顶部起画,盒子更高 → 卡片在视口内上移。内容接近视口\n 高度时位移可观测(reviewer 实测 52px / 102px / 25.16px),只有内容远矮于视口时位移为 0\n (两个 demo 正是落在这一档,巧合而非保证)。 */\n\n/* dialog 不是 flex 容器时 .desc 的 height:100% 拿不到确定高度(:host 是 display:contents,\n dialog 自身默认 block),宿主的内层 grid 因此撑不满、两栏不等高。模式判定按 DOM 形状\n 而不是 attribute,理由见下方 grid 规则上的注释。 */\ndialog:has(.desc) {\n display: flex;\n flex-direction: column;\n}\n\n/* 模态表面的焦点环抑制(<dialog> 壳 + 面板滚动区)集中在 assets/modal-surface.css,见组件 index.ts 的 static styles。 */\n\ndialog[open].is-visible {\n transform: scale(1);\n}\n\ndialog[open].is-closing {\n pointer-events: none;\n transition-duration: var(--wui-duration-dialog-exit, 260ms);\n}\n\ndialog.is-closing::backdrop {\n transition-duration: var(--wui-duration-dialog-exit, 260ms);\n}\n\ndialog[open].is-visible::backdrop {\n opacity: 1;\n}\n\n@media (prefers-reduced-motion: reduce) {\n dialog,\n dialog[open].is-visible {\n transform: none;\n transition-duration: var(--wui-duration-feedback, 100ms);\n }\n}\n\n/* ===== 玻璃卡片主体(样式由 glass.css 提供) ===== */\n\n/* 单层玻璃:卡片自身承担背景/阴影/backdrop-filter。blur(0px) ↔ blur(4px) 平滑插值\n (none↔blur 不可插值才是当年生硬跳变的根因),own opacity 过渡不禁用自身 blur,\n 因此卡片直接 opacity + backdrop-filter 同步过渡即可,无需双层。 */\n.wui-dialog-body {\n --wui-internal-dialog-close-top: var(--wui-dialog-close-top, 20px);\n --wui-internal-dialog-close-right: var(--wui-dialog-close-right, 20px);\n --wui-internal-glass-shadow: var(--wui-shadow-overlay, 2px 16px 40px rgb(0 0 0 / 0.4));\n --wui-glass-corner-radius: var(--wui-radius-overlay, 28px);\n\n /* 卡片只吃 --wui-dialog-padding 的 block 分量;inline 分量让给各分区\n (.header / .desc / .wui-dialog-footer,由 --wui-dialog-inline-padding 控制),\n .desc 这个滚动容器的边因此与卡片内沿重合,滚动条贴边(详见 .desc)。 */\n padding: var(--wui-dialog-padding, var(--wui-space-5, 20px) var(--wui-space-6, 24px) var(--wui-space-6, 24px));\n padding-inline: 0;\n border-radius: var(--wui-radius-overlay, 28px);\n\n opacity: 0;\n background-color: var(--wui-dialog-bg, var(--wui-color-surface-overlay, rgb(248 248 248 / 0.92)));\n backdrop-filter: blur(0) saturate(160%) brightness(var(--wui-glass-brightness, 1.06));\n\n /* transition 列表需手写 -webkit-backdrop-filter:lightningcss 不会在 transition\n 的 value 内追加 -webkit 项,而 iOS 16.4 上 blur 过渡依赖它(属性前缀会自动生成)。 */\n transition:\n opacity var(--wui-duration-dialog-enter, 320ms) var(--wui-ease-dialog, cubic-bezier(0.2, 0, 0, 1)),\n -webkit-backdrop-filter var(--wui-duration-dialog-enter, 320ms) var(--wui-ease-dialog, cubic-bezier(0.2, 0, 0, 1)),\n backdrop-filter var(--wui-duration-dialog-enter, 320ms) var(--wui-ease-dialog, cubic-bezier(0.2, 0, 0, 1));\n}\n\n/* 卡片改 grid 容器:header 与 footer 走 auto(内容撑开),.desc 走 minmax(0,1fr)。\n minmax 的 0 是 .desc 能收缩的前提;配合 .desc 的 min-height:0 与 overflow-y:auto,\n 内容超高时滚动发生在内容区内部,而不是把卡片顶出 dialog 盒子。\n\n 单列而非与基础 .wui-dialog-body 合并:`:has(.desc)` 与类选择器叠加的特异度高于单个类\n 选择器,合并会让「仅默认模式变 grid」变成「所有模式都变 grid」,那会改掉 headless\n 模式的几何(实测高内容 headless 的卡片会从 3044 被夹到 896)。位置也受 stylelint\n no-descending-specificity 约束:须排在基础规则之后、dialog.is-closing 之前。\n\n 模式判定按 **DOM 形状**(`:has(.desc)`)而不是 attribute(`:host(:not([headless]))`):\n 外部直接 `setAttribute('headless', '')` 时 attribute 立刻翻转,Lit 要等下一次 update 才\n 换模板——那个窗口里 DOM 仍是默认形状。`:has(.desc)` 描述的就是当下的 DOM,与它自洽;\n `:host([headless])` 会按 attribute 提前把卡片按 headless 几何渲染,而 DOM 里还带着\n `.desc`。改成 drawer 那种 attribute 形式还要把承重规则上移(`dialog.is-closing\n .wui-dialog-body`;焦点环抑制那条已挪去 `assets/modal-surface.css`)来满足\n no-descending-specificity,收益不抵风险。 */\n.wui-dialog-body:has(.desc) {\n display: grid;\n grid-template-rows: auto minmax(0, 1fr) auto;\n\n /* 这条 min-height: 0 是承重的(`.desc` 那条不是):卡片是 `dialog` 这个 column flex\n 容器的 flex 项,`min-height: auto` 会把它垫到内容高度,视口的 100dvh 兜底就再也\n 夹不住。删掉后 token=5000px 时卡片实测 3154、dialog 896。 */\n min-height: 0;\n}\n\n/* ===== headless 模式:内容区是卡片内部的滚动层 =====\n\n headless 模式没有 header / .desc / footer,卡片就是那个滚动层,所以这里要同时守住两件事:\n 内容不高时卡片贴合内容(与基础规则一致),内容高过视口时把上限交给视口、把溢出交给\n 滚动。少了后半件,卡片会一路长到内容高度并溢出 dialog 盒子;dialog 是 overflow:\n visible(阴影与关闭按钮不能由同尺寸的原生 dialog 裁),溢出的那截落在视口之下,\n 既看不到也够不着。\n\n 上限取与整卡兜底相同的字面量,而不是 `--wui-dialog-max-height`:后者的公开语义是\n `.desc` 的高度(README 有记录),headless 模式根本没有 `.desc`,套上去会凭空改掉\n 这档的既有尺寸语义。\n\n 滚动单独成层而不是让卡片自己滚:关闭按钮是卡片的绝对定位直接子元素,卡片一旦自己\n 滚动,按钮会跟着内容一起滚走。padding 也跟着挪进内层,让内容与滚动容器的裁剪边\n (padding 外沿)留出余量——贴边的控件焦点环才不会被裁成残条;内层没有滚动条槽的\n 引擎里,这圈余量与卡片直接持 padding 时逐像素相同。\n\n headless 的收窄也取结构(`:has(> .wui-dialog-content)`)而不是 `:host([headless])`,与上方\n grid 规则同一条理由:判定跟 DOM 形状走。attribute 与 DOM 在直接 setAttribute 的那个\n 窗口里会短暂分叉,结构选择器始终与 DOM 自洽。 */\n.wui-dialog-body:has(> .wui-dialog-content) {\n overflow: hidden;\n display: grid;\n grid-template-rows: minmax(0, 1fr);\n\n max-height: 100vh;\n padding: 0;\n}\n\n@supports (height: 100dvh) {\n .wui-dialog-body:has(> .wui-dialog-content) {\n max-height: 100dvh;\n }\n}\n\n.wui-dialog-content {\n overflow-y: auto;\n min-height: 0;\n padding: var(--wui-dialog-padding, var(--wui-space-5, 20px) var(--wui-space-6, 24px) var(--wui-space-6, 24px));\n}\n\ndialog.is-closing .wui-dialog-body {\n transition-duration: var(--wui-duration-dialog-exit, 260ms);\n}\n\n:host([closable]) .wui-dialog-body {\n /*\n * headless 模式的关闭按钮浮在卡片右上角,需要一个定位参照,所以显式重述一次\n * position: relative。\n *\n * 实际提供这个上下文的是卡片自带的 .wui-glass(glass.css 的 .wui-glass 已有\n * position: relative),所以这条声明目前是冗余的,写在这里是为了不依赖\n * glass.css 的内部实现——若哪天 glass 改成非定位元素,这条仍然兜得住。\n * 它不是本轮为 closable 建立的包含块边界:body 上的 wui-glass class 与\n * static styles 里的 glass 都早于本轮存在。\n */\n position: relative;\n}\n\ndialog[open].is-visible .wui-dialog-body {\n opacity: 1;\n backdrop-filter: blur(4px) saturate(160%) brightness(var(--wui-glass-brightness, 1.06));\n}\n\n/* ===== 关闭按钮(绝对定位,两种内容模式同一套规则)=====\n\n 偏移由公开 token --wui-dialog-close-top / --wui-dialog-close-right 控制,默认都是字面量\n 20px。默认值不再经过 --wui-space-*:这两个 token 的参照系是卡片边缘,而不是相邻元素\n 之间的间距节奏,消费方调整主题间距时不应意外移动关闭按钮。dialog 本轮也不再与 drawer\n 的 16/16 对齐;--wui-drawer-close-* 保持不变。\n\n 两个公开 token 先在正文容器上解析成同一份内部变量,按钮定位和标题避让都读这份结果,\n 避免只改一个兜底字面量后按钮与标题各自按不同默认值计算。\n\n 两种模式下它都是卡片的直接子元素:绝对定位的包含块是 .wui-dialog-body\n (:host([closable]) 给了它 position: relative),top/right 因此量到卡片 padding box\n 的右上角,与 chrome 带的 padding 无关。 */\n.wui-dialog-close {\n position: absolute;\n z-index: 3;\n top: var(--wui-internal-dialog-close-top);\n right: var(--wui-internal-dialog-close-right);\n}\n\n/*\n * chrome 带:`header` 槽 + `heading` 属性 fallback,与 drawer 的 header 同形。\n * 卡片把 inline padding 让给各分区后(见 .wui-dialog-body),这条带自带水平 padding,\n * 否则内容会贴到卡片内沿。带里装的不一定是标题——消费方可以放整条工具条\n * (SettingsDialog 就是),所以用 flex / space-between 让多个子项排布;fallback 的\n * .wui-dialog-heading 与带共用 title 级排版,与 drawer 的 .wui-drawer-heading 对齐。\n * 带为空(槽无分配元素且 heading 为空)时隐藏,由 index.ts 的 _showHeader 决定。\n */\n.header {\n display: flex;\n gap: var(--wui-space-3, 12px);\n align-items: center;\n justify-content: space-between;\n\n margin: 0 0 var(--wui-dialog-title-gap, var(--wui-space-4, 16px));\n padding-inline: var(--wui-dialog-inline-padding, var(--wui-space-6, 24px));\n\n font-size: var(--wui-font-size-title, 18px);\n font-weight: var(--wui-font-weight-semibold, 600);\n line-height: var(--wui-line-height-tight, 1.2);\n color: var(--wui-color-text, #1b1b1b);\n}\n\n.header[hidden] {\n display: none;\n}\n\n@media (prefers-reduced-motion: reduce) {\n dialog.is-closing .wui-dialog-body,\n dialog[open].is-visible .wui-dialog-body {\n transition-duration: var(--wui-duration-feedback, 100ms);\n }\n}\n\n/* 标题避让:关闭按钮绝对定位、不占 chrome 带的位,长标题会一直排到按钮下面。\n 预留量取「按钮右偏移 + 按钮宽 + 呼吸间距」,而不是只减掉带自身的 padding——这样即便\n 消费者把 --wui-dialog-inline-padding 调到 0,内容右缘也不会越过按钮左缘;跟随\n --wui-dialog-close-right 变化;这里复用正文容器上已解析的内部右侧偏移,与按钮定位同源。\n 26px 对应 index.ts 里按钮的 size=\"26\",改按钮尺寸时一并改这里。\n 只在 closable 时生效:没有按钮就不必留白。 */\n:host([closable]) .header {\n padding-inline-end: calc(var(--wui-internal-dialog-close-right) + 26px + var(--wui-space-3, 12px));\n}\n\n.desc {\n /* 横向显式 clip 而非留默认的 visible:一旦本轴成为滚动容器,另一轴的 visible 会被\n 计算成 auto,宿主内容横向溢出时就会多出一条不该出现的横向滚动条。\n Chromium / WebKit 实测:clip 与本规则下面的 auto 配对时计算值会降级成 hidden\n (仍可被脚本滚动)。Firefox ≥128 不做这个降级、保持 clip,但仓库的 browser-mode\n 项目只跑 chromium,这句未经本仓 CI 证实。两个引擎都不画横向滚动条,所以这里\n 只需要「不是 auto」;断言写成 not.toBe('auto') 而非 toBe('clip'),否则会在\n 降级的引擎上误报。 */\n overflow-x: clip;\n overflow-y: auto;\n\n /* 承重的是上面那条 `overflow-y: auto`:按 CSS Sizing 3,溢出轴不为 visible 的\n grid 项其自动最小尺寸就是 0,`.desc` 无需再显式归零(实测删掉本条测试仍全绿,\n 改成 `auto auto auto` 也一样)。这里保留是防御性的:哪天 `overflow-y` 被改回\n visible,显式归零仍在,minmax(0,1fr) 才不会被顶破。 */\n min-height: 0;\n\n /* 内容区上限 = token + ring 余量:token 语义是内容区高度,ring 不该从它里面扣。\n token 只挂在这里,不与 dialog 的 max-height 共用——共用时外层(整卡)必然先触发。 */\n max-height: calc(var(--wui-dialog-max-height, 90vh) + var(--wui-dialog-desc-focus-padding, 6px) * 2);\n\n /* ===== focus-ring 余量(block 轴)=====\n\n .desc 既是滚动容器又是裁剪盒(clip),贴在内容区任一沿的控件,其 ring\n (offset 2 + width 3 = 5px)会被 padding box 裁掉;block 轴留 6px 余 1px,配一条\n 等量负 margin 把这 6px 从外部间距里扣回去——标题带→内容、内容→footer 的视觉间距\n 与改动前逐像素相同。\n\n inline 轴不再走 focus 余量:卡片的 inline padding 整段移进了本容器(padding-inline\n 取 --wui-dialog-inline-padding,卡片的 inline 分量归零,见 .wui-dialog-body),\n 滚动口因此与卡片内沿重合(滚动条贴边,与 headless 模式 / drawer 一致)。24px 的 inline\n padding 远大于 5px ring,左右焦点环不再被裁;也无需再配负 margin——content box 的\n 可用宽度与改动前相同(卡片原本也是同一档 inline padding),否则 AddDialog 那种两栏\n grid 的 drop zone 会换行(实测见 __tests__/dialog-content-height.browser.spec.ts 的\n 「全宽控件的 focus ring 不被内容区裁掉,且宿主拿到的可用宽度不被改窄」)。\n\n 取舍,是取舍不是疏漏:负 margin 让 `.desc` 的 border box 在 block 轴溢出滚动内容区,\n 所以底部那圈 ring 余量不随内容滚动——滚到最底时 footer 那一侧仍留得住 padding,而\n 内容末端到 `.desc` 下沿的 desc-gap 恒定,不跟着内容走。 */\n margin-top: calc(-1 * var(--wui-dialog-desc-focus-padding, 6px));\n\n /* margin-bottom 是「desc-gap 减去那对 padding-block」,两个都是公开 token:宿主把\n desc-gap 调到 focus-padding 以下就会算出负 margin(24/8 安全,4/2/0 会算出\n -2/-4/-6)。max() 兜住下界 0——那条路径下内容末端与 footer 会贴在一起,但盒模型\n 与 grid 行分配不进负值区间。 */\n margin-bottom: max(\n 0px,\n calc(var(--wui-dialog-desc-gap, var(--wui-space-6, 24px)) - var(--wui-dialog-desc-focus-padding, 6px))\n );\n margin-inline: 0;\n padding-block: var(--wui-dialog-desc-focus-padding, 6px);\n padding-inline: var(--wui-dialog-inline-padding, var(--wui-space-6, 24px));\n\n font-size: var(--wui-font-size, 14px);\n line-height: var(--wui-line-height-normal, 1.5);\n color: var(--wui-color-text, #1b1b1b);\n}\n\n/* `--wui-dialog-max-height` 是公开 token(README 有记录),语义是**内容区**高度。\n 默认值与整卡兜底同族(90vh / dvh),使未覆写的 dialog 视觉高度与改动前一致:\n 移动端 90vh 可能大于 90dvh(vh 取大视口高),少了 min() 内容区会探到工具栏下面。\n\n 必须紧跟在 `.desc` 之后而非文件开头的 @supports 块里:两者特异度相同(0,1,0),\n 靠后者胜。实测把本条挪到前面后,`.desc` 的计算值会退回 90vh。 */\n@supports (height: 100dvh) {\n .desc {\n max-height: calc(var(--wui-dialog-max-height, min(90vh, 90dvh)) + var(--wui-dialog-desc-focus-padding, 6px) * 2);\n }\n}\n\n.wui-dialog-footer {\n display: flex;\n flex-direction: column;\n gap: var(--wui-dialog-footer-gap, 10px);\n justify-content: var(--wui-dialog-footer-justify, flex-end);\n\n /* 卡片 inline padding 归零后由本区自带水平 padding,与 .header / .desc 同源。 */\n padding-inline: var(--wui-dialog-inline-padding, var(--wui-space-6, 24px));\n\n & ::slotted(web-ui-button) {\n display: flex;\n width: 100%;\n }\n}\n\n:host([horizontal]) .wui-dialog-footer {\n flex-direction: row;\n gap: var(--wui-space-3, 12px);\n justify-content: var(--wui-dialog-footer-justify, center);\n\n & ::slotted(web-ui-button:not([full])) {\n width: auto;\n }\n\n & ::slotted(web-ui-button[full]) {\n flex: 1;\n min-width: 0;\n }\n}\n"],"mappings":""}
1
+ {"version":3,"file":"style.js","names":[],"sources":["../../../src/components/dialog/style.css?inline"],"sourcesContent":["*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\n:host {\n display: contents;\n}\n\ndialog {\n /* 必须显式 position: fixed:UA 的 dialog:modal 默认就是 fixed,但作者样式若出现\n position: relative(本轮单层重构曾引入)会把它覆盖回文档流——top layer 里的\n modal dialog 变成按文档流定位:渲染在页面顶部(用户看到的“残影”)且跟随页面\n 滚动(“没有固定居中”)。显式写死 fixed 防御未来任何作者类再覆盖。 */\n position: fixed;\n\n /* 本元素不参与 opacity 过渡:opacity < 1 会让 dialog 成为 backdrop root,后代\n backdrop-filter 在该过渡期间被整体禁用。玻璃卡片(.wui-dialog-body)自身\n opacity + backdrop-filter 插值过渡(own opacity 不禁用自身 blur,见下)。 */\n\n /* 进场由大到小收缩(1.1 → 1),退场反向。取消 .is-visible 后回落到本条起点值,\n enter/exit 共用同一 token。方向与浮动 panel 的 --wui-scale-enter 相反,故单独成 token。 */\n transform: scale(var(--wui-dialog-scale-enter, 1.1));\n\n overflow: visible;\n\n box-sizing: border-box;\n width: min(90vw, var(--wui-dialog-width, 360px));\n\n /* 整卡兜底:防卡片高过视口。真正的内容区上限在 .desc(见下),这里只接住\n 「视口本身很矮、chrome + 内容区上限也放不下」的情况。字面量而非 token,\n 免得与 .desc 抢同一个数值——共用时外层必然先触发(它是整卡约束),\n .desc 的 max-height 就成了永远轮不到的死约束。dvh 增强见文件末尾。 */\n max-height: 100vh;\n padding: 0;\n border: none;\n\n font-family: inherit;\n color: var(--wui-color-text, #1b1b1b);\n\n background: transparent;\n\n transition: transform var(--wui-duration-dialog-enter, 320ms) var(--wui-ease-dialog, cubic-bezier(0.2, 0, 0, 1));\n\n &::backdrop {\n opacity: 0;\n background: var(--wui-dialog-overlay-bg, var(--wui-color-backdrop, rgb(0 0 0 / 0.12)));\n\n /* 与卡片共用同一条 dialog 缓动:用内置 ease 会让 backdrop 在卡片落位后才走完大半透明度。 */\n transition: opacity var(--wui-duration-dialog-enter, 320ms) var(--wui-ease-dialog, cubic-bezier(0.2, 0, 0, 1));\n }\n}\n\n/* 动态视口高度兜底:移动端工具栏收放时不让 dialog 超出可见区域。\n 放在 @supports 内而非与上一条并列,是因为 var() 的回退值要到计算值阶段才替换;\n 不支持 dvh 的引擎会在该阶段拿到非法值,使整条声明变成 invalid-at-computed-value-time\n 而丢失 max-height。并列两条普通声明则由解析期丢弃,行为不同。dialog 这条是字面量,\n 并列也不会丢,但统一放在 @supports 内,免得两条规则看起来像两种不同的兜底策略。\n\n `.desc` 的 dvh 增强不在这里:它必须排在文件后面那条 `.desc` 规则之后才生效,\n 同特异度下靠后者胜。放在这里会被后面那条的 `90vh` 回退静默覆盖。 */\n@supports (height: 100dvh) {\n dialog {\n max-height: 100dvh;\n }\n}\n\n/* ===== 高度约束下移到内容区(chrome 由内容撑开) =====\n\n 宿主过去必须复述一个会变的 chrome 高度:AddDialog / RestoreDialog 都写\n `height: min(calc(82vh - 108px), calc(var(--wui-dialog-max-height, 560px) - 108px))`\n (改动前的写法,见 base b6b7eb34;这两处宿主已按新语义转换),\n 而那 108 里的 footer 一项就是 --wui-control-size——触摸端\n `@media (pointer: coarse)` 把它从 36px 抬到 40px,chrome 随之长高,宿主的常数立刻腐坏。\n\n 现在 header / footer 完全由内容撑开,上限只落在 .desc。修掉的是「宿主必须知道 chrome\n 才能让内容区滚起来」:改动前 .desc 既没有上限也没有 overflow,内容超高时直接把卡片\n 顶出 dialog 盒子,唯一能滚的地方是宿主自己用 `min(calc(82vh - 108px), …)` 之类的\n 内层 height 撑出来的那一块。现在上限与滚动都归 .desc,宿主不必再为「让它滚」而算。\n\n 但「宿主不再需要知道 chrome 多高」并不成立,改成这样写会误导后人。--wui-dialog-max-height\n 的语义是**内容区**上限:宿主若要给整卡定上限,仍然得自己量出 chrome 再从 token 里减掉\n (AddDialog / RestoreDialog 减 142,SettingsDialog 减 106,三处不能互相照抄)。把整卡\n 上限原样写进 token,卡片会比预期高出一个 chrome。\n\n 实测(Chromium + WebKit,见 __tests__/dialog-content-height.browser.spec.ts):本组 CSS\n 只对 **默认模式**生效——headless 模式(默认槽渲染进 .wui-dialog-content、没有 .desc)的**卡片尺寸**不变。\n 但位置不保证:`dialog` 的整卡兜底从 min(90vh, 90dvh) 收紧到字面量 100vh,modal dialog\n 按自身盒子在视口里居中、卡片从盒子顶部起画,盒子更高 → 卡片在视口内上移。内容接近视口\n 高度时位移可观测(reviewer 实测 52px / 102px / 25.16px),只有内容远矮于视口时位移为 0\n (两个 demo 正是落在这一档,巧合而非保证)。 */\n\n/* dialog 不是 flex 容器时 .desc 的 height:100% 拿不到确定高度(:host 是 display:contents,\n dialog 自身默认 block),宿主的内层 grid 因此撑不满、两栏不等高。模式判定按 DOM 形状\n 而不是 attribute,理由见下方 grid 规则上的注释。 */\ndialog:has(.desc) {\n display: flex;\n flex-direction: column;\n}\n\n/* 模态表面的焦点环抑制(<dialog> 壳 + panel 滚动区)集中在 assets/modal-surface.css,见组件 index.ts 的 static styles。 */\n\ndialog[open].is-visible {\n transform: scale(1);\n}\n\ndialog[open].is-closing {\n pointer-events: none;\n transition-duration: var(--wui-duration-dialog-exit, 260ms);\n}\n\ndialog.is-closing::backdrop {\n transition-duration: var(--wui-duration-dialog-exit, 260ms);\n}\n\ndialog[open].is-visible::backdrop {\n opacity: 1;\n}\n\n@media (prefers-reduced-motion: reduce) {\n dialog,\n dialog[open].is-visible {\n transform: none;\n transition-duration: var(--wui-duration-feedback, 100ms);\n }\n}\n\n/* ===== 玻璃卡片主体(样式由 glass.css 提供) ===== */\n\n/* 单层玻璃:卡片自身承担背景/阴影/backdrop-filter。blur(0px) ↔ blur(4px) 平滑插值\n (none↔blur 不可插值才是当年生硬跳变的根因),own opacity 过渡不禁用自身 blur,\n 因此卡片直接 opacity + backdrop-filter 同步过渡即可,无需双层。 */\n.wui-dialog-body {\n --wui-internal-dialog-close-top: var(--wui-dialog-close-top, 20px);\n --wui-internal-dialog-close-right: var(--wui-dialog-close-right, 20px);\n --wui-internal-glass-shadow: var(--wui-shadow-overlay, 2px 16px 40px rgb(0 0 0 / 0.4));\n --wui-glass-corner-radius: var(--wui-radius-overlay, 28px);\n\n /* 卡片只吃 --wui-dialog-padding 的 block 分量;inline 分量让给各分区\n (.header / .desc / .wui-dialog-footer,由 --wui-dialog-inline-padding 控制),\n .desc 这个滚动容器的边因此与卡片内沿重合,scrollbar 贴边(详见 .desc)。 */\n padding: var(--wui-dialog-padding, var(--wui-space-5, 20px) var(--wui-space-6, 24px) var(--wui-space-6, 24px));\n padding-inline: 0;\n border-radius: var(--wui-radius-overlay, 28px);\n\n opacity: 0;\n background-color: var(--wui-dialog-bg, var(--wui-color-surface-overlay, rgb(248 248 248 / 0.92)));\n backdrop-filter: blur(0) saturate(160%) brightness(var(--wui-glass-brightness, 1.06));\n\n /* transition 列表需手写 -webkit-backdrop-filter:lightningcss 不会在 transition\n 的 value 内追加 -webkit 项,而 iOS 16.4 上 blur 过渡依赖它(属性前缀会自动生成)。 */\n transition:\n opacity var(--wui-duration-dialog-enter, 320ms) var(--wui-ease-dialog, cubic-bezier(0.2, 0, 0, 1)),\n -webkit-backdrop-filter var(--wui-duration-dialog-enter, 320ms) var(--wui-ease-dialog, cubic-bezier(0.2, 0, 0, 1)),\n backdrop-filter var(--wui-duration-dialog-enter, 320ms) var(--wui-ease-dialog, cubic-bezier(0.2, 0, 0, 1));\n}\n\n/* 卡片改 grid 容器:header 与 footer 走 auto(内容撑开),.desc 走 minmax(0,1fr)。\n minmax 的 0 是 .desc 能收缩的前提;配合 .desc 的 min-height:0 与 overflow-y:auto,\n 内容超高时滚动发生在内容区内部,而不是把卡片顶出 dialog 盒子。\n\n 单列而非与基础 .wui-dialog-body 合并:`:has(.desc)` 与类选择器叠加的特异度高于单个类\n 选择器,合并会让「仅默认模式变 grid」变成「所有模式都变 grid」,那会改掉 headless\n 模式的几何(实测高内容 headless 的卡片会从 3044 被夹到 896)。位置也受 stylelint\n no-descending-specificity 约束:须排在基础规则之后、dialog.is-closing 之前。\n\n 模式判定按 **DOM 形状**(`:has(.desc)`)而不是 attribute(`:host(:not([headless]))`):\n 外部直接 `setAttribute('headless', '')` 时 attribute 立刻翻转,Lit 要等下一次 update 才\n 换模板——那个窗口里 DOM 仍是默认形状。`:has(.desc)` 描述的就是当下的 DOM,与它自洽;\n `:host([headless])` 会按 attribute 提前把卡片按 headless 几何渲染,而 DOM 里还带着\n `.desc`。改成 drawer 那种 attribute 形式还要把承重规则上移(`dialog.is-closing\n .wui-dialog-body`;焦点环抑制那条已挪去 `assets/modal-surface.css`)来满足\n no-descending-specificity,收益不抵风险。 */\n.wui-dialog-body:has(.desc) {\n display: grid;\n grid-template-rows: auto minmax(0, 1fr) auto;\n\n /* 这条 min-height: 0 是承重的(`.desc` 那条不是):卡片是 `dialog` 这个 column flex\n 容器的 flex 项,`min-height: auto` 会把它垫到内容高度,视口的 100dvh 兜底就再也\n 夹不住。删掉后 token=5000px 时卡片实测 3154、dialog 896。 */\n min-height: 0;\n}\n\n/* ===== headless 模式:内容区是卡片内部的滚动层 =====\n\n headless 模式没有 header / .desc / footer,卡片就是那个滚动层,所以这里要同时守住两件事:\n 内容不高时卡片贴合内容(与基础规则一致),内容高过视口时把上限交给视口、把溢出交给\n 滚动。少了后半件,卡片会一路长到内容高度并溢出 dialog 盒子;dialog 是 overflow:\n visible(阴影与关闭 button 不能由同尺寸的原生 dialog 裁),溢出的那截落在视口之下,\n 既看不到也够不着。\n\n 上限取与整卡兜底相同的字面量,而不是 `--wui-dialog-max-height`:后者的公开语义是\n `.desc` 的高度(README 有记录),headless 模式根本没有 `.desc`,套上去会凭空改掉\n 这档的既有尺寸语义。\n\n 滚动单独成层而不是让卡片自己滚:关闭 button 是卡片的绝对定位直接子元素,卡片一旦自己\n 滚动,button 会跟着内容一起滚走。padding 也跟着挪进内层,让内容与滚动容器的裁剪边\n (padding 外沿)留出余量——贴边的控件焦点环才不会被裁成残条;内层没有 scrollbar 槽的\n 引擎里,这圈余量与卡片直接持 padding 时逐像素相同。\n\n headless 的收窄也取结构(`:has(> .wui-dialog-content)`)而不是 `:host([headless])`,与上方\n grid 规则同一条理由:判定跟 DOM 形状走。attribute 与 DOM 在直接 setAttribute 的那个\n 窗口里会短暂分叉,结构选择器始终与 DOM 自洽。 */\n.wui-dialog-body:has(> .wui-dialog-content) {\n overflow: hidden;\n display: grid;\n grid-template-rows: minmax(0, 1fr);\n\n max-height: 100vh;\n padding: 0;\n}\n\n@supports (height: 100dvh) {\n .wui-dialog-body:has(> .wui-dialog-content) {\n max-height: 100dvh;\n }\n}\n\n.wui-dialog-content {\n overflow-y: auto;\n min-height: 0;\n padding: var(--wui-dialog-padding, var(--wui-space-5, 20px) var(--wui-space-6, 24px) var(--wui-space-6, 24px));\n}\n\ndialog.is-closing .wui-dialog-body {\n transition-duration: var(--wui-duration-dialog-exit, 260ms);\n}\n\n:host([closable]) .wui-dialog-body {\n /*\n * headless 模式的关闭 button 浮在卡片右上角,需要一个定位参照,所以显式重述一次\n * position: relative。\n *\n * 实际提供这个上下文的是卡片自带的 .wui-glass(glass.css 的 .wui-glass 已有\n * position: relative),所以这条声明目前是冗余的,写在这里是为了不依赖\n * glass.css 的内部实现——若哪天 glass 改成非定位元素,这条仍然兜得住。\n * 它不是本轮为 closable 建立的包含块边界:body 上的 wui-glass class 与\n * static styles 里的 glass 都早于本轮存在。\n */\n position: relative;\n}\n\ndialog[open].is-visible .wui-dialog-body {\n opacity: 1;\n backdrop-filter: blur(4px) saturate(160%) brightness(var(--wui-glass-brightness, 1.06));\n}\n\n/* ===== 关闭 button(绝对定位,两种内容模式同一套规则)=====\n\n 偏移由公开 token --wui-dialog-close-top / --wui-dialog-close-right 控制,默认都是字面量\n 20px。默认值不再经过 --wui-space-*:这两个 token 的参照系是卡片边缘,而不是相邻元素\n 之间的间距节奏,消费方调整主题间距时不应意外移动关闭 button。dialog 本轮也不再与 drawer\n 的 16/16 对齐;--wui-drawer-close-* 保持不变。\n\n 两个公开 token 先在正文容器上解析成同一份内部变量,button 定位和标题避让都读这份结果,\n 避免只改一个兜底字面量后 button 与标题各自按不同默认值计算。\n\n 两种模式下它都是卡片的直接子元素:绝对定位的包含块是 .wui-dialog-body\n (:host([closable]) 给了它 position: relative),top/right 因此量到卡片 padding box\n 的右上角,与 chrome 带的 padding 无关。 */\n.wui-dialog-close {\n position: absolute;\n z-index: 3;\n top: var(--wui-internal-dialog-close-top);\n right: var(--wui-internal-dialog-close-right);\n}\n\n/*\n * chrome 带:`header` 槽 + `heading` 属性 fallback,与 drawer 的 header 同形。\n * 卡片把 inline padding 让给各分区后(见 .wui-dialog-body),这条带自带水平 padding,\n * 否则内容会贴到卡片内沿。带里装的不一定是标题——消费方可以放整条工具条\n * (SettingsDialog 就是),所以用 flex / space-between 让多个子项排布;fallback 的\n * .wui-dialog-heading 与带共用 title 级排版,与 drawer 的 .wui-drawer-heading 对齐。\n * 带为空(槽无分配元素且 heading 为空)时隐藏,由 index.ts 的 _showHeader 决定。\n */\n.header {\n display: flex;\n gap: var(--wui-space-3, 12px);\n align-items: center;\n justify-content: space-between;\n\n margin: 0 0 var(--wui-dialog-title-gap, var(--wui-space-4, 16px));\n padding-inline: var(--wui-dialog-inline-padding, var(--wui-space-6, 24px));\n\n font-size: var(--wui-font-size-title, 18px);\n font-weight: var(--wui-font-weight-semibold, 600);\n line-height: var(--wui-line-height-tight, 1.2);\n color: var(--wui-color-text, #1b1b1b);\n}\n\n.header[hidden] {\n display: none;\n}\n\n@media (prefers-reduced-motion: reduce) {\n dialog.is-closing .wui-dialog-body,\n dialog[open].is-visible .wui-dialog-body {\n transition-duration: var(--wui-duration-feedback, 100ms);\n }\n}\n\n/* 标题避让:关闭 button 绝对定位、不占 chrome 带的位,长标题会一直排到 button 下面。\n 预留量取「button 右偏移 + button 宽 + 呼吸间距」,而不是只减掉带自身的 padding——这样即便\n 消费者把 --wui-dialog-inline-padding 调到 0,内容右缘也不会越过 button 左缘;跟随\n --wui-dialog-close-right 变化;这里复用正文容器上已解析的内部右侧偏移,与 button 定位同源。\n 26px 对应 index.ts 里 button 的 size=\"26\",改 button 尺寸时一并改这里。\n 只在 closable 时生效:没有 button 就不必留白。 */\n:host([closable]) .header {\n padding-inline-end: calc(var(--wui-internal-dialog-close-right) + 26px + var(--wui-space-3, 12px));\n}\n\n.desc {\n /* 横向显式 clip 而非留默认的 visible:一旦本轴成为滚动容器,另一轴的 visible 会被\n 计算成 auto,宿主内容横向溢出时就会多出一条不该出现的横向 scrollbar。\n Chromium / WebKit 实测:clip 与本规则下面的 auto 配对时计算值会降级成 hidden\n (仍可被脚本滚动)。Firefox ≥128 不做这个降级、保持 clip,但仓库的 browser-mode\n 项目只跑 chromium,这句未经本仓 CI 证实。两个引擎都不画横向 scrollbar,所以这里\n 只需要「不是 auto」;断言写成 not.toBe('auto') 而非 toBe('clip'),否则会在\n 降级的引擎上误报。 */\n overflow-x: clip;\n overflow-y: auto;\n\n /* 承重的是上面那条 `overflow-y: auto`:按 CSS Sizing 3,溢出轴不为 visible 的\n grid 项其自动最小尺寸就是 0,`.desc` 无需再显式归零(实测删掉本条测试仍全绿,\n 改成 `auto auto auto` 也一样)。这里保留是防御性的:哪天 `overflow-y` 被改回\n visible,显式归零仍在,minmax(0,1fr) 才不会被顶破。 */\n min-height: 0;\n\n /* 内容区上限 = token + ring 余量:token 语义是内容区高度,ring 不该从它里面扣。\n token 只挂在这里,不与 dialog 的 max-height 共用——共用时外层(整卡)必然先触发。 */\n max-height: calc(var(--wui-dialog-max-height, 90vh) + var(--wui-dialog-desc-focus-padding, 6px) * 2);\n\n /* ===== focus-ring 余量(block 轴)=====\n\n .desc 既是滚动容器又是裁剪盒(clip),贴在内容区任一沿的控件,其 ring\n (offset 2 + width 3 = 5px)会被 padding box 裁掉;block 轴留 6px 余 1px,配一条\n 等量负 margin 把这 6px 从外部间距里扣回去——标题带→内容、内容→footer 的视觉间距\n 与改动前逐像素相同。\n\n inline 轴不再走 focus 余量:卡片的 inline padding 整段移进了本容器(padding-inline\n 取 --wui-dialog-inline-padding,卡片的 inline 分量归零,见 .wui-dialog-body),\n 滚动口因此与卡片内沿重合(scrollbar 贴边,与 headless 模式 / drawer 一致)。24px 的 inline\n padding 远大于 5px ring,左右焦点环不再被裁;也无需再配负 margin——content box 的\n 可用宽度与改动前相同(卡片原本也是同一档 inline padding),否则 AddDialog 那种两栏\n grid 的 drop zone 会换行(实测见 __tests__/dialog-content-height.browser.spec.ts 的\n 「全宽控件的 focus ring 不被内容区裁掉,且宿主拿到的可用宽度不被改窄」)。\n\n 取舍,是取舍不是疏漏:负 margin 让 `.desc` 的 border box 在 block 轴溢出滚动内容区,\n 所以底部那圈 ring 余量不随内容滚动——滚到最底时 footer 那一侧仍留得住 padding,而\n 内容末端到 `.desc` 下沿的 desc-gap 恒定,不跟着内容走。 */\n margin-top: calc(-1 * var(--wui-dialog-desc-focus-padding, 6px));\n\n /* margin-bottom 是「desc-gap 减去那对 padding-block」,两个都是公开 token:宿主把\n desc-gap 调到 focus-padding 以下就会算出负 margin(24/8 安全,4/2/0 会算出\n -2/-4/-6)。max() 兜住下界 0——那条路径下内容末端与 footer 会贴在一起,但盒模型\n 与 grid 行分配不进负值区间。 */\n margin-bottom: max(\n 0px,\n calc(var(--wui-dialog-desc-gap, var(--wui-space-6, 24px)) - var(--wui-dialog-desc-focus-padding, 6px))\n );\n margin-inline: 0;\n padding-block: var(--wui-dialog-desc-focus-padding, 6px);\n padding-inline: var(--wui-dialog-inline-padding, var(--wui-space-6, 24px));\n\n font-size: var(--wui-font-size, 14px);\n line-height: var(--wui-line-height-normal, 1.5);\n color: var(--wui-color-text, #1b1b1b);\n}\n\n/* `--wui-dialog-max-height` 是公开 token(README 有记录),语义是**内容区**高度。\n 默认值与整卡兜底同族(90vh / dvh),使未覆写的 dialog 视觉高度与改动前一致:\n 移动端 90vh 可能大于 90dvh(vh 取大视口高),少了 min() 内容区会探到工具栏下面。\n\n 必须紧跟在 `.desc` 之后而非文件开头的 @supports 块里:两者特异度相同(0,1,0),\n 靠后者胜。实测把本条挪到前面后,`.desc` 的计算值会退回 90vh。 */\n@supports (height: 100dvh) {\n .desc {\n max-height: calc(var(--wui-dialog-max-height, min(90vh, 90dvh)) + var(--wui-dialog-desc-focus-padding, 6px) * 2);\n }\n}\n\n.wui-dialog-footer {\n display: flex;\n flex-direction: column;\n gap: var(--wui-dialog-footer-gap, 10px);\n justify-content: var(--wui-dialog-footer-justify, flex-end);\n\n /* 卡片 inline padding 归零后由本区自带水平 padding,与 .header / .desc 同源。 */\n padding-inline: var(--wui-dialog-inline-padding, var(--wui-space-6, 24px));\n\n & ::slotted(web-ui-button) {\n display: flex;\n width: 100%;\n }\n}\n\n:host([horizontal]) .wui-dialog-footer {\n flex-direction: row;\n gap: var(--wui-space-3, 12px);\n justify-content: var(--wui-dialog-footer-justify, center);\n\n & ::slotted(web-ui-button:not([full])) {\n width: auto;\n }\n\n & ::slotted(web-ui-button[full]) {\n flex: 1;\n min-width: 0;\n }\n}\n"],"mappings":""}
@@ -7,7 +7,7 @@ export declare class WebUiDrawer extends LitElement {
7
7
  noScrollLock: boolean;
8
8
  noBackdropClose: boolean;
9
9
  /**
10
- * Controlled 模式下,Escape、遮罩、关闭按钮和拖拽关闭只派发 `open-change` 请求,
10
+ * Controlled 模式下,Escape、backdrop、关闭 button 和拖拽关闭只派发 `open-change` 请求,
11
11
  * 不会自行修改 `open`。Consumer 回写 `open` 后才执行关闭动画。
12
12
  * 程序化 API(show/close/直接赋值 open)不受此模式影响,始终直通。
13
13
  */
@@ -19,7 +19,7 @@ export declare class WebUiDrawer extends LitElement {
19
19
  closable: boolean;
20
20
  /**
21
21
  * Headless 模式:只保留 overlay 基础设施(backdrop、动画、scroll lock、dialog 语义),
22
- * 移除内置 UI(glass 样式、header、close 按钮、footer)。Consumer 自定义内容样式。
22
+ * 移除内置 UI(glass 样式、header、close button、footer)。Consumer 自定义内容样式。
23
23
  */
24
24
  headless: boolean;
25
25
  /**
@@ -28,7 +28,7 @@ export declare class WebUiDrawer extends LitElement {
28
28
  */
29
29
  dialogLabel: string;
30
30
  /**
31
- * 启用拖拽关闭:打开态在抽屉内缘渲染 drag bar(灰色胶囊 + 加宽热区),
31
+ * 启用拖拽关闭:打开态在 drawer 内缘渲染 drag bar(灰色胶囊 + 加宽 drag zone),
32
32
  * 指针拖拽实时跟手,松手按位移与甩动速度判定关闭或弹回。
33
33
  * 关闭态基于原生 dialog 无可见渲染物,因此不支持从关闭态拖拽打开。
34
34
  */
@@ -44,7 +44,7 @@ export declare class WebUiDrawer extends LitElement {
44
44
  private readonly _presence;
45
45
  private readonly _nestedLayers;
46
46
  private readonly _overlay;
47
- /** 当前开启会话的句柄;未开启时为 null。 */
47
+ /** 当前开启会话的 handle;未开启时为 null。 */
48
48
  private _handle;
49
49
  private _dragGesture;
50
50
  private _dragInitialOffset;
@@ -69,14 +69,14 @@ export declare class WebUiDrawer extends LitElement {
69
69
  /**
70
70
  * 上下 placement 的 header / footer 自己接拖拽手势。
71
71
  *
72
- * 为什么不是「把热区铺到 header 上」:那样 header 里的控件会被透明命中层吞掉,
72
+ * 为什么不是「把 drag zone 铺到 header 上」:那样 header 里的控件会被透明命中层吞掉,
73
73
  * 正是 A6 修掉的那条缺陷。这里改成事件层让开——控件照常收自己的事件,拖拽只在
74
74
  * 「落点不是可点控件」时起手。
75
75
  *
76
76
  * 三道闸门任一不成立就不接管:
77
77
  * 1. placement 必须是上下。左右 placement 的拖拽轴是 x,端头在竖边上,header
78
78
  * 既不在可抓边缘上也不该起手(R4 的行为侧)。
79
- * 2. 下层嵌套抽屉整套拖拽提示一起退场。热区与热边靠 CSS 的 pointer-events 退场,
79
+ * 2. 下层嵌套 drawer 整套拖拽提示一起退场。drag zone 与 drag edge 靠 CSS 的 pointer-events 退场,
80
80
  * 事件层必须自己判同一条,否则 header 会成为唯一一条漏网的入口。
81
81
  * 3. 落点路径里有可点控件就让开——控件优先于「这一块可拖」。
82
82
  */
@@ -115,8 +115,8 @@ export declare class WebUiDrawer extends LitElement {
115
115
  * 更新。两条 slotchange 处理器也接不住这条路径——slotchange 只在**分配集合**变化时派发,
116
116
  * 断连期间清空槽位内容时宿主节点本身没动,事件不触发(Chromium 实测)。
117
117
  *
118
- * 漏掉这一半的后果:断连前有 footer、重连时已清空的抽屉留下一节空的 footer。它在 style.css
119
- * 的 padding 下仍有真实高度(实测 32px),于是底部一条点不动的死带,拖拽热区的让位量也照
118
+ * 漏掉这一半的后果:断连前有 footer、重连时已清空的 drawer 留下一节空的 footer。它在 style.css
119
+ * 的 padding 下仍有真实高度(实测 32px),于是底部一条点不动的死带,拖拽 drag zone 的让位量也照
120
120
  * 它算而不归零。
121
121
  *
122
122
  * 判据取 light DOM 子节点而非 `slot.assignedNodes()`:首次连接时 shadow 尚未渲染,slot 元素
@@ -434,7 +434,7 @@ var R = class extends _ {
434
434
  <web-ui-button
435
435
  class="wui-drawer-close"
436
436
  @click=${this._closeFromUser}
437
- aria-label="关闭"
437
+ aria-label="Close"
438
438
  variant="secondary"
439
439
  icon
440
440
  size="26"
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/drawer/index.ts"],"sourcesContent":["import '@/components/button'\nimport '@/components/icon'\n\nimport { html, LitElement, nothing, type PropertyValues, unsafeCSS } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\n\nimport glass from '@/assets/glass.css?inline'\nimport modalSurface from '@/assets/modal-surface.css?inline'\nimport { oouiClose } from '@/icons'\nimport internalScrollbars from '@/scrollbars/internal.css?inline'\nimport { ElementHeightController } from '@/shared/element-height'\nimport { UserChangeController } from '@/shared/events/user-change'\nimport { attachDragGesture, dampOverscroll, type DragGestureHandle } from '@/shared/gesture'\nimport { normalizeLiteral } from '@/shared/normalize'\nimport { dispatchOpenChangeEvent } from '@/shared/open-state'\nimport { defineNativeDialogPresence } from '@/shared/overlay/native-dialog-presence'\nimport { defineNestedDrawerLayers } from '@/shared/overlay/nested-drawer-layers'\nimport { defineOpenOverlay, type OpenOverlayHandle } from '@/shared/overlay/open-overlay'\nimport { defineScrollLockLease } from '@/shared/scroll-lock/scroll-lock'\nimport { prefersReducedMotion } from '@/shared/theme/reduced-motion'\n\nimport style from './style.css?inline'\n\nconst ALLOWED_PLACEMENTS = ['right', 'left', 'top', 'bottom'] as const\n\nexport type DrawerPlacement = (typeof ALLOWED_PLACEMENTS)[number]\n\n/*\n * 释放后的拖拽关闭判定:\n *\n * - 基准校准:位移零点与判定时钟都在**首个 pointermove** 处重置(手势层\n * `calibrateOnFirstMove`,drawer 显式开启),吸收「按下 → 首个 move」之间的空隙——\n * 触摸输入的首个 move 常已相对按下点偏移,不重置会让元素在首个 move 一次性兑现\n * 而跳动。代价是这段位移被丢弃,一次手势至少要累积两个 move 才可能产生位移;\n * `DragMoveInfo/DragEndInfo` 的 `delta*` 与 `duration` 均以该点为零点与起点。\n * - 距离:**自抓取瞬间起的净位移**超过抽屉尺寸的一半(下限 10px)。用净位移而不是\n * 绝对位置:从弹回/收尾途中重新抓取时,抓取瞬间停在哪里都不算「已经拖过一半」。\n * 尺寸量不到时(0)由下限兜底,保证退化场景不会因为阈值 0 而「动一下就关」。\n * - 甩动:整段手势的平均速度**达到** 500px/s(含等号)。用「整段手势」而不是释放\n * 瞬间的滑窗速度:滑窗只描述最后一小段轨迹,反向回扫(先往打开方向拖进量程,\n * 再快速扫回)在释放瞬间同样能凑出很高的滑窗速度,但它不是朝关闭方向的甩动。\n * 净速度 = 净位移 / 整段时长(自校准点起算),分母钳到 50ms;时长为 0 时速度取 0,\n * 而不是按 50ms 兜底放大——直接放大会把「测不到时长」变成一次甩动。\n * - 方向:净位移未朝闭合方向(`<= 0`)时一律弹回。\n * - 改主意:拖拽期内追踪「自折返点起」的位移,一旦自方向确认点回撤满 10px 即标记\n * 取消,甩动分支此后拒绝关闭;位移重新越过关闭距离阈值时清除标记,即\n * 「已经拖过一半」不受回撤影响。见 `_trackSwipeCancel`。\n * - 过冲阻尼:闭合方向全额 1:1 跟手,反向按 `dampOverscroll` 平方根压缩。阻尼作用于\n * **本次手势的增量**,再叠加抓取瞬间的基准位移;对总位移做阻尼会连基准一起压缩,\n * 元素不再接着视觉位置走。\n */\nconst DRAG_CLOSE_RATIO = 0.5\nconst DRAG_MIN_CLOSE_DISTANCE = 10\nconst DRAG_FLICK_VELOCITY = 500\nconst DRAG_MIN_VELOCITY_SPAN_MS = 50\nconst DRAG_REVERSE_CANCEL_THRESHOLD = 10\nconst DRAG_REQUEST_WINDOW_MS = 120\n// 遮罩点击(click 链路)的轻点判定距离:按下起点到 click 坐标的欧氏位移未越过该值\n// 视为轻点,沿用「点遮罩关闭」语义(对齐 _dragCloseThreshold 的 10px 下限量级)。\n// 必须用欧氏长度(而非仅主轴分量):只看主轴会让「主轴不动、垂直大幅滑动」的\n// press–drag–release 链路仍落在窗口内被误读成轻点。\nconst BACKDROP_TAP_DISTANCE = 10\n\n/*\n * header / footer 让开给「可点控件」的选择器(上下 placement 专用)。\n *\n * 上下 placement 的端头整块可拖后,让开靠**逐个控件**在事件层判断,不靠几何切割:\n * 切割要测量每个控件的位置,控件一改位置就失效;逐个控件让开是结构性的。\n *\n * 判据是「这个元素自己就代表一次用户操作」,分三类:\n * - 原生可交互元素与原生可点容器(button / input / label / a[href] / summary …)\n * - 显式可编辑(contenteditable)\n * - 交互语义 role(组件库自绘的 checkbox、tab、menuitem 等没有原生标签)\n * 另加「作者显式给了可聚焦 tabindex」:自定义控件常只靠 tabindex 暴露可操作性。\n *\n * 方向是**保守让开**:判不出来就让开。让开的代价是那一小块起不了拖(用户多点一下),\n * 判错的代价是控件失效(A6 那条回归),两者不对等。\n */\nconst HEADER_FOOTER_INTERACTIVE_SELECTOR = [\n 'button',\n 'input',\n 'select',\n 'textarea',\n 'label',\n 'summary',\n 'a[href]',\n 'audio[controls]',\n 'video[controls]',\n '[contenteditable]:not([contenteditable=\"false\"])',\n '[tabindex]:not([tabindex=\"-1\"])',\n '[role=\"button\"]',\n '[role=\"link\"]',\n '[role=\"checkbox\"]',\n '[role=\"switch\"]',\n '[role=\"radio\"]',\n '[role=\"tab\"]',\n '[role=\"menuitem\"]',\n '[role=\"menuitemcheckbox\"]',\n '[role=\"menuitemradio\"]',\n '[role=\"option\"]',\n '[role=\"textbox\"]',\n '[role=\"combobox\"]',\n '[role=\"slider\"]',\n '[role=\"spinbutton\"]'\n].join(',')\n\n/*\n * 释放后的收尾(弹回打开位 / 滑出到闭合位)由 CSS transition 接管(issue #123):\n * 拖拽期间内联 transform + `transition: none`,松手时把终值与过渡参数写入内联并在\n * 同一次样式重算里解除抑制,回弹即唯一的一次 CSS 过渡。全程无 `element.animate()`、\n * 无 fill、无 JS→CSS 动画交接边界,因此不存在 Safari 采不到 before-change style 的\n * 窗口(r3 的 reflow 烘焙补丁随 WAAPI 一并退役)。\n *\n * 代价是失去弹簧的速度感知轨迹:释放速度只用于估算过渡时长,过冲手感由缓动曲线\n * 近似。两条曲线对应原弹簧预设的阻尼比——close(ζ≈1.05)无过冲,\n * rebound(ζ≈0.74)保留约 3% 过冲。\n */\nconst SETTLE_MIN_MS = 180\nconst SETTLE_MAX_MS = 420\nconst SETTLE_EASE_CLOSE = 'cubic-bezier(0.32, 0.72, 0, 1)'\n// 过冲量由 y1 决定:1.3 时峰值 ≈2.99%,与 ζ≈0.74 的理论过冲(3.15%)对齐;1.12 只剩 0.37%。\nconst SETTLE_EASE_REBOUND = 'cubic-bezier(0.22, 1.3, 0.36, 1)'\n// 估算时长的速度下限(px/s):静止释放时退化为最大时长,避免除零与无限时长。\nconst SETTLE_MIN_VELOCITY = 1\n// transitionend 兜底定时器的宽限:覆盖一帧的调度抖动。\nconst SETTLE_GRACE_MS = 80\n\n/*\n * 拖拽期间由 JS 写入的遮罩透明度变量参与 WAAPI 关键帧。未注册的自定义属性在\n * 关键帧之间是离散插值(半帧跳变),注册为 <number> 后获得线性插值。\n * inherits 必须为 true:::backdrop 只从 originating element 继承「可继承属性」,\n * 注册为不可继承会让变量到不了遮罩,跟手淡出整体失效(Chrome 151 实证)。\n * 注册表是全局的,模块重复执行(HMR)时捕获已注册错误并忽略。\n */\nif (typeof CSS !== 'undefined' && 'registerProperty' in CSS) {\n try {\n CSS.registerProperty({\n name: '--wui-internal-drag-backdrop-opacity',\n syntax: '<number>',\n inherits: true,\n initialValue: '1'\n })\n } catch {\n // 已注册(如 HMR 重复执行)时忽略\n }\n /*\n * 公开 token 注册为 <length>:Consumer 传 unitless 0 时计算值被归一为 0px,\n * 避免 calc(100% + 0) 因 number/percentage 不兼容而使闭合位移失效。\n */\n try {\n CSS.registerProperty({\n name: '--wui-drawer-inset',\n syntax: '<length>',\n inherits: true,\n initialValue: '8px'\n })\n } catch {\n // 已注册(如 HMR 重复执行)时忽略\n }\n /*\n * 嵌套层叠对数曲线的基准 A(shift = A · ln(depth + 1)):注册为 <length> 后\n * 桌面基准只有这一个真相源,JS 从 computed style 读到的永远是确定值;\n * 窄视口基准由 style.css 的媒体查询覆盖同一 token。消费方写 unitless 的\n * `0` 会在计算值阶段归一成 `0px`(层叠露边归零,而不是让 ln 项算出 NaN)。\n *\n * A = 43.2px 来自视觉 pass:桌面基准由 54px 调小两成,四层等宽 320px 时逐层露边\n * 29.94 / 17.52 / 12.43px,总堆叠宽 59.89px(54px 一档是 37.43 / 21.90 / 15.53,\n * 总宽 74.86px)。\n *\n * 取值由**最里层**那一跳决定:对数曲线压得最狠的正是第四层,它只有\n * A·ln(4/3) = 0.288A。12.43px 仍略高于旧的 12px 线性步进,但余量已经收窄到\n * 3.5%——继续下调会让最里层那一条边与上一条边难以分辨,A=54 时这一跳有 15.53px,\n * 余量 29%。若还要更小的基准,应先改 ln 的压缩比而不是只降 A。\n */\n try {\n CSS.registerProperty({\n name: '--wui-drawer-nested-peek-base',\n syntax: '<length>',\n inherits: true,\n initialValue: '43.2px'\n })\n } catch {\n // 已注册(如 HMR 重复执行)时忽略\n }\n /*\n * 收尾过渡的时长:注册为 <time> 后 ::backdrop 也能继承到同一值。不注册时变量在\n * transition 简写里只是 token 替换,值一旦无效整条声明在计算值阶段失效\n * (退化为 transition: none),回弹会变成瞬移。\n * 缓动不注册:CSS 属性语法没有 <easing-function>,且它不需要插值,靠 var() 的\n * 兜底值即可。\n */\n try {\n CSS.registerProperty({\n name: '--wui-internal-settle-duration',\n syntax: '<time>',\n inherits: true,\n initialValue: '280ms'\n })\n } catch {\n // 已注册(如 HMR 重复执行)时忽略\n }\n}\n\n@customElement('web-ui-drawer')\nexport class WebUiDrawer extends LitElement {\n static override styles = [unsafeCSS(glass), unsafeCSS(modalSurface), unsafeCSS(internalScrollbars), unsafeCSS(style)]\n\n @property({ type: Boolean, reflect: true }) open = false\n @property({ type: Boolean, reflect: true, attribute: 'no-scroll-lock' }) noScrollLock = false\n @property({ type: Boolean, reflect: true, attribute: 'no-backdrop-close' }) noBackdropClose = false\n\n /**\n * Controlled 模式下,Escape、遮罩、关闭按钮和拖拽关闭只派发 `open-change` 请求,\n * 不会自行修改 `open`。Consumer 回写 `open` 后才执行关闭动画。\n * 程序化 API(show/close/直接赋值 open)不受此模式影响,始终直通。\n */\n @property({ type: Boolean, reflect: true }) controlled = false\n\n @property({ type: String, reflect: true })\n get placement(): DrawerPlacement {\n return this._placement\n }\n set placement(v: string) {\n const old = this._placement\n this._placement = normalizeLiteral(v, ALLOWED_PLACEMENTS, 'right')\n this.requestUpdate('placement', old)\n }\n private _placement: DrawerPlacement = 'right'\n\n // 标题文字(未传 header slot 时显示默认 header)\n @property({ type: String }) heading = ''\n\n @property({ type: Boolean, reflect: true }) closable = false\n\n /**\n * Headless 模式:只保留 overlay 基础设施(backdrop、动画、scroll lock、dialog 语义),\n * 移除内置 UI(glass 样式、header、close 按钮、footer)。Consumer 自定义内容样式。\n */\n @property({ type: Boolean, reflect: true }) headless = false\n\n /**\n * 内部原生 dialog 的 accessible name。headless 模式必须由 Consumer 提供,\n * 因为该模式不会渲染可自动关联的内置 header。\n */\n @property({ type: String, attribute: 'dialog-label' }) dialogLabel = ''\n\n /**\n * 启用拖拽关闭:打开态在抽屉内缘渲染 drag bar(灰色胶囊 + 加宽热区),\n * 指针拖拽实时跟手,松手按位移与甩动速度判定关闭或弹回。\n * 关闭态基于原生 dialog 无可见渲染物,因此不支持从关闭态拖拽打开。\n */\n @property({ type: Boolean, reflect: true }) override draggable = false\n\n private get dialog() {\n return this.shadowRoot?.querySelector('dialog') ?? null\n }\n\n private _hasHeaderSlot = false\n private _hasFooterSlot = false\n private readonly _userOpenChange = new UserChangeController()\n /*\n * 上下 placement 的拖拽热区要让开头尾两节(见 style.css 的 --wui-internal-drawer-* 说明):\n * `bottom` 贴顶边、热区让开 header,`top` 贴底边、热区让开 footer。让开的量就是那一节的\n * border-box 高度,写成 host 上的自定义属性交给 CSS 去算偏移——样式侧因此不必知道\n * header / footer 的存在与高度,两条 placement 规则各只读自己的那个属性。\n *\n * 用 offsetHeight 而非 CSS 侧推导:`[hidden]` 的节天然是 0(让位量归零,热区回到面板边缘),\n * 而「header 存在但高度为 0」与「没有 header」在布局上是同一件事,不需要额外的存在性开关。\n * 高度是动态的(consumer 的 padding、窄屏断点改写、slotted 内容换行),所以观察而非读取一次。\n */\n private readonly _headerHeight = new ElementHeightController(this, '.wui-drawer-header', height =>\n this._setAvoidance('--wui-internal-drawer-header-inset', height)\n )\n private readonly _footerHeight = new ElementHeightController(this, '.wui-drawer-footer', height =>\n this._setAvoidance('--wui-internal-drawer-footer-inset', height)\n )\n\n /*\n * 让位量 = 该节高度 + 一点呼吸间距,且**只在真有那一节时**才加间距。\n *\n * 间距写成属性值里的 calc 而不是让 CSS 无条件相加,是因为「没有 header」这一态必须精确等于\n * 修复前的几何(让位量 0,热区顶边贴面板顶边)。若让间距无条件生效,无 header 的抽屉会\n * 平移 4px,胶囊相对内缘的既有内缩(drag-bar-inset.browser.spec.ts 钉住的关系)跟着变,\n * 这条护栏就不该为了新方案让步。间距本身仍以 token 暴露,真机调参不必回滚代码。\n */\n private _setAvoidance(property: string, height: number) {\n this.style.setProperty(\n property,\n height > 0 ? `calc(${height}px + var(--wui-drawer-drag-zone-inset, var(--wui-space-1, 4px)))` : '0px'\n )\n }\n private readonly _scrollLock = defineScrollLockLease().make()\n private readonly _presence = defineNativeDialogPresence().make({\n getDialog: () => this.dialog,\n isConnected: () => this.isConnected,\n isOpen: () => this.open\n })\n // nested 层序:打开后纳入全局栈,上层打开/关闭时本层缩放平移。\n private readonly _nestedLayers = defineNestedDrawerLayers().make({\n getDialog: () => this.dialog,\n getPlacement: () => this._placement\n })\n /*\n * 开启态浮层(issue #120 Block 1)。drawer 把自己的原生 <dialog> 登记为 panel,使挂在\n * dialog 上的 portal 面板(select / dropdown / tooltip)在逻辑组合树上成为它的后代,\n * 仲裁者据此选出最内层。\n *\n * 拖拽进行中不响应 Escape,但**仍是候选**:仲裁者照旧吞掉按键并压掉原生 cancel,\n * 只是不走关闭入口。这原本由本层 handleKeydown 兜底,现在只有一个执行者。\n */\n private readonly _overlay = defineOpenOverlay().make({\n requestClose: () => this._closeFromUser(),\n isConnected: () => this.isConnected\n })\n /** 当前开启会话的句柄;未开启时为 null。 */\n private _handle: OpenOverlayHandle | null = null\n\n private _dragGesture: DragGestureHandle | null = null\n // pointerdown 时刻已存在的闭合方向位移(从弹回动画中抓取时非 0)。\n private _dragInitialOffset = 0\n private _dragOffset = 0\n // 位移零点与判定时钟由手势层的 `calibrateOnFirstMove` 校准到首个 pointermove,\n // 这里只保留「改主意」相关的追踪状态。\n // 折返点(轴坐标):随反向移动拉到当前坐标。\n private _cancelBaseline = 0\n // 上一次 move 的轴坐标,用于求移动量。\n private _lastAxisPos = 0\n // 方向确认时的位移;负值表示方向尚未确认。\n private _swipeIntentDisp = -1\n // 已判定为「改主意」,甩动分支据此拒绝关闭。\n private _swipeCancelled = false\n // 释放后的 CSS transition 收尾:完成回调,以及终值与当前值相同时\n // 不会派发 transitionend 的兜底定时器。\n private _settleFinish: (() => void) | null = null\n private _settleTimer: ReturnType<typeof setTimeout> | undefined\n // controlled:弹簧到闭合位后等待 Consumer 回写 open;超时未回写则弹回。\n private _dragAwaitWriteback = false\n private _dragRequestTimer: ReturnType<typeof setTimeout> | undefined\n\n private get _dragAxis(): 'x' | 'y' {\n return this._placement === 'left' || this._placement === 'right' ? 'x' : 'y'\n }\n\n // 位移正方向 = 抽屉的闭合方向(拖出屏幕为正)。\n private get _dragCloseSign(): number {\n switch (this._placement) {\n case 'right':\n return 1\n case 'left':\n return -1\n case 'top':\n return -1\n case 'bottom':\n return 1\n }\n }\n\n private _measureDragSize(): number {\n const dialog = this.dialog\n if (!dialog) return 0\n return this._dragAxis === 'x' ? dialog.offsetWidth : dialog.offsetHeight\n }\n\n // 关闭距离阈值:尺寸的一半,下限 10px。\n // 胶囊的 accent 视觉确认与实际判定共用同一阈值,两者不会漂移。\n private _dragCloseThreshold(size: number): number {\n return Math.max(size * DRAG_CLOSE_RATIO, DRAG_MIN_CLOSE_DISTANCE)\n }\n\n // 浮动卡片(非 headless)的四周留边;闭合位移需越过它才能完全滑出视口。\n // headless 在 :host([headless]) dialog 上显式归零(防嵌套继承),解析失败回退 0。\n private _readDrawerInset(dialog: HTMLDialogElement): number {\n const raw = getComputedStyle(dialog).getPropertyValue('--wui-internal-drawer-inset')\n const parsed = Number.parseFloat(raw)\n return Number.isFinite(parsed) ? parsed : 0\n }\n\n // 闭合方向上的完全出屏距离:抽屉尺寸 + 浮动留边(headless 下即尺寸本身)。\n // CSS 闭合态 transform、controlled 悬停终态与弹簧终点共用同一数学,避免衔接跳变。\n private _dragCloseDistance(dialog: HTMLDialogElement): number {\n return this._measureDragSize() + this._readDrawerInset(dialog)\n }\n\n private _isDragging(): boolean {\n return this._dragGesture?.isDragging() ?? false\n }\n\n /*\n * 拖拽期间本层仍是候选:仲裁者照旧 preventDefault(压掉原生 <dialog> 的 cancel)与\n * stopPropagation,只是不调用 requestClose。`isDragging()` 的翻转由手势层内部决定,\n * 所以随 move 与各终结路径同步(幂等)。除拖拽外本层没有别的惰性来源。\n */\n private _syncDragInert() {\n this._handle?.setInert(this._isDragging())\n }\n\n private _cancelDragAwait() {\n if (this._dragRequestTimer !== undefined) {\n clearTimeout(this._dragRequestTimer)\n this._dragRequestTimer = undefined\n }\n this._dragAwaitWriteback = false\n }\n\n /**\n * 上下 placement 的 header / footer 自己接拖拽手势。\n *\n * 为什么不是「把热区铺到 header 上」:那样 header 里的控件会被透明命中层吞掉,\n * 正是 A6 修掉的那条缺陷。这里改成事件层让开——控件照常收自己的事件,拖拽只在\n * 「落点不是可点控件」时起手。\n *\n * 三道闸门任一不成立就不接管:\n * 1. placement 必须是上下。左右 placement 的拖拽轴是 x,端头在竖边上,header\n * 既不在可抓边缘上也不该起手(R4 的行为侧)。\n * 2. 下层嵌套抽屉整套拖拽提示一起退场。热区与热边靠 CSS 的 pointer-events 退场,\n * 事件层必须自己判同一条,否则 header 会成为唯一一条漏网的入口。\n * 3. 落点路径里有可点控件就让开——控件优先于「这一块可拖」。\n */\n private _handleSectionDragPointerDown(e: PointerEvent) {\n if (this._placement !== 'top' && this._placement !== 'bottom') return\n const section = (e.currentTarget as HTMLElement | null)?.closest('.wui-drawer-header, .wui-drawer-footer')\n const isHeader = section?.classList.contains('wui-drawer-header') ?? false\n // 只有**贴着可抓边缘**的那一节接手势:bottom 的 header、top 的 footer。\n // 另一节在面板远端,在那里起手却朝闭合方向拖是反直觉的,也不属于「端头」——\n // 与热区只让开同一侧那节是同一条边界。\n if (this._placement === 'bottom' && !isHeader) return\n if (this._placement === 'top' && isHeader) return\n // 下层嵌套抽屉整套拖拽提示一起退场:热区与热边靠 CSS 的 pointer-events 退场,\n // 事件层必须判同一条,否则这里是唯一一条漏网的入口。\n if (this.dialog?.classList.contains('is-nested-lower')) return\n if (this._isInteractivePath(e)) return\n this._handleDragPointerDown(e)\n }\n\n /**\n * 按下路径上是否出现可点控件。\n *\n * 走 composedPath 而不是 event.target:消费侧 slotted 控件与组件库自绘控件都各有\n * shadow root,target 只给出最内层那个节点,沿 parentElement 上行会在 shadow 边界\n * 断掉,控件自己那层反而看不见。\n */\n private _isInteractivePath(e: PointerEvent): boolean {\n const path = typeof e.composedPath === 'function' ? e.composedPath() : [e.target]\n for (const node of path) {\n if (node instanceof Element && node.matches(HEADER_FOOTER_INTERACTIVE_SELECTOR)) return true\n }\n return false\n }\n\n private _handleDragPointerDown(e: PointerEvent) {\n if (!this.open || !this.draggable || this._isDragging()) return\n // 多点触控的副指针(isPrimary 明确为 false)不参与手势。\n if (e.isPrimary === false) return\n\n const dialog = this.dialog\n if (!dialog) return\n // is-visible 由 presence 在 showModal 后一帧加上;就位前(打开极早期)忽略。\n // 就位后即使 enter 过渡仍在进行也允许抓取,起始位移从当前计算值续接。\n if (!dialog.classList.contains('is-visible')) return\n\n // 先读取动画中的当前位移再取消弹回动画,避免取消后回跳到内联样式值。\n const currentTransform = getComputedStyle(dialog).transform\n const axisValue =\n currentTransform && currentTransform !== 'none' && typeof DOMMatrixReadOnly === 'function'\n ? new DOMMatrixReadOnly(currentTransform)[this._dragAxis === 'x' ? 'm41' : 'm42']\n : 0\n // 收尾过渡进行中被重新抓取:丢弃收尾状态(不执行其回调,避免它回头写内联),\n // 位移已在下面按当前计算值续接。\n this._abortSettle()\n this._cancelDragAwait()\n\n this._dragInitialOffset = axisValue * this._dragCloseSign\n this._dragOffset = this._dragInitialOffset\n // 判定基准复位:折返点从按下位置起算。\n const axisPos = this._dragAxis === 'x' ? e.clientX : e.clientY\n this._cancelBaseline = axisPos\n this._lastAxisPos = axisPos\n this._swipeIntentDisp = -1\n this._swipeCancelled = false\n dialog.classList.add('is-dragging')\n // 收尾过渡进行中重新抓取时内联还停在收尾终值(打开位/闭合位),不写回当前位移\n // 会在抑制生效的瞬间跳到终值。写回后计算值 == 抓取瞬间的视觉位置,无跳变。\n this._applyDragOffset(dialog, this._dragOffset)\n\n this._dragGesture = attachDragGesture(e, {\n axis: this._dragAxis,\n // 拖拽零点与判定时钟一起重置到首个 move。\n calibrateOnFirstMove: true,\n onMove: info => {\n // info 的 delta/duration 已以校准点为零点。\n const pointerDelta = this._dragAxis === 'x' ? info.deltaX : info.deltaY\n // 本次手势自校准点起的**增量**(朝闭合方向为正,未阻尼)。\n const dragDelta = pointerDelta * this._dragCloseSign\n // 阻尼只作用于增量,再叠加抓取瞬间的基准位移。对总位移做阻尼会在「从弹回中\n // 重新抓取」时差出可见位移——基准位移本身会被一起压缩,元素不再接着视觉位置走。\n this._dragOffset = this._dragInitialOffset + dampOverscroll(dragDelta)\n this._trackSwipeCancel(this._dragAxis === 'x' ? info.clientX : info.clientY, dragDelta)\n\n // 达到关闭阈值时胶囊换成更实的中性灰作视觉确认(ADR-0027)。与距离分支共用同一量\n //(自抓取瞬间起的净位移)与同一阈值,视觉确认与实际判定不会漂移。\n const dragSize = this._measureDragSize()\n const dragDisplacement = this._dragOffset - this._dragInitialOffset\n dialog.classList.toggle('is-drag-close', dragDisplacement > this._dragCloseThreshold(dragSize))\n this._applyDragOffset(dialog, this._dragOffset)\n },\n onEnd: info => {\n this._dragGesture = null\n this._syncDragInert()\n dialog.classList.remove('is-dragging', 'is-drag-close')\n const size = this._measureDragSize()\n const velocity = (this._dragAxis === 'x' ? info.velocityX : info.velocityY) * this._dragCloseSign\n // 净位移从抓取瞬间的位移起算(弹回/收尾过渡中被重新抓取时起点非 0)。\n const displacement = this._dragOffset - this._dragInitialOffset\n // 时长同样自校准点(首个 move)起算,与位移零点同源:按下后的停顿不参与分母。\n // 分母下限 50ms;时长量为 0(首尾时间戳相同,或事件时间戳不可用/倒退)时速度取\n // 0——直接按 50ms 兜底会把「测不到时长」放大成一次甩动。\n const netVelocity =\n info.duration > 0 ? (displacement / Math.max(info.duration, DRAG_MIN_VELOCITY_SPAN_MS)) * 1000 : 0\n // 甩动判据:净位移朝闭合方向(`> 0`)、平均速度达到阈值(`>=`)、\n // 且本次手势未被判定为「改主意」。\n const flicked = displacement > 0 && netVelocity >= DRAG_FLICK_VELOCITY && !this._swipeCancelled\n // 距离分支同样用净位移,不是绝对位置:抓取瞬间停在哪里都不算「已经拖过一半」。\n // `_dragOffset` 只留给收尾的起点使用。\n const shouldClose = displacement > this._dragCloseThreshold(size) || flicked\n\n if (shouldClose) this._settleToClose(dialog, velocity)\n else this._settleRebound(dialog, velocity, this._dragOffset)\n },\n onCancel: () => {\n this._dragGesture = null\n this._syncDragInert()\n dialog.classList.remove('is-dragging', 'is-drag-close')\n this._settleRebound(dialog, 0, this._dragOffset)\n }\n })\n /*\n * 惰性必须与 attach 同拍。漏掉这里,按下到手势终结之间那一整段里 Escape 仍会关闭\n * 抽屉——旧实现用惰性谓词 `isEscapeCloseEnabled: () => !isDragging()` 在仲裁时现算,\n * 没有这个同步问题;改成 push 通道后每个翻转点都得自己推一次。\n *\n * `isDragging()` 的翻转点不止 attach 与 onEnd:手势层的 `handlePointerMove` 在越过死区后\n * 若判定轴向冲突(限定了 x 轴却明显纵向移动,或反之)会直接 `cancel()`,\n * `lostpointercapture` 也会终结手势。这些路径都走 `onCancel`,所以 `onCancel` 里那次\n * `_syncDragInert()` **不是**冗余——删掉它,「移动中取消拖拽」会让抽屉重新变回可被\n * Escape 关闭。\n */\n this._syncDragInert()\n }\n\n /*\n * 「改主意」守卫。\n *\n * 折返点随「反向移动」被拉到当前坐标,因此「自折返点起的位移」度量的是**自最近一次\n * 折返之后**回撤了多少:一旦它比方向确认时的位移少满 10px,本次手势即被标记为\n * 「改主意」,甩动分支据此拒绝关闭;位移重新越过关闭距离阈值即清除标记,\n * 即「已经拖过一半」不受回撤影响。\n *\n * @param axisPos 本次 move 在主轴上(`_dragAxis`)的指针坐标\n * @param displacement 自校准点起、朝闭合方向的**未阻尼**位移\n */\n private _trackSwipeCancel(axisPos: number, displacement: number) {\n const movement = axisPos - this._lastAxisPos\n this._lastAxisPos = axisPos\n if ((movement < 0 && axisPos > this._cancelBaseline) || (movement > 0 && axisPos < this._cancelBaseline)) {\n this._cancelBaseline = axisPos\n }\n\n if (this._swipeIntentDisp < 0) {\n // 方向确认:首次朝闭合方向累积出位移。\n if (displacement > 0) this._swipeIntentDisp = displacement\n return\n }\n\n const cancelDisplacement = (axisPos - this._cancelBaseline) * this._dragCloseSign\n if (cancelDisplacement > this._dragCloseThreshold(this._measureDragSize())) {\n this._swipeCancelled = false\n } else if (this._swipeIntentDisp - cancelDisplacement >= DRAG_REVERSE_CANCEL_THRESHOLD) {\n this._swipeCancelled = true\n }\n }\n\n // 受控状态写入等外部原因强制终结拖拽:清手势状态与拖拽样式,不弹回,\n // 后续管线(关闭/打开)由调用方继续执行。\n private _cancelActiveDrag() {\n if (!this._isDragging()) return\n this._dragGesture?.destroy()\n this._dragGesture = null\n this._syncDragInert()\n const dialog = this.dialog\n if (!dialog) return\n dialog.classList.remove('is-dragging', 'is-drag-close')\n this._clearDragStyles(dialog)\n }\n\n // 将闭合方向位移映射为 dialog transform,并同步遮罩透明度保持跟手反馈。\n private _applyDragOffset(dialog: HTMLDialogElement, offset: number) {\n const sign = this._dragCloseSign\n const value = offset * sign\n dialog.style.transform = this._dragAxis === 'x' ? `translateX(${value}px)` : `translateY(${value}px)`\n const size = this._measureDragSize()\n const progress = size > 0 ? Math.min(1, Math.max(0, offset / size)) : 0\n dialog.style.setProperty('--wui-internal-drag-backdrop-opacity', String(1 - progress))\n }\n\n private _clearDragStyles(dialog: HTMLDialogElement) {\n dialog.style.removeProperty('transform')\n dialog.style.removeProperty('--wui-internal-drag-backdrop-opacity')\n }\n\n // 由 CSS transition 收尾到完全闭合;controlled 下保持闭合位等待回写,其余走常规关闭管线。\n private _settleToClose(dialog: HTMLDialogElement, velocity: number) {\n const from = this._dragOffset\n this._dragOffset = 0\n // 终点 = 完全出屏距离(含浮动留边),与 CSS 闭合态/悬停终态一致,\n // 否则收尾结束后会有一个留边宽度的瞬移。\n const to = this._dragCloseDistance(dialog)\n\n const finishClose = () => {\n if (this.controlled) {\n // 保持在闭合位(is-visible 未移除,状态仍 open),等待 Consumer 回写或超时弹回。\n this._applyDragOffset(dialog, to)\n } else {\n // 移除 is-visible 后基础 transform 即闭合位,清内联样式不产生跳变;\n // presence.sync(false) 检测不到 is-visible 会立即完成关闭,不重播退出动画。\n dialog.classList.remove('is-visible')\n this._clearDragStyles(dialog)\n }\n this._closeFromDrag()\n }\n\n if (prefersReducedMotion(this) || Math.abs(to - from) < 1) {\n // 与 _settleRebound 的无动画路径对称:自己解除抑制,不依赖调用方清过类。\n dialog.classList.remove('is-dragging')\n finishClose()\n return\n }\n\n this._startSettle(\n dialog,\n to,\n 0,\n this._settleDuration(Math.abs(to - from), velocity),\n SETTLE_EASE_CLOSE,\n finishClose\n )\n }\n\n // 弹回打开位:从 from 位移回到 0;结束后把内联样式交还 CSS 打开态。\n private _settleRebound(dialog: HTMLDialogElement, velocity: number, from: number) {\n this._dragOffset = 0\n\n // 收尾结束必须清掉内联 transform,不能像 WAAPI 时代那样保留 0px:\n // translateX(0px) 内联会盖住闭合态 CSS transform,之后用按钮/遮罩/Esc 关闭时\n // 计算值恒为 0、开与关都不再触发过渡。\n const finishRebound = () => {\n this._clearDragStyles(dialog)\n }\n\n if (prefersReducedMotion(this) || Math.abs(from) < 1) {\n // 无动画路径:直接移除内联回到 CSS 打开位。CSS 化后计算值就是可信的打开位 0,\n // 不存在 WAAPI fill 覆盖,无需强制 reflow「烘焙」。\n dialog.classList.remove('is-dragging')\n finishRebound()\n return\n }\n\n this._startSettle(dialog, 0, 1, this._settleDuration(Math.abs(from), velocity), SETTLE_EASE_REBOUND, finishRebound)\n }\n\n // 释放速度只影响收尾时长:以它走完剩余距离的时间作为估计,慢放长、甩动短。\n private _settleDuration(distance: number, velocity: number): number {\n const ms = (distance / Math.max(Math.abs(velocity), SETTLE_MIN_VELOCITY)) * 1000\n return Math.min(SETTLE_MAX_MS, Math.max(SETTLE_MIN_MS, ms))\n }\n\n /*\n * 把释放后的收尾交给 CSS transition:写入终值与「时长/缓动」两个内部变量,并在\n * 同一次样式重算里解除抑制(is-dragging → is-settling)。过渡的 before-change\n * style 就是拖拽期间写入的内联值——没有 WAAPI 覆盖层,各引擎采样一致,这正是\n * 相对旧弹簧路径对 Safari「不采信 fill 覆盖」怪癖免疫的原因。\n */\n private _startSettle(\n dialog: HTMLDialogElement,\n offset: number,\n backdropOpacity: number,\n duration: number,\n easing: string,\n onDone: () => void\n ) {\n // 覆盖前先丢弃上一次收尾:否则它的兜底定时器会提前结束这一次的过渡。\n this._abortSettle()\n const sign = this._dragCloseSign\n dialog.style.setProperty('--wui-internal-settle-duration', `${duration}ms`)\n dialog.style.setProperty('--wui-internal-settle-easing', easing)\n dialog.style.transform =\n this._dragAxis === 'x' ? `translateX(${offset * sign}px)` : `translateY(${offset * sign}px)`\n dialog.style.setProperty('--wui-internal-drag-backdrop-opacity', String(backdropOpacity))\n dialog.classList.add('is-settling')\n dialog.classList.remove('is-dragging')\n\n this._settleFinish = onDone\n // 终值与当前值相同时不会派发 transitionend(也没有可见变化),用定时器兜底。\n this._settleTimer = setTimeout(() => this._finishSettle(), duration + SETTLE_GRACE_MS)\n }\n\n // 收尾过渡结束(transitionend 或兜底超时):先撤 settle 状态再执行收尾。\n private _finishSettle() {\n const finish = this._settleFinish\n if (!finish) return\n this._abortSettle()\n finish()\n }\n\n // 丢弃进行中的收尾(重新抓取、强制关闭、断连):只撤状态,不执行收尾回调。\n private _abortSettle() {\n this._settleFinish = null\n if (this._settleTimer !== undefined) {\n clearTimeout(this._settleTimer)\n this._settleTimer = undefined\n }\n const dialog = this.dialog\n if (!dialog) return\n dialog.classList.remove('is-settling')\n dialog.style.removeProperty('--wui-internal-settle-duration')\n dialog.style.removeProperty('--wui-internal-settle-easing')\n }\n\n private _closeFromDrag() {\n // 先派发关闭请求(_closeFromUser),再进入悬停等待:等待态中 _closeFromUser\n // 会被 L2 去重守卫短路,顺序颠倒会吞掉首次请求。\n this._closeFromUser()\n if (this.controlled) {\n this._dragAwaitWriteback = true\n this._dragRequestTimer = setTimeout(this._handleDragWritebackTimeout, DRAG_REQUEST_WINDOW_MS)\n }\n }\n\n // controlled 回写窗口超时:Consumer 拒绝关闭,从闭合位弹回打开位。\n private readonly _handleDragWritebackTimeout = () => {\n this._dragRequestTimer = undefined\n if (!this._dragAwaitWriteback || !this.open || this._isDragging()) {\n this._dragAwaitWriteback = false\n return\n }\n this._dragAwaitWriteback = false\n\n const dialog = this.dialog\n if (!dialog) return\n dialog.classList.add('is-visible')\n // 悬停终态位于完全出屏位(含留边),弹回也从该真实位置起步,避免首帧内跳。\n this._settleRebound(dialog, 0, this._dragCloseDistance(dialog))\n }\n\n override connectedCallback() {\n super.connectedCallback()\n this._resyncSlotPresence()\n // 重挂载对账:断连时 presence、滚动锁、nested 层序与登记都已被撤销,而\n // `open` 未变化时 `updated()` 不会补跑任何 sync 分支。首次连接时 shadow 尚未\n // 渲染、`this.dialog` 为 null,三种情况都直接跳过;打开态的首次进入仍由\n // updated() 的 `props.has('open')` 分支处理。\n this._presence.reconcile()\n if (this.open) {\n // presence.reconcile 已让打开态的 dialog 同步回到 top layer(此刻 dialog.open\n // 为真),层序 depth 可正确计数,与 updated() 的 register 时机一致。\n this._nestedLayers.register()\n }\n this._syncScrollLock()\n this._reclaimIfOpen()\n }\n\n override firstUpdated() {\n this._checkSlotContent('footer')\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback()\n this._nestedLayers.dispose()\n this._dragGesture?.destroy()\n this._dragGesture = null\n this._presence.dispose()\n this._scrollLock.release()\n // 断连即撤销登记,重连后由 _reclaimIfOpen 显式重新声明。\n this._handle?.release()\n this._handle = null\n this._abortSettle()\n this._cancelDragAwait()\n }\n\n /** 「重挂载恢复」的调用方一半:模块刻意不观察 DOM 连接状态。 */\n private _reclaimIfOpen() {\n if (!this.open || this._handle) return\n const dialog = this.dialog\n if (!dialog) return\n this._handle = this._overlay.claim(dialog, { ancestryFrom: this })\n this._syncDragInert()\n }\n\n /**\n * 对账 header / footer slot 的有无,**只在真的变了时排一次更新**。\n *\n * 排更新是这条对账不可省的一半:`showHeader` 与 footer 的 `?hidden` 只在 render 里求值,\n * 而 Lit 的 `connectedCallback` 只做 `enableUpdating(true)` 与 `setConnected(true)`,本身不排\n * 更新。两条 slotchange 处理器也接不住这条路径——slotchange 只在**分配集合**变化时派发,\n * 断连期间清空槽位内容时宿主节点本身没动,事件不触发(Chromium 实测)。\n *\n * 漏掉这一半的后果:断连前有 footer、重连时已清空的抽屉留下一节空的 footer。它在 style.css\n * 的 padding 下仍有真实高度(实测 32px),于是底部一条点不动的死带,拖拽热区的让位量也照\n * 它算而不归零。\n *\n * 判据取 light DOM 子节点而非 `slot.assignedNodes()`:首次连接时 shadow 尚未渲染,slot 元素\n * 还不存在,那一侧会直接跳过(首次连接的 presence 由 `firstUpdated()` 与 slotchange 兜底);\n * 这里要的正是重连对账——shadow root 跨断连保留,两侧都可用,但只有子节点扫描不依赖渲染\n * 时序。「只在真变了才排」与两条 slotchange 处理器同一纪律,避免每次重连都多一次空渲染。\n */\n private _resyncSlotPresence() {\n const hasHeader = this._hasSlottedChild('header')\n const hasFooter = this._hasSlottedChild('footer')\n if (hasHeader === this._hasHeaderSlot && hasFooter === this._hasFooterSlot) return\n this._hasHeaderSlot = hasHeader\n this._hasFooterSlot = hasFooter\n this.requestUpdate()\n }\n\n private _hasSlottedChild(name: 'header' | 'footer') {\n return Array.from(this.children).some(child => child.getAttribute('slot') === name)\n }\n\n private _checkSlotContent(name: string) {\n const slot = this.shadowRoot?.querySelector(`slot[name=\"${name}\"]`) as HTMLSlotElement | null\n if (!slot) return\n const has = slot.assignedNodes().length > 0\n if (name === 'footer' && has !== this._hasFooterSlot) {\n this._hasFooterSlot = has\n this.requestUpdate()\n }\n }\n\n private handleHeaderSlotChange(e: Event) {\n const has = (e.target as HTMLSlotElement).assignedNodes().length > 0\n if (has !== this._hasHeaderSlot) {\n this._hasHeaderSlot = has\n this.requestUpdate()\n }\n }\n\n private handleFooterSlotChange(e: Event) {\n const has = (e.target as HTMLSlotElement).assignedNodes().length > 0\n if (has !== this._hasFooterSlot) {\n this._hasFooterSlot = has\n this.requestUpdate()\n }\n }\n\n protected override updated(props: PropertyValues) {\n super.updated(props)\n if (!this.isConnected) return\n\n if (props.has('open')) {\n // Consumer 在拖拽进行中写入受控 open:受控状态优先,立即终结手势(等同\n // pointercancel 的清理但不弹回),交由下方 presence 走标准关闭/打开管线。\n if (this._isDragging() && !this.open) this._cancelActiveDrag()\n if (this._userOpenChange.consume()) this.emitOpenChange()\n // controlled 拖拽关闭的回写结果:确认关闭则清除闭合位悬停状态走正常关闭;\n // 拒绝关闭(回写 open=true)则从闭合位(含留边)弹回。\n if (this._dragAwaitWriteback) {\n this._cancelDragAwait()\n const dialog = this.dialog\n if (dialog && !this.open) {\n dialog.classList.remove('is-visible')\n this._clearDragStyles(dialog)\n } else if (dialog && this.open) {\n this._settleRebound(dialog, 0, this._dragCloseDistance(dialog))\n }\n }\n // 关闭且无进行中的拖拽/收尾时,清理可能残留的内联拖拽样式(如收尾路径写在\n // 打开位的 translateX(0px))。若不清理,0px 内联会盖住闭合态 CSS transform,\n // 后续开/关都不再触发过渡。收尾进行中由 _finishSettle 自行收尾,这里丢弃它\n // 以免回调回头写内联与关闭管线竞争。\n if (!this.open && !this._isDragging()) {\n this._abortSettle()\n const dialog = this.dialog\n if (dialog) this._clearDragStyles(dialog)\n }\n this._presence.sync(this.open)\n if (this.open) {\n // presence.sync 内的 showModal() 把 dialog 提升进 top layer。双击手势本身\n // 留下的活选区会被浏览器拿去和提升后的新布局重新解析,结果选区落到刚挂载的\n // drawer 正文上。脆弱的是「手势来源的活选区」,不是 dblclick 的判定窗口:把\n // 打开推迟 250ms(早过双击判定)仍然复现。选区由浏览器在 showModal() 调用\n // 内部生成:脚本没有任何 Selection API 调用,在 dblclick 上 preventDefault\n // 也拦不住。\n //\n // 实测(Chromium)showModal() 不会动打开前已经存在的选区,所以这一次清理\n // 同时也覆盖「先选中页面文本、再打开」的情况——modal 打开后被选中的内容挡在\n // 遮罩后面本来也已不可用。这里只清文档选区,不动 user-select,抽屉内正文\n // 仍可正常拖选;拖拽手柄与开关时序不受影响。\n window.getSelection()?.removeAllRanges()\n // presence.sync 已同步发起 showModal:此刻 dialog.open 为真,计数出正确\n // 的层序 depth 并驱动下层缩放。直接同步 register,不等待 is-visible\n //(那要再等一帧,且打开过渡期间上层关系已应确立)。\n this._nestedLayers.register()\n // 原生 dialog 登记为开启态浮层:挂在它上面的 portal 面板在逻辑组合树上\n // 成为本层后代,Escape 仲裁据此判出最内层(issue #120 Block 1)。\n const dialog = this.dialog\n if (dialog) {\n // 同一面板重新 claim = 新的一次开启:旧会话(含其子层)整体作废。\n this._handle?.release()\n this._handle = this._overlay.claim(dialog, { ancestryFrom: this })\n this._syncDragInert()\n }\n } else {\n this._nestedLayers.unregister()\n this._handle?.release()\n this._handle = null\n }\n }\n if (props.has('open') || props.has('noScrollLock')) this._syncScrollLock()\n }\n\n private handleTransitionEnd(e: TransitionEvent) {\n // transitionend 会从后代元素冒泡上来(slotted 消费者内容、嵌套子 drawer)。\n // 只处理 dialog 自身的事件:presence 与收尾都只认本层 dialog 的过渡,\n // 后代一条 transform 过渡结束不能提前终结本层收尾(与 presence 内部守卫同规则)。\n if (e.target !== e.currentTarget) return\n this._presence.handleTransitionEnd(e)\n\n // 收尾过渡结束:::backdrop 的 opacity 过渡 target 也是 dialog(带 pseudoElement),\n // 因此再按 propertyName 只认 transform。\n if (e.propertyName === 'transform') this._finishSettle()\n }\n\n private handleKeydown(e: KeyboardEvent) {\n if (e.key !== 'Escape') return\n /*\n * Escape 的归属由共享仲裁者在 document 捕获阶段判定(issue #120 Block 1);命中后\n * 它会 stopPropagation,本 handler 不再执行。此处保留两条兜底:\n * ① defaultPrevented 说明仲裁者已介入,不再重复关闭;\n * ② 兜底路径仍按原逻辑处理,避免「面板未登记」时 Escape 失灵。\n *\n * 本 handler **不是**可随手删掉的迁移残留:仲裁者挂在 document 上,只对能跨 shadow\n * 边界的事件生效。实测(overlay-open-owner-260918,2026-09-18)摘掉 `@keydown` 后\n * layout.browser.spec.ts 的两例 Escape 用例立刻转红——它们派发的 keydown 没有\n * `composed: true`,事件止步于 shadow root,到不了 document。\n */\n if (e.defaultPrevented) return\n /*\n * nested 场景防连锁:子 drawer 的 dialog 经 slot 投影在本层 shadow 内,其\n * keydown composed 冒泡路径会再次经过本层 dialog(事件路径含 slot 宿主链)。\n * 若事件传播路径在本层 dialog 之前已存在其他 <dialog>,说明该事件来自子层,本层忽略。\n */\n const path = e.composedPath()\n const ownDialogIndex = path.indexOf(e.currentTarget as Node)\n if (ownDialogIndex > 0) {\n const hasChildDialog = path.slice(0, ownDialogIndex).some(n => n instanceof HTMLDialogElement)\n if (hasChildDialog) return\n }\n // 拖拽进行中忽略 ESC,避免手势与关闭管线竞争。\n if (this._isDragging()) {\n e.preventDefault()\n return\n }\n e.preventDefault()\n this._closeFromUser()\n }\n\n show() {\n if (this.open) return\n this.open = true\n }\n\n close() {\n if (!this.open) return\n this.open = false\n }\n\n private readonly _closeFromUser = () => {\n if (!this.open) return\n // controlled 悬停等待回写期间,关闭意图已在途(open-change 已派发):\n // Escape/遮罩/关闭按钮的重复触发不再派发第二次请求,也不与超时弹回竞争。\n if (this._dragAwaitWriteback) return\n if (this.controlled) {\n this.emitOpenChange(false)\n return\n }\n\n this._userOpenChange.mark()\n this.close()\n }\n\n /*\n * 仲裁者在 capture 阶段 preventDefault 了 Escape 的 keydown,UA 因此不再派发原生\n * cancel,但本 handler 仍是「原生 cancel 到达时也要走完整关闭管线」的契约入口,\n * **不可删**。实测(overlay-open-owner-260918,2026-09-18)摘掉 `@cancel` 后\n * drawer.spec.ts 的「no-backdrop-close 存在时 cancel 仍通过关闭过渡退出」与\n * remount-reconcile 的「打开态被移出文档再接回」两例立刻转红。\n */\n private handleCancel(e: Event) {\n // 保留 top layer 直到 CSS 过渡结束,避免原生关闭跳过退出动画。\n e.preventDefault()\n this._closeFromUser()\n }\n\n private handleNativeClose() {\n // 关闭态下的 close 事件只可能是我们自己 finishClosing 排队的异步事件(或冗余的\n // 外部关闭):交给 presence 消费 self-close 标志,避免标志泄漏到下一次真实关闭。\n if (!this.open) {\n this._presence.handleNativeClose()\n return\n }\n\n // 我们自己 dialog.close() 排队的 close 事件是异步任务:正常时序在本关闭会话内\n // 到达,快速「关闭→重开」时它在重新打开之后才到达(过期事件)。两种情况都已在\n // finishClosing 完成全部清理,由 presence 消费并返回 true,不能当成外部关闭把\n // 刚重开的 dialog 关掉——否则重开即被误关,表现为连续开关丢失过渡动画。\n if (this._presence.handleNativeClose()) return\n\n // 悬停等待期间的原生关闭(如表单 method=\"dialog\"):视为回写窗口内的重复\n // 关闭意图,取消等待不补发请求;已原生关闭的 dialog 不再被超时弹回拉起。\n if (this._dragAwaitWriteback) {\n this._cancelDragAwait()\n return\n }\n\n // 原生关闭可绕过 cancel;controlled 时恢复受控状态,由 Consumer 决定是否关闭。\n if (this.controlled) {\n this._presence.sync(true)\n this.emitOpenChange(false)\n return\n }\n\n this._userOpenChange.mark()\n this.open = false\n }\n\n /*\n * 遮罩点击(backdrop click)的按-放链路回溯。\n *\n * 浏览器对「按下 → 拖动 → 松手」生成的 click 落在起点与松手点 DOM 的**共同祖先**\n * 上:从面板内容、拖拽热区甚至遮罩本处开始拖拽、松手落在遮罩上时,click 的\n * target 都是 dialog,与真正的轻点遮罩从 click 自身无法区分(真实鼠标/触摸下\n * 必现的误关路径,合成事件不派发 click 故自动化易漏测)。因此在 dialog 的\n * pointerdown 记录按下起点与是否落在遮罩上,click 时回溯校验:\n * - 按下起点不在遮罩上(从面板内容/热区开始的链路)→ 不是点遮罩,忽略;\n * - 按下到松手位移超过轻点量级 → 是拖拽松手而非点击,忽略;\n * 两者都通过才沿用「点遮罩关闭」语义。\n */\n private _backdropPress: { onMask: boolean; x: number; y: number } | null = null\n\n private handleDialogPointerDown(e: PointerEvent) {\n this._backdropPress = { onMask: e.target === e.currentTarget, x: e.clientX, y: e.clientY }\n }\n\n private handleBackdropClick(e: MouseEvent) {\n if (e.target !== (e.currentTarget as HTMLDialogElement)) return\n if (this.noBackdropClose) return\n // 拖拽进行中 pointer capture 使 click 落在 dialog 上,忽略以避免与手势竞争。\n if (this._isDragging()) return\n // detail 为 0 表示这次 click 不来自指针(键盘激活、程序化 .click()):\n // pointerdown 记录可能是上一次指针交互的残留,不能据此判定遮罩点击\n //(对齐 image-preview 的既有守卫)。\n if (e.detail === 0) return\n const press = this._backdropPress\n this._backdropPress = null\n if (!press || !press.onMask) return\n if (Math.hypot(e.clientX - press.x, e.clientY - press.y) > BACKDROP_TAP_DISTANCE) return\n this._closeFromUser()\n }\n\n private emitOpenChange(open = this.open) {\n dispatchOpenChangeEvent(this, open)\n }\n\n private _syncScrollLock(isOpen = this.open) {\n this._scrollLock.sync(isOpen && !this.noScrollLock)\n }\n\n override render() {\n const showHeader = this._hasHeaderSlot || !!this.heading\n const dialogLabel = this.dialogLabel.trim()\n const dialogLabelledBy = !dialogLabel && !this.headless && showHeader ? 'wui-drawer-heading' : nothing\n\n // 拖拽热区:仅在打开且 draggable 时渲染;胶囊 + 加宽命中条贴在抽屉内缘。\n // 胶囊是热区的**兄弟节点**而非后代:热区要让开 header / footer(见 style.css),\n // 胶囊作为后代会连带继承那份让位量,被一起推离面板边缘。两者都是 dialog 的直接子节点,\n // 胶囊因此相对 dialog 定位、恒贴可抓取边缘,热区则继续为端头上的按钮让位。\n //\n // 端头热边补上解耦留下的空档:胶囊贴回边缘后就不再落在热区的让开区里\n // (bottom + header 实测胶囊带 [8,12]、热区带 [60,80]),那一段若没有命中层,\n // 胶囊就成了一条按不下去、悬停不亮的死装饰——R1 与 R3 在几何上本就不可同满足,\n // 结构解耦只让两者不再互相位移,命中层必须另给。热边只在上下 placement 渲染:\n // 左右两条的胶囊本就落在热区内,多一个元素只会让 R5「左右几何不变」需要额外论证。\n //\n // 顺序要紧:胶囊排最后,两条热区(边、区)都在它前面,hover / active / 确认态三条规则\n // 因此靠通用兄弟选择器 `~` 挂上去。写成相邻兄弟 `+` 的话热边那条会静默失效。\n const needsEdgeBand = this.placement === 'top' || this.placement === 'bottom'\n const dragBar = this.draggable\n ? html`\n ${\n needsEdgeBand\n ? html`<div class=\"wui-drawer-drag-edge\" @pointerdown=${this._handleDragPointerDown}></div>`\n : nothing\n }\n <div class=\"wui-drawer-drag-zone\" @pointerdown=${this._handleDragPointerDown}></div>\n <div class=\"wui-drawer-drag-bar\"></div>\n `\n : nothing\n\n // 保持同一个 dialog 实例,避免打开期间切换 headless 时脱离 top layer。\n return html`\n <dialog\n aria-label=${dialogLabel || nothing}\n aria-labelledby=${dialogLabelledBy}\n @cancel=${this.handleCancel}\n @close=${this.handleNativeClose}\n @click=${this.handleBackdropClick}\n @pointerdown=${this.handleDialogPointerDown}\n @keydown=${this.handleKeydown}\n @transitionend=${this.handleTransitionEnd}\n >\n ${\n this.headless\n ? html`<slot></slot>${dragBar}`\n : html`\n <div class=\"wui-drawer-body wui-glass\">\n <div\n class=\"wui-drawer-header\"\n id=\"wui-drawer-heading\"\n ?hidden=${!showHeader}\n @pointerdown=${this._handleSectionDragPointerDown}\n >\n <slot name=\"header\" @slotchange=${this.handleHeaderSlotChange}>\n ${this.heading ? html`<span class=\"wui-drawer-heading\">${this.heading}</span>` : nothing}\n </slot>\n </div>\n <div class=\"wui-drawer-content wui-scroll\">\n <slot></slot>\n </div>\n <div\n class=\"wui-drawer-footer\"\n ?hidden=${!this._hasFooterSlot}\n @pointerdown=${this._handleSectionDragPointerDown}\n >\n <slot name=\"footer\" @slotchange=${this.handleFooterSlotChange}></slot>\n </div>\n </div>\n ${\n this.closable\n ? html`\n <web-ui-button\n class=\"wui-drawer-close\"\n @click=${this._closeFromUser}\n aria-label=\"关闭\"\n variant=\"secondary\"\n icon\n size=\"26\"\n >\n <web-ui-icon size=\"14\" .icon=${oouiClose}></web-ui-icon>\n </web-ui-button>\n `\n : nothing\n }\n ${dragBar}\n `\n }\n </dialog>\n `\n }\n\n declare readonly $events: {\n 'open-change': CustomEvent<{ open: boolean }>\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'web-ui-drawer': WebUiDrawer\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/drawer/index.ts"],"sourcesContent":["import '@/components/button'\nimport '@/components/icon'\n\nimport { html, LitElement, nothing, type PropertyValues, unsafeCSS } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\n\nimport glass from '@/assets/glass.css?inline'\nimport modalSurface from '@/assets/modal-surface.css?inline'\nimport { oouiClose } from '@/icons'\nimport internalScrollbars from '@/scrollbars/internal.css?inline'\nimport { ElementHeightController } from '@/shared/element-height'\nimport { UserChangeController } from '@/shared/events/user-change'\nimport { attachDragGesture, dampOverscroll, type DragGestureHandle } from '@/shared/gesture'\nimport { normalizeLiteral } from '@/shared/normalize'\nimport { dispatchOpenChangeEvent } from '@/shared/open-state'\nimport { defineNativeDialogPresence } from '@/shared/overlay/native-dialog-presence'\nimport { defineNestedDrawerLayers } from '@/shared/overlay/nested-drawer-layers'\nimport { defineOpenOverlay, type OpenOverlayHandle } from '@/shared/overlay/open-overlay'\nimport { defineScrollLockLease } from '@/shared/scroll-lock/scroll-lock'\nimport { prefersReducedMotion } from '@/shared/theme/reduced-motion'\n\nimport style from './style.css?inline'\n\nconst ALLOWED_PLACEMENTS = ['right', 'left', 'top', 'bottom'] as const\n\nexport type DrawerPlacement = (typeof ALLOWED_PLACEMENTS)[number]\n\n/*\n * 释放后的拖拽关闭判定:\n *\n * - 基准校准:位移零点与判定时钟都在**首个 pointermove** 处重置(手势层\n * `calibrateOnFirstMove`,drawer 显式开启),吸收「按下 → 首个 move」之间的空隙——\n * 触摸输入的首个 move 常已相对按下点偏移,不重置会让元素在首个 move 一次性兑现\n * 而跳动。代价是这段位移被丢弃,一次手势至少要累积两个 move 才可能产生位移;\n * `DragMoveInfo/DragEndInfo` 的 `delta*` 与 `duration` 均以该点为零点与起点。\n * - 距离:**自抓取瞬间起的净位移**超过 drawer 尺寸的一半(下限 10px)。用净位移而不是\n * 绝对位置:从弹回/收尾途中重新抓取时,抓取瞬间停在哪里都不算「已经拖过一半」。\n * 尺寸量不到时(0)由下限兜底,保证退化场景不会因为阈值 0 而「动一下就关」。\n * - 甩动:整段手势的平均速度**达到** 500px/s(含等号)。用「整段手势」而不是释放\n * 瞬间的滑窗速度:滑窗只描述最后一小段轨迹,反向回扫(先往打开方向拖进量程,\n * 再快速扫回)在释放瞬间同样能凑出很高的滑窗速度,但它不是朝关闭方向的甩动。\n * 净速度 = 净位移 / 整段时长(自校准点起算),分母钳到 50ms;时长为 0 时速度取 0,\n * 而不是按 50ms 兜底放大——直接放大会把「测不到时长」变成一次甩动。\n * - 方向:净位移未朝闭合方向(`<= 0`)时一律弹回。\n * - 改主意:拖拽期内追踪「自折返点起」的位移,一旦自方向确认点回撤满 10px 即标记\n * 取消,甩动分支此后拒绝关闭;位移重新越过关闭距离阈值时清除标记,即\n * 「已经拖过一半」不受回撤影响。见 `_trackSwipeCancel`。\n * - 过冲阻尼:闭合方向全额 1:1 跟手,反向按 `dampOverscroll` 平方根压缩。阻尼作用于\n * **本次手势的增量**,再叠加抓取瞬间的基准位移;对总位移做阻尼会连基准一起压缩,\n * 元素不再接着视觉位置走。\n */\nconst DRAG_CLOSE_RATIO = 0.5\nconst DRAG_MIN_CLOSE_DISTANCE = 10\nconst DRAG_FLICK_VELOCITY = 500\nconst DRAG_MIN_VELOCITY_SPAN_MS = 50\nconst DRAG_REVERSE_CANCEL_THRESHOLD = 10\nconst DRAG_REQUEST_WINDOW_MS = 120\n// backdrop 点击(click 链路)的轻点判定距离:按下起点到 click 坐标的欧氏位移未越过该值\n// 视为轻点,沿用「点 backdrop 关闭」语义(对齐 _dragCloseThreshold 的 10px 下限量级)。\n// 必须用欧氏长度(而非仅主轴分量):只看主轴会让「主轴不动、垂直大幅滑动」的\n// press–drag–release 链路仍落在窗口内被误读成轻点。\nconst BACKDROP_TAP_DISTANCE = 10\n\n/*\n * header / footer 让开给「可点控件」的选择器(上下 placement 专用)。\n *\n * 上下 placement 的端头整块可拖后,让开靠**逐个控件**在事件层判断,不靠几何切割:\n * 切割要测量每个控件的位置,控件一改位置就失效;逐个控件让开是结构性的。\n *\n * 判据是「这个元素自己就代表一次用户操作」,分三类:\n * - 原生可交互元素与原生可点容器(button / input / label / a[href] / summary …)\n * - 显式可编辑(contenteditable)\n * - 交互语义 role(组件库自绘的 checkbox、tab、menuitem 等没有原生 tag)\n * 另加「作者显式给了可聚焦 tabindex」:自定义控件常只靠 tabindex 暴露可操作性。\n *\n * 方向是**保守让开**:判不出来就让开。让开的代价是那一小块起不了拖(用户多点一下),\n * 判错的代价是控件失效(A6 那条回归),两者不对等。\n */\nconst HEADER_FOOTER_INTERACTIVE_SELECTOR = [\n 'button',\n 'input',\n 'select',\n 'textarea',\n 'label',\n 'summary',\n 'a[href]',\n 'audio[controls]',\n 'video[controls]',\n '[contenteditable]:not([contenteditable=\"false\"])',\n '[tabindex]:not([tabindex=\"-1\"])',\n '[role=\"button\"]',\n '[role=\"link\"]',\n '[role=\"checkbox\"]',\n '[role=\"switch\"]',\n '[role=\"radio\"]',\n '[role=\"tab\"]',\n '[role=\"menuitem\"]',\n '[role=\"menuitemcheckbox\"]',\n '[role=\"menuitemradio\"]',\n '[role=\"option\"]',\n '[role=\"textbox\"]',\n '[role=\"combobox\"]',\n '[role=\"slider\"]',\n '[role=\"spinbutton\"]'\n].join(',')\n\n/*\n * 释放后的收尾(弹回打开位 / 滑出到闭合位)由 CSS transition 接管(issue #123):\n * 拖拽期间内联 transform + `transition: none`,松手时把终值与过渡参数写入内联并在\n * 同一次样式重算里解除抑制,回弹即唯一的一次 CSS 过渡。全程无 `element.animate()`、\n * 无 fill、无 JS→CSS 动画交接边界,因此不存在 Safari 采不到 before-change style 的\n * 窗口(r3 的 reflow 烘焙补丁随 WAAPI 一并退役)。\n *\n * 代价是失去弹簧的速度感知轨迹:释放速度只用于估算过渡时长,过冲手感由缓动曲线\n * 近似。两条曲线对应原弹簧预设的阻尼比——close(ζ≈1.05)无过冲,\n * rebound(ζ≈0.74)保留约 3% 过冲。\n */\nconst SETTLE_MIN_MS = 180\nconst SETTLE_MAX_MS = 420\nconst SETTLE_EASE_CLOSE = 'cubic-bezier(0.32, 0.72, 0, 1)'\n// 过冲量由 y1 决定:1.3 时峰值 ≈2.99%,与 ζ≈0.74 的理论过冲(3.15%)对齐;1.12 只剩 0.37%。\nconst SETTLE_EASE_REBOUND = 'cubic-bezier(0.22, 1.3, 0.36, 1)'\n// 估算时长的速度下限(px/s):静止释放时退化为最大时长,避免除零与无限时长。\nconst SETTLE_MIN_VELOCITY = 1\n// transitionend 兜底定时器的宽限:覆盖一帧的调度抖动。\nconst SETTLE_GRACE_MS = 80\n\n/*\n * 拖拽期间由 JS 写入的 backdrop 透明度变量参与 WAAPI 关键帧。未注册的自定义属性在\n * 关键帧之间是离散插值(半帧跳变),注册为 <number> 后获得线性插值。\n * inherits 必须为 true:::backdrop 只从 originating element 继承「可继承属性」,\n * 注册为不可继承会让变量到不了 backdrop,跟手淡出整体失效(Chrome 151 实证)。\n * 注册表是全局的,模块重复执行(HMR)时捕获已注册错误并忽略。\n */\nif (typeof CSS !== 'undefined' && 'registerProperty' in CSS) {\n try {\n CSS.registerProperty({\n name: '--wui-internal-drag-backdrop-opacity',\n syntax: '<number>',\n inherits: true,\n initialValue: '1'\n })\n } catch {\n // 已注册(如 HMR 重复执行)时忽略\n }\n /*\n * 公开 token 注册为 <length>:Consumer 传 unitless 0 时计算值被归一为 0px,\n * 避免 calc(100% + 0) 因 number/percentage 不兼容而使闭合位移失效。\n */\n try {\n CSS.registerProperty({\n name: '--wui-drawer-inset',\n syntax: '<length>',\n inherits: true,\n initialValue: '8px'\n })\n } catch {\n // 已注册(如 HMR 重复执行)时忽略\n }\n /*\n * 嵌套层叠对数曲线的基准 A(shift = A · ln(depth + 1)):注册为 <length> 后\n * 桌面基准只有这一个真相源,JS 从 computed style 读到的永远是确定值;\n * 窄视口基准由 style.css 的媒体查询覆盖同一 token。消费方写 unitless 的\n * `0` 会在计算值阶段归一成 `0px`(层叠露边归零,而不是让 ln 项算出 NaN)。\n *\n * A = 43.2px 来自视觉 pass:桌面基准由 54px 调小两成,四层等宽 320px 时逐层露边\n * 29.94 / 17.52 / 12.43px,总堆叠宽 59.89px(54px 一档是 37.43 / 21.90 / 15.53,\n * 总宽 74.86px)。\n *\n * 取值由**最里层**那一跳决定:对数曲线压得最狠的正是第四层,它只有\n * A·ln(4/3) = 0.288A。12.43px 仍略高于旧的 12px 线性步进,但余量已经收窄到\n * 3.5%——继续下调会让最里层那一条边与上一条边难以分辨,A=54 时这一跳有 15.53px,\n * 余量 29%。若还要更小的基准,应先改 ln 的压缩比而不是只降 A。\n */\n try {\n CSS.registerProperty({\n name: '--wui-drawer-nested-peek-base',\n syntax: '<length>',\n inherits: true,\n initialValue: '43.2px'\n })\n } catch {\n // 已注册(如 HMR 重复执行)时忽略\n }\n /*\n * 收尾过渡的时长:注册为 <time> 后 ::backdrop 也能继承到同一值。不注册时变量在\n * transition 简写里只是 token 替换,值一旦无效整条声明在计算值阶段失效\n * (退化为 transition: none),回弹会变成瞬移。\n * 缓动不注册:CSS 属性语法没有 <easing-function>,且它不需要插值,靠 var() 的\n * 兜底值即可。\n */\n try {\n CSS.registerProperty({\n name: '--wui-internal-settle-duration',\n syntax: '<time>',\n inherits: true,\n initialValue: '280ms'\n })\n } catch {\n // 已注册(如 HMR 重复执行)时忽略\n }\n}\n\n@customElement('web-ui-drawer')\nexport class WebUiDrawer extends LitElement {\n static override styles = [unsafeCSS(glass), unsafeCSS(modalSurface), unsafeCSS(internalScrollbars), unsafeCSS(style)]\n\n @property({ type: Boolean, reflect: true }) open = false\n @property({ type: Boolean, reflect: true, attribute: 'no-scroll-lock' }) noScrollLock = false\n @property({ type: Boolean, reflect: true, attribute: 'no-backdrop-close' }) noBackdropClose = false\n\n /**\n * Controlled 模式下,Escape、backdrop、关闭 button 和拖拽关闭只派发 `open-change` 请求,\n * 不会自行修改 `open`。Consumer 回写 `open` 后才执行关闭动画。\n * 程序化 API(show/close/直接赋值 open)不受此模式影响,始终直通。\n */\n @property({ type: Boolean, reflect: true }) controlled = false\n\n @property({ type: String, reflect: true })\n get placement(): DrawerPlacement {\n return this._placement\n }\n set placement(v: string) {\n const old = this._placement\n this._placement = normalizeLiteral(v, ALLOWED_PLACEMENTS, 'right')\n this.requestUpdate('placement', old)\n }\n private _placement: DrawerPlacement = 'right'\n\n // 标题文字(未传 header slot 时显示默认 header)\n @property({ type: String }) heading = ''\n\n @property({ type: Boolean, reflect: true }) closable = false\n\n /**\n * Headless 模式:只保留 overlay 基础设施(backdrop、动画、scroll lock、dialog 语义),\n * 移除内置 UI(glass 样式、header、close button、footer)。Consumer 自定义内容样式。\n */\n @property({ type: Boolean, reflect: true }) headless = false\n\n /**\n * 内部原生 dialog 的 accessible name。headless 模式必须由 Consumer 提供,\n * 因为该模式不会渲染可自动关联的内置 header。\n */\n @property({ type: String, attribute: 'dialog-label' }) dialogLabel = ''\n\n /**\n * 启用拖拽关闭:打开态在 drawer 内缘渲染 drag bar(灰色胶囊 + 加宽 drag zone),\n * 指针拖拽实时跟手,松手按位移与甩动速度判定关闭或弹回。\n * 关闭态基于原生 dialog 无可见渲染物,因此不支持从关闭态拖拽打开。\n */\n @property({ type: Boolean, reflect: true }) override draggable = false\n\n private get dialog() {\n return this.shadowRoot?.querySelector('dialog') ?? null\n }\n\n private _hasHeaderSlot = false\n private _hasFooterSlot = false\n private readonly _userOpenChange = new UserChangeController()\n /*\n * 上下 placement 的拖拽 drag zone 要让开头尾两节(见 style.css 的 --wui-internal-drawer-* 说明):\n * `bottom` 贴顶边、drag zone 让开 header,`top` 贴底边、drag zone 让开 footer。让开的量就是那一节的\n * border-box 高度,写成 host 上的自定义属性交给 CSS 去算偏移——样式侧因此不必知道\n * header / footer 的存在与高度,两条 placement 规则各只读自己的那个属性。\n *\n * 用 offsetHeight 而非 CSS 侧推导:`[hidden]` 的节天然是 0(让位量归零,drag zone 回到 panel 边缘),\n * 而「header 存在但高度为 0」与「没有 header」在布局上是同一件事,不需要额外的存在性 toggle。\n * 高度是动态的(consumer 的 padding、窄屏断点改写、slotted 内容换行),所以观察而非读取一次。\n */\n private readonly _headerHeight = new ElementHeightController(this, '.wui-drawer-header', height =>\n this._setAvoidance('--wui-internal-drawer-header-inset', height)\n )\n private readonly _footerHeight = new ElementHeightController(this, '.wui-drawer-footer', height =>\n this._setAvoidance('--wui-internal-drawer-footer-inset', height)\n )\n\n /*\n * 让位量 = 该节高度 + 一点呼吸间距,且**只在真有那一节时**才加间距。\n *\n * 间距写成属性值里的 calc 而不是让 CSS 无条件相加,是因为「没有 header」这一态必须精确等于\n * 修复前的几何(让位量 0,drag zone 顶边贴 panel 顶边)。若让间距无条件生效,无 header 的 drawer 会\n * 平移 4px,胶囊相对内缘的既有内缩(drag-bar-inset.browser.spec.ts 钉住的关系)跟着变,\n * 这条护栏就不该为了新方案让步。间距本身仍以 token 暴露,真机调参不必回滚代码。\n */\n private _setAvoidance(property: string, height: number) {\n this.style.setProperty(\n property,\n height > 0 ? `calc(${height}px + var(--wui-drawer-drag-zone-inset, var(--wui-space-1, 4px)))` : '0px'\n )\n }\n private readonly _scrollLock = defineScrollLockLease().make()\n private readonly _presence = defineNativeDialogPresence().make({\n getDialog: () => this.dialog,\n isConnected: () => this.isConnected,\n isOpen: () => this.open\n })\n // nested 层序:打开后纳入全局栈,上层打开/关闭时本层缩放平移。\n private readonly _nestedLayers = defineNestedDrawerLayers().make({\n getDialog: () => this.dialog,\n getPlacement: () => this._placement\n })\n /*\n * 开启态 overlay(issue #120 Block 1)。drawer 把自己的原生 <dialog> 登记为 panel,使挂在\n * dialog 上的 portal panel(select / dropdown / tooltip)在逻辑组合树上成为它的后代,\n * 仲裁者据此选出最内层。\n *\n * 拖拽进行中不响应 Escape,但**仍是候选**:仲裁者照旧吞掉按键并压掉原生 cancel,\n * 只是不走关闭入口。这原本由本层 handleKeydown 兜底,现在只有一个执行者。\n */\n private readonly _overlay = defineOpenOverlay().make({\n requestClose: () => this._closeFromUser(),\n isConnected: () => this.isConnected\n })\n /** 当前开启会话的 handle;未开启时为 null。 */\n private _handle: OpenOverlayHandle | null = null\n\n private _dragGesture: DragGestureHandle | null = null\n // pointerdown 时刻已存在的闭合方向位移(从弹回动画中抓取时非 0)。\n private _dragInitialOffset = 0\n private _dragOffset = 0\n // 位移零点与判定时钟由手势层的 `calibrateOnFirstMove` 校准到首个 pointermove,\n // 这里只保留「改主意」相关的追踪状态。\n // 折返点(轴坐标):随反向移动拉到当前坐标。\n private _cancelBaseline = 0\n // 上一次 move 的轴坐标,用于求移动量。\n private _lastAxisPos = 0\n // 方向确认时的位移;负值表示方向尚未确认。\n private _swipeIntentDisp = -1\n // 已判定为「改主意」,甩动分支据此拒绝关闭。\n private _swipeCancelled = false\n // 释放后的 CSS transition 收尾:完成回调,以及终值与当前值相同时\n // 不会派发 transitionend 的兜底定时器。\n private _settleFinish: (() => void) | null = null\n private _settleTimer: ReturnType<typeof setTimeout> | undefined\n // controlled:弹簧到闭合位后等待 Consumer 回写 open;超时未回写则弹回。\n private _dragAwaitWriteback = false\n private _dragRequestTimer: ReturnType<typeof setTimeout> | undefined\n\n private get _dragAxis(): 'x' | 'y' {\n return this._placement === 'left' || this._placement === 'right' ? 'x' : 'y'\n }\n\n // 位移正方向 = drawer 的闭合方向(拖出屏幕为正)。\n private get _dragCloseSign(): number {\n switch (this._placement) {\n case 'right':\n return 1\n case 'left':\n return -1\n case 'top':\n return -1\n case 'bottom':\n return 1\n }\n }\n\n private _measureDragSize(): number {\n const dialog = this.dialog\n if (!dialog) return 0\n return this._dragAxis === 'x' ? dialog.offsetWidth : dialog.offsetHeight\n }\n\n // 关闭距离阈值:尺寸的一半,下限 10px。\n // 胶囊的 accent 视觉确认与实际判定共用同一阈值,两者不会漂移。\n private _dragCloseThreshold(size: number): number {\n return Math.max(size * DRAG_CLOSE_RATIO, DRAG_MIN_CLOSE_DISTANCE)\n }\n\n // 浮动卡片(非 headless)的四周留边;闭合位移需越过它才能完全滑出视口。\n // headless 在 :host([headless]) dialog 上显式归零(防嵌套继承),解析失败回退 0。\n private _readDrawerInset(dialog: HTMLDialogElement): number {\n const raw = getComputedStyle(dialog).getPropertyValue('--wui-internal-drawer-inset')\n const parsed = Number.parseFloat(raw)\n return Number.isFinite(parsed) ? parsed : 0\n }\n\n // 闭合方向上的完全出屏距离:drawer 尺寸 + 浮动留边(headless 下即尺寸本身)。\n // CSS 闭合态 transform、controlled 悬停终态与弹簧终点共用同一数学,避免衔接跳变。\n private _dragCloseDistance(dialog: HTMLDialogElement): number {\n return this._measureDragSize() + this._readDrawerInset(dialog)\n }\n\n private _isDragging(): boolean {\n return this._dragGesture?.isDragging() ?? false\n }\n\n /*\n * 拖拽期间本层仍是候选:仲裁者照旧 preventDefault(压掉原生 <dialog> 的 cancel)与\n * stopPropagation,只是不调用 requestClose。`isDragging()` 的翻转由手势层内部决定,\n * 所以随 move 与各终结路径同步(幂等)。除拖拽外本层没有别的惰性来源。\n */\n private _syncDragInert() {\n this._handle?.setInert(this._isDragging())\n }\n\n private _cancelDragAwait() {\n if (this._dragRequestTimer !== undefined) {\n clearTimeout(this._dragRequestTimer)\n this._dragRequestTimer = undefined\n }\n this._dragAwaitWriteback = false\n }\n\n /**\n * 上下 placement 的 header / footer 自己接拖拽手势。\n *\n * 为什么不是「把 drag zone 铺到 header 上」:那样 header 里的控件会被透明命中层吞掉,\n * 正是 A6 修掉的那条缺陷。这里改成事件层让开——控件照常收自己的事件,拖拽只在\n * 「落点不是可点控件」时起手。\n *\n * 三道闸门任一不成立就不接管:\n * 1. placement 必须是上下。左右 placement 的拖拽轴是 x,端头在竖边上,header\n * 既不在可抓边缘上也不该起手(R4 的行为侧)。\n * 2. 下层嵌套 drawer 整套拖拽提示一起退场。drag zone 与 drag edge 靠 CSS 的 pointer-events 退场,\n * 事件层必须自己判同一条,否则 header 会成为唯一一条漏网的入口。\n * 3. 落点路径里有可点控件就让开——控件优先于「这一块可拖」。\n */\n private _handleSectionDragPointerDown(e: PointerEvent) {\n if (this._placement !== 'top' && this._placement !== 'bottom') return\n const section = (e.currentTarget as HTMLElement | null)?.closest('.wui-drawer-header, .wui-drawer-footer')\n const isHeader = section?.classList.contains('wui-drawer-header') ?? false\n // 只有**贴着可抓边缘**的那一节接手势:bottom 的 header、top 的 footer。\n // 另一节在 panel 远端,在那里起手却朝闭合方向拖是反直觉的,也不属于「端头」——\n // 与 drag zone 只让开同一侧那节是同一条边界。\n if (this._placement === 'bottom' && !isHeader) return\n if (this._placement === 'top' && isHeader) return\n // 下层嵌套 drawer 整套拖拽提示一起退场:drag zone 与 drag edge 靠 CSS 的 pointer-events 退场,\n // 事件层必须判同一条,否则这里是唯一一条漏网的入口。\n if (this.dialog?.classList.contains('is-nested-lower')) return\n if (this._isInteractivePath(e)) return\n this._handleDragPointerDown(e)\n }\n\n /**\n * 按下路径上是否出现可点控件。\n *\n * 走 composedPath 而不是 event.target:消费侧 slotted 控件与组件库自绘控件都各有\n * shadow root,target 只给出最内层那个节点,沿 parentElement 上行会在 shadow 边界\n * 断掉,控件自己那层反而看不见。\n */\n private _isInteractivePath(e: PointerEvent): boolean {\n const path = typeof e.composedPath === 'function' ? e.composedPath() : [e.target]\n for (const node of path) {\n if (node instanceof Element && node.matches(HEADER_FOOTER_INTERACTIVE_SELECTOR)) return true\n }\n return false\n }\n\n private _handleDragPointerDown(e: PointerEvent) {\n if (!this.open || !this.draggable || this._isDragging()) return\n // 多点触控的副指针(isPrimary 明确为 false)不参与手势。\n if (e.isPrimary === false) return\n\n const dialog = this.dialog\n if (!dialog) return\n // is-visible 由 presence 在 showModal 后一帧加上;就位前(打开极早期)忽略。\n // 就位后即使 enter 过渡仍在进行也允许抓取,起始位移从当前计算值续接。\n if (!dialog.classList.contains('is-visible')) return\n\n // 先读取动画中的当前位移再取消弹回动画,避免取消后回跳到内联样式值。\n const currentTransform = getComputedStyle(dialog).transform\n const axisValue =\n currentTransform && currentTransform !== 'none' && typeof DOMMatrixReadOnly === 'function'\n ? new DOMMatrixReadOnly(currentTransform)[this._dragAxis === 'x' ? 'm41' : 'm42']\n : 0\n // 收尾过渡进行中被重新抓取:丢弃收尾状态(不执行其回调,避免它回头写内联),\n // 位移已在下面按当前计算值续接。\n this._abortSettle()\n this._cancelDragAwait()\n\n this._dragInitialOffset = axisValue * this._dragCloseSign\n this._dragOffset = this._dragInitialOffset\n // 判定基准复位:折返点从按下位置起算。\n const axisPos = this._dragAxis === 'x' ? e.clientX : e.clientY\n this._cancelBaseline = axisPos\n this._lastAxisPos = axisPos\n this._swipeIntentDisp = -1\n this._swipeCancelled = false\n dialog.classList.add('is-dragging')\n // 收尾过渡进行中重新抓取时内联还停在收尾终值(打开位/闭合位),不写回当前位移\n // 会在抑制生效的瞬间跳到终值。写回后计算值 == 抓取瞬间的视觉位置,无跳变。\n this._applyDragOffset(dialog, this._dragOffset)\n\n this._dragGesture = attachDragGesture(e, {\n axis: this._dragAxis,\n // 拖拽零点与判定时钟一起重置到首个 move。\n calibrateOnFirstMove: true,\n onMove: info => {\n // info 的 delta/duration 已以校准点为零点。\n const pointerDelta = this._dragAxis === 'x' ? info.deltaX : info.deltaY\n // 本次手势自校准点起的**增量**(朝闭合方向为正,未阻尼)。\n const dragDelta = pointerDelta * this._dragCloseSign\n // 阻尼只作用于增量,再叠加抓取瞬间的基准位移。对总位移做阻尼会在「从弹回中\n // 重新抓取」时差出可见位移——基准位移本身会被一起压缩,元素不再接着视觉位置走。\n this._dragOffset = this._dragInitialOffset + dampOverscroll(dragDelta)\n this._trackSwipeCancel(this._dragAxis === 'x' ? info.clientX : info.clientY, dragDelta)\n\n // 达到关闭阈值时胶囊换成更实的中性灰作视觉确认(ADR-0027)。与距离分支共用同一量\n //(自抓取瞬间起的净位移)与同一阈值,视觉确认与实际判定不会漂移。\n const dragSize = this._measureDragSize()\n const dragDisplacement = this._dragOffset - this._dragInitialOffset\n dialog.classList.toggle('is-drag-close', dragDisplacement > this._dragCloseThreshold(dragSize))\n this._applyDragOffset(dialog, this._dragOffset)\n },\n onEnd: info => {\n this._dragGesture = null\n this._syncDragInert()\n dialog.classList.remove('is-dragging', 'is-drag-close')\n const size = this._measureDragSize()\n const velocity = (this._dragAxis === 'x' ? info.velocityX : info.velocityY) * this._dragCloseSign\n // 净位移从抓取瞬间的位移起算(弹回/收尾过渡中被重新抓取时起点非 0)。\n const displacement = this._dragOffset - this._dragInitialOffset\n // 时长同样自校准点(首个 move)起算,与位移零点同源:按下后的停顿不参与分母。\n // 分母下限 50ms;时长量为 0(首尾时间戳相同,或事件时间戳不可用/倒退)时速度取\n // 0——直接按 50ms 兜底会把「测不到时长」放大成一次甩动。\n const netVelocity =\n info.duration > 0 ? (displacement / Math.max(info.duration, DRAG_MIN_VELOCITY_SPAN_MS)) * 1000 : 0\n // 甩动判据:净位移朝闭合方向(`> 0`)、平均速度达到阈值(`>=`)、\n // 且本次手势未被判定为「改主意」。\n const flicked = displacement > 0 && netVelocity >= DRAG_FLICK_VELOCITY && !this._swipeCancelled\n // 距离分支同样用净位移,不是绝对位置:抓取瞬间停在哪里都不算「已经拖过一半」。\n // `_dragOffset` 只留给收尾的起点使用。\n const shouldClose = displacement > this._dragCloseThreshold(size) || flicked\n\n if (shouldClose) this._settleToClose(dialog, velocity)\n else this._settleRebound(dialog, velocity, this._dragOffset)\n },\n onCancel: () => {\n this._dragGesture = null\n this._syncDragInert()\n dialog.classList.remove('is-dragging', 'is-drag-close')\n this._settleRebound(dialog, 0, this._dragOffset)\n }\n })\n /*\n * 惰性必须与 attach 同拍。漏掉这里,按下到手势终结之间那一整段里 Escape 仍会关闭\n * drawer——旧实现用惰性谓词 `isEscapeCloseEnabled: () => !isDragging()` 在仲裁时现算,\n * 没有这个同步问题;改成 push 通道后每个翻转点都得自己推一次。\n *\n * `isDragging()` 的翻转点不止 attach 与 onEnd:手势层的 `handlePointerMove` 在越过死区后\n * 若判定轴向冲突(限定了 x 轴却明显纵向移动,或反之)会直接 `cancel()`,\n * `lostpointercapture` 也会终结手势。这些路径都走 `onCancel`,所以 `onCancel` 里那次\n * `_syncDragInert()` **不是**冗余——删掉它,「移动中取消拖拽」会让 drawer 重新变回可被\n * Escape 关闭。\n */\n this._syncDragInert()\n }\n\n /*\n * 「改主意」守卫。\n *\n * 折返点随「反向移动」被拉到当前坐标,因此「自折返点起的位移」度量的是**自最近一次\n * 折返之后**回撤了多少:一旦它比方向确认时的位移少满 10px,本次手势即被标记为\n * 「改主意」,甩动分支据此拒绝关闭;位移重新越过关闭距离阈值即清除标记,\n * 即「已经拖过一半」不受回撤影响。\n *\n * @param axisPos 本次 move 在主轴上(`_dragAxis`)的指针坐标\n * @param displacement 自校准点起、朝闭合方向的**未阻尼**位移\n */\n private _trackSwipeCancel(axisPos: number, displacement: number) {\n const movement = axisPos - this._lastAxisPos\n this._lastAxisPos = axisPos\n if ((movement < 0 && axisPos > this._cancelBaseline) || (movement > 0 && axisPos < this._cancelBaseline)) {\n this._cancelBaseline = axisPos\n }\n\n if (this._swipeIntentDisp < 0) {\n // 方向确认:首次朝闭合方向累积出位移。\n if (displacement > 0) this._swipeIntentDisp = displacement\n return\n }\n\n const cancelDisplacement = (axisPos - this._cancelBaseline) * this._dragCloseSign\n if (cancelDisplacement > this._dragCloseThreshold(this._measureDragSize())) {\n this._swipeCancelled = false\n } else if (this._swipeIntentDisp - cancelDisplacement >= DRAG_REVERSE_CANCEL_THRESHOLD) {\n this._swipeCancelled = true\n }\n }\n\n // 受控状态写入等外部原因强制终结拖拽:清手势状态与拖拽样式,不弹回,\n // 后续管线(关闭/打开)由调用方继续执行。\n private _cancelActiveDrag() {\n if (!this._isDragging()) return\n this._dragGesture?.destroy()\n this._dragGesture = null\n this._syncDragInert()\n const dialog = this.dialog\n if (!dialog) return\n dialog.classList.remove('is-dragging', 'is-drag-close')\n this._clearDragStyles(dialog)\n }\n\n // 将闭合方向位移映射为 dialog transform,并同步 backdrop 透明度保持跟手反馈。\n private _applyDragOffset(dialog: HTMLDialogElement, offset: number) {\n const sign = this._dragCloseSign\n const value = offset * sign\n dialog.style.transform = this._dragAxis === 'x' ? `translateX(${value}px)` : `translateY(${value}px)`\n const size = this._measureDragSize()\n const progress = size > 0 ? Math.min(1, Math.max(0, offset / size)) : 0\n dialog.style.setProperty('--wui-internal-drag-backdrop-opacity', String(1 - progress))\n }\n\n private _clearDragStyles(dialog: HTMLDialogElement) {\n dialog.style.removeProperty('transform')\n dialog.style.removeProperty('--wui-internal-drag-backdrop-opacity')\n }\n\n // 由 CSS transition 收尾到完全闭合;controlled 下保持闭合位等待回写,其余走常规关闭管线。\n private _settleToClose(dialog: HTMLDialogElement, velocity: number) {\n const from = this._dragOffset\n this._dragOffset = 0\n // 终点 = 完全出屏距离(含浮动留边),与 CSS 闭合态/悬停终态一致,\n // 否则收尾结束后会有一个留边宽度的瞬移。\n const to = this._dragCloseDistance(dialog)\n\n const finishClose = () => {\n if (this.controlled) {\n // 保持在闭合位(is-visible 未移除,状态仍 open),等待 Consumer 回写或超时弹回。\n this._applyDragOffset(dialog, to)\n } else {\n // 移除 is-visible 后基础 transform 即闭合位,清内联样式不产生跳变;\n // presence.sync(false) 检测不到 is-visible 会立即完成关闭,不重播退出动画。\n dialog.classList.remove('is-visible')\n this._clearDragStyles(dialog)\n }\n this._closeFromDrag()\n }\n\n if (prefersReducedMotion(this) || Math.abs(to - from) < 1) {\n // 与 _settleRebound 的无动画路径对称:自己解除抑制,不依赖调用方清过类。\n dialog.classList.remove('is-dragging')\n finishClose()\n return\n }\n\n this._startSettle(\n dialog,\n to,\n 0,\n this._settleDuration(Math.abs(to - from), velocity),\n SETTLE_EASE_CLOSE,\n finishClose\n )\n }\n\n // 弹回打开位:从 from 位移回到 0;结束后把内联样式交还 CSS 打开态。\n private _settleRebound(dialog: HTMLDialogElement, velocity: number, from: number) {\n this._dragOffset = 0\n\n // 收尾结束必须清掉内联 transform,不能像 WAAPI 时代那样保留 0px:\n // translateX(0px) 内联会盖住闭合态 CSS transform,之后用 button/backdrop/Esc 关闭时\n // 计算值恒为 0、开与关都不再触发过渡。\n const finishRebound = () => {\n this._clearDragStyles(dialog)\n }\n\n if (prefersReducedMotion(this) || Math.abs(from) < 1) {\n // 无动画路径:直接移除内联回到 CSS 打开位。CSS 化后计算值就是可信的打开位 0,\n // 不存在 WAAPI fill 覆盖,无需强制 reflow「烘焙」。\n dialog.classList.remove('is-dragging')\n finishRebound()\n return\n }\n\n this._startSettle(dialog, 0, 1, this._settleDuration(Math.abs(from), velocity), SETTLE_EASE_REBOUND, finishRebound)\n }\n\n // 释放速度只影响收尾时长:以它走完剩余距离的时间作为估计,慢放长、甩动短。\n private _settleDuration(distance: number, velocity: number): number {\n const ms = (distance / Math.max(Math.abs(velocity), SETTLE_MIN_VELOCITY)) * 1000\n return Math.min(SETTLE_MAX_MS, Math.max(SETTLE_MIN_MS, ms))\n }\n\n /*\n * 把释放后的收尾交给 CSS transition:写入终值与「时长/缓动」两个内部变量,并在\n * 同一次样式重算里解除抑制(is-dragging → is-settling)。过渡的 before-change\n * style 就是拖拽期间写入的内联值——没有 WAAPI 覆盖层,各引擎采样一致,这正是\n * 相对旧弹簧路径对 Safari「不采信 fill 覆盖」怪癖免疫的原因。\n */\n private _startSettle(\n dialog: HTMLDialogElement,\n offset: number,\n backdropOpacity: number,\n duration: number,\n easing: string,\n onDone: () => void\n ) {\n // 覆盖前先丢弃上一次收尾:否则它的兜底定时器会提前结束这一次的过渡。\n this._abortSettle()\n const sign = this._dragCloseSign\n dialog.style.setProperty('--wui-internal-settle-duration', `${duration}ms`)\n dialog.style.setProperty('--wui-internal-settle-easing', easing)\n dialog.style.transform =\n this._dragAxis === 'x' ? `translateX(${offset * sign}px)` : `translateY(${offset * sign}px)`\n dialog.style.setProperty('--wui-internal-drag-backdrop-opacity', String(backdropOpacity))\n dialog.classList.add('is-settling')\n dialog.classList.remove('is-dragging')\n\n this._settleFinish = onDone\n // 终值与当前值相同时不会派发 transitionend(也没有可见变化),用定时器兜底。\n this._settleTimer = setTimeout(() => this._finishSettle(), duration + SETTLE_GRACE_MS)\n }\n\n // 收尾过渡结束(transitionend 或兜底超时):先撤 settle 状态再执行收尾。\n private _finishSettle() {\n const finish = this._settleFinish\n if (!finish) return\n this._abortSettle()\n finish()\n }\n\n // 丢弃进行中的收尾(重新抓取、强制关闭、断连):只撤状态,不执行收尾回调。\n private _abortSettle() {\n this._settleFinish = null\n if (this._settleTimer !== undefined) {\n clearTimeout(this._settleTimer)\n this._settleTimer = undefined\n }\n const dialog = this.dialog\n if (!dialog) return\n dialog.classList.remove('is-settling')\n dialog.style.removeProperty('--wui-internal-settle-duration')\n dialog.style.removeProperty('--wui-internal-settle-easing')\n }\n\n private _closeFromDrag() {\n // 先派发关闭请求(_closeFromUser),再进入悬停等待:等待态中 _closeFromUser\n // 会被 L2 去重守卫短路,顺序颠倒会吞掉首次请求。\n this._closeFromUser()\n if (this.controlled) {\n this._dragAwaitWriteback = true\n this._dragRequestTimer = setTimeout(this._handleDragWritebackTimeout, DRAG_REQUEST_WINDOW_MS)\n }\n }\n\n // controlled 回写窗口超时:Consumer 拒绝关闭,从闭合位弹回打开位。\n private readonly _handleDragWritebackTimeout = () => {\n this._dragRequestTimer = undefined\n if (!this._dragAwaitWriteback || !this.open || this._isDragging()) {\n this._dragAwaitWriteback = false\n return\n }\n this._dragAwaitWriteback = false\n\n const dialog = this.dialog\n if (!dialog) return\n dialog.classList.add('is-visible')\n // 悬停终态位于完全出屏位(含留边),弹回也从该真实位置起步,避免首帧内跳。\n this._settleRebound(dialog, 0, this._dragCloseDistance(dialog))\n }\n\n override connectedCallback() {\n super.connectedCallback()\n this._resyncSlotPresence()\n // 重挂载对账:断连时 presence、滚动锁、nested 层序与登记都已被撤销,而\n // `open` 未变化时 `updated()` 不会补跑任何 sync 分支。首次连接时 shadow 尚未\n // 渲染、`this.dialog` 为 null,三种情况都直接跳过;打开态的首次进入仍由\n // updated() 的 `props.has('open')` 分支处理。\n this._presence.reconcile()\n if (this.open) {\n // presence.reconcile 已让打开态的 dialog 同步回到 top layer(此刻 dialog.open\n // 为真),层序 depth 可正确计数,与 updated() 的 register 时机一致。\n this._nestedLayers.register()\n }\n this._syncScrollLock()\n this._reclaimIfOpen()\n }\n\n override firstUpdated() {\n this._checkSlotContent('footer')\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback()\n this._nestedLayers.dispose()\n this._dragGesture?.destroy()\n this._dragGesture = null\n this._presence.dispose()\n this._scrollLock.release()\n // 断连即撤销登记,重连后由 _reclaimIfOpen 显式重新声明。\n this._handle?.release()\n this._handle = null\n this._abortSettle()\n this._cancelDragAwait()\n }\n\n /** 「重挂载恢复」的调用方一半:模块刻意不观察 DOM 连接状态。 */\n private _reclaimIfOpen() {\n if (!this.open || this._handle) return\n const dialog = this.dialog\n if (!dialog) return\n this._handle = this._overlay.claim(dialog, { ancestryFrom: this })\n this._syncDragInert()\n }\n\n /**\n * 对账 header / footer slot 的有无,**只在真的变了时排一次更新**。\n *\n * 排更新是这条对账不可省的一半:`showHeader` 与 footer 的 `?hidden` 只在 render 里求值,\n * 而 Lit 的 `connectedCallback` 只做 `enableUpdating(true)` 与 `setConnected(true)`,本身不排\n * 更新。两条 slotchange 处理器也接不住这条路径——slotchange 只在**分配集合**变化时派发,\n * 断连期间清空槽位内容时宿主节点本身没动,事件不触发(Chromium 实测)。\n *\n * 漏掉这一半的后果:断连前有 footer、重连时已清空的 drawer 留下一节空的 footer。它在 style.css\n * 的 padding 下仍有真实高度(实测 32px),于是底部一条点不动的死带,拖拽 drag zone 的让位量也照\n * 它算而不归零。\n *\n * 判据取 light DOM 子节点而非 `slot.assignedNodes()`:首次连接时 shadow 尚未渲染,slot 元素\n * 还不存在,那一侧会直接跳过(首次连接的 presence 由 `firstUpdated()` 与 slotchange 兜底);\n * 这里要的正是重连对账——shadow root 跨断连保留,两侧都可用,但只有子节点扫描不依赖渲染\n * 时序。「只在真变了才排」与两条 slotchange 处理器同一纪律,避免每次重连都多一次空渲染。\n */\n private _resyncSlotPresence() {\n const hasHeader = this._hasSlottedChild('header')\n const hasFooter = this._hasSlottedChild('footer')\n if (hasHeader === this._hasHeaderSlot && hasFooter === this._hasFooterSlot) return\n this._hasHeaderSlot = hasHeader\n this._hasFooterSlot = hasFooter\n this.requestUpdate()\n }\n\n private _hasSlottedChild(name: 'header' | 'footer') {\n return Array.from(this.children).some(child => child.getAttribute('slot') === name)\n }\n\n private _checkSlotContent(name: string) {\n const slot = this.shadowRoot?.querySelector(`slot[name=\"${name}\"]`) as HTMLSlotElement | null\n if (!slot) return\n const has = slot.assignedNodes().length > 0\n if (name === 'footer' && has !== this._hasFooterSlot) {\n this._hasFooterSlot = has\n this.requestUpdate()\n }\n }\n\n private handleHeaderSlotChange(e: Event) {\n const has = (e.target as HTMLSlotElement).assignedNodes().length > 0\n if (has !== this._hasHeaderSlot) {\n this._hasHeaderSlot = has\n this.requestUpdate()\n }\n }\n\n private handleFooterSlotChange(e: Event) {\n const has = (e.target as HTMLSlotElement).assignedNodes().length > 0\n if (has !== this._hasFooterSlot) {\n this._hasFooterSlot = has\n this.requestUpdate()\n }\n }\n\n protected override updated(props: PropertyValues) {\n super.updated(props)\n if (!this.isConnected) return\n\n if (props.has('open')) {\n // Consumer 在拖拽进行中写入受控 open:受控状态优先,立即终结手势(等同\n // pointercancel 的清理但不弹回),交由下方 presence 走标准关闭/打开管线。\n if (this._isDragging() && !this.open) this._cancelActiveDrag()\n if (this._userOpenChange.consume()) this.emitOpenChange()\n // controlled 拖拽关闭的回写结果:确认关闭则清除闭合位悬停状态走正常关闭;\n // 拒绝关闭(回写 open=true)则从闭合位(含留边)弹回。\n if (this._dragAwaitWriteback) {\n this._cancelDragAwait()\n const dialog = this.dialog\n if (dialog && !this.open) {\n dialog.classList.remove('is-visible')\n this._clearDragStyles(dialog)\n } else if (dialog && this.open) {\n this._settleRebound(dialog, 0, this._dragCloseDistance(dialog))\n }\n }\n // 关闭且无进行中的拖拽/收尾时,清理可能残留的内联拖拽样式(如收尾路径写在\n // 打开位的 translateX(0px))。若不清理,0px 内联会盖住闭合态 CSS transform,\n // 后续开/关都不再触发过渡。收尾进行中由 _finishSettle 自行收尾,这里丢弃它\n // 以免回调回头写内联与关闭管线竞争。\n if (!this.open && !this._isDragging()) {\n this._abortSettle()\n const dialog = this.dialog\n if (dialog) this._clearDragStyles(dialog)\n }\n this._presence.sync(this.open)\n if (this.open) {\n // presence.sync 内的 showModal() 把 dialog 提升进 top layer。双击手势本身\n // 留下的活选区会被浏览器拿去和提升后的新布局重新解析,结果选区落到刚挂载的\n // drawer 正文上。脆弱的是「手势来源的活选区」,不是 dblclick 的判定窗口:把\n // 打开推迟 250ms(早过双击判定)仍然复现。选区由浏览器在 showModal() 调用\n // 内部生成:脚本没有任何 Selection API 调用,在 dblclick 上 preventDefault\n // 也拦不住。\n //\n // 实测(Chromium)showModal() 不会动打开前已经存在的选区,所以这一次清理\n // 同时也覆盖「先选中页面文本、再打开」的情况——modal 打开后被选中的内容挡在\n // backdrop 后面本来也已不可用。这里只清文档选区,不动 user-select,drawer 内正文\n // 仍可正常拖选;拖拽 handle 与 toggle 时序不受影响。\n window.getSelection()?.removeAllRanges()\n // presence.sync 已同步发起 showModal:此刻 dialog.open 为真,计数出正确\n // 的层序 depth 并驱动下层缩放。直接同步 register,不等待 is-visible\n //(那要再等一帧,且打开过渡期间上层关系已应确立)。\n this._nestedLayers.register()\n // 原生 dialog 登记为开启态 overlay:挂在它上面的 portal panel 在逻辑组合树上\n // 成为本层后代,Escape 仲裁据此判出最内层(issue #120 Block 1)。\n const dialog = this.dialog\n if (dialog) {\n // 同一 panel 重新 claim = 新的一次开启:旧会话(含其子层)整体作废。\n this._handle?.release()\n this._handle = this._overlay.claim(dialog, { ancestryFrom: this })\n this._syncDragInert()\n }\n } else {\n this._nestedLayers.unregister()\n this._handle?.release()\n this._handle = null\n }\n }\n if (props.has('open') || props.has('noScrollLock')) this._syncScrollLock()\n }\n\n private handleTransitionEnd(e: TransitionEvent) {\n // transitionend 会从后代元素冒泡上来(slotted 消费者内容、嵌套子 drawer)。\n // 只处理 dialog 自身的事件:presence 与收尾都只认本层 dialog 的过渡,\n // 后代一条 transform 过渡结束不能提前终结本层收尾(与 presence 内部守卫同规则)。\n if (e.target !== e.currentTarget) return\n this._presence.handleTransitionEnd(e)\n\n // 收尾过渡结束:::backdrop 的 opacity 过渡 target 也是 dialog(带 pseudoElement),\n // 因此再按 propertyName 只认 transform。\n if (e.propertyName === 'transform') this._finishSettle()\n }\n\n private handleKeydown(e: KeyboardEvent) {\n if (e.key !== 'Escape') return\n /*\n * Escape 的归属由共享仲裁者在 document 捕获阶段判定(issue #120 Block 1);命中后\n * 它会 stopPropagation,本 handler 不再执行。此处保留两条兜底:\n * ① defaultPrevented 说明仲裁者已介入,不再重复关闭;\n * ② 兜底路径仍按原逻辑处理,避免「panel 未登记」时 Escape 失灵。\n *\n * 本 handler **不是**可随手删掉的迁移残留:仲裁者挂在 document 上,只对能跨 shadow\n * 边界的事件生效。实测(overlay-open-owner-260918,2026-09-18)摘掉 `@keydown` 后\n * layout.browser.spec.ts 的两例 Escape 用例立刻转红——它们派发的 keydown 没有\n * `composed: true`,事件止步于 shadow root,到不了 document。\n */\n if (e.defaultPrevented) return\n /*\n * nested 场景防连锁:子 drawer 的 dialog 经 slot 投影在本层 shadow 内,其\n * keydown composed 冒泡路径会再次经过本层 dialog(事件路径含 slot 宿主链)。\n * 若事件传播路径在本层 dialog 之前已存在其他 <dialog>,说明该事件来自子层,本层忽略。\n */\n const path = e.composedPath()\n const ownDialogIndex = path.indexOf(e.currentTarget as Node)\n if (ownDialogIndex > 0) {\n const hasChildDialog = path.slice(0, ownDialogIndex).some(n => n instanceof HTMLDialogElement)\n if (hasChildDialog) return\n }\n // 拖拽进行中忽略 ESC,避免手势与关闭管线竞争。\n if (this._isDragging()) {\n e.preventDefault()\n return\n }\n e.preventDefault()\n this._closeFromUser()\n }\n\n show() {\n if (this.open) return\n this.open = true\n }\n\n close() {\n if (!this.open) return\n this.open = false\n }\n\n private readonly _closeFromUser = () => {\n if (!this.open) return\n // controlled 悬停等待回写期间,关闭意图已在途(open-change 已派发):\n // Escape/backdrop/关闭 button 的重复触发不再派发第二次请求,也不与超时弹回竞争。\n if (this._dragAwaitWriteback) return\n if (this.controlled) {\n this.emitOpenChange(false)\n return\n }\n\n this._userOpenChange.mark()\n this.close()\n }\n\n /*\n * 仲裁者在 capture 阶段 preventDefault 了 Escape 的 keydown,UA 因此不再派发原生\n * cancel,但本 handler 仍是「原生 cancel 到达时也要走完整关闭管线」的契约入口,\n * **不可删**。实测(overlay-open-owner-260918,2026-09-18)摘掉 `@cancel` 后\n * drawer.spec.ts 的「no-backdrop-close 存在时 cancel 仍通过关闭过渡退出」与\n * remount-reconcile 的「打开态被移出文档再接回」两例立刻转红。\n */\n private handleCancel(e: Event) {\n // 保留 top layer 直到 CSS 过渡结束,避免原生关闭跳过退出动画。\n e.preventDefault()\n this._closeFromUser()\n }\n\n private handleNativeClose() {\n // 关闭态下的 close 事件只可能是我们自己 finishClosing 排队的异步事件(或冗余的\n // 外部关闭):交给 presence 消费 self-close 标志,避免标志泄漏到下一次真实关闭。\n if (!this.open) {\n this._presence.handleNativeClose()\n return\n }\n\n // 我们自己 dialog.close() 排队的 close 事件是异步任务:正常时序在本关闭会话内\n // 到达,快速「关闭→重开」时它在重新打开之后才到达(过期事件)。两种情况都已在\n // finishClosing 完成全部清理,由 presence 消费并返回 true,不能当成外部关闭把\n // 刚重开的 dialog 关掉——否则重开即被误关,表现为连续 toggle 丢失过渡动画。\n if (this._presence.handleNativeClose()) return\n\n // 悬停等待期间的原生关闭(如表单 method=\"dialog\"):视为回写窗口内的重复\n // 关闭意图,取消等待不补发请求;已原生关闭的 dialog 不再被超时弹回拉起。\n if (this._dragAwaitWriteback) {\n this._cancelDragAwait()\n return\n }\n\n // 原生关闭可绕过 cancel;controlled 时恢复受控状态,由 Consumer 决定是否关闭。\n if (this.controlled) {\n this._presence.sync(true)\n this.emitOpenChange(false)\n return\n }\n\n this._userOpenChange.mark()\n this.open = false\n }\n\n /*\n * backdrop 点击(backdrop click)的按-放链路回溯。\n *\n * 浏览器对「按下 → 拖动 → 松手」生成的 click 落在起点与松手点 DOM 的**共同祖先**\n * 上:从 panel 内容、拖拽 drag zone 甚至 backdrop 本处开始拖拽、松手落在 backdrop 上时,click 的\n * target 都是 dialog,与真正的轻点 backdrop 从 click 自身无法区分(真实鼠标/触摸下\n * 必现的误关路径,合成事件不派发 click 故自动化易漏测)。因此在 dialog 的\n * pointerdown 记录按下起点与是否落在 backdrop 上,click 时回溯校验:\n * - 按下起点不在 backdrop 上(从 panel 内容/drag zone 开始的链路)→ 不是点 backdrop,忽略;\n * - 按下到松手位移超过轻点量级 → 是拖拽松手而非点击,忽略;\n * 两者都通过才沿用「点 backdrop 关闭」语义。\n */\n private _backdropPress: { onMask: boolean; x: number; y: number } | null = null\n\n private handleDialogPointerDown(e: PointerEvent) {\n this._backdropPress = { onMask: e.target === e.currentTarget, x: e.clientX, y: e.clientY }\n }\n\n private handleBackdropClick(e: MouseEvent) {\n if (e.target !== (e.currentTarget as HTMLDialogElement)) return\n if (this.noBackdropClose) return\n // 拖拽进行中 pointer capture 使 click 落在 dialog 上,忽略以避免与手势竞争。\n if (this._isDragging()) return\n // detail 为 0 表示这次 click 不来自指针(键盘激活、程序化 .click()):\n // pointerdown 记录可能是上一次指针交互的残留,不能据此判定 backdrop 点击\n //(对齐 image-preview 的既有守卫)。\n if (e.detail === 0) return\n const press = this._backdropPress\n this._backdropPress = null\n if (!press || !press.onMask) return\n if (Math.hypot(e.clientX - press.x, e.clientY - press.y) > BACKDROP_TAP_DISTANCE) return\n this._closeFromUser()\n }\n\n private emitOpenChange(open = this.open) {\n dispatchOpenChangeEvent(this, open)\n }\n\n private _syncScrollLock(isOpen = this.open) {\n this._scrollLock.sync(isOpen && !this.noScrollLock)\n }\n\n override render() {\n const showHeader = this._hasHeaderSlot || !!this.heading\n const dialogLabel = this.dialogLabel.trim()\n const dialogLabelledBy = !dialogLabel && !this.headless && showHeader ? 'wui-drawer-heading' : nothing\n\n // 拖拽 drag zone:仅在打开且 draggable 时渲染;胶囊 + 加宽命中条贴在 drawer 内缘。\n // 胶囊是 drag zone 的**兄弟节点**而非后代:drag zone 要让开 header / footer(见 style.css),\n // 胶囊作为后代会连带继承那份让位量,被一起推离 panel 边缘。两者都是 dialog 的直接子节点,\n // 胶囊因此相对 dialog 定位、恒贴可抓取边缘,drag zone 则继续为端头上的 button 让位。\n //\n // 端头 drag edge 补上解耦留下的空档:胶囊贴回边缘后就不再落在 drag zone 的让开区里\n // (bottom + header 实测胶囊带 [8,12]、drag zone 带 [60,80]),那一段若没有命中层,\n // 胶囊就成了一条按不下去、悬停不亮的死装饰——R1 与 R3 在几何上本就不可同满足,\n // 结构解耦只让两者不再互相位移,命中层必须另给。drag edge 只在上下 placement 渲染:\n // 左右两条的胶囊本就落在 drag zone 内,多一个元素只会让 R5「左右几何不变」需要额外论证。\n //\n // 顺序要紧:胶囊排最后,两条 drag zone(边、区)都在它前面,hover / active / 确认态三条规则\n // 因此靠通用兄弟选择器 `~` 挂上去。写成相邻兄弟 `+` 的话 drag edge 那条会静默失效。\n const needsEdgeBand = this.placement === 'top' || this.placement === 'bottom'\n const dragBar = this.draggable\n ? html`\n ${\n needsEdgeBand\n ? html`<div class=\"wui-drawer-drag-edge\" @pointerdown=${this._handleDragPointerDown}></div>`\n : nothing\n }\n <div class=\"wui-drawer-drag-zone\" @pointerdown=${this._handleDragPointerDown}></div>\n <div class=\"wui-drawer-drag-bar\"></div>\n `\n : nothing\n\n // 保持同一个 dialog 实例,避免打开期间切换 headless 时脱离 top layer。\n return html`\n <dialog\n aria-label=${dialogLabel || nothing}\n aria-labelledby=${dialogLabelledBy}\n @cancel=${this.handleCancel}\n @close=${this.handleNativeClose}\n @click=${this.handleBackdropClick}\n @pointerdown=${this.handleDialogPointerDown}\n @keydown=${this.handleKeydown}\n @transitionend=${this.handleTransitionEnd}\n >\n ${\n this.headless\n ? html`<slot></slot>${dragBar}`\n : html`\n <div class=\"wui-drawer-body wui-glass\">\n <div\n class=\"wui-drawer-header\"\n id=\"wui-drawer-heading\"\n ?hidden=${!showHeader}\n @pointerdown=${this._handleSectionDragPointerDown}\n >\n <slot name=\"header\" @slotchange=${this.handleHeaderSlotChange}>\n ${this.heading ? html`<span class=\"wui-drawer-heading\">${this.heading}</span>` : nothing}\n </slot>\n </div>\n <div class=\"wui-drawer-content wui-scroll\">\n <slot></slot>\n </div>\n <div\n class=\"wui-drawer-footer\"\n ?hidden=${!this._hasFooterSlot}\n @pointerdown=${this._handleSectionDragPointerDown}\n >\n <slot name=\"footer\" @slotchange=${this.handleFooterSlotChange}></slot>\n </div>\n </div>\n ${\n this.closable\n ? html`\n <web-ui-button\n class=\"wui-drawer-close\"\n @click=${this._closeFromUser}\n aria-label=\"Close\"\n variant=\"secondary\"\n icon\n size=\"26\"\n >\n <web-ui-icon size=\"14\" .icon=${oouiClose}></web-ui-icon>\n </web-ui-button>\n `\n : nothing\n }\n ${dragBar}\n `\n }\n </dialog>\n `\n }\n\n declare readonly $events: {\n 'open-change': CustomEvent<{ open: boolean }>\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'web-ui-drawer': WebUiDrawer\n }\n}\n"],"mappings":"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"}