@xihan-ui/styles 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/CHANGELOG.md +5146 -6
  2. package/README.md +22 -2
  3. package/component-tokens.d.ts +4212 -0
  4. package/component-tokens.js +2 -0
  5. package/components.tokens.json +80116 -0
  6. package/css/accordion.css +131 -75
  7. package/css/affix.css +16 -1
  8. package/css/alert.css +114 -67
  9. package/css/anchor.css +99 -27
  10. package/css/approval.css +461 -0
  11. package/css/avatar-group.css +14 -6
  12. package/css/avatar.css +33 -9
  13. package/css/back-top.css +103 -48
  14. package/css/badge.css +42 -14
  15. package/css/bar-code.css +62 -0
  16. package/css/breadcrumb.css +40 -21
  17. package/css/button-group.css +146 -24
  18. package/css/button.css +116 -99
  19. package/css/calendar-picker.css +448 -0
  20. package/css/calendar-range-picker.css +517 -0
  21. package/css/card.css +54 -77
  22. package/css/carousel.css +412 -68
  23. package/css/cascader.css +429 -246
  24. package/css/checkbox-group.css +225 -77
  25. package/css/checkbox.css +174 -37
  26. package/css/clipboard.css +266 -47
  27. package/css/code-view.css +279 -0
  28. package/css/collapsible.css +108 -63
  29. package/css/color-field.css +158 -0
  30. package/css/color-picker.css +172 -184
  31. package/css/color-slider.css +274 -0
  32. package/css/color-swatch-picker.css +243 -0
  33. package/css/color-swatch.css +35 -0
  34. package/css/combobox.css +267 -273
  35. package/css/command.css +399 -0
  36. package/css/context-menu.css +183 -108
  37. package/css/date-field.css +94 -121
  38. package/css/date-picker.css +486 -249
  39. package/css/date-range-picker.css +559 -0
  40. package/css/description.css +51 -0
  41. package/css/descriptions.css +133 -87
  42. package/css/dialog.css +247 -68
  43. package/css/diff-view.css +428 -0
  44. package/css/download-trigger.css +155 -46
  45. package/css/drawer.css +194 -111
  46. package/css/editable.css +172 -152
  47. package/css/empty-state.css +80 -11
  48. package/css/field-array.css +190 -0
  49. package/css/field.css +84 -31
  50. package/css/fieldset.css +51 -18
  51. package/css/file-upload.css +134 -36
  52. package/css/flex.css +20 -1
  53. package/css/float-button.css +238 -57
  54. package/css/floating-panel.css +173 -92
  55. package/css/focus.css +46 -0
  56. package/css/forced-colors.css +93 -0
  57. package/css/form.css +254 -77
  58. package/css/gradient-text.css +34 -10
  59. package/css/grid.css +516 -5
  60. package/css/heatmap.css +87 -29
  61. package/css/highlight.css +20 -5
  62. package/css/hover-card.css +97 -47
  63. package/css/icon-wrapper.css +15 -3
  64. package/css/icon.css +74 -8
  65. package/css/image-cropper.css +143 -29
  66. package/css/image-viewer.css +150 -89
  67. package/css/image.css +30 -1
  68. package/css/infinite-scroll.css +51 -1
  69. package/css/input-group.css +277 -0
  70. package/css/json-viewer.css +102 -45
  71. package/css/kbd.css +72 -0
  72. package/css/label.css +78 -0
  73. package/css/layers.css +5 -0
  74. package/css/layout.css +175 -31
  75. package/css/list.css +24 -3
  76. package/css/listbox.css +208 -67
  77. package/css/loading-bar.css +52 -3
  78. package/css/log.css +144 -9
  79. package/css/markdown-stream.css +149 -0
  80. package/css/marquee.css +27 -1
  81. package/css/masonry.css +7 -0
  82. package/css/matrix-code.css +67 -0
  83. package/css/mention.css +234 -152
  84. package/css/menu.css +180 -83
  85. package/css/menubar.css +204 -136
  86. package/css/message-feed.css +197 -0
  87. package/css/navigation-menu.css +144 -90
  88. package/css/notification.css +178 -100
  89. package/css/number-animation.css +7 -0
  90. package/css/number-field.css +120 -277
  91. package/css/overlay-arrow.css +12 -7
  92. package/css/page-header.css +99 -14
  93. package/css/pagination.css +175 -135
  94. package/css/password-input.css +222 -219
  95. package/css/pin-input.css +101 -67
  96. package/css/pointer.css +36 -0
  97. package/css/popconfirm.css +185 -94
  98. package/css/popover.css +121 -77
  99. package/css/progress.css +60 -12
  100. package/css/prompt-input.css +224 -0
  101. package/css/question-flow.css +513 -0
  102. package/css/radio-group.css +147 -30
  103. package/css/rating.css +193 -43
  104. package/css/reasoning.css +336 -0
  105. package/css/reset.css +84 -6
  106. package/css/resizable.css +237 -0
  107. package/css/scroll-area.css +68 -7
  108. package/css/scrollbar.css +47 -13
  109. package/css/segmented.css +94 -29
  110. package/css/select.css +288 -274
  111. package/css/separator.css +101 -1
  112. package/css/side-nav.css +133 -75
  113. package/css/signature-pad.css +74 -54
  114. package/css/skeleton.css +84 -11
  115. package/css/slider.css +132 -39
  116. package/css/sortable.css +198 -0
  117. package/css/spinner.css +126 -12
  118. package/css/splitter.css +29 -8
  119. package/css/statistic.css +55 -0
  120. package/css/steps.css +206 -72
  121. package/css/switch.css +268 -36
  122. package/css/table.css +695 -118
  123. package/css/tabs.css +604 -42
  124. package/css/tag-group.css +219 -0
  125. package/css/tag.css +118 -28
  126. package/css/tags-input.css +156 -274
  127. package/css/text-field.css +101 -249
  128. package/css/time-field.css +100 -128
  129. package/css/time-picker.css +323 -252
  130. package/css/time-range-picker.css +719 -0
  131. package/css/timeline.css +202 -68
  132. package/css/timer.css +56 -52
  133. package/css/timestamp.css +34 -0
  134. package/css/toast.css +373 -144
  135. package/css/toggle-group.css +309 -76
  136. package/css/toggle.css +116 -85
  137. package/css/tone.css +47 -0
  138. package/css/tool-call.css +421 -0
  139. package/css/toolbar.css +181 -33
  140. package/css/tooltip.css +107 -36
  141. package/css/tour.css +183 -152
  142. package/css/transfer.css +351 -138
  143. package/css/tree-select.css +323 -216
  144. package/css/tree.css +294 -62
  145. package/css/truncate.css +65 -0
  146. package/css/typography.css +228 -5
  147. package/css/undefined.css +10 -1
  148. package/css/virtualizer.css +9 -10
  149. package/css/visually-hidden.css +7 -0
  150. package/css/watermark.css +7 -0
  151. package/family/action-control.css +487 -0
  152. package/family/collection-item.css +721 -0
  153. package/family/field-chrome.css +298 -0
  154. package/family/motion.css +166 -0
  155. package/family/swatch.css +59 -0
  156. package/index.css +41705 -126
  157. package/index.unlayered.css +33692 -17339
  158. package/package.json +51 -17
  159. package/css/calendar.css +0 -367
  160. package/css/code-block.css +0 -91
  161. package/css/composer.css +0 -134
  162. package/css/countdown.css +0 -26
  163. package/css/dynamic-input.css +0 -220
  164. package/css/ellipsis.css +0 -52
  165. package/css/hotkeys.css +0 -90
  166. package/css/popselect.css +0 -391
  167. package/css/qr-code.css +0 -55
  168. package/css/result.css +0 -119
  169. package/css/space.css +0 -117
  170. package/css/thread.css +0 -120
  171. package/css/time.css +0 -25
