@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
@@ -1,3 +1,12 @@
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
+ /* 定义 radio group 样式。 */
7
+
8
+ @import '../family/action-control.css';
9
+
1
10
  @layer xihan.components {
2
11
  /* 皮肤在场的标记:开发模式下按 scope 探一次,取不到就说明这份皮肤没被引入。 */
3
12
  [data-scope='radio-group'] {
@@ -11,17 +20,27 @@
11
20
  --xh-_radio-group-indicator: var(--xh-control-indicator-md);
12
21
  /* 选中色随语气走。没写 data-tone 时 --xh-_tone 没被声明,退回原来的品牌色 */
13
22
  --xh-_radio-group-accent: var(--xh-_tone, var(--xh-bg-brand));
14
-
23
+ /* 条目是圆圈的承载面:行自己坐画布走 100 → 200,行面上的圆圈按淡底容器阶梯再上一档(200 → 300)。
24
+ 两支私有槽是这一档的唯一来源——宿主拿它们给 --xh-action-host-bg-* 赋值(桥接槽只写不读),
25
+ 圆圈按下读同一支私有槽,即与宿主的 host 槽同源 */
26
+ --xh-_radio-group-host-bg-hover: var(--xh-bg-subtle-hover);
27
+ --xh-_radio-group-host-bg-pressed: var(--xh-bg-subtle-active);
28
+
29
+ /* 标题与条目都是根的直接子节点,一支 gap 同时管「标题到集合」与「条目之间」:
30
+ 集合标题与集合隔 --xh-space-2(§6.4),同组条目之间也是同一档紧密关系 */
15
31
  display: flex;
16
- gap: var(--xh-radio-group-gap, var(--xh-stack-gap-md));
32
+ gap: var(--xh-radio-group-gap, var(--xh-space-2));
17
33
  }
18
34
 
19
35
  [data-scope='radio-group'][data-part='root'][data-orientation='vertical'] {
20
36
  flex-direction: column;
21
37
  }
22
38
 
39
+ /* 横排一行摆不下时条目落到下一行。不换行的话它们只会被压窄,
40
+ 文字逐字折行、每个条目变成一根竖条 */
23
41
  [data-scope='radio-group'][data-part='root'][data-orientation='horizontal'] {
24
42
  flex-direction: row;
43
+ flex-wrap: wrap;
25
44
  align-items: center;
26
45
  }
27
46
 
@@ -38,37 +57,65 @@
38
57
  --xh-_radio-group-indicator: var(--xh-control-indicator-lg);
39
58
  }
40
59
 
60
+ /* 集合标题(§6.4):14 / 500 / fg-muted,字号不随 size 档——随档的是条目文字 */
41
61
  [data-scope='radio-group'][data-part='label'] {
42
62
  color: var(--xh-radio-group-label-fg, var(--xh-fg-muted));
43
- font-size: var(--xh-radio-group-label-font-size, var(--xh-_radio-group-font-size));
63
+ font-size: var(--xh-radio-group-label-font-size, var(--xh-text-label-size));
44
64
  font-weight: var(--xh-radio-group-label-font-weight, var(--xh-text-label-weight));
45
65
  line-height: var(--xh-leading-none);
46
66
  }
47
67
 
