@xihan-ui/styles 1.0.0 → 2.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 (171) hide show
  1. package/CHANGELOG.md +5146 -6
  2. package/README.md +22 -2
  3. package/component-tokens.d.ts +4212 -0
  4. package/component-tokens.js +2 -0
  5. package/components.tokens.json +80116 -0
  6. package/css/accordion.css +131 -75
  7. package/css/affix.css +16 -1
  8. package/css/alert.css +114 -67
  9. package/css/anchor.css +99 -27
  10. package/css/approval.css +461 -0
  11. package/css/avatar-group.css +14 -6
  12. package/css/avatar.css +33 -9
  13. package/css/back-top.css +103 -48
  14. package/css/badge.css +42 -14
  15. package/css/bar-code.css +62 -0
  16. package/css/breadcrumb.css +40 -21
  17. package/css/button-group.css +146 -24
  18. package/css/button.css +116 -99
  19. package/css/calendar-picker.css +448 -0
  20. package/css/calendar-range-picker.css +517 -0
  21. package/css/card.css +54 -77
  22. package/css/carousel.css +412 -68
  23. package/css/cascader.css +429 -246
  24. package/css/checkbox-group.css +225 -77
  25. package/css/checkbox.css +174 -37
  26. package/css/clipboard.css +266 -47
  27. package/css/code-view.css +279 -0
  28. package/css/collapsible.css +108 -63
  29. package/css/color-field.css +158 -0
  30. package/css/color-picker.css +172 -184
  31. package/css/color-slider.css +274 -0
  32. package/css/color-swatch-picker.css +243 -0
  33. package/css/color-swatch.css +35 -0
  34. package/css/combobox.css +267 -273
  35. package/css/command.css +399 -0
  36. package/css/context-menu.css +183 -108
  37. package/css/date-field.css +94 -121
  38. package/css/date-picker.css +486 -249
  39. package/css/date-range-picker.css +559 -0
  40. package/css/description.css +51 -0
  41. package/css/descriptions.css +133 -87
  42. package/css/dialog.css +247 -68
  43. package/css/diff-view.css +428 -0
  44. package/css/download-trigger.css +155 -46
  45. package/css/drawer.css +194 -111
  46. package/css/editable.css +172 -152
  47. package/css/empty-state.css +80 -11
  48. package/css/field-array.css +190 -0
  49. package/css/field.css +84 -31
  50. package/css/fieldset.css +51 -18
  51. package/css/file-upload.css +134 -36
  52. package/css/flex.css +20 -1
  53. package/css/float-button.css +238 -57
  54. package/css/floating-panel.css +173 -92
  55. package/css/focus.css +46 -0
  56. package/css/forced-colors.css +93 -0
  57. package/css/form.css +254 -77
  58. package/css/gradient-text.css +34 -10
  59. package/css/grid.css +516 -5
  60. package/css/heatmap.css +87 -29
  61. package/css/highlight.css +20 -5
  62. package/css/hover-card.css +97 -47
  63. package/css/icon-wrapper.css +15 -3
  64. package/css/icon.css +74 -8
  65. package/css/image-cropper.css +143 -29
  66. package/css/image-viewer.css +150 -89
  67. package/css/image.css +30 -1
  68. package/css/infinite-scroll.css +51 -1
  69. package/css/input-group.css +277 -0
  70. package/css/json-viewer.css +102 -45
  71. package/css/kbd.css +72 -0
  72. package/css/label.css +78 -0
  73. package/css/layers.css +5 -0
  74. package/css/layout.css +175 -31
  75. package/css/list.css +24 -3
  76. package/css/listbox.css +208 -67
  77. package/css/loading-bar.css +52 -3
  78. package/css/log.css +144 -9
  79. package/css/markdown-stream.css +149 -0
  80. package/css/marquee.css +27 -1
  81. package/css/masonry.css +7 -0
  82. package/css/matrix-code.css +67 -0
  83. package/css/mention.css +234 -152
  84. package/css/menu.css +180 -83
  85. package/css/menubar.css +204 -136
  86. package/css/message-feed.css +197 -0
  87. package/css/navigation-menu.css +144 -90
  88. package/css/notification.css +178 -100
  89. package/css/number-animation.css +7 -0
  90. package/css/number-field.css +120 -277
  91. package/css/overlay-arrow.css +12 -7
  92. package/css/page-header.css +99 -14
  93. package/css/pagination.css +175 -135
  94. package/css/password-input.css +222 -219
  95. package/css/pin-input.css +101 -67
  96. package/css/pointer.css +36 -0
  97. package/css/popconfirm.css +185 -94
  98. package/css/popover.css +121 -77
  99. package/css/progress.css +60 -12
  100. package/css/prompt-input.css +224 -0
  101. package/css/question-flow.css +513 -0
  102. package/css/radio-group.css +147 -30
  103. package/css/rating.css +193 -43
  104. package/css/reasoning.css +336 -0
  105. package/css/reset.css +84 -6
  106. package/css/resizable.css +237 -0
  107. package/css/scroll-area.css +68 -7
  108. package/css/scrollbar.css +47 -13
  109. package/css/segmented.css +94 -29
  110. package/css/select.css +288 -274
  111. package/css/separator.css +101 -1
  112. package/css/side-nav.css +133 -75
  113. package/css/signature-pad.css +74 -54
  114. package/css/skeleton.css +84 -11
  115. package/css/slider.css +132 -39
  116. package/css/sortable.css +198 -0
  117. package/css/spinner.css +126 -12
  118. package/css/splitter.css +29 -8
  119. package/css/statistic.css +55 -0
  120. package/css/steps.css +206 -72
  121. package/css/switch.css +268 -36
  122. package/css/table.css +695 -118
  123. package/css/tabs.css +604 -42
  124. package/css/tag-group.css +219 -0
  125. package/css/tag.css +118 -28
  126. package/css/tags-input.css +156 -274
  127. package/css/text-field.css +101 -249
  128. package/css/time-field.css +100 -128
  129. package/css/time-picker.css +323 -252
  130. package/css/time-range-picker.css +719 -0
  131. package/css/timeline.css +202 -68
  132. package/css/timer.css +56 -52
  133. package/css/timestamp.css +34 -0
  134. package/css/toast.css +373 -144
  135. package/css/toggle-group.css +309 -76
  136. package/css/toggle.css +116 -85
  137. package/css/tone.css +47 -0
  138. package/css/tool-call.css +421 -0
  139. package/css/toolbar.css +181 -33
  140. package/css/tooltip.css +107 -36
  141. package/css/tour.css +183 -152
  142. package/css/transfer.css +351 -138
  143. package/css/tree-select.css +323 -216
  144. package/css/tree.css +294 -62
  145. package/css/truncate.css +65 -0
  146. package/css/typography.css +228 -5
  147. package/css/undefined.css +10 -1
  148. package/css/virtualizer.css +9 -10
  149. package/css/visually-hidden.css +7 -0
  150. package/css/watermark.css +7 -0
  151. package/family/action-control.css +487 -0
  152. package/family/collection-item.css +721 -0
  153. package/family/field-chrome.css +298 -0
  154. package/family/motion.css +166 -0
  155. package/family/swatch.css +59 -0
  156. package/index.css +41705 -126
  157. package/index.unlayered.css +33692 -17339
  158. package/package.json +51 -17
  159. package/css/calendar.css +0 -367
  160. package/css/code-block.css +0 -91
  161. package/css/composer.css +0 -134
  162. package/css/countdown.css +0 -26
  163. package/css/dynamic-input.css +0 -220
  164. package/css/ellipsis.css +0 -52
  165. package/css/hotkeys.css +0 -90
  166. package/css/popselect.css +0 -391
  167. package/css/qr-code.css +0 -55
  168. package/css/result.css +0 -119
  169. package/css/space.css +0 -117
  170. package/css/thread.css +0 -120
  171. package/css/time.css +0 -25
