@greypan/web-ui 2.1.7 → 3.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 (147) hide show
  1. package/README.CN.md +95 -41
  2. package/README.md +100 -42
  3. package/dist/assets/glass.js.map +1 -1
  4. package/dist/assets/menu-portal.js +1 -1
  5. package/dist/assets/menu-portal.js.map +1 -1
  6. package/dist/components/autocomplete/index.d.ts +121 -0
  7. package/dist/components/autocomplete/index.js +414 -0
  8. package/dist/components/autocomplete/index.js.map +1 -0
  9. package/dist/components/autocomplete/style.js +6 -0
  10. package/dist/components/autocomplete/style.js.map +1 -0
  11. package/dist/components/avatar/index.d.ts +0 -1
  12. package/dist/components/avatar/index.js +9 -9
  13. package/dist/components/avatar/index.js.map +1 -1
  14. package/dist/components/back-top/index.d.ts +0 -1
  15. package/dist/components/back-top/index.js +1 -1
  16. package/dist/components/back-top/index.js.map +1 -1
  17. package/dist/components/badge/index.d.ts +0 -1
  18. package/dist/components/badge/index.js +5 -5
  19. package/dist/components/badge/index.js.map +1 -1
  20. package/dist/components/button/index.d.ts +6 -2
  21. package/dist/components/button/index.js +40 -19
  22. package/dist/components/button/index.js.map +1 -1
  23. package/dist/components/button/style.js +1 -1
  24. package/dist/components/button/style.js.map +1 -1
  25. package/dist/components/button-group/index.d.ts +0 -1
  26. package/dist/components/button-group/index.js.map +1 -1
  27. package/dist/components/checkbox/index.d.ts +0 -3
  28. package/dist/components/checkbox/index.js +7 -5
  29. package/dist/components/checkbox/index.js.map +1 -1
  30. package/dist/components/checkbox-group/index.js +2 -2
  31. package/dist/components/checkbox-group/index.js.map +1 -1
  32. package/dist/components/context-menu/index.d.ts +4 -3
  33. package/dist/components/context-menu/index.js +47 -50
  34. package/dist/components/context-menu/index.js.map +1 -1
  35. package/dist/components/dialog/index.d.ts +1 -1
  36. package/dist/components/dialog/index.js +20 -19
  37. package/dist/components/dialog/index.js.map +1 -1
  38. package/dist/components/drawer/index.d.ts +2 -3
  39. package/dist/components/drawer/index.js +43 -33
  40. package/dist/components/drawer/index.js.map +1 -1
  41. package/dist/components/drawer/style.js +1 -1
  42. package/dist/components/drawer/style.js.map +1 -1
  43. package/dist/components/dropdown/index.d.ts +2 -9
  44. package/dist/components/dropdown/index.js +58 -53
  45. package/dist/components/dropdown/index.js.map +1 -1
  46. package/dist/components/dropdown-divider/index.d.ts +0 -1
  47. package/dist/components/dropdown-divider/index.js.map +1 -1
  48. package/dist/components/dropdown-header/index.d.ts +0 -1
  49. package/dist/components/dropdown-header/index.js.map +1 -1
  50. package/dist/components/dropdown-item/index.d.ts +0 -2
  51. package/dist/components/dropdown-item/index.js.map +1 -1
  52. package/dist/components/dropdown-item/style.js +1 -1
  53. package/dist/components/dropdown-item/style.js.map +1 -1
  54. package/dist/components/empty/index.d.ts +0 -1
  55. package/dist/components/empty/index.js +6 -6
  56. package/dist/components/empty/index.js.map +1 -1
  57. package/dist/components/icon/index.d.ts +0 -1
  58. package/dist/components/icon/index.js.map +1 -1
  59. package/dist/components/index.d.ts +5 -0
  60. package/dist/components/index.js +36 -35
  61. package/dist/components/input/index.d.ts +3 -0
  62. package/dist/components/input/index.js +48 -24
  63. package/dist/components/input/index.js.map +1 -1
  64. package/dist/components/input/style.js +1 -1
  65. package/dist/components/input/style.js.map +1 -1
  66. package/dist/components/input-number/index.d.ts +2 -0
  67. package/dist/components/input-number/index.js +25 -12
  68. package/dist/components/input-number/index.js.map +1 -1
  69. package/dist/components/input-number/style.js +1 -1
  70. package/dist/components/input-number/style.js.map +1 -1
  71. package/dist/components/layout/index.d.ts +0 -1
  72. package/dist/components/layout/index.js +4 -4
  73. package/dist/components/layout/index.js.map +1 -1
  74. package/dist/components/option/index.d.ts +0 -11
  75. package/dist/components/option/index.js +1 -1
  76. package/dist/components/option/index.js.map +1 -1
  77. package/dist/components/option/style.js +1 -1
  78. package/dist/components/option/style.js.map +1 -1
  79. package/dist/components/popover/index.d.ts +1 -5
  80. package/dist/components/popover/index.js +42 -37
  81. package/dist/components/popover/index.js.map +1 -1
  82. package/dist/components/radio/index.d.ts +0 -3
  83. package/dist/components/radio/index.js +8 -5
  84. package/dist/components/radio/index.js.map +1 -1
  85. package/dist/components/radio-group/index.js +2 -2
  86. package/dist/components/radio-group/index.js.map +1 -1
  87. package/dist/components/segmented/index.js +8 -4
  88. package/dist/components/segmented/index.js.map +1 -1
  89. package/dist/components/segmented-trigger/index.d.ts +1 -0
  90. package/dist/components/segmented-trigger/index.js +12 -3
  91. package/dist/components/segmented-trigger/index.js.map +1 -1
  92. package/dist/components/segmented-trigger/style.js +1 -1
  93. package/dist/components/segmented-trigger/style.js.map +1 -1
  94. package/dist/components/select/index.d.ts +0 -1
  95. package/dist/components/select/index.js +22 -31
  96. package/dist/components/select/index.js.map +1 -1
  97. package/dist/components/select/style.js +1 -1
  98. package/dist/components/select/style.js.map +1 -1
  99. package/dist/components/slider/index.d.ts +0 -2
  100. package/dist/components/slider/index.js +1 -1
  101. package/dist/components/slider/index.js.map +1 -1
  102. package/dist/components/spinner/index.d.ts +0 -3
  103. package/dist/components/spinner/index.js.map +1 -1
  104. package/dist/components/svg-draw-lines/index.d.ts +0 -3
  105. package/dist/components/svg-draw-lines/index.js.map +1 -1
  106. package/dist/components/switch/index.d.ts +0 -1
  107. package/dist/components/switch/index.js +6 -6
  108. package/dist/components/switch/index.js.map +1 -1
  109. package/dist/components/switch/style.js +1 -1
  110. package/dist/components/switch/style.js.map +1 -1
  111. package/dist/components/textarea/index.d.ts +1 -0
  112. package/dist/components/textarea/index.js +51 -34
  113. package/dist/components/textarea/index.js.map +1 -1
  114. package/dist/components/textarea/style.js +1 -1
  115. package/dist/components/textarea/style.js.map +1 -1
  116. package/dist/components/theme/index.d.ts +0 -3
  117. package/dist/components/theme/index.js.map +1 -1
  118. package/dist/components/toast/manager.js.map +1 -1
  119. package/dist/components/toast/style.js +1 -1
  120. package/dist/components/toast/style.js.map +1 -1
  121. package/dist/components/toast/toast.d.ts +0 -4
  122. package/dist/components/toast/toast.js +25 -17
  123. package/dist/components/toast/toast.js.map +1 -1
  124. package/dist/components/toast/types.d.ts +0 -3
  125. package/dist/components/tooltip/index.d.ts +1 -0
  126. package/dist/components/tooltip/index.js +34 -33
  127. package/dist/components/tooltip/index.js.map +1 -1
  128. package/dist/shared/events/user-change.js +17 -0
  129. package/dist/shared/events/user-change.js.map +1 -0
  130. package/dist/shared/menu-portal/menu-portal.js +10 -11
  131. package/dist/shared/menu-portal/menu-portal.js.map +1 -1
  132. package/dist/shared/normalize/index.js.map +1 -1
  133. package/dist/shared/overlay/anchored-panel.js +12 -12
  134. package/dist/shared/overlay/native-dialog-presence.js +8 -3
  135. package/dist/shared/overlay/native-dialog-presence.js.map +1 -1
  136. package/dist/shared/overlay/overlay.js +55 -33
  137. package/dist/shared/overlay/overlay.js.map +1 -1
  138. package/dist/shared/overlay/portal.js +27 -13
  139. package/dist/shared/overlay/portal.js.map +1 -1
  140. package/dist/shared/overlay/presence.js.map +1 -1
  141. package/dist/shared/scroll-lock/scroll-lock.js +13 -12
  142. package/dist/shared/scroll-lock/scroll-lock.js.map +1 -1
  143. package/dist/shared/theme/overlay-root.js +3 -3
  144. package/dist/types/react.d.ts +15 -41
  145. package/dist/types/utils.d.ts +33 -4
  146. package/dist/types/vue.d.ts +17 -49
  147. package/package.json +11 -6
