@xihan-ui/styles 1.0.0 → 1.1.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,197 @@
1
1
  # @xihan-ui/styles
2
2
 
3
+ ## 1.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 442bdcc: **新增** `resizable` 组件:一块能拖着改尺寸的区域,八条边都能推,键盘也能推。
8
+
9
+ `edges` 决定开放哪几条边(默认八向全开),没开放的边不显示把手。`minWidth` / `maxWidth` / `minHeight` / `maxHeight` 夹住范围,`aspectRatio` 锁宽高比,`step` 吸附到整数倍。两个回调分工明确:`onSizeChange` 拖动途中连着发,`onSizeChangeEnd` 收尾才发一次,存尺寸用后者。
10
+
11
+ 键盘按**屏幕方向**推:推东边时右键变宽、推西边时右键变窄,与拖动完全同义;Home / End 直接推到两端。`edge` 说的是逻辑方向——`e` 是行尾侧,从右往左排版时它落在屏幕左边,机器把逻辑边翻成物理边再算几何。
12
+
13
+ **推西边与北边时容器的起点会动**,那段位移写成 root 的 `left` / `top`。皮肤已给 `position: relative`,开箱即对;把 root 改成 `static` 会让这两个方向只变尺寸不移位。只用东 / 南 / 东南三向时没有这个前提。
14
+
15
+ - 97482ad: **新增** `sortable` 组件:列表 / 网格拖拽排序,Vue 与 Web Components 两侧同时可用。
16
+
17
+ 落点走乐观投影——拖动过程中其余条目实时让位,松手即定,不是拖完才跳一下。判据是被拖项的中心越过了谁的中心,沿轴扫描一遇到没越过的就停,因此落点连续,不会从第 0 位跳到第 5 位。几何一律取按下那一刻的快照:让位之后布局已经变了,拿变形后的几何再算会自激振荡。
18
+
19
+ **键盘路径默认开着且关不掉**:空格拾起、方向键挪一格、空格放下、Esc 取消,每一步都写进视觉隐藏的 `role=status` 区域。手柄带 `aria-roledescription="sortable"` 与 `aria-pressed`;拖动中的 Tab 被拦下,焦点一旦移走这一场就没有出口。
20
+
21
+ `orientation` 三档:竖排、横排,以及换行网格用的 `both`(按最近中心判落点)。排版方向由首尾两项的先后推出,从右往左排时自动反向。按下之后要走够 `activationDistance`(默认 5px)才算拖动,因此条目本身仍然可以点击。拖到容器边缘会自动滚动,速度随入侵深度线性上升。
22
+
23
+ `sort` 事件直接给出重排好的 `ids`,可以直接写回数据源,Vue 侧支持 `v-model:ids`。
24
+
25
+ **新增** `@xihan-ui/pointer` 的拖放几何层:排序投影、让位计算、激活阈值与边缘滚动,全部是纯函数,零 DOM、零状态。
26
+
27
+ - 69577fa: **新增** `table` 的列拖拽排序:列上标了 `reorderable` 才产出拖拽把手,拖到别的列上换位;
28
+ 把手自占一个 Tab 位,方向键挪一格、Home / End 挪到可拖区段的首末。
29
+
30
+ **新增** 两个部件 `column-drag-trigger` 与 `live-region`,一个属性名 `data-drop`(落点参照列,
31
+ 取值 `before` / `after`),以及 `api.draggableColumns` / `api.dropTarget` / `api.announcement`。
32
+
33
+ **新增** `shared/drag.ts` 的沿轴落点判定(两档与三档)、插入下标折算与拖拽播报,
34
+ `table.drag.ts` 的可拖列判定与列偏好下标折算。
35
+
36
+ 拖动中被拖的列**原地不动**,只落 `data-dragging`,落点由参照列上的一条指示线表示。
37
+ 不写位移是因为冻结列是 `position: sticky` 的后代,祖先一有 `transform` 就掉出吸附;
38
+ 斑马纹与行间线按 DOM 位置算,跟手让位会让它们在拖动全程与行错开。
39
+
40
+ 键盘不做拾起 / 放下两态:按一下就是一次已过守卫的完整提交,各自播报一句。
41
+ 列头里已经有排序把手与改宽把手两个 Tab 位,再加一套模态按键会让三者互相抢键。
42
+
43
+ 提交走既有的 `COLUMN_PREF.PATCH`,列序仍然只有一处在改。落点按列 id 认而不是按第几个,
44
+ 于是隐藏列自然留在原本的邻居旁边,前缀列压根不进落点快照。
45
+
46
+ 不可拖的列与冻结列都是**屏障**:可拖范围被它们切成段,只有最长的那一段能拖。
47
+ 跨过冻结列去落,落下来那一列会夹在两根钉住的列当中一起悬在滚动之上。
48
+
49
+ 播报区渲在 `root` **之外**。`root` 是 `role=grid`,它的子节点只能是 `row` 与 `rowgroup`,
50
+ 塞一个活动区域进去是 `aria-required-children`(critical)——不带 role 只留 `aria-live` 也一样,
51
+ 无角色但带全局 aria 属性的节点照样被算进 owned。两个适配器都自己把它渲成 `root` 的兄弟,
52
+ 使用者不必操心位置。
53
+
54
+ - 0a147ba: **新增** `table` 的列宽拖拽:列上标了 `resizable` 就产出改宽把手,拖动与方向键都能调。
55
+
56
+ 把手是可聚焦的分隔条,报出当前列宽与上下限;拖出表头仍跟手,系统收走指针时宽度退回按下那一刻。方向键一次 8px、按住 Shift 一次 40px,rtl 下左右两键对调而语义恒是「加宽 / 收窄」。列宽落在列偏好里,可以直接存起来下次还原。
57
+
58
+ 列宽写成百分比这类算不出 px 的写法时不认可改宽——读屏要一个数值,给不出就不该声称自己是可调控件,键盘本来也动不了它。
59
+
60
+ **新增** `@xihan-ui/pointer` 的尺寸调整几何层:八向边推动加约束(上下限 / 宽高比 / 吸附步进 / 容器夹取),纯函数。`floating-panel` 的 `resizeFloatingPanel` 与 `clampFloatingPanelSize` 保留签名、内部改走它,行为不变。
61
+
62
+ - 889c54d: **新增** `table` 的行拖拽排序:`rowReorderable` 打开后整行都是拖动源,拖到别的行上换位;
63
+ 焦点在表体里时 `Alt` + 上下键挪一格。搬完发 `onRowMove`,载荷是 `{ id, parent, index, ids }`——
64
+ 搬到 `parent` 那一行底下的第 `index` 位(`parent` 为 `null` 即根层,`index` 已算过「先摘后插」),
65
+ `ids` 是重排好的整份行序。
66
+
67
+ **新增** 行上的 `data-row-draggable` 与 `data-drop`(`before` / `after` / `inside`),
68
+ 以及 `api.rowReorderDisabledReason`。
69
+
70
+ 行序**不进机器**:`rows` 是 prop,库没有一份自己的行序可写,所以只发意图、写回归宿主。
71
+ 这与列不同——列序有 `columnPreference.order` 这个受控通道。
72
+
73
+ 按下不等于拖动:整行可拖没有把手表明意图,要走够 5px 才算,在那之前界面上一点变化都没有。
74
+ 按在行内的输入框、按钮、链接一类控件上不起拖。
75
+
76
+ **树形行照样搬**:`rows` 里有行声明了 `parentId` 就是树,落点分三档——拖到一行的上下两端
77
+ 是插在它前后(跟着换到那一层),拖到中段是落进它里面、认它当父。只有可展开或已经有子行的行
78
+ 给中段那一档,普通数据行不会因为被拖过就凭空长出一层。键盘上 `Alt` + 左右键改缩进层级:
79
+ 往里认上一个兄弟当爹,往外变成父行的下一个兄弟,rtl 下两键对调;平表下这两个键不归表格管。
80
+ 落进自己的后代会拖出一个环,库自己拦下,指示线也不画。新增 `allowRowDrop` 收业务侧的规矩。
81
+
82
+ 写回是**两件事**:按 `ids` 重排、再把那一行的 `parentId` 设成 `parent`。只做一件都对不上——
83
+ 表格的树是一份带 `parentId` 的扁平数组,结构由 `parentId` 定、同层次序由数组先后定。
84
+
85
+ **两条降级**,各自有原因可读:排序链非空(拖出来的新序下一帧就被排序键覆盖)、
86
+ 宿主只渲了一段(量到的行数与数据行数对不上,窗口外的行没有矩形)。
87
+ 前一条渲染期就知道,后一条按下量过才知道。
88
+
89
+ 展开着的行按**整块**算:数据行连同紧跟它的详情行是一个落点。不并块的话,
90
+ 拖过一个展开着的行时指针明明还在这一块里,落点却因为跨进详情行那一段而反复跳。
91
+
92
+ 拖动中被拖的行**原地不动**:斑马纹按 `nth-of-type` 算、行间线按兄弟选择器算,
93
+ 两者认的都是 DOM 位置,跟手让位会让它们在拖动全程与行错开。
94
+
95
+ **触屏不开拖**。纵向手势在按下那一刻就归了浏览器滚动,`touch-action` 事后改不回来;
96
+ 而把行设成 `touch-action: none` 又会让长表在行上完全滚不动。触屏那一路要等一个专门的
97
+ 拖动把手(不占 Tab 位,自带 `touch-action: none`),单独排。
98
+
99
+ **修复** `table` 的键盘处理器不再吞掉落在可编辑单元格里的按键。表体的处理器挂在 `body` 上,
100
+ 单元格里输入框冒上来的按键也经过它:焦点先落过行再进输入框时,打空格会被当成
101
+ 「切换这一行的选中」、`Ctrl+A` 会全选行、方向键会换行。现在输入法组合中的按键与
102
+ 落在可编辑控件上的按键一律放行。
103
+
104
+ - 161ee77: **新增** `tabs` 的标签拖拽换位:`reorderable` 打开后整个标签都是拖动源,拖到别的标签上换位;
105
+ 焦点在标签带里时 `Alt` + **主轴**方向键挪一位。搬完发 `onTabMove`,载荷是
106
+ `{ value, from, to, values }`,`values` 是重排好的整份标签序,可直接写回数据源。
107
+
108
+ **新增** `live-region` 部件、标签上的 `data-dragging` / `data-drop` / `data-draggable`、
109
+ `translations` prop,以及 `api.dropTarget` / `api.announcement`。
110
+
111
+ 轴向跟随 `orientation`:横排量横轴、竖排量纵轴,键盘也只认主轴那两个键——
112
+ 另一轴的方向键照常放行给页面滚动与读屏。横排 rtl 下左右对调。
113
+
114
+ 禁用的标签**仍进落点快照**。它自己挪不动,但别人可以落在它前后;把它摘掉的话,
115
+ 指针划过它那一段会没有落点,指示线一闪一闪。
116
+
117
+ 顺序不进机器:`collection` 是 prop,库没有一份自己的标签序可写,只发意图、写回归宿主。
118
+
119
+ **重构** 一维重排的三件算术提到 `shared/drag`:`reorderFlat`、`flatMoveCommand`、
120
+ `flatMoveIntentFromKey`(后者收轴向与文字方向两个参数)。`table` 的行拖拽改指共享实现,
121
+ `moveRowIds` / `rowMoveCommand` / `rowMoveIntentFromKey` 三个行专用名字随之删除——
122
+ 它们与本批同属一个未发布的系列,现在合并是免费的。三个组件从此共用同一份重排语义,
123
+ 往后要改「先摘后插」这类算术只有一处。
124
+
125
+ - c0a190f: **新增** 三个拖动把手:`table` 的 `row-drag-trigger`、`tree` 的 `node-drag-trigger`、
126
+ `tabs` 的 `tab-drag-trigger`。它们是**触屏那一路的入口**。
127
+
128
+ 三处的「整块起手」此前都不认触屏:纵向手势在按下那一刻就归了浏览器滚动,
129
+ `touch-action` 事后改不回来;而把整行 / 整个节点设成 `touch-action: none`
130
+ 又会让长列表在上面完全滚不动。把手是一小块专门让出去的地方,自带 `touch-action: none`,
131
+ 手势从按下那一刻就是拖动的——同仓的 `column-resize-trigger` 早就是这个机制。
132
+
133
+ 把手**不占 Tab 位**(`aria-hidden` + `tabindex=-1`):键盘那一路早就由容器上的
134
+ `Alt` + 方向键承担,把手只是指针侧的第二个入口。整块起手(鼠标与笔)原样保留。
135
+
136
+ 按下即拖,不等激活距离——把手是专门的入口,意图无歧义。三处的 `*_DRAG.START` 事件
137
+ 因此多一个 `activate` 旗标,整块起手仍走激活距离那条路。
138
+
139
+ 把手常挂即可:开关关着或这一项拖不动时它自报 `data-disabled`、也不再让出滚动。
140
+ 按拖不拖得动来决定渲不渲,会让 DOM 结构随状态变。
141
+
142
+ **修复** `tabs` 的 Web Components 侧:把手此前只收 `value`,漏了「没给 `collection`、
143
+ 禁用写在标记上」那条来路,于是标签禁着而把手仍判可拖。现在与 `trigger` 走同一条判定。
144
+
145
+ 抓手字形按「线的走向与能拖的方向垂直」转了向:列是横排所以画竖线,行与树节点是纵排
146
+ 所以画横线,`tabs` 跟着 `orientation` 两种都给。
147
+
148
+ - e14c407: **新增** `tree` 的节点拖拽搬家:`draggable` 打开后整个节点都是拖动源,拖到别的节点上换位或换父。
149
+ 焦点在树里时 `Alt` + 上下键在同层兄弟间挪,`Alt` + 左右键改缩进层级。
150
+ 搬完发 `onNodeMove`,载荷是 `{ value, parent, index }`——搬到哪个父下面的第几位,父为 `null` 即根层。
151
+
152
+ **新增** `allowDrop` 与 `translations` 两个 prop、`live-region` 部件、
153
+ 节点上的 `data-dragging` / `data-drop` / `data-draggable`,以及 `api.dropTarget` / `api.announcement`。
154
+ (`TreeTranslations` 以前只有类型没有对应的 prop,空接口所以一直没人发现,这次一并补上。)
155
+
156
+ **落点三档**:`before` / `after` 插在同层,`inside` 落进这个分支。叶子上只有前后两档。
157
+ 「放进这个文件夹」和「插在这两行之间」是两件事,皮肤上必须一眼分得开。
158
+
159
+ **分支量的是 `branch-control` 不是 `branch`**。后者是「这一行 + 整棵子层」的外壳,
160
+ 展开着的时候它的矩形把整棵子树都吞进去,落点会永远命中最外层那个分支,一辈子落不到子节点上。
161
+
162
+ **自我后代判据用 `indexPath` 前缀,不沿 `parent` 上溯**。作者写出自引用的数据是被支持的
163
+ (`collectNodes` 有祖先链防护),沿 `parent` 走会死循环;而同一个 value 挂在两个父下时
164
+ `parent` 只留先出现的那一支,判出来的祖先也是错的。前缀比较 O(深度) 且天然无环。
165
+
166
+ 树的**状态树一行未改**:跟手的会话挂在根级效应上常驻,8 个既有事件原地不动。
167
+
168
+ **修复** `@xihan-ui/pointer` 的 `createMultiPointerSession`:`onEnd` 现在带 `reason`
169
+ (`pointerup` / `pointercancel`)。此前两者走同一条路、调用方分不开,于是**系统收走指针会被当成落定提交**。
170
+ `carousel` 与 `image-viewer` 不受影响(它们本就把两者同等对待)。
171
+
172
+ **修复** `tree` 的键盘处理器不再吞掉落在可编辑节点内容里的按键。处理器挂在 `tree` 部件上,
173
+ 节点里输入框冒上来的按键也经过它:打空格会被当成「选中这一项」,打字会被连打检索吃掉。
174
+ `isEditableTarget` 从 `table` 提到 `shared/`——这是它的第二个消费者。
175
+
176
+ **触屏不开拖**(与 table 行拖拽同因)。
177
+
178
+ **改写** `tree/10-drag-move` 示例。它此前整个用 HTML5 `draggable` + `dataTransfer` 手写,
179
+ 只支持「拖进文件夹」、没有前后排序、落点提示是内联 style——正是 AntD 审计点名的那份样板。
180
+ 现在落点判定、三档落点、指示线、自我后代守卫全归库,宿主只留按 `{ value, parent, index }`
181
+ 搬数组这一段。
182
+
183
+ ### Patch Changes
184
+
185
+ - 7ec6d92: **修复** 开着 `striped` 的表里,偶数行既不响应悬停也显不出选中——斑马纹那条把两档都盖掉了。
186
+ 祖先链照字面写出来是 0-8-0,比悬停的 0-6-0 与选中的 0-3-0 都高,源码位置又在它们之后,
187
+ 于是偶数行恒是斑马底。现在祖先链与 `:nth-of-type` 都收进 `:where()`,斑马纹落到与基础底
188
+ 同档,只压过基础底一条。
189
+
190
+ 行底色的先后自此是:基础底 < 斑马纹 < 选中 < 悬停 / 键盘锚点 < 换父落点。这条由一个跑在真实
191
+ Chromium 上的用例钉住——五档各喂一个互不相同的颜色再量,jsdom 不跑级联,量不出谁盖过谁。
192
+
193
+ - @xihan-ui/tokens@1.1.0
194
+
3
195
  ## 1.0.0
