@xihan-ui/styles 1.0.0-alpha.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 (114) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/LICENSE +21 -0
  3. package/README.md +27 -0
  4. package/css/accordion.css +108 -0
  5. package/css/affix.css +27 -0
  6. package/css/alert.css +108 -0
  7. package/css/anchor.css +137 -0
  8. package/css/avatar-group.css +82 -0
  9. package/css/avatar.css +55 -0
  10. package/css/back-top.css +61 -0
  11. package/css/badge.css +50 -0
  12. package/css/breadcrumb.css +127 -0
  13. package/css/button-group.css +131 -0
  14. package/css/button.css +98 -0
  15. package/css/calendar.css +219 -0
  16. package/css/card.css +116 -0
  17. package/css/carousel.css +193 -0
  18. package/css/cascader.css +397 -0
  19. package/css/checkbox-group.css +185 -0
  20. package/css/checkbox.css +91 -0
  21. package/css/clipboard.css +133 -0
  22. package/css/code-block.css +81 -0
  23. package/css/collapsible.css +63 -0
  24. package/css/color-picker.css +424 -0
  25. package/css/combobox.css +433 -0
  26. package/css/composer.css +127 -0
  27. package/css/context-menu.css +263 -0
  28. package/css/countdown.css +21 -0
  29. package/css/date-field.css +200 -0
  30. package/css/date-picker.css +231 -0
  31. package/css/descriptions.css +200 -0
  32. package/css/dialog.css +127 -0
  33. package/css/drawer.css +372 -0
  34. package/css/dynamic-input.css +157 -0
  35. package/css/editable.css +249 -0
  36. package/css/ellipsis.css +41 -0
  37. package/css/empty-state.css +78 -0
  38. package/css/field.css +65 -0
  39. package/css/file-upload.css +298 -0
  40. package/css/flex.css +95 -0
  41. package/css/float-button.css +121 -0
  42. package/css/form.css +185 -0
  43. package/css/gradient-text.css +62 -0
  44. package/css/grid.css +428 -0
  45. package/css/highlight.css +21 -0
  46. package/css/hover-card.css +146 -0
  47. package/css/icon-wrapper.css +64 -0
  48. package/css/icon.css +54 -0
  49. package/css/image.css +45 -0
  50. package/css/infinite-scroll.css +22 -0
  51. package/css/layers.css +2 -0
  52. package/css/layout.css +169 -0
  53. package/css/list.css +117 -0
  54. package/css/listbox.css +174 -0
  55. package/css/loading-bar.css +66 -0
  56. package/css/log.css +66 -0
  57. package/css/marquee.css +182 -0
  58. package/css/mention.css +222 -0
  59. package/css/menu.css +144 -0
  60. package/css/menubar.css +271 -0
  61. package/css/motion.css +58 -0
  62. package/css/navigation-menu.css +270 -0
  63. package/css/number-animation.css +32 -0
  64. package/css/number-field.css +219 -0
  65. package/css/page-header.css +85 -0
  66. package/css/pagination.css +148 -0
  67. package/css/pin-input.css +157 -0
  68. package/css/popconfirm.css +150 -0
  69. package/css/popover.css +150 -0
  70. package/css/popselect.css +207 -0
  71. package/css/progress.css +37 -0
  72. package/css/qr-code.css +50 -0
  73. package/css/radio-group.css +101 -0
  74. package/css/rating.css +141 -0
  75. package/css/reset.css +22 -0
  76. package/css/result.css +104 -0
  77. package/css/scroll-area.css +176 -0
  78. package/css/select.css +264 -0
  79. package/css/separator.css +17 -0
  80. package/css/skeleton.css +78 -0
  81. package/css/slider.css +197 -0
  82. package/css/spinner.css +74 -0
  83. package/css/splitter.css +127 -0
  84. package/css/statistic.css +67 -0
  85. package/css/steps.css +252 -0
  86. package/css/switch.css +61 -0
  87. package/css/table.css +404 -0
  88. package/css/tabs.css +157 -0
  89. package/css/tags-input.css +360 -0
  90. package/css/text-field.css +195 -0
  91. package/css/thread.css +107 -0
  92. package/css/time-field.css +200 -0
  93. package/css/time-picker.css +347 -0
  94. package/css/time.css +20 -0
  95. package/css/timeline.css +284 -0
  96. package/css/toast.css +195 -0
  97. package/css/toaster.css +69 -0
  98. package/css/toggle-group.css +160 -0
  99. package/css/toggle.css +103 -0
  100. package/css/tone.css +85 -0
  101. package/css/toolbar.css +114 -0
  102. package/css/tooltip.css +109 -0
  103. package/css/tour.css +305 -0
  104. package/css/transfer.css +318 -0
  105. package/css/tree-select.css +441 -0
  106. package/css/tree.css +204 -0
  107. package/css/typography.css +151 -0
  108. package/css/undefined.css +38 -0
  109. package/css/virtualizer.css +104 -0
  110. package/css/visually-hidden.css +13 -0
  111. package/css/watermark.css +48 -0
  112. package/index.css +113 -0
  113. package/index.unlayered.css +16164 -0
  114. package/package.json +163 -0
