@xihan-ui/styles 2.1.0 → 3.0.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 (152) hide show
  1. package/CHANGELOG.md +1227 -0
  2. package/README.md +6 -1
  3. package/component-tokens.d.ts +505 -96
  4. package/components.tokens.json +34636 -25846
  5. package/css/accordion.css +34 -8
  6. package/css/alert.css +59 -1
  7. package/css/anchor.css +46 -8
  8. package/css/approval.css +116 -76
  9. package/css/avatar-group.css +1 -1
  10. package/css/avatar.css +13 -5
  11. package/css/back-top.css +61 -42
  12. package/css/badge.css +62 -12
  13. package/css/bar-code.css +1 -1
  14. package/css/breadcrumb.css +62 -3
  15. package/css/button-group.css +4 -4
  16. package/css/button.css +67 -19
  17. package/css/calendar-picker.css +19 -0
  18. package/css/calendar-range-picker.css +39 -2
  19. package/css/card.css +115 -1
  20. package/css/carousel.css +148 -43
  21. package/css/cartesian-chart.css +830 -0
  22. package/css/cascader.css +181 -65
  23. package/css/checkbox-group.css +59 -89
  24. package/css/checkbox.css +16 -49
  25. package/css/citation.css +470 -0
  26. package/css/clipboard.css +20 -63
  27. package/css/code-view.css +101 -6
  28. package/css/collapsible.css +34 -8
  29. package/css/color-field.css +18 -0
  30. package/css/color-picker.css +55 -58
  31. package/css/color-slider.css +22 -15
  32. package/css/color-swatch-picker.css +3 -1
  33. package/css/combobox.css +80 -33
  34. package/css/command.css +45 -43
  35. package/css/context-menu.css +39 -22
  36. package/css/date-field.css +23 -0
  37. package/css/date-picker.css +57 -22
  38. package/css/date-range-picker.css +301 -12
  39. package/css/description.css +17 -1
  40. package/css/descriptions.css +42 -0
  41. package/css/dialog.css +94 -36
  42. package/css/diff-view.css +97 -12
  43. package/css/download-trigger.css +44 -78
  44. package/css/drawer.css +160 -119
  45. package/css/editable.css +19 -0
  46. package/css/empty-state.css +7 -6
  47. package/css/field-array.css +36 -0
  48. package/css/field.css +16 -18
  49. package/css/file-upload.css +125 -135
  50. package/css/flex.css +324 -8
  51. package/css/float-button.css +165 -38
  52. package/css/floating-panel.css +53 -19
  53. package/css/form.css +23 -17
  54. package/css/funnel-chart.css +106 -0
  55. package/css/graph-chart.css +205 -0
  56. package/css/grid-list.css +235 -0
  57. package/css/grid.css +252 -1
  58. package/css/heatmap.css +119 -36
  59. package/css/hierarchy-chart.css +298 -0
  60. package/css/hover-card.css +31 -14
  61. package/css/icon.css +100 -0
  62. package/css/image-cropper.css +33 -7
  63. package/css/image-viewer.css +167 -22
  64. package/css/image.css +23 -4
  65. package/css/input-group.css +11 -13
  66. package/css/json-viewer.css +50 -2
  67. package/css/layout.css +68 -18
  68. package/css/liquid.css +106 -0
  69. package/css/list.css +1 -1
  70. package/css/listbox.css +46 -6
  71. package/css/loading-bar.css +27 -13
  72. package/css/log.css +129 -33
  73. package/css/markdown-stream.css +1 -1
  74. package/css/marquee.css +163 -7
  75. package/css/matrix-code.css +1 -1
  76. package/css/mention.css +35 -19
  77. package/css/menu.css +35 -21
  78. package/css/menubar.css +36 -20
  79. package/css/message-feed.css +172 -51
  80. package/css/navigation-menu.css +172 -11
  81. package/css/notification.css +429 -108
  82. package/css/number-field.css +22 -0
  83. package/css/page-header.css +3 -1
  84. package/css/pagination.css +126 -24
  85. package/css/password-input.css +40 -8
  86. package/css/pie-chart.css +247 -0
  87. package/css/popconfirm.css +44 -50
  88. package/css/popover.css +50 -14
  89. package/css/progress.css +308 -10
  90. package/css/prompt-input.css +36 -0
  91. package/css/question-flow.css +97 -57
  92. package/css/radar-chart.css +233 -0
  93. package/css/radio-group.css +363 -5
  94. package/css/rating.css +21 -23
  95. package/css/reasoning.css +61 -54
  96. package/css/resizable.css +6 -1
  97. package/css/sankey-chart.css +174 -0
  98. package/css/scroll-area.css +32 -0
  99. package/css/scrollbar.css +1 -1
  100. package/css/select.css +74 -55
  101. package/css/side-nav.css +173 -24
  102. package/css/signature-pad.css +16 -1
  103. package/css/skeleton.css +13 -14
  104. package/css/slider.css +35 -13
  105. package/css/sortable.css +37 -7
  106. package/css/sparkline.css +164 -0
  107. package/css/spinner.css +50 -22
  108. package/css/splitter.css +7 -1
  109. package/css/statistic.css +10 -0
  110. package/css/steps.css +248 -29
  111. package/css/switch.css +23 -48
  112. package/css/table.css +158 -118
  113. package/css/tabs.css +231 -19
  114. package/css/tag-group.css +27 -3
  115. package/css/tag.css +53 -12
  116. package/css/tags-input.css +40 -3
  117. package/css/text-field.css +19 -0
  118. package/css/time-field.css +20 -1
  119. package/css/time-picker.css +44 -15
  120. package/css/time-range-picker.css +45 -16
  121. package/css/timeline.css +1 -1
  122. package/css/toggle-group.css +4 -4
  123. package/css/toggle.css +4 -3
  124. package/css/tool-call.css +59 -51
  125. package/css/toolbar.css +101 -3
  126. package/css/tooltip.css +15 -2
  127. package/css/tour.css +42 -12
  128. package/css/transfer.css +98 -75
  129. package/css/tree-select.css +232 -53
  130. package/css/tree.css +201 -55
  131. package/css/truncate.css +55 -12
  132. package/css/typography.css +93 -0
  133. package/css/virtualizer.css +56 -0
  134. package/css/watermark.css +23 -0
  135. package/family/action-control.css +14 -5
  136. package/family/chart.css +480 -0
  137. package/family/check-mark.css +94 -0
  138. package/family/choice-card.css +97 -0
  139. package/family/collection-item.css +9 -15
  140. package/family/field-chrome.css +1 -12
  141. package/family/loading-ring.css +95 -0
  142. package/family/material.css +72 -0
  143. package/family/menu-choice.css +56 -0
  144. package/family/motion.css +207 -10
  145. package/family/tag-list.css +59 -0
  146. package/index.css +22997 -13678
  147. package/index.unlayered.css +12928 -3605
  148. package/package.json +20 -8
  149. package/css/gradient-text.css +0 -91
  150. package/css/icon-wrapper.css +0 -81
  151. package/css/segmented.css +0 -280
  152. package/css/toast.css +0 -499
