@xihan-ui/styles 2.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/CHANGELOG.md +1328 -0
  2. package/README.md +6 -1
  3. package/component-tokens.d.ts +505 -111
  4. package/components.tokens.json +34750 -26291
  5. package/css/accordion.css +39 -13
  6. package/css/alert.css +61 -3
  7. package/css/anchor.css +49 -11
  8. package/css/approval.css +124 -84
  9. package/css/avatar-group.css +1 -1
  10. package/css/avatar.css +13 -5
  11. package/css/back-top.css +63 -44
  12. package/css/badge.css +63 -13
  13. package/css/bar-code.css +1 -1
  14. package/css/breadcrumb.css +63 -4
  15. package/css/button-group.css +5 -5
  16. package/css/button.css +69 -21
  17. package/css/calendar-picker.css +27 -8
  18. package/css/calendar-range-picker.css +48 -11
  19. package/css/card.css +116 -2
  20. package/css/carousel.css +153 -48
  21. package/css/cartesian-chart.css +830 -0
  22. package/css/cascader.css +184 -70
  23. package/css/checkbox-group.css +69 -99
  24. package/css/checkbox.css +21 -54
  25. package/css/citation.css +470 -0
  26. package/css/clipboard.css +23 -66
  27. package/css/code-view.css +103 -8
  28. package/css/collapsible.css +37 -11
  29. package/css/color-field.css +18 -0
  30. package/css/color-picker.css +57 -62
  31. package/css/color-slider.css +26 -19
  32. package/css/color-swatch-picker.css +8 -6
  33. package/css/combobox.css +82 -37
  34. package/css/command.css +48 -48
  35. package/css/context-menu.css +43 -28
  36. package/css/date-field.css +23 -0
  37. package/css/date-picker.css +57 -24
  38. package/css/date-range-picker.css +301 -14
  39. package/css/description.css +17 -1
  40. package/css/descriptions.css +43 -1
  41. package/css/dialog.css +99 -43
  42. package/css/diff-view.css +101 -16
  43. package/css/download-trigger.css +46 -80
  44. package/css/drawer.css +168 -133
  45. package/css/editable.css +19 -0
  46. package/css/empty-state.css +8 -7
  47. package/css/field-array.css +36 -0
  48. package/css/field.css +17 -19
  49. package/css/fieldset.css +1 -1
  50. package/css/file-upload.css +126 -136
  51. package/css/flex.css +324 -8
  52. package/css/float-button.css +168 -41
  53. package/css/floating-panel.css +59 -27
  54. package/css/form.css +26 -20
  55. package/css/funnel-chart.css +106 -0
  56. package/css/graph-chart.css +205 -0
  57. package/css/grid-list.css +235 -0
  58. package/css/grid.css +252 -1
  59. package/css/heatmap.css +119 -36
  60. package/css/hierarchy-chart.css +298 -0
  61. package/css/hover-card.css +33 -18
  62. package/css/icon.css +100 -0
  63. package/css/image-cropper.css +34 -8
  64. package/css/image-viewer.css +169 -26
  65. package/css/image.css +23 -4
  66. package/css/infinite-scroll.css +2 -2
  67. package/css/input-group.css +13 -15
  68. package/css/json-viewer.css +55 -7
  69. package/css/kbd.css +1 -1
  70. package/css/layout.css +71 -21
  71. package/css/liquid.css +106 -0
  72. package/css/list.css +2 -2
  73. package/css/listbox.css +72 -23
  74. package/css/loading-bar.css +27 -13
  75. package/css/log.css +131 -35
  76. package/css/markdown-stream.css +1 -1
  77. package/css/marquee.css +163 -7
  78. package/css/matrix-code.css +1 -1
  79. package/css/mention.css +35 -21
  80. package/css/menu.css +40 -28
  81. package/css/menubar.css +42 -28
  82. package/css/message-feed.css +174 -53
  83. package/css/navigation-menu.css +177 -18
  84. package/css/notification.css +431 -110
  85. package/css/number-field.css +22 -0
  86. package/css/page-header.css +4 -2
  87. package/css/pagination.css +129 -29
  88. package/css/password-input.css +40 -8
  89. package/css/pie-chart.css +247 -0
  90. package/css/popconfirm.css +50 -58
  91. package/css/popover.css +55 -21
  92. package/css/progress.css +308 -10
  93. package/css/prompt-input.css +37 -1
  94. package/css/question-flow.css +104 -64
  95. package/css/radar-chart.css +233 -0
  96. package/css/radio-group.css +372 -14
  97. package/css/rating.css +25 -27
  98. package/css/reasoning.css +62 -55
  99. package/css/reset.css +1 -1
  100. package/css/resizable.css +6 -1
  101. package/css/sankey-chart.css +174 -0
  102. package/css/scroll-area.css +32 -0
  103. package/css/scrollbar.css +1 -1
  104. package/css/select.css +76 -59
  105. package/css/side-nav.css +177 -30
  106. package/css/signature-pad.css +23 -8
  107. package/css/skeleton.css +13 -14
  108. package/css/slider.css +39 -17
  109. package/css/sortable.css +38 -8
  110. package/css/sparkline.css +164 -0
  111. package/css/spinner.css +50 -22
  112. package/css/splitter.css +8 -2
  113. package/css/statistic.css +10 -0
  114. package/css/steps.css +257 -38
  115. package/css/switch.css +30 -55
  116. package/css/table.css +168 -128
  117. package/css/tabs.css +243 -31
  118. package/css/tag-group.css +65 -37
  119. package/css/tag.css +55 -14
  120. package/css/tags-input.css +40 -3
  121. package/css/text-field.css +19 -0
  122. package/css/time-field.css +20 -1
  123. package/css/time-picker.css +44 -17
  124. package/css/time-range-picker.css +45 -18
  125. package/css/timeline.css +1 -1
  126. package/css/toggle-group.css +8 -8
  127. package/css/toggle.css +6 -5
  128. package/css/tool-call.css +64 -56
  129. package/css/toolbar.css +105 -7
  130. package/css/tooltip.css +18 -7
  131. package/css/tour.css +49 -21
  132. package/css/transfer.css +104 -81
  133. package/css/tree-select.css +235 -58
  134. package/css/tree.css +240 -147
  135. package/css/truncate.css +55 -12
  136. package/css/typography.css +93 -0
  137. package/css/virtualizer.css +57 -1
  138. package/css/watermark.css +23 -0
  139. package/family/action-control.css +14 -5
  140. package/family/chart.css +480 -0
  141. package/family/check-mark.css +94 -0
  142. package/family/choice-card.css +97 -0
  143. package/family/collection-item.css +70 -27
  144. package/family/field-chrome.css +1 -12
  145. package/family/loading-ring.css +95 -0
  146. package/family/material.css +72 -0
  147. package/family/menu-choice.css +56 -0
  148. package/family/motion.css +208 -11
  149. package/family/tag-list.css +59 -0
  150. package/index.css +23363 -14091
  151. package/index.unlayered.css +23198 -13922
  152. package/package.json +20 -8
  153. package/css/gradient-text.css +0 -91
  154. package/css/icon-wrapper.css +0 -81
  155. package/css/segmented.css +0 -280
  156. package/css/toast.css +0 -499
