@xihan-ui/styles 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/CHANGELOG.md +5146 -6
  2. package/README.md +22 -2
  3. package/component-tokens.d.ts +4212 -0
  4. package/component-tokens.js +2 -0
  5. package/components.tokens.json +80116 -0
  6. package/css/accordion.css +131 -75
  7. package/css/affix.css +16 -1
  8. package/css/alert.css +114 -67
  9. package/css/anchor.css +99 -27
  10. package/css/approval.css +461 -0
  11. package/css/avatar-group.css +14 -6
  12. package/css/avatar.css +33 -9
  13. package/css/back-top.css +103 -48
  14. package/css/badge.css +42 -14
  15. package/css/bar-code.css +62 -0
  16. package/css/breadcrumb.css +40 -21
  17. package/css/button-group.css +146 -24
  18. package/css/button.css +116 -99
  19. package/css/calendar-picker.css +448 -0
  20. package/css/calendar-range-picker.css +517 -0
  21. package/css/card.css +54 -77
  22. package/css/carousel.css +412 -68
  23. package/css/cascader.css +429 -246
  24. package/css/checkbox-group.css +225 -77
  25. package/css/checkbox.css +174 -37
  26. package/css/clipboard.css +266 -47
  27. package/css/code-view.css +279 -0
  28. package/css/collapsible.css +108 -63
  29. package/css/color-field.css +158 -0
  30. package/css/color-picker.css +172 -184
  31. package/css/color-slider.css +274 -0
  32. package/css/color-swatch-picker.css +243 -0
  33. package/css/color-swatch.css +35 -0
  34. package/css/combobox.css +267 -273
  35. package/css/command.css +399 -0
  36. package/css/context-menu.css +183 -108
  37. package/css/date-field.css +94 -121
  38. package/css/date-picker.css +486 -249
  39. package/css/date-range-picker.css +559 -0
  40. package/css/description.css +51 -0
  41. package/css/descriptions.css +133 -87
  42. package/css/dialog.css +247 -68
  43. package/css/diff-view.css +428 -0
  44. package/css/download-trigger.css +155 -46
  45. package/css/drawer.css +194 -111
  46. package/css/editable.css +172 -152
  47. package/css/empty-state.css +80 -11
  48. package/css/field-array.css +190 -0
  49. package/css/field.css +84 -31
  50. package/css/fieldset.css +51 -18
  51. package/css/file-upload.css +134 -36
  52. package/css/flex.css +20 -1
  53. package/css/float-button.css +238 -57
  54. package/css/floating-panel.css +173 -92
  55. package/css/focus.css +46 -0
  56. package/css/forced-colors.css +93 -0
  57. package/css/form.css +254 -77
  58. package/css/gradient-text.css +34 -10
  59. package/css/grid.css +516 -5
  60. package/css/heatmap.css +87 -29
  61. package/css/highlight.css +20 -5
  62. package/css/hover-card.css +97 -47
  63. package/css/icon-wrapper.css +15 -3
  64. package/css/icon.css +74 -8
  65. package/css/image-cropper.css +143 -29
  66. package/css/image-viewer.css +150 -89
  67. package/css/image.css +30 -1
  68. package/css/infinite-scroll.css +51 -1
  69. package/css/input-group.css +277 -0
  70. package/css/json-viewer.css +102 -45
  71. package/css/kbd.css +72 -0
  72. package/css/label.css +78 -0
  73. package/css/layers.css +5 -0
  74. package/css/layout.css +175 -31
  75. package/css/list.css +24 -3
  76. package/css/listbox.css +208 -67
  77. package/css/loading-bar.css +52 -3
  78. package/css/log.css +144 -9
  79. package/css/markdown-stream.css +149 -0
  80. package/css/marquee.css +27 -1
  81. package/css/masonry.css +7 -0
  82. package/css/matrix-code.css +67 -0
  83. package/css/mention.css +234 -152
  84. package/css/menu.css +180 -83
  85. package/css/menubar.css +204 -136
  86. package/css/message-feed.css +197 -0
  87. package/css/navigation-menu.css +144 -90
  88. package/css/notification.css +178 -100
  89. package/css/number-animation.css +7 -0
  90. package/css/number-field.css +120 -277
  91. package/css/overlay-arrow.css +12 -7
  92. package/css/page-header.css +99 -14
  93. package/css/pagination.css +175 -135
  94. package/css/password-input.css +222 -219
  95. package/css/pin-input.css +101 -67
  96. package/css/pointer.css +36 -0
  97. package/css/popconfirm.css +185 -94
  98. package/css/popover.css +121 -77
  99. package/css/progress.css +60 -12
  100. package/css/prompt-input.css +224 -0
  101. package/css/question-flow.css +513 -0
  102. package/css/radio-group.css +147 -30
  103. package/css/rating.css +193 -43
  104. package/css/reasoning.css +336 -0
  105. package/css/reset.css +84 -6
  106. package/css/resizable.css +237 -0
  107. package/css/scroll-area.css +68 -7
  108. package/css/scrollbar.css +47 -13
  109. package/css/segmented.css +94 -29
  110. package/css/select.css +288 -274
  111. package/css/separator.css +101 -1
  112. package/css/side-nav.css +133 -75
  113. package/css/signature-pad.css +74 -54
  114. package/css/skeleton.css +84 -11
  115. package/css/slider.css +132 -39
  116. package/css/sortable.css +198 -0
  117. package/css/spinner.css +126 -12
  118. package/css/splitter.css +29 -8
  119. package/css/statistic.css +55 -0
  120. package/css/steps.css +206 -72
  121. package/css/switch.css +268 -36
  122. package/css/table.css +695 -118
  123. package/css/tabs.css +604 -42
  124. package/css/tag-group.css +219 -0
  125. package/css/tag.css +118 -28
  126. package/css/tags-input.css +156 -274
  127. package/css/text-field.css +101 -249
  128. package/css/time-field.css +100 -128
  129. package/css/time-picker.css +323 -252
  130. package/css/time-range-picker.css +719 -0
  131. package/css/timeline.css +202 -68
  132. package/css/timer.css +56 -52
  133. package/css/timestamp.css +34 -0
  134. package/css/toast.css +373 -144
  135. package/css/toggle-group.css +309 -76
  136. package/css/toggle.css +116 -85
  137. package/css/tone.css +47 -0
  138. package/css/tool-call.css +421 -0
  139. package/css/toolbar.css +181 -33
  140. package/css/tooltip.css +107 -36
  141. package/css/tour.css +183 -152
  142. package/css/transfer.css +351 -138
  143. package/css/tree-select.css +323 -216
  144. package/css/tree.css +294 -62
  145. package/css/truncate.css +65 -0
  146. package/css/typography.css +228 -5
  147. package/css/undefined.css +10 -1
  148. package/css/virtualizer.css +9 -10
  149. package/css/visually-hidden.css +7 -0
  150. package/css/watermark.css +7 -0
  151. package/family/action-control.css +487 -0
  152. package/family/collection-item.css +721 -0
  153. package/family/field-chrome.css +298 -0
  154. package/family/motion.css +166 -0
  155. package/family/swatch.css +59 -0
  156. package/index.css +41705 -126
  157. package/index.unlayered.css +33692 -17339
  158. package/package.json +51 -17
  159. package/css/calendar.css +0 -367
  160. package/css/code-block.css +0 -91
  161. package/css/composer.css +0 -134
  162. package/css/countdown.css +0 -26
  163. package/css/dynamic-input.css +0 -220
  164. package/css/ellipsis.css +0 -52
  165. package/css/hotkeys.css +0 -90
  166. package/css/popselect.css +0 -391
  167. package/css/qr-code.css +0 -55
  168. package/css/result.css +0 -119
  169. package/css/space.css +0 -117
  170. package/css/thread.css +0 -120
  171. package/css/time.css +0 -25
