@xihan-ui/styles 2.1.0 → 3.1.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 +1261 -0
  2. package/README.md +6 -1
  3. package/component-tokens.d.ts +533 -96
  4. package/components.tokens.json +35461 -26045
  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 +128 -50
  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 +56 -0
  37. package/css/date-picker.css +132 -26
  38. package/css/date-range-picker.css +337 -14
  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 +20 -18
  49. package/css/file-upload.css +125 -135
  50. package/css/flex.css +324 -8
  51. package/css/float-button.css +263 -38
  52. package/css/floating-panel.css +53 -19
  53. package/css/form.css +31 -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 +435 -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 +175 -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 +103 -17
  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 +271 -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 +13824 -4030
  147. package/index.unlayered.css +13403 -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
@@ -5,59 +5,39 @@
5
5
 
6
6
  /* 定义 notification 样式。 */
7
7
 
8
- /* Notification 独立皮肤同时传递引入操作 / 关闭钮的动作控件配方;full bundle 递归去重。 */
8
+ /* 共享关键帧由 family/motion.css 提供,本皮肤 @import 它,单独引入仍成立。 */
9
+ @import '../family/motion.css';
10
+ /* Notification 独立皮肤同时传递引入操作 / 关闭钮的动作控件配方与加载环配方;full bundle 递归去重。 */
9
11
  @import '../family/action-control.css';
12
+ @import '../family/loading-ring.css';
10
13
 
11
14
  @layer xihan.motion {
12
- /*
13
- * 九个位共用同一条进出场,位移不跟着落位走:要按位置分方向,就得从 group 的
14
- * data-placement 反向选到卡片上,落位一改名整条动效静默失效。
15
- * 竖向一小段位移在九个位上都说得通。
16
- */
17
- @keyframes xh-notification-in {
15
+ /* 叠放的一摞:新卡片从视口边缘外进入,同时落到自己的折叠层级。整幅位移取令牌,减弱动效下归零,只剩淡入 */
16
+ @keyframes xh-notification-stack-in {
18
17
  from {
19
- opacity: 0;
20
- translate: 0 var(--xh-motion-distance-md);
21
- scale: var(--xh-motion-scale-enter);
18
+ opacity: var(--xh-_enter-from-opacity, 0);
19
+ translate: 0 calc(var(--xh-_notification-stack-dir, 1) * (var(--xh-_notification-offset, 0px) - var(--xh-motion-travel)));
20
+ scale: var(--xh-notification-stack-scale, var(--xh-_notification-stack-scale));
22
21
  }
23
22
 
24
23
  to {
25
24
  opacity: 1;
26
- translate: 0 0;
27
- scale: 1;
28
- }
29
- }
30
-
31
- /* loading 那枚字形自己转。逐皮肤自带一份:跨皮肤共用动画名,
32
- 引用方那份皮肤不在场时动画就静默不跑 */
33
- @keyframes xh-notification-spin {
34
- to {
35
- rotate: 360deg;
25
+ translate: 0 calc(var(--xh-_notification-stack-dir, 1) * var(--xh-_notification-offset, 0px));
26
+ scale: var(--xh-notification-stack-scale, var(--xh-_notification-stack-scale));
36
27
  }
37
28
  }
38
29
 
39
- /* 倒计时条:从占满一整条收到零。走一遍就到头,时长由连接层写进时长槽 */
40
- @keyframes xh-countdown {
41
- from {
42
- inline-size: 100%;
43
- }
44
-
45
- to {
46
- inline-size: 0%;
47
- }
48
- }
49
-
50
- @keyframes xh-notification-out {
30
+ @keyframes xh-notification-stack-out {
51
31
  from {
52
32
  opacity: 1;
53
- translate: 0 0;
54
- scale: 1;
33
+ translate: 0 calc(var(--xh-_notification-stack-dir, 1) * var(--xh-_notification-y, 0px));
34
+ scale: var(--xh-_notification-scale, 1);
55
35
  }
56
36
 
57
37
  to {
58
38
  opacity: 0;
59
- translate: 0 var(--xh-motion-distance-md);
60
- scale: var(--xh-motion-scale-exit);
39
+ translate: 0 calc(var(--xh-_notification-stack-dir, 1) * (var(--xh-_notification-y, 0px) - var(--xh-motion-travel)));
40
+ scale: var(--xh-_notification-scale, 1);
61
41
  }
62
42
  }
63
43
  }
@@ -84,7 +64,7 @@
84
64
  }
85
65
 
86
66
  /*
87
- * 一摞 = 一整面视口大小的定位面:贴边靠 padding,九个位靠 justify/align 表达。
67
+ * 逐条排开的一摞 = 一整面视口大小的定位面:贴边靠 padding,九个位靠 justify/align 表达。
88
68
  * 不用四角 inset 逐个摆,是因为 column flex 的 align-items 走的是行内轴,
89
69
  * RTL 下 start/end 自动跟着翻;写死 inset-inline-start 的话居中那三个位
90
70
  * 还得再配一次 translate,方向一变就错。
@@ -97,7 +77,7 @@
97
77
  z-index: var(--xh-notification-layer, var(--xh-layer-toast));
98
78
  display: flex;
99
79
  flex-direction: column;
100
- /* 基线按默认落位 bottom-end 写,下面只覆盖跟它不同的那几个位 */
80
+ /* 基线按卡片的默认落位 bottom-end 写,下面只覆盖跟它不同的那几个位 */
101
81
  justify-content: flex-end;
102
82
  align-items: center;
103
83
  /* 贴边与安全区取大的一头:刘海、圆角与底部横条压住的那一段由系统给出,
@@ -111,13 +91,24 @@
111
91
  env(safe-area-inset-left),
112
92
  env(safe-area-inset-right)
113
93
  );
114
- /* 摞内间距由连接层写进内联 style(两个适配器共用同一份),这里绝不重复写:
94
+ /* 摞内间距由连接层写进内联 style(三个适配器共用同一份),这里绝不重复写:
115
95
  写了也会被内联盖掉,只会让人误以为间距归样式层管 */
116
96
  /* 整面铺开就等于罩住了整块视口。面本身不吃指针,只有卡片吃(见下),
117
97
  否则九个位一叠,底下的页面就再也点不动了 */
118
98
  pointer-events: none;
119
99
  }
