@navikt/ds-react 8.10.4 → 8.10.5

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 (128) hide show
  1. package/cjs/data/drag-and-drop/drag-handler/DragAndDropDragHandler.js +11 -12
  2. package/cjs/data/drag-and-drop/drag-handler/DragAndDropDragHandler.js.map +1 -1
  3. package/cjs/data/drag-and-drop/root/DragAndDrop.context.d.ts +4 -2
  4. package/cjs/data/drag-and-drop/root/DragAndDrop.context.js.map +1 -1
  5. package/cjs/data/drag-and-drop/root/DragAndDropRoot.d.ts +5 -5
  6. package/cjs/data/drag-and-drop/root/DragAndDropRoot.js +49 -28
  7. package/cjs/data/drag-and-drop/root/DragAndDropRoot.js.map +1 -1
  8. package/cjs/data/drag-and-drop/types.d.ts +0 -4
  9. package/cjs/data/{drag-and-drop-old/drag-handler/DataDragAndDropDragHandler.d.ts → drag-and-drop-legacy/drag-handler/DragAndDropDragHandlerLegacy.d.ts} +3 -3
  10. package/cjs/data/{drag-and-drop-old/drag-handler/DataDragAndDropDragHandler.js → drag-and-drop-legacy/drag-handler/DragAndDropDragHandlerLegacy.js} +5 -5
  11. package/cjs/data/drag-and-drop-legacy/drag-handler/DragAndDropDragHandlerLegacy.js.map +1 -0
  12. package/cjs/data/drag-and-drop-legacy/item/DragAndDropItemLegacy.d.ts +27 -0
  13. package/cjs/data/{drag-and-drop-old/item/DataDragAndDropItem.js → drag-and-drop-legacy/item/DragAndDropItemLegacy.js} +12 -12
  14. package/cjs/data/drag-and-drop-legacy/item/DragAndDropItemLegacy.js.map +1 -0
  15. package/cjs/data/drag-and-drop-legacy/root/DragAndDropLegacy.context.d.ts +5 -0
  16. package/cjs/data/drag-and-drop-legacy/root/DragAndDropLegacy.context.js +6 -0
  17. package/cjs/data/drag-and-drop-legacy/root/DragAndDropLegacy.context.js.map +1 -0
  18. package/cjs/data/drag-and-drop-legacy/root/DragAndDropLegacyRoot.d.ts +24 -0
  19. package/cjs/data/{drag-and-drop-old/root/DataDragAndDropRoot.js → drag-and-drop-legacy/root/DragAndDropLegacyRoot.js} +10 -10
  20. package/cjs/data/drag-and-drop-legacy/root/DragAndDropLegacyRoot.js.map +1 -0
  21. package/cjs/data/stories/Data.test-data.js +0 -1
  22. package/cjs/data/stories/Data.test-data.js.map +1 -1
  23. package/cjs/data/table/column-header/DataTableColumnHeader.js +2 -2
  24. package/cjs/data/table/column-header/DataTableColumnHeader.js.map +1 -1
  25. package/cjs/data/table/details-panel-row/DataTableDetailsPanelRow.js +1 -1
  26. package/cjs/data/table/details-panel-row/DataTableDetailsPanelRow.js.map +1 -1
  27. package/cjs/data/table/helpers/collectTableRowEntries.d.ts +7 -5
  28. package/cjs/data/table/helpers/collectTableRowEntries.js +18 -10
  29. package/cjs/data/table/helpers/collectTableRowEntries.js.map +1 -1
  30. package/cjs/data/table/helpers/table-focus.d.ts +0 -3
  31. package/cjs/data/table/helpers/table-focus.js +38 -8
  32. package/cjs/data/table/helpers/table-focus.js.map +1 -1
  33. package/cjs/data/table/hooks/useGridCache.js +2 -2
  34. package/cjs/data/table/hooks/useGridCache.js.map +1 -1
  35. package/cjs/data/table/hooks/useTableDetailsPanel.js +1 -1
  36. package/cjs/data/table/hooks/useTableDetailsPanel.js.map +1 -1
  37. package/cjs/data/table/hooks/useTableItems.d.ts +4 -4
  38. package/cjs/data/table/hooks/useTableItems.js +8 -8
  39. package/cjs/data/table/hooks/useTableItems.js.map +1 -1
  40. package/cjs/data/table/hooks/useTableKeyboardNav.js +5 -1
  41. package/cjs/data/table/hooks/useTableKeyboardNav.js.map +1 -1
  42. package/cjs/data/table/root/DataTable.types.d.ts +0 -3
  43. package/cjs/data/table/root/DataTableRoot.d.ts +1 -1
  44. package/cjs/data/table/root/DataTableRoot.js +7 -11
  45. package/cjs/data/table/root/DataTableRoot.js.map +1 -1
  46. package/esm/data/drag-and-drop/drag-handler/DragAndDropDragHandler.js +11 -12
  47. package/esm/data/drag-and-drop/drag-handler/DragAndDropDragHandler.js.map +1 -1
  48. package/esm/data/drag-and-drop/root/DragAndDrop.context.d.ts +4 -2
  49. package/esm/data/drag-and-drop/root/DragAndDrop.context.js.map +1 -1
  50. package/esm/data/drag-and-drop/root/DragAndDropRoot.d.ts +5 -5
  51. package/esm/data/drag-and-drop/root/DragAndDropRoot.js +49 -28
  52. package/esm/data/drag-and-drop/root/DragAndDropRoot.js.map +1 -1
  53. package/esm/data/drag-and-drop/types.d.ts +0 -4
  54. package/esm/data/{drag-and-drop-old/drag-handler/DataDragAndDropDragHandler.d.ts → drag-and-drop-legacy/drag-handler/DragAndDropDragHandlerLegacy.d.ts} +3 -3
  55. package/esm/data/{drag-and-drop-old/drag-handler/DataDragAndDropDragHandler.js → drag-and-drop-legacy/drag-handler/DragAndDropDragHandlerLegacy.js} +4 -4
  56. package/esm/data/drag-and-drop-legacy/drag-handler/DragAndDropDragHandlerLegacy.js.map +1 -0
  57. package/esm/data/drag-and-drop-legacy/item/DragAndDropItemLegacy.d.ts +27 -0
  58. package/esm/data/{drag-and-drop-old/item/DataDragAndDropItem.js → drag-and-drop-legacy/item/DragAndDropItemLegacy.js} +11 -11
  59. package/esm/data/drag-and-drop-legacy/item/DragAndDropItemLegacy.js.map +1 -0
  60. package/esm/data/drag-and-drop-legacy/root/DragAndDropLegacy.context.d.ts +5 -0
  61. package/esm/data/drag-and-drop-legacy/root/DragAndDropLegacy.context.js +3 -0
  62. package/esm/data/drag-and-drop-legacy/root/DragAndDropLegacy.context.js.map +1 -0
  63. package/esm/data/drag-and-drop-legacy/root/DragAndDropLegacyRoot.d.ts +24 -0
  64. package/esm/data/{drag-and-drop-old/root/DataDragAndDropRoot.js → drag-and-drop-legacy/root/DragAndDropLegacyRoot.js} +8 -8
  65. package/esm/data/drag-and-drop-legacy/root/DragAndDropLegacyRoot.js.map +1 -0
  66. package/esm/data/stories/Data.test-data.js +0 -1
  67. package/esm/data/stories/Data.test-data.js.map +1 -1
  68. package/esm/data/table/column-header/DataTableColumnHeader.js +2 -2
  69. package/esm/data/table/column-header/DataTableColumnHeader.js.map +1 -1
  70. package/esm/data/table/details-panel-row/DataTableDetailsPanelRow.js +1 -1
  71. package/esm/data/table/details-panel-row/DataTableDetailsPanelRow.js.map +1 -1
  72. package/esm/data/table/helpers/collectTableRowEntries.d.ts +7 -5
  73. package/esm/data/table/helpers/collectTableRowEntries.js +18 -10
  74. package/esm/data/table/helpers/collectTableRowEntries.js.map +1 -1
  75. package/esm/data/table/helpers/table-focus.d.ts +0 -3
  76. package/esm/data/table/helpers/table-focus.js +38 -8
  77. package/esm/data/table/helpers/table-focus.js.map +1 -1
  78. package/esm/data/table/hooks/useGridCache.js +2 -2
  79. package/esm/data/table/hooks/useGridCache.js.map +1 -1
  80. package/esm/data/table/hooks/useTableDetailsPanel.js +1 -1
  81. package/esm/data/table/hooks/useTableDetailsPanel.js.map +1 -1
  82. package/esm/data/table/hooks/useTableItems.d.ts +4 -4
  83. package/esm/data/table/hooks/useTableItems.js +8 -8
  84. package/esm/data/table/hooks/useTableItems.js.map +1 -1
  85. package/esm/data/table/hooks/useTableKeyboardNav.js +6 -2
  86. package/esm/data/table/hooks/useTableKeyboardNav.js.map +1 -1
  87. package/esm/data/table/root/DataTable.types.d.ts +0 -3
  88. package/esm/data/table/root/DataTableRoot.d.ts +1 -1
  89. package/esm/data/table/root/DataTableRoot.js +7 -11
  90. package/esm/data/table/root/DataTableRoot.js.map +1 -1
  91. package/package.json +8 -7
  92. package/src/data/drag-and-drop/drag-handler/DragAndDropDragHandler.tsx +11 -16
  93. package/src/data/drag-and-drop/root/DragAndDrop.context.tsx +4 -2
  94. package/src/data/drag-and-drop/root/DragAndDropRoot.tsx +85 -40
  95. package/src/data/drag-and-drop/types.ts +0 -5
  96. package/src/data/{drag-and-drop-old/drag-handler/DataDragAndDropDragHandler.tsx → drag-and-drop-legacy/drag-handler/DragAndDropDragHandlerLegacy.tsx} +5 -5
  97. package/src/data/{drag-and-drop-old/item/DataDragAndDropItem.tsx → drag-and-drop-legacy/item/DragAndDropItemLegacy.tsx} +13 -13
  98. package/src/data/{drag-and-drop-old/root/DataDragAndDrop.context.tsx → drag-and-drop-legacy/root/DragAndDropLegacy.context.tsx} +3 -3
  99. package/src/data/{drag-and-drop-old/root/DataDragAndDropRoot.tsx → drag-and-drop-legacy/root/DragAndDropLegacyRoot.tsx} +19 -21
  100. package/src/data/stories/Data.test-data.tsx +0 -1
  101. package/src/data/table/column-header/DataTableColumnHeader.tsx +2 -0
  102. package/src/data/table/details-panel-row/DataTableDetailsPanelRow.tsx +5 -1
  103. package/src/data/table/helpers/collectTableRowEntries.ts +31 -17
  104. package/src/data/table/helpers/table-focus.ts +63 -9
  105. package/src/data/table/hooks/__tests__/useTableItems.test.ts +46 -7
  106. package/src/data/table/hooks/useGridCache.ts +3 -2
  107. package/src/data/table/hooks/useTableDetailsPanel.tsx +5 -2
  108. package/src/data/table/hooks/useTableItems.ts +20 -19
  109. package/src/data/table/hooks/useTableKeyboardNav.ts +6 -2
  110. package/src/data/table/root/DataTable.types.ts +0 -3
  111. package/src/data/table/root/DataTableRoot.tsx +34 -34
  112. package/src/data/table/root/agent-feature-gap.md +96 -0
  113. package/cjs/data/drag-and-drop-old/drag-handler/DataDragAndDropDragHandler.js.map +0 -1
  114. package/cjs/data/drag-and-drop-old/item/DataDragAndDropItem.d.ts +0 -27
  115. package/cjs/data/drag-and-drop-old/item/DataDragAndDropItem.js.map +0 -1
  116. package/cjs/data/drag-and-drop-old/root/DataDragAndDrop.context.d.ts +0 -5
  117. package/cjs/data/drag-and-drop-old/root/DataDragAndDrop.context.js +0 -6
  118. package/cjs/data/drag-and-drop-old/root/DataDragAndDrop.context.js.map +0 -1
  119. package/cjs/data/drag-and-drop-old/root/DataDragAndDropRoot.d.ts +0 -24
  120. package/cjs/data/drag-and-drop-old/root/DataDragAndDropRoot.js.map +0 -1
  121. package/esm/data/drag-and-drop-old/drag-handler/DataDragAndDropDragHandler.js.map +0 -1
  122. package/esm/data/drag-and-drop-old/item/DataDragAndDropItem.d.ts +0 -27
  123. package/esm/data/drag-and-drop-old/item/DataDragAndDropItem.js.map +0 -1
  124. package/esm/data/drag-and-drop-old/root/DataDragAndDrop.context.d.ts +0 -5
  125. package/esm/data/drag-and-drop-old/root/DataDragAndDrop.context.js +0 -3
  126. package/esm/data/drag-and-drop-old/root/DataDragAndDrop.context.js.map +0 -1
  127. package/esm/data/drag-and-drop-old/root/DataDragAndDropRoot.d.ts +0 -24
  128. package/esm/data/drag-and-drop-old/root/DataDragAndDropRoot.js.map +0 -1
