@xihan-ui/styles 1.0.0-alpha.2 → 1.0.0-alpha.3

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 (118) hide show
  1. package/CHANGELOG.md +447 -0
  2. package/README.md +1 -1
  3. package/css/accordion.css +56 -5
  4. package/css/alert.css +30 -5
  5. package/css/anchor.css +7 -7
  6. package/css/avatar-group.css +3 -3
  7. package/css/avatar.css +3 -3
  8. package/css/back-top.css +16 -1
  9. package/css/badge.css +16 -2
  10. package/css/breadcrumb.css +24 -4
  11. package/css/button-group.css +14 -13
  12. package/css/button.css +42 -11
  13. package/css/calendar.css +119 -2
  14. package/css/card.css +5 -5
  15. package/css/carousel.css +36 -5
  16. package/css/cascader.css +183 -102
  17. package/css/checkbox-group.css +41 -19
  18. package/css/checkbox.css +54 -8
  19. package/css/clipboard.css +2 -1
  20. package/css/code-block.css +12 -7
  21. package/css/collapsible.css +42 -2
  22. package/css/color-picker.css +111 -45
  23. package/css/combobox.css +114 -53
  24. package/css/composer.css +5 -3
  25. package/css/context-menu.css +97 -69
  26. package/css/date-field.css +91 -14
  27. package/css/date-picker.css +276 -65
  28. package/css/descriptions.css +2 -2
  29. package/css/dialog.css +40 -13
  30. package/css/download-trigger.css +76 -0
  31. package/css/drawer.css +45 -14
  32. package/css/dynamic-input.css +68 -17
  33. package/css/editable.css +12 -10
  34. package/css/ellipsis.css +1 -1
  35. package/css/empty-state.css +7 -7
  36. package/css/field.css +9 -6
  37. package/css/fieldset.css +69 -0
  38. package/css/file-upload.css +45 -7
  39. package/css/float-button.css +16 -1
  40. package/css/floating-panel.css +310 -0
  41. package/css/form.css +4 -4
  42. package/css/heatmap.css +410 -0
  43. package/css/highlight.css +1 -1
  44. package/css/hotkeys.css +90 -0
  45. package/css/hover-card.css +40 -45
  46. package/css/image-cropper.css +231 -0
  47. package/css/image-viewer.css +137 -12
  48. package/css/image.css +2 -2
  49. package/css/infinite-scroll.css +1 -1
  50. package/css/json-viewer.css +241 -0
  51. package/css/layout.css +6 -10
  52. package/css/list.css +8 -8
  53. package/css/listbox.css +20 -4
  54. package/css/loading-bar.css +7 -1
  55. package/css/log.css +1 -1
  56. package/css/marquee.css +9 -0
  57. package/css/masonry.css +63 -0
  58. package/css/mention.css +87 -33
  59. package/css/menu.css +113 -47
  60. package/css/menubar.css +146 -60
  61. package/css/navigation-menu.css +54 -29
  62. package/css/number-animation.css +3 -3
  63. package/css/number-field.css +173 -16
  64. package/css/overlay-arrow.css +51 -0
  65. package/css/page-header.css +5 -5
  66. package/css/pagination.css +13 -13
  67. package/css/password-input.css +356 -0
  68. package/css/pin-input.css +13 -10
  69. package/css/popconfirm.css +46 -10
  70. package/css/popover.css +70 -45
  71. package/css/popselect.css +183 -53
  72. package/css/progress.css +47 -7
  73. package/css/qr-code.css +1 -1
  74. package/css/radio-group.css +14 -8
  75. package/css/rating.css +2 -2
  76. package/css/reset.css +17 -0
  77. package/css/result.css +6 -6
  78. package/css/scroll-area.css +61 -89
  79. package/css/scrollbar.css +167 -0
  80. package/css/segmented.css +215 -0
  81. package/css/select.css +274 -125
  82. package/css/separator.css +2 -2
  83. package/css/side-nav.css +111 -19
  84. package/css/signature-pad.css +144 -0
  85. package/css/skeleton.css +7 -1
  86. package/css/slider.css +10 -10
  87. package/css/space.css +117 -0
  88. package/css/spinner.css +22 -10
  89. package/css/statistic.css +9 -9
  90. package/css/steps.css +25 -8
  91. package/css/switch.css +48 -6
  92. package/css/table.css +131 -37
  93. package/css/tabs.css +4 -4
  94. package/css/tag.css +177 -0
  95. package/css/tags-input.css +78 -40
  96. package/css/text-field.css +149 -36
  97. package/css/thread.css +1 -1
  98. package/css/time-field.css +97 -16
  99. package/css/time-picker.css +259 -69
  100. package/css/timeline.css +10 -10
  101. package/css/timer.css +119 -0
  102. package/css/toast.css +34 -10
  103. package/css/toaster.css +1 -1
  104. package/css/toggle.css +6 -6
  105. package/css/tone.css +10 -5
  106. package/css/toolbar.css +1 -1
  107. package/css/tooltip.css +36 -33
  108. package/css/tour.css +103 -58
  109. package/css/transfer.css +68 -17
  110. package/css/tree-select.css +185 -84
  111. package/css/tree.css +85 -1
  112. package/css/typography.css +4 -4
  113. package/css/undefined.css +11 -1
  114. package/css/virtualizer.css +3 -1
  115. package/css/watermark.css +1 -2
  116. package/index.css +17 -0
  117. package/index.unlayered.css +8659 -2799
  118. package/package.json +19 -3