@@ -1,17 +1,31 @@
1
1
  import { getFallbackOverlayRoot as e } from "../theme/overlay-root.js";
2
- import { findNearestTheme as t } from "../theme/theme-scope.js";
2
+ import { findNearestTheme as t, findRootTheme as n } from "../theme/theme-scope.js";
3
+ import { definePlugin as r } from "@greypan/js-kit";
3
4
  //#region src/shared/overlay/portal.ts
4
- function n(e) {
5
- let t = document.createElement("div"), n = t.attachShadow({ mode: "open" }), i = document.createElement("style");
6
- i.textContent = e.style;
7
- let a = document.createElement("div");
8
- a.className = e.className, a.dataset.wuiPresence = "entering", n.append(i, a), r(e.container, e.target).appendChild(t);
9
- let o = [];
5
+ var i = () => r((e) => {
6
+ let t = document.createElement("div");
7
+ t.style.display = "contents";
8
+ let n = t.attachShadow({ mode: "open" }), r = document.createElement("style");
9
+ r.textContent = e.style;
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) => {
13
+ e.forEach((e) => {
14
+ let t = o.indexOf(e);
15
+ t >= 0 && o.splice(t, 1);
16
+ });
17
+ };
10
18
  return {
11
- panel: a,
12
- moveContent(e, t = a) {
19
+ panel: i,
20
+ moveContent(e, t = i) {
13
21
  o.splice(0, o.length, ...e), t.append(...o);
14
22
  },
23
+ appendContent(e, t = i) {
24
+ s(e), o.push(...e), t.append(...e);
25
+ },
26
+ removeContent(e) {
27
+ s(e);
28
+ },
15
29
  restoreContent() {
16
30
  e.target.append(...o), o.length = 0;
17
31
  },
@@ -19,11 +33,11 @@ function n(e) {
19
33
  t.remove();
20
34
  }
21
35
  };
22
- }
23
- function r(n, r) {
24
- return (typeof n == "function" ? n() : n) ?? t(r)?.getOverlayRoot() ?? e();
36
+ });
37
+ function a(r, i, a = {}) {
38
+ return (typeof r == "function" ? r() : r) || ((a.preferRootTheme ? n() : t(i))?.getOverlayRoot() ?? e());
25
39
  }
26
40
  //#endregion
27
- export { n as createOverlayPortal, r as resolveOverlayContainer };
41
+ export { i as defineOverlayPortal, a as resolveOverlayContainer };
28
42
 
29
43
  //# sourceMappingURL=portal.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"portal.js","names":[],"sources":["../../../src/shared/overlay/portal.ts"],"sourcesContent":["import { getFallbackOverlayRoot } from '@/shared/theme/overlay-root'\nimport { findNearestTheme } from '@/shared/theme/theme-scope'\n\nexport type OverlayContainer = HTMLElement | (() => HTMLElement | undefined)\n\ninterface 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 remove(): void\n}\n\nexport function createOverlayPortal(options: OverlayPortalOptions): OverlayPortal {\n const host = document.createElement('div')\n const root = host.attachShadow({ mode: 'open' })\n const style = document.createElement('style')\n style.textContent = options.style\n const panel = document.createElement('div')\n panel.className = options.className\n panel.dataset.wuiPresence = 'entering'\n root.append(style, panel)\n resolveOverlayContainer(options.container, options.target).appendChild(host)\n\n const nodes: Node[] = []\n return {\n panel,\n moveContent(content, target = panel) {\n nodes.splice(0, nodes.length, ...content)\n target.append(...nodes)\n },\n restoreContent() {\n options.target.append(...nodes)\n nodes.length = 0\n },\n remove() {\n host.remove()\n }\n }\n}\n\nexport function resolveOverlayContainer(container: OverlayContainer | undefined, target: Element): HTMLElement {\n const explicit = typeof container === 'function' ? container() : container\n return explicit ?? findNearestTheme(target)?.getOverlayRoot() ?? getFallbackOverlayRoot()\n}\n"],"mappings":";;;AAmBA,SAAgB,EAAoB,GAA8C;CAChF,IAAM,IAAO,SAAS,cAAc,KAAK,GACnC,IAAO,EAAK,aAAa,EAAE,MAAM,OAAO,CAAC,GACzC,IAAQ,SAAS,cAAc,OAAO;CAC5C,EAAM,cAAc,EAAQ;CAC5B,IAAM,IAAQ,SAAS,cAAc,KAAK;CAI1C,AAHA,EAAM,YAAY,EAAQ,WAC1B,EAAM,QAAQ,cAAc,YAC5B,EAAK,OAAO,GAAO,CAAK,GACxB,EAAwB,EAAQ,WAAW,EAAQ,MAAM,CAAC,CAAC,YAAY,CAAI;CAE3E,IAAM,IAAgB,CAAC;CACvB,OAAO;EACL;EACA,YAAY,GAAS,IAAS,GAAO;GAEnC,AADA,EAAM,OAAO,GAAG,EAAM,QAAQ,GAAG,CAAO,GACxC,EAAO,OAAO,GAAG,CAAK;EACxB;EACA,iBAAiB;GAEf,AADA,EAAQ,OAAO,OAAO,GAAG,CAAK,GAC9B,EAAM,SAAS;EACjB;EACA,SAAS;GACP,EAAK,OAAO;EACd;CACF;AACF;AAEA,SAAgB,EAAwB,GAAyC,GAA8B;CAE7G,QADiB,OAAO,KAAc,aAAa,EAAU,IAAI,MAC9C,EAAiB,CAAM,CAAC,EAAE,eAAe,KAAK,EAAuB;AAC1F"}
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 +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 // 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,26 +1,27 @@
1
+ import { definePlugin as e } from "@greypan/js-kit";
1
2
  //#region src/shared/scroll-lock/scroll-lock.ts
2
- var e = 0, t = "", n = 0;
3
- function r() {
4
- e === 0 && (n = window.scrollY, t = document.documentElement.style.overflow, document.documentElement.style.overflow = "hidden", document.body.style.position = "fixed", document.body.style.top = `-${n}px`, document.body.style.width = "100%"), e++;
5
- }
3
+ var t = 0, n = "", r = 0;
6
4
  function i() {
7
- e = Math.max(0, e - 1), e === 0 && (document.documentElement.style.overflow = t, document.body.style.position = "", document.body.style.top = "", document.body.style.width = "", window.scrollTo(0, n));
5
+ t === 0 && (r = window.scrollY, n = document.documentElement.style.overflow, document.documentElement.style.overflow = "hidden", document.body.style.position = "fixed", document.body.style.top = `-${r}px`, document.body.style.width = "100%"), t++;
8
6
  }
9
7
  function a() {
10
- let e = !1;
8
+ t = Math.max(0, t - 1), t === 0 && (document.documentElement.style.overflow = n, document.body.style.position = "", document.body.style.top = "", document.body.style.width = "", window.scrollTo(0, r));
9
+ }
10
+ var o = () => e(() => {
11
+ let e = !1, t = (t) => {
12
+ t !== e && (t ? i() : a(), e = t);
13
+ };
11
14
  return {
12
15
  get isLocked() {
13
16
  return e;
14
17
  },
15
- sync(t) {
16
- t !== e && (t ? r() : i(), e = t);
17
- },
18
+ sync: t,
18
19
  release() {
19
- this.sync(!1);
20
+ t(!1);
20
21
  }
21
22
  };
22
- }
23
+ });
23
24
  //#endregion
