@icen.ai/ui 0.9.0 → 0.10.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 (281) hide show
  1. package/AGENTS.md +138 -80
  2. package/README.md +66 -91
  3. package/dist/base.css +208 -179
  4. package/dist/behaviors/accordion.mjs +8 -8
  5. package/dist/behaviors/ai-branch.d.mts +1 -1
  6. package/dist/behaviors/ai-branch.mjs +1010 -94
  7. package/dist/behaviors/ai-chat.d.mts +7 -7
  8. package/dist/behaviors/ai-chat.mjs +1071 -161
  9. package/dist/behaviors/ai-composer.d.mts +44 -12
  10. package/dist/behaviors/ai-composer.mjs +1997 -524
  11. package/dist/behaviors/ai-core.d.mts +1 -1
  12. package/dist/behaviors/ai-core.mjs +1016 -107
  13. package/dist/behaviors/ai-diff.mjs +1019 -127
  14. package/dist/behaviors/ai-feedback.d.mts +1 -1
  15. package/dist/behaviors/ai-feedback.mjs +1035 -126
  16. package/dist/behaviors/ai-panel.d.mts +8 -8
  17. package/dist/behaviors/ai-panel.mjs +1284 -266
  18. package/dist/behaviors/ai-provider.mjs +1003 -101
  19. package/dist/behaviors/ai-threads.d.mts +1 -1
  20. package/dist/behaviors/ai-threads.mjs +1068 -154
  21. package/dist/behaviors/ai-tool.d.mts +2 -2
  22. package/dist/behaviors/ai-tool.mjs +2037 -396
  23. package/dist/behaviors/ai-tools.d.mts +89 -16
  24. package/dist/behaviors/ai-tools.mjs +4448 -372
  25. package/dist/behaviors/autocomplete.d.mts +53 -0
  26. package/dist/behaviors/autocomplete.mjs +1600 -0
  27. package/dist/behaviors/back-top.d.mts +1 -1
  28. package/dist/behaviors/back-top.mjs +951 -14
  29. package/dist/behaviors/carousel.d.mts +1 -1
  30. package/dist/behaviors/carousel.mjs +906 -11
  31. package/dist/behaviors/cascader.d.mts +48 -0
  32. package/dist/behaviors/cascader.mjs +1853 -0
  33. package/dist/behaviors/charts.d.mts +72 -8
  34. package/dist/behaviors/charts.mjs +1901 -256
  35. package/dist/behaviors/code-block.d.mts +36 -0
  36. package/dist/behaviors/code-block.mjs +1611 -0
  37. package/dist/behaviors/command-palette.d.mts +9 -6
  38. package/dist/behaviors/command-palette.mjs +161 -59
  39. package/dist/behaviors/context-menu.d.mts +9 -8
  40. package/dist/behaviors/context-menu.mjs +1112 -44
  41. package/dist/behaviors/controls.d.mts +13 -13
  42. package/dist/behaviors/controls.mjs +989 -130
  43. package/dist/behaviors/copy.d.mts +21 -2
  44. package/dist/behaviors/copy.mjs +1206 -42
  45. package/dist/behaviors/datatable.d.mts +53 -3
  46. package/dist/behaviors/datatable.mjs +1963 -207
  47. package/dist/behaviors/date-picker.d.mts +0 -1
  48. package/dist/behaviors/date-picker.mjs +1340 -293
  49. package/dist/behaviors/design.d.mts +54 -0
  50. package/dist/behaviors/design.mjs +109 -0
  51. package/dist/behaviors/dropdown.d.mts +5 -6
  52. package/dist/behaviors/dropdown.mjs +262 -110
  53. package/dist/behaviors/events.d.mts +120 -3
  54. package/dist/behaviors/events.mjs +47 -2
  55. package/dist/behaviors/feedback.d.mts +30 -0
  56. package/dist/behaviors/feedback.mjs +296 -0
  57. package/dist/behaviors/focus.d.mts +8 -0
  58. package/dist/behaviors/focus.mjs +24 -0
  59. package/dist/behaviors/form-validate.d.mts +102 -0
  60. package/dist/behaviors/form-validate.mjs +6091 -0
  61. package/dist/behaviors/i18n.d.mts +789 -0
  62. package/dist/behaviors/i18n.mjs +1711 -0
  63. package/dist/behaviors/icons.d.mts +93 -0
  64. package/dist/behaviors/icons.mjs +113 -0
  65. package/dist/behaviors/input.d.mts +9 -1
  66. package/dist/behaviors/input.mjs +1056 -60
  67. package/dist/behaviors/kb-access.d.mts +1 -1
  68. package/dist/behaviors/kb-access.mjs +2129 -239
  69. package/dist/behaviors/kb-acl.d.mts +6 -6
  70. package/dist/behaviors/kb-acl.mjs +1119 -179
  71. package/dist/behaviors/kb-answer.mjs +2070 -418
  72. package/dist/behaviors/kb-audit.d.mts +1 -1
  73. package/dist/behaviors/kb-audit.mjs +1076 -154
  74. package/dist/behaviors/kb-canvas.mjs +1100 -177
  75. package/dist/behaviors/kb-chain.mjs +1022 -100
  76. package/dist/behaviors/kb-checkpoint.d.mts +1 -1
  77. package/dist/behaviors/kb-checkpoint.mjs +1319 -169
  78. package/dist/behaviors/kb-chunks.mjs +1152 -213
  79. package/dist/behaviors/kb-citation.d.mts +1 -1
  80. package/dist/behaviors/kb-citation.mjs +1201 -179
  81. package/dist/behaviors/kb-clarify.mjs +1058 -130
  82. package/dist/behaviors/kb-conflict.mjs +1041 -119
  83. package/dist/behaviors/kb-connector.mjs +1104 -176
  84. package/dist/behaviors/kb-core.d.mts +6 -1
  85. package/dist/behaviors/kb-core.mjs +1077 -162
  86. package/dist/behaviors/kb-eval.mjs +1971 -144
  87. package/dist/behaviors/kb-explain.d.mts +1 -1
  88. package/dist/behaviors/kb-explain.mjs +1024 -102
  89. package/dist/behaviors/kb-filter.mjs +2008 -178
  90. package/dist/behaviors/kb-gap.d.mts +1 -1
  91. package/dist/behaviors/kb-gap.mjs +1043 -121
  92. package/dist/behaviors/kb-hittest.d.mts +1 -1
  93. package/dist/behaviors/kb-hittest.mjs +1093 -150
  94. package/dist/behaviors/kb-metadata.mjs +1052 -130
  95. package/dist/behaviors/kb-passage.d.mts +2 -2
  96. package/dist/behaviors/kb-passage.mjs +1101 -161
  97. package/dist/behaviors/kb-pipeline.d.mts +2 -2
  98. package/dist/behaviors/kb-pipeline.mjs +1082 -162
  99. package/dist/behaviors/kb-qa.mjs +1088 -160
  100. package/dist/behaviors/kb-rerank.mjs +1047 -114
  101. package/dist/behaviors/kb-retrieval.mjs +2063 -224
  102. package/dist/behaviors/kb-review.mjs +1990 -159
  103. package/dist/behaviors/kb-sandbox.d.mts +1 -1
  104. package/dist/behaviors/kb-sandbox.mjs +1026 -104
  105. package/dist/behaviors/kb-segment.mjs +1111 -179
  106. package/dist/behaviors/kb-select.d.mts +24 -0
  107. package/dist/behaviors/kb-select.mjs +1989 -0
  108. package/dist/behaviors/kb-sources.d.mts +1 -1
  109. package/dist/behaviors/kb-sources.mjs +1084 -170
  110. package/dist/behaviors/kb-sql.d.mts +1 -1
  111. package/dist/behaviors/kb-sql.mjs +1143 -226
  112. package/dist/behaviors/kb-trace.d.mts +1 -1
  113. package/dist/behaviors/kb-trace.mjs +1042 -128
  114. package/dist/behaviors/kb-visibility.mjs +1086 -158
  115. package/dist/behaviors/kb-who-can.mjs +1974 -137
  116. package/dist/behaviors/mentions.d.mts +93 -0
  117. package/dist/behaviors/mentions.mjs +2152 -0
  118. package/dist/behaviors/modal.d.mts +4 -3
  119. package/dist/behaviors/modal.mjs +1055 -33
  120. package/dist/behaviors/nav.d.mts +1 -1
  121. package/dist/behaviors/nav.mjs +6 -6
  122. package/dist/behaviors/notification.d.mts +16 -8
  123. package/dist/behaviors/notification.mjs +1615 -265
  124. package/dist/behaviors/panel.d.mts +4 -0
  125. package/dist/behaviors/panel.mjs +994 -0
  126. package/dist/behaviors/popover.d.mts +28 -5
  127. package/dist/behaviors/popover.mjs +225 -24
  128. package/dist/behaviors/select.d.mts +5 -2
  129. package/dist/behaviors/select.mjs +1410 -117
  130. package/dist/behaviors/sidebar.d.mts +1 -1
  131. package/dist/behaviors/sidebar.mjs +13 -13
  132. package/dist/behaviors/slider.d.mts +1 -1
  133. package/dist/behaviors/slider.mjs +9 -9
  134. package/dist/behaviors/split-pane.d.mts +1 -1
  135. package/dist/behaviors/split-pane.mjs +7 -7
  136. package/dist/behaviors/suggestions.d.mts +27 -0
  137. package/dist/behaviors/suggestions.mjs +1089 -0
  138. package/dist/behaviors/svg-parse.d.mts +14 -0
  139. package/dist/behaviors/svg-parse.mjs +37 -0
  140. package/dist/behaviors/tabs.d.mts +2 -2
  141. package/dist/behaviors/tabs.mjs +25 -25
  142. package/dist/behaviors/tag-input.d.mts +23 -5
  143. package/dist/behaviors/tag-input.mjs +1359 -104
  144. package/dist/behaviors/theme-switcher.d.mts +24 -0
  145. package/dist/behaviors/theme-switcher.mjs +1981 -0
  146. package/dist/behaviors/theme.d.mts +15 -8
  147. package/dist/behaviors/theme.mjs +390 -28
  148. package/dist/behaviors/toast.d.mts +24 -25
  149. package/dist/behaviors/toast.mjs +1266 -88
  150. package/dist/behaviors/tooltip.d.mts +4 -0
  151. package/dist/behaviors/tooltip.mjs +378 -0
  152. package/dist/behaviors/tree-select.d.mts +69 -0
  153. package/dist/behaviors/tree-select.mjs +1907 -0
  154. package/dist/behaviors/tree.d.mts +1 -1
  155. package/dist/behaviors/tree.mjs +15 -15
  156. package/dist/behaviors/upload.d.mts +3 -2
  157. package/dist/behaviors/upload.mjs +1076 -66
  158. package/dist/cli.mjs +5 -6
  159. package/dist/components/ai-chat.css +666 -573
  160. package/dist/components/ai-diff.css +153 -132
  161. package/dist/components/ai-panel.css +351 -343
  162. package/dist/components/ai-tool.css +393 -238
  163. package/dist/components/autocomplete.css +61 -0
  164. package/dist/components/autocomplete.mjs +3 -0
  165. package/dist/components/back-top.css +34 -31
  166. package/dist/components/badge.css +44 -41
  167. package/dist/components/breadcrumb.css +67 -46
  168. package/dist/components/btn.css +80 -74
  169. package/dist/components/card.css +117 -106
  170. package/dist/components/cascader.css +311 -0
  171. package/dist/components/cascader.mjs +3 -0
  172. package/dist/components/chart-area.css +8 -2
  173. package/dist/components/chart-bar.css +95 -33
  174. package/dist/components/chart-gauge.css +14 -11
  175. package/dist/components/chart-graph.css +20 -17
  176. package/dist/components/chart-heatmap.css +96 -90
  177. package/dist/components/chart-line.css +8 -3
  178. package/dist/components/chart-pie.css +9 -6
  179. package/dist/components/chart-radar.css +8 -5
  180. package/dist/components/chart-scatter.css +9 -6
  181. package/dist/components/chart-sparkline.css +5 -2
  182. package/dist/components/chart-stack.css +14 -11
  183. package/dist/components/charts.css +164 -73
  184. package/dist/components/code-block.css +418 -0
  185. package/dist/components/code-block.mjs +4 -0
  186. package/dist/components/command-palette.css +138 -117
  187. package/dist/components/content.css +257 -251
  188. package/dist/components/copy.css +72 -44
  189. package/dist/components/datatable.css +423 -181
  190. package/dist/components/date-picker.css +135 -104
  191. package/dist/components/empty.css +68 -65
  192. package/dist/components/feedback.css +201 -183
  193. package/dist/components/form-validate.css +120 -0
  194. package/dist/components/form-validate.mjs +3 -0
  195. package/dist/components/form.css +150 -127
  196. package/dist/components/icon.css +102 -0
  197. package/dist/components/icon.mjs +3 -0
  198. package/dist/components/input.css +159 -104
  199. package/dist/components/kb-agent.css +188 -173
  200. package/dist/components/kb-data.css +257 -253
  201. package/dist/components/kb-ground.css +197 -185
  202. package/dist/components/kb-ingest.css +334 -350
  203. package/dist/components/kb-ops.css +265 -259
  204. package/dist/components/kb-perm.css +260 -271
  205. package/dist/components/kb-search.css +317 -360
  206. package/dist/components/kb.css +120 -109
  207. package/dist/components/layout.css +85 -82
  208. package/dist/components/material-chinese.css +73 -0
  209. package/dist/components/material-soviet.css +76 -0
  210. package/dist/components/material.css +275 -0
  211. package/dist/components/media.css +220 -166
  212. package/dist/components/mentions.css +189 -0
  213. package/dist/components/mentions.mjs +3 -0
  214. package/dist/components/menu.css +113 -105
  215. package/dist/components/modal.css +107 -112
  216. package/dist/components/nav.css +118 -115
  217. package/dist/components/notification.css +238 -174
  218. package/dist/components/pagination.css +56 -53
  219. package/dist/components/panel.css +66 -63
  220. package/dist/components/panel.mjs +1 -0
  221. package/dist/components/pill.css +81 -71
  222. package/dist/components/popover.css +67 -53
  223. package/dist/components/scroll-area.css +113 -110
  224. package/dist/components/segmented.css +146 -143
  225. package/dist/components/select.css +206 -186
  226. package/dist/components/sidebar.css +137 -127
  227. package/dist/components/slider.css +117 -114
  228. package/dist/components/split-pane.css +112 -109
  229. package/dist/components/stat.css +112 -106
  230. package/dist/components/steps.css +109 -103
  231. package/dist/components/switch.css +138 -134
  232. package/dist/components/table.css +131 -107
  233. package/dist/components/tabs.css +114 -108
  234. package/dist/components/tag-input.css +48 -133
  235. package/dist/components/tag.css +81 -71
  236. package/dist/components/theme-switcher.css +105 -0
  237. package/dist/components/theme-switcher.mjs +3 -0
  238. package/dist/components/toast.css +100 -86
  239. package/dist/components/toolbar.css +45 -42
  240. package/dist/components/tooltip.css +102 -43
  241. package/dist/components/tooltip.mjs +1 -0
  242. package/dist/components/tree-select.css +416 -0
  243. package/dist/components/tree-select.mjs +3 -0
  244. package/dist/components/upload.css +135 -92
  245. package/dist/fonts/LICENSES/ark-pixel/OFL.txt +96 -0
  246. package/dist/fonts/LICENSES/cubic-11/OFL.txt +108 -0
  247. package/dist/fonts/LICENSES/galmuri/LICENSE.txt +93 -0
  248. package/dist/fonts/OFL.txt +96 -0
  249. package/dist/fonts/README.md +14 -0
  250. package/dist/fonts/fusion-pixel-12px-zh-hans.woff2 +0 -0
  251. package/dist/index.d.mts +28 -14
  252. package/dist/index.mjs +20705 -9601
  253. package/dist/react.d.mts +91 -0
  254. package/dist/react.mjs +8208 -0
  255. package/dist/registry.json +130 -13
  256. package/dist/tokens/chinese.css +44 -0
  257. package/dist/tokens/colors.css +365 -1222
  258. package/dist/tokens/design.css +109 -0
  259. package/dist/tokens/dtcg.json +11198 -0
  260. package/dist/tokens/figma.variables.json +7017 -0
  261. package/dist/tokens/icon-masks.css +78 -0
  262. package/dist/tokens/pixel.css +50 -0
  263. package/dist/tokens/soviet.css +44 -0
  264. package/dist/tokens/style-profiles.css +36 -547
  265. package/dist/tokens/themes.json +308 -0
  266. package/dist/tokens/tokens-studio.json +6728 -0
  267. package/dist/tokens/typography.css +21 -18
  268. package/dist/tokens.css +1555 -1801
  269. package/dist/ui.css +11975 -9831
  270. package/dist/vue.d.mts +344 -0
  271. package/dist/vue.mjs +7781 -0
  272. package/docs/spec/ai-input-experience.md +75 -0
  273. package/docs/spec/ai-native.md +238 -147
  274. package/docs/spec/design-philosophy.md +189 -0
  275. package/docs/spec/design-primitives.md +196 -0
  276. package/docs/spec/kb-family.md +147 -115
  277. package/package.json +43 -8
  278. package/dist/retro-effects.css +0 -255
  279. package/dist/tokens/retro-effects.css +0 -255
  280. package/docs/research/2026-10-03-ai-kb-components.md +0 -392
  281. package/docs/research/2026-10-03-kb-permissions.md +0 -992
