///
import type { ColumnDef, Row, RowSelectionState, Table, Updater, VisibilityState } from '@tanstack/table-core';
import type { DataTableFilterOperatorTypes, FilterOperatorTypes, FilterSelectOption, FilterTypes, FilterValueType } from '~/types/filters';
import type { BaseSelectProps } from '../select/select-root';
export type DataViewMode = 'client' | 'server';
export declare const SortOrders: {
readonly ASC: "asc";
readonly DESC: "desc";
};
type SortOrdersKeys = keyof typeof SortOrders;
export type SortOrdersValues = (typeof SortOrders)[SortOrdersKeys];
export interface DataViewSort {
name: string;
order: SortOrdersValues;
}
export interface DataViewFilterValues {
value: any;
boolValue?: boolean;
stringValue?: string;
numberValue?: number;
}
export interface InternalFilter extends DataViewFilterValues {
_type?: FilterTypes;
_dataType?: FilterValueType;
name: string;
operator: FilterOperatorTypes;
}
export interface DataViewFilter extends DataViewFilterValues {
name: string;
operator: DataTableFilterOperatorTypes;
}
export interface InternalQuery {
filters?: InternalFilter[];
sort?: DataViewSort[];
group_by?: string[];
offset?: number;
limit?: number;
search?: string;
}
export interface DataViewQuery extends Omit {
filters?: DataViewFilter[];
}
/**
* Renderer-agnostic field metadata. One entry per logical column of the data
* model. Declared once on ``; drives filter, sort, group, and
* visibility behaviour across every renderer. Cell/header rendering belongs on
* each renderer's own column spec, not here.
*/
export interface DataViewField {
accessorKey: string;
/** Human-readable label shown in filter chips, Display controls, and the default Table header. */
label: string;
icon?: React.ReactNode;
filterable?: boolean;
filterType?: FilterTypes;
dataType?: FilterValueType;
filterOptions?: FilterSelectOption[];
defaultFilterValue?: unknown;
filterProps?: {
select?: BaseSelectProps;
};
sortable?: boolean;
groupable?: boolean;
hideable?: boolean;
defaultHidden?: boolean;
showGroupCount?: boolean;
groupCountMap?: Record;
groupLabelsMap?: Record;
/**
* Section order when this field is the active `group_by`, keyed by raw group
* value (the same keys `groupLabelsMap` uses) — e.g.
* `['High', 'Medium', 'Low']` for a priority field, which text sorting alone
* can't produce.
*
* Values absent from the list follow in first-seen data order, and rows with
* no value always land in the last section. A listed value with no rows
* produces no section. Honoured by every renderer that groups.
*/
groupOrder?: string[];
}
/**
* Unified column spec for `DataView.List`. The same shape is used for both
* `variant="table"` and `variant="list"`. The `header` slot is only rendered
* when headers are visible (default for `variant="table"`).
*/
export interface DataViewListColumn {
accessorKey: string;
/** TanStack-style cell renderer. */
cell?: ColumnDef['cell'];
/** TanStack-style header renderer. Overrides the field's `label`. */
header?: ColumnDef['header'];
/** CSS grid track width. `1fr`, `auto`, `'200px'`, `'minmax(80px, 1fr)'`, or a number (pixels). Defaults to `1fr`. */
width?: string | number;
/**
* @deprecated Every cell and header cell carries `data-column={accessorKey}`
* alongside its `data-slot`. Target this column with
* `[data-slot="data-view-list-cell"][data-column="..."]` (and the
* `-header-cell` variant) instead.
*/
classNames?: {
cell?: string;
header?: string;
};
styles?: {
cell?: React.CSSProperties;
header?: React.CSSProperties;
};
}
/**
* Multi-view configuration entry. `value` must match the `name` prop on a
* renderer; `label` is shown in the view switcher.
*/
export interface ViewSpec {
value: string;
label: string;
/** Optional icon rendered before the view's label in the switcher tab. */
leadingIcon?: React.ReactNode;
}
/**
* Local resolver for a group_by key. Lets a string key in `group_by` (which
* stays on the wire untouched for server-mode round-trips) map to a function
* that returns a bucket id per row.
*/
export type GroupByResolver = (row: TData) => string;
export interface DataViewProps {
data: TData[];
/** Renderer-agnostic field metadata. Drives filter/sort/group/visibility. */
fields: DataViewField[];
/** Initial query. Transformed to the internal shape on mount. */
query?: DataViewQuery;
mode?: DataViewMode;
isLoading?: boolean;
totalRowCount?: number;
loadingRowCount?: number;
onTableQueryChange?: (query: DataViewQuery) => void;
defaultSort: DataViewSort;
onLoadMore?: () => Promise | void;
onRowClick?: (row: TData) => void;
onColumnVisibilityChange?: (columnVisibility: VisibilityState) => void;
/**
* Fires with the new selection map whenever rows are selected or deselected
* (`row.toggleSelected()`, `table.toggleAllRowsSelected()`, …). Selection
* itself lives on the table instance — read it through
* `useDataView().table`; this is only for mirroring it outside the tree.
* Keys are `getRowId` values (row indices when `getRowId` is omitted).
*/
onRowSelectionChange?: (rowSelection: RowSelectionState) => void;
/** Stable unique id per row (React key). */
getRowId?: (row: TData, index: number) => string;
/** Multi-view configuration. When set, `DataView.DisplayControls` renders a view switcher and renderers gate themselves on the active view via their `name` prop. */
views?: ViewSpec[];
/** Default active view (uncontrolled). Should match a `views[].value`. */
defaultView?: string;
/** Active view (controlled). */
view?: string;
/** Called when the active view changes. */
onViewChange?: (view: string) => void;
/**
* Optional local resolver map for non-accessor `group_by` keys. The wire
* format (`group_by: string[]`) stays unchanged; resolvers run in client mode
* to compute the bucket id per row when a key matches one in this map.
*/
groupByResolvers?: Record>;
}
/** @deprecated Every key here has an equivalent `[data-slot]` selector — see the List slot table in the DataView docs. Prefer styling by `data-slot` over threading class names through props. */
export type DataViewListClassNames = {
/** @deprecated Use `[data-slot="data-view-list"]` instead. */
root?: string;
/** @deprecated Use `[data-slot="data-view-list-header"]` instead. */
header?: string;
/** @deprecated Use `[data-slot="data-view-list-header-cell"]` instead. */
headerCell?: string;
/** @deprecated Use `[data-slot="data-view-list-row"]` instead. */
row?: string;
/** @deprecated Use `[data-slot="data-view-list-cell"]` instead. */
cell?: string;
/** @deprecated Use `[data-slot="data-view-list-group-header"]` instead. */
groupHeader?: string;
};
export interface DataViewListProps {
/** Multi-view name. When set, the renderer gates itself on the active view. */
name?: string;
/** Visual variant. `table` renders headers and uses `role="table"`; `list` renders no headers and uses `role="list"`. Default `list`. */
variant?: 'table' | 'list';
/** Override the header row visibility. Defaults to `variant === 'table'`. */
showHeaders?: boolean;
/** Override the ARIA role applied to the renderer root. Derived from `variant` by default. */
role?: 'table' | 'list';
/** Optional view-scoped field override. Full replacement of root `fields` for this view's active session. */
fields?: DataViewField[];
/** Column render specs (cell/header/width/styles). */
columns: DataViewListColumn[];
/**
* Initial row-height estimate (px). Rows are auto-measured after they paint,
* so this is only used until the first measurement. Default 40 for
* `variant="table"`, 56 for `variant="list"`.
*/
estimatedRowHeight?: number;
/** When true, only viewport-visible rows render. Parent must have a fixed height. */
virtualized?: boolean;
/** Render thin dividers between rows. Defaults to true for `variant="table"`. */
showDividers?: boolean;
/** Show group section headers when grouping is active. Default true. */
showGroupHeaders?: boolean;
/** When true, group headers stick under the table header while scrolling. Default false. */
stickyGroupHeader?: boolean;
/** @deprecated Style rendered parts by `[data-slot]` instead — see `DataViewListClassNames`. */
classNames?: DataViewListClassNames;
}
/** Date inputs accepted by Timeline props and row fields: Date, epoch ms, or a parseable string. */
export type TimelineDateInput = Date | number | string;
/** Tick granularity of the Timeline axis. */
export type TimelineScale = 'day' | 'week' | 'month' | 'quarter';
/** Full-height marker line with a badge pinned to the axis (milestones, deadlines). */
export interface TimelineMarker {
date: TimelineDateInput;
/** Badge content. Defaults to the marker date formatted as "17 Jan". */
label?: React.ReactNode;
variant?: 'default' | 'accent' | 'danger';
}
/**
* Geometry + state handed to `renderCard`. The Timeline owns positioning; the
* consumer owns the card visual and uses this context to adapt it (e.g. render
* a compact stub when `collapsed`).
*/
export interface TimelineCardContext {
/** Pixel width of the time span (0 when `endField` is omitted). */
width: number;
/**
* True when the span is narrower than `minCardWidth`. Always false for
* point cards (no `endField`) — they size to their content instead.
*/
collapsed: boolean;
/**
* Lane (row) index assigned by packing. Relative to the card's own group
* section when `group_by` is active — every section's first lane is 0.
*/
laneIndex: number;
start: Date;
/** Null when `endField` is omitted (point marker). */
end: Date | null;
}
/**
* Imperative navigation surface exposed through `actionsRef` on
* `DataView.Timeline` (same pattern as Tour's `actionsRef`). Available for the
* lifetime of the component; methods no-op (with a dev warning) while the
* renderer is hidden — inactive view or no data.
*/
export interface TimelineActions {
/**
* Scroll the viewport so `target` lands at `align` (default `'center'`).
* Accepts the `defaultScrollTo` vocabulary: a date input, `'today'`,
* `'start'`, or `'end'` (domain edges). Dates outside the domain clamp to
* the nearest edge; invalid dates no-op with a dev warning. Edge
* alignments keep a small inset so the target doesn't sit flush against
* the viewport edge (yields at the domain edges).
*/
scrollTo: (target: TimelineDateInput | 'today' | 'start' | 'end', options?: {
align?: 'start' | 'center' | 'end';
/** Default `'smooth'` — a navigation action should visibly travel. */
behavior?: 'auto' | 'smooth';
}) => void;
/** The visible time window, or null while the renderer is hidden. */
getVisibleRange: () => [Date, Date] | null;
}
/** @deprecated Every key here has an equivalent `[data-slot]` selector — see the Timeline slot table in the DataView docs. Prefer styling by `data-slot` over threading class names through props. */
export type DataViewTimelineClassNames = {
/** @deprecated Use `[data-slot="data-view-timeline"]` instead. */
root?: string;
/** @deprecated Use `[data-slot="data-view-timeline-axis"]` instead. */
axis?: string;
/** @deprecated Use `[data-slot="data-view-timeline-axis-band"]` instead. */
band?: string;
/** @deprecated Use `[data-slot="data-view-timeline-axis-tick"]` instead. */
tick?: string;
/** @deprecated Use `[data-slot="data-view-timeline-marker"]` instead. */
marker?: string;
/** @deprecated Use `[data-slot="data-view-timeline-gridline"]` instead. */
gridline?: string;
/** @deprecated Use `[data-slot="data-view-timeline-cursor"]` instead. */
cursor?: string;
/** @deprecated Use `[data-slot="data-view-timeline-canvas"]` instead. */
canvas?: string;
/** @deprecated Use `[data-slot="data-view-timeline-card"]` instead. */
card?: string;
/**
* Group section header band (same name/role as `DataViewListClassNames.groupHeader`).
* @deprecated Use `[data-slot="data-view-timeline-group-header"]` instead.
*/
groupHeader?: string;
};
export interface DataViewTimelineProps {
/** Multi-view name. When set, the renderer gates itself on the active view. */
name?: string;
/**
* Accessible name of the scroll region. The pane is keyboard-focusable
* (arrow keys scroll it natively), so screen readers announce this label on
* focus. Default 'Timeline'.
*/
'aria-label'?: string;
/** Optional view-scoped field override. Full replacement of root `fields` for this view's active session. */
fields?: DataViewField[];
/** Accessor key on the row yielding the start date. Rows with a missing/invalid value are skipped. */
startField: string;
/** Accessor key for the end date. Omitted → point markers; present → variable-width span cards. */
endField?: string;
/**
* Renders the card interior. The Timeline owns positioning (x from start,
* width from span, lane from packing, scroll); the consumer owns the card
* visual entirely — chrome, states, truncation, and the collapsed variant.
* Compose `` inside for Display Properties support.
*
* Keep the reference stable (define outside the component or wrap in
* `useCallback`) — cards are memoized against it, and an inline function
* defeats the memo so every visible card re-renders on each scroll frame.
* The same applies to `onRowClick` on the `DataView` root.
*/
renderCard: (row: Row, context: TimelineCardContext) => React.ReactNode;
/** Tick granularity of the time axis. Default 'day'. */
scale?: TimelineScale;
/** Pixel width of one `scale` unit — density/zoom override. */
unitWidth?: number;
/**
* Explicit time domain. Defaults to the data extent (plus today when shown)
* with padding. Either way, a domain narrower than the container is extended
* at the end so the axis and gridlines always fill the visible width.
*/
range?: [TimelineDateInput, TimelineDateInput];
/** Vertical "today" line + axis badge. `true` (default) uses the current date; a date pins it. */
today?: boolean | TimelineDateInput;
/** Additional full-height marker lines with axis badges. */
markers?: TimelineMarker[];
/** Vertical gridlines at every axis tick. Default true. */
showGridlines?: boolean;
/**
* Label every Nth `scale` unit on the axis, counted from the domain start
* (e.g. `2` on a day scale labels every other day). Labels never render
* closer than the collision floor, so a too-dense value degrades gracefully.
* Default: the densest interval whose labels fit.
*/
tickInterval?: number;
/**
* Draw a gridline every Nth `scale` unit, counted from the domain start.
* Independent of `tickInterval` and purely visual — cards, the today line,
* and the hover cursor still land on every unit. Default 1.
*/
gridlineInterval?: number;
/**
* Hover crosshair: a darker line snapped to the sub-interval (tick unit)
* under the pointer, with a date badge pinned to the axis. Default true.
*/
showCursorLine?: boolean;
/** Initial horizontal scroll target. Default 'today'. */
defaultScrollTo?: TimelineDateInput | 'today' | 'start' | 'end';
/**
* After a filter or search change, scroll the earliest matching card into
* view when no match intersects the current viewport — otherwise a filter
* whose results are off-screen leaves the user parked on empty canvas. A
* query change that keeps at least one card on screen doesn't move the
* view. Default true.
*/
scrollToResults?: boolean;
/** Fires (rAF-throttled) with the visible time range as the user scrolls or resizes. */
onVisibleRangeChange?: (range: [Date, Date]) => void;
/** Receives the imperative navigation handle (`scrollTo`, `getVisibleRange`). */
actionsRef?: React.RefObject;
/**
* 'auto' (default) packs non-overlapping cards into shared lanes (greedy
* interval scheduling); 'one-per-row' gives every row its own lane, in
* row-model (sorted) order; 'one-per-sort-value' gives every distinct value of
* the **sorted-by** field its own lane, packing that value's cards by date
* within it. All apply per group section when `group_by` is active — cards
* never share a lane across sections.
*
* Under 'one-per-sort-value' the active sort does double duty: it picks the field
* lanes are built from (sort by `priority` → a High lane, a Medium lane, a
* Low lane) and it orders them, so the Ordering control repositions lanes
* live. Lane order is the sort's order, so rank values that don't sort
* naturally (High/Medium/Low) with a numeric field and sort on that. Rows
* whose value is null, empty, or a non-primitive share one lane, placed last.
*/
lanePacking?: 'auto' | 'one-per-row' | 'one-per-sort-value';
/**
* Lane height in px. Default 66.
*
* Unvirtualized this is an estimate, same contract as `DataView.List`: cards
* render at their natural content height and are measured after paint, the
* estimate only seeding lane layout until real heights arrive, and each lane
* sizing to its tallest card.
*
* With `virtualized` it is exact. A culled card never mounts and so never
* measures, so measured lanes would resize under the user as they scroll —
* lanes take this value instead, and a card taller than it overflows its
* lane rather than growing it. Set it to your card's height.
*/
estimatedRowHeight?: number;
/** Vertical gap between lanes in px. Default 16. */
laneGap?: number;
/** Spans narrower than this (px) flip `context.collapsed` for `renderCard`. Default 60. */
minCardWidth?: number;
/**
* Assumed width (px) of point-marker cards (rows without `endField`) for
* lane packing. Point cards size to their content, so the packer can't know
* their width — set this to roughly the widest point card to prevent
* horizontal overlap within a lane. Default 120.
*/
estimatedPointWidth?: number;
/**
* Render only the cards and gridlines near the visible viewport, culling on
* both axes — a frame costs what's on screen rather than what's in the data.
* Recommended whenever the domain is long or rows are numerous.
*
* Lane heights become fixed to `estimatedRowHeight`; see the note there.
*/
virtualized?: boolean;
/**
* Render the group header band above each section when `group_by` is active.
* Same contract as `DataViewListProps.showGroupHeaders`: false hides the
* bands only — rows stay grouped into their sections. Default true.
*/
showGroupHeaders?: boolean;
/** @deprecated Style rendered parts by `[data-slot]` instead — see `DataViewTimelineClassNames`. */
classNames?: DataViewTimelineClassNames;
}
export type TableQueryUpdateFn = (query: InternalQuery) => InternalQuery;
export type DataViewContextType = {
table: Table;
/** Effective fields for the active view (= override fields if registered, else root fields). */
fields: DataViewField[];
/** Root-declared fields, unchanged by view overrides. */
rootFields: DataViewField[];
data: TData[];
isLoading?: boolean;
loadMoreData: () => void;
mode: DataViewMode;
defaultSort: DataViewSort;
tableQuery: InternalQuery;
totalRowCount?: number;
loadingRowCount?: number;
onDisplaySettingsReset: () => void;
updateTableQuery: (fn: TableQueryUpdateFn) => void;
onRowClick?: (row: TData) => void;
shouldShowFilters: boolean;
columnVisibility: VisibilityState;
setColumnVisibility: (value: Updater) => void;
rowSelection: RowSelectionState;
setRowSelection: (value: Updater) => void;
views?: ViewSpec[];
activeView?: string;
setActiveView: (view: string) => void;
/** Called by each renderer on mount to register its `fields` override for its `name`. Returns a cleanup function. */
registerFieldsForView: (name: string, fields: DataViewField[]) => () => void;
hasData: boolean;
hasActiveQuery: boolean;
isZeroState: boolean;
isEmptyState: boolean;
};
export interface ColumnData {
label: string;
id: string;
isVisible?: boolean;
}
interface SubRows<_T> {
}
export interface GroupedData extends SubRows {
label: string;
group_key: string;
subRows: T[];
count?: number;
showGroupCount?: boolean;
}
export declare const defaultGroupOption: {
id: string;
label: string;
};
export {};
//# sourceMappingURL=data-view.types.d.ts.map