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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/CHANGELOG.md +527 -0
  2. package/README.md +1 -1
  3. package/css/accordion.css +61 -5
  4. package/css/affix.css +5 -0
  5. package/css/alert.css +35 -5
  6. package/css/anchor.css +12 -7
  7. package/css/avatar-group.css +8 -3
  8. package/css/avatar.css +8 -3
  9. package/css/back-top.css +21 -1
  10. package/css/badge.css +21 -2
  11. package/css/breadcrumb.css +29 -4
  12. package/css/button-group.css +19 -13
  13. package/css/button.css +57 -11
  14. package/css/calendar.css +124 -2
  15. package/css/card.css +10 -5
  16. package/css/carousel.css +41 -5
  17. package/css/cascader.css +188 -102
  18. package/css/checkbox-group.css +46 -19
  19. package/css/checkbox.css +59 -8
  20. package/css/clipboard.css +7 -1
  21. package/css/code-block.css +17 -7
  22. package/css/collapsible.css +47 -2
  23. package/css/color-picker.css +117 -46
  24. package/css/combobox.css +132 -52
  25. package/css/composer.css +10 -3
  26. package/css/context-menu.css +101 -75
  27. package/css/countdown.css +5 -0
  28. package/css/date-field.css +96 -14
  29. package/css/date-picker.css +281 -65
  30. package/css/descriptions.css +7 -2
  31. package/css/dialog.css +109 -22
  32. package/css/download-trigger.css +76 -0
  33. package/css/drawer.css +75 -23
  34. package/css/dynamic-input.css +73 -17
  35. package/css/editable.css +17 -10
  36. package/css/ellipsis.css +6 -1
  37. package/css/empty-state.css +12 -7
  38. package/css/field.css +14 -6
  39. package/css/fieldset.css +69 -0
  40. package/css/file-upload.css +50 -7
  41. package/css/flex.css +5 -0
  42. package/css/float-button.css +21 -1
  43. package/css/floating-panel.css +310 -0
  44. package/css/form.css +10 -5
  45. package/css/gradient-text.css +5 -0
  46. package/css/grid.css +5 -0
  47. package/css/heatmap.css +410 -0
  48. package/css/highlight.css +6 -1
  49. package/css/hotkeys.css +90 -0
  50. package/css/hover-card.css +58 -50
  51. package/css/icon-wrapper.css +5 -0
  52. package/css/icon.css +5 -0
  53. package/css/image-cropper.css +231 -0
  54. package/css/image-viewer.css +167 -22
  55. package/css/image.css +7 -2
  56. package/css/infinite-scroll.css +6 -1
  57. package/css/json-viewer.css +241 -0
  58. package/css/layout.css +11 -10
  59. package/css/list.css +13 -8
  60. package/css/listbox.css +25 -4
  61. package/css/loading-bar.css +12 -1
  62. package/css/log.css +6 -1
  63. package/css/marquee.css +14 -0
  64. package/css/masonry.css +63 -0
  65. package/css/mention.css +105 -32
  66. package/css/menu.css +131 -46
  67. package/css/menubar.css +150 -66
  68. package/css/navigation-menu.css +59 -29
  69. package/css/number-animation.css +8 -3
  70. package/css/number-field.css +178 -16
  71. package/css/overlay-arrow.css +51 -0
  72. package/css/page-header.css +10 -5
  73. package/css/pagination.css +18 -13
  74. package/css/password-input.css +356 -0
  75. package/css/pin-input.css +18 -10
  76. package/css/popconfirm.css +73 -16
  77. package/css/popover.css +88 -44
  78. package/css/popselect.css +201 -52
  79. package/css/progress.css +52 -7
  80. package/css/qr-code.css +6 -1
  81. package/css/radio-group.css +19 -8
  82. package/css/rating.css +7 -2
  83. package/css/reset.css +17 -0
  84. package/css/result.css +11 -6
  85. package/css/scroll-area.css +65 -94
  86. package/css/scrollbar.css +167 -0
  87. package/css/segmented.css +215 -0
  88. package/css/select.css +292 -124
  89. package/css/separator.css +7 -2
  90. package/css/side-nav.css +116 -19
  91. package/css/signature-pad.css +144 -0
  92. package/css/skeleton.css +12 -1
  93. package/css/slider.css +15 -10
  94. package/css/space.css +117 -0
  95. package/css/spinner.css +31 -11
  96. package/css/splitter.css +5 -0
  97. package/css/statistic.css +14 -9
  98. package/css/steps.css +30 -8
  99. package/css/switch.css +62 -13
  100. package/css/table.css +136 -37
  101. package/css/tabs.css +9 -4
  102. package/css/tag.css +177 -0
  103. package/css/tags-input.css +83 -40
  104. package/css/text-field.css +154 -36
  105. package/css/thread.css +6 -1
  106. package/css/time-field.css +102 -16
  107. package/css/time-picker.css +264 -69
  108. package/css/time.css +5 -0
  109. package/css/timeline.css +15 -10
  110. package/css/timer.css +119 -0
  111. package/css/toast.css +46 -21
  112. package/css/toaster.css +6 -1
  113. package/css/toggle-group.css +5 -0
  114. package/css/toggle.css +11 -6
  115. package/css/tone.css +10 -5
  116. package/css/toolbar.css +6 -1
  117. package/css/tooltip.css +54 -38
  118. package/css/tour.css +128 -63
  119. package/css/transfer.css +73 -17
  120. package/css/tree-select.css +203 -83
  121. package/css/tree.css +90 -1
  122. package/css/typography.css +9 -4
  123. package/css/undefined.css +11 -1
  124. package/css/virtualizer.css +8 -1
  125. package/css/watermark.css +6 -2
  126. package/index.css +17 -1
  127. package/index.unlayered.css +9394 -2883
  128. package/package.json +19 -4
  129. package/css/motion.css +0 -58
