@greypan/web-ui 6.3.1 → 7.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 (170) hide show
  1. package/README.CN.md +145 -42
  2. package/README.md +156 -53
  3. package/dist/_virtual/{_@oxc-project_runtime@0.146.0 → _@oxc-project_runtime@0.149.0}/helpers/esm/decorate.js +1 -1
  4. package/dist/assets/glass.js +1 -1
  5. package/dist/assets/glass.js.map +1 -1
  6. package/dist/assets/menu-portal.js +1 -1
  7. package/dist/assets/menu-portal.js.map +1 -1
  8. package/dist/assets/overlay-motion.js +1 -1
  9. package/dist/assets/overlay-motion.js.map +1 -1
  10. package/dist/assets/selection-control.js +6 -0
  11. package/dist/assets/selection-control.js.map +1 -0
  12. package/dist/assets/toast-containers.js.map +1 -1
  13. package/dist/components/autocomplete/index.d.ts +2 -0
  14. package/dist/components/autocomplete/index.js +22 -23
  15. package/dist/components/autocomplete/index.js.map +1 -1
  16. package/dist/components/autocomplete/style.js +1 -1
  17. package/dist/components/autocomplete/style.js.map +1 -1
  18. package/dist/components/avatar/index.js +1 -1
  19. package/dist/components/back-top/index.js +1 -1
  20. package/dist/components/badge/index.js +1 -1
  21. package/dist/components/badge/style.js.map +1 -1
  22. package/dist/components/button/index.js +1 -1
  23. package/dist/components/button/style.js.map +1 -1
  24. package/dist/components/button-group/index.js +1 -1
  25. package/dist/components/checkbox/index.d.ts +9 -2
  26. package/dist/components/checkbox/index.js +52 -28
  27. package/dist/components/checkbox/index.js.map +1 -1
  28. package/dist/components/checkbox/style.js +1 -1
  29. package/dist/components/checkbox/style.js.map +1 -1
  30. package/dist/components/checkbox-group/index.d.ts +3 -0
  31. package/dist/components/checkbox-group/index.js +26 -15
  32. package/dist/components/checkbox-group/index.js.map +1 -1
  33. package/dist/components/checkbox-group/style.js +1 -1
  34. package/dist/components/checkbox-group/style.js.map +1 -1
  35. package/dist/components/collapse/index.js +3 -3
  36. package/dist/components/collapse/index.js.map +1 -1
  37. package/dist/components/context-menu/index.d.ts +10 -0
  38. package/dist/components/context-menu/index.js +53 -39
  39. package/dist/components/context-menu/index.js.map +1 -1
  40. package/dist/components/dialog/index.d.ts +6 -0
  41. package/dist/components/dialog/index.js +46 -21
  42. package/dist/components/dialog/index.js.map +1 -1
  43. package/dist/components/dialog/style.js +1 -1
  44. package/dist/components/dialog/style.js.map +1 -1
  45. package/dist/components/drawer/index.d.ts +22 -4
  46. package/dist/components/drawer/index.js +135 -107
  47. package/dist/components/drawer/index.js.map +1 -1
  48. package/dist/components/drawer/style.js +1 -1
  49. package/dist/components/drawer/style.js.map +1 -1
  50. package/dist/components/dropdown/index.d.ts +21 -1
  51. package/dist/components/dropdown/index.js +78 -55
  52. package/dist/components/dropdown/index.js.map +1 -1
  53. package/dist/components/dropdown-divider/index.js +1 -1
  54. package/dist/components/dropdown-header/index.js +1 -1
  55. package/dist/components/dropdown-item/index.js +1 -1
  56. package/dist/components/empty/index.js +1 -1
  57. package/dist/components/icon/index.js +1 -1
  58. package/dist/components/icon/style.js +1 -1
  59. package/dist/components/icon/style.js.map +1 -1
  60. package/dist/components/image-preview/index.js +89 -69
  61. package/dist/components/image-preview/index.js.map +1 -1
  62. package/dist/components/image-preview/style.js +1 -1
  63. package/dist/components/image-preview/style.js.map +1 -1
  64. package/dist/components/index.js +29 -29
  65. package/dist/components/input/index.js +4 -4
  66. package/dist/components/input/index.js.map +1 -1
  67. package/dist/components/input/style.js.map +1 -1
  68. package/dist/components/input-number/index.js +2 -2
  69. package/dist/components/input-number/index.js.map +1 -1
  70. package/dist/components/layout/index.js +3 -3
  71. package/dist/components/layout/index.js.map +1 -1
  72. package/dist/components/layout/style.js +1 -1
  73. package/dist/components/layout/style.js.map +1 -1
  74. package/dist/components/option/index.js +1 -1
  75. package/dist/components/popover/index.d.ts +8 -2
  76. package/dist/components/popover/index.js +48 -53
  77. package/dist/components/popover/index.js.map +1 -1
  78. package/dist/components/radio/index.d.ts +1 -1
  79. package/dist/components/radio/index.js +16 -15
  80. package/dist/components/radio/index.js.map +1 -1
  81. package/dist/components/radio/style.js +1 -1
  82. package/dist/components/radio/style.js.map +1 -1
  83. package/dist/components/radio-group/index.d.ts +3 -0
  84. package/dist/components/radio-group/index.js +26 -15
  85. package/dist/components/radio-group/index.js.map +1 -1
  86. package/dist/components/radio-group/style.js +1 -1
  87. package/dist/components/radio-group/style.js.map +1 -1
  88. package/dist/components/segmented/index.js +1 -1
  89. package/dist/components/segmented/style.js +1 -1
  90. package/dist/components/segmented/style.js.map +1 -1
  91. package/dist/components/segmented-trigger/index.js +1 -1
  92. package/dist/components/select/index.d.ts +2 -0
  93. package/dist/components/select/index.js +23 -22
  94. package/dist/components/select/index.js.map +1 -1
  95. package/dist/components/select/style.js +1 -1
  96. package/dist/components/select/style.js.map +1 -1
  97. package/dist/components/slider/index.js +1 -1
  98. package/dist/components/slider/index.js.map +1 -1
  99. package/dist/components/slider/style.js +1 -1
  100. package/dist/components/slider/style.js.map +1 -1
  101. package/dist/components/spinner/index.js +2 -2
  102. package/dist/components/spinner/index.js.map +1 -1
  103. package/dist/components/spinner/style.js +1 -1
  104. package/dist/components/spinner/style.js.map +1 -1
  105. package/dist/components/svg-draw-lines/index.d.ts +13 -2
  106. package/dist/components/svg-draw-lines/index.js +45 -32
  107. package/dist/components/svg-draw-lines/index.js.map +1 -1
  108. package/dist/components/switch/index.js +2 -2
  109. package/dist/components/switch/index.js.map +1 -1
  110. package/dist/components/switch/style.js +1 -1
  111. package/dist/components/switch/style.js.map +1 -1
  112. package/dist/components/textarea/index.js +5 -5
  113. package/dist/components/textarea/index.js.map +1 -1
  114. package/dist/components/textarea/style.js.map +1 -1
  115. package/dist/components/theme/index.d.ts +17 -0
  116. package/dist/components/theme/index.js +181 -21
  117. package/dist/components/theme/index.js.map +1 -1
  118. package/dist/components/theme/style.js +1 -1
  119. package/dist/components/theme/style.js.map +1 -1
  120. package/dist/components/toast/index.d.ts +1 -1
  121. package/dist/components/toast/manager.d.ts +1 -3
  122. package/dist/components/toast/manager.js +92 -49
  123. package/dist/components/toast/manager.js.map +1 -1
  124. package/dist/components/toast/style.js +1 -1
  125. package/dist/components/toast/style.js.map +1 -1
  126. package/dist/components/toast/toast.d.ts +37 -0
  127. package/dist/components/toast/toast.js +62 -9
  128. package/dist/components/toast/toast.js.map +1 -1
  129. package/dist/components/toast/types.d.ts +0 -4
  130. package/dist/components/tooltip/index.d.ts +6 -1
  131. package/dist/components/tooltip/index.js +17 -15
  132. package/dist/components/tooltip/index.js.map +1 -1
  133. package/dist/shared/form-association/index.js +1 -1
  134. package/dist/shared/form-association/index.js.map +1 -1
  135. package/dist/shared/gesture/drag-gesture.js +66 -64
  136. package/dist/shared/gesture/drag-gesture.js.map +1 -1
  137. package/dist/shared/gesture/physics.js +7 -29
  138. package/dist/shared/gesture/physics.js.map +1 -1
  139. package/dist/shared/menu-behavior/index.js +3 -4
  140. package/dist/shared/menu-behavior/index.js.map +1 -1
  141. package/dist/shared/menu-portal/menu-tree.js +1 -1
  142. package/dist/shared/menu-portal/menu-tree.js.map +1 -1
  143. package/dist/shared/option-portal/combobox-shell.js +7 -8
  144. package/dist/shared/option-portal/combobox-shell.js.map +1 -1
  145. package/dist/shared/overlay/anchored-panel.js +36 -31
  146. package/dist/shared/overlay/anchored-panel.js.map +1 -1
  147. package/dist/shared/overlay/native-dialog-presence.js +18 -2
  148. package/dist/shared/overlay/native-dialog-presence.js.map +1 -1
  149. package/dist/shared/overlay/nested-drawer-layers.js +7 -4
  150. package/dist/shared/overlay/nested-drawer-layers.js.map +1 -1
  151. package/dist/shared/overlay/open-overlay.js +182 -0
  152. package/dist/shared/overlay/open-overlay.js.map +1 -0
  153. package/dist/shared/overlay/overlay.js +1 -1
  154. package/dist/shared/overlay/overlay.js.map +1 -1
  155. package/dist/shared/overlay/portal.js +1 -1
  156. package/dist/shared/overlay/portal.js.map +1 -1
  157. package/dist/shared/overlay/presence.js +19 -22
  158. package/dist/shared/overlay/presence.js.map +1 -1
  159. package/dist/shared/theme/duration.js +9 -0
  160. package/dist/shared/theme/duration.js.map +1 -0
  161. package/dist/shared/theme/reduced-motion.js +15 -0
  162. package/dist/shared/theme/reduced-motion.js.map +1 -0
  163. package/dist/shared/visible-area/index.js +1 -1
  164. package/dist/shared/visible-area/index.js.map +1 -1
  165. package/package.json +3 -3
  166. package/dist/shared/overlay/composition.js +0 -57
  167. package/dist/shared/overlay/composition.js.map +0 -1
  168. package/dist/shared/overlay/lifecycle.js +0 -41
  169. package/dist/shared/overlay/lifecycle.js.map +0 -1
  170. package/dist/vite.svg +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"combobox-shell.js","names":[],"sources":["../../../src/shared/option-portal/combobox-shell.ts"],"sourcesContent":["import type { WebUiOption } from '@/components/option'\nimport { overlayComposition } from '@/shared/overlay/composition'\n\n/**\n * combobox 开合与 option 交互的共享骨架(select / autocomplete)。\n *\n * 与 option-portal 的边界约定一致:交互语义的\"差异点\"经回调注入,\n * 这里收口两组件逐行复制的生命周期不变量——\n * - open/close 状态迁移顺序:guard → 状态翻转 → open-change 派发 →\n * 激活钩子 → scroll-lock 配对 → portal 模式的 rAF 延迟 openOverlay\n * - close 路径:before-close 钩子(portal 内容先收敛)→ 激活清理 →\n * scroll-lock 释放 → closeOverlay(关闭后使 portal 引用失效)\n * - option 四事件监听对的绑定/解绑\n * - focusout 的 rAF :focus-within 复核\n * - 键盘导航的环绕游标算法\n */\n\nexport interface ComboboxOpenHooks {\n /** 是否允许打开(disabled / readonly 等由组件判断)。 */\n canOpen(): boolean\n getIsOpen(): boolean\n setIsOpen(open: boolean): void\n dispatchOpenChange(): void\n syncScrollLock(open: boolean): void\n /** 是否 portal 模式(决定 openOverlay 是否延迟到下一帧)。 */\n isPortal(): boolean\n openOverlay(isKeyboardNavigation: boolean): void\n closeOverlay(): void\n /** 打开时的激活项初始化(键盘导航取选中/首项,指针打开保留现状)。 */\n onOpen?(isKeyboardNavigation: boolean): void\n /** 关闭前钩子:autocomplete 需先把 light DOM 新增节点收敛进当前 portal content。 */\n onBeforeClose?(): void\n /** 关闭时清理激活状态(清 _activeIndex 与 active 属性)。 */\n onAfterClose?(): void\n}\n\nexport interface ComboboxOpenController {\n open(isKeyboardNavigation?: boolean): void\n close(): void\n}\n\nexport function createComboboxOpenController(hooks: ComboboxOpenHooks): ComboboxOpenController {\n function open(isKeyboardNavigation = false) {\n if (hooks.canOpen() === false || hooks.getIsOpen()) return\n hooks.setIsOpen(true)\n hooks.dispatchOpenChange()\n hooks.onOpen?.(isKeyboardNavigation)\n hooks.syncScrollLock(true)\n if (hooks.isPortal()) {\n requestAnimationFrame(() => {\n if (hooks.getIsOpen()) hooks.openOverlay(isKeyboardNavigation)\n })\n } else {\n hooks.openOverlay(isKeyboardNavigation)\n }\n }\n\n function close() {\n if (!hooks.getIsOpen()) return\n hooks.onBeforeClose?.()\n hooks.setIsOpen(false)\n hooks.dispatchOpenChange()\n hooks.onAfterClose?.()\n hooks.syncScrollLock(false)\n hooks.closeOverlay()\n }\n\n return { open, close }\n}\n\nexport interface OptionListenerHandlers {\n onClick: (event: Event) => void\n onPointerOver: (event: PointerEvent) => void\n onPointerDown: (event: PointerEvent) => void\n onUpdate: () => void\n}\n\n/** option 四事件监听对的绑定/解绑;两组件的 handler 签名一致,仅实现不同。 */\nexport function createOptionListenerBinding(handlers: OptionListenerHandlers) {\n function bind(option: WebUiOption) {\n option.addEventListener('click', handlers.onClick)\n option.addEventListener('pointerover', handlers.onPointerOver)\n option.addEventListener('pointerdown', handlers.onPointerDown)\n option.addEventListener('option-update', handlers.onUpdate)\n }\n function unbind(option: WebUiOption) {\n option.removeEventListener('click', handlers.onClick)\n option.removeEventListener('pointerover', handlers.onPointerOver)\n option.removeEventListener('pointerdown', handlers.onPointerDown)\n option.removeEventListener('option-update', handlers.onUpdate)\n }\n return { bind, unbind }\n}\n\n/**\n * focusout 关闭复核:焦点是否真的离开了宿主与面板。\n * rAF 等待 click/pointerdown 相关的焦点迁移完成后再判定。\n */\nexport function handleComboboxFocusOut(\n host: HTMLElement,\n getPanel: () => HTMLElement | null | undefined,\n getIsOpen: () => boolean,\n close: () => void\n): void {\n requestAnimationFrame(() => {\n const panel = getPanel()\n if (getIsOpen() && !host.matches(':focus-within') && !(panel && overlayComposition.hasFocusWithin(panel))) {\n close()\n }\n })\n}\n\n/**\n * 环绕游标:在 enabled 列表内按 delta 移动;从 -1(无激活)向下取首项、向上取末项。\n * 返回在 enabled 中的下一索引;调用方负责换算回全量 options 索引。\n */\nexport function nextWrappingIndex(enabledCount: number, currentIdx: number, delta: number): number {\n if (enabledCount === 0) return -1\n let nextIdx = currentIdx + delta\n if (nextIdx < 0) nextIdx = enabledCount - 1\n if (nextIdx >= enabledCount) nextIdx = 0\n return nextIdx\n}\n"],"mappings":";;AAyCA,SAAgB,EAA6B,GAAkD;CAC7F,SAAS,EAAK,IAAuB,IAAO;EACtC,EAAM,QAAQ,MAAM,MAAS,EAAM,UAAU,MACjD,EAAM,UAAU,EAAI,GACpB,EAAM,mBAAmB,GACzB,EAAM,SAAS,CAAoB,GACnC,EAAM,eAAe,EAAI,GACrB,EAAM,SAAS,IACjB,4BAA4B;GAC1B,AAAI,EAAM,UAAU,KAAG,EAAM,YAAY,CAAoB;EAC/D,CAAC,IAED,EAAM,YAAY,CAAoB;CAE1C;CAEA,SAAS,IAAQ;EACV,EAAM,UAAU,MACrB,EAAM,gBAAgB,GACtB,EAAM,UAAU,EAAK,GACrB,EAAM,mBAAmB,GACzB,EAAM,eAAe,GACrB,EAAM,eAAe,EAAK,GAC1B,EAAM,aAAa;CACrB;CAEA,OAAO;EAAE;EAAM;CAAM;AACvB;AAUA,SAAgB,EAA4B,GAAkC;CAC5E,SAAS,EAAK,GAAqB;EAIjC,AAHA,EAAO,iBAAiB,SAAS,EAAS,OAAO,GACjD,EAAO,iBAAiB,eAAe,EAAS,aAAa,GAC7D,EAAO,iBAAiB,eAAe,EAAS,aAAa,GAC7D,EAAO,iBAAiB,iBAAiB,EAAS,QAAQ;CAC5D;CACA,SAAS,EAAO,GAAqB;EAInC,AAHA,EAAO,oBAAoB,SAAS,EAAS,OAAO,GACpD,EAAO,oBAAoB,eAAe,EAAS,aAAa,GAChE,EAAO,oBAAoB,eAAe,EAAS,aAAa,GAChE,EAAO,oBAAoB,iBAAiB,EAAS,QAAQ;CAC/D;CACA,OAAO;EAAE;EAAM;CAAO;AACxB;AAMA,SAAgB,EACd,GACA,GACA,GACA,GACM;CACN,4BAA4B;EAC1B,IAAM,IAAQ,EAAS;EACvB,AAAI,EAAU,KAAK,CAAC,EAAK,QAAQ,eAAe,KAAK,EAAE,KAAS,EAAmB,eAAe,CAAK,MACrG,EAAM;CAEV,CAAC;AACH;AAMA,SAAgB,EAAkB,GAAsB,GAAoB,GAAuB;CACjG,IAAI,MAAiB,GAAG,OAAO;CAC/B,IAAI,IAAU,IAAa;CAG3B,OAFI,IAAU,MAAG,IAAU,IAAe,IACtC,KAAW,MAAc,IAAU,IAChC;AACT"}