48
- /* position 是给条目内那份视觉隐藏的原生输入做定位基准,别去掉 */
49
- [data-scope='radio-group'][data-part='item'] {
50
- position: relative;
51
- display: inline-flex;
52
- align-items: center;
53
- gap: var(--xh-radio-group-item-gap, var(--xh-_radio-group-item-gap));
54
- border-radius: var(--xh-radio-group-item-radius, var(--xh-shape-control));
55
- color: var(--xh-radio-group-item-fg, var(--xh-fg-default));
56
- font-size: var(--xh-radio-group-item-font-size, var(--xh-_radio-group-font-size));
57
- line-height: var(--xh-leading-none);
58
- cursor: pointer;
59
- }
60
-
61
- [data-scope='radio-group'][data-part='item']:focus-visible {
62
- outline: var(--xh-ring-width) solid var(--xh-ring-focus);
63
- outline-offset: var(--xh-ring-offset);
68
+ /* label 自己不带状态属性,整组禁用只写在 root 上,禁用标签色统一 --xh-fg-subtle(§6.4) */
69
+ [data-scope='radio-group'][data-part='root'][data-disabled] [data-scope='radio-group'][data-part='label'] {
70
+ color: var(--xh-radio-group-label-fg-disabled, var(--xh-fg-subtle));
64
71
  }
65
72
 
66
- /* 禁用走 aria-disabled 而非原生 disabled,条目仍可聚焦,样式由 data-disabled 表达 */
67
- [data-scope='radio-group'][data-part='item'][data-disabled] {
68
- color: var(--xh-radio-group-item-fg-disabled, var(--xh-fg-disabled));
69
- cursor: not-allowed;
70
- }
73
+ /* 条目是「圆圈 + 文案」的整行命中区(§9.2):接 Action Control row 档、ghost 形态,悬停 / 按下 / 禁用面、手型、
74
+ 过渡与焦点环由家族给,按下只换面不缩放(row 档专属按压块 scale: none);这里只把使用者槽映射到桥接槽。
75
+ 行自己坐画布走 100 → 200(--xh-action-bg-* 钉住自己的面),行面上的圆圈按淡底容器阶梯再上一档
76
+ (--xh-action-host-bg-* 向内声明自己是圆圈的承载面,host 槽只对后代生效——自己的面已由 --xh-action-bg-* 钉住;
77
+ 也因此 root 带 data-tone 时 --xh-_tone-subtle 不会把整行染色)。
78
+ xs 的 24px 是命中地板,圆圈 12 / 16 / 20px 居中其间;字号与间距按组档位从 root 的私有槽映射。
79
+ 条目禁用走 aria-disabled 而非原生 disabled,条目仍可聚焦,家族的禁用块读 data-disabled。
80
+ position 是给条目内那份视觉隐藏的原生输入做定位基准,别去掉 */
81
+ [data-scope='radio-group'][data-part='item'] {
82
+ --xh-action-gap: var(--xh-radio-group-item-gap, var(--xh-_radio-group-item-gap));
83
+ --xh-action-radius: var(--xh-radio-group-item-radius, var(--xh-shape-control));
84
+ --xh-action-font-size: var(--xh-radio-group-item-font-size, var(--xh-_radio-group-font-size));
85
+ --xh-action-padding-inline: 0;
86
+ --xh-action-padding-block: 0;
87
+ --xh-action-fg-rest: var(--xh-radio-group-item-fg, var(--xh-fg-default));
88
+ --xh-action-fg-hover: var(--xh-radio-group-item-fg, var(--xh-fg-default));
89
+ --xh-action-fg-pressed: var(--xh-radio-group-item-fg, var(--xh-fg-default));
90
+ --xh-action-fg-focus-visible: var(--xh-radio-group-item-fg, var(--xh-fg-default));
91
+ --xh-action-fg-disabled: var(--xh-radio-group-item-fg-disabled, var(--xh-fg-disabled));
92
+ --xh-action-bg-hover: var(--xh-radio-group-item-bg-hover, var(--xh-bg-subtle));
93
+ --xh-action-bg-pressed: var(--xh-radio-group-item-bg-pressed, var(--xh-bg-subtle-hover));
94
+ --xh-action-host-bg-hover: var(--xh-_radio-group-host-bg-hover);
95
+ --xh-action-host-bg-pressed: var(--xh-_radio-group-host-bg-pressed);
71
96
 
97
+ position: relative;
98
+ /* row 档铺满容器(inline-size: 100%)会把竖排的命中区拉到整行空白处、横排 wrap 也塌:
99
+ 沿用「条目按内容收宽」的既有决定,写法同 checkbox-group 条目 */
100
+ inline-size: auto;
101
+ /* ghost 的 1px 透明边位会把圆圈列相对组标题错开 1px:边归零,几何保持不变 */
102
+ border-width: 0;
103
+ }
104
+
105
+ /* 只读整组都改不动,但不是禁用:颜色照常,把整行的悬停 / 按下面与"可点"这个暗示一起收回来。
106
+ 家族的 :hover / 按压块不排 readonly,只能从槽上收回;排掉禁用项是因为这条选择器权重高过家族的禁用块,
107
+ 不排就会把 not-allowed 覆盖回 default */
108
+ [data-scope='radio-group'][data-part='item'][data-readonly]:not([data-disabled]) {
109
+ --xh-action-bg-hover: transparent;
110
+ --xh-action-bg-pressed: transparent;
111
+ --xh-action-cursor-rest: default;
112
+ --xh-action-cursor-hover: default;
113
+ --xh-action-cursor-pressed: default;
114
+ --xh-action-cursor-focus-visible: default;
115
+ }
116
+
117
+ /* 圆圈是字段家族的控制盒(§8.3 字段静息形态):透明底 + --xh-border-control 描边 + 无影。
118
+ 不缩放(§9.2 行级只换面),过渡只有底与描边;按下走 press 时间线、释放同走 micro */
72
119
  [data-scope='radio-group'][data-part='indicator'] {
73
120
  display: inline-flex;
74
121
  flex: none;
@@ -77,9 +124,30 @@
77
124
  inline-size: var(--xh-radio-group-indicator-size, var(--xh-_radio-group-indicator));
78
125
  block-size: var(--xh-radio-group-indicator-size, var(--xh-_radio-group-indicator));
79
126
  border: var(--xh-stroke-thin) solid var(--xh-radio-group-indicator-border, var(--xh-border-control));
80
- border-radius: var(--xh-radio-group-indicator-radius, var(--xh-shape-pill));
81
- background: var(--xh-radio-group-indicator-bg, var(--xh-bg-canvas));
82
- transition: border-color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
127
+ border-radius: var(--xh-radio-group-indicator-radius, var(--xh-shape-circle));
128
+ background: var(--xh-radio-group-indicator-bg, transparent);
129
+ transition:
130
+ border-color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter),
131
+ background-color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
132
+ }
133
+
134
+ /* 悬停整行时未选中的圆圈描边升一档(字段静息 → hover 的同一条阶梯,§8.3);
135
+ 选中圈与校验失败的圈保住各自的描边——圈内的点 / 警示色本身已经在表达状态 */
136
+ [data-scope='radio-group'][data-part='item']:not([data-disabled]):not([data-readonly]):hover
137
+ > [data-scope='radio-group'][data-part='indicator']:not([data-state='checked']):not([data-invalid]) {
138
+ border-color: var(--xh-radio-group-indicator-border-hover, var(--xh-border-control-hover));
139
+ }
140
+
141
+ /* 按下去要有回应。圆圈不是激活宿主:宿主是投影了 row 档的整行条目,圆圈在宿主的
142
+ :hover / :is(:active, [data-pressed]) 下读宿主 host 槽的同一来源换面、不缩放(§9.2)——行按下是 200,
143
+ 坐在上面的圆圈到 300(行以 --xh-action-host-bg-pressed 声明自己是圆圈的承载面,桥接槽只写不读,
144
+ 圆圈读的是喂给它的那支私有槽),描边不动、几何不动;
145
+ 与家族按压块同一时间线(按下 --xh-motion-duration-press,释放同走 micro) */
146
+ [data-scope='radio-group'][data-part='item']:not([data-disabled]):not([data-readonly]):is(:active, [data-pressed])
147
+ > [data-scope='radio-group'][data-part='indicator'] {
148
+ background: var(--xh-radio-group-indicator-bg-pressed, var(--xh-_radio-group-host-bg-pressed));
149
+ transition-duration: var(--xh-motion-duration-press);
150
+ transition-timing-function: var(--xh-motion-ease-press);
83
151
  }