package/css/side-nav.css CHANGED
@@ -1,5 +1,41 @@
1
+ @layer xihan.motion {
2
+ /* 动画名的查找只认「文档里有没有这个名字」:引用别处文件里的名字,那份文件不一定在场,
3
+ 动画会静默地整个不跑。每份皮肤都能单独引入,所以它用到的关键帧都写在自己身上。 */
4
+ @keyframes xh-pop-in {
5
+ from {
6
+ opacity: 0;
7
+ scale: var(--xh-motion-scale-enter);
8
+ }
9
+
10
+ to {
11
+ opacity: 1;
12
+ scale: 1;
13
+ }
14
+ }
15
+
16
+ @keyframes xh-pop-out {
17
+ from {
18
+ opacity: 1;
19
+ scale: 1;
20
+ }
21
+
22
+ to {
23
+ opacity: 0;
24
+ scale: var(--xh-motion-scale-enter);
25
+ }
26
+ }
27
+ }
28
+
1
29
  @layer xihan.components {
30
+ /* 皮肤在场的标记:开发模式下按 scope 探一次,取不到就说明这份皮肤没被引入。 */
31
+ [data-scope='side-nav'] {
32
+ --xh-side-nav-skin: 1;
33
+ }
34
+
2
35
  [data-scope='side-nav'][data-part='root'] {
36
+ /* 作者塞进指示符槽的图标跟着字号走,与皮肤画的兜底字形同一把尺 */
37
+ --xh-icon-size: var(--xh-side-nav-icon-size, var(--xh-glyph-size-text));
38
+
3
39
  inline-size: var(--xh-side-nav-w, 240px);
4
40
  display: flex;
5
41
  flex-direction: column;
@@ -42,7 +78,7 @@
42
78
  [data-scope='side-nav'][data-part='group-label'] {
43
79
  padding-block: var(--xh-space-1);
44
80
  padding-inline: var(--xh-control-px-md);
45
- font-size: var(--xh-font-size-xs);
81
+ font-size: var(--xh-text-caption-size);
46
82
  color: var(--xh-fg-subtle);
47
83
  user-select: none;
48
84
  }
@@ -59,6 +95,13 @@
59
95
  list-style: none;
60
96
  }
61
97
 
98
+ /* 叶子行的列表项:链接自己撑满,这一层只负责列表语义 */
99
+ [data-scope='side-nav'][data-part='item'] {
100
+ display: flex;
101
+ flex-direction: column;
102
+ list-style: none;
103
+ }
104
+
62
105
  /* —— 行:分支按钮与链接同一副长相 —— */
63
106
  [data-scope='side-nav'][data-part='branch-trigger'],
64
107
  [data-scope='side-nav'][data-part='link'] {
@@ -78,9 +121,10 @@
78
121
  cursor: pointer;
79
122
  }
80
123
 
81
- [data-scope='side-nav'][data-part='branch-trigger']:hover,
82
- [data-scope='side-nav'][data-part='link']:hover {
83
- background: var(--xh-bg-subtle-hover);
124
+ /* 悬停与键盘锚点共用中性灰轻档:锚点只认连接层打的 data-highlighted,不读 :focus */
125
+ [data-scope='side-nav'][data-part='branch-trigger']:not([data-disabled]):is(:hover, [data-highlighted]),
126
+ [data-scope='side-nav'][data-part='link']:not([data-disabled]):is(:hover, [data-highlighted]) {
127
+ background: var(--xh-side-nav-row-bg-hover, var(--xh-bg-subtle));
84
128
  }
85
129
 
86
130
  [data-scope='side-nav'][data-part='branch-trigger']:focus-visible,
@@ -89,15 +133,17 @@
89
133
  outline-offset: var(--xh-ring-offset);
90
134
  }
91
135
 
92
- /* 选中的那条:品牌淡底 + 强调字 */
93
- [data-scope='side-nav'][data-part='link'][data-selected] {
136
+ /* 选中的那条:品牌淡底 + 强调字。
137
+ 守卫与轻档那条同特指度,写在其后才压得住——键盘走到当前页那一行时底色仍是品牌淡底 */
138
+ [data-scope='side-nav'][data-part='link']:not([data-disabled])[data-current] {
94
139
  background: var(--xh-bg-brand-subtle);
95
- color: var(--xh-fg-brand);
140
+ /* 淡底上的字走 strong 档:fg-brand(600)压在 brand-subtle(100)上只有 4.21 */
141
+ color: var(--xh-fg-brand-strong);
96
142
  font-weight: var(--xh-font-weight-medium);
97
143
  }
98
144
 
99
145
  /* 选中项的祖先枝:只染字色,一路指到当前页 */
100
- [data-scope='side-nav'][data-part='branch-trigger'][data-active] {
146
+ [data-scope='side-nav'][data-part='branch-trigger'][data-in-path] {
101
147
  color: var(--xh-fg-brand);
102
148
  }
103
149
 
@@ -116,10 +162,25 @@
116
162
  white-space: nowrap;
117
163
  }
118
164
 
119
- /* 折叠成图标栏:文字与展开指示整个藏掉、行内容居中,不裁半个字。
165
+ /* 折叠成图标栏:行文字裁到看不见但仍参与播报,行内容居中,不裁半个字。
166
+ 用 clip-path 而不是 display:none——图标栏里按钮与链接的可及名就是这段文字,
167
+ 藏成 display:none 它们就一个名字都没有了。
168
+ white-space: nowrap 防止 1px 宽把文本挤成竖排。
120
169
  弹出面板里的行不在此列——面板是正常宽度的浮层,文字照常显示 */
121
170
  [data-scope='side-nav'][data-part='root'][data-collapsed] [data-part='branch-text']:not([data-popout] *),
122
- [data-scope='side-nav'][data-part='root'][data-collapsed] [data-part='link-text']:not([data-popout] *),
171
+ [data-scope='side-nav'][data-part='root'][data-collapsed] [data-part='link-text']:not([data-popout] *) {
172
+ position: absolute;
173
+ inline-size: 1px;
174
+ block-size: 1px;
175
+ padding: 0;
176
+ margin: -1px;
177
+ overflow: hidden;
178
+ clip-path: inset(50%);
179
+ white-space: nowrap;
180
+ border: 0;
181
+ }
182
+
183
+ /* 展开指示是纯装饰(aria-hidden),折叠态整个不占位 */
123
184
  [data-scope='side-nav'][data-part='root'][data-collapsed] [data-part='branch-indicator']:not([data-popout] *) {
124
185
  display: none;
125
186
  }
@@ -130,34 +191,70 @@
130
191
  padding-inline: 0;
131
192
  }
132
193
 
133
- /* 折叠态的弹出面板:贴着触发按钮旁侧的浮层卡片,坐标由连接层写在行内样式上。
194
+ /* 弹出面板的定位层:坐标由连接层写在行内样式上,这里只管层号与命中区。
195
+ 它被搬到浮层落点、不在侧栏子树内,所以卡片外观仍归面板本身;
196
+ 这里不设 display,UA 的 [hidden]{display:none} 就仍然压得住收起态 */
197
+ [data-scope='side-nav'][data-part='positioner'] {
198
+ /* 落定那一侧的可用高度,由定位引擎经内联自定义属性写进来,面板继承它限高;
199
+ 引擎没算(尚未落位 / 无引擎 / 贴边归零)时不额外限高 */
200
+ --xh-_side-nav-available-h: 100vh;
201
+
202
+ position: fixed;
203
+ z-index: var(--xh-side-nav-popout-layer, var(--xh-layer-popover));
204
+ pointer-events: auto;
205
+ }
206
+
207
+ /* 还没落位就先藏着:visibility 而非 display,display:none 会让引擎量不到面板尺寸 */
208
+ [data-scope='side-nav'][data-part='positioner'][data-hidden] {
209
+ visibility: hidden;
210
+ }
211
+
212
+ /* 折叠态的弹出面板:贴着触发按钮旁侧的浮层卡片。
134
213
  面板自己不吃子层缩进(padding 整圈盖掉),面板内的嵌套子层照常缩进 */
135
214
  [data-scope='side-nav'][data-part='branch-content'][data-popout] {
136
- z-index: var(--xh-side-nav-popout-layer, var(--xh-layer-popover));
137
215
  min-inline-size: var(--xh-side-nav-popout-min-w, var(--xh-overlay-menu-min-w));
138
216
  max-inline-size: var(--xh-side-nav-popout-max-w, var(--xh-overlay-max-w));
139
217
  padding: var(--xh-side-nav-popout-p, var(--xh-space-1));
140
218
  border: var(--xh-stroke-thin) solid var(--xh-side-nav-popout-border, var(--xh-border-default));
141
219
  border-radius: var(--xh-side-nav-popout-radius, var(--xh-shape-surface));
142
220
  background: var(--xh-side-nav-popout-bg, var(--xh-bg-surface));
143
- box-shadow: var(--xh-side-nav-popout-shadow, var(--xh-elevation-2));
144
- pointer-events: auto;
221
+ box-shadow: var(--xh-side-nav-popout-shadow, var(--xh-elevation-floating));
222
+
223
+ /* 子导航条目数没有上限,矮视口下不限高就整块伸出视口且滚不到 */
224
+ max-block-size: min(var(--xh-side-nav-popout-max-h, var(--xh-overlay-menu-max-h)), var(--xh-_side-nav-available-h));
225
+ overflow-y: auto;
226
+ }
227
+
228
+ [data-scope='side-nav'][data-part='branch-content'][data-popout][data-state='open'] {
229
+ animation: xh-pop-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter);
230
+ }
231
+
232
+ /* 退场:收起那一帧 hidden 由退场闸门押后,这里才播得出来。forwards 把末态钉住,
233
+ 免得动画结束到收起落下来之间闪一下原样 */
234
+ [data-scope='side-nav'][data-part='branch-content'][data-popout][data-state='closed'] {
235
+ animation: xh-pop-out var(--xh-motion-duration-exit) var(--xh-motion-ease-exit) forwards;
145
236
  }
