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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/CHANGELOG.md +527 -0
  2. package/README.md +1 -1
  3. package/css/accordion.css +61 -5
  4. package/css/affix.css +5 -0
  5. package/css/alert.css +35 -5
  6. package/css/anchor.css +12 -7
  7. package/css/avatar-group.css +8 -3
  8. package/css/avatar.css +8 -3
  9. package/css/back-top.css +21 -1
  10. package/css/badge.css +21 -2
  11. package/css/breadcrumb.css +29 -4
  12. package/css/button-group.css +19 -13
  13. package/css/button.css +57 -11
  14. package/css/calendar.css +124 -2
  15. package/css/card.css +10 -5
  16. package/css/carousel.css +41 -5
  17. package/css/cascader.css +188 -102
  18. package/css/checkbox-group.css +46 -19
  19. package/css/checkbox.css +59 -8
  20. package/css/clipboard.css +7 -1
  21. package/css/code-block.css +17 -7
  22. package/css/collapsible.css +47 -2
  23. package/css/color-picker.css +117 -46
  24. package/css/combobox.css +132 -52
  25. package/css/composer.css +10 -3
  26. package/css/context-menu.css +101 -75
  27. package/css/countdown.css +5 -0
  28. package/css/date-field.css +96 -14
  29. package/css/date-picker.css +281 -65
  30. package/css/descriptions.css +7 -2
  31. package/css/dialog.css +109 -22
  32. package/css/download-trigger.css +76 -0
  33. package/css/drawer.css +75 -23
  34. package/css/dynamic-input.css +73 -17
  35. package/css/editable.css +17 -10
  36. package/css/ellipsis.css +6 -1
  37. package/css/empty-state.css +12 -7
  38. package/css/field.css +14 -6
  39. package/css/fieldset.css +69 -0
  40. package/css/file-upload.css +50 -7
  41. package/css/flex.css +5 -0
  42. package/css/float-button.css +21 -1
  43. package/css/floating-panel.css +310 -0
  44. package/css/form.css +10 -5
  45. package/css/gradient-text.css +5 -0
  46. package/css/grid.css +5 -0
  47. package/css/heatmap.css +410 -0
  48. package/css/highlight.css +6 -1
  49. package/css/hotkeys.css +90 -0
  50. package/css/hover-card.css +58 -50
  51. package/css/icon-wrapper.css +5 -0
  52. package/css/icon.css +5 -0
  53. package/css/image-cropper.css +231 -0
  54. package/css/image-viewer.css +167 -22
  55. package/css/image.css +7 -2
  56. package/css/infinite-scroll.css +6 -1
  57. package/css/json-viewer.css +241 -0
  58. package/css/layout.css +11 -10
  59. package/css/list.css +13 -8
  60. package/css/listbox.css +25 -4
  61. package/css/loading-bar.css +12 -1
  62. package/css/log.css +6 -1
  63. package/css/marquee.css +14 -0
  64. package/css/masonry.css +63 -0
  65. package/css/mention.css +105 -32
  66. package/css/menu.css +131 -46
  67. package/css/menubar.css +150 -66
  68. package/css/navigation-menu.css +59 -29
  69. package/css/number-animation.css +8 -3
  70. package/css/number-field.css +178 -16
  71. package/css/overlay-arrow.css +51 -0
  72. package/css/page-header.css +10 -5
  73. package/css/pagination.css +18 -13
  74. package/css/password-input.css +356 -0
  75. package/css/pin-input.css +18 -10
  76. package/css/popconfirm.css +73 -16
  77. package/css/popover.css +88 -44
  78. package/css/popselect.css +201 -52
  79. package/css/progress.css +52 -7
  80. package/css/qr-code.css +6 -1
  81. package/css/radio-group.css +19 -8
  82. package/css/rating.css +7 -2
  83. package/css/reset.css +17 -0
  84. package/css/result.css +11 -6
  85. package/css/scroll-area.css +65 -94
  86. package/css/scrollbar.css +167 -0
  87. package/css/segmented.css +215 -0
  88. package/css/select.css +292 -124
  89. package/css/separator.css +7 -2
  90. package/css/side-nav.css +116 -19
  91. package/css/signature-pad.css +144 -0
  92. package/css/skeleton.css +12 -1
  93. package/css/slider.css +15 -10
  94. package/css/space.css +117 -0
  95. package/css/spinner.css +31 -11
  96. package/css/splitter.css +5 -0
  97. package/css/statistic.css +14 -9
  98. package/css/steps.css +30 -8
  99. package/css/switch.css +62 -13
  100. package/css/table.css +136 -37
  101. package/css/tabs.css +9 -4
  102. package/css/tag.css +177 -0
  103. package/css/tags-input.css +83 -40
  104. package/css/text-field.css +154 -36
  105. package/css/thread.css +6 -1
  106. package/css/time-field.css +102 -16
  107. package/css/time-picker.css +264 -69
  108. package/css/time.css +5 -0
  109. package/css/timeline.css +15 -10
  110. package/css/timer.css +119 -0
  111. package/css/toast.css +46 -21
  112. package/css/toaster.css +6 -1
  113. package/css/toggle-group.css +5 -0
  114. package/css/toggle.css +11 -6
  115. package/css/tone.css +10 -5
  116. package/css/toolbar.css +6 -1
  117. package/css/tooltip.css +54 -38
  118. package/css/tour.css +128 -63
  119. package/css/transfer.css +73 -17
  120. package/css/tree-select.css +203 -83
  121. package/css/tree.css +90 -1
  122. package/css/typography.css +9 -4
  123. package/css/undefined.css +11 -1
  124. package/css/virtualizer.css +8 -1
  125. package/css/watermark.css +6 -2
  126. package/index.css +17 -1
  127. package/index.unlayered.css +9394 -2883
  128. package/package.json +19 -4
  129. package/css/motion.css +0 -58
