@xihan-ui/styles 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/CHANGELOG.md +5146 -6
  2. package/README.md +22 -2
  3. package/component-tokens.d.ts +4212 -0
  4. package/component-tokens.js +2 -0
  5. package/components.tokens.json +80116 -0
  6. package/css/accordion.css +131 -75
  7. package/css/affix.css +16 -1
  8. package/css/alert.css +114 -67
  9. package/css/anchor.css +99 -27
  10. package/css/approval.css +461 -0
  11. package/css/avatar-group.css +14 -6
  12. package/css/avatar.css +33 -9
  13. package/css/back-top.css +103 -48
  14. package/css/badge.css +42 -14
  15. package/css/bar-code.css +62 -0
  16. package/css/breadcrumb.css +40 -21
  17. package/css/button-group.css +146 -24
  18. package/css/button.css +116 -99
  19. package/css/calendar-picker.css +448 -0
  20. package/css/calendar-range-picker.css +517 -0
  21. package/css/card.css +54 -77
  22. package/css/carousel.css +412 -68
  23. package/css/cascader.css +429 -246
  24. package/css/checkbox-group.css +225 -77
  25. package/css/checkbox.css +174 -37
  26. package/css/clipboard.css +266 -47
  27. package/css/code-view.css +279 -0
  28. package/css/collapsible.css +108 -63
  29. package/css/color-field.css +158 -0
  30. package/css/color-picker.css +172 -184
  31. package/css/color-slider.css +274 -0
  32. package/css/color-swatch-picker.css +243 -0
  33. package/css/color-swatch.css +35 -0
  34. package/css/combobox.css +267 -273
  35. package/css/command.css +399 -0
  36. package/css/context-menu.css +183 -108
  37. package/css/date-field.css +94 -121
  38. package/css/date-picker.css +486 -249
  39. package/css/date-range-picker.css +559 -0
  40. package/css/description.css +51 -0
  41. package/css/descriptions.css +133 -87
  42. package/css/dialog.css +247 -68
  43. package/css/diff-view.css +428 -0
  44. package/css/download-trigger.css +155 -46
  45. package/css/drawer.css +194 -111
  46. package/css/editable.css +172 -152
  47. package/css/empty-state.css +80 -11
  48. package/css/field-array.css +190 -0
  49. package/css/field.css +84 -31
  50. package/css/fieldset.css +51 -18
  51. package/css/file-upload.css +134 -36
  52. package/css/flex.css +20 -1
  53. package/css/float-button.css +238 -57
  54. package/css/floating-panel.css +173 -92
  55. package/css/focus.css +46 -0
  56. package/css/forced-colors.css +93 -0
  57. package/css/form.css +254 -77
  58. package/css/gradient-text.css +34 -10
  59. package/css/grid.css +516 -5
  60. package/css/heatmap.css +87 -29
  61. package/css/highlight.css +20 -5
  62. package/css/hover-card.css +97 -47
  63. package/css/icon-wrapper.css +15 -3
  64. package/css/icon.css +74 -8
  65. package/css/image-cropper.css +143 -29
  66. package/css/image-viewer.css +150 -89
  67. package/css/image.css +30 -1
  68. package/css/infinite-scroll.css +51 -1
  69. package/css/input-group.css +277 -0
  70. package/css/json-viewer.css +102 -45
  71. package/css/kbd.css +72 -0
  72. package/css/label.css +78 -0
  73. package/css/layers.css +5 -0
  74. package/css/layout.css +175 -31
  75. package/css/list.css +24 -3
  76. package/css/listbox.css +208 -67
  77. package/css/loading-bar.css +52 -3
  78. package/css/log.css +144 -9
  79. package/css/markdown-stream.css +149 -0
  80. package/css/marquee.css +27 -1
  81. package/css/masonry.css +7 -0
  82. package/css/matrix-code.css +67 -0
  83. package/css/mention.css +234 -152
  84. package/css/menu.css +180 -83
  85. package/css/menubar.css +204 -136
  86. package/css/message-feed.css +197 -0
  87. package/css/navigation-menu.css +144 -90
  88. package/css/notification.css +178 -100
  89. package/css/number-animation.css +7 -0
  90. package/css/number-field.css +120 -277
  91. package/css/overlay-arrow.css +12 -7
  92. package/css/page-header.css +99 -14
  93. package/css/pagination.css +175 -135
  94. package/css/password-input.css +222 -219
  95. package/css/pin-input.css +101 -67
  96. package/css/pointer.css +36 -0
  97. package/css/popconfirm.css +185 -94
  98. package/css/popover.css +121 -77
  99. package/css/progress.css +60 -12
  100. package/css/prompt-input.css +224 -0
  101. package/css/question-flow.css +513 -0
  102. package/css/radio-group.css +147 -30
  103. package/css/rating.css +193 -43
  104. package/css/reasoning.css +336 -0
  105. package/css/reset.css +84 -6
  106. package/css/resizable.css +237 -0
  107. package/css/scroll-area.css +68 -7
  108. package/css/scrollbar.css +47 -13
  109. package/css/segmented.css +94 -29
  110. package/css/select.css +288 -274
  111. package/css/separator.css +101 -1
  112. package/css/side-nav.css +133 -75
  113. package/css/signature-pad.css +74 -54
  114. package/css/skeleton.css +84 -11
  115. package/css/slider.css +132 -39
  116. package/css/sortable.css +198 -0
  117. package/css/spinner.css +126 -12
  118. package/css/splitter.css +29 -8
  119. package/css/statistic.css +55 -0
  120. package/css/steps.css +206 -72
  121. package/css/switch.css +268 -36
  122. package/css/table.css +695 -118
  123. package/css/tabs.css +604 -42
  124. package/css/tag-group.css +219 -0
  125. package/css/tag.css +118 -28
  126. package/css/tags-input.css +156 -274
  127. package/css/text-field.css +101 -249
  128. package/css/time-field.css +100 -128
  129. package/css/time-picker.css +323 -252
  130. package/css/time-range-picker.css +719 -0
  131. package/css/timeline.css +202 -68
  132. package/css/timer.css +56 -52
  133. package/css/timestamp.css +34 -0
  134. package/css/toast.css +373 -144
  135. package/css/toggle-group.css +309 -76
  136. package/css/toggle.css +116 -85
  137. package/css/tone.css +47 -0
  138. package/css/tool-call.css +421 -0
  139. package/css/toolbar.css +181 -33
  140. package/css/tooltip.css +107 -36
  141. package/css/tour.css +183 -152
  142. package/css/transfer.css +351 -138
  143. package/css/tree-select.css +323 -216
  144. package/css/tree.css +294 -62
  145. package/css/truncate.css +65 -0
  146. package/css/typography.css +228 -5
  147. package/css/undefined.css +10 -1
  148. package/css/virtualizer.css +9 -10
  149. package/css/visually-hidden.css +7 -0
  150. package/css/watermark.css +7 -0
  151. package/family/action-control.css +487 -0
  152. package/family/collection-item.css +721 -0
  153. package/family/field-chrome.css +298 -0
  154. package/family/motion.css +166 -0
  155. package/family/swatch.css +59 -0
  156. package/index.css +41705 -126
  157. package/index.unlayered.css +33692 -17339
  158. package/package.json +51 -17
  159. package/css/calendar.css +0 -367
  160. package/css/code-block.css +0 -91
  161. package/css/composer.css +0 -134
  162. package/css/countdown.css +0 -26
  163. package/css/dynamic-input.css +0 -220
  164. package/css/ellipsis.css +0 -52
  165. package/css/hotkeys.css +0 -90
  166. package/css/popselect.css +0 -391
  167. package/css/qr-code.css +0 -55
  168. package/css/result.css +0 -119
  169. package/css/space.css +0 -117
  170. package/css/thread.css +0 -120
  171. package/css/time.css +0 -25