package/css/toast.css CHANGED
@@ -1,34 +1,57 @@
1
+ /*
2
+ * Copyright (c) 2021-Present XiHanFun and contributors.
3
+ * Licensed under the MIT License. See LICENSE in the project root for license information.
4
+ */
5
+
6
+ /* 定义 toast 样式。 */
7
+
8
+ /* Toast 独立皮肤同时传递引入操作 / 关闭钮的动作控件配方;full bundle 递归去重。 */
9
+ @import '../family/action-control.css';
10
+
1
11
  @layer xihan.motion {
2
- /*
3
- * 九个位共用同一条进出场,位移不跟着落位走:要按位置分方向就得从摞的
4
- * data-placement 反向选到条子上,落位一改名整条动效静默失效。
5
- * 竖向一小段位移在九个位上都说得通。
6
- */
12
+ /* 新条目从视口边缘外进入,同时落到自己的折叠层级。 */
7
13
  @keyframes xh-toast-in {
8
14
  from {
9
15
  opacity: 0;
10
- translate: 0 var(--xh-motion-distance-md);
11
- scale: var(--xh-motion-scale-enter);
16
+ transform: translateY(calc(var(--xh-toast-dir, 1) * (var(--xh-toast-offset-collapsed, 0px) - 100%)))
17
+ scale(var(--xh-toast-scale-collapsed, 1));
12
18
  }
13
19
 
14
20
  to {
15
21
  opacity: 1;
16
- translate: 0 0;
17
- scale: 1;
22
+ transform: translateY(calc(var(--xh-toast-dir, 1) * var(--xh-toast-offset-collapsed, 0px)))
23
+ scale(var(--xh-toast-scale-collapsed, 1));
24
+ }
25
+ }
26
+
27
+ @keyframes xh-toast-spin {
28
+ to {
29
+ rotate: 360deg;
30
+ }
31
+ }
32
+
33
+ /* 倒计时条:从占满一整条收到零。走一遍就到头,时长由连接层写进时长槽 */
34
+ @keyframes xh-countdown {
35
+ from {
36
+ inline-size: 100%;
37
+ }
38
+
39
+ to {
40
+ inline-size: 0%;
18
41
  }
19
42
  }
20
43
 
21
44
  @keyframes xh-toast-out {
22
45
  from {
23
46
  opacity: 1;
24
- translate: 0 0;
25
- scale: 1;
47
+ transform: translateY(calc(var(--xh-toast-dir, 1) * var(--xh-toast-y, 0px)))
48
+ scale(var(--xh-toast-scale, 1));
26
49
  }
27
50
 
28
51
  to {
29
52
  opacity: 0;
30
- translate: 0 var(--xh-motion-distance-md);
31
- scale: var(--xh-motion-scale-enter);
53
+ transform: translateY(calc(var(--xh-toast-dir, 1) * (var(--xh-toast-y, 0px) - 100%)))
54
+ scale(var(--xh-toast-scale, 1));
32
55
  }
33
56
  }
34
57
  }
@@ -39,29 +62,13 @@
39
62
  --xh-toast-skin: 1;
40
63
  }
41
64
 
