生态产品多个设计器现已发布候选版本

查看生态总览
跳转到内容

Sortable 排序 ​

通过拖拽或键盘重新排列内容,或在一组列表之间移动条目。

用法 ​

拖动任务调整顺序

组件结构 ​

加粗的是必需部件。

data-scope="sortable":root · item · item-drag-trigger · drop-indicator · live-region

示例 ​

水平排序 ​

调整标签顺序

网格排序 ​

在换行布局中排序

禁用项目 ​

固定单个项目的位置

看板 ​

在几列之间移动任务

待办

进行中

已完成

设计指引 ​

何时使用 ​

  • 调整任务、标签页、收藏项或表格列的顺序。
  • 需要保存用户定义的排列顺序。
  • 看板一类的几个列表之间移动条目,并落在指定位置。

何时不用 ​

  • 数据规则决定顺序时使用普通排序。
  • 条目要落进树的某个节点里、改变层级时使用树的拖放。

特性 ​

  • 支持垂直、水平和换行网格排序。
  • 拖动时实时显示让位和落点;放下后项目从松手处带着松手速度落进新位置。
  • 支持边缘自动滚动。
  • sort 事件返回重排后的 ids。
  • 写了同一个 group 的几个列表组成一组:条目拖进组里别的列表时,那个列表让出落点并画出落点线,容器随之长出一格。
  • 列表可以嵌套:项里再放一个排序列表(比如外层排分组、每组里一层同 group 的命令列表)时,按在里层条目上的那一下只归里层,外层不跟着拿起;键盘拾起本来就只认焦点所在的那一项或那个手柄。
  • 落进别的列表时由源列表发一次 transfer,载荷给出源列表、目标列表、新旧位次与两个列表各自的新顺序;写不写回归宿主,不写回时条目收回原位。

组合 ​

  • 可在项目中放置独立拖拽手柄。
  • 可与表格组合为列排序面板。

最佳实践 ​

  • 拖拽手柄放在项目的固定位置,与项目内的其他控件分开。
  • 拖放结束后立刻持久化 ids,失败时回滚并提示。
  • 项目高度保持一致,让位动画才能表达落点。
  • 一组里的每个列表写不重复的 listId,transfer 靠它说明从哪来、到哪去。
  • 被拖的条目仍在源列表里跟手,拖出列表的那一段不能被裁掉:入组的列表与它们的外层不设 overflow 裁剪,滚动放在更外层。

反模式 ​

  • 用整个项目作为手柄,项目内的按钮与链接将无法点击。
  • 拖动中改变列表长度或过滤条件。
  • 换行网格(orientation="both")入组:四个方向键都用在列表内,没有键留给列表间移动,会直接报错。

API 参考 ​

产物 ​

层值
自定义元素<xh-sortable>
Vue 组件XhSortableDropIndicator XhSortableItem XhSortableItemDragTrigger XhSortableLiveRegion XhSortableRoot
组合式函数useSortable
状态机sortableMachine
皮肤@xihan-ui/styles/sortable.css

Props ​

属性类型必填说明
idsstring[]是项的稳定标识,数组顺序即当前顺序。这是顺序的唯一真源。 DOM 中项的先后必须与它一致:几何按 DOM 测量,回调按它计算。
orientationSortableAxis排序沿哪根轴进行。换行网格使用 both。
disabledboolean
activationDistancenumber按下之后移动多远才视为开始拖动,默认 5px。提供 0 表示按下即拖动。
autoScrollboolean拖到容器边缘时自动滚动,默认开启。
dirDirection
translationsPartial<SortableTranslations>
groupstring所在的组。同一文档里 group 相同的几个列表组成一组,条目能从一个列表拖进另一个列表并落在指定位置; 键盘拖动中另一条轴上的方向键在相邻列表间移动。不写时列表只在自身内排序。 入组的列表只能是单轴排布(vertical / horizontal)。
listIdstring这个列表在组里的标识,写了 group 就必须写,且组内不重复:transfer 事件用它说明从哪来、到哪去。
onSort(details: SortableSortDetails) => void顺序变化意图。取消的一次不发出。
onTransfer(details: SortableTransferDetails) => void一项落进了同组另一个列表的意图,由源列表发出一次;取消、落回源列表时不发。 两个列表的新顺序都在载荷里,写不写回归宿主:不写回时那一项收回原位。
onDragStart(details: SortableDragStartDetails) => void
onDragEnd(details: SortableDragEndDetails) => void

