import { computed, ref, watch, type Ref, } from 'vue' import type { DragAndDropSortingSettings } from '../drag-and-drop/DragAndDropItemInterfaces' export interface NodeItem { id?: string isEmpty?: boolean isExpandedRow?: boolean children?: NodeItem[] [key: string]: any } export interface NodeItemExtended extends NodeItem { children: NodeItemExtended[] depth: number isExpanded: boolean isLoading: boolean draftOrder: number parent?: NodeItemExtended isLastChildInGroup?: boolean } type Options = { isUseSingleQuery?: boolean dragAndDropSorting?: DragAndDropSortingSettings loadMethod(item: NodeItemExtended): Promise } export default function useNodes(items: Ref, options: Options) { const expandedIds = new Set() const DEFAULT_DEPTH = 1 const extendedNodeItems = ref([]) const expandedNodeItems = computed(() => { const flatten = (input: NodeItemExtended[]) => { const items: NodeItemExtended[] = [] for (let i = 0; i < input.length; i++) { items.push(Object.assign(input[i], { isLastChildInGroup: i === input.length - 1 })) if (input[i].isExpanded) items.push(...flatten(input[i].children.sort((a, b) => a.draftOrder - b.draftOrder))) } return items } return flatten([...extendedNodeItems.value].sort((a, b) => a.draftOrder - b.draftOrder)) }) function toNodeItemExtended(item: NodeItem, parent?: NodeItemExtended): NodeItemExtended { const previousExtendedNodeItem = extendedNodeItems.value.find((i) => i.id === item.id) const result: NodeItemExtended = { ...item, isLoading: false, isExpanded: previousExtendedNodeItem?.isExpanded ?? false, parent, children: previousExtendedNodeItem?.children ?? [], depth: parent ? parent.depth + 1 : DEFAULT_DEPTH, draftOrder: options.dragAndDropSorting ? options.dragAndDropSorting.getSort(item) : 0, } if (!result.children.length) { result.children = options.isUseSingleQuery && item.children ? item.children.map((i) => toNodeItemExtended(i, result)) : [] } return result } async function expand(item: NodeItemExtended, forceReload = false) { const shouldReload = forceReload || (!item.isExpanded && !options.isUseSingleQuery) if (shouldReload) { item.isLoading = true item.children = (await options.loadMethod(item)).map((i) => toNodeItemExtended(i, item)) item.isLoading = false if (typeof item.id !== 'undefined') expandedIds.add(item.id) } else { if (typeof item.id !== 'undefined') expandedIds.delete(item.id) } if (!forceReload) { item.isExpanded = !item.isExpanded } } watch(items, (value) => { extendedNodeItems.value = value.map((item) => toNodeItemExtended(item)) }, { deep: true, immediate: true }) return { DEFAULT_DEPTH, extendedNodeItems, expandedNodeItems, expandedIds, expand, toNodeItemExtended, } }