@@ -1,4 +1,36 @@
1
+ @layer xihan.motion {
2
+ /* 动画名的查找只认「文档里有没有这个名字」:引用别处文件里的名字,那份文件不一定在场,
3
+ 动画会静默地整个不跑。每份皮肤都能单独引入,所以它用到的关键帧都写在自己身上。 */
4
+ @keyframes xh-pop-in {
5
+ from {
6
+ opacity: 0;
7
+ scale: var(--xh-motion-scale-enter);
8
+ }
9
+
10
+ to {
11
+ opacity: 1;
12
+ scale: 1;
13
+ }
14
+ }
15
+ @keyframes xh-pop-out {
16
+ from {
17
+ opacity: 1;
18
+ scale: 1;
19
+ }
20
+
21
+ to {
22
+ opacity: 0;
23
+ scale: var(--xh-motion-scale-enter);
24
+ }
25
+ }
26
+ }
27
+
1
28
  @layer xihan.components {
29
+ /* 皮肤在场的标记:开发模式下按 scope 探一次,取不到就说明这份皮肤没被引入。 */
30
+ [data-scope='hover-card'] {
31
+ --xh-hover-card-skin: 1;
32
+ }
33
+
2
34
  /* 根节点只把 trigger 与浮层收在一处,不参与定位(坐标系仍由祖先的定位上下文决定) */
3
35
  [data-scope='hover-card'][data-part='root'] {
4
36
  display: inline-block;
@@ -31,8 +63,12 @@
31
63
 
32
64
  /* 坐标由定位引擎写进 style(inset-inline-start / inset-block-start),这里只定位模式与层级 */
33
65
  [data-scope='hover-card'][data-part='positioner'] {
66
+ /* 落定那一侧的可用高度,由定位引擎经内联自定义属性写进来,内容层继承它限高;
67
+ 引擎没算(尚未落位 / 无引擎 / 贴边归零)时不额外限高 */
68
+ --xh-_hover-card-available-h: 100vh;
69
+
34
70
  position: fixed;
35
- z-index: var(--xh-layer-popover);
71
+ z-index: var(--xh-hover-card-layer, var(--xh-layer-popover));
36
72
  /* 空壳不吃指针:trigger 与 content 之间那段间隙要能点得穿,
37
73
  指针穿行的判定全靠 content 自己的进出事件 */
38
74
  pointer-events: none;
@@ -58,22 +94,24 @@
58
94
  gap: var(--xh-hover-card-gap, var(--xh-space-2));
59
95
  inline-size: max-content;
60
96
  max-inline-size: var(--xh-hover-card-max-w, var(--xh-_hover-card-max-w));
97
+ /* 内容由作者给,长度不可预知:不限高就整块伸出视口且滚不到 */
98
+ max-block-size: min(var(--xh-hover-card-max-h, var(--xh-overlay-max-h)), var(--xh-_hover-card-available-h));
99
+ overflow-y: auto;
61
100
  padding-block: var(--xh-hover-card-py, var(--xh-_hover-card-py));
62
101
  padding-inline: var(--xh-hover-card-px, var(--xh-_hover-card-px));
63
102
  border: var(--xh-stroke-thin) solid var(--xh-hover-card-border, var(--xh-border-default));
64
103
  border-radius: var(--xh-hover-card-radius, var(--xh-shape-surface));
65
104
  background: var(--xh-hover-card-bg, var(--xh-bg-surface));
66
105
  color: var(--xh-hover-card-fg, var(--xh-fg-default));
67
- font-size: var(--xh-hover-card-font-size, var(--xh-text-body-size));
68
- line-height: var(--xh-leading-normal);
69
- box-shadow: var(--xh-hover-card-shadow, var(--xh-elevation-2));
106
+ box-shadow: var(--xh-hover-card-shadow, var(--xh-elevation-floating));
70
107
  pointer-events: auto;
71
108
  }
72
109
 
73
- /* display 会盖掉 hidden 属性的 UA 规则,收起态显式让位 */
74
- [data-scope='hover-card'][data-part='content'][hidden] {
75
- display: none;
76
- }
110
+ /* 这里刻意没有 [data-part='content'][hidden] { display: none },与 dialog 一致:
111
+ 收起态连接层会给 content 打 hidden,而上面那条 display 正是要盖掉 UA 的
112
+ [hidden]{display:none}——退场动画得先能播。presence 读的是 content 的 animationName,
113
+ 真正的收起由宿主在退场结束后落成内联 display:none(Vue 那边直接不渲染)。
114
+ 其余声明了 display 的角色节点仍补 [hidden],那几处没有退场动画。 */
77
115
 
78
116
  /* 卡片是可编程聚焦的容器(tabindex=-1),焦点环留给里面真正的可聚焦元素 */
79
117
  [data-scope='hover-card'][data-part='content']:focus-visible {
@@ -81,7 +119,13 @@
81
119
  }
82
120
 
83
121
  [data-scope='hover-card'][data-part='content'][data-state='open'] {
84
- animation: xh-fade-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter);
122
+ animation: xh-pop-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter);
123
+ }
124
+
125
+ /* 退场:收起那一帧 hidden 由退场闸门押后,这里才播得出来。forwards 把末态钉住,
126
+ 免得动画结束到真收起之间闪一下原样 */
127
+ [data-scope='hover-card'][data-part='content'][data-state='closed'] {
128
+ animation: xh-pop-out var(--xh-motion-duration-exit) var(--xh-motion-ease-exit) forwards;
85
129
  }