146
237
 
147
238
  [data-scope='side-nav'][data-part='branch-indicator'] {
148
239
  margin-inline-start: auto;
149
240
  display: inline-flex;
150
241
  flex: none;
151
- transition: rotate var(--xh-duration-fast) var(--xh-ease-standard);
242
+ transition: rotate var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
152
243
  }
153
244
 
154
245
  [data-scope='side-nav'][data-part='branch-indicator'][data-state='open'] {
155
246
  rotate: 90deg;
156
247
  }
157
248
 
158
- @media (prefers-reduced-motion: reduce) {
159
- [data-scope='side-nav'][data-part='branch-indicator'] {
160
- transition: none;
161
- }
249
+ /* 没写内容就由皮肤画兜底字形;作者往部件里塞了自己的图形,这几条即不命中 */
250
+ [data-scope='side-nav'][data-part='branch-indicator']:empty::before {
251
+ content: '';
252
+ display: inline-block;
253
+ inline-size: var(--xh-icon-size, var(--xh-glyph-size-text));
254
+ block-size: var(--xh-icon-size, var(--xh-glyph-size-text));
255
+ vertical-align: var(--xh-glyph-baseline-shift);
256
+ background-color: currentColor;
257
+ -webkit-mask: var(--xh-glyph-mark-chevron-right) center / contain no-repeat;
258
+ mask: var(--xh-glyph-mark-chevron-right) center / contain no-repeat;
162
259
  }
163
260
  }