package/css/tabs.css CHANGED
@@ -1,3 +1,15 @@
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
+ /* 定义 tabs 样式。 */
7
+
8
+ /* line 档的页签归 Collection Item 导航当前(nav 语境):面、字色、字重、光标与按压时间线由家族配方按
9
+ 家族角色(data-xh-collection-item / -size / -context)给,本皮肤只映射桥接槽;card / segment 档不归族,面与字仍写在本皮肤。
10
+ line 档的 2px 指示线由本皮肤画:标签带里放了 indicator 部件就由部件滑动,没放则选中标签自画一条静态线 */
11
+ @import '../family/collection-item.css';
12
+
1
13
  @layer xihan.components {
2
14
  /* 皮肤在场的标记:开发模式下按 scope 探一次,取不到就说明这份皮肤没被引入。 */
3
15
  [data-scope='tabs'] {
@@ -10,28 +22,36 @@
10
22
  --xh-_tabs-trigger-px: var(--xh-control-px-md);
11
23
  --xh-_tabs-trigger-font-size: var(--xh-control-font-md);
12
24
 
13
- /* 形态档只换这几个私有槽:列表的底、内边距、基线,与标签的描边、选中底、选中态那道指示。
14
- 缺省档(line)是一条基线加一行标签,选中只换文字色——这里的取值即现状 */
15
- --xh-_tabs-list-gap: var(--xh-control-gap-md);
25
+ /* 缺省是 line 变体:透明标签带,选中项由文字与底部指示条表达。Web Components 升级前还没有
26
+ data-variant,这组几何基础值也必须画成相同结果,避免定义完成时闪换样式;line 档的面与字色由家族
27
+ 按升级后写入的家族角色给,升级前那一帧只有几何没有面色(与 side-nav / menu 同)。
28
+ 悬停与按下的面按承载面分档(§7.2):line 档经家族走白底承载 hover 100 → pressed 200;card 坐在
29
+ 画布上 hover 100 → pressed 200;segment 坐在淡底轨道里 hover 200 → pressed 300。card / segment 的
30
+ 六支面槽只在各自形态块里赋值,line 档不消费它们 */
31
+ --xh-_tabs-list-gap: var(--xh-space-0);
16
32
  --xh-_tabs-list-bg: transparent;
17
33
  --xh-_tabs-list-p: var(--xh-space-0);
18
34
  --xh-_tabs-list-radius: 0;
19
- --xh-_tabs-list-border-width: var(--xh-stroke-thin);
35
+ --xh-_tabs-list-border-width: 0;
20
36
  --xh-_tabs-trigger-border-width: 0;
21
37
  --xh-_tabs-trigger-border: transparent;
22
- --xh-_tabs-trigger-bg-active: transparent;
23
- --xh-_tabs-trigger-bg-active-hover: var(--xh-bg-subtle-hover);
24
- --xh-_tabs-trigger-border-active: transparent;
25
- --xh-_tabs-trigger-shadow-active: none;
38
+ --xh-_tabs-trigger-radius: var(--xh-shape-control);
26
39
 
27
40
  /* 语气落在选中态:指示条与选中标签的文字。
28
41
  没写 data-tone 时 --xh-_tone 一族没被声明,两个槽都退回原来的取色 */
29
42
  --xh-_tabs-accent: var(--xh-_tone, var(--xh-bg-brand));
30
- --xh-_tabs-accent-text: var(--xh-_tone-fg, var(--xh-fg-default));
43
+ --xh-_tabs-accent-text: var(--xh-_tone-fg, var(--xh-fg-brand-strong));
44
+
45
+ /* 翻页钮的盖底:标签带放不下时它浮在两端,挪到它底下的那截标签要被盖住。line / card 档的标签带是透明的,
46
+ 盖底取 surface;坐在别的面上(画布、淡底面板)时由使用者槽换成所在的面。segment 档取轨道的淡底 */
47
+ --xh-_tabs-scroll-trigger-bg: var(--xh-bg-surface);
31
48
 
32
49
  display: flex;
33
50
  flex-direction: column;
34
51
  gap: var(--xh-tabs-gap, var(--xh-stack-gap-md));
52
+ /* 排在一行弹性 / 网格容器里时也让得开:不写这条,根的最小宽是整条标签带的自然宽,
53
+ 外层再窄它也不缩,标签带永远"放得下"、翻页钮永远不露面,整行反倒被顶出去 */
54
+ min-inline-size: 0;
35
55
  }
36
56
 
37
57
  /* 纵向:list 与 panel 并排 */
@@ -53,35 +73,74 @@
53
73
  --xh-_tabs-trigger-font-size: var(--xh-control-font-lg);
54
74
  }
55
75
 
56
- /* —— 形态 —— */
57
- /* 形态只决定选中态怎么画;用哪族颜色由 data-tone 决定。
58
- line 档不在这儿:它就是上面那套基础取值,写不写 data-variant 都长一个样 */
76
+ /* —— 变体 —— */
77
+ /* 主变体的显式写法,与缺省视觉一致:透明标签带与底部基线,选中项由文字和滑动指示条强调。 */
78
+ [data-scope='tabs'][data-part='root'][data-variant='line'] {
79
+ --xh-_tabs-list-gap: var(--xh-space-0);
80
+ --xh-_tabs-list-bg: transparent;
81
+ --xh-_tabs-list-p: var(--xh-space-0);
82
+ --xh-_tabs-list-radius: 0;
83
+ --xh-_tabs-list-border-width: 0;
84
+ --xh-_tabs-trigger-border-width: 0;
85
+ --xh-_tabs-trigger-border: transparent;
86
+ --xh-_tabs-trigger-radius: var(--xh-shape-control);
87
+ }
59
88
 
60
- /* 卡片式:基线收掉,选中的那张标签自己成为一张有描边有底的卡片,顶边一条指示条 */
89
+ /* 卡片式:基线收掉,选中的那张标签自己成为一张有描边有底的卡片,顶边一条指示条。
90
+ 标签坐在画布上,悬停 100 → 按下 200(§7.2 白底承载) */
61
91
  [data-scope='tabs'][data-part='root'][data-variant='card'] {
92
+ --xh-_tabs-list-gap: var(--xh-control-gap-md);
93
+ --xh-_tabs-list-bg: transparent;
94
+ --xh-_tabs-list-p: var(--xh-space-0);
95
+ --xh-_tabs-list-radius: 0;
62
96
  --xh-_tabs-list-border-width: 0;
63
97
  --xh-_tabs-trigger-border-width: var(--xh-stroke-thin);
98
+ --xh-_tabs-trigger-radius: var(--xh-shape-control);
99
+ --xh-_tabs-trigger-bg-hover: var(--xh-bg-subtle);
100
+ --xh-_tabs-trigger-bg-pressed: var(--xh-bg-subtle-hover);
64
101
  --xh-_tabs-trigger-bg-active: var(--xh-_tone-subtle, var(--xh-bg-surface));
65
102
  --xh-_tabs-trigger-bg-active-hover: var(--xh-_tone-subtle-hover, var(--xh-bg-surface));
66
103
  --xh-_tabs-trigger-border-active: var(--xh-_tone-border, var(--xh-border-default));
67
104
  --xh-_tabs-trigger-shadow-active: inset 0 var(--xh-stroke-thick) 0 0 var(--xh-_tabs-accent);
105
+ /* 选中文字不换品牌色:卡片面与顶边条已经表达选中 */
106
+ --xh-_tabs-accent-text: var(--xh-_tone-fg, var(--xh-fg-default));
68
107
  }
