@steedos-widgets/sortable 1.0.1

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.
Files changed (72) hide show
  1. package/.env +1 -0
  2. package/.env.local +8 -0
  3. package/dist/assets-dev.json +25 -0
  4. package/dist/assets.json +25 -0
  5. package/dist/components/MultipleContainers.d.ts +7 -0
  6. package/dist/components/components/Container/Container.d.ts +17 -0
  7. package/dist/components/components/Container/index.d.ts +2 -0
  8. package/dist/components/components/Grid/Grid.d.ts +7 -0
  9. package/dist/components/components/Grid/index.d.ts +1 -0
  10. package/dist/components/components/GridContainer/GridContainer.d.ts +6 -0
  11. package/dist/components/components/GridContainer/index.d.ts +1 -0
  12. package/dist/components/components/Item/Item.d.ts +37 -0
  13. package/dist/components/components/Item/components/Action/Action.d.ts +9 -0
  14. package/dist/components/components/Item/components/Action/index.d.ts +2 -0
  15. package/dist/components/components/Item/components/Handle/Handle.d.ts +3 -0
  16. package/dist/components/components/Item/components/Handle/index.d.ts +1 -0
  17. package/dist/components/components/Item/components/Remove/Remove.d.ts +3 -0
  18. package/dist/components/components/Item/components/Remove/index.d.ts +1 -0
  19. package/dist/components/components/Item/components/index.d.ts +3 -0
  20. package/dist/components/components/Item/index.d.ts +2 -0
  21. package/dist/components/components/index.d.ts +5 -0
  22. package/dist/components/index.d.ts +1 -0
  23. package/dist/components/multipleContainersKeyboardCoordinates.d.ts +2 -0
  24. package/dist/index.d.ts +1 -0
  25. package/dist/meta.d.ts +9 -0
  26. package/dist/meta.js +180 -0
  27. package/dist/metas/MultipleContainers.d.ts +2 -0
  28. package/dist/sortable.cjs.css +335 -0
  29. package/dist/sortable.cjs.js +56525 -0
  30. package/dist/sortable.cjs.js.map +1 -0
  31. package/dist/sortable.esm.css +335 -0
  32. package/dist/sortable.esm.js +56521 -0
  33. package/dist/sortable.esm.js.map +1 -0
  34. package/dist/sortable.umd.css +335 -0
  35. package/dist/sortable.umd.js +22829 -0
  36. package/dist/utilities/createRange.d.ts +1 -0
  37. package/dist/utilities/index.d.ts +1 -0
  38. package/package.json +51 -0
  39. package/rollup.config.ts +109 -0
  40. package/src/assets.json +25 -0
  41. package/src/components/MultipleContainers.tsx +802 -0
  42. package/src/components/components/Container/Container.module.css +103 -0
  43. package/src/components/components/Container/Container.tsx +83 -0
  44. package/src/components/components/Container/index.ts +2 -0
  45. package/src/components/components/Grid/Grid.module.css +30 -0
  46. package/src/components/components/Grid/Grid.tsx +22 -0
  47. package/src/components/components/Grid/index.ts +1 -0
  48. package/src/components/components/GridContainer/GridContainer.module.css +15 -0
  49. package/src/components/components/GridContainer/GridContainer.tsx +23 -0
  50. package/src/components/components/GridContainer/index.ts +1 -0
  51. package/src/components/components/Item/Item.module.css +145 -0
  52. package/src/components/components/Item/Item.tsx +157 -0
  53. package/src/components/components/Item/components/Action/Action.module.css +50 -0
  54. package/src/components/components/Item/components/Action/Action.tsx +33 -0
  55. package/src/components/components/Item/components/Action/index.ts +2 -0
  56. package/src/components/components/Item/components/Handle/Handle.tsx +20 -0
  57. package/src/components/components/Item/components/Handle/index.ts +1 -0
  58. package/src/components/components/Item/components/Remove/Remove.tsx +19 -0
  59. package/src/components/components/Item/components/Remove/index.ts +1 -0
  60. package/src/components/components/Item/components/index.ts +3 -0
  61. package/src/components/components/Item/index.ts +2 -0
  62. package/src/components/components/index.ts +6 -0
  63. package/src/components/index.ts +1 -0
  64. package/src/components/multipleContainersKeyboardCoordinates.ts +116 -0
  65. package/src/index.ts +1 -0
  66. package/src/meta.ts +20 -0
  67. package/src/metas/MultipleContainers.tsx +131 -0
  68. package/src/types/css.d.ts +4 -0
  69. package/src/types/global.d.ts +6 -0
  70. package/src/utilities/createRange.ts +15 -0
  71. package/src/utilities/index.ts +1 -0
  72. package/tsconfig.json +11 -0