package/css/cascader.css CHANGED
@@ -1,3 +1,16 @@
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
+ /* Cascader 独立皮肤同时传递引入字段视觉盒、字段内动作与浮层集合行;full bundle 递归去重。 */
7
+ @import '../family/field-chrome.css';
8
+ @import '../family/action-control.css';
9
+ @import '../family/collection-item.css';
10
+
11
+ /* 共享关键帧由 family/motion.css 提供,本皮肤 @import 它,单独引入仍成立。 */
12
+ @import '../family/motion.css';
13
+
1
14
  @layer xihan.components {
2
15
  /* 皮肤在场的标记:开发模式下按 scope 探一次,取不到就说明这份皮肤没被引入。 */
3
16
  [data-scope='cascader'] {
@@ -6,11 +19,20 @@
6
19
 
7
20
  /* 坐标由定位引擎写进 style(inset-inline-start / inset-block-start),这里只定位模式与层级 */
8
21
  [data-scope='cascader'][data-part='positioner'] {
22
+ /* 落定那一侧的可用宽度,由定位引擎经内联自定义属性写进来,列区据此夹住行内轴;
23
+ 引擎没算(尚未落位 / 无引擎 / 贴边归零)时退到视口宽 */
24
+ --xh-_cascader-available-w: 100vw;
25
+ /* 当前浮层只接受自己的落位方向,不叠加祖先的位移。 */
26
+ --xh-_overlay-enter-up: 0;
27
+ --xh-_overlay-enter-down: 0;
28
+ --xh-_overlay-enter-left: 0;
29
+ --xh-_overlay-enter-right: 0;
30
+
9
31
  /* 列区的自绘条浮在内容之上:轨道不上底色,实色轨道会在列区下缘糊出一条灰带 */
10
32
  --xh-scrollbar-track-bg: transparent;
11
33
 
12
34
  position: fixed;
13
- z-index: var(--xh-cascader-layer, var(--xh-layer-popover));
35
+ z-index: var(--xh-cascader-layer, var(--xh-_layer, var(--xh-layer-popover)));
14
36
  /* 空壳不吃指针,交互只发生在 content 里 */
15
37
  pointer-events: none;
16
38
  }
@@ -20,34 +42,42 @@
20
42
  visibility: hidden;
21
43
  }
22
44
 
45
+ /* 位移遵循实际落位的物理方向,不随书写方向翻转。 */
46
+ [data-scope='cascader'][data-part='positioner'][data-placement^='top'] {
47
+ --xh-_overlay-enter-down: 1;
48
+ }
49
+
50
+ [data-scope='cascader'][data-part='positioner'][data-placement^='bottom'] {
51
+ --xh-_overlay-enter-up: 1;
52
+ }
53
+
54
+ [data-scope='cascader'][data-part='positioner'][data-placement^='left'] {
55
+ --xh-_overlay-enter-right: 1;
56
+ }
57
+
58
+ [data-scope='cascader'][data-part='positioner'][data-placement^='right'] {
59
+ --xh-_overlay-enter-left: 1;
60
+ }
61
+
23
62
  /* 私有槽同时声明在根与 positioner 上:浮层被搬到 portal 落点,不是根的后代,继承不过去 */
24
63
  [data-scope='cascader']:is([data-part='root'], [data-part='positioner']) {
25
- /* 作者塞进指示符槽的图标跟着尺寸档走,与皮肤画的兜底字形同一把尺 */
64
+ /* 作者塞进触发器指示符槽的图标跟着尺寸档走,与皮肤画的兜底字形同一把尺;列里条目的由家族随
65
+ data-xh-collection-size 下发(§6.5)。级联选择器自绘的状态字形(标记盒里的勾与半选杠、分支箭头)
66
+ 不读这支:它们是指示符,走 --xh-control-indicator-* 一族——标记盒在自己身上把 --xh-icon-size 改接到
67
+ 盒的尺,分支箭头走 --xh-cascader-branch-arrow-size */
26
68
  --xh-icon-size: var(--xh-cascader-icon-size, var(--xh-glyph-size-md));
27
69
 
28
- /* 尺寸档只换这几个私有槽,盒与列里的条目都从这两处继承它们,换档不必给每个部件各写一条选择器 */
70
+ /* 尺寸档只换这几个私有槽,盒与列里的条目都从这两处继承它们,换档不必给每个部件各写一条选择器;
71
+ 形态三档由家族按 control 上的 data-variant 生成,浮层与列不跟着变,皮肤不再声明 */
29
72
  --xh-_cascader-h: var(--xh-control-h-md);
30
73
  --xh-_cascader-px: var(--xh-control-px-md);
31
74
  --xh-_cascader-row-py: var(--xh-list-option-py-md);
32
75
  --xh-_cascader-row-px: var(--xh-control-px-md);
33
76
  --xh-_cascader-gap: var(--xh-control-gap-md);
34
77
  --xh-_cascader-font-size: var(--xh-control-font-md);
35
- --xh-_cascader-label-font-size: var(--xh-control-font-md);
36
-
37
- /* 形态档只换触发框的描边与底色这三个槽,浮层与列不跟着变:
38
- 变的是「这是个输入位」的表达方式,不是弹出内容的表达方式 */
39
- --xh-_cascader-border: var(--xh-border-control);
40
- --xh-_cascader-border-hover: var(--xh-border-control-hover);
41
- --xh-_cascader-bg: var(--xh-bg-canvas);
42
-
43
- /* 选中条目的文字色随语气走,没写 data-tone 时退回品牌强调色 */
44
- --xh-_cascader-selected-fg: var(--xh-_tone-fg, var(--xh-fg-brand-strong));
45
-
46
- /* 展开路径的底色:随语气走淡底,没写 data-tone 时退回品牌淡底 */
47
- --xh-_cascader-active-bg: var(--xh-_tone-subtle, var(--xh-bg-brand-subtle));
48
78
 
49
- /* 聚焦环恒用固定环色;校验失败时换成告警环色 */
50
- --xh-_cascader-ring: var(--xh-ring-focus);
79
+ /* 强调色随语气走,只落在勾选标记上;没写 data-tone 时退回品牌色 */
80
+ --xh-_cascader-accent: var(--xh-_tone, var(--xh-fg-brand));
51
81
  }
52
82
 
53
83
  /* 标签一行、盒一行的两行堆叠 */
@@ -55,6 +85,12 @@
55
85
  display: inline-flex;
56
86
  flex-direction: column;
57
87
  gap: var(--xh-cascader-gap, var(--xh-space-1));
88
+
89
+ /* 不传尺寸时一律这么宽,宽度不随内容走;父容器给不出这么宽时整件跟着收,地板同步让步,不越出父容器。
90
+ 地板不高过缺省宽:使用者把缺省宽钉到底线以下时不必再放开底线 */
91
+ inline-size: var(--xh-cascader-control-w, var(--xh-control-w));
92
+ min-inline-size: min(var(--xh-cascader-control-w, var(--xh-control-w)), var(--xh-cascader-control-min-w, var(--xh-control-min-w)), 100%);
93
+ max-inline-size: 100%;
58
94
  }
59
95
 
60
96
  /* 只要声明了 display 就盖掉了 UA 的 [hidden]{display:none},作者手动收起时要自己还回去 */
@@ -62,29 +98,6 @@
62
98
  display: none;
63
99
  }
64
100
 
65
- /* —— 形态 —— */
66
- /* 形态只决定描边与底色怎么用;用哪族颜色由 data-tone 决定,没写 tone 就沿用原来的中性配色 */
67
- /* outline 就是缺省那一身:描边加画布底,写出来只为让三档并列可读 */
68
- [data-scope='cascader']:is([data-part='root'], [data-part='positioner'])[data-variant='outline'] {
69
- --xh-_cascader-border: var(--xh-border-control);
70
- --xh-_cascader-border-hover: var(--xh-border-control-hover);
71
- --xh-_cascader-bg: var(--xh-bg-canvas);
72
- }
73
-
74
- /* subtle 是填充式:底色接过「这里能点」的表达,描边随之让位 */
75
- [data-scope='cascader']:is([data-part='root'], [data-part='positioner'])[data-variant='subtle'] {
76
- --xh-_cascader-border: transparent;
77
- --xh-_cascader-border-hover: var(--xh-_tone-border, var(--xh-border-control-hover));
78
- --xh-_cascader-bg: var(--xh-_tone-subtle, var(--xh-bg-subtle));
79
- }
80
-
81
- /* 无框:底与常态描边透明,悬停浮出描边,聚焦由焦点环表达 */
82
- [data-scope='cascader']:is([data-part='root'], [data-part='positioner'])[data-variant='ghost'] {
83
- --xh-_cascader-border: transparent;
84
- --xh-_cascader-border-hover: var(--xh-_tone-border, var(--xh-border-control-hover));
85
- --xh-_cascader-bg: transparent;
86
- }
87
-
88
101
  /* —— 尺寸 —— */
89
102
  /* 缺省档写在上面的基础规则里 */
90
103
  [data-scope='cascader']:is([data-part='root'], [data-part='positioner'])[data-size='sm'] {
@@ -95,7 +108,6 @@
95
108
  --xh-_cascader-row-px: var(--xh-control-px-sm);
96
109
  --xh-_cascader-gap: var(--xh-control-gap-sm);
97
110
  --xh-_cascader-font-size: var(--xh-control-font-sm);
98
- --xh-_cascader-label-font-size: var(--xh-control-font-sm);
99
111
  }
100
112
 
101
113
  [data-scope='cascader']:is([data-part='root'], [data-part='positioner'])[data-size='lg'] {
@@ -106,71 +118,50 @@
106
118
  --xh-_cascader-row-px: var(--xh-control-px-lg);
107
119
  --xh-_cascader-gap: var(--xh-control-gap-lg);
108
120
  --xh-_cascader-font-size: var(--xh-control-font-lg);
109
- --xh-_cascader-label-font-size: var(--xh-control-font-lg);
110
121
  }
111
122
 
112
123
  [data-scope='cascader'][data-part='label'] {
113
124
  color: var(--xh-cascader-label-fg, var(--xh-fg-default));
114
- font-size: var(--xh-cascader-label-font-size, var(--xh-_cascader-label-font-size));
125
+ font-size: var(--xh-cascader-label-font-size, var(--xh-text-label-size));
115
126
  font-weight: var(--xh-cascader-label-font-weight, var(--xh-text-label-weight));
116
127
  line-height: var(--xh-leading-none);
117
128
  }
118
129
 
119
130
  /* 整个控件禁用会把 data-disabled 一路打到 label / value-text / indicator 上,各处观感保持一致 */
