@xihan-ui/styles 1.0.0-alpha.2 → 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 (118) hide show
  1. package/CHANGELOG.md +447 -0
  2. package/README.md +1 -1
  3. package/css/accordion.css +56 -5
  4. package/css/alert.css +30 -5
  5. package/css/anchor.css +7 -7
  6. package/css/avatar-group.css +3 -3
  7. package/css/avatar.css +3 -3
  8. package/css/back-top.css +16 -1
  9. package/css/badge.css +16 -2
  10. package/css/breadcrumb.css +24 -4
  11. package/css/button-group.css +14 -13
  12. package/css/button.css +42 -11
  13. package/css/calendar.css +119 -2
  14. package/css/card.css +5 -5
  15. package/css/carousel.css +36 -5
  16. package/css/cascader.css +183 -102
  17. package/css/checkbox-group.css +41 -19
  18. package/css/checkbox.css +54 -8
  19. package/css/clipboard.css +2 -1
  20. package/css/code-block.css +12 -7
  21. package/css/collapsible.css +42 -2
  22. package/css/color-picker.css +111 -45
  23. package/css/combobox.css +114 -53
  24. package/css/composer.css +5 -3
  25. package/css/context-menu.css +97 -69
  26. package/css/date-field.css +91 -14
  27. package/css/date-picker.css +276 -65
  28. package/css/descriptions.css +2 -2
  29. package/css/dialog.css +40 -13
  30. package/css/download-trigger.css +76 -0
  31. package/css/drawer.css +45 -14
  32. package/css/dynamic-input.css +68 -17
  33. package/css/editable.css +12 -10
  34. package/css/ellipsis.css +1 -1
  35. package/css/empty-state.css +7 -7
  36. package/css/field.css +9 -6
  37. package/css/fieldset.css +69 -0
  38. package/css/file-upload.css +45 -7
  39. package/css/float-button.css +16 -1
  40. package/css/floating-panel.css +310 -0
  41. package/css/form.css +4 -4
  42. package/css/heatmap.css +410 -0
  43. package/css/highlight.css +1 -1
  44. package/css/hotkeys.css +90 -0
  45. package/css/hover-card.css +40 -45
  46. package/css/image-cropper.css +231 -0
  47. package/css/image-viewer.css +137 -12
  48. package/css/image.css +2 -2
  49. package/css/infinite-scroll.css +1 -1
  50. package/css/json-viewer.css +241 -0
  51. package/css/layout.css +6 -10
  52. package/css/list.css +8 -8
  53. package/css/listbox.css +20 -4
  54. package/css/loading-bar.css +7 -1
  55. package/css/log.css +1 -1
  56. package/css/marquee.css +9 -0
  57. package/css/masonry.css +63 -0
  58. package/css/mention.css +87 -33
  59. package/css/menu.css +113 -47
  60. package/css/menubar.css +146 -60
  61. package/css/navigation-menu.css +54 -29
  62. package/css/number-animation.css +3 -3
  63. package/css/number-field.css +173 -16
  64. package/css/overlay-arrow.css +51 -0
  65. package/css/page-header.css +5 -5
  66. package/css/pagination.css +13 -13
  67. package/css/password-input.css +356 -0
  68. package/css/pin-input.css +13 -10
  69. package/css/popconfirm.css +46 -10
  70. package/css/popover.css +70 -45
  71. package/css/popselect.css +183 -53
  72. package/css/progress.css +47 -7
  73. package/css/qr-code.css +1 -1
  74. package/css/radio-group.css +14 -8
  75. package/css/rating.css +2 -2
  76. package/css/reset.css +17 -0
  77. package/css/result.css +6 -6
  78. package/css/scroll-area.css +61 -89
  79. package/css/scrollbar.css +167 -0
  80. package/css/segmented.css +215 -0
  81. package/css/select.css +274 -125
  82. package/css/separator.css +2 -2
  83. package/css/side-nav.css +111 -19
  84. package/css/signature-pad.css +144 -0
  85. package/css/skeleton.css +7 -1
  86. package/css/slider.css +10 -10
  87. package/css/space.css +117 -0
  88. package/css/spinner.css +22 -10
  89. package/css/statistic.css +9 -9
  90. package/css/steps.css +25 -8
  91. package/css/switch.css +48 -6
  92. package/css/table.css +131 -37
  93. package/css/tabs.css +4 -4
  94. package/css/tag.css +177 -0
  95. package/css/tags-input.css +78 -40
  96. package/css/text-field.css +149 -36
  97. package/css/thread.css +1 -1
  98. package/css/time-field.css +97 -16
  99. package/css/time-picker.css +259 -69
  100. package/css/timeline.css +10 -10
  101. package/css/timer.css +119 -0
  102. package/css/toast.css +34 -10
  103. package/css/toaster.css +1 -1
  104. package/css/toggle.css +6 -6
  105. package/css/tone.css +10 -5
  106. package/css/toolbar.css +1 -1
  107. package/css/tooltip.css +36 -33
  108. package/css/tour.css +103 -58
  109. package/css/transfer.css +68 -17
  110. package/css/tree-select.css +185 -84
  111. package/css/tree.css +85 -1
  112. package/css/typography.css +4 -4
  113. package/css/undefined.css +11 -1
  114. package/css/virtualizer.css +3 -1
  115. package/css/watermark.css +1 -2
  116. package/index.css +17 -0
  117. package/index.unlayered.css +8659 -2799
  118. package/package.json +19 -3
