@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
package/css/layout.css CHANGED
@@ -18,7 +18,7 @@
18
18
  缺哪一段,对应的行/列就没有条目,宽高自动收成 0,不必为"少写一段"另立规则。
19
19
 
20
20
  侧栏两档宽度与头的高度声明在根上:各段自己读这些槽,换档只改这里。
21
- 宽度槽用 -w 缩写,与同处的 --xh-layout-header-h 一致。
21
+ 宽度槽用 -w 缩写,与同处的 --xh-layout-header-h 一致;两档宽度的缺省取侧栏令牌 --xh-sider-w / --xh-sider-collapsed-w。
22
22
  头的高度之所以也归到根上:头吸顶时头这一行的轨道要按同一个值定高,两处必须同源。 */
23
23
  [data-scope='layout'][data-part='root'] {
24
24
  display: grid;
@@ -30,9 +30,9 @@
30
30
  grid-template-rows: auto minmax(0, 1fr) auto;
31
31
  background: var(--xh-layout-bg, var(--xh-bg-page));
32
32
  color: var(--xh-layout-fg, var(--xh-fg-default));
33
- --xh-_layout-sider-expanded-w: var(--xh-layout-sider-w, 15rem);
33
+ --xh-_layout-sider-expanded-w: var(--xh-layout-sider-w, var(--xh-sider-w));
34
34
  --xh-_layout-sider-w: var(--xh-_layout-sider-expanded-w);
35
- --xh-_layout-sider-collapsed-w: var(--xh-layout-sider-collapsed-w, 4rem);
35
+ --xh-_layout-sider-collapsed-w: var(--xh-layout-sider-collapsed-w, var(--xh-sider-collapsed-w));
36
36
  --xh-_layout-header-h: var(--xh-layout-header-h, 3.5rem);
37
37
  /* 侧栏吸附时钉在哪条线上:缺省贴滚动容器上沿,头也吸顶时改成让开头的高度 */
38
38
  --xh-_layout-sider-offset: 0px;
@@ -97,13 +97,15 @@
97
97
  [data-scope='layout'][data-part='sider'] {
98
98
  --xh-action-host-bg-hover: var(--xh-bg-subtle-hover);
99
99
  --xh-action-host-bg-pressed: var(--xh-bg-subtle-active);
100
+ /* 内衬缺省一档,装了 SideNav 的侧栏在下面归零;覆盖档与安全区取大时读的也是这一支 */
101
+ --xh-_layout-sider-padding: var(--xh-space-3);
100
102
 
101
103
  grid-area: sider;
102
104
  inline-size: var(--xh-_layout-sider-w);
103
105
  overflow: hidden;
104
- padding: var(--xh-layout-sider-padding, var(--xh-space-3));
106
+ padding: var(--xh-layout-sider-padding, var(--xh-_layout-sider-padding));
105
107
  background: var(--xh-layout-sider-bg, var(--xh-bg-subtle));
106
- transition: inline-size var(--xh-motion-duration-enter) var(--xh-motion-ease-continuous);
108
+ transition: inline-size var(--xh-motion-duration-move) var(--xh-motion-ease-continuous);
107
109
  }
108
110
 
109
111
  /* 折起来时不换行:窄到放不下的那几个字直接被 overflow 裁掉,不会挤成一列竖排 */
@@ -112,6 +114,19 @@
112
114
  white-space: nowrap;
113
115
  }
114
116
 
117
+ /* 侧栏里装着 SideNav 时内衬缺省归零:SideNav 自带一圈内衬,宽度又与侧栏同取侧栏令牌,
118
+ 再叠一圈侧栏内衬,导航就比侧栏的内容盒宽出两侧内衬、右缘被裁掉。
119
+ 认后代而不是子代:Web Components 在侧栏与导航根之间隔着 <xh-side-nav> 宿主。
120
+ 不认 :has() 的引擎(Firefox 121 以前)照旧留内衬,导航右缘被裁,与改动前一致 */
121
+ [data-scope='layout'][data-part='sider']:has([data-scope='side-nav'][data-part='root']) {
122
+ --xh-_layout-sider-padding: 0;
123
+ }
124
+
125
+ /* 侧栏里的 SideNav 跟着侧栏收放:侧栏这一次开合直接落位(首帧、跨过断点)时,导航的宽度同样直接落位 */
126
+ [data-scope='layout'][data-part='sider'][data-instant] [data-scope='side-nav'][data-part='root'] {
127
+ transition: none;
128
+ }
129
+
115
130
  [data-scope='layout'][data-part='content'] {
116
131
  grid-area: content;
117
132
  /* 内容列已是 minmax(0, 1fr),这里再声明一次,宽表格与长串文本才撑不破列 */
@@ -189,6 +204,23 @@
189
204
  inset-block-start: 0;
190
205
  }