事件 ​

自定义元素将载荷放在 detail;Vue 使用同名 emit。

事件载荷说明
sortSortableSortDetails顺序变化;detail 为 { from, to, id, ids },其中 ids 已重排
transferSortableTransferDetails一项落进了同组另一个列表,由源列表发一次;detail 为 { id, fromList, toList, from, to, fromIds, toIds },节点挪不挪、ids 写不写回归作者
drag-startSortableDragStartDetails拾起;detail 为 { id, from, mode }
drag-endSortableDragEndDetails收尾(含取消);detail 为 { id, from, to, mode, canceled },入了组时另带 fromList / toList

插槽 ​

仅列出带载荷的插槽。

Vue 组件插槽载荷说明
XhSortableItemdefaultSortableItemSlotProps
XhSortableItemDragTriggerdefault—
XhSortableRootdefaultSortableRootSlotProps

React 适配器 props ​

只列各组件自己声明的那些:继承自 ComponentPropsWithRef 的 DOM 属性不在其中,根组件上与上面 Props 表同名的也不重复列。Vue 的对应物是上面的插槽表。

React 组件属性类型必填说明
XhSortableItemitemIdstring是项标识,与 ids 中的值一一对应。
XhSortableItemdisabledboolean单独禁用该项。整份 disabled 在 Root 上,该项是项级的。
XhSortableItemchildrenSlotChildren<SortableItemSlotProps>
XhSortableItemDragTriggeritemIdstring是项标识,与 ids 中的值一一对应。
XhSortableItemDragTriggerdisabledboolean单独禁用该项。整份 disabled 在 Root 上,该项是项级的。
XhSortableRootchildrenSlotChildren<SortableRootSlotProps>

状态 ​

以下名称仅用于内部状态机。

状态:idle · pending · dragging

事件:ITEM.POINTER_DOWN · POINTER.MOVE · POINTER.END · POINTER.CANCEL · ITEM.PICKUP · KEY.MOVE · KEY.MOVE_LIST · KEY.DROP · KEY.CANCEL · GROUP.OVER · GROUP.LEAVE · GROUP.DROP · PRESS.START · PRESS.END

判据:canSort · passedActivation · canPress

connect API ​

getXxxProps() 返回对应部件的宿主属性。

成员类型说明
draggingboolean正在拖(含键盘拖动)。
activeIdstring | null
fromnumber
tonumber
modeSortableMode | null
itemsSortableItemState[]逐项的呈现状态,顺序与 ids 一致。
getRootProps() => T['element']
getItemProps(props: SortableItemProps) => T['element']
getItemDragTriggerProps(props: SortableItemProps) => T['element']
getDropIndicatorProps() => T['element']落点线:拖动中且落点与起点不同位时才存在,位置由内联样式给出。
getLiveRegionProps() => T['element']

无障碍 ​

键盘 ​

规格出处:W3C APG

