@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/editable.css CHANGED
@@ -273,4 +273,23 @@
273
273
  forced-color-adjust: none;
274
274
  }
275
275
  }
276
+
277
+ @media (forced-colors: active) {
278
+ /* 兜底字形是 mask 挖出来的一块底色,这一档里底色被系统换成 Canvas,字形跟着消失。
279
+ 字形退出强制换色、直接写系统色(不能取 currentColor:退出强制换色的伪元素读到的是被系统换掉之前的作者色):
280
+ 按钮面上取 ButtonText,悬停与按下时家族把按钮涂成 Highlight、字形换 HighlightText;
281
+ 禁用取 GrayText */
282
+ [data-scope='editable']:is([data-part='edit-trigger'], [data-part='submit-trigger'], [data-part='cancel-trigger']):empty::before {
283
+ forced-color-adjust: none;
284
+ background-color: ButtonText;
285
+ }
286
+
287
+ [data-scope='editable']:is([data-part='edit-trigger'], [data-part='submit-trigger'], [data-part='cancel-trigger']):not([data-disabled]):not([data-loading]):is(:hover, :active, [data-pressed]):empty::before {
288
+ background-color: HighlightText;
289
+ }
290
+
291
+ [data-scope='editable']:is([data-part='edit-trigger'], [data-part='submit-trigger'], [data-part='cancel-trigger'])[data-disabled]:empty::before {
292
+ background-color: GrayText;
293
+ }
294
+ }
276
295
  }
@@ -131,12 +131,13 @@
131
131
  }
132
132
 
133
133
  /* 图标、标题、说明、动作按次序开幕,间隔由交错步长派生。
134
- backwards 不能省:等待期间起帧不生效的话,后三段会先露一下再跳回去 */
135
- [data-scope='empty-state'][data-part='media'],
136
- [data-scope='empty-state'][data-part='indicator'],
137
- [data-scope='empty-state'][data-part='title'],
138
- [data-scope='empty-state'][data-part='description'],
139
- [data-scope='empty-state'][data-part='action'] {
134
+ backwards 不能省:等待期间起帧不生效的话,后三段会先露一下再跳回去。
135
+ 随页面首屏就在的空状态带 data-instant 直接呈现,之后每一次出现才开幕 */
136
+ [data-scope='empty-state'][data-part='media']:not([data-instant]),
137
+ [data-scope='empty-state'][data-part='indicator']:not([data-instant]),
138
+ [data-scope='empty-state'][data-part='title']:not([data-instant]),
139
+ [data-scope='empty-state'][data-part='description']:not([data-instant]),
140
+ [data-scope='empty-state'][data-part='action']:not([data-instant]) {
140
141
  animation: xh-rise-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter-strong) backwards;
141
142
  }
142
143
 
@@ -7,6 +7,8 @@
7
7
 
8
8
  /* FieldArray 独立皮肤同时传递引入把手的动作控件配方;full bundle 递归去重。 */
9
9
  @import '../family/action-control.css';
10
+ /* 行的进场与退场取共享关键帧。 */
11
+ @import '../family/motion.css';
10
12
 
11
13
  @layer xihan.components {
12
14
  /* 皮肤在场的标记:开发模式下按 scope 探一次,取不到就说明这份皮肤没被引入。 */
@@ -19,6 +21,8 @@
19
21
  display: flex;
20
22
  flex-direction: column;
21
23
  gap: var(--xh-field-array-gap, var(--xh-space-2));
24
+ /* 删掉的行由替身绝对定位在原处播完退场,定位基准是根节点 */
25
+ position: relative;
22
26
  }
23
27
 
24
28
  /* 声明了 display 就盖掉了 UA 的 [hidden]{display:none},收起态得自己还回去 */
@@ -33,6 +37,19 @@
33
37
  gap: var(--xh-field-array-item-gap, var(--xh-space-2));
34
38
  padding: var(--xh-field-array-item-padding, var(--xh-space-0));
35
39
  border-radius: var(--xh-field-array-item-radius, var(--xh-shape-surface));
40
+ /* 增删与上移下移带来的换位:由列表动效先反向补偿、再沿这条过渡滑到新位置 */
41
+ transition: translate var(--xh-motion-duration-move) var(--xh-motion-ease-continuous);
42
+ }
43
+
44
+ /* 新增的行进场,同一批按到达顺序错开;首帧就在的直接呈现(自己或根节点带 data-instant) */
45
+ [data-scope='field-array'][data-part='item']:not([data-instant], [data-instant] > *) {
46
+ animation: xh-item-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter-strong) backwards;
47
+ animation-delay: calc(var(--xh-_stagger-index, 0) * var(--xh-motion-stagger-step));
48
+ }
49
+
50
+ /* 删掉的行:替身留在原处淡出,播完即移除 */
51
+ [data-scope='field-array'][data-part='item'][data-state='closed'] {
52
+ animation: xh-fade-out var(--xh-motion-duration-exit) var(--xh-motion-ease-exit) forwards;
36
53
  }