84
152
 
85
153
  [data-scope='radio-group'][data-part='indicator'][data-state='checked'] {
@@ -93,26 +161,53 @@
93
161
  }
94
162
 
95
163
  /* 圆点用 scale 收放,选中态切换不引起布局抖动。
96
- 直径取指示器的一半:换尺寸档时圈与点一起走,比例不散 */
164
+ 直径取指示器的一半:换尺寸档时圈与点一起走,比例不散。
165
+ 出现时略微涨过 1 再收回,过冲量落在圈内不碰描边 */
97
166
  [data-scope='radio-group'][data-part='indicator']::before {
98
167
  content: '';
99
168
  inline-size: calc(var(--xh-radio-group-indicator-size, var(--xh-_radio-group-indicator)) / 2);
100
169
  block-size: calc(var(--xh-radio-group-indicator-size, var(--xh-_radio-group-indicator)) / 2);
101
- border-radius: var(--xh-shape-pill);
170
+ border-radius: var(--xh-shape-circle);
102
171
  background: var(--xh-radio-group-indicator-dot, var(--xh-_radio-group-accent));
103
172
  scale: 0;
104
- transition: scale var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
173
+ transition: scale var(--xh-motion-duration-micro) var(--xh-motion-ease-settle);
105
174
  }
106
175
 
107
176
  [data-scope='radio-group'][data-part='indicator'][data-state='checked']::before {
108
177
  scale: 1;
109
178
  }
110
179
 