191
206
 
207
+ /* liquid 档:吸顶的顶栏是内容从下面滚过的导航层,换成液态面。贴边铺满的栏不取圆角,
208
+ 边界由下沿那条墨色细线承担。液态面是墨色域,前景在这里按域重取,子孙跟着继承。
209
+ 滚过深浅分界时色调翻转:底色、字色、细线与投影一起淡变,不在同一帧跳 */
210
+ :where([data-material='liquid']) [data-scope='layout'][data-part='header'][data-fixed][data-xh-liquid] {
211
+ border-block-end: var(--xh-stroke-thin) solid var(--xh-layout-border, var(--xh-material-liquid-border));
212
+ background: var(--xh-layout-header-bg, var(--xh-_liquid-bg));
213
+ color: var(--xh-material-liquid-fg);
214
+ box-shadow: var(--xh-_liquid-shadow);
215
+ -webkit-backdrop-filter: var(--xh-material-liquid-backdrop);
216
+ backdrop-filter: var(--xh-material-liquid-backdrop);
217
+ transition:
218
+ background-color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter),
219
+ border-color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter),
220
+ color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter),
221
+ box-shadow var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
222
+ }
223
+
192
224
  /* 头也吸顶时侧栏钉在头的下沿,两段不叠在一起 */
