@onehat/ui 0.3.213 → 0.3.216

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.213",
3
+ "version": "0.3.216",
4
4
  "description": "Base UI for OneHat apps",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -46,6 +46,9 @@
46
46
  "react-color": "^2.19.3",
47
47
  "react-datetime": "^3.2.0",
48
48
  "react-dom": "*",
49
+ "react-dnd": "^16.0.1",
50
+ "react-dnd-html5-backend": "^16.0.1",
51
+ "react-dnd-touch-backend":"16.0.1",
49
52
  "react-draggable": "^4.4.5",
50
53
  "react-native": "*",
51
54
  "react-native-draggable": "^3.3.0",
@@ -0,0 +1,17 @@
1
+ import IconButton from './IconButton.js';
2
+ import Plus from '../Icons/Plus.js';
3
+ import Minus from '../Icons/Minus.js';
4
+ import _ from 'lodash';
5
+
6
+ export default function ExpandButton(props) {
7
+ const {
8
+ isExpanded = false,
9
+ onToggle,
10
+ } = props;
11
+
12
+ return <IconButton
13
+ icon={isExpanded ? Minus : Plus}
14
+ onPress={onToggle}
15
+ {...props}
16
+ />;
17
+ };
@@ -1,5 +1,6 @@
1
1
  import React, { useState, useEffect, useRef, useMemo, useCallback, } from 'react';