@@ -0,0 +1,144 @@
1
+ @layer xihan.components {
2
+ /* 皮肤在场的标记:开发模式下按 scope 探一次,取不到就说明这份皮肤没被引入。 */
3
+ [data-scope='signature-pad'] {
4
+ --xh-signature-pad-skin: 1;
5
+ }
6
+
7
+ /* position 是给表单影子做定位基准的:那份输入被连接层内联写成绝对定位,
8
+ 根上不留基准它就会贴到更外层的定位祖先上,原生校验提示跟着飘到页面别处。 */
9
+ [data-scope='signature-pad'][data-part='root'] {
10
+ position: relative;
11
+ display: flex;
12
+ flex-direction: column;
13
+ gap: var(--xh-signature-pad-gap, var(--xh-stack-gap-md));
14
+ }
15
+
16
+ [data-scope='signature-pad'][data-part='label'] {
17
+ color: var(--xh-signature-pad-label-fg, var(--xh-fg-muted));
18
+ font-size: var(--xh-signature-pad-label-font-size, var(--xh-text-label-size));
19
+ font-weight: var(--xh-signature-pad-label-font-weight, var(--xh-text-label-weight));
20
+ line-height: var(--xh-leading-none);
21
+ }
22
+
23
+ [data-scope='signature-pad'][data-part='label'][data-disabled] {
24
+ color: var(--xh-fg-subtle);
25
+ }
26
+
27
+ /* 画布是块状的写字区:宽度铺满外层,高度由宽高比决定。
28
+ 控件行高那把尺量的是一行控件,一块要写字的面积用不上它。
29
+ touch-action 由连接层内联写死,触摸落笔才不会被页面滚动抢走。 */
30
+ [data-scope='signature-pad'][data-part='control'] {
31
+ display: block;
32
+ inline-size: 100%;
33
+ aspect-ratio: var(--xh-signature-pad-aspect-ratio, 5 / 2);
34
+ border: var(--xh-stroke-thin) solid var(--xh-signature-pad-border, var(--xh-border-control));
35
+ border-radius: var(--xh-signature-pad-radius, var(--xh-shape-surface));
36
+ background: var(--xh-signature-pad-bg, var(--xh-bg-surface));
37
+ cursor: crosshair;
38
+ /* 一划就会把画布连同旁边的文字一起刷成选中态 */
39
+ user-select: none;
40
+ }
41
+
42
+ [data-scope='signature-pad'][data-part='control'][data-invalid] {
43
+ border-color: var(--xh-border-invalid);
44
+ }
45
+
46
+ /* 落笔期间把描边压深一档。border-strong 是装饰梯度里的一档,比 border-control 更淡,
47
+ 用在这儿会让描边在落笔时反而变弱 */
48
+ [data-scope='signature-pad'][data-part='control'][data-drawing] {
49
+ border-color: var(--xh-signature-pad-border-drawing, var(--xh-border-control-hover));
50
+ }
51
+
52
+ /* 只读仍看得见签名,只是改不动:收掉手型,别暗示"在这儿能写" */
53
+ [data-scope='signature-pad'][data-part='control'][data-readonly] {
54
+ cursor: default;
55
+ }
56
+
57
+ [data-scope='signature-pad'][data-part='control'][data-disabled] {
58
+ background: var(--xh-signature-pad-bg-disabled, var(--xh-bg-muted));
59
+ cursor: not-allowed;
60
+ }
61
+
62
+ /* 基准线:位置由连接层按百分比给出,这里只管它长什么样。
63
+ 它是"请在这条线上签"的功能性提示,不是装饰,颜色走控件描边那一档才看得见 */
64
+ [data-scope='signature-pad'][data-part='guide'] {
65
+ stroke: var(--xh-signature-pad-guide-stroke, var(--xh-border-control));
66
+ stroke-width: var(--xh-stroke-thin);
67
+ stroke-dasharray: 6 6;
68
+ }
69
+
70
+ /* 笔迹是一条填充路径(每一笔是它的一条子路径),不是描边:
71
+ 粗细要随压感变,而 stroke-width 全线只有一个值 */
72
+ [data-scope='signature-pad'][data-part='segment'] {
73
+ fill: var(--xh-signature-pad-ink, var(--xh-fg-default));
74
+ stroke: none;
75
+ }
76
+
77
+ [data-scope='signature-pad'][data-part='clear-trigger'] {
78
+ display: inline-flex;
79
+ align-self: flex-start;
80
+ align-items: center;
81
+ justify-content: center;
82
+ gap: var(--xh-signature-pad-clear-gap, var(--xh-control-gap-sm));
83
+ block-size: var(--xh-signature-pad-clear-h, var(--xh-control-h-sm));
84
+ padding-inline: var(--xh-signature-pad-clear-px, var(--xh-control-px-sm));
85
+ border: var(--xh-stroke-thin) solid var(--xh-signature-pad-clear-border, var(--xh-border-control));
86
+ border-radius: var(--xh-signature-pad-clear-radius, var(--xh-shape-control));
87
+ background: var(--xh-signature-pad-clear-bg, var(--xh-bg-surface));
88
+ color: var(--xh-signature-pad-clear-fg, var(--xh-fg-default));
89
+ font-size: var(--xh-text-label-size);
90
+ line-height: var(--xh-leading-none);
91
+ cursor: pointer;
92
+ transition:
93
+ background var(--xh-motion-duration-micro) var(--xh-motion-ease-enter),
94
+ scale var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
95
+ }
96
+
97
+ [data-scope='signature-pad'][data-part='clear-trigger']:hover {
98
+ background: var(--xh-signature-pad-clear-bg-hover, var(--xh-bg-subtle-hover));
99
+ }
100
+
101
+ /* 按下:换到按压档底色并轻微下压,松手弹回;禁用钮不压 */
102
+ [data-scope='signature-pad'][data-part='clear-trigger']:not(:disabled):active {
103
+ background: var(--xh-signature-pad-clear-bg-active, var(--xh-bg-subtle-active));
104
+ scale: var(--xh-motion-scale-press);
105
+ }
106
+
107
+ [data-scope='signature-pad'][data-part='clear-trigger']:focus-visible {
108
+ outline: var(--xh-ring-width) solid var(--xh-ring-focus);
109
+ outline-offset: var(--xh-ring-offset);
110
+ }
111
+
112
+ /* 空画布时按钮照常可按(收起它会让焦点掉回 body),只把它压淡一档 */
113
+ [data-scope='signature-pad'][data-part='clear-trigger'][data-empty] {
114
+ color: var(--xh-fg-muted);
115
+ }
116
+
117
+ /* 单体原生控件走 :disabled;集合条目那套 [data-disabled] 在这里命不中。
118
+ 禁用只换前景色,不再叠 opacity——两样一起上会把文字压到看不见 */
119
+ [data-scope='signature-pad'][data-part='clear-trigger']:disabled {
120
+ background: var(--xh-signature-pad-clear-bg-disabled, var(--xh-bg-muted));
121
+ color: var(--xh-fg-disabled);
122
+ cursor: not-allowed;
123
+ }
124
+
125
+ /* 签没签的那句话。读屏靠它播报,看得见的人也用得上,因此不做视觉隐藏 */
126
+ [data-scope='signature-pad'][data-part='status'] {
127
+ color: var(--xh-signature-pad-status-fg, var(--xh-fg-muted));
128
+ font-size: var(--xh-signature-pad-status-font-size, var(--xh-text-label-size));
129
+ line-height: var(--xh-leading-none);
130
+ }
131
+
132
+ /* 表单影子的视觉隐藏(绝对定位 + 1px + clip-path)连接层内联给全了,这里不重复也不覆盖,
133
+ 只掐掉指针命中:那个 1px 的框压在画布上会截走本该落到画布上的落笔。 */
134
+ [data-scope='signature-pad'][data-part='hidden-input'] {
135
+ pointer-events: none;
136
+ }
137
+
138
+ /* display 会盖掉 UA 的 [hidden]{display:none},凡是被赋了 display 的角色节点都要显式让位 */
139
+ [data-scope='signature-pad'][data-part='root'][hidden],
140
+ [data-scope='signature-pad'][data-part='control'][hidden],
141
+ [data-scope='signature-pad'][data-part='clear-trigger'][hidden] {
142
+ display: none;
143
+ }
144
+ }
package/css/skeleton.css CHANGED
@@ -13,6 +13,11 @@
13
13
  }