@@ -27,12 +27,12 @@
27
27
  /* —— 尺寸 —— */
28
28
  [data-scope='radio-group'][data-part='root'][data-size='sm'] {
29
29
  --xh-_radio-group-item-gap: var(--xh-control-gap-sm);
30
- --xh-_radio-group-font-size: var(--xh-font-size-sm);
30
+ --xh-_radio-group-font-size: var(--xh-control-font-sm);
31
31
  }
32
32
 
33
33
  [data-scope='radio-group'][data-part='root'][data-size='lg'] {
34
34
  --xh-_radio-group-item-gap: var(--xh-control-gap-lg);
35
- --xh-_radio-group-font-size: var(--xh-font-size-lg);
35
+ --xh-_radio-group-font-size: var(--xh-control-font-lg);
36
36
  }
37
37
 
38
38
  [data-scope='radio-group'][data-part='label'] {
@@ -64,7 +64,6 @@
64
64
  [data-scope='radio-group'][data-part='item'][data-disabled] {
65
65
  color: var(--xh-radio-group-item-fg-disabled, var(--xh-fg-disabled));
66
66
  cursor: not-allowed;
67
- opacity: var(--xh-state-disabled-opacity);
68
67
  }
69
68
 
70
69
  [data-scope='radio-group'][data-part='indicator'] {
@@ -72,8 +71,8 @@
72
71
  flex: none;
73
72
  align-items: center;
74
73
  justify-content: center;
75
- inline-size: 16px;
76
- block-size: 16px;
74
+ inline-size: var(--xh-radio-group-indicator-size, var(--xh-control-indicator-size));
75
+ block-size: var(--xh-radio-group-indicator-size, var(--xh-control-indicator-size));
77
76
  border: var(--xh-stroke-thin) solid var(--xh-radio-group-indicator-border, var(--xh-border-control));
78
77
  border-radius: var(--xh-shape-pill);
79
78
  background: var(--xh-radio-group-indicator-bg, var(--xh-bg-canvas));
@@ -84,11 +83,18 @@
84
83
  border-color: var(--xh-radio-group-indicator-border-checked, var(--xh-_radio-group-accent));
85
84
  }
86
85
 
87
- /* 圆点用 scale 收放,选中态切换不引起布局抖动 */
86
+ /* 校验失败:圆圈描边转警示色,压在 checked 规则之后 */
87
+ [data-scope='radio-group'][data-part='indicator'][data-invalid],
88
+ [data-scope='radio-group'][data-part='indicator'][data-state='checked'][data-invalid] {
89
+ border-color: var(--xh-radio-group-indicator-border-invalid, var(--xh-border-invalid));
90
+ }
91
+
92
+ /* 圆点用 scale 收放,选中态切换不引起布局抖动。
93
+ 直径取指示器的一半:换尺寸档时圈与点一起走,比例不散 */
88
94
  [data-scope='radio-group'][data-part='indicator']::before {
89
95
  content: '';
90
- inline-size: 8px;
91
- block-size: 8px;
96
+ inline-size: calc(var(--xh-radio-group-indicator-size, var(--xh-control-indicator-size)) / 2);
97
+ block-size: calc(var(--xh-radio-group-indicator-size, var(--xh-control-indicator-size)) / 2);
92
98
  border-radius: var(--xh-shape-pill);
93
99
  background: var(--xh-radio-group-indicator-dot, var(--xh-_radio-group-accent));
94
100
  scale: 0;
package/css/rating.css CHANGED
@@ -24,13 +24,13 @@
24
24
  [data-scope='rating'][data-part='root'][data-size='sm'] {
25
25
  --xh-_rating-item-gap: var(--xh-control-gap-sm);
26
26
  --xh-_rating-item-size: var(--xh-font-size-xl);
27
- --xh-_rating-font-size: var(--xh-font-size-sm);
27
+ --xh-_rating-font-size: var(--xh-control-font-sm);
28
28
  }
29
29
 
30
30
  [data-scope='rating'][data-part='root'][data-size='lg'] {
31
31
  --xh-_rating-item-gap: var(--xh-control-gap-lg);
32
32
  --xh-_rating-item-size: var(--xh-font-size-3xl);
33
- --xh-_rating-font-size: var(--xh-font-size-lg);
33
+ --xh-_rating-font-size: var(--xh-control-font-lg);
34
34
  }
35
35
 
36
36
  /* 整体压暗只做一次,落在最外层:星星逐颗再叠 opacity 会越叠越黑 */
package/css/reset.css CHANGED
@@ -19,4 +19,21 @@
19
19
  font: inherit;
20
20
  letter-spacing: inherit;
21
21
  }
22
+
23
+ /*
24
+ * 浮层定位层的生命周期契约:落位才露。
25
+ *
26
+ * 坐标依赖浮层自己的尺寸,尺寸要等元素进 DOM 参与排版才量得到,所以浮层一定先渲染、
27
+ * 再量、再定位——展开的那几帧坐标还是兜底的 0。默认藏着,连接层算出坐标后打
28
+ * data-positioned 才显示,忘了接线的新浮层只会不显示,不会先在视口左上角闪一帧。
29
+ * 用 visibility 而不是 display:display:none 会让浮层退出排版,引擎量不到它的尺寸。
30
+ * 锚点被滚出可视区时引擎置 hidden,各皮肤另有一条 [data-hidden] 压回去。
31
+ */
32
+ [data-scope][data-part='positioner'] {
33
+ visibility: hidden;
34
+ }
35
+
36
+ [data-scope][data-part='positioner'][data-positioned] {
37
+ visibility: visible;
38
+ }
22
39
  }