package/css/mention.css CHANGED
@@ -1,13 +1,26 @@
1
1
  @layer xihan.motion {
2
2
  /* 动画名的查找只认「文档里有没有这个名字」:引用别处文件里的名字,那份文件不一定在场,
3
3
  动画会静默地整个不跑。每份皮肤都能单独引入,所以它用到的关键帧都写在自己身上。 */
4
- @keyframes xh-fade-in {
4
+ @keyframes xh-pop-in {
5
5
  from {
6
6
  opacity: 0;
7
+ scale: var(--xh-motion-scale-enter);
7
8
  }
8
9
 
9
10
  to {
10
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);
11
24
  }
12
25
  }
13
26
  }
@@ -18,9 +31,27 @@
18
31
  --xh-mention-skin: 1;
19
32
  }
20
33
 
21
- [data-scope='mention'][data-part='root'] {
22
- /* 尺寸档只换这四个私有槽,输入框与候选都从 root 继承它们,换档不必给每个部件各写一条选择器。
23
- 浮层在 positioner 里、也是 root 的后代,一并继承 */
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);
24
55
  --xh-_mention-px: var(--xh-control-px-md);
25
56
  --xh-_mention-py: var(--xh-field-py);
26
57
  --xh-_mention-gap: var(--xh-control-gap-md);
@@ -33,7 +64,9 @@
33
64
 
34
65
  /* 聚焦环恒用固定环色,不随语气走 */
35
66
  --xh-_mention-ring: var(--xh-ring-focus);
67
+ }
36
68
 
69
+ [data-scope='mention'][data-part='root'] {
37
70
  display: block;
38
71
  }
39
72
 
@@ -44,37 +77,39 @@
44
77
 
45
78
  /* —— 尺寸 —— */
46
79
  /* 缺省档写在上面的基础规则里 */
47
- [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);
48
82
  --xh-_mention-px: var(--xh-control-px-sm);
49
83
  --xh-_mention-py: var(--xh-space-1);
50
84
  --xh-_mention-gap: var(--xh-control-gap-sm);
51
- --xh-_mention-font-size: var(--xh-font-size-sm);
85
+ --xh-_mention-font-size: var(--xh-control-font-sm);
52
86
  }
53
87
 
54
- [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);
55
90
  --xh-_mention-px: var(--xh-control-px-lg);
56
91
  --xh-_mention-py: var(--xh-space-3);
57
92
  --xh-_mention-gap: var(--xh-control-gap-lg);
58
- --xh-_mention-font-size: var(--xh-font-size-lg);
93
+ --xh-_mention-font-size: var(--xh-control-font-lg);
59
94
  }
60
95
 
61
96
  /* —— 形态 —— */
62
97
  /* 输入类不做 solid:实心底压着可编辑的正文,读起来像块不可点的色板。
63
98
  形态只决定描边与底色怎么用,用哪族颜色由 data-tone 决定 */