14
14
 
15
15
  @layer xihan.components {
16
+ /* 皮肤在场的标记:开发模式下按 scope 探一次,取不到就说明这份皮肤没被引入。 */
17
+ [data-scope='skeleton'] {
18
+ --xh-skeleton-skin: 1;
19
+ }
20
+
16
21
  /* 骨架容器:竖着码放各根骨架条 */
17
22
  [data-scope='skeleton'][data-part='root'] {
18
23
  display: flex;
@@ -41,7 +46,7 @@
41
46
  );
42
47
  background-repeat: no-repeat;
43
48
  background-size: 200% 100%;
44
- animation: xh-skeleton-shimmer var(--xh-skeleton-duration, calc(var(--xh-duration-slow) * 5)) var(--xh-ease-standard) infinite;
49
+ animation: xh-skeleton-shimmer var(--xh-skeleton-duration, var(--xh-shimmer-duration)) var(--xh-ease-standard) infinite;
45
50
  }
46
51
 
47
52
  /* 一行文字的高度取正文字号,圆角拉满,看起来就是一行还没排出来的字 */
@@ -68,6 +73,7 @@
68
73
  * 减少动效:撤掉微光那层,只留静态底色。
69
74
  * 骨架屏是自己冒出来、会一直循环到数据回来为止的动效,正是最容易引发不适的那一类;