86
130
 
87
131
  /* —— 尺寸 —— */
@@ -99,52 +143,16 @@
99
143
  --xh-_hover-card-max-w: var(--xh-overlay-max-w-lg);
100
144
  }
101
145
 
102
- /* 箭头是旋转 45° 的方块,只保留朝向锚点那两条边的描边;
146
+ /* 箭头的底色与描边色,几何在 overlay-arrow.css。
103
147
  交叉轴落点由定位引擎经内联自定义属性写进来,没给时退回居中 */
104
148
  [data-scope='hover-card'][data-part='arrow'] {
105
- --xh-_hover-card-arrow-size: var(--xh-hover-card-arrow-size, var(--xh-overlay-arrow-size));
106
149
  --xh-_hover-card-arrow-x: 50%;
107
150
  --xh-_hover-card-arrow-y: 50%;
151
+ --xh-_overlay-arrow-size: var(--xh-hover-card-arrow-size, var(--xh-overlay-arrow-size));
152
+ --xh-_overlay-arrow-x: var(--xh-_hover-card-arrow-x);
153
+ --xh-_overlay-arrow-y: var(--xh-_hover-card-arrow-y);
108
154
 
109
- position: absolute;
110
- inline-size: var(--xh-_hover-card-arrow-size);
111
- block-size: var(--xh-_hover-card-arrow-size);
112
- border: var(--xh-stroke-thin) solid var(--xh-hover-card-border, var(--xh-border-default));
113
155
  background: var(--xh-hover-card-bg, var(--xh-bg-surface));
114
- rotate: 45deg;
115
- }
116
-
117
- [data-scope='hover-card'][data-part='arrow'][data-placement^='bottom'] {
118
- inset-block-start: calc(var(--xh-_hover-card-arrow-size) / -2);
119
- inset-inline-start: calc(var(--xh-_hover-card-arrow-x) - var(--xh-_hover-card-arrow-size) / 2);
120
- border-block-end: 0;
121
- border-inline-end: 0;
122
- }
123
-
124
- [data-scope='hover-card'][data-part='arrow'][data-placement^='top'] {
125
- inset-block-end: calc(var(--xh-_hover-card-arrow-size) / -2);
126
- inset-inline-start: calc(var(--xh-_hover-card-arrow-x) - var(--xh-_hover-card-arrow-size) / 2);
127
- border-block-start: 0;
128
- border-inline-start: 0;
129
- }
130
-
131
- [data-scope='hover-card'][data-part='arrow'][data-placement^='right'] {
132
- inset-inline-start: calc(var(--xh-_hover-card-arrow-size) / -2);
133
- inset-block-start: calc(var(--xh-_hover-card-arrow-y) - var(--xh-_hover-card-arrow-size) / 2);
134
- border-block-start: 0;
135
- border-inline-end: 0;
136
- }
137
-
138
- [data-scope='hover-card'][data-part='arrow'][data-placement^='left'] {
139
- inset-inline-end: calc(var(--xh-_hover-card-arrow-size) / -2);
140
- inset-block-start: calc(var(--xh-_hover-card-arrow-y) - var(--xh-_hover-card-arrow-size) / 2);
141
- border-block-end: 0;
142
- border-inline-start: 0;
143
- }
144
-
145
- @media (prefers-reduced-motion: reduce) {
146
- [data-scope='hover-card'][data-part='content'] {
147
- animation-duration: 0.01ms !important;
148
- }
156
+ border: var(--xh-stroke-thin) solid var(--xh-hover-card-border, var(--xh-border-default));
149
157
  }
150
158
  }