按键生效条件行为
Space / Enterfocus in item-drag-trigger,未在拖动,not disabled拾起这一项,进入键盘拖动;播报它现在第几位、共几项、以及接下来能按什么
ArrowDown / ArrowRight键盘拖动中往后挪一位并播报新位置;已在末位时不动,也不回绕(挪进了同组别的列表时可以排到它的末项之后)。竖直排布认上下键、水平排布认左右键;另一条轴上的方向键未入组时原样放行,入组时归列表间移动
ArrowUp / ArrowLeft键盘拖动中往前挪一位,规则同上;rtl 下左右两键对调,语义恒是「往前 / 往后」
ArrowRight / ArrowDown键盘拖动中,列表入了组(group),按的是另一条轴上的键:竖直排布认 ArrowRight、水平排布认 ArrowDown挪进组里文档序的下一个列表,位次尽量沿用、超出那个列表的长度就排到末尾;播报列表名、它在组里排第几与新位次。已是最后一个列表时不动,也不回绕;rtl 下竖直排布的左右两键对调
ArrowLeft / ArrowUp键盘拖动中,列表入了组(group),按的是另一条轴上的键:竖直排布认 ArrowLeft、水平排布认 ArrowUp挪进组里文档序的上一个列表,规则同上
Space / Enter键盘拖动中放下,按当前位置提交顺序并播报落点;落在同组别的列表里时发 transfer,播报落进了哪个列表的第几位
Escape键盘拖动中取消,顺序回到拾起前,悬着的别的列表撤掉让位;播报已取消与原位置
Enter / Spaceheld on item-drag-trigger, not disabled按住期间把手投影 data-pressed,与指针 :active 同一副按压面;拾起转拖动那一下即撤下(拖动中的回执是 data-dragging),抬起或失焦撤下

ARIA ​

以下属性由 connect 生成。

部件属性值
rootaria-labeltranslations.root
rootrole'group'
item-drag-triggeraria-disabled'true' | 'false'
item-drag-triggeraria-labeltranslations.itemDragTrigger(name)
item-drag-triggeraria-pressed'true' | 'false'
item-drag-triggeraria-roledescriptiontranslations.itemDragTriggerRoleDescription
item-drag-triggerrole'button'
drop-indicatoraria-hidden'true'
live-regionaria-atomic'true'
live-regionaria-live'polite'
live-regionrole'status'
  • 空格拾取或放下,方向键移动,Escape 取消。
  • 入组的列表拿起后,本轴方向键在列表内移动,另一条轴上的方向键在相邻列表间移动:竖排列表用左右键,横排列表用上下键。
  • live-region 会播报当前拖动位置;挪进别的列表时先播报列表名与它在组里排第几,再播报位次。列表名取列表容器的可及名,看板里用 aria-labelledby 指向列标题即可。
  • 拖动期间焦点保留在当前手柄。

样式参考 ​

皮肤 ​

@xihan-ui/styles/sortable.css 按 [data-scope="sortable"][data-part="root"] 部件选择器书写,发布产物以挂载类 .xh-scope-sortable 代替其中的 data-scope(特异性相同),位于 xihan.components 层。覆盖样式使用 xihan.overrides,部件选择器照常可用。

forced-colors: active 下另有一套规则:颜色交给系统,边框与状态标记改用系统色关键字。

数据属性 ​

由 connect 生成;条件不成立时不输出无值属性。

部件属性值
rootdata-disabled''(条件成立时才出现)
rootdata-drag-modecontext.get('mode') | undefined
rootdata-dragging''(条件成立时才出现)
rootdata-drop'inside' | undefined
rootdata-orientationprops.orientation
itemdata-animating''(条件成立时才出现)
itemdata-disabled''(条件成立时才出现)
itemdata-dragging''(条件成立时才出现)
itemdata-indexString(item?.index ?? -1)
item-drag-triggerdata-disabled''(条件成立时才出现)
item-drag-triggerdata-dragging''(条件成立时才出现)
item-drag-triggerdata-pressed''(条件成立时才出现)
item-drag-triggerdata-xh-action-control''
item-drag-triggerdata-xh-action-display'always'
item-drag-triggerdata-xh-action-profile'icon'
item-drag-triggerdata-xh-action-size'xs'
item-drag-triggerdata-xh-action-variant'ghost'
drop-indicatordata-orientationprops.orientation

CSS 变量 ​

本组件公开覆盖槽由独立皮肤的实际消费位生成;默认来源、作用部件和状态均与 CSS 同源。

