@gridsuite/commons-ui 0.198.0 → 0.200.0

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.
@@ -7,6 +7,7 @@ import { useVirtualizer } from "@tanstack/react-virtual";
7
7
  import { MODIFICATION_ROW_HEIGHT, networkModificationTableStyles, createHeaderCellStyle } from "./network-modification-table-styles.js";
8
8
  import { AUTO_EXTENSIBLE_COLUMNS } from "./columns-definition.js";
9
9
  import { useModificationsDragAndDrop } from "./use-modifications-drag-and-drop.js";
10
+ import { useModificationsSelection } from "./use-modifications-selection.js";
10
11
  import { formatToComposedModification, mergeSubModificationsIntoTree, findAllLoadedCompositeModifications, fetchSubModificationsForExpandedRows, isCompositeModification } from "./utils.js";
11
12
  import "@mui/icons-material";
12
13
  import "react-intl";
@@ -33,11 +34,14 @@ function NetworkModificationsTable({
33
34
  }) {
34
35
  const theme = useTheme();
35
36
  const containerRef = useRef(null);
36
- const lastClickedIndex = useRef(null);
37
37
  const [expanded, setExpanded] = useState({});
38
38
  const [composedModifications, setComposedModifications] = useState(
39
39
  formatToComposedModification(modifications)
40
40
  );
41
+ const { rowSelection, onRowSelectionChange, lastClickedRowId, emitSelection } = useModificationsSelection({
42
+ modifications: composedModifications,
43
+ onRowSelected
44
+ });
41
45
  const columns = useMemo(
42
46
  () => createAllColumns(
43
47
  isRowDragDisabled ?? false,
@@ -77,19 +81,23 @@ function NetworkModificationsTable({
77
81
  const table = useReactTable({
78
82
  data: composedModifications,
79
83
  columns,
80
- state: { expanded },
84
+ state: { expanded, rowSelection },
81
85
  getCoreRowModel: getCoreRowModel(),
82
86
  getExpandedRowModel: getExpandedRowModel(),
83
87
  getSubRows: (row) => row.subModifications,
84
88
  getRowId: (row) => row.uuid,
85
89
  getRowCanExpand: (row) => isCompositeModification(row.original),
86
90
  enableRowSelection: true,
87
- enableSubRowSelection: false,
91
+ enableSubRowSelection: true,
88
92
  enableExpanding: true,
89
93
  onExpandedChange: handleExpandRow,
90
- meta: { lastClickedIndex, onRowSelected }
94
+ onRowSelectionChange,
95
+ meta: { lastClickedRowId, onRowSelected }
91
96
  });
92
- const { rows } = table.getRowModel();
97
+ const { rows, flatRows } = table.getRowModel();
98
+ useEffect(() => {
99
+ emitSelection(flatRows);
100
+ }, [rowSelection, flatRows, emitSelection]);
93
101
  const virtualizer = useVirtualizer({
94
102
  count: rows.length,
95
103
  getScrollElement: () => containerRef.current,
@@ -109,8 +117,8 @@ function NetworkModificationsTable({
109
117
  useEffect(() => {
110
118
  table.resetRowSelection();
111
119
  table.resetExpanded();
112
- lastClickedIndex.current = null;
113
- }, [table]);
120
+ lastClickedRowId.current = null;
121
+ }, [lastClickedRowId, table, currentNodeId]);
114
122
  useEffect(() => {
115
123
  if (highlightedModificationUuid && containerRef.current) {
116
124
  const rowIndex = rows.findIndex((row) => row.original.uuid === highlightedModificationUuid);
@@ -1,54 +1,43 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
- import { useCallback, useMemo } from "react";
2
+ import { useCallback } from "react";
3
3
  import { Checkbox } from "@mui/material";
4
4
  import { networkModificationTableStyles } from "../network-modification-table-styles.js";
5
+ function toggleRange(rows, from, to, targetSelected) {
6
+ const [start, end] = from <= to ? [from, to] : [to, from];
7
+ rows.slice(start, end + 1).forEach((r) => {
8
+ if (r.getCanSelect()) {
9
+ r.toggleSelected(targetSelected);
10
+ }
11
+ });
12
+ }
5
13
  function SelectCell({ row, table }) {
6
14
  const { meta } = table.options;
15
+ const isSelected = row.getIsSelected();
16
+ const isIndeterminate = !isSelected && row.getIsSomeSelected();
7
17
  const handleChange = useCallback(
8
18
  (event) => {
9
19
  const rows = table.getRowModel().flatRows;
10
20
  const currentIndex = rows.indexOf(row);
11
- const nextSelection = { ...table.getState().rowSelection };
12
- if (event.shiftKey && meta?.lastClickedIndex.current !== null && meta?.lastClickedIndex.current !== void 0) {
13
- const lastIndex = meta.lastClickedIndex.current;
14
- const [from, to] = lastIndex < currentIndex ? [lastIndex, currentIndex] : [currentIndex, lastIndex];
15
- const isRowSelected = row.getIsSelected();
16
- rows.slice(from, to + 1).forEach((r) => {
17
- if (r.getCanSelect()) {
18
- r.toggleSelected(!isRowSelected);
19
- if (isRowSelected) {
20
- delete nextSelection[r.id];
21
- } else {
22
- nextSelection[r.id] = true;
23
- }
24
- }
25
- });
21
+ const anchorRowId = meta?.lastClickedRowId.current;
22
+ const anchorIndex = anchorRowId == null ? null : rows.findIndex((candidate) => candidate.id === anchorRowId);
23
+ const targetSelected = !isSelected;
24
+ if (event.shiftKey && anchorIndex != null && anchorIndex !== -1) {
25
+ toggleRange(rows, anchorIndex, currentIndex, targetSelected);
26
26
  } else {
27
- row.toggleSelected();
28
- if (row.getIsSelected()) {
29
- delete nextSelection[row.id];
30
- } else {
31
- nextSelection[row.id] = true;
32
- }
27
+ row.toggleSelected(targetSelected);
33
28
  }
34
29
  if (meta) {
35
- meta.lastClickedIndex.current = currentIndex;
36
- const selectedRows = rows.filter((r) => nextSelection[r.id]).map((r) => r.original);
37
- meta.onRowSelected?.(selectedRows);
30
+ meta.lastClickedRowId.current = row.id;
38
31
  }
39
32
  },
40
- [table, row, meta]
41
- );
42
- const hasPartiallySelectedSubRows = useMemo(
43
- () => row.subRows.some((subRow) => subRow.getIsSelected()) && !row.getIsSelected(),
44
- [row]
33
+ [table, row, meta, isSelected]
45
34
  );
46
35
  return /* @__PURE__ */ jsx(
47
36
  Checkbox,
48
37
  {
49
38
  size: "small",
50
- checked: row.getIsSelected(),
51
- indeterminate: hasPartiallySelectedSubRows,
39
+ checked: isSelected,
40
+ indeterminate: isIndeterminate,
52
41
  disabled: !row.getCanSelect(),
53
42
  onClick: handleChange,
54
43
  sx: networkModificationTableStyles.selectCheckBox
@@ -7,7 +7,7 @@ function SelectHeaderCell({ table }) {
7
7
  if (meta) {
8
8
  const nextSelectedRows = table.getIsAllRowsSelected() ? [] : table.getCoreRowModel().rows.map((r) => r.original);
9
9
  meta.onRowSelected?.(nextSelectedRows);
10
- meta.lastClickedIndex.current = null;
10
+ meta.lastClickedRowId.current = null;
11
11
  }
12
12
  table.toggleAllRowsSelected();
13
13
  }, [table]);
@@ -0,0 +1,15 @@
1
+ import { RefObject } from 'react';
2
+ import { Row, RowSelectionState, Updater } from '@tanstack/react-table';
3
+ import { ComposedModificationMetadata } from '../../utils';
4
+ interface UseModificationsSelectionParams {
5
+ modifications: ComposedModificationMetadata[];
6
+ onRowSelected: (selectedRows: ComposedModificationMetadata[]) => void;
7
+ }
8
+ interface UseModificationsSelectionResult {
9
+ rowSelection: RowSelectionState;
10
+ onRowSelectionChange: (updater: Updater<RowSelectionState>) => void;
11
+ lastClickedRowId: RefObject<string | null>;
12
+ emitSelection: (flatRows: Row<ComposedModificationMetadata>[]) => void;
13
+ }
14
+ export declare function useModificationsSelection({ modifications, onRowSelected, }: UseModificationsSelectionParams): UseModificationsSelectionResult;
15
+ export {};
@@ -0,0 +1,80 @@
1
+ import { useState, useRef, useCallback, useEffect } from "react";
2
+ function normalizeCompositeSelection(rawSelection, roots) {
3
+ const next = { ...rawSelection };
4
+ const visit = (node) => {
5
+ const children = node.subModifications;
6
+ if (!children || children.length === 0) {
7
+ return next[node.uuid];
8
+ }
9
+ const everyChildSelected = children.map((child) => visit(child)).every(Boolean);
10
+ if (everyChildSelected) {
11
+ next[node.uuid] = true;
12
+ } else {
13
+ delete next[node.uuid];
14
+ }
15
+ return everyChildSelected;
16
+ };
17
+ roots.forEach((root) => visit(root));
18
+ return next;
19
+ }
20
+ function collectSelection(flatRows) {
21
+ const acc = [];
22
+ let skipBelowDepth = -1;
23
+ flatRows.forEach((row) => {
24
+ if (skipBelowDepth >= 0 && row.depth > skipBelowDepth) {
25
+ return;
26
+ }
27
+ skipBelowDepth = -1;
28
+ if (row.getIsSelected()) {
29
+ acc.push(row.original);
30
+ skipBelowDepth = row.depth;
31
+ }
32
+ });
33
+ return acc;
34
+ }
35
+ function propagateSelectionToLoadedDescendants(selection, roots) {
36
+ let next = selection;
37
+ let mutated = false;
38
+ const visit = (node, ancestorSelected) => {
39
+ const effectiveSelected = ancestorSelected || next[node.uuid];
40
+ if (effectiveSelected && !next[node.uuid]) {
41
+ if (!mutated) {
42
+ next = { ...selection };
43
+ mutated = true;
44
+ }
45
+ next[node.uuid] = true;
46
+ }
47
+ node.subModifications?.forEach((child) => visit(child, effectiveSelected));
48
+ };
49
+ roots.forEach((root) => visit(root, false));
50
+ return mutated ? next : selection;
51
+ }
52
+ function useModificationsSelection({
53
+ modifications,
54
+ onRowSelected
55
+ }) {
56
+ const [rowSelection, setRowSelection] = useState({});
57
+ const lastClickedRowId = useRef(null);
58
+ const onRowSelectionChange = useCallback(
59
+ (updater) => {
60
+ setRowSelection((prev) => {
61
+ const raw = typeof updater === "function" ? updater(prev) : updater;
62
+ return normalizeCompositeSelection(raw, modifications);
63
+ });
64
+ },
65
+ [modifications]
66
+ );
67
+ const emitSelection = useCallback(
68
+ (flatRows) => {
69
+ onRowSelected(collectSelection(flatRows));
70
+ },
71
+ [onRowSelected]
72
+ );
73
+ useEffect(() => {
74
+ setRowSelection((prev) => propagateSelectionToLoadedDescendants(prev, modifications));
75
+ }, [modifications]);
76
+ return { rowSelection, onRowSelectionChange, lastClickedRowId, emitSelection };
77
+ }
78
+ export {
79
+ useModificationsSelection
80
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gridsuite/commons-ui",
3
- "version": "0.198.0",
3
+ "version": "0.200.0",
4
4
  "description": "common react components for gridsuite applications",
5
5
  "author": "gridsuite team",
6
6
  "homepage": "https://github.com/gridsuite",