120
100
 
101
+ /* 轻提示离视口边更近 */
102
+ [data-scope='notification'][data-part='group'][data-preset='toast']:not([data-stacked]) {
103
+ padding-block-start: max(var(--xh-notification-inset, var(--xh-space-4)), env(safe-area-inset-top));
104
+ padding-block-end: max(var(--xh-notification-inset, var(--xh-space-4)), env(safe-area-inset-bottom));
105
+ padding-inline: max(
106
+ var(--xh-notification-inset, var(--xh-space-4)),
107
+ env(safe-area-inset-left),
108
+ env(safe-area-inset-right)
109
+ );
110
+ }
111
+
121
112
  [data-scope='notification'][data-part='group'][hidden] {
122
113
  display: none;
123
114
  }
@@ -146,12 +137,89 @@
146
137
  }
147
138
 
148
139
  /*
149
- * 一张通知卡:左列一枚类型字形,右列「标题 + 说明 + 操作」竖着排,叉钉在右上角。
150
- * 用网格而不是竖排 flex:叉要脱离文档流钉到角上,正文要在字形右边成一列——
151
- * 竖排 flex 里这两件事都做不到(叉会变成第三行)。
152
- * 底与描边一律中性,语气只由左列那枚字形承载:整片兑色是轻提示那条的做法,
153
- * 一张有标题有正文的卡片全片染色会把两层文字都压在同一片语气色上。
154
- * 面走 sheet 三件套(--xh-material-elevated-bg / -border / -shadow,与 toast 同一套)。
140
+ * 叠成一摞:定位面收成一条固定宽度、零高度的线,贴在落位那一角;卡片在里面绝对定位,
141
+ * 按叠摞测量写进来的层深与偏移错开。贴边改由 inset 表达,定位面不再整面铺开。
142
+ */
143
+ [data-scope='notification'][data-part='group'][data-stacked] {
144
+ inset: auto;
145
+ inline-size: min(var(--xh-notification-item-w, var(--xh-overlay-max-w-lg)), calc(100vw - var(--xh-space-8)));
146
+ block-size: 0;
147
+ padding: 0;
148
+ }
149
+
150
+ /* 定位面不再是弹性列:卡片各自绝对定位。收起与空摞照旧撤掉 */
151
+ [data-scope='notification'][data-part='group'][data-stacked]:not([hidden]):not([data-empty]) {
152
+ display: block;
153
+ }
154
+
155
+ [data-scope='notification'][data-part='group'][data-stacked][data-preset='toast'] {
156
+ inline-size: min(var(--xh-notification-item-w, var(--xh-overlay-toast-w)), calc(100vw - var(--xh-space-8)));
157
+ }
158
+
159
+ [data-scope='notification'][data-part='group'][data-stacked][data-placement^='bottom'] {
160
+ inset-block-end: max(var(--xh-notification-inset, var(--xh-space-6)), env(safe-area-inset-bottom));
161
+ }
162
+
163
+ [data-scope='notification'][data-part='group'][data-stacked][data-placement^='top'] {
164
+ inset-block-start: max(var(--xh-notification-inset, var(--xh-space-6)), env(safe-area-inset-top));
165
+ }
166
+
167
+ [data-scope='notification'][data-part='group'][data-stacked][data-preset='toast'][data-placement^='bottom'] {
168
+ inset-block-end: max(var(--xh-notification-inset, var(--xh-space-4)), env(safe-area-inset-bottom));
169
+ }
170
+
171
+ [data-scope='notification'][data-part='group'][data-stacked][data-preset='toast'][data-placement^='top'] {
172
+ inset-block-start: max(var(--xh-notification-inset, var(--xh-space-4)), env(safe-area-inset-top));
173
+ }
174
+
175
+ /* 中间那一行从视口中线往下叠 */
176
+ [data-scope='notification'][data-part='group'][data-stacked][data-placement^='middle'] {
177
+ inset-block-start: 50%;
178
+ }
179
+
180
+ /* 起点是逻辑属性、平移是物理通道:平移乘书写方向的符号,rtl 下居中的那一摞才落回视口正中 */
181
+ [data-scope='notification'][data-part='group'][data-stacked]:is([data-placement='top'], [data-placement='middle'], [data-placement='bottom']) {
182
+ inset-inline-start: 50%;
183
+ translate: calc(-50% * var(--xh-direction-sign)) 0;
184
+ }
185
+
186
+ [data-scope='notification'][data-part='group'][data-stacked][data-placement$='-start'] {
187
+ inset-inline-start: max(
188
+ var(--xh-notification-inset, var(--xh-space-6)),
189
+ env(safe-area-inset-left),
190
+ env(safe-area-inset-right)
191
+ );
192
+ }
193
+
194
+ [data-scope='notification'][data-part='group'][data-stacked][data-placement$='-end'] {
195
+ inset-inline-end: max(
196
+ var(--xh-notification-inset, var(--xh-space-6)),
197
+ env(safe-area-inset-left),
198
+ env(safe-area-inset-right)
199
+ );
200
+ }
201
+
202
+ [data-scope='notification'][data-part='group'][data-stacked][data-preset='toast'][data-placement$='-start'] {
203
+ inset-inline-start: max(
204
+ var(--xh-notification-inset, var(--xh-space-4)),
205
+ env(safe-area-inset-left),
206
+ env(safe-area-inset-right)
207
+ );
208
+ }
209
+
210
+ [data-scope='notification'][data-part='group'][data-stacked][data-preset='toast'][data-placement$='-end'] {
211
+ inset-inline-end: max(
212
+ var(--xh-notification-inset, var(--xh-space-4)),
213
+ env(safe-area-inset-left),
214
+ env(safe-area-inset-right)
215
+ );
216
+ }
217
+
218
+ /*
219
+ * 一张通知:底与描边一律中性,面走 sheet 三件套(--xh-material-elevated-bg / -border / -shadow,
220
+ * 亮暗两档同有 1px 描边)。两种预设共用这张面,排版分开写在下面:
221
+ * 卡片是两列网格,左列一枚类型字形、右列「标题 + 说明 + 操作」、叉钉在右上角;
222
+ * 轻提示是一行,行首字形、文本列、操作钮与叉依次排开。
155
223
  *
156
224
  * 卡片的槽带 item 段(--xh-notification-item-*),与摞的 --xh-notification-inset 分开。
157
225
  */