package/css/toolbar.css CHANGED
@@ -31,7 +31,8 @@
31
31
  --xh-_toolbar-root-bg: transparent;
32
32
  --xh-_toolbar-root-bg-disabled: transparent;
33
33
 
34
- /* 一行排不下就折行:条目宽度由各自的控件决定,硬撑一行会把整条顶出容器 */
34
+ /* 一行排不下就折行:条目宽度由各自的控件决定,硬撑一行会把整条顶出容器。
35
+ 放了「更多」钮时改为收纳,见下方收纳一节 */
35
36
  display: flex;
36
37
  flex-wrap: wrap;
37
38
  align-items: center;
@@ -44,7 +45,7 @@
44
45
  color: var(--xh-toolbar-fg, var(--xh-fg-default));
45
46
  /* 整条置灰与恢复是一次状态切换,底色与字色淡着换,不硬切 */
46
47
  transition:
47
- background var(--xh-motion-duration-micro) var(--xh-motion-ease-enter),
48
+ background-color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter),
48
49
  color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
49
50
  }
50
51
 
@@ -181,12 +182,16 @@
181
182
  }
182
183
 
183
184
  /* 组内分段使用同一块中性操作面(淡底承载:hover 200 → pressed 300 由 host 槽下发);
184
- 中间段保持直角,只给整组两端补回控件圆角。按下态 / 禁用面与散落条目同一套规则 */
185
+ 中间段保持直角,只给整组两端补回控件圆角。按下态 / 禁用面与散落条目同一套规则。
186
+ 分段零间距直角相接,按下只换面不缩放:缩放任一段都会把两侧接缝撕开 */
185
187
  [data-scope='toolbar'][data-part='group'] > [data-scope='toolbar'][data-part='item'] {
186
188
  --xh-action-host-bg-hover: var(--xh-bg-subtle-hover);
187
189
  --xh-action-host-bg-pressed: var(--xh-bg-subtle-active);
188
190
  --xh-action-radius: 0;
191
+ --xh-action-scale-pressed: none;
189
192
  --xh-action-bg-disabled: var(--xh-toolbar-item-bg-disabled, var(--xh-bg-subtle));
193
+ /* 组末端补回的控件圆角:尾部几段收进「更多」菜单后,由留下的最后一段接过去 */
194
+ --xh-_toolbar-segment-end-radius: var(--xh-toolbar-item-radius, var(--xh-shape-control));
190
195
 
191
196
  position: relative;
192
197
  }
@@ -257,6 +262,80 @@
257
262
  margin-block-start: calc(-1 * var(--xh-toolbar-separator-thickness, var(--xh-stroke-thin)));
258
263
  }
259
264
 
