@teselagen/ui 0.4.14 → 0.4.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/AdvancedOptions.d.ts +1 -1
  2. package/AssignDefaultsModeContext.d.ts +1 -1
  3. package/AsyncValidateFieldSpinner/index.d.ts +1 -1
  4. package/BlueprintError/index.d.ts +1 -1
  5. package/BounceLoader/index.d.ts +1 -1
  6. package/CollapsibleCard/index.d.ts +1 -1
  7. package/DNALoader/index.d.ts +1 -1
  8. package/DataTable/CellDragHandle.d.ts +1 -1
  9. package/DataTable/ColumnFilterMenu.d.ts +14 -0
  10. package/DataTable/DisabledLoadingComponent.d.ts +1 -1
  11. package/DataTable/DisplayOptions.d.ts +2 -2
  12. package/DataTable/DropdownCell.d.ts +8 -0
  13. package/DataTable/EditabelCell.d.ts +10 -0
  14. package/DataTable/FilterAndSortMenu.d.ts +2 -2
  15. package/DataTable/SearchBar.d.ts +1 -1
  16. package/DataTable/SortableColumns.d.ts +2 -2
  17. package/DataTable/TableFormTrackerContext.d.ts +1 -1
  18. package/DataTable/defaultProps.d.ts +1 -1
  19. package/DataTable/index.d.ts +0 -5
  20. package/DataTable/utils/computePresets.d.ts +1 -1
  21. package/DataTable/utils/formatPasteData.d.ts +5 -0
  22. package/DataTable/utils/getAllRows.d.ts +1 -0
  23. package/DataTable/utils/getCellCopyText.d.ts +1 -0
  24. package/DataTable/utils/getCellInfo.d.ts +17 -0
  25. package/DataTable/utils/getFieldPathToField.d.ts +1 -0
  26. package/DataTable/utils/getIdOrCodeOrIndex.d.ts +1 -2
  27. package/DataTable/utils/getLastSelectedEntity.d.ts +1 -0
  28. package/DataTable/utils/getNewEntToSelect.d.ts +6 -0
  29. package/DataTable/utils/getRowCopyText.d.ts +3 -0
  30. package/DataTable/utils/handleCopyColumn.d.ts +1 -0
  31. package/DataTable/utils/handleCopyHelper.d.ts +1 -0
  32. package/DataTable/utils/handleCopyRows.d.ts +5 -0
  33. package/DataTable/utils/index.d.ts +21 -0
  34. package/DataTable/utils/isBottomRightCornerOfRectangle.d.ts +8 -0
  35. package/DataTable/utils/isEntityClean.d.ts +1 -0
  36. package/DataTable/utils/removeCleanRows.d.ts +4 -0
  37. package/DataTable/utils/rowClick.d.ts +10 -2
  38. package/DataTable/utils/utils.d.ts +5 -0
  39. package/DataTable/viewColumn.d.ts +2 -2
  40. package/DialogFooter/index.d.ts +1 -1
  41. package/DropdownButton.d.ts +1 -1
  42. package/FillWindow.d.ts +1 -1
  43. package/FormComponents/LoadingDots.d.ts +1 -1
  44. package/FormComponents/Uploader.d.ts +29 -1
  45. package/FormComponents/index.d.ts +34 -34
  46. package/FormComponents/itemUpload.d.ts +1 -1
  47. package/HotkeysDialog/index.d.ts +1 -1
  48. package/InfoHelper/index.d.ts +1 -1
  49. package/IntentText/index.d.ts +1 -1
  50. package/MatchHeaders.d.ts +1 -1
  51. package/MenuBar/index.d.ts +4 -4
  52. package/PromptUnsavedChanges/index.d.ts +1 -1
  53. package/README.md +18 -0
  54. package/ResizableDraggableDialog/index.d.ts +2 -2
  55. package/ScrollToTop/index.d.ts +1 -1
  56. package/SimpleStepViz.d.ts +1 -1
  57. package/Tag.d.ts +1 -1
  58. package/TagSelect/index.d.ts +1 -1
  59. package/TgSelect/index.d.ts +2 -2
  60. package/TgSuggest/index.d.ts +3 -3
  61. package/Timeline/TimelineEvent.d.ts +1 -1
  62. package/Timeline/index.d.ts +2 -2
  63. package/UploadCsvWizard.d.ts +1 -1
  64. package/customIcons.d.ts +19 -19
  65. package/enhancers/withField.d.ts +1 -1
  66. package/enhancers/withFields.d.ts +1 -1
  67. package/enhancers/withLocalStorage.d.ts +1 -1
  68. package/index.cjs.js +14026 -12765
  69. package/index.d.ts +60 -60
  70. package/index.es.js +13844 -12583
  71. package/package.json +7 -4
  72. package/showConfirmationDialog/index.d.ts +2 -2
  73. package/src/DataTable/CellDragHandle.js +6 -7
  74. package/src/DataTable/ColumnFilterMenu.js +60 -0
  75. package/src/DataTable/DropdownCell.js +61 -0
  76. package/src/DataTable/EditabelCell.js +55 -0
  77. package/src/DataTable/PagingTool.js +1 -1
  78. package/src/DataTable/SortableColumns.js +53 -18
  79. package/src/DataTable/dataTableEnhancer.js +1 -1
  80. package/src/DataTable/index.js +385 -759
  81. package/src/DataTable/utils/formatPasteData.js +16 -0
  82. package/src/DataTable/utils/getAllRows.js +11 -0
  83. package/src/DataTable/utils/getCellCopyText.js +7 -0
  84. package/src/DataTable/utils/getCellInfo.js +36 -0
  85. package/src/DataTable/utils/getFieldPathToField.js +7 -0
  86. package/src/DataTable/utils/getIdOrCodeOrIndex.js +1 -1
  87. package/src/DataTable/utils/getLastSelectedEntity.js +11 -0
  88. package/src/DataTable/utils/getNewEntToSelect.js +25 -0
  89. package/src/DataTable/utils/getRowCopyText.js +28 -0
  90. package/src/DataTable/utils/handleCopyColumn.js +21 -0
  91. package/src/DataTable/utils/handleCopyHelper.js +15 -0
  92. package/src/DataTable/utils/handleCopyRows.js +23 -0
  93. package/src/DataTable/utils/index.js +51 -0
  94. package/src/DataTable/utils/isBottomRightCornerOfRectangle.js +20 -0
  95. package/src/DataTable/utils/isEntityClean.js +15 -0
  96. package/src/DataTable/utils/removeCleanRows.js +22 -0
  97. package/src/DataTable/utils/rowClick.js +7 -4
  98. package/src/DataTable/utils/selection.js +1 -1
  99. package/src/DataTable/utils/utils.js +37 -0
  100. package/src/DataTable/validateTableWideErrors.js +1 -1
  101. package/src/FillWindow.js +2 -3
  102. package/src/FormComponents/Uploader.js +400 -400
  103. package/src/FormComponents/tryToMatchSchemas.js +0 -6
  104. package/src/UploadCsvWizard.js +312 -371
  105. package/src/index.js +3 -3
  106. package/src/showDialogOnDocBody.js +5 -9
  107. package/src/useDialog.js +7 -4
  108. package/src/utils/renderOnDoc.js +8 -5
  109. package/style.css +7 -7
  110. package/useDialog.d.ts +2 -2
  111. package/utils/adHoc.d.ts +1 -1
  112. package/utils/commandControls.d.ts +5 -5
  113. package/utils/hotkeyUtils.d.ts +1 -1
  114. package/utils/menuUtils.d.ts +7 -7
  115. package/utils/renderOnDoc.d.ts +1 -1
  116. package/utils/tagUtils.d.ts +1 -1
  117. package/utils/tgFormValues.d.ts +1 -1
  118. package/utils/withStore.d.ts +1 -1
  119. package/wrapDialog.d.ts +1 -1
