@xihan-ui/styles 3.0.0 → 3.2.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 (221) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/README.md +1 -1
  3. package/component-tokens.d.ts +29 -0
  4. package/components.tokens.json +683 -27
  5. package/css/alert.css +2 -2
  6. package/css/approval.css +4 -4
  7. package/css/button.css +9 -5
  8. package/css/calendar-picker.css +3 -3
  9. package/css/calendar-range-picker.css +7 -7
  10. package/css/carousel.css +45 -13
  11. package/css/cartesian-chart.css +11 -11
  12. package/css/cascader.css +10 -10
  13. package/css/citation.css +2 -2
  14. package/css/code-view.css +6 -6
  15. package/css/color-picker.css +81 -0
  16. package/css/color-swatch-picker.css +9 -4
  17. package/css/combobox.css +12 -4
  18. package/css/command.css +1 -1
  19. package/css/date-field.css +39 -0
  20. package/css/date-picker.css +86 -9
  21. package/css/date-range-picker.css +47 -7
  22. package/css/diff-view.css +8 -8
  23. package/css/field-array.css +1 -1
  24. package/css/field.css +4 -0
  25. package/css/file-upload.css +1 -1
  26. package/css/float-button.css +140 -27
  27. package/css/form.css +14 -6
  28. package/css/graph-chart.css +2 -2
  29. package/css/highlight.css +1 -1
  30. package/css/image.css +1 -1
  31. package/css/mention.css +1 -1
  32. package/css/message-feed.css +1 -1
  33. package/css/notification.css +26 -20
  34. package/css/number-field.css +18 -18
  35. package/css/page-header.css +2 -2
  36. package/css/password-input.css +2 -2
  37. package/css/popconfirm.css +3 -3
  38. package/css/question-flow.css +8 -8
  39. package/css/radar-chart.css +6 -6
  40. package/css/rating.css +1 -1
  41. package/css/select.css +2 -2
  42. package/css/separator.css +2 -2
  43. package/css/side-nav.css +13 -13
  44. package/css/skeleton.css +5 -5
  45. package/css/slider.css +6 -6
  46. package/css/sortable.css +2 -2
  47. package/css/table.css +19 -2
  48. package/css/tags-input.css +1 -1
  49. package/css/time-picker.css +59 -2
  50. package/css/tool-call.css +7 -7
  51. package/css/tooltip.css +1 -1
  52. package/css/tree-select.css +2 -2
  53. package/css/tree.css +81 -11
  54. package/dist/css/accordion.css +269 -0
  55. package/dist/css/affix.css +47 -0
  56. package/dist/css/alert.css +247 -0
  57. package/dist/css/anchor.css +252 -0
  58. package/dist/css/approval.css +501 -0
  59. package/dist/css/avatar-group.css +99 -0
  60. package/dist/css/avatar.css +98 -0
  61. package/dist/css/back-top.css +161 -0
  62. package/dist/css/badge.css +205 -0
  63. package/dist/css/bar-code.css +62 -0
  64. package/dist/css/breadcrumb.css +230 -0
  65. package/dist/css/button-group.css +263 -0
  66. package/dist/css/button.css +219 -0
  67. package/dist/css/calendar-picker.css +467 -0
  68. package/dist/css/calendar-range-picker.css +554 -0
  69. package/dist/css/card.css +221 -0
  70. package/dist/css/carousel.css +710 -0
  71. package/dist/css/cartesian-chart.css +830 -0
  72. package/dist/css/cascader.css +961 -0
  73. package/dist/css/checkbox-group.css +358 -0
  74. package/dist/css/checkbox.css +257 -0
  75. package/dist/css/citation.css +470 -0
  76. package/dist/css/clipboard.css +315 -0
  77. package/dist/css/code-view.css +374 -0
  78. package/dist/css/collapsible.css +185 -0
  79. package/dist/css/color-field.css +176 -0
  80. package/dist/css/color-picker.css +595 -0
  81. package/dist/css/color-slider.css +281 -0
  82. package/dist/css/color-swatch-picker.css +250 -0
  83. package/dist/css/color-swatch.css +35 -0
  84. package/dist/css/combobox.css +574 -0
  85. package/dist/css/command.css +399 -0
  86. package/dist/css/context-menu.css +420 -0
  87. package/dist/css/date-field.css +317 -0
  88. package/dist/css/date-picker.css +868 -0
  89. package/dist/css/date-range-picker.css +886 -0
  90. package/dist/css/description.css +67 -0
  91. package/dist/css/descriptions.css +299 -0
  92. package/dist/css/dialog.css +455 -0
  93. package/dist/css/diff-view.css +513 -0
  94. package/dist/css/download-trigger.css +151 -0
  95. package/dist/css/drawer.css +552 -0
  96. package/dist/css/editable.css +295 -0
  97. package/dist/css/empty-state.css +163 -0
  98. package/dist/css/field-array.css +226 -0
  99. package/dist/css/field.css +166 -0
  100. package/dist/css/fieldset.css +102 -0
  101. package/dist/css/file-upload.css +429 -0
  102. package/dist/css/flex.css +441 -0
  103. package/dist/css/float-button.css +569 -0
  104. package/dist/css/floating-panel.css +429 -0
  105. package/dist/css/focus.css +46 -0
  106. package/dist/css/forced-colors.css +93 -0
  107. package/dist/css/form.css +393 -0
  108. package/dist/css/funnel-chart.css +106 -0
  109. package/dist/css/graph-chart.css +205 -0
  110. package/dist/css/grid-list.css +235 -0
  111. package/dist/css/grid.css +1201 -0
  112. package/dist/css/heatmap.css +552 -0
  113. package/dist/css/hierarchy-chart.css +298 -0
  114. package/dist/css/highlight.css +41 -0
  115. package/dist/css/hover-card.css +226 -0
  116. package/dist/css/icon.css +225 -0
  117. package/dist/css/image-cropper.css +373 -0
  118. package/dist/css/image-viewer.css +538 -0
  119. package/dist/css/image.css +98 -0
  120. package/dist/css/infinite-scroll.css +77 -0
  121. package/dist/css/input-group.css +275 -0
  122. package/dist/css/json-viewer.css +379 -0
  123. package/dist/css/kbd.css +72 -0
  124. package/dist/css/label.css +78 -0
  125. package/dist/css/layers.css +7 -0
  126. package/dist/css/layout.css +372 -0
  127. package/dist/css/liquid.css +106 -0
  128. package/dist/css/list.css +152 -0
  129. package/dist/css/listbox.css +390 -0
  130. package/dist/css/loading-bar.css +141 -0
  131. package/dist/css/log.css +306 -0
  132. package/dist/css/markdown-stream.css +149 -0
  133. package/dist/css/marquee.css +378 -0
  134. package/dist/css/masonry.css +70 -0
  135. package/dist/css/matrix-code.css +67 -0
  136. package/dist/css/mention.css +393 -0
  137. package/dist/css/menu.css +364 -0
  138. package/dist/css/menubar.css +456 -0
  139. package/dist/css/message-feed.css +318 -0
  140. package/dist/css/navigation-menu.css +513 -0
  141. package/dist/css/notification.css +782 -0
  142. package/dist/css/number-animation.css +44 -0
  143. package/dist/css/number-field.css +252 -0
  144. package/dist/css/overlay-arrow.css +56 -0
  145. package/dist/css/page-header.css +184 -0
  146. package/dist/css/pagination.css +450 -0
  147. package/dist/css/password-input.css +391 -0
  148. package/dist/css/pie-chart.css +247 -0
  149. package/dist/css/pin-input.css +199 -0
  150. package/dist/css/pointer.css +36 -0
  151. package/dist/css/popconfirm.css +320 -0
  152. package/dist/css/popover.css +279 -0
  153. package/dist/css/progress.css +501 -0
  154. package/dist/css/prompt-input.css +260 -0
  155. package/dist/css/question-flow.css +553 -0
  156. package/dist/css/radar-chart.css +233 -0
  157. package/dist/css/radio-group.css +598 -0
  158. package/dist/css/rating.css +294 -0
  159. package/dist/css/reasoning.css +343 -0
  160. package/dist/css/reset.css +117 -0
  161. package/dist/css/resizable.css +242 -0
  162. package/dist/css/sankey-chart.css +174 -0
  163. package/dist/css/scroll-area.css +240 -0
  164. package/dist/css/scrollbar.css +204 -0
  165. package/dist/css/select.css +612 -0
  166. package/dist/css/separator.css +122 -0
  167. package/dist/css/side-nav.css +476 -0
  168. package/dist/css/signature-pad.css +179 -0
  169. package/dist/css/skeleton.css +161 -0
  170. package/dist/css/slider.css +374 -0
  171. package/dist/css/sortable.css +228 -0
  172. package/dist/css/sparkline.css +164 -0
  173. package/dist/css/spinner.css +236 -0
  174. package/dist/css/splitter.css +159 -0
  175. package/dist/css/statistic.css +137 -0
  176. package/dist/css/steps.css +627 -0
  177. package/dist/css/switch.css +351 -0
  178. package/dist/css/table.css +1145 -0
  179. package/dist/css/tabs.css +944 -0
  180. package/dist/css/tag-group.css +247 -0
  181. package/dist/css/tag.css +310 -0
  182. package/dist/css/tags-input.css +322 -0
  183. package/dist/css/text-field.css +197 -0
  184. package/dist/css/time-field.css +277 -0
  185. package/dist/css/time-picker.css +707 -0
  186. package/dist/css/time-range-picker.css +746 -0
  187. package/dist/css/timeline.css +423 -0
  188. package/dist/css/timer.css +123 -0
  189. package/dist/css/timestamp.css +34 -0
  190. package/dist/css/toggle-group.css +424 -0
  191. package/dist/css/toggle.css +146 -0
  192. package/dist/css/tone.css +189 -0
  193. package/dist/css/tool-call.css +429 -0
  194. package/dist/css/toolbar.css +365 -0
  195. package/dist/css/tooltip.css +218 -0
  196. package/dist/css/tour.css +439 -0
  197. package/dist/css/transfer.css +638 -0
  198. package/dist/css/tree-select.css +878 -0
  199. package/dist/css/tree.css +733 -0
  200. package/dist/css/truncate.css +108 -0
  201. package/dist/css/typography.css +472 -0
  202. package/dist/css/undefined.css +58 -0
  203. package/dist/css/virtualizer.css +166 -0
  204. package/dist/css/visually-hidden.css +20 -0
  205. package/dist/css/watermark.css +82 -0
  206. package/dist/family/action-control.css +487 -0
  207. package/dist/family/chart.css +480 -0
  208. package/dist/family/check-mark.css +94 -0
  209. package/dist/family/choice-card.css +97 -0
  210. package/dist/family/collection-item.css +764 -0
  211. package/dist/family/field-chrome.css +287 -0
  212. package/dist/family/loading-ring.css +95 -0
  213. package/dist/family/material.css +72 -0
  214. package/dist/family/menu-choice.css +56 -0
  215. package/dist/family/motion.css +363 -0
  216. package/dist/family/swatch.css +59 -0
  217. package/dist/family/tag-list.css +59 -0
  218. package/family/action-control.css +3 -12
  219. package/index.css +7307 -6759
  220. package/index.unlayered.css +7309 -6761
  221. package/package.json +165 -164
