@onehat/ui 0.3.227 → 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.227",
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,12 +204,13 @@ 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
- [cachedDragElements, setCachedDragElements] = useState(null),
213
214
  [dragRow, setDragRow] = useState(),
214
215
  getIsExpanded = (index) => {
215
216
  return !!expandedRowsRef.current[index];
@@ -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
387
- }
388
-
389
- // context menu
390
- const selection = [item];
391
- if (!disableWithSelection) {
392
- setSelection(selection);
369
+ }
370
+ break;
371
+ case 3: // triple click
372
+ break;
373
+ default:
393
374
  }
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,31 +530,10 @@ 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
-
517
- setDragRow(row);
518
-
519
- proxy.style.top = top + 'px';
520
- proxy.style.left = '20px';
521
- proxy.style.height = rowRect.height + 'px';
522
- proxy.style.width = rowRect.width + 'px';
523
- proxy.style.display = 'flex';
524
- // proxy.style.backgroundColor = '#ccc';
525
- proxy.style.position = 'absolute';
526
- proxy.style.border = '1px solid #000';
527
- return proxy;
528
- },
529
533
  getOverState = (rows, currentY, mouseX) => {
530
534
  // determines which row the mouse is over
531
535
  // and whether the marker should be moved to the top or bottom of the row
532
- let newIx = 0,
536
+ let newIx = -1,
533
537
  useBottom = false;
534
538
  _.each(rows, (row, ix) => {
535
539
  const
@@ -562,71 +566,89 @@ function GridComponent(props) {
562
566
  useBottom,
563
567
  };
564
568
  },
565
- onRowReorderDragStart = (info, e, proxy, node) => {
566
- // console.log('onRowReorderDragStart', info, e, proxy, node);
567
-
569
+ buildCachedDragElements = (dragState) => {
568
570
  const
569
- proxyRect = proxy.getBoundingClientRect(),
570
- row = node.parentElement.parentElement,
571
- flatlist = row.parentElement,
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,
572
581
  flatlistRect = flatlist.getBoundingClientRect(),
573
582
  rows = _.filter(flatlist.childNodes, (childNode, ix) => {
574
583
  const
575
584
  isZeroHeight = childNode.getBoundingClientRect().height === 0,
576
- isProxy = childNode === proxy,
577
585
  isHeader = showHeaders && ix === 0;
578
- return !isZeroHeight && !isProxy && !isHeader;
586
+ return !isZeroHeight && !isHeader;
579
587
  }),
580
- currentY = proxyRect.top - flatlistRect.top, // top position of pointer, relative to page
581
- { ix, useBottom } = getOverState(rows, currentY, e.clientX);
588
+ { ix, useBottom } = getOverState(rows, clientOffset.y, clientOffset.x);
582
589
 
583
590
 
584
591
  // Render marker showing destination location
585
- const
586
- rowContainerRect = rows[ix].getBoundingClientRect(),
587
- top = (useBottom ? rowContainerRect.bottom : rowContainerRect.top)
588
- - flatlistRect.top
589
- - (flatlist.style.borderWidth ? parseInt(flatlist.style.borderWidth) : 0), // get relative Y position
590
- marker = document.createElement('div');
592
+ const marker = document.createElement('div');
591
593
  marker.style.position = 'absolute';
592
- marker.style.top = top + 'px';
593
- marker.style.height = '4px';
594
+ marker.style.top = '0px';
595
+ marker.style.height = '8px';
594
596
  marker.style.width = flatlistRect.width + 'px';
595
- marker.style.backgroundColor = '#f00';
597
+ marker.style.backgroundColor = '#ccc';
596
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
+ }
597
611
 
598
- setCachedDragElements({ ix, useBottom, marker, rows, });
612
+ return { ix, useBottom, marker, rows };
599
613
  },
600
- onRowReorderDrag = (info, e, proxy, node) => {
601
- // console.log('onRowReorderDrag', info, e, proxy, node);
614
+ onRowReorderDrag = (dragState, dragIx) => {
615
+ // initial setup
616
+ if (!cachedDragElements.current) {
617
+ cachedDragElements.current = buildCachedDragElements(dragState);
618
+ }
619
+
602
620
  const
603
- { marker, rows, } = cachedDragElements,
604
- proxyRect = proxy.getBoundingClientRect(),
605
- row = node.parentElement.parentElement,
606
- flatlist = row.parentElement,
621
+ {
622
+ canDrag,
623
+ isDragging,
624
+ clientOffset,
625
+ sourceClientOffset,
626
+ } = dragState,
627
+ { marker, rows, } = cachedDragElements.current,
628
+ flatlist = gridRef.current._listRef._scrollRef.childNodes[0],
607
629
  flatlistRect = flatlist.getBoundingClientRect(),
608
- currentY = proxyRect.top - flatlistRect.top, // top position of pointer, relative to page
609
- { ix, useBottom } = getOverState(rows, currentY, e.clientX);
610
-
630
+ { ix, useBottom } = getOverState(rows, clientOffset.y, clientOffset.x);
611
631
 
612
632
  // move marker to new location
613
- const
614
- rowContainerRect = rows[ix].getBoundingClientRect(),
615
- top = (useBottom ? rowContainerRect.bottom : rowContainerRect.top)
616
- - flatlistRect.top
617
- - (flatlist.style.borderWidth ? parseInt(flatlist.style.borderWidth) : 0); // get relative Y position
618
- marker.style.top = top + 'px';
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';
643
+ }
619
644
 
620
- setCachedDragElements({ ix, useBottom, marker, rows, });
645
+ cachedDragElements.current = { ix, useBottom, marker, rows, dragIx };
621
646
  },
622
- onRowReorderDragStop = (delta, e, config) => {
623
- // console.log('onRowReorderDragStop', delta, e, config);
647
+ onRowReorderEnd = (item, monitor) => {
624
648
  const
625
- { ix: dropIx, useBottom, marker, rows, } = cachedDragElements,
626
- dragIx = rows.indexOf(dragRow);
627
-
628
- const shouldMove = dropIx !== dragIx;
629
- if (shouldMove) {
649
+ { ix: dropIx, useBottom, marker, rows, dragIx } = cachedDragElements.current,
650
+ shouldMove = dropIx !== dragIx;
651
+ if (shouldMove && dropIx !== -1) {
630
652
  // Update the row with the new ix
631
653
  let dragRecord,
632
654
  dropRecord;
@@ -647,7 +669,7 @@ function GridComponent(props) {
647
669
  }
648
670
 
649
671
  marker.remove();
650
- setCachedDragElements(null);
672
+ cachedDragElements.current = null;
651
673
  },
652
674
  calculatePageSize = (containerHeight) => {
653
675
  const
@@ -934,7 +956,7 @@ function GridComponent(props) {
934
956
  nestedScrollEnabled={true}
935
957
  contentContainerStyle={{
936
958
  overflow: 'auto',
937
- // flex: 1,
959
+ height: '100%',
938
960
  }}
939
961
  refreshing={isLoading}
940
962
  onRefresh={pullToRefresh ? onRefresh : null}
@@ -970,15 +992,21 @@ function GridComponent(props) {
970
992
  }
971
993
 
972
994
  const gridContainerBorderProps = {};
973
- if (isLoading) {
974
- gridContainerBorderProps.borderTopWidth = 2;
975
- gridContainerBorderProps.borderTopColor = '#f00';
976
- } else if (isDragMode) {
995
+ if (isDragMode) {
977
996
  gridContainerBorderProps.borderWidth = styles.REORDER_BORDER_WIDTH;
978
997
  gridContainerBorderProps.borderColor = styles.REORDER_BORDER_COLOR;
979
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';
980
1008
  } else {
981
- gridContainerBorderProps.borderTopWidth = 1
1009
+ gridContainerBorderProps.borderTopWidth = 1;
982
1010
  gridContainerBorderProps.borderTopColor = 'trueGray.300';
983
1011
  }
984
1012
 
@@ -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
+ // }