1
+ {"version":3,"file":"combobox-shell.js","names":[],"sources":["../../../src/shared/option-portal/combobox-shell.ts"],"sourcesContent":["import type { WebUiOption } from '@/components/option'\nimport type { OpenOverlayHandle } from '@/shared/overlay/open-overlay'\n\n/**\n * combobox 开合与 option 交互的共享骨架(select / autocomplete)。\n *\n * 与 option-portal 的边界约定一致:交互语义的\"差异点\"经回调注入,\n * 这里收口两组件逐行复制的生命周期不变量——\n * - open/close 状态迁移顺序:guard → 状态翻转 → open-change 派发 →\n * 激活钩子 → scroll-lock 配对 → portal 模式的 rAF 延迟 openOverlay\n * - close 路径:before-close 钩子(portal 内容先收敛)→ 激活清理 →\n * scroll-lock 释放 → closeOverlay(关闭后使 portal 引用失效)\n * - option 四事件监听对的绑定/解绑\n * - focusout 的 rAF :focus-within 复核\n * - 键盘导航的环绕游标算法\n */\n\nexport interface ComboboxOpenHooks {\n /** 是否允许打开(disabled / readonly 等由组件判断)。 */\n canOpen(): boolean\n getIsOpen(): boolean\n setIsOpen(open: boolean): void\n dispatchOpenChange(): void\n syncScrollLock(open: boolean): void\n /** 是否 portal 模式(决定 openOverlay 是否延迟到下一帧)。 */\n isPortal(): boolean\n openOverlay(isKeyboardNavigation: boolean): void\n closeOverlay(): void\n /** 打开时的激活项初始化(键盘导航取选中/首项,指针打开保留现状)。 */\n onOpen?(isKeyboardNavigation: boolean): void\n /** 关闭前钩子:autocomplete 需先把 light DOM 新增节点收敛进当前 portal content。 */\n onBeforeClose?(): void\n /** 关闭时清理激活状态(清 _activeIndex 与 active 属性)。 */\n onAfterClose?(): void\n}\n\nexport interface ComboboxOpenController {\n open(isKeyboardNavigation?: boolean): void\n close(): void\n}\n\nexport function createComboboxOpenController(hooks: ComboboxOpenHooks): ComboboxOpenController {\n function open(isKeyboardNavigation = false) {\n if (hooks.canOpen() === false || hooks.getIsOpen()) return\n hooks.setIsOpen(true)\n hooks.dispatchOpenChange()\n hooks.onOpen?.(isKeyboardNavigation)\n hooks.syncScrollLock(true)\n if (hooks.isPortal()) {\n requestAnimationFrame(() => {\n if (hooks.getIsOpen()) hooks.openOverlay(isKeyboardNavigation)\n })\n } else {\n hooks.openOverlay(isKeyboardNavigation)\n }\n }\n\n function close() {\n if (!hooks.getIsOpen()) return\n hooks.onBeforeClose?.()\n hooks.setIsOpen(false)\n hooks.dispatchOpenChange()\n hooks.onAfterClose?.()\n hooks.syncScrollLock(false)\n hooks.closeOverlay()\n }\n\n return { open, close }\n}\n\nexport interface OptionListenerHandlers {\n onClick: (event: Event) => void\n onPointerOver: (event: PointerEvent) => void\n onPointerDown: (event: PointerEvent) => void\n onUpdate: () => void\n}\n\n/** option 四事件监听对的绑定/解绑;两组件的 handler 签名一致,仅实现不同。 */\nexport function createOptionListenerBinding(handlers: OptionListenerHandlers) {\n function bind(option: WebUiOption) {\n option.addEventListener('click', handlers.onClick)\n option.addEventListener('pointerover', handlers.onPointerOver)\n option.addEventListener('pointerdown', handlers.onPointerDown)\n option.addEventListener('option-update', handlers.onUpdate)\n }\n function unbind(option: WebUiOption) {\n option.removeEventListener('click', handlers.onClick)\n option.removeEventListener('pointerover', handlers.onPointerOver)\n option.removeEventListener('pointerdown', handlers.onPointerDown)\n option.removeEventListener('option-update', handlers.onUpdate)\n }\n return { bind, unbind }\n}\n\n/**\n * focusout 关闭复核:焦点是否真的离开了宿主与面板。\n * rAF 等待 click/pointerdown 相关的焦点迁移完成后再判定。\n */\nexport function handleComboboxFocusOut(\n host: HTMLElement,\n getHandle: () => OpenOverlayHandle | null | undefined,\n getIsOpen: () => boolean,\n close: () => void\n): void {\n requestAnimationFrame(() => {\n const handle = getHandle()\n if (getIsOpen() && !host.matches(':focus-within') && !handle?.hasFocusWithin()) {\n close()\n }\n })\n}\n\n/**\n * 环绕游标:在 enabled 列表内按 delta 移动;从 -1(无激活)向下取首项、向上取末项。\n * 返回在 enabled 中的下一索引;调用方负责换算回全量 options 索引。\n */\nexport function nextWrappingIndex(enabledCount: number, currentIdx: number, delta: number): number {\n if (enabledCount === 0) return -1\n let nextIdx = currentIdx + delta\n if (nextIdx < 0) nextIdx = enabledCount - 1\n if (nextIdx >= enabledCount) nextIdx = 0\n return nextIdx\n}\n"],"mappings":";AAyCA,SAAgB,EAA6B,GAAkD;CAC7F,SAAS,EAAK,IAAuB,IAAO;EACtC,EAAM,QAAQ,MAAM,MAAS,EAAM,UAAU,MACjD,EAAM,UAAU,EAAI,GACpB,EAAM,mBAAmB,GACzB,EAAM,SAAS,CAAoB,GACnC,EAAM,eAAe,EAAI,GACrB,EAAM,SAAS,IACjB,4BAA4B;GAC1B,AAAI,EAAM,UAAU,KAAG,EAAM,YAAY,CAAoB;EAC/D,CAAC,IAED,EAAM,YAAY,CAAoB;CAE1C;CAEA,SAAS,IAAQ;EACV,EAAM,UAAU,MACrB,EAAM,gBAAgB,GACtB,EAAM,UAAU,EAAK,GACrB,EAAM,mBAAmB,GACzB,EAAM,eAAe,GACrB,EAAM,eAAe,EAAK,GAC1B,EAAM,aAAa;CACrB;CAEA,OAAO;EAAE;EAAM;CAAM;AACvB;AAUA,SAAgB,EAA4B,GAAkC;CAC5E,SAAS,EAAK,GAAqB;EAIjC,AAHA,EAAO,iBAAiB,SAAS,EAAS,OAAO,GACjD,EAAO,iBAAiB,eAAe,EAAS,aAAa,GAC7D,EAAO,iBAAiB,eAAe,EAAS,aAAa,GAC7D,EAAO,iBAAiB,iBAAiB,EAAS,QAAQ;CAC5D;CACA,SAAS,EAAO,GAAqB;EAInC,AAHA,EAAO,oBAAoB,SAAS,EAAS,OAAO,GACpD,EAAO,oBAAoB,eAAe,EAAS,aAAa,GAChE,EAAO,oBAAoB,eAAe,EAAS,aAAa,GAChE,EAAO,oBAAoB,iBAAiB,EAAS,QAAQ;CAC/D;CACA,OAAO;EAAE;EAAM;CAAO;AACxB;AAMA,SAAgB,EACd,GACA,GACA,GACA,GACM;CACN,4BAA4B;EAC1B,IAAM,IAAS,EAAU;EACzB,AAAI,EAAU,KAAK,CAAC,EAAK,QAAQ,eAAe,KAAK,CAAC,GAAQ,eAAe,KAC3E,EAAM;CAEV,CAAC;AACH;AAMA,SAAgB,EAAkB,GAAsB,GAAoB,GAAuB;CACjG,IAAI,MAAiB,GAAG,OAAO;CAC/B,IAAI,IAAU,IAAa;CAG3B,OAFI,IAAU,MAAG,IAAU,IAAe,IACtC,KAAW,MAAc,IAAU,IAChC;AACT"}
@@ -1,52 +1,57 @@
1
- import { overlayComposition as e } from "./composition.js";
2
- import { defineOverlay as t } from "./overlay.js";
3
- import { hideOverlayPresence as n, showOverlayPresence as r } from "./presence.js";
4
- import { definePlugin as i } from "@greypan/js-kit";
1
+ import { defineOverlay as e } from "./overlay.js";
2
+ import { hideOverlayPresence as t, showOverlayPresence as n } from "./presence.js";
3
+ import { definePlugin as r } from "@greypan/js-kit";
5
4
  //#region src/shared/overlay/anchored-panel.ts
