@onehat/ui 0.3.226 → 0.3.230

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onehat/ui",
3
- "version": "0.3.226",
3
+ "version": "0.3.230",
4
4
  "description": "Base UI for OneHat apps",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -26,7 +26,7 @@
26
26
  "license": "UNLICENSED",
27
27
  "dependencies": {
28
28
  "@gluestack-style/react": "^0.2.38",
29
- "@gluestack-ui/themed": "^0.1.53",
29
+ "@gluestack-ui/themed": "^1.1.22",
30
30
  "@hookform/resolvers": "^3.3.1",
31
31
  "@k-renwick/colour-mixer": "^1.2.1",
32
32
  "@onehat/data": "^1.20.0",
@@ -50,12 +50,12 @@ import getIconButtonFromConfig from '../../Functions/getIconButtonFromConfig.js'
50
50
  import testProps from '../../Functions/testProps.js';
51
51
  import nbToRgb from '../../Functions/nbToRgb.js';
52
52
  import Loading from '../Messages/Loading.js';
53
- import GridHeaderRow from './GridHeaderRow.js';
54
- import GridRow from './GridRow.js';
53
+ import GridHeaderRow from '../Grid/GridHeaderRow.js';
54
+ import GridRow, { DragSourceDropTargetGridRow, DragSourceGridRow, DropTargetGridRow } from './GridRow.js';
55
55
  import IconButton from '../Buttons/IconButton.js';
56
56
  import ExpandButton from '../Buttons/ExpandButton.js';
57
57
  import PaginationToolbar from '../Toolbar/PaginationToolbar.js';
58
- import NoRecordsFound from './NoRecordsFound.js';
58
+ import NoRecordsFound from '../Grid/NoRecordsFound.js';
59
59
  import Toolbar from '../Toolbar/Toolbar.js';
60
60
  import NoReorderRows from '../Icons/NoReorderRows.js';
61
61
  import ReorderRows from '../Icons/ReorderRows.js';
@@ -204,13 +204,14 @@ function GridComponent(props) {
204
204
  gridContainerRef = useRef(),
205
205
  isAddingRef = useRef(),
206
206
  expandedRowsRef = useRef({}),
207
+ cachedDragElements = useRef(),
208
+ idsRef = useRef([]),
207
209
  [isInited, setIsInited] = useState(false),
208
210
  [isReady, setIsReady] = useState(false),
209
211
  [isLoading, setIsLoading] = useState(false),
210
212
  [localColumnsConfig, setLocalColumnsConfigRaw] = useState([]),
211
213
  [isDragMode, setIsDragMode] = useState(false),
212
- [dragRowSlot, setDragRowSlot] = useState(null),
213
- [dragRowIx, setDragRowIx] = useState(),
214
+ [dragRow, setDragRow] = useState(),
214
215
  getIsExpanded = (index) => {
215
216
  return !!expandedRowsRef.current[index];
216
217
  },
@@ -303,8 +304,7 @@ function GridComponent(props) {
303
304
  },
304
305
  getFooterToolbarItems = () => {
305
306
  const items = _.map(additionalToolbarButtons, (config, ix) => getIconButtonFromConfig(config, ix, self));
306
-
307
- if (canRowsReorder) {
307
+ if (canRowsReorder && CURRENT_MODE === UI_MODE_WEB) { // DND is currently web-only TODO: implement for RN
308
308
  items.unshift(<IconButton
309
309
  key="reorderBtn"
310
310
  parent={self}
@@ -332,159 +332,184 @@ function GridComponent(props) {
332
332
  rowProps = getRowProps && !isHeaderRow ? getRowProps(item) : {},
333
333
  isSelected = !isHeaderRow && !disableWithSelection && isInSelection(item);
334
334
 
335
- let rowComponent = <Pressable
336
- // {...testProps(Repository ? Repository.schema.name + '-' + item.id : item.id)}
337
- onPress={(e) => {
338
- if (e.preventDefault && e.cancelable) {
339
- e.preventDefault();
340
- }
341
- if (isHeaderRow || isDragMode) {
342
- return
343
- }
344
- if (CURRENT_MODE === UI_MODE_WEB) {
345
- switch (e.detail) {
346
- case 1: // single click
347
- onRowClick(item, e); // sets selection
348
- if (onEditorRowClick) {
349
- onEditorRowClick(item, index, e);
350
- }
351
- break;
352
- case 2: // double click
353
- if (!isSelected) { // If a row was already selected when double-clicked, the first click will deselect it,
354
- onRowClick(item, e); // so reselect it
335
+ let rowComponent =
336
+ <Pressable
337
+ // {...testProps(Repository ? Repository.schema.name + '-' + item.id : item.id)}
338
+ onPress={(e) => {
339
+ if (e.preventDefault && e.cancelable) {
340
+ e.preventDefault();
341
+ }
342
+ if (isHeaderRow || isDragMode) {
343
+ return
344
+ }
345
+ if (CURRENT_MODE === UI_MODE_WEB) {
346
+ switch (e.detail) {
347
+ case 1: // single click
348
+ onRowClick(item, e); // sets selection
349
+ if (onEditorRowClick) {
350
+ onEditorRowClick(item, index, e);
351
+ }
352
+ break;
353
+ case 2: // double click
354
+ if (!isSelected) { // If a row was already selected when double-clicked, the first click will deselect it,
355
+ onRowClick(item, e); // so reselect it
356
+ }
357
+
358
+ if (UiGlobals.doubleClickingGridRowOpensEditorInViewMode) { // global setting
359
+ if (onView) {
360
+ onView(true);
355
361
  }
356
-
357
- if (UiGlobals.doubleClickingGridRowOpensEditorInViewMode) { // global setting
358
- if (onView) {
359
- onView(true);
360
- }
361
- } else {
362
- if (onEdit) {
363
- if (verifyCanEdit && !verifyCanEdit(selection)) {
364
- return;
365
- }
366
- onEdit();
362
+ } else {
363
+ if (onEdit) {
364
+ if (verifyCanEdit && !verifyCanEdit(selection)) {
365
+ return;
367
366
  }
367
+ onEdit();
368
368
  }
369
- break;
370
- case 3: // triple click
371
- break;
372
- default:
373
- }
374
- } else if (CURRENT_MODE === UI_MODE_REACT_NATIVE) {
375
- onRowClick(item, e); // sets selection
376
- if (onEditorRowClick) {
377
- onEditorRowClick(item, index, e);
378
- }
379
- }
380
- }}
381
- onLongPress={(e) => {
382
- if (e.preventDefault && e.cancelable) {
383
- e.preventDefault();
384
- }
385
- if (isHeaderRow || isDragMode) {
386
- return
369
+ }
370
+ break;
371
+ case 3: // triple click
372
+ break;
373
+ default:
387
374
  }
388
-
389
- // context menu
390
- const selection = [item];
391
- if (!disableWithSelection) {
392
- setSelection(selection);
393
- }
394
- if (onEditorRowClick) { // e.g. inline editor
375
+ } else if (CURRENT_MODE === UI_MODE_REACT_NATIVE) {
376
+ onRowClick(item, e); // sets selection
377
+ if (onEditorRowClick) {
395
378
  onEditorRowClick(item, index, e);
396
379
  }
397
- if (onContextMenu) {
398
- onContextMenu(item, e, selection, setSelection);
399
- }
400
- }}
401
- flexDirection="row"
402
- flexGrow={1}
403
- >
404
- {({
405
- isHovered,
406
- isFocused,
407
- isPressed,
408
- }) => {
409
- if (isHeaderRow) {
410
- return <GridHeaderRow
411
- Repository={Repository}
412
- columnsConfig={localColumnsConfig}
413
- setColumnsConfig={setLocalColumnsConfig}
414
- hideNavColumn={hideNavColumn}
415
- canColumnsSort={canColumnsSort}
416
- canColumnsReorder={canColumnsReorder}
417
- canColumnsResize={canColumnsResize}
418
- setSelection={setSelection}
419
- gridRef={gridRef}
420
- isHovered={isHovered}
421
- isInlineEditorShown={isInlineEditorShown}
422
- />;
423
- }
380
+ }
381
+ }}
382
+ onLongPress={(e) => {
383
+ if (e.preventDefault && e.cancelable) {
384
+ e.preventDefault();
385
+ }
386
+ if (isHeaderRow || isDragMode) {
387
+ return
388
+ }
389
+
390
+ // context menu
391
+ const selection = [item];
392
+ if (!disableWithSelection) {
393
+ setSelection(selection);
394
+ }
395
+ if (onEditorRowClick) { // e.g. inline editor
396
+ onEditorRowClick(item, index, e);
397
+ }
398
+ if (onContextMenu) {
399
+ onContextMenu(item, e, selection, setSelection);
400
+ }
401
+ }}
402
+ flexDirection="row"
403
+ flexGrow={1}
404
+ >
405
+ {({
406
+ isHovered,
407
+ isFocused,
408
+ isPressed,
409
+ }) => {
410
+ if (isHeaderRow) {
411
+ return <GridHeaderRow
412
+ Repository={Repository}
413
+ columnsConfig={localColumnsConfig}
414
+ setColumnsConfig={setLocalColumnsConfig}
415
+ hideNavColumn={hideNavColumn}
416
+ canColumnsSort={canColumnsSort}
417
+ canColumnsReorder={canColumnsReorder}
418
+ canColumnsResize={canColumnsResize}
419
+ setSelection={setSelection}
420
+ gridRef={gridRef}
421
+ isHovered={isHovered}
422
+ isInlineEditorShown={isInlineEditorShown}
423
+ />;
424
+ }
424
425
 
425
- let bg = rowProps.bg || styles.GRID_ROW_BG,
426
- mixWith;
427
- if (isSelected) {
428
- if (showHovers && isHovered) {
429
- mixWith = styles.GRID_ROW_SELECTED_HOVER_BG;
430
- } else {
431
- mixWith = styles.GRID_ROW_SELECTED_BG;
432
- }
433
- } else if (showHovers && isHovered) {
434
- mixWith = styles.GRID_ROW_HOVER_BG;
435
- } else if (alternateRowBackgrounds && index % alternatingInterval === 0) { // i.e. every second line, or every third line
436
- mixWith = styles.GRID_ROW_ALTERNATE_BG;
437
- }
438
- if (mixWith) {
439
- const
440
- mixWithObj = nbToRgb(mixWith),
441
- ratio = mixWithObj.alpha ? 1 - mixWithObj.alpha : 0.5;
442
- bg = colourMixer.blend(bg, ratio, mixWithObj.color);
426
+ let bg = rowProps.bg || styles.GRID_ROW_BG,
427
+ mixWith;
428
+ if (isSelected) {
429
+ if (showHovers && isHovered) {
430
+ mixWith = styles.GRID_ROW_SELECTED_HOVER_BG;
431
+ } else {
432
+ mixWith = styles.GRID_ROW_SELECTED_BG;
443
433
  }
434
+ } else if (showHovers && isHovered) {
435
+ mixWith = styles.GRID_ROW_HOVER_BG;
436
+ } else if (alternateRowBackgrounds && index % alternatingInterval === 0) { // i.e. every second line, or every third line
437
+ mixWith = styles.GRID_ROW_ALTERNATE_BG;
438
+ }
439
+ if (mixWith) {
444
440
  const
445
- rowReorderProps = {},
446
- rowDragProps = {};
441
+ mixWithObj = nbToRgb(mixWith),
442
+ ratio = mixWithObj.alpha ? 1 - mixWithObj.alpha : 0.5;
443
+ bg = colourMixer.blend(bg, ratio, mixWithObj.color);
444
+ }
445
+ const
446
+ rowReorderProps = {},
447
+ rowDragProps = {};
448
+ let WhichRow = GridRow;
449
+ if (CURRENT_MODE === UI_MODE_WEB) { // DND is currrently web-only TODO: implement for RN
450
+ // Create a method that gets an always-current copy of the selection ids
451
+ const ids = _.map(selection, (item) => item.id);
452
+ idsRef.current = ids;
453
+ const getIds = () => idsRef.current;
454
+
455
+ // assign event handlers
447
456
  if (canRowsReorder && isDragMode) {
448
- rowReorderProps.isDraggable = true;
449
- rowReorderProps.mode = VERTICAL;
450
- rowReorderProps.onDragStart = onRowReorderDragStart;
451
- rowReorderProps.onDrag = onRowReorderDrag;
452
- rowReorderProps.onDragStop = onRowReorderDragStop;
453
- rowReorderProps.proxyParent = gridRef.current?.getScrollableNode().children[0];
454
- rowReorderProps.proxyPositionRelativeToParent = true;
455
- rowReorderProps.getParentNode = (node) => node.parentElement.parentElement.parentElement;
456
- rowReorderProps.getProxy = getReorderProxy;
457
+ WhichRow = DragSourceGridRow;
458
+ rowReorderProps.isDragSource = true;
459
+ rowReorderProps.dragSourceType = 'row';
460
+ const dragIx = showHeaders ? index - 1 : index;
461
+ rowReorderProps.dragSourceItem = {
462
+ id: item.id,
463
+ getIds,
464
+ onDrag: (dragState) => {
465
+ onRowReorderDrag(dragState, dragIx);
466
+ },
467
+ };
468
+ rowReorderProps.onDragEnd = onRowReorderEnd;
457
469
  } else {
458
470
  // Don't allow drag/drop from withDnd while reordering
459
471
  if (areRowsDragSource) {
472
+ WhichRow = DragSourceGridRow;
460
473
  rowDragProps.isDragSource = true;
461
474
  rowDragProps.dragSourceType = rowDragSourceType;
462
- rowDragProps.dragSourceItem = getRowDragSourceItem ? getRowDragSourceItem(item) : { id: item.id };
475
+ if (getRowDragSourceItem) {
476
+ rowDragProps.dragSourceItem = getRowDragSourceItem(item, getIds);
477
+ } else {
478
+ rowDragProps.dragSourceItem = {
479
+ id: item.id,
480
+ getIds,
481
+ };
482
+ }
463
483
  }
464
484
  if (areRowsDropTarget) {
485
+ WhichRow = DropTargetGridRow;
465
486
  rowDragProps.isDropTarget = true;
466
487
  rowDragProps.dropTargetAccept = dropTargetAccept;
467
488
  rowDragProps.onDrop = (droppedItem) => {
468
- // TODO: the item is somehow getting stale
469
- // might have something to do with memoization
470
- onRowDrop(item, droppedItem);
489
+ onRowDrop(item, droppedItem); // item is what it was dropped on; droppedItem is the dragSourceItem defined above
471
490
  };
472
491
  }
492
+ if (areRowsDragSource && areRowsDropTarget) {
493
+ WhichRow = DragSourceDropTargetGridRow;
494
+ }
473
495
  }
474
- return <GridRow
475
- columnsConfig={localColumnsConfig}
476
- columnProps={columnProps}
477
- fields={fields}
478
- rowProps={rowProps}
479
- hideNavColumn={hideNavColumn}
480
- bg={bg}
481
- item={item}
482
- isInlineEditorShown={isInlineEditorShown}
483
- {...rowReorderProps}
484
- {...rowDragProps}
485
- />;
486
- }}
487
- </Pressable>;
496
+ }
497
+ return <WhichRow
498
+ columnsConfig={localColumnsConfig}
499
+ columnProps={columnProps}
500
+ fields={fields}
501
+ rowProps={rowProps}
502
+ hideNavColumn={hideNavColumn}
503
+ bg={bg}
504
+ item={item}
505
+ isInlineEditorShown={isInlineEditorShown}
506
+ {...rowReorderProps}
507
+ {...rowDragProps}
508
+
509
+ key1={item.id}
510
+ />;
511
+ }}
512
+ </Pressable>;
488
513
 
489
514
  if (showRowExpander && !isHeaderRow) {
490
515
  const isExpanded = getIsExpanded(index);
@@ -505,234 +530,134 @@ function GridComponent(props) {
505
530
  }
506
531
  return rowComponent;
507
532
  },
508
- getReorderProxy = (node) => {
509
- const
510
- row = node.parentElement.parentElement,
511
- rowRect = row.getBoundingClientRect(),
512
- parent = row.parentElement,
513
- parentRect = parent.getBoundingClientRect(),
514
- proxy = row.cloneNode(true),
515
- top = rowRect.top - parentRect.top,
516
- dragRowIx = Array.from(parent.children).indexOf(row)
517
-
518
- setDragRowIx(dragRowIx); // the ix of which record is being dragged
519
-
520
- proxy.style.top = top + 'px';
521
- proxy.style.left = '20px';
522
- proxy.style.height = rowRect.height + 'px';
523
- proxy.style.width = rowRect.width + 'px';
524
- proxy.style.display = 'flex';
525
- // proxy.style.backgroundColor = '#ccc';
526
- proxy.style.position = 'absolute';
527
- proxy.style.border = '1px solid #000';
528
- return proxy;
529
- },
530
- onRowReorderDragStart = (info, e, proxy, node) => {
531
- // console.log('onRowReorderDragStart', info, e, proxy, node);
532
- const
533
- proxyRect = proxy.getBoundingClientRect(),
534
- row = node.parentElement.parentElement,
535
- parent = row.parentElement,
536
- parentRect = parent.getBoundingClientRect(),
537
- rows = _.filter(parent.children, (childNode) => {
538
- return childNode.getBoundingClientRect().height !== 0; // Skip zero-height children
539
- }),
540
- currentY = proxyRect.top - parentRect.top, // top position of pointer, relative to page
541
- headerRowIx = showHeaders ? 0 : null,
542
- firstActualRowIx = showHeaders ? 1 : 0;
543
-
544
- // Figure out which index the user wants
545
- let newIx = 0;
546
- _.each(rows, (child, ix, all) => {
533
+ getOverState = (rows, currentY, mouseX) => {
534
+ // determines which row the mouse is over
535
+ // and whether the marker should be moved to the top or bottom of the row
536
+ let newIx = -1,
537
+ useBottom = false;
538
+ _.each(rows, (row, ix) => {
547
539
  const
548
- rect = child.getBoundingClientRect(), // rect of the row of this iteration
540
+ rect = row.getBoundingClientRect(),
549
541
  {
550
542
  top,
551
543
  bottom,
552
544
  height,
553
545
  } = rect,
554
- compensatedTop = top - parentRect.top,
555
- compensatedBottom = bottom - parentRect.top,
556
- halfHeight = height / 2;
557
-
558
- if (ix === headerRowIx || child === proxy) {
559
- return;
560
- }
561
- if (ix === firstActualRowIx) {
562
- // first row
563
- if (currentY < compensatedTop + halfHeight) {
564
- newIx = firstActualRowIx;
565
- return false;
566
- } else if (currentY < compensatedBottom) {
567
- newIx = firstActualRowIx + 1;
568
- return false;
569
- }
570
- return;
571
- } else if (ix === all.length -1) {
572
- // last row
573
- if (currentY < compensatedTop + halfHeight) {
574
- newIx = ix;
575
- return false;
576
- }
577
- newIx = ix +1;
578
- return false;
579
- }
580
-
581
- // all other rows
582
- if (compensatedTop <= currentY && currentY < compensatedTop + halfHeight) {
546
+ isOver = (
547
+ mouseX >= rect.left &&
548
+ mouseX <= rect.right &&
549
+ currentY >= rect.top &&
550
+ currentY <= rect.bottom
551
+ );
552
+ if (isOver) {
583
553
  newIx = ix;
584
- return false;
585
- } else if (currentY < compensatedBottom) {
586
- newIx = ix +1;
587
- return false;
588
- }
589
- });
590
554
 
591
- let useBottom = false;
592
- if (!rows[newIx] || rows[newIx] === proxy) {
593
- newIx--;
594
- useBottom = true;
595
- }
555
+ const
556
+ halfHeight = height / 2,
557
+ isOverTopHalf = currentY < top + halfHeight;
596
558
 
597
- // Render marker showing destination location
598
- const
599
- rowContainerRect = rows[newIx].getBoundingClientRect(),
600
- top = (useBottom ? rowContainerRect.bottom : rowContainerRect.top) - parentRect.top - parseInt(parent.style.borderWidth), // get relative Y position
601
- gridRowsContainer = gridRef.current._listRef._scrollRef.childNodes[0],
602
- gridRowsContainerRect = gridRowsContainer.getBoundingClientRect(),
603
- marker = document.createElement('div');
604
-
605
- marker.style.position = 'absolute';
606
- marker.style.top = top -4 + 'px'; // -4 so it's always visible
607
- marker.style.height = '4px';
608
- marker.style.width = gridRowsContainerRect.width + 'px';
609
- marker.style.backgroundColor = '#f00';
610
-
611
- gridRowsContainer.appendChild(marker);
559
+ useBottom = !isOverTopHalf;
612
560
 
613
- setDragRowSlot({ ix: newIx, marker, useBottom, });
561
+ return false;
562
+ }
563
+ });
564
+ return {
565
+ ix: newIx,
566
+ useBottom,
567
+ };
614
568
  },
615
- onRowReorderDrag = (info, e, proxy, node) => {
616
- // console.log('onRowReorderDrag', info, e, proxy, node);
569
+ buildCachedDragElements = (dragState) => {
617
570
  const
618
- proxyRect = proxy.getBoundingClientRect(),
619
- row = node.parentElement.parentElement,
620
- parent = row.parentElement,
621
- parentRect = parent.getBoundingClientRect(),
622
- rows = _.filter(parent.children, (childNode) => {
623
- return childNode.getBoundingClientRect().height !== 0; // Skip zero-height children
571
+ {
572
+ canDrag,
573
+ isDragging,
574
+ clientOffset,
575
+ sourceClientOffset,
576
+ } = dragState,
577
+
578
+ scrollRef = gridRef.current._listRef._scrollRef,
579
+ isUsingScroll = scrollRef.childNodes[0].style.overflow === 'auto',
580
+ flatlist = isUsingScroll ? scrollRef.childNodes[0] : scrollRef,
581
+ flatlistRect = flatlist.getBoundingClientRect(),
582
+ rows = _.filter(flatlist.childNodes, (childNode, ix) => {
583
+ const
584
+ isZeroHeight = childNode.getBoundingClientRect().height === 0,
585
+ isHeader = showHeaders && ix === 0;
586
+ return !isZeroHeight && !isHeader;
624
587
  }),
625
- currentY = proxyRect.top - parentRect.top, // top position of pointer, relative to page
626
- headerRowIx = showHeaders ? 0 : null,
627
- firstActualRowIx = showHeaders ? 1 : 0;
588
+ { ix, useBottom } = getOverState(rows, clientOffset.y, clientOffset.x);
628
589
 
629
- // Figure out which index the user wants
630
- let newIx = 0;
631
- _.each(rows, (child, ix, all) => {
632
- const
633
- rect = child.getBoundingClientRect(), // rect of the row of this iteration
634
- {
635
- top,
636
- bottom,
637
- height,
638
- } = rect,
639
- compensatedTop = top - parentRect.top,
640
- compensatedBottom = bottom - parentRect.top,
641
- halfHeight = height / 2;
642
590
 
643
- if (ix === headerRowIx || child === proxy) {
644
- return;
645
- }
646
- if (ix === firstActualRowIx) {
647
- // first row
648
- if (currentY < compensatedTop + halfHeight) {
649
- newIx = firstActualRowIx;
650
- return false;
651
- } else if (currentY < compensatedBottom) {
652
- newIx = firstActualRowIx + 1;
653
- return false;
654
- }
655
- return;
656
- } else if (ix === all.length -1) {
657
- // last row
658
- if (currentY < compensatedTop + halfHeight) {
659
- newIx = ix;
660
- return false;
661
- }
662
- newIx = ix +1;
663
- return false;
664
- }
665
-
666
- // all other rows
667
- if (compensatedTop <= currentY && currentY < compensatedTop + halfHeight) {
668
- newIx = ix;
669
- return false;
670
- } else if (currentY < compensatedBottom) {
671
- newIx = ix +1;
672
- return false;
673
- }
674
- });
591
+ // Render marker showing destination location
592
+ const marker = document.createElement('div');
593
+ marker.style.position = 'absolute';
594
+ marker.style.top = '0px';
595
+ marker.style.height = '8px';
596
+ marker.style.width = flatlistRect.width + 'px';
597
+ marker.style.backgroundColor = '#ccc';
598
+ flatlist.appendChild(marker);
599
+
600
+ if (ix !== -1) {
601
+ marker.style.visibility = 'visible';
602
+ const
603
+ rowContainerRect = rows[ix].getBoundingClientRect(),
604
+ top = (useBottom ? rowContainerRect.bottom : rowContainerRect.top)
605
+ - flatlistRect.top
606
+ - (flatlist.style.borderWidth ? parseInt(flatlist.style.borderWidth) : 0); // get relative Y position
607
+ marker.style.top = top + 'px';
608
+ } else {
609
+ marker.style.visibility = 'hidden';
610
+ }
675
611
 
676
- let useBottom = false;
677
- if (!rows[newIx] || rows[newIx] === proxy) {
678
- newIx--;
679
- useBottom = true;
612
+ return { ix, useBottom, marker, rows };
613
+ },
614
+ onRowReorderDrag = (dragState, dragIx) => {
615
+ // initial setup
616
+ if (!cachedDragElements.current) {
617
+ cachedDragElements.current = buildCachedDragElements(dragState);
680
618
  }
681
619
 
682
- // Render marker showing destination location (can't use regular render cycle because this div is absolutely positioned on page)
683
620
  const
684
- rowContainerRect = rows[newIx].getBoundingClientRect(),
685
- top = (useBottom ? rowContainerRect.bottom : rowContainerRect.top) - parentRect.top - parseInt(parent.style.borderWidth); // get relative Y position
686
- let marker = dragRowSlot?.marker;
687
- if (marker) {
688
- marker.style.top = top -4 + 'px'; // -4 so it's always visible
621
+ {
622
+ canDrag,
623
+ isDragging,
624
+ clientOffset,
625
+ sourceClientOffset,
626
+ } = dragState,
627
+ { marker, rows, } = cachedDragElements.current,
628
+ flatlist = gridRef.current._listRef._scrollRef.childNodes[0],
629
+ flatlistRect = flatlist.getBoundingClientRect(),
630
+ { ix, useBottom } = getOverState(rows, clientOffset.y, clientOffset.x);
631
+
632
+ // move marker to new location
633
+ if (ix !== -1) {
634
+ marker.style.visibility = 'visible';
635
+ const
636
+ rowContainerRect = rows[ix].getBoundingClientRect(),
637
+ top = (useBottom ? rowContainerRect.bottom : rowContainerRect.top)
638
+ - flatlistRect.top
639
+ - (flatlist.style.borderWidth ? parseInt(flatlist.style.borderWidth) : 0); // get relative Y position
640
+ marker.style.top = top + 'px';
641
+ } else {
642
+ marker.style.visibility = 'hidden';
689
643
  }
690
644
 
691
- setDragRowSlot({ ix: newIx, marker, useBottom, });
692
- // console.log('onRowReorderDrag slot', newIx);
693
-
645
+ cachedDragElements.current = { ix, useBottom, marker, rows, dragIx };
694
646
  },
695
- onRowReorderDragStop = (delta, e, config) => {
696
- // console.log('onRowReorderDragStop', delta, e, config);
647
+ onRowReorderEnd = (item, monitor) => {
697
648
  const
698
- dropIx = dragRowSlot.ix,
699
- compensatedDragIx = showHeaders ? dragRowIx -1 : dragRowIx, // ix, without taking header row into account
700
- compensatedDropIx = showHeaders ? dropIx -1 : dropIx, // // ix, without taking header row into account
701
- dropPosition = dragRowSlot.useBottom ? DROP_POSITION_AFTER : DROP_POSITION_BEFORE;
702
-
703
- let shouldMove = true,
704
- finalDropIx = compensatedDropIx;
705
-
706
- if (dropPosition === DROP_POSITION_BEFORE) {
707
- if (dragRowIx === dropIx || dragRowIx === dropIx -1) { // basically before or after the drag row's origin
708
- // Same as origin; don't do anything
709
- shouldMove = false;
710
- } else {
711
- // Actually move it
712
- if (!Repository) { // If we're just going to be switching rows, rather than telling server to reorder rows, so maybe adjust finalDropIx...
713
- if (finalDropIx > compensatedDragIx) { // if we're dropping *before* the origin ix
714
- finalDropIx = finalDropIx -1; // Because we're using BEFORE, we want to switch with the row *prior to* the ix we're dropping before
715
- }
716
- }
717
- }
718
- } else if (dropPosition === DROP_POSITION_AFTER) {
719
- // Only happens on the very last row. Everything else is BEFORE...
720
- if (dragRowIx === dropIx) {
721
- // Same as origin; don't do anything
722
- shouldMove = false;
723
- }
724
- }
725
-
726
- if (shouldMove) {
649
+ { ix: dropIx, useBottom, marker, rows, dragIx } = cachedDragElements.current,
650
+ shouldMove = dropIx !== dragIx;
651
+ if (shouldMove && dropIx !== -1) {
727
652
  // Update the row with the new ix
728
653
  let dragRecord,
729
654
  dropRecord;
730
655
  if (Repository) {
731
- dragRecord = Repository.getByIx(compensatedDragIx);
732
- dropRecord = Repository.getByIx(finalDropIx);
733
-
734
- Repository.reorder(dragRecord, dropRecord, dropPosition);
735
-
656
+ dragRecord = Repository.getByIx(dragIx);
657
+ dropRecord = Repository.getByIx(dropIx);
658
+ if (dropRecord) {
659
+ Repository.reorder(dragRecord, dropRecord, useBottom ? DROP_POSITION_AFTER : DROP_POSITION_BEFORE);
660
+ }
736
661
  } else {
737
662
  function arrayMove(arr, fromIndex, toIndex) {
738
663
  var element = arr[fromIndex];
@@ -743,10 +668,8 @@ function GridComponent(props) {
743
668
  }
744
669
  }
745
670
 
746
- if (dragRowSlot) {
747
- dragRowSlot.marker.remove();
748
- }
749
- setDragRowSlot(null);
671
+ marker.remove();
672
+ cachedDragElements.current = null;
750
673
  },
751
674
  calculatePageSize = (containerHeight) => {
752
675
  const
@@ -1027,15 +950,13 @@ function GridComponent(props) {
1027
950
  }
1028
951
 
1029
952
  let grid = <FlatList
953
+ testID="flatlist"
1030
954
  ref={gridRef}
1031
955
  scrollEnabled={CURRENT_MODE === UI_MODE_WEB}
1032
956
  nestedScrollEnabled={true}
1033
957
  contentContainerStyle={{
1034
958
  overflow: 'auto',
1035
- borderWidth: isDragMode ? styles.REORDER_BORDER_WIDTH : 0,
1036
- borderColor: isDragMode ? styles.REORDER_BORDER_COLOR : null,
1037
- borderStyle: styles.REORDER_BORDER_STYLE,
1038
- flex: 1,
959
+ height: '100%',
1039
960
  }}
1040
961
  refreshing={isLoading}
1041
962
  onRefresh={pullToRefresh ? onRefresh : null}
@@ -1056,6 +977,7 @@ function GridComponent(props) {
1056
977
  bg="trueGray.100"
1057
978
  {...flatListProps}
1058
979
  />
980
+
1059
981
  if (CURRENT_MODE === UI_MODE_REACT_NATIVE) {
1060
982
  grid = <ScrollView flex={1} w="100%">{grid}</ScrollView>
1061
983
  }
@@ -1069,8 +991,28 @@ function GridComponent(props) {
1069
991
  }
1070
992
  }
1071
993
 
994
+ const gridContainerBorderProps = {};
995
+ if (isDragMode) {
996
+ gridContainerBorderProps.borderWidth = styles.REORDER_BORDER_WIDTH;
997
+ gridContainerBorderProps.borderColor = styles.REORDER_BORDER_COLOR;
998
+ gridContainerBorderProps.borderStyle = styles.REORDER_BORDER_STYLE;
999
+ gridContainerBorderProps.borderTopWidth = null;
1000
+ if (isLoading) {
1001
+ gridContainerBorderProps.borderTopColor = '#f00';
1002
+ } else {
1003
+ gridContainerBorderProps.borderTopColor = null;
1004
+ }
1005
+ } else if (isLoading) {
1006
+ gridContainerBorderProps.borderTopWidth = 4;
1007
+ gridContainerBorderProps.borderTopColor = '#f00';
1008
+ } else {
1009
+ gridContainerBorderProps.borderTopWidth = 1;
1010
+ gridContainerBorderProps.borderTopColor = 'trueGray.300';
1011
+ }
1012
+
1072
1013
  grid = <Column
1073
1014
  {...testProps('Grid')}
1015
+ testID="outerContainer"
1074
1016
  ref={containerRef}
1075
1017
  w="100%"
1076
1018
  bg={bg}
@@ -1081,11 +1023,19 @@ function GridComponent(props) {
1081
1023
  >
1082
1024
  {topToolbar}
1083
1025
 
1084
- <Column ref={gridContainerRef} w="100%" flex={1} minHeight={40} borderTopWidth={isLoading ? 2 : 1} borderTopColor={isLoading ? '#f00' : 'trueGray.300'} onClick={() => {
1085
- if (!isDragMode && !isInlineEditorShown) {
1086
- deselectAll();
1087
- }
1088
- }}>
1026
+ <Column
1027
+ testID="gridContainer"
1028
+ ref={gridContainerRef}
1029
+ w="100%"
1030
+ flex={1}
1031
+ minHeight={40}
1032
+ {...gridContainerBorderProps}
1033
+ onClick={() => {
1034
+ if (!isDragMode && !isInlineEditorShown) {
1035
+ deselectAll();
1036
+ }
1037
+ }}
1038
+ >
1089
1039
  {grid}
1090
1040
  </Column>
1091
1041
 
@@ -1,4 +1,4 @@
1
- import { useMemo, } from 'react';
1
+ import { useEffect, useMemo, useRef, } from 'react';
2
2
  import {
3
3
  Box,
4
4
  Row,
@@ -10,7 +10,6 @@ import {
10
10
  import getComponentFromType from '../../Functions/getComponentFromType.js';
11
11
  import UiGlobals from '../../UiGlobals.js';
12
12
  import { withDragSource, withDropTarget } from '../Hoc/withDnd.js';
13
- import withDraggable from '../Hoc/withDraggable.js';
14
13
  import AngleRight from '../Icons/AngleRight.js';
15
14
  import RowDragHandle from './RowDragHandle.js';
16
15
  import _ from 'lodash';
@@ -30,6 +29,8 @@ function GridRow(props) {
30
29
  isDraggable = false, // withDraggable
31
30
  isDragSource = false, // withDnd
32
31
  isOver = false,
32
+ dragSourceRef,
33
+ dropTargetRef,
33
34
  } = props,
34
35
  styles = UiGlobals.styles;
35
36
 
@@ -199,11 +200,11 @@ function GridRow(props) {
199
200
  />}
200
201
  </>;
201
202
 
202
- if (props.dragSourceRef) {
203
- rowContents = <Row flexGrow={1} flex={1} w="100%" bg={bg} ref={props.dragSourceRef}>{rowContents}</Row>;
203
+ if (dragSourceRef) {
204
+ rowContents = <Row flexGrow={1} flex={1} w="100%" bg={bg} ref={dragSourceRef}>{rowContents}</Row>;
204
205
  }
205
- if (props.dropTargetRef) {
206
- rowContents = <Row flexGrow={1} flex={1} w="100%" bg={bg} ref={props.dropTargetRef}>{rowContents}</Row>;
206
+ if (dropTargetRef) {
207
+ rowContents = <Row flexGrow={1} flex={1} w="100%" bg={bg} ref={dropTargetRef}>{rowContents}</Row>;
207
208
  }
208
209
 
209
210
  return <Row
@@ -225,7 +226,14 @@ function GridRow(props) {
225
226
  hash, // this is an easy way to determine if the data has changed and the item needs to be rerendered
226
227
  isInlineEditorShown,
227
228
  isOver,
229
+ dragSourceRef,
230
+ dropTargetRef,
228
231
  ]);
229
232
  }
230
233
 
231
- export default withDraggable(withDragSource(withDropTarget(GridRow)));
234
+ // export default withDraggable(withDragSource(withDropTarget(GridRow)));
235
+ export default GridRow;
236
+
237
+ export const DragSourceGridRow = withDragSource(GridRow);
238
+ export const DropTargetGridRow = withDropTarget(GridRow);
239
+ export const DragSourceDropTargetGridRow = withDragSource(withDropTarget(GridRow));
@@ -1,7 +1,12 @@
1
- import { useDrag, useDrop } from 'react-dnd'; // https://react-dnd.github.io/react-dnd/about don't forget the wrapping <DndProvider /> as shown here: https://react-dnd.github.io/react-dnd/docs/api/dnd-provider
1
+ import { useEffect, useRef, } from 'react';
2
+ import { useDrag, useDrop, useDragLayer } from 'react-dnd'; // https://react-dnd.github.io/react-dnd/about don't forget the wrapping <DndProvider /> as shown here: https://react-dnd.github.io/react-dnd/docs/api/dnd-provider
3
+
2
4
 
3
5
  // This HOC allows components to be dragged and dropped onto another component.
4
- // It doesn't contraint the moment of the preview item.
6
+ // It can't contrain the movement of the preview item, because react-dnd uses
7
+ // a native drag layer for its proxy, and there are no contraints on that provided by OS.
8
+ // If you need constraints, you can potentially use a CustomDragLayer (see code at bottom)
9
+ // but it will lag behind, compared to what the native drag layer can do
5
10
 
6
11
  export function withDragSource(WrappedComponent) {
7
12
  return (props) => {
@@ -16,47 +21,71 @@ export function withDragSource(WrappedComponent) {
16
21
  if (!props.dragSourceItem) {
17
22
  throw Error('dragSourceItem not defined');
18
23
  }
19
-
24
+
20
25
  const {
21
26
  dragSourceType,
22
27
  dragSourceItem,
28
+ dragPreviewOptions = null,
29
+ dragOptions = null,
30
+ dropEffect = null,
31
+ // onDrag,
32
+ onDragEnd = null,
33
+ canDrag = null,
34
+ isDragging = null,
35
+ dragCollect = (monitor, props2) => { // Optional. The collecting function. It should return a plain object of the props to return for injection into your component. It receives two parameters, monitor and props. Read the overview for an introduction to the monitors and the collecting function. See the collecting function described in detail in the next section.
36
+ // monitor fn determines which props from dnd state get passed
37
+ return {
38
+ // canDrag: !!monitor.canDrag(), // Returns trueif no drag operation is in progress, and the owner's canDrag() returns true or is not defined.
39
+ isDragging: !!monitor.isDragging(), // Returns trueif a drag operation is in progress, and either the owner initiated the drag, or its isDragging() is defined and returns true.
40
+ type: monitor.getItemType(), // Returns a string or a symbol identifying the type of the current dragged item. Returns null if no item is being dragged.
41
+ item: monitor.getItem(), // Returns a plain object representing the currently dragged item. Every drag source must specify it by returning an object from its beginDrag() method. Returns nullif no item is being dragged.
42
+ dropResult: monitor.getDropResult(), // Returns a plain object representing the last recorded drop result. The drop targets may optionally specify it by returning an object from their drop()methods. When a chain of drop()is dispatched for the nested targets, bottom up, any parent that explicitly returns its own result from drop()overrides the child drop result previously set by the child. Returns nullif called outside endDrag().
43
+ didDrop: !!monitor.didDrop(), // Returns trueif some drop target has handled the drop event, falseotherwise. Even if a target did not return a drop result, didDrop() returns true. Use it inside endDrag()to test whether any drop target has handled the drop. Returns falseif called outside endDrag().
44
+ initialClientOffset: monitor.getInitialClientOffset(), // Returns the { x, y }client offset of the pointer at the time when the current drag operation has started. Returns nullif no item is being dragged.
45
+ initialSourceClientOffset: monitor.getInitialSourceClientOffset(), // Returns the { x, y }client offset of the drag source component's root DOM node at the time when the current drag operation has started. Returns nullif no item is being dragged.
46
+ clientOffset: monitor.getClientOffset(), // Returns the last recorded { x, y }client offset of the pointer while a drag operation is in progress. Returns nullif no item is being dragged.
47
+ differenceFromInitialOffset: monitor.getDifferenceFromInitialOffset(), // Returns the { x, y }difference between the last recorded client offset of the pointer and the client offset when the current drag operation has started. Returns nullif no item is being dragged.
48
+ sourceClientOffset: monitor.getSourceClientOffset(), // Returns the projected { x, y }client offset of the drag source component's root DOM node, based on its position at the time when the current drag operation has started, and the movement difference. Returns nullif no item is being dragged.
49
+ };
50
+ },
23
51
  } = props,
24
52
  [dragState, dragSourceRef, dragPreviewRef] = useDrag(() => { // A specification object or a function that creates a specification object.
25
53
  // The useDrag hook provides a way to wire your component into the DnD system as a drag source. By passing in a specification into useDrag, you declaratively describe the typeof draggable being generated, the itemobject representing the drag source, what props to collect, and more. The useDraghooks returns a few key items: a set of collected props, and refs that may be attached to drag source and drag preview elements
54
+
26
55
  return {
27
56
  type: dragSourceType, // Required. This must be either a string or a symbol. Only the drop targets registered for the same type will react to this item.
28
57
  item: dragSourceItem, // Required (object or function).
29
58
  // When an object, it is a plain JavaScript object describing the data being dragged. This is the only information available to the drop targets about the drag source so it's important to pick the minimal data they need to know. You may be tempted to put a complex reference here, but you should try very hard to avoid doing this because it couples the drag sources and drop targets. It's a good idea to use something like { id }.
30
59
  // When a function, it is fired at the beginning of the drag operation and returns an object representing the drag operation (see first bullet). If null is returned, the drag operation is cancelled.
31
- previewOptions: null, // Optional. A plain JavaScript object describing drag preview options.
32
- options: null, // Optional. A plain object optionally containing any of the following properties:
33
- // dropEffect: Optional: The type of drop effect to use on this drag. ("move" or "copy" are valid values.)
34
- end: null, // (item, monitor) Optional. When the dragging stops, endis called. For every begin call, a corresponding end call is guaranteed. You may call monitor.didDrop() to check whether or not the drop was handled by a compatible drop target. If it was handled, and the drop target specified a drop result by returning a plain object from its drop()method, it will be available as monitor.getDropResult(). This method is a good place to fire a Flux action. Note: If the component is unmounted while dragging, componentparameter is set to be null.
35
- canDrag: null, // (monitor): Optional. Use it to specify whether the dragging is currently allowed. If you want to always allow it, just omit this method. Specifying it is handy if you'd like to disable dragging based on some predicate over props. Note: You may not call monitor.canDrag()inside this method.
36
- isDragging: null, // (monitor): Optional. By default, only the drag source that initiated the drag operation is considered to be dragging. You can override this behavior by defining a custom isDraggingmethod. It might return something like props.id === monitor.getItem().id. Do this if the original component may be unmounted during the dragging and later “resurrected” with a different parent. For example, when moving a card across the lists in a Kanban board, you want it to retain the dragged appearance—even though technically, the component gets unmounted and a different one gets mounted every time you move it to another list. Note: You may not call monitor.isDragging()inside this method.
37
- collect: (monitor, props) => { // Optional. The collecting function. It should return a plain object of the props to return for injection into your component. It receives two parameters, monitor and props. Read the overview for an introduction to the monitors and the collecting function. See the collecting function described in detail in the next section.
60
+ previewOptions: dragPreviewOptions, // Optional. A plain JavaScript object describing drag preview options.
61
+ options: dragOptions, // Optional. A plain object optionally containing any of the following properties:
62
+ dropEffect, // Optional: The type of drop effect to use on this drag. ("move" or "copy" are valid values.)
63
+ end: onDragEnd, // (item, monitor) Optional. When the dragging stops, endis called. For every begin call, a corresponding end call is guaranteed. You may call monitor.didDrop() to check whether or not the drop was handled by a compatible drop target. If it was handled, and the drop target specified a drop result by returning a plain object from its drop()method, it will be available as monitor.getDropResult(). This method is a good place to fire a Flux action. Note: If the component is unmounted while dragging, componentparameter is set to be null.
64
+ canDrag, // (monitor): Optional. Use it to specify whether the dragging is currently allowed. If you want to always allow it, just omit this method. Specifying it is handy if you'd like to disable dragging based on some predicate over props. Note: You may not call monitor.canDrag()inside this method.
65
+ isDragging, // (monitor): Optional. By default, only the drag source that initiated the drag operation is considered to be dragging. You can override this behavior by defining a custom isDraggingmethod. It might return something like props.id === monitor.getItem().id. Do this if the original component may be unmounted during the dragging and later “resurrected” with a different parent. For example, when moving a card across the lists in a Kanban board, you want it to retain the dragged appearance—even though technically, the component gets unmounted and a different one gets mounted every time you move it to another list. Note: You may not call monitor.isDragging()inside this method.
66
+ collect: dragCollect, /* (monitor, props) => { // Optional. The collecting function. It should return a plain object of the props to return for injection into your component. It receives two parameters, monitor and props. Read the overview for an introduction to the monitors and the collecting function. See the collecting function described in detail in the next section.
38
67
  // monitor fn determines which props from dnd state get passed
39
68
  return {
40
69
  canDrag: !!monitor.canDrag(), // Returns trueif no drag operation is in progress, and the owner's canDrag() returns true or is not defined.
41
70
  isDragging: !!monitor.isDragging(), // Returns trueif a drag operation is in progress, and either the owner initiated the drag, or its isDragging() is defined and returns true.
42
71
  // type: monitor.getItemType(), // Returns a string or a symbol identifying the type of the current dragged item. Returns null if no item is being dragged.
43
- // item: monitor.getItem(), // Returns a plain object representing the currently dragged item. Every drag source must specify it by returning an object from its beginDrag()method. Returns nullif no item is being dragged.
72
+ // item: monitor.getItem(), // Returns a plain object representing the currently dragged item. Every drag source must specify it by returning an object from its beginDrag() method. Returns nullif no item is being dragged.
44
73
  // dropResult: monitor.getDropResult(), // Returns a plain object representing the last recorded drop result. The drop targets may optionally specify it by returning an object from their drop()methods. When a chain of drop()is dispatched for the nested targets, bottom up, any parent that explicitly returns its own result from drop()overrides the child drop result previously set by the child. Returns nullif called outside endDrag().
45
- // didDrop: !!monitor.didDrop(), // Returns trueif some drop target has handled the drop event, falseotherwise. Even if a target did not return a drop result, didDrop()returns true. Use it inside endDrag()to test whether any drop target has handled the drop. Returns falseif called outside endDrag().
74
+ // didDrop: !!monitor.didDrop(), // Returns trueif some drop target has handled the drop event, falseotherwise. Even if a target did not return a drop result, didDrop() returns true. Use it inside endDrag()to test whether any drop target has handled the drop. Returns falseif called outside endDrag().
46
75
  // initialClientOffset: monitor.getInitialClientOffset(), // Returns the { x, y }client offset of the pointer at the time when the current drag operation has started. Returns nullif no item is being dragged.
47
76
  // initialSourceClientOffset: monitor.getInitialSourceClientOffset(), // Returns the { x, y }client offset of the drag source component's root DOM node at the time when the current drag operation has started. Returns nullif no item is being dragged.
48
77
  // clientOffset: monitor.getClientOffset(), // Returns the last recorded { x, y }client offset of the pointer while a drag operation is in progress. Returns nullif no item is being dragged.
49
78
  // differenceFromInitialOffset: monitor.getDifferenceFromInitialOffset(), // Returns the { x, y }difference between the last recorded client offset of the pointer and the client offset when the current drag operation has started. Returns nullif no item is being dragged.
50
79
  // sourceClientOffset: monitor.getSourceClientOffset(), // Returns the projected { x, y }client offset of the drag source component's root DOM node, based on its position at the time when the current drag operation has started, and the movement difference. Returns nullif no item is being dragged.
51
80
  };
52
- },
81
+ },*/
53
82
  };
54
83
  }),
55
84
  {
56
- canDrag,
57
- isDragging,
85
+ canDrag: stateCanDrag,
86
+ isDragging: stateIsDragging,
58
87
  // type,
59
- // item,
88
+ // item: stateItem,
60
89
  // dropResult,
61
90
  // didDrop,
62
91
  // initialClientOffset,
@@ -65,12 +94,24 @@ export function withDragSource(WrappedComponent) {
65
94
  // differenceFromInitialOffset,
66
95
  // sourceClientOffset,
67
96
  } = dragState;
97
+
98
+ if (dragSourceItem.onDrag) {
99
+ // This will enable the onDrag callback, as useDragLayer will cause withDragSource to re-render any time state changes. useDrag will not
100
+ const layer = useDragLayer(dragCollect);
101
+ useEffect(() => {
102
+ if (layer.isDragging && dragSourceItem?.id === layer.item.id) {
103
+ dragSourceItem.onDrag(layer);
104
+ }
105
+ }, [layer]);
106
+ }
68
107
 
69
108
  return <WrappedComponent
70
- canDrag={canDrag}
71
- isDragging={isDragging}
72
- dragSourceRef={dragSourceRef}
73
109
  {...props}
110
+ canDrag={stateCanDrag}
111
+ isDragging={stateIsDragging}
112
+ dragSourceRef={dragSourceRef}
113
+ dragPreviewRef={dragPreviewRef}
114
+ dragState={dragState}
74
115
  />;
75
116
  };
76
117
  }
@@ -78,6 +119,7 @@ export function withDragSource(WrappedComponent) {
78
119
 
79
120
  export function withDropTarget(WrappedComponent) {
80
121
  return (props) => {
122
+
81
123
  if (!props.isDropTarget) {
82
124
  return <WrappedComponent {...props} />;
83
125
  }
@@ -88,17 +130,27 @@ export function withDropTarget(WrappedComponent) {
88
130
 
89
131
  const {
90
132
  dropTargetAccept,
133
+ dropOptions = null,
91
134
  onDrop = null,
135
+ onDropHover = null,
136
+ canDrop = null,
137
+ dropCollect = (monitor, props) => {
138
+ return {
139
+ canDrop: !!monitor.canDrop(),
140
+ isOver: !!monitor.isOver(),
141
+ };
142
+ },
92
143
  } = props,
144
+ localTargetRef = useRef(null),
93
145
  [dropState, dropTargetRef] = useDrop(() => { // A specification object or a function that creates a specification object.
94
146
  // The useDrophook provides a way for you to wire in your component into the DnD system as a drop target. By passing in a specification into the useDrophook, you can specify including what types of data items the drop-target will accept, what props to collect, and more. This function returns an array containing a ref to attach to the Drop Target node and the collected props.
95
147
  return {
96
148
  accept: dropTargetAccept, // Required. A string, a symbol, or an array of either. This drop target will only react to the items produced by the drag sources of the specified type or types. Read the overview to learn more about the items and types.
97
- // options: null, // Optional. A plain object. If some of the props to your component are not scalar (that is, are not primitive values or functions), specifying a custom arePropsEqual(props, otherProps) function inside the options object can improve the performance. Unless you have performance problems, don't worry about it.
149
+ options: dropOptions, // Optional. A plain object. If some of the props to your component are not scalar (that is, are not primitive values or functions), specifying a custom arePropsEqual(props, otherProps) function inside the options object can improve the performance. Unless you have performance problems, don't worry about it.
98
150
  drop: onDrop, // (item, monitor): Optional. Called when a compatible item is dropped on the target. You may either return undefined, or a plain object. If you return an object, it is going to become the drop result and will be available to the drag source in its endDragmethod as monitor.getDropResult(). This is useful in case you want to perform different actions depending on which target received the drop. If you have nested drop targets, you can test whether a nested target has already handled dropby checking monitor.didDrop()and monitor.getDropResult(). Both this method and the source's endDragmethod are good places to fire Flux actions. This method will not be called if canDrop()is defined and returns false.
99
- // hover: null, // (item, monitor): Optional. Called when an item is hovered over the component. You can check monitor.isOver({ shallow: true })to test whether the hover happens over only the current target, or over a nested one. Unlike drop(), this method will be called even if canDrop()is defined and returns false. You can check monitor.canDrop()to test whether this is the case.
100
- // canDrop: null, // (item, monitor): Optional. Use it to specify whether the drop target is able to accept the item. If you want to always allow it, omit this method. Specifying it is handy if you'd like to disable dropping based on some predicate over props or monitor.getItem(). Note: You may not call monitor.canDrop() inside this method.
101
- collect: (monitor, props) => { // Optional. The collecting function. It should return a plain object of the props to return for injection into your component. It receives two parameters, monitorand props. Read the overview for an introduction to the monitors and the collecting function. See the collecting function described in detail in the next section.
151
+ hover: onDropHover, // (item, monitor): Optional. Called when an item is hovered over the component. You can check monitor.isOver({ shallow: true })to test whether the hover happens over only the current target, or over a nested one. Unlike drop(), this method will be called even if canDrop()is defined and returns false. You can check monitor.canDrop()to test whether this is the case.
152
+ canDrop, // (item, monitor): Optional. Use it to specify whether the drop target is able to accept the item. If you want to always allow it, omit this method. Specifying it is handy if you'd like to disable dropping based on some predicate over props or monitor.getItem(). Note: You may not call monitor.canDrop() inside this method.
153
+ collect: dropCollect, /*: (monitor, props) => { // Optional. The collecting function. It should return a plain object of the props to return for injection into your component. It receives two parameters, monitorand props. Read the overview for an introduction to the monitors and the collecting function. See the collecting function described in detail in the next section.
102
154
  return {
103
155
  canDrop: !!monitor.canDrop(),
104
156
  isOver: !!monitor.isOver(),
@@ -112,11 +164,11 @@ export function withDropTarget(WrappedComponent) {
112
164
  // receiveHandlerId
113
165
  // subscribeToStateChange
114
166
  };
115
- },
167
+ },*/
116
168
  };
117
169
  }),
118
170
  {
119
- canDrop,
171
+ canDrop: stateCanDrop,
120
172
  isOver,
121
173
  // didDrop,
122
174
  // clientOffset,
@@ -127,11 +179,70 @@ export function withDropTarget(WrappedComponent) {
127
179
  // initialSourceClientOffset,
128
180
  } = dropState;
129
181
 
182
+ dropTargetRef(localTargetRef); // register DOM node with react-dnd
183
+
130
184
  return <WrappedComponent
131
- canDrop={canDrop}
185
+ canDrop={stateCanDrop}
132
186
  isOver={isOver}
133
- dropTargetRef={dropTargetRef}
187
+ dropTargetRef={localTargetRef}
134
188
  {...props}
135
189
  />;
136
190
  };
137
191
  }
192
+
193
+
194
+ // export function CustomDragLayer(props) {
195
+
196
+ // // if (CURRENT_MODE !== UI_MODE_WEB) {
197
+ // // throw Error('CustomDragLayer only works in web mode');
198
+ // // }
199
+
200
+ // const
201
+ // {
202
+ // onDrag,
203
+ // axis = null,
204
+ // } = props,
205
+ // layer = useDragLayer((monitor) => ({
206
+ // isDragging: monitor.isDragging(),
207
+ // item: monitor.getItem(),
208
+ // currentOffset: monitor.getSourceClientOffset(),
209
+ // })),
210
+ // { isDragging, item, currentOffset } = layer;
211
+
212
+ // useEffect(() => {
213
+ // if (layer.isDragging) {
214
+ // onDrag(layer);
215
+ // }
216
+ // }, [layer]);
217
+
218
+ // return null;
219
+
220
+ // if (!isDragging || !currentOffset) {
221
+ // return null;
222
+ // }
223
+
224
+ // let transform;
225
+ // if (axis === 'x') {
226
+ // transform = `translate(${currentOffset.x}px, 0)`;
227
+ // } else if (axis === 'y') {
228
+ // transform = `translate(0, ${currentOffset.y}px)`;
229
+ // } else {
230
+ // transform = `translate(${currentOffset.x}px, ${currentOffset.y}px)`;
231
+ // }
232
+
233
+ // return (
234
+ // <div id="dragLayer" style={{
235
+ // background: '#f00',
236
+ // position: 'fixed',
237
+ // pointerEvents: 'none',
238
+ // zIndex: 10000,
239
+ // width: '200px',
240
+ // height: '10px',
241
+ // left: 0,
242
+ // top: 0,
243
+ // transform,
244
+ // }}>
245
+ // {children}
246
+ // </div>
247
+ // );
248
+ // }