70
75
  * 这里不能只把时长压到接近零——那样光带会停在某一帧上,留下一道说不清的渐变。
76
+ * 两条规则是同一件事的两个触发条件:系统偏好与作者打的 data-motion。
71
77
  */
72
78
  @media (prefers-reduced-motion: reduce) {
73
79
  [data-scope='skeleton'][data-part='bone'] {
@@ -75,4 +81,9 @@
75
81
  animation: none;
76
82
  }
77
83
  }
84
+
85
+ :where([data-motion='reduce']) [data-scope='skeleton'][data-part='bone'] {
86
+ background-image: none;
87
+ animation: none;
88
+ }
78
89
  }
package/css/slider.css CHANGED
@@ -1,4 +1,9 @@
1
1
  @layer xihan.components {
2
+ /* 皮肤在场的标记:开发模式下按 scope 探一次,取不到就说明这份皮肤没被引入。 */
3
+ [data-scope='slider'] {
4
+ --xh-slider-skin: 1;
5
+ }
6
+
2
7
  /* 定位分工:已选区间与拇指在**主轴**上的起点与长度,由连接层每帧写进内联样式——
3
8
  水平是 inset-inline-start / inline-size,竖直是 inset-block-end / block-size。
4
9
  这份皮肤一个都不碰:内联优先级高于样式表,写了不但是死声明,
@@ -8,8 +13,8 @@
8
13
  /* 私有槽位:组件令牌 → 中档字面量两级回退。轨道厚度与拇指直径同时被 control / track / thumb
9
14
  三个角色节点用到,收成两个槽位挂在 root 上,尺寸档只改这两处,靠继承发到子部件。 */
10
15
  [data-scope='slider'][data-part='root'] {
11
- --xh-_thumb-size: var(--xh-slider-thumb-size, 18px);
12
- --xh-_track-thickness: var(--xh-slider-track-thickness, 6px);
16
+ --xh-_thumb-size: var(--xh-slider-thumb-size, var(--xh-track-thumb-size));
17
+ --xh-_track-thickness: var(--xh-slider-track-thickness, var(--xh-track-thickness));
13
18
 
14
19
  display: flex;
15
20
  flex-direction: column;
@@ -111,7 +116,7 @@
111
116
  }
112
117
 
113
118
  [data-scope='slider'][data-part='range'][data-invalid] {
114
- background: var(--xh-slider-range-bg-invalid, var(--xh-color-danger-500));
119
+ background: var(--xh-slider-range-bg-invalid, var(--xh-border-invalid));
115
120
  }
116
121
 
117
122
  /* 圆点浮在轨道之上:轨道是流内的,拇指是绝对定位的,天然后来居上,不必再堆 z-index。
@@ -125,7 +130,7 @@
125
130
  border: var(--xh-stroke-thick) solid var(--xh-slider-thumb-border, var(--xh-bg-surface));
126
131
  border-radius: var(--xh-slider-thumb-radius, var(--xh-shape-pill));
127
132
  background: var(--xh-slider-thumb-bg, var(--xh-_tone, var(--xh-bg-brand)));
128
- box-shadow: var(--xh-slider-thumb-shadow, var(--xh-elevation-1));
133
+ box-shadow: var(--xh-slider-thumb-shadow, var(--xh-elevation-raised));
129
134
  cursor: grab;
130
135
  transition: scale var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
131
136
  }
@@ -156,13 +161,13 @@
156
161
  /* 多拇指时只有手真正推着的那一个带 data-dragging,放大与光标因此不会全组一起变。
157
162
  放大用 scale 而不是改尺寸:变换绕自身中心发生,圆心不动,负外边距的算式也不用跟着改。 */
158
163
  [data-scope='slider'][data-part='thumb'][data-dragging] {
159
- scale: var(--xh-slider-thumb-scale-dragging, 1.12);
164
+ scale: var(--xh-slider-thumb-scale-dragging, var(--xh-motion-scale-drag));
160
165
  cursor: grabbing;
161
- box-shadow: var(--xh-slider-thumb-shadow-dragging, var(--xh-elevation-2));
166
+ box-shadow: var(--xh-slider-thumb-shadow-dragging, var(--xh-elevation-floating));
162
167
  }
163
168
 
164
169
  [data-scope='slider'][data-part='thumb'][data-invalid] {
165
- background: var(--xh-slider-thumb-bg-invalid, var(--xh-color-danger-500));
170
+ background: var(--xh-slider-thumb-bg-invalid, var(--xh-border-invalid));
166
171
  }
167
172
 
168
173
  [data-scope='slider'][data-part='thumb'][data-readonly] {
@@ -204,7 +209,7 @@
204
209
  }
205
210
 
206
211
  /* 落进已选区间的刻度点跟 range 同色 */
207
- [data-scope='slider'][data-part='mark'][data-active] {
212
+ [data-scope='slider'][data-part='mark'][data-passed] {
208
213
  background: var(--xh-slider-mark-bg-active, var(--xh-_tone, var(--xh-bg-brand)));
209
214
  }
210
215
 
@@ -213,7 +218,7 @@
213
218
  inset-block-start: 100%;
214
219
  margin-block-start: var(--xh-slider-mark-label-gap, var(--xh-space-1));
215
220
  color: var(--xh-slider-mark-label-fg, var(--xh-fg-subtle));
216
- font-size: var(--xh-slider-mark-label-font-size, var(--xh-font-size-xs));
221
+ font-size: var(--xh-slider-mark-label-font-size, var(--xh-text-caption-size));
217
222
  line-height: var(--xh-leading-none);
218
223
  white-space: nowrap;
219
224
  translate: -50% 0;
@@ -222,7 +227,7 @@
222
227
  user-select: none;
223
228
  }
224
229
 
225
- [data-scope='slider'][data-part='mark-label'][data-active] {
230
+ [data-scope='slider'][data-part='mark-label'][data-passed] {
226
231
  color: var(--xh-slider-mark-label-fg-active, var(--xh-fg-default));
227
232
  }
228
233
 
package/css/space.css ADDED
@@ -0,0 +1,117 @@
1
+ @layer xihan.components {
2
+ /* 皮肤在场的标记:开发模式下按 scope 探一次,取不到就说明这份皮肤没被引入。 */
3
+ [data-scope='space'] {
4
+ --xh-space-skin: 1;
5
+ }
6
+
7
+ [data-scope='space'][data-part='root'] {
8
+ display: flex;
9
+ flex-wrap: nowrap;
10
+ /* 不写档位也按 md 留白,这是与裸弹性容器的分野;使用者槽位排在档位之前,写死一个值即盖过所有档 */
11
+ gap: var(--xh-space-gap, var(--xh-space-3));
12
+ }
13
+
14
+ /* 声明了 display 就盖掉了 UA 的 [hidden] 规则,这里还回去 */
15
+ [data-scope='space'][data-part='root'][hidden] {
16
+ display: none;
17
+ }
18
+
19
+ /* 行内盒:宽度收到内容,能和文字排在同一行 */
20
+ [data-scope='space'][data-part='root'][data-inline]:not([hidden]) {
21
+ display: inline-flex;
22
+ }
23
+
24
+ /* 分隔符:夹在两个子项之间的装饰件,样子由放进去的内容决定。
25
+ 这里只兜住两件事——不被主轴挤扁(一条 1px 的竖线会被弹性收缩吃没),
26
+ 交叉轴上不单独发话,跟着容器的对齐走:横排随中线、竖排随拉伸 */
27
+ [data-scope='space'][data-part='split'] {
28
+ flex: none;
29
+ }
30
+
31
+ /* —— 方向与缺省对齐 —— */
32
+ /* 横排时按中线对齐:一排里混着文字、按钮、标签,高度本就不齐,居中最不容易看出错位 */
33
+ [data-scope='space'][data-part='root'][data-orientation='horizontal'] {
34
+ flex-direction: row;
35
+ align-items: center;
36
+ }
37
+
38
+ /* 竖排时拉伸:一列里的块级子项该占满容器宽度 */
39
+ [data-scope='space'][data-part='root'][data-orientation='vertical'] {
40
+ flex-direction: column;
41
+ align-items: stretch;
42
+ }
43
+
44
+ /* —— 折行 —— */
45
+ [data-scope='space'][data-part='root'][data-wrap] {
46
+ flex-wrap: wrap;
47
+ }
48
+
49
+ /* —— 交叉轴对齐 —— */
50
+ /* 排在方向之后:两者特指度相同,写了 align 就该压过随方向来的那份缺省 */
51
+ [data-scope='space'][data-part='root'][data-align='start'] {
52
+ align-items: flex-start;
53
+ }
54
+
55
+ [data-scope='space'][data-part='root'][data-align='center'] {
56
+ align-items: center;
57
+ }
58
+
59
+ [data-scope='space'][data-part='root'][data-align='end'] {
60
+ align-items: flex-end;
61
+ }
62
+
63
+ [data-scope='space'][data-part='root'][data-align='stretch'] {
64
+ align-items: stretch;
65
+ }
66
+
67
+ [data-scope='space'][data-part='root'][data-align='baseline'] {
68
+ align-items: baseline;
69
+ }
70
+
71
+ /* —— 主轴分布 —— */
72
+ [data-scope='space'][data-part='root'][data-justify='start'] {
73
+ justify-content: flex-start;
74
+ }
75
+
76
+ [data-scope='space'][data-part='root'][data-justify='center'] {
77
+ justify-content: center;
78
+ }
79
+
80
+ [data-scope='space'][data-part='root'][data-justify='end'] {
81
+ justify-content: flex-end;
82
+ }
83
+
84
+ [data-scope='space'][data-part='root'][data-justify='between'] {
85
+ justify-content: space-between;
86
+ }
87
+
88
+ [data-scope='space'][data-part='root'][data-justify='around'] {
89
+ justify-content: space-around;
90
+ }
91
+
92
+ [data-scope='space'][data-part='root'][data-justify='evenly'] {
93
+ justify-content: space-evenly;
94
+ }
95
+
96
+ /* —— 间距档位 —— */
97
+ /* 每一档指向一个间距令牌,折行时行与行之间也吃这份间距 */
98
+ [data-scope='space'][data-part='root'][data-gap='xs'] {
99
+ gap: var(--xh-space-gap, var(--xh-space-1));
100
+ }
101
+
102
+ [data-scope='space'][data-part='root'][data-gap='sm'] {
103
+ gap: var(--xh-space-gap, var(--xh-space-2));
104
+ }
105
+
106
+ [data-scope='space'][data-part='root'][data-gap='md'] {
107
+ gap: var(--xh-space-gap, var(--xh-space-3));
108
+ }
109
+
110
+ [data-scope='space'][data-part='root'][data-gap='lg'] {
111
+ gap: var(--xh-space-gap, var(--xh-space-4));
112
+ }
113
+
114
+ [data-scope='space'][data-part='root'][data-gap='xl'] {
115
+ gap: var(--xh-space-gap, var(--xh-space-6));
116
+ }
117
+ }
package/css/spinner.css CHANGED
@@ -1,4 +1,4 @@
1
- @layer xihan.components {
1
+ @layer xihan.motion {
2
2
  /* 转圈的关键帧定义在本文件里。皮肤可以按需单独引入,
3
3
  引用别处文件里的动画名时那份文件不一定在场,动画会静默地整个不跑 */
4
4
  @keyframes xh-spinner-rotate {
@@ -6,6 +6,14 @@
6
6
  transform: rotate(1turn);
7
7
  }
8
8
  }
9
+ }
10
+
11
+ @layer xihan.components {
12
+ /* 皮肤在场的标记:开发模式下按 scope 探一次,取不到就说明这份皮肤没被引入。 */
13
+ [data-scope='spinner'] {
14
+ --xh-spinner-skin: 1;
15
+ }
16
+
9
17
 
10
18
  /* 圆环画在伪元素上,root 自身不转:转的只是图形,同一行里的文案要留在原地读得动 */
11
19
  [data-scope='spinner'][data-part='root'] {
@@ -22,25 +30,26 @@
22
30
  /* 边框算进直径,换厚度不会把圆环撑大 */
23
31
  box-sizing: border-box;
24
32
  flex: none;
25
- inline-size: var(--xh-spinner-size, var(--xh-control-action-size));
26
- block-size: var(--xh-spinner-size, var(--xh-control-action-size));
33
+ inline-size: var(--xh-spinner-size, var(--xh-glyph-size-md));
34
+ block-size: var(--xh-spinner-size, var(--xh-glyph-size-md));
27
35
  border: var(--xh-spinner-thickness, var(--xh-stroke-thick)) solid var(--xh-spinner-track, var(--xh-border-default));
28
36
 
29
37
  /* 一整圈是轨道色,只有起始边换成前景色,转起来才看得出方向 */
30
38
  border-block-start-color: var(--xh-spinner-fg, var(--xh-bg-brand));
31
39
  border-radius: var(--xh-spinner-radius, var(--xh-shape-pill));
32
- animation: xh-spinner-rotate var(--xh-spinner-duration, calc(var(--xh-duration-slow) * 2)) linear infinite;
40
+ animation: xh-spinner-rotate var(--xh-spinner-duration, var(--xh-spin-duration)) linear infinite;
33
41
  }
34
42
 
35
- /* 三档直径对齐控件档位,缺省档写在上面的基础规则里 */
43
+ /* 三档直径走字形尺寸那把尺:圆环与同一行文字里的图标同级,三档同族才不会在 compact 下错位。
44
+ 缺省档写在上面的基础规则里 */
36
45
  [data-scope='spinner'][data-part='root'][data-size='sm']::before {
37
- inline-size: var(--xh-control-indicator-size);
38
- block-size: var(--xh-control-indicator-size);
46
+ inline-size: var(--xh-glyph-size-sm);
47
+ block-size: var(--xh-glyph-size-sm);
39
48
  }
40
49
 
41
50
  [data-scope='spinner'][data-part='root'][data-size='lg']::before {
42
- inline-size: var(--xh-control-h-md);
43
- block-size: var(--xh-control-h-md);
51
+ inline-size: var(--xh-glyph-size-lg);
52
+ block-size: var(--xh-glyph-size-lg);
44
53
  }
45
54
 
46
55
  /* 语气只换起始边那一段,轨道留在中性描边上:轨道与起始边的反差就是转向的读法,
@@ -53,12 +62,13 @@
53
62
 
54
63
  [data-scope='spinner'][data-part='label'] {
55
64
  color: var(--xh-spinner-label-fg, var(--xh-fg-muted));
56
- font-size: var(--xh-spinner-label-size, var(--xh-font-size-sm));
65
+ font-size: var(--xh-spinner-label-size, var(--xh-text-secondary-size));
57
66
  line-height: var(--xh-leading-none);
58
67
  }
59
68
 
60
69
  /* 减少动效:圆环停下并整圈换成虚线,静止的形状仍然读得出"这里还没好";
61
- "在等什么"由 role=status 上的名字与可见文案继续承担 */
70
+ "在等什么"由 role=status 上的名字与可见文案继续承担。
71
+ 两组规则是同一件事的两个触发条件:系统偏好与作者打的 data-motion */
62
72
  @media (prefers-reduced-motion: reduce) {
63
73
  [data-scope='spinner'][data-part='root']::before {
64
74
  border-style: dotted;
@@ -71,4 +81,14 @@
71
81
  border-color: var(--xh-spinner-fg, var(--xh-_tone, var(--xh-bg-brand)));
72
82
  }
73
83
  }
84
+
85
+ :where([data-motion='reduce']) [data-scope='spinner'][data-part='root']::before {
86
+ border-style: dotted;
87
+ border-color: var(--xh-spinner-fg, var(--xh-bg-brand));
88
+ animation: none;
89
+ }
90
+
91
+ :where([data-motion='reduce']) [data-scope='spinner'][data-part='root'][data-tone]::before {
92
+ border-color: var(--xh-spinner-fg, var(--xh-_tone, var(--xh-bg-brand)));
93
+ }
74
94
  }