@@ -1,16 +1,10 @@
1
- /* eslint react/jsx-no-bind: 0 */
2
- import React, { useState } from "react";
3
- import ReactDOM from "react-dom";
4
- import { arrayMove } from "react-sortable-hoc";
5
- import copy from "copy-to-clipboard";
6
- import download from "downloadjs";
1
+ import React, { createRef } from "react";
7
2
  import {
8
3
  invert,
9
4
  toNumber,
10
5
  isEmpty,
11
6
  min,
12
7
  max,
13
- flatMap,
14
8
  set,
15
9
  map,
16
10
  toString,
@@ -32,7 +26,6 @@ import {
32
26
  every
33
27
  } from "lodash-es";
34
28
  import joinUrl from "url-join";
35
-
36
29
  import {
37
30
  Button,
38
31
  Menu,
@@ -41,16 +34,16 @@ import {
41
34
  ContextMenu,
42
35
  Checkbox,
43
36
  Icon,
44
- Popover,
45
37
  Intent,
46
38
  Callout,
47
39
  Tooltip
48
40
  } from "@blueprintjs/core";
41
+ import { arrayMove, useSortable } from "@dnd-kit/sortable";
42
+ import { CSS } from "@dnd-kit/utilities";
49
43
  import classNames from "classnames";
50
44
  import scrollIntoView from "dom-scroll-into-view";
51
- import { SortableElement } from "react-sortable-hoc";
52
45
  import ReactTable from "@teselagen/react-table";
53
- import { withProps, branch, compose } from "recompose";
46
+ import { withProps, compose } from "recompose";
54
47
  import dayjs from "dayjs";
55
48
  import localizedFormat from "dayjs/plugin/localizedFormat";
56
49
  import ReactMarkdown from "react-markdown";
@@ -58,30 +51,50 @@ import immer, { produceWithPatches, enablePatches, applyPatches } from "immer";
58
51
  import papaparse from "papaparse";
59
52
  import remarkGfm from "remark-gfm";
60
53
 
61
- import TgSelect from "../TgSelect";
62
- import { withHotkeys } from "../utils/hotkeyUtils";
54
+ import {
55
+ computePresets,
56
+ defaultParsePaste,
57
+ formatPasteData,
58
+ getAllRows,
59
+ getCellCopyText,
60
+ getCellInfo,
61
+ getEntityIdToEntity,
62
+ getFieldPathToIndex,
63
+ getFieldPathToField,
64
+ getIdOrCodeOrIndex,
65
+ getLastSelectedEntity,
66
+ getNewEntToSelect,
67
+ getNumberStrAtEnd,
68
+ getRecordsFromIdMap,
69
+ getRowCopyText,
70
+ getSelectedRowsFromEntities,
71
+ handleCopyColumn,
72
+ handleCopyHelper,
73
+ handleCopyRows,
74
+ isBottomRightCornerOfRectangle,
75
+ isEntityClean,
76
+ removeCleanRows,
77
+ stripNumberAtEnd
78
+ } from "./utils";
63
79
  import InfoHelper from "../InfoHelper";
80
+ import { withHotkeys } from "../utils/hotkeyUtils";
64
81
  import getTextFromEl from "../utils/getTextFromEl";
65
- import { getSelectedRowsFromEntities } from "./utils/selection";
66
82
  import rowClick, {
67
83
  changeSelectedEntities,
68
84
  finalizeSelection
69
85
  } from "./utils/rowClick";
70
86
  import PagingTool from "./PagingTool";
71
87
  import FilterAndSortMenu from "./FilterAndSortMenu";
72
- import getIdOrCodeOrIndex from "./utils/getIdOrCodeOrIndex";
73
88
  import SearchBar from "./SearchBar";
74
89
  import DisplayOptions from "./DisplayOptions";
75
90
  import DisabledLoadingComponent from "./DisabledLoadingComponent";
76
91
  import SortableColumns from "./SortableColumns";
77
- import computePresets from "./utils/computePresets";
78
92
  import dataTableEnhancer from "./dataTableEnhancer";
79
93
  import defaultProps from "./defaultProps";
80
94
 
81
95
  import "../toastr";
82
96
  import "@teselagen/react-table/react-table.css";
83
97
  import "./style.css";
84
- import { getRecordsFromIdMap } from "./utils/withSelectedEntities";
85
98
  import { CellDragHandle } from "./CellDragHandle";
86
99
  import { nanoid } from "nanoid";
87
100
  import { SwitchField } from "../FormComponents";
@@ -90,15 +103,27 @@ import { editCellHelper } from "./editCellHelper";
90
103
  import { getCellVal } from "./getCellVal";
91
104
  import { getVals } from "./getVals";
92
105
  import { throwFormError } from "../throwFormError";
106
+ import { DropdownCell } from "./DropdownCell";
107
+ import { EditableCell } from "./EditabelCell";
108
+ import { ColumnFilterMenu } from "./ColumnFilterMenu";
93
109
  enablePatches();
94
110
 
95
111
  const PRIMARY_SELECTED_VAL = "main_cell";
96
112
 
97
113
  dayjs.extend(localizedFormat);
98
114
  const IS_LINUX = window.navigator.platform.toLowerCase().search("linux") > -1;
115
+
116
+ const itemSizeEstimators = {
117
+ compact: () => 25.34,
118
+ normal: () => 33.34,
119
+ comfortable: () => 41.34
120
+ };
121
+
99
122
  class DataTable extends React.Component {
100
123
  constructor(props) {
101
124
  super(props);
125
+
126
+ this.tableRef = createRef();
102
127
  if (this.props.helperProp) {
103
128
  this.props.helperProp.updateValidationHelper =
104
129
  this.updateValidationHelper;
@@ -179,25 +204,52 @@ class DataTable extends React.Component {
179
204
  }
180
205
  });
181
206
  }
207
+
182
208
  state = {
183
209
  columns: [],
184
- fullscreen: false
210
+ fullscreen: false,
211
+ // This state prevents the first letter from not being written,
212
+ // when the user starts typing in a cell. It is quite hacky, we should
213
+ // refactor this in the future.
214
+ editableCellInitialValue: ""
185
215
  };
186
-
187
216
  static defaultProps = defaultProps;
188
217
 
189
- toggleFullscreen = () => {
190
- this.setState({
191
- fullscreen: !this.state.fullscreen
192
- });
218
+ getPrimarySelectedCellId = () => {
219
+ const { reduxFormSelectedCells = {} } = this.props;
220
+ for (const k of Object.keys(reduxFormSelectedCells)) {
221
+ if (reduxFormSelectedCells[k] === PRIMARY_SELECTED_VAL) {
222
+ return k;
223
+ }
224
+ }
225
+ };
226
+
227
+ startCellEdit = (cellId, { shouldSelectAll } = {}) => {
228
+ const {
229
+ change,
230
+ reduxFormSelectedCells = {},
231
+ reduxFormEditingCell
232
+ } = computePresets(this.props);
233
+ const newSelectedCells = { ...reduxFormSelectedCells };
234
+ newSelectedCells[cellId] = PRIMARY_SELECTED_VAL;
235
+ //check if the cell is already selected and editing and if so, don't change it
236
+ if (reduxFormEditingCell === cellId) return;
237
+ change("reduxFormSelectedCells", newSelectedCells);
238
+ change("reduxFormEditingCell", cellId);
239
+ if (shouldSelectAll) {
240
+ //we should select the text
241
+ change("reduxFormEditingCellSelectAll", true);
242
+ }
193
243
  };
244
+
194
245
  handleEnterStartCellEdit = e => {
195
246
  e.stopPropagation();
196
247
  this.startCellEdit(this.getPrimarySelectedCellId());
197
248
  };
249
+
198
250
  flashTableBorder = () => {
199
251
  try {
200
- const table = ReactDOM.findDOMNode(this.table);
252
+ const table = this.tableRef.current.tableRef;
201
253
  table.classList.add("tgBorderBlue");
202
254
  setTimeout(() => {
203
255
  table.classList.remove("tgBorderBlue");
@@ -206,6 +258,59 @@ class DataTable extends React.Component {
206
258
  console.error(`err when flashing table border:`, e);
207
259
  }
208
260
  };
261
+
262
+ formatAndValidateEntities = (
263
+ entities,
264
+ { useDefaultValues, indexToStartAt } = {}
265
+ ) => {
266
+ const { schema } = this.props;
267
+ const editableFields = schema.fields.filter(f => !f.isNotEditable);
268
+ const validationErrors = {};
269
+
270
+ const newEnts = immer(entities, entities => {
271
+ entities.forEach((e, index) => {
272
+ editableFields.forEach(columnSchema => {
273
+ if (useDefaultValues) {
274
+ if (e[columnSchema.path] === undefined) {
275
+ if (isFunction(columnSchema.defaultValue)) {
276
+ e[columnSchema.path] = columnSchema.defaultValue(
277
+ index + indexToStartAt,
278
+ e
279
+ );
280
+ } else e[columnSchema.path] = columnSchema.defaultValue;
281
+ }
282
+ }
283
+ //mutative
284
+ const { error } = editCellHelper({
285
+ entity: e,
286
+ columnSchema,
287
+ newVal: e[columnSchema.path]
288
+ });
289
+ if (error) {
290
+ const rowId = getIdOrCodeOrIndex(e, index);
291
+ validationErrors[`${rowId}:${columnSchema.path}`] = error;
292
+ }
293
+ });
294
+ });
295
+ });
296
+ return {
297
+ newEnts,
298
+ validationErrors
299
+ };
300
+ };
301
+
302
+ updateValidation = (entities, newCellValidate) => {
303
+ const { change, schema } = computePresets(this.props);
304
+ const tableWideErr = validateTableWideErrors({
305
+ entities,
306
+ schema,
307
+ newCellValidate,
308
+ props: this.props
309
+ });
310
+ change("reduxFormCellValidation", tableWideErr);
311
+ this.forceUpdate();
312
+ };
313
+
209
314
  handleUndo = () => {
210
315
  const {
211
316
  change,
@@ -231,6 +336,7 @@ class DataTable extends React.Component {
231
336
  });
232
337
  }
233
338
  };
339
+
234
340
  handleRedo = () => {
235
341
  const {
236
342
  change,
@@ -255,6 +361,7 @@ class DataTable extends React.Component {
255
361
  });
256
362
  }
257
363
  };
364
+
258
365
  updateFromProps = (oldProps, newProps) => {
259
366
  const {
260
367
  selectedIds,
@@ -266,7 +373,6 @@ class DataTable extends React.Component {
266
373
  reduxFormExpandedEntityIdMap,
267
374
  change
268
375
  } = newProps;
269
- const table = ReactDOM.findDOMNode(this.table);
270
376
 
271
377
  const idMap = reduxFormSelectedEntityIdMap;
272
378
 
@@ -332,7 +438,8 @@ class DataTable extends React.Component {
332
438
  // if not changing selectedIds then we just want to make sure selected entities
333
439
  // stored in redux are in proper format
334
440
  // if selected ids have changed then it will handle redux selection
335
- const tableScrollElement = table.getElementsByClassName("rt-table")[0];
441
+ const tableScrollElement =
442
+ this.tableRef.current.tableRef.getElementsByClassName("rt-table")[0];
336
443
  const {
337
444
  entities: oldEntities = [],
338
445
  reduxFormSelectedEntityIdMap: oldIdMap
@@ -381,8 +488,9 @@ class DataTable extends React.Component {
381
488
  const entityIndexToScrollTo = entities.findIndex(
382
489
  e => e.id === idToScrollTo || e.code === idToScrollTo
383
490
  );
384
- if (entityIndexToScrollTo === -1 || !table) return;
385
- const tableBody = table.querySelector(".rt-tbody");
491
+ if (entityIndexToScrollTo === -1 || !this.tableRef.current) return;
492
+ const tableBody =
493
+ this.tableRef.current.tableRef.querySelector(".rt-tbody");
386
494
  if (!tableBody) return;
387
495
  const rowEl =
388
496
  tableBody.getElementsByClassName("rt-tr-group")[entityIndexToScrollTo];
@@ -397,45 +505,7 @@ class DataTable extends React.Component {
397
505
  }, 0);
398
506
  }
399
507
  };
400
- formatAndValidateEntities = (
401
- entities,
402
- { useDefaultValues, indexToStartAt } = {}
403
- ) => {
404
- const { schema } = this.props;
405
- const editableFields = schema.fields.filter(f => !f.isNotEditable);
406
- const validationErrors = {};
407
508
 
408
- const newEnts = immer(entities, entities => {
409
- entities.forEach((e, index) => {
410
- editableFields.forEach(columnSchema => {
411
- if (useDefaultValues) {
412
- if (e[columnSchema.path] === undefined) {
413
- if (isFunction(columnSchema.defaultValue)) {
414
- e[columnSchema.path] = columnSchema.defaultValue(
415
- index + indexToStartAt,
416
- e
417
- );
418
- } else e[columnSchema.path] = columnSchema.defaultValue;
419
- }
420
- }
421
- //mutative
422
- const { error } = editCellHelper({
423
- entity: e,
424
- columnSchema,
425
- newVal: e[columnSchema.path]
426
- });
427
- if (error) {
428
- const rowId = getIdOrCodeOrIndex(e, index);
429
- validationErrors[`${rowId}:${columnSchema.path}`] = error;
430
- }
431
- });
432
- });
433
- });
434
- return {
435
- newEnts,
436
- validationErrors
437
- };
438
- };
439
509
  formatAndValidateTableInitial = () => {
440
510
  const {
441
511
  _origEntities,
@@ -462,151 +532,25 @@ class DataTable extends React.Component {
462
532
  });
463
533
  };
464
534
 
465
- componentDidMount() {
466
- const {
467
- isCellEditable,
468
- entities = [],
469
- isLoading,
470
- showForcedHiddenColumns,
471
- setShowForcedHidden
472
- } = this.props;
473
- isCellEditable && this.formatAndValidateTableInitial();
474
- this.updateFromProps({}, computePresets(this.props));
475
- document.addEventListener("paste", this.handlePaste);
476
-
477
- if (!entities.length && !isLoading && !showForcedHiddenColumns) {
478
- setShowForcedHidden(true);
479
- }
480
- // const table = ReactDOM.findDOMNode(this.table);
481
- // let theads = table.getElementsByClassName("rt-thead");
482
- // let tbody = table.getElementsByClassName("rt-tbody")[0];
483
-
484
- // tbody.addEventListener("scroll", () => {
485
- // for (let i = 0; i < theads.length; i++) {
486
- // theads.item(i).scrollLeft = tbody.scrollLeft;
487
- // }
488
- // });
489
- }
490
-
491
- componentDidUpdate(oldProps) {
492
- // const tableBody = table.querySelector(".rt-tbody");
493
- // const headerNode = table.querySelector(".rt-thead.-header");
494
- // if (headerNode) headerNode.style.overflowY = "inherit";
495
- // if (tableBody && tableBody.scrollHeight > tableBody.clientHeight) {
496
- // if (headerNode) {
497
- // headerNode.style.overflowY = "scroll";
498
- // headerNode.style.overflowX = "hidden";
499
- // }
500
- // }
501
-
502
- this.updateFromProps(computePresets(oldProps), computePresets(this.props));
503
-
504
- // comment in to test what is causing re-render
505
- // Object.entries(this.props).forEach(
506
- // ([key, val]) =>
507
- // oldProps[key] !== val && console.info(`Prop '${key}' changed`)
508
- // );
509
- }
510
-
511
- componentWillUnmount() {
512
- document.removeEventListener("paste", this.handlePaste);
513
- }
514
-
515
- handleRowMove = (type, shiftHeld) => e => {
516
- e.preventDefault();
517
- e.stopPropagation();
518
- const props = computePresets(this.props);
519
- const {
520
- noSelect,
521
- entities,
522
- reduxFormSelectedEntityIdMap: idMap,
523
- isEntityDisabled,
524
- isSingleSelect
525
- } = props;
526
- let newIdMap = {};
527
- const lastSelectedEnt = getLastSelectedEntity(idMap);
528
-
529
- if (noSelect) return;
530
- if (lastSelectedEnt) {
531
- let lastSelectedIndex = entities.findIndex(
532
- ent => ent === lastSelectedEnt
533
- );
534
- if (lastSelectedIndex === -1) {
535
- if (lastSelectedEnt.id !== undefined) {
536
- lastSelectedIndex = entities.findIndex(
537
- ent => ent.id === lastSelectedEnt.id
538
- );
539
- } else if (lastSelectedEnt.code !== undefined) {
540
- lastSelectedIndex = entities.findIndex(
541
- ent => ent.code === lastSelectedEnt.code
542
- );
543
- }
544
- }
545
- if (lastSelectedIndex === -1) {
546
- return;
547
- }
548
- const newEntToSelect = getNewEntToSelect({
549
- type,
550
- lastSelectedIndex,
551
- entities,
552
- isEntityDisabled
553
- });
554
-
555
- if (!newEntToSelect) return;
556
- if (shiftHeld && !isSingleSelect) {
557
- if (idMap[newEntToSelect.id || newEntToSelect.code]) {
558
- //the entity being moved to has already been selected
559
- newIdMap = omit(idMap, [lastSelectedEnt.id || lastSelectedEnt.code]);
560
- newIdMap[newEntToSelect.id || newEntToSelect.code].time =
561
- Date.now() + 1;
562
- } else {
563
- //the entity being moved to has NOT been selected yet
564
- newIdMap = {
565
- ...idMap,
566
- [newEntToSelect.id || newEntToSelect.code]: {
567
- entity: newEntToSelect,
568
- time: Date.now()
569
- }
570
- };
571
- }
572
- } else {
573
- //no shiftHeld
574
- newIdMap[newEntToSelect.id || newEntToSelect.code] = {
575
- entity: newEntToSelect,
576
- time: Date.now()
577
- };
535
+ updateEntitiesHelper = (ents, fn) => {
536
+ const { change, reduxFormEntitiesUndoRedoStack = { currentVersion: 0 } } =
537
+ this.props;
538
+ const [nextState, patches, inversePatches] = produceWithPatches(ents, fn);
539
+ if (!inversePatches.length) return;
540
+ const thatNewNew = [...nextState];
541
+ thatNewNew.isDirty = true;
542
+ change("reduxFormEntities", thatNewNew);
543
+ change("reduxFormEntitiesUndoRedoStack", {
544
+ ...omitBy(reduxFormEntitiesUndoRedoStack, (v, k) => {
545
+ return toNumber(k) > reduxFormEntitiesUndoRedoStack.currentVersion + 1;
546
+ }),
547
+ currentVersion: reduxFormEntitiesUndoRedoStack.currentVersion + 1,
548
+ [reduxFormEntitiesUndoRedoStack.currentVersion + 1]: {
549
+ inversePatches,
550
+ patches
578
551
  }
579
- }
580
-
581
- finalizeSelection({
582
- idMap: newIdMap,
583
- entities,
584
- props
585
552
  });
586
553
  };
587
- handleCopyHotkey = e => {
588
- const { isCellEditable, reduxFormSelectedEntityIdMap } = computePresets(
589
- this.props
590
- );
591
-
592
- if (isCellEditable) {
593
- this.handleCopySelectedCells(e);
594
- } else {
595
- this.handleCopySelectedRows(
596
- getRecordsFromIdMap(reduxFormSelectedEntityIdMap),
597
- e
598
- );
599
- }
600
- };
601
-
602
- getPrimarySelectedCellId = () => {
603
- const { reduxFormSelectedCells = {} } = this.props;
604
- for (const k of Object.keys(reduxFormSelectedCells)) {
605
- if (reduxFormSelectedCells[k] === PRIMARY_SELECTED_VAL) {
606
- return k;
607
- }
608
- }
609
- };
610
554
 
611
555
  handlePaste = e => {
612
556
  const {
@@ -757,11 +701,150 @@ class DataTable extends React.Component {
757
701
  change("reduxFormSelectedCells", newSelectedCells);
758
702
  }
759
703
  }
760
- } catch (error) {
761
- console.error(`error:`, error);
704
+ } catch (error) {
705
+ console.error(`error:`, error);
706
+ }
707
+ }
708
+ };
709
+
710
+ componentDidMount() {
711
+ const {
712
+ isCellEditable,
713
+ entities = [],
714
+ isLoading,
715
+ showForcedHiddenColumns,
716
+ setShowForcedHidden
717
+ } = this.props;
718
+ isCellEditable && this.formatAndValidateTableInitial();
719
+ this.updateFromProps({}, computePresets(this.props));
720
+ document.addEventListener("paste", this.handlePaste);
721
+
722
+ if (!entities.length && !isLoading && !showForcedHiddenColumns) {
723
+ setShowForcedHidden(true);
724
+ }
725
+ // const table = this.tableRef.current.tableRef;
726
+ // let theads = table.getElementsByClassName("rt-thead");
727
+ // let tbody = table.getElementsByClassName("rt-tbody")[0];
728
+
729
+ // tbody.addEventListener("scroll", () => {
730
+ // for (let i = 0; i < theads.length; i++) {
731
+ // theads.item(i).scrollLeft = tbody.scrollLeft;
732
+ // }
733
+ // });
734
+ }
735
+
736
+ componentDidUpdate(oldProps) {
737
+ // const tableBody = table.querySelector(".rt-tbody");
738
+ // const headerNode = table.querySelector(".rt-thead.-header");
739
+ // if (headerNode) headerNode.style.overflowY = "inherit";
740
+ // if (tableBody && tableBody.scrollHeight > tableBody.clientHeight) {
741
+ // if (headerNode) {
742
+ // headerNode.style.overflowY = "scroll";
743
+ // headerNode.style.overflowX = "hidden";
744
+ // }
745
+ // }
746
+
747
+ this.updateFromProps(computePresets(oldProps), computePresets(this.props));
748
+
749
+ // comment in to test what is causing re-render
750
+ // Object.entries(this.props).forEach(
751
+ // ([key, val]) =>
752
+ // oldProps[key] !== val && console.info(`Prop '${key}' changed`)
753
+ // );
754
+ }
755
+
756
+ componentWillUnmount() {
757
+ document.removeEventListener("paste", this.handlePaste);
758
+ }
759
+
760
+ handleRowMove = (type, shiftHeld) => e => {
761
+ e.preventDefault();
762
+ e.stopPropagation();
763
+ const props = computePresets(this.props);
764
+ const {
765
+ noSelect,
766
+ entities,
767
+ reduxFormSelectedEntityIdMap: idMap,
768
+ isEntityDisabled,
769
+ isSingleSelect
770
+ } = props;
771
+ let newIdMap = {};
772
+ const lastSelectedEnt = getLastSelectedEntity(idMap);
773
+
774
+ if (noSelect) return;
775
+ if (lastSelectedEnt) {
776
+ let lastSelectedIndex = entities.findIndex(
777
+ ent => ent === lastSelectedEnt
778
+ );
779
+ if (lastSelectedIndex === -1) {
780
+ if (lastSelectedEnt.id !== undefined) {
781
+ lastSelectedIndex = entities.findIndex(
782
+ ent => ent.id === lastSelectedEnt.id
783
+ );
784
+ } else if (lastSelectedEnt.code !== undefined) {
785
+ lastSelectedIndex = entities.findIndex(
786
+ ent => ent.code === lastSelectedEnt.code
787
+ );
788
+ }
789
+ }
790
+ if (lastSelectedIndex === -1) {
791
+ return;
792
+ }
793
+ const newEntToSelect = getNewEntToSelect({
794
+ type,
795
+ lastSelectedIndex,
796
+ entities,
797
+ isEntityDisabled
798
+ });
799
+
800
+ if (!newEntToSelect) return;
801
+ if (shiftHeld && !isSingleSelect) {
802
+ if (idMap[newEntToSelect.id || newEntToSelect.code]) {
803
+ //the entity being moved to has already been selected
804
+ newIdMap = omit(idMap, [lastSelectedEnt.id || lastSelectedEnt.code]);
805
+ newIdMap[newEntToSelect.id || newEntToSelect.code].time =
806
+ Date.now() + 1;
807
+ } else {
808
+ //the entity being moved to has NOT been selected yet
809
+ newIdMap = {
810
+ ...idMap,
811
+ [newEntToSelect.id || newEntToSelect.code]: {
812
+ entity: newEntToSelect,
813
+ time: Date.now()
814
+ }
815
+ };
816
+ }
817
+ } else {
818
+ //no shiftHeld
819
+ newIdMap[newEntToSelect.id || newEntToSelect.code] = {
820
+ entity: newEntToSelect,
821
+ time: Date.now()
822
+ };
762
823
  }
763
824
  }
825
+
826
+ finalizeSelection({
827
+ idMap: newIdMap,
828
+ entities,
829
+ props
830
+ });
831
+ };
832
+
833
+ handleCopyHotkey = e => {
834
+ const { isCellEditable, reduxFormSelectedEntityIdMap } = computePresets(
835
+ this.props
836
+ );
837
+
838
+ if (isCellEditable) {
839
+ this.handleCopySelectedCells(e);
840
+ } else {
841
+ this.handleCopySelectedRows(
842
+ getRecordsFromIdMap(reduxFormSelectedEntityIdMap),
843
+ e
844
+ );
845
+ }
764
846
  };
847
+
765
848
  handleSelectAllRows = e => {
766
849
  const {
767
850
  change,
@@ -800,22 +883,12 @@ class DataTable extends React.Component {
800
883
  });
801
884
  }
802
885
  };
886
+
803
887
  updateValidationHelper = () => {
804
888
  const { entities, reduxFormCellValidation } = computePresets(this.props);
805
889
  this.updateValidation(entities, reduxFormCellValidation);
806
890
  };
807
891
 
808
- updateValidation = (entities, newCellValidate) => {
809
- const { change, schema } = computePresets(this.props);
810
- const tableWideErr = validateTableWideErrors({
811
- entities,
812
- schema,
813
- newCellValidate,
814
- props: this.props
815
- });
816
- change("reduxFormCellValidation", tableWideErr);
817
- this.forceUpdate();
818
- };
819
892
  handleDeleteCell = () => {
820
893
  const {
821
894
  reduxFormSelectedCells,
@@ -856,119 +929,11 @@ class DataTable extends React.Component {
856
929
  this.handleCopyHotkey(e);
857
930
  };
858
931
 
859
- getCellCopyText = cellWrapper => {
860
- const text = cellWrapper && cellWrapper.getAttribute("data-copy-text");
861
- const jsonText = cellWrapper && cellWrapper.getAttribute("data-copy-json");
862
-
863
- const textContent = text || cellWrapper.textContent || "";
864
- return [textContent, jsonText];
865
- };
866
-
867
- handleCopyColumn = (e, cellWrapper, selectedRecords) => {
868
- const specificColumn = cellWrapper.getAttribute("data-test");
869
- let rowElsToCopy = getAllRows(e);
870
- if (!rowElsToCopy) return;
871
- if (selectedRecords) {
872
- const ids = selectedRecords.map(e => getIdOrCodeOrIndex(e)?.toString());
873
- rowElsToCopy = Array.from(rowElsToCopy).filter(rowEl => {
874
- const id = rowEl.closest(".rt-tr-group")?.getAttribute("data-test-id");
875
- return id !== undefined && ids.includes(id);
876
- });
877
- }
878
- if (!rowElsToCopy) return;
879
- this.handleCopyRows(rowElsToCopy, {
880
- specificColumn,
881
- onFinishMsg: "Column Copied"
882
- });
883
- };
884
- handleCopyRows = (
885
- rowElsToCopy,
886
- { specificColumn, onFinishMsg, isDownload } = {}
887
- ) => {
888
- let textToCopy = [];
889
- const jsonToCopy = [];
890
- forEach(rowElsToCopy, rowEl => {
891
- const [t, j] = this.getRowCopyText(rowEl, { specificColumn });
892
- textToCopy.push(t);
893
- jsonToCopy.push(j);
894
- });
895
- textToCopy = textToCopy.filter(text => text).join("\n");
896
- if (!textToCopy) return window.toastr.warning("No text to copy");
897
- if (isDownload) {
898
- download(textToCopy.replaceAll("\t", ","), "tableData.csv", "text/csv");
899
- } else {
900
- this.handleCopyHelper(
901
- textToCopy,
902
- jsonToCopy,
903
- onFinishMsg || "Row Copied"
904
- );
905
- }
906
- };
907
- updateEntitiesHelper = (ents, fn) => {
908
- const { change, reduxFormEntitiesUndoRedoStack = { currentVersion: 0 } } =
909
- this.props;
910
- const [nextState, patches, inversePatches] = produceWithPatches(ents, fn);
911
- if (!inversePatches.length) return;
912
- const thatNewNew = [...nextState];
913
- thatNewNew.isDirty = true;
914
- change("reduxFormEntities", thatNewNew);
915
- change("reduxFormEntitiesUndoRedoStack", {
916
- ...omitBy(reduxFormEntitiesUndoRedoStack, (v, k) => {
917
- return toNumber(k) > reduxFormEntitiesUndoRedoStack.currentVersion + 1;
918
- }),
919
- currentVersion: reduxFormEntitiesUndoRedoStack.currentVersion + 1,
920
- [reduxFormEntitiesUndoRedoStack.currentVersion + 1]: {
921
- inversePatches,
922
- patches
923
- }
924
- });
925
- };
926
-
927
- getRowCopyText = (rowEl, { specificColumn } = {}) => {
928
- //takes in a row element
929
- if (!rowEl) return [];
930
- const textContent = [];
931
- const jsonText = [];
932
-
933
- forEach(rowEl.children, cellEl => {
934
- const cellChild = cellEl.querySelector(`[data-copy-text]`);
935
- if (!cellChild) {
936
- if (specificColumn) return []; //strip it
937
- return; //just leave it blank
938
- }
939
- if (
940
- specificColumn &&
941
- cellChild.getAttribute("data-test") !== specificColumn
942
- ) {
943
- return [];
944
- }
945
- const [t, j] = this.getCellCopyText(cellChild);
946
- textContent.push(t);
947
- jsonText.push(j);
948
- });
949
-
950
- return [flatMap(textContent).join("\t"), jsonText];
951
- };
952
-
953
- handleCopyHelper = (stringToCopy, jsonToCopy, message) => {
954
- !window.Cypress &&
955
- copy(stringToCopy, {
956
- onCopy: clipboardData => {
957
- clipboardData.setData("application/json", JSON.stringify(jsonToCopy));
958
- },
959
- // keep this so that pasting into spreadsheets works.
960
- format: "text/plain"
961
- });
962
- if (message) {
963
- window.toastr.success(message);
964
- }
965
- };
966
-
967
932
  handleCopyTable = (e, opts) => {
968
933
  try {
969
934
  const allRowEls = getAllRows(e);
970
935
  if (!allRowEls) return;
971
- this.handleCopyRows(allRowEls, {
936
+ handleCopyRows(allRowEls, {
972
937
  ...opts,
973
938
  onFinishMsg: "Table Copied"
974
939
  });
@@ -977,6 +942,7 @@ class DataTable extends React.Component {
977
942
  window.toastr.error("Error copying rows.");
978
943
  }
979
944
  };
945
+
980
946
  handleCopySelectedCells = e => {
981
947
  const {
982
948
  entities = [],
@@ -1022,7 +988,7 @@ class DataTable extends React.Component {
1022
988
  } else {
1023
989
  const jsonRow = [];
1024
990
  // ignore header
1025
- let [rowCopyText, json] = this.getRowCopyText(allRows[i + 1]);
991
+ let [rowCopyText, json] = getRowCopyText(allRows[i + 1]);
1026
992
  rowCopyText = rowCopyText.split("\t");
1027
993
  times(row.length, i => {
1028
994
  const cell = row[i];
@@ -1037,7 +1003,7 @@ class DataTable extends React.Component {
1037
1003
  });
1038
1004
  if (!fullCellText) return window.toastr.warning("No text to copy");
1039
1005
 
1040
- this.handleCopyHelper(fullCellText, fullJson, "Selected cells copied");
1006
+ handleCopyHelper(fullCellText, fullJson, "Selected cells copied");
1041
1007
  };
1042
1008
 
1043
1009
  handleCopySelectedRows = (selectedRecords, e) => {
@@ -1060,7 +1026,7 @@ class DataTable extends React.Component {
1060
1026
  if (!allRowEls) return;
1061
1027
  const rowEls = rowNumbersToCopy.map(i => allRowEls[i]);
1062
1028
 
1063
- this.handleCopyRows(rowEls, {
1029
+ handleCopyRows(rowEls, {
1064
1030
  onFinishMsg: "Selected rows copied"
1065
1031
  });
1066
1032
  } catch (error) {
@@ -1117,25 +1083,42 @@ class DataTable extends React.Component {
1117
1083
  />
1118
1084
  );
1119
1085
  };
1086
+
1120
1087
  getThComponent = compose(
1121
1088
  withProps(props => {
1122
1089
  const { columnindex } = props;
1123
1090
  return {
1124
- index: columnindex || 0
1091
+ index: columnindex ?? -1
1125
1092
  };
1126
- }),
1127
- branch(({ immovable }) => "true" !== immovable, SortableElement)
1128
- )(({ toggleSort, className, children, ...rest }) => (
1129
- <div
1130
- className={classNames("rt-th", className)}
1131
- onClick={e => toggleSort && toggleSort(e)}
1132
- role="columnheader"
1133
- tabIndex="-1" // Resolves eslint issues without implementing keyboard navigation incorrectly
1134
- {...rest}
1135
- >
1136
- {children}
1137
- </div>
1138
- ));
1093
+ })
1094
+ )(({ toggleSort, immovable, className, children, style, ...rest }) => {
1095
+ const { attributes, listeners, setNodeRef, transform, transition } =
1096
+ useSortable({
1097
+ id: `${rest.index}`,
1098
+ disabled: immovable === "true"
1099
+ });
1100
+
1101
+ const sortStyles = {
1102
+ transform: CSS.Transform.toString(transform),
1103
+ transition
1104
+ };
1105
+
1106
+ return (
1107
+ <div
1108
+ style={{ ...sortStyles, ...style }}
1109
+ ref={setNodeRef}
1110
+ {...attributes}
1111
+ {...listeners}
1112
+ className={classNames("rt-th", className)}
1113
+ onClick={e => toggleSort && toggleSort(e)}
1114
+ role="columnheader"
1115
+ tabIndex="-1" // Resolves eslint issues without implementing keyboard navigation incorrectly
1116
+ {...rest}
1117
+ >
1118
+ {children}
1119
+ </div>
1120
+ );
1121
+ });
1139
1122
 
1140
1123
  addEntitiesToSelection = entities => {
1141
1124
  const propPresets = computePresets(this.props);
@@ -1319,7 +1302,11 @@ class DataTable extends React.Component {
1319
1302
  icon="fullscreen"
1320
1303
  active={fullscreen}
1321
1304
  minimal
1322
- onClick={this.toggleFullscreen}
1305
+ onClick={() => {
1306
+ this.setState({
1307
+ fullscreen: !this.state.fullscreen
1308
+ });
1309
+ }}
1323
1310
  />
1324
1311
  );
1325
1312
 
@@ -1474,24 +1461,17 @@ class DataTable extends React.Component {
1474
1461
  {...(isCellEditable && {
1475
1462
  tabIndex: -1,
1476
1463
  onKeyDown: e => {
1477
- // const isArrowKey =
1478
- // (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode === 9;
1479
- // if (isArrowKey && e.target?.tagName !== "INPUT") {
1480
- const isTabKey = e.keyCode === 9;
1481
- // const isEnter = e.keyCode === 13;
1482
- // console.log(`onKeydown datatable inner`);
1483
- // console.log(`isEnter:`, isEnter)
1484
- const isArrowKey = e.keyCode >= 37 && e.keyCode <= 40;
1485
- // console.log(`e.target?.tagName:`,e.target?.tagName)
1464
+ const isTabKey = e.key === "Tab";
1465
+ const isArrowKey = e.key.startsWith("Arrow");
1486
1466
  if (
1487
1467
  (isArrowKey && e.target?.tagName !== "INPUT") ||
1488
1468
  isTabKey
1489
1469
  // || (isEnter && e.target?.tagName === "INPUT")
1490
1470
  ) {
1491
1471
  const { schema, entities } = computePresets(this.props);
1492
- const left = e.keyCode === 37;
1493
- const up = e.keyCode === 38;
1494
- const down = e.keyCode === 40 || e.keyCode === 13;
1472
+ const left = e.key === "ArrowLeft";
1473
+ const up = e.key === "ArrowUp";
1474
+ const down = e.key === "ArrowDown" || e.key === "Enter";
1495
1475
  let cellIdToUse = this.getPrimarySelectedCellId();
1496
1476
  const pathToIndex = getFieldPathToIndex(schema);
1497
1477
  const entityMap = getEntityIdToEntity(entities);
@@ -1584,13 +1564,23 @@ class DataTable extends React.Component {
1584
1564
  const entity = entityIdToEntity[rowId].e;
1585
1565
  if (!entity) return;
1586
1566
  const rowDisabled = isEntityDisabled(entity);
1587
- const isNum = e.keyCode >= 48 && e.keyCode <= 57;
1588
- const isLetter = e.keyCode >= 65 && e.keyCode <= 90;
1589
- if (!isNum && !isLetter) return;
1567
+ const isNum = e.code?.startsWith("Digit");
1568
+ const isLetter = e.code?.startsWith("Key");
1569
+ if (!isNum && !isLetter) {
1570
+ this.setState(prev => ({
1571
+ ...prev,
1572
+ editableCellInitialValue: ""
1573
+ }));
1574
+ return;
1575
+ } else {
1576
+ this.setState(prev => ({
1577
+ ...prev,
1578
+ editableCellInitialValue: e.key
1579
+ }));
1580
+ }
1590
1581
  if (rowDisabled) return;
1591
1582
  this.startCellEdit(cellId, { shouldSelectAll: true });
1592
1583
  e.stopPropagation();
1593
- // e.preventDefault();
1594
1584
  }
1595
1585
  })}
1596
1586
  >
@@ -1730,10 +1720,7 @@ class DataTable extends React.Component {
1730
1720
  )}
1731
1721
  <ReactTable
1732
1722
  data={filteredEnts}
1733
- ref={n => {
1734
- if (n) this.table = n;
1735
- }}
1736
- // additionalBodyEl={}
1723
+ ref={this.tableRef}
1737
1724
  className={classNames({
1738
1725
  isCellEditable,
1739
1726
  "tg-table-loading": isLoading,
@@ -1820,7 +1807,7 @@ class DataTable extends React.Component {
1820
1807
  data-tip="Download Table as CSV"
1821
1808
  minimal
1822
1809
  icon="download"
1823
- ></Button>
1810
+ />
1824
1811
  </div>
1825
1812
  )}
1826
1813
  {!noFooter && (
@@ -1965,24 +1952,6 @@ class DataTable extends React.Component {
1965
1952
  };
1966
1953
  };
1967
1954
 
1968
- startCellEdit = (cellId, { shouldSelectAll } = {}) => {
1969
- const {
1970
- change,
1971
- reduxFormSelectedCells = {},
1972
- reduxFormEditingCell
1973
- } = computePresets(this.props);
1974
- const newSelectedCells = { ...reduxFormSelectedCells };
1975
- newSelectedCells[cellId] = PRIMARY_SELECTED_VAL;
1976
- //check if the cell is already selected and editing and if so, don't change it
1977
- if (reduxFormEditingCell === cellId) return;
1978
- change("reduxFormSelectedCells", newSelectedCells);
1979
- change("reduxFormEditingCell", cellId);
1980
- if (shouldSelectAll) {
1981
- //we should select the text
1982
- change("reduxFormEditingCellSelectAll", true);
1983
- }
1984
- };
1985
-
1986
1955
  getTableCellProps = (state, rowInfo, column) => {
1987
1956
  const {
1988
1957
  entities,
@@ -2178,6 +2147,7 @@ class DataTable extends React.Component {
2178
2147
 
2179
2148
  change("reduxFormSelectedCells", newSelectedCells);
2180
2149
  };
2150
+
2181
2151
  renderCheckboxHeader = () => {
2182
2152
  const {
2183
2153
  reduxFormSelectedEntityIdMap,
@@ -2300,9 +2270,10 @@ class DataTable extends React.Component {
2300
2270
  change("reduxFormEditingCell", null);
2301
2271
  this.refocusTable();
2302
2272
  };
2273
+
2303
2274
  refocusTable = () => {
2304
2275
  setTimeout(() => {
2305
- const table = ReactDOM.findDOMNode(this.table)?.closest(
2276
+ const table = this.tableRef.current?.tableRef?.closest(
2306
2277
  ".data-table-container>div"
2307
2278
  );
2308
2279
  table?.focus();
@@ -2579,7 +2550,6 @@ class DataTable extends React.Component {
2579
2550
  <Checkbox
2580
2551
  disabled={isEntityDisabled(row.original)}
2581
2552
  className="tg-cell-edit-boolean-checkbox"
2582
- // {...dataTest}
2583
2553
  checked={oldVal === "True"}
2584
2554
  onChange={e => {
2585
2555
  const checked = e.target.checked;
@@ -2589,9 +2559,6 @@ class DataTable extends React.Component {
2589
2559
  );
2590
2560
  noEllipsis = true;
2591
2561
  } else {
2592
- // if (column.type === "genericSelect") {
2593
- // val =
2594
- // }
2595
2562
  if (reduxFormEditingCell === cellId) {
2596
2563
  if (column.type === "genericSelect") {
2597
2564
  const GenericSelectComp = column.GenericSelectComp;
@@ -2622,7 +2589,7 @@ class DataTable extends React.Component {
2622
2589
  }}
2623
2590
  dataTest={dataTest}
2624
2591
  cancelEdit={this.cancelCellEdit}
2625
- ></DropdownCell>
2592
+ />
2626
2593
  );
2627
2594
  } else {
2628
2595
  return (
@@ -2634,11 +2601,18 @@ class DataTable extends React.Component {
2634
2601
  shouldSelectAll={reduxFormEditingCellSelectAll}
2635
2602
  cancelEdit={this.cancelCellEdit}
2636
2603
  isNumeric={column.type === "number"}
2637
- initialValue={text}
2604
+ initialValue={
2605
+ this.state.editableCellInitialValue.length
2606
+ ? this.state.editableCellInitialValue
2607
+ : text
2608
+ }
2609
+ isEditableCellInitialValue={
2610
+ !!this.state.editableCellInitialValue.length
2611
+ }
2638
2612
  finishEdit={newVal => {
2639
2613
  this.finishCellEdit(cellId, newVal);
2640
2614
  }}
2641
- ></EditableCell>
2615
+ />
2642
2616
  );
2643
2617
  }
2644
2618
  }
@@ -2710,7 +2684,7 @@ class DataTable extends React.Component {
2710
2684
 
2711
2685
  {isSelectedCell &&
2712
2686
  (isRect
2713
- ? this.isBottomRightCornerOfRectangle({
2687
+ ? isBottomRightCornerOfRectangle({
2714
2688
  cellId,
2715
2689
  selectionGrid,
2716
2690
  lastRowIndex,
@@ -2721,11 +2695,11 @@ class DataTable extends React.Component {
2721
2695
  : isSelectedCell === PRIMARY_SELECTED_VAL) && (
2722
2696
  <CellDragHandle
2723
2697
  key={cellId}
2724
- thisTable={this.table}
2698
+ thisTable={this.tableRef.current.tableRef}
2725
2699
  cellId={cellId}
2726
2700
  isSelectionARectangle={this.isSelectionARectangle}
2727
2701
  onDragEnd={this.onDragEnd}
2728
- ></CellDragHandle>
2702
+ />
2729
2703
  )}
2730
2704
  </>
2731
2705
  );
@@ -2735,26 +2709,6 @@ class DataTable extends React.Component {
2735
2709
  });
2736
2710
  return columnsToRender;
2737
2711
  };
2738
- isBottomRightCornerOfRectangle = ({
2739
- cellId,
2740
- selectionGrid,
2741
- lastRowIndex,
2742
- lastCellIndex,
2743
- entityMap,
2744
- pathToIndex
2745
- }) => {
2746
- selectionGrid.forEach(row => {
2747
- // remove undefineds from start of row
2748
- while (row[0] === undefined && row.length) row.shift();
2749
- });
2750
- const [rowId, cellPath] = cellId.split(":");
2751
- const ent = entityMap[rowId];
2752
- if (!ent) return;
2753
- const { i } = ent;
2754
- const cellIndex = pathToIndex[cellPath];
2755
- const isBottomRight = i === lastRowIndex && cellIndex === lastCellIndex;
2756
- return isBottomRight;
2757
- };
2758
2712
 
2759
2713
  onDragEnd = cellsToSelect => {
2760
2714
  const {
@@ -2959,6 +2913,7 @@ class DataTable extends React.Component {
2959
2913
  change("reduxFormSelectedCells", newReduxFormSelectedCells);
2960
2914
  });
2961
2915
  };
2916
+
2962
2917
  getCopyTextForCell = (val, row = {}, column = {}) => {
2963
2918
  const { cellRenderer } = computePresets(this.props);
2964
2919
  // TODOCOPY we need a way to potentially omit certain columns from being added as a \t element (talk to taoh about this)
@@ -3044,6 +2999,7 @@ class DataTable extends React.Component {
3044
2999
  });
3045
3000
  });
3046
3001
  };
3002
+
3047
3003
  getEditableTableInfoAndThrowFormError = () => {
3048
3004
  const { schema, reduxFormEntities, reduxFormCellValidation } =
3049
3005
  computePresets(this.props);
@@ -3153,12 +3109,12 @@ class DataTable extends React.Component {
3153
3109
  //TODOCOPY: we need to make sure that the cell copy is being used by the row copy.. right now we have 2 different things going on
3154
3110
  //do we need to be able to copy hidden cells? It seems like it should just copy what's on the page..?
3155
3111
  const specificColumn = cellWrapper.getAttribute("data-test");
3156
- this.handleCopyRows([cellWrapper.closest(".rt-tr")], {
3112
+ handleCopyRows([cellWrapper.closest(".rt-tr")], {
3157
3113
  specificColumn,
3158
3114
  onFinishMsg: "Cell copied"
3159
3115
  });
3160
- const [text, jsonText] = this.getCellCopyText(cellWrapper);
3161
- this.handleCopyHelper(text, jsonText);
3116
+ const [text, jsonText] = getCellCopyText(cellWrapper);
3117
+ handleCopyHelper(text, jsonText);
3162
3118
  }}
3163
3119
  text="Cell"
3164
3120
  />
@@ -3168,7 +3124,7 @@ class DataTable extends React.Component {
3168
3124
  <MenuItem
3169
3125
  key="copyColumn"
3170
3126
  onClick={() => {
3171
- this.handleCopyColumn(e, cellWrapper);
3127
+ handleCopyColumn(e, cellWrapper);
3172
3128
  }}
3173
3129
  text="Column"
3174
3130
  />
@@ -3178,7 +3134,7 @@ class DataTable extends React.Component {
3178
3134
  <MenuItem
3179
3135
  key="copyColumnSelected"
3180
3136
  onClick={() => {
3181
- this.handleCopyColumn(e, cellWrapper, selectedRecords);
3137
+ handleCopyColumn(e, cellWrapper, selectedRecords);
3182
3138
  }}
3183
3139
  text="Column (Selected)"
3184
3140
  />
@@ -3196,7 +3152,7 @@ class DataTable extends React.Component {
3196
3152
  <MenuItem
3197
3153
  key="copySelectedRows"
3198
3154
  onClick={() => {
3199
- this.handleCopyRows([row]);
3155
+ handleCopyRows([row]);
3200
3156
  // loop through each cell in the row
3201
3157
  }}
3202
3158
  text="Row"
@@ -3247,7 +3203,7 @@ class DataTable extends React.Component {
3247
3203
  onClick={() => {
3248
3204
  this.insertRows({ above: true });
3249
3205
  }}
3250
- ></MenuItem>
3206
+ />
3251
3207
  <MenuItem
3252
3208
  icon="add-row-top"
3253
3209
  text="Add Row Below"
@@ -3255,7 +3211,7 @@ class DataTable extends React.Component {
3255
3211
  onClick={() => {
3256
3212
  this.insertRows({});
3257
3213
  }}
3258
- ></MenuItem>
3214
+ />
3259
3215
  <MenuItem
3260
3216
  icon="remove"
3261
3217
  text={`Remove Row${selectedRowIds.length > 1 ? "s" : ""}`}
@@ -3445,7 +3401,7 @@ class DataTable extends React.Component {
3445
3401
  }}
3446
3402
  indeterminate={isIndeterminate}
3447
3403
  checked={isChecked}
3448
- ></Checkbox>
3404
+ />
3449
3405
  );
3450
3406
  }
3451
3407
 
@@ -3470,7 +3426,7 @@ class DataTable extends React.Component {
3470
3426
  })}
3471
3427
  >
3472
3428
  {columnTitleTextified && !noTitle && (
3473
- <React.Fragment>
3429
+ <>
3474
3430
  {maybeCheckbox}
3475
3431
  <span
3476
3432
  title={columnTitleTextified}
@@ -3485,7 +3441,7 @@ class DataTable extends React.Component {
3485
3441
  >
3486
3442
  {renderTitleInner ? renderTitleInner : columnTitle}{" "}
3487
3443
  </span>
3488
- </React.Fragment>
3444
+ </>
3489
3445
  )}
3490
3446
  <div
3491
3447
  style={{ display: "flex", marginLeft: "auto", alignItems: "center" }}
@@ -3507,333 +3463,3 @@ const WrappedDT = dataTableEnhancer(DataTable);
3507
3463
  export default WrappedDT;
3508
3464
  const ConnectedPagingTool = dataTableEnhancer(PagingTool);
3509
3465
  export { ConnectedPagingTool };
3510
-
3511
- const itemSizeEstimators = {
3512
- compact: () => 25.34,
3513
- normal: () => 33.34,
3514
- comfortable: () => 41.34
3515
- };
3516
-
3517
- function getCellInfo({
3518
- columnIndex,
3519
- columnPath,
3520
- rowId,
3521
- schema,
3522
- entities,
3523
- rowIndex,
3524
- isEntityDisabled,
3525
- entity
3526
- }) {
3527
- const leftpath = schema.fields[columnIndex - 1]?.path;
3528
- const rightpath = schema.fields[columnIndex + 1]?.path;
3529
- const cellIdToLeft = leftpath && `${rowId}:${leftpath}`;
3530
- const cellIdToRight = rightpath && `${rowId}:${rightpath}`;
3531
- const rowAboveId =
3532
- entities[rowIndex - 1] &&
3533
- getIdOrCodeOrIndex(entities[rowIndex - 1], rowIndex - 1);
3534
- const rowBelowId =
3535
- entities[rowIndex + 1] &&
3536
- getIdOrCodeOrIndex(entities[rowIndex + 1], rowIndex + 1);
3537
- const cellIdAbove = rowAboveId && `${rowAboveId}:${columnPath}`;
3538
- const cellIdBelow = rowBelowId && `${rowBelowId}:${columnPath}`;
3539
-
3540
- const cellId = `${rowId}:${columnPath}`;
3541
- const rowDisabled = isEntityDisabled(entity);
3542
- return {
3543
- cellId,
3544
- cellIdAbove,
3545
- cellIdToRight,
3546
- cellIdBelow,
3547
- cellIdToLeft,
3548
- rowDisabled
3549
- };
3550
- }
3551
-
3552
- function ColumnFilterMenu({
3553
- FilterMenu,
3554
- filterActiveForColumn,
3555
- compact,
3556
- extraCompact,
3557
- ...rest
3558
- }) {
3559
- const [columnFilterMenuOpen, setColumnFilterMenuOpen] = useState(false);
3560
- return (
3561
- <Popover
3562
- position="bottom"
3563
- onClose={() => {
3564
- setColumnFilterMenuOpen(false);
3565
- }}
3566
- isOpen={columnFilterMenuOpen}
3567
- modifiers={{
3568
- preventOverflow: { enabled: true },
3569
- hide: { enabled: false },
3570
- flip: { enabled: false }
3571
- }}
3572
- >
3573
- <Icon
3574
- style={{ marginLeft: 5 }}
3575
- icon="filter"
3576
- iconSize={extraCompact ? 14 : undefined}
3577
- onClick={() => {
3578
- setColumnFilterMenuOpen(!columnFilterMenuOpen);
3579
- }}
3580
- className={classNames("tg-filter-menu-button", {
3581
- "tg-active-filter": !!filterActiveForColumn
3582
- })}
3583
- />
3584
- <FilterMenu
3585
- togglePopover={() => {
3586
- setColumnFilterMenuOpen(false);
3587
- }}
3588
- {...rest}
3589
- />
3590
- </Popover>
3591
- );
3592
- }
3593
-
3594
- function getLastSelectedEntity(idMap) {
3595
- let lastSelectedEnt;
3596
- let latestTime;
3597
- forEach(idMap, ({ time, entity }) => {
3598
- if (!latestTime || time > latestTime) {
3599
- lastSelectedEnt = entity;
3600
- latestTime = time;
3601
- }
3602
- });
3603
- return lastSelectedEnt;
3604
- }
3605
-
3606
- function getNewEntToSelect({
3607
- type,
3608
- lastSelectedIndex,
3609
- entities,
3610
- isEntityDisabled
3611
- }) {
3612
- let newIndexToSelect;
3613
- if (type === "up") {
3614
- newIndexToSelect = lastSelectedIndex - 1;
3615
- } else {
3616
- newIndexToSelect = lastSelectedIndex + 1;
3617
- }
3618
- const newEntToSelect = entities[newIndexToSelect];
3619
- if (!newEntToSelect) return;
3620
- if (isEntityDisabled && isEntityDisabled(newEntToSelect)) {
3621
- return getNewEntToSelect({
3622
- type,
3623
- lastSelectedIndex: newIndexToSelect,
3624
- entities,
3625
- isEntityDisabled
3626
- });
3627
- } else {
3628
- return newEntToSelect;
3629
- }
3630
- }
3631
-
3632
- function getAllRows(e) {
3633
- const el = e.target.querySelector(".data-table-container")
3634
- ? e.target.querySelector(".data-table-container")
3635
- : e.target.closest(".data-table-container");
3636
-
3637
- const allRowEls = el.querySelectorAll(".rt-tr");
3638
- if (!allRowEls || !allRowEls.length) {
3639
- return;
3640
- }
3641
- return allRowEls;
3642
- }
3643
-
3644
- function EditableCell({
3645
- shouldSelectAll,
3646
- stopSelectAll,
3647
- initialValue,
3648
- finishEdit,
3649
- cancelEdit,
3650
- isNumeric,
3651
- dataTest
3652
- }) {
3653
- const [v, setV] = useState(initialValue);
3654
- return (
3655
- <input
3656
- style={{
3657
- border: 0,
3658
- width: "95%",
3659
- fontSize: 12,
3660
- background: "none"
3661
- }}
3662
- ref={r => {
3663
- if (shouldSelectAll && r) {
3664
- r?.select();
3665
- stopSelectAll();
3666
- }
3667
- }}
3668
- {...dataTest}
3669
- type={isNumeric ? "number" : undefined}
3670
- value={v}
3671
- autoFocus
3672
- onKeyDown={e => {
3673
- if (e.key === "Enter") {
3674
- finishEdit(v);
3675
- e.stopPropagation();
3676
- } else if (e.key === "Escape") {
3677
- e.stopPropagation();
3678
- cancelEdit();
3679
- }
3680
- }}
3681
- onBlur={() => {
3682
- finishEdit(v);
3683
- }}
3684
- onChange={e => {
3685
- setV(e.target.value);
3686
- }}
3687
- ></input>
3688
- );
3689
- }
3690
-
3691
- function DropdownCell({
3692
- options,
3693
- isMulti,
3694
- initialValue,
3695
- finishEdit,
3696
- cancelEdit,
3697
- dataTest
3698
- }) {
3699
- const [v, setV] = useState(
3700
- isMulti
3701
- ? initialValue.split(",").map(v => ({ value: v, label: v }))
3702
- : initialValue
3703
- );
3704
- return (
3705
- <div
3706
- className={classNames("tg-dropdown-cell-edit-container", {
3707
- "tg-dropdown-cell-edit-container-multi": isMulti
3708
- })}
3709
- >
3710
- <TgSelect
3711
- small
3712
- multi={isMulti}
3713
- autoOpen
3714
- value={v}
3715
- onChange={val => {
3716
- if (isMulti) {
3717
- setV(val);
3718
- return;
3719
- }
3720
- finishEdit(val ? val.value : null);
3721
- }}
3722
- {...dataTest}
3723
- popoverProps={{
3724
- onClose: e => {
3725
- if (isMulti) {
3726
- if (e && e.key === "Escape") {
3727
- cancelEdit();
3728
- } else {
3729
- finishEdit(
3730
- v && v.map
3731
- ? v
3732
- .map(v => v.value)
3733
- .filter(v => v)
3734
- .join(",")
3735
- : v
3736
- );
3737
- }
3738
- } else {
3739
- cancelEdit();
3740
- }
3741
- }
3742
- }}
3743
- options={options.map(value => ({ label: value, value }))}
3744
- ></TgSelect>
3745
- </div>
3746
- );
3747
- }
3748
-
3749
- function getFieldPathToIndex(schema) {
3750
- const fieldToIndex = {};
3751
- schema.fields.forEach((f, i) => {
3752
- fieldToIndex[f.path] = i;
3753
- });
3754
- return fieldToIndex;
3755
- }
3756
-
3757
- function getFieldPathToField(schema) {
3758
- const fieldPathToField = {};
3759
- schema.fields.forEach(f => {
3760
- fieldPathToField[f.path] = f;
3761
- });
3762
- return fieldPathToField;
3763
- }
3764
-
3765
- const defaultParsePaste = str => {
3766
- return str.split(/\r\n|\n|\r/).map(row => row.split("\t"));
3767
- };
3768
-
3769
- function getEntityIdToEntity(entities) {
3770
- const entityIdToEntity = {};
3771
- entities.forEach((e, i) => {
3772
- entityIdToEntity[getIdOrCodeOrIndex(e, i)] = { e, i };
3773
- });
3774
- return entityIdToEntity;
3775
- }
3776
-
3777
- function endsWithNumber(str) {
3778
- return /[0-9]+$/.test(str);
3779
- }
3780
-
3781
- function getNumberStrAtEnd(str) {
3782
- if (endsWithNumber(str)) {
3783
- return str.match(/[0-9]+$/)[0];
3784
- }
3785
-
3786
- return null;
3787
- }
3788
-
3789
- function stripNumberAtEnd(str) {
3790
- return str?.replace?.(getNumberStrAtEnd(str), "");
3791
- }
3792
-
3793
- export function isEntityClean(e) {
3794
- let isClean = true;
3795
- some(e, (val, key) => {
3796
- if (key === "id") return;
3797
- if (key === "_isClean") return;
3798
- if (val) {
3799
- isClean = false;
3800
- return true;
3801
- }
3802
- });
3803
- return isClean;
3804
- }
3805
-
3806
- const formatPasteData = ({ schema, newVal, path }) => {
3807
- const pathToField = getFieldPathToField(schema);
3808
- const column = pathToField[path];
3809
- if (column.type === "genericSelect") {
3810
- if (newVal?.__genSelCol === path) {
3811
- newVal = newVal.__strVal;
3812
- } else {
3813
- newVal = undefined;
3814
- }
3815
- } else {
3816
- newVal = Object.hasOwn(newVal, "__strVal") ? newVal.__strVal : newVal;
3817
- }
3818
- return newVal;
3819
- };
3820
-
3821
- export function removeCleanRows(reduxFormEntities, reduxFormCellValidation) {
3822
- const toFilterOut = {};
3823
- const entsToUse = (reduxFormEntities || []).filter(e => {
3824
- if (!(e._isClean || isEntityClean(e))) return true;
3825
- else {
3826
- toFilterOut[getIdOrCodeOrIndex(e)] = true;
3827
- return false;
3828
- }
3829
- });
3830
-
3831
- const validationToUse = {};
3832
- forEach(reduxFormCellValidation, (v, k) => {
3833
- const [rowId] = k.split(":");
3834
- if (!toFilterOut[rowId]) {
3835
- validationToUse[k] = v;
3836
- }
3837
- });
3838
- return { entsToUse, validationToUse };
3839
- }