265
+ /* —— 收纳 —— */
266
+ /* 放了「更多」钮就不折行:放不下的条目按次序收进它弹出的菜单。条目与分组不压缩——压缩了,
267
+ 量到的就不是自然长度;root 在 flex 父容器里可以被压到比内容窄,可用长度由外面给 */
268
+ [data-scope='toolbar'][data-part='root']:has(> [data-scope='toolbar'][data-part='overflow-trigger']) {
269
+ flex-wrap: nowrap;
270
+ min-inline-size: 0;
271
+ min-block-size: 0;
272
+ }
273
+
274
+ [data-scope='toolbar'][data-part='root']:has(> [data-scope='toolbar'][data-part='overflow-trigger'])
275
+ > :is([data-scope='toolbar'][data-part='item'], [data-scope='toolbar'][data-part='group']) {
276
+ flex-shrink: 0;
277
+ }
278
+
279
+ /* 收起的条目之前的分隔线与整组收空的分组一并让开:它们分隔的那一侧已经没有露着的条目,
280
+ 留着会在「更多」钮前多出一道线、多出一截间距 */
281
+ [data-scope='toolbar'][data-part='root']:has(> [data-scope='toolbar'][data-part='overflow-trigger']:not([hidden]))
282
+ > [data-scope='toolbar'][data-part='separator']:not(
283
+ :has(
284
+ ~ [data-scope='toolbar'][data-part='item']:not([hidden]),
285
+ ~ [data-scope='toolbar'][data-part='group'] > [data-scope='toolbar'][data-part='item']:not([hidden])
286
+ )
287
+ ),
288
+ [data-scope='toolbar'][data-part='root']:has(> [data-scope='toolbar'][data-part='overflow-trigger']:not([hidden]))
289
+ > [data-scope='toolbar'][data-part='group']:not(:has(> [data-scope='toolbar'][data-part='item']:not([hidden]))),
290
+ [data-scope='toolbar'][data-part='root']:has(> [data-scope='toolbar'][data-part='overflow-trigger']:not([hidden]))
291
+ > [data-scope='toolbar'][data-part='group']
292
+ > [data-scope='toolbar'][data-part='separator']:not(:has(~ [data-scope='toolbar'][data-part='item']:not([hidden]))) {
293
+ display: none;
294
+ }
295
+
296
+ /* 组的尾部几段收进菜单后,留下的最后一段补回末端圆角 */
297
+ [data-scope='toolbar'][data-part='group'][data-orientation='horizontal']
298
+ > [data-scope='toolbar'][data-part='item']:not([hidden]):not(:has(~ [data-scope='toolbar'][data-part='item']:not([hidden]))) {
299
+ border-start-end-radius: var(--xh-_toolbar-segment-end-radius);
300
+ border-end-end-radius: var(--xh-_toolbar-segment-end-radius);
301
+ }
302
+
303
+ [data-scope='toolbar'][data-part='group'][data-orientation='vertical']
304
+ > [data-scope='toolbar'][data-part='item']:not([hidden]):not(:has(~ [data-scope='toolbar'][data-part='item']:not([hidden]))) {
305
+ border-end-start-radius: var(--xh-_toolbar-segment-end-radius);
306
+ border-end-end-radius: var(--xh-_toolbar-segment-end-radius);
307
+ }
308
+
309
+ /* 「更多」钮:行尾一枚单图标钮,接 Action Control icon 档 ghost 面,与条目同档(连接层投影 profile /
310
+ variant / size)。盒取条目同高的正方形,四态面、0.97 按压、焦点环与粗指针 44px 命中区由家族配方给;
311
+ 承载面的阶梯随根的 variant 经 host 槽下发(ghost 根白底 100 → 200,subtle 根淡底 200 → 300) */
312
+ [data-scope='toolbar'][data-part='overflow-trigger'] {
313
+ touch-action: manipulation;
314
+ }
315
+
316
+ /* 菜单开着:与悬停同档的中性面,不用品牌色、不另画环;收起的是哪几条由菜单本身说明 */
317
+ [data-scope='toolbar'][data-part='overflow-trigger'][data-state='open'] {
318
+ --xh-action-bg-rest: var(--xh-_action-variant-bg-hover);
319
+ --xh-action-bg-focus-visible: var(--xh-_action-variant-bg-hover);
320
+ }
321
+
322
+ /* 没写内容就由皮肤画兜底字形:横排三点。作者塞了自己的图标这条即不命中;
323
+ 字形与条目里的图标同一把尺(根按尺寸档下发的 --xh-icon-size) */
324
+ [data-scope='toolbar'][data-part='overflow-trigger']:empty::before {
325
+ content: '';
326
+ display: inline-block;
327
+ inline-size: var(--xh-icon-size, var(--xh-glyph-size-text));
328
+ block-size: var(--xh-icon-size, var(--xh-glyph-size-text));
329
+ background-color: currentColor;
330
+ -webkit-mask: var(--xh-glyph-mark-ellipsis) center / contain no-repeat;
331
+ mask: var(--xh-glyph-mark-ellipsis) center / contain no-repeat;
332
+ }
333
+
334
+ /* 全部放得下时连接层给的是 hidden 属性;家族那条 display 会把 UA 规则盖掉 */
335
+ [data-scope='toolbar'][data-part='overflow-trigger'][hidden] {
336
+ display: none;
337
+ }
338
+
260
339
  @media (forced-colors: active) {
261
340
  /* 分段之间那条线画的是一块纯底色,这一档里底色被统一换成 Canvas,线整条消失,
262
341
  几段控件就连成了一长串。换成前景色画回来 */
@@ -264,4 +343,23 @@
264
343
  background: CanvasText;
265
344
  }
266
345
  }
346
+
347
+ @media (forced-colors: active) {
348
+ /* 兜底字形是 mask 挖出来的一块底色,这一档里底色被系统换成 Canvas,字形跟着消失。
349
+ 字形退出强制换色、直接写系统色(不能取 currentColor:退出强制换色的伪元素读到的是被系统换掉之前的作者色):
350
+ 按钮面上取 ButtonText,悬停与按下时家族把按钮涂成 Highlight、字形换 HighlightText;
351
+ 禁用取 GrayText */
352
+ [data-scope='toolbar'][data-part='overflow-trigger']:empty::before {
353
+ forced-color-adjust: none;
354
+ background-color: ButtonText;
355
+ }
356
+
357
+ [data-scope='toolbar'][data-part='overflow-trigger']:not([data-disabled]):not([data-loading]):is(:hover, :active, [data-pressed]):empty::before {
358
+ background-color: HighlightText;
359
+ }
360
+
361
+ [data-scope='toolbar'][data-part='overflow-trigger'][data-disabled]:empty::before {
362
+ background-color: GrayText;
363
+ }
364
+ }
267
365
  }
