@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,360 @@
1
+ @layer xihan.components {
2
+ /* root 只负责纵向堆叠:标题一行、控件一行。可见的那个"框"长在 control 上,
3
+ 因为标签、输入框、清空按钮都在它里面,边框必须把三者一起圈住 */
4
+ [data-scope='tags-input'][data-part='root'] {
5
+ /* 尺寸档只换这六个私有槽,标题、框、输入框与标签都从 root 继承它们,
6
+ 换档不必给每个部件各写一条选择器 */
7
+ --xh-_tags-input-control-h: var(--xh-control-h-md);
8
+ --xh-_tags-input-control-px: var(--xh-control-px-md);
9
+ --xh-_tags-input-control-gap: var(--xh-control-gap-sm);
10
+ --xh-_tags-input-input-font-size: var(--xh-text-body-size);
11
+ --xh-_tags-input-item-font-size: var(--xh-font-size-sm);
12
+ --xh-_tags-input-label-font-size: var(--xh-text-label-size);
13
+ /* 形态档只换这三个私有槽,缺省这一份就是描边式(描边 + 画布底) */
14
+ --xh-_tags-input-control-bg: var(--xh-bg-canvas);
15
+ --xh-_tags-input-control-border: var(--xh-border-default);
16
+ --xh-_tags-input-control-border-hover: var(--xh-border-strong);
17
+ /* 聚焦环与标签块的强调色随语气走。没写 data-tone 时 --xh-_tone 没被声明,退回原来的取值 */
18
+ --xh-_tags-input-ring: var(--xh-ring-focus);
19
+ --xh-_tags-input-accent: var(--xh-_tone, var(--xh-bg-brand));
20
+ --xh-_tags-input-accent-fg: var(--xh-_tone-on, var(--xh-fg-on-brand));
21
+
22
+ display: inline-flex;
23
+ flex-direction: column;
24
+ gap: var(--xh-tags-input-gap, var(--xh-space-1));
25
+ inline-size: 100%;
26
+ }
27
+
28
+ /* 只要声明了 display 就盖掉了 UA 的 [hidden]{display:none},收起态得自己还回去 */
29
+ [data-scope='tags-input'][data-part='root'][hidden] {
30
+ display: none;
31
+ }
32
+
33
+ /* —— 形态 —— */
34
+ /* 形态只决定颜色怎么用;用哪族颜色由 data-tone 决定。
35
+ 不做 solid:实心填充盖住了"这里能打字"这件事,输入框用不上它 */
36
+ [data-scope='tags-input'][data-part='root'][data-variant='outline'] {
37
+ --xh-_tags-input-control-bg: var(--xh-bg-canvas);
38
+ --xh-_tags-input-control-border: var(--xh-border-default);
39
+ --xh-_tags-input-control-border-hover: var(--xh-border-strong);
40
+ }
41
+
42
+ /* 填充式:底色圈出可输入的范围,描边让位给底色 */
43
+ [data-scope='tags-input'][data-part='root'][data-variant='subtle'] {
44
+ --xh-_tags-input-control-bg: var(--xh-_tone-subtle, var(--xh-bg-subtle));
45
+ --xh-_tags-input-control-border: transparent;
46
+ --xh-_tags-input-control-border-hover: var(--xh-_tone-border, var(--xh-border-default));
47
+ }
48
+
49
+ /* 无边框、透明底:静息时框完全隐入背景,可见反馈全靠悬停描边与聚焦环 */
50
+ [data-scope='tags-input'][data-part='root'][data-variant='ghost'] {
51
+ --xh-_tags-input-control-bg: transparent;
52
+ --xh-_tags-input-control-border: transparent;
53
+ --xh-_tags-input-control-border-hover: var(--xh-_tone-border, var(--xh-border-default));
54
+ }
55
+
56
+ /* —— 尺寸 —— */
57
+ [data-scope='tags-input'][data-part='root'][data-size='sm'] {
58
+ --xh-_tags-input-control-h: var(--xh-control-h-sm);
59
+ --xh-_tags-input-control-px: var(--xh-control-px-sm);
60
+ --xh-_tags-input-input-font-size: var(--xh-font-size-sm);
61
+ --xh-_tags-input-item-font-size: var(--xh-font-size-xs);
62
+ --xh-_tags-input-label-font-size: var(--xh-font-size-sm);
63
+ }
64
+
65
+ [data-scope='tags-input'][data-part='root'][data-size='lg'] {
66
+ --xh-_tags-input-control-h: var(--xh-control-h-lg);
67
+ --xh-_tags-input-control-px: var(--xh-control-px-lg);
68
+ --xh-_tags-input-control-gap: var(--xh-control-gap-lg);
69
+ --xh-_tags-input-input-font-size: var(--xh-font-size-lg);
70
+ --xh-_tags-input-item-font-size: var(--xh-font-size-md);
71
+ --xh-_tags-input-label-font-size: var(--xh-font-size-lg);
72
+ }
73
+
74
+ [data-scope='tags-input'][data-part='label'] {
75
+ color: var(--xh-tags-input-label-fg, var(--xh-fg-default));
76
+ font-size: var(--xh-tags-input-label-font-size, var(--xh-_tags-input-label-font-size));
77
+ font-weight: var(--xh-tags-input-label-font-weight, var(--xh-text-label-weight));
78
+ line-height: var(--xh-leading-none);
79
+ }
80
+
81
+ [data-scope='tags-input'][data-part='label'][data-disabled] {
82
+ color: var(--xh-tags-input-label-fg-disabled, var(--xh-fg-subtle));
83
+ }
84
+
85
+ /* 标签换行排,输入框跟在最后一个标签后面。最小高度对齐单行输入框,
86
+ 只有一个标签时看起来仍是一个普通输入框,而不是塌成一条缝 */
87
+ [data-scope='tags-input'][data-part='control'] {
88
+ display: flex;
89
+ flex-wrap: wrap;
90
+ align-items: center;
91
+ gap: var(--xh-tags-input-control-gap, var(--xh-_tags-input-control-gap));
92
+ min-block-size: var(--xh-tags-input-control-h, var(--xh-_tags-input-control-h));
93
+ padding-block: var(--xh-tags-input-control-py, var(--xh-space-1));
94
+ padding-inline: var(--xh-tags-input-control-px, var(--xh-_tags-input-control-px));
95
+ border: var(--xh-stroke-thin) solid var(--xh-tags-input-control-border, var(--xh-_tags-input-control-border));
96
+ border-radius: var(--xh-tags-input-control-radius, var(--xh-shape-control));
97
+ background: var(--xh-tags-input-control-bg, var(--xh-_tags-input-control-bg));
98
+ color: var(--xh-tags-input-control-fg, var(--xh-fg-default));
99
+ cursor: text;
100
+ transition: border-color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
101
+ }
102
+
103
+ [data-scope='tags-input'][data-part='control'][hidden] {
104
+ display: none;
105
+ }
106
+
107
+ /* hover 只在中性态换描边:invalid 与 at-max 正拿边框颜色说事,被 hover 盖掉那条信息就没了 */
108
+ [data-scope='tags-input'][data-part='control']:hover:not([data-disabled], [data-invalid], [data-at-max]) {
109
+ border-color: var(--xh-tags-input-control-border-hover, var(--xh-_tags-input-control-border-hover));
110
+ }
111
+
112
+ /* 焦点环画在这个框上:用户眼里"被聚焦的控件"就是整个框,
113
+ 而真正持有焦点的是里面的 input(它自己那圈环因此让位,见下) */
114
+ [data-scope='tags-input'][data-part='control']:focus-within {
115
+ outline: var(--xh-ring-width) solid var(--xh-_tags-input-ring);
116
+ outline-offset: var(--xh-ring-offset);
117
+ }
118
+
119
+ /* 顶到上限:再加进不去,边框先给个提示。放在 invalid 之前,
120
+ 两者同时成立时按源序让 invalid 赢——真出错比"装满了"要紧 */
121
+ [data-scope='tags-input'][data-part='control'][data-at-max] {
122
+ border-color: var(--xh-tags-input-control-border-at-max, var(--xh-color-warning-600));
123
+ }
124
+
125
+ [data-scope='tags-input'][data-part='control'][data-invalid] {
126
+ border-color: var(--xh-tags-input-control-border-invalid, var(--xh-color-danger-500));
127
+ }
128
+
129
+ /* 只读仍可聚焦、可复制,所以不置灰,只把底色压下去表示"改不动" */
130
+ [data-scope='tags-input'][data-part='control'][data-readonly] {
131
+ background: var(--xh-tags-input-control-bg-readonly, var(--xh-bg-subtle));
132
+ cursor: default;
133
+ }
134
+
135
+ /* control 是容器不是原生控件,禁用只能靠 data-disabled 表达,写 :disabled 永远不命中 */
136
+ [data-scope='tags-input'][data-part='control'][data-disabled] {
137
+ background: var(--xh-tags-input-control-bg-disabled, var(--xh-bg-subtle));
138
+ color: var(--xh-fg-disabled);
139
+ cursor: not-allowed;
140
+ }
141
+
142
+ /* 输入框贴着标签排,自己不画边框与底色——那两样归 control。
143
+ min-inline-size 保证标签占满一行后它还能换到下一行继续输入,而不是被挤成零宽 */
144
+ [data-scope='tags-input'][data-part='input'] {
145
+ flex: 1 1 var(--xh-tags-input-input-basis, 6rem);
146
+ min-inline-size: var(--xh-tags-input-input-min-w, 4rem);
147
+ padding: 0;
148
+ border: 0;
149
+ background: transparent;
150
+ color: inherit;
151
+ /* 表单控件不继承页面字体,只给字号会让输入框用 UA 默认字族,与周围文本对不上 */
152
+ font-family: inherit;
153
+ font-size: var(--xh-tags-input-input-font-size, var(--xh-_tags-input-input-font-size));
154
+ line-height: var(--xh-leading-normal);
155
+ }
156
+
157
+ [data-scope='tags-input'][data-part='input']::placeholder {
158
+ color: var(--xh-tags-input-placeholder-fg, var(--xh-fg-subtle));
159
+ }
160
+
161
+ /* 焦点环由 control 统一画(见上):这里再画一圈会在框里套框,两条环互相压着 */
162
+ [data-scope='tags-input'][data-part='input']:focus-visible {
163
+ outline: none;
164
+ }
165
+
166
+ /* input 是单体表单控件,禁用走原生 disabled */
167
+ [data-scope='tags-input'][data-part='input']:disabled {
168
+ color: var(--xh-fg-disabled);
169
+ cursor: not-allowed;
170
+ }
171
+
172
+ /* 一个标签一颗胶囊。inline-size 交给内容决定,长标签由 item-text 截断 */
173
+ [data-scope='tags-input'][data-part='item'] {
174
+ display: inline-flex;
175
+ flex: none;
176
+ align-items: center;
177
+ max-inline-size: 100%;
178
+ border-radius: var(--xh-tags-input-item-radius, var(--xh-shape-pill));
179
+ background: var(--xh-tags-input-item-bg, var(--xh-bg-subtle));
180
+ color: var(--xh-tags-input-item-fg, var(--xh-fg-default));
181
+ font-size: var(--xh-tags-input-item-font-size, var(--xh-_tags-input-item-font-size));
182
+ line-height: var(--xh-leading-none);
183
+ transition:
184
+ background var(--xh-motion-duration-micro) var(--xh-motion-ease-enter),
185
+ color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
186
+ }
187
+
188
+ [data-scope='tags-input'][data-part='item'][hidden] {
189
+ display: none;
190
+ }
191
+
192
+ /* 光标走到这颗标签上(退格第一下、方向键):整颗反白,与"删掉它"这件事对得上。
193
+ 禁用态下不给高亮:那时候本来就走不进标签 */
194
+ [data-scope='tags-input'][data-part='item'][data-highlighted]:not([data-disabled]) {
195
+ background: var(--xh-tags-input-item-bg-highlight, var(--xh-_tags-input-accent));
196
+ color: var(--xh-tags-input-item-fg-highlight, var(--xh-_tags-input-accent-fg));
197
+ }
198
+
199
+ /* 标签不是原生控件,禁用同样只能靠 data-disabled */
200
+ [data-scope='tags-input'][data-part='item'][data-disabled] {
201
+ color: var(--xh-fg-disabled);
202
+ cursor: not-allowed;
203
+ }
204
+
205
+ /* 就地编辑时整颗胶囊让位给编辑框:底色撤掉,免得输入框外面套一圈灰底 */
206
+ [data-scope='tags-input'][data-part='item'][data-editing] {
207
+ background: transparent;
208
+ }
209
+
210
+ [data-scope='tags-input'][data-part='item-preview'] {
211
+ display: inline-flex;
212
+ align-items: center;
213
+ gap: var(--xh-tags-input-item-gap, var(--xh-space-1));
214
+ max-inline-size: 100%;
215
+ padding-block: var(--xh-tags-input-item-py, var(--xh-space-1));
216
+ padding-inline: var(--xh-tags-input-item-px, var(--xh-space-2));
217
+ user-select: none;
218
+ }
219
+
220
+ /* 就地编辑时连接层给的是 hidden 属性;上面那条 display 会把 UA 规则盖掉,
221
+ 少了这条预览会和编辑框叠在一起 */
222
+ [data-scope='tags-input'][data-part='item-preview'][hidden] {
223
+ display: none;
224
+ }
225
+
226
+ /* 长标签截断而不是把整行撑开:胶囊挤爆一行会把输入框顶到看不见的地方 */
227
+ [data-scope='tags-input'][data-part='item-text'] {
228
+ overflow: hidden;
229
+ white-space: nowrap;
230
+ text-overflow: ellipsis;
231
+ }
232
+
233
+ /* 删除按钮画成胶囊里的小圆钮,与文字同一行、不撑高胶囊 */
234
+ [data-scope='tags-input'][data-part='item-delete-trigger'] {
235
+ display: inline-flex;
236
+ flex: none;
237
+ align-items: center;
238
+ justify-content: center;
239
+ inline-size: var(--xh-tags-input-delete-size, 1rem);
240
+ block-size: var(--xh-tags-input-delete-size, 1rem);
241
+ padding: 0;
242
+ border: 0;
243
+ border-radius: var(--xh-radius-full);
244
+ background: var(--xh-tags-input-delete-bg, transparent);
245
+ color: var(--xh-tags-input-delete-fg, var(--xh-fg-muted));
246
+ font-family: inherit;
247
+ font-size: var(--xh-tags-input-delete-font-size, var(--xh-font-size-xs));
248
+ line-height: var(--xh-leading-none);
249
+ cursor: pointer;
250
+ transition: background var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
251
+ }
252
+
253
+ [data-scope='tags-input'][data-part='item-delete-trigger'][hidden] {
254
+ display: none;
255
+ }
256
+
257
+ /* 整颗标签反白时按钮跟着改色,否则灰字压在品牌底色上会看不清 */
258
+ [data-scope='tags-input'][data-part='item-delete-trigger'][data-highlighted]:not(:disabled) {
259
+ color: var(--xh-tags-input-delete-fg-highlight, var(--xh-_tags-input-accent-fg));
260
+ }
261
+
262
+ [data-scope='tags-input'][data-part='item-delete-trigger']:hover:not(:disabled) {
263
+ background: var(--xh-tags-input-delete-bg-hover, var(--xh-bg-subtle-hover));
264
+ color: var(--xh-tags-input-delete-fg-hover, var(--xh-fg-default));
265
+ }
266
+
267
+ [data-scope='tags-input'][data-part='item-delete-trigger']:active:not(:disabled) {
268
+ background: var(--xh-tags-input-delete-bg-active, var(--xh-bg-subtle-active));
269
+ }
270
+
271
+ /* 按钮不占 Tab 位(tabindex=-1),正常操作走不到这里;
272
+ 留着焦点环是给程序化聚焦兜底——删完把焦点交回输入框之前,它确实短暂持有过焦点 */
273
+ [data-scope='tags-input'][data-part='item-delete-trigger']:focus-visible {
274
+ outline: var(--xh-ring-width) solid var(--xh-_tags-input-ring);
275
+ /* 比默认档窄一道:它长在胶囊里,按默认偏移画会压到相邻胶囊 */
276
+ outline-offset: var(--xh-stroke-thin);
277
+ }
278
+
279
+ /* 按钮是单体控件,禁用走原生 disabled */
280
+ [data-scope='tags-input'][data-part='item-delete-trigger']:disabled {
281
+ background: transparent;
282
+ color: var(--xh-fg-disabled);
283
+ cursor: not-allowed;
284
+ }
285
+
286
+ /* 就地编辑框:尺寸对齐胶囊,看起来像是在原地把标签改了,而不是另开一个框。
287
+ 不声明 display —— 收起态靠 UA 的 [hidden]{display:none} 就够,
288
+ 自己加一条 display 反而要再补一条 [hidden] 规则去抵消它 */
289
+ [data-scope='tags-input'][data-part='item-input'] {
290
+ max-inline-size: 100%;
291
+ padding-block: var(--xh-tags-input-item-py, var(--xh-space-1));
292
+ padding-inline: var(--xh-tags-input-item-px, var(--xh-space-2));
293
+ border: var(--xh-stroke-thin) solid var(--xh-tags-input-item-input-border, var(--xh-border-strong));
294
+ border-radius: var(--xh-tags-input-item-radius, var(--xh-shape-pill));
295
+ background: var(--xh-tags-input-item-input-bg, var(--xh-bg-canvas));
296
+ color: var(--xh-tags-input-item-input-fg, var(--xh-fg-default));
297
+ font-family: inherit;
298
+ font-size: var(--xh-tags-input-item-font-size, var(--xh-_tags-input-item-font-size));
299
+ line-height: var(--xh-leading-none);
300
+ }
301
+
302
+ /* 它是真正持有焦点的那个节点,环画在自己身上;
303
+ 外层 control 的 :focus-within 环同时亮着也合理——焦点确实在框内 */
304
+ [data-scope='tags-input'][data-part='item-input']:focus-visible {
305
+ outline: var(--xh-ring-width) solid var(--xh-_tags-input-ring);
306
+ /* 比默认档窄一道:它长在胶囊里,按默认偏移画会压到相邻胶囊 */
307
+ outline-offset: var(--xh-stroke-thin);
308
+ }
309
+
310
+ [data-scope='tags-input'][data-part='item-input']:disabled {
311
+ color: var(--xh-fg-disabled);
312
+ cursor: not-allowed;
313
+ }
314
+
315
+ /* 清空按钮贴在框的末端,与输入框同一行 */
316
+ [data-scope='tags-input'][data-part='clear-trigger'] {
317
+ display: inline-flex;
318
+ flex: none;
319
+ align-items: center;
320
+ justify-content: center;
321
+ inline-size: var(--xh-tags-input-clear-size, 1.25rem);
322
+ block-size: var(--xh-tags-input-clear-size, 1.25rem);
323
+ padding: 0;
324
+ border: 0;
325
+ border-radius: var(--xh-radius-full);
326
+ background: var(--xh-tags-input-clear-bg, transparent);
327
+ color: var(--xh-tags-input-clear-fg, var(--xh-fg-muted));
328
+ font-family: inherit;
329
+ font-size: var(--xh-tags-input-clear-font-size, var(--xh-font-size-sm));
330
+ line-height: var(--xh-leading-none);
331
+ cursor: pointer;
332
+ transition: background var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
333
+ }
334
+
335
+ [data-scope='tags-input'][data-part='clear-trigger'][hidden] {
336
+ display: none;
337
+ }
338
+
339
+ [data-scope='tags-input'][data-part='clear-trigger']:hover:not(:disabled) {
340
+ background: var(--xh-tags-input-clear-bg-hover, var(--xh-bg-subtle-hover));
341
+ color: var(--xh-tags-input-clear-fg-hover, var(--xh-fg-default));
342
+ }
343
+
344
+ [data-scope='tags-input'][data-part='clear-trigger']:active:not(:disabled) {
345
+ background: var(--xh-tags-input-clear-bg-active, var(--xh-bg-subtle-active));
346
+ }
347
+
348
+ [data-scope='tags-input'][data-part='clear-trigger']:focus-visible {
349
+ outline: var(--xh-ring-width) solid var(--xh-_tags-input-ring);
350
+ /* 比默认档窄一道:它长在胶囊里,按默认偏移画会压到相邻胶囊 */
351
+ outline-offset: var(--xh-stroke-thin);
352
+ }
353
+
354
+ /* 没东西可清、只读或禁用时按钮转灰但不收起:位置留着,加进第一个标签就亮 */
355
+ [data-scope='tags-input'][data-part='clear-trigger']:disabled {
356
+ background: transparent;
357
+ color: var(--xh-fg-disabled);
358
+ cursor: not-allowed;
359
+ }
360
+ }
@@ -0,0 +1,195 @@
1
+ @layer xihan.components {
2
+ /* root 只负责纵向堆叠:标签一行、输入行一行。
3
+ 输入框与清空按钮中间那层包裹是作者自己写的、不在角色表里,
4
+ 所以尺寸与间距一律长在各自的角色节点上,不去假设那层存在 */
5
+ [data-scope='text-field'][data-part='root'] {
6
+ /* 尺寸档只换这几个私有槽,标题、输入框与清空按钮都从 root 继承它们,
7
+ 换档不必给每个部件各写一条选择器 */
8
+ --xh-_text-field-h: var(--xh-control-h-md);
9
+ --xh-_text-field-px: var(--xh-control-px-md);
10
+ --xh-_text-field-font-size: var(--xh-text-body-size);
11
+ --xh-_text-field-label-font-size: var(--xh-text-label-size);
12
+ --xh-_text-field-clear-font-size: var(--xh-font-size-sm);
13
+
14
+ /* 形态档只换这三个私有槽:底、常态描边、悬停描边 */
15
+ --xh-_text-field-bg: var(--xh-bg-canvas);
16
+ --xh-_text-field-border: var(--xh-border-default);
17
+ --xh-_text-field-border-hover: var(--xh-border-strong);
18
+
19
+ /* 语气只落在聚焦这一处:环与聚焦态描边。正文颜色不归它管。
20
+ 没写 data-tone 时 --xh-_tone 没被声明,退回原来的聚焦环色与常态描边 */
21
+ --xh-_text-field-ring: var(--xh-ring-focus);
22
+ --xh-_text-field-border-focus: var(--xh-text-field-input-border, var(--xh-_tone, var(--xh-border-default)));
23
+
24
+ display: inline-flex;
25
+ flex-direction: column;
26
+ gap: var(--xh-text-field-gap, var(--xh-space-1));
27
+ }
28
+
29
+ /* 只要声明了 display 就盖掉了 UA 的 [hidden]{display:none},收起态得自己还回去 */
30
+ [data-scope='text-field'][data-part='root'][hidden] {
31
+ display: none;
32
+ }
33
+
34
+ /* —— 尺寸 —— */
35
+ /* 缺省档写在上面的基础规则里 */
36
+ [data-scope='text-field'][data-part='root'][data-size='sm'] {
37
+ --xh-_text-field-h: var(--xh-control-h-sm);
38
+ --xh-_text-field-px: var(--xh-control-px-sm);
39
+ --xh-_text-field-font-size: var(--xh-font-size-sm);
40
+ --xh-_text-field-label-font-size: var(--xh-font-size-sm);
41
+ --xh-_text-field-clear-font-size: var(--xh-font-size-xs);
42
+ }
43
+
44
+ [data-scope='text-field'][data-part='root'][data-size='lg'] {
45
+ --xh-_text-field-h: var(--xh-control-h-lg);
46
+ --xh-_text-field-px: var(--xh-control-px-lg);
47
+ --xh-_text-field-font-size: var(--xh-font-size-lg);
48
+ --xh-_text-field-label-font-size: var(--xh-font-size-lg);
49
+ --xh-_text-field-clear-font-size: var(--xh-font-size-md);
50
+ }
51
+
52
+ /* —— 形态 —— */
53
+ /* 形态只决定底与描边怎么画;用哪族颜色由 data-tone 决定。
54
+ 输入框没有实心档:填满一个要往里打字的框,字与底就没法同时读 */
55
+ [data-scope='text-field'][data-part='root'][data-variant='outline'] {
56
+ --xh-_text-field-bg: var(--xh-bg-canvas);
57
+ --xh-_text-field-border: var(--xh-border-default);
58
+ --xh-_text-field-border-hover: var(--xh-border-strong);
59
+ --xh-_text-field-border-focus: var(--xh-text-field-input-border, var(--xh-_tone, var(--xh-border-default)));
60
+ }
61
+
62
+ /* 填充式:底色顶替描边表达边界。描边只退成透明而不去掉,1px 的位置留着,
63
+ 与 outline 档并排时基线对得上、聚焦画描边也不引起跳动 */
64
+ [data-scope='text-field'][data-part='root'][data-variant='subtle'] {
65
+ --xh-_text-field-bg: var(--xh-_tone-subtle, var(--xh-bg-subtle));
66
+ --xh-_text-field-border: transparent;
67
+ --xh-_text-field-border-hover: var(--xh-_tone-border, var(--xh-border-default));
68
+ --xh-_text-field-border-focus: var(--xh-_tone, var(--xh-border-default));
69
+ }
70
+
71
+ /* 无框:底与描边都透明,平时只看得见字,悬停与聚焦才浮出边界 */
72
+ [data-scope='text-field'][data-part='root'][data-variant='ghost'] {
73
+ --xh-_text-field-bg: transparent;
74
+ --xh-_text-field-border: transparent;
75
+ --xh-_text-field-border-hover: var(--xh-_tone-border, var(--xh-border-default));
76
+ --xh-_text-field-border-focus: var(--xh-_tone, var(--xh-border-default));
77
+ }
78
+
79
+ [data-scope='text-field'][data-part='label'] {
80
+ color: var(--xh-text-field-label-fg, var(--xh-fg-default));
81
+ font-size: var(--xh-text-field-label-font-size, var(--xh-_text-field-label-font-size));
82
+ font-weight: var(--xh-text-field-label-font-weight, var(--xh-text-label-weight));
83
+ line-height: var(--xh-leading-none);
84
+ }
85
+
86
+ [data-scope='text-field'][data-part='label'][data-disabled] {
87
+ color: var(--xh-text-field-label-fg-disabled, var(--xh-fg-subtle));
88
+ }
89
+
90
+ [data-scope='text-field'][data-part='input'] {
91
+ block-size: var(--xh-text-field-input-h, var(--xh-_text-field-h));
92
+ padding-inline: var(--xh-text-field-input-px, var(--xh-_text-field-px));
93
+ padding-block: 0;
94
+ border: var(--xh-stroke-thin) solid var(--xh-text-field-input-border, var(--xh-_text-field-border));
95
+ border-radius: var(--xh-text-field-input-radius, var(--xh-shape-control));
96
+ background: var(--xh-text-field-input-bg, var(--xh-_text-field-bg));
97
+ color: var(--xh-text-field-input-fg, var(--xh-fg-default));
98
+ /* 表单控件不继承页面字体,只给字号会让输入框用 UA 默认字族,与周围文本对不上 */
99
+ font-family: inherit;
100
+ font-size: var(--xh-text-field-input-font-size, var(--xh-_text-field-font-size));
101
+ line-height: var(--xh-leading-normal);
102
+ transition: border-color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
103
+ }
104
+
105
+ [data-scope='text-field'][data-part='input']::placeholder {
106
+ color: var(--xh-text-field-placeholder-fg, var(--xh-fg-subtle));
107
+ }
108
+
109
+ /* hover 只在中性态换描边:invalid 与 at-limit 正拿边框颜色说事,
110
+ 被 hover 盖掉那条信息就没了 */
111
+ [data-scope='text-field'][data-part='input']:hover:not(:disabled, [data-invalid], [data-at-limit]) {
112
+ border-color: var(--xh-text-field-input-border-hover, var(--xh-_text-field-border-hover));
113
+ }
114
+
115
+ /* 聚焦态描边与环共用一族语气色。这条特异度低于上面的 hover、也低于下面的
116
+ at-limit 与 invalid,三者仍按原来的次序赢过它 */
117
+ [data-scope='text-field'][data-part='input']:focus-visible {
118
+ border-color: var(--xh-text-field-input-border-focus, var(--xh-_text-field-border-focus));
119
+ outline: var(--xh-ring-width) solid var(--xh-_text-field-ring);
120
+ outline-offset: var(--xh-ring-offset);
121
+ }
122
+
123
+ /* 顶到字数上限:再敲进不去,边框先给个提示。放在 invalid 之前,
124
+ 两者同时成立时按源序让 invalid 赢——真出错比"写满了"要紧 */
125
+ [data-scope='text-field'][data-part='input'][data-at-limit] {
126
+ border-color: var(--xh-text-field-input-border-at-limit, var(--xh-color-warning-600));
127
+ }
128
+
129
+ [data-scope='text-field'][data-part='input'][data-invalid] {
130
+ border-color: var(--xh-text-field-input-border-invalid, var(--xh-color-danger-500));
131
+ }
132
+
133
+ /* 只读仍可聚焦、可复制,所以不置灰,只把底色压下去表示"改不动" */
134
+ [data-scope='text-field'][data-part='input'][readonly] {
135
+ background: var(--xh-text-field-input-bg-readonly, var(--xh-bg-subtle));
136
+ }
137
+
138
+ /* input 是单体表单控件,禁用走原生 disabled */
139
+ [data-scope='text-field'][data-part='input']:disabled {
140
+ background: var(--xh-text-field-input-bg-disabled, var(--xh-bg-subtle));
141
+ color: var(--xh-fg-disabled);
142
+ cursor: not-allowed;
143
+ }
144
+
145
+ /* 清空按钮画成输入框右侧的小方钮:它是 input 的兄弟节点而不是子节点,
146
+ 没法叠进框内,只能与框等高、贴着排,靠同一套边角与高度看起来是一体的 */
147
+ [data-scope='text-field'][data-part='clear-trigger'] {
148
+ display: inline-flex;
149
+ flex: none;
150
+ align-items: center;
151
+ justify-content: center;
152
+ inline-size: var(--xh-text-field-clear-size, var(--xh-_text-field-h));
153
+ block-size: var(--xh-text-field-clear-size, var(--xh-_text-field-h));
154
+ padding: 0;
155
+ border: 0;
156
+ border-radius: var(--xh-text-field-clear-radius, var(--xh-shape-control));
157
+ background: var(--xh-text-field-clear-bg, transparent);
158
+ color: var(--xh-text-field-clear-fg, var(--xh-fg-muted));
159
+ font-family: inherit;
160
+ font-size: var(--xh-text-field-clear-font-size, var(--xh-_text-field-clear-font-size));
161
+ line-height: var(--xh-leading-none);
162
+ cursor: pointer;
163
+ user-select: none;
164
+ transition: background var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
165
+ }
166
+
167
+ /* 没开 clearable 时连接层给的是 hidden 属性;上面那条 display 会把 UA 规则盖掉,
168
+ 少了这条按钮就一直杵在那儿 */
169
+ [data-scope='text-field'][data-part='clear-trigger'][hidden] {
170
+ display: none;
171
+ }
172
+
173
+ [data-scope='text-field'][data-part='clear-trigger']:hover:not(:disabled) {
174
+ background: var(--xh-text-field-clear-bg-hover, var(--xh-bg-subtle-hover));
175
+ color: var(--xh-text-field-clear-fg-hover, var(--xh-fg-default));
176
+ }
177
+
178
+ [data-scope='text-field'][data-part='clear-trigger']:active:not(:disabled) {
179
+ background: var(--xh-text-field-clear-bg-active, var(--xh-bg-subtle-active));
180
+ }
181
+
182
+ /* 按钮不占 Tab 位(tabindex=-1)也不抢焦点,正常操作走不到这里;
183
+ 留着是给程序化聚焦兜底,落焦时得看得见 */
184
+ [data-scope='text-field'][data-part='clear-trigger']:focus-visible {
185
+ outline: var(--xh-ring-width) solid var(--xh-_text-field-ring);
186
+ outline-offset: var(--xh-ring-offset);
187
+ }
188
+
189
+ /* 框里没字、只读或禁用时按钮转灰但不收起:位置留着,敲进第一个字就亮 */
190
+ [data-scope='text-field'][data-part='clear-trigger']:disabled {
191
+ background: var(--xh-text-field-clear-bg, transparent);
192
+ color: var(--xh-fg-disabled);
193
+ cursor: not-allowed;
194
+ }
195
+ }
package/css/thread.css ADDED
@@ -0,0 +1,107 @@
1
+ @layer xihan.components {
2
+ /* 自建定位上下文,作为绝对定位的回到底部按钮的参照系;
3
+ block-size: 100% 让高度由外部决定,使滚动发生在内部。 */
4
+ [data-scope='thread'][data-part='root'] {
5
+ position: relative;
6
+ display: flex;
7
+ flex-direction: column;
8
+ block-size: 100%;
9
+ }
10
+
11
+ /* 上面声明的 display 会盖掉 UA 的 [hidden]{display:none},这里补回 */
12
+ [data-scope='thread'][data-part='root'][hidden] {
13
+ display: none;
14
+ }
15
+
16
+ /* 滚动容器,粘底读的是它的 scrollTop / clientHeight / scrollHeight。
17
+ min-block-size: 0 让弹性子项能被压缩从而触发溢出;
18
+ overscroll-behavior: contain 阻止滚动量传给页面;
19
+ scrollbar-gutter: stable 避免滚动条出现与消失时推动文字。 */
20
+ [data-scope='thread'][data-part='viewport'] {
21
+ flex: 1;
22
+ min-block-size: 0;
23
+ /* 逻辑属性 overflow-block 尚无可用实现,写物理轴 */
24
+ overflow-y: auto;
25
+ overscroll-behavior: contain;
26
+ scrollbar-gutter: stable;
27
+ }
28
+
29
+ /* 视口带 tabindex=0,需要焦点环;偏移取负画在内侧,避免被 root 裁掉 */
30
+ [data-scope='thread'][data-part='viewport']:focus-visible {
31
+ outline: var(--xh-ring-width) solid var(--xh-ring-focus);
32
+ outline-offset: calc(-1 * var(--xh-ring-width));
33
+ }
34
+
35
+ /* 只定消息之间的竖向间距与上下留白,左右留白留给作者 */
36
+ [data-scope='thread'][data-part='content'] {
37
+ display: flex;
38
+ flex-direction: column;
39
+ gap: var(--xh-thread-content-gap, var(--xh-stack-gap-md));
40
+ padding-block: var(--xh-thread-content-py, var(--xh-space-4));
41
+ }
42
+
43
+ [data-scope='thread'][data-part='content'][hidden] {
44
+ display: none;
45
+ }
46
+
47
+ /* 浮在视口右下角,z-index 使其不受源序影响地盖在消息之上 */
48
+ [data-scope='thread'][data-part='scroll-button'] {
49
+ position: absolute;
50
+ z-index: 1;
51
+ display: inline-flex;
52
+ align-items: center;
53
+ justify-content: center;
54
+ gap: var(--xh-thread-scroll-button-gap, var(--xh-control-gap-md));
55
+ inset-block-end: var(--xh-thread-scroll-button-inset, var(--xh-space-4));
56
+ inset-inline-end: var(--xh-thread-scroll-button-inset, var(--xh-space-4));
57
+ min-inline-size: var(--xh-thread-scroll-button-h, var(--xh-control-h-md));
58
+ block-size: var(--xh-thread-scroll-button-h, var(--xh-control-h-md));
59
+ padding-inline: var(--xh-thread-scroll-button-px, var(--xh-control-px-md));
60
+ border: var(--xh-stroke-thin) solid var(--xh-thread-scroll-button-border, var(--xh-border-default));
61
+ border-radius: var(--xh-thread-scroll-button-radius, var(--xh-shape-pill));
62
+ background: var(--xh-thread-scroll-button-bg, var(--xh-bg-surface));
63
+ color: var(--xh-thread-scroll-button-fg, var(--xh-fg-default));
64
+ box-shadow: var(--xh-thread-scroll-button-shadow, var(--xh-elevation-2));
65
+ /* 按钮默认不继承页面字族,显式继承 */
66
+ font-family: inherit;
67
+ font-size: var(--xh-thread-scroll-button-font-size, var(--xh-text-label-size));
68
+ font-weight: var(--xh-thread-scroll-button-font-weight, var(--xh-text-label-weight));
69
+ line-height: var(--xh-leading-none);
70
+ white-space: nowrap;
71
+ cursor: pointer;
72
+ user-select: none;
73
+ transition: background var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
74
+ }
75
+
76
+ /* 连接层收起按钮时写 hidden 属性,上面的 display 会盖掉 UA 规则,这里补回 */
77
+ [data-scope='thread'][data-part='scroll-button'][hidden] {
78
+ display: none;
79
+ }
80
+
81
+ [data-scope='thread'][data-part='scroll-button']:hover {
82
+ background: var(--xh-thread-scroll-button-bg-hover, var(--xh-bg-subtle-hover));
83
+ }
84
+
85
+ [data-scope='thread'][data-part='scroll-button']:active {
86
+ background: var(--xh-thread-scroll-button-bg-active, var(--xh-bg-subtle-active));
87
+ }
88
+
89
+ [data-scope='thread'][data-part='scroll-button']:focus-visible {
90
+ outline: var(--xh-ring-width) solid var(--xh-ring-focus);
91
+ outline-offset: var(--xh-ring-offset);
92
+ }
93
+
94
+ /* 视觉隐藏但仍参与播报:用 clip-path 裁到不可见,不能用 display:none 或 visibility:hidden;
95
+ white-space: nowrap 防止 1px 宽把文本挤成竖排。 */
96
+ [data-scope='thread'][data-part='live-region'] {
97
+ position: absolute;
98
+ inline-size: 1px;
99
+ block-size: 1px;
100
+ padding: 0;
101
+ margin: -1px;
102
+ overflow: hidden;
103
+ clip-path: inset(50%);
104
+ white-space: nowrap;
105
+ border: 0;
106
+ }
107
+ }