180
+ /* 选中的圆点是这颗圈的「填色」:按下随行换到语气的 active 档(与 checkbox-group 勾中方框按下同档),
181
+ 选中圈的描边保住不换 */
182
+ [data-scope='radio-group'][data-part='item']:not([data-disabled]):not([data-readonly]):is(:active, [data-pressed])
183
+ > [data-scope='radio-group'][data-part='indicator'][data-state='checked']::before {
184
+ background: var(--xh-radio-group-indicator-dot-pressed, var(--xh-_tone-active, var(--xh-bg-brand-active)));
185
+ }
186
+
187
+ /* 禁用(§7.2):--xh-border-default 描边 + --xh-bg-subtle 底 + --xh-fg-disabled 圆点,不靠 opacity;
188
+ 与 checkbox-group 方框、独立 checkbox 逐档一致。条目自己带 data-disabled(组禁用也逐条下放),圆圈读它 */
189
+ [data-scope='radio-group'][data-part='item'][data-disabled] > [data-scope='radio-group'][data-part='indicator'] {
190
+ border-color: var(--xh-radio-group-indicator-border-disabled, var(--xh-border-default));
191
+ background: var(--xh-radio-group-indicator-bg-disabled, var(--xh-bg-subtle));
192
+ }
193
+
194
+ [data-scope='radio-group'][data-part='item'][data-disabled] > [data-scope='radio-group'][data-part='indicator']::before {
195
+ background: var(--xh-radio-group-indicator-dot-disabled, var(--xh-fg-disabled));
196
+ }
197
+
111
198
  [data-scope='radio-group'][data-part='item-text'] {
112
199
  color: inherit;
113
200
  user-select: none;
114
201
  }
115
202
 
203
+ /* 家族 row 档在粗指针下用 ::after 把热区扩到 44px 高:条目按 24px 行 + 8px 间距密排,热区会压进相邻条目、
204
+ 指针落错行,A 档 24px 由 xs 地板保住(同 checkbox-group 条目) */
205
+ @media (pointer: coarse) {
206
+ [data-scope='radio-group'][data-part='item']::after {
207
+ min-block-size: 0;
208
+ }
209
+ }
210
+
116
211
  /* 声明过任何 display 就盖掉了 UA 的 [hidden]{display:none},收起态得自己还回去。
117
212
  写在文件末尾:兜底必须排在该部件所有 display 声明之后 */
118
213
  [data-scope='radio-group'][data-part='root'][hidden],
@@ -120,4 +215,26 @@
120
215
  [data-scope='radio-group'][data-part='indicator'][hidden] {
121
216
  display: none;
122
217
  }
218
+
219
+ @media (forced-colors: active) {
220
+ /* 按下:常态靠圆圈换底表出,这一档里底色被统一换成 Canvas;按住那一下把圈的描边换成系统高亮色 */
221
+ [data-scope='radio-group'][data-part='item']:not([data-disabled]):not([data-readonly]):is(:active, [data-pressed])
222
+ > [data-scope='radio-group'][data-part='indicator'] {
223
+ border-color: Highlight;
224
+ }
225
+
226
+ /* 禁用的圆点跟随系统的置灰前景 */
227
+ [data-scope='radio-group'][data-part='item'][data-disabled]
228
+ > [data-scope='radio-group'][data-part='indicator'][data-state='checked']::before {
229
+ background: GrayText;
230
+ }
231
+ }
232
+
233
+ @media print {
234
+ /* 打印默认丢底色:选中那颗圆点是一块底,落到纸上整个消失,选中与没选中的圈一模一样。
235
+ 圆点改画成一圈实边——边框印得出来,直径与圆心都不动(描边吃进盒里)。 */
236
+ [data-scope='radio-group'][data-part='indicator'][data-state='checked']::before {
237
+ border: var(--xh-stroke-thick) solid currentColor;
238
+ }
239
+ }
123
240
  }
