@vendure/dashboard 3.5.2-master-202512170238 → 3.5.2-master-202512180239

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.
@@ -361,6 +361,22 @@ export interface ListPageProps<
361
361
  * the list needs to be refreshed.
362
362
  */
363
363
  registerRefresher?: PaginatedListRefresherRegisterFn;
364
+ /**
365
+ * @description
366
+ * Callback when items are reordered via drag and drop.
367
+ * Only applies to top-level items. When provided, enables drag-and-drop functionality.
368
+ *
369
+ * @param oldIndex - The original index of the dragged item
370
+ * @param newIndex - The new index where the item was dropped
371
+ * @param item - The data of the item that was moved
372
+ */
373
+ onReorder?: (oldIndex: number, newIndex: number, item: any) => void | Promise<void>;
374
+ /**
375
+ * @description
376
+ * When true, drag and drop will be disabled. This will only have an effect if the onReorder prop is also set Useful when filtering or searching.
377
+ * Defaults to false. Only relevant when `onReorder` is provided.
378
+ */
379
+ disableDragAndDrop?: boolean;
364
380
  }
365
381
 
366
382
  /**
@@ -481,6 +497,8 @@ export function ListPage<
481
497
  setTableOptions,
482
498
  bulkActions,
483
499
  registerRefresher,
500
+ onReorder,
501
+ disableDragAndDrop = false,
484
502
  }: Readonly<ListPageProps<T, U, V, AC>>) {
485
503
  const route = typeof routeOrFn === 'function' ? routeOrFn() : routeOrFn;
486
504
  const routeSearch = route.useSearch();
@@ -536,6 +554,47 @@ export function ListPage<
536
554
  });
537
555
  }
538
556
 
557
+ const commonTableProps = {
558
+ listQuery,
559
+ deleteMutation,
560
+ transformVariables,
561
+ customizeColumns: customizeColumns as any,
562
+ additionalColumns: additionalColumns as any,
563
+ defaultColumnOrder: columnOrder as any,
564
+ defaultVisibility: columnVisibility as any,
565
+ onSearchTermChange,
566
+ page: pagination.page,
567
+ itemsPerPage: pagination.itemsPerPage,
568
+ sorting,
569
+ columnFilters,
570
+ onPageChange: (table: Table<any>, page: number, perPage: number) => {
571
+ persistListStateToUrl(table, { page, perPage });
572
+ if (pageId) {
573
+ setTableSettings(pageId, 'pageSize', perPage);
574
+ }
575
+ },
576
+ onSortChange: (table: Table<any>, sorting: SortingState) => {
577
+ persistListStateToUrl(table, { sort: sorting });
578
+ },
579
+ onFilterChange: (table: Table<any>, filters: ColumnFiltersState) => {
580
+ persistListStateToUrl(table, { filters });
581
+ if (pageId) {
582
+ setTableSettings(pageId, 'columnFilters', filters);
583
+ }
584
+ },
585
+ onColumnVisibilityChange: (table: Table<any>, columnVisibility: any) => {
586
+ if (pageId) {
587
+ setTableSettings(pageId, 'columnVisibility', columnVisibility);
588
+ }
589
+ },
590
+ facetedFilters,
591
+ rowActions,
592
+ bulkActions,
593
+ setTableOptions,
594
+ transformData,
595
+ registerRefresher,
596
+ };
597
+
539
598
  return (
540
599
  <Page pageId={pageId}>
541
600
  <PageTitle>{title}</PageTitle>
@@ -543,44 +602,9 @@ export function ListPage<
543
602
  <PageLayout>
544
603
  <FullWidthPageBlock blockId="list-table">
545
604
  <PaginatedListDataTable
546
- listQuery={listQuery}
547
- deleteMutation={deleteMutation}
548
- transformVariables={transformVariables}
549
- customizeColumns={customizeColumns as any}
550
- additionalColumns={additionalColumns as any}
551
- defaultColumnOrder={columnOrder as any}
552
- defaultVisibility={columnVisibility as any}
553
- onSearchTermChange={onSearchTermChange}
554
- page={pagination.page}
555
- itemsPerPage={pagination.itemsPerPage}
556
- sorting={sorting}
557
- columnFilters={columnFilters}
558
- onPageChange={(table, page, perPage) => {
559
- persistListStateToUrl(table, { page, perPage });
560
- if (pageId) {
561
- setTableSettings(pageId, 'pageSize', perPage);
562
- }
563
- }}
564
- onSortChange={(table, sorting) => {
565
- persistListStateToUrl(table, { sort: sorting });
566
- }}
567
- onFilterChange={(table, filters) => {
568
- persistListStateToUrl(table, { filters });
569
- if (pageId) {
570
- setTableSettings(pageId, 'columnFilters', filters);
571
- }
572
- }}
573
- onColumnVisibilityChange={(table, columnVisibility) => {
574
- if (pageId) {
575
- setTableSettings(pageId, 'columnVisibility', columnVisibility);
576
- }
577
- }}
578
- facetedFilters={facetedFilters}
579
- rowActions={rowActions}
580
- bulkActions={bulkActions}
581
- setTableOptions={setTableOptions}
582
- transformData={transformData}
583
- registerRefresher={registerRefresher}
605
+ {...commonTableProps}
606
+ onReorder={onReorder}
607
+ disableDragAndDrop={disableDragAndDrop}
584
608
  />
585
609
  </FullWidthPageBlock>
586
610
  </PageLayout>
@@ -0,0 +1,86 @@
1
+ import { DragEndEvent, KeyboardSensor, PointerSensor, useSensor, useSensors } from '@dnd-kit/core';
2
+ import { arrayMove, sortableKeyboardCoordinates } from '@dnd-kit/sortable';
3
+ import { useCallback, useEffect, useMemo, useState } from 'react';
4
+
5
+ interface UseDragAndDropOptions<TData> {
6
+ data: TData[];
7
+ onReorder?: (oldIndex: number, newIndex: number, item: TData, allItems?: TData[]) => void | Promise<void>;
8
+ onError?: (error: Error) => void;
9
+ disabled?: boolean;
10
+ }
11
+
12
+ /**
13
+ * @description
14
+ * Provides the sensors and state management for drag and drop functionality.
15
+ *
16
+ *
17
+ * @docsCategory hooks
18
+ * @docsPage useDragAndDrop
19
+ * @docsWeight 0
20
+ * @since 3.3.0
21
+ */
22
+ export function useDragAndDrop<TData = any>(options: UseDragAndDropOptions<TData>) {
23
+ const sensors = useSensors(
24
+ useSensor(PointerSensor),
25
+ useSensor(KeyboardSensor, {
26
+ coordinateGetter: sortableKeyboardCoordinates,
27
+ }),
28
+ );
29
+
30
+ const { data, onReorder, disabled = false } = options;
31
+
32
+ const [localData, setLocalData] = useState<TData[]>(data);
33
+ const [isReordering, setIsReordering] = useState(false);
34
+
35
+ // Update local data when data prop changes (but not during reordering)
36
+ useEffect(() => {
37
+ if (!isReordering) {
38
+ setLocalData(data);
39
+ }
40
+ }, [data, isReordering]);
41
+
42
+ const handleDragEnd = useCallback(
43
+ async (event: DragEndEvent) => {
44
+ const { active, over } = event;
45
+
46
+ if (!over || active.id === over.id || !onReorder || disabled) {
47
+ return;
48
+ }
49
+
50
+ const oldIndex = localData.findIndex(item => (item as { id: string }).id === active.id);
51
+ const newIndex = localData.findIndex(item => (item as { id: string }).id === over.id);
52
+
53
+ if (oldIndex === -1 || newIndex === -1) {
54
+ return;
55
+ }
56
+
57
+ // Optimistically update the UI
58
+ const originalState = [...localData];
59
+ const newData = arrayMove(localData, oldIndex, newIndex);
60
+ setLocalData(newData);
61
+ setIsReordering(true);
62
+
63
+ try {
64
+ // Call the user's onReorder callback with all items for context
65
+ await onReorder(oldIndex, newIndex, localData[oldIndex], localData);
66
+ } catch (error) {
67
+ // Revert on error
68
+ setLocalData(originalState);
69
+ options.onError?.(error as Error);
70
+ } finally {
71
+ setIsReordering(false);
72
+ }
73
+ },
74
+ [localData, onReorder, disabled],
75
+ );
76
+
77
+ const itemIds = useMemo(() => localData.map(item => (item as { id: string }).id), [localData]);
78
+
79
+ return {
80
+ sensors,
81
+ localData,
82
+ handleDragEnd,
83
+ itemIds,
84
+ isReordering,
85
+ };
86
+ }