24
- export { a as createScrollLockLease, r as lockScroll, i as unlockScroll };
25
+ export { o as defineScrollLockLease, i as lockScroll, a as unlockScroll };
25
26
 
26
27
  //# sourceMappingURL=scroll-lock.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"scroll-lock.js","names":[],"sources":["../../../src/shared/scroll-lock/scroll-lock.ts"],"sourcesContent":["let lockCount = 0\nlet savedOverflow = ''\nlet savedScrollY = 0\n\nexport interface ScrollLockLease {\n readonly isLocked: boolean\n sync(shouldLock: boolean): void\n release(): void\n}\n\n/**\n * 锁定页面滚动。支持嵌套调用(引用计数),仅在最后一个锁释放时恢复。\n */\nexport function lockScroll() {\n if (lockCount === 0) {\n savedScrollY = window.scrollY\n savedOverflow = document.documentElement.style.overflow\n document.documentElement.style.overflow = 'hidden'\n // 固定 body 防止 iOS 弹性滚动\n document.body.style.position = 'fixed'\n document.body.style.top = `-${savedScrollY}px`\n document.body.style.width = '100%'\n }\n lockCount++\n}\n\n/**\n * 解锁页面滚动。与 lockScroll 配对,引用归零时恢复。\n */\nexport function unlockScroll() {\n lockCount = Math.max(0, lockCount - 1)\n if (lockCount === 0) {\n document.documentElement.style.overflow = savedOverflow\n document.body.style.position = ''\n document.body.style.top = ''\n document.body.style.width = ''\n window.scrollTo(0, savedScrollY)\n }\n}\n\n/**\n * 组件实例持有的滚动锁租约。它只会释放自身已获取的那一次锁,\n * 避免卸载、属性切换和嵌套浮层互相影响。\n */\nexport function createScrollLockLease(): ScrollLockLease {\n let isLocked = false\n\n return {\n get isLocked() {\n return isLocked\n },\n\n sync(shouldLock) {\n if (shouldLock === isLocked) return\n if (shouldLock) lockScroll()\n else unlockScroll()\n isLocked = shouldLock\n },\n\n release() {\n this.sync(false)\n }\n }\n}\n"],"mappings":";AAAA,IAAI,IAAY,GACZ,IAAgB,IAChB,IAAe;AAWnB,SAAgB,IAAa;CAU3B,AATI,MAAc,MAChB,IAAe,OAAO,SACtB,IAAgB,SAAS,gBAAgB,MAAM,UAC/C,SAAS,gBAAgB,MAAM,WAAW,UAE1C,SAAS,KAAK,MAAM,WAAW,SAC/B,SAAS,KAAK,MAAM,MAAM,IAAI,EAAa,KAC3C,SAAS,KAAK,MAAM,QAAQ,SAE9B;AACF;AAKA,SAAgB,IAAe;CAE7B,AADA,IAAY,KAAK,IAAI,GAAG,IAAY,CAAC,GACjC,MAAc,MAChB,SAAS,gBAAgB,MAAM,WAAW,GAC1C,SAAS,KAAK,MAAM,WAAW,IAC/B,SAAS,KAAK,MAAM,MAAM,IAC1B,SAAS,KAAK,MAAM,QAAQ,IAC5B,OAAO,SAAS,GAAG,CAAY;AAEnC;AAMA,SAAgB,IAAyC;CACvD,IAAI,IAAW;CAEf,OAAO;EACL,IAAI,WAAW;GACb,OAAO;EACT;EAEA,KAAK,GAAY;GACX,MAAe,MACf,IAAY,EAAW,IACtB,EAAa,GAClB,IAAW;EACb;EAEA,UAAU;GACR,KAAK,KAAK,EAAK;EACjB;CACF;AACF"}
