@olenbetong/appframe-ds 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @olenbetong/appframe-ds
2
2
 
3
+ ## 1.0.2
4
+
5
+ ### Patch Changes
6
+
7
+ - e7e2be3: Migrate the lookup combobox and grid to react-window 2 (`List` with `rowComponent`/`rowProps`, dynamic row heights via `useDynamicRowHeight`), update @atlaskit/pragmatic-drag-and-drop to 3.x and its non-deprecated entry points, and drop the unused react-virtualized-auto-sizer and @types/react-window dependencies.
8
+ - Updated dependencies [c48fb2f]
9
+ - @olenbetong/appframe-react@2.2.0
10
+
3
11
  ## 1.0.1
4
12
 
5
13
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@olenbetong/appframe-ds",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Components that bind Designsystemet components to Appframe data objects",
5
5
  "type": "module",
6
6
  "types": "./es/index.d.ts",
@@ -56,23 +56,22 @@
56
56
  }
57
57
  },
58
58
  "dependencies": {
59
- "@atlaskit/pragmatic-drag-and-drop": "2.0.2",
59
+ "@atlaskit/pragmatic-drag-and-drop": "3.1.0",
60
60
  "@atlaskit/pragmatic-drag-and-drop-hitbox": "2.2.0",
61
61
  "@base-ui/react": "1.8.0",
62
62
  "clsx": "^2.1.1",
63
- "dockview-react": "8.3.0",
63
+ "dockview-react": "8.3.1",
64
64
  "localforage": "^1.10.0",
65
65
  "mitt": "^3.0.1",
66
66
  "react": "19.3.0",
67
67
  "react-dom": "19.3.0",
68
- "react-virtualized-auto-sizer": "^1.0.26",
69
- "react-window": "^1.8.11",
70
- "@olenbetong/appframe-data": "1.7.1",
68
+ "react-window": "2.3.1",
71
69
  "@olenbetong/appframe-core": "2.13.1",
72
- "@olenbetong/appframe-react": "2.1.4"
70
+ "@olenbetong/appframe-data": "1.7.1",
71
+ "@olenbetong/appframe-react": "2.2.0"
73
72
  },