@@ -183,59 +251,193 @@
183
251
  /* 摞的定位面整面铺开且不吃指针,卡片必须自己吃回来:
184
252
  悬停按住计时、按钮可点、焦点进出,全指着这一条 */
185
253
  pointer-events: auto;
254
+ /* 换位:一张卡收起或新卡插进来时,列表动效先按旧位置反向补偿,再由这条过渡带回新位置 */
255
+ transition: translate var(--xh-motion-duration-move) var(--xh-motion-ease-continuous);
186
256
  }
187
257
 
188
- /* 除指示符外一律落第二列:作者往卡片里塞的任何东西都跟着正文走 */
189
- [data-scope='notification'][data-part='item'] > *:not([data-part='item-indicator']) {
258
+ /* 轻提示:一行排开,行首那一格贴着内容盒的起始角,文本列与两颗钮在行里垂直居中。
259
+ 指示符盒自带 --xh-space-1 内衬:与间距合起来,字形到正文 12px,与卡片、Alert 同一个距离 */
260
+ [data-scope='notification'][data-part='item'][data-preset='toast'] {
261
+ flex: none;
262
+ display: flex;
263
+ align-items: flex-start;
264
+ column-gap: var(--xh-notification-item-gap, var(--xh-space-2));
265
+ inline-size: var(--xh-notification-item-w, var(--xh-overlay-toast-w));
266
+ padding-block: var(--xh-notification-item-py, var(--xh-space-3));
267
+ padding-inline: var(--xh-notification-item-px, var(--xh-space-4));
268
+ font-size: var(--xh-notification-item-font-size, var(--xh-text-label-size));
269
+ /* 叠摞几何常驻:单独摆放时是恒等位移与比例,放进叠放的一摞后由那边换成层深,
270
+ 两处用同一套画法,卡片进出一摞时栅格不跳 */
271
+ translate: 0 calc(var(--xh-_notification-stack-dir, 1) * var(--xh-_notification-y, 0px));
272
+ scale: var(--xh-_notification-scale, 1);
273
+ transform-origin: bottom center;
274
+ }
275
+
276
+ /* 卡片:除指示符外一律落第二列,作者往卡片里塞的任何东西都跟着正文走 */
277
+ [data-scope='notification'][data-part='item'][data-preset='card'] > *:not([data-part='item-indicator']) {
190
278
  grid-column: 2;
191
279
  }
192
280
 
193
- /* 退场窗口走完连接层给的是 hidden 属性,而上面声明了 display——
281
+ /* 退场动画播完连接层给的是 hidden 属性,而上面声明了 display——
194
282
  漏掉这条,已经"卸载"的通知会一直杵在屏幕上,且再也没人来收 */
195
283
  [data-scope='notification'][data-part='item'][hidden] {
196
284
  display: none;
197
285
  }
198
286
 
199
- /* 生命周期只有三段,unmounted 那段由上面的 [hidden] 收走,这里只管前两段 */
287
+ /* 生命周期只有三段,unmounted 那段由上面的 [hidden] 收走,这里只管前两段。
288
+ 逐条排开的那几摞共用同一条进出场,位移不跟着落位走:要按位置分方向,就得从 group 的
289
+ data-placement 反向选到卡片上,落位一改名整条动效静默失效。竖向一小段位移在九个位上都说得通。
290
+ 同一批新到的几条按到达顺序错开开幕,不按它在那一摞里排第几(序号由条目到达的追踪写进
291
+ --xh-_stagger-index,封顶)。backwards 不能省:等待期间起帧不生效的话,
292
+ 卡片会先以全不透明度露一下再跳回起帧 */
200
293
  [data-scope='notification'][data-part='item'][data-state='visible'] {
201
- animation: xh-notification-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter-strong) backwards;
294
+ animation: xh-sheet-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter-strong) backwards;
295
+ animation-delay: calc(var(--xh-_stagger-index, 0) * var(--xh-motion-stagger-step));
296
+ }
297
+
298
+ /* forwards 不能省:动画播完到卡片真正收起之间还隔着一次提交,
299
+ 不定住末帧的话动画跑完会弹回全不透明,卡片在被收起前先闪一下 */
300
+ [data-scope='notification'][data-part='item'][data-state='dismissing'] {
301
+ animation: xh-sheet-out var(--xh-motion-duration-exit) var(--xh-motion-ease-exit) forwards;
302
+ }
303
+
304
+ /*
305
+ * 叠放的一摞:最新一条在最前,后层按层深逐层收拢——层深由叠摞测量写进来,每往后一层乘一次
306
+ * --xh-motion-scale-stack;减弱动效下那支令牌为 1,层与层只靠位移分开。
307
+ * 收拢比例作者可以用 --xh-notification-stack-scale 整摞改写。
308
+ */
309
+ [data-scope='notification'][data-part='group'][data-stacked] [data-scope='notification'][data-part='item'] {
310
+ --xh-_notification-stack-scale: max(0, 1 - var(--xh-_notification-depth, 0) * (1 - var(--xh-motion-scale-stack)));
311
+ --xh-_notification-y: var(--xh-_notification-offset, 0px);
312
+ --xh-_notification-scale: var(--xh-notification-stack-scale, var(--xh-_notification-stack-scale));
313
+
314
+ position: absolute;
315
+ inset-inline: 0;
316
+ inline-size: 100%;
317
+ translate: 0 calc(var(--xh-_notification-stack-dir, 1) * var(--xh-_notification-y));
318
+ scale: var(--xh-_notification-scale);
319
+ transform-origin: bottom center;
320
+ transition:
321
+ translate var(--xh-motion-duration-move) var(--xh-motion-ease-continuous),
322
+ scale var(--xh-motion-duration-move) var(--xh-motion-ease-continuous),
323
+ block-size var(--xh-motion-duration-move) var(--xh-motion-ease-continuous),
324
+ opacity var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
202
325
  }
