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

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 (58) hide show
  1. package/CHANGELOG.md +261 -0
  2. package/css/back-top.css +2 -2
  3. package/css/button-group.css +1 -1
  4. package/css/button.css +1 -1
  5. package/css/calendar.css +1 -1
  6. package/css/carousel.css +2 -2
  7. package/css/cascader.css +199 -24
  8. package/css/checkbox-group.css +20 -20
  9. package/css/checkbox.css +1 -1
  10. package/css/clipboard.css +3 -3
  11. package/css/color-picker.css +21 -5
  12. package/css/combobox.css +23 -20
  13. package/css/composer.css +1 -1
  14. package/css/context-menu.css +56 -26
  15. package/css/date-field.css +5 -5
  16. package/css/date-picker.css +66 -2
  17. package/css/drawer.css +3 -3
  18. package/css/dynamic-input.css +2 -2
  19. package/css/editable.css +6 -6
  20. package/css/field.css +32 -1
  21. package/css/file-upload.css +3 -3
  22. package/css/float-button.css +2 -2
  23. package/css/form.css +17 -5
  24. package/css/hover-card.css +15 -11
  25. package/css/image-viewer.css +176 -0
  26. package/css/listbox.css +9 -6
  27. package/css/mention.css +14 -22
  28. package/css/menu.css +38 -23
  29. package/css/menubar.css +28 -17
  30. package/css/number-field.css +16 -16
  31. package/css/pin-input.css +6 -6
  32. package/css/popconfirm.css +23 -0
  33. package/css/popover.css +15 -11
  34. package/css/popselect.css +31 -16
  35. package/css/progress.css +68 -1
  36. package/css/radio-group.css +1 -1
  37. package/css/select.css +148 -17
  38. package/css/side-nav.css +163 -0
  39. package/css/slider.css +55 -0
  40. package/css/steps.css +2 -2
  41. package/css/switch.css +27 -0
  42. package/css/table.css +6 -6
  43. package/css/tags-input.css +7 -7
  44. package/css/text-field.css +23 -10
  45. package/css/thread.css +1 -1
  46. package/css/time-field.css +5 -5
  47. package/css/time-picker.css +36 -26
  48. package/css/toggle-group.css +1 -1
  49. package/css/toggle.css +1 -1
  50. package/css/tone.css +22 -0
  51. package/css/tooltip.css +20 -15
  52. package/css/tour.css +16 -12
  53. package/css/transfer.css +11 -9
  54. package/css/tree-select.css +26 -18
  55. package/css/tree.css +8 -5
  56. package/index.css +6 -2
  57. package/index.unlayered.css +1499 -413
  58. package/package.json +4 -2
@@ -120,6 +120,29 @@
120
120
  color: var(--xh-popconfirm-confirm-fg, var(--xh-_tone-on, var(--xh-fg-on-brand)));
121
121
  }
122
122
 
123
+ /* 转圈的关键帧定义在本文件里:引用别处文件的动画名时那份文件不一定在场,动画会静默不跑 */
124
+ @keyframes xh-popconfirm-rotate {
125
+ to {
126
+ transform: rotate(1turn);
127
+ }
128
+ }
129
+
130
+ /* 异步确认挂起:按钮转圈、指针挂起;不是禁用,焦点与对比度都保持 */
131
+ [data-scope='popconfirm'][data-part='confirm-trigger'][data-loading] {
132
+ cursor: wait;
133
+ }
134
+
135
+ [data-scope='popconfirm'][data-part='confirm-trigger'][data-loading]::before {
136
+ content: '';
137
+ inline-size: 1em;
138
+ block-size: 1em;
139
+ margin-inline-end: var(--xh-space-1);
140
+ border: var(--xh-stroke-thin) solid transparent;
141
+ border-block-start-color: currentcolor;
142
+ border-radius: var(--xh-shape-pill);
143
+ animation: xh-popconfirm-rotate var(--xh-popconfirm-loading-duration, calc(var(--xh-duration-slow) * 2)) linear infinite;
144
+ }
145
+
123
146
  [data-scope='popconfirm'][data-part='confirm-trigger']:hover {
124
147
  background: var(--xh-_tone-hover, var(--xh-bg-brand-hover));
125
148
  }