package/css/tour.css ADDED
@@ -0,0 +1,305 @@
1
+ @layer xihan.motion {
2
+ /* 气泡贴着目标探出来:纯淡入分不清它是从哪个元素长出来的 */
3
+ @keyframes xh-tour-content-in {
4
+ from {
5
+ opacity: 0;
6
+ translate: 0 4px;
7
+ }
8
+
9
+ to {
10
+ opacity: 1;
11
+ translate: 0 0;
12
+ }
13
+ }
14
+
15
+ /* 高亮框落定时那一下呼吸:换步时视线要跟着它走,纯位移太容易被漏看 */
16
+ @keyframes xh-tour-spotlight-in {
17
+ from {
18
+ opacity: 0;
19
+ scale: 1.04;
20
+ }
21
+
22
+ to {
23
+ opacity: 1;
24
+ scale: 1;
25
+ }
26
+ }
27
+
28
+ @media (prefers-reduced-motion: reduce) {
29
+ [data-scope='tour'] [data-part] {
30
+ animation-duration: 0.01ms !important;
31
+ transition-duration: 0.01ms !important;
32
+ }
33
+ }
34
+ }
35
+
36
+ @layer xihan.components {
37
+ /* 根节点只是几个浮层的归属容器,不参与排版。
38
+ 这里不给 display,UA 的 [hidden]{display:none} 就仍然压得住 */
39
+ [data-scope='tour'][data-part='root'] {
40
+ color: var(--xh-tour-fg, var(--xh-fg-default));
41
+ }
42
+
43
+ /* 遮罩:整页压暗,把注意力逼到高亮框那一块上 */
44
+ [data-scope='tour'][data-part='backdrop'] {
45
+ position: fixed;
46
+ inset: 0;
47
+ z-index: var(--xh-tour-backdrop-z, var(--xh-layer-modal));
48
+ background: var(--xh-tour-backdrop-bg, var(--xh-bg-overlay));
49
+ }
50
+
51
+ [data-scope='tour'][data-part='backdrop'][hidden] {
52
+ display: none;
53
+ }
54
+
55
+ [data-scope='tour'][data-part='backdrop'][data-state='open'] {
56
+ animation: xh-fade-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter);
57
+ }
58
+
59
+ [data-scope='tour'][data-part='backdrop'][data-state='closed'] {
60
+ animation: xh-fade-out var(--xh-motion-duration-exit) var(--xh-motion-ease-exit);
61
+ }
62
+
63
+ /* 挖洞的高亮框。位置与尺寸由连接层量出来写进内联 style(position/inset/尺寸那几条轴归它),
64
+ 这里只管长相:一圈描边 + 一片铺满视口的投影当暗幕,中间那块因此是亮的——
65
+ 这就是"洞"。不吃指针:它盖在目标上面,吃了的话目标就再也点不动了 */
66
+ [data-scope='tour'][data-part='spotlight'] {
67
+ z-index: var(--xh-tour-spotlight-z, var(--xh-layer-modal));
68
+ pointer-events: none;
69
+ border-radius: var(--xh-tour-spotlight-radius, var(--xh-shape-surface));
70
+ box-shadow:
71
+ 0 0 0 var(--xh-stroke-thick) var(--xh-tour-spotlight-ring, var(--xh-ring-focus)),
72
+ 0 0 0 100vmax var(--xh-tour-spotlight-shroud, var(--xh-bg-overlay));
73
+ /* 换步时框自己滑过去,用户的视线跟得上;逻辑属性照样可过渡 */
74
+ transition:
75
+ inset-inline-start var(--xh-motion-duration-enter) var(--xh-motion-ease-enter),
76
+ inset-block-start var(--xh-motion-duration-enter) var(--xh-motion-ease-enter),
77
+ inline-size var(--xh-motion-duration-enter) var(--xh-motion-ease-enter),
78
+ block-size var(--xh-motion-duration-enter) var(--xh-motion-ease-enter);
79
+ }
80
+
81
+ [data-scope='tour'][data-part='spotlight'][hidden] {
82
+ display: none;
83
+ }
84
+
85
+ [data-scope='tour'][data-part='spotlight'][data-state='open'] {
86
+ animation: xh-tour-spotlight-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter);
87
+ }
88
+
89
+ /* 高亮框在场时遮罩让位:暗幕已经由那圈铺满视口的投影负责,
90
+ 两层叠加会把页面压得过暗,而且洞里也会跟着变暗——洞就白挖了。
91
+ 居中步的高亮框带 hidden,此时遮罩照常压暗(那一步本来就没有要突出的元素) */
92
+ [data-scope='tour'][data-part='root']:has([data-scope='tour'][data-part='spotlight']:not([hidden]))
93
+ [data-scope='tour'][data-part='backdrop'] {
94
+ background: transparent;
95
+ }
96
+
97
+ /* 空壳不吃指针,交互只发生在 content 上。
98
+ 锚定步的坐标由定位引擎写进内联 style,这里不碰那两条轴 */
99
+ [data-scope='tour'][data-part='positioner'] {
100
+ z-index: var(--xh-tour-positioner-z, var(--xh-layer-popover));
101
+ pointer-events: none;
102
+ }
103
+
104
+ /* 居中步:没有锚点,铺满视口把气泡摆中间 */
105
+ [data-scope='tour'][data-part='positioner'][data-position='center'] {
106
+ inset: 0;
107
+ display: flex;
108
+ align-items: center;
109
+ justify-content: center;
110
+ padding: var(--xh-space-4);
111
+ }
112
+
113
+ /* 必须排在 data-position='center' 之后:两条选择器的特指度相同(都是三个属性),
114
+ 写在前面会被那条 display:flex 盖掉,收起态的居中步就一直摊在屏幕上 */
115
+ [data-scope='tour'][data-part='positioner'][hidden] {
116
+ display: none;
117
+ }
118
+
119
+ [data-scope='tour'][data-part='content'] {
120
+ position: relative;
121
+ display: flex;
122
+ flex-direction: column;
123
+ gap: var(--xh-tour-gap, var(--xh-space-2));
124
+ inline-size: max-content;
125
+ max-inline-size: var(--xh-tour-max-w, 22rem);
126
+ padding-block: var(--xh-tour-py, var(--xh-space-4));
127
+ padding-inline: var(--xh-tour-px, var(--xh-control-px-lg));
128
+ border: var(--xh-stroke-thin) solid var(--xh-tour-border, var(--xh-border-default));
129
+ border-radius: var(--xh-tour-radius, var(--xh-shape-surface));
130
+ background: var(--xh-tour-bg, var(--xh-bg-surface));
131
+ color: var(--xh-tour-fg, var(--xh-fg-default));
132
+ box-shadow: var(--xh-tour-shadow, var(--xh-elevation-3));
133
+ pointer-events: auto;
134
+ }
135
+
136
+ /* display 会盖掉 hidden 属性的 UA 规则,收起态显式让位 */
137
+ [data-scope='tour'][data-part='content'][hidden] {
138
+ display: none;
139
+ }
140
+
141
+ /* 焦点落在气泡本身(Enter/Space 归"下一步"管),但它是纯容器,不画焦点环 */
142
+ [data-scope='tour'][data-part='content']:focus-visible {
143
+ outline: none;
144
+ }
145
+
146
+ [data-scope='tour'][data-part='content'][data-state='open'] {
147
+ animation: xh-tour-content-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter);
148
+ }
149
+
150
+ [data-scope='tour'][data-part='title'] {
151
+ margin: 0;
152
+ font-size: var(--xh-tour-title-font-size, var(--xh-text-heading-3-size));
153
+ font-weight: var(--xh-tour-title-font-weight, var(--xh-text-heading-3-weight));
154
+ color: var(--xh-tour-title-fg, var(--xh-fg-default));
155
+ }
156
+
157
+ /* 关闭按钮是绝对定位的,不占流内宽度:标题得自己让出右上角那块,
158
+ 否则长标题会直接钻到按钮底下。只在真有关闭按钮时让位。 */
159
+ [data-scope='tour'][data-part='content']:has([data-scope='tour'][data-part='close-trigger'])
160
+ [data-scope='tour'][data-part='title'] {
161
+ padding-inline-end: calc(var(--xh-control-h-sm) + var(--xh-space-2));
162
+ }
163
+
164
+ [data-scope='tour'][data-part='description'] {
165
+ margin: 0;
166
+ font-size: var(--xh-text-body-size);
167
+ line-height: var(--xh-text-body-leading);
168
+ color: var(--xh-tour-description-fg, var(--xh-fg-muted));
169
+ }
170
+
171
+ /* "第 m 步,共 n 步":辅助信息,字号压一档,与按钮排在同一行的两端 */
172
+ [data-scope='tour'][data-part='progress-text'] {
173
+ font-size: var(--xh-tour-progress-font-size, var(--xh-font-size-xs));
174
+ color: var(--xh-tour-progress-fg, var(--xh-fg-subtle));
175
+ }
176
+
177
+ [data-scope='tour'][data-part='prev-trigger'],
178
+ [data-scope='tour'][data-part='next-trigger'],
179
+ [data-scope='tour'][data-part='skip-trigger'] {
180
+ display: inline-flex;
181
+ align-items: center;
182
+ justify-content: center;
183
+ block-size: var(--xh-control-h-sm);
184
+ padding-inline: var(--xh-control-px-md);
185
+ border: var(--xh-stroke-thin) solid transparent;
186
+ border-radius: var(--xh-shape-control);
187
+ font-size: var(--xh-font-size-sm);
188
+ cursor: pointer;
189
+ }
190
+
191
+ [data-scope='tour'][data-part='prev-trigger'][hidden],
192
+ [data-scope='tour'][data-part='next-trigger'][hidden],
193
+ [data-scope='tour'][data-part='skip-trigger'][hidden] {
194
+ display: none;
195
+ }
196
+
197
+ /* 主按钮:整条引导的主线动作 */
198
+ [data-scope='tour'][data-part='next-trigger'] {
199
+ background: var(--xh-tour-next-bg, var(--xh-bg-brand));
200
+ color: var(--xh-tour-next-fg, var(--xh-fg-on-brand));
201
+ }
202
+
203
+ [data-scope='tour'][data-part='next-trigger']:hover {
204
+ background: var(--xh-tour-next-bg-hover, var(--xh-bg-brand-hover));
205
+ }
206
+
207
+ [data-scope='tour'][data-part='prev-trigger'],
208
+ [data-scope='tour'][data-part='skip-trigger'] {
209
+ background: transparent;
210
+ color: var(--xh-fg-muted);
211
+ }
212
+
213
+ [data-scope='tour'][data-part='prev-trigger'] {
214
+ border-color: var(--xh-border-default);
215
+ color: var(--xh-fg-default);
216
+ }
217
+
218
+ [data-scope='tour'][data-part='prev-trigger']:hover:not(:disabled),
219
+ [data-scope='tour'][data-part='skip-trigger']:hover {
220
+ background: var(--xh-bg-subtle);
221
+ }
222
+
223
+ /* 单体控件用原生 disabled(集合条目才用 [data-disabled]):首步的上一步就是这个形态 */
224
+ [data-scope='tour'][data-part='prev-trigger']:disabled {
225
+ color: var(--xh-fg-disabled);
226
+ border-color: var(--xh-border-subtle);
227
+ cursor: not-allowed;
228
+ }
229
+
230
+ [data-scope='tour'][data-part='close-trigger'] {
231
+ position: absolute;
232
+ inset-block-start: var(--xh-space-2);
233
+ inset-inline-end: var(--xh-space-2);
234
+ display: inline-flex;
235
+ align-items: center;
236
+ justify-content: center;
237
+ block-size: var(--xh-control-h-sm);
238
+ inline-size: var(--xh-control-h-sm);
239
+ border: 0;
240
+ border-radius: var(--xh-shape-control);
241
+ background: transparent;
242
+ color: var(--xh-fg-muted);
243
+ cursor: pointer;
244
+ }
245
+
246
+ [data-scope='tour'][data-part='close-trigger'][hidden] {
247
+ display: none;
248
+ }
249
+
250
+ [data-scope='tour'][data-part='close-trigger']:hover {
251
+ background: var(--xh-bg-subtle);
252
+ color: var(--xh-fg-default);
253
+ }
254
+
255
+ [data-scope='tour'][data-part='prev-trigger']:focus-visible,
256
+ [data-scope='tour'][data-part='next-trigger']:focus-visible,
257
+ [data-scope='tour'][data-part='skip-trigger']:focus-visible,
258
+ [data-scope='tour'][data-part='close-trigger']:focus-visible {
259
+ outline: var(--xh-ring-width) solid var(--xh-ring-focus);
260
+ outline-offset: var(--xh-ring-offset);
261
+ }
262
+
263
+ /* 箭头是旋转 45° 的方块,只保留朝向目标那两条边的描边;
264
+ 引擎不产出箭头偏移,因此沿交叉轴固定居中。居中步没有锚点,连接层会给它发 hidden */
265
+ [data-scope='tour'][data-part='arrow'] {
266
+ position: absolute;
267
+ inline-size: var(--xh-tour-arrow-size, var(--xh-overlay-arrow-size));
268
+ block-size: var(--xh-tour-arrow-size, var(--xh-overlay-arrow-size));
269
+ background: var(--xh-tour-bg, var(--xh-bg-surface));
270
+ border: var(--xh-stroke-thin) solid var(--xh-tour-border, var(--xh-border-default));
271
+ rotate: 45deg;
272
+ }
273
+
274
+ [data-scope='tour'][data-part='arrow'][hidden] {
275
+ display: none;
276
+ }
277
+
278
+ [data-scope='tour'][data-part='arrow'][data-placement^='bottom'] {
279
+ inset-block-start: calc(var(--xh-tour-arrow-size, var(--xh-overlay-arrow-size)) / -2);
280
+ inset-inline-start: calc(50% - var(--xh-tour-arrow-size, var(--xh-overlay-arrow-size)) / 2);
281
+ border-block-end: 0;
282
+ border-inline-end: 0;
283
+ }
284
+
285
+ [data-scope='tour'][data-part='arrow'][data-placement^='top'] {
286
+ inset-block-end: calc(var(--xh-tour-arrow-size, var(--xh-overlay-arrow-size)) / -2);
287
+ inset-inline-start: calc(50% - var(--xh-tour-arrow-size, var(--xh-overlay-arrow-size)) / 2);
288
+ border-block-start: 0;
289
+ border-inline-start: 0;
290
+ }
291
+
292
+ [data-scope='tour'][data-part='arrow'][data-placement^='right'] {
293
+ inset-inline-start: calc(var(--xh-tour-arrow-size, var(--xh-overlay-arrow-size)) / -2);
294
+ inset-block-start: calc(50% - var(--xh-tour-arrow-size, var(--xh-overlay-arrow-size)) / 2);
295
+ border-block-start: 0;
296
+ border-inline-end: 0;
297
+ }
298
+
299
+ [data-scope='tour'][data-part='arrow'][data-placement^='left'] {
300
+ inset-inline-end: calc(var(--xh-tour-arrow-size, var(--xh-overlay-arrow-size)) / -2);
301
+ inset-block-start: calc(50% - var(--xh-tour-arrow-size, var(--xh-overlay-arrow-size)) / 2);
302
+ border-block-end: 0;
303
+ border-inline-start: 0;
304
+ }
305
+ }
@@ -0,0 +1,318 @@
1
+ @layer xihan.components {
2
+ /* 三栏栅格:两侧面板各占一栏、跨两行,中间那栏上下各放一个搬运按钮,
3
+ 两个按钮在中线处相遇。栅格的列号本身就是随书写方向走的(第 1 栏永远在行首侧),
4
+ dir=rtl 时整块自动镜像,不必另写一套。
5
+ 中间那栏没有对应的角色节点(anatomy 里两个按钮是 root 的直接子节点),
6
+ 所以只能按位置显式安放,不能靠自动流——自动流会把"往左搬"甩到右侧面板的位置上 */
7
+ [data-scope='transfer'][data-part='root'] {
8
+ display: grid;
9
+ grid-template-rows: 1fr 1fr;
10
+ grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
11
+ gap: var(--xh-transfer-gap, var(--xh-space-3));
12
+ align-items: center;
13
+ color: var(--xh-transfer-fg, var(--xh-fg-default));
14
+ }
15
+
16
+ [data-scope='transfer'][data-part='source-panel'] {
17
+ grid-row: 1 / 3;
18
+ grid-column: 1;
19
+ }
20
+
21
+ [data-scope='transfer'][data-part='target-panel'] {
22
+ grid-row: 1 / 3;
23
+ grid-column: 3;
24
+ }
25
+
26
+ /* 上格贴底、下格贴顶:两个按钮因此夹着中线成对出现,而不是各自漂在半栏中央 */
27
+ [data-scope='transfer'][data-part='to-target-trigger'] {
28
+ grid-row: 1;
29
+ grid-column: 2;
30
+ align-self: end;
31
+ }
32
+
33
+ [data-scope='transfer'][data-part='to-source-trigger'] {
34
+ grid-row: 2;
35
+ grid-column: 2;
36
+ align-self: start;
37
+ }
38
+
39
+ [data-scope='transfer'][data-part='source-panel'],
40
+ [data-scope='transfer'][data-part='target-panel'] {
41
+ display: flex;
42
+ flex-direction: column;
43
+ block-size: 100%;
44
+ min-inline-size: 0;
45
+ border: var(--xh-stroke-thin) solid var(--xh-transfer-panel-border, var(--xh-border-default));
46
+ border-radius: var(--xh-transfer-panel-radius, var(--xh-shape-surface));
47
+ background: var(--xh-transfer-panel-bg, var(--xh-bg-surface));
48
+ overflow: hidden;
49
+ }
50
+
51
+ /* 整体禁用只写在 root 与各角色节点上;面板自己也带 data-disabled,观感一处收口 */
52
+ [data-scope='transfer'][data-part='source-panel'][data-disabled],
53
+ [data-scope='transfer'][data-part='target-panel'][data-disabled] {
54
+ background: var(--xh-transfer-panel-bg-disabled, var(--xh-bg-muted));
55
+ }
56
+
57
+ [data-scope='transfer'][data-part='panel-header'] {
58
+ display: flex;
59
+ gap: var(--xh-transfer-header-gap, var(--xh-control-gap-md));
60
+ align-items: center;
61
+ padding-block: var(--xh-transfer-header-py, var(--xh-space-2));
62
+ padding-inline: var(--xh-transfer-header-px, var(--xh-control-px-sm));
63
+ border-block-end: var(--xh-stroke-thin) solid var(--xh-transfer-panel-border, var(--xh-border-default));
64
+ }
65
+
66
+ /* 标题占满剩余宽度,把计数挤到行尾侧 */
67
+ [data-scope='transfer'][data-part='panel-title'] {
68
+ flex: 1;
69
+ min-inline-size: 0;
70
+ overflow: hidden;
71
+ color: var(--xh-transfer-title-fg, var(--xh-fg-default));
72
+ font-size: var(--xh-transfer-title-font-size, var(--xh-text-label-size));
73
+ font-weight: var(--xh-transfer-title-font-weight, var(--xh-text-label-weight));
74
+ line-height: var(--xh-leading-none);
75
+ white-space: nowrap;
76
+ text-overflow: ellipsis;
77
+ }
78
+
79
+ [data-scope='transfer'][data-part='panel-count'] {
80
+ flex: none;
81
+ color: var(--xh-transfer-count-fg, var(--xh-fg-subtle));
82
+ font-size: var(--xh-transfer-count-font-size, var(--xh-font-size-xs));
83
+ line-height: var(--xh-leading-none);
84
+ font-variant-numeric: tabular-nums;
85
+ }
86
+
87
+ /* 连接层只给数字(data-checked-count / data-count),文案是作者的事:
88
+ 语言、量词、单复数都由他定。作者什么都不写时这里补出"勾中/可见",
89
+ 写了内容就用作者的(:empty 只命中空节点) */
90
+ [data-scope='transfer'][data-part='panel-count']:empty::after {
91
+ content: attr(data-checked-count) ' / ' attr(data-count);
92
+ }
93
+
94
+ [data-scope='transfer'][data-part='search'] {
95
+ display: block;
96
+ inline-size: 100%;
97
+ block-size: var(--xh-transfer-search-h, var(--xh-control-h-sm));
98
+ padding-inline: var(--xh-transfer-search-px, var(--xh-control-px-sm));
99
+ border: 0;
100
+ border-block-end: var(--xh-stroke-thin) solid var(--xh-transfer-panel-border, var(--xh-border-default));
101
+ background: var(--xh-transfer-search-bg, transparent);
102
+ color: var(--xh-transfer-search-fg, var(--xh-fg-default));
103
+ font: inherit;
104
+ font-size: var(--xh-transfer-search-font-size, var(--xh-text-body-size));
105
+ }
106
+
107
+ [data-scope='transfer'][data-part='search']:focus-visible {
108
+ outline: var(--xh-ring-width) solid var(--xh-ring-focus);
109
+ outline-offset: calc(var(--xh-ring-offset) * -1);
110
+ }
111
+
112
+ /* 单体控件的禁用是原生 disabled,样式只能写 :disabled;集合条目才读 data-disabled */
113
+ [data-scope='transfer'][data-part='search']:disabled {
114
+ color: var(--xh-fg-disabled);
115
+ cursor: not-allowed;
116
+ }
117
+
118
+ [data-scope='transfer'][data-part='list'] {
119
+ display: flex;
120
+ flex: 1;
121
+ flex-direction: column;
122
+ min-block-size: var(--xh-transfer-list-min-h, 8rem);
123
+ max-block-size: var(--xh-transfer-list-max-h, 16rem);
124
+ padding-block: var(--xh-transfer-list-py, var(--xh-space-1));
125
+ padding-inline: var(--xh-transfer-list-px, var(--xh-space-1));
126
+ overflow: auto;
127
+ }
128
+
129
+ /* 焦点在列表外时容器自己进 Tab 序列兜底,落焦不画环:高亮永远长在条目上 */
130
+ [data-scope='transfer'][data-part='list']:focus-visible {
131
+ outline: none;
132
+ }
133
+
134
+ [data-scope='transfer'][data-part='item'] {
135
+ display: flex;
136
+ flex: none;
137
+ gap: var(--xh-transfer-item-gap, var(--xh-control-gap-md));
138
+ align-items: center;
139
+ padding-block: var(--xh-transfer-item-py, var(--xh-space-1));
140
+ padding-inline: var(--xh-transfer-item-px, var(--xh-control-px-sm));
141
+ border-radius: var(--xh-transfer-item-radius, var(--xh-shape-control));
142
+ color: var(--xh-transfer-item-fg, var(--xh-fg-default));
143
+ font-size: var(--xh-transfer-item-font-size, var(--xh-text-body-size));
144
+ /* 行高留出文字的上下伸展空间,行内那层 overflow:hidden 才不会把字削掉 */
145
+ line-height: var(--xh-transfer-item-leading, var(--xh-leading-normal));
146
+ white-space: nowrap;
147
+ cursor: pointer;
148
+ user-select: none;
149
+ transition: background var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
150
+ }
151
+
152
+ /* 键盘高亮与指针悬停共用一套底色:条目本身承载焦点,不另画描边。
153
+ data-highlighted 跟的是连接层记的焦点条目,与勾选互相独立 */
154
+ [data-scope='transfer'][data-part='item']:not([data-disabled]):is(:hover, [data-highlighted]) {
155
+ background: var(--xh-transfer-item-bg-highlight, var(--xh-bg-subtle-hover));
156
+ }
157
+
158
+ [data-scope='transfer'][data-part='item']:focus-visible {
159
+ outline: var(--xh-ring-width) solid var(--xh-ring-focus);
160
+ outline-offset: calc(var(--xh-ring-offset) * -1);
161
+ }
162
+
163
+ /* 禁用走 aria-disabled 而非原生 disabled:条目仍要可聚焦(它还得当方向键的起点),
164
+ 所以样式只能读 data-disabled,写 :disabled 永远不命中 */
165
+ [data-scope='transfer'][data-part='item'][data-disabled] {
166
+ color: var(--xh-fg-disabled);
167
+ cursor: not-allowed;
168
+ }
169
+
170
+ [data-scope='transfer'][data-part='item-text'] {
171
+ flex: 1;
172
+ min-inline-size: 0;
173
+ overflow: hidden;
174
+ white-space: nowrap;
175
+ text-overflow: ellipsis;
176
+ }
177
+
178
+ /* 条目的勾选格与全选格共用同一套盒子,并成一条规则写:分开写迟早只改到一半,
179
+ 两个方框就此走形。全选格没有对应的角色节点(它自己就是按钮),由伪元素补出来 */
180
+ [data-scope='transfer'][data-part='item-checkbox'],
181
+ [data-scope='transfer'][data-part='select-all-trigger']::before {
182
+ display: inline-flex;
183
+ flex: none;
184
+ align-items: center;
185
+ justify-content: center;
186
+ inline-size: var(--xh-transfer-checkbox-size, 16px);
187
+ block-size: var(--xh-transfer-checkbox-size, 16px);
188
+ border: var(--xh-stroke-thin) solid var(--xh-transfer-checkbox-border, var(--xh-border-default));
189
+ border-radius: var(--xh-transfer-checkbox-radius, var(--xh-radius-sm));
190
+ background: var(--xh-transfer-checkbox-bg, var(--xh-bg-canvas));
191
+ color: var(--xh-transfer-checkbox-fg, var(--xh-fg-on-brand));
192
+ font-size: var(--xh-transfer-checkbox-font-size, 12px);
193
+ line-height: var(--xh-leading-none);
194
+ transition:
195
+ background var(--xh-motion-duration-micro) var(--xh-motion-ease-enter),
196
+ border-color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
197
+ }
198
+
199
+ /* content 只能给伪元素,绝不能并进上面那条:content 落在普通元素上会顶掉它的全部子节点,
200
+ item-checkbox 里作者写的图标就此消失 */
201
+ [data-scope='transfer'][data-part='select-all-trigger']::before {
202
+ content: '';
203
+ }
204
+
205
+ /* 勾中即填色。全选格三态里 all 与 some 都算"有值",与条目共用同一副填色 */
206
+ [data-scope='transfer'][data-part='item-checkbox'][data-state='checked'],
207
+ [data-scope='transfer'][data-part='select-all-trigger']:is([data-state='all'], [data-state='some'])::before {
208
+ border-color: var(--xh-transfer-checkbox-border-checked, var(--xh-bg-brand));
209
+ background: var(--xh-transfer-checkbox-bg-checked, var(--xh-bg-brand));
210
+ }
211
+
212
+ /* 勾号由皮肤补,作者不必自己往格子里塞字符 */
213
+ [data-scope='transfer'][data-part='item-checkbox'][data-state='checked']::before {
214
+ content: '✓';
215
+ }
216
+
217
+ /* 全选格的三态:all 打勾、some 打横杠、none 留空。
218
+ some 就是读屏听到的 aria-checked=mixed,两者同源,样式只认 data-state */
219
+ [data-scope='transfer'][data-part='select-all-trigger'][data-state='all']::before {
220
+ content: '✓';
221
+ }
222
+
223
+ [data-scope='transfer'][data-part='select-all-trigger'][data-state='some']::before {
224
+ content: '−';
225
+ }
226
+
227
+ [data-scope='transfer'][data-part='item-checkbox'][data-disabled] {
228
+ border-color: var(--xh-transfer-checkbox-border-disabled, var(--xh-border-subtle));
229
+ background: var(--xh-transfer-checkbox-bg-disabled, var(--xh-bg-muted));
230
+ }
231
+
232
+ /* 全选格是原生按钮(三态经 aria-checked 上报,激活由平台负责),
233
+ 禁用因此写 :disabled,不写 data-disabled */
234
+ [data-scope='transfer'][data-part='select-all-trigger'] {
235
+ display: inline-flex;
236
+ flex: none;
237
+ gap: var(--xh-transfer-select-all-gap, var(--xh-control-gap-sm));
238
+ align-items: center;
239
+ padding: 0;
240
+ border: 0;
241
+ border-radius: var(--xh-transfer-select-all-radius, var(--xh-shape-control));
242
+ background: none;
243
+ color: var(--xh-transfer-select-all-fg, var(--xh-fg-muted));
244
+ font-size: var(--xh-transfer-select-all-font-size, var(--xh-font-size-xs));
245
+ font-weight: var(--xh-font-weight-medium);
246
+ line-height: var(--xh-leading-none);
247
+ cursor: pointer;
248
+ }
249
+
250
+ [data-scope='transfer'][data-part='select-all-trigger']:focus-visible {
251
+ outline: var(--xh-ring-width) solid var(--xh-ring-focus);
252
+ outline-offset: var(--xh-ring-offset);
253
+ }
254
+
255
+ [data-scope='transfer'][data-part='select-all-trigger']:disabled {
256
+ color: var(--xh-fg-disabled);
257
+ cursor: not-allowed;
258
+ }
259
+
260
+ [data-scope='transfer'][data-part='to-target-trigger'],
261
+ [data-scope='transfer'][data-part='to-source-trigger'] {
262
+ display: inline-flex;
263
+ align-items: center;
264
+ justify-content: center;
265
+ min-inline-size: var(--xh-transfer-trigger-size, var(--xh-control-h-sm));
266
+ block-size: var(--xh-transfer-trigger-size, var(--xh-control-h-sm));
267
+ padding-inline: var(--xh-transfer-trigger-px, var(--xh-space-2));
268
+ border: var(--xh-stroke-thin) solid var(--xh-transfer-trigger-border, var(--xh-border-default));
269
+ border-radius: var(--xh-transfer-trigger-radius, var(--xh-shape-control));
270
+ background: var(--xh-transfer-trigger-bg, var(--xh-bg-subtle));
271
+ color: var(--xh-transfer-trigger-fg, var(--xh-fg-default));
272
+ font-size: var(--xh-transfer-trigger-font-size, var(--xh-text-label-size));
273
+ line-height: var(--xh-leading-none);
274
+ cursor: pointer;
275
+ transition: background var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
276
+ }
277
+
278
+ [data-scope='transfer'][data-part='to-target-trigger']:hover:not(:disabled),
279
+ [data-scope='transfer'][data-part='to-source-trigger']:hover:not(:disabled) {
280
+ background: var(--xh-transfer-trigger-bg-hover, var(--xh-bg-subtle-hover));
281
+ }
282
+
283
+ [data-scope='transfer'][data-part='to-target-trigger']:active:not(:disabled),
284
+ [data-scope='transfer'][data-part='to-source-trigger']:active:not(:disabled) {
285
+ background: var(--xh-transfer-trigger-bg-active, var(--xh-bg-subtle-active));
286
+ }
287
+
288
+ [data-scope='transfer'][data-part='to-target-trigger']:focus-visible,
289
+ [data-scope='transfer'][data-part='to-source-trigger']:focus-visible {
290
+ outline: var(--xh-ring-width) solid var(--xh-ring-focus);
291
+ outline-offset: var(--xh-ring-offset);
292
+ }
293
+
294
+ /* 搬运按钮是单体控件,禁用走原生 disabled */
295
+ [data-scope='transfer'][data-part='to-target-trigger']:disabled,
296
+ [data-scope='transfer'][data-part='to-source-trigger']:disabled {
297
+ color: var(--xh-fg-disabled);
298
+ cursor: not-allowed;
299
+ }
300
+
301
+ /* 上面凡是给了 display 的角色节点都要补这条:display 声明会盖掉 UA 的 [hidden]{display:none},
302
+ 不补的话收起态整个失效。连接层真正会写 hidden 的是 search(searchable 关掉时)、
303
+ item(不属于本侧、或被搜索筛掉)、item-checkbox(oneWay 下的右侧),
304
+ 其余几个是作者自己收起时的兜底 */
305
+ [data-scope='transfer'][data-part='root'][hidden],
306
+ [data-scope='transfer'][data-part='source-panel'][hidden],
307
+ [data-scope='transfer'][data-part='target-panel'][hidden],
308
+ [data-scope='transfer'][data-part='panel-header'][hidden],
309
+ [data-scope='transfer'][data-part='search'][hidden],
310
+ [data-scope='transfer'][data-part='list'][hidden],
311
+ [data-scope='transfer'][data-part='item'][hidden],
312
+ [data-scope='transfer'][data-part='item-checkbox'][hidden],
313
+ [data-scope='transfer'][data-part='select-all-trigger'][hidden],
314
+ [data-scope='transfer'][data-part='to-target-trigger'][hidden],
315
+ [data-scope='transfer'][data-part='to-source-trigger'][hidden] {
316
+ display: none;
317
+ }
318
+ }