@xihan-ui/styles 1.0.0-alpha.1 → 1.0.0-alpha.3

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 (129) hide show
  1. package/CHANGELOG.md +527 -0
  2. package/README.md +1 -1
  3. package/css/accordion.css +61 -5
  4. package/css/affix.css +5 -0
  5. package/css/alert.css +35 -5
  6. package/css/anchor.css +12 -7
  7. package/css/avatar-group.css +8 -3
  8. package/css/avatar.css +8 -3
  9. package/css/back-top.css +21 -1
  10. package/css/badge.css +21 -2
  11. package/css/breadcrumb.css +29 -4
  12. package/css/button-group.css +19 -13
  13. package/css/button.css +57 -11
  14. package/css/calendar.css +124 -2
  15. package/css/card.css +10 -5
  16. package/css/carousel.css +41 -5
  17. package/css/cascader.css +188 -102
  18. package/css/checkbox-group.css +46 -19
  19. package/css/checkbox.css +59 -8
  20. package/css/clipboard.css +7 -1
  21. package/css/code-block.css +17 -7
  22. package/css/collapsible.css +47 -2
  23. package/css/color-picker.css +117 -46
  24. package/css/combobox.css +132 -52
  25. package/css/composer.css +10 -3
  26. package/css/context-menu.css +101 -75
  27. package/css/countdown.css +5 -0
  28. package/css/date-field.css +96 -14
  29. package/css/date-picker.css +281 -65
  30. package/css/descriptions.css +7 -2
  31. package/css/dialog.css +109 -22
  32. package/css/download-trigger.css +76 -0
  33. package/css/drawer.css +75 -23
  34. package/css/dynamic-input.css +73 -17
  35. package/css/editable.css +17 -10
  36. package/css/ellipsis.css +6 -1
  37. package/css/empty-state.css +12 -7
  38. package/css/field.css +14 -6
  39. package/css/fieldset.css +69 -0
  40. package/css/file-upload.css +50 -7
  41. package/css/flex.css +5 -0
  42. package/css/float-button.css +21 -1
  43. package/css/floating-panel.css +310 -0
  44. package/css/form.css +10 -5
  45. package/css/gradient-text.css +5 -0
  46. package/css/grid.css +5 -0
  47. package/css/heatmap.css +410 -0
  48. package/css/highlight.css +6 -1
  49. package/css/hotkeys.css +90 -0
  50. package/css/hover-card.css +58 -50
  51. package/css/icon-wrapper.css +5 -0
  52. package/css/icon.css +5 -0
  53. package/css/image-cropper.css +231 -0
  54. package/css/image-viewer.css +167 -22
  55. package/css/image.css +7 -2
  56. package/css/infinite-scroll.css +6 -1
  57. package/css/json-viewer.css +241 -0
  58. package/css/layout.css +11 -10
  59. package/css/list.css +13 -8
  60. package/css/listbox.css +25 -4
  61. package/css/loading-bar.css +12 -1
  62. package/css/log.css +6 -1
  63. package/css/marquee.css +14 -0
  64. package/css/masonry.css +63 -0
  65. package/css/mention.css +105 -32
  66. package/css/menu.css +131 -46
  67. package/css/menubar.css +150 -66
  68. package/css/navigation-menu.css +59 -29
  69. package/css/number-animation.css +8 -3
  70. package/css/number-field.css +178 -16
  71. package/css/overlay-arrow.css +51 -0
  72. package/css/page-header.css +10 -5
  73. package/css/pagination.css +18 -13
  74. package/css/password-input.css +356 -0
  75. package/css/pin-input.css +18 -10
  76. package/css/popconfirm.css +73 -16
  77. package/css/popover.css +88 -44
  78. package/css/popselect.css +201 -52
  79. package/css/progress.css +52 -7
  80. package/css/qr-code.css +6 -1
  81. package/css/radio-group.css +19 -8
  82. package/css/rating.css +7 -2
  83. package/css/reset.css +17 -0
  84. package/css/result.css +11 -6
  85. package/css/scroll-area.css +65 -94
  86. package/css/scrollbar.css +167 -0
  87. package/css/segmented.css +215 -0
  88. package/css/select.css +292 -124
  89. package/css/separator.css +7 -2
  90. package/css/side-nav.css +116 -19
  91. package/css/signature-pad.css +144 -0
  92. package/css/skeleton.css +12 -1
  93. package/css/slider.css +15 -10
  94. package/css/space.css +117 -0
  95. package/css/spinner.css +31 -11
  96. package/css/splitter.css +5 -0
  97. package/css/statistic.css +14 -9
  98. package/css/steps.css +30 -8
  99. package/css/switch.css +62 -13
  100. package/css/table.css +136 -37
  101. package/css/tabs.css +9 -4
  102. package/css/tag.css +177 -0
  103. package/css/tags-input.css +83 -40
  104. package/css/text-field.css +154 -36
  105. package/css/thread.css +6 -1
  106. package/css/time-field.css +102 -16
  107. package/css/time-picker.css +264 -69
  108. package/css/time.css +5 -0
  109. package/css/timeline.css +15 -10
  110. package/css/timer.css +119 -0
  111. package/css/toast.css +46 -21
  112. package/css/toaster.css +6 -1
  113. package/css/toggle-group.css +5 -0
  114. package/css/toggle.css +11 -6
  115. package/css/tone.css +10 -5
  116. package/css/toolbar.css +6 -1
  117. package/css/tooltip.css +54 -38
  118. package/css/tour.css +128 -63
  119. package/css/transfer.css +73 -17
  120. package/css/tree-select.css +203 -83
  121. package/css/tree.css +90 -1
  122. package/css/typography.css +9 -4
  123. package/css/undefined.css +11 -1
  124. package/css/virtualizer.css +8 -1
  125. package/css/watermark.css +6 -2
  126. package/index.css +17 -1
  127. package/index.unlayered.css +9394 -2883
  128. package/package.json +19 -4
  129. package/css/motion.css +0 -58