package/css/popover.css CHANGED
@@ -110,40 +110,44 @@
110
110
  }
111
111
 
112
112
  /* 箭头是旋转 45° 的方块,只保留朝向锚点那两条边的描边;
113
- 引擎不产出箭头偏移,因此沿交叉轴固定居中 */
113
+ 交叉轴落点由定位引擎经内联自定义属性写进来,没给时退回居中 */
114
114
  [data-scope='popover'][data-part='arrow'] {
115
+ --xh-_popover-arrow-size: var(--xh-popover-arrow-size, var(--xh-overlay-arrow-size));
116
+ --xh-_popover-arrow-x: 50%;
117
+ --xh-_popover-arrow-y: 50%;
118
+
115
119
  position: absolute;
116
- inline-size: var(--xh-popover-arrow-size, var(--xh-overlay-arrow-size));
117
- block-size: var(--xh-popover-arrow-size, var(--xh-overlay-arrow-size));
120
+ inline-size: var(--xh-_popover-arrow-size);
121
+ block-size: var(--xh-_popover-arrow-size);
118
122
  background: var(--xh-popover-bg, var(--xh-bg-surface));
119
123
  border: var(--xh-stroke-thin) solid var(--xh-popover-border, var(--xh-border-default));
120
124
  rotate: 45deg;
121
125
  }
122
126
 
123
127
  [data-scope='popover'][data-part='arrow'][data-placement^='bottom'] {
124
- inset-block-start: calc(var(--xh-popover-arrow-size, var(--xh-overlay-arrow-size)) / -2);
125
- inset-inline-start: calc(50% - var(--xh-popover-arrow-size, var(--xh-overlay-arrow-size)) / 2);
128
+ inset-block-start: calc(var(--xh-_popover-arrow-size) / -2);
129
+ inset-inline-start: calc(var(--xh-_popover-arrow-x) - var(--xh-_popover-arrow-size) / 2);
126
130
  border-block-end: 0;
127
131
  border-inline-end: 0;
128
132
  }
129
133
 
130
134
  [data-scope='popover'][data-part='arrow'][data-placement^='top'] {
131
- inset-block-end: calc(var(--xh-popover-arrow-size, var(--xh-overlay-arrow-size)) / -2);
132
- inset-inline-start: calc(50% - var(--xh-popover-arrow-size, var(--xh-overlay-arrow-size)) / 2);
135
+ inset-block-end: calc(var(--xh-_popover-arrow-size) / -2);
136
+ inset-inline-start: calc(var(--xh-_popover-arrow-x) - var(--xh-_popover-arrow-size) / 2);
133
137
  border-block-start: 0;
134
138
  border-inline-start: 0;
135
139
  }
136
140
 
137
141
  [data-scope='popover'][data-part='arrow'][data-placement^='right'] {
138
- inset-inline-start: calc(var(--xh-popover-arrow-size, var(--xh-overlay-arrow-size)) / -2);
139
- inset-block-start: calc(50% - var(--xh-popover-arrow-size, var(--xh-overlay-arrow-size)) / 2);
142
+ inset-inline-start: calc(var(--xh-_popover-arrow-size) / -2);
143
+ inset-block-start: calc(var(--xh-_popover-arrow-y) - var(--xh-_popover-arrow-size) / 2);
140
144
  border-block-start: 0;
141
145
  border-inline-end: 0;
142
146
  }
143
147
 
144
148
  [data-scope='popover'][data-part='arrow'][data-placement^='left'] {
145
- inset-inline-end: calc(var(--xh-popover-arrow-size, var(--xh-overlay-arrow-size)) / -2);
146
- inset-block-start: calc(50% - var(--xh-popover-arrow-size, var(--xh-overlay-arrow-size)) / 2);
149
+ inset-inline-end: calc(var(--xh-_popover-arrow-size) / -2);
150
+ inset-block-start: calc(var(--xh-_popover-arrow-y) - var(--xh-_popover-arrow-size) / 2);
147
151
  border-block-end: 0;
148
152
  border-inline-start: 0;
149
153
  }
package/css/popselect.css CHANGED
@@ -9,14 +9,13 @@
9
9
 