193
225
  [data-scope='layout'][data-part='root'][data-header-fixed][data-sider-fixed] {
194
226
  --xh-_layout-sider-offset: var(--xh-_layout-header-h);
@@ -215,10 +247,10 @@
215
247
  参照的是视口,与上面 sticky 那条同一个注意事项:整副骨架装进带 transform / filter 的祖先里时,
216
248
  固定定位的包含块会换成那个祖先,面板跟着一起滚。 */
217
249
  [data-scope='layout'][data-part='sider'][data-presentation='sheet'] {
218
- /* 位移方向的私有槽位:默认按行首 + LTR,行尾与 RTL 在下面翻符号。
219
- 写成槽位而不是写死 ±100%:贴边用的是逻辑 inset,RTL 下行首落在物理右边,
250
+ /* 位移方向的私有槽位:默认按行首 + LTR,行尾与 RTL 在下面翻符号。幅度取整幅位移令牌,
251
+ 减弱动效下归零。写成槽位而不是写死物理方向:贴边用的是逻辑 inset,RTL 下行首落在物理右边,
220
252
  位移若写死物理方向就会出现「贴在右边、却从左边滑进来」 */
221
- --xh-_layout-sheet-shift: -100%;
253
+ --xh-_layout-sheet-shift: calc(-1 * var(--xh-motion-travel));
222
254
  /* 这一档的底是 elevated 白底:交互阶梯按承载面走,把上面占位档下发的 200 → 300 写回
223
255
  白底的 hover 100 → pressed 200 */
224
256
  --xh-action-host-bg-hover: var(--xh-bg-subtle);
@@ -238,10 +270,10 @@
238
270
  /* 贴死视口的那一档,内衬与安全区取大的一头:刘海、圆角与底部横条压住的那一段由系统给出。
239
271
  桌面上 env() 恒为 0,取值与内衬槽完全一致。安全区只有物理方向的四个名字,
240
272
  行内轴上两侧同取较宽的那一段,RTL 下不会翻错边 */
241
- padding-block-start: max(var(--xh-layout-sider-padding, var(--xh-space-3)), env(safe-area-inset-top));
242
- padding-block-end: max(var(--xh-layout-sider-padding, var(--xh-space-3)), env(safe-area-inset-bottom));
273
+ padding-block-start: max(var(--xh-layout-sider-padding, var(--xh-_layout-sider-padding)), env(safe-area-inset-top));
274
+ padding-block-end: max(var(--xh-layout-sider-padding, var(--xh-_layout-sider-padding)), env(safe-area-inset-bottom));
243
275
  padding-inline: max(
244
- var(--xh-layout-sider-padding, var(--xh-space-3)),
276
+ var(--xh-layout-sider-padding, var(--xh-_layout-sider-padding)),
245
277
  env(safe-area-inset-left),
246
278
  env(safe-area-inset-right)
247
279
  );
@@ -256,16 +288,19 @@
256
288
  overscroll 不在这里写 contain:这一面与占位档共用同一个部件,scroll-surface-registry 把侧栏整体登记为
257
289
  页内结构容器(overscroll=false),门禁按整个部件核,不按档分 */
258
290
  overflow-y: auto;
259
- /* 推出来与收进去是同一段行程,视口尺度的位移走 slide 档。
260
- visibility 与它同拍:收起那一路等位移播完才真正藏起来,展开那一路立刻露出来 */
291
+ /* 推出来走 slide 档:视口尺度的位移。并列的不透明度取淡变档:
292
+ 缺省两端都是不透明、看不出来,减弱动效下整幅位移归零、由它表达进出。
293
+ visibility 展开那一路立刻露出来 */
294
+ opacity: 1;
261
295
  transition:
262
296
  translate var(--xh-motion-duration-slide) var(--xh-motion-ease-slide),
297
+ opacity var(--xh-motion-duration-enter) var(--xh-motion-ease-enter),
263
298
  visibility var(--xh-motion-duration-slide) var(--xh-motion-ease-slide);
264
299
  }
265
300
 
266
301
  /* 挂在行尾:贴另一条边,位移随之反向,描边也换到贴着内容的那一侧 */
267
302
  [data-scope='layout'][data-part='sider'][data-presentation='sheet'][data-placement='end'] {
268
- --xh-_layout-sheet-shift: 100%;
303
+ --xh-_layout-sheet-shift: var(--xh-motion-travel);
269
304
 
270
305
  inset-inline-start: auto;
271
306
  inset-inline-end: 0;
@@ -274,18 +309,24 @@
274
309
  }
275
310
 
276
311
  [data-scope='layout'][data-part='sider'][data-presentation='sheet']:dir(rtl) {
277
- --xh-_layout-sheet-shift: 100%;
312
+ --xh-_layout-sheet-shift: var(--xh-motion-travel);
278
313
  }
279
314
 
280
315
  [data-scope='layout'][data-part='sider'][data-presentation='sheet'][data-placement='end']:dir(rtl) {
281
- --xh-_layout-sheet-shift: -100%;
316
+ --xh-_layout-sheet-shift: calc(-1 * var(--xh-motion-travel));
282
317
  }
283
318
 
284
319
  /* 收起:推出画外并让出无障碍树与 Tab 序列。位移量按面板自身宽度的百分比算,
285
- 宽度换成任何一档都正好推到画外 */
320
+ 宽度换成任何一档都正好推到画外。收进去是退场,位移与淡出同走退场档,
321
+ visibility 等这一段播完才真正藏起来;减弱动效下位移归零,只剩淡出 */
286
322
  [data-scope='layout'][data-part='sider'][data-presentation='sheet'][data-collapsed] {
287
323
  translate: var(--xh-_layout-sheet-shift) 0;
324
+ opacity: var(--xh-motion-travel-opacity);
288
325
  visibility: hidden;
326
+ transition:
327
+ translate var(--xh-motion-duration-exit) var(--xh-motion-ease-exit),
328
+ opacity var(--xh-motion-duration-exit) var(--xh-motion-ease-exit),
329
+ visibility var(--xh-motion-duration-exit) var(--xh-motion-ease-exit);
289
330
  }
290
331
 
291
332
  /* 覆盖档的遮罩:与面板同一个层号,谁盖谁看文档序——遮罩写在侧栏之前。
@@ -299,10 +340,19 @@
299
340
  transition: opacity var(--xh-motion-duration-enter) var(--xh-motion-ease-enter);
300
341
  }
301
342
 
302
- /* 收起态:淡出并让开指针,底下的内容照常点得到 */
343
+ /* 收起态:淡出并让开指针,底下的内容照常点得到;淡出走退场档 */
303
344
  [data-scope='layout'][data-part='sider-backdrop'][data-collapsed] {
304
345
  opacity: 0;
305
346
  pointer-events: none;
347
+ transition: opacity var(--xh-motion-duration-exit) var(--xh-motion-ease-exit);
348
+ }
349
+
350
+ /* 侧栏与遮罩的过渡只给用户的开合:首帧与跨过断点这类环境引起的开合,连接层投影 data-instant,
351
+ 直接落位。三条各压过同一部件上带过渡的那一档(占位 / 覆盖展开、覆盖收起、遮罩收起) */
352
+ [data-scope='layout']:is([data-part='sider'], [data-part='sider-backdrop'])[data-instant],
353
+ [data-scope='layout'][data-part='sider'][data-presentation='sheet'][data-collapsed][data-instant],
354
+ [data-scope='layout'][data-part='sider-backdrop'][data-collapsed][data-instant] {
355
+ transition: none;
306
356
  }
307
357
 
308
358
  /* 纸上没有覆盖这回事:遮罩印出来就是盖住正文的一片灰 */
package/css/liquid.css ADDED
@@ -0,0 +1,106 @@
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
+ /*
7
+ * 液态材质层:与组件无关,全库共用一份。
8
+ *
9
+ * data-material="liquid" 下,连接层投影了 data-xh-liquid 的导航层部件(浮动钮、回到顶部等浮在内容之上的控制面)
10
+ * 换成液态面。这里只声明私有槽,各组件皮肤在自己的部件上把 Action Control 的桥接槽指过来;standard 档下
11
+ * 这些槽没人读,部件保持原材质。
12
+ *
13
+ * 静态形态(服务端、挂载前,或液态面还没判定下层):色调随主题极性,不透明度取可读下限,这一档对任何
14
+ * 下层都保证标签 4.5:1。组件的状态机把部件挂进 core 的液态面,由它按下层在部件上写:
15
+ * data-xh-ink dark / light:部件成为黑墨或白墨域。墨色域本身是浅色 / 深色主题边界,
16
+ * 按主题取值的液态通道随之落到那一种色调
17
+ * data-xh-liquid-clarity clear:下层均匀且色调已知,换通透档
18
+ * --xh-_liquid-light-x / -y 光源方向的单位向量,决定 1px 亮边落在哪一侧
19
+ * --xh-_liquid-deform 按住时的形变(transform 串),松手弹回后撤掉
20
+ *
21
+ * 同一宿主里的几块可以结成液态组(core 的 trackLiquidGoo):宿主带 data-xh-liquid-goo,最前面多一层
22
+ * [data-xh-liquid-goo-layer] 色块层,沿整组的外形画底色、墨色细线、亮边与投影;组件皮肤在这一档把块自己的
23
+ * 面、细线、亮边与投影撤掉,只留磨砂。
24
+ */
25
+ @layer xihan.components {
26
+ :where([data-material='liquid']) [data-xh-liquid] {
27
+ /* 光源缺省在左上方 */
28
+ --xh-_liquid-light-x: -0.71;
29
+ --xh-_liquid-light-y: -0.71;
30
+ --xh-_liquid-bg: var(--xh-material-liquid-bg);
31
+ /* 1px 亮边是两层零模糊内阴影:阴影形状朝背光方向偏移,露出的一弯落在朝光源的一侧,
32
+ 另一层反过来偏移,在背光一侧留一道弱亮边。面内不加高光 */
33
+ --xh-_liquid-rim:
34
+ inset calc(var(--xh-_liquid-light-x) * var(--xh-stroke-thin) * -1) calc(var(--xh-_liquid-light-y) * var(--xh-stroke-thin) * -1) 0 0 var(--xh-material-liquid-highlight),
35
+ inset calc(var(--xh-_liquid-light-x) * var(--xh-stroke-thin)) calc(var(--xh-_liquid-light-y) * var(--xh-stroke-thin)) 0 0 var(--xh-material-liquid-rim-far);
36
+ --xh-_liquid-shadow: var(--xh-_liquid-rim), var(--xh-material-liquid-shadow);
37
+ /* 交互阶梯往部件自己的前景色兑:浅色调变深、深色调变浅 */
38
+ --xh-_liquid-bg-hover: color-mix(in oklab, var(--xh-_liquid-bg), var(--xh-material-liquid-fg) 8%);
39
+ --xh-_liquid-bg-pressed: color-mix(in oklab, var(--xh-_liquid-bg), var(--xh-material-liquid-fg) 14%);
40
+ /* 按住时的黏滞形变:液态面在手指按住期间逐帧写进来(朝手指鼓出、沿指向拉长、另一个方向压扁),
41
+ 松手由弹簧带回后撤掉;组件皮肤拿它当 transform。没按住时为 none */
42
+ --xh-_liquid-deform: none;
43
+ }
44
+
45
+ /* 通透档:下层均匀且色调已知时由液态面写上,着色按主题取值,已随墨色域落到对应色调 */
46
+ :where([data-material='liquid']) [data-xh-liquid][data-xh-liquid-clarity='clear'] {
47
+ --xh-_liquid-bg: color-mix(in oklab, var(--xh-material-liquid-tint) calc(var(--xh-material-liquid-alpha-clear) * 100%), transparent);
48
+ }
49
+
50
+ /* 液态组的色块层:core 在宿主最前面生成(aria-hidden、不接指针),压在各块之下,每块一个色块,
51
+ 跟着块的盒子、圆角与变换走。色块层套 core 放在它旁边的粘连滤镜(行内 filter),滤镜只取色块的轮廓,
52
+ 把靠得够近的连成一片,再沿整体外形画液态面:投影、底色、墨色细线、朝光亮边与背光弱亮边,颜色都在下面
53
+ 按段取液态令牌,投影由色块自己带着、滤镜只留外形之外的那部分。色调(墨色域)与通透档由 core 按源块的
54
+ 读数同步到色块层与装滤镜的 <svg> 上。
55
+ 色块层不设层号,靠排在宿主最前压在各块之下:宿主须是定位元素,组里的块须是定位元素(或装在定位容器里)。
56
+ 层往外多铺一圈,给模糊与投影留出余地。组件可以在自己的宿主上改写投影槽,把使用者的投影槽接进来 */
57
+ [data-xh-liquid-goo-layer] {
58
+ --xh-_liquid-goo-shadow: var(--xh-material-liquid-shadow);
59
+
60
+ position: absolute;
61
+ inset: calc(-1 * var(--xh-space-8));
62
+ pointer-events: none;
63
+ }
64
+
65
+ /* 色块提供轮廓与投影,底色由滤镜铺 */
66
+ [data-xh-liquid-goo-layer] > span {
67
+ position: absolute;
68
+ box-sizing: border-box;
69
+ background-color: var(--xh-material-liquid-tint);
70
+ box-shadow: var(--xh-_liquid-goo-shadow);
71
+ }
72
+
73
+ [data-xh-liquid-goo-filter] [data-xh-goo-paint='fill'] {
74
+ flood-color: var(--xh-material-liquid-tint);
75
+ flood-opacity: var(--xh-material-liquid-alpha-floor);
76
+ }
77
+
78
+ [data-xh-liquid-goo-filter][data-xh-liquid-clarity='clear'] [data-xh-goo-paint='fill'] {
79
+ flood-opacity: var(--xh-material-liquid-alpha-clear);
80
+ }
81
+
82
+ [data-xh-liquid-goo-filter] [data-xh-goo-paint='edge'] {
83
+ flood-color: var(--xh-material-liquid-border);
84
+ }
85
+
86
+ [data-xh-liquid-goo-filter] [data-xh-goo-paint='near'] {
87
+ flood-color: var(--xh-material-liquid-highlight);
88
+ }
89
+
90
+ [data-xh-liquid-goo-filter] [data-xh-goo-paint='far'] {
91
+ flood-color: var(--xh-material-liquid-rim-far);
92
+ }
93
+
94
+ /* 强制色:系统配色下只留块自己的边,装饰色块撤掉 */
95
+ @media (forced-colors: active) {
96
+ [data-xh-liquid-goo-layer] {
97
+ display: none;
98
+ }
99
+ }
100
+
101
+ /* 从右往左排版时光源缺省在右上方 */
102
+ :where([dir='rtl']) :where([data-material='liquid']) [data-xh-liquid],
103
+ :where([data-material='liquid']) :where([dir='rtl']) [data-xh-liquid] {
104
+ --xh-_liquid-light-x: 0.71;
105
+ }
106
+ }
package/css/list.css CHANGED
@@ -91,7 +91,7 @@
91
91
  }