@@ -1,4 +1,9 @@
1
1
  @layer xihan.components {
2
+ /* 皮肤在场的标记:开发模式下按 scope 探一次,取不到就说明这份皮肤没被引入。 */
3
+ [data-scope='icon-wrapper'] {
4
+ --xh-icon-wrapper-skin: 1;
5
+ }
6
+
2
7
  /* 定直径的图标底座:图元在正中,底色、描边与形状由三轴决定。
3
8
  直径与图元直径分两个槽位,底座换档时里面的图元跟着一起换 */
4
9
  [data-scope='icon-wrapper'][data-part='root'] {
package/css/icon.css CHANGED
@@ -1,4 +1,9 @@
1
1
  @layer xihan.components {
2
+ /* 皮肤在场的标记:开发模式下按 scope 探一次,取不到就说明这份皮肤没被引入。 */
3
+ [data-scope='icon'] {
4
+ --xh-icon-skin: 1;
5
+ }
6
+
2
7
  /*
3
8
  * 根 <svg>。直径、描边粗细、行内位置都由这里给,记录只带几何。
4
9
  * 不写 fill / stroke:记录里那两条呈现属性取 currentColor,改这里会把
@@ -0,0 +1,231 @@
1
+ @layer xihan.components {
2
+ /* 皮肤在场的标记:开发模式下按 scope 探一次,取不到就说明这份皮肤没被引入。 */
3
+ [data-scope='image-cropper'] {
4
+ --xh-image-cropper-skin: 1;
5
+ }
6
+
7
+ /* 定位分工:裁切框在视口里的 left / top / width / height,以及图片与裁切框的
8
+ transform,由连接层每帧写进内联样式——那四个数是图片像素换算出来的百分比,
9
+ 只有连接层算得出来。这份皮肤一个都不碰,写了不但是死声明,还会跟内联值抢位置。
10
+
11
+ 私有槽两个,都挂在 root 上靠继承发下去:
12
+ handle-size 是把手的边长,zoom 是当前缩放倍率——裁切框整体被 scale 放大,
13
+ 把手与描边要按倍率反向缩回去,否则放到 3 倍时把手也变成三倍大、描边变成三像素粗。 */
14
+ [data-scope='image-cropper'][data-part='root'] {
15
+ --xh-_image-cropper-handle-size: var(--xh-image-cropper-handle-size, var(--xh-control-indicator-size));
16
+ --xh-_image-cropper-zoom: 1;
17
+
18
+ position: relative;
19
+ display: block;
20
+ inline-size: var(--xh-image-cropper-w, 100%);
21
+ }
22
+
23
+ /* 量坐标的那个盒子。里面装的是图片像素坐标,不是文字:裁切框与把手的落点一律写物理属性,
24
+ 不依赖这里的文字方向。overflow 收口:放大后的图片与遮罩都靠它裁掉。 */
25
+ [data-scope='image-cropper'][data-part='viewport'] {
26
+ position: relative;
27
+ display: block;
28
+ overflow: hidden;
29
+ border-radius: var(--xh-image-cropper-radius, var(--xh-shape-surface));
30
+ background: var(--xh-image-cropper-bg, var(--xh-bg-muted));
31
+ user-select: none;
32
+ }
33
+
34
+ /* 图片铺满视口、高度自适应:视口的高度因此由图片撑出来,
35
+ 裁切框的百分比坐标才与图片的像素一一对应。 */
36
+ [data-scope='image-cropper'][data-part='image'] {
37
+ display: block;
38
+ inline-size: 100%;
39
+ block-size: auto;
40
+ }
41
+
42
+ /* 裁切框。遮罩用一圈超大的实心投影把框外压暗——不必再摆四块遮罩节点,
43
+ 圆形裁切时投影也自动跟着圆角走。描边按倍率缩回去,放大时仍是一根细线。 */
44
+ [data-scope='image-cropper'][data-part='crop-area'] {
45
+ position: absolute;
46
+ box-sizing: border-box;
47
+ border-style: solid;
48
+ border-width: calc(var(--xh-stroke-thin) / var(--xh-_image-cropper-zoom));
49
+ border-color: var(--xh-image-cropper-crop-border, var(--xh-bg-surface));
50
+ box-shadow: 0 0 0 9999px var(--xh-image-cropper-mask, var(--xh-bg-overlay));
51
+ cursor: move;
52
+ }
53
+
54
+ /* 圆形裁切的这一档只能写 50%:shape 那四档给的是固定长度,
55
+ 裁切框的宽高由数据决定、两边通常不等长,固定长度画出来是胶囊不是椭圆。 */
56
+ [data-scope='image-cropper'][data-part='crop-area'][data-shape='round'] {
57
+ border-radius: 50%;
58
+ }
59
+
60
+ [data-scope='image-cropper'][data-part='crop-area'][data-dragging] {
61
+ cursor: grabbing;
62
+ }
63
+
64
+ [data-scope='image-cropper'][data-part='crop-area'][data-readonly] {
65
+ cursor: default;
66
+ }
67
+
68
+ [data-scope='image-cropper'][data-part='crop-area'][data-disabled] {
69
+ cursor: not-allowed;
70
+ }
71
+
72
+ [data-scope='image-cropper'][data-part='crop-area']:focus-visible {
73
+ outline: var(--xh-ring-width) solid var(--xh-ring-focus);
74
+ outline-offset: var(--xh-ring-offset);
75
+ }
76
+
77
+ /* 三分构图参考线:两条竖线、两条横线,落点写死在三分处,不占节点、不吃指针。
78
+ 停靠点逐条写全而不用 repeating-:重复单元从 0 起算会在框的两条边缘上各多画一根,
79
+ 那两根与裁切框的描边叠在一起,看着像描边加粗了一倍。
80
+ 线宽跟着裁切框的倍率缩回去,与描边同一口径。
81
+ 圆形裁切时参考线没有意义,收起来。 */
82
+ [data-scope='image-cropper'][data-part='grid'] {
83
+ --xh-_image-cropper-grid-w: calc(var(--xh-stroke-thin) / var(--xh-_image-cropper-zoom));
84
+
85
+ position: absolute;
86
+ inset: 0;
87
+ background-image:
88
+ linear-gradient(
89
+ to right,
90
+ transparent 0 33.3333%,
91
+ var(--xh-image-cropper-grid-line, var(--xh-border-subtle)) 33.3333% calc(33.3333% + var(--xh-_image-cropper-grid-w)),
92
+ transparent calc(33.3333% + var(--xh-_image-cropper-grid-w)) 66.6667%,
93
+ var(--xh-image-cropper-grid-line, var(--xh-border-subtle)) 66.6667% calc(66.6667% + var(--xh-_image-cropper-grid-w)),
94
+ transparent calc(66.6667% + var(--xh-_image-cropper-grid-w)) 100%
95
+ ),
96
+ linear-gradient(
97
+ to bottom,
98
+ transparent 0 33.3333%,
99
+ var(--xh-image-cropper-grid-line, var(--xh-border-subtle)) 33.3333% calc(33.3333% + var(--xh-_image-cropper-grid-w)),
100
+ transparent calc(33.3333% + var(--xh-_image-cropper-grid-w)) 66.6667%,
101
+ var(--xh-image-cropper-grid-line, var(--xh-border-subtle)) 66.6667% calc(66.6667% + var(--xh-_image-cropper-grid-w)),
102
+ transparent calc(66.6667% + var(--xh-_image-cropper-grid-w)) 100%
103
+ );
104
+ pointer-events: none;
105
+ }
106
+
107
+ [data-scope='image-cropper'][data-part='grid'][data-shape='round'] {
108
+ background-image: none;
109
+ }
110
+
111
+ /* 把手钉在裁切框的角与边上。落点写物理 left / top:裁切框的坐标是图片像素,
112
+ 连接层写进内联样式的也是物理 left / top,把手跟着走才不会与框错开一侧。
113
+ translate 把方块的中心挪到点上;scale 按倍率缩回去,缩放时把手看着大小不变。 */
114
+ [data-scope='image-cropper'][data-part='crop-handle'] {
115
+ position: absolute;
116
+ display: block;
117
+ box-sizing: border-box;
118
+ border-style: solid;
119
+ border-width: calc(var(--xh-stroke-thin) / var(--xh-_image-cropper-zoom));
120
+ border-color: var(--xh-image-cropper-handle-border, var(--xh-border-strong));
121
+ border-radius: var(--xh-image-cropper-handle-radius, var(--xh-shape-inset));
122
+ background: var(--xh-image-cropper-handle-bg, var(--xh-bg-surface));
123
+ inline-size: var(--xh-_image-cropper-handle-size);
124
+ block-size: var(--xh-_image-cropper-handle-size);
125
+ translate: -50% -50%;
126
+ scale: calc(1 / var(--xh-_image-cropper-zoom));
127
+ }
128
+
129
+ /* 看得见的方块比手指小:另铺一层不可见的命中区,把它撑到最小可点尺寸。
130
+ inset 取负值即向四面外扩,方块本身更大时这个值转正、命中区仍是方块那么大。 */
131
+ [data-scope='image-cropper'][data-part='crop-handle']::before {
132
+ position: absolute;
133
+ inset: calc((var(--xh-_image-cropper-handle-size) - var(--xh-control-action-size)) / 2);
134
+ content: '';
135
+ }
136
+
137
+ [data-scope='image-cropper'][data-part='crop-handle']:focus-visible {
138
+ outline: var(--xh-ring-width) solid var(--xh-ring-focus);
139
+ outline-offset: var(--xh-ring-offset);
140
+ }
141
+
142
+ [data-scope='image-cropper'][data-part='crop-handle'][data-resizing] {
143
+ background: var(--xh-image-cropper-handle-bg-resizing, var(--xh-bg-brand));
144
+ }
145
+
146
+ /* —— 八个方位:落点 + 光标 —— */
147
+ [data-scope='image-cropper'][data-part='crop-handle'][data-position='nw'] {
148
+ top: 0;
149
+ /* stylelint-disable-next-line property-disallowed-list -- 裁切框的坐标是图片像素,落点跟着物理方向走 */
150
+ left: 0;
151
+ cursor: nwse-resize;
152
+ }
153
+
154
+ [data-scope='image-cropper'][data-part='crop-handle'][data-position='n'] {
155
+ top: 0;
156
+ /* stylelint-disable-next-line property-disallowed-list -- 同上 */
157
+ left: 50%;
158
+ cursor: ns-resize;
159
+ }
160
+
161
+ [data-scope='image-cropper'][data-part='crop-handle'][data-position='ne'] {
162
+ top: 0;
163
+ /* stylelint-disable-next-line property-disallowed-list -- 同上 */
164
+ left: 100%;
165
+ cursor: nesw-resize;
166
+ }
167
+
168
+ [data-scope='image-cropper'][data-part='crop-handle'][data-position='e'] {
169
+ top: 50%;
170
+ /* stylelint-disable-next-line property-disallowed-list -- 同上 */
171
+ left: 100%;
172
+ cursor: ew-resize;
173
+ }
174
+
175
+ [data-scope='image-cropper'][data-part='crop-handle'][data-position='se'] {
176
+ top: 100%;
177
+ /* stylelint-disable-next-line property-disallowed-list -- 同上 */
178
+ left: 100%;
179
+ cursor: nwse-resize;
180
+ }
181
+
182
+ [data-scope='image-cropper'][data-part='crop-handle'][data-position='s'] {
183
+ top: 100%;
184
+ /* stylelint-disable-next-line property-disallowed-list -- 同上 */
185
+ left: 50%;
186
+ cursor: ns-resize;
187
+ }
188
+
189
+ [data-scope='image-cropper'][data-part='crop-handle'][data-position='sw'] {
190
+ top: 100%;
191
+ /* stylelint-disable-next-line property-disallowed-list -- 同上 */
192
+ left: 0;
193
+ cursor: nesw-resize;
194
+ }
195
+
196
+ [data-scope='image-cropper'][data-part='crop-handle'][data-position='w'] {
197
+ top: 50%;
198
+ /* stylelint-disable-next-line property-disallowed-list -- 同上 */
199
+ left: 0;
200
+ cursor: ew-resize;
201
+ }
202
+
203
+ /* 只读与禁用的光标排在八个方位之后:那八条每条都写了 cursor,特异性又完全相同,
204
+ 排在前面会被逐条盖掉,整条规则等于没写。 */
205
+ [data-scope='image-cropper'][data-part='crop-handle'][data-readonly] {
206
+ cursor: default;
207
+ }
208
+
209
+ [data-scope='image-cropper'][data-part='crop-handle'][data-disabled] {
210
+ cursor: not-allowed;
211
+ }
212
+
213
+ /* 禁用的整体压暗只做一次,落在最外层:视口、裁切框、把手各自再叠一层会越叠越黑。
214
+ 这里只用 opacity、不换前景色:框里是图像,没有文字可换。 */
215
+ [data-scope='image-cropper'][data-part='root'][data-disabled] {
216
+ cursor: not-allowed;
217
+ opacity: var(--xh-state-disabled-opacity);
218
+ }
219
+
220
+ /* hidden-input 是 type=hidden 的原生输入,浏览器本就不渲染它,
221
+ 这里一条声明都不给:给了 display / 尺寸反而会把它拽回布局里撑出一块空白。 */
222
+
223
+ /* display 会盖掉 UA 的 [hidden]{display:none},凡是被赋了 display 的角色节点都要显式让位。
224
+ 这条必须排在全部 display 声明之后。 */
225
+ [data-scope='image-cropper'][data-part='root'][hidden],
226
+ [data-scope='image-cropper'][data-part='viewport'][hidden],
227
+ [data-scope='image-cropper'][data-part='image'][hidden],
228
+ [data-scope='image-cropper'][data-part='crop-handle'][hidden] {
229
+ display: none;
230
+ }
231
+ }
@@ -1,8 +1,37 @@
1
+ @layer xihan.motion {
2
+ /* 动画名的查找只认「文档里有没有这个名字」:引用别处文件里的名字,那份文件不一定在场,
3
+ 动画会静默地整个不跑。每份皮肤都能单独引入,所以它用到的关键帧都写在自己身上。 */
4
+ @keyframes xh-fade-in {
5
+ from {
6
+ opacity: 0;
7
+ }
8
+
9
+ to {
10
+ opacity: 1;
11
+ }
12
+ }
13
+
14
+ @keyframes xh-fade-out {
15
+ from {
16
+ opacity: 1;
17
+ }
18
+
19
+ to {
20
+ opacity: 0;
21
+ }
22
+ }
23
+ }
24
+
1
25
  @layer xihan.components {
26
+ /* 皮肤在场的标记:开发模式下按 scope 探一次,取不到就说明这份皮肤没被引入。 */
27
+ [data-scope='image-viewer'] {
28
+ --xh-image-viewer-skin: 1;
29
+ }
30
+
2
31
  [data-scope='image-viewer'][data-part='backdrop'] {
3
32
  position: fixed;
4
33
  inset: 0;
5
- z-index: var(--xh-layer-modal);
34
+ z-index: var(--xh-image-viewer-backdrop-layer, var(--xh-layer-modal));
6
35
  /* 看片要压住页面底色,遮罩比普通对话框更深 */
7
36
  background: var(--xh-image-viewer-backdrop-bg, color-mix(in oklab, var(--xh-color-neutral-950) 78%, transparent));
8
37
  }
@@ -12,19 +41,22 @@
12
41
  }
13
42
 
14
43
  [data-scope='image-viewer'][data-part='backdrop'][data-state='closed'] {
15
- animation: xh-fade-out var(--xh-motion-duration-exit) var(--xh-motion-ease-exit);
44
+ animation: xh-fade-out var(--xh-motion-duration-exit) var(--xh-motion-ease-exit) forwards;
16
45
  }
17
46
 
18
47
  [data-scope='image-viewer'][data-part='positioner'] {
19
48
  position: fixed;
20
49
  inset: 0;
21
- z-index: var(--xh-layer-modal);
50
+ z-index: var(--xh-image-viewer-layer, var(--xh-layer-modal));
22
51
  display: flex;
23
52
  align-items: center;
24
53
  justify-content: center;
25
54
  }
26
55
 
27
56
  [data-scope='image-viewer'][data-part='content'] {
57
+ /* 作者塞进指示符槽的图标跟着字号走,与皮肤画的兜底字形同一把尺 */
58
+ --xh-icon-size: var(--xh-image-viewer-icon-size, var(--xh-glyph-size-text));
59
+
28
60
  position: relative;
29
61
  inline-size: 100%;
30
62
  block-size: 100%;
@@ -41,11 +73,7 @@
41
73
  }
42
74
 
43
75
  [data-scope='image-viewer'][data-part='content'][data-state='closed'] {
44
- animation: xh-fade-out var(--xh-motion-duration-exit) var(--xh-motion-ease-exit);
45
- }
46
-
47
- [data-scope='image-viewer'][data-part='content'][hidden] {
48
- display: none;
76
+ animation: xh-fade-out var(--xh-motion-duration-exit) var(--xh-motion-ease-exit) forwards;
49
77
  }
50
78
 
51
79
  [data-scope='image-viewer'][data-part='viewport'] {
@@ -69,7 +97,7 @@
69
97
  max-block-size: 85%;
70
98
  user-select: none;
71
99
  /* 变换是内联 style 写的,只给缩放旋转一点缓动;拖拽平移要跟手,不缓 */
72
- transition: transform var(--xh-duration-fast) var(--xh-ease-out);
100
+ transition: transform var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
73
101
  }
74
102
 
75
103
  [data-scope='image-viewer'][data-part='image'][data-panning] {
@@ -80,8 +108,7 @@
80
108
  [data-scope='image-viewer'][data-part='toolbar'],
81
109
  [data-scope='image-viewer'][data-part='counter'],
82
110
  [data-scope='image-viewer'][data-part='prev-trigger'],
83
- [data-scope='image-viewer'][data-part='next-trigger'],
84
- [data-scope='image-viewer'][data-part='close-trigger'] {
111
+ [data-scope='image-viewer'][data-part='next-trigger'] {
85
112
  position: absolute;
86
113
  z-index: 1;
87
114
  background: var(--xh-image-viewer-chrome-bg, color-mix(in oklab, var(--xh-color-neutral-950) 55%, transparent));
@@ -121,12 +148,11 @@
121
148
  inset-inline-start: 50%;
122
149
  translate: -50% 0;
123
150
  padding: var(--xh-space-1) var(--xh-space-3);
124
- font-size: var(--xh-font-size-sm);
151
+ font-size: var(--xh-text-secondary-size);
125
152
  }
126
153
 
127
154
  [data-scope='image-viewer'][data-part='prev-trigger'],
128
- [data-scope='image-viewer'][data-part='next-trigger'],
129
- [data-scope='image-viewer'][data-part='close-trigger'] {
155
+ [data-scope='image-viewer'][data-part='next-trigger'] {
130
156
  display: inline-flex;
131
157
  align-items: center;
132
158
  justify-content: center;
@@ -148,9 +174,39 @@
148
174
  translate: 0 -50%;
149
175
  }
150
176
 
177
+ /* 右上角的关闭钮:与翻页钮同底同高,但圆角走控件档,按下有缩放 */
151
178
  [data-scope='image-viewer'][data-part='close-trigger'] {
179
+ position: absolute;
180
+ z-index: 1;
152
181
  inset-block-start: var(--xh-space-6);
153
182
  inset-inline-end: var(--xh-space-6);
183
+ display: inline-flex;
184
+ align-items: center;
185
+ justify-content: center;
186
+ inline-size: var(--xh-image-viewer-close-size, var(--xh-control-h-lg));
187
+ block-size: var(--xh-image-viewer-close-size, var(--xh-control-h-lg));
188
+ border: none;
189
+ border-radius: var(--xh-image-viewer-close-radius, var(--xh-shape-control));
190
+ background: var(--xh-image-viewer-chrome-bg, color-mix(in oklab, var(--xh-color-neutral-950) 55%, transparent));
191
+ color: inherit;
192
+ font-size: var(--xh-glyph-size-md);
193
+ cursor: pointer;
194
+ transition:
195
+ background var(--xh-motion-duration-micro) var(--xh-motion-ease-enter),
196
+ scale var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
197
+ }
198
+
199
+ [data-scope='image-viewer'][data-part='close-trigger'][hidden] {
200
+ display: none;
201
+ }
202
+
203
+ [data-scope='image-viewer'][data-part='close-trigger']:hover:not(:disabled) {
204
+ background: var(--xh-image-viewer-close-bg-hover, color-mix(in oklab, var(--xh-color-neutral-950) 70%, transparent));
205
+ }
206
+
207
+ [data-scope='image-viewer'][data-part='close-trigger']:active {
208
+ background: var(--xh-image-viewer-close-bg-active, color-mix(in oklab, var(--xh-color-neutral-950) 80%, transparent));
209
+ scale: var(--xh-motion-scale-press);
154
210
  }
155
211
 
156
212
  [data-scope='image-viewer'] button[data-part]:focus-visible {
@@ -163,14 +219,103 @@
163
219
  cursor: not-allowed;
164
220
  }
165
221
 
166
- @media (prefers-reduced-motion: reduce) {
167
- [data-scope='image-viewer'][data-part='backdrop'],
168
- [data-scope='image-viewer'][data-part='content'] {
169
- animation-duration: 0.01ms;
170
- }
222
+ /* 没写内容就由皮肤画兜底字形;作者往部件里塞了自己的图形,这几条即不命中 */
223
+ [data-scope='image-viewer'][data-part='zoom-in-trigger']:empty::before {
224
+ content: '';
225
+ display: inline-block;
226
+ inline-size: var(--xh-icon-size, var(--xh-glyph-size-text));
227
+ block-size: var(--xh-icon-size, var(--xh-glyph-size-text));
228
+ vertical-align: var(--xh-glyph-baseline-shift);
229
+ background-color: currentColor;
230
+ -webkit-mask: var(--xh-glyph-mark-zoom-in) center / contain no-repeat;
231
+ mask: var(--xh-glyph-mark-zoom-in) center / contain no-repeat;
232
+ }
171
233
 
172
- [data-scope='image-viewer'][data-part='image'] {
173
- transition: none;
174
- }
234
+ [data-scope='image-viewer'][data-part='zoom-out-trigger']:empty::before {
235
+ content: '';
236
+ display: inline-block;
237
+ inline-size: var(--xh-icon-size, var(--xh-glyph-size-text));
238
+ block-size: var(--xh-icon-size, var(--xh-glyph-size-text));
239
+ vertical-align: var(--xh-glyph-baseline-shift);
240
+ background-color: currentColor;
241
+ -webkit-mask: var(--xh-glyph-mark-zoom-out) center / contain no-repeat;
242
+ mask: var(--xh-glyph-mark-zoom-out) center / contain no-repeat;
243
+ }
244
+
245
+ [data-scope='image-viewer'][data-part='rotate-left-trigger']:empty::before {
246
+ content: '';
247
+ display: inline-block;
248
+ inline-size: var(--xh-icon-size, var(--xh-glyph-size-text));
249
+ block-size: var(--xh-icon-size, var(--xh-glyph-size-text));
250
+ vertical-align: var(--xh-glyph-baseline-shift);
251
+ background-color: currentColor;
252
+ -webkit-mask: var(--xh-glyph-mark-rotate-left) center / contain no-repeat;
253
+ mask: var(--xh-glyph-mark-rotate-left) center / contain no-repeat;
254
+ }
255
+
256
+ [data-scope='image-viewer'][data-part='rotate-right-trigger']:empty::before {
257
+ content: '';
258
+ display: inline-block;
259
+ inline-size: var(--xh-icon-size, var(--xh-glyph-size-text));
260
+ block-size: var(--xh-icon-size, var(--xh-glyph-size-text));
261
+ vertical-align: var(--xh-glyph-baseline-shift);
262
+ background-color: currentColor;
263
+ -webkit-mask: var(--xh-glyph-mark-rotate-right) center / contain no-repeat;
264
+ mask: var(--xh-glyph-mark-rotate-right) center / contain no-repeat;
265
+ }
266
+
267
+ [data-scope='image-viewer'][data-part='flip-horizontal-trigger']:empty::before {
268
+ content: '';
269
+ display: inline-block;
270
+ inline-size: var(--xh-icon-size, var(--xh-glyph-size-text));
271
+ block-size: var(--xh-icon-size, var(--xh-glyph-size-text));
272
+ vertical-align: var(--xh-glyph-baseline-shift);
273
+ background-color: currentColor;
274
+ -webkit-mask: var(--xh-glyph-mark-flip-horizontal) center / contain no-repeat;
275
+ mask: var(--xh-glyph-mark-flip-horizontal) center / contain no-repeat;
276
+ }
277
+
278
+ [data-scope='image-viewer'][data-part='flip-vertical-trigger']:empty::before {
279
+ content: '';
280
+ display: inline-block;
281
+ inline-size: var(--xh-icon-size, var(--xh-glyph-size-text));
282
+ block-size: var(--xh-icon-size, var(--xh-glyph-size-text));
283
+ vertical-align: var(--xh-glyph-baseline-shift);
284
+ background-color: currentColor;
285
+ -webkit-mask: var(--xh-glyph-mark-flip-vertical) center / contain no-repeat;
286
+ mask: var(--xh-glyph-mark-flip-vertical) center / contain no-repeat;
287
+ }
288
+
289
+ [data-scope='image-viewer'][data-part='prev-trigger']:empty::before {
290
+ content: '';
291
+ display: inline-block;
292
+ inline-size: var(--xh-icon-size, var(--xh-glyph-size-text));
293
+ block-size: var(--xh-icon-size, var(--xh-glyph-size-text));
294
+ vertical-align: var(--xh-glyph-baseline-shift);
295
+ background-color: currentColor;
296
+ -webkit-mask: var(--xh-glyph-mark-chevron-left) center / contain no-repeat;
297
+ mask: var(--xh-glyph-mark-chevron-left) center / contain no-repeat;
298
+ }
299
+
300
+ [data-scope='image-viewer'][data-part='next-trigger']:empty::before {
301
+ content: '';
302
+ display: inline-block;
303
+ inline-size: var(--xh-icon-size, var(--xh-glyph-size-text));
304
+ block-size: var(--xh-icon-size, var(--xh-glyph-size-text));
305
+ vertical-align: var(--xh-glyph-baseline-shift);
306
+ background-color: currentColor;
307
+ -webkit-mask: var(--xh-glyph-mark-chevron-right) center / contain no-repeat;
308
+ mask: var(--xh-glyph-mark-chevron-right) center / contain no-repeat;
309
+ }
310
+
311
+ [data-scope='image-viewer'][data-part='close-trigger']:empty::before {
312
+ content: '';
313
+ display: inline-block;
314
+ inline-size: var(--xh-icon-size, var(--xh-glyph-size-text));
315
+ block-size: var(--xh-icon-size, var(--xh-glyph-size-text));
316
+ vertical-align: var(--xh-glyph-baseline-shift);
317
+ background-color: currentColor;
318
+ -webkit-mask: var(--xh-glyph-mark-close) center / contain no-repeat;
319
+ mask: var(--xh-glyph-mark-close) center / contain no-repeat;
175
320
  }
176
321
  }