203
326
 
204
- /* 一摞里的多条按位次错开开幕。backwards 不能省:等待期间起帧不生效的话,
205
- 卡片会先以全不透明度露一下再跳回起帧。第 6 条起并到同一拍上 */
206
- [data-scope='notification'][data-part='item'][data-state='visible']:nth-child(2) {
207
- animation-delay: calc(var(--xh-motion-stagger-step) * 1);
327
+ /* 贴底的几摞往上叠,贴顶与中间那一行往下叠 */
328
+ [data-scope='notification'][data-part='group'][data-stacked][data-placement^='bottom'] [data-scope='notification'][data-part='item'] {
329
+ --xh-_notification-stack-dir: -1;
330
+
331
+ inset-block-end: 0;
208
332
  }
209
333
 
210
- [data-scope='notification'][data-part='item'][data-state='visible']:nth-child(3) {
211
- animation-delay: calc(var(--xh-motion-stagger-step) * 2);
334
+ [data-scope='notification'][data-part='group'][data-stacked]:is([data-placement^='top'], [data-placement^='middle']) [data-scope='notification'][data-part='item'] {
335
+ --xh-_notification-stack-dir: 1;
336
+
337
+ inset-block-start: 0;
338
+ transform-origin: top center;
212
339
  }
213
340
 
214
- [data-scope='notification'][data-part='item'][data-state='visible']:nth-child(4) {
215
- animation-delay: calc(var(--xh-motion-stagger-step) * 3);
341
+ /* 叠放的卡片从视口边缘推入、沿叠放方向退出,进出场都带着自己那一层的位移与收拢比例 */
342
+ [data-scope='notification'][data-part='group'][data-stacked] [data-scope='notification'][data-part='item'][data-state='visible'] {
343
+ animation: xh-notification-stack-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter-strong) backwards;
216
344
  }
217
345
 
218
- [data-scope='notification'][data-part='item'][data-state='visible']:nth-child(5) {
219
- animation-delay: calc(var(--xh-motion-stagger-step) * 4);
346
+ [data-scope='notification'][data-part='group'][data-stacked] [data-scope='notification'][data-part='item'][data-state='dismissing'] {
347
+ animation: xh-notification-stack-out var(--xh-motion-duration-exit) var(--xh-motion-ease-exit) forwards;
220
348
  }
221
349
 
222
- [data-scope='notification'][data-part='item'][data-state='visible']:nth-child(n + 6) {
223
- animation-delay: calc(var(--xh-motion-stagger-step) * 5);
350
+ /* 收拢的后层只露出一截边:高度压成最前那一条,内容淡出,免得几层字叠在一起 */
351
+ [data-scope='notification'][data-part='group'][data-stacked] [data-scope='notification'][data-part='item'][data-stack-index]:not([data-frontmost]):not([data-expanded]) {
352
+ block-size: var(--xh-_notification-front-height, auto);
353
+ overflow: hidden;
224
354
  }
225
355
 
226
- /* forwards 不能省:退场窗口(removeDelay 默认 200ms)比退场动画长,
227
- 不定住末帧的话动画跑完会弹回全不透明,卡片在被收起前先闪一下 */
228
- [data-scope='notification'][data-part='item'][data-state='dismissing'] {
229
- animation: xh-notification-out var(--xh-motion-duration-exit) var(--xh-motion-ease-exit) forwards;
356
+ [data-scope='notification'][data-part='group'][data-stacked] [data-scope='notification'][data-part='item'] > :not([data-part='item-close-trigger']) {
357
+ transition: opacity var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
358
+ }
359
+
360
+ [data-scope='notification'][data-part='group'][data-stacked] [data-scope='notification'][data-part='item'][data-stack-index]:not([data-frontmost]):not([data-expanded])
361
+ > :not([data-part='item-close-trigger']) {
362
+ opacity: 0;
363
+ }
364
+
365
+ /* 展开:按真实高度排成一列,收拢比例归 1 */
366
+ [data-scope='notification'][data-part='group'][data-stacked] [data-scope='notification'][data-part='item'][data-expanded] {
367
+ --xh-_notification-y: var(--xh-_notification-offset-expanded, 0px);
368
+ --xh-_notification-scale: 1;
369
+
370
+ block-size: var(--xh-_notification-height, auto);
371
+ }
372
+
373
+ /*
374
+ * 语气的非颜色通道。行首字形按 data-tone 区分信息、成功、警告与失败;加载中另由 data-loading 说。
375
+ * 行首那一格叠着两层:::before 是加载环(加载环家族配方画成与 Spinner 环档同一副环,随 data-loading
376
+ * 淡入淡出、转与停),::after 是语气字形,加载中淡出让位——加载落定时环淡出、字形淡入,同一格里交叉淡变。
377
+ * 两层的颜色都照语气走。轻提示没渲染 item-indicator 时由卡片自己的伪元素补上。
378
+ */
379
+ [data-scope='notification'][data-part='item'][data-preset='toast'] {
380
+ --xh-_loading-ring-fg: var(--xh-notification-indicator-fg, var(--xh-_tone-fg));
381
+ }
382
+
383
+ /* 环那一层占着行首那一格,字形那一层叠在它上面 */
384
+ [data-scope='notification'][data-part='item'][data-preset='toast']::before {
385
+ content: '';
386
+ flex: none;
387
+ display: inline-block;
388
+ inline-size: var(--xh-icon-size, var(--xh-glyph-size-text));
389
+ block-size: var(--xh-icon-size, var(--xh-glyph-size-text));
390
+ vertical-align: var(--xh-glyph-baseline-shift);
391
+ }
392
+
393
+ /* 字形那一层按卡片的内衬落在同一格上:行首那一格贴着内容盒的起始角(一行按 flex-start 排列) */
394
+ [data-scope='notification'][data-part='item'][data-preset='toast']::after {
395
+ content: '';
396
+ position: absolute;
397
+ inset-block-start: var(--xh-notification-item-py, var(--xh-space-3));
398
+ inset-inline-start: var(--xh-notification-item-px, var(--xh-space-4));
399
+ inline-size: var(--xh-icon-size, var(--xh-glyph-size-text));
400
+ block-size: var(--xh-icon-size, var(--xh-glyph-size-text));
401
+ background-color: var(--xh-notification-indicator-fg, var(--xh-_tone-fg));
402
+ -webkit-mask: var(--xh-glyph-mark-info) center / contain no-repeat;
403
+ mask: var(--xh-glyph-mark-info) center / contain no-repeat;
404
+ transition: opacity var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
405
+ }
406
+
407
+ [data-scope='notification'][data-part='item'][data-preset='toast'][data-tone='success']::after {
408
+ -webkit-mask-image: var(--xh-glyph-mark-check);
409
+ mask-image: var(--xh-glyph-mark-check);
410
+ }
411
+
412
+ [data-scope='notification'][data-part='item'][data-preset='toast'][data-tone='warning']::after {
413
+ -webkit-mask-image: var(--xh-glyph-mark-warning);
414
+ mask-image: var(--xh-glyph-mark-warning);
415
+ }
416
+
417
+ [data-scope='notification'][data-part='item'][data-preset='toast'][data-tone='danger']::after {
418
+ -webkit-mask-image: var(--xh-glyph-mark-close);
419
+ mask-image: var(--xh-glyph-mark-close);
420
+ }
421
+
422
+ /* 加载中不是语气:字形淡出让位给环,颜色仍照语气走 */
423
+ [data-scope='notification'][data-part='item'][data-preset='toast'][data-loading]::after {
424
+ opacity: 0;
425
+ }
426
+
427
+ /* 作者渲染了指示符部件,行首那两层兜底就让位给它,一行里不出现两枚图形 */
428
+ [data-scope='notification'][data-part='item'][data-preset='toast']:has([data-part='item-indicator'])::before,
429
+ [data-scope='notification'][data-part='item'][data-preset='toast']:has([data-part='item-indicator'])::after {
430
+ content: none;
230
431
  }
231
432
 
232
- /* 独占第一列第一行,并与标题首行顶对齐:盒高取一行标题的行高,
433
+ /* 卡片:独占第一列第一行,并与标题首行顶对齐:盒高取一行标题的行高,
233
434
  字形在盒里居中,标题字号改了也不会错位 */
234
435
  [data-scope='notification'][data-part='item-indicator'] {
235
436
  --xh-icon-size: var(--xh-notification-indicator-size, var(--xh-glyph-size-md));
236
437
 
237
438
  grid-column: 1;
238
439
  grid-row: 1;
440
+ position: relative;
239
441
  display: inline-flex;
240
442
  align-items: center;
241
443
  justify-content: center;
@@ -244,56 +446,81 @@
244
446
  color: var(--xh-notification-indicator-fg, var(--xh-_tone-fg));
245
447
  }
246
448
 
449
+ /* 轻提示:方盒自带一圈内衬,与间距合起来让出字形到正文的那段距离 */
450
+ [data-scope='notification'][data-part='item'][data-preset='toast'] [data-part='item-indicator'] {
451
+ flex: none;
452
+ box-sizing: content-box;
453
+ inline-size: var(--xh-icon-size, var(--xh-glyph-size-text));
454
+ block-size: var(--xh-icon-size, var(--xh-glyph-size-text));
455
+ padding: var(--xh-notification-indicator-p, var(--xh-space-1));
456
+ }
457
+
247
458
  [data-scope='notification'][data-part='item-indicator'][hidden] {
248
459
  display: none;
249
460
  }
250
461
 
251
- /* 作者没往指示符里写内容就由皮肤画兜底字形,按语气各画一枚;
252
- 塞了自己的图形这几条即不命中 */
462
+ /* 作者没往指示符里写内容就由皮肤画兜底的环与字形,塞了自己的图形这几条即不命中。
463
+ 两层叠在同一格:::before 是加载环(加载环配方画成与 Spinner 环档同一副环,随 data-loading 淡入淡出、
464
+ 转与停,起始边取指示符的语气色),::after 是按语气各画一枚的字形,加载中淡出让位——
465
+ 加载落定时环淡出、字形淡入,同一格里交叉淡变。「在等什么」由卡片上的 role=status 与可见文案承担 */
253
466
  [data-scope='notification'][data-part='item-indicator']:empty::before {
254
467
  content: '';
255
468
  display: inline-block;
256
469
  inline-size: var(--xh-icon-size, var(--xh-glyph-size-text));
257
470
  block-size: var(--xh-icon-size, var(--xh-glyph-size-text));
258
471
  vertical-align: var(--xh-glyph-baseline-shift);
472
+ }
473
+
474
+ [data-scope='notification'][data-part='item-indicator']:empty::after {
475
+ content: '';
476
+ position: absolute;
477
+ inset: 0;
478
+ inline-size: var(--xh-icon-size, var(--xh-glyph-size-text));
479
+ block-size: var(--xh-icon-size, var(--xh-glyph-size-text));
480
+ margin: auto;
259
481
  background-color: currentColor;
260
482
  -webkit-mask: var(--xh-glyph-mark-info) center / contain no-repeat;
261
483
  mask: var(--xh-glyph-mark-info) center / contain no-repeat;
484
+ transition: opacity var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
262
485
  }
263
486
 
264
- [data-scope='notification'][data-part='item'][data-tone='success'] [data-part='item-indicator']:empty::before {
487
+ [data-scope='notification'][data-part='item'][data-tone='success'] [data-part='item-indicator']:empty::after {
265
488
  -webkit-mask-image: var(--xh-glyph-mark-check);
266
489
  mask-image: var(--xh-glyph-mark-check);
267
490
  }
268
491
 
269
- [data-scope='notification'][data-part='item'][data-tone='warning'] [data-part='item-indicator']:empty::before {
492
+ [data-scope='notification'][data-part='item'][data-tone='warning'] [data-part='item-indicator']:empty::after {
270
493
  -webkit-mask-image: var(--xh-glyph-mark-warning);
271
494
  mask-image: var(--xh-glyph-mark-warning);
272
495
  }
273
496
 
274
- [data-scope='notification'][data-part='item'][data-tone='danger'] [data-part='item-indicator']:empty::before {
497
+ [data-scope='notification'][data-part='item'][data-tone='danger'] [data-part='item-indicator']:empty::after {
275
498
  -webkit-mask-image: var(--xh-glyph-mark-close);
276
499
  mask-image: var(--xh-glyph-mark-close);
277
500
  }
278
501
 
279
- /* 加载中不是语气:只换字形,颜色沿用当前语气的前景档;写在四档语气之后,转圈压过它们 */
280
- [data-scope='notification'][data-part='item'][data-loading] [data-part='item-indicator']:empty::before {
281
- -webkit-mask-image: var(--xh-glyph-mark-rotate-right);
282
- mask-image: var(--xh-glyph-mark-rotate-right);
283
- animation: xh-notification-spin var(--xh-spin-duration) var(--xh-motion-ease-loop) infinite;
502
+ /* 加载中不是语气:字形淡出让位给环,颜色仍照语气走 */
503
+ [data-scope='notification'][data-part='item-indicator'][data-loading]:empty::after {
504
+ opacity: 0;
284
505
  }
285
506
 
286
- /* 减少动效:转圈停下,静止的箭头仍然读得出「还没好」;
287
- "在等什么"由卡片上的 role=status 与可见文案继续承担。
288
- 两组规则是同一件事的两个触发条件:系统偏好与作者打的 data-motion */
289
- @media (prefers-reduced-motion: reduce) {
290
- [data-scope='notification'][data-part='item'][data-loading] [data-part='item-indicator']:empty::before {
291
- animation: none;
292
- }
507
+ /* 标题与说明的文本列。卡片里它落在第二列,两行之间与网格同一个行距;
508
+ 轻提示里它占满行首字形与两颗钮之间的那一段,在行里垂直居中 */
509
+ [data-scope='notification'][data-part='item-content'] {
510
+ display: flex;
511
+ flex-direction: column;
512
+ row-gap: var(--xh-notification-item-row-gap, var(--xh-space-2));
513
+ min-inline-size: 0;
514
+ }
515
+
516
+ [data-scope='notification'][data-part='item'][data-preset='toast'] [data-part='item-content'] {
517
+ flex: 1 1 auto;
518
+ align-self: center;
519
+ row-gap: 0;
293
520
  }
294
521
 
295
- :where([data-motion='reduce']) [data-scope='notification'][data-part='item'][data-loading] [data-part='item-indicator']:empty::before {
296
- animation: none;
522
+ [data-scope='notification'][data-part='item-content'][hidden] {
523
+ display: none;
297
524
  }
298
525
 
299
526
  [data-scope='notification'][data-part='item-title'] {
@@ -305,21 +532,39 @@
305
532
  line-height: var(--xh-notification-title-leading, var(--xh-leading-tight));
306
533
  }
307
534
 
308
- /* 叉钉在右上角,标题得给它让位;说明与操作在第二行往下,不受影响。
535
+ /* 轻提示的语气落到标题与行首字形:一句话的结果,颜色本身就是信息 */
536
+ [data-scope='notification'][data-part='item'][data-preset='toast'] [data-part='item-title'] {
537
+ min-inline-size: 0;
538
+ color: var(--xh-notification-title-fg, var(--xh-_tone-fg));
539
+ }
540
+
541
+ /* 卡片的叉钉在右上角,标题得给它让位;说明与操作在第二行往下,不受影响。
309
542
  让位量与叉的贴边取同一个值,作者改了贴边槽,标题让出的那段跟着走 */
310
- [data-scope='notification'][data-part='item']:has([data-part='item-close-trigger']) [data-part='item-title'] {
543
+ [data-scope='notification'][data-part='item'][data-preset='card']:has([data-part='item-close-trigger']) [data-part='item-title'] {
311
544
  padding-inline-end: calc(
312
545
  var(--xh-notification-close-size, var(--xh-control-h-sm)) +
313
546
  var(--xh-notification-close-inset, var(--xh-surface-action-inset))
314
547
  );
315
548
  }
316
549
 
550
+ /* 正文有上限:推一段长文(公告全文)进来时卡片不被撑到整屏高——摞是整面视口的定位面、不吃指针也不裁切,
551
+ 撑出视口的那截既看不到也滚不到。超出的部分在正文自己里面滚,标题与操作钮留在卡片上;
552
+ 滚到头不把滚动量传给页面 */
317
553
  [data-scope='notification'][data-part='item-description'] {
318
554
  margin: 0;
555
+ max-block-size: var(--xh-notification-description-max-h, var(--xh-viewport-h-md));
556
+ overflow-y: auto;
557
+ overscroll-behavior: contain;
319
558
  color: var(--xh-notification-description-fg, var(--xh-fg-muted));
320
559
  font-size: var(--xh-notification-description-font-size, var(--xh-text-secondary-size));
321
560
  }
322
561
 
562
+ /* 轻提示的说明只是一行短上下文,行高收紧到正文之下 */
563
+ [data-scope='notification'][data-part='item'][data-preset='toast'] [data-part='item-description'] {
564
+ min-inline-size: 0;
565
+ line-height: var(--xh-notification-description-leading, var(--xh-leading-normal));
566
+ }
567
+
323
568
  /* 没给文案的那一个仍然要留在 DOM 里(aria-labelledby / aria-describedby 指着它),
324
569
  但空盒子会照吃一份 gap,卡片看着就多出一截莫名的留白 */
325
570
  [data-scope='notification'][data-part='item-title']:empty,
@@ -332,25 +577,50 @@
332
577
  display: none;
333
578
  }
334
579
 
335
- /* 倒计时条横跨整张卡片:它不是正文的一部分,不跟着第二列走 */
336
- [data-scope='notification'][data-part='item'] > [data-part='item-progress'] {
337
- grid-column: 1 / -1;
338
- }
339
-
340
580
  /*
341
581
  * 倒计时条。总时长由连接层写进 --xh-notification-progress-duration(不自动消失的那些
342
582
  * 整条收起),计时被按住时动画一并停住——停表这件事此前没有任何可画的地方。
343
583
  * 取装饰档 --xh-_tone-soft:压在卡片底色上仍看得出来。
584
+ * 裁切只认物理方向:收起的那一侧取书写方向的行尾,rtl 下从左往右收。
344
585
  */
345
586
  [data-scope='notification'][data-part='item-progress'] {
587
+ --xh-_countdown-cut-left: var(--xh-direction-start);
588
+ --xh-_countdown-cut-right: var(--xh-direction-end);
589
+
346
590
  overflow: hidden;
347
591
  inline-size: 100%;
348
592
  block-size: var(--xh-notification-progress-thickness, var(--xh-space-0_5));
349
- border-radius: var(--xh-notification-progress-radius, var(--xh-shape-pill));
350
593
  background: var(--xh-notification-progress-bg, var(--xh-_tone-soft));
351
594
  animation: xh-countdown var(--xh-notification-progress-duration, var(--xh-motion-duration-slide)) var(--xh-motion-ease-loop) forwards;
352
595
  }
353
596
 
597
+ /* 卡片:横跨整张卡片的一行,它不是正文的一部分,不跟着第二列走;两端取胶囊 */
598
+ [data-scope='notification'][data-part='item'][data-preset='card'] [data-part='item-progress'] {
599
+ --xh-_countdown-radius: var(--xh-notification-progress-radius, var(--xh-shape-pill));
600
+
601
+ grid-column: 1 / -1;
602
+ border-radius: var(--xh-_countdown-radius);
603
+ }
604
+
605
+ /* 轻提示:贴着卡片底边走 */
606
+ [data-scope='notification'][data-part='item'][data-preset='toast'] [data-part='item-progress'] {
607
+ position: absolute;
608
+ inset-block-end: 0;
609
+ inset-inline-start: 0;
610
+ }
611
+
612
+ /* 减弱动效:倒计时不连续地缩,按秒一格一格走,段数由连接层随时长交出。
613
+ 两条规则是同一件事的两个触发条件:系统偏好与作者打的 data-motion */
614
+ @media (prefers-reduced-motion: reduce) {
615
+ [data-scope='notification'][data-part='item-progress'] {
616
+ animation-timing-function: steps(var(--xh-_notification-progress-steps, 1), end);
617
+ }
618
+ }
619
+
620
+ :where([data-motion='reduce']) [data-scope='notification'][data-part='item-progress'] {
621
+ animation-timing-function: steps(var(--xh-_notification-progress-steps, 1), end);
622
+ }
623
+
354
624
  [data-scope='notification'][data-part='item'][data-paused] [data-part='item-progress'] {
355
625
  animation-play-state: paused;
356
626
  }
@@ -361,7 +631,7 @@
361
631
 
362
632
  /* 操作钮是带文案的离散动作钮:盒几何、悬停 / 按下与 0.97 按压、粗指针热区、焦点环、禁用面全由 Action Control
363
633
  的 text outline 档给(连接层投 sm;非 Button 的触发器缺省中性描边、透明底),组件只映射既有使用者槽。
364
- 语气只落到左列字形:这颗钮坐在带 data-tone 的卡片里,底 / 边 / 字钉在中性语义面上,不随语气;
634
+ 语气只落到字形与轻提示的标题:这颗钮坐在带 data-tone 的卡片里,底 / 边 / 字钉在中性语义面上,不随语气;
365
635
  卡片是 sheet 白面承载:hover --xh-bg-subtle(100)→ pressed --xh-bg-subtle-hover(200) */
366
636
  [data-scope='notification'][data-part='item-action-trigger'] {
367
637
  --xh-action-visual-size: var(--xh-notification-action-h, var(--xh-_action-profile-visual-size));
@@ -388,36 +658,73 @@
388
658
  font-weight: var(--xh-notification-action-font-weight, var(--xh-font-weight-medium));
389
659
  }
390
660
 
661
+ /* 轻提示是单行 flex,按钮不参与伸缩:文案长了该换行的是文字 */
662
+ [data-scope='notification'][data-part='item'][data-preset='toast'] [data-part='item-action-trigger'] {
663
+ flex: none;
664
+ align-self: center;
665
+ }
666
+
391
667
  /* 家族那条 display 会把 UA 的 [hidden]{display:none} 盖掉 */
392
668
  [data-scope='notification'][data-part='item-action-trigger'][hidden] {
393
669
  display: none;
394
670
  }
395
671
 
396
672
  /* 关闭钮是只有字形的离散动作钮:盒几何、悬停 / 按下与 0.97 按压、粗指针热区、焦点环、禁用面全由 Action Control
397
- 的 icon ghost 档给(连接层投 sm:32px 正方盒,面板角上的叉与此前 --xh-control-h-sm 同尺寸),组件只映射既有
398
- 使用者槽。静息透明,sheet 白面承载 hover 100 → pressed 200,写了语气时悬停底跟着语气走(--xh-_tone-subtle)。
399
- 钉在右上角的内衬处,不参与网格排布——留在流里它会变成正文下面的第三行 */
673
+ 的 icon ghost 档给(卡片投 sm:32px 正方盒;轻提示投 xs:24px,走行级动作钮那一档),组件只映射既有使用者槽。
674
+ 静息透明,sheet 白面承载 hover 100 → pressed 200,写了语气时悬停底跟着语气走(--xh-_tone-subtle) */
400
675
  [data-scope='notification'][data-part='item-close-trigger'] {
401
676
  --xh-action-visual-size: var(--xh-notification-close-size, var(--xh-_action-profile-visual-size));
402
677
  --xh-action-min-inline-size: var(--xh-notification-close-size, var(--xh-_action-profile-visual-size));
403
678
  --xh-action-radius: var(--xh-notification-close-radius, var(--xh-shape-control));
679
+ --xh-action-bg-rest: var(--xh-notification-close-bg, var(--xh-_action-variant-bg-rest));
404
680
  --xh-action-bg-hover: var(--xh-notification-close-bg-hover, var(--xh-_action-variant-bg-hover));
405
681
  --xh-action-bg-pressed: var(--xh-notification-close-bg-active, var(--xh-_action-variant-bg-pressed));
406
682
  --xh-action-fg-rest: var(--xh-notification-close-fg, var(--xh-fg-muted));
407
683
  --xh-action-fg-hover: var(--xh-notification-close-fg-hover, var(--xh-fg-default));
408
684
  --xh-action-fg-pressed: var(--xh-notification-close-fg-hover, var(--xh-fg-default));
409
- --xh-action-border-rest: transparent;
685
+ --xh-action-border-rest: var(--xh-notification-close-border, var(--xh-_action-variant-border-rest));
410
686
  --xh-action-highlight-rest: transparent;
411
687
  --xh-action-shadow-rest: none;
412
688
  --xh-action-shadow-hover: none;
413
689
  --xh-action-shadow-pressed: none;
414
- /* 叉的字形按钮档取值(sm → 16px),与左列那枚 md 档的类型字形分开 */
690
+ /* 叉的字形按钮档取值,与行首那枚 md 档的类型字形分开 */
415
691
  --xh-icon-size: var(--xh-notification-icon-size, var(--xh-_action-profile-glyph-size));
692
+ }
416
693
 
694
+ /* 卡片:钉在右上角的内衬处,不参与网格排布——留在流里它会变成正文下面的第三行 */
695
+ [data-scope='notification'][data-part='item'][data-preset='card'] [data-part='item-close-trigger'] {
417
696
  position: absolute;
418
697
  inset-block-start: var(--xh-notification-close-inset, var(--xh-surface-action-inset));
419
698
  inset-inline-end: var(--xh-notification-close-inset, var(--xh-surface-action-inset));
420
- border-width: 0;
699
+ }
700
+
701
+ /* 轻提示:排在一行短消息的行尾,与文本列一起垂直居中 */
702
+ [data-scope='notification'][data-part='item'][data-preset='toast'] [data-part='item-close-trigger'] {
703
+ flex: none;
704
+ align-self: center;
705
+ }
706
+
707
+ /* 轻提示的叉在有悬停能力的设备上默认静默、指针进入卡片或焦点落进卡片后显现——不走家族的
708
+ hover-focus 显示策略:那一档用 visibility 收起,这颗叉占 Tab 位,收成 visibility: hidden
709
+ 后键盘就够不到它了;这里只压 opacity 与 pointer-events,焦点仍能落进来 */
710
+ @media (hover: hover) {
711
+ [data-scope='notification'][data-part='item'][data-preset='toast'] [data-part='item-close-trigger'] {
712
+ opacity: 0;
713
+ pointer-events: none;
714
+ }
715
+
716
+ [data-scope='notification'][data-part='item'][data-preset='toast']:hover [data-part='item-close-trigger'],
717
+ [data-scope='notification'][data-part='item'][data-preset='toast']:focus-within [data-part='item-close-trigger'] {
718
+ opacity: 1;
719
+ pointer-events: auto;
720
+ }
721
+ }
722
+
723
+ /* 收拢的后层只允许最前一条露出关闭入口;触屏没有 hover,必须显式收起后层的叉 */
724
+ [data-scope='notification'][data-part='group'][data-stacked] [data-scope='notification'][data-part='item'][data-stack-index]:not([data-frontmost]):not([data-expanded])
725
+ [data-part='item-close-trigger'] {
726
+ opacity: 0;
727
+ pointer-events: none;
421
728
  }
422
729
 
423
730
  /* closable=false 时连接层同时给了 hidden 与 disabled,这条是它真正收起的那一半:
@@ -439,17 +746,37 @@
439
746
  }
440
747
 
441
748
  @media print {
442
- /* 打印:动画会被定格在任意一帧——转了一半的圈、扫到一半的微光。这里换成静止的表达,
443
- 取值与减弱动效那一档相同。两档各写一块、不并进一条媒体查询列表:减弱动效另有一份
444
- :where([data-motion='reduce']) 的钩子版本与它成对,打印没有对应的钩子可配,
445
- 并成一条之后这层配对关系在文本上就断了。 */
446
- /* 通知组固定在视口上,打印那一刻在场就会盖住正文。 */
749
+ /* 通知组固定在视口上,打印那一刻在场就会盖住正文,且它已经错过了被读到的时机。 */
447
750
  [data-scope='notification'][data-part='group'] {
448
751
  display: none;
449
752
  }
450
753
 
451
- [data-scope='notification'][data-part='item'][data-loading] [data-part='item-indicator']:empty::before {
452
- animation: none;
754
+ /* 作者把卡片摆在摞外的用法:加载环的静止替代由加载环配方在打印档给出 */
755
+ }
756
+
757
+ @media (forced-colors: active) {
758
+ /* 兜底字形是 mask 挖出来的一块底色,这一档里底色被系统换成 Canvas,字形跟着消失。
759
+ 字形退出强制换色、直接写系统色(不能取 currentColor:退出强制换色的伪元素读到的是被系统换掉之前的作者色):
760
+ 按钮面上取 ButtonText,悬停与按下时家族把按钮涂成 Highlight、字形换 HighlightText;
761
+ 落在 Canvas 面上的取 CanvasText;
762
+ 禁用取 GrayText */
763
+ [data-scope='notification'][data-part='item'][data-preset='toast']::after,
764
+ [data-scope='notification'][data-part='item-indicator']:empty::after {
765
+ forced-color-adjust: none;
766
+ background-color: CanvasText;
767
+ }
768
+
769
+ [data-scope='notification'][data-part='item-close-trigger']:empty::before {
770
+ forced-color-adjust: none;
771
+ background-color: ButtonText;
772
+ }
773
+
774
+ [data-scope='notification'][data-part='item-close-trigger']:not([data-disabled]):not([data-loading]):is(:hover, :active, [data-pressed]):empty::before {
775
+ background-color: HighlightText;
776
+ }
777
+
778
+ [data-scope='notification'][data-part='item-close-trigger'][data-disabled]:empty::before {
779
+ background-color: GrayText;
453
780
  }
454
781
  }
455
782
  }