Support observable grid queries and file filters

This commit is contained in:
2026-07-30 05:22:09 +02:00
parent 47e106684d
commit 6970bf7457
2 changed files with 22 additions and 13 deletions

View File

@@ -126,11 +126,16 @@ type DataGridBaseProps<T> = {
/** /**
* Query ownership is deliberately explicit: client grids receive all rows and * Query ownership is deliberately explicit: client grids receive all rows and
* DataGrid queries them locally; server grids receive one page and must handle * DataGrid queries them locally, while optional `onQueryChange` observers can
* the complete query through `onQueryChange`. * explain the current match. Server grids receive one page and must handle the
* complete query through `onQueryChange`.
*/ */
export type DataGridProps<T> = DataGridBaseProps<T> & ( export type DataGridProps<T> = DataGridBaseProps<T> & (
| {pagination?: DataGridClientPagination;onQueryChange?: never;} | {
pagination?: DataGridClientPagination;
/** Observe client-side filters and sorting without taking ownership of them. */
onQueryChange?: (query: DataGridQueryState) => void;
}
| {pagination: DataGridServerPagination;onQueryChange: (query: DataGridQueryState) => void;} | {pagination: DataGridServerPagination;onQueryChange: (query: DataGridQueryState) => void;}
); );
@@ -256,7 +261,7 @@ export default function DataGrid<T>({
const serverQueryMode = pagination?.mode === "server"; const serverQueryMode = pagination?.mode === "server";
const onQueryChangeRef = useRef(onQueryChange); const onQueryChangeRef = useRef(onQueryChange);
const serverPaginationRef = useRef<DataGridServerPagination | null>(serverQueryMode ? pagination : null); const serverPaginationRef = useRef<DataGridServerPagination | null>(serverQueryMode ? pagination : null);
const lastServerQueryRef = useRef<DataGridQueryState | null>(null); const lastQueryRef = useRef<DataGridQueryState | null>(null);
const lastLayoutSignatureRef = useRef<string | null>(null); const lastLayoutSignatureRef = useRef<string | null>(null);
const lastContainerWidthRef = useRef<number | undefined>(undefined); const lastContainerWidthRef = useRef<number | undefined>(undefined);
const [measuredWidths, setMeasuredWidths] = useState<Record<string, number>>({}); const [measuredWidths, setMeasuredWidths] = useState<Record<string, number>>({});
@@ -265,7 +270,6 @@ export default function DataGrid<T>({
useEffect(() => { useEffect(() => {
serverPaginationRef.current = serverQueryMode ? pagination as DataGridServerPagination : null; serverPaginationRef.current = serverQueryMode ? pagination as DataGridServerPagination : null;
if (!serverQueryMode) lastServerQueryRef.current = null;
}, [pagination, serverQueryMode]); }, [pagination, serverQueryMode]);
useEffect(() => { useEffect(() => {
@@ -510,18 +514,23 @@ export default function DataGrid<T>({
}, [openFilterColumnId]); }, [openFilterColumnId]);
useEffect(() => { useEffect(() => {
if (!serverQueryMode) return; const nextQuery = {
const query = {
sort: state.sort ?? null, sort: state.sort ?? null,
filters: { ...(state.filters ?? {}) } filters: { ...(state.filters ?? {}) }
}; };
const previousQuery = lastServerQueryRef.current; const previousQuery = lastQueryRef.current;
lastServerQueryRef.current = query; lastQueryRef.current = nextQuery;
const serverPagination = serverPaginationRef.current; const serverPagination = serverPaginationRef.current;
if (previousQuery && !dataGridQueriesEqual(previousQuery, query) && serverPagination && serverPagination.page !== 1) { if (
serverQueryMode &&
previousQuery &&
!dataGridQueriesEqual(previousQuery, nextQuery) &&
serverPagination &&
serverPagination.page !== 1
) {
serverPagination.onPageChange(1); serverPagination.onPageChange(1);
} }
onQueryChangeRef.current?.(query); onQueryChangeRef.current?.(nextQuery);
}, [serverQueryMode, state.sort, state.filters]); }, [serverQueryMode, state.sort, state.filters]);
const filterTypes = useMemo(() => { const filterTypes = useMemo(() => {

View File

@@ -857,7 +857,7 @@ export type FilesFileFolder = {
}; };
export type FilesFileSpacesResponse = { spaces: FilesFileSpace[] }; export type FilesFileSpacesResponse = { spaces: FilesFileSpace[] };
export type FilesFileListResponse = { files: FilesManagedFile[]; cursor?: string | null; next_cursor?: string | null }; export type FilesFileListResponse = { files: FilesManagedFile[]; total: number; cursor?: string | null; next_cursor?: string | null };
export type FilesFileFoldersResponse = { folders: FilesFileFolder[]; cursor?: string | null; next_cursor?: string | null }; export type FilesFileFoldersResponse = { folders: FilesFileFolder[]; cursor?: string | null; next_cursor?: string | null };
export type FilesFileDeltaResponse = { export type FilesFileDeltaResponse = {
files: FilesManagedFile[]; files: FilesManagedFile[];
@@ -917,7 +917,7 @@ export type FilesFileExplorerUiCapability = {
FolderTree: ComponentType<FilesFolderTreeProps>; FolderTree: ComponentType<FilesFolderTreeProps>;
ManagedFileChooser?: ComponentType<FilesManagedFileChooserProps>; ManagedFileChooser?: ComponentType<FilesManagedFileChooserProps>;
listFileSpaces?: (settings: ApiSettings) => Promise<FilesFileSpacesResponse>; listFileSpaces?: (settings: ApiSettings) => Promise<FilesFileSpacesResponse>;
listFiles?: (settings: ApiSettings, params?: { owner_type?: FilesOwnerType | string; owner_id?: string; campaign_id?: string; path_prefix?: string; page_size?: number; cursor?: string | null }) => Promise<FilesFileListResponse>; listFiles?: (settings: ApiSettings, params?: { owner_type?: FilesOwnerType | string; owner_id?: string; campaign_id?: string; path_prefix?: string; campaign_usage?: "linked" | "unlinked"; audit_relevant?: boolean; page_size?: number; cursor?: string | null }) => Promise<FilesFileListResponse>;
listFilesDelta?: (settings: ApiSettings, params?: { owner_type?: FilesOwnerType | string; owner_id?: string; campaign_id?: string; path_prefix?: string; since?: string | null; limit?: number }) => Promise<FilesFileDeltaResponse>; listFilesDelta?: (settings: ApiSettings, params?: { owner_type?: FilesOwnerType | string; owner_id?: string; campaign_id?: string; path_prefix?: string; since?: string | null; limit?: number }) => Promise<FilesFileDeltaResponse>;
listFolders?: (settings: ApiSettings, params: { owner_type: FilesOwnerType; owner_id: string; page_size?: number; cursor?: string | null }) => Promise<FilesFileFoldersResponse>; listFolders?: (settings: ApiSettings, params: { owner_type: FilesOwnerType; owner_id: string; page_size?: number; cursor?: string | null }) => Promise<FilesFileFoldersResponse>;
resolveFilePatterns?: ( resolveFilePatterns?: (