package/css/tooltip.css CHANGED
@@ -114,6 +114,18 @@
114
114
  backdrop-filter: var(--xh-tooltip-backdrop, var(--xh-material-frosted-compact-backdrop));
115
115
  }
116
116
 
117
+ /* 跟随鼠标的提示压在指针旁边、随指针挪:它接了指针就会挡住 trigger 上的移动,指针一滑进去便算离开了 trigger。
118
+ 这一档浮层本体同样让指针穿过去,停留细看交给 trigger 本身 */
119
+ [data-scope='tooltip'][data-part='positioner'][data-follow-cursor] [data-scope='tooltip'][data-part='content'] {
120
+ pointer-events: none;
121
+ }
122
+
123
+ /* 反白面是一块彩色面(连接层打 data-xh-ink-surface):面内的作者内容按这块底取墨色。
124
+ 底色取自被墨色域改写的令牌,所以域只落在面内的子元素上,这里的取值仍按外层主题解析 */
125
+ [data-scope='tooltip'][data-part='content'][data-xh-ink-surface] {
126
+ --xh-ink-surface: var(--xh-tooltip-bg, var(--xh-_tooltip-solid));
127
+ }
128
+
117
129
  /* 光学层单独承载 tint 与透明度;backdrop-filter 只采样元素背后,不处理正文。 */
118
130
  [data-scope='tooltip'][data-part='content']::before {
119
131
  content: '';
@@ -126,8 +138,9 @@
126
138
  pointer-events: none;
127
139
  }
128
140
 
129
- /* 入场走 enter 档(锚定列表族的 tooltip 入场 --xh-motion-duration-enter),退场仍是 exit */
130
- [data-scope='tooltip'][data-part='content'][data-state='open'] {
141
+ /* 入场走 enter 档(锚定列表族的 tooltip 入场 --xh-motion-duration-enter),退场仍是 exit。
142
+ 接替上一个提示打开的(data-instant)直接呈现:横扫一排钮时提示一个个跟上,不再一个个滑入 */
143
+ [data-scope='tooltip'][data-part='content'][data-state='open']:not([data-instant]) {
131
144
  animation: xh-overlay-slide-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter-strong);
132
145
  }
133
146
 
package/css/tour.css CHANGED
@@ -13,7 +13,7 @@
13
13
  /* 高亮框落定时那一下呼吸:换步时视线要跟着它走,纯位移太容易被漏看 */
14
14
  @keyframes xh-tour-spotlight-in {
15
15
  from {
16
- opacity: 0;
16
+ opacity: var(--xh-_enter-from-opacity, 0);
17
17
  scale: calc(2 - var(--xh-motion-scale-enter));
18
18
  }
19
19
 
@@ -70,7 +70,7 @@
70
70
  display: none;
71
71
  }
72
72
 
73
- [data-scope='tour'][data-part='backdrop'][data-state='open'] {
73
+ [data-scope='tour'][data-part='backdrop'][data-state='open']:not([data-instant]) {
74
74
  animation: xh-fade-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter);
75
75
  }
76
76
 
@@ -94,13 +94,23 @@
94
94
  box-shadow:
95
95
  0 0 0 var(--xh-stroke-thick) var(--xh-tour-spotlight-ring, var(--xh-ring-focus)),
96
96
  0 0 0 100vmax var(--xh-_tour-spotlight-shroud);
97
- /* 换步时框自己滑过去,用户的视线跟得上;逻辑属性照样可过渡 */
97
+ }
98
+
99
+ /* 换步那一段(data-animating):聚光框与气泡同一段时长、同一条曲线一起滑到下一个目标,视线跟得上;
100
+ 页面滚动与视口缩放时不挂,两者跟手、不拖尾。坐标由连接层按物理 left / top 写进内联,过渡跟着写物理轴 */
101
+ [data-scope='tour'][data-part='spotlight'][data-animating] {
98
102
  transition:
99
- inset-inline-start var(--xh-motion-duration-enter) var(--xh-motion-ease-continuous),
100
- inset-block-start var(--xh-motion-duration-enter) var(--xh-motion-ease-continuous),
101
- inline-size var(--xh-motion-duration-enter) var(--xh-motion-ease-continuous),
102
- block-size var(--xh-motion-duration-enter) var(--xh-motion-ease-continuous),
103
- border-radius var(--xh-motion-duration-enter) var(--xh-motion-ease-continuous);
103
+ left var(--xh-motion-duration-move) var(--xh-motion-ease-continuous),
104
+ top var(--xh-motion-duration-move) var(--xh-motion-ease-continuous),
105
+ inline-size var(--xh-motion-duration-move) var(--xh-motion-ease-continuous),
106
+ block-size var(--xh-motion-duration-move) var(--xh-motion-ease-continuous),
107
+ border-radius var(--xh-motion-duration-move) var(--xh-motion-ease-continuous);
108
+ }
109
+
110
+ [data-scope='tour'][data-part='positioner'][data-animating] {
111
+ transition:
112
+ left var(--xh-motion-duration-move) var(--xh-motion-ease-continuous),
113
+ top var(--xh-motion-duration-move) var(--xh-motion-ease-continuous);
104
114
  }
105
115
 
106
116
  [data-scope='tour'][data-part='spotlight'][data-dimmed] {
@@ -111,7 +121,7 @@
111
121
  display: none;
112
122
  }
113
123
 
114
- [data-scope='tour'][data-part='spotlight'][data-state='open'] {
124
+ [data-scope='tour'][data-part='spotlight'][data-state='open']:not([data-instant]) {
115
125
  animation: xh-tour-spotlight-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter-strong);
116
126
  }
117
127
 