92
92
 
93
93
  [data-scope='list'][data-part='root'][data-hoverable] > [data-scope='list'][data-part='item'] {
94
- transition: background var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
94
+ transition: background-color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
95
95
  }
96
96
 
97
97
  /* 悬停换底色:只在能用指针的设备上生效,触摸端不留一个按不掉的悬停态 */
package/css/listbox.css CHANGED
@@ -7,6 +7,8 @@
7
7
 
8
8
  @import '../family/action-control.css';
9
9
  @import '../family/collection-item.css';
10
+ @import '../family/loading-ring.css';
11
+ @import '../family/motion.css';
10
12
 
11
13
  @layer xihan.components {
12
14
  /* 皮肤在场的标记:开发模式下按 scope 探一次,取不到就说明这份皮肤没被引入。 */
@@ -117,7 +119,7 @@
117
119
  /* 手写结构按真正的条目判断,空白文本与分组标题都不算候选。
118
120
  只排除 content 内部的 hidden 祖先;外层浮层退场时不改变本列表的几何。
119
121
  禁用条目仍是有效内容,不参与此处排除。 */
120
- [data-scope='listbox'][data-part='content']:not(:has(
122
+ [data-scope='listbox'][data-part='content']:not([data-xh-virtualized]):not(:has(
121
123
  [data-scope='listbox'][data-part='item']:not([hidden]):not([data-scope='listbox'][data-part='content'] [hidden] *)
122
124
  )),
123
125
  [data-scope='listbox'][data-part='group']:not(:has(
@@ -164,11 +166,18 @@
164
166
  display: block;
165
167
  padding-block: var(--xh-listbox-group-label-py, var(--xh-space-1));
166
168
  padding-inline: var(--xh-listbox-group-label-px, var(--xh-_listbox-item-px));
167
- color: var(--xh-listbox-group-label-fg, var(--xh-fg-subtle));
169
+ color: var(--xh-listbox-group-label-fg, var(--xh-fg-muted));
168
170
  font-size: var(--xh-listbox-group-label-font-size, var(--xh-text-caption-size));
169
171
  font-weight: var(--xh-listbox-group-label-font-weight, var(--xh-font-weight-medium));
170
172
  line-height: var(--xh-leading-none);
171
173
  user-select: none;
174
+ transition: opacity var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
175
+ }
176
+
177
+ /* 刷新:已有选项时后台在取,保留上一帧,条目与分组标题按 micro 淡下,数据到了再淡回;在途占位此时让位。
178
+ 淡的是内容不是面:content 自己是描边面,整块淡下去边也跟着淡 */
179
+ [data-scope='listbox'][data-part='content'][aria-busy='true'] [data-scope='listbox']:is([data-part='item'], [data-part='group-label']) {
180
+ opacity: var(--xh-state-disabled-opacity);
172
181
  }
173
182
 
174
183
  [data-scope='listbox'][data-part='group-label'][hidden] {
@@ -185,7 +194,7 @@
185
194
  display: block;
186
195
  padding-block: var(--xh-listbox-empty-py, var(--xh-space-3));
187
196
  padding-inline: var(--xh-listbox-empty-px, var(--xh-_listbox-item-px));
188
- color: var(--xh-listbox-empty-fg, var(--xh-fg-subtle));
197
+ color: var(--xh-listbox-empty-fg, var(--xh-fg-muted));
189
198
  font-size: var(--xh-listbox-empty-font-size, var(--xh-_listbox-font-size));
190
199
  line-height: var(--xh-leading-none);
191
200
  text-align: center;
@@ -202,10 +211,13 @@
202
211
 
203
212
  /* 在途占位:与空态占位同一个位置、同一副观感,两者不同屏 */
204
213
  [data-scope='listbox'][data-part='loading'] {
205
- display: block;
214
+ display: flex;
215
+ align-items: center;
216
+ justify-content: center;
217
+ gap: var(--xh-_listbox-gap);
206
218
  padding-block: var(--xh-listbox-loading-py, var(--xh-space-3));
207
219
  padding-inline: var(--xh-listbox-loading-px, var(--xh-_listbox-item-px));
208
- color: var(--xh-listbox-loading-fg, var(--xh-fg-subtle));
220
+ color: var(--xh-listbox-loading-fg, var(--xh-fg-muted));
209
221
  font-size: var(--xh-listbox-loading-font-size, var(--xh-_listbox-font-size));
210
222
  line-height: var(--xh-leading-none);
211
223
  text-align: center;
@@ -215,6 +227,15 @@
215
227
  display: none;
216
228
  }
217
229
 
230
+ /* 首次加载:文案前一枚加载环,由加载环配方画(连接层投影 data-xh-loading-ring 与 data-loading),
231
+ 直径取所在尺寸档的图标档 */
232
+ [data-scope='listbox'][data-part='loading']::before {
233
+ content: '';
234
+ flex: none;
235
+ inline-size: var(--xh-icon-size, var(--xh-glyph-size-md));
236
+ block-size: var(--xh-icon-size, var(--xh-glyph-size-md));
237
+ }
238
+
218
239
  /* 取下一页的按钮:铺满一行摆在列表尾——铺满一行的独立动作条目,接 Action Control 的 row 档、
219
240
  ghost 形态:宽度由容器给、高度随内容、按下只换面不缩放;盒几何、悬停 / 按下 / 禁用面、
220
241
  焦点环与粗指针热区全由家族给,白底承载 hover 100 → pressed 200。
@@ -250,7 +271,7 @@
250
271
  --xh-collection-inline-padding: var(--xh-listbox-item-px, var(--xh-_listbox-item-px));
251
272
  --xh-collection-gap: var(--xh-listbox-item-gap, var(--xh-_listbox-gap));
252
273
  --xh-collection-font-size: var(--xh-listbox-item-font-size, var(--xh-_listbox-font-size));
253
- --xh-collection-radius: var(--xh-listbox-item-radius, var(--xh-shape-control));
274
+ --xh-collection-radius: var(--xh-listbox-item-radius, var(--xh-shape-inset));
254
275
  /* 作者的图标尺槽经桥接槽下发到条目,家族缺省按 data-xh-collection-size 取档 */
255
276
  --xh-collection-glyph-size: var(--xh-listbox-icon-size, var(--xh-_collection-glyph-size));
256
277
  --xh-collection-fg-rest: var(--xh-listbox-item-fg, var(--xh-fg-default));
@@ -347,4 +368,23 @@
347
368
  print-color-adjust: exact;
348
369
  }
349
370
  }
371
+
372
+ @media (forced-colors: active) {
373
+ /* 兜底字形是 mask 挖出来的一块底色,这一档里底色被系统换成 Canvas,字形跟着消失。
374
+ 字形退出强制换色、直接写系统色(不能取 currentColor:退出强制换色的伪元素读到的是被系统换掉之前的作者色):
375
+ 条目面上取 CanvasText;按下与没被高亮时的悬停,家族把条目涂成 Highlight,字形换 HighlightText;
376
+ 禁用取 GrayText */
377
+ [data-scope='listbox'][data-part='item-indicator']:empty::before {
378
+ forced-color-adjust: none;
379
+ background-color: CanvasText;
380
+ }
381
+
382
+ [data-scope='listbox'][data-part='item']:not([aria-disabled='true'], [data-disabled], [aria-busy='true'], [data-error]):is(:active, [data-pressed], :hover:not(:focus-visible, [data-highlighted])) [data-scope='listbox'][data-part='item-indicator']:empty::before {
383
+ background-color: HighlightText;
384
+ }
385
+
386
+ [data-scope='listbox'][data-part='item']:is([aria-disabled='true'], [data-disabled]) [data-scope='listbox'][data-part='item-indicator']:empty::before {
387
+ background-color: GrayText;
388
+ }
389
+ }
350
390
  }
@@ -40,11 +40,12 @@
40
40
  display: none;
41
41
  }
42
42
 
43
- /* 冲到 100 之后进入淡出:机器会在淡出窗口走完之前一直停在 finishing,这段过渡才有时间跑完。
44
- 窗口(machine 的 LOADING_BAR_FADE_DURATION,200ms)比过渡本身(motion-duration-exit,120ms)
45
- 宽出一截是有意的余量;两者不是同一个数,改窗口时只要保证它仍不短于过渡 */
43
+ /* 走满之后才淡出:complete 期间根节点保持不透明,进度段冲到头的平移播完,机器才进 finishing。
44
+ 满格先停一拍(延迟一个 micro)再淡:用户要看清「走完了」,一到头就开始淡读起来像中途断掉。
45
+ 机器等的是浏览器实际起播的这段过渡(含延迟),不按毫秒猜 */
46
46
  [data-scope='loading-bar'][data-part='root'][data-state='finishing'] {
47
47
  opacity: 0;
48
+ transition-delay: var(--xh-motion-duration-micro);
48
49
  }
49
50
 
50
51
  /* 背景槽。默认透明:顶部加载条通常直接压在页面顶边上,一条贯穿整屏的浅色底
@@ -57,17 +58,24 @@
57
58
  }
58
59
 
59
60
  /*
60
- * 进度段。宽度那条轴(inline-size)由连接层写进内联样式,这里绝不重复写。
61
- * 背景色不走内联:要别的颜色改 --xh-loading-bar-range 这个槽,或者换语气。
61
+ * 进度段。铺满轨道,按连接层交出的比例往行首平移、由轨道裁掉:推进只走合成,
62
+ * 不重排也不重绘;末端的亮边跟着它一起走。背景色不走内联:要别的颜色改 --xh-loading-bar-range 这个槽,或者换语气。
62
63
  *
63
64
  * 语气取装饰档 --xh-_tone-soft:那一档压在画布上够 3:1,细带才看得出来。
64
65
  * 槽位由 root 上的 data-tone 声明,继承下来。
65
66
  * 没写 data-tone 时该槽没被声明,退回原来的品牌色。
66
67
  */
67
68
  [data-scope='loading-bar'][data-part='range'] {
69
+ inline-size: 100%;
68
70
  block-size: 100%;
69
71
  background: var(--xh-loading-bar-range, var(--xh-_tone-soft, var(--xh-bg-brand)));
70
- transition: inline-size var(--xh-loading-bar-speed, var(--xh-motion-duration-enter)) var(--xh-motion-ease-continuous);
72
+ translate: calc((var(--xh-_loading-bar-value, 0) - 1) * 100%) 0;
73
+ transition: translate var(--xh-loading-bar-speed, var(--xh-motion-duration-move)) var(--xh-motion-ease-continuous);
74
+ }
75
+
76
+ /* translate 只认物理方向:RTL 下行首在右边,平移方向翻转 */
77
+ [data-scope='loading-bar'][data-part='range']:dir(rtl) {
78
+ translate: calc((1 - var(--xh-_loading-bar-value, 0)) * 100%) 0;
71
79
  }
72
80
 
73
81
  /* 渲染了亮边才把进度段变成定位面:亮边贴在它的末端 */
@@ -94,7 +102,7 @@
94
102
  );