37
54
 
38
55
  [data-scope='field-array'][data-part='item'][hidden] {
@@ -187,4 +204,23 @@
187
204
  [data-scope='field-array'][data-part='item-action'][hidden] {
188
205
  display: none;
189
206
  }
207
+
208
+ @media (forced-colors: active) {
209
+ /* 兜底字形是 mask 挖出来的一块底色,这一档里底色被系统换成 Canvas,字形跟着消失。
210
+ 字形退出强制换色、直接写系统色(不能取 currentColor:退出强制换色的伪元素读到的是被系统换掉之前的作者色):
211
+ 按钮面上取 ButtonText,悬停与按下时家族把按钮涂成 Highlight、字形换 HighlightText;
212
+ 禁用取 GrayText */
213
+ [data-scope='field-array']:is([data-part='move-up-trigger'], [data-part='move-down-trigger'], [data-part='item-delete-trigger']):empty::before {
214
+ forced-color-adjust: none;
215
+ background-color: ButtonText;
216
+ }
217
+
218
+ [data-scope='field-array']:is([data-part='move-up-trigger'], [data-part='move-down-trigger'], [data-part='item-delete-trigger']):not([data-disabled]):not([data-loading]):is(:hover, :active, [data-pressed]):empty::before {
219
+ background-color: HighlightText;
220
+ }
221
+
222
+ [data-scope='field-array']:is([data-part='move-up-trigger'], [data-part='move-down-trigger'], [data-part='item-delete-trigger'])[data-disabled]:empty::before {
223
+ background-color: GrayText;
224
+ }
225
+ }
190
226
  }
package/css/field.css CHANGED
@@ -53,11 +53,6 @@
53
53
  grid-column: 2;
54
54
  }
55
55
 
56
- /* 横排下这一列是网格的自动流,占位块跟着控件走第二列(选择器不匹配伪元素,单列一条) */
57
- [data-scope='form'][data-part='root'][data-layout='horizontal'] [data-scope='field'][data-part='root']::after {
58
- grid-column: 2;
59
- }
60
-
61
56
  [data-scope='field'][data-part='label'] {
62
57
  /* 一段不带空格的长标识(字段名、英文标识符)在窄处从词内断开:
63
58
  横排表单里标签列是一段比例宽度,这行让标签在那一列里折行,而不是从列的左边顶出去 */
@@ -71,6 +66,8 @@
71
66
  color: var(--xh-field-label-fg, var(--xh-fg-default));
72
67
  font-size: var(--xh-field-label-font-size, var(--xh-text-label-size));
73
68
  font-weight: var(--xh-field-label-font-weight, var(--xh-text-label-weight));
69
+ /* 转无效或转禁用时标签换色,两档之间接一段过渡(与字段集组标题同一档) */
70
+ transition: color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
74
71
  }
75
72
 