@@ -209,7 +219,8 @@
209
219
  /* 焦点落在气泡本身,Enter / Space 落在它身上推进下一步:它是可操作目标,
210
220
  环由 focus.css 的默认内收环画,这里不另写 */
211
221
 
212
- [data-scope='tour'][data-part='content'][data-state='open'] {
222
+ /* 挂载时就开着的那一段(data-instant)属于首帧,直接呈现 */
223
+ [data-scope='tour'][data-part='content'][data-state='open']:not([data-instant]) {
213
224
  animation: xh-overlay-pop-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter-strong);
214
225
  }
215
226
 
@@ -236,7 +247,7 @@
236
247
  /* 说明文字走说明档:13px + fg-muted */
237
248
  [data-scope='tour'][data-part='description'] {
238
249
  margin: 0;
239
- font-size: var(--xh-text-secondary-size);
250
+ font-size: var(--xh-tour-description-font-size, var(--xh-text-secondary-size));
240
251
  line-height: var(--xh-leading-normal);
241
252
  color: var(--xh-tour-description-fg, var(--xh-fg-muted));
242
253
  }
@@ -266,7 +277,7 @@
266
277
  border-radius: var(--xh-shape-circle);
267
278
  background: var(--xh-tour-progress-dot-bg, var(--xh-border-default));
268
279
  transition:
269
- inline-size var(--xh-motion-duration-micro) var(--xh-motion-ease-continuous),
280
+ inline-size var(--xh-motion-duration-nudge) var(--xh-motion-ease-continuous),
270
281
  background-color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
271
282
  }
272
283
 
@@ -406,4 +417,23 @@
406
417
  display: none;
407
418
  }
408
419
  }
420
+
421
+ @media (forced-colors: active) {
422
+ /* 兜底字形是 mask 挖出来的一块底色,这一档里底色被系统换成 Canvas,字形跟着消失。
423
+ 字形退出强制换色、直接写系统色(不能取 currentColor:退出强制换色的伪元素读到的是被系统换掉之前的作者色):
424
+ 按钮面上取 ButtonText,悬停与按下时家族把按钮涂成 Highlight、字形换 HighlightText;
425
+ 禁用取 GrayText */
426
+ [data-scope='tour'][data-part='close-trigger']:empty::before {
427
+ forced-color-adjust: none;
428
+ background-color: ButtonText;
429
+ }
430
+
431
+ [data-scope='tour'][data-part='close-trigger']:not([data-disabled]):not([data-loading]):is(:hover, :active, [data-pressed]):empty::before {
432
+ background-color: HighlightText;
433
+ }
434
+
435
+ [data-scope='tour'][data-part='close-trigger'][data-disabled]:empty::before {
436
+ background-color: GrayText;
437
+ }
438
+ }
409
439
  }
package/css/transfer.css CHANGED
@@ -6,7 +6,11 @@
6
6
  /* 定义 transfer 样式。 */
7
7
 
8
8
  @import '../family/action-control.css';
9
+ @import '../family/check-mark.css';
9
10
  @import '../family/collection-item.css';
11
+ @import '../family/field-chrome.css';
12
+ @import '../family/loading-ring.css';
13
+ @import '../family/motion.css';
10
14
 
