@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,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
+ /* 定义 image cropper 样式。 */
7
+
1
8
  @layer xihan.components {
2
9
  /* 皮肤在场的标记:开发模式下按 scope 探一次,取不到就说明这份皮肤没被引入。 */
3
10
  [data-scope='image-cropper'] {
@@ -26,7 +33,7 @@
26
33
  position: relative;
27
34
  display: block;
28
35
  overflow: hidden;
29
- border-radius: var(--xh-image-cropper-radius, var(--xh-shape-surface));
36
+ border-radius: var(--xh-image-cropper-viewport-radius, var(--xh-shape-surface));
30
37
  background: var(--xh-image-cropper-bg, var(--xh-bg-muted));
31
38
  user-select: none;
32
39
  }
@@ -42,6 +49,8 @@
42
49
  /* 裁切框。遮罩用一圈超大的实心投影把框外压暗——不必再摆四块遮罩节点,
43
50
  圆形裁切时投影也自动跟着圆角走。描边按倍率缩回去,放大时仍是一根细线。 */
44
51
  [data-scope='image-cropper'][data-part='crop-area'] {
52
+ --xh-_image-cropper-corner-radius: var(--xh-shape-control);
53
+
45
54
  position: absolute;
46
55
  box-sizing: border-box;
47
56
  border-style: solid;
@@ -51,30 +60,22 @@
51
60
  cursor: move;
52
61
  }
53
62
 
54
- /* 圆形裁切的这一档只能写 50%:shape 那四档给的是固定长度,
55
- 裁切框的宽高由数据决定、两边通常不等长,固定长度画出来是胶囊不是椭圆。 */
63
+ /* 圆形裁切取 circle 档(50%):shape 的固定长度档画出来是胶囊不是椭圆,
64
+ 裁切框的宽高由数据决定、两边通常不等长,只有百分比才随框变成椭圆;圆角只能来自语义形状令牌(§6.3) */
56
65
  [data-scope='image-cropper'][data-part='crop-area'][data-shape='round'] {
57
- border-radius: 50%;
66
+ --xh-_image-cropper-corner-radius: var(--xh-shape-circle);
67
+
68
+ border-radius: var(--xh-image-cropper-crop-area-radius, var(--xh-shape-circle));
58
69
  }
59
70
 
60
71
  [data-scope='image-cropper'][data-part='crop-area'][data-dragging] {
61
72
  cursor: grabbing;
62
73
  }
63
74
 
64
- [data-scope='image-cropper'][data-part='crop-area'][data-readonly] {
75
+ [data-scope='image-cropper'][data-part='crop-area'][data-readonly]:not([data-disabled]) {
65
76
  cursor: default;
66
77
  }
67
78
 
68
- [data-scope='image-cropper'][data-part='crop-area'][data-disabled] {
69
- cursor: not-allowed;
70
- }
71
-
72
- /* 环往内收:裁切框长在视口里,视口那层 overflow: hidden 会把画在外沿的环裁掉 */
73
- [data-scope='image-cropper'][data-part='crop-area']:focus-visible {
74
- outline: var(--xh-ring-width) solid var(--xh-ring-focus);
75
- outline-offset: calc(-1 * var(--xh-ring-width));
76
- }
77
-
78
79
  /* 三分构图参考线:两条竖线、两条横线,落点写死在三分处,不占节点、不吃指针。
79
80
  停靠点逐条写全而不用 repeating-:重复单元从 0 起算会在框的两条边缘上各多画一根,
80
81
  那两根与裁切框的描边叠在一起,看着像描边加粗了一倍。
@@ -109,24 +110,33 @@
109
110
  background-image: none;
110
111
  }
111
112
 
112
- /* 把手钉在裁切框的角与边上。落点写物理 left / top:裁切框的坐标是图片像素,
113
- 连接层写进内联样式的也是物理 left / top,把手跟着走才不会与框错开一侧。
114
- translate 把方块的中心挪到点上;scale 按倍率缩回去,缩放时把手看着大小不变。 */
113
+ /* 把手的命中区仍以裁切框边界为中心,视觉短条与折角贴住边框并向框内绘制。 */
115
114
  [data-scope='image-cropper'][data-part='crop-handle'] {
115
+ --xh-_image-cropper-handle-color: var(--xh-image-cropper-handle-bg, var(--xh-bg-surface));
116
+
116
117
  position: absolute;
117
118
  display: block;
118
119
  box-sizing: border-box;
119
- border-style: solid;
120
- border-width: calc(var(--xh-stroke-thin) / var(--xh-_image-cropper-zoom));
121
- border-color: var(--xh-image-cropper-handle-border, var(--xh-border-strong));
122
- border-radius: var(--xh-image-cropper-handle-radius, var(--xh-shape-inset));
123
- background: var(--xh-image-cropper-handle-bg, var(--xh-bg-surface));
120
+ border: 0;
121
+ background: transparent;
124
122
  inline-size: var(--xh-_image-cropper-handle-size);
125
123
  block-size: var(--xh-_image-cropper-handle-size);
126
124
  translate: -50% -50%;
127
125
  scale: calc(1 / var(--xh-_image-cropper-zoom));
128
126
  }
129
127
 
128
+ [data-scope='image-cropper'][data-part='crop-handle']::after {
129
+ content: '';
130
+ position: absolute;
131
+ box-sizing: border-box;
132
+ border-radius: var(--xh-image-cropper-handle-radius, var(--xh-shape-pill));
133
+ background: var(--xh-_image-cropper-handle-color);
134
+ pointer-events: none;
135
+ transition:
136
+ background var(--xh-motion-duration-micro) var(--xh-motion-ease-enter),
137
+ border-color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
138
+ }
139
+
130
140
  /* 看得见的方块比手指小:另铺一层不可见的命中区,把它撑到最小可点尺寸。
131
141
  inset 取负值即向四面外扩,方块本身更大时这个值转正、命中区仍是方块那么大。 */
132
142
  [data-scope='image-cropper'][data-part='crop-handle']::before {
@@ -135,14 +145,19 @@
135
145
  content: '';
136
146
  }
137
147
 
138
- /* 与裁切框同一口径:把手也在视口的 overflow: hidden 里,环画在外沿会被裁掉 */
139
- [data-scope='image-cropper'][data-part='crop-handle']:focus-visible {
140
- outline: var(--xh-ring-width) solid var(--xh-ring-focus);
141
- outline-offset: calc(-1 * var(--xh-ring-width));
148
+ /* 把手静息是白面(bg-surface),悬停从同一语义面派生一档(§7.2 8 条),不切到品牌淡底;拖动中才是品牌实心 */
149
+ [data-scope='image-cropper'][data-part='crop-handle']:hover:not([data-disabled], [data-readonly]) {
150
+ --xh-_image-cropper-handle-color: var(--xh-image-cropper-handle-bg-hover, var(--xh-bg-subtle));
142
151
  }
143
152
 
144
153
  [data-scope='image-cropper'][data-part='crop-handle'][data-resizing] {
145
- background: var(--xh-image-cropper-handle-bg-resizing, var(--xh-bg-brand));
154
+ --xh-_image-cropper-handle-color: var(--xh-image-cropper-handle-bg-resizing, var(--xh-bg-brand));
155
+ }
156
+
157
+ [data-scope='image-cropper'][data-part='crop-handle']:focus-visible {
158
+ --xh-_image-cropper-handle-color: var(--xh-ring-focus);
159
+
160
+ outline: none;
146
161
  }
147
162
 
148
163
  /* —— 八个方位:落点 + 光标 —— */
@@ -202,6 +217,96 @@
202
217
  cursor: ew-resize;
203
218
  }
204
219
 
220
+ /* stylelint-disable property-disallowed-list -- 裁切框的八向位置使用图片物理坐标,内部标记必须朝对应物理方向内收 */
221
+ [data-scope='image-cropper'][data-part='crop-handle']:is([data-position='n'], [data-position='s'])::after {
222
+ left: 50%;
223
+ inline-size: var(--xh-image-cropper-handle-length, var(--xh-space-8));
224
+ block-size: var(--xh-image-cropper-handle-thickness, var(--xh-stroke-strong));
225
+ translate: -50% 0;
226
+ }
227
+
228
+ [data-scope='image-cropper'][data-part='crop-handle'][data-position='n']::after {
229
+ top: 50%;
230
+ }
231
+
232
+ [data-scope='image-cropper'][data-part='crop-handle'][data-position='s']::after {
233
+ bottom: 50%;
234
+ }
235
+
236
+ [data-scope='image-cropper'][data-part='crop-handle']:is([data-position='e'], [data-position='w'])::after {
237
+ top: 50%;
238
+ inline-size: var(--xh-image-cropper-handle-thickness, var(--xh-stroke-strong));
239
+ block-size: var(--xh-image-cropper-handle-length, var(--xh-space-8));
240
+ translate: 0 -50%;
241
+ }
242
+
243
+ [data-scope='image-cropper'][data-part='crop-handle'][data-position='e']::after {
244
+ right: 50%;
245
+ }
246
+
247
+ [data-scope='image-cropper'][data-part='crop-handle'][data-position='w']::after {
248
+ left: 50%;
249
+ }
250
+
251
+ [data-scope='image-cropper'][data-part='crop-handle'][data-position='nw'] {
252
+ border-start-start-radius: var(--xh-_image-cropper-corner-radius);
253
+ }
254
+
255
+ [data-scope='image-cropper'][data-part='crop-handle'][data-position='ne'] {
256
+ border-start-end-radius: var(--xh-_image-cropper-corner-radius);
257
+ }
258
+
259
+ [data-scope='image-cropper'][data-part='crop-handle'][data-position='sw'] {
260
+ border-end-start-radius: var(--xh-_image-cropper-corner-radius);
261
+ }
262
+
263
+ [data-scope='image-cropper'][data-part='crop-handle'][data-position='se'] {
264
+ border-end-end-radius: var(--xh-_image-cropper-corner-radius);
265
+ }
266
+
267
+ [data-scope='image-cropper'][data-part='crop-handle']:is(
268
+ [data-position='nw'],
269
+ [data-position='ne'],
270
+ [data-position='sw'],
271
+ [data-position='se']
272
+ )::after {
273
+ inline-size: calc(var(--xh-_image-cropper-handle-size) / 2);
274
+ block-size: calc(var(--xh-_image-cropper-handle-size) / 2);
275
+ border: 0 solid var(--xh-image-cropper-handle-border, var(--xh-_image-cropper-handle-color));
276
+ border-radius: inherit;
277
+ background: transparent;
278
+ outline: none;
279
+ }
280
+
281
+ [data-scope='image-cropper'][data-part='crop-handle'][data-position='nw']::after {
282
+ right: 0;
283
+ bottom: 0;
284
+ border-block-start-width: var(--xh-image-cropper-handle-thickness, var(--xh-stroke-strong));
285
+ border-inline-start-width: var(--xh-image-cropper-handle-thickness, var(--xh-stroke-strong));
286
+ }
287
+
288
+ [data-scope='image-cropper'][data-part='crop-handle'][data-position='ne']::after {
289
+ bottom: 0;
290
+ left: 0;
291
+ border-block-start-width: var(--xh-image-cropper-handle-thickness, var(--xh-stroke-strong));
292
+ border-inline-end-width: var(--xh-image-cropper-handle-thickness, var(--xh-stroke-strong));
293
+ }
294
+
295
+ [data-scope='image-cropper'][data-part='crop-handle'][data-position='sw']::after {
296
+ top: 0;
297
+ right: 0;
298
+ border-block-end-width: var(--xh-image-cropper-handle-thickness, var(--xh-stroke-strong));
299
+ border-inline-start-width: var(--xh-image-cropper-handle-thickness, var(--xh-stroke-strong));
300
+ }
301
+
302
+ [data-scope='image-cropper'][data-part='crop-handle'][data-position='se']::after {
303
+ top: 0;
304
+ left: 0;
305
+ border-block-end-width: var(--xh-image-cropper-handle-thickness, var(--xh-stroke-strong));
306
+ border-inline-end-width: var(--xh-image-cropper-handle-thickness, var(--xh-stroke-strong));
307
+ }
308
+ /* stylelint-enable property-disallowed-list */
309
+
205
310
  /* 只读与禁用的光标排在八个方位之后:那八条每条都写了 cursor,特异性又完全相同,
206
311
  排在前面会被逐条盖掉,整条规则等于没写。 */
207
312
  [data-scope='image-cropper'][data-part='crop-handle'][data-readonly] {
@@ -212,10 +317,19 @@
212
317
  cursor: not-allowed;
213
318
  }
214
319
 
320
+ /* 两条控制轴用原生 range:拇指与轨道交给平台画,这里只给横向铺满与品牌色。
321
+ 不写 block-size:原生 range 的高度由 UA 与拇指尺寸定,写死会把拇指切掉一截。 */
322
+ [data-scope='image-cropper'][data-part='zoom-slider'],
323
+ [data-scope='image-cropper'][data-part='rotate-slider'] {
324
+ inline-size: var(--xh-image-cropper-slider-w, 100%);
325
+ /* 原生 range 自带左右各一段外边距,与铺满的宽度叠在一起就把这两条轴顶出容器 */
326
+ margin-inline: 0;
327
+ accent-color: var(--xh-image-cropper-slider-accent, var(--xh-bg-brand));
328
+ }
329
+
215
330
  /* 禁用的整体压暗只做一次,落在最外层:视口、裁切框、把手各自再叠一层会越叠越黑。
216
331
  这里只用 opacity、不换前景色:框里是图像,没有文字可换。 */
217
332
  [data-scope='image-cropper'][data-part='root'][data-disabled] {
218
- cursor: not-allowed;
219
333
  opacity: var(--xh-state-disabled-opacity);
220
334
  }
221
335
 
@@ -1,26 +1,14 @@
1
- @layer xihan.motion {
2
- /* 动画名的查找只认「文档里有没有这个名字」:引用别处文件里的名字,那份文件不一定在场,
3
- 动画会静默地整个不跑。每份皮肤都能单独引入,所以它用到的关键帧都写在自己身上。 */
4
- @keyframes xh-fade-in {
5
- from {
6
- opacity: 0;
7
- }
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
+ */
8
5
 
9
- to {
10
- opacity: 1;
11
- }
12
- }
6
+ /* 定义 image viewer 样式。 */
13
7
 
14
- @keyframes xh-fade-out {
15
- from {
16
- opacity: 1;
17
- }
8
+ @import '../family/action-control.css';
18
9
 
19
- to {
20
- opacity: 0;
21
- }
22
- }
23
- }
10
+ /* 共享关键帧由 family/motion.css 提供,本皮肤 @import 它,单独引入仍成立。 */
11
+ @import '../family/motion.css';
24
12
 
25
13
  @layer xihan.components {
26
14
  /* 皮肤在场的标记:开发模式下按 scope 探一次,取不到就说明这份皮肤没被引入。 */
@@ -31,11 +19,22 @@
31
19
  [data-scope='image-viewer'][data-part='backdrop'] {
32
20
  position: fixed;
33
21
  inset: 0;
34
- z-index: var(--xh-image-viewer-backdrop-layer, var(--xh-layer-modal));
22
+ z-index: var(--xh-image-viewer-backdrop-layer, var(--xh-_layer, var(--xh-layer-modal)));
35
23
  /* 看片要压住页面底色,遮罩比普通对话框更深 */
36
24
  background: var(--xh-image-viewer-backdrop-bg, color-mix(in oklab, var(--xh-color-neutral-950) 78%, transparent));
37
25
  }
38
26
 
27
+ /* 同一层底色之上再糊背后的页面 */
28
+ [data-scope='image-viewer'][data-part='backdrop'][data-variant='blur'] {
29
+ -webkit-backdrop-filter: blur(var(--xh-image-viewer-backdrop-blur, var(--xh-overlay-backdrop-blur)));
30
+ backdrop-filter: blur(var(--xh-image-viewer-backdrop-blur, var(--xh-overlay-backdrop-blur)));
31
+ }
32
+
33
+ /* 去掉底色,只留下这一层吃指针:交互外关闭与滚动锁定照旧 */
34
+ [data-scope='image-viewer'][data-part='backdrop'][data-variant='transparent'] {
35
+ background: transparent;
36
+ }
37
+
39
38
  [data-scope='image-viewer'][data-part='backdrop'][data-state='open'] {
40
39
  animation: xh-fade-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter);
41
40
  }
@@ -47,7 +46,7 @@
47
46
  [data-scope='image-viewer'][data-part='positioner'] {
48
47
  position: fixed;
49
48
  inset: 0;
50
- z-index: var(--xh-image-viewer-layer, var(--xh-layer-modal));
49
+ z-index: var(--xh-image-viewer-layer, var(--xh-_layer, var(--xh-layer-modal)));
51
50
  display: flex;
52
51
  align-items: center;
53
52
  justify-content: center;
@@ -58,8 +57,12 @@
58
57
  既不让页面上的层号插进来,也不让它们漏出去压住别人 */
59
58
  isolation: isolate;
60
59
 
61
- /* 作者塞进指示符槽的图标跟着字号走,与皮肤画的兜底字形同一把尺 */
62
- --xh-icon-size: var(--xh-image-viewer-icon-size, var(--xh-glyph-size-text));
60
+ /* 作者塞进浮层的图标:内容层给一档 md 的尺,十颗钮各自再按 Action Control 档位覆盖 */
61
+ --xh-icon-size: var(--xh-image-viewer-icon-size, var(--xh-glyph-size-md));
62
+ /* 十颗钮共用的深色 chrome:压在使用者的图片上的半透明深底,是这一层自己的面,不取页面语义面 */
63
+ --xh-_image-viewer-chrome-bg: var(--xh-image-viewer-chrome-bg, color-mix(in oklab, var(--xh-color-neutral-950) 55%, transparent));
64
+ --xh-_image-viewer-chrome-bg-hover: var(--xh-image-viewer-action-bg-hover, color-mix(in oklab, var(--xh-color-neutral-950) 70%, transparent));
65
+ --xh-_image-viewer-chrome-bg-active: var(--xh-image-viewer-action-bg-active, color-mix(in oklab, var(--xh-color-neutral-950) 80%, transparent));
63
66
 
64
67
  position: relative;
65
68
  inline-size: 100%;
@@ -72,7 +75,9 @@
72
75
  color: var(--xh-image-viewer-fg, var(--xh-color-neutral-0));
73
76
  }
74
77
 
78
+ /* 在场那一档才提前备好合成层:属性随 data-state 一起消失,收起后不长期占着 */
75
79
  [data-scope='image-viewer'][data-part='content'][data-state='open'] {
80
+ will-change: opacity;
76
81
  animation: xh-fade-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter);
77
82
  }
78
83
 
@@ -92,135 +97,181 @@
92
97
  touch-action: none;
93
98
  }