64
- [data-scope='mention'][data-part='root'][data-variant='outline'] {
99
+ [data-scope='mention']:is([data-part='root'], [data-part='positioner'])[data-variant='outline'] {
65
100
  --xh-_mention-input-bg: var(--xh-bg-canvas);
66
101
  --xh-_mention-input-border: var(--xh-border-control);
67
102
  --xh-_mention-input-border-hover: var(--xh-border-control-hover);
68
103
  }
69
104
 
70
105
  /* 填充式:底色顶替描边,悬停时才浮出一道淡边 */
71
- [data-scope='mention'][data-part='root'][data-variant='subtle'] {
106
+ [data-scope='mention']:is([data-part='root'], [data-part='positioner'])[data-variant='subtle'] {
72
107
  --xh-_mention-input-bg: var(--xh-_tone-subtle, var(--xh-bg-subtle));
73
108
  --xh-_mention-input-border: transparent;
74
109
  --xh-_mention-input-border-hover: var(--xh-_tone-border, var(--xh-border-control-hover));
75
110
  }
76
111
 
77
- [data-scope='mention'][data-part='root'][data-variant='ghost'] {
112
+ [data-scope='mention']:is([data-part='root'], [data-part='positioner'])[data-variant='ghost'] {
78
113
  --xh-_mention-input-bg: transparent;
79
114
  --xh-_mention-input-border: transparent;
80
115
  --xh-_mention-input-border-hover: var(--xh-_tone-border, var(--xh-border-control-hover));
@@ -85,6 +120,9 @@
85
120
  [data-scope='mention'][data-part='input'] {
86
121
  display: block;
87
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));
88
126
  padding-block: var(--xh-mention-input-py, var(--xh-_mention-py));
89
127
  padding-inline: var(--xh-mention-input-px, var(--xh-_mention-px));
90
128
  border: var(--xh-stroke-thin) solid var(--xh-mention-input-border, var(--xh-_mention-input-border));
@@ -100,6 +138,13 @@
100
138
  transition: border-color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
101
139
  }
102
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
+
103
148
  [data-scope='mention'][data-part='input'][hidden] {
104
149
  display: none;
105
150
  }
@@ -108,16 +153,28 @@
108
153
  color: var(--xh-mention-placeholder-fg, var(--xh-fg-subtle));
109
154
  }
110
155
 
111
- [data-scope='mention'][data-part='input']:hover:not(:disabled) {
156
+ [data-scope='mention'][data-part='input']:hover:not(:disabled, [data-invalid]) {
112
157
  border-color: var(--xh-mention-input-border-hover, var(--xh-_mention-input-border-hover));
113
158
  }
114
159
 
115
160
  [data-scope='mention'][data-part='input']:focus-visible {
116
- 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)));
117
162
  outline: var(--xh-ring-width) solid var(--xh-_mention-ring);
118
163
  outline-offset: var(--xh-ring-offset);
119
164
  }
120
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
+
121
178
  /* 输入框是单体表单控件,禁用走原生 disabled(候选那边一律 aria-disabled) */
122
179
  [data-scope='mention'][data-part='input']:disabled {
123
180
  background: var(--xh-mention-input-bg-disabled, var(--xh-bg-subtle));
@@ -127,40 +184,28 @@
127
184
  cursor: not-allowed;
128
185
  }
129
186
 
130
- /* 坐标由定位引擎写进 style(inset-inline-start / inset-block-start),这里只定位模式与层级 */
131
- [data-scope='mention'][data-part='positioner'] {
132
- position: fixed;
133
- z-index: var(--xh-layer-popover);
134
- /* 空壳不吃指针,交互只发生在 content 上 */
135
- pointer-events: none;
136
- }
137
-
138
- /* 输入框被滚出可视区:整个浮层随之收起 */
139
- [data-scope='mention'][data-part='positioner'][data-hidden] {
140
- visibility: hidden;
141
- }
142
-
143
187
  [data-scope='mention'][data-part='content'] {
144
188
  display: flex;
145
189
  flex-direction: column;
146
190
  min-inline-size: var(--xh-mention-content-min-w, var(--xh-overlay-min-w));
147
191
  max-inline-size: var(--xh-mention-content-max-w, var(--xh-overlay-max-w));
148
- 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));
149
193
  padding-block: var(--xh-mention-content-py, var(--xh-space-1));
150
194
  padding-inline: var(--xh-mention-content-px, var(--xh-space-1));
151
195
  border: var(--xh-stroke-thin) solid var(--xh-mention-content-border, var(--xh-border-default));
152
196
  border-radius: var(--xh-mention-content-radius, var(--xh-shape-surface));
153
197
  background: var(--xh-mention-content-bg, var(--xh-bg-surface));
154
198
  color: var(--xh-mention-content-fg, var(--xh-fg-default));
155
- box-shadow: var(--xh-mention-content-shadow, var(--xh-elevation-2));
199
+ box-shadow: var(--xh-mention-content-shadow, var(--xh-elevation-floating));
156
200
  overflow-y: auto;
157
201
  pointer-events: auto;
158
202
  }
159
203
 
160
- /* display 会盖掉 hidden 属性的 UA 规则,收起态显式让位 */
161
- [data-scope='mention'][data-part='content'][hidden] {
162
- display: none;
163
- }
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],那几处没有退场动画。 */
164
209
 