@@ -3,10 +3,10 @@ import React, { useRef } from "react";
3
3
  import { HStack } from "../../../primitives/stack";
4
4
  import { cl } from "../../../utils/helpers";
5
5
  import { useMergeRefs } from "../../../utils/hooks";
6
- import { DataDragAndDropDragHandler } from "../drag-handler/DataDragAndDropDragHandler";
7
- import { DataDragAndDropContext } from "../root/DataDragAndDrop.context";
6
+ import { DragAndDropDragHandlerLegacy } from "../drag-handler/DragAndDropDragHandlerLegacy";
7
+ import { DragAndDropLegacyContext } from "../root/DragAndDropLegacy.context";
8
8
 
9
- interface DataDragAndDropItemProps extends React.HTMLAttributes<HTMLDivElement> {
9
+ interface DragAndDropItemLegacyProps extends React.HTMLAttributes<HTMLDivElement> {
10
10
  children: React.ReactNode;
11
11
  /**
12
12
  * Unique id
@@ -21,17 +21,17 @@ interface DataDragAndDropItemProps extends React.HTMLAttributes<HTMLDivElement>
21
21
  /**
22
22
  * TODO
23
23
  *
24
- * @see 🏷️ {@link DataDragAndDropItemProps}
24
+ * @see 🏷️ {@link DragAndDropItemLegacyProps}
25
25
  * @example
26
26
  * ```tsx
27
- * <DragAndDrop.Item numOfSelectedRows={selectedRows.length} onClear={handleClear}>
27
+ * <DragAndDropLegacy.Item numOfSelectedRows={selectedRows.length} onClear={handleClear}>
28
28
  * TODO
29
- * </DragAndDrop.Item>
29
+ * </DragAndDropLegacy.Item>
30
30
  * ```
31
31
  */
32
- const DataDragAndDropItem = React.forwardRef<
32
+ const DragAndDropItemLegacy = React.forwardRef<
33
33
  HTMLDivElement,
34
- DataDragAndDropItemProps
34
+ DragAndDropItemLegacyProps
35
35
  >(({ children, id, index, className, ...rest }, forwardedRef) => {
36
36
  const handleRef = useRef<HTMLDivElement>(null);
37
37
  const { ref, isDragging, isDropTarget } = useSortable({
@@ -40,7 +40,7 @@ const DataDragAndDropItem = React.forwardRef<
40
40
  handle: handleRef,
41
41
  });
42
42
  const mergedRef = useMergeRefs(ref, forwardedRef);
43
- const context = React.useContext(DataDragAndDropContext);
43
+ const context = React.useContext(DragAndDropLegacyContext);
44
44
  const mouseDragging = isDragging && context?.inputMethod === "mouse";
45
45
  const mouseDropTarget = isDropTarget && context?.inputMethod === "mouse";
46
46
  const keyboardDragging = isDragging && context?.inputMethod === "keyboard";
@@ -58,7 +58,7 @@ const DataDragAndDropItem = React.forwardRef<
58
58
  data-drop-target={mouseDropTarget}
59
59
  tabIndex={-1}
60
60
  >
61
- <DataDragAndDropDragHandler
61
+ <DragAndDropDragHandlerLegacy
62
62
  handleRef={handleRef}
63
63
  keyboardDragging={keyboardDragging}
64
64
  alt
@@ -69,6 +69,6 @@ const DataDragAndDropItem = React.forwardRef<
69
69
  );
70
70
  });
71
71
 
72
- export default DataDragAndDropItem;
73
- export { DataDragAndDropItem };
74
- export type { DataDragAndDropItemProps };
72
+ export default DragAndDropItemLegacy;
73
+ export { DragAndDropItemLegacy };
74
+ export type { DragAndDropItemLegacyProps };
@@ -1,11 +1,11 @@
1
1
  import { createContext } from "react";
2
2
 
3
- interface DataDragAndDropContextType {
3
+ interface DragAndDropContextLegacyType {
4
4
  inputMethod: "mouse" | "keyboard" | null;
5
5
  // setInputMethod: (method: "mouse" | "keyboard" | null) => void;
6
6
  // setItems: React.Dispatch<React.SetStateAction<any[]>>;
7
7
  }
8
8
 
9
- export const DataDragAndDropContext = createContext<
10
- DataDragAndDropContextType | undefined
9
+ export const DragAndDropLegacyContext = createContext<
10
+ DragAndDropContextLegacyType | undefined
11
11
  >(undefined);
@@ -2,34 +2,32 @@ import { DragDropProvider, DragOverlay } from "@dnd-kit/react";
2
2
  import { isSortable } from "@dnd-kit/react/sortable";
3
3
  import React, { forwardRef, isValidElement } from "react";
4
4
  import { VStack } from "../../../primitives/stack";
5
- import DataDragAndDropItem, {
6
- DataDragAndDropItemProps,
7
- } from "../item/DataDragAndDropItem";
8
- import { DataDragAndDropContext } from "./DataDragAndDrop.context";
5
+ import DragAndDropItemLegacy from "../item/DragAndDropItemLegacy";
6
+ import { DragAndDropLegacyContext } from "./DragAndDropLegacy.context";
9
7
 
10
- interface DataDragAndDropProps extends React.HTMLAttributes<HTMLDivElement> {
8
+ interface DragAndDropLegacyProps extends React.HTMLAttributes<HTMLDivElement> {
11
9
  children: any[];
12
10
  setItems: React.Dispatch<React.SetStateAction<any[]>>;
13
11
  }
14
12
 
15
- interface DataDragAndDropRootComponent extends React.ForwardRefExoticComponent<
16
- DataDragAndDropProps & React.RefAttributes<HTMLDivElement>
13
+ interface DragAndDropLegacyRootComponent extends React.ForwardRefExoticComponent<
14
+ DragAndDropLegacyProps & React.RefAttributes<HTMLDivElement>
17
15
  > {
18
16
  /**
19
- * @see 🏷️ {@link DataDragAndDropItemProps}
17
+ * @see 🏷️ {@link DragAndDropItemLegacyProps}
20
18
  * * @example
21
19
  * ```jsx
22
- * <DragAndDrop>
23
- * <DragAndDrop.Item id="1" index={0}>
20
+ * <DragAndDropLegacy>
21
+ * <DragAndDropLegacy.Item id="1" index={0}>
24
22
  * ...
25
- * </DragAndDrop.Item>
26
- * </DragAndDrop>
23
+ * </DragAndDropLegacy.Item>
24
+ * </DragAndDropLegacy>
27
25
  * ```
28
26
  */
29
- Item: typeof DataDragAndDropItem;
27
+ Item: typeof DragAndDropItemLegacy;
30
28
  }
31
29
 
32
- const DataDragAndDrop = forwardRef<HTMLDivElement, DataDragAndDropProps>(
30
+ const DragAndDropLegacy = forwardRef<HTMLDivElement, DragAndDropLegacyProps>(
33
31
  ({ setItems, children, ...rest }, forwardedRef) => {
34
32
  const [inputMethod, setInputMethod] = React.useState<
35
33
  "mouse" | "keyboard" | null
@@ -45,7 +43,7 @@ const DataDragAndDrop = forwardRef<HTMLDivElement, DataDragAndDropProps>(
45
43
  };
46
44
 
47
45
  return (
48
- <DataDragAndDropContext.Provider value={{ inputMethod }}>
46
+ <DragAndDropLegacyContext.Provider value={{ inputMethod }}>
49
47
  <DragDropProvider
50
48
  // TODO Look into overriding default keybinds, might eliminate context need
51
49
  onBeforeDragStart={(event) =>
@@ -84,13 +82,13 @@ const DataDragAndDrop = forwardRef<HTMLDivElement, DataDragAndDropProps>(
84
82
  }}
85
83
  </DragOverlay>
86
84
  </DragDropProvider>
87
- </DataDragAndDropContext.Provider>
85
+ </DragAndDropLegacyContext.Provider>
88
86
  );
89
87
  },
90
- ) as DataDragAndDropRootComponent;
88
+ ) as DragAndDropLegacyRootComponent;
91
89
 
92
- DataDragAndDrop.Item = DataDragAndDropItem;
90
+ DragAndDropLegacy.Item = DragAndDropItemLegacy;
93
91
 
94
- export { DataDragAndDrop, DataDragAndDropItem };
95
- export default DataDragAndDrop;
96
- export type { DataDragAndDropItemProps, DataDragAndDropProps };
92
+ export { DragAndDropLegacy, DragAndDropItemLegacy };
93
+ export default DragAndDropLegacy;
94
+ export type { DragAndDropLegacyProps };
@@ -26,7 +26,6 @@ const columnDef_TEST_DATA: ColumnDefinitions<SWData, Details> = [
26
26
  id: "id",
27
27
  label: "Id",
28
28
  cell: (row) => row.id,
29
- align: "right",
30
29
  autoWidth: true,
31
30
  details: {
32
31
  visible: true,
@@ -111,6 +111,7 @@ const DataTableColumnHeader = forwardRef<
111
111
  >
112
112
  {sortable ? (
113
113
  <button
114
+ type="button"
114
115
  className="aksel-data-table__th-sort-button"
115
116
  onClick={onSortClick}
116
117
  >
@@ -140,6 +141,7 @@ const DataTableColumnHeader = forwardRef<
140
141
  {resizeResult.enabled && !UNSAFE_isSelection && (
141
142
  <button
142
143
  {...resizeResult.resizeHandlerProps}
144
+ type="button"
143
145
  className="aksel-data-table__th-resize-handle"
144
146
  aria-label={
145
147
  resizeResult.isResizingWithKeyboard
@@ -43,7 +43,11 @@ function DataTableDetailsPanelRow<T>({
43
43
 
44
44
  return (
45
45
  <tr className="aksel-data-table__details-panel-row">
46
- <td id={expansionId} colSpan={fullWidthColSpan}>
46
+ <td
47
+ id={expansionId}
48
+ colSpan={fullWidthColSpan}
49
+ className="aksel-data-table__details-panel-row-cell"
50
+ >
47
51
  <div style={style}>{content}</div>
48
52
  </td>
49
53
  </tr>
@@ -2,20 +2,22 @@ type TableRowEntryId = string | number;
2
2
 
3
3
  type CollectTableRowEntriesArgs<T> = {
4
4
  items: T[];
5
- getRowId: (rowData: T, index: number) => TableRowEntryId;
5
+ getRowId?: (rowData: T, index: number) => TableRowEntryId;
6
6
  getRows?: (rowData: T) => T[];
7
7
  isRowExpandable?: (rowData: T) => boolean;
8
8
  };
9
9
 
10
10
  interface ItemDetail<T> {
11
- id: string | number;
11
+ id: TableRowEntryId;
12
+ rowData: T;
12
13
  level: number;
13
- parent: null | T;
14
- children: readonly T[];
14
+ parentId: TableRowEntryId | null;
15
+ children: readonly TableRowEntryId[];
15
16
  }
16
17
 
17
18
  type CollectTableRowEntriesReturn<T> = {
18
- itemDetails: Map<T, ItemDetail<T>>;
19
+ itemDetails: Map<TableRowEntryId, ItemDetail<T>>;
20
+ rootRowIds: TableRowEntryId[];
19
21
  /**
20
22
  * Direct child ids for each row, used to traverse nested selection groups
21
23
  * without storing every descendant list on each ancestor.
@@ -29,50 +31,62 @@ function collectTableRowEntries<T>({
29
31
  getRows,
30
32
  isRowExpandable,
31
33
  }: CollectTableRowEntriesArgs<T>): CollectTableRowEntriesReturn<T> {
32
- const itemDetailsMap = new Map<T, ItemDetail<T>>();
34
+ const itemDetailsMap = new Map<TableRowEntryId, ItemDetail<T>>();
33
35
  const childRowIdsById = new Map<TableRowEntryId, TableRowEntryId[]>();
36
+ const rootRowIds: TableRowEntryId[] = [];
34
37
 
35
38
  const traverseRow = (
36
39
  rowData: T,
37
40
  rowIndex: number,
38
41
  level: number,
39
- parent: T | null,
42
+ parentRowId: TableRowEntryId | null,
40
43
  ): TableRowEntryId => {
41
- const rowId = getRowId(rowData, rowIndex);
44
+ const rowId = getRowId
45
+ ? getRowId(rowData, rowIndex)
46
+ : getFallbackTableRowId(rowIndex, parentRowId);
42
47
 
43
48
  const children =
44
49
  ((isRowExpandable?.(rowData) ?? true) ? getRows?.(rowData) : []) ?? [];
45
50
 
46
- itemDetailsMap.set(rowData, {
47
- id: rowId,
48
- level,
49
- parent,
50
- children,
51
- });
52
-
53
51
  const childRowIds: TableRowEntryId[] = [];
54
52
 
55
53
  for (let childIndex = 0; childIndex < children.length; childIndex++) {
56
54
  const childRow = children[childIndex];
57
- const childRowId = traverseRow(childRow, childIndex, level + 1, rowData);
55
+ const childRowId = traverseRow(childRow, childIndex, level + 1, rowId);
58
56
  childRowIds.push(childRowId);
59
57
  }
60
58
 
59
+ itemDetailsMap.set(rowId, {
60
+ id: rowId,
61
+ rowData,
62
+ level,
63
+ parentId: parentRowId,
64
+ children: childRowIds,
65
+ });
66
+
61
67
  childRowIdsById.set(rowId, childRowIds);
62
68
 
63
69
  return rowId;
64
70
  };
65
71
 
66
72
  for (let rowIndex = 0; rowIndex < items.length; rowIndex++) {
67
- traverseRow(items[rowIndex], rowIndex, 0, null);
73
+ rootRowIds.push(traverseRow(items[rowIndex], rowIndex, 0, null));
68
74
  }
69
75
 
70
76
  return {
71
77
  itemDetails: itemDetailsMap,
78
+ rootRowIds,
72
79
  childRowIdsById,
73
80
  };
74
81
  }
75
82
 
83
+ function getFallbackTableRowId(
84
+ rowIndex: number,
85
+ parentRowId: TableRowEntryId | null,
86
+ ): string {
87
+ return parentRowId == null ? String(rowIndex) : `${parentRowId}.${rowIndex}`;
88
+ }
89
+
76
90
  export { collectTableRowEntries };
77
91
  export type {
78
92
  CollectTableRowEntriesArgs,
@@ -121,16 +121,70 @@ function prepareCellFocus(cell: Element): HTMLElement | null {
121
121
  /**
122
122
  * Applies focus and scroll to an element.
123
123
  */
124
+ function getStickyOffsets(element: HTMLElement): {
125
+ stickyOffsetStart: number;
126
+ stickyOffsetEnd: number;
127
+ stickyHeaderHeight: number;
128
+ } {
129
+ const table = element.closest(".aksel-data-table");
130
+
131
+ if (!table) {
132
+ return {
133
+ stickyOffsetStart: 0,
134
+ stickyOffsetEnd: 0,
135
+ stickyHeaderHeight: 0,
136
+ };
137
+ }
138
+
139
+ const stickyHeader = table.querySelector<HTMLElement>(
140
+ `.aksel-data-table__tr[data-sticky="true"]`,
141
+ );
142
+
143
+ const stickyNodesStart = table.querySelectorAll<HTMLElement>(
144
+ `.aksel-data-table__column-header[data-sticky="start"]`,
145
+ );
146
+
147
+ const stickyNodesEnd = table.querySelectorAll<HTMLElement>(
148
+ `.aksel-data-table__column-header[data-sticky="end"]`,
149
+ );
150
+
151
+ return {
152
+ stickyOffsetStart: Array.from(stickyNodesStart).reduce(
153
+ (offset, node) => offset + node.getBoundingClientRect().width,
154
+ 0,
155
+ ),
156
+ stickyOffsetEnd: Array.from(stickyNodesEnd).reduce(
157
+ (offset, node) => offset + node.getBoundingClientRect().width,
158
+ 0,
159
+ ),
160
+ stickyHeaderHeight: stickyHeader?.getBoundingClientRect().height ?? 0,
161
+ };
162
+ }
163
+
124
164
  function applyFocusAndScroll(element: HTMLElement): void {
125
- element.focus({
126
- preventScroll: true,
127
- });
128
-
129
- element.scrollIntoView({
130
- behavior: "smooth",
131
- block: "nearest",
132
- inline: "nearest",
133
- });
165
+ const { stickyOffsetStart, stickyOffsetEnd, stickyHeaderHeight } =
166
+ getStickyOffsets(element);
167
+
168
+ const originalScrollMarginInline = element.style.scrollMarginInline;
169
+ const originalScrollMarginBlockStart = element.style.scrollMarginBlockStart;
170
+
171
+ element.style.scrollMarginInline = `${stickyOffsetStart}px ${stickyOffsetEnd}px`;
172
+ element.style.scrollMarginBlockStart = `${stickyHeaderHeight}px`;
173
+
174
+ try {
175
+ element.focus({
176
+ preventScroll: true,
177
+ });
178
+
179
+ element.scrollIntoView({
180
+ behavior: "auto",
181
+ block: "nearest",
182
+ inline: "nearest",
183
+ });
184
+ } finally {
185
+ element.style.scrollMarginBlockStart = originalScrollMarginBlockStart;
186
+ element.style.scrollMarginInline = originalScrollMarginInline;
187
+ }
134
188
  }
135
189
 
136
190
  /**
@@ -45,6 +45,12 @@ const fallbackRows: FallbackTestRow[] = [
45
45
  },
46
46
  ];
47
47
 
48
+ const duplicatedRowObject: TestRow = {
49
+ id: "shared",
50
+ name: "Shared",
51
+ subRows: [{ id: "shared-child", name: "Child" }],
52
+ };
53
+
48
54
  const getSubRows = (row: TestRow) => row.subRows ?? [];
49
55
 
50
56
  const getVisibleIds = (rows: TestRow[]) => rows.map((row) => row.id);
@@ -59,16 +65,18 @@ describe("useTableItems", () => {
59
65
  );
60
66
 
61
67
  expect(getVisibleIds(result.current.items)).toEqual(["a", "b"]);
62
- expect(result.current.itemDetails.get(plainRows[0])).toMatchObject({
68
+ expect(result.current.itemDetails.get("a")).toMatchObject({
63
69
  id: "a",
70
+ rowData: plainRows[0],
64
71
  level: 0,
65
- parent: null,
72
+ parentId: null,
66
73
  children: [],
67
74
  });
68
- expect(result.current.itemDetails.get(plainRows[1])).toMatchObject({
75
+ expect(result.current.itemDetails.get("b")).toMatchObject({
69
76
  id: "b",
77
+ rowData: plainRows[1],
70
78
  level: 0,
71
- parent: null,
79
+ parentId: null,
72
80
  children: [],
73
81
  });
74
82
  });
@@ -102,14 +110,13 @@ describe("useTableItems", () => {
102
110
  expect(result.current.childRowIdsById.get("b")).toEqual(["b1"]);
103
111
  });
104
112
 
105
- test("uses the same fallback root id to reveal child rows when getRowId is omitted", () => {
113
+ test("uses unique fallback ids to reveal child rows when getRowId is omitted", () => {
106
114
  const { result } = renderHook(() =>
107
115
  useTableItems({
108
116
  items: fallbackRows,
109
- getRowId: (_, index) => index,
110
117
  subRows: {
111
118
  getRows: (row: any) => row.subRows ?? [],
112
- defaultExpandedRowIds: [0],
119
+ defaultExpandedRowIds: ["0"],
113
120
  },
114
121
  }),
115
122
  );
@@ -118,6 +125,7 @@ describe("useTableItems", () => {
118
125
  "Parent",
119
126
  "Child",
120
127
  ]);
128
+ expect(result.current.childRowIdsById.get("0")).toEqual(["0.0"]);
121
129
  });
122
130
 
123
131
  test("updates visible rows in depth-first order for controlled expanded ids", () => {
@@ -149,4 +157,35 @@ describe("useTableItems", () => {
149
157
  "b1",
150
158
  ]);
151
159
  });
160
+
161
+ test("tracks duplicated row objects by row id instead of object identity", () => {
162
+ const { result } = renderHook(() =>
163
+ useTableItems({
164
+ items: [duplicatedRowObject, duplicatedRowObject],
165
+ subRows: {
166
+ getRows: getSubRows,
167
+ defaultExpandedRowIds: ["0"],
168
+ },
169
+ }),
170
+ );
171
+
172
+ expect(result.current.visibleRowIds).toEqual(["0", "0.0", "1"]);
173
+ expect(getVisibleIds(result.current.items)).toEqual([
174
+ "shared",
175
+ "shared-child",
176
+ "shared",
177
+ ]);
178
+ expect(result.current.itemDetails.get("0")).toMatchObject({
179
+ id: "0",
180
+ rowData: duplicatedRowObject,
181
+ parentId: null,
182
+ children: ["0.0"],
183
+ });
184
+ expect(result.current.itemDetails.get("1")).toMatchObject({
185
+ id: "1",
186
+ rowData: duplicatedRowObject,
187
+ parentId: null,
188
+ children: ["1.0"],
189
+ });
190
+ });
152
191
  });
@@ -14,7 +14,7 @@ function useGridCache(tableRef: HTMLTableElement | null, enabled: boolean) {
14
14
  });
15
15
 
16
16
  const [activeCell, setActiveCell] = useState<Element | null>(null);
17
- const activeCellRef = useValueAsRef(activeCell).current;
17
+ const activeCellRef = useValueAsRef(activeCell);
18
18
  const observerRef = useRef<MutationObserver | null>(null);
19
19
 
20
20
  useEffect(() => {
@@ -24,7 +24,8 @@ function useGridCache(tableRef: HTMLTableElement | null, enabled: boolean) {
24
24
 
25
25
  observerRef.current = new MutationObserver(() => {
26
26
  gridCacheRef.current.dirty = true;
27
- if (activeCellRef && !activeCellRef.isConnected) {
27
+
28
+ if (activeCellRef.current && !activeCellRef.current.isConnected) {
28
29
  setActiveCell(null);
29
30
  }
30
31
  });
@@ -90,7 +90,10 @@ function DataTableDetailsPanelProvider<T>({
90
90
  const tableItemsContext = useTableItemsContext(false);
91
91
 
92
92
  const { itemDetails } = tableItemsContext ?? {
93
- itemDetails: new Map(),
93
+ itemDetails: new Map<
94
+ string | number,
95
+ { rowData: T; id: string | number; level: number }
96
+ >(),
94
97
  };
95
98
 
96
99
  const expandableIds = React.useMemo(() => {
@@ -100,7 +103,7 @@ function DataTableDetailsPanelProvider<T>({
100
103
 
101
104
  const ids = new Set<string | number>();
102
105
 
103
- for (const [rowData, { id, level }] of itemDetails.entries()) {
106
+ for (const { rowData, id, level } of itemDetails.values()) {
104
107
  /* We only allow Master - Details pattern on top level rows */
105
108
  if (level > 0) {
106
109
  continue;
@@ -17,13 +17,13 @@ type SubRowsProps<T> = {
17
17
 
18
18
  type UseTableItemsArgs<T> = {
19
19
  items: T[];
20
- getRowId: (rowData: T, index: number) => TableRowEntryId;
20
+ getRowId?: (rowData: T) => TableRowEntryId;
21
21
  subRows?: SubRowsProps<T>;
22
22
  };
23
23
 
24
24
  type useTableItemsReturn<T> = {
25
25
  items: T[];
26
- itemDetails: Map<T, ItemDetail<T>>;
26
+ itemDetails: Map<TableRowEntryId, ItemDetail<T>>;
27
27
  /** Row ids for the rows currently rendered in the table body. */
28
28
  visibleRowIds: TableRowEntryId[];
29
29
  /** Direct child ids for each row, used to traverse selection groups lazily. */
@@ -57,32 +57,35 @@ function useTableItems<T>(args: UseTableItemsArgs<T>): useTableItemsReturn<T> {
57
57
 
58
58
  const { itemDetails, visibleItems, visibleRowIds, childRowIdsById } =
59
59
  useMemo(() => {
60
- const { itemDetails: rowEntriesMap, childRowIdsById: _childRowIdsById } =
61
- collectTableRowEntries({
62
- items,
63
- getRowId,
64
- getRows,
65
- isRowExpandable,
66
- });
60
+ const {
61
+ itemDetails: rowEntriesMap,
62
+ rootRowIds,
63
+ childRowIdsById: _childRowIdsById,
64
+ } = collectTableRowEntries({
65
+ items,
66
+ getRowId,
67
+ getRows,
68
+ isRowExpandable,
69
+ });
67
70
 
68
71
  const localVisibleItems: T[] = [];
69
72
  const localVisibleRowIds: TableRowEntryId[] = [];
70
73
 
71
- const addVisibleRows = (rowData: T): TableRowEntryId[] => {
72
- const details = rowEntriesMap.get(rowData);
74
+ const addVisibleRows = (rowId: TableRowEntryId): TableRowEntryId[] => {
75
+ const details = rowEntriesMap.get(rowId);
73
76
 
74
77
  if (!details) {
75
78
  return [];
76
79
  }
77
80
 
78
- localVisibleItems.push(rowData);
81
+ localVisibleItems.push(details.rowData);
79
82
  localVisibleRowIds.push(details.id);
80
83
 
81
84
  const visibleDescendantRowIds: TableRowEntryId[] = [];
82
85
 
83
86
  if (expandedIdsSet.has(details.id)) {
84
- for (const childRow of details.children) {
85
- const childVisibleRowIds = addVisibleRows(childRow);
87
+ for (const childRowId of details.children) {
88
+ const childVisibleRowIds = addVisibleRows(childRowId);
86
89
  visibleDescendantRowIds.push(...childVisibleRowIds);
87
90
  }
88
91
  }
@@ -90,8 +93,8 @@ function useTableItems<T>(args: UseTableItemsArgs<T>): useTableItemsReturn<T> {
90
93
  return [details.id, ...visibleDescendantRowIds];
91
94
  };
92
95
 
93
- for (const rowData of items) {
94
- addVisibleRows(rowData);
96
+ for (const rowId of rootRowIds) {
97
+ addVisibleRows(rowId);
95
98
  }
96
99
 
97
100
  return {
@@ -125,9 +128,7 @@ function useTableItems<T>(args: UseTableItemsArgs<T>): useTableItemsReturn<T> {
125
128
 
126
129
  const { Provider: TableItemsProvider, useContext: useTableItemsContext } =
127
130
  /* TODO: Can we type this better? */
128
- createStrictContext<
129
- Omit<useTableItemsReturn<any>, "visibleRowIds" | "childRowIdsById">
130
- >({
131
+ createStrictContext<Omit<useTableItemsReturn<any>, "childRowIdsById">>({
131
132
  name: "TableItemsContext",
132
133
  errorMessage:
133
134
  "useTableItemsContext must be used within a TableItemsProvider",
@@ -1,7 +1,7 @@
1
1
  import { useEffect, useState } from "react";
2
2
  import { useEventCallback } from "../../../utils/hooks";
3
3
  import { focusInitialTableTarget } from "../helpers/table-cell";
4
- import { focusCellAndUpdateTabIndex } from "../helpers/table-focus";
4
+ import { focusCell, focusCellAndUpdateTabIndex } from "../helpers/table-focus";
5
5
  import {
6
6
  findFirstCell,
7
7
  findFirstCellInRow,
@@ -137,6 +137,10 @@ function useTableKeyboardNav({
137
137
  const target = event.target as Element | null;
138
138
 
139
139
  if (tableRef && target === tableRef) {
140
+ if (activeCell) {
141
+ focusCell(activeCell);
142
+ return;
143
+ }
140
144
  focusInitialTableTarget(tableRef);
141
145
  return;
142
146
  }
@@ -173,7 +177,7 @@ function useTableKeyboardNav({
173
177
 
174
178
  return {
175
179
  /* Table should only have tabIndex until the focus is moved inside and is enabled */
176
- tabIndex: enabled ? (activeCell ? undefined : 0) : undefined,
180
+ tabIndex: enabled ? 0 : undefined,
177
181
  setTableRef,
178
182
  };
179
183
  }
@@ -29,9 +29,6 @@ type ColumnDefinition<T, DetailsT = Record<string, any>> = Pick<
29
29
  * Assigned to the cell's `th` element instead of `td` if true.
30
30
  *
31
31
  * Should be used for cells that act as row headers. Each row should have one rowheader, and only have one cell with `isRowHeader: true`,
32
- *
33
- * TODO: Not implemented
34
- * - Add a generic tablecell component that can render either a td or th based on context or this prop.
35
32
  */
36
33
  isRowHeader?: boolean;
37
34
  /**