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
* DataGrid queries them locally; server grids receive one page and must handle
* the complete query through `onQueryChange`.
* DataGrid queries them locally, while optional `onQueryChange` observers can
* explain the current match. Server grids receive one page and must handle the
* complete query through `onQueryChange`.
*/
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;}
);
@@ -256,7 +261,7 @@ export default function DataGrid<T>({
const serverQueryMode = pagination?.mode === "server";
const onQueryChangeRef = useRef(onQueryChange);
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 lastContainerWidthRef = useRef<number | undefined>(undefined);
const [measuredWidths, setMeasuredWidths] = useState<Record<string, number>>({});
@@ -265,7 +270,6 @@ export default function DataGrid<T>({
useEffect(() => {
serverPaginationRef.current = serverQueryMode ? pagination as DataGridServerPagination : null;
if (!serverQueryMode) lastServerQueryRef.current = null;
}, [pagination, serverQueryMode]);
useEffect(() => {
@@ -510,18 +514,23 @@ export default function DataGrid<T>({
}, [openFilterColumnId]);
useEffect(() => {
if (!serverQueryMode) return;
const query = {
const nextQuery = {
sort: state.sort ?? null,
filters: { ...(state.filters ?? {}) }
};
const previousQuery = lastServerQueryRef.current;
lastServerQueryRef.current = query;
const previousQuery = lastQueryRef.current;
lastQueryRef.current = nextQuery;
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);
}
onQueryChangeRef.current?.(query);
onQueryChangeRef.current?.(nextQuery);
}, [serverQueryMode, state.sort, state.filters]);
const filterTypes = useMemo(() => {

View File

@@ -857,7 +857,7 @@ export type FilesFileFolder = {
};
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 FilesFileDeltaResponse = {
files: FilesManagedFile[];
@@ -917,7 +917,7 @@ export type FilesFileExplorerUiCapability = {
FolderTree: ComponentType<FilesFolderTreeProps>;
ManagedFileChooser?: ComponentType<FilesManagedFileChooserProps>;
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>;
listFolders?: (settings: ApiSettings, params: { owner_type: FilesOwnerType; owner_id: string; page_size?: number; cursor?: string | null }) => Promise<FilesFileFoldersResponse>;
resolveFilePatterns?: (