2
2
  import {
3
+ Box,
3
4
  Column,
4
5
  FlatList,
5
6
  Pressable,
@@ -34,6 +35,7 @@ import withContextMenu from '../Hoc/withContextMenu.js';
34
35
  import withAlert from '../Hoc/withAlert.js';
35
36
  import withComponent from '../Hoc/withComponent.js';
36
37
  import withData from '../Hoc/withData.js';
38
+ import { withDropTarget } from '../Hoc/withDnd.js';
37
39
  import withEvents from '../Hoc/withEvents.js';
38
40
  import withSideEditor from '../Hoc/withSideEditor.js';
39
41
  import withFilters from '../Hoc/withFilters.js';
@@ -49,8 +51,9 @@ import testProps from '../../Functions/testProps.js';
49
51
  import nbToRgb from '../../Functions/nbToRgb.js';
50
52
  import Loading from '../Messages/Loading.js';
51
53
  import GridHeaderRow from './GridHeaderRow.js';
52
- import GridRow, { ReorderableGridRow } from './GridRow.js';
54
+ import GridRow from './GridRow.js';
53
55
  import IconButton from '../Buttons/IconButton.js';
56
+ import ExpandButton from '../Buttons/ExpandButton.js';
54
57
  import PaginationToolbar from '../Toolbar/PaginationToolbar.js';
55
58
  import NoRecordsFound from './NoRecordsFound.js';
56
59
  import Toolbar from '../Toolbar/Toolbar.js';
@@ -95,22 +98,26 @@ function GridComponent(props) {
95
98
  },
96
99
  flatListProps = {},
97
100
  onRowPress,
98
- // enableEditors = false,
101
+ onRender,
99
102
  forceLoadOnRender = false,
100
103
  pullToRefresh = true,
101
104
  hideNavColumn = true,
102
105
  noneFoundText,
103
106
  autoAdjustPageSizeToHeight = true,
104
- disableLoadingIndicator = false,
105
107
  disableSelectorSelected = false,
106
108
  showRowExpander = false,
107
- rowExpanderTpl = '',
109
+ getExpandedRowContent,
108
110
  showHeaders = true,
109
111
  showHovers = true,
110
112
  canColumnsSort = true,
111
113
  canColumnsReorder = true,
112
114
  canColumnsResize = true,
113
115
  canRowsReorder = false,
116
+ areRowsDragSource = false,
117
+ rowDragSourceType,
118
+ areRowsDropTarget = false,
119
+ dropTargetAccept,
120
+ onRowDrop,
114
121
  allowToggleSelection = false, // i.e. single click with no shift key toggles the selection of the item clicked on
115
122
  disableBottomToolbar = false,
116
123
  disablePagination = false,
@@ -133,8 +140,6 @@ function GridComponent(props) {
133
140
  onEdit,
134
141
  onDelete,
135
142
  onView,
136
- onDuplicate,
137
- onReset,
138
143
  onContextMenu,
139
144
  isAdding,
140
145
 
@@ -147,6 +152,12 @@ function GridComponent(props) {
147
152
  idIx,
148
153
  displayIx,
149
154
 
155
+ // withDnd
156
+ isDropTarget,
157
+ canDrop,
158
+ isOver,
159
+ dropTargetRef,
160
+
150
161
  // withPresetButtons
151
162
  onChangeColumnsConfig,
152
163
 
@@ -191,6 +202,7 @@ function GridComponent(props) {
191
202
  gridRef = useRef(),
192
203
  gridContainerRef = useRef(),
193
204
  isAddingRef = useRef(),
205
+ expandedRowsRef = useRef({}),
194
206
  [isInited, setIsInited] = useState(false),
195
207
  [isReady, setIsReady] = useState(false),
196
208
  [isLoading, setIsLoading] = useState(false),
@@ -198,6 +210,13 @@ function GridComponent(props) {
198
210
  [isDragMode, setIsDragMode] = useState(false),
199
211
  [dragRowSlot, setDragRowSlot] = useState(null),
200
212
  [dragRowIx, setDragRowIx] = useState(),
213
+ getIsExpanded = (index) => {
214
+ return !!expandedRowsRef.current[index];
215
+ },
216
+ setIsExpanded = (index, isExpanded) => {
217
+ expandedRowsRef.current[index] = isExpanded;
218
+ forceUpdate();
219
+ },
201
220
  setLocalColumnsConfig = (config) => {
202
221
  if (localColumnsConfigKey && !hasFunctionColumn) {
203
222
  setSaved(localColumnsConfigKey, config);
@@ -312,7 +331,7 @@ function GridComponent(props) {
312
331
  rowProps = getRowProps && !isHeaderRow ? getRowProps(item) : {},
313
332
  isSelected = !isHeaderRow && !disableWithSelection && isInSelection(item);
314
333
 
315
- return <Pressable
334
+ let rowComponent = <Pressable
316
335
  // {...testProps(Repository ? Repository.schema.name + '-' + item.id : item.id)}
317
336
  onPress={(e) => {
318
337
  if (e.preventDefault && e.cancelable) {
@@ -421,23 +440,33 @@ function GridComponent(props) {
421
440
  ratio = mixWithObj.alpha ? 1 - mixWithObj.alpha : 0.5;
422
441
  bg = colourMixer.blend(bg, ratio, mixWithObj.color);
423
442
  }
424
- let WhichGridRow = GridRow,
425
- rowReorderProps = {};
443
+ const
444
+ rowReorderProps = {},
445
+ rowDragProps = {};
426
446
  if (canRowsReorder && isDragMode) {
427
- WhichGridRow = ReorderableGridRow;
428
- rowReorderProps = {
429
- mode: VERTICAL,
430
- onDragStart: onRowReorderDragStart,
431
- onDrag: onRowReorderDrag,
432
- onDragStop: onRowReorderDragStop,
433
- proxyParent: gridRef.current?.getScrollableNode().children[0],
434
- proxyPositionRelativeToParent: true,
435
- getParentNode: (node) => node.parentElement.parentElement.parentElement,
436
- getProxy: getReorderProxy,
447
+ rowReorderProps.isDraggable = true;
448
+ rowReorderProps.mode = VERTICAL;
449
+ rowReorderProps.onDragStart = onRowReorderDragStart;
450
+ rowReorderProps.onDrag = onRowReorderDrag;
451
+ rowReorderProps.onDragStop = onRowReorderDragStop;
452
+ rowReorderProps.proxyParent = gridRef.current?.getScrollableNode().children[0];
453
+ rowReorderProps.proxyPositionRelativeToParent = true;
454
+ rowReorderProps.getParentNode = (node) => node.parentElement.parentElement.parentElement;
455
+ rowReorderProps.getProxy = getReorderProxy;
456
+ }
457
+ if (areRowsDragSource) {
458
+ rowDragProps.isDragSource = true;
459
+ rowDragProps.dragSourceType = rowDragSourceType;
460
+ rowDragProps.dragSourceItem = { id: item.id };
461
+ }
462
+ if (areRowsDropTarget) {
463
+ rowDragProps.isDropTarget = true;
464
+ rowDragProps.dropTargetAccept = dropTargetAccept;
465
+ rowDragProps.onDrop = (droppedItem) => {
466
+ onRowDrop(item, droppedItem);
437
467
  };
438
468
  }
439
-
440
- return <WhichGridRow
469
+ return <GridRow
441
470
  columnsConfig={localColumnsConfig}
442
471
  columnProps={columnProps}
443
472
  fields={fields}
@@ -447,9 +476,29 @@ function GridComponent(props) {
447
476
  item={item}
448
477
  isInlineEditorShown={isInlineEditorShown}
449
478
  {...rowReorderProps}
479
+ {...rowDragProps}
450
480
  />;
451
481
  }}
452
482
  </Pressable>;
483
+
484
+ if (showRowExpander && !isHeaderRow) {
485
+ const isExpanded = getIsExpanded(index);
486
+ rowComponent = <Column>
487
+ <Row>
488
+ <ExpandButton
489
+ isExpanded={isExpanded}
490
+ onToggle={() => setIsExpanded(index, !isExpanded)}
491
+ _icon={{
492
+ size: 'sm',
493
+ }}
494
+ py={0}
495
+ />
496
+ {rowComponent}
497
+ </Row>
498
+ {isExpanded ? getExpandedRowContent(row) : null}
499
+ </Column>
500
+ }
501
+ return rowComponent;
453
502
  },
454
503
  getReorderProxy = (node) => {
455
504
  const
@@ -750,6 +799,9 @@ function GridComponent(props) {
750
799
  }
751
800
  Repository.pauseEvents();
752
801
  }
802
+ if (onRender) {
803
+ onRender(self)
804
+ }
753
805
  return () => {};
754
806
  }
755
807
 
@@ -857,6 +909,11 @@ function GridComponent(props) {
857
909
  if (!Repository.isAutoLoad) {
858
910
  Repository.reload();
859
911
  }
912
+ },
913
+ onChangePage = () => {
914
+ if (showRowExpander) {
915
+ expandedRowsRef.current = {}; // clear expanded rows
916
+ }
860
917
  };
861
918
 
862
919
  Repository.on('beforeLoad', setTrue);
@@ -867,7 +924,7 @@ function GridComponent(props) {
867
924
  Repository.ons(['changeData', 'change'], forceUpdate);
868
925
  Repository.on('changeFilters', onChangeFilters);
869
926
  Repository.on('changeSorters', onChangeSorters);
870
-
927
+ Repository.on('changePage', onChangePage);
871
928
 
872
929
  applySelectorSelected();
873
930
  Repository.resumeEvents();
@@ -885,6 +942,7 @@ function GridComponent(props) {
885
942
  Repository.offs(['changeData', 'change'], forceUpdate);
886
943
  Repository.off('changeFilters', onChangeFilters);
887
944
  Repository.off('changeSorters', onChangeSorters);
945
+ Repository.off('changePage', onChangePage);
888
946
  };
889
947
  }, [isInited]);
890
948
 
@@ -1006,7 +1064,7 @@ function GridComponent(props) {
1006
1064
  }
1007
1065
  }
1008
1066
 
1009
- return <Column
1067
+ grid = <Column
1010
1068
  {...testProps('Grid')}
1011
1069
  ref={containerRef}
1012
1070
  w="100%"
@@ -1028,7 +1086,18 @@ function GridComponent(props) {
1028
1086
 
1029
1087
  {listFooterComponent}
1030
1088
 
1031
- </Column>;
1089
+ </Column>
1090
+
1091
+ if (isDropTarget) {
1092
+ grid = <Box
1093
+ ref={dropTargetRef}
1094
+ borderWidth={canDrop && isOver ? 4 : 0}
1095
+ borderColor="#0ff"
1096
+ w="100%"
1097
+ {...sizeProps}
1098
+ >{grid}</Box>
1099
+ }
1100
+ return grid;
1032
1101
 
1033
1102
  }
1034
1103
 
@@ -1036,12 +1105,14 @@ export const Grid = withComponent(
1036
1105
  withAlert(
1037
1106
  withEvents(
1038
1107
  withData(
1039
- withMultiSelection(
1040
- withSelection(
1041
- withFilters(
1042
- withPresetButtons(
1043
- withContextMenu(
1044
- GridComponent
1108
+ withDropTarget(
1109
+ withMultiSelection(
1110
+ withSelection(
1111
+ withFilters(
1112
+ withPresetButtons(
1113
+ withContextMenu(
1114
+ GridComponent
1115
+ )
1045
1116
  ),
1046
1117
  true // isGrid
1047
1118
  )
@@ -1057,13 +1128,15 @@ export const SideGridEditor = withComponent(
1057
1128
  withAlert(
1058
1129
  withEvents(
1059
1130
  withData(
1060
- withMultiSelection(
1061
- withSelection(
1062
- withSideEditor(
1063
- withFilters(
1064
- withPresetButtons(
1065
- withContextMenu(
1066
- GridComponent
1131
+ withDropTarget(
1132
+ withMultiSelection(
1133
+ withSelection(
1134
+ withSideEditor(
1135
+ withFilters(
1136
+ withPresetButtons(
1137
+ withContextMenu(
1138
+ GridComponent
1139
+ )
1067
1140
  ),
1068
1141
  true // isGrid
1069
1142
  )
@@ -1080,15 +1153,17 @@ export const WindowedGridEditor = withComponent(
1080
1153
  withAlert(
1081
1154
  withEvents(
1082
1155
  withData(
1083
- withMultiSelection(
1084
- withSelection(
1085
- withWindowedEditor(
1086
- withFilters(
1087
- withPresetButtons(
1088
- withContextMenu(
1089
- GridComponent
1090
- ),
1091
- true // isGrid
1156
+ withDropTarget(
1157
+ withMultiSelection(
1158
+ withSelection(
1159
+ withWindowedEditor(
1160
+ withFilters(
1161
+ withPresetButtons(
1162
+ withContextMenu(
1163
+ GridComponent
1164
+ ),
1165
+ true // isGrid
1166
+ )
1092
1167
  )
1093
1168
  )
1094
1169
  )
@@ -1103,16 +1178,18 @@ export const InlineGridEditor = withComponent(
1103
1178
  withAlert(
1104
1179
  withEvents(
1105
1180
  withData(
1106
- withMultiSelection(
1107
- withSelection(
1108
- withInlineEditor(
1109
- withFilters(
1110
- withPresetButtons(
1111
- withContextMenu(
1112
- GridComponent
1113
- )
1114
- ),
1115
- true // isGrid
1181
+ withDropTarget(
1182
+ withMultiSelection(
1183
+ withSelection(
1184
+ withInlineEditor(
1185
+ withFilters(
1186
+ withPresetButtons(
1187
+ withContextMenu(
1188
+ GridComponent
1189
+ )
1190
+ ),
1191
+ true // isGrid
1192
+ )
1116
1193
  )
1117
1194
  )
1118
1195
  )
@@ -1,24 +1,23 @@
1
- import { useState, useMemo, } from 'react';
1
+ import { useMemo, } from 'react';
2
2
  import {
3
3
  Box,
4
4
  Row,
5
5
  Text,
6
6
  } from 'native-base';
7
- import {
8
- VERTICAL,
9
- } from '../../Constants/Directions.js';
10
7
  import {
11
8
  UI_MODE_WEB,
12
9
  } from '../../Constants/UiModes.js';
13
10
  import getComponentFromType from '../../Functions/getComponentFromType.js';
14
11
  import UiGlobals from '../../UiGlobals.js';
12
+ import { withDragSource, withDropTarget } from '../Hoc/withDnd.js';
15
13
  import withDraggable from '../Hoc/withDraggable.js';
16
14
  import AngleRight from '../Icons/AngleRight.js';
15
+ import RowDragHandle from './RowDragHandle.js';
17
16
  import _ from 'lodash';
18
17
 
19
18
  // This was broken out from Grid simply so we can memoize it
20
19
 
21
- export default function GridRow(props) {
20
+ function GridRow(props) {
22
21
  const {
23
22
  columnsConfig,
24
23
  columnProps,
@@ -28,6 +27,8 @@ export default function GridRow(props) {
28
27
  bg,
29
28
  item,
30
29
  isInlineEditorShown,
30
+ isDragSource = false,
31
+ isOver = false,
31
32
  } = props,
32
33
  styles = UiGlobals.styles;
33
34
 
@@ -39,182 +40,187 @@ export default function GridRow(props) {
39
40
  isPhantom = item.isPhantom,
40
41
  hash = item?.hash || item;
41
42
 
42
-
43
- return useMemo(() => {
44
- const renderColumns = (item) => {
45
- if (_.isArray(columnsConfig)) {
46
- return _.map(columnsConfig, (config, key, all) => {
47
- const propsToPass = columnProps[key] || {};
48
- if (all.length === 1) {
49
- propsToPass.w = '100%';
43
+ if (props.dragSourceRef) {
44
+ rowProps.ref = props.dragSourceRef;
45
+ }
46
+ if (props.dropTargetRef) {
47
+ rowProps.ref = props.dropTargetRef;
48
+ }
49
+ return useMemo(() => {
50
+ const renderColumns = (item) => {
51
+ if (_.isArray(columnsConfig)) {
52
+ return _.map(columnsConfig, (config, key, all) => {
53
+ const propsToPass = columnProps[key] || {};
54
+ if (all.length === 1) {
55
+ propsToPass.w = '100%';
56
+ } else {
57
+ if (config.w) {
58
+ propsToPass.w = config.w;
59
+ } else if (config.flex) {
60
+ propsToPass.flex = config.flex;
61
+ propsToPass.minWidth = 100;
50
62
  } else {
51
- if (config.w) {
52
- propsToPass.w = config.w;
53
- } else if (config.flex) {
54
- propsToPass.flex = config.flex;
55
- propsToPass.minWidth = 100;
56
- } else {
57
- propsToPass.flex = 1;
58
- }
59
- }
60
- propsToPass.p = 1;
61
- propsToPass.justifyContent = 'center';
62
-
63
- if (isInlineEditorShown) {
64
- propsToPass.minWidth = styles.INLINE_EDITOR_MIN_WIDTH;
63
+ propsToPass.flex = 1;
65
64
  }
65
+ }
66
+ propsToPass.p = 1;
67
+ propsToPass.justifyContent = 'center';
66
68
 
67
- let value;
68
- if (_.isFunction(config)) {
69
- return config(item, key);
70
- }
71
- if (_.isPlainObject(config)) {
72
- if (config.renderer) {
73
- const extraProps = _.omit(config, [
74
- 'columnId',
75
- 'header',
76
- 'fieldName',
77
- 'type',
78
- 'isEditable',
79
- 'editor',
80
- 'format',
81
- 'renderer',
82
- 'reorderable',
83
- 'resizable',
84
- 'sortable',
85
- 'w',
86
- 'flex',
87
- 'showDragHandles',
88
- ]);
69
+ if (isInlineEditorShown) {
70
+ propsToPass.minWidth = styles.INLINE_EDITOR_MIN_WIDTH;
71
+ }
89
72
 
90
- if (!extraProps._web) {
91
- extraProps._web = {};
92
- }
93
- if (!extraProps._web.style) {
94
- extraProps._web.style = {};
95
- }
96
- extraProps._web.style = {
97
- userSelect: 'none',
98
- };
73
+ let value;
74
+ if (_.isFunction(config)) {
75
+ return config(item, key);
76
+ }
77
+ if (_.isPlainObject(config)) {
78
+ if (config.renderer) {
79
+ const extraProps = _.omit(config, [
80
+ 'columnId',
81
+ 'header',
82
+ 'fieldName',
83
+ 'type',
84
+ 'isEditable',
85
+ 'editor',
86
+ 'format',
87
+ 'renderer',
88
+ 'reorderable',
89
+ 'resizable',
90
+ 'sortable',
91
+ 'w',
92
+ 'flex',
93
+ 'showDragHandles',
94
+ ]);
99
95
 
100
- return <Row key={key} {...propsToPass} {...extraProps}>{config.renderer(item)}</Row>;
96
+ if (!extraProps._web) {
97
+ extraProps._web = {};
98
+ }
99
+ if (!extraProps._web.style) {
100
+ extraProps._web.style = {};
101
101
  }
102
- if (config.fieldName) {
103
- if (item?.properties && item.properties[config.fieldName]) {
104
- const property = item.properties[config.fieldName];
105
- value = property.displayValue;
106
- const type = property?.viewerType?.type;
102
+ extraProps._web.style = {
103
+ userSelect: 'none',
104
+ };
107
105
 
108
- if (type) {
109
- const Element = getComponentFromType(type);
110
- const elementProps = {};
111
- if (UiGlobals.mode === UI_MODE_WEB) {
112
- elementProps.textOverflow = 'ellipsis';
113
- }
114
- if (type.match(/(Tag|TagEditor)$/)) {
115
- elementProps.isViewOnly = true; // TODO: this won't work for InlineGridEditor, bc that Grid can't use isViewOnly when actually editing
116
- }
117
- return <Element
118
- value={value}
119
- key={key}
120
- overflow="hidden"
121
- alignSelf="center"
122
- style={{
123
- userSelect: 'none',
124
- }}
125
- fontSize={styles.GRID_CELL_FONTSIZE}
126
- px={styles.GRID_CELL_PX}
127
- py={styles.GRID_CELL_PY}
128
- numberOfLines={1}
129
- ellipsizeMode="head"
130
- {...propsToPass}
131
- {...elementProps}
132
- />;
106
+ return <Row key={key} {...propsToPass} {...extraProps}>{config.renderer(item)}</Row>;
107
+ }
108
+ if (config.fieldName) {
109
+ if (item?.properties && item.properties[config.fieldName]) {
110
+ const property = item.properties[config.fieldName];
111
+ value = property.displayValue;
112
+ const type = property?.viewerType?.type;
113
+
114
+ if (type) {
115
+ const Element = getComponentFromType(type);
116
+ const elementProps = {};
117
+ if (UiGlobals.mode === UI_MODE_WEB) {
118
+ elementProps.textOverflow = 'ellipsis';
133
119
  }
134
- } else if (item[config.fieldName]) {
135
- value = item[config.fieldName];
136
- } else if (fields) {
137
- const ix = fields.indexOf(config.fieldName);
138
- value = item[ix];
120
+ if (type.match(/(Tag|TagEditor)$/)) {
121
+ elementProps.isViewOnly = true; // TODO: this won't work for InlineGridEditor, bc that Grid can't use isViewOnly when actually editing
122
+ }
123
+ return <Element
124
+ value={value}
125
+ key={key}
126
+ overflow="hidden"
127
+ alignSelf="center"
128
+ style={{
129
+ userSelect: 'none',
130
+ }}
131
+ fontSize={styles.GRID_CELL_FONTSIZE}
132
+ px={styles.GRID_CELL_PX}
133
+ py={styles.GRID_CELL_PY}
134
+ numberOfLines={1}
135
+ ellipsizeMode="head"
136
+ {...propsToPass}
137
+ {...elementProps}
138
+ />;
139
139
  }
140
- }
141
- }
142
- if (_.isString(config)) {
143
- if (fields) {
144
- const ix = fields.indexOf(config);
140
+ } else if (item[config.fieldName]) {
141
+ value = item[config.fieldName];
142
+ } else if (fields) {
143
+ const ix = fields.indexOf(config.fieldName);
145
144
  value = item[ix];
146
- } else {
147
- value = item[config];
148
145
  }
149
146
  }
150
- if (_.isFunction(value)) {
151
- return value(key);
152
- }
153
- const elementProps = {};
154
- if (UiGlobals.mode === UI_MODE_WEB) {
155
- elementProps.textOverflow = 'ellipsis';
147
+ }
148
+ if (_.isString(config)) {
149
+ if (fields) {
150
+ const ix = fields.indexOf(config);
151
+ value = item[ix];
152
+ } else {
153
+ value = item[config];
156
154
  }
157
- return <Text
158
- key={key}
159
- overflow="hidden"
160
- alignSelf="center"
161
- style={{
162
- userSelect: 'none',
163
- }}
164
- fontSize={styles.GRID_CELL_FONTSIZE}
165
- px={styles.GRID_CELL_PX}
166
- py={styles.GRID_CELL_PY}
167
- numberOfLines={1}
168
- ellipsizeMode="head"
169
- {...elementProps}
170
- {...propsToPass}
171
- >{value}</Text>;
172
- });
173
- } else {
174
- // TODO: if 'columnsConfig' is an object, parse its contents
175
- throw new Error('Non-array columnsConfig not yet supported');
176
- }
177
- };
178
- return <Row
179
- alignItems="center"
180
- flexGrow={1}
181
- {...rowProps}
182
- bg={bg}
183
- key={hash}
184
- >
185
- {isPhantom && <Box position="absolute" bg="#f00" h={2} w={2} t={0} l={0} />}
186
-
187
- {renderColumns(item)}
188
-
189
- {!hideNavColumn && <AngleRight
190
- color={styles.GRID_NAV_COLUMN_COLOR}
191
- variant="ghost"
192
- w={30}
193
- alignSelf="center"
194
- mx={3}
195
- />}
196
- </Row>;
197
- }, [
198
- columnsConfig,
199
- columnProps,
200
- fields,
201
- rowProps,
202
- hideNavColumn,
203
- bg,
204
- item,
205
- isPhantom,
206
- hash, // this is an easy way to determine if the data has changed and the item needs to be rerendered
207
- isInlineEditorShown,
208
- ]);
209
- }
155
+ }
156
+ if (_.isFunction(value)) {
157
+ return value(key);
158
+ }
159
+ const elementProps = {};
160
+ if (UiGlobals.mode === UI_MODE_WEB) {
161
+ elementProps.textOverflow = 'ellipsis';
162
+ }
163
+ return <Text
164
+ key={key}
165
+ overflow="hidden"
166
+ alignSelf="center"
167
+ style={{
168
+ userSelect: 'none',
169
+ }}
170
+ fontSize={styles.GRID_CELL_FONTSIZE}
171
+ px={styles.GRID_CELL_PX}
172
+ py={styles.GRID_CELL_PY}
173
+ numberOfLines={1}
174
+ ellipsizeMode="head"
175
+ {...elementProps}
176
+ {...propsToPass}
177
+ >{value}</Text>;
178
+ });
179
+ } else {
180
+ // TODO: if 'columnsConfig' is an object, parse its contents
181
+ throw new Error('Non-array columnsConfig not yet supported');
182
+ }
183
+ };
184
+ if (isOver) {
185
+ rowProps.borderWidth = 4;
186
+ rowProps.borderColor = '#0ff';
187
+ } else {
188
+ rowProps.borderWidth = 0;
189
+ rowProps.borderColor = null;
190
+ }
191
+ return <Row
192
+ alignItems="center"
193
+ flexGrow={1}
194
+ {...rowProps}
195
+ bg={bg}
196
+ key={hash}
197
+ >
198
+ {isDragSource && <RowDragHandle />}
199
+ {isPhantom && <Box position="absolute" bg="#f00" h={2} w={2} t={0} l={0} />}
200
+
201
+ {renderColumns(item)}
210
202
 
211
- function withAdditionalProps(WrappedComponent) {
212
- return (props) => {
213
- return <WrappedComponent
214
- mode={VERTICAL}
215
- {...props}
216
- />;
217
- };
203
+ {!hideNavColumn && <AngleRight
204
+ color={styles.GRID_NAV_COLUMN_COLOR}
205
+ variant="ghost"
206
+ w={30}
207
+ alignSelf="center"
208
+ mx={3}
209
+ />}
210
+ </Row>;
211
+ }, [
212
+ columnsConfig,
213
+ columnProps,
214
+ fields,
215
+ rowProps,
216
+ hideNavColumn,
217
+ bg,
218
+ item,
219
+ isPhantom,
220
+ hash, // this is an easy way to determine if the data has changed and the item needs to be rerendered
221
+ isInlineEditorShown,
222
+ isOver,
223
+ ]);
218
224
  }
219
225
 
220
- export const ReorderableGridRow = withAdditionalProps(withDraggable(GridRow));
226
+ export default withDraggable(withDragSource(withDropTarget(GridRow)));
@@ -0,0 +1,24 @@
1
+ import {
2
+ Column,
3
+ Icon,
4
+ Row,
5
+ Text,
6
+ } from 'native-base';
7
+ import styles from '../../Styles/StyleSheets.js';
8
+ import GripVertical from '../Icons/GripVertical.js';
9
+
10
+ function RowDragHandle(props) {
11
+ return <Column
12
+ testID="HeaderReorderHandle"
13
+ bg="trueGray.100"
14
+ h="100%"
15
+ w={3}
16
+ alignItems="center"
17
+ justifyContent="center"
18
+ style={styles.ewResize}
19
+ >
20
+ <Icon as={GripVertical} testID="handle" size="xs" w="100%" h="100%" color="#ccc" />
21
+ </Column>;
22
+ }
23
+
24
+ export default RowDragHandle;
@@ -91,7 +91,11 @@ export default function withContextMenu(WrappedComponent) {
91
91
  w: 20,
92
92
  mr: 2,
93
93
  };
94
- icon = React.cloneElement(icon, {...iconProps});
94
+ if (React.isValidElement(icon)) {
95
+ icon = React.cloneElement(icon, {...iconProps});
96
+ } else {
97
+ icon = <Icon as={icon} {...iconProps} />;
98
+ }
95
99
  }
96
100
 
97
101
  // <div style={{
@@ -0,0 +1,137 @@
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
2
+
3
+ // This HOC allows components to be dragged and dropped onto another component.
4
+ // It doesn't contraint the moment of the preview item.
5
+
6
+ export function withDragSource(WrappedComponent) {
7
+ return (props) => {
8
+
9
+ if (!props.isDragSource) {
10
+ return <WrappedComponent {...props} />;
11
+ }
12
+
13
+ if (!props.dragSourceType) {
14
+ throw Error('dragSourceType not defined');
15
+ }
16
+ if (!props.dragSourceItem) {
17
+ throw Error('dragSourceItem not defined');
18
+ }
19
+
20
+ const {
21
+ dragSourceType,
22
+ dragSourceItem,
23
+ } = props,
24
+ [dragState, dragSourceRef, dragPreviewRef] = useDrag(() => { // A specification object or a function that creates a specification object.
25
+ // 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
26
+ return {
27
+ 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
+ item: dragSourceItem, // Required (object or function).
29
+ // 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
+ // 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.
38
+ // monitor fn determines which props from dnd state get passed
39
+ return {
40
+ canDrag: !!monitor.canDrag(), // Returns trueif no drag operation is in progress, and the owner's canDrag() returns true or is not defined.
41
+ 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
+ // 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.
44
+ // 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().
46
+ // 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
+ // 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
+ // 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
+ // 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
+ // 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
+ };
52
+ },
53
+ };
54
+ }),
55
+ {
56
+ canDrag,
57
+ isDragging,
58
+ // type,
59
+ // item,
60
+ // dropResult,
61
+ // didDrop,
62
+ // initialClientOffset,
63
+ // initialSourceClientOffset,
64
+ // clientOffset,
65
+ // differenceFromInitialOffset,
66
+ // sourceClientOffset,
67
+ } = dragState;
68
+
69
+ return <WrappedComponent
70
+ canDrag={canDrag}
71
+ isDragging={isDragging}
72
+ dragSourceRef={dragSourceRef}
73
+ {...props}
74
+ />;
75
+ };
76
+ }
77
+
78
+
79
+ export function withDropTarget(WrappedComponent) {
80
+ return (props) => {
81
+ if (!props.isDropTarget) {
82
+ return <WrappedComponent {...props} />;
83
+ }
84
+
85
+ if (!props.dropTargetAccept) {
86
+ throw Error('dropTargetAccept not defined');
87
+ }
88
+
89
+ const {
90
+ dropTargetAccept,
91
+ onDrop = null,
92
+ } = props,
93
+ [dropState, dropTargetRef] = useDrop(() => { // A specification object or a function that creates a specification object.
94
+ // 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
+ return {
96
+ 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.
98
+ 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.
102
+ return {
103
+ canDrop: !!monitor.canDrop(),
104
+ isOver: !!monitor.isOver(),
105
+ // didDrop: !!monitor.didDrop(),
106
+ // clientOffset: monitor.getClientOffset(),
107
+ // differenceFromInitialOffset: monitor.getDifferenceFromInitialOffset(),
108
+ // dropResult: monitor.getDropResult(),
109
+ // handlerId: monitor.getHandlerId(),
110
+ // initialClientOffset: monitor.getInitialClientOffset(),
111
+ // initialSourceClientOffset: monitor.getInitialSourceClientOffset(),
112
+ // receiveHandlerId
113
+ // subscribeToStateChange
114
+ };
115
+ },
116
+ };
117
+ }),
118
+ {
119
+ canDrop,
120
+ isOver,
121
+ // didDrop,
122
+ // clientOffset,
123
+ // differenceFromInitialOffset,
124
+ // dropResult,
125
+ // handlerId,
126
+ // initialClientOffset,
127
+ // initialSourceClientOffset,
128
+ } = dropState;
129
+
130
+ return <WrappedComponent
131
+ canDrop={canDrop}
132
+ isOver={isOver}
133
+ dropTargetRef={dropTargetRef}
134
+ {...props}
135
+ />;
136
+ };
137
+ }
@@ -23,6 +23,10 @@ import getComponentFromType from '../../Functions/getComponentFromType.js';
23
23
  export default function withDraggable(WrappedComponent) {
24
24
  return (props) => {
25
25
 
26
+ if (!props.isDraggable) {
27
+ return <WrappedComponent {...props} />;
28
+ }
29
+
26
30
  const {
27
31
  // extract and pass
28
32
  onDragStart,
@@ -327,12 +327,12 @@ export default function withPresetButtons(WrappedComponent, isGrid = false) {
327
327
  {...propsToPass}
328
328
  disablePresetButtons={false}
329
329
  contextMenuItems={[
330
- ...contextMenuItems,
331
330
  ...localContextMenuItems,
331
+ ...contextMenuItems,
332
332
  ]}
333
333
  additionalToolbarButtons={[
334
- ...additionalToolbarButtons,
335
334
  ...localAdditionalToolbarButtons,
335
+ ...additionalToolbarButtons,
336
336
  ]}
337
337
  onChangeColumnsConfig={onChangeColumnsConfigDecorator}
338
338
  />
@@ -203,6 +203,7 @@ import DataMgt from './Screens/DataMgt.js';
203
203
  import Date from './Form/Field/Date.js';
204
204
  import DateRange from './Filter/DateRange.js';
205
205
  import DisplayField from './Form/Field/DisplayField.js';
206
+ import ExpandButton from './Buttons/ExpandButton.js';
206
207
  import FieldSet from './Form/FieldSet.js';
207
208
  import FiltersForm from './Form/FiltersForm.js';
208
209
  // import FiltersToolbar from '../Components/Toolbar/FiltersToolbar.js';
@@ -436,6 +437,7 @@ const components = {
436
437
  Date,
437
438
  DateRange,
438
439
  DisplayField,
440
+ ExpandButton,
439
441
  FieldSet,
440
442
  FiltersForm,
441
443
  // FiltersToolbar,