42
- /*
43
- * 一摞 = 一整面视口大小的定位面:贴边靠 padding,落位靠 justify/align 表达。
44
- * 不用四角 inset 逐个摆,是因为 column flex 的 align-items 走的是行内轴,
45
- * RTL 下 start/end 自动跟着翻;写死 inset-inline-start 的话居中那三个位
46
- * 还得再配一次 translate,方向一变就错。
47
- * 摞由全局服务渲染,没有对应的容器组件——一次操作的反馈落在哪儿是整个服务的口径。
48
- */
65
+ /* 固定宽度的堆叠定位面;每条 Toast 在其中绝对定位。 */
49
66
  [data-scope='toast'][data-part='group'] {
50
67
  position: fixed;
51
- inset-block: 0;
52
- inset-inline: 0;
53
68
  z-index: var(--xh-toast-layer, var(--xh-layer-toast));
54
- display: flex;
55
- flex-direction: column;
56
- /* 基线按默认落位 top 写,下面只覆盖跟它不同的那几个位 */
57
- justify-content: flex-start;
58
- align-items: center;
59
- padding-block: var(--xh-toast-inset, var(--xh-space-6));
60
- padding-inline: var(--xh-toast-inset, var(--xh-space-6));
61
- /* 摞内间距由服务写进内联 style,这里绝不重复写:
62
- 写了也会被内联盖掉,只会让人误以为间距归样式层管 */
63
- /* 整面铺开就等于罩住了整块视口。面本身不吃指针,只有条子吃(见下),
64
- 否则底下的页面就再也点不动了 */
69
+ display: block;
70
+ inline-size: min(var(--xh-toast-inline-size, 28.75rem), calc(100vw - var(--xh-space-8)));
71
+ block-size: 0;
65
72
  pointer-events: none;
66
73
  }
67
74
 
@@ -69,61 +76,86 @@
69
76
  display: none;
70
77
  }
71
78
 
