import { default as React } from 'react'; import { DataSource, LookupColumnDef, LookupFilterDef } from '@object-ui/types'; import { LookupCellRendererResolver } from './lookupColumnDisplay.js'; /** * Cell renderer function signature — matches getCellRenderer from @object-ui/fields. * Accepts a field type and returns a React component that renders a formatted cell. */ export type CellRendererResolver = LookupCellRendererResolver; /** * Filter column definition used by the inline filter bar. * A subset of LookupColumnDef enriched with filter-specific metadata. * Compatible with FilterUISchema.filters entries for easy bridging. */ export interface RecordPickerFilterColumn { field: string; label?: string; type: 'text' | 'number' | 'select' | 'date' | 'boolean'; options?: Array<{ label: string; value: any; }>; } /** * Props passed to the custom filter bar renderer (renderFilterBar slot). * Allows plugging in FilterUI or any custom component. */ export interface RecordPickerFilterBarProps { /** Filter column definitions describing each filterable field */ filterColumns: RecordPickerFilterColumn[]; /** Current filter values keyed by field name */ values: Record; /** Called when a single filter value changes */ onChange: (field: string, value: any) => void; /** Clear all filter values */ onClear: () => void; /** Number of actively applied filters */ activeCount: number; } /** * Props passed to the custom grid renderer (renderGrid slot). * Allows plugging in ObjectGrid or any compatible table component. */ export interface RecordPickerGridSlotProps { /** Resolved column definitions */ columns: LookupColumnDef[]; /** Current page of records */ records: any[]; /** Whether data is loading */ loading: boolean; /** Total record count across all pages */ totalCount: number; /** Current page number (1-based) */ currentPage: number; /** Records per page */ pageSize: number; /** Current sort field, null if unsorted */ sortField: string | null; /** Current sort direction */ sortDirection: 'asc' | 'desc'; /** Called when a column header is clicked to sort */ onSort: (field: string) => void; /** Called when page changes */ onPageChange: (page: number) => void; /** Called when a row is clicked */ onRowClick: (record: any) => void; /** Check if a record is selected */ isSelected: (record: any) => boolean; /** Whether multiple selection is enabled */ multiple: boolean; /** Record ID field name */ idField: string; /** Cell renderer resolver */ cellRenderer?: CellRendererResolver; } /** * Convert LookupFilterDef[] to a Record compatible with * QueryParams.$filter. Supports operator mapping for eq/ne/gt/lt/gte/lte/ * contains/in/notIn. */ export declare function lookupFiltersToRecord(filters: LookupFilterDef[]): Record; export interface RecordPickerDialogProps { /** Whether the dialog is open */ open: boolean; /** Called when the dialog should close */ onOpenChange: (open: boolean) => void; /** Dialog title */ title?: string; /** Allow multiple selection */ multiple?: boolean; /** DataSource to fetch records from */ dataSource: DataSource; /** Object name to query (e.g. 'customers') */ objectName: string; /** Columns to display. Defaults to [displayField, descriptionField]. */ columns?: Array; /** Primary display field (default: 'name') */ displayField?: string; /** * Optional `titleFormat` template (e.g. `"{full_name}"` or * `"{case_number} - {subject}"`). When set and the displayField column is * auto-inferred, the column renders via the template instead of reading * a possibly-missing field. Mirrors how DetailView/ObjectCalendar resolve * record titles. */ titleFormat?: string | null; /** Record id field (default: 'id') */ idField?: string; /** Page size (default: 10) */ pageSize?: number; /** Currently selected value(s) */ value?: any; /** Called when selection changes */ onSelect: (value: any) => void; /** * Called with the full record objects corresponding to the selected value(s). * Useful for parent components (e.g. LookupField) that need display data * (labels, descriptions) for the selected records beyond just their IDs. */ onSelectRecords?: (records: any[]) => void; /** * Base filters applied to every query. * Converted from LookupFieldMetadata.lookup_filters. * Restricts which records are selectable (e.g. only active records). */ lookupFilters?: LookupFilterDef[]; /** * Hard filter constraint applied to every query. Unlike `lookupFilters`, * entries here never surface in the filter bar and cannot be overridden by * user filter input. * * Two shapes, discriminated STRUCTURALLY (`Array.isArray`), because the two * callers speak two legitimate vocabularies and neither should be bent into * the other: * * - **`QueryParams.$filter` record form** (`{ account: 'a1' }`) — the * dependent (cascading) lookup chain, where the parent field's value MUST * scope the candidate set (#2215). Merged by KEY OVERWRITE, so a cascaded * value REPLACES a stale `lookupFilters` entry on the same field instead of * intersecting with it. That precedence is load-bearing: an `and` of both * would ask for `account = 'stale' AND account = 'a1'` and return nothing. * - **A spec `ViewFilterRule[]`** (`[{ field, operator, value? }]`) — an * author's `record:related_list.add.picker.filter`, handed over VERBATIM * (#3831). Lowered by `mergeFilterNodes`, the repo's single filter sink, so * all 19 `VIEW_FILTER_OPERATORS` reach the wire — including the four * (`before`, `after`, `is_empty`, `is_not_empty`) the record form has no * `$op` for. No second operator vocabulary is introduced here: two already * exist (the spec's `AST_OPERATOR_MAP`, data-objectstack's * `FILTER_OPERATOR_ALIASES`) and #3948 is what a third costs. * * The discriminator is exact rather than heuristic — every AST node is an * ARRAY and a rule is a plain OBJECT, the same predicate `toFilterNode` uses. * * Typed `unknown` rather than `Record< string, any >` on purpose: that type * ACCEPTED a rule array (TypeScript lets an array satisfy a string index of * `any`), the old object-spread merge then flattened it to * `{"0": {...}, "1": {...}}`, and the query filtered on columns literally * named `0`/`1` — type-check green, wrong query, no diagnostic anywhere. */ baseFilter?: unknown; /** * Cell renderer resolver function. * When provided, columns with a `type` property will be rendered using the * resolved cell renderer (e.g. badges for select, formatted currency, etc.). * Typically pass `getCellRenderer` from @object-ui/fields. */ cellRenderer?: CellRendererResolver; /** * The referenced object's schema `fields` map (field name → field * definition). When provided, cell renderers receive the FULL field * metadata — `options`, `currency`, `scale`, `precision`, `format`, * `reference_to`, … — exactly like the list view enriches its columns from * the object schema. Without it a `select` column falls back to * title-casing the raw stored value instead of resolving the option label * (#3333: `manufacturing` rendered as "Manufacturing" instead of the * authored option label). * * The filter bar reads the same map: a `select` filter column with no * authored `options` takes them from the schema field here, so the filter * panel's dropdown offers exactly the options the table cells render * (#3336 — it used to open empty, leaving the field unfilterable). */ fieldsMeta?: Record; /** * Filter bar column definitions. * When provided, shows an inline filter bar below the search input. * Columns can include type-specific inputs (text, number, select, date, boolean). */ filterColumns?: RecordPickerFilterColumn[]; /** * Custom filter bar renderer slot. * When provided, replaces the built-in filter bar with a custom component * (e.g. FilterUI from @object-ui/plugin-view). * Receives filter state and callbacks via RecordPickerFilterBarProps. * * @example * renderFilterBar={(props) => ( * Object.entries(values).forEach(([k, v]) => props.onChange(k, v))} * /> * )} */ renderFilterBar?: (props: RecordPickerFilterBarProps) => React.ReactNode; /** * Custom grid renderer slot. * When provided, replaces the built-in table with a custom grid component * (e.g. ObjectGrid from @object-ui/plugin-grid). * Receives data, columns, and interaction callbacks via RecordPickerGridSlotProps. * * @example * renderGrid={(props) => ( * * )} */ renderGrid?: (props: RecordPickerGridSlotProps) => React.ReactNode; } /** * RecordPickerDialog — Enterprise-grade record selection dialog. * * Renders records in a table with multi-column display, search, * pagination, column sorting, keyboard navigation, loading/error/empty * states, and single/multi-select. Responsive: mobile-friendly width * via Tailwind breakpoints. */ export declare function RecordPickerDialog({ open, onOpenChange, title, multiple, dataSource, objectName, columns: columnsProp, displayField, titleFormat, idField, pageSize, value, onSelect, onSelectRecords, lookupFilters, baseFilter, cellRenderer, fieldsMeta, filterColumns, renderFilterBar, renderGrid, }: RecordPickerDialogProps): React.JSX.Element;