74
73
  "devDependencies": {
75
- "@digdir/designsystemet-react": "1.21.0",
74
+ "@digdir/designsystemet-react": "1.21.1",
76
75
  "@emotion/react": "^11.14.0",
77
76
  "@emotion/styled": "^11.14.1",
78
77
  "@mui/material": "9.4.0",
@@ -85,8 +84,7 @@
85
84
  "@testing-library/user-event": "^14.6.7",
86
85
  "@types/react": "19.3.0",
87
86
  "@types/react-dom": "19.3.0",
88
- "@types/react-window": "^1.8.8",
89
- "happy-dom": "^20.14.3",
87
+ "happy-dom": "20.14.5",
90
88
  "react-error-boundary": "6.1.5",
91
89
  "react-router": "8.3.1",
92
90
  "typescript": "7.0.2"
@@ -6,8 +6,8 @@ import type { DataObject } from "@olenbetong/appframe-data";
6
6
  import { type LookupColumn, useComboboxData } from "@olenbetong/appframe-react";
7
7
 
8
8
  import clsx from "clsx";
9
- import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
10
- import { VariableSizeList as VirtualList } from "react-window";
9
+ import { useEffect, useLayoutEffect, useRef, useState } from "react";
10
+ import { List, type RowComponentProps, useDynamicRowHeight, useListRef } from "react-window";
11
11
 
12
12
  import { LookupGrid } from "./LookupGrid.js";
13
13
 
@@ -43,6 +43,46 @@ export type LookupComboboxProps<T extends Record<string, unknown>> =
43
43
  | LookupComboboxListProps<T>
44
44
  | LookupComboboxGridProps<T>;
45
45
 
46
+ type LookupComboboxRowProps<T extends Record<string, unknown>> = {
47
+ data: T[];
48
+ currentIndex: number;
49
+ isDesktop: boolean;
50
+ getItemId: (item: T | undefined) => string | undefined;
51
+ isItemSelected?: (item: T) => boolean;
52
+ onSelectItem: (item: T) => void;
53
+ renderItem: LookupComboboxListProps<T>["renderItem"];
54
+ };
55
+
56
+ function LookupComboboxRow<T extends Record<string, unknown>>({
57
+ index,
58
+ style,
59
+ data,
60
+ currentIndex,
61
+ isDesktop,
62
+ getItemId,
63
+ isItemSelected,
64
+ onSelectItem,
65
+ renderItem: ItemComponent,
66
+ }: RowComponentProps<LookupComboboxRowProps<T>>) {
67
+ let item = data[index];
68
+ let itemId = getItemId(item);
69
+ let active = index === currentIndex;
70
+
71
+ return (
72
+ <ItemComponent
73
+ className={clsx("ObCombobox-listItem", active && "active", isItemSelected?.(item) && "selected")}
74
+ aria-selected={active}
75
+ onClick={() => onSelectItem(item)}
76
+ item={item}
77
+ style={{
78
+ ...style,
79
+ padding: isDesktop ? "0 8px" : "8px",
80
+ }}
81
+ id={itemId}
82
+ />
83
+ );
84
+ }
85
+
46
86
  export function LookupCombobox<T extends Record<string, unknown>>(props: LookupComboboxProps<T>) {
47
87
  let {
48
88
  nullable,
@@ -55,7 +95,7 @@ export function LookupCombobox<T extends Record<string, unknown>>(props: LookupC
55
95
  } = props;
56
96
  let isGrid = props.variant === "grid";
57
97
  let columns = isGrid ? (props as LookupComboboxGridProps<T>).columns : undefined;
58
- let listRef = useRef<VirtualList>(null);
98
+ let listRef = useListRef(null);
59
99
 
60
100
  let [isDesktop, setIsDesktop] = useState(
61
101
  () => typeof window !== "undefined" && window.matchMedia("(width >= 768px) and (pointer: fine)").matches,
@@ -102,47 +142,15 @@ export function LookupCombobox<T extends Record<string, unknown>>(props: LookupC
102
142
  return () => resizeObserver.disconnect();
103
143
  }, []);
104
144
 
105
- let sizeMap = useRef(new Map<number, number>());
106
- let getItemSize = (index: number) => sizeMap.current.get(index) ?? 56;
107
- let setSize = useCallback((index: number, size: number) => {
108
- if (sizeMap.current.get(index) !== size) {
109
- sizeMap.current.set(index, size);
110
- // listRef will not be set on the initial render, so delay resetting
111
- setTimeout(() => listRef.current?.resetAfterIndex(index, true));
112
- }
113
- }, []);
114
-
115
- let Row = ({ index, style: { height, ...style } }: { index: number; style: React.CSSProperties }) => {
116
- let rowRef = useRef<HTMLLIElement>(null);
117
-
118
- useLayoutEffect(() => {
119
- if (!rowRef.current) return;
120
- setSize(index, rowRef.current.offsetHeight);
121
- }, [index]);
122
-
123
- let item = data[index];
124
- let itemId = getItemId(item);
125
- let active = index === currentIndex;
126
- let ItemComponent = (props as LookupComboboxListProps<T>).renderItem;
127
-
128
- return (
129
- <ItemComponent
130
- className={clsx("ObCombobox-listItem", active && "active", isItemSelected?.(item) && "selected")}
131
- aria-selected={active}
132
- onClick={() => handleSelectItem(item)}
133
- item={item}
134
- style={{
135
- ...style,
136
- padding: isDesktop ? "0 8px" : "8px",
137
- }}
138
- ref={rowRef}
139
- key={itemId}
140
- id={itemId}
141
- />
142
- );
143
- };
145
+ // Rows are rendered by the consumer and have no fixed height, so let the
146
+ // list measure them as they mount.
147
+ let rowHeight = useDynamicRowHeight({ defaultRowHeight: 56 });
144
148
 
145
- useEffect(() => listRef.current?.scrollToItem(currentIndex, "smart"), [currentIndex]);
149
+ useEffect(() => {
150
+ if (currentIndex >= 0 && currentIndex < data.length) {
151
+ listRef.current?.scrollToRow({ index: currentIndex, align: "smart" });
152
+ }
153
+ }, [currentIndex, data.length, listRef]);
146
154
 
147
155
  useEffect(() => {
148
156
  if (initialSearchValue !== undefined) {
@@ -203,16 +211,24 @@ export function LookupCombobox<T extends Record<string, unknown>>(props: LookupC
203
211
  />
204
212
  ) : (
205
213
  <div className="ObCombobox-list" role="listbox">
206
- <VirtualList
207
- ref={listRef}
208
- height={height}
209
- width="100%"
210
- itemCount={data.length}
211
- itemSize={getItemSize}
214
+ <List<LookupComboboxRowProps<T>>
215
+ role="presentation"
216
+ listRef={listRef}
217
+ style={{ height }}
218
+ rowCount={data.length}
219
+ rowHeight={rowHeight}
212
220
  overscanCount={5}
213
- >
214
- {Row}
215
- </VirtualList>
221
+ rowComponent={LookupComboboxRow}
222
+ rowProps={{
223
+ data,
224
+ currentIndex,
225
+ isDesktop,
226
+ getItemId,
227
+ isItemSelected,
228
+ onSelectItem: handleSelectItem,
229
+ renderItem: (props as LookupComboboxListProps<T>).renderItem,
230
+ }}
231
+ />
216
232
  </div>
217
233
  ))}
218
234
  </div>
@@ -4,8 +4,8 @@ import { getLocalizedString } from "@olenbetong/appframe-core";
4
4
  import type { LookupColumn } from "@olenbetong/appframe-react";
5
5
 
6
6
  import clsx from "clsx";
7
- import { useEffect, useMemo, useRef } from "react";
8
- import { FixedSizeList as VirtualList } from "react-window";
7
+ import { useEffect, useMemo } from "react";
8
+ import { List, type RowComponentProps, useListRef } from "react-window";
9
9
 
10
10
  const ROW_HEIGHT = 40;
11
11
  const HEADER_HEIGHT = 40;
@@ -29,6 +29,49 @@ function getColumnWidth<T extends Record<string, unknown>>(column: LookupColumn<
29
29
  return column.width ?? "minmax(80px, 1fr)";
30
30
  }
31
31
 
32
+ type LookupGridRowProps<T extends Record<string, unknown>> = Omit<LookupGridProps<T>, "height"> & {
33
+ gridTemplateColumns: string;
34
+ };
35
+
36
+ function LookupGridRow<T extends Record<string, unknown>>({
37
+ index,
38
+ style,
39
+ columns,
40
+ data,
41
+ currentIndex,
42
+ currentColumn,
43
+ gridTemplateColumns,
44
+ getItemId,
45
+ isItemSelected,
46
+ onSelectItem,
47
+ }: RowComponentProps<LookupGridRowProps<T>>) {
48
+ let item = data[index];
49
+ let itemId = getItemId(item);
50
+ let active = index === currentIndex;
51
+
52
+ return (
53
+ <div
54
+ role="row"
55
+ aria-selected={active}
56
+ className={clsx("ObLookupGrid-row", active && "active", isItemSelected?.(item) && "selected")}
57
+ style={{ ...style, gridTemplateColumns }}
58
+ onClick={() => onSelectItem(item)}
59
+ id={itemId}
60
+ >
61
+ {columns.map((column, columnIndex) => (
62
+ <div
63
+ role="gridcell"
64
+ className={clsx("ObLookupGrid-cell", active && columnIndex === currentColumn && "active")}
65
+ key={column.field}
66
+ title={String(item[column.field] ?? "")}
67
+ >
68
+ {column.renderCell ? column.renderCell(item[column.field], item) : String(item[column.field] ?? "")}
69
+ </div>
70
+ ))}
71
+ </div>
72
+ );
73
+ }
74
+
32
75
  export function LookupGrid<T extends Record<string, unknown>>({
33
76
  columns,
34
77
  data,
@@ -39,40 +82,14 @@ export function LookupGrid<T extends Record<string, unknown>>({
39
82
  isItemSelected,
40
83
  onSelectItem,
41
84
  }: LookupGridProps<T>) {
42
- let listRef = useRef<VirtualList>(null);
85
+ let listRef = useListRef(null);
43
86
  let gridTemplateColumns = useMemo(() => columns.map(getColumnWidth).join(" "), [columns]);
44
87
 
45
- useEffect(() => listRef.current?.scrollToItem(currentIndex, "smart"), [currentIndex]);
46
-
47
- // oxlint-disable-next-line react/no-nested-component-definitions -- react-window row renderer
48
- let Row = ({ index, style }: { index: number; style: React.CSSProperties }) => {
49
- let item = data[index];
50
- let itemId = getItemId(item);
51
- let active = index === currentIndex;
52
-
53
- return (
54
- <div
55
- role="row"
56
- aria-selected={active}
57
- className={clsx("ObLookupGrid-row", active && "active", isItemSelected?.(item) && "selected")}
58
- style={{ ...style, gridTemplateColumns }}
59
- onClick={() => onSelectItem(item)}
60
- key={itemId}
61
- id={itemId}
62
- >
63
- {columns.map((column, columnIndex) => (
64
- <div
65
- role="gridcell"
66
- className={clsx("ObLookupGrid-cell", active && columnIndex === currentColumn && "active")}
67
- key={column.field}
68
- title={String(item[column.field] ?? "")}
69
- >
70
- {column.renderCell ? column.renderCell(item[column.field], item) : String(item[column.field] ?? "")}
71
- </div>
72
- ))}
73
- </div>
74
- );
75
- };
88
+ useEffect(() => {
89
+ if (currentIndex >= 0 && currentIndex < data.length) {
90
+ listRef.current?.scrollToRow({ index: currentIndex, align: "smart" });
91
+ }
92
+ }, [currentIndex, data.length, listRef]);
76
93
 
77
94
  return (
78
95
  <div role="grid" aria-label={getLocalizedString("Search results")} className="ObLookupGrid-root">
@@ -83,16 +100,25 @@ export function LookupGrid<T extends Record<string, unknown>>({
83
100
  </div>
84
101
  ))}
85
102
  </div>
86
- <VirtualList
87
- ref={listRef}
88
- height={Math.max(0, height - HEADER_HEIGHT)}
89
- width="100%"
90
- itemCount={data.length}
91
- itemSize={ROW_HEIGHT}
103
+ <List<LookupGridRowProps<T>>
104
+ role="rowgroup"
105
+ listRef={listRef}
106
+ style={{ height: Math.max(0, height - HEADER_HEIGHT) }}
107
+ rowCount={data.length}
108
+ rowHeight={ROW_HEIGHT}
92
109
  overscanCount={5}
93
- >
94
- {Row}
95
- </VirtualList>
110
+ rowComponent={LookupGridRow}
111
+ rowProps={{
112
+ columns,
113
+ data,
114
+ currentIndex,
115
+ currentColumn,
116
+ gridTemplateColumns,
117
+ getItemId,
118
+ isItemSelected,
119
+ onSelectItem,
120
+ }}
121
+ />
96
122
  </div>
97
123
  );
98
124
  }
@@ -1,4 +1,4 @@
1
- import { monitorForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
1
+ import { monitorForElements } from "@atlaskit/pragmatic-drag-and-drop/adapter/element-adapter";
2
2
  import { type Edge, extractClosestEdge } from "@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge";
3
3
  import { useEffect, useRef } from "react";
4
4
 
@@ -1,5 +1,5 @@
1
- import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine";
2
- import { draggable, dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
1
+ import { combine } from "@atlaskit/pragmatic-drag-and-drop/utils/combine";
2
+ import { draggable, dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/adapter/element-adapter";
3
3
  import {
4
4
  attachClosestEdge,
5
5
  type Edge,