120
131
  [data-scope='cascader'][data-part='label'][data-disabled] {
121
- color: var(--xh-fg-subtle);
132
+ color: var(--xh-cascader-label-fg-disabled, var(--xh-fg-subtle));
122
133
  }
123
134
 
124
- /* 盒:描边、底色、控件高度与行内内衬都落在这一层,触发按钮与清空按钮在里面并排 */
135
+ /* 盒:描边、底色、圆角、控件高度、行内内衬、聚焦环、五态与悬停由 Field Chrome 家族画在这一层,
136
+ 触发按钮与清空按钮在里面并排;Tab 停靠点是盒里的 trigger(展开后才移进列里),环由家族的
137
+ :focus-within 画在盒上。组件只映射既有公开槽(shadow 缺省 none:字段静息描边式、无影);盒里是按钮 */
125
138
  [data-scope='cascader'][data-part='control'] {
126
- display: inline-flex;
127
- align-items: center;
128
- gap: var(--xh-cascader-control-gap, var(--xh-_cascader-gap));
129
- min-inline-size: var(--xh-cascader-control-min-w, var(--xh-control-min-w));
130
- block-size: var(--xh-cascader-control-h, var(--xh-_cascader-h));
131
- padding-inline: var(--xh-cascader-control-px, var(--xh-_cascader-px));
132
- border: var(--xh-stroke-thin) solid var(--xh-cascader-control-border, var(--xh-_cascader-border));
133
- border-radius: var(--xh-cascader-control-radius, var(--xh-shape-control));
134
- background: var(--xh-cascader-control-bg, var(--xh-_cascader-bg));
135
- color: var(--xh-cascader-control-fg, var(--xh-fg-default));
136
- cursor: pointer;
137
- transition: border-color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
139
+ --xh-field-control-height: var(--xh-cascader-control-h, var(--xh-_cascader-h));
140
+ --xh-field-control-padding-inline: var(--xh-cascader-control-px, var(--xh-_cascader-px));
141
+ --xh-field-control-gap: var(--xh-cascader-control-gap, var(--xh-_cascader-gap));
142
+ --xh-field-control-min-inline-size: var(--xh-cascader-control-min-w, var(--xh-control-min-w));
143
+ --xh-field-control-radius: var(--xh-cascader-control-radius, var(--xh-shape-control));
144
+ --xh-field-bg-rest: var(--xh-cascader-control-bg, var(--xh-_field-variant-bg-rest));
145
+ --xh-field-bg-hover: var(--xh-cascader-control-bg-hover, var(--xh-_field-variant-bg-hover));
146
+ --xh-field-bg-read-only: var(--xh-cascader-control-bg-readonly, var(--xh-_field-variant-bg-read-only));
147
+ --xh-field-bg-disabled: var(--xh-cascader-control-bg-disabled, var(--xh-_field-variant-bg-disabled));
148
+ --xh-field-fg-rest: var(--xh-cascader-control-fg, var(--xh-fg-default));
149
+ --xh-field-border-rest: var(--xh-cascader-control-border, var(--xh-_field-variant-border-rest));
150
+ --xh-field-border-hover: var(--xh-cascader-control-border-hover, var(--xh-_field-variant-border-hover));
151
+ --xh-field-border-focus: var(--xh-cascader-control-border-focus, var(--xh-_field-variant-border-focus));
152
+ --xh-field-border-invalid: var(--xh-cascader-control-border-invalid, var(--xh-_field-variant-border-invalid));
153
+ --xh-field-ring-focus: var(--xh-ring-focus);
154
+ --xh-field-ring-invalid: var(--xh-ring-invalid);
155
+ --xh-field-shadow-rest: var(--xh-cascader-control-shadow, none);
156
+ --xh-field-cursor-rest: pointer;
157
+ /* 家族 chrome 会重写 --xh-icon-size,使用者图标槽必须经桥接槽下发才能在盒内生效 */
158
+ --xh-field-glyph-size: var(--xh-cascader-icon-size, var(--xh-_field-size-glyph-size));
138
159
  }
139
160
 
140
161
  [data-scope='cascader'][data-part='control'][hidden] {
141
162
  display: none;
142
163
  }
143
164
 
144
- /* hover 只在中性态换描边:invalid 正拿边框颜色说事,被 hover 盖掉那条信息就没了 */
145
- [data-scope='cascader'][data-part='control']:hover:not([data-disabled], [data-invalid]) {
146
- border-color: var(--xh-cascader-control-border-hover, var(--xh-_cascader-border-hover));
147
- }
148
-
149
- /* Tab 停靠点是盒里的 trigger(展开后才移进列里),环画在盒上 */
150
- [data-scope='cascader'][data-part='control']:focus-within:not([data-disabled]) {
151
- border-color: var(--xh-cascader-control-border-focus, var(--xh-_tone, var(--xh-border-control-focus)));
152
- outline: var(--xh-ring-width) solid var(--xh-_cascader-ring);
153
- outline-offset: var(--xh-ring-offset);
154
- }
155
-
156
- [data-scope='cascader'][data-part='control'][data-invalid] {
157
- border-color: var(--xh-cascader-control-border-invalid, var(--xh-border-invalid));
158
-
159
- /* 校验失败时环也跟着变红:两圈等宽相邻却一红一蓝,读的人不知道哪个才是当前状态 */
160
- --xh-_cascader-ring: var(--xh-ring-invalid);
161
- }
162
-
163
- /* 只读仍可聚焦、浮层仍展得开、列仍能浏览,所以不置灰,只把底色压下去表示「改不动」 */
164
- [data-scope='cascader'][data-part='control'][data-readonly] {
165
- background: var(--xh-cascader-control-bg-readonly, var(--xh-bg-subtle));
166
- }
167
-
168
- [data-scope='cascader'][data-part='control'][data-disabled] {
169
- background: var(--xh-cascader-control-bg-disabled, var(--xh-bg-subtle));
170
- color: var(--xh-fg-disabled);
171
- cursor: not-allowed;
172
- }
173
-
174
165
  /* 盒内那颗可聚焦的按钮:不画描边不铺底也不自带高度,只占满剩余宽度、排布回显与箭头 */
175
166
  [data-scope='cascader'][data-part='trigger'] {
176
167
  display: inline-flex;
@@ -197,7 +188,6 @@
197
188
  /* trigger 是单体控件,用原生 disabled(列里的集合条目一律 aria-disabled,写 :disabled 永不命中) */
198
189
  [data-scope='cascader'][data-part='trigger']:disabled {
199
190
  color: var(--xh-fg-disabled);
200
- cursor: not-allowed;
201
191
  }
202
192
 
203
193
  /* 路径连起来会很长,必须能截断;框宽由盒定,这里得允许自己被压缩 */
@@ -219,8 +209,9 @@
219
209
  }
220
210
 
221
211
  /* 有值时清空钮顶上来,箭头让位:两个图标并排堆着,用户分不清点哪个。
222
- 让位而不是禁用——禁用的图标还占着位置,看着像坏了 */
223
- [data-scope='cascader'][data-part='indicator'][data-clearable] {
212
+ 判据查的是盒里此刻真有一颗没收起的清空钮,不是「有没有值」——
213
+ 作者没摆清空钮时箭头照常留着,不然让了位没人接,指示符整个消失 */
214
+ [data-scope='cascader'][data-part='control']:has([data-part='clear-trigger']:not([hidden])) [data-part='indicator'] {
224
215
  display: none;
225
216
  }
226
217
 
@@ -230,7 +221,7 @@
230
221
  align-items: center;
231
222
  justify-content: center;
232
223
  color: var(--xh-cascader-indicator-fg, var(--xh-fg-muted));
233
- transition: rotate var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
224
+ transition: rotate var(--xh-motion-duration-micro) var(--xh-motion-ease-enter-strong);
234
225
  }
235
226
 
236
227
  [data-scope='cascader'][data-part='indicator'][hidden] {
@@ -257,63 +248,73 @@
257
248
  color: var(--xh-fg-disabled);
258
249
  }
259
250
 
