@parca/profile 0.16.444 → 0.16.446

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 (48) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.d.ts.map +1 -1
  3. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.js +9 -2
  4. package/dist/ProfileIcicleGraph/index.d.ts.map +1 -1
  5. package/dist/ProfileIcicleGraph/index.js +3 -11
  6. package/dist/ProfileView/ColorStackLegend.d.ts.map +1 -0
  7. package/dist/{ProfileIcicleGraph/IcicleGraphArrow → ProfileView}/ColorStackLegend.js +2 -2
  8. package/dist/ProfileView/VisualizationPanel.d.ts +4 -1
  9. package/dist/ProfileView/VisualizationPanel.d.ts.map +1 -1
  10. package/dist/ProfileView/VisualizationPanel.js +4 -6
  11. package/dist/ProfileView/index.d.ts.map +1 -1
  12. package/dist/ProfileView/index.js +33 -10
  13. package/dist/ProfileViewWithData.d.ts.map +1 -1
  14. package/dist/ProfileViewWithData.js +1 -3
  15. package/dist/Table/index.d.ts +2 -29
  16. package/dist/Table/index.d.ts.map +1 -1
  17. package/dist/Table/index.js +65 -160
  18. package/dist/Table/utils/functions.d.ts +49 -0
  19. package/dist/Table/utils/functions.d.ts.map +1 -0
  20. package/dist/Table/utils/functions.js +181 -0
  21. package/dist/components/ActionButtons/GroupByDropdown.js +1 -1
  22. package/dist/components/ActionButtons/SortByDropdown.d.ts +3 -0
  23. package/dist/components/ActionButtons/SortByDropdown.d.ts.map +1 -0
  24. package/dist/components/ActionButtons/SortByDropdown.js +49 -0
  25. package/dist/components/VisualisationToolbar/MultiLevelDropdown.d.ts.map +1 -1
  26. package/dist/components/VisualisationToolbar/MultiLevelDropdown.js +3 -27
  27. package/dist/components/VisualisationToolbar/TableColumnsDropdown.d.ts.map +1 -1
  28. package/dist/components/VisualisationToolbar/TableColumnsDropdown.js +3 -1
  29. package/dist/components/VisualisationToolbar/index.d.ts +11 -0
  30. package/dist/components/VisualisationToolbar/index.d.ts.map +1 -1
  31. package/dist/components/VisualisationToolbar/index.js +13 -6
  32. package/dist/styles.css +1 -1
  33. package/package.json +3 -3
  34. package/src/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.tsx +19 -2
  35. package/src/ProfileIcicleGraph/index.tsx +2 -18
  36. package/src/{ProfileIcicleGraph/IcicleGraphArrow → ProfileView}/ColorStackLegend.tsx +2 -2
  37. package/src/ProfileView/VisualizationPanel.tsx +13 -10
  38. package/src/ProfileView/index.tsx +59 -9
  39. package/src/ProfileViewWithData.tsx +1 -3
  40. package/src/Table/index.tsx +138 -265
  41. package/src/Table/utils/functions.ts +284 -0
  42. package/src/components/ActionButtons/GroupByDropdown.tsx +1 -1
  43. package/src/components/ActionButtons/SortByDropdown.tsx +84 -0
  44. package/src/components/VisualisationToolbar/MultiLevelDropdown.tsx +7 -30
  45. package/src/components/VisualisationToolbar/TableColumnsDropdown.tsx +3 -1
  46. package/src/components/VisualisationToolbar/index.tsx +103 -58
  47. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ColorStackLegend.d.ts.map +0 -1
  48. /package/dist/{ProfileIcicleGraph/IcicleGraphArrow → ProfileView}/ColorStackLegend.d.ts +0 -0
@@ -32,11 +32,36 @@ import {
32
32
  useURLState,
33
33
  } from '@parca/components';
34
34
  import {type RowRendererProps} from '@parca/components/dist/Table';
35
+ import {useCurrentColorProfile} from '@parca/hooks';
35
36
  import {ProfileType} from '@parca/parser';
36
- import {getLastItem, isSearchMatch, valueFormatter} from '@parca/utilities';
37
+ import {isSearchMatch, valueFormatter} from '@parca/utilities';
37
38
 