10
10
  /* 形态档只换这三个槽:缺省即 outline(描边 + 画布底) */
11
11
  --xh-_popselect-trigger-bg: var(--xh-bg-canvas);
12
- --xh-_popselect-trigger-border: var(--xh-border-default);
13
- --xh-_popselect-trigger-border-hover: var(--xh-border-strong);
12
+ --xh-_popselect-trigger-border: var(--xh-border-control);
13
+ --xh-_popselect-trigger-border-hover: var(--xh-border-control-hover);
14
14
 
15
- /* 强调色随语气走,只落在聚焦与选中上,正文文字色不动。
16
- 没写 data-tone 时 --xh-_tone 没被声明,三处都退回原来的取值 */
15
+ /* 聚焦环恒用固定环色;强调色随语气走,只落在勾选标记与选中文字上,正文文字色不动。
16
+ 没写 data-tone 时 --xh-_tone 没被声明,两处都退回原来的取值 */
17
17
  --xh-_popselect-ring: var(--xh-ring-focus);
18
18
  --xh-_popselect-accent: var(--xh-_tone, var(--xh-bg-brand));
19
- --xh-_popselect-item-highlight-bg: var(--xh-_tone-subtle-hover, var(--xh-bg-subtle-hover));
20
19
  --xh-_popselect-item-selected-fg: var(--xh-_tone-fg, var(--xh-fg-brand-strong));
21
20
 
22
21
  display: inline-flex;
@@ -42,21 +41,21 @@
42
41
  /* 形态只决定描边与底色怎么用,用哪族颜色由 data-tone 决定 */
43
42
  [data-scope='popselect'][data-part='root'][data-variant='outline'] {
44
43
  --xh-_popselect-trigger-bg: var(--xh-bg-canvas);
45
- --xh-_popselect-trigger-border: var(--xh-border-default);
46
- --xh-_popselect-trigger-border-hover: var(--xh-border-strong);
44
+ --xh-_popselect-trigger-border: var(--xh-border-control);
45
+ --xh-_popselect-trigger-border-hover: var(--xh-border-control-hover);
47
46
  }
48
47
 
49
48
  /* 填充式:底色顶替描边,悬停时才浮出一道淡边 */
50
49
  [data-scope='popselect'][data-part='root'][data-variant='subtle'] {
51
50
  --xh-_popselect-trigger-bg: var(--xh-_tone-subtle, var(--xh-bg-subtle));
52
51
  --xh-_popselect-trigger-border: transparent;
53
- --xh-_popselect-trigger-border-hover: var(--xh-_tone-border, var(--xh-border-default));
52
+ --xh-_popselect-trigger-border-hover: var(--xh-_tone-border, var(--xh-border-control-hover));
54
53
  }
55
54
 
56
55
  [data-scope='popselect'][data-part='root'][data-variant='ghost'] {
57
56
  --xh-_popselect-trigger-bg: transparent;
58
57
  --xh-_popselect-trigger-border: transparent;
59
- --xh-_popselect-trigger-border-hover: var(--xh-_tone-border, var(--xh-border-default));
58
+ --xh-_popselect-trigger-border-hover: var(--xh-_tone-border, var(--xh-border-control-hover));
60
59
  }
61
60
 
62
61
  [data-scope='popselect'][data-part='trigger'] {
@@ -130,8 +129,14 @@
130
129
  display: none;
131
130
  }
132
131
 