260
- /* 盒内靠右的小方钮,不撑高那一行 */
251
+ /* 盒内靠右的小方钮,不撑高那一行:正方视觉盒、inset 圆角、悬停 100 / 按下 200 与 0.97 按压、粗指针热区与
252
+ :focus-visible 关环(盒的 :focus-within 已画了一圈)全由 Action Control 的 field-inset ghost 档给,
253
+ 组件只映射既有覆盖槽 */
261
254
  [data-scope='cascader'][data-part='clear-trigger'] {
262
- display: inline-flex;
263
- flex: none;
264
- align-items: center;
265
- justify-content: center;
266
- inline-size: var(--xh-cascader-action-size, var(--xh-control-action-size));
267
- block-size: var(--xh-cascader-action-size, var(--xh-control-action-size));
268
- padding: 0;
269
- border: 0;
270
- border-radius: var(--xh-cascader-action-radius, var(--xh-shape-control));
271
- background: var(--xh-cascader-action-bg, transparent);
272
- color: var(--xh-cascader-action-fg, var(--xh-fg-muted));
273
- font-family: inherit;
274
- font-size: var(--xh-cascader-action-font-size, var(--xh-text-secondary-size));
275
- line-height: var(--xh-leading-none);
276
- cursor: pointer;
277
- user-select: none;
278
- transition: background var(--xh-motion-duration-micro) var(--xh-motion-ease-enter), scale var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
279
- }
280
-
255
+ --xh-action-visual-size: var(--xh-cascader-action-size, var(--xh-_action-profile-visual-size));
256
+ --xh-action-min-inline-size: var(--xh-cascader-action-size, var(--xh-_action-profile-visual-size));
257
+ --xh-action-radius: var(--xh-cascader-action-radius, var(--xh-shape-inset));
258
+ /* 底色阶梯由 ghost 档给(透明 → 100 → 200),使用者槽只作覆盖入口 */
259
+ --xh-action-bg-rest: var(--xh-cascader-action-bg, var(--xh-_action-variant-bg-rest));
260
+ --xh-action-bg-hover: var(--xh-cascader-action-bg-hover, var(--xh-_action-variant-bg-hover));
261
+ --xh-action-bg-pressed: var(--xh-cascader-action-bg-active, var(--xh-_action-variant-bg-pressed));
262
+ --xh-action-fg-rest: var(--xh-cascader-action-fg, var(--xh-fg-muted));
263
+ --xh-action-fg-hover: var(--xh-cascader-action-fg-hover, var(--xh-fg-default));
264
+ --xh-action-border-rest: transparent;
265
+ --xh-action-highlight-rest: transparent;
266
+ --xh-action-shadow-rest: none;
267
+ --xh-action-shadow-hover: none;
268
+ --xh-action-shadow-pressed: none;
269
+ --xh-action-font-size: var(--xh-cascader-action-font-size, var(--xh-text-secondary-size));
270
+ /* 配方尚未把 profile 字形档灌进 --xh-icon-size,钮内字形先按 field-inset 档逐钮取值 */
271
+ --xh-icon-size: var(--xh-_action-profile-glyph-size);
272
+
273
+ border-width: 0;
274
+ }
275
+
276
+ /* 没值时连接层给的是 hidden 属性;家族那条 display 会把 UA 规则盖掉 */
281
277
  [data-scope='cascader'][data-part='clear-trigger'][hidden] {
282
278
  display: none;
283
279
  }
284
280
 
285
- [data-scope='cascader'][data-part='clear-trigger']:hover {
286
- background: var(--xh-cascader-action-bg-hover, var(--xh-bg-subtle-hover));
287
- color: var(--xh-cascader-action-fg-hover, var(--xh-fg-default));
288
- }
289
-
290
- [data-scope='cascader'][data-part='clear-trigger']:active {
291
- background: var(--xh-cascader-action-bg-active, var(--xh-bg-subtle-active));
292
- scale: var(--xh-motion-scale-press);
293
- }
294
-
295
- /* 清空按钮退出了 Tab 序列(tabindex=-1),正常操作走不到这里;
296
- 留着是给程序化聚焦兜底,落焦时得看得见 */
297
- [data-scope='cascader'][data-part='clear-trigger']:focus-visible {
298
- outline: var(--xh-ring-width) solid var(--xh-ring-focus);
299
- outline-offset: var(--xh-ring-offset);
300
- }
301
-
302
281
  /* 级联的骨架就在这一条上:列沿书写方向并排,一层一列。
303
282
  横向随列数增减,因此不设固定宽度,只给每列自己的宽度 */
304
283
  [data-scope='cascader'][data-part='content'] {
284
+ position: relative;
305
285
  display: flex;
306
286
  flex-direction: row;
307
287
  align-items: stretch;
308
- max-inline-size: var(--xh-cascader-content-max-w, var(--xh-overlay-max-w-xl));
309
- border: var(--xh-stroke-thin) solid var(--xh-cascader-content-border, var(--xh-border-default));
310
- border-radius: var(--xh-cascader-content-radius, var(--xh-shape-surface));
311
- background: var(--xh-cascader-content-bg, var(--xh-bg-surface));
312
- color: var(--xh-cascader-content-fg, var(--xh-fg-default));
313
- box-shadow: var(--xh-cascader-content-shadow, var(--xh-elevation-floating));
288
+ /* 静态档与落位后的可用宽度取小:列多起来时外框自己就宽过了可用区,
289
+ 盒内不产生溢出,下面那条 overflow-x 便一直没事可做,最右那一列落在屏外点不到 */
290
+ max-inline-size: min(var(--xh-cascader-content-max-w, var(--xh-overlay-max-w-xl)), var(--xh-_cascader-available-w));
291
+ border: var(--xh-stroke-thin) solid var(--xh-cascader-content-border, var(--xh-material-frosted-border));
292
+ border-radius: var(--xh-cascader-content-radius, var(--xh-shape-overlay));
293
+ background: var(--xh-cascader-content-bg, var(--xh-material-frosted-bg));
294
+ color: var(--xh-cascader-content-fg, var(--xh-material-frosted-fg));
295
+ box-shadow: var(--xh-cascader-content-shadow, var(--xh-material-frosted-shadow));
296
+ -webkit-backdrop-filter: var(--xh-cascader-content-backdrop, var(--xh-material-frosted-backdrop));
297
+ backdrop-filter: var(--xh-cascader-content-backdrop, var(--xh-material-frosted-backdrop));
314
298
  /* 列多到放不下时整体横向滚动;纵向溢出归每一列自己 */
315
299
  overflow-x: auto;
300
+ /* 浮层滚动面:滚到头不把页面一起带走 */
301
+ overscroll-behavior: contain;
316
302
  pointer-events: auto;
303
+ /* 各列与搜索列表的竖条贴在自己的盒子上、挂在这一层里(它已是定位盒):轨道不画底,
304
+ 条子随列一起横滚,列与列之间才不会各多出一道灰带 */
305
+ --xh-scrollbar-track-bg: transparent;
306
+ }
307
+
308
+ /* 唯一外壳的内侧顶光;列和状态部件不另画材质面。 */
309
+ [data-scope='cascader'][data-part='content']::before {
310
+ content: '';
311
+ position: absolute;
312
+ inset-block-start: 0;
313
+ inset-inline: 0;
314
+ block-size: var(--xh-stroke-thin);
315
+ border-radius: inherit;
316
+ background: var(--xh-cascader-content-highlight, var(--xh-material-frosted-highlight));
317
+ pointer-events: none;
317
318
  }
318
319
 
319
320
  /* 这里刻意没有 [data-part='content'][hidden] { display: none },与 dialog 一致:
@@ -328,13 +329,15 @@
328
329
  }
329
330
 
330
331
  [data-scope='cascader'][data-part='content'][data-state='open'] {
331
- animation: xh-pop-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter);
332
+ animation: xh-overlay-slide-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter-strong);
333
+ /* 只在开着的这一档挂:合成层随 data-state 一起收走,收起后不再占着 */
334
+ will-change: opacity, translate;
332
335
  }
333
336
 
334
337
  /* 退场:收起那一帧 hidden 由退场闸门押后,这里才播得出来。forwards 把末态钉住,
335
338
  免得动画结束到真收起之间闪一下原样 */
336
339
  [data-scope='cascader'][data-part='content'][data-state='closed'] {
337
- animation: xh-pop-out var(--xh-motion-duration-exit) var(--xh-motion-ease-exit) forwards;
340
+ animation: xh-overlay-slide-out var(--xh-motion-duration-exit) var(--xh-motion-ease-exit) forwards;
338
341
  }
339
342
 
340
343
  /* 一列:宽度由内容撑出、设下限防塌,高度定死——切换展开路径时浮层不上下跳动,
@@ -349,6 +352,8 @@
349
352
  padding-block: var(--xh-cascader-column-py, var(--xh-space-1));
350
353
  padding-inline: var(--xh-cascader-column-px, var(--xh-space-1));
351
354
  overflow-y: auto;
355
+ /* 浮层滚动面:滚到头不把页面一起带走 */
356
+ overscroll-behavior: contain;
352
357
  }
353
358
 