package/css/select.css CHANGED
@@ -7,7 +7,10 @@
7
7
  @import '../family/field-chrome.css';
8
8
  @import '../family/action-control.css';
9
9
  @import '../family/collection-item.css';
10
+ @import '../family/loading-ring.css';
10
11
  @import '../family/motion.css';
12
+ @import '../family/material.css';
13
+ @import '../family/tag-list.css';
11
14
 
12
15
  @layer xihan.components {
13
16
  /* 皮肤在场的标记:开发模式下按 scope 探一次,取不到就说明这份皮肤没被引入。 */
@@ -23,8 +26,8 @@
23
26
  /* 落定那一侧的可用宽度,同样由定位引擎经内联自定义属性写进来,行内轴据此夹取;
24
27
  引擎没算(尚未落位 / 无引擎 / 贴边归零)时退到视口宽 */
25
28
  --xh-_select-available-w: 100vw;
26
- /* 触发器实测宽度,由定位引擎经内联自定义属性写进来,内容层拿它当最小宽的下界;
27
- 引擎没算(尚未落位 / 无引擎)时为 0,max 取静态档,等于不抬宽 */
29
+ /* 字段盒实测宽度,由定位引擎经内联自定义属性写进来,内容层与它等宽;
30
+ 引擎没算(尚未落位 / 无引擎)时为 0,面板取下界 */
28
31
  --xh-_select-anchor-w: 0px;
29
32
  /* WC 嵌套层仍在父层 DOM 内;先截断父 positioner 的方向,再按本层 placement 开一侧。 */
30
33
  --xh-_overlay-enter-up: 0;
@@ -80,7 +83,7 @@
80
83
  [data-scope='select'][data-part='root'] {
81
84
  /* 作者塞进指示符槽的图标跟着控件的尺寸档走;它自己写了 size 时仍以作者的为准。
82
85
  浮层条目里自绘的状态字形(勾选标记所在的标记盒与盒里的勾)不读这支:它们是指示符,走
83
- --xh-control-indicator-* 一族——标记盒在自己身上把 --xh-icon-size 改接到盒的尺(§6.5) */
86
+ --xh-control-indicator-* 一族——标记盒在自己身上把 --xh-icon-size 改接到盒的尺 */
84
87
  --xh-icon-size: var(--xh-select-icon-size, var(--xh-glyph-size-md));
85
88
 
86
89
  display: inline-flex;
@@ -212,22 +215,11 @@
212
215
  display: none;
213
216
  }
214
217
 
215
- /* 标签行:触发器里收着可见标签与 +N 那一枚的一行,行里每一枚都是 tag(data-scope="tag"),
216
- 样子归 tag.css,这里只管行怎么排。它是弹性子项,只缩不涨:
217
- 得允许自己被压缩并把溢出裁掉,否则标签会冲出盒的右缘、把展开箭头挤到盒外;
218
+ /* 标签行:触发器里收着可见标签与 +N 那一枚的一行,行里每一枚都是 tag(data-scope="tag"),样子归 tag.css;
219
+ 行怎么排、标签怎么进出归标签行家族配方(family/tag-list.css),这里只把使用者的间距槽接进去。
218
220
  展开箭头靠触发器的 space-between 留在末端,行本身不必撑开 */
219
221
  [data-scope='select'][data-part='tag-list'] {
220
- display: inline-flex;
221
- flex: 0 1 auto;
222
- align-items: center;
223
- gap: var(--xh-select-tag-list-gap, var(--xh-space-1));
224
- min-inline-size: 0;
225
- overflow: hidden;
226
- }
227
-
228
- /* 无选中时连接层给 hidden,占位文字回来 */
229
- [data-scope='select'][data-part='tag-list'][hidden] {
230
- display: none;
222
+ --xh-tag-list-gap: var(--xh-select-tag-list-gap, var(--xh-space-1));
231
223
  }
232
224
 
233
225
  /* 标签行露面时 value-text 让位:两者是触发器里的同一格,占位文字与标签不同屏。
@@ -236,18 +228,6 @@
236
228
  display: none;
237
229
  }
238
230
 
239
- /* 行里的标签:行装不下时各自缩短,截断落在 tag 自己的 label 上;弹性子项默认最小尺寸是内容尺寸,
240
- 不归零则长标签把行撑破、省略号永远轮不到。+N 那一枚(带 data-count)不缩,始终完整可见。
241
- 标签里的删除钮是 tag 的 close-trigger,样子全归 tag.css,这里不画 */
242
- [data-scope='select'][data-part='tag-list'] > [data-scope='tag'][data-part='root'] {
243
- flex: 0 1 auto;
244
- min-inline-size: 0;
245
- }
246
-
247
- [data-scope='select'][data-part='tag-list'] > [data-scope='tag'][data-part='root'][data-count] {
248
- flex: none;
249
- }
250
-
251
231
  /* 环画在盒上,按钮自己不再画:两圈套着的环读起来像两个控件 */
252
232
  [data-scope='select'][data-part='trigger']:focus-visible {
253
233
  outline: none;
@@ -289,7 +269,7 @@
289
269
  inline-size: var(--xh-select-action-size, var(--xh-control-action-size));
290
270
  block-size: var(--xh-select-action-size, var(--xh-control-action-size));
291
271
  color: var(--xh-select-indicator-fg, var(--xh-fg-muted));
292
- transition: rotate var(--xh-motion-duration-micro) var(--xh-motion-ease-enter-strong);
272
+ transition: rotate var(--xh-motion-duration-nudge) var(--xh-motion-ease-enter-strong);
293
273
  }
294
274
 
295
275
  /* 没写内容就由皮肤画箭头;作者往部件里塞了自己的图形,这条规则即不命中 */
@@ -311,44 +291,34 @@
311
291
  /* 浮层外壳:描边、底色、阴影与整体尺寸归它。它自己不滚——滚的是里面的 list,
312
292
  底部操作区因此留在视口里,不随条目滚走 */
313
293
  [data-scope='select'][data-part='content'] {
294
+ /* 材质由家族配方画(family/material.css 的 frosted 面:描边、底、1px 顶光、前景、投影与背景滤镜),
295
+ 这里只把使用者槽接进来 */
296
+ --xh-frosted-bg: var(--xh-select-content-bg, var(--xh-_material-bg));
297
+ --xh-frosted-border: var(--xh-select-content-border, var(--xh-_material-border));
298
+ --xh-frosted-fg: var(--xh-select-content-fg, var(--xh-_material-fg));
299
+ --xh-frosted-shadow: var(--xh-select-content-shadow, var(--xh-_material-shadow));
300
+ --xh-frosted-backdrop: var(--xh-select-content-backdrop, var(--xh-_material-backdrop));
301
+ --xh-frosted-highlight: var(--xh-select-content-highlight, var(--xh-_material-highlight));
302
+
314
303
  position: relative;
315
304
  display: flex;
316
305
  flex-direction: column;
317
- /* 下界跟着触发器走,面板不窄于它;可用宽度的夹取写在这一侧:
318
- min-inline-size 恒压过 max-inline-size,锚点比可用区还宽时只夹 max 等于没夹 */
319
- min-inline-size: min(
306
+ /* 列表型浮层与字段盒等宽:长选项在条目里截断,面板不随最长的一条变宽。锚点比下界还窄时取下界,
307
+ 比可用区还宽时收成可用宽度;要封顶由作者另写上界槽 */
308
+ inline-size: min(
320
309
  max(var(--xh-select-content-min-w, var(--xh-overlay-menu-min-w)), var(--xh-_select-anchor-w)),
321
310
  var(--xh-_select-available-w)
322
311
  );
323
- /* 静态档与落位后的可用宽度取小:窄视口下面板伸出去,最右侧的内容就点不到了 */
324
- max-inline-size: min(var(--xh-select-content-max-w, var(--xh-overlay-max-w)), var(--xh-_select-available-w));
312
+ max-inline-size: var(--xh-select-content-max-w, none);
325
313
  max-block-size: min(var(--xh-select-content-max-h, var(--xh-overlay-menu-max-h)), var(--xh-_select-available-h));
326
314
  padding-block: var(--xh-select-content-py, var(--xh-space-1));
327
315
  padding-inline: var(--xh-select-content-px, var(--xh-space-1));
328
- border: var(--xh-stroke-thin) solid var(--xh-select-content-border, var(--xh-material-frosted-border));
329
316
  border-radius: var(--xh-select-content-radius, var(--xh-shape-overlay));
330
- background: var(--xh-select-content-bg, var(--xh-material-frosted-bg));
331
- color: var(--xh-select-content-fg, var(--xh-material-frosted-fg));
332
- box-shadow: var(--xh-select-content-shadow, var(--xh-material-frosted-shadow));
333
- -webkit-backdrop-filter: var(--xh-select-content-backdrop, var(--xh-material-frosted-backdrop));
334
- backdrop-filter: var(--xh-select-content-backdrop, var(--xh-material-frosted-backdrop));
335
317
  /* 条目的悬停底色是方角,滚到两端时会探出外壳的圆角 */
336
318
  overflow: hidden;
337
319
  pointer-events: auto;
338
320
  }
339
321
 
340
- /* 顶光单独叠在真实四边 border 内侧;强制色归零后轮廓仍在。 */
341
- [data-scope='select'][data-part='content']::before {
342
- content: '';
343
- position: absolute;
344
- inset-block-start: 0;
345
- inset-inline: 0;
346
- block-size: var(--xh-stroke-thin);
347
- border-radius: inherit;
348
- background: var(--xh-select-content-highlight, var(--xh-material-frosted-highlight));
349
- pointer-events: none;
350
- }
351
-
352
322
  /* 列表框本体:只管滚动,留白归外壳。
353
323
  min-block-size: 0 是 flex 子项能真正滚动的前提——不给的话它按内容撑高,
354
324
  外壳的 max-block-size 就只会把溢出的部分裁掉而不是交给它滚 */
@@ -361,6 +331,12 @@
361
331
  overflow-y: auto;
362
332
  /* 浮层滚动面:滚到头不把页面一起带走 */
363
333
  overscroll-behavior: contain;
334
+ transition: opacity var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
335
+ }
336
+
337
+ /* 刷新:已有候选时后台在取,保留上一帧,列表按 micro 淡下,数据到了再淡回;在途占位此时让位 */
338
+ [data-scope='select'][data-part='list'][aria-busy='true'] {
339
+ opacity: var(--xh-state-disabled-opacity);
364
340
  }
365
341
 
366
342
  /* 底部操作区:贴在列表下沿,用一道分隔线与条目断开。
@@ -392,10 +368,9 @@
392
368
  outline: none;
393
369
  }
394
370
 
395
- [data-scope='select'][data-part='content'][data-state='open'] {
371
+ /* 挂载时就开着的那一段(data-instant)属于首帧,直接呈现 */
372
+ [data-scope='select'][data-part='content'][data-state='open']:not([data-instant]) {
396
373
  animation: xh-overlay-slide-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter-strong);
397
- /* 只在开着的这一档挂:合成层随 data-state 一起收走,收起后不再占着 */
398
- will-change: opacity, translate;
399
374
  }