133
- /* 容器只是焦点域兜底落点,落焦不画环:高亮永远长在条目上 */
134
- [data-scope='popselect'][data-part='content']:focus-visible {
132
+ /* 焦点落在容器上只有一种情形:指针打开、无选中值、还没有任何条目当锚点。
133
+ 那一刻它认领着 Tab 位,环画在浮层内沿;有锚点时环长在条目上,容器不再画 */
134
+ [data-scope='popselect'][data-part='content'][tabindex='0']:focus-visible {
135
+ outline: var(--xh-ring-width) solid var(--xh-ring-focus);
136
+ outline-offset: calc(-1 * var(--xh-ring-width));
137
+ }
138
+
139
+ [data-scope='popselect'][data-part='content']:not([tabindex='0']):focus-visible {
135
140
  outline: none;
136
141
  }
137
142
 
@@ -143,8 +148,8 @@
143
148
  display: flex;
144
149
  align-items: center;
145
150
  gap: var(--xh-popselect-item-gap, var(--xh-_popselect-gap));
146
- padding-block: var(--xh-popselect-item-py, var(--xh-space-1));
147
- padding-inline: var(--xh-popselect-item-px, var(--xh-control-px-sm));
151
+ padding-block: var(--xh-popselect-item-py, var(--xh-space-1_5));
152
+ padding-inline: var(--xh-popselect-item-px, var(--xh-control-px-md));
148
153
  border-radius: var(--xh-popselect-item-radius, var(--xh-shape-control));
149
154
  color: var(--xh-popselect-item-fg, var(--xh-fg-default));
150
155
  font-size: var(--xh-popselect-item-font-size, var(--xh-_popselect-font-size));
@@ -153,11 +158,14 @@
153
158
  white-space: nowrap;
154
159
  cursor: pointer;
155
160
  user-select: none;
161
+ transition:
162
+ background var(--xh-motion-duration-micro) var(--xh-motion-ease-enter),
163
+ color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
156
164
  }
157
165
 
158
- /* 键盘高亮与指针高亮共用一套底色:条目本身承载焦点,不另画描边 */
166
+ /* 悬停与键盘锚点共用中性灰轻档:条目本身承载焦点,不另画描边 */
159
167
  [data-scope='popselect'][data-part='item']:not([data-disabled]):is(:hover, [data-highlighted]) {
160
- background: var(--xh-popselect-item-bg-highlight, var(--xh-_popselect-item-highlight-bg));
168
+ background: var(--xh-popselect-item-bg-hover, var(--xh-bg-subtle));
161
169
  }
162
170
 
163
171
  /* 选中只换文字色与字重,配合行尾的 ✓ 标记;底色整条通道留给高亮,两者因此能同时看见 */
@@ -166,7 +174,14 @@
166
174
  font-weight: var(--xh-popselect-item-font-weight-selected, var(--xh-font-weight-medium));
167
175
  }
168
176
 
169
- [data-scope='popselect'][data-part='item']:focus {
177
+ /* 键盘走到条目上要画环:轻档底色对白底只有 1.1:1,够不上焦点指示器的对比度要求。
178
+ 指针悬停走的是程序化聚焦,鼠标模态下 :focus-visible 不匹配,不会误画 */
179
+ [data-scope='popselect'][data-part='item']:focus-visible {
180
+ outline: var(--xh-ring-width) solid var(--xh-ring-focus);
181
+ outline-offset: calc(-1 * var(--xh-ring-width));
182
+ }
183
+
184
+ [data-scope='popselect'][data-part='item']:focus:not(:focus-visible) {
170
185
  outline: none;
171
186
  }
172
187
 
package/css/progress.css CHANGED
@@ -26,7 +26,7 @@
26
26
  }
27
27
 
28
28
  /* —— 尺寸 —— */
29
- /* 只有厚度分档;中间档不写规则,缺省即中档 */
29
+ /* 线形只有厚度分档;中间档不写规则,缺省即中档 */
30
30
  [data-scope='progress'][data-part='root'][data-size='sm'] {
31
31
  --xh-_thickness: var(--xh-progress-thickness, var(--xh-space-1));
32
32
  }
@@ -34,4 +34,71 @@
34
34
  [data-scope='progress'][data-part='root'][data-size='lg'] {
35
35
  --xh-_thickness: var(--xh-progress-thickness, var(--xh-space-2));
36
36
  }