354
359
  /* 展开路径砍短了这一列就收起来。display 盖掉了 UA 的 [hidden]{display:none},
@@ -358,40 +363,100 @@
358
363
  }
359
364
 
360
365
  /* 分隔线画在列的起始缘,且第一列不画:这样列数增减时线永远只出现在列与列之间。
361
- 用逻辑属性,rtl 下自动跑到另一侧 */
362
- [data-scope='cascader'][data-part='column'] + [data-scope='cascader'][data-part='column'] {
363
- border-inline-start: var(--xh-stroke-thin) solid var(--xh-cascader-column-divider, var(--xh-border-subtle));
364
- }
365
-
366
- /* 焦点落在根列上只有一种情形:指针打开、无选中值、还没有任何条目当锚点。
367
- 那一刻它认领着 Tab 位,环画在列内沿;有锚点时环长在条目上,列不再画 */
368
- [data-scope='cascader'][data-part='column'][tabindex='0']:focus-visible {
369
- outline: var(--xh-ring-width) solid var(--xh-ring-focus);
370
- outline-offset: calc(-1 * var(--xh-ring-width));
366
+ 用逻辑属性,rtl 下自动跑到另一侧。每列后面紧跟着它自己的竖条节点,相邻选择器接不到下一列,
367
+ 按「前面有过列」取后续的列 */
368
+ [data-scope='cascader'][data-part='column'] ~ [data-scope='cascader'][data-part='column'] {
369
+ border-inline-start: var(--xh-stroke-thin) solid var(--xh-cascader-column-divider, var(--xh-material-frosted-separator));
371
370
  }
372
371
 
373
372
  [data-scope='cascader'][data-part='column']:not([tabindex='0']):focus-visible {
374
373
  outline: none;
375
374
  }
376
375
 
377
- [data-scope='cascader'][data-part='item'] {
376
+ [data-scope='cascader'][data-part='group'] {
378
377
  display: flex;
379
- align-items: center;
380
- gap: var(--xh-cascader-row-gap, var(--xh-_cascader-gap));
381
- max-inline-size: var(--xh-cascader-row-max-w, var(--xh-overlay-max-w));
382
- padding-block: var(--xh-cascader-row-py, var(--xh-_cascader-row-py));
383
- padding-inline: var(--xh-cascader-row-px, var(--xh-_cascader-row-px));
384
- border-radius: var(--xh-cascader-row-radius, var(--xh-shape-control));
385
- color: var(--xh-cascader-row-fg, var(--xh-fg-default));
386
- font-size: var(--xh-cascader-row-font-size, var(--xh-_cascader-font-size));
378
+ flex: none;
379
+ flex-direction: column;
380
+ gap: var(--xh-cascader-group-gap, var(--xh-list-option-gap));
381
+ }
382
+
383
+ [data-scope='cascader'][data-part='group'][hidden] {
384
+ display: none;
385
+ }
386
+
387
+ /* 组间留白只加在后继分组上:首组顶上已经有列的内边距,再加一层会显得头重 */
388
+ [data-scope='cascader'][data-part='group'] + [data-scope='cascader'][data-part='group'] {
389
+ margin-block-start: var(--xh-cascader-group-spacing, var(--xh-space-1_5));
390
+ }
391
+
392
+ /* 分组标题不是条目、进不了导航,画成小号次要文字,
393
+ 内缩与条目对齐(同一个 padding-inline),标题与条目文字才在一条竖线上 */
394
+ [data-scope='cascader'][data-part='group-label'] {
395
+ display: block;
396
+ padding-block: var(--xh-cascader-group-label-py, var(--xh-space-1));
397
+ padding-inline: var(--xh-cascader-group-label-px, var(--xh-_cascader-row-px));
398
+ color: var(--xh-cascader-group-label-fg, var(--xh-material-frosted-fg-muted));
399
+ font-size: var(--xh-cascader-group-label-font-size, var(--xh-text-caption-size));
400
+ font-weight: var(--xh-cascader-group-label-font-weight, var(--xh-font-weight-medium));
401
+ line-height: var(--xh-leading-none);
402
+ user-select: none;
403
+ }
404
+
405
+ [data-scope='cascader'][data-part='group-label'][hidden] {
406
+ display: none;
407
+ }
408
+
409
+ [data-scope='cascader'][data-part='group-label'][data-disabled] {
410
+ color: var(--xh-fg-disabled);
411
+ }
412
+
413
+ /* 列内条目:网格、尺寸档、悬停 / 高亮 100、按下 200、展开路径的中性面(与悬停同档)、禁用面与选中标记
414
+ 由 Collection Item 家族按 overlay 语境给(选中 = 透明底 + 行尾对号,正文保持 rest 色与常规字重),
415
+ 公开 Cascader 槽只映射到家族合同。data-highlighted 跟的是连接层记的焦点行(指针与键盘共用),
416
+ 环只在真正的键盘焦点上接回,见下 */
417
+ [data-scope='cascader'][data-part='item'] {
418
+ --xh-collection-block-padding: var(--xh-cascader-item-py, var(--xh-_cascader-row-py));
419
+ --xh-collection-inline-padding: var(--xh-cascader-item-px, var(--xh-_cascader-row-px));
420
+ --xh-collection-gap: var(--xh-cascader-item-gap, var(--xh-_cascader-gap));
421
+ --xh-collection-font-size: var(--xh-cascader-item-font-size, var(--xh-_cascader-font-size));
422
+ --xh-collection-radius: var(--xh-cascader-item-radius, var(--xh-shape-control));
423
+ --xh-collection-glyph-size: var(--xh-cascader-icon-size, var(--xh-_collection-glyph-size));
424
+ --xh-collection-fg-rest: var(--xh-cascader-item-fg, var(--xh-material-frosted-fg));
425
+ --xh-collection-fg-hover: var(--xh-cascader-item-fg, var(--xh-material-frosted-fg));
426
+ --xh-collection-fg-keyboard-highlight: var(--xh-cascader-item-fg, var(--xh-material-frosted-fg));
427
+ --xh-collection-fg-open-path: var(--xh-cascader-item-fg, var(--xh-material-frosted-fg));
428
+ --xh-collection-fg-selected: var(--xh-cascader-item-fg-selected, var(--xh-cascader-item-fg, var(--xh-material-frosted-fg)));
429
+ --xh-collection-fg-selected-highlight: var(--xh-cascader-item-fg-selected, var(--xh-cascader-item-fg, var(--xh-material-frosted-fg)));
430
+ --xh-collection-fg-selected-hover: var(--xh-cascader-item-fg-selected, var(--xh-cascader-item-fg, var(--xh-material-frosted-fg)));
431
+ --xh-collection-fg-selected-pressed: var(--xh-cascader-item-fg-selected, var(--xh-cascader-item-fg, var(--xh-material-frosted-fg)));
432
+ --xh-collection-fg-pressed: var(--xh-cascader-item-fg, var(--xh-material-frosted-fg));
433
+ --xh-collection-font-weight-open-path: var(--xh-cascader-item-active-font-weight, var(--xh-font-weight-regular));
434
+ --xh-collection-font-weight-selected: var(--xh-cascader-item-font-weight-selected, var(--xh-font-weight-regular));
435
+ --xh-collection-font-weight-selected-highlight: var(--xh-cascader-item-font-weight-selected, var(--xh-font-weight-regular));
436
+ --xh-collection-font-weight-selected-hover: var(--xh-cascader-item-font-weight-selected, var(--xh-font-weight-regular));
437
+ --xh-collection-font-weight-selected-pressed: var(--xh-cascader-item-font-weight-selected, var(--xh-font-weight-regular));
438
+ --xh-collection-bg-hover: var(--xh-cascader-item-bg-hover, var(--xh-bg-subtle));
439
+ --xh-collection-bg-keyboard-highlight: var(--xh-cascader-item-bg-hover, var(--xh-bg-subtle));
440
+ --xh-collection-bg-open-path: var(--xh-cascader-item-bg-active, var(--xh-bg-subtle));
441
+ --xh-collection-bg-selected-highlight: var(--xh-cascader-item-bg-hover, var(--xh-bg-subtle));
442
+ --xh-collection-bg-selected-hover: var(--xh-cascader-item-bg-hover, var(--xh-bg-subtle));
443
+ --xh-collection-bg-pressed: var(--xh-cascader-item-bg-pressed, var(--xh-bg-subtle-hover));
444
+ --xh-collection-bg-selected-pressed: var(--xh-cascader-item-bg-pressed, var(--xh-bg-subtle-hover));
445
+ --xh-collection-outline-keyboard-highlight: transparent;
446
+ --xh-collection-outline-selected-highlight: transparent;
447
+ --xh-collection-indicator-fg-rest: var(--xh-cascader-item-check-fg, var(--xh-cascader-item-indicator-fg, var(--xh-_cascader-accent)));
448
+ --xh-collection-indicator-fg-checked: var(--xh-cascader-item-check-fg, var(--xh-cascader-item-indicator-fg, var(--xh-_cascader-accent)));
449
+ --xh-collection-indicator-fg-selected: var(--xh-cascader-item-check-fg, var(--xh-cascader-item-indicator-fg, var(--xh-_cascader-accent)));
450
+ --xh-collection-indicator-fg-selected-hover: var(--xh-cascader-item-check-fg, var(--xh-cascader-item-indicator-fg, var(--xh-_cascader-accent)));
451
+ --xh-collection-indicator-fg-selected-highlight: var(--xh-cascader-item-check-fg, var(--xh-cascader-item-indicator-fg, var(--xh-_cascader-accent)));
452
+ --xh-collection-indicator-fg-selected-pressed: var(--xh-cascader-item-check-fg, var(--xh-cascader-item-indicator-fg, var(--xh-_cascader-accent)));
453
+ --xh-collection-indicator-fg-open-path: var(--xh-cascader-item-check-fg, var(--xh-cascader-item-indicator-fg, var(--xh-_cascader-accent)));
454
+
455
+ max-inline-size: var(--xh-cascader-item-max-w, var(--xh-overlay-max-w));
387
456
  /* 行高留出文字的上下伸展空间,行内那层 overflow:hidden 才不会把字削掉 */
388
- line-height: var(--xh-cascader-row-leading, var(--xh-leading-normal));
457
+ line-height: var(--xh-cascader-item-leading, var(--xh-leading-normal));
389
458
  white-space: nowrap;
390
- cursor: pointer;
391
459
  user-select: none;
392
- transition:
393
- background var(--xh-motion-duration-micro) var(--xh-motion-ease-enter),
394
- color var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
395
460
  }
396
461
 
397
462
  /* 条目常挂不卸载:不属于当前这一列的那些靠 hidden 收起。
@@ -400,80 +465,97 @@
400
465
  display: none;
401
466
  }
402
467
 
403
- /* 底色分两档、靠色相分家:悬停与键盘锚点是中性灰轻档,展开路径是品牌淡底加粗。
404
- hover 恒中性灰、不接语气槽——两档同色相的话路径就认不出来了。
405
- 悬停展开把路径挪走后留在原地的锚点降回轻档,每列至多一条路径高亮 */
406
- [data-scope='cascader'][data-part='item']:not([data-disabled]):is(:hover, [data-highlighted]) {
407
- background: var(--xh-cascader-row-bg-hover, var(--xh-bg-subtle));
468
+ /* 一列开幕:条目自上而下逐条淡入。列从 hidden 转回来、或换了展开路径把另一批条目放出来时,
469
+ 这段动画重新起跑。backwards 让每条在自己那份延迟里保持起始帧 */
470
+ [data-scope='cascader'][data-part='column'] [data-part='item'] {
471
+ animation: xh-fade-in var(--xh-motion-duration-enter) var(--xh-motion-ease-enter) backwards;
408
472
  }
409
473
 
410
- /* 必须排在轻档之后:锚点走到路径上时同特指度靠书写顺序压过轻档 */
411
- [data-scope='cascader'][data-part='item']:not([data-disabled])[data-in-path] {
412
- background: var(--xh-cascader-row-bg-active, var(--xh-_cascader-active-bg));
413
- font-weight: var(--xh-cascader-row-active-font-weight, var(--xh-font-weight-semibold));
474
+ /* 交错的间隔:第二条起逐级往后推,第六条起统一钉在第五级,条目再多也不会拖出一条越来越慢的尾巴。
475
+ 列里同时挂着别的父节点下的条目(收起态),计数把它们排掉,序号才对得上看得见的那几条 */
476
+ [data-scope='cascader'][data-part='column'] [data-part='item']:nth-child(2 of :not([hidden])) {
477
+ animation-delay: var(--xh-motion-stagger-step);
414
478
  }
415
479
 
416
- /* 选中只换文字色与字重,配合行尾的 标记;底色强档归展开路径、轻档归悬停,
417
- 三者因此能同时看见 */
418
- [data-scope='cascader']:is([data-part='item'], [data-part='search-item'])[data-state='checked'] {
419
- color: var(--xh-cascader-row-fg-selected, var(--xh-_cascader-selected-fg));
420
- font-weight: var(--xh-cascader-row-selected-font-weight, var(--xh-font-weight-medium));
480
+ [data-scope='cascader'][data-part='column'] [data-part='item']:nth-child(3 of :not([hidden])) {
481
+ animation-delay: calc(2 * var(--xh-motion-stagger-step));
421
482
  }
422
483
 
423
- /* 条目自己就是 option,环直接画在它身上;往内收,行是撑满列宽的,环画在外沿会被裁掉 */
424
- [data-scope='cascader'][data-part='item']:focus-visible {
425
- outline: var(--xh-ring-width) solid var(--xh-ring-focus);
426
- outline-offset: calc(-1 * var(--xh-ring-width));
484
+ [data-scope='cascader'][data-part='column'] [data-part='item']:nth-child(4 of :not([hidden])) {
485
+ animation-delay: calc(3 * var(--xh-motion-stagger-step));
427
486
  }
428
487
 
429
- /* 禁用走 aria-disabled 而非原生 disabled:条目仍要可聚焦(它还得当方向键的起点),
430
- 所以只能靠 data-disabled 表达,写 :disabled 永远不命中 */
431
- [data-scope='cascader'][data-part='item'][data-disabled] {
432
- color: var(--xh-fg-disabled);
433
- cursor: not-allowed;
488
+ [data-scope='cascader'][data-part='column'] [data-part='item']:nth-child(5 of :not([hidden])) {
489
+ animation-delay: calc(4 * var(--xh-motion-stagger-step));
490
+ }
491
+
492
+ [data-scope='cascader'][data-part='column'] [data-part='item']:nth-child(n + 6 of :not([hidden])) {
493
+ animation-delay: calc(5 * var(--xh-motion-stagger-step));
494
+ }
495
+
496
+ /* 键盘焦点出现时环才接回并立即到位,不压在背景过渡的中间帧上 */
497
+ [data-scope='cascader'][data-part='item']:focus-visible:not(:hover) {
498
+ --xh-collection-outline-keyboard-highlight: var(--xh-ring-focus);
499
+ --xh-collection-outline-selected-highlight: var(--xh-ring-focus);
500
+
501
+ transition: none;
434
502
  }
435
503
 
436
- /* 分支条目行尾画右向小箭头:不点开也看得出还有下一级。位置由 item-text 的 flex:1 顶到行尾。
504
+ /* 分支条目行尾画右向小箭头:不点开也看得出还有下一级。落在家族网格的 suffix 列、跨两行居中。
437
505
  与菜单族的子菜单箭头同走字形令牌——此前这里是「描全边框再抹掉两条、把剩下的直角旋 45 度」,
438
- 作者换掉标记时换不动它,RTL 还得再翻一次旋转角 */
506
+ 作者换掉标记时换不动它,RTL 还得再翻一次旋转角。
507
+ 箭头是指示符(§6.5):与同一行的标记盒同走 --xh-control-indicator-size,不读家族按档下发的
508
+ --xh-icon-size——按图标档取的 20px 会比 16px 的标记盒大一圈 */
439
509
  [data-scope='cascader'][data-part='item'][data-branch]::after {
440
510
  content: '';
441
- flex: none;
442
- inline-size: var(--xh-cascader-branch-arrow-size, var(--xh-icon-size, var(--xh-glyph-size-text)));
443
- block-size: var(--xh-cascader-branch-arrow-size, var(--xh-icon-size, var(--xh-glyph-size-text)));
511
+ grid-column: suffix;
512
+ grid-row: 1 / span 2;
513
+ inline-size: var(--xh-cascader-branch-arrow-size, var(--xh-control-indicator-size));
514
+ block-size: var(--xh-cascader-branch-arrow-size, var(--xh-control-indicator-size));
515
+ margin-inline-start: var(--xh-cascader-item-gap, var(--xh-_cascader-gap));
444
516
  background-color: var(--xh-cascader-branch-arrow-fg, var(--xh-fg-subtle));
445
517
  -webkit-mask: var(--xh-glyph-mark-chevron-right) center / contain no-repeat;
446
518
  mask: var(--xh-glyph-mark-chevron-right) center / contain no-repeat;
447
519
  }
448
520
 
449
521
  [data-scope='cascader'][data-part='item'][data-branch][data-disabled]::after {
450
- border-color: var(--xh-fg-disabled);
522
+ background-color: var(--xh-fg-disabled);
523
+ }
524
+
525
+ [data-scope='cascader'][data-part='item'][data-branch]:dir(rtl)::after {
526
+ -webkit-mask-image: var(--xh-glyph-mark-chevron-left);
527
+ mask-image: var(--xh-glyph-mark-chevron-left);
451
528
  }
452
529
 
530
+ /* 正文落在家族网格的 text 列;网格项默认最小尺寸是内容尺寸,不归零则长文本把行撑破、省略号轮不上 */
453
531
  [data-scope='cascader'][data-part='item-text'] {
454
- flex: 1;
455
532
  min-inline-size: 0;
456
533
  overflow: hidden;
457
534
  white-space: nowrap;
458
535
  text-overflow: ellipsis;
459
536
  }
460
537
 
461
- /* 勾选标记只在选中项上出现;未选中时占位不塌,切换选中不引起行宽抖动 */
538
+ /* 勾选标记落在家族网格的 indicator 列,只在选中项上显形(家族按 aria-selected / data-state=checked 给);
539
+ 未选中时占位不塌,切换选中不引起行宽抖动。
540
+ 盒里的勾与半选杠是指示符(§6.5):盒在自己身上把 --xh-icon-size 改接到盒的尺,皮肤画的兜底字形与作者
541
+ 塞进盒里的 XhIcon 读同一把尺、与盒同边长(行尾标记不是勾选格,不取 × 0.75,与搜索候选行尾的勾同尺);
542
+ 不读家族按档下发到行的 --xh-icon-size——那支只管作者放进条目里的图标,按图标档取的 20px 会比 16 / 14 的盒还大 */
462
543
  [data-scope='cascader'][data-part='item-indicator'] {
544
+ --xh-icon-size: var(--xh-cascader-item-indicator-size, var(--xh-control-indicator-size));
545
+
463
546
  display: inline-flex;
464
- flex: none;
465
547
  align-items: center;
466
548
  justify-content: center;
467
549
  inline-size: var(--xh-cascader-item-indicator-size, var(--xh-control-indicator-size));
468
- color: var(--xh-cascader-item-indicator-fg, var(--xh-_tone, var(--xh-fg-brand)));
469
- visibility: hidden;
550
+ block-size: var(--xh-cascader-item-indicator-size, var(--xh-control-indicator-size));
470
551
  }
471
552
 
472
553
  [data-scope='cascader'][data-part='item-indicator'][hidden] {
473
554
  display: none;
474
555
  }
475
556
 
476
- /* 没写内容就由皮肤画勾;作者往部件里塞了自己的图形,这条规则即不命中 */
557
+ /* 没写内容就由皮肤画勾;作者往部件里塞了自己的图形,这条规则即不命中。
558
+ 尺寸读标记盒自己改接过的 --xh-icon-size:与盒同边长,随指示符档换档(见上面标记盒那块) */
477
559
  [data-scope='cascader'][data-part='item-indicator']:empty::before {
478
560
  content: '';
479
561
  display: inline-block;
@@ -485,10 +567,6 @@
485
567
  mask: var(--xh-glyph-mark-check) center / contain no-repeat;
486
568
  }
487
569
 
488
- [data-scope='cascader'][data-part='item-indicator'][data-state='checked'] {
489
- visibility: visible;
490
- }
491
-
492
570
  /* 级联勾选的半选态只在皮肤自绘(:empty)时露面画横杠:
493
571
  作者塞了自己的选中图形时,半选放出来会顶着一个语义相反的勾,表达交还作者 */
494
572
  [data-scope='cascader'][data-part='item-indicator'][data-state='indeterminate']:empty {
@@ -504,9 +582,9 @@
504
582
  background-color: currentColor;
505
583
  -webkit-mask: var(--xh-glyph-mark-minus) center / contain no-repeat;
506
584
  mask: var(--xh-glyph-mark-minus) center / contain no-repeat;
507
- font-weight: var(--xh-font-weight-semibold);
508
585
  }
509
586
 
587
+ /* 家族按行的禁用降级标记色;半选横线也一并跟着 */
510
588
  [data-scope='cascader'][data-part='item-indicator'][data-disabled] {
511
589
  color: var(--xh-fg-disabled);
512
590
  }
@@ -522,7 +600,7 @@
522
600
  padding-block: var(--xh-cascader-input-py, var(--xh-space-2));
523
601
  padding-inline: var(--xh-cascader-input-px, var(--xh-control-px-md));
524
602
  border: none;
525
- border-block-end: var(--xh-stroke-thin) solid var(--xh-cascader-search-divider, var(--xh-border-subtle));
603
+ border-block-end: var(--xh-stroke-thin) solid var(--xh-cascader-search-divider, var(--xh-material-frosted-separator));
526
604
  border-start-start-radius: inherit;
527
605
  border-start-end-radius: inherit;
528
606
  background: none;
@@ -532,14 +610,26 @@
532
610
  line-height: var(--xh-leading-normal);
533
611
  }
534
612
 
535
- [data-scope='cascader'][data-part='input']:focus-visible {
536
- outline: none;
537
- }
538
-
539
613
  [data-scope='cascader'][data-part='input'][hidden] {
540
614
  display: none;
541
615
  }
542
616
 
617
+ /* 自动填充:引擎给填过的框强加一套系统色(多数是黄底黑字),盖得过 background 与 color,
618
+ 深色档下就剩一条不跟主题走的黄带。底改用 inset 阴影铺回来,扩散量 100vmax 填满内容盒;
619
+ 字改用 -webkit-text-fill-color 接回来,那是这个态下唯一还听样式表的前景通道。
620
+ 不去拉长 transition 把系统底推迟显现:那只是把它藏起来,减弱动效通道一关过渡就当场露出。
621
+ 两个选择器名分写两条:一条选择器列表里只要有一个名字不被认识,整条规则就被丢弃。
622
+ 搜索框排在浮层面里,铺的底取浮起面而不是页面底,不然框内会比周围亮出一块。 */
623
+ [data-scope='cascader'][data-part='input']:autofill {
624
+ box-shadow: inset 0 0 0 100vmax var(--xh-cascader-input-autofill-bg, var(--xh-bg-surface));
625
+ -webkit-text-fill-color: var(--xh-cascader-input-autofill-fg, var(--xh-fg-default));
626
+ }
627
+
628
+ [data-scope='cascader'][data-part='input']:-webkit-autofill {
629
+ box-shadow: inset 0 0 0 100vmax var(--xh-cascader-input-autofill-bg, var(--xh-bg-surface));
630
+ -webkit-text-fill-color: var(--xh-cascader-input-autofill-fg, var(--xh-fg-default));
631
+ }
632
+
543
633
  /* 与列同高:搜索视图与浏览视图互换时浮层不改高度,候选多了在列表内自己滚 */
544
634
  [data-scope='cascader'][data-part='search-list'] {
545
635
  display: flex;
@@ -549,6 +639,8 @@
549
639
  block-size: var(--xh-cascader-column-h, var(--xh-viewport-h-sm));
550
640
  padding: var(--xh-cascader-search-p, var(--xh-space-1));
551
641
  overflow-y: auto;
642
+ /* 浮层滚动面:滚到头不把页面一起带走 */
643
+ overscroll-behavior: contain;
552
644
  }
553
645
 
554
646
  [data-scope='cascader'][data-part='search-list'][hidden] {
@@ -565,35 +657,86 @@
565
657
  display: none;
566
658
  }
567
659
 
568
- /* 候选与列内条目同一套行度量,虚拟高亮与悬停同用轻档 */
660
+ /* 候选与列内条目同一套行度量与家族状态面(悬停 / 高亮 100、按下 200、禁用面由 Collection Item 给)。
661
+ 搜索结果没有独立的正文部件,匿名文本进不了家族网格的 text 列,行保持块级排版,
662
+ 末端为同一枚皮肤对号预留固定轨 */
569
663
  [data-scope='cascader'][data-part='search-item'] {
570
- padding-block: var(--xh-cascader-row-py, var(--xh-_cascader-row-py));
571
- padding-inline: var(--xh-cascader-row-px, var(--xh-_cascader-row-px));
664
+ --xh-collection-block-padding: var(--xh-cascader-item-py, var(--xh-_cascader-row-py));
665
+ --xh-collection-inline-padding: var(--xh-cascader-item-px, var(--xh-_cascader-row-px));
666
+ --xh-collection-gap: var(--xh-cascader-item-gap, var(--xh-_cascader-gap));
667
+ --xh-collection-font-size: var(--xh-cascader-item-font-size, var(--xh-_cascader-font-size));
668
+ --xh-collection-radius: var(--xh-cascader-item-radius, var(--xh-shape-control));
669
+ --xh-collection-fg-rest: var(--xh-cascader-item-fg, var(--xh-material-frosted-fg));
670
+ --xh-collection-fg-hover: var(--xh-cascader-item-fg, var(--xh-material-frosted-fg));
671
+ --xh-collection-fg-keyboard-highlight: var(--xh-cascader-item-fg, var(--xh-material-frosted-fg));
672
+ --xh-collection-fg-selected: var(--xh-cascader-item-fg-selected, var(--xh-cascader-item-fg, var(--xh-material-frosted-fg)));
673
+ --xh-collection-fg-selected-highlight: var(--xh-cascader-item-fg-selected, var(--xh-cascader-item-fg, var(--xh-material-frosted-fg)));
674
+ --xh-collection-fg-selected-hover: var(--xh-cascader-item-fg-selected, var(--xh-cascader-item-fg, var(--xh-material-frosted-fg)));
675
+ --xh-collection-fg-selected-pressed: var(--xh-cascader-item-fg-selected, var(--xh-cascader-item-fg, var(--xh-material-frosted-fg)));
676
+ --xh-collection-fg-pressed: var(--xh-cascader-item-fg, var(--xh-material-frosted-fg));
677
+ --xh-collection-font-weight-selected: var(--xh-cascader-item-font-weight-selected, var(--xh-font-weight-regular));
678
+ --xh-collection-font-weight-selected-highlight: var(--xh-cascader-item-font-weight-selected, var(--xh-font-weight-regular));
679
+ --xh-collection-font-weight-selected-hover: var(--xh-cascader-item-font-weight-selected, var(--xh-font-weight-regular));
680
+ --xh-collection-font-weight-selected-pressed: var(--xh-cascader-item-font-weight-selected, var(--xh-font-weight-regular));
681
+ --xh-collection-bg-hover: var(--xh-cascader-item-bg-hover, var(--xh-bg-subtle));
682
+ --xh-collection-bg-keyboard-highlight: var(--xh-cascader-item-bg-hover, var(--xh-bg-subtle));
683
+ --xh-collection-bg-selected-highlight: var(--xh-cascader-item-bg-hover, var(--xh-bg-subtle));
684
+ --xh-collection-bg-selected-hover: var(--xh-cascader-item-bg-hover, var(--xh-bg-subtle));
685
+ --xh-collection-bg-pressed: var(--xh-cascader-item-bg-pressed, var(--xh-bg-subtle-hover));
686
+ --xh-collection-bg-selected-pressed: var(--xh-cascader-item-bg-pressed, var(--xh-bg-subtle-hover));
687
+ --xh-collection-outline-keyboard-highlight: transparent;
688
+ --xh-collection-outline-selected-highlight: transparent;
689
+
690
+ position: relative;
691
+ display: block;
692
+ /* 末端为对号预留固定轨:对号绝对定位,不进内容流 */
693
+ padding-inline-end: calc(
694
+ var(--xh-cascader-item-px, var(--xh-_cascader-row-px)) +
695
+ var(--xh-cascader-item-indicator-size, var(--xh-control-indicator-size)) +
696
+ var(--xh-cascader-item-gap, var(--xh-_cascader-gap))
697
+ );
572
698
  overflow: hidden;
573
- border-radius: var(--xh-cascader-row-radius, var(--xh-shape-control));
574
- color: var(--xh-cascader-row-fg, var(--xh-fg-default));
575
- font-size: var(--xh-cascader-row-font-size, var(--xh-_cascader-font-size));
576
- line-height: var(--xh-cascader-row-leading, var(--xh-leading-normal));
699
+ line-height: var(--xh-cascader-item-leading, var(--xh-leading-normal));
577
700
  text-overflow: ellipsis;
578
701
  white-space: nowrap;
579
- cursor: pointer;
580
702
  user-select: none;
581
- transition: background var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
582
703
  }
583
704
 
584
- [data-scope='cascader'][data-part='search-item']:not([data-disabled]):is(:hover, [data-highlighted]) {
585
- background: var(--xh-cascader-row-bg-hover, var(--xh-bg-subtle));
705
+ /* 键盘焦点出现时环才接回并立即到位,不压在背景过渡的中间帧上 */
706
+ [data-scope='cascader'][data-part='search-item']:focus-visible:not(:hover) {
707
+ --xh-collection-outline-keyboard-highlight: var(--xh-ring-focus);
708
+ --xh-collection-outline-selected-highlight: var(--xh-ring-focus);
709
+
710
+ transition: none;
711
+ }
712
+
713
+ /* 自动生成的搜索结果复用 item-indicator 的尺寸、颜色和对号字形,不新增第二套作者槽。 */
714
+ [data-scope='cascader'][data-part='search-item']::after {
715
+ content: '';
716
+ position: absolute;
717
+ inset-block-start: 50%;
718
+ inset-inline-end: var(--xh-cascader-item-px, var(--xh-_cascader-row-px));
719
+ inline-size: var(--xh-cascader-item-indicator-size, var(--xh-control-indicator-size));
720
+ block-size: var(--xh-cascader-item-indicator-size, var(--xh-control-indicator-size));
721
+ translate: 0 -50%;
722
+ background-color: var(--xh-cascader-item-indicator-fg, var(--xh-_cascader-accent));
723
+ visibility: hidden;
724
+ -webkit-mask: var(--xh-glyph-mark-check) center / contain no-repeat;
725
+ mask: var(--xh-glyph-mark-check) center / contain no-repeat;
586
726
  }
587
727
 
588
- /* 焦点恒在输入框,环只跟虚拟高亮走,悬停不画;往内收,行是撑满列宽的,环画在外沿会被裁掉 */
589
- [data-scope='cascader'][data-part='search-item']:not([data-disabled])[data-highlighted] {
590
- outline: var(--xh-ring-width) solid var(--xh-ring-focus);
591
- outline-offset: calc(-1 * var(--xh-ring-width));
728
+ [data-scope='cascader'][data-part='search-item'][data-state='checked']::after {
729
+ visibility: visible;
592
730
  }
593
731
 
594
- [data-scope='cascader'][data-part='search-item'][data-disabled] {
595
- color: var(--xh-fg-disabled);
596
- cursor: not-allowed;
732
+ [data-scope='cascader'][data-part='search-item'][data-state='indeterminate']::after {
733
+ visibility: visible;
734
+ -webkit-mask-image: var(--xh-glyph-mark-minus);
735
+ mask-image: var(--xh-glyph-mark-minus);
736
+ }
737
+
738
+ [data-scope='cascader'][data-part='search-item'][data-disabled]::after {
739
+ background-color: var(--xh-fg-disabled);
597
740
  }
598
741
 
599
742
  [data-scope='cascader'][data-part='search-item'][hidden] {
@@ -614,14 +757,56 @@
614
757
  min-inline-size: var(--xh-cascader-column-min-w, 7rem);
615
758
  min-block-size: var(--xh-cascader-empty-min-h, 5rem);
616
759
  padding: var(--xh-cascader-empty-p, var(--xh-space-3));
617
- color: var(--xh-cascader-empty-fg, var(--xh-fg-subtle));
618
- font-size: var(--xh-cascader-row-font-size, var(--xh-_cascader-font-size));
760
+ color: var(--xh-cascader-empty-fg, var(--xh-material-frosted-fg-muted));
761
+ font-size: var(--xh-cascader-item-font-size, var(--xh-_cascader-font-size));
619
762
  }
620
763
 
621
764
  [data-scope='cascader'][data-part='empty'][hidden] {
622
765
  display: none;
623
766
  }
624
767
 
768
+ /* 在途占位:与空态占位同一个位置、同一副观感,两者不同屏 */
769
+ [data-scope='cascader'][data-part='loading'] {
770
+ display: flex;
771
+ flex: 0 0 100%;
772
+ align-items: center;
773
+ justify-content: center;
774
+ min-inline-size: var(--xh-cascader-loading-min-w, var(--xh-cascader-column-min-w, 7rem));
775
+ min-block-size: var(--xh-cascader-loading-min-h, 5rem);
776
+ padding: var(--xh-cascader-loading-p, var(--xh-space-3));
777
+ color: var(--xh-cascader-loading-fg, var(--xh-material-frosted-fg-muted));
778
+ font-size: var(--xh-cascader-loading-font-size, var(--xh-_cascader-font-size));
779
+ }
780
+
781
+ [data-scope='cascader'][data-part='loading'][hidden] {
782
+ display: none;
783
+ }
784
+
785
+ /* 底栏在场时浮层壳允许换行:列照旧并排在第一行,底栏占满一整行落在列下面。
786
+ 没写底栏的浮层不命中这条,列多到放不下时仍是整体横向滚动 */
787
+ [data-scope='cascader'][data-part='content']:has(> [data-part='footer']) {
788
+ flex-wrap: wrap;
789
+ }
790
+
791
+ /* 浮层底部的操作区:横跨全部列,与列之间用一条分隔线断开 */
792
+ [data-scope='cascader'][data-part='footer'] {
793
+ display: flex;
794
+ /* 基准就是一整行:换行容器里它因此自己独占一行,落在全部列的下面 */
795
+ flex-basis: 100%;
796
+ align-items: center;
797
+ gap: var(--xh-cascader-footer-gap, var(--xh-space-2));
798
+ padding-block: var(--xh-cascader-footer-py, var(--xh-space-2));
799
+ padding-inline: var(--xh-cascader-footer-px, var(--xh-space-2));
800
+ border-block-start: var(--xh-stroke-thin) solid var(--xh-cascader-footer-border, var(--xh-border-subtle));
801
+ color: var(--xh-cascader-footer-fg, var(--xh-fg-muted));
802
+ font-size: var(--xh-cascader-footer-font-size, var(--xh-text-secondary-size));
803
+ }
804
+
805
+ /* 上面给了 display,UA 的 [hidden]{display:none} 已被盖掉,这里补回来 */
806
+ [data-scope='cascader'][data-part='footer'][hidden] {
807
+ display: none;
808
+ }
809
+
625
810
  /* 没写内容就由皮肤画兜底字形;作者往部件里塞了自己的图形,这几条即不命中 */
626
811
  [data-scope='cascader'][data-part='clear-trigger']:empty::before {
627
812
  content: '';
@@ -633,32 +818,30 @@
633
818
  -webkit-mask: var(--xh-glyph-mark-close) center / contain no-repeat;
634
819
  mask: var(--xh-glyph-mark-close) center / contain no-repeat;
635
820
  }
636
- }
637
821
 
638
- @layer xihan.motion {
639
- /* 浮层是横向长出来的:淡入之外再沿书写方向起始缘推一小段,观感上像列被「拉开」。
640
- 位移量从 content 上的私有槽取,rtl 侧翻符号后同一份关键帧两个方向都对。
641
- 关键帧写在本文件里、名字带组件前缀,不动公共的那一份 */
642
- @keyframes xh-pop-in {
643
- from {
644
- opacity: 0;
645
- scale: var(--xh-motion-scale-enter);
822
+ @media (forced-colors: active) {
823
+ /* 蒙版底色会被系统改成 Canvas 而消失;明确用系统前景保住箭头、对号与半选横线。 */
824
+ [data-scope='cascader'][data-part='item'][data-branch]::after,
825
+ [data-scope='cascader'][data-part='item-indicator']:empty::before,
826
+ [data-scope='cascader'][data-part='search-item']::after {
827
+ forced-color-adjust: none;
828
+ background-color: CanvasText;
646
829
  }
647
830
 
648
- to {
649
- opacity: 1;
650
- scale: 1;
831
+ [data-scope='cascader'][data-part='item'][data-branch][data-disabled]::after,
832
+ [data-scope='cascader'][data-part='item-indicator'][data-disabled]:empty::before,
833
+ [data-scope='cascader'][data-part='search-item'][data-disabled]::after {
834
+ background-color: GrayText;
651
835
  }
652
836
  }
653
- @keyframes xh-pop-out {
654
- from {
655
- opacity: 1;
656
- scale: 1;
657
- }
837
+ }
658
838
 
659
- to {
660
- opacity: 0;
661
- scale: var(--xh-motion-scale-enter);
839
+ @layer xihan.motion {
840
+ @media print {
841
+ /* 打印:定位层与遮罩是交互期才存在的东西,落到纸上只会盖住底下的正文。
842
+ 收起写在这份皮肤里而不是 reset:reset 层排在组件层之前,组件层里任何一条 display 都压得住它。 */
843
+ [data-scope='cascader'][data-part='positioner'] {
844
+ display: none;
662
845
  }
663
846
  }
664
847
  }