package/css/result.css CHANGED
@@ -9,8 +9,8 @@
9
9
  换档不必给每个部件各写一条选择器 */
10
10
  --xh-_result-gap: var(--xh-stack-gap-md);
11
11
  --xh-_result-py: var(--xh-section-py);
12
- --xh-_result-icon-size: 3.5rem;
13
- --xh-_result-title-size: var(--xh-font-size-xl);
12
+ --xh-_result-icon-size: var(--xh-glyph-size-3xl);
13
+ --xh-_result-title-size: var(--xh-text-heading-3-size);
14
14
  /* 说明的读行宽度:整句话不收窄就会拉成一条难读的长行 */
15
15
  --xh-_result-measure: 32rem;
16
16
  /* 图标区的语气色,由 data-status 换掉;没写 status 就退成中性 */
@@ -36,15 +36,15 @@
36
36
  [data-scope='result'][data-part='root'][data-size='sm'] {
37
37
  --xh-_result-gap: var(--xh-space-3);
38
38
  --xh-_result-py: var(--xh-space-6);
39
- --xh-_result-icon-size: 2.5rem;
40
- --xh-_result-title-size: var(--xh-font-size-lg);
39
+ --xh-_result-icon-size: var(--xh-glyph-size-2xl);
40
+ --xh-_result-title-size: var(--xh-control-font-lg);
41
41
  }
42
42
 
43
43
  [data-scope='result'][data-part='root'][data-size='lg'] {
44
44
  --xh-_result-gap: var(--xh-space-6);
45
45
  --xh-_result-py: var(--xh-space-8);
46
- --xh-_result-icon-size: 4.5rem;
47
- --xh-_result-title-size: var(--xh-font-size-2xl);
46
+ --xh-_result-icon-size: var(--xh-glyph-size-4xl);
47
+ --xh-_result-title-size: var(--xh-text-heading-2-size);
48
48
  }
49
49
 
50
50
  /* —— 语气 —— */
