@xihan-ui/styles 1.0.0-alpha.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 (114) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/LICENSE +21 -0
  3. package/README.md +27 -0
  4. package/css/accordion.css +108 -0
  5. package/css/affix.css +27 -0
  6. package/css/alert.css +108 -0
  7. package/css/anchor.css +137 -0
  8. package/css/avatar-group.css +82 -0
  9. package/css/avatar.css +55 -0
  10. package/css/back-top.css +61 -0
  11. package/css/badge.css +50 -0
  12. package/css/breadcrumb.css +127 -0
  13. package/css/button-group.css +131 -0
  14. package/css/button.css +98 -0
  15. package/css/calendar.css +219 -0
  16. package/css/card.css +116 -0
  17. package/css/carousel.css +193 -0
  18. package/css/cascader.css +397 -0
  19. package/css/checkbox-group.css +185 -0
  20. package/css/checkbox.css +91 -0
  21. package/css/clipboard.css +133 -0
  22. package/css/code-block.css +81 -0
  23. package/css/collapsible.css +63 -0
  24. package/css/color-picker.css +424 -0
  25. package/css/combobox.css +433 -0
  26. package/css/composer.css +127 -0
  27. package/css/context-menu.css +263 -0
  28. package/css/countdown.css +21 -0
  29. package/css/date-field.css +200 -0
  30. package/css/date-picker.css +231 -0
  31. package/css/descriptions.css +200 -0
  32. package/css/dialog.css +127 -0
  33. package/css/drawer.css +372 -0
  34. package/css/dynamic-input.css +157 -0
  35. package/css/editable.css +249 -0
  36. package/css/ellipsis.css +41 -0
  37. package/css/empty-state.css +78 -0
  38. package/css/field.css +65 -0
  39. package/css/file-upload.css +298 -0
  40. package/css/flex.css +95 -0
  41. package/css/float-button.css +121 -0
  42. package/css/form.css +185 -0
  43. package/css/gradient-text.css +62 -0
  44. package/css/grid.css +428 -0
  45. package/css/highlight.css +21 -0
  46. package/css/hover-card.css +146 -0
  47. package/css/icon-wrapper.css +64 -0
  48. package/css/icon.css +54 -0
  49. package/css/image.css +45 -0
  50. package/css/infinite-scroll.css +22 -0
  51. package/css/layers.css +2 -0
  52. package/css/layout.css +169 -0
  53. package/css/list.css +117 -0
  54. package/css/listbox.css +174 -0
  55. package/css/loading-bar.css +66 -0
  56. package/css/log.css +66 -0
  57. package/css/marquee.css +182 -0
  58. package/css/mention.css +222 -0
  59. package/css/menu.css +144 -0
  60. package/css/menubar.css +271 -0
  61. package/css/motion.css +58 -0
  62. package/css/navigation-menu.css +270 -0
  63. package/css/number-animation.css +32 -0
  64. package/css/number-field.css +219 -0
  65. package/css/page-header.css +85 -0
  66. package/css/pagination.css +148 -0
  67. package/css/pin-input.css +157 -0
  68. package/css/popconfirm.css +150 -0
  69. package/css/popover.css +150 -0
  70. package/css/popselect.css +207 -0
  71. package/css/progress.css +37 -0
  72. package/css/qr-code.css +50 -0
  73. package/css/radio-group.css +101 -0
  74. package/css/rating.css +141 -0
  75. package/css/reset.css +22 -0
  76. package/css/result.css +104 -0
  77. package/css/scroll-area.css +176 -0
  78. package/css/select.css +264 -0
  79. package/css/separator.css +17 -0
  80. package/css/skeleton.css +78 -0
  81. package/css/slider.css +197 -0
  82. package/css/spinner.css +74 -0
  83. package/css/splitter.css +127 -0
  84. package/css/statistic.css +67 -0
  85. package/css/steps.css +252 -0
  86. package/css/switch.css +61 -0
  87. package/css/table.css +404 -0
  88. package/css/tabs.css +157 -0
  89. package/css/tags-input.css +360 -0
  90. package/css/text-field.css +195 -0
  91. package/css/thread.css +107 -0
  92. package/css/time-field.css +200 -0
  93. package/css/time-picker.css +347 -0
  94. package/css/time.css +20 -0
  95. package/css/timeline.css +284 -0
  96. package/css/toast.css +195 -0
  97. package/css/toaster.css +69 -0
  98. package/css/toggle-group.css +160 -0
  99. package/css/toggle.css +103 -0
  100. package/css/tone.css +85 -0
  101. package/css/toolbar.css +114 -0
  102. package/css/tooltip.css +109 -0
  103. package/css/tour.css +305 -0
  104. package/css/transfer.css +318 -0
  105. package/css/tree-select.css +441 -0
  106. package/css/tree.css +204 -0
  107. package/css/typography.css +151 -0
  108. package/css/undefined.css +38 -0
  109. package/css/virtualizer.css +104 -0
  110. package/css/visually-hidden.css +13 -0
  111. package/css/watermark.css +48 -0
  112. package/index.css +113 -0
  113. package/index.unlayered.css +16164 -0
  114. package/package.json +163 -0