@@ -0,0 +1,802 @@
1
+ import React, { useCallback, useEffect, useRef, useState } from 'react';
2
+ import { createPortal, unstable_batchedUpdates } from 'react-dom';
3
+ import { map, keyBy, cloneDeep } from 'lodash';
4
+
5
+ import { createObject } from '@steedos-widgets/amis-lib'
6
+
7
+ import {
8
+ CancelDrop,
9
+ closestCenter,
10
+ pointerWithin,
11
+ rectIntersection,
12
+ CollisionDetection,
13
+ DndContext,
14
+ DragOverlay,
15
+ DropAnimation,
16
+ getFirstCollision,
17
+ KeyboardSensor,
18
+ MouseSensor,
19
+ TouchSensor,
20
+ Modifiers,
21
+ useDroppable,
22
+ UniqueIdentifier,
23
+ useSensors,
24
+ useSensor,
25
+ MeasuringStrategy,
26
+ KeyboardCoordinateGetter,
27
+ defaultDropAnimationSideEffects,
28
+ } from '@dnd-kit/core';
29
+ import {
30
+ AnimateLayoutChanges,
31
+ SortableContext,
32
+ useSortable,
33
+ arrayMove,
34
+ defaultAnimateLayoutChanges,
35
+ verticalListSortingStrategy,
36
+ SortingStrategy,
37
+ horizontalListSortingStrategy,
38
+ } from '@dnd-kit/sortable';
39
+ import { CSS } from '@dnd-kit/utilities';
40
+ import { coordinateGetter as multipleContainersCoordinateGetter } from './multipleContainersKeyboardCoordinates';
41
+
42
+ import { Item } from './components/Item'
43
+ import { Container, ContainerProps } from './components/';
44
+
45
+ import { createRange } from '../utilities';
46
+
47
+ export default {
48
+ title: 'Presets/Sortable/Multiple Containers',
49
+ };
50
+
51
+ const animateLayoutChanges: AnimateLayoutChanges = (args) =>
52
+ defaultAnimateLayoutChanges({ ...args, wasDragging: true });
53
+
54
+ function DroppableContainer({
55
+ children,
56
+ columns = 1,
57
+ disabled,
58
+ id,
59
+ items,
60
+ style,
61
+ ...props
62
+ }: ContainerProps & {
63
+ disabled?: boolean;
64
+ id: UniqueIdentifier;
65
+ items: UniqueIdentifier[];
66
+ style?: React.CSSProperties;
67
+ }) {
68
+ const {
69
+ active,
70
+ attributes,
71
+ isDragging,
72
+ listeners,
73
+ over,
74
+ setNodeRef,
75
+ transition,
76
+ transform,
77
+ } = useSortable({
78
+ id,
79
+ data: {
80
+ type: 'container',
81
+ children: items,
82
+ },
83
+ animateLayoutChanges,
84
+ });
85
+ const isOverContainer = over
86
+ ? (id === over.id && active?.data.current?.type !== 'container') ||
87
+ items.includes(over.id)
88
+ : false;
89
+
90
+ return (
91
+ <Container
92
+ ref={disabled ? undefined : setNodeRef}
93
+ style={{
94
+ ...style,
95
+ transition,
96
+ transform: CSS.Translate.toString(transform),
97
+ opacity: isDragging ? 0.5 : undefined,
98
+ }}
99
+ hover={isOverContainer}
100
+ handleProps={{
101
+ ...attributes,
102
+ ...listeners,
103
+ }}
104
+ columns={columns}
105
+ {...props}
106
+ >
107
+ {children}
108
+ </Container>
109
+ );
110
+ }
111
+
112
+ const dropAnimation: DropAnimation = {
113
+ sideEffects: defaultDropAnimationSideEffects({
114
+ styles: {
115
+ active: {
116
+ opacity: '0.5',
117
+ },
118
+ },
119
+ }),
120
+ };
121
+
122
+ type Items = Record<UniqueIdentifier, UniqueIdentifier[]>;
123
+
124
+ interface Props {
125
+ adjustScale?: boolean;
126
+ cancelDrop?: CancelDrop;
127
+ columns?: number;
128
+ containerStyle?: React.CSSProperties;
129
+ coordinateGetter?: KeyboardCoordinateGetter;
130
+ getItemStyles?(args: {
131
+ value: UniqueIdentifier;
132
+ index: number;
133
+ overIndex: number;
134
+ isDragging: boolean;
135
+ containerId: UniqueIdentifier;
136
+ isSorting: boolean;
137
+ isDragOverlay: boolean;
138
+ }): React.CSSProperties;
139
+ wrapperStyle?(args: { index: number }): React.CSSProperties;
140
+ itemCount?: number;
141
+ items?: Items;
142
+ handle?: boolean;
143
+ renderItem?: any;
144
+ strategy?: SortingStrategy;
145
+ modifiers?: Modifiers;
146
+ minimal?: boolean;
147
+ trashable?: boolean;
148
+ addable?: boolean;
149
+ scrollable?: boolean;
150
+ vertical?: boolean;
151
+ containerSource: [{id:string, label:string}?],
152
+ itemSource: [{id:string, label:string, color: string, columnSpan: number, body: [any]}?],
153
+ defaultValue: any,
154
+ onChange: Function,
155
+ data: any,
156
+ dispatchEvent: Function,
157
+ render: Function,
158
+ itemBody: any
159
+ }
160
+
161
+ export const TRASH_ID = 'void';
162
+ const PLACEHOLDER_ID = 'placeholder';
163
+ const empty: UniqueIdentifier[] = [];
164
+
165
+ export function MultipleContainers(props) {
166
+
167
+ let {
168
+ adjustScale = false,
169
+ itemCount = 3,
170
+ cancelDrop,
171
+ columns = 1,
172
+ handle = false,
173
+ containerStyle,
174
+ coordinateGetter = multipleContainersCoordinateGetter,
175
+ getItemStyles = () => ({}),
176
+ wrapperStyle = () => ({}),
177
+ minimal = false,
178
+ modifiers,
179
+ renderItem,
180
+ strategy = verticalListSortingStrategy,
181
+ addable = false,
182
+ trashable = false,
183
+ vertical = false,
184
+ scrollable,
185
+ containerSource = [],
186
+ itemSource = [],
187
+ defaultValue,
188
+ onChange: amisOnChange,
189
+ data: amisData,
190
+ dispatchEvent: amisDispatchEvent,
191
+ render: amisRender,
192
+ itemBody: amisItemBody = [{
193
+ "type": "tpl",
194
+ "tpl": "${label}",
195
+ "inline": false,
196
+ }],
197
+ }: Props = props
198
+
199
+ defaultValue && delete(defaultValue.$$id);
200
+
201
+ const [items, setItems] = useState<Items>(
202
+ () => {
203
+ return (defaultValue as Items) ?? {
204
+ A: ['A1', 'A2'],
205
+ B: ['B1', 'B2'],
206
+ C: ['C1', 'C2'],
207
+ }
208
+ }
209
+ );
210
+
211
+ const [containers, setContainers] = useState(
212
+ Object.keys(items) as UniqueIdentifier[]
213
+ );
214
+
215
+ const handleChange = async () => {
216
+ if (!amisDispatchEvent || !amisOnChange)
217
+ return
218
+ const value = items;
219
+
220
+ // 支持 amis OnEvent.change
221
+ const rendererEvent = await amisDispatchEvent(
222
+ 'change',
223
+ createObject(amisData, {
224
+ value
225
+ })
226
+ );
227
+ if (rendererEvent?.prevented) {
228
+ return;
229
+ }
230
+
231
+ setTimeout(()=> amisOnChange(value), 1000);
232
+ }
233
+
234
+ const [activeId, setActiveId] = useState<UniqueIdentifier | null>(null);
235
+
236
+ const lastOverId = useRef<UniqueIdentifier | null>(null);
237
+ const recentlyMovedToNewContainer = useRef(false);
238
+ const isSortingContainer = activeId ? containers.includes(activeId) : false;
239
+
240
+ /**
241
+ * Custom collision detection strategy optimized for multiple containers
242
+ *
243
+ * - First, find any droppable containers intersecting with the pointer.
244
+ * - If there are none, find intersecting containers with the active draggable.
245
+ * - If there are no intersecting containers, return the last matched intersection
246
+ *
247
+ */
248
+ const collisionDetectionStrategy: CollisionDetection = useCallback(
249
+ (args) => {
250
+ if (activeId && activeId in items) {
251
+ return closestCenter({
252
+ ...args,
253
+ droppableContainers: args.droppableContainers.filter(
254
+ (container) => container.id in items
255
+ ),
256
+ });
257
+ }
258
+
259
+ // Start by finding any intersecting droppable
260
+ const pointerIntersections = pointerWithin(args);
261
+ const intersections =
262
+ pointerIntersections.length > 0
263
+ ? // If there are droppables intersecting with the pointer, return those
264
+ pointerIntersections
265
+ : rectIntersection(args);
266
+ let overId = getFirstCollision(intersections, 'id');
267
+
268
+ if (overId != null) {
269
+ if (overId === TRASH_ID) {
270
+ // If the intersecting droppable is the trash, return early
271
+ // Remove this if you're not using trashable functionality in your app
272
+ return intersections;
273
+ }
274
+
275
+ if (overId in items) {
276
+ const containerItems = items[overId];
277
+
278
+ // If a container is matched and it contains items (columns 'A', 'B', 'C')
279
+ if (containerItems.length > 0) {
280
+ // Return the closest droppable within that container
281
+ overId = closestCenter({
282
+ ...args,
283
+ droppableContainers: args.droppableContainers.filter(
284
+ (container) =>
285
+ container.id !== overId &&
286
+ containerItems.includes(container.id)
287
+ ),
288
+ })[0]?.id;
289
+ }
290
+ }
291
+
292
+ lastOverId.current = overId;
293
+
294
+ return [{ id: overId }];
295
+ }
296
+
297
+ // When a draggable item moves to a new container, the layout may shift
298
+ // and the `overId` may become `null`. We manually set the cached `lastOverId`
299
+ // to the id of the draggable item that was moved to the new container, otherwise
300
+ // the previous `overId` will be returned which can cause items to incorrectly shift positions
301
+ if (recentlyMovedToNewContainer.current) {
302
+ lastOverId.current = activeId;
303
+ }
304
+
305
+ // If no droppable is matched, return the last match
306
+ return lastOverId.current ? [{ id: lastOverId.current }] : [];
307
+ },
308
+ [activeId, items]
309
+ );
310
+ const [clonedItems, setClonedItems] = useState<Items | null>(null);
311
+ const sensors = useSensors(
312
+ useSensor(MouseSensor),
313
+ useSensor(TouchSensor),
314
+ useSensor(KeyboardSensor, {
315
+ coordinateGetter,
316
+ })
317
+ );
318
+ const findContainer = (id: UniqueIdentifier) => {
319
+ if (id in items) {
320
+ return id;
321
+ }
322
+
323
+ return Object.keys(items).find((key) => items[key].includes(id));
324
+ };
325
+
326
+ const getIndex = (id: UniqueIdentifier) => {
327
+ const container = findContainer(id);
328
+
329
+ if (!container) {
330
+ return -1;
331
+ }
332
+
333
+ const index = items[container].indexOf(id);
334
+
335
+ return index;
336
+ };
337
+
338
+ const onDragCancel = () => {
339
+ if (clonedItems) {
340
+ // Reset items to their original state in case items have been
341
+ // Dragged across containers
342
+ setItems(clonedItems);
343
+ }
344
+
345
+ setActiveId(null);
346
+ setClonedItems(null);
347
+ };
348
+
349
+ useEffect(() => {
350
+ requestAnimationFrame(() => {
351
+ recentlyMovedToNewContainer.current = false;
352
+ });
353
+ }, [items]);
354
+
355
+ return (
356
+ <DndContext
357
+ sensors={sensors}
358
+ collisionDetection={collisionDetectionStrategy}
359
+ measuring={{
360
+ droppable: {
361
+ strategy: MeasuringStrategy.Always,
362
+ },
363
+ }}
364
+ onDragStart={({ active }) => {
365
+ setActiveId(active.id);
366
+ setClonedItems(items);
367
+ }}
368
+ onDragOver={({ active, over }) => {
369
+ const overId = over?.id;
370
+
371
+ if (overId == null || overId === TRASH_ID || active.id in items) {
372
+ return;
373
+ }
374
+
375
+ const overContainer = findContainer(overId);
376
+ const activeContainer = findContainer(active.id);
377
+
378
+ if (!overContainer || !activeContainer) {
379
+ return;
380
+ }
381
+
382
+ if (activeContainer !== overContainer) {
383
+ setItems((items) => {
384
+ const activeItems = items[activeContainer];
385
+ const overItems = items[overContainer];
386
+ const overIndex = overItems.indexOf(overId);
387
+ const activeIndex = activeItems.indexOf(active.id);
388
+
389
+ let newIndex: number;
390
+
391
+ if (overId in items) {
392
+ newIndex = overItems.length + 1;
393
+ } else {
394
+ const isBelowOverItem =
395
+ over &&
396
+ active.rect.current.translated &&
397
+ active.rect.current.translated.top >
398
+ over.rect.top + over.rect.height;
399
+
400
+ const modifier = isBelowOverItem ? 1 : 0;
401
+
402
+ newIndex =
403
+ overIndex >= 0 ? overIndex + modifier : overItems.length + 1;
404
+ }
405
+
406
+ recentlyMovedToNewContainer.current = true;
407
+
408
+ return {
409
+ ...items,
410
+ [activeContainer]: items[activeContainer].filter(
411
+ (item) => item !== active.id
412
+ ),
413
+ [overContainer]: [
414
+ ...items[overContainer].slice(0, newIndex),
415
+ items[activeContainer][activeIndex],
416
+ ...items[overContainer].slice(
417
+ newIndex,
418
+ items[overContainer].length
419
+ ),
420
+ ],
421
+ };
422
+ });
423
+ }
424
+ }}
425
+ onDragEnd={({ active, over }) => {
426
+ if (active.id in items && over?.id) {
427
+ setContainers((containers) => {
428
+ const activeIndex = containers.indexOf(active.id);
429
+ const overIndex = containers.indexOf(over.id);
430
+
431
+ return arrayMove(containers, activeIndex, overIndex);
432
+ });
433
+ }
434
+
435
+ const activeContainer = findContainer(active.id);
436
+
437
+ if (!activeContainer) {
438
+ setActiveId(null);
439
+ return;
440
+ }
441
+
442
+ const overId = over?.id;
443
+
444
+ if (overId == null) {
445
+ setActiveId(null);
446
+ return;
447
+ }
448
+
449
+ if (overId === TRASH_ID) {
450
+ setItems((items) => ({
451
+ ...items,
452
+ [activeContainer]: items[activeContainer].filter(
453
+ (id) => id !== activeId
454
+ ),
455
+ }));
456
+ setActiveId(null);
457
+ return;
458
+ }
459
+
460
+ if (overId === PLACEHOLDER_ID) {
461
+ const newContainerId = getNextContainerId();
462
+
463
+ unstable_batchedUpdates(() => {
464
+ setContainers((containers) => [...containers, newContainerId]);
465
+ setItems((items) => ({
466
+ ...items,
467
+ [activeContainer]: items[activeContainer].filter(
468
+ (id) => id !== activeId
469
+ ),
470
+ [newContainerId]: [active.id],
471
+ }));
472
+ console.log('拖动结束,更新form value')
473
+ handleChange()
474
+ setActiveId(null);
475
+ });
476
+ return;
477
+ }
478
+
479
+ const overContainer = findContainer(overId);
480
+
481
+ if (overContainer) {
482
+ const activeIndex = items[activeContainer].indexOf(active.id);
483
+ const overIndex = items[overContainer].indexOf(overId);
484
+
485
+ if (activeIndex !== overIndex) {
486
+ setItems((items) => ({
487
+ ...items,
488
+ [overContainer]: arrayMove(
489
+ items[overContainer],
490
+ activeIndex,
491
+ overIndex
492
+ ),
493
+ }));
494
+ }
495
+ }
496
+
497
+ setActiveId(null);
498
+
499
+ console.log('拖动结束2,更新form value')
500
+ handleChange()
501
+ }}
502
+ cancelDrop={cancelDrop}
503
+ onDragCancel={onDragCancel}
504
+ modifiers={modifiers}
505
+ >
506
+ <div
507
+ style={{
508
+ display: 'inline-grid',
509
+ boxSizing: 'border-box',
510
+ gridAutoFlow: vertical ? 'row' : 'column',
511
+ width: vertical? '100%': 'auto'
512
+ }}
513
+ >
514
+ <SortableContext
515
+ items={[...containers, PLACEHOLDER_ID]}
516
+ strategy={
517
+ vertical
518
+ ? verticalListSortingStrategy
519
+ : horizontalListSortingStrategy
520
+ }
521
+ >
522
+ {containers.map((containerId) => {
523
+ const container = cloneDeep(keyBy(containerSource, 'id')[containerId]) || {id: containerId, label: 'Container ' + containerId}
524
+ return (
525
+ <DroppableContainer
526
+ key={containerId}
527
+ // id={containerId}
528
+ // label={container.label}
529
+ columns={columns}
530
+ items={items[containerId]}
531
+ scrollable={scrollable}
532
+ style={containerStyle}
533
+ unstyled={minimal}
534
+ onRemove={() => handleRemove(containerId)}
535
+ {...container}
536
+ >
537
+ <SortableContext
538
+ items={items[containerId]}
539
+ strategy={strategy}
540
+ >
541
+ {items[containerId].map((value, index) => {
542
+ const item = cloneDeep(keyBy(itemSource, 'id')[value]) || {id: value, label: '' + value, columnSpan:1, body: amisItemBody}
543
+ if (item.columnSpan && item.columnSpan > columns)
544
+ item.columnSpan = columns
545
+ if (!item.body)
546
+ item.body = amisItemBody
547
+ return (
548
+ <SortableItem
549
+ disabled={isSortingContainer}
550
+ key={value}
551
+ value={amisRender? amisRender('body', item.body, {data: {...item}}) : (
552
+ <span>{item.label}</span>
553
+ )}
554
+ index={index}
555
+ handle={handle}
556
+ style={getItemStyles}
557
+ wrapperStyle={wrapperStyle}
558
+ renderItem={renderItem}
559
+ containerId={containerId}
560
+ getIndex={getIndex}
561
+ {...item}
562
+ />
563
+ );
564
+ })}
565
+ </SortableContext>
566
+ </DroppableContainer>
567
+ )})}
568
+ {minimal || !addable ? undefined : (
569
+ <DroppableContainer
570
+ id={PLACEHOLDER_ID}
571
+ disabled={isSortingContainer}
572
+ items={empty}
573
+ onClick={handleAddColumn}
574
+ placeholder
575
+ >
576
+ + Add
577
+ </DroppableContainer>
578
+ )}
579
+ </SortableContext>
580
+
581
+ {createPortal(
582
+ <DragOverlay adjustScale={adjustScale} dropAnimation={dropAnimation}>
583
+ {activeId
584
+ ? containers.includes(activeId)
585
+ ? renderContainerDragOverlay(activeId)
586
+ : renderSortableItemDragOverlay(activeId)
587
+ : null}
588
+ </DragOverlay>,
589
+ document.body
590
+ )}
591
+ {trashable && activeId && !containers.includes(activeId) ? (
592
+ <Trash id={TRASH_ID} />
593
+ ) : null}
594
+ </div>
595
+ </DndContext>
596
+ );
597
+
598
+ function renderSortableItemDragOverlay(id: UniqueIdentifier) {
599
+ const item = cloneDeep(keyBy(itemSource, 'id')[id]) || {id: id, label: '' + id, columnSpan:1}
600
+ if (item.columnSpan && item.columnSpan > columns)
601
+ item.columnSpan = columns
602
+ return (
603
+ <Item
604
+ value={amisRender? amisRender('body', amisItemBody, {data: {...item}}) : (
605
+ <span>{item.label}</span>
606
+ )}
607
+ handle={handle}
608
+ style={getItemStyles({
609
+ containerId: findContainer(id) as UniqueIdentifier,
610
+ overIndex: -1,
611
+ index: getIndex(id),
612
+ value: id,
613
+ isSorting: true,
614
+ isDragging: true,
615
+ isDragOverlay: true,
616
+ })}
617
+ color={getColor(id)}
618
+ wrapperStyle={wrapperStyle({ index: 0 })}
619
+ renderItem={renderItem}
620
+ dragOverlay
621
+ />
622
+ );
623
+ }
624
+
625
+ function renderContainerDragOverlay(containerId: UniqueIdentifier) {
626
+ return (
627
+ <Container
628
+ label={`Column ${containerId}`}
629
+ columns={columns}
630
+ style={{
631
+ height: '100%',
632
+ }}
633
+ shadow
634
+ unstyled={false}
635
+ >
636
+ {items[containerId].map((item, index) => (
637
+ <Item
638
+ key={item}
639
+ value={item}
640
+ handle={handle}
641
+ style={getItemStyles({
642
+ containerId,
643
+ overIndex: -1,
644
+ index: getIndex(item),
645
+ value: item,
646
+ isDragging: false,
647
+ isSorting: false,
648
+ isDragOverlay: false,
649
+ })}
650
+ color={getColor(item)}
651
+ wrapperStyle={wrapperStyle({ index })}
652
+ renderItem={renderItem}
653
+ />
654
+ ))}
655
+ </Container>
656
+ );
657
+ }
658
+
659
+ function handleRemove(containerID: UniqueIdentifier) {
660
+ setContainers((containers) =>
661
+ containers.filter((id) => id !== containerID)
662
+ );
663
+ }
664
+
665
+ function handleAddColumn() {
666
+ const newContainerId = getNextContainerId();
667
+
668
+ unstable_batchedUpdates(() => {
669
+ setContainers((containers) => [...containers, newContainerId]);
670
+ setItems((items) => ({
671
+ ...items,
672
+ [newContainerId]: [],
673
+ }));
674
+ });
675
+ }
676
+
677
+ function getNextContainerId() {
678
+ const containerIds = Object.keys(items);
679
+ const lastContainerId = containerIds[containerIds.length - 1];
680
+
681
+ return String.fromCharCode(lastContainerId.charCodeAt(0) + 1);
682
+ }
683
+
684
+ function getColor(id: UniqueIdentifier) {
685
+ const item = cloneDeep(keyBy(itemSource, 'id')[id])
686
+ return item && item.color? item.color : undefined
687
+ }
688
+
689
+ function SortableItem({
690
+ disabled,
691
+ id,
692
+ label,
693
+ index,
694
+ handle,
695
+ renderItem,
696
+ style,
697
+ containerId,
698
+ getIndex,
699
+ value,
700
+ wrapperStyle,
701
+ ...props
702
+ }: SortableItemProps) {
703
+ const {
704
+ setNodeRef,
705
+ setActivatorNodeRef,
706
+ listeners,
707
+ isDragging,
708
+ isSorting,
709
+ over,
710
+ overIndex,
711
+ transform,
712
+ transition,
713
+ } = useSortable({
714
+ id,
715
+ });
716
+ const mounted = useMountStatus();
717
+ const mountedWhileDragging = isDragging && !mounted;
718
+
719
+ return (
720
+ <Item
721
+ ref={disabled ? undefined : setNodeRef}
722
+ value={value}
723
+ dragging={isDragging}
724
+ sorting={isSorting}
725
+ handle={handle}
726
+ handleProps={handle ? { ref: setActivatorNodeRef } : undefined}
727
+ index={index}
728
+ wrapperStyle={wrapperStyle({ index })}
729
+ style={style({
730
+ index,
731
+ value: id,
732
+ isDragging,
733
+ isSorting,
734
+ overIndex: over ? getIndex(over.id) : overIndex,
735
+ containerId,
736
+ })}
737
+ color={getColor(id)}
738
+ transition={transition}
739
+ transform={transform}
740
+ fadeIn={mountedWhileDragging}
741
+ listeners={listeners}
742
+ renderItem={renderItem}
743
+ {...props}
744
+ />
745
+ );
746
+ }
747
+
748
+ }
749
+
750
+ function Trash({ id }: { id: UniqueIdentifier }) {
751
+ const { setNodeRef, isOver } = useDroppable({
752
+ id,
753
+ });
754
+
755
+ return (
756
+ <div
757
+ ref={setNodeRef}
758
+ style={{
759
+ display: 'flex',
760
+ alignItems: 'center',
761
+ justifyContent: 'center',
762
+ position: 'fixed',
763
+ left: '50%',
764
+ marginLeft: -150,
765
+ bottom: 20,
766
+ width: 300,
767
+ height: 60,
768
+ borderRadius: 5,
769
+ border: '1px solid',
770
+ borderColor: isOver ? 'red' : '#DDD',
771
+ }}
772
+ >
773
+ Drop here to delete
774
+ </div>
775
+ );
776
+ }
777
+
778
+ interface SortableItemProps {
779
+ containerId: UniqueIdentifier;
780
+ id: UniqueIdentifier;
781
+ label: string,
782
+ index: number;
783
+ handle: boolean;
784
+ value: any;
785
+ disabled?: boolean;
786
+ style(args: any): React.CSSProperties;
787
+ getIndex(id: UniqueIdentifier): number;
788
+ renderItem(): React.ReactElement;
789
+ wrapperStyle({ index }: { index: number }): React.CSSProperties;
790
+ }
791
+
792
+ function useMountStatus() {
793
+ const [isMounted, setIsMounted] = useState(false);
794
+
795
+ useEffect(() => {
796
+ const timeout = setTimeout(() => setIsMounted(true), 500);
797
+
798
+ return () => clearTimeout(timeout);
799
+ }, []);
800
+
801
+ return isMounted;
802
+ }