1
+ {"version":3,"file":"scroll-lock.js","names":[],"sources":["../../../src/shared/scroll-lock/scroll-lock.ts"],"sourcesContent":["import { definePlugin } from '@greypan/js-kit'\n\nlet lockCount = 0\nlet savedOverflow = ''\nlet savedScrollY = 0\n\nexport interface ScrollLockLease {\n readonly isLocked: boolean\n sync(shouldLock: boolean): void\n release(): void\n}\n\n/**\n * 锁定页面滚动。支持嵌套调用(引用计数),仅在最后一个锁释放时恢复。\n */\nexport function lockScroll() {\n if (lockCount === 0) {\n savedScrollY = window.scrollY\n savedOverflow = document.documentElement.style.overflow\n document.documentElement.style.overflow = 'hidden'\n // 固定 body 防止 iOS 弹性滚动\n document.body.style.position = 'fixed'\n document.body.style.top = `-${savedScrollY}px`\n document.body.style.width = '100%'\n }\n lockCount++\n}\n\n/**\n * 解锁页面滚动。与 lockScroll 配对,引用归零时恢复。\n */\nexport function unlockScroll() {\n lockCount = Math.max(0, lockCount - 1)\n if (lockCount === 0) {\n document.documentElement.style.overflow = savedOverflow\n document.body.style.position = ''\n document.body.style.top = ''\n document.body.style.width = ''\n window.scrollTo(0, savedScrollY)\n }\n}\n\n/**\n * 构建组件实例持有的滚动锁租约。每个租约仅释放自身获得的那一次锁,\n * 因此卸载、属性切换和嵌套浮层不会互相影响。\n */\nexport const defineScrollLockLease = () =>\n definePlugin<ScrollLockLease, Record<never, never>>(() => {\n let isLocked = false\n\n const sync = (shouldLock: boolean) => {\n if (shouldLock === isLocked) return\n if (shouldLock) lockScroll()\n else unlockScroll()\n isLocked = shouldLock\n }\n\n return {\n get isLocked() {\n return isLocked\n },\n\n sync,\n\n release() {\n sync(false)\n }\n }\n })\n"],"mappings":";;AAEA,IAAI,IAAY,GACZ,IAAgB,IAChB,IAAe;AAWnB,SAAgB,IAAa;CAU3B,AATI,MAAc,MAChB,IAAe,OAAO,SACtB,IAAgB,SAAS,gBAAgB,MAAM,UAC/C,SAAS,gBAAgB,MAAM,WAAW,UAE1C,SAAS,KAAK,MAAM,WAAW,SAC/B,SAAS,KAAK,MAAM,MAAM,IAAI,EAAa,KAC3C,SAAS,KAAK,MAAM,QAAQ,SAE9B;AACF;AAKA,SAAgB,IAAe;CAE7B,AADA,IAAY,KAAK,IAAI,GAAG,IAAY,CAAC,GACjC,MAAc,MAChB,SAAS,gBAAgB,MAAM,WAAW,GAC1C,SAAS,KAAK,MAAM,WAAW,IAC/B,SAAS,KAAK,MAAM,MAAM,IAC1B,SAAS,KAAK,MAAM,QAAQ,IAC5B,OAAO,SAAS,GAAG,CAAY;AAEnC;AAMA,IAAa,UACX,QAA0D;CACxD,IAAI,IAAW,IAET,KAAQ,MAAwB;EAChC,MAAe,MACf,IAAY,EAAW,IACtB,EAAa,GAClB,IAAW;CACb;CAEA,OAAO;EACL,IAAI,WAAW;GACb,OAAO;EACT;EAEA;EAEA,UAAU;GACR,EAAK,EAAK;EACZ;CACF;AACF,CAAC"}
@@ -1,9 +1,9 @@
1
1
  import e from "../../assets/glass.js";