165
210
  /* 宿主把候选全筛没了:浮层还开着,但这一副框壳没有内容可框,整个让位。
166
211
  组件不替宿主判空——「没有匹配项」要不要说话、说什么,是宿主的事 */
@@ -175,7 +220,13 @@
175
220
  }
176
221
 
177
222
  [data-scope='mention'][data-part='content'][data-state='open'] {
178
- 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;
179
230
  }
180
231
 
181
232
  [data-scope='mention'][data-part='item'] {
@@ -205,6 +256,9 @@
205
256
  两套底色各画各的会出现"鼠标停在 A 上、回车却插进了 B" */
206
257
  [data-scope='mention'][data-part='item'][data-highlighted]:not([data-disabled]) {
207
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));
208
262
  }
209
263
 
210
264
  /* 禁用走 aria-disabled 而非原生 disabled(原生 disabled 不派 click,禁用守卫就走不到),
package/css/menu.css CHANGED
@@ -1,13 +1,26 @@
1
1
  @layer xihan.motion {
2
2
  /* 动画名的查找只认「文档里有没有这个名字」:引用别处文件里的名字,那份文件不一定在场,
3
3
  动画会静默地整个不跑。每份皮肤都能单独引入,所以它用到的关键帧都写在自己身上。 */
4
- @keyframes xh-fade-in {
4
+ @keyframes xh-pop-in {
5
5
  from {
6
6
  opacity: 0;
7
+ scale: var(--xh-motion-scale-enter);
7
8
  }
8
9
 
9
10
  to {
10
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);
11
24
  }
12
25
  }
13
26
  }
@@ -20,8 +33,12 @@
20
33
 
21
34
  /* 坐标由定位引擎写进 style(inset-inline-start / inset-block-start),这里只定位模式与层级 */
22
35
  [data-scope='menu'][data-part='positioner'] {
36
+ /* 落定那一侧的可用高度,由定位引擎经内联自定义属性写进来,content 继承它限高;
37
+ 引擎没算(尚未落位 / 无引擎)时不额外限高 */
38
+ --xh-_menu-available-h: 100vh;
39
+
23
40
  position: fixed;
24
- z-index: var(--xh-layer-popover);
41
+ z-index: var(--xh-menu-layer, var(--xh-layer-popover));
25
42
  /* 空壳不吃指针,交互只发生在 content 上 */
26
43
  pointer-events: none;
27
44
  }
@@ -31,7 +48,23 @@
31
48
  visibility: hidden;
32
49
  }
33
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
+
34
64
  [data-scope='menu'][data-part='content'] {
65
+ /* 作者塞进条目里的图标跟着字号走,与皮肤画的兜底字形同一把尺 */
66
+ --xh-icon-size: var(--xh-menu-icon-size, var(--xh-glyph-size-text));
67
+
35
68
  /* 尺寸档只换这四个私有槽,条目从 content 继承它们,换档不必给每个部件各写一条选择器。
36
69
  菜单没有 root 部件(浮层树的最外层就是 content),三轴属性都落在这里 */
37
70
  --xh-_menu-item-py: var(--xh-space-1_5);
@@ -47,20 +80,26 @@
47
80
  flex-direction: column;
48
81
  min-inline-size: var(--xh-menu-min-w, var(--xh-overlay-menu-min-w));
49
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));
50
86
  padding-block: var(--xh-menu-py, var(--xh-space-1));
51
87
  padding-inline: var(--xh-menu-px, var(--xh-space-1));
52
88
  border: var(--xh-stroke-thin) solid var(--xh-menu-border, var(--xh-border-default));
53
89
  border-radius: var(--xh-menu-radius, var(--xh-shape-surface));
54
90
  background: var(--xh-menu-bg, var(--xh-bg-surface));
55
91
  color: var(--xh-menu-fg, var(--xh-fg-default));
56
- 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;
57
95
  pointer-events: auto;
58
96
  }
59
97
 
60
- /* display 会盖掉 hidden 属性的 UA 规则,收起态显式让位 */
61
- [data-scope='menu'][data-part='content'][hidden] {
62
- display: none;
63
- }
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],那几处没有退场动画。 */
64
103
 
65
104
  /* 容器只是焦点域兜底落点,落焦不画环:高亮永远长在条目上 */
66
105
  [data-scope='menu'][data-part='content']:focus-visible {
@@ -68,7 +107,13 @@
68
107
  }
69
108
 
70
109
  [data-scope='menu'][data-part='content'][data-state='open'] {
71
- 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;
72
117
  }
73
118
 
74
119
  /* —— 尺寸 —— */