@@ -40,6 +40,12 @@ interface PanelSizing {
40
40
  /** 高度上限 */
41
41
  maxHeight?: number;
42
42
  }
43
+ /** Reveal an item inside one explicit scrollport. Never scrolls document ancestors;
44
+ * use this instead of scrollIntoView for portalled menus and nested listboxes. */
45
+ declare function revealPopoverItem(item: HTMLElement, scrollport: HTMLElement, opts?: {
46
+ focus?: boolean;
47
+ axis?: 'block' | 'inline' | 'both';
48
+ }): void;
43
49
  /** 从元素读取 data-panel-* 尺寸属性(非法/缺失字段跳过,不抛异常)。 */
44
50
  declare function readPanelSizing(el: Element | null | undefined): PanelSizing;
45
51
  /** 合并尺寸:程序面 overrides 优先于属性面(undefined 字段不覆盖)。 */
@@ -52,7 +58,7 @@ declare function sizingToLayout<T extends {
52
58
  }>(sizing: PanelSizing, opts: T): T;
53
59
  /** 把尺寸契约内联到自建面板(dropdown / context-menu / date-picker 等 fixed 面板)。
54
60
  * 传 null 复位全部内联尺寸——单例面板跨实例复用时防「上次的设置串味」。
55
- * 视口守卫:width / maxWidth 一律夹到 视口宽 − 24px(窄屏上过大的 data-panel-width 不溢出,
61
+ * 视口守卫:width / maxWidth 一律夹到 视口宽 − 24px(窄屏上过大的 data-icen-panel-width 不溢出,
56
62
  * 与 computePopoverLayout 的 availableWidth 同一纪律,不再两套标准)。 */
