@greypan/web-ui 3.0.0 → 4.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 (220) hide show
  1. package/README.CN.md +297 -34
  2. package/README.md +304 -39
  3. package/dist/_virtual/{_@oxc-project_runtime@0.140.0 → _@oxc-project_runtime@0.146.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/toast-containers.js +1 -1
  11. package/dist/assets/toast-containers.js.map +1 -1
  12. package/dist/components/autocomplete/index.d.ts +4 -12
  13. package/dist/components/autocomplete/index.js +95 -102
  14. package/dist/components/autocomplete/index.js.map +1 -1
  15. package/dist/components/autocomplete/style.js +1 -1
  16. package/dist/components/autocomplete/style.js.map +1 -1
  17. package/dist/components/avatar/index.d.ts +0 -1
  18. package/dist/components/avatar/index.js +8 -13
  19. package/dist/components/avatar/index.js.map +1 -1
  20. package/dist/components/back-top/index.js +1 -1
  21. package/dist/components/back-top/style.js +1 -1
  22. package/dist/components/back-top/style.js.map +1 -1
  23. package/dist/components/badge/index.d.ts +1 -0
  24. package/dist/components/badge/index.js +12 -14
  25. package/dist/components/badge/index.js.map +1 -1
  26. package/dist/components/button/index.d.ts +2 -0
  27. package/dist/components/button/index.js +51 -38
  28. package/dist/components/button/index.js.map +1 -1
  29. package/dist/components/button/style.js +1 -1
  30. package/dist/components/button/style.js.map +1 -1
  31. package/dist/components/button-group/index.d.ts +1 -2
  32. package/dist/components/button-group/index.js +19 -23
  33. package/dist/components/button-group/index.js.map +1 -1
  34. package/dist/components/checkbox/index.d.ts +4 -7
  35. package/dist/components/checkbox/index.js +48 -37
  36. package/dist/components/checkbox/index.js.map +1 -1
  37. package/dist/components/checkbox/style.js +1 -1
  38. package/dist/components/checkbox/style.js.map +1 -1
  39. package/dist/components/checkbox-group/index.d.ts +4 -10
  40. package/dist/components/checkbox-group/index.js +74 -60
  41. package/dist/components/checkbox-group/index.js.map +1 -1
  42. package/dist/components/context-menu/index.js +7 -8
  43. package/dist/components/context-menu/index.js.map +1 -1
  44. package/dist/components/dialog/index.d.ts +1 -0
  45. package/dist/components/dialog/index.js +13 -9
  46. package/dist/components/dialog/index.js.map +1 -1
  47. package/dist/components/dialog/style.js +1 -1
  48. package/dist/components/dialog/style.js.map +1 -1
  49. package/dist/components/drawer/index.d.ts +15 -0
  50. package/dist/components/drawer/index.js +58 -34
  51. package/dist/components/drawer/index.js.map +1 -1
  52. package/dist/components/drawer/style.js +1 -1
  53. package/dist/components/drawer/style.js.map +1 -1
  54. package/dist/components/dropdown/index.js +1 -1
  55. package/dist/components/dropdown-divider/index.js +1 -1
  56. package/dist/components/dropdown-header/index.js +1 -1
  57. package/dist/components/dropdown-header/style.js +1 -1
  58. package/dist/components/dropdown-header/style.js.map +1 -1
  59. package/dist/components/dropdown-item/index.js +3 -2
  60. package/dist/components/dropdown-item/index.js.map +1 -1
  61. package/dist/components/empty/index.js +1 -1
  62. package/dist/components/empty/style.js +1 -1
  63. package/dist/components/empty/style.js.map +1 -1
  64. package/dist/components/icon/index.js +1 -1
  65. package/dist/components/index.d.ts +1 -1
  66. package/dist/components/index.js +9 -9
  67. package/dist/components/input/index.d.ts +2 -3
  68. package/dist/components/input/index.js +62 -52
  69. package/dist/components/input/index.js.map +1 -1
  70. package/dist/components/input/style.js +1 -1
  71. package/dist/components/input/style.js.map +1 -1
  72. package/dist/components/input-number/index.d.ts +2 -3
  73. package/dist/components/input-number/index.js +58 -38
  74. package/dist/components/input-number/index.js.map +1 -1
  75. package/dist/components/input-number/style.js +1 -1
  76. package/dist/components/input-number/style.js.map +1 -1
  77. package/dist/components/layout/index.d.ts +53 -3
  78. package/dist/components/layout/index.js +146 -39
  79. package/dist/components/layout/index.js.map +1 -1
  80. package/dist/components/layout/style.js +1 -1
  81. package/dist/components/layout/style.js.map +1 -1
  82. package/dist/components/option/index.js +1 -1
  83. package/dist/components/option/style.js +1 -1
  84. package/dist/components/option/style.js.map +1 -1
  85. package/dist/components/popover/index.js +7 -5
  86. package/dist/components/popover/index.js.map +1 -1
  87. package/dist/components/popover/style.js +1 -1
  88. package/dist/components/popover/style.js.map +1 -1
  89. package/dist/components/radio/index.d.ts +4 -7
  90. package/dist/components/radio/index.js +48 -37
  91. package/dist/components/radio/index.js.map +1 -1
  92. package/dist/components/radio/style.js +1 -1
  93. package/dist/components/radio/style.js.map +1 -1
  94. package/dist/components/radio-group/index.d.ts +4 -10
  95. package/dist/components/radio-group/index.js +70 -52
  96. package/dist/components/radio-group/index.js.map +1 -1
  97. package/dist/components/segmented/index.d.ts +6 -11
  98. package/dist/components/segmented/index.js +67 -55
  99. package/dist/components/segmented/index.js.map +1 -1
  100. package/dist/components/segmented/style.js +1 -1
  101. package/dist/components/segmented/style.js.map +1 -1
  102. package/dist/components/segmented-trigger/index.d.ts +1 -2
  103. package/dist/components/segmented-trigger/index.js +18 -19
  104. package/dist/components/segmented-trigger/index.js.map +1 -1
  105. package/dist/components/segmented-trigger/style.js +1 -1
  106. package/dist/components/segmented-trigger/style.js.map +1 -1
  107. package/dist/components/select/index.d.ts +13 -8
  108. package/dist/components/select/index.js +110 -75
  109. package/dist/components/select/index.js.map +1 -1
  110. package/dist/components/select/style.js +1 -1
  111. package/dist/components/select/style.js.map +1 -1
  112. package/dist/components/slider/index.d.ts +3 -5
  113. package/dist/components/slider/index.js +59 -42
  114. package/dist/components/slider/index.js.map +1 -1
  115. package/dist/components/slider/style.js +1 -1
  116. package/dist/components/slider/style.js.map +1 -1
  117. package/dist/components/spinner/index.d.ts +0 -2
  118. package/dist/components/spinner/index.js +14 -13
  119. package/dist/components/spinner/index.js.map +1 -1
  120. package/dist/components/spinner/style.js +1 -1
  121. package/dist/components/spinner/style.js.map +1 -1
  122. package/dist/components/svg-draw-lines/index.js +1 -1
  123. package/dist/components/switch/index.d.ts +5 -6
  124. package/dist/components/switch/index.js +51 -37
  125. package/dist/components/switch/index.js.map +1 -1
  126. package/dist/components/switch/style.js +1 -1
  127. package/dist/components/switch/style.js.map +1 -1
  128. package/dist/components/textarea/index.d.ts +5 -6
  129. package/dist/components/textarea/index.js +73 -63
  130. package/dist/components/textarea/index.js.map +1 -1
  131. package/dist/components/textarea/style.js +1 -1
  132. package/dist/components/textarea/style.js.map +1 -1
  133. package/dist/components/theme/index.js +1 -1
  134. package/dist/components/theme/style.js +1 -1
  135. package/dist/components/theme/style.js.map +1 -1
  136. package/dist/components/toast/style.js +1 -1
  137. package/dist/components/toast/style.js.map +1 -1
  138. package/dist/components/toast/toast.js +12 -12
  139. package/dist/components/toast/toast.js.map +1 -1
  140. package/dist/components/tooltip/index.js +1 -1
  141. package/dist/components/tooltip/style.js +1 -1
  142. package/dist/components/tooltip/style.js.map +1 -1
  143. package/dist/icons/generated/lucide-clapperboard.d.ts +6 -0
  144. package/dist/icons/generated/lucide-clapperboard.js +10 -0
  145. package/dist/icons/generated/lucide-clapperboard.js.map +1 -0
  146. package/dist/icons/generated/lucide-external-link.d.ts +6 -0
  147. package/dist/icons/generated/lucide-external-link.js +10 -0
  148. package/dist/icons/generated/lucide-external-link.js.map +1 -0
  149. package/dist/icons/generated/lucide-eye.d.ts +6 -0
  150. package/dist/icons/generated/lucide-eye.js +10 -0
  151. package/dist/icons/generated/lucide-eye.js.map +1 -0
  152. package/dist/icons/generated/lucide-file-text.d.ts +6 -0
  153. package/dist/icons/generated/lucide-file-text.js +10 -0
  154. package/dist/icons/generated/lucide-file-text.js.map +1 -0
  155. package/dist/icons/generated/lucide-film.d.ts +6 -0
  156. package/dist/icons/generated/lucide-film.js +10 -0
  157. package/dist/icons/generated/lucide-film.js.map +1 -0
  158. package/dist/icons/generated/lucide-folder-open.d.ts +6 -0
  159. package/dist/icons/generated/lucide-folder-open.js +10 -0
  160. package/dist/icons/generated/lucide-folder-open.js.map +1 -0
  161. package/dist/icons/generated/lucide-headphones.d.ts +6 -0
  162. package/dist/icons/generated/lucide-headphones.js +10 -0
  163. package/dist/icons/generated/lucide-headphones.js.map +1 -0
  164. package/dist/icons/generated/lucide-layout-grid.d.ts +6 -0
  165. package/dist/icons/generated/lucide-layout-grid.js +10 -0
  166. package/dist/icons/generated/lucide-layout-grid.js.map +1 -0
  167. package/dist/icons/generated/lucide-link.d.ts +6 -0
  168. package/dist/icons/generated/lucide-link.js +10 -0
  169. package/dist/icons/generated/lucide-link.js.map +1 -0
  170. package/dist/icons/generated/lucide-music.d.ts +6 -0
  171. package/dist/icons/generated/lucide-music.js +10 -0
  172. package/dist/icons/generated/lucide-music.js.map +1 -0
  173. package/dist/icons/generated/lucide-package.d.ts +6 -0
  174. package/dist/icons/generated/lucide-package.js +10 -0
  175. package/dist/icons/generated/lucide-package.js.map +1 -0
  176. package/dist/icons/generated/lucide-pen-line.d.ts +6 -0
  177. package/dist/icons/generated/lucide-pen-line.js +10 -0
  178. package/dist/icons/generated/lucide-pen-line.js.map +1 -0
  179. package/dist/icons/generated/lucide-pencil.d.ts +6 -0
  180. package/dist/icons/generated/lucide-pencil.js +10 -0
  181. package/dist/icons/generated/lucide-pencil.js.map +1 -0
  182. package/dist/icons/generated/lucide-play.d.ts +6 -0
  183. package/dist/icons/generated/lucide-play.js +10 -0
  184. package/dist/icons/generated/lucide-play.js.map +1 -0
  185. package/dist/icons/generated/lucide-refresh-cw.d.ts +6 -0
  186. package/dist/icons/generated/lucide-refresh-cw.js +10 -0
  187. package/dist/icons/generated/lucide-refresh-cw.js.map +1 -0
  188. package/dist/icons/generated/lucide-sliders-horizontal.d.ts +6 -0
  189. package/dist/icons/generated/lucide-sliders-horizontal.js +10 -0
  190. package/dist/icons/generated/lucide-sliders-horizontal.js.map +1 -0
  191. package/dist/icons/generated/lucide-tags.d.ts +6 -0
  192. package/dist/icons/generated/lucide-tags.js +10 -0
  193. package/dist/icons/generated/lucide-tags.js.map +1 -0
  194. package/dist/icons/generated/lucide-wrench.d.ts +6 -0
  195. package/dist/icons/generated/lucide-wrench.js +10 -0
  196. package/dist/icons/generated/lucide-wrench.js.map +1 -0
  197. package/dist/icons/index.d.ts +18 -0
  198. package/dist/icons/index.js +19 -1
  199. package/dist/node_modules/.pnpm/@floating-ui_core@1.8.0/node_modules/@floating-ui/core/dist/floating-ui.core.js +1 -3
  200. package/dist/node_modules/.pnpm/@floating-ui_core@1.8.0/node_modules/@floating-ui/core/dist/floating-ui.core.js.map +1 -1
  201. package/dist/node_modules/.pnpm/@floating-ui_utils@0.2.12/node_modules/@floating-ui/utils/dist/floating-ui.utils.dom.js +2 -1
  202. package/dist/node_modules/.pnpm/@floating-ui_utils@0.2.12/node_modules/@floating-ui/utils/dist/floating-ui.utils.dom.js.map +1 -1
  203. package/dist/shared/events/user-change.js.map +1 -1
  204. package/dist/shared/form-association/index.js +41 -0
  205. package/dist/shared/form-association/index.js.map +1 -0
  206. package/dist/shared/group-management/index.js +109 -0
  207. package/dist/shared/group-management/index.js.map +1 -0
  208. package/dist/shared/menu-portal/menu-tree.js +20 -7
  209. package/dist/shared/menu-portal/menu-tree.js.map +1 -1
  210. package/dist/shared/option-portal/index.js +65 -0
  211. package/dist/shared/option-portal/index.js.map +1 -0
  212. package/dist/shared/overlay/overlay.js +1 -1
  213. package/dist/shared/overlay/overlay.js.map +1 -1
  214. package/dist/shared/overlay/portal.js +9 -6
  215. package/dist/shared/overlay/portal.js.map +1 -1
  216. package/dist/shared/overlay/presence.js.map +1 -1
  217. package/dist/shared/visible-area/index.js +69 -0
  218. package/dist/shared/visible-area/index.js.map +1 -0
  219. package/dist/types/utils.d.ts +6 -1
  220. package/package.json +7 -5
@@ -0,0 +1,65 @@
1
+ import { definePlugin as e } from "@greypan/js-kit";
2
+ //#region src/shared/option-portal/index.ts
3
+ var t = "web-ui-option", n = () => e((e) => {
4
+ let n = /* @__PURE__ */ new Set(), r = /* @__PURE__ */ new WeakMap(), i = 0, a = (t) => {
5
+ e.bindOption(t), t.addEventListener("option-unregister", s);
6
+ }, o = (t) => {
7
+ e.unbindOption(t), t.removeEventListener("option-unregister", s);
8
+ }, s = (e) => {
9
+ e.target instanceof HTMLElement && e.target.localName === t && n.add(e.target), l.scheduleRefresh();
10
+ }, c = () => {
11
+ l.scheduleRefresh();
12
+ }, l = {
13
+ queryOptions() {
14
+ let n = e.getPortal()?.panel ?? e.getPortalContent(), r = n ? [...n.querySelectorAll(t)] : [];
15
+ return r.length > 0 ? r : [...e.element.querySelectorAll(t)];
16
+ },
17
+ refresh(e, t) {
18
+ let n = l.queryOptions();
19
+ for (let t of e) n.includes(t) || o(t);
20
+ return n.forEach(a), {
21
+ options: n,
22
+ activeIndex: t && n.includes(t) ? n.indexOf(t) : -1
23
+ };
24
+ },
25
+ ensureOptionIds(t) {
26
+ let n = new Set(t.map((e) => e.id).filter((e) => !!e));
27
+ t.forEach((t) => {
28
+ if (t.id) return;
29
+ let a = r.get(t);
30
+ if (!a || n.has(a)) {
31
+ do
32
+ a = `${e.idPrefix}-option-${++i}`;
33
+ while (n.has(a));
34
+ r.set(t, a);
35
+ }
36
+ t.id = a, n.add(a);
37
+ });
38
+ },
39
+ syncPortalContent() {
40
+ let t = e.getPortal(), r = e.getPortalContent();
41
+ if (!t || !r) return;
42
+ for (let i of n) !r.contains(i) && !e.element.contains(i) && t.removeContent([i]);
43
+ if (n.clear(), !e.isOpen()) return;
44
+ let i = e.getMigratableNodes();
45
+ i.length && t.appendContent(i, r);
46
+ },
47
+ scheduleRefresh() {
48
+ queueMicrotask(() => {
49
+ e.element.isConnected && (l.syncPortalContent(), e.requestUpdate(), e.isOpen() && e.hasUpdated() && e.onImmediateSync?.());
50
+ });
51
+ },
52
+ handleOptionUnregister: s,
53
+ bindHost() {
54
+ e.element.addEventListener("option-register", c), e.element.addEventListener("option-unregister", s);
55
+ },
56
+ dispose() {
57
+ e.element.removeEventListener("option-register", c), e.element.removeEventListener("option-unregister", s), n.clear();
58
+ }
59
+ };
60
+ return l;
61
+ });
62
+ //#endregion
63
+ export { n as defineOptionPortal };
64
+
65
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/shared/option-portal/index.ts"],"sourcesContent":["import { definePlugin } from '@greypan/js-kit'\n\nimport type { WebUiOption } from '@/components/option'\nimport type { OverlayPortal } from '@/shared/overlay/portal'\n\n/**\n * select / autocomplete 共用的 portal option 域:宿主侧 option 注册表、\n * portal 内容同步(打开期迁移 + 真删除清理)与微任务调度。\n *\n * 交互语义与 Lit 响应式由各组件保留(同 anchored-panel 的边界约定):\n * 组件通过 host 回调注入差异点(迁移节点来源、打开期即时同步等),\n * 并持有 _options/_activeIndex 的 @state,shared 只经返回值同步数据。\n */\n\nexport interface OptionPortalHost {\n /** 承载宿主级监听的元素(select / autocomplete 宿主自身)。 */\n readonly element: HTMLElement\n /** option id 前缀;宿主应保证跨实例唯一(如含实例序号),供 aria 引用与样式定位。 */\n readonly idPrefix: string\n getPortal(): OverlayPortal | undefined\n getPortalContent(): HTMLElement | undefined\n /** 打开判定:syncPortalContent 仅在打开期执行内容迁移。 */\n isOpen(): boolean\n /**\n * 打开期待迁移的节点来源。两组件粒度不同(select 迁裸 option,\n * autocomplete 迁全部 childNodes),保持各自现状不在此统一。\n */\n getMigratableNodes(): Node[]\n /** 微任务回调中判断宿主是否已完成首次渲染(Lit hasUpdated)。 */\n hasUpdated(): boolean\n /** 微任务末的宿主刷新(requestUpdate)。 */\n requestUpdate(): void\n /**\n * 打开期即时同步钩子。select 在微任务内立即刷新标签与激活态\n * (portal 打开时 willUpdate 查不到面板外的新增项),autocomplete 无此需要。\n */\n onImmediateSync?(): void\n /**\n * 组件侧的逐元素交互监听(click/pointerover/pointerdown/option-update)。\n * shared 负责随 refresh 配对调用,语义由组件定义。\n */\n bindOption(option: WebUiOption): void\n unbindOption(option: WebUiOption): void\n}\n\nexport interface OptionPortalApi {\n /** 面板优先查询,面板无 option 或关闭时回退 light DOM。 */\n queryOptions(): WebUiOption[]\n /**\n * diff 出旧集合解绑、新集合绑定;按 option 身份保持激活索引。\n * 返回新注册表数组与新的激活索引(-1 表示激活项已失效,如移除/迁移)。\n */\n refresh(\n previous: readonly WebUiOption[],\n activeOption: WebUiOption | undefined\n ): {\n options: WebUiOption[]\n activeIndex: number\n }\n /** 稳定 id 分配(WeakMap 身份 + 冲突循环),跨 remove/reorder 保持不变。 */\n ensureOptionIds(options: readonly WebUiOption[]): void\n /** pending 真删除清理 + 打开期把滞留 light DOM 的节点迁入面板。 */\n syncPortalContent(): void\n /** register/unregister/contentChange 的统一微任务收敛入口(slot 监听由组件模板持有)。 */\n scheduleRefresh(): void\n /** 宿主级与逐元素 unregister 监听共用的处理器。 */\n handleOptionUnregister(event: Event): void\n /** 宿主级监听配对(connectedCallback 调用,与 dispose 配对)。 */\n bindHost(): void\n /** 断连清理:解绑宿主监听并清空 pending 集合。 */\n dispose(): void\n}\n\nconst OPTION_TAG = 'web-ui-option'\n\nexport const defineOptionPortal = () =>\n definePlugin<OptionPortalApi, OptionPortalHost>(host => {\n const pendingUnregistered = new Set<WebUiOption>()\n // 稳定 id:WeakMap 按 option 身份记忆分配结果,remove/reorder 后 id 不变;\n // aria-activedescendant / 外部样式都依赖 id 的时序稳定性。\n const optionIds = new WeakMap<WebUiOption, string>()\n let nextOptionId = 0\n\n const bindOption = (option: WebUiOption) => {\n host.bindOption(option)\n option.addEventListener('option-unregister', handleOptionUnregister)\n }\n\n const unbindOption = (option: WebUiOption) => {\n host.unbindOption(option)\n option.removeEventListener('option-unregister', handleOptionUnregister)\n }\n\n const handleOptionUnregister = (event: Event) => {\n // Portal 迁移与真实删除都会触发 unregister;先记录候选,\n // 微任务末 syncPortalContent 按最终位置区分二者。\n if (event.target instanceof HTMLElement && event.target.localName === OPTION_TAG) {\n pendingUnregistered.add(event.target as WebUiOption)\n }\n api.scheduleRefresh()\n }\n\n const handleRegister = () => {\n api.scheduleRefresh()\n }\n\n const api: OptionPortalApi = {\n queryOptions() {\n const panel = host.getPortal()?.panel ?? host.getPortalContent()\n const panelOptions = panel ? [...panel.querySelectorAll<WebUiOption>(OPTION_TAG)] : []\n // 面板内容仅在浮层打开、moveContent 之后存在;关闭时回退 light DOM,\n // 否则 portal 组件首次加载时注册表为空。\n return panelOptions.length > 0 ? panelOptions : [...host.element.querySelectorAll<WebUiOption>(OPTION_TAG)]\n },\n\n refresh(previous, activeOption) {\n const nextOptions = api.queryOptions()\n\n // register/unregister 是 composed 事件:Portal 迁移时 target 会 retarget 成宿主,\n // 不能作为监听器绑定依据;绑定只发生在按 DOM 查询确认身份的 option 上。\n for (const option of previous) if (!nextOptions.includes(option)) unbindOption(option)\n nextOptions.forEach(bindOption)\n\n // 激活索引按 option 身份保持,避免移除中间项后静默偏移到相邻项\n return {\n options: nextOptions,\n activeIndex: activeOption && nextOptions.includes(activeOption) ? nextOptions.indexOf(activeOption) : -1\n }\n },\n\n ensureOptionIds(registryOptions) {\n const usedIds = new Set(registryOptions.map(option => option.id).filter((id): id is string => Boolean(id)))\n registryOptions.forEach(option => {\n if (option.id) return\n\n let id = optionIds.get(option)\n if (!id || usedIds.has(id)) {\n do {\n id = `${host.idPrefix}-option-${++nextOptionId}`\n } while (usedIds.has(id))\n optionIds.set(option, id)\n }\n option.id = id\n usedIds.add(id)\n })\n },\n\n syncPortalContent() {\n const portal = host.getPortal()\n const portalContent = host.getPortalContent()\n if (!portal || !portalContent) return\n\n // 真删除(既不在面板也不在 light DOM)的 option 从 portal 追踪列表摘除,\n // 否则关闭时 restoreContent 会把它复活回 light DOM。\n for (const option of pendingUnregistered) {\n if (!portalContent.contains(option) && !host.element.contains(option)) portal.removeContent([option])\n }\n pendingUnregistered.clear()\n\n if (!host.isOpen()) return\n\n // Portal 打开期间框架条件渲染(v-if)可能在 light DOM 插入新节点;\n // 面板内已有内容时 queryOptions 不会回退 light DOM,必须显式迁入面板。\n const nodes = host.getMigratableNodes()\n if (nodes.length) portal.appendContent(nodes, portalContent)\n },\n\n scheduleRefresh() {\n queueMicrotask(() => {\n if (!host.element.isConnected) return\n api.syncPortalContent()\n host.requestUpdate()\n // 关闭状态下 willUpdate 查不到 portal 面板内容;打开期的新增/删除\n // 由宿主在微任务内立即同步标签与激活态。\n if (host.isOpen() && host.hasUpdated()) host.onImmediateSync?.()\n })\n },\n\n handleOptionUnregister,\n\n bindHost() {\n host.element.addEventListener('option-register', handleRegister)\n host.element.addEventListener('option-unregister', handleOptionUnregister)\n },\n\n dispose() {\n host.element.removeEventListener('option-register', handleRegister)\n host.element.removeEventListener('option-unregister', handleOptionUnregister)\n pendingUnregistered.clear()\n }\n }\n\n return api\n })\n"],"mappings":";;AAyEA,IAAM,IAAa,iBAEN,UACX,GAAgD,MAAQ;CACtD,IAAM,oBAAsB,IAAI,IAAiB,GAG3C,oBAAY,IAAI,QAA6B,GAC/C,IAAe,GAEb,KAAc,MAAwB;EAE1C,AADA,EAAK,WAAW,CAAM,GACtB,EAAO,iBAAiB,qBAAqB,CAAsB;CACrE,GAEM,KAAgB,MAAwB;EAE5C,AADA,EAAK,aAAa,CAAM,GACxB,EAAO,oBAAoB,qBAAqB,CAAsB;CACxE,GAEM,KAA0B,MAAiB;EAM/C,AAHI,EAAM,kBAAkB,eAAe,EAAM,OAAO,cAAc,KACpE,EAAoB,IAAI,EAAM,MAAqB,GAErD,EAAI,gBAAgB;CACtB,GAEM,UAAuB;EAC3B,EAAI,gBAAgB;CACtB,GAEM,IAAuB;EAC3B,eAAe;GACb,IAAM,IAAQ,EAAK,UAAU,CAAC,EAAE,SAAS,EAAK,iBAAiB,GACzD,IAAe,IAAQ,CAAC,GAAG,EAAM,iBAA8B,CAAU,CAAC,IAAI,CAAC;GAGrF,OAAO,EAAa,SAAS,IAAI,IAAe,CAAC,GAAG,EAAK,QAAQ,iBAA8B,CAAU,CAAC;EAC5G;EAEA,QAAQ,GAAU,GAAc;GAC9B,IAAM,IAAc,EAAI,aAAa;GAIrC,KAAK,IAAM,KAAU,GAAU,AAAK,EAAY,SAAS,CAAM,KAAG,EAAa,CAAM;GAIrF,OAHA,EAAY,QAAQ,CAAU,GAGvB;IACL,SAAS;IACT,aAAa,KAAgB,EAAY,SAAS,CAAY,IAAI,EAAY,QAAQ,CAAY,IAAI;GACxG;EACF;EAEA,gBAAgB,GAAiB;GAC/B,IAAM,IAAU,IAAI,IAAI,EAAgB,KAAI,MAAU,EAAO,EAAE,CAAC,CAAC,QAAQ,MAAqB,EAAQ,CAAG,CAAC;GAC1G,EAAgB,SAAQ,MAAU;IAChC,IAAI,EAAO,IAAI;IAEf,IAAI,IAAK,EAAU,IAAI,CAAM;IAC7B,IAAI,CAAC,KAAM,EAAQ,IAAI,CAAE,GAAG;KAC1B;MACE,IAAK,GAAG,EAAK,SAAS,UAAU,EAAE;YAC3B,EAAQ,IAAI,CAAE;KACvB,EAAU,IAAI,GAAQ,CAAE;IAC1B;IAEA,AADA,EAAO,KAAK,GACZ,EAAQ,IAAI,CAAE;GAChB,CAAC;EACH;EAEA,oBAAoB;GAClB,IAAM,IAAS,EAAK,UAAU,GACxB,IAAgB,EAAK,iBAAiB;GAC5C,IAAI,CAAC,KAAU,CAAC,GAAe;GAI/B,KAAK,IAAM,KAAU,GACnB,AAAI,CAAC,EAAc,SAAS,CAAM,KAAK,CAAC,EAAK,QAAQ,SAAS,CAAM,KAAG,EAAO,cAAc,CAAC,CAAM,CAAC;GAItG,IAFA,EAAoB,MAAM,GAEtB,CAAC,EAAK,OAAO,GAAG;GAIpB,IAAM,IAAQ,EAAK,mBAAmB;GACtC,AAAI,EAAM,UAAQ,EAAO,cAAc,GAAO,CAAa;EAC7D;EAEA,kBAAkB;GAChB,qBAAqB;IACd,EAAK,QAAQ,gBAClB,EAAI,kBAAkB,GACtB,EAAK,cAAc,GAGf,EAAK,OAAO,KAAK,EAAK,WAAW,KAAG,EAAK,kBAAkB;GACjE,CAAC;EACH;EAEA;EAEA,WAAW;GAET,AADA,EAAK,QAAQ,iBAAiB,mBAAmB,CAAc,GAC/D,EAAK,QAAQ,iBAAiB,qBAAqB,CAAsB;EAC3E;EAEA,UAAU;GAGR,AAFA,EAAK,QAAQ,oBAAoB,mBAAmB,CAAc,GAClE,EAAK,QAAQ,oBAAoB,qBAAqB,CAAsB,GAC5E,EAAoB,MAAM;EAC5B;CACF;CAEA,OAAO;AACT,CAAC"}
@@ -57,7 +57,7 @@ var h = () => o((n) => {
57
57
  strategy: r.strategy,
58
58
  middleware: n
59
59
  }).then(({ x: e, y: t, placement: n }) => {
60
- i.style.left = `${e}px`, i.style.top = `${t}px`, i.style.setProperty("--wui-overlay-transform-origin", m(n));
60
+ i.style.left = `${e}px`, i.style.top = `${t}px`, i.style.setProperty("--wui-internal-overlay-transform-origin", m(n));
61
61
  });
62
62
  };
63
63
  o(), s?.(), s = e(c, i, o);
@@ -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\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 元素的定位基准(如右键菜单的鼠标坐标)。 */\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): Middleware | undefined {\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 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 // 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): 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)\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\n function updatePosition() {\n const middleware = createPositioningMiddleware(overlay, options, widthStyles)\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 overlay.style.left = `${x}px`\n overlay.style.top = `${y}px`\n overlay.style.setProperty('--wui-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 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\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 元素的定位基准(如右键菜单的鼠标坐标)。 */\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): Middleware | undefined {\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 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 // 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): 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)\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\n function updatePosition() {\n const middleware = createPositioningMiddleware(overlay, options, widthStyles)\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 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 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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"}
@@ -8,29 +8,32 @@ var i = () => r((e) => {
8
8
  let n = t.attachShadow({ mode: "open" }), r = document.createElement("style");
9
9
  r.textContent = e.style;
10
10
  let i = document.createElement("div");
11
- i.className = e.className, i.dataset.wuiPresence = "entering", n.append(r, i), a(e.container, e.target).appendChild(t);
12
- let o = [], s = (e) => {
11
+ i.className = e.className, i.dataset.wuiPresence = "entering", n.append(r, i);
12
+ let o = [], s = new MutationObserver(() => e.onContentChange?.()), c = (e) => {
13
13
  e.forEach((e) => {
14
14
  let t = o.indexOf(e);
15
15
  t >= 0 && o.splice(t, 1);
16
16
  });
17
17
  };
18
- return {
18
+ return a(e.container, e.target).appendChild(t), e.onContentChange && s.observe(i, {
19
+ childList: !0,
20
+ subtree: !0
21
+ }), {
19
22
  panel: i,
20
23
  moveContent(e, t = i) {
21
24
  o.splice(0, o.length, ...e), t.append(...o);
22
25
  },
23
26
  appendContent(e, t = i) {
24
- s(e), o.push(...e), t.append(...e);
27
+ c(e), o.push(...e), t.append(...e);
25
28
  },
26
29
  removeContent(e) {
27
- s(e);
30
+ c(e);
28
31
  },
29
32
  restoreContent() {
30
33
  e.target.append(...o), o.length = 0;
31
34
  },
32
35
  remove() {
33
- t.remove();
36
+ s.disconnect(), t.remove();
34
37
  }
35
38
  };
36
39
  });
@@ -1 +1 @@
1
- {"version":3,"file":"portal.js","names":[],"sources":["../../../src/shared/overlay/portal.ts"],"sourcesContent":["import { definePlugin } from '@greypan/js-kit'\n\nimport { getFallbackOverlayRoot } from '@/shared/theme/overlay-root'\nimport { findNearestTheme, findRootTheme } from '@/shared/theme/theme-scope'\n\nexport type OverlayContainer = HTMLElement | (() => HTMLElement | undefined)\n\nexport interface OverlayContainerResolutionOptions {\n /** 无 target 时优先使用文档中的 root theme。 */\n preferRootTheme?: boolean\n}\n\nexport interface OverlayPortalOptions {\n container?: OverlayContainer\n target: Element\n style: string\n className: string\n}\n\nexport interface OverlayPortal {\n readonly panel: HTMLElement\n restoreContent(): void\n moveContent(nodes: Node[], target?: HTMLElement): void\n appendContent(nodes: Node[], target?: HTMLElement): void\n removeContent(nodes: Node[]): void\n remove(): void\n}\n\n/**\n * 构建带 Shadow DOM 样式边界的 Portal 面板,并追踪被迁移的内容节点以便恢复。\n */\nexport const defineOverlayPortal = () =>\n definePlugin<OverlayPortal, OverlayPortalOptions>(ctx => {\n const host = document.createElement('div')\n // :host 规则可能让宿主在 overlay 容器中生成行盒;display: contents 保留面板定位而不参与布局。\n host.style.display = 'contents'\n\n const root = host.attachShadow({ mode: 'open' })\n const style = document.createElement('style')\n style.textContent = ctx.style\n const panel = document.createElement('div')\n panel.className = ctx.className\n panel.dataset.wuiPresence = 'entering'\n root.append(style, panel)\n\n resolveOverlayContainer(ctx.container, ctx.target).appendChild(host)\n\n const trackedNodes: Node[] = []\n const untrackNodes = (nodes: Node[]) => {\n nodes.forEach(node => {\n const index = trackedNodes.indexOf(node)\n if (index >= 0) trackedNodes.splice(index, 1)\n })\n }\n\n return {\n panel,\n\n moveContent(nodes, target = panel) {\n trackedNodes.splice(0, trackedNodes.length, ...nodes)\n target.append(...trackedNodes)\n },\n\n appendContent(nodes, target = panel) {\n untrackNodes(nodes)\n trackedNodes.push(...nodes)\n target.append(...nodes)\n },\n\n removeContent(nodes) {\n untrackNodes(nodes)\n },\n\n restoreContent() {\n ctx.target.append(...trackedNodes)\n trackedNodes.length = 0\n },\n\n remove() {\n host.remove()\n }\n }\n })\n\nexport function resolveOverlayContainer(\n container: OverlayContainer | undefined,\n target: Element,\n options: OverlayContainerResolutionOptions = {}\n): HTMLElement {\n const explicit = typeof container === 'function' ? container() : container\n if (explicit) return explicit\n\n // 无 target 的调用(如菜单)优先使用 root theme;有 target 时使用最近的 theme。\n const theme = options.preferRootTheme ? findRootTheme() : findNearestTheme(target)\n return theme?.getOverlayRoot() ?? getFallbackOverlayRoot()\n}\n"],"mappings":";;;;AA+BA,IAAa,UACX,GAAkD,MAAO;CACvD,IAAM,IAAO,SAAS,cAAc,KAAK;CAEzC,EAAK,MAAM,UAAU;CAErB,IAAM,IAAO,EAAK,aAAa,EAAE,MAAM,OAAO,CAAC,GACzC,IAAQ,SAAS,cAAc,OAAO;CAC5C,EAAM,cAAc,EAAI;CACxB,IAAM,IAAQ,SAAS,cAAc,KAAK;CAK1C,AAJA,EAAM,YAAY,EAAI,WACtB,EAAM,QAAQ,cAAc,YAC5B,EAAK,OAAO,GAAO,CAAK,GAExB,EAAwB,EAAI,WAAW,EAAI,MAAM,CAAC,CAAC,YAAY,CAAI;CAEnE,IAAM,IAAuB,CAAC,GACxB,KAAgB,MAAkB;EACtC,EAAM,SAAQ,MAAQ;GACpB,IAAM,IAAQ,EAAa,QAAQ,CAAI;GACvC,AAAI,KAAS,KAAG,EAAa,OAAO,GAAO,CAAC;EAC9C,CAAC;CACH;CAEA,OAAO;EACL;EAEA,YAAY,GAAO,IAAS,GAAO;GAEjC,AADA,EAAa,OAAO,GAAG,EAAa,QAAQ,GAAG,CAAK,GACpD,EAAO,OAAO,GAAG,CAAY;EAC/B;EAEA,cAAc,GAAO,IAAS,GAAO;GAGnC,AAFA,EAAa,CAAK,GAClB,EAAa,KAAK,GAAG,CAAK,GAC1B,EAAO,OAAO,GAAG,CAAK;EACxB;EAEA,cAAc,GAAO;GACnB,EAAa,CAAK;EACpB;EAEA,iBAAiB;GAEf,AADA,EAAI,OAAO,OAAO,GAAG,CAAY,GACjC,EAAa,SAAS;EACxB;EAEA,SAAS;GACP,EAAK,OAAO;EACd;CACF;AACF,CAAC;AAEH,SAAgB,EACd,GACA,GACA,IAA6C,CAAC,GACjC;CAMb,QALiB,OAAO,KAAc,aAAa,EAAU,IAAI,QAInD,EAAQ,kBAAkB,EAAc,IAAI,EAAiB,CAAM,EAAA,EACnE,eAAe,KAAK,EAAuB;AAC3D"}
1
+ {"version":3,"file":"portal.js","names":[],"sources":["../../../src/shared/overlay/portal.ts"],"sourcesContent":["import { definePlugin } from '@greypan/js-kit'\n\nimport { getFallbackOverlayRoot } from '@/shared/theme/overlay-root'\nimport { findNearestTheme, findRootTheme } from '@/shared/theme/theme-scope'\n\nexport type OverlayContainer = HTMLElement | (() => HTMLElement | undefined)\n\nexport interface OverlayContainerResolutionOptions {\n /** 无 target 时优先使用文档中的 root theme。 */\n preferRootTheme?: boolean\n}\n\nexport interface OverlayPortalOptions {\n container?: OverlayContainer\n target: Element\n style: string\n className: string\n onContentChange?: () => void\n}\n\nexport interface OverlayPortal {\n readonly panel: HTMLElement\n restoreContent(): void\n moveContent(nodes: Node[], target?: HTMLElement): void\n appendContent(nodes: Node[], target?: HTMLElement): void\n removeContent(nodes: Node[]): void\n remove(): void\n}\n\n/**\n * 构建带 Shadow DOM 样式边界的 Portal 面板,并追踪被迁移的内容节点以便恢复。\n */\nexport const defineOverlayPortal = () =>\n definePlugin<OverlayPortal, OverlayPortalOptions>(ctx => {\n const host = document.createElement('div')\n // :host 规则可能让宿主在 overlay 容器中生成行盒;display: contents 保留面板定位而不参与布局。\n host.style.display = 'contents'\n\n const root = host.attachShadow({ mode: 'open' })\n const style = document.createElement('style')\n style.textContent = ctx.style\n const panel = document.createElement('div')\n panel.className = ctx.className\n panel.dataset.wuiPresence = 'entering'\n root.append(style, panel)\n\n const trackedNodes: Node[] = []\n const contentObserver = new MutationObserver(() => ctx.onContentChange?.())\n const untrackNodes = (nodes: Node[]) => {\n nodes.forEach(node => {\n const index = trackedNodes.indexOf(node)\n if (index >= 0) trackedNodes.splice(index, 1)\n })\n }\n\n resolveOverlayContainer(ctx.container, ctx.target).appendChild(host)\n if (ctx.onContentChange) contentObserver.observe(panel, { childList: true, subtree: true })\n\n return {\n panel,\n\n moveContent(nodes, target = panel) {\n trackedNodes.splice(0, trackedNodes.length, ...nodes)\n target.append(...trackedNodes)\n },\n\n appendContent(nodes, target = panel) {\n untrackNodes(nodes)\n trackedNodes.push(...nodes)\n target.append(...nodes)\n },\n\n removeContent(nodes) {\n untrackNodes(nodes)\n },\n\n restoreContent() {\n ctx.target.append(...trackedNodes)\n trackedNodes.length = 0\n },\n\n remove() {\n contentObserver.disconnect()\n host.remove()\n }\n }\n })\n\nexport function resolveOverlayContainer(\n container: OverlayContainer | undefined,\n target: Element,\n options: OverlayContainerResolutionOptions = {}\n): HTMLElement {\n const explicit = typeof container === 'function' ? container() : container\n if (explicit) return explicit\n\n // 无 target 的调用(如菜单)优先使用 root theme;有 target 时使用最近的 theme。\n const theme = options.preferRootTheme ? findRootTheme() : findNearestTheme(target)\n return theme?.getOverlayRoot() ?? getFallbackOverlayRoot()\n}\n"],"mappings":";;;;AAgCA,IAAa,UACX,GAAkD,MAAO;CACvD,IAAM,IAAO,SAAS,cAAc,KAAK;CAEzC,EAAK,MAAM,UAAU;CAErB,IAAM,IAAO,EAAK,aAAa,EAAE,MAAM,OAAO,CAAC,GACzC,IAAQ,SAAS,cAAc,OAAO;CAC5C,EAAM,cAAc,EAAI;CACxB,IAAM,IAAQ,SAAS,cAAc,KAAK;CAG1C,AAFA,EAAM,YAAY,EAAI,WACtB,EAAM,QAAQ,cAAc,YAC5B,EAAK,OAAO,GAAO,CAAK;CAExB,IAAM,IAAuB,CAAC,GACxB,IAAkB,IAAI,uBAAuB,EAAI,kBAAkB,CAAC,GACpE,KAAgB,MAAkB;EACtC,EAAM,SAAQ,MAAQ;GACpB,IAAM,IAAQ,EAAa,QAAQ,CAAI;GACvC,AAAI,KAAS,KAAG,EAAa,OAAO,GAAO,CAAC;EAC9C,CAAC;CACH;CAKA,OAHA,EAAwB,EAAI,WAAW,EAAI,MAAM,CAAC,CAAC,YAAY,CAAI,GAC/D,EAAI,mBAAiB,EAAgB,QAAQ,GAAO;EAAE,WAAW;EAAM,SAAS;CAAK,CAAC,GAEnF;EACL;EAEA,YAAY,GAAO,IAAS,GAAO;GAEjC,AADA,EAAa,OAAO,GAAG,EAAa,QAAQ,GAAG,CAAK,GACpD,EAAO,OAAO,GAAG,CAAY;EAC/B;EAEA,cAAc,GAAO,IAAS,GAAO;GAGnC,AAFA,EAAa,CAAK,GAClB,EAAa,KAAK,GAAG,CAAK,GAC1B,EAAO,OAAO,GAAG,CAAK;EACxB;EAEA,cAAc,GAAO;GACnB,EAAa,CAAK;EACpB;EAEA,iBAAiB;GAEf,AADA,EAAI,OAAO,OAAO,GAAG,CAAY,GACjC,EAAa,SAAS;EACxB;EAEA,SAAS;GAEP,AADA,EAAgB,WAAW,GAC3B,EAAK,OAAO;EACd;CACF;AACF,CAAC;AAEH,SAAgB,EACd,GACA,GACA,IAA6C,CAAC,GACjC;CAMb,QALiB,OAAO,KAAc,aAAa,EAAU,IAAI,QAInD,EAAQ,kBAAkB,EAAc,IAAI,EAAiB,CAAM,EAAA,EACnE,eAAe,KAAK,EAAuB;AAC3D"}
@@ -1 +1 @@
1
- {"version":3,"file":"presence.js","names":[],"sources":["../../../src/shared/overlay/presence.ts"],"sourcesContent":["const DEFAULT_EXIT_DURATION = 160\nconst EXIT_FALLBACK_BUFFER = 80\n\nconst pendingExits = new WeakMap<HTMLElement, () => void>()\n\ninterface OverlayPresenceOptions {\n isInstant?: boolean\n}\n\nfunction parseDuration(value: string): number {\n const trimmed = value.trim()\n if (trimmed.endsWith('ms')) return Number.parseFloat(trimmed)\n if (trimmed.endsWith('s')) return Number.parseFloat(trimmed) * 1000\n return 0\n}\n\n// 读取元素实际 CSS transition 的最长时长,供可覆盖主题 token 的 JS 生命周期使用。\nexport function getTransitionDuration(panel: HTMLElement, fallback = DEFAULT_EXIT_DURATION): number {\n const style = getComputedStyle(panel)\n const durations = style.transitionDuration.split(',').map(parseDuration)\n const delays = style.transitionDelay.split(',').map(parseDuration)\n const longest = durations.reduce(\n (max, duration, index) => Math.max(max, duration + (delays[index] ?? delays[0] ?? 0)),\n 0\n )\n\n return longest || fallback\n}\n\n// 以可中断的方式显示浮层,避免快速切换时重启动画。\nexport function showOverlayPresence(panel: HTMLElement, options: OverlayPresenceOptions = {}) {\n pendingExits.get(panel)?.()\n panel.hidden = false\n if (options.isInstant) {\n panel.dataset.wuiPresence = 'open'\n return\n }\n panel.dataset.wuiPresence = 'entering'\n\n // Commit the initial state so a newly mounted panel has a transition source.\n void panel.offsetWidth\n\n requestAnimationFrame(() => {\n if (panel.dataset.wuiPresence === 'entering') panel.dataset.wuiPresence = 'open'\n })\n}\n\n// 播放退出过渡;若在结束前重新显示则返回 false。\nexport function hideOverlayPresence(panel: HTMLElement): Promise<boolean> {\n pendingExits.get(panel)?.()\n panel.dataset.wuiPresence = 'closing'\n\n // jsdom 等没有计算 CSS transition 的环境无需伪造退场等待。\n if (getTransitionDuration(panel, 0) === 0) {\n panel.hidden = true\n return Promise.resolve(true)\n }\n\n return new Promise(resolve => {\n let settled = false\n const finish = (didClose: boolean) => {\n if (settled) return\n settled = true\n panel.removeEventListener('transitionend', onTransitionEnd)\n clearTimeout(timeout)\n pendingExits.delete(panel)\n if (didClose && panel.dataset.wuiPresence === 'closing') panel.hidden = true\n resolve(didClose)\n }\n\n const onTransitionEnd = (event: TransitionEvent) => {\n if (event.target === panel && (event.propertyName === 'opacity' || event.propertyName === 'transform'))\n finish(true)\n }\n\n pendingExits.set(panel, () => finish(false))\n panel.addEventListener('transitionend', onTransitionEnd)\n const timeout = setTimeout(() => finish(true), getTransitionDuration(panel) + EXIT_FALLBACK_BUFFER)\n })\n}\n"],"mappings":";AAAA,IAGM,oBAAe,IAAI,QAAiC;AAM1D,SAAS,EAAc,GAAuB;CAC5C,IAAM,IAAU,EAAM,KAAK;CAG3B,OAFI,EAAQ,SAAS,IAAI,IAAU,OAAO,WAAW,CAAO,IACxD,EAAQ,SAAS,GAAG,IAAU,OAAO,WAAW,CAAO,IAAI,MACxD;AACT;AAGA,SAAgB,EAAsB,GAAoB,IAAW,KAA+B;CAClG,IAAM,IAAQ,iBAAiB,CAAK,GAC9B,IAAY,EAAM,mBAAmB,MAAM,GAAG,CAAC,CAAC,IAAI,CAAa,GACjE,IAAS,EAAM,gBAAgB,MAAM,GAAG,CAAC,CAAC,IAAI,CAAa;CAMjE,OALgB,EAAU,QACvB,GAAK,GAAU,MAAU,KAAK,IAAI,GAAK,KAAY,EAAO,MAAU,EAAO,MAAM,EAAE,GACpF,CAGK,KAAW;AACpB;AAGA,SAAgB,EAAoB,GAAoB,IAAkC,CAAC,GAAG;CAG5F,IAFA,EAAa,IAAI,CAAK,CAAC,GAAG,GAC1B,EAAM,SAAS,IACX,EAAQ,WAAW;EACrB,EAAM,QAAQ,cAAc;EAC5B;CACF;CAMA,AALA,EAAM,QAAQ,cAAc,YAG5B,EAAW,aAEX,4BAA4B;EAC1B,AAAI,EAAM,QAAQ,gBAAgB,eAAY,EAAM,QAAQ,cAAc;CAC5E,CAAC;AACH;AAGA,SAAgB,EAAoB,GAAsC;CAUxE,OATA,EAAa,IAAI,CAAK,CAAC,GAAG,GAC1B,EAAM,QAAQ,cAAc,WAGxB,EAAsB,GAAO,CAAC,MAAM,KACtC,EAAM,SAAS,IACR,QAAQ,QAAQ,EAAI,KAGtB,IAAI,SAAQ,MAAW;EAC5B,IAAI,IAAU,IACR,KAAU,MAAsB;GAChC,MACJ,IAAU,IACV,EAAM,oBAAoB,iBAAiB,CAAe,GAC1D,aAAa,CAAO,GACpB,EAAa,OAAO,CAAK,GACrB,KAAY,EAAM,QAAQ,gBAAgB,cAAW,EAAM,SAAS,KACxE,EAAQ,CAAQ;EAClB,GAEM,KAAmB,MAA2B;GAClD,AAAI,EAAM,WAAW,MAAU,EAAM,iBAAiB,aAAa,EAAM,iBAAiB,gBACxF,EAAO,EAAI;EACf;EAGA,AADA,EAAa,IAAI,SAAa,EAAO,EAAK,CAAC,GAC3C,EAAM,iBAAiB,iBAAiB,CAAe;EACvD,IAAM,IAAU,iBAAiB,EAAO,EAAI,GAAG,EAAsB,CAAK,IAAI,EAAoB;CACpG,CAAC;AACH"}
1
+ {"version":3,"file":"presence.js","names":[],"sources":["../../../src/shared/overlay/presence.ts"],"sourcesContent":["const DEFAULT_EXIT_DURATION = 160\nconst EXIT_FALLBACK_BUFFER = 80\n\nconst pendingExits = new WeakMap<HTMLElement, () => void>()\n\ninterface OverlayPresenceOptions {\n isInstant?: boolean\n}\n\nfunction parseDuration(value: string): number {\n const trimmed = value.trim()\n if (trimmed.endsWith('ms')) return Number.parseFloat(trimmed)\n if (trimmed.endsWith('s')) return Number.parseFloat(trimmed) * 1000\n return 0\n}\n\n// 读取元素实际 CSS transition 的最长时长,供可覆盖主题 token 的 JS 生命周期使用。\nexport function getTransitionDuration(panel: HTMLElement, fallback = DEFAULT_EXIT_DURATION): number {\n const style = getComputedStyle(panel)\n const durations = style.transitionDuration.split(',').map(parseDuration)\n const delays = style.transitionDelay.split(',').map(parseDuration)\n const longest = durations.reduce(\n (max, duration, index) => Math.max(max, duration + (delays[index] ?? delays[0] ?? 0)),\n 0\n )\n\n return longest || fallback\n}\n\n// 以可中断的方式显示浮层,避免快速切换时重启动画。\nexport function showOverlayPresence(panel: HTMLElement, options: OverlayPresenceOptions = {}) {\n pendingExits.get(panel)?.()\n panel.hidden = false\n if (options.isInstant) {\n panel.dataset.wuiPresence = 'open'\n return\n }\n panel.dataset.wuiPresence = 'entering'\n\n // 提交初始状态,让新挂载的面板拥有过渡起点。\n void panel.offsetWidth\n\n requestAnimationFrame(() => {\n if (panel.dataset.wuiPresence === 'entering') panel.dataset.wuiPresence = 'open'\n })\n}\n\n// 播放退出过渡;若在结束前重新显示则返回 false。\nexport function hideOverlayPresence(panel: HTMLElement): Promise<boolean> {\n pendingExits.get(panel)?.()\n panel.dataset.wuiPresence = 'closing'\n\n // jsdom 等没有计算 CSS transition 的环境无需伪造退场等待。\n if (getTransitionDuration(panel, 0) === 0) {\n panel.hidden = true\n return Promise.resolve(true)\n }\n\n return new Promise(resolve => {\n let settled = false\n const finish = (didClose: boolean) => {\n if (settled) return\n settled = true\n panel.removeEventListener('transitionend', onTransitionEnd)\n clearTimeout(timeout)\n pendingExits.delete(panel)\n if (didClose && panel.dataset.wuiPresence === 'closing') panel.hidden = true\n resolve(didClose)\n }\n\n const onTransitionEnd = (event: TransitionEvent) => {\n if (event.target === panel && (event.propertyName === 'opacity' || event.propertyName === 'transform'))\n finish(true)\n }\n\n pendingExits.set(panel, () => finish(false))\n panel.addEventListener('transitionend', onTransitionEnd)\n const timeout = setTimeout(() => finish(true), getTransitionDuration(panel) + EXIT_FALLBACK_BUFFER)\n })\n}\n"],"mappings":";AAAA,IAGM,oBAAe,IAAI,QAAiC;AAM1D,SAAS,EAAc,GAAuB;CAC5C,IAAM,IAAU,EAAM,KAAK;CAG3B,OAFI,EAAQ,SAAS,IAAI,IAAU,OAAO,WAAW,CAAO,IACxD,EAAQ,SAAS,GAAG,IAAU,OAAO,WAAW,CAAO,IAAI,MACxD;AACT;AAGA,SAAgB,EAAsB,GAAoB,IAAW,KAA+B;CAClG,IAAM,IAAQ,iBAAiB,CAAK,GAC9B,IAAY,EAAM,mBAAmB,MAAM,GAAG,CAAC,CAAC,IAAI,CAAa,GACjE,IAAS,EAAM,gBAAgB,MAAM,GAAG,CAAC,CAAC,IAAI,CAAa;CAMjE,OALgB,EAAU,QACvB,GAAK,GAAU,MAAU,KAAK,IAAI,GAAK,KAAY,EAAO,MAAU,EAAO,MAAM,EAAE,GACpF,CAGK,KAAW;AACpB;AAGA,SAAgB,EAAoB,GAAoB,IAAkC,CAAC,GAAG;CAG5F,IAFA,EAAa,IAAI,CAAK,CAAC,GAAG,GAC1B,EAAM,SAAS,IACX,EAAQ,WAAW;EACrB,EAAM,QAAQ,cAAc;EAC5B;CACF;CAMA,AALA,EAAM,QAAQ,cAAc,YAG5B,EAAW,aAEX,4BAA4B;EAC1B,AAAI,EAAM,QAAQ,gBAAgB,eAAY,EAAM,QAAQ,cAAc;CAC5E,CAAC;AACH;AAGA,SAAgB,EAAoB,GAAsC;CAUxE,OATA,EAAa,IAAI,CAAK,CAAC,GAAG,GAC1B,EAAM,QAAQ,cAAc,WAGxB,EAAsB,GAAO,CAAC,MAAM,KACtC,EAAM,SAAS,IACR,QAAQ,QAAQ,EAAI,KAGtB,IAAI,SAAQ,MAAW;EAC5B,IAAI,IAAU,IACR,KAAU,MAAsB;GAChC,MACJ,IAAU,IACV,EAAM,oBAAoB,iBAAiB,CAAe,GAC1D,aAAa,CAAO,GACpB,EAAa,OAAO,CAAK,GACrB,KAAY,EAAM,QAAQ,gBAAgB,cAAW,EAAM,SAAS,KACxE,EAAQ,CAAQ;EAClB,GAEM,KAAmB,MAA2B;GAClD,AAAI,EAAM,WAAW,MAAU,EAAM,iBAAiB,aAAa,EAAM,iBAAiB,gBACxF,EAAO,EAAI;EACf;EAGA,AADA,EAAa,IAAI,SAAa,EAAO,EAAK,CAAC,GAC3C,EAAM,iBAAiB,iBAAiB,CAAe;EACvD,IAAM,IAAU,iBAAiB,EAAO,EAAI,GAAG,EAAsB,CAAK,IAAI,EAAoB;CACpG,CAAC;AACH"}
@@ -0,0 +1,69 @@
1
+ import { definePlugin as e } from "@greypan/js-kit";
2
+ //#region src/shared/visible-area/index.ts
3
+ var t = {
4
+ top: 0,
5
+ right: 0,
6
+ bottom: 0,
7
+ left: 0,
8
+ width: 0,
9
+ height: 0
10
+ };
11
+ function n(e) {
12
+ if (!e) return t;
13
+ let n = e.getBoundingClientRect(), r = Math.max(0, n.left), i = Math.max(0, n.top), a = Math.min(window.innerWidth, n.right), o = Math.min(window.innerHeight, n.bottom);
14
+ return a <= r || o <= i ? t : {
15
+ top: i,
16
+ right: a,
17
+ bottom: o,
18
+ left: r,
19
+ width: a - r,
20
+ height: o - i
21
+ };
22
+ }
23
+ function r(e, t) {
24
+ return e?.top === t.top && e.right === t.right && e.bottom === t.bottom && e.left === t.left && e.width === t.width && e.height === t.height;
25
+ }
26
+ function i(t) {
27
+ return e(() => {
28
+ let e = null, i, a, o = !1, s, c = () => {
29
+ if (a = void 0, !o) return;
30
+ let i = n(e);
31
+ r(s, i) || (s = i, t.onVisibleAreaChange(i));
32
+ }, l = () => {
33
+ !o || a !== void 0 || (a = requestAnimationFrame(c));
34
+ }, u = () => {
35
+ i?.disconnect(), i = void 0, !(!e || typeof ResizeObserver > "u") && (i = new ResizeObserver(l), i.observe(e));
36
+ };
37
+ return {
38
+ setTarget(t) {
39
+ if (e === t) {
40
+ l();
41
+ return;
42
+ }
43
+ e = t, s = void 0, o && (u(), l());
44
+ },
45
+ refresh: l,
46
+ connect() {
47
+ o || (o = !0, window.addEventListener("scroll", l, { passive: !0 }), window.addEventListener("resize", l, { passive: !0 }), u(), l());
48
+ },
49
+ disconnect() {
50
+ o && (o = !1, window.removeEventListener("scroll", l), window.removeEventListener("resize", l), i?.disconnect(), i = void 0, s = void 0, a !== void 0 && cancelAnimationFrame(a), a = void 0);
51
+ }
52
+ };
53
+ });
54
+ }
55
+ var a = class {
56
+ constructor(e, t) {
57
+ this.tracker = t, e.addController(this);
58
+ }
59
+ hostConnected() {
60
+ this.tracker.connect();
61
+ }
62
+ hostDisconnected() {
63
+ this.tracker.disconnect();
64
+ }
65
+ };
66
+ //#endregion
67
+ export { a as VisibleAreaController, i as defineVisibleAreaTracker };
68
+
69
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/shared/visible-area/index.ts"],"sourcesContent":["import { definePlugin } from '@greypan/js-kit'\nimport type { ReactiveController, ReactiveControllerHost } from 'lit'\n\nexport interface VisibleArea {\n readonly top: number\n readonly right: number\n readonly bottom: number\n readonly left: number\n readonly width: number\n readonly height: number\n}\n\nexport interface VisibleAreaTracker {\n setTarget(target: Element | null): void\n refresh(): void\n connect(): void\n disconnect(): void\n}\n\nconst emptyVisibleArea: VisibleArea = {\n top: 0,\n right: 0,\n bottom: 0,\n left: 0,\n width: 0,\n height: 0\n}\n\nfunction getVisibleArea(target: Element | null): VisibleArea {\n if (!target) return emptyVisibleArea\n\n const rect = target.getBoundingClientRect()\n const left = Math.max(0, rect.left)\n const top = Math.max(0, rect.top)\n const right = Math.min(window.innerWidth, rect.right)\n const bottom = Math.min(window.innerHeight, rect.bottom)\n\n if (right <= left || bottom <= top) return emptyVisibleArea\n\n return {\n top,\n right,\n bottom,\n left,\n width: right - left,\n height: bottom - top\n }\n}\n\nfunction visibleAreasEqual(a: VisibleArea | undefined, b: VisibleArea): boolean {\n return (\n a?.top === b.top &&\n a.right === b.right &&\n a.bottom === b.bottom &&\n a.left === b.left &&\n a.width === b.width &&\n a.height === b.height\n )\n}\n\n/**\n * 追踪单个元素与浏览器 viewport 的交集区域。\n *\n * 模块统一持有 ResizeObserver、viewport 监听器和 rAF 合帧;\n * 使用方只需管理 target 并响应可见区域变化。\n */\nexport function defineVisibleAreaTracker(options: { readonly onVisibleAreaChange: (area: VisibleArea) => void }) {\n return definePlugin<VisibleAreaTracker, Record<never, never>>(() => {\n let target: Element | null = null\n let observer: ResizeObserver | undefined\n let frame: number | undefined\n let connected = false\n let previousArea: VisibleArea | undefined\n\n const report = () => {\n frame = undefined\n if (!connected) return\n\n const nextArea = getVisibleArea(target)\n if (visibleAreasEqual(previousArea, nextArea)) return\n\n previousArea = nextArea\n options.onVisibleAreaChange(nextArea)\n }\n\n const refresh = () => {\n if (!connected || frame !== undefined) return\n frame = requestAnimationFrame(report)\n }\n\n const observeTarget = () => {\n observer?.disconnect()\n observer = undefined\n\n if (!target || typeof ResizeObserver === 'undefined') return\n\n observer = new ResizeObserver(refresh)\n observer.observe(target)\n }\n\n return {\n setTarget(nextTarget) {\n if (target === nextTarget) {\n refresh()\n return\n }\n\n target = nextTarget\n previousArea = undefined\n if (!connected) return\n\n observeTarget()\n refresh()\n },\n\n refresh,\n\n connect() {\n if (connected) return\n\n connected = true\n window.addEventListener('scroll', refresh, { passive: true })\n window.addEventListener('resize', refresh, { passive: true })\n observeTarget()\n refresh()\n },\n\n disconnect() {\n if (!connected) return\n\n connected = false\n window.removeEventListener('scroll', refresh)\n window.removeEventListener('resize', refresh)\n observer?.disconnect()\n observer = undefined\n previousArea = undefined\n\n if (frame !== undefined) cancelAnimationFrame(frame)\n frame = undefined\n }\n }\n })\n}\n\n/** 将内部可见区域追踪器接入 Lit host 生命周期。 */\nexport class VisibleAreaController implements ReactiveController {\n constructor(\n host: ReactiveControllerHost,\n private readonly tracker: VisibleAreaTracker\n ) {\n host.addController(this)\n }\n\n hostConnected() {\n this.tracker.connect()\n }\n\n hostDisconnected() {\n this.tracker.disconnect()\n }\n}\n"],"mappings":";;AAmBA,IAAM,IAAgC;CACpC,KAAK;CACL,OAAO;CACP,QAAQ;CACR,MAAM;CACN,OAAO;CACP,QAAQ;AACV;AAEA,SAAS,EAAe,GAAqC;CAC3D,IAAI,CAAC,GAAQ,OAAO;CAEpB,IAAM,IAAO,EAAO,sBAAsB,GACpC,IAAO,KAAK,IAAI,GAAG,EAAK,IAAI,GAC5B,IAAM,KAAK,IAAI,GAAG,EAAK,GAAG,GAC1B,IAAQ,KAAK,IAAI,OAAO,YAAY,EAAK,KAAK,GAC9C,IAAS,KAAK,IAAI,OAAO,aAAa,EAAK,MAAM;CAIvD,OAFI,KAAS,KAAQ,KAAU,IAAY,IAEpC;EACL;EACA;EACA;EACA;EACA,OAAO,IAAQ;EACf,QAAQ,IAAS;CACnB;AACF;AAEA,SAAS,EAAkB,GAA4B,GAAyB;CAC9E,OACE,GAAG,QAAQ,EAAE,OACb,EAAE,UAAU,EAAE,SACd,EAAE,WAAW,EAAE,UACf,EAAE,SAAS,EAAE,QACb,EAAE,UAAU,EAAE,SACd,EAAE,WAAW,EAAE;AAEnB;AAQA,SAAgB,EAAyB,GAAwE;CAC/G,OAAO,QAA6D;EAClE,IAAI,IAAyB,MACzB,GACA,GACA,IAAY,IACZ,GAEE,UAAe;GAEnB,IADA,IAAQ,KAAA,GACJ,CAAC,GAAW;GAEhB,IAAM,IAAW,EAAe,CAAM;GAClC,EAAkB,GAAc,CAAQ,MAE5C,IAAe,GACf,EAAQ,oBAAoB,CAAQ;EACtC,GAEM,UAAgB;GAChB,CAAC,KAAa,MAAU,KAAA,MAC5B,IAAQ,sBAAsB,CAAM;EACtC,GAEM,UAAsB;GAC1B,GAAU,WAAW,GACrB,IAAW,KAAA,GAEP,GAAC,KAAU,OAAO,iBAAmB,SAEzC,IAAW,IAAI,eAAe,CAAO,GACrC,EAAS,QAAQ,CAAM;EACzB;EAEA,OAAO;GACL,UAAU,GAAY;IACpB,IAAI,MAAW,GAAY;KACzB,EAAQ;KACR;IACF;IAEA,IAAS,GACT,IAAe,KAAA,GACV,MAEL,EAAc,GACd,EAAQ;GACV;GAEA;GAEA,UAAU;IACJ,MAEJ,IAAY,IACZ,OAAO,iBAAiB,UAAU,GAAS,EAAE,SAAS,GAAK,CAAC,GAC5D,OAAO,iBAAiB,UAAU,GAAS,EAAE,SAAS,GAAK,CAAC,GAC5D,EAAc,GACd,EAAQ;GACV;GAEA,aAAa;IACN,MAEL,IAAY,IACZ,OAAO,oBAAoB,UAAU,CAAO,GAC5C,OAAO,oBAAoB,UAAU,CAAO,GAC5C,GAAU,WAAW,GACrB,IAAW,KAAA,GACX,IAAe,KAAA,GAEX,MAAU,KAAA,KAAW,qBAAqB,CAAK,GACnD,IAAQ,KAAA;GACV;EACF;CACF,CAAC;AACH;AAGA,IAAa,IAAb,MAAiE;CAC/D,YACE,GACA,GACA;EACA,AAFiB,KAAA,UAAA,GAEjB,EAAK,cAAc,IAAI;CACzB;CAEA,gBAAgB;EACd,KAAK,QAAQ,QAAQ;CACvB;CAEA,mBAAmB;EACjB,KAAK,QAAQ,WAAW;CAC1B;AACF"}
@@ -32,11 +32,16 @@ export type WithHost<T extends EventTarget, E> = E extends Event ? E & {
32
32
  export type HostEventMap<T extends HTMLElement> = {
33
33
  [K in keyof ComponentEvents<T>]: WithHost<T, ComponentEvents<T>[K]>;
34
34
  };
35
+ /**
36
+ * 组件 `$events` 中的 string 事件名。
37
+ * 无公共事件或仅有非 string 键的组件得到 `never`。
38
+ */
39
+ export type WebUiEventName<T extends HTMLElement> = keyof ComponentEvents<T> & string;
35
40
  /**
36
41
  * 具名 handler 的显式事件类型,用于 Vue 命名 handler 等无法从上下文推导 `$event` 的位置。
37
42
  * 用法:`function onChange(e: WebUiEvent<WebUiSelect, 'change'>)`
38
43
  */
39
- export type WebUiEvent<T extends HTMLElement, K extends keyof HostEventMap<T>> = HostEventMap<T>[K];
44
+ export type WebUiEvent<T extends HTMLElement, K extends WebUiEventName<T>> = HostEventMap<T>[K];
40
45
  /**
41
46
  * React 自定义事件(kebab-case 事件名)→ `on<event>` 绑定。
42
47
  * 标准 DOM 事件(input/change/focus/blur)由 React 的 camelCase handler 提供,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@greypan/web-ui",
3
- "version": "3.0.0",
3
+ "version": "4.0.0",
4
4
  "description": "Lit-based web components for React, Vue, and vanilla JS",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -41,12 +41,12 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "@floating-ui/dom": "^1.8.0",
44
- "@greypan/js-kit": "1.6.6",
45
- "@greypan/browser-kit": "1.7.7"
44
+ "@greypan/js-kit": "2.0.0",
45
+ "@greypan/browser-kit": "2.0.0"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@iconify-json/jam": "^1.2.3",
49
- "@iconify-json/lucide": "^1.2.117",
49
+ "@iconify-json/lucide": "^1.2.126",
50
50
  "@iconify-json/si": "^1.2.17",
51
51
  "@iconify-json/heroicons": "^1.2.3",
52
52
  "@iconify-json/ooui": "^1.2.20",
@@ -56,7 +56,9 @@
56
56
  "lit": "^3.3.3",
57
57
  "vite-plugin-dts": "^5.0.3",
58
58
  "vue": "^3.5.39",
59
- "@greypan/tsconfig": "1.0.4"
59
+ "vite": "npm:@voidzero-dev/vite-plus-core@0.3.0",
60
+ "vite-plus": "0.3.0",
61
+ "@greypan/tsconfig": "1.0.5"
60
62
  },
61
63
  "peerDependencies": {
62
64
  "@types/react": ">=19",