4
196
 
5
197
  ### Major Changes
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @xihan-ui/styles
2
2
 
3
- 默认皮肤:125 份纯 CSS,零 JS。只按 `[data-scope][data-part]` 与 `data-*` 选中,不匹配标签名也不匹配 class——所以整套换掉它不影响任何行为。
3
+ 默认皮肤:127 份纯 CSS,零 JS。只按 `[data-scope][data-part]` 与 `data-*` 选中,不匹配标签名也不匹配 class——所以整套换掉它不影响任何行为。
4
4
 
5
5
  **谁会装它**:不想自己写样式的人装它。要做自己的设计语言,就不装,照解剖文档写一套自己的。
6
6
 
@@ -0,0 +1,113 @@
1
+ @layer xihan.components {
2
+ /* 皮肤在场的标记:开发模式下按 scope 探一次,取不到就说明这份皮肤没被引入。 */
3
+ [data-scope='resizable'] {
4
+ --xh-resizable-skin: 1;
5
+ }
6
+
7
+ /* 分工:**尺寸与位移**由连接层每帧写进内联样式(inline-size / block-size / left / top)。
8
+ 这份皮肤一条都不碰那四个——内联优先级更高,写了不但是死声明,
9
+ 还会在拖动的每一帧跟连接层抢位置。皮肤只管定位上下文、把手落位与状态表现。 */
10
+
11
+ /* 定位上下文,八向可调靠它才成立:
12
+ 推西边与北边时容器的起点会动,那段位移写成 left / top。relative 的 left / top
13
+ 是视觉位移、元素仍占原位,因此对边钉得住。改成 static 会让这两个方向只变尺寸不移位。 */
14
+ [data-scope='resizable'][data-part='root'] {
15
+ position: relative;
16
+ /* 把层序关在容器里:把手的 1 / 2 只跟彼此比,不跟外面的层号打架 */
17
+ isolation: isolate;
18
+ box-sizing: border-box;
19
+ }
20
+
21
+ /* 调整期间关掉选中:把手一按就会把容器里的文字刷成选中态 */
22
+ [data-scope='resizable'][data-part='root'][data-resizing] {
23
+ user-select: none;
24
+ }
25
+
26
+ [data-scope='resizable'][data-part='root'][hidden] {
27
+ display: none;
28
+ }
29
+
30
+ /* 把手压在边上,往外挪半个身位,两侧各留出一点可抓的余量——
31
+ 贴着边只有一两个像素能按到。落位按 data-edge 分派。 */
32
+ [data-scope='resizable'][data-part='handle'] {
33
+ position: absolute;
34
+ z-index: 1;
35
+ display: block;
36
+ touch-action: none;
37
+ }
38
+
39
+ [data-scope='resizable'][data-part='handle'][hidden] {
40
+ display: none;
41
+ }
42
+
43
+ /* 四条边:各占一整条,厚度是可抓的余量 */
44
+ [data-scope='resizable'][data-part='handle'][data-edge='n'],
45
+ [data-scope='resizable'][data-part='handle'][data-edge='s'] {
46
+ inset-inline: var(--xh-resizable-grip, var(--xh-space-2));
47
+ block-size: var(--xh-resizable-grip, var(--xh-space-2));
48
+ }
49
+
50
+ [data-scope='resizable'][data-part='handle'][data-edge='e'],
51
+ [data-scope='resizable'][data-part='handle'][data-edge='w'] {
52
+ inset-block: var(--xh-resizable-grip, var(--xh-space-2));
53
+ inline-size: var(--xh-resizable-grip, var(--xh-space-2));
54
+ }
55
+
56
+ /* 落位用逻辑属性:e 说的是行尾侧,从右往左排版时它就该落在屏幕左边——
57
+ 机器那边也是把逻辑边翻成物理边再算几何的,两处同源 */
58
+ [data-scope='resizable'][data-part='handle'][data-edge='n'] {
59
+ inset-block-start: calc(var(--xh-resizable-grip, var(--xh-space-2)) / -2);
60
+ }
61
+
62
+ [data-scope='resizable'][data-part='handle'][data-edge='s'] {
63
+ inset-block-end: calc(var(--xh-resizable-grip, var(--xh-space-2)) / -2);
64
+ }
65
+
66
+ [data-scope='resizable'][data-part='handle'][data-edge='e'] {
67
+ inset-inline-end: calc(var(--xh-resizable-grip, var(--xh-space-2)) / -2);
68
+ }
69
+
70
+ [data-scope='resizable'][data-part='handle'][data-edge='w'] {
71
+ inset-inline-start: calc(var(--xh-resizable-grip, var(--xh-space-2)) / -2);
72
+ }
73
+
74
+ /* 四个角:压在两条边的交点上,盖过相邻两条边(它们同时动两轴) */
75
+ [data-scope='resizable'][data-part='handle'][data-edge='ne'],
76
+ [data-scope='resizable'][data-part='handle'][data-edge='nw'],
77
+ [data-scope='resizable'][data-part='handle'][data-edge='se'],
78
+ [data-scope='resizable'][data-part='handle'][data-edge='sw'] {
79
+ z-index: 2;
80
+ inline-size: var(--xh-resizable-corner, var(--xh-space-3));
81
+ block-size: var(--xh-resizable-corner, var(--xh-space-3));
82
+ }
83
+
84
+ [data-scope='resizable'][data-part='handle'][data-edge='nw'] {
85
+ inset-block-start: calc(var(--xh-resizable-corner, var(--xh-space-3)) / -2);
86
+ inset-inline-start: calc(var(--xh-resizable-corner, var(--xh-space-3)) / -2);
87
+ }
88
+
89
+ [data-scope='resizable'][data-part='handle'][data-edge='ne'] {
90
+ inset-block-start: calc(var(--xh-resizable-corner, var(--xh-space-3)) / -2);
91
+ inset-inline-end: calc(var(--xh-resizable-corner, var(--xh-space-3)) / -2);
92
+ }
93
+
94
+ [data-scope='resizable'][data-part='handle'][data-edge='sw'] {
95
+ inset-block-end: calc(var(--xh-resizable-corner, var(--xh-space-3)) / -2);
96
+ inset-inline-start: calc(var(--xh-resizable-corner, var(--xh-space-3)) / -2);
97
+ }
98
+
99
+ [data-scope='resizable'][data-part='handle'][data-edge='se'] {
100
+ inset-block-end: calc(var(--xh-resizable-corner, var(--xh-space-3)) / -2);
101
+ inset-inline-end: calc(var(--xh-resizable-corner, var(--xh-space-3)) / -2);
102
+ }
103
+
104
+ [data-scope='resizable'][data-part='handle']:focus-visible {
105
+ outline: var(--xh-ring-width) solid var(--xh-ring-focus);
106
+ outline-offset: var(--xh-ring-offset);
107
+ }
108
+
109
+ /* 没开放的边不显示把手:留着一个抓不动的区域只会让人反复去试 */
110
+ [data-scope='resizable'][data-part='handle'][data-disabled] {
111
+ display: none;
112
+ }
113
+ }
@@ -0,0 +1,103 @@
1
+ @layer xihan.components {
2
+ /* 皮肤在场的标记:开发模式下按 scope 探一次,取不到就说明这份皮肤没被引入。 */
3
+ [data-scope='sortable'] {
4
+ --xh-sortable-skin: 1;
5
+ }
6
+
7
+ /* 分工:**每一项的位移**由连接层每帧写进内联 transform(让位与跟手都在那儿算)。
8
+ 这份皮肤一条都不碰 transform——内联优先级更高,写了不但是死声明,
9
+ 还会在拖动的每一帧跟连接层抢位置。皮肤只管排布方向、间距、光标与状态表现。 */
10
+
11
+ [data-scope='sortable'][data-part='root'] {
12
+ display: flex;
13
+ gap: var(--xh-sortable-gap, var(--xh-space-2));
14
+ }
15
+
16
+ /* 拖动期间关掉选中:整项可拖时按下不 preventDefault,不关这条会一路刷蓝 */
17
+ [data-scope='sortable'][data-part='root'][data-dragging] {
18
+ user-select: none;
19
+ }
20
+
21
+ [data-scope='sortable'][data-part='root'][data-orientation='vertical'] {
22
+ flex-direction: column;
23
+ }
24
+
25
+ [data-scope='sortable'][data-part='root'][data-orientation='horizontal'] {
26
+ flex-direction: row;
27
+ }
28
+
29
+ /* 换行网格:主轴横排、允许折行,落点按最近中心判,与单轴那两档是不同的判据 */
30
+ [data-scope='sortable'][data-part='root'][data-orientation='both'] {
31
+ flex-direction: row;
32
+ flex-wrap: wrap;
33
+ }
34
+
35
+ /* 项里面放什么、怎么排全是使用者的事:这里不给 display 也不给间距。
36
+ 给了会盖掉使用者自己的单元格布局——网格里的一格被压成 flex 行之后就不再撑满,
37
+ 看起来像是尺寸算错了。皮肤只管拖起来那一项的视觉反馈。 */
38
+
39
+ /* 让位期间关掉过渡,跟手的那一项才不会慢半拍粘在指针后面。
40
+ 其余项反过来要过渡:它们是被让开的,瞬移会看不出发生了什么。 */
41
+ [data-scope='sortable'][data-part='root'][data-dragging] [data-part='item']:not([data-dragging]) {
42
+ transition: transform var(--xh-motion-duration-micro) var(--xh-motion-ease-enter);
43
+ }
44
+
45
+ [data-scope='sortable'][data-part='item'][data-dragging] {
46
+ /* 拖起来的那一项浮在上面。层级由连接层写内联,这里只管观感 */
47
+ box-shadow: var(--xh-sortable-item-shadow-dragging, var(--xh-elevation-raised));
48
+ opacity: var(--xh-sortable-item-opacity-dragging, 0.9);
49
+ }
50
+
51
+ [data-scope='sortable'][data-part='item-handle'] {
52
+ display: inline-flex;
53
+ align-items: center;
54
+ justify-content: center;
55
+ flex: none;
56
+ inline-size: var(--xh-sortable-handle-size, var(--xh-control-action-size));
57
+ block-size: var(--xh-sortable-handle-size, var(--xh-control-action-size));
58
+ padding: 0;
59
+ border: 0;
60
+ border-radius: var(--xh-sortable-handle-radius, var(--xh-shape-control));
61
+ background: transparent;
62
+ color: var(--xh-sortable-handle-fg, var(--xh-fg-muted));
63
+ cursor: grab;
64
+ }
65
+
66
+ [data-scope='sortable'][data-part='item-handle']:hover {
67
+ background: var(--xh-sortable-handle-bg-hover, var(--xh-bg-subtle-hover));
68
+ color: var(--xh-sortable-handle-fg-hover, var(--xh-fg-default));
69
+ }
70
+
71
+ [data-scope='sortable'][data-part='item-handle'][data-dragging] {
72
+ cursor: grabbing;
73
+ }
74
+
75
+ [data-scope='sortable'][data-part='item-handle']:focus-visible {
76
+ outline: var(--xh-ring-width) solid var(--xh-ring-focus);
77
+ outline-offset: var(--xh-ring-offset);
78
+ }
79
+
80
+ [data-scope='sortable'][data-part='item-handle'][aria-disabled='true'] {
81
+ cursor: not-allowed;
82
+ color: var(--xh-sortable-handle-fg-disabled, var(--xh-fg-disabled));
83
+ }
84
+
85
+ /* 活动区域只说给读屏听,不占版面。声明与 shared/visually-hidden.ts 逐条同值。 */
86
+ [data-scope='sortable'][data-part='live-region'] {
87
+ position: absolute;
88
+ inline-size: 1px;
89
+ block-size: 1px;
90
+ padding: 0;
91
+ margin: -1px;
92
+ overflow: hidden;
93
+ clip-path: inset(50%);
94
+ white-space: nowrap;
95
+ border: 0;
96
+ }
97
+ /* 给了 display 的角色节点必须补这条:display 声明会盖掉 UA 的 [hidden]{display:none},
98
+ 不补的话作者给节点加 hidden 后它照样显示 */
99
+ [data-scope='sortable'][data-part='root'][hidden],
100
+ [data-scope='sortable'][data-part='item-handle'][hidden] {
101
+ display: none;
102
+ }
103
+ }