57
63
  declare function applyPanelSizing(panel: HTMLElement, sizing: PanelSizing | null): void;
58
64
  interface PopoverLayout {
@@ -68,15 +74,32 @@ interface OpenPopoverOptions extends PopoverLayoutOptions {
68
74
  sizing?: PanelSizing;
69
75
  /** 关闭(含外点/Esc 触发的关闭)后的回调 */
70
76
  onClose?: () => void;
77
+ /** Optional initial focus inside an interactive panel. */
78
+ initialFocus?: HTMLElement;
79
+ /** Restore the anchor only if the panel still owns focus; default true. */
80
+ restoreFocus?: boolean;
71
81
  }
72
82
  /** 视口智能布局:返回 {side, left, top, width, maxHeight}。
73
83
  * 纵向侧(top/bottom):选侧按上下空间,宽按 min/max 夹取,高按内容预期。
74
84
  * 横向侧(left/right):锚点左右翻转,宽取 min(无内容测量,横向侧取下限),
75
85
  * 高占满可用空间,纵向按 align(start/center/end;stretch 视作 start)贴锚。 */
76
86
  declare function computePopoverLayout(anchor: PopoverRect, viewport: PopoverViewport, opts?: PopoverLayoutOptions): PopoverLayout;
77
- /** 打开浮层:panel 移入 body 并按 anchor 布局;重复调用同一 panel 为先关后开。 */
87
+ /** Shared ownership for action menus: opening one retires the previous renderer
88
+ * without stealing focus. The release handle cannot release a newer owner. */
89
+ declare function claimMenuFocus(close: () => void): () => void;
90
+ /** 打开浮层:panel 移入 body 并按 anchor 布局;重复调用同一 panel 为先关后开;
91
+ * 退场中重开从当前动画位置续接(复用既有门户记录,不重复插占位)。 */
78
92
  declare function openPopover(panel: HTMLElement, options: OpenPopoverOptions): void;
79
- /** 关闭浮层:移除监听并把 panel 还原到打开前的位置与 hidden 状态。 */
80
- declare function closePopover(panel: HTMLElement): void;
93
+ /** 关闭浮层:视觉先退场(可被打断重开),finalize 再还原门户;焦点与 onClose 立即结算。
94
+ * immediate: true 供卸载/销毁路径——跳过退场直接 finalize(拆页时不把面板留在 body)。 */
95
+ declare function closePopover(panel: HTMLElement, opts?: {
96
+ immediate?: boolean;
97
+ }): void;
98
+ /**
99
+ * 声明式浮层委托:<button data-icen-popover-target="panelId"> 点击开关 .icen-popover 面板,
100
+ * 同步触发器 aria-haspopup/aria-expanded/aria-controls;面板内 .icen-popover-close 点击关闭。
101
+ * 同一 root 重复调用返回同一销毁函数;销毁摘除委托并关闭本作用域打开的浮层。SSR 下返回 no-op。
102
+ */
103
+ declare function initPopover(root?: ParentNode): () => void;
81
104
 
82
- export { type OpenPopoverOptions, type PanelSizing, type PopoverAlign, type PopoverLayout, type PopoverLayoutOptions, type PopoverRect, type PopoverSide, type PopoverViewport, applyPanelSizing, closePopover, computePopoverLayout, openPopover, readPanelSizing, resolvePanelSizing, sizingToLayout };
105
+ export { type OpenPopoverOptions, type PanelSizing, type PopoverAlign, type PopoverLayout, type PopoverLayoutOptions, type PopoverRect, type PopoverSide, type PopoverViewport, applyPanelSizing, claimMenuFocus, closePopover, computePopoverLayout, initPopover, openPopover, readPanelSizing, resolvePanelSizing, revealPopoverItem, sizingToLayout };
@@ -1,10 +1,32 @@
1
1
  // src/behaviors/popover.ts
2
+ function revealPopoverItem(item, scrollport, opts = {}) {
3
+ if (typeof document === "undefined") return;
4
+ if (opts.focus) item.focus({ preventScroll: true });
5
+ const rect = item.getBoundingClientRect(), bounds = scrollport.getBoundingClientRect();
6
+ const style = getComputedStyle(scrollport);
7
+ const inset = (value, extent) => {
8
+ const amount = Number.parseFloat(value) || 0;
9
+ return value.endsWith("%") ? amount * extent / 100 : amount;
10
+ };
11
+ if (opts.axis !== "inline") {
12
+ const top = bounds.top + scrollport.clientTop + inset(style.scrollPaddingTop, scrollport.clientHeight);
13
+ const bottom = bounds.top + scrollport.clientTop + scrollport.clientHeight - inset(style.scrollPaddingBottom, scrollport.clientHeight);
14
+ if (rect.top < top) scrollport.scrollTop -= top - rect.top;
15
+ else if (rect.bottom > bottom) scrollport.scrollTop += rect.bottom - bottom;
16
+ }
17
+ if (opts.axis === "inline" || opts.axis === "both") {
18
+ const left = bounds.left + scrollport.clientLeft + inset(style.scrollPaddingLeft, scrollport.clientWidth);
19
+ const right = bounds.left + scrollport.clientLeft + scrollport.clientWidth - inset(style.scrollPaddingRight, scrollport.clientWidth);
20
+ if (rect.left < left) scrollport.scrollLeft -= left - rect.left;
21
+ else if (rect.right > right) scrollport.scrollLeft += rect.right - right;
22
+ }
23
+ }
2
24
  var PANEL_SIZING_ATTRS = {
3
- width: "data-panel-width",
4
- minWidth: "data-panel-min",
5
- maxWidth: "data-panel-max",
6
- minHeight: "data-panel-min-height",
7
- maxHeight: "data-panel-max-height"
25
+ width: "data-icen-panel-width",
26
+ minWidth: "data-icen-panel-min",
27
+ maxWidth: "data-icen-panel-max",
28
+ minHeight: "data-icen-panel-min-height",
29
+ maxHeight: "data-icen-panel-max-height"
8
30
  };
9
31
  function readPanelSizing(el) {
10
32
  const out = {};
@@ -85,10 +107,10 @@ function computePopoverLayout(anchor, viewport, opts = {}) {
85
107
  const preferredSide = opts.side ?? "top";
86
108
  const align = opts.align ?? "start";
87
109
  if (preferredSide === "left" || preferredSide === "right") {
110
+ const minWidth2 = Math.max(1, opts.minWidth ?? DEFAULT_MIN_WIDTH);
88
111
  const availableLeft = Math.max(0, anchor.left - margin - offset);
89
112
  const availableRight = Math.max(0, viewport.width - anchor.right - margin - offset);
90
- const side2 = (preferredSide === "left" ? availableLeft : availableRight) >= DEFAULT_MIN_WIDTH ? preferredSide : preferredSide === "left" ? "right" : "left";
91
- const minWidth2 = Math.max(1, opts.minWidth ?? DEFAULT_MIN_WIDTH);
113
+ const side2 = (preferredSide === "left" ? availableLeft : availableRight) >= minWidth2 ? preferredSide : preferredSide === "left" ? "right" : "left";
92
114
  const maxWidth2 = Math.min(
93
115
  Math.max(opts.maxWidth ?? DEFAULT_MAX_WIDTH, minWidth2),
94
116
  side2 === "left" ? availableLeft : availableRight
@@ -117,7 +139,7 @@ function computePopoverLayout(anchor, viewport, opts = {}) {
117
139
  const minHeight = Math.min(Math.max(80, opts.minHeight ?? DEFAULT_MIN_HEIGHT), availableHeight);
118
140
  const requestedMaxHeight = Math.max(minHeight, opts.maxHeight ?? DEFAULT_MAX_HEIGHT);
119
141
  const fallbackHeight = Math.min(260, requestedMaxHeight);
120
- const contentHeight = Math.max(opts.contentHeightHint ?? 0, fallbackHeight);
142
+ const contentHeight = opts.contentHeightHint != null && opts.contentHeightHint > 0 ? opts.contentHeightHint : fallbackHeight;
121
143
  const desiredHeight = clamp(contentHeight, minHeight, Math.min(requestedMaxHeight, availableHeight));
122
144
  const side = chooseSide(preferredSide, availableTop, availableBottom, desiredHeight);
123
145
  const sideSpace = side === "top" ? availableTop : availableBottom;
@@ -134,6 +156,16 @@ function computePopoverLayout(anchor, viewport, opts = {}) {
134
156
  return { side, left, top, width, maxHeight };
135
157
  }
136
158
  var openPanels = /* @__PURE__ */ new Map();
159
+ var closingPanels = /* @__PURE__ */ new Map();
160
+ var EXIT_FALLBACK_MS = 240;
161
+ var menuCloser = null;
162
+ function claimMenuFocus(close) {
163
+ menuCloser?.();
164
+ menuCloser = close;
165
+ return () => {
166
+ if (menuCloser === close) menuCloser = null;
167
+ };
168
+ }
137
169
  function rectFromElement(el) {
138
170
  const r = el.getBoundingClientRect();
139
171
  return { left: r.left, top: r.top, right: r.right, bottom: r.bottom, width: r.width, height: r.height };
@@ -151,21 +183,54 @@ function relayout(state) {
151
183
  closePopover(state.panel);
152
184
  return;
153
185
  }
154
- const layout = computePopoverLayout(
186
+ let layout = computePopoverLayout(
155
187
  anchor,
156
188
  { width: window.innerWidth, height: window.innerHeight },
157
189
  state.opts
158
190
  );
159
191
  const { panel } = state;
192
+ const scrollPositions = [panel, ...panel.querySelectorAll("*")].filter((node) => node.scrollTop !== 0 || node.scrollLeft !== 0).map((node) => ({ node, top: node.scrollTop, left: node.scrollLeft }));
193
+ panel.style.width = `${layout.width}px`;
194
+ if (state.opts.contentHeightHint == null && (layout.side === "top" || layout.side === "bottom")) {
195
+ panel.style.maxHeight = `${Math.min(state.opts.maxHeight ?? DEFAULT_MAX_HEIGHT, Math.max(80, window.innerHeight - (state.opts.margin ?? 12) * 2))}px`;
196
+ const measured = panel.offsetHeight;
197
+ if (measured > 0) layout = computePopoverLayout(anchor, { width: window.innerWidth, height: window.innerHeight }, { ...state.opts, contentHeightHint: measured });
198
+ }
160
199
  panel.style.left = `${layout.left}px`;
161
- panel.style.top = `${layout.top}px`;
162
200
  panel.style.width = `${layout.width}px`;
163
201
  panel.style.maxHeight = `${layout.maxHeight}px`;
164
- panel.dataset.side = layout.side;
202
+ let top = layout.top;
203
+ const actualHeight = panel.offsetHeight;
204
+ if (actualHeight > 0 && (layout.side === "top" || layout.side === "bottom")) {
205
+ const desired = layout.side === "top" ? anchor.top - (state.opts.offset ?? 8) - actualHeight : anchor.bottom + (state.opts.offset ?? 8);
206
+ const margin = state.opts.margin ?? 12;
207
+ top = clamp(desired, margin, window.innerHeight - margin - actualHeight);
208
+ }
209
+ panel.style.top = `${top}px`;
210
+ for (const pos of scrollPositions) {
211
+ pos.node.scrollTop = pos.top;
212
+ pos.node.scrollLeft = pos.left;
213
+ }
214
+ panel.dataset.icenSide = layout.side;
165
215
  panel.style.visibility = state.prevVisibility;
166
216
  }
167
- function onWindowChange() {
168
- openPanels.forEach(relayout);
217
+ function observeOpenPanel(state) {
218
+ if (typeof ResizeObserver === "undefined") return;
219
+ state.resizeObserver = new ResizeObserver(() => {
220
+ if (state.layoutFrame != null) return;
221
+ state.layoutFrame = window.requestAnimationFrame(() => {
222
+ state.layoutFrame = void 0;
223
+ if (openPanels.get(state.panel) === state) relayout(state);
224
+ });
225
+ });
226
+ state.resizeObserver.observe(state.panel);
227
+ if (state.anchor instanceof Element) state.resizeObserver.observe(state.anchor);
228
+ }
229
+ function onWindowChange(event) {
230
+ openPanels.forEach((state) => {
231
+ if (event?.type === "scroll" && event.target instanceof Node && state.panel.contains(event.target)) return;
232
+ relayout(state);
233
+ });
169
234
  }
170
235
  function onOutsidePointerDown(e) {
171
236
  const target = e.target;
@@ -177,18 +242,40 @@ function onOutsidePointerDown(e) {
177
242
  });
178
243
  }
179
244
  function onKeyDown(e) {
180
- if (e.key !== "Escape") return;
245
+ if (e.key !== "Escape" || e.defaultPrevented || e.isComposing || e.keyCode === 229 || menuCloser) return;
181
246
  const states = Array.from(openPanels.values());
182
247
  const top = states[states.length - 1];
183
248
  if (top) {
184
249
  e.preventDefault();
250
+ e.stopPropagation();
185
251
  closePopover(top.panel);
186
252
  }
187
253
  }
188
254
  function openPopover(panel, options) {
189
255
  if (typeof document === "undefined") return;
256
+ const pending = closingPanels.get(panel);
257
+ if (pending) {
258
+ window.clearTimeout(pending.timer);
259
+ panel.removeEventListener("transitionend", pending.onEnd);
260
+ closingPanels.delete(panel);
261
+ delete panel.dataset.icenState;
262
+ openPanels.set(panel, pending.state);
263
+ panel.hidden = false;
264
+ panel.style.visibility = "hidden";
265
+ relayout(pending.state);
266
+ observeOpenPanel(pending.state);
267
+ if (openPanels.size === 1) {
268
+ window.addEventListener("resize", onWindowChange);
269
+ window.addEventListener("scroll", onWindowChange, true);
270
+ document.addEventListener("pointerdown", onOutsidePointerDown, true);
271
+ window.addEventListener("keydown", onKeyDown, true);
272
+ }
273
+ const ifs = options.initialFocus;
274
+ if (ifs && panel.contains(ifs)) ifs.focus({ preventScroll: true });
275
+ return;
276
+ }
190
277
  if (openPanels.has(panel)) closePopover(panel);
191
- const { anchor, onClose, sizing, ...rest } = options;
278
+ const { anchor, onClose, sizing, initialFocus, restoreFocus, ...rest } = options;
192
279
  const layoutOpts = sizingToLayout(resolvePanelSizing(panel, sizing), rest);
193
280
  const placeholder = document.createComment("popover-anchor");
194
281
  panel.parentNode?.insertBefore(placeholder, panel);
@@ -201,25 +288,25 @@ function openPopover(panel, options) {
201
288
  onClose,
202
289
  placeholder,
203
290
  wasHidden,
204
- prevVisibility
291
+ prevVisibility,
292
+ returnFocus: restoreFocus === false ? null : anchor instanceof HTMLElement ? anchor : document.activeElement instanceof HTMLElement ? document.activeElement : null
205
293
  };
206
294
  openPanels.set(panel, state);
207
295
  document.body.appendChild(panel);
208
296
  panel.hidden = false;
209
297
  panel.style.visibility = "hidden";
210
298
  relayout(state);
299
+ observeOpenPanel(state);
300
+ if (initialFocus && panel.contains(initialFocus)) initialFocus.focus({ preventScroll: true });
211
301
  if (openPanels.size === 1) {
212
302
  window.addEventListener("resize", onWindowChange);
213
303
  window.addEventListener("scroll", onWindowChange, true);
214
304
  document.addEventListener("pointerdown", onOutsidePointerDown, true);
215
- window.addEventListener("keydown", onKeyDown);
305
+ window.addEventListener("keydown", onKeyDown, true);
216
306
  }
217
307
  }
218
- function closePopover(panel) {
219
- if (typeof document === "undefined") return;
220
- const state = openPanels.get(panel);
221
- if (!state) return;
222
- openPanels.delete(panel);
308
+ function finalizeClose(panel, state) {
309
+ delete panel.dataset.icenState;
223
310
  panel.style.visibility = state.prevVisibility;
224
311
  if (typeof state.wasHidden === "string") panel.setAttribute("hidden", state.wasHidden);
225
312
  else panel.hidden = state.wasHidden;
@@ -227,25 +314,139 @@ function closePopover(panel) {
227
314
  panel.style.top = "";
228
315
  panel.style.width = "";
229
316
  panel.style.maxHeight = "";
230
- delete panel.dataset.side;
317
+ delete panel.dataset.icenSide;
231
318
  if (state.placeholder.parentNode) {
232
319
  state.placeholder.parentNode.insertBefore(panel, state.placeholder);
233
320
  state.placeholder.remove();
234
321
  }
322
+ }
323
+ function closePopover(panel, opts) {
324
+ if (typeof document === "undefined") return;
325
+ const state = openPanels.get(panel);
326
+ if (!state) return;
327
+ openPanels.delete(panel);
328
+ state.resizeObserver?.disconnect();
329
+ if (state.layoutFrame != null) {
330
+ window.cancelAnimationFrame(state.layoutFrame);
331
+ state.layoutFrame = void 0;
332
+ }
333
+ const ownsFocus = panel.contains(document.activeElement);
334
+ if (ownsFocus && state.returnFocus?.isConnected) state.returnFocus.focus({ preventScroll: true });
235
335
  if (openPanels.size === 0) {
236
336
  window.removeEventListener("resize", onWindowChange);
237
337
  window.removeEventListener("scroll", onWindowChange, true);
238
338
  document.removeEventListener("pointerdown", onOutsidePointerDown, true);
239
- window.removeEventListener("keydown", onKeyDown);
339
+ window.removeEventListener("keydown", onKeyDown, true);
240
340
  }
241
341
  state.onClose?.();
342
+ const calm = opts?.immediate || typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
343
+ if (calm || !panel.isConnected) {
344
+ finalizeClose(panel, state);
345
+ return;
346
+ }
347
+ panel.dataset.icenState = "closing";
348
+ const finalize = () => {
349
+ const pending = closingPanels.get(panel);
350
+ if (!pending || pending.state !== state) return;
351
+ window.clearTimeout(pending.timer);
352
+ panel.removeEventListener("transitionend", pending.onEnd);
353
+ closingPanels.delete(panel);
354
+ finalizeClose(panel, state);
355
+ };
356
+ const onEnd = (event) => {
357
+ if (event.target === panel && event.propertyName === "opacity") finalize();
358
+ };
359
+ panel.addEventListener("transitionend", onEnd);
360
+ closingPanels.set(panel, {
361
+ state,
362
+ timer: window.setTimeout(finalize, EXIT_FALLBACK_MS),
363
+ onEnd
364
+ });
365
+ }
366
+ var popoverScopes = /* @__PURE__ */ new WeakMap();
367
+ var popoverDefaults = /* @__PURE__ */ new WeakMap();
368
+ function rememberPopoverDefaults(panel) {
369
+ let defaults = popoverDefaults.get(panel);
370
+ if (!defaults) {
371
+ const side = panel.dataset.icenSide;
372
+ const align = panel.getAttribute("data-icen-align");
373
+ defaults = {
374
+ side: side === "top" || side === "bottom" || side === "left" || side === "right" ? side : void 0,
375
+ align: align === "start" || align === "center" || align === "end" ? align : void 0
376
+ };
377
+ popoverDefaults.set(panel, defaults);
378
+ }
379
+ return defaults;
380
+ }
381
+ function initPopover(root) {
382
+ if (typeof document === "undefined") return () => {
383
+ };
384
+ const scope = root ?? document;
385
+ const existing = popoverScopes.get(scope);
386
+ if (existing) return existing;
387
+ const opened = /* @__PURE__ */ new Map();
388
+ const dismissHandlers = /* @__PURE__ */ new Map();
389
+ const watchDismiss = (panel) => {
390
+ if (dismissHandlers.has(panel)) return;
391
+ const handler = (event) => {
392
+ const target = event.target;
393
+ if (!(target instanceof Element)) return;
394
+ if (target.closest(".icen-popover-close, [data-icen-popover-dismiss]")) closePopover(panel);
395
+ };
396
+ panel.addEventListener("click", handler);
397
+ dismissHandlers.set(panel, handler);
398
+ };
399
+ const onClick = (event) => {
400
+ const target = event.target;
401
+ if (!(target instanceof Element)) return;
402
+ const trigger = target.closest("[data-icen-popover-target]");
403
+ if (!trigger || !scope.contains(trigger)) return;
404
+ const id = trigger.getAttribute("data-icen-popover-target") ?? "";
405
+ const panel = document.getElementById(id);
406
+ if (!panel || !panel.classList.contains("icen-popover")) return;
407
+ if (openPanels.has(panel)) {
408
+ closePopover(panel);
409
+ return;
410
+ }
411
+ const defaults = rememberPopoverDefaults(panel);
412
+ const sideAttr = trigger.getAttribute("data-icen-side");
413
+ const alignAttr = trigger.getAttribute("data-icen-align");
414
+ if (!trigger.hasAttribute("aria-haspopup")) trigger.setAttribute("aria-haspopup", "dialog");
415
+ trigger.setAttribute("aria-expanded", "true");
416
+ if (panel.id) trigger.setAttribute("aria-controls", panel.id);
417
+ opened.set(panel, trigger);
418
+ watchDismiss(panel);
419
+ openPopover(panel, {
420
+ anchor: trigger,
421
+ side: sideAttr ?? defaults.side ?? "bottom",
422
+ align: alignAttr ?? defaults.align ?? "start",
423
+ initialFocus: panel.querySelector("[data-icen-autofocus], [autofocus]") ?? void 0,
424
+ onClose: () => {
425
+ opened.delete(panel);
426
+ trigger.setAttribute("aria-expanded", "false");
427
+ }
428
+ });
429
+ };
430
+ scope.addEventListener("click", onClick);
431
+ const dispose = () => {
432
+ scope.removeEventListener("click", onClick);
433
+ for (const panel of [...opened.keys()]) closePopover(panel, { immediate: true });
434
+ for (const [panel, handler] of dismissHandlers) panel.removeEventListener("click", handler);
435
+ dismissHandlers.clear();
436
+ popoverScopes.delete(scope);
437
+ };
438
+ popoverScopes.set(scope, dispose);
439
+ return dispose;
242
440
  }
243
441
  export {
244
442
  applyPanelSizing,
443
+ claimMenuFocus,
245
444
  closePopover,
246
445
  computePopoverLayout,
446
+ initPopover,
247
447
  openPopover,
248
448
  readPanelSizing,
249
449
  resolvePanelSizing,
450
+ revealPopoverItem,
250
451
  sizingToLayout
251
452
  };
@@ -1,6 +1,9 @@
1
- /** 为 root 下每个 [data-select] 容器初始化(root 自身是 [data-select] 也算)。
1
+ /** 为 root 下每个 [data-icen-select] 容器初始化(root 自身是 [data-icen-select] 也算)。
2
2
  * 返回销毁函数:摘除本次初始化容器的全部监听并复位幂等标记(可重新 init);
3
3
  * 最后一个容器销毁时同时摘除 document 级单例委托。 */
4
4
  declare function initSelect(root?: ParentNode): () => void;
5
+ /** Resync authored selection classes, labels and form values without emitting a
6
+ * user-change event. Child/text/native-disabled changes are observed automatically. */
7
+ declare function refreshSelect(root?: ParentNode): void;
5
8
 
6
- export { initSelect };
9
+ export { initSelect, refreshSelect };