37
+
38
+ /* —— 环形 —— */
39
+ /* 环画在固定的 100×100 viewBox 里,直径由这里的尺寸决定;
40
+ 画面与环心文字叠在同一格上,文字因此恒在圆心 */
41
+ [data-scope='progress'][data-part='root'][data-variant='circle'],
42
+ [data-scope='progress'][data-part='root'][data-variant='dashboard'] {
43
+ --xh-_diameter: var(--xh-progress-size, 7.5rem);
44
+
45
+ display: inline-grid;
46
+ place-items: center;
47
+ inline-size: var(--xh-_diameter);
48
+ block-size: var(--xh-_diameter);
49
+ }
50
+
51
+ [data-scope='progress'][data-part='root'][data-variant='circle'][data-size='sm'],
52
+ [data-scope='progress'][data-part='root'][data-variant='dashboard'][data-size='sm'] {
53
+ --xh-_diameter: var(--xh-progress-size, 5rem);
54
+ }
55
+
56
+ [data-scope='progress'][data-part='root'][data-variant='circle'][data-size='lg'],
57
+ [data-scope='progress'][data-part='root'][data-variant='dashboard'][data-size='lg'] {
58
+ --xh-_diameter: var(--xh-progress-size, 10rem);
59
+ }
60
+
61
+ [data-scope='progress'][data-part='canvas'],
62
+ [data-scope='progress'][data-part='label'] {
63
+ grid-area: 1 / 1;
64
+ }
65
+
66
+ [data-scope='progress'][data-part='canvas'] {
67
+ inline-size: 100%;
68
+ block-size: 100%;
69
+ }
70
+
71
+ /* 环心文字:占位不撑格子,长内容自己换行由作者定 */
72
+ [data-scope='progress'][data-part='label'] {
73
+ color: var(--xh-progress-label-fg, var(--xh-fg-default));
74
+ font-size: var(--xh-progress-label-font-size, var(--xh-text-body-size));
75
+ line-height: var(--xh-leading-none);
76
+ text-align: center;
77
+ }
78
+
79
+ /* 圆心、半径与那两段虚线都由连接层算好写进内联样式,这里只上色。
80
+ 底槽不跟着语气换族:只有已完成的那段换色,才读得出"完成了多少" */
81
+ [data-scope='progress'][data-part='track'][data-variant='circle'],
82
+ [data-scope='progress'][data-part='track'][data-variant='dashboard'] {
83
+ stroke: var(--xh-progress-track, var(--xh-bg-subtle-active));
84
+ }
85
+
86
+ [data-scope='progress'][data-part='range'][data-variant='circle'],
87
+ [data-scope='progress'][data-part='range'][data-variant='dashboard'] {
88
+ stroke: var(--xh-progress-range, var(--xh-_tone, var(--xh-bg-brand)));
89
+ stroke-linecap: var(--xh-progress-linecap, round);
90
+ transition: stroke-dashoffset var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
91
+ }
92
+
93
+ /* 零进度:圆角端点会在长度为 0 处画出一个圆点,收掉它 */
94
+ [data-scope='progress'][data-part='range'][data-empty] {
95
+ visibility: hidden;
96
+ }
97
+
98
+ /* 关掉动效的用户不该被进度条的补间晃一下;这里只管本组件 */
99
+ @media (prefers-reduced-motion: reduce) {
100
+ [data-scope='progress'][data-part='range'] {
101
+ transition-duration: 0.01ms;
102
+ }
103
+ }
37
104
  }
@@ -69,7 +69,7 @@
69
69
  justify-content: center;
70
70
  inline-size: 16px;
71
71
  block-size: 16px;
72
- border: var(--xh-stroke-thin) solid var(--xh-radio-group-indicator-border, var(--xh-border-default));
72
+ border: var(--xh-stroke-thin) solid var(--xh-radio-group-indicator-border, var(--xh-border-control));
73
73
  border-radius: var(--xh-shape-pill);
74
74
  background: var(--xh-radio-group-indicator-bg, var(--xh-bg-canvas));
75
75
  transition: border-color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
package/css/select.css CHANGED
@@ -10,14 +10,13 @@
10
10
 
11
11
  /* 形态档只换这三个槽:缺省即 outline(描边 + 画布底) */
12
12
  --xh-_select-trigger-bg: var(--xh-bg-canvas);
13
- --xh-_select-trigger-border: var(--xh-border-default);
14
- --xh-_select-trigger-border-hover: var(--xh-border-strong);
13
+ --xh-_select-trigger-border: var(--xh-border-control);
14
+ --xh-_select-trigger-border-hover: var(--xh-border-control-hover);
15
15
 