400
375
 
401
376
  /* 退场:收起那一帧 hidden 由退场闸门押后,这里才播得出来。forwards 把末态钉住,
@@ -424,7 +399,10 @@
424
399
 
425
400
  /* 在途占位:与空态占位同一个位置、同一副观感,两者不同屏 */
426
401
  [data-scope='select'][data-part='loading'] {
427
- display: block;
402
+ display: flex;
403
+ align-items: center;
404
+ justify-content: center;
405
+ gap: var(--xh-_select-gap);
428
406
  flex: none;
429
407
  padding-block: var(--xh-select-loading-py, var(--xh-space-3));
430
408
  padding-inline: var(--xh-select-loading-px, var(--xh-_select-item-px));
@@ -438,6 +416,15 @@
438
416
  display: none;
439
417
  }
440
418
 
419
+ /* 首次加载:文案前一枚加载环,由加载环配方画(连接层投影 data-xh-loading-ring 与 data-loading),
420
+ 直径取所在尺寸档的图标档 */
421
+ [data-scope='select'][data-part='loading']::before {
422
+ content: '';
423
+ flex: none;
424
+ inline-size: var(--xh-icon-size, var(--xh-glyph-size-md));
425
+ block-size: var(--xh-icon-size, var(--xh-glyph-size-md));
426
+ }
427
+
441
428
  [data-scope='select'][data-part='group'] {
442
429
  display: flex;
443
430
  flex-direction: column;
@@ -478,7 +465,7 @@
478
465
  --xh-collection-inline-padding: var(--xh-select-item-px, var(--xh-_select-item-px));
479
466
  --xh-collection-gap: var(--xh-select-item-gap, var(--xh-_select-gap));
480
467
  --xh-collection-font-size: var(--xh-select-item-font-size, var(--xh-_select-font-size));
481
- --xh-collection-radius: var(--xh-select-item-radius, var(--xh-shape-control));
468
+ --xh-collection-radius: var(--xh-select-item-radius, var(--xh-shape-inset));
482
469
  --xh-collection-fg-rest: var(--xh-select-item-fg, var(--xh-material-frosted-fg));
483
470
  --xh-collection-fg-hover: var(--xh-select-item-fg, var(--xh-material-frosted-fg));
484
471
  --xh-collection-fg-keyboard-highlight: var(--xh-select-item-fg, var(--xh-material-frosted-fg));
@@ -530,7 +517,7 @@
530
517
  }