11
15
  @layer xihan.components {
12
16
  /* 皮肤在场的标记:开发模式下按 scope 探一次,取不到就说明这份皮肤没被引入。 */
@@ -27,9 +31,10 @@
27
31
  /* 两侧定高小列表的自绘条挂在根上:根是条子的定位盒(面板不定位),轨道底色关掉才不会在列表边缘糊出一条灰带 */
28
32
  --xh-scrollbar-track-bg: transparent;
29
33
 
30
- /* 尺寸档只换这五个私有槽,面板头、搜索框、条目与勾选格都从 root 继承它们,
34
+ /* 尺寸档只换这六个私有槽,面板头、搜索框、条目与勾选格都从 root 继承它们,
31
35
  换档不必给每个部件各写一条选择器 */
32
36
  --xh-_transfer-item-py: var(--xh-list-option-py-md);
37
+ --xh-_transfer-h: var(--xh-control-h-md);
33
38
  --xh-_transfer-px: var(--xh-control-px-md);
34
39
  --xh-_transfer-gap: var(--xh-control-gap-md);
35
40
  --xh-_transfer-font-size: var(--xh-control-font-md);
@@ -53,6 +58,7 @@
53
58
  [data-scope='transfer'][data-part='root'][data-size='sm'] {
54
59
  --xh-icon-size: var(--xh-transfer-icon-size, var(--xh-glyph-size-sm));
55
60
  --xh-_transfer-item-py: var(--xh-list-option-py-sm);
61
+ --xh-_transfer-h: var(--xh-control-h-sm);
56
62
  --xh-_transfer-px: var(--xh-control-px-sm);
57
63
  --xh-_transfer-gap: var(--xh-control-gap-sm);
58
64
  --xh-_transfer-font-size: var(--xh-control-font-sm);
@@ -62,6 +68,7 @@
62
68
  [data-scope='transfer'][data-part='root'][data-size='lg'] {
63
69
  --xh-icon-size: var(--xh-transfer-icon-size, var(--xh-glyph-size-lg));
64
70
  --xh-_transfer-item-py: var(--xh-list-option-py-lg);
71
+ --xh-_transfer-h: var(--xh-control-h-lg);
65
72
  --xh-_transfer-px: var(--xh-control-px-lg);
66
73
  --xh-_transfer-gap: var(--xh-control-gap-lg);
67
74
  --xh-_transfer-font-size: var(--xh-control-font-lg);
@@ -163,19 +170,32 @@
163
170
  content: attr(data-checked-count) ' / ' attr(data-count);
164
171
  }
165
172
 
173
+ /* 面板内嵌的搜索框:通栏一行,控件高与字号随尺寸档,只画一道面内分隔的下划线(取面材质的分隔令牌);
174
+ 重置与占位前景由字段家族按 data-xh-field-input 给 */
166
175
  [data-scope='transfer'][data-part='search'] {
176
+ --xh-field-placeholder-fg: var(--xh-transfer-placeholder-fg, var(--xh-fg-subtle));
177
+ /* 自动填充铺的底取所在面板的面,不取画布:否则框内比周围亮出(或暗出)一块 */
178
+ --xh-field-autofill-bg: var(--xh-bg-surface);
179
+
167
180
  display: block;
181
+ flex: none;
168
182
  inline-size: 100%;
169
- block-size: var(--xh-transfer-search-h, var(--xh-control-h-sm));
183
+ block-size: var(--xh-transfer-search-h, var(--xh-_transfer-h));
184
+ padding-block: 0;
170
185
  padding-inline: var(--xh-transfer-search-px, var(--xh-_transfer-px));
171
186
  border: 0;
172
- border-block-end: var(--xh-stroke-thin) solid var(--xh-transfer-search-border, var(--xh-border-control));
187
+ border-block-end: var(--xh-stroke-thin) solid var(--xh-transfer-search-border, var(--xh-material-solid-separator));
173
188
  background: var(--xh-transfer-search-bg, transparent);
174
189
  color: var(--xh-transfer-search-fg, var(--xh-fg-default));
175
190
  font: inherit;
176
191
  font-size: var(--xh-transfer-search-font-size, var(--xh-_transfer-font-size));
177
192
  }
178
193
 
194
+ /* 聚焦不画环:插入符就是焦点指示。框没有外壳,全局那圈键盘焦点环只会压在通栏一行上 */
195
+ [data-scope='transfer'][data-part='search']:focus-visible {
196
+ outline: none;
197
+ }
198
+
179
199
  /* 单体控件的禁用是原生 disabled,样式只能写 :disabled;集合条目才读 data-disabled */
180
200
  [data-scope='transfer'][data-part='search']:disabled {
181
201
  color: var(--xh-fg-disabled);
@@ -193,6 +213,38 @@
193
213
  padding-block: var(--xh-transfer-list-py, var(--xh-space-1));
194
214
  padding-inline: var(--xh-transfer-list-px, var(--xh-space-1));
195
215
  overflow: auto;
216
+ transition: opacity var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
217
+ }
218
+
219
+ /* 刷新:这一侧已有条目时后台在取,保留上一帧,列表按 micro 淡下,数据到了再淡回;在途占位此时让位 */
220
+ [data-scope='transfer'][data-part='list'][aria-busy='true'] {
221
+ opacity: var(--xh-state-disabled-opacity);
222
+ }
223
+
224
+ /* Virtualizer viewport 才是真正的滚动层。此时 list 只负责 Transfer 的定高,
225
+ 不再用内边距和自身滚动制造第二个窗口;viewport 铺满整块内容区,
226
+ 贴着它量出的自绘滚动条也就从列表顶一直落到列表底。 */
227
+ [data-scope='transfer'][data-part='list'][data-xh-virtualized] {
228
+ gap: 0;
229
+ min-block-size: 0;
230
+ padding: 0;
231
+ overflow: hidden;
232
+ }
233
+
234
+ /* Vue / React 的 virtualizer root 在 list 外,下面第一条只命中 viewport;
235
+ Web Components 的 Light DOM root 在 list 内,两条一起命中。
236
+ Web 作者把 xh-virtualizer 宿主设为 display: contents 后,root 会直接参与 list 的 flex 排布。 */
237
+ [data-scope='transfer'][data-part='list'][data-xh-virtualized]
238
+ [data-scope='virtualizer'][data-part='root'] {
239
+ flex: 1 1 auto;
240
+ min-block-size: 0;
241
+ block-size: 100%;
242
+ }
243
+
244
+ [data-scope='transfer'][data-part='list'][data-xh-virtualized]
245
+ [data-scope='virtualizer'][data-part='viewport'] {
246
+ min-block-size: 0;
247
+ block-size: 100%;
196
248
  }
197
249
 
198
250
  /* 焦点在列表外时容器自己进 Tab 序列兜底,落焦不画环:高亮永远长在条目上 */
@@ -207,7 +259,7 @@
207
259
  flex: none;
208
260
  padding-block: var(--xh-transfer-empty-py, var(--xh-space-3));
209
261
  padding-inline: var(--xh-transfer-empty-px, var(--xh-_transfer-px));
210
- color: var(--xh-transfer-empty-fg, var(--xh-fg-subtle));
262
+ color: var(--xh-transfer-empty-fg, var(--xh-fg-muted));
211
263
  font-size: var(--xh-transfer-empty-font-size, var(--xh-_transfer-font-size));
212
264
  line-height: var(--xh-leading-none);
213
265
  text-align: center;
@@ -224,11 +276,14 @@
224
276
 
225
277
  /* 在途占位:与空态占位同一个位置、同一副观感,两者不同屏 */
226
278
  [data-scope='transfer'][data-part='loading'] {
227
- display: block;
279
+ display: flex;
280
+ align-items: center;
281
+ justify-content: center;
282
+ gap: var(--xh-_transfer-gap);
228
283
  flex: none;
229
284
  padding-block: var(--xh-transfer-loading-py, var(--xh-space-3));
230
285
  padding-inline: var(--xh-transfer-loading-px, var(--xh-_transfer-px));
231
- color: var(--xh-transfer-loading-fg, var(--xh-fg-subtle));
286
+ color: var(--xh-transfer-loading-fg, var(--xh-fg-muted));
232
287
  font-size: var(--xh-transfer-loading-font-size, var(--xh-_transfer-font-size));
233
288
  line-height: var(--xh-leading-none);
234
289
  text-align: center;
@@ -238,6 +293,15 @@
238
293
  display: none;
239
294
  }
240
295
 
296
+ /* 首次加载:文案前一枚加载环,由加载环配方画(连接层投影 data-xh-loading-ring 与 data-loading),
297
+ 直径取所在尺寸档的图标档 */
298
+ [data-scope='transfer'][data-part='loading']::before {
299
+ content: '';
300
+ flex: none;
301
+ inline-size: var(--xh-icon-size, var(--xh-glyph-size-md));
302
+ block-size: var(--xh-icon-size, var(--xh-glyph-size-md));
303
+ }
304
+
241
305
  [data-scope='transfer'][data-part='loading'][data-disabled] {
242
306
  color: var(--xh-fg-disabled);
243
307
  }
@@ -270,7 +334,7 @@
270
334
  display: block;
271
335
  padding-block: var(--xh-transfer-group-label-py, var(--xh-space-1));
272
336
  padding-inline: var(--xh-transfer-group-label-px, var(--xh-_transfer-px));
273
- color: var(--xh-transfer-group-label-fg, var(--xh-fg-subtle));
337
+ color: var(--xh-transfer-group-label-fg, var(--xh-fg-muted));
274
338
  font-size: var(--xh-transfer-group-label-font-size, var(--xh-text-caption-size));
275
339
  font-weight: var(--xh-transfer-group-label-font-weight, var(--xh-font-weight-medium));
276
340
  line-height: var(--xh-leading-none);
@@ -295,7 +359,7 @@
295
359
  --xh-collection-inline-padding: var(--xh-transfer-item-px, var(--xh-_transfer-px));
296
360
  --xh-collection-gap: 0;
297
361
  --xh-collection-font-size: var(--xh-transfer-item-font-size, var(--xh-_transfer-font-size));
298
- --xh-collection-radius: var(--xh-transfer-item-radius, var(--xh-shape-control));
362
+ --xh-collection-radius: var(--xh-transfer-item-radius, var(--xh-shape-inset));
299
363
  /* 作者的图标尺槽经桥接槽下发到条目,家族缺省按 data-xh-collection-size 取档 */
300
364
  --xh-collection-glyph-size: var(--xh-transfer-icon-size, var(--xh-_collection-glyph-size));
301
365
  --xh-collection-fg-rest: var(--xh-transfer-item-fg, var(--xh-fg-default));
@@ -360,7 +424,7 @@
360
424
  font-size: var(--xh-transfer-checkbox-font-size, calc(var(--xh-_transfer-box) * 0.75));
361
425
  line-height: var(--xh-leading-none);
362
426
  transition:
363
- background var(--xh-motion-duration-micro) var(--xh-motion-ease-enter),
427
+ background-color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter),
364
428
  border-color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
365
429
  }