变量部件CSS 属性状态默认来源说明
--xh-sortable-drag-bg-activeitem-drag-triggerbackground-colordisabled
is(:active, [data-pressed])
loading
not([data-disabled])
not([data-loading])
pressed
--xh-_action-variant-bg-pressedsortable 的 item-drag-trigger 部件 background-color 覆盖槽。
--xh-sortable-drag-bg-hoveritem-drag-triggerbackground-colordisabled
hover
loading
not([data-disabled])
not([data-loading])
--xh-_action-variant-bg-hoversortable 的 item-drag-trigger 部件 background-color 覆盖槽。
--xh-sortable-drag-fgitem-drag-triggercolordefault--xh-fg-mutedsortable 的 item-drag-trigger 部件 color 覆盖槽。
--xh-sortable-drag-fg-disableditem-drag-triggercolordisabled--xh-fg-disabledsortable 的 item-drag-trigger 部件 color 覆盖槽。
--xh-sortable-drag-fg-hoveritem-drag-triggercolordisabled
hover
is(:active, [data-pressed])
loading
not([data-disabled])
not([data-loading])
pressed
--xh-fg-defaultsortable 的 item-drag-trigger 部件 color 覆盖槽。
--xh-sortable-drag-grip-hitem-drag-triggerblock-sizeempty--xh-space-3sortable 的 item-drag-trigger 部件 block-size 覆盖槽。
--xh-sortable-drag-grip-witem-drag-triggerinline-sizeempty--xh-space-1sortable 的 item-drag-trigger 部件 inline-size 覆盖槽。
--xh-sortable-drag-icon-sizeitem-drag-trigger--xh-icon-sizedefault--xh-_action-profile-glyph-sizesortable 的 item-drag-trigger 部件 --xh-icon-size 覆盖槽。
--xh-sortable-drag-radiusitem-drag-triggerborder-radiusdefault--xh-shape-controlsortable 的 item-drag-trigger 部件 border-radius 覆盖槽。
--xh-sortable-drag-sizeitem-drag-triggerblock-size
inline-size
min-inline-size
default
xh-action-profile=icon
--xh-_action-profile-visual-sizesortable 的 item-drag-trigger 部件 block-size、inline-size、min-inline-size 覆盖槽。
--xh-sortable-drop-indicator-bgdrop-indicatorbackgrounddefault--xh-bg-brandsortable 的 drop-indicator 部件 background 覆盖槽。
--xh-sortable-drop-indicator-radiusdrop-indicatorborder-radiusdefault--xh-shape-pillsortable 的 drop-indicator 部件 border-radius 覆盖槽。
--xh-sortable-drop-indicator-sizedrop-indicatorblock-size
inline-size
orientation=both
orientation=horizontal
orientation=vertical
--xh-stroke-thicksortable 的 drop-indicator 部件 block-size、inline-size 覆盖槽。
--xh-sortable-gaprootgapdefault--xh-space-2sortable 的 root 部件 gap 覆盖槽。
--xh-sortable-item-opacity-draggingitemopacitydragging0.9sortable 的 item 部件 opacity 覆盖槽。
--xh-sortable-item-shadow-draggingitembox-shadowdragging--xh-elevation-liftedsortable 的 item 部件 box-shadow 覆盖槽。

动效 ​

动效角色:按压 · 状态 · 切换(见动效规范)。

box-shadow · opacity · translate 走 transition 过渡。时长与缓动读动效令牌,改令牌即改全局节奏。

皮肤之外还有一段:值由内核逐帧算出(frameLoop),皮肤里看不到这段。

系统开启减弱动效时由令牌层统一收敛,皮肤不另作判断。

响应式 ​

皮肤另按输入能力分档:pointer: coarse:同一份皮肤在触屏与带指针的设备上不一样,与视口宽度无关。

RTL ​

皮肤用逻辑属性排布(inline-start 一族),dir="rtl" 下自动镜像;另有按 dir 分支的规则。

Released under The MIT License