531
518
 
532
519
  /* 勾选标记只在选中项上出现;未选中时占位不塌,切换选中不引起行宽抖动。
533
- 盒与盒里的勾是指示符(§6.5):盒按指示符档取尺,并在自己身上把 --xh-icon-size 改接到盒的尺,皮肤画的
520
+ 盒与盒里的勾是指示符:盒按指示符档取尺,并在自己身上把 --xh-icon-size 改接到盒的尺,皮肤画的
534
521
  兜底勾与作者塞进盒里的 XhIcon 读同一把尺、与盒同边长(勾选标记不是勾选格,不取 × 0.75,与 Listbox / Menu 族
535
522
  同口径);不读家族按档下发到行的 --xh-icon-size——那支只管作者放进条目里的图标,按图标档取的 20px 比
536
523
  16 / 14 的指示符档大一圈 */
@@ -592,4 +579,34 @@
592
579
  display: none;
593
580
  }
594
581
  }
582
+
583
+ @media (forced-colors: active) {
584
+ /* 兜底字形是 mask 挖出来的一块底色,这一档里底色被系统换成 Canvas,字形跟着消失。
585
+ 字形退出强制换色、直接写系统色(不能取 currentColor:退出强制换色的伪元素读到的是被系统换掉之前的作者色):
586
+ 按钮面上取 ButtonText,悬停与按下时家族把按钮涂成 Highlight、字形换 HighlightText;
587
+ 条目面上取 CanvasText;按下与没被高亮时的悬停,家族把条目涂成 Highlight,字形换 HighlightText;
588
+ 字段面上取 CanvasText;
589
+ 禁用取 GrayText */
590
+ [data-scope='select'][data-part='indicator']:empty::before,
591
+ [data-scope='select'][data-part='item-indicator']:empty::before {
592
+ forced-color-adjust: none;
593
+ background-color: CanvasText;
594
+ }
595
+
596
+ [data-scope='select'][data-part='clear-trigger']:empty::before {
597
+ forced-color-adjust: none;
598
+ background-color: ButtonText;
599
+ }
600
+
601
+ [data-scope='select'][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='select'][data-part='item-indicator']:empty::before,
602
+ [data-scope='select'][data-part='clear-trigger']:not([data-disabled]):not([data-loading]):is(:hover, :active, [data-pressed]):empty::before {
603
+ background-color: HighlightText;
604
+ }
605
+
606
+ [data-scope='select'][data-part='control'][data-disabled] [data-scope='select'][data-part='indicator']:empty::before,
607
+ [data-scope='select'][data-part='item']:is([aria-disabled='true'], [data-disabled]) [data-scope='select'][data-part='item-indicator']:empty::before,
608
+ [data-scope='select'][data-part='clear-trigger'][data-disabled]:empty::before {
609
+ background-color: GrayText;
610
+ }
611
+ }
595
612
  }
package/css/side-nav.css CHANGED
@@ -5,10 +5,16 @@
5
5
 
6
6
  /* 定义 side nav 样式。 */
7
7
 
8
- /* 共享关键帧由 family/motion.css 提供,行走 Collection Item 家族;本皮肤 @import 它们,单独引入仍成立。 */
8
+ /* 共享关键帧由 family/motion.css 提供,行走 Collection Item 家族,搜索框的重置与占位前景走字段家族;
9
+ 本皮肤 @import 它们,单独引入仍成立。 */
10
+ @import '../family/field-chrome.css';
9
11
  @import '../family/collection-item.css';
10
12
  @import '../family/motion.css';
11
13
 