366
430
 
@@ -383,64 +447,9 @@
383
447
  --xh-_ring-color: currentColor;
384
448
  }
385
449
 
386
- /* 没写内容就由皮肤画勾;作者往格子里塞了自己的图形,这条规则即不命中。
387
- 底下两处(全选格的两态)不带这条守卫:触发器里装的是全选那行文字,:empty 恒不命中 */
388
- [data-scope='transfer'][data-part='item-checkbox'][data-state='checked']:empty::before {
389
- content: '';
390
- display: inline-block;
391
- inline-size: var(--xh-icon-size, var(--xh-glyph-size-text));
392
- block-size: var(--xh-icon-size, var(--xh-glyph-size-text));
393
- vertical-align: var(--xh-glyph-baseline-shift);
394
- background-color: currentColor;
395
- -webkit-mask: var(--xh-glyph-mark-check) center / contain no-repeat;
396
- mask: var(--xh-glyph-mark-check) center / contain no-repeat;
397
- }
398
-
399
- /* 全选格的三态:all 打勾、some 打横杠、none 留空。
400
- some 就是读屏听到的 aria-checked=mixed,两者同源,样式只认 data-state。
401
-
402
- 字形走 ::after 而不是画方框的那颗 ::before:mask 会把所在元素的描边与底色一并裁成
403
- 字形的轮廓,写在 ::before 上就等于把方框本身裁没了,只剩一枚白勾。条目那一侧是
404
- 真实节点当方框、它的 ::before 当字形的两层结构,这里没有第二个节点,只能用
405
- 第二个伪元素补上那一层。
406
- 方框是行首那颗 ::before、宽度即勾选格尺寸,所以字形按「盒与字形的宽度差之半」
407
- 内推即落在圆心;纵向靠 inset-block: 0 + margin-block: auto 居中。
408
- font-size 与 ::before 同值,--xh-icon-size 的 1em 兜底才和条目那侧量得一样大 */
409
- [data-scope='transfer'][data-part='select-all-trigger'][data-state='checked']::after {
410
- content: '';
411
- position: absolute;
412
- inset-block: 0;
413
- inset-inline-start: 0;
414
- margin-block: auto;
415
- margin-inline-start: calc(
416
- (var(--xh-transfer-checkbox-size, var(--xh-_transfer-box)) - var(--xh-icon-size, var(--xh-glyph-size-text))) /
417
- 2
418
- );
419
- inline-size: var(--xh-icon-size, var(--xh-glyph-size-text));
420
- block-size: var(--xh-icon-size, var(--xh-glyph-size-text));
421
- background-color: var(--xh-transfer-checkbox-fg, var(--xh-_transfer-on-accent));
422
- font-size: var(--xh-transfer-checkbox-font-size, calc(var(--xh-_transfer-box) * 0.75));
423
- -webkit-mask: var(--xh-glyph-mark-check) center / contain no-repeat;
424
- mask: var(--xh-glyph-mark-check) center / contain no-repeat;
425
- }
426
-
427
- [data-scope='transfer'][data-part='select-all-trigger'][data-state='indeterminate']::after {
428
- content: '';
429
- position: absolute;
430
- inset-block: 0;
431
- inset-inline-start: 0;
432
- margin-block: auto;
433
- margin-inline-start: calc(
434
- (var(--xh-transfer-checkbox-size, var(--xh-_transfer-box)) - var(--xh-icon-size, var(--xh-glyph-size-text))) /
435
- 2
436
- );
437
- inline-size: var(--xh-icon-size, var(--xh-glyph-size-text));
438
- block-size: var(--xh-icon-size, var(--xh-glyph-size-text));
439
- background-color: var(--xh-transfer-checkbox-fg, var(--xh-_transfer-on-accent));
440
- font-size: var(--xh-transfer-checkbox-font-size, calc(var(--xh-_transfer-box) * 0.75));
441
- -webkit-mask: var(--xh-glyph-mark-minus) center / contain no-repeat;
442
- mask: var(--xh-glyph-mark-minus) center / contain no-repeat;
443
- }
450
+ /* 格子里的勾与全选格的三态标记(all 打勾、some 打横杠、none 留空)由勾选标记配方画:常驻、按状态淡变
451
+ 并轻缩放。条目的格子是真实节点,标记是它的 ::before;全选格自己就是按钮,方框只能是行首那颗 ::before,
452
+ 标记是 ::after、叠在方框正中——两支桥接槽写在全选格上 */
444
453
 