@@ -0,0 +1,733 @@
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
+ /* 定义 tree 样式。 */
7
+
8
+ /* 共享关键帧由 family/motion.css 提供,本皮肤 @import 它,单独引入仍成立。 */
9
+ @import '../family/motion.css';
10
+ @import '../family/collection-item.css';
11
+ @import '../family/loading-ring.css';
12
+
13
+ @layer xihan.components {
14
+ /* 皮肤在场的标记:开发模式下按 scope 探一次,取不到就说明这份皮肤没被引入。 */
15
+ .xh-scope-tree {
16
+ --xh-tree-skin: 1;
17
+ }
18
+
19
+ .xh-scope-tree[data-part='root'] {
20
+ /* 尺寸档只换这几支私有槽,行、指示符盒、缩进与占位从 root 继承,走集合家族的尺寸档:
21
+ 行的块向内衬取列表条目档、行内内衬 / 间距 / 字号取控件档、指示符盒取指示符档、缩进一档一格 */
22
+ --xh-_tree-row-py: var(--xh-list-option-py-md);
23
+ --xh-_tree-row-px: var(--xh-control-px-md);
24
+ --xh-_tree-row-gap: var(--xh-control-gap-md);
25
+ --xh-_tree-font-size: var(--xh-control-font-md);
26
+ --xh-_tree-indicator: var(--xh-control-indicator-md);
27
+ --xh-_tree-indent: var(--xh-space-4);
28
+ /* 行外的字形(空态 / 在途占位里的加载环与作者塞的图标)按档取尺;行内的由家族随 data-xh-collection-size
29
+ 下发。这支只管作者放进行里的图标:树自绘的状态字形(展开方向、行尾对号与半选杠)是指示符,
30
+ 各部件在自己身上把 --xh-icon-size 改接到指示符盒的尺 */
31
+ --xh-icon-size: var(--xh-tree-icon-size, var(--xh-glyph-size-md));
32
+
33
+ display: flex;
34
+ flex-direction: column;
35
+ gap: var(--xh-tree-gap, var(--xh-space-2));
36
+ }
37
+
38
+ .xh-scope-tree[data-part='root'][data-size='sm'] {
39
+ --xh-_tree-row-py: var(--xh-list-option-py-sm);
40
+ --xh-_tree-row-px: var(--xh-control-px-sm);
41
+ --xh-_tree-row-gap: var(--xh-control-gap-sm);
42
+ --xh-_tree-font-size: var(--xh-control-font-sm);
43
+ --xh-_tree-indicator: var(--xh-control-indicator-sm);
44
+ --xh-_tree-indent: var(--xh-space-3);
45
+ --xh-icon-size: var(--xh-tree-icon-size, var(--xh-glyph-size-sm));
46
+ }
47
+
48
+ .xh-scope-tree[data-part='root'][data-size='lg'] {
49
+ --xh-_tree-row-py: var(--xh-list-option-py-lg);
50
+ --xh-_tree-row-px: var(--xh-control-px-lg);
51
+ --xh-_tree-row-gap: var(--xh-control-gap-lg);
52
+ --xh-_tree-font-size: var(--xh-control-font-lg);
53
+ --xh-_tree-indicator: var(--xh-control-indicator-lg);
54
+ --xh-_tree-indent: var(--xh-space-5);
55
+ --xh-icon-size: var(--xh-tree-icon-size, var(--xh-glyph-size-lg));
56
+ }
57
+
58
+ .xh-scope-tree[data-part='root'][hidden] {
59
+ display: none;
60
+ }
61
+
62
+ /* 淡底档:描边透明、底换成 subtle;outline 即基础规则。
63
+ 形态由 root 的 data-variant 向容器下发,容器基础规则直接写 border-default / bg-surface,
64
+ 与 list / descriptions 的 outline 面同源(列表容器族) */
65
+ .xh-scope-tree[data-part='root'][data-variant='subtle'] .xh-scope-tree[data-part='tree'] {
66
+ border-color: transparent;
67
+ background: var(--xh-tree-bg, var(--xh-bg-subtle));
68
+ }
69
+
70
+ /* 无框档:边框留着但透明,去掉外框不会让行的位置跳一格 */
71
+ .xh-scope-tree[data-part='root'][data-variant='ghost'] .xh-scope-tree[data-part='tree'] {
72
+ border-color: transparent;
73
+ background: transparent;
74
+ }
75
+
76
+ .xh-scope-tree[data-part='label'] {
77
+ color: var(--xh-tree-label-fg, var(--xh-fg-muted));
78
+ font-size: var(--xh-tree-label-font-size, var(--xh-text-label-size));
79
+ font-weight: var(--xh-tree-label-font-weight, var(--xh-text-label-weight));
80
+ line-height: var(--xh-leading-none);
81
+ }
82
+
83
+ /* 整棵树禁用会把 data-disabled 一路打到 label / tree 上,各处观感保持一致 */
84
+ .xh-scope-tree[data-part='label'][data-disabled] {
85
+ color: var(--xh-fg-subtle);
86
+ }
87
+
88
+ /* 常驻容器不是浮层:留边框与底色,但不投影、不脱离文档流,滚动发生在容器自身 */
89
+ .xh-scope-tree[data-part='tree'] {
90
+ display: flex;
91
+ flex-direction: column;
92
+ gap: var(--xh-tree-tree-gap, var(--xh-list-option-gap));
93
+ max-block-size: var(--xh-tree-max-h, var(--xh-viewport-h-lg));
94
+ padding-block: var(--xh-tree-py, var(--xh-space-1));
95
+ padding-inline: var(--xh-tree-px, var(--xh-space-1));
96
+ border: var(--xh-stroke-thin) solid var(--xh-tree-border, var(--xh-border-default));
97
+ border-radius: var(--xh-tree-radius, var(--xh-shape-surface));
98
+ background: var(--xh-tree-bg, var(--xh-bg-surface));
99
+ color: var(--xh-tree-fg, var(--xh-fg-default));
100
+ /* 两轴一起给:深层节点靠缩进往右推,横向溢出是常态,得够得着 */
101
+ overflow: auto;
102
+ }
103
+
104
+ /* display 会盖掉 hidden 属性的 UA 规则,作者手动收起时要显式让位 */
105
+ .xh-scope-tree[data-part='tree'][hidden] {
106
+ display: none;
107
+ }
108
+
109
+ /* 空态占位:待在 root 里、tree 的兄弟(role=tree 只许拥有 treeitem 与 group)。
110
+ 与在途占位同一副排版:一行居中的次要文字,字号与行内内衬随尺寸档与条目同档,块向内距一档 */
111
+ .xh-scope-tree[data-part='empty'] {
112
+ display: flex;
113
+ align-items: center;
114
+ justify-content: center;
115
+ padding-block: var(--xh-tree-empty-py, var(--xh-space-3));
116
+ padding-inline: var(--xh-tree-empty-px, var(--xh-_tree-row-px));
117
+ color: var(--xh-tree-empty-fg, var(--xh-fg-muted));
118
+ font-size: var(--xh-tree-empty-font-size, var(--xh-_tree-font-size));
119
+ line-height: var(--xh-leading-normal);
120
+ text-align: center;
121
+ }
122
+
123
+ /* 上面给了 display,UA 的 [hidden]{display:none} 已被盖掉,这里补回来 */
124
+ .xh-scope-tree[data-part='empty'][hidden] {
125
+ display: none;
126
+ }
127
+
128
+ .xh-scope-tree[data-part='empty'][data-disabled] {
129
+ color: var(--xh-fg-disabled);
130
+ }
131
+
132
+ /* 在途占位:与空态占位同一个位置、同一副排版,两者不同屏。还没有节点时一枚加载环排在文案之前 */
133
+ .xh-scope-tree[data-part='loading'] {
134
+ display: flex;
135
+ align-items: center;
136
+ justify-content: center;
137
+ gap: var(--xh-tree-loading-gap, var(--xh-_tree-row-gap));
138
+ padding-block: var(--xh-tree-loading-py, var(--xh-space-3));
139
+ padding-inline: var(--xh-tree-loading-px, var(--xh-_tree-row-px));
140
+ color: var(--xh-tree-loading-fg, var(--xh-fg-muted));
141
+ font-size: var(--xh-tree-loading-font-size, var(--xh-_tree-font-size));
142
+ line-height: var(--xh-leading-normal);
143
+ text-align: center;
144
+ }
145
+
146
+ /* 环由加载环配方画成(轨道 + 起始边、随 data-loading 淡入并转),这里只给它盒子:直径取尺寸档的图标档 */
147
+ .xh-scope-tree[data-part='loading']::before {
148
+ content: '';
149
+ flex: none;
150
+ inline-size: var(--xh-icon-size, var(--xh-glyph-size-md));
151
+ block-size: var(--xh-icon-size, var(--xh-glyph-size-md));
152
+ }
153
+
154
+ .xh-scope-tree[data-part='loading'][hidden] {
155
+ display: none;
156
+ }
157
+
158
+ .xh-scope-tree[data-part='loading'][data-disabled] {
159
+ color: var(--xh-fg-disabled);
160
+ }
161
+
162
+ /* 焦点在树外时容器自己进 Tab 序列兜底,落焦不画环:高亮永远长在行上 */
163
+ .xh-scope-tree[data-part='tree']:focus-visible {
164
+ outline: none;
165
+ }
166
+
167
+ /* 分支只是"这一行 + 它的子层"的外壳,不自带内边距:
168
+ 真正长成一行的是 branch-control,子层缩进归 branch-content。
169
+ 行距由这里出:分支自己那一行与它展开的子层之间,同样按条目间距断开 */
170
+ .xh-scope-tree[data-part='branch'] {
171
+ display: flex;
172
+ flex-direction: column;
173
+ gap: var(--xh-tree-branch-gap, var(--xh-list-option-gap));
174
+ }
175
+
176
+ .xh-scope-tree[data-part='branch'][hidden] {
177
+ display: none;
178
+ }
179
+
180
+ /* 焦点落在 branch 上(它才是 treeitem),但环画在它自己那一行上:
181
+ 整棵子树套一圈描边会把展开的层级全框进去 */
182
+ .xh-scope-tree[data-part='branch']:focus-visible {
183
+ outline: none;
184
+ }
185
+
186
+ .xh-scope-tree[data-part='branch']:focus-visible > .xh-scope-tree[data-part='branch-control'] {
187
+ outline: var(--xh-ring-width) solid var(--xh-ring-focus);
188
+ outline-offset: var(--xh-ring-offset);
189
+ }
190
+
191
+ /* 行:叶子与分支共用同一套盒子,两种行在视觉上必须齐平。
192
+ 行走 Collection Item 的 overlay 语境,与树选择同一种选中读法:悬停 / 高亮 / 按下面(白底承载
193
+ hover 100 → pressed 200)由家族给,选中不换面、不换字色,只在行尾亮对号,选中叠悬停 / 按下沿用同一条阶梯;
194
+ 家族按 aria-selected / data-selected 与 aria-disabled / data-disabled 给出,这里只把公开槽映射到桥接槽。
195
+ 布局保持 flex 而不用家族网格:一行里可以并排多个前导部件(箭头、拖拽把手)与作者自己的图标、
196
+ 按钮,单列的 prefix 放不下;间距统一由行的 gap 给,家族按槽下发的外边距(--xh-collection-gap)归零 */
197
+ .xh-scope-tree[data-part='item'],
198
+ .xh-scope-tree[data-part='branch-control'] {
199
+ --xh-collection-block-padding: var(--xh-tree-row-py, var(--xh-_tree-row-py));
200
+ --xh-collection-inline-padding: var(--xh-tree-row-px, var(--xh-_tree-row-px));
201
+ --xh-collection-gap: 0;
202
+ --xh-collection-font-size: var(--xh-tree-row-font-size, var(--xh-_tree-font-size));
203
+ --xh-collection-radius: var(--xh-tree-row-radius, var(--xh-shape-inset));
204
+ /* 作者的图标尺槽经桥接槽下发到行,家族缺省按 md 档 */
205
+ --xh-collection-glyph-size: var(--xh-tree-icon-size, var(--xh-_collection-glyph-size));
206
+ /* 箭头 / 对号盒与叶子的占位同一把尺:指示符档(md comfortable 16 / compact 14),与同一行里的
207
+ 拖拽把手同一支。不读家族按档下发的 --xh-icon-size——那支只管作者放进行里的图标,按图标档取的
208
+ 20px 会比 16px 的指示符盒大一圈 */
209
+ --xh-_tree-indicator-size: var(--xh-tree-indicator-size, var(--xh-_tree-indicator));
210
+ --xh-collection-fg-rest: var(--xh-tree-row-fg, var(--xh-fg-default));
211
+ --xh-collection-fg-hover: var(--xh-tree-row-fg, var(--xh-fg-default));
212
+ --xh-collection-fg-keyboard-highlight: var(--xh-tree-row-fg, var(--xh-fg-default));
213
+ --xh-collection-fg-pressed: var(--xh-tree-row-fg, var(--xh-fg-default));
214
+ --xh-collection-fg-selected: var(--xh-tree-row-fg-selected, var(--xh-tree-row-fg, var(--xh-fg-default)));
215
+ --xh-collection-fg-selected-hover: var(--xh-tree-row-fg-selected, var(--xh-tree-row-fg, var(--xh-fg-default)));
216
+ --xh-collection-fg-selected-highlight: var(--xh-tree-row-fg-selected, var(--xh-tree-row-fg, var(--xh-fg-default)));
217
+ --xh-collection-fg-selected-pressed: var(--xh-tree-row-fg-selected, var(--xh-tree-row-fg, var(--xh-fg-default)));
218
+ --xh-collection-font-weight-selected: var(--xh-tree-row-selected-font-weight, var(--xh-font-weight-regular));
219
+ --xh-collection-font-weight-selected-hover: var(--xh-tree-row-selected-font-weight, var(--xh-font-weight-regular));
220
+ --xh-collection-font-weight-selected-highlight: var(--xh-tree-row-selected-font-weight, var(--xh-font-weight-regular));
221
+ --xh-collection-font-weight-selected-pressed: var(--xh-tree-row-selected-font-weight, var(--xh-font-weight-regular));
222
+ --xh-collection-bg-hover: var(--xh-tree-row-bg-hover, var(--xh-bg-subtle));
223
+ --xh-collection-bg-keyboard-highlight: var(--xh-tree-row-bg-hover, var(--xh-bg-subtle));
224
+ --xh-collection-bg-pressed: var(--xh-tree-row-bg-pressed, var(--xh-bg-subtle-hover));
225
+ --xh-collection-bg-selected-hover: var(--xh-tree-row-bg-hover, var(--xh-bg-subtle));
226
+ --xh-collection-bg-selected-highlight: var(--xh-tree-row-bg-hover, var(--xh-bg-subtle));
227
+ --xh-collection-bg-selected-pressed: var(--xh-tree-row-bg-pressed, var(--xh-bg-subtle-hover));
228
+ /* data-highlighted 跟的是连接层记的焦点行(指针点选也会落焦):只换面,焦点环在 :focus-visible 下再接回 */
229
+ --xh-collection-outline-keyboard-highlight: transparent;
230
+ --xh-collection-outline-selected-highlight: transparent;
231
+ /* 行尾对号:槽按本部件取名(item-check-fg),标记部件的旧槽留在兜底位 */
232
+ --xh-collection-indicator-fg-rest: var(--xh-tree-item-check-fg, var(--xh-tree-item-indicator-fg, var(--xh-fg-brand)));
233
+ --xh-collection-indicator-fg-checked: var(--xh-tree-item-check-fg, var(--xh-tree-item-indicator-fg, var(--xh-fg-brand)));
234
+ --xh-collection-indicator-fg-selected: var(--xh-tree-item-check-fg, var(--xh-tree-item-indicator-fg, var(--xh-fg-brand)));
235
+ --xh-collection-indicator-fg-selected-hover: var(--xh-tree-item-check-fg, var(--xh-tree-item-indicator-fg, var(--xh-fg-brand)));
236
+ --xh-collection-indicator-fg-selected-highlight: var(--xh-tree-item-check-fg, var(--xh-tree-item-indicator-fg, var(--xh-fg-brand)));
237
+ --xh-collection-indicator-fg-selected-pressed: var(--xh-tree-item-check-fg, var(--xh-tree-item-indicator-fg, var(--xh-fg-brand)));
238
+
239
+ display: flex;
240
+ align-items: center;
241
+ gap: var(--xh-tree-row-gap, var(--xh-_tree-row-gap));
242
+ /* 行高留出文字的上下伸展空间,行内那层 overflow:hidden 才不会把字削掉 */
243
+ line-height: var(--xh-tree-row-leading, var(--xh-leading-normal));
244
+ white-space: nowrap;
245
+ user-select: none;
246
+ /* 换面走家族的时间线(按下 press,其余一律 micro,描边色同样不进过渡);box-shadow 一并接过渡:
247
+ 换父落点那圈内描边挂在行自己身上,跟着 data-drop 在行间移动。
248
+ translate 是落下后的换位:宿主写回 collection 之后,连接层把行按旧位置反向补偿,这条过渡带它滑到新位置 */
249
+ transition:
250
+ background-color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter),
251
+ color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter),
252
+ box-shadow var(--xh-motion-duration-micro) var(--xh-motion-ease-enter),
253
+ opacity var(--xh-motion-duration-micro) var(--xh-motion-ease-enter),
254
+ translate var(--xh-motion-duration-move) var(--xh-motion-ease-continuous);
255
+ }
256
+
257
+ /* 已有节点时重新取数:不换成占位,行保留上一帧、按 micro 淡到禁用透明度,数据到了再淡回;
258
+ 树框(描边与底)不淡,取数在途时行不接指针 */
259
+ .xh-scope-tree[data-part='tree'][data-loading] :is(.xh-scope-tree[data-part='item'], .xh-scope-tree[data-part='branch-control']) {
260
+ opacity: var(--xh-tree-row-loading-opacity, var(--xh-state-disabled-opacity));
261
+ pointer-events: none;
262
+ }
263
+
264
+ /* 分支行的首格是展开箭头,叶子行没有这一格,由行盒自己补出来(对号在行尾,不再替叶子顶这一格),
265
+ 否则叶子会比同级分支往行首缩一格,层级关系读不出来。占位宽度与箭头盒同一把尺 */
266
+ [data-orientation='vertical'] > .xh-scope-tree[data-part='item'] {
267
+ padding-inline-start: calc(
268
+ var(--xh-tree-row-px, var(--xh-_tree-row-px)) +
269
+ var(--xh-_tree-indicator-size) +
270
+ var(--xh-tree-row-gap, var(--xh-_tree-row-gap))
271
+ );
272
+ }
273
+
274
+ /* 家族给了 display,UA 的 [hidden]{display:none} 已被盖掉 */
275
+ .xh-scope-tree[data-part='item'][hidden],
276
+ .xh-scope-tree[data-part='branch-control'][hidden] {
277
+ display: none;
278
+ }
279
+
280
+ /* 键盘焦点出现时环才接回并立即到位,不压在背景过渡的中间帧上:叶子自己承载焦点;
281
+ 分支的焦点在 branch 上、环画在它那一行(见上面 branch:focus-visible 那条) */
282
+ .xh-scope-tree[data-part='item']:focus-visible:not(:hover) {
283
+ --xh-collection-outline-keyboard-highlight: var(--xh-ring-focus);
284
+ --xh-collection-outline-selected-highlight: var(--xh-ring-focus);
285
+
286
+ /* 换面即时,只留换位:Alt + 方向键搬的正是这一行,它照样从旧位置滑到新位置 */
287
+ transition: translate var(--xh-motion-duration-move) var(--xh-motion-ease-continuous);
288
+ }
289
+
290
+ /* 禁用走 aria-disabled 而非原生 disabled:叶子的禁用面由家族按 aria-disabled 给;
291
+ 分支行不是 treeitem 本体(aria-disabled 在 branch 上),按连接层的 data-disabled 映射到家族槽:
292
+ 字色降级、光标 not-allowed,悬停 / 高亮 / 按下由家族守卫拦掉 */
293
+ .xh-scope-tree[data-part='branch-control'][data-disabled] {
294
+ --xh-collection-fg-rest: var(--xh-fg-disabled);
295
+ --xh-collection-cursor-rest: not-allowed;
296
+ }
297
+
298
+ .xh-scope-tree[data-part='item-text'],
299
+ .xh-scope-tree[data-part='branch-text'] {
300
+ flex: 1;
301
+ /* 弹性子项默认最小尺寸是内容尺寸,不归零则长文本把行撑破、省略号轮不上 */
302
+ min-inline-size: 0;
303
+ overflow: hidden;
304
+ white-space: nowrap;
305
+ text-overflow: ellipsis;
306
+ }
307
+
308
+ /* 选中对号落在行尾,与树选择同位:作者在行里摆在哪儿都排到最后,
309
+ 行尾那一格(item-suffix)在它之前;正文吃满剩余宽度,对号因此贴着行的末端。
310
+ 未选中时占位不塌,切换选中不引起行宽抖动。盒尺取指示符档;盒里的对号是指示符,
311
+ 在自己身上把 --xh-icon-size 改接到盒的尺,兜底对号与作者塞进盒里的 XhIcon 读同一把尺、与盒同边长 */
312
+ .xh-scope-tree[data-part='item-indicator'] {
313
+ --xh-icon-size: var(--xh-_tree-indicator-size);
314
+
315
+ display: inline-flex;
316
+ flex: none;
317
+ order: 1;
318
+ align-items: center;
319
+ justify-content: center;
320
+ inline-size: var(--xh-_tree-indicator-size);
321
+ }
322
+
323
+ .xh-scope-tree[data-part='item-indicator'][hidden] {
324
+ display: none;
325
+ }
326
+
327
+ /* 家族只按行的 aria-selected 显示对号:分支行的 aria-selected 在 branch 上、半选没有选中语义,
328
+ 这两档按标记自身的 data-selected / data-indeterminate 显形 */
329
+ .xh-scope-tree[data-part='item-indicator']:is([data-selected], [data-indeterminate]) {
330
+ opacity: 1;
331
+ }
332
+
333
+ /* 分支行的禁用不在行的 aria-disabled 上(那在 branch 上),标记按自身的 data-disabled 降级 */
334
+ .xh-scope-tree[data-part='item-indicator'][data-disabled] {
335
+ color: var(--xh-fg-disabled);
336
+ }
337
+
338
+ /* 展开箭头与方向指示符:两者都可能被作者用作"点这里展开"的把手,
339
+ 所以尺寸与叶子的勾选标记一致,两种行的文字起点才对得齐。
340
+ 盒里的 chevron 是指示符:与盒同边长,--xh-icon-size 在自己身上改接到盒的尺 */
341
+ .xh-scope-tree[data-part='branch-trigger'],
342
+ .xh-scope-tree[data-part='branch-indicator'] {
343
+ --xh-icon-size: var(--xh-_tree-indicator-size);
344
+
345
+ display: inline-flex;
346
+ flex: none;
347
+ align-items: center;
348
+ justify-content: center;
349
+ inline-size: var(--xh-_tree-indicator-size);
350
+ color: var(--xh-tree-branch-indicator-fg, var(--xh-fg-subtle));
351
+ transition: rotate var(--xh-motion-duration-nudge) var(--xh-motion-ease-enter-strong);
352
+ }
353
+
354
+ .xh-scope-tree[data-part='branch-trigger'][hidden],
355
+ .xh-scope-tree[data-part='branch-indicator'][hidden] {
356
+ display: none;
357
+ }
358
+
359
+ /* 收起时指向行尾、展开时转向下方,与逻辑书写方向一致:rtl 下行尾在左侧,收起的箭头转半圈;
360
+ 旋转角乘书写方向的符号(ltr 0deg、rtl 180deg),按就近的 dir 走 */
361
+ .xh-scope-tree[data-part='branch-trigger'],
362
+ .xh-scope-tree[data-part='branch-indicator'] {
363
+ rotate: calc(90deg - 90deg * var(--xh-direction-sign));
364
+ }
365
+
366
+ .xh-scope-tree[data-part='branch-trigger'][data-state='open'],
367
+ .xh-scope-tree[data-part='branch-indicator'][data-state='open'] {
368
+ rotate: 90deg;
369
+ }
370
+
371
+ .xh-scope-tree[data-part='branch-trigger'][data-disabled],
372
+ .xh-scope-tree[data-part='branch-indicator'][data-disabled] {
373
+ color: var(--xh-fg-disabled);
374
+ }
375
+
376
+ /* 层级缩进靠子层容器自己顶:分支物理嵌套着子层,每深一层就多缩一格,
377
+ 无需把层级号写进内联样式,也就不必在两个适配器里各写一遍 */
378
+ .xh-scope-tree[data-part='branch-content'] {
379
+ display: flex;
380
+ flex-direction: column;
381
+ gap: var(--xh-tree-branch-content-gap, var(--xh-list-option-gap));
382
+ padding-inline-start: var(--xh-tree-indent, var(--xh-_tree-indent));
383
+ }
384
+
385
+ /*
386
+ * 连接线:父节点展开箭头的中线向下引一道竖线,每个子节点从竖线横出一段接到自己行首(├),
387
+ * 最后一个子节点的竖线止于它那一行的中线(└),层级深时一眼看得出谁挂在谁下面。
388
+ * 线由每个子节点自己画:子节点的行画「这一段竖线 + 横出的那一段」,展开着的非末位分支再由它的子层
389
+ * 把竖线接过它整棵子树,交给下一个兄弟。子节点之间、分支行与子层之间的间距由线向上下伸出补齐,竖线不断开。
390
+ * 只画在竖排的子层里:顶层节点没有父节点可连,横排的末端层没有行首可接。
391
+ * 首个与末位子节点按子层里的结构次序认:子层里只放节点,收起的子树整块不显示、不影响次序。
392
+ * 它是内部分隔的一种,颜色取 border-subtle;行悬停与选中换面时线压在面上照样看得见
393
+ */
394
+ .xh-scope-tree[data-part='tree'][data-lines] .xh-scope-tree[data-part='branch-content'] {
395
+ position: relative;
396
+ }
397
+
398
+ /* 竖线落在父节点展开箭头的中线上:父行的行首内边距加半个箭头盒,换算到缩进了一格的子节点坐标里。
399
+ 向上伸出的量补的是上方那段间距(首个子节点补分支行与子层之间的那段),向下补到下一个兄弟 */
400
+ .xh-scope-tree[data-part='tree'][data-lines] .xh-scope-tree[data-part='branch-content'][data-orientation='vertical'] > .xh-scope-tree[data-part='item'],
401
+ .xh-scope-tree[data-part='tree'][data-lines] .xh-scope-tree[data-part='branch-content'][data-orientation='vertical'] > .xh-scope-tree[data-part='branch'] > .xh-scope-tree[data-part='branch-control'] {
402
+ --xh-_tree-line-x: calc(
403
+ var(--xh-tree-row-px, var(--xh-_tree-row-px)) +
404
+ var(--xh-_tree-indicator-size) / 2 -
405
+ var(--xh-stroke-thin) / 2 -
406
+ var(--xh-tree-indent, var(--xh-_tree-indent))
407
+ );
408
+ --xh-_tree-line-top: 0px;
409
+ --xh-_tree-line-bottom: var(--xh-tree-branch-content-gap, var(--xh-list-option-gap));
410
+
411
+ position: relative;
412
+ }
413
+
414
+ .xh-scope-tree[data-part='tree'][data-lines] .xh-scope-tree[data-part='branch-content'][data-orientation='vertical'] > .xh-scope-tree[data-part='item']:first-child,
415
+ .xh-scope-tree[data-part='tree'][data-lines] .xh-scope-tree[data-part='branch-content'][data-orientation='vertical'] > .xh-scope-tree[data-part='branch']:first-child > .xh-scope-tree[data-part='branch-control'] {
416
+ --xh-_tree-line-top: var(--xh-tree-branch-gap, var(--xh-list-option-gap));
417
+ }
418
+
419
+ /* 展开着的非末位分支:行下面先是它自己的子层,竖线只伸到子层顶上,余下那段由子层接着画 */
420
+ .xh-scope-tree[data-part='tree'][data-lines] .xh-scope-tree[data-part='branch-content'][data-orientation='vertical'] > .xh-scope-tree[data-part='branch'] > .xh-scope-tree[data-part='branch-control'][data-state='open'] {
421
+ --xh-_tree-line-bottom: var(--xh-tree-branch-gap, var(--xh-list-option-gap));
422
+ }
423
+
424
+ .xh-scope-tree[data-part='tree'][data-lines] .xh-scope-tree[data-part='branch-content'][data-orientation='vertical'] > .xh-scope-tree[data-part='item']::before,
425
+ .xh-scope-tree[data-part='tree'][data-lines] .xh-scope-tree[data-part='branch-content'][data-orientation='vertical'] > .xh-scope-tree[data-part='branch'] > .xh-scope-tree[data-part='branch-control']::before {
426
+ content: '';
427
+ position: absolute;
428
+ inset-block: calc(var(--xh-_tree-line-top) * -1) calc(var(--xh-_tree-line-bottom) * -1);
429
+ inset-inline-start: var(--xh-_tree-line-x);
430
+ /* 分支横出到展开箭头盒的起点;叶子没有箭头,横线穿过占位那一格接到正文前 */
431
+ inline-size: calc(var(--xh-tree-row-px, var(--xh-_tree-row-px)) - var(--xh-_tree-line-x));
432
+ border-inline-start: var(--xh-stroke-thin) solid var(--xh-tree-line-color, var(--xh-border-subtle));
433
+ /* 横出的那段落在行的中线:线盒上下伸出的量不对称时按差值的一半补回 */
434
+ background: linear-gradient(var(--xh-tree-line-color, var(--xh-border-subtle)), var(--xh-tree-line-color, var(--xh-border-subtle)))
435
+ 0 calc(50% + (var(--xh-_tree-line-top) - var(--xh-_tree-line-bottom)) / 2) / 100% var(--xh-stroke-thin) no-repeat;
436
+ pointer-events: none;
437
+ }
438
+
439
+ .xh-scope-tree[data-part='tree'][data-lines] .xh-scope-tree[data-part='branch-content'][data-orientation='vertical'] > .xh-scope-tree[data-part='item']::before {
440
+ inline-size: calc(
441
+ var(--xh-tree-row-px, var(--xh-_tree-row-px)) +
442
+ var(--xh-_tree-indicator-size) -
443
+ var(--xh-_tree-line-x)
444
+ );
445
+ }
446
+
447
+ /* 最后一个子节点:竖线止于行的中线并在那里拐向行首(└),下面不再有兄弟要接 */
448
+ .xh-scope-tree[data-part='tree'][data-lines] .xh-scope-tree[data-part='branch-content'][data-orientation='vertical'] > .xh-scope-tree[data-part='item']:last-child::before,
449
+ .xh-scope-tree[data-part='tree'][data-lines] .xh-scope-tree[data-part='branch-content'][data-orientation='vertical'] > .xh-scope-tree[data-part='branch']:last-child > .xh-scope-tree[data-part='branch-control']::before {
450
+ inset-block-end: calc(50% - var(--xh-stroke-thin) / 2);
451
+ border-block-end: var(--xh-stroke-thin) solid var(--xh-tree-line-color, var(--xh-border-subtle));
452
+ background: none;
453
+ }
454
+
455
+ /* 展开着的非末位分支:它的子层把父节点那道竖线接过整棵子树,一直伸到下一个兄弟。
456
+ 子层的盒与分支同起点(缩进是子层自己的内边距),竖线位置与分支行里那一段相同 */
457
+ .xh-scope-tree[data-part='tree'][data-lines] .xh-scope-tree[data-part='branch-content'][data-orientation='vertical'] > .xh-scope-tree[data-part='branch']:not(:last-child) > .xh-scope-tree[data-part='branch-content']::after {
458
+ content: '';
459
+ position: absolute;
460
+ inset-block: 0 calc(var(--xh-tree-branch-content-gap, var(--xh-list-option-gap)) * -1);
461
+ inset-inline-start: calc(
462
+ var(--xh-tree-row-px, var(--xh-_tree-row-px)) +
463
+ var(--xh-tree-indicator-size, var(--xh-_tree-indicator)) / 2 -
464
+ var(--xh-stroke-thin) / 2 -
465
+ var(--xh-tree-indent, var(--xh-_tree-indent))
466
+ );
467
+ border-inline-start: var(--xh-stroke-thin) solid var(--xh-tree-line-color, var(--xh-border-subtle));
468
+ pointer-events: none;
469
+ }
470
+
471
+ /* 末端那一层横排:子节点并排铺开、必要时折行。缩进照旧由子层容器自己顶——
472
+ 那是这一层相对上一层的位置,与层内怎么排无关 */
473
+ .xh-scope-tree[data-part='branch-content'][data-orientation='horizontal'] {
474
+ flex-flow: row wrap;
475
+ align-items: center;
476
+ column-gap: var(--xh-tree-leaf-row-gap, var(--xh-space-3));
477
+ row-gap: var(--xh-tree-branch-content-gap, var(--xh-list-option-gap));
478
+ }
479
+
480
+ /* 横排的一层里节点并排,行盒不能再各自吃满一行 */
481
+ [data-orientation='horizontal'] > .xh-scope-tree[data-part='item'] {
482
+ flex: none;
483
+ }
484
+
485
+ /* 收起态的唯一开关:display 会盖掉 hidden 属性的 UA 规则,漏了这条整棵子树就收不起来。
486
+ 作者写的 hidden 与连接层发的 data-state=closed 都收在这里 */
487
+ .xh-scope-tree[data-part='branch-content'][hidden],
488
+ .xh-scope-tree[data-part='branch-content'][data-state='closed'] {
489
+ display: none;
490
+ }
491
+
492
+ /* 没写内容就由皮肤画兜底字形;作者往部件里塞了自己的图形,这几条即不命中。
493
+ 尺寸读部件自己改接过的 --xh-icon-size:chevron 与对号都与盒同边长(见上面两块) */
494
+ .xh-scope-tree[data-part='branch-trigger']:empty::before,
495
+ .xh-scope-tree[data-part='branch-indicator']:empty::before {
496
+ content: '';
497
+ display: inline-block;
498
+ inline-size: var(--xh-icon-size, var(--xh-glyph-size-text));
499
+ block-size: var(--xh-icon-size, var(--xh-glyph-size-text));
500
+ vertical-align: var(--xh-glyph-baseline-shift);
501
+ background-color: currentColor;
502
+ -webkit-mask: var(--xh-glyph-mark-chevron-right) center / contain no-repeat;
503
+ mask: var(--xh-glyph-mark-chevron-right) center / contain no-repeat;
504
+ }
505
+
506
+ /* 勾与半选杠叠成两层遮罩,按状态只换两层的尺寸:离开半选淡出的那一段里横杠留着,
507
+ 淡出播完才换回勾——不在淡出途中闪出一枚勾 */
508
+ .xh-scope-tree[data-part='item-indicator']:empty::before {
509
+ content: '';
510
+ display: inline-block;
511
+ inline-size: var(--xh-icon-size, var(--xh-glyph-size-text));
512
+ block-size: var(--xh-icon-size, var(--xh-glyph-size-text));
513
+ vertical-align: var(--xh-glyph-baseline-shift);
514
+ background-color: currentColor;
515
+ -webkit-mask: var(--xh-glyph-mark-check) center / 100% 100% no-repeat, var(--xh-glyph-mark-minus) center / 0 0 no-repeat;
516
+ mask: var(--xh-glyph-mark-check) center / 100% 100% no-repeat, var(--xh-glyph-mark-minus) center / 0 0 no-repeat;
517
+ transition:
518
+ -webkit-mask-size 0s var(--xh-motion-duration-micro),
519
+ mask-size 0s var(--xh-motion-duration-micro);
520
+ }
521
+
522
+ .xh-scope-tree[data-part='item-indicator'][data-selected]:empty::before {
523
+ transition-delay: 0s;
524
+ }
525
+
526
+ .xh-scope-tree[data-part='item-indicator'][data-indeterminate]:not([data-selected]):empty::before {
527
+ -webkit-mask-size: 0 0, 100% 100%;
528
+ mask-size: 0 0, 100% 100%;
529
+ transition-delay: 0s;
530
+ }
531
+
532
+ /* 这一枝在取子节点:展开箭头换成转圈,不再按开合转向;只换字形,颜色沿用箭头的弱前景 */
533
+ .xh-scope-tree[data-part='branch'][data-loading] > .xh-scope-tree[data-part='branch-control'] .xh-scope-tree[data-part='branch-trigger'],
534
+ .xh-scope-tree[data-part='branch'][data-loading] > .xh-scope-tree[data-part='branch-control'] .xh-scope-tree[data-part='branch-indicator'] {
535
+ rotate: none;
536
+ }
537
+
538
+ .xh-scope-tree[data-part='branch'][data-loading] > .xh-scope-tree[data-part='branch-control'] .xh-scope-tree[data-part='branch-trigger']:empty::before,
539
+ .xh-scope-tree[data-part='branch'][data-loading] > .xh-scope-tree[data-part='branch-control'] .xh-scope-tree[data-part='branch-indicator']:empty::before {
540
+ -webkit-mask-image: var(--xh-glyph-mark-rotate-right);
541
+ mask-image: var(--xh-glyph-mark-rotate-right);
542
+ animation: xh-spin var(--xh-motion-loop-spin) var(--xh-motion-ease-loop) infinite;
543
+ }
544
+
545
+ /* 减少动效:转圈停下,静止的箭头仍然读得出「还没好」,在途由分支的 aria-busy 念出。
546
+ 两组规则是同一件事的两个触发条件:系统偏好与作者打的 data-motion */
547
+ @media (prefers-reduced-motion: reduce) {
548
+ .xh-scope-tree[data-part='branch'][data-loading] > .xh-scope-tree[data-part='branch-control'] .xh-scope-tree[data-part='branch-trigger']:empty::before,
549
+ .xh-scope-tree[data-part='branch'][data-loading] > .xh-scope-tree[data-part='branch-control'] .xh-scope-tree[data-part='branch-indicator']:empty::before {
550
+ animation: none;
551
+ }
552
+ }
553
+
554
+ :where([data-motion='reduce']) .xh-scope-tree[data-part='branch'][data-loading] > .xh-scope-tree[data-part='branch-control'] .xh-scope-tree[data-part='branch-trigger']:empty::before,
555
+ :where([data-motion='reduce']) .xh-scope-tree[data-part='branch'][data-loading] > .xh-scope-tree[data-part='branch-control'] .xh-scope-tree[data-part='branch-indicator']:empty::before {
556
+ animation: none;
557
+ }
558
+
559
+ /* —— 节点拖拽搬家 —— */
560
+ /* 整行都是拖动源,光标换成抓手告诉人这一行按得动。
561
+ 这条不写 touch-action:整行关掉纵向手势,树容器自己就滚不动了;
562
+ 触屏那一路交给下面那块专属把手 */
563
+ .xh-scope-tree[data-part='item'][data-draggable],
564
+ .xh-scope-tree[data-part='branch-control'][data-draggable] {
565
+ cursor: grab;
566
+ }
567
+
568
+ /* 节点拖拽把手:行里的一小块专属拖动区,按下即拖。触屏靠它起手,
569
+ 代价只是这一小块地方不再跟着树滚。整行起手那一路原样保留,把手是叠加的第二个入口。
570
+ touch-action 不在这儿写:连接层按「拖不动就不写」把它写进了内联 style,
571
+ 皮肤再写一遍会盖掉那份条件判断,让不可拖的节点也白白让出滚动 */
572
+ .xh-scope-tree[data-part='node-drag-trigger'] {
573
+ display: inline-flex;
574
+ flex: none;
575
+ align-items: center;
576
+ justify-content: center;
577
+ inline-size: var(--xh-tree-drag-size, var(--xh-_tree-indicator));
578
+ block-size: var(--xh-tree-drag-size, var(--xh-_tree-indicator));
579
+ border-radius: var(--xh-tree-drag-radius, var(--xh-shape-control));
580
+ color: var(--xh-tree-drag-fg, var(--xh-fg-subtle));
581
+ cursor: grab;
582
+ }
583
+
584
+ .xh-scope-tree[data-part='node-drag-trigger'][hidden] {
585
+ display: none;
586
+ }
587
+
588
+ /* 抓手是两条横线:盒子取边框盒,块向两侧的描边就是那两条线,宽高与线宽都走令牌。
589
+ 线的走向与能拖的方向垂直——节点沿块向上下排,画的就是横线。
590
+ 用逻辑属性写,书写模式一换,线跟着排列轴自己转过去。
591
+ :empty 守卫让作者塞进来的图标整个顶掉它,而不是两份叠在一起 */
592
+ .xh-scope-tree[data-part='node-drag-trigger']:empty::after {
593
+ content: '';
594
+ display: block;
595
+ inline-size: var(--xh-tree-drag-grip-long, var(--xh-space-2));
596
+ block-size: var(--xh-tree-drag-grip-short, var(--xh-space-1));
597
+ border-block: var(--xh-stroke-thin) solid currentColor;
598
+ }
599
+
600
+ .xh-scope-tree[data-part='node-drag-trigger']:not([data-disabled]):hover,
601
+ .xh-scope-tree[data-part='node-drag-trigger'][data-dragging] {
602
+ color: var(--xh-tree-drag-fg-active, var(--xh-fg-default));
603
+ }
604
+
605
+ .xh-scope-tree[data-part='node-drag-trigger'][data-dragging] {
606
+ cursor: grabbing;
607
+ }
608
+
609
+ /* 不可拖的节点照样带一个把手,这里保住它的位置、只把它压到禁用色。
610
+ 收成 display: none 是不行的:把手占着行首那一格,藏掉之后同一层里能拖与不能拖的两个节点,
611
+ 文字起点会错开一格,层级关系跟着读歪 */
612
+ .xh-scope-tree[data-part='node-drag-trigger'][data-disabled] {
613
+ color: var(--xh-tree-drag-fg-disabled, var(--xh-fg-disabled));
614
+ }
615
+
616
+ /* 被拖的那一行压暗,让人看得出正在搬的是它。这里只动 opacity、cursor 与 user-select,
617
+ 一个 transform 都不写:行原地不动,要落到哪儿全交给落点指示 */
618
+ .xh-scope-tree[data-part='item'][data-dragging],
619
+ .xh-scope-tree[data-part='branch-control'][data-dragging] {
620
+ opacity: var(--xh-tree-dragging-opacity, var(--xh-state-dragging-opacity));
621
+ cursor: grabbing;
622
+ user-select: none;
623
+ }
624
+
625
+ /* 落点指示线:插到哪一行的哪一侧,横线就画在那一行的那条边上。
626
+ 定位上下文只给要画线的那一行:行平时都不定位,缺了它伪元素会一路上溯到最近的定位祖先,
627
+ 线就画到树外面去;写到所有行上则是给每一行都多担一份,而落点同一时刻只有一个。
628
+ 整块高亮那一档不在此列:它不靠伪元素,用不着这层上下文 */
629
+ .xh-scope-tree[data-part='item']:is([data-drop='before'], [data-drop='after']),
630
+ .xh-scope-tree[data-part='branch-control']:is([data-drop='before'], [data-drop='after']) {
631
+ position: relative;
632
+ }
633
+
634
+ /* 线贯穿整行宽度。行自身不收 overflow,线不会被行裁掉;
635
+ 但仍整条画在行内不压着边——压着边画的话,首行那条上沿线会被树容器的圆角与滚动裁掉半条 */
636
+ .xh-scope-tree[data-part='item']:is([data-drop='before'], [data-drop='after'])::after,
637
+ .xh-scope-tree[data-part='branch-control']:is([data-drop='before'], [data-drop='after'])::after {
638
+ content: '';
639
+ position: absolute;
640
+ inset-inline: 0;
641
+ block-size: var(--xh-tree-drop-line, var(--xh-stroke-thick));
642
+ background: var(--xh-tree-drop-fg, var(--xh-bg-brand));
643
+ }
644
+
645
+ /* 落在上沿还是下沿走逻辑属性,块向跟着书写模式走。
646
+ 换成 box-shadow 的偏移量则是物理的,写不出逻辑方向 */
647
+ .xh-scope-tree[data-part='item'][data-drop='before']::after,
648
+ .xh-scope-tree[data-part='branch-control'][data-drop='before']::after {
649
+ inset-block-start: 0;
650
+ }
651
+
652
+ .xh-scope-tree[data-part='item'][data-drop='after']::after,
653
+ .xh-scope-tree[data-part='branch-control'][data-drop='after']::after {
654
+ inset-block-end: 0;
655
+ }
656
+
657
+ /* 放进这个节点里面:整行淡底加一圈内描边,与上下沿那条横线一眼分得开。
658
+ 描边走零偏移的内投影,四边同时着色、不涉及方向,也跟着行的圆角走。
659
+ 选择器里那条 :not([data-disabled]) 是把特指度补到与悬停那条齐平,再靠排在它之后压过它:
660
+ 指针本来就停在落点这一行上,少了这一段,落点底色会被悬停那档灰盖掉。
661
+ 禁用的节点从来不做落点,这一段因此不会漏画任何一处高亮 */
662
+ .xh-scope-tree[data-part='item'][data-drop='inside']:not([data-disabled]),
663
+ .xh-scope-tree[data-part='branch-control'][data-drop='inside']:not([data-disabled]) {
664
+ /* 落点不是选中:品牌淡底专属选中 / 当前,落点面取悬停之上一档的中性面 */
665
+ background: var(--xh-tree-drop-inside-bg, var(--xh-bg-subtle-hover));
666
+ box-shadow: inset 0 0 0 var(--xh-tree-drop-line, var(--xh-stroke-thick))
667
+ var(--xh-tree-drop-fg, var(--xh-bg-brand));
668
+ }
669
+
670
+ /* 活动区域只说给读屏听,不占版面。声明与 shared/visually-hidden.ts 逐条同值。 */
671
+ .xh-scope-tree[data-part='live-region'] {
672
+ position: absolute;
673
+ inline-size: 1px;
674
+ block-size: 1px;
675
+ padding: 0;
676
+ margin: -1px;
677
+ overflow: hidden;
678
+ clip-path: inset(50%);
679
+ white-space: nowrap;
680
+ border: 0;
681
+ }
682
+
683
+ @media print {
684
+ /* 打印默认丢底色,而对号与半选杠是遮罩出来的一块底色:行本就不换面,丢了它选中在纸上就没了踪影 */
685
+ .xh-scope-tree[data-part='item-indicator'] {
686
+ print-color-adjust: exact;
687
+ }
688
+
689
+ /* 转了一半的圈定格在纸上没有意义,停下 */
690
+ .xh-scope-tree[data-part='branch'][data-loading] > .xh-scope-tree[data-part='branch-control'] .xh-scope-tree[data-part='branch-trigger']:empty::before,
691
+ .xh-scope-tree[data-part='branch'][data-loading] > .xh-scope-tree[data-part='branch-control'] .xh-scope-tree[data-part='branch-indicator']:empty::before {
692
+ animation: none;
693
+ }
694
+ }
695
+
696
+ /* 强制色:连接线的横段是一块渐变底色、竖段是描边,系统色下一个被抹掉、一个被换成文字色;
697
+ 两段都写 GrayText,并只让连接线不参与强制换色 */
698
+ @media (forced-colors: active) {
699
+ .xh-scope-tree[data-part='tree'][data-lines] .xh-scope-tree[data-part='branch-content'][data-orientation='vertical'] > .xh-scope-tree[data-part='item']::before,
700
+ .xh-scope-tree[data-part='tree'][data-lines] .xh-scope-tree[data-part='branch-content'][data-orientation='vertical'] > .xh-scope-tree[data-part='branch'] > .xh-scope-tree[data-part='branch-control']::before {
701
+ border-color: GrayText;
702
+ background-image: linear-gradient(GrayText, GrayText);
703
+ forced-color-adjust: none;
704
+ }
705
+
706
+ .xh-scope-tree[data-part='tree'][data-lines] .xh-scope-tree[data-part='branch-content'][data-orientation='vertical'] > .xh-scope-tree[data-part='branch'] > .xh-scope-tree[data-part='branch-content']::after {
707
+ border-color: GrayText;
708
+ forced-color-adjust: none;
709
+ }
710
+ }
711
+
712
+ @media (forced-colors: active) {
713
+ /* 兜底字形是 mask 挖出来的一块底色,这一档里底色被系统换成 Canvas,字形跟着消失。
714
+ 字形退出强制换色、直接写系统色(不能取 currentColor:退出强制换色的伪元素读到的是被系统换掉之前的作者色):
715
+ 条目面上取 CanvasText;按下与没被高亮时的悬停,家族把条目涂成 Highlight,字形换 HighlightText;
716
+ 禁用取 GrayText */
717
+ .xh-scope-tree:is([data-part='branch-trigger'], [data-part='branch-indicator']):empty::before,
718
+ .xh-scope-tree[data-part='item-indicator']:empty::before {
719
+ forced-color-adjust: none;
720
+ background-color: CanvasText;
721
+ }
722
+
723
+ .xh-scope-tree[data-part='branch-control']:not([aria-disabled='true'], [data-disabled], [aria-busy='true'], [data-error]):is(:active, [data-pressed], :hover:not(:focus-visible, [data-highlighted])) .xh-scope-tree:is([data-part='branch-trigger'], [data-part='branch-indicator']):empty::before,
724
+ .xh-scope-tree[data-part='item']:not([aria-disabled='true'], [data-disabled], [aria-busy='true'], [data-error]):is(:active, [data-pressed], :hover:not(:focus-visible, [data-highlighted])) .xh-scope-tree[data-part='item-indicator']:empty::before {
725
+ background-color: HighlightText;
726
+ }
727
+
728
+ .xh-scope-tree[data-part='branch-control']:is([aria-disabled='true'], [data-disabled]) .xh-scope-tree:is([data-part='branch-trigger'], [data-part='branch-indicator']):empty::before,
729
+ .xh-scope-tree[data-part='item']:is([aria-disabled='true'], [data-disabled]) .xh-scope-tree[data-part='item-indicator']:empty::before {
730
+ background-color: GrayText;
731
+ }
732
+ }
733
+ }