2
- import t from "../../assets/menu-portal.js";
3
- import n from "../../assets/overlay-motion.js";
2
+ import t from "../../assets/overlay-motion.js";
3
+ import n from "../../assets/menu-portal.js";
4
4
  import r from "../../assets/toast-containers.js";
5
5
  //#region src/shared/theme/overlay-root.ts
6
- var i = "data-wui-overlay-root", a = `${e}\n${r}\n${t}\n${n}`;
6
+ var i = "data-wui-overlay-root", a = `${e}\n${r}\n${n}\n${t}`;
7
7
  function o(e) {
8
8
  let t = document.createElement("style");
9
9
  t.textContent = a, e.prepend(t);
@@ -1,45 +1,19 @@
1
1
  import { DetailedHTMLProps, HTMLAttributes } from 'react';
2
- import { WebUiAvatar, WebUiBackTop, WebUiBadge, WebUiButton, WebUiButtonGroup, WebUiCheckbox, WebUiCheckboxGroup, WebUiContextMenu, WebUiDialog, WebUiDrawer, WebUiEmpty, WebUiDropdownDivider, WebUiDropdownHeader, WebUiDropdownItem, WebUiDropdown, WebUiIcon, WebUiInput, WebUiInputNumber, WebUiLayout, WebUiOption, WebUiPopover, WebUiRadio, WebUiRadioGroup, WebUiSegmented, WebUiSegmentedTrigger, WebUiSelect, WebUiSlider, WebUiSpinner, WebUiTextarea, WebUiTheme, WebUiToast, WebUiSwitch, WebUiSvgDrawLines, WebUiTooltip } from '../components';
3
- import { ExactEventListeners, ExtractProps, OmitLitBase } from './utils';
4
- export type LitReactWrapper<T extends HTMLElement> = T extends {
5
- readonly $events: infer E;
6
- } ? DetailedHTMLProps<ExtractProps<OmitLitBase<T>> & ExactEventListeners<E> & HTMLAttributes<T>, T> : DetailedHTMLProps<ExtractProps<OmitLitBase<T>> & HTMLAttributes<T>, T>;
7
- export interface WebUiComponents {
8
- 'web-ui-avatar': LitReactWrapper<WebUiAvatar>;
9
- 'web-ui-back-top': LitReactWrapper<WebUiBackTop>;
10
- 'web-ui-badge': LitReactWrapper<WebUiBadge>;
11
- 'web-ui-button': LitReactWrapper<WebUiButton>;
12
- 'web-ui-button-group': LitReactWrapper<WebUiButtonGroup>;
13
- 'web-ui-checkbox': LitReactWrapper<WebUiCheckbox>;
14
- 'web-ui-checkbox-group': LitReactWrapper<WebUiCheckboxGroup>;
15
- 'web-ui-context-menu': LitReactWrapper<WebUiContextMenu>;
16
- 'web-ui-dialog': LitReactWrapper<WebUiDialog>;
17
- 'web-ui-drawer': LitReactWrapper<WebUiDrawer>;
18
- 'web-ui-empty': LitReactWrapper<WebUiEmpty>;
19
- 'web-ui-dropdown-divider': LitReactWrapper<WebUiDropdownDivider>;
20
- 'web-ui-dropdown-header': LitReactWrapper<WebUiDropdownHeader>;
21
- 'web-ui-dropdown-item': LitReactWrapper<WebUiDropdownItem>;
22
- 'web-ui-dropdown': LitReactWrapper<WebUiDropdown>;
23
- 'web-ui-icon': LitReactWrapper<WebUiIcon>;
24
- 'web-ui-input': LitReactWrapper<WebUiInput>;
25
- 'web-ui-input-number': LitReactWrapper<WebUiInputNumber>;
26
- 'web-ui-select': LitReactWrapper<WebUiSelect>;
27
- 'web-ui-slider': LitReactWrapper<WebUiSlider>;
28
- 'web-ui-spinner': LitReactWrapper<WebUiSpinner>;
29
- 'web-ui-svg-draw-lines': LitReactWrapper<WebUiSvgDrawLines>;
30
- 'web-ui-option': LitReactWrapper<WebUiOption>;
31
- 'web-ui-popover': LitReactWrapper<WebUiPopover>;
32
- 'web-ui-radio': LitReactWrapper<WebUiRadio>;
33
- 'web-ui-radio-group': LitReactWrapper<WebUiRadioGroup>;
34
- 'web-ui-segmented': LitReactWrapper<WebUiSegmented>;
35
- 'web-ui-segmented-trigger': LitReactWrapper<WebUiSegmentedTrigger>;
36
- 'web-ui-layout': LitReactWrapper<WebUiLayout>;
37
- 'web-ui-switch': LitReactWrapper<WebUiSwitch>;
38
- 'web-ui-textarea': LitReactWrapper<WebUiTextarea>;
39
- 'web-ui-theme': LitReactWrapper<WebUiTheme>;
40
- 'web-ui-toast': LitReactWrapper<WebUiToast>;
41
- 'web-ui-tooltip': LitReactWrapper<WebUiTooltip>;
42
- }
2
+ import { WebUiElementMap } from '../components';
3
+ import { ExtractProps, OmitLitBase, ReactCustomEventListeners } from './utils';
4
+ /**
5
+ * Lit Web Component 的 React 包装类型。
6
+ *
7
+ * - 标准 DOM 事件(input/change/focus/blur)使用 React 惯用 camelCase handler,
8
+ * `currentTarget` 收窄为组件实例(`target` 遵循 React SyntheticEvent 语义,不承诺)。
9
+ * - kebab-case 自定义事件(open-change/toast-close)生成精确的 `on<event>` 绑定。
10
+ * - 与 `HTMLAttributes<T>` 全量交叉:组件自身属性与通用 HTML 属性(含原生事件)共存,
11
+ * 重叠键类型相容(如 `placeholder`),组件类型在交叉中自然生效;`ref` 保持具体元素类型。
12
+ */
13
+ export type LitReactWrapper<T extends HTMLElement> = DetailedHTMLProps<ExtractProps<OmitLitBase<T>> & ReactCustomEventListeners<T> & HTMLAttributes<T>, T>;
14
+ export type WebUiComponents = {
15
+ [K in keyof WebUiElementMap]: LitReactWrapper<WebUiElementMap[K]>;
16
+ };
43
17
  declare module 'react' {
44
18
  namespace JSX {
45
19
  interface IntrinsicElements extends WebUiComponents {
@@ -10,10 +10,39 @@ export type ExtractProps<T> = {
10
10
  };
11
11
  export type OmitLitBase<T> = Omit<T, keyof HTMLElement | keyof LitElement>;
12
12
  /**
13
- * 将 EventMap 转换为 React 19 Custom Element 的精确事件键。
14
- * 例如 { 'open-change': CustomEvent<...> } → { 'onopen-change'?: (e: CustomEvent<...>) => void }
13
+ * 从组件 `$events` 接口提取公共事件映射。
14
+ * 无 `$events` 的组件(无公共事件)得到空映射。
15
15
  */
16
- export type ExactEventListeners<T> = {
17
- [K in keyof T & string as `on${K}`]?: (e: T[K]) => void;
16
+ export type ComponentEvents<T> = T extends {
17
+ readonly $events: infer E;
18
+ } ? E : Record<never, never>;
19
+ /**
20
+ * 事件宿主化:在事件本体上注入组件实例 target/currentTarget 类型。
21
+ * 浏览器组合事件在宿主上重定向后,`event.target` 恒等于宿主元素,因此该收窄零例外。
22
+ * target/currentTarget 标为 readonly,与 DOM `Event` 契约一致。
23
+ */
24
+ export type WithHost<T extends EventTarget, E> = E extends Event ? E & {
25
+ readonly target: T;
26
+ readonly currentTarget: T;
27
+ } : E;
28
+ /**
29
+ * 组件宿主化事件映射:`$events` 的每个条目都收窄 target/currentTarget 到组件实例。
30
+ * React 自定义事件与 Vue emits 均从该映射生成 handler 类型。
31
+ */
32
+ export type HostEventMap<T extends HTMLElement> = {
33
+ [K in keyof ComponentEvents<T>]: WithHost<T, ComponentEvents<T>[K]>;
34
+ };
35
+ /**
36
+ * 具名 handler 的显式事件类型,用于 Vue 命名 handler 等无法从上下文推导 `$event` 的位置。
37
+ * 用法:`function onChange(e: WebUiEvent<WebUiSelect, 'change'>)`
38
+ */
39
+ export type WebUiEvent<T extends HTMLElement, K extends keyof HostEventMap<T>> = HostEventMap<T>[K];
40
+ /**
41
+ * React 自定义事件(kebab-case 事件名)→ `on<event>` 绑定。
42
+ * 标准 DOM 事件(input/change/focus/blur)由 React 的 camelCase handler 提供,
43
+ * 不在适配层生成 lowercase 别名。
44
+ */
45
+ export type ReactCustomEventListeners<T extends HTMLElement> = {
46
+ [K in keyof HostEventMap<T> & string as K extends `${string}-${string}` ? `on${K}` : never]?: (e: HostEventMap<T>[K]) => void;
18
47
  };
19
48
  export {};
@@ -1,60 +1,28 @@
1
- import { ComponentOptionsMixin, DefineComponent, HTMLAttributes } from 'vue';
2
- import { WebUiAvatar, WebUiBackTop, WebUiBadge, WebUiButton, WebUiButtonGroup, WebUiCheckbox, WebUiCheckboxGroup, WebUiContextMenu, WebUiDialog, WebUiDrawer, WebUiEmpty, WebUiDropdownDivider, WebUiDropdownHeader, WebUiDropdownItem, WebUiDropdown, WebUiIcon, WebUiInput, WebUiInputNumber, WebUiLayout, WebUiOption, WebUiPopover, WebUiRadio, WebUiRadioGroup, WebUiSegmented, WebUiSegmentedTrigger, WebUiSelect, WebUiSlider, WebUiSpinner, WebUiTextarea, WebUiTheme, WebUiToast, WebUiSwitch, WebUiTooltip, WebUiSvgDrawLines } from '../components';
3
- import { ExtractProps, OmitLitBase } from './utils';
4
- type ExtractVueEmits<T> = T extends {
1
+ import { ComponentOptionsMixin, DefineComponent, EmitsToProps, HTMLAttributes, PublicProps } from 'vue';
2
+ import { WebUiElementMap } from '../components';
3
+ import { ComponentEvents, ExtractProps, OmitLitBase, WithHost } from './utils';
4
+ type VueEmits<T extends HTMLElement> = T extends {
5
5
  readonly $events: infer E;
6
6
  } ? {
7
- [K in keyof E]: (e: E[K]) => void;
8
- } : Record<string, never>;
7
+ [K in keyof E]: (e: WithHost<T, E[K]>) => void;
8
+ } : Record<never, never>;
9
+ type VueNativeAttrs<T extends HTMLElement> = Omit<HTMLAttributes, `on${Capitalize<keyof ComponentEvents<T> & string>}`>;
10
+ type VueProps<T extends HTMLElement> = ExtractProps<OmitLitBase<T>> & VueNativeAttrs<T>;
11
+ type VueResolvedProps<T extends HTMLElement> = Readonly<VueProps<T>> & (VueEmits<T> extends Record<string, never> ? unknown : EmitsToProps<VueEmits<T>>);
9
12
  /**
10
13
  * Lit Web Component 的 Vue 包装类型。
11
14
  *
12
- * - Props: 从 LitElement 推导的组件属性 + Vue HTMLAttributes(含原生 DOM 事件)
13
- * - Emits: 从 $events 接口提取的自定义事件,Volar 可识别 @event 绑定
15
+ * - Props: 组件属性 + 局部 HTMLAttributes(含原生 DOM 事件),不污染其他 Vue 组件
16
+ * - Emits: 从 $events 提取的宿主化事件,Volar 可识别 @event 绑定
17
+ * - TypeEl: 模板 ref/$el 类型为具体 Custom Element 实例
18
+ * - 不再全局扩展 ComponentCustomProps
14
19
  */
15
- export type LitVueWrapper<T> = T extends {
16
- readonly $events: infer E;
17
- } ? DefineComponent<ExtractProps<OmitLitBase<T>>, Record<string, never>, Record<string, never>, Record<string, never>, Record<string, never>, ComponentOptionsMixin, ComponentOptionsMixin, ExtractVueEmits<T>> : DefineComponent<ExtractProps<OmitLitBase<T>>>;
18
- export interface WebUiComponents {
19
- 'web-ui-avatar': LitVueWrapper<WebUiAvatar>;
20
- 'web-ui-back-top': LitVueWrapper<WebUiBackTop>;
21
- 'web-ui-badge': LitVueWrapper<WebUiBadge>;
22
- 'web-ui-button': LitVueWrapper<WebUiButton>;
23
- 'web-ui-button-group': LitVueWrapper<WebUiButtonGroup>;
24
- 'web-ui-checkbox': LitVueWrapper<WebUiCheckbox>;
25
- 'web-ui-checkbox-group': LitVueWrapper<WebUiCheckboxGroup>;
26
- 'web-ui-context-menu': LitVueWrapper<WebUiContextMenu>;
27
- 'web-ui-dialog': LitVueWrapper<WebUiDialog>;
28
- 'web-ui-drawer': LitVueWrapper<WebUiDrawer>;
29
- 'web-ui-empty': LitVueWrapper<WebUiEmpty>;
30
- 'web-ui-dropdown-divider': LitVueWrapper<WebUiDropdownDivider>;
31
- 'web-ui-dropdown-header': LitVueWrapper<WebUiDropdownHeader>;
32
- 'web-ui-dropdown-item': LitVueWrapper<WebUiDropdownItem>;
33
- 'web-ui-dropdown': LitVueWrapper<WebUiDropdown>;
34
- 'web-ui-icon': LitVueWrapper<WebUiIcon>;
35
- 'web-ui-input': LitVueWrapper<WebUiInput>;
36
- 'web-ui-input-number': LitVueWrapper<WebUiInputNumber>;
37
- 'web-ui-select': LitVueWrapper<WebUiSelect>;
38
- 'web-ui-slider': LitVueWrapper<WebUiSlider>;
39
- 'web-ui-spinner': LitVueWrapper<WebUiSpinner>;
40
- 'web-ui-svg-draw-lines': LitVueWrapper<WebUiSvgDrawLines>;
41
- 'web-ui-option': LitVueWrapper<WebUiOption>;
42
- 'web-ui-popover': LitVueWrapper<WebUiPopover>;
43
- 'web-ui-radio': LitVueWrapper<WebUiRadio>;
44
- 'web-ui-radio-group': LitVueWrapper<WebUiRadioGroup>;
45
- 'web-ui-segmented': LitVueWrapper<WebUiSegmented>;
46
- 'web-ui-segmented-trigger': LitVueWrapper<WebUiSegmentedTrigger>;
47
- 'web-ui-layout': LitVueWrapper<WebUiLayout>;
48
- 'web-ui-switch': LitVueWrapper<WebUiSwitch>;
49
- 'web-ui-textarea': LitVueWrapper<WebUiTextarea>;
50
- 'web-ui-theme': LitVueWrapper<WebUiTheme>;
51
- 'web-ui-toast': LitVueWrapper<WebUiToast>;
52
- 'web-ui-tooltip': LitVueWrapper<WebUiTooltip>;
53
- }
20
+ export type LitVueWrapper<T extends HTMLElement> = DefineComponent<VueProps<T>, Record<string, never>, Record<string, never>, Record<string, never>, Record<string, never>, ComponentOptionsMixin, ComponentOptionsMixin, VueEmits<T>, string, PublicProps, VueResolvedProps<T>, Record<never, never>, Record<never, never>, Record<never, never>, Record<never, never>, string, Record<never, never>, true, Record<never, never>, T>;
21
+ export type WebUiComponents = {
22
+ [K in keyof WebUiElementMap]: LitVueWrapper<WebUiElementMap[K]>;
23
+ };
54
24
  declare module 'vue' {
55
25
  interface GlobalComponents extends WebUiComponents {
56
26
  }
57
- interface ComponentCustomProps extends HTMLAttributes {
58
- }
59
27
  }
60
28
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@greypan/web-ui",
3
- "version": "2.1.7",
3
+ "version": "3.0.0",
4
4
  "description": "Lit-based web components for React, Vue, and vanilla JS",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -41,8 +41,8 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "@floating-ui/dom": "^1.8.0",
44
- "@greypan/browser-kit": "1.7.6",
45
- "@greypan/js-kit": "1.6.5"
44
+ "@greypan/js-kit": "1.6.6",
45
+ "@greypan/browser-kit": "1.7.7"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@iconify-json/jam": "^1.2.3",
@@ -59,9 +59,9 @@
59
59
  "@greypan/tsconfig": "1.0.4"
60
60
  },
61
61
  "peerDependencies": {
62
- "@types/react": ">=16",
62
+ "@types/react": ">=19",
63
63
  "lit": "^3.3.3",
64
- "vue": ">=3"
64
+ "vue": ">=3.5"
65
65
  },
66
66
  "peerDependenciesMeta": {
67
67
  "@types/react": {
@@ -72,7 +72,12 @@
72
72
  }
73
73
  },
74
74
  "browserslist": [
75
- "defaults"
75
+ "Chrome >=111",
76
+ "Edge >=111",
77
+ "Safari >=16.4",
78
+ "iOS >=16.4",
79
+ "Firefox >=128",
80
+ "not dead"
76
81
  ],
77
82
  "scripts": {
78
83
  "build": "vp build",