@@ -0,0 +1,441 @@
1
+ @layer xihan.components {
2
+ /* 解剖里没有把「触发器 + 清空按钮」裹在一起的 control 部件,一行是靠 flex-wrap 分出来的:
3
+ 标签占满整行把后面的挤到下一行,触发器与清空按钮就并排落在同一行上。
4
+ 这样作者少写标签、少写清空按钮,剩下的部件也不会塌 */
5
+ [data-scope='tree-select'][data-part='root'] {
6
+ /* 尺寸档只换这几个私有槽,触发框与树里的行都从 root 继承它们,换档不必给每个部件各写一条选择器 */
7
+ --xh-_tree-select-h: var(--xh-control-h-md);
8
+ --xh-_tree-select-px: var(--xh-control-px-md);
9
+ --xh-_tree-select-gap: var(--xh-control-gap-md);
10
+ --xh-_tree-select-font-size: var(--xh-text-body-size);
11
+ --xh-_tree-select-label-font-size: var(--xh-text-label-size);
12
+
13
+ /* 形态档只换触发框的描边与底色这三个槽,浮层与树不跟着变:
14
+ 变的是「这是个输入位」的表达方式,不是弹出内容的表达方式 */
15
+ --xh-_tree-select-border: var(--xh-border-default);
16
+ --xh-_tree-select-border-hover: var(--xh-border-strong);
17
+ --xh-_tree-select-bg: var(--xh-bg-canvas);
18
+
19
+ /* 选中行的文字色随语气走,没写 data-tone 时退回品牌强调色 */
20
+ --xh-_tree-select-selected-fg: var(--xh-_tone-fg, var(--xh-fg-brand-strong));
21
+
22
+ display: inline-flex;
23
+ flex-wrap: wrap;
24
+ align-items: center;
25
+ gap: var(--xh-tree-select-gap, var(--xh-space-1));
26
+ }
27
+
28
+ /* 只要声明了 display 就盖掉了 UA 的 [hidden]{display:none},作者手动收起时要自己还回去 */
29
+ [data-scope='tree-select'][data-part='root'][hidden] {
30
+ display: none;
31
+ }
32
+
33
+ /* —— 形态 —— */
34
+ /* 形态只决定描边与底色怎么用;用哪族颜色由 data-tone 决定,没写 tone 就沿用原来的中性配色 */
35
+ /* outline 就是缺省那一身:描边加画布底,写出来只为让三档并列可读 */
36
+ [data-scope='tree-select'][data-part='root'][data-variant='outline'] {
37
+ --xh-_tree-select-border: var(--xh-border-default);
38
+ --xh-_tree-select-border-hover: var(--xh-border-strong);
39
+ --xh-_tree-select-bg: var(--xh-bg-canvas);
40
+ }
41
+
42
+ /* subtle 是填充式:底色接过「这里能点」的表达,描边随之让位 */
43
+ [data-scope='tree-select'][data-part='root'][data-variant='subtle'] {
44
+ --xh-_tree-select-border: transparent;
45
+ --xh-_tree-select-border-hover: var(--xh-_tone-border, var(--xh-border-default));
46
+ --xh-_tree-select-bg: var(--xh-_tone-subtle, var(--xh-bg-subtle));
47
+ }
48
+
49
+ /* ghost 无框无底,可见反馈只在聚焦时给;边框宽度仍占位,切换形态不引起布局抖动 */
50
+ [data-scope='tree-select'][data-part='root'][data-variant='ghost'] {
51
+ --xh-_tree-select-border: transparent;
52
+ --xh-_tree-select-border-hover: transparent;
53
+ --xh-_tree-select-bg: transparent;
54
+ }
55
+
56
+ /* —— 尺寸 —— */
57
+ /* 缺省档写在上面的基础规则里 */
58
+ [data-scope='tree-select'][data-part='root'][data-size='sm'] {
59
+ --xh-_tree-select-h: var(--xh-control-h-sm);
60
+ --xh-_tree-select-px: var(--xh-control-px-sm);
61
+ --xh-_tree-select-gap: var(--xh-control-gap-sm);
62
+ --xh-_tree-select-font-size: var(--xh-font-size-sm);
63
+ --xh-_tree-select-label-font-size: var(--xh-font-size-sm);
64
+ }
65
+
66
+ [data-scope='tree-select'][data-part='root'][data-size='lg'] {
67
+ --xh-_tree-select-h: var(--xh-control-h-lg);
68
+ --xh-_tree-select-px: var(--xh-control-px-lg);
69
+ --xh-_tree-select-gap: var(--xh-control-gap-lg);
70
+ --xh-_tree-select-font-size: var(--xh-font-size-lg);
71
+ --xh-_tree-select-label-font-size: var(--xh-font-size-lg);
72
+ }
73
+
74
+ [data-scope='tree-select'][data-part='label'] {
75
+ /* 占满整行,把触发器那一行挤下去 */
76
+ flex-basis: 100%;
77
+ color: var(--xh-tree-select-label-fg, var(--xh-fg-muted));
78
+ font-size: var(--xh-tree-select-label-font-size, var(--xh-_tree-select-label-font-size));
79
+ font-weight: var(--xh-tree-select-label-font-weight, var(--xh-text-label-weight));
80
+ line-height: var(--xh-leading-none);
81
+ }
82
+
83
+ /* 整个控件禁用会把 data-disabled 一路打到 label / value-text / indicator 上,各处观感保持一致 */
84
+ [data-scope='tree-select'][data-part='label'][data-disabled] {
85
+ color: var(--xh-fg-subtle);
86
+ }
87
+
88
+ /* 画成一个框的就是 trigger 自己(不像 combobox 那样另有 control):
89
+ 值回显与展开箭头都长在它里面,清空按钮贴在框外与它并排 */
90
+ [data-scope='tree-select'][data-part='trigger'] {
91
+ display: inline-flex;
92
+ flex: 1;
93
+ align-items: center;
94
+ justify-content: space-between;
95
+ gap: var(--xh-tree-select-trigger-gap, var(--xh-_tree-select-gap));
96
+ min-inline-size: var(--xh-tree-select-trigger-min-w, 12rem);
97
+ block-size: var(--xh-tree-select-trigger-h, var(--xh-_tree-select-h));
98
+ padding-inline: var(--xh-tree-select-trigger-px, var(--xh-_tree-select-px));
99
+ border: var(--xh-stroke-thin) solid var(--xh-tree-select-trigger-border, var(--xh-_tree-select-border));
100
+ border-radius: var(--xh-tree-select-trigger-radius, var(--xh-shape-control));
101
+ background: var(--xh-tree-select-trigger-bg, var(--xh-_tree-select-bg));
102
+ color: var(--xh-tree-select-trigger-fg, var(--xh-fg-default));
103
+ font-family: inherit;
104
+ font-size: var(--xh-tree-select-trigger-font-size, var(--xh-_tree-select-font-size));
105
+ line-height: var(--xh-leading-none);
106
+ text-align: start;
107
+ cursor: pointer;
108
+ transition: border-color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
109
+ }
110
+
111
+ [data-scope='tree-select'][data-part='trigger'][hidden] {
112
+ display: none;
113
+ }
114
+
115
+ /* hover 只在中性态换描边:invalid 正拿边框颜色说事,被 hover 盖掉那条信息就没了 */
116
+ [data-scope='tree-select'][data-part='trigger']:hover:not(:disabled, [data-invalid]) {
117
+ border-color: var(--xh-tree-select-trigger-border-hover, var(--xh-_tree-select-border-hover));
118
+ }
119
+
120
+ /* trigger 是整个控件唯一的 Tab 停靠点(展开后停靠点才移进树里),落焦必须看得见。
121
+ 语气落在环上:没写 data-tone 时 --xh-_tone 没被声明,退回原来的聚焦环色 */
122
+ [data-scope='tree-select'][data-part='trigger']:focus-visible {
123
+ outline: var(--xh-ring-width) solid var(--xh-_tone, var(--xh-ring-focus));
124
+ outline-offset: var(--xh-ring-offset);
125
+ }
126
+
127
+ [data-scope='tree-select'][data-part='trigger'][data-invalid] {
128
+ border-color: var(--xh-tree-select-trigger-border-invalid, var(--xh-color-danger-500));
129
+ }
130
+
131
+ /* 只读仍可聚焦、浮层仍展得开、树仍能浏览,所以不置灰,只把底色压下去表示「改不动」 */
132
+ [data-scope='tree-select'][data-part='trigger'][data-readonly] {
133
+ background: var(--xh-tree-select-trigger-bg-readonly, var(--xh-bg-subtle));
134
+ }
135
+
136
+ /* trigger 是单体控件,用原生 disabled(树里的集合条目一律 aria-disabled,写 :disabled 永不命中) */
137
+ [data-scope='tree-select'][data-part='trigger']:disabled {
138
+ background: var(--xh-tree-select-trigger-bg-disabled, var(--xh-bg-subtle));
139
+ color: var(--xh-fg-disabled);
140
+ cursor: not-allowed;
141
+ }
142
+
143
+ /* 多选时值会连成很长一串,必须能截断;框宽由 trigger 定,这里得允许自己被压缩 */
144
+ [data-scope='tree-select'][data-part='value-text'] {
145
+ flex: 1;
146
+ min-inline-size: 0;
147
+ overflow: hidden;
148
+ /* 行高留出文字的上下伸展空间,本行那层 overflow:hidden 才不会把字削掉;
149
+ 触发框自带固定高度,抬行高不改变几何 */
150
+ line-height: var(--xh-tree-select-value-leading, var(--xh-leading-normal));
151
+ white-space: nowrap;
152
+ text-overflow: ellipsis;
153
+ }
154
+
155
+ /* 无选中:显示的是 placeholder,画淡一档 */
156
+ [data-scope='tree-select'][data-part='value-text'][data-placeholder] {
157
+ color: var(--xh-tree-select-placeholder-fg, var(--xh-fg-subtle));
158
+ }
159
+
160
+ [data-scope='tree-select'][data-part='value-text'][data-disabled] {
161
+ color: var(--xh-fg-disabled);
162
+ }
163
+
164
+ [data-scope='tree-select'][data-part='indicator'] {
165
+ display: inline-flex;
166
+ flex: none;
167
+ align-items: center;
168
+ justify-content: center;
169
+ color: var(--xh-tree-select-indicator-fg, var(--xh-fg-muted));
170
+ transition: rotate var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
171
+ }
172
+
173
+ [data-scope='tree-select'][data-part='indicator'][hidden] {
174
+ display: none;
175
+ }
176
+
177
+ [data-scope='tree-select'][data-part='indicator'][data-state='open'] {
178
+ rotate: 180deg;
179
+ }
180
+
181
+ [data-scope='tree-select'][data-part='indicator'][data-disabled] {
182
+ color: var(--xh-fg-disabled);
183
+ }
184
+
185
+ /* 贴在触发框旁边的小方钮,不撑高那一行 */
186
+ [data-scope='tree-select'][data-part='clear-trigger'] {
187
+ display: inline-flex;
188
+ flex: none;
189
+ align-items: center;
190
+ justify-content: center;
191
+ inline-size: var(--xh-tree-select-action-size, var(--xh-control-action-size));
192
+ block-size: var(--xh-tree-select-action-size, var(--xh-control-action-size));
193
+ padding: 0;
194
+ border: 0;
195
+ border-radius: var(--xh-tree-select-action-radius, var(--xh-shape-control));
196
+ background: var(--xh-tree-select-action-bg, transparent);
197
+ color: var(--xh-tree-select-action-fg, var(--xh-fg-muted));
198
+ font-family: inherit;
199
+ font-size: var(--xh-tree-select-action-font-size, var(--xh-font-size-sm));
200
+ line-height: var(--xh-leading-none);
201
+ cursor: pointer;
202
+ user-select: none;
203
+ transition: background var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
204
+ }
205
+
206
+ [data-scope='tree-select'][data-part='clear-trigger'][hidden] {
207
+ display: none;
208
+ }
209
+
210
+ [data-scope='tree-select'][data-part='clear-trigger']:hover:not(:disabled) {
211
+ background: var(--xh-tree-select-action-bg-hover, var(--xh-bg-subtle-hover));
212
+ color: var(--xh-tree-select-action-fg-hover, var(--xh-fg-default));
213
+ }
214
+
215
+ [data-scope='tree-select'][data-part='clear-trigger']:active:not(:disabled) {
216
+ background: var(--xh-tree-select-action-bg-active, var(--xh-bg-subtle-active));
217
+ }
218
+
219
+ /* 清空按钮退出了 Tab 序列(tabindex=-1),正常操作走不到这里;
220
+ 留着是给程序化聚焦兜底,落焦时得看得见 */
221
+ [data-scope='tree-select'][data-part='clear-trigger']:focus-visible {
222
+ outline: var(--xh-ring-width) solid var(--xh-_tone, var(--xh-ring-focus));
223
+ outline-offset: var(--xh-ring-offset);
224
+ }
225
+
226
+ /* 无选中、只读、整控件禁用三种情况下都按不动,用的是原生 disabled */
227
+ [data-scope='tree-select'][data-part='clear-trigger']:disabled {
228
+ background: var(--xh-tree-select-action-bg, transparent);
229
+ color: var(--xh-fg-disabled);
230
+ cursor: not-allowed;
231
+ }
232
+
233
+ /* 坐标由定位引擎写进 style(inset-inline-start / inset-block-start),这里只定位模式与层级 */
234
+ [data-scope='tree-select'][data-part='positioner'] {
235
+ position: fixed;
236
+ z-index: var(--xh-layer-popover);
237
+ /* 空壳不吃指针,交互只发生在 content 里 */
238
+ pointer-events: none;
239
+ }
240
+
241
+ /* 锚点被滚出可视区:整个浮层随之收起 */
242
+ [data-scope='tree-select'][data-part='positioner'][data-hidden] {
243
+ visibility: hidden;
244
+ }
245
+
246
+ [data-scope='tree-select'][data-part='content'] {
247
+ display: flex;
248
+ flex-direction: column;
249
+ min-inline-size: var(--xh-tree-select-content-min-w, var(--xh-overlay-min-w));
250
+ max-inline-size: var(--xh-tree-select-content-max-w, var(--xh-overlay-max-w));
251
+ max-block-size: var(--xh-tree-select-content-max-h, var(--xh-overlay-max-h));
252
+ padding-block: var(--xh-tree-select-content-py, var(--xh-space-1));
253
+ padding-inline: var(--xh-tree-select-content-px, var(--xh-space-1));
254
+ border: var(--xh-stroke-thin) solid var(--xh-tree-select-content-border, var(--xh-border-default));
255
+ border-radius: var(--xh-tree-select-content-radius, var(--xh-shape-surface));
256
+ background: var(--xh-tree-select-content-bg, var(--xh-bg-surface));
257
+ color: var(--xh-tree-select-content-fg, var(--xh-fg-default));
258
+ box-shadow: var(--xh-tree-select-content-shadow, var(--xh-elevation-2));
259
+ /* 两轴一起给:深层节点靠缩进往行末推,横向溢出是常态,得够得着 */
260
+ overflow: auto;
261
+ pointer-events: auto;
262
+ }
263
+
264
+ /* 收起态的唯一开关:display 盖掉了 UA 的 [hidden]{display:none},漏了这条浮层就收不起来 */
265
+ [data-scope='tree-select'][data-part='content'][hidden] {
266
+ display: none;
267
+ }
268
+
269
+ /* 容器只是焦点域的兜底落点,落焦不画环:高亮永远长在行上 */
270
+ [data-scope='tree-select'][data-part='content']:focus-visible {
271
+ outline: none;
272
+ }
273
+
274
+ [data-scope='tree-select'][data-part='content'][data-state='open'] {
275
+ animation: xh-fade-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter);
276
+ }
277
+
278
+ /* 边框、底色与滚动都归 content,树本身只把行竖着排开 */
279
+ [data-scope='tree-select'][data-part='tree'] {
280
+ display: flex;
281
+ flex-direction: column;
282
+ }
283
+
284
+ [data-scope='tree-select'][data-part='tree'][hidden] {
285
+ display: none;
286
+ }
287
+
288
+ /* 没有锚点时容器自己进 Tab 序列兜底,落焦不画环:高亮永远长在行上 */
289
+ [data-scope='tree-select'][data-part='tree']:focus-visible {
290
+ outline: none;
291
+ }
292
+
293
+ /* 焦点落在 branch 上(它才是 treeitem),但环画在它自己那一行上:
294
+ 整棵子树套一圈描边会把展开的层级全框进去 */
295
+ [data-scope='tree-select'][data-part='branch']:focus-visible {
296
+ outline: none;
297
+ }
298
+
299
+ [data-scope='tree-select'][data-part='branch']:focus-visible > [data-scope='tree-select'][data-part='branch-control'] {
300
+ outline: var(--xh-ring-width) solid var(--xh-_tone, var(--xh-ring-focus));
301
+ /* 往内收:行是撑满宽度的,环画在外沿会被容器裁掉 */
302
+ outline-offset: calc(-1 * var(--xh-ring-width));
303
+ }
304
+
305
+ /* 行:叶子与分支共用同一套盒子,两种行在视觉上必须齐平 */
306
+ [data-scope='tree-select'][data-part='item'],
307
+ [data-scope='tree-select'][data-part='branch-control'] {
308
+ display: flex;
309
+ align-items: center;
310
+ gap: var(--xh-tree-select-row-gap, var(--xh-_tree-select-gap));
311
+ padding-block: var(--xh-tree-select-row-py, var(--xh-space-1));
312
+ padding-inline: var(--xh-tree-select-row-px, var(--xh-control-px-sm));
313
+ border-radius: var(--xh-tree-select-row-radius, var(--xh-shape-control));
314
+ color: var(--xh-tree-select-row-fg, var(--xh-fg-default));
315
+ font-size: var(--xh-tree-select-row-font-size, var(--xh-_tree-select-font-size));
316
+ /* 行高留出文字的上下伸展空间,行内那层 overflow:hidden 才不会把字削掉 */
317
+ line-height: var(--xh-tree-select-row-leading, var(--xh-leading-normal));
318
+ white-space: nowrap;
319
+ cursor: pointer;
320
+ user-select: none;
321
+ }
322
+
323
+ [data-scope='tree-select'][data-part='item'][hidden],
324
+ [data-scope='tree-select'][data-part='branch-control'][hidden] {
325
+ display: none;
326
+ }
327
+
328
+ /* 键盘高亮与指针高亮共用一套底色。data-highlighted 跟的是连接层记的焦点行,
329
+ 与选中互相独立:可以停在一个没被选中的节点上 */
330
+ [data-scope='tree-select'][data-part='item']:not([data-disabled]):is(:hover, [data-highlighted]),
331
+ [data-scope='tree-select'][data-part='branch-control']:not([data-disabled]):is(:hover, [data-highlighted]) {
332
+ background: var(--xh-tree-select-row-bg-highlight, var(--xh-bg-subtle-hover));
333
+ }
334
+
335
+ /* 选中只换文字色与字重,配合行尾的 ✓ 标记;底色整条通道留给高亮,
336
+ 两者因此能同时看见 */
337
+ [data-scope='tree-select'][data-part='item'][data-selected],
338
+ [data-scope='tree-select'][data-part='branch-control'][data-selected] {
339
+ color: var(--xh-tree-select-row-fg-selected, var(--xh-_tree-select-selected-fg));
340
+ font-weight: var(--xh-tree-select-row-selected-font-weight, var(--xh-font-weight-medium));
341
+ }
342
+
343
+ /* 叶子自己就是 treeitem,环直接画在它身上 */
344
+ [data-scope='tree-select'][data-part='item']:focus-visible {
345
+ outline: var(--xh-ring-width) solid var(--xh-_tone, var(--xh-ring-focus));
346
+ outline-offset: calc(-1 * var(--xh-ring-width));
347
+ }
348
+
349
+ /* 禁用走 aria-disabled 而非原生 disabled:节点仍要可聚焦(它还得当方向键的起点),
350
+ 所以只能靠 data-disabled 表达,写 :disabled 永远不命中 */
351
+ [data-scope='tree-select'][data-part='item'][data-disabled],
352
+ [data-scope='tree-select'][data-part='branch-control'][data-disabled] {
353
+ color: var(--xh-fg-disabled);
354
+ cursor: not-allowed;
355
+ }
356
+
357
+ [data-scope='tree-select'][data-part='item-text'],
358
+ [data-scope='tree-select'][data-part='branch-text'] {
359
+ flex: 1;
360
+ min-inline-size: 0;
361
+ overflow: hidden;
362
+ white-space: nowrap;
363
+ text-overflow: ellipsis;
364
+ }
365
+
366
+ /* 勾选标记只在选中项上出现;未选中时占位不塌,切换选中不引起行宽抖动 */
367
+ [data-scope='tree-select'][data-part='item-indicator'] {
368
+ display: inline-flex;
369
+ flex: none;
370
+ align-items: center;
371
+ justify-content: center;
372
+ inline-size: var(--xh-tree-select-indicator-size, var(--xh-control-indicator-size));
373
+ color: var(--xh-tree-select-item-indicator-fg, var(--xh-_tone, var(--xh-bg-brand)));
374
+ visibility: hidden;
375
+ }
376
+
377
+ [data-scope='tree-select'][data-part='item-indicator'][hidden] {
378
+ display: none;
379
+ }
380
+
381
+ [data-scope='tree-select'][data-part='item-indicator'][data-selected] {
382
+ visibility: visible;
383
+ }
384
+
385
+ [data-scope='tree-select'][data-part='item-indicator'][data-disabled] {
386
+ color: var(--xh-fg-disabled);
387
+ }
388
+
389
+ /* 展开箭头与方向指示符:两者都可能被作者用作「点这里展开」的把手,
390
+ 所以尺寸与叶子的勾选标记一致,两种行的文字起点才对得齐 */
391
+ [data-scope='tree-select'][data-part='branch-trigger'],
392
+ [data-scope='tree-select'][data-part='branch-indicator'] {
393
+ display: inline-flex;
394
+ flex: none;
395
+ align-items: center;
396
+ justify-content: center;
397
+ inline-size: var(--xh-tree-select-indicator-size, var(--xh-control-indicator-size));
398
+ color: var(--xh-tree-select-branch-indicator-fg, var(--xh-fg-subtle));
399
+ transition: rotate var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
400
+ }
401
+
402
+ [data-scope='tree-select'][data-part='branch-trigger'][hidden],
403
+ [data-scope='tree-select'][data-part='branch-indicator'][hidden] {
404
+ display: none;
405
+ }
406
+
407
+ /* 收起时朝向行首、展开时转向下方,与逻辑书写方向一致:
408
+ rtl 下 inline-start 在右侧,箭头因此要反着转 */
409
+ [data-scope='tree-select'][data-part='branch-trigger'][data-state='open'],
410
+ [data-scope='tree-select'][data-part='branch-indicator'][data-state='open'] {
411
+ rotate: 90deg;
412
+ }
413
+
414
+ [dir='rtl'] [data-scope='tree-select'][data-part='branch-trigger'],
415
+ [dir='rtl'] [data-scope='tree-select'][data-part='branch-indicator'] {
416
+ rotate: 180deg;
417
+ }
418
+
419
+ [dir='rtl'] [data-scope='tree-select'][data-part='branch-trigger'][data-state='open'],
420
+ [dir='rtl'] [data-scope='tree-select'][data-part='branch-indicator'][data-state='open'] {
421
+ rotate: 90deg;
422
+ }
423
+
424
+ [data-scope='tree-select'][data-part='branch-trigger'][data-disabled],
425
+ [data-scope='tree-select'][data-part='branch-indicator'][data-disabled] {
426
+ color: var(--xh-fg-disabled);
427
+ }
428
+
429
+ /* 层级缩进靠子层容器自己顶:分支物理嵌套着子层,每深一层就多缩一格,
430
+ 无需把层级号写进内联样式,也就不必在两个适配器里各写一遍 */
431
+ [data-scope='tree-select'][data-part='branch-content'] {
432
+ display: flex;
433
+ flex-direction: column;
434
+ padding-inline-start: var(--xh-tree-select-indent, var(--xh-space-4));
435
+ }
436
+
437
+ /* 收起态的唯一开关:display 盖掉了 UA 的 [hidden]{display:none},漏了这条整棵子树就收不起来 */
438
+ [data-scope='tree-select'][data-part='branch-content'][hidden] {
439
+ display: none;
440
+ }
441
+ }
package/css/tree.css ADDED
@@ -0,0 +1,204 @@
1
+ @layer xihan.components {
2
+ [data-scope='tree'][data-part='root'] {
3
+ display: flex;
4
+ flex-direction: column;
5
+ gap: var(--xh-tree-gap, var(--xh-space-2));
6
+ }
7
+
8
+ [data-scope='tree'][data-part='root'][hidden] {
9
+ display: none;
10
+ }
11
+
12
+ [data-scope='tree'][data-part='label'] {
13
+ color: var(--xh-tree-label-fg, var(--xh-fg-muted));
14
+ font-size: var(--xh-tree-label-font-size, var(--xh-text-label-size));
15
+ font-weight: var(--xh-tree-label-font-weight, var(--xh-text-label-weight));
16
+ line-height: var(--xh-leading-none);
17
+ }
18
+
19
+ /* 整棵树禁用会把 data-disabled 一路打到 label / tree 上,各处观感保持一致 */
20
+ [data-scope='tree'][data-part='label'][data-disabled] {
21
+ color: var(--xh-fg-subtle);
22
+ }
23
+
24
+ /* 常驻容器不是浮层:留边框与底色,但不投影、不脱离文档流,滚动发生在容器自身 */
25
+ [data-scope='tree'][data-part='tree'] {
26
+ display: flex;
27
+ flex-direction: column;
28
+ max-block-size: var(--xh-tree-max-h, 20rem);
29
+ padding-block: var(--xh-tree-py, var(--xh-space-1));
30
+ padding-inline: var(--xh-tree-px, var(--xh-space-1));
31
+ border: var(--xh-stroke-thin) solid var(--xh-tree-border, var(--xh-border-default));
32
+ border-radius: var(--xh-tree-radius, var(--xh-shape-surface));
33
+ background: var(--xh-tree-bg, var(--xh-bg-surface));
34
+ color: var(--xh-tree-fg, var(--xh-fg-default));
35
+ /* 两轴一起给:深层节点靠缩进往右推,横向溢出是常态,得够得着 */
36
+ overflow: auto;
37
+ }
38
+
39
+ /* display 会盖掉 hidden 属性的 UA 规则,作者手动收起时要显式让位 */
40
+ [data-scope='tree'][data-part='tree'][hidden] {
41
+ display: none;
42
+ }
43
+
44
+ /* 焦点在树外时容器自己进 Tab 序列兜底,落焦不画环:高亮永远长在行上 */
45
+ [data-scope='tree'][data-part='tree']:focus-visible {
46
+ outline: none;
47
+ }
48
+
49
+ /* 分支只是"这一行 + 它的子层"的外壳,不自带行距与内边距:
50
+ 真正长成一行的是 branch-control,子层缩进归 branch-content */
51
+ [data-scope='tree'][data-part='branch'][hidden] {
52
+ display: none;
53
+ }
54
+
55
+ /* 焦点落在 branch 上(它才是 treeitem),但环画在它自己那一行上:
56
+ 整棵子树套一圈描边会把展开的层级全框进去 */
57
+ [data-scope='tree'][data-part='branch']:focus-visible {
58
+ outline: none;
59
+ }
60
+
61
+ [data-scope='tree'][data-part='branch']:focus-visible > [data-scope='tree'][data-part='branch-control'] {
62
+ outline: var(--xh-ring-width) solid var(--xh-ring-focus);
63
+ /* 往内收:行是撑满宽度的,环画在外沿会被容器裁掉 */
64
+ outline-offset: calc(-1 * var(--xh-ring-width));
65
+ }
66
+
67
+ /* 行:叶子与分支共用同一套盒子,两种行在视觉上必须齐平 */
68
+ [data-scope='tree'][data-part='item'],
69
+ [data-scope='tree'][data-part='branch-control'] {
70
+ display: flex;
71
+ align-items: center;
72
+ gap: var(--xh-tree-row-gap, var(--xh-control-gap-md));
73
+ padding-block: var(--xh-tree-row-py, var(--xh-space-1));
74
+ padding-inline: var(--xh-tree-row-px, var(--xh-control-px-sm));
75
+ border-radius: var(--xh-tree-row-radius, var(--xh-shape-control));
76
+ color: var(--xh-tree-row-fg, var(--xh-fg-default));
77
+ font-size: var(--xh-tree-row-font-size, var(--xh-text-body-size));
78
+ /* 行高留出文字的上下伸展空间,行内那层 overflow:hidden 才不会把字削掉 */
79
+ line-height: var(--xh-tree-row-leading, var(--xh-leading-normal));
80
+ white-space: nowrap;
81
+ cursor: pointer;
82
+ user-select: none;
83
+ }
84
+
85
+ [data-scope='tree'][data-part='item'][hidden],
86
+ [data-scope='tree'][data-part='branch-control'][hidden] {
87
+ display: none;
88
+ }
89
+
90
+ /* 键盘高亮与指针高亮共用一套底色。data-highlighted 跟的是连接层记的焦点行,
91
+ 与选中互相独立:可以停在一个没被选中的节点上 */
92
+ [data-scope='tree'][data-part='item']:not([data-disabled]):is(:hover, [data-highlighted]),
93
+ [data-scope='tree'][data-part='branch-control']:not([data-disabled]):is(:hover, [data-highlighted]) {
94
+ background: var(--xh-tree-row-bg-highlight, var(--xh-bg-subtle-hover));
95
+ }
96
+
97
+ /* 选中只换文字色与字重,配合行尾的 ✓ 标记;底色整条通道留给高亮,
98
+ 两者因此能同时看见 */
99
+ [data-scope='tree'][data-part='item'][data-selected],
100
+ [data-scope='tree'][data-part='branch-control'][data-selected] {
101
+ color: var(--xh-tree-row-fg-selected, var(--xh-fg-brand-strong));
102
+ font-weight: var(--xh-tree-row-selected-font-weight, var(--xh-font-weight-medium));
103
+ }
104
+
105
+ /* 叶子自己就是 treeitem,环直接画在它身上 */
106
+ [data-scope='tree'][data-part='item']:focus-visible {
107
+ outline: var(--xh-ring-width) solid var(--xh-ring-focus);
108
+ outline-offset: calc(-1 * var(--xh-ring-width));
109
+ }
110
+
111
+ /* 禁用走 aria-disabled 而非原生 disabled:节点仍要可聚焦(它还得当方向键的起点),
112
+ 所以只能靠 data-disabled 表达,写 :disabled 永远不命中 */
113
+ [data-scope='tree'][data-part='item'][data-disabled],
114
+ [data-scope='tree'][data-part='branch-control'][data-disabled] {
115
+ color: var(--xh-fg-disabled);
116
+ cursor: not-allowed;
117
+ }
118
+
119
+ [data-scope='tree'][data-part='item-text'],
120
+ [data-scope='tree'][data-part='branch-text'] {
121
+ flex: 1;
122
+ /* 弹性子项默认最小尺寸是内容尺寸,不归零则长文本把行撑破、省略号轮不上 */
123
+ min-inline-size: 0;
124
+ overflow: hidden;
125
+ white-space: nowrap;
126
+ text-overflow: ellipsis;
127
+ }
128
+
129
+ /* 勾选标记只在选中项上出现;未选中时占位不塌,切换选中不引起行宽抖动 */
130
+ [data-scope='tree'][data-part='item-indicator'] {
131
+ display: inline-flex;
132
+ flex: none;
133
+ align-items: center;
134
+ justify-content: center;
135
+ inline-size: var(--xh-tree-indicator-size, var(--xh-control-indicator-size));
136
+ color: var(--xh-tree-indicator-fg, var(--xh-bg-brand));
137
+ visibility: hidden;
138
+ }
139
+
140
+ [data-scope='tree'][data-part='item-indicator'][hidden] {
141
+ display: none;
142
+ }
143
+
144
+ [data-scope='tree'][data-part='item-indicator'][data-selected] {
145
+ visibility: visible;
146
+ }
147
+
148
+ [data-scope='tree'][data-part='item-indicator'][data-disabled] {
149
+ color: var(--xh-fg-disabled);
150
+ }
151
+
152
+ /* 展开箭头与方向指示符:两者都可能被作者用作"点这里展开"的把手,
153
+ 所以尺寸与叶子的勾选标记一致,两种行的文字起点才对得齐 */
154
+ [data-scope='tree'][data-part='branch-trigger'],
155
+ [data-scope='tree'][data-part='branch-indicator'] {
156
+ display: inline-flex;
157
+ flex: none;
158
+ align-items: center;
159
+ justify-content: center;
160
+ inline-size: var(--xh-tree-indicator-size, var(--xh-control-indicator-size));
161
+ color: var(--xh-tree-branch-indicator-fg, var(--xh-fg-subtle));
162
+ transition: rotate var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
163
+ }
164
+
165
+ [data-scope='tree'][data-part='branch-trigger'][hidden],
166
+ [data-scope='tree'][data-part='branch-indicator'][hidden] {
167
+ display: none;
168
+ }
169
+
170
+ /* 收起时朝向行首、展开时转向下方,与逻辑书写方向一致:
171
+ rtl 下 inline-start 在右侧,箭头因此要反着转 */
172
+ [data-scope='tree'][data-part='branch-trigger'][data-state='open'],
173
+ [data-scope='tree'][data-part='branch-indicator'][data-state='open'] {
174
+ rotate: 90deg;
175
+ }
176
+
177
+ [dir='rtl'] [data-scope='tree'][data-part='branch-trigger'],
178
+ [dir='rtl'] [data-scope='tree'][data-part='branch-indicator'] {
179
+ rotate: 180deg;
180
+ }
181
+
182
+ [dir='rtl'] [data-scope='tree'][data-part='branch-trigger'][data-state='open'],
183
+ [dir='rtl'] [data-scope='tree'][data-part='branch-indicator'][data-state='open'] {
184
+ rotate: 90deg;
185
+ }
186
+
187
+ [data-scope='tree'][data-part='branch-trigger'][data-disabled],
188
+ [data-scope='tree'][data-part='branch-indicator'][data-disabled] {
189
+ color: var(--xh-fg-disabled);
190
+ }
191
+
192
+ /* 层级缩进靠子层容器自己顶:分支物理嵌套着子层,每深一层就多缩一格,
193
+ 无需把层级号写进内联样式,也就不必在两个适配器里各写一遍 */
194
+ [data-scope='tree'][data-part='branch-content'] {
195
+ display: flex;
196
+ flex-direction: column;
197
+ padding-inline-start: var(--xh-tree-indent, var(--xh-space-4));
198
+ }
199
+
200
+ /* 收起态的唯一开关:display 会盖掉 hidden 属性的 UA 规则,漏了这条整棵子树就收不起来 */
201
+ [data-scope='tree'][data-part='branch-content'][hidden] {
202
+ display: none;
203
+ }
204
+ }