import type { MenuProps } from '@planview/pv-uikit' import type { ColumnPreference } from './state' export type SortDirection = 'asc' | 'desc' export type ColumnField = Extract | string export type GridColumnBorder = 'left' | 'right' | 'both' export type GridTreeIndentSize = 'large' | 'small' export type HeaderCellParams = { columnId: ColumnField tabIndex: number } type FooterCellParams = { label: string | number columnId: ColumnField } export type ColumnHeaderConfig = { /** * Should the header use left or right alignment. */ align?: 'left' | 'right' /** * When this column is marked as a tree column, it will automatically gain an * expand/collapse control. In lazy-loaded situations, it may not be possible * to expand all given the server cost. This flag changes the behavior to only * allow collapsing of rows, not expanding */ treeCollapseOnly?: boolean /** * Renderer to be used for the header cell. This component is * responsible for rendering sort information. * * Read more about overriding the default renderer in the [useColumnHeader](https://planview-ds.github.io/react-pvds/?path=/docs/pv-grid-hooks-usecolumnheader--docs) documentation. */ Renderer?: (props: HeaderCellParams) => React.JSX.Element } export type ColumnFooterConfig< TDataModel extends GridRowData, TMetaModel extends GridRowMeta, > = { /** * Aggregation method to be used for collected data. */ aggregation?: | 'sum' | 'avg' | 'min' | 'max' | 'size' | ((values: Array) => number | Date) /** * The value is used for collecting value from each row and will be passed to aggregation function. * It is recommended to define a value getter on the `cell` -configuration and this attribute should only be used when there is need to override it. */ value?: ( props: CellValueParams ) => number | Date | null /** * Function to format the label of the cell. Defaults to value */ label?: (value: number | Date | null) => string /** * Renderer to be used for the footer cell. This component is * responsible for rendering aggregation information. */ Renderer?: (props: FooterCellParams) => React.JSX.Element } export type CellValueParams< TDataModel extends GridRowData, TMetaModel extends GridRowMeta, > = { columnId: ColumnField row: TDataModel rowMeta: TMetaModel } export type CellColSpanParams< TDataModel extends GridRowData, TMetaModel extends GridRowMeta, > = { columnId: ColumnField row: TDataModel rowMeta: TMetaModel sortedColumnIds: ColumnField[] } export type CellLabelParams = { columnId: ColumnField row: T rowMeta: TM value: any } export type CellParams = { value: any label: string rowId: TDataModel['id'] columnId: ColumnField tabIndex: number } export type EditorConfirmPayload = { continueEditing?: false | 'current' | 'next' | 'previous' } /** * This generic can be used to provide type information to the * onCellChange callback. You can [read more and see examples](https://planview-ds.github.io/react-pvds/?path=/docs/pv-grid-components-grid-cell-change-callback--docs) * in our documentation. * * @template TDataModel Model used to ensure rowId is of the correct type. * @template TKey String literal or a union of string literals that * reference existing properties on the model or * that match the ids of editable columns. * @template TNextVal nextValue type being passed from the editor * (assumed to also be the previousValue type if one is not provided). * @template TPrevVal - Value type of the previousValue if different * from the nextValue type. */ export type GridConfirmPayload< TDataModel extends GridRowData = any, TKey extends string = any, TNextVal = void, TPrevVal = TNextVal, > = { [P in TKey]: { nextValue: TNextVal extends void ? P extends keyof TDataModel ? TDataModel[P] : any : TNextVal previousValue: TPrevVal extends void ? P extends keyof TDataModel ? TDataModel[P] : any : TPrevVal columnId: P rowId: TDataModel['id'] } }[TKey] export type CellEditorParams = CellParams & { /** * Which mode is this? This is useful only with Hybrid Editors. It will always be "edit" for traditional editors. */ mode: 'navigation' | 'editing' /** * confirm callback */ onConfirm: (value: any, payload?: EditorConfirmPayload) => void /** * cancel callback */ onCancel: () => void } export type ColumnCellConfig< TDataModel extends GridRowData, TMetaModel extends GridRowMeta, > = { /** * The value is used for sorting, and will be used * for display if label or Renderer are not provided */ value?: (props: CellValueParams) => any /** * The display value of the cell. Defaults to value function * or property from data object. */ label?: (props: CellLabelParams) => string /** * Renderer to be used for the cell. This is a normal functional * component, so it can leverage hooks to get its data. */ Renderer?: (props: CellParams) => React.JSX.Element /** * @deprecated: this prop is not used anymore and will be ignored. Focus is always handled inline. * * If focus should be passed into and handled in the component level. * This is always `inline` while rendering Editor */ focusStrategy?: 'auto' | 'inline' /** * Can this cell value be edited. Can either be a boolean, or a function that returns a boolean */ editable?: | boolean | ((props: CellValueParams) => boolean) /** * Should the editor be used as both renderer and editor. Very few components fit into this category, * and they should not take user text input. An example of an always editing cell is a binary switch. * * Use 'always' to render the editor even when editable is false. * */ hybridEditor?: boolean | 'always' /** * Editor to be used for the cell. This is a normal functional * component, so it can leverage hooks to get its data. */ Editor?: (props: CellEditorParams) => React.JSX.Element /** * Should this cell span multiple columns. An empty array or an array with only this columnId will turn off column spanning. * When multiple ids are set or returned, then this renderer will be used regardless of which column is first. If cells * are not contiguous, and cells between the specified columns will become part of the colSpan. * * If this column is sticky, all columns in the colSpan must also be sticky on the same side (left or right). * * See [Column Spans](https://planview-ds.github.io/react-pvds/docs/pv-grid-components-grid-column-spans--docs) documentation for more information. */ colSpan?: | ColumnField[] | (( props: CellColSpanParams ) => ColumnField[]) } export type Column< TDataModel extends GridRowData, TMetaModel extends GridRowMeta = GridRowMeta, > = { /** * Unique column id. If no additional configuration for value or formatting are * applied, the field matching this id from the row will be displayed in the cells */ id: ColumnField /** * Translated visual column heading label */ label: string /** * Optional label of the column. This will be used for accessibility, labelling the column in case there is no visual label defined. * This will also be used in the context menu for column visibility. */ accessibleLabel?: string /** * Does this column allow the user to change the sort by clicking on it */ sortable?: boolean /** * Which type of sort should be used on this column * * fast: will work for some use cases, but is not locale aware nor * will it sort numbers contained in strings in numerical order. * * natural: this is a locale aware sort that is additionally configured * to sort numbers contained within strings in numerical order. The locale * used for natural sorting is inherited from react-intl IntlProvider context. */ sortStrategy?: 'fast' | 'natural' /** * When dealing with values that are `null`, `undefined` or `""` (empty string), this setting * allows you to control where they end up after sorting. * * - `low` (default) - Treats them as low values, causing them to show first in ascending sort and last in descending sort * - `high` - Treats them as high values, causing them to show last in ascending sort and first in descending sort * - `always-first` - Always show before sorted content * - `always-last` - Always show after sorted content */ sortEmptyAs?: GridColumnSortEmptyAs /** * Can this column be moved around by the user. * Defaults to true. */ movable?: boolean /** * Should the column stay on screen instead of scrolling off screen * * When turned on, this will automatically set `lockedLocation` to match and set `movable` to false */ sticky?: 'none' | 'left' | 'right' /** * Can this column be resized */ resizable?: boolean /** * Minimum width in pixels * Defaults to size.medium */ minWidth?: number /** * Default width in pixels */ width?: number /** * Maximum width in pixels * Defaults to Infinity */ maxWidth?: number /** * Should this be locked on the left or right of the table. This does not * make the column sticky, but does prevent other columns from being * reordered before (left) or after (right) this column. * * When turned on, this will automatically set `movable` to false */ lockedLocation?: 'none' | 'left' | 'right' /** * Configuration for the header, including custom rendering */ header?: ColumnHeaderConfig /** * Configuration of footer in a flat Grid, including aggregation */ footer?: ColumnFooterConfig /** * Configuration for the cells, including formatting and custom rendering */ cell?: ColumnCellConfig /** Is this the cell that should render expand/collapse controls? Only one column should be configured with this per grid */ tree?: boolean /** Should the tree indentation be large or small (Defaults to `large`) */ treeIndentSize?: GridTreeIndentSize /** * Should the column present a border. This will automatically set header, cell, and * footer border properties if they are set to auto or not configured. * * Use sparingly when needed to separate a few columns. Do not apply enough borders to make * the grid appear like a SpreadsheetGrid which adds vertical grid lines to indicate * a different set of expectations for the user like range cell selection and copy/paste. */ border?: GridColumnBorder /** * Is column is hideable. Defaults to true. * * * This flag will be overridden if `tree` is set to true. */ hideable?: boolean /** * Is column is hidden. * * This flag will be overridden if `tree` is set to true. */ hidden?: boolean } export type ColumnGroup = Pick< Column, 'resizable' | 'lockedLocation' | 'movable' | 'label' | 'sticky' > & { /** * List of ids of columns grouped together under this group. */ children: Column['id'][] /** * Unique column-group id. */ id: string /** Should the column group present a border. This will automatically override appropriate column borders to enforce this setting. Borders shown will apply to header, body, and footer cells. */ border?: GridColumnBorder } export type Sort = { /* Which column is being sorted */ columnId: string /* What is the sort direction */ direction: SortDirection } export type FilterMode = 'default' | 'highlight' export type GridRowIndexes = [start: number, end: number] export type GridRowId = string | number export type GridRowData = { id: GridRowId } /** @deprecated use GridRowData */ export type GridDataRow = GridRowData export type GridRowMeta = { /** * Tree and group both can act as expandable parents, * but differ when it comes to selection * */ type?: 'tree' | 'group' | 'leaf' /** * The tree is rebuilt as needed starting with the root ids. * Children point to other row ids */ children?: TDataModel['id'][] /** * When using drag and drop, if a row should not be draggable you can set * this to true. This is true by default for type = group */ preventDrag?: boolean } export type GridServerData< TDataModel extends GridRowData, TMetaModel extends GridRowMeta, > = { /** * Orderer array of row ids. Rows not yet loaded should use stable ids, but not map * to a record in the data collection. */ ids: TDataModel['id'][] /** * Map or object keyed by id with stable references to the actual row */ data: Map | Record /** * Map or object keyed by id with stable references to row metadata */ meta?: Map | Record /** * Optional callback that will be called frequently during scroll operations and during mount * You will want to throttle or debounce this method and only request missing data. */ fetch?: (indexes: GridRowIndexes) => void } export type GridDataProp< TDataModel extends GridRowData, TMetaModel extends GridRowMeta, > = TDataModel[] | GridServerData export type GridColumnSort = { columnId: string direction: SortDirection } export type GridColumnSortEmptyAs = | 'low' | 'high' | 'always-first' | 'always-last' export type GridPreferences = { columns: ColumnPreference[] } export type GridPreferencesAdapter = { load: () => | Promise | GridPreferences | undefined save: (prefs: GridPreferences) => void } export type GridActionsMenu< TDataModel extends GridRowData = any, TMetaModel extends GridRowMeta = any, > = | GridActionsMenuComponent | { Menu?: never MenuItems?: never /** @deprecated use MenuItems as a direct replacement, or Menu for more granular control */ Component?: GridActionsMenuComponent visible?: ( props: GridActionsMenuProps ) => boolean } | { Menu?: never MenuItems: GridActionsMenuComponent /** @deprecated use MenuItems as a direct replacement, or Menu for more granular control */ Component?: never visible?: ( props: GridActionsMenuProps ) => boolean } | { Menu: GridActionsMenuFullComponent MenuItems?: never /** @deprecated use MenuItems as a direct replacement, or Menu for more granular control */ Component?: never visible?: ( props: GridActionsMenuProps ) => boolean } export type GridActionsMenuComponent< TDataModel extends GridRowData = GridRowData, TMetaModel extends GridRowMeta = GridRowMeta, > = (props: GridActionsMenuProps) => React.JSX.Element export type GridActionsMenuFullComponent< TDataModel extends GridRowData = GridRowData, TMetaModel extends GridRowMeta = GridRowMeta, > = ( props: GridActionsMenuFullProps ) => React.JSX.Element export type GridRowDropInfo< TDataModel extends GridRowData = any, TMetaModel extends GridRowMeta = any, > = { draggedRowIds: TDataModel['id'][] draggedRows: { row: TDataModel rowMeta: TMetaModel }[] targetParentId: null | TDataModel['id'] targetParent: null | { row: TDataModel rowMeta: TMetaModel } // These are only non-null when mode is `default` and rows // are dropped at a specific index targetIndex: null | number resultIds: null | TDataModel['id'][] droppedAfterId: null | TDataModel['id'] } export type GridDragResponse = | { allowed: false message: string } | { allowed: true message?: never } export type GridRangeSelection = { from: { columnId: string; rowId: TDataModel['id'] } to: { columnId: string; rowId: TDataModel['id'] } } export type GridRowDragMode = 'default' | 'parent' export type GridRowDragConfig< TDataModel extends GridRowData, TMetaModel extends GridRowMeta, > = { /** * What type of drag and drop operations are enabled? * * default: drop items in specific locations (flat or tree) or under a new parent (tree grids only) * parent: drop items under a new parent, but not in a specific order */ mode?: GridRowDragMode /** * When dragging a row, one column is used as the content for the drag preview. By default, it will be the first * non-selection column that is not hidden. If you want to use a specific column, you can set this property * to the id of the column you want to use. */ previewColumnId?: ColumnField /** * Can one item be dragged over a leaf node to create a new parent node? * Only applicable in a tree grid */ enableLeafConversion?: boolean /** * Can more than one row be dragged at a time? Defaults to false */ multiple?: boolean /** * Callback that will be called as the drop target (parent or index) changes. If a drop is not allowed * you must return a localized message explaining why. */ canDrop?: ( info: GridRowDropInfo ) => GridDragResponse /** Callback that will be called when a drop is allowed and completed */ onDrop: (info: GridRowDropInfo) => void } export type GridProps< TDataModel extends GridRowData = any, TMetaModel extends GridRowMeta = any, > = { /** * Accessible label for this grid. Will be used to provide an aria-label. */ label?: string /** * Columns that are part of the configuration. You can provide * the data type for your rows to help with typescript hinting * on some of the configuration options. */ columns: Column[] /** * Column-grouping configuration. */ columnGroups?: ColumnGroup[] /** * When enabled the user can trigger a context menu by right-clicking on any column header. Within this menu, options are provided to show or hide specific columns. */ enableColumnVisibilityMenu?: boolean /** * Data used to generate rows. It can either be an array of objects * (must have an `id` field with a unique id for the row) or an object * with `ids`, `data` and `fetch` method. This second use will be documented * further in future releases. For early uses, please provide an array of objects. */ rows: GridDataProp /** * Default sort is used to initialize sorting but not * take full control of sorting state */ defaultSort?: GridColumnSort[] /** * Use this properly to fully control sorting. You should also add an * onSortChange callback to be notified when sorting should change */ sort?: GridColumnSort[] /** * Callback when sorting changes */ onSortChange?: (sort: GridColumnSort[]) => void /** * Should the grid handle sorting, or should it only display the indicators for sorting */ sortMode?: 'internal' | 'external' /** * Multi-column sorting. By default, the grid supports sorting on multiple columns at once. * If you are using `sortMode="internal"`, you shouldn't need to turn this off. But if you * are using server sorting or something else custom, you may need to turn this off. */ multiColumnSort?: boolean /** * When provided, will limit the data to these ids * * An empty array means there were no matches and the grid * should be empty. * * A null value instead means there are no filters applied * and the grid should show all rows. */ filteredIds?: TDataModel['id'][] | null /** * When provided, will be used to filter each row */ filter?: (row: TDataModel) => boolean /** * When provided, will render placeholder rows for the data * true will render 10 placeholder rows, otherwise will render the * number of rows specified */ loading?: boolean | number /** * Should rows that do not match the filter be hidden, * or should rows that do not match be dimmed * * Defaults to 'show' */ filterMode?: FilterMode /** * A set of selected ids. If your selection is tied to the URL (enabling a user to use * browser history Back/Forward), you may want to instead pass an object: * * ```tsx * { ids: selection, autoScroll: true } * ``` * * This will ensure the final id in the selection is scrolled into view if needed * when the selection changes programmatically. User initiated selection changes * will not cause it to scroll. */ selection?: | Set | { ids: Set; autoScroll?: boolean } /** * When mode is set to single, clicking anywhere * on a row will cause it to be selected and any other row will * be deselected. Checkboxes will not be rendered on the left. * * When set to 'multi', clicking anywhere on a row will cause it to be * selected and any other row will be deselected. Clicking on a checkbox * will instead add/remove the row from the selection. Clicking the checkbox in the * header will select all rows. * * `multi-hidden` behaves the same as `multi` except that the selection column * is not added to the grid. This should only be used when presenting two grids * side by side and this grid is in a secondary position. */ selectionMode?: 'none' | 'single' | 'multi' | 'multi-hidden' /** * When rows are selected or deselected, this is run * with an updated set of ids. * * Question: does the user need a way to prevent a row from being updated? */ onSelectionChange?: (selection: Set) => void /** * A set of tree or group rows that should be expanded to render their child rows */ expandedRows?: Set /** * When tree or group rows are expanded or collapsed, this is run * with an updated set of ids. */ onExpandedRowsChange?: ( expandedRows: Set, context: { expanded: Set collapsed: Set } ) => void /** * Should the rows be size medium with a separator line, or size * small with no separator */ rowHeight?: 'small' | 'medium' /** * Callback called whenever a row is clicked. Will not be called in cases where the click would take * an action other than selecting the row. */ onRowClick?: (rowId: TDataModel['id']) => void /** * A preferences adapter can be used to save and load column settings like width, visibility and order. * These values can be persisted to local storage, a database, etc. * * The adapter must provide two methods: `load` and `save` * * - `save` will be called when a user finishes resizing or reordering a column * - `load` will be called when grid is initialized and whenever the adapter object changes. * * Be sure to have a consistent instance handed to the prop to avoid unnecessary loads. */ preferencesAdapter?: GridPreferencesAdapter /** * Called whenever an edit action is completed on a cell. Since the representation * of data between value and editor may differ, this may be called even when nothing has changed * as a result of the edit. * * See [Cell Change Callback](https://planview-ds.github.io/react-pvds/?path=/docs/pv-grid-components-grid-cell-change-callback--docs) for * details on how to handle types. */ onCellChange?: (payload: GridConfirmPayload) => void /** * In situations where the grid renders no data, use this property to present more context to the user * by passing in `EmptyState`, `EmptyStateError` or `EmptyStateFilter` components from `@planview/pv-uikit`. * * Will only be shown when there are no rows to render (no data or all data excluded by filters) */ emptyContent?: React.ReactNode /** * When provided, a menu will be available on right-click of a row and shown * as a triple dot menu for the row. The contents of the menu can be customized per row, * but it is recommended the same options be shown for each row, with actions * that don't apply to a particular row marked as disabled. * * This property (or the Component sub-property) should point to a component that * returns a fragment with children suitable for use in our Menu component such as ListItem, * ListGroup, and SubMenu * * See [Actions Menu](https://planview-ds.github.io/react-pvds/?path=/docs/pv-grid-components-grid-actions-menu--docs) page for * more information and examples. */ actionsMenu?: GridActionsMenu /** * Configuration for enabling drag and drop and keyboard reordering of rows * * See the [Row drag](https://planview-ds.github.io/react-pvds/?path=/docs/pv-grid-components-grid-row-drag--docs) documentation page for more details. */ rowDrag?: GridRowDragConfig } export type GridActionsMenuProps< TDataModel extends GridRowData = GridRowData, TMetaModel extends GridRowMeta = GridRowMeta, > = { row: TDataModel rowMeta: TMetaModel } /** @deprecated use GridActionsMenuProps */ export type GridActionsMenuParams< TDataModel extends GridRowData, TMetaModel extends GridRowMeta, > = GridActionsMenuProps export type GridActionsMenuFullProps< TDataModel extends GridRowData = GridRowData, TMetaModel extends GridRowMeta = GridRowMeta, > = { row: TDataModel rowMeta: TMetaModel menuProps: Omit } export type GridStateProps = { actionsMenuPresent: boolean footerEnabled: boolean selection?: Set selectionMode?: Exclude< GridProps['selectionMode'], 'multi-hidden' > rowDrag: Pick< GridRowDragConfig, | 'mode' | 'enableLeafConversion' | 'multiple' | 'canDrop' | 'previewColumnId' > & { enabled: boolean } hideHeaders?: boolean initialFocusMode?: 'cell' | 'row' loopHorizontally?: boolean spreadsheet?: boolean rangeSelection?: GridRangeSelection | null } & Omit< GridProps, | 'label' | 'filterIds' | 'selectionMode' | 'selection' | 'onSelectionChange' | 'onExpandedRowsChange' | 'onSortChange' | 'rowHeight' | 'onCellChange' | 'emptyContent' | 'actionsMenu' | 'rowDrag' >