import { type Atom, OriginComponent, type OriginComponentInputType, type OriginComponentOutputType, atomization, extendsEvent, useEffectWithoutFirst, } from "@cn-ui/reactive"; import { type Accessor, For, type JSX, type JSXElement, children, createContext, createEffect, mergeProps, useContext, } from "solid-js"; import type { Options } from "sortablejs"; import { useSortable } from "./useSortable"; /** Sortable 组件的公共参数 */ export const SortableShared = createContext<{ /** 当使用 sharedList 的时候进行数据的统一 */ sharedData?: Atom[]; /** 默认参数 */ options?: Options; }>({}); /* TODO 还有很多示例未完成数据统一化 */ export interface SortableListProps { fallback?: JSX.Element; /** 获取 each 中的元素的 id 的方法,默认获取 */ getId?: (item: T) => string; children: (item: T, index: Accessor) => JSXElement; options?: Options; disabled?: Atom; setData?: (data: DataTransfer, el: HTMLElement) => void; onSorted?: (data: T[]) => void; } export type SortableListType = < T extends { id: string; }, >( props: OriginComponentOutputType, HTMLElement, string>, ) => JSX.Element; /** * @zh 使用响应式对象操控可排序列表, 内部列表不用再写 data-id 属性 */ export const SortableList = OriginComponent(function ( baseProps: OriginComponentInputType, HTMLDivElement, T[]>, ) { const context = useContext(SortableShared)!; const props = mergeProps( { options: context?.options ?? {}, }, baseProps, ); const each = atomization(props.model); const VoidId = Math.random().toString(); const getId = props.getId || ((item) => item.id.toString()); const disabled = atomization(props.disabled ?? false); const getRealIdList = () => { const arr: string[] = getSortable().toArray(); return arr.filter((i, index) => i !== VoidId && arr.indexOf(i) === index); }; useEffectWithoutFirst(() => { const sortable = getSortable(); sortable?.option("disabled", disabled()); }, [disabled]); /** 向外注入数据 */ const RefreshData = () => { const sortable = getRealIdList(); each(() => { const groupData = context.sharedData?.flatMap((i) => i() as T[]) || each(); return sortable .map((id) => { return groupData.find((item) => getId(item) === id)!; }) .filter((i) => i); }); }; const { initSort, getSortable } = useSortable({ animation: 150, ...props.options, // animation: 0, // swap: false, // delayOnTouchOnly: true, // only delay if user is using touch // delay: 100, setData: props.setData, onSort(...args) { props.options?.onSort?.apply(this, args as any); RefreshData(); props.onSorted?.(each()); }, }); const resort = () => { const sortable = getSortable(); if (sortable) { const IdMap = each().map((i) => getId(i)); const sortableTag = getRealIdList().join(","); if (sortableTag !== IdMap.join(",")) { // console.log('外部导入', IdMap); sortable.sort(IdMap); } } }; createEffect(resort); // fixed 把元素放置在末尾会发生 bug // 多添加一个空白的数据,让最后一个不能移动就行了 return (
{ initSort(i); }} /** @ts-ignore */ class={props.class("cn-sortable")} style={props.style()} {...extendsEvent(props)} > {(item, index) => { if (item === null) return
; const dom = children(() => props.children(item, index))() as HTMLElement; dom.dataset.id = getId(item); return dom; }}
); });