69
108
 
70
- /* 分段控件式:整条列表是一块底,选中的标签是压在这块底上浮起的一片,没有基线也没有指示条 */
109
+ /* 分段式(§7.3 有滑块开关):淡底轨道承载一块白色抬起的选中面,选中文字保持默认字色。
110
+ 未选中标签坐在淡底轨道里,悬停 200 → 按下 300;选中面自己不再叠悬停 / 按下态。
111
+ 选中面的三件(surface-raised 底、border-default 描边、raised 影)写在下面标签自己的规则里 */
71
112
  [data-scope='tabs'][data-part='root'][data-variant='segment'] {
72
113
  --xh-_tabs-list-gap: var(--xh-space-0_5);
73
114
  --xh-_tabs-list-bg: var(--xh-bg-subtle);
74
115
  --xh-_tabs-list-p: var(--xh-space-1);
75
- --xh-_tabs-list-radius: var(--xh-shape-control);
116
+ --xh-_tabs-list-radius: var(--xh-shape-surface);
76
117
  --xh-_tabs-list-border-width: 0;
77
- --xh-_tabs-trigger-bg-active: var(--xh-_tone-subtle, var(--xh-bg-surface));
78
- --xh-_tabs-trigger-bg-active-hover: var(--xh-_tone-subtle-hover, var(--xh-bg-surface));
79
- --xh-_tabs-trigger-shadow-active: var(--xh-elevation-raised);
118
+ --xh-_tabs-trigger-border-width: var(--xh-stroke-thin);
119
+ --xh-_tabs-trigger-border: transparent;
120
+ --xh-_tabs-trigger-radius: var(--xh-shape-control);
121
+ --xh-_tabs-trigger-bg-hover: var(--xh-bg-subtle-hover);
122
+ --xh-_tabs-trigger-bg-pressed: var(--xh-bg-subtle-active);
123
+ --xh-_tabs-trigger-bg-active: var(--xh-_tone-subtle, var(--xh-bg-surface-raised));
124
+ --xh-_tabs-trigger-bg-active-hover: var(--xh-_tone-subtle, var(--xh-bg-surface-raised));
125
+ --xh-_tabs-trigger-border-active: var(--xh-_tone-border, var(--xh-border-default));
126
+ --xh-_tabs-accent-text: var(--xh-_tone-fg, var(--xh-fg-default));
127
+ --xh-_tabs-scroll-trigger-bg: var(--xh-bg-subtle);
80
128
  }
81
129
 
130
+ /* 一行排不下不折行:标签整条沿主轴位移露出被裁掉的那截,两端的翻页钮与滚轮驱动,位移量由机器写在
131
+ --xh-_tabs-scroll 上(下面孩子那条规则读它)。只裁主轴(overflow-x: clip),交叉轴仍是 visible:
132
+ 标签的焦点环画在盒内不受影响,粗指针下往块轴两侧外扩的命中区、segment 档抬起面的投影也都不被裁。
133
+ 不做成滚动容器(overflow: hidden 会把两个轴一起裁,粗指针那圈外扩就没了;原生滚动条也不该露在标签带下) */
82
134
  [data-scope='tabs'][data-part='list'] {
135
+ /* 位移量:放得下时 0,放不下时由机器写成内联样式盖过这条 */
136
+ --xh-_tabs-scroll: 0;
137
+
83
138
  position: relative;
139
+ /* 关起标签带内的层序:segment 档的标签抬一层压在滑动的抬起面上、翻页钮再抬一层压在标签上,两个层号都不漏到外面去 */
140
+ isolation: isolate;
84
141
  display: flex;
142
+ flex-wrap: nowrap;
143
+ overflow: clip visible;
85
144
  align-items: center;
86
145
  gap: var(--xh-tabs-list-gap, var(--xh-_tabs-list-gap));
87
146
  padding: var(--xh-tabs-list-p, var(--xh-_tabs-list-p));
@@ -90,19 +149,40 @@
90
149
  background: var(--xh-tabs-list-bg, var(--xh-_tabs-list-bg));
91
150
  }
92
151
 
152
+ /* 竖排:主轴换成块向,裁块向、行向放开。竖排的标签带高随内容长,只有使用者限了高才会放不下 */
93
153
  [data-scope='tabs'][data-part='list'][aria-orientation='vertical'] {
94
154
  flex-direction: column;
95
155
  align-items: stretch;
156
+ overflow: visible clip;
96
157
  border-block-end: 0;
97
158
  border-inline-end: var(--xh-_tabs-list-border-width) solid var(--xh-tabs-list-border, var(--xh-border-default));
98
159
  }
99
160
 