94
99
 
95
- [data-scope='image-viewer'][data-part='viewport'][data-panning] {
100
+ [data-scope='image-viewer'][data-part='viewport'][data-dragging] {
96
101
  cursor: grabbing;
97
102
  }
98
103
 
104
+ /* 原图还在路上:指针先报出来,此刻拖也没有东西可拖 */
105
+ [data-scope='image-viewer'][data-part='viewport'][data-loading] {
106
+ cursor: progress;
107
+ }
108
+
109
+ /* 取图期间在画面正中垫一块占位面:不垫的话打开一张几 MB 的原图,
110
+ 台前是一整块什么都没有的暗底,用户分不清是在加载还是这张图坏了 */
111
+ [data-scope='image-viewer'][data-part='viewport'][data-loading]::after {
112
+ content: '';
113
+ position: absolute;
114
+ inline-size: var(--xh-image-viewer-loading-size, calc(var(--xh-control-h-lg) * 3));
115
+ block-size: var(--xh-image-viewer-loading-size, calc(var(--xh-control-h-lg) * 3));
116
+ border-radius: var(--xh-image-viewer-loading-radius, var(--xh-shape-surface));
117
+ background: var(--xh-image-viewer-loading-bg, var(--xh-bg-surface-raised));
118
+ }
119
+
99
120
  [data-scope='image-viewer'][data-part='image'] {
100
121
  max-inline-size: 90%;
101
122
  max-block-size: 85%;
102
123
  user-select: none;
103
124
  /* 变换是内联 style 写的,只给缩放旋转一点缓动;拖拽平移要跟手,不缓 */
104
- transition: transform var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
125
+ transition: transform var(--xh-motion-duration-micro) var(--xh-motion-ease-continuous);
105
126
  }
106
127
 
107
- [data-scope='image-viewer'][data-part='image'][data-panning] {
128
+ /* 拖拽期间变换每帧都在改:只在这一档备好合成层,松手后随 data-dragging 一起撤掉 */
129
+ [data-scope='image-viewer'][data-part='image'][data-dragging] {
108
130
  transition: none;
131
+ will-change: transform;
109
132
  }
110
133
 
111
- /* —— 悬浮件共用:半透明深底加浅字 —— */
134
+ /* —— 悬浮件共用:半透明深底加浅字 ——
135
+ 四件都钉在铺满视口的那一层的边上,贴边一律与安全区取大的一头:刘海、圆角与
136
+ 底部横条压住的那一段由系统给出,贴边比它窄时钮会被切掉一角;桌面上 env() 恒为 0。
137
+ 安全区只有物理方向的四个名字,行内轴上两侧同取较宽的那一段,RTL 下不会翻错边。
138
+ 工具条是容器、计数是一行字的气泡,各按身份取圆角(surface / control,§6.3),
139
+ --xh-image-viewer-overlay-radius 仍是两者共用的使用者入口;翻页钮的面走下面的桥接槽 */
112
140
  [data-scope='image-viewer'][data-part='toolbar'],
113
- [data-scope='image-viewer'][data-part='counter'],
114
- [data-scope='image-viewer'][data-part='prev-trigger'],
115
- [data-scope='image-viewer'][data-part='next-trigger'] {
141
+ [data-scope='image-viewer'][data-part='counter'] {
116
142
  position: absolute;
117
143
  z-index: 1;
118
- background: var(--xh-image-viewer-chrome-bg, color-mix(in oklab, var(--xh-color-neutral-950) 55%, transparent));
144
+ background: var(--xh-_image-viewer-chrome-bg);
119
145
  color: inherit;
120
146
  border: none;
121
- border-radius: var(--xh-image-viewer-overlay-radius, var(--xh-shape-pill));
122
147
  }
123
148
 
124
149
  [data-scope='image-viewer'][data-part='toolbar'] {
125
- inset-block-end: var(--xh-space-6);
150
+ inset-block-end: max(var(--xh-space-6), env(safe-area-inset-bottom));
126
151
  inset-inline-start: 50%;
127
152
  translate: -50% 0;
128
153
  display: flex;
129
154
  align-items: center;
130
155
  gap: var(--xh-image-viewer-toolbar-gap, var(--xh-space-1));
131
156
  padding: var(--xh-image-viewer-toolbar-padding, var(--xh-space-1_5) var(--xh-space-3));
157
+ border-radius: var(--xh-image-viewer-toolbar-radius-outer, var(--xh-image-viewer-overlay-radius, var(--xh-shape-surface)));
132
158
  }
133
159
 
134
- /* 工具条里的按钮:底色交给工具条,本体只管命中区 */
160
+ /* 工具条里的七颗钮:接 Action Control icon 档 xs(24px 视觉盒),底色交给工具条,本体只管命中区与按压。
161
+ 悬停 / 按下的底从 chrome 那层深底派生,不取页面的 --xh-bg-subtle-* 阶梯。
162
+ position 交给配方的 relative:粗指针下 44px 命中区 ::after 以钮自身为包含块;
163
+ 这里若写 static,七块命中区会一起以工具条为包含块、彼此盖住,触屏上只剩最后一颗可点 */
135
164
  [data-scope='image-viewer'][data-part='toolbar'] > [data-part] {
136
- position: static;
137
- display: inline-flex;
138
- align-items: center;
139
- justify-content: center;
140
- inline-size: var(--xh-control-action-size);
141
- block-size: var(--xh-control-action-size);
142
- background: none;
143
- border: none;
144
- border-radius: var(--xh-image-viewer-toolbar-radius, var(--xh-shape-control));
145
- color: inherit;
146
- font-size: var(--xh-glyph-size-sm);
147
- cursor: pointer;
165
+ --xh-action-bg-rest: transparent;
166
+ --xh-action-bg-hover: var(--xh-_image-viewer-chrome-bg-hover);
167
+ --xh-action-bg-pressed: var(--xh-_image-viewer-chrome-bg-active);
168
+ --xh-action-bg-focus-visible: transparent;
169
+ --xh-action-bg-disabled: transparent;
170
+ --xh-action-fg-rest: currentColor;
171
+ --xh-action-fg-hover: currentColor;
172
+ --xh-action-fg-pressed: currentColor;
173
+ --xh-action-fg-focus-visible: currentColor;
174
+ --xh-action-fg-disabled: currentColor;
175
+ --xh-action-border-rest: transparent;
176
+ --xh-action-border-hover: transparent;
177
+ --xh-action-border-pressed: transparent;
178
+ --xh-action-border-focus-visible: transparent;
179
+ --xh-action-border-disabled: transparent;
180
+ --xh-action-radius: var(--xh-image-viewer-toolbar-radius, var(--xh-_action-profile-radius));
181
+ --xh-icon-size: var(--xh-image-viewer-icon-size, var(--xh-_action-profile-glyph-size));
148
182
  }
149
183
 
150
184
  [data-scope='image-viewer'][data-part='counter'] {
151
- inset-block-start: var(--xh-space-6);
185
+ inset-block-start: max(var(--xh-space-6), env(safe-area-inset-top));
152
186
  inset-inline-start: 50%;
153
187
  translate: -50% 0;
154
188
  padding: var(--xh-image-viewer-counter-padding, var(--xh-space-1) var(--xh-space-3));
189
+ border-radius: var(--xh-image-viewer-counter-radius, var(--xh-image-viewer-overlay-radius, var(--xh-shape-control)));
155
190
  font-size: var(--xh-text-secondary-size);
156
191
  }
157
192
 
193
+ /* 两颗翻页钮:浮在图上的单图标圆钮,接 Action Control floating 档 md(48px,正方盒取 circle,§6.3)。
194
+ 面桥接到自家深色 chrome:跟的是图上那层半透明深底不是页面底色,所以不取 frosted 缺省面,
195
+ 也不取语义档的 --xh-bg-subtle-*;到头时是原生 disabled,压不动的东西不给回应 */
158
196
  [data-scope='image-viewer'][data-part='prev-trigger'],
159
197
  [data-scope='image-viewer'][data-part='next-trigger'] {
160
- display: inline-flex;
161
- align-items: center;
162
- justify-content: center;
163
- inline-size: var(--xh-control-h-lg);
164
- block-size: var(--xh-control-h-lg);
165
- font-size: var(--xh-glyph-size-md);
166
- cursor: pointer;
198
+ --xh-action-bg-rest: var(--xh-_image-viewer-chrome-bg);
199
+ --xh-action-bg-hover: var(--xh-_image-viewer-chrome-bg-hover);
200
+ --xh-action-bg-pressed: var(--xh-_image-viewer-chrome-bg-active);
201
+ --xh-action-bg-focus-visible: var(--xh-_image-viewer-chrome-bg);
202
+ --xh-action-bg-disabled: var(--xh-_image-viewer-chrome-bg);
203
+ --xh-action-fg-rest: currentColor;
204
+ --xh-action-fg-hover: currentColor;
205
+ --xh-action-fg-pressed: currentColor;
206
+ --xh-action-fg-focus-visible: currentColor;
207
+ --xh-action-fg-disabled: currentColor;
208
+ --xh-action-border-rest: transparent;
209
+ --xh-action-border-hover: transparent;
210
+ --xh-action-border-pressed: transparent;
211
+ --xh-action-border-focus-visible: transparent;
212
+ --xh-action-border-disabled: transparent;
213
+ --xh-action-radius: var(--xh-image-viewer-overlay-radius, var(--xh-_action-profile-radius));
214
+ --xh-icon-size: var(--xh-image-viewer-icon-size, var(--xh-_action-profile-glyph-size));
215
+
216
+ position: absolute;
217
+ z-index: 1;
167
218
  }
168
219
 
169
220
  [data-scope='image-viewer'][data-part='prev-trigger'] {
170
- inset-inline-start: var(--xh-space-6);
221
+ inset-inline-start: max(var(--xh-space-6), env(safe-area-inset-left), env(safe-area-inset-right));
171
222
  inset-block-start: 50%;
172
223
  translate: 0 -50%;
173
224
  }
174
225
 
175
226
  [data-scope='image-viewer'][data-part='next-trigger'] {
176
- inset-inline-end: var(--xh-space-6);
227
+ inset-inline-end: max(var(--xh-space-6), env(safe-area-inset-left), env(safe-area-inset-right));
177
228
  inset-block-start: 50%;
178
229
  translate: 0 -50%;
179
230
  }
180
231
 
181
- /* 右上角的关闭钮:与翻页钮同底同高,但圆角走控件档,按下有缩放 */
232
+ /* 右上角的关闭钮:接 Action Control icon 档 lg(40px,触控靶走 lg),圆角走控件档;面与翻页钮同一层深色 chrome */
182
233
  [data-scope='image-viewer'][data-part='close-trigger'] {
234
+ --xh-action-bg-rest: var(--xh-_image-viewer-chrome-bg);
235
+ --xh-action-bg-hover: var(--xh-image-viewer-close-bg-hover, var(--xh-_image-viewer-chrome-bg-hover));
236
+ --xh-action-bg-pressed: var(--xh-image-viewer-close-bg-active, var(--xh-_image-viewer-chrome-bg-active));
237
+ --xh-action-bg-focus-visible: var(--xh-_image-viewer-chrome-bg);
238
+ --xh-action-bg-disabled: var(--xh-_image-viewer-chrome-bg);
239
+ /* 看图时整块 chrome 盖住页面,叉的颜色随那层继承;使用者要单独换叉的颜色走这支槽 */
240
+ --xh-action-fg-rest: var(--xh-image-viewer-close-fg, currentColor);
241
+ --xh-action-fg-hover: var(--xh-image-viewer-close-fg, currentColor);
242
+ --xh-action-fg-pressed: var(--xh-image-viewer-close-fg, currentColor);
243
+ --xh-action-fg-focus-visible: var(--xh-image-viewer-close-fg, currentColor);
244
+ --xh-action-fg-disabled: var(--xh-image-viewer-close-fg, currentColor);
245
+ --xh-action-border-rest: transparent;
246
+ --xh-action-border-hover: transparent;
247
+ --xh-action-border-pressed: transparent;
248
+ --xh-action-border-focus-visible: transparent;
249
+ --xh-action-border-disabled: transparent;
250
+ --xh-action-radius: var(--xh-image-viewer-close-radius, var(--xh-shape-control));
251
+ --xh-action-visual-size: var(--xh-image-viewer-close-size, var(--xh-_action-profile-visual-size));
252
+ --xh-icon-size: var(--xh-image-viewer-icon-size, var(--xh-_action-profile-glyph-size));
253
+
183
254
  position: absolute;
184
255
  z-index: 1;
185
- inset-block-start: var(--xh-space-6);
186
- inset-inline-end: var(--xh-space-6);
187
- display: inline-flex;
188
- align-items: center;
189
- justify-content: center;
190
- inline-size: var(--xh-image-viewer-close-size, var(--xh-control-h-lg));
191
- block-size: var(--xh-image-viewer-close-size, var(--xh-control-h-lg));
192
- border: none;
193
- border-radius: var(--xh-image-viewer-close-radius, var(--xh-shape-control));
194
- background: var(--xh-image-viewer-chrome-bg, color-mix(in oklab, var(--xh-color-neutral-950) 55%, transparent));
195
- color: inherit;
196
- font-size: var(--xh-glyph-size-md);
197
- cursor: pointer;
198
- transition:
199
- background var(--xh-motion-duration-micro) var(--xh-motion-ease-enter),
200
- scale var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
256
+ inset-block-start: max(var(--xh-space-6), env(safe-area-inset-top));
257
+ inset-inline-end: max(var(--xh-space-6), env(safe-area-inset-left), env(safe-area-inset-right));
201
258
  }
202
259
 
203
- [data-scope='image-viewer'][data-part='close-trigger'][hidden] {
204
- display: none;
205
- }
206
-
207
- [data-scope='image-viewer'][data-part='close-trigger']:hover:not(:disabled) {
208
- background: var(--xh-image-viewer-close-bg-hover, color-mix(in oklab, var(--xh-color-neutral-950) 70%, transparent));
209
- }
210
-
211
- [data-scope='image-viewer'][data-part='close-trigger']:active {
212
- background: var(--xh-image-viewer-close-bg-active, color-mix(in oklab, var(--xh-color-neutral-950) 80%, transparent));
213
- scale: var(--xh-motion-scale-press);
260
+ /* 三颗钮的面是一层深色纱,纱底下是使用者的图片,环色算不出稳定的取值。
261
+ content 把前景钉死在浅字上、三颗钮的前景随它继承,环取 currentColor 即是那支浅字,
262
+ 不随图片内容也不随主题翻 */
263
+ [data-scope='image-viewer'][data-part='close-trigger']:focus-visible,
264
+ [data-scope='image-viewer'][data-part='prev-trigger']:focus-visible,
265
+ [data-scope='image-viewer'][data-part='next-trigger']:focus-visible {
266
+ --xh-action-ring-color-focus-visible: currentColor;
214
267
  }
215
268
 
216
- [data-scope='image-viewer'] button[data-part]:focus-visible {
217
- outline: var(--xh-ring-width) solid var(--xh-ring-focus);
218
- outline-offset: var(--xh-ring-offset);
269
+ [data-scope='image-viewer'][data-part='close-trigger'][hidden] {
270
+ display: none;
219
271
  }
220
272
 
221
273
  [data-scope='image-viewer'] button[data-part]:disabled {
222
274
  opacity: var(--xh-state-disabled-opacity);
223
- cursor: not-allowed;
224
275
  }
225
276
 
226
277
  /* 没写内容就由皮肤画兜底字形;作者往部件里塞了自己的图形,这几条即不命中 */
@@ -328,7 +379,17 @@
328
379
  [data-scope='image-viewer'][data-part='positioner'][hidden],
329
380
  [data-scope='image-viewer'][data-part='viewport'][hidden],
330
381
  [data-scope='image-viewer'][data-part='toolbar'][hidden],
382
+ [data-scope='image-viewer'][data-part='prev-trigger'][hidden],
331
383
  [data-scope='image-viewer'][data-part='next-trigger'][hidden] {
332
384
  display: none;
333
385
  }
386
+
387
+ @media print {
388
+ /* 打印:定位层与遮罩是交互期才存在的东西,落到纸上只会盖住底下的正文。
389
+ 收起写在这份皮肤里而不是 reset:reset 层排在组件层之前,组件层里任何一条 display 都压得住它。 */
390
+ [data-scope='image-viewer'][data-part='backdrop'],
391
+ [data-scope='image-viewer'][data-part='positioner'] {
392
+ display: none;
393
+ }
394
+ }
334
395
  }
package/css/image.css CHANGED
@@ -1,3 +1,13 @@
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
+ /* 定义 image 样式。 */
7
+
8
+ /* 共享关键帧由 family/motion.css 提供,本皮肤 @import 它,单独引入仍成立。 */
9
+ @import '../family/motion.css';
10
+
1
11
  @layer xihan.components {
2
12
  /* 皮肤在场的标记:开发模式下按 scope 探一次,取不到就说明这份皮肤没被引入。 */
3
13
  [data-scope='image'] {
@@ -22,11 +32,30 @@
22
32
  display: none;
23
33
  }
24
34
 
25
- /* 不声明 display:加载完成前的 hidden 直接交给浏览器的 [hidden] 规则收起 */
35
+ /* 不声明 display:加载完成前的 hidden 直接交给浏览器的 [hidden] 规则收起。
36
+ 载完那一刻 hidden 撤掉、这个节点从不显示变为显示,淡入在那一刻起跑,
37
+ 图片不是硬切上来盖掉占位层 */
26
38
  [data-scope='image'][data-part='image'] {
27
39
  inline-size: 100%;
28
40
  block-size: 100%;
29
41
  object-fit: var(--xh-image-fit, cover);
42
+ animation: xh-fade-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter);
43
+ }
44
+
45
+ /* 铺满图位的占位层:加载期间占住画面,作者往里放骨架屏或模糊小图 */
46
+ [data-scope='image'][data-part='placeholder'] {
47
+ position: absolute;
48
+ inset: 0;
49
+ display: flex;
50
+ align-items: center;
51
+ justify-content: center;
52
+ background: var(--xh-image-placeholder-bg, var(--xh-bg-subtle-hover));
53
+ color: var(--xh-image-placeholder-fg, var(--xh-fg-subtle));
54
+ }
55
+
56
+ /* display 会盖掉 hidden 属性的 UA 规则:图片落位与失败后都靠这条收起 */
57
+ [data-scope='image'][data-part='placeholder'][hidden] {
58
+ display: none;
30
59
  }
31
60
 
32
61
  [data-scope='image'][data-part='fallback'] {