445
454
  [data-scope='transfer'][data-part='item-checkbox'][data-disabled] {
446
455
  border-color: var(--xh-transfer-checkbox-border-disabled, var(--xh-border-default));
@@ -455,6 +464,9 @@
455
464
  [data-scope='transfer'][data-part='select-all-trigger'] {
456
465
  /* 叠在方框上的那颗字形 ::after 与方框里的勾同一把尺 */
457
466
  --xh-icon-size: calc(var(--xh-transfer-checkbox-size, var(--xh-_transfer-box)) * 0.75);
467
+ /* 勾选标记叠在行首方框正中:方框边长与标记色交给配方 */
468
+ --xh-check-mark-box-size: var(--xh-transfer-checkbox-size, var(--xh-_transfer-box));
469
+ --xh-check-mark-fg: var(--xh-transfer-checkbox-fg, var(--xh-_transfer-on-accent));
458
470
 
459
471
  --xh-action-min-inline-size: 0;
460
472
  --xh-action-padding-inline: 0;
@@ -476,14 +488,6 @@
476
488
  font-weight: var(--xh-font-weight-medium);
477
489
  }
478
490
 
479
- /* 家族 text 档在粗指针下用 ::after 扩热区,勾中 / 半选时这里的 ::after 是叠在方框上的字形:钉回原位、下限归零 */
480
- @media (pointer: coarse) {
481
- [data-scope='transfer'][data-part='select-all-trigger']:is([data-state='checked'], [data-state='indeterminate'])::after {
482
- min-block-size: 0;
483
- translate: none;
484
- }
485
- }
486
-
487
491
  /* 两颗搬运钮是只有字形的离散动作钮:接 Action Control 的 icon 档、outline 形态(中性描边、透明底,
488
492
  白底承载 hover 100 → pressed 200,按下 0.97 并同时换底),盒、焦点环、粗指针热区与禁用面由家族给;
489
493
  此前的 raised 悬停抬升退役(边界只由描边承担)。这里只把使用者槽映射到桥接槽;
@@ -612,4 +616,23 @@
612
616
  mask: var(--xh-glyph-mark-chevron-right) center / contain no-repeat;
613
617
  }
614
618
  }
619
+
620
+ @media (forced-colors: active) {
621
+ /* 兜底字形是 mask 挖出来的一块底色,这一档里底色被系统换成 Canvas,字形跟着消失。
622
+ 字形退出强制换色、直接写系统色(不能取 currentColor:退出强制换色的伪元素读到的是被系统换掉之前的作者色):
623
+ 按钮面上取 ButtonText,悬停与按下时家族把按钮涂成 Highlight、字形换 HighlightText;
624
+ 禁用取 GrayText */
625
+ [data-scope='transfer']:is([data-part='to-source-trigger'], [data-part='to-target-trigger']):empty::before {
626
+ forced-color-adjust: none;
627
+ background-color: ButtonText;
628
+ }
629
+
630
+ [data-scope='transfer']:is([data-part='to-source-trigger'], [data-part='to-target-trigger']):not([data-disabled]):not([data-loading]):is(:hover, :active, [data-pressed]):empty::before {
631
+ background-color: HighlightText;
632
+ }
633
+
634
+ [data-scope='transfer']:is([data-part='to-source-trigger'], [data-part='to-target-trigger'])[data-disabled]:empty::before {
635
+ background-color: GrayText;
636
+ }
637
+ }
615
638
  }