95
103
  }
96
104
 
97
- /* 条子还没起步时亮边一起收着:0% 宽的进度段末端不该先亮出一道边 */
105
+ /* 条子还没起步时亮边一起收着:进度为 0 的末端不该先亮出一道边 */
98
106
  [data-scope='loading-bar'][data-part='peg'][data-state='idle'] {
99
107
  opacity: 0;
100
108
  }
@@ -103,19 +111,25 @@
103
111
  display: none;
104
112
  }
105
113
 
106
- /* 减少动效:宽度与淡出都立刻到位。进度本身照常推进,只是不再有过渡的过程——
107
- 一条按 200ms 一拍持续变宽的带子正是最容易引发不适的那种动效。
114
+ /* 减少动效:作者写进两个时长槽的值不作数,退回令牌——进度段的平移随几何时长立刻到位,
115
+ 收尾的淡出保留减弱档的那一小段。一条按 200ms 一拍持续推进的带子正是最容易引发不适的那种动效。
108
116
  两条规则是同一件事的两个触发条件:系统偏好与作者打的 data-motion */
109
117
  @media (prefers-reduced-motion: reduce) {
110
- [data-scope='loading-bar'][data-part='root'],
118
+ [data-scope='loading-bar'][data-part='root'] {
119
+ transition-duration: var(--xh-motion-duration-exit);
120
+ }
121
+
111
122
  [data-scope='loading-bar'][data-part='range'] {
112
- transition: none;
123
+ transition-duration: var(--xh-motion-duration-move);
113
124
  }
114
125
  }
115
126
 
116
- :where([data-motion='reduce']) [data-scope='loading-bar'][data-part='root'],
127
+ :where([data-motion='reduce']) [data-scope='loading-bar'][data-part='root'] {
128
+ transition-duration: var(--xh-motion-duration-exit);
129
+ }
130
+
117
131
  :where([data-motion='reduce']) [data-scope='loading-bar'][data-part='range'] {
118
- transition: none;
132
+ transition-duration: var(--xh-motion-duration-move);
119
133
  }
120
134
 
121
135
  @media print {