package/css/mention.css CHANGED
@@ -1,7 +1,57 @@
1
+ @layer xihan.motion {
2
+ /* 动画名的查找只认「文档里有没有这个名字」:引用别处文件里的名字,那份文件不一定在场,
3
+ 动画会静默地整个不跑。每份皮肤都能单独引入,所以它用到的关键帧都写在自己身上。 */
4
+ @keyframes xh-pop-in {
5
+ from {
6
+ opacity: 0;
7
+ scale: var(--xh-motion-scale-enter);
8
+ }
9
+
10
+ to {
11
+ opacity: 1;
12
+ scale: 1;
13
+ }
14
+ }
15
+ @keyframes xh-pop-out {
16
+ from {
17
+ opacity: 1;
18
+ scale: 1;
19
+ }
20
+
21
+ to {
22
+ opacity: 0;
23
+ scale: var(--xh-motion-scale-enter);
24
+ }
25
+ }
26
+ }
27
+
1
28
  @layer xihan.components {
2
- [data-scope='mention'][data-part='root'] {
3
- /* 尺寸档只换这四个私有槽,输入框与候选都从 root 继承它们,换档不必给每个部件各写一条选择器。
4
- 浮层在 positioner 里、也是 root 的后代,一并继承 */
29
+ /* 皮肤在场的标记:开发模式下按 scope 探一次,取不到就说明这份皮肤没被引入。 */
30
+ [data-scope='mention'] {
31
+ --xh-mention-skin: 1;
32
+ }
33
+
34
+ /* 坐标由定位引擎写进 style(inset-inline-start / inset-block-start),这里只定位模式与层级 */
35
+ [data-scope='mention'][data-part='positioner'] {
36
+ /* 落定那一侧的可用高度,由定位引擎经内联自定义属性写进来,内容层继承它限高;
37
+ 引擎没算(尚未落位 / 无引擎)时不额外限高 */
38
+ --xh-_mention-available-h: 100vh;
39
+
40
+ position: fixed;
41
+ z-index: var(--xh-mention-layer, var(--xh-layer-popover));
42
+ /* 空壳不吃指针,交互只发生在 content 上 */
43
+ pointer-events: none;
44
+ }
45
+
46
+ /* 输入框被滚出可视区:整个浮层随之收起 */
47
+ [data-scope='mention'][data-part='positioner'][data-hidden] {
48
+ visibility: hidden;
49
+ }
50
+
51
+ /* 私有槽同时声明在根与 positioner 上:浮层被搬到 portal 落点,不是根的后代,继承不过去 */
52
+ [data-scope='mention']:is([data-part='root'], [data-part='positioner']) {
53
+ /* 尺寸档只换这五个私有槽,输入框与候选都从这两处继承它们,换档不必给每个部件各写一条选择器 */
54
+ --xh-_mention-h: var(--xh-control-h-md);
5
55
  --xh-_mention-px: var(--xh-control-px-md);
6
56
  --xh-_mention-py: var(--xh-field-py);
7
57
  --xh-_mention-gap: var(--xh-control-gap-md);
@@ -14,7 +64,9 @@
14
64
 
15
65
  /* 聚焦环恒用固定环色,不随语气走 */
16
66
  --xh-_mention-ring: var(--xh-ring-focus);
67
+ }
17
68
 
69
+ [data-scope='mention'][data-part='root'] {
18
70
  display: block;
19
71
  }
20
72
 
@@ -25,37 +77,39 @@
25
77
 
26
78
  /* —— 尺寸 —— */
27
79
  /* 缺省档写在上面的基础规则里 */
28
- [data-scope='mention'][data-part='root'][data-size='sm'] {
80
+ [data-scope='mention']:is([data-part='root'], [data-part='positioner'])[data-size='sm'] {
81
+ --xh-_mention-h: var(--xh-control-h-sm);
29
82
  --xh-_mention-px: var(--xh-control-px-sm);
30
83
  --xh-_mention-py: var(--xh-space-1);
31
84
  --xh-_mention-gap: var(--xh-control-gap-sm);
32
- --xh-_mention-font-size: var(--xh-font-size-sm);
85
+ --xh-_mention-font-size: var(--xh-control-font-sm);
33
86
  }
34
87
 
35
- [data-scope='mention'][data-part='root'][data-size='lg'] {
88
+ [data-scope='mention']:is([data-part='root'], [data-part='positioner'])[data-size='lg'] {
89
+ --xh-_mention-h: var(--xh-control-h-lg);
36
90
  --xh-_mention-px: var(--xh-control-px-lg);
37
91
  --xh-_mention-py: var(--xh-space-3);
38
92
  --xh-_mention-gap: var(--xh-control-gap-lg);
39
- --xh-_mention-font-size: var(--xh-font-size-lg);
93
+ --xh-_mention-font-size: var(--xh-control-font-lg);
40
94
  }
41
95
 
42
96
  /* —— 形态 —— */
43
97
  /* 输入类不做 solid:实心底压着可编辑的正文,读起来像块不可点的色板。
44
98
  形态只决定描边与底色怎么用,用哪族颜色由 data-tone 决定 */
45
- [data-scope='mention'][data-part='root'][data-variant='outline'] {
99
+ [data-scope='mention']:is([data-part='root'], [data-part='positioner'])[data-variant='outline'] {
46
100
  --xh-_mention-input-bg: var(--xh-bg-canvas);
47
101
  --xh-_mention-input-border: var(--xh-border-control);
48
102
  --xh-_mention-input-border-hover: var(--xh-border-control-hover);
49
103
  }
50
104
 
51
105
  /* 填充式:底色顶替描边,悬停时才浮出一道淡边 */
52
- [data-scope='mention'][data-part='root'][data-variant='subtle'] {
106
+ [data-scope='mention']:is([data-part='root'], [data-part='positioner'])[data-variant='subtle'] {
53
107
  --xh-_mention-input-bg: var(--xh-_tone-subtle, var(--xh-bg-subtle));
54
108
  --xh-_mention-input-border: transparent;
55
109
  --xh-_mention-input-border-hover: var(--xh-_tone-border, var(--xh-border-control-hover));
56
110
  }
57
111
 
58
- [data-scope='mention'][data-part='root'][data-variant='ghost'] {
112
+ [data-scope='mention']:is([data-part='root'], [data-part='positioner'])[data-variant='ghost'] {
59
113
  --xh-_mention-input-bg: transparent;
60
114
  --xh-_mention-input-border: transparent;
61
115
  --xh-_mention-input-border-hover: var(--xh-_tone-border, var(--xh-border-control-hover));
@@ -66,6 +120,9 @@
66
120
  [data-scope='mention'][data-part='input'] {
67
121
  display: block;
68
122
  inline-size: 100%;
123
+ min-inline-size: var(--xh-mention-input-min-w, var(--xh-control-min-w));
124
+ /* 缺省宿主是 textarea:高度按行数撑,控件档只兜住底线 */
125
+ min-block-size: var(--xh-mention-input-h, var(--xh-_mention-h));
69
126
  padding-block: var(--xh-mention-input-py, var(--xh-_mention-py));
70
127
  padding-inline: var(--xh-mention-input-px, var(--xh-_mention-px));
71
128
  border: var(--xh-stroke-thin) solid var(--xh-mention-input-border, var(--xh-_mention-input-border));
@@ -81,6 +138,13 @@
81
138
  transition: border-color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
82
139
  }
83
140
 
141
+ /* 单行宿主定高到控件档,纵向内距归零,与并排的其他控件等高。
142
+ 单行与多行在 DOM 上只差一个标签名,没有别的标记可选,所以按标签名分支 */
143
+ input[data-scope='mention'][data-part='input'] {
144
+ block-size: var(--xh-mention-input-h, var(--xh-_mention-h));
145
+ padding-block: 0;
146
+ }
147
+
84
148
  [data-scope='mention'][data-part='input'][hidden] {
85
149
  display: none;
86
150
  }
@@ -89,16 +153,28 @@
89
153
  color: var(--xh-mention-placeholder-fg, var(--xh-fg-subtle));
90
154
  }
91
155
 
92
- [data-scope='mention'][data-part='input']:hover:not(:disabled) {
156
+ [data-scope='mention'][data-part='input']:hover:not(:disabled, [data-invalid]) {
93
157
  border-color: var(--xh-mention-input-border-hover, var(--xh-_mention-input-border-hover));
94
158
  }
95
159
 
96
160
  [data-scope='mention'][data-part='input']:focus-visible {
97
- border-color: var(--xh-mention-input-border-focus, var(--xh-_mention-ring));
161
+ border-color: var(--xh-mention-input-border-focus, var(--xh-_tone, var(--xh-border-control-focus)));
98
162
  outline: var(--xh-ring-width) solid var(--xh-_mention-ring);
99
163
  outline-offset: var(--xh-ring-offset);
100
164
  }
101
165
 
166
+ [data-scope='mention'][data-part='input'][data-invalid] {
167
+ border-color: var(--xh-mention-input-border-invalid, var(--xh-border-invalid));
168
+
169
+ /* 校验失败时环也跟着变红:两圈等宽相邻却一红一蓝,读的人不知道哪个才是当前状态 */
170
+ --xh-_mention-ring: var(--xh-ring-invalid);
171
+ }
172
+
173
+ /* 只读仍可聚焦、可复制,所以不置灰,只把底色压下去表示"改不动" */
174
+ [data-scope='mention'][data-part='input'][readonly] {
175
+ background: var(--xh-mention-input-bg-readonly, var(--xh-bg-subtle));
176
+ }
177
+
102
178
  /* 输入框是单体表单控件,禁用走原生 disabled(候选那边一律 aria-disabled) */
103
179
  [data-scope='mention'][data-part='input']:disabled {
104
180
  background: var(--xh-mention-input-bg-disabled, var(--xh-bg-subtle));
@@ -108,40 +184,28 @@
108
184
  cursor: not-allowed;
109
185
  }
110
186
 
111
- /* 坐标由定位引擎写进 style(inset-inline-start / inset-block-start),这里只定位模式与层级 */
112
- [data-scope='mention'][data-part='positioner'] {
113
- position: fixed;
114
- z-index: var(--xh-layer-popover);
115
- /* 空壳不吃指针,交互只发生在 content 上 */
116
- pointer-events: none;
117
- }
118
-
119
- /* 输入框被滚出可视区:整个浮层随之收起 */
120
- [data-scope='mention'][data-part='positioner'][data-hidden] {
121
- visibility: hidden;
122
- }
123
-
124
187
  [data-scope='mention'][data-part='content'] {
125
188
  display: flex;
126
189
  flex-direction: column;
127
190
  min-inline-size: var(--xh-mention-content-min-w, var(--xh-overlay-min-w));
128
191
  max-inline-size: var(--xh-mention-content-max-w, var(--xh-overlay-max-w));
129
- max-block-size: var(--xh-mention-content-max-h, var(--xh-overlay-max-h));
192
+ max-block-size: min(var(--xh-mention-content-max-h, var(--xh-overlay-max-h)), var(--xh-_mention-available-h));
130
193
  padding-block: var(--xh-mention-content-py, var(--xh-space-1));
131
194
  padding-inline: var(--xh-mention-content-px, var(--xh-space-1));
132
195
  border: var(--xh-stroke-thin) solid var(--xh-mention-content-border, var(--xh-border-default));
133
196
  border-radius: var(--xh-mention-content-radius, var(--xh-shape-surface));
134
197
  background: var(--xh-mention-content-bg, var(--xh-bg-surface));
135
198
  color: var(--xh-mention-content-fg, var(--xh-fg-default));
136
- box-shadow: var(--xh-mention-content-shadow, var(--xh-elevation-2));
199
+ box-shadow: var(--xh-mention-content-shadow, var(--xh-elevation-floating));
137
200
  overflow-y: auto;
138
201
  pointer-events: auto;
139
202
  }
140
203
 
141
- /* display 会盖掉 hidden 属性的 UA 规则,收起态显式让位 */
142
- [data-scope='mention'][data-part='content'][hidden] {
143
- display: none;
144
- }
204
+ /* 这里刻意没有 [data-part='content'][hidden] { display: none },与 dialog 一致:
205
+ 收起态连接层会给 content 打 hidden,而上面那条 display 正是要盖掉 UA 的
206
+ [hidden]{display:none}——退场动画得先能播。presence 读的是 content 的 animationName,
207
+ 真正的收起由宿主在退场结束后落成内联 display:none(Vue 那边直接不渲染)。
208
+ 其余声明了 display 的角色节点仍补 [hidden],那几处没有退场动画。 */
145
209
 
146
210
  /* 宿主把候选全筛没了:浮层还开着,但这一副框壳没有内容可框,整个让位。
147
211
  组件不替宿主判空——「没有匹配项」要不要说话、说什么,是宿主的事 */
@@ -156,7 +220,13 @@
156
220
  }
157
221
 
158
222
  [data-scope='mention'][data-part='content'][data-state='open'] {
159
- animation: xh-fade-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter);
223
+ animation: xh-pop-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter);
224
+ }
225
+
226
+ /* 退场:收起那一帧 hidden 由退场闸门押后,这里才播得出来。forwards 把末态钉住,
227
+ 免得动画结束到真收起之间闪一下原样 */
228
+ [data-scope='mention'][data-part='content'][data-state='closed'] {
229
+ animation: xh-pop-out var(--xh-motion-duration-exit) var(--xh-motion-ease-exit) forwards;
160
230
  }
161
231
 
162
232
  [data-scope='mention'][data-part='item'] {
@@ -186,6 +256,9 @@
186
256
  两套底色各画各的会出现"鼠标停在 A 上、回车却插进了 B" */
187
257
  [data-scope='mention'][data-part='item'][data-highlighted]:not([data-disabled]) {
188
258
  background: var(--xh-mention-item-bg-hover, var(--xh-bg-subtle));
259
+ /* 焦点恒在输入框,高亮条目自带内收环;列表是滚动容器,环画在外沿会被裁掉 */
260
+ outline: var(--xh-ring-width) solid var(--xh-ring-focus);
261
+ outline-offset: calc(-1 * var(--xh-ring-width));
189
262
  }
190
263
 
191
264
  /* 禁用走 aria-disabled 而非原生 disabled(原生 disabled 不派 click,禁用守卫就走不到),
package/css/menu.css CHANGED
@@ -1,8 +1,44 @@
1
+ @layer xihan.motion {
2
+ /* 动画名的查找只认「文档里有没有这个名字」:引用别处文件里的名字,那份文件不一定在场,
3
+ 动画会静默地整个不跑。每份皮肤都能单独引入,所以它用到的关键帧都写在自己身上。 */
4
+ @keyframes xh-pop-in {
5
+ from {
6
+ opacity: 0;
7
+ scale: var(--xh-motion-scale-enter);
8
+ }
9
+
10
+ to {
11
+ opacity: 1;
12
+ scale: 1;
13
+ }
14
+ }
15
+ @keyframes xh-pop-out {
16
+ from {
17
+ opacity: 1;
18
+ scale: 1;
19
+ }
20
+
21
+ to {
22
+ opacity: 0;
23
+ scale: var(--xh-motion-scale-enter);
24
+ }
25
+ }
26
+ }
27
+
1
28
  @layer xihan.components {
29
+ /* 皮肤在场的标记:开发模式下按 scope 探一次,取不到就说明这份皮肤没被引入。 */
30
+ [data-scope='menu'] {
31
+ --xh-menu-skin: 1;
32
+ }
33
+
2
34
  /* 坐标由定位引擎写进 style(inset-inline-start / inset-block-start),这里只定位模式与层级 */
3
35
  [data-scope='menu'][data-part='positioner'] {
36
+ /* 落定那一侧的可用高度,由定位引擎经内联自定义属性写进来,content 继承它限高;
37
+ 引擎没算(尚未落位 / 无引擎)时不额外限高 */
38
+ --xh-_menu-available-h: 100vh;
39
+
4
40
  position: fixed;
5
- z-index: var(--xh-layer-popover);
41
+ z-index: var(--xh-menu-layer, var(--xh-layer-popover));
6
42
  /* 空壳不吃指针,交互只发生在 content 上 */
7
43
  pointer-events: none;
8
44
  }
@@ -12,7 +48,23 @@
12
48
  visibility: hidden;
13
49
  }
14
50
 
51
+ /* 触发器的长相归作者,皮肤只表达展开态。私有槽声明在它自己身上:
52
+ 菜单没有 root 部件,触发器上头没有别的 menu 节点可以挂 */
53
+ [data-scope='menu'][data-part='trigger'] {
54
+ /* 展开着的那张菜单的触发器走品牌淡底强档,随语气换色相;没写 data-tone 时退回品牌淡底 */
55
+ --xh-_menu-active-bg: var(--xh-_tone-subtle, var(--xh-bg-brand-subtle));
56
+ }
57
+
58
+ /* 展开着的那一张是打开路径,走品牌淡底强档:哪一张开着必须一眼看得出来。
59
+ 不加粗:加粗会推挤相邻内容 */
60
+ [data-scope='menu'][data-part='trigger']:not([data-disabled])[data-state='open'] {
61
+ background: var(--xh-menu-trigger-bg-active, var(--xh-_menu-active-bg));
62
+ }
63
+
15
64
  [data-scope='menu'][data-part='content'] {
65
+ /* 作者塞进条目里的图标跟着字号走,与皮肤画的兜底字形同一把尺 */
66
+ --xh-icon-size: var(--xh-menu-icon-size, var(--xh-glyph-size-text));
67
+
16
68
  /* 尺寸档只换这四个私有槽,条目从 content 继承它们,换档不必给每个部件各写一条选择器。
17
69
  菜单没有 root 部件(浮层树的最外层就是 content),三轴属性都落在这里 */
18
70
  --xh-_menu-item-py: var(--xh-space-1_5);
@@ -28,20 +80,26 @@
28
80
  flex-direction: column;
29
81
  min-inline-size: var(--xh-menu-min-w, var(--xh-overlay-menu-min-w));
30
82
  max-inline-size: var(--xh-menu-max-w, var(--xh-overlay-max-w));
83
+ /* 两道上限取更小的那道:自己的高度上限,以及落定那一侧真正剩下的空间。
84
+ 列表族的 16rem 对命令菜单偏矮,这里另给一档 */
85
+ max-block-size: min(var(--xh-menu-max-h, var(--xh-overlay-menu-max-h)), var(--xh-_menu-available-h));
31
86
  padding-block: var(--xh-menu-py, var(--xh-space-1));
32
87
  padding-inline: var(--xh-menu-px, var(--xh-space-1));
33
88
  border: var(--xh-stroke-thin) solid var(--xh-menu-border, var(--xh-border-default));
34
89
  border-radius: var(--xh-menu-radius, var(--xh-shape-surface));
35
90
  background: var(--xh-menu-bg, var(--xh-bg-surface));
36
91
  color: var(--xh-menu-fg, var(--xh-fg-default));
37
- box-shadow: var(--xh-menu-shadow, var(--xh-elevation-2));
92
+ box-shadow: var(--xh-menu-shadow, var(--xh-elevation-floating));
93
+ /* 条目超过上限就在菜单内部滚。条目的焦点环是内收的,滚动容器裁在内边距缘上,环切不着 */
94
+ overflow-y: auto;
38
95
  pointer-events: auto;
39
96
  }
40
97
 
41
- /* display 会盖掉 hidden 属性的 UA 规则,收起态显式让位 */
42
- [data-scope='menu'][data-part='content'][hidden] {
43
- display: none;
44
- }
98
+ /* 这里刻意没有 [data-part='content'][hidden] { display: none },与 dialog 一致:
99
+ 收起态连接层会给 content 打 hidden,而上面那条 display 正是要盖掉 UA 的
100
+ [hidden]{display:none}——退场动画得先能播。presence 读的是 content 的 animationName,
101
+ 真正的收起由宿主在退场结束后落成内联 display:none(Vue 那边直接不渲染)。
102
+ 其余声明了 display 的角色节点仍补 [hidden],那几处没有退场动画。 */
45
103
 
46
104
  /* 容器只是焦点域兜底落点,落焦不画环:高亮永远长在条目上 */
47
105
  [data-scope='menu'][data-part='content']:focus-visible {
@@ -49,7 +107,13 @@
49
107
  }
50
108
 
51
109
  [data-scope='menu'][data-part='content'][data-state='open'] {
52
- animation: xh-fade-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter);
110
+ animation: xh-pop-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter);
111
+ }
112
+
113
+ /* 退场:收起那一帧 hidden 由退场闸门押后,这里才播得出来。forwards 把末态钉住,
114
+ 免得动画结束到真收起之间闪一下原样 */
115
+ [data-scope='menu'][data-part='content'][data-state='closed'] {
116
+ animation: xh-pop-out var(--xh-motion-duration-exit) var(--xh-motion-ease-exit) forwards;
53
117
  }
54
118
 
55
119
  /* —— 尺寸 —— */
@@ -59,14 +123,14 @@
59
123
  --xh-_menu-item-py: var(--xh-space-1);
60
124
  --xh-_menu-item-px: var(--xh-control-px-sm);
61
125
  --xh-_menu-item-gap: var(--xh-control-gap-sm);
62
- --xh-_menu-font-size: var(--xh-font-size-sm);
126
+ --xh-_menu-font-size: var(--xh-control-font-sm);
63
127
  }
64
128
 
65
129
  [data-scope='menu'][data-part='content'][data-size='lg'] {
66
130
  --xh-_menu-item-py: var(--xh-space-2);
67
131
  --xh-_menu-item-px: var(--xh-control-px-lg);
68
132
  --xh-_menu-item-gap: var(--xh-control-gap-lg);
69
- --xh-_menu-font-size: var(--xh-font-size-lg);
133
+ --xh-_menu-font-size: var(--xh-control-font-lg);
70
134
  }
71
135
 
72
136
  [data-scope='menu'][data-part='item'] {
@@ -87,8 +151,8 @@
87
151
  color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
88
152
  }
89
153
 
90
- /* 悬停与键盘焦点共用中性灰轻档:条目本身承载焦点,不另画描边 */
91
- [data-scope='menu'][data-part='item']:not([data-disabled]):is(:hover, :focus) {
154
+ /* 悬停与键盘锚点共用中性灰轻档:锚点只认连接层打的 data-highlighted,不读 :focus */
155
+ [data-scope='menu'][data-part='item']:not([data-disabled]):is(:hover, [data-highlighted]) {
92
156
  background: var(--xh-menu-item-bg-hover, var(--xh-bg-subtle));
93
157
  }
94
158
 
@@ -99,7 +163,13 @@
99
163
  font-weight: var(--xh-menu-item-active-font-weight, var(--xh-font-weight-semibold));
100
164
  }
101
165
 
102
- [data-scope='menu'][data-part='item']:focus {
166
+ /* 键盘落焦的条目画内收环;指针触发的聚焦不画 */
167
+ [data-scope='menu'][data-part='item']:focus-visible {
168
+ outline: var(--xh-ring-width) solid var(--xh-ring-focus);
169
+ outline-offset: calc(-1 * var(--xh-ring-width));
170
+ }
171
+
172
+ [data-scope='menu'][data-part='item']:focus:not(:focus-visible) {
103
173
  outline: none;
104
174
  }
105
175
 
@@ -108,52 +178,67 @@
108
178
  cursor: not-allowed;
109
179
  }
110
180
 
181
+ /* 带子菜单的条目行尾画右向小箭头:不点开也看得出还有下一级。
182
+ 位置靠 margin 顶到行尾,图形取兜底字形令牌 */
183
+ [data-scope='menu'][data-part='item'][aria-haspopup='menu']::after {
184
+ content: '';
185
+ flex: none;
186
+ margin-inline-start: auto;
187
+ inline-size: var(--xh-icon-size, var(--xh-glyph-size-text));
188
+ block-size: var(--xh-icon-size, var(--xh-glyph-size-text));
189
+ background-color: currentColor;
190
+ -webkit-mask: var(--xh-glyph-mark-chevron-right) center / contain no-repeat;
191
+ mask: var(--xh-glyph-mark-chevron-right) center / contain no-repeat;
192
+ }
193
+
194
+ /* rtl 下子菜单开在左侧,箭头跟着指向行进方向 */
195
+ [dir='rtl'] [data-scope='menu'][data-part='item'][aria-haspopup='menu']::after {
196
+ -webkit-mask-image: var(--xh-glyph-mark-chevron-left);
197
+ mask-image: var(--xh-glyph-mark-chevron-left);
198
+ }
199
+
200
+ [data-scope='menu'][data-part='group'] {
201
+ display: flex;
202
+ flex-direction: column;
203
+ }
204
+
205
+ [data-scope='menu'][data-part='group'][hidden] {
206
+ display: none;
207
+ }
208
+
209
+ /* 分组标题不是条目、进不了导航,画成小号次要文字,
210
+ 内缩与条目对齐(同一个 padding-inline),标题与条目文字才在一条竖线上 */
211
+ [data-scope='menu'][data-part='group-label'] {
212
+ display: block;
213
+ padding-block: var(--xh-menu-group-label-py, var(--xh-space-1));
214
+ padding-inline: var(--xh-menu-group-label-px, var(--xh-_menu-item-px));
215
+ color: var(--xh-menu-group-label-fg, var(--xh-fg-subtle));
216
+ font-size: var(--xh-menu-group-label-font-size, var(--xh-text-caption-size));
217
+ font-weight: var(--xh-menu-group-label-font-weight, var(--xh-font-weight-medium));
218
+ line-height: var(--xh-leading-none);
219
+ user-select: none;
220
+ }
221
+
222
+ [data-scope='menu'][data-part='group-label'][hidden] {
223
+ display: none;
224
+ }
225
+
111
226
  [data-scope='menu'][data-part='separator'] {
112
227
  block-size: var(--xh-menu-separator-thickness, var(--xh-stroke-thin));
113
228
  margin-block: var(--xh-menu-separator-my, var(--xh-space-1));
114
229
  background: var(--xh-menu-separator-color, var(--xh-border-default));
115
230
  }
116
231
 
117
- /* 箭头是旋转 45° 的方块,只保留朝向锚点那两条边的描边;
232
+ /* 箭头的底色与描边色,几何在 overlay-arrow.css。
118
233
  交叉轴落点由定位引擎经内联自定义属性写进来,没给时退回居中 */
119
234
  [data-scope='menu'][data-part='arrow'] {
120
- --xh-_menu-arrow-size: var(--xh-menu-arrow-size, var(--xh-overlay-arrow-size));
121
235
  --xh-_menu-arrow-x: 50%;
122
236
  --xh-_menu-arrow-y: 50%;
237
+ --xh-_overlay-arrow-size: var(--xh-menu-arrow-size, var(--xh-overlay-arrow-size));
238
+ --xh-_overlay-arrow-x: var(--xh-_menu-arrow-x);
239
+ --xh-_overlay-arrow-y: var(--xh-_menu-arrow-y);
123
240
 
124
- position: absolute;
125
- inline-size: var(--xh-_menu-arrow-size);
126
- block-size: var(--xh-_menu-arrow-size);
127
241
  background: var(--xh-menu-bg, var(--xh-bg-surface));
128
242
  border: var(--xh-stroke-thin) solid var(--xh-menu-border, var(--xh-border-default));
129
- rotate: 45deg;
130
- }
131
-
132
- [data-scope='menu'][data-part='arrow'][data-placement^='bottom'] {
133
- inset-block-start: calc(var(--xh-_menu-arrow-size) / -2);
134
- inset-inline-start: calc(var(--xh-_menu-arrow-x) - var(--xh-_menu-arrow-size) / 2);
135
- border-block-end: 0;
136
- border-inline-end: 0;
137
- }
138
-
139
- [data-scope='menu'][data-part='arrow'][data-placement^='top'] {
140
- inset-block-end: calc(var(--xh-_menu-arrow-size) / -2);
141
- inset-inline-start: calc(var(--xh-_menu-arrow-x) - var(--xh-_menu-arrow-size) / 2);
142
- border-block-start: 0;
143
- border-inline-start: 0;
144
- }
145
-
146
- [data-scope='menu'][data-part='arrow'][data-placement^='right'] {
147
- inset-inline-start: calc(var(--xh-_menu-arrow-size) / -2);
148
- inset-block-start: calc(var(--xh-_menu-arrow-y) - var(--xh-_menu-arrow-size) / 2);
149
- border-block-start: 0;
150
- border-inline-end: 0;
151
- }
152
-
153
- [data-scope='menu'][data-part='arrow'][data-placement^='left'] {
154
- inset-inline-end: calc(var(--xh-_menu-arrow-size) / -2);
155
- inset-block-start: calc(var(--xh-_menu-arrow-y) - var(--xh-_menu-arrow-size) / 2);
156
- border-block-end: 0;
157
- border-inline-start: 0;
158
243
  }
159
244
  }