@@ -1,37 +1,30 @@
1
- @layer xihan.motion {
2
- /* 滚动条从收起到露出的淡入。收起走的是 [hidden](display:none),没有退场动画可言——
3
- display:none 是瞬时的,硬挂 fade-out 只会看到一帧都不播。
4
- 元素从 display:none 变回可见时动画会重新开始,这一条因此每次露面都播一遍。 */
5
- @keyframes xh-scroll-area-bar-in {
6
- from {
7
- opacity: 0;
8
- }
9
-
10
- to {
11
- opacity: 1;
12
- }
13
- }
14
- }
15
-
16
1
  @layer xihan.components {
17
2
  /* 皮肤在场的标记:开发模式下按 scope 探一次,取不到就说明这份皮肤没被引入。 */
18
3
  [data-scope='scroll-area'] {
19
4
  --xh-scroll-area-skin: 1;
20
5
  }
21
6
 
22
- /* 定位分工:滑块在**主轴**上的起点与长度由连接层每帧写进内联样式——
23
- 竖向是 inset-block-start / block-size,横向是 inset-inline-start / inline-size。
24
- 这份皮肤一个都不碰:内联优先级高于样式表,写了不但是死声明,
25
- 换轴的那一帧还会跟连接层清空另一轴的动作抢位置,把滑块钉在两轴都被约束的地方。
26
- 皮肤只管另一条轴(交叉轴)的尺寸、颜色、圆角与状态表现。 */
7
+ /* 滚动区是视口加两条 scrollbar 的组装:轨道、滑块与交叉口戴 data-scope="scrollbar",
8
+ 它们的皮归 scrollbar.css;这里只管视口、内容层,以及充当 scrollbar 根节点的那个挂载点。 */
27
9
 
28
- /* 滚动条与右下角补丁都是绝对定位的,以它为参照,因此它必须自己建定位上下文。
10
+ /* 滚动条是绝对定位的,以它为参照,因此它必须自己建定位上下文。
29
11
  overflow:hidden 管的是溢出边界:作者给 root 定了尺寸后,内容与滚动条都不该漫出这个框。 */
30
12
  [data-scope='scroll-area'][data-part='root'] {
13
+ /* 厚度只算一次:尺寸档换的就是它,视口让道、交叉口让位与补丁都按同一个数 */
14
+ --xh-_scrollbar-thickness: var(--xh-scrollbar-thickness-md);
15
+
31
16
  position: relative;
32
17
  overflow: hidden;
33
18
  }
34
19
 
20
+ [data-scope='scroll-area'][data-part='root'][data-size='sm'] {
21
+ --xh-_scrollbar-thickness: var(--xh-scrollbar-thickness-sm);
22
+ }
23
+
24
+ [data-scope='scroll-area'][data-part='root'][data-size='lg'] {
25
+ --xh-_scrollbar-thickness: var(--xh-scrollbar-thickness-lg);
26
+ }
27
+
35
28
  /* 真正滚动的那层。原生滚动条藏起来,滚动能力一点不减——
36
29
  键盘(PageUp/PageDown、方向键、Home/End)与滚轮走的都还是浏览器原生通路。 */
37
30
  [data-scope='scroll-area'][data-part='viewport'] {
@@ -41,13 +34,17 @@
41
34
  这两条是本文件里仅有的物理属性,换成逻辑写法会在多数浏览器上直接失效 */
42
35
  overflow-x: auto;
43
36
  overflow-y: auto;
44
- /* 藏原生滚动条的标准写法;下面那条 ::-webkit-scrollbar 兜住还没实现它的引擎。
45
- 只藏外观,滚动能力一点不动:这里刻意不写 overscroll-behavior,
46
- 滚到尽头要不要把滚动传给外层,仍然按浏览器的默认来 */
37
+ }
38
+
39
+ /* 藏原生滚动条的标准写法;下面那条 ::-webkit-scrollbar 兜住还没实现它的引擎。
40
+ 只藏外观,滚动能力一点不动:这里刻意不写 overscroll-behavior,
41
+ 滚到尽头要不要把滚动传给外层,仍然按浏览器的默认来。
42
+ 触屏上自绘滚动条交给了原生(data-native),原生滚动条就留着 */
43
+ [data-scope='scroll-area'][data-part='viewport']:not([data-native]) {
47
44
  scrollbar-width: none;
48
45
  }
49
46
 
50
- [data-scope='scroll-area'][data-part='viewport']::-webkit-scrollbar {
47
+ [data-scope='scroll-area'][data-part='viewport']:not([data-native])::-webkit-scrollbar {
51
48
  /* 这里不是角色节点而是伪元素,不涉及 [hidden] 那条规矩 */
52
49
  display: none;
53
50
  }
@@ -69,14 +66,13 @@
69
66
  }
70
67
 
71
68
  /* 有滚动条要占道时,视口在那条轴上收窄一条道的宽度,末端内容不再被压在滚动条底下。
72
- 缩视口而不是给视口加内边距:滚动容器末端缘的 padding 计不计进 scrollWidth,各引擎不一致。
73
- 子结合符:只认自己这一层的滚动条,嵌套滚动区不会把外层视口也削掉。 */
74
- [data-scope='scroll-area'][data-part='root']:has(> [data-part='scrollbar'][data-orientation='vertical'][data-gutter]) > [data-scope='scroll-area'][data-part='viewport'] {
75
- inline-size: calc(100% - var(--xh-scroll-area-bar-size, 10px));
69
+ 缩视口而不是给视口加内边距:滚动容器末端缘的 padding 计不计进 scrollWidth,各引擎不一致。 */
70
+ [data-scope='scroll-area'][data-part='viewport'][data-lane-vertical] {
71
+ inline-size: calc(100% - var(--xh-_scrollbar-thickness));
76
72
  }
77
73
 
78
- [data-scope='scroll-area'][data-part='root']:has(> [data-part='scrollbar'][data-orientation='horizontal'][data-gutter]) > [data-scope='scroll-area'][data-part='viewport'] {
79
- block-size: calc(100% - var(--xh-scroll-area-bar-size, 10px));
74
+ [data-scope='scroll-area'][data-part='viewport'][data-lane-horizontal] {
75
+ block-size: calc(100% - var(--xh-_scrollbar-thickness));
80
76
  }
81
77
 
82
78
  /* 横向溢出全靠它撑:内容若跟着视口一起收成 100% 宽,横轴永远量不出溢出,
@@ -85,91 +81,67 @@
85
81
  min-inline-size: fit-content;
86
82
  }
87
83
 
88
- /* 轨道。touch-action 关掉浏览器自己的手势:不关的话手指一按滑块页面就跟着滚,
89
- 指针随即被系统收走(pointercancel),拖到一半的滑块停在原地。
90
- user-select 同理——拖动时选中页面文字会让滑块"粘"住。 */
84
+ /* 滚动条挂载点同时充当那条 scrollbar 的根节点:scrollbar 那份皮肤的根规则落在
85
+ [data-scope='scrollbar'][data-part='root'] 上,而这里没有那个节点,根该有的排布得在这儿补齐
86
+ (轨道与滑块的皮仍归 scrollbar 那一份)。滑块主轴的起点与长度由连接层写进内联样式,这里不碰。 */
91
87
  [data-scope='scroll-area'][data-part='scrollbar'] {
92
88
  position: absolute;
93
- padding: var(--xh-scroll-area-bar-padding, 2px);
94
- background: var(--xh-scroll-area-bar-bg, var(--xh-bg-subtle));
89
+ /* touch-action 关掉浏览器自己的手势:不关的话手指一按滑块页面就跟着滚,
90
+ 指针随即被系统收走(pointercancel),拖到一半的滑块停在原地。
91
+ user-select 同理——拖动时选中页面文字会让滑块"粘"住。 */
95
92
  touch-action: none;
96
93
  user-select: none;
94
+ /* 露出与收起都淡变;收起态连 visibility 一起收,退场播完才真正摸不到 */
95
+ transition:
96
+ opacity var(--xh-motion-duration-enter) var(--xh-motion-ease-enter),
97
+ visibility 0s linear 0s;
97
98
  }
98
99
 
99
100
  [data-scope='scroll-area'][data-part='scrollbar'][data-orientation='vertical'] {
100
101
  inset-block: 0;
101
102
  inset-inline-end: 0;
102
- inline-size: var(--xh-scroll-area-bar-size, 10px);
103
+ inline-size: var(--xh-_scrollbar-thickness);
103
104
  }
104
105
 
105
106
  [data-scope='scroll-area'][data-part='scrollbar'][data-orientation='horizontal'] {
106
107
  inset-inline: 0;
107
108
  inset-block-end: 0;
108
- block-size: var(--xh-scroll-area-bar-size, 10px);
109
+ block-size: var(--xh-_scrollbar-thickness);
109
110
  }
110
111
 
111
- [data-scope='scroll-area'][data-part='scrollbar'][data-state='visible'] {
112
- animation: xh-scroll-area-bar-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter);
112
+ /* 两条都在场:各自在末端让出交叉口那一格,交叉口由竖条里的 corner 补 */
113
+ [data-scope='scroll-area'][data-part='scrollbar'][data-orientation='vertical'][data-gutter] {
114
+ inset-block-end: var(--xh-_scrollbar-thickness);
113
115
  }
114
116
 
115
- /* 除视口外全是纯容器:既不进 Tab 序列也不承载焦点,作者若给它们加了 tabindex,
116
- 该显形的也该是视口那圈环,不是这里再多画一圈 */
117
- [data-scope='scroll-area'][data-part='root']:focus-visible,
118
- [data-scope='scroll-area'][data-part='content']:focus-visible,
119
- [data-scope='scroll-area'][data-part='scrollbar']:focus-visible,
120
- [data-scope='scroll-area'][data-part='corner']:focus-visible {
121
- outline: none;
117
+ [data-scope='scroll-area'][data-part='scrollbar'][data-orientation='horizontal'][data-gutter] {
118
+ inset-inline-end: var(--xh-_scrollbar-thickness);
122
119
  }
123
120
 
124
- /* 滑块。主轴(起点 + 长度)来自内联样式,这里只给定位模式、交叉轴与配色。
125
- 绝不给 inset / size 加过渡:滚动时这两个值每帧都在变,一加过渡滑块就永远追在内容后面。
126
- 也刻意不写 min-block-size / min-inline-size:滑块的最短长度由几何层的常量算进内联尺寸里,
127
- 样式表再给一个下限就成了两个事实源,两者一旦不一致,滑块的实际长度与它能走的距离就对不上。 */
128
- [data-scope='scroll-area'][data-part='thumb'] {
129
- position: absolute;
130
- border-radius: var(--xh-scroll-area-thumb-radius, var(--xh-shape-pill));
131
- background: var(--xh-scroll-area-thumb-bg, var(--xh-color-neutral-400));
132
- cursor: default;
133
- transition: background var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
121
+ [data-scope='scroll-area'][data-part='scrollbar'][data-state='hidden'] {
122
+ visibility: hidden;
123
+ opacity: 0;
124
+ pointer-events: none;
125
+ transition:
126
+ opacity var(--xh-motion-duration-exit) var(--xh-motion-ease-exit),
127
+ visibility 0s linear var(--xh-motion-duration-exit);
134
128
  }
135
129
 
136
- /* 交叉轴在轨道内两侧各收进一个轨道内边距,与轨道那条 padding 共用同一个旋钮。
137
- 这一侧的 inset 连接层每帧写成空串(即不设),样式表接手才有确定值;
138
- 不写的话绝对定位会退回"静态位置"。
139
- 主轴那一侧一律不写:几何层量的轨道是 padding box,滑块长度也解析在同一个盒子上。 */
140
- [data-scope='scroll-area'][data-part='thumb'][data-orientation='vertical'] {
141
- inset-inline: var(--xh-scroll-area-bar-padding, 2px);
142
- }
143
-
144
- [data-scope='scroll-area'][data-part='thumb'][data-orientation='horizontal'] {
145
- inset-block: var(--xh-scroll-area-bar-padding, 2px);
146
- }
147
-
148
- [data-scope='scroll-area'][data-part='thumb']:hover {
149
- background: var(--xh-scroll-area-thumb-bg-hover, var(--xh-color-neutral-500));
150
- }
151
-
152
- /* 手正按在这条轴的滑块上时才变深:两条轴各有一个滑块,全打上标记就分不出手在哪一个上 */
153
- [data-scope='scroll-area'][data-part='thumb'][data-dragging] {
154
- background: var(--xh-scroll-area-thumb-bg-dragging, var(--xh-color-neutral-600));
130
+ /* 触屏上交给原生滚动:整条不占位也不画 */
131
+ [data-scope='scroll-area'][data-part='scrollbar'][data-native] {
132
+ display: none;
155
133
  }
156
134
 
157
- /* 两条滚动条同时在场时,右下角那个交叉口谁都盖不住,留着会露出底下的内容。
158
- z-index 只是为了不依赖作者把 corner 写在两条滚动条之后 */
159
- [data-scope='scroll-area'][data-part='corner'] {
160
- position: absolute;
161
- z-index: 1;
162
- display: block;
163
- inset-block-end: 0;
164
- inset-inline-end: 0;
165
- inline-size: var(--xh-scroll-area-bar-size, 10px);
166
- block-size: var(--xh-scroll-area-bar-size, 10px);
167
- background: var(--xh-scroll-area-corner-bg, var(--xh-bg-subtle));
135
+ /* 除视口外全是纯容器:既不进 Tab 序列也不承载焦点,作者若给它们加了 tabindex,
136
+ 该显形的也该是视口那圈环,不是这里再多画一圈 */
137
+ [data-scope='scroll-area'][data-part='root']:focus-visible,
138
+ [data-scope='scroll-area'][data-part='content']:focus-visible,
139
+ [data-scope='scroll-area'][data-part='scrollbar']:focus-visible {
140
+ outline: none;
168
141
  }
169
142
 
170
143
  /* display 会盖掉 UA 的 [hidden]{display:none},凡是被赋了 display 的角色节点都要显式让位 */
171
- [data-scope='scroll-area'][data-part='scrollbar'][hidden],
172
- [data-scope='scroll-area'][data-part='corner'][hidden] {
144
+ [data-scope='scroll-area'][data-part='scrollbar'][hidden] {
173
145
  display: none;
174
146
  }
175
147
  }
@@ -0,0 +1,167 @@
1
+ @layer xihan.components {
2
+ /* 皮肤在场的标记:开发模式下按 scope 探一次,取不到就说明这份皮肤没被引入。 */
3
+ [data-scope='scrollbar'] {
4
+ --xh-scrollbar-skin: 1;
5
+ }
6
+
7
+ /* 挂了自绘滚动条的容器:原生滚动条只藏外观,滚动能力一点不动。
8
+ 标记由机器挂监听时打上,最后一条滚动条拆走才撤。下面那条 ::-webkit-scrollbar 兜住还没实现 scrollbar-width 的引擎 */
9
+ [data-xh-scrollbar] {
10
+ scrollbar-width: none;
11
+ }
12
+
13
+ [data-xh-scrollbar]::-webkit-scrollbar {
14
+ display: none;
15
+ }
16
+
17
+ /* 定位分工:滑块在**主轴**上的起点与长度由连接层每帧写进内联样式——
18
+ 竖向是 inset-block-start / block-size,横向是 inset-inline-start / inline-size。
19
+ 这份皮肤一个都不碰:内联优先级高于样式表,写了不但是死声明,
20
+ 换轴的那一帧还会跟连接层清空另一轴的动作抢位置,把滑块钉在两轴都被约束的地方。
21
+ 皮肤只管另一条轴(交叉轴)的尺寸、颜色、圆角与状态表现。
22
+
23
+ 根用绝对定位、贴容器的对应边:滚动容器归作者,本组件只要求它自己的定位祖先建了上下文。
24
+ 作者要把它放在别处(比如浮在内容之上、或摆到框外),改这一条即可。 */
25
+ [data-scope='scrollbar'][data-part='root'] {
26
+ /* 厚度只算一次:尺寸档换的就是它,交叉口的让位与补丁都按同一个数 */
27
+ --xh-_scrollbar-thickness: var(--xh-scrollbar-thickness-md);
28
+
29
+ position: absolute;
30
+ /* touch-action 关掉浏览器自己的手势:不关的话手指一按滑块页面就跟着滚,
31
+ 指针随即被系统收走(pointercancel),拖到一半的滑块停在原地。
32
+ user-select 同理——拖动时选中页面文字会让滑块"粘"住。 */
33
+ touch-action: none;
34
+ user-select: none;
35
+ /* 露出与收起都淡变;收起态连 visibility 一起收,退场播完才真正摸不到、也进不了 Tab 序 */
36
+ transition:
37
+ opacity var(--xh-motion-duration-enter) var(--xh-motion-ease-enter),
38
+ visibility 0s linear 0s;
39
+ }
40
+
41
+ [data-scope='scrollbar'][data-part='root'][data-size='sm'] {
42
+ --xh-_scrollbar-thickness: var(--xh-scrollbar-thickness-sm);
43
+ }
44
+
45
+ [data-scope='scrollbar'][data-part='root'][data-size='lg'] {
46
+ --xh-_scrollbar-thickness: var(--xh-scrollbar-thickness-lg);
47
+ }
48
+
49
+ [data-scope='scrollbar'][data-part='root'][data-orientation='vertical'] {
50
+ inset-block: 0;
51
+ inset-inline-end: 0;
52
+ inline-size: var(--xh-_scrollbar-thickness);
53
+ }
54
+
55
+ [data-scope='scrollbar'][data-part='root'][data-orientation='horizontal'] {
56
+ inset-inline: 0;
57
+ inset-block-end: 0;
58
+ block-size: var(--xh-_scrollbar-thickness);
59
+ }
60
+
61
+ /* 横竖两条同时摆着:各自在末端让出交叉口那一格,交叉口由 corner 补 */
62
+ [data-scope='scrollbar'][data-part='root'][data-orientation='vertical'][data-gutter] {
63
+ inset-block-end: var(--xh-_scrollbar-thickness);
64
+ }
65
+
66
+ [data-scope='scrollbar'][data-part='root'][data-orientation='horizontal'][data-gutter] {
67
+ inset-inline-end: var(--xh-_scrollbar-thickness);
68
+ }
69
+
70
+ [data-scope='scrollbar'][data-part='root'][data-state='hidden'] {
71
+ visibility: hidden;
72
+ opacity: 0;
73
+ pointer-events: none;
74
+ transition:
75
+ opacity var(--xh-motion-duration-exit) var(--xh-motion-ease-exit),
76
+ visibility 0s linear var(--xh-motion-duration-exit);
77
+ }
78
+
79
+ /* 触屏上交给原生滚动:整条不占位也不画 */
80
+ [data-scope='scrollbar'][data-part='root'][data-native] {
81
+ display: none;
82
+ }
83
+
84
+ /* 轨道铺满根,滑块的行程按它量。内边距把滑块从两侧收进来一点,
85
+ 与 scroll-area 那份共用同一个间隙令牌 */
86
+ [data-scope='scrollbar'][data-part='track'] {
87
+ position: relative;
88
+ inline-size: 100%;
89
+ block-size: 100%;
90
+ padding: var(--xh-scrollbar-gutter);
91
+ background: var(--xh-scrollbar-track-bg, var(--xh-bg-scrollbar-track));
92
+ }
93
+
94
+ /* 滑块。主轴(起点 + 长度)来自内联样式,这里只给定位模式、交叉轴与配色。
95
+ 绝不给 inset / size 加过渡:滚动时这两个值每帧都在变,一加过渡滑块就永远追在内容后面。
96
+ 也刻意不写 min-block-size / min-inline-size:滑块的最短长度由几何层折进内联尺寸里,
97
+ 样式表再给一个下限就成了两个事实源,两者一旦不一致,滑块的实际长度与它能走的距离就对不上。 */
98
+ [data-scope='scrollbar'][data-part='thumb'] {
99
+ position: absolute;
100
+ border-radius: var(--xh-scrollbar-thumb-radius, var(--xh-shape-pill));
101
+ background: var(--xh-scrollbar-thumb-bg, var(--xh-fg-scrollbar-thumb));
102
+ cursor: default;
103
+ transition: background var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
104
+ }
105
+
106
+ /* 交叉轴在轨道内两侧各收进一个间隙,与轨道那条 padding 共用同一个旋钮。
107
+ 这一侧的 inset 连接层每帧写成空串(即不设),样式表接手才有确定值;
108
+ 不写的话绝对定位会退回"静态位置"。
109
+ 主轴那一侧一律不写:几何层量的轨道是 padding box,滑块长度也解析在同一个盒子上。 */
110
+ [data-scope='scrollbar'][data-part='thumb'][data-orientation='vertical'] {
111
+ inset-inline: var(--xh-scrollbar-gutter);
112
+ }
113
+
114
+ [data-scope='scrollbar'][data-part='thumb'][data-orientation='horizontal'] {
115
+ inset-block: var(--xh-scrollbar-gutter);
116
+ }
117
+
118
+ [data-scope='scrollbar'][data-part='thumb']:hover {
119
+ background: var(--xh-scrollbar-thumb-bg-hover, var(--xh-fg-scrollbar-thumb-hover));
120
+ }
121
+
122
+ [data-scope='scrollbar'][data-part='thumb'][data-dragging] {
123
+ background: var(--xh-scrollbar-thumb-bg-active, var(--xh-fg-scrollbar-thumb-active));
124
+ }
125
+
126
+ /* 只有开了 focusable 的滑块进得了 Tab 序,环也就只画在它身上。
127
+ 偏移取负:环画在轨道内侧,否则会被滚动容器的溢出边界裁掉半圈 */
128
+ [data-scope='scrollbar'][data-part='thumb']:focus-visible {
129
+ outline: var(--xh-ring-width) solid var(--xh-ring-focus);
130
+ outline-offset: calc(-1 * var(--xh-ring-width));
131
+ }
132
+
133
+ /* 根与轨道都是纯容器,不承载焦点;作者若给它们加了 tabindex,也不该在这儿多画一圈 */
134
+ [data-scope='scrollbar'][data-part='root']:focus-visible,
135
+ [data-scope='scrollbar'][data-part='track']:focus-visible {
136
+ outline: none;
137
+ }
138
+
139
+ /* 禁用:整条不显形,这里只兜住作者硬把它显出来的那一路 */
140
+ [data-scope='scrollbar'][data-part='thumb'][data-disabled] {
141
+ background: var(--xh-scrollbar-thumb-bg-disabled, var(--xh-border-subtle));
142
+ cursor: not-allowed;
143
+ }
144
+
145
+ /* 交叉口补丁:贴在本条末端之外的那一格,与轨道同色,跟着本条淡入淡出 */
146
+ [data-scope='scrollbar'][data-part='corner'] {
147
+ position: absolute;
148
+ inline-size: var(--xh-_scrollbar-thickness);
149
+ block-size: var(--xh-_scrollbar-thickness);
150
+ background: var(--xh-scrollbar-corner-bg, var(--xh-scrollbar-track-bg, var(--xh-bg-scrollbar-track)));
151
+ }
152
+
153
+ [data-scope='scrollbar'][data-part='corner'][data-orientation='vertical'] {
154
+ inset-inline-start: 0;
155
+ inset-block-end: calc(-1 * var(--xh-_scrollbar-thickness));
156
+ }
157
+
158
+ [data-scope='scrollbar'][data-part='corner'][data-orientation='horizontal'] {
159
+ inset-block-start: 0;
160
+ inset-inline-end: calc(-1 * var(--xh-_scrollbar-thickness));
161
+ }
162
+
163
+ [data-scope='scrollbar'][data-part='root'][hidden],
164
+ [data-scope='scrollbar'][data-part='corner'][hidden] {
165
+ display: none;
166
+ }
167
+ }