@@ -78,14 +123,14 @@
78
123
  --xh-_menu-item-py: var(--xh-space-1);
79
124
  --xh-_menu-item-px: var(--xh-control-px-sm);
80
125
  --xh-_menu-item-gap: var(--xh-control-gap-sm);
81
- --xh-_menu-font-size: var(--xh-font-size-sm);
126
+ --xh-_menu-font-size: var(--xh-control-font-sm);
82
127
  }
83
128
 
84
129
  [data-scope='menu'][data-part='content'][data-size='lg'] {
85
130
  --xh-_menu-item-py: var(--xh-space-2);
86
131
  --xh-_menu-item-px: var(--xh-control-px-lg);
87
132
  --xh-_menu-item-gap: var(--xh-control-gap-lg);
88
- --xh-_menu-font-size: var(--xh-font-size-lg);
133
+ --xh-_menu-font-size: var(--xh-control-font-lg);
89
134
  }
90
135
 
91
136
  [data-scope='menu'][data-part='item'] {
@@ -106,8 +151,8 @@
106
151
  color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
107
152
  }
108
153
 
109
- /* 悬停与键盘焦点共用中性灰轻档:条目本身承载焦点,不另画描边 */
110
- [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]) {
111
156
  background: var(--xh-menu-item-bg-hover, var(--xh-bg-subtle));
112
157
  }
113
158
 
@@ -118,7 +163,13 @@
118
163
  font-weight: var(--xh-menu-item-active-font-weight, var(--xh-font-weight-semibold));
119
164
  }
120
165
 
121
- [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) {
122
173
  outline: none;
123
174
  }
124
175
 
@@ -127,52 +178,67 @@
127
178
  cursor: not-allowed;
128
179
  }
129
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
+
130
226
  [data-scope='menu'][data-part='separator'] {
131
227
  block-size: var(--xh-menu-separator-thickness, var(--xh-stroke-thin));
132
228
  margin-block: var(--xh-menu-separator-my, var(--xh-space-1));
133
229
  background: var(--xh-menu-separator-color, var(--xh-border-default));
134
230
  }
135
231
 
136
- /* 箭头是旋转 45° 的方块,只保留朝向锚点那两条边的描边;
232
+ /* 箭头的底色与描边色,几何在 overlay-arrow.css。
137
233
  交叉轴落点由定位引擎经内联自定义属性写进来,没给时退回居中 */
138
234
  [data-scope='menu'][data-part='arrow'] {
139
- --xh-_menu-arrow-size: var(--xh-menu-arrow-size, var(--xh-overlay-arrow-size));
140
235
  --xh-_menu-arrow-x: 50%;
141
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);
142
240
 
143
- position: absolute;
144
- inline-size: var(--xh-_menu-arrow-size);
145
- block-size: var(--xh-_menu-arrow-size);
146
241
  background: var(--xh-menu-bg, var(--xh-bg-surface));
147
242
  border: var(--xh-stroke-thin) solid var(--xh-menu-border, var(--xh-border-default));
148
- rotate: 45deg;
149
- }
150
-
151
- [data-scope='menu'][data-part='arrow'][data-placement^='bottom'] {
152
- inset-block-start: calc(var(--xh-_menu-arrow-size) / -2);
153
- inset-inline-start: calc(var(--xh-_menu-arrow-x) - var(--xh-_menu-arrow-size) / 2);
154
- border-block-end: 0;
155
- border-inline-end: 0;
156
- }
157
-
158
- [data-scope='menu'][data-part='arrow'][data-placement^='top'] {
159
- inset-block-end: calc(var(--xh-_menu-arrow-size) / -2);
160
- inset-inline-start: calc(var(--xh-_menu-arrow-x) - var(--xh-_menu-arrow-size) / 2);
161
- border-block-start: 0;
162
- border-inline-start: 0;
163
- }
164
-
165
- [data-scope='menu'][data-part='arrow'][data-placement^='right'] {
166
- inset-inline-start: calc(var(--xh-_menu-arrow-size) / -2);
167
- inset-block-start: calc(var(--xh-_menu-arrow-y) - var(--xh-_menu-arrow-size) / 2);
168
- border-block-start: 0;
169
- border-inline-end: 0;
170
- }
171
-
172
- [data-scope='menu'][data-part='arrow'][data-placement^='left'] {
173
- inset-inline-end: calc(var(--xh-_menu-arrow-size) / -2);
174
- inset-block-start: calc(var(--xh-_menu-arrow-y) - var(--xh-_menu-arrow-size) / 2);
175
- border-block-end: 0;
176
- border-inline-start: 0;
177
243
  }
178
244
  }