14
+ /* 图标栏的名称提示就是库内的 Tooltip(data-scope="tooltip" 的 positioner 与 content),
15
+ 反白面、进退场与各环境档都由它的皮肤给;本皮肤 @import 它,单独引入仍成立 */
16
+ @import './tooltip.css';
17
+
12
18
  @layer xihan.components {
13
19
  /* 皮肤在场的标记:开发模式下按 scope 探一次,取不到就说明这份皮肤没被引入。 */
14
20
  [data-scope='side-nav'] {
@@ -19,7 +25,7 @@
19
25
  不是根的后代,继承不过去。面板里的行与栏内那些消费的是同一批槽 */
20
26
  [data-scope='side-nav']:is([data-part='root'], [data-part='positioner']) {
21
27
  /* 行外的字形按档取尺,缺省 md,随 data-size 换档并同样声明在定位层上(弹出面板里的行也拿得到);
22
- 行内的由家族随 data-xh-collection-size 下发(§6.5)。这支只管作者放进行里的图标:
28
+ 行内的由家族随 data-xh-collection-size 下发。这支只管作者放进行里的图标:
23
29
  侧栏自绘的状态字形(分支行尾的展开方向 chevron)是指示符,branch-indicator 在自己身上
24
30
  把 --xh-icon-size 改接到 --xh-control-indicator-* 一族的盒尺 */
25
31
  --xh-icon-size: var(--xh-side-nav-icon-size, var(--xh-glyph-size-md));
@@ -49,21 +55,83 @@
49
55
  --xh-_side-nav-row-font-size: var(--xh-control-font-lg);
50
56
  }
51
57
 
58
+ /* 展开与折叠两档宽度缺省取侧栏令牌,与 Layout 的侧栏同源 */
52
59
  [data-scope='side-nav'][data-part='root'] {
53
- inline-size: var(--xh-side-nav-w, 240px);
60
+ inline-size: var(--xh-side-nav-w, var(--xh-sider-w));
54
61
  display: flex;
55
62
  flex-direction: column;
56
63
  gap: var(--xh-side-nav-gap, var(--xh-space-1));
57
64
  padding: var(--xh-side-nav-p, var(--xh-space-2));
58
65
  color: var(--xh-side-nav-fg, var(--xh-fg-default));
66
+ /* 占位式侧栏折叠是屏内的尺寸变化:与 Layout 侧栏同一段 move、同一条曲线,放在侧栏里时两者一起收放 */
67
+ transition: inline-size var(--xh-motion-duration-move) var(--xh-motion-ease-continuous);
59
68
  }
60
69
 
61
- /* 折叠成图标栏:整栏收窄,文字随行宽被裁掉 */
70
+ /* 折叠成图标栏:整栏收窄;行文字先淡出,收窄落定之后才裁掉(见下) */
62
71
  [data-scope='side-nav'][data-part='root'][data-collapsed] {
63
- inline-size: var(--xh-side-nav-collapsed-w, 56px);
72
+ inline-size: var(--xh-side-nav-collapsed-w, var(--xh-sider-collapsed-w));
73
+ }
74
+
75
+ /* 搜索框:面板内嵌搜索的写法——不画字段外壳,通栏一行,高与字号随尺寸档,只画一道面内分隔的下划线;
76
+ 重置与占位前景由字段家族按 data-xh-field-input 给,行内内衬与行同一支槽 */
77
+ [data-scope='side-nav'][data-part='input'] {
78
+ --xh-field-placeholder-fg: var(--xh-side-nav-placeholder-fg, var(--xh-fg-subtle));
79
+
80
+ flex: none;
81
+ min-inline-size: 0;
82
+ block-size: var(--xh-side-nav-input-h, var(--xh-_side-nav-row-h));
83
+ padding-block: 0;
84
+ padding-inline: var(--xh-side-nav-input-px, var(--xh-_side-nav-row-px));
85
+ border: none;
86
+ border-block-end: var(--xh-stroke-thin) solid var(--xh-side-nav-search-divider, var(--xh-material-solid-separator));
87
+ background: none;
88
+ color: inherit;
89
+ font-family: inherit;
90
+ font-size: var(--xh-side-nav-input-font-size, var(--xh-_side-nav-row-font-size));
91
+ line-height: var(--xh-leading-normal);
92
+ transition: opacity var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
93
+ }
94
+
95
+ /* 聚焦不画环:插入符就是焦点指示。框没有外壳,全局那圈键盘焦点环只会压在通栏一行上 */
96
+ [data-scope='side-nav'][data-part='input']:focus-visible {
97
+ outline: none;
98
+ }
99
+
100
+ /* 自动填充:引擎给填过的框强加一套系统色,盖得过 background 与 color。
101
+ 底改用 inset 阴影铺回来,字改用 -webkit-text-fill-color 接回来;
102
+ 两个选择器名分写两条,一条列表里只要有一个名字不被认识整条就被丢弃 */
103
+ [data-scope='side-nav'][data-part='input']:autofill {
104
+ box-shadow: inset 0 0 0 100vmax var(--xh-side-nav-input-autofill-bg, var(--xh-bg-surface));
105
+ -webkit-text-fill-color: var(--xh-side-nav-input-autofill-fg, var(--xh-fg-default));
106
+ }
107
+
108
+ [data-scope='side-nav'][data-part='input']:-webkit-autofill {
109
+ box-shadow: inset 0 0 0 100vmax var(--xh-side-nav-input-autofill-bg, var(--xh-bg-surface));
110
+ -webkit-text-fill-color: var(--xh-side-nav-input-autofill-fg, var(--xh-fg-default));
111
+ }
112
+
113
+ /* 折叠一开始搜索框就与行文字一起淡出,展开等整栏宽度落定才淡入;落成图标栏之后让位:
114
+ 留着高度、不可见也不可聚焦(过滤此时暂停),下面的行不上下跳 */
115
+ [data-scope='side-nav'][data-part='root']:is([data-collapsed], [data-animating]) [data-part='input'] {
116
+ opacity: 0;
117
+ }
118
+
119
+ [data-scope='side-nav'][data-part='root'][data-collapsed]:not([data-animating]) [data-part='input'] {
120
+ visibility: hidden;
64
121
  }
65
122
 
123
+ /* 搜索一条都没命中的占位:list 的兄弟,只一句次要文字,面归侧栏所在的宿主 */
124
+ [data-scope='side-nav'][data-part='empty'] {
125
+ padding-block: var(--xh-side-nav-empty-py, var(--xh-space-3));
126
+ padding-inline: var(--xh-side-nav-empty-px, var(--xh-_side-nav-row-px));
127
+ color: var(--xh-side-nav-empty-fg, var(--xh-fg-muted));
128
+ font-size: var(--xh-side-nav-empty-font-size, var(--xh-_side-nav-row-font-size));
129
+ text-align: center;
130
+ }
131
+
132
+ /* 三层列表同一副排法:顶层、分组里的与分支的子层都是 ul,行与行之间同一个 gap,不带 UA 的缩进与外边距 */
66
133
  [data-scope='side-nav'][data-part='list'],
134
+ [data-scope='side-nav'][data-part='group-list'],
67
135
  [data-scope='side-nav'][data-part='branch-content'] {
68
136
  display: flex;
69
137
  flex-direction: column;
@@ -82,6 +150,7 @@
82
150
  display: none;
83
151
  }
84
152
 
153
+ /* 分组是上一层列表里的一条:标题与组内列表上下排,间距与行距同一个 gap */
85
154
  [data-scope='side-nav'][data-part='group'] {
86
155
  display: flex;
87
156
  flex-direction: column;
@@ -90,17 +159,31 @@
90
159
  }
91
160
 
92
161
  [data-scope='side-nav'][data-part='group-label'] {
162
+ position: relative;
93
163
  padding-block: var(--xh-side-nav-group-label-py, var(--xh-space-1));
94
164
  padding-inline: var(--xh-side-nav-group-label-px, var(--xh-_side-nav-row-px));
95
165
  font-size: var(--xh-text-caption-size);
96
166
  font-weight: var(--xh-text-label-weight);
97
167
  color: var(--xh-fg-subtle);
98
168
  user-select: none;
169
+ transition: color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
99
170
  }
100
171
 
101
- /* 折叠时分组标题没有落点,藏掉 */
102
- [data-scope='side-nav'][data-part='group-label'][data-collapsed] {
103
- display: none;
172
+ /* 图标栏里分组标题没有落点:字淡成透明、仍参与播报,标题留着自己的高度,下面的行不上下跳;
173
+ 折叠一开始就淡出,展开等整栏宽度落定才淡入。落成图标栏之后在这一格里画一道细分隔,分组仍看得出来 */
174
+ [data-scope='side-nav'][data-part='root']:is([data-collapsed], [data-animating]) [data-part='group-label'] {
175
+ overflow: hidden;
176
+ color: transparent;
177
+ white-space: nowrap;
178
+ }
179
+
180
+ [data-scope='side-nav'][data-part='root'][data-collapsed]:not([data-animating]) [data-part='group-label']::after {
181
+ content: '';
182
+ position: absolute;
183
+ inset-inline: var(--xh-_side-nav-row-px);
184
+ inset-block-start: calc(50% - var(--xh-stroke-thin) / 2);
185
+ block-size: var(--xh-stroke-thin);
186
+ background: var(--xh-border-subtle);
104
187
  }
105
188
 
106
189
  [data-scope='side-nav'][data-part='branch'] {
@@ -118,7 +201,7 @@
118
201
  }
119
202
 
120
203
  /* —— 行:分支按钮与链接同一副长相 ——
121
- 两种行都走 Collection Item 的 page 语境(页内持久集合,§7.3):悬停 / 高亮 / 按下面(白底承载 hover 100 →
204
+ 两种行都走 Collection Item 的 page 语境(页内持久集合):悬停 / 高亮 / 按下面(白底承载 hover 100 →
122
205
  pressed 200,只换面不缩放)、当前页(品牌淡底 + 淡底前景 + 起始侧 2px 指示条,current+hover 20% /
123
206
  +pressed 28%)、展开路径(与 hover 同档的中性面,不用品牌色、不加粗)与禁用面都由家族按 data-current /
124
207
  data-in-path / aria-disabled 给出。这里只把公开槽映射到桥接槽。
@@ -140,16 +223,16 @@
140
223
  --xh-collection-bg-hover: var(--xh-side-nav-row-bg-hover, var(--xh-bg-subtle));
141
224
  --xh-collection-bg-keyboard-highlight: var(--xh-side-nav-row-bg-hover, var(--xh-bg-subtle));
142
225
  --xh-collection-bg-pressed: var(--xh-side-nav-row-bg-pressed, var(--xh-bg-subtle-hover));
143
- /* 当前页:公开槽名不变,缺省前景由品牌深字改为淡底前景,字重保持 medium;2px 指示条取品牌色 */
226
+ /* 当前页:页内持久集合的唯一标记是品牌淡底行面 + 淡底前景,字重不变(与静息同为 regular) */
144
227
  --xh-collection-bg-current: var(--xh-side-nav-row-bg-active, var(--xh-bg-brand-subtle));
145
228
  --xh-collection-fg-current: var(--xh-side-nav-row-fg-active, var(--xh-fg-on-brand-subtle));
146
229
  --xh-collection-fg-current-hover: var(--xh-side-nav-row-fg-active, var(--xh-fg-on-brand-subtle));
147
230
  --xh-collection-fg-current-highlight: var(--xh-side-nav-row-fg-active, var(--xh-fg-on-brand-subtle));
148
231
  --xh-collection-fg-current-pressed: var(--xh-side-nav-row-fg-active, var(--xh-fg-on-brand-subtle));
149
- --xh-collection-font-weight-current: var(--xh-side-nav-row-font-weight-active, var(--xh-font-weight-medium));
150
- --xh-collection-font-weight-current-hover: var(--xh-side-nav-row-font-weight-active, var(--xh-font-weight-medium));
151
- --xh-collection-font-weight-current-highlight: var(--xh-side-nav-row-font-weight-active, var(--xh-font-weight-medium));
152
- --xh-collection-font-weight-current-pressed: var(--xh-side-nav-row-font-weight-active, var(--xh-font-weight-medium));
232
+ --xh-collection-font-weight-current: var(--xh-side-nav-row-font-weight-active, var(--xh-font-weight-regular));
233
+ --xh-collection-font-weight-current-hover: var(--xh-side-nav-row-font-weight-active, var(--xh-font-weight-regular));
234
+ --xh-collection-font-weight-current-highlight: var(--xh-side-nav-row-font-weight-active, var(--xh-font-weight-regular));
235
+ --xh-collection-font-weight-current-pressed: var(--xh-side-nav-row-font-weight-active, var(--xh-font-weight-regular));
153
236
  --xh-collection-indicator-fg-current: var(--xh-side-nav-indicator-color, var(--xh-fg-brand));
154
237
  --xh-collection-indicator-fg-current-hover: var(--xh-side-nav-indicator-color, var(--xh-fg-brand));
155
238
  --xh-collection-indicator-fg-current-highlight: var(--xh-side-nav-indicator-color, var(--xh-fg-brand));
@@ -187,15 +270,24 @@
187
270
  overflow: hidden;
188
271
  text-overflow: ellipsis;
189
272
  white-space: nowrap;
273
+ transition: opacity var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
190
274
  }
191
275
 
192
- /* 折叠成图标栏:行文字裁到看不见但仍参与播报,行内容居中,不裁半个字。
276
+ /* 行文字与展开箭头的淡变:折叠一开始就淡出,此时仍在行里、由收窄的行宽裁着;展开时等整栏宽度落定才淡入。
277
+ 弹出面板里的行不在此列 */
278
+ [data-scope='side-nav'][data-part='root']:is([data-collapsed], [data-animating]) [data-part='branch-text']:not([data-popout] *),
279
+ [data-scope='side-nav'][data-part='root']:is([data-collapsed], [data-animating]) [data-part='link-text']:not([data-popout] *),
280
+ [data-scope='side-nav'][data-part='root']:is([data-collapsed], [data-animating]) [data-part='branch-indicator']:not([data-popout] *) {
281
+ opacity: 0;
282
+ }
283
+
284
+ /* 落成图标栏(收窄落定):行文字裁到看不见但仍参与播报,行内容居中,不裁半个字。
193
285
  用 clip-path 而不是 display:none——图标栏里按钮与链接的可及名就是这段文字,
194
286
  藏成 display:none 它们就一个名字都没有了。
195
287
  white-space: nowrap 防止 1px 宽把文本挤成竖排。
196
288
  弹出面板里的行不在此列——面板是正常宽度的浮层,文字照常显示 */
197
- [data-scope='side-nav'][data-part='root'][data-collapsed] [data-part='branch-text']:not([data-popout] *),
198
- [data-scope='side-nav'][data-part='root'][data-collapsed] [data-part='link-text']:not([data-popout] *) {
289
+ [data-scope='side-nav'][data-part='root'][data-collapsed]:not([data-animating]) [data-part='branch-text']:not([data-popout] *),
290
+ [data-scope='side-nav'][data-part='root'][data-collapsed]:not([data-animating]) [data-part='link-text']:not([data-popout] *) {
199
291
  position: absolute;
200
292
  inline-size: 1px;
201
293
  block-size: 1px;
@@ -207,13 +299,13 @@
207
299
  border: 0;
208
300
  }
209
301
 
210
- /* 展开指示是纯装饰(aria-hidden),折叠态整个不占位 */
211
- [data-scope='side-nav'][data-part='root'][data-collapsed] [data-part='branch-indicator']:not([data-popout] *) {
302
+ /* 展开指示是纯装饰(aria-hidden),落成图标栏之后整个不占位 */
303
+ [data-scope='side-nav'][data-part='root'][data-collapsed]:not([data-animating]) [data-part='branch-indicator']:not([data-popout] *) {
212
304
  display: none;
213
305
  }
214
306
 
215
- [data-scope='side-nav'][data-part='root'][data-collapsed] [data-part='branch-trigger']:not([data-popout] *),
216
- [data-scope='side-nav'][data-part='root'][data-collapsed] [data-part='link']:not([data-popout] *) {
307
+ [data-scope='side-nav'][data-part='root'][data-collapsed]:not([data-animating]) [data-part='branch-trigger']:not([data-popout] *),
308
+ [data-scope='side-nav'][data-part='root'][data-collapsed]:not([data-animating]) [data-part='link']:not([data-popout] *) {
217
309
  --xh-collection-inline-padding: 0;
218
310
 
219
311
  justify-content: center;
@@ -230,11 +322,34 @@
230
322
  引擎没算(尚未落位 / 无引擎 / 贴边归零)时退到视口宽 */
231
323
  --xh-_side-nav-available-w: 100vw;
232
324
 
325
+ /* 弹出面板从锚点一侧短移入,方向按实际落位只激活一支;先清零,截断父层定位层的方向继承 */
326
+ --xh-_overlay-enter-up: 0;
327
+ --xh-_overlay-enter-down: 0;
328
+ --xh-_overlay-enter-left: 0;
329
+ --xh-_overlay-enter-right: 0;
330
+
233
331
  position: fixed;
234
332
  z-index: var(--xh-side-nav-popout-layer, var(--xh-_layer, var(--xh-layer-popover)));
235
333
  pointer-events: auto;
236
334
  }
237
335
 
336
+ /* 物理 placement 与定位引擎同一套,不随书写方向翻 */
337
+ [data-scope='side-nav'][data-part='positioner'][data-placement^='top'] {
338
+ --xh-_overlay-enter-down: 1;
339
+ }
340
+
341
+ [data-scope='side-nav'][data-part='positioner'][data-placement^='bottom'] {
342
+ --xh-_overlay-enter-up: 1;
343
+ }
344
+
345
+ [data-scope='side-nav'][data-part='positioner'][data-placement^='left'] {
346
+ --xh-_overlay-enter-right: 1;
347
+ }
348
+
349
+ [data-scope='side-nav'][data-part='positioner'][data-placement^='right'] {
350
+ --xh-_overlay-enter-left: 1;
351
+ }
352
+
238
353
  /* 还没落位就先藏着:visibility 而非 display,display:none 会让引擎量不到面板尺寸 */
239
354
  [data-scope='side-nav'][data-part='positioner'][data-hidden] {
240
355
  visibility: hidden;
@@ -253,26 +368,25 @@
253
368
  box-shadow: var(--xh-side-nav-popout-shadow, var(--xh-elevation-floating));
254
369
 
255
370
  /* 子导航条目数没有上限,矮视口下不限高就整块伸出视口且滚不到。
256
- 浮层滚动面:到底后不把滚动链穿透给页面(§6.6);细条由 reset 层给 */
371
+ 浮层滚动面:到底后不把滚动链穿透给页面;细条由 reset 层给 */
257
372
  max-block-size: min(var(--xh-side-nav-popout-max-h, var(--xh-overlay-menu-max-h)), var(--xh-_side-nav-available-h));
258
373
  overflow-y: auto;
259
374
  overscroll-behavior: contain;
260
375
  }
261
376
 
262
- /* 在场那一档才提前备好合成层:属性随 data-state 一起消失,收起后不长期占着 */
377
+ /* 弹出分支是锚定在分支行旁侧的一列导航,与菜单同属锚定列表:从锚点一侧短移淡入,不缩放 */
263
378
  [data-scope='side-nav'][data-part='branch-content'][data-popout][data-state='open'] {
264
- will-change: opacity, scale;
265
- animation: xh-pop-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter-strong);
379
+ animation: xh-overlay-slide-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter-strong);
266
380
  }
267
381
 
268
382
  /* 退场:收起那一帧 hidden 由退场闸门押后,这里才播得出来。forwards 把末态钉住,
269
383
  免得动画结束到收起落下来之间闪一下原样 */
270
384
  [data-scope='side-nav'][data-part='branch-content'][data-popout][data-state='closed'] {
271
- animation: xh-pop-out var(--xh-motion-duration-exit) var(--xh-motion-ease-exit) forwards;
385
+ animation: xh-overlay-slide-out var(--xh-motion-duration-exit) var(--xh-motion-ease-exit) forwards;
272
386
  }
273
387
 
274
388
  /* 箭头是行尾的后缀:auto 外边距把它顶到行尾,家族按 suffix 槽下发的外边距已在行上归零。
275
- 盒里的 chevron 是指示符(§6.5):盒按指示符档定尺(comfortable 16 / compact 14,与 Tree / TreeSelect
389
+ 盒里的 chevron 是指示符:盒按指示符档定尺(comfortable 16 / compact 14,与 Tree / TreeSelect
276
390
  的 branch-indicator 同款定尺盒),--xh-icon-size 在自己身上改接到盒的尺,兜底字形与作者塞进盒里的
277
391
  XhIcon 读同一把尺、与盒同边长;不读家族按档下发到行的 --xh-icon-size——那支只管作者放进行里的
278
392
  图标,按图标档取的 20px 比指示符档大一圈。盒自己定尺而不随字形撑开:转 90° 时才不抖 */
@@ -286,7 +400,12 @@
286
400
  justify-content: center;
287
401
  inline-size: var(--xh-side-nav-branch-indicator-size, var(--xh-control-indicator-size));
288
402
  block-size: var(--xh-side-nav-branch-indicator-size, var(--xh-control-indicator-size));
289
- transition: rotate var(--xh-motion-duration-micro) var(--xh-motion-ease-enter-strong);
403
+ /* 收起时指向行尾、展开时转向下方,与逻辑书写方向一致:rtl 下行尾在左侧,收起的箭头转半圈;
404
+ 旋转角乘书写方向的符号(ltr 0deg、rtl 180deg),按就近的 dir 走 */
405
+ rotate: calc(90deg - 90deg * var(--xh-direction-sign));
406
+ transition:
407
+ rotate var(--xh-motion-duration-nudge) var(--xh-motion-ease-enter-strong),
408
+ opacity var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
290
409
  }
291
410
 
292
411
  [data-scope='side-nav'][data-part='branch-indicator'][data-state='open'] {
@@ -311,6 +430,7 @@
311
430
  [data-scope='side-nav'][data-part='root'][hidden],
312
431
  [data-scope='side-nav'][data-part='list'][hidden],
313
432
  [data-scope='side-nav'][data-part='group'][hidden],
433
+ [data-scope='side-nav'][data-part='group-list'][hidden],
314
434
  [data-scope='side-nav'][data-part='branch'][hidden],
315
435
  [data-scope='side-nav'][data-part='branch-trigger'][hidden],
316
436
  [data-scope='side-nav'][data-part='item'][hidden],
@@ -319,11 +439,38 @@
319
439
  display: none;
320
440
  }
321
441
 
442
+ @media (forced-colors: active) {
443
+ /* 图标栏里分组之间那道细分隔画的是一块底色,这一档里底色被统一换成 Canvas、整道消失;换成 GrayText 画回来 */
444
+ [data-scope='side-nav'][data-part='root'][data-collapsed]:not([data-animating]) [data-part='group-label']::after {
445
+ background: GrayText;
446
+ }
447
+ }
448
+
322
449
  @media print {
323
- /* 打印:定位层与遮罩是交互期才存在的东西,落到纸上只会盖住底下的正文。
450
+ /* 打印:定位层与遮罩是交互期才存在的东西,落到纸上只会盖住底下的正文;搜索框同理,纸上没法输入。
324
451
  收起写在这份皮肤里而不是 reset:reset 层排在组件层之前,组件层里任何一条 display 都压得住它。 */
325
- [data-scope='side-nav'][data-part='positioner'] {
452
+ [data-scope='side-nav'][data-part='positioner'],
453
+ [data-scope='side-nav'][data-part='input'] {
326
454
  display: none;
327
455
  }
328
456
  }
457
+
458
+ @media (forced-colors: active) {
459
+ /* 兜底字形是 mask 挖出来的一块底色,这一档里底色被系统换成 Canvas,字形跟着消失。
460
+ 字形退出强制换色、直接写系统色(不能取 currentColor:退出强制换色的伪元素读到的是被系统换掉之前的作者色):
461
+ 条目面上取 CanvasText;按下与没被高亮时的悬停,家族把条目涂成 Highlight,字形换 HighlightText;
462
+ 禁用取 GrayText */
463
+ [data-scope='side-nav'][data-part='branch-indicator']:empty::before {
464
+ forced-color-adjust: none;
465
+ background-color: CanvasText;
466
+ }
467
+
468
+ [data-scope='side-nav'][data-part='branch-trigger']:not([aria-disabled='true'], [data-disabled], [aria-busy='true'], [data-error]):is(:active, [data-pressed], :hover:not(:focus-visible, [data-highlighted])) [data-scope='side-nav'][data-part='branch-indicator']:empty::before {
469
+ background-color: HighlightText;
470
+ }
471
+
472
+ [data-scope='side-nav'][data-part='branch-trigger']:is([aria-disabled='true'], [data-disabled]) [data-scope='side-nav'][data-part='branch-indicator']:empty::before {
473
+ background-color: GrayText;
474
+ }
475
+ }
329
476
  }