16
- /* 强调色随语气走,只落在聚焦与选中上,正文文字色不动。
17
- 没写 data-tone 时 --xh-_tone 没被声明,三处都退回原来的取值 */
16
+ /* 聚焦环恒用固定环色;强调色随语气走,只落在勾选标记与选中文字上,正文文字色不动。
17
+ 没写 data-tone 时 --xh-_tone 没被声明,两处都退回原来的取值 */
18
18
  --xh-_select-ring: var(--xh-ring-focus);
19
19
  --xh-_select-accent: var(--xh-_tone, var(--xh-bg-brand));
20
- --xh-_select-item-highlight-bg: var(--xh-_tone-subtle-hover, var(--xh-bg-subtle-hover));
21
20
  --xh-_select-item-selected-fg: var(--xh-_tone-fg, var(--xh-fg-brand-strong));
22
21
 
23
22
  display: inline-flex;
@@ -50,21 +49,21 @@
50
49
  形态只决定描边与底色怎么用,用哪族颜色由 data-tone 决定 */
51
50
  [data-scope='select'][data-part='root'][data-variant='outline'] {
52
51
  --xh-_select-trigger-bg: var(--xh-bg-canvas);
53
- --xh-_select-trigger-border: var(--xh-border-default);
54
- --xh-_select-trigger-border-hover: var(--xh-border-strong);
52
+ --xh-_select-trigger-border: var(--xh-border-control);
53
+ --xh-_select-trigger-border-hover: var(--xh-border-control-hover);
55
54
  }
56
55
 
57
56
  /* 填充式:底色顶替描边,悬停时才浮出一道淡边 */
58
57
  [data-scope='select'][data-part='root'][data-variant='subtle'] {
59
58
  --xh-_select-trigger-bg: var(--xh-_tone-subtle, var(--xh-bg-subtle));
60
59
  --xh-_select-trigger-border: transparent;
61
- --xh-_select-trigger-border-hover: var(--xh-_tone-border, var(--xh-border-default));
60
+ --xh-_select-trigger-border-hover: var(--xh-_tone-border, var(--xh-border-control-hover));
62
61
  }
63
62
 
64
63
  [data-scope='select'][data-part='root'][data-variant='ghost'] {
65
64
  --xh-_select-trigger-bg: transparent;
66
65
  --xh-_select-trigger-border: transparent;
67
- --xh-_select-trigger-border-hover: var(--xh-_tone-border, var(--xh-border-default));
66
+ --xh-_select-trigger-border-hover: var(--xh-_tone-border, var(--xh-border-control-hover));
68
67
  }
69
68
 
70
69
  [data-scope='select'][data-part='label'] {
@@ -98,10 +97,126 @@
98
97
  cursor: pointer;
99
98
  }
100
99
 