6
- var a = () => i((i) => {
7
- let a, o, s = () => o?.panel ?? i.getLocalPanel(), c = () => {
8
- o && (e.unregisterPanel(o.panel), o.restoreContent(), o.remove(), o = void 0, a = void 0);
9
- }, l = () => i.isPortal() ? (o ??= i.createPortal(), o.panel) : i.getLocalPanel(), u = (e, n) => {
10
- if (a) {
11
- a.updateAnchor(e), a.update(i.getPositioning());
5
+ var i = () => r((r) => {
6
+ let i, a, o = null, s = () => a?.panel ?? r.getLocalPanel(), c = () => {
7
+ o?.release(), o = null;
8
+ }, l = () => {
9
+ a && (a.restoreContent(), a.remove(), a = void 0, i = void 0);
10
+ }, u = () => r.isPortal() ? (a ??= r.createPortal(), a.panel) : r.getLocalPanel(), d = (t, n) => {
11
+ if (i) {
12
+ i.updateAnchor(t), i.update(r.getPositioning());
12
13
  return;
13
14
  }
14
- a = t().make({
15
- anchor: e,
15
+ i = e().make({
16
+ anchor: t,
16
17
  overlay: n,
17
- ...i.getPositioning()
18
+ ...r.getPositioning()
18
19
  });
19
- }, d = (t = !1) => {
20
- let n = i.getAnchor();
21
- if (!n) return;
22
- let o = l();
23
- o && (e.registerPanelFromAncestry(o, n), u(n, o), a?.open(), r(o, { isInstant: t }));
24
- }, f = () => {
25
- a?.dispose(), a = void 0;
26
- let t = s();
27
- t && e.unregisterPanel(t), c();
20
+ }, f = (e = !1) => {
21
+ let t = r.getAnchor();
22
+ if (!t) return;
23
+ let a = u();
24
+ a && (c(), o = r.openOverlay.claim(a, {
25
+ arbitration: r.arbitration,
26
+ ancestryFrom: t
27
+ }), d(t, a), i?.open(), n(a, { isInstant: e }));
28
+ }, p = () => {
29
+ i?.dispose(), i = void 0, c(), l();
28
30
  };
29
31
  return {
30
32
  getPanel() {
31
33
  return s();
32
34
  },
33
- open: d,
34
- async close(t) {
35
- a?.close();
36
- let r = s();
37
- return r && !await n(r) || t() ? !1 : (r && e.unregisterPanel(r), c(), !0);
35
+ getHandle() {
36
+ return o;
37
+ },
38
+ open: f,
39
+ async close(e) {
40
+ c(), i?.close();
41
+ let n = s();
42
+ return n && !await t(n) || e() ? !1 : (l(), !0);
38
43
  },
39
44
  updatePosition() {
40
- a?.update(i.getPositioning());
45
+ i?.update(r.getPositioning());
41
46
  },
42
47
  reconfigure(e) {
43
48
  let t = s()?.dataset.wuiPresence === "entering";
44
- f(), e && d(!t);
49
+ p(), e && f(!t);
45
50
  },
46
- dispose: f
51
+ dispose: p
47
52
  };
48
53
  });
49
54
  //#endregion
50
- export { a as defineAnchoredPanel };
55
+ export { i as defineAnchoredPanel };
51
56
 
52
57
  //# sourceMappingURL=anchored-panel.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"anchored-panel.js","names":[],"sources":["../../../src/shared/overlay/anchored-panel.ts"],"sourcesContent":["import { definePlugin } from '@greypan/js-kit'\n\nimport { overlayComposition } from './composition'\nimport type { OverlayOptions } from './overlay'\nimport { defineOverlay } from './overlay'\nimport type { OverlayApi } from './overlay'\nimport type { OverlayPortal } from './portal'\nimport { hideOverlayPresence, showOverlayPresence } from './presence'\n\nexport interface AnchoredPanelOptions {\n getAnchor(): HTMLElement | null\n getLocalPanel(): HTMLElement | null\n getPositioning(): OverlayOptions\n isPortal(): boolean\n createPortal(): OverlayPortal\n}\n\n/**\n * 管理锚定面板的本地/Portal 容器、定位器和可中断 presence。\n * 内容迁移与交互语义由各组件保留,避免把不同组件的 public contract 混入此模块。\n */\nexport interface AnchoredPanelApi {\n getPanel(): HTMLElement | null\n open(isInstant?: boolean): void\n close(isStillOpen: () => boolean): Promise<boolean>\n updatePosition(): void\n reconfigure(isOpen: boolean): void\n dispose(): void\n}\n\nexport const defineAnchoredPanel = () =>\n definePlugin<AnchoredPanelApi, AnchoredPanelOptions>(ctx => {\n let overlay: OverlayApi | undefined\n let portal: OverlayPortal | undefined\n\n const getPanel = () => portal?.panel ?? ctx.getLocalPanel()\n\n const disposePortal = () => {\n if (!portal) return\n overlayComposition.unregisterPanel(portal.panel)\n portal.restoreContent()\n portal.remove()\n portal = undefined\n overlay = undefined\n }\n\n const getOrCreatePanel = (): HTMLElement | null => {\n if (!ctx.isPortal()) return ctx.getLocalPanel()\n portal ??= ctx.createPortal()\n return portal.panel\n }\n\n const ensureOverlay = (anchor: HTMLElement, panel: HTMLElement) => {\n if (overlay) {\n overlay.updateAnchor(anchor)\n overlay.update(ctx.getPositioning())\n return\n }\n overlay = defineOverlay().make({ anchor, overlay: panel, ...ctx.getPositioning() })\n }\n\n const open = (isInstant = false) => {\n const anchor = ctx.getAnchor()\n if (!anchor) return\n const panel = getOrCreatePanel()\n if (!panel) return\n // portal panel 与宿主物理分离;本地 panel 也可能隔着 shadow 边界。\n // 以 anchor 祖先链寻找最近的已登记 overlay,建立统一逻辑父子关系。\n overlayComposition.registerPanelFromAncestry(panel, anchor)\n ensureOverlay(anchor, panel)\n overlay?.open()\n showOverlayPresence(panel, { isInstant })\n }\n\n const dispose = () => {\n overlay?.dispose()\n overlay = undefined\n const panel = getPanel()\n if (panel) overlayComposition.unregisterPanel(panel)\n disposePortal()\n }\n\n return {\n getPanel() {\n return getPanel()\n },\n\n open,\n\n async close(isStillOpen) {\n overlay?.close()\n const panel = getPanel()\n if (panel && !(await hideOverlayPresence(panel))) return false\n if (isStillOpen()) return false\n if (panel) overlayComposition.unregisterPanel(panel)\n disposePortal()\n return true\n },\n\n updatePosition() {\n overlay?.update(ctx.getPositioning())\n },\n\n reconfigure(isOpen) {\n const shouldAnimate = getPanel()?.dataset.wuiPresence === 'entering'\n dispose()\n if (isOpen) open(!shouldAnimate)\n },\n\n dispose\n }\n })\n"],"mappings":";;;;;AA8BA,IAAa,UACX,GAAqD,MAAO;CAC1D,IAAI,GACA,GAEE,UAAiB,GAAQ,SAAS,EAAI,cAAc,GAEpD,UAAsB;EACrB,MACL,EAAmB,gBAAgB,EAAO,KAAK,GAC/C,EAAO,eAAe,GACtB,EAAO,OAAO,GACd,IAAS,KAAA,GACT,IAAU,KAAA;CACZ,GAEM,UACC,EAAI,SAAS,KAClB,MAAW,EAAI,aAAa,GACrB,EAAO,SAFc,EAAI,cAAc,GAK1C,KAAiB,GAAqB,MAAuB;EACjE,IAAI,GAAS;GAEX,AADA,EAAQ,aAAa,CAAM,GAC3B,EAAQ,OAAO,EAAI,eAAe,CAAC;GACnC;EACF;EACA,IAAU,EAAc,CAAC,CAAC,KAAK;GAAE;GAAQ,SAAS;GAAO,GAAG,EAAI,eAAe;EAAE,CAAC;CACpF,GAEM,KAAQ,IAAY,OAAU;EAClC,IAAM,IAAS,EAAI,UAAU;EAC7B,IAAI,CAAC,GAAQ;EACb,IAAM,IAAQ,EAAiB;EAC1B,MAGL,EAAmB,0BAA0B,GAAO,CAAM,GAC1D,EAAc,GAAQ,CAAK,GAC3B,GAAS,KAAK,GACd,EAAoB,GAAO,EAAE,aAAU,CAAC;CAC1C,GAEM,UAAgB;EAEpB,AADA,GAAS,QAAQ,GACjB,IAAU,KAAA;EACV,IAAM,IAAQ,EAAS;EAEvB,AADI,KAAO,EAAmB,gBAAgB,CAAK,GACnD,EAAc;CAChB;CAEA,OAAO;EACL,WAAW;GACT,OAAO,EAAS;EAClB;EAEA;EAEA,MAAM,MAAM,GAAa;GACvB,GAAS,MAAM;GACf,IAAM,IAAQ,EAAS;GAKvB,OAJI,KAAS,CAAE,MAAM,EAAoB,CAAK,KAC1C,EAAY,IAAU,MACtB,KAAO,EAAmB,gBAAgB,CAAK,GACnD,EAAc,GACP;EACT;EAEA,iBAAiB;GACf,GAAS,OAAO,EAAI,eAAe,CAAC;EACtC;EAEA,YAAY,GAAQ;GAClB,IAAM,IAAgB,EAAS,CAAC,EAAE,QAAQ,gBAAgB;GAE1D,AADA,EAAQ,GACJ,KAAQ,EAAK,CAAC,CAAa;EACjC;EAEA;CACF;AACF,CAAC"}
1
+ {"version":3,"file":"anchored-panel.js","names":[],"sources":["../../../src/shared/overlay/anchored-panel.ts"],"sourcesContent":["import { definePlugin } from '@greypan/js-kit'\n\nimport type { OpenOverlay, OpenOverlayHandle, OverlayArbitration } from './open-overlay'\nimport type { OverlayOptions } from './overlay'\nimport { defineOverlay } from './overlay'\nimport type { OverlayApi } from './overlay'\nimport type { OverlayPortal } from './portal'\nimport { hideOverlayPresence, showOverlayPresence } from './presence'\n\nexport interface AnchoredPanelOptions {\n getAnchor(): HTMLElement | null\n getLocalPanel(): HTMLElement | null\n getPositioning(): OverlayOptions\n isPortal(): boolean\n createPortal(): OverlayPortal\n /**\n * 承载「本面板是一次开启态浮层」的实例:登记与 Escape 归属由它完成,本模块不再\n * 自己维护逻辑父子关系。\n *\n * 必须是**调用方持有的那个实例**(而不是这里新建一个)——调用方同时要用它的帧\n * 事务入口,两个实例会让「我开着吗」重新分裂。\n */\n openOverlay: OpenOverlay\n /** 本面板与 Escape 仲裁的关系。tooltip 传 `none`;缺省 `escape`。 */\n arbitration?: OverlayArbitration\n}\n\n/**\n * 管理锚定面板的本地/Portal 容器、定位器和可中断 presence。\n * 内容迁移与交互语义由各组件保留,避免把不同组件的 public contract 混入此模块。\n */\nexport interface AnchoredPanelApi {\n getPanel(): HTMLElement | null\n /**\n * 当前开启会话的句柄;未开启时为 `null`。\n * 查询(`contains` / `containsEvent` / `hasFocusWithin`)与 `setInert` 都走它,\n * 调用方因此不必持有 panel 引用,也不知道存在全局注册表。\n */\n getHandle(): OpenOverlayHandle | null\n open(isInstant?: boolean): void\n close(isStillOpen: () => boolean): Promise<boolean>\n updatePosition(): void\n reconfigure(isOpen: boolean): void\n dispose(): void\n}\n\nexport const defineAnchoredPanel = () =>\n definePlugin<AnchoredPanelApi, AnchoredPanelOptions>(ctx => {\n let overlay: OverlayApi | undefined\n let portal: OverlayPortal | undefined\n let handle: OpenOverlayHandle | null = null\n\n const getPanel = () => portal?.panel ?? ctx.getLocalPanel()\n\n const releaseHandle = () => {\n handle?.release()\n handle = null\n }\n\n const disposePortal = () => {\n if (!portal) return\n portal.restoreContent()\n portal.remove()\n portal = undefined\n overlay = undefined\n }\n\n const getOrCreatePanel = (): HTMLElement | null => {\n if (!ctx.isPortal()) return ctx.getLocalPanel()\n portal ??= ctx.createPortal()\n return portal.panel\n }\n\n const ensureOverlay = (anchor: HTMLElement, panel: HTMLElement) => {\n if (overlay) {\n overlay.updateAnchor(anchor)\n overlay.update(ctx.getPositioning())\n return\n }\n overlay = defineOverlay().make({ anchor, overlay: panel, ...ctx.getPositioning() })\n }\n\n const open = (isInstant = false) => {\n const anchor = ctx.getAnchor()\n if (!anchor) return\n const panel = getOrCreatePanel()\n if (!panel) return\n // portal panel 与宿主物理分离;本地 panel 也可能隔着 shadow 边界。\n // 以 anchor 祖先链寻找最近的已开启浮层,建立统一逻辑父子关系。\n // claim ⟺ open:重复 open 视为新的一次开启,旧会话整体作废。\n releaseHandle()\n handle = ctx.openOverlay.claim(panel, { arbitration: ctx.arbitration, ancestryFrom: anchor })\n ensureOverlay(anchor, panel)\n overlay?.open()\n showOverlayPresence(panel, { isInstant })\n }\n\n const dispose = () => {\n overlay?.dispose()\n overlay = undefined\n releaseHandle()\n disposePortal()\n }\n\n return {\n getPanel() {\n return getPanel()\n },\n\n getHandle() {\n return handle\n },\n\n open,\n\n async close(isStillOpen) {\n /*\n * 开启态在这一刻结束:仲裁归属与逻辑组合树同时撤销,不等退场动画。\n *\n * 撤销晚于 `open → false` 会让关闭中的面板继续当「最内层」——它已不再开启却仍\n * 占着名额,于是紧接着的 Escape 被它吞掉,外层浮层永远等不到自己那一次\n * (issue #120 承诺的「一次 Escape 关一层」退化成关不掉外面那层)。\n * 旧实现里仲裁候选读 `isOpen()`、组合树读登记表,两者寿命本就不同;本模块用\n * 「已登记 ⟺ 已开启」把二者统一到 open 语义上。\n *\n * 退场被中断(重新打开)时句柄已撤销,随后的 `open()` 会重新 claim —— 与\n * 「重复 open 视为新的一次开启」走同一条路径,不会留下无主的层。\n */\n releaseHandle()\n overlay?.close()\n const panel = getPanel()\n if (panel && !(await hideOverlayPresence(panel))) return false\n if (isStillOpen()) return false\n disposePortal()\n return true\n },\n\n updatePosition() {\n overlay?.update(ctx.getPositioning())\n },\n\n reconfigure(isOpen) {\n const shouldAnimate = getPanel()?.dataset.wuiPresence === 'entering'\n dispose()\n if (isOpen) open(!shouldAnimate)\n },\n\n dispose\n }\n })\n"],"mappings":";;;;AA8CA,IAAa,UACX,GAAqD,MAAO;CAC1D,IAAI,GACA,GACA,IAAmC,MAEjC,UAAiB,GAAQ,SAAS,EAAI,cAAc,GAEpD,UAAsB;EAE1B,AADA,GAAQ,QAAQ,GAChB,IAAS;CACX,GAEM,UAAsB;EACrB,MACL,EAAO,eAAe,GACtB,EAAO,OAAO,GACd,IAAS,KAAA,GACT,IAAU,KAAA;CACZ,GAEM,UACC,EAAI,SAAS,KAClB,MAAW,EAAI,aAAa,GACrB,EAAO,SAFc,EAAI,cAAc,GAK1C,KAAiB,GAAqB,MAAuB;EACjE,IAAI,GAAS;GAEX,AADA,EAAQ,aAAa,CAAM,GAC3B,EAAQ,OAAO,EAAI,eAAe,CAAC;GACnC;EACF;EACA,IAAU,EAAc,CAAC,CAAC,KAAK;GAAE;GAAQ,SAAS;GAAO,GAAG,EAAI,eAAe;EAAE,CAAC;CACpF,GAEM,KAAQ,IAAY,OAAU;EAClC,IAAM,IAAS,EAAI,UAAU;EAC7B,IAAI,CAAC,GAAQ;EACb,IAAM,IAAQ,EAAiB;EAC1B,MAIL,EAAc,GACd,IAAS,EAAI,YAAY,MAAM,GAAO;GAAE,aAAa,EAAI;GAAa,cAAc;EAAO,CAAC,GAC5F,EAAc,GAAQ,CAAK,GAC3B,GAAS,KAAK,GACd,EAAoB,GAAO,EAAE,aAAU,CAAC;CAC1C,GAEM,UAAgB;EAIpB,AAHA,GAAS,QAAQ,GACjB,IAAU,KAAA,GACV,EAAc,GACd,EAAc;CAChB;CAEA,OAAO;EACL,WAAW;GACT,OAAO,EAAS;EAClB;EAEA,YAAY;GACV,OAAO;EACT;EAEA;EAEA,MAAM,MAAM,GAAa;GAcvB,AADA,EAAc,GACd,GAAS,MAAM;GACf,IAAM,IAAQ,EAAS;GAIvB,OAHI,KAAS,CAAE,MAAM,EAAoB,CAAK,KAC1C,EAAY,IAAU,MAC1B,EAAc,GACP;EACT;EAEA,iBAAiB;GACf,GAAS,OAAO,EAAI,eAAe,CAAC;EACtC;EAEA,YAAY,GAAQ;GAClB,IAAM,IAAgB,EAAS,CAAC,EAAE,QAAQ,gBAAgB;GAE1D,AADA,EAAQ,GACJ,KAAQ,EAAK,CAAC,CAAa;EACjC;EAEA;CACF;AACF,CAAC"}
@@ -8,7 +8,7 @@ var n = () => t((t) => {
8
8
  r !== void 0 && (cancelAnimationFrame(r), r = void 0);
9
9
  }, c = () => {
10
10
  let e = t.getDialog();
11
- !e || !i || t.isOpen() || (o(), i = !1, e.classList.remove("is-closing", "is-visible"), e.open && (a = !0, e.close?.()));
11
+ e && i && !t.isOpen() && (o(), i = !1, e.classList.remove("is-closing", "is-visible"), e.open && (a = !0, e.close?.()));
12
12
  }, l = () => {
13
13
  let e = t.getDialog();
14
14
  if (e) {
@@ -16,7 +16,7 @@ var n = () => t((t) => {
16
16
  e.showModal?.();
17
17
  } catch {}
18
18
  e.offsetWidth, s(), r = requestAnimationFrame(() => {
19
- r = void 0, !(!t.isConnected() || !t.isOpen() || !e.open) && e.classList.add("is-visible");
19
+ r = void 0, t.isConnected() && t.isOpen() && e.open && e.classList.add("is-visible");
20
20
  });
21
21
  }
22
22
  }, u = () => {
@@ -28,11 +28,27 @@ var n = () => t((t) => {
28
28
  }
29
29
  r.classList.add("is-closing"), r.classList.remove("is-visible"), o(), n = setTimeout(() => c(), e(r) + 80);
30
30
  }
31
+ }, d = (e) => {
32
+ try {
33
+ return e.matches(":modal");
34
+ } catch {
35
+ return !1;
36
+ }
31
37
  };
32
38
  return {
33
39
  sync(e) {
34
40
  e ? l() : u();
35
41
  },
42
+ reconcile() {
43
+ let e = t.getDialog();
44
+ if (e) {
45
+ if (!t.isOpen()) {
46
+ s(), o(), i = !1, e.open && (a = !0, e.close()), e.classList.remove("is-closing", "is-visible");
47
+ return;
48
+ }
49
+ e.open && !d(e) && (a = !0, e.close()), l();
50
+ }
51
+ },
36
52
  handleTransitionEnd(e) {
37
53
  e.target === t.getDialog() && e.propertyName === "transform" && c();
38
54
  },
@@ -1 +1 @@
1
- {"version":3,"file":"native-dialog-presence.js","names":[],"sources":["../../../src/shared/overlay/native-dialog-presence.ts"],"sourcesContent":["import { definePlugin } from '@greypan/js-kit'\n\nimport { getTransitionDuration } from './presence'\n\nexport interface NativeDialogPresenceOptions {\n getDialog(): HTMLDialogElement | null\n isConnected(): boolean\n isOpen(): boolean\n}\n\nexport interface NativeDialogPresenceApi {\n sync(open: boolean): void\n handleTransitionEnd(event: TransitionEvent): void\n /**\n * 处理原生 dialog 的 `close` 事件。\n *\n * 返回 `true` 表示该事件是本模块自己 `finishClosing → dialog.close()` 排队的\n * 异步事件(正常时序在本关闭会话内到达,快速重开时在重开后到达的过期事件),\n * 消费方不应把它当成外部关闭;返回 `false` 表示真实的外部关闭\n * (如表单 method=\"dialog\"、宿主程序化 dialog.close()),消费方需自行恢复状态。\n */\n handleNativeClose(): boolean\n dispose(): void\n}\n\n// 管理原生 dialog 保持在 top layer 直至退出动画完成的生命周期。\nexport const defineNativeDialogPresence = () =>\n definePlugin<NativeDialogPresenceApi, NativeDialogPresenceOptions>(ctx => {\n let closeFallbackTimer: ReturnType<typeof setTimeout> | undefined\n let openFrame: number | undefined\n let isClosing = false\n // 由本模块 finishClosing 主动 dialog.close() 时置 true。原生 dialog 的 close 事件\n // 是异步任务(queue a task),可能在之后任意时刻才到达:正常时序在本关闭会话内\n // 到达(消费方 open 已为 false,本就该忽略);快速「关闭→重开」时它在重新打开\n // 之后才到达,若消费方把它当成外部关闭,会把刚重开的 dialog 关掉——这是 drawer\n // 连续开关后丢失过渡动画(重开即被误关)的根因。置位后由消费方的\n // handleNativeClose 经 handleNativeClose() 消费;消费方 open 为 false 提前返回时\n // 也必须调用一次以消费标志,避免泄漏到下一次真实关闭。\n let selfClosePending = false\n\n const clearCloseFallback = () => {\n if (closeFallbackTimer === undefined) return\n clearTimeout(closeFallbackTimer)\n closeFallbackTimer = undefined\n }\n\n const cancelOpenFrame = () => {\n if (openFrame === undefined) return\n cancelAnimationFrame(openFrame)\n openFrame = undefined\n }\n\n const finishClosing = () => {\n const dialog = ctx.getDialog()\n if (!dialog || !isClosing || ctx.isOpen()) return\n clearCloseFallback()\n isClosing = false\n dialog.classList.remove('is-closing', 'is-visible')\n if (dialog.open) {\n selfClosePending = true\n dialog.close?.()\n }\n }\n\n const startOpening = () => {\n const dialog = ctx.getDialog()\n if (!dialog) return\n isClosing = false\n clearCloseFallback()\n dialog.classList.remove('is-closing')\n if (!dialog.open) {\n try {\n // 并发多个 modal dialog(nested drawer 场景)在规范中合法;\n // 此 try/catch 为防御性兜底(如对已在 top layer 的 dialog 重复 showModal\n // 会抛 InvalidStateError),失败时由下方 rAF 回调的 dialog.open 判断中止动画。\n dialog.showModal?.()\n } catch {\n // 保留未打开状态\n }\n }\n // 强制计算初始 top-layer 布局,确保 Safari 等浏览器在下一帧切换 class 时\n // 有稳定的 transition 起点,而不是偶发跳过进场过渡。\n void dialog.offsetWidth\n\n cancelOpenFrame()\n openFrame = requestAnimationFrame(() => {\n openFrame = undefined\n if (!ctx.isConnected() || !ctx.isOpen() || !dialog.open) return\n dialog.classList.add('is-visible')\n })\n }\n\n const startClosing = () => {\n const dialog = ctx.getDialog()\n if (!dialog?.open) return\n cancelOpenFrame()\n isClosing = true\n if (!dialog.classList.contains('is-visible')) {\n finishClosing()\n return\n }\n\n dialog.classList.add('is-closing')\n dialog.classList.remove('is-visible')\n clearCloseFallback()\n closeFallbackTimer = setTimeout(() => finishClosing(), getTransitionDuration(dialog) + 80)\n }\n\n return {\n sync(open) {\n if (open) startOpening()\n else startClosing()\n },\n\n handleTransitionEnd(event) {\n if (event.target !== ctx.getDialog() || event.propertyName !== 'transform') return\n finishClosing()\n },\n\n handleNativeClose() {\n if (selfClosePending) {\n // 我们自己的 dialog.close() 排队的事件:正常时序在本会话内到达、快速重开\n // 时是上一会话的过期事件——两种情况本模块都已在 finishClosing 完成全部清理,\n // 消费标志并让调用方跳过外部关闭处理即可。\n selfClosePending = false\n return true\n }\n // 真实的外部关闭:重置打开/关闭状态,等待消费方恢复(如 open=false)。\n cancelOpenFrame()\n clearCloseFallback()\n isClosing = false\n ctx.getDialog()?.classList.remove('is-closing', 'is-visible')\n return false\n },\n\n dispose() {\n cancelOpenFrame()\n clearCloseFallback()\n isClosing = false\n selfClosePending = false\n }\n }\n })\n"],"mappings":";;;AA0BA,IAAa,UACX,GAAmE,MAAO;CACxE,IAAI,GACA,GACA,IAAY,IAQZ,IAAmB,IAEjB,UAA2B;EAC3B,MAAuB,KAAA,MAC3B,aAAa,CAAkB,GAC/B,IAAqB,KAAA;CACvB,GAEM,UAAwB;EACxB,MAAc,KAAA,MAClB,qBAAqB,CAAS,GAC9B,IAAY,KAAA;CACd,GAEM,UAAsB;EAC1B,IAAM,IAAS,EAAI,UAAU;EACzB,CAAC,KAAU,CAAC,KAAa,EAAI,OAAO,MACxC,EAAmB,GACnB,IAAY,IACZ,EAAO,UAAU,OAAO,cAAc,YAAY,GAC9C,EAAO,SACT,IAAmB,IACnB,EAAO,QAAQ;CAEnB,GAEM,UAAqB;EACzB,IAAM,IAAS,EAAI,UAAU;EACxB,OAIL;OAHA,IAAY,IACZ,EAAmB,GACnB,EAAO,UAAU,OAAO,YAAY,GAChC,CAAC,EAAO,MACV,IAAI;IAIF,EAAO,YAAY;GACrB,QAAQ,CAER;GAOF,AAHA,EAAY,aAEZ,EAAgB,GAChB,IAAY,4BAA4B;IACtC,IAAY,KAAA,GACR,GAAC,EAAI,YAAY,KAAK,CAAC,EAAI,OAAO,KAAK,CAAC,EAAO,SACnD,EAAO,UAAU,IAAI,YAAY;GACnC,CAAC;EAXC;CAYJ,GAEM,UAAqB;EACzB,IAAM,IAAS,EAAI,UAAU;EACxB,OAAQ,MAGb;OAFA,EAAgB,GAChB,IAAY,IACR,CAAC,EAAO,UAAU,SAAS,YAAY,GAAG;IAC5C,EAAc;IACd;GACF;GAKA,AAHA,EAAO,UAAU,IAAI,YAAY,GACjC,EAAO,UAAU,OAAO,YAAY,GACpC,EAAmB,GACnB,IAAqB,iBAAiB,EAAc,GAAG,EAAsB,CAAM,IAAI,EAAE;EALzF;CAMF;CAEA,OAAO;EACL,KAAK,GAAM;GACT,AAAI,IAAM,EAAa,IAClB,EAAa;EACpB;EAEA,oBAAoB,GAAO;GACrB,EAAM,WAAW,EAAI,UAAU,KAAK,EAAM,iBAAiB,eAC/D,EAAc;EAChB;EAEA,oBAAoB;GAalB,OAZI,KAIF,IAAmB,IACZ,OAGT,EAAgB,GAChB,EAAmB,GACnB,IAAY,IACZ,EAAI,UAAU,CAAC,EAAE,UAAU,OAAO,cAAc,YAAY,GACrD;EACT;EAEA,UAAU;GAIR,AAHA,EAAgB,GAChB,EAAmB,GACnB,IAAY,IACZ,IAAmB;EACrB;CACF;AACF,CAAC"}
1
+ {"version":3,"file":"native-dialog-presence.js","names":[],"sources":["../../../src/shared/overlay/native-dialog-presence.ts"],"sourcesContent":["import { definePlugin } from '@greypan/js-kit'\n\nimport { getTransitionDuration } from './presence'\n\nexport interface NativeDialogPresenceOptions {\n getDialog(): HTMLDialogElement | null\n isConnected(): boolean\n isOpen(): boolean\n}\n\nexport interface NativeDialogPresenceApi {\n sync(open: boolean): void\n /**\n * 重挂载对账:宿主在打开态被移出文档再接回时调用。\n *\n * 平台行为(Chromium/WebKit 实测):`<dialog>` 随宿主移出文档时脱离 top layer,\n * 但 `open` 属性残留。此时 `sync(true)` 救不回来——`startOpening` 的\n * `if (!dialog.open)` 守卫会跳过 `showModal`,而直接对残留 open 的 dialog 调\n * `showModal` 会抛 `InvalidStateError`;且排队的 `close` 事件若被消费方当成\n * 外部关闭,会把 `open` 误置为 false。本方法先标记 self-close 再真正 close,\n * 随后走 `startOpening`(此刻 open 属性已清,`showModal` 正常执行,排队的\n * `close` 事件由 `handleNativeClose` 消费),dialog 因此同步回到 top layer。\n */\n reconcile(): void\n handleTransitionEnd(event: TransitionEvent): void\n /**\n * 处理原生 dialog 的 `close` 事件。\n *\n * 返回 `true` 表示该事件是本模块自己 `finishClosing → dialog.close()` 排队的\n * 异步事件(正常时序在本关闭会话内到达,快速重开时在重开后到达的过期事件),\n * 消费方不应把它当成外部关闭;返回 `false` 表示真实的外部关闭\n * (如表单 method=\"dialog\"、宿主程序化 dialog.close()),消费方需自行恢复状态。\n */\n handleNativeClose(): boolean\n dispose(): void\n}\n\n// 管理原生 dialog 保持在 top layer 直至退出动画完成的生命周期。\nexport const defineNativeDialogPresence = () =>\n definePlugin<NativeDialogPresenceApi, NativeDialogPresenceOptions>(ctx => {\n let closeFallbackTimer: ReturnType<typeof setTimeout> | undefined\n let openFrame: number | undefined\n let isClosing = false\n // 由本模块 finishClosing 主动 dialog.close() 时置 true。原生 dialog 的 close 事件\n // 是异步任务(queue a task),可能在之后任意时刻才到达:正常时序在本关闭会话内\n // 到达(消费方 open 已为 false,本就该忽略);快速「关闭→重开」时它在重新打开\n // 之后才到达,若消费方把它当成外部关闭,会把刚重开的 dialog 关掉——这是 drawer\n // 连续开关后丢失过渡动画(重开即被误关)的根因。置位后由消费方的\n // handleNativeClose 经 handleNativeClose() 消费;消费方 open 为 false 提前返回时\n // 也必须调用一次以消费标志,避免泄漏到下一次真实关闭。\n let selfClosePending = false\n\n const clearCloseFallback = () => {\n if (closeFallbackTimer === undefined) return\n clearTimeout(closeFallbackTimer)\n closeFallbackTimer = undefined\n }\n\n const cancelOpenFrame = () => {\n if (openFrame === undefined) return\n cancelAnimationFrame(openFrame)\n openFrame = undefined\n }\n\n const finishClosing = () => {\n const dialog = ctx.getDialog()\n if (!dialog || !isClosing || ctx.isOpen()) return\n clearCloseFallback()\n isClosing = false\n dialog.classList.remove('is-closing', 'is-visible')\n if (dialog.open) {\n selfClosePending = true\n dialog.close?.()\n }\n }\n\n const startOpening = () => {\n const dialog = ctx.getDialog()\n if (!dialog) return\n isClosing = false\n clearCloseFallback()\n dialog.classList.remove('is-closing')\n if (!dialog.open) {\n try {\n // 并发多个 modal dialog(nested drawer 场景)在规范中合法;\n // 此 try/catch 为防御性兜底(如对已在 top layer 的 dialog 重复 showModal\n // 会抛 InvalidStateError),失败时由下方 rAF 回调的 dialog.open 判断中止动画。\n dialog.showModal?.()\n } catch {\n // 保留未打开状态\n }\n }\n // 强制计算初始 top-layer 布局,确保 Safari 等浏览器在下一帧切换 class 时\n // 有稳定的 transition 起点,而不是偶发跳过进场过渡。\n void dialog.offsetWidth\n\n cancelOpenFrame()\n openFrame = requestAnimationFrame(() => {\n openFrame = undefined\n if (!ctx.isConnected() || !ctx.isOpen() || !dialog.open) return\n dialog.classList.add('is-visible')\n })\n }\n\n const startClosing = () => {\n const dialog = ctx.getDialog()\n if (!dialog?.open) return\n cancelOpenFrame()\n isClosing = true\n if (!dialog.classList.contains('is-visible')) {\n finishClosing()\n return\n }\n\n dialog.classList.add('is-closing')\n dialog.classList.remove('is-visible')\n clearCloseFallback()\n closeFallbackTimer = setTimeout(() => finishClosing(), getTransitionDuration(dialog) + 80)\n }\n\n // jsdom 的选择器引擎不认识 :modal(可能抛错);抛错按「不在 top layer」处理,\n // reconcile 随之走 close+showModal 的收敛路径,行为仍正确。\n const isInTopLayer = (dialog: HTMLDialogElement): boolean => {\n try {\n return dialog.matches(':modal')\n } catch {\n return false\n }\n }\n\n return {\n sync(open) {\n if (open) startOpening()\n else startClosing()\n },\n\n reconcile() {\n const dialog = ctx.getDialog()\n if (!dialog) return\n\n if (!ctx.isOpen()) {\n // 宿主在断连期间被置为关闭(或关闭动画被 disconnect 打断):\n // updated() 在断连时被跳过,inner dialog 可能仍残留 open 属性与\n // is-visible/is-closing class,补一次强制收敛到关闭态。\n cancelOpenFrame()\n clearCloseFallback()\n isClosing = false\n if (dialog.open) {\n selfClosePending = true\n dialog.close()\n }\n dialog.classList.remove('is-closing', 'is-visible')\n return\n }\n\n // 打开态重挂载:open 属性残留但已脱离 top layer 时,必须先真关闭再重开。\n if (dialog.open && !isInTopLayer(dialog)) {\n selfClosePending = true\n dialog.close()\n }\n startOpening()\n },\n\n handleTransitionEnd(event) {\n if (event.target !== ctx.getDialog() || event.propertyName !== 'transform') return\n finishClosing()\n },\n\n handleNativeClose() {\n if (selfClosePending) {\n // 我们自己的 dialog.close() 排队的事件:正常时序在本会话内到达、快速重开\n // 时是上一会话的过期事件——两种情况本模块都已在 finishClosing 完成全部清理,\n // 消费标志并让调用方跳过外部关闭处理即可。\n selfClosePending = false\n return true\n }\n // 真实的外部关闭:重置打开/关闭状态,等待消费方恢复(如 open=false)。\n cancelOpenFrame()\n clearCloseFallback()\n isClosing = false\n ctx.getDialog()?.classList.remove('is-closing', 'is-visible')\n return false\n },\n\n dispose() {\n cancelOpenFrame()\n clearCloseFallback()\n isClosing = false\n selfClosePending = false\n }\n }\n })\n"],"mappings":";;;AAsCA,IAAa,UACX,GAAmE,MAAO;CACxE,IAAI,GACA,GACA,IAAY,IAQZ,IAAmB,IAEjB,UAA2B;EAC3B,MAAuB,KAAA,MAC3B,aAAa,CAAkB,GAC/B,IAAqB,KAAA;CACvB,GAEM,UAAwB;EACxB,MAAc,KAAA,MAClB,qBAAqB,CAAS,GAC9B,IAAY,KAAA;CACd,GAEM,UAAsB;EAC1B,IAAM,IAAS,EAAI,UAAU;EACzB,AAAC,KAAW,KAAa,GAAI,OAAO,MACxC,EAAmB,GACnB,IAAY,IACZ,EAAO,UAAU,OAAO,cAAc,YAAY,GAC9C,EAAO,SACT,IAAmB,IACnB,EAAO,QAAQ;CAEnB,GAEM,UAAqB;EACzB,IAAM,IAAS,EAAI,UAAU;EACxB,OAIL;OAHA,IAAY,IACZ,EAAmB,GACnB,EAAO,UAAU,OAAO,YAAY,GAChC,CAAC,EAAO,MACV,IAAI;IAIF,EAAO,YAAY;GACrB,QAAQ,CAER;GAOF,AAHA,EAAY,aAEZ,EAAgB,GAChB,IAAY,4BAA4B;IACtC,IAAY,KAAA,GACP,EAAI,YAAY,KAAM,EAAI,OAAO,KAAM,EAAO,QACnD,EAAO,UAAU,IAAI,YAAY;GACnC,CAAC;EAXC;CAYJ,GAEM,UAAqB;EACzB,IAAM,IAAS,EAAI,UAAU;EACxB,OAAQ,MAGb;OAFA,EAAgB,GAChB,IAAY,IACR,CAAC,EAAO,UAAU,SAAS,YAAY,GAAG;IAC5C,EAAc;IACd;GACF;GAKA,AAHA,EAAO,UAAU,IAAI,YAAY,GACjC,EAAO,UAAU,OAAO,YAAY,GACpC,EAAmB,GACnB,IAAqB,iBAAiB,EAAc,GAAG,EAAsB,CAAM,IAAI,EAAE;EALzF;CAMF,GAIM,KAAgB,MAAuC;EAC3D,IAAI;GACF,OAAO,EAAO,QAAQ,QAAQ;EAChC,QAAQ;GACN,OAAO;EACT;CACF;CAEA,OAAO;EACL,KAAK,GAAM;GACT,AAAI,IAAM,EAAa,IAClB,EAAa;EACpB;EAEA,YAAY;GACV,IAAM,IAAS,EAAI,UAAU;GACxB,OAEL;QAAI,CAAC,EAAI,OAAO,GAAG;KAWjB,AAPA,EAAgB,GAChB,EAAmB,GACnB,IAAY,IACR,EAAO,SACT,IAAmB,IACnB,EAAO,MAAM,IAEf,EAAO,UAAU,OAAO,cAAc,YAAY;KAClD;IACF;IAOA,AAJI,EAAO,QAAQ,CAAC,EAAa,CAAM,MACrC,IAAmB,IACnB,EAAO,MAAM,IAEf,EAAa;GAPb;EAQF;EAEA,oBAAoB,GAAO;GACrB,EAAM,WAAW,EAAI,UAAU,KAAK,EAAM,iBAAiB,eAC/D,EAAc;EAChB;EAEA,oBAAoB;GAalB,OAZI,KAIF,IAAmB,IACZ,OAGT,EAAgB,GAChB,EAAmB,GACnB,IAAY,IACZ,EAAI,UAAU,CAAC,EAAE,UAAU,OAAO,cAAc,YAAY,GACrD;EACT;EAEA,UAAU;GAIR,AAHA,EAAgB,GAChB,EAAmB,GACnB,IAAY,IACZ,IAAmB;EACrB;CACF;AACF,CAAC"}
@@ -33,10 +33,13 @@ function c() {
33
33
  var l = () => e((e) => ({
34
34
  register() {
35
35
  let t = e.getDialog();
36
- t && (c(), i.add({
37
- dialog: t,
38
- placement: () => e.getPlacement()
39
- }), o());
36
+ if (t) {
37
+ for (let e of i) e.dialog === t && i.delete(e);
38
+ c(), i.add({
39
+ dialog: t,
40
+ placement: () => e.getPlacement()
41
+ }), o();
42
+ }
40
43
  },
41
44
  unregister() {
42
45
  let t = e.getDialog();
@@ -1 +1 @@
1
- {"version":3,"file":"nested-drawer-layers.js","names":[],"sources":["../../../src/shared/overlay/nested-drawer-layers.ts"],"sourcesContent":["/*\n * Nested drawer 层序管理(对齐 Base UI/shadcn nested drawer 行为):\n * 每打开一层 modal drawer,其下所有已打开的 drawer 按 0.95^n 缩放并向\n * 屏幕内侧平移,在顶层抽屉后方露出阶梯式卡片边缘(peeking edge);顶层全尺寸。\n *\n * 层序来源是原生 top layer 本身:depth = 在自身 showModal 之前已打开的\n * modal dialog 数。上层关闭通过 document 捕获阶段的 `close` 事件感知\n * (原生 close 事件不冒泡,但捕获阶段在 document 上可观察所有 target),\n * 底层重算 depth 后由 CSS transition 平滑回弹。\n *\n * 视觉规格:\n * - scale = 0.95^depth\n * - shift = shrink + depth * 12px(向屏幕内侧偏移,露出阶梯卡片边缘)\n * - 过渡 transform 450ms cubic-bezier(0.22, 1, 0.36, 1)\n *\n * 拖拽与弹簧期间 JS 直接写 dialog.style.transform(优先级高于本机制的\n * CSS 变量组合),顶层才有拖拽,故无冲突。\n */\n\nimport { definePlugin } from '@greypan/js-kit'\n\nconst DEPTH_VARIABLE = '--wui-internal-drawer-nested-depth'\nconst NESTED_SCALE = 0.95\nconst NESTED_PEEK_OFFSET = 12\n\ninterface NestedDrawerEntry {\n dialog: HTMLDialogElement\n placement: () => 'right' | 'left' | 'top' | 'bottom'\n}\n\nconst entries = new Set<NestedDrawerEntry>()\n\nlet documentListenerAttached = false\n\nfunction applyLayers() {\n const openEntries: Array<{\n entry: NestedDrawerEntry\n dialog: HTMLDialogElement\n size: number\n }> = []\n\n for (const entry of entries) {\n const dialog = entry.dialog\n if (!dialog.isConnected || !dialog.open) {\n entries.delete(entry)\n continue\n }\n const size =\n entry.placement() === 'left' || entry.placement() === 'right' ? dialog.offsetWidth : dialog.offsetHeight\n openEntries.push({ entry, dialog, size })\n }\n\n const total = openEntries.length\n for (let i = 0; i < total; i++) {\n const { dialog, size } = openEntries[i]\n // 注册顺序:先打开的在前 (index 小),后打开的在后 (index 大)。\n // depth = 在我之后打开的 drawer 数量 = (total - 1) - i\n const depth = total - 1 - i\n const scale = Math.pow(NESTED_SCALE, depth)\n const shrink = (size * (1 - scale)) / 2\n\n // 检查所有排在我之后的上层 drawer 的最大尺寸(若上层更宽,补偿宽度差确保露边)\n let aboveMaxSize = 0\n for (let j = i + 1; j < total; j++) {\n if (openEntries[j].size > aboveMaxSize) {\n aboveMaxSize = openEntries[j].size\n }\n }\n const sizeDiff = Math.max(0, aboveMaxSize - size)\n const shift = depth > 0 ? shrink + sizeDiff + depth * NESTED_PEEK_OFFSET : 0\n\n dialog.style.setProperty(DEPTH_VARIABLE, String(depth))\n dialog.style.setProperty('--wui-internal-drawer-nested-scale', scale.toFixed(4))\n dialog.style.setProperty('--wui-internal-drawer-nested-shift', `${shift.toFixed(2)}px`)\n if (depth > 0) {\n dialog.classList.add('is-nested-lower')\n } else {\n dialog.classList.remove('is-nested-lower')\n }\n }\n}\n\nfunction handleAnyDialogClose(event: Event) {\n if (!(event.target instanceof HTMLDialogElement)) return\n // 上层(或任意层)关闭后,重算所有存活 drawer 的 depth。\n if (entries.size > 0) applyLayers()\n}\n\nfunction ensureDocumentListener() {\n if (documentListenerAttached) return\n document.addEventListener('close', handleAnyDialogClose, true)\n documentListenerAttached = true\n}\n\nexport interface NestedDrawerLayerOptions {\n getDialog(): HTMLDialogElement | null\n getPlacement(): 'right' | 'left' | 'top' | 'bottom'\n}\n\nexport interface NestedDrawerLayerApi {\n /** dialog 进入 top layer(打开动画就绪)后调用,纳入层序管理。 */\n register(): void\n /** 关闭流程开始时调用:移出层序并触发底层回弹。 */\n unregister(): void\n dispose(): void\n}\n\nexport const defineNestedDrawerLayers = () =>\n definePlugin<NestedDrawerLayerApi, NestedDrawerLayerOptions>(ctx => ({\n register() {\n const dialog = ctx.getDialog()\n if (!dialog) return\n ensureDocumentListener()\n entries.add({ dialog, placement: () => ctx.getPlacement() })\n applyLayers()\n },\n\n unregister() {\n const dialog = ctx.getDialog()\n for (const entry of entries) {\n if (entry.dialog === dialog) entries.delete(entry)\n }\n // 移除后重算剩余层(顶层关闭 → 次层回到 depth 0)。\n if (entries.size > 0) applyLayers()\n },\n\n dispose() {\n const dialog = ctx.getDialog()\n for (const entry of entries) {\n if (entry.dialog === dialog) entries.delete(entry)\n }\n if (entries.size === 0 && documentListenerAttached) {\n document.removeEventListener('close', handleAnyDialogClose, true)\n documentListenerAttached = false\n }\n }\n }))\n"],"mappings":";;AAqBA,IAAM,IAAiB,sCACjB,IAAe,KACf,IAAqB,IAOrB,oBAAU,IAAI,IAAuB,GAEvC,IAA2B;AAE/B,SAAS,IAAc;CACrB,IAAM,IAID,CAAC;CAEN,KAAK,IAAM,KAAS,GAAS;EAC3B,IAAM,IAAS,EAAM;EACrB,IAAI,CAAC,EAAO,eAAe,CAAC,EAAO,MAAM;GACvC,EAAQ,OAAO,CAAK;GACpB;EACF;EACA,IAAM,IACJ,EAAM,UAAU,MAAM,UAAU,EAAM,UAAU,MAAM,UAAU,EAAO,cAAc,EAAO;EAC9F,EAAY,KAAK;GAAE;GAAO;GAAQ;EAAK,CAAC;CAC1C;CAEA,IAAM,IAAQ,EAAY;CAC1B,KAAK,IAAI,IAAI,GAAG,IAAI,GAAO,KAAK;EAC9B,IAAM,EAAE,WAAQ,YAAS,EAAY,IAG/B,IAAQ,IAAQ,IAAI,GACpB,IAAiB,MAAc,GAC/B,IAAU,KAAQ,IAAI,KAAU,GAGlC,IAAe;EACnB,KAAK,IAAI,IAAI,IAAI,GAAG,IAAI,GAAO,KAC7B,AAAI,EAAY,EAAE,CAAC,OAAO,MACxB,IAAe,EAAY,EAAE,CAAC;EAGlC,IAAM,IAAW,KAAK,IAAI,GAAG,IAAe,CAAI,GAC1C,IAAQ,IAAQ,IAAI,IAAS,IAAW,IAAQ,IAAqB;EAK3E,AAHA,EAAO,MAAM,YAAY,GAAgB,OAAO,CAAK,CAAC,GACtD,EAAO,MAAM,YAAY,sCAAsC,EAAM,QAAQ,CAAC,CAAC,GAC/E,EAAO,MAAM,YAAY,sCAAsC,GAAG,EAAM,QAAQ,CAAC,EAAE,GAAG,GAClF,IAAQ,IACV,EAAO,UAAU,IAAI,iBAAiB,IAEtC,EAAO,UAAU,OAAO,iBAAiB;CAE7C;AACF;AAEA,SAAS,EAAqB,GAAc;CACpC,EAAM,kBAAkB,qBAE1B,EAAQ,OAAO,KAAG,EAAY;AACpC;AAEA,SAAS,IAAyB;CAC5B,AAEJ,OADA,SAAS,iBAAiB,SAAS,GAAsB,EAAI,GAClC;AAC7B;AAeA,IAAa,UACX,GAA6D,OAAQ;CACnE,WAAW;EACT,IAAM,IAAS,EAAI,UAAU;EACxB,MACL,EAAuB,GACvB,EAAQ,IAAI;GAAE;GAAQ,iBAAiB,EAAI,aAAa;EAAE,CAAC,GAC3D,EAAY;CACd;CAEA,aAAa;EACX,IAAM,IAAS,EAAI,UAAU;EAC7B,KAAK,IAAM,KAAS,GAClB,AAAI,EAAM,WAAW,KAAQ,EAAQ,OAAO,CAAK;EAGnD,AAAI,EAAQ,OAAO,KAAG,EAAY;CACpC;CAEA,UAAU;EACR,IAAM,IAAS,EAAI,UAAU;EAC7B,KAAK,IAAM,KAAS,GAClB,AAAI,EAAM,WAAW,KAAQ,EAAQ,OAAO,CAAK;EAEnD,AAAI,EAAQ,SAAS,KAAK,MACxB,SAAS,oBAAoB,SAAS,GAAsB,EAAI,GAChE,IAA2B;CAE/B;AACF,EAAE"}
1
+ {"version":3,"file":"nested-drawer-layers.js","names":[],"sources":["../../../src/shared/overlay/nested-drawer-layers.ts"],"sourcesContent":["/*\n * Nested drawer 层序管理:\n * 每打开一层 modal drawer,其下所有已打开的 drawer 按 0.95^n 缩放并向\n * 屏幕内侧平移,在顶层抽屉后方露出阶梯式卡片边缘(peeking edge);顶层全尺寸。\n *\n * 层序来源是原生 top layer 本身:depth = 在自身 showModal 之前已打开的\n * modal dialog 数。上层关闭通过 document 捕获阶段的 `close` 事件感知\n * (原生 close 事件不冒泡,但捕获阶段在 document 上可观察所有 target),\n * 底层重算 depth 后由 CSS transition 平滑回弹。\n *\n * 视觉规格:\n * - scale = 0.95^depth\n * - shift = shrink + depth * 12px(向屏幕内侧偏移,露出阶梯卡片边缘)\n * - 过渡 transform 450ms cubic-bezier(0.22, 1, 0.36, 1)\n *\n * 拖拽与释放后的收尾期间 JS 直接写 dialog.style.transform(优先级高于本机制的\n * CSS 变量组合),顶层才有拖拽,故无冲突。\n */\n\nimport { definePlugin } from '@greypan/js-kit'\n\nconst DEPTH_VARIABLE = '--wui-internal-drawer-nested-depth'\nconst NESTED_SCALE = 0.95\nconst NESTED_PEEK_OFFSET = 12\n\ninterface NestedDrawerEntry {\n dialog: HTMLDialogElement\n placement: () => 'right' | 'left' | 'top' | 'bottom'\n}\n\nconst entries = new Set<NestedDrawerEntry>()\n\nlet documentListenerAttached = false\n\nfunction applyLayers() {\n const openEntries: Array<{\n entry: NestedDrawerEntry\n dialog: HTMLDialogElement\n size: number\n }> = []\n\n for (const entry of entries) {\n const dialog = entry.dialog\n if (!dialog.isConnected || !dialog.open) {\n entries.delete(entry)\n continue\n }\n const size =\n entry.placement() === 'left' || entry.placement() === 'right' ? dialog.offsetWidth : dialog.offsetHeight\n openEntries.push({ entry, dialog, size })\n }\n\n const total = openEntries.length\n for (let i = 0; i < total; i++) {\n const { dialog, size } = openEntries[i]\n // 注册顺序:先打开的在前 (index 小),后打开的在后 (index 大)。\n // depth = 在我之后打开的 drawer 数量 = (total - 1) - i\n const depth = total - 1 - i\n const scale = Math.pow(NESTED_SCALE, depth)\n const shrink = (size * (1 - scale)) / 2\n\n // 检查所有排在我之后的上层 drawer 的最大尺寸(若上层更宽,补偿宽度差确保露边)\n let aboveMaxSize = 0\n for (let j = i + 1; j < total; j++) {\n if (openEntries[j].size > aboveMaxSize) {\n aboveMaxSize = openEntries[j].size\n }\n }\n const sizeDiff = Math.max(0, aboveMaxSize - size)\n const shift = depth > 0 ? shrink + sizeDiff + depth * NESTED_PEEK_OFFSET : 0\n\n dialog.style.setProperty(DEPTH_VARIABLE, String(depth))\n dialog.style.setProperty('--wui-internal-drawer-nested-scale', scale.toFixed(4))\n dialog.style.setProperty('--wui-internal-drawer-nested-shift', `${shift.toFixed(2)}px`)\n if (depth > 0) {\n dialog.classList.add('is-nested-lower')\n } else {\n dialog.classList.remove('is-nested-lower')\n }\n }\n}\n\nfunction handleAnyDialogClose(event: Event) {\n if (!(event.target instanceof HTMLDialogElement)) return\n // 上层(或任意层)关闭后,重算所有存活 drawer 的 depth。\n if (entries.size > 0) applyLayers()\n}\n\nfunction ensureDocumentListener() {\n if (documentListenerAttached) return\n document.addEventListener('close', handleAnyDialogClose, true)\n documentListenerAttached = true\n}\n\nexport interface NestedDrawerLayerOptions {\n getDialog(): HTMLDialogElement | null\n getPlacement(): 'right' | 'left' | 'top' | 'bottom'\n}\n\nexport interface NestedDrawerLayerApi {\n /** dialog 进入 top layer(打开动画就绪)后调用,纳入层序管理。 */\n register(): void\n /** 关闭流程开始时调用:移出层序并触发底层回弹。 */\n unregister(): void\n dispose(): void\n}\n\nexport const defineNestedDrawerLayers = () =>\n definePlugin<NestedDrawerLayerApi, NestedDrawerLayerOptions>(ctx => ({\n register() {\n const dialog = ctx.getDialog()\n if (!dialog) return\n // 幂等保护:重挂载对账与 updated() 的 open 分支可能对同一 dialog 先后各调\n // 一次 register,重复条目会让 applyLayers 把同一层计两次。\n for (const entry of entries) {\n if (entry.dialog === dialog) entries.delete(entry)\n }\n ensureDocumentListener()\n entries.add({ dialog, placement: () => ctx.getPlacement() })\n applyLayers()\n },\n\n unregister() {\n const dialog = ctx.getDialog()\n for (const entry of entries) {\n if (entry.dialog === dialog) entries.delete(entry)\n }\n // 移除后重算剩余层(顶层关闭 → 次层回到 depth 0)。\n if (entries.size > 0) applyLayers()\n },\n\n dispose() {\n const dialog = ctx.getDialog()\n for (const entry of entries) {\n if (entry.dialog === dialog) entries.delete(entry)\n }\n if (entries.size === 0 && documentListenerAttached) {\n document.removeEventListener('close', handleAnyDialogClose, true)\n documentListenerAttached = false\n }\n }\n }))\n"],"mappings":";;AAqBA,IAAM,IAAiB,sCACjB,IAAe,KACf,IAAqB,IAOrB,oBAAU,IAAI,IAAuB,GAEvC,IAA2B;AAE/B,SAAS,IAAc;CACrB,IAAM,IAID,CAAC;CAEN,KAAK,IAAM,KAAS,GAAS;EAC3B,IAAM,IAAS,EAAM;EACrB,IAAI,CAAC,EAAO,eAAe,CAAC,EAAO,MAAM;GACvC,EAAQ,OAAO,CAAK;GACpB;EACF;EACA,IAAM,IACJ,EAAM,UAAU,MAAM,UAAU,EAAM,UAAU,MAAM,UAAU,EAAO,cAAc,EAAO;EAC9F,EAAY,KAAK;GAAE;GAAO;GAAQ;EAAK,CAAC;CAC1C;CAEA,IAAM,IAAQ,EAAY;CAC1B,KAAK,IAAI,IAAI,GAAG,IAAI,GAAO,KAAK;EAC9B,IAAM,EAAE,WAAQ,YAAS,EAAY,IAG/B,IAAQ,IAAQ,IAAI,GACpB,IAAiB,MAAc,GAC/B,IAAU,KAAQ,IAAI,KAAU,GAGlC,IAAe;EACnB,KAAK,IAAI,IAAI,IAAI,GAAG,IAAI,GAAO,KAC7B,AAAI,EAAY,EAAE,CAAC,OAAO,MACxB,IAAe,EAAY,EAAE,CAAC;EAGlC,IAAM,IAAW,KAAK,IAAI,GAAG,IAAe,CAAI,GAC1C,IAAQ,IAAQ,IAAI,IAAS,IAAW,IAAQ,IAAqB;EAK3E,AAHA,EAAO,MAAM,YAAY,GAAgB,OAAO,CAAK,CAAC,GACtD,EAAO,MAAM,YAAY,sCAAsC,EAAM,QAAQ,CAAC,CAAC,GAC/E,EAAO,MAAM,YAAY,sCAAsC,GAAG,EAAM,QAAQ,CAAC,EAAE,GAAG,GAClF,IAAQ,IACV,EAAO,UAAU,IAAI,iBAAiB,IAEtC,EAAO,UAAU,OAAO,iBAAiB;CAE7C;AACF;AAEA,SAAS,EAAqB,GAAc;CACpC,EAAM,kBAAkB,qBAE1B,EAAQ,OAAO,KAAG,EAAY;AACpC;AAEA,SAAS,IAAyB;CAC5B,AAEJ,OADA,SAAS,iBAAiB,SAAS,GAAsB,EAAI,GAClC;AAC7B;AAeA,IAAa,UACX,GAA6D,OAAQ;CACnE,WAAW;EACT,IAAM,IAAS,EAAI,UAAU;EACxB,OAGL;QAAK,IAAM,KAAS,GAClB,AAAI,EAAM,WAAW,KAAQ,EAAQ,OAAO,CAAK;GAInD,AAFA,EAAuB,GACvB,EAAQ,IAAI;IAAE;IAAQ,iBAAiB,EAAI,aAAa;GAAE,CAAC,GAC3D,EAAY;EAJuC;CAKrD;CAEA,aAAa;EACX,IAAM,IAAS,EAAI,UAAU;EAC7B,KAAK,IAAM,KAAS,GAClB,AAAI,EAAM,WAAW,KAAQ,EAAQ,OAAO,CAAK;EAGnD,AAAI,EAAQ,OAAO,KAAG,EAAY;CACpC;CAEA,UAAU;EACR,IAAM,IAAS,EAAI,UAAU;EAC7B,KAAK,IAAM,KAAS,GAClB,AAAI,EAAM,WAAW,KAAQ,EAAQ,OAAO,CAAK;EAEnD,AAAI,EAAQ,SAAS,KAAK,MACxB,SAAS,oBAAoB,SAAS,GAAsB,EAAI,GAChE,IAA2B;CAE/B;AACF,EAAE"}
@@ -0,0 +1,182 @@
1
+ import { definePlugin as e } from "@greypan/js-kit";
2
+ //#region src/shared/overlay/open-overlay.ts
3
+ var t = /* @__PURE__ */ new Set(), n = /* @__PURE__ */ new WeakMap(), r = /* @__PURE__ */ new WeakMap(), i = /* @__PURE__ */ new WeakMap(), a = /* @__PURE__ */ new WeakMap(), o = !1, s = 0, c = {
4
+ setInert() {},
5
+ adopt: () => c,
6
+ contains: () => !1,
7
+ containsEvent: () => !1,
8
+ hasFocusWithin: () => !1,
9
+ release() {}
10
+ };
11
+ function l(e) {
12
+ return e instanceof Element && e.assignedSlot ? e.assignedSlot : e.parentNode ? e.parentNode : e instanceof ShadowRoot ? e.host : null;
13
+ }
14
+ function u(e, t) {
15
+ let n = t;
16
+ for (; n;) {
17
+ if (n === e || e.contains(n)) return !0;
18
+ n = l(n);
19
+ }
20
+ return !1;
21
+ }
22
+ function d(e) {
23
+ let t = i.get(e);
24
+ if (!t) return;
25
+ let n = a.get(t);
26
+ n?.delete(e), n?.size === 0 && a.delete(t), i.delete(e);
27
+ }
28
+ function f(e, t) {
29
+ if (d(e), !t || t === e) return;
30
+ let n = a.get(t) ?? /* @__PURE__ */ new Set();
31
+ n.add(e), a.set(t, n), i.set(e, t);
32
+ }
33
+ function p(e, t) {
34
+ if (u(e, t)) return !0;
35
+ for (let n of a.get(e) ?? []) if (p(n, t)) return !0;
36
+ return !1;
37
+ }
38
+ function m(e) {
39
+ if (e.matches(":focus-within")) return !0;
40
+ for (let t of a.get(e) ?? []) if (m(t)) return !0;
41
+ return !1;
42
+ }
43
+ function h(e, t) {
44
+ let r = t === e ? l(t) : t;
45
+ for (; r;) {
46
+ if (r instanceof HTMLElement && r !== e) {
47
+ let e = n.get(r);
48
+ if (e && !e.released) return r;
49
+ }
50
+ r = l(r);
51
+ }
52
+ return null;
53
+ }
54
+ function g(e) {
55
+ if (!e.released) {
56
+ e.released = !0;
57
+ for (let t of Array.from(a.get(e.panel) ?? [])) {
58
+ let e = n.get(t);
59
+ e ? g(e) : d(t);
60
+ }
61
+ t.delete(e), n.delete(e.panel), a.delete(e.panel), d(e.panel), x();
62
+ }
63
+ }
64
+ function _(e) {
65
+ let i = n.get(e.panel);
66
+ i && g(i);
67
+ let a = {
68
+ panel: e.panel,
69
+ host: e.host,
70
+ arbitration: e.arbitration,
71
+ inert: !1,
72
+ seq: ++s,
73
+ released: !1
74
+ };
75
+ t.add(a), n.set(e.panel, a), f(e.panel, e.parent);
76
+ let o = {
77
+ setInert(e) {
78
+ a.released || (a.inert = e);
79
+ },
80
+ adopt(e, t = {}) {
81
+ return a.released ? c : _({
82
+ panel: e,
83
+ host: a.host,
84
+ arbitration: t.arbitration ?? "none",
85
+ parent: a.panel
86
+ });
87
+ },
88
+ contains(e) {
89
+ return !a.released && p(a.panel, e);
90
+ },
91
+ containsEvent(e) {
92
+ return !a.released && e.composedPath().some((e) => e instanceof Node && p(a.panel, e));
93
+ },
94
+ hasFocusWithin() {
95
+ return !a.released && m(a.panel);
96
+ },
97
+ release() {
98
+ g(a);
99
+ }
100
+ };
101
+ return r.set(o, a), x(), o;
102
+ }
103
+ function v(e) {
104
+ return !e.released && e.arbitration !== "none" && e.panel.isConnected;
105
+ }
106
+ function y() {
107
+ let e = null;
108
+ for (let n of t) if (v(n)) {
109
+ if (!e) {
110
+ e = n;
111
+ continue;
112
+ }
113
+ if (p(e.panel, n.panel)) {
114
+ e = n;
115
+ continue;
116
+ }
117
+ p(n.panel, e.panel) || n.seq > e.seq && (e = n);
118
+ }
119
+ return e;
120
+ }
121
+ function b(e) {
122
+ if (e.key !== "Escape" || e.defaultPrevented) return;
123
+ let t = y();
124
+ t && (e.preventDefault(), e.stopPropagation(), !t.inert && t.host.requestClose());
125
+ }
126
+ function x() {
127
+ let e = !1;
128
+ for (let n of t) if (!n.released && n.arbitration !== "none") {
129
+ e = !0;
130
+ break;
131
+ }
132
+ e !== o && (o = e, e ? document.addEventListener("keydown", b, !0) : document.removeEventListener("keydown", b, !0));
133
+ }
134
+ var S = () => e((e) => {
135
+ let t = 0, n = !1, i = /* @__PURE__ */ new Set(), a = (e) => {
136
+ cancelAnimationFrame(e.id), i.delete(e);
137
+ }, o = () => {
138
+ for (let e of Array.from(i)) a(e);
139
+ };
140
+ return {
141
+ claim(t, n = {}) {
142
+ return _({
143
+ panel: t,
144
+ host: e,
145
+ arbitration: n.arbitration ?? "escape",
146
+ parent: h(t, n.ancestryFrom ?? t) ?? void 0
147
+ });
148
+ },
149
+ scheduleFrame(e, o) {
150
+ if (n) return { cancel() {} };
151
+ let s = t, c = {
152
+ generation: s,
153
+ id: requestAnimationFrame(() => {
154
+ if (i.delete(c), !(n || s !== t)) {
155
+ if (o) {
156
+ let e = r.get(o);
157
+ if (!e || e.released) return;
158
+ }
159
+ e();
160
+ }
161
+ }),
162
+ cancel() {
163
+ a(c);
164
+ }
165
+ };
166
+ return i.add(c), c;
167
+ },
168
+ invalidate: () => {
169
+ t += 1, o();
170
+ },
171
+ suspend() {
172
+ n || (n = !0, o());
173
+ },
174
+ resume() {
175
+ n && (n = !1, t += 1);
176
+ }
177
+ };
178
+ });
179
+ //#endregion
180
+ export { S as defineOpenOverlay };
181
+
182
+ //# sourceMappingURL=open-overlay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"open-overlay.js","names":[],"sources":["../../../src/shared/overlay/open-overlay.ts"],"sourcesContent":["/**\n * 开启态浮层(open overlay)——「哪一层正开着」的唯一拥有者。\n *\n * 合并原先三个各自问一遍「你现在开着吗」的模块:composition(逻辑父子树)、\n * escape-dismiss(最内层仲裁)、lifecycle(帧事务失效)。合并的判据不是三者文件相邻,\n * 而是它们共享同一条不变量:**已登记 ⟺ 已开启 ⟺ 参与 Escape 仲裁**。\n *\n * 拆成三个模块时这条不变量没有主人,8 个浮层组件各自把它拼成三步协议\n * (`registerPanelFromAncestry` + `setPanel` / `unregisterPanel` + `setPanel(null)`)。\n * 结果是「漏撤销」与「重挂载后与仲裁失联」只能靠每个组件自己记得,而\n * `lifecycle` 还得让调用方手工传一个 `expectedOpen` 来复述「我开着吗」。\n *\n * 本模块的取舍:\n *\n * - 身份是**句柄**而非 panel 元素:`release()` 不需要调用方回忆当初传了哪个 panel。\n * - 开启状态是**声明**而非询问:仲裁时不再回调宿主问 `isOpen()`,宿主只提供\n * `requestClose()`;模块持有的真相即真相。\n * - `claim` 即「我开着」:一次 claim 对应一次开启,登记与仲裁是同一个动作。\n * - 查询走句柄(`contains` / `containsEvent` / `hasFocusWithin`),调用方不再需要\n * 持有 panel 引用,也不知道存在全局注册表。\n * - 实例作用域与会话作用域分开:帧事务的 lifetime 比一次开合长(tooltip 跨断连\n * `resume()`),把它压进会话句柄会在 `release()` 时误杀事务。\n *\n * 刻意**不**吸收 presence、滚动锁与 drawer 的层序(nested layers):它们回答的是\n * 视觉问题(`is-visible`、层序缩放),与「谁是最内层」正交,硬合并会互相污染。\n */\n\nimport { definePlugin } from '@greypan/js-kit'\n\n/**\n * 本层是否参与 Escape 仲裁。\n *\n * - `escape`:正常候选。命中即走宿主既有的用户关闭入口。\n * - `none`:只在逻辑组合树内,完全不参与仲裁,Escape 穿过它落到下层。用于 tooltip\n * —— 它今天就在树里(经 anchored panel 登记)但从不作为候选。\n *\n * 「暂时不可关闭」不在这里,而由 `setInert` 表达。静态与动态是同一件事的两面:\n * `no-escape-close` 这类策略看似静态,但它是**被观察的属性**,开启期间可被改写,\n * 所以同样要走动态通道(claim 后立即 `setInert(noEscapeClose)`)。把静态策略做成\n * 第三个枚举值只会产生一个没有真实调用方的成员。\n */\nexport type OverlayArbitration = 'escape' | 'none'\n\nexport interface OverlayClaimOptions {\n /** 默认 `escape`。 */\n arbitration?: OverlayArbitration\n /**\n * 沿该节点的祖先链寻找最近的已开启浮层,作为本层的逻辑父级;缺省用 panel 自身。\n *\n * portal 面板与宿主物理分离,从 panel 自身向上走找不到组合父级,必须由调用方给出\n * 锚点(trigger)或宿主,才能把「谁在谁里面」判对。\n *\n * **顺序约束**:祖先链查找要求 panel 与 ancestryFrom 此刻都已就位(在 DOM 中)。\n * 本模块不做「等一帧再补登记」——那会让「已登记」与「已开启」短暂不等。\n */\n ancestryFrom?: Node\n}\n\nexport interface OverlayFrameHandle {\n cancel(): void\n}\n\n/** 会话作用域:一次开启对应一个句柄。 */\nexport interface OpenOverlayHandle {\n /**\n * 惰性:仍是候选(照旧 `preventDefault()` 压掉原生 `<dialog>` 的关闭请求),但不走\n * 关闭入口。这是「暂时不可关闭」的唯一通道,静态策略与瞬时状态都走这里。\n *\n * 不改变本层在树里的位置,也不改变 `seq` —— 因此不会影响并列浮层的裁决顺序。\n * `none` 层不受影响(它本就不是候选)。\n *\n * **与会话同 lifetime,新会话恒从 `false` 开始**:`claim` 建立的每一层都重置为\n * 非惰性。所以凡是「重新 claim」的路径(`anchored-panel.reconfigure`、同一 panel 重复\n * `open()`)之后,都必须按当前状态重推一次,否则惰性会被静默清掉。重推放在哪里取决于\n * 该组件在 reconfigure 后是否渲染:会渲染的(select / autocomplete 在 `updated()` 里\n * 同步)可以把紧随 claim 的那一行降级为备份,不会渲染的(popover)必须自己补。\n */\n setInert(inert: boolean): void\n /**\n * 把子层 panel 纳入本句柄的子树(多级子菜单),默认不成为独立候选。\n *\n * 与 `claim` 的区别只在父级如何确定:`claim` 走祖先链,`adopt` 由调用方指名。\n *\n * 在已 `release()` 的句柄上调用**不建层**、返回空操作句柄 —— 否则会建出一个父层已死\n * 的孤层:它仍参与仲裁,却不在父层的子树里,除调用方手里那个句柄外没有回收路径。\n */\n adopt(panel: HTMLElement, options?: OverlayClaimOptions): OpenOverlayHandle\n /** 本层子树(含已 adopt 的子层)是否包含 node。 */\n contains(node: Node): boolean\n /** event 的完整 composed path 是否落入本层子树。 */\n containsEvent(event: Event): boolean\n /** 焦点是否在本层子树内。 */\n hasFocusWithin(): boolean\n /** 幂等。撤销本层及其全部子层,退出仲裁。 */\n release(): void\n}\n\n/** 实例作用域:每个宿主组件一次,跨开合与断连存活。 */\nexport interface OpenOverlay {\n claim(panel: HTMLElement, options?: OverlayClaimOptions): OpenOverlayHandle\n /**\n * 帧事务调度。`whileLive` 句柄一旦 release,回调自动作废 —— 取代原先由调用方\n * 手工传入 `expectedOpen` 的轮询(那个参数正是「模块还得回头问宿主」的证据)。\n */\n scheduleFrame(callback: () => void, whileLive?: OpenOverlayHandle): OverlayFrameHandle\n /** 结束当前事务:取消未执行的 frame,并让旧 generation 的回调失效。 */\n invalidate(): void\n /** 断连后停止调度(可 `resume()`);generation 继续单调递增,旧事务保持失效。 */\n suspend(): void\n resume(): void\n}\n\nexport interface OpenOverlayHost {\n /** 走宿主既有的用户关闭入口(内部负责 controlled 语义与 `open-change` 派发)。 */\n requestClose(): void\n}\n\ninterface Layer {\n panel: HTMLElement\n host: OpenOverlayHost\n arbitration: OverlayArbitration\n /** 动态惰性开关,与会话同 lifetime。 */\n inert: boolean\n /** 登记序号:互不包含的并列浮层里,后开的更靠上。 */\n seq: number\n released: boolean\n}\n\n/*\n * 模块级注册表:单例 document 监听器需要一个全局视野,这正是「谁是最内层」不能被\n * 拆到各组件里的原因。显式 release 是唯一的删除路径;`panel.isConnected` 作为兜底\n * 过滤,避免「宿主被移除但忘了 release」留下仍可仲裁的幽灵层(读 panel 的 DOM 属性\n * 不是回调宿主,不违反声明式的取舍)。\n */\nconst layers = new Set<Layer>()\nconst layerByPanel = new WeakMap<HTMLElement, Layer>()\nconst layerByHandle = new WeakMap<OpenOverlayHandle, Layer>()\nconst parents = new WeakMap<HTMLElement, HTMLElement>()\nconst childPanels = new WeakMap<HTMLElement, Set<HTMLElement>>()\nlet listening = false\nlet sequence = 0\n\n/*\n * 已失效句柄的空操作替身,用于 `adopt` 在 released 句柄上的返回值。其余句柄方法在\n * released 时都静默降级,返回值也必须一起降级,否则调用方会以为自己拿到了一棵可用子树。\n */\nconst inactiveHandle: OpenOverlayHandle = {\n setInert() {},\n adopt: () => inactiveHandle,\n contains: () => false,\n containsEvent: () => false,\n hasFocusWithin: () => false,\n release() {}\n}\n\nfunction nextCompositionNode(node: Node): Node | null {\n if (node instanceof Element && node.assignedSlot) return node.assignedSlot\n if (node.parentNode) return node.parentNode\n if (node instanceof ShadowRoot) return node.host\n return null\n}\n\nfunction nodeIsInside(root: HTMLElement, node: Node): boolean {\n let current: Node | null = node\n while (current) {\n if (current === root || root.contains(current)) return true\n current = nextCompositionNode(current)\n }\n return false\n}\n\nfunction detachFromParent(panel: HTMLElement): void {\n const parent = parents.get(panel)\n if (!parent) return\n const siblings = childPanels.get(parent)\n siblings?.delete(panel)\n if (siblings?.size === 0) childPanels.delete(parent)\n parents.delete(panel)\n}\n\nfunction attachToParent(panel: HTMLElement, parent: HTMLElement | undefined): void {\n detachFromParent(panel)\n if (!parent || parent === panel) return\n const siblings = childPanels.get(parent) ?? new Set<HTMLElement>()\n siblings.add(panel)\n childPanels.set(parent, siblings)\n parents.set(panel, parent)\n}\n\n/** panel 自身或其任何已登记子层是否包含 target。 */\nfunction containsPanel(panel: HTMLElement, target: Node): boolean {\n if (nodeIsInside(panel, target)) return true\n for (const child of childPanels.get(panel) ?? []) {\n if (containsPanel(child, target)) return true\n }\n return false\n}\n\nfunction hasFocusWithinPanel(panel: HTMLElement): boolean {\n if (panel.matches(':focus-within')) return true\n for (const child of childPanels.get(panel) ?? []) {\n if (hasFocusWithinPanel(child)) return true\n }\n return false\n}\n\n/** 从 from 沿祖先链找最近的已开启浮层 panel;panel 自身永远不算自己的父级。 */\nfunction findOpenAncestor(panel: HTMLElement, from: Node): HTMLElement | null {\n let current: Node | null = from === panel ? nextCompositionNode(from) : from\n while (current) {\n if (current instanceof HTMLElement && current !== panel) {\n const candidate = layerByPanel.get(current)\n if (candidate && !candidate.released) return current\n }\n current = nextCompositionNode(current)\n }\n return null\n}\n\nfunction releaseLayer(layer: Layer): void {\n if (layer.released) return\n layer.released = true\n for (const child of Array.from(childPanels.get(layer.panel) ?? [])) {\n const childLayer = layerByPanel.get(child)\n if (childLayer) releaseLayer(childLayer)\n else detachFromParent(child)\n }\n layers.delete(layer)\n layerByPanel.delete(layer.panel)\n childPanels.delete(layer.panel)\n detachFromParent(layer.panel)\n syncListener()\n}\n\nfunction createLayer(input: {\n panel: HTMLElement\n host: OpenOverlayHost\n arbitration: OverlayArbitration\n parent?: HTMLElement\n}): OpenOverlayHandle {\n const previous = layerByPanel.get(input.panel)\n // 同一 panel 重新 claim = 新的一次开启:旧会话(含其子层)整体作废,seq 重新分配。\n if (previous) releaseLayer(previous)\n\n const layer: Layer = {\n panel: input.panel,\n host: input.host,\n arbitration: input.arbitration,\n inert: false,\n seq: ++sequence,\n released: false\n }\n layers.add(layer)\n layerByPanel.set(input.panel, layer)\n attachToParent(input.panel, input.parent)\n\n const handle: OpenOverlayHandle = {\n setInert(inert) {\n if (layer.released) return\n layer.inert = inert\n },\n\n adopt(panel, options = {}) {\n if (layer.released) return inactiveHandle\n return createLayer({\n panel,\n host: layer.host,\n arbitration: options.arbitration ?? 'none',\n parent: layer.panel\n })\n },\n\n contains(node) {\n return !layer.released && containsPanel(layer.panel, node)\n },\n\n containsEvent(event) {\n return (\n !layer.released && event.composedPath().some(node => node instanceof Node && containsPanel(layer.panel, node))\n )\n },\n\n hasFocusWithin() {\n return !layer.released && hasFocusWithinPanel(layer.panel)\n },\n\n release() {\n releaseLayer(layer)\n }\n }\n\n layerByHandle.set(handle, layer)\n syncListener()\n return handle\n}\n\nfunction isCandidate(layer: Layer): boolean {\n return !layer.released && layer.arbitration !== 'none' && layer.panel.isConnected\n}\n\n/**\n * 在候选里取最内层:子树包含关系下的极大元;互不包含时取后开的。\n *\n * 刻意**不按事件路径筛选**:抽屉打开、内部列表也打开、而焦点停在抽屉上时,事件路径\n * 只命中抽屉,按路径判定会关掉外层、留下内层 —— 正是本模块要修的缺陷。正确语义是\n * 「最内层优先」,与焦点无关。\n */\nfunction resolve(): Layer | null {\n let best: Layer | null = null\n for (const layer of layers) {\n if (!isCandidate(layer)) continue\n if (!best) {\n best = layer\n continue\n }\n if (containsPanel(best.panel, layer.panel)) {\n best = layer\n continue\n }\n if (containsPanel(layer.panel, best.panel)) continue\n if (layer.seq > best.seq) best = layer\n }\n return best\n}\n\nfunction handleKeydown(event: KeyboardEvent): void {\n if (event.key !== 'Escape') return\n // 已有更早的捕获监听处理过本次 Escape:不重复介入。\n if (event.defaultPrevented) return\n\n const layer = resolve()\n if (!layer) return\n\n /*\n * preventDefault 压掉原生 dialog 的关闭请求;stopPropagation 让组件自身的 handler\n * 不再执行。惰性层同样被吞掉按键,只是不走关闭入口 —— 否则事件会落到下层浮层,\n * 关闭顺序与用户预期相反。\n *\n * 已知边界:stopPropagation 不阻止**同一节点**(document)上后注册的其他捕获监听。\n * 刻意不用 stopImmediatePropagation —— 那会连带掐掉应用自己在 document 上的捕获\n * 监听,超出本模块职责。\n */\n event.preventDefault()\n event.stopPropagation()\n if (layer.inert) return\n layer.host.requestClose()\n}\n\nfunction syncListener(): void {\n let shouldListen = false\n for (const layer of layers) {\n if (!layer.released && layer.arbitration !== 'none') {\n shouldListen = true\n break\n }\n }\n if (shouldListen === listening) return\n listening = shouldListen\n if (shouldListen) document.addEventListener('keydown', handleKeydown, true)\n else document.removeEventListener('keydown', handleKeydown, true)\n}\n\nexport const defineOpenOverlay = () =>\n definePlugin<OpenOverlay, OpenOverlayHost>(host => {\n let generation = 0\n let suspended = false\n const frames = new Set<OverlayFrameHandle & { id: number; generation: number }>()\n\n const cancelFrame = (frame: OverlayFrameHandle & { id: number; generation: number }) => {\n cancelAnimationFrame(frame.id)\n frames.delete(frame)\n }\n\n const cancelFrames = () => {\n for (const frame of Array.from(frames)) cancelFrame(frame)\n }\n\n const invalidateCurrent = () => {\n generation += 1\n cancelFrames()\n }\n\n const api: OpenOverlay = {\n claim(panel, options = {}) {\n const arbitration = options.arbitration ?? 'escape'\n const ancestryFrom = options.ancestryFrom ?? panel\n return createLayer({\n panel,\n host,\n arbitration,\n parent: findOpenAncestor(panel, ancestryFrom) ?? undefined\n })\n },\n\n scheduleFrame(callback, whileLive) {\n if (suspended) return { cancel() {} }\n\n const scheduledGeneration = generation\n const frame: OverlayFrameHandle & { id: number; generation: number } = {\n generation: scheduledGeneration,\n id: requestAnimationFrame(() => {\n frames.delete(frame)\n if (suspended || scheduledGeneration !== generation) return\n // 会话已结束:该帧属于上一轮开合,丢弃而不是执行在错误的层上。\n // 句柄已 release,或同一 panel 重新 claim 换了新句柄(旧层仍在 WeakMap 里,\n // 标记为 released),两种情形都要丢。\n if (whileLive) {\n const liveLayer = layerByHandle.get(whileLive)\n if (!liveLayer || liveLayer.released) return\n }\n callback()\n }),\n cancel() {\n cancelFrame(frame)\n }\n }\n frames.add(frame)\n return frame\n },\n\n invalidate: invalidateCurrent,\n\n suspend() {\n if (suspended) return\n suspended = true\n // 停调度之后再撤销在途帧(两者互不依赖,顺序不影响语义)。不能只靠回调内部的\n // suspended 复查:那会让这些帧白占一个 rAF 周期(旧实现在此处因早退而没有取消)。\n cancelFrames()\n },\n\n resume() {\n if (!suspended) return\n suspended = false\n generation += 1\n }\n }\n\n return api\n })\n"],"mappings":"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"}
@@ -59,7 +59,7 @@ var g = () => s((n) => {
59
59
  strategy: r.strategy,
60
60
  middleware: o
61
61
  }).then(({ x: e, y: t, placement: r }) => {
62
- !d.isCurrent(n) || !s || (i.style.left = `${e}px`, i.style.top = `${t}px`, i.style.setProperty("--wui-internal-overlay-transform-origin", h(r)));
62
+ d.isCurrent(n) && s && (i.style.left = `${e}px`, i.style.top = `${t}px`, i.style.setProperty("--wui-internal-overlay-transform-origin", h(r)));
63
63
  });
64
64
  };
65
65
  u(), c?.(), c = e(l, i, u);
@@ -1 +1 @@
1
- {"version":3,"file":"overlay.js","names":[],"sources":["../../../src/shared/overlay/overlay.ts"],"sourcesContent":["import {\n autoUpdate,\n computePosition,\n flip,\n offset,\n shift,\n size,\n type Middleware,\n type Placement,\n type Strategy\n} from '@floating-ui/dom'\nimport { definePlugin } from '@greypan/js-kit'\n\nimport { defineOverlayPositioningGeneration, type OverlayPositioningGeneration } from './positioning-generation'\n\nexport interface OverlayOptions {\n placement?: Placement\n offset?: number\n flip?: boolean\n shift?: boolean\n /** 面板宽度与锚点宽度保持一致。 */\n matchWidth?: boolean\n /** 面板至少与锚点同宽,同时允许内容将面板撑宽。 */\n minAnchorWidth?: boolean\n strategy?: Strategy\n}\n\n/** 虚拟锚点:无对应 DOM 元素的定位基准。context-menu 未走 defineOverlay(定位语义分歧见 ADR-0038)。 */\nexport interface OverlayVirtualAnchor {\n getBoundingClientRect(): DOMRect\n}\n\nexport type OverlayAnchor = HTMLElement | OverlayVirtualAnchor\n\nexport interface OverlayApi {\n isOpen(): boolean\n readonly options: Required<OverlayOptions>\n open(): void\n close(): void\n toggle(): void\n update(options: Partial<OverlayOptions>): void\n updateAnchor(anchor: OverlayAnchor): void\n dispose(): void\n}\n\nconst DEFAULT_OVERLAY_MIN_WIDTH = 120\nconst OVERLAY_MIN_WIDTH_VARIABLE = '--wui-overlay-min-width'\n\nconst DEFAULT_OPTIONS: Required<OverlayOptions> = {\n placement: 'bottom-start',\n offset: 4,\n flip: true,\n shift: true,\n matchWidth: false,\n minAnchorWidth: false,\n strategy: 'absolute'\n}\n\ninterface WidthStyleState {\n hasManagedStyles: boolean\n}\n\nfunction readOverlayMinWidth(overlay: HTMLElement): number {\n if (typeof getComputedStyle !== 'function') return DEFAULT_OVERLAY_MIN_WIDTH\n\n const minWidth = Number.parseFloat(getComputedStyle(overlay).getPropertyValue(OVERLAY_MIN_WIDTH_VARIABLE))\n return Number.isFinite(minWidth) ? minWidth : DEFAULT_OVERLAY_MIN_WIDTH\n}\n\nfunction clearManagedWidthStyles(overlay: HTMLElement) {\n overlay.style.removeProperty('width')\n overlay.style.removeProperty('min-width')\n}\n\nfunction createWidthMiddleware(\n overlay: HTMLElement,\n options: Required<OverlayOptions>,\n widthStyles: WidthStyleState,\n positioningGeneration: OverlayPositioningGeneration,\n generation: number\n): Middleware | undefined {\n const isCurrentGeneration = () => positioningGeneration.isCurrent(generation)\n if (options.matchWidth) {\n // matchWidth 必须移除之前 minAnchorWidth 写入的 min-width,才能精确跟随 trigger。\n overlay.style.removeProperty('min-width')\n widthStyles.hasManagedStyles = true\n return size({\n apply({ rects }) {\n if (!isCurrentGeneration()) return\n overlay.style.width = `${rects.reference.width}px`\n }\n })\n }\n\n if (options.minAnchorWidth) {\n const minWidth = readOverlayMinWidth(overlay)\n widthStyles.hasManagedStyles = true\n return size({\n apply({ rects }) {\n if (!isCurrentGeneration()) return\n // max-content 让内容决定宽度,min-width 再提供 trigger 与 floor 两个下限。\n overlay.style.width = 'max-content'\n overlay.style.minWidth = `${Math.max(rects.reference.width, minWidth)}px`\n }\n })\n }\n\n if (widthStyles.hasManagedStyles) {\n clearManagedWidthStyles(overlay)\n widthStyles.hasManagedStyles = false\n }\n\n return undefined\n}\n\nfunction createPositioningMiddleware(\n overlay: HTMLElement,\n options: Required<OverlayOptions>,\n widthStyles: WidthStyleState,\n positioningGeneration: OverlayPositioningGeneration,\n generation: number\n): Middleware[] {\n const middleware: Middleware[] = [offset(options.offset)]\n if (options.flip) middleware.push(flip())\n if (options.shift) middleware.push(shift({ padding: 8 }))\n\n const widthMiddleware = createWidthMiddleware(overlay, options, widthStyles, positioningGeneration, generation)\n if (widthMiddleware) middleware.push(widthMiddleware)\n\n return middleware\n}\n\nfunction getTransformOrigin(placement: Placement): string {\n const [side, alignment] = placement.split('-')\n const horizontalOrigin = alignment === 'start' ? 'left' : alignment === 'end' ? 'right' : 'center'\n const verticalOrigin = alignment === 'start' ? 'top' : alignment === 'end' ? 'bottom' : 'center'\n\n if (side === 'top') return `bottom ${horizontalOrigin}`\n if (side === 'bottom') return `top ${horizontalOrigin}`\n if (side === 'left') return `right ${verticalOrigin}`\n return `left ${verticalOrigin}`\n}\n\nexport const defineOverlay = () =>\n definePlugin<OverlayApi, { anchor: OverlayAnchor; overlay: HTMLElement } & OverlayOptions>(ctx => {\n const options: Required<OverlayOptions> = {\n placement: ctx.placement ?? DEFAULT_OPTIONS.placement,\n offset: ctx.offset ?? DEFAULT_OPTIONS.offset,\n flip: ctx.flip ?? DEFAULT_OPTIONS.flip,\n shift: ctx.shift ?? DEFAULT_OPTIONS.shift,\n matchWidth: ctx.matchWidth ?? DEFAULT_OPTIONS.matchWidth,\n minAnchorWidth: ctx.minAnchorWidth ?? DEFAULT_OPTIONS.minAnchorWidth,\n strategy: ctx.strategy ?? DEFAULT_OPTIONS.strategy\n }\n\n const overlay = ctx.overlay\n const widthStyles: WidthStyleState = { hasManagedStyles: false }\n let isOpen = false\n let cleanupAutoUpdate: (() => void) | null = null\n let currentAnchor = ctx.anchor\n // 每次刷新都是新定位代;open 期间 autoUpdate 的多次回调属于同一代。\n // reposition/close 会让上一代 promise 失效,避免乱序完成覆盖最新坐标。\n const positioningGeneration = defineOverlayPositioningGeneration().make()\n\n function updatePosition() {\n const generation = positioningGeneration.next()\n const middleware = createPositioningMiddleware(overlay, options, widthStyles, positioningGeneration, generation)\n\n const applyPosition = () => {\n void computePosition(currentAnchor, overlay, {\n placement: options.placement,\n strategy: options.strategy,\n middleware\n }).then(({ x, y, placement }) => {\n if (!positioningGeneration.isCurrent(generation) || !isOpen) return\n overlay.style.left = `${x}px`\n overlay.style.top = `${y}px`\n overlay.style.setProperty('--wui-internal-overlay-transform-origin', getTransformOrigin(placement))\n })\n }\n\n // 立即定位一次,避免浮层首帧出现在左上角;autoUpdate 负责后续滚动/尺寸变化。\n applyPosition()\n cleanupAutoUpdate?.()\n cleanupAutoUpdate = autoUpdate(currentAnchor, overlay, applyPosition)\n }\n\n return {\n isOpen() {\n return isOpen\n },\n options,\n\n open() {\n if (isOpen) return\n isOpen = true\n overlay.style.display = ''\n updatePosition()\n },\n\n close() {\n if (!isOpen) return\n isOpen = false\n positioningGeneration.invalidate()\n cleanupAutoUpdate?.()\n cleanupAutoUpdate = null\n },\n\n toggle() {\n if (isOpen) this.close()\n else this.open()\n },\n\n update(nextOptions) {\n Object.assign(options, nextOptions)\n if (isOpen) updatePosition()\n },\n\n updateAnchor(newAnchor: OverlayAnchor) {\n currentAnchor = newAnchor\n if (isOpen) updatePosition()\n },\n\n dispose() {\n this.close()\n }\n }\n })\n"],"mappings":"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"}
1
+ {"version":3,"file":"overlay.js","names":[],"sources":["../../../src/shared/overlay/overlay.ts"],"sourcesContent":["import {\n autoUpdate,\n computePosition,\n flip,\n offset,\n shift,\n size,\n type Middleware,\n type Placement,\n type Strategy\n} from '@floating-ui/dom'\nimport { definePlugin } from '@greypan/js-kit'\n\nimport { defineOverlayPositioningGeneration, type OverlayPositioningGeneration } from './positioning-generation'\n\nexport interface OverlayOptions {\n placement?: Placement\n offset?: number\n flip?: boolean\n shift?: boolean\n /** 面板宽度与锚点宽度保持一致。 */\n matchWidth?: boolean\n /** 面板至少与锚点同宽,同时允许内容将面板撑宽。 */\n minAnchorWidth?: boolean\n strategy?: Strategy\n}\n\n/** 虚拟锚点:无对应 DOM 元素的定位基准。context-menu 未走 defineOverlay(定位语义分歧见 ADR-0038)。 */\nexport interface OverlayVirtualAnchor {\n getBoundingClientRect(): DOMRect\n}\n\nexport type OverlayAnchor = HTMLElement | OverlayVirtualAnchor\n\nexport interface OverlayApi {\n isOpen(): boolean\n readonly options: Required<OverlayOptions>\n open(): void\n close(): void\n toggle(): void\n update(options: Partial<OverlayOptions>): void\n updateAnchor(anchor: OverlayAnchor): void\n dispose(): void\n}\n\nconst DEFAULT_OVERLAY_MIN_WIDTH = 120\nconst OVERLAY_MIN_WIDTH_VARIABLE = '--wui-overlay-min-width'\n\nconst DEFAULT_OPTIONS: Required<OverlayOptions> = {\n placement: 'bottom-start',\n offset: 4,\n flip: true,\n shift: true,\n matchWidth: false,\n minAnchorWidth: false,\n strategy: 'absolute'\n}\n\ninterface WidthStyleState {\n hasManagedStyles: boolean\n}\n\nfunction readOverlayMinWidth(overlay: HTMLElement): number {\n if (typeof getComputedStyle !== 'function') return DEFAULT_OVERLAY_MIN_WIDTH\n\n const minWidth = Number.parseFloat(getComputedStyle(overlay).getPropertyValue(OVERLAY_MIN_WIDTH_VARIABLE))\n return Number.isFinite(minWidth) ? minWidth : DEFAULT_OVERLAY_MIN_WIDTH\n}\n\nfunction clearManagedWidthStyles(overlay: HTMLElement) {\n overlay.style.removeProperty('width')\n overlay.style.removeProperty('min-width')\n}\n\nfunction createWidthMiddleware(\n overlay: HTMLElement,\n options: Required<OverlayOptions>,\n widthStyles: WidthStyleState,\n positioningGeneration: OverlayPositioningGeneration,\n generation: number\n): Middleware | undefined {\n const isCurrentGeneration = () => positioningGeneration.isCurrent(generation)\n if (options.matchWidth) {\n // matchWidth 必须移除之前 minAnchorWidth 写入的 min-width,才能精确跟随 trigger。\n overlay.style.removeProperty('min-width')\n widthStyles.hasManagedStyles = true\n return size({\n apply({ rects }) {\n if (!isCurrentGeneration()) return\n overlay.style.width = `${rects.reference.width}px`\n }\n })\n }\n\n if (options.minAnchorWidth) {\n const minWidth = readOverlayMinWidth(overlay)\n widthStyles.hasManagedStyles = true\n return size({\n apply({ rects }) {\n if (!isCurrentGeneration()) return\n // max-content 让内容决定宽度,min-width 再提供 trigger 与 floor 两个下限。\n overlay.style.width = 'max-content'\n overlay.style.minWidth = `${Math.max(rects.reference.width, minWidth)}px`\n }\n })\n }\n\n if (widthStyles.hasManagedStyles) {\n clearManagedWidthStyles(overlay)\n widthStyles.hasManagedStyles = false\n }\n\n return undefined\n}\n\nfunction createPositioningMiddleware(\n overlay: HTMLElement,\n options: Required<OverlayOptions>,\n widthStyles: WidthStyleState,\n positioningGeneration: OverlayPositioningGeneration,\n generation: number\n): Middleware[] {\n const middleware: Middleware[] = [offset(options.offset)]\n if (options.flip) middleware.push(flip())\n if (options.shift) middleware.push(shift({ padding: 8 }))\n\n const widthMiddleware = createWidthMiddleware(overlay, options, widthStyles, positioningGeneration, generation)\n if (widthMiddleware) middleware.push(widthMiddleware)\n\n return middleware\n}\n\nfunction getTransformOrigin(placement: Placement): string {\n const [side, alignment] = placement.split('-')\n const horizontalOrigin = alignment === 'start' ? 'left' : alignment === 'end' ? 'right' : 'center'\n const verticalOrigin = alignment === 'start' ? 'top' : alignment === 'end' ? 'bottom' : 'center'\n\n if (side === 'top') return `bottom ${horizontalOrigin}`\n if (side === 'bottom') return `top ${horizontalOrigin}`\n if (side === 'left') return `right ${verticalOrigin}`\n return `left ${verticalOrigin}`\n}\n\nexport const defineOverlay = () =>\n definePlugin<OverlayApi, { anchor: OverlayAnchor; overlay: HTMLElement } & OverlayOptions>(ctx => {\n const options: Required<OverlayOptions> = {\n placement: ctx.placement ?? DEFAULT_OPTIONS.placement,\n offset: ctx.offset ?? DEFAULT_OPTIONS.offset,\n flip: ctx.flip ?? DEFAULT_OPTIONS.flip,\n shift: ctx.shift ?? DEFAULT_OPTIONS.shift,\n matchWidth: ctx.matchWidth ?? DEFAULT_OPTIONS.matchWidth,\n minAnchorWidth: ctx.minAnchorWidth ?? DEFAULT_OPTIONS.minAnchorWidth,\n strategy: ctx.strategy ?? DEFAULT_OPTIONS.strategy\n }\n\n const overlay = ctx.overlay\n const widthStyles: WidthStyleState = { hasManagedStyles: false }\n let isOpen = false\n let cleanupAutoUpdate: (() => void) | null = null\n let currentAnchor = ctx.anchor\n // 每次刷新都是新定位代;open 期间 autoUpdate 的多次回调属于同一代。\n // reposition/close 会让上一代 promise 失效,避免乱序完成覆盖最新坐标。\n const positioningGeneration = defineOverlayPositioningGeneration().make()\n\n function updatePosition() {\n const generation = positioningGeneration.next()\n const middleware = createPositioningMiddleware(overlay, options, widthStyles, positioningGeneration, generation)\n\n const applyPosition = () => {\n void computePosition(currentAnchor, overlay, {\n placement: options.placement,\n strategy: options.strategy,\n middleware\n }).then(({ x, y, placement }) => {\n if (!positioningGeneration.isCurrent(generation) || !isOpen) return\n overlay.style.left = `${x}px`\n overlay.style.top = `${y}px`\n overlay.style.setProperty('--wui-internal-overlay-transform-origin', getTransformOrigin(placement))\n })\n }\n\n // 立即定位一次,避免浮层首帧出现在左上角;autoUpdate 负责后续滚动/尺寸变化。\n applyPosition()\n cleanupAutoUpdate?.()\n cleanupAutoUpdate = autoUpdate(currentAnchor, overlay, applyPosition)\n }\n\n return {\n isOpen() {\n return isOpen\n },\n options,\n\n open() {\n if (isOpen) return\n isOpen = true\n overlay.style.display = ''\n updatePosition()\n },\n\n close() {\n if (!isOpen) return\n isOpen = false\n positioningGeneration.invalidate()\n cleanupAutoUpdate?.()\n cleanupAutoUpdate = null\n },\n\n toggle() {\n if (isOpen) this.close()\n else this.open()\n },\n\n update(nextOptions) {\n Object.assign(options, nextOptions)\n if (isOpen) updatePosition()\n },\n\n updateAnchor(newAnchor: OverlayAnchor) {\n currentAnchor = newAnchor\n if (isOpen) updatePosition()\n },\n\n dispose() {\n this.close()\n }\n }\n })\n"],"mappings":";;;;AA6CA,IAAM,IAA4B,KAC5B,IAA6B,2BAE7B,IAA4C;CAChD,WAAW;CACX,QAAQ;CACR,MAAM;CACN,OAAO;CACP,YAAY;CACZ,gBAAgB;CAChB,UAAU;AACZ;AAMA,SAAS,EAAoB,GAA8B;CACzD,IAAI,OAAO,oBAAqB,YAAY,OAAO;CAEnD,IAAM,IAAW,OAAO,WAAW,iBAAiB,CAAO,CAAC,CAAC,iBAAiB,CAA0B,CAAC;CACzG,OAAO,OAAO,SAAS,CAAQ,IAAI,IAAW;AAChD;AAEA,SAAS,EAAwB,GAAsB;CAErD,AADA,EAAQ,MAAM,eAAe,OAAO,GACpC,EAAQ,MAAM,eAAe,WAAW;AAC1C;AAEA,SAAS,EACP,GACA,GACA,GACA,GACA,GACwB;CACxB,IAAM,UAA4B,EAAsB,UAAU,CAAU;CAC5E,IAAI,EAAQ,YAIV,OAFA,EAAQ,MAAM,eAAe,WAAW,GACxC,EAAY,mBAAmB,IACxB,EAAK,EACV,MAAM,EAAE,YAAS;EACV,EAAoB,MACzB,EAAQ,MAAM,QAAQ,GAAG,EAAM,UAAU,MAAM;CACjD,EACF,CAAC;CAGH,IAAI,EAAQ,gBAAgB;EAC1B,IAAM,IAAW,EAAoB,CAAO;EAE5C,OADA,EAAY,mBAAmB,IACxB,EAAK,EACV,MAAM,EAAE,YAAS;GACV,EAAoB,MAEzB,EAAQ,MAAM,QAAQ,eACtB,EAAQ,MAAM,WAAW,GAAG,KAAK,IAAI,EAAM,UAAU,OAAO,CAAQ,EAAE;EACxE,EACF,CAAC;CACH;CAEA,AAEE,EAAY,sBADZ,EAAwB,CAAO,GACA;AAInC;AAEA,SAAS,EACP,GACA,GACA,GACA,GACA,GACc;CACd,IAAM,IAA2B,CAAC,EAAO,EAAQ,MAAM,CAAC;CAExD,AADI,EAAQ,QAAM,EAAW,KAAK,EAAK,CAAC,GACpC,EAAQ,SAAO,EAAW,KAAK,EAAM,EAAE,SAAS,EAAE,CAAC,CAAC;CAExD,IAAM,IAAkB,EAAsB,GAAS,GAAS,GAAa,GAAuB,CAAU;CAG9G,OAFI,KAAiB,EAAW,KAAK,CAAe,GAE7C;AACT;AAEA,SAAS,EAAmB,GAA8B;CACxD,IAAM,CAAC,GAAM,KAAa,EAAU,MAAM,GAAG,GACvC,IAAmB,MAAc,UAAU,SAAS,MAAc,QAAQ,UAAU,UACpF,IAAiB,MAAc,UAAU,QAAQ,MAAc,QAAQ,WAAW;CAKxF,OAHI,MAAS,QAAc,UAAU,MACjC,MAAS,WAAiB,OAAO,MACjC,MAAS,SAAe,SAAS,MAC9B,QAAQ;AACjB;AAEA,IAAa,UACX,GAA2F,MAAO;CAChG,IAAM,IAAoC;EACxC,WAAW,EAAI,aAAa,EAAgB;EAC5C,QAAQ,EAAI,UAAU,EAAgB;EACtC,MAAM,EAAI,QAAQ,EAAgB;EAClC,OAAO,EAAI,SAAS,EAAgB;EACpC,YAAY,EAAI,cAAc,EAAgB;EAC9C,gBAAgB,EAAI,kBAAkB,EAAgB;EACtD,UAAU,EAAI,YAAY,EAAgB;CAC5C,GAEM,IAAU,EAAI,SACd,IAA+B,EAAE,kBAAkB,GAAM,GAC3D,IAAS,IACT,IAAyC,MACzC,IAAgB,EAAI,QAGlB,IAAwB,EAAmC,CAAC,CAAC,KAAK;CAExE,SAAS,IAAiB;EACxB,IAAM,IAAa,EAAsB,KAAK,GACxC,IAAa,EAA4B,GAAS,GAAS,GAAa,GAAuB,CAAU,GAEzG,UAAsB;GAC1B,EAAqB,GAAe,GAAS;IAC3C,WAAW,EAAQ;IACnB,UAAU,EAAQ;IAClB;GACF,CAAC,CAAC,CAAC,MAAM,EAAE,MAAG,MAAG,mBAAgB;IAC3B,AAAC,EAAsB,UAAU,CAAU,KAAM,MACrD,EAAQ,MAAM,OAAO,GAAG,EAAE,KAC1B,EAAQ,MAAM,MAAM,GAAG,EAAE,KACzB,EAAQ,MAAM,YAAY,2CAA2C,EAAmB,CAAS,CAAC;GACpG,CAAC;EACH;EAKA,AAFA,EAAc,GACd,IAAoB,GACpB,IAAoB,EAAW,GAAe,GAAS,CAAa;CACtE;CAEA,OAAO;EACL,SAAS;GACP,OAAO;EACT;EACA;EAEA,OAAO;GACD,MACJ,IAAS,IACT,EAAQ,MAAM,UAAU,IACxB,EAAe;EACjB;EAEA,QAAQ;GACD,MACL,IAAS,IACT,EAAsB,WAAW,GACjC,IAAoB,GACpB,IAAoB;EACtB;EAEA,SAAS;GACP,AAAI,IAAQ,KAAK,MAAM,IAClB,KAAK,KAAK;EACjB;EAEA,OAAO,GAAa;GAElB,AADA,OAAO,OAAO,GAAS,CAAW,GAC9B,KAAQ,EAAe;EAC7B;EAEA,aAAa,GAA0B;GAErC,AADA,IAAgB,GACZ,KAAQ,EAAe;EAC7B;EAEA,UAAU;GACR,KAAK,MAAM;EACb;CACF;AACF,CAAC"}
@@ -56,7 +56,7 @@ var a = "wui-portal-content-anchor", o = () => r((e) => {
56
56
  }
57
57
  return !1;
58
58
  }, r = new Set(t.flatMap((e) => [...e.addedNodes])), s = [];
59
- for (let e of t) for (let t of e.addedNodes) !(t instanceof Comment) || t.textContent === a || i.contains(t) && (n(e.target) || s.push(t));
59
+ for (let e of t) for (let t of e.addedNodes) t instanceof Comment && t.textContent !== a && i.contains(t) && (n(e.target) || s.push(t));
60
60
  if (!s.length) return;
61
61
  let c = Array.from(e.target.childNodes), l = o.filter((e) => !r.has(e.node) && t.some((t) => [...t.removedNodes].includes(e.node))).sort((e, t) => c.indexOf(e.marker) - c.indexOf(t.marker));
62
62
  for (let [t, n] of s.entries()) {