72
- /* 块轴落位:^= 一次吃掉某一行的三个位(top / top-start / top-end) */
73
- [data-scope='toast'][data-part='group'][data-placement^='middle'] {
74
- justify-content: center;
79
+ [data-scope='toast'][data-part='group'][data-placement='bottom'] {
80
+ inset-block-end: max(var(--xh-toast-inset, var(--xh-space-4)), env(safe-area-inset-bottom));
81
+ inset-inline-start: 50%;
82
+ translate: -50% 0;
75
83
  }
76
84
 
77
- [data-scope='toast'][data-part='group'][data-placement^='bottom'] {
78
- justify-content: flex-end;
85
+ [data-scope='toast'][data-part='group'][data-placement='top'] {
86
+ inset-block-start: max(var(--xh-toast-inset, var(--xh-space-4)), env(safe-area-inset-top));
87
+ inset-inline-start: 50%;
88
+ translate: -50% 0;
79
89
  }
80
90
 
81
- /* 行内轴落位:不带后缀的 top / middle / bottom 用基线的居中 */
82
91
  [data-scope='toast'][data-part='group'][data-placement$='-start'] {
83
- align-items: flex-start;
92
+ inset-inline-start: max(var(--xh-toast-inset, var(--xh-space-4)), env(safe-area-inset-left));
84
93
  }
85
94
 
86
95
  [data-scope='toast'][data-part='group'][data-placement$='-end'] {
87
- align-items: flex-end;
96
+ inset-inline-end: max(var(--xh-toast-inset, var(--xh-space-4)), env(safe-area-inset-right));
88
97
  }
89
98
 
90
- /*
91
- * 一条轻提示是「一句话 + 一枚状态字形」,横排一行、贴着文字收缩。
92
- * 语气由淡底、描边与字形三者一起承载,正文留中性——正文也跟着兑成语气色的话,
93
- * 绿字压在绿底上,对比度是整条里最差的那一处。
94
- */
99
+ [data-scope='toast'][data-part='group'][data-placement^='bottom'][data-placement$='-start'],
100
+ [data-scope='toast'][data-part='group'][data-placement^='bottom'][data-placement$='-end'] {
101
+ inset-block-end: max(var(--xh-toast-inset, var(--xh-space-4)), env(safe-area-inset-bottom));
102
+ }
103
+
104
+ [data-scope='toast'][data-part='group'][data-placement^='top'][data-placement$='-start'],
105
+ [data-scope='toast'][data-part='group'][data-placement^='top'][data-placement$='-end'] {
106
+ inset-block-start: max(var(--xh-toast-inset, var(--xh-space-4)), env(safe-area-inset-top));
107
+ }
108
+
109
+ /* 中性浮层承载整条反馈,语气只落到状态字形和标题。
110
+ 面走 sheet 三件套(§8.4:--xh-material-elevated-bg / -border / -shadow,亮暗两档同有 1px 描边)。 */
95
111
  [data-scope='toast'][data-part='root'] {
96
- /* 状态字形按控件指示符那一档走,与 alert 的图标框同一把尺 */
97
- --xh-icon-size: var(--xh-toast-icon-size, var(--xh-control-indicator-size));
112
+ /* 作者塞进指示符槽的图标与皮肤画的兜底字形同一把尺:Feedback 指示符统一 md 档(§6.5) */
113
+ --xh-icon-size: var(--xh-toast-icon-size, var(--xh-glyph-size-md));
98
114
 
99
- /* 底与描边取语气层派生好的槽,不在这儿自己再兑一遍:
100
- 兑法差一档,字形压上去的对比度就跟着差一档(alert 踩过这个坑) */
101
- --xh-_toast-tint: var(--xh-_tone-subtle);
102
- --xh-_toast-edge: var(--xh-_tone-border);
115
+ --xh-toast-y: var(--xh-toast-offset-collapsed, 0px);
116
+ --xh-toast-scale: var(--xh-toast-scale-collapsed, 1);
103
117
 
118
+ position: relative;
104
119
  display: flex;
105
- align-items: center;
106
- gap: var(--xh-toast-gap, var(--xh-control-gap-md));
107
- /* 收缩包裹:一句「已保存」撑成定宽方块,右边那片空白会让它读起来像一张公告卡 */
108
- inline-size: var(--xh-toast-w, auto);
109
- /* 摞是整面视口宽的定位面且带内边距,取 100% 就等于自动让出两侧留白,
110
- 窄屏上不必再算一遍 viewport 减法;再叠一道绝对上限,免得一条长文案铺成横幅 */
111
- max-inline-size: min(var(--xh-overlay-max-w-xl), 100%);
112
- /* 纵内衬取控件档而不是面档:面档(12px)叠上 21px 行盒与两道细边就是 47px 高,
113
- 那是卡片的体量;控件档(8px)落在 39px,与短消息该有的高度一致
114
- (带叉 42px、带操作钮 46px,那两档本就不是默认排版) */
115
- padding-block: var(--xh-toast-py, var(--xh-field-py));
116
- padding-inline: var(--xh-toast-px, var(--xh-surface-px-sm));
117
- border: var(--xh-stroke-thin) solid var(--xh-toast-border, var(--xh-_toast-edge));
118
- border-radius: var(--xh-toast-radius, var(--xh-shape-surface));
119
- background: var(--xh-toast-bg, var(--xh-_toast-tint));
120
- color: var(--xh-toast-fg, var(--xh-fg-default));
121
- box-shadow: var(--xh-toast-shadow, var(--xh-elevation-sheet));
122
- font-size: var(--xh-toast-font-size, var(--xh-text-body-size));
120
+ flex: none;
121
+ align-items: flex-start;
122
+ gap: var(--xh-toast-gap, var(--xh-space-1_5));
123
+ inline-size: min(var(--xh-toast-inline-size, 28.75rem), 100%);
124
+ padding-block: var(--xh-toast-py, var(--xh-space-3));
125
+ padding-inline: var(--xh-toast-px, var(--xh-space-4));
126
+ border: var(--xh-stroke-thin) solid var(--xh-toast-border, var(--xh-material-elevated-border));
127
+ border-radius: var(--xh-toast-radius, var(--xh-shape-overlay));
128
+ background: var(--xh-toast-bg, var(--xh-material-elevated-bg));
129
+ color: var(--xh-toast-fg, var(--xh-material-elevated-fg));
130
+ box-shadow: var(--xh-toast-shadow, var(--xh-material-elevated-shadow));
131
+ font-size: var(--xh-toast-font-size, var(--xh-text-label-size));
123
132
  line-height: var(--xh-toast-leading, var(--xh-text-body-leading));
124
133
  /* 摞的定位面整面铺开且不吃指针,条子必须自己吃回来:
125
134
  悬停按住计时、按钮可点、焦点进出,全指着这一条 */
126
135
  pointer-events: auto;
136
+ transform: translateY(calc(var(--xh-toast-dir, 1) * var(--xh-toast-y))) scale(var(--xh-toast-scale));
137
+ transform-origin: bottom center;
138
+ transition:
139
+ transform var(--xh-motion-duration-enter) var(--xh-motion-ease-continuous),
140
+ block-size var(--xh-motion-duration-enter) var(--xh-motion-ease-continuous),
141
+ opacity var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
142
+ }
143
+
144
+ [data-scope='toast'][data-part='group'] [data-scope='toast'][data-part='root'] {
145
+ position: absolute;
146
+ inset-inline: 0;
147
+ inline-size: 100%;
148
+ }
149
+
150
+ [data-scope='toast'][data-part='root'][data-placement^='bottom'] {
151
+ --xh-toast-dir: -1;
152
+ inset-block-end: 0;
153
+ }
154
+
155
+ [data-scope='toast'][data-part='root'][data-placement^='top'] {
156
+ --xh-toast-dir: 1;
157
+ inset-block-start: 0;
158
+ transform-origin: top center;
127
159
  }
128
160
 
129
161
  /* 退场窗口走完连接层给的是 hidden 属性,而上面声明了 display——
@@ -134,126 +166,305 @@
134
166
 
135
167
  /* 生命周期只有三段,unmounted 那段由上面的 [hidden] 收走,这里只管前两段 */
136
168
  [data-scope='toast'][data-part='root'][data-state='visible'] {
137
- animation: xh-toast-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter);
169
+ animation: xh-toast-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter-strong) backwards;
170
+ }
171
+
172
+ [data-scope='toast'][data-part='root'][data-stack-index]:not([data-frontmost]):not([data-expanded]) {
173
+ block-size: var(--xh-toast-front-height, auto);
174
+ overflow: hidden;
175
+ }
176
+
177
+ [data-scope='toast'][data-part='root'][data-stack-index]:not([data-frontmost]):not([data-expanded])
178
+ > :not([data-part='close-trigger']) {
179
+ opacity: 0;
138
180
  }
139
181
 
140
- /* forwards 不能省:退场窗口(removeDelay 默认 200ms)比退场动画长,
182
+ [data-scope='toast'][data-part='root'][data-expanded] {
183
+ --xh-toast-y: var(--xh-toast-offset-expanded, 0px);
184
+ --xh-toast-scale: 1;
185
+ block-size: var(--xh-toast-height, auto);
186
+ }
187
+
188
+ [data-scope='toast'][data-part='root'] > :not([data-part='close-trigger']) {
189
+ transition: opacity var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
190
+ }
191
+
192
+ /* forwards 不能省:退场窗口(removeDelay 默认 300ms)比退场动画长,
141
193
  不定住末帧的话动画跑完会弹回全不透明,条子在被收起前先闪一下 */