39
+ import {getFilenameColors, getMappingColors} from '../ProfileIcicleGraph/IcicleGraphArrow/';
40
+ import {colorByColors} from '../ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes';
41
+ import useMappingList, {
42
+ useFilenamesList,
43
+ } from '../ProfileIcicleGraph/IcicleGraphArrow/useMappingList';
38
44
  import {useProfileViewContext} from '../ProfileView/ProfileViewContext';
39
- import {hexifyAddress} from '../utils';
45
+ import {
46
+ ColumnName,
47
+ DataRow,
48
+ DummyRow,
49
+ ROW_HEIGHT,
50
+ RowName,
51
+ addPlusSign,
52
+ getCalleeRows,
53
+ getCallerRows,
54
+ getRowColor,
55
+ getScrollTargetIndex,
56
+ isFirstSubRow,
57
+ isLastSubRow,
58
+ isSubRow,
59
+ ratioString,
60
+ rowBgClassNames,
61
+ sizeToBottomStyle,
62
+ sizeToHeightStyle,
63
+ sizeToWidthStyle,
64
+ } from './utils/functions';
40
65
  import {getTopAndBottomExpandedRowModel} from './utils/topAndBottomExpandedRowModel';
41
66
 
42
67
  const FIELD_MAPPING_FILE = 'mapping_file';
@@ -51,30 +76,6 @@ const FIELD_CUMULATIVE_DIFF = 'cumulative_diff';
51
76
  const FIELD_CALLERS = 'callers';
52
77
  const FIELD_CALLEES = 'callees';
53
78
 
54
- export interface DataRow {
55
- id: number;
56
- name: string;
57
- flat: bigint;
58
- flatDiff: bigint;
59
- cumulative: bigint;
60
- cumulativeDiff: bigint;
61
- mappingFile: string;
62
- functionSystemName: string;
63
- functionFileName: string;
64
- callers?: DataRow[];
65
- callees?: DataRow[];
66
- subRows?: Row[];
67
- isTopSubRow?: boolean;
68
- isBottomSubRow?: boolean;
69
- }
70
-
71
- interface DummyRow {
72
- size: number;
73
- message?: string;
74
- isTopSubRow?: boolean;
75
- isBottomSubRow?: boolean;
76
- }
77
-
78
79
  export type Row = DataRow | DummyRow;
79
80
 
