@olenbetong/appframe-ds 1.0.3 → 1.2.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.
- package/CHANGELOG.md +13 -0
- package/package.json +8 -7
- package/scripts/copyAssets.mjs +27 -55
- package/src/AfLookup/Lookup.css +129 -125
- package/src/AfLookup/Lookup.tsx +184 -162
- package/src/AfLookup/LookupCombobox.css +78 -78
- package/src/AfLookup/LookupCombobox.tsx +237 -237
- package/src/AfLookup/LookupEdit.tsx +130 -130
- package/src/AfLookup/LookupGrid.tsx +124 -124
- package/src/AfLookup/index.ts +5 -5
- package/src/autocomplete/Autocomplete.css +256 -256
- package/src/autocomplete/Autocomplete.tsx +320 -320
- package/src/autocomplete/AutocompleteFrame.tsx +168 -168
- package/src/autocomplete/Combobox.tsx +286 -286
- package/src/autocomplete/index.ts +4 -4
- package/src/autocomplete/types.ts +204 -204
- package/src/autocomplete/utils.ts +183 -183
- package/src/binding/BoundNumericTextField.tsx +102 -102
- package/src/binding/BoundTextField.tsx +49 -49
- package/src/binding/CancelButton.tsx +30 -30
- package/src/binding/CancelIconButton.tsx +30 -30
- package/src/binding/DataEditToolbar.tsx +92 -92
- package/src/binding/DeleteButton.tsx +35 -35
- package/src/binding/DeleteIconButton.tsx +35 -35
- package/src/binding/RefreshButton.tsx +31 -31
- package/src/binding/RefreshIconButton.tsx +33 -33
- package/src/binding/RefreshRowIconButton.tsx +33 -33
- package/src/binding/SaveButton.tsx +37 -37
- package/src/binding/SaveIconButton.tsx +38 -38
- package/src/container/Container.tsx +49 -49
- package/src/container/index.ts +1 -1
- package/src/cutoff/CutOffPicker.css +12 -0
- package/src/cutoff/CutOffPicker.tsx +165 -0
- package/src/cutoff/context.tsx +194 -0
- package/src/cutoff/dsCutOffDates.ts +21 -0
- package/src/cutoff/index.ts +4 -0
- package/src/cutoff/utils.test.ts +59 -0
- package/src/cutoff/utils.ts +276 -0
- package/src/filter/ChipListInput.tsx +79 -79
- package/src/filter/FieldFilterPanel.css +150 -150
- package/src/filter/FieldFilterPanel.tsx +309 -309
- package/src/filter/FilterBuilder.css +152 -152
- package/src/filter/FilterBuilder.test.tsx +153 -153
- package/src/filter/FilterBuilder.tsx +435 -435
- package/src/filter/FilterEditor.css +231 -231
- package/src/filter/FilterEditor.test.tsx +259 -259
- package/src/filter/FilterEditor.tsx +75 -74
- package/src/filter/FilterGroupEditor.tsx +160 -160
- package/src/filter/FilterNameDialog.css +22 -22
- package/src/filter/FilterNameDialog.tsx +81 -81
- package/src/filter/FilterRow.tsx +117 -117
- package/src/filter/FilterShareDialog.css +28 -28
- package/src/filter/FilterShareDialog.tsx +201 -201
- package/src/filter/FilterStringField.tsx +78 -77
- package/src/filter/FilterValueEditor.tsx +220 -220
- package/src/filter/SavedFilterTree.css +107 -107
- package/src/filter/SavedFilterTree.test.tsx +94 -94
- package/src/filter/SavedFilterTree.tsx +218 -222
- package/src/filter/fieldFilter.test.ts +158 -158
- package/src/filter/fieldFilter.ts +139 -139
- package/src/filter/index.ts +27 -27
- package/src/filter/types.ts +38 -38
- package/src/filter/useDistinctValues.test.ts +102 -102
- package/src/filter/useDistinctValues.ts +230 -230
- package/src/global.d.ts +11 -11
- package/src/grid/AfGridColumnsPanel.tsx +118 -118
- package/src/grid/AfGridContext.tsx +51 -51
- package/src/grid/AfGridError.tsx +46 -46
- package/src/grid/AfHeaderFilterCell.tsx +147 -151
- package/src/grid/AfHeaderFilterPanel.tsx +111 -111
- package/src/grid/GridEditLookup.css +12 -0
- package/src/grid/GridEditLookup.tsx +121 -0
- package/src/grid/Toolbar.tsx +233 -233
- package/src/grid/editing.ts +100 -100
- package/src/grid/filter.ts +78 -78
- package/src/grid/formatters.ts +48 -48
- package/src/grid/index.css +245 -245
- package/src/grid/index.tsx +489 -484
- package/src/grid/license.ts +48 -48
- package/src/grid/localization.ts +369 -369
- package/src/grid/slots/index.tsx +307 -307
- package/src/grid/slots/slots.css +64 -64
- package/src/grid/theme.tsx +138 -138
- package/src/grid/useAfColumns.tsx +371 -371
- package/src/grid/useAfCurrentIndex.ts +63 -63
- package/src/grid/useAfData.ts +20 -20
- package/src/grid/useAfFilter.ts +160 -160
- package/src/grid/useAfFilterFields.ts +55 -55
- package/src/grid/useAfGridApi.ts +71 -71
- package/src/grid/useAfKeyBindings.ts +36 -36
- package/src/grid/useAfNewItemRow.ts +258 -258
- package/src/grid/useAfPagination.ts +53 -53
- package/src/grid/useAfPersistedState.ts +230 -230
- package/src/grid/useAfRowEditModel.ts +145 -145
- package/src/grid/useAfRowGrouping.ts +47 -47
- package/src/grid/useAfServerAggregation.ts +145 -145
- package/src/grid/useAfSortModel.ts +84 -84
- package/src/i18n.ts +13 -0
- package/src/index.ts +14 -12
- package/src/input/InputAdornments.css +75 -75
- package/src/input/InputAdornments.tsx +102 -102
- package/src/input/index.ts +1 -1
- package/src/layout/AppMenu.tsx +74 -75
- package/src/layout/AppMenuDrawer.css +70 -70
- package/src/layout/AppMenuDrawer.tsx +122 -122
- package/src/layout/ErrorBoundary.tsx +162 -162
- package/src/layout/ResponsiveDialog.css +86 -0
- package/src/layout/ResponsiveDialog.tsx +55 -0
- package/src/layout/index.tsx +69 -68
- package/src/link/Link.tsx +23 -0
- package/src/link/index.ts +1 -0
- package/src/mdi/Mdi.css +138 -138
- package/src/mdi/Mdi.test.tsx +91 -87
- package/src/mdi/Mdi.tsx +141 -141
- package/src/mdi/MdiConfirmCloseDialog.tsx +47 -47
- package/src/mdi/MdiContext.tsx +59 -59
- package/src/mdi/MdiDocumentContext.tsx +60 -60
- package/src/mdi/MdiPanel.tsx +61 -61
- package/src/mdi/MdiRestore.test.tsx +86 -82
- package/src/mdi/MdiTab.tsx +30 -30
- package/src/mdi/controller.test.ts +382 -382
- package/src/mdi/controller.ts +656 -656
- package/src/mdi/dockviewHost.ts +132 -132
- package/src/mdi/fakeHost.ts +200 -200
- package/src/mdi/host.ts +44 -44
- package/src/mdi/ids.test.ts +20 -20
- package/src/mdi/ids.ts +18 -18
- package/src/mdi/index.ts +31 -31
- package/src/mdi/layoutRestore.test.ts +156 -156
- package/src/mdi/layoutRestore.ts +311 -311
- package/src/mdi/layoutSpec.test.ts +143 -143
- package/src/mdi/layoutSpec.ts +214 -214
- package/src/mdi/layoutStore.test.ts +93 -93
- package/src/mdi/layoutStore.ts +99 -99
- package/src/mdi/memorySettingsStore.ts +21 -21
- package/src/mdi/registry.ts +34 -34
- package/src/mdi/testTypes.ts +37 -37
- package/src/mdi/theme.ts +22 -22
- package/src/mdi/types.ts +214 -214
- package/src/page/Page.css +53 -53
- package/src/page/PageBlock.css +64 -64
- package/src/page/PageBlock.tsx +92 -92
- package/src/page/PageHeader.css +88 -88
- package/src/page/PageHeader.tsx +115 -116
- package/src/page/PageTitle.css +5 -0
- package/src/page/PageTitle.tsx +21 -0
- package/src/page/index.ts +4 -3
- package/src/paper/Paper.tsx +52 -52
- package/src/progress/LinearProgress.css +93 -93
- package/src/progress/LinearProgress.tsx +104 -104
- package/src/progress/index.ts +1 -1
- package/src/report-downloader/ReportDownloader.css +67 -67
- package/src/report-downloader/components/StatusItem.tsx +87 -86
- package/src/report-downloader/components/StatusList.tsx +28 -28
- package/src/report-downloader/index.ts +41 -41
- package/src/report-downloader/status.tsx +92 -92
- package/src/sortable/DropIndicator.css +51 -51
- package/src/sortable/DropIndicator.tsx +27 -27
- package/src/sortable/SortableItemList.css +21 -21
- package/src/sortable/SortableItemList.tsx +264 -264
- package/src/sortable/index.ts +8 -8
- package/src/sortable/reorderSortOrder.ts +126 -126
- package/src/sortable/sortOrder.ts +101 -101
- package/src/sortable/types.ts +9 -9
- package/src/sortable/useSortableContainer.ts +53 -53
- package/src/sortable/useSortableItem.ts +95 -95
- package/src/sortable/useSortableList.ts +117 -117
- package/src/test/happy-dom-document-class.ts +24 -24
- package/src/test/setup.ts +12 -12
- package/src/theme/useDsColorScheme.ts +53 -53
- package/tsconfig.build.json +9 -9
- package/tsconfig.json +8 -8
- package/vitest.config.ts +10 -10
|
@@ -1,237 +1,237 @@
|
|
|
1
|
-
import "./LookupCombobox.css";
|
|
2
|
-
|
|
3
|
-
import { Button, Textfield } from "@digdir/designsystemet-react";
|
|
4
|
-
import {
|
|
5
|
-
import type
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
export interface LookupComboboxBaseProps<T extends Record<string, unknown>> {
|
|
15
|
-
nullable?: boolean;
|
|
16
|
-
dataObject: DataObject<T>;
|
|
17
|
-
uniqueIdField?: string;
|
|
18
|
-
initialSearchValue?: string;
|
|
19
|
-
isItemSelected?: (item: T) => boolean;
|
|
20
|
-
onChange?: (item: T | null) => void;
|
|
21
|
-
value?: T;
|
|
22
|
-
onClose?: () => void;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface LookupComboboxListProps<T extends Record<string, unknown>> extends LookupComboboxBaseProps<T> {
|
|
26
|
-
/**
|
|
27
|
-
* Renders the popup content as a list (default).
|
|
28
|
-
*/
|
|
29
|
-
variant?: "list";
|
|
30
|
-
renderItem: (props: React.ComponentPropsWithRef<"li"> & { item: T }) => React.ReactNode;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface LookupComboboxGridProps<T extends Record<string, unknown>> extends LookupComboboxBaseProps<T> {
|
|
34
|
-
/**
|
|
35
|
-
* Renders the popup content as a grid with a header row and one column per
|
|
36
|
-
* entry in `columns`.
|
|
37
|
-
*/
|
|
38
|
-
variant: "grid";
|
|
39
|
-
columns: LookupColumn<T>[];
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export type LookupComboboxProps<T extends Record<string, unknown>> =
|
|
43
|
-
| LookupComboboxListProps<T>
|
|
44
|
-
| LookupComboboxGridProps<T>;
|
|
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
|
-
|
|
86
|
-
export function LookupCombobox<T extends Record<string, unknown>>(props: LookupComboboxProps<T>) {
|
|
87
|
-
let {
|
|
88
|
-
nullable,
|
|
89
|
-
dataObject,
|
|
90
|
-
isItemSelected,
|
|
91
|
-
uniqueIdField = "PrimKey",
|
|
92
|
-
initialSearchValue,
|
|
93
|
-
onChange,
|
|
94
|
-
onClose,
|
|
95
|
-
} = props;
|
|
96
|
-
let isGrid = props.variant === "grid";
|
|
97
|
-
let columns = isGrid ? (props as LookupComboboxGridProps<T>).columns : undefined;
|
|
98
|
-
let listRef = useListRef(null);
|
|
99
|
-
|
|
100
|
-
let [isDesktop, setIsDesktop] = useState(
|
|
101
|
-
() => typeof window !== "undefined" && window.matchMedia("(width >= 768px) and (pointer: fine)").matches,
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
useEffect(() => {
|
|
105
|
-
let mq = window.matchMedia("(width >= 768px) and (pointer: fine)");
|
|
106
|
-
let handler = (e: MediaQueryListEvent) => setIsDesktop(e.matches);
|
|
107
|
-
mq.addEventListener("change", handler);
|
|
108
|
-
return () => mq.removeEventListener("change", handler);
|
|
109
|
-
}, []);
|
|
110
|
-
|
|
111
|
-
let {
|
|
112
|
-
id,
|
|
113
|
-
data,
|
|
114
|
-
loading,
|
|
115
|
-
inputValue,
|
|
116
|
-
setInputValue,
|
|
117
|
-
currentIndex,
|
|
118
|
-
currentColumn,
|
|
119
|
-
getItemId,
|
|
120
|
-
handleSelectItem,
|
|
121
|
-
handleKeyDown,
|
|
122
|
-
} = useComboboxData<T>({
|
|
123
|
-
dataObject,
|
|
124
|
-
uniqueIdField,
|
|
125
|
-
columnCount: columns?.length ?? 1,
|
|
126
|
-
onChange,
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
let wrapperRef = useRef<HTMLDivElement>(null);
|
|
130
|
-
let [height, setHeight] = useState<number>(0);
|
|
131
|
-
|
|
132
|
-
useLayoutEffect(() => {
|
|
133
|
-
if (!wrapperRef.current) return;
|
|
134
|
-
|
|
135
|
-
let resizeObserver = new ResizeObserver(() => {
|
|
136
|
-
let newHeight = wrapperRef.current!.offsetHeight;
|
|
137
|
-
if (newHeight > 0) setHeight(newHeight);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
resizeObserver.observe(wrapperRef.current);
|
|
141
|
-
|
|
142
|
-
return () => resizeObserver.disconnect();
|
|
143
|
-
}, []);
|
|
144
|
-
|
|
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 });
|
|
148
|
-
|
|
149
|
-
useEffect(() => {
|
|
150
|
-
if (currentIndex >= 0 && currentIndex < data.length) {
|
|
151
|
-
listRef.current?.scrollToRow({ index: currentIndex, align: "smart" });
|
|
152
|
-
}
|
|
153
|
-
}, [currentIndex, data.length, listRef]);
|
|
154
|
-
|
|
155
|
-
useEffect(() => {
|
|
156
|
-
if (initialSearchValue !== undefined) {
|
|
157
|
-
setInputValue(initialSearchValue);
|
|
158
|
-
}
|
|
159
|
-
}, [initialSearchValue, setInputValue]);
|
|
160
|
-
|
|
161
|
-
return (
|
|
162
|
-
// oxlint-disable-next-line jsx-a11y/click-events-have-key-events -- element is not interactive, click is used to prevent dialog close
|
|
163
|
-
// oxlint-disable-next-line jsx-a11y/no-static-element-interactions -- element is not interactive, click is used to prevent dialog close
|
|
164
|
-
<div className="ObCombobox-root" id={id} onClick={(evt) => evt.stopPropagation()}>
|
|
165
|
-
<div className="ObCombobox-actions">
|
|
166
|
-
{nullable && (
|
|
167
|
-
<Button
|
|
168
|
-
className="ObCombobox-clear"
|
|
169
|
-
data-size={isDesktop ? "sm" : "md"}
|
|
170
|
-
variant="tertiary"
|
|
171
|
-
onClick={() => {
|
|
172
|
-
onChange?.(null);
|
|
173
|
-
}}
|
|
174
|
-
>
|
|
175
|
-
{getLocalizedString("Clear")}
|
|
176
|
-
</Button>
|
|
177
|
-
)}
|
|
178
|
-
{onClose && (
|
|
179
|
-
<Button data-size={isDesktop ? "sm" : "md"} variant="tertiary" className="ObCombobox-close" onClick={onClose}>
|
|
180
|
-
{getLocalizedString("Close")}
|
|
181
|
-
</Button>
|
|
182
|
-
)}
|
|
183
|
-
</div>
|
|
184
|
-
<Textfield
|
|
185
|
-
type="search"
|
|
186
|
-
className="ObCombobox-input"
|
|
187
|
-
aria-label={getLocalizedString("Search")}
|
|
188
|
-
placeholder={getLocalizedString("Search")}
|
|
189
|
-
onKeyDown={handleKeyDown}
|
|
190
|
-
aria-activedescendant={getItemId(data[currentIndex]) ?? ""}
|
|
191
|
-
value={inputValue}
|
|
192
|
-
onChange={(event) => setInputValue((event.target as HTMLInputElement).value)}
|
|
193
|
-
/>
|
|
194
|
-
<div className="ObCombobox-listWrap" ref={wrapperRef}>
|
|
195
|
-
{loading && (
|
|
196
|
-
<div className="ObCombobox-progress">
|
|
197
|
-
<div className="ObCombobox-progress-bar" />
|
|
198
|
-
</div>
|
|
199
|
-
)}
|
|
200
|
-
{height > 0 &&
|
|
201
|
-
(columns ? (
|
|
202
|
-
<LookupGrid<T>
|
|
203
|
-
columns={columns}
|
|
204
|
-
data={data}
|
|
205
|
-
height={height}
|
|
206
|
-
currentIndex={currentIndex}
|
|
207
|
-
currentColumn={currentColumn}
|
|
208
|
-
getItemId={getItemId}
|
|
209
|
-
isItemSelected={isItemSelected}
|
|
210
|
-
onSelectItem={handleSelectItem}
|
|
211
|
-
/>
|
|
212
|
-
) : (
|
|
213
|
-
<div className="ObCombobox-list" role="listbox">
|
|
214
|
-
<List<LookupComboboxRowProps<T>>
|
|
215
|
-
role="presentation"
|
|
216
|
-
listRef={listRef}
|
|
217
|
-
style={{ height }}
|
|
218
|
-
rowCount={data.length}
|
|
219
|
-
rowHeight={rowHeight}
|
|
220
|
-
overscanCount={5}
|
|
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
|
-
/>
|
|
232
|
-
</div>
|
|
233
|
-
))}
|
|
234
|
-
</div>
|
|
235
|
-
</div>
|
|
236
|
-
);
|
|
237
|
-
}
|
|
1
|
+
import "./LookupCombobox.css";
|
|
2
|
+
|
|
3
|
+
import { Button, Textfield } from "@digdir/designsystemet-react";
|
|
4
|
+
import type { DataObject } from "@olenbetong/appframe-data";
|
|
5
|
+
import { type LookupColumn, useComboboxData } from "@olenbetong/appframe-react";
|
|
6
|
+
|
|
7
|
+
import clsx from "clsx";
|
|
8
|
+
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
9
|
+
import { List, type RowComponentProps, useDynamicRowHeight, useListRef } from "react-window";
|
|
10
|
+
|
|
11
|
+
import { LookupGrid } from "./LookupGrid.js";
|
|
12
|
+
import { getLocalizedString } from "../i18n.js";
|
|
13
|
+
|
|
14
|
+
export interface LookupComboboxBaseProps<T extends Record<string, unknown>> {
|
|
15
|
+
nullable?: boolean;
|
|
16
|
+
dataObject: DataObject<T>;
|
|
17
|
+
uniqueIdField?: string;
|
|
18
|
+
initialSearchValue?: string;
|
|
19
|
+
isItemSelected?: (item: T) => boolean;
|
|
20
|
+
onChange?: (item: T | null) => void;
|
|
21
|
+
value?: T;
|
|
22
|
+
onClose?: () => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface LookupComboboxListProps<T extends Record<string, unknown>> extends LookupComboboxBaseProps<T> {
|
|
26
|
+
/**
|
|
27
|
+
* Renders the popup content as a list (default).
|
|
28
|
+
*/
|
|
29
|
+
variant?: "list";
|
|
30
|
+
renderItem: (props: React.ComponentPropsWithRef<"li"> & { item: T }) => React.ReactNode;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface LookupComboboxGridProps<T extends Record<string, unknown>> extends LookupComboboxBaseProps<T> {
|
|
34
|
+
/**
|
|
35
|
+
* Renders the popup content as a grid with a header row and one column per
|
|
36
|
+
* entry in `columns`.
|
|
37
|
+
*/
|
|
38
|
+
variant: "grid";
|
|
39
|
+
columns: LookupColumn<T>[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type LookupComboboxProps<T extends Record<string, unknown>> =
|
|
43
|
+
| LookupComboboxListProps<T>
|
|
44
|
+
| LookupComboboxGridProps<T>;
|
|
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
|
+
|
|
86
|
+
export function LookupCombobox<T extends Record<string, unknown>>(props: LookupComboboxProps<T>) {
|
|
87
|
+
let {
|
|
88
|
+
nullable,
|
|
89
|
+
dataObject,
|
|
90
|
+
isItemSelected,
|
|
91
|
+
uniqueIdField = "PrimKey",
|
|
92
|
+
initialSearchValue,
|
|
93
|
+
onChange,
|
|
94
|
+
onClose,
|
|
95
|
+
} = props;
|
|
96
|
+
let isGrid = props.variant === "grid";
|
|
97
|
+
let columns = isGrid ? (props as LookupComboboxGridProps<T>).columns : undefined;
|
|
98
|
+
let listRef = useListRef(null);
|
|
99
|
+
|
|
100
|
+
let [isDesktop, setIsDesktop] = useState(
|
|
101
|
+
() => typeof window !== "undefined" && window.matchMedia("(width >= 768px) and (pointer: fine)").matches,
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
let mq = window.matchMedia("(width >= 768px) and (pointer: fine)");
|
|
106
|
+
let handler = (e: MediaQueryListEvent) => setIsDesktop(e.matches);
|
|
107
|
+
mq.addEventListener("change", handler);
|
|
108
|
+
return () => mq.removeEventListener("change", handler);
|
|
109
|
+
}, []);
|
|
110
|
+
|
|
111
|
+
let {
|
|
112
|
+
id,
|
|
113
|
+
data,
|
|
114
|
+
loading,
|
|
115
|
+
inputValue,
|
|
116
|
+
setInputValue,
|
|
117
|
+
currentIndex,
|
|
118
|
+
currentColumn,
|
|
119
|
+
getItemId,
|
|
120
|
+
handleSelectItem,
|
|
121
|
+
handleKeyDown,
|
|
122
|
+
} = useComboboxData<T>({
|
|
123
|
+
dataObject,
|
|
124
|
+
uniqueIdField,
|
|
125
|
+
columnCount: columns?.length ?? 1,
|
|
126
|
+
onChange,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
let wrapperRef = useRef<HTMLDivElement>(null);
|
|
130
|
+
let [height, setHeight] = useState<number>(0);
|
|
131
|
+
|
|
132
|
+
useLayoutEffect(() => {
|
|
133
|
+
if (!wrapperRef.current) return;
|
|
134
|
+
|
|
135
|
+
let resizeObserver = new ResizeObserver(() => {
|
|
136
|
+
let newHeight = wrapperRef.current!.offsetHeight;
|
|
137
|
+
if (newHeight > 0) setHeight(newHeight);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
resizeObserver.observe(wrapperRef.current);
|
|
141
|
+
|
|
142
|
+
return () => resizeObserver.disconnect();
|
|
143
|
+
}, []);
|
|
144
|
+
|
|
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 });
|
|
148
|
+
|
|
149
|
+
useEffect(() => {
|
|
150
|
+
if (currentIndex >= 0 && currentIndex < data.length) {
|
|
151
|
+
listRef.current?.scrollToRow({ index: currentIndex, align: "smart" });
|
|
152
|
+
}
|
|
153
|
+
}, [currentIndex, data.length, listRef]);
|
|
154
|
+
|
|
155
|
+
useEffect(() => {
|
|
156
|
+
if (initialSearchValue !== undefined) {
|
|
157
|
+
setInputValue(initialSearchValue);
|
|
158
|
+
}
|
|
159
|
+
}, [initialSearchValue, setInputValue]);
|
|
160
|
+
|
|
161
|
+
return (
|
|
162
|
+
// oxlint-disable-next-line jsx-a11y/click-events-have-key-events -- element is not interactive, click is used to prevent dialog close
|
|
163
|
+
// oxlint-disable-next-line jsx-a11y/no-static-element-interactions -- element is not interactive, click is used to prevent dialog close
|
|
164
|
+
<div className="ObCombobox-root" id={id} onClick={(evt) => evt.stopPropagation()}>
|
|
165
|
+
<div className="ObCombobox-actions">
|
|
166
|
+
{nullable && (
|
|
167
|
+
<Button
|
|
168
|
+
className="ObCombobox-clear"
|
|
169
|
+
data-size={isDesktop ? "sm" : "md"}
|
|
170
|
+
variant="tertiary"
|
|
171
|
+
onClick={() => {
|
|
172
|
+
onChange?.(null);
|
|
173
|
+
}}
|
|
174
|
+
>
|
|
175
|
+
{getLocalizedString("Clear")}
|
|
176
|
+
</Button>
|
|
177
|
+
)}
|
|
178
|
+
{onClose && (
|
|
179
|
+
<Button data-size={isDesktop ? "sm" : "md"} variant="tertiary" className="ObCombobox-close" onClick={onClose}>
|
|
180
|
+
{getLocalizedString("Close")}
|
|
181
|
+
</Button>
|
|
182
|
+
)}
|
|
183
|
+
</div>
|
|
184
|
+
<Textfield
|
|
185
|
+
type="search"
|
|
186
|
+
className="ObCombobox-input"
|
|
187
|
+
aria-label={getLocalizedString("Search")}
|
|
188
|
+
placeholder={getLocalizedString("Search")}
|
|
189
|
+
onKeyDown={handleKeyDown}
|
|
190
|
+
aria-activedescendant={getItemId(data[currentIndex]) ?? ""}
|
|
191
|
+
value={inputValue}
|
|
192
|
+
onChange={(event) => setInputValue((event.target as HTMLInputElement).value)}
|
|
193
|
+
/>
|
|
194
|
+
<div className="ObCombobox-listWrap" ref={wrapperRef}>
|
|
195
|
+
{loading && (
|
|
196
|
+
<div className="ObCombobox-progress">
|
|
197
|
+
<div className="ObCombobox-progress-bar" />
|
|
198
|
+
</div>
|
|
199
|
+
)}
|
|
200
|
+
{height > 0 &&
|
|
201
|
+
(columns ? (
|
|
202
|
+
<LookupGrid<T>
|
|
203
|
+
columns={columns}
|
|
204
|
+
data={data}
|
|
205
|
+
height={height}
|
|
206
|
+
currentIndex={currentIndex}
|
|
207
|
+
currentColumn={currentColumn}
|
|
208
|
+
getItemId={getItemId}
|
|
209
|
+
isItemSelected={isItemSelected}
|
|
210
|
+
onSelectItem={handleSelectItem}
|
|
211
|
+
/>
|
|
212
|
+
) : (
|
|
213
|
+
<div className="ObCombobox-list" role="listbox">
|
|
214
|
+
<List<LookupComboboxRowProps<T>>
|
|
215
|
+
role="presentation"
|
|
216
|
+
listRef={listRef}
|
|
217
|
+
style={{ height }}
|
|
218
|
+
rowCount={data.length}
|
|
219
|
+
rowHeight={rowHeight}
|
|
220
|
+
overscanCount={5}
|
|
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
|
+
/>
|
|
232
|
+
</div>
|
|
233
|
+
))}
|
|
234
|
+
</div>
|
|
235
|
+
</div>
|
|
236
|
+
);
|
|
237
|
+
}
|