package/css/rating.css CHANGED
@@ -1,3 +1,12 @@
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
+ /* 定义 rating 样式。 */
7
+
8
+ @import '../family/action-control.css';
9
+
1
10
  @layer xihan.components {
2
11
  /* 皮肤在场的标记:开发模式下按 scope 探一次,取不到就说明这份皮肤没被引入。 */
3
12
  [data-scope='rating'] {
@@ -7,47 +16,45 @@
7
16
  /* position 是给表单影子做定位基准的:那份输入被连接层内联写成绝对定位,
8
17
  根上不留基准它就会贴到更外层的定位祖先上,原生校验提示跟着飘到页面别处。 */
9
18
  [data-scope='rating'][data-part='root'] {
10
- /* 尺寸档只换这三个私有槽,标题与星星都从 root 继承它们,换档不必给每个部件各写一条选择器 */
19
+ /* 尺寸档只换这三个私有槽,星星与分值都从 root 继承它们,换档不必给每个部件各写一条选择器。
20
+ 星是控件内的字形,按档取 --xh-glyph-size-sm / md / lg(§6.5);标签字号不随档 */
11
21
  --xh-_rating-item-gap: var(--xh-control-gap-md);
12
- --xh-_rating-item-size: var(--xh-font-size-2xl);
22
+ --xh-_rating-item-size: var(--xh-glyph-size-md);
23
+ --xh-_rating-item-box-size: var(--xh-control-action-size);
13
24
  --xh-_rating-font-size: var(--xh-control-font-md);
25
+ --xh-icon-size: var(--xh-rating-item-font-size, var(--xh-_rating-item-size));
14
26
  /* 点亮色随语气走。没写 data-tone 时 --xh-_tone 没被声明,退回原来的警示色 */
15
27
  --xh-_rating-accent: var(--xh-_tone, var(--xh-color-warning-500));
16
28
 
17
29
  position: relative;
18
30
  display: inline-flex;
19
31
  flex-direction: column;
20
- gap: var(--xh-rating-gap, var(--xh-stack-gap-md));
32
+ /* 字段标签贴控件(§6.4 复合单字段) */
33
+ gap: var(--xh-rating-gap, var(--xh-space-1));
21
34
  }
22
35
 
23
36
  /* —— 尺寸 —— */
24
37
  [data-scope='rating'][data-part='root'][data-size='sm'] {
25
38
  --xh-_rating-item-gap: var(--xh-control-gap-sm);
26
- --xh-_rating-item-size: var(--xh-font-size-xl);
39
+ --xh-_rating-item-size: var(--xh-glyph-size-sm);
27
40
  --xh-_rating-font-size: var(--xh-control-font-sm);
28
41
  }
29
42
 
30
43
  [data-scope='rating'][data-part='root'][data-size='lg'] {
31
44
  --xh-_rating-item-gap: var(--xh-control-gap-lg);
32
- --xh-_rating-item-size: var(--xh-font-size-3xl);
45
+ --xh-_rating-item-size: var(--xh-glyph-size-lg);
33
46
  --xh-_rating-font-size: var(--xh-control-font-lg);
34
47
  }
35
48
 
36
- /* 整体压暗只做一次,落在最外层:星星逐颗再叠 opacity 会越叠越黑 */
37
- [data-scope='rating'][data-part='root'][data-disabled] {
38
- cursor: not-allowed;
39
- opacity: var(--xh-state-disabled-opacity);
40
- }
41
-
49
+ /* 字段标签:14 / 500 / fg-default(§6.4),禁用换到 --xh-fg-subtle;文字部件不叠 opacity */
42
50
  [data-scope='rating'][data-part='label'] {
43
- color: var(--xh-rating-label-fg, var(--xh-fg-muted));
44
- font-size: var(--xh-rating-label-font-size, var(--xh-_rating-font-size));
51
+ color: var(--xh-rating-label-fg, var(--xh-fg-default));
52
+ font-size: var(--xh-rating-label-font-size, var(--xh-text-label-size));
45
53
  font-weight: var(--xh-rating-label-font-weight, var(--xh-text-label-weight));
46
- line-height: var(--xh-leading-none);
47
54
  }
48
55
 
49
56
  [data-scope='rating'][data-part='label'][data-disabled] {
50
- color: var(--xh-fg-subtle);
57
+ color: var(--xh-rating-label-fg-disabled, var(--xh-fg-subtle));
51
58
  }
52
59
 
53
60
  [data-scope='rating'][data-part='control'] {
@@ -56,46 +63,125 @@
56
63
  gap: var(--xh-rating-item-gap, var(--xh-_rating-item-gap));
57
64
  }
58
65
 
66
+ /* 禁用:星带是纯图形,整体压暗只做一次、落在星带上(星星逐颗再叠 opacity 会越叠越黑);
67
+ 标签与分值是文字,各自换到禁用前景,不落在这层 opacity 之下 */
68
+ [data-scope='rating'][data-part='control'][data-disabled] {
69
+ opacity: var(--xh-state-disabled-opacity);
70
+ }
71
+
72
+ /* 分值文本在场时根改成两列栅格:星星带与分值并排,标题仍独占一整行。
73
+ 没写这个部件的评分不命中这条,还是原来的竖排 */
74
+ [data-scope='rating'][data-part='root']:has(> [data-part='value-text']) {
75
+ display: inline-grid;
76
+ grid-template-columns: auto auto;
77
+ align-items: center;
78
+ justify-content: start;
79
+ }
80
+
81
+ [data-scope='rating'][data-part='root']:has(> [data-part='value-text']) > [data-part='label'] {
82
+ grid-column: 1 / -1;
83
+ }
84
+
85
+ /* 数字等宽:指针从一颗星划到另一颗时分值在原地变,不带着星星左右挪 */
86
+ [data-scope='rating'][data-part='value-text'] {
87
+ color: var(--xh-rating-value-text-fg, var(--xh-fg-muted));
88
+ font-size: var(--xh-rating-value-text-font-size, var(--xh-_rating-font-size));
89
+ font-variant-numeric: tabular-nums;
90
+ line-height: var(--xh-leading-none);
91
+ }
92
+
93
+ [data-scope='rating'][data-part='value-text'][data-disabled] {
94
+ color: var(--xh-rating-value-text-fg-disabled, var(--xh-fg-subtle));
95
+ }
96
+
59
97
  /* 容器只是焦点域的兜底落点(焦点从带外进来先落在它身上,随即被转投给某颗星),
60
98
  落焦不画环:高亮永远长在星星上,容器再画一圈只是重影。 */
61
99
  [data-scope='rating'][data-part='control']:focus-visible {
62
100
  outline: none;
63
101
  }
64
102
 
65
- [data-scope='rating'][data-part='control'][data-readonly] {
103
+ [data-scope='rating'][data-part='control'][data-readonly]:not([data-disabled]) {
66
104
  cursor: default;
67
105
  }
68
106
 
69
- [data-scope='rating'][data-part='control'][data-disabled] {
70
- cursor: not-allowed;
107
+ /* 条目固定成一枚方形图标盒:连接层用 clientWidth 判定指针落在左半还是右半,
108
+ 固定盒能让半档分界与图形中心严格重合。
109
+ 星接 Action Control icon 档(§9.1 定尺的星):盒型、按下 / 禁用面、0.97 缩放与换底、焦点环与过渡由家族配方给,
110
+ 这里只把使用者槽映射到桥接槽。静息透明(ghost 形态);悬停不换底——悬停预览由点亮的星形(data-highlighted)
111
+ 表达,再给盒换面是重复的通道;按下换到 200 档中性面(白底承载)。前景五态映射同一个值:点亮时五态一起换到
112
+ 点亮色,家族的悬停 / 按下块才不会把点亮色换回静息色;禁用不换前景,星带整体的压暗落在 control 的 opacity 上 */
113
+ [data-scope='rating'][data-part='item'] {
114
+ --xh-_rating-item-bg-pressed: var(--xh-rating-item-bg-pressed, var(--xh-bg-subtle-hover));
115
+
116
+ --xh-action-visual-size: var(--xh-_rating-item-box-size);
117
+ --xh-action-min-inline-size: var(--xh-_rating-item-box-size);
118
+ --xh-action-radius: var(--xh-rating-item-radius, var(--xh-shape-control));
119
+ --xh-action-font-size: var(--xh-rating-item-font-size, var(--xh-_rating-item-size));
120
+ --xh-action-bg-hover: transparent;
121
+ --xh-action-bg-pressed: var(--xh-_rating-item-bg-pressed);
122
+ --xh-action-fg-rest: var(--xh-rating-item-fg, var(--xh-fg-subtle));
123
+ --xh-action-fg-hover: var(--xh-rating-item-fg, var(--xh-fg-subtle));
124
+ --xh-action-fg-pressed: var(--xh-rating-item-fg, var(--xh-fg-subtle));
125
+ --xh-action-fg-focus-visible: var(--xh-rating-item-fg, var(--xh-fg-subtle));
126
+ --xh-action-fg-disabled: var(--xh-rating-item-fg, var(--xh-fg-subtle));
127
+ --xh-action-border-rest: transparent;
128
+ --xh-action-border-hover: transparent;
129
+ --xh-action-border-pressed: transparent;
130
+ --xh-action-border-focus-visible: transparent;
131
+ --xh-action-border-disabled: transparent;
71
132
  }
72
133
 
73
- /* 星星字符由作者写,皮肤只负责上色与盒子。
74
- inline-block 是判定"点在左半边还是右半边"的前提:连接层拿 offsetX / clientWidth 算比例,
75
- 行内盒会把字符两侧的换行空白算进宽度,半颗的分界线就偏了。 */
76
- [data-scope='rating'][data-part='item'] {
77
- display: inline-block;
78
- border-radius: var(--xh-rating-item-radius, var(--xh-shape-control));
79
- color: var(--xh-rating-item-fg, var(--xh-fg-subtle));
80
- font-size: var(--xh-rating-item-font-size, var(--xh-_rating-item-size));
81
- line-height: var(--xh-leading-none);
82
- cursor: pointer;
83
- /* 从一颗星划到另一颗会把中间的字符连着选中,星带看上去像被刷蓝了 */
84
- user-select: none;
85
- transition: color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
134
+ /* 家族 icon 档在粗指针下用 ::after 扩热区到 44px:五颗星密排,44px 热区会压住相邻的星,
135
+ 连接层又按 currentTarget 的宽度判半颗,热区串到邻星上半档就判错;星本身仍是 24px 盒,热区不外扩。
136
+ 空条目的 ::after 是点亮那层星形,下限归零才不会被撑成 44px */
137
+ @media (pointer: coarse) {
138
+ [data-scope='rating'][data-part='item']::after {
139
+ min-inline-size: 0;
140
+ min-block-size: 0;
141
+ }
142
+ }
143
+
144
+ /* 空条目使用两层同源星形:底层保持中性轮廓,上层按完整或半档裁出强调色。 */
145
+ [data-scope='rating'][data-part='item']:empty::before,
146
+ [data-scope='rating'][data-part='item']:empty::after {
147
+ content: '';
148
+ position: absolute;
149
+ inset-block-start: 50%;
150
+ inset-inline-start: 50%;
151
+ inline-size: var(--xh-icon-size, var(--xh-glyph-size-text));
152
+ block-size: var(--xh-icon-size, var(--xh-glyph-size-text));
153
+ translate: -50% -50%;
154
+ background-color: var(--xh-rating-item-fg, var(--xh-fg-subtle));
155
+ -webkit-mask: var(--xh-glyph-mark-star) center / contain no-repeat;
156
+ mask: var(--xh-glyph-mark-star) center / contain no-repeat;
157
+ pointer-events: none;
158
+ }
159
+
160
+ [data-scope='rating'][data-part='item']:empty::after {
161
+ background-color: var(--xh-rating-item-fg-highlighted, var(--xh-_rating-accent));
162
+ clip-path: inset(0 100% 0 0);
163
+ transition: clip-path var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
86
164
  }
87
165
 
88
166
  /* 点亮的判据是 data-highlighted,不是 aria-checked:
89
167
  aria-checked 只认落定的值,而悬停预览要求手划过去当场就亮。
90
168
  两者共用同一档颜色,预览与选中因此看不出差别——这正是"预览"该有的样子。 */
91
169
  [data-scope='rating'][data-part='item'][data-highlighted] {
92
- color: var(--xh-rating-item-fg-highlighted, var(--xh-_rating-accent));
170
+ --xh-action-fg-rest: var(--xh-rating-item-fg-highlighted, var(--xh-_rating-accent));
171
+ --xh-action-fg-hover: var(--xh-rating-item-fg-highlighted, var(--xh-_rating-accent));
172
+ --xh-action-fg-pressed: var(--xh-rating-item-fg-highlighted, var(--xh-_rating-accent));
173
+ --xh-action-fg-focus-visible: var(--xh-rating-item-fg-highlighted, var(--xh-_rating-accent));
174
+ --xh-action-fg-disabled: var(--xh-rating-item-fg-highlighted, var(--xh-_rating-accent));
175
+ }
176
+
177
+ [data-scope='rating'][data-part='item'][data-highlighted]:empty::after {
178
+ clip-path: inset(0);
93
179
  }
94
180
 
95
181
  /* 半颗:一道 50% 硬边渐变裁到字形上,左半亮右半灰。
96
182
  必须排在 data-highlighted 之后:半颗那颗星同时带着 data-highlighted,两条选择器特异度相同,
97
183
  顺序一反 color 就把 transparent 覆盖回实色,渐变整个看不见。 */
98
- [data-scope='rating'][data-part='item'][data-half] {
184
+ [data-scope='rating'][data-part='item'][data-half]:not(:empty) {
99
185
  background-image: linear-gradient(
100
186
  to right,
101
187
  var(--xh-rating-item-fg-highlighted, var(--xh-_rating-accent)) 50%,
@@ -108,7 +194,7 @@
108
194
 
109
195
  /* rtl 下"半颗"落在星的右半边(连接层按 1 - offsetX/width 判),亮的那半得跟着掉头。
110
196
  渐变方向没有逻辑关键字可用,只能靠 :dir() 分流,不能指望逻辑属性自动换向。 */
111
- [data-scope='rating'][data-part='item'][data-half]:dir(rtl) {
197
+ [data-scope='rating'][data-part='item'][data-half]:not(:empty):dir(rtl) {
112
198
  background-image: linear-gradient(
113
199
  to left,
114
200
  var(--xh-rating-item-fg-highlighted, var(--xh-_rating-accent)) 50%,
@@ -116,19 +202,23 @@
116
202
  );
117
203
  }
118
204
 
119
- [data-scope='rating'][data-part='item']:focus-visible {
120
- outline: var(--xh-ring-width) solid var(--xh-ring-focus);
121
- outline-offset: var(--xh-ring-offset);
205
+ [data-scope='rating'][data-part='item'][data-half]:empty::after {
206
+ clip-path: inset(0 50% 0 0);
122
207
  }
123
208
 
124
- /* 只读仍进得了 Tab 序列、读屏也念得出,只是改不动:收掉手型,别暗示"点这儿能改" */
125
- [data-scope='rating'][data-part='item'][data-readonly] {
126
- cursor: default;
209
+ [data-scope='rating'][data-part='item'][data-half]:empty:dir(rtl)::after {
210
+ clip-path: inset(0 0 0 50%);
127
211
  }
128
212
 
129
- /* 集合条目一律 aria-disabled、没有原生 disabled,:disabled 永远命不中,只能认 data-disabled */
130
- [data-scope='rating'][data-part='item'][data-disabled] {
131
- cursor: not-allowed;
213
+ /* 只读仍进得了 Tab 序列、读屏也念得出,只是改不动:收掉手型,别暗示"点这儿能改";
214
+ 家族的按压块由这里的映射钉回静息面——按下不缩放、不换底 */
215
+ [data-scope='rating'][data-part='item'][data-readonly]:not([data-disabled]) {
216
+ --xh-_rating-item-bg-pressed: transparent;
217
+ --xh-action-scale-pressed: none;
218
+ --xh-action-cursor-rest: default;
219
+ --xh-action-cursor-hover: default;
220
+ --xh-action-cursor-pressed: default;
221
+ --xh-action-cursor-focus-visible: default;
132
222
  }
133
223
 
134
224
  /* 表单影子的视觉隐藏(绝对定位 + 1px + clip-path)连接层内联给全了,这里不重复也不覆盖,
@@ -143,4 +233,64 @@
143
233
  [data-scope='rating'][data-part='item'][hidden] {
144
234
  display: none;
145
235
  }
236
+
237
+ /*
238
+ * 高对比档:点亮与没点亮只差一档字色,这一档里两档一起被换成同一个系统前景色,
239
+ * 整条星带看上去全是亮的。改用系统自己的两个角色分开:亮的取高亮色、没亮的取不可用色。
240
+ * 半颗那道裁到字形上的渐变在这一档里被丢弃,留着透明填充整颗星会消失,
241
+ * 把填充还回去并按点亮那一档画。
242
+ */
243
+ @media (forced-colors: active) {
244
+ [data-scope='rating'][data-part='item'] {
245
+ color: GrayText;
246
+ }
247
+
248
+ [data-scope='rating'][data-part='item']:empty::before {
249
+ background-color: GrayText;
250
+ }
251
+
252
+ [data-scope='rating'][data-part='item']:empty::after {
253
+ background-color: Highlight;
254
+ }
255
+
256
+ [data-scope='rating'][data-part='item'][data-highlighted],
257
+ [data-scope='rating'][data-part='item'][data-half]:not(:empty) {
258
+ background-image: none;
259
+ -webkit-background-clip: border-box;
260
+ background-clip: border-box;
261
+ color: Highlight;
262
+ /* 亮档已经由字色表出,公共层那圈锚点虚线在这里只是重影 */
263
+ outline: none;
264
+ }
265
+
266
+ /* 家族在这一档把悬停 / 按下的盒填成 Highlight、字换 HighlightText:点亮的那层星形也是 Highlight,
267
+ 填了盒就把它吞掉。盒保持 Canvas 底,字色仍按点亮与否取 GrayText / Highlight;
268
+ 按住那一下给盒画一圈系统高亮环表出按下 */
269
+ [data-scope='rating'][data-part='item']:not([data-disabled]):not([data-loading]):hover,
270
+ [data-scope='rating'][data-part='item']:not([data-disabled]):not([data-loading]):is(:active, [data-pressed]) {
271
+ background-color: Canvas;
272
+ color: GrayText;
273
+ }
274
+
275
+ [data-scope='rating'][data-part='item'][data-highlighted]:not([data-disabled]):not([data-loading]):hover,
276
+ [data-scope='rating'][data-part='item'][data-highlighted]:not([data-disabled]):not([data-loading]):is(:active, [data-pressed]) {
277
+ color: Highlight;
278
+ }
279
+
280
+ [data-scope='rating'][data-part='item']:not([data-disabled]):not([data-readonly]):is(:active, [data-pressed]) {
281
+ outline: var(--xh-stroke-thick) solid Highlight;
282
+ outline-offset: calc(-1 * var(--xh-stroke-thick));
283
+ }
284
+ }
285
+
286
+ @media print {
287
+ /* 打印默认丢底图:半颗那道裁到字形上的渐变整个消失,星上只剩 color: transparent,
288
+ 那颗星在纸上什么都不剩。把填充还回去并按点亮那一档画整颗。 */
289
+ [data-scope='rating'][data-part='item'][data-half]:not(:empty) {
290
+ background-image: none;
291
+ -webkit-background-clip: border-box;
292
+ background-clip: border-box;
293
+ color: var(--xh-rating-item-fg-highlighted, var(--xh-_rating-accent));
294
+ }
295
+ }
146
296
  }