80
81
  export const isDummyRow = (row: Row): row is DummyRow => {
@@ -94,50 +95,9 @@ interface TableProps {
94
95
  setActionButtons?: (buttons: React.JSX.Element) => void;
95
96
  isHalfScreen: boolean;
96
97
  unit?: string;
98
+ metadataMappingFiles?: string[];
97
99
  }
98
100
 
99
- export type ColumnName =
100
- | 'flat'
101
- | 'flatPercentage'
102
- | 'flatDiff'
103
- | 'flatDiffPercentage'
104
- | 'cumulative'
105
- | 'cumulativePercentage'
106
- | 'cumulativeDiff'
107
- | 'cumulativeDiffPercentage'
108
- | 'name'
109
- | 'functionSystemName'
110
- | 'functionFileName'
111
- | 'mappingFile';
112
-
113
- const rowBgClassNames = (isExpanded: boolean, isSubRow: boolean): Record<string, boolean> => {
114
- return {
115
- relative: true,
116
- 'bg-indigo-100 dark:bg-gray-600': isSubRow,
117
- 'bg-indigo-50 dark:bg-gray-700': isExpanded,
118
- };
119
- };
120
-
121
- const ROW_HEIGHT = 29;
122
-
123
- const sizeToHeightStyle = (size: number): Record<string, string> => {
124
- return {
125
- height: `${size * ROW_HEIGHT}px`,
126
- };
127
- };
128
-
129
- const sizeToWidthStyle = (size: number): Record<string, string> => {
130
- return {
131
- width: `${size * ROW_HEIGHT}px`,
132
- };
133
- };
134
-
135
- const sizeToBottomStyle = (size: number): Record<string, string> => {
136
- return {
137
- bottom: `-${size * ROW_HEIGHT}px`,
138
- };
139
- };
140
-
141
101
  const CustomRowRenderer = ({
142
102
  row,
143
103
  usePointerCursor,
@@ -241,13 +201,13 @@ const CustomRowRenderer = ({
241
201
  {idx === 0 && isExpanded ? (
242
202
  <>
243
203
  <div
244
- className={`absolute top-0 left-0 bg-white dark:bg-indigo-500 px-1 uppercase -rotate-90 origin-top-left z-10 text-[10px] border-l border-y border-gray-200 dark:border-gray-700 text-left`}
204
+ className={`absolute top-0 left-0 bg-white dark:bg-indigo-500 px-1 uppercase -rotate-90 origin-top-left z-[9] text-[10px] border-l border-y border-gray-200 dark:border-gray-700 text-left`}
245
205
  style={{...sizeToWidthStyle(3)}}
246
206
  >
247
207
  Callers {'->'}
248
208
  </div>
249
209
  <div
250
- className={`absolute left-[18px] bg-white dark:bg-indigo-500 px-1 uppercase -rotate-90 origin-bottom-left z-10 text-[10px] border-r border-y border-gray-200 dark:border-gray-700`}
210
+ className={`absolute left-[18px] bg-white dark:bg-indigo-500 px-1 uppercase -rotate-90 origin-bottom-left z-[9] text-[10px] border-r border-y border-gray-200 dark:border-gray-700`}
251
211
  style={{
252
212
  ...sizeToWidthStyle(3),
253
213
  ...sizeToBottomStyle(3),
@@ -266,68 +226,6 @@ const CustomRowRenderer = ({
266
226
  );
267
227
  };
268
228
 
269
- const getCallerRows = (callers: DataRow[]): Row[] => {
270
- if (callers.length === 0) {
271
- return [{size: 3, message: 'No callers.', isTopSubRow: true}];
272
- }
273
-
274
- const rows = callers.map(row => {
275
- return {...row, isTopSubRow: true};
276
- });
277
- if (rows.length >= 3) {
278
- return rows;
279
- }
280
-
281
- return [...rows, {size: 3 - rows.length, message: '', isTopSubRow: true}];
282
- };
283
-
284
- const getCalleeRows = (callees: DataRow[]): Row[] => {
285
- if (callees.length === 0) {
286
- return [{size: 3, message: 'No callees.', isBottomSubRow: true}];
287
- }
288
-
289
- const rows = callees.map(row => {
290
- return {...row, isBottomSubRow: true};
291
- });
292
- if (rows.length >= 3) {
293
- return rows;
294
- }
295
-
296
- return [{size: 3 - rows.length, message: '', isBottomSubRow: true}, ...rows];
297
- };
298
-
299
- export const getPercentageString = (value: bigint | number, total: bigint | number): string => {
300
- if (total === 0n) {
301
- return '0%';
302
- }
303
-
304
- const percentage = (Number(value) / Number(total)) * 100;
305
- return `${percentage.toFixed(2)}%`;
306
- };
307
-
308
- export const getRatioString = (value: bigint | number, total: bigint, filtered: bigint): string => {
309
- if (filtered === 0n) {
310
- return ` ${getPercentageString(value, total)}`;
311
- }
312
-
313
- return `${getPercentageString(value, total)} / ${getPercentageString(value, filtered)}`;
314
- };
315
-
316
- export const possibleColumns = [
317
- 'flat',
318
- 'flatPercentage',
319
- 'flatDiff',
320
- 'flatDiffPercentage',
321
- 'cumulative',
322
- 'cumulativePercentage',
323
- 'cumulativeDiff',
324
- 'cumulativeDiffPercentage',
325
- 'name',
326
- 'functionSystemName',
327
- 'functionFileName',
328
- 'mappingFile',
329
- ];
330
-
331
229
  export const Table = React.memo(function Table({
332
230
  data,
333
231
  total,
@@ -338,43 +236,82 @@ export const Table = React.memo(function Table({
338
236
  setSearchString = () => {},
339
237
  isHalfScreen,
340
238
  unit,
239
+ metadataMappingFiles,
341
240
  }: TableProps): React.JSX.Element {
241
+ const currentColorProfile = useCurrentColorProfile();
342
242
  const [dashboardItems] = useURLState<string[]>('dashboard_items', {
343
243
  alwaysReturnArray: true,
344
244
  });
245
+
345
246
  const [tableColumns] = useURLState<string[]>('table_columns', {
346
247
  alwaysReturnArray: true,
347
248
  });
348
-
249
+ const [colorBy, setColorBy] = useURLState('color_by');
349
250
  const {isDarkMode} = useParcaContext();
350
251
  const [expanded, setExpanded] = useState<ExpandedState>({});
351
252
  const [scrollToIndex, setScrollToIndex] = useState<number | undefined>(undefined);
352
253
 
353
254
  const {compareMode} = useProfileViewContext();
354
255
 
355
- const percentageString = (value: bigint | number, total: bigint | number): string => {
356
- if (total === 0n) {
357
- return '0%';
256
+ const table = useMemo(() => {
257
+ if (loading || data == null) {
258
+ return null;
358
259
  }
359
260
 
360
- const percentage = (Number(value) / Number(total)) * 100;
361
- return `${percentage.toFixed(2)}%`;
362
- };
261
+ return tableFromIPC(data);
262
+ }, [data, loading]);
363
263
 
364
- const ratioString = (value: bigint | number): string => {
365
- if (filtered === 0n) {
366
- return ` ${percentageString(value, total)}`;
264
+ const mappingsList = useMappingList(metadataMappingFiles);
265
+ const filenamesList = useFilenamesList(table);
266
+ const colorByValue = colorBy === undefined || colorBy === '' ? 'binary' : (colorBy as string);
267
+
268
+ const mappingsListCount = useMemo(
269
+ () => mappingsList.filter(m => m !== '').length,
270
+ [mappingsList]
271
+ );
272
+
273
+ // If there is only one mapping file, we want to color by filename by default.
274
+ useEffect(() => {
275
+ if (mappingsListCount === 1 && colorBy !== 'filename') {
276
+ setColorBy('filename');
367
277
  }
278
+ // eslint-disable-next-line react-hooks/exhaustive-deps
279
+ }, [mappingsListCount]);
280
+
281
+ const filenameColors = useMemo(() => {
282
+ const colors = getFilenameColors(filenamesList, isDarkMode, currentColorProfile);
283
+ return colors;
284
+ }, [isDarkMode, filenamesList, currentColorProfile]);
368
285
 
369
- return `${percentageString(value, total)} / ${percentageString(value, filtered)}`;
286
+ const mappingColors = useMemo(() => {
287
+ const colors = getMappingColors(mappingsList, isDarkMode, currentColorProfile);
288
+ return colors;
289
+ }, [isDarkMode, mappingsList, currentColorProfile]);
290
+
291
+ const colorByList = {
292
+ filename: filenameColors,
293
+ binary: mappingColors,
370
294
  };
371
295
 
296
+ type ColorByKey = keyof typeof colorByList;
297
+
298
+ const colorByColors: colorByColors = colorByList[colorByValue as ColorByKey];
299
+
372
300
  const columnHelper = createColumnHelper<Row>();
373
301
 
374
302
  unit = useMemo(() => unit ?? profileType?.sampleUnit ?? '', [unit, profileType?.sampleUnit]);
375
303
 
376
304
  const columns = useMemo<Array<ColumnDef<Row>>>(() => {
377
305
  return [
306
+ columnHelper.accessor('color', {
307
+ id: 'color',
308
+ header: '',
309
+ cell: info => {
310
+ const color = info.getValue() as string;
311
+ return <div className="w-4 h-4 rounded-[4px]" style={{backgroundColor: color}} />;
312
+ },
313
+ size: 10,
314
+ }),
378
315
  columnHelper.accessor('flat', {
379
316
  id: 'flat',
380
317
  header: 'Flat',
@@ -392,7 +329,7 @@ export const Table = React.memo(function Table({
392
329
  if (isDummyRow(info.row.original)) {
393
330
  return '';
394
331
  }
395
- return ratioString((info as CellContext<DataRow, bigint>).getValue());
332
+ return ratioString((info as CellContext<DataRow, bigint>).getValue(), total, filtered);
396
333
  },
397
334
  size: 120,
398
335
  meta: {
@@ -418,7 +355,7 @@ export const Table = React.memo(function Table({
418
355
  if (isDummyRow(info.row.original)) {
419
356
  return '';
420
357
  }
421
- return ratioString((info as CellContext<DataRow, bigint>).getValue());
358
+ return ratioString((info as CellContext<DataRow, bigint>).getValue(), total, filtered);
422
359
  },
423
360
  size: 120,
424
361
  meta: {
@@ -443,7 +380,7 @@ export const Table = React.memo(function Table({
443
380
  if (isDummyRow(info.row.original)) {
444
381
  return '';
445
382
  }
446
- return ratioString((info as CellContext<DataRow, bigint>).getValue());
383
+ return ratioString((info as CellContext<DataRow, bigint>).getValue(), total, filtered);
447
384
  },
448
385
  size: 150,
449
386
  meta: {
@@ -469,7 +406,7 @@ export const Table = React.memo(function Table({
469
406
  if (isDummyRow(info.row.original)) {
470
407
  return '';
471
408
  }
472
- return ratioString((info as CellContext<DataRow, bigint>).getValue());
409
+ return ratioString((info as CellContext<DataRow, bigint>).getValue(), total, filtered);
473
410
  },
474
411
  size: 170,
475
412
  meta: {
@@ -503,6 +440,7 @@ export const Table = React.memo(function Table({
503
440
 
504
441
  const [columnVisibility, setColumnVisibility] = useState(() => {
505
442
  return {
443
+ color: true,
506
444
  flat: true,
507
445
  flatPercentage: false,
508
446
  flatDiff: compareMode,
@@ -552,37 +490,40 @@ export const Table = React.memo(function Table({
552
490
  [selectSpan, dashboardItems.length]
553
491
  );
554
492
 
555
- const onRowDoubleClick = useCallback((row: RowType<Row>, rows: Array<RowType<Row>>) => {
556
- if (isDummyRow(row.original)) {
557
- return;
558
- }
559
- if (!isSubRow(row.original)) {
560
- row.toggleExpanded();
561
- return;
562
- }
563
- // find the original row for this subrow and toggle it
564
- const newRow = rows.find(
565
- r =>
566
- !isDummyRow(r.original) &&
567
- !isDummyRow(row.original) &&
568
- r.original.name === row.original.name &&
569
- !isSubRow(r.original)
570
- );
571
- const parentRow = rows.find(r => {
572
- const parent = row.getParentRow()!;
573
- if (isDummyRow(parent.original) || isDummyRow(r.original)) {
574
- return false;
493
+ const onRowDoubleClick = useCallback(
494
+ (row: RowType<Row>, rows: Array<RowType<Row>>) => {
495
+ if (isDummyRow(row.original)) {
496
+ return;
497
+ }
498
+ if (!isSubRow(row.original)) {
499
+ row.toggleExpanded();
500
+ return;
501
+ }
502
+ // find the original row for this subrow and toggle it
503
+ const newRow = rows.find(
504
+ r =>
505
+ !isDummyRow(r.original) &&
506
+ !isDummyRow(row.original) &&
507
+ r.original.name === row.original.name &&
508
+ !isSubRow(r.original)
509
+ );
510
+ const parentRow = rows.find(r => {
511
+ const parent = row.getParentRow()!;
512
+ if (isDummyRow(parent.original) || isDummyRow(r.original)) {
513
+ return false;
514
+ }
515
+ return r.original.name === parent.original.name;
516
+ });
517
+ if (parentRow == null || newRow == null) {
518
+ return;
575
519
  }
576
- return r.original.name === parent.original.name;
577
- });
578
- if (parentRow == null || newRow == null) {
579
- return;
580
- }
581
520
 
582
- newRow.toggleExpanded();
521
+ newRow.toggleExpanded();
583
522
 
584
- setScrollToIndex(getScrollTargetIndex(rows, parentRow, newRow));
585
- }, []);
523
+ setScrollToIndex(getScrollTargetIndex(rows, parentRow, newRow));
524
+ },
525
+ [setScrollToIndex]
526
+ );
586
527
 
587
528
  const shouldHighlightRow = useCallback(
588
529
  (row: Row) => {
@@ -608,14 +549,6 @@ export const Table = React.memo(function Table({
608
549
  ];
609
550
  }, [compareMode]);
610
551
 
611
- const table = useMemo(() => {
612
- if (loading || data == null) {
613
- return null;
614
- }
615
-
616
- return tableFromIPC(data);
617
- }, [data, loading]);
618
-
619
552
  const rows: DataRow[] = useMemo(() => {
620
553
  if (table == null || table.numRows === 0) {
621
554
  return [];
@@ -644,6 +577,13 @@ export const Table = React.memo(function Table({
644
577
 
645
578
  return {
646
579
  id: i,
580
+ color: getRowColor(
581
+ colorByColors,
582
+ mappingFileColumn,
583
+ i,
584
+ functionFileNameColumn,
585
+ colorBy as string
586
+ ),
647
587
  name: RowName(mappingFileColumn, locationAddressColumn, functionNameColumn, i),
648
588
  flat,
649
589
  flatDiff,
@@ -676,7 +616,21 @@ export const Table = React.memo(function Table({
676
616
  }
677
617
 
678
618
  return rows;
679
- }, [table]);
619
+ }, [table, colorByColors, colorBy]);
620
+
621
+ useEffect(() => {
622
+ setTimeout(() => {
623
+ if (currentSearchString == null || rows.length === 0) return;
624
+
625
+ const firstHighlightedRowIndex = rows.findIndex(row => {
626
+ return !isDummyRow(row) && isSearchMatch(currentSearchString, row.name);
627
+ });
628
+
629
+ if (firstHighlightedRowIndex !== -1) {
630
+ setScrollToIndex(firstHighlightedRowIndex);
631
+ }
632
+ }, 1000); // Adding a delay to allow the table to render seems to be the only way to get this to work i.e. scrolling down to the highlighted row
633
+ }, [currentSearchString, rows]);
680
634
 
681
635
  if (loading) {
682
636
  return (
@@ -734,85 +688,4 @@ export const Table = React.memo(function Table({
734
688
  );
735
689
  });
736
690
 
737
- export const addPlusSign = (num: string): string => {
738
- if (num.charAt(0) === '0' || num.charAt(0) === '-') {
739
- return num;
740
- }
741
-
742
- return `+${num}`;
743
- };
744
-
745
- export const RowName = (
746
- mappingFileColumn: Vector | null,
747
- locationAddressColumn: Vector | null,
748
- functionNameColumn: Vector | null,
749
- row: number
750
- ): string => {
751
- if (mappingFileColumn === null) {
752
- console.error('mapping_file column not found');
753
- return '';
754
- }
755
-
756
- const mappingFile: string | null = mappingFileColumn?.get(row);
757
- let mapping = '';
758
- // Show the last item in the mapping file only if there are more than 1 mappings
759
- if (mappingFile != null && mappingFileColumn.data.length > 1) {
760
- mapping = `[${getLastItem(mappingFile) ?? ''}]`;
761
- }
762
- const functionName: string | null = functionNameColumn?.get(row) ?? '';
763
- if (functionName !== null && functionName !== '') {
764
- return `${mapping} ${functionName}`;
765
- }
766
-
767
- const address: bigint = locationAddressColumn?.get(row) ?? 0;
768
-
769
- return hexifyAddress(address);
770
- };
771
-
772
- const getRowsCount = (rows: Array<RowType<Row>>): number => {
773
- if (rows.length < 6) {
774
- return 6;
775
- }
776
-
777
- return rows.length;
778
- };
779
-
780
- function getScrollTargetIndex(
781
- rows: Array<RowType<Row>>,
782
- parentRow: RowType<Row>,
783
- newRow: RowType<Row>
784
- ): number {
785
- const parentIndex = rows.indexOf(parentRow);
786
- const newRowIndex = rows.indexOf(newRow);
787
- let targetIndex = newRowIndex;
788
- if (parentIndex > newRowIndex) {
789
- // Adjusting the number of subs rows to scroll to the main row after expansion.
790
- targetIndex -= getRowsCount(newRow.subRows);
791
- }
792
- if (parentIndex < newRowIndex) {
793
- // If the parent row is above the new row, we need to adjust the number of subrows of the parent.
794
- targetIndex += getRowsCount(parentRow.subRows);
795
- }
796
- if (targetIndex < 0) {
797
- targetIndex = 0;
798
- }
799
- return targetIndex;
800
- }
801
-
802
- function isSubRow(row: Row): boolean {
803
- return row.isTopSubRow === true || row.isBottomSubRow === true;
804
- }
805
-
806
- function isLastSubRow(row: RowType<Row>, rows: Array<RowType<Row>>): boolean {
807
- const index = rows.indexOf(row);
808
- const nextRow = rows[index + 1];
809
- return nextRow == null || (!isSubRow(nextRow.original) && !nextRow.getIsExpanded());
810
- }
811
-
812
- function isFirstSubRow(row: RowType<Row>, rows: Array<RowType<Row>>): boolean {
813
- const index = rows.indexOf(row);
814
- const prevRow = rows[index - 1];
815
- return prevRow == null || (!isSubRow(prevRow.original) && !prevRow.getIsExpanded());
816
- }
817
-
818
691
  export default Table;