76
73
  [data-scope='field'][data-part='label'][data-disabled] {
@@ -121,6 +118,8 @@
121
118
  [data-scope='field'][data-part='description'] {
122
119
  color: var(--xh-field-description-fg, var(--xh-fg-muted));
123
120
  font-size: var(--xh-field-description-font-size, var(--xh-text-secondary-size));
121
+ /* 与标签同一档过渡:整个字段转禁用时两处一起变淡 */
122
+ transition: color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
124
123
  }
125
124
 
126
125
  [data-scope='field'][data-part='description'][data-disabled] {
@@ -132,27 +131,26 @@
132
131
  font-size: var(--xh-field-error-font-size, var(--xh-text-secondary-size));
133
132
  }
134
133
 
135
- /* 辅助文字那一行的占位:字段渲染了错误文案、当前又没在报错时,补一个与错误文案等高的空块。
136
- 报错时这个空块让位给错误文案,字段的外框高度前后一样,表单不会因为一段错误文字重新排版,
137
- 定高或限高的父级里也不会把控件挤出去。
134
+ /* 辅助文字那一行的占位:字段渲染了错误文案、当前又没在报错时,收起的错误文案自己留在流里占住一行
135
+ (不可见、不进可及树),与报错时的错误文案等高。字段的外框高度前后一样,表单不会因为一段错误文字
136
+ 重新排版,定高或限高的父级里也不会把控件挤出去;退场就在这一行里淡出,播完才藏起。
138
137
 
139
- 两个条件缺一不可:
140
- 渲染了 error-text 且它带 hidden——没有错误文案可显示的字段不占这一行;
141
- 没有在场的 description——描述已经占着这一行了,报错时由描述让位给错误文案(见 description.css)。
138
+ 没有在场的 description 才这样占:描述已经占着这一行了,报错时由描述让位给错误文案(见 description.css),
139
+ 收起的错误文案按公共层不占位。没渲染错误文案的字段本来就没有这一行。
142
140
 
143
141
  高度按错误文案那一档的字号乘行距算,得到的正是它一行的高度。
144
142
  不写 1lh:那个单位晚于本库声明的浏览器地板,取不到时整条声明被丢弃、占位高退回 0,
145
143
  字段又会随报错撑高一次,而这种失效不报错。
146
- 与前一块的距离由根的 gap 给,和错误文案顶上来时是同一段。 */
147
- [data-scope='field'][data-part='root']:has(> [data-scope='field'][data-part='error-text'][hidden]):not(
148
- :has(> [data-scope='field'][data-part='description']:not([hidden]))
149
- )::after {
150
- content: '';
144
+ 与前一块的距离由根的 gap 给,和错误文案顶上来时是同一段;横排表单里它与别的子块一样落在控件那一列。 */
145
+ [data-scope='field'][data-part='root']:not(:has(> [data-scope='field'][data-part='description']:not([hidden])))
146
+ > [data-scope='field'][data-part='error-text'][hidden] {
147
+ position: static;
151
148
  block-size: calc(
152
149
  var(--xh-field-error-font-size, var(--xh-text-secondary-size)) * var(--xh-leading-normal)
153
150
  );
154
- font-size: var(--xh-field-error-font-size, var(--xh-text-secondary-size));
155
- line-height: var(--xh-leading-normal);
151
+ transition:
152
+ opacity var(--xh-motion-duration-exit) var(--xh-motion-ease-exit),
153
+ visibility 0s var(--xh-motion-duration-exit);
156
154
  }
157
155
 
158
156
  /* 声明过任何 display 就盖掉了 UA 的 [hidden]{display:none},收起态得自己还回去。
@@ -5,6 +5,10 @@
5
5
 
6
6
  /* 定义 file upload 样式。 */
7
7
 
8
+ /* 文件行的进场、退场与完成对号的淡入取共享关键帧。 */
9
+ @import '../family/motion.css';
10
+ @import '../family/action-control.css';
11
+
8
12
  @layer xihan.components {
9
13
  /* 皮肤在场的标记:开发模式下按 scope 探一次,取不到就说明这份皮肤没被引入。 */
10
14
  [data-scope='file-upload'] {
@@ -95,54 +99,34 @@
95
99
  color: var(--xh-fg-disabled);
96
100
  }
97
101
 
102
+ /* 「选择文件」是定尺的独立动作按钮:盒型、三档几何、悬停 100 / 按下 200 与 0.97 按压、焦点环、禁用面与
103
+ 粗指针命中区由 Action Control 家族按 text 档 outline 给(连接层投影),不再悬停抬 raised 影。
104
+ 这里只把使用者槽桥接到矩阵之前(使用者槽 → 形态矩阵 → 家族缺省) */
98
105
  [data-scope='file-upload'][data-part='trigger'] {
99
- display: inline-flex;
106
+ --xh-action-bg-rest: var(--xh-file-upload-trigger-bg, var(--xh-_action-variant-bg-rest));
107
+ --xh-action-bg-hover: var(--xh-file-upload-trigger-bg-hover, var(--xh-_action-variant-bg-hover));
108
+ --xh-action-bg-pressed: var(--xh-file-upload-trigger-bg-active, var(--xh-_action-variant-bg-pressed));
109
+ --xh-action-fg-rest: var(--xh-file-upload-trigger-fg, var(--xh-_action-variant-fg-rest));
110
+ --xh-action-fg-hover: var(--xh-file-upload-trigger-fg, var(--xh-_action-variant-fg-hover));
111
+ --xh-action-fg-pressed: var(--xh-file-upload-trigger-fg, var(--xh-_action-variant-fg-pressed));
112
+ --xh-action-border-rest: var(--xh-file-upload-trigger-border, var(--xh-_action-variant-border-rest));
113
+ /* 矩阵各态无影:使用者槽仍可自行加 */
114
+ --xh-action-shadow-hover: var(--xh-file-upload-trigger-shadow-hover, none);
115
+ --xh-action-radius: var(--xh-file-upload-trigger-radius, var(--xh-shape-control));
116
+ --xh-action-visual-size: var(--xh-file-upload-trigger-h, var(--xh-_action-profile-visual-size));
117
+ --xh-action-padding-inline: var(--xh-file-upload-trigger-px, var(--xh-_action-profile-padding-inline));
118
+ --xh-action-gap: var(--xh-file-upload-trigger-gap, var(--xh-_action-profile-gap));
119
+ --xh-action-font-size: var(--xh-file-upload-trigger-font-size, var(--xh-_action-profile-font-size));
120
+ --xh-icon-size: var(--xh-_action-profile-glyph-size);
121
+
100
122
  flex: none;
101
- align-items: center;
102
- justify-content: center;
103
- gap: var(--xh-file-upload-trigger-gap, var(--xh-control-gap-md));
104
- block-size: var(--xh-file-upload-trigger-h, var(--xh-control-h-md));
105
- padding-inline: var(--xh-file-upload-trigger-px, var(--xh-control-px-md));
106
- border: var(--xh-stroke-thin) solid var(--xh-file-upload-trigger-border, var(--xh-border-control));
107
- border-radius: var(--xh-file-upload-trigger-radius, var(--xh-shape-control));
108
- background: var(--xh-file-upload-trigger-bg, var(--xh-bg-surface));
109
- color: var(--xh-file-upload-trigger-fg, var(--xh-fg-default));
110
- /* 表单控件不继承页面字体,只给字号会让按钮用 UA 默认字族 */
111
- font-family: inherit;
112
- font-size: var(--xh-file-upload-trigger-font-size, var(--xh-text-body-size));
113
- line-height: var(--xh-leading-none);
114
- cursor: pointer;
115
- transition:
116
- background var(--xh-motion-duration-micro) var(--xh-motion-ease-enter),
117
- box-shadow var(--xh-motion-duration-micro) var(--xh-motion-ease-enter),
118
- scale var(--xh-motion-duration-release) var(--xh-motion-ease-release);
123
+ font-weight: var(--xh-file-upload-trigger-font-weight, var(--xh-text-label-weight));
119
124
  }
120
125
 
121
126
  [data-scope='file-upload'][data-part='trigger'][hidden] {
122
127
  display: none;
123
128
  }
124
129
 
125
- /* 悬停换底色的同时把这块面抬起一档:只换底色的回执在一颗按钮上太轻 */
126
- [data-scope='file-upload'][data-part='trigger']:hover:not(:disabled) {
127
- background: var(--xh-file-upload-trigger-bg-hover, var(--xh-bg-subtle-hover));
128
- box-shadow: var(--xh-file-upload-trigger-shadow-hover, var(--xh-elevation-raised));
129
- }
130
-
131
- /* 按下:换到按压档底色并轻微下压,抬起的那一档同时落回,松手弹回;禁用钮不压 */
132
- [data-scope='file-upload'][data-part='trigger']:not(:disabled):is(:active, [data-pressed]) {
133
- background: var(--xh-file-upload-trigger-bg-active, var(--xh-bg-subtle-active));
134
- box-shadow: none;
135
- scale: var(--xh-motion-scale-press);
136
- transition-duration: var(--xh-motion-duration-press);
137
- transition-timing-function: var(--xh-motion-ease-press);
138
- }
139
-
140
- /* 单体控件走原生 disabled(与集合条目的 aria-disabled 相反),因此用 :disabled */
141
- [data-scope='file-upload'][data-part='trigger']:disabled {
142
- background: var(--xh-bg-subtle);
143
- color: var(--xh-fg-disabled);
144
- }
145
-
146
130
  /* 连接层已经把这一套视觉隐藏写进了内联 style,这里是给元素升级前那一瞬兜底
147
131
  (作者手写的是一个普通 file input,接线完成前它是可见的)。
148
132
  刻意不写 display:藏起来的输入仍要留在布局里,原生表单校验的气泡才有可指的锚点 */
@@ -159,8 +143,10 @@
159
143
  }
160
144
 
161
145
  /* 文件条数由用户一次选进来多少给,行数没有尽头:
162
- 列表到这个高度就不再长,剩下的行在列表内滚出来,投放区与触发钮不会被推出视野 */
146
+ 列表到这个高度就不再长,剩下的行在列表内滚出来,投放区与触发钮不会被推出视野。
147
+ 定位是给列表动效放离场替身用的:删掉的那一行在原排布位上播完退场 */
163
148
  [data-scope='file-upload'][data-part='list'] {
149
+ position: relative;
164
150
  display: flex;
165
151
  max-block-size: var(--xh-file-upload-list-max-h, var(--xh-viewport-h-md));
166
152
  overflow-y: auto;
@@ -192,6 +178,23 @@
192
178
  font-size: var(--xh-file-upload-item-font-size, var(--xh-text-body-size));
193
179
  }
194
180
 
181
+ /* 列表动效:新收下的文件进场,同一批按到达顺序错开;首帧就在的直接呈现(自己或列表带 data-instant)。
182
+ 删掉的那一行由替身留在原处淡出,播完即移除;其余行先按旧位置反向补偿、再沿 translate 过渡滑到新位置 */
183
+ [data-scope='file-upload'][data-part='item'] {
184
+ transition: translate var(--xh-motion-duration-move) var(--xh-motion-ease-continuous);
185
+ }
186
+
187
+ [data-scope='file-upload'][data-part='item']:not([data-instant], [data-instant] > *) {
188
+ animation: xh-item-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter-strong) backwards;
189
+ animation-delay: calc(var(--xh-_stagger-index, 0) * var(--xh-motion-stagger-step));
190
+ }
191
+
192
+ /* 替身的 data-state 换成了 closed,传输状态从进度条(同一份状态事实)上读回来:
193
+ 失败与传完那一行退场时仍带着行首标记与失败配色,文件名不会在淡出途中往行首一跳 */
194
+ [data-scope='file-upload'][data-part='item'][data-state='closed'] {
195
+ animation: xh-fade-out var(--xh-motion-duration-exit) var(--xh-motion-ease-exit) forwards;
196
+ }
197
+
195
198
  [data-scope='file-upload'][data-part='item'][hidden] {
196
199
  display: none;
197
200
  }
@@ -206,12 +209,14 @@
206
209
  警示与下面的勾都是固定状态标记(不是 :empty 兜底,作者塞进条目的部件顶不掉):按指示符档取尺
207
210
  (comfortable 16 / compact 14,随密度换档),不读只管作者图标的 --xh-icon-size——随文 1em 落在
208
211
  14 上下,comfortable 下比同一行里的指示符档小一圈、compact 下又不换档 */
209
- [data-scope='file-upload'][data-part='item'][data-state='error'] {
212
+ [data-scope='file-upload'][data-part='item'][data-state='error'],
213
+ [data-scope='file-upload'][data-part='item'][data-state='closed']:has(> [data-part='item-progress'][data-state='error']) {
210
214
  border-color: var(--xh-file-upload-item-border-error, var(--xh-border-invalid));
211
215
  color: var(--xh-file-upload-item-fg-error, var(--xh-fg-danger));
212
216
  }
213
217
 
214
- [data-scope='file-upload'][data-part='item'][data-state='error']::before {
218
+ [data-scope='file-upload'][data-part='item'][data-state='error']::before,
219
+ [data-scope='file-upload'][data-part='item'][data-state='closed']:has(> [data-part='item-progress'][data-state='error'])::before {
215
220
  content: '';
216
221
  flex: none;
217
222
  inline-size: var(--xh-file-upload-item-mark-size, var(--xh-control-indicator-size));
@@ -223,7 +228,8 @@
223
228
 
224
229
  /* 传完的那一行补一枚对勾,与失败那一枚警示字形对称:
225
230
  不标出来的话「传完了」与「还没开始」在这一行上看不出分别 */
226
- [data-scope='file-upload'][data-part='item'][data-state='done']::before {
231
+ [data-scope='file-upload'][data-part='item'][data-state='done']::before,
232
+ [data-scope='file-upload'][data-part='item'][data-state='closed']:has(> [data-part='item-progress'][data-state='done'])::before {
227
233
  content: '';
228
234
  flex: none;
229
235
  inline-size: var(--xh-file-upload-item-mark-size, var(--xh-control-indicator-size));
@@ -233,30 +239,42 @@
233
239
  mask: var(--xh-glyph-mark-check) center / contain no-repeat;
234
240
  }
235
241
 
236
- /* 传输进度条:只在传输中露面,宽度按连接层写下的比例走。
237
- 进度是连续量,走 continuous 曲线,不是入场曲线 */
242
+ /* 这一会儿才传完的那一行:进度条走满之后与对号交叉——条子淡出、对号淡入。
243
+ 已经落定的与远程附件没有进度条可播,对号直接呈现 */
244
+ [data-scope='file-upload'][data-part='item'][data-state='done']:has(> [data-part='item-progress']:not([hidden]))::before {
245
+ animation: xh-fade-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter) var(--xh-motion-duration-move) backwards;
246
+ }
247
+
248
+ /* 传输进度条:只在传输中不透明,填充按连接层写下的比例平移到位;进度是连续量,走 continuous 曲线。
249
+ 传完那一刻不当场收起:填充先走满(move),随后整条淡出(exit,延后一个 move),播完连接层才写 hidden。
250
+ 没在传也没刚传完的由连接层写 hidden */
238
251
  [data-scope='file-upload'][data-part='item-progress'] {
239
- display: none;
252
+ display: block;
240
253
  overflow: hidden;
241
254
  flex: none;
242
255
  inline-size: var(--xh-file-upload-item-progress-w, calc(var(--xh-control-h-md) * 2));
243
256
  block-size: var(--xh-file-upload-item-progress-h, var(--xh-stroke-thick));
244
257
  border-radius: var(--xh-file-upload-item-progress-radius, var(--xh-shape-pill));
245
258
  background: var(--xh-file-upload-item-progress-track, var(--xh-bg-subtle));
259
+ opacity: 0;
260
+ transition: opacity var(--xh-motion-duration-exit) var(--xh-motion-ease-exit) var(--xh-motion-duration-move);
246
261
  }
247
262
 
248
263
  [data-scope='file-upload'][data-part='item-progress'][data-state='uploading'] {
249
- display: block;
264
+ opacity: 1;
250
265
  }
251
266
 
252
- [data-scope='file-upload'][data-part='item-progress'][data-state='uploading']::before {
267
+ /* 铺满轨道,往行首平移藏起未完成的那一截、由轨道裁掉:推进只走合成,不重排也不重绘。
268
+ translate 只认物理方向,乘书写方向的符号,RTL 下平移方向翻转 */
269
+ [data-scope='file-upload'][data-part='item-progress']::before {
253
270
  content: '';
254
271
  display: block;
255
272
  block-size: 100%;
256
273
  border-radius: inherit;
257
274
  background: var(--xh-file-upload-item-progress-fill, var(--xh-bg-brand));
258
- inline-size: calc(var(--xh-_file-upload-progress, 0) * 100%);
259
- transition: inline-size var(--xh-motion-duration-micro) var(--xh-motion-ease-continuous);
275
+ inline-size: 100%;
276
+ translate: calc((var(--xh-_file-upload-progress, 0) - 1) * 100% * var(--xh-direction-sign)) 0;
277
+ transition: translate var(--xh-motion-duration-move) var(--xh-motion-ease-continuous);
260
278
  }
261
279
 
262
280
  [data-scope='file-upload'][data-part='item-progress'][hidden] {
@@ -320,108 +338,52 @@
320
338
  color: var(--xh-fg-disabled);
321
339
  }
322
340
 
323
- /* 删除是一行里的小图标按钮:正方形、无边框,靠底色表达 hover */
341
+ /* 删除是一行里的小图标钮:Action Control icon 档 ghost、xs 视觉盒,悬停 100 / 按下 200 与 0.97 按压由家族给;
342
+ 悬停与按下时字换危险色,说明这一下会删掉。钮里的叉按 xs 档的字形尺画 */
324
343
  [data-scope='file-upload'][data-part='item-delete-trigger'] {
325
- display: inline-flex;
344
+ --xh-action-bg-hover: var(--xh-file-upload-delete-bg-hover, var(--xh-_action-variant-bg-hover));
345
+ --xh-action-bg-pressed: var(--xh-file-upload-delete-bg-active, var(--xh-_action-variant-bg-pressed));
346
+ --xh-action-fg-rest: var(--xh-file-upload-delete-fg, var(--xh-fg-muted));
347
+ --xh-action-fg-hover: var(--xh-file-upload-delete-fg-hover, var(--xh-fg-danger-hover));
348
+ --xh-action-fg-pressed: var(--xh-file-upload-delete-fg-hover, var(--xh-fg-danger-hover));
349
+ --xh-action-radius: var(--xh-file-upload-delete-radius, var(--xh-shape-control));
350
+ --xh-action-visual-size: var(--xh-file-upload-delete-size, var(--xh-_action-profile-visual-size));
351
+ --xh-icon-size: var(--xh-_action-profile-glyph-size);
352
+
326
353
  flex: none;
327
- align-items: center;
328
- justify-content: center;
329
- inline-size: var(--xh-file-upload-delete-size, var(--xh-control-action-size));
330
- block-size: var(--xh-file-upload-delete-size, var(--xh-control-action-size));
331
- padding: 0;
332
- border: 0;
333
- border-radius: var(--xh-file-upload-delete-radius, var(--xh-shape-control));
334
- background: transparent;
335
- color: var(--xh-file-upload-delete-fg, var(--xh-fg-muted));
336
- font-family: inherit;
337
- line-height: var(--xh-leading-none);
338
- cursor: pointer;
339
- transition:
340
- background var(--xh-motion-duration-micro) var(--xh-motion-ease-enter),
341
- scale var(--xh-motion-duration-release) var(--xh-motion-ease-release);
342
354
  }
343
355
 
344
356
  [data-scope='file-upload'][data-part='item-delete-trigger'][hidden] {
345
357
  display: none;
346
358
  }
347
359
 
348
- [data-scope='file-upload'][data-part='item-delete-trigger']:hover:not(:disabled) {
349
- background: var(--xh-file-upload-delete-bg-hover, var(--xh-bg-subtle-hover));
350
- color: var(--xh-file-upload-delete-fg-hover, var(--xh-fg-danger-hover));
351
- }
352
-
353
- /* 按下:换到按压档底色并轻微下压,松手弹回;禁用钮不压 */
354
- [data-scope='file-upload'][data-part='item-delete-trigger']:not(:disabled):is(:active, [data-pressed]) {
355
- background: var(--xh-file-upload-delete-bg-active, var(--xh-bg-subtle-active));
356
- scale: var(--xh-motion-scale-press);
357
- transition-duration: var(--xh-motion-duration-press);
358
- transition-timing-function: var(--xh-motion-ease-press);
359
- }
360
-
361
- [data-scope='file-upload'][data-part='item-delete-trigger']:disabled {
362
- color: var(--xh-fg-disabled);
363
- }
364
-
365
- /* 清空是收尾动作,画成不带底色的文字按钮:它比"选择文件"次要 */
360
+ /* 清空是收尾动作,画成不带底的文字钮:Action Control text 档 ghost、sm,比「选择文件」小一档 */
366
361
  [data-scope='file-upload'][data-part='clear-trigger'] {
367
- display: inline-flex;
362
+ --xh-action-bg-hover: var(--xh-file-upload-clear-bg-hover, var(--xh-_action-variant-bg-hover));
363
+ --xh-action-bg-pressed: var(--xh-file-upload-clear-bg-active, var(--xh-_action-variant-bg-pressed));
364
+ --xh-action-fg-rest: var(--xh-file-upload-clear-fg, var(--xh-fg-muted));
365
+ --xh-action-fg-hover: var(--xh-file-upload-clear-fg-hover, var(--xh-fg-default));
366
+ --xh-action-fg-pressed: var(--xh-file-upload-clear-fg-hover, var(--xh-fg-default));
367
+ --xh-action-radius: var(--xh-file-upload-clear-radius, var(--xh-shape-control));
368
+ --xh-action-visual-size: var(--xh-file-upload-clear-h, var(--xh-_action-profile-visual-size));
369
+ --xh-action-padding-inline: var(--xh-file-upload-clear-px, var(--xh-_action-profile-padding-inline));
370
+ --xh-action-gap: var(--xh-file-upload-clear-gap, var(--xh-_action-profile-gap));
371
+ --xh-action-font-size: var(--xh-file-upload-clear-font-size, var(--xh-_action-profile-font-size));
372
+ --xh-icon-size: var(--xh-_action-profile-glyph-size);
373
+
368
374
  flex: none;
369
375
  align-self: flex-start;
370
- align-items: center;
371
- justify-content: center;
372
- gap: var(--xh-file-upload-clear-gap, var(--xh-control-gap-sm));
373
- block-size: var(--xh-file-upload-clear-h, var(--xh-control-h-sm));
374
- padding-inline: var(--xh-file-upload-clear-px, var(--xh-control-px-sm));
375
- border: 0;
376
- border-radius: var(--xh-file-upload-clear-radius, var(--xh-shape-control));
377
- background: transparent;
378
- color: var(--xh-file-upload-clear-fg, var(--xh-fg-muted));
379
- font-family: inherit;
380
- font-size: var(--xh-file-upload-clear-font-size, var(--xh-text-caption-size));
381
- line-height: var(--xh-leading-none);
382
- cursor: pointer;
383
- transition:
384
- background var(--xh-motion-duration-micro) var(--xh-motion-ease-enter),
385
- scale var(--xh-motion-duration-release) var(--xh-motion-ease-release);
386
376
  }
387
377
 
388
378
  [data-scope='file-upload'][data-part='clear-trigger'][hidden] {
389
379
  display: none;
390
380
  }
391
381
 
392
- [data-scope='file-upload'][data-part='clear-trigger']:hover:not(:disabled) {
393
- background: var(--xh-file-upload-clear-bg-hover, var(--xh-bg-subtle-hover));
394
- color: var(--xh-file-upload-clear-fg-hover, var(--xh-fg-default));
395
- }
396
-
397
- /* 按下:换到按压档底色并轻微下压,松手弹回;禁用钮不压 */
398
- [data-scope='file-upload'][data-part='clear-trigger']:not(:disabled):is(:active, [data-pressed]) {
399
- background: var(--xh-file-upload-clear-bg-active, var(--xh-bg-subtle-active));
400
- scale: var(--xh-motion-scale-press);
401
- transition-duration: var(--xh-motion-duration-press);
402
- transition-timing-function: var(--xh-motion-ease-press);
403
- }
404
-
405
- /* 空列表时按钮照常可按(收起它会让焦点掉回 body),只把前景压淡一档 */
382
+ /* 空列表时按钮照常可按(收起它会让焦点掉回 body),只把前景压淡一档;三态一起压,家族的悬停规则更特定 */
406
383
  [data-scope='file-upload'][data-part='clear-trigger'][data-empty] {
407
- color: var(--xh-fg-subtle);
408
- }
409
-
410
- [data-scope='file-upload'][data-part='clear-trigger']:disabled {
411
- color: var(--xh-fg-disabled);
412
- }
413
-
414
- /*
415
- * 高对比档:三颗钮的按压面常态靠换底表出,这一档里底色被统一换成 Canvas,按住那一下与静息塌成同一个样子;
416
- * 用系统高亮反色画回来,兜底字形读 currentColor、自己跟着换
417
- */
418
- @media (forced-colors: active) {
419
- [data-scope='file-upload'][data-part='trigger']:not(:disabled):is(:active, [data-pressed]),
420
- [data-scope='file-upload'][data-part='item-delete-trigger']:not(:disabled):is(:active, [data-pressed]),
421
- [data-scope='file-upload'][data-part='clear-trigger']:not(:disabled):is(:active, [data-pressed]) {
422
- background: Highlight;
423
- color: HighlightText;
424
- }
384
+ --xh-action-fg-rest: var(--xh-fg-subtle);
385
+ --xh-action-fg-hover: var(--xh-fg-subtle);
386
+ --xh-action-fg-pressed: var(--xh-fg-subtle);
425
387
  }
426
388
 
427
389
  /* 没写内容就由皮肤画兜底字形;作者往部件里塞了自己的图形,这几条即不命中 */
@@ -436,4 +398,32 @@
436
398
  -webkit-mask: var(--xh-glyph-mark-close) center / contain no-repeat;
437
399
  mask: var(--xh-glyph-mark-close) center / contain no-repeat;
438
400
  }
401
+
402
+ @media (forced-colors: active) {
403
+ /* 兜底字形是 mask 挖出来的一块底色,这一档里底色被系统换成 Canvas,字形跟着消失。
404
+ 字形退出强制换色、直接写系统色(不能取 currentColor:退出强制换色的伪元素读到的是被系统换掉之前的作者色):
405
+ 按钮面上取 ButtonText,悬停与按下时家族把按钮涂成 Highlight、字形换 HighlightText;
406
+ 落在 Canvas 面上的取 CanvasText;
407
+ 禁用取 GrayText */
408
+ [data-scope='file-upload'][data-part='item'][data-state='error']::before,
409
+ [data-scope='file-upload'][data-part='item'][data-state='closed']:has(> [data-part='item-progress'][data-state='error'])::before,
410
+ [data-scope='file-upload'][data-part='item'][data-state='done']::before,
411
+ [data-scope='file-upload'][data-part='item'][data-state='closed']:has(> [data-part='item-progress'][data-state='done'])::before {
412
+ forced-color-adjust: none;
413
+ background-color: CanvasText;
414
+ }
415
+
416
+ [data-scope='file-upload']:is([data-part='item-delete-trigger'], [data-part='clear-trigger']):empty::before {
417
+ forced-color-adjust: none;
418
+ background-color: ButtonText;
419
+ }
420
+
421
+ [data-scope='file-upload']:is([data-part='item-delete-trigger'], [data-part='clear-trigger']):not([data-disabled]):not([data-loading]):is(:hover, :active, [data-pressed]):empty::before {
422
+ background-color: HighlightText;
423
+ }
424
+
425
+ [data-scope='file-upload']:is([data-part='item-delete-trigger'], [data-part='clear-trigger'])[data-disabled]:empty::before {
426
+ background-color: GrayText;
427
+ }
428
+ }
439
429
  }