101
- [data-scope='select'][data-part='trigger']:hover:not(:disabled) {
100
+ /* hover 只在中性态换描边:invalid 正拿边框颜色说事,被 hover 盖掉那条信息就没了 */
101
+ [data-scope='select'][data-part='trigger']:hover:not(:disabled, [data-invalid]) {
102
102
  border-color: var(--xh-select-trigger-border-hover, var(--xh-_select-trigger-border-hover));
103
103
  }
104
104
 
105
+ [data-scope='select'][data-part='trigger'][data-invalid] {
106
+ border-color: var(--xh-select-trigger-border-invalid, var(--xh-color-danger-500));
107
+ }
108
+
109
+ /* 收纳容器:触发器与清空按钮的定位基准,触发器在里面占满一行 */
110
+ [data-scope='select'][data-part='control'] {
111
+ position: relative;
112
+ display: flex;
113
+ align-items: center;
114
+ }
115
+
116
+ [data-scope='select'][data-part='control'] > [data-part='trigger'] {
117
+ flex: 1;
118
+ }
119
+
120
+ /* 清空按钮:trigger 的兄弟节点,幽灵小圆钮;没选中或禁用时连接层给 hidden */
121
+ [data-scope='select'][data-part='clear-trigger'] {
122
+ display: inline-grid;
123
+ place-items: center;
124
+ inline-size: var(--xh-select-clear-size, var(--xh-control-indicator-size));
125
+ block-size: var(--xh-select-clear-size, var(--xh-control-indicator-size));
126
+ padding: 0;
127
+ border: none;
128
+ border-radius: var(--xh-shape-pill);
129
+ background: none;
130
+ color: var(--xh-select-clear-fg, var(--xh-fg-subtle));
131
+
132
+ /* 字号行高钉死:继承页面字号会让行框高过按钮,字形被基线顶离正中 */
133
+ font-size: var(--xh-select-clear-font-size, var(--xh-font-size-xs));
134
+ line-height: var(--xh-leading-none);
135
+ cursor: pointer;
136
+ }
137
+
138
+ [data-scope='select'][data-part='clear-trigger']:hover {
139
+ background: var(--xh-select-clear-bg-hover, var(--xh-bg-subtle-hover));
140
+ color: var(--xh-select-clear-fg-hover, var(--xh-fg-default));
141
+ }
142
+
143
+ [data-scope='select'][data-part='clear-trigger']:focus-visible {
144
+ outline: var(--xh-ring-width) solid var(--xh-_select-ring);
145
+ outline-offset: var(--xh-ring-offset);
146
+ }
147
+
148
+ [data-scope='select'][data-part='clear-trigger'][hidden] {
149
+ display: none;
150
+ }
151
+
152
+ /* 进了 control 就嵌进触发器右端(与展开指示符同位),纵向钉在正中;平时不占位也不接指针 */
153
+ [data-scope='select'][data-part='control'] [data-part='clear-trigger'] {
154
+ position: absolute;
155
+ inset-block-start: 50%;
156
+ inset-inline-end: var(--xh-select-trigger-px, var(--xh-_select-px));
157
+ translate: 0 -50%;
158
+ opacity: 0;
159
+ pointer-events: none;
160
+ }
161
+
162
+ /* 悬停或键盘聚到清空钮上时显形 */
163
+ [data-scope='select'][data-part='control']:hover [data-part='clear-trigger']:not([hidden]),
164
+ [data-scope='select'][data-part='control'] [data-part='clear-trigger']:focus-visible {
165
+ opacity: 1;
166
+ pointer-events: auto;
167
+ }
168
+
169
+ /* 清空钮显形期间展开指示符让位,两个图标不叠在一起 */
170
+ [data-scope='select'][data-part='control']:hover:has([data-part='clear-trigger']:not([hidden])) [data-part='indicator'],
171
+ [data-scope='select'][data-part='control']:has([data-part='clear-trigger']:focus-visible) [data-part='indicator'] {
172
+ opacity: 0;
173
+ }
174
+
175
+ /* 多选标签:淡底小药丸;放触发器里是纯展示,放外面配 tag-remove 可删 */
176
+ [data-scope='select'][data-part='tag'] {
177
+ display: inline-flex;
178
+ align-items: center;
179
+ gap: var(--xh-select-tag-gap, var(--xh-space-1));
180
+ padding-block: 0;
181
+ padding-inline: var(--xh-select-tag-px, var(--xh-space-2));
182
+ border-radius: var(--xh-select-tag-radius, var(--xh-shape-pill));
183
+ background: var(--xh-select-tag-bg, var(--xh-bg-subtle));
184
+ color: var(--xh-select-tag-fg, var(--xh-fg-default));
185
+ font-size: var(--xh-select-tag-font-size, var(--xh-font-size-xs));
186
+ line-height: var(--xh-leading-normal);
187
+ white-space: nowrap;
188
+ }
189
+
190
+ [data-scope='select'][data-part='tag'][data-disabled] {
191
+ color: var(--xh-fg-disabled);
192
+ }
193
+
194
+ [data-scope='select'][data-part='tag-remove'] {
195
+ display: inline-grid;
196
+ place-items: center;
197
+ padding: 0;
198
+ border: none;
199
+ border-radius: var(--xh-shape-pill);
200
+ background: none;
201
+ color: var(--xh-select-tag-remove-fg, var(--xh-fg-subtle));
202
+ font-size: inherit;
203
+ cursor: pointer;
204
+ }
205
+
206
+ [data-scope='select'][data-part='tag-remove']:hover {
207
+ color: var(--xh-select-tag-remove-fg-hover, var(--xh-fg-default));
208
+ }
209
+
210
+ [data-scope='select'][data-part='tag-remove']:focus-visible {
211
+ outline: var(--xh-ring-width) solid var(--xh-_select-ring);
212
+ outline-offset: var(--xh-ring-offset);
213
+ }
214
+
215
+ [data-scope='select'][data-part='tag-remove'][data-disabled] {
216
+ color: var(--xh-fg-disabled);
217
+ cursor: not-allowed;
218
+ }
219
+
105
220
  [data-scope='select'][data-part='trigger']:focus-visible {
106
221
  outline: var(--xh-ring-width) solid var(--xh-_select-ring);
107
222
  outline-offset: var(--xh-ring-offset);
@@ -181,8 +296,14 @@
181
296
  display: none;
182
297
  }
183
298
 
184
- /* 容器只是焦点域兜底落点,落焦不画环:高亮永远长在条目上 */
185
- [data-scope='select'][data-part='content']:focus-visible {
299
+ /* 焦点落在容器上只有一种情形:指针打开、无选中值、还没有任何条目当锚点。
300
+ 那一刻它认领着 Tab 位,环画在浮层内沿;有锚点时环长在条目上,容器不再画 */
301
+ [data-scope='select'][data-part='content'][tabindex='0']:focus-visible {
302
+ outline: var(--xh-ring-width) solid var(--xh-ring-focus);
303
+ outline-offset: calc(-1 * var(--xh-ring-width));
304
+ }
305
+
306
+ [data-scope='select'][data-part='content']:not([tabindex='0']):focus-visible {
186
307
  outline: none;
187
308
  }
188
309
 
@@ -194,8 +315,8 @@
194
315
  display: flex;
195
316
  align-items: center;
196
317
  gap: var(--xh-select-item-gap, var(--xh-_select-gap));
197
- padding-block: var(--xh-select-item-py, var(--xh-space-1));
198
- padding-inline: var(--xh-select-item-px, var(--xh-control-px-sm));
318
+ padding-block: var(--xh-select-item-py, var(--xh-space-1_5));
319
+ padding-inline: var(--xh-select-item-px, var(--xh-control-px-md));
199
320
  border-radius: var(--xh-select-item-radius, var(--xh-shape-control));
200
321
  color: var(--xh-select-item-fg, var(--xh-fg-default));
201
322
  font-size: var(--xh-select-item-font-size, var(--xh-_select-font-size));
@@ -204,11 +325,14 @@
204
325
  white-space: nowrap;
205
326
  cursor: pointer;
206
327
  user-select: none;
328
+ transition:
329
+ background var(--xh-motion-duration-micro) var(--xh-motion-ease-enter),
330
+ color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
207
331
  }
208
332
 
209
- /* 键盘高亮与指针高亮共用一套底色:条目本身承载焦点,不另画描边 */
333
+ /* 悬停与键盘锚点共用中性灰轻档:条目本身承载焦点,不另画描边 */
210
334
  [data-scope='select'][data-part='item']:not([data-disabled]):is(:hover, [data-highlighted]) {
211
- background: var(--xh-select-item-bg-highlight, var(--xh-_select-item-highlight-bg));
335
+ background: var(--xh-select-item-bg-hover, var(--xh-bg-subtle));
212
336
  }
213
337
 
214
338
  /* 选中只换文字色与字重,配合行尾的 ✓ 标记;底色整条通道留给高亮,
@@ -218,7 +342,14 @@
218
342
  font-weight: var(--xh-select-item-font-weight-selected, var(--xh-font-weight-medium));
219
343
  }
220
344
 
221
- [data-scope='select'][data-part='item']:focus {
345
+ /* 键盘走到条目上要画环:轻档底色对白底只有 1.1:1,够不上焦点指示器的对比度要求。
346
+ 指针悬停走的是程序化聚焦,鼠标模态下 :focus-visible 不匹配,不会误画 */
347
+ [data-scope='select'][data-part='item']:focus-visible {
348
+ outline: var(--xh-ring-width) solid var(--xh-ring-focus);
349
+ outline-offset: calc(-1 * var(--xh-ring-width));
350
+ }
351
+
352
+ [data-scope='select'][data-part='item']:focus:not(:focus-visible) {
222
353
  outline: none;
223
354
  }
224
355