100
- /* 组内无锚点时 list 自己在 Tab 序列里,落焦要看得见 */
101
- [data-scope='tabs'][data-part='list']:focus-visible {
102
- outline: var(--xh-ring-width) solid var(--xh-ring-focus);
103
- outline-offset: var(--xh-ring-offset);
161
+ /* 排在标签带里的孩子——标签、分隔线、指示条——整体沿主轴位移;两端的翻页钮贴边不动,不在这条里。
162
+ 位移量是机器逐帧补间写进来的(屏内的像素级推移走 continuous 档,与指示条同一档),这里不接过渡:
163
+ 各部件各有自己的过渡清单(line 档的标签归家族),往每一份里都加一条 translate 会把家族与皮肤耦在一起 */
164
+ [data-scope='tabs'][data-part='list']
165
+ > :not([data-scope='tabs'][data-part='prev-trigger'], [data-scope='tabs'][data-part='next-trigger']) {
166
+ translate: var(--xh-_tabs-scroll) 0;
167
+ }
168
+
169
+ [data-scope='tabs'][data-part='list'][aria-orientation='vertical']
170
+ > :not([data-scope='tabs'][data-part='prev-trigger'], [data-scope='tabs'][data-part='next-trigger']) {
171
+ translate: 0 var(--xh-_tabs-scroll);
104
172
  }
105
173
 
174
+ /* segment 档的标签带是一块淡底面:--xh-bg-subtle 底 + 一圈透明占位边 + 无影(§8.3 三选一)。
175
+ 它同时是标签的淡底承载面,向内下发 hover 200 → pressed 300 的承载阶梯(§7.2);
176
+ 只写在标签带上不写在 root 上:内容面板里的按钮坐在画布上,不该跟着抬一档 */
177
+ [data-scope='tabs'][data-part='root'][data-variant='segment'] [data-scope='tabs'][data-part='list'] {
178
+ --xh-action-host-bg-hover: var(--xh-bg-subtle-hover);
179
+ --xh-action-host-bg-pressed: var(--xh-bg-subtle-active);
180
+
181
+ border: var(--xh-stroke-thin) solid var(--xh-tabs-list-border, transparent);
182
+ }
183
+
184
+ /* 标签的几何三档共用;面、字色、字重与光标不在这里写:line 档由家族按投影的家族角色给,
185
+ card / segment 档写在下面带形态限定的规则里。本条 (0,2,0) 压过家族基础块 (0,1,0),写了面就会盖掉家族的换面 */
106
186
  [data-scope='tabs'][data-part='trigger'] {
107
187
  position: relative;
108
188
  display: inline-flex;
@@ -112,42 +192,125 @@
112
192
  block-size: var(--xh-tabs-trigger-h, var(--xh-_tabs-trigger-h));
113
193
  padding-inline: var(--xh-tabs-trigger-px, var(--xh-_tabs-trigger-px));
114
194
  border: var(--xh-_tabs-trigger-border-width) solid var(--xh-tabs-trigger-border, var(--xh-_tabs-trigger-border));
115
- border-radius: var(--xh-tabs-trigger-radius, var(--xh-shape-control));
116
- background: var(--xh-tabs-trigger-bg, transparent);
117
- color: var(--xh-tabs-trigger-fg, var(--xh-fg-muted));
195
+ border-radius: var(--xh-tabs-trigger-radius, var(--xh-_tabs-trigger-radius));
118
196
  font-size: var(--xh-tabs-trigger-font-size, var(--xh-_tabs-trigger-font-size));
119
- font-weight: var(--xh-tabs-trigger-font-weight, var(--xh-text-label-weight));
120
197
  line-height: var(--xh-leading-none);
121
198
  white-space: nowrap;
199
+ /* 家族基础块给条目写了 min-inline-size: 0(菜单条目要能收窄给文字省略号用);标签不省略号,
200
+ segment 档又按 flex: 1 1 0 等分带宽,下限归零后排不下的标签会被压到文字之下。
201
+ 收回 auto:下限按文字算,排不下的那截由标签带位移露出 */
202
+ min-inline-size: auto;
203
+ }
204
+
205
+ /* line 档(§7.3 导航当前页):把公开槽映射到 Collection Item 的 nav 语境桥接槽。静息 muted / regular,
206
+ 悬停 100 + default 字,按下 200 只换面;当前页透明面 + 品牌深字(随语气的 --xh-_tabs-accent-text)+
207
+ medium,叠悬停 100(§9.3)。2px 指示线不由家族按条目画:放了 indicator 部件是 list 上的滑动部件,
208
+ 没放则由下面的静态线规则画在选中标签自己身上。高度由 block-size 定,块向内边距归零 */
209
+ [data-scope='tabs'][data-part='root'][data-variant='line'] [data-scope='tabs'][data-part='trigger'] {
210
+ --xh-collection-block-padding: var(--xh-space-0);
211
+ --xh-collection-bg-rest: var(--xh-tabs-trigger-bg, transparent);
212
+ --xh-collection-fg-rest: var(--xh-tabs-trigger-fg, var(--xh-fg-muted));
213
+ --xh-collection-font-weight-rest: var(--xh-tabs-trigger-font-weight, var(--xh-font-weight-regular));
214
+ --xh-collection-bg-hover: var(--xh-tabs-trigger-bg-hover, var(--xh-bg-subtle));
215
+ --xh-collection-fg-hover: var(--xh-tabs-trigger-fg-hover, var(--xh-fg-default));
216
+ --xh-collection-bg-pressed: var(--xh-tabs-trigger-bg-pressed, var(--xh-bg-subtle-hover));
217
+ --xh-collection-fg-pressed: var(--xh-tabs-trigger-fg-pressed, var(--xh-fg-default));
218
+ --xh-collection-bg-current: var(--xh-tabs-trigger-bg-active, transparent);
219
+ --xh-collection-bg-current-hover: var(--xh-tabs-trigger-bg-active-hover, var(--xh-bg-subtle));
220
+ --xh-collection-fg-current: var(--xh-tabs-trigger-fg-active, var(--xh-_tabs-accent-text));
221
+ --xh-collection-font-weight-current: var(--xh-tabs-trigger-font-weight-active, var(--xh-font-weight-medium));
222
+ }
223
+
224
+ /* card / segment 档不归族:面、字色、字重、光标与过渡写在标签自己身上 */
225
+ [data-scope='tabs'][data-part='root']:is([data-variant='card'], [data-variant='segment'])
226
+ [data-scope='tabs'][data-part='trigger'] {
227
+ background: var(--xh-tabs-trigger-bg, transparent);
228
+ color: var(--xh-tabs-trigger-fg, var(--xh-fg-muted));
229
+ font-weight: var(--xh-tabs-trigger-font-weight, var(--xh-text-label-weight));
122
230
  cursor: pointer;
123
- transition: color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
231
+ /* 标签是铺开的一段(§9.2 Tabs trigger 归行级),按下只换面不缩放:transition 里没有 scale */
232
+ transition:
233
+ color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter),
234
+ background-color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter),
235
+ box-shadow var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
236
+ }
237
+
238
+ /* 横排标签的宽随文字走,不把标签带等分铺满一行:line 与 card 档是一排并列的页签,
239
+ 每一枚只占自己的字宽加内衬,标签带右侧留白;放不下的那截由标签带位移露出 */
240
+ [data-scope='tabs'][data-part='root'][data-orientation='horizontal']
241
+ [data-scope='tabs'][data-part='trigger'] {
242
+ flex: 0 0 auto;
243
+ }
244
+
245
+ /* segment 档是一条轨道里的格子(与 Segmented 同构):格子按可用宽度等分 */
246
+ [data-scope='tabs'][data-part='root'][data-orientation='horizontal'][data-variant='segment']
247
+ [data-scope='tabs'][data-part='trigger'] {
248
+ flex: 1 1 0;
124
249
  }
125
250
 