142
194
  [data-scope='toast'][data-part='root'][data-state='dismissing'] {
143
195
  animation: xh-toast-out var(--xh-motion-duration-exit) var(--xh-motion-ease-exit) forwards;
144
196
  }
145
197
 
146
- [data-scope='toast'][data-part='title'] {
147
- /* 作者可能把它写成 h2/p,外边距得先归零,否则条子内的行距对不上 */
148
- margin: 0;
149
- /* 吃掉剩余宽度,把操作钮与叉推到行尾;min-inline-size 让它在撞上宽度上限时能换行,
150
- flex 项的 min-width 默认是 auto,不归零的话长文案会把条子顶出上限 */
198
+ /*
199
+ * 语气的非颜色通道。行首字形按 data-tone 区分信息、成功、警告与失败;加载中另由 data-loading 说,
200
+ * 字形换成转圈但配色仍照语气走。
201
+ * 作者未渲染 indicator 时由 root 的伪元素补上。
202
+ */
203
+ [data-scope='toast'][data-part='root']::before {
204
+ content: '';
205
+ flex: none;
206
+ display: inline-block;
207
+ inline-size: var(--xh-icon-size, var(--xh-glyph-size-text));
208
+ block-size: var(--xh-icon-size, var(--xh-glyph-size-text));
209
+ vertical-align: var(--xh-glyph-baseline-shift);
210
+ background-color: var(--xh-toast-icon-fg, var(--xh-_tone-fg));
211
+ -webkit-mask: var(--xh-glyph-mark-info) center / contain no-repeat;
212
+ mask: var(--xh-glyph-mark-info) center / contain no-repeat;
213
+ }
214
+
215
+ [data-scope='toast'][data-part='root'][data-tone='success']::before {
216
+ -webkit-mask-image: var(--xh-glyph-mark-check);
217
+ mask-image: var(--xh-glyph-mark-check);
218
+ }
219
+
220
+ [data-scope='toast'][data-part='root'][data-tone='warning']::before {
221
+ -webkit-mask-image: var(--xh-glyph-mark-warning);
222
+ mask-image: var(--xh-glyph-mark-warning);
223
+ }
224
+
225
+ [data-scope='toast'][data-part='root'][data-tone='danger']::before {
226
+ -webkit-mask-image: var(--xh-glyph-mark-close);
227
+ mask-image: var(--xh-glyph-mark-close);
228
+ }
229
+
230
+ /* 加载中不是语气:只换字形,颜色沿用当前语气的前景档;写在四档语气之后,转圈压过它们 */
231
+ [data-scope='toast'][data-part='root'][data-loading]::before {
232
+ -webkit-mask-image: var(--xh-glyph-mark-rotate-right);
233
+ mask-image: var(--xh-glyph-mark-rotate-right);
234
+ animation: xh-toast-spin var(--xh-spin-duration) var(--xh-motion-ease-loop) infinite;
235
+ }
236
+
237
+ /* 减少动效:转圈停下,静止的箭头仍然读得出「还没好」;「在等什么」由条子上的
238
+ role=status 与可见文案继续承担。
239
+ 两组规则是同一件事的两个触发条件:系统偏好与作者打的 data-motion */
240
+ @media (prefers-reduced-motion: reduce) {
241
+ [data-scope='toast'][data-part='root'][data-loading]::before {
242
+ animation: none;
243
+ }
244
+ }
245
+
246
+ :where([data-motion='reduce']) [data-scope='toast'][data-part='root'][data-loading]::before {
247
+ animation: none;
248
+ }
249
+
250
+ /* 作者渲染了指示符部件,行首那枚兜底字形就让位给它,一行里不出现两枚图形 */
251
+ [data-scope='toast'][data-part='root']:has([data-part='indicator'])::before {
252
+ content: none;
253
+ }
254
+
255
+ /* 指示符部件:字形与 root 上那枚同尺同色,作者往里塞节点即整枚换掉 */
256
+ [data-scope='toast'][data-part='indicator'] {
257
+ flex: none;
258
+ display: inline-flex;
259
+ align-items: center;
260
+ justify-content: center;
261
+ box-sizing: content-box;
262
+ inline-size: var(--xh-icon-size, var(--xh-glyph-size-text));
263
+ block-size: var(--xh-icon-size, var(--xh-glyph-size-text));
264
+ padding: var(--xh-toast-indicator-p, var(--xh-space-1));
265
+ color: var(--xh-toast-icon-fg, var(--xh-_tone-fg));
266
+ }
267
+
268
+ /* 没写内容就由皮肤画兜底字形;作者往部件里塞了自己的图形,这几条即不命中 */
269
+ [data-scope='toast'][data-part='indicator']:empty::before {
270
+ content: '';
271
+ display: inline-block;
272
+ inline-size: var(--xh-icon-size, var(--xh-glyph-size-text));
273
+ block-size: var(--xh-icon-size, var(--xh-glyph-size-text));
274
+ vertical-align: var(--xh-glyph-baseline-shift);
275
+ background-color: currentColor;
276
+ -webkit-mask: var(--xh-glyph-mark-info) center / contain no-repeat;
277
+ mask: var(--xh-glyph-mark-info) center / contain no-repeat;
278
+ }
279
+
280
+ [data-scope='toast'][data-part='indicator'][data-tone='success']:empty::before {
281
+ -webkit-mask-image: var(--xh-glyph-mark-check);
282
+ mask-image: var(--xh-glyph-mark-check);
283
+ }
284
+
285
+ [data-scope='toast'][data-part='indicator'][data-tone='warning']:empty::before {
286
+ -webkit-mask-image: var(--xh-glyph-mark-warning);
287
+ mask-image: var(--xh-glyph-mark-warning);
288
+ }
289
+
290
+ [data-scope='toast'][data-part='indicator'][data-tone='danger']:empty::before {
291
+ -webkit-mask-image: var(--xh-glyph-mark-close);
292
+ mask-image: var(--xh-glyph-mark-close);
293
+ }
294
+
295
+ [data-scope='toast'][data-part='indicator'][data-loading]:empty::before {
296
+ -webkit-mask-image: var(--xh-glyph-mark-rotate-right);
297
+ mask-image: var(--xh-glyph-mark-rotate-right);
298
+ animation: xh-toast-spin var(--xh-spin-duration) var(--xh-motion-ease-loop) infinite;
299
+ }
300
+
301
+ /* 减少动效:与 root 上那枚同一处理——转圈停下,静止的箭头仍读得出「还没好」。
302
+ 两组规则是同一件事的两个触发条件:系统偏好与作者打的 data-motion */
303
+ @media (prefers-reduced-motion: reduce) {
304
+ [data-scope='toast'][data-part='indicator'][data-loading]:empty::before {
305
+ animation: none;
306
+ }
307
+ }
308
+
309
+ :where([data-motion='reduce']) [data-scope='toast'][data-part='indicator'][data-loading]:empty::before {
310
+ animation: none;
311
+ }
312
+
313
+ [data-scope='toast'][data-part='indicator'][hidden] {
314
+ display: none;
315
+ }
316
+
317
+ [data-scope='toast'][data-part='content'] {
151
318
  flex: 1 1 auto;
319
+ display: flex;
320
+ flex-direction: column;
321
+ align-self: center;
322
+ min-inline-size: 0;
323
+ }
324
+
325
+ [data-scope='toast'][data-part='title'],
326
+ [data-scope='toast'][data-part='description'] {
327
+ margin: 0;
152
328
  min-inline-size: 0;
153
- color: var(--xh-toast-title-fg, var(--xh-fg-default));
154
- /* 与条子同字号同行高、不加粗:加粗是"这是标题、下面还有正文"的信号,
155
- 而轻提示没有正文——那一层是 notification 的 */
156
- font-size: var(--xh-toast-title-font-size, var(--xh-text-body-size));
157
- font-weight: var(--xh-toast-title-font-weight, var(--xh-text-body-weight));
329
+ }
330
+
331
+ [data-scope='toast'][data-part='title'] {
332
+ color: var(--xh-toast-title-fg, var(--xh-_tone-fg));
333
+ font-size: var(--xh-toast-title-font-size, var(--xh-text-label-size));
334
+ font-weight: var(--xh-toast-title-font-weight, var(--xh-font-weight-semibold));
158
335
  line-height: var(--xh-toast-title-leading, var(--xh-text-body-leading));
159
336
  }
160
337
 
338
+ [data-scope='toast'][data-part='description'] {
339
+ color: var(--xh-toast-description-fg, var(--xh-fg-muted));
340
+ font-size: var(--xh-toast-description-font-size, var(--xh-text-secondary-size));
341
+ line-height: var(--xh-toast-description-leading, var(--xh-leading-normal));
342
+ }
343
+
161
344
  /* 没给文案时仍然要留在 DOM 里(aria-labelledby 指着它),
162
345
  但空盒子会照吃一份 gap,条子看着就多出一截莫名的留白 */
163
346
  [data-scope='toast'][data-part='title']:empty {
164
347
  display: none;
165
348
  }
166
349
 
350
+ [data-scope='toast'][data-part='description']:empty {
351
+ display: none;
352
+ }
353
+
167
354
  [data-scope='toast'][data-part='title'][hidden] {
168
355
  display: none;
169
356
  }
170
357
 
358
+ /* 操作钮是带文案的离散动作钮:盒几何、悬停 / 按下与 0.97 按压、粗指针热区、焦点环、禁用面全由 Action Control
359
+ 的 text outline 档给(连接层投 sm;非 Button 的触发器缺省中性描边、透明底,§7.2.2),组件只映射既有使用者槽。
360
+ 语气只落到状态字形和标题:这颗钮坐在带 data-tone 的条子里,底 / 边 / 字钉在中性语义面上,不随语气;
361
+ 条子是 sheet 白面承载:hover --xh-bg-subtle(100)→ pressed --xh-bg-subtle-hover(200)。
362
+ 条子是单行 flex,按钮不参与伸缩:文案长了该换行的是文字 */
171
363
  [data-scope='toast'][data-part='action-trigger'] {
172
- /* 条子是单行 flex,按钮不参与伸缩:文案长了该换行的是文字,不是把按钮压扁 */
364
+ --xh-action-visual-size: var(--xh-toast-action-h, var(--xh-_action-profile-visual-size));
365
+ --xh-action-padding-inline: var(--xh-toast-action-px, var(--xh-_action-profile-padding-inline));
366
+ --xh-action-radius: var(--xh-toast-action-radius, var(--xh-shape-control));
367
+ --xh-action-bg-rest: var(--xh-toast-action-bg, transparent);
368
+ --xh-action-bg-hover: var(--xh-toast-action-bg-hover, var(--xh-bg-subtle));
369
+ --xh-action-bg-pressed: var(--xh-toast-action-bg-active, var(--xh-bg-subtle-hover));
370
+ --xh-action-fg-rest: var(--xh-toast-action-fg, var(--xh-fg-default));
371
+ --xh-action-fg-hover: var(--xh-toast-action-fg, var(--xh-fg-default));
372
+ --xh-action-fg-pressed: var(--xh-toast-action-fg, var(--xh-fg-default));
373
+ --xh-action-border-rest: var(--xh-toast-action-border, var(--xh-border-control));
374
+ --xh-action-border-hover: var(--xh-toast-action-border, var(--xh-border-control-hover));
375
+ --xh-action-border-pressed: var(--xh-toast-action-border, var(--xh-border-control-hover));
376
+ --xh-action-highlight-rest: transparent;
377
+ --xh-action-shadow-rest: none;
378
+ --xh-action-shadow-hover: none;
379
+ --xh-action-shadow-pressed: none;
380
+
173
381
  flex: none;
174
- display: inline-flex;
175
- align-items: center;
176
- justify-content: center;
177
- block-size: var(--xh-toast-action-h, var(--xh-control-h-sm));
178
- padding-inline: var(--xh-toast-action-px, var(--xh-control-px-sm));
179
- border: var(--xh-stroke-thin) solid var(--xh-toast-action-border, var(--xh-border-default));
180
- border-radius: var(--xh-toast-action-radius, var(--xh-shape-control));
181
- background: var(--xh-toast-action-bg, var(--xh-bg-subtle));
182
- color: var(--xh-toast-action-fg, var(--xh-fg-default));
183
- /* 按钮不继承字族字号,不接回来的话条子里会突然冒出一段系统 UI 字体 */
184
- font: inherit;
382
+ align-self: center;
185
383
  font-weight: var(--xh-toast-action-font-weight, var(--xh-font-weight-medium));
186
- line-height: var(--xh-leading-none);
187
- cursor: pointer;
188
384
  }
189
385
 
386
+ /* 家族那条 display 会把 UA 的 [hidden]{display:none} 盖掉 */
190
387
  [data-scope='toast'][data-part='action-trigger'][hidden] {
191
388
  display: none;
192
389
  }
193
390
 
194
- [data-scope='toast'][data-part='action-trigger']:hover:not(:disabled) {
195
- background: var(--xh-toast-action-bg-hover, var(--xh-bg-subtle-hover));
391
+ /*
392
+ * 倒计时条。总时长由连接层写进 --xh-toast-progress-duration(不自动消失的那些整条收起),
393
+ * 计时被按住时动画一并停住——停表这件事此前没有任何可画的地方。
394
+ * 取装饰档 --xh-_tone-soft:压在同族淡底上仍看得出来。
395
+ */
396
+ [data-scope='toast'][data-part='progress'] {
397
+ position: absolute;
398
+ inset-block-end: 0;
399
+ inset-inline-start: 0;
400
+ overflow: hidden;
401
+ inline-size: 100%;
402
+ block-size: var(--xh-toast-progress-thickness, var(--xh-space-0_5));
403
+ background: var(--xh-toast-progress-bg, var(--xh-_tone-soft));
404
+ animation: xh-countdown var(--xh-toast-progress-duration, var(--xh-motion-duration-slide)) var(--xh-motion-ease-loop) forwards;
196
405
  }
197
406
 
198
- [data-scope='toast'][data-part='action-trigger']:active:not(:disabled) {
199
- background: var(--xh-toast-action-bg-active, var(--xh-bg-subtle-active));
407
+ [data-scope='toast'][data-part='root'][data-paused] [data-part='progress'] {
408
+ animation-play-state: paused;
200
409
  }
201
410
 
202
- [data-scope='toast'][data-part='action-trigger']:focus-visible {
203
- outline: var(--xh-ring-width) solid var(--xh-ring-focus);
204
- outline-offset: var(--xh-ring-offset);
411
+ [data-scope='toast'][data-part='progress'][hidden] {
412
+ display: none;
205
413
  }
206
414
 
415
+ /* 关闭钮是只有字形的离散动作钮:盒几何、悬停 / 按下与 0.97 按压、粗指针热区、焦点环、禁用面全由 Action Control
416
+ 的 icon ghost 档给(连接层投 xs:24px 正方盒,与此前 --xh-control-action-size 同尺寸;那颗叉排在单行短消息里,
417
+ 走行级动作钮那一档),组件只映射既有使用者槽。静息透明,sheet 白面承载 hover 100 → pressed 200,
418
+ 写了语气时悬停底跟着语气走(--xh-_tone-subtle)。
419
+ 有悬停能力的设备上默认静默、指针进入条子或焦点落进条子后显现——不走家族的 hover-focus 显示策略:
420
+ 那一档用 visibility 收起,这颗叉占 Tab 位,收成 visibility: hidden 后键盘就够不到它了;
421
+ 这里只压 opacity 与 pointer-events,焦点仍能落进来 */
207
422
  [data-scope='toast'][data-part='close-trigger'] {
208
- flex: none;
209
- /* 推到行内末侧;和操作按钮同处一行时排在它后面 */
210
- margin-inline-start: auto;
211
- display: inline-flex;
212
- align-items: center;
213
- justify-content: center;
214
- /* 行级动作钮那一档(24px),不是浮层角落钮的 28px:
215
- 后者比一行正文的行盒还高,单它就把整条顶高一截 */
216
- block-size: var(--xh-toast-close-size, var(--xh-control-action-size));
217
- inline-size: var(--xh-toast-close-size, var(--xh-control-action-size));
218
- border: 0;
219
- border-radius: var(--xh-toast-close-radius, var(--xh-shape-control));
220
- background: transparent;
221
- color: var(--xh-toast-close-fg, var(--xh-fg-muted));
222
- font: inherit;
223
- line-height: var(--xh-leading-none);
224
- cursor: pointer;
225
- transition:
226
- background var(--xh-motion-duration-micro) var(--xh-motion-ease-enter),
227
- color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter),
228
- scale var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
229
- }
423
+ --xh-action-visual-size: var(--xh-toast-close-size, var(--xh-_action-profile-visual-size));
424
+ --xh-action-min-inline-size: var(--xh-toast-close-size, var(--xh-_action-profile-visual-size));
425
+ --xh-action-radius: var(--xh-toast-close-radius, var(--xh-shape-control));
426
+ --xh-action-bg-rest: var(--xh-toast-close-bg, var(--xh-_action-variant-bg-rest));
427
+ --xh-action-bg-hover: var(--xh-toast-close-bg-hover, var(--xh-_action-variant-bg-hover));
428
+ --xh-action-bg-pressed: var(--xh-toast-close-bg-active, var(--xh-_action-variant-bg-pressed));
429
+ --xh-action-fg-rest: var(--xh-toast-close-fg, var(--xh-fg-muted));
430
+ --xh-action-fg-hover: var(--xh-toast-close-fg-hover, var(--xh-fg-default));
431
+ --xh-action-fg-pressed: var(--xh-toast-close-fg-hover, var(--xh-fg-default));
432
+ --xh-action-border-rest: var(--xh-toast-close-border, var(--xh-_action-variant-border-rest));
433
+ --xh-action-highlight-rest: transparent;
434
+ --xh-action-shadow-rest: none;
435
+ --xh-action-shadow-hover: none;
436
+ --xh-action-shadow-pressed: none;
437
+ /* 叉的字形按钮档取值(xs → 16px),与 root 上给指示符的 md 档分开 */
438
+ --xh-icon-size: var(--xh-toast-icon-size, var(--xh-_action-profile-glyph-size));
230
439
 
231
- /* closable=false 时连接层同时给了 hidden 与 disabled,这条是它真正收起的那一半:
232
- 上面声明了 display,不补回来就会留下一个按不动的叉,比压根没有叉更让人困惑 */
233
- [data-scope='toast'][data-part='close-trigger'][hidden] {
234
- display: none;
440
+ flex: none;
441
+ align-self: center;
235
442
  }
236
443
 
237
- [data-scope='toast'][data-part='close-trigger']:hover:not(:disabled) {
238
- background: var(--xh-toast-close-bg-hover, var(--xh-bg-subtle-hover));
239
- color: var(--xh-toast-close-fg-hover, var(--xh-fg-default));
240
- }
444
+ @media (hover: hover) {
445
+ [data-scope='toast'][data-part='close-trigger'] {
446
+ opacity: 0;
447
+ pointer-events: none;
448
+ }
241
449
 
242
- [data-scope='toast'][data-part='close-trigger']:active {
243
- background: var(--xh-toast-close-bg-active, var(--xh-bg-subtle-active));
244
- scale: var(--xh-motion-scale-press);
450
+ [data-scope='toast'][data-part='root']:hover [data-part='close-trigger'],
451
+ [data-scope='toast'][data-part='root']:focus-within [data-part='close-trigger'] {
452
+ opacity: 1;
453
+ pointer-events: auto;
454
+ }
245
455
  }
246
456
 
247
- /* 单体控件用原生 disabled。常规路径下它与 hidden 同时出现看不见,
248
- 但作者若在覆盖层把收起改了回来,至少还能看出这颗叉是按不动的 */
249
- [data-scope='toast'][data-part='close-trigger']:disabled {
250
- color: var(--xh-fg-disabled);
251
- cursor: not-allowed;
457
+ /* 折叠堆叠只允许最前一条露出关闭入口;触屏没有 hover,必须显式收起后台按钮。 */
458
+ [data-scope='toast'][data-part='root'][data-stack-index]:not([data-frontmost]):not([data-expanded])
459
+ [data-part='close-trigger'] {
460
+ opacity: 0;
461
+ pointer-events: none;
252
462
  }
253
463
 
254
- [data-scope='toast'][data-part='close-trigger']:focus-visible {
255
- outline: var(--xh-ring-width) solid var(--xh-ring-focus);
256
- outline-offset: var(--xh-ring-offset);
464
+ /* closable=false 时连接层同时给了 hidden 与 disabled,这条是它真正收起的那一半:
465
+ 上面声明了 display,不补回来就会留下一个按不动的叉,比压根没有叉更让人困惑 */
466
+ [data-scope='toast'][data-part='close-trigger'][hidden] {
467
+ display: none;
257
468
  }
258
469
 
259
470
  /* 没写内容就由皮肤画兜底字形;作者往部件里塞了自己的图形,这几条即不命中 */
@@ -267,4 +478,22 @@
267
478
  -webkit-mask: var(--xh-glyph-mark-close) center / contain no-repeat;
268
479
  mask: var(--xh-glyph-mark-close) center / contain no-repeat;
269
480
  }
481
+
482
+ @media print {
483
+ /* 打印:轻提示是瞬态的,打印那一刻恰好在场就会横在正文上,且它已经错过了被读到的时机。 */
484
+ [data-scope='toast'][data-part='group'] {
485
+ display: none;
486
+ }
487
+
488
+ /* 摞收起后条子本不该出现在纸上;这一条兜的是作者把条子摆在别处的用法:
489
+ 动画会被定格在任意一帧——转了一半的圈。取值与减弱动效那一档相同。 */
490
+ [data-scope='toast'][data-part='root'][data-loading]::before {
491
+ animation: none;
492
+ }
493
+ }
494
+
495
+ [data-scope='toast'][data-part='content'][hidden],
496
+ [data-scope='toast'][data-part='description'][hidden] {
497
+ display: none;
498
+ }
270
499
  }