package/css/skeleton.css CHANGED
@@ -1,3 +1,10 @@
1
+ /*
2
+ * Copyright (c) 2021-Present XiHanFun and contributors.
3
+ * Licensed under the MIT License. See LICENSE in the project root for license information.
4
+ */
5
+
6
+ /* 定义 skeleton 样式。 */
7
+
1
8
  @layer xihan.motion {
2
9
  /* 一条比底色略亮的窄带从头扫到尾。背景图比自身宽一倍,靠移动背景位置扫过去,
3
10
  不动 transform 也不动布局,整段动画只在合成层上跑 */
@@ -10,6 +17,13 @@
10
17
  background-position: -200% 0;
11
18
  }
12
19
  }
20
+
21
+ /* 呼吸档:整根条子在原色与禁用档之间来回淡。往返由 alternate 给,帧里只写一端 */
22
+ @keyframes xh-skeleton-pulse {
23
+ to {
24
+ opacity: var(--xh-state-disabled-opacity);
25
+ }
26
+ }
13
27
  }
14
28
 
15
29
  @layer xihan.components {
@@ -22,7 +36,7 @@
22
36
  [data-scope='skeleton'][data-part='root'] {
23
37
  display: flex;
24
38
  flex-direction: column;
25
- gap: var(--xh-skeleton-gap, var(--xh-space-2));
39
+ gap: var(--xh-skeleton-gap, var(--xh-space-3));
26
40
  }
27
41
 
28
42
  /* 声明了 display 就盖掉了 UA 的 [hidden]{display:none},补这条兜住加载结束后的收起态 */
@@ -33,42 +47,79 @@
33
47
  /*
34
48
  * 单根骨架条。底色与微光两层叠在一起:
35
49
  * background-color 是静止时看到的那层,background-image 是扫过去的那道光。
50
+ * 微光取比底亮一档的那支面色:浅色下比 bg-subtle 亮、深色下同样比它亮,
51
+ * 两个主题里扫过去的都是一道亮带,而不是浅色下反倒扫出一道暗带。
36
52
  */
37
- [data-scope='skeleton'][data-part='bone'] {
53
+ [data-scope='skeleton'][data-part='item'] {
38
54
  inline-size: 100%;
55
+ pointer-events: none;
39
56
  border-radius: var(--xh-skeleton-radius, var(--xh-shape-control));
40
57
  background-color: var(--xh-skeleton-bg, var(--xh-bg-subtle));
41
58
  background-image: linear-gradient(
42
59
  90deg,
43
60
  transparent 20%,
44
- var(--xh-skeleton-sheen, var(--xh-bg-subtle-hover)) 50%,
61
+ var(--xh-skeleton-sheen, var(--xh-bg-surface-raised)) 50%,
45
62
  transparent 80%
46
63
  );
47
64
  background-repeat: no-repeat;
48
65
  background-size: 200% 100%;
49
- animation: xh-skeleton-shimmer var(--xh-skeleton-duration, var(--xh-shimmer-duration)) var(--xh-ease-standard) infinite;
66
+ animation: xh-skeleton-shimmer var(--xh-skeleton-duration, var(--xh-shimmer-duration)) var(--xh-motion-ease-loop) infinite;
50
67
  }
51
68
 
52
69
  /* 一行文字的高度取正文字号,圆角拉满,看起来就是一行还没排出来的字 */
53
- [data-scope='skeleton'][data-part='bone'][data-variant='text'] {
54
- block-size: var(--xh-skeleton-text-block-size, var(--xh-text-body-size));
70
+ [data-scope='skeleton'][data-part='item'][data-shape='text'] {
71
+ block-size: var(--xh-skeleton-text-block-size, var(--xh-text-caption-size));
55
72
  border-radius: var(--xh-skeleton-text-radius, var(--xh-shape-pill));
56
73
  }
57
74
 
58
75
  /* 头像位:正方形加满圆角。宽度定死不跟着容器拉长,高度也不许被主轴压扁 */
59
- [data-scope='skeleton'][data-part='bone'][data-variant='circle'] {
76
+ [data-scope='skeleton'][data-part='item'][data-shape='circle'] {
60
77
  flex: none;
61
78
  inline-size: var(--xh-skeleton-circle-size, var(--xh-control-h-lg));
62
79
  aspect-ratio: 1;
63
- border-radius: var(--xh-skeleton-circle-radius, var(--xh-shape-pill));
80
+ border-radius: var(--xh-skeleton-circle-radius, var(--xh-shape-circle));
64
81
  }
65
82
 
66
83
  /* 图片、卡片这类成块的占位:给个最小高度,撑开由作者决定 */
67
- [data-scope='skeleton'][data-part='bone'][data-variant='rect'] {
84
+ [data-scope='skeleton'][data-part='item'][data-shape='rect'] {
68
85
  min-block-size: var(--xh-skeleton-rect-block-size, var(--xh-control-h-lg));
69
86
  border-radius: var(--xh-skeleton-rect-radius, var(--xh-shape-surface));
70
87
  }
71
88
 
89
+ /*
90
+ * 呼吸档:撤掉微光那层,整根条子在原色与禁用档之间来回淡。
91
+ * 微光扫的是一道亮带,呼吸改的是整根的浓淡——两者都表达"还在等",
92
+ * 换哪一档由作者定,缺省仍是微光。
93
+ */
94
+ [data-scope='skeleton'][data-part='root'][data-animation='pulse'] [data-part='item'] {
95
+ background-image: none;
96
+ animation: xh-skeleton-pulse var(--xh-skeleton-pulse-duration, var(--xh-shimmer-duration)) var(--xh-motion-ease-sweep) infinite alternate;
97
+ }
98
+
99
+ /* 静止档:底色留着,两层动效都撤掉 */
100
+ [data-scope='skeleton'][data-part='root'][data-animation='none'] [data-part='item'] {
101
+ background-image: none;
102
+ animation: none;
103
+ }
104
+
105
+ /* 呼吸档自带一条动画,压不住下面基础降级里的 animation: none,这一档各自再写一份。
106
+ 两组规则是同一件事的两个触发条件:系统偏好与作者打的 data-motion */
107
+ @media (prefers-reduced-motion: reduce) {
108
+ [data-scope='skeleton'][data-part='root'][data-animation='pulse'] [data-part='item'] {
109
+ animation: none;
110
+ }
111
+ }
112
+
113
+ :where([data-motion='reduce']) [data-scope='skeleton'][data-part='root'][data-animation='pulse'] [data-part='item'] {
114
+ animation: none;
115
+ }
116
+
117
+ @media print {
118
+ [data-scope='skeleton'][data-part='root'][data-animation='pulse'] [data-part='item'] {
119
+ animation: none;
120
+ }
121
+ }
122
+
72
123
  /*
73
124
  * 减少动效:撤掉微光那层,只留静态底色。
74
125
  * 骨架屏是自己冒出来、会一直循环到数据回来为止的动效,正是最容易引发不适的那一类;
@@ -76,14 +127,36 @@
76
127
  * 两条规则是同一件事的两个触发条件:系统偏好与作者打的 data-motion。
77
128
  */
78
129
  @media (prefers-reduced-motion: reduce) {
79
- [data-scope='skeleton'][data-part='bone'] {
130
+ [data-scope='skeleton'][data-part='item'] {
80
131
  background-image: none;
81
132
  animation: none;
82
133
  }
83
134
  }
84
135
 
85
- :where([data-motion='reduce']) [data-scope='skeleton'][data-part='bone'] {
136
+ :where([data-motion='reduce']) [data-scope='skeleton'][data-part='item'] {
86
137
  background-image: none;
87
138
  animation: none;
88
139
  }
140
+
141
+ @media print {
142
+ /* 打印:动画会被定格在任意一帧——转了一半的圈、扫到一半的微光。这里换成静止的表达,
143
+ 取值与减弱动效那一档相同。两档各写一块、不并进一条媒体查询列表:减弱动效另有一份
144
+ :where([data-motion='reduce']) 的钩子版本与它成对,打印没有对应的钩子可配,
145
+ 并成一条之后这层配对关系在文本上就断了。 */
146
+ [data-scope='skeleton'][data-part='item'] {
147
+ background-image: none;
148
+ animation: none;
149
+ }
150
+ }
151
+
152
+ /*
153
+ * 高对比档:骨架条整根靠底色画出来,这一档里底色与微光一起被系统换掉,
154
+ * 一块什么都没有的空白——正在加载这件事没有了任何表达。改成描一圈边把条子勾出来。
155
+ */
156
+ @media (forced-colors: active) {
157
+ [data-scope='skeleton'][data-part='item'] {
158
+ border: var(--xh-stroke-thin) solid GrayText;
159
+ background-image: none;
160
+ }
161
+ }
89
162
  }
package/css/slider.css CHANGED
@@ -1,3 +1,10 @@
1
+ /*
2
+ * Copyright (c) 2021-Present XiHanFun and contributors.
3
+ * Licensed under the MIT License. See LICENSE in the project root for license information.
4
+ */
5
+
6
+ /* 定义 slider 样式。 */
7
+
1
8
  @layer xihan.components {
2
9
  /* 皮肤在场的标记:开发模式下按 scope 探一次,取不到就说明这份皮肤没被引入。 */
3
10
  [data-scope='slider'] {
@@ -18,7 +25,8 @@
18
25
 
19
26
  display: flex;
20
27
  flex-direction: column;
21
- gap: var(--xh-slider-gap, var(--xh-stack-gap-md));
28
+ /* 字段标签贴控件(§6.4 复合单字段) */
29
+ gap: var(--xh-slider-gap, var(--xh-space-1));
22
30
  }
23
31
 
24
32
  /* 竖排时整条控件收成一块,不再撑满一行:宽度由拇指直径决定 */
@@ -27,21 +35,18 @@
27
35
  align-items: center;
28
36
  }
29
37
 
30
- /* 禁用的整体压暗只做一次,落在最外层:track / range / thumb 各自再叠一层 opacity 会越叠越黑 */
31
- [data-scope='slider'][data-part='root'][data-disabled] {
32
- cursor: not-allowed;
33
- opacity: var(--xh-state-disabled-opacity);
34
- }
38
+ /* 禁用不整体压暗(§7.2 9 条):前景与表面各自降级——连接层把 data-disabled 发到每个角色节点上,
39
+ 轨道、区间、拇指、刻度与标签就近消费,写在各自的规则里 */
35
40
 
41
+ /* 字段标签:14 / 500 / fg-default(§6.4),禁用换到 --xh-fg-subtle */
36
42
  [data-scope='slider'][data-part='label'] {
37
- color: var(--xh-slider-label-fg, var(--xh-fg-muted));
43
+ color: var(--xh-slider-label-fg, var(--xh-fg-default));
38
44
  font-size: var(--xh-slider-label-font-size, var(--xh-text-label-size));
39
45
  font-weight: var(--xh-slider-label-font-weight, var(--xh-text-label-weight));
40
- line-height: var(--xh-leading-none);
41
46
  }
42
47
 
43
48
  [data-scope='slider'][data-part='label'][data-disabled] {
44
- color: var(--xh-fg-subtle);
49
+ color: var(--xh-slider-label-fg-disabled, var(--xh-fg-subtle));
45
50
  }
46
51
 
47
52
  /* 拇指以它为定位上下文(拇指常常浮出轨道,挂在 track 上会被轨道的盒子束住)。
@@ -55,6 +60,14 @@
55
60
  cursor: pointer;
56
61
  }
57
62
 
63
+ /* 刻度文案以刻度为圆心横向摊开,首末两档各有一半落在轨道之外,页面因此恒多出半个文案的宽度。
64
+ 排了刻度文案的横轨两侧各让出一段,那半个文案落进让出来的这一段里。
65
+ 让在控件上而不是根上:轨道、刻度、拇指的坐标都以控件为基准,一起让开才不会彼此错位。 */
66
+ [data-scope='slider'][data-part='root']:has([data-part='tick-label'])
67
+ [data-part='control'][data-orientation='horizontal'] {
68
+ margin-inline: var(--xh-slider-control-gutter, var(--xh-space-6));
69
+ }
70
+
58
71
  [data-scope='slider'][data-part='control'][data-orientation='vertical'] {
59
72
  flex-direction: column;
60
73
  justify-content: center;
@@ -67,7 +80,7 @@
67
80
  cursor: grabbing;
68
81
  }
69
82
 
70
- [data-scope='slider'][data-part='control'][data-readonly] {
83
+ [data-scope='slider'][data-part='control'][data-readonly]:not([data-disabled]) {
71
84
  cursor: default;
72
85
  }
73
86
 
@@ -93,6 +106,11 @@
93
106
  inline-size: var(--xh-_track-thickness);
94
107
  }
95
108
 
109
+ /* 禁用:底槽退到 100 档淡底,已选区间换成禁用前景——两者仍分得开,值还读得出,只是没了品牌色 */
110
+ [data-scope='slider'][data-part='track'][data-disabled] {
111
+ background: var(--xh-slider-track-bg-disabled, var(--xh-bg-subtle));
112
+ }
113
+
96
114
  /* 主轴(起点 + 长度)来自内联样式,这里只给定位模式与配色。
97
115
  绝不给 inset / size 加过渡:拖拽时这两个值每帧都在变,一加过渡区间就永远追在指针后面。
98
116
  语气槽由 root 上的 data-tone 声明、经继承发下来;没写 data-tone 时退回原来的品牌色。
@@ -119,24 +137,39 @@
119
137
  background: var(--xh-slider-range-bg-invalid, var(--xh-border-invalid));
120
138
  }
121
139
 
140
+ [data-scope='slider'][data-part='range'][data-disabled] {
141
+ background: var(--xh-slider-range-bg-disabled, var(--xh-fg-disabled));
142
+ }
143
+
122
144
  /* 圆点浮在轨道之上:轨道是流内的,拇指是绝对定位的,天然后来居上,不必再堆 z-index。
123
145
  box-sizing 必须显式写:描边要吃进直径里,否则实际盒子比 --xh-_thumb-size 宽两条边,
124
- 下面按半个直径回拉的负外边距就对不上圆心。 */
146
+ 下面按半个直径回拉的负外边距就对不上圆心。
147
+ 拇指是可拖起的 raised 面(§8):描边取 --xh-border-default,raised 影只是加成、不作边界 */
125
148
  [data-scope='slider'][data-part='thumb'] {
126
149
  position: absolute;
127
150
  box-sizing: border-box;
128
151
  inline-size: var(--xh-_thumb-size);
129
152
  block-size: var(--xh-_thumb-size);
130
- border: var(--xh-stroke-thick) solid var(--xh-slider-thumb-border, var(--xh-bg-surface));
131
- border-radius: var(--xh-slider-thumb-radius, var(--xh-shape-pill));
153
+ border: var(--xh-stroke-thick) solid var(--xh-slider-thumb-border, var(--xh-border-default));
154
+ border-radius: var(--xh-slider-thumb-radius, var(--xh-shape-circle));
132
155
  background: var(--xh-slider-thumb-bg, var(--xh-_tone, var(--xh-bg-brand)));
156
+ /* 圆点上没有字,这一支是给环用的:环灌 currentColor 时取到的是与这块底配对的前景色,
157
+ 不写就取到从页面继承下来的正文色 */
158
+ color: var(--xh-_tone-on, var(--xh-fg-on-brand));
133
159
  box-shadow: var(--xh-slider-thumb-shadow, var(--xh-elevation-raised));
134
160
  cursor: grab;
135
161
  transition:
136
- scale var(--xh-motion-duration-micro) var(--xh-motion-ease-enter),
162
+ scale var(--xh-motion-duration-micro) var(--xh-motion-ease-enter-strong),
137
163
  box-shadow var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
138
164
  }
139
165
 
166
+ /* 面是实心的,环色与面同族,画在面里就看不出来;环改取面自己的前景色——
167
+ 那一族色本来就要在这块面上把字读清楚,与面的对比是按可读性定的。
168
+ 禁用的拇指退成白面,不在这一档里,吃库环 */
169
+ [data-scope='slider'][data-part='thumb']:not([data-disabled]):focus-visible {
170
+ --xh-_ring-color: currentColor;
171
+ }
172
+
140
173
  /* 内联样式把 inset-inline-start 定在值所在的比例上,那是拇指的**起始边**不是圆心;
141
174
  用负的逻辑外边距回拉半个直径,圆心才落在比例点上。
142
175
  这里刻意不用 translate(-50%):rtl 下 inset-inline-start 映射到 right,
@@ -155,31 +188,75 @@
155
188
  margin-block-end: calc(var(--xh-_thumb-size) / -2);
156
189
  }
157
190
 
158
- [data-scope='slider'][data-part='thumb']:focus-visible {
159
- outline: var(--xh-ring-width) solid var(--xh-ring-focus);
160
- outline-offset: var(--xh-ring-offset);
161
- }
162
-
163
191
  /* 多拇指时只有手真正推着的那一个带 data-dragging,放大与光标因此不会全组一起变。
164
- 放大用 scale 而不是改尺寸:变换绕自身中心发生,圆心不动,负外边距的算式也不用跟着改。 */
192
+ 放大用 scale 而不是改尺寸:变换绕自身中心发生,圆心不动,负外边距的算式也不用跟着改。
193
+ 海拔从静态的 raised 换到 lifted:跟着手走的东西比贴在页面上的抬得高一档,
194
+ 又不到锚定浮层那一层——用 floating 会让拇指和下拉面板一样重。 */
165
195
  [data-scope='slider'][data-part='thumb'][data-dragging] {
166
196
  scale: var(--xh-slider-thumb-scale-dragging, var(--xh-motion-scale-drag));
167
197
  cursor: grabbing;
168
- box-shadow: var(--xh-slider-thumb-shadow-dragging, var(--xh-elevation-floating));
198
+ box-shadow: var(--xh-slider-thumb-shadow-dragging, var(--xh-elevation-lifted));
169
199
  }
170
200
 
171
201
  [data-scope='slider'][data-part='thumb'][data-invalid] {
172
202
  background: var(--xh-slider-thumb-bg-invalid, var(--xh-border-invalid));
173
203
  }
174
204
 
175
- [data-scope='slider'][data-part='thumb'][data-readonly] {
205
+ [data-scope='slider'][data-part='thumb'][data-readonly]:not([data-disabled]) {
176
206
  cursor: default;
177
207
  }
178
208
 
209
+ /* 禁用:面退到实体白面、描边留着、影收掉——拖不起来的东西不该看着像浮在轨道上。
210
+ 不写 color:圆点上没有字,禁用拇指不进 Tab 序列也不会聚焦,环的前景在这里没有消费者 */
179
211
  [data-scope='slider'][data-part='thumb'][data-disabled] {
212
+ background: var(--xh-slider-thumb-bg-disabled, var(--xh-bg-surface));
213
+ box-shadow: var(--xh-slider-thumb-shadow-disabled, none);
180
214
  cursor: not-allowed;
181
215
  }
182
216
 
217
+ /* 值气泡:挂在拇指里,跟着拇指一起走位。
218
+ 两侧 inset 都钉在 50% 再靠自动外边距回中:气泡宽度由文字撑出,用 translate 回中的话
219
+ rtl 下 inset-inline-start 映射到 right,位移是物理方向的,会把气泡推向错误的一侧。
220
+ 默认只在推动那一刻露面,静止时留在 DOM 里但不占视线 */
221
+ [data-scope='slider'][data-part='value-text'] {
222
+ position: absolute;
223
+ inset-block-end: 100%;
224
+ inset-inline: 50%;
225
+ inline-size: max-content;
226
+ margin-block-end: var(--xh-slider-value-text-offset, var(--xh-space-2));
227
+ margin-inline: auto;
228
+ padding-block: var(--xh-slider-value-text-py, var(--xh-space-0_5));
229
+ padding-inline: var(--xh-slider-value-text-px, var(--xh-space-2));
230
+ border-radius: var(--xh-slider-value-text-radius, var(--xh-shape-control));
231
+ background: var(--xh-slider-value-text-bg, var(--xh-_tone, var(--xh-bg-brand)));
232
+ color: var(--xh-slider-value-text-fg, var(--xh-_tone-on, var(--xh-fg-on-brand)));
233
+ font-size: var(--xh-slider-value-text-font-size, var(--xh-text-caption-size));
234
+ line-height: var(--xh-leading-normal);
235
+ white-space: nowrap;
236
+ /* 气泡贴在拇指上方,指针要落到拇指上,不能被它挡住 */
237
+ pointer-events: none;
238
+ opacity: 0;
239
+ transition: opacity var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
240
+ }
241
+
242
+ [data-scope='slider'][data-part='value-text'][hidden] {
243
+ display: none;
244
+ }
245
+
246
+ /* 只有手真正推着的那一个拇指带 data-dragging,多拇指时气泡不会全组一起冒出来 */
247
+ [data-scope='slider'][data-part='value-text'][data-dragging] {
248
+ opacity: 1;
249
+ }
250
+
251
+ /* 竖排轨道的气泡挪到行侧:压在拇指上方会挡住上一档的值 */
252
+ [data-scope='slider'][data-part='value-text'][data-orientation='vertical'] {
253
+ inset-block: 50%;
254
+ inset-inline: 100% auto;
255
+ block-size: max-content;
256
+ margin-block: auto;
257
+ margin-inline: var(--xh-slider-value-text-offset, var(--xh-space-2)) 0;
258
+ }
259
+
183
260
  /* —— 尺寸 —— */
184
261
  /* 尺寸只改 root 上那两个槽位,control / track / thumb 跟着一起走;中间档不写规则,缺省即中档。
185
262
  两档都保持"拇指是轨道的三倍厚"这个比例,换档时圆点与底槽的关系看着不变。 */
@@ -194,33 +271,34 @@
194
271
  }
195
272
 
196
273
  /* —— 刻度:圆点钉在轨道上、文案排在轨道外侧,坐标由连接层按百分比内联给出 —— */
197
- [data-scope='slider'][data-part='marks'] {
274
+ [data-scope='slider'][data-part='tick-group'] {
198
275
  position: absolute;
199
276
  inset: 0;
200
277
  pointer-events: none;
201
278
  }
202
279
 
203
- [data-scope='slider'][data-part='mark'] {
280
+ /* 刻度点是正方盒,取 circle(§6.3 正方盒不得用 pill 冒充圆) */
281
+ [data-scope='slider'][data-part='tick'] {
204
282
  position: absolute;
205
283
  inset-block-start: 50%;
206
- inline-size: var(--xh-slider-mark-size, var(--xh-space-1));
207
- block-size: var(--xh-slider-mark-size, var(--xh-space-1));
208
- border-radius: var(--xh-slider-mark-radius, var(--xh-shape-pill));
209
- background: var(--xh-slider-mark-bg, var(--xh-border-strong));
284
+ inline-size: var(--xh-slider-tick-size, var(--xh-space-1));
285
+ block-size: var(--xh-slider-tick-size, var(--xh-space-1));
286
+ border-radius: var(--xh-slider-tick-radius, var(--xh-shape-circle));
287
+ background: var(--xh-slider-tick-bg, var(--xh-border-strong));
210
288
  translate: -50% -50%;
211
289
  }
212
290
 
213
291
  /* 落进已选区间的刻度点跟 range 同色 */
214
- [data-scope='slider'][data-part='mark'][data-passed] {
215
- background: var(--xh-slider-mark-bg-active, var(--xh-_tone, var(--xh-bg-brand)));
292
+ [data-scope='slider'][data-part='tick'][data-passed] {
293
+ background: var(--xh-slider-tick-bg-active, var(--xh-_tone, var(--xh-bg-brand)));
216
294
  }
217
295
 
218
- [data-scope='slider'][data-part='mark-label'] {
296
+ [data-scope='slider'][data-part='tick-label'] {
219
297
  position: absolute;
220
298
  inset-block-start: 100%;
221
- margin-block-start: var(--xh-slider-mark-label-gap, var(--xh-space-1));
222
- color: var(--xh-slider-mark-label-fg, var(--xh-fg-subtle));
223
- font-size: var(--xh-slider-mark-label-font-size, var(--xh-text-caption-size));
299
+ margin-block-start: var(--xh-slider-tick-label-gap, var(--xh-space-1));
300
+ color: var(--xh-slider-tick-label-fg, var(--xh-fg-subtle));
301
+ font-size: var(--xh-slider-tick-label-font-size, var(--xh-text-caption-size));
224
302
  line-height: var(--xh-leading-none);
225
303
  white-space: nowrap;
226
304
  translate: -50% 0;
@@ -229,22 +307,37 @@
229
307
  user-select: none;
230
308
  }
231
309
 
232
- [data-scope='slider'][data-part='mark-label'][data-passed] {
233
- color: var(--xh-slider-mark-label-fg-active, var(--xh-fg-default));
310
+ [data-scope='slider'][data-part='tick-label'][data-passed] {
311
+ color: var(--xh-slider-tick-label-fg-active, var(--xh-fg-default));
312
+ }
313
+
314
+ /* 禁用:刻度点与文案由 tick-group 上的 data-disabled 下发(连接层不给每个刻度单发),
315
+ 已过的刻度点与区间同色,文案换禁用前景,点它跳档的手型收掉 */
316
+ [data-scope='slider'][data-part='tick-group'][data-disabled] [data-part='tick'] {
317
+ background: var(--xh-slider-tick-bg-disabled, var(--xh-border-default));
318
+ }
319
+
320
+ [data-scope='slider'][data-part='tick-group'][data-disabled] [data-part='tick'][data-passed] {
321
+ background: var(--xh-slider-tick-bg-active-disabled, var(--xh-fg-disabled));
322
+ }
323
+
324
+ [data-scope='slider'][data-part='tick-group'][data-disabled] [data-part='tick-label'] {
325
+ color: var(--xh-slider-tick-label-fg-disabled, var(--xh-fg-disabled));
326
+ cursor: not-allowed;
234
327
  }
235
328
 
236
329
  /* 竖直轨道:点与文案换轴,坐标由连接层写在 block 轴上 */
237
- [data-scope='slider'][data-part='root'][data-orientation='vertical'] [data-part='mark'] {
330
+ [data-scope='slider'][data-part='root'][data-orientation='vertical'] [data-part='tick'] {
238
331
  inset-block-start: auto;
239
332
  inset-inline-start: 50%;
240
333
  translate: -50% 50%;
241
334
  }
242
335
 
243
- [data-scope='slider'][data-part='root'][data-orientation='vertical'] [data-part='mark-label'] {
336
+ [data-scope='slider'][data-part='root'][data-orientation='vertical'] [data-part='tick-label'] {
244
337
  inset-block-start: auto;
245
338
  inset-inline-start: 100%;
246
339
  margin-block-start: 0;
247
- margin-inline-start: var(--xh-slider-mark-label-gap, var(--xh-space-1));
340
+ margin-inline-start: var(--xh-slider-tick-label-gap, var(--xh-space-1));
248
341
  translate: 0 50%;
249
342
  }
250
343
 
@@ -0,0 +1,198 @@
1
+ /*
2
+ * Copyright (c) 2021-Present XiHanFun and contributors.
3
+ * Licensed under the MIT License. See LICENSE in the project root for license information.
4
+ */
5
+
6
+ /* 定义 sortable 样式。 */
7
+
8
+ /* Sortable 独立皮肤同时传递引入把手的动作控件配方;full bundle 递归去重。 */
9
+ @import '../family/action-control.css';
10
+
11
+ @layer xihan.components {
12
+ /* 皮肤在场的标记:开发模式下按 scope 探一次,取不到就说明这份皮肤没被引入。 */
13
+ [data-scope='sortable'] {
14
+ --xh-sortable-skin: 1;
15
+ }
16
+
17
+ /* 分工:**每一项的位移**由连接层每帧写进内联 transform(让位与跟手都在那儿算)。
18
+ 这份皮肤一条都不碰 transform——内联优先级更高,写了不但是死声明,
19
+ 还会在拖动的每一帧跟连接层抢位置。皮肤只管排布方向、间距、光标与状态表现。 */
20
+
21
+ [data-scope='sortable'][data-part='root'] {
22
+ display: flex;
23
+ gap: var(--xh-sortable-gap, var(--xh-space-2));
24
+ }
25
+
26
+ /* 拖动期间关掉选中:整项可拖时按下不 preventDefault,不关这条会一路刷蓝 */
27
+ [data-scope='sortable'][data-part='root'][data-dragging] {
28
+ user-select: none;
29
+ }
30
+
31
+ [data-scope='sortable'][data-part='root'][data-orientation='vertical'] {
32
+ flex-direction: column;
33
+ }
34
+
35
+ [data-scope='sortable'][data-part='root'][data-orientation='horizontal'] {
36
+ flex-direction: row;
37
+ }
38
+
39
+ /* 换行网格:主轴横排、允许折行,落点按最近中心判,与单轴那两档是不同的判据 */
40
+ [data-scope='sortable'][data-part='root'][data-orientation='both'] {
41
+ flex-direction: row;
42
+ flex-wrap: wrap;
43
+ }
44
+
45
+ /* 项里面放什么、怎么排全是使用者的事:这里不给 display 也不给间距。
46
+ 给了会盖掉使用者自己的单元格布局——网格里的一格被压成 flex 行之后就不再撑满,
47
+ 看起来像是尺寸算错了。皮肤只管拖起来那一项的视觉反馈。 */
48
+
49
+ /* 让位期间关掉过渡,跟手的那一项才不会慢半拍粘在指针后面。
50
+ 其余项反过来要过渡:它们是被让开的,瞬移会看不出发生了什么。 */
51
+ [data-scope='sortable'][data-part='root'][data-dragging] [data-part='item']:not([data-dragging]) {
52
+ transition: transform var(--xh-motion-duration-micro) var(--xh-motion-ease-continuous);
53
+ }
54
+
55
+ /* 抓起与放下时的抬升、压深都淡变。位移不进这条:它由连接层每帧写内联 transform */
56
+ [data-scope='sortable'][data-part='item'] {
57
+ transition:
58
+ box-shadow var(--xh-motion-duration-micro) var(--xh-motion-ease-enter),
59
+ opacity var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
60
+ }
61
+
62
+ [data-scope='sortable'][data-part='item'][data-dragging] {
63
+ /* 拖起来的那一项浮在上面。层级由连接层写内联,这里只管观感。
64
+ 海拔取 lifted:被指针拎起、正跟着手走的东西比贴在页面上的 raised 高一档,又不到锚定浮层那一层。
65
+ 拖起的面是作者自己的内容,皮肤不给它强加描边——描边归作者的项目布局 */
66
+ box-shadow: var(--xh-sortable-item-shadow-dragging, var(--xh-elevation-lifted));
67
+ opacity: var(--xh-sortable-item-opacity-dragging, 0.9);
68
+ }
69
+
70
+ /* 把手是只有字形的离散动作钮(§9.1 定尺把手):盒几何、悬停 / 按下与 0.97 按压、粗指针热区、焦点环、
71
+ 禁用面全由 Action Control 的 icon ghost 档给(连接层投 xs:24px 正方盒,与此前 --xh-control-action-size
72
+ 同尺寸),组件只映射既有使用者槽。静息透明,白底承载 hover 100 → pressed 200(此前悬停落 200 是错档)。
73
+ 手型不是 pointer:抓起来拖的东西给 grab,按住拖动中给 grabbing;家族的手型槽逐态映射 */
74
+ [data-scope='sortable'][data-part='item-drag-trigger'] {
75
+ --xh-action-visual-size: var(--xh-sortable-drag-size, var(--xh-_action-profile-visual-size));
76
+ --xh-action-min-inline-size: var(--xh-sortable-drag-size, var(--xh-_action-profile-visual-size));
77
+ --xh-action-radius: var(--xh-sortable-drag-radius, var(--xh-shape-control));
78
+ --xh-action-bg-hover: var(--xh-sortable-drag-bg-hover, var(--xh-_action-variant-bg-hover));
79
+ --xh-action-bg-pressed: var(--xh-sortable-drag-bg-active, var(--xh-_action-variant-bg-pressed));
80
+ --xh-action-fg-rest: var(--xh-sortable-drag-fg, var(--xh-fg-muted));
81
+ --xh-action-fg-hover: var(--xh-sortable-drag-fg-hover, var(--xh-fg-default));
82
+ --xh-action-fg-pressed: var(--xh-sortable-drag-fg-hover, var(--xh-fg-default));
83
+ --xh-action-fg-disabled: var(--xh-sortable-drag-fg-disabled, var(--xh-fg-disabled));
84
+ --xh-action-border-rest: transparent;
85
+ --xh-action-highlight-rest: transparent;
86
+ --xh-action-shadow-rest: none;
87
+ --xh-action-shadow-hover: none;
88
+ --xh-action-shadow-pressed: none;
89
+ --xh-action-cursor-rest: grab;
90
+ --xh-action-cursor-hover: grab;
91
+ --xh-action-cursor-focus-visible: grab;
92
+ --xh-action-cursor-pressed: grabbing;
93
+ /* 作者塞进把手的图标按档取字形尺(xs → 16px);皮肤画的兜底抓手是两条描边线,不走这把尺 */
94
+ --xh-icon-size: var(--xh-sortable-drag-icon-size, var(--xh-_action-profile-glyph-size));
95
+
96
+ border-width: 0;
97
+ }
98
+
99
+ /* 抓手是两条竖线:盒子取边框盒,两侧描边就是那两条线,宽高与线宽都走令牌。
100
+ :empty 守卫让作者塞进来的图标整个顶掉它,而不是两份叠在一起。
101
+ 不画这条的话按钮里空无一物,只剩一块摸不着边的透明命中区 */
102
+ [data-scope='sortable'][data-part='item-drag-trigger']:empty::after {
103
+ content: '';
104
+ display: block;
105
+ inline-size: var(--xh-sortable-drag-grip-w, var(--xh-space-1));
106
+ block-size: var(--xh-sortable-drag-grip-h, var(--xh-space-3));
107
+ border-inline: var(--xh-stroke-thin) solid currentColor;
108
+ }
109
+
110
+ /* 手指落点:家族 icon 档用 ::after 把命中区扩到 44×44;没塞图标的把手 ::after 已被抓手占着,
111
+ 抓手那条只写了宽高与描边,家族的定位、44px 下限与 -50% 平移会从同一个 ::after 漏进来,
112
+ 两条竖线被撑成 44px 高、跨出把手——这里逐项钉回,热区改由空着的 ::before 扩;
113
+ 作者塞了图标时 :empty 不成立,家族的 ::after 照常落下 */
114
+ @media (pointer: coarse) {
115
+ [data-scope='sortable'][data-part='item-drag-trigger']:empty::after {
116
+ position: static;
117
+ min-inline-size: 0;
118
+ min-block-size: 0;
119
+ translate: none;
120
+ }
121
+
122
+ [data-scope='sortable'][data-part='item-drag-trigger']:empty::before {
123
+ content: '';
124
+ position: absolute;
125
+ inset-block-start: 50%;
126
+ inset-inline-start: 50%;
127
+ min-inline-size: 44px;
128
+ min-block-size: 44px;
129
+ inline-size: 100%;
130
+ block-size: 100%;
131
+ translate: -50% -50%;
132
+ }
133
+
134
+ /* 与家族同一笔:起点是逻辑属性、平移是物理通道,rtl 下行内分量掉头,热区才落回把手中心 */
135
+ [data-scope='sortable'][data-part='item-drag-trigger']:empty:dir(rtl)::before {
136
+ translate: 50% -50%;
137
+ }
138
+ }
139
+
140
+ /* 拖动中(指针按住或键盘拾起):光标换成"抓着"的样子,各态的手型槽一并换掉——
141
+ 家族的悬停 / 按下块比这条特指度高,直接写 cursor 会被盖回去 */
142
+ [data-scope='sortable'][data-part='item-drag-trigger'][data-dragging] {
143
+ --xh-action-cursor-rest: grabbing;
144
+ --xh-action-cursor-hover: grabbing;
145
+ --xh-action-cursor-focus-visible: grabbing;
146
+ --xh-action-cursor-pressed: grabbing;
147
+ }
148
+
149
+ /* 落点线:容器的绝对定位子节点,起点与长度由连接层每帧写内联,这里只给粗细与颜色。
150
+ 容器要自建定位上下文,线才落在它自己的坐标系里 */
151
+ [data-scope='sortable'][data-part='root']:has([data-part='drop-indicator']) {
152
+ position: relative;
153
+ }
154
+
155
+ /* 不写层号:它是定位子节点,写在末项之后就画在各项之上,组件内不另立层序 */
156
+ [data-scope='sortable'][data-part='drop-indicator'] {
157
+ position: absolute;
158
+ display: block;
159
+ /* 线不接指针:它盖在项上面,接了就挡住落点那一格 */
160
+ pointer-events: none;
161
+ border-radius: var(--xh-sortable-drop-indicator-radius, var(--xh-shape-pill));
162
+ background: var(--xh-sortable-drop-indicator-bg, var(--xh-bg-brand));
163
+ /* 落点一格一格地跳,线跟着滑过去 */
164
+ transition: transform var(--xh-motion-duration-micro) var(--xh-motion-ease-continuous);
165
+ }
166
+
167
+ /* 竖排时是一条横线铺满容器,横排与换行网格时是一条竖线 */
168
+ [data-scope='sortable'][data-part='drop-indicator'][data-orientation='vertical'] {
169
+ inline-size: 100%;
170
+ block-size: var(--xh-sortable-drop-indicator-size, var(--xh-stroke-thick));
171
+ }
172
+
173
+ [data-scope='sortable'][data-part='drop-indicator'][data-orientation='horizontal'],
174
+ [data-scope='sortable'][data-part='drop-indicator'][data-orientation='both'] {
175
+ inline-size: var(--xh-sortable-drop-indicator-size, var(--xh-stroke-thick));
176
+ block-size: 100%;
177
+ }
178
+
179
+ /* 活动区域只说给读屏听,不占版面。声明与 shared/visually-hidden.ts 逐条同值。 */
180
+ [data-scope='sortable'][data-part='live-region'] {
181
+ position: absolute;
182
+ inline-size: 1px;
183
+ block-size: 1px;
184
+ padding: 0;
185
+ margin: -1px;
186
+ overflow: hidden;
187
+ clip-path: inset(50%);
188
+ white-space: nowrap;
189
+ border: 0;
190
+ }
191
+ /* 给了 display 的角色节点必须补这条:display 声明会盖掉 UA 的 [hidden]{display:none},
192
+ 不补的话作者给节点加 hidden 后它照样显示 */
193
+ [data-scope='sortable'][data-part='root'][hidden],
194
+ [data-scope='sortable'][data-part='item-drag-trigger'][hidden],
195
+ [data-scope='sortable'][data-part='drop-indicator'][hidden] {
196
+ display: none;
197
+ }
198
+ }