126
- [data-scope='tabs'][data-part='trigger']:hover {
127
- background: var(--xh-tabs-trigger-bg-hover, var(--xh-bg-subtle-hover));
251
+ /* 禁用项带的是 aria-disabled 而非原生 disabled,:hover 照样命中;
252
+ 不挡掉的话置灰的那一页悬停仍会换底,看着还能点。
253
+ 悬停面按形态分档:card 100、segment 200(见各形态块上的私有槽);line 档由家族给 */
254
+ [data-scope='tabs'][data-part='root']:is([data-variant='card'], [data-variant='segment'])
255
+ [data-scope='tabs'][data-part='trigger']:not([data-disabled]):hover {
256
+ background: var(--xh-tabs-trigger-bg-hover, var(--xh-_tabs-trigger-bg-hover));
128
257
  }
129
258
 
130
- [data-scope='tabs'][data-part='trigger']:focus-visible {
131
- outline: var(--xh-ring-width) solid var(--xh-ring-focus);
132
- outline-offset: var(--xh-ring-offset);
259
+ /* 按下去要有回应:card / segment 档的未选中标签换到按下面(card 200、segment 300),几何不动。
260
+ 选中标签不叠按下面:card 的卡片面与 segment 的抬起面本身就是选中身份(§7.3 有滑块开关无叠加态) */
261
+ [data-scope='tabs'][data-part='root']:is([data-variant='card'], [data-variant='segment'])
262
+ [data-scope='tabs'][data-part='trigger']:not([data-disabled]):not([data-state='active']):is(:active, [data-pressed]) {
263
+ background: var(--xh-tabs-trigger-bg-pressed, var(--xh-_tabs-trigger-bg-pressed));
264
+ transition-duration: var(--xh-motion-duration-press);
265
+ transition-timing-function: var(--xh-motion-ease-press);
133
266
  }
134
267
 
135
- [data-scope='tabs'][data-part='trigger'][data-state='active'] {
268
+ /* card / segment 档的选中标签:字色收回默认(--xh-_tabs-accent-text 在两档都落 fg-default),
269
+ 由卡片面 / 抬起面表达当前;line 档的当前页由家族按 data-current 给 */
270
+ [data-scope='tabs'][data-part='root']:is([data-variant='card'], [data-variant='segment'])
271
+ [data-scope='tabs'][data-part='trigger'][data-state='active'] {
136
272
  border-color: var(--xh-tabs-trigger-border-active, var(--xh-_tabs-trigger-border-active));
137
273
  background: var(--xh-tabs-trigger-bg-active, var(--xh-_tabs-trigger-bg-active));
138
274
  color: var(--xh-tabs-trigger-fg-active, var(--xh-_tabs-accent-text));
139
275
  box-shadow: var(--xh-tabs-trigger-shadow-active, var(--xh-_tabs-trigger-shadow-active));
276
+ font-weight: var(--xh-tabs-trigger-font-weight-active, var(--xh-font-weight-medium));
140
277
  }
141
278
 
142
- /* 选中项被悬停:上面那条与常态 :hover 特异度相同,谁写在后面谁赢,
143
- 不单列一条的话选中项的悬停底会被形态档的选中底吃掉 */
144
- [data-scope='tabs'][data-part='trigger'][data-state='active']:hover {
279
+ /* segment 档的选中标签就是那块白色抬起的滑块面(§7.3 有滑块开关):surface-raised 底 +
280
+ border-default 描边 + raised 影三件写在标签自己身上。标签带里放了 indicator 部件时,
281
+ 这块面搬到部件上跟着滑(见下面的 indicator 规则),标签自己透空、只留字 */
282
+ [data-scope='tabs'][data-part='root'][data-variant='segment']
283
+ [data-scope='tabs'][data-part='trigger'][data-state='active'] {
284
+ border-color: var(--xh-tabs-trigger-border-active, var(--xh-_tone-border, var(--xh-border-default)));
285
+ background: var(--xh-tabs-trigger-bg-active, var(--xh-_tone-subtle, var(--xh-bg-surface-raised)));
286
+ box-shadow: var(--xh-tabs-trigger-shadow-active, var(--xh-elevation-raised));
287
+ }
288
+
289
+ /* segment 档的标签压在抬起面之上:面是绝对定位的部件,写在标签之后也不许盖住字。
290
+ 层号只在标签带的层叠上下文里比大小(list 上有 isolation: isolate) */
291
+ [data-scope='tabs'][data-part='root'][data-variant='segment'] [data-scope='tabs'][data-part='trigger'] {
292
+ z-index: 1;
293
+ }
294
+
295
+ [data-scope='tabs'][data-part='root'][data-variant='segment']
296
+ [data-scope='tabs'][data-part='list']:has([data-scope='tabs'][data-part='indicator']:not([hidden]))
297
+ [data-scope='tabs'][data-part='trigger'][data-state='active'] {
298
+ border-color: transparent;
299
+ background: transparent;
300
+ box-shadow: none;
301
+ }
302
+
303
+ /* 选中项被悬停:常态 :hover 那条带着禁用守卫,特指度高过上面那条选中档,
304
+ 不单列一条的话指针一压到选中项上,形态档的选中底就被轻档吃掉 */
305
+ [data-scope='tabs'][data-part='root']:is([data-variant='card'], [data-variant='segment'])
306
+ [data-scope='tabs'][data-part='trigger']:not([data-disabled])[data-state='active']:hover {
145
307
  background: var(--xh-tabs-trigger-bg-active-hover, var(--xh-_tabs-trigger-bg-active-hover));
146
308
  }
147
309
 
148
- [data-scope='tabs'][data-part='trigger'][data-disabled] {
310
+ /* line 档的禁用字色由家族按 aria-disabled 给 */
311
+ [data-scope='tabs'][data-part='root']:is([data-variant='card'], [data-variant='segment'])
312
+ [data-scope='tabs'][data-part='trigger'][data-disabled] {
149
313
  color: var(--xh-fg-disabled);
150
- cursor: not-allowed;
151
314
  }
152
315
 
153
316
  [data-scope='tabs'][data-part='content'] {
@@ -155,16 +318,415 @@
155
318
  color: var(--xh-tabs-content-fg, var(--xh-fg-default));
156
319
  }
157
320
 
158
- [data-scope='tabs'][data-part='content']:focus-visible {
159
- outline: var(--xh-ring-width) solid var(--xh-ring-focus);
160
- outline-offset: var(--xh-ring-offset);
321
+ /* —— 标签拖拽换位 —— */
322
+ /* 整张标签都是拖动源,光标换成抓手告诉人这张标签按得动。
323
+ 这条不写 touch-action:整张标签关掉手势,标签带自己就滚不动了;
324
+ 触屏那一路交给下面那块专属把手 */
325
+ [data-scope='tabs'][data-part='trigger'][data-draggable] {
326
+ cursor: grab;
327
+ }
328
+
329
+ /* 标签拖拽把手:标签里的一小块专属拖动区,按下即拖。触屏靠它起手,
330
+ 代价只是这一小块地方不再跟着标签带滚。整张标签起手那一路原样保留,把手是叠加的第二个入口。
331
+ touch-action 不在这儿写:连接层按「拖不动就不写」把它写进了内联 style,
332
+ 皮肤再写一遍会盖掉那份条件判断,让不可拖的标签也白白让出滚动 */
333
+ [data-scope='tabs'][data-part='tab-drag-trigger'] {
334
+ display: inline-flex;
335
+ flex: none;
336
+ align-items: center;
337
+ justify-content: center;
338
+ inline-size: var(--xh-tabs-drag-size, var(--xh-control-indicator-size));
339
+ block-size: var(--xh-tabs-drag-size, var(--xh-control-indicator-size));
340
+ border-radius: var(--xh-tabs-drag-radius, var(--xh-shape-control));
341
+ color: var(--xh-tabs-drag-fg, var(--xh-fg-subtle));
342
+ cursor: grab;
343
+ }
344
+
345
+ /* 抓手是两条与排列轴垂直的线:盒子取边框盒,两侧描边就是那两条线,宽高与线宽都走令牌。
346
+ 标签带横排时标签左右排开,画的是两条竖线;竖排那一档在下面换成横线。
347
+ 基础这一档同时兜住 root 上没写方向的情形。
348
+ :empty 守卫让作者塞进来的图标整个顶掉它,而不是两份叠在一起 */
349
+ [data-scope='tabs'][data-part='tab-drag-trigger']:empty::after {
350
+ content: '';
351
+ display: block;
352
+ inline-size: var(--xh-tabs-drag-grip-short, var(--xh-space-1));
353
+ block-size: var(--xh-tabs-drag-grip-long, var(--xh-space-2));
354
+ border-inline: var(--xh-stroke-thin) solid currentColor;
355
+ }
356
+
357
+ /* 竖排的标签上下排开,两条线跟着转九十度:长短对调,描边换到块向两侧,
358
+ 行向那两条得显式清零,否则四边都留着描边,抓手成了一个方框 */
359
+ [data-scope='tabs'][data-part='root'][data-orientation='vertical']
360
+ [data-scope='tabs'][data-part='tab-drag-trigger']:empty::after {
361
+ inline-size: var(--xh-tabs-drag-grip-long, var(--xh-space-2));
362
+ block-size: var(--xh-tabs-drag-grip-short, var(--xh-space-1));
363
+ border-inline: 0;
364
+ border-block: var(--xh-stroke-thin) solid currentColor;
365
+ }
366
+
367
+ [data-scope='tabs'][data-part='tab-drag-trigger']:not([data-disabled]):hover,
368
+ [data-scope='tabs'][data-part='tab-drag-trigger'][data-dragging] {
369
+ color: var(--xh-tabs-drag-fg-active, var(--xh-fg-default));
370
+ }
371
+
372
+ [data-scope='tabs'][data-part='tab-drag-trigger'][data-dragging] {
373
+ cursor: grabbing;
374
+ }
375
+
376
+ /* 不可拖的标签照样带一个把手,这里保住它的位置、只把它压到禁用色。
377
+ 收成 display: none 是不行的:把手占着标签内的头一格,藏掉之后同一条标签带里
378
+ 能拖与不能拖的两张标签,文字起点会错开一格 */
379
+ [data-scope='tabs'][data-part='tab-drag-trigger'][data-disabled] {
380
+ color: var(--xh-tabs-drag-fg-disabled, var(--xh-fg-disabled));
381
+ }
382
+
383
+ /* 被拖的那张标签压暗,让人看得出正在搬的是它。这里只动 opacity、cursor 与 user-select,
384
+ 一个 transform 都不写:标签原地不动,要落到哪儿全交给落点指示 */
385
+ [data-scope='tabs'][data-part='trigger'][data-dragging] {
386
+ opacity: var(--xh-tabs-dragging-opacity, var(--xh-state-dragging-opacity));
387
+ cursor: grabbing;
388
+ user-select: none;
389
+ }
390
+
391
+ /* 落点指示线:插到哪张标签的哪一侧,线就画在那张标签的那条边上。
392
+ 定位上下文不必在这儿再给:标签的基础规则里已经是 position: relative。
393
+ 线走 ::after:选中态那道指示画的是 box-shadow(card 档的顶边条、segment 档的浮起),
394
+ line 档放了部件时指示条在 list 上,没放时选中标签的静态线也占 ::after——它带 :not([data-drop]) 守卫,
395
+ 拖动落到当前页上时让给落点线。标签自身不收 overflow,线不会被裁掉;仍整条画在盒内不压着边 */
396
+ [data-scope='tabs'][data-part='trigger']:is([data-drop='before'], [data-drop='after'])::after {
397
+ content: '';
398
+ position: absolute;
399
+ background: var(--xh-tabs-drop-fg, var(--xh-bg-brand));
400
+ }
401
+
402
+ /* 线的方向跟着标签带的排法换:方向写在 root 的 data-orientation 上,从根往下选。
403
+ 横排的标签左右排开,线是一条竖的,贯穿标签的块向、贴在行向的前后沿 */
404
+ [data-scope='tabs'][data-part='root'][data-orientation='horizontal']
405
+ [data-scope='tabs'][data-part='trigger']:is([data-drop='before'], [data-drop='after'])::after {
406
+ inset-block: 0;
407
+ inline-size: var(--xh-tabs-drop-line, var(--xh-stroke-thick));
408
+ }
409
+
410
+ /* 落在前沿还是后沿走逻辑属性:rtl 下 before 自己就换到右边去了。
411
+ 换成 box-shadow 的偏移量则是物理的,写不出逻辑方向 */
412
+ [data-scope='tabs'][data-part='root'][data-orientation='horizontal']
413
+ [data-scope='tabs'][data-part='trigger'][data-drop='before']::after {
414
+ inset-inline-start: 0;
415
+ }
416
+
417
+ [data-scope='tabs'][data-part='root'][data-orientation='horizontal']
418
+ [data-scope='tabs'][data-part='trigger'][data-drop='after']::after {
419
+ inset-inline-end: 0;
420
+ }
421
+
422
+ /* 竖排的标签上下排开,线是一条横的,贯穿标签的行向、贴在块向的前后沿 */
423
+ [data-scope='tabs'][data-part='root'][data-orientation='vertical']
424
+ [data-scope='tabs'][data-part='trigger']:is([data-drop='before'], [data-drop='after'])::after {
425
+ inset-inline: 0;
426
+ block-size: var(--xh-tabs-drop-line, var(--xh-stroke-thick));
427
+ }
428
+
429
+ /* 块向同样走逻辑属性,跟着书写模式走 */
430
+ [data-scope='tabs'][data-part='root'][data-orientation='vertical']
431
+ [data-scope='tabs'][data-part='trigger'][data-drop='before']::after {
432
+ inset-block-start: 0;
433
+ }
434
+
435
+ [data-scope='tabs'][data-part='root'][data-orientation='vertical']
436
+ [data-scope='tabs'][data-part='trigger'][data-drop='after']::after {
437
+ inset-block-end: 0;
438
+ }
439
+
440
+ /* 活动区域只说给读屏听,不占版面。声明与 shared/visually-hidden.ts 逐条同值。 */
441
+ [data-scope='tabs'][data-part='live-region'] {
442
+ position: absolute;
443
+ inline-size: 1px;
444
+ block-size: 1px;
445
+ padding: 0;
446
+ margin: -1px;
447
+ overflow: hidden;
448
+ clip-path: inset(50%);
449
+ white-space: nowrap;
450
+ border: 0;
451
+ }
452
+
453
+ /* 选中标签的四个几何量由机器量好写成私有槽 --xh-_tabs-indicator-x / -y / -w / -h(它量得到,样式表量不到);
454
+ line 档只取主轴那两支画一条线,这里管交叉轴的贴边与粗细,以及长什么样。它是可选部件——不渲染它时,
455
+ line 档由选中标签自画一条同规格的静态线(见下),card 档由卡片面表达,segment 档由标签自己的抬起面表达 */
456
+ [data-scope='tabs'][data-part='indicator'] {
457
+ /* 还没量到时的落点,量到即被机器写的私有槽盖过 */
458
+ --xh-_tabs-indicator-x: 0;
459
+ --xh-_tabs-indicator-y: 0;
460
+ --xh-_tabs-indicator-w: 0;
461
+ --xh-_tabs-indicator-h: 0;
462
+
463
+ position: absolute;
464
+ inset-block-end: 0;
465
+ inset-inline-start: var(--xh-_tabs-indicator-x);
466
+ inline-size: var(--xh-_tabs-indicator-w);
467
+ block-size: var(--xh-tabs-indicator-thickness, var(--xh-stroke-thick));
468
+ border-radius: var(--xh-tabs-indicator-radius, var(--xh-shape-pill));
469
+ background: var(--xh-tabs-indicator-color, var(--xh-_tabs-accent));
470
+ transition:
471
+ inset-inline-start var(--xh-motion-duration-enter) var(--xh-motion-ease-continuous),
472
+ inline-size var(--xh-motion-duration-enter) var(--xh-motion-ease-continuous);
473
+ }
474
+
475
+ /* 卡片变体已经由卡片面表达状态,不再叠一条下划线。 */
476
+ [data-scope='tabs'][data-part='indicator'][data-variant='card'] {
477
+ display: none;
478
+ }
479
+
480
+ /* segment 档(§7.3 有滑块开关):部件就是那块白色抬起的滑块面,四个几何量都取、整块跟着滑——
481
+ surface-raised 底 + border-default 描边 + raised 影三件与 Segmented 的滑块同一套;
482
+ 描边吃进盒里(border-box),面与标签仍是同一块矩形。标签压在它上面,点击一律交给标签。
483
+ 用连续档而不是进场那条:滑块是被推过去的,起步就该有速度;四个几何量同时走,标签宽不等时才不会先变形再挪位 */
484
+ [data-scope='tabs'][data-part='indicator'][data-variant='segment'] {
485
+ inset-block-end: auto;
486
+ inset-inline-end: auto;
487
+ inset-inline-start: var(--xh-_tabs-indicator-x);
488
+ inset-block-start: var(--xh-_tabs-indicator-y);
489
+ inline-size: var(--xh-_tabs-indicator-w);
490
+ block-size: var(--xh-_tabs-indicator-h);
491
+ box-sizing: border-box;
492
+ border: var(--xh-stroke-thin) solid var(--xh-tabs-trigger-border-active, var(--xh-_tone-border, var(--xh-border-default)));
493
+ border-radius: var(--xh-tabs-trigger-radius, var(--xh-_tabs-trigger-radius));
494
+ background: var(--xh-tabs-trigger-bg-active, var(--xh-_tone-subtle, var(--xh-bg-surface-raised)));
495
+ box-shadow: var(--xh-tabs-trigger-shadow-active, var(--xh-elevation-raised));
496
+ pointer-events: none;
497
+ transition:
498
+ inset-inline-start var(--xh-motion-duration-enter) var(--xh-motion-ease-continuous),
499
+ inset-block-start var(--xh-motion-duration-enter) var(--xh-motion-ease-continuous),
500
+ inline-size var(--xh-motion-duration-enter) var(--xh-motion-ease-continuous),
501
+ block-size var(--xh-motion-duration-enter) var(--xh-motion-ease-continuous);
502
+ }
503
+
504
+ /* line 档没放 indicator 部件时,选中标签自画一条静态指示线(§7.3 导航当前页):
505
+ 横向缺省也要像纵向示例那样有一条选中线,作者不必为了这条线多放一个部件。
506
+ 线画在选中标签的 ::after 上,以标签盒为定位参照(基础规则里已是 position: relative),
507
+ 厚度 / 颜色 / 圆角与 indicator 部件读同一组槽与缺省——作者改一处,部件与静态线一起改。
508
+ 不做动画:它随 data-current 在标签间跳,滑动只有部件做得到。
509
+ 标签带里一旦放了 indicator 部件,这条线就收起、位置交给部件滑动,不重复画两条:
510
+ 由标签带按 :has 探一次,把私有槽写成 none,线的 content 读它;标签自己不必再各带一段 :has。
511
+ ::after 同时是换位落点线的载体:拖动中标签带着 data-drop 时让给落点线,松手即回来 */
512
+ [data-scope='tabs'][data-part='list']:has([data-scope='tabs'][data-part='indicator']) {
513
+ --xh-_tabs-static-line: none;
514
+ }
515
+
516
+ [data-scope='tabs'][data-part='root'][data-variant='line']
517
+ [data-scope='tabs'][data-part='trigger'][data-current]:not([data-drop])::after {
518
+ content: var(--xh-_tabs-static-line, '');
519
+ position: absolute;
520
+ pointer-events: none;
521
+ border-radius: var(--xh-tabs-indicator-radius, var(--xh-shape-pill));
522
+ background: var(--xh-tabs-indicator-color, var(--xh-_tabs-accent));
523
+ }
524
+
525
+ /* 横排:贴标签的块向末端(底边),横贯整枚标签 */
526
+ [data-scope='tabs'][data-part='root'][data-variant='line'][data-orientation='horizontal']
527
+ [data-scope='tabs'][data-part='trigger'][data-current]:not([data-drop])::after {
528
+ inset-block-end: 0;
529
+ inset-inline: 0;
530
+ block-size: var(--xh-tabs-indicator-thickness, var(--xh-stroke-thick));
531
+ }
532
+
533
+ /* 竖排:贴标签的行向末端(与 indicator 部件同一侧,rtl 随逻辑属性镜像),纵贯整枚标签 */
534
+ [data-scope='tabs'][data-part='root'][data-variant='line'][data-orientation='vertical']
535
+ [data-scope='tabs'][data-part='trigger'][data-current]:not([data-drop])::after {
536
+ inset-inline-end: 0;
537
+ inset-block: 0;
538
+ inline-size: var(--xh-tabs-indicator-thickness, var(--xh-stroke-thick));
539
+ }
540
+
541
+ /* 竖排 line 档:滑条从基线挪到结束缘那条轨道上,主轴换成块向,取 -y / -h 两支 */
542
+ [data-scope='tabs'][data-part='indicator'][data-orientation='vertical']:not([data-variant='segment']) {
543
+ inset-block-end: auto;
544
+ inset-inline-start: auto;
545
+ inset-inline-end: 0;
546
+ inset-block-start: var(--xh-_tabs-indicator-y);
547
+ block-size: var(--xh-_tabs-indicator-h);
548
+ inline-size: var(--xh-tabs-indicator-thickness, var(--xh-stroke-thick));
549
+ transition:
550
+ inset-block-start var(--xh-motion-duration-enter) var(--xh-motion-ease-continuous),
551
+ block-size var(--xh-motion-duration-enter) var(--xh-motion-ease-continuous);
552
+ }
553
+
554
+ /* 标签之间的细线:横排时是一条竖线,竖排时躺平 */
555
+ [data-scope='tabs'][data-part='separator'] {
556
+ flex: none;
557
+ align-self: center;
558
+ inline-size: var(--xh-tabs-separator-thickness, var(--xh-stroke-thin));
559
+ block-size: var(--xh-tabs-separator-size, var(--xh-space-4));
560
+ border-radius: var(--xh-tabs-separator-radius, var(--xh-shape-pill));
561
+ background: var(--xh-tabs-separator-color, var(--xh-border-default));
562
+ }
563
+
564
+ [data-scope='tabs'][data-part='separator'][data-orientation='vertical'] {
565
+ align-self: stretch;
566
+ inline-size: auto;
567
+ block-size: var(--xh-tabs-separator-thickness, var(--xh-stroke-thin));
568
+ }
569
+
570
+ /* —— 两端翻页钮 —— */
571
+ /* 标签带放不下时浮在两端的单图标钮,接 Action Control icon 档 ghost 面(连接层投影 profile / variant / size):
572
+ 盒取标签同高的正方形,四态面、0.97 按压与粗指针 44px 命中区由家族配方给。绝对定位贴在标签带两端、
573
+ 不随标签位移;挪到它底下的那截标签由静息底盖住(透明 ghost 面盖不住字,静息 / 焦点 / 禁用三列换成盖底槽,
574
+ 悬停与按下仍走 ghost 的阶梯——line / card 在白底上 100 → 200,segment 由轨道下发 200 → 300) */
575
+ [data-scope='tabs'][data-part='prev-trigger'],
576
+ [data-scope='tabs'][data-part='next-trigger'] {
577
+ --xh-action-visual-size: var(--xh-tabs-trigger-h, var(--xh-_tabs-trigger-h));
578
+ --xh-action-bg-rest: var(--xh-tabs-scroll-trigger-bg, var(--xh-_tabs-scroll-trigger-bg));
579
+ --xh-action-bg-focus-visible: var(--xh-tabs-scroll-trigger-bg, var(--xh-_tabs-scroll-trigger-bg));
580
+ --xh-action-bg-disabled: var(--xh-tabs-scroll-trigger-bg, var(--xh-_tabs-scroll-trigger-bg));
581
+ --xh-action-fg-rest: var(--xh-tabs-scroll-trigger-fg, var(--xh-fg-muted));
582
+ /* 作者塞进来的图标跟着 icon 档走,与皮肤画的兜底字形同一把尺 */
583
+ --xh-icon-size: var(--xh-tabs-scroll-icon-size, var(--xh-_action-profile-glyph-size));
584
+
585
+ position: absolute;
586
+ z-index: 2;
587
+ inset-block-start: 50%;
588
+ translate: 0 -50%;
589
+ }
590
+
591
+ /* 贴在标签带内衬盒的两端:segment 档的轨道留着一圈内衬,钮坐在内衬里、不压到轨道边上 */
592
+ [data-scope='tabs'][data-part='prev-trigger'] {
593
+ inset-inline-start: var(--xh-tabs-list-p, var(--xh-_tabs-list-p));
594
+ }
595
+
596
+ [data-scope='tabs'][data-part='next-trigger'] {
597
+ inset-inline-end: var(--xh-tabs-list-p, var(--xh-_tabs-list-p));
598
+ }
599
+
600
+ /* 竖排:两只钮躺平成横贯标签带的一条,各贴块向的首尾 */
601
+ [data-scope='tabs'][data-part='prev-trigger'][data-orientation='vertical'],
602
+ [data-scope='tabs'][data-part='next-trigger'][data-orientation='vertical'] {
603
+ inline-size: auto;
604
+ inset-inline: var(--xh-tabs-list-p, var(--xh-_tabs-list-p));
605
+ inset-block-start: auto;
606
+ translate: 0 0;
607
+ }
608
+
609
+ [data-scope='tabs'][data-part='prev-trigger'][data-orientation='vertical'] {
610
+ inset-block-start: var(--xh-tabs-list-p, var(--xh-_tabs-list-p));
611
+ }
612
+
613
+ [data-scope='tabs'][data-part='next-trigger'][data-orientation='vertical'] {
614
+ inset-block-end: var(--xh-tabs-list-p, var(--xh-_tabs-list-p));
615
+ }
616
+
617
+ /* 挪到头的那一侧是原生 disabled(不可聚焦),连接层同时给 data-disabled:到头即收起——
618
+ 它浮在标签上,留着会盖住边上那枚完整露出的标签;配方 disabled 列给的面被 opacity 0 盖住,两者不冲突 */
619
+ [data-scope='tabs'][data-part='prev-trigger'][data-disabled],
620
+ [data-scope='tabs'][data-part='next-trigger'][data-disabled] {
621
+ opacity: 0;
622
+ pointer-events: none;
623
+ }
624
+
625
+ /* 没写内容就由皮肤画兜底字形:横排一左一右的 chevron,竖排一上一下;作者塞了自己的图形这几条即不命中 */
626
+ [data-scope='tabs'][data-part='prev-trigger']:empty::before,
627
+ [data-scope='tabs'][data-part='next-trigger']:empty::before {
628
+ content: '';
629
+ display: inline-block;
630
+ inline-size: var(--xh-icon-size, var(--xh-glyph-size-text));
631
+ block-size: var(--xh-icon-size, var(--xh-glyph-size-text));
632
+ vertical-align: var(--xh-glyph-baseline-shift);
633
+ background-color: currentColor;
634
+ -webkit-mask: var(--xh-glyph-mark-chevron-left) center / contain no-repeat;
635
+ mask: var(--xh-glyph-mark-chevron-left) center / contain no-repeat;
636
+ }
637
+
638
+ [data-scope='tabs'][data-part='next-trigger']:empty::before {
639
+ -webkit-mask-image: var(--xh-glyph-mark-chevron-right);
640
+ mask-image: var(--xh-glyph-mark-chevron-right);
641
+ }
642
+
643
+ /* rtl 下标签序从右往左走,箭头跟着指向行进方向 */
644
+ [dir='rtl'] [data-scope='tabs'][data-part='prev-trigger']:empty::before {
645
+ -webkit-mask-image: var(--xh-glyph-mark-chevron-right);
646
+ mask-image: var(--xh-glyph-mark-chevron-right);
647
+ }
648
+
649
+ [dir='rtl'] [data-scope='tabs'][data-part='next-trigger']:empty::before {
650
+ -webkit-mask-image: var(--xh-glyph-mark-chevron-left);
651
+ mask-image: var(--xh-glyph-mark-chevron-left);
652
+ }
653
+
654
+ [data-scope='tabs'][data-part='prev-trigger'][data-orientation='vertical']:empty::before {
655
+ -webkit-mask-image: var(--xh-glyph-mark-chevron-up);
656
+ mask-image: var(--xh-glyph-mark-chevron-up);
657
+ }
658
+
659
+ [data-scope='tabs'][data-part='next-trigger'][data-orientation='vertical']:empty::before {
660
+ -webkit-mask-image: var(--xh-glyph-mark-chevron-down);
661
+ mask-image: var(--xh-glyph-mark-chevron-down);
161
662
  }
162
663
 
163
664
  /* 声明过任何 display 就盖掉了 UA 的 [hidden]{display:none},收起态得自己还回去。
164
665
  写在文件末尾:兜底必须排在该部件所有 display 声明之后 */
165
666
  [data-scope='tabs'][data-part='root'][hidden],
166
667
  [data-scope='tabs'][data-part='list'][hidden],
167
- [data-scope='tabs'][data-part='trigger'][hidden] {
668
+ [data-scope='tabs'][data-part='trigger'][hidden],
669
+ [data-scope='tabs'][data-part='indicator'][hidden],
670
+ [data-scope='tabs'][data-part='separator'][hidden],
671
+ [data-scope='tabs'][data-part='prev-trigger'][hidden],
672
+ [data-scope='tabs'][data-part='next-trigger'][hidden],
673
+ [data-scope='tabs'][data-part='tab-drag-trigger'][hidden] {
168
674
  display: none;
169
675
  }
676
+
677
+ /* 手指落点:命中区由伪元素向块轴两侧外扩,标签的盒与标签带的高度一点不动——
678
+ 加高标签会把基线与滑条一起顶下去。
679
+ 只扩块轴:标签在行内轴上首尾相接(segment 档只留半格缝),往两侧扩会与相邻标签的命中区叠上。
680
+ 取 8px 是按最矮的 sm 档算的(28 + 8 × 2 = 44),md 与 lg 档更宽裕。
681
+ 用 ::before:::after 已经被换位时的落点线占着 */
682
+ @media (pointer: coarse) {
683
+ [data-scope='tabs'][data-part='trigger']::before {
684
+ content: '';
685
+ position: absolute;
686
+ inset-block: -8px;
687
+ inset-inline: 0;
688
+ }
689
+ }
690
+
691
+ @media (forced-colors: active) {
692
+ /* 滑条与分隔线画的都是一块纯底色,这一档里底色被统一换成 Canvas,两者整根消失。
693
+ 滑条换成系统高亮色、分隔线换成前景色画回来 */
694
+ [data-scope='tabs'][data-part='indicator'] {
695
+ background: Highlight;
696
+ }
697
+
698
+ /* 没放部件时选中标签自画的那条静态线同样是一块纯底色,同一档换成系统高亮色 */
699
+ [data-scope='tabs'][data-part='root'][data-variant='line']
700
+ [data-scope='tabs'][data-part='trigger'][data-current]:not([data-drop])::after {
701
+ background: Highlight;
702
+ }
703
+
704
+ [data-scope='tabs'][data-part='separator'] {
705
+ background: CanvasText;
706
+ }
707
+
708
+ /* 按下:常态靠底色深一档表出,这一档里底色被统一换掉;按住那一下用系统高亮反色画回来。
709
+ line 档的按下反色由家族的 forced-colors 段给 */
710
+ [data-scope='tabs'][data-part='root']:is([data-variant='card'], [data-variant='segment'])
711
+ [data-scope='tabs'][data-part='trigger']:not([data-disabled]):not([data-state='active']):is(:active, [data-pressed]) {
712
+ background: Highlight;
713
+ color: HighlightText;
714
+ }
715
+ }
716
+
717
+ @media print {
718
+ /* 打印默认丢背景与投影:三档形态表达选中的三条通道(选中底、card 档的顶边条、
719
+ segment 档的浮起)在纸上一条都不剩,只有一条比别人深一点的字色。
720
+ 改用下划线与字重表达同一件事,两者都不改盒子的高度 */
721
+ [data-scope='tabs'][data-part='trigger'][data-state='active'] {
722
+ font-weight: var(--xh-font-weight-semibold);
723
+ text-decoration: underline;
724
+ }
725
+
726
+ /* 纸上翻不了页,两只钮不上纸 */
727
+ [data-scope='tabs'][data-part='prev-trigger'],
728
+ [data-scope='tabs'][data-part='next-trigger'] {
729
+ display: none;
730
+ }
731
+ }
170
732
  }