diff --git a/examples/src/pages/_document.page.tsx b/examples/src/pages/_document.page.tsx index bad287368..246230371 100644 --- a/examples/src/pages/_document.page.tsx +++ b/examples/src/pages/_document.page.tsx @@ -10,7 +10,7 @@ export default function Document() { /> > = { + age: { + field: 'age', + header: 'Age', + type: 'number', + defaultWidth: 100, + renderValue: ({ value }) => value, + }, + salary: { + field: 'salary', + type: 'number', + defaultWidth: 210, + }, + currency: { field: 'currency', header: 'Currency', defaultWidth: 100 }, + preferredLanguage: { + field: 'preferredLanguage', + header: 'Programming Language', + }, + + canDesign: { + defaultWidth: 135, + field: 'canDesign', + header: 'Design Skills', + renderValue: ({ value }) => { + return ( +
+ + {value === null ? 'Some' : value === 'yes' ? 'Yes' : 'No'} +
+ ); + }, + }, + country: { + field: 'country', + header: 'Country', + }, + firstName: { field: 'firstName', header: 'First Name' }, + stack: { field: 'stack', header: 'Stack' }, + + city: { + field: 'city', + header: 'City', + renderHeader: ({ column }) => `${column.computedVisibleIndex} City`, + }, +}; + +export default function App() { + const groupBy: DataSourceGroupBy[] = React.useMemo( + () => [ + { + field: 'country', + }, + { field: 'stack' }, + ], + [], + ); + + return ( + data={dataSource} primaryKey="id" groupBy={groupBy}> +

Your DataGrid

+ + + ); +} + +function AppGrid() { + const { dataArray } = useDataSourceState(); + return ( +
+ {dataArray.length} rows + + groupRenderStrategy="single-column" + defaultActiveRowIndex={0} + domProps={{ + style: { + flex: 1, + }, + }} + columns={columns} + columnDefaultWidth={150} + /> +
+ ); +} + +const dataSource = () => { + return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers100') + .then((r) => r.json()) + .then((data: Developer[]) => data); +}; diff --git a/examples/src/pages/tests/table/full-demo2.page.tsx b/examples/src/pages/tests/table/full-demo2.page.tsx new file mode 100644 index 000000000..8404ae287 --- /dev/null +++ b/examples/src/pages/tests/table/full-demo2.page.tsx @@ -0,0 +1,328 @@ +import { + InfiniteTable, + DataSource, + GroupRowsState, + InfiniteTablePropColumnTypes, + DataSourcePropRowSelection_MultiRow, + InfiniteTableColumn, + InfiniteTableColumnRenderValueParam, + DataSourcePropAggregationReducers, + DataSourceGroupBy, + components, + DataSourcePropFilterValue, +} from '@infinite-table/infinite-react'; +import * as React from 'react'; +import { useState } from 'react'; + +const { CheckBox } = components; + +type Developer = { + id: number; + firstName: string; + lastName: string; + country: string; + city: string; + currency: string; + preferredLanguage: string; + stack: string; + canDesign: 'yes' | 'no'; + hobby: string; + salary: number; + age: number; +}; + +const avgReducer = { + initialValue: 0, + reducer: (acc: number, sum: number) => acc + sum, + done: (value: number, arr: any[]) => + arr.length ? Math.floor(value / arr.length) : 0, +}; +const aggregationReducers: DataSourcePropAggregationReducers = { + salary: { + field: 'salary', + + ...avgReducer, + }, + age: { + field: 'age', + ...avgReducer, + }, + currency: { + field: 'currency', + initialValue: new Set(), + reducer: (acc: Set, value: string) => { + acc.add(value); + return acc; + }, + done: (value: Set) => { + return value.size > 1 ? 'Mixed' : value.values().next().value; + }, + }, + canDesign: { + field: 'canDesign', + + initialValue: false, + reducer: (acc: boolean | null, value: 'yes' | 'no') => { + if (acc === null) { + return acc; + } + if (acc === false && value === 'yes') { + return null; + } + if (acc === true && value === 'no') { + return null; + } + return acc; + }, + }, +}; + +const flags = { + 'United States': '๐Ÿ‡บ๐Ÿ‡ธ', + Canada: '๐Ÿ‡จ๐Ÿ‡ฆ', + France: '๐Ÿ‡ซ๐Ÿ‡ท', + Germany: '๐Ÿ‡ฉ๐Ÿ‡ช', + 'United Kingdom': '๐Ÿ‡ฌ๐Ÿ‡ง', + 'South Africa': '๐Ÿ‡ฟ๐Ÿ‡ฆ', + 'New Zealand': '๐Ÿ‡ณ๐Ÿ‡ฟ', + Sweden: '๐Ÿ‡ธ๐Ÿ‡ช', + China: '๐Ÿ‡จ๐Ÿ‡ณ', + Brazil: '๐Ÿ‡ง๐Ÿ‡ท', + Turkey: '๐Ÿ‡น๐Ÿ‡ท', + Italy: '๐Ÿ‡ฎ๐Ÿ‡น', + India: '๐Ÿ‡ฎ๐Ÿ‡ณ', + Indonesia: '๐Ÿ‡ฎ๐Ÿ‡ฉ', + Japan: '๐Ÿ‡ฏ๐Ÿ‡ต', + Argentina: '๐Ÿ‡ฆ๐Ÿ‡ท', + 'Saudi Arabia': '๐Ÿ‡ธ๐Ÿ‡ฆ', + Mexico: '๐Ÿ‡ฒ๐Ÿ‡ฝ', + 'United Arab Emirates': '๐Ÿ‡ฆ๐Ÿ‡ช', +}; +function getColumns(): Record> { + return { + age: { + field: 'age', + header: 'Age', + type: 'number', + defaultWidth: 100, + renderValue: ({ value }) => value, + }, + salary: { + field: 'salary', + type: 'number', + defaultWidth: 210, + }, + currency: { field: 'currency', header: 'Currency', defaultWidth: 100 }, + preferredLanguage: { + field: 'preferredLanguage', + header: 'Programming Language', + }, + + canDesign: { + defaultWidth: 135, + field: 'canDesign', + header: 'Design Skills', + renderValue: ({ value }) => { + return ( +
+ + {value === null ? 'Some' : value === 'yes' ? 'Yes' : 'No'} +
+ ); + }, + }, + country: { + field: 'country', + header: 'Country', + renderValue: ({ value }) => { + return ( + + {(flags as any)[value] || null} {value} + + ); + }, + }, + firstName: { field: 'firstName', header: 'First Name' }, + stack: { field: 'stack', header: 'Stack' }, + + city: { + field: 'city', + header: 'City', + renderHeader: ({ column }) => `${column.computedVisibleIndex} City`, + }, + }; +} + +// 250+100+210+100+150+135+150+150+150+150 +// โ†’ 123.456,789 +const groupColumn: InfiniteTableColumn = { + header: 'Grouping', + field: 'firstName', + defaultWidth: 250, + renderSelectionCheckBox: true, + defaultFilterable: false, + + // in this function we have access to collapsed info + // and grouping info about the current row - see rowInfo.groupBy + renderValue: ({ + value, + rowInfo, + }: InfiniteTableColumnRenderValueParam) => { + if (!rowInfo.isGroupRow) { + return `${rowInfo.indexInAll} ${rowInfo.id} ${value}`; + } + const groupBy = rowInfo.groupBy || []; + const collapsed = rowInfo.collapsed; + const currentGroupBy = groupBy[groupBy.length - 1]; + + if (currentGroupBy?.field === 'age') { + return `๐Ÿฅณ ${value}${collapsed ? ' ๐Ÿคทโ€โ™‚๏ธ' : ''}`; + } + + return `${rowInfo.indexInAll} ${value}`; + }, +}; + +const defaultGroupRowsState = new GroupRowsState({ + //make all groups collapsed by default + collapsedRows: true, + expandedRows: [ + ['United States'], + ['United States', 'backend'], + ['France'], + ['Turkey'], + ], +}); + +const columnTypes: InfiniteTablePropColumnTypes = { + number: { + align: 'end', + style: () => { + return {}; + }, + renderValue: ({ value, data, rowInfo }) => { + return new Intl.NumberFormat('en-US', { + style: 'currency', + currency: + rowInfo.isGroupRow && rowInfo.data?.currency === 'Mixed' + ? 'USD' + : data?.currency || 'USD', + }).format(value); + }, + }, +}; + +const defaultRowSelection: DataSourcePropRowSelection_MultiRow = { + selectedRows: [['India'], ['France'], ['Turkey']], + deselectedRows: [['United States', 'frontend']], + defaultSelection: false, +}; + +const defaultFilterValue: DataSourcePropFilterValue = [ + { + field: 'age', + filter: { + operator: 'gt', + type: 'number', + value: null, + }, + }, +]; + +const domProps = { + style: { + minHeight: '50vh', + width: '100vw', + // width: 575, + marginTop: 200, + }, + className: 'infinite-theme-mode--light', +}; + +export default function App() { + const [{ min, max }, setMinMax] = useState({ min: 0, max: 0 }); + const columns = React.useMemo(() => { + const cols = getColumns(); + + if (cols.salary) { + cols.salary.render = ({ renderBag }) => { + return ( +
+ {renderBag.all} +
+ ); + }; + } + + return cols; + }, [min, max]); + + const groupBy: DataSourceGroupBy[] = React.useMemo( + () => [ + { + field: 'country', + }, + { field: 'stack' }, + ], + [], + ); + + return ( + + data={dataSource} + primaryKey="id" + defaultFilterValue={defaultFilterValue} + filterMode="local" + useGroupKeysForMultiRowSelection + defaultRowSelection={defaultRowSelection} + onDataArrayChange={(data) => { + const min = Math.min(...data.map((data) => data.salary ?? 0)); + const max = Math.max(...data.map((data) => data.salary ?? 0)); + + setMinMax({ min, max }); + }} + defaultGroupRowsState={defaultGroupRowsState} + aggregationReducers={aggregationReducers} + groupBy={groupBy} + > + + groupRenderStrategy="single-column" + defaultColumnPinning={{ + 'group-by': 'start', + age: 'start', + }} + domProps={domProps} + defaultActiveRowIndex={0} + groupColumn={groupColumn} + licenseKey={process.env.NEXT_PUBLIC_INFINITE_LICENSE_KEY} + columns={columns} + columnTypes={columnTypes} + columnDefaultWidth={150} + /> + + ); +} + +const dataSource = () => { + return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers100') + .then((r) => r.json()) + .then((data: Developer[]) => data); +}; diff --git a/examples/src/pages/tests/table/theme/shadcn.page.tsx b/examples/src/pages/tests/table/theme/shadcn.page.tsx index 07bde3812..c1c71bef8 100644 --- a/examples/src/pages/tests/table/theme/shadcn.page.tsx +++ b/examples/src/pages/tests/table/theme/shadcn.page.tsx @@ -87,8 +87,9 @@ export default function Example() { display: 'flex', flexFlow: 'row', }} + className="infinite-theme-mode--light" > -
+
primaryKey="OrderId" data={orders.slice(0, 10)} diff --git a/source/src/components/InfiniteTable/components/InfiniteTableHeader/ResizeHandle/ResizeHandle.css.ts b/source/src/components/InfiniteTable/components/InfiniteTableHeader/ResizeHandle/ResizeHandle.css.ts index e6f0efccd..07f8ebc38 100644 --- a/source/src/components/InfiniteTable/components/InfiniteTableHeader/ResizeHandle/ResizeHandle.css.ts +++ b/source/src/components/InfiniteTable/components/InfiniteTableHeader/ResizeHandle/ResizeHandle.css.ts @@ -139,6 +139,7 @@ export const ResizeHandleDraggerClsRecipe = recipe({ variants: { computedPinned: 'start', computedLastInCategory: true, + computedFirstInCategory: false, }, style: { right: 0, @@ -148,6 +149,7 @@ export const ResizeHandleDraggerClsRecipe = recipe({ variants: { computedPinned: 'end', computedFirstInCategory: true, + computedLastInCategory: false, }, style: { right: 'unset', diff --git a/source/src/components/InfiniteTable/types/InfiniteTableProps.ts b/source/src/components/InfiniteTable/types/InfiniteTableProps.ts index b20c36041..d825b8182 100644 --- a/source/src/components/InfiniteTable/types/InfiniteTableProps.ts +++ b/source/src/components/InfiniteTable/types/InfiniteTableProps.ts @@ -171,6 +171,7 @@ export type InfiniteTableColumnType = { shouldAcceptEdit?: InfiniteTableColumn['shouldAcceptEdit']; style?: InfiniteTableColumn['style']; headerStyle?: InfiniteTableColumn['headerStyle']; + className?: InfiniteTableColumn['className']; headerClassName?: InfiniteTableColumn['headerClassName']; }; export type InfiniteTablePropColumnTypesMap = Map< diff --git a/source/src/components/InfiniteTable/utils/getComputedColumns.ts b/source/src/components/InfiniteTable/utils/getComputedColumns.ts index 5e3aa24f8..084c6aa96 100644 --- a/source/src/components/InfiniteTable/utils/getComputedColumns.ts +++ b/source/src/components/InfiniteTable/utils/getComputedColumns.ts @@ -636,6 +636,7 @@ export const getComputedColumns = ({ renderValue: colType.renderValue, render: colType.render, style: colType.style, + className: colType.className, contentFocusable: colType.contentFocusable, renderMenuIcon: colType.renderMenuIcon, diff --git a/source/src/components/InfiniteTable/vars-balsam-dark.css.ts b/source/src/components/InfiniteTable/vars-balsam-dark.css.ts index 03f5c9ad3..3a9218792 100644 --- a/source/src/components/InfiniteTable/vars-balsam-dark.css.ts +++ b/source/src/components/InfiniteTable/vars-balsam-dark.css.ts @@ -5,6 +5,7 @@ const borderColor = `#5c5c5c`; export const BalsamDarkVars = { ...BalsamLightVars, + [ThemeVars.components.Cell.borderColor]: borderColor, [ThemeVars.themeMode]: 'dark', [ThemeVars.background]: '#2d3436', @@ -17,7 +18,8 @@ export const BalsamDarkVars = { [ThemeVars.color.color]: '#f5f5f5', - [ThemeVars.components.Cell.borderTop]: `1px solid ${borderColor}`, + [ThemeVars.components.Cell + .borderTop]: `1px solid ${ThemeVars.components.Cell.borderColor}`, [ThemeVars.components.HeaderCell.background]: '#1c1c1c', [ThemeVars.components.HeaderCell .hoverBackground]: `color-mix(in srgb, ${ThemeVars.components.HeaderCell.background}, white 2%)`, diff --git a/source/src/components/InfiniteTable/vars-common.css.ts b/source/src/components/InfiniteTable/vars-common.css.ts index bb2c66f4a..713d92529 100644 --- a/source/src/components/InfiniteTable/vars-common.css.ts +++ b/source/src/components/InfiniteTable/vars-common.css.ts @@ -29,4 +29,5 @@ export const CommonThemeVars = { [ThemeVars.components.LoadMask.color]: ThemeVars.components.Cell.color, [ThemeVars.components.Row.background]: ThemeVars.background, + [ThemeVars.components.GroupingToolbar.background]: ThemeVars.background, }; diff --git a/source/src/components/InfiniteTable/vars-default-light.css.ts b/source/src/components/InfiniteTable/vars-default-light.css.ts index 0b158afaf..92411cd1a 100644 --- a/source/src/components/InfiniteTable/vars-default-light.css.ts +++ b/source/src/components/InfiniteTable/vars-default-light.css.ts @@ -1,5 +1,6 @@ import { fallbackVar } from '@vanilla-extract/css'; import { CSS_LOADED_VALUE, ThemeVars } from './vars.css'; +import { CommonThemeVars } from './vars-common.css'; // declare here vars that default to other vars const LoadMaskVars = { [ThemeVars.components.LoadMask.textBackground]: 'rgba(255,255,255,0.8)', @@ -28,15 +29,13 @@ const HeaderCellVars = { [ThemeVars.components.HeaderCell.resizeHandleConstrainedHoverBackground]: ThemeVars.color.error, [ThemeVars.components.HeaderCell.background]: '#ededed', - [ThemeVars.components.HeaderCell.borderRight]: - ThemeVars.components.Cell.border, [ThemeVars.components.HeaderCell.filterEditorBackground]: ThemeVars.components.Row.background, [ThemeVars.components.HeaderCell .filterEditorBorder]: `${ThemeVars.components.Cell.border}`, [ThemeVars.components.HeaderCell.filterEditorFocusBorderColor]: ThemeVars.color.accent, - [ThemeVars.components.HeaderCell.border]: ThemeVars.components.Cell.border, + [ThemeVars.components.HeaderCell.filterEditorColor]: `currentColor`, [ThemeVars.components.HeaderCell.filterEditorBorderRadius]: ThemeVars.borderRadius, @@ -92,30 +91,26 @@ const CellVars = { [ThemeVars.components.Cell.flashingOverlayZIndex]: -1, [ThemeVars.components.Cell .horizontalLayoutColumnReorderDisabledPageOpacity]: 0.3, - [ThemeVars.components.Cell.flashingBackground]: ThemeVars.color.accent, - [ThemeVars.components.Cell.flashingUpBackground]: ThemeVars.color.success, - [ThemeVars.components.Cell.flashingDownBackground]: ThemeVars.color.error, + [ThemeVars.components.Cell .padding]: `${ThemeVars.spacing[2]} ${ThemeVars.spacing[3]}`, + [ThemeVars.components.Cell.borderColor]: '#c6c6c6', [ThemeVars.components.Cell - .border]: `${ThemeVars.components.Cell.borderWidth} solid #c6c6c6`, + .border]: `${ThemeVars.components.Cell.borderWidth} solid ${ThemeVars.components.Cell.borderColor}`, [ThemeVars.components.Cell .borderLeft]: `${ThemeVars.components.Cell.borderWidth} solid transparent`, [ThemeVars.components.Cell .borderRight]: `${ThemeVars.components.Cell.borderWidth} solid transparent`, [ThemeVars.components.Cell - .pinnedBorder]: `${ThemeVars.components.Cell.borderWidth} solid #2a323d`, + .pinnedBorder]: `${ThemeVars.components.Cell.border}`, [ThemeVars.components.Cell.borderInvisible]: 'none', [ThemeVars.components.Cell.borderRadius]: ThemeVars.spacing[2], [ThemeVars.components.Cell.reorderEffectDuration]: '.2s', - // [ThemeVars.components.Cell.selectedCellBorder]: '2px solid red', [ThemeVars.components.Cell.selectedBorderStyle]: 'solid', [ThemeVars.components.Cell.activeBorderStyle]: 'dashed', [ThemeVars.components.Cell.activeBorderWidth]: '1px', - // [ThemeVars.components.Cell.activeBorderColor]: '#4d95d7', - // [ThemeVars.components.Cell.activeBackground]: 'rgba(77, 149, 215, 0.25)', [ThemeVars.components.Cell.activeBackgroundAlpha]: '0.25', [ThemeVars.components.Cell.activeBackgroundAlphaWhenTableUnfocused]: '0.1', @@ -156,8 +151,6 @@ const ExpandCollapseIconVars = { }; const RowVars = { - [ThemeVars.components.Row.background]: ThemeVars.background, - [ThemeVars.components.Row.oddBackground]: '#f6f6f6', [ThemeVars.components.Row.disabledOpacity]: '0.5', [ThemeVars.components.Row.disabledBackground]: '#eeeeee', @@ -179,20 +172,14 @@ const RowDetailsVars = { }; const MenuVars = { - [ThemeVars.components.Menu.background]: ThemeVars.background, - [ThemeVars.components.Menu.color]: ThemeVars.components.Cell.color, [ThemeVars.components.Menu.separatorColor]: 'currentColor', [ThemeVars.components.Menu.padding]: ThemeVars.spacing[3], [ThemeVars.components.Menu.cellPaddingVertical]: ThemeVars.spacing[3], [ThemeVars.components.Menu.cellPaddingHorizontal]: ThemeVars.spacing[3], [ThemeVars.components.Menu.cellMarginVertical]: ThemeVars.spacing[0], - [ThemeVars.components.Menu.itemDisabledBackground]: - ThemeVars.components.Menu.background, + [ThemeVars.components.Menu.itemDisabledOpacity]: 0.5, - [ThemeVars.components.Menu.itemActiveBackground]: - ThemeVars.components.Row.hoverBackground, - [ThemeVars.components.Menu.itemPressedBackground]: - ThemeVars.components.Row.hoverBackground, + [ThemeVars.components.Menu.itemActiveOpacity]: 0.9, [ThemeVars.components.Menu.itemPressedOpacity]: 1, [ThemeVars.components.Menu.borderRadius]: ThemeVars.spacing[2], @@ -235,6 +222,8 @@ export const LightVars = { [ThemeVars.background]: 'white', [ThemeVars.minHeight]: '100px', + ...CommonThemeVars, + ...SelectionCheckBoxVars, ...MenuVars, ...RowDetailsVars, diff --git a/source/src/components/InfiniteTable/vars.css.ts b/source/src/components/InfiniteTable/vars.css.ts index 00a68f3b2..cd9d137ef 100644 --- a/source/src/components/InfiniteTable/vars.css.ts +++ b/source/src/components/InfiniteTable/vars.css.ts @@ -184,6 +184,7 @@ export const ThemeVars = createGlobalThemeContract( flashingDownBackground: 'flashing-down-background', padding: 'cell-padding', borderWidth: 'cell-border-width', + borderColor: 'cell-border-color', /** * Specifies the border for cells. * diff --git a/www/content/blog/2023/01/16/infinite-table-is-here.page.md b/www/content/blog/2023/01/16/infinite-table-is-here.page.md index 339920807..3c4452cbf 100644 --- a/www/content/blog/2023/01/16/infinite-table-is-here.page.md +++ b/www/content/blog/2023/01/16/infinite-table-is-here.page.md @@ -113,7 +113,7 @@ It helps you display huge datasets and get the most out of your data by providin - [ lazy loading](/docs/learn/working-with-data/lazy-loading) - [ keyboard navigation](/docs/learn/keyboard-navigation/navigating-cells) - [ fixed and flexible columns](/docs/learn/columns/fixed-and-flexible-size) -- [ column grouping](/docs/learn/column-groups) +- [ column grouping](/docs/learn/columns/column-grouping) - [ theming](/docs/learn/theming) - ... and many others diff --git a/www/content/blog/2024/06/13/how-to-use-excel-like-editing-in-datagrid.page.md b/www/content/blog/2024/06/13/how-to-use-excel-like-editing-in-datagrid.page.md index 1ed61ebe8..7f7b65ab0 100644 --- a/www/content/blog/2024/06/13/how-to-use-excel-like-editing-in-datagrid.page.md +++ b/www/content/blog/2024/06/13/how-to-use-excel-like-editing-in-datagrid.page.md @@ -32,7 +32,7 @@ import { } ``` -The `instantEdit` [keyboard shorcut](/docs/learn/keyboard-navigation/keyboard-shorcuts) is configured (by default) to respond to any key (via the special `*` identifier which matches anything) and will start editing the cell as soon as a key is pressed. This behavior is the same as in Excel, Google Sheets, Numbers or other spreadsheet software. +The `instantEdit` [keyboard shorcut](/docs/learn/keyboard-navigation/keyboard-shortcuts) is configured (by default) to respond to any key (via the special `*` identifier which matches anything) and will start editing the cell as soon as a key is pressed. This behavior is the same as in Excel, Google Sheets, Numbers or other spreadsheet software. diff --git a/www/content/blog/2025/10/09/customizing-cells.page.tsx b/www/content/blog/2025/10/09/customizing-cells.page.tsx new file mode 100644 index 000000000..721c736e5 --- /dev/null +++ b/www/content/blog/2025/10/09/customizing-cells.page.tsx @@ -0,0 +1,100 @@ +import { + InfiniteTable, + DataSource, + InfiniteTableColumn, + components, + InfiniteTableProps, +} from '@infinite-table/infinite-react'; +import * as React from 'react'; + +const { CheckBox } = components; + +type Developer = { + id: number; + firstName: string; + lastName: string; + country: string; + city: string; + currency: string; + preferredLanguage: string; + stack: string; + canDesign: 'yes' | 'no'; + hobby: string; + salary: number; + age: number; +}; + +const columns: Record> = { + age: { + field: 'age', + header: 'Age', + type: 'number', + defaultWidth: 100, + renderValue: ({ value }) => value, + }, + salary: { + field: 'salary', + type: 'number', + defaultWidth: 150, + }, + currency: { field: 'currency', header: 'Currency', defaultWidth: 120 }, + preferredLanguage: { + field: 'preferredLanguage', + header: 'Programming Language', + }, + + canDesign: { + defaultWidth: 135, + field: 'canDesign', + header: 'Design Skills', + renderValue: ({ value }) => { + return ( +
+ + {value === null ? 'Some' : value === 'yes' ? 'Yes' : 'No'} +
+ ); + }, + }, + country: { + field: 'country', + header: 'Country', + }, + firstName: { field: 'firstName', header: 'First Name' }, + stack: { field: 'stack', header: 'Stack' }, + + city: { + field: 'city', + header: 'City', + renderHeader: ({ column }) => `${column.computedVisibleIndex} City`, + }, +}; + +const columnTypes: InfiniteTableProps['columnTypes'] = { + default: {}, +}; + +export default function App() { + return ( + data={dataSource} primaryKey="id"> + + columnTypes={columnTypes} + columns={columns} + columnDefaultWidth={150} + > + + ); +} + +const dataSource = () => { + return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers100') + .then((r) => r.json()) + .then((data: Developer[]) => data); +}; diff --git a/www/content/blog/2025/10/09/customizing-structure.page.tsx b/www/content/blog/2025/10/09/customizing-structure.page.tsx new file mode 100644 index 000000000..1302365e5 --- /dev/null +++ b/www/content/blog/2025/10/09/customizing-structure.page.tsx @@ -0,0 +1,135 @@ +import { + InfiniteTable, + DataSource, + InfiniteTableColumn, + DataSourceGroupBy, + components, + useDataSourceState, +} from '@infinite-table/infinite-react'; +import * as React from 'react'; + +const { CheckBox } = components; + +type Developer = { + id: number; + firstName: string; + lastName: string; + country: string; + city: string; + currency: string; + preferredLanguage: string; + stack: string; + canDesign: 'yes' | 'no'; + hobby: string; + salary: number; + age: number; +}; + +const columns: Record> = { + age: { + field: 'age', + header: 'Age', + type: 'number', + defaultWidth: 100, + renderValue: ({ value }) => value, + }, + salary: { + field: 'salary', + type: 'number', + defaultWidth: 210, + }, + currency: { field: 'currency', header: 'Currency', defaultWidth: 100 }, + preferredLanguage: { + field: 'preferredLanguage', + header: 'Programming Language', + }, + + canDesign: { + defaultWidth: 135, + field: 'canDesign', + header: 'Design Skills', + renderValue: ({ value }) => { + return ( +
+ + {value === null ? 'Some' : value === 'yes' ? 'Yes' : 'No'} +
+ ); + }, + }, + country: { + field: 'country', + header: 'Country', + }, + firstName: { field: 'firstName', header: 'First Name' }, + stack: { field: 'stack', header: 'Stack' }, + + city: { + field: 'city', + header: 'City', + renderHeader: ({ column }) => `${column.computedVisibleIndex} City`, + }, +}; + +export default function App() { + const groupBy: DataSourceGroupBy[] = React.useMemo( + () => [ + { + field: 'country', + }, + { field: 'stack' }, + ], + [], + ); + + return ( +
+ + data={dataSource} + primaryKey="id" + defaultGroupBy={groupBy} + > +

Your DataGrid

+ + +
+ ); +} + +function AppGrid() { + const { dataArray } = useDataSourceState(); + return ( +
+ + groupRenderStrategy="single-column" + defaultActiveRowIndex={0} + columns={columns} + columnDefaultWidth={150} + > +
+
+ +
+ Showing {dataArray.length} rows +
+ +
+ +
+ +
+ ); +} + +const dataSource = () => { + return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers100') + .then((r) => r.json()) + .then((data: Developer[]) => data); +}; diff --git a/www/content/blog/2025/10/09/customizing-your-datagrid-component-with-tailwind.page.md b/www/content/blog/2025/10/09/customizing-your-datagrid-component-with-tailwind.page.md new file mode 100644 index 000000000..c68479c32 --- /dev/null +++ b/www/content/blog/2025/10/09/customizing-your-datagrid-component-with-tailwind.page.md @@ -0,0 +1,73 @@ +--- +title: Customizing your DataGrid component with Tailwind CSS +description: Find out how to customize your DataGrid component to fit your app needs, using Tailwind CSS +date: 2025-10-09 +author: radu +draft: true +tags: theming, customizing +thumbnail: ./customizing-your-datagrid-component.png +--- + +We haven't spoken about this very much, but Infinite Table does offer you very powerful ways to customize the structure of your component. After all, we're a React-first DataGrid, so the component and composition patterns it offers should feel at home in a React app. + + +```tsx title="Default structure of InfiniteTable" + + + +``` + +## Customizing the nesting of the InfiniteTable component + +However, be aware that you the `` component doesn't have to be a direct child of the `` component. The `` component doesn't actually render anything, but its job is to load, process and prepare the data in a way that `` understands and can display. And actually you can use the DataSource context to gain access to the data yourself. + +```tsx {4} title="InfiniteTable can be nested anywhere inside the component" + +

Your DataGrid

+ + + +
+``` + + + + +Inside the `` component you can use the DataSource-provided context via the hook that our component exposes. + + + +## Choosing what to render + +Besides the flexibility of nesting your DataGrid component anywhere in your app, we also offer you the ability to choose what parts of the DataGrid you want to render and where. + +Let's suppose you want to show the header after the body of the DataGrid or choose to insert something in between. That should be easy, right? **It is with Infinite!** - but try to do that with the other commercial DataGrids out there! + +```tsx live tailwind file="./customizing-structure.page.tsx" + +``` + +As demoed above, the good part is that you can very easily add additional elements to your structure and have the grouping toolbar displayed on the side, vertically. + +```tsx {8} title="Example structure for vertical grouping toolbar" + + +
+
+ + +
+ +
+
+
+``` + + + +In the example above, try dragging the header of the `age` column onto the `GroupingToolbar` to add grouping by `age`. + + + + diff --git a/www/content/blog/2025/10/09/customizing-your-datagrid-component.png b/www/content/blog/2025/10/09/customizing-your-datagrid-component.png new file mode 100644 index 000000000..b916452e9 Binary files /dev/null and b/www/content/blog/2025/10/09/customizing-your-datagrid-component.png differ diff --git a/www/content/docs/index.page.md b/www/content/docs/index.page.md index 5727c06ad..942f78259 100644 --- a/www/content/docs/index.page.md +++ b/www/content/docs/index.page.md @@ -19,7 +19,7 @@ It helps you display huge datasets and get the most out of your data by providin - [ lazy loading](/docs/learn/working-with-data/lazy-loading) - [ keyboard navigation](/docs/learn/keyboard-navigation/navigating-cells) - [ fixed and flexible columns](/docs/learn/columns/fixed-and-flexible-size) -- [ column grouping](/docs/learn/column-groups) +- [ column grouping](/docs/learn/columns/column-grouping) - [ filtering](/docs/learn/filtering) - [ theming](/docs/learn/theming) diff --git a/www/content/docs/reference/api/index.page.md b/www/content/docs/reference/api/index.page.md index 25a316aa4..4f3c21c6f 100644 --- a/www/content/docs/reference/api/index.page.md +++ b/www/content/docs/reference/api/index.page.md @@ -23,7 +23,7 @@ const onReady = ( For API on row/group selection, see the [Selection API page](/docs/reference/selection-api). -See the [Infinite Table Cell Selection API page](/docs/reference/cell-selecti-api) for the cell selection API. +See the [Infinite Table Cell Selection API page](/docs/reference/cell-selection-api) for the cell selection API. See the [Infinite Table Column API page](/docs/reference/column-api) for the column API. See the [Infinite Table Keyboard Navigation API page](/docs/reference/keyboard-navigation-api) for the keyboard navigation API. diff --git a/www/content/docs/reference/hooks/index.page.md b/www/content/docs/reference/hooks/index.page.md index c11ab9939..41294c404 100644 --- a/www/content/docs/reference/hooks/index.page.md +++ b/www/content/docs/reference/hooks/index.page.md @@ -15,24 +15,53 @@ See below for the full list of hooks exposed by `InfiniteTable`, each with examp > Gives you access to the master row info in the current RowDetail component. - + - This example shows a master DataGrid with cities & countries. The details for each city shows a DataGrid with developers in that city. - -```ts file="$DOCS/learn/master-detail/master-detail-component-example.page.tsx" +```ts file="$DOCS/learn/master-detail/master-detail-component-example.page.tsx" title="Basic master detail DataGrid example" size="lg" ``` - + + + + +> You can use it in your app components that are nested inside the `` +```ts +import { useDataSourceState } from '@infinite-table/infinite-react' +``` + +Using it gives you access to the underlying data that InfiniteTable is using. + + +Please make sure you know what you're doing. This is intended only for advanced and complex use-cases. + + + + +```tsx title="InfiniteTable can be nested anywhere inside the component" + +

Your DataGrid> + + + + +``` + +Any component nested inside the `` can access the underlying data. + +```tsx live file="using-datasource-context.page.tsx" + +``` + > Use it inside the or (or other rendering functions) to retrieve information about the cell that is being rendered. diff --git a/www/content/docs/reference/hooks/using-datasource-context.page.tsx b/www/content/docs/reference/hooks/using-datasource-context.page.tsx new file mode 100644 index 000000000..8fc18b9ae --- /dev/null +++ b/www/content/docs/reference/hooks/using-datasource-context.page.tsx @@ -0,0 +1,88 @@ +import * as React from 'react'; + +import { + InfiniteTable, + DataSource, + useDataSourceState, + type InfiniteTableColumn, +} from '@infinite-table/infinite-react'; + +type Developer = { + id: number; + firstName: string; + preferredLanguage: string; + stack: string; + salary: number; + currency: string; + country: string; +}; + +const columns: Record> = { + firstName: { field: 'firstName', header: 'First Name' }, + preferredLanguage: { + field: 'preferredLanguage', + header: 'Programming Language', + }, + stack: { field: 'stack', header: 'Stack' }, + + salary: { + field: 'salary', + type: 'number', + defaultWidth: 210, + }, + currency: { field: 'currency', header: 'Currency', defaultWidth: 100 }, +}; + +const domProps = { + style: { + flex: 1, + }, +}; + +export default function App() { + return ( + + data={dataSource} + primaryKey="id" + defaultGroupBy={[{ field: 'country' }]} + > + + + ); +} + +function AppGrid() { + const { dataArray } = useDataSourceState(); + return ( +
+

Your DataGrid

+

+ Displaying {dataArray.length} rows. Collapse/expand rows to see this + number change. +

+ + + groupRenderStrategy="single-column" + defaultActiveRowIndex={0} + domProps={domProps} + columns={columns} + columnDefaultWidth={150} + /> +
+ ); +} + +const dataSource = () => { + return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers100') + .then((r) => r.json()) + .then((data: Developer[]) => data); +}; diff --git a/www/content/docs/reference/row-selection-api/index.page.md b/www/content/docs/reference/row-selection-api/index.page.md index b01e3e32f..0f1884345 100644 --- a/www/content/docs/reference/row-selection-api/index.page.md +++ b/www/content/docs/reference/row-selection-api/index.page.md @@ -31,7 +31,7 @@ const onReady = ({api}: {api:InfiniteTableApi}) => { ``` See the [Infinite Table API page](/docs/reference/api) for the main API. -See the [Infinite Table Cell Selection API page](/docs/reference/cell-selecti-api) for the cell selection API. +See the [Infinite Table Cell Selection API page](/docs/reference/cell-selection-api) for the cell selection API. See the [Infinite Table Column API page](/docs/reference/column-api) for the column API. See the [Infinite Table Row Detail API page](/docs/reference/row-detail-api) for the row detail API (when master-detail is configured). diff --git a/www/content/docs/reference/type-definitions/index.page.md b/www/content/docs/reference/type-definitions/index.page.md index 53a2a9e0f..1f37d00da 100644 --- a/www/content/docs/reference/type-definitions/index.page.md +++ b/www/content/docs/reference/type-definitions/index.page.md @@ -19,6 +19,17 @@ So, for example, the type for is + + +> Represents the state of the whole `` component. + +You can grab a reference to the `` component state via the hook that Infinite exposes. + +Available properties: + + - `dataArray` - array of + + > Represents the selection state of the tree nodes. See for more details. diff --git a/www/public/gen/assets/customizing-your-datagrid-component.png b/www/public/gen/assets/customizing-your-datagrid-component.png new file mode 100644 index 000000000..ebe3c5dd5 Binary files /dev/null and b/www/public/gen/assets/customizing-your-datagrid-component.png differ diff --git a/www/public/gen/assets/grouping-hero-image.png b/www/public/gen/assets/grouping-hero-image.png new file mode 100644 index 000000000..f3df27b3c Binary files /dev/null and b/www/public/gen/assets/grouping-hero-image.png differ diff --git a/www/src/.gen/index.json b/www/src/.gen/index.json index 450cd46d6..a57fc698b 100644 --- a/www/src/.gen/index.json +++ b/www/src/.gen/index.json @@ -69,6 +69,17 @@ "readingTime": "1 min read", "content": "\n# Blog post about keeping productivity high in infinite table - tests, build tools, styling approach\n" }, + { + "filePath": "/comparison/index", + "routePath": "/comparison/", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/comparison/index.page.md", + "fileName": "index.page.md", + "folderPath": "/comparison/", + "frontmatter": {}, + "excerpt": "# Comparison of React DataGrids", + "readingTime": "1 min read", + "content": "# Comparison of React DataGrids\n\n## DevExtreme Data Grid\n\n\nSandpack deps=\"devextreme-react,devextreme\">\n\n```tsx file=\"devexpress.app.tsx\"\n\n```\n\n```html file=devexpress.index.html\n\n```\n\n```ts file=\"columns.ts\"\n\n```\n\nSandpack\n\n## AG Grid\n\nSandpack deps=\"ag-grid-community,ag-grid-react,ag-grid-enterprise\"\n\n`tsx file=\"aggrid.page.tsx\"\n\n`\n\n`ts file=\"columns.ts\"\n\n`\n\nSandpack\n\n## Infinite Table\n\nSandpack\n\n`tsx file=\"infinite.page.tsx\"\n\n`\n\n`ts file=\"columns.ts\"\n\n`\n\nSandpack\n" + }, { "filePath": "/docs/devtools", "routePath": "/docs/devtools", @@ -95,18 +106,7 @@ }, "excerpt": "", "readingTime": "3 min read", - "content": "\n\n\n## What is Infinite Table?\n\nInfinite Table is a React DataGrid component for displaying virtualized tabular data.\n\nIt helps you display huge datasets and get the most out of your data by providing you the right tools to enjoy these features:\n\n- [ sorting](/docs/learn/sorting/overview)\n- [ row grouping](/docs/learn/grouping-and-pivoting/grouping-rows) - both server-side and client-side\n- [ pivoting](/docs/learn/grouping-and-pivoting/pivoting/overview) - both server-side and client-side\n- [ aggregations](/docs/learn/grouping-and-pivoting/grouping-rows#aggregations)\n- [ live pagination](/docs/learn/working-with-data/live-pagination)\n- [ lazy loading](/docs/learn/working-with-data/lazy-loading)\n- [ keyboard navigation](/docs/learn/keyboard-navigation/navigating-cells)\n- [ fixed and flexible columns](/docs/learn/columns/fixed-and-flexible-size)\n- [ column grouping](/docs/learn/column-groups)\n- [ filtering](/docs/learn/filtering)\n- [ theming](/docs/learn/theming)\n\n## Installation\n\nInstallation could not be more straightforward - just one npm command:\n\n\nnpm i @infinite-table/infinite-react\n\n\n## โค๏ธ TypeScript\n\nInfinite Table is fully typed and offers you a great developer experience, to help you get up and running quickly.\n\n> The TypeScript typings file is included in the npm package - you don't have to download an additional **@types** package\n\n\n\nRead more about how to use our TypeScript types\n\n\n\n\n## ๐Ÿ“„ Extensive Documentation\n\nWe're aware good documentation is a must and are updating our documentation as we add new features. Head to [our getting started](/docs/learn/getting-started) guide to get up and running quickly.\n\n## ๐Ÿข Enterprise-Ready\n\nInfinite Table is ready to power your enterprise apps, as it supports advanced [data fetching](/docs/learn/working-with-data#data-loading-strategies), [filtering](/docs/learn/filtering), [sorting](/docs/learn/sorting/overview), [grouping](/docs/learn/grouping-and-pivoting/grouping-rows), [pivoting](/docs/learn/grouping-and-pivoting/pivoting/overview), [aggregations](/docs/learn/grouping-and-pivoting/group-aggregations), [live pagination](/docs/learn/working-with-data/live-pagination), [lazy loading](/docs/learn/working-with-data/lazy-loading) - all of those with support for both client-side and server-side implementations.\n\nYou can choose to leverage our built-in implementations in the browser, or you can process your data on the server with full support from our-side.\n\n### ๐Ÿ”’ Secure by Default\n\nWe take security seriously and only have a total of 3 dependencies in our full dependency graph - and this number will only go down.\n\n### ๐Ÿ“ฆ Small Bundle Size\n\nOur bundle size is under `300kB` and we're dedicated to [keeping it small](https://bundlephobia.com/package/@infinite-table/infinite-react).\n\n\n\n\nSee our bundle size in BundlePhobia\n\n\n\n\n### ๐Ÿงช Automated End-to-End Tests\n\nOur releases are automated and, we have full end-to-end tests that ensure we're delivering to our standards.\n\nReal-browser tests help us move with confidence and continue to ship great features.\n\n\n\n\nCheck out our end-to-end tests in GitHub\n\n\n\n\n\n## ๐ŸŽจ Themable\n\n`Infinite Table` is fully customizable, via CSS variables.\n\nIt ships with both a **light** and a **dark** theme - all you have to do is import the CSS file from the package.\n\n```ts\nimport '@infinite-table/infinite-react/index.css';\n\n// This file includes both the light and the dark themes.\n```\n\n\n\n\nRead how to use themes and **CSS variables** to customize every aspect of Infinite Table\n\n\n\n" - }, - { - "filePath": "/comparison/index", - "routePath": "/comparison/", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/comparison/index.page.md", - "fileName": "index.page.md", - "folderPath": "/comparison/", - "frontmatter": {}, - "excerpt": "# Comparison of React DataGrids", - "readingTime": "1 min read", - "content": "# Comparison of React DataGrids\n\n## DevExtreme Data Grid\n\n\nSandpack deps=\"devextreme-react,devextreme\">\n\n```tsx file=\"devexpress.app.tsx\"\n\n```\n\n```html file=devexpress.index.html\n\n```\n\n```ts file=\"columns.ts\"\n\n```\n\nSandpack\n\n## AG Grid\n\nSandpack deps=\"ag-grid-community,ag-grid-react,ag-grid-enterprise\"\n\n`tsx file=\"aggrid.page.tsx\"\n\n`\n\n`ts file=\"columns.ts\"\n\n`\n\nSandpack\n\n## Infinite Table\n\nSandpack\n\n`tsx file=\"infinite.page.tsx\"\n\n`\n\n`ts file=\"columns.ts\"\n\n`\n\nSandpack\n" + "content": "\n\n\n## What is Infinite Table?\n\nInfinite Table is a React DataGrid component for displaying virtualized tabular data.\n\nIt helps you display huge datasets and get the most out of your data by providing you the right tools to enjoy these features:\n\n- [ sorting](/docs/learn/sorting/overview)\n- [ row grouping](/docs/learn/grouping-and-pivoting/grouping-rows) - both server-side and client-side\n- [ pivoting](/docs/learn/grouping-and-pivoting/pivoting/overview) - both server-side and client-side\n- [ aggregations](/docs/learn/grouping-and-pivoting/grouping-rows#aggregations)\n- [ live pagination](/docs/learn/working-with-data/live-pagination)\n- [ lazy loading](/docs/learn/working-with-data/lazy-loading)\n- [ keyboard navigation](/docs/learn/keyboard-navigation/navigating-cells)\n- [ fixed and flexible columns](/docs/learn/columns/fixed-and-flexible-size)\n- [ column grouping](/docs/learn/columns/column-grouping)\n- [ filtering](/docs/learn/filtering)\n- [ theming](/docs/learn/theming)\n\n## Installation\n\nInstallation could not be more straightforward - just one npm command:\n\n\nnpm i @infinite-table/infinite-react\n\n\n## โค๏ธ TypeScript\n\nInfinite Table is fully typed and offers you a great developer experience, to help you get up and running quickly.\n\n> The TypeScript typings file is included in the npm package - you don't have to download an additional **@types** package\n\n\n\nRead more about how to use our TypeScript types\n\n\n\n\n## ๐Ÿ“„ Extensive Documentation\n\nWe're aware good documentation is a must and are updating our documentation as we add new features. Head to [our getting started](/docs/learn/getting-started) guide to get up and running quickly.\n\n## ๐Ÿข Enterprise-Ready\n\nInfinite Table is ready to power your enterprise apps, as it supports advanced [data fetching](/docs/learn/working-with-data#data-loading-strategies), [filtering](/docs/learn/filtering), [sorting](/docs/learn/sorting/overview), [grouping](/docs/learn/grouping-and-pivoting/grouping-rows), [pivoting](/docs/learn/grouping-and-pivoting/pivoting/overview), [aggregations](/docs/learn/grouping-and-pivoting/group-aggregations), [live pagination](/docs/learn/working-with-data/live-pagination), [lazy loading](/docs/learn/working-with-data/lazy-loading) - all of those with support for both client-side and server-side implementations.\n\nYou can choose to leverage our built-in implementations in the browser, or you can process your data on the server with full support from our-side.\n\n### ๐Ÿ”’ Secure by Default\n\nWe take security seriously and only have a total of 3 dependencies in our full dependency graph - and this number will only go down.\n\n### ๐Ÿ“ฆ Small Bundle Size\n\nOur bundle size is under `300kB` and we're dedicated to [keeping it small](https://bundlephobia.com/package/@infinite-table/infinite-react).\n\n\n\n\nSee our bundle size in BundlePhobia\n\n\n\n\n### ๐Ÿงช Automated End-to-End Tests\n\nOur releases are automated and, we have full end-to-end tests that ensure we're delivering to our standards.\n\nReal-browser tests help us move with confidence and continue to ship great features.\n\n\n\n\nCheck out our end-to-end tests in GitHub\n\n\n\n\n\n## ๐ŸŽจ Themable\n\n`Infinite Table` is fully customizable, via CSS variables.\n\nIt ships with both a **light** and a **dark** theme - all you have to do is import the CSS file from the package.\n\n```ts\nimport '@infinite-table/infinite-react/index.css';\n\n// This file includes both the light and the dark themes.\n```\n\n\n\n\nRead how to use themes and **CSS variables** to customize every aspect of Infinite Table\n\n\n\n" }, { "filePath": "/docs/reference/error-codes", @@ -181,6 +181,20 @@ "readingTime": "1 min read", "content": "\n## 1.0.0 ๐Ÿš€\n\n@milestone id=\"60\"\n" }, + { + "filePath": "/docs/learn/common-issues/index", + "routePath": "/docs/learn/common-issues/", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/learn/common-issues/index.page.md", + "fileName": "index.page.md", + "folderPath": "/docs/learn/common-issues/", + "frontmatter": { + "title": "Common Issues", + "description": "Avoid common pitfalls and issues when using the component. Learn how to use it properly to perform smooth and avoid jank." + }, + "excerpt": "As people have started using `` we've noticed a few issues keep popping up.", + "readingTime": "2 min read", + "content": "\nAs people have started using `` we've noticed a few issues keep popping up.\n\nWhile we're trying to refine our API to be easier to use and understand, developers using the component still need to be aware of some design decisions and conventions used in the component.\n\n## Issue: Performance degradation because props are new on every render\n\nPassing new props on every render to the `` component or to the `` component can be a performance bottleneck:\n\n```ts\n\n```\n\nInstead pass the **same** reference when things do change - stored in state or any other place:\n\n```ts\nconst [groupBy, setGroupBy] = useState([{ field: 'country' }]);\n\n;\n```\n\n\n\nWhen in dev mode, you can set `localStorage.debug = \"*\"` in your localstorage to see potential issues logged to the console.\n\nFor example, you might see:\n\n`InfiniteTable:rerender Triggered by new values for the following props +1s columns`\n\n\n\n## Issue: State inside custom components rendered in cells is lost while scrolling\n\nWhen using custom rendering or custom components for columns, make sure all your rendering logic is [controlled](https://reactjs.org/docs/forms.html#controlled-components) and that it doesn't have any local or transient state.\n\nThis is important because `InfiniteTable` makes heavy use of virtualization, in both _column cells and column headers_, so **custom components can and will be unmounted and re-mounted multiple times**, during the virtualization process (triggered by user scrolling, sorting, filtering and a few other interactions).\n" + }, { "filePath": "/docs/learn/columns/cell-and-column-styling", "routePath": "/docs/learn/columns/cell-and-column-styling", @@ -320,20 +334,6 @@ "readingTime": "4 min read", "content": "\nColumns are a central feature in `InfiniteTable`.\n\nYou define columns as a an object, with keys being column ids while values are the column definitions.\n\nYou then use them in the `columns` prop in your `InfiniteTable` component.\n\nThe prop is typed either as\n\n- `Record>`\n- or `InfiniteTablePropColumns`, which is an alias for the type above\n\n\n\nIn `InfiniteTable`, columns are identified by their key in the object. **We'll refer to this as the column id**.\nThe column ids are used in many places - like defining the column order, column pinning, column visibility, etc.\n\n\n\n```ts\nexport type Employee = {\n id: number;\n companyName: string;\n firstName: string;\n lastName: string;\n country: string;\n city: string;\n department: string;\n team: string;\n salary: number;\n\n};\n\n// InfiniteTableColumn is a generic type, you have to bind it to a specific data-type\nimport { InfiniteTableColumn } from '@infinite-table/infinite-react';\n\n// we're binding it here to the `Employee` type\n// which means the `column.field` has to be `keyof Employee`\nexport const columns: Record> = {\n 'firstName':\n {\n field: 'firstName',\n header: 'First Name',\n },\n 'country':\n {\n field: 'country',\n },\n 'city':\n {\n field: 'city'\n },\n 'salary':\n {\n field: 'salary',\n type: 'number'\n },\n}\n\n```\n\n\n\nIt's very important to remember you should not pass a different reference of a prop on each render. `` is a optimized to only re-render when props change - so if you change the props on every re-render you will get a performance penalty.\n\nYou should use `React.useCallback` / `React.useMemo` / `React.useState` to make sure you only update the props you pass down to `InfiniteTable` when you have to.\n\n\n\n\n\n```ts file=\"basic-columns-example.page.tsx\"\n\n```\n\n\n\n\nFind out how to render custom content inside columns or even take full control of column cells and header.\n\n\n## Column Types\n\nColumn types allow you to customize column behavior and appearance for multiple columns at once. Most of the properties available for columns are also available for column types - for a full list, see columnTypes reference.\n\nThere are two special column types for now, but more are coming soon:\n\n- `default` - all columns have this type, if not otherwise specified. The type does not contain any configuration, but allows you to define it and apply common configuration to all columns.\n- `number` - if specified on a column (in combination with local uncontrolled sorting), the column will be sorted numerically.\n\n\nFind out how to use column types to customize the appearance and behaviour of your columns.\n\n\n## Column Order\n\nThe implicit column order is the order in which columns have been defined in the object. You can however control that explicitly by using the `columnOrder: string[]` prop.\n\n```tsx\n\nconst columnOrder = ['firstName','id','curency']\n\nconst App = () => {\n return primaryKey={\"id\"} dataSource={...}>\n \n columnOrder={columnOrder}\n onColumnOrderChange={(columnOrder: string[]) => {}}\n />\n \n}\n```\n\nThe prop is an array of strings, representing the column ids. A column id is the key of the column in the object.\n\n\n\nThe array can contain identifiers that are not yet defined in the Map, or can contain duplicate ids. This is a feature, not a bug. We want to allow you to use the in a flexible way so it can define the order of current and future columns.\n\n\n\n\n is a controlled prop. For the uncontrolled version, see \n\nWhen using controlled , make sure you also update the order by using the callback prop.\n\n\n\n\n```tsx file=\"$DOCS/reference/columnOrder-example.page.tsx\"\n\n```\n\n\n\n\n\nBy keeping the column order simple, namely an array of strings, ordering becomes much easier.\n\nThe alternative would be to make `columns` an array, which most DataGrids do - and whenever they are reordered, a new `columns` array would be needed.\n\n\n" }, - { - "filePath": "/docs/learn/common-issues/index", - "routePath": "/docs/learn/common-issues/", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/learn/common-issues/index.page.md", - "fileName": "index.page.md", - "folderPath": "/docs/learn/common-issues/", - "frontmatter": { - "title": "Common Issues", - "description": "Avoid common pitfalls and issues when using the component. Learn how to use it properly to perform smooth and avoid jank." - }, - "excerpt": "As people have started using `` we've noticed a few issues keep popping up.", - "readingTime": "2 min read", - "content": "\nAs people have started using `` we've noticed a few issues keep popping up.\n\nWhile we're trying to refine our API to be easier to use and understand, developers using the component still need to be aware of some design decisions and conventions used in the component.\n\n## Issue: Performance degradation because props are new on every render\n\nPassing new props on every render to the `` component or to the `` component can be a performance bottleneck:\n\n```ts\n\n```\n\nInstead pass the **same** reference when things do change - stored in state or any other place:\n\n```ts\nconst [groupBy, setGroupBy] = useState([{ field: 'country' }]);\n\n;\n```\n\n\n\nWhen in dev mode, you can set `localStorage.debug = \"*\"` in your localstorage to see potential issues logged to the console.\n\nFor example, you might see:\n\n`InfiniteTable:rerender Triggered by new values for the following props +1s columns`\n\n\n\n## Issue: State inside custom components rendered in cells is lost while scrolling\n\nWhen using custom rendering or custom components for columns, make sure all your rendering logic is [controlled](https://reactjs.org/docs/forms.html#controlled-components) and that it doesn't have any local or transient state.\n\nThis is important because `InfiniteTable` makes heavy use of virtualization, in both _column cells and column headers_, so **custom components can and will be unmounted and re-mounted multiple times**, during the virtualization process (triggered by user scrolling, sorting, filtering and a few other interactions).\n" - }, { "filePath": "/docs/learn/context-menus/using-context-menus", "routePath": "/docs/learn/context-menus/using-context-menus", @@ -624,46 +624,6 @@ "readingTime": "3 min read", "content": "\nOur `TypeScript` types are published as part of the package, as named exports from the root of the package.\n\nThe 2 main components that you can need to use and import are:\n\n- `InfiniteTable`\n- `DataSource`\n\n```tsx title=\"Importing InfiniteTable and DataSource components\"\nimport { InfiniteTable, DataSource } from '@infinite-table/infinite-react';\n```\n\n\n\nIn our TypeScript typings, those components are exported as generic components, so they need to be bound to the type of the data they are rendering.\n\n```tsx\ntype Developer = {\n id: number;\n\n firstName: string;\n lastName: string;\n\n currency: string;\n salary: number;\n}\n\nconst App = () => {\n return data={data} primaryKey=\"id\">\n \n columns={{...}}\n />\n \n}\n```\n\nThroughout the documentation, we will use the `DATA_TYPE` placeholder to refer to the type of the data that the `InfiniteTable` and `DataSource` components are bound to.\n\n\n\n\n\nYou can still use `InfiniteTable` in plain JavaScript, but you won't get all the type-checking benefits.\n\n\n\nBoth `InfiniteTable` and `DataSource` components have types provided for most of the props they support. Generally the naming pattern is `Prop`, so here are a few examples to clarify the rule:\n\n```ts\nimport type {\n InfiniteTablePropColumns,\n // corresponding to the `columns` prop\n DataSourcePropGroupBy,\n // corresponding to the `groupBy` prop\n} from '@infinite-table/infinite-react';\n```\n\n## `DataSource` Types\n\nHere are a few examples for types for the `DataSource` component:\n\n- `DataSourcePropGroupBy` - the type for DataSource.groupBy\n\n```tsx\nimport type { DataSourcePropGroupBy } from '@infinite-table/infinite-react';\n```\n\n- `DataSourcePropAggregationReducers` - the type for DataSource.aggregationReducers\n\n```tsx\nimport type { DataSourcePropAggregationReducers } from '@infinite-table/infinite-react';\n```\n\n\n\nNot all the `DataSource` props have types exported that follow this convention, so you can always use `DataSourceProps` to get the type that define all the props.\n\nIn this way you can access specific prop types by name\n\n- `DataSourceProps['groupBy']` - the type for DataSource.groupBy\n- `DataSourceProps['data']` - the type for DataSource.data\n- etc\n\n\n\n## `InfiniteTable` Types\n\nBelow you can find a few examples for types for the `InfiniteTable` component:\n\n- `InfiniteTablePropColumns` - the type for InfiniteTable.columns\n\n```tsx\nimport type { InfiniteTablePropColumns } from '@infinite-table/infinite-react';\n```\n\n- `InfiniteTablePropRowStyle` - the type for InfiniteTable.rowStyle\n\n```tsx\nimport type { InfiniteTablePropRowStyle } from '@infinite-table/infinite-react';\n```\n\n- `InfiniteTablePropColumnGroups` - the type for InfiniteTable.columnGroups\n\n```tsx\nimport type { InfiniteTablePropColumnGroups } from '@infinite-table/infinite-react';\n```\n\n\n\nNot all the `InfiniteTable` props have types exported that follow this convention, so you can always use `InfiniteTableProps` to get the type that define all the props the `InfiniteTable` component supports.\n\nIn this way you can access specific prop types by name:\n\n- `InfiniteTableProps['columns']` - the type for InfiniteTable.columns\n- `InfiniteTableProps['columnSizing']` - the type for InfiniteTable.columnSizing\n- etc\n\n\n\n\n\nWorth mentioning is the `InfiniteTableColumn` prop, which defines the type for the table .\n\n\n" }, - { - "filePath": "/docs/learn/grouping-and-pivoting/group-aggregations", - "routePath": "/docs/learn/grouping-and-pivoting/group-aggregations", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/learn/grouping-and-pivoting/group-aggregations.page.md", - "fileName": "group-aggregations.page.md", - "folderPath": "/docs/learn/grouping-and-pivoting/", - "frontmatter": { - "title": "Aggregations", - "description": "Learn how to define & use aggregations on grouped rows in Infinite Table for React." - }, - "excerpt": "A natural next step when grouping data is **aggregating the grouped values**. We allow developers to define any number of aggregations and bind them to any column.", - "readingTime": "8 min read", - "content": "\nA natural next step when grouping data is **aggregating the grouped values**. We allow developers to define any number of aggregations and bind them to any column.\n\nThe aggregations are defined on the `` component and are easily available at render time. A client-side aggregation needs a reducer function that accumulates the values in the data array and computes the final result.\n\n\n\nThroughout the docs, we might refer to aggregations as reducers - which, more technically, they are, since they reduce an array of values (from a group) to a single value.\n\n\n\n## Client-Side Aggregations\n\nWhen using client-side aggregation, each aggregation can have the following:\n\n### An initial value\n\nThe `initialValue` is optional value to use as the initial (accumulator) value for the reducer function. You can think of aggregations as an \"enhanced\" version of [Array.reduce](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce), so initial value should sound familiar.\n\n\n\nThe `initialValue` can be a function - in this case it will be called to compute the initial value.\n\n\n\n### A reducer function\n\n`reducer` is the function to call for each value in the (grouped) data array. It is called with the following arguments:\n\n- `accumulator` - the value returned by the previous call to the reducer function, or the `initialValue` if this is the first call. You return the new accumulator value from this function.\n- `value` - the value of the current item in the data array. If the aggregation has a `field`, this is the value of that field in the current item. Otherwise, value is the result of calling the `reducer.getter(data)` function (if one exists) or null if no getter is defined.\n- `dataItem` - the current item in the data array.\n- `index` - the index of the current item in the data array.\n\n### A `field` property or a `getter` function\n\nFor simple use-cases of client-side aggregations, a `field` is the way to go. This defines the field property (from the DATA_TYPE) to which the aggregation is bound.\n\nFor more complex scenarios, the aggregation should have a `getter` function. If both a `field` and a `getter` are provided, the `getter` has higher priority and will be used.\n\nUse this `getter` function to compute the value the current item in the array brings to the aggregation.\n\n```tsx title=\"Aggregation_custom_getter_function\"\n// useful for retrieving nested values\n\ngetter: (dataItem: Developer) => data.salary.net;\n```\n\n\n\nFor using nested values inside aggregations, use the aggregation `getter` function.\n\n\n\n### A completion `done` function\n\nThe completion `done` function is optional - if specified, will be after iterating over all the values in the grouped data array. Can be used to change the final result of the aggregation. It is called with the following arguments:\n\n- `accumulator` - the value returned by the last call to the reducer function\n- `data` - the grouped data array.\n This is useful for computing averages, for example:\n\n```tsx title=\"Done function for avg reducer\"\ndone: (acc, data) => acc / data.length;\n```\n\n### Putting it all together\n\nLet's take a look at a simple example of aggregating two columns, one to display the avg and the other one should compute the sum of the salary column for grouped rows.\n\n```tsx title=\"Average Aggregation\"\nimport { DataSource, InfiniteTable } from '@infinite-table/infinite-react';\n\nconst sum = (a: number, b: number) => a + b;\n\nconst reducers = {\n avg: {\n initialValue: 0,\n field: 'age',\n reducer: sum,\n done: (acc, data) => Math.round(acc / data.length),\n },\n\n sumAgg: {\n initialValue: 0,\n field: 'salary',\n reducer: sum\n }\n}\n\nfunction App() {\n return \n aggregationReducers={reducers}\n >\n {...} />\n \n}\n```\n\nIn the above example, note that aggregations are an object where the keys of the object are used to identify the aggregation and the values are the aggregation configuration objects, as described above.\n\n\n\nAt run-time, you have access to the aggregation reducer results inside group rows - you can use the `rowInfo.reducerResults` object to access those values. For the example above, you change how group rows are rendered for a certain column and display the aggregation results in a custom way:\n\n```tsx {9} title=\"Custom_group_row_rendering_for_the_country_column\"\n\ncountry: {\n field: 'country',\n\n // define a custom renderGroupValue fn for the country column\n\n renderGroupValue: ({ rowInfo }) => {\n const { reducerResults = {} } = rowInfo;\n // note the keys in the reducerResults objects match the keys in the aggregationReducers object\n return `Avg age: ${reducerResults.avg}, total salary ${reducerResults.sumAgg}`;\n },\n},\n```\n\n\n\n\n\n```ts file=\"aggregations-simple-example.page.tsx\"\n\n```\n\n\n\n## Server-Side Aggregations\n\nServer-side aggregations are defined in the same way as client-side aggregations (except the `reducer` function is missing), but the aggregation values are computed by the server and returned as part of the data response.\n\nFor computing the grouping and aggregations on the server, the backend needs to know the grouping and aggregation configuration. As such, Infinite Table will call the DataSource data function with an object that contains all the required info:\n\n- `groupBy` - the array of grouping fields, as passed to the `` component.\n- `pivotBy` - the array of pivot fields, as passed to the `` component.\n- `aggregationReducers` - the value of the prop, as configured on the `` component.\n- `sortInfo` - the current sorting information for the data.\n\nFor the lazy-loading use-case, there are other useful properties you can use from the object passed into the `data` function:\n\n- `groupKeys: string[]` - the group keys for the current group - the `data` fn is generally called lazily when the user expands a group row. This info is useful for fetching the data for a specific group.\n- `lazyLoadStartIndex` - provided when batching is also enabled via the prop. This is the index of the first item in the current batch.\n- `lazyLoadBatchSize` - also used when batching is enabled. This is the number of items in the current batch.\n\nBesides the above information, if filtering is used, a `fiterValue` is also made available.\n\nIn order to showcase the server-side aggregations, let's build an example similar to the above one, but let's lazily load group data.\n\n```tsx {2} title=\"DataSourcewith lazyLoad enabled\"\n\n```\n\nAs soon a grouping and aggregations are no longer computed on the client, your `data` function needs to send those configurations on the backend, so it needs to get a bit more complicated:\n\n```tsx title=\"Data_function_sending_configurations_to_the_backend\"\nconst data = ({ groupBy, aggregationReducers, sortInfo, groupKeys }) => {\n // it's important to send the current group keys - for top level, this will be []\n const args: string[] = [`groupKeys=${JSON.stringify(groupKeys)}`];\n\n // turn the sorting info into an array\n if (sortInfo && !Array.isArray(sortInfo)) {\n sortInfo = [sortInfo];\n }\n\n if (sortInfo) {\n // the backend expects the sort info to be an array of field,dir pairs\n args.push(\n 'sortInfo=' +\n JSON.stringify(\n sortInfo.map((s) => ({\n field: s.field,\n dir: s.dir,\n })),\n ),\n );\n }\n\n if (groupBy) {\n // for grouping, send an array of objects with the `field` property\n args.push(\n 'groupBy=' + JSON.stringify(groupBy.map((p) => ({ field: p.field }))),\n );\n }\n\n if (aggregationReducers) {\n args.push(\n 'reducers=' +\n JSON.stringify(\n // by convention, we send an array of reducers, each with `field` `name`(= \"avg\") and `id`\n // it's up to you to decide what the backend needs\n Object.keys(aggregationReducers).map((key) => ({\n field: aggregationReducers[key].field,\n id: key,\n name: aggregationReducers[key].reducer,\n })),\n ),\n );\n }\n\n const url = BASE_URL + `/developers10k-sql?` + args.join('&');\n return fetch(url).then(r=>r.json())\n}\n\n\n```\n\nWhen fetching without grouping (or with local grouping and aggregations), the `` component expects a flat array of data items coming from the server.\n\nHowever, when the grouping is happening server-side, the `` component expects a response that has the following shape:\n\n- `data` - the root array with grouping and aggregation info. Each item in the array should have the following:\n - `keys` - an array of the keys for the current group - eg `['USA']` or `['USA', 'New York']`\n - `data` - an object with all the common values for the group - eg `{ country: 'USA' }` or `{ country: 'USA', city: 'New York' }`\n - `aggregations` - an object with the aggregation values for the group - eg `{ age: 30, salary: 120300 }`. The keys in this object should match the keys in the object.\n - `pivot` - pivoting information for the current group - more on that on the dedicated [Pivoting page](./pivoting/overview).\n\nWhen the user is expanding the last level, in order to see the leaf rows, the shape of the response is expected to be the same as when there is no grouping - namely an array of data items or an object where the `data` property is an array of data items.\n\nLet's put all of this into a working example.\n\n\n\n\n\nThis showcases grouping and aggregations on the server - both the `age` and `salary` columns have an AVG aggregation defined.\n\nGrouping is done by the `country`, `city` and `stack` columns.\n\n\n\n```tsx file=\"grouping-and-aggregations-with-lazy-load-example.page.tsx\"\n\n```\n\n\n\n\n\nWhen the user is doing a sort on the table, the `` is fetched from scratch, but the expanded/collapsed state is preserved, and all the required groups that need to be re-fetched are reloaded as needed (if they are not eagerly included in the served data).\n\n\n" - }, - { - "filePath": "/docs/learn/grouping-and-pivoting/grouping-rows", - "routePath": "/docs/learn/grouping-and-pivoting/grouping-rows", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/learn/grouping-and-pivoting/grouping-rows.page.md", - "fileName": "grouping-rows.page.md", - "folderPath": "/docs/learn/grouping-and-pivoting/", - "frontmatter": { - "title": "Grouping rows" - }, - "excerpt": "You can use any `field` available in the `DataSource` to do the grouping - it can even be a `field` that is not a column.", - "readingTime": "15 min read", - "content": "\nYou can use any `field` available in the `DataSource` to do the grouping - it can even be a `field` that is not a column.\n\n\n\nWhen using TypeScript, both `DataSource` and `InfiniteTable` components are generic and need to be rendered/instantiated with a `DATA_TYPE` parameter. The fields in that `DATA_TYPE` can then be used for grouping.\n\n\n\n```tsx\ntype Person = {\n name: string;\n age: number;\n country: string;\n id: string;\n}\n\nconst groupBy = [{field: 'country'}]\n\n groupBy={groupBy}>\n />\n\n\n```\n\nIn the example above, we're grouping by `country`, which is a field available in the `Person` type. Specifying a field not defined in the `Person` type would be a type error.\n\nAdditionally, a `column` object can be used together with the `field` to define how the group column should be rendered.\n\n```tsx {4}\nconst groupBy = [\n {\n field: 'country',\n column: {\n // custom column configuration for group column\n width: 150,\n header: 'Country group',\n },\n },\n];\n```\n\nThe example below puts it all together.\n\nAlso see the groupBy API reference to find out more.\n\n\n\n```ts file=\"row-grouping-example.page.tsx\"\n\n```\n\n\n\nIn `groupBy.column` you can use any column property - so, for example, you can define a custom `renderValue` function to customize the rendering.\n\n```tsx {5}\nconst groupBy = [\n {\n field: 'country',\n column: {\n renderValue: ({ value }) => <>Country: {value},\n },\n },\n];\n```\n\n\n\nThe generated group column(s) - can be one for all groups or one for each group - will inherit the `style`/`className`/renderers from the columns corresponding to the group fields themselves (if those columns exist).\n\nAdditionally, there are other ways to override those inherited configurations, in order to configure the group columns:\n\n- use to specify how each grouping column should look for the respective field (in case of groupRenderStrateg=\"multi-column\")\n- use prop\n - can be used as an object - ideal for when you have simple requirements and when groupRenderStrateg=\"single-column\"\n - as a function that returns a column configuration - can be used like this in either single or multiple group render strategy\n\n\n\n## Controlling the collapse/expand state\n\nWhen you do grouping, by default, all row groups are expanded. Of course you have full control over this and you do this via the / props.\n\nIf you simply want to specify the initial expanded/collapsed state, you should use the prop.\n\n```tsx title=\"Specifying the default state for group rows\"\nconst defaultGroupRowsState: DataSourcePropGroupRowsStateObject = {\n collapsedRows: true,\n expandedRows: [['Mexico'], ['Mexico', 'backend'], ['India']],\n};\n```\n\nThe two properties in this object are `collapsedRows` and `expandedRows`, and each can have the following values:\n - `true` - meaning that all groups have this state\n - an array of arrays - representing the exceptions to the default value\n\n\nSo if you have `collapsedRows` set to `true` and then `expandedRows` set to `[['Mexico'], ['Mexico', 'backend'], ['India']]` then all rows are collapsed by default, except the rows specified in the `expandedRows`.\n\n\n\n\n```ts file=\"row-grouping-state-example.page.tsx\"\n\n```\n\n\n\n\n\nYou can specify expand/collapse state at any level of nesting.\n\nLet's suppose by default all rows are collapsed - if you want a node to be visible then you have to specify all its parents as expanded.\n\nSo having this\n```tsx \nconst defaultGroupRowsState = {\n collapsedRows: true,\n expandedRows: [['Mexico', 'backend']],\n};\n```\nwill show all rows as collapsed, and just as soon as you expand `Mexico` you will see the `backend` group row for Mexico to be expanded.\n\n\nThis data format gives you ultimate flexibility and allows you to easily restore an expand/collpase state at a later time, if you wanted to.\n\n\nIf you use the controlled , make sure you update it by leveraging the callback prop.\n\n\n\n\n\n```ts file=\"row-grouping-state-controlled-example.page.tsx\"\n\n```\n\n\n\nIn addition to simple objects with the shape described above, the / can also be instanges of `GroupRowsState` class, which is exported by the Infinite Table package. This class is simply a wrapper around those objects, but it gives you additional utility methods.\n\n\n\nThe callback gives you an instance of back as the single argument. If you're using plain objects, just do `groupRowsState.getState()` and you'll get the corresponding plain object for the current expand/collapse state.\n\n give you some additional helper methods, which you can read about here\n\n\n## Grouping strategies\n\nMultiple grouping strategies are supported by, `InfiniteTable` DataGrid:\n\n- multi column mode - multiple group columns are generated, one for each specified group field\n- single column mode - a single group column is generated, even when there are multiple group fields\n\nYou can specify the rendering strategy explicitly by setting the property to any of the following: `multi-column`, `single-column`. If you don't set it explicitly, it will choose the best default based on your configuration.\n\n### Multiple groups columns\n\nWhen grouping by multiple fields, by default the component will render a group column for each group field\n\n```tsx\nconst groupBy = [\n {\n field: 'age',\n column: {\n width: 100,\n renderValue: ({ value }) => <>Age: {value},\n },\n },\n {\n field: 'companyName',\n },\n {\n field: 'country',\n },\n];\n```\n\nLet's see an example of how the component would render the table with the multi-column strategy.\n\n\n\n```ts files=[\"row-grouping-multi-column-example.page.tsx\",\"columns.ts\"]\n\n```\n\n\n\nFor the `multi-column` strategy, you can use in order to hide columns for groups which are currently not visible.\n\n\n\n```ts files=[\"$DOCS/reference/hideEmptyGroupColumns-example.page.tsx\",\"$DOCS/reference/employee-columns.ts\"]\n\n```\n\n\n\n\n\nYou can specify an `id` for group columns. This is helpful if you want to size those columns (via ) or pin them (via ) or configure them in other ways. If no `id` is specified, it will be generated like this: `\"group-by-${field}\"`\n\n\n\n### Single group column\n\nYou can group by multiple fields, yet only render a single group column. To choose this rendering strategy, specify property to be `single-column` (or specify as an object.)\n\nIn this case, you can't override the group column for each group field, as there's only one group column being generated. However, you can specify a property to customize the generated column.\n\n\n\nBy default the generated group column will \"inherit\" many of the properties (the column style or className or renderers) of the columns corresponding to the group fields (if such columns exist, because it's not mandatory that they are defined).\n\n\n\n\n\n```ts files=[\"row-grouping-single-column-example.page.tsx\",\"columns.ts\"]\n\n```\n\n\n\n\n\nIf is specified to an object and no is passed, the render strategy will be `single-column`.\n\n can also be a function, which allows you to individually customize each group column - in case the `multi-column` strategy is used.\n\n\n\n\n\nYou can specify an `id` for the single . This is helpful if you want to size this column (via ) or pin it (via ) or configure it in other ways. If no `id` is specified, it will default to `\"group-by\"`.\n\n\n\n## Customizing the group column\n\nThere are many ways to customize the group column(s) and we're going to show a few of them below:\n\n### Binding the group column to a `field`\n\nBy default, group columns only show values in the group rows - but they are normal columns, so why not bind them to a field of the `DATA_TYPE`?\n\n```tsx {6,11}\nconst groupColumn = {\n id: 'the-group', // can specify an id\n style: {\n color: 'tomato',\n },\n field: 'firstName', // non-group rows will render the first name\n};\nconst columns = {\n theFirstName: {\n field: 'firstName',\n style: {\n // this style will also be applied in the group column,\n // since it is bound to this same `field`\n fontWeight: 'bold',\n },\n },\n};\n```\n\nThis makes the column display the value of the `field` in non-group/normal rows. Also, if you have another column bound to that `field`, the renderers/styling of that column will be used for the value of the group column, in non-group rows.\n\n\n\n```ts file=\"$DOCS/reference/bind-group-column-to-field-example.page.tsx\"\n\n```\n\n\n\n### Use `groupColumn` to customize rendering\n\nThe will inherit its own rendering and styling from the columns that are bound to the fields used in . However, you can override any of those properties so you have full control over the rendering process.\n\n```tsx {3,6}\nconst groupColumn = {\n field: 'firstName',\n renderGroupValue: ({ value }) => {\n return `Group: ${value}`;\n },\n renderLeafValue: ({ value }) => {\n return `First name: ${value}`;\n },\n};\n```\n\n\n\n\n\nThe column that renders the `firstName` has a custom renderer that adds a `.` at the end.\nThe group column is bound to the same `firstName` field, but specifies a different renderer, which will be used instead.\n\n\n\n```ts file=\"$DOCS/reference/group-column-custom-renderers-example.page.tsx\"\n\n```\n\n\n\n\n\nLearn more about customizing column rendering via multiple renderer functions.\n\n\n\n## Hiding columns when grouping\n\nWhen grouping is enabled, you can choose to hide some columns. Here are the two main ways to do this:\n\n- use - this will make columns bound to the group fields be hidden when grouping is active\n- use (also available on the column types, as ) - this is a column-level property, so you have more fine-grained control over what is hidden and when.\n\nValid values for are:\n\n- `\"*\"` - when any grouping is active, hide the column that specifies this property\n- `true` - when the field this column is bound to is used in grouping, hides this column\n- `keyof DATA_TYPE` - specify an exact field that, when grouped by, makes this column be hidden\n- `{[k in keyof DATA_TYPE]: true}` - an object that can specify more fields. When there is grouping by any of those fields, the current column gets hidden.\n\n\n\n\n\nIn this example, the column bound to `firstName` field is set to hide when any grouping is active, since the group column is anyways found to the `firstName` field.\n\nIn addition, is set to `true`, so the `stack` and `preferredLanguage` columns are also hidden, since they are grouped by.\n\n\n\n```ts file=\"$DOCS/reference/hide-columns-when-grouping-example.page.tsx\"\n\n```\n\n\n\n## Sorting the group column\n\nWhen groupRenderStrategy=\"single-column\" is used, the group column is sortable by default if all the columns that are involved in grouping are sortable. Sorting the group column makes the `sortInfo` have a value that looks like this:\n\n```ts\nconst sortInfo = [\n {\n dir: 1,\n id: 'group-by',\n field: ['stack', 'age'],\n type: ['string', 'number'],\n },\n];\n```\n\ngroupRenderStrategy=\"multi-column\", each group column is sortable by default if the column with the corresponding field is sortable.\n\n \n\nThe property can be used to override the default behavior.\n\n \n\n\n\n```ts file=\"$DOCS/reference/group-column-sorted-initially-example.page.tsx\"\n\n```\n\n\n\n\n\nWhen a group column is configured and the `groupBy` fields are not bound to actual columns in the table, the group column will not be sortable by default.\n\nIf you want to make it sortable, you have to specify a array, of the same length as the `groupBy` array, that specifies the sort type for each group field.\n\n\n\n## Aggregations\n\nWhen grouping, you can also aggregate the values of the grouped rows. This is done via the DataSource.aggregationReducers property. See the example below\n\n\n\n```ts file=\"grouping-with-aggregations-example.page.tsx\"\n\n```\n\n\n\nEach reducer from the `aggregationReducers` map can have the following properties:\n\n- `field` - the field to aggregate on\n- `getter(data)` - a value-getter function, if the aggregation values are are not mapped directly to a `field`\n- `initialValue` - the initial value to start with when computing the aggregation (for client-side aggregations only)\n- `reducer: string | (acc, current, data: DATA_TYPE, index)=>value` - the reducer function to use when computing the aggregation (for client-side aggregations only). For server-side aggregations, this will be a `string`\n- `done(value, arr)` - a function that is called when the aggregation is done (for client-side aggregations only) and returns the final value of the aggregation\n- `name` - useful especially in combination with , as it will be used as the pivot column header.\n\nIf an aggregation reducer is bound to a `field` in the dataset, and there is a column mapped to the same `field`, that column will show the corresponding aggregation value for each group row, as shown in the example above.\n\n\n\nIf you want to prevent the user to expand the last level of group rows, you can override the `render` function for the group column\n\n\n\n```ts file=\"grouping-with-aggregations-discard-expand-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\nDive deeper into the aggregation reducers and how they work.\n\n\n\n## Server side grouping with lazy loading\n\nLazy loading becomes all the more useful when working with grouped data.\n\nThe `DataSource` function is called with an object that has all the information about the current `DataSource` state(grouping/pivoting/sorting/lazy-loading, etc) - see the paragraphs above for details.\n\nServer side grouping needs two kinds of data responses in order to work properly:\n\n- response for **non-leaf row groups** - these are groups that have children. For such groups (including the top-level group), the `DataSource.data` function must return a promise that's resolved to an object with the following properties:\n - `totalCount` - the total number of records in the group\n - `data` - an array of objects that describes non-leaf child groups, each object has the following properties:\n - `keys` - an array of the group keys (usually strings) that uniquely identifies the group, from the root to the current group\n - `data` - an object that describes the common properties of the group\n - `aggregations` - an object that describes the aggregations for the current group\n- response for **leaf rows** - these are normal rows - rows that would have been served in the non-grouped response. The resolved object should have the following properties:\n - `data` - an array of objects that describes the rows\n - `totalCount` - the total number of records on the server, that are part of the current group\n\nHere's an example, that assumes grouping by `country` and `city` and aggregations by `age` and `salary` (average values):\n\n```tsx\n//request:\ngroupKeys: [] // empty keys array, so it's a top-level group\ngroupBy: [{\"field\":\"country\"},{\"field\":\"city\"}]\nreducers: [{\"field\":\"salary\",\"id\":\"avgSalary\",\"name\":\"avg\"},{\"field\":\"age\",\"id\":\"avgAge\",\"name\":\"avg\"}]\n// lazyLoadStartIndex: 0, - passed if lazyLoad is configured with a batchSize\n// lazyLoadBatchSize: 20 - passed if lazyLoad is configured with a batchSize\n\n//response\n{\n cache: true,\n totalCount: 20,\n data: [\n {\n data: {country: \"Argentina\"},\n aggregations: {avgSalary: 20000, avgAge: 30},\n keys: [\"Argentina\"],\n },\n {\n data: {country: \"Australia\"},\n aggregations: {avgSalary: 25000, avgAge: 35},\n keys: [\"Australia\"],\n }\n //...\n ]\n}\n```\n\nNow let's expand the first group and see how the request/response would look like:\n\n```tsx\n\n//request:\ngroupKeys: [\"Argentina\"]\ngroupBy: [{\"field\":\"country\"},{\"field\":\"city\"}]\nreducers: [{\"field\":\"salary\",\"id\":\"avgSalary\",\"name\":\"avg\"},{\"field\":\"age\",\"id\":\"avgAge\",\"name\":\"avg\"}]\n\n//response\n{\n totalCount: 4,\n data: [\n {\n data: {country: \"Argentina\", city: \"Buenos Aires\"},\n aggregations: {avgSalary: 20000, avgAge: 30},\n keys: [\"Argentina\", \"Buenos Aires\"],\n },\n {\n data: {country: \"Argentina\", city: \"Cordoba\"},\n aggregations: {avgSalary: 25000, avgAge: 35},\n keys: [\"Argentina\", \"Cordoba\"],\n },\n //...\n ]\n}\n```\n\nFinally, let's have a look at the leaf/normal rows and a request for them:\n\n```tsx\n\n//request\ngroupKeys: [\"Argentina\",\"Buenos Aires\"]\ngroupBy: [{\"field\":\"country\"},{\"field\":\"city\"}]\nreducers: [{\"field\":\"salary\",\"id\":\"avgSalary\",\"name\":\"avg\"},{\"field\":\"age\",\"id\":\"avgAge\",\"name\":\"avg\"}]\n\n//response\n{\n totalCount: 20,\n data: [\n {\n id: 34,\n country: \"Argentina\",\n city: \"Buenos Aires\",\n age: 30,\n salary: 20000,\n stack: \"full-stack\",\n firstName: \"John\",\n //...\n },\n {\n id: 35,\n country: \"Argentina\",\n city: \"Buenos Aires\",\n age: 35,\n salary: 25000,\n stack: \"backend\",\n firstName: \"Jane\",\n //...\n },\n //...\n ]\n}\n```\n\n\n\nWhen a row group is expanded, since `InfiniteTable` has the group `keys` from the previous response when the node was loaded, it will use the `keys` array and pass them to the `DataSource.data` function when requesting for the children of the respective group.\n\nYou know when to serve last-level rows, because in that case, the length of the `groupKeys` array will be equal to the length of the `groupBy` array.\n\n\n\n\n\n```ts file=\"server-side-grouping-with-lazy-load-example.page.tsx\"\n\n```\n\n\n\n## Eager loading for group row nodes\n\nWhen using lazy-loading together with batching, node data (without children) is loaded when a node (normal or grouped) comes into view. Only when a group node is expanded will its children be loaded. However, you can do this loading eagerly, by using the `dataset` property on the node you want to load.\n\n\n\nThis can be useful in combination with using `dataParams.groupRowsState` from the function - so your datasource can know which groups are expanded, and thus it can serve those groups already loaded with children.\n\n\n\n```tsx {18}\n//request:\ngroupKeys: [] // empty keys array, so it's a top-level group\ngroupBy: [{\"field\":\"country\"},{\"field\":\"city\"}]\nreducers: [{\"field\":\"salary\",\"id\":\"avgSalary\",\"name\":\"avg\"},{\"field\":\"age\",\"id\":\"avgAge\",\"name\":\"avg\"}]\n// lazyLoadStartIndex: 0, - passed if lazyLoad is configured with a batchSize\n// lazyLoadBatchSize: 20 - passed if lazyLoad is configured with a batchSize\n\n//response\n{\n cache: true,\n totalCount: 20,\n data: [\n {\n data: {country: \"Argentina\"},\n aggregations: {avgSalary: 20000, avgAge: 30},\n keys: [\"Argentina\"],\n // NOTE this dataset property used for eager-loading of group nodes\n dataset: {\n // the shape of the dataset is the same as the one normally returned by the datasource\n cache: true,\n totalCount: 4,\n data: [\n {\n data: {country: \"Argentina\", city: \"Buenos Aires\"},\n aggregations: {avgSalary: 20000, avgAge: 30},\n keys: [\"Argentina\", \"Buenos Aires\"],\n },\n {\n data: {country: \"Argentina\", city: \"Cordoba\"},\n aggregations: {avgSalary: 25000, avgAge: 35},\n keys: [\"Argentina\", \"Cordoba\"],\n },\n ]\n }\n },\n {\n data: {country: \"Australia\"},\n aggregations: {avgSalary: 25000, avgAge: 35},\n keys: [\"Australia\"],\n }\n //...\n ]\n}\n```\n" - }, - { - "filePath": "/docs/learn/grouping-and-pivoting/index", - "routePath": "/docs/learn/grouping-and-pivoting/", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/learn/grouping-and-pivoting/index.page.md", - "fileName": "index.page.md", - "folderPath": "/docs/learn/grouping-and-pivoting/", - "frontmatter": { - "title": "Grouping and Pivoting" - }, - "excerpt": "Infinite Table comes with grouping and pivoting capabilities built-in. The `DataSource` component does the actual data grouping and pivoting - while the `InfiniteTable` component does the specialized rendering.", - "readingTime": "1 min read", - "content": "\nInfinite Table comes with grouping and pivoting capabilities built-in. The `DataSource` component does the actual data grouping and pivoting - while the `InfiniteTable` component does the specialized rendering.\n\n\n\nLearn row grouping and explore the possibilities.\n\n\nRead thorough documentation covering pivoting and aggregation.\n\n\n\n\n\n```ts files=[\"row-grouping-example.page.tsx\",\"columns.ts\"]\n\n```\n\n\n" - }, { "filePath": "/docs/learn/keyboard-navigation/keyboard-shortcuts", "routePath": "/docs/learn/keyboard-navigation/keyboard-shortcuts", @@ -707,43 +667,44 @@ "content": "\nTo enable keyboard navigation for table rows, specify keyboardNavigation=\"row\" in your React Infinite Table component.\n\nWhen row navigation is enabled, clicking a row highlights it and the user can use the arrow keys to navigate the table rows.\n\n\n\n\n\nClick on the table and use the arrow keys to navigate the rows.\n\n\n\n```ts file=\"navigating-rows-initial-example.page.tsx\"\n\n```\n\n\n\n\n\n- Use `ArrowUp` and `ArrowDown` to navigate to the previous and next row.\n- Use `PageUp` and `PageDown` to navigate the rows vertically by pages (a page is considered equal to the visible row count).\n- Use `Home` and `End` to navigate vertically to the first and last row respectively\n\n\n\nOther possible values for the prop, besides `\"row\"`, are `\"cell\"` and `false`.\n\n## Using a default active row\n\nYou can also specify an initial active row, by using defaultActiveRowIndex=2. This tells the table that there should be a default active row, namely the one at index 2 (so the third row).\n\n\n\n\n\nThis example starts with row at index `2` already active.\n\n\n\n```ts file=\"navigating-rows-uncontrolled-example.page.tsx\"\n\n```\n\n\n\n## Listening to active row changes\n\nYou can easily listen to changes in the row navigation by using the callback.\n\n\n\nWhen you use controlled , make sure to use onActiveRowIndexChange to update the prop value, as otherwise the component will not update on navigation\n\n\n\n\n\n\n\nThis example starts with row at index `2` already active and uses onActiveRowIndexChange to update .\n\n\n\n```ts file=\"navigating-rows-controlled-example.page.tsx\"\n\n```\n\n\n\n## Toggling group rows\n\nWhen the DataSource is grouped, you can use the keyboard to collapse/expand group rows, by pressing the `Enter` key on the active row.\n\n\n\nSince you're in row navigation mode, you can also use\n\n- `โ†` to collapse a group row\n- `โ†’` to expand a group row\n\n\n\n\n\n\n\nPress the `Enter` key on the active group row to toggle it. `ArrowLeft` will collapse a group row and `ArrowRight` will expand a group row.\n\n\n\n```ts file=\"$DOCS/reference/keyboard-toggle-group-rows.page.tsx\"\n\n```\n\n\n\n## Selecting Rows with the Keyboard\n\nWhen is enabled (read more about it in the [row selection page](../selection/row-selection)), you can use the spacebar key to select a group row (or `shift` + spacebar to do multiple selection).\n\nBy default is enabled, so you can use the **spacebar** key to select multiple rows, when selectionMode=\"multi-row\". Using the spacebar key is equivalent to doing a mouse click, so expect the combination of **spacebar** + `cmd`/`ctrl`/`shift` modifier keys to behave just like clicking + the same modifier keys.\n\n\n\n\n\nUse spacebar + optional `cmd`/`ctrl`/`shift` modifier keys just like you would do clicking + the same modifier keys.\n\n\n\n```ts file=\"$DOCS/reference/default-selection-mode-multi-row-keyboard-toggle-example-row-navigation.page.tsx\"\n\n```\n\n\n\n\n\nFor selection all the rows in the table, you can use `cmd`/`ctrl` + `A` keyboard shortcut.\n\n\n\n\n\nKeyboard selection is also possible when there's a column configured with checkbox selection - [make sure you read more about it](../selection/row-selection#using-a-selection-checkbox).\n\n\n\n## Theming\n\nBy default, the style of the element that highlights the active row is the same style as that of the element that highlights the active cell.\n\nThe easiest is to override the style is via those three CSS variables:\n\n- `--infinite-active-cell-border-color--r` - the `red` component of the border color\n- `--infinite-active-cell-border-color--g` - the `green` component of the border color\n- `--infinite-active-cell-border-color--b` - the `blue` component of the border color\n\nThe initial values for those are `77`, `149` and`215` respectively, so the border color is `rgb(77, 149, 215)`.\n\nIn addition, the background color of the element that highlights the active row is set to the same color as the border color (computed based on the above `r`, `g` and `b` variables), but with an opacity of `0.25`, configured via the `--infinite-active-row-background-alpha` CSS variable.\n\nWhen the table is not focused, the opacity for the background color is set to `0.1`, which is the default value of the `--infinite-active-row-background-alpha--table-unfocused` CSS variable.\n\n\n \nTo summarize, use\n\n- `--infinite-active-cell-border-color--r`\n- `--infinite-active-cell-border-color--g`\n- `--infinite-active-cell-border-color--b`\n\nto control border and background color of the active row highlight element.\n\nNo, it's not a mistake that the element that highlights the active row is configured via the same CSS variables as the element that highlights the active cell. This is deliberate - so override CSS variables for cell, and those are propagated to the row highlight element.\n\n\n\nThere are other CSS variables as well, that give you fined-tuned control over both the border and background color for the active row, if you don't want to use the above three variables to propagate the same color across both border and background.\n\n- `--infinite-active-cell-background` - the background color. If you use this, you need to set opacity yourself. Applied for both cell and row.\n- `--infinite-active-row-background` - the background color. If you use this, you need to set opacity yourself. If this is specified, it takes precendence over `--infinite-active-cell-background`\n- `--infinite-active-cell-background` - the background color. If you use this, you need to set opacity yourself. Applied for both cell and row.\n- `--infinite-active-row-background` - the background color. If this is specified, it takes precedence over `--infinite-active-cell-background`\n- `--infinite-active-row-border` - border configuration (eg:`2px solid magenta`). If you use this, it will not be propagated to the background color.\n\nFor more details on the CSS variables, see the [CSS Variables documentation](../theming/css-variables##active-row-background).\n\n\n\n\n\nUse the color picker to configured the desired color for the active row highlight\n\n\n\n```ts file=\"navigating-rows-theming-example.page.tsx\"\n\n```\n\n\n" }, { - "filePath": "/docs/learn/rows/disabled-rows", - "routePath": "/docs/learn/rows/disabled-rows", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/learn/rows/disabled-rows.page.md", - "fileName": "disabled-rows.page.md", - "folderPath": "/docs/learn/rows/", + "filePath": "/docs/learn/grouping-and-pivoting/group-aggregations", + "routePath": "/docs/learn/grouping-and-pivoting/group-aggregations", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/learn/grouping-and-pivoting/group-aggregations.page.md", + "fileName": "group-aggregations.page.md", + "folderPath": "/docs/learn/grouping-and-pivoting/", "frontmatter": { - "title": "Disabled Rows" + "title": "Aggregations", + "description": "Learn how to define & use aggregations on grouped rows in Infinite Table for React." }, - "excerpt": "Disabling rows allows you to have some rows that are not selectable, not clickable, not reacheable via keyboard navigation and other interactions.", - "readingTime": "2 min read", - "content": "\nDisabling rows allows you to have some rows that are not selectable, not clickable, not reacheable via keyboard navigation and other interactions.\n\nThe `DataSource` manages the disabled state of rows, via the (uncontrolled) prop and (controlled) prop.\n\n```tsx\n\n idProperty=\"id\"\n data={[]}\n defaultRowDisabledState={{\n enabledRows: true,\n disabledRows: ['id1', 'id4', 'id5']\n }}\n/>\n \n {/* ... */}\n />\n\n```\n\n\n\nIn addition to using the / props, you can also specify the function prop, which overrides those other props and ultimately determines whether a row is disabled or not.\n\n\n\n\n\n```tsx file=\"initialRowDisabledState-example.page.tsx\"\n```\n\n\n\n## Using disabled rows while rendering\n\nWhen rendering a cell, you have access to the row disabled state - the type has a `rowDisabled` property which is true if the row is disabled.\n\n\n\n\n This example uses custom rendering for the `firstName` column to render an emoji for disabled rows.\n\n\n```tsx file=\"custom-rendering-for-disabled-rows-example.page.tsx\"\n```\n\n\n\n## Using the API to enable/disable rows\n\nYou can use the `DataSourceApi` to enable or disable rows programmatically.\n\n\n\n```tsx\ndataSourceApi.setRowEnabled(rowId, enabled);\n\n```\n\n\n\n```tsx\ndataSourceApi.setRowEnabledAt(rowIndex, enabled);\n```\n\n\n\n\nUse the context menu on each row to toggle the disabled state of the respective row.\n\n\n```tsx file=\"using-api-to-disable-rows-example.page.tsx\"\n```\n\n" + "excerpt": "A natural next step when grouping data is **aggregating the grouped values**. We allow developers to define any number of aggregations and bind them to any column.", + "readingTime": "8 min read", + "content": "\nA natural next step when grouping data is **aggregating the grouped values**. We allow developers to define any number of aggregations and bind them to any column.\n\nThe aggregations are defined on the `` component and are easily available at render time. A client-side aggregation needs a reducer function that accumulates the values in the data array and computes the final result.\n\n\n\nThroughout the docs, we might refer to aggregations as reducers - which, more technically, they are, since they reduce an array of values (from a group) to a single value.\n\n\n\n## Client-Side Aggregations\n\nWhen using client-side aggregation, each aggregation can have the following:\n\n### An initial value\n\nThe `initialValue` is optional value to use as the initial (accumulator) value for the reducer function. You can think of aggregations as an \"enhanced\" version of [Array.reduce](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce), so initial value should sound familiar.\n\n\n\nThe `initialValue` can be a function - in this case it will be called to compute the initial value.\n\n\n\n### A reducer function\n\n`reducer` is the function to call for each value in the (grouped) data array. It is called with the following arguments:\n\n- `accumulator` - the value returned by the previous call to the reducer function, or the `initialValue` if this is the first call. You return the new accumulator value from this function.\n- `value` - the value of the current item in the data array. If the aggregation has a `field`, this is the value of that field in the current item. Otherwise, value is the result of calling the `reducer.getter(data)` function (if one exists) or null if no getter is defined.\n- `dataItem` - the current item in the data array.\n- `index` - the index of the current item in the data array.\n\n### A `field` property or a `getter` function\n\nFor simple use-cases of client-side aggregations, a `field` is the way to go. This defines the field property (from the DATA_TYPE) to which the aggregation is bound.\n\nFor more complex scenarios, the aggregation should have a `getter` function. If both a `field` and a `getter` are provided, the `getter` has higher priority and will be used.\n\nUse this `getter` function to compute the value the current item in the array brings to the aggregation.\n\n```tsx title=\"Aggregation_custom_getter_function\"\n// useful for retrieving nested values\n\ngetter: (dataItem: Developer) => data.salary.net;\n```\n\n\n\nFor using nested values inside aggregations, use the aggregation `getter` function.\n\n\n\n### A completion `done` function\n\nThe completion `done` function is optional - if specified, will be after iterating over all the values in the grouped data array. Can be used to change the final result of the aggregation. It is called with the following arguments:\n\n- `accumulator` - the value returned by the last call to the reducer function\n- `data` - the grouped data array.\n This is useful for computing averages, for example:\n\n```tsx title=\"Done function for avg reducer\"\ndone: (acc, data) => acc / data.length;\n```\n\n### Putting it all together\n\nLet's take a look at a simple example of aggregating two columns, one to display the avg and the other one should compute the sum of the salary column for grouped rows.\n\n```tsx title=\"Average Aggregation\"\nimport { DataSource, InfiniteTable } from '@infinite-table/infinite-react';\n\nconst sum = (a: number, b: number) => a + b;\n\nconst reducers = {\n avg: {\n initialValue: 0,\n field: 'age',\n reducer: sum,\n done: (acc, data) => Math.round(acc / data.length),\n },\n\n sumAgg: {\n initialValue: 0,\n field: 'salary',\n reducer: sum\n }\n}\n\nfunction App() {\n return \n aggregationReducers={reducers}\n >\n {...} />\n \n}\n```\n\nIn the above example, note that aggregations are an object where the keys of the object are used to identify the aggregation and the values are the aggregation configuration objects, as described above.\n\n\n\nAt run-time, you have access to the aggregation reducer results inside group rows - you can use the `rowInfo.reducerResults` object to access those values. For the example above, you change how group rows are rendered for a certain column and display the aggregation results in a custom way:\n\n```tsx {9} title=\"Custom_group_row_rendering_for_the_country_column\"\n\ncountry: {\n field: 'country',\n\n // define a custom renderGroupValue fn for the country column\n\n renderGroupValue: ({ rowInfo }) => {\n const { reducerResults = {} } = rowInfo;\n // note the keys in the reducerResults objects match the keys in the aggregationReducers object\n return `Avg age: ${reducerResults.avg}, total salary ${reducerResults.sumAgg}`;\n },\n},\n```\n\n\n\n\n\n```ts file=\"aggregations-simple-example.page.tsx\"\n\n```\n\n\n\n## Server-Side Aggregations\n\nServer-side aggregations are defined in the same way as client-side aggregations (except the `reducer` function is missing), but the aggregation values are computed by the server and returned as part of the data response.\n\nFor computing the grouping and aggregations on the server, the backend needs to know the grouping and aggregation configuration. As such, Infinite Table will call the DataSource data function with an object that contains all the required info:\n\n- `groupBy` - the array of grouping fields, as passed to the `` component.\n- `pivotBy` - the array of pivot fields, as passed to the `` component.\n- `aggregationReducers` - the value of the prop, as configured on the `` component.\n- `sortInfo` - the current sorting information for the data.\n\nFor the lazy-loading use-case, there are other useful properties you can use from the object passed into the `data` function:\n\n- `groupKeys: string[]` - the group keys for the current group - the `data` fn is generally called lazily when the user expands a group row. This info is useful for fetching the data for a specific group.\n- `lazyLoadStartIndex` - provided when batching is also enabled via the prop. This is the index of the first item in the current batch.\n- `lazyLoadBatchSize` - also used when batching is enabled. This is the number of items in the current batch.\n\nBesides the above information, if filtering is used, a `fiterValue` is also made available.\n\nIn order to showcase the server-side aggregations, let's build an example similar to the above one, but let's lazily load group data.\n\n```tsx {2} title=\"DataSourcewith lazyLoad enabled\"\n\n```\n\nAs soon a grouping and aggregations are no longer computed on the client, your `data` function needs to send those configurations on the backend, so it needs to get a bit more complicated:\n\n```tsx title=\"Data_function_sending_configurations_to_the_backend\"\nconst data = ({ groupBy, aggregationReducers, sortInfo, groupKeys }) => {\n // it's important to send the current group keys - for top level, this will be []\n const args: string[] = [`groupKeys=${JSON.stringify(groupKeys)}`];\n\n // turn the sorting info into an array\n if (sortInfo && !Array.isArray(sortInfo)) {\n sortInfo = [sortInfo];\n }\n\n if (sortInfo) {\n // the backend expects the sort info to be an array of field,dir pairs\n args.push(\n 'sortInfo=' +\n JSON.stringify(\n sortInfo.map((s) => ({\n field: s.field,\n dir: s.dir,\n })),\n ),\n );\n }\n\n if (groupBy) {\n // for grouping, send an array of objects with the `field` property\n args.push(\n 'groupBy=' + JSON.stringify(groupBy.map((p) => ({ field: p.field }))),\n );\n }\n\n if (aggregationReducers) {\n args.push(\n 'reducers=' +\n JSON.stringify(\n // by convention, we send an array of reducers, each with `field` `name`(= \"avg\") and `id`\n // it's up to you to decide what the backend needs\n Object.keys(aggregationReducers).map((key) => ({\n field: aggregationReducers[key].field,\n id: key,\n name: aggregationReducers[key].reducer,\n })),\n ),\n );\n }\n\n const url = BASE_URL + `/developers10k-sql?` + args.join('&');\n return fetch(url).then(r=>r.json())\n}\n\n\n```\n\nWhen fetching without grouping (or with local grouping and aggregations), the `` component expects a flat array of data items coming from the server.\n\nHowever, when the grouping is happening server-side, the `` component expects a response that has the following shape:\n\n- `data` - the root array with grouping and aggregation info. Each item in the array should have the following:\n - `keys` - an array of the keys for the current group - eg `['USA']` or `['USA', 'New York']`\n - `data` - an object with all the common values for the group - eg `{ country: 'USA' }` or `{ country: 'USA', city: 'New York' }`\n - `aggregations` - an object with the aggregation values for the group - eg `{ age: 30, salary: 120300 }`. The keys in this object should match the keys in the object.\n - `pivot` - pivoting information for the current group - more on that on the dedicated [Pivoting page](./pivoting/overview).\n\nWhen the user is expanding the last level, in order to see the leaf rows, the shape of the response is expected to be the same as when there is no grouping - namely an array of data items or an object where the `data` property is an array of data items.\n\nLet's put all of this into a working example.\n\n\n\n\n\nThis showcases grouping and aggregations on the server - both the `age` and `salary` columns have an AVG aggregation defined.\n\nGrouping is done by the `country`, `city` and `stack` columns.\n\n\n\n```tsx file=\"grouping-and-aggregations-with-lazy-load-example.page.tsx\"\n\n```\n\n\n\n\n\nWhen the user is doing a sort on the table, the `` is fetched from scratch, but the expanded/collapsed state is preserved, and all the required groups that need to be re-fetched are reloaded as needed (if they are not eagerly included in the served data).\n\n\n" }, { - "filePath": "/docs/learn/rows/styling-rows", - "routePath": "/docs/learn/rows/styling-rows", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/learn/rows/styling-rows.page.md", - "fileName": "styling-rows.page.md", - "folderPath": "/docs/learn/rows/", + "filePath": "/docs/learn/grouping-and-pivoting/grouping-rows", + "routePath": "/docs/learn/grouping-and-pivoting/grouping-rows", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/learn/grouping-and-pivoting/grouping-rows.page.md", + "fileName": "grouping-rows.page.md", + "folderPath": "/docs/learn/grouping-and-pivoting/", "frontmatter": { - "title": "Styling Rows" + "title": "Grouping rows" }, - "excerpt": "Rows can be styled by using the `rowStyle` and the `rowClassName` props", - "readingTime": "1 min read", - "content": "\nRows can be styled by using the `rowStyle` and the `rowClassName` props\n\n- the prop can be a style `object` or a `function` that returns a style `object` or `undefined`\n- the prop can be a `string` (the name of a CSS class) or a `function` that returns a `string` or `undefined`\n\n```tsx title=\"Defining-a-rowStyle-function\"\nconst rowStyle: InfiniteTablePropRowStyle = ({\n data,\n rowInfo,\n}: {\n data: Employee | null;\n rowInfo: InfiniteTableRowInfo;\n}) => {\n const salary = data ? data.salary : 0;\n\n if (salary > 150_000) {\n return { background: 'tomato' };\n }\n if (rowInfo.indexInAll % 10 === 0) {\n return { background: 'lightblue', color: 'black' };\n }\n};\n```\n\n\n\nThe function prop has the same signature as the function prop.\n\n\n\n## Row styling example\n\n\n\n```ts files=[\"$DOCS/reference/rowStyle-example.page.tsx\",\"$DOCS/reference/rowStyle-example-columns.ts\"]\n\n```\n\n\n\n\n\nIn the function, you can access the rowInfo object, which contains information about the current row. It's especially useful when you have grouping and aggregation, as it contains the aggregation values and other extra info.\n\n\n" + "excerpt": "You can use any `field` available in the `DataSource` to do the grouping - it can even be a `field` that is not a column.", + "readingTime": "15 min read", + "content": "\nYou can use any `field` available in the `DataSource` to do the grouping - it can even be a `field` that is not a column.\n\n\n\nWhen using TypeScript, both `DataSource` and `InfiniteTable` components are generic and need to be rendered/instantiated with a `DATA_TYPE` parameter. The fields in that `DATA_TYPE` can then be used for grouping.\n\n\n\n```tsx\ntype Person = {\n name: string;\n age: number;\n country: string;\n id: string;\n}\n\nconst groupBy = [{field: 'country'}]\n\n groupBy={groupBy}>\n />\n\n\n```\n\nIn the example above, we're grouping by `country`, which is a field available in the `Person` type. Specifying a field not defined in the `Person` type would be a type error.\n\nAdditionally, a `column` object can be used together with the `field` to define how the group column should be rendered.\n\n```tsx {4}\nconst groupBy = [\n {\n field: 'country',\n column: {\n // custom column configuration for group column\n width: 150,\n header: 'Country group',\n },\n },\n];\n```\n\nThe example below puts it all together.\n\nAlso see the groupBy API reference to find out more.\n\n\n\n```ts file=\"row-grouping-example.page.tsx\"\n\n```\n\n\n\nIn `groupBy.column` you can use any column property - so, for example, you can define a custom `renderValue` function to customize the rendering.\n\n```tsx {5}\nconst groupBy = [\n {\n field: 'country',\n column: {\n renderValue: ({ value }) => <>Country: {value},\n },\n },\n];\n```\n\n\n\nThe generated group column(s) - can be one for all groups or one for each group - will inherit the `style`/`className`/renderers from the columns corresponding to the group fields themselves (if those columns exist).\n\nAdditionally, there are other ways to override those inherited configurations, in order to configure the group columns:\n\n- use to specify how each grouping column should look for the respective field (in case of groupRenderStrateg=\"multi-column\")\n- use prop\n - can be used as an object - ideal for when you have simple requirements and when groupRenderStrateg=\"single-column\"\n - as a function that returns a column configuration - can be used like this in either single or multiple group render strategy\n\n\n\n## Controlling the collapse/expand state\n\nWhen you do grouping, by default, all row groups are expanded. Of course you have full control over this and you do this via the / props.\n\nIf you simply want to specify the initial expanded/collapsed state, you should use the prop.\n\n```tsx title=\"Specifying the default state for group rows\"\nconst defaultGroupRowsState: DataSourcePropGroupRowsStateObject = {\n collapsedRows: true,\n expandedRows: [['Mexico'], ['Mexico', 'backend'], ['India']],\n};\n```\n\nThe two properties in this object are `collapsedRows` and `expandedRows`, and each can have the following values:\n - `true` - meaning that all groups have this state\n - an array of arrays - representing the exceptions to the default value\n\n\nSo if you have `collapsedRows` set to `true` and then `expandedRows` set to `[['Mexico'], ['Mexico', 'backend'], ['India']]` then all rows are collapsed by default, except the rows specified in the `expandedRows`.\n\n\n\n\n```ts file=\"row-grouping-state-example.page.tsx\"\n\n```\n\n\n\n\n\nYou can specify expand/collapse state at any level of nesting.\n\nLet's suppose by default all rows are collapsed - if you want a node to be visible then you have to specify all its parents as expanded.\n\nSo having this\n```tsx \nconst defaultGroupRowsState = {\n collapsedRows: true,\n expandedRows: [['Mexico', 'backend']],\n};\n```\nwill show all rows as collapsed, and just as soon as you expand `Mexico` you will see the `backend` group row for Mexico to be expanded.\n\n\nThis data format gives you ultimate flexibility and allows you to easily restore an expand/collpase state at a later time, if you wanted to.\n\n\nIf you use the controlled , make sure you update it by leveraging the callback prop.\n\n\n\n\n\n```ts file=\"row-grouping-state-controlled-example.page.tsx\"\n\n```\n\n\n\nIn addition to simple objects with the shape described above, the / can also be instanges of `GroupRowsState` class, which is exported by the Infinite Table package. This class is simply a wrapper around those objects, but it gives you additional utility methods.\n\n\n\nThe callback gives you an instance of back as the single argument. If you're using plain objects, just do `groupRowsState.getState()` and you'll get the corresponding plain object for the current expand/collapse state.\n\n give you some additional helper methods, which you can read about here\n\n\n## Grouping strategies\n\nMultiple grouping strategies are supported by, `InfiniteTable` DataGrid:\n\n- multi column mode - multiple group columns are generated, one for each specified group field\n- single column mode - a single group column is generated, even when there are multiple group fields\n\nYou can specify the rendering strategy explicitly by setting the property to any of the following: `multi-column`, `single-column`. If you don't set it explicitly, it will choose the best default based on your configuration.\n\n### Multiple groups columns\n\nWhen grouping by multiple fields, by default the component will render a group column for each group field\n\n```tsx\nconst groupBy = [\n {\n field: 'age',\n column: {\n width: 100,\n renderValue: ({ value }) => <>Age: {value},\n },\n },\n {\n field: 'companyName',\n },\n {\n field: 'country',\n },\n];\n```\n\nLet's see an example of how the component would render the table with the multi-column strategy.\n\n\n\n```ts files=[\"row-grouping-multi-column-example.page.tsx\",\"columns.ts\"]\n\n```\n\n\n\nFor the `multi-column` strategy, you can use in order to hide columns for groups which are currently not visible.\n\n\n\n```ts files=[\"$DOCS/reference/hideEmptyGroupColumns-example.page.tsx\",\"$DOCS/reference/employee-columns.ts\"]\n\n```\n\n\n\n\n\nYou can specify an `id` for group columns. This is helpful if you want to size those columns (via ) or pin them (via ) or configure them in other ways. If no `id` is specified, it will be generated like this: `\"group-by-${field}\"`\n\n\n\n### Single group column\n\nYou can group by multiple fields, yet only render a single group column. To choose this rendering strategy, specify property to be `single-column` (or specify as an object.)\n\nIn this case, you can't override the group column for each group field, as there's only one group column being generated. However, you can specify a property to customize the generated column.\n\n\n\nBy default the generated group column will \"inherit\" many of the properties (the column style or className or renderers) of the columns corresponding to the group fields (if such columns exist, because it's not mandatory that they are defined).\n\n\n\n\n\n```ts files=[\"row-grouping-single-column-example.page.tsx\",\"columns.ts\"]\n\n```\n\n\n\n\n\nIf is specified to an object and no is passed, the render strategy will be `single-column`.\n\n can also be a function, which allows you to individually customize each group column - in case the `multi-column` strategy is used.\n\n\n\n\n\nYou can specify an `id` for the single . This is helpful if you want to size this column (via ) or pin it (via ) or configure it in other ways. If no `id` is specified, it will default to `\"group-by\"`.\n\n\n\n## Customizing the group column\n\nThere are many ways to customize the group column(s) and we're going to show a few of them below:\n\n### Binding the group column to a `field`\n\nBy default, group columns only show values in the group rows - but they are normal columns, so why not bind them to a field of the `DATA_TYPE`?\n\n```tsx {6,11}\nconst groupColumn = {\n id: 'the-group', // can specify an id\n style: {\n color: 'tomato',\n },\n field: 'firstName', // non-group rows will render the first name\n};\nconst columns = {\n theFirstName: {\n field: 'firstName',\n style: {\n // this style will also be applied in the group column,\n // since it is bound to this same `field`\n fontWeight: 'bold',\n },\n },\n};\n```\n\nThis makes the column display the value of the `field` in non-group/normal rows. Also, if you have another column bound to that `field`, the renderers/styling of that column will be used for the value of the group column, in non-group rows.\n\n\n\n```ts file=\"$DOCS/reference/bind-group-column-to-field-example.page.tsx\"\n\n```\n\n\n\n### Use `groupColumn` to customize rendering\n\nThe will inherit its own rendering and styling from the columns that are bound to the fields used in . However, you can override any of those properties so you have full control over the rendering process.\n\n```tsx {3,6}\nconst groupColumn = {\n field: 'firstName',\n renderGroupValue: ({ value }) => {\n return `Group: ${value}`;\n },\n renderLeafValue: ({ value }) => {\n return `First name: ${value}`;\n },\n};\n```\n\n\n\n\n\nThe column that renders the `firstName` has a custom renderer that adds a `.` at the end.\nThe group column is bound to the same `firstName` field, but specifies a different renderer, which will be used instead.\n\n\n\n```ts file=\"$DOCS/reference/group-column-custom-renderers-example.page.tsx\"\n\n```\n\n\n\n\n\nLearn more about customizing column rendering via multiple renderer functions.\n\n\n\n## Hiding columns when grouping\n\nWhen grouping is enabled, you can choose to hide some columns. Here are the two main ways to do this:\n\n- use - this will make columns bound to the group fields be hidden when grouping is active\n- use (also available on the column types, as ) - this is a column-level property, so you have more fine-grained control over what is hidden and when.\n\nValid values for are:\n\n- `\"*\"` - when any grouping is active, hide the column that specifies this property\n- `true` - when the field this column is bound to is used in grouping, hides this column\n- `keyof DATA_TYPE` - specify an exact field that, when grouped by, makes this column be hidden\n- `{[k in keyof DATA_TYPE]: true}` - an object that can specify more fields. When there is grouping by any of those fields, the current column gets hidden.\n\n\n\n\n\nIn this example, the column bound to `firstName` field is set to hide when any grouping is active, since the group column is anyways found to the `firstName` field.\n\nIn addition, is set to `true`, so the `stack` and `preferredLanguage` columns are also hidden, since they are grouped by.\n\n\n\n```ts file=\"$DOCS/reference/hide-columns-when-grouping-example.page.tsx\"\n\n```\n\n\n\n## Sorting the group column\n\nWhen groupRenderStrategy=\"single-column\" is used, the group column is sortable by default if all the columns that are involved in grouping are sortable. Sorting the group column makes the `sortInfo` have a value that looks like this:\n\n```ts\nconst sortInfo = [\n {\n dir: 1,\n id: 'group-by',\n field: ['stack', 'age'],\n type: ['string', 'number'],\n },\n];\n```\n\ngroupRenderStrategy=\"multi-column\", each group column is sortable by default if the column with the corresponding field is sortable.\n\n \n\nThe property can be used to override the default behavior.\n\n \n\n\n\n```ts file=\"$DOCS/reference/group-column-sorted-initially-example.page.tsx\"\n\n```\n\n\n\n\n\nWhen a group column is configured and the `groupBy` fields are not bound to actual columns in the table, the group column will not be sortable by default.\n\nIf you want to make it sortable, you have to specify a array, of the same length as the `groupBy` array, that specifies the sort type for each group field.\n\n\n\n## Aggregations\n\nWhen grouping, you can also aggregate the values of the grouped rows. This is done via the DataSource.aggregationReducers property. See the example below\n\n\n\n```ts file=\"grouping-with-aggregations-example.page.tsx\"\n\n```\n\n\n\nEach reducer from the `aggregationReducers` map can have the following properties:\n\n- `field` - the field to aggregate on\n- `getter(data)` - a value-getter function, if the aggregation values are are not mapped directly to a `field`\n- `initialValue` - the initial value to start with when computing the aggregation (for client-side aggregations only)\n- `reducer: string | (acc, current, data: DATA_TYPE, index)=>value` - the reducer function to use when computing the aggregation (for client-side aggregations only). For server-side aggregations, this will be a `string`\n- `done(value, arr)` - a function that is called when the aggregation is done (for client-side aggregations only) and returns the final value of the aggregation\n- `name` - useful especially in combination with , as it will be used as the pivot column header.\n\nIf an aggregation reducer is bound to a `field` in the dataset, and there is a column mapped to the same `field`, that column will show the corresponding aggregation value for each group row, as shown in the example above.\n\n\n\nIf you want to prevent the user to expand the last level of group rows, you can override the `render` function for the group column\n\n\n\n```ts file=\"grouping-with-aggregations-discard-expand-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\nDive deeper into the aggregation reducers and how they work.\n\n\n\n## Server side grouping with lazy loading\n\nLazy loading becomes all the more useful when working with grouped data.\n\nThe `DataSource` function is called with an object that has all the information about the current `DataSource` state(grouping/pivoting/sorting/lazy-loading, etc) - see the paragraphs above for details.\n\nServer side grouping needs two kinds of data responses in order to work properly:\n\n- response for **non-leaf row groups** - these are groups that have children. For such groups (including the top-level group), the `DataSource.data` function must return a promise that's resolved to an object with the following properties:\n - `totalCount` - the total number of records in the group\n - `data` - an array of objects that describes non-leaf child groups, each object has the following properties:\n - `keys` - an array of the group keys (usually strings) that uniquely identifies the group, from the root to the current group\n - `data` - an object that describes the common properties of the group\n - `aggregations` - an object that describes the aggregations for the current group\n- response for **leaf rows** - these are normal rows - rows that would have been served in the non-grouped response. The resolved object should have the following properties:\n - `data` - an array of objects that describes the rows\n - `totalCount` - the total number of records on the server, that are part of the current group\n\nHere's an example, that assumes grouping by `country` and `city` and aggregations by `age` and `salary` (average values):\n\n```tsx\n//request:\ngroupKeys: [] // empty keys array, so it's a top-level group\ngroupBy: [{\"field\":\"country\"},{\"field\":\"city\"}]\nreducers: [{\"field\":\"salary\",\"id\":\"avgSalary\",\"name\":\"avg\"},{\"field\":\"age\",\"id\":\"avgAge\",\"name\":\"avg\"}]\n// lazyLoadStartIndex: 0, - passed if lazyLoad is configured with a batchSize\n// lazyLoadBatchSize: 20 - passed if lazyLoad is configured with a batchSize\n\n//response\n{\n cache: true,\n totalCount: 20,\n data: [\n {\n data: {country: \"Argentina\"},\n aggregations: {avgSalary: 20000, avgAge: 30},\n keys: [\"Argentina\"],\n },\n {\n data: {country: \"Australia\"},\n aggregations: {avgSalary: 25000, avgAge: 35},\n keys: [\"Australia\"],\n }\n //...\n ]\n}\n```\n\nNow let's expand the first group and see how the request/response would look like:\n\n```tsx\n\n//request:\ngroupKeys: [\"Argentina\"]\ngroupBy: [{\"field\":\"country\"},{\"field\":\"city\"}]\nreducers: [{\"field\":\"salary\",\"id\":\"avgSalary\",\"name\":\"avg\"},{\"field\":\"age\",\"id\":\"avgAge\",\"name\":\"avg\"}]\n\n//response\n{\n totalCount: 4,\n data: [\n {\n data: {country: \"Argentina\", city: \"Buenos Aires\"},\n aggregations: {avgSalary: 20000, avgAge: 30},\n keys: [\"Argentina\", \"Buenos Aires\"],\n },\n {\n data: {country: \"Argentina\", city: \"Cordoba\"},\n aggregations: {avgSalary: 25000, avgAge: 35},\n keys: [\"Argentina\", \"Cordoba\"],\n },\n //...\n ]\n}\n```\n\nFinally, let's have a look at the leaf/normal rows and a request for them:\n\n```tsx\n\n//request\ngroupKeys: [\"Argentina\",\"Buenos Aires\"]\ngroupBy: [{\"field\":\"country\"},{\"field\":\"city\"}]\nreducers: [{\"field\":\"salary\",\"id\":\"avgSalary\",\"name\":\"avg\"},{\"field\":\"age\",\"id\":\"avgAge\",\"name\":\"avg\"}]\n\n//response\n{\n totalCount: 20,\n data: [\n {\n id: 34,\n country: \"Argentina\",\n city: \"Buenos Aires\",\n age: 30,\n salary: 20000,\n stack: \"full-stack\",\n firstName: \"John\",\n //...\n },\n {\n id: 35,\n country: \"Argentina\",\n city: \"Buenos Aires\",\n age: 35,\n salary: 25000,\n stack: \"backend\",\n firstName: \"Jane\",\n //...\n },\n //...\n ]\n}\n```\n\n\n\nWhen a row group is expanded, since `InfiniteTable` has the group `keys` from the previous response when the node was loaded, it will use the `keys` array and pass them to the `DataSource.data` function when requesting for the children of the respective group.\n\nYou know when to serve last-level rows, because in that case, the length of the `groupKeys` array will be equal to the length of the `groupBy` array.\n\n\n\n\n\n```ts file=\"server-side-grouping-with-lazy-load-example.page.tsx\"\n\n```\n\n\n\n## Eager loading for group row nodes\n\nWhen using lazy-loading together with batching, node data (without children) is loaded when a node (normal or grouped) comes into view. Only when a group node is expanded will its children be loaded. However, you can do this loading eagerly, by using the `dataset` property on the node you want to load.\n\n\n\nThis can be useful in combination with using `dataParams.groupRowsState` from the function - so your datasource can know which groups are expanded, and thus it can serve those groups already loaded with children.\n\n\n\n```tsx {18}\n//request:\ngroupKeys: [] // empty keys array, so it's a top-level group\ngroupBy: [{\"field\":\"country\"},{\"field\":\"city\"}]\nreducers: [{\"field\":\"salary\",\"id\":\"avgSalary\",\"name\":\"avg\"},{\"field\":\"age\",\"id\":\"avgAge\",\"name\":\"avg\"}]\n// lazyLoadStartIndex: 0, - passed if lazyLoad is configured with a batchSize\n// lazyLoadBatchSize: 20 - passed if lazyLoad is configured with a batchSize\n\n//response\n{\n cache: true,\n totalCount: 20,\n data: [\n {\n data: {country: \"Argentina\"},\n aggregations: {avgSalary: 20000, avgAge: 30},\n keys: [\"Argentina\"],\n // NOTE this dataset property used for eager-loading of group nodes\n dataset: {\n // the shape of the dataset is the same as the one normally returned by the datasource\n cache: true,\n totalCount: 4,\n data: [\n {\n data: {country: \"Argentina\", city: \"Buenos Aires\"},\n aggregations: {avgSalary: 20000, avgAge: 30},\n keys: [\"Argentina\", \"Buenos Aires\"],\n },\n {\n data: {country: \"Argentina\", city: \"Cordoba\"},\n aggregations: {avgSalary: 25000, avgAge: 35},\n keys: [\"Argentina\", \"Cordoba\"],\n },\n ]\n }\n },\n {\n data: {country: \"Australia\"},\n aggregations: {avgSalary: 25000, avgAge: 35},\n keys: [\"Australia\"],\n }\n //...\n ]\n}\n```\n" }, { - "filePath": "/docs/learn/rows/using-row-info", - "routePath": "/docs/learn/rows/using-row-info", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/learn/rows/using-row-info.page.md", - "fileName": "using-row-info.page.md", - "folderPath": "/docs/learn/rows/", + "filePath": "/docs/learn/grouping-and-pivoting/index", + "routePath": "/docs/learn/grouping-and-pivoting/", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/learn/grouping-and-pivoting/index.page.md", + "fileName": "index.page.md", + "folderPath": "/docs/learn/grouping-and-pivoting/", "frontmatter": { - "title": "Using Rows at Runtime" + "title": "Grouping and Pivoting" }, - "excerpt": "At runtime, the function and a lot of other functions use the rowInfo object to access the current row and use it to decide how to render the current cell or row.", - "readingTime": "7 min read", - "content": "\nAt runtime, the function and a lot of other functions use the rowInfo object to access the current row and use it to decide how to render the current cell or row.\n\nThe `rowInfo` object has a few variations, depending on the presence or absence of grouping. See type definition here.\n\n\n\nAll those variations are discriminated in the `TypeScript` typings, so you can easily use the different types of `rowInfo` objects.\n\n\n\n## Ungrouped Scenario - normal `rowInfo`\n\nWhen there is no grouping, the `rowInfo` object has the following properties:\n\n- `data` - type: `DATA_TYPE`\n- `dataSourceHasGrouping` - type: `false`\n- `isGroupRow` - type: `false`\n- `id` - type: `any`. The id of the row, as defined by the prop.\n- `selfLoaded` - type: `boolean`. Useful in lazy-loading scenarios, when there is batching present. If you're not in such a scenario, the value will be `false`. You can use this to show a loading indicator for the row.\n- `indexInAll` - type `number`. The index of the row in the full dataset. Called like this because for grouping scenarios, there's also an `indexInGroup`\n\n### Discriminator\n\n```ts\nrowInfo.dataSourceHasGrouping === false;\n```\n\n## Grouped scenario - normal `rowInfo`\n\nWhen there is grouping defined, and the row is not a group row, the `rowInfo` object has the following properties:\n\n- `data` - type: `DATA_TYPE`\n- `dataSourceHasGrouping` - type: `true`\n- `isGroupRow` - type: `false`\n- `indexInAll` - like the above\n- `indexInGroup` - type: `number`. The index of the row in its parent group.\n- `groupKeys` - type: `any[]`, but usually it's actually `string[]`. For normal rows, the group keys will have all the keys starting from the topmost parent down to the last group row in the hierarchy (the direct parent of the current row).\n\n```txt\nExample: People grouped by country and city\n\n> Italy - country - groupKeys: ['Italy']\n > Rome - city - groupKeys: ['Italy', 'Rome']\n - Marco - person - groupKeys: ['Italy', 'Rome']\n - Luca - person - groupKeys: ['Italy', 'Rome']\n - Giuseppe - person - groupKeys: ['Italy', 'Rome']\n```\n\n- `groupBy` - type `(keyof T)[]`. Has the same structure as groupKeys, but it will contain the fields used to group the rows.\n- `rootGroupBy` - type `(keyof T)[]`. The groupBy value of the DataSource component, mapped to the `groupBy.field`\n- `parents` - a list of `rowInfo` objects that are the parents of the current row.\n- `indexInParentGroups[]` - type: `number[]`. See below for an example\n\n```\n> Italy - country - indexInParentGroups: [0]\n > Rome - city - indexInParentGroups: [0,0]\n - Marco - person - indexInParentGroups: [0,0,0]\n - Luca - person - indexInParentGroups: [0,0,1]\n - Giuseppe - person - indexInParentGroups: [0,0,2]\n> USA - country - indexInParentGroups: [1]\n > LA - city - indexInParentGroups: [1,0]\n - Bob - person - indexInParentGroups: [1,0,2]\n```\n\n- `groupCount` - type: `number`. The count of leaf rows that the current group (in this case, the parent group) contains\n- `groupNesting` - type `number`. The nesting of the parent group.\n- `collapsed` - type `boolean`.\n- `selfLoaded` - type: `boolean`. Useful in lazy-loading scenarios, when there is batching present. If you're not in such a scenario, the value will be `false`.\n\n### Discriminator\n\n```ts\nrowInfo.dataSourceHasGrouping === true && rowInfo.isGroupRow === false;\n```\n\n## Grouped scenario - group `rowInfo`\n\nWhen there is grouping defined, and the row is a group row, the `rowInfo` object has the following properties:\n\n- `data` - type: `Partial | null`. The `data` object that might be available is the result of the aggregation reducers. If none are specified, `data` will be `null`\n- `dataSourceHasGrouping` - type: `true`\n- `isGroupRow` - type: `true`\n- `error` - type: `string?`. If there was an error while loading the group (when the group row is expanded), this will contain the error message. If the group row was loaded with the `cache: true` flag sent in the server response, the error will remain on the `rowInfo` object even when you collapse the group row, otherwise, if `cache: true` was not present, the `error` property will be removed on collapse.\n- `indexInAll` - like the above\n- `indexInGroup` - type: `number`. The index of the row in the its parent group.\n- `deepRowInfoArray` - an array of `rowInfo` objects. This array contains all the (uncollapsed, so visible) row infos under this group, at any level of nesting, in the order in which they are visible in the table.\n- `reducerResults` - type `Record`. The result of the aggregation reducers for each field in the prop.\n- `groupCount` - type: `number`. The count of leaf rows that the current group (in this case, the parent group) contains\n- `groupData` - type: `DATA_TYPE[]`. The array of the data of all leaf nodes (normal nodes) that are inside this group.\n\n```txt\nExample: People grouped by country and city\n\n> Italy - country - groupKeys: ['Italy']\n > Rome - city - groupKeys: ['Italy', 'Rome']\n - Marco - person - groupKeys: ['Italy', 'Rome']\n - Luca - person - groupKeys: ['Italy', 'Rome']\n - Giuseppe - person - groupKeys: ['Italy', 'Rome']\n```\n\n- `collapsedChildrenCount` - type: `number`. The count of all leaf nodes (normal rows) inside the group that are not being visible due to collapsing (either the current row is collapsed or any of its children)\n- `directChildrenCount` - type: `number`. The count of the direct children of the current group. Direct children can be either normal rows or groups.\n- `directChildrenLoadedCount` - type: `number`. Like `directChildrenCount`, but only counts the rows that are loaded (when batched lazy loading is configured).\n- `childrenAvailable` - type: `boolean`. For lazy/batched grouping, this is true if the group has been expanded at least once. NOTE: if this is true, it doesn't mean that all the children have been loaded, it only means that at least some children have been loaded and are available. Use `directChildrenCount` and `directChildrenLoadedCount` to know if all the children have been loaded or not.\n- `childrenLoading` - type: `boolean`. Boolean flag that will be true while lazy loading direct children of the current row group. Use `directChildrenLoadedCount` and `directChildrenCount` to know if all the children have been loaded or not.\n- `childrenSelectedCount` the number of all leaf rows in the current group that are selected.\n- `groupKeys` - type: `any[]`, but usually it's actually `string[]`. For group rows, the group keys will have all the keys starting from the topmost parent down to the current group row (key for current group row is included).\n- `groupBy` - type `(keyof T)[]`. Has the same structure as groupKeys, but it will contain the fields used to group the rows.\n- `rootGroupBy` - type `(keyof T)[]`. The groupBy value of the DataSource component, mapped to the `groupBy.field`\n- `groupCount` - type: `number`. The count of leaf rows that the current group (in this case, the parent group) contains\n- `groupNesting` - type `number`. The nesting of the parent group.\n- `parents` - a list of `rowInfo` objects that are the parents of the current row.\n- `indexInParentGroups[]` - type: `number[]`. See below for an example\n\n```\n> Italy - country - indexInParentGroups: [0]\n > Rome - city - indexInParentGroups: [0,0]\n - Marco - person - indexInParentGroups: [0,0,0]\n - Luca - person - indexInParentGroups: [0,0,1]\n - Giuseppe - person - indexInParentGroups: [0,0,2]\n> USA - country - indexInParentGroups: [1]\n > LA - city - indexInParentGroups: [1,0]\n - Bob - person - indexInParentGroups: [1,0,2]\n```\n\n- `collapsed` - type `boolean`.\n- `selfLoaded` - type: `boolean`. Useful in lazy-loading scenarios, when there is batching present. If you're not in such a scenario, the value will be `false`.\n\n### Discriminator\n\n```ts\nrowInfo.dataSourceHasGrouping === true && rowInfo.isGroupRow === true;\n```\n" + "excerpt": "Infinite Table comes with grouping and pivoting capabilities built-in. The `DataSource` component does the actual data grouping and pivoting - while the `InfiniteTable` component does the specialized rendering.", + "readingTime": "1 min read", + "content": "\nInfinite Table comes with grouping and pivoting capabilities built-in. The `DataSource` component does the actual data grouping and pivoting - while the `InfiniteTable` component does the specialized rendering.\n\n\n\nLearn row grouping and explore the possibilities.\n\n\nRead thorough documentation covering pivoting and aggregation.\n\n\n\n\n\n```ts files=[\"row-grouping-example.page.tsx\",\"columns.ts\"]\n\n```\n\n\n" }, { "filePath": "/docs/learn/master-detail/caching-detail-datagrid", @@ -801,6 +762,45 @@ "readingTime": "4 min read", "content": "\nThe React DataGrid that Infinite Table offers has native support for master-detail rows.\n\n\n\nThe single most important property for the master-detail DataGrid configuration is the function prop - which makes the DataGrid be considered master-detail.\n\nIn addition, make sure you have a column with the `renderRowDetailIcon: true` flag set. on a column makes the column display the row detail expand icon.\n\nThe row detail in the DataGrid can contain another DataGrid or any other custom content.\n\n\n\n\nIt's very imporant that the function prop you pass into `` is stable and doesn't change on every render. So make sure you pass a reference to the same function every time - except of course if you want the row detail to change based on some other state.\n\n\n\n\n\n\nThis example shows a master DataGrid with cities & countries.\n\nThe details for each city shows a DataGrid with developers in that city.\n\nThe detail DataGrid is configured with remote sorting.\n\n\n\n```ts file=\"master-detail-example.page.tsx\"\n\n```\n\n\n\n\nIf you want to use a component instead of the function, you can use the property. This works similarly and makes the DataGrid be considered master-detail. Inside the component, you can use the hook to get the master row information.\n\n\n\n## Loading the Detail DataSource\n\nWhen master-detail is configured and the row detail renders a DataGrid, the function for the detail `` will be called with the `masterRowInfo` as a property available in the object passed as argument.\n\n```tsx title=\"Loading the detail DataGrid data\" {2}\nconst detailDataFn: DataSourceData = ({\n masterRowInfo,\n sortInfo,\n ...\n}) => {\n\n return Promise.resolve([...])\n}\n\n data={detailDataFn}>\n {...}\n\n```\n\nYou can see the live example above for more details.\n\n## Rendering a detail DataGrid\n\nUsing the prop, you can render any custom content for the row details.\n\nThe content doesn't need to include Infinite Table.\n\nYou can, however, render an Infinite Table React DataGrid, at any level of nesting inside the row detail content.\n\n\n\n\n\nIn this example, the row detail contains custom content, along with another Infinite Table DataGrid. You can nest a child DataGrid inside the row details at any level of nesting.\n\n\n\n```ts file=\"master-detail-custom-datagrid-example.page.tsx\"\n\n```\n\n\n\n## Configuring the master-detail height\n\nIn order to configure the height of the row details, you can use the prop.\n\n```tsx title=\"Configuring the row detail height\" {3}\n\n columns={masterColumns}\n rowDetailHeight={500}\n rowDetailRenderer={renderDetail}\n/>\n```\n\nThe default value for the is `300` px.\n\n can be one of the following:\n\n- `number` - the height in pixels\n- `string` - the name of a CSS variable that configures the height - eg: `--master-detail-height`\n- `(rowInfo) => number` - a function that can return a different height for each row. The sole argument is the rowInfo object.\n\n\n\n\n\nThis master-detail DataGrid is configured with a custom of `200px`.\n\n\n\n```ts file=\"master-detail-custom-detail-height-example.page.tsx\"\n\n```\n\n\n\n\n## Conditional row details\n\nNot all rows in a DataGrid need to have details. To configure which rows have details, you can use the function prop.\n\n```tsx title=\"Using conditional row details\" {5}\n\n columns={masterColumns}\n rowDetailHeight={500}\n rowDetailRenderer={renderDetail}\n isRowDetailEnabled={(rowInfo) => rowInfo.data.cityName.contains('i')}\n/>\n```\n\nThe function prop is called with the rowInfo object and is expected to return a boolean value.\n\n\n\n\n\n\nThis example shows a master DataGrid with cities & countries.\n\nNot all rows have details - every other row is configured without details via the function prop.\n\n\n\n```ts file=\"master-detail-per-row-example.page.tsx\"\n\n```\n\n\n" }, + { + "filePath": "/docs/learn/rows/disabled-rows", + "routePath": "/docs/learn/rows/disabled-rows", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/learn/rows/disabled-rows.page.md", + "fileName": "disabled-rows.page.md", + "folderPath": "/docs/learn/rows/", + "frontmatter": { + "title": "Disabled Rows" + }, + "excerpt": "Disabling rows allows you to have some rows that are not selectable, not clickable, not reacheable via keyboard navigation and other interactions.", + "readingTime": "2 min read", + "content": "\nDisabling rows allows you to have some rows that are not selectable, not clickable, not reacheable via keyboard navigation and other interactions.\n\nThe `DataSource` manages the disabled state of rows, via the (uncontrolled) prop and (controlled) prop.\n\n```tsx\n\n idProperty=\"id\"\n data={[]}\n defaultRowDisabledState={{\n enabledRows: true,\n disabledRows: ['id1', 'id4', 'id5']\n }}\n/>\n \n {/* ... */}\n />\n\n```\n\n\n\nIn addition to using the / props, you can also specify the function prop, which overrides those other props and ultimately determines whether a row is disabled or not.\n\n\n\n\n\n```tsx file=\"initialRowDisabledState-example.page.tsx\"\n```\n\n\n\n## Using disabled rows while rendering\n\nWhen rendering a cell, you have access to the row disabled state - the type has a `rowDisabled` property which is true if the row is disabled.\n\n\n\n\n This example uses custom rendering for the `firstName` column to render an emoji for disabled rows.\n\n\n```tsx file=\"custom-rendering-for-disabled-rows-example.page.tsx\"\n```\n\n\n\n## Using the API to enable/disable rows\n\nYou can use the `DataSourceApi` to enable or disable rows programmatically.\n\n\n\n```tsx\ndataSourceApi.setRowEnabled(rowId, enabled);\n\n```\n\n\n\n```tsx\ndataSourceApi.setRowEnabledAt(rowIndex, enabled);\n```\n\n\n\n\nUse the context menu on each row to toggle the disabled state of the respective row.\n\n\n```tsx file=\"using-api-to-disable-rows-example.page.tsx\"\n```\n\n" + }, + { + "filePath": "/docs/learn/rows/styling-rows", + "routePath": "/docs/learn/rows/styling-rows", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/learn/rows/styling-rows.page.md", + "fileName": "styling-rows.page.md", + "folderPath": "/docs/learn/rows/", + "frontmatter": { + "title": "Styling Rows" + }, + "excerpt": "Rows can be styled by using the `rowStyle` and the `rowClassName` props", + "readingTime": "1 min read", + "content": "\nRows can be styled by using the `rowStyle` and the `rowClassName` props\n\n- the prop can be a style `object` or a `function` that returns a style `object` or `undefined`\n- the prop can be a `string` (the name of a CSS class) or a `function` that returns a `string` or `undefined`\n\n```tsx title=\"Defining-a-rowStyle-function\"\nconst rowStyle: InfiniteTablePropRowStyle = ({\n data,\n rowInfo,\n}: {\n data: Employee | null;\n rowInfo: InfiniteTableRowInfo;\n}) => {\n const salary = data ? data.salary : 0;\n\n if (salary > 150_000) {\n return { background: 'tomato' };\n }\n if (rowInfo.indexInAll % 10 === 0) {\n return { background: 'lightblue', color: 'black' };\n }\n};\n```\n\n\n\nThe function prop has the same signature as the function prop.\n\n\n\n## Row styling example\n\n\n\n```ts files=[\"$DOCS/reference/rowStyle-example.page.tsx\",\"$DOCS/reference/rowStyle-example-columns.ts\"]\n\n```\n\n\n\n\n\nIn the function, you can access the rowInfo object, which contains information about the current row. It's especially useful when you have grouping and aggregation, as it contains the aggregation values and other extra info.\n\n\n" + }, + { + "filePath": "/docs/learn/rows/using-row-info", + "routePath": "/docs/learn/rows/using-row-info", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/learn/rows/using-row-info.page.md", + "fileName": "using-row-info.page.md", + "folderPath": "/docs/learn/rows/", + "frontmatter": { + "title": "Using Rows at Runtime" + }, + "excerpt": "At runtime, the function and a lot of other functions use the rowInfo object to access the current row and use it to decide how to render the current cell or row.", + "readingTime": "7 min read", + "content": "\nAt runtime, the function and a lot of other functions use the rowInfo object to access the current row and use it to decide how to render the current cell or row.\n\nThe `rowInfo` object has a few variations, depending on the presence or absence of grouping. See type definition here.\n\n\n\nAll those variations are discriminated in the `TypeScript` typings, so you can easily use the different types of `rowInfo` objects.\n\n\n\n## Ungrouped Scenario - normal `rowInfo`\n\nWhen there is no grouping, the `rowInfo` object has the following properties:\n\n- `data` - type: `DATA_TYPE`\n- `dataSourceHasGrouping` - type: `false`\n- `isGroupRow` - type: `false`\n- `id` - type: `any`. The id of the row, as defined by the prop.\n- `selfLoaded` - type: `boolean`. Useful in lazy-loading scenarios, when there is batching present. If you're not in such a scenario, the value will be `false`. You can use this to show a loading indicator for the row.\n- `indexInAll` - type `number`. The index of the row in the full dataset. Called like this because for grouping scenarios, there's also an `indexInGroup`\n\n### Discriminator\n\n```ts\nrowInfo.dataSourceHasGrouping === false;\n```\n\n## Grouped scenario - normal `rowInfo`\n\nWhen there is grouping defined, and the row is not a group row, the `rowInfo` object has the following properties:\n\n- `data` - type: `DATA_TYPE`\n- `dataSourceHasGrouping` - type: `true`\n- `isGroupRow` - type: `false`\n- `indexInAll` - like the above\n- `indexInGroup` - type: `number`. The index of the row in its parent group.\n- `groupKeys` - type: `any[]`, but usually it's actually `string[]`. For normal rows, the group keys will have all the keys starting from the topmost parent down to the last group row in the hierarchy (the direct parent of the current row).\n\n```txt\nExample: People grouped by country and city\n\n> Italy - country - groupKeys: ['Italy']\n > Rome - city - groupKeys: ['Italy', 'Rome']\n - Marco - person - groupKeys: ['Italy', 'Rome']\n - Luca - person - groupKeys: ['Italy', 'Rome']\n - Giuseppe - person - groupKeys: ['Italy', 'Rome']\n```\n\n- `groupBy` - type `(keyof T)[]`. Has the same structure as groupKeys, but it will contain the fields used to group the rows.\n- `rootGroupBy` - type `(keyof T)[]`. The groupBy value of the DataSource component, mapped to the `groupBy.field`\n- `parents` - a list of `rowInfo` objects that are the parents of the current row.\n- `indexInParentGroups[]` - type: `number[]`. See below for an example\n\n```\n> Italy - country - indexInParentGroups: [0]\n > Rome - city - indexInParentGroups: [0,0]\n - Marco - person - indexInParentGroups: [0,0,0]\n - Luca - person - indexInParentGroups: [0,0,1]\n - Giuseppe - person - indexInParentGroups: [0,0,2]\n> USA - country - indexInParentGroups: [1]\n > LA - city - indexInParentGroups: [1,0]\n - Bob - person - indexInParentGroups: [1,0,2]\n```\n\n- `groupCount` - type: `number`. The count of leaf rows that the current group (in this case, the parent group) contains\n- `groupNesting` - type `number`. The nesting of the parent group.\n- `collapsed` - type `boolean`.\n- `selfLoaded` - type: `boolean`. Useful in lazy-loading scenarios, when there is batching present. If you're not in such a scenario, the value will be `false`.\n\n### Discriminator\n\n```ts\nrowInfo.dataSourceHasGrouping === true && rowInfo.isGroupRow === false;\n```\n\n## Grouped scenario - group `rowInfo`\n\nWhen there is grouping defined, and the row is a group row, the `rowInfo` object has the following properties:\n\n- `data` - type: `Partial | null`. The `data` object that might be available is the result of the aggregation reducers. If none are specified, `data` will be `null`\n- `dataSourceHasGrouping` - type: `true`\n- `isGroupRow` - type: `true`\n- `error` - type: `string?`. If there was an error while loading the group (when the group row is expanded), this will contain the error message. If the group row was loaded with the `cache: true` flag sent in the server response, the error will remain on the `rowInfo` object even when you collapse the group row, otherwise, if `cache: true` was not present, the `error` property will be removed on collapse.\n- `indexInAll` - like the above\n- `indexInGroup` - type: `number`. The index of the row in the its parent group.\n- `deepRowInfoArray` - an array of `rowInfo` objects. This array contains all the (uncollapsed, so visible) row infos under this group, at any level of nesting, in the order in which they are visible in the table.\n- `reducerResults` - type `Record`. The result of the aggregation reducers for each field in the prop.\n- `groupCount` - type: `number`. The count of leaf rows that the current group (in this case, the parent group) contains\n- `groupData` - type: `DATA_TYPE[]`. The array of the data of all leaf nodes (normal nodes) that are inside this group.\n\n```txt\nExample: People grouped by country and city\n\n> Italy - country - groupKeys: ['Italy']\n > Rome - city - groupKeys: ['Italy', 'Rome']\n - Marco - person - groupKeys: ['Italy', 'Rome']\n - Luca - person - groupKeys: ['Italy', 'Rome']\n - Giuseppe - person - groupKeys: ['Italy', 'Rome']\n```\n\n- `collapsedChildrenCount` - type: `number`. The count of all leaf nodes (normal rows) inside the group that are not being visible due to collapsing (either the current row is collapsed or any of its children)\n- `directChildrenCount` - type: `number`. The count of the direct children of the current group. Direct children can be either normal rows or groups.\n- `directChildrenLoadedCount` - type: `number`. Like `directChildrenCount`, but only counts the rows that are loaded (when batched lazy loading is configured).\n- `childrenAvailable` - type: `boolean`. For lazy/batched grouping, this is true if the group has been expanded at least once. NOTE: if this is true, it doesn't mean that all the children have been loaded, it only means that at least some children have been loaded and are available. Use `directChildrenCount` and `directChildrenLoadedCount` to know if all the children have been loaded or not.\n- `childrenLoading` - type: `boolean`. Boolean flag that will be true while lazy loading direct children of the current row group. Use `directChildrenLoadedCount` and `directChildrenCount` to know if all the children have been loaded or not.\n- `childrenSelectedCount` the number of all leaf rows in the current group that are selected.\n- `groupKeys` - type: `any[]`, but usually it's actually `string[]`. For group rows, the group keys will have all the keys starting from the topmost parent down to the current group row (key for current group row is included).\n- `groupBy` - type `(keyof T)[]`. Has the same structure as groupKeys, but it will contain the fields used to group the rows.\n- `rootGroupBy` - type `(keyof T)[]`. The groupBy value of the DataSource component, mapped to the `groupBy.field`\n- `groupCount` - type: `number`. The count of leaf rows that the current group (in this case, the parent group) contains\n- `groupNesting` - type `number`. The nesting of the parent group.\n- `parents` - a list of `rowInfo` objects that are the parents of the current row.\n- `indexInParentGroups[]` - type: `number[]`. See below for an example\n\n```\n> Italy - country - indexInParentGroups: [0]\n > Rome - city - indexInParentGroups: [0,0]\n - Marco - person - indexInParentGroups: [0,0,0]\n - Luca - person - indexInParentGroups: [0,0,1]\n - Giuseppe - person - indexInParentGroups: [0,0,2]\n> USA - country - indexInParentGroups: [1]\n > LA - city - indexInParentGroups: [1,0]\n - Bob - person - indexInParentGroups: [1,0,2]\n```\n\n- `collapsed` - type `boolean`.\n- `selfLoaded` - type: `boolean`. Useful in lazy-loading scenarios, when there is batching present. If you're not in such a scenario, the value will be `false`.\n\n### Discriminator\n\n```ts\nrowInfo.dataSourceHasGrouping === true && rowInfo.isGroupRow === true;\n```\n" + }, { "filePath": "/docs/learn/selection/cell-selection", "routePath": "/docs/learn/selection/cell-selection", @@ -829,6 +829,34 @@ "readingTime": "11 min read", "content": "\n`InfiniteTable` offers support for both single and multiple row selection. For selecting cells, see the [Cell Selection](/docs/learn/selection/cell-selection) page.\n\n```tsx title=\"Configure the selection mode on the DataSource component\"\n\n\n// can be \"single-row\", \"multi-row\", \"multi-cell\" or false\n```\n\nMultiple row selection allows people to select rows just like they would in their MacOS Finder app, by clicking desired rows and using the cmd/shift keys as modifiers.\n\nThe DataGrid also offers support for **checkbox selection**, which is another easy way of interacting with grid rows, especially when grouped or nested data is used.\n\nRow selection (both single and multiple) is driven by the prop, which will contain **primary keys of the selected rows**.\n\n\n\nThe value or values you specify for row selection are primary keys of the rows in the DataGrid.\n\nRow selection is defined on the `DataSource` component, so that's where you specify your prop (or the uncontrolled version of it, namely and also the callback prop of ).\n\n\n\n\n\nYou can explicitly specify the as `\"single-row\"` or `\"multi-row\"` (or `false`) but it will generally be derived from the value of your / prop.\n\n\n\n# Single Row Selection\n\nThis is the most basic row selection - in this case the prop (or the uncontrolled variant ) will be the primary key of the selected row (a string or a number or `null` for no selection).\n\n```ts {4}\n\n primaryKey=\"id\"\n data={[...]}\n defaultRowSelection={4}\n>\n \n\n\n```\n\n\n\n\n\nSingle row selection example - click a row to see selection change. You can also use your keyboard - press the spacebar to select/deselect a row.\n\n\n\n```ts file=\"$DOCS/reference/default-single-row-selection-example.page.tsx\"\n\n```\n\n\n\nRow selection is changed when the user clicks a row. Clicking a row selects it and clicking it again keeps the row selected. For deselecting the row with the mouse use `cmd`/`ctrl` + click.\n\n## Keybord support\n\nYou can also use your keyboard to select a row, as by default, is `true`. Using your keyboard, navigate to the desired row and then press the spacebar to select it. Pressing the spacebar again on the selected row will deselect it.\n\n\n\nBoth `cell` and `row` are available and you can use either of them to perform row selection.\n\n\n\n## Controlled single row selection\n\nRow selection can be used as a controlled or uncontrolled property. For the controlled version, make sure you also define your callback prop to update the selection.\n\n\n\n\n\nThis example uses callback prop to update the controlled \n\n\n\n```ts file=\"$DOCS/reference/controlled-single-row-selection-example.page.tsx\"\n\n```\n\n\n\n# Multi Row Selection\n\nYou can configure multiple selection for rows so users can interact with it through clicking around or via a checkbox selection column.\n\n## Using your mouse and keyboard to select rows\n\nIf you're using checkboxes for selection, users will be selecting rows via click or click + `cmd`/`ctrl` and `shift` keys, just like they are used to in their native Finder/Explorer applications.\n\n### Mouse interactions\n\nFor selecting with the mouse, the following gestures are supported (we tried to exactly replicate the logic/behaviour from MacOS Finder app, so most people should find it really intuitive):\n\n- clicking a row (with no modifier keys) will select that row, while clearing any existing selection\n- click + `cmd`/`ctrl` modifier key will toggle the selection for the clicked row while keeping any other existing selection. So if the row was not selected, it's being added to the current selection, while if the row was already selected, it's being removed from the selection\n- click + `shift` modifier key will perform a multi selection, starting from the last selection click where the `shift` key was not used.\n\n\n\n\n\nUse your mouse to select multiple rows. Expect click and click + `cmd`/`ctrl`/`shift` modifier keys to behave just like they are in the MacOS Finder app.\n\n\n\n```ts file=\"$DOCS/reference/default-selection-mode-multi-row-example.page.tsx\"\n\n```\n\n\n\n### Keyboard interactions\n\nBy default is enabled, so you can use the **spacebar** key to select multiple rows. Using the spacebar key is equivalent to doing a mouse click, so expect the combination of **spacebar** + `cmd`/`ctrl`/`shift` modifier keys to behave just like clicking + the same modifier keys.\n\n\n\n\n\nUse spacebar + optional `cmd`/`ctrl`/`shift` modifier keys just like you would do clicking + the same modifier keys.\n\n\n\n```ts file=\"$DOCS/reference/default-selection-mode-multi-row-keyboard-toggle-example.page.tsx\"\n\n```\n\n\n\n\n\nFor selecting all the rows in the table, you can use `cmd`/`ctrl` + `A` keyboard shortcut.\n\n\n\n## Using a selection checkbox\n\nSelection multiple rows is made easier when there is a checkbox column and even-more-so when there is grouping.\n\nConfiguring checkbox selection is as easy as specifying renderSelectionCheckBox on any of the columns in the grid. renderSelectionCheckBox can either be the boolean `true` or a render function that allows the customization of the selection checkbox.\n\n```ts {8}\nconst columns: InfiniteTablePropColumns = {\n id: {\n field: 'id',\n defaultWidth: 80,\n },\n country: {\n // show the selection checkbox for this column\n renderSelectionCheckBox: true,\n field: 'country',\n },\n firstName: {\n field: 'firstName',\n },\n};\n```\n\n\n\nAny column can show a selection checkbox if column.renderSelectionCheckBox is set to `true`.\n\nThere is nothing prevening you from providing multiple checkbox columns.\n\n\n\n\n\n\n\nUse the selection checkboxes to select rows. You can also use the spacebar key (+ optional shift modifier) to modify the selection\n\n\n\n```ts file=\"$DOCS/reference/default-checkbox-selection-multi-row-example.page.tsx\"\n\n```\n\n\n\n### Mouse interactions\n\nThe mouse interactions are the obvious ones you would expect from checkbox selection. Clicking a checkbox will toggle the selection for the correspondign row. Also, clicking the header checkbox will select/deselect all the rows in the table. The selection checkbox in the column header can be in an indeterminate state (when just some of the rows are selected), and when clicking it, it will become checked and select all rows.\n\n\n\nYou can use renderHeaderSelectionCheckBox for a column to customize the checkbox in the column header. If no header selection checkbox is specified, renderSelectionCheckBox will be used for the column header as well, just like it's used for grid rows.\n\n\n\n### Keyboard interactions\n\nWhen multi-row selection is configured to use checkboxes, you can still use your keyboard to select rows. Navigate to the desired row (you can have keyboard navigation active for either cells or rows) and press the spacebar. If the row is not selected it will select it, otherwise it will deselect it.\n\n\n\nThe only supported modifier key when selecting a row by pressing **spacebar** is the `shift` key - it allows users to extend the selection over multiple rows, which is handy.\n\n\n\n## Specify a `rowSelection` value\n\nWhen multiple row selection is used, the prop should be an object that can have the following shape:\n\n```ts\nconst rowSelection = {\n selectedRows: [3, 6, 100, 23], // those specific rows are selected\n defaultSelection: false, // while all other rows are deselected by default\n};\n\n// or\nconst rowSelection = {\n deselectedRows: [3, 6, 100, 23], // those specific rows are deselected\n defaultSelection: true, // all other rows are selected\n};\n\n// or, for grouped data - this example assumes groupBy=continent,country,city\n\n// for using this form of multi-row selection when you have grouping,\n// you have to specify DataSource.useGroupKeysForMultiRowSelection = true\nconst rowSelection = {\n selectedRows: [\n 45, // row with id 45 is selected, no matter the group it is nested in\n ['Europe', 'France'], // all rows in Europe/France are selected\n ['Asia'], // all rows in Asia are selected\n ],\n deselectedRows: [\n ['Europe', 'France', 'Paris'], // all rows in Paris are deselected\n ],\n defaultSelection: false, // all other rows are selected\n};\n```\n\nAs shown above, the `rowSelection.selectedRows` and `rowSelection.deselectedRows` arrays can either contain:\n\n- primary keys of rows (which are usually strings or numbers) - any non-array value inside `rowSelection.selectedRows`/`rowSelection.deselectedRows` is considered an id/primaryKey value for a leaf row in the grouped dataset.\n- arrays of group keys (can be combined with primary keys as well) - those arrays describe the path of the specified selected group. Please note that `rowSelection.selectedRows` can contain certain paths while `rowSelection.deselectedRows` can contain child paths of those paths ... or any other imaginable combination. For this kind of , you need to enable .\n\n\n \nRow Selection only uses primary keys by default, even when you have grouped data.\n\nFor grouping however, you might want to use selection with group keys - for doing that, specify DataSource.useGroupKeysForMultiRowSelection=true.\nNote that if you use selection with group keys, the selection will not be relevant/consistent when the changes.\n\nWhen you have both grouping and lazy loading, must be enabled - read more about it in the note below.\n\n\n\n\n\nWhen is being used - this means not all available groups/rows have actually been loaded yet in the dataset - we need a way to allow you to specify that those possibly unloaded rows/groups are selected or not. In this case, the `rowSelection.selectedRows`/`rowSelection.deselectedRows` arrays should not have row primary keys as strings/numbers, but rather rows/groups specified by their full path (so should be set to `true`).\n\n```ts {6}\n// this example assumes groupBy=continent,country,city\nconst rowSelection = {\n selectedRows: [\n // row with id 45 is selected - we need this because in the lazyLoad scenario,\n // not all parents might have been made available yet\n ['Europe','Italy', 'Rome', 45],\n ['Europe','France'], // all rows in Europe/France are selected\n ['Asia'] // all rows in Asia are selected\n ]\n deselectedRows: [\n ['Europe','Italy','Rome'] // all rows in Rome are deselected\n // but note that row with id 45 is selected, so Rome will be\n // rendered with an indeterminate selection state\n ],\n defaultSelection: false // all other rows are selected\n}\n```\n\nIn the example above, we know that there are 3 groups (`continent`, `country`, `city`), so any item in the array that has a 4th element is a fully specified leaf node. While lazy loading, we need this fully specified path for specific nodes, so we know which group rows to render with indeterminate selection.\n\n\n\n### Controlled selection with checkbox column\n\nWhen using the controlled , make sure to specify the callback prop to update the selection accordingly as a result of user interaction.\n\n\n\n\n\nThis example shows how you can use multiple row selection with a predefined controlled value.\n\nGo ahead and select some groups/rows and see the selection value adjust.\n\nThe example also shows how you can use the `InfiniteTableApi` to retrieve the actual ids of the selected rows.\n\n\n\n```ts file=\"$DOCS/reference/controlled-multi-row-selection-example.page.tsx\"\n\n```\n\n\n\n## Multi Selection with Lazy Load and Grouping\n\nProbably the most complex use-case for multi selection (with checkbox) is the combination of grouping and lazy-loading.\n\nIn this scenario, not all groups and/or rows are loaded at a given point in time, but we need to be able to know how to render each checkbox for each group - either checked, unchecked or indeterminate, all this depending on whether all children, at any nesting levels are selected or not.\n\nIn order to make this possible, the value will only contain arrays (and not individual primary keys) in the `selectedRows` and `deselectedRows` arrays and the DataSource will be configured with .\n\n\n\n\n\nThe `DataSet` has lazy loading and grouping.\n\nThe selection uses group keys (see ), so it can specify as selected even rows/groups that have not been loaded yet.\n\nNote in the example below that some of the group rows are partly selected, even if the leaf rows which are specified as selected in the are not yet loaded.\n\n\n\n```ts file=\"$DOCS/reference/lazy-multi-row-selection-example.page.tsx\"\n\n```\n\n\n" }, + { + "filePath": "/docs/learn/theming/css-variables", + "routePath": "/docs/learn/theming/css-variables", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/learn/theming/css-variables.page.md", + "fileName": "css-variables.page.md", + "folderPath": "/docs/learn/theming/", + "frontmatter": { + "title": "CSS Variables", + "description": "Reference list of CSS variables that can be used to style the Infinite Table for React" + }, + "excerpt": "Below you can find the complete list of CSS variables that can be used to style the component.", + "readingTime": "9 min read", + "content": "\nBelow you can find the complete list of CSS variables that can be used to style the component.\n\n{/* START VARS */}\n### Accent color\n\nBrand-specific accent color. This probably needs override to match your app.\n\n```css\n--infinite-accent-color\n```\n\n### Error color\n\n\n\n```css\n--infinite-error-color\n```\n\n### Color\n\nThe text color inside the component\n\n```css\n--infinite-color\n```\n\n### Space 0\n\n\n\n```css\n--infinite-space-0\n```\n\n### Space 1\n\n\n\n```css\n--infinite-space-1\n```\n\n### Space 2\n\n\n\n```css\n--infinite-space-2\n```\n\n### Space 3\n\n\n\n```css\n--infinite-space-3\n```\n\n### Space 4\n\n\n\n```css\n--infinite-space-4\n```\n\n### Space 5\n\n\n\n```css\n--infinite-space-5\n```\n\n### Space 6\n\n\n\n```css\n--infinite-space-6\n```\n\n### Space 7\n\n\n\n```css\n--infinite-space-7\n```\n\n### Space 8\n\n\n\n```css\n--infinite-space-8\n```\n\n### Space 9\n\n\n\n```css\n--infinite-space-9\n```\n\n### Space 10\n\n\n\n```css\n--infinite-space-10\n```\n\n### Font size 0\n\n\n\n```css\n--infinite-font-size-0\n```\n\n### Font size 1\n\n\n\n```css\n--infinite-font-size-1\n```\n\n### Font size 2\n\n\n\n```css\n--infinite-font-size-2\n```\n\n### Font size 3\n\n\n\n```css\n--infinite-font-size-3\n```\n\n### Font size 4\n\n\n\n```css\n--infinite-font-size-4\n```\n\n### Font size 5\n\n\n\n```css\n--infinite-font-size-5\n```\n\n### Font size 6\n\n\n\n```css\n--infinite-font-size-6\n```\n\n### Font size 7\n\n\n\n```css\n--infinite-font-size-7\n```\n\n### Font family\n\n\n\n```css\n--infinite-font-family\n```\n\n### Min height\n\n\n\n```css\n--infinite-min-height\n```\n\n### Border radius\n\n\n\n```css\n--infinite-border-radius\n```\n\n### Background\n\nThe background color for the whole component.\n\nOverriden in the `dark` theme.\n\n```css\n--infinite-background\n```\n\n### Icon size\n\n\n\n```css\n--infinite-icon-size\n```\n\n### Load mask padding\n\nThe padding used for the content inside the LoadMask.\n\n```css\n--infinite-load-mask-padding\n```\n\n### Load mask color\n\n\n\n```css\n--infinite-load-mask-color\n```\n\n### Load mask text background\n\n\n\n```css\n--infinite-load-mask-text-background\n```\n\n### Load mask overlay background\n\n\n\n```css\n--infinite-load-mask-overlay-background\n```\n\n### Load mask overlay opacity\n\n\n\n```css\n--infinite-load-mask-overlay-opacity\n```\n\n### Load mask border radius\n\n\n\n```css\n--infinite-load-mask-border-radius\n```\n\n### Header background\n\nBackground color for the header. Defaults to [`--infinie-header-cell-background`](#header-cell-background).\n\nOverriden in the `dark` theme.\n\n```css\n--infinite-header-background\n```\n\n### Header color\n\nThe text color inside the header.\n\nOverriden in the `dark` theme.\n\n```css\n--infinite-header-color\n```\n\n### Column header height\n\nThe height of the column header.\n\n```css\n--infinite-column-header-height\n```\n\n### Header cell background\n\nBackground for header cells.\n\nOverriden in the `dark` theme.\n\n```css\n--infinite-header-cell-background\n```\n\n### Header cell hover background\n\n\n\n```css\n--infinite-header-cell-hover-background\n```\n\n### Header cell padding\n\n\n\n```css\n--infinite-header-cell-padding\n```\n\n### Header cell padding x\n\n\n\n```css\n--infinite-header-cell-padding-x\n```\n\n### Header cell padding y\n\n\n\n```css\n--infinite-header-cell-padding-y\n```\n\n### Header cell icon size\n\n\n\n```css\n--infinite-header-cell-icon-size\n```\n\n### Header cell menu icon line width\n\n\n\n```css\n--infinite-header-cell-menu-icon-line-width\n```\n\n### Header cell sort icon margin\n\n\n\n```css\n--infinite-header-cell-sort-icon-margin\n```\n\n### Resize handle active area width\n\nThe width of the area you can hover over in order to grab the column resize handle.\nDefaults to `20px`.\n\nThe purpose of this active area is to make it easier to grab the resize handle.\n\n```css\n--infinite-resize-handle-active-area-width\n```\n\n### Resize handle width\n\nThe width of the colored column resize handle that is displayed on hover and on drag. Defaults to `2px`\n\n```css\n--infinite-resize-handle-width\n```\n\n### Resize handle hover background\n\nThe color of the column resize handle - the resize handle is the visible indicator that you see\nwhen hovering over the right-edge of a resizable column. Also visible on drag while doing a column resize.\n\n```css\n--infinite-resize-handle-hover-background\n```\n\n### Resize handle constrained hover background\n\nThe color of the column resize handle when it has reached a min/max constraint.\n\n```css\n--infinite-resize-handle-constrained-hover-background\n```\n\n### Filter operator padding x\n\n\n\n```css\n--infinite-filter-operator-padding-x\n```\n\n### Filter editor padding x\n\n\n\n```css\n--infinite-filter-editor-padding-x\n```\n\n### Filter editor margin x\n\n\n\n```css\n--infinite-filter-editor-margin-x\n```\n\n### Filter operator padding y\n\n\n\n```css\n--infinite-filter-operator-padding-y\n```\n\n### Filter editor padding y\n\n\n\n```css\n--infinite-filter-editor-padding-y\n```\n\n### Filter editor margin y\n\n\n\n```css\n--infinite-filter-editor-margin-y\n```\n\n### Filter editor background\n\n\n\n```css\n--infinite-filter-editor-background\n```\n\n### Filter editor border\n\n\n\n```css\n--infinite-filter-editor-border\n```\n\n### Filter editor focus border color\n\n\n\n```css\n--infinite-filter-editor-focus-border-color\n```\n\n### Filter editor border radius\n\n\n\n```css\n--infinite-filter-editor-border-radius\n```\n\n### Filter editor color\n\n\n\n```css\n--infinite-filter-editor-color\n```\n\n### Cell padding\n\n\n\n```css\n--infinite-cell-padding\n```\n\n### Cell border width\n\n\n\n```css\n--infinite-cell-border-width\n```\n\n### Cell border\n\nSpecifies the border for cells.\n\nOverriden in the `dark` theme - eg: `1px solid #2a323d`\n\n```css\n--infinite-cell-border\n```\n\n### Cell border invisible\n\n\n\n```css\n--infinite-cell-border-invisible\n```\n\n### Cell border radius\n\n\n\n```css\n--infinite-cell-border-radius\n```\n\n### Column reorder effect duration\n\n\n\n```css\n--infinite-column-reorder-effect-duration\n```\n\n### Pinned cell border\n\n\n\n```css\n--infinite-pinned-cell-border\n```\n\n### Cell color\n\nText color inside rows. Defaults to `currentColor`\n\nOverriden in `dark` theme.\n\n```css\n--infinite-cell-color\n```\n\n### Selected cell background\n\nThe background for selected cells, when cell selection is enabled.\n\nIf not specified, it will default to `var(--infinite-active-cell-background)`.\n\n```css\n--infinite-selected-cell-background\n```\n\n### Selected cell background default\n\n\n\n```css\n--infinite-selected-cell-background-default\n```\n\n### Selected cell background alpha\n\nThe opacity of the background color for the selected cell.\n\nIf not specified, it will default to the value for `var(--infinite-active-cell-background-alpha)`\n\n```css\n--infinite-selected-cell-background-alpha\n```\n\n### Selected cell background alpha table unfocused\n\nThe opacity of the background color for the selected cell, when the table is unfocused.\nIf not specified, it will default to `var(--infinite-active-cell-background-alpha--table-unfocused)`.\n\n```css\n--infinite-selected-cell-background-alpha--table-unfocused\n```\n\n### Selected cell border color\n\nThe color for border of the selected cell (when cell selection is enabled).\n Defaults to `var(--infinite-active-cell-border-color)`.\n\n```css\n--infinite-selected-cell-border-color\n```\n\n### Selected cell border width\n\nThe width of the border for the selected cell. Defaults to `var(--infinite-active-cell-border-width)`.\n\n```css\n--infinite-selected-cell-border-width\n```\n\n### Selected cell border style\n\nThe style of the border for the selected cell (eg: 'solid', 'dashed', 'dotted') - defaults to 'dashed'.\nDefaults to `var(--infinite-active-cell-border-style)`.\n\n```css\n--infinite-selected-cell-border-style\n```\n\n### Selected cell border\n\nSpecifies the border for the selected cell. Defaults to `var(--infinite-selected-cell-border-width) var(--infinite-selected-cell-border-style) var(--infinite-selected-cell-border-color)`.\n\n```css\n--infinite-selected-cell-border\n```\n\n### Active cell background alpha\n\nThe opacity of the background color for the active cell (when cell keyboard navigation is enabled).\nEg: 0.25\n\nIf `activeBackground` is not explicitly defined (this is the default), the background color of the active cell\nis the same as the border color (`activeBorderColor`), but with this modified opacity.\n\nIf `activeBorderColor` is also not defined, the accent color will be used.\n\nThis is applied when the component has focus.\n\n```css\n--infinite-active-cell-background-alpha\n```\n\n### Active cell background alpha table unfocused\n\nSame as the above, but applied when the component does not have focus.\n\n```css\n--infinite-active-cell-background-alpha--table-unfocused\n```\n\n### Active cell background\n\nThe background color of the active cell.\n\nIf not specified, it will default to `activeBorderColor` with the opacity of `activeBackgroundAlpha`.\nIf `activeBorderColor` is not specified, it will default to the accent color, with the same opacity as mentioned.\n\nHowever, specify this to explicitly override the default.\n\n```css\n--infinite-active-cell-background\n```\n\n### Active cell background default\n\n\n\n```css\n--infinite-active-cell-background-default\n```\n\n### Active cell border color\n\nThe color for border of the active cell (when cell keyboard navigation is enabled).\n\n```css\n--infinite-active-cell-border-color\n```\n\n### Active cell border width\n\nThe width of the border for the active cell.\n\n```css\n--infinite-active-cell-border-width\n```\n\n### Active cell border style\n\nThe style of the border for the active cell (eg: 'solid', 'dashed', 'dotted') - defaults to 'dashed'.\n\n```css\n--infinite-active-cell-border-style\n```\n\n### Active cell border\n\nSpecifies the border for the active cell. Defaults to `var(--infinite-active-cell-border-width) var(--infinite-active-cell-border-style) var(--infinite-active-cell-border-color)`.\n\n```css\n--infinite-active-cell-border\n```\n\n### Selection checkbox margin inline\n\n\n\n```css\n--infinite-selection-checkbox-margin-inline\n```\n\n### Menu background\n\n\n\n```css\n--infinite-menu-background\n```\n\n### Menu color\n\n\n\n```css\n--infinite-menu-color\n```\n\n### Menu padding\n\n\n\n```css\n--infinite-menu-padding\n```\n\n### Menu cell padding vertical\n\n\n\n```css\n--infinite-menu-cell-padding-vertical\n```\n\n### Menu cell padding horizontal\n\n\n\n```css\n--infinite-menu-cell-padding-horizontal\n```\n\n### Menu cell margin vertical\n\n\n\n```css\n--infinite-menu-cell-margin-vertical\n```\n\n### Menu item disabled background\n\n\n\n```css\n--infinite-menu-item-disabled-background\n```\n\n### Menu item active background\n\n\n\n```css\n--infinite-menu-item-active-background\n```\n\n### Menu item active opacity\n\n\n\n```css\n--infinite-menu-item-active-opacity\n```\n\n### Menu item pressed opacity\n\n\n\n```css\n--infinite-menu-item-pressed-opacity\n```\n\n### Menu item pressed background\n\n\n\n```css\n--infinite-menu-item-pressed-background\n```\n\n### Menu item disabled opacity\n\n\n\n```css\n--infinite-menu-item-disabled-opacity\n```\n\n### Menu border radius\n\n\n\n```css\n--infinite-menu-border-radius\n```\n\n### Menu shadow color\n\n\n\n```css\n--infinite-menu-shadow-color\n```\n\n### Rowdetail background\n\n\n\n```css\n--infinite-rowdetail-background\n```\n\n### Rowdetail padding\n\n\n\n```css\n--infinite-rowdetail-padding\n```\n\n### Rowdetail grid height\n\n\n\n```css\n--infinite-rowdetail-grid-height\n```\n\n### Row background\n\nBackground color for rows. Defaults to [`--infinite-background`](#background).\n\nOverriden in `dark` theme.\n\n```css\n--infinite-row-background\n```\n\n### Row odd background\n\nBackground color for odd rows. Even rows will use [`--infinite-row-background`](#row-background).\n\nOverriden in `dark` theme.\n\n```css\n--infinite-row-odd-background\n```\n\n### Row selected background\n\n\n\n```css\n--infinite-row-selected-background\n```\n\n### Active row background\n\nThe background color of the active row. Defaults to the value of `var(--infinite-active-cell-background)`.\n\nHowever, specify this to explicitly override the default.\n\n```css\n--infinite-active-row-background\n```\n\n### Active row border color\n\nThe border color for the active row. Defaults to the value of `var(--infinite-active-cell-border-color)`.\n\n```css\n--infinite-active-row-border-color\n```\n\n### Active row border width\n\nThe width of the border for the active row. Defaults to the value of `var(--infinite-active-cell-border-width)`.\n\n```css\n--infinite-active-row-border-width\n```\n\n### Active row border style\n\nThe style of the border for the active row (eg: 'solid', 'dashed', 'dotted') - defaults to the value of `var(--infinite-active-cell-border-style)`, which is `dashed` by default.\n\n```css\n--infinite-active-row-border-style\n```\n\n### Active row border\n\nSpecifies the border for the active row. Defaults to `var(--infinite-active-row-border-width) var(--infinite-active-row-border-style) var(--infinite-active-row-border-color)`.\n\n```css\n--infinite-active-row-border\n```\n\n### Active row background alpha\n\nThe opacity of the background color for the active row (when row keyboard navigation is enabled).\nWhen you explicitly specify `--infinite-active-row-background`, this variable will not be used.\nInstead, this variable is used when the active row background uses the color of the active cell (border).\n\nThis is applied when the component has focus.\n\nDefaults to the value of `var(--infinite-active-cell-background-alpha)`.\n\n```css\n--infinite-active-row-background-alpha\n```\n\n### Active row background alpha table unfocused\n\nSame as the above, but applied when the component does not have focus.\n\nWhen you explicitly specify `--infinite-active-row-background`, this variable will not be used.\nInstead, this variable is used when the active row background uses the color of the active cell (border).\n\nDefaults to the value of `var(--infinite-active-cell-background-alpha--table-unfocused)`.\n\n```css\n--infinite-active-row-background-alpha--table-unfocused\n```\n\n### Row hover background\n\nBackground color for rows, on hover.\n\nOverriden in the `dark` theme.\n\n```css\n--infinite-row-hover-background\n```\n\n### Row selected hover background\n\n\n\n```css\n--infinite-row-selected-hover-background\n```\n\n### Group row background\n\n\n\n```css\n--infinite-group-row-background\n```\n\n### Group row column nesting\n\n\n\n```css\n--infinite-group-row-column-nesting\n```\n\n### Row pointer events while scrolling\n\n\n\n```css\n--infinite-row-pointer-events-while-scrolling\n```{/* END VARS */}\n" + }, + { + "filePath": "/docs/learn/theming/index", + "routePath": "/docs/learn/theming/", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/learn/theming/index.page.md", + "fileName": "index.page.md", + "folderPath": "/docs/learn/theming/", + "frontmatter": { + "title": "Theming", + "description": "Read our docs on the available themes and how you can customize the look and feel of InfiniteTable for React." + }, + "excerpt": "`` ships with a CSS file that you need to import in your codebase to make the component look as intended.", + "readingTime": "3 min read", + "content": "\n`` ships with a CSS file that you need to import in your codebase to make the component look as intended.\n\n```ts\nimport '@infinite-table/infinite-react/index.css';\n```\n\nThis root CSS file includes the `\"default\"` theme.\n\nThe other available themes are\n\n - `shadcn`\n - `minimalist`\n - `ocean`\n - `balsam`\n\nand if you want to use any of them, you have to import their respective CSS file explicitly:\n\n```ts\nimport '@infinite-table/infinite-react/theme/shadcn.css'\nimport '@infinite-table/infinite-react/theme/balsam.css'\nimport '@infinite-table/infinite-react/theme/minimalist.css'\nimport '@infinite-table/infinite-react/theme/ocean.css'\n```\n\nEach theme CSS file includes both the **`light`** and the **`dark`** modes.\n\n\n\nVersion `6.2.0` is the first version where the root CSS file (`@infinite-table/infinite-react/index.css`) doesn't include all the themes. Previous to this version, simply importing the root CSS file gave you access to all available themes.\n\nSplitting each theme into a dedicated CSS file helps reduce the bundle size for our users, as most people will only use one theme for `` in their apps.\n\n\n\n## Applying a theme\n\nThe following themes are currently available:\n\n - `default` - applied by default, no special configuration needed. It's included in the root CSS you need to import from `@infinite-table/infinite-react/index.css`\n - `balsam`\n - `minimalist`\n - `ocean`\n - `shadcn` - for this theme to correctly show up, make sure the shadcn CSS vars are available on page - see [shadcn theming](https://ui.shadcn.com/docs/theming) for details\n\n\nTo apply a theme (except the default one), you have to set the className `\"infinite-theme-name--THEME_NAME\"` to any parent element of the `` component (or even on the component itself).\n\nYou will want to apply the theme name and theme mode classNames to the same element, so you'll end up with a className like `\"infinite-theme-name--minimalist infinite-theme-mode--dark\"`.\n\n```tsx title=\"Applying the minimalist theme with dark mode explicitly\"\n\n \n \n \n\n```\n\n\n\n\n\n\n\nExample configured with `minimalist` theme and `dark` mode by default.\n\n\n```tsx live title=\"Theme switching demo - defaults to minimalist theme in dark mode\" size=\"md\" viewMode=\"preview\" files=\"theme-switching-minimalist-theme-default-example.page.tsx,columns.ts\"\n```\n\n\n\n\n## Theme mode - light or dark\n\nAt runtime, the `light` or `dark` mode is applied based on the user OS settings for the [preferred color scheme](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme).\n\nTo explicitly apply the light mode, apply the className `\"infinite-theme-mode--light\"` to any parent element of the `` component.\n\nTo explicitly apply the dark mode, apply the className `\"infinite-theme-mode--dark\"` to any parent element of the `` component.\n\n```tsx title=\"Explicitly applying light mode via container className\"\n
\n \n \n \n
\n```\n\nIf instead you specify a `infinite-theme-mode--dark` CSS className, the dark mode will be applied\n\n```tsx title=\"Explicitly applying dark theme via container className\"\n\n
\n \n \n \n
\n\n```\n\n\n\nExample configured with `default` theme and `light` mode by default.\n\n\n```tsx live title=\"Theme switching demo - defaults to light theme\" size=\"md\" viewMode=\"preview\" files=\"theme-switching-example.page.tsx,columns.ts\"\n\n```\n\n\n\nIf you don't explicitly have a `infinite-theme-mode--light` or `infinite-theme-mode--dark` ancestor, `InfiniteTable` will use the browser/OS preference (via `@media (prefers-color-scheme: ...)`) to apply the dark or light theme.\n\n\n\n## Available themes\n\n### Default theme\n\nThe `default` theme is applied when you don't specify any explicit theme by default.\n\n### Minimalist theme\n\nThe `minimalist` theme is inspired from minimalistic designs and is a good choice if you want to keep the UI simple and clean." + }, { "filePath": "/docs/learn/sorting/multiple-sorting", "routePath": "/docs/learn/sorting/multiple-sorting", @@ -871,34 +899,6 @@ "readingTime": "10 min read", "content": "\nBy default, the Infinite Table is sortable - clicking a column will sort the grid by that column. Clicking again will reverse the sort and a third click on the column removes the sort altogether.\n\nAt any point, clicking another column header removes any existing column sort and performs a new sort by the clicked column.\n\n\n\nThis is called single sorting - only one column can be sorted at a time.\n\nTechnically, it's the `` that's being sorted, not the `` component.\n\n\n\n\n\n\n\nBy default, clicking a column header sorts the column.\n\n\n\n```ts file=\"local-single-sorting-example-defaults-with-local-data.page.tsx\"\n\n```\n\n\n\n## Apply a default sort order\n\nYou can specify a default sort order by using the prop - specify an object like\n\n```ts\n// sort by `firstName`, in ascending order\ndefaultSortInfo = { field: 'firstName', dir: 1 };\n```\n\n\n\n is an uncontrolled property, so updating the sorting by clicking a column header does not require you to respond to user actions via the .\n\nUncontrolled sorting is managed internally by the `` component, so you don't need to worry about it.\n\nFor controlled sorting, make sure you use the prop and the callback.\n\n\n\n\n\n\n\nThe `age` column is sorted in ascending order.\n\n\n\n```ts file=\"local-uncontrolled-single-sorting-example-with-local-data.page.tsx\"\n\n```\n\n\n\n## Controlled sorting\n\nFor controlled, single sorting, use the as an object like this:\n\n```ts\n// sort by `firstName`, in ascending order\nsortInfo = { field: 'firstName', dir: 1 };\n```\n\nor you can specify `null` for explicit no sorting\n\n```ts\n// no sorting\nsortInfo = null;\n```\n\n\n\nWhen you use controlled sorting via , make sure you also listen to for changes, to get notifications when sorting is changed by the user. Also, for controlled sorting, it's your responsibility to sort the data - read bellow in the [controlled and uncontrolled section](#controlled-and-uncontrolled-sorting).\n\n\n\n## Describing the sort order\n\nTo describe the sorting order, you have to use an object that has the following shape:\n\n- `dir` - `1 | -1` - the direction of the sorting\n- `field?` - `keyof DATA_TYPE` - the field to sort by - optional.\n- `id?` - `string` - if you don't sort by a field, you can specify an id of the column this sorting is bound to. Note that columns have a valueGetter, which will be used when doing local sorting and the column is not bound to an exact field.\n- `type?` - the sort type - one of the keys in - eg `\"string\"`, `\"number\"`, `\"date\"` - will be used for local sorting, to provide the proper comparison function.\n\n### Multiple Sorting\n\nIf you want to use multiple sorting, specify an array of objects like\n\n```ts\n// sort by age in descending order, then by `firstName` in ascending order\nsortInfo = [\n { field: 'age', type: 'number', dir: -1 },\n { field: 'firstName', dir: 1 },\n];\n\n// no sorting\nsortInfo = [];\n```\n\nThis allows sorting by multiple fields (to which columns are bound) - you can specify however many you want - so when sorting two objects in the `DataSource`, the first `sortInfo` is used to compare the two, and then, on equal values, the next `sortInfo` is used and so on.\n\n\n\n\n\nThis table allows sorting multiple columns - initially the `country` column is sorted in descending order and the `salary` column is sorted in ascending order. Click the `salary` column to toggle the column sort to descending. Clicking it a second time will remove it from the sort altogether.\n\n\n\n```ts file=\"local-uncontrolled-multi-sorting-example-with-remote-data.page.tsx\"\n\n```\n\n\n\n\n\n```ts file=\"remote-uncontrolled-multi-sorting-example.page.tsx\"\n\n```\n\n\n\n\n\nIf you use uncontrolled sorting via there's no way to switch between single and multiple sorting after the component is mounted. If you have this use-case, you need to use the controlled prop.\n\n\n\n## Understanding sort mode\n\nSorting can be done both locally in the browser and remotely on the server. For configuring where sorting is performed you need to specify the . Possible values for are `false` (for local sorting) and `true` (for remote sorting).\n\nThis allows you fine-grained control on how sorting is done, either in the client or on the server.\n\n### Uncontrolled sorting\n\nIf you use uncontrolled sorting (namely you don't care about updating the yourself as a result of user interaction - via ) - then by default, the is `false` (so local sorting) unless you specify otherwise.\n\nYou can initially render the component with no sort state or you can specify a default sorting state, via the uncontrolled prop .\n\n```tsx\n// initially render the component with ascending sorting on `firstName` field\n// also, note this is an array, so multiple sorting will be enabled\nconst defaultSortInfo = [{ field: 'firstName', dir: 1 }];\n\n\n primaryKey=\"id\"\n data={data}\n defaultSortInfo={defaultSortInfo}\n>\n \n
;\n```\n\nIf your data is remote and you want the sorting to happen on the backend, you can still use uncontrolled sorting, but you need to specify shouldReloadData.sortInfo=true.\n\nUsing remote sort mode will trigger a call to the function whenever sorting changes, so you can re-fetch the data from the backend, according to the new `sortInfo`.\n\nWhe `local` uncontrolled sorting is used, the `` sorts the data internally, based on the existing sorting information. To start with a specific `sortInfo`, use the prop. As the user interacts with the table, is being called with updated sort info and the `` continues to sort the data accordingly.\n\n\n\nThe prop is an uncontrolled prop, so it's all managed inside the `` component and you can't change it from the outside. If you need to control it from outside the component, use the controlled sortInfo prop - read the next section for more details\n\n\n\n### Controlled Sorting\n\nWhen you use the controlled prop, by default the is `true` (remote sorting), unless you specify otherwise.\n\nAlso, be aware that when the user interacts with the DataGrid when controlled sorting is configured, the prop will not update automatically - you need to listen to and update the yourself.\n\nJust like with uncontrolled sorting, updating the controlled when `shouldReloadData.sortInfo=true`, will trigger a call to the function, so new sorted data can be re-fetched.\n\n\n\nWhen the controlled is combined with shouldReloadData.sortInfo=false, the `` will sort the data internally, on any changes of the sorting information.\n\nBut remember it's your responsibility to update the prop when the user interacts with the DataGrid.\n\n\n\nBoth controlled and uncontrolled work in combination with - use it to be notified when sorting changes, so you can react and update your app accordingly if needed.\n\n### Local Sorting\n\nWhen you use uncontrolled sorting locally, the `` will sort the data internally, based on the prop. Local sorting is available for any configured source - be it an array or a function that returns a promise.\n\n\n\nYou can use , which is called whenever any of the sorting, filtering, grouping or pivoting information changes.\n\n\n\n\n\n```ts file=\"local-uncontrolled-single-sorting-example-with-remote-data.page.tsx\"\n\n```\n\n\n\n\n\n```ts file=\"local-uncontrolled-single-sorting-example-with-remote-data.page.tsx\"\n\n```\n\n\n\n### Remote Sorting\n\nSorting remotely makes a lot of sense when using a function as your source. Whenever the sort information is changed, the function will be called with all the information needed to retrieve the data from the remote endpoint.\n\n\n\nFor remote sorting, make sure you specify shouldReloadData.sortInfo=true - if you don't, the data will also be sorted locally in the browser (which most of the times will be harmless, but it means wasted CPU cycles).\n\n\n\n\n\n```ts file=\"remote-controlled-multi-sorting-example.page.tsx\"\n\n```\n\n\n\nIn the example above, remote and controlled sorting are combined - because `shouldReloadData.sortInfo=true` is specified, the `` will call the `data` function whenever sorting changes, and will pass in the `dataParams` object that contains the sort information.\n\n## Custom Sort Functions with `sortTypes`\n\nBy default, all columns are sorted as strings, even if they contain numeric values. To make numeric columns sort as numbers, you need to specify a `dataType` for the column, or, a column `sortType`.\n\nThere are two `dataType` values that can be used:\n\n- `\"string\"`\n- `\"number\"`\n\nEach dataType has its own sorting function and its own filtering operators & functions.\n\nSorting works in combination with the property, which is an object with keys being sort types and values being functions that compare two values of the same type.\n\n```ts\nconst sortTypes = {\n string: (a, b) => a.localeCompare(b),\n number: (a, b) => a - b,\n};\n```\n\nThose are the two sort types supported by default.\n\n\n\nThe functions specified in the object need to always sort data in ascending order.\n\n\n\n\nA column can choose to use a specific , in which case, for local sorting, the corresponding sort function will be used, or, it can simply specify a dataType and the `sortType` with the same name will be used (when no explicit sortType is defined).\n\nTo conclude, the dataType of a column will be used as the sortType and filterType, when those are not explicitly specified.\n\n\n\n\n\n```ts file=\"$DOCS/reference/datasource-props/sortTypes-example.page.tsx\"\n\n```\n\n\n\n\n\nIn this example, for the `\"color\"` column, we specified column.sortType=\"color\" - we could have passed that as `column.dataType` instead, but if the grid had filtering, it wouldn't know what filters to use for \"color\" - so we used column.sortType to only change how the data is sorted.\n\n\n\n\n\nWhen you provide a prop and the sorting information uses a custom sortType, make sure you specify that as the `type` property of the sorting info object.\n\n```tsx\ndefaultSortInfo={{\n field: 'color',\n dir: 1,\n // note this custom sort type\n type: 'color',\n}}\n```\n\nYou will need to have a property for that type in your object as well.\n\n```tsx\nsortTypes={{\n color: (a, b) => //...\n}}\n```\n\n\n\n## Replacing the sort function\n\nWhile there are many ways to customise sorting, including the mentioned above, you might want to completely replace the sorting function used by the `` component.\n\nYou can do this by configuring the prop.\n\n```tsx\nconst sortFunction = (sortInfo, dataArray) => {\n // sort the dataArray according to the sortInfo\n // and return the sorted array\n // return sortedDataArray;\n};\n sortFunction={sortFunction} />;\n```\n\nThe function specified in the prop is called with the as the first argument and the data array as the second. It should return a sorted array, as per the it was called with.\n\n\n\nWhen is specified, will be forced to `false`, as the sorting is done in the browser.\n\n\n\n\n```ts file=\"$DOCS/reference/datasource-props/local-sortFunction-single-sorting-example-with-local-data-example.page.tsx\"\n\n```\n\n\n" }, - { - "filePath": "/docs/learn/theming/css-variables", - "routePath": "/docs/learn/theming/css-variables", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/learn/theming/css-variables.page.md", - "fileName": "css-variables.page.md", - "folderPath": "/docs/learn/theming/", - "frontmatter": { - "title": "CSS Variables", - "description": "Reference list of CSS variables that can be used to style the Infinite Table for React" - }, - "excerpt": "Below you can find the complete list of CSS variables that can be used to style the component.", - "readingTime": "9 min read", - "content": "\nBelow you can find the complete list of CSS variables that can be used to style the component.\n\n{/* START VARS */}\n### Accent color\n\nBrand-specific accent color. This probably needs override to match your app.\n\n```css\n--infinite-accent-color\n```\n\n### Error color\n\n\n\n```css\n--infinite-error-color\n```\n\n### Color\n\nThe text color inside the component\n\n```css\n--infinite-color\n```\n\n### Space 0\n\n\n\n```css\n--infinite-space-0\n```\n\n### Space 1\n\n\n\n```css\n--infinite-space-1\n```\n\n### Space 2\n\n\n\n```css\n--infinite-space-2\n```\n\n### Space 3\n\n\n\n```css\n--infinite-space-3\n```\n\n### Space 4\n\n\n\n```css\n--infinite-space-4\n```\n\n### Space 5\n\n\n\n```css\n--infinite-space-5\n```\n\n### Space 6\n\n\n\n```css\n--infinite-space-6\n```\n\n### Space 7\n\n\n\n```css\n--infinite-space-7\n```\n\n### Space 8\n\n\n\n```css\n--infinite-space-8\n```\n\n### Space 9\n\n\n\n```css\n--infinite-space-9\n```\n\n### Space 10\n\n\n\n```css\n--infinite-space-10\n```\n\n### Font size 0\n\n\n\n```css\n--infinite-font-size-0\n```\n\n### Font size 1\n\n\n\n```css\n--infinite-font-size-1\n```\n\n### Font size 2\n\n\n\n```css\n--infinite-font-size-2\n```\n\n### Font size 3\n\n\n\n```css\n--infinite-font-size-3\n```\n\n### Font size 4\n\n\n\n```css\n--infinite-font-size-4\n```\n\n### Font size 5\n\n\n\n```css\n--infinite-font-size-5\n```\n\n### Font size 6\n\n\n\n```css\n--infinite-font-size-6\n```\n\n### Font size 7\n\n\n\n```css\n--infinite-font-size-7\n```\n\n### Font family\n\n\n\n```css\n--infinite-font-family\n```\n\n### Min height\n\n\n\n```css\n--infinite-min-height\n```\n\n### Border radius\n\n\n\n```css\n--infinite-border-radius\n```\n\n### Background\n\nThe background color for the whole component.\n\nOverriden in the `dark` theme.\n\n```css\n--infinite-background\n```\n\n### Icon size\n\n\n\n```css\n--infinite-icon-size\n```\n\n### Load mask padding\n\nThe padding used for the content inside the LoadMask.\n\n```css\n--infinite-load-mask-padding\n```\n\n### Load mask color\n\n\n\n```css\n--infinite-load-mask-color\n```\n\n### Load mask text background\n\n\n\n```css\n--infinite-load-mask-text-background\n```\n\n### Load mask overlay background\n\n\n\n```css\n--infinite-load-mask-overlay-background\n```\n\n### Load mask overlay opacity\n\n\n\n```css\n--infinite-load-mask-overlay-opacity\n```\n\n### Load mask border radius\n\n\n\n```css\n--infinite-load-mask-border-radius\n```\n\n### Header background\n\nBackground color for the header. Defaults to [`--infinie-header-cell-background`](#header-cell-background).\n\nOverriden in the `dark` theme.\n\n```css\n--infinite-header-background\n```\n\n### Header color\n\nThe text color inside the header.\n\nOverriden in the `dark` theme.\n\n```css\n--infinite-header-color\n```\n\n### Column header height\n\nThe height of the column header.\n\n```css\n--infinite-column-header-height\n```\n\n### Header cell background\n\nBackground for header cells.\n\nOverriden in the `dark` theme.\n\n```css\n--infinite-header-cell-background\n```\n\n### Header cell hover background\n\n\n\n```css\n--infinite-header-cell-hover-background\n```\n\n### Header cell padding\n\n\n\n```css\n--infinite-header-cell-padding\n```\n\n### Header cell padding x\n\n\n\n```css\n--infinite-header-cell-padding-x\n```\n\n### Header cell padding y\n\n\n\n```css\n--infinite-header-cell-padding-y\n```\n\n### Header cell icon size\n\n\n\n```css\n--infinite-header-cell-icon-size\n```\n\n### Header cell menu icon line width\n\n\n\n```css\n--infinite-header-cell-menu-icon-line-width\n```\n\n### Header cell sort icon margin\n\n\n\n```css\n--infinite-header-cell-sort-icon-margin\n```\n\n### Resize handle active area width\n\nThe width of the area you can hover over in order to grab the column resize handle.\nDefaults to `20px`.\n\nThe purpose of this active area is to make it easier to grab the resize handle.\n\n```css\n--infinite-resize-handle-active-area-width\n```\n\n### Resize handle width\n\nThe width of the colored column resize handle that is displayed on hover and on drag. Defaults to `2px`\n\n```css\n--infinite-resize-handle-width\n```\n\n### Resize handle hover background\n\nThe color of the column resize handle - the resize handle is the visible indicator that you see\nwhen hovering over the right-edge of a resizable column. Also visible on drag while doing a column resize.\n\n```css\n--infinite-resize-handle-hover-background\n```\n\n### Resize handle constrained hover background\n\nThe color of the column resize handle when it has reached a min/max constraint.\n\n```css\n--infinite-resize-handle-constrained-hover-background\n```\n\n### Filter operator padding x\n\n\n\n```css\n--infinite-filter-operator-padding-x\n```\n\n### Filter editor padding x\n\n\n\n```css\n--infinite-filter-editor-padding-x\n```\n\n### Filter editor margin x\n\n\n\n```css\n--infinite-filter-editor-margin-x\n```\n\n### Filter operator padding y\n\n\n\n```css\n--infinite-filter-operator-padding-y\n```\n\n### Filter editor padding y\n\n\n\n```css\n--infinite-filter-editor-padding-y\n```\n\n### Filter editor margin y\n\n\n\n```css\n--infinite-filter-editor-margin-y\n```\n\n### Filter editor background\n\n\n\n```css\n--infinite-filter-editor-background\n```\n\n### Filter editor border\n\n\n\n```css\n--infinite-filter-editor-border\n```\n\n### Filter editor focus border color\n\n\n\n```css\n--infinite-filter-editor-focus-border-color\n```\n\n### Filter editor border radius\n\n\n\n```css\n--infinite-filter-editor-border-radius\n```\n\n### Filter editor color\n\n\n\n```css\n--infinite-filter-editor-color\n```\n\n### Cell padding\n\n\n\n```css\n--infinite-cell-padding\n```\n\n### Cell border width\n\n\n\n```css\n--infinite-cell-border-width\n```\n\n### Cell border\n\nSpecifies the border for cells.\n\nOverriden in the `dark` theme - eg: `1px solid #2a323d`\n\n```css\n--infinite-cell-border\n```\n\n### Cell border invisible\n\n\n\n```css\n--infinite-cell-border-invisible\n```\n\n### Cell border radius\n\n\n\n```css\n--infinite-cell-border-radius\n```\n\n### Column reorder effect duration\n\n\n\n```css\n--infinite-column-reorder-effect-duration\n```\n\n### Pinned cell border\n\n\n\n```css\n--infinite-pinned-cell-border\n```\n\n### Cell color\n\nText color inside rows. Defaults to `currentColor`\n\nOverriden in `dark` theme.\n\n```css\n--infinite-cell-color\n```\n\n### Selected cell background\n\nThe background for selected cells, when cell selection is enabled.\n\nIf not specified, it will default to `var(--infinite-active-cell-background)`.\n\n```css\n--infinite-selected-cell-background\n```\n\n### Selected cell background default\n\n\n\n```css\n--infinite-selected-cell-background-default\n```\n\n### Selected cell background alpha\n\nThe opacity of the background color for the selected cell.\n\nIf not specified, it will default to the value for `var(--infinite-active-cell-background-alpha)`\n\n```css\n--infinite-selected-cell-background-alpha\n```\n\n### Selected cell background alpha table unfocused\n\nThe opacity of the background color for the selected cell, when the table is unfocused.\nIf not specified, it will default to `var(--infinite-active-cell-background-alpha--table-unfocused)`.\n\n```css\n--infinite-selected-cell-background-alpha--table-unfocused\n```\n\n### Selected cell border color\n\nThe color for border of the selected cell (when cell selection is enabled).\n Defaults to `var(--infinite-active-cell-border-color)`.\n\n```css\n--infinite-selected-cell-border-color\n```\n\n### Selected cell border width\n\nThe width of the border for the selected cell. Defaults to `var(--infinite-active-cell-border-width)`.\n\n```css\n--infinite-selected-cell-border-width\n```\n\n### Selected cell border style\n\nThe style of the border for the selected cell (eg: 'solid', 'dashed', 'dotted') - defaults to 'dashed'.\nDefaults to `var(--infinite-active-cell-border-style)`.\n\n```css\n--infinite-selected-cell-border-style\n```\n\n### Selected cell border\n\nSpecifies the border for the selected cell. Defaults to `var(--infinite-selected-cell-border-width) var(--infinite-selected-cell-border-style) var(--infinite-selected-cell-border-color)`.\n\n```css\n--infinite-selected-cell-border\n```\n\n### Active cell background alpha\n\nThe opacity of the background color for the active cell (when cell keyboard navigation is enabled).\nEg: 0.25\n\nIf `activeBackground` is not explicitly defined (this is the default), the background color of the active cell\nis the same as the border color (`activeBorderColor`), but with this modified opacity.\n\nIf `activeBorderColor` is also not defined, the accent color will be used.\n\nThis is applied when the component has focus.\n\n```css\n--infinite-active-cell-background-alpha\n```\n\n### Active cell background alpha table unfocused\n\nSame as the above, but applied when the component does not have focus.\n\n```css\n--infinite-active-cell-background-alpha--table-unfocused\n```\n\n### Active cell background\n\nThe background color of the active cell.\n\nIf not specified, it will default to `activeBorderColor` with the opacity of `activeBackgroundAlpha`.\nIf `activeBorderColor` is not specified, it will default to the accent color, with the same opacity as mentioned.\n\nHowever, specify this to explicitly override the default.\n\n```css\n--infinite-active-cell-background\n```\n\n### Active cell background default\n\n\n\n```css\n--infinite-active-cell-background-default\n```\n\n### Active cell border color\n\nThe color for border of the active cell (when cell keyboard navigation is enabled).\n\n```css\n--infinite-active-cell-border-color\n```\n\n### Active cell border width\n\nThe width of the border for the active cell.\n\n```css\n--infinite-active-cell-border-width\n```\n\n### Active cell border style\n\nThe style of the border for the active cell (eg: 'solid', 'dashed', 'dotted') - defaults to 'dashed'.\n\n```css\n--infinite-active-cell-border-style\n```\n\n### Active cell border\n\nSpecifies the border for the active cell. Defaults to `var(--infinite-active-cell-border-width) var(--infinite-active-cell-border-style) var(--infinite-active-cell-border-color)`.\n\n```css\n--infinite-active-cell-border\n```\n\n### Selection checkbox margin inline\n\n\n\n```css\n--infinite-selection-checkbox-margin-inline\n```\n\n### Menu background\n\n\n\n```css\n--infinite-menu-background\n```\n\n### Menu color\n\n\n\n```css\n--infinite-menu-color\n```\n\n### Menu padding\n\n\n\n```css\n--infinite-menu-padding\n```\n\n### Menu cell padding vertical\n\n\n\n```css\n--infinite-menu-cell-padding-vertical\n```\n\n### Menu cell padding horizontal\n\n\n\n```css\n--infinite-menu-cell-padding-horizontal\n```\n\n### Menu cell margin vertical\n\n\n\n```css\n--infinite-menu-cell-margin-vertical\n```\n\n### Menu item disabled background\n\n\n\n```css\n--infinite-menu-item-disabled-background\n```\n\n### Menu item active background\n\n\n\n```css\n--infinite-menu-item-active-background\n```\n\n### Menu item active opacity\n\n\n\n```css\n--infinite-menu-item-active-opacity\n```\n\n### Menu item pressed opacity\n\n\n\n```css\n--infinite-menu-item-pressed-opacity\n```\n\n### Menu item pressed background\n\n\n\n```css\n--infinite-menu-item-pressed-background\n```\n\n### Menu item disabled opacity\n\n\n\n```css\n--infinite-menu-item-disabled-opacity\n```\n\n### Menu border radius\n\n\n\n```css\n--infinite-menu-border-radius\n```\n\n### Menu shadow color\n\n\n\n```css\n--infinite-menu-shadow-color\n```\n\n### Rowdetail background\n\n\n\n```css\n--infinite-rowdetail-background\n```\n\n### Rowdetail padding\n\n\n\n```css\n--infinite-rowdetail-padding\n```\n\n### Rowdetail grid height\n\n\n\n```css\n--infinite-rowdetail-grid-height\n```\n\n### Row background\n\nBackground color for rows. Defaults to [`--infinite-background`](#background).\n\nOverriden in `dark` theme.\n\n```css\n--infinite-row-background\n```\n\n### Row odd background\n\nBackground color for odd rows. Even rows will use [`--infinite-row-background`](#row-background).\n\nOverriden in `dark` theme.\n\n```css\n--infinite-row-odd-background\n```\n\n### Row selected background\n\n\n\n```css\n--infinite-row-selected-background\n```\n\n### Active row background\n\nThe background color of the active row. Defaults to the value of `var(--infinite-active-cell-background)`.\n\nHowever, specify this to explicitly override the default.\n\n```css\n--infinite-active-row-background\n```\n\n### Active row border color\n\nThe border color for the active row. Defaults to the value of `var(--infinite-active-cell-border-color)`.\n\n```css\n--infinite-active-row-border-color\n```\n\n### Active row border width\n\nThe width of the border for the active row. Defaults to the value of `var(--infinite-active-cell-border-width)`.\n\n```css\n--infinite-active-row-border-width\n```\n\n### Active row border style\n\nThe style of the border for the active row (eg: 'solid', 'dashed', 'dotted') - defaults to the value of `var(--infinite-active-cell-border-style)`, which is `dashed` by default.\n\n```css\n--infinite-active-row-border-style\n```\n\n### Active row border\n\nSpecifies the border for the active row. Defaults to `var(--infinite-active-row-border-width) var(--infinite-active-row-border-style) var(--infinite-active-row-border-color)`.\n\n```css\n--infinite-active-row-border\n```\n\n### Active row background alpha\n\nThe opacity of the background color for the active row (when row keyboard navigation is enabled).\nWhen you explicitly specify `--infinite-active-row-background`, this variable will not be used.\nInstead, this variable is used when the active row background uses the color of the active cell (border).\n\nThis is applied when the component has focus.\n\nDefaults to the value of `var(--infinite-active-cell-background-alpha)`.\n\n```css\n--infinite-active-row-background-alpha\n```\n\n### Active row background alpha table unfocused\n\nSame as the above, but applied when the component does not have focus.\n\nWhen you explicitly specify `--infinite-active-row-background`, this variable will not be used.\nInstead, this variable is used when the active row background uses the color of the active cell (border).\n\nDefaults to the value of `var(--infinite-active-cell-background-alpha--table-unfocused)`.\n\n```css\n--infinite-active-row-background-alpha--table-unfocused\n```\n\n### Row hover background\n\nBackground color for rows, on hover.\n\nOverriden in the `dark` theme.\n\n```css\n--infinite-row-hover-background\n```\n\n### Row selected hover background\n\n\n\n```css\n--infinite-row-selected-hover-background\n```\n\n### Group row background\n\n\n\n```css\n--infinite-group-row-background\n```\n\n### Group row column nesting\n\n\n\n```css\n--infinite-group-row-column-nesting\n```\n\n### Row pointer events while scrolling\n\n\n\n```css\n--infinite-row-pointer-events-while-scrolling\n```{/* END VARS */}\n" - }, - { - "filePath": "/docs/learn/theming/index", - "routePath": "/docs/learn/theming/", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/learn/theming/index.page.md", - "fileName": "index.page.md", - "folderPath": "/docs/learn/theming/", - "frontmatter": { - "title": "Theming", - "description": "Read our docs on the available themes and how you can customize the look and feel of InfiniteTable for React." - }, - "excerpt": "`` ships with a CSS file that you need to import in your codebase to make the component look as intended.", - "readingTime": "3 min read", - "content": "\n`` ships with a CSS file that you need to import in your codebase to make the component look as intended.\n\n```ts\nimport '@infinite-table/infinite-react/index.css';\n```\n\nThis root CSS file includes the `\"default\"` theme.\n\nThe other available themes are\n\n - `shadcn`\n - `minimalist`\n - `ocean`\n - `balsam`\n\nand if you want to use any of them, you have to import their respective CSS file explicitly:\n\n```ts\nimport '@infinite-table/infinite-react/theme/shadcn.css'\nimport '@infinite-table/infinite-react/theme/balsam.css'\nimport '@infinite-table/infinite-react/theme/minimalist.css'\nimport '@infinite-table/infinite-react/theme/ocean.css'\n```\n\nEach theme CSS file includes both the **`light`** and the **`dark`** modes.\n\n\n\nVersion `6.2.0` is the first version where the root CSS file (`@infinite-table/infinite-react/index.css`) doesn't include all the themes. Previous to this version, simply importing the root CSS file gave you access to all available themes.\n\nSplitting each theme into a dedicated CSS file helps reduce the bundle size for our users, as most people will only use one theme for `` in their apps.\n\n\n\n## Applying a theme\n\nThe following themes are currently available:\n\n - `default` - applied by default, no special configuration needed. It's included in the root CSS you need to import from `@infinite-table/infinite-react/index.css`\n - `balsam`\n - `minimalist`\n - `ocean`\n - `shadcn` - for this theme to correctly show up, make sure the shadcn CSS vars are available on page - see [shadcn theming](https://ui.shadcn.com/docs/theming) for details\n\n\nTo apply a theme (except the default one), you have to set the className `\"infinite-theme-name--THEME_NAME\"` to any parent element of the `` component (or even on the component itself).\n\nYou will want to apply the theme name and theme mode classNames to the same element, so you'll end up with a className like `\"infinite-theme-name--minimalist infinite-theme-mode--dark\"`.\n\n```tsx title=\"Applying the minimalist theme with dark mode explicitly\"\n\n \n \n \n\n```\n\n\n\n\n\n\n\nExample configured with `minimalist` theme and `dark` mode by default.\n\n\n```tsx live title=\"Theme switching demo - defaults to minimalist theme in dark mode\" size=\"md\" viewMode=\"preview\" files=\"theme-switching-minimalist-theme-default-example.page.tsx,columns.ts\"\n```\n\n\n\n\n## Theme mode - light or dark\n\nAt runtime, the `light` or `dark` mode is applied based on the user OS settings for the [preferred color scheme](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme).\n\nTo explicitly apply the light mode, apply the className `\"infinite-theme-mode--light\"` to any parent element of the `` component.\n\nTo explicitly apply the dark mode, apply the className `\"infinite-theme-mode--dark\"` to any parent element of the `` component.\n\n```tsx title=\"Explicitly applying light mode via container className\"\n
\n \n \n \n
\n```\n\nIf instead you specify a `infinite-theme-mode--dark` CSS className, the dark mode will be applied\n\n```tsx title=\"Explicitly applying dark theme via container className\"\n\n
\n \n \n \n
\n\n```\n\n\n\nExample configured with `default` theme and `light` mode by default.\n\n\n```tsx live title=\"Theme switching demo - defaults to light theme\" size=\"md\" viewMode=\"preview\" files=\"theme-switching-example.page.tsx,columns.ts\"\n\n```\n\n\n\nIf you don't explicitly have a `infinite-theme-mode--light` or `infinite-theme-mode--dark` ancestor, `InfiniteTable` will use the browser/OS preference (via `@media (prefers-color-scheme: ...)`) to apply the dark or light theme.\n\n\n\n## Available themes\n\n### Default theme\n\nThe `default` theme is applied when you don't specify any explicit theme by default.\n\n### Minimalist theme\n\nThe `minimalist` theme is inspired from minimalistic designs and is a good choice if you want to keep the UI simple and clean." - }, { "filePath": "/docs/learn/tree-grid/overview", "routePath": "/docs/learn/tree-grid/overview", @@ -1049,7 +1049,7 @@ }, "excerpt": "When rendering the `InfiniteTable` component, you can get access to the API by getting it from the callback prop.", "readingTime": "8 min read", - "content": "\nWhen rendering the `InfiniteTable` component, you can get access to the API by getting it from the callback prop.\n\n```tsx {2}\nconst onReady = (\n {api, dataSourceApi}: {\n api: InfiniteTableApi,\n dataSourceApi: DataSourceApi\n }) => {\n // api is accessible here\n // you may want to store a reference to it in a ref or somewhere in your app state\n}\n\n\n columns={[...]}\n onReady={onReady}\n/>\n```\n\nFor API on row/group selection, see the [Selection API page](/docs/reference/selection-api).\n\nSee the [Infinite Table Cell Selection API page](/docs/reference/cell-selecti-api) for the cell selection API.\nSee the [Infinite Table Column API page](/docs/reference/column-api) for the column API.\nSee the [Infinite Table Keyboard Navigation API page](/docs/reference/keyboard-navigation-api) for the keyboard navigation API.\n\nSee the [Infinite Table Row Details API page](/docs/reference/row-detail-api) for the row detail API (when master-detail is configured).\nSee the [Tree API page](/docs/reference/tree-api) for the tree API (when using the `` component).\n\n\n\n void\">\n\n> Confirms the current edit operation and closes the editor.\n\nIf the `value` parameter is provided, it will be used as the value the cell will be updated with. If the `value` parameter is not provided, the current value of the cell will be used.\n\nSee related and .\n\n\n\n void\">\n\n> Cancels the current edit operation and closes the editor.\n\nSee related and .\n\n\n\n void\">\n\n> Hides the context menu that's currently displayed (if there's one).\n\n\n\n void\">\n\n> Rejects the current edit operation with the specified error and closes the editor.\n\nThe error will later be available to the callback prop, via the parameter of the function (also applicable for related functions that are called with same the same parameter).\n\nSee related and .\n\n\n\n void\">\n\n> Clears any filter for the specified column\n\n\n\n void\">\n\n> Toggles the sorting for the specified column.\n\nThis is the same method the component uses internally when the user clicks a column header.\n\nIf the column is not sorted, it gets sorted in ascending order.\n\nIf the column is sorted in ascending order, it gets sorted in descending order.\n\nIf the column is sorted in descending order, the sorting is cleared.\n\n\n\nThe `options` is optional and can have the `multiSortBehavior` property, which can be either `append` or `replace`. See related prop. If not provided, the default behavior is used.\n\n\n\nSee related and .\n\n\n\n void\">\n\n> Sets the sorting for the specified column.\n\nThe sort direction is specified by the `dir` parameter, which can be:\n\n- `1` for ascending\n- `-1` for descending\n- `null` for clearing the sorting.\n\nSee related and .\n\n\n\n 1|-1|null\">\n\n> Returns the sorting currently applied to the specified column.\n\nThe return value is:\n\n- `1` for ascending\n- `-1` for descending\n- `null` for no sorting.\n\nSee related and .\n\n\n\n boolean\">\n\n> Collapses the specified group row. Returns true if the group was expanded and is now being collapsed.\n\n```tsx\napi.collapseGroupRow(['USA', 'New York']); // collapses the group with these keys\n```\n\n\n\n boolean\">\n\n> Expands the specified group row. Returns true if the group was collapsed and is now being expanded.\n\n```tsx\napi.expandGroupRow(['USA', 'New York']); // expands the group with these keys\n```\n\n\n\n any\">\n\n> Returns the value for the specified cell. The value is either the raw value (as retrieved via the `field` property of the column or by calling the column valueGetter) or the formatted value - if the column has a valueFormatter.\n\nCall this function with an object that has a `columnId` and either a `rowIndex` or a `primaryKey` property.\n\nSee related .\n\n\n\nThis function should not be called during a cell render (eg: in / or other functions called during render).\n\n\n\n\n\n ({value, rawValue, formattedValue })\">\n\n> Returns an object with raw and formatted values for the specified cell.\n\nCall this function with an object that has a `columnId` and either a `rowIndex` or a `primaryKey` property.\n\nThe returned object has the following properties:\n\n- `rawValue` - the raw value of the cell - as retrieved from the property of the column or by calling the column valueGetter\n- `formattedValue` - the formatted value of the cell - if the column has a valueFormatter, it's the value returned by the formatter, otherwise it's the same as the `rawValue`\n- `value` - it's either `formattedValue` or `rawValue`. If the column has a valueFormatter, it's the value returned by the formatter, otherwise it's the `rawValue`\n\nSee related .\n\n\n\nThis function should not be called during a cell render (eg: in / or other functions called during render).\n\n\n\n\n\n InfiniteTableColumnAPI\">\n\n> Returns [a column API object](/docs/reference/column-api) bound to the specified column\n\nThe parameter can be either a column id or a column index (note this is not the index in all columns, but rather the index in current visible columns).\n\n\n\n { renderStartIndex, renderEndIndex }\">\n\n> Returns the vertical render range of the table\n\nThe vertical render range is the range of rows that are currently rendered in the table viewport.\n\n\n\n void\">\n\n> Called when the table has been layed out and sized and is ready to be used.\n\nThis callback prop will be called with an object containing the `api` (which is an instance of `InfiniteTableApi`) and [`dataSourceApi`](/docs/reference/datasource-api) objects.\n\n\n\n Promise\">\n\n> Tries to start editing the cell specified by the given row index and column id.\n\nReturns a promise that resolves to `true` if editing was started, or `false` if editing was not started because the cell is not editable.\n\nSee for more details on how to configure a cell as editable.\n\n\n\n```ts file=\"api-start-edit-example.page.tsx\"\n\n```\n\n\n\n\n\n boolean\">\n\n> Can be used to scroll a cell into the visible viewport\n\nIf scrolling was successful and the row and column combination was found, it returns `true`, otherwise `false`. The first arg of the function is the row index, while the second one is the column id or the column index (note this is not the index in all columns, but rather the index in current visible columns).\n\n\n\n boolean\">\n\n> Can be used to scroll a column into the visible viewport\n\nIf scrolling was successful and the column was found, it returns `true`, otherwise `false`.\nThe only parameter of this method is the column id.\n\n\n\n|setter\">\n\n> Gets or sets the `scrollLeft` value in the grid viewport\n\nCan be used as either a setter, to set the scroll left position or a getter to read the scroll left position.\n\n```ts\n// use as setter - will scroll the table viewport\napi.scrollLeft = 200;\n\n// use as getter to read the current scroll left value\nconst scrollLeft = api.scrollLeft;\n```\n\n\n\n boolean\">\n\n> Can be used to scroll a row into the visible viewport\n\nIf scrolling was successful and the row was found, it returns `true`, otherwise `false`\n\n\n\n|setter\">\n\n> Gets or sets the `scrollTop` value in the grid viewport\n\nCan be used as either a setter, to set the scroll top position or a getter to read the scroll top position.\n\n```ts\n// use as setter - will scroll the table viewport\napi.scrollTop = 1200;\n\n// use as getter to read the current scroll top value\nconst scrollTop = api.scrollTop;\n```\n\n\n\n\n\n> Getter for the [Row Selection API](/docs/reference/row-selection-api)\n\n\n\n\n\n> Getter for the [Row Detail API](/docs/reference/row-detail-api)\n\n\n\n\n> Getter for the [Cell Selection API](/docs/reference/cell-selection-api)\n\n\n\nvoid\">\n\n> Sets a filter value for the specified column\n\n\n\n void\">\n\n> Set the column order.\n\nIf `true` is specified, it resets the column order to the order the columns are specified in the prop (the iteration order of that object).\n\n```ts\napi.setColumnOrder(['id', 'firstName', 'age']);\n// restore default order\napi.setColumnOrder(true);\n```\n\n\n\n void\">\n\n> Toggles the collapse/expand state of the specified group row\n\n```tsx\napi.toggleGroupRow(['USA', 'New York']); // toggle the group with these keys\n```\n\n\n\n\n" + "content": "\nWhen rendering the `InfiniteTable` component, you can get access to the API by getting it from the callback prop.\n\n```tsx {2}\nconst onReady = (\n {api, dataSourceApi}: {\n api: InfiniteTableApi,\n dataSourceApi: DataSourceApi\n }) => {\n // api is accessible here\n // you may want to store a reference to it in a ref or somewhere in your app state\n}\n\n\n columns={[...]}\n onReady={onReady}\n/>\n```\n\nFor API on row/group selection, see the [Selection API page](/docs/reference/selection-api).\n\nSee the [Infinite Table Cell Selection API page](/docs/reference/cell-selection-api) for the cell selection API.\nSee the [Infinite Table Column API page](/docs/reference/column-api) for the column API.\nSee the [Infinite Table Keyboard Navigation API page](/docs/reference/keyboard-navigation-api) for the keyboard navigation API.\n\nSee the [Infinite Table Row Details API page](/docs/reference/row-detail-api) for the row detail API (when master-detail is configured).\nSee the [Tree API page](/docs/reference/tree-api) for the tree API (when using the `` component).\n\n\n\n void\">\n\n> Confirms the current edit operation and closes the editor.\n\nIf the `value` parameter is provided, it will be used as the value the cell will be updated with. If the `value` parameter is not provided, the current value of the cell will be used.\n\nSee related and .\n\n\n\n void\">\n\n> Cancels the current edit operation and closes the editor.\n\nSee related and .\n\n\n\n void\">\n\n> Hides the context menu that's currently displayed (if there's one).\n\n\n\n void\">\n\n> Rejects the current edit operation with the specified error and closes the editor.\n\nThe error will later be available to the callback prop, via the parameter of the function (also applicable for related functions that are called with same the same parameter).\n\nSee related and .\n\n\n\n void\">\n\n> Clears any filter for the specified column\n\n\n\n void\">\n\n> Toggles the sorting for the specified column.\n\nThis is the same method the component uses internally when the user clicks a column header.\n\nIf the column is not sorted, it gets sorted in ascending order.\n\nIf the column is sorted in ascending order, it gets sorted in descending order.\n\nIf the column is sorted in descending order, the sorting is cleared.\n\n\n\nThe `options` is optional and can have the `multiSortBehavior` property, which can be either `append` or `replace`. See related prop. If not provided, the default behavior is used.\n\n\n\nSee related and .\n\n\n\n void\">\n\n> Sets the sorting for the specified column.\n\nThe sort direction is specified by the `dir` parameter, which can be:\n\n- `1` for ascending\n- `-1` for descending\n- `null` for clearing the sorting.\n\nSee related and .\n\n\n\n 1|-1|null\">\n\n> Returns the sorting currently applied to the specified column.\n\nThe return value is:\n\n- `1` for ascending\n- `-1` for descending\n- `null` for no sorting.\n\nSee related and .\n\n\n\n boolean\">\n\n> Collapses the specified group row. Returns true if the group was expanded and is now being collapsed.\n\n```tsx\napi.collapseGroupRow(['USA', 'New York']); // collapses the group with these keys\n```\n\n\n\n boolean\">\n\n> Expands the specified group row. Returns true if the group was collapsed and is now being expanded.\n\n```tsx\napi.expandGroupRow(['USA', 'New York']); // expands the group with these keys\n```\n\n\n\n any\">\n\n> Returns the value for the specified cell. The value is either the raw value (as retrieved via the `field` property of the column or by calling the column valueGetter) or the formatted value - if the column has a valueFormatter.\n\nCall this function with an object that has a `columnId` and either a `rowIndex` or a `primaryKey` property.\n\nSee related .\n\n\n\nThis function should not be called during a cell render (eg: in / or other functions called during render).\n\n\n\n\n\n ({value, rawValue, formattedValue })\">\n\n> Returns an object with raw and formatted values for the specified cell.\n\nCall this function with an object that has a `columnId` and either a `rowIndex` or a `primaryKey` property.\n\nThe returned object has the following properties:\n\n- `rawValue` - the raw value of the cell - as retrieved from the property of the column or by calling the column valueGetter\n- `formattedValue` - the formatted value of the cell - if the column has a valueFormatter, it's the value returned by the formatter, otherwise it's the same as the `rawValue`\n- `value` - it's either `formattedValue` or `rawValue`. If the column has a valueFormatter, it's the value returned by the formatter, otherwise it's the `rawValue`\n\nSee related .\n\n\n\nThis function should not be called during a cell render (eg: in / or other functions called during render).\n\n\n\n\n\n InfiniteTableColumnAPI\">\n\n> Returns [a column API object](/docs/reference/column-api) bound to the specified column\n\nThe parameter can be either a column id or a column index (note this is not the index in all columns, but rather the index in current visible columns).\n\n\n\n { renderStartIndex, renderEndIndex }\">\n\n> Returns the vertical render range of the table\n\nThe vertical render range is the range of rows that are currently rendered in the table viewport.\n\n\n\n void\">\n\n> Called when the table has been layed out and sized and is ready to be used.\n\nThis callback prop will be called with an object containing the `api` (which is an instance of `InfiniteTableApi`) and [`dataSourceApi`](/docs/reference/datasource-api) objects.\n\n\n\n Promise\">\n\n> Tries to start editing the cell specified by the given row index and column id.\n\nReturns a promise that resolves to `true` if editing was started, or `false` if editing was not started because the cell is not editable.\n\nSee for more details on how to configure a cell as editable.\n\n\n\n```ts file=\"api-start-edit-example.page.tsx\"\n\n```\n\n\n\n\n\n boolean\">\n\n> Can be used to scroll a cell into the visible viewport\n\nIf scrolling was successful and the row and column combination was found, it returns `true`, otherwise `false`. The first arg of the function is the row index, while the second one is the column id or the column index (note this is not the index in all columns, but rather the index in current visible columns).\n\n\n\n boolean\">\n\n> Can be used to scroll a column into the visible viewport\n\nIf scrolling was successful and the column was found, it returns `true`, otherwise `false`.\nThe only parameter of this method is the column id.\n\n\n\n|setter\">\n\n> Gets or sets the `scrollLeft` value in the grid viewport\n\nCan be used as either a setter, to set the scroll left position or a getter to read the scroll left position.\n\n```ts\n// use as setter - will scroll the table viewport\napi.scrollLeft = 200;\n\n// use as getter to read the current scroll left value\nconst scrollLeft = api.scrollLeft;\n```\n\n\n\n boolean\">\n\n> Can be used to scroll a row into the visible viewport\n\nIf scrolling was successful and the row was found, it returns `true`, otherwise `false`\n\n\n\n|setter\">\n\n> Gets or sets the `scrollTop` value in the grid viewport\n\nCan be used as either a setter, to set the scroll top position or a getter to read the scroll top position.\n\n```ts\n// use as setter - will scroll the table viewport\napi.scrollTop = 1200;\n\n// use as getter to read the current scroll top value\nconst scrollTop = api.scrollTop;\n```\n\n\n\n\n\n> Getter for the [Row Selection API](/docs/reference/row-selection-api)\n\n\n\n\n\n> Getter for the [Row Detail API](/docs/reference/row-detail-api)\n\n\n\n\n> Getter for the [Cell Selection API](/docs/reference/cell-selection-api)\n\n\n\nvoid\">\n\n> Sets a filter value for the specified column\n\n\n\n void\">\n\n> Set the column order.\n\nIf `true` is specified, it resets the column order to the order the columns are specified in the prop (the iteration order of that object).\n\n```ts\napi.setColumnOrder(['id', 'firstName', 'age']);\n// restore default order\napi.setColumnOrder(true);\n```\n\n\n\n void\">\n\n> Toggles the collapse/expand state of the specified group row\n\n```tsx\napi.toggleGroupRow(['USA', 'New York']); // toggle the group with these keys\n```\n\n\n\n\n" }, { "filePath": "/docs/reference/cell-selection-api/index", @@ -1094,34 +1094,18 @@ "content": "\nWhen rendering the `DataSource` component, you can get access to the API by getting it from the callback prop.\n\n```tsx {3}\n\n onReady={(api: DataSourceApi) => {\n // api is accessible here\n // you may want to store a reference to it in a ref or somewhere in your app state\n }}\n/>\n```\n\nYou can also get it from the `InfiniteTable` callback prop:\n\n```tsx {4}\n\n columns={[...]}\n onReady={(\n {api, dataSourceApi}: {\n api: InfiniteTableApi,\n dataSourceApi: DataSourceApi\n }) => {\n\n // both api and dataSourceApi are accessible here\n }}\n/>\n```\n\nFor API on row/group selection, see the [Selection API page](/docs/reference/selection-api).\n\n\n\n boolean\">\n\n> Returns `true` if the row at the specified index is disabled, `false` otherwise.\n\nSee the prop for more information.\n\nFor checking if a row is disabled by its primary key, see the method.\n\nFor changing the enable/disable state for the row, see the .\n\n\n\n```ts file=\"../datasource-props/rowDisabledState-example.page.tsx\"\n```\n\n\n\n\n\n boolean\">\n\n> Returns `true` if the row with the specified primary key is disabled, `false` otherwise.\n\nSee the prop for more information.\n\nFor checking if a row is disabled by its index, see the method.\n\nFor changing the enable/disable state for the row, see the .\n\n\n\n```ts file=\"../datasource-props/rowDisabledState-example.page.tsx\"\n\n```\n\n\n\n\n\n void\">\n\n> Sets the enable/disable state for the row with the specified primary key.\n\nSee the prop for more information.\n\nFor setting the enable/disable state for a row by its index, see the method.\n\n\n\n```ts file=\"../datasource-props/rowDisabledState-example.page.tsx\"\n\n```\n\n\n\n\n\n\">\n\n> A reference to the [Tree API](/docs/reference/tree-api).\n\nWhen using the `` component, this property will be available on the `DataSourceApi` instance.\n\n\n void\">\n\n> Sets the enable/disable state for the row at the specified index.\n\nSee the prop for more information.\n\nFor setting the enable/disable state for a row by its primary key, see the method.\n\n\n\n```ts file=\"../datasource-props/rowDisabledState-example.page.tsx\"\n\n```\n\n\n\n\n\n Promise\">\n\n> Replaces all data in the DataSource with the provided data.\n\nClears the current data array in the `` and replaces it with the provided data.\n\nWhen calling this, if there are pending data mutations, they will be discarded.\n\nSee related method.\n\n\n\n Promise\">\n\n> Clears all data in the DataSource.\n\nSee related method.\n\n\n\n Promise\">\n\n> Adds the specified data at the end of the data source.\n\nThe given data param should be of type `DATA_TYPE` (the TypeScript generic data type that the `DataSource` was bound to).\n\nFor adding an array of data, see the method.\n\n\n\nIf the component has sorting, the added data might not be displayed at the end of the data source.\n\n\n\nThis method batches data updates and waits for a request animation frame until it persists the data to the `DataSource`. This means you can execute multiple calls to `addData` (or , , ) in the same frame and they will be batched and persisted together.\n\nThe return value is a `Promise` that resolves when the data has been added. When multiple `addData` (and friends) calls are executed in the same frame, the result of those calls is a reference to the same promise.\n\n```ts\nconst promise1 = dataSourceApi.add({ ... })\nconst promise2 = dataSourceApi.add({ ... })\nconst promise3 = dataSourceApi.insertData({ ... }, { position: 'before', primaryKey: 4 })\n\n// promise1, promise2 and promise3 are the same promise\n// as the calls are run in the same raf and batched together\n// promise1 === promise2\n// promise1 === promise3\n```\n\nFor adding an array of data, see the method.\nFor inserting data at a specific position, see the method.\n\n allows you to listen to data mutations.\n\n\n\n```ts file=\"addData-example.page.tsx\"\n\n```\n\n\n\n\n\n Promise\">\n\n> Adds an array of data at the end of the data source\n\nSee related method.\n\nFor adding at the beginning of the data source, see the method.\n\n allows you to listen to data mutations.\n\n\n\n DATA_TYPE | null\">\n\n> Retrieves the data object for the specified node path.\n\n\n\n```tsx file=\"tree-getDataByNodePath-example.page.tsx\"\n```\n\n\n\n\n\n DATA_TYPE | null\">\n\n> Retrieves the data object for the specified primary key.\n\nYou can call this method to retrieve objects from the data source even when they have been filtered out via or , as long as they are present in the initial data.\n\n\n\nThe alternative API method can only be used to retrieve row info objects of rows that are not filtered out - so only rows that match the filtering, if one is present.\n\n\n\n\n\n number\">\n> Retrieves the index of a row by its primary key. If the row is not found, returns `-1`. See related \n\n\n\nThe primary key you pass in needs to exist in the current data set. If you pass in a primary key that has been filtered out or that's not in the data set, the method will return `-1`.\n\n\n\n\n\n any | undefined \">\n\n> Retrieves the primary key of a row by its current index. If the row is not found, returns `undefined`. See related \n\n\n\nThe index needs to be of an existing row, after all filtering is applied. If you pass in an non-existent index, the method will return `undefined`.\n\n\n\n\n\n\n DATA_TYPE | null\">\n\n> Retrieves the data object for the node with the specified path. If the node is not found, returns `null`. See related . See related .\n\n\n\nThe node path needs to be of an existing node. If you pass in a non-existent (or filtered out) node path, the method will return `null`.\n\n\n\n\n\n```tsx file=\"tree-getDataByNodePath-example.page.tsx\"\n```\n\n\n\n\n\n\n InfiniteTableRowInfo | null\">\n\n> Retrieves the row info object for the node with the specified path. If the node is not found, returns `null`. See related .\n\n\n\nThe node path needs to be of an existing node. If you pass in a non-existent (or filtered out) node path, the method will return `null`.\n\n\n\n\n\n\n InfiniteTableRowInfo | null\">\n\n> Retrieves the row info object for the row at the specified index. If none found, returns `null`. See related .\n\n\n\n InfiniteTableRowInfo | null\">\n\n> Retrieves the row info object for the row with the specified primary key. If none found, returns `null`.\n\nThis method will only find row info objects for rows that are currently in the dataset and matching the filtering, if one is present. Can also be called for group rows.\n\n\n\nSee related method, which retrieves the raw data object for the specified primary key, even if it has been filtered out.\n\n\n\n\n\n InfiniteTableRowInfo[]\">\n\n> Returns the current row info array. See the type definition of the row info object.\n\nThe row info array represents the current state of the DataSource. This array may contain more items than the actual data array fetched initially by the DataSource. This is because it includes group rows, when grouping is defined, as well as unfetched rows in some advanced scenarios.\n\n\n\n Promise\">\n\n> Inserts the given data at the specified position relative to the given primary key.\n\nThe `position` can be one of the following:\n\n- `start` | `end` - inserts the data at the beginning or end of the data source. In this case, no `primaryKey` is needed.\n- `before` | `after` - inserts the data before or after the data item that has the specified primary key. **In thise case, the `primaryKey` is required.**\n\nWe're intentionally not encouraging inserting at a specified `index`, as the index of rows in the visible viewport can change as the user sorts, filters or groups the data.\n\nFor inserting an array of data, see the method.\n\n allows you to listen to data mutations.\n\n\n\n\n\nClick any row in the table to make it the current active row, and then use the second button to add a new row after the active row.\n\n\n\n```ts file=\"insert-example.page.tsx\"\n\n```\n\n\n\n\n\n Promise\">\n\n> Inserts an array of data at the specified position (and relative to the given primary key or node path).\n\nJust like the method, the `position` can be one of the following:\n\n- `start` | `end` - inserts the data at the beginning or end of the data source. In this case, no `primaryKey` is needed. If `nodePath` is provided, `\"start\"` means insert the data at the start of the children array of that node; `\"end\"` means insert the data at the end of the children array of that node.\n- `before` | `after` - inserts the data before or after the data item that has the specified primary key / node path. **In thise case, the `primaryKey` or the `nodePath` is required.**\n\nAll the data items passed to this method will be inserted (in the order in the array) at the specified position.\n\n\n\nWhen using this method for tree nodes, `waitForNode` defaults to `true` (you can specify a boolean value, or a number to override the default timeout). When `waitForNode` is `true`, the method will insert the data in the respective node's children array, after making sure the node exists.\n\n\n\n allows you to listen to data mutations.\n\n\n\n Promise\">\n\n> Updates the data for the nodes specified by the node paths.\n\nThe first parameter should be an array of objects, where each object has a `data` property (the data to update) and a `nodePath` property (the path of the node to update).\n\n\n```tsx title=\"Updating an aray of tree nodes\"\ndataSourceApi.updateDataArrayByNodePath([\n {\n data: {\n fileName: 'Vacation.pdf',\n sizeInKB: 1000,\n },\n nodePath: ['1', '10'],\n },\n {\n data: {\n fileName: 'Report.docx',\n sizeInKB: 2000,\n },\n nodePath: ['1', '11'],\n },\n]);\n```\n\n\n\n\n DATA_TYPE[] | any, nodePath: NodePath) => Promise, options?\">\n\n> Updates the children of the node specified by the node path.\n\nThe first parameter can be an array (or `null` or `undefined`) or a function that returns an array (or `null` or `undefined`).\n\nThe second parameter is the node path.\n\nWhen a function is passed as the first parameter, it will be called with the children of the node. The return value will be used as the new children of the node. This gives you an opportunity to update the children based on the current children state.\n\n```tsx title=\"Updating the children of a tree node\"\ndataSourceApi.updateChildrenByNodePath(\n (currentChildren) => {\n return [\n ...(currentChildren || []),\n {\n name: 'untitled.txt',\n id: '8',\n },\n ];\n },\n ['1', '3'],\n);\n```\n\n\n\nWhen using a function, it will be called with the current children of the node (1st parameter) and also with the node data object (2nd parameter).\n\n\n\n\n\nAs a third parameter, you can pass in an options object, which supports the `waitForNode` property (`boolean` or `number` to override the default timeout). When `waitForNode` is `true`, the method will update the children of the node, after making sure the node exists (and waiting for the specified timeout if needed).\n\n\n\n\n\n Promise\">\n\n> Returns a promise that tells if the node path exists.\n\nCalling this method will give you a promise that will tell you if the node path exists or not.\n\nIf the `DataSource` can find the node path either immediately or before the specified timeout expires, the promise will resolve to `true`, otherwise it will resolve to `false`.\n\nIf no `timeout` is specified, it will default to `1000`ms.\n\n\n\n, nodePath: NodePath, options?) => Promise\">\n\n> Updates the data for the node specified by the node path.\n\n\n\nIf the primary keys in your `` are unique globally (not just within the same node), you can still use the method.\n\n\n\n\n```tsx title=\"Updating a tree node by path\"\ndataSourceApi.updateDataByNodePath({\n fileName: 'New Name',\n sizeInKB: 1000,\n}, ['1', '10']);\n```\n\n\n\n```ts file=\"tree-updateDataByPath-example.page.tsx\"\n```\n\n\n\n\n\nThe third parameter is an options object, which can have a `waitForNode` property (either `boolean` or `number` - use a `number` to override the default timeout). NOTE: if you don't pass it, it defaults to `1000ms`.\n\nWhen `waitForNode` is used, if the node does not exist yet, it will wait for the specified timeout and then update the data.\n\n\n\n\n\n Promise\">\n\n> Removes the node specified by the specified node path.\n\n\n\nIf the primary keys in your `` are unique globally (not just within the same node), you can still use the method.\n\n\n\n\n```tsx title=\"Removing a tree node by path\"\ndataSourceApi.removeDataByNodePath(['1', '10']);\n```\n\n\n\n```ts file=\"tree-removeDataByNodePath-example.page.tsx\"\n```\n\n\n\n\n\n, options?) => Promise\">\n\n> Updates the data item to match the given data object. For updating tree nodes by path, see the method.\n\n\n\nThe data object must have a primary key that matches the primary key of the data item that you want to update. Besides the primary key, it can contain any number of properties that you want to update.\n\n\n\n```ts\ndataSourceApi.updateData({\n // if the primaryKey is the id, make sure to include it\n id: 1,\n\n // and then include any properties you want to update - in this case, the name and age\n name: 'John Doe',\n age: 30,\n});\n```\n\n\n\n```ts file=\"simple-updateData-example.page.tsx\"\n```\n\n\n\nFor updating an array of data, see the method.\n\n\n\nThe second parameter is an options object, which can have a `waitForNode` property (either `boolean` or `number` - use a `number` to override the default timeout). NOTE: if you don't pass it, it defaults to `1000ms`.\n\nWhen `waitForNode` is used, if the node does not exist yet, it will wait for the specified timeout and then update the data.\n\n\n\n allows you to listen to data mutations.\n\n\n\n\n\nThe DataSource has 10k items.\n\nIn this example, we're updating 5 rows (in the visible viewport) every 30ms.\n\nThe update rate could be much higher, but we're keeping it at current levels to make it easier to see the changes.\n\n\n\n```ts file=\"live-updates-example.page.tsx\"\n\n```\n\n\n\n\n\n[], options?) => Promise\">\n\n> Updates an array of data items to match the given data objects.\n\nSee related method.\n\n allows you to listen to data mutations.\n\n\n\n void\">\n\n> Called only once, after the DataSource component has been mounted.\n\nThis callback prop will be called with an `DataSourceApi` instance. For retrieving the [`InfiniteTableApi`](/docs/reference/api), see the `InfiniteTable` callback prop.\n\n\n\n) => Promise\">\n\n> Removes the data item that matches the given data object.\n\nThe data object must at least have a primary key that matches the primary key of the data item that you want to remove. All the other properties are ignored.\n\nFor removing an array of data, see the method.\n\nIf you only want to remove by a primary key, you can call instead.\nIf you have an array of primary keys, you can call instead.\n\n allows you to listen to data mutations.\n\n\n\n DATA_TYPE[]\">\n\n> Returns the data array that was last loaded by the `DataSource`\n\nThis is the array loaded by the `DataSource`, before any filtering, sorting or grouping is applied.\n\n\n\n[]) => Promise\">\n\n> Removes the data items that match the given data objects.\n\nThe data objects must at least have a primary key that matches the primary key of the data item that you want to remove. All the other properties are ignored.\n\nFor removing only one item, see the method.\n\nIf you only want to remove by a primary key, you can call instead.\nIf you have an array of primary keys, you can call instead.\n\n allows you to listen to data mutations.\n\n\n\n Promise\">\n\n> Removes the data items with the specified primary keys.\n\nFor removing only one data item, see the method.\n\nIf you have a data object, you can call instead.\nIf you have an array of data objects, you can call instead.\n\n allows you to listen to data mutations.\n\n\n\n Promise\">\n\n> Removes the data item with the specified primary key.\n\nFor removing an array of data, see the method.\n\nIf you have a data object, you can call instead.\nIf you have an array of data objects, you can call instead.\n\n allows you to listen to data mutations.\n\n\n\n" }, { - "filePath": "/docs/reference/datasource-props/index", - "routePath": "/docs/reference/datasource-props/", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/reference/datasource-props/index.page.md", + "filePath": "/docs/reference/keyboard-navigation-api/index", + "routePath": "/docs/reference/keyboard-navigation-api/", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/reference/keyboard-navigation-api/index.page.md", "fileName": "index.page.md", - "folderPath": "/docs/reference/datasource-props/", + "folderPath": "/docs/reference/keyboard-navigation-api/", "frontmatter": { - "title": "DataSource Props", - "layout": "API", - "description": "Props Reference page for your DataSource in Infinite Table - with complete examples" + "title": "Infinite Table Keyboard Navigation API", + "layout": "API" }, - "excerpt": "In the API Reference below we'll use **`DATA_TYPE`** to refer to the TypeScript type that represents the data the component is bound to.", - "readingTime": "39 min read", - "content": "\nIn the API Reference below we'll use **`DATA_TYPE`** to refer to the TypeScript type that represents the data the component is bound to.\n\n\n\n string\">\n\n> The name of the id/primary key property of an item in the array. The value of this property needs to be unique.\n\nThis is probably one of the most important properties of the `` component, as it is used to identify items in the array.\n\n\n\nUnlike with other DataGrid components, with `InfiniteTable` you don't need to have a column mapped to the primary key field.\n\nThe primary key is used internally by the component and is not displayed in the grid if you don't explicitly have a column bound to that field.\n\n\n\nIf the primary key is not unique, Infinite Table DataGrid won't work properly.\n\n\n\n```ts file=\"data-example.page.tsx\"\n\n```\n\n\n\n\n\nThe primary key can be either a string (the name of a property in the data object), or a function that returns a string.\n\nUsing functions (for more dynamic primary keys) is supported, but hasn't been tested extensively - so please report any issues you might encounter.\n\n\n\n\n\n\n\n> Specifies the expand/collapse state of the tree nodes. See for the shape of this object. For the uncontrolled version, see .\n\nIf no `treeExpandState` prop is specified, the tree will be rendered as fully expanded by default.\n\nWhen using the controlled version, make sure to update the `treeExpandState` prop by using the callback.\n\n\n\n```ts file=\"tree-controlled-expandstate-example.page.tsx\"\n\n```\n\n\n\n\n\n void\">\n\n> Called when the tree expand state changes.\n\nWhen the user interacts with the tree (by expanding or collapsing a node), this callback is called with the new tree state.\n\nThe first parameter is the new tree state, and the second parameter is an object with the following properties:\n\n- `dataSourceApi` - the DataSource API instance\n- `nodePath` - the path of the node that changed state. If the state was produced by an or call, this will be `null`.\n- `nodeState` - the new state of the node (`\"collapsed\"` or `\"expanded\"`)\n\n\n\n\n```ts file=\"tree-controlled-expandstate-example.page.tsx\"\n\n```\n\n\n\n\n\n void\">\n\n> Called when a node is expanded. See related and props.\n\n\n\n\nThe and callbacks are called when a node is expanded or collapsed, respectively - either via user interaction or by an API call. However, they will not be called when the or methods are called.\n\n\n\n\n\n, treeExpandState: TreeExpandState) => boolean\">\n\n> Decides if the current (non-leaf) node is expanded. \n\nThe inverse prop, is also available. Only one of these props can be specified.\n\n\n\nIf this prop is specified, is ignored.\n\n\n\n\n\n, treeExpandState: TreeExpandState) => boolean\">\n\n> Decides if the current (non-leaf) node is collapsed. See related prop.\n\nThe inverse prop, is also available. Only one of these props can be specified.\n\n\n\nIf this prop is specified, is ignored.\n\n\n\n\n\n) => boolean\">\n\n> Decides if the current (non-leaf) node can be expanded or collapsed and if the tree icon is disabled.\n\nBy default, parent nodes with `children: []` are read-only, meaning they won't respond to expand/collapse clicks.\n\nHowever, if you specify a custom `isNodeReadOnly` function, you can change this behavior.\n\n\n\nWhen a node is read-only, the and methods need the `options.force` flag to be set to `true` in order to override the read-only restriction.\n\nHowever, and will work regardless of the `isNodeReadOnly` setting.\n\nFor full control over the expand/collapse state of read-only nodes, you can use the / props.\n\n\n\n\n\n```ts file=\"tree-isNodeReadOnly-example.page.tsx\"\n\n```\n\n\n\n\n void\">\n\n> Called when a node is collapsed. See related and props.\n\n\n\n\nThe and callbacks are called when a node is expanded or collapsed, respectively - either via user interaction or by an API call. However, they will not be called when the or methods are called.\n\n\n\n\n\n\n\n> The name of the property in the data object that contains the child nodes for each tree node.\n\nOnly available when you're using the `` component.\n\nIf not specified, it defaults to `\"children\"`.\n\n\n\nEach node gets a `nodePath` property, which is the array with the ids of all the parent nodes leading down to the current node. The node path includes the id of the current node\n\n\n\n\n```tsx {2} title=\"Node path vs row id\"\nconst data = [\n { id: '1', name: 'Documents', // path: ['1']\n children: [\n { id: '10', name: 'Private', // path: ['1', '10']\n children: [\n { id: '100', name: 'Report.docx' }, // path: ['1', '10', '100'] \n { id: '101', name: 'Vacation.docx' },// path: ['1', '10', '101']\n ],\n },\n ]\n },\n {\n id: '2',\n name: 'Downloads', // path: ['2']\n children: [\n {\n id: '20',\n name: 'cat.jpg', // path: ['2', '20']\n },\n ],\n },\n];\n```\n\n\n\n```ts file=\"tree-nodesKey-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n> Specifies the expand/collapse state of the tree nodes. See for the shape of this object. For the controlled version, see .\n\n\n\n\n\n```ts file=\"tree-uncontrolled-expandstate-example.page.tsx\"\n\n```\n\n\n\n\n\n\n) => boolean\">\n\n> This function ultimately decides the disabled state of a row. It overrides both / props.\n\nIt's called with a single argument - the row info object for the row in question.\n\nIt should return `true` if the row is disabled, and `false` otherwise.\n\n\n\nWhen this prop is used, will not be called.\n\n\n\n\n\n\n\n\n> The uncontrolled prop for managing row enabled/disabled state. For the controlled version see . For listening to row disabled state changes, see .\n\nThe value for this prop is an object with two properties:\n\n- `enabledRows` - either `true` or an array of row ids that are enabled. When `true` is passed, `disabledRows` should be an array of row ids that are disabled.\n- `disabledRows` - either `true` or an array of row ids that are disabled. When `true` is passed, `enabledRows` should be an array of row ids that are enabled.\n\n\n\nThe values in the `enabledRows`/`disabledRows` arrays are row ids, and not indexes.\n\n\n\n\n\n\nThis prop can be overriden by using the prop.\n\n\n\nHere's an example of how to use the `defaultRowDisabledState` prop:\n\n\n\n\n\nRows with ids `1`, `3`, `4` and `5` are disabled.\n\n\n\n```ts file=\"defaultRowDisabledState-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n> Manages row enabled/disabled state. For the uncontrolled version see . For listening to row disabled state changes, see .\n\nThe value for this prop is an object with two properties:\n\n- `enabledRows` - either `true` or an array of row ids that are enabled. When `true` is passed, `disabledRows` should be an array of row ids that are disabled.\n- `disabledRows` - either `true` or an array of row ids that are disabled. When `true` is passed, `enabledRows` should be an array of row ids that are enabled.\n\n\nWhen using this controlled prop, you will need to update the `rowDisabledState` prop by using the callback.\n\n\n\n\nThis prop can be overriden by using the prop.\n\n\n\n\n\n\n\nRows with ids `1`, `3`, `4` and `5` are disabled initially.\n\nRight click rows and use the context menu to enable/disable rows.\n\n\n\n```ts file=\"rowDisabledState-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n> Determines what nodes are selected and deselected. For the uncontrolled version see .\n\nThe value of this prop determines if a node is selected or not.\n\nSee for details on the shape of this object.\n\n\n\n\n```ts file=\"tree-controlled-selectedstate-example.page.tsx\"\n\n```\n\n\n\n\n\n void\">\n\n> Called when the tree selection changes. See .\n\n\n\n```ts file=\"tree-controlled-selectedstate-example.page.tsx\"\n\n```\n\n\n\nWhen using `multi-row` , the signature of this callback is:\n\n - `treeSelection` - the new tree selection state\n - `context` - an object with the following properties:\n - `selectionMode` - will be `\"multi-row\"`\n - `lastUpdatedNodePath` - the path of the node that was last updated (either via user action or api call). Will be `null` of the action that triggered this callback was or .\n - `dataSourceApi` - the [DataSource API](/docs/reference/datasource-api) instance\n\n\n\n\n\n> Determines what nodes are selected and deselected. For the controlled version see .\n\nThe value of this prop determines if a node is selected or not.\n\nSee for details on the shape of this object.\n\n\n\n\n```ts file=\"tree-uncontrolled-selectedstate-example.page.tsx\"\n\n```\n\n\n\n\n\n void\">\n\n> Called when the row disabled state changes.\n\nIt's called with just 1 argument (`rowDisabledState`), which is an instance of the `RowDisabledState` class. To get a literal object that represents the row disabled state, call the `rowDisabledState.getState()` method.\n\n```tsx {3,19}\nimport {\n DataSource,\n RowDisabledStateObject,\n} from '@infinite-table/infinite-react';\nfunction App() {\n const [rowDisabledState, setRowDisabledState] = React.useState<\n RowDisabledStateObject\n >({\n enabledRows: true,\n disabledRows: [1, 3, 4, 5],\n });\n return (\n <>\n \n data={data}\n primaryKey=\"id\"\n rowDisabledState={rowDisabledState}\n onRowDisabledStateChange={(rowState) => {\n setRowDisabledState(rowState.getState());\n }}\n />\n \n );\n}\n```\n\n\nWhen using the controlled prop, you will need to update the `rowDisabledState` by using this callback.\n\n\n\n\n\n\nRows with ids `1`, `3`, `4` and `5` are disabled initially.\n\nRight click rows and use the context menu to enable/disable rows.\n\n\n\n```ts file=\"rowDisabledState-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n\">\n\n> Specifies the functions to use for aggregating data. The object is a map where the keys are ids for aggregations and values are object of the shape described below.\n\nThe `DataSourceAggregationReducer` type can have the following properties\n\n- `initialValue` - type `any`, mandatory for client-side aggregations. It can be a function, in which case, it will be called to compute the initial value for the aggregation. Otherwise, the initial value will be used as is.\n- `field` - the field to aggregate on. Optional - if not specified, make sure you specify `getter`\n- `getter`: `(data:T)=> any` - a getter function, called with the current `data` object.\n- `reducer`: `string | (accumulator, value, data: T) => any` - either a string (for server-side aggregations) or a mandatory aggregation function for client-side aggregations.\n- `done`: `(accumulator, arr: T[]) => any` - a function that is called to finish the aggregation after all values have been accumulated. The function should return the final value of the aggregation. Only used for client-side aggregations.\n- `name` - useful especially in combination with , as it will be used as the pivot column header.\n- `pivotColumn` - if specified, will configure the pivot column generated for this aggregation. This object has the same shape as a normal column, but supports an extra `inheritFromColumn` property, which can either be a `string` (a column id), or a `boolean`. The default behavior for a pivot column is to inherit the configuration of the initial column that has the same `field` property. `inheritFromColumn` allows you to specify another column to inherit from, or, if `false` is passed, the pivot column will not inherit from any other column.\n\n\n\n```ts files=[\"groupBy-example.page.tsx\",\"columns.ts\"]\n\n```\n\n\n\nAggregation reducers can be used in combination with grouping and pivoting. The example below shows aggregations used with server-side pivoting\n\n\n\n```ts file=\"$DOCS/learn/grouping-and-pivoting/pivoting/remote-pivoting-example.page.tsx\"\n\n```\n\n\n\nPivot columns generated for aggregations will inehrit from initial columns - the example shows how to leverage this behavior and how to extend it\n\n\n\n```ts file=\"$DOCS/learn/grouping-and-pivoting/pivoting/pivot-column-inherit-example.page.tsx\"\n\n```\n\n\n\n\n\n DATA_TYPE[]|Promise\">\n\n> Specifies the data the component is bound to.\n\nCan be one of the following:\n\n- an array of the bound type - eg: `Employee[]`\n- a Promise tha resolves to an array like the above\n- a function that returns an any of the above\n\n\n\nIf the `data` prop is a function, it will be called with an object of type . Click to see more details.\n\n\n\n\n\n```ts file=\"data-example.page.tsx\"\n\n```\n\n\n\n\n\nIt's important to note you can re-fetch data by changing the reference you pass as the `data` prop to the `` component. Passing another `data` function, will cause the component to re-execute the function and thus load new data.\n\n\n\n\n\n```ts files=[\"$DOCS/learn/working-with-data/refetch-example.page.tsx\",\"$DOCS/learn/working-with-data/columns.ts\"]\n\n```\n\n\n\n\n\n\n\n> Uncontrolled prop used for filtering. Can be used for both [client-side](/docs/learn/filtering/filtering-client-side) and [server-side](/docs/learn/filtering/filtering-server-side) filtering.\n\nIf you want to show the column filter editors, you have to either specify this property, or the controlled - even if you have no initial filters. For no initial filters, use `defaultFilterValue=[]`.\n\nFor the controlled version, and more details on the shape of the objects in the array, see .\n\n\n\n```ts file=\"defaultFilterValue-example.page.tsx\"\n\n```\n\n\n\n\n\nYou can control the visibility of the column filters by using the prop.\n\n\n\n\n\n\n\n> Describes the selected row(s) in the `DataSource`\n\nSee more docs in the controlled version of this prop, \n\nFor single selection, the prop will be of type: `number | string | null`. Use `null` for empty selection in single selection mode.\n\nFor multiple selection, the prop will have the following shape:\n\n```ts\nconst rowSelection = {\n selectedRows: [3, 6, 100, 23], // those specific rows are selected\n defaultSelection: false, // all rows deselected by default\n};\n\n// or\nconst rowSelection = {\n deselectedRows: [3, 6, 100, 23], // those specific rows are deselected\n defaultSelection: true, // all other rows are selected\n};\n\n// or, for grouped data - this example assumes groupBy=continent,country,city\nconst rowSelection = {\n selectedRows: [\n 45, // row with id 45 is selected, no matter the group\n ['Europe', 'France'], // all rows in Europe/France are selected\n ['Asia'], // all rows in Asia are selected\n ],\n deselectedRows: [\n ['Europe', 'France', 'Paris'], // all rows in Paris are deselected\n ],\n defaultSelection: false, // all other rows are selected\n};\n```\n\nFor using group keys in the selection value, see related \n\n\n\n```ts file=\"$DOCS/reference/uncontrolled-multiple-row-selection-example.page.tsx\"\n\n```\n\n\n\n\n\n|DataSourceSingleSortInfo[]|null\">\n\n> Information for sorting the data. This is an uncontrolled prop.\n\nFor detailed explanations, see (controlled property).\n\n\n\nWhen you provide a `defaultSortInfo` prop and the sorting information uses a custom sortType, make sure you specify that as the `type` property of the sorting info object.\n\n```tsx\ndefaultSortInfo={{\n field: 'color',\n dir: 1,\n // note this custom sort type\n type: 'color',\n}}\n```\n\nYou will need to have a property for that type in your object as well.\n\n```tsx\nsortTypes={{\n color: (a, b) => //...\n}}\n```\n\n\n\n\n\n```ts file=\"$DOCS/learn/sorting/local-uncontrolled-single-sorting-example-with-remote-data.page.tsx\"\n\n```\n\n\n\n\n\n```ts file=\"./customSortType-with-uncontrolled-sortInfo-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n> The delay in milliseconds before the filter is applied. This is useful when you want to wait for the user to finish typing before applying the filter.\n\nThis is especially useful in order to reduce the number of requests sent to the server, when remote filtering is used.\n\nIf not specified, defaults to `200` milliseconds. This means, any changes to the column filters, that happen inside a 200ms window (or the current value of ), will be debounced and only the last value will be sent to the server.\n\n\n\nIf you want to prevent debouncing/batching filter values, you can set to `0`.\n\n\n\n\n\n\n\n> The delay in milliseconds to wait before mutations are applied. This is useful to batch multiple mutations together.\n\nIf not specified, a `requestAnimationFrame` will be used to batch mutations.\n\nThe following mutative operations are batched:\n \n - \n - \n - \n - \n - \n - \n - \n - \n - \n - \n - \n - \n\n\n\n DATA_TYPE | boolean\">\n\n> A function to be used for filtering a `TreeDataSource`.\n\nThe function should return a boolean value or a data object.\n\n- when returning `false` the current data object will be filtered out.\n- when returning `true`, the current data object will be included in the filtered data, with no changes.\n- when returning a data object, the object will be used instead of the current data object for the row. This means that you can modify the data object to only include some of its children (which match a specific criteria)\n\n\n\nThe `treeFilterFunction` is called with an object that has a `filterTreeNode` function property. This function is a helper function you can use to continue the filtering further down the tree on the current (non-leaf) node.\n\nThis function will call the filtering function for each child of the current node. If all the children are filtered out, the current node will be filtered out as well. If there are any children that match the criteria, a clone of the current node will be returned with only the matching children.\n\nYou can opt to not use this helper function, and instead implement your own filtering logic. In this case, make sure you don't mutate data objects but rather return cloned versions of them.\n\n\n\n\n\n```ts file=tree-filter-function-example.page.tsx\n\n```\n\n\n\n\n\n boolean\">\n\n> A function to be used for client-side filtering.\n\nUsing this function will not show any special filtering UI for columns.\n\n\n\nFor filtering when using a `TreeGrid`, see .\n\n\n\n\n\n\n\nLoads data from remote location but will only show rows that have `id > 100`.\n\n\n\n```ts file=\"custom-filter-function-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n> Explicitly configures where filtering will take place and if changes in the should trigger a reload of the data source - applicable when is a function.\n Replaces the deprecated \n\n- `false` (the default) - filtering will be done on the client side and the function will not be invoked again.\n- `true` - filtering will be done on the server side - the function will be called with an object that includes the `filterValue` property, so it can be sent to the server\n\n\n\n\n\n> Explicitly configures where filtering will take place. Update to use the prop.\n\n- `'local'` - filtering will be done on the client side\n- `'remote'` - filtering will be done on the server side - the function will be called with an object that includes the `filterValue` property, so it can be sent to the server\n\n\n\n\">\n\n> Specifies the available types of filters for the columns.\n\nA filter type is a concept that defines how a certain type of data is to be filtered.\nA filter type will have a key, used to define the filter in the `filterTypes` object, and also the following properties:\n\n- `label`\n- `emptyValues` - an array of values considered to be empty values - when any of these values is used in the filter, the filter will match all records.\n- `operators` - an array of operator this filter type supports\n- `defaultOperator` - the default operator for the filter type\n- `components` - an object that describes the custom components to be used for the filter type\n - `FilterEditor` - a custom filter editor component for this filter type\n - `FilterOperatorSwitch` - a custom component that is displayed at the left of the `FilterEditor` and can be used for switching between operators - only needed for very very advanced use-cases.\n\nLet's imagine you have a `DataSource` with developers, each with a `salary` column, and for that column you want to allow `>`, `>=`, `<` and `<=` comparisons (operators).\n\nFor this, you would define the following filter type:\n\n```tsx\nconst filterTypes = {\n income: {\n label: 'Income',\n emptyValues: ['', null, undefined],\n defaultOperator: 'gt',\n operators: [\n {\n name: 'gt',\n label: 'Greater than',\n fn: ({ currentValue, filterValue }) => {\n return currentValue > filterValue;\n },\n },\n {\n name: 'gte',\n //...\n },\n {\n name: 'lt',\n //...\n },\n {\n name: 'lte',\n //...\n },\n ],\n },\n};\n```\n\n\n\nEach operator for a certain filter type needs to at least have a `name` and `fn` defined. The `fn` property is a function that will be called when client-side filtering is enabled, with an object that has the following properties:\n\n- `currentValue` - the cell value of the current row for the column being filtered\n- `filterValue` - the value of the filter editor\n- `emptyValues` - the array of values considered to be empty values for the filter type\n- `data` - the current row data object - `typeof DATA_TYPE`\n- `index` - the index of the current row in the table - `number`\n- `dataArray` - the array of all rows originally in the table - `typeof DATA_TYPE[]`\n- `field?` - the field the current column is bound to (can be undefined if the column is not bound to a field)\n\n\n\n\n\n\n\nThe `salary` column has a custom filter type, with the following operators: `gt`, `gte`, `lt` and `lte`.\n\n\n\n```ts file=\"filter-types-example.page.tsx\"\n\n```\n\n\n\n\n\nBy default, the `string` and `number` filter types are available. You can import the default filter types like this:\n\n```ts\nimport { defaultFilterTypes } from '@infinite-table/infinite-react';\n```\n\nIf you want to make all your instances of `InfiniteTable` have new operators for those filter types, you can simply mutate the exported `defaultFilterTypes` object.\n\n\n\n\n\nThe `string` columns have a new `Not includes` operator.\n\n\n\n```ts file=\"default-filter-types-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\nWhen you specify new , the default filter types of `string` and `number` are still available - unless the new object contains those keys and overrides them explicitly.\n\n\n\nThe current implementation of the default filter types is the following:\n\n```tsx\nexport const defaultFilterTypes: Record> = {\n string: {\n label: 'Text',\n emptyValues: [''],\n defaultOperator: 'includes',\n components: {\n FilterEditor: StringFilterEditor,\n },\n operators: [\n {\n name: 'includes',\n components: {\n Icon: // custom icon as a React component ...\n },\n label: 'includes',\n fn: ({ currentValue, filterValue }) => {\n return (\n typeof currentValue === 'string' &&\n typeof filterValue == 'string' &&\n currentValue.toLowerCase().includes(filterValue.toLowerCase())\n );\n },\n },\n {\n label: 'Equals',\n components: {\n Icon: // custom icon as a React component ...\n },\n name: 'eq',\n fn: ({ currentValue: value, filterValue }) => {\n return typeof value === 'string' && value === filterValue;\n },\n },\n {\n name: 'startsWith',\n components: {\n Icon: // custom icon as a React component ...\n },\n label: 'Starts With',\n fn: ({ currentValue: value, filterValue }) => {\n return value.startsWith(filterValue);\n },\n },\n {\n name: 'endsWith',\n components: {\n Icon: // custom icon as a React component ...\n },\n label: 'Ends With',\n fn: ({ currentValue: value, filterValue }) => {\n return value.endsWith(filterValue);\n },\n },\n ],\n },\n number: {\n label: 'Number',\n emptyValues: ['', null, undefined],\n defaultOperator: 'eq',\n components: {\n FilterEditor: NumberFilterEditor,\n },\n operators: [\n {\n label: 'Equals',\n components: {\n Icon: // custom icon as a React component ...\n },\n name: 'eq',\n fn: ({ currentValue, filterValue }) => {\n return currentValue == filterValue;\n },\n },\n {\n label: 'Not Equals',\n components: {\n Icon: // custom icon as a React component ...\n },\n name: 'neq',\n fn: ({ currentValue, filterValue }) => {\n return currentValue != filterValue;\n },\n },\n {\n name: 'gt',\n label: 'Greater Than',\n components: {\n Icon: // custom icon as a React component ...\n },\n fn: ({ currentValue, filterValue, emptyValues }) => {\n if (emptyValues.includes(currentValue)) {\n return true;\n }\n return currentValue > filterValue;\n },\n },\n {\n name: 'gte',\n components: {\n Icon: // custom icon as a React component ...\n },\n label: 'Greater Than or Equal',\n fn: ({ currentValue, filterValue, emptyValues }) => {\n if (emptyValues.includes(currentValue)) {\n return true;\n }\n return currentValue >= filterValue;\n },\n },\n {\n name: 'lt',\n components: {\n Icon: // custom icon as a React component ...\n },\n label: 'Less Than',\n fn: ({ currentValue, filterValue, emptyValues }) => {\n if (emptyValues.includes(currentValue)) {\n return true;\n }\n return currentValue < filterValue;\n },\n },\n {\n name: 'lte',\n components: {\n Icon: // custom icon as a React component ...\n },\n label: 'Less Than or Equal',\n fn: ({ currentValue, filterValue, emptyValues }) => {\n if (emptyValues.includes(currentValue)) {\n return true;\n }\n return currentValue <= filterValue;\n },\n }\n ],\n },\n };\n```\n\n\n\n\n\n> A custom React component to be used as an editor for the current filter type\n\nEvery filter type can define the following `components`\n\n- `FilterEditor` - a React component to be used as an editor for the current filter type\n- `FilterOperatorSwitch` - a custom component that is displayed at the left of the `FilterEditor` and can be used for switching between operators - only needed for very very advanced use-cases.\n\n\n\nFilter type operators can override the `FilterEditor` component - they can specify the following components:\n\n- `FilterEditor` - if specified, it overrides the `FilterEditor` of the filter type\n- `Icon` - a React component to be used as an icon for the operator - displayed by the menu triggered when clicking on the `FilterOperatorSwitch` component\n\n\n\n\n\n\n\nThe `canDesign` column is using a custom `bool` filter type with a custom filter editor.\n\nThe checkbox has indeterminate state, which will match all values in the data source.\n\n\n\n```ts file=\"$DOCS/reference/hooks/custom-filter-editor-hooks-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n> Controlled prop used for filtering. Can be used for both [client-side](/docs/learn/filtering/filtering-client-side) and [server-side](/docs/learn/filtering/filtering-server-side) filtering.\n\nFor the uncontrolled version, see \n\nIf you want to show the column filter editors, you have to either specify this property, or the uncontrolled - even if you have no initial filters. For no initial filters, use `filterValue=[]`.\n\nThe objects in this array have the following shape:\n\n- `filter` - an object describing the filter\n - `filter.value` - the value to filter by\n - `filter.type` - the current type of the filter (eg: `string`, `number` or another custom type you specify in the filterTypes prop)\n - `filter.operator` - the name of the operator being applied\n- `field` - the field being filtered - generally matched with a column. This is optional, as some columns can have no field.\n- `id` - the id of the column being filtered. This is optional - for columns bound to a field, the `field` should be used instead of the `id`.\n- `disabled` - whether this filter is applied or not\n\n\n\n```ts file=\"onFilterValueChange-example.page.tsx\"\n\n```\n\n\n\n\n\nYou can control the visibility of the column filters by using the prop.\n\n\n\n\n\n\n\n> Whether the datasource will load data lazily - useful for server-side grouping and pivoting. If set to `true` or to an object (with `batchSize` property), the prop must be a function that returns a promise.\n\n\n\n```ts file=\"$DOCS/learn/grouping-and-pivoting/pivoting/remote-pivoting-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n> Controls the expand/collapse state of group rows, when is used\n\n\nSee related , and \n\n\n```tsx title=\"Specifying the state for group rows\"\nconst groupRowsState: DataSourcePropGroupRowsStateObject = {\n collapsedRows: true,\n expandedRows: [['Mexico'], ['Mexico', 'backend'], ['India']],\n};\n```\n\nThe two properties in this object are `collapsedRows` and `expandedRows`, and each can have the following values:\n - `true` - meaning that all groups have this state\n - an array of arrays - representing the exceptions to the default value\n\n\nSo if you have `collapsedRows` set to `true` and then `expandedRows` set to `[['Mexico'], ['Mexico', 'backend'], ['India']]` then all rows are collapsed by default, except the rows specified in the `expandedRows`.\n\n\n\n```ts file=\"./group-rows-state-controlled-example.page.tsx\"\n```\n\n\n\n\n\n void\">\n\n> Callback prop when the changes.\n\nSee related and \n\nThis function is called with an object that's an instance of , when the user interacts with group rows and expands/collapses them.\n\nIf you want to get a plain object from this instance, call the `.getState()` method.\n\nSee reference to find out all the utility methods this instance gives you.\n\n\n\n\n\n> Specifies the initial expand/collapse state of group rows, when is used\n\n\nFor the controlled version, see related .\n\n\n```tsx title=\"Specifying the initial state for group rows\"\nconst defaultGroupRowsState: DataSourcePropGroupRowsStateObject = {\n expandedRows: true,\n collapsedRows: [['Mexico'], ['Mexico', 'backend'], ['India']],\n};\n```\n\nThe two properties in this object are `collapsedRows` and `expandedRows`, and each can have the following values:\n - `true` - meaning that all groups have this state\n - an array of arrays - representing the exceptions to the default value\n\n\nSo if you have `expandedRows` set to `true` and then `collapsedRows` set to `[['Mexico'], ['Mexico', 'backend'], ['India']]` then all rows are expanded by default, except the rows specified in the `collapsedRows`.\n\n\n```ts file=\"./group-rows-initial-state-example.page.tsx\"\n```\n\n\n\n\n\n\n> An array of objects with `field` properties, that control how rows are being grouped.\n\nEach item in the array can have the following properties:\n\n- field - `keyof DATA_TYPE`\n- column - config object for the group column - see .\n\n\n\nWhen using groupRenderStrategy=\"multi-column\", it can be very useful for each group to configure it's own column - use for this.\n\n\nSee for the type definition.\n\n\n\n```ts files=[\"groupBy-example.page.tsx\",\"columns.ts\"]\n\n```\n\n\n\n\n\n[]\">\n\n> An array of objects with `field` properties that control how pivoting works. Pivoting is very often associated with aggregations, so see related for more details.\n\nEach item in the array can have the following properties:\n\n- field - `keyof DATA_TYPE`\n- column - config object or function for generated pivot columns.\n\n\n\nFor more details on the type of the items in this array prop, see .\n\n\n\n\n\n```ts file=\"$DOCS/learn/grouping-and-pivoting/pivoting/pivoting-customize-column-example.page.tsx\"\n\n```\n\n\n\n\n \n>\">\n\n> An object that configures how the column for the current group should look like\n\nIf is specified, it overrides this property (the objects actually get merged, with `groupColumn` having higher priority and being merged last).\n\n\n\nIf you are using a groupRenderStrategy=\"single-column\", then using `groupBy.column` should not be used, as you could have many groups with conflicting column configuration.\n\nIn this case, use the prop.\n\n\n\n\n\n\n\nThis example uses `groupBy.column` to configure the generated columns corresponding to each group.\n\n\n\n```ts files=[\"groupBy-multi-with-column-example.page.tsx\",\"columns.ts\"]\n\n```\n\n\n\n\n\n\n\n> Whether the component should use live pagination.\n\nUse this in combination with and \n\n\n\n```ts file=\"$DOCS/learn/working-with-data/live-pagination-example.page.tsx\"\n\n```\n\n\n\n\n\n void\">\n\n> Callback prop to be called when the data changes via the DataSource API.\n\nCalled when any of the following methods have been called in the `DataSource` api\n\n- \n- \n\n- \n- \n\n- \n- \n\n- \n- \n\n- \n- \n\nThis callback is called with an object that has the following properties:\n\n- `primaryKeyField` - the field configured as the primary key for the ``\n- `mutations` - a `Map` with mutations. The keys in the map are primary keys of the mutated data\n\nThe values in the mutations are object descriptors of mutations, that have the following shape:\n\n- `type`: `'insert'|'update'|'delete'`\n- `originalData`: `DATA_TYPE | null` - the original data before the mutation. In case of `insert`, it will be `null`\n- `data`: `Partial` - the updates to be performed on the data. In case of `delete`, it will be `undefined`. This is an object that will contain the primary key, and the updated values for the data (not necessarily the full object, except for `insert`, where it will be of type `DATA_TYPE`).\n\n\n\nstring|number)\" defaulValue={undefined}>\n\n> A cursor value for live pagination. A good value for this is the id of the last item in the array. It can also be defined as a function\n\nUse this in combination with and \n\n\n\nWhen this is a function, it is called with a parameter object that has the following properties:\n\n- `array` - the current array of data\n- `lastItem` - the last item in the array\n- `length` - the length of the data array\n\n\n\n\n\n```ts file=\"$DOCS/learn/working-with-data/live-pagination-example.page.tsx\"\n\n```\n\n\n\n\n\n)=>void\">\n\n> A function to be called when data-related state changes.\n\nCan be used to implement \n\nThe function is called with an object that has the following properties:\n\n- `sortInfo` - current sort information - see for details\n- `groupBy` - current grouping information - see for details\n- `filterValue` - current filtering information - see for details\n- `livePaginationCursor` - the value for the live pagination cursor - see for details\n- `changes` - an object that can help you figure out what change caused `onDataParamsChange` to be called.\n\n\n\n```ts file=\"$DOCS/learn/working-with-data/live-pagination-example.page.tsx\"\n\n```\n\n\n\n\n void\">\n\n> Callback prop called when the changes.\n\nThis might not be called immediately, as there might be a set.\n\n\n\n```ts file=\"onFilterValueChange-example.page.tsx\"\n\n```\n\n\n\n\n\n void\">\n\n> A function to be called when the changes.\n\nAlso see related .\n\n\n\n) => void\">\n\n> The callback that is called when the `DataSource` is ready. The [`dataSourceApi`](/docs/reference/datasource-api) is passed as the first argument.\n\n\n\n void\">\n\n> A function to be called when the changes.\n\n\n\n\n\nUse your mouse to select/deselect cells.\n\n\n\n```ts file=\"$DOCS/learn/selection/controlled-cell-selection-example.page.tsx\"\n\n```\n\n\n\n\n\n void\">\n\n> A function to be called when the changes.\n\n\n\n\n\nUse your mouse or keyboard (press the spacebar) to select/deselect a single row.\n\n\n\n```ts file=\"$DOCS/reference/controlled-single-row-selection-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\nThis example shows how you can use multiple row selection with a predefined controlled value.\n\nGo ahead and select some groups/rows and see the selection value adjust.\n\nThe example also shows how you can use the `InfiniteTableApi` to retrieve the actual ids of the selected rows.\n\n\n\n```ts file=\"$DOCS/reference/controlled-multi-row-selection-example.page.tsx\"\n\n```\n\n\n\n\n void\">\n\n> Called when sorting changes on the DataSource.\n\nThe sorting can change either via a user interaction or by calling an API method (from the [root API](api) or the [Column API](column-api)).\n\nSee related for controlled sorting and for uncontrolled sorting.\n\n\n\n\n\n> A value that can be used to trigger a re-fetch of the data.\n\nBy updating the value of this prop (eg: you can use it as a counter, and increment it) the `` component reloads it's if it's defined as a function. More specifically, the `data` function is called again and the result will replace the current data.\n\n\n\n\n\nThis example shows how you can use the `refetchKey` to trigger reloading the data\n\n\n\n```ts file=\"refetchKey-example.page.tsx\"\n\n```\n\n\n \n\n\n\n\n> Describes the selected row(s) in the `DataSource`\n\nFor single selection, the prop will be of type: `number | string | null`. Use `null` for empty selection in single selection mode.\n\nFor multiple selection, the prop will have the following shape:\n\n```ts\nconst rowSelection = {\n selectedRows: [3, 6, 100, 23], // those specific rows are selected\n defaultSelection: false, // all rows deselected by default\n};\n\n// or\nconst rowSelection = {\n deselectedRows: [3, 6, 100, 23], // those specific rows are deselected\n defaultSelection: true, // all other rows are selected\n};\n\n// or, for grouped data - this example assumes groupBy=continent,country,city\nconst rowSelection = {\n selectedRows: [\n 45, // row with id 45 is selected, no matter the group\n ['Europe', 'France'], // all rows in Europe/France are selected\n ['Asia'], // all rows in Asia are selected\n ],\n deselectedRows: [\n ['Europe', 'France', 'Paris'], // all rows in Paris are deselected\n ],\n defaultSelection: false, // all other rows are selected\n};\n```\n\nFor using group keys in the selection value, see related \n\n\n\n\n\nUse your mouse or keyboard (press the spacebar) to select/deselect a single row.\n\n\n\n```ts file=\"$DOCS/reference/controlled-single-row-selection-example.page.tsx\"\n\n```\n\n\n\n\n\nWhen is being used - this means not all available groups/rows have actually been loaded yet in the dataset - we need a way to allow you to specify that those possibly unloaded rows/groups are selected or not. In this case, the `rowSelection.selectedRows`/`rowSelection.deselectedRows` arrays should not have row primary keys as strings/numbers, but rather rows/groups specified by their full path (so should be set to `true`).\n\n```ts {6}\n// this example assumes groupBy=continent,country,city\nconst rowSelection = {\n selectedRows: [\n // row with id 45 is selected - we need this because in the lazyLoad scenario,\n // not all parents might have been made available yet\n ['Europe','Italy', 'Rome', 45],\n ['Europe','France'], // all rows in Europe/France are selected\n ['Asia'] // all rows in Asia are selected\n ]\n deselectedRows: [\n ['Europe','Italy','Rome'] // all rows in Rome are deselected\n // but note that row with id 45 is selected, so Rome will be rendered with an indeterminate selection state\n ],\n defaultSelection: false // all other rows are selected\n}\n```\n\nIn the example above, we know that there are 3 groups (`continent`, `country`, `city`), so any item in the array that has a 4th element is a fully specified leaf node. While lazy loading, we need this fully specified path for specific nodes, so we know which group rows to render with indeterminate selection.\n\n\n\n\n\nThe prop can be used for both lazy and non-lazy `DataSource` components.\n\n\n\n\n\n\n\nThis example shows how you can use multiple row selection with a predefined controlled value.\n\nGo ahead and select some groups/rows and see the selection value adjust.\n\nThe example also shows how you can use the `InfiniteTableApi` to retrieve the actual ids of the selected rows.\n\n\n\n```ts file=\"$DOCS/reference/controlled-multi-row-selection-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n> Specifies the type of selection that should be enabled.\n\n\n\n\nRead more on row selection (`multi-row` and `single-row`).\n\n\n\n\n\nRead more on cell selection (`multi-cell` and `single-cell`).\n\n\n\n\n\n\n\n```ts file=\"selectionMode-example.page.tsx\"\n\n```\n\n\n\n\n\n[], arr: T[]) => T[]\">\n\n> Custom sorting function to replace the `multisort` function used by default.\n\nThe function specified in the prop is called with the as the first argument and the data array as the second. It should return a sorted array, as per the it was called with.\n\n\n\nWhen is specified, will be forced to `false`, as the sorting is done in the browser.\n\n\n\n\nThe `@infinite-table/infinite-react` package exports a `multisort` function - this is the default function used for local sorting.\n\n```ts\nimport { multisort } from '@infinite-table/infinite-react';\n\nconst arr: Developer[] = [\n /*...*/\n];\nconst sortInfo = [\n {\n field: 'age',\n dir: -1,\n },\n {\n field: 'name',\n dir: 1,\n },\n];\nmultisort(sortInfo, arr);\n```\n\nIf you want to implement your own custom sort function, the `multisort` fn is a good starting point you can use.\n\n\n\n\n\n```ts file=\"$DOCS/reference/datasource-props/local-sortFunction-single-sorting-example-with-local-data-example.page.tsx\"\n\n```\n\n\n\n\n\n|DataSourceSingleSortInfo[]|null\">\n\n> Information for sorting the data. This is a controlled prop.\n\nAlso see related (uncontrolled version), , and .\n\nSorting can be single (only one field/column can be sorted at a time) or multiple (multiple fields/columns can be sorted at the same time). Therefore, this property an be an array of objects or a single object (or null) - the shape of the objects (of type `DataSourceSingleSortInfo`)is the following.\n\n- `dir` - `1 | -1` - the direction of the sorting\n- `field`? - `keyof DATA_TYPE` - the field to sort\n- `id`? - `string` - if you don't sort by a field, you can specify an id of the column this sorting is bound to. Note that columns have a valueGetter, which will be used when doing local sorting and the column is not bound to an exact field.\n- `type` - the sort type - one of the keys in - eg `\"string\"`, `\"number\"` - will be used for local sorting, to provide the proper comparison function.\n\nWhen you want to use multiple sorting, but have no default sort order/information, use `[]` (the empty array) to denote multiple sorting should be enabled.\n\nIf no `sortInfo` is provided, by default, when clicking a sortable column, single sorting will be applied.\n\n\n\nFor configuring if a column is sortable or not, see and . By default, all columns are sortable.\n\n\n\n\n\n```ts file=\"$DOCS/learn/sorting/remote-controlled-multi-sorting-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n\n\n> Specifies if changes in the should trigger a reload of the data source - applicable when is a function. Replaces the deprecated .\n\nSee related and .\n\nWhen set to `false` (the default), the data is sorted locally (in the browser) after the data-source is loaded. When set to `true`, the data should be sorted by the server (or by the data-source function that serves the data).\n\nSee [the Sorting page](/docs/learn/sorting/overview) for more details.\n\nFor configuring the sorting behavior when multiple sorting is enabled, see .\n\n\n\n\n\n> Specifies which changes in the data-related props should trigger a reload of the data source - applicable when is a function.\n\nSee .\nSee .\nSee .\nSee .\n\n\n\n\n\n\n> Specifies where the sorting should be done. Use instead.\n\nSee related and .\n\nWhen set to `'local'`, the data is sorted locally (in the browser) after the data-source is loaded. When set to `'remote'`, the data should be sorted by the server (or by the data-source function that serves the data).\n\nSee [the Sorting page](/docs/learn/sorting/overview) for more details.\n\nFor configuring the sorting behavior when multiple sorting is enabled, see .\n\n\n\n number)>\">\n\n> Describes the available sorting functions used for local sorting. The object you provide will be merged into the default sort types.\n\nCurrently there are two `sortTypes` available:\n\n- `\"string\"`\n- `\"number\"`\n- `\"date\"`\n\nThose values can be used for the column.sortType and column.dataType properties.\n\n```ts\n// default implementation\nconst sortTypes = {\n string: (a, b) => a.localeCompare(b),\n number: (a, b) => a - b,\n date: (a, b) => a - b,\n};\n```\n\nWhen a column does not explicitly specify the column.sortType, the column.dataType will be used instead. And if no column.dataType is defined, it will default to `string`.\n\nYou can add new sort types to the DataSource and InfiniteTable components by specifying this property - the object will be merged into the default sort types.\n\n\n\n```ts file=\"./sortTypes-example.page.tsx\"\n\n```\n\n\n\n\n\nIn this example, for the `\"color\"` column, we specified column.sortType=\"color\" - we could have passed that as `column.dataType` instead, but if the grid had filtering, it wouldn't know what filters to use for \"color\" - so we usedcolumn.sortType to only change how the data is sorted.\n\n\n\n\n\n\n\n> Specifies whether contains group keys or only row ids/primary keys.\n\nWhen this is `true`, you might want to use the [getSelectedPrimaryKeys](./selection-api#getSelectedPrimaryKeys) method.\n\n\n\n\n\nThis example shows how you can use have row selection with group keys instead of just the primary keys of rows.\n\n\n\n```ts file=\"$DOCS/reference/controlled-multi-row-selection-example-with-group-keys.page.tsx\"\n\n```\n\n\n\n\n\n\n" - }, - { - "filePath": "/docs/reference/hooks/index", - "routePath": "/docs/reference/hooks/", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/reference/hooks/index.page.md", - "fileName": "index.page.md", - "folderPath": "/docs/reference/hooks/", - "frontmatter": { - "title": "Infinite Table Hooks", - "layout": "API", - "description": "Hooks Reference page for Infinite Table - with complete examples" - }, - "excerpt": "Infinite Table exposes a few custom hooks that can be used to customize the component and its behavior. Most of the hooks will be useful when you want to implement custom components for `InfiniteTable` - like custom cells, headers, cell editors, etc.", - "readingTime": "4 min read", - "content": "\nInfinite Table exposes a few custom hooks that can be used to customize the component and its behavior. Most of the hooks will be useful when you want to implement custom components for `InfiniteTable` - like custom cells, headers, cell editors, etc.\n\nSee below for the full list of hooks exposed by `InfiniteTable`, each with examples and code snippets.\n\n\n\n\n\n> Gives you access to the master row info in the current RowDetail component.\n\n\n\n\n\n\nThis example shows a master DataGrid with cities & countries.\n\nThe details for each city shows a DataGrid with developers in that city.\n\n\n\n```ts file=\"$DOCS/learn/master-detail/master-detail-component-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n> Use it inside the or (or other rendering functions) to retrieve information about the cell that is being rendered.\n\n```ts\nimport { useInfiniteColumnCell } from '@infinite-table/infinite-react';\n```\n\nFor custom column header components, see related .\n\nWhen using this hook inside a custom column cell component, make sure you get `domRef` from the hook result and pass it on to the final `JSX.Element` that is the DOM root of the component.\n\n```tsx\nconst CustomCellComponent = (props: React.HTMLProps) => {\n const { domRef, ...other } = useInfiniteColumnCell();\n\n return (\n
\n {props.children}\n
\n );\n};\n```\n\nYou should not pass the `domRef` along when using the hook inside the or function.\n\n\n\n```tsx file=\"$DOCS/reference/column-render-hooks-example.page.tsx\"\n\n```\n\n\n\n
\n\n\n\n> Allows you to write a custom editor to be used for [editing](/docs/learn/editing/overview). The hook returns an object shape.\n\nInside this hook, you can also call to get access to the cell-related information.\n\nSee related \n\n\n\nWhen writing a custom editor, it's probably good to stop the propagation of the `KeyDown` event, so that the table doesn't react to the key presses (and do navigation and other stuff).\n\n\n\n\n\n\n\nTry editing the `salary` column - it has a custom editor\n\n\n\n```tsx file=\"custom-editor-hooks-example.page.tsx\"\n\n```\n\n\n\n\n\n ({ column, value, setValue, className, filtered,... })\">\n\n> Used to write custom filter editors for columns.\n\nThe return value of this hook is an object with the following properties:\n\n- `value` - the value that should be passed to the filter editor\n- `setValue(value)` - the functon you have to call to update the filtering for the current column\n- `column` - the current column\n- `operatorName`: `string` - the name of the operator currently being applied\n- `className` - a CSS class name to apply to the filter editor, for default styling\n- `filtered` - a boolean indicating whether the column is currently filtered or not\n- `disabled` - a boolean indicating whether the filter editor should be rendered as disabled or not\n- `filterTypeKey`: `string` - the key of the filter type\n- `filterType` - the filter type object for the current column\n- `filterTypes` - a reference to the object as configured in the `DataSource`\n\n\n\n\n\nThe `canDesign` column is using a custom `bool` filter type with a custom filter editor.\n\nThe checkbox has indeterminate state, which will match all values in the data source.\n\n\n\n```ts file=\"custom-filter-editor-hooks-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n> Used inside or \n\n```ts\nimport { useInfiniteHeaderCell } from '@infinite-table/infinite-react';\n```\n\nFor custom column cell components, see related .\n\nWhen using this hook inside a custom column header component, make sure you get `domRef` from the hook result and pass it on to the final `JSX.Element` that is the DOM root of the component.\n\n```tsx\nconst CustomHeaderComponent = (props: React.HTMLProps) => {\n const { domRef, ...other } = useInfiniteHeaderCell();\n\n return (\n
\n {props.children}\n
\n );\n};\n```\n\nYou should not pass the `domRef` along when using the hook inside the\n function.\n\n\n\n```tsx file=\"$DOCS/reference/column-header-hooks-example.page.tsx\"\n\n```\n\n\n\n
\n\n
\n" + "excerpt": "Available starting with version `6.1.1`.", + "readingTime": "3 min read", + "content": "\nAvailable starting with version `6.1.1`.\n\nSee the [Keyboard Navigation](/docs/learn/keyboard-navigation/navigating-cells) page for more details.\n\n```tsx title=\"Configuring the keyboard navigation to be 'cell'\"\n\n\n// can be \"cell\" (default), \"row\" or false\n```\n\nYou can retrieve the keyboard navigation api by reading it from the `api.keyboardNavigationApi` property.\n\n```tsx {4}\n\nconst onReady = ({api}: {api:InfiniteTableApi}) => {\n // do something with it\n api.keyboardNavigationApi.gotoCell({direction: 'top'})\n}\n\n\n columns={[...]}\n onReady={onReady}\n/>\n```\n\nSee the [Infinite Table API page](/docs/reference/api) for the main API.\nSee the [Infinite Table Cell Selection API page](/docs/reference/cell-selection-api) for the row selection API.\nSee the [Infinite Table Row Selection API page](/docs/reference/row-selection-api) for the row selection API.\nSee the [Infinite Table Column API page](/docs/reference/column-api) for the column API.\n\n\n\n void\">\n\n> Sets the keyboard navigation mode. See \n\nThe sole argument is of the same type as the \n\nSee the [Keyboard Navigation](/docs/learn/keyboard-navigation/navigating-cells) page for more details.\n\n\n\nIf you are using controlled or , make sure you update the values by using the and callbacks respectively.\n\n\n\n\n\n\n void\">\n\n> Sets the value for /\nSee related \n\n\n\n\nIf you are using controlled make sure you update the controlled value by using the callback prop.\n\n\n\n\n\n void\">\n\n> Sets the value for /\n\n\n\n\nIf you are using controlled , make sure you update the values by using the callback prop.\n\n\n\n\n\n number | false\">\n\n> Changes the active row index to the next row. See related , \n\nReturns `false` if the action was not successful (eg: already at the last row), otherwise the new active row index.\n\nThis sets the value for \n\n\n\n\n number | false\">\n\n> Changes the active row index to the prev row. See related , \n\nReturns `false` if the action was not successful (eg: already at the first row), otherwise the new active row index.\n\nThis sets the value for \n\n\n\n [number, number] | false\">\n\n> Changes the active cell index, by navigating to the specified direction (equivalent to pressing the arrow keys).\nSee related \n\n\n\n```tsx file=\"goto-cell-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n" }, { "filePath": "/docs/reference/row-detail-api/index", @@ -1138,18 +1122,34 @@ "content": "\nThis API can be used when [master-detail](/docs/learn/master-detail/overview) is configured in the DataGrid.\n\nYou can retrieve the row details api by reading it from the `api.rowDetailApi` property.\n\n```tsx {4}\n\nconst onReady = ({api}: {api:InfiniteTableApi}) => {\n // do something with it\n api.rowDetailApi.collapseAllDetails()\n}\n\n\n columns={[...]}\n onReady={onReady}\n/>\n```\n\nSee the [Infinite Table API page](/docs/reference/api) for the main API.\nSee the [Infinite Table Cell Selection API page](/docs/reference/cell-selection-api) for the row selection API.\nSee the [Infinite Table Row Selection API page](/docs/reference/row-selection-api) for the row selection API.\nSee the [Infinite Table Column API page](/docs/reference/column-api) for the column API.\n\n\n\n void\">\n\n> Collapses all row details.\n\n\n\n\n\nSome of the rows in the master DataGrid are expanded by default.\n\nYou can collapse them via the Row Detail API.\n\n\n\n```ts file=\"$DOCS/learn/master-detail/master-detail-api-example.page.tsx\"\n\n```\n\n\n\n\n\n void\">\n\n> Expands all row details.\n\n\n\n\n\nClick the `Expand All` button to expand all row details.\n\n\n\n```ts file=\"$DOCS/learn/master-detail/master-detail-api-example.page.tsx\"\n\n```\n\n\n\n\n\n boolean\">\n\n> Checks if the row detail is collapsed for the row with the specified primary key.\n\n\n\n boolean\">\n\n> Checks if the row detail is expanded for the row with the specified primary key.\n\n\n\n void\">\n\n> Collapses the detail for the row with the specified primary key.\n\n\n\n boolean\">\n\n> Expands the detail for the row with the specified primary key.\n\n\n\n boolean\">\n\n> Toggles the expand/collapse state of the row detail, for the row with the specified primary key.\n\n\n\n\n" }, { - "filePath": "/docs/reference/keyboard-navigation-api/index", - "routePath": "/docs/reference/keyboard-navigation-api/", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/reference/keyboard-navigation-api/index.page.md", + "filePath": "/docs/reference/hooks/index", + "routePath": "/docs/reference/hooks/", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/reference/hooks/index.page.md", "fileName": "index.page.md", - "folderPath": "/docs/reference/keyboard-navigation-api/", + "folderPath": "/docs/reference/hooks/", "frontmatter": { - "title": "Infinite Table Keyboard Navigation API", - "layout": "API" + "title": "Infinite Table Hooks", + "layout": "API", + "description": "Hooks Reference page for Infinite Table - with complete examples" }, - "excerpt": "Available starting with version `6.1.1`.", - "readingTime": "3 min read", - "content": "\nAvailable starting with version `6.1.1`.\n\nSee the [Keyboard Navigation](/docs/learn/keyboard-navigation/navigating-cells) page for more details.\n\n```tsx title=\"Configuring the keyboard navigation to be 'cell'\"\n\n\n// can be \"cell\" (default), \"row\" or false\n```\n\nYou can retrieve the keyboard navigation api by reading it from the `api.keyboardNavigationApi` property.\n\n```tsx {4}\n\nconst onReady = ({api}: {api:InfiniteTableApi}) => {\n // do something with it\n api.keyboardNavigationApi.gotoCell({direction: 'top'})\n}\n\n\n columns={[...]}\n onReady={onReady}\n/>\n```\n\nSee the [Infinite Table API page](/docs/reference/api) for the main API.\nSee the [Infinite Table Cell Selection API page](/docs/reference/cell-selection-api) for the row selection API.\nSee the [Infinite Table Row Selection API page](/docs/reference/row-selection-api) for the row selection API.\nSee the [Infinite Table Column API page](/docs/reference/column-api) for the column API.\n\n\n\n void\">\n\n> Sets the keyboard navigation mode. See \n\nThe sole argument is of the same type as the \n\nSee the [Keyboard Navigation](/docs/learn/keyboard-navigation/navigating-cells) page for more details.\n\n\n\nIf you are using controlled or , make sure you update the values by using the and callbacks respectively.\n\n\n\n\n\n\n void\">\n\n> Sets the value for /\nSee related \n\n\n\n\nIf you are using controlled make sure you update the controlled value by using the callback prop.\n\n\n\n\n\n void\">\n\n> Sets the value for /\n\n\n\n\nIf you are using controlled , make sure you update the values by using the callback prop.\n\n\n\n\n\n number | false\">\n\n> Changes the active row index to the next row. See related , \n\nReturns `false` if the action was not successful (eg: already at the last row), otherwise the new active row index.\n\nThis sets the value for \n\n\n\n\n number | false\">\n\n> Changes the active row index to the prev row. See related , \n\nReturns `false` if the action was not successful (eg: already at the first row), otherwise the new active row index.\n\nThis sets the value for \n\n\n\n [number, number] | false\">\n\n> Changes the active cell index, by navigating to the specified direction (equivalent to pressing the arrow keys).\nSee related \n\n\n\n```tsx file=\"goto-cell-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n" + "excerpt": "Infinite Table exposes a few custom hooks that can be used to customize the component and its behavior. Most of the hooks will be useful when you want to implement custom components for `InfiniteTable` - like custom cells, headers, cell editors, etc.", + "readingTime": "5 min read", + "content": "\nInfinite Table exposes a few custom hooks that can be used to customize the component and its behavior. Most of the hooks will be useful when you want to implement custom components for `InfiniteTable` - like custom cells, headers, cell editors, etc.\n\nSee below for the full list of hooks exposed by `InfiniteTable`, each with examples and code snippets.\n\n\n\n\n\n> Gives you access to the master row info in the current RowDetail component.\n\n\n\n\n\nThis example shows a master DataGrid with cities & countries.\n\nThe details for each city shows a DataGrid with developers in that city.\n\n\n```ts file=\"$DOCS/learn/master-detail/master-detail-component-example.page.tsx\" title=\"Basic master detail DataGrid example\" size=\"lg\"\n\n```\n\n\n\n\n\n> You can use it in your app components that are nested inside the ``\n\n```ts\nimport { useDataSourceState } from '@infinite-table/infinite-react'\n```\n\nUsing it gives you access to the underlying data that InfiniteTable is using.\n\n\n\nPlease make sure you know what you're doing. This is intended only for advanced and complex use-cases.\n\n\n\n\n```tsx title=\"InfiniteTable can be nested anywhere inside the component\"\n\n

Your DataGrid>\n \n \n \n\n```\n\nAny component nested inside the `` can access the underlying data.\n\n```tsx live file=\"using-datasource-context.page.tsx\"\n\n```\n\n\n\n\n> Use it inside the or (or other rendering functions) to retrieve information about the cell that is being rendered.\n\n```ts\nimport { useInfiniteColumnCell } from '@infinite-table/infinite-react';\n```\n\nFor custom column header components, see related .\n\nWhen using this hook inside a custom column cell component, make sure you get `domRef` from the hook result and pass it on to the final `JSX.Element` that is the DOM root of the component.\n\n```tsx\nconst CustomCellComponent = (props: React.HTMLProps) => {\n const { domRef, ...other } = useInfiniteColumnCell();\n\n return (\n
\n {props.children}\n
\n );\n};\n```\n\nYou should not pass the `domRef` along when using the hook inside the or function.\n\n\n\n```tsx file=\"$DOCS/reference/column-render-hooks-example.page.tsx\"\n\n```\n\n\n\n
\n\n\n\n> Allows you to write a custom editor to be used for [editing](/docs/learn/editing/overview). The hook returns an object shape.\n\nInside this hook, you can also call to get access to the cell-related information.\n\nSee related \n\n\n\nWhen writing a custom editor, it's probably good to stop the propagation of the `KeyDown` event, so that the table doesn't react to the key presses (and do navigation and other stuff).\n\n\n\n\n\n\n\nTry editing the `salary` column - it has a custom editor\n\n\n\n```tsx file=\"custom-editor-hooks-example.page.tsx\"\n\n```\n\n\n\n\n\n ({ column, value, setValue, className, filtered,... })\">\n\n> Used to write custom filter editors for columns.\n\nThe return value of this hook is an object with the following properties:\n\n- `value` - the value that should be passed to the filter editor\n- `setValue(value)` - the functon you have to call to update the filtering for the current column\n- `column` - the current column\n- `operatorName`: `string` - the name of the operator currently being applied\n- `className` - a CSS class name to apply to the filter editor, for default styling\n- `filtered` - a boolean indicating whether the column is currently filtered or not\n- `disabled` - a boolean indicating whether the filter editor should be rendered as disabled or not\n- `filterTypeKey`: `string` - the key of the filter type\n- `filterType` - the filter type object for the current column\n- `filterTypes` - a reference to the object as configured in the `DataSource`\n\n\n\n\n\nThe `canDesign` column is using a custom `bool` filter type with a custom filter editor.\n\nThe checkbox has indeterminate state, which will match all values in the data source.\n\n\n\n```ts file=\"custom-filter-editor-hooks-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n> Used inside or \n\n```ts\nimport { useInfiniteHeaderCell } from '@infinite-table/infinite-react';\n```\n\nFor custom column cell components, see related .\n\nWhen using this hook inside a custom column header component, make sure you get `domRef` from the hook result and pass it on to the final `JSX.Element` that is the DOM root of the component.\n\n```tsx\nconst CustomHeaderComponent = (props: React.HTMLProps) => {\n const { domRef, ...other } = useInfiniteHeaderCell();\n\n return (\n
\n {props.children}\n
\n );\n};\n```\n\nYou should not pass the `domRef` along when using the hook inside the\n function.\n\n\n\n```tsx file=\"$DOCS/reference/column-header-hooks-example.page.tsx\"\n\n```\n\n\n\n
\n\n\n" + }, + { + "filePath": "/docs/reference/datasource-props/index", + "routePath": "/docs/reference/datasource-props/", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/reference/datasource-props/index.page.md", + "fileName": "index.page.md", + "folderPath": "/docs/reference/datasource-props/", + "frontmatter": { + "title": "DataSource Props", + "layout": "API", + "description": "Props Reference page for your DataSource in Infinite Table - with complete examples" + }, + "excerpt": "In the API Reference below we'll use **`DATA_TYPE`** to refer to the TypeScript type that represents the data the component is bound to.", + "readingTime": "39 min read", + "content": "\nIn the API Reference below we'll use **`DATA_TYPE`** to refer to the TypeScript type that represents the data the component is bound to.\n\n\n\n string\">\n\n> The name of the id/primary key property of an item in the array. The value of this property needs to be unique.\n\nThis is probably one of the most important properties of the `` component, as it is used to identify items in the array.\n\n\n\nUnlike with other DataGrid components, with `InfiniteTable` you don't need to have a column mapped to the primary key field.\n\nThe primary key is used internally by the component and is not displayed in the grid if you don't explicitly have a column bound to that field.\n\n\n\nIf the primary key is not unique, Infinite Table DataGrid won't work properly.\n\n\n\n```ts file=\"data-example.page.tsx\"\n\n```\n\n\n\n\n\nThe primary key can be either a string (the name of a property in the data object), or a function that returns a string.\n\nUsing functions (for more dynamic primary keys) is supported, but hasn't been tested extensively - so please report any issues you might encounter.\n\n\n\n\n\n\n\n> Specifies the expand/collapse state of the tree nodes. See for the shape of this object. For the uncontrolled version, see .\n\nIf no `treeExpandState` prop is specified, the tree will be rendered as fully expanded by default.\n\nWhen using the controlled version, make sure to update the `treeExpandState` prop by using the callback.\n\n\n\n```ts file=\"tree-controlled-expandstate-example.page.tsx\"\n\n```\n\n\n\n\n\n void\">\n\n> Called when the tree expand state changes.\n\nWhen the user interacts with the tree (by expanding or collapsing a node), this callback is called with the new tree state.\n\nThe first parameter is the new tree state, and the second parameter is an object with the following properties:\n\n- `dataSourceApi` - the DataSource API instance\n- `nodePath` - the path of the node that changed state. If the state was produced by an or call, this will be `null`.\n- `nodeState` - the new state of the node (`\"collapsed\"` or `\"expanded\"`)\n\n\n\n\n```ts file=\"tree-controlled-expandstate-example.page.tsx\"\n\n```\n\n\n\n\n\n void\">\n\n> Called when a node is expanded. See related and props.\n\n\n\n\nThe and callbacks are called when a node is expanded or collapsed, respectively - either via user interaction or by an API call. However, they will not be called when the or methods are called.\n\n\n\n\n\n, treeExpandState: TreeExpandState) => boolean\">\n\n> Decides if the current (non-leaf) node is expanded. \n\nThe inverse prop, is also available. Only one of these props can be specified.\n\n\n\nIf this prop is specified, is ignored.\n\n\n\n\n\n, treeExpandState: TreeExpandState) => boolean\">\n\n> Decides if the current (non-leaf) node is collapsed. See related prop.\n\nThe inverse prop, is also available. Only one of these props can be specified.\n\n\n\nIf this prop is specified, is ignored.\n\n\n\n\n\n) => boolean\">\n\n> Decides if the current (non-leaf) node can be expanded or collapsed and if the tree icon is disabled.\n\nBy default, parent nodes with `children: []` are read-only, meaning they won't respond to expand/collapse clicks.\n\nHowever, if you specify a custom `isNodeReadOnly` function, you can change this behavior.\n\n\n\nWhen a node is read-only, the and methods need the `options.force` flag to be set to `true` in order to override the read-only restriction.\n\nHowever, and will work regardless of the `isNodeReadOnly` setting.\n\nFor full control over the expand/collapse state of read-only nodes, you can use the / props.\n\n\n\n\n\n```ts file=\"tree-isNodeReadOnly-example.page.tsx\"\n\n```\n\n\n\n\n void\">\n\n> Called when a node is collapsed. See related and props.\n\n\n\n\nThe and callbacks are called when a node is expanded or collapsed, respectively - either via user interaction or by an API call. However, they will not be called when the or methods are called.\n\n\n\n\n\n\n\n> The name of the property in the data object that contains the child nodes for each tree node.\n\nOnly available when you're using the `` component.\n\nIf not specified, it defaults to `\"children\"`.\n\n\n\nEach node gets a `nodePath` property, which is the array with the ids of all the parent nodes leading down to the current node. The node path includes the id of the current node\n\n\n\n\n```tsx {2} title=\"Node path vs row id\"\nconst data = [\n { id: '1', name: 'Documents', // path: ['1']\n children: [\n { id: '10', name: 'Private', // path: ['1', '10']\n children: [\n { id: '100', name: 'Report.docx' }, // path: ['1', '10', '100'] \n { id: '101', name: 'Vacation.docx' },// path: ['1', '10', '101']\n ],\n },\n ]\n },\n {\n id: '2',\n name: 'Downloads', // path: ['2']\n children: [\n {\n id: '20',\n name: 'cat.jpg', // path: ['2', '20']\n },\n ],\n },\n];\n```\n\n\n\n```ts file=\"tree-nodesKey-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n> Specifies the expand/collapse state of the tree nodes. See for the shape of this object. For the controlled version, see .\n\n\n\n\n\n```ts file=\"tree-uncontrolled-expandstate-example.page.tsx\"\n\n```\n\n\n\n\n\n\n) => boolean\">\n\n> This function ultimately decides the disabled state of a row. It overrides both / props.\n\nIt's called with a single argument - the row info object for the row in question.\n\nIt should return `true` if the row is disabled, and `false` otherwise.\n\n\n\nWhen this prop is used, will not be called.\n\n\n\n\n\n\n\n\n> The uncontrolled prop for managing row enabled/disabled state. For the controlled version see . For listening to row disabled state changes, see .\n\nThe value for this prop is an object with two properties:\n\n- `enabledRows` - either `true` or an array of row ids that are enabled. When `true` is passed, `disabledRows` should be an array of row ids that are disabled.\n- `disabledRows` - either `true` or an array of row ids that are disabled. When `true` is passed, `enabledRows` should be an array of row ids that are enabled.\n\n\n\nThe values in the `enabledRows`/`disabledRows` arrays are row ids, and not indexes.\n\n\n\n\n\n\nThis prop can be overriden by using the prop.\n\n\n\nHere's an example of how to use the `defaultRowDisabledState` prop:\n\n\n\n\n\nRows with ids `1`, `3`, `4` and `5` are disabled.\n\n\n\n```ts file=\"defaultRowDisabledState-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n> Manages row enabled/disabled state. For the uncontrolled version see . For listening to row disabled state changes, see .\n\nThe value for this prop is an object with two properties:\n\n- `enabledRows` - either `true` or an array of row ids that are enabled. When `true` is passed, `disabledRows` should be an array of row ids that are disabled.\n- `disabledRows` - either `true` or an array of row ids that are disabled. When `true` is passed, `enabledRows` should be an array of row ids that are enabled.\n\n\nWhen using this controlled prop, you will need to update the `rowDisabledState` prop by using the callback.\n\n\n\n\nThis prop can be overriden by using the prop.\n\n\n\n\n\n\n\nRows with ids `1`, `3`, `4` and `5` are disabled initially.\n\nRight click rows and use the context menu to enable/disable rows.\n\n\n\n```ts file=\"rowDisabledState-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n> Determines what nodes are selected and deselected. For the uncontrolled version see .\n\nThe value of this prop determines if a node is selected or not.\n\nSee for details on the shape of this object.\n\n\n\n\n```ts file=\"tree-controlled-selectedstate-example.page.tsx\"\n\n```\n\n\n\n\n\n void\">\n\n> Called when the tree selection changes. See .\n\n\n\n```ts file=\"tree-controlled-selectedstate-example.page.tsx\"\n\n```\n\n\n\nWhen using `multi-row` , the signature of this callback is:\n\n - `treeSelection` - the new tree selection state\n - `context` - an object with the following properties:\n - `selectionMode` - will be `\"multi-row\"`\n - `lastUpdatedNodePath` - the path of the node that was last updated (either via user action or api call). Will be `null` of the action that triggered this callback was or .\n - `dataSourceApi` - the [DataSource API](/docs/reference/datasource-api) instance\n\n\n\n\n\n> Determines what nodes are selected and deselected. For the controlled version see .\n\nThe value of this prop determines if a node is selected or not.\n\nSee for details on the shape of this object.\n\n\n\n\n```ts file=\"tree-uncontrolled-selectedstate-example.page.tsx\"\n\n```\n\n\n\n\n\n void\">\n\n> Called when the row disabled state changes.\n\nIt's called with just 1 argument (`rowDisabledState`), which is an instance of the `RowDisabledState` class. To get a literal object that represents the row disabled state, call the `rowDisabledState.getState()` method.\n\n```tsx {3,19}\nimport {\n DataSource,\n RowDisabledStateObject,\n} from '@infinite-table/infinite-react';\nfunction App() {\n const [rowDisabledState, setRowDisabledState] = React.useState<\n RowDisabledStateObject\n >({\n enabledRows: true,\n disabledRows: [1, 3, 4, 5],\n });\n return (\n <>\n \n data={data}\n primaryKey=\"id\"\n rowDisabledState={rowDisabledState}\n onRowDisabledStateChange={(rowState) => {\n setRowDisabledState(rowState.getState());\n }}\n />\n \n );\n}\n```\n\n\nWhen using the controlled prop, you will need to update the `rowDisabledState` by using this callback.\n\n\n\n\n\n\nRows with ids `1`, `3`, `4` and `5` are disabled initially.\n\nRight click rows and use the context menu to enable/disable rows.\n\n\n\n```ts file=\"rowDisabledState-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n\">\n\n> Specifies the functions to use for aggregating data. The object is a map where the keys are ids for aggregations and values are object of the shape described below.\n\nThe `DataSourceAggregationReducer` type can have the following properties\n\n- `initialValue` - type `any`, mandatory for client-side aggregations. It can be a function, in which case, it will be called to compute the initial value for the aggregation. Otherwise, the initial value will be used as is.\n- `field` - the field to aggregate on. Optional - if not specified, make sure you specify `getter`\n- `getter`: `(data:T)=> any` - a getter function, called with the current `data` object.\n- `reducer`: `string | (accumulator, value, data: T) => any` - either a string (for server-side aggregations) or a mandatory aggregation function for client-side aggregations.\n- `done`: `(accumulator, arr: T[]) => any` - a function that is called to finish the aggregation after all values have been accumulated. The function should return the final value of the aggregation. Only used for client-side aggregations.\n- `name` - useful especially in combination with , as it will be used as the pivot column header.\n- `pivotColumn` - if specified, will configure the pivot column generated for this aggregation. This object has the same shape as a normal column, but supports an extra `inheritFromColumn` property, which can either be a `string` (a column id), or a `boolean`. The default behavior for a pivot column is to inherit the configuration of the initial column that has the same `field` property. `inheritFromColumn` allows you to specify another column to inherit from, or, if `false` is passed, the pivot column will not inherit from any other column.\n\n\n\n```ts files=[\"groupBy-example.page.tsx\",\"columns.ts\"]\n\n```\n\n\n\nAggregation reducers can be used in combination with grouping and pivoting. The example below shows aggregations used with server-side pivoting\n\n\n\n```ts file=\"$DOCS/learn/grouping-and-pivoting/pivoting/remote-pivoting-example.page.tsx\"\n\n```\n\n\n\nPivot columns generated for aggregations will inehrit from initial columns - the example shows how to leverage this behavior and how to extend it\n\n\n\n```ts file=\"$DOCS/learn/grouping-and-pivoting/pivoting/pivot-column-inherit-example.page.tsx\"\n\n```\n\n\n\n\n\n DATA_TYPE[]|Promise\">\n\n> Specifies the data the component is bound to.\n\nCan be one of the following:\n\n- an array of the bound type - eg: `Employee[]`\n- a Promise tha resolves to an array like the above\n- a function that returns an any of the above\n\n\n\nIf the `data` prop is a function, it will be called with an object of type . Click to see more details.\n\n\n\n\n\n```ts file=\"data-example.page.tsx\"\n\n```\n\n\n\n\n\nIt's important to note you can re-fetch data by changing the reference you pass as the `data` prop to the `` component. Passing another `data` function, will cause the component to re-execute the function and thus load new data.\n\n\n\n\n\n```ts files=[\"$DOCS/learn/working-with-data/refetch-example.page.tsx\",\"$DOCS/learn/working-with-data/columns.ts\"]\n\n```\n\n\n\n\n\n\n\n> Uncontrolled prop used for filtering. Can be used for both [client-side](/docs/learn/filtering/filtering-client-side) and [server-side](/docs/learn/filtering/filtering-server-side) filtering.\n\nIf you want to show the column filter editors, you have to either specify this property, or the controlled - even if you have no initial filters. For no initial filters, use `defaultFilterValue=[]`.\n\nFor the controlled version, and more details on the shape of the objects in the array, see .\n\n\n\n```ts file=\"defaultFilterValue-example.page.tsx\"\n\n```\n\n\n\n\n\nYou can control the visibility of the column filters by using the prop.\n\n\n\n\n\n\n\n> Describes the selected row(s) in the `DataSource`\n\nSee more docs in the controlled version of this prop, \n\nFor single selection, the prop will be of type: `number | string | null`. Use `null` for empty selection in single selection mode.\n\nFor multiple selection, the prop will have the following shape:\n\n```ts\nconst rowSelection = {\n selectedRows: [3, 6, 100, 23], // those specific rows are selected\n defaultSelection: false, // all rows deselected by default\n};\n\n// or\nconst rowSelection = {\n deselectedRows: [3, 6, 100, 23], // those specific rows are deselected\n defaultSelection: true, // all other rows are selected\n};\n\n// or, for grouped data - this example assumes groupBy=continent,country,city\nconst rowSelection = {\n selectedRows: [\n 45, // row with id 45 is selected, no matter the group\n ['Europe', 'France'], // all rows in Europe/France are selected\n ['Asia'], // all rows in Asia are selected\n ],\n deselectedRows: [\n ['Europe', 'France', 'Paris'], // all rows in Paris are deselected\n ],\n defaultSelection: false, // all other rows are selected\n};\n```\n\nFor using group keys in the selection value, see related \n\n\n\n```ts file=\"$DOCS/reference/uncontrolled-multiple-row-selection-example.page.tsx\"\n\n```\n\n\n\n\n\n|DataSourceSingleSortInfo[]|null\">\n\n> Information for sorting the data. This is an uncontrolled prop.\n\nFor detailed explanations, see (controlled property).\n\n\n\nWhen you provide a `defaultSortInfo` prop and the sorting information uses a custom sortType, make sure you specify that as the `type` property of the sorting info object.\n\n```tsx\ndefaultSortInfo={{\n field: 'color',\n dir: 1,\n // note this custom sort type\n type: 'color',\n}}\n```\n\nYou will need to have a property for that type in your object as well.\n\n```tsx\nsortTypes={{\n color: (a, b) => //...\n}}\n```\n\n\n\n\n\n```ts file=\"$DOCS/learn/sorting/local-uncontrolled-single-sorting-example-with-remote-data.page.tsx\"\n\n```\n\n\n\n\n\n```ts file=\"./customSortType-with-uncontrolled-sortInfo-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n> The delay in milliseconds before the filter is applied. This is useful when you want to wait for the user to finish typing before applying the filter.\n\nThis is especially useful in order to reduce the number of requests sent to the server, when remote filtering is used.\n\nIf not specified, defaults to `200` milliseconds. This means, any changes to the column filters, that happen inside a 200ms window (or the current value of ), will be debounced and only the last value will be sent to the server.\n\n\n\nIf you want to prevent debouncing/batching filter values, you can set to `0`.\n\n\n\n\n\n\n\n> The delay in milliseconds to wait before mutations are applied. This is useful to batch multiple mutations together.\n\nIf not specified, a `requestAnimationFrame` will be used to batch mutations.\n\nThe following mutative operations are batched:\n \n - \n - \n - \n - \n - \n - \n - \n - \n - \n - \n - \n - \n\n\n\n DATA_TYPE | boolean\">\n\n> A function to be used for filtering a `TreeDataSource`.\n\nThe function should return a boolean value or a data object.\n\n- when returning `false` the current data object will be filtered out.\n- when returning `true`, the current data object will be included in the filtered data, with no changes.\n- when returning a data object, the object will be used instead of the current data object for the row. This means that you can modify the data object to only include some of its children (which match a specific criteria)\n\n\n\nThe `treeFilterFunction` is called with an object that has a `filterTreeNode` function property. This function is a helper function you can use to continue the filtering further down the tree on the current (non-leaf) node.\n\nThis function will call the filtering function for each child of the current node. If all the children are filtered out, the current node will be filtered out as well. If there are any children that match the criteria, a clone of the current node will be returned with only the matching children.\n\nYou can opt to not use this helper function, and instead implement your own filtering logic. In this case, make sure you don't mutate data objects but rather return cloned versions of them.\n\n\n\n\n\n```ts file=tree-filter-function-example.page.tsx\n\n```\n\n\n\n\n\n boolean\">\n\n> A function to be used for client-side filtering.\n\nUsing this function will not show any special filtering UI for columns.\n\n\n\nFor filtering when using a `TreeGrid`, see .\n\n\n\n\n\n\n\nLoads data from remote location but will only show rows that have `id > 100`.\n\n\n\n```ts file=\"custom-filter-function-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n> Explicitly configures where filtering will take place and if changes in the should trigger a reload of the data source - applicable when is a function.\n Replaces the deprecated \n\n- `false` (the default) - filtering will be done on the client side and the function will not be invoked again.\n- `true` - filtering will be done on the server side - the function will be called with an object that includes the `filterValue` property, so it can be sent to the server\n\n\n\n\n\n> Explicitly configures where filtering will take place. Update to use the prop.\n\n- `'local'` - filtering will be done on the client side\n- `'remote'` - filtering will be done on the server side - the function will be called with an object that includes the `filterValue` property, so it can be sent to the server\n\n\n\n\">\n\n> Specifies the available types of filters for the columns.\n\nA filter type is a concept that defines how a certain type of data is to be filtered.\nA filter type will have a key, used to define the filter in the `filterTypes` object, and also the following properties:\n\n- `label`\n- `emptyValues` - an array of values considered to be empty values - when any of these values is used in the filter, the filter will match all records.\n- `operators` - an array of operator this filter type supports\n- `defaultOperator` - the default operator for the filter type\n- `components` - an object that describes the custom components to be used for the filter type\n - `FilterEditor` - a custom filter editor component for this filter type\n - `FilterOperatorSwitch` - a custom component that is displayed at the left of the `FilterEditor` and can be used for switching between operators - only needed for very very advanced use-cases.\n\nLet's imagine you have a `DataSource` with developers, each with a `salary` column, and for that column you want to allow `>`, `>=`, `<` and `<=` comparisons (operators).\n\nFor this, you would define the following filter type:\n\n```tsx\nconst filterTypes = {\n income: {\n label: 'Income',\n emptyValues: ['', null, undefined],\n defaultOperator: 'gt',\n operators: [\n {\n name: 'gt',\n label: 'Greater than',\n fn: ({ currentValue, filterValue }) => {\n return currentValue > filterValue;\n },\n },\n {\n name: 'gte',\n //...\n },\n {\n name: 'lt',\n //...\n },\n {\n name: 'lte',\n //...\n },\n ],\n },\n};\n```\n\n\n\nEach operator for a certain filter type needs to at least have a `name` and `fn` defined. The `fn` property is a function that will be called when client-side filtering is enabled, with an object that has the following properties:\n\n- `currentValue` - the cell value of the current row for the column being filtered\n- `filterValue` - the value of the filter editor\n- `emptyValues` - the array of values considered to be empty values for the filter type\n- `data` - the current row data object - `typeof DATA_TYPE`\n- `index` - the index of the current row in the table - `number`\n- `dataArray` - the array of all rows originally in the table - `typeof DATA_TYPE[]`\n- `field?` - the field the current column is bound to (can be undefined if the column is not bound to a field)\n\n\n\n\n\n\n\nThe `salary` column has a custom filter type, with the following operators: `gt`, `gte`, `lt` and `lte`.\n\n\n\n```ts file=\"filter-types-example.page.tsx\"\n\n```\n\n\n\n\n\nBy default, the `string` and `number` filter types are available. You can import the default filter types like this:\n\n```ts\nimport { defaultFilterTypes } from '@infinite-table/infinite-react';\n```\n\nIf you want to make all your instances of `InfiniteTable` have new operators for those filter types, you can simply mutate the exported `defaultFilterTypes` object.\n\n\n\n\n\nThe `string` columns have a new `Not includes` operator.\n\n\n\n```ts file=\"default-filter-types-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\nWhen you specify new , the default filter types of `string` and `number` are still available - unless the new object contains those keys and overrides them explicitly.\n\n\n\nThe current implementation of the default filter types is the following:\n\n```tsx\nexport const defaultFilterTypes: Record> = {\n string: {\n label: 'Text',\n emptyValues: [''],\n defaultOperator: 'includes',\n components: {\n FilterEditor: StringFilterEditor,\n },\n operators: [\n {\n name: 'includes',\n components: {\n Icon: // custom icon as a React component ...\n },\n label: 'includes',\n fn: ({ currentValue, filterValue }) => {\n return (\n typeof currentValue === 'string' &&\n typeof filterValue == 'string' &&\n currentValue.toLowerCase().includes(filterValue.toLowerCase())\n );\n },\n },\n {\n label: 'Equals',\n components: {\n Icon: // custom icon as a React component ...\n },\n name: 'eq',\n fn: ({ currentValue: value, filterValue }) => {\n return typeof value === 'string' && value === filterValue;\n },\n },\n {\n name: 'startsWith',\n components: {\n Icon: // custom icon as a React component ...\n },\n label: 'Starts With',\n fn: ({ currentValue: value, filterValue }) => {\n return value.startsWith(filterValue);\n },\n },\n {\n name: 'endsWith',\n components: {\n Icon: // custom icon as a React component ...\n },\n label: 'Ends With',\n fn: ({ currentValue: value, filterValue }) => {\n return value.endsWith(filterValue);\n },\n },\n ],\n },\n number: {\n label: 'Number',\n emptyValues: ['', null, undefined],\n defaultOperator: 'eq',\n components: {\n FilterEditor: NumberFilterEditor,\n },\n operators: [\n {\n label: 'Equals',\n components: {\n Icon: // custom icon as a React component ...\n },\n name: 'eq',\n fn: ({ currentValue, filterValue }) => {\n return currentValue == filterValue;\n },\n },\n {\n label: 'Not Equals',\n components: {\n Icon: // custom icon as a React component ...\n },\n name: 'neq',\n fn: ({ currentValue, filterValue }) => {\n return currentValue != filterValue;\n },\n },\n {\n name: 'gt',\n label: 'Greater Than',\n components: {\n Icon: // custom icon as a React component ...\n },\n fn: ({ currentValue, filterValue, emptyValues }) => {\n if (emptyValues.includes(currentValue)) {\n return true;\n }\n return currentValue > filterValue;\n },\n },\n {\n name: 'gte',\n components: {\n Icon: // custom icon as a React component ...\n },\n label: 'Greater Than or Equal',\n fn: ({ currentValue, filterValue, emptyValues }) => {\n if (emptyValues.includes(currentValue)) {\n return true;\n }\n return currentValue >= filterValue;\n },\n },\n {\n name: 'lt',\n components: {\n Icon: // custom icon as a React component ...\n },\n label: 'Less Than',\n fn: ({ currentValue, filterValue, emptyValues }) => {\n if (emptyValues.includes(currentValue)) {\n return true;\n }\n return currentValue < filterValue;\n },\n },\n {\n name: 'lte',\n components: {\n Icon: // custom icon as a React component ...\n },\n label: 'Less Than or Equal',\n fn: ({ currentValue, filterValue, emptyValues }) => {\n if (emptyValues.includes(currentValue)) {\n return true;\n }\n return currentValue <= filterValue;\n },\n }\n ],\n },\n };\n```\n\n\n\n\n\n> A custom React component to be used as an editor for the current filter type\n\nEvery filter type can define the following `components`\n\n- `FilterEditor` - a React component to be used as an editor for the current filter type\n- `FilterOperatorSwitch` - a custom component that is displayed at the left of the `FilterEditor` and can be used for switching between operators - only needed for very very advanced use-cases.\n\n\n\nFilter type operators can override the `FilterEditor` component - they can specify the following components:\n\n- `FilterEditor` - if specified, it overrides the `FilterEditor` of the filter type\n- `Icon` - a React component to be used as an icon for the operator - displayed by the menu triggered when clicking on the `FilterOperatorSwitch` component\n\n\n\n\n\n\n\nThe `canDesign` column is using a custom `bool` filter type with a custom filter editor.\n\nThe checkbox has indeterminate state, which will match all values in the data source.\n\n\n\n```ts file=\"$DOCS/reference/hooks/custom-filter-editor-hooks-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n> Controlled prop used for filtering. Can be used for both [client-side](/docs/learn/filtering/filtering-client-side) and [server-side](/docs/learn/filtering/filtering-server-side) filtering.\n\nFor the uncontrolled version, see \n\nIf you want to show the column filter editors, you have to either specify this property, or the uncontrolled - even if you have no initial filters. For no initial filters, use `filterValue=[]`.\n\nThe objects in this array have the following shape:\n\n- `filter` - an object describing the filter\n - `filter.value` - the value to filter by\n - `filter.type` - the current type of the filter (eg: `string`, `number` or another custom type you specify in the filterTypes prop)\n - `filter.operator` - the name of the operator being applied\n- `field` - the field being filtered - generally matched with a column. This is optional, as some columns can have no field.\n- `id` - the id of the column being filtered. This is optional - for columns bound to a field, the `field` should be used instead of the `id`.\n- `disabled` - whether this filter is applied or not\n\n\n\n```ts file=\"onFilterValueChange-example.page.tsx\"\n\n```\n\n\n\n\n\nYou can control the visibility of the column filters by using the prop.\n\n\n\n\n\n\n\n> Whether the datasource will load data lazily - useful for server-side grouping and pivoting. If set to `true` or to an object (with `batchSize` property), the prop must be a function that returns a promise.\n\n\n\n```ts file=\"$DOCS/learn/grouping-and-pivoting/pivoting/remote-pivoting-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n> Controls the expand/collapse state of group rows, when is used\n\n\nSee related , and \n\n\n```tsx title=\"Specifying the state for group rows\"\nconst groupRowsState: DataSourcePropGroupRowsStateObject = {\n collapsedRows: true,\n expandedRows: [['Mexico'], ['Mexico', 'backend'], ['India']],\n};\n```\n\nThe two properties in this object are `collapsedRows` and `expandedRows`, and each can have the following values:\n - `true` - meaning that all groups have this state\n - an array of arrays - representing the exceptions to the default value\n\n\nSo if you have `collapsedRows` set to `true` and then `expandedRows` set to `[['Mexico'], ['Mexico', 'backend'], ['India']]` then all rows are collapsed by default, except the rows specified in the `expandedRows`.\n\n\n\n```ts file=\"./group-rows-state-controlled-example.page.tsx\"\n```\n\n\n\n\n\n void\">\n\n> Callback prop when the changes.\n\nSee related and \n\nThis function is called with an object that's an instance of , when the user interacts with group rows and expands/collapses them.\n\nIf you want to get a plain object from this instance, call the `.getState()` method.\n\nSee reference to find out all the utility methods this instance gives you.\n\n\n\n\n\n> Specifies the initial expand/collapse state of group rows, when is used\n\n\nFor the controlled version, see related .\n\n\n```tsx title=\"Specifying the initial state for group rows\"\nconst defaultGroupRowsState: DataSourcePropGroupRowsStateObject = {\n expandedRows: true,\n collapsedRows: [['Mexico'], ['Mexico', 'backend'], ['India']],\n};\n```\n\nThe two properties in this object are `collapsedRows` and `expandedRows`, and each can have the following values:\n - `true` - meaning that all groups have this state\n - an array of arrays - representing the exceptions to the default value\n\n\nSo if you have `expandedRows` set to `true` and then `collapsedRows` set to `[['Mexico'], ['Mexico', 'backend'], ['India']]` then all rows are expanded by default, except the rows specified in the `collapsedRows`.\n\n\n```ts file=\"./group-rows-initial-state-example.page.tsx\"\n```\n\n\n\n\n\n\n> An array of objects with `field` properties, that control how rows are being grouped.\n\nEach item in the array can have the following properties:\n\n- field - `keyof DATA_TYPE`\n- column - config object for the group column - see .\n\n\n\nWhen using groupRenderStrategy=\"multi-column\", it can be very useful for each group to configure it's own column - use for this.\n\n\nSee for the type definition.\n\n\n\n```ts files=[\"groupBy-example.page.tsx\",\"columns.ts\"]\n\n```\n\n\n\n\n\n[]\">\n\n> An array of objects with `field` properties that control how pivoting works. Pivoting is very often associated with aggregations, so see related for more details.\n\nEach item in the array can have the following properties:\n\n- field - `keyof DATA_TYPE`\n- column - config object or function for generated pivot columns.\n\n\n\nFor more details on the type of the items in this array prop, see .\n\n\n\n\n\n```ts file=\"$DOCS/learn/grouping-and-pivoting/pivoting/pivoting-customize-column-example.page.tsx\"\n\n```\n\n\n\n\n \n>\">\n\n> An object that configures how the column for the current group should look like\n\nIf is specified, it overrides this property (the objects actually get merged, with `groupColumn` having higher priority and being merged last).\n\n\n\nIf you are using a groupRenderStrategy=\"single-column\", then using `groupBy.column` should not be used, as you could have many groups with conflicting column configuration.\n\nIn this case, use the prop.\n\n\n\n\n\n\n\nThis example uses `groupBy.column` to configure the generated columns corresponding to each group.\n\n\n\n```ts files=[\"groupBy-multi-with-column-example.page.tsx\",\"columns.ts\"]\n\n```\n\n\n\n\n\n\n\n> Whether the component should use live pagination.\n\nUse this in combination with and \n\n\n\n```ts file=\"$DOCS/learn/working-with-data/live-pagination-example.page.tsx\"\n\n```\n\n\n\n\n\n void\">\n\n> Callback prop to be called when the data changes via the DataSource API.\n\nCalled when any of the following methods have been called in the `DataSource` api\n\n- \n- \n\n- \n- \n\n- \n- \n\n- \n- \n\n- \n- \n\nThis callback is called with an object that has the following properties:\n\n- `primaryKeyField` - the field configured as the primary key for the ``\n- `mutations` - a `Map` with mutations. The keys in the map are primary keys of the mutated data\n\nThe values in the mutations are object descriptors of mutations, that have the following shape:\n\n- `type`: `'insert'|'update'|'delete'`\n- `originalData`: `DATA_TYPE | null` - the original data before the mutation. In case of `insert`, it will be `null`\n- `data`: `Partial` - the updates to be performed on the data. In case of `delete`, it will be `undefined`. This is an object that will contain the primary key, and the updated values for the data (not necessarily the full object, except for `insert`, where it will be of type `DATA_TYPE`).\n\n\n\nstring|number)\" defaulValue={undefined}>\n\n> A cursor value for live pagination. A good value for this is the id of the last item in the array. It can also be defined as a function\n\nUse this in combination with and \n\n\n\nWhen this is a function, it is called with a parameter object that has the following properties:\n\n- `array` - the current array of data\n- `lastItem` - the last item in the array\n- `length` - the length of the data array\n\n\n\n\n\n```ts file=\"$DOCS/learn/working-with-data/live-pagination-example.page.tsx\"\n\n```\n\n\n\n\n\n)=>void\">\n\n> A function to be called when data-related state changes.\n\nCan be used to implement \n\nThe function is called with an object that has the following properties:\n\n- `sortInfo` - current sort information - see for details\n- `groupBy` - current grouping information - see for details\n- `filterValue` - current filtering information - see for details\n- `livePaginationCursor` - the value for the live pagination cursor - see for details\n- `changes` - an object that can help you figure out what change caused `onDataParamsChange` to be called.\n\n\n\n```ts file=\"$DOCS/learn/working-with-data/live-pagination-example.page.tsx\"\n\n```\n\n\n\n\n void\">\n\n> Callback prop called when the changes.\n\nThis might not be called immediately, as there might be a set.\n\n\n\n```ts file=\"onFilterValueChange-example.page.tsx\"\n\n```\n\n\n\n\n\n void\">\n\n> A function to be called when the changes.\n\nAlso see related .\n\n\n\n) => void\">\n\n> The callback that is called when the `DataSource` is ready. The [`dataSourceApi`](/docs/reference/datasource-api) is passed as the first argument.\n\n\n\n void\">\n\n> A function to be called when the changes.\n\n\n\n\n\nUse your mouse to select/deselect cells.\n\n\n\n```ts file=\"$DOCS/learn/selection/controlled-cell-selection-example.page.tsx\"\n\n```\n\n\n\n\n\n void\">\n\n> A function to be called when the changes.\n\n\n\n\n\nUse your mouse or keyboard (press the spacebar) to select/deselect a single row.\n\n\n\n```ts file=\"$DOCS/reference/controlled-single-row-selection-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\nThis example shows how you can use multiple row selection with a predefined controlled value.\n\nGo ahead and select some groups/rows and see the selection value adjust.\n\nThe example also shows how you can use the `InfiniteTableApi` to retrieve the actual ids of the selected rows.\n\n\n\n```ts file=\"$DOCS/reference/controlled-multi-row-selection-example.page.tsx\"\n\n```\n\n\n\n\n void\">\n\n> Called when sorting changes on the DataSource.\n\nThe sorting can change either via a user interaction or by calling an API method (from the [root API](api) or the [Column API](column-api)).\n\nSee related for controlled sorting and for uncontrolled sorting.\n\n\n\n\n\n> A value that can be used to trigger a re-fetch of the data.\n\nBy updating the value of this prop (eg: you can use it as a counter, and increment it) the `` component reloads it's if it's defined as a function. More specifically, the `data` function is called again and the result will replace the current data.\n\n\n\n\n\nThis example shows how you can use the `refetchKey` to trigger reloading the data\n\n\n\n```ts file=\"refetchKey-example.page.tsx\"\n\n```\n\n\n \n\n\n\n\n> Describes the selected row(s) in the `DataSource`\n\nFor single selection, the prop will be of type: `number | string | null`. Use `null` for empty selection in single selection mode.\n\nFor multiple selection, the prop will have the following shape:\n\n```ts\nconst rowSelection = {\n selectedRows: [3, 6, 100, 23], // those specific rows are selected\n defaultSelection: false, // all rows deselected by default\n};\n\n// or\nconst rowSelection = {\n deselectedRows: [3, 6, 100, 23], // those specific rows are deselected\n defaultSelection: true, // all other rows are selected\n};\n\n// or, for grouped data - this example assumes groupBy=continent,country,city\nconst rowSelection = {\n selectedRows: [\n 45, // row with id 45 is selected, no matter the group\n ['Europe', 'France'], // all rows in Europe/France are selected\n ['Asia'], // all rows in Asia are selected\n ],\n deselectedRows: [\n ['Europe', 'France', 'Paris'], // all rows in Paris are deselected\n ],\n defaultSelection: false, // all other rows are selected\n};\n```\n\nFor using group keys in the selection value, see related \n\n\n\n\n\nUse your mouse or keyboard (press the spacebar) to select/deselect a single row.\n\n\n\n```ts file=\"$DOCS/reference/controlled-single-row-selection-example.page.tsx\"\n\n```\n\n\n\n\n\nWhen is being used - this means not all available groups/rows have actually been loaded yet in the dataset - we need a way to allow you to specify that those possibly unloaded rows/groups are selected or not. In this case, the `rowSelection.selectedRows`/`rowSelection.deselectedRows` arrays should not have row primary keys as strings/numbers, but rather rows/groups specified by their full path (so should be set to `true`).\n\n```ts {6}\n// this example assumes groupBy=continent,country,city\nconst rowSelection = {\n selectedRows: [\n // row with id 45 is selected - we need this because in the lazyLoad scenario,\n // not all parents might have been made available yet\n ['Europe','Italy', 'Rome', 45],\n ['Europe','France'], // all rows in Europe/France are selected\n ['Asia'] // all rows in Asia are selected\n ]\n deselectedRows: [\n ['Europe','Italy','Rome'] // all rows in Rome are deselected\n // but note that row with id 45 is selected, so Rome will be rendered with an indeterminate selection state\n ],\n defaultSelection: false // all other rows are selected\n}\n```\n\nIn the example above, we know that there are 3 groups (`continent`, `country`, `city`), so any item in the array that has a 4th element is a fully specified leaf node. While lazy loading, we need this fully specified path for specific nodes, so we know which group rows to render with indeterminate selection.\n\n\n\n\n\nThe prop can be used for both lazy and non-lazy `DataSource` components.\n\n\n\n\n\n\n\nThis example shows how you can use multiple row selection with a predefined controlled value.\n\nGo ahead and select some groups/rows and see the selection value adjust.\n\nThe example also shows how you can use the `InfiniteTableApi` to retrieve the actual ids of the selected rows.\n\n\n\n```ts file=\"$DOCS/reference/controlled-multi-row-selection-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n> Specifies the type of selection that should be enabled.\n\n\n\n\nRead more on row selection (`multi-row` and `single-row`).\n\n\n\n\n\nRead more on cell selection (`multi-cell` and `single-cell`).\n\n\n\n\n\n\n\n```ts file=\"selectionMode-example.page.tsx\"\n\n```\n\n\n\n\n\n[], arr: T[]) => T[]\">\n\n> Custom sorting function to replace the `multisort` function used by default.\n\nThe function specified in the prop is called with the as the first argument and the data array as the second. It should return a sorted array, as per the it was called with.\n\n\n\nWhen is specified, will be forced to `false`, as the sorting is done in the browser.\n\n\n\n\nThe `@infinite-table/infinite-react` package exports a `multisort` function - this is the default function used for local sorting.\n\n```ts\nimport { multisort } from '@infinite-table/infinite-react';\n\nconst arr: Developer[] = [\n /*...*/\n];\nconst sortInfo = [\n {\n field: 'age',\n dir: -1,\n },\n {\n field: 'name',\n dir: 1,\n },\n];\nmultisort(sortInfo, arr);\n```\n\nIf you want to implement your own custom sort function, the `multisort` fn is a good starting point you can use.\n\n\n\n\n\n```ts file=\"$DOCS/reference/datasource-props/local-sortFunction-single-sorting-example-with-local-data-example.page.tsx\"\n\n```\n\n\n\n\n\n|DataSourceSingleSortInfo[]|null\">\n\n> Information for sorting the data. This is a controlled prop.\n\nAlso see related (uncontrolled version), , and .\n\nSorting can be single (only one field/column can be sorted at a time) or multiple (multiple fields/columns can be sorted at the same time). Therefore, this property an be an array of objects or a single object (or null) - the shape of the objects (of type `DataSourceSingleSortInfo`)is the following.\n\n- `dir` - `1 | -1` - the direction of the sorting\n- `field`? - `keyof DATA_TYPE` - the field to sort\n- `id`? - `string` - if you don't sort by a field, you can specify an id of the column this sorting is bound to. Note that columns have a valueGetter, which will be used when doing local sorting and the column is not bound to an exact field.\n- `type` - the sort type - one of the keys in - eg `\"string\"`, `\"number\"` - will be used for local sorting, to provide the proper comparison function.\n\nWhen you want to use multiple sorting, but have no default sort order/information, use `[]` (the empty array) to denote multiple sorting should be enabled.\n\nIf no `sortInfo` is provided, by default, when clicking a sortable column, single sorting will be applied.\n\n\n\nFor configuring if a column is sortable or not, see and . By default, all columns are sortable.\n\n\n\n\n\n```ts file=\"$DOCS/learn/sorting/remote-controlled-multi-sorting-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n\n\n> Specifies if changes in the should trigger a reload of the data source - applicable when is a function. Replaces the deprecated .\n\nSee related and .\n\nWhen set to `false` (the default), the data is sorted locally (in the browser) after the data-source is loaded. When set to `true`, the data should be sorted by the server (or by the data-source function that serves the data).\n\nSee [the Sorting page](/docs/learn/sorting/overview) for more details.\n\nFor configuring the sorting behavior when multiple sorting is enabled, see .\n\n\n\n\n\n> Specifies which changes in the data-related props should trigger a reload of the data source - applicable when is a function.\n\nSee .\nSee .\nSee .\nSee .\n\n\n\n\n\n\n> Specifies where the sorting should be done. Use instead.\n\nSee related and .\n\nWhen set to `'local'`, the data is sorted locally (in the browser) after the data-source is loaded. When set to `'remote'`, the data should be sorted by the server (or by the data-source function that serves the data).\n\nSee [the Sorting page](/docs/learn/sorting/overview) for more details.\n\nFor configuring the sorting behavior when multiple sorting is enabled, see .\n\n\n\n number)>\">\n\n> Describes the available sorting functions used for local sorting. The object you provide will be merged into the default sort types.\n\nCurrently there are two `sortTypes` available:\n\n- `\"string\"`\n- `\"number\"`\n- `\"date\"`\n\nThose values can be used for the column.sortType and column.dataType properties.\n\n```ts\n// default implementation\nconst sortTypes = {\n string: (a, b) => a.localeCompare(b),\n number: (a, b) => a - b,\n date: (a, b) => a - b,\n};\n```\n\nWhen a column does not explicitly specify the column.sortType, the column.dataType will be used instead. And if no column.dataType is defined, it will default to `string`.\n\nYou can add new sort types to the DataSource and InfiniteTable components by specifying this property - the object will be merged into the default sort types.\n\n\n\n```ts file=\"./sortTypes-example.page.tsx\"\n\n```\n\n\n\n\n\nIn this example, for the `\"color\"` column, we specified column.sortType=\"color\" - we could have passed that as `column.dataType` instead, but if the grid had filtering, it wouldn't know what filters to use for \"color\" - so we usedcolumn.sortType to only change how the data is sorted.\n\n\n\n\n\n\n\n> Specifies whether contains group keys or only row ids/primary keys.\n\nWhen this is `true`, you might want to use the [getSelectedPrimaryKeys](./selection-api#getSelectedPrimaryKeys) method.\n\n\n\n\n\nThis example shows how you can use have row selection with group keys instead of just the primary keys of rows.\n\n\n\n```ts file=\"$DOCS/reference/controlled-multi-row-selection-example-with-group-keys.page.tsx\"\n\n```\n\n\n\n\n\n\n" }, { "filePath": "/docs/reference/row-selection-api/index", @@ -1163,7 +1163,7 @@ }, "excerpt": "```tsx title=\"Configuring the selection mode to be 'multi-row'\"", "readingTime": "7 min read", - "content": "\n```tsx title=\"Configuring the selection mode to be 'multi-row'\"\n\n\n// can be \"single-row\", \"multi-row\", \"multi-cell\" or false\n```\n\n\n\nTo enable multi-row selection, you need to specify selectionMode=\"multi-row\" on the `` component.\n\n\n\nYou can retrieve the row selection api by reading it from the `api.rowSelectionApi` property.\n\n```tsx {4}\n\nconst onReady = ({api}: {api:InfiniteTableApi}) => {\n // do something with it\n api.rowSelectionApi.selectGroupRow(['USA'])\n}\n\n\n columns={[...]}\n onReady={onReady}\n/>\n```\n\nSee the [Infinite Table API page](/docs/reference/api) for the main API.\nSee the [Infinite Table Cell Selection API page](/docs/reference/cell-selecti-api) for the cell selection API.\nSee the [Infinite Table Column API page](/docs/reference/column-api) for the column API.\nSee the [Infinite Table Row Detail API page](/docs/reference/row-detail-api) for the row detail API (when master-detail is configured).\n\n\n\n\n\n> Boolean getter to report whether all the rows are selected or not\n\n\n\n void\">\n\n> Deselects the group row that is identified by the given group keys. Only makes sense when the DataSource is grouped.\n\nCalling this method triggers a call to DataSource.onRowSelectionChange.\n\nFor selecting a group row, see related [selectGroupRow](#selectGroupRow).\n\n\n\nMost often, you don't need to use this imperative way of selecting group rows. Simply update the DataSource.rowSelection to exclude the group row from the selection.\n\n\n\n\n\n boolean\">\n\n> Deselects the specified row. Optionally provide the group keys, if you have access to them.\n\nSee note from [isRowSelected](#isRowSelected) for whether you need to provide the `groupKeys` or not.\n\nCalling this method triggers a call to DataSource.onRowSelectionChange.\n\nFor selecting the row, see related [selectRow](#selectRow).\n\n\n\nMost often, you don't need to use this imperative way of deselecting rows. Simply update the DataSource.rowSelection to remove the row you want from the selection.\n\n\n\n\n\n void\">\n\n> Deselects all the rows in the DataSource.\n\nCalling this method triggers a call to DataSource.onRowSelectionChange.\n\nFor selecting all rows, see related [selectAll](#selectAll).\n\n\n\nMost often, you don't need to use this imperative way of deselecting all rows. Simply update the DataSource.rowSelection (when multiple row selection is enabled) to a value of\n\n```tsx\n{ defaultSelection: false, selectedRows: []}\n```\n\n\n\n\n\n true|false|null\">\n\n> Returns the state of a group row - only applicable when the DataSource is grouped\n\nThe returned values can be:\n\n- `true` - the group row and all its children are selected, at any level of nesting\n- `false` - the group row and all its children are deselected, at any level of nesting\n- `null` - the group row has some (not all) children selected, at any level of nesting\n\nBaiscally, `true` means the group row and all children are selected, `false` means the group row is not selected and doesn't have any selected children, while `null` is the indeterminate state, where just some (but not all) of the children of the group are selected.\n\nIf you provide a the value of a `rowSelection`, it will be used as the source of truth for selection. If no value for `rowSelection` is provided, it will use the current row selection.\n\n\n\nIf you don't provide a value for the `rowSelection` and are calling this method in the callback prop, you might be one step behind the selection. In such a case, make sure you pass to this function the value you receive in the callback.\n\n\n\n\n\n (string|number)[]\">\n\n> Retrieves the ids (primary keys) of the selected rows, when the selection contains group keys instead of primary keys (so when is `true` and the DataSource is grouped).\n\nIf you provide a the value of a `rowSelection`, it will be used as the source of truth for retrieving the row ids. If no value for `rowSelection` is provided, it will use the current row selection.\n\n\n\nThis will not work properly when the `DataSource` is configured with lazy loading, since it cannot give you primary keys of rows not yet loaded.\n\n\n\n\n\nIf you don't provide a value for the `rowSelection` and are calling this method in the callback prop, you might be one step behind the selection. In such a case, make sure you pass to this function the value you receive in the callback.\n\n\n\n\n\n\n\nThis example shows how you can use getSelectedPrimaryKeys with multiple row selection to retrieve the actual ids of the selected rows.\n\n\n\n```ts file=\"$DOCS/reference/controlled-multi-row-selection-example-with-group-keys.page.tsx\"\n\n```\n\n\n\n\n\n boolean\">\n\n> Checks if a row specified by its primary key is selected or not. Optionally provide the group keys, if you have access to them.\n\n\nThe group keys are not mandatory, and they are useful only when the DataSource is grouped.\n\nEven if you don't pass them, the component will try to retrieve them from its internal state - note though that in lazy-load scenarios, not all rows/groups may have been loaded, so in this case, you have to make sure you provide the `groupKeys` when calling this method.\n\n\n\n\n\n boolean\">\n\n> Checks if a row specified by its primary key is deselected or not. Optionally provide the group keys, if you have access to them.\n\nSee note from [isRowSelected](#isRowSelected)\n\n\n\n void\">\n\n> Selects all the rows in the DataSource.\n\nCalling this method triggers a call to DataSource.onRowSelectionChange.\n\nFor deselecting all rows, see related [deselectAll](#deselectAll).\n\n\n\nMost often, you don't need to use this imperative way of selecting rows. Simply update the DataSource.rowSelection (when multiple row selection is enabled) to a value of\n\n```tsx\n{ defaultSelection: true, deselectedRows: []}\n```\n\n\n\n\n\n void\">\n\n> Selects the group row that is identified by the given group keys. Only makes sense when the DataSource is grouped.\n\nCalling this method triggers a call to DataSource.onRowSelectionChange.\n\nFor deselecting a group row, see related [deselectGroupRow](#deselectGroupRow).\n\n\n\nMost often, you don't need to use this imperative way of selecting group rows. Simply update the DataSource.rowSelection to include the group row you want to select.\n\n\n\n\n\n boolean\">\n\n> Selects the specified row. Optionally provide the group keys, if you have access to them.\n\nSee note from [isRowSelected](#isRowSelected) for whether you need to provide the `groupKeys` or not.\n\nCalling this method triggers a call to DataSource.onRowSelectionChange.\n\nFor deselecting the row, see related [deselectRow](#deselectRow).\n\n\n\nMost often, you don't need to use this imperative way of selecting rows. Simply update the DataSource.rowSelection to include the row you want in the selection.\n\n\n\n\n\n void\">\n\n> Toggles the selection of the group row that is identified by the given group keys. Only makes sense when the DataSource is grouped.\n\nCalling this method triggers a call to DataSource.onRowSelectionChange.\n\nFor deselecting a group row, see related [deselectGroupRow](#deselectGroupRow).\nFor selecting a group row, see related [selectGroupRow](#selectGroupRow).\n\n\n\nMost often, you don't need to use this imperative way of selecting group rows. Simply update the DataSource.rowSelection to include the group row you want to select.\n\n\n\n\n\n boolean\">\n\n> Toggles the selection of the specified row. Optionally provide the group keys, if you have access to them.\n\nSee note from [isRowSelected](#isRowSelected) for whether you need to provide the `groupKeys` or not.\n\nCalling this method triggers a call to DataSource.onRowSelectionChange.\n\nFor deselecting the row, see related [deselectRow](#deselectRow).\nFor selecting the row, see related [selectRow](#selectRow).\nFor toggling the selection for a group row, see related [toggleGroupRowSelection](#toggleGroupRowSelection).\n\n\n\nMost often, you don't need to use this imperative way of selecting rows. Simply update the DataSource.rowSelection to include or exclude the given row.\n\n\n\n\n\n\n" + "content": "\n```tsx title=\"Configuring the selection mode to be 'multi-row'\"\n\n\n// can be \"single-row\", \"multi-row\", \"multi-cell\" or false\n```\n\n\n\nTo enable multi-row selection, you need to specify selectionMode=\"multi-row\" on the `` component.\n\n\n\nYou can retrieve the row selection api by reading it from the `api.rowSelectionApi` property.\n\n```tsx {4}\n\nconst onReady = ({api}: {api:InfiniteTableApi}) => {\n // do something with it\n api.rowSelectionApi.selectGroupRow(['USA'])\n}\n\n\n columns={[...]}\n onReady={onReady}\n/>\n```\n\nSee the [Infinite Table API page](/docs/reference/api) for the main API.\nSee the [Infinite Table Cell Selection API page](/docs/reference/cell-selection-api) for the cell selection API.\nSee the [Infinite Table Column API page](/docs/reference/column-api) for the column API.\nSee the [Infinite Table Row Detail API page](/docs/reference/row-detail-api) for the row detail API (when master-detail is configured).\n\n\n\n\n\n> Boolean getter to report whether all the rows are selected or not\n\n\n\n void\">\n\n> Deselects the group row that is identified by the given group keys. Only makes sense when the DataSource is grouped.\n\nCalling this method triggers a call to DataSource.onRowSelectionChange.\n\nFor selecting a group row, see related [selectGroupRow](#selectGroupRow).\n\n\n\nMost often, you don't need to use this imperative way of selecting group rows. Simply update the DataSource.rowSelection to exclude the group row from the selection.\n\n\n\n\n\n boolean\">\n\n> Deselects the specified row. Optionally provide the group keys, if you have access to them.\n\nSee note from [isRowSelected](#isRowSelected) for whether you need to provide the `groupKeys` or not.\n\nCalling this method triggers a call to DataSource.onRowSelectionChange.\n\nFor selecting the row, see related [selectRow](#selectRow).\n\n\n\nMost often, you don't need to use this imperative way of deselecting rows. Simply update the DataSource.rowSelection to remove the row you want from the selection.\n\n\n\n\n\n void\">\n\n> Deselects all the rows in the DataSource.\n\nCalling this method triggers a call to DataSource.onRowSelectionChange.\n\nFor selecting all rows, see related [selectAll](#selectAll).\n\n\n\nMost often, you don't need to use this imperative way of deselecting all rows. Simply update the DataSource.rowSelection (when multiple row selection is enabled) to a value of\n\n```tsx\n{ defaultSelection: false, selectedRows: []}\n```\n\n\n\n\n\n true|false|null\">\n\n> Returns the state of a group row - only applicable when the DataSource is grouped\n\nThe returned values can be:\n\n- `true` - the group row and all its children are selected, at any level of nesting\n- `false` - the group row and all its children are deselected, at any level of nesting\n- `null` - the group row has some (not all) children selected, at any level of nesting\n\nBaiscally, `true` means the group row and all children are selected, `false` means the group row is not selected and doesn't have any selected children, while `null` is the indeterminate state, where just some (but not all) of the children of the group are selected.\n\nIf you provide a the value of a `rowSelection`, it will be used as the source of truth for selection. If no value for `rowSelection` is provided, it will use the current row selection.\n\n\n\nIf you don't provide a value for the `rowSelection` and are calling this method in the callback prop, you might be one step behind the selection. In such a case, make sure you pass to this function the value you receive in the callback.\n\n\n\n\n\n (string|number)[]\">\n\n> Retrieves the ids (primary keys) of the selected rows, when the selection contains group keys instead of primary keys (so when is `true` and the DataSource is grouped).\n\nIf you provide a the value of a `rowSelection`, it will be used as the source of truth for retrieving the row ids. If no value for `rowSelection` is provided, it will use the current row selection.\n\n\n\nThis will not work properly when the `DataSource` is configured with lazy loading, since it cannot give you primary keys of rows not yet loaded.\n\n\n\n\n\nIf you don't provide a value for the `rowSelection` and are calling this method in the callback prop, you might be one step behind the selection. In such a case, make sure you pass to this function the value you receive in the callback.\n\n\n\n\n\n\n\nThis example shows how you can use getSelectedPrimaryKeys with multiple row selection to retrieve the actual ids of the selected rows.\n\n\n\n```ts file=\"$DOCS/reference/controlled-multi-row-selection-example-with-group-keys.page.tsx\"\n\n```\n\n\n\n\n\n boolean\">\n\n> Checks if a row specified by its primary key is selected or not. Optionally provide the group keys, if you have access to them.\n\n\nThe group keys are not mandatory, and they are useful only when the DataSource is grouped.\n\nEven if you don't pass them, the component will try to retrieve them from its internal state - note though that in lazy-load scenarios, not all rows/groups may have been loaded, so in this case, you have to make sure you provide the `groupKeys` when calling this method.\n\n\n\n\n\n boolean\">\n\n> Checks if a row specified by its primary key is deselected or not. Optionally provide the group keys, if you have access to them.\n\nSee note from [isRowSelected](#isRowSelected)\n\n\n\n void\">\n\n> Selects all the rows in the DataSource.\n\nCalling this method triggers a call to DataSource.onRowSelectionChange.\n\nFor deselecting all rows, see related [deselectAll](#deselectAll).\n\n\n\nMost often, you don't need to use this imperative way of selecting rows. Simply update the DataSource.rowSelection (when multiple row selection is enabled) to a value of\n\n```tsx\n{ defaultSelection: true, deselectedRows: []}\n```\n\n\n\n\n\n void\">\n\n> Selects the group row that is identified by the given group keys. Only makes sense when the DataSource is grouped.\n\nCalling this method triggers a call to DataSource.onRowSelectionChange.\n\nFor deselecting a group row, see related [deselectGroupRow](#deselectGroupRow).\n\n\n\nMost often, you don't need to use this imperative way of selecting group rows. Simply update the DataSource.rowSelection to include the group row you want to select.\n\n\n\n\n\n boolean\">\n\n> Selects the specified row. Optionally provide the group keys, if you have access to them.\n\nSee note from [isRowSelected](#isRowSelected) for whether you need to provide the `groupKeys` or not.\n\nCalling this method triggers a call to DataSource.onRowSelectionChange.\n\nFor deselecting the row, see related [deselectRow](#deselectRow).\n\n\n\nMost often, you don't need to use this imperative way of selecting rows. Simply update the DataSource.rowSelection to include the row you want in the selection.\n\n\n\n\n\n void\">\n\n> Toggles the selection of the group row that is identified by the given group keys. Only makes sense when the DataSource is grouped.\n\nCalling this method triggers a call to DataSource.onRowSelectionChange.\n\nFor deselecting a group row, see related [deselectGroupRow](#deselectGroupRow).\nFor selecting a group row, see related [selectGroupRow](#selectGroupRow).\n\n\n\nMost often, you don't need to use this imperative way of selecting group rows. Simply update the DataSource.rowSelection to include the group row you want to select.\n\n\n\n\n\n boolean\">\n\n> Toggles the selection of the specified row. Optionally provide the group keys, if you have access to them.\n\nSee note from [isRowSelected](#isRowSelected) for whether you need to provide the `groupKeys` or not.\n\nCalling this method triggers a call to DataSource.onRowSelectionChange.\n\nFor deselecting the row, see related [deselectRow](#deselectRow).\nFor selecting the row, see related [selectRow](#selectRow).\nFor toggling the selection for a group row, see related [toggleGroupRowSelection](#toggleGroupRowSelection).\n\n\n\nMost often, you don't need to use this imperative way of selecting rows. Simply update the DataSource.rowSelection to include or exclude the given row.\n\n\n\n\n\n\n" }, { "filePath": "/docs/reference/selection-api/index", @@ -1194,20 +1194,6 @@ "readingTime": "3 min read", "content": "\nWhen rendering the `TreeDataSource` component, you can get access to the Tree API by reading it from the [DataSource API](/docs/reference/datasource-api) property.\n\n```tsx {3}\n\n onReady={(api: DataSourceApi) => {\n api.treeApi // <----\n // treeApi is accessible here\n // you may want to store a reference to it in a ref or somewhere in your app state\n \n }}\n/>\n```\n\nFor updating tree nodes, see the following methods:\n\n- \n- \n\n\n\n void\">\n\n> Expands all the nodes in the tree. See related prop.\n\n\n\n```tsx file=\"tree-expandall-example.page.tsx\"\n\n```\n\n\n\n\n\n void\">\n\n> Selects all the nodes in the tree. See related prop.\n\nThis works if the tree has selection enabled. See [tree selection](/docs/learn/tree-grid/tree-selection) for more details.\n\n\n\n```tsx file=\"tree-selectall-example.page.tsx\"\n```\n\n\n\n\n\n void\">\n\n> Selects the node with the given node path. See related and methods.\n\n\n\n\n```tsx file=\"tree-selectnode-example.page.tsx\"\n```\n\n\n\n\n\n void\">\n\n> Deselects the node with the given node path. See related and methods.\n\n\n\n```tsx file=\"tree-selectnode-example.page.tsx\"\n```\n\n\n\n\n\n\n void\">\n\n> Toggles the selection state of the node with the given node path. See related and methods.\n\n\n\n```tsx file=\"tree-selectnode-example.page.tsx\"\n```\n\n\n\n\n\n void\">\n\n> Deselects all the nodes in the tree. See related prop.\n\n\nThis works if the tree has selection enabled. See [tree selection](/docs/learn/tree-grid/tree-selection) for more details.\n\n\n\n```tsx file=\"tree-selectall-example.page.tsx\"\n```\n\n\n\n\n\n void\">\n\n> Collapses all the nodes in the tree. See related prop.\n\n\n\n```tsx file=\"tree-expandall-example.page.tsx\"\n\n```\n\n\n\n\n\n\n boolean\">\n\n> Returns `true` if the node is expanded, `false` otherwise.\n\n\n\n```tsx file=\"tree-toggleNodeExpandState-example.page.tsx\"\n```\n\n\n\n\n\n void\">\n\n> Toggles the node with the give node path.\n\nIf the node at the given path is expanded, it will be collapsed and vice versa.\n\nSee related and methods.\n\n\n\n void\">\n\n> Expands the node with the given node path. See related and methods.\n\nExpands the node. Does not affect other child nodes.\n\n\n\n```tsx file=\"tree-toggleNodeExpandState-example.page.tsx\"\n```\n\n\n\n\n\nIf `options.force` is `true`, the node will be expanded even if is `true` for the given node.\n\n\n\n\n void\">\n\n> Collapses the node with the given node path. See related and methods.\n\nCollapses the node. Does not affect other child nodes.\n\n\n\n```tsx file=\"tree-toggleNodeExpandState-example.page.tsx\"\n```\n\n\n\n\n\nIf `options.force` is `true`, the node will be collapsed even if is `true` for the given node.\n\n\n\n\n\n" }, - { - "filePath": "/docs/reference/type-definitions/index", - "routePath": "/docs/reference/type-definitions/", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/reference/type-definitions/index.page.md", - "fileName": "index.page.md", - "folderPath": "/docs/reference/type-definitions/", - "frontmatter": { - "title": "Infinite Table Type Definitions", - "description": "TypeScript type definitions for Infinite Table" - }, - "excerpt": "These are the public type definitions for `InfiniteTable` and related components, that you can import with named imports from the `@infinite-table/infinite-react` package.", - "readingTime": "20 min read", - "content": "\nThese are the public type definitions for `InfiniteTable` and related components, that you can import with named imports from the `@infinite-table/infinite-react` package.\n\n```tsx title=\"Importing the type for rowInfo\"\nimport type { InfiniteTableRowInfo } from '@infinite-table/infinite-react';\n```\n\n\n\nThe types of all properties in the `InfiniteTable` and `DataSource` components respect the following naming convention: `Prop`\n\nSo, for example, the type for is \n\n\n\n\n\n\n\n> Represents the selection state of the tree nodes. See for more details.\n\n```ts\nimport type { TreeSelectionValue } from '@infinite-table/infinite-react';\n```\n\nThe selection value is an object with the following properties:\n\n- `defaultSelection`: `boolean` - whether the tree nodes are selected by default or not.\n- `selectedPaths?`: `NodePath[]` - the paths of the selected nodes. Mandatory if `defaultSelection` is `false`.\n- `deselectedPaths`: `NodePath[]` - the paths of the deselected nodes. Mandatory if `defaultSelection` is `true`.\n\n```tsx title=\"Example of tree selection value\"\nconst treeSelection: TreeSelectionValue = {\n defaultSelection: false,\n selectedPaths: [['1'], ['2', '20']],\n deselectedPaths: [['1','10']],\n};\n// node ['1'] will be selected but indeterminate\n// since ['1','10'] is in the deselectedPaths\n// node ['2','20'] will be fully selected\n```\n\n\n\n\n\n> Represents the expand/collapse state of the tree nodes. See for more details.\n\n```ts\nimport type { TreeExpandStateValue } from '@infinite-table/infinite-react';\n```\n\nYou can specify the expand/collapse state of the tree nodes in two ways:\n\n1. With node paths (recommended)\n\nWhen using node paths, the object should have the following properties:\n\n- `defaultExpanded`: `boolean` - whether the tree nodes are expanded by default or not.\n- `collapsedPaths`: `string[]` - when `defaultExpanded` is `true`, this is a mandatory prop.\n- `expandedPaths`: `string[]` - when `defaultExpanded` is `false`, this is a mandatory prop.\n\n```tsx title=\"Example of treeExpandState with node paths\"\nconst treeExpandState = {\n defaultExpanded: true,\n collapsedPaths: [\n ['1', '10'],\n ['2', '20'],\n ['5']\n ],\n expandedPaths: [\n ['1', '4'],\n ['5','nested node in 5'],\n ],\n};\n```\n\n2. With node ids\n\nWhen using node ids, the object should have the following properties:\n\n- `defaultExpanded`: `boolean` - whether the tree nodes are expanded by default or not.\n- `collapsedIds`: `string[]` - when `defaultExpanded` is `true`, this is a mandatory prop.\n- `expandedIds`: `string[]` - when `defaultExpanded` is `false`, this is a mandatory prop.\n\n\n```tsx title=\"Example of treeExpandState with node ids\"\nconst treeExpandState = {\n defaultExpanded: true,\n collapsedIds: ['1', '2', '5'],\n expandedIds: ['10', '20', 'nested node in 5'],\n};\n```\n\n\n\n\n\n> Represents the collapse/expand state of row details - when [master-detail is configured](/docs/learn/master-detail/overview). Also see for the most important property in the master-detail configuration.\n\nThis class can be instantiated and the value passed to the prop (or its uncontrolled variant, ).\n\n```tsx title=\"Passing an instance of RowDetailState to the InfiniteTable\"\nconst rowDetailState = new RowDetailState({\n collapsedRows: true,\n expandedRows: [2, 3, 4],\n});\n\n rowDetailState={rowDetailState} />;\n```\n\n```tsx title=\"Passing an object literal to the InfiniteTable\"\n\n rowDetailState={{\n collapsedRows: true,\n expandedRows: [2, 3, 4],\n }}\n/>\n```\n\n\n\nThe instance is only useful if you want to interrogate the object, with methods like `areAllCollapsed()`, `areAllExpanded()`, `isRowDetailsExpanded(rowId)` and so on.\n\nWhen using the callback, it's called with an instance of this class - if you want to use the object literal, make sure you call `rowDetailState.getState()` to get the plain object.\n\n\nThe and accept both an object literal and an instance of this class.\n\nThe object literal has the following properties:\n\n- `collapsedRows`: `boolean | any[]` - if `true`, all row details are collapsed. If an array, it contains the row ids of the rows that are collapsed.\n- `expandedRows`: `boolean | any[]` - if `true`, all row details are expanded. If an array, it contains the row ids of the rows that are expanded.\n\nYou can create an instance using the object literal notation and you can get the object literal from the instance using the `getState` method:\n\n```tsx\nconst rowDetailState = new RowDetailState({\n collapsedRows: true,\n expandedRows: [2, 3, 4],\n});\nconst clone = new RowDetailState(rowDetailState);\n\nconst state = rowDetailState.getState();\n```\n\nYou can mark rows as expanded/collapsed even after creating the instance:\n\n```tsx\nconst rowDetailState = new RowDetailState({\n collapsedRows: true,\n expandedRows: [2, 3, 4],\n});\nrowDetailState.expandRowDetails(5);\nrowDetailState.collapseRowDetails(2);\n\n// now you can pass this instance back to the InfiniteTable component\n```\n\n\n\n\n\n> The type for the object passed into the function prop of the `DataSource` component.\n\nWhen the function is called, it will be called with an object of this type.\n\nThe following properties are available on this object:\n\n- `sortInfo?` - - the current sort info for the grid.\n- `groupBy?` - an array of - the current group by for the grid.\n- `pivotBy?` - an array of - the current pivot by for the grid.\n- `filterValue?` - an array of - the current filter value for the grid.\n- `masterRowInfo?` - - only available if the DataSource is a detail DataSource - meaning there is a master DataGrid, and the DataSource is used to load the detail DataGrid.\n\n\n\n\n\n> The type for the items in the array prop of the `DataSource` component.\n\n\n\n\n\n> Describes the collapse/expand state for group rows, when [grouping is used](/docs/learn/grouping-and-pivoting/grouping-rows). \n\nThis is a class, and instances of it can be used as a value for the / props.\n\nIt's the sole argument available in the callback.\n\nIt gives you the following additional utility methods:\n\n - `getState()`\n - `areAllExpanded()`\n - `areAllCollapsed()`\n - `expandAll()`\n - `collapseAll()`\n - `isGroupRowExpanded(keys: any[][])`\n - `isGroupRowCollapsed(keys: any[][])`\n - `expandGroupRow(keys: any[][])`\n - `collapseGroupRow(keys: any[][])`\n - `toggleGroupRow(keys: any[][])`\n\n\nTo create an instance, pass a plain object that describes the / value:\n\n\n```tsx\nconst state = new GroupRowsState({\n expandedRows: true,\n collapsedRows: [\n ['Europe']\n ['Europe','France'],\n ['Italy']\n ]\n})\n\nconsole.log(state.getState())\n// will log the above object that was used \n// as the sole argument for the constructor\n\n```\n\n\n\nWhen you call those methods, be aware you're not updating the React state! So you'll have to clone the object, call the method on the clone and then update the React state - in the code below, notice the `onClick` code for the `Expand all`/`Collapse all` buttons.\n\n\n\n```ts file=\"using-group-rows-state-controlled-example.page.tsx\"\n```\n\n\n\n\n\n\n\n\n\n> Describes a pivot value for the grid.\n\nThis is the type for the items in the array prop of the `DataSource` component.\n\nThe most important property in this type is the `field` - which will be `keyof DATA_TYPE` - the field to pivot by.\n\nAnother important property in this type is the `column`. It will be used to configure the generated pivot columns:\n\n- if it's an object literal, it will be applied to all generated columns\n- if it's a function, it will be called for each generated column, and the return value will be used to configure the column.\n\n```tsx\nconst pivotBy: DataSourcePivotBy[] = [\n { field: 'country' },\n {\n field: 'canDesign',\n column: ({ column: pivotCol }) => {\n const lastKey =\n pivotCol.pivotGroupKeys[pivotCol.pivotGroupKeys.length - 1];\n\n return {\n header: lastKey === 'yes' ? '๐Ÿ’… Designer' : '๐Ÿ’ป Non-designer',\n };\n },\n },\n];\n```\n\n\n\n\n\n> Represents runtime information passed to rendering and styling functions called when rendering the column headers\n\nThis object is passed to , , and functions.\n\nIt is an object with the following properties:\n\n- `column` - see for details\n- `columnSortInfo` - the current sort info for the column. it will be an object of type or `null`.\n- `filtered: boolean` - if the column is currently filtered or not\n- `api` - [`InfiniteTableApi`](/docs/reference/api) - the api object.\n- `columnApi` - [`InfiniteTableColumnApi`](/docs/reference/column-api) - the column api object.\n- `renderBag` - an object with various JSX values, the default elements rendered by the Infinite Table for the column header. It contains the following properties:\n - `header` - the default column header text\n - `sortIcon` - the default sort icon\n - `menuIcon` - the default column menu icon\n - `filterIcon` - the default column filter icon\n - `selectionCheckBox` - the default column selection checkbox\n\n```tsx title=\"Example column.renderHeader function\"\nconst renderHeader = ({ renderBag }) => {\n return (\n \n ({renderBag.header}) {renderBag.sortIcon}\n \n );\n};\nconst columns = {\n salary: {\n field: 'salary',\n type: 'number',\n renderHeader,\n },\n};\n```\n\n\n\n\n\n> Represents runtime information passed to many styling functions called when rendering the column cells\n\nThis object is passed at runtime during the rendering of column cells.\n\nIt is an object with the following properties:\n\n- `column` - see for details\n- `rowInfo` - see for details\n- `data` - the data object for the current row. The type of this object is `DATA_TYPE | Partial | null`. For regular rows, it will be of type `DATA_TYPE`, while for group rows it will be `Partial`. For rows not yet loaded (because of batching being used), it will be `null`.\n- `value` - the underlying value of the current cell - will generally be `data[column.field]`, if the column is bound to a `field` property\n- `inEdit`: `boolean`\n- `editError`: `Error`\n- `rowSelected`: `boolean | null;`\n- `rowActive`: `boolean | null`\n- `rowHasSelectedCells`: `boolean` - if the current row has selected cells or not\n\n\n\nThe following functions all have this as first argument:\n\n- \n- \n\n\n\n\n\n\n> Represents runtime information passed to many styling functions called when rendering rows/cells\n\nThis object is passed at runtime during the rendering of grid rows/cells.\n\nIt is an object with the following properties:\n\n- `rowInfo` - see for details\n- `rowIndex`: `number` - the index of the row\n- `rowHasSelectedCells`: `boolean` - if the current row has selected cells or not\n\n\n\nThe following functions all have this as first argument:\n\n- \n- \n- \n\n\n\n\n\n\n> Represents information on a specific sort. Contains info about the field to sort by, the sort direction, and the sort type.\n\nThis is the referenced by the prop.\n\nBasically the prop can be either an array of objects, or a single object (or null).\n\nThese are the type properties:\n\n- `dir`: `1 | -1` - 1 means ascending sort order; -1 means descending sort order.\n- `field?`: `keyof DATA_TYPE` - the field to sort by.\n- `id?`: `string` - an id for the sort info. When a column is not bound to a `field`, use the column id as the `id` property of the sort info, if you need to specify a default sort order by that column. Note that columns have a valueGetter, which will be used when doing local sorting and the column is not bound to an exact field.\n- `type?`: `string` - the sort type to apply. See for more details. For example, you can use `\"string\"` or `\"number\"` or `\"date\"`\n\n\n\n\n\n> The type of the DataSource prop.\n\nValid types for this prop are:\n\n- `null`\n- \n- []\n\n\n\n\n\n> The type of the prop. Basically this type is an array of .\n\n\n\n\n\n> This represents an enhanced column definition for a column. A computed column is basically a column with more information computed at runtime, based on everything Infinite Table can aggregate about it.\n\nThis type also includes the properties of the `InfinteTableColumn` type: , , , etc.\n\nAdditional type properties:\n\n- `id`: `string` - the id of the column. This is the same as the prop.\n- `computedEditable`: `boolean| Function` - whether this column is ediable or not. See for more details.\n- `computedWidth`: `number` - the actual calculated width of the column (in pixels) that will be used for rendering. This is computed based on the , and other min/max constraints.\n- `computedPinned`: `false | \"start\" | \"end\"`\n- `computedSortInfo`: or null - the sort info for this column.\n- `computedSorted`: `boolean` - whether this column is currently sorted or not.\n- `computedSortedAsc`: `boolean` - whether this column is currently sorted ascending or not.\n- `computedSortedDesc`: `boolean` - whether this column is currently sorted descending or not.\n- `computedFiltered`: `boolean` - whether this column is currently filtered or not.\n- ... and more (docs coming soon)\n\n\n\n\n\n> The type for the parameter of (and related rendering functions) and also for the object you get back when you call \n\nThese are the type properties:\n\n- `isGroupRow`: `boolean` - whether the current row is a group row or not.\n- `data`: `DATA_TYPE` | `Partial` | `null` - the data object for the current row.\n Because the DataSource can be grouped, the `data` object can be either the original data object, or a partial data object (containing the aggregated values - in case of a group row), or null. You can use `isGroupRow` to discriminate between these cases. If `isGroupRow` is `false`, then `data` is of type `DATA_TYPE`.\n- `rowInfo`: . See that type for more details.\n- `rawValue`: `string` | `number` | other - the raw value for the cell - as computed from the column field or valueGetter function.\n- `value`: `Renderable` - the current value to render for the cell. This is based on the `rawValue`, but if a column valueFormatter exists, it will be the result of that.\n- `column`: - the (computed) column definition for the current cell.\n- `columnsMap`: a map collection of objects, keyed by column id.\n- `fieldsToColumn`: a map collection of objects, keyed by the column field. If a column is not bound to a field, it will not be included in this map.\n- `align`: the computed value of the align prop for the current cell. This will be `\"start\"`, `\"center\"` or `\"end\"`.\n- `api`: [`InfiniteTableApi`](/docs/reference/api) - the api object.\n- `rowInfo`: - the row info for the current row.\n- `rowIndex`: `number` - the index of the current row.\n- `renderBag`: See [column rendering](/docs/learn/columns/column-rendering#rendering-pipeline) for more details.\n- `toggleCurrentGroupRow`: `() => void` - a function that can be used to toggle the current row, if it's a group row.\n- `toggleCurrentTreeNode`: `() => void` - a function that can be used to toggle the expand/collapse state of the current tree node (only available when rendering [a tree grid](/docs/learn/tree-grid/overview)).\n- `rootGroupBy`: - the group by specified in the prop of the `DataSource`.\n- `groupByForColumn`: available for group columns. When is `\"multi-column\"`, this will be a single , for each of the generated group columns. When is `\"single-column\"`, this will be an array of objects - it will be available only in the single group column that will be generated.\n\n\n\n\n\n> The type for the object you get back when you call \n\nThese are the type properties:\n\n- `api`: [`InfiniteTableApi`](/docs/reference/api) - the api object.\n- `initialValue`: `any` - the initial value for the editor.\n- `value`: `any` - the current value for the editor. Initially will be the same as `initialValue`. If you use this value, then your editor is \"controlled\", so make sure that when the editor is changed, you call the `setValue` function with the new value.\n- `setValue`: `(value: any) => void` - should be called to update the value in the cell editor. Calling this does not complete the edit.\n- `confirmEdit`: a reference to InfiniteTableApi.confirmEdit. If you have called `setValue` while editing (meaning your editor was controlled), you don't have to pass any parameters to this function. - the last value of the editor will be used. If your editor is uncontrolled and you haven't called `setValue`, you need to call `confirmEdit` with the value that you want to confirm for the edit.\n\n- `cancelEdit`: a reference to InfiniteTableApi.cancelEdit. Call this to cancel the edit and close the editor. Doesn't require any parameters.\n- `rejectEdit`: a reference to InfiniteTableApi.rejectEdit. Call this to reject the edit and close the editor. You can pass an `Error` object when calling this function to specify the reason for the rejection.\n- `readOnly`: `boolean` - whether the cell is read-only or not.\n\n\n\nInside the hook, you can still call to get access to the cell-related information.\n\n\n\n\n\n\n\n> The type for the objects in the array. See related \n\n\n\nThe type is generic, and the generic type parameter is the type of the data in the grid. In this documentation, either `DATA_TYPE` or `T` will be used to refer to the generic type parameter.\n\n\n\nThese are the type properties:\n\n- `field` - `keyof DATA_TYPE`. The field to group by.\n- `column`: `Partial`\n- `toKey?`: `(value: any, data: DATA_TYPE) => any` - a function that can be used to decide the bucket where each data object from the data set will be placed. If not provided, the `field` value will be used.\n\n\n\n\n\n> Type for `rowInfo` object representing rows in the table. See [Using RowInfo](/docs/learn/rows/using-row-info) for more details.\n\n\n\nThe type is generic, and the generic type parameter is the type of the data in the grid. In this documentation, either `DATA_TYPE` or `T` will be used to refer to the generic type parameter.\n\n\n\nMany methods in Infinite Table are called with `rowInfo` objects that are typed to . (see , , , , and many others)\n\nThis is a discriminated type, based on the `dataSourceHasGrouping` boolean property and the `isGroupRow` boolean property. This means that the type of the object will change based on the value of those properties.\n\n```ts\n\nexport type InfiniteTableRowInfo =\n // dataSourceHasGrouping = false, isGroupRow = false\n | InfiniteTable_NoGrouping_RowInfoNormal;\n\n // dataSourceHasGrouping = true, isGroupRow = false\n | InfiniteTable_HasGrouping_RowInfoNormal\n\n // dataSourceHasGrouping = true, isGroupRow = true\n | InfiniteTable_HasGrouping_RowInfoGroup\n\n // tree scenarios - leaf node\n | InfiniteTable_Tree_RowInfoLeafNode\n\n // tree scenarios - parent node\n | InfiniteTable_Tree_RowInfoParentNode;\n\n```\n\nThe common properties of the type (in all discriminated cases) are:\n\n- `id` - the primary key of the row, as retrieved using the prop.\n- `indexInAll` - the index in all currently visible rows.\n- `rowSelected` - whether the row is selected or not - `boolean | null`.\n- `rowDisabled` - whether the row is disabled or not - `boolean`.\n\n### InfiniteTable_NoGrouping_RowInfoNormal\n\nThis type has `dataSourceHasGrouping` set to `false` and `isGroupRow` set to `false`.\n\nAdditional properties to the ones already mentioned above:\n\n- `data` - the data for the underlying row, of type `DATA_TYPE`.\n- `isGroupRow` - `false`\n- `isTreeNode` - `false`\n- `dataSourceHasGrouping` - `false`\n- `selfLoaded` - `boolean` - useful when lazy loading is configured.\n\n### InfiniteTable_HasGrouping_RowInfoNormal\n\nThis type has `dataSourceHasGrouping` set to `true` and `isGroupRow` set to `false`. So we're in a scenario where grouping is configured via , but the current row is not a group row.\n\nAdditional properties this type exposes:\n\n- `data` - the data for the underlying row, of type `DATA_TYPE`.\n- `dataSourceHasGrouping` - `true`\n- `isGroupRow` - `false`\n- `isTreeNode` - `false`\n- `indexInGroup` - type: `number`. The index of the row in its parent group.\n- `groupKeys` - type: `any[]`, but usually it's actually `string[]`. For normal rows, the group keys will have all the keys starting from the topmost parent down to the last group row in the hierarchy (the direct parent of the current row).\n- `groupBy` - type `(keyof T)[]`. Has the same structure as groupKeys, but it will contain the fields used to group the rows.\n- `rootGroupBy` - type `(keyof T)[]`. The groupBy value of the DataSource component, mapped to the `groupBy.field`\n- `parents` - a list of `rowInfo` objects that are the parents of the current row.\n- `indexInParentGroups[]` - type: `number[]`. See below for an example\n- `groupCount` - type: `number`. The count of leaf rows that the current group (in this case, the parent group) contains\n- `groupNesting` - type `number`. The nesting of the parent group.\n- `collapsed` - type `boolean`.\n- `selfLoaded` - type: `boolean`. Useful in lazy-loading scenarios, when there is batching present. If you're not in such a scenario, the value will be `false`.\n\n### InfiniteTable_HasGrouping_RowInfoGroup\n\nThis type has `dataSourceHasGrouping` set to `true` and `isGroupRow` set to `true`. So we're in a scenario where grouping is configured via and the current row is a group row.\n\nAdditional properties this type exposes:\n\n- `data` - the data for the underlying row, of type `Partial | null`. If there are [aggregations configured](/docs/learn/grouping-and-pivoting/group-aggregations), then `data` will be an object that contains those aggregated values (so the shape of the object will be `Partial`). When no aggregations, `data` will be `null`\n- `dataSourceHasGrouping` - `true`\n- `isGroupRow` - `true`\n- `isTreeNode` - `false`\n- `error` - type: `string?`. If there was an error while loading the group (when the group row is expanded), this will contain the error message. If the group row was loaded with the `cache: true` flag sent in the server response, the error will remain on the `rowInfo` object even when you collapse the group row, otherwise, if `cache: true` was not present, the `error` property will be removed on collapse.\n- `indexInGroup` - type: `number`. The index of the row in the its parent group.\n- `deepRowInfoArray` - an array of `rowInfo` objects. This array contains all the (uncollapsed, so visible) row infos under this group, at any level of nesting, in the order in which they are visible in the table.\n- `reducerResults` - type `Record`. The result of the aggregation reducers for each field in the prop.\n- `groupCount` - type: `number`. The count of leaf rows that the current group (in this case, the parent group) contains\n- `groupData` - type: `DATA_TYPE[]`. The array of the data of all leaf nodes (normal nodes) that are inside this group.\n\n### InfiniteTable_Tree_RowInfoBase\n\nThe base type for row nodes when using the `` component.\n\n- `nodePath`: `any[]` - the path for the current row info\n- `isTreeNode`: `true`\n- `isParentNode`: `boolean`\n- `indexInParent`: `number`\n- `treeNesting`: `number` - the nesting level of the current node.\n\n### InfiniteTable_Tree_RowInfoParentNode\n\nThe type used for parent nodes in tree scenarios.\n\nIn addition to the properties already available via `InfiniteTable_Tree_RowInfoBase`, it adds the following properties:\n\n- `isParentNode` - `true`\n- `isTreeNode` - `true`\n- `totalLeafNodesCount` - `number`\n- `collapsedLeafNodesCount` - `number`\n\n### InfiniteTable_Tree_RowInfoLeafNode\n\nThe type used for leaf nodes in tree scenarios.\n\nIn addition to the properties already available via `InfiniteTable_Tree_RowInfoBase`, it adds the following properties:\n\n- `isParentNode` - `false`\n- `isTreeNode` - `true`\n\n\n\n\n" - }, { "filePath": "/blog/2021/12/10/infinite-launch", "routePath": "/blog/2021/12/10/infinite-launch", @@ -1230,6 +1216,20 @@ "readingTime": "1 min read", "content": "\nToday we are announcing the alpha version (`0.0.7`) of `Infinite Table` ready to be used by early adopters - you can take it from npm\n\n\nnpm i @infinite-table/infinite-react\n\n\nWe're thrilled by the work done by the whole team and this is the result of years of their combined experience and passion ๐ŸŽ‰!\n\n### Future plans\n\nWe have big plans for the future of `Infinite Table` - first we want to finish the current react implementation and see it widely used and wildly successful and then we can move on to other frontend libraries/frameworks.\n\nThe **virtualization engine** we've built for this component is library agnostic so we'll have to port the **rendering** part to other platforms - which could prove to be a not-so-difficult task with all the experience we have in building this.\n\n# ๐Ÿš€\n" }, + { + "filePath": "/docs/reference/type-definitions/index", + "routePath": "/docs/reference/type-definitions/", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/reference/type-definitions/index.page.md", + "fileName": "index.page.md", + "folderPath": "/docs/reference/type-definitions/", + "frontmatter": { + "title": "Infinite Table Type Definitions", + "description": "TypeScript type definitions for Infinite Table" + }, + "excerpt": "These are the public type definitions for `InfiniteTable` and related components, that you can import with named imports from the `@infinite-table/infinite-react` package.", + "readingTime": "20 min read", + "content": "\nThese are the public type definitions for `InfiniteTable` and related components, that you can import with named imports from the `@infinite-table/infinite-react` package.\n\n```tsx title=\"Importing the type for rowInfo\"\nimport type { InfiniteTableRowInfo } from '@infinite-table/infinite-react';\n```\n\n\n\nThe types of all properties in the `InfiniteTable` and `DataSource` components respect the following naming convention: `Prop`\n\nSo, for example, the type for is \n\n\n\n\n\n\n\n> Represents the state of the whole `` component.\n\nYou can grab a reference to the `` component state via the hook that Infinite exposes.\n\nAvailable properties:\n\n - `dataArray` - array of \n\n\n\n\n> Represents the selection state of the tree nodes. See for more details.\n\n```ts\nimport type { TreeSelectionValue } from '@infinite-table/infinite-react';\n```\n\nThe selection value is an object with the following properties:\n\n- `defaultSelection`: `boolean` - whether the tree nodes are selected by default or not.\n- `selectedPaths?`: `NodePath[]` - the paths of the selected nodes. Mandatory if `defaultSelection` is `false`.\n- `deselectedPaths`: `NodePath[]` - the paths of the deselected nodes. Mandatory if `defaultSelection` is `true`.\n\n```tsx title=\"Example of tree selection value\"\nconst treeSelection: TreeSelectionValue = {\n defaultSelection: false,\n selectedPaths: [['1'], ['2', '20']],\n deselectedPaths: [['1','10']],\n};\n// node ['1'] will be selected but indeterminate\n// since ['1','10'] is in the deselectedPaths\n// node ['2','20'] will be fully selected\n```\n\n\n\n\n\n> Represents the expand/collapse state of the tree nodes. See for more details.\n\n```ts\nimport type { TreeExpandStateValue } from '@infinite-table/infinite-react';\n```\n\nYou can specify the expand/collapse state of the tree nodes in two ways:\n\n1. With node paths (recommended)\n\nWhen using node paths, the object should have the following properties:\n\n- `defaultExpanded`: `boolean` - whether the tree nodes are expanded by default or not.\n- `collapsedPaths`: `string[]` - when `defaultExpanded` is `true`, this is a mandatory prop.\n- `expandedPaths`: `string[]` - when `defaultExpanded` is `false`, this is a mandatory prop.\n\n```tsx title=\"Example of treeExpandState with node paths\"\nconst treeExpandState = {\n defaultExpanded: true,\n collapsedPaths: [\n ['1', '10'],\n ['2', '20'],\n ['5']\n ],\n expandedPaths: [\n ['1', '4'],\n ['5','nested node in 5'],\n ],\n};\n```\n\n2. With node ids\n\nWhen using node ids, the object should have the following properties:\n\n- `defaultExpanded`: `boolean` - whether the tree nodes are expanded by default or not.\n- `collapsedIds`: `string[]` - when `defaultExpanded` is `true`, this is a mandatory prop.\n- `expandedIds`: `string[]` - when `defaultExpanded` is `false`, this is a mandatory prop.\n\n\n```tsx title=\"Example of treeExpandState with node ids\"\nconst treeExpandState = {\n defaultExpanded: true,\n collapsedIds: ['1', '2', '5'],\n expandedIds: ['10', '20', 'nested node in 5'],\n};\n```\n\n\n\n\n\n> Represents the collapse/expand state of row details - when [master-detail is configured](/docs/learn/master-detail/overview). Also see for the most important property in the master-detail configuration.\n\nThis class can be instantiated and the value passed to the prop (or its uncontrolled variant, ).\n\n```tsx title=\"Passing an instance of RowDetailState to the InfiniteTable\"\nconst rowDetailState = new RowDetailState({\n collapsedRows: true,\n expandedRows: [2, 3, 4],\n});\n\n rowDetailState={rowDetailState} />;\n```\n\n```tsx title=\"Passing an object literal to the InfiniteTable\"\n\n rowDetailState={{\n collapsedRows: true,\n expandedRows: [2, 3, 4],\n }}\n/>\n```\n\n\n\nThe instance is only useful if you want to interrogate the object, with methods like `areAllCollapsed()`, `areAllExpanded()`, `isRowDetailsExpanded(rowId)` and so on.\n\nWhen using the callback, it's called with an instance of this class - if you want to use the object literal, make sure you call `rowDetailState.getState()` to get the plain object.\n\n\nThe and accept both an object literal and an instance of this class.\n\nThe object literal has the following properties:\n\n- `collapsedRows`: `boolean | any[]` - if `true`, all row details are collapsed. If an array, it contains the row ids of the rows that are collapsed.\n- `expandedRows`: `boolean | any[]` - if `true`, all row details are expanded. If an array, it contains the row ids of the rows that are expanded.\n\nYou can create an instance using the object literal notation and you can get the object literal from the instance using the `getState` method:\n\n```tsx\nconst rowDetailState = new RowDetailState({\n collapsedRows: true,\n expandedRows: [2, 3, 4],\n});\nconst clone = new RowDetailState(rowDetailState);\n\nconst state = rowDetailState.getState();\n```\n\nYou can mark rows as expanded/collapsed even after creating the instance:\n\n```tsx\nconst rowDetailState = new RowDetailState({\n collapsedRows: true,\n expandedRows: [2, 3, 4],\n});\nrowDetailState.expandRowDetails(5);\nrowDetailState.collapseRowDetails(2);\n\n// now you can pass this instance back to the InfiniteTable component\n```\n\n\n\n\n\n> The type for the object passed into the function prop of the `DataSource` component.\n\nWhen the function is called, it will be called with an object of this type.\n\nThe following properties are available on this object:\n\n- `sortInfo?` - - the current sort info for the grid.\n- `groupBy?` - an array of - the current group by for the grid.\n- `pivotBy?` - an array of - the current pivot by for the grid.\n- `filterValue?` - an array of - the current filter value for the grid.\n- `masterRowInfo?` - - only available if the DataSource is a detail DataSource - meaning there is a master DataGrid, and the DataSource is used to load the detail DataGrid.\n\n\n\n\n\n> The type for the items in the array prop of the `DataSource` component.\n\n\n\n\n\n> Describes the collapse/expand state for group rows, when [grouping is used](/docs/learn/grouping-and-pivoting/grouping-rows). \n\nThis is a class, and instances of it can be used as a value for the / props.\n\nIt's the sole argument available in the callback.\n\nIt gives you the following additional utility methods:\n\n - `getState()`\n - `areAllExpanded()`\n - `areAllCollapsed()`\n - `expandAll()`\n - `collapseAll()`\n - `isGroupRowExpanded(keys: any[][])`\n - `isGroupRowCollapsed(keys: any[][])`\n - `expandGroupRow(keys: any[][])`\n - `collapseGroupRow(keys: any[][])`\n - `toggleGroupRow(keys: any[][])`\n\n\nTo create an instance, pass a plain object that describes the / value:\n\n\n```tsx\nconst state = new GroupRowsState({\n expandedRows: true,\n collapsedRows: [\n ['Europe']\n ['Europe','France'],\n ['Italy']\n ]\n})\n\nconsole.log(state.getState())\n// will log the above object that was used \n// as the sole argument for the constructor\n\n```\n\n\n\nWhen you call those methods, be aware you're not updating the React state! So you'll have to clone the object, call the method on the clone and then update the React state - in the code below, notice the `onClick` code for the `Expand all`/`Collapse all` buttons.\n\n\n\n```ts file=\"using-group-rows-state-controlled-example.page.tsx\"\n```\n\n\n\n\n\n\n\n\n\n> Describes a pivot value for the grid.\n\nThis is the type for the items in the array prop of the `DataSource` component.\n\nThe most important property in this type is the `field` - which will be `keyof DATA_TYPE` - the field to pivot by.\n\nAnother important property in this type is the `column`. It will be used to configure the generated pivot columns:\n\n- if it's an object literal, it will be applied to all generated columns\n- if it's a function, it will be called for each generated column, and the return value will be used to configure the column.\n\n```tsx\nconst pivotBy: DataSourcePivotBy[] = [\n { field: 'country' },\n {\n field: 'canDesign',\n column: ({ column: pivotCol }) => {\n const lastKey =\n pivotCol.pivotGroupKeys[pivotCol.pivotGroupKeys.length - 1];\n\n return {\n header: lastKey === 'yes' ? '๐Ÿ’… Designer' : '๐Ÿ’ป Non-designer',\n };\n },\n },\n];\n```\n\n\n\n\n\n> Represents runtime information passed to rendering and styling functions called when rendering the column headers\n\nThis object is passed to , , and functions.\n\nIt is an object with the following properties:\n\n- `column` - see for details\n- `columnSortInfo` - the current sort info for the column. it will be an object of type or `null`.\n- `filtered: boolean` - if the column is currently filtered or not\n- `api` - [`InfiniteTableApi`](/docs/reference/api) - the api object.\n- `columnApi` - [`InfiniteTableColumnApi`](/docs/reference/column-api) - the column api object.\n- `renderBag` - an object with various JSX values, the default elements rendered by the Infinite Table for the column header. It contains the following properties:\n - `header` - the default column header text\n - `sortIcon` - the default sort icon\n - `menuIcon` - the default column menu icon\n - `filterIcon` - the default column filter icon\n - `selectionCheckBox` - the default column selection checkbox\n\n```tsx title=\"Example column.renderHeader function\"\nconst renderHeader = ({ renderBag }) => {\n return (\n \n ({renderBag.header}) {renderBag.sortIcon}\n \n );\n};\nconst columns = {\n salary: {\n field: 'salary',\n type: 'number',\n renderHeader,\n },\n};\n```\n\n\n\n\n\n> Represents runtime information passed to many styling functions called when rendering the column cells\n\nThis object is passed at runtime during the rendering of column cells.\n\nIt is an object with the following properties:\n\n- `column` - see for details\n- `rowInfo` - see for details\n- `data` - the data object for the current row. The type of this object is `DATA_TYPE | Partial | null`. For regular rows, it will be of type `DATA_TYPE`, while for group rows it will be `Partial`. For rows not yet loaded (because of batching being used), it will be `null`.\n- `value` - the underlying value of the current cell - will generally be `data[column.field]`, if the column is bound to a `field` property\n- `inEdit`: `boolean`\n- `editError`: `Error`\n- `rowSelected`: `boolean | null;`\n- `rowActive`: `boolean | null`\n- `rowHasSelectedCells`: `boolean` - if the current row has selected cells or not\n\n\n\nThe following functions all have this as first argument:\n\n- \n- \n\n\n\n\n\n\n> Represents runtime information passed to many styling functions called when rendering rows/cells\n\nThis object is passed at runtime during the rendering of grid rows/cells.\n\nIt is an object with the following properties:\n\n- `rowInfo` - see for details\n- `rowIndex`: `number` - the index of the row\n- `rowHasSelectedCells`: `boolean` - if the current row has selected cells or not\n\n\n\nThe following functions all have this as first argument:\n\n- \n- \n- \n\n\n\n\n\n\n> Represents information on a specific sort. Contains info about the field to sort by, the sort direction, and the sort type.\n\nThis is the referenced by the prop.\n\nBasically the prop can be either an array of objects, or a single object (or null).\n\nThese are the type properties:\n\n- `dir`: `1 | -1` - 1 means ascending sort order; -1 means descending sort order.\n- `field?`: `keyof DATA_TYPE` - the field to sort by.\n- `id?`: `string` - an id for the sort info. When a column is not bound to a `field`, use the column id as the `id` property of the sort info, if you need to specify a default sort order by that column. Note that columns have a valueGetter, which will be used when doing local sorting and the column is not bound to an exact field.\n- `type?`: `string` - the sort type to apply. See for more details. For example, you can use `\"string\"` or `\"number\"` or `\"date\"`\n\n\n\n\n\n> The type of the DataSource prop.\n\nValid types for this prop are:\n\n- `null`\n- \n- []\n\n\n\n\n\n> The type of the prop. Basically this type is an array of .\n\n\n\n\n\n> This represents an enhanced column definition for a column. A computed column is basically a column with more information computed at runtime, based on everything Infinite Table can aggregate about it.\n\nThis type also includes the properties of the `InfinteTableColumn` type: , , , etc.\n\nAdditional type properties:\n\n- `id`: `string` - the id of the column. This is the same as the prop.\n- `computedEditable`: `boolean| Function` - whether this column is ediable or not. See for more details.\n- `computedWidth`: `number` - the actual calculated width of the column (in pixels) that will be used for rendering. This is computed based on the , and other min/max constraints.\n- `computedPinned`: `false | \"start\" | \"end\"`\n- `computedSortInfo`: or null - the sort info for this column.\n- `computedSorted`: `boolean` - whether this column is currently sorted or not.\n- `computedSortedAsc`: `boolean` - whether this column is currently sorted ascending or not.\n- `computedSortedDesc`: `boolean` - whether this column is currently sorted descending or not.\n- `computedFiltered`: `boolean` - whether this column is currently filtered or not.\n- ... and more (docs coming soon)\n\n\n\n\n\n> The type for the parameter of (and related rendering functions) and also for the object you get back when you call \n\nThese are the type properties:\n\n- `isGroupRow`: `boolean` - whether the current row is a group row or not.\n- `data`: `DATA_TYPE` | `Partial` | `null` - the data object for the current row.\n Because the DataSource can be grouped, the `data` object can be either the original data object, or a partial data object (containing the aggregated values - in case of a group row), or null. You can use `isGroupRow` to discriminate between these cases. If `isGroupRow` is `false`, then `data` is of type `DATA_TYPE`.\n- `rowInfo`: . See that type for more details.\n- `rawValue`: `string` | `number` | other - the raw value for the cell - as computed from the column field or valueGetter function.\n- `value`: `Renderable` - the current value to render for the cell. This is based on the `rawValue`, but if a column valueFormatter exists, it will be the result of that.\n- `column`: - the (computed) column definition for the current cell.\n- `columnsMap`: a map collection of objects, keyed by column id.\n- `fieldsToColumn`: a map collection of objects, keyed by the column field. If a column is not bound to a field, it will not be included in this map.\n- `align`: the computed value of the align prop for the current cell. This will be `\"start\"`, `\"center\"` or `\"end\"`.\n- `api`: [`InfiniteTableApi`](/docs/reference/api) - the api object.\n- `rowInfo`: - the row info for the current row.\n- `rowIndex`: `number` - the index of the current row.\n- `renderBag`: See [column rendering](/docs/learn/columns/column-rendering#rendering-pipeline) for more details.\n- `toggleCurrentGroupRow`: `() => void` - a function that can be used to toggle the current row, if it's a group row.\n- `toggleCurrentTreeNode`: `() => void` - a function that can be used to toggle the expand/collapse state of the current tree node (only available when rendering [a tree grid](/docs/learn/tree-grid/overview)).\n- `rootGroupBy`: - the group by specified in the prop of the `DataSource`.\n- `groupByForColumn`: available for group columns. When is `\"multi-column\"`, this will be a single , for each of the generated group columns. When is `\"single-column\"`, this will be an array of objects - it will be available only in the single group column that will be generated.\n\n\n\n\n\n> The type for the object you get back when you call \n\nThese are the type properties:\n\n- `api`: [`InfiniteTableApi`](/docs/reference/api) - the api object.\n- `initialValue`: `any` - the initial value for the editor.\n- `value`: `any` - the current value for the editor. Initially will be the same as `initialValue`. If you use this value, then your editor is \"controlled\", so make sure that when the editor is changed, you call the `setValue` function with the new value.\n- `setValue`: `(value: any) => void` - should be called to update the value in the cell editor. Calling this does not complete the edit.\n- `confirmEdit`: a reference to InfiniteTableApi.confirmEdit. If you have called `setValue` while editing (meaning your editor was controlled), you don't have to pass any parameters to this function. - the last value of the editor will be used. If your editor is uncontrolled and you haven't called `setValue`, you need to call `confirmEdit` with the value that you want to confirm for the edit.\n\n- `cancelEdit`: a reference to InfiniteTableApi.cancelEdit. Call this to cancel the edit and close the editor. Doesn't require any parameters.\n- `rejectEdit`: a reference to InfiniteTableApi.rejectEdit. Call this to reject the edit and close the editor. You can pass an `Error` object when calling this function to specify the reason for the rejection.\n- `readOnly`: `boolean` - whether the cell is read-only or not.\n\n\n\nInside the hook, you can still call to get access to the cell-related information.\n\n\n\n\n\n\n\n> The type for the objects in the array. See related \n\n\n\nThe type is generic, and the generic type parameter is the type of the data in the grid. In this documentation, either `DATA_TYPE` or `T` will be used to refer to the generic type parameter.\n\n\n\nThese are the type properties:\n\n- `field` - `keyof DATA_TYPE`. The field to group by.\n- `column`: `Partial`\n- `toKey?`: `(value: any, data: DATA_TYPE) => any` - a function that can be used to decide the bucket where each data object from the data set will be placed. If not provided, the `field` value will be used.\n\n\n\n\n\n> Type for `rowInfo` object representing rows in the table. See [Using RowInfo](/docs/learn/rows/using-row-info) for more details.\n\n\n\nThe type is generic, and the generic type parameter is the type of the data in the grid. In this documentation, either `DATA_TYPE` or `T` will be used to refer to the generic type parameter.\n\n\n\nMany methods in Infinite Table are called with `rowInfo` objects that are typed to . (see , , , , and many others)\n\nThis is a discriminated type, based on the `dataSourceHasGrouping` boolean property and the `isGroupRow` boolean property. This means that the type of the object will change based on the value of those properties.\n\n```ts\n\nexport type InfiniteTableRowInfo =\n // dataSourceHasGrouping = false, isGroupRow = false\n | InfiniteTable_NoGrouping_RowInfoNormal;\n\n // dataSourceHasGrouping = true, isGroupRow = false\n | InfiniteTable_HasGrouping_RowInfoNormal\n\n // dataSourceHasGrouping = true, isGroupRow = true\n | InfiniteTable_HasGrouping_RowInfoGroup\n\n // tree scenarios - leaf node\n | InfiniteTable_Tree_RowInfoLeafNode\n\n // tree scenarios - parent node\n | InfiniteTable_Tree_RowInfoParentNode;\n\n```\n\nThe common properties of the type (in all discriminated cases) are:\n\n- `id` - the primary key of the row, as retrieved using the prop.\n- `indexInAll` - the index in all currently visible rows.\n- `rowSelected` - whether the row is selected or not - `boolean | null`.\n- `rowDisabled` - whether the row is disabled or not - `boolean`.\n\n### InfiniteTable_NoGrouping_RowInfoNormal\n\nThis type has `dataSourceHasGrouping` set to `false` and `isGroupRow` set to `false`.\n\nAdditional properties to the ones already mentioned above:\n\n- `data` - the data for the underlying row, of type `DATA_TYPE`.\n- `isGroupRow` - `false`\n- `isTreeNode` - `false`\n- `dataSourceHasGrouping` - `false`\n- `selfLoaded` - `boolean` - useful when lazy loading is configured.\n\n### InfiniteTable_HasGrouping_RowInfoNormal\n\nThis type has `dataSourceHasGrouping` set to `true` and `isGroupRow` set to `false`. So we're in a scenario where grouping is configured via , but the current row is not a group row.\n\nAdditional properties this type exposes:\n\n- `data` - the data for the underlying row, of type `DATA_TYPE`.\n- `dataSourceHasGrouping` - `true`\n- `isGroupRow` - `false`\n- `isTreeNode` - `false`\n- `indexInGroup` - type: `number`. The index of the row in its parent group.\n- `groupKeys` - type: `any[]`, but usually it's actually `string[]`. For normal rows, the group keys will have all the keys starting from the topmost parent down to the last group row in the hierarchy (the direct parent of the current row).\n- `groupBy` - type `(keyof T)[]`. Has the same structure as groupKeys, but it will contain the fields used to group the rows.\n- `rootGroupBy` - type `(keyof T)[]`. The groupBy value of the DataSource component, mapped to the `groupBy.field`\n- `parents` - a list of `rowInfo` objects that are the parents of the current row.\n- `indexInParentGroups[]` - type: `number[]`. See below for an example\n- `groupCount` - type: `number`. The count of leaf rows that the current group (in this case, the parent group) contains\n- `groupNesting` - type `number`. The nesting of the parent group.\n- `collapsed` - type `boolean`.\n- `selfLoaded` - type: `boolean`. Useful in lazy-loading scenarios, when there is batching present. If you're not in such a scenario, the value will be `false`.\n\n### InfiniteTable_HasGrouping_RowInfoGroup\n\nThis type has `dataSourceHasGrouping` set to `true` and `isGroupRow` set to `true`. So we're in a scenario where grouping is configured via and the current row is a group row.\n\nAdditional properties this type exposes:\n\n- `data` - the data for the underlying row, of type `Partial | null`. If there are [aggregations configured](/docs/learn/grouping-and-pivoting/group-aggregations), then `data` will be an object that contains those aggregated values (so the shape of the object will be `Partial`). When no aggregations, `data` will be `null`\n- `dataSourceHasGrouping` - `true`\n- `isGroupRow` - `true`\n- `isTreeNode` - `false`\n- `error` - type: `string?`. If there was an error while loading the group (when the group row is expanded), this will contain the error message. If the group row was loaded with the `cache: true` flag sent in the server response, the error will remain on the `rowInfo` object even when you collapse the group row, otherwise, if `cache: true` was not present, the `error` property will be removed on collapse.\n- `indexInGroup` - type: `number`. The index of the row in the its parent group.\n- `deepRowInfoArray` - an array of `rowInfo` objects. This array contains all the (uncollapsed, so visible) row infos under this group, at any level of nesting, in the order in which they are visible in the table.\n- `reducerResults` - type `Record`. The result of the aggregation reducers for each field in the prop.\n- `groupCount` - type: `number`. The count of leaf rows that the current group (in this case, the parent group) contains\n- `groupData` - type: `DATA_TYPE[]`. The array of the data of all leaf nodes (normal nodes) that are inside this group.\n\n### InfiniteTable_Tree_RowInfoBase\n\nThe base type for row nodes when using the `` component.\n\n- `nodePath`: `any[]` - the path for the current row info\n- `isTreeNode`: `true`\n- `isParentNode`: `boolean`\n- `indexInParent`: `number`\n- `treeNesting`: `number` - the nesting level of the current node.\n\n### InfiniteTable_Tree_RowInfoParentNode\n\nThe type used for parent nodes in tree scenarios.\n\nIn addition to the properties already available via `InfiniteTable_Tree_RowInfoBase`, it adds the following properties:\n\n- `isParentNode` - `true`\n- `isTreeNode` - `true`\n- `totalLeafNodesCount` - `number`\n- `collapsedLeafNodesCount` - `number`\n\n### InfiniteTable_Tree_RowInfoLeafNode\n\nThe type used for leaf nodes in tree scenarios.\n\nIn addition to the properties already available via `InfiniteTable_Tree_RowInfoBase`, it adds the following properties:\n\n- `isParentNode` - `false`\n- `isTreeNode` - `true`\n\n\n\n\n" + }, { "filePath": "/blog/2022/06/15/infinite-launch-beta", "routePath": "/blog/2022/06/15/infinite-launch-beta", @@ -1300,50 +1300,50 @@ "content": "\nThis spring, we've been hard at work preparing for our Autumn release.\n\nWe have implemented a few new functionalities:\n\n- [column resizing](#column-resizing) is now available\n- [column reordering](#column-reordering) can be achieved via drag & drop\n- [keyboard navigation](#keyboard-navigation) with support for both row and cell navigation\n\nAnd we have updated some of the existing features:\n\n- [lazy grouping](#lazy-grouping)\n - expands lazy loaded rows correctly and\n - also the server response can contain multiple levels of `children`, which basically allows the backend to send more data for groups you don't want to load lazily\n- [column groups](#column-grouping) are now improved with support for proportional column resizing\n- [pivot columns](#pivoting) are now easier to style and customize\n\n\n\nAt the end of the spring, we started working on row and cell selection and we've made good progress on it.\n\nRow selection is already implemented for non-lazy group data and we're working on integrating it with lazy group data (e.g groups lazily loaded from the server). Of course, it will have integration with checkbox selection.\n\nMultiple row selection will have 2 ways to select data:\n\n- via mouse/keyboard interaction - we've emulated the behavior you're used to from your Finder in MacOS.\n- via checkbox - this is especially useful when the table is configured with grouping.\n\n\n\n## New Features\n\n### Column Resizing\n\nBy default columns are now resizable. You can control this at column level via column.resizable or at grid level via .\n\n\n\nRead more about how you can configure column resizing to fit your needs.\n\n\n\n\n\n\nFor resizable columns, hover the mouse between column headers to grab & drag the resize handle.\n\nHold SHIFT when grabbing in order to **share space on resize**.\n\n\n\n\nA nice feature is support for SHIFT resizing - which will share space on resize between adjacent columns - try it in the example above.\n\n### Column Reordering\n\n\n\nColumn order is a core functionality of `InfiniteTable` - read how you can leverage it in your app.\n\n\n\nThe default column order is the order in which columns appear in the columns object, but you can specify a or tightly control it via the controlled property - use to get notifications when columns are reordered by the user.\n\n\n\n\n\n### Keyboard Navigation\n\nBoth cell and row navigation is supported - use to configure it. By default, cell navigation is enabled.\n\n\n\n\n\nThis example starts with cell `[2,0]` already active.\n\n\n\n\n\n## Updated Features\n\n### Lazy grouping\n\nServer side grouping has support for lazy loading - `InfiniteTable` will automatically load lazy rows that are configured as expanded.\n\n\n\n\n\nIn this example, `France` is specified as expanded, so as soon as it is rendered, `InfiniteTable` will also request its children.\n\n\n\n\n\nAnother nice feature is the ability for a group node to also contain its direct children in the server response, which basically allows the backend to eagerly load data for certain groups.\n\n\n\nLazy grouping (with or without batching) is an advanced feature that allows you to integrate with huge datasets without loading them into the browser.\n\n\n\n### Column grouping\n\nColumn grouping was enhanced with support for pinned columns. Now you can use them in combination.\n\n\n\nColumn groups is a powerful way to arrange columns to fit your business requirements - read how easy it is to define them.\n\n\n\n\n\n\n\nNote the `country` column is pinned at the start of the table but is also part of a column group\n\n\n\n\n\n### Pivoting\n\nPivot columns are now easier to style and benefit from piped rendering to allow maximum customization.\n\n\n\nPivoting is probably our most advanced use-case. We offer full support for server-side pivoting and aggregations.\n\n\n\n\n\n\n\nPivot columns for the `canDesign` field are customized.\n\n\n\n\n" }, { - "filePath": "/blog/2022/11/01/infinite-table-monthly-update-october-2022", - "routePath": "/blog/2022/11/01/infinite-table-monthly-update-october-2022", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2022/11/01/infinite-table-monthly-update-october-2022.page.md", - "fileName": "infinite-table-monthly-update-october-2022.page.md", - "folderPath": "/blog/2022/11/01/", + "filePath": "/blog/2022/09/01/infinite-table-monthly-update-august-2022", + "routePath": "/blog/2022/09/01/infinite-table-monthly-update-august-2022", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2022/09/01/infinite-table-monthly-update-august-2022.page.md", + "fileName": "infinite-table-monthly-update-august-2022.page.md", + "folderPath": "/blog/2022/09/01/", "frontmatter": { - "title": "Quarterly Update - Autumn 2022", - "description": "Infinite Table update for Autumn 2022 - grid menus and new website", + "title": "Quarterly Update - Summer 2022", + "description": "Infinite Table update for Summper 2022 - row selection, column rendering, group columns", "author": [ "admin" ], - "date": "2022-11-01T00:00:00.000Z", + "date": "2022-09-01T00:00:00.000Z", "authorData": { "label": [ "admin" ] } }, - "excerpt": "_In the autumn our focus was implementing a dedicated Menu component so it can be used for column menus and row context menu._", - "readingTime": "2 min read", - "content": "\n_In the autumn our focus was implementing a dedicated Menu component so it can be used for column menus and row context menu._\n\n_In addition to that, we've been working on a new design for our website and getting everything ready for the release._\n\n## Summary\n\nSome new functionalities we added to InfiniteTable include:\n\n- column menus\n- support for tab navigation\n\n\n\nWe redesigned our website in preparation for our **v1** release and public launch.\n\nTo receive your free 3-month license, please email us at [admin@infinite-table.com](mailto:admin@infinite-table.com) while we're still working our way through to `1.0.0`\n\n\n\n## New Features\n\nHere's what we worked on in the last two months:\n\n### Menu component\n\nWe've built a brand new Menu component for Infinite Table, which we're using as a column menu and in the very near future will be used for row context menus.\n\n![Grid with menu](/blogs/grid-with-menu.png)\n\nOur policy is to develop all our components in-house and own them in order not to introduce third-party dependencies and vulnerabilities. It also helps us keep the overall bundle size small (since we're sharing some utilities) so your apps are leaner.\n\nWe have to confess menus are tricky - we made ours support any level of nesting. They're tricky because of the nesting, the smart alignment and containment they need to provide in order to be truly useful. The Infinite Menu can be aligned to different targets and using a multitude of anchoring positions, always taking into account the position with the most available space in relation to a container or a specified area. This makes it really flexible and powerful - we think you'll want to use it as standalone as well once it's documented.\n\n### Tab navigation\n\nPrevious versions of Infinite Table did not have support for tab navigation due to our heavy virtualized rendering (the visual order of the cells was not the same as the DOM order). With the latest release, Infinite Table can now handle tab navigation correctly. Column cells that render `` fields or any other focusable elements can now be reached with tab navigation if the column specifies a prop.\n" + "excerpt": "Over the summer, we continued our work on preparing for our official release, focusing mainly on adding new functionalities and documenting them thoroughly, together with enhancements to existing features.", + "readingTime": "7 min read", + "content": "\nOver the summer, we continued our work on preparing for our official release, focusing mainly on adding new functionalities and documenting them thoroughly, together with enhancements to existing features.\n\n## Summary\n\nWe have implemented a few new functionalities, including:\n\n- [row selection is now available ๐ŸŽ‰](#row-selection)\n- [column rendering pipeline](#column-rendering-pipeline)\n- [group columns are now sortable ๐Ÿ”ƒ](#sortable-group-columns)\n\nAnd we have updated some of the existing features:\n\n- [group columns inherit](#enhanced-group-columns) styles and configuration\n- [column hiding when grouping](#column-hiding-when-grouping)\n- [group columns can be bound to a field](#group-columns-bound-to-a-field)\n- [using the column valueGetter in sorting](#column-valuegetter-in-sorting)\n\n\n\nWe started working on column and context menus.\nWe will first release fully customizable **column** menus to show/hide columns and to easily perform other operations on columns.\nThis will be followed by **context** menus where you will be able to define your own custom actions on rows/cells in the table.\n\n---\n\nDon't worry, the menus will be fully customizable, the menu items are fully replaceable with whatever you need, or you will be able to swap our menu component with a custom one of your own.\n\n\n\n## New Features\n\nHere's what we shipped over the summer:\n\n### Row Selection\n\nRow selection can be single or multiple, with or without a checkbox, with or without grouping and for a lazy or non-lazy `DataSource` - ๐Ÿ˜… that was a long enumeration, but seriously, we think we got something great out there.\n\nYou can specify the selection via the (controlled) or (uncontrolled) props, and listen to changes via the callback prop.\n\n\n\n\n\n- Example shows how you can use multiple row selection with a predefined controlled value.\n\n- Go ahead and select some groups/rows and see the selection value adjust.\n\n- Example also shows how you can use the [InfiniteTableApi](/docs/reference/api) to retrieve the actual ids of the selected rows.\n\n\n\n\n\n\n\nSingle vs multiple selection, grouped or ungrouped data, checkbox selection, lazy selection - read about all the possible combinations you can use to fit your needs.\n\n\n\n### Column Rendering Pipeline\n\nThe rendering pipeline for columns is a series of functions defined on the column that are called while rendering.\n\n\n\nAll the functions that have the word `render` in their name will be called with an object that has a `renderBag` property, which contains values that will be rendered.\n\n\n\nThe default function (the last one in the pipeline) ends up rendering a few things:\n\n- a `value` - generally comes from the field the column is bound to\n- a `groupIcon` - for group columns\n- a `selectionCheckBox` - for columns that have defined (combined with row selection)\n\nWhen the rendering process starts for a column cell, all the above end up in the `renderBag` object.\n\nFor example:\n\n```tsx {3,12}\nconst column: InfiniteTableColumn = {\n valueGetter: () => 'world',\n renderValue: ({ value, renderBag, rowInfo }) => {\n // at this stage, `value` is 'world' and `renderBag.value` has the same value, 'world'\n return {value};\n },\n\n render: ({ value, renderBag, rowInfo }) => {\n // at this stage `value` is 'world'\n // but `renderBag.value` is world, as this was the value returned by `renderValue`\n return
Hello {renderBag.value}!
;\n },\n};\n```\n\n\n\nRead about how using the rendering pipeline helps your write less code.\n\n\n\nHere is the full list of the functions in the rendering pipeline, in order of invocation:\n\n1. - doesn't have access to `renderBag` 2. - doesn't have access to `renderBag` 3. - can use all properties in `renderBag` 4. - can use all properties in `renderBag` 5. - can use all properties in `renderBag` 6. - can use all properties in `renderBag` 7. - can use all properties in `renderBag` 8. - can use all properties in `renderBag`\n\nAdditionally, the custom component has access to the `renderBag` via \n\n### Sortable Group Columns\n\nWhen groupRenderStrategy=\"single-column\" is used, the group column is sortable by default if all the columns that are involved in grouping are sortable.\n\nSorting the group column makes the `sortInfo` have a value that looks like this:\n\n```ts\nconst sortInfo = [{ field: ['stack', 'age'], dir: 1, id: 'group-by' }];\n```\n\nWhen groupRenderStrategy=\"multi-column\", each group column is sortable by default if the column with the corresponding field is sortable.\n\n \n\nIn both single and multi group column render strategy, you can use the property to override the default behavior.\n\n \n\n## Updated Features\n\nHereโ€™s a list of Infinite Table functionalities that we enhanced in the last month:\n\n### Enhanced Group Columns\n\nGroup columns now inherit configuration from the columns bound to the field they are grouped by - if such columns exist.\n\n\n\n\n\nIn this example, the group column inherits the styling of the `country` column, because the `country` field is used for grouping.\n\n\n\n\n\n\n\nThe generated group column(s) - can be one for all groups or one for each group - will inherit the `style`/`className`/renderers from the columns corresponding to the group fields themselves (if those columns exist).\n\nAdditionally, there are other ways to override those inherited configurations, in order to configure the group columns:\n\n- use to specify how each grouping column should look for the respective field (in case of groupRenderStrateg=\"multi-column\")\n- use prop\n - can be used as an object - ideal for when you have simple requirements and when groupRenderStrateg=\"single-column\"\n - as a function that returns a column configuration - can be used like this in either single or multiple group render strategy\n\n\n\n### Column Hiding when Grouping\n\nWhen grouping is enabled, you can choose to hide some columns. Here are the two main ways to do this:\n\n- use - this will make columns bound to the group fields be hidden when grouping is active\n- use (also available on the column types, as ) - this is a column-level property, so you have more fine-grained control over what is hidden and when.\n\nValid values for are:\n\n- `\"*\"` - when any grouping is active, hide the column that specifies this property\n- `true` - when the field this column is bound to is used in grouping, hides this column\n- `keyof DATA_TYPE` - specify an exact field that, when grouped by, makes this column be hidden\n- `{[k in keyof DATA_TYPE]: true}` - an object that can specify more fields. When there is grouping by any of those fields, the current column gets hidden.\n\n\n\n\n\nIn this example, the column bound to `firstName` field is set to hide when any grouping is active, since the group column is anyways found to the `firstName` field.\n\nIn addition, is set to `true`, so the `stack` and `preferredLanguage` columns are also hidden, since they are grouped by.\n\n\n\n\n\n### Group Columns Bound to a Field\n\nGroup columns can now be bound to a field, by leveraging the (obviously ...) property. This will make the group column render the value of that field for non-group rows.\n\n\n\n\n\nIn addition, you can now use and for configuring the rendered value for grouped vs non-grouped rows.\n\n### Column valueGetter in Sorting\n\nColumns allow you to define a valueGetter to change the value they are rendering (e.g. useful when the `DataSet` has nested objects).\n\nPreviously, this value returned by was not used when sorting the table. With the latest update, the value returned by valueGetter is correctly used when sorting the grid locally.\n" }, { - "filePath": "/blog/2022/09/01/infinite-table-monthly-update-august-2022", - "routePath": "/blog/2022/09/01/infinite-table-monthly-update-august-2022", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2022/09/01/infinite-table-monthly-update-august-2022.page.md", - "fileName": "infinite-table-monthly-update-august-2022.page.md", - "folderPath": "/blog/2022/09/01/", + "filePath": "/blog/2022/11/01/infinite-table-monthly-update-october-2022", + "routePath": "/blog/2022/11/01/infinite-table-monthly-update-october-2022", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2022/11/01/infinite-table-monthly-update-october-2022.page.md", + "fileName": "infinite-table-monthly-update-october-2022.page.md", + "folderPath": "/blog/2022/11/01/", "frontmatter": { - "title": "Quarterly Update - Summer 2022", - "description": "Infinite Table update for Summper 2022 - row selection, column rendering, group columns", + "title": "Quarterly Update - Autumn 2022", + "description": "Infinite Table update for Autumn 2022 - grid menus and new website", "author": [ "admin" ], - "date": "2022-09-01T00:00:00.000Z", + "date": "2022-11-01T00:00:00.000Z", "authorData": { "label": [ "admin" ] } }, - "excerpt": "Over the summer, we continued our work on preparing for our official release, focusing mainly on adding new functionalities and documenting them thoroughly, together with enhancements to existing features.", - "readingTime": "7 min read", - "content": "\nOver the summer, we continued our work on preparing for our official release, focusing mainly on adding new functionalities and documenting them thoroughly, together with enhancements to existing features.\n\n## Summary\n\nWe have implemented a few new functionalities, including:\n\n- [row selection is now available ๐ŸŽ‰](#row-selection)\n- [column rendering pipeline](#column-rendering-pipeline)\n- [group columns are now sortable ๐Ÿ”ƒ](#sortable-group-columns)\n\nAnd we have updated some of the existing features:\n\n- [group columns inherit](#enhanced-group-columns) styles and configuration\n- [column hiding when grouping](#column-hiding-when-grouping)\n- [group columns can be bound to a field](#group-columns-bound-to-a-field)\n- [using the column valueGetter in sorting](#column-valuegetter-in-sorting)\n\n\n\nWe started working on column and context menus.\nWe will first release fully customizable **column** menus to show/hide columns and to easily perform other operations on columns.\nThis will be followed by **context** menus where you will be able to define your own custom actions on rows/cells in the table.\n\n---\n\nDon't worry, the menus will be fully customizable, the menu items are fully replaceable with whatever you need, or you will be able to swap our menu component with a custom one of your own.\n\n\n\n## New Features\n\nHere's what we shipped over the summer:\n\n### Row Selection\n\nRow selection can be single or multiple, with or without a checkbox, with or without grouping and for a lazy or non-lazy `DataSource` - ๐Ÿ˜… that was a long enumeration, but seriously, we think we got something great out there.\n\nYou can specify the selection via the (controlled) or (uncontrolled) props, and listen to changes via the callback prop.\n\n\n\n\n\n- Example shows how you can use multiple row selection with a predefined controlled value.\n\n- Go ahead and select some groups/rows and see the selection value adjust.\n\n- Example also shows how you can use the [InfiniteTableApi](/docs/reference/api) to retrieve the actual ids of the selected rows.\n\n\n\n\n\n\n\nSingle vs multiple selection, grouped or ungrouped data, checkbox selection, lazy selection - read about all the possible combinations you can use to fit your needs.\n\n\n\n### Column Rendering Pipeline\n\nThe rendering pipeline for columns is a series of functions defined on the column that are called while rendering.\n\n\n\nAll the functions that have the word `render` in their name will be called with an object that has a `renderBag` property, which contains values that will be rendered.\n\n\n\nThe default function (the last one in the pipeline) ends up rendering a few things:\n\n- a `value` - generally comes from the field the column is bound to\n- a `groupIcon` - for group columns\n- a `selectionCheckBox` - for columns that have defined (combined with row selection)\n\nWhen the rendering process starts for a column cell, all the above end up in the `renderBag` object.\n\nFor example:\n\n```tsx {3,12}\nconst column: InfiniteTableColumn = {\n valueGetter: () => 'world',\n renderValue: ({ value, renderBag, rowInfo }) => {\n // at this stage, `value` is 'world' and `renderBag.value` has the same value, 'world'\n return {value};\n },\n\n render: ({ value, renderBag, rowInfo }) => {\n // at this stage `value` is 'world'\n // but `renderBag.value` is world, as this was the value returned by `renderValue`\n return
Hello {renderBag.value}!
;\n },\n};\n```\n\n\n\nRead about how using the rendering pipeline helps your write less code.\n\n\n\nHere is the full list of the functions in the rendering pipeline, in order of invocation:\n\n1. - doesn't have access to `renderBag` 2. - doesn't have access to `renderBag` 3. - can use all properties in `renderBag` 4. - can use all properties in `renderBag` 5. - can use all properties in `renderBag` 6. - can use all properties in `renderBag` 7. - can use all properties in `renderBag` 8. - can use all properties in `renderBag`\n\nAdditionally, the custom component has access to the `renderBag` via \n\n### Sortable Group Columns\n\nWhen groupRenderStrategy=\"single-column\" is used, the group column is sortable by default if all the columns that are involved in grouping are sortable.\n\nSorting the group column makes the `sortInfo` have a value that looks like this:\n\n```ts\nconst sortInfo = [{ field: ['stack', 'age'], dir: 1, id: 'group-by' }];\n```\n\nWhen groupRenderStrategy=\"multi-column\", each group column is sortable by default if the column with the corresponding field is sortable.\n\n \n\nIn both single and multi group column render strategy, you can use the property to override the default behavior.\n\n \n\n## Updated Features\n\nHereโ€™s a list of Infinite Table functionalities that we enhanced in the last month:\n\n### Enhanced Group Columns\n\nGroup columns now inherit configuration from the columns bound to the field they are grouped by - if such columns exist.\n\n\n\n\n\nIn this example, the group column inherits the styling of the `country` column, because the `country` field is used for grouping.\n\n\n\n\n\n\n\nThe generated group column(s) - can be one for all groups or one for each group - will inherit the `style`/`className`/renderers from the columns corresponding to the group fields themselves (if those columns exist).\n\nAdditionally, there are other ways to override those inherited configurations, in order to configure the group columns:\n\n- use to specify how each grouping column should look for the respective field (in case of groupRenderStrateg=\"multi-column\")\n- use prop\n - can be used as an object - ideal for when you have simple requirements and when groupRenderStrateg=\"single-column\"\n - as a function that returns a column configuration - can be used like this in either single or multiple group render strategy\n\n\n\n### Column Hiding when Grouping\n\nWhen grouping is enabled, you can choose to hide some columns. Here are the two main ways to do this:\n\n- use - this will make columns bound to the group fields be hidden when grouping is active\n- use (also available on the column types, as ) - this is a column-level property, so you have more fine-grained control over what is hidden and when.\n\nValid values for are:\n\n- `\"*\"` - when any grouping is active, hide the column that specifies this property\n- `true` - when the field this column is bound to is used in grouping, hides this column\n- `keyof DATA_TYPE` - specify an exact field that, when grouped by, makes this column be hidden\n- `{[k in keyof DATA_TYPE]: true}` - an object that can specify more fields. When there is grouping by any of those fields, the current column gets hidden.\n\n\n\n\n\nIn this example, the column bound to `firstName` field is set to hide when any grouping is active, since the group column is anyways found to the `firstName` field.\n\nIn addition, is set to `true`, so the `stack` and `preferredLanguage` columns are also hidden, since they are grouped by.\n\n\n\n\n\n### Group Columns Bound to a Field\n\nGroup columns can now be bound to a field, by leveraging the (obviously ...) property. This will make the group column render the value of that field for non-group rows.\n\n\n\n\n\nIn addition, you can now use and for configuring the rendered value for grouped vs non-grouped rows.\n\n### Column valueGetter in Sorting\n\nColumns allow you to define a valueGetter to change the value they are rendering (e.g. useful when the `DataSet` has nested objects).\n\nPreviously, this value returned by was not used when sorting the table. With the latest update, the value returned by valueGetter is correctly used when sorting the grid locally.\n" + "excerpt": "_In the autumn our focus was implementing a dedicated Menu component so it can be used for column menus and row context menu._", + "readingTime": "2 min read", + "content": "\n_In the autumn our focus was implementing a dedicated Menu component so it can be used for column menus and row context menu._\n\n_In addition to that, we've been working on a new design for our website and getting everything ready for the release._\n\n## Summary\n\nSome new functionalities we added to InfiniteTable include:\n\n- column menus\n- support for tab navigation\n\n\n\nWe redesigned our website in preparation for our **v1** release and public launch.\n\nTo receive your free 3-month license, please email us at [admin@infinite-table.com](mailto:admin@infinite-table.com) while we're still working our way through to `1.0.0`\n\n\n\n## New Features\n\nHere's what we worked on in the last two months:\n\n### Menu component\n\nWe've built a brand new Menu component for Infinite Table, which we're using as a column menu and in the very near future will be used for row context menus.\n\n![Grid with menu](/blogs/grid-with-menu.png)\n\nOur policy is to develop all our components in-house and own them in order not to introduce third-party dependencies and vulnerabilities. It also helps us keep the overall bundle size small (since we're sharing some utilities) so your apps are leaner.\n\nWe have to confess menus are tricky - we made ours support any level of nesting. They're tricky because of the nesting, the smart alignment and containment they need to provide in order to be truly useful. The Infinite Menu can be aligned to different targets and using a multitude of anchoring positions, always taking into account the position with the most available space in relation to a container or a specified area. This makes it really flexible and powerful - we think you'll want to use it as standalone as well once it's documented.\n\n### Tab navigation\n\nPrevious versions of Infinite Table did not have support for tab navigation due to our heavy virtualized rendering (the visual order of the cells was not the same as the DOM order). With the latest release, Infinite Table can now handle tab navigation correctly. Column cells that render `` fields or any other focusable elements can now be reached with tab navigation if the column specifies a prop.\n" }, { "filePath": "/blog/2022/11/08/why-another-datagrid", @@ -1369,113 +1369,210 @@ "content": "\nWe've been working on finding better ways to display tabular data for over 2 decades now and collectively we have 35+ years of experience working on this.\n\nIt all began on the desktop with a great range of DataGrids and then we moved to the web and the `` component - yeah, we've been around for quite some while - all the while dealing with the same problems and requirements again and again.\n\nThis is the story of how we got to where we are today....\n\n## A (personal) History of DataGrids\n\n\n\nThis article is not meant to be a complete history of DataGrids.\n\nRather, it's personal reflections on the long journey the Infinite Table team have experienced while using and building components for displaying tabular data, culminating in Infinite Table, the modern declarative DataGrid for React.\n\n\n\n## Desktop Components\n\nDataGrids have been around as long as any of us can remember.\n\nThey are a vital tool which allows business users to visualise, edit, manage and personalise their data.\n\nBefore Tim Berners-Lee and his colleagues changed the world for ever (and for a couple of decades after), \"serious\" business applications lived on the desktop.\n\nThis was accompanied and facilitated by a plethora of great DataGrids from the likes of DevExpress, Telerik, Syncfusion, Infragistics and others.\n\nThese products defined the feature-set that users came to expect in a DataGrid - row grouping, formatting, multiple sorting, pivoting etc.\n\nAnd which any DataGrid worth its salt today needs to offer today.\n\nFor 2 decades and more these DataGrid repeatedly proved their worth in multiple changing desktop formats - MFC, WinForms, WPF and others.\n\n## Enter the Browser\n\nAnd then the browser came along and, in time, everything changed.\n\nWhile it really took until HTML5 to convince most power users to move from the desktop to the web, the need to display tabular data in the browser was there right from the start.\n\nInitially the only way to show tabular data in the browser was to use the `
` component, and it was this piece of code that made it happen:\n\n```css\ntable-layout: fixed;\n```\n\nthis is telling the browser ([see MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/table-layout#values)) that it shouldn't compute the space available for all rows & cells in the table before rendering but instead size the columns based on the content of the first row. This is speeding up the rendering time by quite a lot, and it's the early solution to the problem of rendering large data-sets.\n\nHowever, it was not perfect, and rendering **large** datasets was still a **huge** problem. Also, no fancy resizable / reorderable / stackable columns were available - at least not by default.\n\nThese shortcomings were obvious to developers dealing with massive datasets, so various groups and companies started coming up with solutions. One such solution came from Yahoo! as part of their larger widget library called `YUI` (it was back in the days when Y! was a big deal).\n\n### YUI DataTable\n\nEnter YUI era - launched in 2006, the Yahoo! User Interface Library was a step forward in reusability and component architecture. With the release of YUI 3, it received a modernized set of components, and the [YUI DataTable](https://clarle.github.io/yui3/yui/docs/datatable/) was probably the most advanced DataGrid solution out there. The component had a templating engine under the hood and allowed developers to customize some parts of the table. For its time, it was packed with functionality and was a great solution for many use-cases.\n\nIt had a rich API, exposing lots of events, callbacks and methods for things like moving columns around, getting the data record for a given row, adding rows and columns, etc - all imperative code. The API was powerful and allowed developers to build complex solutions, but it was all stateful and imperative - something very normal for its epoch, but something we've learned to avoid in the last few years.\n\nHere's some code showcasing the YUI DataTable\n\n```js {6} title=\"YUI DataTable with sorting\"\nvar table = new Y.DataTable({\n columns: [\n { key: 'item', width: '125px' },\n {\n key: 'cost',\n formatter: 'ยฃ: {value}',\n sortable: true,\n },\n ],\n sortable: true,\n data: data,\n}).render('#example');\n\n// to programatically sort\ntable.sort({ cost: 'asc' });\n```\n\nNotice in the code above, the component had support for custom formatters via a template (in the style of Mustache templates). YUI DataTable was a great component, certainly lacking some features by modern standards, but it was amazingly rich for its time. In some respects, it's still better than some of the modern DataGrids out there. The major missing piece is virtualization for both rows and columns in the table.\n\nA nice feature YUI DataTable had was the ability to separate the DataSource component and the data loading into a separate abstraction layer, so it would be somewhat decoupled from the main UI component.\n\n```js\nvar dataSource = new Y.DataSource.IO({\n source: '/restaurants/fetch.php?',\n});\n\ndataSource.plug(Y.Plugin.DataSourceXMLSchema, {\n schema: {\n resultListLocator: 'Result',\n resultFields: [{ key: 'Title' }, { key: 'Phone' }, { key: 'Rating' }],\n },\n});\nvar table = new Y.DataTable({\n columns: ['Title', 'Phone', 'Rating'],\n summary: 'Chinese restaurants near 98089',\n});\n\ntable.plug(Y.Plugin.DataTableDataSource, {\n datasource: dataSource,\n initialRequest: 'zip=94089&query=chinese',\n});\n```\n\nInfinite Table is getting this a step further and splitting the data loading and the rendering into two separate components - `` and ``:\n\n- the `` component is responsible for managing the data - fetching it, sorting, grouping, pivoting, filtering, etc and making it available via the React context to the UI component.\n- the `` component is responsible only for rendering the data. This means you can even use the `` component with another React component and implement your own rendering and virtualization.\n\n```tsx\n primaryKey=\"id\" data={...}>\n {/* if you wanted to, you can replace\n with your own custom component */}\n\n columns={...}>\n\n```\n\nThis level of separation allows us to iterate more rapidly on new features and also makes testing ๐Ÿงช easier.\n\n### ExtJS 3\n\nThe next solution we've worked with was [ExtJS version 3](https://docs.sencha.com/extjs/3.4.0/#!/api/Ext.grid.GridPanel), which was built on the legacy of YUI 3. At the time, back in 2010, it was the most advanced DataGrid solution out there - used for some of the most complex applications in the enterprise world, from CMSs to ERP systems.\n\nThe ExtJS 3 DataGrid brought excellent product execution in a few areas:\n\n- the [documentation](https://docs.sencha.com/extjs/3.4.0/) was excellent for its time - very rich, easy to navigate and search, with useful examples. As a bonus, from the docs you had access to the source-code of all components, which was a nice addition.\n- it came together with a rich set of components for building complex UIs - grids, trees, combo-boxes, form inputs, menus, dialogs, etc. Powerful layout components were available, which allowed developers to build complex app layouts by composing components together - and everything felt like it was part of the same story, which it was.\n- enthusiastic community - the forums were very active and the community was writing lots of good plugins.\n\n```js title=\"ExtJS 3 DataGrid code snippet\"\nvar grid = new Ext.grid.GridPanel({\n // data fetching abstracted in a \"Store\" component\n store: new Ext.data.Store({\n // ...\n }),\n // columns abstracted in a ColumnModel\n colModel: new Ext.grid.ColumnModel({\n defaults: {\n width: 120,\n sortable: true,\n },\n columns: [\n {\n id: 'company',\n header: 'Company',\n width: 200,\n sortable: true,\n dataIndex: 'company',\n },\n {\n header: 'Price',\n renderer: Ext.util.Format.usMoney,\n dataIndex: 'price',\n },\n { header: 'Change', dataIndex: 'change' },\n { header: '% Change', dataIndex: 'pctChange' },\n {\n header: 'Last Updated',\n width: 135,\n dataIndex: 'lastChange',\n xtype: 'datecolumn',\n format: 'M d, Y',\n },\n ],\n }),\n viewConfig: {\n forceFit: true,\n // Return CSS class to apply to rows depending upon data values\n getRowClass: function (record, index) {\n var c = record.get('change');\n if (c < 0) {\n return 'price-fall';\n } else if (c > 0) {\n return 'price-rise';\n }\n },\n },\n sm: new Ext.grid.RowSelectionModel({ singleSelect: true }),\n // size need if not inside a layout\n width: 600,\n height: 300,\n});\n```\n\nBuilding on the legacy of YUI 3, the ExtJS added virtualization to make the DataGrid perform well for large datasets - it really made the component fly - since there was no framework overhead, and ExtJS was working directly with the DOM, the scrolling experience was pretty smooth.\n\nAlso ExtJS tried to make things declarative and you could describe most of your UI by nesting JavaScript objects into a root object. The idea was clever, but it was only applicable for the initial rendering and you had to write imperative code as soon as you wanted some changes after the initial render.\n\n\n\nIt was while working on a project with ExtJS 3 and exploring everything it had to offer that we had the great idea ๐Ÿ˜… that we should start writing a DataGrid component.\n\nWe were digging deep into ExtJS source code, wrote a few plugins for it and then decided to take the challenge and build a brand new DataGrid ๐Ÿ˜ฑ.\n\nIt was supposed to take us just a few short months ๐Ÿ˜…...\n\n\n\n## The React Revolution\n\nWe were quite far in building the DataGrid component, with a dedicated templating engine under the hood (by the way, it was really good in comparison to similar solutions at that time), virtualization implemented and major functionalities finished ... when JSConf EU 2013 happened.\n\n### JSConf EU 2013\n\nWe vividly remember [watching Pete Hunt talk about ReactJS and rethinking best practices](https://www.youtube.com/watch?v=x7cQ3mrcKaY) at JSConf EU 2013.\n\n\n\nBy the time the presentation was finished we knew we had to do something.\n\nThis declarative way of describing the UI got us hooked and we knew we had to **drop what we were doing and adopt React** for anything going forward. It proved to be the right decision and we were early adopters of [React](https://reactjs.org/). It was astonishing to us how easy it was to learn React at the time - only taking a few hours to fully grasp the mental model and start building reusable components.\n\n\n2013 was the year we switched trajectory and went full-React with all our new projects. We went back to the drawing board and started our first experiments with a DataGrid component in React. \n\n\nWhile we were building the DataGrid in React we got side-tracked with other projects but we saw the same pattern again and again - people trying to implement the grid component again and again, in various projects. Most of those attempts either failed terribly or at best they were good-enough for a simple use-case.\n\n### AG Grid\n\nIt was around this time, in 2015, that [AG Grid](https://www.ag-grid.com/) was launched.\n\nAnd, wow, it was good - very good.\n\nWe immediately adopted it in all kind of projects while still trying to find time on the side to build our own DataGrid solution, the React way, with a fully declarative API.\n\nWe were inspired ๐Ÿ™ by AG Grid, seeing the breadth of features it offers and its expansive growth.\n\nIt is a feat of engineering which illustrates just how much the browser can be pushed by extensive use of virtualization - being able to render millions of rows and thousands of columns is no small feat.\n\nAll this while keeping the performance similar as if it was rendering just a few rows and columns.\n\n\n\nIn the code above ([taken from AG Grid getting started page](https://www.ag-grid.com/javascript-data-grid/getting-started/#copy-in-application-code)), note that AG Grid is exposing its [API](https://www.ag-grid.com/javascript-data-grid/grid-api/) on the `gridOptions` object.\n\nThe API is huge and allow you to do pretty much anything you want with the grid - in an imperative way, which is what you're probably looking for if you're not integrating with a library/framework like Angular or React.\n\nAfter vanilla JavaScript and Angular versions of AG Grid, a React version was finally released.\n\nIt was a step in the right direction - to make AG Grid more declarative - though it was a thin wrapper around React, with all the renderers and API still being imperative and not feeling like the best fit inside a React app.\n\nA few years later, AG Grid finally released a `reactUI` [version](https://blog.ag-grid.com/react-ui-overview/), with tighter integration with React and a more declarative API โค๏ธ\n\nAll this time other solutions popped up in the React community.\n\n### React Table\n\nOne such solution that got massive adoption from the community was [React Table](https://tanstack.com/table/v8/) - now rebranded as TanStack Table.\n\nIt's growth began around 2018, around the time when headless UI components started to gain traction.\n\nReact Table was one of the first popular headless UI components to be released - in the same category it's worth mentioning [Downshift](https://www.downshift-js.com/) (initially launched and popularized by [Kent C. Dodds](https://kentcdodds.com/)), which helped push headless UI components to the community.\n\nReact Table is a great solution for people who want to build their own UI on top of it.\n\nSome of the benefits of headless UI approach you get from React Table are:\n\n- full control over markup and styles\n- supports all styling patterns (CSS, CSS-in-JS, UI libraries, etc)\n- smaller bundle-sizes.\n\nThis flexibility and total control come with a cost of needing more setup, more code and more maintainance over time. Also complex features that might already be implemented in a full-featured DataGrid will need to be implemented again from scratch.\n\nHowever, we do think it's a great ๐Ÿ’ฏ fit for some use-cases - we've used it ourselves successfully in some projects ๐Ÿ™. But it's not for everyone, as in our experience, most teams today want to ship faster ๐ŸŽ and not spend time and mental energy on building their own UI.\n\n\n\nNotice in the code above how you're responsible for creating the markup for the table, the headers, column groups,the cells, etc. You have TOTAL control over every aspect of the component, but this means you have to own it!\n\nAt the other end of the spectrum is AG Grid a full-featured DataGrid that offers all this out of the box.\n\nWith Infinite Table, we're trying to strike a balance between these 2 very different approaches - by offering a declarative API that is easy to use and get started with, while still giving you the flexibility to customize the UI and the behavior of the component, via both controlled and uncontrolled props.\n\nLet's take a look at an example of a similar UI, this time built with Infinite Table.\n\n\n\n## Infinite Table\n\nAll this time we kept an eye on other components out there to get inspired. We got fresh ideas from various teams and projects - either enterprise or open source - either full-fledged or headless components like [react-table](https://tanstack.com/table/v8/).\n\nWe've learned a lot from all these projects we've worked with and we've put all the best ideas in Infinite Table.\n\nInfinite is the fruit of years of iteration, experimentation, failures and sweat on a product that we've poured our hearts in over the course of so many years. We've agonized over all our APIs and design decisions in order to make Infinite Table the best React DataGrid component out there.\n\nWe're aware we're not there yet, but we're here to stay ๐Ÿ‘‹ and keep getting better. We want to work closely with the community at large and get fresh ideas from other projects and teams. We can all be winners when we work together and respect each-other โค๏ธ\n\nIt's amazing what happens when you focus on a problem for such a long time (yeah, we know ๐Ÿ˜ฑ). We wanted to give up several times but kept pushing for over a decade. The result is a component that we're proud of and is already starting to be used by enterprise clients across many industries (more on that in a later blogpost).\n\nHere are some of the key areas where we believe Infinite Table shines:\n\n### Ready to Use\n\nInfinite Table is ready to use out of the box - namely it's not headless. We target companies and individuals who want to ship โ€” faster ๐ŸŽ! We're aware you don't want to re-invent the wheel nor do you want to invest 6 months of your team to build a poor implementation of a DataGrid component that will be hard to maintain and will be a source of bugs and frustration. **You want to ship โ€” and soon!**. If this is you and you are already using React then Infinite Table is written for you!\n\n### Feels like React - Declarative API\n\nWe want Infinite Table to feel at home in any React app. Everything about the DataGrid should be declarative - when you want to update the table, change a prop and the table will respond. No imperative API calls - we want you to be able to use Infinite Table in a way that feels natural to you and your team, so you can stay productive and use React everywhere in your frontend.\n\nLet's take for example how you would switch a column from a column group to another:\n\n```tsx {35} title=\"Fully declarative way to update columns\"\nfunction getColumns() {\n return {\n firstName: {\n field: 'firstName',\n width: 200,\n columnGroup: 'personalInfo',\n },\n address: {\n field: 'address',\n width: 200,\n columnGroup: 'personalInfo',\n },\n age: {\n field: 'age',\n columnGroup: 'about'\n }\n } as InfiniteTablePropColumns\n}\n\nconst columnGroups = {\n personalInfo: { header: \"Personal info\" },\n about: { header: \"About\" }\n};\n\nfunction App() {\n const [columns, setColumns] = useState>(getColumns)\n const [colGroupForAddress, setColGroupForAddress] = useState('personalInfo')\n\n const toggle = () => {\n const cols = getColumns()\n\n const newColGroup = colGroupForAddress === 'personalInfo' ? 'about' : 'personalInfo'\n cols.address.columnGroup = newColGroup\n\n setColumns(cols)\n setColGroupForAddress(newColGroup)\n }}\n const btn = \n\n return <>\n {btn}\n data={...} primaryKey=\"id\">\n \n \n \n}\n```\n\nNote in the code above that in order to update the column group for the `address` column, we simply change the `columnGroup` prop of the column and then we update the state of the component. The table will automatically re-render and update the column group for the `address` column. This is a fully declarative way to update the table. You don't need to call any imperative API to update it - change the props and the table will reflect the changes.\n\n### Fully Controlled\n\nReact introduced controlled components to the wider community and we've been using them for years. It's were the power of React lies - it gives the developer the flexibility to fully control (when needed) every input point of an app or component.\n\nAll the props Infinite Table is exposing have both controlled and uncontrolled versions. This allows you to start using the component very quickly and without much effort, but also gives you the flexibility to fully control the component when needed, as your app grows and you need more control over the DataGrid.\n\n### Composable API with small API surface\n\nWhen building a complex component, there are two major opposing forces:\n\n- adding functionality and\n- keeping the component (and the API) simple.\n\nWe're trying to reconcile both with Infinite Table so we've built everything with composition in mind.\n\nA practical example of composition is favoring function props instead of boolean flags or objects. Why implement a feature under a boolean flag or a static object when you can expose a functionality via a function prop? The function prop can be used to handle more cases than any boolean flag could ever handle!\n\nA good example of composability is the prop - it can be a column object or a function. It control the columns that are generated for grouping:\n\n- when it's a column object, it makes the table render a single column for grouping (as if was set to `\"single-column\"`)\n- when it's a function, it behaves like is set to `\"multi-column\"` and it's being called for each of the generated columns.\n\n```tsx title=\"Group column as an object\"\n\n```\n\nvs\n\n```tsx title=\"Group column as a function\"\n {\n // this allows you to affect all generated group columns in a single place\n // especially useful when the generated columns are dynamic or generated via a pivot\n return {...}\n }}\n/>\n```\n\nWe've learned from our experience with other DataGrid components that the more features you add, the more complex your API becomes. So we tried to keep the API surface as small as possible, while still offering a rich set of declarative props as building blocks that can be composed to accomplish more complex functionalities.\n\n## Conclusion\n\nWe're very excited to share our Infinite Table journey with you โค๏ธ ๐Ÿคฉ\n\nAfter years in the DataGrid space and working and agonizing on this component, we're happy to finally ship it ๐Ÿ›ณ ๐Ÿš€.\n\nWe're looking forward to receiving [your feedback](https://github.com/infinite-table/infinite-react/issues) and suggestions.\n\nWe're here to stay and we're committed to improving Infinite Table and to make it your go-to React DataGrid component to help you ship โ€” faster! All the while staying true to the community!\n" }, { - "filePath": "/blog/2024/01/23/how-to-customise-datagrid-loading-state", - "routePath": "/blog/2024/01/23/how-to-customise-datagrid-loading-state", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2024/01/23/how-to-customise-datagrid-loading-state.page.md", - "fileName": "how-to-customise-datagrid-loading-state.page.md", - "folderPath": "/blog/2024/01/23/", + "filePath": "/blog/2023/01/16/infinite-table-is-here", + "routePath": "/blog/2023/01/16/infinite-table-is-here", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2023/01/16/infinite-table-is-here.page.md", + "fileName": "infinite-table-is-here.page.md", + "folderPath": "/blog/2023/01/16/", "frontmatter": { - "title": "How to customise the DataGrid loading state", - "author": "admin", - "date": "2024-01-23T00:00:00.000Z", + "title": "๐Ÿ“ฃ Infinite Table is Here ๐ŸŽ‰", + "description": "Infinite Table is ready for prime time. With version 1.0.0 we're releasing a DataGrid that's feature packed and ready to be used in enterprise-grade apps", + "author": [ + "admin" + ], + "date": "2023-01-16T00:00:00.000Z", "authorData": { - "label": "admin" + "label": [ + "admin" + ] } }, - "excerpt": "We're starting a series of short `\"How to\"` articles that are very focused and show how to achieve a specific thing with the Infinite Table DataGrid.", - "readingTime": "2 min read", - "content": "\nWe're starting a series of short `\"How to\"` articles that are very focused and show how to achieve a specific thing with the Infinite Table DataGrid.\n\nIn this article, we'll document how to customise the DataGrid loading state.\n\n## Customising the loading text\n\nFirst off, you can customise the text that is displayed when the DataGrid is loading data. By default, the DataGrid displays a `\"Loading\"` text, but you can customise it to anything you want (even JSX, not only string values).\n\n```tsx title=\"Customising the loading text\" {9}\nconst developers: Developer = [\n { id: '1', firstName: 'Bob' },\n { id: '2', firstName: 'Bill' },\n]\n\n// make sure to add \"loading\" to the DataSource so you see the loading state\n loading data={developers} primaryKey=\"id\">\n \n loadingText={\n Loading your data ...\n }\n columns={{\n firstName: {\n field: 'firstName',\n },\n id: {\n field: 'id',\n }\n }}\n {...props}\n />\n\n```\n\n\nFor the value of you can use JSX, not only strings.\n\n\n\n\n## Customising the loading component - the `LoadMask`\n\nIn addition to the loading text, you can also customise the `LoadMask` component. This is the component that is displayed when the DataGrid is loading data. By default, it's a `
` with `width: 100%; height: 100%; zIndex: 1; display: flex` that contains the loading text.\n\nYou do this by overriding the prop in your Infinite Table configuration.\n\n```tsx title=\"Customising the LoadMask component\" {7,15}\n// make sure to add \"loading\" to the DataSource so you see the loading state\nexport default function App() {\n return (\n loading data={developers} primaryKey=\"id\">\n \n components={{\n LoadMask,\n }}\n columns={columns}\n />\n \n );\n}\n\nfunction LoadMask() {\n return (\n \n \n Loading App ...\n
\n \n );\n}\n```\n\n\n" + "excerpt": "_Infinite Table React is ready for prime time._", + "readingTime": "7 min read", + "content": "\n_Infinite Table React is ready for prime time._\n\n_With version 1.0.0 we're releasing a DataGrid that's feature packed and ready to be used in the wild!_\n\n\n\n1๏ธโƒฃ seriously fast\n\n2๏ธโƒฃ no empty or white rows while scrolling\n\n3๏ธโƒฃ packed with features\n\n4๏ธโƒฃ built from the ground up for React\n\n5๏ธโƒฃ clear, concise and easily composable props & API\n\n\n\nWe think you'll love Infinite Table.\n\nThis is the DataGrid we would have loved to use more than 15 years ago when [we started working with tables in the browser](/blog/2022/11/08/why-another-datagrid).\n\nAnd now it's finally here ๐ŸŽ‰.\n\n### Built from the Ground Up with React & TypeScript\n\n#### React all the Way\n\nInfinite Table feels native to React, not as a after-thought, but built with React fully in mind.\n\nIt's declarative all the way and exposes everything as props, both controlled and uncontrolled.\n\nIf you don't like the default behavior of a prop, use the controlled version and implement your own logic and handling - see for example the [following props related to column order](/docs/reference/infinite-table-props#search=columnorder):\n\n- - controlled property for managing order of columns\n- - uncontrolled version of the above\n- - callback prop for notifications and for updating controlled column order\n\n#### Fully Controlled\n\nReact introduced controlled components to the wider community and we've been using them for years.\n\nIt's where the power of React lies - giving the developer the flexibility to fully control (when needed) every input point of an app or component.\n\nAll the props which Infinite Table exposes, have both controlled and uncontrolled versions. This allows you to start using the component very quickly and without much effort, but also with the all-important flexibility to fully control the component when needed, as your app grows and you need more control over the DataGrid.\n\n#### TypeScript & Generic Components\n\nInfinite Table is also built with TypeScript, giving you all the benefits of a great type system.\n\nIn addition, the exposed components are exported as generic components, so you can specify the type of the data you're working with, for improved type safety.\n\n```tsx\nimport { InfiniteTable, DataSource } from '@infinite-table/infinite-react'\n\ntype Person = { id: number, name: string, age: number}\n\nconst data: Person[] = [\n { id: 1, name: 'John', age: 25 },\n //...\n];\nconst columns = {\n id: { field: 'id' },\n name: { field: 'name' },\n}\n\n// ready to render\n data={data} primaryKey=\"id\">\n columns={columns} />\n\n```\n\n### Why Use Infinite Table, cont.\n\n#### Fast - virtualization\n\nInfinite Table is fast by leveraging **virtualization** both **vertically** (for rows) and **horizontally** (for columns).\n\nThis means DOM nodes are created only for the visible cells, thus reducing the number of DOM nodes and associated memory strain and improving performance.\n\n#### No white space while scrolling - clever layout & rendering\n\nIn addition to virtualization, we use clever layout & rendering techniques to avoid white space while scrolling.\n\nWhen you scroll, the table will not show any empty rows or white space - no matter how fast you're scrolling!\n\n\n\nWe think this is one of the features that sets us apart from other components.\n\nWe've spent a lot of time and effort making sure no whitespace is visible while scrolling the table.\n\n\n\n### Batteries Included\n\nWe want you to be productive immediately and stop worrying about the basics. Infinite Table comes with a lot of features out of the box, so you can focus on the important stuff.\n\nIt helps you display huge datasets and get the most out of your data by providing you the right tools to enjoy these features:\n\n- [ sorting](/docs/learn/sorting/overview)\n- [ row grouping](/docs/learn/grouping-and-pivoting/grouping-rows) - both server-side and client-side\n- [ pivoting](/docs/learn/grouping-and-pivoting/pivoting/overview) - both server-side and client-side\n- [ aggregations](/docs/learn/grouping-and-pivoting/grouping-rows#aggregations)\n- [ live pagination](/docs/learn/working-with-data/live-pagination)\n- [ lazy loading](/docs/learn/working-with-data/lazy-loading)\n- [ keyboard navigation](/docs/learn/keyboard-navigation/navigating-cells)\n- [ fixed and flexible columns](/docs/learn/columns/fixed-and-flexible-size)\n- [ column grouping](/docs/learn/columns/column-grouping)\n- [ theming](/docs/learn/theming)\n- ... and many others\n\nInfinite Table is built for companies and individuals who want to ship โ€” faster ๐ŸŽ!\n\n### (Almost) No External Dependencies\n\nWe've implemented everything from scratch and only directly depend on 2 packages (we'll probably get rid of them as well in the future) - all our dependecy graph totals a mere 3 packages.\n\n\n\nWe've reduced external dependencies for 2 main reasons:\n\n- avoid security issues with dependencies (or dependencies of dependencies...you know it) - remember left-pad?\n- keep the bundle size small\n\n\n\n### Composable API - with a small surface\n\nWhen building a component of this scale, there are two major opposing forces:\n\n- adding functionality\n- keeping the component (and the API) simple\n\nWe're continually trying to reconcile both with Infinite Table, so we've built everything with composition in mind.\n\n\n\nA practical example of composition is favouring function props instead of boolean flags or objects. Why implement a feature under a boolean flag or a static object when you can expose a functionality via a function prop? The function prop can be used to handle more cases than any boolean flag could ever handle!\n\n\n\nA good example of composability is the prop which controls the columns that are generated for grouping.\n\nIt can be either a column object or a function:\n\n- when it's a column object, it makes the table render a single column for grouping (as if was set to `\"single-column\"`)\n- when it's a function, it behaves like is set to `\"multi-column\"` and it's being called for each of the generated columns.\n\n```tsx title=\"Group_column_as_an_object\"\n\n```\n\nvs\n\n```tsx title=\"Group_column_as_a_function\"\n {\n // this allows you to affect all generated group columns in a single place\n // especially useful when the generated columns are dynamic or generated via a pivot\n return {...}\n }}\n/>\n```\n\nOur experience with other DataGrid components taught us that the more features you add, the more complex your API becomes.\n\nSo we tried to keep the API surface as small as possible, while still offering a rich set of declarative props as building blocks that can be composed to accomplish more complex functionalities.\n\n### Built for the community, available on NPM\n\nWe're thrilled to share Infinite Table with the world.\n\nWe wanted to make it very easy for everyone to [get started](/docs/learn/getting-started) with it, so all you require is just an npm install:\n\n\nnpm i @infinite-table/infinite-react\n\n\nThe component will show a footer with a [Powered by Infinite Table](https://infinite-table.com) link displayed. However, all the functionalities are still available and fully working. So if you keep the link visible, you can use the component for free in any setup!\n\nAlthough you can use Infinite Table for free, we encourage you to [purchase a license](/pricing) - buying a license will remove the footer link. This will help us keep delivering new features and improvements to the component and support you and your team going forward!\n\n\n\nGet started with Infinite Table and learn how to use it in your project.\n\n\n\nGet Infinite Table for your project and team!\n\n\n" }, { - "filePath": "/blog/2024/02/02/how-to-configure-default-sorting", - "routePath": "/blog/2024/02/02/how-to-configure-default-sorting", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2024/02/02/how-to-configure-default-sorting.page.md", - "fileName": "how-to-configure-default-sorting.page.md", - "folderPath": "/blog/2024/02/02/", + "filePath": "/blog/2023/01/26/filtering-data-with-infinite-table-for-react", + "routePath": "/blog/2023/01/26/filtering-data-with-infinite-table-for-react", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2023/01/26/filtering-data-with-infinite-table-for-react.page.md", + "fileName": "filtering-data-with-infinite-table-for-react.page.md", + "folderPath": "/blog/2023/01/26/", "frontmatter": { - "title": "How to customise the DataGrid default sorting", - "author": "admin", - "date": "2024-02-02T00:00:00.000Z", + "title": "Filtering Data with Infinite Table for React", + "description": "Learn how to filter data both client-side and server-side with Infinite Table for React", + "author": [ + "admin" + ], + "date": "2023-01-26T00:00:00.000Z", "authorData": { - "label": "admin" + "label": [ + "admin" + ] } }, - "excerpt": "In this article, we'll show you how easy it is to configure the default sorting for the React DataGrid.", - "readingTime": "3 min read", - "content": "\nIn this article, we'll show you how easy it is to configure the default sorting for the React DataGrid.\n\n## Using the `defaultSort` prop on the DataSource\n\nSorting is configured on the DataGrid `` component. For this, you use the prop, as either an object, for sorting by a single column, or an array of objects, for sorting by multiple columns.\n\n```tsx title=\"Specifying a default sort order\"\n// sort by country DESC and salary ASC\nconst defaultSortInfo={[\n { field: \"country\", dir: -1 },\n { field: \"salary\", dir: 1 },\n]}\n\n\n```\n\nThat's it! Now, when the DataGrid is first rendered, it will be sorted by the `country` column in descending order, and then by the `salary` column in ascending order.\n\n\n\nFor sorting to work properly for numeric columns, don't forget to specify `type: \"number\"` in the column configuration.\n\n\n\n\n\n\n\nWhen the is an array, the DataGrid will know you want to allow sorting by multiple columns.\n\nSee our page on [multiple sorting](/docs/learn/sorting/multiple-sorting) for more details.\n\n\n\n## Local vs remote sorting\n\nThe above example uses local sorting. If you don't explicitly specify a that changes in the should trigger a reload (via the prop), the sorting will be done locally, in the browser.\n\nHowever, you can also have remote sorting - for this scenario, make sure you use shouldReloadData.sortInfo=true.\n\n\n\nIn this case, it's your responsability to send the `sortInfo` to your backend using the prop of the DataSource - your `data` function will be called by the DataGrid whenever sorting changes. The arguments the function is called with will include the sort information (along with other details like filtering, grouping, aggregations, etc).\n\n```tsx\nconst dataSource: DataSourceData = ({ sortInfo }) => {\n if (sortInfo && !Array.isArray(sortInfo)) {\n sortInfo = [sortInfo];\n }\n const args = [\n sortInfo\n ? 'sortInfo=' +\n JSON.stringify(\n sortInfo.map((s) => ({\n field: s.field,\n dir: s.dir,\n })),\n )\n : null,\n ]\n .filter(Boolean)\n .join('&');\n\n return fetch('https://your-backend.com/fetch-data?' + args)\n .then((r) => r.json())\n .then((data: Developer[]) => data);\n};\n```\n\n\n\n\n\n## Responding to sorting changes\n\nWhen the user changes the sorting in the React DataGrid UI, the DataSource function is called for you, with the new sort information.\n\nHowever, you might want to respond in other ways - for this, you can use callback prop.\n\n\n\nIf you use the controlled instead of the uncontrolled , you will need to configure the callback to respond to sorting changes and update the UI.\n\n\n\n## Using the column sort info for rendering\n\nAt runtime, you have access to the column sort information, both in the column header - see and in the column cells - see .\n\n\n\nFor example, you can customise the icon that is displayed in the column header to indicate the sort direction.\n\nVia the you have full access to how the column header is rendered and can use the sorting/filtering/grouping/aggregation/pivoting information of that column to customise the rendering.\n" + "excerpt": "_Today we shipped cutting-edge column filtering functionality, that enables intuitive client-side and server-side filtering_", + "readingTime": "5 min read", + "content": "\n_Today we shipped cutting-edge column filtering functionality, that enables intuitive client-side and server-side filtering_\n\n\n\n1๏ธโƒฃ Narrow down your data with your own filter types and operators\n\n2๏ธโƒฃ Works both client-side and server-side\n\n3๏ธโƒฃ Easy customization of filters and filter editors\n\n4๏ธโƒฃ Optimized for performance\n\n5๏ธโƒฃ Easy to use across multiple columns\n\n\n\nFilters were, by far, the most requested feature to add to Infinite Table after our initial launch.\n\nThe recently-released version `1.1.0` of Infinite Table for React introduces support for column filters, which work both client-side and server-side.\n\nIn order to enable filtering - specify the property on the `` component, as shown below:\n\n```tsx {4} title=\"Enabling_filters_on_the_DataSource\"\n data={/* ... */} primaryKey=\"id\" defaultFilterValue={[]}>\n columns={columns} />\n\n```\n\nThis configures the `` component with an empty array of filters; columns will pick this up and each will display a filter editor in the column header.\n\nOf course, you can define some initial filters:\n\n```tsx title=\"Initial_filters:_filter_by_age_greater_than_40\"\ndefaultFilterValue={[\n {\n field: 'age',\n filter: {\n type: 'number',\n operator: 'gt',\n value: 40\n }\n }\n]}\n```\n\nYou can see how all of this looks like when we put it all together in the examples below.\n\n## Local and Remote Filtering\n\nBecause the `` prop is a function that returns a `Promise` with remote data, the filtering will happen server-side by default.\n\n\n\nWhen using remote filtering, it's your responsability to send the DataSource to the backend (you get this object as a parameter in your function). This value includes for each column the value in the filter editor, the column filter type and the operator in use. In this case, the frontend and the backend need to agree on the operator names and what each one means.\n\n\n\nWhenever filters change, when remote filtering is configured, the function prop is called again, with an object that has the `filterValue` correctly set to the current filters (together with `sortInfo` and other data-related props like `groupBy`, etc).\n\n\nHowever, we can use the to force client-side filtering:\n\n```tsx\n filterMode=\"local\" filterDelay={0} />\n```\n\nWe also specify the filterDelay=0 in order to perform filtering immediately, without debouncing and batching filter changes, for a quicker response โšก๏ธ ๐ŸŽ\n\n\n\n\n\nEven if your data is loaded from a remote source, using `filterMode=\"local\"` will perform all filtering on the client-side - so you don't need to send the `filterValue` to the server in your `data` function.\n\n\n\n## Defining Filter Types and Custom Filter Editors\n\nCurrently there are 2 filter types available in Infinite Table:\n\n- `string`\n- `number`\n\nConceptually, you can think of filter types similar to data types - generally if two columns will have the same data type, they will display the same filter.\n\nEach filter type supports a number of operators and each operator has a name and can define it's own filtering function, which will be used when local filtering is used.\n\n\n\nThe example above, besides showing how to define a custom filter type, also shows how to define a custom filter editor.\n\n\n\nFor defining a custom filter editor to be used in a filter type, we need to write a new React component that uses the hook.\n\n```tsx\nimport { useInfiniteColumnFilterEditor } from '@infinite-table/infinite-react';\n\nexport function BoolFilterEditor() {\n const { value, setValue } = useInfiniteColumnFilterEditor();\n return <>{/* ... */};\n}\n```\n\nThis custom hook allows you to get the current `value` of the filter and also to retrieve the `setValue` function that we need to call when we want to update filtering.\n\nRead more about this [in the docs - how to provide a custom editor](/docs/learn/filtering/providing-a-custom-filter-editor).\n\n\n\n## Customise Filterable Columns and Filter Icons\n\nMaybe you don't want all your columns to be filterable.\n\nFor controlling which columns are filterable and which are not, use the property.\n\nThis overrides the global prop.\n\nWe have also made it easy for you to customize the filter icon that is displayed in the column header.\n\n\n\nYou change the filter icon by using the prop - for full control, it's being called even when the column is not filtered, but you have a `filtered` property on the argument the function is called with.\n\nIn the example above, the `salary` column is configured to render no filter icon, but the `header` is customized to be bolded when the column is filtered.\n\n## Ready for Your Challenge!\n\nWe listened to your requests for advanced filtering.\n\nAnd we believe that we've come up with something that's really powerful and customizable.\n\nNow it's your turn to try it out and show us what you can build with it! ๐Ÿš€\n\nIf you have any questions, feel free to reach out to us on [Twitter](https://twitter.com/infinite_table) or in the [GitHub Discussions](https://github.com/infinite-table/infinite-react/discussions).\n\nMake sure you try out filtering in Infinite Table for yourself ([and consult our extensive docs](/docs/learn/filtering) if required).\n\n\n\nLearn how to use filtering in the browser.\n\n\nFigure out how to use filtering with server-side integration.\n\n\n" }, { - "filePath": "/blog/2024/02/26/master-detail-now-available-in-react-datagrid", - "routePath": "/blog/2024/02/26/master-detail-now-available-in-react-datagrid", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2024/02/26/master-detail-now-available-in-react-datagrid.page.md", - "fileName": "master-detail-now-available-in-react-datagrid.page.md", - "folderPath": "/blog/2024/02/26/", + "filePath": "/blog/2023/10/02/version-3-0-0", + "routePath": "/blog/2023/10/02/version-3-0-0", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2023/10/02/version-3-0-0.page.md", + "fileName": "version-3-0-0.page.md", + "folderPath": "/blog/2023/10/02/", "frontmatter": { - "title": "Master detail is now available in the Infinite React DataGrid", - "author": "admin", - "date": "2024-02-26T00:00:00.000Z", + "title": "Infinite Table React DataGrid version 3.0.0 released", + "description": "InfiniteTable DataGrid for React version 3.0.0 brings many small fixes and enhancements, along with a major new feature: cell selection", + "author": [ + "admin" + ], + "date": "2023-10-02T00:00:00.000Z", "authorData": { - "label": "admin" + "label": [ + "admin" + ] } }, - "excerpt": "Today is a big day for the Infinite React DataGrid - we're excited to announce that the master detail feature is now available!", + "excerpt": "Version `3.0.0` is a release that brings a long awaited feature: cell selection. This allows the user to perform fined-grained cell selection, either via the prop or via the [Cell Selection API](/docs/reference/cell-selection-api).", "readingTime": "4 min read", - "content": "\nToday is a big day for the Infinite React DataGrid - we're excited to announce that the master detail feature is now available!\n\nWith this addition, our DataGrid is now enterprise-ready! We know master-detail scenarios are needed in many business applications, and we're happy to provide this feature to our users starting today!\n\n\n\n1๏ธโƒฃ [support for multiple levels of master-detail & rendering custom content](#what-can-you-do-with-master-detail)\n2๏ธโƒฃ [configurable detail height](#configurable-detail-height)\n3๏ธโƒฃ [control over expand/collapse state](#configurable-expandcollapse-state)\n4๏ธโƒฃ [caching mechanism for detail DataGrids](#master-detail-caching)\n\n\n\n## What can you do with master-detail?\n\nMaster-detail allows you to have rows in the DataGrid that expand to show more details. This can be used to show more information about the row, or even to show another DataGrid with related data.\n\nYou can render basically anything in the detail row - it doesn't need to be another DataGrid. However, if you do want to show another DataGrid, you can, and you can do that at any level of depth.\n\nIn the detail `` component, you have access to the master row, so it will be very easy to load related data based on the master row the user expands.\n\n\n\n## Configurable detail height\n\nOur master-detail implementation is very configurable - you can control the height of the row details, the expand/collapse state, and much more.\n\nThe height of the row details is fully adjustable - see the prop to learn about all the options you have.\n\n\n\nAs seen in the snippet above, it's also really easy to control the expand/collapse state of the row details. You can choose to have some rows expanded by default so details of those rows will be visible from the start.\n\n## Configurable expand/collapse state\n\nUsing the , you can control (in a declarative way) which rows are expanded and which are collapsed. In addition, if you prefer the imperative approach, we also have an [API to work with row details](/docs/reference/row-detail-api).\n\nIf you have some rows with details and some without, that's also covered. Use the to control which rows will have details and which will not.\n\n\n\nAnother important configuration is choosing the column that has the row detail expand/collapse icon. Use the prop on the column that needs to display the expand/collapse icon. If this prop is a function, it can be used to customize the icon rendered for expanding/collapsing the row detail.\n\n\n\n## Master detail caching\n\nBy far the most common scenario will be to render another DataGrid in the detail row.\n\nFor such cases we offer a caching mechanism that will keep the state of the detail DataGrid when the user collapses and then expands the row again.\n\n\n\nTo enable caching, use the prop.\n\nIt can be one of the following:\n\n- `false` - caching is disabled - this is the default\n- `true` - enables caching for all detail DataGrids\n- `number` - the maximum number of detail DataGrids to keep in the cache. When the limit is reached, the oldest detail DataGrid will be removed from the cache.\n\n\n\n\n\n\n\nThis example will cache the last 5 detail DataGrids - meaning they won't reload when you expand them again.\nYou can try collapsing a row and then expanding it again to see the caching in action - it won't reload the data.\nBut when you open up a row that hasn't been opened before, it will load the data from the remote location.\n\n\n\n\n\nRead our docs on [caching detail DataGrids](/docs/learn/master-detail/caching-detail-datagrid) to learn more how you can use this feature to improve the user experience.\n" + "content": "\nVersion `3.0.0` is a release that brings a long awaited feature: cell selection. This allows the user to perform fined-grained cell selection, either via the prop or via the [Cell Selection API](/docs/reference/cell-selection-api).\n\n\n\n1๏ธโƒฃ [support for single and multiple cell selection](#1-support-for-single-and-multiple-cell-selection)\n2๏ธโƒฃ [cell selection using wildcards](#2-cell-selection-using-wildcards)\n3๏ธโƒฃ [cell selection API](#3-cell-selection-api)\n\n\n\n## 1๏ธโƒฃ Support for single and multiple cell selection\n\nIt's been a [long-requested feature to implement cell selection](https://github.com/infinite-table/infinite-react/issues/120).\n\nWe knew we needed to implement it, but we wanted to do it right while keeping it easy to understand.\n\nIn fact, we prepared some things in advance - namely was there, it just needed to accept a new value: `\"multi-cell\"`.\n\n```tsx title=\"Configuring multi-cell selection\"\n\n selectionMode=\"multi-cell\" // <--- THIS\n primaryKey=\"id\"\n data={[...]}\n/>\n```\n\nThe line above is all you need to do to enable cell selection. This allows the user to `Click` or `Cmd/Ctrl+Click` to select a specific cell and `Shift+Click` to select a range of cells. It's exactly the behavior you'd expect from a spreadsheet application.\n\nTry `Cmd/Ctrl+Click`ing in the DataGrid cells below to see multiple cell selection in action.\n\n\n\n### Using a default selection\n\nIf you want to render the DataGrid with a default selection, you can use the prop.\n\n```tsx\nconst defaultCellSelection = {\n defaultSelection: false,\n selectedCells: [\n [3, 'hobby'],\n [4, 'firstName'],\n [4, 'hobby'],\n [4, 'preferredLanguage'],\n [4, 'salary'],\n ],\n};\n```\n\nThe format for the uncontrolled (and also for the controlled ) is an object with two properties:\n\n- `defaultSelection` - `boolean` - whether or not cells are selected by default.\n- and either\n - `selectedCells` - `[string|number, string][]` - only needed when `defaultSelection` is `false`\n- or\n - `deselectedCells` - `[string|number, string][]` - only needed when `defaultSelection` is `true`\n\nThe value for `selectedCells` and `deselectedCells` should be an array of `[rowId, colId]` tuples.\n\nThe `rowId` is the `id` of the row (the primary key), and the `colId` is the `id` of the column (the identifier of the column in the prop).\n\nThis object shape for the / props allows you full flexibility in specifying the selection. You can specify a single cell, a range of cells, or even a non-contiguous selection. You can default to everything being selected, or everything being deselected and then enumerate your specific exceptions.\n\n\n\n## 2๏ธโƒฃ Cell Selection using wildcards\n\nThe above examples show how to select specific cells, but what if you want to select all cells in a column, or all cells in a row?\n\nWell, that turns out to be straightforward as well. You can use the `*` wildcard to select all cells in a column or all cells in a row.\n\n```tsx title=\"All cells in row with id rowId3 and all cells in hobby column are selected\"\nconst defaultCellSelection = {\n defaultSelection: false,\n selectedCells: [\n ['*', 'hobby'],\n ['rowId3', '*'],\n ],\n}\n\n```\n\n\n\nWildcard selection is really powerful and it allows you to select lots of cells without the need to enumerate them all.\n\nFor example, you can easily select all cells except a few.\n\n### Listening to selection changes\n\nYou can listen to selection changes by using the prop.\n\nIf you're using controlled cell selection, you have to update the prop yourself in response to user interaction - so will be your way of listening to selection changes.\n\n## 3๏ธโƒฃ Cell Selection API\n\nIn addition to managing cell selection declaratively, which we encourage, you can also use the [Cell Selection API](/docs/reference/cell-selection-api) to imperatively update the current selection.\n\nWe offer the following methods:\n\n- - selects a single cell, while allowing you to keep or to clear previous selection\n- - deselects the specified cell\n- - selects a whole column in the DataGrid\n- - deselects the specified column\n- - selects a range of cells\n- - deselects the specified range of cells\n- - selects all cells in the DataGrid\n- - clears selection (deselects all cells in the DataGrid)\n- - checks if the specified cell is selected or not\n\n## Conclusion\n\nWe'd love to hear your feedback - what do you think we've got right and what's missing. Please reach out to us via email at admin@infinite-table.com or follow us [@get_infinite](https://twitter.com/get_infinite) to keep up-to-date with news about the product.\n\nTalk soon ๐Ÿ™Œ\n" }, { - "filePath": "/blog/2024/03/06/setting-up-master-detail-datagrid", - "routePath": "/blog/2024/03/06/setting-up-master-detail-datagrid", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2024/03/06/setting-up-master-detail-datagrid.page.md", - "fileName": "setting-up-master-detail-datagrid.page.md", - "folderPath": "/blog/2024/03/06/", + "filePath": "/blog/2023/10/05/building-a-datagrid-with-the-right-tools", + "routePath": "/blog/2023/10/05/building-a-datagrid-with-the-right-tools", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2023/10/05/building-a-datagrid-with-the-right-tools.page.md", + "fileName": "building-a-datagrid-with-the-right-tools.page.md", + "folderPath": "/blog/2023/10/05/", "frontmatter": { - "title": "Setting up a master-detail DataGrid with Infinite Table for React", + "title": "Building a DataGrid with the right tools", "author": "admin", - "hide_in_homepage": true, - "date": "2024-03-06T00:00:00.000Z", + "date": "2023-10-05T00:00:00.000Z", "authorData": { "label": "admin" } }, - "excerpt": "We recently [announced the release of master-detail in the Infinite React DataGrid](/blog/2024/02/26/master-detail-now-available-in-react-datagrid), so we also made a video tutorial to follow along, if video is your preferred learning method.", - "readingTime": "1 min read", - "content": "\nWe recently [announced the release of master-detail in the Infinite React DataGrid](/blog/2024/02/26/master-detail-now-available-in-react-datagrid), so we also made a video tutorial to follow along, if video is your preferred learning method.\n\nThis shows the very basics of configuring the Infinite React DataGrid with master-detail, and it's a great starting point for more advanced configurations.\n\n\n\nYou can find the full source code for the tutorial in the code sandbox below.\n\nThis example is two levels deep, but the Infinite React DataGrid supports any number of levels of master-detail.\n\n\n" + "excerpt": "Building for the browser has historically been very tedious. In the old days you had to resort to all sorts of hacks for getting the right layout - anyone remembers conditional comments targeting IE6-9? ๐Ÿ˜…", + "readingTime": "8 min read", + "content": "\nBuilding for the browser has historically been very tedious. In the old days you had to resort to all sorts of hacks for getting the right layout - anyone remembers conditional comments targeting IE6-9? ๐Ÿ˜…\n\nYeah, we don't miss those days either.\n\nThings have evolved in the last few years, and the amount of goodies JS/CSS/HTML/layout goodies we now take for granted is staggering. New CSS features like flex/grid/custom properties really make a difference. Also browser performance has improved a LOT, and today we can do things in the browser that were unthinkable just a few years ago.\n\nHowever, not everything is easier now than it was back in the browser-war days. Handling all kinds of devices, managing changing dependencies, configuring build tools, choosing the right styling approach, proper E2E testing, keeping a small bundle size, CI pipelines, etc. are all things that can (and will) go wrong if you don't have the right tools.\n\n## TypeScript\n\nIt's obvious today to just go with `TypeScript`, but a few years ago, it was not as obvious. We've been using TypeScript for quite a few years now, and we're very happy with it. We can never imagine going back to plain JS.\n\n## React\n\nBuilding on top of `React` has given us an amazing component model that's very composable and easy to reason about - and the ecosystem is huge.\n\nRead about our journey in the [Why another DataGrid?](/blog/2022/11/08/why-another-datagrid) blog post. Back when React was launching, many of our team members were writing DataGrids - either in vanilla JS or using some libraries (`jQuery` anyone? - we don't miss browser incompatibilities).\n\n## CSS Variables and Vanilla Extract\n\nAs a `DataGrid` Infinite Table is built on top of CSS variables - we're going all in with CSS variables. They have a few gotchas in very advanced cases, but all-in-all they're amazing - and especially for performance.\n\nWe're not short of [CSS variables that we expose - see the full list](/docs/learn/theming/css-variables).\n\nUsing them has been pivotal not only to the ease of theming, but also to the performance of the DataGrid.\nBeing able to change a CSS custom property on a single DOM element and then reuse it across many elements that are children of the first one is a huge performance win. Our DataGrid performance would not be the same without CSS variables.\n\n### Vanilla Extract\n\nThe single tool that has made our life a lot easier working with CSS is [Vanilla Extract](https://vanilla-extract.style/). If you're developing a component library, you should definitely use it! Not so much for simple & static apps - there are other styling solutions that are easier to use, like [tailwindCSS](https://tailwindcss.com/). But for component libraries, **Vanilla Extract is amazing**!\n\nDid we mention it's amazing? ๐Ÿ˜…\nThe fact that you can use TypeScript with it, can use \"Find All References\", see where everything is used is a huge win. You're not writing readonly CSS anymore - because that tends to be the case with most CSS. People are afraid to change it or remove old CSS code, just in case those rules are still being used or referenced somehow. This way, CSS only grows with time, and this is a code smell.\n\nWith Vanilla Extract, you get to forget about that. You know what's being used and what's not.\n\nAlso, hashing class names to avoid collisions is nice - and something now very common in the modern JS ecosystem. It all started with CSS modules, and now it's everywhere, Vanilla Extract included.\n\nOther great features we use extensively are:\n\n- public facing CSS variables - their names are stable\n- private CSS variables - their names are hashed\n- sharing CSS values with the TS codebase is a dream come true.\n- Vanilla Extract recipes - generating and applying CSS classes based on a combination of properties. It's enough that you have 2-3 properties, each with a few values, and managing their combinations can be a pain. Vanilla Extract recipes manage this in a very elegant way.\n\n## End-to-end testing with Playwright and NextJS\n\nRemember the days of Selenium? All those flaky tests, the slow execution, the hard to debug issues? They're gone!\n\n[Playwright](https://playwright.dev/) all the way! 300+ tests and going strong! Yes, you read that right! We have more than 300 tests making sure the all the DataGrid features are working as expected. Sorting, filtering, row grouping, column groups, pivoting, aggregations, lazy loading, live pagination, keyboard navigation, cell and row selection, theming - they're all tested! And we're not talking about unit tests, but end-to-end tests. We're testing the DataGrid in the browser, with real data just like real users would.\n\nPlaywright is an amazing tool, but we're not using it standalone. Paired with a [NextJS](https://nextjs.org/) app, with file-system based routing, we've created files/routes for each functionality. Each NextJS file in turn has a Playwright test file with the same name, but a different extension.\n\nThis has the benefit that it's always very obvious which test is running against which page. The test and the route always have the same file name, just the extension is different. The test source-code doesn't explicitly contain code that navigates to a specific page, all this is done under the hood, using this simple convention.\n\nThis way, we have a very clear separation of concerns, and it's very easy to add new tests. We just create a new file in the `pages` folder, and a new test file sibling to it. Another amazing benefit is that we can start the NextJS app and point our browser to whatever page we want to see or debug and it's there. We can very easily do the actions the test is doing and see if we get the expected results. This is a huge win for debugging.\n\n## A tailored state management\n\nWe've built a very simple yet highly effective state management solution for our DataGrid. It's built to make updating the internal state of the DataGrid as easy as possible - we want a simple API, with clear actions. Our actions map almost 1-to-1 to the DataGrid properties, which makes it very obvious to know who changed what.\n\nWe can't overstate how important it is to have a clear data flow through the DataGrid. This is because the DataGrid is by far the most complex UI component you'll ever use (and we'll ever build). You can't possibly go beyond that - at least not in common business apps, where you have the normal UI controls you can expect, like inputs, buttons, dropdowns, etc. Just the ComboBox can come near the complexity of the DataGrid, but it's still far behind.\n\nIt's important to be able to tame all this complexity - otherwise it can slow down the development process and bring it to a halt, making it difficult to add new features or fix bugs. With our current model, even though the DataGrid grew in complexity and features, we never felt our velocity dropping! We enjoy that!\n\n## No dependencies\n\nWe're very proud of the fact that we have no dependencies in our DataGrid. When you install our package, you only install our package - and nothing else. Nothing that can go wrong due to version conflicts, missing dependencies, npm issues ([remember left-pad](https://www.davidhaney.io/npm-left-pad-have-we-forgotten-how-to-program/)?).\n\nYes, we still depend on packages in our dev process, but we're striving to keep that small as well. It's already complex enough to keep TS, React, NextJS, npm (with workspaces), aliases, esbuild, tsup, playwright all working together in harmony. But we've got through it, and we're very happy with the result. It was worth it!\n\n## Separating concerns\n\nWe've separated our DataGrid into 2 main parts:\n\n- the `` component - handles data loading and processing\n- the `` component - handles the rendering\n\nThis was a brilliant idea! It's new? No! It's not our invention, but we're happy we decided to apply it.\n\nIt adds a better separation between the two big parts of the DataGrid. This also helps tame some of the complexity, while adding clarity to the codebase. It's easier to reason about the code when you know that the `` component is responsible for data loading and processing, while the `` component is ONLY responsible for rendering.\n\n## Conclusion\n\nWe're not sorry for choosing any of the above tools or approaches when building the InfiniteTable DataGrid component.\n\nOur developer velocity is high, and we're able to add new features and fix bugs at a fast pace. We're happy with the result and we're confident that we'll be able to keep this pace in the future.\n\nThe right tools get the right job done! They make a lot easier. Looking back, we only regret we didn't have those tools 5 years ago - but hey, things are moving in the right direction, and we're happy to be part of this journey.\n\nWhat are your tools for developer productivity?\n" }, { - "filePath": "/blog/2024/04/18/the-best-testing-setup-for-frontends-playwright-nextjs", - "routePath": "/blog/2024/04/18/the-best-testing-setup-for-frontends-playwright-nextjs", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2024/04/18/the-best-testing-setup-for-frontends-playwright-nextjs.page.md", - "fileName": "the-best-testing-setup-for-frontends-playwright-nextjs.page.md", - "folderPath": "/blog/2024/04/18/", + "filePath": "/blog/2023/07/14/version-2-0-0", + "routePath": "/blog/2023/07/14/version-2-0-0", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2023/07/14/version-2-0-0.page.md", + "fileName": "version-2-0-0.page.md", + "folderPath": "/blog/2023/07/14/", "frontmatter": { - "title": "The best testing setup for frontends, with Playwright and NextJS", - "author": "admin", - "date": "2024-04-18T00:00:00.000Z", + "title": "Infinite Table DataGrid for React reaches version 2.0.0", + "description": "With version 2.0.0 InfiniteTable DataGrid for React brings lots of fixes and enhancements including support for sorting group columns, better APIs, improved pivoting, smarter column menus and more.", + "author": [ + "admin" + ], + "date": "2023-07-14T00:00:00.000Z", "authorData": { - "label": "admin" + "label": [ + "admin" + ] } }, - "excerpt": "We want to share with you the best testing setup we've experienced - and this includes using [Playwright](https://playwright.dev/) and [NextJS](https://nextjs.org/). It's a setup we've come up with for Infinite React DataGrid, which is a complex component, with lots of things to test, but this configuration has helped us ship with more confidence and speed.", - "readingTime": "13 min read", - "content": "\nWe want to share with you the best testing setup we've experienced - and this includes using [Playwright](https://playwright.dev/) and [NextJS](https://nextjs.org/). It's a setup we've come up with for Infinite React DataGrid, which is a complex component, with lots of things to test, but this configuration has helped us ship with more confidence and speed.\n\n## What you should expect from a testing setup\n\n### Fast feedback \n\nโšก๏ธ Quick โšก๏ธ feedback is a no-brainer, since without a fast turnaround, devs will not have the patience to run the tests and will move on to the next \"burning\" issue or to the next cup of coffee.\n\nAlso, you can't run all the test suite at once, so you need to be able to run only the tests that are relevant to the changes you've made. This has long been available in unit-testing frameworks, but it's not so common in end-to-end testing, when loading a webpage and rendering an actual component is involved.\n\nIn this article we want to show you how we achieved fast feedback that allows rapid developer iterations.\n\n### Stability and predictability\n\nYou don't need flaky tests that fail randomly - it's the last thing you want when doing a release, or even during development.\nWaiting for an element to appear on page or an animation to finish or an interaction to complete is a common source of flakiness in end-to-end tests, but Playwright gives you the tools to address these issues - thank you [Playwright locators](https://playwright.dev/docs/locators) ๐Ÿ™ and other playwright testing framework features.\n\n### Ease of maintenance and debugging\n\nAnother crucial point when you setup a testing framework and start writing tests is how easy is to write a new test, to inspect what is being tested and to reproduce failing tests. All these should be as easy as opening loading a URL in a browser - this is exactly what this setup gives you, with NextJS and Playwright playing very well together.\nWhen one of your tests fails, Playwright outputs a command you can run to reproduce the exact failure and actually see the UI at the moment of the failure, with the ability to navigate through the test timeline and see what happened before the failure.\n\n## Setting up NextJS and Playwright\n\n### Step 1 - creating the NextJS app\n```sh\n$ npx create-next-app@latest\n```\n\nYou're being asked a few questions. For `Would you like to use src/ directory?` we chose `Yes`. Also, we're using TypeScript.\n\nWhen you run this command, make sure for this question `Would you like to use App Router?` you reply `No`, as you want to use file-system routing to make it very easy and intuitive to add new pages and tests.\n\n\nCheck out our repo for this stage of the setup - [Step 1 - setting up NextJS](https://github.com/infinite-table/testing-setup-nextjs-playwright/tree/01-setup-nextjs).\n\n\n\n\nBefore you go to the next step, you can configure your `next.config.mjs` to use the `.page` extension for your pages.\n\n```js\nconst nextConfig = {\n reactStrictMode: true,\n pageExtensions: [\"page.tsx\", \"page.ts\", \"page.js\"],\n};\nexport default nextConfig\n```\n\nThis is useful so NextJS will only compile those files as pages that your tests will be targeting, and not all the files in the `pages` folder, which will also contain your tests.\n\nSo you know all your `.page` files are pages that your tests will be run against and all your `.spec` files are tests (see next step).\n\n\n\n### Step 2 - setting up Playwright\n\n```sh\n$ npm init playwright@latest\n```\n\nAgain a few questions about your setup.\n\n`Where to put your end-to-end tests?` - choose `src/pages` - which makes your NextJS pages folder the place where you put your end-to-end tests.\n\nThis script installs `@playwright/test` and creates a `playwright.config.ts` file with the default configuration. Most importantly, the `testDir` is configured to `./src/pages`.\n\nBy default, all `.spec` files in the `testDir` (which is set to `src/pages`) will be run as tests.\n\n\nCheck out our repo for this stage of the setup - [Step 2 - setting up Playwright](https://github.com/infinite-table/testing-setup-nextjs-playwright/tree/02-setup-playwright).\n\n\nThere are some additional configurations you might want to do in this step.\nYou probably want to change the default `reporter` from `'html'` to `'list'` in your `playwright.config.ts` - the `'html'` reporter will open a browser window with the test results, which you might not prefer. You'd rather see the results in the terminal.\n\n ```ts {3} title=\"Configure the reporter in playwright.config.ts\"\nexport default defineConfig({\n testDir: \"./src/pages\",\n reporter: \"list\", // the 'html' reporter will open a browser window with the test results\n // ...\n})\n ```\n\n\n\nFor now, you might want to only run your tests in one browser, so comment out any additional entries in the `projects` array in your `playwright.config.ts` file - that controls the devices that will be used in your tests.\n\n\n\nThe last piece of the puzzle before running your first test with Playwright is defining the `test` script in your `package.json`.\n\n```json {4} title=\"package.json\"\n{\n \"name\": \"testing-setup-nextjs-playwright\",\n \"scripts\": {\n \"test\": \"npx playwright test\",\n \"dev\": \"next dev\",\n \"build\": \"next build\",\n },\n}\n```\n\nExecuting the `npm run test` command will run the tests in the `src/pages` folder - for now, you should have a single file, `example.spec.ts`, which was generated by the `npm init playwright` command.\n\n![Playwright test output](/blog-images/step-2-initial-results.png)\n\nYour initial test file was something very basic. This file is importing the `test` (and `expect`) function from `@playwright/test` - and this is what you're using to define tests (and write assertions).\n\n```ts {1} title=\"example.spec.ts\"\nimport { test, expect } from \"@playwright/test\";\n\ntest(\"has title\", async ({ page }) => {\n await page.goto(\"https://playwright.dev/\");\n\n // Expect a title \"to contain\" a substring.\n await expect(page).toHaveTitle(/Playwright/);\n});\n```\n\n### Step 3 - configuring the naming convention in Playwright to open the right pages\n\nThis step is probably the most important one in your configuration. Normally your tests will open webpages before you start testing - but this is not something you want to do explicitly in your project. Rather, you want your tests to automatically navigate to the corresponding page for the test. This is what this step is achieving - and we're using [Playwright fixtures](https://playwright.dev/docs/test-fixtures) to do this.\n\nThink of a fixture as some code that's configuring the testing environment for each of your tests.\nA fixture will extend the `test` function from `@playwright/test` with additional functionalities. Mainly, we want before every test to open the correct page, without writing this explicitly in every test. Based on the location of the test file in the file system, we want to navigate to a webpage for it and we assume it will have the same path as the test file. This is possible because NextJS is configured to use file-system routing.\n\n```ts {1,3-5} title=\"Defining the fixture file - test-fixtures.ts\"\nimport {\n test as base,\n expect,\n PlaywrightTestArgs,\n PlaywrightTestOptions,\n Page,\n} from \"@playwright/test\";\n\nexport * from \"@playwright/test\";\n\nexport const test = base.extend<\n PlaywrightTestArgs &\n PlaywrightTestOptions\n>({\n //@ts-ignore\n page: async ({ baseURL, page }, use, testInfo) => {\n const testFilePath = testInfo.titlePath[0];\n const fileName = testFilePath.replace(\".spec.ts\", \"\");\n const url = `${baseURL}${fileName}`;\n\n // navigate to the corresponding page for this test\n await page.goto(url);\n\n await use(page);\n },\n});\n```\n\nWe'll give this fixture file the name `test-fixtures.ts` and put it in the root of the project.\n\nNow instead of importing the `test` function from `@playwright/test` we want to import it from the `test-fixtures.ts` file - we'll do this in all our tests. To make this easier, let's also define a path alias in the `tsconfig.json` file.\n\n```json {4} title=\"tsconfig.json\"\n{\n \"compilerOptions\": {\n \"paths\": {\n \"@playwright/test\": [\"test-fixtures.ts\"],\n }\n }\n}\n```\n\nWe're ready to write our first test page in NextJS and use the new fixture in the Playwright test.\n\n```tsx title=\"src/pages/example.page.tsx\"\nexport default function App() {\n return
Hello world
;\n}\n```\n\n```ts {1} title=\"src/pages/example.spec.ts\"\nimport { test, expect } from \"@testing\"; // notice the import\n\ntest(\"Main example has corrent content\", async ({ page }) => {\n // notice we don't need to navigate to the page, this is done by the fixture\n await expect(await page.innerText(\"body\")).toContain(\"Hello world\");\n});\n```\n\nFor our tests against the NextJS app, we obviously need to start the app.\n\nLet's configure a custom port of `5432` in the package.json `dev` script.\n```json {3} title=\"package.json\"\n{\n \"scripts\": {\n \"dev\": \"next dev --port 5432\",\n \"test\": \"npx playwright test\"\n }\n //...\n}\n```\nWe need to use the same port in the Playwright configuration file.\nAlso we'll use a smaller test `timeout` (the default is 30s).\n\n```ts {9,11} title=\"playwright.config.ts\"\nimport { defineConfig } from \"@playwright/test\";\n/**\n * See https://playwright.dev/docs/test-configuration.\n */\nexport default defineConfig({\n testDir: \"./src/pages\",\n reporter: \"list\",\n use: {\n baseURL: \"http://localhost:5432/\",\n },\n timeout: process.env.CI ? 10000 : 4000,\n // ... more options\n});\n\n```\n\nWe're now ready to roll!\n\n`npm run dev` will run NextJS and `npm run test` will run the tests against your NextJS app.\n\n\n\nTo make the setup easier, avoid using `index.page.tsx` pages in NextJS - give your pages another name, to avoid issues with directory index pages in tests. This can easily be solved in the test fixture, but for the sake of clarity and brevity we're not doing it now.\n\n\n\n\n\n\nCheck out our repo for this stage of the setup - [Step 3 - configuring the Playwright fixture and naming convention](https://github.com/infinite-table/testing-setup-nextjs-playwright/tree/03-configure-naming-convention).\n\n\n\n### Step 4 - adding watch mode\n\nAs we mentioned initially, no testing setup is great unless it gives you very fast feedback. For this, we obviously need watch mode.\n\nWe want to be able to re-run tests when our test code has changed, but even better, when our NextJS page has changed - so the page the test is running against.\nNextJS has watch mode built-in in dev mode, so whenever a page is changed, it's recompiled and the browser is served the updated page. We'll use this in our advantage, so tests will always see the latest version of the page.\nThis means the last piece of the puzzle is to make Playwright re-run the tests when the page has changed or the test itself has changed.\n\nFor this, we'll use [`chokidar`](https://www.npmjs.com/package/chokidar) - more specifically the [`chokidar-cli`](https://www.npmjs.com/package/chokidar-cli) package. `chokidar` is probably the most useful file watching library for the nodejs ecosystem and it will serve us well.\n\n```json {4} title=\"package.json\"\n{\n \"scripts\": {\n \"test\": \"npx playwright test\",\n \"test:watch\": \"chokidar '**/*.spec.ts' '**/*.page.tsx' -c 'test_file_path=$(echo {path} | sed s/page.tsx/spec.ts/) && npm run test -- --retries=0 ${test_file_path}'\"\n }\n}\n```\n\nThe `test:watch` script is watching for changes in `.spec.ts` files and `.page.tsx` files and whenever there's a change in one of those files, it's re-running the respective test. (When a change was found in a `.page.tsx` file, we're using `sed` to replace the `.page.tsx` extension with `.spec.ts`, because we want to pass the test file to the `npm run test` command so it knows what test to re-run.)\n\n\n\nThe above `test:watch` script was written for MacOS (and Unix-like systems). If you're using Windows, you might need to adjust the command to achieve the same result.\n\n\n\n\n\nDon't forget to run `npm run dev` before running `npm run test` or `npm run test:watch` - you need the NextJS app running to be able to run the tests. After all, that's what you're testing ๐Ÿ˜….\n\n\n\n### Step 5 - running tests on production build\n\nIn the last step, we want to build a production build of the NextJS app and run the tests against it. \n\nSo first let's configure the `next.config.mjs` file to build a static site when `npm run build` is run.\n\n```js {3} title=\"next.config.mjs - configured to export a static site\"\nconst nextConfig = {\n reactStrictMode: true,\n output: \"export\",\n pageExtensions: [\"page.tsx\", \"page.ts\", \"page.js\"],\n};\n\nexport default nextConfig;\n```\nNotice the `\"output\": \"export\"` property. Having configured this, the `npm run build` will create an `/out` folder with the compiled assets and pages of the app.\n\nNext we need an NPM script to serve the compiled app with a static server.\n\n```json {3,4} title=\"package.json - serve script\"\n{\n \"scripts\": {\n \"serve\": \"npx http-server --port 5432 out\",\n \"//...\": \"// other scripts\"\n },\n}\n```\n\nWe could either run this `serve` script ourselves to start the webserver before running our tests or even better, we can instruct Playwright to [use this webserver automatically](https://playwright.dev/docs/test-webserver#configuring-a-web-server). So let's do that in our `playwright.config.ts` file.\n\n```ts {3,5} title=\"playwright.config.ts - configured to use a custom server\"\nexport default defineConfig({\n //... other options\n\n // on CI, run the static server to serve the built app\n webServer: process.env.CI\n ? {\n command: \"npm run serve\",\n url: \"http://localhost:5432\",\n reuseExistingServer: true,\n timeout: 120 * 1000,\n }\n : undefined,\n})\n```\n\n\n\nIn order for Playwright to correctly detect the webserver is running ok, we need to make sure we have a valid index page at that address, so we need to add a `index.page.tsx` file in the `pages` folder.\n\n```tsx title=\"src/pages/index.page.tsx\"\nexport default function App() {\n return
Index page
;\n}\n```\n\nThis is just useful in the CI environment so that Playwright can detect the server is running and the app is served correctly.\n\n
\n\nNext, in order to run our tests as if we're in the CI environment, let's add a `test:ci` script, which is basically calling the `test` script but setting the `CI` environment variable to `true`.\n\n```json {3,4} title=\"package.json - test:ci script\"\n{\n \"scripts\": {\n \"test:ci\": \"CI=true npm run test\",\n \"test\": \"npx playwright test\",\n \"serve\": \"npx http-server --port 5432 out\",\n \"//...\": \"// other scripts\"\n },\n}\n```\n\n\nWe're now ready to run our tests against the production build of the NextJS app.\n\n```sh\nnpm run build && npm run test:ci\n```\n\n\nThis script first builds the NextJS static app and then runs the tests against it.\n\n## Configuring CI github actions \n\nWe're now ready to integrate our [testing workflow into CI via Github actions](https://playwright.dev/docs/ci-intro).\n\nCreate a YAML file `.github/workflows/test.yml` in the root of your project with the following content.\n\n```yaml {19,23} title=\".github/workflows/test.yml\"\nname: Playwright Tests\non:\n push:\n branches: [main, master]\n pull_request:\n branches: [main, master]\njobs:\n test:\n timeout-minutes: 60\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - uses: actions/setup-node@v4\n with:\n node-version: lts/*\n - name: Install dependencies\n run: npm ci\n - name: Build app\n run: npm run build\n - name: Install Playwright Browsers\n run: npx playwright install --with-deps\n - name: Run Playwright tests\n run: npm run test\n - uses: actions/upload-artifact@v4\n if: always()\n with:\n name: playwright-report\n path: playwright-report/\n retention-days: 30\n```\n\nWith this, you're ready to go! Push your changes to the main branch and see your tests running and passing in the CI environment. Go green! ๐ŸŸข\n\n## Demo repository\n\nYou can find the full setup in our [testing-setup-nextjs-playwright repo](https://github.com/infinite-table/testing-setup-nextjs-playwright/tree/main?tab=readme-ov-file). Check it out and give it a star if you find it useful.\n\n## Profit ๐Ÿš€\n\nWith this setup, you have a very convenient way to write your tests against real pages, loaded in a real browser, just like the end user experiences. And with the watch mode giving you instant feedback, you no longer have an excuse to not write tests.\n\nThis is the same setup we've been using for developing and testing the [Infinite Table React DataGrid](https://infinite-table.com) and it has been serving us really well.\n\nDataGrids are some of the most complex UI components one can build, so having a reliable tool that allowed us to iterate very quickly was crucial to us. This helped us add new features, while being confident that all of the existing core functionalities like row/column grouping, filtering, sorting, pagination, pivoting still work as expected.\n\nThe setup was a pivotal point in our development process and it's what gives us and our enterprise customers the peace of mind that the product is stable and reliable, both now and in the future.\n\n\n" + "excerpt": "Version `2.0.0` is a release that allowed us to polish many areas of the component and consolidate its existing features and APIs.", + "readingTime": "4 min read", + "content": "\nVersion `2.0.0` is a release that allowed us to polish many areas of the component and consolidate its existing features and APIs.\n\nWe hope this makes your experience with Infinite Table as your React DataGrid of choice even better.\n\nThough it doesn't add major new features, this version does improve the overall experience of using the component. In this article we're detailing the most important improvements this release brings.\n\n\n\n1๏ธโƒฃ [better support for sorting group columns](#1-better-support-for-sorting-group-columns)\n2๏ธโƒฃ [allows configuring the behavior when multiple sorting is enabled](#2-multi-sort-behavior)\n3๏ธโƒฃ [smarter column menus](#3-smarter-column-menus)\n4๏ธโƒฃ [improved support for boolean pivot columns](#4-improved-support-for-boolean-pivot-columns)\n5๏ธโƒฃ [better and more exhaustive APIs](#5-better-and-more-exhaustive-apis)\n\n\n\n\n\n## 1๏ธโƒฃ Better support for sorting group columns\n\nBefore version `2.0.0`, group columns were sortable, but only if the configured `groupBy` fields were bound to actual columns.\n\nThis release enables you to make group columns sortable even when other columns are not defined. For this to work, you have to specify a sortType as an array, so the column knows how to sort the group values.\n\n```tsx title=\"Configuring sortType for group columns\"\n\n groupColumn={{\n sortType: ['string', 'number'],\n field: 'firstName',\n defaultWidth: 150,\n }}\n groupRenderStrategy=\"single-column\"\n columns={columns}\n columnDefaultWidth={120}\n/>\n```\n\n\n\n## 2๏ธโƒฃ Multi sort behavior\n\nWe have introduced to allow you to configure how the component behaves when multiple sorting is enabled. Two options are available:\n\n- `append` - when this behavior is used, clicking a column header adds that column to the alredy existing sort. If the column is already sorted, the sort direction is reversed. In order to remove a column from the sort, the user needs to click the column header in order to toggle sorting from ascending to descending and then to no sorting.\n\n- `replace` - the default behavior - a user clicking a column header removes any existing sorting and sets that column as sorted. In order to add a new column to the sort, the user needs to hold the `Ctrl/Cmd` key while clicking the column header.\n\nmultiSortBehavior=\"replace\" is the new default behavior, and also a more natural one, so we recommend using it.\n\n\n\n## 3๏ธโƒฃ Smarter column menus\n\nColumn menus are now smarter - in previous versions of Infinite Table, users were able to hide the column that had the menu opened, and the menu would hang in its initial position.\n\nWhen this happens, in version `2.0.0`, the menu realigns itself to other existing columns, thus providing a better user experience.\n\n## 4๏ธโƒฃ Improved support for boolean pivot columns\n\nIt's pretty common to pivot by boolean columns, and this is now fully supported in Infinite Table. Previous to version `2.0.0`, the column headers for boolean pivot columns were not rendered correctly.\n\n\n\n## 5๏ธโƒฃ Better and more exhaustive APIs\n\nWe have improved our APIs, with new methods and fixes. Among other things, we've polished our [Column API](/docs/reference/column-api) to offer you the ability to do more with your columns. Previously there were things that were only possible to do if you had access to the internal state of the component, but now we've moved more things to the API.\n\nFor example, our column sorting code is now centralised, and using gives you the same action as clicking a column header (this was not the case previously).\n\nWe've added quite a few more methods to our APIs, here's some of the most important ones:\n\n- ColumnAPI.\n- ColumnAPI.\n- ColumnAPI.\n- ColumnAPI.\n- ColumnAPI.\n\n## Conclusion\n\nWe've been working on version `2.0.0` for a few months now and we hope you'll enjoy all the little details that make this version a better product, with all the improvements it brings in various areas of the component.\n\nWe'd love to hear your feedback, so please reach out to us via email at admin@infinite-table.com or follow us [@get_infinite](https://twitter.com/get_infinite) to keep up-to-date with news about the product.\n\nThank you ๐Ÿ™Œ\n" }, { - "filePath": "/blog/2024/04/22/the-best-testing-strategies-for-frontends", - "routePath": "/blog/2024/04/22/the-best-testing-strategies-for-frontends", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2024/04/22/the-best-testing-strategies-for-frontends.page.md", - "fileName": "the-best-testing-strategies-for-frontends.page.md", - "folderPath": "/blog/2024/04/22/", + "filePath": "/blog/2023/02/16/using-menus-in-infinite-table", + "routePath": "/blog/2023/02/16/using-menus-in-infinite-table", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2023/02/16/using-menus-in-infinite-table.page.md", + "fileName": "using-menus-in-infinite-table.page.md", + "folderPath": "/blog/2023/02/16/", "frontmatter": { - "title": "The best testing strategies for frontends", + "title": "Using Menus in Infinite Table", + "description": "Find out how to use menus in Infinite Table to customise the DataGrid to fit your needs: custom context menus, column menus and more.", + "author": [ + "admin" + ], + "date": "2023-02-16T00:00:00.000Z", + "authorData": { + "label": [ + "admin" + ] + } + }, + "excerpt": "_With version 1.1.0, our DataGrid now includes support for context menus, which are fully configurable so you can create custom menus for any cell in the table._", + "readingTime": "7 min read", + "content": "\n_With version 1.1.0, our DataGrid now includes support for context menus, which are fully configurable so you can create custom menus for any cell in the table._\n\n\n\n1๏ธโƒฃ are fully configurable\n\n2๏ธโƒฃ adjust their position based on the available space\n\n3๏ธโƒฃ can be used to create custom menus for any cell in the table\n\n4๏ธโƒฃ give you full access to the information in the cell or the whole DataGrid\n\n\n\n## How it works\n\nIn Infinite Table you can configure a context menu to be displayed when you right-click a cell by using the prop. Simply specify a function that returns an array of objects, each with `label` and `key` properties. Each object in the array is a row in the context menu - with the `label` being the displayed content and the `key` being a unique identifier for the menu row.\n\n```tsx title=\"Configuring_a_context_menu\"\nconst getCellContextMenuItems = ({ column, data, value }) => {\n if (column.id === 'currency') {\n return [\n {\n label: `Convert ${value}`,\n key: 'currency-convert',\n onAction: (key, item) => {\n alert('clicked ' + item.key);\n },\n },\n ];\n }\n\n if (column.id === 'age') {\n return null;\n }\n\n return [\n {\n label: `Welcome ${value}`,\n key: 'hi',\n },\n ];\n};\n\n data={data} primaryKey=\"id\">\n \n getCellContextMenuItems={getCellContextMenuItems}\n columns={columns}\n />\n
;\n```\n\nIn the function prop, you have access to all the information you need, in the first argument of the function:\n\n- `column` - the column on which the user right-clicked\n- `data` - the data object for the row the user right-clicked\n- `value` - the value of the cell on which the context menu has been triggered. This is generally `data[column.field]`, but it can be different if the column has a or \n- `rowInfo` - an object that contains more information about the row, like the `id` (the primary key) and the row index\n- `isGroupRow`\n- and more\n\n\n\nIf is specified and returns `null`, no custom context menu will be displayed, instead the default browser context menu will be shown (in this case, we do not call `preventDefault()` on the event object).\n\nIf returns an empty array, the default browser context menu will not be shown (in this case, we are calling `preventDefault()` on the event object), but also no custom context menu will be displayed, as there are no menu items to show.\n\n\n\n\n\nEach item on the context menu can specify an `onAction` function, which will be called when the user clicks on the menu item. The function will receive the `key` and the `item` as arguments.\n\nIn addition, since the menu items are returned from inside the `getCellContextMenuItems` function, the `onAction` callback has access to the same information as the `getCellContextMenuItems` function.\n\n\n\n\n## Configuring the context menu to have multiple columns\n\nIn the above example, notice each context menu item has only one cell, where the `label` property is displayed.\n\nHowever, Infinite Table for React allows you to create more complex menus, with multiple columns.\n\nIn order to do this, use the same prop, but return an object, with `columns` and `items`\n\n```tsx\nconst getCellContextMenuItems = () => {\n return {\n columns: [{ name: 'label' }, { name: 'lcon' }],\n items: [\n {\n label: 'Welcome',\n icon: '๐Ÿ‘‹',\n key: 'hi',\n },\n {\n label: 'Convert',\n icon: '๐Ÿ”',\n key: 'convert',\n },\n ],\n };\n};\n```\n\n\n\nWhen is used to configure the column menus, each column `name` should have a corresponding property in the objects returned in the `items` array (each object also needs to keep the `key` property).\n\nAlso, we recommend keeping a column named `label`.\n\n\n\n\n\n## Smart positioning\n\nContext menus in Infinite Table are smart enough to adjust their position based on the available space relative to the mouse-click coordinates. The menu will always try to fit inside the grid viewport and to look for the best position that will not cause the menu to be cut off or overflow outside the DataGrid.\n\nThe same algorithm is applied to column menus and also to filter menus (the menu displayed when a filter is shown and the user wants to change the filter operator).\n\n## Context menus outside cells, for the table body\n\nThere are scenarios when you want to display a context menu even when you right-click outside a cell, but inside the table body - for those cases, you can use (in fact, you can use the prop for all context menus).\n\nThe signature of is almost identical with that of , with the exception that cell-related information can be undefined - if the user didn't right-click a cell, but somewhere else in the table body.\n\n\n\nIn the example above, if you click outside a cell, a menu with a single item will be displayed - `Add Item`. If you click on a cell, the menu will be different, and will show information about the clicked cell.\n\n## Column menus\n\nBesides context menus, the DataGrid also supports menus for columns, that allow you to sort/unsort, pin/unpin, clear filtering and toggle column visibility.\n\nJust like context menus, the column menus can also be fully customised, by using the prop.\n\n```tsx title=\"Customizing-column-menu\"\nfunction getColumnMenuItems(items, { column }) {\n if (column.id === 'firstName') {\n // you can adjust the default items for a specific column\n items.splice(0, 0, {\n key: 'firstName',\n label: 'First name menu item',\n onClick: () => {\n console.log('Hey there!');\n },\n });\n }\n\n // or for all columns\n items.push({\n key: 'hello',\n label: 'Hello World',\n onClick: () => {\n alert('Hello World from column ' + column.id);\n },\n });\n return items;\n}\n```\n\n\n\nThe first argument passed to the prop is the array of items that is displayed by default in the column menu.\n\nYou can either modify this array and return it or you can return another totally different array.\n\n\n\n\n\nAs with context menus, positioning column menus is also smart - the menu will always try to fit inside the grid viewport, so it will align to the right or the left of the column, depending on the available space.\n\n## Conclusion\n\nIn this article, we've explained just some of the scenarios that are now possible with Infinite Table for React, by using the new context and column menus.\n\n\n\nLearn more about working with context menus.\n\n\nConfiguring column menus to fit your needs - read more.\n\n\n\nWe hope you'll use these functionalities to build amazing DataGrids for your applications, that are fully tailored to your needs.\n\nIf you find any issues or have any questions, please reach out to us on [Twitter](https://twitter.com/infinite_table) or in the [GitHub Discussions](https://github.com/infinite-table/infinite-react/discussions) or [issues](https://github.com/infinite-table/infinite-react/issues).\n\nWe're happy to help and improve how you work with the component - we want to make it very easy and straight-forward to use it and are looking for ways to simplify our APIs to **achieve more with less**.\n" + }, + { + "filePath": "/blog/2024/02/02/how-to-configure-default-sorting", + "routePath": "/blog/2024/02/02/how-to-configure-default-sorting", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2024/02/02/how-to-configure-default-sorting.page.md", + "fileName": "how-to-configure-default-sorting.page.md", + "folderPath": "/blog/2024/02/02/", + "frontmatter": { + "title": "How to customise the DataGrid default sorting", "author": "admin", - "date": "2024-04-22T00:00:00.000Z", + "date": "2024-02-02T00:00:00.000Z", "authorData": { "label": "admin" } }, - "excerpt": "In our previous article, we focused on documenting [the best testing setup for frontends](https://infinite-table/blog/2024/04/18/the-best-testing-setup-for-frontends-playwright-nextjs), which used Playwright and Next.js. You can check out the repository [here](https://github.com/infinite-table/testing-setup-nextjs-playwright) where you can find the full setup.", - "readingTime": "5 min read", - "content": "\nIn our previous article, we focused on documenting [the best testing setup for frontends](https://infinite-table/blog/2024/04/18/the-best-testing-setup-for-frontends-playwright-nextjs), which used Playwright and Next.js. You can check out the repository [here](https://github.com/infinite-table/testing-setup-nextjs-playwright) where you can find the full setup.\n\nWe consider the combination described above, Playwright + NextJS being the best combo around for testing frontends. Ok, you can switch out NextJS with other meta-framework that offers file-system routing, but the idea is the same: every test is made of 2 sibling files, with the same name but different extension.\n\nIn the test files, Playwright is configured to navigate automatically to the page being tested, so no need for adjustments if you move files around. This saves you a lot of time and hustle and makes your tests more robust and focused.\n\nBut in addition to end-to-end testing with Playwright and NextJS, there are other forms of testing out there which are available and can be used to complement your testing strategy. In this article, we'll focus on what we think are the best testing strategies for frontends. So here are a few options:\n\n - E2E testing\n - Component testing \n - Visual regression testing\n - Unit testing\n\nFor each of those options there are plenty of tools you can use, each with its own pros and cons.\n\n## E2E testing\n\nWith the advent of tools like [Puppeteer](https://pptr.dev/) and now [Playwright](https://playwright.dev/), end-to-end testing has become much easier and more reliable. For anyone who's used Selenium in the past, you know what I'm talking about. \nPuppeteer has opened the way in terms of E2E tooling, but Playwright has taken it to the next level and made it easier to await for certain selectors or conditions to be fulfilled (via [locators](https://playwright.dev/docs/locators)), thus making tests more reliable and less flaky.\nAlso, it's a game changer that it introduced a test-runner - this made the integration between the headless browser and the actual test code much smoother.\n\n### Reasons to use E2E\n\nEnd to end testing is actually a real browser, so the closest possible environment to what your app will be using.\n\nNo need to fake the page with JSDOM, no need to only do shallow rendering in React. Just use the platform!\n\n## Component testing\n\nProbably [Enzyme](https://enzymejs.github.io/enzyme/) was the first to popularize component testing in React by doing shallow rendering and expecting some things to be there in the React component tree. Then [React Testing library](https://testing-library.com/) came and took component testing to a whole new level.\n\nThe tools are great for what they're doing, but with the advent of better tooling, we should move on to better ways of testing. With the tools we have now in 2024, there's no more need to use JSDOM and simulate a browser enviroment. It used to be very cumbersome to start a headless browser back in the day, but now with Playwright/Puppeteer, it's a breeze.\n\n## Visual regression testing\n\nAlso in the days before Playwright was around, there was much hype about visual regression testing. It was very very tempting to use it - who wouldn't want to have a tool that automatically checks if the UI has (mistakenly) changed? It might fit a few use-cases, but in general, it's not worth the effort of maintaining all those tests for any little change in the UI. True, you can set thresholds for the differences, but it's still a lot of work to maintain it, especially in highly dynamic frontends and teams.\n\nWith better CSS approaches like [TailwindCSS](https://tailwindcss.com/) and [Vanilla Extract](https://vanilla-extract.style/) (which we're heavily using) it's much easier to maintain the UI and make sure it doesn't change unexpectedly. No more conflicting CSS classes, much less CSS specificity issues and much less CSS code in general.\n\nOne of the troubles in large and tangled CSS codebases is that it's write-only. Well, not write-only per se, but teams are generally afraid to remove a line of CSS cause it might break someone else's code or it might still be used.\n\nWith [Vanilla Extract](https://vanilla-extract.style/) you can be sure that if you remove a CSS class, it's not used anywhere else in the codebase. It's been a game changer in terms of CSS maintainability and productivity for us at [Infinite Table](https://infinite-table.com/).\n\nSo with all those tools to make styling easier, the need for visual regression testing has dropped significantly.\n\n\n## Unit testing\n\nUnit testing will be here to stay - at least if besides your UI, your app has some significant business logic. We're using it in combination with E2E testing to make sure complex use-cases work as expected.\n\nFor example, our [logic for row grouping](https://infinite-table.com/docs/learn/grouping-and-pivoting/grouping-rows) is fully tested with unit tests. We do have E2E tests for it, but with unit tests we can have full coverage of all the nitty-gritty details of the grouping logic. We do the same for pivoting and aggregating. Column sizing and column grouping are also covered with unit tests.\n\nWe think there's always going to be a place for unit testing to ensure robustness and reliability of the app under even the most complex use-cases and user inputs.\n\n## Conclusion\n\nIn our experience, the best testing strategy for modern frontends is a combination of E2E testing (using Playwright+NextJS), and unit testing. Visual regression testing is not worth the effort in our opinion, especially with the advent of better CSS tooling like TailwindCSS and [Vanilla Extract](https://vanilla-extract.style/).\n\nThough we used shallow component testing in the past, we're not going back to it - mocking the DOM and the browser is no longer worth it when you can use a real browser with Playwright.\n\nWe hope this article has been helpful in guiding you towards the best testing strategy for your frontend. If you have any questions or comments, feel free to reach out to us at [admin@infinite-table.com](mailto:admin@infinite-table.com). We're always happy to help! \n" + "excerpt": "In this article, we'll show you how easy it is to configure the default sorting for the React DataGrid.", + "readingTime": "3 min read", + "content": "\nIn this article, we'll show you how easy it is to configure the default sorting for the React DataGrid.\n\n## Using the `defaultSort` prop on the DataSource\n\nSorting is configured on the DataGrid `` component. For this, you use the prop, as either an object, for sorting by a single column, or an array of objects, for sorting by multiple columns.\n\n```tsx title=\"Specifying a default sort order\"\n// sort by country DESC and salary ASC\nconst defaultSortInfo={[\n { field: \"country\", dir: -1 },\n { field: \"salary\", dir: 1 },\n]}\n\n\n```\n\nThat's it! Now, when the DataGrid is first rendered, it will be sorted by the `country` column in descending order, and then by the `salary` column in ascending order.\n\n\n\nFor sorting to work properly for numeric columns, don't forget to specify `type: \"number\"` in the column configuration.\n\n\n\n\n\n\n\nWhen the is an array, the DataGrid will know you want to allow sorting by multiple columns.\n\nSee our page on [multiple sorting](/docs/learn/sorting/multiple-sorting) for more details.\n\n\n\n## Local vs remote sorting\n\nThe above example uses local sorting. If you don't explicitly specify a that changes in the should trigger a reload (via the prop), the sorting will be done locally, in the browser.\n\nHowever, you can also have remote sorting - for this scenario, make sure you use shouldReloadData.sortInfo=true.\n\n\n\nIn this case, it's your responsability to send the `sortInfo` to your backend using the prop of the DataSource - your `data` function will be called by the DataGrid whenever sorting changes. The arguments the function is called with will include the sort information (along with other details like filtering, grouping, aggregations, etc).\n\n```tsx\nconst dataSource: DataSourceData = ({ sortInfo }) => {\n if (sortInfo && !Array.isArray(sortInfo)) {\n sortInfo = [sortInfo];\n }\n const args = [\n sortInfo\n ? 'sortInfo=' +\n JSON.stringify(\n sortInfo.map((s) => ({\n field: s.field,\n dir: s.dir,\n })),\n )\n : null,\n ]\n .filter(Boolean)\n .join('&');\n\n return fetch('https://your-backend.com/fetch-data?' + args)\n .then((r) => r.json())\n .then((data: Developer[]) => data);\n};\n```\n\n\n\n\n\n## Responding to sorting changes\n\nWhen the user changes the sorting in the React DataGrid UI, the DataSource function is called for you, with the new sort information.\n\nHowever, you might want to respond in other ways - for this, you can use callback prop.\n\n\n\nIf you use the controlled instead of the uncontrolled , you will need to configure the callback to respond to sorting changes and update the UI.\n\n\n\n## Using the column sort info for rendering\n\nAt runtime, you have access to the column sort information, both in the column header - see and in the column cells - see .\n\n\n\nFor example, you can customise the icon that is displayed in the column header to indicate the sort direction.\n\nVia the you have full access to how the column header is rendered and can use the sorting/filtering/grouping/aggregation/pivoting information of that column to customise the rendering.\n" + }, + { + "filePath": "/blog/2023/12/11/quick-guide-filtering-the-datagrid", + "routePath": "/blog/2023/12/11/quick-guide-filtering-the-datagrid", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2023/12/11/quick-guide-filtering-the-datagrid.page.md", + "fileName": "quick-guide-filtering-the-datagrid.page.md", + "folderPath": "/blog/2023/12/11/", + "frontmatter": { + "title": "Quick Guide - Filtering the DataGrid", + "author": "admin", + "draft": true, + "date": "2023-12-11T00:00:00.000Z", + "authorData": { + "label": "admin" + } + }, + "excerpt": "This is the first article in a series of quick guides that will help you get started with the DataGrid, each focusing on a specific feature. In this article, we will learn how to use filtering in the DataGrid.", + "readingTime": "1 min read", + "content": "\nThis is the first article in a series of quick guides that will help you get started with the DataGrid, each focusing on a specific feature. In this article, we will learn how to use filtering in the DataGrid.\n\n### Applying Filters on the DataSource\n\nYou apply filters on the `DataSource` component\n\n```tsx {4-11} title=\"Specifying an initial filter value for the DataSource\"\n\n data={...}\n primaryKey=\"id\"\n defaultFilterValue={[\n field: 'salary',\n filter: {\n operator: 'gt',\n value: 50000,\n type: 'number',\n },\n ]}\n/>\n```\n" + }, + { + "filePath": "/blog/2024/01/23/how-to-customise-datagrid-loading-state", + "routePath": "/blog/2024/01/23/how-to-customise-datagrid-loading-state", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2024/01/23/how-to-customise-datagrid-loading-state.page.md", + "fileName": "how-to-customise-datagrid-loading-state.page.md", + "folderPath": "/blog/2024/01/23/", + "frontmatter": { + "title": "How to customise the DataGrid loading state", + "author": "admin", + "date": "2024-01-23T00:00:00.000Z", + "authorData": { + "label": "admin" + } + }, + "excerpt": "We're starting a series of short `\"How to\"` articles that are very focused and show how to achieve a specific thing with the Infinite Table DataGrid.", + "readingTime": "2 min read", + "content": "\nWe're starting a series of short `\"How to\"` articles that are very focused and show how to achieve a specific thing with the Infinite Table DataGrid.\n\nIn this article, we'll document how to customise the DataGrid loading state.\n\n## Customising the loading text\n\nFirst off, you can customise the text that is displayed when the DataGrid is loading data. By default, the DataGrid displays a `\"Loading\"` text, but you can customise it to anything you want (even JSX, not only string values).\n\n```tsx title=\"Customising the loading text\" {9}\nconst developers: Developer = [\n { id: '1', firstName: 'Bob' },\n { id: '2', firstName: 'Bill' },\n]\n\n// make sure to add \"loading\" to the DataSource so you see the loading state\n loading data={developers} primaryKey=\"id\">\n \n loadingText={\n Loading your data ...\n }\n columns={{\n firstName: {\n field: 'firstName',\n },\n id: {\n field: 'id',\n }\n }}\n {...props}\n />\n\n```\n\n\nFor the value of you can use JSX, not only strings.\n\n\n\n\n## Customising the loading component - the `LoadMask`\n\nIn addition to the loading text, you can also customise the `LoadMask` component. This is the component that is displayed when the DataGrid is loading data. By default, it's a `
` with `width: 100%; height: 100%; zIndex: 1; display: flex` that contains the loading text.\n\nYou do this by overriding the prop in your Infinite Table configuration.\n\n```tsx title=\"Customising the LoadMask component\" {7,15}\n// make sure to add \"loading\" to the DataSource so you see the loading state\nexport default function App() {\n return (\n loading data={developers} primaryKey=\"id\">\n \n components={{\n LoadMask,\n }}\n columns={columns}\n />\n \n );\n}\n\nfunction LoadMask() {\n return (\n \n \n Loading App ...\n
\n \n );\n}\n```\n\n\n" + }, + { + "filePath": "/blog/2024/02/26/master-detail-now-available-in-react-datagrid", + "routePath": "/blog/2024/02/26/master-detail-now-available-in-react-datagrid", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2024/02/26/master-detail-now-available-in-react-datagrid.page.md", + "fileName": "master-detail-now-available-in-react-datagrid.page.md", + "folderPath": "/blog/2024/02/26/", + "frontmatter": { + "title": "Master detail is now available in the Infinite React DataGrid", + "author": "admin", + "date": "2024-02-26T00:00:00.000Z", + "authorData": { + "label": "admin" + } + }, + "excerpt": "Today is a big day for the Infinite React DataGrid - we're excited to announce that the master detail feature is now available!", + "readingTime": "4 min read", + "content": "\nToday is a big day for the Infinite React DataGrid - we're excited to announce that the master detail feature is now available!\n\nWith this addition, our DataGrid is now enterprise-ready! We know master-detail scenarios are needed in many business applications, and we're happy to provide this feature to our users starting today!\n\n\n\n1๏ธโƒฃ [support for multiple levels of master-detail & rendering custom content](#what-can-you-do-with-master-detail)\n2๏ธโƒฃ [configurable detail height](#configurable-detail-height)\n3๏ธโƒฃ [control over expand/collapse state](#configurable-expandcollapse-state)\n4๏ธโƒฃ [caching mechanism for detail DataGrids](#master-detail-caching)\n\n\n\n## What can you do with master-detail?\n\nMaster-detail allows you to have rows in the DataGrid that expand to show more details. This can be used to show more information about the row, or even to show another DataGrid with related data.\n\nYou can render basically anything in the detail row - it doesn't need to be another DataGrid. However, if you do want to show another DataGrid, you can, and you can do that at any level of depth.\n\nIn the detail `` component, you have access to the master row, so it will be very easy to load related data based on the master row the user expands.\n\n\n\n## Configurable detail height\n\nOur master-detail implementation is very configurable - you can control the height of the row details, the expand/collapse state, and much more.\n\nThe height of the row details is fully adjustable - see the prop to learn about all the options you have.\n\n\n\nAs seen in the snippet above, it's also really easy to control the expand/collapse state of the row details. You can choose to have some rows expanded by default so details of those rows will be visible from the start.\n\n## Configurable expand/collapse state\n\nUsing the , you can control (in a declarative way) which rows are expanded and which are collapsed. In addition, if you prefer the imperative approach, we also have an [API to work with row details](/docs/reference/row-detail-api).\n\nIf you have some rows with details and some without, that's also covered. Use the to control which rows will have details and which will not.\n\n\n\nAnother important configuration is choosing the column that has the row detail expand/collapse icon. Use the prop on the column that needs to display the expand/collapse icon. If this prop is a function, it can be used to customize the icon rendered for expanding/collapsing the row detail.\n\n\n\n## Master detail caching\n\nBy far the most common scenario will be to render another DataGrid in the detail row.\n\nFor such cases we offer a caching mechanism that will keep the state of the detail DataGrid when the user collapses and then expands the row again.\n\n\n\nTo enable caching, use the prop.\n\nIt can be one of the following:\n\n- `false` - caching is disabled - this is the default\n- `true` - enables caching for all detail DataGrids\n- `number` - the maximum number of detail DataGrids to keep in the cache. When the limit is reached, the oldest detail DataGrid will be removed from the cache.\n\n\n\n\n\n\n\nThis example will cache the last 5 detail DataGrids - meaning they won't reload when you expand them again.\nYou can try collapsing a row and then expanding it again to see the caching in action - it won't reload the data.\nBut when you open up a row that hasn't been opened before, it will load the data from the remote location.\n\n\n\n\n\nRead our docs on [caching detail DataGrids](/docs/learn/master-detail/caching-detail-datagrid) to learn more how you can use this feature to improve the user experience.\n" }, { "filePath": "/blog/2024/03/08/how-to-select-cells-and-use-cell-selection", @@ -1495,6 +1592,61 @@ "readingTime": "3 min read", "content": "\nThe article will cover some popular cell-selection scenarios in Infinite React DataGrid.\n\n## Multiple cell selection\n\nBy far, the most common use-case for cell selection is multiple cell selection.\nFor this, you need to configure the prop on the `` component to use `\"multi-cell\"`.\n\n\n\nIn addition, if you want to specify a default value for cell selection, you can use the prop - or the controlled alternative , in which case also make sure you update the value when is called.\n\n\n\n```tsx\n\n```\n\n\n\nWhen multiple cell selection is configured in the React DataGrid, the user can select cells by `CMD`/`CTRL` clicking to add a single cell to the selection or by `SHIFT` clicking to select a range of cells.\n\n## Showing a chart based on selected cells\n\nLet's implement a common use-case for multiple cell selection - showing charts based on the selected cells, for example, a bar chart, with names on the x axis and ages on the y axis.\n\n\n\nIn this example, to retrieve the values from the selected cells, we used the from the [cell selection API](/docs/reference/cell-selection-api).\n\n## Cell selection format\n\nThe prop is an object with the following shape:\n\n- `defaultSelection` - `boolean` - whether or not cells are selected by default.\n- either:\n - `selectedCells`: `[rowId, colId][]` - an array of cells that should be selected (this is combined with `defaultSelection: false`)\n- or\n - `deselectedCells`: `[rowId, colId][]` - an array of cells that should be deselected (this is combined with `defaultSelection: true`)\n\n\n\nWhen `defaultSelection` is `true`, you will only need to specify the `deselectedCells` prop.\n\nAnd when `defaultSelection` is `false`, you will only need to specify the `selectedCells` prop.\n\n\n\nIn this way, you can either specify which cells should be selected or which cells should be deselected - and have a default that matches the most common case.\n\n\n\nThe `selectedCells`/`deselectedCells` are arrays of `[rowId, colId]` tuples. The `rowId` is the `id` of the row (the primary key), and the `colId` is the `id` of the column (the identifier of the column in the prop).\n\n\n\n## Using include-lists and exclude-lists for specifying cell selection\n\nAs already demonstrated in the previous snippet, you can pass a default value for cell selection.\n\nIn addition to listing or excluding specific cells from selection, you can use wildcards:\n\n```tsx title=\"Include-list: selecting all cells in a column\"\nconst defaultCellSelection = {\n defaultSelection: false, // all cells are deselected by default\n selectedCells: [\n // all cells in the stack column\n ['*', 'stack'],\n // also this specific cell\n ['row2', 'firstName'],\n ],\n};\n```\n\n```tsx title=\"Include-list: selecting all cells in a row\"\nconst defaultCellSelection = {\n defaultSelection: false, // all cells are deselected by default\n selectedCells: [\n // all cells in the row\n ['row1', '*'],\n // also this specific cell\n ['row2', 'firstName'],\n ],\n};\n```\n\n```tsx title=\"Exclude-list: selecting everything except a column\"\nconst defaultCellSelection = {\n defaultSelection: true, // all cells are selected by default\n deselectedCells: [['*', 'stack']],\n};\n```\n\n\n\n## Single cell selection\n\nSingle cell selection is not common - what you probably want to use in this case is the prop to emulate single cell selection - but that's basically cell navigation.\n" }, + { + "filePath": "/blog/2024/04/18/the-best-testing-setup-for-frontends-playwright-nextjs", + "routePath": "/blog/2024/04/18/the-best-testing-setup-for-frontends-playwright-nextjs", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2024/04/18/the-best-testing-setup-for-frontends-playwright-nextjs.page.md", + "fileName": "the-best-testing-setup-for-frontends-playwright-nextjs.page.md", + "folderPath": "/blog/2024/04/18/", + "frontmatter": { + "title": "The best testing setup for frontends, with Playwright and NextJS", + "author": "admin", + "date": "2024-04-18T00:00:00.000Z", + "authorData": { + "label": "admin" + } + }, + "excerpt": "We want to share with you the best testing setup we've experienced - and this includes using [Playwright](https://playwright.dev/) and [NextJS](https://nextjs.org/). It's a setup we've come up with for Infinite React DataGrid, which is a complex component, with lots of things to test, but this configuration has helped us ship with more confidence and speed.", + "readingTime": "13 min read", + "content": "\nWe want to share with you the best testing setup we've experienced - and this includes using [Playwright](https://playwright.dev/) and [NextJS](https://nextjs.org/). It's a setup we've come up with for Infinite React DataGrid, which is a complex component, with lots of things to test, but this configuration has helped us ship with more confidence and speed.\n\n## What you should expect from a testing setup\n\n### Fast feedback \n\nโšก๏ธ Quick โšก๏ธ feedback is a no-brainer, since without a fast turnaround, devs will not have the patience to run the tests and will move on to the next \"burning\" issue or to the next cup of coffee.\n\nAlso, you can't run all the test suite at once, so you need to be able to run only the tests that are relevant to the changes you've made. This has long been available in unit-testing frameworks, but it's not so common in end-to-end testing, when loading a webpage and rendering an actual component is involved.\n\nIn this article we want to show you how we achieved fast feedback that allows rapid developer iterations.\n\n### Stability and predictability\n\nYou don't need flaky tests that fail randomly - it's the last thing you want when doing a release, or even during development.\nWaiting for an element to appear on page or an animation to finish or an interaction to complete is a common source of flakiness in end-to-end tests, but Playwright gives you the tools to address these issues - thank you [Playwright locators](https://playwright.dev/docs/locators) ๐Ÿ™ and other playwright testing framework features.\n\n### Ease of maintenance and debugging\n\nAnother crucial point when you setup a testing framework and start writing tests is how easy is to write a new test, to inspect what is being tested and to reproduce failing tests. All these should be as easy as opening loading a URL in a browser - this is exactly what this setup gives you, with NextJS and Playwright playing very well together.\nWhen one of your tests fails, Playwright outputs a command you can run to reproduce the exact failure and actually see the UI at the moment of the failure, with the ability to navigate through the test timeline and see what happened before the failure.\n\n## Setting up NextJS and Playwright\n\n### Step 1 - creating the NextJS app\n```sh\n$ npx create-next-app@latest\n```\n\nYou're being asked a few questions. For `Would you like to use src/ directory?` we chose `Yes`. Also, we're using TypeScript.\n\nWhen you run this command, make sure for this question `Would you like to use App Router?` you reply `No`, as you want to use file-system routing to make it very easy and intuitive to add new pages and tests.\n\n\nCheck out our repo for this stage of the setup - [Step 1 - setting up NextJS](https://github.com/infinite-table/testing-setup-nextjs-playwright/tree/01-setup-nextjs).\n\n\n\n\nBefore you go to the next step, you can configure your `next.config.mjs` to use the `.page` extension for your pages.\n\n```js\nconst nextConfig = {\n reactStrictMode: true,\n pageExtensions: [\"page.tsx\", \"page.ts\", \"page.js\"],\n};\nexport default nextConfig\n```\n\nThis is useful so NextJS will only compile those files as pages that your tests will be targeting, and not all the files in the `pages` folder, which will also contain your tests.\n\nSo you know all your `.page` files are pages that your tests will be run against and all your `.spec` files are tests (see next step).\n\n\n\n### Step 2 - setting up Playwright\n\n```sh\n$ npm init playwright@latest\n```\n\nAgain a few questions about your setup.\n\n`Where to put your end-to-end tests?` - choose `src/pages` - which makes your NextJS pages folder the place where you put your end-to-end tests.\n\nThis script installs `@playwright/test` and creates a `playwright.config.ts` file with the default configuration. Most importantly, the `testDir` is configured to `./src/pages`.\n\nBy default, all `.spec` files in the `testDir` (which is set to `src/pages`) will be run as tests.\n\n\nCheck out our repo for this stage of the setup - [Step 2 - setting up Playwright](https://github.com/infinite-table/testing-setup-nextjs-playwright/tree/02-setup-playwright).\n\n\nThere are some additional configurations you might want to do in this step.\nYou probably want to change the default `reporter` from `'html'` to `'list'` in your `playwright.config.ts` - the `'html'` reporter will open a browser window with the test results, which you might not prefer. You'd rather see the results in the terminal.\n\n ```ts {3} title=\"Configure the reporter in playwright.config.ts\"\nexport default defineConfig({\n testDir: \"./src/pages\",\n reporter: \"list\", // the 'html' reporter will open a browser window with the test results\n // ...\n})\n ```\n\n\n\nFor now, you might want to only run your tests in one browser, so comment out any additional entries in the `projects` array in your `playwright.config.ts` file - that controls the devices that will be used in your tests.\n\n\n\nThe last piece of the puzzle before running your first test with Playwright is defining the `test` script in your `package.json`.\n\n```json {4} title=\"package.json\"\n{\n \"name\": \"testing-setup-nextjs-playwright\",\n \"scripts\": {\n \"test\": \"npx playwright test\",\n \"dev\": \"next dev\",\n \"build\": \"next build\",\n },\n}\n```\n\nExecuting the `npm run test` command will run the tests in the `src/pages` folder - for now, you should have a single file, `example.spec.ts`, which was generated by the `npm init playwright` command.\n\n![Playwright test output](/blog-images/step-2-initial-results.png)\n\nYour initial test file was something very basic. This file is importing the `test` (and `expect`) function from `@playwright/test` - and this is what you're using to define tests (and write assertions).\n\n```ts {1} title=\"example.spec.ts\"\nimport { test, expect } from \"@playwright/test\";\n\ntest(\"has title\", async ({ page }) => {\n await page.goto(\"https://playwright.dev/\");\n\n // Expect a title \"to contain\" a substring.\n await expect(page).toHaveTitle(/Playwright/);\n});\n```\n\n### Step 3 - configuring the naming convention in Playwright to open the right pages\n\nThis step is probably the most important one in your configuration. Normally your tests will open webpages before you start testing - but this is not something you want to do explicitly in your project. Rather, you want your tests to automatically navigate to the corresponding page for the test. This is what this step is achieving - and we're using [Playwright fixtures](https://playwright.dev/docs/test-fixtures) to do this.\n\nThink of a fixture as some code that's configuring the testing environment for each of your tests.\nA fixture will extend the `test` function from `@playwright/test` with additional functionalities. Mainly, we want before every test to open the correct page, without writing this explicitly in every test. Based on the location of the test file in the file system, we want to navigate to a webpage for it and we assume it will have the same path as the test file. This is possible because NextJS is configured to use file-system routing.\n\n```ts {1,3-5} title=\"Defining the fixture file - test-fixtures.ts\"\nimport {\n test as base,\n expect,\n PlaywrightTestArgs,\n PlaywrightTestOptions,\n Page,\n} from \"@playwright/test\";\n\nexport * from \"@playwright/test\";\n\nexport const test = base.extend<\n PlaywrightTestArgs &\n PlaywrightTestOptions\n>({\n //@ts-ignore\n page: async ({ baseURL, page }, use, testInfo) => {\n const testFilePath = testInfo.titlePath[0];\n const fileName = testFilePath.replace(\".spec.ts\", \"\");\n const url = `${baseURL}${fileName}`;\n\n // navigate to the corresponding page for this test\n await page.goto(url);\n\n await use(page);\n },\n});\n```\n\nWe'll give this fixture file the name `test-fixtures.ts` and put it in the root of the project.\n\nNow instead of importing the `test` function from `@playwright/test` we want to import it from the `test-fixtures.ts` file - we'll do this in all our tests. To make this easier, let's also define a path alias in the `tsconfig.json` file.\n\n```json {4} title=\"tsconfig.json\"\n{\n \"compilerOptions\": {\n \"paths\": {\n \"@playwright/test\": [\"test-fixtures.ts\"],\n }\n }\n}\n```\n\nWe're ready to write our first test page in NextJS and use the new fixture in the Playwright test.\n\n```tsx title=\"src/pages/example.page.tsx\"\nexport default function App() {\n return
Hello world
;\n}\n```\n\n```ts {1} title=\"src/pages/example.spec.ts\"\nimport { test, expect } from \"@testing\"; // notice the import\n\ntest(\"Main example has corrent content\", async ({ page }) => {\n // notice we don't need to navigate to the page, this is done by the fixture\n await expect(await page.innerText(\"body\")).toContain(\"Hello world\");\n});\n```\n\nFor our tests against the NextJS app, we obviously need to start the app.\n\nLet's configure a custom port of `5432` in the package.json `dev` script.\n```json {3} title=\"package.json\"\n{\n \"scripts\": {\n \"dev\": \"next dev --port 5432\",\n \"test\": \"npx playwright test\"\n }\n //...\n}\n```\nWe need to use the same port in the Playwright configuration file.\nAlso we'll use a smaller test `timeout` (the default is 30s).\n\n```ts {9,11} title=\"playwright.config.ts\"\nimport { defineConfig } from \"@playwright/test\";\n/**\n * See https://playwright.dev/docs/test-configuration.\n */\nexport default defineConfig({\n testDir: \"./src/pages\",\n reporter: \"list\",\n use: {\n baseURL: \"http://localhost:5432/\",\n },\n timeout: process.env.CI ? 10000 : 4000,\n // ... more options\n});\n\n```\n\nWe're now ready to roll!\n\n`npm run dev` will run NextJS and `npm run test` will run the tests against your NextJS app.\n\n\n\nTo make the setup easier, avoid using `index.page.tsx` pages in NextJS - give your pages another name, to avoid issues with directory index pages in tests. This can easily be solved in the test fixture, but for the sake of clarity and brevity we're not doing it now.\n\n\n\n\n\n\nCheck out our repo for this stage of the setup - [Step 3 - configuring the Playwright fixture and naming convention](https://github.com/infinite-table/testing-setup-nextjs-playwright/tree/03-configure-naming-convention).\n\n\n\n### Step 4 - adding watch mode\n\nAs we mentioned initially, no testing setup is great unless it gives you very fast feedback. For this, we obviously need watch mode.\n\nWe want to be able to re-run tests when our test code has changed, but even better, when our NextJS page has changed - so the page the test is running against.\nNextJS has watch mode built-in in dev mode, so whenever a page is changed, it's recompiled and the browser is served the updated page. We'll use this in our advantage, so tests will always see the latest version of the page.\nThis means the last piece of the puzzle is to make Playwright re-run the tests when the page has changed or the test itself has changed.\n\nFor this, we'll use [`chokidar`](https://www.npmjs.com/package/chokidar) - more specifically the [`chokidar-cli`](https://www.npmjs.com/package/chokidar-cli) package. `chokidar` is probably the most useful file watching library for the nodejs ecosystem and it will serve us well.\n\n```json {4} title=\"package.json\"\n{\n \"scripts\": {\n \"test\": \"npx playwright test\",\n \"test:watch\": \"chokidar '**/*.spec.ts' '**/*.page.tsx' -c 'test_file_path=$(echo {path} | sed s/page.tsx/spec.ts/) && npm run test -- --retries=0 ${test_file_path}'\"\n }\n}\n```\n\nThe `test:watch` script is watching for changes in `.spec.ts` files and `.page.tsx` files and whenever there's a change in one of those files, it's re-running the respective test. (When a change was found in a `.page.tsx` file, we're using `sed` to replace the `.page.tsx` extension with `.spec.ts`, because we want to pass the test file to the `npm run test` command so it knows what test to re-run.)\n\n\n\nThe above `test:watch` script was written for MacOS (and Unix-like systems). If you're using Windows, you might need to adjust the command to achieve the same result.\n\n\n\n\n\nDon't forget to run `npm run dev` before running `npm run test` or `npm run test:watch` - you need the NextJS app running to be able to run the tests. After all, that's what you're testing ๐Ÿ˜….\n\n\n\n### Step 5 - running tests on production build\n\nIn the last step, we want to build a production build of the NextJS app and run the tests against it. \n\nSo first let's configure the `next.config.mjs` file to build a static site when `npm run build` is run.\n\n```js {3} title=\"next.config.mjs - configured to export a static site\"\nconst nextConfig = {\n reactStrictMode: true,\n output: \"export\",\n pageExtensions: [\"page.tsx\", \"page.ts\", \"page.js\"],\n};\n\nexport default nextConfig;\n```\nNotice the `\"output\": \"export\"` property. Having configured this, the `npm run build` will create an `/out` folder with the compiled assets and pages of the app.\n\nNext we need an NPM script to serve the compiled app with a static server.\n\n```json {3,4} title=\"package.json - serve script\"\n{\n \"scripts\": {\n \"serve\": \"npx http-server --port 5432 out\",\n \"//...\": \"// other scripts\"\n },\n}\n```\n\nWe could either run this `serve` script ourselves to start the webserver before running our tests or even better, we can instruct Playwright to [use this webserver automatically](https://playwright.dev/docs/test-webserver#configuring-a-web-server). So let's do that in our `playwright.config.ts` file.\n\n```ts {3,5} title=\"playwright.config.ts - configured to use a custom server\"\nexport default defineConfig({\n //... other options\n\n // on CI, run the static server to serve the built app\n webServer: process.env.CI\n ? {\n command: \"npm run serve\",\n url: \"http://localhost:5432\",\n reuseExistingServer: true,\n timeout: 120 * 1000,\n }\n : undefined,\n})\n```\n\n\n\nIn order for Playwright to correctly detect the webserver is running ok, we need to make sure we have a valid index page at that address, so we need to add a `index.page.tsx` file in the `pages` folder.\n\n```tsx title=\"src/pages/index.page.tsx\"\nexport default function App() {\n return
Index page
;\n}\n```\n\nThis is just useful in the CI environment so that Playwright can detect the server is running and the app is served correctly.\n\n
\n\nNext, in order to run our tests as if we're in the CI environment, let's add a `test:ci` script, which is basically calling the `test` script but setting the `CI` environment variable to `true`.\n\n```json {3,4} title=\"package.json - test:ci script\"\n{\n \"scripts\": {\n \"test:ci\": \"CI=true npm run test\",\n \"test\": \"npx playwright test\",\n \"serve\": \"npx http-server --port 5432 out\",\n \"//...\": \"// other scripts\"\n },\n}\n```\n\n\nWe're now ready to run our tests against the production build of the NextJS app.\n\n```sh\nnpm run build && npm run test:ci\n```\n\n\nThis script first builds the NextJS static app and then runs the tests against it.\n\n## Configuring CI github actions \n\nWe're now ready to integrate our [testing workflow into CI via Github actions](https://playwright.dev/docs/ci-intro).\n\nCreate a YAML file `.github/workflows/test.yml` in the root of your project with the following content.\n\n```yaml {19,23} title=\".github/workflows/test.yml\"\nname: Playwright Tests\non:\n push:\n branches: [main, master]\n pull_request:\n branches: [main, master]\njobs:\n test:\n timeout-minutes: 60\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - uses: actions/setup-node@v4\n with:\n node-version: lts/*\n - name: Install dependencies\n run: npm ci\n - name: Build app\n run: npm run build\n - name: Install Playwright Browsers\n run: npx playwright install --with-deps\n - name: Run Playwright tests\n run: npm run test\n - uses: actions/upload-artifact@v4\n if: always()\n with:\n name: playwright-report\n path: playwright-report/\n retention-days: 30\n```\n\nWith this, you're ready to go! Push your changes to the main branch and see your tests running and passing in the CI environment. Go green! ๐ŸŸข\n\n## Demo repository\n\nYou can find the full setup in our [testing-setup-nextjs-playwright repo](https://github.com/infinite-table/testing-setup-nextjs-playwright/tree/main?tab=readme-ov-file). Check it out and give it a star if you find it useful.\n\n## Profit ๐Ÿš€\n\nWith this setup, you have a very convenient way to write your tests against real pages, loaded in a real browser, just like the end user experiences. And with the watch mode giving you instant feedback, you no longer have an excuse to not write tests.\n\nThis is the same setup we've been using for developing and testing the [Infinite Table React DataGrid](https://infinite-table.com) and it has been serving us really well.\n\nDataGrids are some of the most complex UI components one can build, so having a reliable tool that allowed us to iterate very quickly was crucial to us. This helped us add new features, while being confident that all of the existing core functionalities like row/column grouping, filtering, sorting, pagination, pivoting still work as expected.\n\nThe setup was a pivotal point in our development process and it's what gives us and our enterprise customers the peace of mind that the product is stable and reliable, both now and in the future.\n\n\n" + }, + { + "filePath": "/blog/2024/03/06/setting-up-master-detail-datagrid", + "routePath": "/blog/2024/03/06/setting-up-master-detail-datagrid", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2024/03/06/setting-up-master-detail-datagrid.page.md", + "fileName": "setting-up-master-detail-datagrid.page.md", + "folderPath": "/blog/2024/03/06/", + "frontmatter": { + "title": "Setting up a master-detail DataGrid with Infinite Table for React", + "author": "admin", + "hide_in_homepage": true, + "date": "2024-03-06T00:00:00.000Z", + "authorData": { + "label": "admin" + } + }, + "excerpt": "We recently [announced the release of master-detail in the Infinite React DataGrid](/blog/2024/02/26/master-detail-now-available-in-react-datagrid), so we also made a video tutorial to follow along, if video is your preferred learning method.", + "readingTime": "1 min read", + "content": "\nWe recently [announced the release of master-detail in the Infinite React DataGrid](/blog/2024/02/26/master-detail-now-available-in-react-datagrid), so we also made a video tutorial to follow along, if video is your preferred learning method.\n\nThis shows the very basics of configuring the Infinite React DataGrid with master-detail, and it's a great starting point for more advanced configurations.\n\n\n\nYou can find the full source code for the tutorial in the code sandbox below.\n\nThis example is two levels deep, but the Infinite React DataGrid supports any number of levels of master-detail.\n\n\n" + }, + { + "filePath": "/blog/2024/04/22/the-best-testing-strategies-for-frontends", + "routePath": "/blog/2024/04/22/the-best-testing-strategies-for-frontends", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2024/04/22/the-best-testing-strategies-for-frontends.page.md", + "fileName": "the-best-testing-strategies-for-frontends.page.md", + "folderPath": "/blog/2024/04/22/", + "frontmatter": { + "title": "The best testing strategies for frontends", + "author": "admin", + "date": "2024-04-22T00:00:00.000Z", + "authorData": { + "label": "admin" + } + }, + "excerpt": "In our previous article, we focused on documenting [the best testing setup for frontends](https://infinite-table/blog/2024/04/18/the-best-testing-setup-for-frontends-playwright-nextjs), which used Playwright and Next.js. You can check out the repository [here](https://github.com/infinite-table/testing-setup-nextjs-playwright) where you can find the full setup.", + "readingTime": "5 min read", + "content": "\nIn our previous article, we focused on documenting [the best testing setup for frontends](https://infinite-table/blog/2024/04/18/the-best-testing-setup-for-frontends-playwright-nextjs), which used Playwright and Next.js. You can check out the repository [here](https://github.com/infinite-table/testing-setup-nextjs-playwright) where you can find the full setup.\n\nWe consider the combination described above, Playwright + NextJS being the best combo around for testing frontends. Ok, you can switch out NextJS with other meta-framework that offers file-system routing, but the idea is the same: every test is made of 2 sibling files, with the same name but different extension.\n\nIn the test files, Playwright is configured to navigate automatically to the page being tested, so no need for adjustments if you move files around. This saves you a lot of time and hustle and makes your tests more robust and focused.\n\nBut in addition to end-to-end testing with Playwright and NextJS, there are other forms of testing out there which are available and can be used to complement your testing strategy. In this article, we'll focus on what we think are the best testing strategies for frontends. So here are a few options:\n\n - E2E testing\n - Component testing \n - Visual regression testing\n - Unit testing\n\nFor each of those options there are plenty of tools you can use, each with its own pros and cons.\n\n## E2E testing\n\nWith the advent of tools like [Puppeteer](https://pptr.dev/) and now [Playwright](https://playwright.dev/), end-to-end testing has become much easier and more reliable. For anyone who's used Selenium in the past, you know what I'm talking about. \nPuppeteer has opened the way in terms of E2E tooling, but Playwright has taken it to the next level and made it easier to await for certain selectors or conditions to be fulfilled (via [locators](https://playwright.dev/docs/locators)), thus making tests more reliable and less flaky.\nAlso, it's a game changer that it introduced a test-runner - this made the integration between the headless browser and the actual test code much smoother.\n\n### Reasons to use E2E\n\nEnd to end testing is actually a real browser, so the closest possible environment to what your app will be using.\n\nNo need to fake the page with JSDOM, no need to only do shallow rendering in React. Just use the platform!\n\n## Component testing\n\nProbably [Enzyme](https://enzymejs.github.io/enzyme/) was the first to popularize component testing in React by doing shallow rendering and expecting some things to be there in the React component tree. Then [React Testing library](https://testing-library.com/) came and took component testing to a whole new level.\n\nThe tools are great for what they're doing, but with the advent of better tooling, we should move on to better ways of testing. With the tools we have now in 2024, there's no more need to use JSDOM and simulate a browser enviroment. It used to be very cumbersome to start a headless browser back in the day, but now with Playwright/Puppeteer, it's a breeze.\n\n## Visual regression testing\n\nAlso in the days before Playwright was around, there was much hype about visual regression testing. It was very very tempting to use it - who wouldn't want to have a tool that automatically checks if the UI has (mistakenly) changed? It might fit a few use-cases, but in general, it's not worth the effort of maintaining all those tests for any little change in the UI. True, you can set thresholds for the differences, but it's still a lot of work to maintain it, especially in highly dynamic frontends and teams.\n\nWith better CSS approaches like [TailwindCSS](https://tailwindcss.com/) and [Vanilla Extract](https://vanilla-extract.style/) (which we're heavily using) it's much easier to maintain the UI and make sure it doesn't change unexpectedly. No more conflicting CSS classes, much less CSS specificity issues and much less CSS code in general.\n\nOne of the troubles in large and tangled CSS codebases is that it's write-only. Well, not write-only per se, but teams are generally afraid to remove a line of CSS cause it might break someone else's code or it might still be used.\n\nWith [Vanilla Extract](https://vanilla-extract.style/) you can be sure that if you remove a CSS class, it's not used anywhere else in the codebase. It's been a game changer in terms of CSS maintainability and productivity for us at [Infinite Table](https://infinite-table.com/).\n\nSo with all those tools to make styling easier, the need for visual regression testing has dropped significantly.\n\n\n## Unit testing\n\nUnit testing will be here to stay - at least if besides your UI, your app has some significant business logic. We're using it in combination with E2E testing to make sure complex use-cases work as expected.\n\nFor example, our [logic for row grouping](https://infinite-table.com/docs/learn/grouping-and-pivoting/grouping-rows) is fully tested with unit tests. We do have E2E tests for it, but with unit tests we can have full coverage of all the nitty-gritty details of the grouping logic. We do the same for pivoting and aggregating. Column sizing and column grouping are also covered with unit tests.\n\nWe think there's always going to be a place for unit testing to ensure robustness and reliability of the app under even the most complex use-cases and user inputs.\n\n## Conclusion\n\nIn our experience, the best testing strategy for modern frontends is a combination of E2E testing (using Playwright+NextJS), and unit testing. Visual regression testing is not worth the effort in our opinion, especially with the advent of better CSS tooling like TailwindCSS and [Vanilla Extract](https://vanilla-extract.style/).\n\nThough we used shallow component testing in the past, we're not going back to it - mocking the DOM and the browser is no longer worth it when you can use a real browser with Playwright.\n\nWe hope this article has been helpful in guiding you towards the best testing strategy for your frontend. If you have any questions or comments, feel free to reach out to us at [admin@infinite-table.com](mailto:admin@infinite-table.com). We're always happy to help! \n" + }, { "filePath": "/blog/2024/05/27/minimalist-theme-for-react-datagrid", "routePath": "/blog/2024/05/27/minimalist-theme-for-react-datagrid", @@ -1529,7 +1681,7 @@ }, "excerpt": "Excel-like editing is a very popular request we had. In this short article, we show you how to configure Excel-like editing in the Infinite React DataGrid.", "readingTime": "3 min read", - "content": "\nExcel-like editing is a very popular request we had. In this short article, we show you how to configure Excel-like editing in the Infinite React DataGrid.\n\n\n\nThis behavior is achieved by using the [Instant Edit keyboard shorcut](/docs/learn/keyboard-navigation/keyboard-shortcuts#instant-edit).\n\n\n## Configuring keyboard shortcuts\n\n```ts {4,12}\nimport {\n DataSource,\n InfiniteTable,\n keyboardShortcuts\n} from '@infinite-table/infinite-react';\n\n function App() {\n return primaryKey=\"id\" data={dataSource}>\n \n columns={columns}\n keyboardShortcuts={[\n keyboardShortcuts.instantEdit\n ]}\n />\n
\n}\n```\n\nThe `instantEdit` [keyboard shorcut](/docs/learn/keyboard-navigation/keyboard-shorcuts) is configured (by default) to respond to any key (via the special `*` identifier which matches anything) and will start editing the cell as soon as a key is pressed. This behavior is the same as in Excel, Google Sheets, Numbers or other spreadsheet software.\n\n\n\n\nTo enable editing globally, you can use the boolean prop on the `InfiniteTable` DataGrid component. This will make all columns editable.\n\nOr you can be more specific and choose to make individual columns editable via the column.defaultEditable prop. This overrides the global .\n\n\n\n\n\n\nRead about how you can configure various editors for your columns.\n\n\n\nA picture is worth a thousand words - see a chart for the editing flow.\n\n\n\n\n\n## Finishing an Edit\n\nAn edit is generally finished by user interaction - either the user confirms the edit by pressing the `Enter` key or cancels it by pressing the `Escape` key.\n\nAs soon as the edit is confirmed by the user, `InfiniteTable` needs to decide whether the edit should be accepted or not.\n\nIn order to decide (either synchronously or asynchronously) whether an edit should be accepted or not, you can use the global prop or the column-level column.shouldAcceptEdit alternative.\n\n\n\nWhen neither the global nor the column-level column.shouldAcceptEdit are defined, all edits are accepted by default.\n\n\n\n\n\nOnce an edit is accepted, the callback prop is called, if defined.\n\nWhen an edit is rejected, the callback prop is called instead.\n\nThe accept/reject status of an edit is decided by using the `shouldAcceptEdit` props described above. However an edit can also be cancelled by the user pressing the `Escape` key in the cell editor - to be notified of this, use the callback prop.\n\n\n\nUsing shouldAcceptEdit to decide whether a value is acceptable or not\n\n\n\nIn this example, the `salary` column is configured with a shouldAcceptEdit function property that rejects non-numeric values.\n\n\n\n\n## Persisting an Edit\n\nBy default, accepted edits are persisted to the `DataSource` via the DataSourceAPI.updateData method.\n\nTo change how you persist values (which might include persisting to remote locations), use the function prop on the `InfiniteTable` component.\n\n\n\nThe function prop can return a `Promise` for async persistence. To signal that the persisting failed, reject the promise or resolve it with an `Error` object.\n\nAfter persisting the edit, if all went well, the callback prop is called. If the persisting failed (was rejected), the callback prop is called instead.\n\n" + "content": "\nExcel-like editing is a very popular request we had. In this short article, we show you how to configure Excel-like editing in the Infinite React DataGrid.\n\n\n\nThis behavior is achieved by using the [Instant Edit keyboard shorcut](/docs/learn/keyboard-navigation/keyboard-shortcuts#instant-edit).\n\n\n## Configuring keyboard shortcuts\n\n```ts {4,12}\nimport {\n DataSource,\n InfiniteTable,\n keyboardShortcuts\n} from '@infinite-table/infinite-react';\n\n function App() {\n return primaryKey=\"id\" data={dataSource}>\n \n columns={columns}\n keyboardShortcuts={[\n keyboardShortcuts.instantEdit\n ]}\n />\n
\n}\n```\n\nThe `instantEdit` [keyboard shorcut](/docs/learn/keyboard-navigation/keyboard-shortcuts) is configured (by default) to respond to any key (via the special `*` identifier which matches anything) and will start editing the cell as soon as a key is pressed. This behavior is the same as in Excel, Google Sheets, Numbers or other spreadsheet software.\n\n\n\n\nTo enable editing globally, you can use the boolean prop on the `InfiniteTable` DataGrid component. This will make all columns editable.\n\nOr you can be more specific and choose to make individual columns editable via the column.defaultEditable prop. This overrides the global .\n\n\n\n\n\n\nRead about how you can configure various editors for your columns.\n\n\n\nA picture is worth a thousand words - see a chart for the editing flow.\n\n\n\n\n\n## Finishing an Edit\n\nAn edit is generally finished by user interaction - either the user confirms the edit by pressing the `Enter` key or cancels it by pressing the `Escape` key.\n\nAs soon as the edit is confirmed by the user, `InfiniteTable` needs to decide whether the edit should be accepted or not.\n\nIn order to decide (either synchronously or asynchronously) whether an edit should be accepted or not, you can use the global prop or the column-level column.shouldAcceptEdit alternative.\n\n\n\nWhen neither the global nor the column-level column.shouldAcceptEdit are defined, all edits are accepted by default.\n\n\n\n\n\nOnce an edit is accepted, the callback prop is called, if defined.\n\nWhen an edit is rejected, the callback prop is called instead.\n\nThe accept/reject status of an edit is decided by using the `shouldAcceptEdit` props described above. However an edit can also be cancelled by the user pressing the `Escape` key in the cell editor - to be notified of this, use the callback prop.\n\n\n\nUsing shouldAcceptEdit to decide whether a value is acceptable or not\n\n\n\nIn this example, the `salary` column is configured with a shouldAcceptEdit function property that rejects non-numeric values.\n\n\n\n\n## Persisting an Edit\n\nBy default, accepted edits are persisted to the `DataSource` via the DataSourceAPI.updateData method.\n\nTo change how you persist values (which might include persisting to remote locations), use the function prop on the `InfiniteTable` component.\n\n\n\nThe function prop can return a `Promise` for async persistence. To signal that the persisting failed, reject the promise or resolve it with an `Error` object.\n\nAfter persisting the edit, if all went well, the callback prop is called. If the persisting failed (was rejected), the callback prop is called instead.\n\n" }, { "filePath": "/blog/2024/06/05/master-detail-datagrid-with-charts", @@ -1567,24 +1719,6 @@ "readingTime": "3 min read", "content": "\nMany modern apps rely heavily on white-space to make the user interface easy to read and follow. However, there are financial apps or data-heavy apps where you need to display a lot of information in a small space.\nIn this blogpost, we want to show you how to tweak the theming of the Infinite React DataGrid to make it more dense and maximise screen real estate.\n\n## Configuring the spacing in the DataGrid cells\n\nThe CSS variable you want to target is `--infinite-cell-padding` - it's used to set the padding of the cells in the DataGrid. By default, the padding is set to `var(--infinite-space-2) var(--infinite-space-3)`. This means that the padding is set to `4px 8px` for a root font size of `16px`.\n\n```css {2} title=\"Default definition for --infinite-cell-padding\"\n:root {\n --infinite-cell-padding: var(--infinite-space-2) var(--infinite-space-3); /* vertical horizontal */\n\n --infinite-space-2: .25rem; /* 4px - for a root font size of 16px */\n --infinite-space-3: .5rem; /* 8px */\n}\n```\n\nYou can override this variable in your CSS to make the padding smaller. For example, you can set the padding to `2px 4px` by setting the variable like this:\n\n```css {2} title=\"Override the --infinite-cell-padding variable\"\nbody {\n --infinite-cell-padding: 2px 4px;\n}\n```\n\n\n\nIt's important to understand that cell height is not given by the padding, but by the prop.\n\nSo if you want to make the DataGrid more dense, you should also consider setting the prop to a smaller value.\n\n\n\n\n\n## Configuring the spacing in the column headers\n\nFor configuring padding inside column headers, you need to use the ```--infinite-header-cell-padding``` CSS var.\n\n```css {2} title=\"Default definition for --infinite-header-cell-padding\"\n:root {\n --infinite-header-cell-padding: var(--infinite-header-cell-padding-y) var(--infinite-header-cell-padding-x);\n --infinite-header-cell-padding-x: var(--infinite-space-3);\n --infinite-header-cell-padding-y: var(--infinite-space-3);\n}\n```\n\nYou can make the padding smaller for example give it a value of `2px 4px` by setting the variable like this:\n\n```css {2} title=\"Override the --infinite-header-cell-padding variable\"\nbody {\n --infinite-header-cell-padding: 2px 4px;\n}\n```\n\n \n\nThe above demo also uses the prop to make sure that the column headers don't reserve a space for the sort icon when the respective column is not sorted.\n\n\n\nAnother option would be to override the CSS spacing scale that InfiniteTable defines - but that affects more than just the padding of the cells and headers.\n\n```CSS title=\"Default values for the spacing scale\"\n:root {\n --infinite-space-1: .125rem;\n --infinite-space-2: .25rem;\n --infinite-space-3: .5rem;\n --infinite-space-4: 0.75rem;\n --infinite-space-5: 1rem;\n}\n```\n\nYou're encouraged to experiment with these variables to find the right balance for your app.\n\n" }, - { - "filePath": "/blog/2024/10/10/new-themes-available", - "routePath": "/blog/2024/10/10/new-themes-available", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2024/10/10/new-themes-available.page.md", - "fileName": "new-themes-available.page.md", - "folderPath": "/blog/2024/10/10/", - "frontmatter": { - "title": "New DataGrid themes available - ocean and balsam", - "author": "admin", - "date": "2024-10-10T00:00:00.000Z", - "authorData": { - "label": "admin" - } - }, - "excerpt": "With the release of `` v5.0.0 we've added two new themes: `ocean` and `balsam`.", - "readingTime": "1 min read", - "content": "\nWith the release of `` v5.0.0 we've added two new themes: `ocean` and `balsam`.\n\nNow you have a selection of themes to choose from: `default`, `minimalist`, `ocean` and `balsam`.\n\nTo apply a theme, you have to set the className `\"infinite-theme-name--THEME_NAME\"` to any parent element of the `` component (or even on the component itself).\n\n\n\n\n\n\n\n\nLearn how to theme Infinite Table to match your brand\n\n\n\n" - }, { "filePath": "/blog/2024/10/15/how-do-i-flash-cells", "routePath": "/blog/2024/10/15/how-do-i-flash-cells", @@ -1603,6 +1737,24 @@ "readingTime": "3 min read", "content": "\nFlashing cells is an important feature that has been requested by some of our users - both [in public](https://github.com/infinite-table/infinite-react/issues/250) and private conversations.\n\nIt's also a very useful addition for DataGrids users that work in the financial industry. Version `5.0.0` of `` shipped flashing and in this blogpost we want to show how to use it.\n\n## Configuring a flashing column.\n\nIn order to configure a column to flash its cells when the data changes, you need to specify a custom `ColumnCell` component.\n\n```tsx {14}\n\nimport { FlashingColumnCell } from '@infinite-table/infinite-react';\n\nconst columns: InfiniteTablePropColumns = {\n id: {\n field: 'id',\n },\n firstName: {\n field: 'firstName',\n },\n salary: {\n field: 'salary',\n components: {\n ColumnCell: FlashingColumnCell,\n }\n },\n};\n```\n\n`@infinite-table/infinite-react` exports a `FlashingColumnCell` React component that you can pass to the `components.ColumnCell` prop of any column you want to flash.\n\n\n\n\n\n\nThe default flashing duration is `1000` milliseconds.\n\n\n\n## Customizing the flashing duration\n\nIf you want to customize the flashing duration, you need to pass a different `components.ColumnCell` to the column. You can very easily do this by calling `createFlashingColumnCellComponent` and passing the `flashDuration` option.\n\n```tsx\n\nimport { createFlashingColumnCellComponent } from '@infinite-table/infinite-react';\n\nconst FlashingColumnCell = createFlashingColumnCellComponent({\n flashDuration: 500,\n flashClassName: 'my-flash-class',\n});\n\nconst columns: InfiniteTablePropColumns = {\n salary: {\n field: 'salary',\n components: {\n ColumnCell: FlashingColumnCell,\n }\n }\n}\n```\n\n\n\nWhen calling `createFlashingColumnCellComponent`, besides the `flashDuration` option, you can also pass a `flashClassName`, which is a CSS class name that will be applied to the flashing cell for the duration of the flash.\n\n\n\n## Customizing the flash colors\n\nIf you want to customize the flash colors, you have three CSS variables available: \n\n- `--infinite-flashing-background`: background color to be used when non-numeric cells flash.\n- `--infinite-flashing-up-background`: background color to use for flashing numeric cells, when the value goes up.\n- `--infinite-flashing-down-background`: background color to use for flashing numeric cells, when the value goes down.\n\nThe example below is configured to use the following colors:\n - flash up - yellow\n - flash down - magenta\n - flash non-numeric - blue\n\nAlso, the flashing duration is configured to take 2 seconds.\n\n\n\nBesides clicking the \"start updates\" button, you can also edit the salary value in any cell. When you confirm the edit, the salary value will flash.\n\n\n\n\n\n## Taking it further\n\nInfinite Table implements flashing by passing in a custom `ColumnCell` component. However, you're not limited to using our [default implementation](https://github.com/infinite-table/infinite-react/blob/master/source/src/components/InfiniteTable/components/InfiniteTableRow/FlashingColumnCell.tsx). You can very easily create your own component and apply your own custom logic.\n\nMaybe you want display both the new and the old values in the cell - this can be implemented quite easily. It's up to you to extend the cell rendering to suit your business requirements.\n\nThe current flashing implementation is flashing on any change in a cell, but you might be interested only in some of the changes. You can definitely use to detect when a cell edit is persisted and then decide whether to flash the cell or not. The possibilities are very diverse.\n\nWe're keen to see what you build!\n\n\n\n" }, + { + "filePath": "/blog/2024/10/10/new-themes-available", + "routePath": "/blog/2024/10/10/new-themes-available", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2024/10/10/new-themes-available.page.md", + "fileName": "new-themes-available.page.md", + "folderPath": "/blog/2024/10/10/", + "frontmatter": { + "title": "New DataGrid themes available - ocean and balsam", + "author": "admin", + "date": "2024-10-10T00:00:00.000Z", + "authorData": { + "label": "admin" + } + }, + "excerpt": "With the release of `` v5.0.0 we've added two new themes: `ocean` and `balsam`.", + "readingTime": "1 min read", + "content": "\nWith the release of `` v5.0.0 we've added two new themes: `ocean` and `balsam`.\n\nNow you have a selection of themes to choose from: `default`, `minimalist`, `ocean` and `balsam`.\n\nTo apply a theme, you have to set the className `\"infinite-theme-name--THEME_NAME\"` to any parent element of the `` component (or even on the component itself).\n\n\n\n\n\n\n\n\nLearn how to theme Infinite Table to match your brand\n\n\n\n" + }, { "filePath": "/blog/2024/10/16/shadcn-ui-theme-available", "routePath": "/blog/2024/10/16/shadcn-ui-theme-available", @@ -1622,156 +1774,19 @@ "content": "\nWe've all come to know and love โค๏ธ [shadcn/ui](https://ui.shadcn.com/). It comes with a consistent look and feel for all the components in the UI kit, and it's built on top of Tailwind, so people feel at home using it. As React developers, we're thankful for all the hard work happening in the React ecosystem, and we're happy to see more and more UI libraries focusing on providing great developer experience.\n\nAfter recently building [a few other themes](/blog/2024/10/10/new-themes-available), we knew we had to build a shadcn/ui theme for ``.\n\nSo we built one! It's simply called `shadcn`. For it to work, you'll need to make sure the shadcn/ui CSS variables are available, as the `` theme variables will rely on the values of those CSS variables. Other than that, simply import the `` CSS file and you're good to go.\n\n```tsx\nimport '@infinite-table/infinite-react/index.css';\n\n
\n \n \n \n
\n```\n\nYou'll have to include the `infinite-theme-name--shadcn` class name on a parent element of `` (or even on the `` component itself). Additionally, for dark mode, you'll have to use the `dark` class name (on the body element for example) to put the shadcn/ui CSS variables in dark mode, and then `` will pick that up. This means that for this theme, using the `infinite-theme-mode--dark` class name is optional.\n\n\n\n\n\nEnjoy!" }, { - "filePath": "/blog/2023/01/26/filtering-data-with-infinite-table-for-react", - "routePath": "/blog/2023/01/26/filtering-data-with-infinite-table-for-react", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2023/01/26/filtering-data-with-infinite-table-for-react.page.md", - "fileName": "filtering-data-with-infinite-table-for-react.page.md", - "folderPath": "/blog/2023/01/26/", - "frontmatter": { - "title": "Filtering Data with Infinite Table for React", - "description": "Learn how to filter data both client-side and server-side with Infinite Table for React", - "author": [ - "admin" - ], - "date": "2023-01-26T00:00:00.000Z", - "authorData": { - "label": [ - "admin" - ] - } - }, - "excerpt": "_Today we shipped cutting-edge column filtering functionality, that enables intuitive client-side and server-side filtering_", - "readingTime": "5 min read", - "content": "\n_Today we shipped cutting-edge column filtering functionality, that enables intuitive client-side and server-side filtering_\n\n\n\n1๏ธโƒฃ Narrow down your data with your own filter types and operators\n\n2๏ธโƒฃ Works both client-side and server-side\n\n3๏ธโƒฃ Easy customization of filters and filter editors\n\n4๏ธโƒฃ Optimized for performance\n\n5๏ธโƒฃ Easy to use across multiple columns\n\n\n\nFilters were, by far, the most requested feature to add to Infinite Table after our initial launch.\n\nThe recently-released version `1.1.0` of Infinite Table for React introduces support for column filters, which work both client-side and server-side.\n\nIn order to enable filtering - specify the property on the `` component, as shown below:\n\n```tsx {4} title=\"Enabling_filters_on_the_DataSource\"\n data={/* ... */} primaryKey=\"id\" defaultFilterValue={[]}>\n columns={columns} />\n\n```\n\nThis configures the `` component with an empty array of filters; columns will pick this up and each will display a filter editor in the column header.\n\nOf course, you can define some initial filters:\n\n```tsx title=\"Initial_filters:_filter_by_age_greater_than_40\"\ndefaultFilterValue={[\n {\n field: 'age',\n filter: {\n type: 'number',\n operator: 'gt',\n value: 40\n }\n }\n]}\n```\n\nYou can see how all of this looks like when we put it all together in the examples below.\n\n## Local and Remote Filtering\n\nBecause the `` prop is a function that returns a `Promise` with remote data, the filtering will happen server-side by default.\n\n\n\nWhen using remote filtering, it's your responsability to send the DataSource to the backend (you get this object as a parameter in your function). This value includes for each column the value in the filter editor, the column filter type and the operator in use. In this case, the frontend and the backend need to agree on the operator names and what each one means.\n\n\n\nWhenever filters change, when remote filtering is configured, the function prop is called again, with an object that has the `filterValue` correctly set to the current filters (together with `sortInfo` and other data-related props like `groupBy`, etc).\n\n\nHowever, we can use the to force client-side filtering:\n\n```tsx\n filterMode=\"local\" filterDelay={0} />\n```\n\nWe also specify the filterDelay=0 in order to perform filtering immediately, without debouncing and batching filter changes, for a quicker response โšก๏ธ ๐ŸŽ\n\n\n\n\n\nEven if your data is loaded from a remote source, using `filterMode=\"local\"` will perform all filtering on the client-side - so you don't need to send the `filterValue` to the server in your `data` function.\n\n\n\n## Defining Filter Types and Custom Filter Editors\n\nCurrently there are 2 filter types available in Infinite Table:\n\n- `string`\n- `number`\n\nConceptually, you can think of filter types similar to data types - generally if two columns will have the same data type, they will display the same filter.\n\nEach filter type supports a number of operators and each operator has a name and can define it's own filtering function, which will be used when local filtering is used.\n\n\n\nThe example above, besides showing how to define a custom filter type, also shows how to define a custom filter editor.\n\n\n\nFor defining a custom filter editor to be used in a filter type, we need to write a new React component that uses the hook.\n\n```tsx\nimport { useInfiniteColumnFilterEditor } from '@infinite-table/infinite-react';\n\nexport function BoolFilterEditor() {\n const { value, setValue } = useInfiniteColumnFilterEditor();\n return <>{/* ... */};\n}\n```\n\nThis custom hook allows you to get the current `value` of the filter and also to retrieve the `setValue` function that we need to call when we want to update filtering.\n\nRead more about this [in the docs - how to provide a custom editor](/docs/learn/filtering/providing-a-custom-filter-editor).\n\n\n\n## Customise Filterable Columns and Filter Icons\n\nMaybe you don't want all your columns to be filterable.\n\nFor controlling which columns are filterable and which are not, use the property.\n\nThis overrides the global prop.\n\nWe have also made it easy for you to customize the filter icon that is displayed in the column header.\n\n\n\nYou change the filter icon by using the prop - for full control, it's being called even when the column is not filtered, but you have a `filtered` property on the argument the function is called with.\n\nIn the example above, the `salary` column is configured to render no filter icon, but the `header` is customized to be bolded when the column is filtered.\n\n## Ready for Your Challenge!\n\nWe listened to your requests for advanced filtering.\n\nAnd we believe that we've come up with something that's really powerful and customizable.\n\nNow it's your turn to try it out and show us what you can build with it! ๐Ÿš€\n\nIf you have any questions, feel free to reach out to us on [Twitter](https://twitter.com/infinite_table) or in the [GitHub Discussions](https://github.com/infinite-table/infinite-react/discussions).\n\nMake sure you try out filtering in Infinite Table for yourself ([and consult our extensive docs](/docs/learn/filtering) if required).\n\n\n\nLearn how to use filtering in the browser.\n\n\nFigure out how to use filtering with server-side integration.\n\n\n" - }, - { - "filePath": "/blog/2023/02/16/using-menus-in-infinite-table", - "routePath": "/blog/2023/02/16/using-menus-in-infinite-table", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2023/02/16/using-menus-in-infinite-table.page.md", - "fileName": "using-menus-in-infinite-table.page.md", - "folderPath": "/blog/2023/02/16/", - "frontmatter": { - "title": "Using Menus in Infinite Table", - "description": "Find out how to use menus in Infinite Table to customise the DataGrid to fit your needs: custom context menus, column menus and more.", - "author": [ - "admin" - ], - "date": "2023-02-16T00:00:00.000Z", - "authorData": { - "label": [ - "admin" - ] - } - }, - "excerpt": "_With version 1.1.0, our DataGrid now includes support for context menus, which are fully configurable so you can create custom menus for any cell in the table._", - "readingTime": "7 min read", - "content": "\n_With version 1.1.0, our DataGrid now includes support for context menus, which are fully configurable so you can create custom menus for any cell in the table._\n\n\n\n1๏ธโƒฃ are fully configurable\n\n2๏ธโƒฃ adjust their position based on the available space\n\n3๏ธโƒฃ can be used to create custom menus for any cell in the table\n\n4๏ธโƒฃ give you full access to the information in the cell or the whole DataGrid\n\n\n\n## How it works\n\nIn Infinite Table you can configure a context menu to be displayed when you right-click a cell by using the prop. Simply specify a function that returns an array of objects, each with `label` and `key` properties. Each object in the array is a row in the context menu - with the `label` being the displayed content and the `key` being a unique identifier for the menu row.\n\n```tsx title=\"Configuring_a_context_menu\"\nconst getCellContextMenuItems = ({ column, data, value }) => {\n if (column.id === 'currency') {\n return [\n {\n label: `Convert ${value}`,\n key: 'currency-convert',\n onAction: (key, item) => {\n alert('clicked ' + item.key);\n },\n },\n ];\n }\n\n if (column.id === 'age') {\n return null;\n }\n\n return [\n {\n label: `Welcome ${value}`,\n key: 'hi',\n },\n ];\n};\n\n data={data} primaryKey=\"id\">\n \n getCellContextMenuItems={getCellContextMenuItems}\n columns={columns}\n />\n;\n```\n\nIn the function prop, you have access to all the information you need, in the first argument of the function:\n\n- `column` - the column on which the user right-clicked\n- `data` - the data object for the row the user right-clicked\n- `value` - the value of the cell on which the context menu has been triggered. This is generally `data[column.field]`, but it can be different if the column has a or \n- `rowInfo` - an object that contains more information about the row, like the `id` (the primary key) and the row index\n- `isGroupRow`\n- and more\n\n\n\nIf is specified and returns `null`, no custom context menu will be displayed, instead the default browser context menu will be shown (in this case, we do not call `preventDefault()` on the event object).\n\nIf returns an empty array, the default browser context menu will not be shown (in this case, we are calling `preventDefault()` on the event object), but also no custom context menu will be displayed, as there are no menu items to show.\n\n\n\n\n\nEach item on the context menu can specify an `onAction` function, which will be called when the user clicks on the menu item. The function will receive the `key` and the `item` as arguments.\n\nIn addition, since the menu items are returned from inside the `getCellContextMenuItems` function, the `onAction` callback has access to the same information as the `getCellContextMenuItems` function.\n\n\n\n\n## Configuring the context menu to have multiple columns\n\nIn the above example, notice each context menu item has only one cell, where the `label` property is displayed.\n\nHowever, Infinite Table for React allows you to create more complex menus, with multiple columns.\n\nIn order to do this, use the same prop, but return an object, with `columns` and `items`\n\n```tsx\nconst getCellContextMenuItems = () => {\n return {\n columns: [{ name: 'label' }, { name: 'lcon' }],\n items: [\n {\n label: 'Welcome',\n icon: '๐Ÿ‘‹',\n key: 'hi',\n },\n {\n label: 'Convert',\n icon: '๐Ÿ”',\n key: 'convert',\n },\n ],\n };\n};\n```\n\n\n\nWhen is used to configure the column menus, each column `name` should have a corresponding property in the objects returned in the `items` array (each object also needs to keep the `key` property).\n\nAlso, we recommend keeping a column named `label`.\n\n\n\n\n\n## Smart positioning\n\nContext menus in Infinite Table are smart enough to adjust their position based on the available space relative to the mouse-click coordinates. The menu will always try to fit inside the grid viewport and to look for the best position that will not cause the menu to be cut off or overflow outside the DataGrid.\n\nThe same algorithm is applied to column menus and also to filter menus (the menu displayed when a filter is shown and the user wants to change the filter operator).\n\n## Context menus outside cells, for the table body\n\nThere are scenarios when you want to display a context menu even when you right-click outside a cell, but inside the table body - for those cases, you can use (in fact, you can use the prop for all context menus).\n\nThe signature of is almost identical with that of , with the exception that cell-related information can be undefined - if the user didn't right-click a cell, but somewhere else in the table body.\n\n\n\nIn the example above, if you click outside a cell, a menu with a single item will be displayed - `Add Item`. If you click on a cell, the menu will be different, and will show information about the clicked cell.\n\n## Column menus\n\nBesides context menus, the DataGrid also supports menus for columns, that allow you to sort/unsort, pin/unpin, clear filtering and toggle column visibility.\n\nJust like context menus, the column menus can also be fully customised, by using the prop.\n\n```tsx title=\"Customizing-column-menu\"\nfunction getColumnMenuItems(items, { column }) {\n if (column.id === 'firstName') {\n // you can adjust the default items for a specific column\n items.splice(0, 0, {\n key: 'firstName',\n label: 'First name menu item',\n onClick: () => {\n console.log('Hey there!');\n },\n });\n }\n\n // or for all columns\n items.push({\n key: 'hello',\n label: 'Hello World',\n onClick: () => {\n alert('Hello World from column ' + column.id);\n },\n });\n return items;\n}\n```\n\n\n\nThe first argument passed to the prop is the array of items that is displayed by default in the column menu.\n\nYou can either modify this array and return it or you can return another totally different array.\n\n\n\n\n\nAs with context menus, positioning column menus is also smart - the menu will always try to fit inside the grid viewport, so it will align to the right or the left of the column, depending on the available space.\n\n## Conclusion\n\nIn this article, we've explained just some of the scenarios that are now possible with Infinite Table for React, by using the new context and column menus.\n\n\n\nLearn more about working with context menus.\n\n\nConfiguring column menus to fit your needs - read more.\n\n\n\nWe hope you'll use these functionalities to build amazing DataGrids for your applications, that are fully tailored to your needs.\n\nIf you find any issues or have any questions, please reach out to us on [Twitter](https://twitter.com/infinite_table) or in the [GitHub Discussions](https://github.com/infinite-table/infinite-react/discussions) or [issues](https://github.com/infinite-table/infinite-react/issues).\n\nWe're happy to help and improve how you work with the component - we want to make it very easy and straight-forward to use it and are looking for ways to simplify our APIs to **achieve more with less**.\n" - }, - { - "filePath": "/blog/2023/01/16/infinite-table-is-here", - "routePath": "/blog/2023/01/16/infinite-table-is-here", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2023/01/16/infinite-table-is-here.page.md", - "fileName": "infinite-table-is-here.page.md", - "folderPath": "/blog/2023/01/16/", - "frontmatter": { - "title": "๐Ÿ“ฃ Infinite Table is Here ๐ŸŽ‰", - "description": "Infinite Table is ready for prime time. With version 1.0.0 we're releasing a DataGrid that's feature packed and ready to be used in enterprise-grade apps", - "author": [ - "admin" - ], - "date": "2023-01-16T00:00:00.000Z", - "authorData": { - "label": [ - "admin" - ] - } - }, - "excerpt": "_Infinite Table React is ready for prime time._", - "readingTime": "7 min read", - "content": "\n_Infinite Table React is ready for prime time._\n\n_With version 1.0.0 we're releasing a DataGrid that's feature packed and ready to be used in the wild!_\n\n\n\n1๏ธโƒฃ seriously fast\n\n2๏ธโƒฃ no empty or white rows while scrolling\n\n3๏ธโƒฃ packed with features\n\n4๏ธโƒฃ built from the ground up for React\n\n5๏ธโƒฃ clear, concise and easily composable props & API\n\n\n\nWe think you'll love Infinite Table.\n\nThis is the DataGrid we would have loved to use more than 15 years ago when [we started working with tables in the browser](/blog/2022/11/08/why-another-datagrid).\n\nAnd now it's finally here ๐ŸŽ‰.\n\n### Built from the Ground Up with React & TypeScript\n\n#### React all the Way\n\nInfinite Table feels native to React, not as a after-thought, but built with React fully in mind.\n\nIt's declarative all the way and exposes everything as props, both controlled and uncontrolled.\n\nIf you don't like the default behavior of a prop, use the controlled version and implement your own logic and handling - see for example the [following props related to column order](/docs/reference/infinite-table-props#search=columnorder):\n\n- - controlled property for managing order of columns\n- - uncontrolled version of the above\n- - callback prop for notifications and for updating controlled column order\n\n#### Fully Controlled\n\nReact introduced controlled components to the wider community and we've been using them for years.\n\nIt's where the power of React lies - giving the developer the flexibility to fully control (when needed) every input point of an app or component.\n\nAll the props which Infinite Table exposes, have both controlled and uncontrolled versions. This allows you to start using the component very quickly and without much effort, but also with the all-important flexibility to fully control the component when needed, as your app grows and you need more control over the DataGrid.\n\n#### TypeScript & Generic Components\n\nInfinite Table is also built with TypeScript, giving you all the benefits of a great type system.\n\nIn addition, the exposed components are exported as generic components, so you can specify the type of the data you're working with, for improved type safety.\n\n```tsx\nimport { InfiniteTable, DataSource } from '@infinite-table/infinite-react'\n\ntype Person = { id: number, name: string, age: number}\n\nconst data: Person[] = [\n { id: 1, name: 'John', age: 25 },\n //...\n];\nconst columns = {\n id: { field: 'id' },\n name: { field: 'name' },\n}\n\n// ready to render\n data={data} primaryKey=\"id\">\n columns={columns} />\n\n```\n\n### Why Use Infinite Table, cont.\n\n#### Fast - virtualization\n\nInfinite Table is fast by leveraging **virtualization** both **vertically** (for rows) and **horizontally** (for columns).\n\nThis means DOM nodes are created only for the visible cells, thus reducing the number of DOM nodes and associated memory strain and improving performance.\n\n#### No white space while scrolling - clever layout & rendering\n\nIn addition to virtualization, we use clever layout & rendering techniques to avoid white space while scrolling.\n\nWhen you scroll, the table will not show any empty rows or white space - no matter how fast you're scrolling!\n\n\n\nWe think this is one of the features that sets us apart from other components.\n\nWe've spent a lot of time and effort making sure no whitespace is visible while scrolling the table.\n\n\n\n### Batteries Included\n\nWe want you to be productive immediately and stop worrying about the basics. Infinite Table comes with a lot of features out of the box, so you can focus on the important stuff.\n\nIt helps you display huge datasets and get the most out of your data by providing you the right tools to enjoy these features:\n\n- [ sorting](/docs/learn/sorting/overview)\n- [ row grouping](/docs/learn/grouping-and-pivoting/grouping-rows) - both server-side and client-side\n- [ pivoting](/docs/learn/grouping-and-pivoting/pivoting/overview) - both server-side and client-side\n- [ aggregations](/docs/learn/grouping-and-pivoting/grouping-rows#aggregations)\n- [ live pagination](/docs/learn/working-with-data/live-pagination)\n- [ lazy loading](/docs/learn/working-with-data/lazy-loading)\n- [ keyboard navigation](/docs/learn/keyboard-navigation/navigating-cells)\n- [ fixed and flexible columns](/docs/learn/columns/fixed-and-flexible-size)\n- [ column grouping](/docs/learn/column-groups)\n- [ theming](/docs/learn/theming)\n- ... and many others\n\nInfinite Table is built for companies and individuals who want to ship โ€” faster ๐ŸŽ!\n\n### (Almost) No External Dependencies\n\nWe've implemented everything from scratch and only directly depend on 2 packages (we'll probably get rid of them as well in the future) - all our dependecy graph totals a mere 3 packages.\n\n\n\nWe've reduced external dependencies for 2 main reasons:\n\n- avoid security issues with dependencies (or dependencies of dependencies...you know it) - remember left-pad?\n- keep the bundle size small\n\n\n\n### Composable API - with a small surface\n\nWhen building a component of this scale, there are two major opposing forces:\n\n- adding functionality\n- keeping the component (and the API) simple\n\nWe're continually trying to reconcile both with Infinite Table, so we've built everything with composition in mind.\n\n\n\nA practical example of composition is favouring function props instead of boolean flags or objects. Why implement a feature under a boolean flag or a static object when you can expose a functionality via a function prop? The function prop can be used to handle more cases than any boolean flag could ever handle!\n\n\n\nA good example of composability is the prop which controls the columns that are generated for grouping.\n\nIt can be either a column object or a function:\n\n- when it's a column object, it makes the table render a single column for grouping (as if was set to `\"single-column\"`)\n- when it's a function, it behaves like is set to `\"multi-column\"` and it's being called for each of the generated columns.\n\n```tsx title=\"Group_column_as_an_object\"\n\n```\n\nvs\n\n```tsx title=\"Group_column_as_a_function\"\n {\n // this allows you to affect all generated group columns in a single place\n // especially useful when the generated columns are dynamic or generated via a pivot\n return {...}\n }}\n/>\n```\n\nOur experience with other DataGrid components taught us that the more features you add, the more complex your API becomes.\n\nSo we tried to keep the API surface as small as possible, while still offering a rich set of declarative props as building blocks that can be composed to accomplish more complex functionalities.\n\n### Built for the community, available on NPM\n\nWe're thrilled to share Infinite Table with the world.\n\nWe wanted to make it very easy for everyone to [get started](/docs/learn/getting-started) with it, so all you require is just an npm install:\n\n\nnpm i @infinite-table/infinite-react\n\n\nThe component will show a footer with a [Powered by Infinite Table](https://infinite-table.com) link displayed. However, all the functionalities are still available and fully working. So if you keep the link visible, you can use the component for free in any setup!\n\nAlthough you can use Infinite Table for free, we encourage you to [purchase a license](/pricing) - buying a license will remove the footer link. This will help us keep delivering new features and improvements to the component and support you and your team going forward!\n\n\n\nGet started with Infinite Table and learn how to use it in your project.\n\n\n\nGet Infinite Table for your project and team!\n\n\n" - }, - { - "filePath": "/blog/2023/07/14/version-2-0-0", - "routePath": "/blog/2023/07/14/version-2-0-0", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2023/07/14/version-2-0-0.page.md", - "fileName": "version-2-0-0.page.md", - "folderPath": "/blog/2023/07/14/", - "frontmatter": { - "title": "Infinite Table DataGrid for React reaches version 2.0.0", - "description": "With version 2.0.0 InfiniteTable DataGrid for React brings lots of fixes and enhancements including support for sorting group columns, better APIs, improved pivoting, smarter column menus and more.", - "author": [ - "admin" - ], - "date": "2023-07-14T00:00:00.000Z", - "authorData": { - "label": [ - "admin" - ] - } - }, - "excerpt": "Version `2.0.0` is a release that allowed us to polish many areas of the component and consolidate its existing features and APIs.", - "readingTime": "4 min read", - "content": "\nVersion `2.0.0` is a release that allowed us to polish many areas of the component and consolidate its existing features and APIs.\n\nWe hope this makes your experience with Infinite Table as your React DataGrid of choice even better.\n\nThough it doesn't add major new features, this version does improve the overall experience of using the component. In this article we're detailing the most important improvements this release brings.\n\n\n\n1๏ธโƒฃ [better support for sorting group columns](#1-better-support-for-sorting-group-columns)\n2๏ธโƒฃ [allows configuring the behavior when multiple sorting is enabled](#2-multi-sort-behavior)\n3๏ธโƒฃ [smarter column menus](#3-smarter-column-menus)\n4๏ธโƒฃ [improved support for boolean pivot columns](#4-improved-support-for-boolean-pivot-columns)\n5๏ธโƒฃ [better and more exhaustive APIs](#5-better-and-more-exhaustive-apis)\n\n\n\n\n\n## 1๏ธโƒฃ Better support for sorting group columns\n\nBefore version `2.0.0`, group columns were sortable, but only if the configured `groupBy` fields were bound to actual columns.\n\nThis release enables you to make group columns sortable even when other columns are not defined. For this to work, you have to specify a sortType as an array, so the column knows how to sort the group values.\n\n```tsx title=\"Configuring sortType for group columns\"\n\n groupColumn={{\n sortType: ['string', 'number'],\n field: 'firstName',\n defaultWidth: 150,\n }}\n groupRenderStrategy=\"single-column\"\n columns={columns}\n columnDefaultWidth={120}\n/>\n```\n\n\n\n## 2๏ธโƒฃ Multi sort behavior\n\nWe have introduced to allow you to configure how the component behaves when multiple sorting is enabled. Two options are available:\n\n- `append` - when this behavior is used, clicking a column header adds that column to the alredy existing sort. If the column is already sorted, the sort direction is reversed. In order to remove a column from the sort, the user needs to click the column header in order to toggle sorting from ascending to descending and then to no sorting.\n\n- `replace` - the default behavior - a user clicking a column header removes any existing sorting and sets that column as sorted. In order to add a new column to the sort, the user needs to hold the `Ctrl/Cmd` key while clicking the column header.\n\nmultiSortBehavior=\"replace\" is the new default behavior, and also a more natural one, so we recommend using it.\n\n\n\n## 3๏ธโƒฃ Smarter column menus\n\nColumn menus are now smarter - in previous versions of Infinite Table, users were able to hide the column that had the menu opened, and the menu would hang in its initial position.\n\nWhen this happens, in version `2.0.0`, the menu realigns itself to other existing columns, thus providing a better user experience.\n\n## 4๏ธโƒฃ Improved support for boolean pivot columns\n\nIt's pretty common to pivot by boolean columns, and this is now fully supported in Infinite Table. Previous to version `2.0.0`, the column headers for boolean pivot columns were not rendered correctly.\n\n\n\n## 5๏ธโƒฃ Better and more exhaustive APIs\n\nWe have improved our APIs, with new methods and fixes. Among other things, we've polished our [Column API](/docs/reference/column-api) to offer you the ability to do more with your columns. Previously there were things that were only possible to do if you had access to the internal state of the component, but now we've moved more things to the API.\n\nFor example, our column sorting code is now centralised, and using gives you the same action as clicking a column header (this was not the case previously).\n\nWe've added quite a few more methods to our APIs, here's some of the most important ones:\n\n- ColumnAPI.\n- ColumnAPI.\n- ColumnAPI.\n- ColumnAPI.\n- ColumnAPI.\n\n## Conclusion\n\nWe've been working on version `2.0.0` for a few months now and we hope you'll enjoy all the little details that make this version a better product, with all the improvements it brings in various areas of the component.\n\nWe'd love to hear your feedback, so please reach out to us via email at admin@infinite-table.com or follow us [@get_infinite](https://twitter.com/get_infinite) to keep up-to-date with news about the product.\n\nThank you ๐Ÿ™Œ\n" - }, - { - "filePath": "/blog/2023/10/02/version-3-0-0", - "routePath": "/blog/2023/10/02/version-3-0-0", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2023/10/02/version-3-0-0.page.md", - "fileName": "version-3-0-0.page.md", - "folderPath": "/blog/2023/10/02/", - "frontmatter": { - "title": "Infinite Table React DataGrid version 3.0.0 released", - "description": "InfiniteTable DataGrid for React version 3.0.0 brings many small fixes and enhancements, along with a major new feature: cell selection", - "author": [ - "admin" - ], - "date": "2023-10-02T00:00:00.000Z", - "authorData": { - "label": [ - "admin" - ] - } - }, - "excerpt": "Version `3.0.0` is a release that brings a long awaited feature: cell selection. This allows the user to perform fined-grained cell selection, either via the prop or via the [Cell Selection API](/docs/reference/cell-selection-api).", - "readingTime": "4 min read", - "content": "\nVersion `3.0.0` is a release that brings a long awaited feature: cell selection. This allows the user to perform fined-grained cell selection, either via the prop or via the [Cell Selection API](/docs/reference/cell-selection-api).\n\n\n\n1๏ธโƒฃ [support for single and multiple cell selection](#1-support-for-single-and-multiple-cell-selection)\n2๏ธโƒฃ [cell selection using wildcards](#2-cell-selection-using-wildcards)\n3๏ธโƒฃ [cell selection API](#3-cell-selection-api)\n\n\n\n## 1๏ธโƒฃ Support for single and multiple cell selection\n\nIt's been a [long-requested feature to implement cell selection](https://github.com/infinite-table/infinite-react/issues/120).\n\nWe knew we needed to implement it, but we wanted to do it right while keeping it easy to understand.\n\nIn fact, we prepared some things in advance - namely was there, it just needed to accept a new value: `\"multi-cell\"`.\n\n```tsx title=\"Configuring multi-cell selection\"\n\n selectionMode=\"multi-cell\" // <--- THIS\n primaryKey=\"id\"\n data={[...]}\n/>\n```\n\nThe line above is all you need to do to enable cell selection. This allows the user to `Click` or `Cmd/Ctrl+Click` to select a specific cell and `Shift+Click` to select a range of cells. It's exactly the behavior you'd expect from a spreadsheet application.\n\nTry `Cmd/Ctrl+Click`ing in the DataGrid cells below to see multiple cell selection in action.\n\n\n\n### Using a default selection\n\nIf you want to render the DataGrid with a default selection, you can use the prop.\n\n```tsx\nconst defaultCellSelection = {\n defaultSelection: false,\n selectedCells: [\n [3, 'hobby'],\n [4, 'firstName'],\n [4, 'hobby'],\n [4, 'preferredLanguage'],\n [4, 'salary'],\n ],\n};\n```\n\nThe format for the uncontrolled (and also for the controlled ) is an object with two properties:\n\n- `defaultSelection` - `boolean` - whether or not cells are selected by default.\n- and either\n - `selectedCells` - `[string|number, string][]` - only needed when `defaultSelection` is `false`\n- or\n - `deselectedCells` - `[string|number, string][]` - only needed when `defaultSelection` is `true`\n\nThe value for `selectedCells` and `deselectedCells` should be an array of `[rowId, colId]` tuples.\n\nThe `rowId` is the `id` of the row (the primary key), and the `colId` is the `id` of the column (the identifier of the column in the prop).\n\nThis object shape for the / props allows you full flexibility in specifying the selection. You can specify a single cell, a range of cells, or even a non-contiguous selection. You can default to everything being selected, or everything being deselected and then enumerate your specific exceptions.\n\n\n\n## 2๏ธโƒฃ Cell Selection using wildcards\n\nThe above examples show how to select specific cells, but what if you want to select all cells in a column, or all cells in a row?\n\nWell, that turns out to be straightforward as well. You can use the `*` wildcard to select all cells in a column or all cells in a row.\n\n```tsx title=\"All cells in row with id rowId3 and all cells in hobby column are selected\"\nconst defaultCellSelection = {\n defaultSelection: false,\n selectedCells: [\n ['*', 'hobby'],\n ['rowId3', '*'],\n ],\n}\n\n```\n\n\n\nWildcard selection is really powerful and it allows you to select lots of cells without the need to enumerate them all.\n\nFor example, you can easily select all cells except a few.\n\n### Listening to selection changes\n\nYou can listen to selection changes by using the prop.\n\nIf you're using controlled cell selection, you have to update the prop yourself in response to user interaction - so will be your way of listening to selection changes.\n\n## 3๏ธโƒฃ Cell Selection API\n\nIn addition to managing cell selection declaratively, which we encourage, you can also use the [Cell Selection API](/docs/reference/cell-selection-api) to imperatively update the current selection.\n\nWe offer the following methods:\n\n- - selects a single cell, while allowing you to keep or to clear previous selection\n- - deselects the specified cell\n- - selects a whole column in the DataGrid\n- - deselects the specified column\n- - selects a range of cells\n- - deselects the specified range of cells\n- - selects all cells in the DataGrid\n- - clears selection (deselects all cells in the DataGrid)\n- - checks if the specified cell is selected or not\n\n## Conclusion\n\nWe'd love to hear your feedback - what do you think we've got right and what's missing. Please reach out to us via email at admin@infinite-table.com or follow us [@get_infinite](https://twitter.com/get_infinite) to keep up-to-date with news about the product.\n\nTalk soon ๐Ÿ™Œ\n" - }, - { - "filePath": "/blog/2023/10/05/building-a-datagrid-with-the-right-tools", - "routePath": "/blog/2023/10/05/building-a-datagrid-with-the-right-tools", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2023/10/05/building-a-datagrid-with-the-right-tools.page.md", - "fileName": "building-a-datagrid-with-the-right-tools.page.md", - "folderPath": "/blog/2023/10/05/", - "frontmatter": { - "title": "Building a DataGrid with the right tools", - "author": "admin", - "date": "2023-10-05T00:00:00.000Z", - "authorData": { - "label": "admin" - } - }, - "excerpt": "Building for the browser has historically been very tedious. In the old days you had to resort to all sorts of hacks for getting the right layout - anyone remembers conditional comments targeting IE6-9? ๐Ÿ˜…", - "readingTime": "8 min read", - "content": "\nBuilding for the browser has historically been very tedious. In the old days you had to resort to all sorts of hacks for getting the right layout - anyone remembers conditional comments targeting IE6-9? ๐Ÿ˜…\n\nYeah, we don't miss those days either.\n\nThings have evolved in the last few years, and the amount of goodies JS/CSS/HTML/layout goodies we now take for granted is staggering. New CSS features like flex/grid/custom properties really make a difference. Also browser performance has improved a LOT, and today we can do things in the browser that were unthinkable just a few years ago.\n\nHowever, not everything is easier now than it was back in the browser-war days. Handling all kinds of devices, managing changing dependencies, configuring build tools, choosing the right styling approach, proper E2E testing, keeping a small bundle size, CI pipelines, etc. are all things that can (and will) go wrong if you don't have the right tools.\n\n## TypeScript\n\nIt's obvious today to just go with `TypeScript`, but a few years ago, it was not as obvious. We've been using TypeScript for quite a few years now, and we're very happy with it. We can never imagine going back to plain JS.\n\n## React\n\nBuilding on top of `React` has given us an amazing component model that's very composable and easy to reason about - and the ecosystem is huge.\n\nRead about our journey in the [Why another DataGrid?](/blog/2022/11/08/why-another-datagrid) blog post. Back when React was launching, many of our team members were writing DataGrids - either in vanilla JS or using some libraries (`jQuery` anyone? - we don't miss browser incompatibilities).\n\n## CSS Variables and Vanilla Extract\n\nAs a `DataGrid` Infinite Table is built on top of CSS variables - we're going all in with CSS variables. They have a few gotchas in very advanced cases, but all-in-all they're amazing - and especially for performance.\n\nWe're not short of [CSS variables that we expose - see the full list](/docs/learn/theming/css-variables).\n\nUsing them has been pivotal not only to the ease of theming, but also to the performance of the DataGrid.\nBeing able to change a CSS custom property on a single DOM element and then reuse it across many elements that are children of the first one is a huge performance win. Our DataGrid performance would not be the same without CSS variables.\n\n### Vanilla Extract\n\nThe single tool that has made our life a lot easier working with CSS is [Vanilla Extract](https://vanilla-extract.style/). If you're developing a component library, you should definitely use it! Not so much for simple & static apps - there are other styling solutions that are easier to use, like [tailwindCSS](https://tailwindcss.com/). But for component libraries, **Vanilla Extract is amazing**!\n\nDid we mention it's amazing? ๐Ÿ˜…\nThe fact that you can use TypeScript with it, can use \"Find All References\", see where everything is used is a huge win. You're not writing readonly CSS anymore - because that tends to be the case with most CSS. People are afraid to change it or remove old CSS code, just in case those rules are still being used or referenced somehow. This way, CSS only grows with time, and this is a code smell.\n\nWith Vanilla Extract, you get to forget about that. You know what's being used and what's not.\n\nAlso, hashing class names to avoid collisions is nice - and something now very common in the modern JS ecosystem. It all started with CSS modules, and now it's everywhere, Vanilla Extract included.\n\nOther great features we use extensively are:\n\n- public facing CSS variables - their names are stable\n- private CSS variables - their names are hashed\n- sharing CSS values with the TS codebase is a dream come true.\n- Vanilla Extract recipes - generating and applying CSS classes based on a combination of properties. It's enough that you have 2-3 properties, each with a few values, and managing their combinations can be a pain. Vanilla Extract recipes manage this in a very elegant way.\n\n## End-to-end testing with Playwright and NextJS\n\nRemember the days of Selenium? All those flaky tests, the slow execution, the hard to debug issues? They're gone!\n\n[Playwright](https://playwright.dev/) all the way! 300+ tests and going strong! Yes, you read that right! We have more than 300 tests making sure the all the DataGrid features are working as expected. Sorting, filtering, row grouping, column groups, pivoting, aggregations, lazy loading, live pagination, keyboard navigation, cell and row selection, theming - they're all tested! And we're not talking about unit tests, but end-to-end tests. We're testing the DataGrid in the browser, with real data just like real users would.\n\nPlaywright is an amazing tool, but we're not using it standalone. Paired with a [NextJS](https://nextjs.org/) app, with file-system based routing, we've created files/routes for each functionality. Each NextJS file in turn has a Playwright test file with the same name, but a different extension.\n\nThis has the benefit that it's always very obvious which test is running against which page. The test and the route always have the same file name, just the extension is different. The test source-code doesn't explicitly contain code that navigates to a specific page, all this is done under the hood, using this simple convention.\n\nThis way, we have a very clear separation of concerns, and it's very easy to add new tests. We just create a new file in the `pages` folder, and a new test file sibling to it. Another amazing benefit is that we can start the NextJS app and point our browser to whatever page we want to see or debug and it's there. We can very easily do the actions the test is doing and see if we get the expected results. This is a huge win for debugging.\n\n## A tailored state management\n\nWe've built a very simple yet highly effective state management solution for our DataGrid. It's built to make updating the internal state of the DataGrid as easy as possible - we want a simple API, with clear actions. Our actions map almost 1-to-1 to the DataGrid properties, which makes it very obvious to know who changed what.\n\nWe can't overstate how important it is to have a clear data flow through the DataGrid. This is because the DataGrid is by far the most complex UI component you'll ever use (and we'll ever build). You can't possibly go beyond that - at least not in common business apps, where you have the normal UI controls you can expect, like inputs, buttons, dropdowns, etc. Just the ComboBox can come near the complexity of the DataGrid, but it's still far behind.\n\nIt's important to be able to tame all this complexity - otherwise it can slow down the development process and bring it to a halt, making it difficult to add new features or fix bugs. With our current model, even though the DataGrid grew in complexity and features, we never felt our velocity dropping! We enjoy that!\n\n## No dependencies\n\nWe're very proud of the fact that we have no dependencies in our DataGrid. When you install our package, you only install our package - and nothing else. Nothing that can go wrong due to version conflicts, missing dependencies, npm issues ([remember left-pad](https://www.davidhaney.io/npm-left-pad-have-we-forgotten-how-to-program/)?).\n\nYes, we still depend on packages in our dev process, but we're striving to keep that small as well. It's already complex enough to keep TS, React, NextJS, npm (with workspaces), aliases, esbuild, tsup, playwright all working together in harmony. But we've got through it, and we're very happy with the result. It was worth it!\n\n## Separating concerns\n\nWe've separated our DataGrid into 2 main parts:\n\n- the `` component - handles data loading and processing\n- the `` component - handles the rendering\n\nThis was a brilliant idea! It's new? No! It's not our invention, but we're happy we decided to apply it.\n\nIt adds a better separation between the two big parts of the DataGrid. This also helps tame some of the complexity, while adding clarity to the codebase. It's easier to reason about the code when you know that the `` component is responsible for data loading and processing, while the `` component is ONLY responsible for rendering.\n\n## Conclusion\n\nWe're not sorry for choosing any of the above tools or approaches when building the InfiniteTable DataGrid component.\n\nOur developer velocity is high, and we're able to add new features and fix bugs at a fast pace. We're happy with the result and we're confident that we'll be able to keep this pace in the future.\n\nThe right tools get the right job done! They make a lot easier. Looking back, we only regret we didn't have those tools 5 years ago - but hey, things are moving in the right direction, and we're happy to be part of this journey.\n\nWhat are your tools for developer productivity?\n" - }, - { - "filePath": "/blog/2023/12/11/quick-guide-filtering-the-datagrid", - "routePath": "/blog/2023/12/11/quick-guide-filtering-the-datagrid", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2023/12/11/quick-guide-filtering-the-datagrid.page.md", - "fileName": "quick-guide-filtering-the-datagrid.page.md", - "folderPath": "/blog/2023/12/11/", + "filePath": "/blog/2025/03/20/async-context-menus", + "routePath": "/blog/2025/03/20/async-context-menus", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2025/03/20/async-context-menus.page.md", + "fileName": "async-context-menus.page.md", + "folderPath": "/blog/2025/03/20/", "frontmatter": { - "title": "Quick Guide - Filtering the DataGrid", - "author": "admin", - "draft": true, - "date": "2023-12-11T00:00:00.000Z", - "authorData": { - "label": "admin" - } + "title": "Async Context Menus", + "description": "Learn how to use async context menus in Infinite Table.", + "date": "2025-03-20T00:00:00.000Z" }, - "excerpt": "This is the first article in a series of quick guides that will help you get started with the DataGrid, each focusing on a specific feature. In this article, we will learn how to use filtering in the DataGrid.", + "excerpt": "Infinite Table 6.1.0 introduces support for lazy loading context menus. This is useful when you need to load your context menu items conditionally, from the backend, based on the cell's value or other conditions.", "readingTime": "1 min read", - "content": "\nThis is the first article in a series of quick guides that will help you get started with the DataGrid, each focusing on a specific feature. In this article, we will learn how to use filtering in the DataGrid.\n\n### Applying Filters on the DataSource\n\nYou apply filters on the `DataSource` component\n\n```tsx {4-11} title=\"Specifying an initial filter value for the DataSource\"\n\n data={...}\n primaryKey=\"id\"\n defaultFilterValue={[\n field: 'salary',\n filter: {\n operator: 'gt',\n value: 50000,\n type: 'number',\n },\n ]}\n/>\n```\n" + "content": "\nInfinite Table 6.1.0 introduces support for lazy loading context menus. This is useful when you need to load your context menu items conditionally, from the backend, based on the cell's value or other conditions.\n\n## How it works\n\nStarting with version `6.1.0`, the and props can now return a `Promise` that resolves to an array of `MenuItem` objects (or an object with `items` and `columns` properties, if you need to also configure the columns).\n\n\n\n\nRight click any cell in the DataGrid - the context menu will be displayed with a delay of `400ms`.\n\n\n\n\n\n\nThe is called with an object that gives you access to all the info regarding the current right-clicked cell - both the row information and the current column. You can use that to decide whether you want to return a menu immediately or to fetch some data from the server and display the context menu after the server response comes in.\n\n" }, { "filePath": "/blog/2025/05/12/the-first-devtools-for-a-datagrid", @@ -1792,21 +1807,6 @@ "readingTime": "3 min read", "content": "\nWe're happy to announce that [Infinite Table DevTools extension](https://chromewebstore.google.com/detail/infinite-table-devtools-e/jpipjljbfffijmgiecljadbogfegejfa) is now live!\n\nInfinite Table is the first DataGrid with a Chrome DevTools extension. Starting with version `7.0.0` of Infinite, you can specify the `debugId` property on the `` instance and it will be picked up by the devtools.\n\n\n\nTo see the extension on a live demo, head to the [chrome webstore](https://chromewebstore.google.com/detail/infinite-table-devtools-e/jpipjljbfffijmgiecljadbogfegejfa) to download the extension.\n\nThen visit [our live demo page](/full-demo) and open your browser devtools - you should see the \"Infinite Table\" devtool tab. Click it and enjoy interacting with the DataGrid!\n\n\n\n```tsx {16}\nconst columns = {\n name: {\n field: 'firstName',\n },\n lastName: {\n field: 'lastName',\n },\n age: {\n field: 'age',\n },\n}\n\nconst App = () => {\n return \n \n \n}\n```\n\n\n\nIf you have multiple instances, each with a unique `debugId` property, they will all show up\n\n\n\"Infinite\n\n\n## Current features\n\nThe Devtools extension was launched with an initial feature-set, which will expand as we grow and as we get user feedbak - so be sure to tell us what you'd like to see in the devtools.\n\nCurrently, it offers the ability to do the following:\n - see the list of all columns and adjust which are visible\n - see timings of the following data operations: sorting, filtering, group/pivot/tree.\n This always show how much the last operation of that type took.\n - interact with the grouping and sorting information in the `` - and revert it to user-values at any time\n - see and clear the logs\n - see various warning messages and performance-related issues.\n\n## Planned features\n\nAs we already mentioned, we're planning to expand the devtools, as we're just getting a taste of what's possible. It took us some time to figure our our best workflow in developing the devtools, and we're now confident we can iterate much faster.\n\nHaving said this, we're looking for feedback from you on what insights you'd like to see in the InfiniteTable DataGrid via the devtools. We have our own list of things we want to work on, but we plan to incorporate user-feedback asap. So here's our wishlist for the devtools:\n\n - ability to see more timings on various operations - including a chart with historical values during the lifetime of a DataGrid instance - something similar to how React DevTools shows render operations and their durations.\n - add the ability to filter logs via channel\n - show more performance tips&tricks that can make your DataGrid faster\n - allow you to interact with many props of the DataGrid - row and cell selection, keyboard navigation, filters, column state, sorting, pivoting, pivot result columns, aggregations, column groups, lazy loading, theming and more.\n - give you a full state of the DataGrid, and the ability to apply and restore it at any time.\n - show you the details of your license key and remind you if it's close to the expiration date.\n\n\n## Your turn\n\nIt's your turn to give us feedback on the Infinite Table DevTools Extension!\n\nLet us know what you think and how you'd like to use it in order to enhance your interaction with the DataGrid. " }, - { - "filePath": "/blog/2025/03/20/async-context-menus", - "routePath": "/blog/2025/03/20/async-context-menus", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2025/03/20/async-context-menus.page.md", - "fileName": "async-context-menus.page.md", - "folderPath": "/blog/2025/03/20/", - "frontmatter": { - "title": "Async Context Menus", - "description": "Learn how to use async context menus in Infinite Table.", - "date": "2025-03-20T00:00:00.000Z" - }, - "excerpt": "Infinite Table 6.1.0 introduces support for lazy loading context menus. This is useful when you need to load your context menu items conditionally, from the backend, based on the cell's value or other conditions.", - "readingTime": "1 min read", - "content": "\nInfinite Table 6.1.0 introduces support for lazy loading context menus. This is useful when you need to load your context menu items conditionally, from the backend, based on the cell's value or other conditions.\n\n## How it works\n\nStarting with version `6.1.0`, the and props can now return a `Promise` that resolves to an array of `MenuItem` objects (or an object with `items` and `columns` properties, if you need to also configure the columns).\n\n\n\n\nRight click any cell in the DataGrid - the context menu will be displayed with a delay of `400ms`.\n\n\n\n\n\n\nThe is called with an object that gives you access to all the info regarding the current right-clicked cell - both the row information and the current column. You can use that to decide whether you want to return a menu immediately or to fetch some data from the server and display the context menu after the server response comes in.\n\n" - }, { "filePath": "/blog/2025/08/16/grouping-toolbar-now-available-in-the-datagrid", "routePath": "/blog/2025/08/16/grouping-toolbar-now-available-in-the-datagrid", @@ -1826,6 +1826,36 @@ "readingTime": "3 min read", "content": "\n\nWith version `7.2.0`, we added another component to make your interaction with the DataGrid easier - namely the `GroupingToolbar`.\n\nThis toolbar allows users to interact with row grouping very easily, via drag and drop. Drag column headers on the GroupingToolbar component and off you go, grouping is updated. Additionally, you can drag items on the GroupingToolbar in order to change the order of the row grouping.\n\n```tsx {3} title=\"Base structure for using the grouping toolbar\"\n\n \n \n \n \n \n\n```\n\nSimply reference the component via `InfiniteTable.GroupingToolbar` and nest it under ``.\n\n\n\nIn the above and below examples, for simplicity, we're not showing the whole configuration of the `` and `` components - for full code examples, see further below.\n\n\n\nThe good part is that you can very easily add additional elements to your structure and have the grouping toolbar displayed on the side, vertically.\n\n```tsx {8} title=\"Example structure for vertical grouping toolbar\"\n\n \n
\n
\n \n \n
\n \n
\n
\n
\n```\n\n\n\n\n\nIn the example above, try dragging the header of the `hobby` column onto the GroupingToolbar to add grouping by `hobby`.\n\n\n\n\n## Horizontal and vertical layout\n\nAs shown above, you can use the `GroupingToolbar` both horizontally and vertically. This is configured via the `orientation` prop - either `\"horizontal\"` (the default) or `\"vertical\"`.\n\nMake sure you configure this to match your desired layout.\n\n\n\n## Customizing and Extending the GroupingToolbar\n\nWhen building this, we were sure you will want to customize almost everything about the toolbar. So we prepared a simple way to do this, via the `components` prop of the `GroupingToolbar`.\n\nThe following components are available:\n - `Placeholder` - controls the placeholder that's visible when there are no row groups available.\n - `ToolbarItem` - used to replace the toolbar items - corresponding to the row groups.\n - `Host` - the component itself - useful to override when you want to add some other React elements before or after the toolbar items.\n\nIn the example below, we demo how you can display a custom placeholder for the GroupingToolbar.\n\n\n\nWith all these ways to hook into the component, there are no limits to the styling and structure of your layout.\n\nGive it a try and let us know (via github issues or [twitter](https://x.com/get_infinite)) if there's anything you'd like to see improved or have questions about!\n\n## Summary\n\nThe new `GroupingToolbar` component brings an intuitive drag-and-drop interface to row grouping in `InfiniteTable`. Whether you prefer horizontal or vertical layouts, the toolbar provides a seamless way to manage grouping while maintaining the flexibility to customize its appearance and behavior.\n\nWe're excited to see how you'll use this new feature in your applications. Happy coding!\n\n" }, + { + "filePath": "/blog/2025/10/09/customizing-your-datagrid-component-with-tailwind", + "routePath": "/blog/2025/10/09/customizing-your-datagrid-component-with-tailwind", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2025/10/09/customizing-your-datagrid-component-with-tailwind.page.md", + "fileName": "customizing-your-datagrid-component-with-tailwind.page.md", + "folderPath": "/blog/2025/10/09/", + "frontmatter": { + "title": "Customizing your DataGrid component with Tailwind CSS", + "description": "Find out how to customize your DataGrid component to fit your app needs, using Tailwind CSS", + "date": "2025-10-09T00:00:00.000Z", + "author": "radu", + "draft": true, + "tags": [ + "theming", + "customizing" + ], + "thumbnail": "/gen/assets/customizing-your-datagrid-component.png", + "authorData": { + "label": "radu" + }, + "thumbnailDimensions": { + "width": 1536, + "height": 1024, + "type": "png" + } + }, + "excerpt": "We haven't spoken about this very much, but Infinite Table does offer you very powerful ways to customize the structure of your component. After all, we're a React-first DataGrid, so the component and composition patterns it offers should feel at home in a React app.", + "readingTime": "2 min read", + "content": "\nWe haven't spoken about this very much, but Infinite Table does offer you very powerful ways to customize the structure of your component. After all, we're a React-first DataGrid, so the component and composition patterns it offers should feel at home in a React app.\n\n\n```tsx title=\"Default structure of InfiniteTable\"\n\n \n\n```\n\n## Customizing the nesting of the InfiniteTable component\n\nHowever, be aware that you the `` component doesn't have to be a direct child of the `` component. The `` component doesn't actually render anything, but its job is to load, process and prepare the data in a way that `` understands and can display. And actually you can use the DataSource context to gain access to the data yourself.\n\n```tsx {4} title=\"InfiniteTable can be nested anywhere inside the component\"\n\n

Your DataGrid

\n \n \n \n
\n```\n\n\n\n\nInside the `` component you can use the DataSource-provided context via the hook that our component exposes.\n\n\n\n## Choosing what to render\n\nBesides the flexibility of nesting your DataGrid component anywhere in your app, we also offer you the ability to choose what parts of the DataGrid you want to render and where.\n\nLet's suppose you want to show the header after the body of the DataGrid or choose to insert something in between. That should be easy, right? **It is with Infinite!** - but try to do that with the other commercial DataGrids out there!\n\n```tsx live tailwind file=\"./customizing-structure.page.tsx\"\n\n```\n\nAs demoed above, the good part is that you can very easily add additional elements to your structure and have the grouping toolbar displayed on the side, vertically.\n\n```tsx {8} title=\"Example structure for vertical grouping toolbar\"\n\n \n
\n
\n \n \n
\n \n
\n
\n
\n```\n\n\n\nIn the example above, try dragging the header of the `age` column onto the `GroupingToolbar` to add grouping by `age`.\n\n\n\n\n" + }, { "filePath": "/docs/learn/grouping-and-pivoting/pivoting/customizing-pivot-columns", "routePath": "/docs/learn/grouping-and-pivoting/pivoting/customizing-pivot-columns", @@ -1923,6 +1953,17 @@ "readingTime": "1 min read", "content": "\n# Blog post about keeping productivity high in infinite table - tests, build tools, styling approach\n" }, + "/comparison/": { + "filePath": "/comparison/index", + "routePath": "/comparison/", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/comparison/index.page.md", + "fileName": "index.page.md", + "folderPath": "/comparison/", + "frontmatter": {}, + "excerpt": "# Comparison of React DataGrids", + "readingTime": "1 min read", + "content": "# Comparison of React DataGrids\n\n## DevExtreme Data Grid\n\n\nSandpack deps=\"devextreme-react,devextreme\">\n\n```tsx file=\"devexpress.app.tsx\"\n\n```\n\n```html file=devexpress.index.html\n\n```\n\n```ts file=\"columns.ts\"\n\n```\n\nSandpack\n\n## AG Grid\n\nSandpack deps=\"ag-grid-community,ag-grid-react,ag-grid-enterprise\"\n\n`tsx file=\"aggrid.page.tsx\"\n\n`\n\n`ts file=\"columns.ts\"\n\n`\n\nSandpack\n\n## Infinite Table\n\nSandpack\n\n`tsx file=\"infinite.page.tsx\"\n\n`\n\n`ts file=\"columns.ts\"\n\n`\n\nSandpack\n" + }, "/docs/devtools": { "filePath": "/docs/devtools", "routePath": "/docs/devtools", @@ -1949,18 +1990,7 @@ }, "excerpt": "", "readingTime": "3 min read", - "content": "\n\n\n## What is Infinite Table?\n\nInfinite Table is a React DataGrid component for displaying virtualized tabular data.\n\nIt helps you display huge datasets and get the most out of your data by providing you the right tools to enjoy these features:\n\n- [ sorting](/docs/learn/sorting/overview)\n- [ row grouping](/docs/learn/grouping-and-pivoting/grouping-rows) - both server-side and client-side\n- [ pivoting](/docs/learn/grouping-and-pivoting/pivoting/overview) - both server-side and client-side\n- [ aggregations](/docs/learn/grouping-and-pivoting/grouping-rows#aggregations)\n- [ live pagination](/docs/learn/working-with-data/live-pagination)\n- [ lazy loading](/docs/learn/working-with-data/lazy-loading)\n- [ keyboard navigation](/docs/learn/keyboard-navigation/navigating-cells)\n- [ fixed and flexible columns](/docs/learn/columns/fixed-and-flexible-size)\n- [ column grouping](/docs/learn/column-groups)\n- [ filtering](/docs/learn/filtering)\n- [ theming](/docs/learn/theming)\n\n## Installation\n\nInstallation could not be more straightforward - just one npm command:\n\n\nnpm i @infinite-table/infinite-react\n\n\n## โค๏ธ TypeScript\n\nInfinite Table is fully typed and offers you a great developer experience, to help you get up and running quickly.\n\n> The TypeScript typings file is included in the npm package - you don't have to download an additional **@types** package\n\n\n\nRead more about how to use our TypeScript types\n\n\n\n\n## ๐Ÿ“„ Extensive Documentation\n\nWe're aware good documentation is a must and are updating our documentation as we add new features. Head to [our getting started](/docs/learn/getting-started) guide to get up and running quickly.\n\n## ๐Ÿข Enterprise-Ready\n\nInfinite Table is ready to power your enterprise apps, as it supports advanced [data fetching](/docs/learn/working-with-data#data-loading-strategies), [filtering](/docs/learn/filtering), [sorting](/docs/learn/sorting/overview), [grouping](/docs/learn/grouping-and-pivoting/grouping-rows), [pivoting](/docs/learn/grouping-and-pivoting/pivoting/overview), [aggregations](/docs/learn/grouping-and-pivoting/group-aggregations), [live pagination](/docs/learn/working-with-data/live-pagination), [lazy loading](/docs/learn/working-with-data/lazy-loading) - all of those with support for both client-side and server-side implementations.\n\nYou can choose to leverage our built-in implementations in the browser, or you can process your data on the server with full support from our-side.\n\n### ๐Ÿ”’ Secure by Default\n\nWe take security seriously and only have a total of 3 dependencies in our full dependency graph - and this number will only go down.\n\n### ๐Ÿ“ฆ Small Bundle Size\n\nOur bundle size is under `300kB` and we're dedicated to [keeping it small](https://bundlephobia.com/package/@infinite-table/infinite-react).\n\n\n\n\nSee our bundle size in BundlePhobia\n\n\n\n\n### ๐Ÿงช Automated End-to-End Tests\n\nOur releases are automated and, we have full end-to-end tests that ensure we're delivering to our standards.\n\nReal-browser tests help us move with confidence and continue to ship great features.\n\n\n\n\nCheck out our end-to-end tests in GitHub\n\n\n\n\n\n## ๐ŸŽจ Themable\n\n`Infinite Table` is fully customizable, via CSS variables.\n\nIt ships with both a **light** and a **dark** theme - all you have to do is import the CSS file from the package.\n\n```ts\nimport '@infinite-table/infinite-react/index.css';\n\n// This file includes both the light and the dark themes.\n```\n\n\n\n\nRead how to use themes and **CSS variables** to customize every aspect of Infinite Table\n\n\n\n" - }, - "/comparison/": { - "filePath": "/comparison/index", - "routePath": "/comparison/", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/comparison/index.page.md", - "fileName": "index.page.md", - "folderPath": "/comparison/", - "frontmatter": {}, - "excerpt": "# Comparison of React DataGrids", - "readingTime": "1 min read", - "content": "# Comparison of React DataGrids\n\n## DevExtreme Data Grid\n\n\nSandpack deps=\"devextreme-react,devextreme\">\n\n```tsx file=\"devexpress.app.tsx\"\n\n```\n\n```html file=devexpress.index.html\n\n```\n\n```ts file=\"columns.ts\"\n\n```\n\nSandpack\n\n## AG Grid\n\nSandpack deps=\"ag-grid-community,ag-grid-react,ag-grid-enterprise\"\n\n`tsx file=\"aggrid.page.tsx\"\n\n`\n\n`ts file=\"columns.ts\"\n\n`\n\nSandpack\n\n## Infinite Table\n\nSandpack\n\n`tsx file=\"infinite.page.tsx\"\n\n`\n\n`ts file=\"columns.ts\"\n\n`\n\nSandpack\n" + "content": "\n\n\n## What is Infinite Table?\n\nInfinite Table is a React DataGrid component for displaying virtualized tabular data.\n\nIt helps you display huge datasets and get the most out of your data by providing you the right tools to enjoy these features:\n\n- [ sorting](/docs/learn/sorting/overview)\n- [ row grouping](/docs/learn/grouping-and-pivoting/grouping-rows) - both server-side and client-side\n- [ pivoting](/docs/learn/grouping-and-pivoting/pivoting/overview) - both server-side and client-side\n- [ aggregations](/docs/learn/grouping-and-pivoting/grouping-rows#aggregations)\n- [ live pagination](/docs/learn/working-with-data/live-pagination)\n- [ lazy loading](/docs/learn/working-with-data/lazy-loading)\n- [ keyboard navigation](/docs/learn/keyboard-navigation/navigating-cells)\n- [ fixed and flexible columns](/docs/learn/columns/fixed-and-flexible-size)\n- [ column grouping](/docs/learn/columns/column-grouping)\n- [ filtering](/docs/learn/filtering)\n- [ theming](/docs/learn/theming)\n\n## Installation\n\nInstallation could not be more straightforward - just one npm command:\n\n\nnpm i @infinite-table/infinite-react\n\n\n## โค๏ธ TypeScript\n\nInfinite Table is fully typed and offers you a great developer experience, to help you get up and running quickly.\n\n> The TypeScript typings file is included in the npm package - you don't have to download an additional **@types** package\n\n\n\nRead more about how to use our TypeScript types\n\n\n\n\n## ๐Ÿ“„ Extensive Documentation\n\nWe're aware good documentation is a must and are updating our documentation as we add new features. Head to [our getting started](/docs/learn/getting-started) guide to get up and running quickly.\n\n## ๐Ÿข Enterprise-Ready\n\nInfinite Table is ready to power your enterprise apps, as it supports advanced [data fetching](/docs/learn/working-with-data#data-loading-strategies), [filtering](/docs/learn/filtering), [sorting](/docs/learn/sorting/overview), [grouping](/docs/learn/grouping-and-pivoting/grouping-rows), [pivoting](/docs/learn/grouping-and-pivoting/pivoting/overview), [aggregations](/docs/learn/grouping-and-pivoting/group-aggregations), [live pagination](/docs/learn/working-with-data/live-pagination), [lazy loading](/docs/learn/working-with-data/lazy-loading) - all of those with support for both client-side and server-side implementations.\n\nYou can choose to leverage our built-in implementations in the browser, or you can process your data on the server with full support from our-side.\n\n### ๐Ÿ”’ Secure by Default\n\nWe take security seriously and only have a total of 3 dependencies in our full dependency graph - and this number will only go down.\n\n### ๐Ÿ“ฆ Small Bundle Size\n\nOur bundle size is under `300kB` and we're dedicated to [keeping it small](https://bundlephobia.com/package/@infinite-table/infinite-react).\n\n\n\n\nSee our bundle size in BundlePhobia\n\n\n\n\n### ๐Ÿงช Automated End-to-End Tests\n\nOur releases are automated and, we have full end-to-end tests that ensure we're delivering to our standards.\n\nReal-browser tests help us move with confidence and continue to ship great features.\n\n\n\n\nCheck out our end-to-end tests in GitHub\n\n\n\n\n\n## ๐ŸŽจ Themable\n\n`Infinite Table` is fully customizable, via CSS variables.\n\nIt ships with both a **light** and a **dark** theme - all you have to do is import the CSS file from the package.\n\n```ts\nimport '@infinite-table/infinite-react/index.css';\n\n// This file includes both the light and the dark themes.\n```\n\n\n\n\nRead how to use themes and **CSS variables** to customize every aspect of Infinite Table\n\n\n\n" }, "/docs/reference/error-codes": { "filePath": "/docs/reference/error-codes", @@ -2035,6 +2065,20 @@ "readingTime": "1 min read", "content": "\n## 1.0.0 ๐Ÿš€\n\n@milestone id=\"60\"\n" }, + "/docs/learn/common-issues/": { + "filePath": "/docs/learn/common-issues/index", + "routePath": "/docs/learn/common-issues/", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/learn/common-issues/index.page.md", + "fileName": "index.page.md", + "folderPath": "/docs/learn/common-issues/", + "frontmatter": { + "title": "Common Issues", + "description": "Avoid common pitfalls and issues when using the component. Learn how to use it properly to perform smooth and avoid jank." + }, + "excerpt": "As people have started using `` we've noticed a few issues keep popping up.", + "readingTime": "2 min read", + "content": "\nAs people have started using `` we've noticed a few issues keep popping up.\n\nWhile we're trying to refine our API to be easier to use and understand, developers using the component still need to be aware of some design decisions and conventions used in the component.\n\n## Issue: Performance degradation because props are new on every render\n\nPassing new props on every render to the `` component or to the `` component can be a performance bottleneck:\n\n```ts\n\n```\n\nInstead pass the **same** reference when things do change - stored in state or any other place:\n\n```ts\nconst [groupBy, setGroupBy] = useState([{ field: 'country' }]);\n\n;\n```\n\n\n\nWhen in dev mode, you can set `localStorage.debug = \"*\"` in your localstorage to see potential issues logged to the console.\n\nFor example, you might see:\n\n`InfiniteTable:rerender Triggered by new values for the following props +1s columns`\n\n\n\n## Issue: State inside custom components rendered in cells is lost while scrolling\n\nWhen using custom rendering or custom components for columns, make sure all your rendering logic is [controlled](https://reactjs.org/docs/forms.html#controlled-components) and that it doesn't have any local or transient state.\n\nThis is important because `InfiniteTable` makes heavy use of virtualization, in both _column cells and column headers_, so **custom components can and will be unmounted and re-mounted multiple times**, during the virtualization process (triggered by user scrolling, sorting, filtering and a few other interactions).\n" + }, "/docs/learn/columns/cell-and-column-styling": { "filePath": "/docs/learn/columns/cell-and-column-styling", "routePath": "/docs/learn/columns/cell-and-column-styling", @@ -2174,20 +2218,6 @@ "readingTime": "4 min read", "content": "\nColumns are a central feature in `InfiniteTable`.\n\nYou define columns as a an object, with keys being column ids while values are the column definitions.\n\nYou then use them in the `columns` prop in your `InfiniteTable` component.\n\nThe prop is typed either as\n\n- `Record>`\n- or `InfiniteTablePropColumns`, which is an alias for the type above\n\n\n\nIn `InfiniteTable`, columns are identified by their key in the object. **We'll refer to this as the column id**.\nThe column ids are used in many places - like defining the column order, column pinning, column visibility, etc.\n\n\n\n```ts\nexport type Employee = {\n id: number;\n companyName: string;\n firstName: string;\n lastName: string;\n country: string;\n city: string;\n department: string;\n team: string;\n salary: number;\n\n};\n\n// InfiniteTableColumn is a generic type, you have to bind it to a specific data-type\nimport { InfiniteTableColumn } from '@infinite-table/infinite-react';\n\n// we're binding it here to the `Employee` type\n// which means the `column.field` has to be `keyof Employee`\nexport const columns: Record> = {\n 'firstName':\n {\n field: 'firstName',\n header: 'First Name',\n },\n 'country':\n {\n field: 'country',\n },\n 'city':\n {\n field: 'city'\n },\n 'salary':\n {\n field: 'salary',\n type: 'number'\n },\n}\n\n```\n\n\n\nIt's very important to remember you should not pass a different reference of a prop on each render. `` is a optimized to only re-render when props change - so if you change the props on every re-render you will get a performance penalty.\n\nYou should use `React.useCallback` / `React.useMemo` / `React.useState` to make sure you only update the props you pass down to `InfiniteTable` when you have to.\n\n\n\n\n\n```ts file=\"basic-columns-example.page.tsx\"\n\n```\n\n\n\n\nFind out how to render custom content inside columns or even take full control of column cells and header.\n\n\n## Column Types\n\nColumn types allow you to customize column behavior and appearance for multiple columns at once. Most of the properties available for columns are also available for column types - for a full list, see columnTypes reference.\n\nThere are two special column types for now, but more are coming soon:\n\n- `default` - all columns have this type, if not otherwise specified. The type does not contain any configuration, but allows you to define it and apply common configuration to all columns.\n- `number` - if specified on a column (in combination with local uncontrolled sorting), the column will be sorted numerically.\n\n\nFind out how to use column types to customize the appearance and behaviour of your columns.\n\n\n## Column Order\n\nThe implicit column order is the order in which columns have been defined in the object. You can however control that explicitly by using the `columnOrder: string[]` prop.\n\n```tsx\n\nconst columnOrder = ['firstName','id','curency']\n\nconst App = () => {\n return primaryKey={\"id\"} dataSource={...}>\n \n columnOrder={columnOrder}\n onColumnOrderChange={(columnOrder: string[]) => {}}\n />\n \n}\n```\n\nThe prop is an array of strings, representing the column ids. A column id is the key of the column in the object.\n\n\n\nThe array can contain identifiers that are not yet defined in the Map, or can contain duplicate ids. This is a feature, not a bug. We want to allow you to use the in a flexible way so it can define the order of current and future columns.\n\n\n\n\n is a controlled prop. For the uncontrolled version, see \n\nWhen using controlled , make sure you also update the order by using the callback prop.\n\n\n\n\n```tsx file=\"$DOCS/reference/columnOrder-example.page.tsx\"\n\n```\n\n\n\n\n\nBy keeping the column order simple, namely an array of strings, ordering becomes much easier.\n\nThe alternative would be to make `columns` an array, which most DataGrids do - and whenever they are reordered, a new `columns` array would be needed.\n\n\n" }, - "/docs/learn/common-issues/": { - "filePath": "/docs/learn/common-issues/index", - "routePath": "/docs/learn/common-issues/", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/learn/common-issues/index.page.md", - "fileName": "index.page.md", - "folderPath": "/docs/learn/common-issues/", - "frontmatter": { - "title": "Common Issues", - "description": "Avoid common pitfalls and issues when using the component. Learn how to use it properly to perform smooth and avoid jank." - }, - "excerpt": "As people have started using `` we've noticed a few issues keep popping up.", - "readingTime": "2 min read", - "content": "\nAs people have started using `` we've noticed a few issues keep popping up.\n\nWhile we're trying to refine our API to be easier to use and understand, developers using the component still need to be aware of some design decisions and conventions used in the component.\n\n## Issue: Performance degradation because props are new on every render\n\nPassing new props on every render to the `` component or to the `` component can be a performance bottleneck:\n\n```ts\n\n```\n\nInstead pass the **same** reference when things do change - stored in state or any other place:\n\n```ts\nconst [groupBy, setGroupBy] = useState([{ field: 'country' }]);\n\n;\n```\n\n\n\nWhen in dev mode, you can set `localStorage.debug = \"*\"` in your localstorage to see potential issues logged to the console.\n\nFor example, you might see:\n\n`InfiniteTable:rerender Triggered by new values for the following props +1s columns`\n\n\n\n## Issue: State inside custom components rendered in cells is lost while scrolling\n\nWhen using custom rendering or custom components for columns, make sure all your rendering logic is [controlled](https://reactjs.org/docs/forms.html#controlled-components) and that it doesn't have any local or transient state.\n\nThis is important because `InfiniteTable` makes heavy use of virtualization, in both _column cells and column headers_, so **custom components can and will be unmounted and re-mounted multiple times**, during the virtualization process (triggered by user scrolling, sorting, filtering and a few other interactions).\n" - }, "/docs/learn/context-menus/using-context-menus": { "filePath": "/docs/learn/context-menus/using-context-menus", "routePath": "/docs/learn/context-menus/using-context-menus", @@ -2474,49 +2504,9 @@ "title": "TypeScript Types", "description": "Infinite Table types for TypeScript are published as part of the package, as named exports from the root of the package." }, - "excerpt": "Our `TypeScript` types are published as part of the package, as named exports from the root of the package.", - "readingTime": "3 min read", - "content": "\nOur `TypeScript` types are published as part of the package, as named exports from the root of the package.\n\nThe 2 main components that you can need to use and import are:\n\n- `InfiniteTable`\n- `DataSource`\n\n```tsx title=\"Importing InfiniteTable and DataSource components\"\nimport { InfiniteTable, DataSource } from '@infinite-table/infinite-react';\n```\n\n\n\nIn our TypeScript typings, those components are exported as generic components, so they need to be bound to the type of the data they are rendering.\n\n```tsx\ntype Developer = {\n id: number;\n\n firstName: string;\n lastName: string;\n\n currency: string;\n salary: number;\n}\n\nconst App = () => {\n return data={data} primaryKey=\"id\">\n \n columns={{...}}\n />\n \n}\n```\n\nThroughout the documentation, we will use the `DATA_TYPE` placeholder to refer to the type of the data that the `InfiniteTable` and `DataSource` components are bound to.\n\n\n\n\n\nYou can still use `InfiniteTable` in plain JavaScript, but you won't get all the type-checking benefits.\n\n\n\nBoth `InfiniteTable` and `DataSource` components have types provided for most of the props they support. Generally the naming pattern is `Prop`, so here are a few examples to clarify the rule:\n\n```ts\nimport type {\n InfiniteTablePropColumns,\n // corresponding to the `columns` prop\n DataSourcePropGroupBy,\n // corresponding to the `groupBy` prop\n} from '@infinite-table/infinite-react';\n```\n\n## `DataSource` Types\n\nHere are a few examples for types for the `DataSource` component:\n\n- `DataSourcePropGroupBy` - the type for DataSource.groupBy\n\n```tsx\nimport type { DataSourcePropGroupBy } from '@infinite-table/infinite-react';\n```\n\n- `DataSourcePropAggregationReducers` - the type for DataSource.aggregationReducers\n\n```tsx\nimport type { DataSourcePropAggregationReducers } from '@infinite-table/infinite-react';\n```\n\n\n\nNot all the `DataSource` props have types exported that follow this convention, so you can always use `DataSourceProps` to get the type that define all the props.\n\nIn this way you can access specific prop types by name\n\n- `DataSourceProps['groupBy']` - the type for DataSource.groupBy\n- `DataSourceProps['data']` - the type for DataSource.data\n- etc\n\n\n\n## `InfiniteTable` Types\n\nBelow you can find a few examples for types for the `InfiniteTable` component:\n\n- `InfiniteTablePropColumns` - the type for InfiniteTable.columns\n\n```tsx\nimport type { InfiniteTablePropColumns } from '@infinite-table/infinite-react';\n```\n\n- `InfiniteTablePropRowStyle` - the type for InfiniteTable.rowStyle\n\n```tsx\nimport type { InfiniteTablePropRowStyle } from '@infinite-table/infinite-react';\n```\n\n- `InfiniteTablePropColumnGroups` - the type for InfiniteTable.columnGroups\n\n```tsx\nimport type { InfiniteTablePropColumnGroups } from '@infinite-table/infinite-react';\n```\n\n\n\nNot all the `InfiniteTable` props have types exported that follow this convention, so you can always use `InfiniteTableProps` to get the type that define all the props the `InfiniteTable` component supports.\n\nIn this way you can access specific prop types by name:\n\n- `InfiniteTableProps['columns']` - the type for InfiniteTable.columns\n- `InfiniteTableProps['columnSizing']` - the type for InfiniteTable.columnSizing\n- etc\n\n\n\n\n\nWorth mentioning is the `InfiniteTableColumn` prop, which defines the type for the table .\n\n\n" - }, - "/docs/learn/grouping-and-pivoting/group-aggregations": { - "filePath": "/docs/learn/grouping-and-pivoting/group-aggregations", - "routePath": "/docs/learn/grouping-and-pivoting/group-aggregations", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/learn/grouping-and-pivoting/group-aggregations.page.md", - "fileName": "group-aggregations.page.md", - "folderPath": "/docs/learn/grouping-and-pivoting/", - "frontmatter": { - "title": "Aggregations", - "description": "Learn how to define & use aggregations on grouped rows in Infinite Table for React." - }, - "excerpt": "A natural next step when grouping data is **aggregating the grouped values**. We allow developers to define any number of aggregations and bind them to any column.", - "readingTime": "8 min read", - "content": "\nA natural next step when grouping data is **aggregating the grouped values**. We allow developers to define any number of aggregations and bind them to any column.\n\nThe aggregations are defined on the `` component and are easily available at render time. A client-side aggregation needs a reducer function that accumulates the values in the data array and computes the final result.\n\n\n\nThroughout the docs, we might refer to aggregations as reducers - which, more technically, they are, since they reduce an array of values (from a group) to a single value.\n\n\n\n## Client-Side Aggregations\n\nWhen using client-side aggregation, each aggregation can have the following:\n\n### An initial value\n\nThe `initialValue` is optional value to use as the initial (accumulator) value for the reducer function. You can think of aggregations as an \"enhanced\" version of [Array.reduce](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce), so initial value should sound familiar.\n\n\n\nThe `initialValue` can be a function - in this case it will be called to compute the initial value.\n\n\n\n### A reducer function\n\n`reducer` is the function to call for each value in the (grouped) data array. It is called with the following arguments:\n\n- `accumulator` - the value returned by the previous call to the reducer function, or the `initialValue` if this is the first call. You return the new accumulator value from this function.\n- `value` - the value of the current item in the data array. If the aggregation has a `field`, this is the value of that field in the current item. Otherwise, value is the result of calling the `reducer.getter(data)` function (if one exists) or null if no getter is defined.\n- `dataItem` - the current item in the data array.\n- `index` - the index of the current item in the data array.\n\n### A `field` property or a `getter` function\n\nFor simple use-cases of client-side aggregations, a `field` is the way to go. This defines the field property (from the DATA_TYPE) to which the aggregation is bound.\n\nFor more complex scenarios, the aggregation should have a `getter` function. If both a `field` and a `getter` are provided, the `getter` has higher priority and will be used.\n\nUse this `getter` function to compute the value the current item in the array brings to the aggregation.\n\n```tsx title=\"Aggregation_custom_getter_function\"\n// useful for retrieving nested values\n\ngetter: (dataItem: Developer) => data.salary.net;\n```\n\n\n\nFor using nested values inside aggregations, use the aggregation `getter` function.\n\n\n\n### A completion `done` function\n\nThe completion `done` function is optional - if specified, will be after iterating over all the values in the grouped data array. Can be used to change the final result of the aggregation. It is called with the following arguments:\n\n- `accumulator` - the value returned by the last call to the reducer function\n- `data` - the grouped data array.\n This is useful for computing averages, for example:\n\n```tsx title=\"Done function for avg reducer\"\ndone: (acc, data) => acc / data.length;\n```\n\n### Putting it all together\n\nLet's take a look at a simple example of aggregating two columns, one to display the avg and the other one should compute the sum of the salary column for grouped rows.\n\n```tsx title=\"Average Aggregation\"\nimport { DataSource, InfiniteTable } from '@infinite-table/infinite-react';\n\nconst sum = (a: number, b: number) => a + b;\n\nconst reducers = {\n avg: {\n initialValue: 0,\n field: 'age',\n reducer: sum,\n done: (acc, data) => Math.round(acc / data.length),\n },\n\n sumAgg: {\n initialValue: 0,\n field: 'salary',\n reducer: sum\n }\n}\n\nfunction App() {\n return \n aggregationReducers={reducers}\n >\n {...} />\n \n}\n```\n\nIn the above example, note that aggregations are an object where the keys of the object are used to identify the aggregation and the values are the aggregation configuration objects, as described above.\n\n\n\nAt run-time, you have access to the aggregation reducer results inside group rows - you can use the `rowInfo.reducerResults` object to access those values. For the example above, you change how group rows are rendered for a certain column and display the aggregation results in a custom way:\n\n```tsx {9} title=\"Custom_group_row_rendering_for_the_country_column\"\n\ncountry: {\n field: 'country',\n\n // define a custom renderGroupValue fn for the country column\n\n renderGroupValue: ({ rowInfo }) => {\n const { reducerResults = {} } = rowInfo;\n // note the keys in the reducerResults objects match the keys in the aggregationReducers object\n return `Avg age: ${reducerResults.avg}, total salary ${reducerResults.sumAgg}`;\n },\n},\n```\n\n\n\n\n\n```ts file=\"aggregations-simple-example.page.tsx\"\n\n```\n\n\n\n## Server-Side Aggregations\n\nServer-side aggregations are defined in the same way as client-side aggregations (except the `reducer` function is missing), but the aggregation values are computed by the server and returned as part of the data response.\n\nFor computing the grouping and aggregations on the server, the backend needs to know the grouping and aggregation configuration. As such, Infinite Table will call the DataSource data function with an object that contains all the required info:\n\n- `groupBy` - the array of grouping fields, as passed to the `` component.\n- `pivotBy` - the array of pivot fields, as passed to the `` component.\n- `aggregationReducers` - the value of the prop, as configured on the `` component.\n- `sortInfo` - the current sorting information for the data.\n\nFor the lazy-loading use-case, there are other useful properties you can use from the object passed into the `data` function:\n\n- `groupKeys: string[]` - the group keys for the current group - the `data` fn is generally called lazily when the user expands a group row. This info is useful for fetching the data for a specific group.\n- `lazyLoadStartIndex` - provided when batching is also enabled via the prop. This is the index of the first item in the current batch.\n- `lazyLoadBatchSize` - also used when batching is enabled. This is the number of items in the current batch.\n\nBesides the above information, if filtering is used, a `fiterValue` is also made available.\n\nIn order to showcase the server-side aggregations, let's build an example similar to the above one, but let's lazily load group data.\n\n```tsx {2} title=\"DataSourcewith lazyLoad enabled\"\n\n```\n\nAs soon a grouping and aggregations are no longer computed on the client, your `data` function needs to send those configurations on the backend, so it needs to get a bit more complicated:\n\n```tsx title=\"Data_function_sending_configurations_to_the_backend\"\nconst data = ({ groupBy, aggregationReducers, sortInfo, groupKeys }) => {\n // it's important to send the current group keys - for top level, this will be []\n const args: string[] = [`groupKeys=${JSON.stringify(groupKeys)}`];\n\n // turn the sorting info into an array\n if (sortInfo && !Array.isArray(sortInfo)) {\n sortInfo = [sortInfo];\n }\n\n if (sortInfo) {\n // the backend expects the sort info to be an array of field,dir pairs\n args.push(\n 'sortInfo=' +\n JSON.stringify(\n sortInfo.map((s) => ({\n field: s.field,\n dir: s.dir,\n })),\n ),\n );\n }\n\n if (groupBy) {\n // for grouping, send an array of objects with the `field` property\n args.push(\n 'groupBy=' + JSON.stringify(groupBy.map((p) => ({ field: p.field }))),\n );\n }\n\n if (aggregationReducers) {\n args.push(\n 'reducers=' +\n JSON.stringify(\n // by convention, we send an array of reducers, each with `field` `name`(= \"avg\") and `id`\n // it's up to you to decide what the backend needs\n Object.keys(aggregationReducers).map((key) => ({\n field: aggregationReducers[key].field,\n id: key,\n name: aggregationReducers[key].reducer,\n })),\n ),\n );\n }\n\n const url = BASE_URL + `/developers10k-sql?` + args.join('&');\n return fetch(url).then(r=>r.json())\n}\n\n\n```\n\nWhen fetching without grouping (or with local grouping and aggregations), the `` component expects a flat array of data items coming from the server.\n\nHowever, when the grouping is happening server-side, the `` component expects a response that has the following shape:\n\n- `data` - the root array with grouping and aggregation info. Each item in the array should have the following:\n - `keys` - an array of the keys for the current group - eg `['USA']` or `['USA', 'New York']`\n - `data` - an object with all the common values for the group - eg `{ country: 'USA' }` or `{ country: 'USA', city: 'New York' }`\n - `aggregations` - an object with the aggregation values for the group - eg `{ age: 30, salary: 120300 }`. The keys in this object should match the keys in the object.\n - `pivot` - pivoting information for the current group - more on that on the dedicated [Pivoting page](./pivoting/overview).\n\nWhen the user is expanding the last level, in order to see the leaf rows, the shape of the response is expected to be the same as when there is no grouping - namely an array of data items or an object where the `data` property is an array of data items.\n\nLet's put all of this into a working example.\n\n\n\n\n\nThis showcases grouping and aggregations on the server - both the `age` and `salary` columns have an AVG aggregation defined.\n\nGrouping is done by the `country`, `city` and `stack` columns.\n\n\n\n```tsx file=\"grouping-and-aggregations-with-lazy-load-example.page.tsx\"\n\n```\n\n\n\n\n\nWhen the user is doing a sort on the table, the `` is fetched from scratch, but the expanded/collapsed state is preserved, and all the required groups that need to be re-fetched are reloaded as needed (if they are not eagerly included in the served data).\n\n\n" - }, - "/docs/learn/grouping-and-pivoting/grouping-rows": { - "filePath": "/docs/learn/grouping-and-pivoting/grouping-rows", - "routePath": "/docs/learn/grouping-and-pivoting/grouping-rows", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/learn/grouping-and-pivoting/grouping-rows.page.md", - "fileName": "grouping-rows.page.md", - "folderPath": "/docs/learn/grouping-and-pivoting/", - "frontmatter": { - "title": "Grouping rows" - }, - "excerpt": "You can use any `field` available in the `DataSource` to do the grouping - it can even be a `field` that is not a column.", - "readingTime": "15 min read", - "content": "\nYou can use any `field` available in the `DataSource` to do the grouping - it can even be a `field` that is not a column.\n\n\n\nWhen using TypeScript, both `DataSource` and `InfiniteTable` components are generic and need to be rendered/instantiated with a `DATA_TYPE` parameter. The fields in that `DATA_TYPE` can then be used for grouping.\n\n\n\n```tsx\ntype Person = {\n name: string;\n age: number;\n country: string;\n id: string;\n}\n\nconst groupBy = [{field: 'country'}]\n\n groupBy={groupBy}>\n />\n\n\n```\n\nIn the example above, we're grouping by `country`, which is a field available in the `Person` type. Specifying a field not defined in the `Person` type would be a type error.\n\nAdditionally, a `column` object can be used together with the `field` to define how the group column should be rendered.\n\n```tsx {4}\nconst groupBy = [\n {\n field: 'country',\n column: {\n // custom column configuration for group column\n width: 150,\n header: 'Country group',\n },\n },\n];\n```\n\nThe example below puts it all together.\n\nAlso see the groupBy API reference to find out more.\n\n\n\n```ts file=\"row-grouping-example.page.tsx\"\n\n```\n\n\n\nIn `groupBy.column` you can use any column property - so, for example, you can define a custom `renderValue` function to customize the rendering.\n\n```tsx {5}\nconst groupBy = [\n {\n field: 'country',\n column: {\n renderValue: ({ value }) => <>Country: {value},\n },\n },\n];\n```\n\n\n\nThe generated group column(s) - can be one for all groups or one for each group - will inherit the `style`/`className`/renderers from the columns corresponding to the group fields themselves (if those columns exist).\n\nAdditionally, there are other ways to override those inherited configurations, in order to configure the group columns:\n\n- use to specify how each grouping column should look for the respective field (in case of groupRenderStrateg=\"multi-column\")\n- use prop\n - can be used as an object - ideal for when you have simple requirements and when groupRenderStrateg=\"single-column\"\n - as a function that returns a column configuration - can be used like this in either single or multiple group render strategy\n\n\n\n## Controlling the collapse/expand state\n\nWhen you do grouping, by default, all row groups are expanded. Of course you have full control over this and you do this via the / props.\n\nIf you simply want to specify the initial expanded/collapsed state, you should use the prop.\n\n```tsx title=\"Specifying the default state for group rows\"\nconst defaultGroupRowsState: DataSourcePropGroupRowsStateObject = {\n collapsedRows: true,\n expandedRows: [['Mexico'], ['Mexico', 'backend'], ['India']],\n};\n```\n\nThe two properties in this object are `collapsedRows` and `expandedRows`, and each can have the following values:\n - `true` - meaning that all groups have this state\n - an array of arrays - representing the exceptions to the default value\n\n\nSo if you have `collapsedRows` set to `true` and then `expandedRows` set to `[['Mexico'], ['Mexico', 'backend'], ['India']]` then all rows are collapsed by default, except the rows specified in the `expandedRows`.\n\n\n\n\n```ts file=\"row-grouping-state-example.page.tsx\"\n\n```\n\n\n\n\n\nYou can specify expand/collapse state at any level of nesting.\n\nLet's suppose by default all rows are collapsed - if you want a node to be visible then you have to specify all its parents as expanded.\n\nSo having this\n```tsx \nconst defaultGroupRowsState = {\n collapsedRows: true,\n expandedRows: [['Mexico', 'backend']],\n};\n```\nwill show all rows as collapsed, and just as soon as you expand `Mexico` you will see the `backend` group row for Mexico to be expanded.\n\n\nThis data format gives you ultimate flexibility and allows you to easily restore an expand/collpase state at a later time, if you wanted to.\n\n\nIf you use the controlled , make sure you update it by leveraging the callback prop.\n\n\n\n\n\n```ts file=\"row-grouping-state-controlled-example.page.tsx\"\n\n```\n\n\n\nIn addition to simple objects with the shape described above, the / can also be instanges of `GroupRowsState` class, which is exported by the Infinite Table package. This class is simply a wrapper around those objects, but it gives you additional utility methods.\n\n\n\nThe callback gives you an instance of back as the single argument. If you're using plain objects, just do `groupRowsState.getState()` and you'll get the corresponding plain object for the current expand/collapse state.\n\n give you some additional helper methods, which you can read about here\n\n\n## Grouping strategies\n\nMultiple grouping strategies are supported by, `InfiniteTable` DataGrid:\n\n- multi column mode - multiple group columns are generated, one for each specified group field\n- single column mode - a single group column is generated, even when there are multiple group fields\n\nYou can specify the rendering strategy explicitly by setting the property to any of the following: `multi-column`, `single-column`. If you don't set it explicitly, it will choose the best default based on your configuration.\n\n### Multiple groups columns\n\nWhen grouping by multiple fields, by default the component will render a group column for each group field\n\n```tsx\nconst groupBy = [\n {\n field: 'age',\n column: {\n width: 100,\n renderValue: ({ value }) => <>Age: {value},\n },\n },\n {\n field: 'companyName',\n },\n {\n field: 'country',\n },\n];\n```\n\nLet's see an example of how the component would render the table with the multi-column strategy.\n\n\n\n```ts files=[\"row-grouping-multi-column-example.page.tsx\",\"columns.ts\"]\n\n```\n\n\n\nFor the `multi-column` strategy, you can use in order to hide columns for groups which are currently not visible.\n\n\n\n```ts files=[\"$DOCS/reference/hideEmptyGroupColumns-example.page.tsx\",\"$DOCS/reference/employee-columns.ts\"]\n\n```\n\n\n\n\n\nYou can specify an `id` for group columns. This is helpful if you want to size those columns (via ) or pin them (via ) or configure them in other ways. If no `id` is specified, it will be generated like this: `\"group-by-${field}\"`\n\n\n\n### Single group column\n\nYou can group by multiple fields, yet only render a single group column. To choose this rendering strategy, specify property to be `single-column` (or specify as an object.)\n\nIn this case, you can't override the group column for each group field, as there's only one group column being generated. However, you can specify a property to customize the generated column.\n\n\n\nBy default the generated group column will \"inherit\" many of the properties (the column style or className or renderers) of the columns corresponding to the group fields (if such columns exist, because it's not mandatory that they are defined).\n\n\n\n\n\n```ts files=[\"row-grouping-single-column-example.page.tsx\",\"columns.ts\"]\n\n```\n\n\n\n\n\nIf is specified to an object and no is passed, the render strategy will be `single-column`.\n\n can also be a function, which allows you to individually customize each group column - in case the `multi-column` strategy is used.\n\n\n\n\n\nYou can specify an `id` for the single . This is helpful if you want to size this column (via ) or pin it (via ) or configure it in other ways. If no `id` is specified, it will default to `\"group-by\"`.\n\n\n\n## Customizing the group column\n\nThere are many ways to customize the group column(s) and we're going to show a few of them below:\n\n### Binding the group column to a `field`\n\nBy default, group columns only show values in the group rows - but they are normal columns, so why not bind them to a field of the `DATA_TYPE`?\n\n```tsx {6,11}\nconst groupColumn = {\n id: 'the-group', // can specify an id\n style: {\n color: 'tomato',\n },\n field: 'firstName', // non-group rows will render the first name\n};\nconst columns = {\n theFirstName: {\n field: 'firstName',\n style: {\n // this style will also be applied in the group column,\n // since it is bound to this same `field`\n fontWeight: 'bold',\n },\n },\n};\n```\n\nThis makes the column display the value of the `field` in non-group/normal rows. Also, if you have another column bound to that `field`, the renderers/styling of that column will be used for the value of the group column, in non-group rows.\n\n\n\n```ts file=\"$DOCS/reference/bind-group-column-to-field-example.page.tsx\"\n\n```\n\n\n\n### Use `groupColumn` to customize rendering\n\nThe will inherit its own rendering and styling from the columns that are bound to the fields used in . However, you can override any of those properties so you have full control over the rendering process.\n\n```tsx {3,6}\nconst groupColumn = {\n field: 'firstName',\n renderGroupValue: ({ value }) => {\n return `Group: ${value}`;\n },\n renderLeafValue: ({ value }) => {\n return `First name: ${value}`;\n },\n};\n```\n\n\n\n\n\nThe column that renders the `firstName` has a custom renderer that adds a `.` at the end.\nThe group column is bound to the same `firstName` field, but specifies a different renderer, which will be used instead.\n\n\n\n```ts file=\"$DOCS/reference/group-column-custom-renderers-example.page.tsx\"\n\n```\n\n\n\n\n\nLearn more about customizing column rendering via multiple renderer functions.\n\n\n\n## Hiding columns when grouping\n\nWhen grouping is enabled, you can choose to hide some columns. Here are the two main ways to do this:\n\n- use - this will make columns bound to the group fields be hidden when grouping is active\n- use (also available on the column types, as ) - this is a column-level property, so you have more fine-grained control over what is hidden and when.\n\nValid values for are:\n\n- `\"*\"` - when any grouping is active, hide the column that specifies this property\n- `true` - when the field this column is bound to is used in grouping, hides this column\n- `keyof DATA_TYPE` - specify an exact field that, when grouped by, makes this column be hidden\n- `{[k in keyof DATA_TYPE]: true}` - an object that can specify more fields. When there is grouping by any of those fields, the current column gets hidden.\n\n\n\n\n\nIn this example, the column bound to `firstName` field is set to hide when any grouping is active, since the group column is anyways found to the `firstName` field.\n\nIn addition, is set to `true`, so the `stack` and `preferredLanguage` columns are also hidden, since they are grouped by.\n\n\n\n```ts file=\"$DOCS/reference/hide-columns-when-grouping-example.page.tsx\"\n\n```\n\n\n\n## Sorting the group column\n\nWhen groupRenderStrategy=\"single-column\" is used, the group column is sortable by default if all the columns that are involved in grouping are sortable. Sorting the group column makes the `sortInfo` have a value that looks like this:\n\n```ts\nconst sortInfo = [\n {\n dir: 1,\n id: 'group-by',\n field: ['stack', 'age'],\n type: ['string', 'number'],\n },\n];\n```\n\ngroupRenderStrategy=\"multi-column\", each group column is sortable by default if the column with the corresponding field is sortable.\n\n \n\nThe property can be used to override the default behavior.\n\n \n\n\n\n```ts file=\"$DOCS/reference/group-column-sorted-initially-example.page.tsx\"\n\n```\n\n\n\n\n\nWhen a group column is configured and the `groupBy` fields are not bound to actual columns in the table, the group column will not be sortable by default.\n\nIf you want to make it sortable, you have to specify a array, of the same length as the `groupBy` array, that specifies the sort type for each group field.\n\n\n\n## Aggregations\n\nWhen grouping, you can also aggregate the values of the grouped rows. This is done via the DataSource.aggregationReducers property. See the example below\n\n\n\n```ts file=\"grouping-with-aggregations-example.page.tsx\"\n\n```\n\n\n\nEach reducer from the `aggregationReducers` map can have the following properties:\n\n- `field` - the field to aggregate on\n- `getter(data)` - a value-getter function, if the aggregation values are are not mapped directly to a `field`\n- `initialValue` - the initial value to start with when computing the aggregation (for client-side aggregations only)\n- `reducer: string | (acc, current, data: DATA_TYPE, index)=>value` - the reducer function to use when computing the aggregation (for client-side aggregations only). For server-side aggregations, this will be a `string`\n- `done(value, arr)` - a function that is called when the aggregation is done (for client-side aggregations only) and returns the final value of the aggregation\n- `name` - useful especially in combination with , as it will be used as the pivot column header.\n\nIf an aggregation reducer is bound to a `field` in the dataset, and there is a column mapped to the same `field`, that column will show the corresponding aggregation value for each group row, as shown in the example above.\n\n\n\nIf you want to prevent the user to expand the last level of group rows, you can override the `render` function for the group column\n\n\n\n```ts file=\"grouping-with-aggregations-discard-expand-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\nDive deeper into the aggregation reducers and how they work.\n\n\n\n## Server side grouping with lazy loading\n\nLazy loading becomes all the more useful when working with grouped data.\n\nThe `DataSource` function is called with an object that has all the information about the current `DataSource` state(grouping/pivoting/sorting/lazy-loading, etc) - see the paragraphs above for details.\n\nServer side grouping needs two kinds of data responses in order to work properly:\n\n- response for **non-leaf row groups** - these are groups that have children. For such groups (including the top-level group), the `DataSource.data` function must return a promise that's resolved to an object with the following properties:\n - `totalCount` - the total number of records in the group\n - `data` - an array of objects that describes non-leaf child groups, each object has the following properties:\n - `keys` - an array of the group keys (usually strings) that uniquely identifies the group, from the root to the current group\n - `data` - an object that describes the common properties of the group\n - `aggregations` - an object that describes the aggregations for the current group\n- response for **leaf rows** - these are normal rows - rows that would have been served in the non-grouped response. The resolved object should have the following properties:\n - `data` - an array of objects that describes the rows\n - `totalCount` - the total number of records on the server, that are part of the current group\n\nHere's an example, that assumes grouping by `country` and `city` and aggregations by `age` and `salary` (average values):\n\n```tsx\n//request:\ngroupKeys: [] // empty keys array, so it's a top-level group\ngroupBy: [{\"field\":\"country\"},{\"field\":\"city\"}]\nreducers: [{\"field\":\"salary\",\"id\":\"avgSalary\",\"name\":\"avg\"},{\"field\":\"age\",\"id\":\"avgAge\",\"name\":\"avg\"}]\n// lazyLoadStartIndex: 0, - passed if lazyLoad is configured with a batchSize\n// lazyLoadBatchSize: 20 - passed if lazyLoad is configured with a batchSize\n\n//response\n{\n cache: true,\n totalCount: 20,\n data: [\n {\n data: {country: \"Argentina\"},\n aggregations: {avgSalary: 20000, avgAge: 30},\n keys: [\"Argentina\"],\n },\n {\n data: {country: \"Australia\"},\n aggregations: {avgSalary: 25000, avgAge: 35},\n keys: [\"Australia\"],\n }\n //...\n ]\n}\n```\n\nNow let's expand the first group and see how the request/response would look like:\n\n```tsx\n\n//request:\ngroupKeys: [\"Argentina\"]\ngroupBy: [{\"field\":\"country\"},{\"field\":\"city\"}]\nreducers: [{\"field\":\"salary\",\"id\":\"avgSalary\",\"name\":\"avg\"},{\"field\":\"age\",\"id\":\"avgAge\",\"name\":\"avg\"}]\n\n//response\n{\n totalCount: 4,\n data: [\n {\n data: {country: \"Argentina\", city: \"Buenos Aires\"},\n aggregations: {avgSalary: 20000, avgAge: 30},\n keys: [\"Argentina\", \"Buenos Aires\"],\n },\n {\n data: {country: \"Argentina\", city: \"Cordoba\"},\n aggregations: {avgSalary: 25000, avgAge: 35},\n keys: [\"Argentina\", \"Cordoba\"],\n },\n //...\n ]\n}\n```\n\nFinally, let's have a look at the leaf/normal rows and a request for them:\n\n```tsx\n\n//request\ngroupKeys: [\"Argentina\",\"Buenos Aires\"]\ngroupBy: [{\"field\":\"country\"},{\"field\":\"city\"}]\nreducers: [{\"field\":\"salary\",\"id\":\"avgSalary\",\"name\":\"avg\"},{\"field\":\"age\",\"id\":\"avgAge\",\"name\":\"avg\"}]\n\n//response\n{\n totalCount: 20,\n data: [\n {\n id: 34,\n country: \"Argentina\",\n city: \"Buenos Aires\",\n age: 30,\n salary: 20000,\n stack: \"full-stack\",\n firstName: \"John\",\n //...\n },\n {\n id: 35,\n country: \"Argentina\",\n city: \"Buenos Aires\",\n age: 35,\n salary: 25000,\n stack: \"backend\",\n firstName: \"Jane\",\n //...\n },\n //...\n ]\n}\n```\n\n\n\nWhen a row group is expanded, since `InfiniteTable` has the group `keys` from the previous response when the node was loaded, it will use the `keys` array and pass them to the `DataSource.data` function when requesting for the children of the respective group.\n\nYou know when to serve last-level rows, because in that case, the length of the `groupKeys` array will be equal to the length of the `groupBy` array.\n\n\n\n\n\n```ts file=\"server-side-grouping-with-lazy-load-example.page.tsx\"\n\n```\n\n\n\n## Eager loading for group row nodes\n\nWhen using lazy-loading together with batching, node data (without children) is loaded when a node (normal or grouped) comes into view. Only when a group node is expanded will its children be loaded. However, you can do this loading eagerly, by using the `dataset` property on the node you want to load.\n\n\n\nThis can be useful in combination with using `dataParams.groupRowsState` from the function - so your datasource can know which groups are expanded, and thus it can serve those groups already loaded with children.\n\n\n\n```tsx {18}\n//request:\ngroupKeys: [] // empty keys array, so it's a top-level group\ngroupBy: [{\"field\":\"country\"},{\"field\":\"city\"}]\nreducers: [{\"field\":\"salary\",\"id\":\"avgSalary\",\"name\":\"avg\"},{\"field\":\"age\",\"id\":\"avgAge\",\"name\":\"avg\"}]\n// lazyLoadStartIndex: 0, - passed if lazyLoad is configured with a batchSize\n// lazyLoadBatchSize: 20 - passed if lazyLoad is configured with a batchSize\n\n//response\n{\n cache: true,\n totalCount: 20,\n data: [\n {\n data: {country: \"Argentina\"},\n aggregations: {avgSalary: 20000, avgAge: 30},\n keys: [\"Argentina\"],\n // NOTE this dataset property used for eager-loading of group nodes\n dataset: {\n // the shape of the dataset is the same as the one normally returned by the datasource\n cache: true,\n totalCount: 4,\n data: [\n {\n data: {country: \"Argentina\", city: \"Buenos Aires\"},\n aggregations: {avgSalary: 20000, avgAge: 30},\n keys: [\"Argentina\", \"Buenos Aires\"],\n },\n {\n data: {country: \"Argentina\", city: \"Cordoba\"},\n aggregations: {avgSalary: 25000, avgAge: 35},\n keys: [\"Argentina\", \"Cordoba\"],\n },\n ]\n }\n },\n {\n data: {country: \"Australia\"},\n aggregations: {avgSalary: 25000, avgAge: 35},\n keys: [\"Australia\"],\n }\n //...\n ]\n}\n```\n" - }, - "/docs/learn/grouping-and-pivoting/": { - "filePath": "/docs/learn/grouping-and-pivoting/index", - "routePath": "/docs/learn/grouping-and-pivoting/", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/learn/grouping-and-pivoting/index.page.md", - "fileName": "index.page.md", - "folderPath": "/docs/learn/grouping-and-pivoting/", - "frontmatter": { - "title": "Grouping and Pivoting" - }, - "excerpt": "Infinite Table comes with grouping and pivoting capabilities built-in. The `DataSource` component does the actual data grouping and pivoting - while the `InfiniteTable` component does the specialized rendering.", - "readingTime": "1 min read", - "content": "\nInfinite Table comes with grouping and pivoting capabilities built-in. The `DataSource` component does the actual data grouping and pivoting - while the `InfiniteTable` component does the specialized rendering.\n\n\n\nLearn row grouping and explore the possibilities.\n\n\nRead thorough documentation covering pivoting and aggregation.\n\n\n\n\n\n```ts files=[\"row-grouping-example.page.tsx\",\"columns.ts\"]\n\n```\n\n\n" + "excerpt": "Our `TypeScript` types are published as part of the package, as named exports from the root of the package.", + "readingTime": "3 min read", + "content": "\nOur `TypeScript` types are published as part of the package, as named exports from the root of the package.\n\nThe 2 main components that you can need to use and import are:\n\n- `InfiniteTable`\n- `DataSource`\n\n```tsx title=\"Importing InfiniteTable and DataSource components\"\nimport { InfiniteTable, DataSource } from '@infinite-table/infinite-react';\n```\n\n\n\nIn our TypeScript typings, those components are exported as generic components, so they need to be bound to the type of the data they are rendering.\n\n```tsx\ntype Developer = {\n id: number;\n\n firstName: string;\n lastName: string;\n\n currency: string;\n salary: number;\n}\n\nconst App = () => {\n return data={data} primaryKey=\"id\">\n \n columns={{...}}\n />\n \n}\n```\n\nThroughout the documentation, we will use the `DATA_TYPE` placeholder to refer to the type of the data that the `InfiniteTable` and `DataSource` components are bound to.\n\n\n\n\n\nYou can still use `InfiniteTable` in plain JavaScript, but you won't get all the type-checking benefits.\n\n\n\nBoth `InfiniteTable` and `DataSource` components have types provided for most of the props they support. Generally the naming pattern is `Prop`, so here are a few examples to clarify the rule:\n\n```ts\nimport type {\n InfiniteTablePropColumns,\n // corresponding to the `columns` prop\n DataSourcePropGroupBy,\n // corresponding to the `groupBy` prop\n} from '@infinite-table/infinite-react';\n```\n\n## `DataSource` Types\n\nHere are a few examples for types for the `DataSource` component:\n\n- `DataSourcePropGroupBy` - the type for DataSource.groupBy\n\n```tsx\nimport type { DataSourcePropGroupBy } from '@infinite-table/infinite-react';\n```\n\n- `DataSourcePropAggregationReducers` - the type for DataSource.aggregationReducers\n\n```tsx\nimport type { DataSourcePropAggregationReducers } from '@infinite-table/infinite-react';\n```\n\n\n\nNot all the `DataSource` props have types exported that follow this convention, so you can always use `DataSourceProps` to get the type that define all the props.\n\nIn this way you can access specific prop types by name\n\n- `DataSourceProps['groupBy']` - the type for DataSource.groupBy\n- `DataSourceProps['data']` - the type for DataSource.data\n- etc\n\n\n\n## `InfiniteTable` Types\n\nBelow you can find a few examples for types for the `InfiniteTable` component:\n\n- `InfiniteTablePropColumns` - the type for InfiniteTable.columns\n\n```tsx\nimport type { InfiniteTablePropColumns } from '@infinite-table/infinite-react';\n```\n\n- `InfiniteTablePropRowStyle` - the type for InfiniteTable.rowStyle\n\n```tsx\nimport type { InfiniteTablePropRowStyle } from '@infinite-table/infinite-react';\n```\n\n- `InfiniteTablePropColumnGroups` - the type for InfiniteTable.columnGroups\n\n```tsx\nimport type { InfiniteTablePropColumnGroups } from '@infinite-table/infinite-react';\n```\n\n\n\nNot all the `InfiniteTable` props have types exported that follow this convention, so you can always use `InfiniteTableProps` to get the type that define all the props the `InfiniteTable` component supports.\n\nIn this way you can access specific prop types by name:\n\n- `InfiniteTableProps['columns']` - the type for InfiniteTable.columns\n- `InfiniteTableProps['columnSizing']` - the type for InfiniteTable.columnSizing\n- etc\n\n\n\n\n\nWorth mentioning is the `InfiniteTableColumn` prop, which defines the type for the table .\n\n\n" }, "/docs/learn/keyboard-navigation/keyboard-shortcuts": { "filePath": "/docs/learn/keyboard-navigation/keyboard-shortcuts", @@ -2560,44 +2550,45 @@ "readingTime": "5 min read", "content": "\nTo enable keyboard navigation for table rows, specify keyboardNavigation=\"row\" in your React Infinite Table component.\n\nWhen row navigation is enabled, clicking a row highlights it and the user can use the arrow keys to navigate the table rows.\n\n\n\n\n\nClick on the table and use the arrow keys to navigate the rows.\n\n\n\n```ts file=\"navigating-rows-initial-example.page.tsx\"\n\n```\n\n\n\n\n\n- Use `ArrowUp` and `ArrowDown` to navigate to the previous and next row.\n- Use `PageUp` and `PageDown` to navigate the rows vertically by pages (a page is considered equal to the visible row count).\n- Use `Home` and `End` to navigate vertically to the first and last row respectively\n\n\n\nOther possible values for the prop, besides `\"row\"`, are `\"cell\"` and `false`.\n\n## Using a default active row\n\nYou can also specify an initial active row, by using defaultActiveRowIndex=2. This tells the table that there should be a default active row, namely the one at index 2 (so the third row).\n\n\n\n\n\nThis example starts with row at index `2` already active.\n\n\n\n```ts file=\"navigating-rows-uncontrolled-example.page.tsx\"\n\n```\n\n\n\n## Listening to active row changes\n\nYou can easily listen to changes in the row navigation by using the callback.\n\n\n\nWhen you use controlled , make sure to use onActiveRowIndexChange to update the prop value, as otherwise the component will not update on navigation\n\n\n\n\n\n\n\nThis example starts with row at index `2` already active and uses onActiveRowIndexChange to update .\n\n\n\n```ts file=\"navigating-rows-controlled-example.page.tsx\"\n\n```\n\n\n\n## Toggling group rows\n\nWhen the DataSource is grouped, you can use the keyboard to collapse/expand group rows, by pressing the `Enter` key on the active row.\n\n\n\nSince you're in row navigation mode, you can also use\n\n- `โ†` to collapse a group row\n- `โ†’` to expand a group row\n\n\n\n\n\n\n\nPress the `Enter` key on the active group row to toggle it. `ArrowLeft` will collapse a group row and `ArrowRight` will expand a group row.\n\n\n\n```ts file=\"$DOCS/reference/keyboard-toggle-group-rows.page.tsx\"\n\n```\n\n\n\n## Selecting Rows with the Keyboard\n\nWhen is enabled (read more about it in the [row selection page](../selection/row-selection)), you can use the spacebar key to select a group row (or `shift` + spacebar to do multiple selection).\n\nBy default is enabled, so you can use the **spacebar** key to select multiple rows, when selectionMode=\"multi-row\". Using the spacebar key is equivalent to doing a mouse click, so expect the combination of **spacebar** + `cmd`/`ctrl`/`shift` modifier keys to behave just like clicking + the same modifier keys.\n\n\n\n\n\nUse spacebar + optional `cmd`/`ctrl`/`shift` modifier keys just like you would do clicking + the same modifier keys.\n\n\n\n```ts file=\"$DOCS/reference/default-selection-mode-multi-row-keyboard-toggle-example-row-navigation.page.tsx\"\n\n```\n\n\n\n\n\nFor selection all the rows in the table, you can use `cmd`/`ctrl` + `A` keyboard shortcut.\n\n\n\n\n\nKeyboard selection is also possible when there's a column configured with checkbox selection - [make sure you read more about it](../selection/row-selection#using-a-selection-checkbox).\n\n\n\n## Theming\n\nBy default, the style of the element that highlights the active row is the same style as that of the element that highlights the active cell.\n\nThe easiest is to override the style is via those three CSS variables:\n\n- `--infinite-active-cell-border-color--r` - the `red` component of the border color\n- `--infinite-active-cell-border-color--g` - the `green` component of the border color\n- `--infinite-active-cell-border-color--b` - the `blue` component of the border color\n\nThe initial values for those are `77`, `149` and`215` respectively, so the border color is `rgb(77, 149, 215)`.\n\nIn addition, the background color of the element that highlights the active row is set to the same color as the border color (computed based on the above `r`, `g` and `b` variables), but with an opacity of `0.25`, configured via the `--infinite-active-row-background-alpha` CSS variable.\n\nWhen the table is not focused, the opacity for the background color is set to `0.1`, which is the default value of the `--infinite-active-row-background-alpha--table-unfocused` CSS variable.\n\n\n \nTo summarize, use\n\n- `--infinite-active-cell-border-color--r`\n- `--infinite-active-cell-border-color--g`\n- `--infinite-active-cell-border-color--b`\n\nto control border and background color of the active row highlight element.\n\nNo, it's not a mistake that the element that highlights the active row is configured via the same CSS variables as the element that highlights the active cell. This is deliberate - so override CSS variables for cell, and those are propagated to the row highlight element.\n\n\n\nThere are other CSS variables as well, that give you fined-tuned control over both the border and background color for the active row, if you don't want to use the above three variables to propagate the same color across both border and background.\n\n- `--infinite-active-cell-background` - the background color. If you use this, you need to set opacity yourself. Applied for both cell and row.\n- `--infinite-active-row-background` - the background color. If you use this, you need to set opacity yourself. If this is specified, it takes precendence over `--infinite-active-cell-background`\n- `--infinite-active-cell-background` - the background color. If you use this, you need to set opacity yourself. Applied for both cell and row.\n- `--infinite-active-row-background` - the background color. If this is specified, it takes precedence over `--infinite-active-cell-background`\n- `--infinite-active-row-border` - border configuration (eg:`2px solid magenta`). If you use this, it will not be propagated to the background color.\n\nFor more details on the CSS variables, see the [CSS Variables documentation](../theming/css-variables##active-row-background).\n\n\n\n\n\nUse the color picker to configured the desired color for the active row highlight\n\n\n\n```ts file=\"navigating-rows-theming-example.page.tsx\"\n\n```\n\n\n" }, - "/docs/learn/rows/disabled-rows": { - "filePath": "/docs/learn/rows/disabled-rows", - "routePath": "/docs/learn/rows/disabled-rows", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/learn/rows/disabled-rows.page.md", - "fileName": "disabled-rows.page.md", - "folderPath": "/docs/learn/rows/", + "/docs/learn/grouping-and-pivoting/group-aggregations": { + "filePath": "/docs/learn/grouping-and-pivoting/group-aggregations", + "routePath": "/docs/learn/grouping-and-pivoting/group-aggregations", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/learn/grouping-and-pivoting/group-aggregations.page.md", + "fileName": "group-aggregations.page.md", + "folderPath": "/docs/learn/grouping-and-pivoting/", "frontmatter": { - "title": "Disabled Rows" + "title": "Aggregations", + "description": "Learn how to define & use aggregations on grouped rows in Infinite Table for React." }, - "excerpt": "Disabling rows allows you to have some rows that are not selectable, not clickable, not reacheable via keyboard navigation and other interactions.", - "readingTime": "2 min read", - "content": "\nDisabling rows allows you to have some rows that are not selectable, not clickable, not reacheable via keyboard navigation and other interactions.\n\nThe `DataSource` manages the disabled state of rows, via the (uncontrolled) prop and (controlled) prop.\n\n```tsx\n\n idProperty=\"id\"\n data={[]}\n defaultRowDisabledState={{\n enabledRows: true,\n disabledRows: ['id1', 'id4', 'id5']\n }}\n/>\n \n {/* ... */}\n />\n\n```\n\n\n\nIn addition to using the / props, you can also specify the function prop, which overrides those other props and ultimately determines whether a row is disabled or not.\n\n\n\n\n\n```tsx file=\"initialRowDisabledState-example.page.tsx\"\n```\n\n\n\n## Using disabled rows while rendering\n\nWhen rendering a cell, you have access to the row disabled state - the type has a `rowDisabled` property which is true if the row is disabled.\n\n\n\n\n This example uses custom rendering for the `firstName` column to render an emoji for disabled rows.\n\n\n```tsx file=\"custom-rendering-for-disabled-rows-example.page.tsx\"\n```\n\n\n\n## Using the API to enable/disable rows\n\nYou can use the `DataSourceApi` to enable or disable rows programmatically.\n\n\n\n```tsx\ndataSourceApi.setRowEnabled(rowId, enabled);\n\n```\n\n\n\n```tsx\ndataSourceApi.setRowEnabledAt(rowIndex, enabled);\n```\n\n\n\n\nUse the context menu on each row to toggle the disabled state of the respective row.\n\n\n```tsx file=\"using-api-to-disable-rows-example.page.tsx\"\n```\n\n" + "excerpt": "A natural next step when grouping data is **aggregating the grouped values**. We allow developers to define any number of aggregations and bind them to any column.", + "readingTime": "8 min read", + "content": "\nA natural next step when grouping data is **aggregating the grouped values**. We allow developers to define any number of aggregations and bind them to any column.\n\nThe aggregations are defined on the `` component and are easily available at render time. A client-side aggregation needs a reducer function that accumulates the values in the data array and computes the final result.\n\n\n\nThroughout the docs, we might refer to aggregations as reducers - which, more technically, they are, since they reduce an array of values (from a group) to a single value.\n\n\n\n## Client-Side Aggregations\n\nWhen using client-side aggregation, each aggregation can have the following:\n\n### An initial value\n\nThe `initialValue` is optional value to use as the initial (accumulator) value for the reducer function. You can think of aggregations as an \"enhanced\" version of [Array.reduce](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce), so initial value should sound familiar.\n\n\n\nThe `initialValue` can be a function - in this case it will be called to compute the initial value.\n\n\n\n### A reducer function\n\n`reducer` is the function to call for each value in the (grouped) data array. It is called with the following arguments:\n\n- `accumulator` - the value returned by the previous call to the reducer function, or the `initialValue` if this is the first call. You return the new accumulator value from this function.\n- `value` - the value of the current item in the data array. If the aggregation has a `field`, this is the value of that field in the current item. Otherwise, value is the result of calling the `reducer.getter(data)` function (if one exists) or null if no getter is defined.\n- `dataItem` - the current item in the data array.\n- `index` - the index of the current item in the data array.\n\n### A `field` property or a `getter` function\n\nFor simple use-cases of client-side aggregations, a `field` is the way to go. This defines the field property (from the DATA_TYPE) to which the aggregation is bound.\n\nFor more complex scenarios, the aggregation should have a `getter` function. If both a `field` and a `getter` are provided, the `getter` has higher priority and will be used.\n\nUse this `getter` function to compute the value the current item in the array brings to the aggregation.\n\n```tsx title=\"Aggregation_custom_getter_function\"\n// useful for retrieving nested values\n\ngetter: (dataItem: Developer) => data.salary.net;\n```\n\n\n\nFor using nested values inside aggregations, use the aggregation `getter` function.\n\n\n\n### A completion `done` function\n\nThe completion `done` function is optional - if specified, will be after iterating over all the values in the grouped data array. Can be used to change the final result of the aggregation. It is called with the following arguments:\n\n- `accumulator` - the value returned by the last call to the reducer function\n- `data` - the grouped data array.\n This is useful for computing averages, for example:\n\n```tsx title=\"Done function for avg reducer\"\ndone: (acc, data) => acc / data.length;\n```\n\n### Putting it all together\n\nLet's take a look at a simple example of aggregating two columns, one to display the avg and the other one should compute the sum of the salary column for grouped rows.\n\n```tsx title=\"Average Aggregation\"\nimport { DataSource, InfiniteTable } from '@infinite-table/infinite-react';\n\nconst sum = (a: number, b: number) => a + b;\n\nconst reducers = {\n avg: {\n initialValue: 0,\n field: 'age',\n reducer: sum,\n done: (acc, data) => Math.round(acc / data.length),\n },\n\n sumAgg: {\n initialValue: 0,\n field: 'salary',\n reducer: sum\n }\n}\n\nfunction App() {\n return \n aggregationReducers={reducers}\n >\n {...} />\n \n}\n```\n\nIn the above example, note that aggregations are an object where the keys of the object are used to identify the aggregation and the values are the aggregation configuration objects, as described above.\n\n\n\nAt run-time, you have access to the aggregation reducer results inside group rows - you can use the `rowInfo.reducerResults` object to access those values. For the example above, you change how group rows are rendered for a certain column and display the aggregation results in a custom way:\n\n```tsx {9} title=\"Custom_group_row_rendering_for_the_country_column\"\n\ncountry: {\n field: 'country',\n\n // define a custom renderGroupValue fn for the country column\n\n renderGroupValue: ({ rowInfo }) => {\n const { reducerResults = {} } = rowInfo;\n // note the keys in the reducerResults objects match the keys in the aggregationReducers object\n return `Avg age: ${reducerResults.avg}, total salary ${reducerResults.sumAgg}`;\n },\n},\n```\n\n\n\n\n\n```ts file=\"aggregations-simple-example.page.tsx\"\n\n```\n\n\n\n## Server-Side Aggregations\n\nServer-side aggregations are defined in the same way as client-side aggregations (except the `reducer` function is missing), but the aggregation values are computed by the server and returned as part of the data response.\n\nFor computing the grouping and aggregations on the server, the backend needs to know the grouping and aggregation configuration. As such, Infinite Table will call the DataSource data function with an object that contains all the required info:\n\n- `groupBy` - the array of grouping fields, as passed to the `` component.\n- `pivotBy` - the array of pivot fields, as passed to the `` component.\n- `aggregationReducers` - the value of the prop, as configured on the `` component.\n- `sortInfo` - the current sorting information for the data.\n\nFor the lazy-loading use-case, there are other useful properties you can use from the object passed into the `data` function:\n\n- `groupKeys: string[]` - the group keys for the current group - the `data` fn is generally called lazily when the user expands a group row. This info is useful for fetching the data for a specific group.\n- `lazyLoadStartIndex` - provided when batching is also enabled via the prop. This is the index of the first item in the current batch.\n- `lazyLoadBatchSize` - also used when batching is enabled. This is the number of items in the current batch.\n\nBesides the above information, if filtering is used, a `fiterValue` is also made available.\n\nIn order to showcase the server-side aggregations, let's build an example similar to the above one, but let's lazily load group data.\n\n```tsx {2} title=\"DataSourcewith lazyLoad enabled\"\n\n```\n\nAs soon a grouping and aggregations are no longer computed on the client, your `data` function needs to send those configurations on the backend, so it needs to get a bit more complicated:\n\n```tsx title=\"Data_function_sending_configurations_to_the_backend\"\nconst data = ({ groupBy, aggregationReducers, sortInfo, groupKeys }) => {\n // it's important to send the current group keys - for top level, this will be []\n const args: string[] = [`groupKeys=${JSON.stringify(groupKeys)}`];\n\n // turn the sorting info into an array\n if (sortInfo && !Array.isArray(sortInfo)) {\n sortInfo = [sortInfo];\n }\n\n if (sortInfo) {\n // the backend expects the sort info to be an array of field,dir pairs\n args.push(\n 'sortInfo=' +\n JSON.stringify(\n sortInfo.map((s) => ({\n field: s.field,\n dir: s.dir,\n })),\n ),\n );\n }\n\n if (groupBy) {\n // for grouping, send an array of objects with the `field` property\n args.push(\n 'groupBy=' + JSON.stringify(groupBy.map((p) => ({ field: p.field }))),\n );\n }\n\n if (aggregationReducers) {\n args.push(\n 'reducers=' +\n JSON.stringify(\n // by convention, we send an array of reducers, each with `field` `name`(= \"avg\") and `id`\n // it's up to you to decide what the backend needs\n Object.keys(aggregationReducers).map((key) => ({\n field: aggregationReducers[key].field,\n id: key,\n name: aggregationReducers[key].reducer,\n })),\n ),\n );\n }\n\n const url = BASE_URL + `/developers10k-sql?` + args.join('&');\n return fetch(url).then(r=>r.json())\n}\n\n\n```\n\nWhen fetching without grouping (or with local grouping and aggregations), the `` component expects a flat array of data items coming from the server.\n\nHowever, when the grouping is happening server-side, the `` component expects a response that has the following shape:\n\n- `data` - the root array with grouping and aggregation info. Each item in the array should have the following:\n - `keys` - an array of the keys for the current group - eg `['USA']` or `['USA', 'New York']`\n - `data` - an object with all the common values for the group - eg `{ country: 'USA' }` or `{ country: 'USA', city: 'New York' }`\n - `aggregations` - an object with the aggregation values for the group - eg `{ age: 30, salary: 120300 }`. The keys in this object should match the keys in the object.\n - `pivot` - pivoting information for the current group - more on that on the dedicated [Pivoting page](./pivoting/overview).\n\nWhen the user is expanding the last level, in order to see the leaf rows, the shape of the response is expected to be the same as when there is no grouping - namely an array of data items or an object where the `data` property is an array of data items.\n\nLet's put all of this into a working example.\n\n\n\n\n\nThis showcases grouping and aggregations on the server - both the `age` and `salary` columns have an AVG aggregation defined.\n\nGrouping is done by the `country`, `city` and `stack` columns.\n\n\n\n```tsx file=\"grouping-and-aggregations-with-lazy-load-example.page.tsx\"\n\n```\n\n\n\n\n\nWhen the user is doing a sort on the table, the `` is fetched from scratch, but the expanded/collapsed state is preserved, and all the required groups that need to be re-fetched are reloaded as needed (if they are not eagerly included in the served data).\n\n\n" }, - "/docs/learn/rows/styling-rows": { - "filePath": "/docs/learn/rows/styling-rows", - "routePath": "/docs/learn/rows/styling-rows", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/learn/rows/styling-rows.page.md", - "fileName": "styling-rows.page.md", - "folderPath": "/docs/learn/rows/", + "/docs/learn/grouping-and-pivoting/grouping-rows": { + "filePath": "/docs/learn/grouping-and-pivoting/grouping-rows", + "routePath": "/docs/learn/grouping-and-pivoting/grouping-rows", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/learn/grouping-and-pivoting/grouping-rows.page.md", + "fileName": "grouping-rows.page.md", + "folderPath": "/docs/learn/grouping-and-pivoting/", "frontmatter": { - "title": "Styling Rows" + "title": "Grouping rows" }, - "excerpt": "Rows can be styled by using the `rowStyle` and the `rowClassName` props", - "readingTime": "1 min read", - "content": "\nRows can be styled by using the `rowStyle` and the `rowClassName` props\n\n- the prop can be a style `object` or a `function` that returns a style `object` or `undefined`\n- the prop can be a `string` (the name of a CSS class) or a `function` that returns a `string` or `undefined`\n\n```tsx title=\"Defining-a-rowStyle-function\"\nconst rowStyle: InfiniteTablePropRowStyle = ({\n data,\n rowInfo,\n}: {\n data: Employee | null;\n rowInfo: InfiniteTableRowInfo;\n}) => {\n const salary = data ? data.salary : 0;\n\n if (salary > 150_000) {\n return { background: 'tomato' };\n }\n if (rowInfo.indexInAll % 10 === 0) {\n return { background: 'lightblue', color: 'black' };\n }\n};\n```\n\n\n\nThe function prop has the same signature as the function prop.\n\n\n\n## Row styling example\n\n\n\n```ts files=[\"$DOCS/reference/rowStyle-example.page.tsx\",\"$DOCS/reference/rowStyle-example-columns.ts\"]\n\n```\n\n\n\n\n\nIn the function, you can access the rowInfo object, which contains information about the current row. It's especially useful when you have grouping and aggregation, as it contains the aggregation values and other extra info.\n\n\n" + "excerpt": "You can use any `field` available in the `DataSource` to do the grouping - it can even be a `field` that is not a column.", + "readingTime": "15 min read", + "content": "\nYou can use any `field` available in the `DataSource` to do the grouping - it can even be a `field` that is not a column.\n\n\n\nWhen using TypeScript, both `DataSource` and `InfiniteTable` components are generic and need to be rendered/instantiated with a `DATA_TYPE` parameter. The fields in that `DATA_TYPE` can then be used for grouping.\n\n\n\n```tsx\ntype Person = {\n name: string;\n age: number;\n country: string;\n id: string;\n}\n\nconst groupBy = [{field: 'country'}]\n\n groupBy={groupBy}>\n />\n\n\n```\n\nIn the example above, we're grouping by `country`, which is a field available in the `Person` type. Specifying a field not defined in the `Person` type would be a type error.\n\nAdditionally, a `column` object can be used together with the `field` to define how the group column should be rendered.\n\n```tsx {4}\nconst groupBy = [\n {\n field: 'country',\n column: {\n // custom column configuration for group column\n width: 150,\n header: 'Country group',\n },\n },\n];\n```\n\nThe example below puts it all together.\n\nAlso see the groupBy API reference to find out more.\n\n\n\n```ts file=\"row-grouping-example.page.tsx\"\n\n```\n\n\n\nIn `groupBy.column` you can use any column property - so, for example, you can define a custom `renderValue` function to customize the rendering.\n\n```tsx {5}\nconst groupBy = [\n {\n field: 'country',\n column: {\n renderValue: ({ value }) => <>Country: {value},\n },\n },\n];\n```\n\n\n\nThe generated group column(s) - can be one for all groups or one for each group - will inherit the `style`/`className`/renderers from the columns corresponding to the group fields themselves (if those columns exist).\n\nAdditionally, there are other ways to override those inherited configurations, in order to configure the group columns:\n\n- use to specify how each grouping column should look for the respective field (in case of groupRenderStrateg=\"multi-column\")\n- use prop\n - can be used as an object - ideal for when you have simple requirements and when groupRenderStrateg=\"single-column\"\n - as a function that returns a column configuration - can be used like this in either single or multiple group render strategy\n\n\n\n## Controlling the collapse/expand state\n\nWhen you do grouping, by default, all row groups are expanded. Of course you have full control over this and you do this via the / props.\n\nIf you simply want to specify the initial expanded/collapsed state, you should use the prop.\n\n```tsx title=\"Specifying the default state for group rows\"\nconst defaultGroupRowsState: DataSourcePropGroupRowsStateObject = {\n collapsedRows: true,\n expandedRows: [['Mexico'], ['Mexico', 'backend'], ['India']],\n};\n```\n\nThe two properties in this object are `collapsedRows` and `expandedRows`, and each can have the following values:\n - `true` - meaning that all groups have this state\n - an array of arrays - representing the exceptions to the default value\n\n\nSo if you have `collapsedRows` set to `true` and then `expandedRows` set to `[['Mexico'], ['Mexico', 'backend'], ['India']]` then all rows are collapsed by default, except the rows specified in the `expandedRows`.\n\n\n\n\n```ts file=\"row-grouping-state-example.page.tsx\"\n\n```\n\n\n\n\n\nYou can specify expand/collapse state at any level of nesting.\n\nLet's suppose by default all rows are collapsed - if you want a node to be visible then you have to specify all its parents as expanded.\n\nSo having this\n```tsx \nconst defaultGroupRowsState = {\n collapsedRows: true,\n expandedRows: [['Mexico', 'backend']],\n};\n```\nwill show all rows as collapsed, and just as soon as you expand `Mexico` you will see the `backend` group row for Mexico to be expanded.\n\n\nThis data format gives you ultimate flexibility and allows you to easily restore an expand/collpase state at a later time, if you wanted to.\n\n\nIf you use the controlled , make sure you update it by leveraging the callback prop.\n\n\n\n\n\n```ts file=\"row-grouping-state-controlled-example.page.tsx\"\n\n```\n\n\n\nIn addition to simple objects with the shape described above, the / can also be instanges of `GroupRowsState` class, which is exported by the Infinite Table package. This class is simply a wrapper around those objects, but it gives you additional utility methods.\n\n\n\nThe callback gives you an instance of back as the single argument. If you're using plain objects, just do `groupRowsState.getState()` and you'll get the corresponding plain object for the current expand/collapse state.\n\n give you some additional helper methods, which you can read about here\n\n\n## Grouping strategies\n\nMultiple grouping strategies are supported by, `InfiniteTable` DataGrid:\n\n- multi column mode - multiple group columns are generated, one for each specified group field\n- single column mode - a single group column is generated, even when there are multiple group fields\n\nYou can specify the rendering strategy explicitly by setting the property to any of the following: `multi-column`, `single-column`. If you don't set it explicitly, it will choose the best default based on your configuration.\n\n### Multiple groups columns\n\nWhen grouping by multiple fields, by default the component will render a group column for each group field\n\n```tsx\nconst groupBy = [\n {\n field: 'age',\n column: {\n width: 100,\n renderValue: ({ value }) => <>Age: {value},\n },\n },\n {\n field: 'companyName',\n },\n {\n field: 'country',\n },\n];\n```\n\nLet's see an example of how the component would render the table with the multi-column strategy.\n\n\n\n```ts files=[\"row-grouping-multi-column-example.page.tsx\",\"columns.ts\"]\n\n```\n\n\n\nFor the `multi-column` strategy, you can use in order to hide columns for groups which are currently not visible.\n\n\n\n```ts files=[\"$DOCS/reference/hideEmptyGroupColumns-example.page.tsx\",\"$DOCS/reference/employee-columns.ts\"]\n\n```\n\n\n\n\n\nYou can specify an `id` for group columns. This is helpful if you want to size those columns (via ) or pin them (via ) or configure them in other ways. If no `id` is specified, it will be generated like this: `\"group-by-${field}\"`\n\n\n\n### Single group column\n\nYou can group by multiple fields, yet only render a single group column. To choose this rendering strategy, specify property to be `single-column` (or specify as an object.)\n\nIn this case, you can't override the group column for each group field, as there's only one group column being generated. However, you can specify a property to customize the generated column.\n\n\n\nBy default the generated group column will \"inherit\" many of the properties (the column style or className or renderers) of the columns corresponding to the group fields (if such columns exist, because it's not mandatory that they are defined).\n\n\n\n\n\n```ts files=[\"row-grouping-single-column-example.page.tsx\",\"columns.ts\"]\n\n```\n\n\n\n\n\nIf is specified to an object and no is passed, the render strategy will be `single-column`.\n\n can also be a function, which allows you to individually customize each group column - in case the `multi-column` strategy is used.\n\n\n\n\n\nYou can specify an `id` for the single . This is helpful if you want to size this column (via ) or pin it (via ) or configure it in other ways. If no `id` is specified, it will default to `\"group-by\"`.\n\n\n\n## Customizing the group column\n\nThere are many ways to customize the group column(s) and we're going to show a few of them below:\n\n### Binding the group column to a `field`\n\nBy default, group columns only show values in the group rows - but they are normal columns, so why not bind them to a field of the `DATA_TYPE`?\n\n```tsx {6,11}\nconst groupColumn = {\n id: 'the-group', // can specify an id\n style: {\n color: 'tomato',\n },\n field: 'firstName', // non-group rows will render the first name\n};\nconst columns = {\n theFirstName: {\n field: 'firstName',\n style: {\n // this style will also be applied in the group column,\n // since it is bound to this same `field`\n fontWeight: 'bold',\n },\n },\n};\n```\n\nThis makes the column display the value of the `field` in non-group/normal rows. Also, if you have another column bound to that `field`, the renderers/styling of that column will be used for the value of the group column, in non-group rows.\n\n\n\n```ts file=\"$DOCS/reference/bind-group-column-to-field-example.page.tsx\"\n\n```\n\n\n\n### Use `groupColumn` to customize rendering\n\nThe will inherit its own rendering and styling from the columns that are bound to the fields used in . However, you can override any of those properties so you have full control over the rendering process.\n\n```tsx {3,6}\nconst groupColumn = {\n field: 'firstName',\n renderGroupValue: ({ value }) => {\n return `Group: ${value}`;\n },\n renderLeafValue: ({ value }) => {\n return `First name: ${value}`;\n },\n};\n```\n\n\n\n\n\nThe column that renders the `firstName` has a custom renderer that adds a `.` at the end.\nThe group column is bound to the same `firstName` field, but specifies a different renderer, which will be used instead.\n\n\n\n```ts file=\"$DOCS/reference/group-column-custom-renderers-example.page.tsx\"\n\n```\n\n\n\n\n\nLearn more about customizing column rendering via multiple renderer functions.\n\n\n\n## Hiding columns when grouping\n\nWhen grouping is enabled, you can choose to hide some columns. Here are the two main ways to do this:\n\n- use - this will make columns bound to the group fields be hidden when grouping is active\n- use (also available on the column types, as ) - this is a column-level property, so you have more fine-grained control over what is hidden and when.\n\nValid values for are:\n\n- `\"*\"` - when any grouping is active, hide the column that specifies this property\n- `true` - when the field this column is bound to is used in grouping, hides this column\n- `keyof DATA_TYPE` - specify an exact field that, when grouped by, makes this column be hidden\n- `{[k in keyof DATA_TYPE]: true}` - an object that can specify more fields. When there is grouping by any of those fields, the current column gets hidden.\n\n\n\n\n\nIn this example, the column bound to `firstName` field is set to hide when any grouping is active, since the group column is anyways found to the `firstName` field.\n\nIn addition, is set to `true`, so the `stack` and `preferredLanguage` columns are also hidden, since they are grouped by.\n\n\n\n```ts file=\"$DOCS/reference/hide-columns-when-grouping-example.page.tsx\"\n\n```\n\n\n\n## Sorting the group column\n\nWhen groupRenderStrategy=\"single-column\" is used, the group column is sortable by default if all the columns that are involved in grouping are sortable. Sorting the group column makes the `sortInfo` have a value that looks like this:\n\n```ts\nconst sortInfo = [\n {\n dir: 1,\n id: 'group-by',\n field: ['stack', 'age'],\n type: ['string', 'number'],\n },\n];\n```\n\ngroupRenderStrategy=\"multi-column\", each group column is sortable by default if the column with the corresponding field is sortable.\n\n \n\nThe property can be used to override the default behavior.\n\n \n\n\n\n```ts file=\"$DOCS/reference/group-column-sorted-initially-example.page.tsx\"\n\n```\n\n\n\n\n\nWhen a group column is configured and the `groupBy` fields are not bound to actual columns in the table, the group column will not be sortable by default.\n\nIf you want to make it sortable, you have to specify a array, of the same length as the `groupBy` array, that specifies the sort type for each group field.\n\n\n\n## Aggregations\n\nWhen grouping, you can also aggregate the values of the grouped rows. This is done via the DataSource.aggregationReducers property. See the example below\n\n\n\n```ts file=\"grouping-with-aggregations-example.page.tsx\"\n\n```\n\n\n\nEach reducer from the `aggregationReducers` map can have the following properties:\n\n- `field` - the field to aggregate on\n- `getter(data)` - a value-getter function, if the aggregation values are are not mapped directly to a `field`\n- `initialValue` - the initial value to start with when computing the aggregation (for client-side aggregations only)\n- `reducer: string | (acc, current, data: DATA_TYPE, index)=>value` - the reducer function to use when computing the aggregation (for client-side aggregations only). For server-side aggregations, this will be a `string`\n- `done(value, arr)` - a function that is called when the aggregation is done (for client-side aggregations only) and returns the final value of the aggregation\n- `name` - useful especially in combination with , as it will be used as the pivot column header.\n\nIf an aggregation reducer is bound to a `field` in the dataset, and there is a column mapped to the same `field`, that column will show the corresponding aggregation value for each group row, as shown in the example above.\n\n\n\nIf you want to prevent the user to expand the last level of group rows, you can override the `render` function for the group column\n\n\n\n```ts file=\"grouping-with-aggregations-discard-expand-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\nDive deeper into the aggregation reducers and how they work.\n\n\n\n## Server side grouping with lazy loading\n\nLazy loading becomes all the more useful when working with grouped data.\n\nThe `DataSource` function is called with an object that has all the information about the current `DataSource` state(grouping/pivoting/sorting/lazy-loading, etc) - see the paragraphs above for details.\n\nServer side grouping needs two kinds of data responses in order to work properly:\n\n- response for **non-leaf row groups** - these are groups that have children. For such groups (including the top-level group), the `DataSource.data` function must return a promise that's resolved to an object with the following properties:\n - `totalCount` - the total number of records in the group\n - `data` - an array of objects that describes non-leaf child groups, each object has the following properties:\n - `keys` - an array of the group keys (usually strings) that uniquely identifies the group, from the root to the current group\n - `data` - an object that describes the common properties of the group\n - `aggregations` - an object that describes the aggregations for the current group\n- response for **leaf rows** - these are normal rows - rows that would have been served in the non-grouped response. The resolved object should have the following properties:\n - `data` - an array of objects that describes the rows\n - `totalCount` - the total number of records on the server, that are part of the current group\n\nHere's an example, that assumes grouping by `country` and `city` and aggregations by `age` and `salary` (average values):\n\n```tsx\n//request:\ngroupKeys: [] // empty keys array, so it's a top-level group\ngroupBy: [{\"field\":\"country\"},{\"field\":\"city\"}]\nreducers: [{\"field\":\"salary\",\"id\":\"avgSalary\",\"name\":\"avg\"},{\"field\":\"age\",\"id\":\"avgAge\",\"name\":\"avg\"}]\n// lazyLoadStartIndex: 0, - passed if lazyLoad is configured with a batchSize\n// lazyLoadBatchSize: 20 - passed if lazyLoad is configured with a batchSize\n\n//response\n{\n cache: true,\n totalCount: 20,\n data: [\n {\n data: {country: \"Argentina\"},\n aggregations: {avgSalary: 20000, avgAge: 30},\n keys: [\"Argentina\"],\n },\n {\n data: {country: \"Australia\"},\n aggregations: {avgSalary: 25000, avgAge: 35},\n keys: [\"Australia\"],\n }\n //...\n ]\n}\n```\n\nNow let's expand the first group and see how the request/response would look like:\n\n```tsx\n\n//request:\ngroupKeys: [\"Argentina\"]\ngroupBy: [{\"field\":\"country\"},{\"field\":\"city\"}]\nreducers: [{\"field\":\"salary\",\"id\":\"avgSalary\",\"name\":\"avg\"},{\"field\":\"age\",\"id\":\"avgAge\",\"name\":\"avg\"}]\n\n//response\n{\n totalCount: 4,\n data: [\n {\n data: {country: \"Argentina\", city: \"Buenos Aires\"},\n aggregations: {avgSalary: 20000, avgAge: 30},\n keys: [\"Argentina\", \"Buenos Aires\"],\n },\n {\n data: {country: \"Argentina\", city: \"Cordoba\"},\n aggregations: {avgSalary: 25000, avgAge: 35},\n keys: [\"Argentina\", \"Cordoba\"],\n },\n //...\n ]\n}\n```\n\nFinally, let's have a look at the leaf/normal rows and a request for them:\n\n```tsx\n\n//request\ngroupKeys: [\"Argentina\",\"Buenos Aires\"]\ngroupBy: [{\"field\":\"country\"},{\"field\":\"city\"}]\nreducers: [{\"field\":\"salary\",\"id\":\"avgSalary\",\"name\":\"avg\"},{\"field\":\"age\",\"id\":\"avgAge\",\"name\":\"avg\"}]\n\n//response\n{\n totalCount: 20,\n data: [\n {\n id: 34,\n country: \"Argentina\",\n city: \"Buenos Aires\",\n age: 30,\n salary: 20000,\n stack: \"full-stack\",\n firstName: \"John\",\n //...\n },\n {\n id: 35,\n country: \"Argentina\",\n city: \"Buenos Aires\",\n age: 35,\n salary: 25000,\n stack: \"backend\",\n firstName: \"Jane\",\n //...\n },\n //...\n ]\n}\n```\n\n\n\nWhen a row group is expanded, since `InfiniteTable` has the group `keys` from the previous response when the node was loaded, it will use the `keys` array and pass them to the `DataSource.data` function when requesting for the children of the respective group.\n\nYou know when to serve last-level rows, because in that case, the length of the `groupKeys` array will be equal to the length of the `groupBy` array.\n\n\n\n\n\n```ts file=\"server-side-grouping-with-lazy-load-example.page.tsx\"\n\n```\n\n\n\n## Eager loading for group row nodes\n\nWhen using lazy-loading together with batching, node data (without children) is loaded when a node (normal or grouped) comes into view. Only when a group node is expanded will its children be loaded. However, you can do this loading eagerly, by using the `dataset` property on the node you want to load.\n\n\n\nThis can be useful in combination with using `dataParams.groupRowsState` from the function - so your datasource can know which groups are expanded, and thus it can serve those groups already loaded with children.\n\n\n\n```tsx {18}\n//request:\ngroupKeys: [] // empty keys array, so it's a top-level group\ngroupBy: [{\"field\":\"country\"},{\"field\":\"city\"}]\nreducers: [{\"field\":\"salary\",\"id\":\"avgSalary\",\"name\":\"avg\"},{\"field\":\"age\",\"id\":\"avgAge\",\"name\":\"avg\"}]\n// lazyLoadStartIndex: 0, - passed if lazyLoad is configured with a batchSize\n// lazyLoadBatchSize: 20 - passed if lazyLoad is configured with a batchSize\n\n//response\n{\n cache: true,\n totalCount: 20,\n data: [\n {\n data: {country: \"Argentina\"},\n aggregations: {avgSalary: 20000, avgAge: 30},\n keys: [\"Argentina\"],\n // NOTE this dataset property used for eager-loading of group nodes\n dataset: {\n // the shape of the dataset is the same as the one normally returned by the datasource\n cache: true,\n totalCount: 4,\n data: [\n {\n data: {country: \"Argentina\", city: \"Buenos Aires\"},\n aggregations: {avgSalary: 20000, avgAge: 30},\n keys: [\"Argentina\", \"Buenos Aires\"],\n },\n {\n data: {country: \"Argentina\", city: \"Cordoba\"},\n aggregations: {avgSalary: 25000, avgAge: 35},\n keys: [\"Argentina\", \"Cordoba\"],\n },\n ]\n }\n },\n {\n data: {country: \"Australia\"},\n aggregations: {avgSalary: 25000, avgAge: 35},\n keys: [\"Australia\"],\n }\n //...\n ]\n}\n```\n" }, - "/docs/learn/rows/using-row-info": { - "filePath": "/docs/learn/rows/using-row-info", - "routePath": "/docs/learn/rows/using-row-info", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/learn/rows/using-row-info.page.md", - "fileName": "using-row-info.page.md", - "folderPath": "/docs/learn/rows/", + "/docs/learn/grouping-and-pivoting/": { + "filePath": "/docs/learn/grouping-and-pivoting/index", + "routePath": "/docs/learn/grouping-and-pivoting/", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/learn/grouping-and-pivoting/index.page.md", + "fileName": "index.page.md", + "folderPath": "/docs/learn/grouping-and-pivoting/", "frontmatter": { - "title": "Using Rows at Runtime" + "title": "Grouping and Pivoting" }, - "excerpt": "At runtime, the function and a lot of other functions use the rowInfo object to access the current row and use it to decide how to render the current cell or row.", - "readingTime": "7 min read", - "content": "\nAt runtime, the function and a lot of other functions use the rowInfo object to access the current row and use it to decide how to render the current cell or row.\n\nThe `rowInfo` object has a few variations, depending on the presence or absence of grouping. See type definition here.\n\n\n\nAll those variations are discriminated in the `TypeScript` typings, so you can easily use the different types of `rowInfo` objects.\n\n\n\n## Ungrouped Scenario - normal `rowInfo`\n\nWhen there is no grouping, the `rowInfo` object has the following properties:\n\n- `data` - type: `DATA_TYPE`\n- `dataSourceHasGrouping` - type: `false`\n- `isGroupRow` - type: `false`\n- `id` - type: `any`. The id of the row, as defined by the prop.\n- `selfLoaded` - type: `boolean`. Useful in lazy-loading scenarios, when there is batching present. If you're not in such a scenario, the value will be `false`. You can use this to show a loading indicator for the row.\n- `indexInAll` - type `number`. The index of the row in the full dataset. Called like this because for grouping scenarios, there's also an `indexInGroup`\n\n### Discriminator\n\n```ts\nrowInfo.dataSourceHasGrouping === false;\n```\n\n## Grouped scenario - normal `rowInfo`\n\nWhen there is grouping defined, and the row is not a group row, the `rowInfo` object has the following properties:\n\n- `data` - type: `DATA_TYPE`\n- `dataSourceHasGrouping` - type: `true`\n- `isGroupRow` - type: `false`\n- `indexInAll` - like the above\n- `indexInGroup` - type: `number`. The index of the row in its parent group.\n- `groupKeys` - type: `any[]`, but usually it's actually `string[]`. For normal rows, the group keys will have all the keys starting from the topmost parent down to the last group row in the hierarchy (the direct parent of the current row).\n\n```txt\nExample: People grouped by country and city\n\n> Italy - country - groupKeys: ['Italy']\n > Rome - city - groupKeys: ['Italy', 'Rome']\n - Marco - person - groupKeys: ['Italy', 'Rome']\n - Luca - person - groupKeys: ['Italy', 'Rome']\n - Giuseppe - person - groupKeys: ['Italy', 'Rome']\n```\n\n- `groupBy` - type `(keyof T)[]`. Has the same structure as groupKeys, but it will contain the fields used to group the rows.\n- `rootGroupBy` - type `(keyof T)[]`. The groupBy value of the DataSource component, mapped to the `groupBy.field`\n- `parents` - a list of `rowInfo` objects that are the parents of the current row.\n- `indexInParentGroups[]` - type: `number[]`. See below for an example\n\n```\n> Italy - country - indexInParentGroups: [0]\n > Rome - city - indexInParentGroups: [0,0]\n - Marco - person - indexInParentGroups: [0,0,0]\n - Luca - person - indexInParentGroups: [0,0,1]\n - Giuseppe - person - indexInParentGroups: [0,0,2]\n> USA - country - indexInParentGroups: [1]\n > LA - city - indexInParentGroups: [1,0]\n - Bob - person - indexInParentGroups: [1,0,2]\n```\n\n- `groupCount` - type: `number`. The count of leaf rows that the current group (in this case, the parent group) contains\n- `groupNesting` - type `number`. The nesting of the parent group.\n- `collapsed` - type `boolean`.\n- `selfLoaded` - type: `boolean`. Useful in lazy-loading scenarios, when there is batching present. If you're not in such a scenario, the value will be `false`.\n\n### Discriminator\n\n```ts\nrowInfo.dataSourceHasGrouping === true && rowInfo.isGroupRow === false;\n```\n\n## Grouped scenario - group `rowInfo`\n\nWhen there is grouping defined, and the row is a group row, the `rowInfo` object has the following properties:\n\n- `data` - type: `Partial | null`. The `data` object that might be available is the result of the aggregation reducers. If none are specified, `data` will be `null`\n- `dataSourceHasGrouping` - type: `true`\n- `isGroupRow` - type: `true`\n- `error` - type: `string?`. If there was an error while loading the group (when the group row is expanded), this will contain the error message. If the group row was loaded with the `cache: true` flag sent in the server response, the error will remain on the `rowInfo` object even when you collapse the group row, otherwise, if `cache: true` was not present, the `error` property will be removed on collapse.\n- `indexInAll` - like the above\n- `indexInGroup` - type: `number`. The index of the row in the its parent group.\n- `deepRowInfoArray` - an array of `rowInfo` objects. This array contains all the (uncollapsed, so visible) row infos under this group, at any level of nesting, in the order in which they are visible in the table.\n- `reducerResults` - type `Record`. The result of the aggregation reducers for each field in the prop.\n- `groupCount` - type: `number`. The count of leaf rows that the current group (in this case, the parent group) contains\n- `groupData` - type: `DATA_TYPE[]`. The array of the data of all leaf nodes (normal nodes) that are inside this group.\n\n```txt\nExample: People grouped by country and city\n\n> Italy - country - groupKeys: ['Italy']\n > Rome - city - groupKeys: ['Italy', 'Rome']\n - Marco - person - groupKeys: ['Italy', 'Rome']\n - Luca - person - groupKeys: ['Italy', 'Rome']\n - Giuseppe - person - groupKeys: ['Italy', 'Rome']\n```\n\n- `collapsedChildrenCount` - type: `number`. The count of all leaf nodes (normal rows) inside the group that are not being visible due to collapsing (either the current row is collapsed or any of its children)\n- `directChildrenCount` - type: `number`. The count of the direct children of the current group. Direct children can be either normal rows or groups.\n- `directChildrenLoadedCount` - type: `number`. Like `directChildrenCount`, but only counts the rows that are loaded (when batched lazy loading is configured).\n- `childrenAvailable` - type: `boolean`. For lazy/batched grouping, this is true if the group has been expanded at least once. NOTE: if this is true, it doesn't mean that all the children have been loaded, it only means that at least some children have been loaded and are available. Use `directChildrenCount` and `directChildrenLoadedCount` to know if all the children have been loaded or not.\n- `childrenLoading` - type: `boolean`. Boolean flag that will be true while lazy loading direct children of the current row group. Use `directChildrenLoadedCount` and `directChildrenCount` to know if all the children have been loaded or not.\n- `childrenSelectedCount` the number of all leaf rows in the current group that are selected.\n- `groupKeys` - type: `any[]`, but usually it's actually `string[]`. For group rows, the group keys will have all the keys starting from the topmost parent down to the current group row (key for current group row is included).\n- `groupBy` - type `(keyof T)[]`. Has the same structure as groupKeys, but it will contain the fields used to group the rows.\n- `rootGroupBy` - type `(keyof T)[]`. The groupBy value of the DataSource component, mapped to the `groupBy.field`\n- `groupCount` - type: `number`. The count of leaf rows that the current group (in this case, the parent group) contains\n- `groupNesting` - type `number`. The nesting of the parent group.\n- `parents` - a list of `rowInfo` objects that are the parents of the current row.\n- `indexInParentGroups[]` - type: `number[]`. See below for an example\n\n```\n> Italy - country - indexInParentGroups: [0]\n > Rome - city - indexInParentGroups: [0,0]\n - Marco - person - indexInParentGroups: [0,0,0]\n - Luca - person - indexInParentGroups: [0,0,1]\n - Giuseppe - person - indexInParentGroups: [0,0,2]\n> USA - country - indexInParentGroups: [1]\n > LA - city - indexInParentGroups: [1,0]\n - Bob - person - indexInParentGroups: [1,0,2]\n```\n\n- `collapsed` - type `boolean`.\n- `selfLoaded` - type: `boolean`. Useful in lazy-loading scenarios, when there is batching present. If you're not in such a scenario, the value will be `false`.\n\n### Discriminator\n\n```ts\nrowInfo.dataSourceHasGrouping === true && rowInfo.isGroupRow === true;\n```\n" + "excerpt": "Infinite Table comes with grouping and pivoting capabilities built-in. The `DataSource` component does the actual data grouping and pivoting - while the `InfiniteTable` component does the specialized rendering.", + "readingTime": "1 min read", + "content": "\nInfinite Table comes with grouping and pivoting capabilities built-in. The `DataSource` component does the actual data grouping and pivoting - while the `InfiniteTable` component does the specialized rendering.\n\n\n\nLearn row grouping and explore the possibilities.\n\n\nRead thorough documentation covering pivoting and aggregation.\n\n\n\n\n\n```ts files=[\"row-grouping-example.page.tsx\",\"columns.ts\"]\n\n```\n\n\n" }, "/docs/learn/master-detail/caching-detail-datagrid": { "filePath": "/docs/learn/master-detail/caching-detail-datagrid", @@ -2655,6 +2646,45 @@ "readingTime": "4 min read", "content": "\nThe React DataGrid that Infinite Table offers has native support for master-detail rows.\n\n\n\nThe single most important property for the master-detail DataGrid configuration is the function prop - which makes the DataGrid be considered master-detail.\n\nIn addition, make sure you have a column with the `renderRowDetailIcon: true` flag set. on a column makes the column display the row detail expand icon.\n\nThe row detail in the DataGrid can contain another DataGrid or any other custom content.\n\n\n\n\nIt's very imporant that the function prop you pass into `` is stable and doesn't change on every render. So make sure you pass a reference to the same function every time - except of course if you want the row detail to change based on some other state.\n\n\n\n\n\n\nThis example shows a master DataGrid with cities & countries.\n\nThe details for each city shows a DataGrid with developers in that city.\n\nThe detail DataGrid is configured with remote sorting.\n\n\n\n```ts file=\"master-detail-example.page.tsx\"\n\n```\n\n\n\n\nIf you want to use a component instead of the function, you can use the property. This works similarly and makes the DataGrid be considered master-detail. Inside the component, you can use the hook to get the master row information.\n\n\n\n## Loading the Detail DataSource\n\nWhen master-detail is configured and the row detail renders a DataGrid, the function for the detail `` will be called with the `masterRowInfo` as a property available in the object passed as argument.\n\n```tsx title=\"Loading the detail DataGrid data\" {2}\nconst detailDataFn: DataSourceData = ({\n masterRowInfo,\n sortInfo,\n ...\n}) => {\n\n return Promise.resolve([...])\n}\n\n data={detailDataFn}>\n {...}\n\n```\n\nYou can see the live example above for more details.\n\n## Rendering a detail DataGrid\n\nUsing the prop, you can render any custom content for the row details.\n\nThe content doesn't need to include Infinite Table.\n\nYou can, however, render an Infinite Table React DataGrid, at any level of nesting inside the row detail content.\n\n\n\n\n\nIn this example, the row detail contains custom content, along with another Infinite Table DataGrid. You can nest a child DataGrid inside the row details at any level of nesting.\n\n\n\n```ts file=\"master-detail-custom-datagrid-example.page.tsx\"\n\n```\n\n\n\n## Configuring the master-detail height\n\nIn order to configure the height of the row details, you can use the prop.\n\n```tsx title=\"Configuring the row detail height\" {3}\n\n columns={masterColumns}\n rowDetailHeight={500}\n rowDetailRenderer={renderDetail}\n/>\n```\n\nThe default value for the is `300` px.\n\n can be one of the following:\n\n- `number` - the height in pixels\n- `string` - the name of a CSS variable that configures the height - eg: `--master-detail-height`\n- `(rowInfo) => number` - a function that can return a different height for each row. The sole argument is the rowInfo object.\n\n\n\n\n\nThis master-detail DataGrid is configured with a custom of `200px`.\n\n\n\n```ts file=\"master-detail-custom-detail-height-example.page.tsx\"\n\n```\n\n\n\n\n## Conditional row details\n\nNot all rows in a DataGrid need to have details. To configure which rows have details, you can use the function prop.\n\n```tsx title=\"Using conditional row details\" {5}\n\n columns={masterColumns}\n rowDetailHeight={500}\n rowDetailRenderer={renderDetail}\n isRowDetailEnabled={(rowInfo) => rowInfo.data.cityName.contains('i')}\n/>\n```\n\nThe function prop is called with the rowInfo object and is expected to return a boolean value.\n\n\n\n\n\n\nThis example shows a master DataGrid with cities & countries.\n\nNot all rows have details - every other row is configured without details via the function prop.\n\n\n\n```ts file=\"master-detail-per-row-example.page.tsx\"\n\n```\n\n\n" }, + "/docs/learn/rows/disabled-rows": { + "filePath": "/docs/learn/rows/disabled-rows", + "routePath": "/docs/learn/rows/disabled-rows", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/learn/rows/disabled-rows.page.md", + "fileName": "disabled-rows.page.md", + "folderPath": "/docs/learn/rows/", + "frontmatter": { + "title": "Disabled Rows" + }, + "excerpt": "Disabling rows allows you to have some rows that are not selectable, not clickable, not reacheable via keyboard navigation and other interactions.", + "readingTime": "2 min read", + "content": "\nDisabling rows allows you to have some rows that are not selectable, not clickable, not reacheable via keyboard navigation and other interactions.\n\nThe `DataSource` manages the disabled state of rows, via the (uncontrolled) prop and (controlled) prop.\n\n```tsx\n\n idProperty=\"id\"\n data={[]}\n defaultRowDisabledState={{\n enabledRows: true,\n disabledRows: ['id1', 'id4', 'id5']\n }}\n/>\n \n {/* ... */}\n />\n\n```\n\n\n\nIn addition to using the / props, you can also specify the function prop, which overrides those other props and ultimately determines whether a row is disabled or not.\n\n\n\n\n\n```tsx file=\"initialRowDisabledState-example.page.tsx\"\n```\n\n\n\n## Using disabled rows while rendering\n\nWhen rendering a cell, you have access to the row disabled state - the type has a `rowDisabled` property which is true if the row is disabled.\n\n\n\n\n This example uses custom rendering for the `firstName` column to render an emoji for disabled rows.\n\n\n```tsx file=\"custom-rendering-for-disabled-rows-example.page.tsx\"\n```\n\n\n\n## Using the API to enable/disable rows\n\nYou can use the `DataSourceApi` to enable or disable rows programmatically.\n\n\n\n```tsx\ndataSourceApi.setRowEnabled(rowId, enabled);\n\n```\n\n\n\n```tsx\ndataSourceApi.setRowEnabledAt(rowIndex, enabled);\n```\n\n\n\n\nUse the context menu on each row to toggle the disabled state of the respective row.\n\n\n```tsx file=\"using-api-to-disable-rows-example.page.tsx\"\n```\n\n" + }, + "/docs/learn/rows/styling-rows": { + "filePath": "/docs/learn/rows/styling-rows", + "routePath": "/docs/learn/rows/styling-rows", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/learn/rows/styling-rows.page.md", + "fileName": "styling-rows.page.md", + "folderPath": "/docs/learn/rows/", + "frontmatter": { + "title": "Styling Rows" + }, + "excerpt": "Rows can be styled by using the `rowStyle` and the `rowClassName` props", + "readingTime": "1 min read", + "content": "\nRows can be styled by using the `rowStyle` and the `rowClassName` props\n\n- the prop can be a style `object` or a `function` that returns a style `object` or `undefined`\n- the prop can be a `string` (the name of a CSS class) or a `function` that returns a `string` or `undefined`\n\n```tsx title=\"Defining-a-rowStyle-function\"\nconst rowStyle: InfiniteTablePropRowStyle = ({\n data,\n rowInfo,\n}: {\n data: Employee | null;\n rowInfo: InfiniteTableRowInfo;\n}) => {\n const salary = data ? data.salary : 0;\n\n if (salary > 150_000) {\n return { background: 'tomato' };\n }\n if (rowInfo.indexInAll % 10 === 0) {\n return { background: 'lightblue', color: 'black' };\n }\n};\n```\n\n\n\nThe function prop has the same signature as the function prop.\n\n\n\n## Row styling example\n\n\n\n```ts files=[\"$DOCS/reference/rowStyle-example.page.tsx\",\"$DOCS/reference/rowStyle-example-columns.ts\"]\n\n```\n\n\n\n\n\nIn the function, you can access the rowInfo object, which contains information about the current row. It's especially useful when you have grouping and aggregation, as it contains the aggregation values and other extra info.\n\n\n" + }, + "/docs/learn/rows/using-row-info": { + "filePath": "/docs/learn/rows/using-row-info", + "routePath": "/docs/learn/rows/using-row-info", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/learn/rows/using-row-info.page.md", + "fileName": "using-row-info.page.md", + "folderPath": "/docs/learn/rows/", + "frontmatter": { + "title": "Using Rows at Runtime" + }, + "excerpt": "At runtime, the function and a lot of other functions use the rowInfo object to access the current row and use it to decide how to render the current cell or row.", + "readingTime": "7 min read", + "content": "\nAt runtime, the function and a lot of other functions use the rowInfo object to access the current row and use it to decide how to render the current cell or row.\n\nThe `rowInfo` object has a few variations, depending on the presence or absence of grouping. See type definition here.\n\n\n\nAll those variations are discriminated in the `TypeScript` typings, so you can easily use the different types of `rowInfo` objects.\n\n\n\n## Ungrouped Scenario - normal `rowInfo`\n\nWhen there is no grouping, the `rowInfo` object has the following properties:\n\n- `data` - type: `DATA_TYPE`\n- `dataSourceHasGrouping` - type: `false`\n- `isGroupRow` - type: `false`\n- `id` - type: `any`. The id of the row, as defined by the prop.\n- `selfLoaded` - type: `boolean`. Useful in lazy-loading scenarios, when there is batching present. If you're not in such a scenario, the value will be `false`. You can use this to show a loading indicator for the row.\n- `indexInAll` - type `number`. The index of the row in the full dataset. Called like this because for grouping scenarios, there's also an `indexInGroup`\n\n### Discriminator\n\n```ts\nrowInfo.dataSourceHasGrouping === false;\n```\n\n## Grouped scenario - normal `rowInfo`\n\nWhen there is grouping defined, and the row is not a group row, the `rowInfo` object has the following properties:\n\n- `data` - type: `DATA_TYPE`\n- `dataSourceHasGrouping` - type: `true`\n- `isGroupRow` - type: `false`\n- `indexInAll` - like the above\n- `indexInGroup` - type: `number`. The index of the row in its parent group.\n- `groupKeys` - type: `any[]`, but usually it's actually `string[]`. For normal rows, the group keys will have all the keys starting from the topmost parent down to the last group row in the hierarchy (the direct parent of the current row).\n\n```txt\nExample: People grouped by country and city\n\n> Italy - country - groupKeys: ['Italy']\n > Rome - city - groupKeys: ['Italy', 'Rome']\n - Marco - person - groupKeys: ['Italy', 'Rome']\n - Luca - person - groupKeys: ['Italy', 'Rome']\n - Giuseppe - person - groupKeys: ['Italy', 'Rome']\n```\n\n- `groupBy` - type `(keyof T)[]`. Has the same structure as groupKeys, but it will contain the fields used to group the rows.\n- `rootGroupBy` - type `(keyof T)[]`. The groupBy value of the DataSource component, mapped to the `groupBy.field`\n- `parents` - a list of `rowInfo` objects that are the parents of the current row.\n- `indexInParentGroups[]` - type: `number[]`. See below for an example\n\n```\n> Italy - country - indexInParentGroups: [0]\n > Rome - city - indexInParentGroups: [0,0]\n - Marco - person - indexInParentGroups: [0,0,0]\n - Luca - person - indexInParentGroups: [0,0,1]\n - Giuseppe - person - indexInParentGroups: [0,0,2]\n> USA - country - indexInParentGroups: [1]\n > LA - city - indexInParentGroups: [1,0]\n - Bob - person - indexInParentGroups: [1,0,2]\n```\n\n- `groupCount` - type: `number`. The count of leaf rows that the current group (in this case, the parent group) contains\n- `groupNesting` - type `number`. The nesting of the parent group.\n- `collapsed` - type `boolean`.\n- `selfLoaded` - type: `boolean`. Useful in lazy-loading scenarios, when there is batching present. If you're not in such a scenario, the value will be `false`.\n\n### Discriminator\n\n```ts\nrowInfo.dataSourceHasGrouping === true && rowInfo.isGroupRow === false;\n```\n\n## Grouped scenario - group `rowInfo`\n\nWhen there is grouping defined, and the row is a group row, the `rowInfo` object has the following properties:\n\n- `data` - type: `Partial | null`. The `data` object that might be available is the result of the aggregation reducers. If none are specified, `data` will be `null`\n- `dataSourceHasGrouping` - type: `true`\n- `isGroupRow` - type: `true`\n- `error` - type: `string?`. If there was an error while loading the group (when the group row is expanded), this will contain the error message. If the group row was loaded with the `cache: true` flag sent in the server response, the error will remain on the `rowInfo` object even when you collapse the group row, otherwise, if `cache: true` was not present, the `error` property will be removed on collapse.\n- `indexInAll` - like the above\n- `indexInGroup` - type: `number`. The index of the row in the its parent group.\n- `deepRowInfoArray` - an array of `rowInfo` objects. This array contains all the (uncollapsed, so visible) row infos under this group, at any level of nesting, in the order in which they are visible in the table.\n- `reducerResults` - type `Record`. The result of the aggregation reducers for each field in the prop.\n- `groupCount` - type: `number`. The count of leaf rows that the current group (in this case, the parent group) contains\n- `groupData` - type: `DATA_TYPE[]`. The array of the data of all leaf nodes (normal nodes) that are inside this group.\n\n```txt\nExample: People grouped by country and city\n\n> Italy - country - groupKeys: ['Italy']\n > Rome - city - groupKeys: ['Italy', 'Rome']\n - Marco - person - groupKeys: ['Italy', 'Rome']\n - Luca - person - groupKeys: ['Italy', 'Rome']\n - Giuseppe - person - groupKeys: ['Italy', 'Rome']\n```\n\n- `collapsedChildrenCount` - type: `number`. The count of all leaf nodes (normal rows) inside the group that are not being visible due to collapsing (either the current row is collapsed or any of its children)\n- `directChildrenCount` - type: `number`. The count of the direct children of the current group. Direct children can be either normal rows or groups.\n- `directChildrenLoadedCount` - type: `number`. Like `directChildrenCount`, but only counts the rows that are loaded (when batched lazy loading is configured).\n- `childrenAvailable` - type: `boolean`. For lazy/batched grouping, this is true if the group has been expanded at least once. NOTE: if this is true, it doesn't mean that all the children have been loaded, it only means that at least some children have been loaded and are available. Use `directChildrenCount` and `directChildrenLoadedCount` to know if all the children have been loaded or not.\n- `childrenLoading` - type: `boolean`. Boolean flag that will be true while lazy loading direct children of the current row group. Use `directChildrenLoadedCount` and `directChildrenCount` to know if all the children have been loaded or not.\n- `childrenSelectedCount` the number of all leaf rows in the current group that are selected.\n- `groupKeys` - type: `any[]`, but usually it's actually `string[]`. For group rows, the group keys will have all the keys starting from the topmost parent down to the current group row (key for current group row is included).\n- `groupBy` - type `(keyof T)[]`. Has the same structure as groupKeys, but it will contain the fields used to group the rows.\n- `rootGroupBy` - type `(keyof T)[]`. The groupBy value of the DataSource component, mapped to the `groupBy.field`\n- `groupCount` - type: `number`. The count of leaf rows that the current group (in this case, the parent group) contains\n- `groupNesting` - type `number`. The nesting of the parent group.\n- `parents` - a list of `rowInfo` objects that are the parents of the current row.\n- `indexInParentGroups[]` - type: `number[]`. See below for an example\n\n```\n> Italy - country - indexInParentGroups: [0]\n > Rome - city - indexInParentGroups: [0,0]\n - Marco - person - indexInParentGroups: [0,0,0]\n - Luca - person - indexInParentGroups: [0,0,1]\n - Giuseppe - person - indexInParentGroups: [0,0,2]\n> USA - country - indexInParentGroups: [1]\n > LA - city - indexInParentGroups: [1,0]\n - Bob - person - indexInParentGroups: [1,0,2]\n```\n\n- `collapsed` - type `boolean`.\n- `selfLoaded` - type: `boolean`. Useful in lazy-loading scenarios, when there is batching present. If you're not in such a scenario, the value will be `false`.\n\n### Discriminator\n\n```ts\nrowInfo.dataSourceHasGrouping === true && rowInfo.isGroupRow === true;\n```\n" + }, "/docs/learn/selection/cell-selection": { "filePath": "/docs/learn/selection/cell-selection", "routePath": "/docs/learn/selection/cell-selection", @@ -2683,6 +2713,34 @@ "readingTime": "11 min read", "content": "\n`InfiniteTable` offers support for both single and multiple row selection. For selecting cells, see the [Cell Selection](/docs/learn/selection/cell-selection) page.\n\n```tsx title=\"Configure the selection mode on the DataSource component\"\n\n\n// can be \"single-row\", \"multi-row\", \"multi-cell\" or false\n```\n\nMultiple row selection allows people to select rows just like they would in their MacOS Finder app, by clicking desired rows and using the cmd/shift keys as modifiers.\n\nThe DataGrid also offers support for **checkbox selection**, which is another easy way of interacting with grid rows, especially when grouped or nested data is used.\n\nRow selection (both single and multiple) is driven by the prop, which will contain **primary keys of the selected rows**.\n\n\n\nThe value or values you specify for row selection are primary keys of the rows in the DataGrid.\n\nRow selection is defined on the `DataSource` component, so that's where you specify your prop (or the uncontrolled version of it, namely and also the callback prop of ).\n\n\n\n\n\nYou can explicitly specify the as `\"single-row\"` or `\"multi-row\"` (or `false`) but it will generally be derived from the value of your / prop.\n\n\n\n# Single Row Selection\n\nThis is the most basic row selection - in this case the prop (or the uncontrolled variant ) will be the primary key of the selected row (a string or a number or `null` for no selection).\n\n```ts {4}\n\n primaryKey=\"id\"\n data={[...]}\n defaultRowSelection={4}\n>\n \n\n\n```\n\n\n\n\n\nSingle row selection example - click a row to see selection change. You can also use your keyboard - press the spacebar to select/deselect a row.\n\n\n\n```ts file=\"$DOCS/reference/default-single-row-selection-example.page.tsx\"\n\n```\n\n\n\nRow selection is changed when the user clicks a row. Clicking a row selects it and clicking it again keeps the row selected. For deselecting the row with the mouse use `cmd`/`ctrl` + click.\n\n## Keybord support\n\nYou can also use your keyboard to select a row, as by default, is `true`. Using your keyboard, navigate to the desired row and then press the spacebar to select it. Pressing the spacebar again on the selected row will deselect it.\n\n\n\nBoth `cell` and `row` are available and you can use either of them to perform row selection.\n\n\n\n## Controlled single row selection\n\nRow selection can be used as a controlled or uncontrolled property. For the controlled version, make sure you also define your callback prop to update the selection.\n\n\n\n\n\nThis example uses callback prop to update the controlled \n\n\n\n```ts file=\"$DOCS/reference/controlled-single-row-selection-example.page.tsx\"\n\n```\n\n\n\n# Multi Row Selection\n\nYou can configure multiple selection for rows so users can interact with it through clicking around or via a checkbox selection column.\n\n## Using your mouse and keyboard to select rows\n\nIf you're using checkboxes for selection, users will be selecting rows via click or click + `cmd`/`ctrl` and `shift` keys, just like they are used to in their native Finder/Explorer applications.\n\n### Mouse interactions\n\nFor selecting with the mouse, the following gestures are supported (we tried to exactly replicate the logic/behaviour from MacOS Finder app, so most people should find it really intuitive):\n\n- clicking a row (with no modifier keys) will select that row, while clearing any existing selection\n- click + `cmd`/`ctrl` modifier key will toggle the selection for the clicked row while keeping any other existing selection. So if the row was not selected, it's being added to the current selection, while if the row was already selected, it's being removed from the selection\n- click + `shift` modifier key will perform a multi selection, starting from the last selection click where the `shift` key was not used.\n\n\n\n\n\nUse your mouse to select multiple rows. Expect click and click + `cmd`/`ctrl`/`shift` modifier keys to behave just like they are in the MacOS Finder app.\n\n\n\n```ts file=\"$DOCS/reference/default-selection-mode-multi-row-example.page.tsx\"\n\n```\n\n\n\n### Keyboard interactions\n\nBy default is enabled, so you can use the **spacebar** key to select multiple rows. Using the spacebar key is equivalent to doing a mouse click, so expect the combination of **spacebar** + `cmd`/`ctrl`/`shift` modifier keys to behave just like clicking + the same modifier keys.\n\n\n\n\n\nUse spacebar + optional `cmd`/`ctrl`/`shift` modifier keys just like you would do clicking + the same modifier keys.\n\n\n\n```ts file=\"$DOCS/reference/default-selection-mode-multi-row-keyboard-toggle-example.page.tsx\"\n\n```\n\n\n\n\n\nFor selecting all the rows in the table, you can use `cmd`/`ctrl` + `A` keyboard shortcut.\n\n\n\n## Using a selection checkbox\n\nSelection multiple rows is made easier when there is a checkbox column and even-more-so when there is grouping.\n\nConfiguring checkbox selection is as easy as specifying renderSelectionCheckBox on any of the columns in the grid. renderSelectionCheckBox can either be the boolean `true` or a render function that allows the customization of the selection checkbox.\n\n```ts {8}\nconst columns: InfiniteTablePropColumns = {\n id: {\n field: 'id',\n defaultWidth: 80,\n },\n country: {\n // show the selection checkbox for this column\n renderSelectionCheckBox: true,\n field: 'country',\n },\n firstName: {\n field: 'firstName',\n },\n};\n```\n\n\n\nAny column can show a selection checkbox if column.renderSelectionCheckBox is set to `true`.\n\nThere is nothing prevening you from providing multiple checkbox columns.\n\n\n\n\n\n\n\nUse the selection checkboxes to select rows. You can also use the spacebar key (+ optional shift modifier) to modify the selection\n\n\n\n```ts file=\"$DOCS/reference/default-checkbox-selection-multi-row-example.page.tsx\"\n\n```\n\n\n\n### Mouse interactions\n\nThe mouse interactions are the obvious ones you would expect from checkbox selection. Clicking a checkbox will toggle the selection for the correspondign row. Also, clicking the header checkbox will select/deselect all the rows in the table. The selection checkbox in the column header can be in an indeterminate state (when just some of the rows are selected), and when clicking it, it will become checked and select all rows.\n\n\n\nYou can use renderHeaderSelectionCheckBox for a column to customize the checkbox in the column header. If no header selection checkbox is specified, renderSelectionCheckBox will be used for the column header as well, just like it's used for grid rows.\n\n\n\n### Keyboard interactions\n\nWhen multi-row selection is configured to use checkboxes, you can still use your keyboard to select rows. Navigate to the desired row (you can have keyboard navigation active for either cells or rows) and press the spacebar. If the row is not selected it will select it, otherwise it will deselect it.\n\n\n\nThe only supported modifier key when selecting a row by pressing **spacebar** is the `shift` key - it allows users to extend the selection over multiple rows, which is handy.\n\n\n\n## Specify a `rowSelection` value\n\nWhen multiple row selection is used, the prop should be an object that can have the following shape:\n\n```ts\nconst rowSelection = {\n selectedRows: [3, 6, 100, 23], // those specific rows are selected\n defaultSelection: false, // while all other rows are deselected by default\n};\n\n// or\nconst rowSelection = {\n deselectedRows: [3, 6, 100, 23], // those specific rows are deselected\n defaultSelection: true, // all other rows are selected\n};\n\n// or, for grouped data - this example assumes groupBy=continent,country,city\n\n// for using this form of multi-row selection when you have grouping,\n// you have to specify DataSource.useGroupKeysForMultiRowSelection = true\nconst rowSelection = {\n selectedRows: [\n 45, // row with id 45 is selected, no matter the group it is nested in\n ['Europe', 'France'], // all rows in Europe/France are selected\n ['Asia'], // all rows in Asia are selected\n ],\n deselectedRows: [\n ['Europe', 'France', 'Paris'], // all rows in Paris are deselected\n ],\n defaultSelection: false, // all other rows are selected\n};\n```\n\nAs shown above, the `rowSelection.selectedRows` and `rowSelection.deselectedRows` arrays can either contain:\n\n- primary keys of rows (which are usually strings or numbers) - any non-array value inside `rowSelection.selectedRows`/`rowSelection.deselectedRows` is considered an id/primaryKey value for a leaf row in the grouped dataset.\n- arrays of group keys (can be combined with primary keys as well) - those arrays describe the path of the specified selected group. Please note that `rowSelection.selectedRows` can contain certain paths while `rowSelection.deselectedRows` can contain child paths of those paths ... or any other imaginable combination. For this kind of , you need to enable .\n\n\n \nRow Selection only uses primary keys by default, even when you have grouped data.\n\nFor grouping however, you might want to use selection with group keys - for doing that, specify DataSource.useGroupKeysForMultiRowSelection=true.\nNote that if you use selection with group keys, the selection will not be relevant/consistent when the changes.\n\nWhen you have both grouping and lazy loading, must be enabled - read more about it in the note below.\n\n\n\n\n\nWhen is being used - this means not all available groups/rows have actually been loaded yet in the dataset - we need a way to allow you to specify that those possibly unloaded rows/groups are selected or not. In this case, the `rowSelection.selectedRows`/`rowSelection.deselectedRows` arrays should not have row primary keys as strings/numbers, but rather rows/groups specified by their full path (so should be set to `true`).\n\n```ts {6}\n// this example assumes groupBy=continent,country,city\nconst rowSelection = {\n selectedRows: [\n // row with id 45 is selected - we need this because in the lazyLoad scenario,\n // not all parents might have been made available yet\n ['Europe','Italy', 'Rome', 45],\n ['Europe','France'], // all rows in Europe/France are selected\n ['Asia'] // all rows in Asia are selected\n ]\n deselectedRows: [\n ['Europe','Italy','Rome'] // all rows in Rome are deselected\n // but note that row with id 45 is selected, so Rome will be\n // rendered with an indeterminate selection state\n ],\n defaultSelection: false // all other rows are selected\n}\n```\n\nIn the example above, we know that there are 3 groups (`continent`, `country`, `city`), so any item in the array that has a 4th element is a fully specified leaf node. While lazy loading, we need this fully specified path for specific nodes, so we know which group rows to render with indeterminate selection.\n\n\n\n### Controlled selection with checkbox column\n\nWhen using the controlled , make sure to specify the callback prop to update the selection accordingly as a result of user interaction.\n\n\n\n\n\nThis example shows how you can use multiple row selection with a predefined controlled value.\n\nGo ahead and select some groups/rows and see the selection value adjust.\n\nThe example also shows how you can use the `InfiniteTableApi` to retrieve the actual ids of the selected rows.\n\n\n\n```ts file=\"$DOCS/reference/controlled-multi-row-selection-example.page.tsx\"\n\n```\n\n\n\n## Multi Selection with Lazy Load and Grouping\n\nProbably the most complex use-case for multi selection (with checkbox) is the combination of grouping and lazy-loading.\n\nIn this scenario, not all groups and/or rows are loaded at a given point in time, but we need to be able to know how to render each checkbox for each group - either checked, unchecked or indeterminate, all this depending on whether all children, at any nesting levels are selected or not.\n\nIn order to make this possible, the value will only contain arrays (and not individual primary keys) in the `selectedRows` and `deselectedRows` arrays and the DataSource will be configured with .\n\n\n\n\n\nThe `DataSet` has lazy loading and grouping.\n\nThe selection uses group keys (see ), so it can specify as selected even rows/groups that have not been loaded yet.\n\nNote in the example below that some of the group rows are partly selected, even if the leaf rows which are specified as selected in the are not yet loaded.\n\n\n\n```ts file=\"$DOCS/reference/lazy-multi-row-selection-example.page.tsx\"\n\n```\n\n\n" }, + "/docs/learn/theming/css-variables": { + "filePath": "/docs/learn/theming/css-variables", + "routePath": "/docs/learn/theming/css-variables", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/learn/theming/css-variables.page.md", + "fileName": "css-variables.page.md", + "folderPath": "/docs/learn/theming/", + "frontmatter": { + "title": "CSS Variables", + "description": "Reference list of CSS variables that can be used to style the Infinite Table for React" + }, + "excerpt": "Below you can find the complete list of CSS variables that can be used to style the component.", + "readingTime": "9 min read", + "content": "\nBelow you can find the complete list of CSS variables that can be used to style the component.\n\n{/* START VARS */}\n### Accent color\n\nBrand-specific accent color. This probably needs override to match your app.\n\n```css\n--infinite-accent-color\n```\n\n### Error color\n\n\n\n```css\n--infinite-error-color\n```\n\n### Color\n\nThe text color inside the component\n\n```css\n--infinite-color\n```\n\n### Space 0\n\n\n\n```css\n--infinite-space-0\n```\n\n### Space 1\n\n\n\n```css\n--infinite-space-1\n```\n\n### Space 2\n\n\n\n```css\n--infinite-space-2\n```\n\n### Space 3\n\n\n\n```css\n--infinite-space-3\n```\n\n### Space 4\n\n\n\n```css\n--infinite-space-4\n```\n\n### Space 5\n\n\n\n```css\n--infinite-space-5\n```\n\n### Space 6\n\n\n\n```css\n--infinite-space-6\n```\n\n### Space 7\n\n\n\n```css\n--infinite-space-7\n```\n\n### Space 8\n\n\n\n```css\n--infinite-space-8\n```\n\n### Space 9\n\n\n\n```css\n--infinite-space-9\n```\n\n### Space 10\n\n\n\n```css\n--infinite-space-10\n```\n\n### Font size 0\n\n\n\n```css\n--infinite-font-size-0\n```\n\n### Font size 1\n\n\n\n```css\n--infinite-font-size-1\n```\n\n### Font size 2\n\n\n\n```css\n--infinite-font-size-2\n```\n\n### Font size 3\n\n\n\n```css\n--infinite-font-size-3\n```\n\n### Font size 4\n\n\n\n```css\n--infinite-font-size-4\n```\n\n### Font size 5\n\n\n\n```css\n--infinite-font-size-5\n```\n\n### Font size 6\n\n\n\n```css\n--infinite-font-size-6\n```\n\n### Font size 7\n\n\n\n```css\n--infinite-font-size-7\n```\n\n### Font family\n\n\n\n```css\n--infinite-font-family\n```\n\n### Min height\n\n\n\n```css\n--infinite-min-height\n```\n\n### Border radius\n\n\n\n```css\n--infinite-border-radius\n```\n\n### Background\n\nThe background color for the whole component.\n\nOverriden in the `dark` theme.\n\n```css\n--infinite-background\n```\n\n### Icon size\n\n\n\n```css\n--infinite-icon-size\n```\n\n### Load mask padding\n\nThe padding used for the content inside the LoadMask.\n\n```css\n--infinite-load-mask-padding\n```\n\n### Load mask color\n\n\n\n```css\n--infinite-load-mask-color\n```\n\n### Load mask text background\n\n\n\n```css\n--infinite-load-mask-text-background\n```\n\n### Load mask overlay background\n\n\n\n```css\n--infinite-load-mask-overlay-background\n```\n\n### Load mask overlay opacity\n\n\n\n```css\n--infinite-load-mask-overlay-opacity\n```\n\n### Load mask border radius\n\n\n\n```css\n--infinite-load-mask-border-radius\n```\n\n### Header background\n\nBackground color for the header. Defaults to [`--infinie-header-cell-background`](#header-cell-background).\n\nOverriden in the `dark` theme.\n\n```css\n--infinite-header-background\n```\n\n### Header color\n\nThe text color inside the header.\n\nOverriden in the `dark` theme.\n\n```css\n--infinite-header-color\n```\n\n### Column header height\n\nThe height of the column header.\n\n```css\n--infinite-column-header-height\n```\n\n### Header cell background\n\nBackground for header cells.\n\nOverriden in the `dark` theme.\n\n```css\n--infinite-header-cell-background\n```\n\n### Header cell hover background\n\n\n\n```css\n--infinite-header-cell-hover-background\n```\n\n### Header cell padding\n\n\n\n```css\n--infinite-header-cell-padding\n```\n\n### Header cell padding x\n\n\n\n```css\n--infinite-header-cell-padding-x\n```\n\n### Header cell padding y\n\n\n\n```css\n--infinite-header-cell-padding-y\n```\n\n### Header cell icon size\n\n\n\n```css\n--infinite-header-cell-icon-size\n```\n\n### Header cell menu icon line width\n\n\n\n```css\n--infinite-header-cell-menu-icon-line-width\n```\n\n### Header cell sort icon margin\n\n\n\n```css\n--infinite-header-cell-sort-icon-margin\n```\n\n### Resize handle active area width\n\nThe width of the area you can hover over in order to grab the column resize handle.\nDefaults to `20px`.\n\nThe purpose of this active area is to make it easier to grab the resize handle.\n\n```css\n--infinite-resize-handle-active-area-width\n```\n\n### Resize handle width\n\nThe width of the colored column resize handle that is displayed on hover and on drag. Defaults to `2px`\n\n```css\n--infinite-resize-handle-width\n```\n\n### Resize handle hover background\n\nThe color of the column resize handle - the resize handle is the visible indicator that you see\nwhen hovering over the right-edge of a resizable column. Also visible on drag while doing a column resize.\n\n```css\n--infinite-resize-handle-hover-background\n```\n\n### Resize handle constrained hover background\n\nThe color of the column resize handle when it has reached a min/max constraint.\n\n```css\n--infinite-resize-handle-constrained-hover-background\n```\n\n### Filter operator padding x\n\n\n\n```css\n--infinite-filter-operator-padding-x\n```\n\n### Filter editor padding x\n\n\n\n```css\n--infinite-filter-editor-padding-x\n```\n\n### Filter editor margin x\n\n\n\n```css\n--infinite-filter-editor-margin-x\n```\n\n### Filter operator padding y\n\n\n\n```css\n--infinite-filter-operator-padding-y\n```\n\n### Filter editor padding y\n\n\n\n```css\n--infinite-filter-editor-padding-y\n```\n\n### Filter editor margin y\n\n\n\n```css\n--infinite-filter-editor-margin-y\n```\n\n### Filter editor background\n\n\n\n```css\n--infinite-filter-editor-background\n```\n\n### Filter editor border\n\n\n\n```css\n--infinite-filter-editor-border\n```\n\n### Filter editor focus border color\n\n\n\n```css\n--infinite-filter-editor-focus-border-color\n```\n\n### Filter editor border radius\n\n\n\n```css\n--infinite-filter-editor-border-radius\n```\n\n### Filter editor color\n\n\n\n```css\n--infinite-filter-editor-color\n```\n\n### Cell padding\n\n\n\n```css\n--infinite-cell-padding\n```\n\n### Cell border width\n\n\n\n```css\n--infinite-cell-border-width\n```\n\n### Cell border\n\nSpecifies the border for cells.\n\nOverriden in the `dark` theme - eg: `1px solid #2a323d`\n\n```css\n--infinite-cell-border\n```\n\n### Cell border invisible\n\n\n\n```css\n--infinite-cell-border-invisible\n```\n\n### Cell border radius\n\n\n\n```css\n--infinite-cell-border-radius\n```\n\n### Column reorder effect duration\n\n\n\n```css\n--infinite-column-reorder-effect-duration\n```\n\n### Pinned cell border\n\n\n\n```css\n--infinite-pinned-cell-border\n```\n\n### Cell color\n\nText color inside rows. Defaults to `currentColor`\n\nOverriden in `dark` theme.\n\n```css\n--infinite-cell-color\n```\n\n### Selected cell background\n\nThe background for selected cells, when cell selection is enabled.\n\nIf not specified, it will default to `var(--infinite-active-cell-background)`.\n\n```css\n--infinite-selected-cell-background\n```\n\n### Selected cell background default\n\n\n\n```css\n--infinite-selected-cell-background-default\n```\n\n### Selected cell background alpha\n\nThe opacity of the background color for the selected cell.\n\nIf not specified, it will default to the value for `var(--infinite-active-cell-background-alpha)`\n\n```css\n--infinite-selected-cell-background-alpha\n```\n\n### Selected cell background alpha table unfocused\n\nThe opacity of the background color for the selected cell, when the table is unfocused.\nIf not specified, it will default to `var(--infinite-active-cell-background-alpha--table-unfocused)`.\n\n```css\n--infinite-selected-cell-background-alpha--table-unfocused\n```\n\n### Selected cell border color\n\nThe color for border of the selected cell (when cell selection is enabled).\n Defaults to `var(--infinite-active-cell-border-color)`.\n\n```css\n--infinite-selected-cell-border-color\n```\n\n### Selected cell border width\n\nThe width of the border for the selected cell. Defaults to `var(--infinite-active-cell-border-width)`.\n\n```css\n--infinite-selected-cell-border-width\n```\n\n### Selected cell border style\n\nThe style of the border for the selected cell (eg: 'solid', 'dashed', 'dotted') - defaults to 'dashed'.\nDefaults to `var(--infinite-active-cell-border-style)`.\n\n```css\n--infinite-selected-cell-border-style\n```\n\n### Selected cell border\n\nSpecifies the border for the selected cell. Defaults to `var(--infinite-selected-cell-border-width) var(--infinite-selected-cell-border-style) var(--infinite-selected-cell-border-color)`.\n\n```css\n--infinite-selected-cell-border\n```\n\n### Active cell background alpha\n\nThe opacity of the background color for the active cell (when cell keyboard navigation is enabled).\nEg: 0.25\n\nIf `activeBackground` is not explicitly defined (this is the default), the background color of the active cell\nis the same as the border color (`activeBorderColor`), but with this modified opacity.\n\nIf `activeBorderColor` is also not defined, the accent color will be used.\n\nThis is applied when the component has focus.\n\n```css\n--infinite-active-cell-background-alpha\n```\n\n### Active cell background alpha table unfocused\n\nSame as the above, but applied when the component does not have focus.\n\n```css\n--infinite-active-cell-background-alpha--table-unfocused\n```\n\n### Active cell background\n\nThe background color of the active cell.\n\nIf not specified, it will default to `activeBorderColor` with the opacity of `activeBackgroundAlpha`.\nIf `activeBorderColor` is not specified, it will default to the accent color, with the same opacity as mentioned.\n\nHowever, specify this to explicitly override the default.\n\n```css\n--infinite-active-cell-background\n```\n\n### Active cell background default\n\n\n\n```css\n--infinite-active-cell-background-default\n```\n\n### Active cell border color\n\nThe color for border of the active cell (when cell keyboard navigation is enabled).\n\n```css\n--infinite-active-cell-border-color\n```\n\n### Active cell border width\n\nThe width of the border for the active cell.\n\n```css\n--infinite-active-cell-border-width\n```\n\n### Active cell border style\n\nThe style of the border for the active cell (eg: 'solid', 'dashed', 'dotted') - defaults to 'dashed'.\n\n```css\n--infinite-active-cell-border-style\n```\n\n### Active cell border\n\nSpecifies the border for the active cell. Defaults to `var(--infinite-active-cell-border-width) var(--infinite-active-cell-border-style) var(--infinite-active-cell-border-color)`.\n\n```css\n--infinite-active-cell-border\n```\n\n### Selection checkbox margin inline\n\n\n\n```css\n--infinite-selection-checkbox-margin-inline\n```\n\n### Menu background\n\n\n\n```css\n--infinite-menu-background\n```\n\n### Menu color\n\n\n\n```css\n--infinite-menu-color\n```\n\n### Menu padding\n\n\n\n```css\n--infinite-menu-padding\n```\n\n### Menu cell padding vertical\n\n\n\n```css\n--infinite-menu-cell-padding-vertical\n```\n\n### Menu cell padding horizontal\n\n\n\n```css\n--infinite-menu-cell-padding-horizontal\n```\n\n### Menu cell margin vertical\n\n\n\n```css\n--infinite-menu-cell-margin-vertical\n```\n\n### Menu item disabled background\n\n\n\n```css\n--infinite-menu-item-disabled-background\n```\n\n### Menu item active background\n\n\n\n```css\n--infinite-menu-item-active-background\n```\n\n### Menu item active opacity\n\n\n\n```css\n--infinite-menu-item-active-opacity\n```\n\n### Menu item pressed opacity\n\n\n\n```css\n--infinite-menu-item-pressed-opacity\n```\n\n### Menu item pressed background\n\n\n\n```css\n--infinite-menu-item-pressed-background\n```\n\n### Menu item disabled opacity\n\n\n\n```css\n--infinite-menu-item-disabled-opacity\n```\n\n### Menu border radius\n\n\n\n```css\n--infinite-menu-border-radius\n```\n\n### Menu shadow color\n\n\n\n```css\n--infinite-menu-shadow-color\n```\n\n### Rowdetail background\n\n\n\n```css\n--infinite-rowdetail-background\n```\n\n### Rowdetail padding\n\n\n\n```css\n--infinite-rowdetail-padding\n```\n\n### Rowdetail grid height\n\n\n\n```css\n--infinite-rowdetail-grid-height\n```\n\n### Row background\n\nBackground color for rows. Defaults to [`--infinite-background`](#background).\n\nOverriden in `dark` theme.\n\n```css\n--infinite-row-background\n```\n\n### Row odd background\n\nBackground color for odd rows. Even rows will use [`--infinite-row-background`](#row-background).\n\nOverriden in `dark` theme.\n\n```css\n--infinite-row-odd-background\n```\n\n### Row selected background\n\n\n\n```css\n--infinite-row-selected-background\n```\n\n### Active row background\n\nThe background color of the active row. Defaults to the value of `var(--infinite-active-cell-background)`.\n\nHowever, specify this to explicitly override the default.\n\n```css\n--infinite-active-row-background\n```\n\n### Active row border color\n\nThe border color for the active row. Defaults to the value of `var(--infinite-active-cell-border-color)`.\n\n```css\n--infinite-active-row-border-color\n```\n\n### Active row border width\n\nThe width of the border for the active row. Defaults to the value of `var(--infinite-active-cell-border-width)`.\n\n```css\n--infinite-active-row-border-width\n```\n\n### Active row border style\n\nThe style of the border for the active row (eg: 'solid', 'dashed', 'dotted') - defaults to the value of `var(--infinite-active-cell-border-style)`, which is `dashed` by default.\n\n```css\n--infinite-active-row-border-style\n```\n\n### Active row border\n\nSpecifies the border for the active row. Defaults to `var(--infinite-active-row-border-width) var(--infinite-active-row-border-style) var(--infinite-active-row-border-color)`.\n\n```css\n--infinite-active-row-border\n```\n\n### Active row background alpha\n\nThe opacity of the background color for the active row (when row keyboard navigation is enabled).\nWhen you explicitly specify `--infinite-active-row-background`, this variable will not be used.\nInstead, this variable is used when the active row background uses the color of the active cell (border).\n\nThis is applied when the component has focus.\n\nDefaults to the value of `var(--infinite-active-cell-background-alpha)`.\n\n```css\n--infinite-active-row-background-alpha\n```\n\n### Active row background alpha table unfocused\n\nSame as the above, but applied when the component does not have focus.\n\nWhen you explicitly specify `--infinite-active-row-background`, this variable will not be used.\nInstead, this variable is used when the active row background uses the color of the active cell (border).\n\nDefaults to the value of `var(--infinite-active-cell-background-alpha--table-unfocused)`.\n\n```css\n--infinite-active-row-background-alpha--table-unfocused\n```\n\n### Row hover background\n\nBackground color for rows, on hover.\n\nOverriden in the `dark` theme.\n\n```css\n--infinite-row-hover-background\n```\n\n### Row selected hover background\n\n\n\n```css\n--infinite-row-selected-hover-background\n```\n\n### Group row background\n\n\n\n```css\n--infinite-group-row-background\n```\n\n### Group row column nesting\n\n\n\n```css\n--infinite-group-row-column-nesting\n```\n\n### Row pointer events while scrolling\n\n\n\n```css\n--infinite-row-pointer-events-while-scrolling\n```{/* END VARS */}\n" + }, + "/docs/learn/theming/": { + "filePath": "/docs/learn/theming/index", + "routePath": "/docs/learn/theming/", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/learn/theming/index.page.md", + "fileName": "index.page.md", + "folderPath": "/docs/learn/theming/", + "frontmatter": { + "title": "Theming", + "description": "Read our docs on the available themes and how you can customize the look and feel of InfiniteTable for React." + }, + "excerpt": "`` ships with a CSS file that you need to import in your codebase to make the component look as intended.", + "readingTime": "3 min read", + "content": "\n`` ships with a CSS file that you need to import in your codebase to make the component look as intended.\n\n```ts\nimport '@infinite-table/infinite-react/index.css';\n```\n\nThis root CSS file includes the `\"default\"` theme.\n\nThe other available themes are\n\n - `shadcn`\n - `minimalist`\n - `ocean`\n - `balsam`\n\nand if you want to use any of them, you have to import their respective CSS file explicitly:\n\n```ts\nimport '@infinite-table/infinite-react/theme/shadcn.css'\nimport '@infinite-table/infinite-react/theme/balsam.css'\nimport '@infinite-table/infinite-react/theme/minimalist.css'\nimport '@infinite-table/infinite-react/theme/ocean.css'\n```\n\nEach theme CSS file includes both the **`light`** and the **`dark`** modes.\n\n\n\nVersion `6.2.0` is the first version where the root CSS file (`@infinite-table/infinite-react/index.css`) doesn't include all the themes. Previous to this version, simply importing the root CSS file gave you access to all available themes.\n\nSplitting each theme into a dedicated CSS file helps reduce the bundle size for our users, as most people will only use one theme for `` in their apps.\n\n\n\n## Applying a theme\n\nThe following themes are currently available:\n\n - `default` - applied by default, no special configuration needed. It's included in the root CSS you need to import from `@infinite-table/infinite-react/index.css`\n - `balsam`\n - `minimalist`\n - `ocean`\n - `shadcn` - for this theme to correctly show up, make sure the shadcn CSS vars are available on page - see [shadcn theming](https://ui.shadcn.com/docs/theming) for details\n\n\nTo apply a theme (except the default one), you have to set the className `\"infinite-theme-name--THEME_NAME\"` to any parent element of the `` component (or even on the component itself).\n\nYou will want to apply the theme name and theme mode classNames to the same element, so you'll end up with a className like `\"infinite-theme-name--minimalist infinite-theme-mode--dark\"`.\n\n```tsx title=\"Applying the minimalist theme with dark mode explicitly\"\n\n \n \n \n\n```\n\n\n\n\n\n\n\nExample configured with `minimalist` theme and `dark` mode by default.\n\n\n```tsx live title=\"Theme switching demo - defaults to minimalist theme in dark mode\" size=\"md\" viewMode=\"preview\" files=\"theme-switching-minimalist-theme-default-example.page.tsx,columns.ts\"\n```\n\n\n\n\n## Theme mode - light or dark\n\nAt runtime, the `light` or `dark` mode is applied based on the user OS settings for the [preferred color scheme](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme).\n\nTo explicitly apply the light mode, apply the className `\"infinite-theme-mode--light\"` to any parent element of the `` component.\n\nTo explicitly apply the dark mode, apply the className `\"infinite-theme-mode--dark\"` to any parent element of the `` component.\n\n```tsx title=\"Explicitly applying light mode via container className\"\n
\n \n \n \n
\n```\n\nIf instead you specify a `infinite-theme-mode--dark` CSS className, the dark mode will be applied\n\n```tsx title=\"Explicitly applying dark theme via container className\"\n\n
\n \n \n \n
\n\n```\n\n\n\nExample configured with `default` theme and `light` mode by default.\n\n\n```tsx live title=\"Theme switching demo - defaults to light theme\" size=\"md\" viewMode=\"preview\" files=\"theme-switching-example.page.tsx,columns.ts\"\n\n```\n\n\n\nIf you don't explicitly have a `infinite-theme-mode--light` or `infinite-theme-mode--dark` ancestor, `InfiniteTable` will use the browser/OS preference (via `@media (prefers-color-scheme: ...)`) to apply the dark or light theme.\n\n\n\n## Available themes\n\n### Default theme\n\nThe `default` theme is applied when you don't specify any explicit theme by default.\n\n### Minimalist theme\n\nThe `minimalist` theme is inspired from minimalistic designs and is a good choice if you want to keep the UI simple and clean." + }, "/docs/learn/sorting/multiple-sorting": { "filePath": "/docs/learn/sorting/multiple-sorting", "routePath": "/docs/learn/sorting/multiple-sorting", @@ -2725,34 +2783,6 @@ "readingTime": "10 min read", "content": "\nBy default, the Infinite Table is sortable - clicking a column will sort the grid by that column. Clicking again will reverse the sort and a third click on the column removes the sort altogether.\n\nAt any point, clicking another column header removes any existing column sort and performs a new sort by the clicked column.\n\n\n\nThis is called single sorting - only one column can be sorted at a time.\n\nTechnically, it's the `` that's being sorted, not the `` component.\n\n\n\n\n\n\n\nBy default, clicking a column header sorts the column.\n\n\n\n```ts file=\"local-single-sorting-example-defaults-with-local-data.page.tsx\"\n\n```\n\n\n\n## Apply a default sort order\n\nYou can specify a default sort order by using the prop - specify an object like\n\n```ts\n// sort by `firstName`, in ascending order\ndefaultSortInfo = { field: 'firstName', dir: 1 };\n```\n\n\n\n is an uncontrolled property, so updating the sorting by clicking a column header does not require you to respond to user actions via the .\n\nUncontrolled sorting is managed internally by the `` component, so you don't need to worry about it.\n\nFor controlled sorting, make sure you use the prop and the callback.\n\n\n\n\n\n\n\nThe `age` column is sorted in ascending order.\n\n\n\n```ts file=\"local-uncontrolled-single-sorting-example-with-local-data.page.tsx\"\n\n```\n\n\n\n## Controlled sorting\n\nFor controlled, single sorting, use the as an object like this:\n\n```ts\n// sort by `firstName`, in ascending order\nsortInfo = { field: 'firstName', dir: 1 };\n```\n\nor you can specify `null` for explicit no sorting\n\n```ts\n// no sorting\nsortInfo = null;\n```\n\n\n\nWhen you use controlled sorting via , make sure you also listen to for changes, to get notifications when sorting is changed by the user. Also, for controlled sorting, it's your responsibility to sort the data - read bellow in the [controlled and uncontrolled section](#controlled-and-uncontrolled-sorting).\n\n\n\n## Describing the sort order\n\nTo describe the sorting order, you have to use an object that has the following shape:\n\n- `dir` - `1 | -1` - the direction of the sorting\n- `field?` - `keyof DATA_TYPE` - the field to sort by - optional.\n- `id?` - `string` - if you don't sort by a field, you can specify an id of the column this sorting is bound to. Note that columns have a valueGetter, which will be used when doing local sorting and the column is not bound to an exact field.\n- `type?` - the sort type - one of the keys in - eg `\"string\"`, `\"number\"`, `\"date\"` - will be used for local sorting, to provide the proper comparison function.\n\n### Multiple Sorting\n\nIf you want to use multiple sorting, specify an array of objects like\n\n```ts\n// sort by age in descending order, then by `firstName` in ascending order\nsortInfo = [\n { field: 'age', type: 'number', dir: -1 },\n { field: 'firstName', dir: 1 },\n];\n\n// no sorting\nsortInfo = [];\n```\n\nThis allows sorting by multiple fields (to which columns are bound) - you can specify however many you want - so when sorting two objects in the `DataSource`, the first `sortInfo` is used to compare the two, and then, on equal values, the next `sortInfo` is used and so on.\n\n\n\n\n\nThis table allows sorting multiple columns - initially the `country` column is sorted in descending order and the `salary` column is sorted in ascending order. Click the `salary` column to toggle the column sort to descending. Clicking it a second time will remove it from the sort altogether.\n\n\n\n```ts file=\"local-uncontrolled-multi-sorting-example-with-remote-data.page.tsx\"\n\n```\n\n\n\n\n\n```ts file=\"remote-uncontrolled-multi-sorting-example.page.tsx\"\n\n```\n\n\n\n\n\nIf you use uncontrolled sorting via there's no way to switch between single and multiple sorting after the component is mounted. If you have this use-case, you need to use the controlled prop.\n\n\n\n## Understanding sort mode\n\nSorting can be done both locally in the browser and remotely on the server. For configuring where sorting is performed you need to specify the . Possible values for are `false` (for local sorting) and `true` (for remote sorting).\n\nThis allows you fine-grained control on how sorting is done, either in the client or on the server.\n\n### Uncontrolled sorting\n\nIf you use uncontrolled sorting (namely you don't care about updating the yourself as a result of user interaction - via ) - then by default, the is `false` (so local sorting) unless you specify otherwise.\n\nYou can initially render the component with no sort state or you can specify a default sorting state, via the uncontrolled prop .\n\n```tsx\n// initially render the component with ascending sorting on `firstName` field\n// also, note this is an array, so multiple sorting will be enabled\nconst defaultSortInfo = [{ field: 'firstName', dir: 1 }];\n\n\n primaryKey=\"id\"\n data={data}\n defaultSortInfo={defaultSortInfo}\n>\n \n
;\n```\n\nIf your data is remote and you want the sorting to happen on the backend, you can still use uncontrolled sorting, but you need to specify shouldReloadData.sortInfo=true.\n\nUsing remote sort mode will trigger a call to the function whenever sorting changes, so you can re-fetch the data from the backend, according to the new `sortInfo`.\n\nWhe `local` uncontrolled sorting is used, the `` sorts the data internally, based on the existing sorting information. To start with a specific `sortInfo`, use the prop. As the user interacts with the table, is being called with updated sort info and the `` continues to sort the data accordingly.\n\n\n\nThe prop is an uncontrolled prop, so it's all managed inside the `` component and you can't change it from the outside. If you need to control it from outside the component, use the controlled sortInfo prop - read the next section for more details\n\n\n\n### Controlled Sorting\n\nWhen you use the controlled prop, by default the is `true` (remote sorting), unless you specify otherwise.\n\nAlso, be aware that when the user interacts with the DataGrid when controlled sorting is configured, the prop will not update automatically - you need to listen to and update the yourself.\n\nJust like with uncontrolled sorting, updating the controlled when `shouldReloadData.sortInfo=true`, will trigger a call to the function, so new sorted data can be re-fetched.\n\n\n\nWhen the controlled is combined with shouldReloadData.sortInfo=false, the `` will sort the data internally, on any changes of the sorting information.\n\nBut remember it's your responsibility to update the prop when the user interacts with the DataGrid.\n\n\n\nBoth controlled and uncontrolled work in combination with - use it to be notified when sorting changes, so you can react and update your app accordingly if needed.\n\n### Local Sorting\n\nWhen you use uncontrolled sorting locally, the `` will sort the data internally, based on the prop. Local sorting is available for any configured source - be it an array or a function that returns a promise.\n\n\n\nYou can use , which is called whenever any of the sorting, filtering, grouping or pivoting information changes.\n\n\n\n\n\n```ts file=\"local-uncontrolled-single-sorting-example-with-remote-data.page.tsx\"\n\n```\n\n\n\n\n\n```ts file=\"local-uncontrolled-single-sorting-example-with-remote-data.page.tsx\"\n\n```\n\n\n\n### Remote Sorting\n\nSorting remotely makes a lot of sense when using a function as your source. Whenever the sort information is changed, the function will be called with all the information needed to retrieve the data from the remote endpoint.\n\n\n\nFor remote sorting, make sure you specify shouldReloadData.sortInfo=true - if you don't, the data will also be sorted locally in the browser (which most of the times will be harmless, but it means wasted CPU cycles).\n\n\n\n\n\n```ts file=\"remote-controlled-multi-sorting-example.page.tsx\"\n\n```\n\n\n\nIn the example above, remote and controlled sorting are combined - because `shouldReloadData.sortInfo=true` is specified, the `` will call the `data` function whenever sorting changes, and will pass in the `dataParams` object that contains the sort information.\n\n## Custom Sort Functions with `sortTypes`\n\nBy default, all columns are sorted as strings, even if they contain numeric values. To make numeric columns sort as numbers, you need to specify a `dataType` for the column, or, a column `sortType`.\n\nThere are two `dataType` values that can be used:\n\n- `\"string\"`\n- `\"number\"`\n\nEach dataType has its own sorting function and its own filtering operators & functions.\n\nSorting works in combination with the property, which is an object with keys being sort types and values being functions that compare two values of the same type.\n\n```ts\nconst sortTypes = {\n string: (a, b) => a.localeCompare(b),\n number: (a, b) => a - b,\n};\n```\n\nThose are the two sort types supported by default.\n\n\n\nThe functions specified in the object need to always sort data in ascending order.\n\n\n\n\nA column can choose to use a specific , in which case, for local sorting, the corresponding sort function will be used, or, it can simply specify a dataType and the `sortType` with the same name will be used (when no explicit sortType is defined).\n\nTo conclude, the dataType of a column will be used as the sortType and filterType, when those are not explicitly specified.\n\n\n\n\n\n```ts file=\"$DOCS/reference/datasource-props/sortTypes-example.page.tsx\"\n\n```\n\n\n\n\n\nIn this example, for the `\"color\"` column, we specified column.sortType=\"color\" - we could have passed that as `column.dataType` instead, but if the grid had filtering, it wouldn't know what filters to use for \"color\" - so we used column.sortType to only change how the data is sorted.\n\n\n\n\n\nWhen you provide a prop and the sorting information uses a custom sortType, make sure you specify that as the `type` property of the sorting info object.\n\n```tsx\ndefaultSortInfo={{\n field: 'color',\n dir: 1,\n // note this custom sort type\n type: 'color',\n}}\n```\n\nYou will need to have a property for that type in your object as well.\n\n```tsx\nsortTypes={{\n color: (a, b) => //...\n}}\n```\n\n\n\n## Replacing the sort function\n\nWhile there are many ways to customise sorting, including the mentioned above, you might want to completely replace the sorting function used by the `` component.\n\nYou can do this by configuring the prop.\n\n```tsx\nconst sortFunction = (sortInfo, dataArray) => {\n // sort the dataArray according to the sortInfo\n // and return the sorted array\n // return sortedDataArray;\n};\n sortFunction={sortFunction} />;\n```\n\nThe function specified in the prop is called with the as the first argument and the data array as the second. It should return a sorted array, as per the it was called with.\n\n\n\nWhen is specified, will be forced to `false`, as the sorting is done in the browser.\n\n\n\n\n```ts file=\"$DOCS/reference/datasource-props/local-sortFunction-single-sorting-example-with-local-data-example.page.tsx\"\n\n```\n\n\n" }, - "/docs/learn/theming/css-variables": { - "filePath": "/docs/learn/theming/css-variables", - "routePath": "/docs/learn/theming/css-variables", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/learn/theming/css-variables.page.md", - "fileName": "css-variables.page.md", - "folderPath": "/docs/learn/theming/", - "frontmatter": { - "title": "CSS Variables", - "description": "Reference list of CSS variables that can be used to style the Infinite Table for React" - }, - "excerpt": "Below you can find the complete list of CSS variables that can be used to style the component.", - "readingTime": "9 min read", - "content": "\nBelow you can find the complete list of CSS variables that can be used to style the component.\n\n{/* START VARS */}\n### Accent color\n\nBrand-specific accent color. This probably needs override to match your app.\n\n```css\n--infinite-accent-color\n```\n\n### Error color\n\n\n\n```css\n--infinite-error-color\n```\n\n### Color\n\nThe text color inside the component\n\n```css\n--infinite-color\n```\n\n### Space 0\n\n\n\n```css\n--infinite-space-0\n```\n\n### Space 1\n\n\n\n```css\n--infinite-space-1\n```\n\n### Space 2\n\n\n\n```css\n--infinite-space-2\n```\n\n### Space 3\n\n\n\n```css\n--infinite-space-3\n```\n\n### Space 4\n\n\n\n```css\n--infinite-space-4\n```\n\n### Space 5\n\n\n\n```css\n--infinite-space-5\n```\n\n### Space 6\n\n\n\n```css\n--infinite-space-6\n```\n\n### Space 7\n\n\n\n```css\n--infinite-space-7\n```\n\n### Space 8\n\n\n\n```css\n--infinite-space-8\n```\n\n### Space 9\n\n\n\n```css\n--infinite-space-9\n```\n\n### Space 10\n\n\n\n```css\n--infinite-space-10\n```\n\n### Font size 0\n\n\n\n```css\n--infinite-font-size-0\n```\n\n### Font size 1\n\n\n\n```css\n--infinite-font-size-1\n```\n\n### Font size 2\n\n\n\n```css\n--infinite-font-size-2\n```\n\n### Font size 3\n\n\n\n```css\n--infinite-font-size-3\n```\n\n### Font size 4\n\n\n\n```css\n--infinite-font-size-4\n```\n\n### Font size 5\n\n\n\n```css\n--infinite-font-size-5\n```\n\n### Font size 6\n\n\n\n```css\n--infinite-font-size-6\n```\n\n### Font size 7\n\n\n\n```css\n--infinite-font-size-7\n```\n\n### Font family\n\n\n\n```css\n--infinite-font-family\n```\n\n### Min height\n\n\n\n```css\n--infinite-min-height\n```\n\n### Border radius\n\n\n\n```css\n--infinite-border-radius\n```\n\n### Background\n\nThe background color for the whole component.\n\nOverriden in the `dark` theme.\n\n```css\n--infinite-background\n```\n\n### Icon size\n\n\n\n```css\n--infinite-icon-size\n```\n\n### Load mask padding\n\nThe padding used for the content inside the LoadMask.\n\n```css\n--infinite-load-mask-padding\n```\n\n### Load mask color\n\n\n\n```css\n--infinite-load-mask-color\n```\n\n### Load mask text background\n\n\n\n```css\n--infinite-load-mask-text-background\n```\n\n### Load mask overlay background\n\n\n\n```css\n--infinite-load-mask-overlay-background\n```\n\n### Load mask overlay opacity\n\n\n\n```css\n--infinite-load-mask-overlay-opacity\n```\n\n### Load mask border radius\n\n\n\n```css\n--infinite-load-mask-border-radius\n```\n\n### Header background\n\nBackground color for the header. Defaults to [`--infinie-header-cell-background`](#header-cell-background).\n\nOverriden in the `dark` theme.\n\n```css\n--infinite-header-background\n```\n\n### Header color\n\nThe text color inside the header.\n\nOverriden in the `dark` theme.\n\n```css\n--infinite-header-color\n```\n\n### Column header height\n\nThe height of the column header.\n\n```css\n--infinite-column-header-height\n```\n\n### Header cell background\n\nBackground for header cells.\n\nOverriden in the `dark` theme.\n\n```css\n--infinite-header-cell-background\n```\n\n### Header cell hover background\n\n\n\n```css\n--infinite-header-cell-hover-background\n```\n\n### Header cell padding\n\n\n\n```css\n--infinite-header-cell-padding\n```\n\n### Header cell padding x\n\n\n\n```css\n--infinite-header-cell-padding-x\n```\n\n### Header cell padding y\n\n\n\n```css\n--infinite-header-cell-padding-y\n```\n\n### Header cell icon size\n\n\n\n```css\n--infinite-header-cell-icon-size\n```\n\n### Header cell menu icon line width\n\n\n\n```css\n--infinite-header-cell-menu-icon-line-width\n```\n\n### Header cell sort icon margin\n\n\n\n```css\n--infinite-header-cell-sort-icon-margin\n```\n\n### Resize handle active area width\n\nThe width of the area you can hover over in order to grab the column resize handle.\nDefaults to `20px`.\n\nThe purpose of this active area is to make it easier to grab the resize handle.\n\n```css\n--infinite-resize-handle-active-area-width\n```\n\n### Resize handle width\n\nThe width of the colored column resize handle that is displayed on hover and on drag. Defaults to `2px`\n\n```css\n--infinite-resize-handle-width\n```\n\n### Resize handle hover background\n\nThe color of the column resize handle - the resize handle is the visible indicator that you see\nwhen hovering over the right-edge of a resizable column. Also visible on drag while doing a column resize.\n\n```css\n--infinite-resize-handle-hover-background\n```\n\n### Resize handle constrained hover background\n\nThe color of the column resize handle when it has reached a min/max constraint.\n\n```css\n--infinite-resize-handle-constrained-hover-background\n```\n\n### Filter operator padding x\n\n\n\n```css\n--infinite-filter-operator-padding-x\n```\n\n### Filter editor padding x\n\n\n\n```css\n--infinite-filter-editor-padding-x\n```\n\n### Filter editor margin x\n\n\n\n```css\n--infinite-filter-editor-margin-x\n```\n\n### Filter operator padding y\n\n\n\n```css\n--infinite-filter-operator-padding-y\n```\n\n### Filter editor padding y\n\n\n\n```css\n--infinite-filter-editor-padding-y\n```\n\n### Filter editor margin y\n\n\n\n```css\n--infinite-filter-editor-margin-y\n```\n\n### Filter editor background\n\n\n\n```css\n--infinite-filter-editor-background\n```\n\n### Filter editor border\n\n\n\n```css\n--infinite-filter-editor-border\n```\n\n### Filter editor focus border color\n\n\n\n```css\n--infinite-filter-editor-focus-border-color\n```\n\n### Filter editor border radius\n\n\n\n```css\n--infinite-filter-editor-border-radius\n```\n\n### Filter editor color\n\n\n\n```css\n--infinite-filter-editor-color\n```\n\n### Cell padding\n\n\n\n```css\n--infinite-cell-padding\n```\n\n### Cell border width\n\n\n\n```css\n--infinite-cell-border-width\n```\n\n### Cell border\n\nSpecifies the border for cells.\n\nOverriden in the `dark` theme - eg: `1px solid #2a323d`\n\n```css\n--infinite-cell-border\n```\n\n### Cell border invisible\n\n\n\n```css\n--infinite-cell-border-invisible\n```\n\n### Cell border radius\n\n\n\n```css\n--infinite-cell-border-radius\n```\n\n### Column reorder effect duration\n\n\n\n```css\n--infinite-column-reorder-effect-duration\n```\n\n### Pinned cell border\n\n\n\n```css\n--infinite-pinned-cell-border\n```\n\n### Cell color\n\nText color inside rows. Defaults to `currentColor`\n\nOverriden in `dark` theme.\n\n```css\n--infinite-cell-color\n```\n\n### Selected cell background\n\nThe background for selected cells, when cell selection is enabled.\n\nIf not specified, it will default to `var(--infinite-active-cell-background)`.\n\n```css\n--infinite-selected-cell-background\n```\n\n### Selected cell background default\n\n\n\n```css\n--infinite-selected-cell-background-default\n```\n\n### Selected cell background alpha\n\nThe opacity of the background color for the selected cell.\n\nIf not specified, it will default to the value for `var(--infinite-active-cell-background-alpha)`\n\n```css\n--infinite-selected-cell-background-alpha\n```\n\n### Selected cell background alpha table unfocused\n\nThe opacity of the background color for the selected cell, when the table is unfocused.\nIf not specified, it will default to `var(--infinite-active-cell-background-alpha--table-unfocused)`.\n\n```css\n--infinite-selected-cell-background-alpha--table-unfocused\n```\n\n### Selected cell border color\n\nThe color for border of the selected cell (when cell selection is enabled).\n Defaults to `var(--infinite-active-cell-border-color)`.\n\n```css\n--infinite-selected-cell-border-color\n```\n\n### Selected cell border width\n\nThe width of the border for the selected cell. Defaults to `var(--infinite-active-cell-border-width)`.\n\n```css\n--infinite-selected-cell-border-width\n```\n\n### Selected cell border style\n\nThe style of the border for the selected cell (eg: 'solid', 'dashed', 'dotted') - defaults to 'dashed'.\nDefaults to `var(--infinite-active-cell-border-style)`.\n\n```css\n--infinite-selected-cell-border-style\n```\n\n### Selected cell border\n\nSpecifies the border for the selected cell. Defaults to `var(--infinite-selected-cell-border-width) var(--infinite-selected-cell-border-style) var(--infinite-selected-cell-border-color)`.\n\n```css\n--infinite-selected-cell-border\n```\n\n### Active cell background alpha\n\nThe opacity of the background color for the active cell (when cell keyboard navigation is enabled).\nEg: 0.25\n\nIf `activeBackground` is not explicitly defined (this is the default), the background color of the active cell\nis the same as the border color (`activeBorderColor`), but with this modified opacity.\n\nIf `activeBorderColor` is also not defined, the accent color will be used.\n\nThis is applied when the component has focus.\n\n```css\n--infinite-active-cell-background-alpha\n```\n\n### Active cell background alpha table unfocused\n\nSame as the above, but applied when the component does not have focus.\n\n```css\n--infinite-active-cell-background-alpha--table-unfocused\n```\n\n### Active cell background\n\nThe background color of the active cell.\n\nIf not specified, it will default to `activeBorderColor` with the opacity of `activeBackgroundAlpha`.\nIf `activeBorderColor` is not specified, it will default to the accent color, with the same opacity as mentioned.\n\nHowever, specify this to explicitly override the default.\n\n```css\n--infinite-active-cell-background\n```\n\n### Active cell background default\n\n\n\n```css\n--infinite-active-cell-background-default\n```\n\n### Active cell border color\n\nThe color for border of the active cell (when cell keyboard navigation is enabled).\n\n```css\n--infinite-active-cell-border-color\n```\n\n### Active cell border width\n\nThe width of the border for the active cell.\n\n```css\n--infinite-active-cell-border-width\n```\n\n### Active cell border style\n\nThe style of the border for the active cell (eg: 'solid', 'dashed', 'dotted') - defaults to 'dashed'.\n\n```css\n--infinite-active-cell-border-style\n```\n\n### Active cell border\n\nSpecifies the border for the active cell. Defaults to `var(--infinite-active-cell-border-width) var(--infinite-active-cell-border-style) var(--infinite-active-cell-border-color)`.\n\n```css\n--infinite-active-cell-border\n```\n\n### Selection checkbox margin inline\n\n\n\n```css\n--infinite-selection-checkbox-margin-inline\n```\n\n### Menu background\n\n\n\n```css\n--infinite-menu-background\n```\n\n### Menu color\n\n\n\n```css\n--infinite-menu-color\n```\n\n### Menu padding\n\n\n\n```css\n--infinite-menu-padding\n```\n\n### Menu cell padding vertical\n\n\n\n```css\n--infinite-menu-cell-padding-vertical\n```\n\n### Menu cell padding horizontal\n\n\n\n```css\n--infinite-menu-cell-padding-horizontal\n```\n\n### Menu cell margin vertical\n\n\n\n```css\n--infinite-menu-cell-margin-vertical\n```\n\n### Menu item disabled background\n\n\n\n```css\n--infinite-menu-item-disabled-background\n```\n\n### Menu item active background\n\n\n\n```css\n--infinite-menu-item-active-background\n```\n\n### Menu item active opacity\n\n\n\n```css\n--infinite-menu-item-active-opacity\n```\n\n### Menu item pressed opacity\n\n\n\n```css\n--infinite-menu-item-pressed-opacity\n```\n\n### Menu item pressed background\n\n\n\n```css\n--infinite-menu-item-pressed-background\n```\n\n### Menu item disabled opacity\n\n\n\n```css\n--infinite-menu-item-disabled-opacity\n```\n\n### Menu border radius\n\n\n\n```css\n--infinite-menu-border-radius\n```\n\n### Menu shadow color\n\n\n\n```css\n--infinite-menu-shadow-color\n```\n\n### Rowdetail background\n\n\n\n```css\n--infinite-rowdetail-background\n```\n\n### Rowdetail padding\n\n\n\n```css\n--infinite-rowdetail-padding\n```\n\n### Rowdetail grid height\n\n\n\n```css\n--infinite-rowdetail-grid-height\n```\n\n### Row background\n\nBackground color for rows. Defaults to [`--infinite-background`](#background).\n\nOverriden in `dark` theme.\n\n```css\n--infinite-row-background\n```\n\n### Row odd background\n\nBackground color for odd rows. Even rows will use [`--infinite-row-background`](#row-background).\n\nOverriden in `dark` theme.\n\n```css\n--infinite-row-odd-background\n```\n\n### Row selected background\n\n\n\n```css\n--infinite-row-selected-background\n```\n\n### Active row background\n\nThe background color of the active row. Defaults to the value of `var(--infinite-active-cell-background)`.\n\nHowever, specify this to explicitly override the default.\n\n```css\n--infinite-active-row-background\n```\n\n### Active row border color\n\nThe border color for the active row. Defaults to the value of `var(--infinite-active-cell-border-color)`.\n\n```css\n--infinite-active-row-border-color\n```\n\n### Active row border width\n\nThe width of the border for the active row. Defaults to the value of `var(--infinite-active-cell-border-width)`.\n\n```css\n--infinite-active-row-border-width\n```\n\n### Active row border style\n\nThe style of the border for the active row (eg: 'solid', 'dashed', 'dotted') - defaults to the value of `var(--infinite-active-cell-border-style)`, which is `dashed` by default.\n\n```css\n--infinite-active-row-border-style\n```\n\n### Active row border\n\nSpecifies the border for the active row. Defaults to `var(--infinite-active-row-border-width) var(--infinite-active-row-border-style) var(--infinite-active-row-border-color)`.\n\n```css\n--infinite-active-row-border\n```\n\n### Active row background alpha\n\nThe opacity of the background color for the active row (when row keyboard navigation is enabled).\nWhen you explicitly specify `--infinite-active-row-background`, this variable will not be used.\nInstead, this variable is used when the active row background uses the color of the active cell (border).\n\nThis is applied when the component has focus.\n\nDefaults to the value of `var(--infinite-active-cell-background-alpha)`.\n\n```css\n--infinite-active-row-background-alpha\n```\n\n### Active row background alpha table unfocused\n\nSame as the above, but applied when the component does not have focus.\n\nWhen you explicitly specify `--infinite-active-row-background`, this variable will not be used.\nInstead, this variable is used when the active row background uses the color of the active cell (border).\n\nDefaults to the value of `var(--infinite-active-cell-background-alpha--table-unfocused)`.\n\n```css\n--infinite-active-row-background-alpha--table-unfocused\n```\n\n### Row hover background\n\nBackground color for rows, on hover.\n\nOverriden in the `dark` theme.\n\n```css\n--infinite-row-hover-background\n```\n\n### Row selected hover background\n\n\n\n```css\n--infinite-row-selected-hover-background\n```\n\n### Group row background\n\n\n\n```css\n--infinite-group-row-background\n```\n\n### Group row column nesting\n\n\n\n```css\n--infinite-group-row-column-nesting\n```\n\n### Row pointer events while scrolling\n\n\n\n```css\n--infinite-row-pointer-events-while-scrolling\n```{/* END VARS */}\n" - }, - "/docs/learn/theming/": { - "filePath": "/docs/learn/theming/index", - "routePath": "/docs/learn/theming/", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/learn/theming/index.page.md", - "fileName": "index.page.md", - "folderPath": "/docs/learn/theming/", - "frontmatter": { - "title": "Theming", - "description": "Read our docs on the available themes and how you can customize the look and feel of InfiniteTable for React." - }, - "excerpt": "`` ships with a CSS file that you need to import in your codebase to make the component look as intended.", - "readingTime": "3 min read", - "content": "\n`` ships with a CSS file that you need to import in your codebase to make the component look as intended.\n\n```ts\nimport '@infinite-table/infinite-react/index.css';\n```\n\nThis root CSS file includes the `\"default\"` theme.\n\nThe other available themes are\n\n - `shadcn`\n - `minimalist`\n - `ocean`\n - `balsam`\n\nand if you want to use any of them, you have to import their respective CSS file explicitly:\n\n```ts\nimport '@infinite-table/infinite-react/theme/shadcn.css'\nimport '@infinite-table/infinite-react/theme/balsam.css'\nimport '@infinite-table/infinite-react/theme/minimalist.css'\nimport '@infinite-table/infinite-react/theme/ocean.css'\n```\n\nEach theme CSS file includes both the **`light`** and the **`dark`** modes.\n\n\n\nVersion `6.2.0` is the first version where the root CSS file (`@infinite-table/infinite-react/index.css`) doesn't include all the themes. Previous to this version, simply importing the root CSS file gave you access to all available themes.\n\nSplitting each theme into a dedicated CSS file helps reduce the bundle size for our users, as most people will only use one theme for `` in their apps.\n\n\n\n## Applying a theme\n\nThe following themes are currently available:\n\n - `default` - applied by default, no special configuration needed. It's included in the root CSS you need to import from `@infinite-table/infinite-react/index.css`\n - `balsam`\n - `minimalist`\n - `ocean`\n - `shadcn` - for this theme to correctly show up, make sure the shadcn CSS vars are available on page - see [shadcn theming](https://ui.shadcn.com/docs/theming) for details\n\n\nTo apply a theme (except the default one), you have to set the className `\"infinite-theme-name--THEME_NAME\"` to any parent element of the `` component (or even on the component itself).\n\nYou will want to apply the theme name and theme mode classNames to the same element, so you'll end up with a className like `\"infinite-theme-name--minimalist infinite-theme-mode--dark\"`.\n\n```tsx title=\"Applying the minimalist theme with dark mode explicitly\"\n\n \n \n \n\n```\n\n\n\n\n\n\n\nExample configured with `minimalist` theme and `dark` mode by default.\n\n\n```tsx live title=\"Theme switching demo - defaults to minimalist theme in dark mode\" size=\"md\" viewMode=\"preview\" files=\"theme-switching-minimalist-theme-default-example.page.tsx,columns.ts\"\n```\n\n\n\n\n## Theme mode - light or dark\n\nAt runtime, the `light` or `dark` mode is applied based on the user OS settings for the [preferred color scheme](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme).\n\nTo explicitly apply the light mode, apply the className `\"infinite-theme-mode--light\"` to any parent element of the `` component.\n\nTo explicitly apply the dark mode, apply the className `\"infinite-theme-mode--dark\"` to any parent element of the `` component.\n\n```tsx title=\"Explicitly applying light mode via container className\"\n
\n \n \n \n
\n```\n\nIf instead you specify a `infinite-theme-mode--dark` CSS className, the dark mode will be applied\n\n```tsx title=\"Explicitly applying dark theme via container className\"\n\n
\n \n \n \n
\n\n```\n\n\n\nExample configured with `default` theme and `light` mode by default.\n\n\n```tsx live title=\"Theme switching demo - defaults to light theme\" size=\"md\" viewMode=\"preview\" files=\"theme-switching-example.page.tsx,columns.ts\"\n\n```\n\n\n\nIf you don't explicitly have a `infinite-theme-mode--light` or `infinite-theme-mode--dark` ancestor, `InfiniteTable` will use the browser/OS preference (via `@media (prefers-color-scheme: ...)`) to apply the dark or light theme.\n\n\n\n## Available themes\n\n### Default theme\n\nThe `default` theme is applied when you don't specify any explicit theme by default.\n\n### Minimalist theme\n\nThe `minimalist` theme is inspired from minimalistic designs and is a good choice if you want to keep the UI simple and clean." - }, "/docs/learn/tree-grid/overview": { "filePath": "/docs/learn/tree-grid/overview", "routePath": "/docs/learn/tree-grid/overview", @@ -2903,7 +2933,7 @@ }, "excerpt": "When rendering the `InfiniteTable` component, you can get access to the API by getting it from the callback prop.", "readingTime": "8 min read", - "content": "\nWhen rendering the `InfiniteTable` component, you can get access to the API by getting it from the callback prop.\n\n```tsx {2}\nconst onReady = (\n {api, dataSourceApi}: {\n api: InfiniteTableApi,\n dataSourceApi: DataSourceApi\n }) => {\n // api is accessible here\n // you may want to store a reference to it in a ref or somewhere in your app state\n}\n\n\n columns={[...]}\n onReady={onReady}\n/>\n```\n\nFor API on row/group selection, see the [Selection API page](/docs/reference/selection-api).\n\nSee the [Infinite Table Cell Selection API page](/docs/reference/cell-selecti-api) for the cell selection API.\nSee the [Infinite Table Column API page](/docs/reference/column-api) for the column API.\nSee the [Infinite Table Keyboard Navigation API page](/docs/reference/keyboard-navigation-api) for the keyboard navigation API.\n\nSee the [Infinite Table Row Details API page](/docs/reference/row-detail-api) for the row detail API (when master-detail is configured).\nSee the [Tree API page](/docs/reference/tree-api) for the tree API (when using the `` component).\n\n\n\n void\">\n\n> Confirms the current edit operation and closes the editor.\n\nIf the `value` parameter is provided, it will be used as the value the cell will be updated with. If the `value` parameter is not provided, the current value of the cell will be used.\n\nSee related and .\n\n\n\n void\">\n\n> Cancels the current edit operation and closes the editor.\n\nSee related and .\n\n\n\n void\">\n\n> Hides the context menu that's currently displayed (if there's one).\n\n\n\n void\">\n\n> Rejects the current edit operation with the specified error and closes the editor.\n\nThe error will later be available to the callback prop, via the parameter of the function (also applicable for related functions that are called with same the same parameter).\n\nSee related and .\n\n\n\n void\">\n\n> Clears any filter for the specified column\n\n\n\n void\">\n\n> Toggles the sorting for the specified column.\n\nThis is the same method the component uses internally when the user clicks a column header.\n\nIf the column is not sorted, it gets sorted in ascending order.\n\nIf the column is sorted in ascending order, it gets sorted in descending order.\n\nIf the column is sorted in descending order, the sorting is cleared.\n\n\n\nThe `options` is optional and can have the `multiSortBehavior` property, which can be either `append` or `replace`. See related prop. If not provided, the default behavior is used.\n\n\n\nSee related and .\n\n\n\n void\">\n\n> Sets the sorting for the specified column.\n\nThe sort direction is specified by the `dir` parameter, which can be:\n\n- `1` for ascending\n- `-1` for descending\n- `null` for clearing the sorting.\n\nSee related and .\n\n\n\n 1|-1|null\">\n\n> Returns the sorting currently applied to the specified column.\n\nThe return value is:\n\n- `1` for ascending\n- `-1` for descending\n- `null` for no sorting.\n\nSee related and .\n\n\n\n boolean\">\n\n> Collapses the specified group row. Returns true if the group was expanded and is now being collapsed.\n\n```tsx\napi.collapseGroupRow(['USA', 'New York']); // collapses the group with these keys\n```\n\n\n\n boolean\">\n\n> Expands the specified group row. Returns true if the group was collapsed and is now being expanded.\n\n```tsx\napi.expandGroupRow(['USA', 'New York']); // expands the group with these keys\n```\n\n\n\n any\">\n\n> Returns the value for the specified cell. The value is either the raw value (as retrieved via the `field` property of the column or by calling the column valueGetter) or the formatted value - if the column has a valueFormatter.\n\nCall this function with an object that has a `columnId` and either a `rowIndex` or a `primaryKey` property.\n\nSee related .\n\n\n\nThis function should not be called during a cell render (eg: in / or other functions called during render).\n\n\n\n\n\n ({value, rawValue, formattedValue })\">\n\n> Returns an object with raw and formatted values for the specified cell.\n\nCall this function with an object that has a `columnId` and either a `rowIndex` or a `primaryKey` property.\n\nThe returned object has the following properties:\n\n- `rawValue` - the raw value of the cell - as retrieved from the property of the column or by calling the column valueGetter\n- `formattedValue` - the formatted value of the cell - if the column has a valueFormatter, it's the value returned by the formatter, otherwise it's the same as the `rawValue`\n- `value` - it's either `formattedValue` or `rawValue`. If the column has a valueFormatter, it's the value returned by the formatter, otherwise it's the `rawValue`\n\nSee related .\n\n\n\nThis function should not be called during a cell render (eg: in / or other functions called during render).\n\n\n\n\n\n InfiniteTableColumnAPI\">\n\n> Returns [a column API object](/docs/reference/column-api) bound to the specified column\n\nThe parameter can be either a column id or a column index (note this is not the index in all columns, but rather the index in current visible columns).\n\n\n\n { renderStartIndex, renderEndIndex }\">\n\n> Returns the vertical render range of the table\n\nThe vertical render range is the range of rows that are currently rendered in the table viewport.\n\n\n\n void\">\n\n> Called when the table has been layed out and sized and is ready to be used.\n\nThis callback prop will be called with an object containing the `api` (which is an instance of `InfiniteTableApi`) and [`dataSourceApi`](/docs/reference/datasource-api) objects.\n\n\n\n Promise\">\n\n> Tries to start editing the cell specified by the given row index and column id.\n\nReturns a promise that resolves to `true` if editing was started, or `false` if editing was not started because the cell is not editable.\n\nSee for more details on how to configure a cell as editable.\n\n\n\n```ts file=\"api-start-edit-example.page.tsx\"\n\n```\n\n\n\n\n\n boolean\">\n\n> Can be used to scroll a cell into the visible viewport\n\nIf scrolling was successful and the row and column combination was found, it returns `true`, otherwise `false`. The first arg of the function is the row index, while the second one is the column id or the column index (note this is not the index in all columns, but rather the index in current visible columns).\n\n\n\n boolean\">\n\n> Can be used to scroll a column into the visible viewport\n\nIf scrolling was successful and the column was found, it returns `true`, otherwise `false`.\nThe only parameter of this method is the column id.\n\n\n\n|setter\">\n\n> Gets or sets the `scrollLeft` value in the grid viewport\n\nCan be used as either a setter, to set the scroll left position or a getter to read the scroll left position.\n\n```ts\n// use as setter - will scroll the table viewport\napi.scrollLeft = 200;\n\n// use as getter to read the current scroll left value\nconst scrollLeft = api.scrollLeft;\n```\n\n\n\n boolean\">\n\n> Can be used to scroll a row into the visible viewport\n\nIf scrolling was successful and the row was found, it returns `true`, otherwise `false`\n\n\n\n|setter\">\n\n> Gets or sets the `scrollTop` value in the grid viewport\n\nCan be used as either a setter, to set the scroll top position or a getter to read the scroll top position.\n\n```ts\n// use as setter - will scroll the table viewport\napi.scrollTop = 1200;\n\n// use as getter to read the current scroll top value\nconst scrollTop = api.scrollTop;\n```\n\n\n\n\n\n> Getter for the [Row Selection API](/docs/reference/row-selection-api)\n\n\n\n\n\n> Getter for the [Row Detail API](/docs/reference/row-detail-api)\n\n\n\n\n> Getter for the [Cell Selection API](/docs/reference/cell-selection-api)\n\n\n\nvoid\">\n\n> Sets a filter value for the specified column\n\n\n\n void\">\n\n> Set the column order.\n\nIf `true` is specified, it resets the column order to the order the columns are specified in the prop (the iteration order of that object).\n\n```ts\napi.setColumnOrder(['id', 'firstName', 'age']);\n// restore default order\napi.setColumnOrder(true);\n```\n\n\n\n void\">\n\n> Toggles the collapse/expand state of the specified group row\n\n```tsx\napi.toggleGroupRow(['USA', 'New York']); // toggle the group with these keys\n```\n\n\n\n\n" + "content": "\nWhen rendering the `InfiniteTable` component, you can get access to the API by getting it from the callback prop.\n\n```tsx {2}\nconst onReady = (\n {api, dataSourceApi}: {\n api: InfiniteTableApi,\n dataSourceApi: DataSourceApi\n }) => {\n // api is accessible here\n // you may want to store a reference to it in a ref or somewhere in your app state\n}\n\n\n columns={[...]}\n onReady={onReady}\n/>\n```\n\nFor API on row/group selection, see the [Selection API page](/docs/reference/selection-api).\n\nSee the [Infinite Table Cell Selection API page](/docs/reference/cell-selection-api) for the cell selection API.\nSee the [Infinite Table Column API page](/docs/reference/column-api) for the column API.\nSee the [Infinite Table Keyboard Navigation API page](/docs/reference/keyboard-navigation-api) for the keyboard navigation API.\n\nSee the [Infinite Table Row Details API page](/docs/reference/row-detail-api) for the row detail API (when master-detail is configured).\nSee the [Tree API page](/docs/reference/tree-api) for the tree API (when using the `` component).\n\n\n\n void\">\n\n> Confirms the current edit operation and closes the editor.\n\nIf the `value` parameter is provided, it will be used as the value the cell will be updated with. If the `value` parameter is not provided, the current value of the cell will be used.\n\nSee related and .\n\n\n\n void\">\n\n> Cancels the current edit operation and closes the editor.\n\nSee related and .\n\n\n\n void\">\n\n> Hides the context menu that's currently displayed (if there's one).\n\n\n\n void\">\n\n> Rejects the current edit operation with the specified error and closes the editor.\n\nThe error will later be available to the callback prop, via the parameter of the function (also applicable for related functions that are called with same the same parameter).\n\nSee related and .\n\n\n\n void\">\n\n> Clears any filter for the specified column\n\n\n\n void\">\n\n> Toggles the sorting for the specified column.\n\nThis is the same method the component uses internally when the user clicks a column header.\n\nIf the column is not sorted, it gets sorted in ascending order.\n\nIf the column is sorted in ascending order, it gets sorted in descending order.\n\nIf the column is sorted in descending order, the sorting is cleared.\n\n\n\nThe `options` is optional and can have the `multiSortBehavior` property, which can be either `append` or `replace`. See related prop. If not provided, the default behavior is used.\n\n\n\nSee related and .\n\n\n\n void\">\n\n> Sets the sorting for the specified column.\n\nThe sort direction is specified by the `dir` parameter, which can be:\n\n- `1` for ascending\n- `-1` for descending\n- `null` for clearing the sorting.\n\nSee related and .\n\n\n\n 1|-1|null\">\n\n> Returns the sorting currently applied to the specified column.\n\nThe return value is:\n\n- `1` for ascending\n- `-1` for descending\n- `null` for no sorting.\n\nSee related and .\n\n\n\n boolean\">\n\n> Collapses the specified group row. Returns true if the group was expanded and is now being collapsed.\n\n```tsx\napi.collapseGroupRow(['USA', 'New York']); // collapses the group with these keys\n```\n\n\n\n boolean\">\n\n> Expands the specified group row. Returns true if the group was collapsed and is now being expanded.\n\n```tsx\napi.expandGroupRow(['USA', 'New York']); // expands the group with these keys\n```\n\n\n\n any\">\n\n> Returns the value for the specified cell. The value is either the raw value (as retrieved via the `field` property of the column or by calling the column valueGetter) or the formatted value - if the column has a valueFormatter.\n\nCall this function with an object that has a `columnId` and either a `rowIndex` or a `primaryKey` property.\n\nSee related .\n\n\n\nThis function should not be called during a cell render (eg: in / or other functions called during render).\n\n\n\n\n\n ({value, rawValue, formattedValue })\">\n\n> Returns an object with raw and formatted values for the specified cell.\n\nCall this function with an object that has a `columnId` and either a `rowIndex` or a `primaryKey` property.\n\nThe returned object has the following properties:\n\n- `rawValue` - the raw value of the cell - as retrieved from the property of the column or by calling the column valueGetter\n- `formattedValue` - the formatted value of the cell - if the column has a valueFormatter, it's the value returned by the formatter, otherwise it's the same as the `rawValue`\n- `value` - it's either `formattedValue` or `rawValue`. If the column has a valueFormatter, it's the value returned by the formatter, otherwise it's the `rawValue`\n\nSee related .\n\n\n\nThis function should not be called during a cell render (eg: in / or other functions called during render).\n\n\n\n\n\n InfiniteTableColumnAPI\">\n\n> Returns [a column API object](/docs/reference/column-api) bound to the specified column\n\nThe parameter can be either a column id or a column index (note this is not the index in all columns, but rather the index in current visible columns).\n\n\n\n { renderStartIndex, renderEndIndex }\">\n\n> Returns the vertical render range of the table\n\nThe vertical render range is the range of rows that are currently rendered in the table viewport.\n\n\n\n void\">\n\n> Called when the table has been layed out and sized and is ready to be used.\n\nThis callback prop will be called with an object containing the `api` (which is an instance of `InfiniteTableApi`) and [`dataSourceApi`](/docs/reference/datasource-api) objects.\n\n\n\n Promise\">\n\n> Tries to start editing the cell specified by the given row index and column id.\n\nReturns a promise that resolves to `true` if editing was started, or `false` if editing was not started because the cell is not editable.\n\nSee for more details on how to configure a cell as editable.\n\n\n\n```ts file=\"api-start-edit-example.page.tsx\"\n\n```\n\n\n\n\n\n boolean\">\n\n> Can be used to scroll a cell into the visible viewport\n\nIf scrolling was successful and the row and column combination was found, it returns `true`, otherwise `false`. The first arg of the function is the row index, while the second one is the column id or the column index (note this is not the index in all columns, but rather the index in current visible columns).\n\n\n\n boolean\">\n\n> Can be used to scroll a column into the visible viewport\n\nIf scrolling was successful and the column was found, it returns `true`, otherwise `false`.\nThe only parameter of this method is the column id.\n\n\n\n|setter\">\n\n> Gets or sets the `scrollLeft` value in the grid viewport\n\nCan be used as either a setter, to set the scroll left position or a getter to read the scroll left position.\n\n```ts\n// use as setter - will scroll the table viewport\napi.scrollLeft = 200;\n\n// use as getter to read the current scroll left value\nconst scrollLeft = api.scrollLeft;\n```\n\n\n\n boolean\">\n\n> Can be used to scroll a row into the visible viewport\n\nIf scrolling was successful and the row was found, it returns `true`, otherwise `false`\n\n\n\n|setter\">\n\n> Gets or sets the `scrollTop` value in the grid viewport\n\nCan be used as either a setter, to set the scroll top position or a getter to read the scroll top position.\n\n```ts\n// use as setter - will scroll the table viewport\napi.scrollTop = 1200;\n\n// use as getter to read the current scroll top value\nconst scrollTop = api.scrollTop;\n```\n\n\n\n\n\n> Getter for the [Row Selection API](/docs/reference/row-selection-api)\n\n\n\n\n\n> Getter for the [Row Detail API](/docs/reference/row-detail-api)\n\n\n\n\n> Getter for the [Cell Selection API](/docs/reference/cell-selection-api)\n\n\n\nvoid\">\n\n> Sets a filter value for the specified column\n\n\n\n void\">\n\n> Set the column order.\n\nIf `true` is specified, it resets the column order to the order the columns are specified in the prop (the iteration order of that object).\n\n```ts\napi.setColumnOrder(['id', 'firstName', 'age']);\n// restore default order\napi.setColumnOrder(true);\n```\n\n\n\n void\">\n\n> Toggles the collapse/expand state of the specified group row\n\n```tsx\napi.toggleGroupRow(['USA', 'New York']); // toggle the group with these keys\n```\n\n\n\n\n" }, "/docs/reference/cell-selection-api/": { "filePath": "/docs/reference/cell-selection-api/index", @@ -2947,35 +2977,19 @@ "readingTime": "16 min read", "content": "\nWhen rendering the `DataSource` component, you can get access to the API by getting it from the callback prop.\n\n```tsx {3}\n\n onReady={(api: DataSourceApi) => {\n // api is accessible here\n // you may want to store a reference to it in a ref or somewhere in your app state\n }}\n/>\n```\n\nYou can also get it from the `InfiniteTable` callback prop:\n\n```tsx {4}\n\n columns={[...]}\n onReady={(\n {api, dataSourceApi}: {\n api: InfiniteTableApi,\n dataSourceApi: DataSourceApi\n }) => {\n\n // both api and dataSourceApi are accessible here\n }}\n/>\n```\n\nFor API on row/group selection, see the [Selection API page](/docs/reference/selection-api).\n\n\n\n boolean\">\n\n> Returns `true` if the row at the specified index is disabled, `false` otherwise.\n\nSee the prop for more information.\n\nFor checking if a row is disabled by its primary key, see the method.\n\nFor changing the enable/disable state for the row, see the .\n\n\n\n```ts file=\"../datasource-props/rowDisabledState-example.page.tsx\"\n```\n\n\n\n\n\n boolean\">\n\n> Returns `true` if the row with the specified primary key is disabled, `false` otherwise.\n\nSee the prop for more information.\n\nFor checking if a row is disabled by its index, see the method.\n\nFor changing the enable/disable state for the row, see the .\n\n\n\n```ts file=\"../datasource-props/rowDisabledState-example.page.tsx\"\n\n```\n\n\n\n\n\n void\">\n\n> Sets the enable/disable state for the row with the specified primary key.\n\nSee the prop for more information.\n\nFor setting the enable/disable state for a row by its index, see the method.\n\n\n\n```ts file=\"../datasource-props/rowDisabledState-example.page.tsx\"\n\n```\n\n\n\n\n\n\">\n\n> A reference to the [Tree API](/docs/reference/tree-api).\n\nWhen using the `` component, this property will be available on the `DataSourceApi` instance.\n\n\n void\">\n\n> Sets the enable/disable state for the row at the specified index.\n\nSee the prop for more information.\n\nFor setting the enable/disable state for a row by its primary key, see the method.\n\n\n\n```ts file=\"../datasource-props/rowDisabledState-example.page.tsx\"\n\n```\n\n\n\n\n\n Promise\">\n\n> Replaces all data in the DataSource with the provided data.\n\nClears the current data array in the `` and replaces it with the provided data.\n\nWhen calling this, if there are pending data mutations, they will be discarded.\n\nSee related method.\n\n\n\n Promise\">\n\n> Clears all data in the DataSource.\n\nSee related method.\n\n\n\n Promise\">\n\n> Adds the specified data at the end of the data source.\n\nThe given data param should be of type `DATA_TYPE` (the TypeScript generic data type that the `DataSource` was bound to).\n\nFor adding an array of data, see the method.\n\n\n\nIf the component has sorting, the added data might not be displayed at the end of the data source.\n\n\n\nThis method batches data updates and waits for a request animation frame until it persists the data to the `DataSource`. This means you can execute multiple calls to `addData` (or , , ) in the same frame and they will be batched and persisted together.\n\nThe return value is a `Promise` that resolves when the data has been added. When multiple `addData` (and friends) calls are executed in the same frame, the result of those calls is a reference to the same promise.\n\n```ts\nconst promise1 = dataSourceApi.add({ ... })\nconst promise2 = dataSourceApi.add({ ... })\nconst promise3 = dataSourceApi.insertData({ ... }, { position: 'before', primaryKey: 4 })\n\n// promise1, promise2 and promise3 are the same promise\n// as the calls are run in the same raf and batched together\n// promise1 === promise2\n// promise1 === promise3\n```\n\nFor adding an array of data, see the method.\nFor inserting data at a specific position, see the method.\n\n allows you to listen to data mutations.\n\n\n\n```ts file=\"addData-example.page.tsx\"\n\n```\n\n\n\n\n\n Promise\">\n\n> Adds an array of data at the end of the data source\n\nSee related method.\n\nFor adding at the beginning of the data source, see the method.\n\n allows you to listen to data mutations.\n\n\n\n DATA_TYPE | null\">\n\n> Retrieves the data object for the specified node path.\n\n\n\n```tsx file=\"tree-getDataByNodePath-example.page.tsx\"\n```\n\n\n\n\n\n DATA_TYPE | null\">\n\n> Retrieves the data object for the specified primary key.\n\nYou can call this method to retrieve objects from the data source even when they have been filtered out via or , as long as they are present in the initial data.\n\n\n\nThe alternative API method can only be used to retrieve row info objects of rows that are not filtered out - so only rows that match the filtering, if one is present.\n\n\n\n\n\n number\">\n> Retrieves the index of a row by its primary key. If the row is not found, returns `-1`. See related \n\n\n\nThe primary key you pass in needs to exist in the current data set. If you pass in a primary key that has been filtered out or that's not in the data set, the method will return `-1`.\n\n\n\n\n\n any | undefined \">\n\n> Retrieves the primary key of a row by its current index. If the row is not found, returns `undefined`. See related \n\n\n\nThe index needs to be of an existing row, after all filtering is applied. If you pass in an non-existent index, the method will return `undefined`.\n\n\n\n\n\n\n DATA_TYPE | null\">\n\n> Retrieves the data object for the node with the specified path. If the node is not found, returns `null`. See related . See related .\n\n\n\nThe node path needs to be of an existing node. If you pass in a non-existent (or filtered out) node path, the method will return `null`.\n\n\n\n\n\n```tsx file=\"tree-getDataByNodePath-example.page.tsx\"\n```\n\n\n\n\n\n\n InfiniteTableRowInfo | null\">\n\n> Retrieves the row info object for the node with the specified path. If the node is not found, returns `null`. See related .\n\n\n\nThe node path needs to be of an existing node. If you pass in a non-existent (or filtered out) node path, the method will return `null`.\n\n\n\n\n\n\n InfiniteTableRowInfo | null\">\n\n> Retrieves the row info object for the row at the specified index. If none found, returns `null`. See related .\n\n\n\n InfiniteTableRowInfo | null\">\n\n> Retrieves the row info object for the row with the specified primary key. If none found, returns `null`.\n\nThis method will only find row info objects for rows that are currently in the dataset and matching the filtering, if one is present. Can also be called for group rows.\n\n\n\nSee related method, which retrieves the raw data object for the specified primary key, even if it has been filtered out.\n\n\n\n\n\n InfiniteTableRowInfo[]\">\n\n> Returns the current row info array. See the type definition of the row info object.\n\nThe row info array represents the current state of the DataSource. This array may contain more items than the actual data array fetched initially by the DataSource. This is because it includes group rows, when grouping is defined, as well as unfetched rows in some advanced scenarios.\n\n\n\n Promise\">\n\n> Inserts the given data at the specified position relative to the given primary key.\n\nThe `position` can be one of the following:\n\n- `start` | `end` - inserts the data at the beginning or end of the data source. In this case, no `primaryKey` is needed.\n- `before` | `after` - inserts the data before or after the data item that has the specified primary key. **In thise case, the `primaryKey` is required.**\n\nWe're intentionally not encouraging inserting at a specified `index`, as the index of rows in the visible viewport can change as the user sorts, filters or groups the data.\n\nFor inserting an array of data, see the method.\n\n allows you to listen to data mutations.\n\n\n\n\n\nClick any row in the table to make it the current active row, and then use the second button to add a new row after the active row.\n\n\n\n```ts file=\"insert-example.page.tsx\"\n\n```\n\n\n\n\n\n Promise\">\n\n> Inserts an array of data at the specified position (and relative to the given primary key or node path).\n\nJust like the method, the `position` can be one of the following:\n\n- `start` | `end` - inserts the data at the beginning or end of the data source. In this case, no `primaryKey` is needed. If `nodePath` is provided, `\"start\"` means insert the data at the start of the children array of that node; `\"end\"` means insert the data at the end of the children array of that node.\n- `before` | `after` - inserts the data before or after the data item that has the specified primary key / node path. **In thise case, the `primaryKey` or the `nodePath` is required.**\n\nAll the data items passed to this method will be inserted (in the order in the array) at the specified position.\n\n\n\nWhen using this method for tree nodes, `waitForNode` defaults to `true` (you can specify a boolean value, or a number to override the default timeout). When `waitForNode` is `true`, the method will insert the data in the respective node's children array, after making sure the node exists.\n\n\n\n allows you to listen to data mutations.\n\n\n\n Promise\">\n\n> Updates the data for the nodes specified by the node paths.\n\nThe first parameter should be an array of objects, where each object has a `data` property (the data to update) and a `nodePath` property (the path of the node to update).\n\n\n```tsx title=\"Updating an aray of tree nodes\"\ndataSourceApi.updateDataArrayByNodePath([\n {\n data: {\n fileName: 'Vacation.pdf',\n sizeInKB: 1000,\n },\n nodePath: ['1', '10'],\n },\n {\n data: {\n fileName: 'Report.docx',\n sizeInKB: 2000,\n },\n nodePath: ['1', '11'],\n },\n]);\n```\n\n\n\n\n DATA_TYPE[] | any, nodePath: NodePath) => Promise, options?\">\n\n> Updates the children of the node specified by the node path.\n\nThe first parameter can be an array (or `null` or `undefined`) or a function that returns an array (or `null` or `undefined`).\n\nThe second parameter is the node path.\n\nWhen a function is passed as the first parameter, it will be called with the children of the node. The return value will be used as the new children of the node. This gives you an opportunity to update the children based on the current children state.\n\n```tsx title=\"Updating the children of a tree node\"\ndataSourceApi.updateChildrenByNodePath(\n (currentChildren) => {\n return [\n ...(currentChildren || []),\n {\n name: 'untitled.txt',\n id: '8',\n },\n ];\n },\n ['1', '3'],\n);\n```\n\n\n\nWhen using a function, it will be called with the current children of the node (1st parameter) and also with the node data object (2nd parameter).\n\n\n\n\n\nAs a third parameter, you can pass in an options object, which supports the `waitForNode` property (`boolean` or `number` to override the default timeout). When `waitForNode` is `true`, the method will update the children of the node, after making sure the node exists (and waiting for the specified timeout if needed).\n\n\n\n\n\n Promise\">\n\n> Returns a promise that tells if the node path exists.\n\nCalling this method will give you a promise that will tell you if the node path exists or not.\n\nIf the `DataSource` can find the node path either immediately or before the specified timeout expires, the promise will resolve to `true`, otherwise it will resolve to `false`.\n\nIf no `timeout` is specified, it will default to `1000`ms.\n\n\n\n, nodePath: NodePath, options?) => Promise\">\n\n> Updates the data for the node specified by the node path.\n\n\n\nIf the primary keys in your `` are unique globally (not just within the same node), you can still use the method.\n\n\n\n\n```tsx title=\"Updating a tree node by path\"\ndataSourceApi.updateDataByNodePath({\n fileName: 'New Name',\n sizeInKB: 1000,\n}, ['1', '10']);\n```\n\n\n\n```ts file=\"tree-updateDataByPath-example.page.tsx\"\n```\n\n\n\n\n\nThe third parameter is an options object, which can have a `waitForNode` property (either `boolean` or `number` - use a `number` to override the default timeout). NOTE: if you don't pass it, it defaults to `1000ms`.\n\nWhen `waitForNode` is used, if the node does not exist yet, it will wait for the specified timeout and then update the data.\n\n\n\n\n\n Promise\">\n\n> Removes the node specified by the specified node path.\n\n\n\nIf the primary keys in your `` are unique globally (not just within the same node), you can still use the method.\n\n\n\n\n```tsx title=\"Removing a tree node by path\"\ndataSourceApi.removeDataByNodePath(['1', '10']);\n```\n\n\n\n```ts file=\"tree-removeDataByNodePath-example.page.tsx\"\n```\n\n\n\n\n\n, options?) => Promise\">\n\n> Updates the data item to match the given data object. For updating tree nodes by path, see the method.\n\n\n\nThe data object must have a primary key that matches the primary key of the data item that you want to update. Besides the primary key, it can contain any number of properties that you want to update.\n\n\n\n```ts\ndataSourceApi.updateData({\n // if the primaryKey is the id, make sure to include it\n id: 1,\n\n // and then include any properties you want to update - in this case, the name and age\n name: 'John Doe',\n age: 30,\n});\n```\n\n\n\n```ts file=\"simple-updateData-example.page.tsx\"\n```\n\n\n\nFor updating an array of data, see the method.\n\n\n\nThe second parameter is an options object, which can have a `waitForNode` property (either `boolean` or `number` - use a `number` to override the default timeout). NOTE: if you don't pass it, it defaults to `1000ms`.\n\nWhen `waitForNode` is used, if the node does not exist yet, it will wait for the specified timeout and then update the data.\n\n\n\n allows you to listen to data mutations.\n\n\n\n\n\nThe DataSource has 10k items.\n\nIn this example, we're updating 5 rows (in the visible viewport) every 30ms.\n\nThe update rate could be much higher, but we're keeping it at current levels to make it easier to see the changes.\n\n\n\n```ts file=\"live-updates-example.page.tsx\"\n\n```\n\n\n\n\n\n[], options?) => Promise\">\n\n> Updates an array of data items to match the given data objects.\n\nSee related method.\n\n allows you to listen to data mutations.\n\n\n\n void\">\n\n> Called only once, after the DataSource component has been mounted.\n\nThis callback prop will be called with an `DataSourceApi` instance. For retrieving the [`InfiniteTableApi`](/docs/reference/api), see the `InfiniteTable` callback prop.\n\n\n\n) => Promise\">\n\n> Removes the data item that matches the given data object.\n\nThe data object must at least have a primary key that matches the primary key of the data item that you want to remove. All the other properties are ignored.\n\nFor removing an array of data, see the method.\n\nIf you only want to remove by a primary key, you can call instead.\nIf you have an array of primary keys, you can call instead.\n\n allows you to listen to data mutations.\n\n\n\n DATA_TYPE[]\">\n\n> Returns the data array that was last loaded by the `DataSource`\n\nThis is the array loaded by the `DataSource`, before any filtering, sorting or grouping is applied.\n\n\n\n[]) => Promise\">\n\n> Removes the data items that match the given data objects.\n\nThe data objects must at least have a primary key that matches the primary key of the data item that you want to remove. All the other properties are ignored.\n\nFor removing only one item, see the method.\n\nIf you only want to remove by a primary key, you can call instead.\nIf you have an array of primary keys, you can call instead.\n\n allows you to listen to data mutations.\n\n\n\n Promise\">\n\n> Removes the data items with the specified primary keys.\n\nFor removing only one data item, see the method.\n\nIf you have a data object, you can call instead.\nIf you have an array of data objects, you can call instead.\n\n allows you to listen to data mutations.\n\n\n\n Promise\">\n\n> Removes the data item with the specified primary key.\n\nFor removing an array of data, see the method.\n\nIf you have a data object, you can call instead.\nIf you have an array of data objects, you can call instead.\n\n allows you to listen to data mutations.\n\n\n\n" }, - "/docs/reference/datasource-props/": { - "filePath": "/docs/reference/datasource-props/index", - "routePath": "/docs/reference/datasource-props/", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/reference/datasource-props/index.page.md", - "fileName": "index.page.md", - "folderPath": "/docs/reference/datasource-props/", - "frontmatter": { - "title": "DataSource Props", - "layout": "API", - "description": "Props Reference page for your DataSource in Infinite Table - with complete examples" - }, - "excerpt": "In the API Reference below we'll use **`DATA_TYPE`** to refer to the TypeScript type that represents the data the component is bound to.", - "readingTime": "39 min read", - "content": "\nIn the API Reference below we'll use **`DATA_TYPE`** to refer to the TypeScript type that represents the data the component is bound to.\n\n\n\n string\">\n\n> The name of the id/primary key property of an item in the array. The value of this property needs to be unique.\n\nThis is probably one of the most important properties of the `` component, as it is used to identify items in the array.\n\n\n\nUnlike with other DataGrid components, with `InfiniteTable` you don't need to have a column mapped to the primary key field.\n\nThe primary key is used internally by the component and is not displayed in the grid if you don't explicitly have a column bound to that field.\n\n\n\nIf the primary key is not unique, Infinite Table DataGrid won't work properly.\n\n\n\n```ts file=\"data-example.page.tsx\"\n\n```\n\n\n\n\n\nThe primary key can be either a string (the name of a property in the data object), or a function that returns a string.\n\nUsing functions (for more dynamic primary keys) is supported, but hasn't been tested extensively - so please report any issues you might encounter.\n\n\n\n\n\n\n\n> Specifies the expand/collapse state of the tree nodes. See for the shape of this object. For the uncontrolled version, see .\n\nIf no `treeExpandState` prop is specified, the tree will be rendered as fully expanded by default.\n\nWhen using the controlled version, make sure to update the `treeExpandState` prop by using the callback.\n\n\n\n```ts file=\"tree-controlled-expandstate-example.page.tsx\"\n\n```\n\n\n\n\n\n void\">\n\n> Called when the tree expand state changes.\n\nWhen the user interacts with the tree (by expanding or collapsing a node), this callback is called with the new tree state.\n\nThe first parameter is the new tree state, and the second parameter is an object with the following properties:\n\n- `dataSourceApi` - the DataSource API instance\n- `nodePath` - the path of the node that changed state. If the state was produced by an or call, this will be `null`.\n- `nodeState` - the new state of the node (`\"collapsed\"` or `\"expanded\"`)\n\n\n\n\n```ts file=\"tree-controlled-expandstate-example.page.tsx\"\n\n```\n\n\n\n\n\n void\">\n\n> Called when a node is expanded. See related and props.\n\n\n\n\nThe and callbacks are called when a node is expanded or collapsed, respectively - either via user interaction or by an API call. However, they will not be called when the or methods are called.\n\n\n\n\n\n, treeExpandState: TreeExpandState) => boolean\">\n\n> Decides if the current (non-leaf) node is expanded. \n\nThe inverse prop, is also available. Only one of these props can be specified.\n\n\n\nIf this prop is specified, is ignored.\n\n\n\n\n\n, treeExpandState: TreeExpandState) => boolean\">\n\n> Decides if the current (non-leaf) node is collapsed. See related prop.\n\nThe inverse prop, is also available. Only one of these props can be specified.\n\n\n\nIf this prop is specified, is ignored.\n\n\n\n\n\n) => boolean\">\n\n> Decides if the current (non-leaf) node can be expanded or collapsed and if the tree icon is disabled.\n\nBy default, parent nodes with `children: []` are read-only, meaning they won't respond to expand/collapse clicks.\n\nHowever, if you specify a custom `isNodeReadOnly` function, you can change this behavior.\n\n\n\nWhen a node is read-only, the and methods need the `options.force` flag to be set to `true` in order to override the read-only restriction.\n\nHowever, and will work regardless of the `isNodeReadOnly` setting.\n\nFor full control over the expand/collapse state of read-only nodes, you can use the / props.\n\n\n\n\n\n```ts file=\"tree-isNodeReadOnly-example.page.tsx\"\n\n```\n\n\n\n\n void\">\n\n> Called when a node is collapsed. See related and props.\n\n\n\n\nThe and callbacks are called when a node is expanded or collapsed, respectively - either via user interaction or by an API call. However, they will not be called when the or methods are called.\n\n\n\n\n\n\n\n> The name of the property in the data object that contains the child nodes for each tree node.\n\nOnly available when you're using the `` component.\n\nIf not specified, it defaults to `\"children\"`.\n\n\n\nEach node gets a `nodePath` property, which is the array with the ids of all the parent nodes leading down to the current node. The node path includes the id of the current node\n\n\n\n\n```tsx {2} title=\"Node path vs row id\"\nconst data = [\n { id: '1', name: 'Documents', // path: ['1']\n children: [\n { id: '10', name: 'Private', // path: ['1', '10']\n children: [\n { id: '100', name: 'Report.docx' }, // path: ['1', '10', '100'] \n { id: '101', name: 'Vacation.docx' },// path: ['1', '10', '101']\n ],\n },\n ]\n },\n {\n id: '2',\n name: 'Downloads', // path: ['2']\n children: [\n {\n id: '20',\n name: 'cat.jpg', // path: ['2', '20']\n },\n ],\n },\n];\n```\n\n\n\n```ts file=\"tree-nodesKey-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n> Specifies the expand/collapse state of the tree nodes. See for the shape of this object. For the controlled version, see .\n\n\n\n\n\n```ts file=\"tree-uncontrolled-expandstate-example.page.tsx\"\n\n```\n\n\n\n\n\n\n) => boolean\">\n\n> This function ultimately decides the disabled state of a row. It overrides both / props.\n\nIt's called with a single argument - the row info object for the row in question.\n\nIt should return `true` if the row is disabled, and `false` otherwise.\n\n\n\nWhen this prop is used, will not be called.\n\n\n\n\n\n\n\n\n> The uncontrolled prop for managing row enabled/disabled state. For the controlled version see . For listening to row disabled state changes, see .\n\nThe value for this prop is an object with two properties:\n\n- `enabledRows` - either `true` or an array of row ids that are enabled. When `true` is passed, `disabledRows` should be an array of row ids that are disabled.\n- `disabledRows` - either `true` or an array of row ids that are disabled. When `true` is passed, `enabledRows` should be an array of row ids that are enabled.\n\n\n\nThe values in the `enabledRows`/`disabledRows` arrays are row ids, and not indexes.\n\n\n\n\n\n\nThis prop can be overriden by using the prop.\n\n\n\nHere's an example of how to use the `defaultRowDisabledState` prop:\n\n\n\n\n\nRows with ids `1`, `3`, `4` and `5` are disabled.\n\n\n\n```ts file=\"defaultRowDisabledState-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n> Manages row enabled/disabled state. For the uncontrolled version see . For listening to row disabled state changes, see .\n\nThe value for this prop is an object with two properties:\n\n- `enabledRows` - either `true` or an array of row ids that are enabled. When `true` is passed, `disabledRows` should be an array of row ids that are disabled.\n- `disabledRows` - either `true` or an array of row ids that are disabled. When `true` is passed, `enabledRows` should be an array of row ids that are enabled.\n\n\nWhen using this controlled prop, you will need to update the `rowDisabledState` prop by using the callback.\n\n\n\n\nThis prop can be overriden by using the prop.\n\n\n\n\n\n\n\nRows with ids `1`, `3`, `4` and `5` are disabled initially.\n\nRight click rows and use the context menu to enable/disable rows.\n\n\n\n```ts file=\"rowDisabledState-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n> Determines what nodes are selected and deselected. For the uncontrolled version see .\n\nThe value of this prop determines if a node is selected or not.\n\nSee for details on the shape of this object.\n\n\n\n\n```ts file=\"tree-controlled-selectedstate-example.page.tsx\"\n\n```\n\n\n\n\n\n void\">\n\n> Called when the tree selection changes. See .\n\n\n\n```ts file=\"tree-controlled-selectedstate-example.page.tsx\"\n\n```\n\n\n\nWhen using `multi-row` , the signature of this callback is:\n\n - `treeSelection` - the new tree selection state\n - `context` - an object with the following properties:\n - `selectionMode` - will be `\"multi-row\"`\n - `lastUpdatedNodePath` - the path of the node that was last updated (either via user action or api call). Will be `null` of the action that triggered this callback was or .\n - `dataSourceApi` - the [DataSource API](/docs/reference/datasource-api) instance\n\n\n\n\n\n> Determines what nodes are selected and deselected. For the controlled version see .\n\nThe value of this prop determines if a node is selected or not.\n\nSee for details on the shape of this object.\n\n\n\n\n```ts file=\"tree-uncontrolled-selectedstate-example.page.tsx\"\n\n```\n\n\n\n\n\n void\">\n\n> Called when the row disabled state changes.\n\nIt's called with just 1 argument (`rowDisabledState`), which is an instance of the `RowDisabledState` class. To get a literal object that represents the row disabled state, call the `rowDisabledState.getState()` method.\n\n```tsx {3,19}\nimport {\n DataSource,\n RowDisabledStateObject,\n} from '@infinite-table/infinite-react';\nfunction App() {\n const [rowDisabledState, setRowDisabledState] = React.useState<\n RowDisabledStateObject\n >({\n enabledRows: true,\n disabledRows: [1, 3, 4, 5],\n });\n return (\n <>\n \n data={data}\n primaryKey=\"id\"\n rowDisabledState={rowDisabledState}\n onRowDisabledStateChange={(rowState) => {\n setRowDisabledState(rowState.getState());\n }}\n />\n \n );\n}\n```\n\n\nWhen using the controlled prop, you will need to update the `rowDisabledState` by using this callback.\n\n\n\n\n\n\nRows with ids `1`, `3`, `4` and `5` are disabled initially.\n\nRight click rows and use the context menu to enable/disable rows.\n\n\n\n```ts file=\"rowDisabledState-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n\">\n\n> Specifies the functions to use for aggregating data. The object is a map where the keys are ids for aggregations and values are object of the shape described below.\n\nThe `DataSourceAggregationReducer` type can have the following properties\n\n- `initialValue` - type `any`, mandatory for client-side aggregations. It can be a function, in which case, it will be called to compute the initial value for the aggregation. Otherwise, the initial value will be used as is.\n- `field` - the field to aggregate on. Optional - if not specified, make sure you specify `getter`\n- `getter`: `(data:T)=> any` - a getter function, called with the current `data` object.\n- `reducer`: `string | (accumulator, value, data: T) => any` - either a string (for server-side aggregations) or a mandatory aggregation function for client-side aggregations.\n- `done`: `(accumulator, arr: T[]) => any` - a function that is called to finish the aggregation after all values have been accumulated. The function should return the final value of the aggregation. Only used for client-side aggregations.\n- `name` - useful especially in combination with , as it will be used as the pivot column header.\n- `pivotColumn` - if specified, will configure the pivot column generated for this aggregation. This object has the same shape as a normal column, but supports an extra `inheritFromColumn` property, which can either be a `string` (a column id), or a `boolean`. The default behavior for a pivot column is to inherit the configuration of the initial column that has the same `field` property. `inheritFromColumn` allows you to specify another column to inherit from, or, if `false` is passed, the pivot column will not inherit from any other column.\n\n\n\n```ts files=[\"groupBy-example.page.tsx\",\"columns.ts\"]\n\n```\n\n\n\nAggregation reducers can be used in combination with grouping and pivoting. The example below shows aggregations used with server-side pivoting\n\n\n\n```ts file=\"$DOCS/learn/grouping-and-pivoting/pivoting/remote-pivoting-example.page.tsx\"\n\n```\n\n\n\nPivot columns generated for aggregations will inehrit from initial columns - the example shows how to leverage this behavior and how to extend it\n\n\n\n```ts file=\"$DOCS/learn/grouping-and-pivoting/pivoting/pivot-column-inherit-example.page.tsx\"\n\n```\n\n\n\n\n\n DATA_TYPE[]|Promise\">\n\n> Specifies the data the component is bound to.\n\nCan be one of the following:\n\n- an array of the bound type - eg: `Employee[]`\n- a Promise tha resolves to an array like the above\n- a function that returns an any of the above\n\n\n\nIf the `data` prop is a function, it will be called with an object of type . Click to see more details.\n\n\n\n\n\n```ts file=\"data-example.page.tsx\"\n\n```\n\n\n\n\n\nIt's important to note you can re-fetch data by changing the reference you pass as the `data` prop to the `` component. Passing another `data` function, will cause the component to re-execute the function and thus load new data.\n\n\n\n\n\n```ts files=[\"$DOCS/learn/working-with-data/refetch-example.page.tsx\",\"$DOCS/learn/working-with-data/columns.ts\"]\n\n```\n\n\n\n\n\n\n\n> Uncontrolled prop used for filtering. Can be used for both [client-side](/docs/learn/filtering/filtering-client-side) and [server-side](/docs/learn/filtering/filtering-server-side) filtering.\n\nIf you want to show the column filter editors, you have to either specify this property, or the controlled - even if you have no initial filters. For no initial filters, use `defaultFilterValue=[]`.\n\nFor the controlled version, and more details on the shape of the objects in the array, see .\n\n\n\n```ts file=\"defaultFilterValue-example.page.tsx\"\n\n```\n\n\n\n\n\nYou can control the visibility of the column filters by using the prop.\n\n\n\n\n\n\n\n> Describes the selected row(s) in the `DataSource`\n\nSee more docs in the controlled version of this prop, \n\nFor single selection, the prop will be of type: `number | string | null`. Use `null` for empty selection in single selection mode.\n\nFor multiple selection, the prop will have the following shape:\n\n```ts\nconst rowSelection = {\n selectedRows: [3, 6, 100, 23], // those specific rows are selected\n defaultSelection: false, // all rows deselected by default\n};\n\n// or\nconst rowSelection = {\n deselectedRows: [3, 6, 100, 23], // those specific rows are deselected\n defaultSelection: true, // all other rows are selected\n};\n\n// or, for grouped data - this example assumes groupBy=continent,country,city\nconst rowSelection = {\n selectedRows: [\n 45, // row with id 45 is selected, no matter the group\n ['Europe', 'France'], // all rows in Europe/France are selected\n ['Asia'], // all rows in Asia are selected\n ],\n deselectedRows: [\n ['Europe', 'France', 'Paris'], // all rows in Paris are deselected\n ],\n defaultSelection: false, // all other rows are selected\n};\n```\n\nFor using group keys in the selection value, see related \n\n\n\n```ts file=\"$DOCS/reference/uncontrolled-multiple-row-selection-example.page.tsx\"\n\n```\n\n\n\n\n\n|DataSourceSingleSortInfo[]|null\">\n\n> Information for sorting the data. This is an uncontrolled prop.\n\nFor detailed explanations, see (controlled property).\n\n\n\nWhen you provide a `defaultSortInfo` prop and the sorting information uses a custom sortType, make sure you specify that as the `type` property of the sorting info object.\n\n```tsx\ndefaultSortInfo={{\n field: 'color',\n dir: 1,\n // note this custom sort type\n type: 'color',\n}}\n```\n\nYou will need to have a property for that type in your object as well.\n\n```tsx\nsortTypes={{\n color: (a, b) => //...\n}}\n```\n\n\n\n\n\n```ts file=\"$DOCS/learn/sorting/local-uncontrolled-single-sorting-example-with-remote-data.page.tsx\"\n\n```\n\n\n\n\n\n```ts file=\"./customSortType-with-uncontrolled-sortInfo-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n> The delay in milliseconds before the filter is applied. This is useful when you want to wait for the user to finish typing before applying the filter.\n\nThis is especially useful in order to reduce the number of requests sent to the server, when remote filtering is used.\n\nIf not specified, defaults to `200` milliseconds. This means, any changes to the column filters, that happen inside a 200ms window (or the current value of ), will be debounced and only the last value will be sent to the server.\n\n\n\nIf you want to prevent debouncing/batching filter values, you can set to `0`.\n\n\n\n\n\n\n\n> The delay in milliseconds to wait before mutations are applied. This is useful to batch multiple mutations together.\n\nIf not specified, a `requestAnimationFrame` will be used to batch mutations.\n\nThe following mutative operations are batched:\n \n - \n - \n - \n - \n - \n - \n - \n - \n - \n - \n - \n - \n\n\n\n DATA_TYPE | boolean\">\n\n> A function to be used for filtering a `TreeDataSource`.\n\nThe function should return a boolean value or a data object.\n\n- when returning `false` the current data object will be filtered out.\n- when returning `true`, the current data object will be included in the filtered data, with no changes.\n- when returning a data object, the object will be used instead of the current data object for the row. This means that you can modify the data object to only include some of its children (which match a specific criteria)\n\n\n\nThe `treeFilterFunction` is called with an object that has a `filterTreeNode` function property. This function is a helper function you can use to continue the filtering further down the tree on the current (non-leaf) node.\n\nThis function will call the filtering function for each child of the current node. If all the children are filtered out, the current node will be filtered out as well. If there are any children that match the criteria, a clone of the current node will be returned with only the matching children.\n\nYou can opt to not use this helper function, and instead implement your own filtering logic. In this case, make sure you don't mutate data objects but rather return cloned versions of them.\n\n\n\n\n\n```ts file=tree-filter-function-example.page.tsx\n\n```\n\n\n\n\n\n boolean\">\n\n> A function to be used for client-side filtering.\n\nUsing this function will not show any special filtering UI for columns.\n\n\n\nFor filtering when using a `TreeGrid`, see .\n\n\n\n\n\n\n\nLoads data from remote location but will only show rows that have `id > 100`.\n\n\n\n```ts file=\"custom-filter-function-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n> Explicitly configures where filtering will take place and if changes in the should trigger a reload of the data source - applicable when is a function.\n Replaces the deprecated \n\n- `false` (the default) - filtering will be done on the client side and the function will not be invoked again.\n- `true` - filtering will be done on the server side - the function will be called with an object that includes the `filterValue` property, so it can be sent to the server\n\n\n\n\n\n> Explicitly configures where filtering will take place. Update to use the prop.\n\n- `'local'` - filtering will be done on the client side\n- `'remote'` - filtering will be done on the server side - the function will be called with an object that includes the `filterValue` property, so it can be sent to the server\n\n\n\n\">\n\n> Specifies the available types of filters for the columns.\n\nA filter type is a concept that defines how a certain type of data is to be filtered.\nA filter type will have a key, used to define the filter in the `filterTypes` object, and also the following properties:\n\n- `label`\n- `emptyValues` - an array of values considered to be empty values - when any of these values is used in the filter, the filter will match all records.\n- `operators` - an array of operator this filter type supports\n- `defaultOperator` - the default operator for the filter type\n- `components` - an object that describes the custom components to be used for the filter type\n - `FilterEditor` - a custom filter editor component for this filter type\n - `FilterOperatorSwitch` - a custom component that is displayed at the left of the `FilterEditor` and can be used for switching between operators - only needed for very very advanced use-cases.\n\nLet's imagine you have a `DataSource` with developers, each with a `salary` column, and for that column you want to allow `>`, `>=`, `<` and `<=` comparisons (operators).\n\nFor this, you would define the following filter type:\n\n```tsx\nconst filterTypes = {\n income: {\n label: 'Income',\n emptyValues: ['', null, undefined],\n defaultOperator: 'gt',\n operators: [\n {\n name: 'gt',\n label: 'Greater than',\n fn: ({ currentValue, filterValue }) => {\n return currentValue > filterValue;\n },\n },\n {\n name: 'gte',\n //...\n },\n {\n name: 'lt',\n //...\n },\n {\n name: 'lte',\n //...\n },\n ],\n },\n};\n```\n\n\n\nEach operator for a certain filter type needs to at least have a `name` and `fn` defined. The `fn` property is a function that will be called when client-side filtering is enabled, with an object that has the following properties:\n\n- `currentValue` - the cell value of the current row for the column being filtered\n- `filterValue` - the value of the filter editor\n- `emptyValues` - the array of values considered to be empty values for the filter type\n- `data` - the current row data object - `typeof DATA_TYPE`\n- `index` - the index of the current row in the table - `number`\n- `dataArray` - the array of all rows originally in the table - `typeof DATA_TYPE[]`\n- `field?` - the field the current column is bound to (can be undefined if the column is not bound to a field)\n\n\n\n\n\n\n\nThe `salary` column has a custom filter type, with the following operators: `gt`, `gte`, `lt` and `lte`.\n\n\n\n```ts file=\"filter-types-example.page.tsx\"\n\n```\n\n\n\n\n\nBy default, the `string` and `number` filter types are available. You can import the default filter types like this:\n\n```ts\nimport { defaultFilterTypes } from '@infinite-table/infinite-react';\n```\n\nIf you want to make all your instances of `InfiniteTable` have new operators for those filter types, you can simply mutate the exported `defaultFilterTypes` object.\n\n\n\n\n\nThe `string` columns have a new `Not includes` operator.\n\n\n\n```ts file=\"default-filter-types-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\nWhen you specify new , the default filter types of `string` and `number` are still available - unless the new object contains those keys and overrides them explicitly.\n\n\n\nThe current implementation of the default filter types is the following:\n\n```tsx\nexport const defaultFilterTypes: Record> = {\n string: {\n label: 'Text',\n emptyValues: [''],\n defaultOperator: 'includes',\n components: {\n FilterEditor: StringFilterEditor,\n },\n operators: [\n {\n name: 'includes',\n components: {\n Icon: // custom icon as a React component ...\n },\n label: 'includes',\n fn: ({ currentValue, filterValue }) => {\n return (\n typeof currentValue === 'string' &&\n typeof filterValue == 'string' &&\n currentValue.toLowerCase().includes(filterValue.toLowerCase())\n );\n },\n },\n {\n label: 'Equals',\n components: {\n Icon: // custom icon as a React component ...\n },\n name: 'eq',\n fn: ({ currentValue: value, filterValue }) => {\n return typeof value === 'string' && value === filterValue;\n },\n },\n {\n name: 'startsWith',\n components: {\n Icon: // custom icon as a React component ...\n },\n label: 'Starts With',\n fn: ({ currentValue: value, filterValue }) => {\n return value.startsWith(filterValue);\n },\n },\n {\n name: 'endsWith',\n components: {\n Icon: // custom icon as a React component ...\n },\n label: 'Ends With',\n fn: ({ currentValue: value, filterValue }) => {\n return value.endsWith(filterValue);\n },\n },\n ],\n },\n number: {\n label: 'Number',\n emptyValues: ['', null, undefined],\n defaultOperator: 'eq',\n components: {\n FilterEditor: NumberFilterEditor,\n },\n operators: [\n {\n label: 'Equals',\n components: {\n Icon: // custom icon as a React component ...\n },\n name: 'eq',\n fn: ({ currentValue, filterValue }) => {\n return currentValue == filterValue;\n },\n },\n {\n label: 'Not Equals',\n components: {\n Icon: // custom icon as a React component ...\n },\n name: 'neq',\n fn: ({ currentValue, filterValue }) => {\n return currentValue != filterValue;\n },\n },\n {\n name: 'gt',\n label: 'Greater Than',\n components: {\n Icon: // custom icon as a React component ...\n },\n fn: ({ currentValue, filterValue, emptyValues }) => {\n if (emptyValues.includes(currentValue)) {\n return true;\n }\n return currentValue > filterValue;\n },\n },\n {\n name: 'gte',\n components: {\n Icon: // custom icon as a React component ...\n },\n label: 'Greater Than or Equal',\n fn: ({ currentValue, filterValue, emptyValues }) => {\n if (emptyValues.includes(currentValue)) {\n return true;\n }\n return currentValue >= filterValue;\n },\n },\n {\n name: 'lt',\n components: {\n Icon: // custom icon as a React component ...\n },\n label: 'Less Than',\n fn: ({ currentValue, filterValue, emptyValues }) => {\n if (emptyValues.includes(currentValue)) {\n return true;\n }\n return currentValue < filterValue;\n },\n },\n {\n name: 'lte',\n components: {\n Icon: // custom icon as a React component ...\n },\n label: 'Less Than or Equal',\n fn: ({ currentValue, filterValue, emptyValues }) => {\n if (emptyValues.includes(currentValue)) {\n return true;\n }\n return currentValue <= filterValue;\n },\n }\n ],\n },\n };\n```\n\n\n\n\n\n> A custom React component to be used as an editor for the current filter type\n\nEvery filter type can define the following `components`\n\n- `FilterEditor` - a React component to be used as an editor for the current filter type\n- `FilterOperatorSwitch` - a custom component that is displayed at the left of the `FilterEditor` and can be used for switching between operators - only needed for very very advanced use-cases.\n\n\n\nFilter type operators can override the `FilterEditor` component - they can specify the following components:\n\n- `FilterEditor` - if specified, it overrides the `FilterEditor` of the filter type\n- `Icon` - a React component to be used as an icon for the operator - displayed by the menu triggered when clicking on the `FilterOperatorSwitch` component\n\n\n\n\n\n\n\nThe `canDesign` column is using a custom `bool` filter type with a custom filter editor.\n\nThe checkbox has indeterminate state, which will match all values in the data source.\n\n\n\n```ts file=\"$DOCS/reference/hooks/custom-filter-editor-hooks-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n> Controlled prop used for filtering. Can be used for both [client-side](/docs/learn/filtering/filtering-client-side) and [server-side](/docs/learn/filtering/filtering-server-side) filtering.\n\nFor the uncontrolled version, see \n\nIf you want to show the column filter editors, you have to either specify this property, or the uncontrolled - even if you have no initial filters. For no initial filters, use `filterValue=[]`.\n\nThe objects in this array have the following shape:\n\n- `filter` - an object describing the filter\n - `filter.value` - the value to filter by\n - `filter.type` - the current type of the filter (eg: `string`, `number` or another custom type you specify in the filterTypes prop)\n - `filter.operator` - the name of the operator being applied\n- `field` - the field being filtered - generally matched with a column. This is optional, as some columns can have no field.\n- `id` - the id of the column being filtered. This is optional - for columns bound to a field, the `field` should be used instead of the `id`.\n- `disabled` - whether this filter is applied or not\n\n\n\n```ts file=\"onFilterValueChange-example.page.tsx\"\n\n```\n\n\n\n\n\nYou can control the visibility of the column filters by using the prop.\n\n\n\n\n\n\n\n> Whether the datasource will load data lazily - useful for server-side grouping and pivoting. If set to `true` or to an object (with `batchSize` property), the prop must be a function that returns a promise.\n\n\n\n```ts file=\"$DOCS/learn/grouping-and-pivoting/pivoting/remote-pivoting-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n> Controls the expand/collapse state of group rows, when is used\n\n\nSee related , and \n\n\n```tsx title=\"Specifying the state for group rows\"\nconst groupRowsState: DataSourcePropGroupRowsStateObject = {\n collapsedRows: true,\n expandedRows: [['Mexico'], ['Mexico', 'backend'], ['India']],\n};\n```\n\nThe two properties in this object are `collapsedRows` and `expandedRows`, and each can have the following values:\n - `true` - meaning that all groups have this state\n - an array of arrays - representing the exceptions to the default value\n\n\nSo if you have `collapsedRows` set to `true` and then `expandedRows` set to `[['Mexico'], ['Mexico', 'backend'], ['India']]` then all rows are collapsed by default, except the rows specified in the `expandedRows`.\n\n\n\n```ts file=\"./group-rows-state-controlled-example.page.tsx\"\n```\n\n\n\n\n\n void\">\n\n> Callback prop when the changes.\n\nSee related and \n\nThis function is called with an object that's an instance of , when the user interacts with group rows and expands/collapses them.\n\nIf you want to get a plain object from this instance, call the `.getState()` method.\n\nSee reference to find out all the utility methods this instance gives you.\n\n\n\n\n\n> Specifies the initial expand/collapse state of group rows, when is used\n\n\nFor the controlled version, see related .\n\n\n```tsx title=\"Specifying the initial state for group rows\"\nconst defaultGroupRowsState: DataSourcePropGroupRowsStateObject = {\n expandedRows: true,\n collapsedRows: [['Mexico'], ['Mexico', 'backend'], ['India']],\n};\n```\n\nThe two properties in this object are `collapsedRows` and `expandedRows`, and each can have the following values:\n - `true` - meaning that all groups have this state\n - an array of arrays - representing the exceptions to the default value\n\n\nSo if you have `expandedRows` set to `true` and then `collapsedRows` set to `[['Mexico'], ['Mexico', 'backend'], ['India']]` then all rows are expanded by default, except the rows specified in the `collapsedRows`.\n\n\n```ts file=\"./group-rows-initial-state-example.page.tsx\"\n```\n\n\n\n\n\n\n> An array of objects with `field` properties, that control how rows are being grouped.\n\nEach item in the array can have the following properties:\n\n- field - `keyof DATA_TYPE`\n- column - config object for the group column - see .\n\n\n\nWhen using groupRenderStrategy=\"multi-column\", it can be very useful for each group to configure it's own column - use for this.\n\n\nSee for the type definition.\n\n\n\n```ts files=[\"groupBy-example.page.tsx\",\"columns.ts\"]\n\n```\n\n\n\n\n\n[]\">\n\n> An array of objects with `field` properties that control how pivoting works. Pivoting is very often associated with aggregations, so see related for more details.\n\nEach item in the array can have the following properties:\n\n- field - `keyof DATA_TYPE`\n- column - config object or function for generated pivot columns.\n\n\n\nFor more details on the type of the items in this array prop, see .\n\n\n\n\n\n```ts file=\"$DOCS/learn/grouping-and-pivoting/pivoting/pivoting-customize-column-example.page.tsx\"\n\n```\n\n\n\n\n \n>\">\n\n> An object that configures how the column for the current group should look like\n\nIf is specified, it overrides this property (the objects actually get merged, with `groupColumn` having higher priority and being merged last).\n\n\n\nIf you are using a groupRenderStrategy=\"single-column\", then using `groupBy.column` should not be used, as you could have many groups with conflicting column configuration.\n\nIn this case, use the prop.\n\n\n\n\n\n\n\nThis example uses `groupBy.column` to configure the generated columns corresponding to each group.\n\n\n\n```ts files=[\"groupBy-multi-with-column-example.page.tsx\",\"columns.ts\"]\n\n```\n\n\n\n\n\n\n\n> Whether the component should use live pagination.\n\nUse this in combination with and \n\n\n\n```ts file=\"$DOCS/learn/working-with-data/live-pagination-example.page.tsx\"\n\n```\n\n\n\n\n\n void\">\n\n> Callback prop to be called when the data changes via the DataSource API.\n\nCalled when any of the following methods have been called in the `DataSource` api\n\n- \n- \n\n- \n- \n\n- \n- \n\n- \n- \n\n- \n- \n\nThis callback is called with an object that has the following properties:\n\n- `primaryKeyField` - the field configured as the primary key for the ``\n- `mutations` - a `Map` with mutations. The keys in the map are primary keys of the mutated data\n\nThe values in the mutations are object descriptors of mutations, that have the following shape:\n\n- `type`: `'insert'|'update'|'delete'`\n- `originalData`: `DATA_TYPE | null` - the original data before the mutation. In case of `insert`, it will be `null`\n- `data`: `Partial` - the updates to be performed on the data. In case of `delete`, it will be `undefined`. This is an object that will contain the primary key, and the updated values for the data (not necessarily the full object, except for `insert`, where it will be of type `DATA_TYPE`).\n\n\n\nstring|number)\" defaulValue={undefined}>\n\n> A cursor value for live pagination. A good value for this is the id of the last item in the array. It can also be defined as a function\n\nUse this in combination with and \n\n\n\nWhen this is a function, it is called with a parameter object that has the following properties:\n\n- `array` - the current array of data\n- `lastItem` - the last item in the array\n- `length` - the length of the data array\n\n\n\n\n\n```ts file=\"$DOCS/learn/working-with-data/live-pagination-example.page.tsx\"\n\n```\n\n\n\n\n\n)=>void\">\n\n> A function to be called when data-related state changes.\n\nCan be used to implement \n\nThe function is called with an object that has the following properties:\n\n- `sortInfo` - current sort information - see for details\n- `groupBy` - current grouping information - see for details\n- `filterValue` - current filtering information - see for details\n- `livePaginationCursor` - the value for the live pagination cursor - see for details\n- `changes` - an object that can help you figure out what change caused `onDataParamsChange` to be called.\n\n\n\n```ts file=\"$DOCS/learn/working-with-data/live-pagination-example.page.tsx\"\n\n```\n\n\n\n\n void\">\n\n> Callback prop called when the changes.\n\nThis might not be called immediately, as there might be a set.\n\n\n\n```ts file=\"onFilterValueChange-example.page.tsx\"\n\n```\n\n\n\n\n\n void\">\n\n> A function to be called when the changes.\n\nAlso see related .\n\n\n\n) => void\">\n\n> The callback that is called when the `DataSource` is ready. The [`dataSourceApi`](/docs/reference/datasource-api) is passed as the first argument.\n\n\n\n void\">\n\n> A function to be called when the changes.\n\n\n\n\n\nUse your mouse to select/deselect cells.\n\n\n\n```ts file=\"$DOCS/learn/selection/controlled-cell-selection-example.page.tsx\"\n\n```\n\n\n\n\n\n void\">\n\n> A function to be called when the changes.\n\n\n\n\n\nUse your mouse or keyboard (press the spacebar) to select/deselect a single row.\n\n\n\n```ts file=\"$DOCS/reference/controlled-single-row-selection-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\nThis example shows how you can use multiple row selection with a predefined controlled value.\n\nGo ahead and select some groups/rows and see the selection value adjust.\n\nThe example also shows how you can use the `InfiniteTableApi` to retrieve the actual ids of the selected rows.\n\n\n\n```ts file=\"$DOCS/reference/controlled-multi-row-selection-example.page.tsx\"\n\n```\n\n\n\n\n void\">\n\n> Called when sorting changes on the DataSource.\n\nThe sorting can change either via a user interaction or by calling an API method (from the [root API](api) or the [Column API](column-api)).\n\nSee related for controlled sorting and for uncontrolled sorting.\n\n\n\n\n\n> A value that can be used to trigger a re-fetch of the data.\n\nBy updating the value of this prop (eg: you can use it as a counter, and increment it) the `` component reloads it's if it's defined as a function. More specifically, the `data` function is called again and the result will replace the current data.\n\n\n\n\n\nThis example shows how you can use the `refetchKey` to trigger reloading the data\n\n\n\n```ts file=\"refetchKey-example.page.tsx\"\n\n```\n\n\n \n\n\n\n\n> Describes the selected row(s) in the `DataSource`\n\nFor single selection, the prop will be of type: `number | string | null`. Use `null` for empty selection in single selection mode.\n\nFor multiple selection, the prop will have the following shape:\n\n```ts\nconst rowSelection = {\n selectedRows: [3, 6, 100, 23], // those specific rows are selected\n defaultSelection: false, // all rows deselected by default\n};\n\n// or\nconst rowSelection = {\n deselectedRows: [3, 6, 100, 23], // those specific rows are deselected\n defaultSelection: true, // all other rows are selected\n};\n\n// or, for grouped data - this example assumes groupBy=continent,country,city\nconst rowSelection = {\n selectedRows: [\n 45, // row with id 45 is selected, no matter the group\n ['Europe', 'France'], // all rows in Europe/France are selected\n ['Asia'], // all rows in Asia are selected\n ],\n deselectedRows: [\n ['Europe', 'France', 'Paris'], // all rows in Paris are deselected\n ],\n defaultSelection: false, // all other rows are selected\n};\n```\n\nFor using group keys in the selection value, see related \n\n\n\n\n\nUse your mouse or keyboard (press the spacebar) to select/deselect a single row.\n\n\n\n```ts file=\"$DOCS/reference/controlled-single-row-selection-example.page.tsx\"\n\n```\n\n\n\n\n\nWhen is being used - this means not all available groups/rows have actually been loaded yet in the dataset - we need a way to allow you to specify that those possibly unloaded rows/groups are selected or not. In this case, the `rowSelection.selectedRows`/`rowSelection.deselectedRows` arrays should not have row primary keys as strings/numbers, but rather rows/groups specified by their full path (so should be set to `true`).\n\n```ts {6}\n// this example assumes groupBy=continent,country,city\nconst rowSelection = {\n selectedRows: [\n // row with id 45 is selected - we need this because in the lazyLoad scenario,\n // not all parents might have been made available yet\n ['Europe','Italy', 'Rome', 45],\n ['Europe','France'], // all rows in Europe/France are selected\n ['Asia'] // all rows in Asia are selected\n ]\n deselectedRows: [\n ['Europe','Italy','Rome'] // all rows in Rome are deselected\n // but note that row with id 45 is selected, so Rome will be rendered with an indeterminate selection state\n ],\n defaultSelection: false // all other rows are selected\n}\n```\n\nIn the example above, we know that there are 3 groups (`continent`, `country`, `city`), so any item in the array that has a 4th element is a fully specified leaf node. While lazy loading, we need this fully specified path for specific nodes, so we know which group rows to render with indeterminate selection.\n\n\n\n\n\nThe prop can be used for both lazy and non-lazy `DataSource` components.\n\n\n\n\n\n\n\nThis example shows how you can use multiple row selection with a predefined controlled value.\n\nGo ahead and select some groups/rows and see the selection value adjust.\n\nThe example also shows how you can use the `InfiniteTableApi` to retrieve the actual ids of the selected rows.\n\n\n\n```ts file=\"$DOCS/reference/controlled-multi-row-selection-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n> Specifies the type of selection that should be enabled.\n\n\n\n\nRead more on row selection (`multi-row` and `single-row`).\n\n\n\n\n\nRead more on cell selection (`multi-cell` and `single-cell`).\n\n\n\n\n\n\n\n```ts file=\"selectionMode-example.page.tsx\"\n\n```\n\n\n\n\n\n[], arr: T[]) => T[]\">\n\n> Custom sorting function to replace the `multisort` function used by default.\n\nThe function specified in the prop is called with the as the first argument and the data array as the second. It should return a sorted array, as per the it was called with.\n\n\n\nWhen is specified, will be forced to `false`, as the sorting is done in the browser.\n\n\n\n\nThe `@infinite-table/infinite-react` package exports a `multisort` function - this is the default function used for local sorting.\n\n```ts\nimport { multisort } from '@infinite-table/infinite-react';\n\nconst arr: Developer[] = [\n /*...*/\n];\nconst sortInfo = [\n {\n field: 'age',\n dir: -1,\n },\n {\n field: 'name',\n dir: 1,\n },\n];\nmultisort(sortInfo, arr);\n```\n\nIf you want to implement your own custom sort function, the `multisort` fn is a good starting point you can use.\n\n\n\n\n\n```ts file=\"$DOCS/reference/datasource-props/local-sortFunction-single-sorting-example-with-local-data-example.page.tsx\"\n\n```\n\n\n\n\n\n|DataSourceSingleSortInfo[]|null\">\n\n> Information for sorting the data. This is a controlled prop.\n\nAlso see related (uncontrolled version), , and .\n\nSorting can be single (only one field/column can be sorted at a time) or multiple (multiple fields/columns can be sorted at the same time). Therefore, this property an be an array of objects or a single object (or null) - the shape of the objects (of type `DataSourceSingleSortInfo`)is the following.\n\n- `dir` - `1 | -1` - the direction of the sorting\n- `field`? - `keyof DATA_TYPE` - the field to sort\n- `id`? - `string` - if you don't sort by a field, you can specify an id of the column this sorting is bound to. Note that columns have a valueGetter, which will be used when doing local sorting and the column is not bound to an exact field.\n- `type` - the sort type - one of the keys in - eg `\"string\"`, `\"number\"` - will be used for local sorting, to provide the proper comparison function.\n\nWhen you want to use multiple sorting, but have no default sort order/information, use `[]` (the empty array) to denote multiple sorting should be enabled.\n\nIf no `sortInfo` is provided, by default, when clicking a sortable column, single sorting will be applied.\n\n\n\nFor configuring if a column is sortable or not, see and . By default, all columns are sortable.\n\n\n\n\n\n```ts file=\"$DOCS/learn/sorting/remote-controlled-multi-sorting-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n\n\n> Specifies if changes in the should trigger a reload of the data source - applicable when is a function. Replaces the deprecated .\n\nSee related and .\n\nWhen set to `false` (the default), the data is sorted locally (in the browser) after the data-source is loaded. When set to `true`, the data should be sorted by the server (or by the data-source function that serves the data).\n\nSee [the Sorting page](/docs/learn/sorting/overview) for more details.\n\nFor configuring the sorting behavior when multiple sorting is enabled, see .\n\n\n\n\n\n> Specifies which changes in the data-related props should trigger a reload of the data source - applicable when is a function.\n\nSee .\nSee .\nSee .\nSee .\n\n\n\n\n\n\n> Specifies where the sorting should be done. Use instead.\n\nSee related and .\n\nWhen set to `'local'`, the data is sorted locally (in the browser) after the data-source is loaded. When set to `'remote'`, the data should be sorted by the server (or by the data-source function that serves the data).\n\nSee [the Sorting page](/docs/learn/sorting/overview) for more details.\n\nFor configuring the sorting behavior when multiple sorting is enabled, see .\n\n\n\n number)>\">\n\n> Describes the available sorting functions used for local sorting. The object you provide will be merged into the default sort types.\n\nCurrently there are two `sortTypes` available:\n\n- `\"string\"`\n- `\"number\"`\n- `\"date\"`\n\nThose values can be used for the column.sortType and column.dataType properties.\n\n```ts\n// default implementation\nconst sortTypes = {\n string: (a, b) => a.localeCompare(b),\n number: (a, b) => a - b,\n date: (a, b) => a - b,\n};\n```\n\nWhen a column does not explicitly specify the column.sortType, the column.dataType will be used instead. And if no column.dataType is defined, it will default to `string`.\n\nYou can add new sort types to the DataSource and InfiniteTable components by specifying this property - the object will be merged into the default sort types.\n\n\n\n```ts file=\"./sortTypes-example.page.tsx\"\n\n```\n\n\n\n\n\nIn this example, for the `\"color\"` column, we specified column.sortType=\"color\" - we could have passed that as `column.dataType` instead, but if the grid had filtering, it wouldn't know what filters to use for \"color\" - so we usedcolumn.sortType to only change how the data is sorted.\n\n\n\n\n\n\n\n> Specifies whether contains group keys or only row ids/primary keys.\n\nWhen this is `true`, you might want to use the [getSelectedPrimaryKeys](./selection-api#getSelectedPrimaryKeys) method.\n\n\n\n\n\nThis example shows how you can use have row selection with group keys instead of just the primary keys of rows.\n\n\n\n```ts file=\"$DOCS/reference/controlled-multi-row-selection-example-with-group-keys.page.tsx\"\n\n```\n\n\n\n\n\n\n" - }, - "/docs/reference/hooks/": { - "filePath": "/docs/reference/hooks/index", - "routePath": "/docs/reference/hooks/", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/reference/hooks/index.page.md", + "/docs/reference/keyboard-navigation-api/": { + "filePath": "/docs/reference/keyboard-navigation-api/index", + "routePath": "/docs/reference/keyboard-navigation-api/", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/reference/keyboard-navigation-api/index.page.md", "fileName": "index.page.md", - "folderPath": "/docs/reference/hooks/", + "folderPath": "/docs/reference/keyboard-navigation-api/", "frontmatter": { - "title": "Infinite Table Hooks", - "layout": "API", - "description": "Hooks Reference page for Infinite Table - with complete examples" + "title": "Infinite Table Keyboard Navigation API", + "layout": "API" }, - "excerpt": "Infinite Table exposes a few custom hooks that can be used to customize the component and its behavior. Most of the hooks will be useful when you want to implement custom components for `InfiniteTable` - like custom cells, headers, cell editors, etc.", - "readingTime": "4 min read", - "content": "\nInfinite Table exposes a few custom hooks that can be used to customize the component and its behavior. Most of the hooks will be useful when you want to implement custom components for `InfiniteTable` - like custom cells, headers, cell editors, etc.\n\nSee below for the full list of hooks exposed by `InfiniteTable`, each with examples and code snippets.\n\n\n\n\n\n> Gives you access to the master row info in the current RowDetail component.\n\n\n\n\n\n\nThis example shows a master DataGrid with cities & countries.\n\nThe details for each city shows a DataGrid with developers in that city.\n\n\n\n```ts file=\"$DOCS/learn/master-detail/master-detail-component-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n> Use it inside the or (or other rendering functions) to retrieve information about the cell that is being rendered.\n\n```ts\nimport { useInfiniteColumnCell } from '@infinite-table/infinite-react';\n```\n\nFor custom column header components, see related .\n\nWhen using this hook inside a custom column cell component, make sure you get `domRef` from the hook result and pass it on to the final `JSX.Element` that is the DOM root of the component.\n\n```tsx\nconst CustomCellComponent = (props: React.HTMLProps) => {\n const { domRef, ...other } = useInfiniteColumnCell();\n\n return (\n
\n {props.children}\n
\n );\n};\n```\n\nYou should not pass the `domRef` along when using the hook inside the or function.\n\n\n\n```tsx file=\"$DOCS/reference/column-render-hooks-example.page.tsx\"\n\n```\n\n\n\n
\n\n\n\n> Allows you to write a custom editor to be used for [editing](/docs/learn/editing/overview). The hook returns an object shape.\n\nInside this hook, you can also call to get access to the cell-related information.\n\nSee related \n\n\n\nWhen writing a custom editor, it's probably good to stop the propagation of the `KeyDown` event, so that the table doesn't react to the key presses (and do navigation and other stuff).\n\n\n\n\n\n\n\nTry editing the `salary` column - it has a custom editor\n\n\n\n```tsx file=\"custom-editor-hooks-example.page.tsx\"\n\n```\n\n\n\n\n\n ({ column, value, setValue, className, filtered,... })\">\n\n> Used to write custom filter editors for columns.\n\nThe return value of this hook is an object with the following properties:\n\n- `value` - the value that should be passed to the filter editor\n- `setValue(value)` - the functon you have to call to update the filtering for the current column\n- `column` - the current column\n- `operatorName`: `string` - the name of the operator currently being applied\n- `className` - a CSS class name to apply to the filter editor, for default styling\n- `filtered` - a boolean indicating whether the column is currently filtered or not\n- `disabled` - a boolean indicating whether the filter editor should be rendered as disabled or not\n- `filterTypeKey`: `string` - the key of the filter type\n- `filterType` - the filter type object for the current column\n- `filterTypes` - a reference to the object as configured in the `DataSource`\n\n\n\n\n\nThe `canDesign` column is using a custom `bool` filter type with a custom filter editor.\n\nThe checkbox has indeterminate state, which will match all values in the data source.\n\n\n\n```ts file=\"custom-filter-editor-hooks-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n> Used inside or \n\n```ts\nimport { useInfiniteHeaderCell } from '@infinite-table/infinite-react';\n```\n\nFor custom column cell components, see related .\n\nWhen using this hook inside a custom column header component, make sure you get `domRef` from the hook result and pass it on to the final `JSX.Element` that is the DOM root of the component.\n\n```tsx\nconst CustomHeaderComponent = (props: React.HTMLProps) => {\n const { domRef, ...other } = useInfiniteHeaderCell();\n\n return (\n
\n {props.children}\n
\n );\n};\n```\n\nYou should not pass the `domRef` along when using the hook inside the\n function.\n\n\n\n```tsx file=\"$DOCS/reference/column-header-hooks-example.page.tsx\"\n\n```\n\n\n\n
\n\n
\n" + "excerpt": "Available starting with version `6.1.1`.", + "readingTime": "3 min read", + "content": "\nAvailable starting with version `6.1.1`.\n\nSee the [Keyboard Navigation](/docs/learn/keyboard-navigation/navigating-cells) page for more details.\n\n```tsx title=\"Configuring the keyboard navigation to be 'cell'\"\n\n\n// can be \"cell\" (default), \"row\" or false\n```\n\nYou can retrieve the keyboard navigation api by reading it from the `api.keyboardNavigationApi` property.\n\n```tsx {4}\n\nconst onReady = ({api}: {api:InfiniteTableApi}) => {\n // do something with it\n api.keyboardNavigationApi.gotoCell({direction: 'top'})\n}\n\n\n columns={[...]}\n onReady={onReady}\n/>\n```\n\nSee the [Infinite Table API page](/docs/reference/api) for the main API.\nSee the [Infinite Table Cell Selection API page](/docs/reference/cell-selection-api) for the row selection API.\nSee the [Infinite Table Row Selection API page](/docs/reference/row-selection-api) for the row selection API.\nSee the [Infinite Table Column API page](/docs/reference/column-api) for the column API.\n\n\n\n void\">\n\n> Sets the keyboard navigation mode. See \n\nThe sole argument is of the same type as the \n\nSee the [Keyboard Navigation](/docs/learn/keyboard-navigation/navigating-cells) page for more details.\n\n\n\nIf you are using controlled or , make sure you update the values by using the and callbacks respectively.\n\n\n\n\n\n\n void\">\n\n> Sets the value for /\nSee related \n\n\n\n\nIf you are using controlled make sure you update the controlled value by using the callback prop.\n\n\n\n\n\n void\">\n\n> Sets the value for /\n\n\n\n\nIf you are using controlled , make sure you update the values by using the callback prop.\n\n\n\n\n\n number | false\">\n\n> Changes the active row index to the next row. See related , \n\nReturns `false` if the action was not successful (eg: already at the last row), otherwise the new active row index.\n\nThis sets the value for \n\n\n\n\n number | false\">\n\n> Changes the active row index to the prev row. See related , \n\nReturns `false` if the action was not successful (eg: already at the first row), otherwise the new active row index.\n\nThis sets the value for \n\n\n\n [number, number] | false\">\n\n> Changes the active cell index, by navigating to the specified direction (equivalent to pressing the arrow keys).\nSee related \n\n\n\n```tsx file=\"goto-cell-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n" }, "/docs/reference/row-detail-api/": { "filePath": "/docs/reference/row-detail-api/index", @@ -2991,19 +3005,35 @@ "readingTime": "2 min read", "content": "\nThis API can be used when [master-detail](/docs/learn/master-detail/overview) is configured in the DataGrid.\n\nYou can retrieve the row details api by reading it from the `api.rowDetailApi` property.\n\n```tsx {4}\n\nconst onReady = ({api}: {api:InfiniteTableApi}) => {\n // do something with it\n api.rowDetailApi.collapseAllDetails()\n}\n\n\n columns={[...]}\n onReady={onReady}\n/>\n```\n\nSee the [Infinite Table API page](/docs/reference/api) for the main API.\nSee the [Infinite Table Cell Selection API page](/docs/reference/cell-selection-api) for the row selection API.\nSee the [Infinite Table Row Selection API page](/docs/reference/row-selection-api) for the row selection API.\nSee the [Infinite Table Column API page](/docs/reference/column-api) for the column API.\n\n\n\n void\">\n\n> Collapses all row details.\n\n\n\n\n\nSome of the rows in the master DataGrid are expanded by default.\n\nYou can collapse them via the Row Detail API.\n\n\n\n```ts file=\"$DOCS/learn/master-detail/master-detail-api-example.page.tsx\"\n\n```\n\n\n\n\n\n void\">\n\n> Expands all row details.\n\n\n\n\n\nClick the `Expand All` button to expand all row details.\n\n\n\n```ts file=\"$DOCS/learn/master-detail/master-detail-api-example.page.tsx\"\n\n```\n\n\n\n\n\n boolean\">\n\n> Checks if the row detail is collapsed for the row with the specified primary key.\n\n\n\n boolean\">\n\n> Checks if the row detail is expanded for the row with the specified primary key.\n\n\n\n void\">\n\n> Collapses the detail for the row with the specified primary key.\n\n\n\n boolean\">\n\n> Expands the detail for the row with the specified primary key.\n\n\n\n boolean\">\n\n> Toggles the expand/collapse state of the row detail, for the row with the specified primary key.\n\n\n\n\n" }, - "/docs/reference/keyboard-navigation-api/": { - "filePath": "/docs/reference/keyboard-navigation-api/index", - "routePath": "/docs/reference/keyboard-navigation-api/", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/reference/keyboard-navigation-api/index.page.md", + "/docs/reference/hooks/": { + "filePath": "/docs/reference/hooks/index", + "routePath": "/docs/reference/hooks/", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/reference/hooks/index.page.md", "fileName": "index.page.md", - "folderPath": "/docs/reference/keyboard-navigation-api/", + "folderPath": "/docs/reference/hooks/", "frontmatter": { - "title": "Infinite Table Keyboard Navigation API", - "layout": "API" + "title": "Infinite Table Hooks", + "layout": "API", + "description": "Hooks Reference page for Infinite Table - with complete examples" }, - "excerpt": "Available starting with version `6.1.1`.", - "readingTime": "3 min read", - "content": "\nAvailable starting with version `6.1.1`.\n\nSee the [Keyboard Navigation](/docs/learn/keyboard-navigation/navigating-cells) page for more details.\n\n```tsx title=\"Configuring the keyboard navigation to be 'cell'\"\n\n\n// can be \"cell\" (default), \"row\" or false\n```\n\nYou can retrieve the keyboard navigation api by reading it from the `api.keyboardNavigationApi` property.\n\n```tsx {4}\n\nconst onReady = ({api}: {api:InfiniteTableApi}) => {\n // do something with it\n api.keyboardNavigationApi.gotoCell({direction: 'top'})\n}\n\n\n columns={[...]}\n onReady={onReady}\n/>\n```\n\nSee the [Infinite Table API page](/docs/reference/api) for the main API.\nSee the [Infinite Table Cell Selection API page](/docs/reference/cell-selection-api) for the row selection API.\nSee the [Infinite Table Row Selection API page](/docs/reference/row-selection-api) for the row selection API.\nSee the [Infinite Table Column API page](/docs/reference/column-api) for the column API.\n\n\n\n void\">\n\n> Sets the keyboard navigation mode. See \n\nThe sole argument is of the same type as the \n\nSee the [Keyboard Navigation](/docs/learn/keyboard-navigation/navigating-cells) page for more details.\n\n\n\nIf you are using controlled or , make sure you update the values by using the and callbacks respectively.\n\n\n\n\n\n\n void\">\n\n> Sets the value for /\nSee related \n\n\n\n\nIf you are using controlled make sure you update the controlled value by using the callback prop.\n\n\n\n\n\n void\">\n\n> Sets the value for /\n\n\n\n\nIf you are using controlled , make sure you update the values by using the callback prop.\n\n\n\n\n\n number | false\">\n\n> Changes the active row index to the next row. See related , \n\nReturns `false` if the action was not successful (eg: already at the last row), otherwise the new active row index.\n\nThis sets the value for \n\n\n\n\n number | false\">\n\n> Changes the active row index to the prev row. See related , \n\nReturns `false` if the action was not successful (eg: already at the first row), otherwise the new active row index.\n\nThis sets the value for \n\n\n\n [number, number] | false\">\n\n> Changes the active cell index, by navigating to the specified direction (equivalent to pressing the arrow keys).\nSee related \n\n\n\n```tsx file=\"goto-cell-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n" + "excerpt": "Infinite Table exposes a few custom hooks that can be used to customize the component and its behavior. Most of the hooks will be useful when you want to implement custom components for `InfiniteTable` - like custom cells, headers, cell editors, etc.", + "readingTime": "5 min read", + "content": "\nInfinite Table exposes a few custom hooks that can be used to customize the component and its behavior. Most of the hooks will be useful when you want to implement custom components for `InfiniteTable` - like custom cells, headers, cell editors, etc.\n\nSee below for the full list of hooks exposed by `InfiniteTable`, each with examples and code snippets.\n\n\n\n\n\n> Gives you access to the master row info in the current RowDetail component.\n\n\n\n\n\nThis example shows a master DataGrid with cities & countries.\n\nThe details for each city shows a DataGrid with developers in that city.\n\n\n```ts file=\"$DOCS/learn/master-detail/master-detail-component-example.page.tsx\" title=\"Basic master detail DataGrid example\" size=\"lg\"\n\n```\n\n\n\n\n\n> You can use it in your app components that are nested inside the ``\n\n```ts\nimport { useDataSourceState } from '@infinite-table/infinite-react'\n```\n\nUsing it gives you access to the underlying data that InfiniteTable is using.\n\n\n\nPlease make sure you know what you're doing. This is intended only for advanced and complex use-cases.\n\n\n\n\n```tsx title=\"InfiniteTable can be nested anywhere inside the component\"\n\n

Your DataGrid>\n \n \n \n\n```\n\nAny component nested inside the `` can access the underlying data.\n\n```tsx live file=\"using-datasource-context.page.tsx\"\n\n```\n\n\n\n\n> Use it inside the or (or other rendering functions) to retrieve information about the cell that is being rendered.\n\n```ts\nimport { useInfiniteColumnCell } from '@infinite-table/infinite-react';\n```\n\nFor custom column header components, see related .\n\nWhen using this hook inside a custom column cell component, make sure you get `domRef` from the hook result and pass it on to the final `JSX.Element` that is the DOM root of the component.\n\n```tsx\nconst CustomCellComponent = (props: React.HTMLProps) => {\n const { domRef, ...other } = useInfiniteColumnCell();\n\n return (\n
\n {props.children}\n
\n );\n};\n```\n\nYou should not pass the `domRef` along when using the hook inside the or function.\n\n\n\n```tsx file=\"$DOCS/reference/column-render-hooks-example.page.tsx\"\n\n```\n\n\n\n
\n\n\n\n> Allows you to write a custom editor to be used for [editing](/docs/learn/editing/overview). The hook returns an object shape.\n\nInside this hook, you can also call to get access to the cell-related information.\n\nSee related \n\n\n\nWhen writing a custom editor, it's probably good to stop the propagation of the `KeyDown` event, so that the table doesn't react to the key presses (and do navigation and other stuff).\n\n\n\n\n\n\n\nTry editing the `salary` column - it has a custom editor\n\n\n\n```tsx file=\"custom-editor-hooks-example.page.tsx\"\n\n```\n\n\n\n\n\n ({ column, value, setValue, className, filtered,... })\">\n\n> Used to write custom filter editors for columns.\n\nThe return value of this hook is an object with the following properties:\n\n- `value` - the value that should be passed to the filter editor\n- `setValue(value)` - the functon you have to call to update the filtering for the current column\n- `column` - the current column\n- `operatorName`: `string` - the name of the operator currently being applied\n- `className` - a CSS class name to apply to the filter editor, for default styling\n- `filtered` - a boolean indicating whether the column is currently filtered or not\n- `disabled` - a boolean indicating whether the filter editor should be rendered as disabled or not\n- `filterTypeKey`: `string` - the key of the filter type\n- `filterType` - the filter type object for the current column\n- `filterTypes` - a reference to the object as configured in the `DataSource`\n\n\n\n\n\nThe `canDesign` column is using a custom `bool` filter type with a custom filter editor.\n\nThe checkbox has indeterminate state, which will match all values in the data source.\n\n\n\n```ts file=\"custom-filter-editor-hooks-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n> Used inside or \n\n```ts\nimport { useInfiniteHeaderCell } from '@infinite-table/infinite-react';\n```\n\nFor custom column cell components, see related .\n\nWhen using this hook inside a custom column header component, make sure you get `domRef` from the hook result and pass it on to the final `JSX.Element` that is the DOM root of the component.\n\n```tsx\nconst CustomHeaderComponent = (props: React.HTMLProps) => {\n const { domRef, ...other } = useInfiniteHeaderCell();\n\n return (\n
\n {props.children}\n
\n );\n};\n```\n\nYou should not pass the `domRef` along when using the hook inside the\n function.\n\n\n\n```tsx file=\"$DOCS/reference/column-header-hooks-example.page.tsx\"\n\n```\n\n\n\n
\n\n\n" + }, + "/docs/reference/datasource-props/": { + "filePath": "/docs/reference/datasource-props/index", + "routePath": "/docs/reference/datasource-props/", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/reference/datasource-props/index.page.md", + "fileName": "index.page.md", + "folderPath": "/docs/reference/datasource-props/", + "frontmatter": { + "title": "DataSource Props", + "layout": "API", + "description": "Props Reference page for your DataSource in Infinite Table - with complete examples" + }, + "excerpt": "In the API Reference below we'll use **`DATA_TYPE`** to refer to the TypeScript type that represents the data the component is bound to.", + "readingTime": "39 min read", + "content": "\nIn the API Reference below we'll use **`DATA_TYPE`** to refer to the TypeScript type that represents the data the component is bound to.\n\n\n\n string\">\n\n> The name of the id/primary key property of an item in the array. The value of this property needs to be unique.\n\nThis is probably one of the most important properties of the `` component, as it is used to identify items in the array.\n\n\n\nUnlike with other DataGrid components, with `InfiniteTable` you don't need to have a column mapped to the primary key field.\n\nThe primary key is used internally by the component and is not displayed in the grid if you don't explicitly have a column bound to that field.\n\n\n\nIf the primary key is not unique, Infinite Table DataGrid won't work properly.\n\n\n\n```ts file=\"data-example.page.tsx\"\n\n```\n\n\n\n\n\nThe primary key can be either a string (the name of a property in the data object), or a function that returns a string.\n\nUsing functions (for more dynamic primary keys) is supported, but hasn't been tested extensively - so please report any issues you might encounter.\n\n\n\n\n\n\n\n> Specifies the expand/collapse state of the tree nodes. See for the shape of this object. For the uncontrolled version, see .\n\nIf no `treeExpandState` prop is specified, the tree will be rendered as fully expanded by default.\n\nWhen using the controlled version, make sure to update the `treeExpandState` prop by using the callback.\n\n\n\n```ts file=\"tree-controlled-expandstate-example.page.tsx\"\n\n```\n\n\n\n\n\n void\">\n\n> Called when the tree expand state changes.\n\nWhen the user interacts with the tree (by expanding or collapsing a node), this callback is called with the new tree state.\n\nThe first parameter is the new tree state, and the second parameter is an object with the following properties:\n\n- `dataSourceApi` - the DataSource API instance\n- `nodePath` - the path of the node that changed state. If the state was produced by an or call, this will be `null`.\n- `nodeState` - the new state of the node (`\"collapsed\"` or `\"expanded\"`)\n\n\n\n\n```ts file=\"tree-controlled-expandstate-example.page.tsx\"\n\n```\n\n\n\n\n\n void\">\n\n> Called when a node is expanded. See related and props.\n\n\n\n\nThe and callbacks are called when a node is expanded or collapsed, respectively - either via user interaction or by an API call. However, they will not be called when the or methods are called.\n\n\n\n\n\n, treeExpandState: TreeExpandState) => boolean\">\n\n> Decides if the current (non-leaf) node is expanded. \n\nThe inverse prop, is also available. Only one of these props can be specified.\n\n\n\nIf this prop is specified, is ignored.\n\n\n\n\n\n, treeExpandState: TreeExpandState) => boolean\">\n\n> Decides if the current (non-leaf) node is collapsed. See related prop.\n\nThe inverse prop, is also available. Only one of these props can be specified.\n\n\n\nIf this prop is specified, is ignored.\n\n\n\n\n\n) => boolean\">\n\n> Decides if the current (non-leaf) node can be expanded or collapsed and if the tree icon is disabled.\n\nBy default, parent nodes with `children: []` are read-only, meaning they won't respond to expand/collapse clicks.\n\nHowever, if you specify a custom `isNodeReadOnly` function, you can change this behavior.\n\n\n\nWhen a node is read-only, the and methods need the `options.force` flag to be set to `true` in order to override the read-only restriction.\n\nHowever, and will work regardless of the `isNodeReadOnly` setting.\n\nFor full control over the expand/collapse state of read-only nodes, you can use the / props.\n\n\n\n\n\n```ts file=\"tree-isNodeReadOnly-example.page.tsx\"\n\n```\n\n\n\n\n void\">\n\n> Called when a node is collapsed. See related and props.\n\n\n\n\nThe and callbacks are called when a node is expanded or collapsed, respectively - either via user interaction or by an API call. However, they will not be called when the or methods are called.\n\n\n\n\n\n\n\n> The name of the property in the data object that contains the child nodes for each tree node.\n\nOnly available when you're using the `` component.\n\nIf not specified, it defaults to `\"children\"`.\n\n\n\nEach node gets a `nodePath` property, which is the array with the ids of all the parent nodes leading down to the current node. The node path includes the id of the current node\n\n\n\n\n```tsx {2} title=\"Node path vs row id\"\nconst data = [\n { id: '1', name: 'Documents', // path: ['1']\n children: [\n { id: '10', name: 'Private', // path: ['1', '10']\n children: [\n { id: '100', name: 'Report.docx' }, // path: ['1', '10', '100'] \n { id: '101', name: 'Vacation.docx' },// path: ['1', '10', '101']\n ],\n },\n ]\n },\n {\n id: '2',\n name: 'Downloads', // path: ['2']\n children: [\n {\n id: '20',\n name: 'cat.jpg', // path: ['2', '20']\n },\n ],\n },\n];\n```\n\n\n\n```ts file=\"tree-nodesKey-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n> Specifies the expand/collapse state of the tree nodes. See for the shape of this object. For the controlled version, see .\n\n\n\n\n\n```ts file=\"tree-uncontrolled-expandstate-example.page.tsx\"\n\n```\n\n\n\n\n\n\n) => boolean\">\n\n> This function ultimately decides the disabled state of a row. It overrides both / props.\n\nIt's called with a single argument - the row info object for the row in question.\n\nIt should return `true` if the row is disabled, and `false` otherwise.\n\n\n\nWhen this prop is used, will not be called.\n\n\n\n\n\n\n\n\n> The uncontrolled prop for managing row enabled/disabled state. For the controlled version see . For listening to row disabled state changes, see .\n\nThe value for this prop is an object with two properties:\n\n- `enabledRows` - either `true` or an array of row ids that are enabled. When `true` is passed, `disabledRows` should be an array of row ids that are disabled.\n- `disabledRows` - either `true` or an array of row ids that are disabled. When `true` is passed, `enabledRows` should be an array of row ids that are enabled.\n\n\n\nThe values in the `enabledRows`/`disabledRows` arrays are row ids, and not indexes.\n\n\n\n\n\n\nThis prop can be overriden by using the prop.\n\n\n\nHere's an example of how to use the `defaultRowDisabledState` prop:\n\n\n\n\n\nRows with ids `1`, `3`, `4` and `5` are disabled.\n\n\n\n```ts file=\"defaultRowDisabledState-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n> Manages row enabled/disabled state. For the uncontrolled version see . For listening to row disabled state changes, see .\n\nThe value for this prop is an object with two properties:\n\n- `enabledRows` - either `true` or an array of row ids that are enabled. When `true` is passed, `disabledRows` should be an array of row ids that are disabled.\n- `disabledRows` - either `true` or an array of row ids that are disabled. When `true` is passed, `enabledRows` should be an array of row ids that are enabled.\n\n\nWhen using this controlled prop, you will need to update the `rowDisabledState` prop by using the callback.\n\n\n\n\nThis prop can be overriden by using the prop.\n\n\n\n\n\n\n\nRows with ids `1`, `3`, `4` and `5` are disabled initially.\n\nRight click rows and use the context menu to enable/disable rows.\n\n\n\n```ts file=\"rowDisabledState-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n> Determines what nodes are selected and deselected. For the uncontrolled version see .\n\nThe value of this prop determines if a node is selected or not.\n\nSee for details on the shape of this object.\n\n\n\n\n```ts file=\"tree-controlled-selectedstate-example.page.tsx\"\n\n```\n\n\n\n\n\n void\">\n\n> Called when the tree selection changes. See .\n\n\n\n```ts file=\"tree-controlled-selectedstate-example.page.tsx\"\n\n```\n\n\n\nWhen using `multi-row` , the signature of this callback is:\n\n - `treeSelection` - the new tree selection state\n - `context` - an object with the following properties:\n - `selectionMode` - will be `\"multi-row\"`\n - `lastUpdatedNodePath` - the path of the node that was last updated (either via user action or api call). Will be `null` of the action that triggered this callback was or .\n - `dataSourceApi` - the [DataSource API](/docs/reference/datasource-api) instance\n\n\n\n\n\n> Determines what nodes are selected and deselected. For the controlled version see .\n\nThe value of this prop determines if a node is selected or not.\n\nSee for details on the shape of this object.\n\n\n\n\n```ts file=\"tree-uncontrolled-selectedstate-example.page.tsx\"\n\n```\n\n\n\n\n\n void\">\n\n> Called when the row disabled state changes.\n\nIt's called with just 1 argument (`rowDisabledState`), which is an instance of the `RowDisabledState` class. To get a literal object that represents the row disabled state, call the `rowDisabledState.getState()` method.\n\n```tsx {3,19}\nimport {\n DataSource,\n RowDisabledStateObject,\n} from '@infinite-table/infinite-react';\nfunction App() {\n const [rowDisabledState, setRowDisabledState] = React.useState<\n RowDisabledStateObject\n >({\n enabledRows: true,\n disabledRows: [1, 3, 4, 5],\n });\n return (\n <>\n \n data={data}\n primaryKey=\"id\"\n rowDisabledState={rowDisabledState}\n onRowDisabledStateChange={(rowState) => {\n setRowDisabledState(rowState.getState());\n }}\n />\n \n );\n}\n```\n\n\nWhen using the controlled prop, you will need to update the `rowDisabledState` by using this callback.\n\n\n\n\n\n\nRows with ids `1`, `3`, `4` and `5` are disabled initially.\n\nRight click rows and use the context menu to enable/disable rows.\n\n\n\n```ts file=\"rowDisabledState-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n\">\n\n> Specifies the functions to use for aggregating data. The object is a map where the keys are ids for aggregations and values are object of the shape described below.\n\nThe `DataSourceAggregationReducer` type can have the following properties\n\n- `initialValue` - type `any`, mandatory for client-side aggregations. It can be a function, in which case, it will be called to compute the initial value for the aggregation. Otherwise, the initial value will be used as is.\n- `field` - the field to aggregate on. Optional - if not specified, make sure you specify `getter`\n- `getter`: `(data:T)=> any` - a getter function, called with the current `data` object.\n- `reducer`: `string | (accumulator, value, data: T) => any` - either a string (for server-side aggregations) or a mandatory aggregation function for client-side aggregations.\n- `done`: `(accumulator, arr: T[]) => any` - a function that is called to finish the aggregation after all values have been accumulated. The function should return the final value of the aggregation. Only used for client-side aggregations.\n- `name` - useful especially in combination with , as it will be used as the pivot column header.\n- `pivotColumn` - if specified, will configure the pivot column generated for this aggregation. This object has the same shape as a normal column, but supports an extra `inheritFromColumn` property, which can either be a `string` (a column id), or a `boolean`. The default behavior for a pivot column is to inherit the configuration of the initial column that has the same `field` property. `inheritFromColumn` allows you to specify another column to inherit from, or, if `false` is passed, the pivot column will not inherit from any other column.\n\n\n\n```ts files=[\"groupBy-example.page.tsx\",\"columns.ts\"]\n\n```\n\n\n\nAggregation reducers can be used in combination with grouping and pivoting. The example below shows aggregations used with server-side pivoting\n\n\n\n```ts file=\"$DOCS/learn/grouping-and-pivoting/pivoting/remote-pivoting-example.page.tsx\"\n\n```\n\n\n\nPivot columns generated for aggregations will inehrit from initial columns - the example shows how to leverage this behavior and how to extend it\n\n\n\n```ts file=\"$DOCS/learn/grouping-and-pivoting/pivoting/pivot-column-inherit-example.page.tsx\"\n\n```\n\n\n\n\n\n DATA_TYPE[]|Promise\">\n\n> Specifies the data the component is bound to.\n\nCan be one of the following:\n\n- an array of the bound type - eg: `Employee[]`\n- a Promise tha resolves to an array like the above\n- a function that returns an any of the above\n\n\n\nIf the `data` prop is a function, it will be called with an object of type . Click to see more details.\n\n\n\n\n\n```ts file=\"data-example.page.tsx\"\n\n```\n\n\n\n\n\nIt's important to note you can re-fetch data by changing the reference you pass as the `data` prop to the `` component. Passing another `data` function, will cause the component to re-execute the function and thus load new data.\n\n\n\n\n\n```ts files=[\"$DOCS/learn/working-with-data/refetch-example.page.tsx\",\"$DOCS/learn/working-with-data/columns.ts\"]\n\n```\n\n\n\n\n\n\n\n> Uncontrolled prop used for filtering. Can be used for both [client-side](/docs/learn/filtering/filtering-client-side) and [server-side](/docs/learn/filtering/filtering-server-side) filtering.\n\nIf you want to show the column filter editors, you have to either specify this property, or the controlled - even if you have no initial filters. For no initial filters, use `defaultFilterValue=[]`.\n\nFor the controlled version, and more details on the shape of the objects in the array, see .\n\n\n\n```ts file=\"defaultFilterValue-example.page.tsx\"\n\n```\n\n\n\n\n\nYou can control the visibility of the column filters by using the prop.\n\n\n\n\n\n\n\n> Describes the selected row(s) in the `DataSource`\n\nSee more docs in the controlled version of this prop, \n\nFor single selection, the prop will be of type: `number | string | null`. Use `null` for empty selection in single selection mode.\n\nFor multiple selection, the prop will have the following shape:\n\n```ts\nconst rowSelection = {\n selectedRows: [3, 6, 100, 23], // those specific rows are selected\n defaultSelection: false, // all rows deselected by default\n};\n\n// or\nconst rowSelection = {\n deselectedRows: [3, 6, 100, 23], // those specific rows are deselected\n defaultSelection: true, // all other rows are selected\n};\n\n// or, for grouped data - this example assumes groupBy=continent,country,city\nconst rowSelection = {\n selectedRows: [\n 45, // row with id 45 is selected, no matter the group\n ['Europe', 'France'], // all rows in Europe/France are selected\n ['Asia'], // all rows in Asia are selected\n ],\n deselectedRows: [\n ['Europe', 'France', 'Paris'], // all rows in Paris are deselected\n ],\n defaultSelection: false, // all other rows are selected\n};\n```\n\nFor using group keys in the selection value, see related \n\n\n\n```ts file=\"$DOCS/reference/uncontrolled-multiple-row-selection-example.page.tsx\"\n\n```\n\n\n\n\n\n|DataSourceSingleSortInfo[]|null\">\n\n> Information for sorting the data. This is an uncontrolled prop.\n\nFor detailed explanations, see (controlled property).\n\n\n\nWhen you provide a `defaultSortInfo` prop and the sorting information uses a custom sortType, make sure you specify that as the `type` property of the sorting info object.\n\n```tsx\ndefaultSortInfo={{\n field: 'color',\n dir: 1,\n // note this custom sort type\n type: 'color',\n}}\n```\n\nYou will need to have a property for that type in your object as well.\n\n```tsx\nsortTypes={{\n color: (a, b) => //...\n}}\n```\n\n\n\n\n\n```ts file=\"$DOCS/learn/sorting/local-uncontrolled-single-sorting-example-with-remote-data.page.tsx\"\n\n```\n\n\n\n\n\n```ts file=\"./customSortType-with-uncontrolled-sortInfo-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n> The delay in milliseconds before the filter is applied. This is useful when you want to wait for the user to finish typing before applying the filter.\n\nThis is especially useful in order to reduce the number of requests sent to the server, when remote filtering is used.\n\nIf not specified, defaults to `200` milliseconds. This means, any changes to the column filters, that happen inside a 200ms window (or the current value of ), will be debounced and only the last value will be sent to the server.\n\n\n\nIf you want to prevent debouncing/batching filter values, you can set to `0`.\n\n\n\n\n\n\n\n> The delay in milliseconds to wait before mutations are applied. This is useful to batch multiple mutations together.\n\nIf not specified, a `requestAnimationFrame` will be used to batch mutations.\n\nThe following mutative operations are batched:\n \n - \n - \n - \n - \n - \n - \n - \n - \n - \n - \n - \n - \n\n\n\n DATA_TYPE | boolean\">\n\n> A function to be used for filtering a `TreeDataSource`.\n\nThe function should return a boolean value or a data object.\n\n- when returning `false` the current data object will be filtered out.\n- when returning `true`, the current data object will be included in the filtered data, with no changes.\n- when returning a data object, the object will be used instead of the current data object for the row. This means that you can modify the data object to only include some of its children (which match a specific criteria)\n\n\n\nThe `treeFilterFunction` is called with an object that has a `filterTreeNode` function property. This function is a helper function you can use to continue the filtering further down the tree on the current (non-leaf) node.\n\nThis function will call the filtering function for each child of the current node. If all the children are filtered out, the current node will be filtered out as well. If there are any children that match the criteria, a clone of the current node will be returned with only the matching children.\n\nYou can opt to not use this helper function, and instead implement your own filtering logic. In this case, make sure you don't mutate data objects but rather return cloned versions of them.\n\n\n\n\n\n```ts file=tree-filter-function-example.page.tsx\n\n```\n\n\n\n\n\n boolean\">\n\n> A function to be used for client-side filtering.\n\nUsing this function will not show any special filtering UI for columns.\n\n\n\nFor filtering when using a `TreeGrid`, see .\n\n\n\n\n\n\n\nLoads data from remote location but will only show rows that have `id > 100`.\n\n\n\n```ts file=\"custom-filter-function-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n> Explicitly configures where filtering will take place and if changes in the should trigger a reload of the data source - applicable when is a function.\n Replaces the deprecated \n\n- `false` (the default) - filtering will be done on the client side and the function will not be invoked again.\n- `true` - filtering will be done on the server side - the function will be called with an object that includes the `filterValue` property, so it can be sent to the server\n\n\n\n\n\n> Explicitly configures where filtering will take place. Update to use the prop.\n\n- `'local'` - filtering will be done on the client side\n- `'remote'` - filtering will be done on the server side - the function will be called with an object that includes the `filterValue` property, so it can be sent to the server\n\n\n\n\">\n\n> Specifies the available types of filters for the columns.\n\nA filter type is a concept that defines how a certain type of data is to be filtered.\nA filter type will have a key, used to define the filter in the `filterTypes` object, and also the following properties:\n\n- `label`\n- `emptyValues` - an array of values considered to be empty values - when any of these values is used in the filter, the filter will match all records.\n- `operators` - an array of operator this filter type supports\n- `defaultOperator` - the default operator for the filter type\n- `components` - an object that describes the custom components to be used for the filter type\n - `FilterEditor` - a custom filter editor component for this filter type\n - `FilterOperatorSwitch` - a custom component that is displayed at the left of the `FilterEditor` and can be used for switching between operators - only needed for very very advanced use-cases.\n\nLet's imagine you have a `DataSource` with developers, each with a `salary` column, and for that column you want to allow `>`, `>=`, `<` and `<=` comparisons (operators).\n\nFor this, you would define the following filter type:\n\n```tsx\nconst filterTypes = {\n income: {\n label: 'Income',\n emptyValues: ['', null, undefined],\n defaultOperator: 'gt',\n operators: [\n {\n name: 'gt',\n label: 'Greater than',\n fn: ({ currentValue, filterValue }) => {\n return currentValue > filterValue;\n },\n },\n {\n name: 'gte',\n //...\n },\n {\n name: 'lt',\n //...\n },\n {\n name: 'lte',\n //...\n },\n ],\n },\n};\n```\n\n\n\nEach operator for a certain filter type needs to at least have a `name` and `fn` defined. The `fn` property is a function that will be called when client-side filtering is enabled, with an object that has the following properties:\n\n- `currentValue` - the cell value of the current row for the column being filtered\n- `filterValue` - the value of the filter editor\n- `emptyValues` - the array of values considered to be empty values for the filter type\n- `data` - the current row data object - `typeof DATA_TYPE`\n- `index` - the index of the current row in the table - `number`\n- `dataArray` - the array of all rows originally in the table - `typeof DATA_TYPE[]`\n- `field?` - the field the current column is bound to (can be undefined if the column is not bound to a field)\n\n\n\n\n\n\n\nThe `salary` column has a custom filter type, with the following operators: `gt`, `gte`, `lt` and `lte`.\n\n\n\n```ts file=\"filter-types-example.page.tsx\"\n\n```\n\n\n\n\n\nBy default, the `string` and `number` filter types are available. You can import the default filter types like this:\n\n```ts\nimport { defaultFilterTypes } from '@infinite-table/infinite-react';\n```\n\nIf you want to make all your instances of `InfiniteTable` have new operators for those filter types, you can simply mutate the exported `defaultFilterTypes` object.\n\n\n\n\n\nThe `string` columns have a new `Not includes` operator.\n\n\n\n```ts file=\"default-filter-types-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\nWhen you specify new , the default filter types of `string` and `number` are still available - unless the new object contains those keys and overrides them explicitly.\n\n\n\nThe current implementation of the default filter types is the following:\n\n```tsx\nexport const defaultFilterTypes: Record> = {\n string: {\n label: 'Text',\n emptyValues: [''],\n defaultOperator: 'includes',\n components: {\n FilterEditor: StringFilterEditor,\n },\n operators: [\n {\n name: 'includes',\n components: {\n Icon: // custom icon as a React component ...\n },\n label: 'includes',\n fn: ({ currentValue, filterValue }) => {\n return (\n typeof currentValue === 'string' &&\n typeof filterValue == 'string' &&\n currentValue.toLowerCase().includes(filterValue.toLowerCase())\n );\n },\n },\n {\n label: 'Equals',\n components: {\n Icon: // custom icon as a React component ...\n },\n name: 'eq',\n fn: ({ currentValue: value, filterValue }) => {\n return typeof value === 'string' && value === filterValue;\n },\n },\n {\n name: 'startsWith',\n components: {\n Icon: // custom icon as a React component ...\n },\n label: 'Starts With',\n fn: ({ currentValue: value, filterValue }) => {\n return value.startsWith(filterValue);\n },\n },\n {\n name: 'endsWith',\n components: {\n Icon: // custom icon as a React component ...\n },\n label: 'Ends With',\n fn: ({ currentValue: value, filterValue }) => {\n return value.endsWith(filterValue);\n },\n },\n ],\n },\n number: {\n label: 'Number',\n emptyValues: ['', null, undefined],\n defaultOperator: 'eq',\n components: {\n FilterEditor: NumberFilterEditor,\n },\n operators: [\n {\n label: 'Equals',\n components: {\n Icon: // custom icon as a React component ...\n },\n name: 'eq',\n fn: ({ currentValue, filterValue }) => {\n return currentValue == filterValue;\n },\n },\n {\n label: 'Not Equals',\n components: {\n Icon: // custom icon as a React component ...\n },\n name: 'neq',\n fn: ({ currentValue, filterValue }) => {\n return currentValue != filterValue;\n },\n },\n {\n name: 'gt',\n label: 'Greater Than',\n components: {\n Icon: // custom icon as a React component ...\n },\n fn: ({ currentValue, filterValue, emptyValues }) => {\n if (emptyValues.includes(currentValue)) {\n return true;\n }\n return currentValue > filterValue;\n },\n },\n {\n name: 'gte',\n components: {\n Icon: // custom icon as a React component ...\n },\n label: 'Greater Than or Equal',\n fn: ({ currentValue, filterValue, emptyValues }) => {\n if (emptyValues.includes(currentValue)) {\n return true;\n }\n return currentValue >= filterValue;\n },\n },\n {\n name: 'lt',\n components: {\n Icon: // custom icon as a React component ...\n },\n label: 'Less Than',\n fn: ({ currentValue, filterValue, emptyValues }) => {\n if (emptyValues.includes(currentValue)) {\n return true;\n }\n return currentValue < filterValue;\n },\n },\n {\n name: 'lte',\n components: {\n Icon: // custom icon as a React component ...\n },\n label: 'Less Than or Equal',\n fn: ({ currentValue, filterValue, emptyValues }) => {\n if (emptyValues.includes(currentValue)) {\n return true;\n }\n return currentValue <= filterValue;\n },\n }\n ],\n },\n };\n```\n\n\n\n\n\n> A custom React component to be used as an editor for the current filter type\n\nEvery filter type can define the following `components`\n\n- `FilterEditor` - a React component to be used as an editor for the current filter type\n- `FilterOperatorSwitch` - a custom component that is displayed at the left of the `FilterEditor` and can be used for switching between operators - only needed for very very advanced use-cases.\n\n\n\nFilter type operators can override the `FilterEditor` component - they can specify the following components:\n\n- `FilterEditor` - if specified, it overrides the `FilterEditor` of the filter type\n- `Icon` - a React component to be used as an icon for the operator - displayed by the menu triggered when clicking on the `FilterOperatorSwitch` component\n\n\n\n\n\n\n\nThe `canDesign` column is using a custom `bool` filter type with a custom filter editor.\n\nThe checkbox has indeterminate state, which will match all values in the data source.\n\n\n\n```ts file=\"$DOCS/reference/hooks/custom-filter-editor-hooks-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n> Controlled prop used for filtering. Can be used for both [client-side](/docs/learn/filtering/filtering-client-side) and [server-side](/docs/learn/filtering/filtering-server-side) filtering.\n\nFor the uncontrolled version, see \n\nIf you want to show the column filter editors, you have to either specify this property, or the uncontrolled - even if you have no initial filters. For no initial filters, use `filterValue=[]`.\n\nThe objects in this array have the following shape:\n\n- `filter` - an object describing the filter\n - `filter.value` - the value to filter by\n - `filter.type` - the current type of the filter (eg: `string`, `number` or another custom type you specify in the filterTypes prop)\n - `filter.operator` - the name of the operator being applied\n- `field` - the field being filtered - generally matched with a column. This is optional, as some columns can have no field.\n- `id` - the id of the column being filtered. This is optional - for columns bound to a field, the `field` should be used instead of the `id`.\n- `disabled` - whether this filter is applied or not\n\n\n\n```ts file=\"onFilterValueChange-example.page.tsx\"\n\n```\n\n\n\n\n\nYou can control the visibility of the column filters by using the prop.\n\n\n\n\n\n\n\n> Whether the datasource will load data lazily - useful for server-side grouping and pivoting. If set to `true` or to an object (with `batchSize` property), the prop must be a function that returns a promise.\n\n\n\n```ts file=\"$DOCS/learn/grouping-and-pivoting/pivoting/remote-pivoting-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n> Controls the expand/collapse state of group rows, when is used\n\n\nSee related , and \n\n\n```tsx title=\"Specifying the state for group rows\"\nconst groupRowsState: DataSourcePropGroupRowsStateObject = {\n collapsedRows: true,\n expandedRows: [['Mexico'], ['Mexico', 'backend'], ['India']],\n};\n```\n\nThe two properties in this object are `collapsedRows` and `expandedRows`, and each can have the following values:\n - `true` - meaning that all groups have this state\n - an array of arrays - representing the exceptions to the default value\n\n\nSo if you have `collapsedRows` set to `true` and then `expandedRows` set to `[['Mexico'], ['Mexico', 'backend'], ['India']]` then all rows are collapsed by default, except the rows specified in the `expandedRows`.\n\n\n\n```ts file=\"./group-rows-state-controlled-example.page.tsx\"\n```\n\n\n\n\n\n void\">\n\n> Callback prop when the changes.\n\nSee related and \n\nThis function is called with an object that's an instance of , when the user interacts with group rows and expands/collapses them.\n\nIf you want to get a plain object from this instance, call the `.getState()` method.\n\nSee reference to find out all the utility methods this instance gives you.\n\n\n\n\n\n> Specifies the initial expand/collapse state of group rows, when is used\n\n\nFor the controlled version, see related .\n\n\n```tsx title=\"Specifying the initial state for group rows\"\nconst defaultGroupRowsState: DataSourcePropGroupRowsStateObject = {\n expandedRows: true,\n collapsedRows: [['Mexico'], ['Mexico', 'backend'], ['India']],\n};\n```\n\nThe two properties in this object are `collapsedRows` and `expandedRows`, and each can have the following values:\n - `true` - meaning that all groups have this state\n - an array of arrays - representing the exceptions to the default value\n\n\nSo if you have `expandedRows` set to `true` and then `collapsedRows` set to `[['Mexico'], ['Mexico', 'backend'], ['India']]` then all rows are expanded by default, except the rows specified in the `collapsedRows`.\n\n\n```ts file=\"./group-rows-initial-state-example.page.tsx\"\n```\n\n\n\n\n\n\n> An array of objects with `field` properties, that control how rows are being grouped.\n\nEach item in the array can have the following properties:\n\n- field - `keyof DATA_TYPE`\n- column - config object for the group column - see .\n\n\n\nWhen using groupRenderStrategy=\"multi-column\", it can be very useful for each group to configure it's own column - use for this.\n\n\nSee for the type definition.\n\n\n\n```ts files=[\"groupBy-example.page.tsx\",\"columns.ts\"]\n\n```\n\n\n\n\n\n[]\">\n\n> An array of objects with `field` properties that control how pivoting works. Pivoting is very often associated with aggregations, so see related for more details.\n\nEach item in the array can have the following properties:\n\n- field - `keyof DATA_TYPE`\n- column - config object or function for generated pivot columns.\n\n\n\nFor more details on the type of the items in this array prop, see .\n\n\n\n\n\n```ts file=\"$DOCS/learn/grouping-and-pivoting/pivoting/pivoting-customize-column-example.page.tsx\"\n\n```\n\n\n\n\n \n>\">\n\n> An object that configures how the column for the current group should look like\n\nIf is specified, it overrides this property (the objects actually get merged, with `groupColumn` having higher priority and being merged last).\n\n\n\nIf you are using a groupRenderStrategy=\"single-column\", then using `groupBy.column` should not be used, as you could have many groups with conflicting column configuration.\n\nIn this case, use the prop.\n\n\n\n\n\n\n\nThis example uses `groupBy.column` to configure the generated columns corresponding to each group.\n\n\n\n```ts files=[\"groupBy-multi-with-column-example.page.tsx\",\"columns.ts\"]\n\n```\n\n\n\n\n\n\n\n> Whether the component should use live pagination.\n\nUse this in combination with and \n\n\n\n```ts file=\"$DOCS/learn/working-with-data/live-pagination-example.page.tsx\"\n\n```\n\n\n\n\n\n void\">\n\n> Callback prop to be called when the data changes via the DataSource API.\n\nCalled when any of the following methods have been called in the `DataSource` api\n\n- \n- \n\n- \n- \n\n- \n- \n\n- \n- \n\n- \n- \n\nThis callback is called with an object that has the following properties:\n\n- `primaryKeyField` - the field configured as the primary key for the ``\n- `mutations` - a `Map` with mutations. The keys in the map are primary keys of the mutated data\n\nThe values in the mutations are object descriptors of mutations, that have the following shape:\n\n- `type`: `'insert'|'update'|'delete'`\n- `originalData`: `DATA_TYPE | null` - the original data before the mutation. In case of `insert`, it will be `null`\n- `data`: `Partial` - the updates to be performed on the data. In case of `delete`, it will be `undefined`. This is an object that will contain the primary key, and the updated values for the data (not necessarily the full object, except for `insert`, where it will be of type `DATA_TYPE`).\n\n\n\nstring|number)\" defaulValue={undefined}>\n\n> A cursor value for live pagination. A good value for this is the id of the last item in the array. It can also be defined as a function\n\nUse this in combination with and \n\n\n\nWhen this is a function, it is called with a parameter object that has the following properties:\n\n- `array` - the current array of data\n- `lastItem` - the last item in the array\n- `length` - the length of the data array\n\n\n\n\n\n```ts file=\"$DOCS/learn/working-with-data/live-pagination-example.page.tsx\"\n\n```\n\n\n\n\n\n)=>void\">\n\n> A function to be called when data-related state changes.\n\nCan be used to implement \n\nThe function is called with an object that has the following properties:\n\n- `sortInfo` - current sort information - see for details\n- `groupBy` - current grouping information - see for details\n- `filterValue` - current filtering information - see for details\n- `livePaginationCursor` - the value for the live pagination cursor - see for details\n- `changes` - an object that can help you figure out what change caused `onDataParamsChange` to be called.\n\n\n\n```ts file=\"$DOCS/learn/working-with-data/live-pagination-example.page.tsx\"\n\n```\n\n\n\n\n void\">\n\n> Callback prop called when the changes.\n\nThis might not be called immediately, as there might be a set.\n\n\n\n```ts file=\"onFilterValueChange-example.page.tsx\"\n\n```\n\n\n\n\n\n void\">\n\n> A function to be called when the changes.\n\nAlso see related .\n\n\n\n) => void\">\n\n> The callback that is called when the `DataSource` is ready. The [`dataSourceApi`](/docs/reference/datasource-api) is passed as the first argument.\n\n\n\n void\">\n\n> A function to be called when the changes.\n\n\n\n\n\nUse your mouse to select/deselect cells.\n\n\n\n```ts file=\"$DOCS/learn/selection/controlled-cell-selection-example.page.tsx\"\n\n```\n\n\n\n\n\n void\">\n\n> A function to be called when the changes.\n\n\n\n\n\nUse your mouse or keyboard (press the spacebar) to select/deselect a single row.\n\n\n\n```ts file=\"$DOCS/reference/controlled-single-row-selection-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\nThis example shows how you can use multiple row selection with a predefined controlled value.\n\nGo ahead and select some groups/rows and see the selection value adjust.\n\nThe example also shows how you can use the `InfiniteTableApi` to retrieve the actual ids of the selected rows.\n\n\n\n```ts file=\"$DOCS/reference/controlled-multi-row-selection-example.page.tsx\"\n\n```\n\n\n\n\n void\">\n\n> Called when sorting changes on the DataSource.\n\nThe sorting can change either via a user interaction or by calling an API method (from the [root API](api) or the [Column API](column-api)).\n\nSee related for controlled sorting and for uncontrolled sorting.\n\n\n\n\n\n> A value that can be used to trigger a re-fetch of the data.\n\nBy updating the value of this prop (eg: you can use it as a counter, and increment it) the `` component reloads it's if it's defined as a function. More specifically, the `data` function is called again and the result will replace the current data.\n\n\n\n\n\nThis example shows how you can use the `refetchKey` to trigger reloading the data\n\n\n\n```ts file=\"refetchKey-example.page.tsx\"\n\n```\n\n\n \n\n\n\n\n> Describes the selected row(s) in the `DataSource`\n\nFor single selection, the prop will be of type: `number | string | null`. Use `null` for empty selection in single selection mode.\n\nFor multiple selection, the prop will have the following shape:\n\n```ts\nconst rowSelection = {\n selectedRows: [3, 6, 100, 23], // those specific rows are selected\n defaultSelection: false, // all rows deselected by default\n};\n\n// or\nconst rowSelection = {\n deselectedRows: [3, 6, 100, 23], // those specific rows are deselected\n defaultSelection: true, // all other rows are selected\n};\n\n// or, for grouped data - this example assumes groupBy=continent,country,city\nconst rowSelection = {\n selectedRows: [\n 45, // row with id 45 is selected, no matter the group\n ['Europe', 'France'], // all rows in Europe/France are selected\n ['Asia'], // all rows in Asia are selected\n ],\n deselectedRows: [\n ['Europe', 'France', 'Paris'], // all rows in Paris are deselected\n ],\n defaultSelection: false, // all other rows are selected\n};\n```\n\nFor using group keys in the selection value, see related \n\n\n\n\n\nUse your mouse or keyboard (press the spacebar) to select/deselect a single row.\n\n\n\n```ts file=\"$DOCS/reference/controlled-single-row-selection-example.page.tsx\"\n\n```\n\n\n\n\n\nWhen is being used - this means not all available groups/rows have actually been loaded yet in the dataset - we need a way to allow you to specify that those possibly unloaded rows/groups are selected or not. In this case, the `rowSelection.selectedRows`/`rowSelection.deselectedRows` arrays should not have row primary keys as strings/numbers, but rather rows/groups specified by their full path (so should be set to `true`).\n\n```ts {6}\n// this example assumes groupBy=continent,country,city\nconst rowSelection = {\n selectedRows: [\n // row with id 45 is selected - we need this because in the lazyLoad scenario,\n // not all parents might have been made available yet\n ['Europe','Italy', 'Rome', 45],\n ['Europe','France'], // all rows in Europe/France are selected\n ['Asia'] // all rows in Asia are selected\n ]\n deselectedRows: [\n ['Europe','Italy','Rome'] // all rows in Rome are deselected\n // but note that row with id 45 is selected, so Rome will be rendered with an indeterminate selection state\n ],\n defaultSelection: false // all other rows are selected\n}\n```\n\nIn the example above, we know that there are 3 groups (`continent`, `country`, `city`), so any item in the array that has a 4th element is a fully specified leaf node. While lazy loading, we need this fully specified path for specific nodes, so we know which group rows to render with indeterminate selection.\n\n\n\n\n\nThe prop can be used for both lazy and non-lazy `DataSource` components.\n\n\n\n\n\n\n\nThis example shows how you can use multiple row selection with a predefined controlled value.\n\nGo ahead and select some groups/rows and see the selection value adjust.\n\nThe example also shows how you can use the `InfiniteTableApi` to retrieve the actual ids of the selected rows.\n\n\n\n```ts file=\"$DOCS/reference/controlled-multi-row-selection-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n> Specifies the type of selection that should be enabled.\n\n\n\n\nRead more on row selection (`multi-row` and `single-row`).\n\n\n\n\n\nRead more on cell selection (`multi-cell` and `single-cell`).\n\n\n\n\n\n\n\n```ts file=\"selectionMode-example.page.tsx\"\n\n```\n\n\n\n\n\n[], arr: T[]) => T[]\">\n\n> Custom sorting function to replace the `multisort` function used by default.\n\nThe function specified in the prop is called with the as the first argument and the data array as the second. It should return a sorted array, as per the it was called with.\n\n\n\nWhen is specified, will be forced to `false`, as the sorting is done in the browser.\n\n\n\n\nThe `@infinite-table/infinite-react` package exports a `multisort` function - this is the default function used for local sorting.\n\n```ts\nimport { multisort } from '@infinite-table/infinite-react';\n\nconst arr: Developer[] = [\n /*...*/\n];\nconst sortInfo = [\n {\n field: 'age',\n dir: -1,\n },\n {\n field: 'name',\n dir: 1,\n },\n];\nmultisort(sortInfo, arr);\n```\n\nIf you want to implement your own custom sort function, the `multisort` fn is a good starting point you can use.\n\n\n\n\n\n```ts file=\"$DOCS/reference/datasource-props/local-sortFunction-single-sorting-example-with-local-data-example.page.tsx\"\n\n```\n\n\n\n\n\n|DataSourceSingleSortInfo[]|null\">\n\n> Information for sorting the data. This is a controlled prop.\n\nAlso see related (uncontrolled version), , and .\n\nSorting can be single (only one field/column can be sorted at a time) or multiple (multiple fields/columns can be sorted at the same time). Therefore, this property an be an array of objects or a single object (or null) - the shape of the objects (of type `DataSourceSingleSortInfo`)is the following.\n\n- `dir` - `1 | -1` - the direction of the sorting\n- `field`? - `keyof DATA_TYPE` - the field to sort\n- `id`? - `string` - if you don't sort by a field, you can specify an id of the column this sorting is bound to. Note that columns have a valueGetter, which will be used when doing local sorting and the column is not bound to an exact field.\n- `type` - the sort type - one of the keys in - eg `\"string\"`, `\"number\"` - will be used for local sorting, to provide the proper comparison function.\n\nWhen you want to use multiple sorting, but have no default sort order/information, use `[]` (the empty array) to denote multiple sorting should be enabled.\n\nIf no `sortInfo` is provided, by default, when clicking a sortable column, single sorting will be applied.\n\n\n\nFor configuring if a column is sortable or not, see and . By default, all columns are sortable.\n\n\n\n\n\n```ts file=\"$DOCS/learn/sorting/remote-controlled-multi-sorting-example.page.tsx\"\n\n```\n\n\n\n\n\n\n\n\n\n> Specifies if changes in the should trigger a reload of the data source - applicable when is a function. Replaces the deprecated .\n\nSee related and .\n\nWhen set to `false` (the default), the data is sorted locally (in the browser) after the data-source is loaded. When set to `true`, the data should be sorted by the server (or by the data-source function that serves the data).\n\nSee [the Sorting page](/docs/learn/sorting/overview) for more details.\n\nFor configuring the sorting behavior when multiple sorting is enabled, see .\n\n\n\n\n\n> Specifies which changes in the data-related props should trigger a reload of the data source - applicable when is a function.\n\nSee .\nSee .\nSee .\nSee .\n\n\n\n\n\n\n> Specifies where the sorting should be done. Use instead.\n\nSee related and .\n\nWhen set to `'local'`, the data is sorted locally (in the browser) after the data-source is loaded. When set to `'remote'`, the data should be sorted by the server (or by the data-source function that serves the data).\n\nSee [the Sorting page](/docs/learn/sorting/overview) for more details.\n\nFor configuring the sorting behavior when multiple sorting is enabled, see .\n\n\n\n number)>\">\n\n> Describes the available sorting functions used for local sorting. The object you provide will be merged into the default sort types.\n\nCurrently there are two `sortTypes` available:\n\n- `\"string\"`\n- `\"number\"`\n- `\"date\"`\n\nThose values can be used for the column.sortType and column.dataType properties.\n\n```ts\n// default implementation\nconst sortTypes = {\n string: (a, b) => a.localeCompare(b),\n number: (a, b) => a - b,\n date: (a, b) => a - b,\n};\n```\n\nWhen a column does not explicitly specify the column.sortType, the column.dataType will be used instead. And if no column.dataType is defined, it will default to `string`.\n\nYou can add new sort types to the DataSource and InfiniteTable components by specifying this property - the object will be merged into the default sort types.\n\n\n\n```ts file=\"./sortTypes-example.page.tsx\"\n\n```\n\n\n\n\n\nIn this example, for the `\"color\"` column, we specified column.sortType=\"color\" - we could have passed that as `column.dataType` instead, but if the grid had filtering, it wouldn't know what filters to use for \"color\" - so we usedcolumn.sortType to only change how the data is sorted.\n\n\n\n\n\n\n\n> Specifies whether contains group keys or only row ids/primary keys.\n\nWhen this is `true`, you might want to use the [getSelectedPrimaryKeys](./selection-api#getSelectedPrimaryKeys) method.\n\n\n\n\n\nThis example shows how you can use have row selection with group keys instead of just the primary keys of rows.\n\n\n\n```ts file=\"$DOCS/reference/controlled-multi-row-selection-example-with-group-keys.page.tsx\"\n\n```\n\n\n\n\n\n\n" }, "/docs/reference/row-selection-api/": { "filePath": "/docs/reference/row-selection-api/index", @@ -3017,7 +3047,7 @@ }, "excerpt": "```tsx title=\"Configuring the selection mode to be 'multi-row'\"", "readingTime": "7 min read", - "content": "\n```tsx title=\"Configuring the selection mode to be 'multi-row'\"\n\n\n// can be \"single-row\", \"multi-row\", \"multi-cell\" or false\n```\n\n\n\nTo enable multi-row selection, you need to specify selectionMode=\"multi-row\" on the `` component.\n\n\n\nYou can retrieve the row selection api by reading it from the `api.rowSelectionApi` property.\n\n```tsx {4}\n\nconst onReady = ({api}: {api:InfiniteTableApi}) => {\n // do something with it\n api.rowSelectionApi.selectGroupRow(['USA'])\n}\n\n\n columns={[...]}\n onReady={onReady}\n/>\n```\n\nSee the [Infinite Table API page](/docs/reference/api) for the main API.\nSee the [Infinite Table Cell Selection API page](/docs/reference/cell-selecti-api) for the cell selection API.\nSee the [Infinite Table Column API page](/docs/reference/column-api) for the column API.\nSee the [Infinite Table Row Detail API page](/docs/reference/row-detail-api) for the row detail API (when master-detail is configured).\n\n\n\n\n\n> Boolean getter to report whether all the rows are selected or not\n\n\n\n void\">\n\n> Deselects the group row that is identified by the given group keys. Only makes sense when the DataSource is grouped.\n\nCalling this method triggers a call to DataSource.onRowSelectionChange.\n\nFor selecting a group row, see related [selectGroupRow](#selectGroupRow).\n\n\n\nMost often, you don't need to use this imperative way of selecting group rows. Simply update the DataSource.rowSelection to exclude the group row from the selection.\n\n\n\n\n\n boolean\">\n\n> Deselects the specified row. Optionally provide the group keys, if you have access to them.\n\nSee note from [isRowSelected](#isRowSelected) for whether you need to provide the `groupKeys` or not.\n\nCalling this method triggers a call to DataSource.onRowSelectionChange.\n\nFor selecting the row, see related [selectRow](#selectRow).\n\n\n\nMost often, you don't need to use this imperative way of deselecting rows. Simply update the DataSource.rowSelection to remove the row you want from the selection.\n\n\n\n\n\n void\">\n\n> Deselects all the rows in the DataSource.\n\nCalling this method triggers a call to DataSource.onRowSelectionChange.\n\nFor selecting all rows, see related [selectAll](#selectAll).\n\n\n\nMost often, you don't need to use this imperative way of deselecting all rows. Simply update the DataSource.rowSelection (when multiple row selection is enabled) to a value of\n\n```tsx\n{ defaultSelection: false, selectedRows: []}\n```\n\n\n\n\n\n true|false|null\">\n\n> Returns the state of a group row - only applicable when the DataSource is grouped\n\nThe returned values can be:\n\n- `true` - the group row and all its children are selected, at any level of nesting\n- `false` - the group row and all its children are deselected, at any level of nesting\n- `null` - the group row has some (not all) children selected, at any level of nesting\n\nBaiscally, `true` means the group row and all children are selected, `false` means the group row is not selected and doesn't have any selected children, while `null` is the indeterminate state, where just some (but not all) of the children of the group are selected.\n\nIf you provide a the value of a `rowSelection`, it will be used as the source of truth for selection. If no value for `rowSelection` is provided, it will use the current row selection.\n\n\n\nIf you don't provide a value for the `rowSelection` and are calling this method in the callback prop, you might be one step behind the selection. In such a case, make sure you pass to this function the value you receive in the callback.\n\n\n\n\n\n (string|number)[]\">\n\n> Retrieves the ids (primary keys) of the selected rows, when the selection contains group keys instead of primary keys (so when is `true` and the DataSource is grouped).\n\nIf you provide a the value of a `rowSelection`, it will be used as the source of truth for retrieving the row ids. If no value for `rowSelection` is provided, it will use the current row selection.\n\n\n\nThis will not work properly when the `DataSource` is configured with lazy loading, since it cannot give you primary keys of rows not yet loaded.\n\n\n\n\n\nIf you don't provide a value for the `rowSelection` and are calling this method in the callback prop, you might be one step behind the selection. In such a case, make sure you pass to this function the value you receive in the callback.\n\n\n\n\n\n\n\nThis example shows how you can use getSelectedPrimaryKeys with multiple row selection to retrieve the actual ids of the selected rows.\n\n\n\n```ts file=\"$DOCS/reference/controlled-multi-row-selection-example-with-group-keys.page.tsx\"\n\n```\n\n\n\n\n\n boolean\">\n\n> Checks if a row specified by its primary key is selected or not. Optionally provide the group keys, if you have access to them.\n\n\nThe group keys are not mandatory, and they are useful only when the DataSource is grouped.\n\nEven if you don't pass them, the component will try to retrieve them from its internal state - note though that in lazy-load scenarios, not all rows/groups may have been loaded, so in this case, you have to make sure you provide the `groupKeys` when calling this method.\n\n\n\n\n\n boolean\">\n\n> Checks if a row specified by its primary key is deselected or not. Optionally provide the group keys, if you have access to them.\n\nSee note from [isRowSelected](#isRowSelected)\n\n\n\n void\">\n\n> Selects all the rows in the DataSource.\n\nCalling this method triggers a call to DataSource.onRowSelectionChange.\n\nFor deselecting all rows, see related [deselectAll](#deselectAll).\n\n\n\nMost often, you don't need to use this imperative way of selecting rows. Simply update the DataSource.rowSelection (when multiple row selection is enabled) to a value of\n\n```tsx\n{ defaultSelection: true, deselectedRows: []}\n```\n\n\n\n\n\n void\">\n\n> Selects the group row that is identified by the given group keys. Only makes sense when the DataSource is grouped.\n\nCalling this method triggers a call to DataSource.onRowSelectionChange.\n\nFor deselecting a group row, see related [deselectGroupRow](#deselectGroupRow).\n\n\n\nMost often, you don't need to use this imperative way of selecting group rows. Simply update the DataSource.rowSelection to include the group row you want to select.\n\n\n\n\n\n boolean\">\n\n> Selects the specified row. Optionally provide the group keys, if you have access to them.\n\nSee note from [isRowSelected](#isRowSelected) for whether you need to provide the `groupKeys` or not.\n\nCalling this method triggers a call to DataSource.onRowSelectionChange.\n\nFor deselecting the row, see related [deselectRow](#deselectRow).\n\n\n\nMost often, you don't need to use this imperative way of selecting rows. Simply update the DataSource.rowSelection to include the row you want in the selection.\n\n\n\n\n\n void\">\n\n> Toggles the selection of the group row that is identified by the given group keys. Only makes sense when the DataSource is grouped.\n\nCalling this method triggers a call to DataSource.onRowSelectionChange.\n\nFor deselecting a group row, see related [deselectGroupRow](#deselectGroupRow).\nFor selecting a group row, see related [selectGroupRow](#selectGroupRow).\n\n\n\nMost often, you don't need to use this imperative way of selecting group rows. Simply update the DataSource.rowSelection to include the group row you want to select.\n\n\n\n\n\n boolean\">\n\n> Toggles the selection of the specified row. Optionally provide the group keys, if you have access to them.\n\nSee note from [isRowSelected](#isRowSelected) for whether you need to provide the `groupKeys` or not.\n\nCalling this method triggers a call to DataSource.onRowSelectionChange.\n\nFor deselecting the row, see related [deselectRow](#deselectRow).\nFor selecting the row, see related [selectRow](#selectRow).\nFor toggling the selection for a group row, see related [toggleGroupRowSelection](#toggleGroupRowSelection).\n\n\n\nMost often, you don't need to use this imperative way of selecting rows. Simply update the DataSource.rowSelection to include or exclude the given row.\n\n\n\n\n\n\n" + "content": "\n```tsx title=\"Configuring the selection mode to be 'multi-row'\"\n\n\n// can be \"single-row\", \"multi-row\", \"multi-cell\" or false\n```\n\n\n\nTo enable multi-row selection, you need to specify selectionMode=\"multi-row\" on the `` component.\n\n\n\nYou can retrieve the row selection api by reading it from the `api.rowSelectionApi` property.\n\n```tsx {4}\n\nconst onReady = ({api}: {api:InfiniteTableApi}) => {\n // do something with it\n api.rowSelectionApi.selectGroupRow(['USA'])\n}\n\n\n columns={[...]}\n onReady={onReady}\n/>\n```\n\nSee the [Infinite Table API page](/docs/reference/api) for the main API.\nSee the [Infinite Table Cell Selection API page](/docs/reference/cell-selection-api) for the cell selection API.\nSee the [Infinite Table Column API page](/docs/reference/column-api) for the column API.\nSee the [Infinite Table Row Detail API page](/docs/reference/row-detail-api) for the row detail API (when master-detail is configured).\n\n\n\n\n\n> Boolean getter to report whether all the rows are selected or not\n\n\n\n void\">\n\n> Deselects the group row that is identified by the given group keys. Only makes sense when the DataSource is grouped.\n\nCalling this method triggers a call to DataSource.onRowSelectionChange.\n\nFor selecting a group row, see related [selectGroupRow](#selectGroupRow).\n\n\n\nMost often, you don't need to use this imperative way of selecting group rows. Simply update the DataSource.rowSelection to exclude the group row from the selection.\n\n\n\n\n\n boolean\">\n\n> Deselects the specified row. Optionally provide the group keys, if you have access to them.\n\nSee note from [isRowSelected](#isRowSelected) for whether you need to provide the `groupKeys` or not.\n\nCalling this method triggers a call to DataSource.onRowSelectionChange.\n\nFor selecting the row, see related [selectRow](#selectRow).\n\n\n\nMost often, you don't need to use this imperative way of deselecting rows. Simply update the DataSource.rowSelection to remove the row you want from the selection.\n\n\n\n\n\n void\">\n\n> Deselects all the rows in the DataSource.\n\nCalling this method triggers a call to DataSource.onRowSelectionChange.\n\nFor selecting all rows, see related [selectAll](#selectAll).\n\n\n\nMost often, you don't need to use this imperative way of deselecting all rows. Simply update the DataSource.rowSelection (when multiple row selection is enabled) to a value of\n\n```tsx\n{ defaultSelection: false, selectedRows: []}\n```\n\n\n\n\n\n true|false|null\">\n\n> Returns the state of a group row - only applicable when the DataSource is grouped\n\nThe returned values can be:\n\n- `true` - the group row and all its children are selected, at any level of nesting\n- `false` - the group row and all its children are deselected, at any level of nesting\n- `null` - the group row has some (not all) children selected, at any level of nesting\n\nBaiscally, `true` means the group row and all children are selected, `false` means the group row is not selected and doesn't have any selected children, while `null` is the indeterminate state, where just some (but not all) of the children of the group are selected.\n\nIf you provide a the value of a `rowSelection`, it will be used as the source of truth for selection. If no value for `rowSelection` is provided, it will use the current row selection.\n\n\n\nIf you don't provide a value for the `rowSelection` and are calling this method in the callback prop, you might be one step behind the selection. In such a case, make sure you pass to this function the value you receive in the callback.\n\n\n\n\n\n (string|number)[]\">\n\n> Retrieves the ids (primary keys) of the selected rows, when the selection contains group keys instead of primary keys (so when is `true` and the DataSource is grouped).\n\nIf you provide a the value of a `rowSelection`, it will be used as the source of truth for retrieving the row ids. If no value for `rowSelection` is provided, it will use the current row selection.\n\n\n\nThis will not work properly when the `DataSource` is configured with lazy loading, since it cannot give you primary keys of rows not yet loaded.\n\n\n\n\n\nIf you don't provide a value for the `rowSelection` and are calling this method in the callback prop, you might be one step behind the selection. In such a case, make sure you pass to this function the value you receive in the callback.\n\n\n\n\n\n\n\nThis example shows how you can use getSelectedPrimaryKeys with multiple row selection to retrieve the actual ids of the selected rows.\n\n\n\n```ts file=\"$DOCS/reference/controlled-multi-row-selection-example-with-group-keys.page.tsx\"\n\n```\n\n\n\n\n\n boolean\">\n\n> Checks if a row specified by its primary key is selected or not. Optionally provide the group keys, if you have access to them.\n\n\nThe group keys are not mandatory, and they are useful only when the DataSource is grouped.\n\nEven if you don't pass them, the component will try to retrieve them from its internal state - note though that in lazy-load scenarios, not all rows/groups may have been loaded, so in this case, you have to make sure you provide the `groupKeys` when calling this method.\n\n\n\n\n\n boolean\">\n\n> Checks if a row specified by its primary key is deselected or not. Optionally provide the group keys, if you have access to them.\n\nSee note from [isRowSelected](#isRowSelected)\n\n\n\n void\">\n\n> Selects all the rows in the DataSource.\n\nCalling this method triggers a call to DataSource.onRowSelectionChange.\n\nFor deselecting all rows, see related [deselectAll](#deselectAll).\n\n\n\nMost often, you don't need to use this imperative way of selecting rows. Simply update the DataSource.rowSelection (when multiple row selection is enabled) to a value of\n\n```tsx\n{ defaultSelection: true, deselectedRows: []}\n```\n\n\n\n\n\n void\">\n\n> Selects the group row that is identified by the given group keys. Only makes sense when the DataSource is grouped.\n\nCalling this method triggers a call to DataSource.onRowSelectionChange.\n\nFor deselecting a group row, see related [deselectGroupRow](#deselectGroupRow).\n\n\n\nMost often, you don't need to use this imperative way of selecting group rows. Simply update the DataSource.rowSelection to include the group row you want to select.\n\n\n\n\n\n boolean\">\n\n> Selects the specified row. Optionally provide the group keys, if you have access to them.\n\nSee note from [isRowSelected](#isRowSelected) for whether you need to provide the `groupKeys` or not.\n\nCalling this method triggers a call to DataSource.onRowSelectionChange.\n\nFor deselecting the row, see related [deselectRow](#deselectRow).\n\n\n\nMost often, you don't need to use this imperative way of selecting rows. Simply update the DataSource.rowSelection to include the row you want in the selection.\n\n\n\n\n\n void\">\n\n> Toggles the selection of the group row that is identified by the given group keys. Only makes sense when the DataSource is grouped.\n\nCalling this method triggers a call to DataSource.onRowSelectionChange.\n\nFor deselecting a group row, see related [deselectGroupRow](#deselectGroupRow).\nFor selecting a group row, see related [selectGroupRow](#selectGroupRow).\n\n\n\nMost often, you don't need to use this imperative way of selecting group rows. Simply update the DataSource.rowSelection to include the group row you want to select.\n\n\n\n\n\n boolean\">\n\n> Toggles the selection of the specified row. Optionally provide the group keys, if you have access to them.\n\nSee note from [isRowSelected](#isRowSelected) for whether you need to provide the `groupKeys` or not.\n\nCalling this method triggers a call to DataSource.onRowSelectionChange.\n\nFor deselecting the row, see related [deselectRow](#deselectRow).\nFor selecting the row, see related [selectRow](#selectRow).\nFor toggling the selection for a group row, see related [toggleGroupRowSelection](#toggleGroupRowSelection).\n\n\n\nMost often, you don't need to use this imperative way of selecting rows. Simply update the DataSource.rowSelection to include or exclude the given row.\n\n\n\n\n\n\n" }, "/docs/reference/selection-api/": { "filePath": "/docs/reference/selection-api/index", @@ -3048,20 +3078,6 @@ "readingTime": "3 min read", "content": "\nWhen rendering the `TreeDataSource` component, you can get access to the Tree API by reading it from the [DataSource API](/docs/reference/datasource-api) property.\n\n```tsx {3}\n\n onReady={(api: DataSourceApi) => {\n api.treeApi // <----\n // treeApi is accessible here\n // you may want to store a reference to it in a ref or somewhere in your app state\n \n }}\n/>\n```\n\nFor updating tree nodes, see the following methods:\n\n- \n- \n\n\n\n void\">\n\n> Expands all the nodes in the tree. See related prop.\n\n\n\n```tsx file=\"tree-expandall-example.page.tsx\"\n\n```\n\n\n\n\n\n void\">\n\n> Selects all the nodes in the tree. See related prop.\n\nThis works if the tree has selection enabled. See [tree selection](/docs/learn/tree-grid/tree-selection) for more details.\n\n\n\n```tsx file=\"tree-selectall-example.page.tsx\"\n```\n\n\n\n\n\n void\">\n\n> Selects the node with the given node path. See related and methods.\n\n\n\n\n```tsx file=\"tree-selectnode-example.page.tsx\"\n```\n\n\n\n\n\n void\">\n\n> Deselects the node with the given node path. See related and methods.\n\n\n\n```tsx file=\"tree-selectnode-example.page.tsx\"\n```\n\n\n\n\n\n\n void\">\n\n> Toggles the selection state of the node with the given node path. See related and methods.\n\n\n\n```tsx file=\"tree-selectnode-example.page.tsx\"\n```\n\n\n\n\n\n void\">\n\n> Deselects all the nodes in the tree. See related prop.\n\n\nThis works if the tree has selection enabled. See [tree selection](/docs/learn/tree-grid/tree-selection) for more details.\n\n\n\n```tsx file=\"tree-selectall-example.page.tsx\"\n```\n\n\n\n\n\n void\">\n\n> Collapses all the nodes in the tree. See related prop.\n\n\n\n```tsx file=\"tree-expandall-example.page.tsx\"\n\n```\n\n\n\n\n\n\n boolean\">\n\n> Returns `true` if the node is expanded, `false` otherwise.\n\n\n\n```tsx file=\"tree-toggleNodeExpandState-example.page.tsx\"\n```\n\n\n\n\n\n void\">\n\n> Toggles the node with the give node path.\n\nIf the node at the given path is expanded, it will be collapsed and vice versa.\n\nSee related and methods.\n\n\n\n void\">\n\n> Expands the node with the given node path. See related and methods.\n\nExpands the node. Does not affect other child nodes.\n\n\n\n```tsx file=\"tree-toggleNodeExpandState-example.page.tsx\"\n```\n\n\n\n\n\nIf `options.force` is `true`, the node will be expanded even if is `true` for the given node.\n\n\n\n\n void\">\n\n> Collapses the node with the given node path. See related and methods.\n\nCollapses the node. Does not affect other child nodes.\n\n\n\n```tsx file=\"tree-toggleNodeExpandState-example.page.tsx\"\n```\n\n\n\n\n\nIf `options.force` is `true`, the node will be collapsed even if is `true` for the given node.\n\n\n\n\n\n" }, - "/docs/reference/type-definitions/": { - "filePath": "/docs/reference/type-definitions/index", - "routePath": "/docs/reference/type-definitions/", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/reference/type-definitions/index.page.md", - "fileName": "index.page.md", - "folderPath": "/docs/reference/type-definitions/", - "frontmatter": { - "title": "Infinite Table Type Definitions", - "description": "TypeScript type definitions for Infinite Table" - }, - "excerpt": "These are the public type definitions for `InfiniteTable` and related components, that you can import with named imports from the `@infinite-table/infinite-react` package.", - "readingTime": "20 min read", - "content": "\nThese are the public type definitions for `InfiniteTable` and related components, that you can import with named imports from the `@infinite-table/infinite-react` package.\n\n```tsx title=\"Importing the type for rowInfo\"\nimport type { InfiniteTableRowInfo } from '@infinite-table/infinite-react';\n```\n\n\n\nThe types of all properties in the `InfiniteTable` and `DataSource` components respect the following naming convention: `Prop`\n\nSo, for example, the type for is \n\n\n\n\n\n\n\n> Represents the selection state of the tree nodes. See for more details.\n\n```ts\nimport type { TreeSelectionValue } from '@infinite-table/infinite-react';\n```\n\nThe selection value is an object with the following properties:\n\n- `defaultSelection`: `boolean` - whether the tree nodes are selected by default or not.\n- `selectedPaths?`: `NodePath[]` - the paths of the selected nodes. Mandatory if `defaultSelection` is `false`.\n- `deselectedPaths`: `NodePath[]` - the paths of the deselected nodes. Mandatory if `defaultSelection` is `true`.\n\n```tsx title=\"Example of tree selection value\"\nconst treeSelection: TreeSelectionValue = {\n defaultSelection: false,\n selectedPaths: [['1'], ['2', '20']],\n deselectedPaths: [['1','10']],\n};\n// node ['1'] will be selected but indeterminate\n// since ['1','10'] is in the deselectedPaths\n// node ['2','20'] will be fully selected\n```\n\n\n\n\n\n> Represents the expand/collapse state of the tree nodes. See for more details.\n\n```ts\nimport type { TreeExpandStateValue } from '@infinite-table/infinite-react';\n```\n\nYou can specify the expand/collapse state of the tree nodes in two ways:\n\n1. With node paths (recommended)\n\nWhen using node paths, the object should have the following properties:\n\n- `defaultExpanded`: `boolean` - whether the tree nodes are expanded by default or not.\n- `collapsedPaths`: `string[]` - when `defaultExpanded` is `true`, this is a mandatory prop.\n- `expandedPaths`: `string[]` - when `defaultExpanded` is `false`, this is a mandatory prop.\n\n```tsx title=\"Example of treeExpandState with node paths\"\nconst treeExpandState = {\n defaultExpanded: true,\n collapsedPaths: [\n ['1', '10'],\n ['2', '20'],\n ['5']\n ],\n expandedPaths: [\n ['1', '4'],\n ['5','nested node in 5'],\n ],\n};\n```\n\n2. With node ids\n\nWhen using node ids, the object should have the following properties:\n\n- `defaultExpanded`: `boolean` - whether the tree nodes are expanded by default or not.\n- `collapsedIds`: `string[]` - when `defaultExpanded` is `true`, this is a mandatory prop.\n- `expandedIds`: `string[]` - when `defaultExpanded` is `false`, this is a mandatory prop.\n\n\n```tsx title=\"Example of treeExpandState with node ids\"\nconst treeExpandState = {\n defaultExpanded: true,\n collapsedIds: ['1', '2', '5'],\n expandedIds: ['10', '20', 'nested node in 5'],\n};\n```\n\n\n\n\n\n> Represents the collapse/expand state of row details - when [master-detail is configured](/docs/learn/master-detail/overview). Also see for the most important property in the master-detail configuration.\n\nThis class can be instantiated and the value passed to the prop (or its uncontrolled variant, ).\n\n```tsx title=\"Passing an instance of RowDetailState to the InfiniteTable\"\nconst rowDetailState = new RowDetailState({\n collapsedRows: true,\n expandedRows: [2, 3, 4],\n});\n\n rowDetailState={rowDetailState} />;\n```\n\n```tsx title=\"Passing an object literal to the InfiniteTable\"\n\n rowDetailState={{\n collapsedRows: true,\n expandedRows: [2, 3, 4],\n }}\n/>\n```\n\n\n\nThe instance is only useful if you want to interrogate the object, with methods like `areAllCollapsed()`, `areAllExpanded()`, `isRowDetailsExpanded(rowId)` and so on.\n\nWhen using the callback, it's called with an instance of this class - if you want to use the object literal, make sure you call `rowDetailState.getState()` to get the plain object.\n\n\nThe and accept both an object literal and an instance of this class.\n\nThe object literal has the following properties:\n\n- `collapsedRows`: `boolean | any[]` - if `true`, all row details are collapsed. If an array, it contains the row ids of the rows that are collapsed.\n- `expandedRows`: `boolean | any[]` - if `true`, all row details are expanded. If an array, it contains the row ids of the rows that are expanded.\n\nYou can create an instance using the object literal notation and you can get the object literal from the instance using the `getState` method:\n\n```tsx\nconst rowDetailState = new RowDetailState({\n collapsedRows: true,\n expandedRows: [2, 3, 4],\n});\nconst clone = new RowDetailState(rowDetailState);\n\nconst state = rowDetailState.getState();\n```\n\nYou can mark rows as expanded/collapsed even after creating the instance:\n\n```tsx\nconst rowDetailState = new RowDetailState({\n collapsedRows: true,\n expandedRows: [2, 3, 4],\n});\nrowDetailState.expandRowDetails(5);\nrowDetailState.collapseRowDetails(2);\n\n// now you can pass this instance back to the InfiniteTable component\n```\n\n\n\n\n\n> The type for the object passed into the function prop of the `DataSource` component.\n\nWhen the function is called, it will be called with an object of this type.\n\nThe following properties are available on this object:\n\n- `sortInfo?` - - the current sort info for the grid.\n- `groupBy?` - an array of - the current group by for the grid.\n- `pivotBy?` - an array of - the current pivot by for the grid.\n- `filterValue?` - an array of - the current filter value for the grid.\n- `masterRowInfo?` - - only available if the DataSource is a detail DataSource - meaning there is a master DataGrid, and the DataSource is used to load the detail DataGrid.\n\n\n\n\n\n> The type for the items in the array prop of the `DataSource` component.\n\n\n\n\n\n> Describes the collapse/expand state for group rows, when [grouping is used](/docs/learn/grouping-and-pivoting/grouping-rows). \n\nThis is a class, and instances of it can be used as a value for the / props.\n\nIt's the sole argument available in the callback.\n\nIt gives you the following additional utility methods:\n\n - `getState()`\n - `areAllExpanded()`\n - `areAllCollapsed()`\n - `expandAll()`\n - `collapseAll()`\n - `isGroupRowExpanded(keys: any[][])`\n - `isGroupRowCollapsed(keys: any[][])`\n - `expandGroupRow(keys: any[][])`\n - `collapseGroupRow(keys: any[][])`\n - `toggleGroupRow(keys: any[][])`\n\n\nTo create an instance, pass a plain object that describes the / value:\n\n\n```tsx\nconst state = new GroupRowsState({\n expandedRows: true,\n collapsedRows: [\n ['Europe']\n ['Europe','France'],\n ['Italy']\n ]\n})\n\nconsole.log(state.getState())\n// will log the above object that was used \n// as the sole argument for the constructor\n\n```\n\n\n\nWhen you call those methods, be aware you're not updating the React state! So you'll have to clone the object, call the method on the clone and then update the React state - in the code below, notice the `onClick` code for the `Expand all`/`Collapse all` buttons.\n\n\n\n```ts file=\"using-group-rows-state-controlled-example.page.tsx\"\n```\n\n\n\n\n\n\n\n\n\n> Describes a pivot value for the grid.\n\nThis is the type for the items in the array prop of the `DataSource` component.\n\nThe most important property in this type is the `field` - which will be `keyof DATA_TYPE` - the field to pivot by.\n\nAnother important property in this type is the `column`. It will be used to configure the generated pivot columns:\n\n- if it's an object literal, it will be applied to all generated columns\n- if it's a function, it will be called for each generated column, and the return value will be used to configure the column.\n\n```tsx\nconst pivotBy: DataSourcePivotBy[] = [\n { field: 'country' },\n {\n field: 'canDesign',\n column: ({ column: pivotCol }) => {\n const lastKey =\n pivotCol.pivotGroupKeys[pivotCol.pivotGroupKeys.length - 1];\n\n return {\n header: lastKey === 'yes' ? '๐Ÿ’… Designer' : '๐Ÿ’ป Non-designer',\n };\n },\n },\n];\n```\n\n\n\n\n\n> Represents runtime information passed to rendering and styling functions called when rendering the column headers\n\nThis object is passed to , , and functions.\n\nIt is an object with the following properties:\n\n- `column` - see for details\n- `columnSortInfo` - the current sort info for the column. it will be an object of type or `null`.\n- `filtered: boolean` - if the column is currently filtered or not\n- `api` - [`InfiniteTableApi`](/docs/reference/api) - the api object.\n- `columnApi` - [`InfiniteTableColumnApi`](/docs/reference/column-api) - the column api object.\n- `renderBag` - an object with various JSX values, the default elements rendered by the Infinite Table for the column header. It contains the following properties:\n - `header` - the default column header text\n - `sortIcon` - the default sort icon\n - `menuIcon` - the default column menu icon\n - `filterIcon` - the default column filter icon\n - `selectionCheckBox` - the default column selection checkbox\n\n```tsx title=\"Example column.renderHeader function\"\nconst renderHeader = ({ renderBag }) => {\n return (\n \n ({renderBag.header}) {renderBag.sortIcon}\n \n );\n};\nconst columns = {\n salary: {\n field: 'salary',\n type: 'number',\n renderHeader,\n },\n};\n```\n\n\n\n\n\n> Represents runtime information passed to many styling functions called when rendering the column cells\n\nThis object is passed at runtime during the rendering of column cells.\n\nIt is an object with the following properties:\n\n- `column` - see for details\n- `rowInfo` - see for details\n- `data` - the data object for the current row. The type of this object is `DATA_TYPE | Partial | null`. For regular rows, it will be of type `DATA_TYPE`, while for group rows it will be `Partial`. For rows not yet loaded (because of batching being used), it will be `null`.\n- `value` - the underlying value of the current cell - will generally be `data[column.field]`, if the column is bound to a `field` property\n- `inEdit`: `boolean`\n- `editError`: `Error`\n- `rowSelected`: `boolean | null;`\n- `rowActive`: `boolean | null`\n- `rowHasSelectedCells`: `boolean` - if the current row has selected cells or not\n\n\n\nThe following functions all have this as first argument:\n\n- \n- \n\n\n\n\n\n\n> Represents runtime information passed to many styling functions called when rendering rows/cells\n\nThis object is passed at runtime during the rendering of grid rows/cells.\n\nIt is an object with the following properties:\n\n- `rowInfo` - see for details\n- `rowIndex`: `number` - the index of the row\n- `rowHasSelectedCells`: `boolean` - if the current row has selected cells or not\n\n\n\nThe following functions all have this as first argument:\n\n- \n- \n- \n\n\n\n\n\n\n> Represents information on a specific sort. Contains info about the field to sort by, the sort direction, and the sort type.\n\nThis is the referenced by the prop.\n\nBasically the prop can be either an array of objects, or a single object (or null).\n\nThese are the type properties:\n\n- `dir`: `1 | -1` - 1 means ascending sort order; -1 means descending sort order.\n- `field?`: `keyof DATA_TYPE` - the field to sort by.\n- `id?`: `string` - an id for the sort info. When a column is not bound to a `field`, use the column id as the `id` property of the sort info, if you need to specify a default sort order by that column. Note that columns have a valueGetter, which will be used when doing local sorting and the column is not bound to an exact field.\n- `type?`: `string` - the sort type to apply. See for more details. For example, you can use `\"string\"` or `\"number\"` or `\"date\"`\n\n\n\n\n\n> The type of the DataSource prop.\n\nValid types for this prop are:\n\n- `null`\n- \n- []\n\n\n\n\n\n> The type of the prop. Basically this type is an array of .\n\n\n\n\n\n> This represents an enhanced column definition for a column. A computed column is basically a column with more information computed at runtime, based on everything Infinite Table can aggregate about it.\n\nThis type also includes the properties of the `InfinteTableColumn` type: , , , etc.\n\nAdditional type properties:\n\n- `id`: `string` - the id of the column. This is the same as the prop.\n- `computedEditable`: `boolean| Function` - whether this column is ediable or not. See for more details.\n- `computedWidth`: `number` - the actual calculated width of the column (in pixels) that will be used for rendering. This is computed based on the , and other min/max constraints.\n- `computedPinned`: `false | \"start\" | \"end\"`\n- `computedSortInfo`: or null - the sort info for this column.\n- `computedSorted`: `boolean` - whether this column is currently sorted or not.\n- `computedSortedAsc`: `boolean` - whether this column is currently sorted ascending or not.\n- `computedSortedDesc`: `boolean` - whether this column is currently sorted descending or not.\n- `computedFiltered`: `boolean` - whether this column is currently filtered or not.\n- ... and more (docs coming soon)\n\n\n\n\n\n> The type for the parameter of (and related rendering functions) and also for the object you get back when you call \n\nThese are the type properties:\n\n- `isGroupRow`: `boolean` - whether the current row is a group row or not.\n- `data`: `DATA_TYPE` | `Partial` | `null` - the data object for the current row.\n Because the DataSource can be grouped, the `data` object can be either the original data object, or a partial data object (containing the aggregated values - in case of a group row), or null. You can use `isGroupRow` to discriminate between these cases. If `isGroupRow` is `false`, then `data` is of type `DATA_TYPE`.\n- `rowInfo`: . See that type for more details.\n- `rawValue`: `string` | `number` | other - the raw value for the cell - as computed from the column field or valueGetter function.\n- `value`: `Renderable` - the current value to render for the cell. This is based on the `rawValue`, but if a column valueFormatter exists, it will be the result of that.\n- `column`: - the (computed) column definition for the current cell.\n- `columnsMap`: a map collection of objects, keyed by column id.\n- `fieldsToColumn`: a map collection of objects, keyed by the column field. If a column is not bound to a field, it will not be included in this map.\n- `align`: the computed value of the align prop for the current cell. This will be `\"start\"`, `\"center\"` or `\"end\"`.\n- `api`: [`InfiniteTableApi`](/docs/reference/api) - the api object.\n- `rowInfo`: - the row info for the current row.\n- `rowIndex`: `number` - the index of the current row.\n- `renderBag`: See [column rendering](/docs/learn/columns/column-rendering#rendering-pipeline) for more details.\n- `toggleCurrentGroupRow`: `() => void` - a function that can be used to toggle the current row, if it's a group row.\n- `toggleCurrentTreeNode`: `() => void` - a function that can be used to toggle the expand/collapse state of the current tree node (only available when rendering [a tree grid](/docs/learn/tree-grid/overview)).\n- `rootGroupBy`: - the group by specified in the prop of the `DataSource`.\n- `groupByForColumn`: available for group columns. When is `\"multi-column\"`, this will be a single , for each of the generated group columns. When is `\"single-column\"`, this will be an array of objects - it will be available only in the single group column that will be generated.\n\n\n\n\n\n> The type for the object you get back when you call \n\nThese are the type properties:\n\n- `api`: [`InfiniteTableApi`](/docs/reference/api) - the api object.\n- `initialValue`: `any` - the initial value for the editor.\n- `value`: `any` - the current value for the editor. Initially will be the same as `initialValue`. If you use this value, then your editor is \"controlled\", so make sure that when the editor is changed, you call the `setValue` function with the new value.\n- `setValue`: `(value: any) => void` - should be called to update the value in the cell editor. Calling this does not complete the edit.\n- `confirmEdit`: a reference to InfiniteTableApi.confirmEdit. If you have called `setValue` while editing (meaning your editor was controlled), you don't have to pass any parameters to this function. - the last value of the editor will be used. If your editor is uncontrolled and you haven't called `setValue`, you need to call `confirmEdit` with the value that you want to confirm for the edit.\n\n- `cancelEdit`: a reference to InfiniteTableApi.cancelEdit. Call this to cancel the edit and close the editor. Doesn't require any parameters.\n- `rejectEdit`: a reference to InfiniteTableApi.rejectEdit. Call this to reject the edit and close the editor. You can pass an `Error` object when calling this function to specify the reason for the rejection.\n- `readOnly`: `boolean` - whether the cell is read-only or not.\n\n\n\nInside the hook, you can still call to get access to the cell-related information.\n\n\n\n\n\n\n\n> The type for the objects in the array. See related \n\n\n\nThe type is generic, and the generic type parameter is the type of the data in the grid. In this documentation, either `DATA_TYPE` or `T` will be used to refer to the generic type parameter.\n\n\n\nThese are the type properties:\n\n- `field` - `keyof DATA_TYPE`. The field to group by.\n- `column`: `Partial`\n- `toKey?`: `(value: any, data: DATA_TYPE) => any` - a function that can be used to decide the bucket where each data object from the data set will be placed. If not provided, the `field` value will be used.\n\n\n\n\n\n> Type for `rowInfo` object representing rows in the table. See [Using RowInfo](/docs/learn/rows/using-row-info) for more details.\n\n\n\nThe type is generic, and the generic type parameter is the type of the data in the grid. In this documentation, either `DATA_TYPE` or `T` will be used to refer to the generic type parameter.\n\n\n\nMany methods in Infinite Table are called with `rowInfo` objects that are typed to . (see , , , , and many others)\n\nThis is a discriminated type, based on the `dataSourceHasGrouping` boolean property and the `isGroupRow` boolean property. This means that the type of the object will change based on the value of those properties.\n\n```ts\n\nexport type InfiniteTableRowInfo =\n // dataSourceHasGrouping = false, isGroupRow = false\n | InfiniteTable_NoGrouping_RowInfoNormal;\n\n // dataSourceHasGrouping = true, isGroupRow = false\n | InfiniteTable_HasGrouping_RowInfoNormal\n\n // dataSourceHasGrouping = true, isGroupRow = true\n | InfiniteTable_HasGrouping_RowInfoGroup\n\n // tree scenarios - leaf node\n | InfiniteTable_Tree_RowInfoLeafNode\n\n // tree scenarios - parent node\n | InfiniteTable_Tree_RowInfoParentNode;\n\n```\n\nThe common properties of the type (in all discriminated cases) are:\n\n- `id` - the primary key of the row, as retrieved using the prop.\n- `indexInAll` - the index in all currently visible rows.\n- `rowSelected` - whether the row is selected or not - `boolean | null`.\n- `rowDisabled` - whether the row is disabled or not - `boolean`.\n\n### InfiniteTable_NoGrouping_RowInfoNormal\n\nThis type has `dataSourceHasGrouping` set to `false` and `isGroupRow` set to `false`.\n\nAdditional properties to the ones already mentioned above:\n\n- `data` - the data for the underlying row, of type `DATA_TYPE`.\n- `isGroupRow` - `false`\n- `isTreeNode` - `false`\n- `dataSourceHasGrouping` - `false`\n- `selfLoaded` - `boolean` - useful when lazy loading is configured.\n\n### InfiniteTable_HasGrouping_RowInfoNormal\n\nThis type has `dataSourceHasGrouping` set to `true` and `isGroupRow` set to `false`. So we're in a scenario where grouping is configured via , but the current row is not a group row.\n\nAdditional properties this type exposes:\n\n- `data` - the data for the underlying row, of type `DATA_TYPE`.\n- `dataSourceHasGrouping` - `true`\n- `isGroupRow` - `false`\n- `isTreeNode` - `false`\n- `indexInGroup` - type: `number`. The index of the row in its parent group.\n- `groupKeys` - type: `any[]`, but usually it's actually `string[]`. For normal rows, the group keys will have all the keys starting from the topmost parent down to the last group row in the hierarchy (the direct parent of the current row).\n- `groupBy` - type `(keyof T)[]`. Has the same structure as groupKeys, but it will contain the fields used to group the rows.\n- `rootGroupBy` - type `(keyof T)[]`. The groupBy value of the DataSource component, mapped to the `groupBy.field`\n- `parents` - a list of `rowInfo` objects that are the parents of the current row.\n- `indexInParentGroups[]` - type: `number[]`. See below for an example\n- `groupCount` - type: `number`. The count of leaf rows that the current group (in this case, the parent group) contains\n- `groupNesting` - type `number`. The nesting of the parent group.\n- `collapsed` - type `boolean`.\n- `selfLoaded` - type: `boolean`. Useful in lazy-loading scenarios, when there is batching present. If you're not in such a scenario, the value will be `false`.\n\n### InfiniteTable_HasGrouping_RowInfoGroup\n\nThis type has `dataSourceHasGrouping` set to `true` and `isGroupRow` set to `true`. So we're in a scenario where grouping is configured via and the current row is a group row.\n\nAdditional properties this type exposes:\n\n- `data` - the data for the underlying row, of type `Partial | null`. If there are [aggregations configured](/docs/learn/grouping-and-pivoting/group-aggregations), then `data` will be an object that contains those aggregated values (so the shape of the object will be `Partial`). When no aggregations, `data` will be `null`\n- `dataSourceHasGrouping` - `true`\n- `isGroupRow` - `true`\n- `isTreeNode` - `false`\n- `error` - type: `string?`. If there was an error while loading the group (when the group row is expanded), this will contain the error message. If the group row was loaded with the `cache: true` flag sent in the server response, the error will remain on the `rowInfo` object even when you collapse the group row, otherwise, if `cache: true` was not present, the `error` property will be removed on collapse.\n- `indexInGroup` - type: `number`. The index of the row in the its parent group.\n- `deepRowInfoArray` - an array of `rowInfo` objects. This array contains all the (uncollapsed, so visible) row infos under this group, at any level of nesting, in the order in which they are visible in the table.\n- `reducerResults` - type `Record`. The result of the aggregation reducers for each field in the prop.\n- `groupCount` - type: `number`. The count of leaf rows that the current group (in this case, the parent group) contains\n- `groupData` - type: `DATA_TYPE[]`. The array of the data of all leaf nodes (normal nodes) that are inside this group.\n\n### InfiniteTable_Tree_RowInfoBase\n\nThe base type for row nodes when using the `` component.\n\n- `nodePath`: `any[]` - the path for the current row info\n- `isTreeNode`: `true`\n- `isParentNode`: `boolean`\n- `indexInParent`: `number`\n- `treeNesting`: `number` - the nesting level of the current node.\n\n### InfiniteTable_Tree_RowInfoParentNode\n\nThe type used for parent nodes in tree scenarios.\n\nIn addition to the properties already available via `InfiniteTable_Tree_RowInfoBase`, it adds the following properties:\n\n- `isParentNode` - `true`\n- `isTreeNode` - `true`\n- `totalLeafNodesCount` - `number`\n- `collapsedLeafNodesCount` - `number`\n\n### InfiniteTable_Tree_RowInfoLeafNode\n\nThe type used for leaf nodes in tree scenarios.\n\nIn addition to the properties already available via `InfiniteTable_Tree_RowInfoBase`, it adds the following properties:\n\n- `isParentNode` - `false`\n- `isTreeNode` - `true`\n\n\n\n\n" - }, "/blog/2021/12/10/infinite-launch": { "filePath": "/blog/2021/12/10/infinite-launch", "routePath": "/blog/2021/12/10/infinite-launch", @@ -3080,9 +3096,23 @@ ] } }, - "excerpt": "Today we are announcing the alpha version (`0.0.7`) of `Infinite Table` ready to be used by early adopters - you can take it from npm", - "readingTime": "1 min read", - "content": "\nToday we are announcing the alpha version (`0.0.7`) of `Infinite Table` ready to be used by early adopters - you can take it from npm\n\n\nnpm i @infinite-table/infinite-react\n\n\nWe're thrilled by the work done by the whole team and this is the result of years of their combined experience and passion ๐ŸŽ‰!\n\n### Future plans\n\nWe have big plans for the future of `Infinite Table` - first we want to finish the current react implementation and see it widely used and wildly successful and then we can move on to other frontend libraries/frameworks.\n\nThe **virtualization engine** we've built for this component is library agnostic so we'll have to port the **rendering** part to other platforms - which could prove to be a not-so-difficult task with all the experience we have in building this.\n\n# ๐Ÿš€\n" + "excerpt": "Today we are announcing the alpha version (`0.0.7`) of `Infinite Table` ready to be used by early adopters - you can take it from npm", + "readingTime": "1 min read", + "content": "\nToday we are announcing the alpha version (`0.0.7`) of `Infinite Table` ready to be used by early adopters - you can take it from npm\n\n\nnpm i @infinite-table/infinite-react\n\n\nWe're thrilled by the work done by the whole team and this is the result of years of their combined experience and passion ๐ŸŽ‰!\n\n### Future plans\n\nWe have big plans for the future of `Infinite Table` - first we want to finish the current react implementation and see it widely used and wildly successful and then we can move on to other frontend libraries/frameworks.\n\nThe **virtualization engine** we've built for this component is library agnostic so we'll have to port the **rendering** part to other platforms - which could prove to be a not-so-difficult task with all the experience we have in building this.\n\n# ๐Ÿš€\n" + }, + "/docs/reference/type-definitions/": { + "filePath": "/docs/reference/type-definitions/index", + "routePath": "/docs/reference/type-definitions/", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/docs/reference/type-definitions/index.page.md", + "fileName": "index.page.md", + "folderPath": "/docs/reference/type-definitions/", + "frontmatter": { + "title": "Infinite Table Type Definitions", + "description": "TypeScript type definitions for Infinite Table" + }, + "excerpt": "These are the public type definitions for `InfiniteTable` and related components, that you can import with named imports from the `@infinite-table/infinite-react` package.", + "readingTime": "20 min read", + "content": "\nThese are the public type definitions for `InfiniteTable` and related components, that you can import with named imports from the `@infinite-table/infinite-react` package.\n\n```tsx title=\"Importing the type for rowInfo\"\nimport type { InfiniteTableRowInfo } from '@infinite-table/infinite-react';\n```\n\n\n\nThe types of all properties in the `InfiniteTable` and `DataSource` components respect the following naming convention: `Prop`\n\nSo, for example, the type for is \n\n\n\n\n\n\n\n> Represents the state of the whole `` component.\n\nYou can grab a reference to the `` component state via the hook that Infinite exposes.\n\nAvailable properties:\n\n - `dataArray` - array of \n\n\n\n\n> Represents the selection state of the tree nodes. See for more details.\n\n```ts\nimport type { TreeSelectionValue } from '@infinite-table/infinite-react';\n```\n\nThe selection value is an object with the following properties:\n\n- `defaultSelection`: `boolean` - whether the tree nodes are selected by default or not.\n- `selectedPaths?`: `NodePath[]` - the paths of the selected nodes. Mandatory if `defaultSelection` is `false`.\n- `deselectedPaths`: `NodePath[]` - the paths of the deselected nodes. Mandatory if `defaultSelection` is `true`.\n\n```tsx title=\"Example of tree selection value\"\nconst treeSelection: TreeSelectionValue = {\n defaultSelection: false,\n selectedPaths: [['1'], ['2', '20']],\n deselectedPaths: [['1','10']],\n};\n// node ['1'] will be selected but indeterminate\n// since ['1','10'] is in the deselectedPaths\n// node ['2','20'] will be fully selected\n```\n\n\n\n\n\n> Represents the expand/collapse state of the tree nodes. See for more details.\n\n```ts\nimport type { TreeExpandStateValue } from '@infinite-table/infinite-react';\n```\n\nYou can specify the expand/collapse state of the tree nodes in two ways:\n\n1. With node paths (recommended)\n\nWhen using node paths, the object should have the following properties:\n\n- `defaultExpanded`: `boolean` - whether the tree nodes are expanded by default or not.\n- `collapsedPaths`: `string[]` - when `defaultExpanded` is `true`, this is a mandatory prop.\n- `expandedPaths`: `string[]` - when `defaultExpanded` is `false`, this is a mandatory prop.\n\n```tsx title=\"Example of treeExpandState with node paths\"\nconst treeExpandState = {\n defaultExpanded: true,\n collapsedPaths: [\n ['1', '10'],\n ['2', '20'],\n ['5']\n ],\n expandedPaths: [\n ['1', '4'],\n ['5','nested node in 5'],\n ],\n};\n```\n\n2. With node ids\n\nWhen using node ids, the object should have the following properties:\n\n- `defaultExpanded`: `boolean` - whether the tree nodes are expanded by default or not.\n- `collapsedIds`: `string[]` - when `defaultExpanded` is `true`, this is a mandatory prop.\n- `expandedIds`: `string[]` - when `defaultExpanded` is `false`, this is a mandatory prop.\n\n\n```tsx title=\"Example of treeExpandState with node ids\"\nconst treeExpandState = {\n defaultExpanded: true,\n collapsedIds: ['1', '2', '5'],\n expandedIds: ['10', '20', 'nested node in 5'],\n};\n```\n\n\n\n\n\n> Represents the collapse/expand state of row details - when [master-detail is configured](/docs/learn/master-detail/overview). Also see for the most important property in the master-detail configuration.\n\nThis class can be instantiated and the value passed to the prop (or its uncontrolled variant, ).\n\n```tsx title=\"Passing an instance of RowDetailState to the InfiniteTable\"\nconst rowDetailState = new RowDetailState({\n collapsedRows: true,\n expandedRows: [2, 3, 4],\n});\n\n rowDetailState={rowDetailState} />;\n```\n\n```tsx title=\"Passing an object literal to the InfiniteTable\"\n\n rowDetailState={{\n collapsedRows: true,\n expandedRows: [2, 3, 4],\n }}\n/>\n```\n\n\n\nThe instance is only useful if you want to interrogate the object, with methods like `areAllCollapsed()`, `areAllExpanded()`, `isRowDetailsExpanded(rowId)` and so on.\n\nWhen using the callback, it's called with an instance of this class - if you want to use the object literal, make sure you call `rowDetailState.getState()` to get the plain object.\n\n\nThe and accept both an object literal and an instance of this class.\n\nThe object literal has the following properties:\n\n- `collapsedRows`: `boolean | any[]` - if `true`, all row details are collapsed. If an array, it contains the row ids of the rows that are collapsed.\n- `expandedRows`: `boolean | any[]` - if `true`, all row details are expanded. If an array, it contains the row ids of the rows that are expanded.\n\nYou can create an instance using the object literal notation and you can get the object literal from the instance using the `getState` method:\n\n```tsx\nconst rowDetailState = new RowDetailState({\n collapsedRows: true,\n expandedRows: [2, 3, 4],\n});\nconst clone = new RowDetailState(rowDetailState);\n\nconst state = rowDetailState.getState();\n```\n\nYou can mark rows as expanded/collapsed even after creating the instance:\n\n```tsx\nconst rowDetailState = new RowDetailState({\n collapsedRows: true,\n expandedRows: [2, 3, 4],\n});\nrowDetailState.expandRowDetails(5);\nrowDetailState.collapseRowDetails(2);\n\n// now you can pass this instance back to the InfiniteTable component\n```\n\n\n\n\n\n> The type for the object passed into the function prop of the `DataSource` component.\n\nWhen the function is called, it will be called with an object of this type.\n\nThe following properties are available on this object:\n\n- `sortInfo?` - - the current sort info for the grid.\n- `groupBy?` - an array of - the current group by for the grid.\n- `pivotBy?` - an array of - the current pivot by for the grid.\n- `filterValue?` - an array of - the current filter value for the grid.\n- `masterRowInfo?` - - only available if the DataSource is a detail DataSource - meaning there is a master DataGrid, and the DataSource is used to load the detail DataGrid.\n\n\n\n\n\n> The type for the items in the array prop of the `DataSource` component.\n\n\n\n\n\n> Describes the collapse/expand state for group rows, when [grouping is used](/docs/learn/grouping-and-pivoting/grouping-rows). \n\nThis is a class, and instances of it can be used as a value for the / props.\n\nIt's the sole argument available in the callback.\n\nIt gives you the following additional utility methods:\n\n - `getState()`\n - `areAllExpanded()`\n - `areAllCollapsed()`\n - `expandAll()`\n - `collapseAll()`\n - `isGroupRowExpanded(keys: any[][])`\n - `isGroupRowCollapsed(keys: any[][])`\n - `expandGroupRow(keys: any[][])`\n - `collapseGroupRow(keys: any[][])`\n - `toggleGroupRow(keys: any[][])`\n\n\nTo create an instance, pass a plain object that describes the / value:\n\n\n```tsx\nconst state = new GroupRowsState({\n expandedRows: true,\n collapsedRows: [\n ['Europe']\n ['Europe','France'],\n ['Italy']\n ]\n})\n\nconsole.log(state.getState())\n// will log the above object that was used \n// as the sole argument for the constructor\n\n```\n\n\n\nWhen you call those methods, be aware you're not updating the React state! So you'll have to clone the object, call the method on the clone and then update the React state - in the code below, notice the `onClick` code for the `Expand all`/`Collapse all` buttons.\n\n\n\n```ts file=\"using-group-rows-state-controlled-example.page.tsx\"\n```\n\n\n\n\n\n\n\n\n\n> Describes a pivot value for the grid.\n\nThis is the type for the items in the array prop of the `DataSource` component.\n\nThe most important property in this type is the `field` - which will be `keyof DATA_TYPE` - the field to pivot by.\n\nAnother important property in this type is the `column`. It will be used to configure the generated pivot columns:\n\n- if it's an object literal, it will be applied to all generated columns\n- if it's a function, it will be called for each generated column, and the return value will be used to configure the column.\n\n```tsx\nconst pivotBy: DataSourcePivotBy[] = [\n { field: 'country' },\n {\n field: 'canDesign',\n column: ({ column: pivotCol }) => {\n const lastKey =\n pivotCol.pivotGroupKeys[pivotCol.pivotGroupKeys.length - 1];\n\n return {\n header: lastKey === 'yes' ? '๐Ÿ’… Designer' : '๐Ÿ’ป Non-designer',\n };\n },\n },\n];\n```\n\n\n\n\n\n> Represents runtime information passed to rendering and styling functions called when rendering the column headers\n\nThis object is passed to , , and functions.\n\nIt is an object with the following properties:\n\n- `column` - see for details\n- `columnSortInfo` - the current sort info for the column. it will be an object of type or `null`.\n- `filtered: boolean` - if the column is currently filtered or not\n- `api` - [`InfiniteTableApi`](/docs/reference/api) - the api object.\n- `columnApi` - [`InfiniteTableColumnApi`](/docs/reference/column-api) - the column api object.\n- `renderBag` - an object with various JSX values, the default elements rendered by the Infinite Table for the column header. It contains the following properties:\n - `header` - the default column header text\n - `sortIcon` - the default sort icon\n - `menuIcon` - the default column menu icon\n - `filterIcon` - the default column filter icon\n - `selectionCheckBox` - the default column selection checkbox\n\n```tsx title=\"Example column.renderHeader function\"\nconst renderHeader = ({ renderBag }) => {\n return (\n \n ({renderBag.header}) {renderBag.sortIcon}\n \n );\n};\nconst columns = {\n salary: {\n field: 'salary',\n type: 'number',\n renderHeader,\n },\n};\n```\n\n\n\n\n\n> Represents runtime information passed to many styling functions called when rendering the column cells\n\nThis object is passed at runtime during the rendering of column cells.\n\nIt is an object with the following properties:\n\n- `column` - see for details\n- `rowInfo` - see for details\n- `data` - the data object for the current row. The type of this object is `DATA_TYPE | Partial | null`. For regular rows, it will be of type `DATA_TYPE`, while for group rows it will be `Partial`. For rows not yet loaded (because of batching being used), it will be `null`.\n- `value` - the underlying value of the current cell - will generally be `data[column.field]`, if the column is bound to a `field` property\n- `inEdit`: `boolean`\n- `editError`: `Error`\n- `rowSelected`: `boolean | null;`\n- `rowActive`: `boolean | null`\n- `rowHasSelectedCells`: `boolean` - if the current row has selected cells or not\n\n\n\nThe following functions all have this as first argument:\n\n- \n- \n\n\n\n\n\n\n> Represents runtime information passed to many styling functions called when rendering rows/cells\n\nThis object is passed at runtime during the rendering of grid rows/cells.\n\nIt is an object with the following properties:\n\n- `rowInfo` - see for details\n- `rowIndex`: `number` - the index of the row\n- `rowHasSelectedCells`: `boolean` - if the current row has selected cells or not\n\n\n\nThe following functions all have this as first argument:\n\n- \n- \n- \n\n\n\n\n\n\n> Represents information on a specific sort. Contains info about the field to sort by, the sort direction, and the sort type.\n\nThis is the referenced by the prop.\n\nBasically the prop can be either an array of objects, or a single object (or null).\n\nThese are the type properties:\n\n- `dir`: `1 | -1` - 1 means ascending sort order; -1 means descending sort order.\n- `field?`: `keyof DATA_TYPE` - the field to sort by.\n- `id?`: `string` - an id for the sort info. When a column is not bound to a `field`, use the column id as the `id` property of the sort info, if you need to specify a default sort order by that column. Note that columns have a valueGetter, which will be used when doing local sorting and the column is not bound to an exact field.\n- `type?`: `string` - the sort type to apply. See for more details. For example, you can use `\"string\"` or `\"number\"` or `\"date\"`\n\n\n\n\n\n> The type of the DataSource prop.\n\nValid types for this prop are:\n\n- `null`\n- \n- []\n\n\n\n\n\n> The type of the prop. Basically this type is an array of .\n\n\n\n\n\n> This represents an enhanced column definition for a column. A computed column is basically a column with more information computed at runtime, based on everything Infinite Table can aggregate about it.\n\nThis type also includes the properties of the `InfinteTableColumn` type: , , , etc.\n\nAdditional type properties:\n\n- `id`: `string` - the id of the column. This is the same as the prop.\n- `computedEditable`: `boolean| Function` - whether this column is ediable or not. See for more details.\n- `computedWidth`: `number` - the actual calculated width of the column (in pixels) that will be used for rendering. This is computed based on the , and other min/max constraints.\n- `computedPinned`: `false | \"start\" | \"end\"`\n- `computedSortInfo`: or null - the sort info for this column.\n- `computedSorted`: `boolean` - whether this column is currently sorted or not.\n- `computedSortedAsc`: `boolean` - whether this column is currently sorted ascending or not.\n- `computedSortedDesc`: `boolean` - whether this column is currently sorted descending or not.\n- `computedFiltered`: `boolean` - whether this column is currently filtered or not.\n- ... and more (docs coming soon)\n\n\n\n\n\n> The type for the parameter of (and related rendering functions) and also for the object you get back when you call \n\nThese are the type properties:\n\n- `isGroupRow`: `boolean` - whether the current row is a group row or not.\n- `data`: `DATA_TYPE` | `Partial` | `null` - the data object for the current row.\n Because the DataSource can be grouped, the `data` object can be either the original data object, or a partial data object (containing the aggregated values - in case of a group row), or null. You can use `isGroupRow` to discriminate between these cases. If `isGroupRow` is `false`, then `data` is of type `DATA_TYPE`.\n- `rowInfo`: . See that type for more details.\n- `rawValue`: `string` | `number` | other - the raw value for the cell - as computed from the column field or valueGetter function.\n- `value`: `Renderable` - the current value to render for the cell. This is based on the `rawValue`, but if a column valueFormatter exists, it will be the result of that.\n- `column`: - the (computed) column definition for the current cell.\n- `columnsMap`: a map collection of objects, keyed by column id.\n- `fieldsToColumn`: a map collection of objects, keyed by the column field. If a column is not bound to a field, it will not be included in this map.\n- `align`: the computed value of the align prop for the current cell. This will be `\"start\"`, `\"center\"` or `\"end\"`.\n- `api`: [`InfiniteTableApi`](/docs/reference/api) - the api object.\n- `rowInfo`: - the row info for the current row.\n- `rowIndex`: `number` - the index of the current row.\n- `renderBag`: See [column rendering](/docs/learn/columns/column-rendering#rendering-pipeline) for more details.\n- `toggleCurrentGroupRow`: `() => void` - a function that can be used to toggle the current row, if it's a group row.\n- `toggleCurrentTreeNode`: `() => void` - a function that can be used to toggle the expand/collapse state of the current tree node (only available when rendering [a tree grid](/docs/learn/tree-grid/overview)).\n- `rootGroupBy`: - the group by specified in the prop of the `DataSource`.\n- `groupByForColumn`: available for group columns. When is `\"multi-column\"`, this will be a single , for each of the generated group columns. When is `\"single-column\"`, this will be an array of objects - it will be available only in the single group column that will be generated.\n\n\n\n\n\n> The type for the object you get back when you call \n\nThese are the type properties:\n\n- `api`: [`InfiniteTableApi`](/docs/reference/api) - the api object.\n- `initialValue`: `any` - the initial value for the editor.\n- `value`: `any` - the current value for the editor. Initially will be the same as `initialValue`. If you use this value, then your editor is \"controlled\", so make sure that when the editor is changed, you call the `setValue` function with the new value.\n- `setValue`: `(value: any) => void` - should be called to update the value in the cell editor. Calling this does not complete the edit.\n- `confirmEdit`: a reference to InfiniteTableApi.confirmEdit. If you have called `setValue` while editing (meaning your editor was controlled), you don't have to pass any parameters to this function. - the last value of the editor will be used. If your editor is uncontrolled and you haven't called `setValue`, you need to call `confirmEdit` with the value that you want to confirm for the edit.\n\n- `cancelEdit`: a reference to InfiniteTableApi.cancelEdit. Call this to cancel the edit and close the editor. Doesn't require any parameters.\n- `rejectEdit`: a reference to InfiniteTableApi.rejectEdit. Call this to reject the edit and close the editor. You can pass an `Error` object when calling this function to specify the reason for the rejection.\n- `readOnly`: `boolean` - whether the cell is read-only or not.\n\n\n\nInside the hook, you can still call to get access to the cell-related information.\n\n\n\n\n\n\n\n> The type for the objects in the array. See related \n\n\n\nThe type is generic, and the generic type parameter is the type of the data in the grid. In this documentation, either `DATA_TYPE` or `T` will be used to refer to the generic type parameter.\n\n\n\nThese are the type properties:\n\n- `field` - `keyof DATA_TYPE`. The field to group by.\n- `column`: `Partial`\n- `toKey?`: `(value: any, data: DATA_TYPE) => any` - a function that can be used to decide the bucket where each data object from the data set will be placed. If not provided, the `field` value will be used.\n\n\n\n\n\n> Type for `rowInfo` object representing rows in the table. See [Using RowInfo](/docs/learn/rows/using-row-info) for more details.\n\n\n\nThe type is generic, and the generic type parameter is the type of the data in the grid. In this documentation, either `DATA_TYPE` or `T` will be used to refer to the generic type parameter.\n\n\n\nMany methods in Infinite Table are called with `rowInfo` objects that are typed to . (see , , , , and many others)\n\nThis is a discriminated type, based on the `dataSourceHasGrouping` boolean property and the `isGroupRow` boolean property. This means that the type of the object will change based on the value of those properties.\n\n```ts\n\nexport type InfiniteTableRowInfo =\n // dataSourceHasGrouping = false, isGroupRow = false\n | InfiniteTable_NoGrouping_RowInfoNormal;\n\n // dataSourceHasGrouping = true, isGroupRow = false\n | InfiniteTable_HasGrouping_RowInfoNormal\n\n // dataSourceHasGrouping = true, isGroupRow = true\n | InfiniteTable_HasGrouping_RowInfoGroup\n\n // tree scenarios - leaf node\n | InfiniteTable_Tree_RowInfoLeafNode\n\n // tree scenarios - parent node\n | InfiniteTable_Tree_RowInfoParentNode;\n\n```\n\nThe common properties of the type (in all discriminated cases) are:\n\n- `id` - the primary key of the row, as retrieved using the prop.\n- `indexInAll` - the index in all currently visible rows.\n- `rowSelected` - whether the row is selected or not - `boolean | null`.\n- `rowDisabled` - whether the row is disabled or not - `boolean`.\n\n### InfiniteTable_NoGrouping_RowInfoNormal\n\nThis type has `dataSourceHasGrouping` set to `false` and `isGroupRow` set to `false`.\n\nAdditional properties to the ones already mentioned above:\n\n- `data` - the data for the underlying row, of type `DATA_TYPE`.\n- `isGroupRow` - `false`\n- `isTreeNode` - `false`\n- `dataSourceHasGrouping` - `false`\n- `selfLoaded` - `boolean` - useful when lazy loading is configured.\n\n### InfiniteTable_HasGrouping_RowInfoNormal\n\nThis type has `dataSourceHasGrouping` set to `true` and `isGroupRow` set to `false`. So we're in a scenario where grouping is configured via , but the current row is not a group row.\n\nAdditional properties this type exposes:\n\n- `data` - the data for the underlying row, of type `DATA_TYPE`.\n- `dataSourceHasGrouping` - `true`\n- `isGroupRow` - `false`\n- `isTreeNode` - `false`\n- `indexInGroup` - type: `number`. The index of the row in its parent group.\n- `groupKeys` - type: `any[]`, but usually it's actually `string[]`. For normal rows, the group keys will have all the keys starting from the topmost parent down to the last group row in the hierarchy (the direct parent of the current row).\n- `groupBy` - type `(keyof T)[]`. Has the same structure as groupKeys, but it will contain the fields used to group the rows.\n- `rootGroupBy` - type `(keyof T)[]`. The groupBy value of the DataSource component, mapped to the `groupBy.field`\n- `parents` - a list of `rowInfo` objects that are the parents of the current row.\n- `indexInParentGroups[]` - type: `number[]`. See below for an example\n- `groupCount` - type: `number`. The count of leaf rows that the current group (in this case, the parent group) contains\n- `groupNesting` - type `number`. The nesting of the parent group.\n- `collapsed` - type `boolean`.\n- `selfLoaded` - type: `boolean`. Useful in lazy-loading scenarios, when there is batching present. If you're not in such a scenario, the value will be `false`.\n\n### InfiniteTable_HasGrouping_RowInfoGroup\n\nThis type has `dataSourceHasGrouping` set to `true` and `isGroupRow` set to `true`. So we're in a scenario where grouping is configured via and the current row is a group row.\n\nAdditional properties this type exposes:\n\n- `data` - the data for the underlying row, of type `Partial | null`. If there are [aggregations configured](/docs/learn/grouping-and-pivoting/group-aggregations), then `data` will be an object that contains those aggregated values (so the shape of the object will be `Partial`). When no aggregations, `data` will be `null`\n- `dataSourceHasGrouping` - `true`\n- `isGroupRow` - `true`\n- `isTreeNode` - `false`\n- `error` - type: `string?`. If there was an error while loading the group (when the group row is expanded), this will contain the error message. If the group row was loaded with the `cache: true` flag sent in the server response, the error will remain on the `rowInfo` object even when you collapse the group row, otherwise, if `cache: true` was not present, the `error` property will be removed on collapse.\n- `indexInGroup` - type: `number`. The index of the row in the its parent group.\n- `deepRowInfoArray` - an array of `rowInfo` objects. This array contains all the (uncollapsed, so visible) row infos under this group, at any level of nesting, in the order in which they are visible in the table.\n- `reducerResults` - type `Record`. The result of the aggregation reducers for each field in the prop.\n- `groupCount` - type: `number`. The count of leaf rows that the current group (in this case, the parent group) contains\n- `groupData` - type: `DATA_TYPE[]`. The array of the data of all leaf nodes (normal nodes) that are inside this group.\n\n### InfiniteTable_Tree_RowInfoBase\n\nThe base type for row nodes when using the `` component.\n\n- `nodePath`: `any[]` - the path for the current row info\n- `isTreeNode`: `true`\n- `isParentNode`: `boolean`\n- `indexInParent`: `number`\n- `treeNesting`: `number` - the nesting level of the current node.\n\n### InfiniteTable_Tree_RowInfoParentNode\n\nThe type used for parent nodes in tree scenarios.\n\nIn addition to the properties already available via `InfiniteTable_Tree_RowInfoBase`, it adds the following properties:\n\n- `isParentNode` - `true`\n- `isTreeNode` - `true`\n- `totalLeafNodesCount` - `number`\n- `collapsedLeafNodesCount` - `number`\n\n### InfiniteTable_Tree_RowInfoLeafNode\n\nThe type used for leaf nodes in tree scenarios.\n\nIn addition to the properties already available via `InfiniteTable_Tree_RowInfoBase`, it adds the following properties:\n\n- `isParentNode` - `false`\n- `isTreeNode` - `true`\n\n\n\n\n" }, "/blog/2022/06/15/infinite-launch-beta": { "filePath": "/blog/2022/06/15/infinite-launch-beta", @@ -3153,29 +3183,6 @@ "readingTime": "4 min read", "content": "\nThis spring, we've been hard at work preparing for our Autumn release.\n\nWe have implemented a few new functionalities:\n\n- [column resizing](#column-resizing) is now available\n- [column reordering](#column-reordering) can be achieved via drag & drop\n- [keyboard navigation](#keyboard-navigation) with support for both row and cell navigation\n\nAnd we have updated some of the existing features:\n\n- [lazy grouping](#lazy-grouping)\n - expands lazy loaded rows correctly and\n - also the server response can contain multiple levels of `children`, which basically allows the backend to send more data for groups you don't want to load lazily\n- [column groups](#column-grouping) are now improved with support for proportional column resizing\n- [pivot columns](#pivoting) are now easier to style and customize\n\n\n\nAt the end of the spring, we started working on row and cell selection and we've made good progress on it.\n\nRow selection is already implemented for non-lazy group data and we're working on integrating it with lazy group data (e.g groups lazily loaded from the server). Of course, it will have integration with checkbox selection.\n\nMultiple row selection will have 2 ways to select data:\n\n- via mouse/keyboard interaction - we've emulated the behavior you're used to from your Finder in MacOS.\n- via checkbox - this is especially useful when the table is configured with grouping.\n\n\n\n## New Features\n\n### Column Resizing\n\nBy default columns are now resizable. You can control this at column level via column.resizable or at grid level via .\n\n\n\nRead more about how you can configure column resizing to fit your needs.\n\n\n\n\n\n\nFor resizable columns, hover the mouse between column headers to grab & drag the resize handle.\n\nHold SHIFT when grabbing in order to **share space on resize**.\n\n\n\n\nA nice feature is support for SHIFT resizing - which will share space on resize between adjacent columns - try it in the example above.\n\n### Column Reordering\n\n\n\nColumn order is a core functionality of `InfiniteTable` - read how you can leverage it in your app.\n\n\n\nThe default column order is the order in which columns appear in the columns object, but you can specify a or tightly control it via the controlled property - use to get notifications when columns are reordered by the user.\n\n\n\n\n\n### Keyboard Navigation\n\nBoth cell and row navigation is supported - use to configure it. By default, cell navigation is enabled.\n\n\n\n\n\nThis example starts with cell `[2,0]` already active.\n\n\n\n\n\n## Updated Features\n\n### Lazy grouping\n\nServer side grouping has support for lazy loading - `InfiniteTable` will automatically load lazy rows that are configured as expanded.\n\n\n\n\n\nIn this example, `France` is specified as expanded, so as soon as it is rendered, `InfiniteTable` will also request its children.\n\n\n\n\n\nAnother nice feature is the ability for a group node to also contain its direct children in the server response, which basically allows the backend to eagerly load data for certain groups.\n\n\n\nLazy grouping (with or without batching) is an advanced feature that allows you to integrate with huge datasets without loading them into the browser.\n\n\n\n### Column grouping\n\nColumn grouping was enhanced with support for pinned columns. Now you can use them in combination.\n\n\n\nColumn groups is a powerful way to arrange columns to fit your business requirements - read how easy it is to define them.\n\n\n\n\n\n\n\nNote the `country` column is pinned at the start of the table but is also part of a column group\n\n\n\n\n\n### Pivoting\n\nPivot columns are now easier to style and benefit from piped rendering to allow maximum customization.\n\n\n\nPivoting is probably our most advanced use-case. We offer full support for server-side pivoting and aggregations.\n\n\n\n\n\n\n\nPivot columns for the `canDesign` field are customized.\n\n\n\n\n" }, - "/blog/2022/11/01/infinite-table-monthly-update-october-2022": { - "filePath": "/blog/2022/11/01/infinite-table-monthly-update-october-2022", - "routePath": "/blog/2022/11/01/infinite-table-monthly-update-october-2022", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2022/11/01/infinite-table-monthly-update-october-2022.page.md", - "fileName": "infinite-table-monthly-update-october-2022.page.md", - "folderPath": "/blog/2022/11/01/", - "frontmatter": { - "title": "Quarterly Update - Autumn 2022", - "description": "Infinite Table update for Autumn 2022 - grid menus and new website", - "author": [ - "admin" - ], - "date": "2022-11-01T00:00:00.000Z", - "authorData": { - "label": [ - "admin" - ] - } - }, - "excerpt": "_In the autumn our focus was implementing a dedicated Menu component so it can be used for column menus and row context menu._", - "readingTime": "2 min read", - "content": "\n_In the autumn our focus was implementing a dedicated Menu component so it can be used for column menus and row context menu._\n\n_In addition to that, we've been working on a new design for our website and getting everything ready for the release._\n\n## Summary\n\nSome new functionalities we added to InfiniteTable include:\n\n- column menus\n- support for tab navigation\n\n\n\nWe redesigned our website in preparation for our **v1** release and public launch.\n\nTo receive your free 3-month license, please email us at [admin@infinite-table.com](mailto:admin@infinite-table.com) while we're still working our way through to `1.0.0`\n\n\n\n## New Features\n\nHere's what we worked on in the last two months:\n\n### Menu component\n\nWe've built a brand new Menu component for Infinite Table, which we're using as a column menu and in the very near future will be used for row context menus.\n\n![Grid with menu](/blogs/grid-with-menu.png)\n\nOur policy is to develop all our components in-house and own them in order not to introduce third-party dependencies and vulnerabilities. It also helps us keep the overall bundle size small (since we're sharing some utilities) so your apps are leaner.\n\nWe have to confess menus are tricky - we made ours support any level of nesting. They're tricky because of the nesting, the smart alignment and containment they need to provide in order to be truly useful. The Infinite Menu can be aligned to different targets and using a multitude of anchoring positions, always taking into account the position with the most available space in relation to a container or a specified area. This makes it really flexible and powerful - we think you'll want to use it as standalone as well once it's documented.\n\n### Tab navigation\n\nPrevious versions of Infinite Table did not have support for tab navigation due to our heavy virtualized rendering (the visual order of the cells was not the same as the DOM order). With the latest release, Infinite Table can now handle tab navigation correctly. Column cells that render `` fields or any other focusable elements can now be reached with tab navigation if the column specifies a prop.\n" - }, "/blog/2022/09/01/infinite-table-monthly-update-august-2022": { "filePath": "/blog/2022/09/01/infinite-table-monthly-update-august-2022", "routePath": "/blog/2022/09/01/infinite-table-monthly-update-august-2022", @@ -3199,6 +3206,29 @@ "readingTime": "7 min read", "content": "\nOver the summer, we continued our work on preparing for our official release, focusing mainly on adding new functionalities and documenting them thoroughly, together with enhancements to existing features.\n\n## Summary\n\nWe have implemented a few new functionalities, including:\n\n- [row selection is now available ๐ŸŽ‰](#row-selection)\n- [column rendering pipeline](#column-rendering-pipeline)\n- [group columns are now sortable ๐Ÿ”ƒ](#sortable-group-columns)\n\nAnd we have updated some of the existing features:\n\n- [group columns inherit](#enhanced-group-columns) styles and configuration\n- [column hiding when grouping](#column-hiding-when-grouping)\n- [group columns can be bound to a field](#group-columns-bound-to-a-field)\n- [using the column valueGetter in sorting](#column-valuegetter-in-sorting)\n\n\n\nWe started working on column and context menus.\nWe will first release fully customizable **column** menus to show/hide columns and to easily perform other operations on columns.\nThis will be followed by **context** menus where you will be able to define your own custom actions on rows/cells in the table.\n\n---\n\nDon't worry, the menus will be fully customizable, the menu items are fully replaceable with whatever you need, or you will be able to swap our menu component with a custom one of your own.\n\n\n\n## New Features\n\nHere's what we shipped over the summer:\n\n### Row Selection\n\nRow selection can be single or multiple, with or without a checkbox, with or without grouping and for a lazy or non-lazy `DataSource` - ๐Ÿ˜… that was a long enumeration, but seriously, we think we got something great out there.\n\nYou can specify the selection via the (controlled) or (uncontrolled) props, and listen to changes via the callback prop.\n\n\n\n\n\n- Example shows how you can use multiple row selection with a predefined controlled value.\n\n- Go ahead and select some groups/rows and see the selection value adjust.\n\n- Example also shows how you can use the [InfiniteTableApi](/docs/reference/api) to retrieve the actual ids of the selected rows.\n\n\n\n\n\n\n\nSingle vs multiple selection, grouped or ungrouped data, checkbox selection, lazy selection - read about all the possible combinations you can use to fit your needs.\n\n\n\n### Column Rendering Pipeline\n\nThe rendering pipeline for columns is a series of functions defined on the column that are called while rendering.\n\n\n\nAll the functions that have the word `render` in their name will be called with an object that has a `renderBag` property, which contains values that will be rendered.\n\n\n\nThe default function (the last one in the pipeline) ends up rendering a few things:\n\n- a `value` - generally comes from the field the column is bound to\n- a `groupIcon` - for group columns\n- a `selectionCheckBox` - for columns that have defined (combined with row selection)\n\nWhen the rendering process starts for a column cell, all the above end up in the `renderBag` object.\n\nFor example:\n\n```tsx {3,12}\nconst column: InfiniteTableColumn = {\n valueGetter: () => 'world',\n renderValue: ({ value, renderBag, rowInfo }) => {\n // at this stage, `value` is 'world' and `renderBag.value` has the same value, 'world'\n return {value};\n },\n\n render: ({ value, renderBag, rowInfo }) => {\n // at this stage `value` is 'world'\n // but `renderBag.value` is world, as this was the value returned by `renderValue`\n return
Hello {renderBag.value}!
;\n },\n};\n```\n\n\n\nRead about how using the rendering pipeline helps your write less code.\n\n\n\nHere is the full list of the functions in the rendering pipeline, in order of invocation:\n\n1. - doesn't have access to `renderBag` 2. - doesn't have access to `renderBag` 3. - can use all properties in `renderBag` 4. - can use all properties in `renderBag` 5. - can use all properties in `renderBag` 6. - can use all properties in `renderBag` 7. - can use all properties in `renderBag` 8. - can use all properties in `renderBag`\n\nAdditionally, the custom component has access to the `renderBag` via \n\n### Sortable Group Columns\n\nWhen groupRenderStrategy=\"single-column\" is used, the group column is sortable by default if all the columns that are involved in grouping are sortable.\n\nSorting the group column makes the `sortInfo` have a value that looks like this:\n\n```ts\nconst sortInfo = [{ field: ['stack', 'age'], dir: 1, id: 'group-by' }];\n```\n\nWhen groupRenderStrategy=\"multi-column\", each group column is sortable by default if the column with the corresponding field is sortable.\n\n \n\nIn both single and multi group column render strategy, you can use the property to override the default behavior.\n\n \n\n## Updated Features\n\nHereโ€™s a list of Infinite Table functionalities that we enhanced in the last month:\n\n### Enhanced Group Columns\n\nGroup columns now inherit configuration from the columns bound to the field they are grouped by - if such columns exist.\n\n\n\n\n\nIn this example, the group column inherits the styling of the `country` column, because the `country` field is used for grouping.\n\n\n\n\n\n\n\nThe generated group column(s) - can be one for all groups or one for each group - will inherit the `style`/`className`/renderers from the columns corresponding to the group fields themselves (if those columns exist).\n\nAdditionally, there are other ways to override those inherited configurations, in order to configure the group columns:\n\n- use to specify how each grouping column should look for the respective field (in case of groupRenderStrateg=\"multi-column\")\n- use prop\n - can be used as an object - ideal for when you have simple requirements and when groupRenderStrateg=\"single-column\"\n - as a function that returns a column configuration - can be used like this in either single or multiple group render strategy\n\n\n\n### Column Hiding when Grouping\n\nWhen grouping is enabled, you can choose to hide some columns. Here are the two main ways to do this:\n\n- use - this will make columns bound to the group fields be hidden when grouping is active\n- use (also available on the column types, as ) - this is a column-level property, so you have more fine-grained control over what is hidden and when.\n\nValid values for are:\n\n- `\"*\"` - when any grouping is active, hide the column that specifies this property\n- `true` - when the field this column is bound to is used in grouping, hides this column\n- `keyof DATA_TYPE` - specify an exact field that, when grouped by, makes this column be hidden\n- `{[k in keyof DATA_TYPE]: true}` - an object that can specify more fields. When there is grouping by any of those fields, the current column gets hidden.\n\n\n\n\n\nIn this example, the column bound to `firstName` field is set to hide when any grouping is active, since the group column is anyways found to the `firstName` field.\n\nIn addition, is set to `true`, so the `stack` and `preferredLanguage` columns are also hidden, since they are grouped by.\n\n\n\n\n\n### Group Columns Bound to a Field\n\nGroup columns can now be bound to a field, by leveraging the (obviously ...) property. This will make the group column render the value of that field for non-group rows.\n\n\n\n\n\nIn addition, you can now use and for configuring the rendered value for grouped vs non-grouped rows.\n\n### Column valueGetter in Sorting\n\nColumns allow you to define a valueGetter to change the value they are rendering (e.g. useful when the `DataSet` has nested objects).\n\nPreviously, this value returned by was not used when sorting the table. With the latest update, the value returned by valueGetter is correctly used when sorting the grid locally.\n" }, + "/blog/2022/11/01/infinite-table-monthly-update-october-2022": { + "filePath": "/blog/2022/11/01/infinite-table-monthly-update-october-2022", + "routePath": "/blog/2022/11/01/infinite-table-monthly-update-october-2022", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2022/11/01/infinite-table-monthly-update-october-2022.page.md", + "fileName": "infinite-table-monthly-update-october-2022.page.md", + "folderPath": "/blog/2022/11/01/", + "frontmatter": { + "title": "Quarterly Update - Autumn 2022", + "description": "Infinite Table update for Autumn 2022 - grid menus and new website", + "author": [ + "admin" + ], + "date": "2022-11-01T00:00:00.000Z", + "authorData": { + "label": [ + "admin" + ] + } + }, + "excerpt": "_In the autumn our focus was implementing a dedicated Menu component so it can be used for column menus and row context menu._", + "readingTime": "2 min read", + "content": "\n_In the autumn our focus was implementing a dedicated Menu component so it can be used for column menus and row context menu._\n\n_In addition to that, we've been working on a new design for our website and getting everything ready for the release._\n\n## Summary\n\nSome new functionalities we added to InfiniteTable include:\n\n- column menus\n- support for tab navigation\n\n\n\nWe redesigned our website in preparation for our **v1** release and public launch.\n\nTo receive your free 3-month license, please email us at [admin@infinite-table.com](mailto:admin@infinite-table.com) while we're still working our way through to `1.0.0`\n\n\n\n## New Features\n\nHere's what we worked on in the last two months:\n\n### Menu component\n\nWe've built a brand new Menu component for Infinite Table, which we're using as a column menu and in the very near future will be used for row context menus.\n\n![Grid with menu](/blogs/grid-with-menu.png)\n\nOur policy is to develop all our components in-house and own them in order not to introduce third-party dependencies and vulnerabilities. It also helps us keep the overall bundle size small (since we're sharing some utilities) so your apps are leaner.\n\nWe have to confess menus are tricky - we made ours support any level of nesting. They're tricky because of the nesting, the smart alignment and containment they need to provide in order to be truly useful. The Infinite Menu can be aligned to different targets and using a multitude of anchoring positions, always taking into account the position with the most available space in relation to a container or a specified area. This makes it really flexible and powerful - we think you'll want to use it as standalone as well once it's documented.\n\n### Tab navigation\n\nPrevious versions of Infinite Table did not have support for tab navigation due to our heavy virtualized rendering (the visual order of the cells was not the same as the DOM order). With the latest release, Infinite Table can now handle tab navigation correctly. Column cells that render `` fields or any other focusable elements can now be reached with tab navigation if the column specifies a prop.\n" + }, "/blog/2022/11/08/why-another-datagrid": { "filePath": "/blog/2022/11/08/why-another-datagrid", "routePath": "/blog/2022/11/08/why-another-datagrid", @@ -3222,23 +3252,138 @@ "readingTime": "18 min read", "content": "\nWe've been working on finding better ways to display tabular data for over 2 decades now and collectively we have 35+ years of experience working on this.\n\nIt all began on the desktop with a great range of DataGrids and then we moved to the web and the `

` component - yeah, we've been around for quite some while - all the while dealing with the same problems and requirements again and again.\n\nThis is the story of how we got to where we are today....\n\n## A (personal) History of DataGrids\n\n\n\nThis article is not meant to be a complete history of DataGrids.\n\nRather, it's personal reflections on the long journey the Infinite Table team have experienced while using and building components for displaying tabular data, culminating in Infinite Table, the modern declarative DataGrid for React.\n\n\n\n## Desktop Components\n\nDataGrids have been around as long as any of us can remember.\n\nThey are a vital tool which allows business users to visualise, edit, manage and personalise their data.\n\nBefore Tim Berners-Lee and his colleagues changed the world for ever (and for a couple of decades after), \"serious\" business applications lived on the desktop.\n\nThis was accompanied and facilitated by a plethora of great DataGrids from the likes of DevExpress, Telerik, Syncfusion, Infragistics and others.\n\nThese products defined the feature-set that users came to expect in a DataGrid - row grouping, formatting, multiple sorting, pivoting etc.\n\nAnd which any DataGrid worth its salt today needs to offer today.\n\nFor 2 decades and more these DataGrid repeatedly proved their worth in multiple changing desktop formats - MFC, WinForms, WPF and others.\n\n## Enter the Browser\n\nAnd then the browser came along and, in time, everything changed.\n\nWhile it really took until HTML5 to convince most power users to move from the desktop to the web, the need to display tabular data in the browser was there right from the start.\n\nInitially the only way to show tabular data in the browser was to use the `
` component, and it was this piece of code that made it happen:\n\n```css\ntable-layout: fixed;\n```\n\nthis is telling the browser ([see MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/table-layout#values)) that it shouldn't compute the space available for all rows & cells in the table before rendering but instead size the columns based on the content of the first row. This is speeding up the rendering time by quite a lot, and it's the early solution to the problem of rendering large data-sets.\n\nHowever, it was not perfect, and rendering **large** datasets was still a **huge** problem. Also, no fancy resizable / reorderable / stackable columns were available - at least not by default.\n\nThese shortcomings were obvious to developers dealing with massive datasets, so various groups and companies started coming up with solutions. One such solution came from Yahoo! as part of their larger widget library called `YUI` (it was back in the days when Y! was a big deal).\n\n### YUI DataTable\n\nEnter YUI era - launched in 2006, the Yahoo! User Interface Library was a step forward in reusability and component architecture. With the release of YUI 3, it received a modernized set of components, and the [YUI DataTable](https://clarle.github.io/yui3/yui/docs/datatable/) was probably the most advanced DataGrid solution out there. The component had a templating engine under the hood and allowed developers to customize some parts of the table. For its time, it was packed with functionality and was a great solution for many use-cases.\n\nIt had a rich API, exposing lots of events, callbacks and methods for things like moving columns around, getting the data record for a given row, adding rows and columns, etc - all imperative code. The API was powerful and allowed developers to build complex solutions, but it was all stateful and imperative - something very normal for its epoch, but something we've learned to avoid in the last few years.\n\nHere's some code showcasing the YUI DataTable\n\n```js {6} title=\"YUI DataTable with sorting\"\nvar table = new Y.DataTable({\n columns: [\n { key: 'item', width: '125px' },\n {\n key: 'cost',\n formatter: 'ยฃ: {value}',\n sortable: true,\n },\n ],\n sortable: true,\n data: data,\n}).render('#example');\n\n// to programatically sort\ntable.sort({ cost: 'asc' });\n```\n\nNotice in the code above, the component had support for custom formatters via a template (in the style of Mustache templates). YUI DataTable was a great component, certainly lacking some features by modern standards, but it was amazingly rich for its time. In some respects, it's still better than some of the modern DataGrids out there. The major missing piece is virtualization for both rows and columns in the table.\n\nA nice feature YUI DataTable had was the ability to separate the DataSource component and the data loading into a separate abstraction layer, so it would be somewhat decoupled from the main UI component.\n\n```js\nvar dataSource = new Y.DataSource.IO({\n source: '/restaurants/fetch.php?',\n});\n\ndataSource.plug(Y.Plugin.DataSourceXMLSchema, {\n schema: {\n resultListLocator: 'Result',\n resultFields: [{ key: 'Title' }, { key: 'Phone' }, { key: 'Rating' }],\n },\n});\nvar table = new Y.DataTable({\n columns: ['Title', 'Phone', 'Rating'],\n summary: 'Chinese restaurants near 98089',\n});\n\ntable.plug(Y.Plugin.DataTableDataSource, {\n datasource: dataSource,\n initialRequest: 'zip=94089&query=chinese',\n});\n```\n\nInfinite Table is getting this a step further and splitting the data loading and the rendering into two separate components - `` and ``:\n\n- the `` component is responsible for managing the data - fetching it, sorting, grouping, pivoting, filtering, etc and making it available via the React context to the UI component.\n- the `` component is responsible only for rendering the data. This means you can even use the `` component with another React component and implement your own rendering and virtualization.\n\n```tsx\n primaryKey=\"id\" data={...}>\n {/* if you wanted to, you can replace\n with your own custom component */}\n\n columns={...}>\n\n```\n\nThis level of separation allows us to iterate more rapidly on new features and also makes testing ๐Ÿงช easier.\n\n### ExtJS 3\n\nThe next solution we've worked with was [ExtJS version 3](https://docs.sencha.com/extjs/3.4.0/#!/api/Ext.grid.GridPanel), which was built on the legacy of YUI 3. At the time, back in 2010, it was the most advanced DataGrid solution out there - used for some of the most complex applications in the enterprise world, from CMSs to ERP systems.\n\nThe ExtJS 3 DataGrid brought excellent product execution in a few areas:\n\n- the [documentation](https://docs.sencha.com/extjs/3.4.0/) was excellent for its time - very rich, easy to navigate and search, with useful examples. As a bonus, from the docs you had access to the source-code of all components, which was a nice addition.\n- it came together with a rich set of components for building complex UIs - grids, trees, combo-boxes, form inputs, menus, dialogs, etc. Powerful layout components were available, which allowed developers to build complex app layouts by composing components together - and everything felt like it was part of the same story, which it was.\n- enthusiastic community - the forums were very active and the community was writing lots of good plugins.\n\n```js title=\"ExtJS 3 DataGrid code snippet\"\nvar grid = new Ext.grid.GridPanel({\n // data fetching abstracted in a \"Store\" component\n store: new Ext.data.Store({\n // ...\n }),\n // columns abstracted in a ColumnModel\n colModel: new Ext.grid.ColumnModel({\n defaults: {\n width: 120,\n sortable: true,\n },\n columns: [\n {\n id: 'company',\n header: 'Company',\n width: 200,\n sortable: true,\n dataIndex: 'company',\n },\n {\n header: 'Price',\n renderer: Ext.util.Format.usMoney,\n dataIndex: 'price',\n },\n { header: 'Change', dataIndex: 'change' },\n { header: '% Change', dataIndex: 'pctChange' },\n {\n header: 'Last Updated',\n width: 135,\n dataIndex: 'lastChange',\n xtype: 'datecolumn',\n format: 'M d, Y',\n },\n ],\n }),\n viewConfig: {\n forceFit: true,\n // Return CSS class to apply to rows depending upon data values\n getRowClass: function (record, index) {\n var c = record.get('change');\n if (c < 0) {\n return 'price-fall';\n } else if (c > 0) {\n return 'price-rise';\n }\n },\n },\n sm: new Ext.grid.RowSelectionModel({ singleSelect: true }),\n // size need if not inside a layout\n width: 600,\n height: 300,\n});\n```\n\nBuilding on the legacy of YUI 3, the ExtJS added virtualization to make the DataGrid perform well for large datasets - it really made the component fly - since there was no framework overhead, and ExtJS was working directly with the DOM, the scrolling experience was pretty smooth.\n\nAlso ExtJS tried to make things declarative and you could describe most of your UI by nesting JavaScript objects into a root object. The idea was clever, but it was only applicable for the initial rendering and you had to write imperative code as soon as you wanted some changes after the initial render.\n\n\n\nIt was while working on a project with ExtJS 3 and exploring everything it had to offer that we had the great idea ๐Ÿ˜… that we should start writing a DataGrid component.\n\nWe were digging deep into ExtJS source code, wrote a few plugins for it and then decided to take the challenge and build a brand new DataGrid ๐Ÿ˜ฑ.\n\nIt was supposed to take us just a few short months ๐Ÿ˜…...\n\n\n\n## The React Revolution\n\nWe were quite far in building the DataGrid component, with a dedicated templating engine under the hood (by the way, it was really good in comparison to similar solutions at that time), virtualization implemented and major functionalities finished ... when JSConf EU 2013 happened.\n\n### JSConf EU 2013\n\nWe vividly remember [watching Pete Hunt talk about ReactJS and rethinking best practices](https://www.youtube.com/watch?v=x7cQ3mrcKaY) at JSConf EU 2013.\n\n\n\nBy the time the presentation was finished we knew we had to do something.\n\nThis declarative way of describing the UI got us hooked and we knew we had to **drop what we were doing and adopt React** for anything going forward. It proved to be the right decision and we were early adopters of [React](https://reactjs.org/). It was astonishing to us how easy it was to learn React at the time - only taking a few hours to fully grasp the mental model and start building reusable components.\n\n\n2013 was the year we switched trajectory and went full-React with all our new projects. We went back to the drawing board and started our first experiments with a DataGrid component in React. \n\n\nWhile we were building the DataGrid in React we got side-tracked with other projects but we saw the same pattern again and again - people trying to implement the grid component again and again, in various projects. Most of those attempts either failed terribly or at best they were good-enough for a simple use-case.\n\n### AG Grid\n\nIt was around this time, in 2015, that [AG Grid](https://www.ag-grid.com/) was launched.\n\nAnd, wow, it was good - very good.\n\nWe immediately adopted it in all kind of projects while still trying to find time on the side to build our own DataGrid solution, the React way, with a fully declarative API.\n\nWe were inspired ๐Ÿ™ by AG Grid, seeing the breadth of features it offers and its expansive growth.\n\nIt is a feat of engineering which illustrates just how much the browser can be pushed by extensive use of virtualization - being able to render millions of rows and thousands of columns is no small feat.\n\nAll this while keeping the performance similar as if it was rendering just a few rows and columns.\n\n\n\nIn the code above ([taken from AG Grid getting started page](https://www.ag-grid.com/javascript-data-grid/getting-started/#copy-in-application-code)), note that AG Grid is exposing its [API](https://www.ag-grid.com/javascript-data-grid/grid-api/) on the `gridOptions` object.\n\nThe API is huge and allow you to do pretty much anything you want with the grid - in an imperative way, which is what you're probably looking for if you're not integrating with a library/framework like Angular or React.\n\nAfter vanilla JavaScript and Angular versions of AG Grid, a React version was finally released.\n\nIt was a step in the right direction - to make AG Grid more declarative - though it was a thin wrapper around React, with all the renderers and API still being imperative and not feeling like the best fit inside a React app.\n\nA few years later, AG Grid finally released a `reactUI` [version](https://blog.ag-grid.com/react-ui-overview/), with tighter integration with React and a more declarative API โค๏ธ\n\nAll this time other solutions popped up in the React community.\n\n### React Table\n\nOne such solution that got massive adoption from the community was [React Table](https://tanstack.com/table/v8/) - now rebranded as TanStack Table.\n\nIt's growth began around 2018, around the time when headless UI components started to gain traction.\n\nReact Table was one of the first popular headless UI components to be released - in the same category it's worth mentioning [Downshift](https://www.downshift-js.com/) (initially launched and popularized by [Kent C. Dodds](https://kentcdodds.com/)), which helped push headless UI components to the community.\n\nReact Table is a great solution for people who want to build their own UI on top of it.\n\nSome of the benefits of headless UI approach you get from React Table are:\n\n- full control over markup and styles\n- supports all styling patterns (CSS, CSS-in-JS, UI libraries, etc)\n- smaller bundle-sizes.\n\nThis flexibility and total control come with a cost of needing more setup, more code and more maintainance over time. Also complex features that might already be implemented in a full-featured DataGrid will need to be implemented again from scratch.\n\nHowever, we do think it's a great ๐Ÿ’ฏ fit for some use-cases - we've used it ourselves successfully in some projects ๐Ÿ™. But it's not for everyone, as in our experience, most teams today want to ship faster ๐ŸŽ and not spend time and mental energy on building their own UI.\n\n\n\nNotice in the code above how you're responsible for creating the markup for the table, the headers, column groups,the cells, etc. You have TOTAL control over every aspect of the component, but this means you have to own it!\n\nAt the other end of the spectrum is AG Grid a full-featured DataGrid that offers all this out of the box.\n\nWith Infinite Table, we're trying to strike a balance between these 2 very different approaches - by offering a declarative API that is easy to use and get started with, while still giving you the flexibility to customize the UI and the behavior of the component, via both controlled and uncontrolled props.\n\nLet's take a look at an example of a similar UI, this time built with Infinite Table.\n\n\n\n## Infinite Table\n\nAll this time we kept an eye on other components out there to get inspired. We got fresh ideas from various teams and projects - either enterprise or open source - either full-fledged or headless components like [react-table](https://tanstack.com/table/v8/).\n\nWe've learned a lot from all these projects we've worked with and we've put all the best ideas in Infinite Table.\n\nInfinite is the fruit of years of iteration, experimentation, failures and sweat on a product that we've poured our hearts in over the course of so many years. We've agonized over all our APIs and design decisions in order to make Infinite Table the best React DataGrid component out there.\n\nWe're aware we're not there yet, but we're here to stay ๐Ÿ‘‹ and keep getting better. We want to work closely with the community at large and get fresh ideas from other projects and teams. We can all be winners when we work together and respect each-other โค๏ธ\n\nIt's amazing what happens when you focus on a problem for such a long time (yeah, we know ๐Ÿ˜ฑ). We wanted to give up several times but kept pushing for over a decade. The result is a component that we're proud of and is already starting to be used by enterprise clients across many industries (more on that in a later blogpost).\n\nHere are some of the key areas where we believe Infinite Table shines:\n\n### Ready to Use\n\nInfinite Table is ready to use out of the box - namely it's not headless. We target companies and individuals who want to ship โ€” faster ๐ŸŽ! We're aware you don't want to re-invent the wheel nor do you want to invest 6 months of your team to build a poor implementation of a DataGrid component that will be hard to maintain and will be a source of bugs and frustration. **You want to ship โ€” and soon!**. If this is you and you are already using React then Infinite Table is written for you!\n\n### Feels like React - Declarative API\n\nWe want Infinite Table to feel at home in any React app. Everything about the DataGrid should be declarative - when you want to update the table, change a prop and the table will respond. No imperative API calls - we want you to be able to use Infinite Table in a way that feels natural to you and your team, so you can stay productive and use React everywhere in your frontend.\n\nLet's take for example how you would switch a column from a column group to another:\n\n```tsx {35} title=\"Fully declarative way to update columns\"\nfunction getColumns() {\n return {\n firstName: {\n field: 'firstName',\n width: 200,\n columnGroup: 'personalInfo',\n },\n address: {\n field: 'address',\n width: 200,\n columnGroup: 'personalInfo',\n },\n age: {\n field: 'age',\n columnGroup: 'about'\n }\n } as InfiniteTablePropColumns\n}\n\nconst columnGroups = {\n personalInfo: { header: \"Personal info\" },\n about: { header: \"About\" }\n};\n\nfunction App() {\n const [columns, setColumns] = useState>(getColumns)\n const [colGroupForAddress, setColGroupForAddress] = useState('personalInfo')\n\n const toggle = () => {\n const cols = getColumns()\n\n const newColGroup = colGroupForAddress === 'personalInfo' ? 'about' : 'personalInfo'\n cols.address.columnGroup = newColGroup\n\n setColumns(cols)\n setColGroupForAddress(newColGroup)\n }}\n const btn = \n\n return <>\n {btn}\n data={...} primaryKey=\"id\">\n \n \n \n}\n```\n\nNote in the code above that in order to update the column group for the `address` column, we simply change the `columnGroup` prop of the column and then we update the state of the component. The table will automatically re-render and update the column group for the `address` column. This is a fully declarative way to update the table. You don't need to call any imperative API to update it - change the props and the table will reflect the changes.\n\n### Fully Controlled\n\nReact introduced controlled components to the wider community and we've been using them for years. It's were the power of React lies - it gives the developer the flexibility to fully control (when needed) every input point of an app or component.\n\nAll the props Infinite Table is exposing have both controlled and uncontrolled versions. This allows you to start using the component very quickly and without much effort, but also gives you the flexibility to fully control the component when needed, as your app grows and you need more control over the DataGrid.\n\n### Composable API with small API surface\n\nWhen building a complex component, there are two major opposing forces:\n\n- adding functionality and\n- keeping the component (and the API) simple.\n\nWe're trying to reconcile both with Infinite Table so we've built everything with composition in mind.\n\nA practical example of composition is favoring function props instead of boolean flags or objects. Why implement a feature under a boolean flag or a static object when you can expose a functionality via a function prop? The function prop can be used to handle more cases than any boolean flag could ever handle!\n\nA good example of composability is the prop - it can be a column object or a function. It control the columns that are generated for grouping:\n\n- when it's a column object, it makes the table render a single column for grouping (as if was set to `\"single-column\"`)\n- when it's a function, it behaves like is set to `\"multi-column\"` and it's being called for each of the generated columns.\n\n```tsx title=\"Group column as an object\"\n\n```\n\nvs\n\n```tsx title=\"Group column as a function\"\n {\n // this allows you to affect all generated group columns in a single place\n // especially useful when the generated columns are dynamic or generated via a pivot\n return {...}\n }}\n/>\n```\n\nWe've learned from our experience with other DataGrid components that the more features you add, the more complex your API becomes. So we tried to keep the API surface as small as possible, while still offering a rich set of declarative props as building blocks that can be composed to accomplish more complex functionalities.\n\n## Conclusion\n\nWe're very excited to share our Infinite Table journey with you โค๏ธ ๐Ÿคฉ\n\nAfter years in the DataGrid space and working and agonizing on this component, we're happy to finally ship it ๐Ÿ›ณ ๐Ÿš€.\n\nWe're looking forward to receiving [your feedback](https://github.com/infinite-table/infinite-react/issues) and suggestions.\n\nWe're here to stay and we're committed to improving Infinite Table and to make it your go-to React DataGrid component to help you ship โ€” faster! All the while staying true to the community!\n" }, - "/blog/2024/01/23/how-to-customise-datagrid-loading-state": { - "filePath": "/blog/2024/01/23/how-to-customise-datagrid-loading-state", - "routePath": "/blog/2024/01/23/how-to-customise-datagrid-loading-state", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2024/01/23/how-to-customise-datagrid-loading-state.page.md", - "fileName": "how-to-customise-datagrid-loading-state.page.md", - "folderPath": "/blog/2024/01/23/", + "/blog/2023/01/16/infinite-table-is-here": { + "filePath": "/blog/2023/01/16/infinite-table-is-here", + "routePath": "/blog/2023/01/16/infinite-table-is-here", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2023/01/16/infinite-table-is-here.page.md", + "fileName": "infinite-table-is-here.page.md", + "folderPath": "/blog/2023/01/16/", "frontmatter": { - "title": "How to customise the DataGrid loading state", + "title": "๐Ÿ“ฃ Infinite Table is Here ๐ŸŽ‰", + "description": "Infinite Table is ready for prime time. With version 1.0.0 we're releasing a DataGrid that's feature packed and ready to be used in enterprise-grade apps", + "author": [ + "admin" + ], + "date": "2023-01-16T00:00:00.000Z", + "authorData": { + "label": [ + "admin" + ] + } + }, + "excerpt": "_Infinite Table React is ready for prime time._", + "readingTime": "7 min read", + "content": "\n_Infinite Table React is ready for prime time._\n\n_With version 1.0.0 we're releasing a DataGrid that's feature packed and ready to be used in the wild!_\n\n\n\n1๏ธโƒฃ seriously fast\n\n2๏ธโƒฃ no empty or white rows while scrolling\n\n3๏ธโƒฃ packed with features\n\n4๏ธโƒฃ built from the ground up for React\n\n5๏ธโƒฃ clear, concise and easily composable props & API\n\n\n\nWe think you'll love Infinite Table.\n\nThis is the DataGrid we would have loved to use more than 15 years ago when [we started working with tables in the browser](/blog/2022/11/08/why-another-datagrid).\n\nAnd now it's finally here ๐ŸŽ‰.\n\n### Built from the Ground Up with React & TypeScript\n\n#### React all the Way\n\nInfinite Table feels native to React, not as a after-thought, but built with React fully in mind.\n\nIt's declarative all the way and exposes everything as props, both controlled and uncontrolled.\n\nIf you don't like the default behavior of a prop, use the controlled version and implement your own logic and handling - see for example the [following props related to column order](/docs/reference/infinite-table-props#search=columnorder):\n\n- - controlled property for managing order of columns\n- - uncontrolled version of the above\n- - callback prop for notifications and for updating controlled column order\n\n#### Fully Controlled\n\nReact introduced controlled components to the wider community and we've been using them for years.\n\nIt's where the power of React lies - giving the developer the flexibility to fully control (when needed) every input point of an app or component.\n\nAll the props which Infinite Table exposes, have both controlled and uncontrolled versions. This allows you to start using the component very quickly and without much effort, but also with the all-important flexibility to fully control the component when needed, as your app grows and you need more control over the DataGrid.\n\n#### TypeScript & Generic Components\n\nInfinite Table is also built with TypeScript, giving you all the benefits of a great type system.\n\nIn addition, the exposed components are exported as generic components, so you can specify the type of the data you're working with, for improved type safety.\n\n```tsx\nimport { InfiniteTable, DataSource } from '@infinite-table/infinite-react'\n\ntype Person = { id: number, name: string, age: number}\n\nconst data: Person[] = [\n { id: 1, name: 'John', age: 25 },\n //...\n];\nconst columns = {\n id: { field: 'id' },\n name: { field: 'name' },\n}\n\n// ready to render\n data={data} primaryKey=\"id\">\n columns={columns} />\n\n```\n\n### Why Use Infinite Table, cont.\n\n#### Fast - virtualization\n\nInfinite Table is fast by leveraging **virtualization** both **vertically** (for rows) and **horizontally** (for columns).\n\nThis means DOM nodes are created only for the visible cells, thus reducing the number of DOM nodes and associated memory strain and improving performance.\n\n#### No white space while scrolling - clever layout & rendering\n\nIn addition to virtualization, we use clever layout & rendering techniques to avoid white space while scrolling.\n\nWhen you scroll, the table will not show any empty rows or white space - no matter how fast you're scrolling!\n\n\n\nWe think this is one of the features that sets us apart from other components.\n\nWe've spent a lot of time and effort making sure no whitespace is visible while scrolling the table.\n\n\n\n### Batteries Included\n\nWe want you to be productive immediately and stop worrying about the basics. Infinite Table comes with a lot of features out of the box, so you can focus on the important stuff.\n\nIt helps you display huge datasets and get the most out of your data by providing you the right tools to enjoy these features:\n\n- [ sorting](/docs/learn/sorting/overview)\n- [ row grouping](/docs/learn/grouping-and-pivoting/grouping-rows) - both server-side and client-side\n- [ pivoting](/docs/learn/grouping-and-pivoting/pivoting/overview) - both server-side and client-side\n- [ aggregations](/docs/learn/grouping-and-pivoting/grouping-rows#aggregations)\n- [ live pagination](/docs/learn/working-with-data/live-pagination)\n- [ lazy loading](/docs/learn/working-with-data/lazy-loading)\n- [ keyboard navigation](/docs/learn/keyboard-navigation/navigating-cells)\n- [ fixed and flexible columns](/docs/learn/columns/fixed-and-flexible-size)\n- [ column grouping](/docs/learn/columns/column-grouping)\n- [ theming](/docs/learn/theming)\n- ... and many others\n\nInfinite Table is built for companies and individuals who want to ship โ€” faster ๐ŸŽ!\n\n### (Almost) No External Dependencies\n\nWe've implemented everything from scratch and only directly depend on 2 packages (we'll probably get rid of them as well in the future) - all our dependecy graph totals a mere 3 packages.\n\n\n\nWe've reduced external dependencies for 2 main reasons:\n\n- avoid security issues with dependencies (or dependencies of dependencies...you know it) - remember left-pad?\n- keep the bundle size small\n\n\n\n### Composable API - with a small surface\n\nWhen building a component of this scale, there are two major opposing forces:\n\n- adding functionality\n- keeping the component (and the API) simple\n\nWe're continually trying to reconcile both with Infinite Table, so we've built everything with composition in mind.\n\n\n\nA practical example of composition is favouring function props instead of boolean flags or objects. Why implement a feature under a boolean flag or a static object when you can expose a functionality via a function prop? The function prop can be used to handle more cases than any boolean flag could ever handle!\n\n\n\nA good example of composability is the prop which controls the columns that are generated for grouping.\n\nIt can be either a column object or a function:\n\n- when it's a column object, it makes the table render a single column for grouping (as if was set to `\"single-column\"`)\n- when it's a function, it behaves like is set to `\"multi-column\"` and it's being called for each of the generated columns.\n\n```tsx title=\"Group_column_as_an_object\"\n\n```\n\nvs\n\n```tsx title=\"Group_column_as_a_function\"\n {\n // this allows you to affect all generated group columns in a single place\n // especially useful when the generated columns are dynamic or generated via a pivot\n return {...}\n }}\n/>\n```\n\nOur experience with other DataGrid components taught us that the more features you add, the more complex your API becomes.\n\nSo we tried to keep the API surface as small as possible, while still offering a rich set of declarative props as building blocks that can be composed to accomplish more complex functionalities.\n\n### Built for the community, available on NPM\n\nWe're thrilled to share Infinite Table with the world.\n\nWe wanted to make it very easy for everyone to [get started](/docs/learn/getting-started) with it, so all you require is just an npm install:\n\n\nnpm i @infinite-table/infinite-react\n\n\nThe component will show a footer with a [Powered by Infinite Table](https://infinite-table.com) link displayed. However, all the functionalities are still available and fully working. So if you keep the link visible, you can use the component for free in any setup!\n\nAlthough you can use Infinite Table for free, we encourage you to [purchase a license](/pricing) - buying a license will remove the footer link. This will help us keep delivering new features and improvements to the component and support you and your team going forward!\n\n\n\nGet started with Infinite Table and learn how to use it in your project.\n\n\n\nGet Infinite Table for your project and team!\n\n\n" + }, + "/blog/2023/01/26/filtering-data-with-infinite-table-for-react": { + "filePath": "/blog/2023/01/26/filtering-data-with-infinite-table-for-react", + "routePath": "/blog/2023/01/26/filtering-data-with-infinite-table-for-react", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2023/01/26/filtering-data-with-infinite-table-for-react.page.md", + "fileName": "filtering-data-with-infinite-table-for-react.page.md", + "folderPath": "/blog/2023/01/26/", + "frontmatter": { + "title": "Filtering Data with Infinite Table for React", + "description": "Learn how to filter data both client-side and server-side with Infinite Table for React", + "author": [ + "admin" + ], + "date": "2023-01-26T00:00:00.000Z", + "authorData": { + "label": [ + "admin" + ] + } + }, + "excerpt": "_Today we shipped cutting-edge column filtering functionality, that enables intuitive client-side and server-side filtering_", + "readingTime": "5 min read", + "content": "\n_Today we shipped cutting-edge column filtering functionality, that enables intuitive client-side and server-side filtering_\n\n\n\n1๏ธโƒฃ Narrow down your data with your own filter types and operators\n\n2๏ธโƒฃ Works both client-side and server-side\n\n3๏ธโƒฃ Easy customization of filters and filter editors\n\n4๏ธโƒฃ Optimized for performance\n\n5๏ธโƒฃ Easy to use across multiple columns\n\n\n\nFilters were, by far, the most requested feature to add to Infinite Table after our initial launch.\n\nThe recently-released version `1.1.0` of Infinite Table for React introduces support for column filters, which work both client-side and server-side.\n\nIn order to enable filtering - specify the property on the `` component, as shown below:\n\n```tsx {4} title=\"Enabling_filters_on_the_DataSource\"\n data={/* ... */} primaryKey=\"id\" defaultFilterValue={[]}>\n columns={columns} />\n\n```\n\nThis configures the `` component with an empty array of filters; columns will pick this up and each will display a filter editor in the column header.\n\nOf course, you can define some initial filters:\n\n```tsx title=\"Initial_filters:_filter_by_age_greater_than_40\"\ndefaultFilterValue={[\n {\n field: 'age',\n filter: {\n type: 'number',\n operator: 'gt',\n value: 40\n }\n }\n]}\n```\n\nYou can see how all of this looks like when we put it all together in the examples below.\n\n## Local and Remote Filtering\n\nBecause the `` prop is a function that returns a `Promise` with remote data, the filtering will happen server-side by default.\n\n\n\nWhen using remote filtering, it's your responsability to send the DataSource to the backend (you get this object as a parameter in your function). This value includes for each column the value in the filter editor, the column filter type and the operator in use. In this case, the frontend and the backend need to agree on the operator names and what each one means.\n\n\n\nWhenever filters change, when remote filtering is configured, the function prop is called again, with an object that has the `filterValue` correctly set to the current filters (together with `sortInfo` and other data-related props like `groupBy`, etc).\n\n\nHowever, we can use the to force client-side filtering:\n\n```tsx\n filterMode=\"local\" filterDelay={0} />\n```\n\nWe also specify the filterDelay=0 in order to perform filtering immediately, without debouncing and batching filter changes, for a quicker response โšก๏ธ ๐ŸŽ\n\n\n\n\n\nEven if your data is loaded from a remote source, using `filterMode=\"local\"` will perform all filtering on the client-side - so you don't need to send the `filterValue` to the server in your `data` function.\n\n\n\n## Defining Filter Types and Custom Filter Editors\n\nCurrently there are 2 filter types available in Infinite Table:\n\n- `string`\n- `number`\n\nConceptually, you can think of filter types similar to data types - generally if two columns will have the same data type, they will display the same filter.\n\nEach filter type supports a number of operators and each operator has a name and can define it's own filtering function, which will be used when local filtering is used.\n\n\n\nThe example above, besides showing how to define a custom filter type, also shows how to define a custom filter editor.\n\n\n\nFor defining a custom filter editor to be used in a filter type, we need to write a new React component that uses the hook.\n\n```tsx\nimport { useInfiniteColumnFilterEditor } from '@infinite-table/infinite-react';\n\nexport function BoolFilterEditor() {\n const { value, setValue } = useInfiniteColumnFilterEditor();\n return <>{/* ... */};\n}\n```\n\nThis custom hook allows you to get the current `value` of the filter and also to retrieve the `setValue` function that we need to call when we want to update filtering.\n\nRead more about this [in the docs - how to provide a custom editor](/docs/learn/filtering/providing-a-custom-filter-editor).\n\n\n\n## Customise Filterable Columns and Filter Icons\n\nMaybe you don't want all your columns to be filterable.\n\nFor controlling which columns are filterable and which are not, use the property.\n\nThis overrides the global prop.\n\nWe have also made it easy for you to customize the filter icon that is displayed in the column header.\n\n\n\nYou change the filter icon by using the prop - for full control, it's being called even when the column is not filtered, but you have a `filtered` property on the argument the function is called with.\n\nIn the example above, the `salary` column is configured to render no filter icon, but the `header` is customized to be bolded when the column is filtered.\n\n## Ready for Your Challenge!\n\nWe listened to your requests for advanced filtering.\n\nAnd we believe that we've come up with something that's really powerful and customizable.\n\nNow it's your turn to try it out and show us what you can build with it! ๐Ÿš€\n\nIf you have any questions, feel free to reach out to us on [Twitter](https://twitter.com/infinite_table) or in the [GitHub Discussions](https://github.com/infinite-table/infinite-react/discussions).\n\nMake sure you try out filtering in Infinite Table for yourself ([and consult our extensive docs](/docs/learn/filtering) if required).\n\n\n\nLearn how to use filtering in the browser.\n\n\nFigure out how to use filtering with server-side integration.\n\n\n" + }, + "/blog/2023/10/02/version-3-0-0": { + "filePath": "/blog/2023/10/02/version-3-0-0", + "routePath": "/blog/2023/10/02/version-3-0-0", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2023/10/02/version-3-0-0.page.md", + "fileName": "version-3-0-0.page.md", + "folderPath": "/blog/2023/10/02/", + "frontmatter": { + "title": "Infinite Table React DataGrid version 3.0.0 released", + "description": "InfiniteTable DataGrid for React version 3.0.0 brings many small fixes and enhancements, along with a major new feature: cell selection", + "author": [ + "admin" + ], + "date": "2023-10-02T00:00:00.000Z", + "authorData": { + "label": [ + "admin" + ] + } + }, + "excerpt": "Version `3.0.0` is a release that brings a long awaited feature: cell selection. This allows the user to perform fined-grained cell selection, either via the prop or via the [Cell Selection API](/docs/reference/cell-selection-api).", + "readingTime": "4 min read", + "content": "\nVersion `3.0.0` is a release that brings a long awaited feature: cell selection. This allows the user to perform fined-grained cell selection, either via the prop or via the [Cell Selection API](/docs/reference/cell-selection-api).\n\n\n\n1๏ธโƒฃ [support for single and multiple cell selection](#1-support-for-single-and-multiple-cell-selection)\n2๏ธโƒฃ [cell selection using wildcards](#2-cell-selection-using-wildcards)\n3๏ธโƒฃ [cell selection API](#3-cell-selection-api)\n\n\n\n## 1๏ธโƒฃ Support for single and multiple cell selection\n\nIt's been a [long-requested feature to implement cell selection](https://github.com/infinite-table/infinite-react/issues/120).\n\nWe knew we needed to implement it, but we wanted to do it right while keeping it easy to understand.\n\nIn fact, we prepared some things in advance - namely was there, it just needed to accept a new value: `\"multi-cell\"`.\n\n```tsx title=\"Configuring multi-cell selection\"\n\n selectionMode=\"multi-cell\" // <--- THIS\n primaryKey=\"id\"\n data={[...]}\n/>\n```\n\nThe line above is all you need to do to enable cell selection. This allows the user to `Click` or `Cmd/Ctrl+Click` to select a specific cell and `Shift+Click` to select a range of cells. It's exactly the behavior you'd expect from a spreadsheet application.\n\nTry `Cmd/Ctrl+Click`ing in the DataGrid cells below to see multiple cell selection in action.\n\n\n\n### Using a default selection\n\nIf you want to render the DataGrid with a default selection, you can use the prop.\n\n```tsx\nconst defaultCellSelection = {\n defaultSelection: false,\n selectedCells: [\n [3, 'hobby'],\n [4, 'firstName'],\n [4, 'hobby'],\n [4, 'preferredLanguage'],\n [4, 'salary'],\n ],\n};\n```\n\nThe format for the uncontrolled (and also for the controlled ) is an object with two properties:\n\n- `defaultSelection` - `boolean` - whether or not cells are selected by default.\n- and either\n - `selectedCells` - `[string|number, string][]` - only needed when `defaultSelection` is `false`\n- or\n - `deselectedCells` - `[string|number, string][]` - only needed when `defaultSelection` is `true`\n\nThe value for `selectedCells` and `deselectedCells` should be an array of `[rowId, colId]` tuples.\n\nThe `rowId` is the `id` of the row (the primary key), and the `colId` is the `id` of the column (the identifier of the column in the prop).\n\nThis object shape for the / props allows you full flexibility in specifying the selection. You can specify a single cell, a range of cells, or even a non-contiguous selection. You can default to everything being selected, or everything being deselected and then enumerate your specific exceptions.\n\n\n\n## 2๏ธโƒฃ Cell Selection using wildcards\n\nThe above examples show how to select specific cells, but what if you want to select all cells in a column, or all cells in a row?\n\nWell, that turns out to be straightforward as well. You can use the `*` wildcard to select all cells in a column or all cells in a row.\n\n```tsx title=\"All cells in row with id rowId3 and all cells in hobby column are selected\"\nconst defaultCellSelection = {\n defaultSelection: false,\n selectedCells: [\n ['*', 'hobby'],\n ['rowId3', '*'],\n ],\n}\n\n```\n\n\n\nWildcard selection is really powerful and it allows you to select lots of cells without the need to enumerate them all.\n\nFor example, you can easily select all cells except a few.\n\n### Listening to selection changes\n\nYou can listen to selection changes by using the prop.\n\nIf you're using controlled cell selection, you have to update the prop yourself in response to user interaction - so will be your way of listening to selection changes.\n\n## 3๏ธโƒฃ Cell Selection API\n\nIn addition to managing cell selection declaratively, which we encourage, you can also use the [Cell Selection API](/docs/reference/cell-selection-api) to imperatively update the current selection.\n\nWe offer the following methods:\n\n- - selects a single cell, while allowing you to keep or to clear previous selection\n- - deselects the specified cell\n- - selects a whole column in the DataGrid\n- - deselects the specified column\n- - selects a range of cells\n- - deselects the specified range of cells\n- - selects all cells in the DataGrid\n- - clears selection (deselects all cells in the DataGrid)\n- - checks if the specified cell is selected or not\n\n## Conclusion\n\nWe'd love to hear your feedback - what do you think we've got right and what's missing. Please reach out to us via email at admin@infinite-table.com or follow us [@get_infinite](https://twitter.com/get_infinite) to keep up-to-date with news about the product.\n\nTalk soon ๐Ÿ™Œ\n" + }, + "/blog/2023/10/05/building-a-datagrid-with-the-right-tools": { + "filePath": "/blog/2023/10/05/building-a-datagrid-with-the-right-tools", + "routePath": "/blog/2023/10/05/building-a-datagrid-with-the-right-tools", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2023/10/05/building-a-datagrid-with-the-right-tools.page.md", + "fileName": "building-a-datagrid-with-the-right-tools.page.md", + "folderPath": "/blog/2023/10/05/", + "frontmatter": { + "title": "Building a DataGrid with the right tools", "author": "admin", - "date": "2024-01-23T00:00:00.000Z", + "date": "2023-10-05T00:00:00.000Z", "authorData": { "label": "admin" } }, - "excerpt": "We're starting a series of short `\"How to\"` articles that are very focused and show how to achieve a specific thing with the Infinite Table DataGrid.", - "readingTime": "2 min read", - "content": "\nWe're starting a series of short `\"How to\"` articles that are very focused and show how to achieve a specific thing with the Infinite Table DataGrid.\n\nIn this article, we'll document how to customise the DataGrid loading state.\n\n## Customising the loading text\n\nFirst off, you can customise the text that is displayed when the DataGrid is loading data. By default, the DataGrid displays a `\"Loading\"` text, but you can customise it to anything you want (even JSX, not only string values).\n\n```tsx title=\"Customising the loading text\" {9}\nconst developers: Developer = [\n { id: '1', firstName: 'Bob' },\n { id: '2', firstName: 'Bill' },\n]\n\n// make sure to add \"loading\" to the DataSource so you see the loading state\n loading data={developers} primaryKey=\"id\">\n \n loadingText={\n Loading your data ...\n }\n columns={{\n firstName: {\n field: 'firstName',\n },\n id: {\n field: 'id',\n }\n }}\n {...props}\n />\n\n```\n\n\nFor the value of you can use JSX, not only strings.\n\n\n\n\n## Customising the loading component - the `LoadMask`\n\nIn addition to the loading text, you can also customise the `LoadMask` component. This is the component that is displayed when the DataGrid is loading data. By default, it's a `
` with `width: 100%; height: 100%; zIndex: 1; display: flex` that contains the loading text.\n\nYou do this by overriding the prop in your Infinite Table configuration.\n\n```tsx title=\"Customising the LoadMask component\" {7,15}\n// make sure to add \"loading\" to the DataSource so you see the loading state\nexport default function App() {\n return (\n loading data={developers} primaryKey=\"id\">\n \n components={{\n LoadMask,\n }}\n columns={columns}\n />\n \n );\n}\n\nfunction LoadMask() {\n return (\n \n \n Loading App ...\n
\n \n );\n}\n```\n\n\n" + "excerpt": "Building for the browser has historically been very tedious. In the old days you had to resort to all sorts of hacks for getting the right layout - anyone remembers conditional comments targeting IE6-9? ๐Ÿ˜…", + "readingTime": "8 min read", + "content": "\nBuilding for the browser has historically been very tedious. In the old days you had to resort to all sorts of hacks for getting the right layout - anyone remembers conditional comments targeting IE6-9? ๐Ÿ˜…\n\nYeah, we don't miss those days either.\n\nThings have evolved in the last few years, and the amount of goodies JS/CSS/HTML/layout goodies we now take for granted is staggering. New CSS features like flex/grid/custom properties really make a difference. Also browser performance has improved a LOT, and today we can do things in the browser that were unthinkable just a few years ago.\n\nHowever, not everything is easier now than it was back in the browser-war days. Handling all kinds of devices, managing changing dependencies, configuring build tools, choosing the right styling approach, proper E2E testing, keeping a small bundle size, CI pipelines, etc. are all things that can (and will) go wrong if you don't have the right tools.\n\n## TypeScript\n\nIt's obvious today to just go with `TypeScript`, but a few years ago, it was not as obvious. We've been using TypeScript for quite a few years now, and we're very happy with it. We can never imagine going back to plain JS.\n\n## React\n\nBuilding on top of `React` has given us an amazing component model that's very composable and easy to reason about - and the ecosystem is huge.\n\nRead about our journey in the [Why another DataGrid?](/blog/2022/11/08/why-another-datagrid) blog post. Back when React was launching, many of our team members were writing DataGrids - either in vanilla JS or using some libraries (`jQuery` anyone? - we don't miss browser incompatibilities).\n\n## CSS Variables and Vanilla Extract\n\nAs a `DataGrid` Infinite Table is built on top of CSS variables - we're going all in with CSS variables. They have a few gotchas in very advanced cases, but all-in-all they're amazing - and especially for performance.\n\nWe're not short of [CSS variables that we expose - see the full list](/docs/learn/theming/css-variables).\n\nUsing them has been pivotal not only to the ease of theming, but also to the performance of the DataGrid.\nBeing able to change a CSS custom property on a single DOM element and then reuse it across many elements that are children of the first one is a huge performance win. Our DataGrid performance would not be the same without CSS variables.\n\n### Vanilla Extract\n\nThe single tool that has made our life a lot easier working with CSS is [Vanilla Extract](https://vanilla-extract.style/). If you're developing a component library, you should definitely use it! Not so much for simple & static apps - there are other styling solutions that are easier to use, like [tailwindCSS](https://tailwindcss.com/). But for component libraries, **Vanilla Extract is amazing**!\n\nDid we mention it's amazing? ๐Ÿ˜…\nThe fact that you can use TypeScript with it, can use \"Find All References\", see where everything is used is a huge win. You're not writing readonly CSS anymore - because that tends to be the case with most CSS. People are afraid to change it or remove old CSS code, just in case those rules are still being used or referenced somehow. This way, CSS only grows with time, and this is a code smell.\n\nWith Vanilla Extract, you get to forget about that. You know what's being used and what's not.\n\nAlso, hashing class names to avoid collisions is nice - and something now very common in the modern JS ecosystem. It all started with CSS modules, and now it's everywhere, Vanilla Extract included.\n\nOther great features we use extensively are:\n\n- public facing CSS variables - their names are stable\n- private CSS variables - their names are hashed\n- sharing CSS values with the TS codebase is a dream come true.\n- Vanilla Extract recipes - generating and applying CSS classes based on a combination of properties. It's enough that you have 2-3 properties, each with a few values, and managing their combinations can be a pain. Vanilla Extract recipes manage this in a very elegant way.\n\n## End-to-end testing with Playwright and NextJS\n\nRemember the days of Selenium? All those flaky tests, the slow execution, the hard to debug issues? They're gone!\n\n[Playwright](https://playwright.dev/) all the way! 300+ tests and going strong! Yes, you read that right! We have more than 300 tests making sure the all the DataGrid features are working as expected. Sorting, filtering, row grouping, column groups, pivoting, aggregations, lazy loading, live pagination, keyboard navigation, cell and row selection, theming - they're all tested! And we're not talking about unit tests, but end-to-end tests. We're testing the DataGrid in the browser, with real data just like real users would.\n\nPlaywright is an amazing tool, but we're not using it standalone. Paired with a [NextJS](https://nextjs.org/) app, with file-system based routing, we've created files/routes for each functionality. Each NextJS file in turn has a Playwright test file with the same name, but a different extension.\n\nThis has the benefit that it's always very obvious which test is running against which page. The test and the route always have the same file name, just the extension is different. The test source-code doesn't explicitly contain code that navigates to a specific page, all this is done under the hood, using this simple convention.\n\nThis way, we have a very clear separation of concerns, and it's very easy to add new tests. We just create a new file in the `pages` folder, and a new test file sibling to it. Another amazing benefit is that we can start the NextJS app and point our browser to whatever page we want to see or debug and it's there. We can very easily do the actions the test is doing and see if we get the expected results. This is a huge win for debugging.\n\n## A tailored state management\n\nWe've built a very simple yet highly effective state management solution for our DataGrid. It's built to make updating the internal state of the DataGrid as easy as possible - we want a simple API, with clear actions. Our actions map almost 1-to-1 to the DataGrid properties, which makes it very obvious to know who changed what.\n\nWe can't overstate how important it is to have a clear data flow through the DataGrid. This is because the DataGrid is by far the most complex UI component you'll ever use (and we'll ever build). You can't possibly go beyond that - at least not in common business apps, where you have the normal UI controls you can expect, like inputs, buttons, dropdowns, etc. Just the ComboBox can come near the complexity of the DataGrid, but it's still far behind.\n\nIt's important to be able to tame all this complexity - otherwise it can slow down the development process and bring it to a halt, making it difficult to add new features or fix bugs. With our current model, even though the DataGrid grew in complexity and features, we never felt our velocity dropping! We enjoy that!\n\n## No dependencies\n\nWe're very proud of the fact that we have no dependencies in our DataGrid. When you install our package, you only install our package - and nothing else. Nothing that can go wrong due to version conflicts, missing dependencies, npm issues ([remember left-pad](https://www.davidhaney.io/npm-left-pad-have-we-forgotten-how-to-program/)?).\n\nYes, we still depend on packages in our dev process, but we're striving to keep that small as well. It's already complex enough to keep TS, React, NextJS, npm (with workspaces), aliases, esbuild, tsup, playwright all working together in harmony. But we've got through it, and we're very happy with the result. It was worth it!\n\n## Separating concerns\n\nWe've separated our DataGrid into 2 main parts:\n\n- the `` component - handles data loading and processing\n- the `` component - handles the rendering\n\nThis was a brilliant idea! It's new? No! It's not our invention, but we're happy we decided to apply it.\n\nIt adds a better separation between the two big parts of the DataGrid. This also helps tame some of the complexity, while adding clarity to the codebase. It's easier to reason about the code when you know that the `` component is responsible for data loading and processing, while the `` component is ONLY responsible for rendering.\n\n## Conclusion\n\nWe're not sorry for choosing any of the above tools or approaches when building the InfiniteTable DataGrid component.\n\nOur developer velocity is high, and we're able to add new features and fix bugs at a fast pace. We're happy with the result and we're confident that we'll be able to keep this pace in the future.\n\nThe right tools get the right job done! They make a lot easier. Looking back, we only regret we didn't have those tools 5 years ago - but hey, things are moving in the right direction, and we're happy to be part of this journey.\n\nWhat are your tools for developer productivity?\n" + }, + "/blog/2023/07/14/version-2-0-0": { + "filePath": "/blog/2023/07/14/version-2-0-0", + "routePath": "/blog/2023/07/14/version-2-0-0", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2023/07/14/version-2-0-0.page.md", + "fileName": "version-2-0-0.page.md", + "folderPath": "/blog/2023/07/14/", + "frontmatter": { + "title": "Infinite Table DataGrid for React reaches version 2.0.0", + "description": "With version 2.0.0 InfiniteTable DataGrid for React brings lots of fixes and enhancements including support for sorting group columns, better APIs, improved pivoting, smarter column menus and more.", + "author": [ + "admin" + ], + "date": "2023-07-14T00:00:00.000Z", + "authorData": { + "label": [ + "admin" + ] + } + }, + "excerpt": "Version `2.0.0` is a release that allowed us to polish many areas of the component and consolidate its existing features and APIs.", + "readingTime": "4 min read", + "content": "\nVersion `2.0.0` is a release that allowed us to polish many areas of the component and consolidate its existing features and APIs.\n\nWe hope this makes your experience with Infinite Table as your React DataGrid of choice even better.\n\nThough it doesn't add major new features, this version does improve the overall experience of using the component. In this article we're detailing the most important improvements this release brings.\n\n\n\n1๏ธโƒฃ [better support for sorting group columns](#1-better-support-for-sorting-group-columns)\n2๏ธโƒฃ [allows configuring the behavior when multiple sorting is enabled](#2-multi-sort-behavior)\n3๏ธโƒฃ [smarter column menus](#3-smarter-column-menus)\n4๏ธโƒฃ [improved support for boolean pivot columns](#4-improved-support-for-boolean-pivot-columns)\n5๏ธโƒฃ [better and more exhaustive APIs](#5-better-and-more-exhaustive-apis)\n\n\n\n\n\n## 1๏ธโƒฃ Better support for sorting group columns\n\nBefore version `2.0.0`, group columns were sortable, but only if the configured `groupBy` fields were bound to actual columns.\n\nThis release enables you to make group columns sortable even when other columns are not defined. For this to work, you have to specify a sortType as an array, so the column knows how to sort the group values.\n\n```tsx title=\"Configuring sortType for group columns\"\n\n groupColumn={{\n sortType: ['string', 'number'],\n field: 'firstName',\n defaultWidth: 150,\n }}\n groupRenderStrategy=\"single-column\"\n columns={columns}\n columnDefaultWidth={120}\n/>\n```\n\n\n\n## 2๏ธโƒฃ Multi sort behavior\n\nWe have introduced to allow you to configure how the component behaves when multiple sorting is enabled. Two options are available:\n\n- `append` - when this behavior is used, clicking a column header adds that column to the alredy existing sort. If the column is already sorted, the sort direction is reversed. In order to remove a column from the sort, the user needs to click the column header in order to toggle sorting from ascending to descending and then to no sorting.\n\n- `replace` - the default behavior - a user clicking a column header removes any existing sorting and sets that column as sorted. In order to add a new column to the sort, the user needs to hold the `Ctrl/Cmd` key while clicking the column header.\n\nmultiSortBehavior=\"replace\" is the new default behavior, and also a more natural one, so we recommend using it.\n\n\n\n## 3๏ธโƒฃ Smarter column menus\n\nColumn menus are now smarter - in previous versions of Infinite Table, users were able to hide the column that had the menu opened, and the menu would hang in its initial position.\n\nWhen this happens, in version `2.0.0`, the menu realigns itself to other existing columns, thus providing a better user experience.\n\n## 4๏ธโƒฃ Improved support for boolean pivot columns\n\nIt's pretty common to pivot by boolean columns, and this is now fully supported in Infinite Table. Previous to version `2.0.0`, the column headers for boolean pivot columns were not rendered correctly.\n\n\n\n## 5๏ธโƒฃ Better and more exhaustive APIs\n\nWe have improved our APIs, with new methods and fixes. Among other things, we've polished our [Column API](/docs/reference/column-api) to offer you the ability to do more with your columns. Previously there were things that were only possible to do if you had access to the internal state of the component, but now we've moved more things to the API.\n\nFor example, our column sorting code is now centralised, and using gives you the same action as clicking a column header (this was not the case previously).\n\nWe've added quite a few more methods to our APIs, here's some of the most important ones:\n\n- ColumnAPI.\n- ColumnAPI.\n- ColumnAPI.\n- ColumnAPI.\n- ColumnAPI.\n\n## Conclusion\n\nWe've been working on version `2.0.0` for a few months now and we hope you'll enjoy all the little details that make this version a better product, with all the improvements it brings in various areas of the component.\n\nWe'd love to hear your feedback, so please reach out to us via email at admin@infinite-table.com or follow us [@get_infinite](https://twitter.com/get_infinite) to keep up-to-date with news about the product.\n\nThank you ๐Ÿ™Œ\n" + }, + "/blog/2023/02/16/using-menus-in-infinite-table": { + "filePath": "/blog/2023/02/16/using-menus-in-infinite-table", + "routePath": "/blog/2023/02/16/using-menus-in-infinite-table", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2023/02/16/using-menus-in-infinite-table.page.md", + "fileName": "using-menus-in-infinite-table.page.md", + "folderPath": "/blog/2023/02/16/", + "frontmatter": { + "title": "Using Menus in Infinite Table", + "description": "Find out how to use menus in Infinite Table to customise the DataGrid to fit your needs: custom context menus, column menus and more.", + "author": [ + "admin" + ], + "date": "2023-02-16T00:00:00.000Z", + "authorData": { + "label": [ + "admin" + ] + } + }, + "excerpt": "_With version 1.1.0, our DataGrid now includes support for context menus, which are fully configurable so you can create custom menus for any cell in the table._", + "readingTime": "7 min read", + "content": "\n_With version 1.1.0, our DataGrid now includes support for context menus, which are fully configurable so you can create custom menus for any cell in the table._\n\n\n\n1๏ธโƒฃ are fully configurable\n\n2๏ธโƒฃ adjust their position based on the available space\n\n3๏ธโƒฃ can be used to create custom menus for any cell in the table\n\n4๏ธโƒฃ give you full access to the information in the cell or the whole DataGrid\n\n\n\n## How it works\n\nIn Infinite Table you can configure a context menu to be displayed when you right-click a cell by using the prop. Simply specify a function that returns an array of objects, each with `label` and `key` properties. Each object in the array is a row in the context menu - with the `label` being the displayed content and the `key` being a unique identifier for the menu row.\n\n```tsx title=\"Configuring_a_context_menu\"\nconst getCellContextMenuItems = ({ column, data, value }) => {\n if (column.id === 'currency') {\n return [\n {\n label: `Convert ${value}`,\n key: 'currency-convert',\n onAction: (key, item) => {\n alert('clicked ' + item.key);\n },\n },\n ];\n }\n\n if (column.id === 'age') {\n return null;\n }\n\n return [\n {\n label: `Welcome ${value}`,\n key: 'hi',\n },\n ];\n};\n\n data={data} primaryKey=\"id\">\n \n getCellContextMenuItems={getCellContextMenuItems}\n columns={columns}\n />\n;\n```\n\nIn the function prop, you have access to all the information you need, in the first argument of the function:\n\n- `column` - the column on which the user right-clicked\n- `data` - the data object for the row the user right-clicked\n- `value` - the value of the cell on which the context menu has been triggered. This is generally `data[column.field]`, but it can be different if the column has a or \n- `rowInfo` - an object that contains more information about the row, like the `id` (the primary key) and the row index\n- `isGroupRow`\n- and more\n\n\n\nIf is specified and returns `null`, no custom context menu will be displayed, instead the default browser context menu will be shown (in this case, we do not call `preventDefault()` on the event object).\n\nIf returns an empty array, the default browser context menu will not be shown (in this case, we are calling `preventDefault()` on the event object), but also no custom context menu will be displayed, as there are no menu items to show.\n\n\n\n\n\nEach item on the context menu can specify an `onAction` function, which will be called when the user clicks on the menu item. The function will receive the `key` and the `item` as arguments.\n\nIn addition, since the menu items are returned from inside the `getCellContextMenuItems` function, the `onAction` callback has access to the same information as the `getCellContextMenuItems` function.\n\n\n\n\n## Configuring the context menu to have multiple columns\n\nIn the above example, notice each context menu item has only one cell, where the `label` property is displayed.\n\nHowever, Infinite Table for React allows you to create more complex menus, with multiple columns.\n\nIn order to do this, use the same prop, but return an object, with `columns` and `items`\n\n```tsx\nconst getCellContextMenuItems = () => {\n return {\n columns: [{ name: 'label' }, { name: 'lcon' }],\n items: [\n {\n label: 'Welcome',\n icon: '๐Ÿ‘‹',\n key: 'hi',\n },\n {\n label: 'Convert',\n icon: '๐Ÿ”',\n key: 'convert',\n },\n ],\n };\n};\n```\n\n\n\nWhen is used to configure the column menus, each column `name` should have a corresponding property in the objects returned in the `items` array (each object also needs to keep the `key` property).\n\nAlso, we recommend keeping a column named `label`.\n\n\n\n\n\n## Smart positioning\n\nContext menus in Infinite Table are smart enough to adjust their position based on the available space relative to the mouse-click coordinates. The menu will always try to fit inside the grid viewport and to look for the best position that will not cause the menu to be cut off or overflow outside the DataGrid.\n\nThe same algorithm is applied to column menus and also to filter menus (the menu displayed when a filter is shown and the user wants to change the filter operator).\n\n## Context menus outside cells, for the table body\n\nThere are scenarios when you want to display a context menu even when you right-click outside a cell, but inside the table body - for those cases, you can use (in fact, you can use the prop for all context menus).\n\nThe signature of is almost identical with that of , with the exception that cell-related information can be undefined - if the user didn't right-click a cell, but somewhere else in the table body.\n\n\n\nIn the example above, if you click outside a cell, a menu with a single item will be displayed - `Add Item`. If you click on a cell, the menu will be different, and will show information about the clicked cell.\n\n## Column menus\n\nBesides context menus, the DataGrid also supports menus for columns, that allow you to sort/unsort, pin/unpin, clear filtering and toggle column visibility.\n\nJust like context menus, the column menus can also be fully customised, by using the prop.\n\n```tsx title=\"Customizing-column-menu\"\nfunction getColumnMenuItems(items, { column }) {\n if (column.id === 'firstName') {\n // you can adjust the default items for a specific column\n items.splice(0, 0, {\n key: 'firstName',\n label: 'First name menu item',\n onClick: () => {\n console.log('Hey there!');\n },\n });\n }\n\n // or for all columns\n items.push({\n key: 'hello',\n label: 'Hello World',\n onClick: () => {\n alert('Hello World from column ' + column.id);\n },\n });\n return items;\n}\n```\n\n\n\nThe first argument passed to the prop is the array of items that is displayed by default in the column menu.\n\nYou can either modify this array and return it or you can return another totally different array.\n\n\n\n\n\nAs with context menus, positioning column menus is also smart - the menu will always try to fit inside the grid viewport, so it will align to the right or the left of the column, depending on the available space.\n\n## Conclusion\n\nIn this article, we've explained just some of the scenarios that are now possible with Infinite Table for React, by using the new context and column menus.\n\n\n\nLearn more about working with context menus.\n\n\nConfiguring column menus to fit your needs - read more.\n\n\n\nWe hope you'll use these functionalities to build amazing DataGrids for your applications, that are fully tailored to your needs.\n\nIf you find any issues or have any questions, please reach out to us on [Twitter](https://twitter.com/infinite_table) or in the [GitHub Discussions](https://github.com/infinite-table/infinite-react/discussions) or [issues](https://github.com/infinite-table/infinite-react/issues).\n\nWe're happy to help and improve how you work with the component - we want to make it very easy and straight-forward to use it and are looking for ways to simplify our APIs to **achieve more with less**.\n" }, "/blog/2024/02/02/how-to-configure-default-sorting": { "filePath": "/blog/2024/02/02/how-to-configure-default-sorting", @@ -3258,6 +3403,43 @@ "readingTime": "3 min read", "content": "\nIn this article, we'll show you how easy it is to configure the default sorting for the React DataGrid.\n\n## Using the `defaultSort` prop on the DataSource\n\nSorting is configured on the DataGrid `` component. For this, you use the prop, as either an object, for sorting by a single column, or an array of objects, for sorting by multiple columns.\n\n```tsx title=\"Specifying a default sort order\"\n// sort by country DESC and salary ASC\nconst defaultSortInfo={[\n { field: \"country\", dir: -1 },\n { field: \"salary\", dir: 1 },\n]}\n\n\n```\n\nThat's it! Now, when the DataGrid is first rendered, it will be sorted by the `country` column in descending order, and then by the `salary` column in ascending order.\n\n\n\nFor sorting to work properly for numeric columns, don't forget to specify `type: \"number\"` in the column configuration.\n\n\n\n\n\n\n\nWhen the is an array, the DataGrid will know you want to allow sorting by multiple columns.\n\nSee our page on [multiple sorting](/docs/learn/sorting/multiple-sorting) for more details.\n\n\n\n## Local vs remote sorting\n\nThe above example uses local sorting. If you don't explicitly specify a that changes in the should trigger a reload (via the prop), the sorting will be done locally, in the browser.\n\nHowever, you can also have remote sorting - for this scenario, make sure you use shouldReloadData.sortInfo=true.\n\n\n\nIn this case, it's your responsability to send the `sortInfo` to your backend using the prop of the DataSource - your `data` function will be called by the DataGrid whenever sorting changes. The arguments the function is called with will include the sort information (along with other details like filtering, grouping, aggregations, etc).\n\n```tsx\nconst dataSource: DataSourceData = ({ sortInfo }) => {\n if (sortInfo && !Array.isArray(sortInfo)) {\n sortInfo = [sortInfo];\n }\n const args = [\n sortInfo\n ? 'sortInfo=' +\n JSON.stringify(\n sortInfo.map((s) => ({\n field: s.field,\n dir: s.dir,\n })),\n )\n : null,\n ]\n .filter(Boolean)\n .join('&');\n\n return fetch('https://your-backend.com/fetch-data?' + args)\n .then((r) => r.json())\n .then((data: Developer[]) => data);\n};\n```\n\n\n\n\n\n## Responding to sorting changes\n\nWhen the user changes the sorting in the React DataGrid UI, the DataSource function is called for you, with the new sort information.\n\nHowever, you might want to respond in other ways - for this, you can use callback prop.\n\n\n\nIf you use the controlled instead of the uncontrolled , you will need to configure the callback to respond to sorting changes and update the UI.\n\n\n\n## Using the column sort info for rendering\n\nAt runtime, you have access to the column sort information, both in the column header - see and in the column cells - see .\n\n\n\nFor example, you can customise the icon that is displayed in the column header to indicate the sort direction.\n\nVia the you have full access to how the column header is rendered and can use the sorting/filtering/grouping/aggregation/pivoting information of that column to customise the rendering.\n" }, + "/blog/2023/12/11/quick-guide-filtering-the-datagrid": { + "filePath": "/blog/2023/12/11/quick-guide-filtering-the-datagrid", + "routePath": "/blog/2023/12/11/quick-guide-filtering-the-datagrid", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2023/12/11/quick-guide-filtering-the-datagrid.page.md", + "fileName": "quick-guide-filtering-the-datagrid.page.md", + "folderPath": "/blog/2023/12/11/", + "frontmatter": { + "title": "Quick Guide - Filtering the DataGrid", + "author": "admin", + "draft": true, + "date": "2023-12-11T00:00:00.000Z", + "authorData": { + "label": "admin" + } + }, + "excerpt": "This is the first article in a series of quick guides that will help you get started with the DataGrid, each focusing on a specific feature. In this article, we will learn how to use filtering in the DataGrid.", + "readingTime": "1 min read", + "content": "\nThis is the first article in a series of quick guides that will help you get started with the DataGrid, each focusing on a specific feature. In this article, we will learn how to use filtering in the DataGrid.\n\n### Applying Filters on the DataSource\n\nYou apply filters on the `DataSource` component\n\n```tsx {4-11} title=\"Specifying an initial filter value for the DataSource\"\n\n data={...}\n primaryKey=\"id\"\n defaultFilterValue={[\n field: 'salary',\n filter: {\n operator: 'gt',\n value: 50000,\n type: 'number',\n },\n ]}\n/>\n```\n" + }, + "/blog/2024/01/23/how-to-customise-datagrid-loading-state": { + "filePath": "/blog/2024/01/23/how-to-customise-datagrid-loading-state", + "routePath": "/blog/2024/01/23/how-to-customise-datagrid-loading-state", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2024/01/23/how-to-customise-datagrid-loading-state.page.md", + "fileName": "how-to-customise-datagrid-loading-state.page.md", + "folderPath": "/blog/2024/01/23/", + "frontmatter": { + "title": "How to customise the DataGrid loading state", + "author": "admin", + "date": "2024-01-23T00:00:00.000Z", + "authorData": { + "label": "admin" + } + }, + "excerpt": "We're starting a series of short `\"How to\"` articles that are very focused and show how to achieve a specific thing with the Infinite Table DataGrid.", + "readingTime": "2 min read", + "content": "\nWe're starting a series of short `\"How to\"` articles that are very focused and show how to achieve a specific thing with the Infinite Table DataGrid.\n\nIn this article, we'll document how to customise the DataGrid loading state.\n\n## Customising the loading text\n\nFirst off, you can customise the text that is displayed when the DataGrid is loading data. By default, the DataGrid displays a `\"Loading\"` text, but you can customise it to anything you want (even JSX, not only string values).\n\n```tsx title=\"Customising the loading text\" {9}\nconst developers: Developer = [\n { id: '1', firstName: 'Bob' },\n { id: '2', firstName: 'Bill' },\n]\n\n// make sure to add \"loading\" to the DataSource so you see the loading state\n loading data={developers} primaryKey=\"id\">\n \n loadingText={\n Loading your data ...\n }\n columns={{\n firstName: {\n field: 'firstName',\n },\n id: {\n field: 'id',\n }\n }}\n {...props}\n />\n\n```\n\n\nFor the value of you can use JSX, not only strings.\n\n\n\n\n## Customising the loading component - the `LoadMask`\n\nIn addition to the loading text, you can also customise the `LoadMask` component. This is the component that is displayed when the DataGrid is loading data. By default, it's a `
` with `width: 100%; height: 100%; zIndex: 1; display: flex` that contains the loading text.\n\nYou do this by overriding the prop in your Infinite Table configuration.\n\n```tsx title=\"Customising the LoadMask component\" {7,15}\n// make sure to add \"loading\" to the DataSource so you see the loading state\nexport default function App() {\n return (\n loading data={developers} primaryKey=\"id\">\n \n components={{\n LoadMask,\n }}\n columns={columns}\n />\n \n );\n}\n\nfunction LoadMask() {\n return (\n \n \n Loading App ...\n
\n \n );\n}\n```\n\n\n" + }, "/blog/2024/02/26/master-detail-now-available-in-react-datagrid": { "filePath": "/blog/2024/02/26/master-detail-now-available-in-react-datagrid", "routePath": "/blog/2024/02/26/master-detail-now-available-in-react-datagrid", @@ -3276,6 +3458,42 @@ "readingTime": "4 min read", "content": "\nToday is a big day for the Infinite React DataGrid - we're excited to announce that the master detail feature is now available!\n\nWith this addition, our DataGrid is now enterprise-ready! We know master-detail scenarios are needed in many business applications, and we're happy to provide this feature to our users starting today!\n\n\n\n1๏ธโƒฃ [support for multiple levels of master-detail & rendering custom content](#what-can-you-do-with-master-detail)\n2๏ธโƒฃ [configurable detail height](#configurable-detail-height)\n3๏ธโƒฃ [control over expand/collapse state](#configurable-expandcollapse-state)\n4๏ธโƒฃ [caching mechanism for detail DataGrids](#master-detail-caching)\n\n\n\n## What can you do with master-detail?\n\nMaster-detail allows you to have rows in the DataGrid that expand to show more details. This can be used to show more information about the row, or even to show another DataGrid with related data.\n\nYou can render basically anything in the detail row - it doesn't need to be another DataGrid. However, if you do want to show another DataGrid, you can, and you can do that at any level of depth.\n\nIn the detail `` component, you have access to the master row, so it will be very easy to load related data based on the master row the user expands.\n\n\n\n## Configurable detail height\n\nOur master-detail implementation is very configurable - you can control the height of the row details, the expand/collapse state, and much more.\n\nThe height of the row details is fully adjustable - see the prop to learn about all the options you have.\n\n\n\nAs seen in the snippet above, it's also really easy to control the expand/collapse state of the row details. You can choose to have some rows expanded by default so details of those rows will be visible from the start.\n\n## Configurable expand/collapse state\n\nUsing the , you can control (in a declarative way) which rows are expanded and which are collapsed. In addition, if you prefer the imperative approach, we also have an [API to work with row details](/docs/reference/row-detail-api).\n\nIf you have some rows with details and some without, that's also covered. Use the to control which rows will have details and which will not.\n\n\n\nAnother important configuration is choosing the column that has the row detail expand/collapse icon. Use the prop on the column that needs to display the expand/collapse icon. If this prop is a function, it can be used to customize the icon rendered for expanding/collapsing the row detail.\n\n\n\n## Master detail caching\n\nBy far the most common scenario will be to render another DataGrid in the detail row.\n\nFor such cases we offer a caching mechanism that will keep the state of the detail DataGrid when the user collapses and then expands the row again.\n\n\n\nTo enable caching, use the prop.\n\nIt can be one of the following:\n\n- `false` - caching is disabled - this is the default\n- `true` - enables caching for all detail DataGrids\n- `number` - the maximum number of detail DataGrids to keep in the cache. When the limit is reached, the oldest detail DataGrid will be removed from the cache.\n\n\n\n\n\n\n\nThis example will cache the last 5 detail DataGrids - meaning they won't reload when you expand them again.\nYou can try collapsing a row and then expanding it again to see the caching in action - it won't reload the data.\nBut when you open up a row that hasn't been opened before, it will load the data from the remote location.\n\n\n\n\n\nRead our docs on [caching detail DataGrids](/docs/learn/master-detail/caching-detail-datagrid) to learn more how you can use this feature to improve the user experience.\n" }, + "/blog/2024/03/08/how-to-select-cells-and-use-cell-selection": { + "filePath": "/blog/2024/03/08/how-to-select-cells-and-use-cell-selection", + "routePath": "/blog/2024/03/08/how-to-select-cells-and-use-cell-selection", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2024/03/08/how-to-select-cells-and-use-cell-selection.page.md", + "fileName": "how-to-select-cells-and-use-cell-selection.page.md", + "folderPath": "/blog/2024/03/08/", + "frontmatter": { + "title": "How to use multiple cell selection in the DataGrid", + "author": "admin", + "date": "2024-03-08T00:00:00.000Z", + "authorData": { + "label": "admin" + } + }, + "excerpt": "The article will cover some popular cell-selection scenarios in Infinite React DataGrid.", + "readingTime": "3 min read", + "content": "\nThe article will cover some popular cell-selection scenarios in Infinite React DataGrid.\n\n## Multiple cell selection\n\nBy far, the most common use-case for cell selection is multiple cell selection.\nFor this, you need to configure the prop on the `` component to use `\"multi-cell\"`.\n\n\n\nIn addition, if you want to specify a default value for cell selection, you can use the prop - or the controlled alternative , in which case also make sure you update the value when is called.\n\n\n\n```tsx\n\n```\n\n\n\nWhen multiple cell selection is configured in the React DataGrid, the user can select cells by `CMD`/`CTRL` clicking to add a single cell to the selection or by `SHIFT` clicking to select a range of cells.\n\n## Showing a chart based on selected cells\n\nLet's implement a common use-case for multiple cell selection - showing charts based on the selected cells, for example, a bar chart, with names on the x axis and ages on the y axis.\n\n\n\nIn this example, to retrieve the values from the selected cells, we used the from the [cell selection API](/docs/reference/cell-selection-api).\n\n## Cell selection format\n\nThe prop is an object with the following shape:\n\n- `defaultSelection` - `boolean` - whether or not cells are selected by default.\n- either:\n - `selectedCells`: `[rowId, colId][]` - an array of cells that should be selected (this is combined with `defaultSelection: false`)\n- or\n - `deselectedCells`: `[rowId, colId][]` - an array of cells that should be deselected (this is combined with `defaultSelection: true`)\n\n\n\nWhen `defaultSelection` is `true`, you will only need to specify the `deselectedCells` prop.\n\nAnd when `defaultSelection` is `false`, you will only need to specify the `selectedCells` prop.\n\n\n\nIn this way, you can either specify which cells should be selected or which cells should be deselected - and have a default that matches the most common case.\n\n\n\nThe `selectedCells`/`deselectedCells` are arrays of `[rowId, colId]` tuples. The `rowId` is the `id` of the row (the primary key), and the `colId` is the `id` of the column (the identifier of the column in the prop).\n\n\n\n## Using include-lists and exclude-lists for specifying cell selection\n\nAs already demonstrated in the previous snippet, you can pass a default value for cell selection.\n\nIn addition to listing or excluding specific cells from selection, you can use wildcards:\n\n```tsx title=\"Include-list: selecting all cells in a column\"\nconst defaultCellSelection = {\n defaultSelection: false, // all cells are deselected by default\n selectedCells: [\n // all cells in the stack column\n ['*', 'stack'],\n // also this specific cell\n ['row2', 'firstName'],\n ],\n};\n```\n\n```tsx title=\"Include-list: selecting all cells in a row\"\nconst defaultCellSelection = {\n defaultSelection: false, // all cells are deselected by default\n selectedCells: [\n // all cells in the row\n ['row1', '*'],\n // also this specific cell\n ['row2', 'firstName'],\n ],\n};\n```\n\n```tsx title=\"Exclude-list: selecting everything except a column\"\nconst defaultCellSelection = {\n defaultSelection: true, // all cells are selected by default\n deselectedCells: [['*', 'stack']],\n};\n```\n\n\n\n## Single cell selection\n\nSingle cell selection is not common - what you probably want to use in this case is the prop to emulate single cell selection - but that's basically cell navigation.\n" + }, + "/blog/2024/04/18/the-best-testing-setup-for-frontends-playwright-nextjs": { + "filePath": "/blog/2024/04/18/the-best-testing-setup-for-frontends-playwright-nextjs", + "routePath": "/blog/2024/04/18/the-best-testing-setup-for-frontends-playwright-nextjs", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2024/04/18/the-best-testing-setup-for-frontends-playwright-nextjs.page.md", + "fileName": "the-best-testing-setup-for-frontends-playwright-nextjs.page.md", + "folderPath": "/blog/2024/04/18/", + "frontmatter": { + "title": "The best testing setup for frontends, with Playwright and NextJS", + "author": "admin", + "date": "2024-04-18T00:00:00.000Z", + "authorData": { + "label": "admin" + } + }, + "excerpt": "We want to share with you the best testing setup we've experienced - and this includes using [Playwright](https://playwright.dev/) and [NextJS](https://nextjs.org/). It's a setup we've come up with for Infinite React DataGrid, which is a complex component, with lots of things to test, but this configuration has helped us ship with more confidence and speed.", + "readingTime": "13 min read", + "content": "\nWe want to share with you the best testing setup we've experienced - and this includes using [Playwright](https://playwright.dev/) and [NextJS](https://nextjs.org/). It's a setup we've come up with for Infinite React DataGrid, which is a complex component, with lots of things to test, but this configuration has helped us ship with more confidence and speed.\n\n## What you should expect from a testing setup\n\n### Fast feedback \n\nโšก๏ธ Quick โšก๏ธ feedback is a no-brainer, since without a fast turnaround, devs will not have the patience to run the tests and will move on to the next \"burning\" issue or to the next cup of coffee.\n\nAlso, you can't run all the test suite at once, so you need to be able to run only the tests that are relevant to the changes you've made. This has long been available in unit-testing frameworks, but it's not so common in end-to-end testing, when loading a webpage and rendering an actual component is involved.\n\nIn this article we want to show you how we achieved fast feedback that allows rapid developer iterations.\n\n### Stability and predictability\n\nYou don't need flaky tests that fail randomly - it's the last thing you want when doing a release, or even during development.\nWaiting for an element to appear on page or an animation to finish or an interaction to complete is a common source of flakiness in end-to-end tests, but Playwright gives you the tools to address these issues - thank you [Playwright locators](https://playwright.dev/docs/locators) ๐Ÿ™ and other playwright testing framework features.\n\n### Ease of maintenance and debugging\n\nAnother crucial point when you setup a testing framework and start writing tests is how easy is to write a new test, to inspect what is being tested and to reproduce failing tests. All these should be as easy as opening loading a URL in a browser - this is exactly what this setup gives you, with NextJS and Playwright playing very well together.\nWhen one of your tests fails, Playwright outputs a command you can run to reproduce the exact failure and actually see the UI at the moment of the failure, with the ability to navigate through the test timeline and see what happened before the failure.\n\n## Setting up NextJS and Playwright\n\n### Step 1 - creating the NextJS app\n```sh\n$ npx create-next-app@latest\n```\n\nYou're being asked a few questions. For `Would you like to use src/ directory?` we chose `Yes`. Also, we're using TypeScript.\n\nWhen you run this command, make sure for this question `Would you like to use App Router?` you reply `No`, as you want to use file-system routing to make it very easy and intuitive to add new pages and tests.\n\n\nCheck out our repo for this stage of the setup - [Step 1 - setting up NextJS](https://github.com/infinite-table/testing-setup-nextjs-playwright/tree/01-setup-nextjs).\n\n\n\n\nBefore you go to the next step, you can configure your `next.config.mjs` to use the `.page` extension for your pages.\n\n```js\nconst nextConfig = {\n reactStrictMode: true,\n pageExtensions: [\"page.tsx\", \"page.ts\", \"page.js\"],\n};\nexport default nextConfig\n```\n\nThis is useful so NextJS will only compile those files as pages that your tests will be targeting, and not all the files in the `pages` folder, which will also contain your tests.\n\nSo you know all your `.page` files are pages that your tests will be run against and all your `.spec` files are tests (see next step).\n\n\n\n### Step 2 - setting up Playwright\n\n```sh\n$ npm init playwright@latest\n```\n\nAgain a few questions about your setup.\n\n`Where to put your end-to-end tests?` - choose `src/pages` - which makes your NextJS pages folder the place where you put your end-to-end tests.\n\nThis script installs `@playwright/test` and creates a `playwright.config.ts` file with the default configuration. Most importantly, the `testDir` is configured to `./src/pages`.\n\nBy default, all `.spec` files in the `testDir` (which is set to `src/pages`) will be run as tests.\n\n\nCheck out our repo for this stage of the setup - [Step 2 - setting up Playwright](https://github.com/infinite-table/testing-setup-nextjs-playwright/tree/02-setup-playwright).\n\n\nThere are some additional configurations you might want to do in this step.\nYou probably want to change the default `reporter` from `'html'` to `'list'` in your `playwright.config.ts` - the `'html'` reporter will open a browser window with the test results, which you might not prefer. You'd rather see the results in the terminal.\n\n ```ts {3} title=\"Configure the reporter in playwright.config.ts\"\nexport default defineConfig({\n testDir: \"./src/pages\",\n reporter: \"list\", // the 'html' reporter will open a browser window with the test results\n // ...\n})\n ```\n\n\n\nFor now, you might want to only run your tests in one browser, so comment out any additional entries in the `projects` array in your `playwright.config.ts` file - that controls the devices that will be used in your tests.\n\n\n\nThe last piece of the puzzle before running your first test with Playwright is defining the `test` script in your `package.json`.\n\n```json {4} title=\"package.json\"\n{\n \"name\": \"testing-setup-nextjs-playwright\",\n \"scripts\": {\n \"test\": \"npx playwright test\",\n \"dev\": \"next dev\",\n \"build\": \"next build\",\n },\n}\n```\n\nExecuting the `npm run test` command will run the tests in the `src/pages` folder - for now, you should have a single file, `example.spec.ts`, which was generated by the `npm init playwright` command.\n\n![Playwright test output](/blog-images/step-2-initial-results.png)\n\nYour initial test file was something very basic. This file is importing the `test` (and `expect`) function from `@playwright/test` - and this is what you're using to define tests (and write assertions).\n\n```ts {1} title=\"example.spec.ts\"\nimport { test, expect } from \"@playwright/test\";\n\ntest(\"has title\", async ({ page }) => {\n await page.goto(\"https://playwright.dev/\");\n\n // Expect a title \"to contain\" a substring.\n await expect(page).toHaveTitle(/Playwright/);\n});\n```\n\n### Step 3 - configuring the naming convention in Playwright to open the right pages\n\nThis step is probably the most important one in your configuration. Normally your tests will open webpages before you start testing - but this is not something you want to do explicitly in your project. Rather, you want your tests to automatically navigate to the corresponding page for the test. This is what this step is achieving - and we're using [Playwright fixtures](https://playwright.dev/docs/test-fixtures) to do this.\n\nThink of a fixture as some code that's configuring the testing environment for each of your tests.\nA fixture will extend the `test` function from `@playwright/test` with additional functionalities. Mainly, we want before every test to open the correct page, without writing this explicitly in every test. Based on the location of the test file in the file system, we want to navigate to a webpage for it and we assume it will have the same path as the test file. This is possible because NextJS is configured to use file-system routing.\n\n```ts {1,3-5} title=\"Defining the fixture file - test-fixtures.ts\"\nimport {\n test as base,\n expect,\n PlaywrightTestArgs,\n PlaywrightTestOptions,\n Page,\n} from \"@playwright/test\";\n\nexport * from \"@playwright/test\";\n\nexport const test = base.extend<\n PlaywrightTestArgs &\n PlaywrightTestOptions\n>({\n //@ts-ignore\n page: async ({ baseURL, page }, use, testInfo) => {\n const testFilePath = testInfo.titlePath[0];\n const fileName = testFilePath.replace(\".spec.ts\", \"\");\n const url = `${baseURL}${fileName}`;\n\n // navigate to the corresponding page for this test\n await page.goto(url);\n\n await use(page);\n },\n});\n```\n\nWe'll give this fixture file the name `test-fixtures.ts` and put it in the root of the project.\n\nNow instead of importing the `test` function from `@playwright/test` we want to import it from the `test-fixtures.ts` file - we'll do this in all our tests. To make this easier, let's also define a path alias in the `tsconfig.json` file.\n\n```json {4} title=\"tsconfig.json\"\n{\n \"compilerOptions\": {\n \"paths\": {\n \"@playwright/test\": [\"test-fixtures.ts\"],\n }\n }\n}\n```\n\nWe're ready to write our first test page in NextJS and use the new fixture in the Playwright test.\n\n```tsx title=\"src/pages/example.page.tsx\"\nexport default function App() {\n return
Hello world
;\n}\n```\n\n```ts {1} title=\"src/pages/example.spec.ts\"\nimport { test, expect } from \"@testing\"; // notice the import\n\ntest(\"Main example has corrent content\", async ({ page }) => {\n // notice we don't need to navigate to the page, this is done by the fixture\n await expect(await page.innerText(\"body\")).toContain(\"Hello world\");\n});\n```\n\nFor our tests against the NextJS app, we obviously need to start the app.\n\nLet's configure a custom port of `5432` in the package.json `dev` script.\n```json {3} title=\"package.json\"\n{\n \"scripts\": {\n \"dev\": \"next dev --port 5432\",\n \"test\": \"npx playwright test\"\n }\n //...\n}\n```\nWe need to use the same port in the Playwright configuration file.\nAlso we'll use a smaller test `timeout` (the default is 30s).\n\n```ts {9,11} title=\"playwright.config.ts\"\nimport { defineConfig } from \"@playwright/test\";\n/**\n * See https://playwright.dev/docs/test-configuration.\n */\nexport default defineConfig({\n testDir: \"./src/pages\",\n reporter: \"list\",\n use: {\n baseURL: \"http://localhost:5432/\",\n },\n timeout: process.env.CI ? 10000 : 4000,\n // ... more options\n});\n\n```\n\nWe're now ready to roll!\n\n`npm run dev` will run NextJS and `npm run test` will run the tests against your NextJS app.\n\n\n\nTo make the setup easier, avoid using `index.page.tsx` pages in NextJS - give your pages another name, to avoid issues with directory index pages in tests. This can easily be solved in the test fixture, but for the sake of clarity and brevity we're not doing it now.\n\n\n\n\n\n\nCheck out our repo for this stage of the setup - [Step 3 - configuring the Playwright fixture and naming convention](https://github.com/infinite-table/testing-setup-nextjs-playwright/tree/03-configure-naming-convention).\n\n\n\n### Step 4 - adding watch mode\n\nAs we mentioned initially, no testing setup is great unless it gives you very fast feedback. For this, we obviously need watch mode.\n\nWe want to be able to re-run tests when our test code has changed, but even better, when our NextJS page has changed - so the page the test is running against.\nNextJS has watch mode built-in in dev mode, so whenever a page is changed, it's recompiled and the browser is served the updated page. We'll use this in our advantage, so tests will always see the latest version of the page.\nThis means the last piece of the puzzle is to make Playwright re-run the tests when the page has changed or the test itself has changed.\n\nFor this, we'll use [`chokidar`](https://www.npmjs.com/package/chokidar) - more specifically the [`chokidar-cli`](https://www.npmjs.com/package/chokidar-cli) package. `chokidar` is probably the most useful file watching library for the nodejs ecosystem and it will serve us well.\n\n```json {4} title=\"package.json\"\n{\n \"scripts\": {\n \"test\": \"npx playwright test\",\n \"test:watch\": \"chokidar '**/*.spec.ts' '**/*.page.tsx' -c 'test_file_path=$(echo {path} | sed s/page.tsx/spec.ts/) && npm run test -- --retries=0 ${test_file_path}'\"\n }\n}\n```\n\nThe `test:watch` script is watching for changes in `.spec.ts` files and `.page.tsx` files and whenever there's a change in one of those files, it's re-running the respective test. (When a change was found in a `.page.tsx` file, we're using `sed` to replace the `.page.tsx` extension with `.spec.ts`, because we want to pass the test file to the `npm run test` command so it knows what test to re-run.)\n\n\n\nThe above `test:watch` script was written for MacOS (and Unix-like systems). If you're using Windows, you might need to adjust the command to achieve the same result.\n\n\n\n\n\nDon't forget to run `npm run dev` before running `npm run test` or `npm run test:watch` - you need the NextJS app running to be able to run the tests. After all, that's what you're testing ๐Ÿ˜….\n\n\n\n### Step 5 - running tests on production build\n\nIn the last step, we want to build a production build of the NextJS app and run the tests against it. \n\nSo first let's configure the `next.config.mjs` file to build a static site when `npm run build` is run.\n\n```js {3} title=\"next.config.mjs - configured to export a static site\"\nconst nextConfig = {\n reactStrictMode: true,\n output: \"export\",\n pageExtensions: [\"page.tsx\", \"page.ts\", \"page.js\"],\n};\n\nexport default nextConfig;\n```\nNotice the `\"output\": \"export\"` property. Having configured this, the `npm run build` will create an `/out` folder with the compiled assets and pages of the app.\n\nNext we need an NPM script to serve the compiled app with a static server.\n\n```json {3,4} title=\"package.json - serve script\"\n{\n \"scripts\": {\n \"serve\": \"npx http-server --port 5432 out\",\n \"//...\": \"// other scripts\"\n },\n}\n```\n\nWe could either run this `serve` script ourselves to start the webserver before running our tests or even better, we can instruct Playwright to [use this webserver automatically](https://playwright.dev/docs/test-webserver#configuring-a-web-server). So let's do that in our `playwright.config.ts` file.\n\n```ts {3,5} title=\"playwright.config.ts - configured to use a custom server\"\nexport default defineConfig({\n //... other options\n\n // on CI, run the static server to serve the built app\n webServer: process.env.CI\n ? {\n command: \"npm run serve\",\n url: \"http://localhost:5432\",\n reuseExistingServer: true,\n timeout: 120 * 1000,\n }\n : undefined,\n})\n```\n\n\n\nIn order for Playwright to correctly detect the webserver is running ok, we need to make sure we have a valid index page at that address, so we need to add a `index.page.tsx` file in the `pages` folder.\n\n```tsx title=\"src/pages/index.page.tsx\"\nexport default function App() {\n return
Index page
;\n}\n```\n\nThis is just useful in the CI environment so that Playwright can detect the server is running and the app is served correctly.\n\n
\n\nNext, in order to run our tests as if we're in the CI environment, let's add a `test:ci` script, which is basically calling the `test` script but setting the `CI` environment variable to `true`.\n\n```json {3,4} title=\"package.json - test:ci script\"\n{\n \"scripts\": {\n \"test:ci\": \"CI=true npm run test\",\n \"test\": \"npx playwright test\",\n \"serve\": \"npx http-server --port 5432 out\",\n \"//...\": \"// other scripts\"\n },\n}\n```\n\n\nWe're now ready to run our tests against the production build of the NextJS app.\n\n```sh\nnpm run build && npm run test:ci\n```\n\n\nThis script first builds the NextJS static app and then runs the tests against it.\n\n## Configuring CI github actions \n\nWe're now ready to integrate our [testing workflow into CI via Github actions](https://playwright.dev/docs/ci-intro).\n\nCreate a YAML file `.github/workflows/test.yml` in the root of your project with the following content.\n\n```yaml {19,23} title=\".github/workflows/test.yml\"\nname: Playwright Tests\non:\n push:\n branches: [main, master]\n pull_request:\n branches: [main, master]\njobs:\n test:\n timeout-minutes: 60\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - uses: actions/setup-node@v4\n with:\n node-version: lts/*\n - name: Install dependencies\n run: npm ci\n - name: Build app\n run: npm run build\n - name: Install Playwright Browsers\n run: npx playwright install --with-deps\n - name: Run Playwright tests\n run: npm run test\n - uses: actions/upload-artifact@v4\n if: always()\n with:\n name: playwright-report\n path: playwright-report/\n retention-days: 30\n```\n\nWith this, you're ready to go! Push your changes to the main branch and see your tests running and passing in the CI environment. Go green! ๐ŸŸข\n\n## Demo repository\n\nYou can find the full setup in our [testing-setup-nextjs-playwright repo](https://github.com/infinite-table/testing-setup-nextjs-playwright/tree/main?tab=readme-ov-file). Check it out and give it a star if you find it useful.\n\n## Profit ๐Ÿš€\n\nWith this setup, you have a very convenient way to write your tests against real pages, loaded in a real browser, just like the end user experiences. And with the watch mode giving you instant feedback, you no longer have an excuse to not write tests.\n\nThis is the same setup we've been using for developing and testing the [Infinite Table React DataGrid](https://infinite-table.com) and it has been serving us really well.\n\nDataGrids are some of the most complex UI components one can build, so having a reliable tool that allowed us to iterate very quickly was crucial to us. This helped us add new features, while being confident that all of the existing core functionalities like row/column grouping, filtering, sorting, pagination, pivoting still work as expected.\n\nThe setup was a pivotal point in our development process and it's what gives us and our enterprise customers the peace of mind that the product is stable and reliable, both now and in the future.\n\n\n" + }, "/blog/2024/03/06/setting-up-master-detail-datagrid": { "filePath": "/blog/2024/03/06/setting-up-master-detail-datagrid", "routePath": "/blog/2024/03/06/setting-up-master-detail-datagrid", @@ -3295,24 +3513,6 @@ "readingTime": "1 min read", "content": "\nWe recently [announced the release of master-detail in the Infinite React DataGrid](/blog/2024/02/26/master-detail-now-available-in-react-datagrid), so we also made a video tutorial to follow along, if video is your preferred learning method.\n\nThis shows the very basics of configuring the Infinite React DataGrid with master-detail, and it's a great starting point for more advanced configurations.\n\n\n\nYou can find the full source code for the tutorial in the code sandbox below.\n\nThis example is two levels deep, but the Infinite React DataGrid supports any number of levels of master-detail.\n\n\n" }, - "/blog/2024/04/18/the-best-testing-setup-for-frontends-playwright-nextjs": { - "filePath": "/blog/2024/04/18/the-best-testing-setup-for-frontends-playwright-nextjs", - "routePath": "/blog/2024/04/18/the-best-testing-setup-for-frontends-playwright-nextjs", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2024/04/18/the-best-testing-setup-for-frontends-playwright-nextjs.page.md", - "fileName": "the-best-testing-setup-for-frontends-playwright-nextjs.page.md", - "folderPath": "/blog/2024/04/18/", - "frontmatter": { - "title": "The best testing setup for frontends, with Playwright and NextJS", - "author": "admin", - "date": "2024-04-18T00:00:00.000Z", - "authorData": { - "label": "admin" - } - }, - "excerpt": "We want to share with you the best testing setup we've experienced - and this includes using [Playwright](https://playwright.dev/) and [NextJS](https://nextjs.org/). It's a setup we've come up with for Infinite React DataGrid, which is a complex component, with lots of things to test, but this configuration has helped us ship with more confidence and speed.", - "readingTime": "13 min read", - "content": "\nWe want to share with you the best testing setup we've experienced - and this includes using [Playwright](https://playwright.dev/) and [NextJS](https://nextjs.org/). It's a setup we've come up with for Infinite React DataGrid, which is a complex component, with lots of things to test, but this configuration has helped us ship with more confidence and speed.\n\n## What you should expect from a testing setup\n\n### Fast feedback \n\nโšก๏ธ Quick โšก๏ธ feedback is a no-brainer, since without a fast turnaround, devs will not have the patience to run the tests and will move on to the next \"burning\" issue or to the next cup of coffee.\n\nAlso, you can't run all the test suite at once, so you need to be able to run only the tests that are relevant to the changes you've made. This has long been available in unit-testing frameworks, but it's not so common in end-to-end testing, when loading a webpage and rendering an actual component is involved.\n\nIn this article we want to show you how we achieved fast feedback that allows rapid developer iterations.\n\n### Stability and predictability\n\nYou don't need flaky tests that fail randomly - it's the last thing you want when doing a release, or even during development.\nWaiting for an element to appear on page or an animation to finish or an interaction to complete is a common source of flakiness in end-to-end tests, but Playwright gives you the tools to address these issues - thank you [Playwright locators](https://playwright.dev/docs/locators) ๐Ÿ™ and other playwright testing framework features.\n\n### Ease of maintenance and debugging\n\nAnother crucial point when you setup a testing framework and start writing tests is how easy is to write a new test, to inspect what is being tested and to reproduce failing tests. All these should be as easy as opening loading a URL in a browser - this is exactly what this setup gives you, with NextJS and Playwright playing very well together.\nWhen one of your tests fails, Playwright outputs a command you can run to reproduce the exact failure and actually see the UI at the moment of the failure, with the ability to navigate through the test timeline and see what happened before the failure.\n\n## Setting up NextJS and Playwright\n\n### Step 1 - creating the NextJS app\n```sh\n$ npx create-next-app@latest\n```\n\nYou're being asked a few questions. For `Would you like to use src/ directory?` we chose `Yes`. Also, we're using TypeScript.\n\nWhen you run this command, make sure for this question `Would you like to use App Router?` you reply `No`, as you want to use file-system routing to make it very easy and intuitive to add new pages and tests.\n\n\nCheck out our repo for this stage of the setup - [Step 1 - setting up NextJS](https://github.com/infinite-table/testing-setup-nextjs-playwright/tree/01-setup-nextjs).\n\n\n\n\nBefore you go to the next step, you can configure your `next.config.mjs` to use the `.page` extension for your pages.\n\n```js\nconst nextConfig = {\n reactStrictMode: true,\n pageExtensions: [\"page.tsx\", \"page.ts\", \"page.js\"],\n};\nexport default nextConfig\n```\n\nThis is useful so NextJS will only compile those files as pages that your tests will be targeting, and not all the files in the `pages` folder, which will also contain your tests.\n\nSo you know all your `.page` files are pages that your tests will be run against and all your `.spec` files are tests (see next step).\n\n\n\n### Step 2 - setting up Playwright\n\n```sh\n$ npm init playwright@latest\n```\n\nAgain a few questions about your setup.\n\n`Where to put your end-to-end tests?` - choose `src/pages` - which makes your NextJS pages folder the place where you put your end-to-end tests.\n\nThis script installs `@playwright/test` and creates a `playwright.config.ts` file with the default configuration. Most importantly, the `testDir` is configured to `./src/pages`.\n\nBy default, all `.spec` files in the `testDir` (which is set to `src/pages`) will be run as tests.\n\n\nCheck out our repo for this stage of the setup - [Step 2 - setting up Playwright](https://github.com/infinite-table/testing-setup-nextjs-playwright/tree/02-setup-playwright).\n\n\nThere are some additional configurations you might want to do in this step.\nYou probably want to change the default `reporter` from `'html'` to `'list'` in your `playwright.config.ts` - the `'html'` reporter will open a browser window with the test results, which you might not prefer. You'd rather see the results in the terminal.\n\n ```ts {3} title=\"Configure the reporter in playwright.config.ts\"\nexport default defineConfig({\n testDir: \"./src/pages\",\n reporter: \"list\", // the 'html' reporter will open a browser window with the test results\n // ...\n})\n ```\n\n\n\nFor now, you might want to only run your tests in one browser, so comment out any additional entries in the `projects` array in your `playwright.config.ts` file - that controls the devices that will be used in your tests.\n\n\n\nThe last piece of the puzzle before running your first test with Playwright is defining the `test` script in your `package.json`.\n\n```json {4} title=\"package.json\"\n{\n \"name\": \"testing-setup-nextjs-playwright\",\n \"scripts\": {\n \"test\": \"npx playwright test\",\n \"dev\": \"next dev\",\n \"build\": \"next build\",\n },\n}\n```\n\nExecuting the `npm run test` command will run the tests in the `src/pages` folder - for now, you should have a single file, `example.spec.ts`, which was generated by the `npm init playwright` command.\n\n![Playwright test output](/blog-images/step-2-initial-results.png)\n\nYour initial test file was something very basic. This file is importing the `test` (and `expect`) function from `@playwright/test` - and this is what you're using to define tests (and write assertions).\n\n```ts {1} title=\"example.spec.ts\"\nimport { test, expect } from \"@playwright/test\";\n\ntest(\"has title\", async ({ page }) => {\n await page.goto(\"https://playwright.dev/\");\n\n // Expect a title \"to contain\" a substring.\n await expect(page).toHaveTitle(/Playwright/);\n});\n```\n\n### Step 3 - configuring the naming convention in Playwright to open the right pages\n\nThis step is probably the most important one in your configuration. Normally your tests will open webpages before you start testing - but this is not something you want to do explicitly in your project. Rather, you want your tests to automatically navigate to the corresponding page for the test. This is what this step is achieving - and we're using [Playwright fixtures](https://playwright.dev/docs/test-fixtures) to do this.\n\nThink of a fixture as some code that's configuring the testing environment for each of your tests.\nA fixture will extend the `test` function from `@playwright/test` with additional functionalities. Mainly, we want before every test to open the correct page, without writing this explicitly in every test. Based on the location of the test file in the file system, we want to navigate to a webpage for it and we assume it will have the same path as the test file. This is possible because NextJS is configured to use file-system routing.\n\n```ts {1,3-5} title=\"Defining the fixture file - test-fixtures.ts\"\nimport {\n test as base,\n expect,\n PlaywrightTestArgs,\n PlaywrightTestOptions,\n Page,\n} from \"@playwright/test\";\n\nexport * from \"@playwright/test\";\n\nexport const test = base.extend<\n PlaywrightTestArgs &\n PlaywrightTestOptions\n>({\n //@ts-ignore\n page: async ({ baseURL, page }, use, testInfo) => {\n const testFilePath = testInfo.titlePath[0];\n const fileName = testFilePath.replace(\".spec.ts\", \"\");\n const url = `${baseURL}${fileName}`;\n\n // navigate to the corresponding page for this test\n await page.goto(url);\n\n await use(page);\n },\n});\n```\n\nWe'll give this fixture file the name `test-fixtures.ts` and put it in the root of the project.\n\nNow instead of importing the `test` function from `@playwright/test` we want to import it from the `test-fixtures.ts` file - we'll do this in all our tests. To make this easier, let's also define a path alias in the `tsconfig.json` file.\n\n```json {4} title=\"tsconfig.json\"\n{\n \"compilerOptions\": {\n \"paths\": {\n \"@playwright/test\": [\"test-fixtures.ts\"],\n }\n }\n}\n```\n\nWe're ready to write our first test page in NextJS and use the new fixture in the Playwright test.\n\n```tsx title=\"src/pages/example.page.tsx\"\nexport default function App() {\n return
Hello world
;\n}\n```\n\n```ts {1} title=\"src/pages/example.spec.ts\"\nimport { test, expect } from \"@testing\"; // notice the import\n\ntest(\"Main example has corrent content\", async ({ page }) => {\n // notice we don't need to navigate to the page, this is done by the fixture\n await expect(await page.innerText(\"body\")).toContain(\"Hello world\");\n});\n```\n\nFor our tests against the NextJS app, we obviously need to start the app.\n\nLet's configure a custom port of `5432` in the package.json `dev` script.\n```json {3} title=\"package.json\"\n{\n \"scripts\": {\n \"dev\": \"next dev --port 5432\",\n \"test\": \"npx playwright test\"\n }\n //...\n}\n```\nWe need to use the same port in the Playwright configuration file.\nAlso we'll use a smaller test `timeout` (the default is 30s).\n\n```ts {9,11} title=\"playwright.config.ts\"\nimport { defineConfig } from \"@playwright/test\";\n/**\n * See https://playwright.dev/docs/test-configuration.\n */\nexport default defineConfig({\n testDir: \"./src/pages\",\n reporter: \"list\",\n use: {\n baseURL: \"http://localhost:5432/\",\n },\n timeout: process.env.CI ? 10000 : 4000,\n // ... more options\n});\n\n```\n\nWe're now ready to roll!\n\n`npm run dev` will run NextJS and `npm run test` will run the tests against your NextJS app.\n\n\n\nTo make the setup easier, avoid using `index.page.tsx` pages in NextJS - give your pages another name, to avoid issues with directory index pages in tests. This can easily be solved in the test fixture, but for the sake of clarity and brevity we're not doing it now.\n\n\n\n\n\n\nCheck out our repo for this stage of the setup - [Step 3 - configuring the Playwright fixture and naming convention](https://github.com/infinite-table/testing-setup-nextjs-playwright/tree/03-configure-naming-convention).\n\n\n\n### Step 4 - adding watch mode\n\nAs we mentioned initially, no testing setup is great unless it gives you very fast feedback. For this, we obviously need watch mode.\n\nWe want to be able to re-run tests when our test code has changed, but even better, when our NextJS page has changed - so the page the test is running against.\nNextJS has watch mode built-in in dev mode, so whenever a page is changed, it's recompiled and the browser is served the updated page. We'll use this in our advantage, so tests will always see the latest version of the page.\nThis means the last piece of the puzzle is to make Playwright re-run the tests when the page has changed or the test itself has changed.\n\nFor this, we'll use [`chokidar`](https://www.npmjs.com/package/chokidar) - more specifically the [`chokidar-cli`](https://www.npmjs.com/package/chokidar-cli) package. `chokidar` is probably the most useful file watching library for the nodejs ecosystem and it will serve us well.\n\n```json {4} title=\"package.json\"\n{\n \"scripts\": {\n \"test\": \"npx playwright test\",\n \"test:watch\": \"chokidar '**/*.spec.ts' '**/*.page.tsx' -c 'test_file_path=$(echo {path} | sed s/page.tsx/spec.ts/) && npm run test -- --retries=0 ${test_file_path}'\"\n }\n}\n```\n\nThe `test:watch` script is watching for changes in `.spec.ts` files and `.page.tsx` files and whenever there's a change in one of those files, it's re-running the respective test. (When a change was found in a `.page.tsx` file, we're using `sed` to replace the `.page.tsx` extension with `.spec.ts`, because we want to pass the test file to the `npm run test` command so it knows what test to re-run.)\n\n\n\nThe above `test:watch` script was written for MacOS (and Unix-like systems). If you're using Windows, you might need to adjust the command to achieve the same result.\n\n\n\n\n\nDon't forget to run `npm run dev` before running `npm run test` or `npm run test:watch` - you need the NextJS app running to be able to run the tests. After all, that's what you're testing ๐Ÿ˜….\n\n\n\n### Step 5 - running tests on production build\n\nIn the last step, we want to build a production build of the NextJS app and run the tests against it. \n\nSo first let's configure the `next.config.mjs` file to build a static site when `npm run build` is run.\n\n```js {3} title=\"next.config.mjs - configured to export a static site\"\nconst nextConfig = {\n reactStrictMode: true,\n output: \"export\",\n pageExtensions: [\"page.tsx\", \"page.ts\", \"page.js\"],\n};\n\nexport default nextConfig;\n```\nNotice the `\"output\": \"export\"` property. Having configured this, the `npm run build` will create an `/out` folder with the compiled assets and pages of the app.\n\nNext we need an NPM script to serve the compiled app with a static server.\n\n```json {3,4} title=\"package.json - serve script\"\n{\n \"scripts\": {\n \"serve\": \"npx http-server --port 5432 out\",\n \"//...\": \"// other scripts\"\n },\n}\n```\n\nWe could either run this `serve` script ourselves to start the webserver before running our tests or even better, we can instruct Playwright to [use this webserver automatically](https://playwright.dev/docs/test-webserver#configuring-a-web-server). So let's do that in our `playwright.config.ts` file.\n\n```ts {3,5} title=\"playwright.config.ts - configured to use a custom server\"\nexport default defineConfig({\n //... other options\n\n // on CI, run the static server to serve the built app\n webServer: process.env.CI\n ? {\n command: \"npm run serve\",\n url: \"http://localhost:5432\",\n reuseExistingServer: true,\n timeout: 120 * 1000,\n }\n : undefined,\n})\n```\n\n\n\nIn order for Playwright to correctly detect the webserver is running ok, we need to make sure we have a valid index page at that address, so we need to add a `index.page.tsx` file in the `pages` folder.\n\n```tsx title=\"src/pages/index.page.tsx\"\nexport default function App() {\n return
Index page
;\n}\n```\n\nThis is just useful in the CI environment so that Playwright can detect the server is running and the app is served correctly.\n\n
\n\nNext, in order to run our tests as if we're in the CI environment, let's add a `test:ci` script, which is basically calling the `test` script but setting the `CI` environment variable to `true`.\n\n```json {3,4} title=\"package.json - test:ci script\"\n{\n \"scripts\": {\n \"test:ci\": \"CI=true npm run test\",\n \"test\": \"npx playwright test\",\n \"serve\": \"npx http-server --port 5432 out\",\n \"//...\": \"// other scripts\"\n },\n}\n```\n\n\nWe're now ready to run our tests against the production build of the NextJS app.\n\n```sh\nnpm run build && npm run test:ci\n```\n\n\nThis script first builds the NextJS static app and then runs the tests against it.\n\n## Configuring CI github actions \n\nWe're now ready to integrate our [testing workflow into CI via Github actions](https://playwright.dev/docs/ci-intro).\n\nCreate a YAML file `.github/workflows/test.yml` in the root of your project with the following content.\n\n```yaml {19,23} title=\".github/workflows/test.yml\"\nname: Playwright Tests\non:\n push:\n branches: [main, master]\n pull_request:\n branches: [main, master]\njobs:\n test:\n timeout-minutes: 60\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - uses: actions/setup-node@v4\n with:\n node-version: lts/*\n - name: Install dependencies\n run: npm ci\n - name: Build app\n run: npm run build\n - name: Install Playwright Browsers\n run: npx playwright install --with-deps\n - name: Run Playwright tests\n run: npm run test\n - uses: actions/upload-artifact@v4\n if: always()\n with:\n name: playwright-report\n path: playwright-report/\n retention-days: 30\n```\n\nWith this, you're ready to go! Push your changes to the main branch and see your tests running and passing in the CI environment. Go green! ๐ŸŸข\n\n## Demo repository\n\nYou can find the full setup in our [testing-setup-nextjs-playwright repo](https://github.com/infinite-table/testing-setup-nextjs-playwright/tree/main?tab=readme-ov-file). Check it out and give it a star if you find it useful.\n\n## Profit ๐Ÿš€\n\nWith this setup, you have a very convenient way to write your tests against real pages, loaded in a real browser, just like the end user experiences. And with the watch mode giving you instant feedback, you no longer have an excuse to not write tests.\n\nThis is the same setup we've been using for developing and testing the [Infinite Table React DataGrid](https://infinite-table.com) and it has been serving us really well.\n\nDataGrids are some of the most complex UI components one can build, so having a reliable tool that allowed us to iterate very quickly was crucial to us. This helped us add new features, while being confident that all of the existing core functionalities like row/column grouping, filtering, sorting, pagination, pivoting still work as expected.\n\nThe setup was a pivotal point in our development process and it's what gives us and our enterprise customers the peace of mind that the product is stable and reliable, both now and in the future.\n\n\n" - }, "/blog/2024/04/22/the-best-testing-strategies-for-frontends": { "filePath": "/blog/2024/04/22/the-best-testing-strategies-for-frontends", "routePath": "/blog/2024/04/22/the-best-testing-strategies-for-frontends", @@ -3331,24 +3531,6 @@ "readingTime": "5 min read", "content": "\nIn our previous article, we focused on documenting [the best testing setup for frontends](https://infinite-table/blog/2024/04/18/the-best-testing-setup-for-frontends-playwright-nextjs), which used Playwright and Next.js. You can check out the repository [here](https://github.com/infinite-table/testing-setup-nextjs-playwright) where you can find the full setup.\n\nWe consider the combination described above, Playwright + NextJS being the best combo around for testing frontends. Ok, you can switch out NextJS with other meta-framework that offers file-system routing, but the idea is the same: every test is made of 2 sibling files, with the same name but different extension.\n\nIn the test files, Playwright is configured to navigate automatically to the page being tested, so no need for adjustments if you move files around. This saves you a lot of time and hustle and makes your tests more robust and focused.\n\nBut in addition to end-to-end testing with Playwright and NextJS, there are other forms of testing out there which are available and can be used to complement your testing strategy. In this article, we'll focus on what we think are the best testing strategies for frontends. So here are a few options:\n\n - E2E testing\n - Component testing \n - Visual regression testing\n - Unit testing\n\nFor each of those options there are plenty of tools you can use, each with its own pros and cons.\n\n## E2E testing\n\nWith the advent of tools like [Puppeteer](https://pptr.dev/) and now [Playwright](https://playwright.dev/), end-to-end testing has become much easier and more reliable. For anyone who's used Selenium in the past, you know what I'm talking about. \nPuppeteer has opened the way in terms of E2E tooling, but Playwright has taken it to the next level and made it easier to await for certain selectors or conditions to be fulfilled (via [locators](https://playwright.dev/docs/locators)), thus making tests more reliable and less flaky.\nAlso, it's a game changer that it introduced a test-runner - this made the integration between the headless browser and the actual test code much smoother.\n\n### Reasons to use E2E\n\nEnd to end testing is actually a real browser, so the closest possible environment to what your app will be using.\n\nNo need to fake the page with JSDOM, no need to only do shallow rendering in React. Just use the platform!\n\n## Component testing\n\nProbably [Enzyme](https://enzymejs.github.io/enzyme/) was the first to popularize component testing in React by doing shallow rendering and expecting some things to be there in the React component tree. Then [React Testing library](https://testing-library.com/) came and took component testing to a whole new level.\n\nThe tools are great for what they're doing, but with the advent of better tooling, we should move on to better ways of testing. With the tools we have now in 2024, there's no more need to use JSDOM and simulate a browser enviroment. It used to be very cumbersome to start a headless browser back in the day, but now with Playwright/Puppeteer, it's a breeze.\n\n## Visual regression testing\n\nAlso in the days before Playwright was around, there was much hype about visual regression testing. It was very very tempting to use it - who wouldn't want to have a tool that automatically checks if the UI has (mistakenly) changed? It might fit a few use-cases, but in general, it's not worth the effort of maintaining all those tests for any little change in the UI. True, you can set thresholds for the differences, but it's still a lot of work to maintain it, especially in highly dynamic frontends and teams.\n\nWith better CSS approaches like [TailwindCSS](https://tailwindcss.com/) and [Vanilla Extract](https://vanilla-extract.style/) (which we're heavily using) it's much easier to maintain the UI and make sure it doesn't change unexpectedly. No more conflicting CSS classes, much less CSS specificity issues and much less CSS code in general.\n\nOne of the troubles in large and tangled CSS codebases is that it's write-only. Well, not write-only per se, but teams are generally afraid to remove a line of CSS cause it might break someone else's code or it might still be used.\n\nWith [Vanilla Extract](https://vanilla-extract.style/) you can be sure that if you remove a CSS class, it's not used anywhere else in the codebase. It's been a game changer in terms of CSS maintainability and productivity for us at [Infinite Table](https://infinite-table.com/).\n\nSo with all those tools to make styling easier, the need for visual regression testing has dropped significantly.\n\n\n## Unit testing\n\nUnit testing will be here to stay - at least if besides your UI, your app has some significant business logic. We're using it in combination with E2E testing to make sure complex use-cases work as expected.\n\nFor example, our [logic for row grouping](https://infinite-table.com/docs/learn/grouping-and-pivoting/grouping-rows) is fully tested with unit tests. We do have E2E tests for it, but with unit tests we can have full coverage of all the nitty-gritty details of the grouping logic. We do the same for pivoting and aggregating. Column sizing and column grouping are also covered with unit tests.\n\nWe think there's always going to be a place for unit testing to ensure robustness and reliability of the app under even the most complex use-cases and user inputs.\n\n## Conclusion\n\nIn our experience, the best testing strategy for modern frontends is a combination of E2E testing (using Playwright+NextJS), and unit testing. Visual regression testing is not worth the effort in our opinion, especially with the advent of better CSS tooling like TailwindCSS and [Vanilla Extract](https://vanilla-extract.style/).\n\nThough we used shallow component testing in the past, we're not going back to it - mocking the DOM and the browser is no longer worth it when you can use a real browser with Playwright.\n\nWe hope this article has been helpful in guiding you towards the best testing strategy for your frontend. If you have any questions or comments, feel free to reach out to us at [admin@infinite-table.com](mailto:admin@infinite-table.com). We're always happy to help! \n" }, - "/blog/2024/03/08/how-to-select-cells-and-use-cell-selection": { - "filePath": "/blog/2024/03/08/how-to-select-cells-and-use-cell-selection", - "routePath": "/blog/2024/03/08/how-to-select-cells-and-use-cell-selection", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2024/03/08/how-to-select-cells-and-use-cell-selection.page.md", - "fileName": "how-to-select-cells-and-use-cell-selection.page.md", - "folderPath": "/blog/2024/03/08/", - "frontmatter": { - "title": "How to use multiple cell selection in the DataGrid", - "author": "admin", - "date": "2024-03-08T00:00:00.000Z", - "authorData": { - "label": "admin" - } - }, - "excerpt": "The article will cover some popular cell-selection scenarios in Infinite React DataGrid.", - "readingTime": "3 min read", - "content": "\nThe article will cover some popular cell-selection scenarios in Infinite React DataGrid.\n\n## Multiple cell selection\n\nBy far, the most common use-case for cell selection is multiple cell selection.\nFor this, you need to configure the prop on the `` component to use `\"multi-cell\"`.\n\n\n\nIn addition, if you want to specify a default value for cell selection, you can use the prop - or the controlled alternative , in which case also make sure you update the value when is called.\n\n\n\n```tsx\n\n```\n\n\n\nWhen multiple cell selection is configured in the React DataGrid, the user can select cells by `CMD`/`CTRL` clicking to add a single cell to the selection or by `SHIFT` clicking to select a range of cells.\n\n## Showing a chart based on selected cells\n\nLet's implement a common use-case for multiple cell selection - showing charts based on the selected cells, for example, a bar chart, with names on the x axis and ages on the y axis.\n\n\n\nIn this example, to retrieve the values from the selected cells, we used the from the [cell selection API](/docs/reference/cell-selection-api).\n\n## Cell selection format\n\nThe prop is an object with the following shape:\n\n- `defaultSelection` - `boolean` - whether or not cells are selected by default.\n- either:\n - `selectedCells`: `[rowId, colId][]` - an array of cells that should be selected (this is combined with `defaultSelection: false`)\n- or\n - `deselectedCells`: `[rowId, colId][]` - an array of cells that should be deselected (this is combined with `defaultSelection: true`)\n\n\n\nWhen `defaultSelection` is `true`, you will only need to specify the `deselectedCells` prop.\n\nAnd when `defaultSelection` is `false`, you will only need to specify the `selectedCells` prop.\n\n\n\nIn this way, you can either specify which cells should be selected or which cells should be deselected - and have a default that matches the most common case.\n\n\n\nThe `selectedCells`/`deselectedCells` are arrays of `[rowId, colId]` tuples. The `rowId` is the `id` of the row (the primary key), and the `colId` is the `id` of the column (the identifier of the column in the prop).\n\n\n\n## Using include-lists and exclude-lists for specifying cell selection\n\nAs already demonstrated in the previous snippet, you can pass a default value for cell selection.\n\nIn addition to listing or excluding specific cells from selection, you can use wildcards:\n\n```tsx title=\"Include-list: selecting all cells in a column\"\nconst defaultCellSelection = {\n defaultSelection: false, // all cells are deselected by default\n selectedCells: [\n // all cells in the stack column\n ['*', 'stack'],\n // also this specific cell\n ['row2', 'firstName'],\n ],\n};\n```\n\n```tsx title=\"Include-list: selecting all cells in a row\"\nconst defaultCellSelection = {\n defaultSelection: false, // all cells are deselected by default\n selectedCells: [\n // all cells in the row\n ['row1', '*'],\n // also this specific cell\n ['row2', 'firstName'],\n ],\n};\n```\n\n```tsx title=\"Exclude-list: selecting everything except a column\"\nconst defaultCellSelection = {\n defaultSelection: true, // all cells are selected by default\n deselectedCells: [['*', 'stack']],\n};\n```\n\n\n\n## Single cell selection\n\nSingle cell selection is not common - what you probably want to use in this case is the prop to emulate single cell selection - but that's basically cell navigation.\n" - }, "/blog/2024/05/27/minimalist-theme-for-react-datagrid": { "filePath": "/blog/2024/05/27/minimalist-theme-for-react-datagrid", "routePath": "/blog/2024/05/27/minimalist-theme-for-react-datagrid", @@ -3383,7 +3565,7 @@ }, "excerpt": "Excel-like editing is a very popular request we had. In this short article, we show you how to configure Excel-like editing in the Infinite React DataGrid.", "readingTime": "3 min read", - "content": "\nExcel-like editing is a very popular request we had. In this short article, we show you how to configure Excel-like editing in the Infinite React DataGrid.\n\n\n\nThis behavior is achieved by using the [Instant Edit keyboard shorcut](/docs/learn/keyboard-navigation/keyboard-shortcuts#instant-edit).\n\n\n## Configuring keyboard shortcuts\n\n```ts {4,12}\nimport {\n DataSource,\n InfiniteTable,\n keyboardShortcuts\n} from '@infinite-table/infinite-react';\n\n function App() {\n return primaryKey=\"id\" data={dataSource}>\n \n columns={columns}\n keyboardShortcuts={[\n keyboardShortcuts.instantEdit\n ]}\n />\n \n}\n```\n\nThe `instantEdit` [keyboard shorcut](/docs/learn/keyboard-navigation/keyboard-shorcuts) is configured (by default) to respond to any key (via the special `*` identifier which matches anything) and will start editing the cell as soon as a key is pressed. This behavior is the same as in Excel, Google Sheets, Numbers or other spreadsheet software.\n\n\n\n\nTo enable editing globally, you can use the boolean prop on the `InfiniteTable` DataGrid component. This will make all columns editable.\n\nOr you can be more specific and choose to make individual columns editable via the column.defaultEditable prop. This overrides the global .\n\n\n\n\n\n\nRead about how you can configure various editors for your columns.\n\n\n\nA picture is worth a thousand words - see a chart for the editing flow.\n\n\n\n\n\n## Finishing an Edit\n\nAn edit is generally finished by user interaction - either the user confirms the edit by pressing the `Enter` key or cancels it by pressing the `Escape` key.\n\nAs soon as the edit is confirmed by the user, `InfiniteTable` needs to decide whether the edit should be accepted or not.\n\nIn order to decide (either synchronously or asynchronously) whether an edit should be accepted or not, you can use the global prop or the column-level column.shouldAcceptEdit alternative.\n\n\n\nWhen neither the global nor the column-level column.shouldAcceptEdit are defined, all edits are accepted by default.\n\n\n\n\n\nOnce an edit is accepted, the callback prop is called, if defined.\n\nWhen an edit is rejected, the callback prop is called instead.\n\nThe accept/reject status of an edit is decided by using the `shouldAcceptEdit` props described above. However an edit can also be cancelled by the user pressing the `Escape` key in the cell editor - to be notified of this, use the callback prop.\n\n\n\nUsing shouldAcceptEdit to decide whether a value is acceptable or not\n\n\n\nIn this example, the `salary` column is configured with a shouldAcceptEdit function property that rejects non-numeric values.\n\n\n\n\n## Persisting an Edit\n\nBy default, accepted edits are persisted to the `DataSource` via the DataSourceAPI.updateData method.\n\nTo change how you persist values (which might include persisting to remote locations), use the function prop on the `InfiniteTable` component.\n\n\n\nThe function prop can return a `Promise` for async persistence. To signal that the persisting failed, reject the promise or resolve it with an `Error` object.\n\nAfter persisting the edit, if all went well, the callback prop is called. If the persisting failed (was rejected), the callback prop is called instead.\n\n" + "content": "\nExcel-like editing is a very popular request we had. In this short article, we show you how to configure Excel-like editing in the Infinite React DataGrid.\n\n\n\nThis behavior is achieved by using the [Instant Edit keyboard shorcut](/docs/learn/keyboard-navigation/keyboard-shortcuts#instant-edit).\n\n\n## Configuring keyboard shortcuts\n\n```ts {4,12}\nimport {\n DataSource,\n InfiniteTable,\n keyboardShortcuts\n} from '@infinite-table/infinite-react';\n\n function App() {\n return primaryKey=\"id\" data={dataSource}>\n \n columns={columns}\n keyboardShortcuts={[\n keyboardShortcuts.instantEdit\n ]}\n />\n \n}\n```\n\nThe `instantEdit` [keyboard shorcut](/docs/learn/keyboard-navigation/keyboard-shortcuts) is configured (by default) to respond to any key (via the special `*` identifier which matches anything) and will start editing the cell as soon as a key is pressed. This behavior is the same as in Excel, Google Sheets, Numbers or other spreadsheet software.\n\n\n\n\nTo enable editing globally, you can use the boolean prop on the `InfiniteTable` DataGrid component. This will make all columns editable.\n\nOr you can be more specific and choose to make individual columns editable via the column.defaultEditable prop. This overrides the global .\n\n\n\n\n\n\nRead about how you can configure various editors for your columns.\n\n\n\nA picture is worth a thousand words - see a chart for the editing flow.\n\n\n\n\n\n## Finishing an Edit\n\nAn edit is generally finished by user interaction - either the user confirms the edit by pressing the `Enter` key or cancels it by pressing the `Escape` key.\n\nAs soon as the edit is confirmed by the user, `InfiniteTable` needs to decide whether the edit should be accepted or not.\n\nIn order to decide (either synchronously or asynchronously) whether an edit should be accepted or not, you can use the global prop or the column-level column.shouldAcceptEdit alternative.\n\n\n\nWhen neither the global nor the column-level column.shouldAcceptEdit are defined, all edits are accepted by default.\n\n\n\n\n\nOnce an edit is accepted, the callback prop is called, if defined.\n\nWhen an edit is rejected, the callback prop is called instead.\n\nThe accept/reject status of an edit is decided by using the `shouldAcceptEdit` props described above. However an edit can also be cancelled by the user pressing the `Escape` key in the cell editor - to be notified of this, use the callback prop.\n\n\n\nUsing shouldAcceptEdit to decide whether a value is acceptable or not\n\n\n\nIn this example, the `salary` column is configured with a shouldAcceptEdit function property that rejects non-numeric values.\n\n\n\n\n## Persisting an Edit\n\nBy default, accepted edits are persisted to the `DataSource` via the DataSourceAPI.updateData method.\n\nTo change how you persist values (which might include persisting to remote locations), use the function prop on the `InfiniteTable` component.\n\n\n\nThe function prop can return a `Promise` for async persistence. To signal that the persisting failed, reject the promise or resolve it with an `Error` object.\n\nAfter persisting the edit, if all went well, the callback prop is called. If the persisting failed (was rejected), the callback prop is called instead.\n\n" }, "/blog/2024/06/05/master-detail-datagrid-with-charts": { "filePath": "/blog/2024/06/05/master-detail-datagrid-with-charts", @@ -3421,24 +3603,6 @@ "readingTime": "3 min read", "content": "\nMany modern apps rely heavily on white-space to make the user interface easy to read and follow. However, there are financial apps or data-heavy apps where you need to display a lot of information in a small space.\nIn this blogpost, we want to show you how to tweak the theming of the Infinite React DataGrid to make it more dense and maximise screen real estate.\n\n## Configuring the spacing in the DataGrid cells\n\nThe CSS variable you want to target is `--infinite-cell-padding` - it's used to set the padding of the cells in the DataGrid. By default, the padding is set to `var(--infinite-space-2) var(--infinite-space-3)`. This means that the padding is set to `4px 8px` for a root font size of `16px`.\n\n```css {2} title=\"Default definition for --infinite-cell-padding\"\n:root {\n --infinite-cell-padding: var(--infinite-space-2) var(--infinite-space-3); /* vertical horizontal */\n\n --infinite-space-2: .25rem; /* 4px - for a root font size of 16px */\n --infinite-space-3: .5rem; /* 8px */\n}\n```\n\nYou can override this variable in your CSS to make the padding smaller. For example, you can set the padding to `2px 4px` by setting the variable like this:\n\n```css {2} title=\"Override the --infinite-cell-padding variable\"\nbody {\n --infinite-cell-padding: 2px 4px;\n}\n```\n\n\n\nIt's important to understand that cell height is not given by the padding, but by the prop.\n\nSo if you want to make the DataGrid more dense, you should also consider setting the prop to a smaller value.\n\n\n\n\n\n## Configuring the spacing in the column headers\n\nFor configuring padding inside column headers, you need to use the ```--infinite-header-cell-padding``` CSS var.\n\n```css {2} title=\"Default definition for --infinite-header-cell-padding\"\n:root {\n --infinite-header-cell-padding: var(--infinite-header-cell-padding-y) var(--infinite-header-cell-padding-x);\n --infinite-header-cell-padding-x: var(--infinite-space-3);\n --infinite-header-cell-padding-y: var(--infinite-space-3);\n}\n```\n\nYou can make the padding smaller for example give it a value of `2px 4px` by setting the variable like this:\n\n```css {2} title=\"Override the --infinite-header-cell-padding variable\"\nbody {\n --infinite-header-cell-padding: 2px 4px;\n}\n```\n\n \n\nThe above demo also uses the prop to make sure that the column headers don't reserve a space for the sort icon when the respective column is not sorted.\n\n\n\nAnother option would be to override the CSS spacing scale that InfiniteTable defines - but that affects more than just the padding of the cells and headers.\n\n```CSS title=\"Default values for the spacing scale\"\n:root {\n --infinite-space-1: .125rem;\n --infinite-space-2: .25rem;\n --infinite-space-3: .5rem;\n --infinite-space-4: 0.75rem;\n --infinite-space-5: 1rem;\n}\n```\n\nYou're encouraged to experiment with these variables to find the right balance for your app.\n\n" }, - "/blog/2024/10/10/new-themes-available": { - "filePath": "/blog/2024/10/10/new-themes-available", - "routePath": "/blog/2024/10/10/new-themes-available", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2024/10/10/new-themes-available.page.md", - "fileName": "new-themes-available.page.md", - "folderPath": "/blog/2024/10/10/", - "frontmatter": { - "title": "New DataGrid themes available - ocean and balsam", - "author": "admin", - "date": "2024-10-10T00:00:00.000Z", - "authorData": { - "label": "admin" - } - }, - "excerpt": "With the release of `` v5.0.0 we've added two new themes: `ocean` and `balsam`.", - "readingTime": "1 min read", - "content": "\nWith the release of `` v5.0.0 we've added two new themes: `ocean` and `balsam`.\n\nNow you have a selection of themes to choose from: `default`, `minimalist`, `ocean` and `balsam`.\n\nTo apply a theme, you have to set the className `\"infinite-theme-name--THEME_NAME\"` to any parent element of the `` component (or even on the component itself).\n\n\n\n\n\n\n\n\nLearn how to theme Infinite Table to match your brand\n\n\n\n" - }, "/blog/2024/10/15/how-do-i-flash-cells": { "filePath": "/blog/2024/10/15/how-do-i-flash-cells", "routePath": "/blog/2024/10/15/how-do-i-flash-cells", @@ -3457,6 +3621,24 @@ "readingTime": "3 min read", "content": "\nFlashing cells is an important feature that has been requested by some of our users - both [in public](https://github.com/infinite-table/infinite-react/issues/250) and private conversations.\n\nIt's also a very useful addition for DataGrids users that work in the financial industry. Version `5.0.0` of `` shipped flashing and in this blogpost we want to show how to use it.\n\n## Configuring a flashing column.\n\nIn order to configure a column to flash its cells when the data changes, you need to specify a custom `ColumnCell` component.\n\n```tsx {14}\n\nimport { FlashingColumnCell } from '@infinite-table/infinite-react';\n\nconst columns: InfiniteTablePropColumns = {\n id: {\n field: 'id',\n },\n firstName: {\n field: 'firstName',\n },\n salary: {\n field: 'salary',\n components: {\n ColumnCell: FlashingColumnCell,\n }\n },\n};\n```\n\n`@infinite-table/infinite-react` exports a `FlashingColumnCell` React component that you can pass to the `components.ColumnCell` prop of any column you want to flash.\n\n\n\n\n\n\nThe default flashing duration is `1000` milliseconds.\n\n\n\n## Customizing the flashing duration\n\nIf you want to customize the flashing duration, you need to pass a different `components.ColumnCell` to the column. You can very easily do this by calling `createFlashingColumnCellComponent` and passing the `flashDuration` option.\n\n```tsx\n\nimport { createFlashingColumnCellComponent } from '@infinite-table/infinite-react';\n\nconst FlashingColumnCell = createFlashingColumnCellComponent({\n flashDuration: 500,\n flashClassName: 'my-flash-class',\n});\n\nconst columns: InfiniteTablePropColumns = {\n salary: {\n field: 'salary',\n components: {\n ColumnCell: FlashingColumnCell,\n }\n }\n}\n```\n\n\n\nWhen calling `createFlashingColumnCellComponent`, besides the `flashDuration` option, you can also pass a `flashClassName`, which is a CSS class name that will be applied to the flashing cell for the duration of the flash.\n\n\n\n## Customizing the flash colors\n\nIf you want to customize the flash colors, you have three CSS variables available: \n\n- `--infinite-flashing-background`: background color to be used when non-numeric cells flash.\n- `--infinite-flashing-up-background`: background color to use for flashing numeric cells, when the value goes up.\n- `--infinite-flashing-down-background`: background color to use for flashing numeric cells, when the value goes down.\n\nThe example below is configured to use the following colors:\n - flash up - yellow\n - flash down - magenta\n - flash non-numeric - blue\n\nAlso, the flashing duration is configured to take 2 seconds.\n\n\n\nBesides clicking the \"start updates\" button, you can also edit the salary value in any cell. When you confirm the edit, the salary value will flash.\n\n\n\n\n\n## Taking it further\n\nInfinite Table implements flashing by passing in a custom `ColumnCell` component. However, you're not limited to using our [default implementation](https://github.com/infinite-table/infinite-react/blob/master/source/src/components/InfiniteTable/components/InfiniteTableRow/FlashingColumnCell.tsx). You can very easily create your own component and apply your own custom logic.\n\nMaybe you want display both the new and the old values in the cell - this can be implemented quite easily. It's up to you to extend the cell rendering to suit your business requirements.\n\nThe current flashing implementation is flashing on any change in a cell, but you might be interested only in some of the changes. You can definitely use to detect when a cell edit is persisted and then decide whether to flash the cell or not. The possibilities are very diverse.\n\nWe're keen to see what you build!\n\n\n\n" }, + "/blog/2024/10/10/new-themes-available": { + "filePath": "/blog/2024/10/10/new-themes-available", + "routePath": "/blog/2024/10/10/new-themes-available", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2024/10/10/new-themes-available.page.md", + "fileName": "new-themes-available.page.md", + "folderPath": "/blog/2024/10/10/", + "frontmatter": { + "title": "New DataGrid themes available - ocean and balsam", + "author": "admin", + "date": "2024-10-10T00:00:00.000Z", + "authorData": { + "label": "admin" + } + }, + "excerpt": "With the release of `` v5.0.0 we've added two new themes: `ocean` and `balsam`.", + "readingTime": "1 min read", + "content": "\nWith the release of `` v5.0.0 we've added two new themes: `ocean` and `balsam`.\n\nNow you have a selection of themes to choose from: `default`, `minimalist`, `ocean` and `balsam`.\n\nTo apply a theme, you have to set the className `\"infinite-theme-name--THEME_NAME\"` to any parent element of the `` component (or even on the component itself).\n\n\n\n\n\n\n\n\nLearn how to theme Infinite Table to match your brand\n\n\n\n" + }, "/blog/2024/10/16/shadcn-ui-theme-available": { "filePath": "/blog/2024/10/16/shadcn-ui-theme-available", "routePath": "/blog/2024/10/16/shadcn-ui-theme-available", @@ -3475,157 +3657,20 @@ "readingTime": "2 min read", "content": "\nWe've all come to know and love โค๏ธ [shadcn/ui](https://ui.shadcn.com/). It comes with a consistent look and feel for all the components in the UI kit, and it's built on top of Tailwind, so people feel at home using it. As React developers, we're thankful for all the hard work happening in the React ecosystem, and we're happy to see more and more UI libraries focusing on providing great developer experience.\n\nAfter recently building [a few other themes](/blog/2024/10/10/new-themes-available), we knew we had to build a shadcn/ui theme for ``.\n\nSo we built one! It's simply called `shadcn`. For it to work, you'll need to make sure the shadcn/ui CSS variables are available, as the `` theme variables will rely on the values of those CSS variables. Other than that, simply import the `` CSS file and you're good to go.\n\n```tsx\nimport '@infinite-table/infinite-react/index.css';\n\n
\n \n \n \n
\n```\n\nYou'll have to include the `infinite-theme-name--shadcn` class name on a parent element of `` (or even on the `` component itself). Additionally, for dark mode, you'll have to use the `dark` class name (on the body element for example) to put the shadcn/ui CSS variables in dark mode, and then `` will pick that up. This means that for this theme, using the `infinite-theme-mode--dark` class name is optional.\n\n\n\n\n\nEnjoy!" }, - "/blog/2023/01/26/filtering-data-with-infinite-table-for-react": { - "filePath": "/blog/2023/01/26/filtering-data-with-infinite-table-for-react", - "routePath": "/blog/2023/01/26/filtering-data-with-infinite-table-for-react", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2023/01/26/filtering-data-with-infinite-table-for-react.page.md", - "fileName": "filtering-data-with-infinite-table-for-react.page.md", - "folderPath": "/blog/2023/01/26/", - "frontmatter": { - "title": "Filtering Data with Infinite Table for React", - "description": "Learn how to filter data both client-side and server-side with Infinite Table for React", - "author": [ - "admin" - ], - "date": "2023-01-26T00:00:00.000Z", - "authorData": { - "label": [ - "admin" - ] - } - }, - "excerpt": "_Today we shipped cutting-edge column filtering functionality, that enables intuitive client-side and server-side filtering_", - "readingTime": "5 min read", - "content": "\n_Today we shipped cutting-edge column filtering functionality, that enables intuitive client-side and server-side filtering_\n\n\n\n1๏ธโƒฃ Narrow down your data with your own filter types and operators\n\n2๏ธโƒฃ Works both client-side and server-side\n\n3๏ธโƒฃ Easy customization of filters and filter editors\n\n4๏ธโƒฃ Optimized for performance\n\n5๏ธโƒฃ Easy to use across multiple columns\n\n\n\nFilters were, by far, the most requested feature to add to Infinite Table after our initial launch.\n\nThe recently-released version `1.1.0` of Infinite Table for React introduces support for column filters, which work both client-side and server-side.\n\nIn order to enable filtering - specify the property on the `` component, as shown below:\n\n```tsx {4} title=\"Enabling_filters_on_the_DataSource\"\n data={/* ... */} primaryKey=\"id\" defaultFilterValue={[]}>\n columns={columns} />\n\n```\n\nThis configures the `` component with an empty array of filters; columns will pick this up and each will display a filter editor in the column header.\n\nOf course, you can define some initial filters:\n\n```tsx title=\"Initial_filters:_filter_by_age_greater_than_40\"\ndefaultFilterValue={[\n {\n field: 'age',\n filter: {\n type: 'number',\n operator: 'gt',\n value: 40\n }\n }\n]}\n```\n\nYou can see how all of this looks like when we put it all together in the examples below.\n\n## Local and Remote Filtering\n\nBecause the `` prop is a function that returns a `Promise` with remote data, the filtering will happen server-side by default.\n\n\n\nWhen using remote filtering, it's your responsability to send the DataSource to the backend (you get this object as a parameter in your function). This value includes for each column the value in the filter editor, the column filter type and the operator in use. In this case, the frontend and the backend need to agree on the operator names and what each one means.\n\n\n\nWhenever filters change, when remote filtering is configured, the function prop is called again, with an object that has the `filterValue` correctly set to the current filters (together with `sortInfo` and other data-related props like `groupBy`, etc).\n\n\nHowever, we can use the to force client-side filtering:\n\n```tsx\n filterMode=\"local\" filterDelay={0} />\n```\n\nWe also specify the filterDelay=0 in order to perform filtering immediately, without debouncing and batching filter changes, for a quicker response โšก๏ธ ๐ŸŽ\n\n\n\n\n\nEven if your data is loaded from a remote source, using `filterMode=\"local\"` will perform all filtering on the client-side - so you don't need to send the `filterValue` to the server in your `data` function.\n\n\n\n## Defining Filter Types and Custom Filter Editors\n\nCurrently there are 2 filter types available in Infinite Table:\n\n- `string`\n- `number`\n\nConceptually, you can think of filter types similar to data types - generally if two columns will have the same data type, they will display the same filter.\n\nEach filter type supports a number of operators and each operator has a name and can define it's own filtering function, which will be used when local filtering is used.\n\n\n\nThe example above, besides showing how to define a custom filter type, also shows how to define a custom filter editor.\n\n\n\nFor defining a custom filter editor to be used in a filter type, we need to write a new React component that uses the hook.\n\n```tsx\nimport { useInfiniteColumnFilterEditor } from '@infinite-table/infinite-react';\n\nexport function BoolFilterEditor() {\n const { value, setValue } = useInfiniteColumnFilterEditor();\n return <>{/* ... */};\n}\n```\n\nThis custom hook allows you to get the current `value` of the filter and also to retrieve the `setValue` function that we need to call when we want to update filtering.\n\nRead more about this [in the docs - how to provide a custom editor](/docs/learn/filtering/providing-a-custom-filter-editor).\n\n\n\n## Customise Filterable Columns and Filter Icons\n\nMaybe you don't want all your columns to be filterable.\n\nFor controlling which columns are filterable and which are not, use the property.\n\nThis overrides the global prop.\n\nWe have also made it easy for you to customize the filter icon that is displayed in the column header.\n\n\n\nYou change the filter icon by using the prop - for full control, it's being called even when the column is not filtered, but you have a `filtered` property on the argument the function is called with.\n\nIn the example above, the `salary` column is configured to render no filter icon, but the `header` is customized to be bolded when the column is filtered.\n\n## Ready for Your Challenge!\n\nWe listened to your requests for advanced filtering.\n\nAnd we believe that we've come up with something that's really powerful and customizable.\n\nNow it's your turn to try it out and show us what you can build with it! ๐Ÿš€\n\nIf you have any questions, feel free to reach out to us on [Twitter](https://twitter.com/infinite_table) or in the [GitHub Discussions](https://github.com/infinite-table/infinite-react/discussions).\n\nMake sure you try out filtering in Infinite Table for yourself ([and consult our extensive docs](/docs/learn/filtering) if required).\n\n\n\nLearn how to use filtering in the browser.\n\n\nFigure out how to use filtering with server-side integration.\n\n\n" - }, - "/blog/2023/02/16/using-menus-in-infinite-table": { - "filePath": "/blog/2023/02/16/using-menus-in-infinite-table", - "routePath": "/blog/2023/02/16/using-menus-in-infinite-table", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2023/02/16/using-menus-in-infinite-table.page.md", - "fileName": "using-menus-in-infinite-table.page.md", - "folderPath": "/blog/2023/02/16/", - "frontmatter": { - "title": "Using Menus in Infinite Table", - "description": "Find out how to use menus in Infinite Table to customise the DataGrid to fit your needs: custom context menus, column menus and more.", - "author": [ - "admin" - ], - "date": "2023-02-16T00:00:00.000Z", - "authorData": { - "label": [ - "admin" - ] - } - }, - "excerpt": "_With version 1.1.0, our DataGrid now includes support for context menus, which are fully configurable so you can create custom menus for any cell in the table._", - "readingTime": "7 min read", - "content": "\n_With version 1.1.0, our DataGrid now includes support for context menus, which are fully configurable so you can create custom menus for any cell in the table._\n\n\n\n1๏ธโƒฃ are fully configurable\n\n2๏ธโƒฃ adjust their position based on the available space\n\n3๏ธโƒฃ can be used to create custom menus for any cell in the table\n\n4๏ธโƒฃ give you full access to the information in the cell or the whole DataGrid\n\n\n\n## How it works\n\nIn Infinite Table you can configure a context menu to be displayed when you right-click a cell by using the prop. Simply specify a function that returns an array of objects, each with `label` and `key` properties. Each object in the array is a row in the context menu - with the `label` being the displayed content and the `key` being a unique identifier for the menu row.\n\n```tsx title=\"Configuring_a_context_menu\"\nconst getCellContextMenuItems = ({ column, data, value }) => {\n if (column.id === 'currency') {\n return [\n {\n label: `Convert ${value}`,\n key: 'currency-convert',\n onAction: (key, item) => {\n alert('clicked ' + item.key);\n },\n },\n ];\n }\n\n if (column.id === 'age') {\n return null;\n }\n\n return [\n {\n label: `Welcome ${value}`,\n key: 'hi',\n },\n ];\n};\n\n data={data} primaryKey=\"id\">\n \n getCellContextMenuItems={getCellContextMenuItems}\n columns={columns}\n />\n;\n```\n\nIn the function prop, you have access to all the information you need, in the first argument of the function:\n\n- `column` - the column on which the user right-clicked\n- `data` - the data object for the row the user right-clicked\n- `value` - the value of the cell on which the context menu has been triggered. This is generally `data[column.field]`, but it can be different if the column has a or \n- `rowInfo` - an object that contains more information about the row, like the `id` (the primary key) and the row index\n- `isGroupRow`\n- and more\n\n\n\nIf is specified and returns `null`, no custom context menu will be displayed, instead the default browser context menu will be shown (in this case, we do not call `preventDefault()` on the event object).\n\nIf returns an empty array, the default browser context menu will not be shown (in this case, we are calling `preventDefault()` on the event object), but also no custom context menu will be displayed, as there are no menu items to show.\n\n\n\n\n\nEach item on the context menu can specify an `onAction` function, which will be called when the user clicks on the menu item. The function will receive the `key` and the `item` as arguments.\n\nIn addition, since the menu items are returned from inside the `getCellContextMenuItems` function, the `onAction` callback has access to the same information as the `getCellContextMenuItems` function.\n\n\n\n\n## Configuring the context menu to have multiple columns\n\nIn the above example, notice each context menu item has only one cell, where the `label` property is displayed.\n\nHowever, Infinite Table for React allows you to create more complex menus, with multiple columns.\n\nIn order to do this, use the same prop, but return an object, with `columns` and `items`\n\n```tsx\nconst getCellContextMenuItems = () => {\n return {\n columns: [{ name: 'label' }, { name: 'lcon' }],\n items: [\n {\n label: 'Welcome',\n icon: '๐Ÿ‘‹',\n key: 'hi',\n },\n {\n label: 'Convert',\n icon: '๐Ÿ”',\n key: 'convert',\n },\n ],\n };\n};\n```\n\n\n\nWhen is used to configure the column menus, each column `name` should have a corresponding property in the objects returned in the `items` array (each object also needs to keep the `key` property).\n\nAlso, we recommend keeping a column named `label`.\n\n\n\n\n\n## Smart positioning\n\nContext menus in Infinite Table are smart enough to adjust their position based on the available space relative to the mouse-click coordinates. The menu will always try to fit inside the grid viewport and to look for the best position that will not cause the menu to be cut off or overflow outside the DataGrid.\n\nThe same algorithm is applied to column menus and also to filter menus (the menu displayed when a filter is shown and the user wants to change the filter operator).\n\n## Context menus outside cells, for the table body\n\nThere are scenarios when you want to display a context menu even when you right-click outside a cell, but inside the table body - for those cases, you can use (in fact, you can use the prop for all context menus).\n\nThe signature of is almost identical with that of , with the exception that cell-related information can be undefined - if the user didn't right-click a cell, but somewhere else in the table body.\n\n\n\nIn the example above, if you click outside a cell, a menu with a single item will be displayed - `Add Item`. If you click on a cell, the menu will be different, and will show information about the clicked cell.\n\n## Column menus\n\nBesides context menus, the DataGrid also supports menus for columns, that allow you to sort/unsort, pin/unpin, clear filtering and toggle column visibility.\n\nJust like context menus, the column menus can also be fully customised, by using the prop.\n\n```tsx title=\"Customizing-column-menu\"\nfunction getColumnMenuItems(items, { column }) {\n if (column.id === 'firstName') {\n // you can adjust the default items for a specific column\n items.splice(0, 0, {\n key: 'firstName',\n label: 'First name menu item',\n onClick: () => {\n console.log('Hey there!');\n },\n });\n }\n\n // or for all columns\n items.push({\n key: 'hello',\n label: 'Hello World',\n onClick: () => {\n alert('Hello World from column ' + column.id);\n },\n });\n return items;\n}\n```\n\n\n\nThe first argument passed to the prop is the array of items that is displayed by default in the column menu.\n\nYou can either modify this array and return it or you can return another totally different array.\n\n\n\n\n\nAs with context menus, positioning column menus is also smart - the menu will always try to fit inside the grid viewport, so it will align to the right or the left of the column, depending on the available space.\n\n## Conclusion\n\nIn this article, we've explained just some of the scenarios that are now possible with Infinite Table for React, by using the new context and column menus.\n\n\n\nLearn more about working with context menus.\n\n\nConfiguring column menus to fit your needs - read more.\n\n\n\nWe hope you'll use these functionalities to build amazing DataGrids for your applications, that are fully tailored to your needs.\n\nIf you find any issues or have any questions, please reach out to us on [Twitter](https://twitter.com/infinite_table) or in the [GitHub Discussions](https://github.com/infinite-table/infinite-react/discussions) or [issues](https://github.com/infinite-table/infinite-react/issues).\n\nWe're happy to help and improve how you work with the component - we want to make it very easy and straight-forward to use it and are looking for ways to simplify our APIs to **achieve more with less**.\n" - }, - "/blog/2023/01/16/infinite-table-is-here": { - "filePath": "/blog/2023/01/16/infinite-table-is-here", - "routePath": "/blog/2023/01/16/infinite-table-is-here", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2023/01/16/infinite-table-is-here.page.md", - "fileName": "infinite-table-is-here.page.md", - "folderPath": "/blog/2023/01/16/", - "frontmatter": { - "title": "๐Ÿ“ฃ Infinite Table is Here ๐ŸŽ‰", - "description": "Infinite Table is ready for prime time. With version 1.0.0 we're releasing a DataGrid that's feature packed and ready to be used in enterprise-grade apps", - "author": [ - "admin" - ], - "date": "2023-01-16T00:00:00.000Z", - "authorData": { - "label": [ - "admin" - ] - } - }, - "excerpt": "_Infinite Table React is ready for prime time._", - "readingTime": "7 min read", - "content": "\n_Infinite Table React is ready for prime time._\n\n_With version 1.0.0 we're releasing a DataGrid that's feature packed and ready to be used in the wild!_\n\n\n\n1๏ธโƒฃ seriously fast\n\n2๏ธโƒฃ no empty or white rows while scrolling\n\n3๏ธโƒฃ packed with features\n\n4๏ธโƒฃ built from the ground up for React\n\n5๏ธโƒฃ clear, concise and easily composable props & API\n\n\n\nWe think you'll love Infinite Table.\n\nThis is the DataGrid we would have loved to use more than 15 years ago when [we started working with tables in the browser](/blog/2022/11/08/why-another-datagrid).\n\nAnd now it's finally here ๐ŸŽ‰.\n\n### Built from the Ground Up with React & TypeScript\n\n#### React all the Way\n\nInfinite Table feels native to React, not as a after-thought, but built with React fully in mind.\n\nIt's declarative all the way and exposes everything as props, both controlled and uncontrolled.\n\nIf you don't like the default behavior of a prop, use the controlled version and implement your own logic and handling - see for example the [following props related to column order](/docs/reference/infinite-table-props#search=columnorder):\n\n- - controlled property for managing order of columns\n- - uncontrolled version of the above\n- - callback prop for notifications and for updating controlled column order\n\n#### Fully Controlled\n\nReact introduced controlled components to the wider community and we've been using them for years.\n\nIt's where the power of React lies - giving the developer the flexibility to fully control (when needed) every input point of an app or component.\n\nAll the props which Infinite Table exposes, have both controlled and uncontrolled versions. This allows you to start using the component very quickly and without much effort, but also with the all-important flexibility to fully control the component when needed, as your app grows and you need more control over the DataGrid.\n\n#### TypeScript & Generic Components\n\nInfinite Table is also built with TypeScript, giving you all the benefits of a great type system.\n\nIn addition, the exposed components are exported as generic components, so you can specify the type of the data you're working with, for improved type safety.\n\n```tsx\nimport { InfiniteTable, DataSource } from '@infinite-table/infinite-react'\n\ntype Person = { id: number, name: string, age: number}\n\nconst data: Person[] = [\n { id: 1, name: 'John', age: 25 },\n //...\n];\nconst columns = {\n id: { field: 'id' },\n name: { field: 'name' },\n}\n\n// ready to render\n data={data} primaryKey=\"id\">\n columns={columns} />\n\n```\n\n### Why Use Infinite Table, cont.\n\n#### Fast - virtualization\n\nInfinite Table is fast by leveraging **virtualization** both **vertically** (for rows) and **horizontally** (for columns).\n\nThis means DOM nodes are created only for the visible cells, thus reducing the number of DOM nodes and associated memory strain and improving performance.\n\n#### No white space while scrolling - clever layout & rendering\n\nIn addition to virtualization, we use clever layout & rendering techniques to avoid white space while scrolling.\n\nWhen you scroll, the table will not show any empty rows or white space - no matter how fast you're scrolling!\n\n\n\nWe think this is one of the features that sets us apart from other components.\n\nWe've spent a lot of time and effort making sure no whitespace is visible while scrolling the table.\n\n\n\n### Batteries Included\n\nWe want you to be productive immediately and stop worrying about the basics. Infinite Table comes with a lot of features out of the box, so you can focus on the important stuff.\n\nIt helps you display huge datasets and get the most out of your data by providing you the right tools to enjoy these features:\n\n- [ sorting](/docs/learn/sorting/overview)\n- [ row grouping](/docs/learn/grouping-and-pivoting/grouping-rows) - both server-side and client-side\n- [ pivoting](/docs/learn/grouping-and-pivoting/pivoting/overview) - both server-side and client-side\n- [ aggregations](/docs/learn/grouping-and-pivoting/grouping-rows#aggregations)\n- [ live pagination](/docs/learn/working-with-data/live-pagination)\n- [ lazy loading](/docs/learn/working-with-data/lazy-loading)\n- [ keyboard navigation](/docs/learn/keyboard-navigation/navigating-cells)\n- [ fixed and flexible columns](/docs/learn/columns/fixed-and-flexible-size)\n- [ column grouping](/docs/learn/column-groups)\n- [ theming](/docs/learn/theming)\n- ... and many others\n\nInfinite Table is built for companies and individuals who want to ship โ€” faster ๐ŸŽ!\n\n### (Almost) No External Dependencies\n\nWe've implemented everything from scratch and only directly depend on 2 packages (we'll probably get rid of them as well in the future) - all our dependecy graph totals a mere 3 packages.\n\n\n\nWe've reduced external dependencies for 2 main reasons:\n\n- avoid security issues with dependencies (or dependencies of dependencies...you know it) - remember left-pad?\n- keep the bundle size small\n\n\n\n### Composable API - with a small surface\n\nWhen building a component of this scale, there are two major opposing forces:\n\n- adding functionality\n- keeping the component (and the API) simple\n\nWe're continually trying to reconcile both with Infinite Table, so we've built everything with composition in mind.\n\n\n\nA practical example of composition is favouring function props instead of boolean flags or objects. Why implement a feature under a boolean flag or a static object when you can expose a functionality via a function prop? The function prop can be used to handle more cases than any boolean flag could ever handle!\n\n\n\nA good example of composability is the prop which controls the columns that are generated for grouping.\n\nIt can be either a column object or a function:\n\n- when it's a column object, it makes the table render a single column for grouping (as if was set to `\"single-column\"`)\n- when it's a function, it behaves like is set to `\"multi-column\"` and it's being called for each of the generated columns.\n\n```tsx title=\"Group_column_as_an_object\"\n\n```\n\nvs\n\n```tsx title=\"Group_column_as_a_function\"\n {\n // this allows you to affect all generated group columns in a single place\n // especially useful when the generated columns are dynamic or generated via a pivot\n return {...}\n }}\n/>\n```\n\nOur experience with other DataGrid components taught us that the more features you add, the more complex your API becomes.\n\nSo we tried to keep the API surface as small as possible, while still offering a rich set of declarative props as building blocks that can be composed to accomplish more complex functionalities.\n\n### Built for the community, available on NPM\n\nWe're thrilled to share Infinite Table with the world.\n\nWe wanted to make it very easy for everyone to [get started](/docs/learn/getting-started) with it, so all you require is just an npm install:\n\n\nnpm i @infinite-table/infinite-react\n\n\nThe component will show a footer with a [Powered by Infinite Table](https://infinite-table.com) link displayed. However, all the functionalities are still available and fully working. So if you keep the link visible, you can use the component for free in any setup!\n\nAlthough you can use Infinite Table for free, we encourage you to [purchase a license](/pricing) - buying a license will remove the footer link. This will help us keep delivering new features and improvements to the component and support you and your team going forward!\n\n\n\nGet started with Infinite Table and learn how to use it in your project.\n\n\n\nGet Infinite Table for your project and team!\n\n\n" - }, - "/blog/2023/07/14/version-2-0-0": { - "filePath": "/blog/2023/07/14/version-2-0-0", - "routePath": "/blog/2023/07/14/version-2-0-0", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2023/07/14/version-2-0-0.page.md", - "fileName": "version-2-0-0.page.md", - "folderPath": "/blog/2023/07/14/", - "frontmatter": { - "title": "Infinite Table DataGrid for React reaches version 2.0.0", - "description": "With version 2.0.0 InfiniteTable DataGrid for React brings lots of fixes and enhancements including support for sorting group columns, better APIs, improved pivoting, smarter column menus and more.", - "author": [ - "admin" - ], - "date": "2023-07-14T00:00:00.000Z", - "authorData": { - "label": [ - "admin" - ] - } - }, - "excerpt": "Version `2.0.0` is a release that allowed us to polish many areas of the component and consolidate its existing features and APIs.", - "readingTime": "4 min read", - "content": "\nVersion `2.0.0` is a release that allowed us to polish many areas of the component and consolidate its existing features and APIs.\n\nWe hope this makes your experience with Infinite Table as your React DataGrid of choice even better.\n\nThough it doesn't add major new features, this version does improve the overall experience of using the component. In this article we're detailing the most important improvements this release brings.\n\n\n\n1๏ธโƒฃ [better support for sorting group columns](#1-better-support-for-sorting-group-columns)\n2๏ธโƒฃ [allows configuring the behavior when multiple sorting is enabled](#2-multi-sort-behavior)\n3๏ธโƒฃ [smarter column menus](#3-smarter-column-menus)\n4๏ธโƒฃ [improved support for boolean pivot columns](#4-improved-support-for-boolean-pivot-columns)\n5๏ธโƒฃ [better and more exhaustive APIs](#5-better-and-more-exhaustive-apis)\n\n\n\n\n\n## 1๏ธโƒฃ Better support for sorting group columns\n\nBefore version `2.0.0`, group columns were sortable, but only if the configured `groupBy` fields were bound to actual columns.\n\nThis release enables you to make group columns sortable even when other columns are not defined. For this to work, you have to specify a sortType as an array, so the column knows how to sort the group values.\n\n```tsx title=\"Configuring sortType for group columns\"\n\n groupColumn={{\n sortType: ['string', 'number'],\n field: 'firstName',\n defaultWidth: 150,\n }}\n groupRenderStrategy=\"single-column\"\n columns={columns}\n columnDefaultWidth={120}\n/>\n```\n\n\n\n## 2๏ธโƒฃ Multi sort behavior\n\nWe have introduced to allow you to configure how the component behaves when multiple sorting is enabled. Two options are available:\n\n- `append` - when this behavior is used, clicking a column header adds that column to the alredy existing sort. If the column is already sorted, the sort direction is reversed. In order to remove a column from the sort, the user needs to click the column header in order to toggle sorting from ascending to descending and then to no sorting.\n\n- `replace` - the default behavior - a user clicking a column header removes any existing sorting and sets that column as sorted. In order to add a new column to the sort, the user needs to hold the `Ctrl/Cmd` key while clicking the column header.\n\nmultiSortBehavior=\"replace\" is the new default behavior, and also a more natural one, so we recommend using it.\n\n\n\n## 3๏ธโƒฃ Smarter column menus\n\nColumn menus are now smarter - in previous versions of Infinite Table, users were able to hide the column that had the menu opened, and the menu would hang in its initial position.\n\nWhen this happens, in version `2.0.0`, the menu realigns itself to other existing columns, thus providing a better user experience.\n\n## 4๏ธโƒฃ Improved support for boolean pivot columns\n\nIt's pretty common to pivot by boolean columns, and this is now fully supported in Infinite Table. Previous to version `2.0.0`, the column headers for boolean pivot columns were not rendered correctly.\n\n\n\n## 5๏ธโƒฃ Better and more exhaustive APIs\n\nWe have improved our APIs, with new methods and fixes. Among other things, we've polished our [Column API](/docs/reference/column-api) to offer you the ability to do more with your columns. Previously there were things that were only possible to do if you had access to the internal state of the component, but now we've moved more things to the API.\n\nFor example, our column sorting code is now centralised, and using gives you the same action as clicking a column header (this was not the case previously).\n\nWe've added quite a few more methods to our APIs, here's some of the most important ones:\n\n- ColumnAPI.\n- ColumnAPI.\n- ColumnAPI.\n- ColumnAPI.\n- ColumnAPI.\n\n## Conclusion\n\nWe've been working on version `2.0.0` for a few months now and we hope you'll enjoy all the little details that make this version a better product, with all the improvements it brings in various areas of the component.\n\nWe'd love to hear your feedback, so please reach out to us via email at admin@infinite-table.com or follow us [@get_infinite](https://twitter.com/get_infinite) to keep up-to-date with news about the product.\n\nThank you ๐Ÿ™Œ\n" - }, - "/blog/2023/10/02/version-3-0-0": { - "filePath": "/blog/2023/10/02/version-3-0-0", - "routePath": "/blog/2023/10/02/version-3-0-0", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2023/10/02/version-3-0-0.page.md", - "fileName": "version-3-0-0.page.md", - "folderPath": "/blog/2023/10/02/", - "frontmatter": { - "title": "Infinite Table React DataGrid version 3.0.0 released", - "description": "InfiniteTable DataGrid for React version 3.0.0 brings many small fixes and enhancements, along with a major new feature: cell selection", - "author": [ - "admin" - ], - "date": "2023-10-02T00:00:00.000Z", - "authorData": { - "label": [ - "admin" - ] - } - }, - "excerpt": "Version `3.0.0` is a release that brings a long awaited feature: cell selection. This allows the user to perform fined-grained cell selection, either via the prop or via the [Cell Selection API](/docs/reference/cell-selection-api).", - "readingTime": "4 min read", - "content": "\nVersion `3.0.0` is a release that brings a long awaited feature: cell selection. This allows the user to perform fined-grained cell selection, either via the prop or via the [Cell Selection API](/docs/reference/cell-selection-api).\n\n\n\n1๏ธโƒฃ [support for single and multiple cell selection](#1-support-for-single-and-multiple-cell-selection)\n2๏ธโƒฃ [cell selection using wildcards](#2-cell-selection-using-wildcards)\n3๏ธโƒฃ [cell selection API](#3-cell-selection-api)\n\n\n\n## 1๏ธโƒฃ Support for single and multiple cell selection\n\nIt's been a [long-requested feature to implement cell selection](https://github.com/infinite-table/infinite-react/issues/120).\n\nWe knew we needed to implement it, but we wanted to do it right while keeping it easy to understand.\n\nIn fact, we prepared some things in advance - namely was there, it just needed to accept a new value: `\"multi-cell\"`.\n\n```tsx title=\"Configuring multi-cell selection\"\n\n selectionMode=\"multi-cell\" // <--- THIS\n primaryKey=\"id\"\n data={[...]}\n/>\n```\n\nThe line above is all you need to do to enable cell selection. This allows the user to `Click` or `Cmd/Ctrl+Click` to select a specific cell and `Shift+Click` to select a range of cells. It's exactly the behavior you'd expect from a spreadsheet application.\n\nTry `Cmd/Ctrl+Click`ing in the DataGrid cells below to see multiple cell selection in action.\n\n\n\n### Using a default selection\n\nIf you want to render the DataGrid with a default selection, you can use the prop.\n\n```tsx\nconst defaultCellSelection = {\n defaultSelection: false,\n selectedCells: [\n [3, 'hobby'],\n [4, 'firstName'],\n [4, 'hobby'],\n [4, 'preferredLanguage'],\n [4, 'salary'],\n ],\n};\n```\n\nThe format for the uncontrolled (and also for the controlled ) is an object with two properties:\n\n- `defaultSelection` - `boolean` - whether or not cells are selected by default.\n- and either\n - `selectedCells` - `[string|number, string][]` - only needed when `defaultSelection` is `false`\n- or\n - `deselectedCells` - `[string|number, string][]` - only needed when `defaultSelection` is `true`\n\nThe value for `selectedCells` and `deselectedCells` should be an array of `[rowId, colId]` tuples.\n\nThe `rowId` is the `id` of the row (the primary key), and the `colId` is the `id` of the column (the identifier of the column in the prop).\n\nThis object shape for the / props allows you full flexibility in specifying the selection. You can specify a single cell, a range of cells, or even a non-contiguous selection. You can default to everything being selected, or everything being deselected and then enumerate your specific exceptions.\n\n\n\n## 2๏ธโƒฃ Cell Selection using wildcards\n\nThe above examples show how to select specific cells, but what if you want to select all cells in a column, or all cells in a row?\n\nWell, that turns out to be straightforward as well. You can use the `*` wildcard to select all cells in a column or all cells in a row.\n\n```tsx title=\"All cells in row with id rowId3 and all cells in hobby column are selected\"\nconst defaultCellSelection = {\n defaultSelection: false,\n selectedCells: [\n ['*', 'hobby'],\n ['rowId3', '*'],\n ],\n}\n\n```\n\n\n\nWildcard selection is really powerful and it allows you to select lots of cells without the need to enumerate them all.\n\nFor example, you can easily select all cells except a few.\n\n### Listening to selection changes\n\nYou can listen to selection changes by using the prop.\n\nIf you're using controlled cell selection, you have to update the prop yourself in response to user interaction - so will be your way of listening to selection changes.\n\n## 3๏ธโƒฃ Cell Selection API\n\nIn addition to managing cell selection declaratively, which we encourage, you can also use the [Cell Selection API](/docs/reference/cell-selection-api) to imperatively update the current selection.\n\nWe offer the following methods:\n\n- - selects a single cell, while allowing you to keep or to clear previous selection\n- - deselects the specified cell\n- - selects a whole column in the DataGrid\n- - deselects the specified column\n- - selects a range of cells\n- - deselects the specified range of cells\n- - selects all cells in the DataGrid\n- - clears selection (deselects all cells in the DataGrid)\n- - checks if the specified cell is selected or not\n\n## Conclusion\n\nWe'd love to hear your feedback - what do you think we've got right and what's missing. Please reach out to us via email at admin@infinite-table.com or follow us [@get_infinite](https://twitter.com/get_infinite) to keep up-to-date with news about the product.\n\nTalk soon ๐Ÿ™Œ\n" - }, - "/blog/2023/10/05/building-a-datagrid-with-the-right-tools": { - "filePath": "/blog/2023/10/05/building-a-datagrid-with-the-right-tools", - "routePath": "/blog/2023/10/05/building-a-datagrid-with-the-right-tools", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2023/10/05/building-a-datagrid-with-the-right-tools.page.md", - "fileName": "building-a-datagrid-with-the-right-tools.page.md", - "folderPath": "/blog/2023/10/05/", - "frontmatter": { - "title": "Building a DataGrid with the right tools", - "author": "admin", - "date": "2023-10-05T00:00:00.000Z", - "authorData": { - "label": "admin" - } - }, - "excerpt": "Building for the browser has historically been very tedious. In the old days you had to resort to all sorts of hacks for getting the right layout - anyone remembers conditional comments targeting IE6-9? ๐Ÿ˜…", - "readingTime": "8 min read", - "content": "\nBuilding for the browser has historically been very tedious. In the old days you had to resort to all sorts of hacks for getting the right layout - anyone remembers conditional comments targeting IE6-9? ๐Ÿ˜…\n\nYeah, we don't miss those days either.\n\nThings have evolved in the last few years, and the amount of goodies JS/CSS/HTML/layout goodies we now take for granted is staggering. New CSS features like flex/grid/custom properties really make a difference. Also browser performance has improved a LOT, and today we can do things in the browser that were unthinkable just a few years ago.\n\nHowever, not everything is easier now than it was back in the browser-war days. Handling all kinds of devices, managing changing dependencies, configuring build tools, choosing the right styling approach, proper E2E testing, keeping a small bundle size, CI pipelines, etc. are all things that can (and will) go wrong if you don't have the right tools.\n\n## TypeScript\n\nIt's obvious today to just go with `TypeScript`, but a few years ago, it was not as obvious. We've been using TypeScript for quite a few years now, and we're very happy with it. We can never imagine going back to plain JS.\n\n## React\n\nBuilding on top of `React` has given us an amazing component model that's very composable and easy to reason about - and the ecosystem is huge.\n\nRead about our journey in the [Why another DataGrid?](/blog/2022/11/08/why-another-datagrid) blog post. Back when React was launching, many of our team members were writing DataGrids - either in vanilla JS or using some libraries (`jQuery` anyone? - we don't miss browser incompatibilities).\n\n## CSS Variables and Vanilla Extract\n\nAs a `DataGrid` Infinite Table is built on top of CSS variables - we're going all in with CSS variables. They have a few gotchas in very advanced cases, but all-in-all they're amazing - and especially for performance.\n\nWe're not short of [CSS variables that we expose - see the full list](/docs/learn/theming/css-variables).\n\nUsing them has been pivotal not only to the ease of theming, but also to the performance of the DataGrid.\nBeing able to change a CSS custom property on a single DOM element and then reuse it across many elements that are children of the first one is a huge performance win. Our DataGrid performance would not be the same without CSS variables.\n\n### Vanilla Extract\n\nThe single tool that has made our life a lot easier working with CSS is [Vanilla Extract](https://vanilla-extract.style/). If you're developing a component library, you should definitely use it! Not so much for simple & static apps - there are other styling solutions that are easier to use, like [tailwindCSS](https://tailwindcss.com/). But for component libraries, **Vanilla Extract is amazing**!\n\nDid we mention it's amazing? ๐Ÿ˜…\nThe fact that you can use TypeScript with it, can use \"Find All References\", see where everything is used is a huge win. You're not writing readonly CSS anymore - because that tends to be the case with most CSS. People are afraid to change it or remove old CSS code, just in case those rules are still being used or referenced somehow. This way, CSS only grows with time, and this is a code smell.\n\nWith Vanilla Extract, you get to forget about that. You know what's being used and what's not.\n\nAlso, hashing class names to avoid collisions is nice - and something now very common in the modern JS ecosystem. It all started with CSS modules, and now it's everywhere, Vanilla Extract included.\n\nOther great features we use extensively are:\n\n- public facing CSS variables - their names are stable\n- private CSS variables - their names are hashed\n- sharing CSS values with the TS codebase is a dream come true.\n- Vanilla Extract recipes - generating and applying CSS classes based on a combination of properties. It's enough that you have 2-3 properties, each with a few values, and managing their combinations can be a pain. Vanilla Extract recipes manage this in a very elegant way.\n\n## End-to-end testing with Playwright and NextJS\n\nRemember the days of Selenium? All those flaky tests, the slow execution, the hard to debug issues? They're gone!\n\n[Playwright](https://playwright.dev/) all the way! 300+ tests and going strong! Yes, you read that right! We have more than 300 tests making sure the all the DataGrid features are working as expected. Sorting, filtering, row grouping, column groups, pivoting, aggregations, lazy loading, live pagination, keyboard navigation, cell and row selection, theming - they're all tested! And we're not talking about unit tests, but end-to-end tests. We're testing the DataGrid in the browser, with real data just like real users would.\n\nPlaywright is an amazing tool, but we're not using it standalone. Paired with a [NextJS](https://nextjs.org/) app, with file-system based routing, we've created files/routes for each functionality. Each NextJS file in turn has a Playwright test file with the same name, but a different extension.\n\nThis has the benefit that it's always very obvious which test is running against which page. The test and the route always have the same file name, just the extension is different. The test source-code doesn't explicitly contain code that navigates to a specific page, all this is done under the hood, using this simple convention.\n\nThis way, we have a very clear separation of concerns, and it's very easy to add new tests. We just create a new file in the `pages` folder, and a new test file sibling to it. Another amazing benefit is that we can start the NextJS app and point our browser to whatever page we want to see or debug and it's there. We can very easily do the actions the test is doing and see if we get the expected results. This is a huge win for debugging.\n\n## A tailored state management\n\nWe've built a very simple yet highly effective state management solution for our DataGrid. It's built to make updating the internal state of the DataGrid as easy as possible - we want a simple API, with clear actions. Our actions map almost 1-to-1 to the DataGrid properties, which makes it very obvious to know who changed what.\n\nWe can't overstate how important it is to have a clear data flow through the DataGrid. This is because the DataGrid is by far the most complex UI component you'll ever use (and we'll ever build). You can't possibly go beyond that - at least not in common business apps, where you have the normal UI controls you can expect, like inputs, buttons, dropdowns, etc. Just the ComboBox can come near the complexity of the DataGrid, but it's still far behind.\n\nIt's important to be able to tame all this complexity - otherwise it can slow down the development process and bring it to a halt, making it difficult to add new features or fix bugs. With our current model, even though the DataGrid grew in complexity and features, we never felt our velocity dropping! We enjoy that!\n\n## No dependencies\n\nWe're very proud of the fact that we have no dependencies in our DataGrid. When you install our package, you only install our package - and nothing else. Nothing that can go wrong due to version conflicts, missing dependencies, npm issues ([remember left-pad](https://www.davidhaney.io/npm-left-pad-have-we-forgotten-how-to-program/)?).\n\nYes, we still depend on packages in our dev process, but we're striving to keep that small as well. It's already complex enough to keep TS, React, NextJS, npm (with workspaces), aliases, esbuild, tsup, playwright all working together in harmony. But we've got through it, and we're very happy with the result. It was worth it!\n\n## Separating concerns\n\nWe've separated our DataGrid into 2 main parts:\n\n- the `` component - handles data loading and processing\n- the `` component - handles the rendering\n\nThis was a brilliant idea! It's new? No! It's not our invention, but we're happy we decided to apply it.\n\nIt adds a better separation between the two big parts of the DataGrid. This also helps tame some of the complexity, while adding clarity to the codebase. It's easier to reason about the code when you know that the `` component is responsible for data loading and processing, while the `` component is ONLY responsible for rendering.\n\n## Conclusion\n\nWe're not sorry for choosing any of the above tools or approaches when building the InfiniteTable DataGrid component.\n\nOur developer velocity is high, and we're able to add new features and fix bugs at a fast pace. We're happy with the result and we're confident that we'll be able to keep this pace in the future.\n\nThe right tools get the right job done! They make a lot easier. Looking back, we only regret we didn't have those tools 5 years ago - but hey, things are moving in the right direction, and we're happy to be part of this journey.\n\nWhat are your tools for developer productivity?\n" - }, - "/blog/2023/12/11/quick-guide-filtering-the-datagrid": { - "filePath": "/blog/2023/12/11/quick-guide-filtering-the-datagrid", - "routePath": "/blog/2023/12/11/quick-guide-filtering-the-datagrid", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2023/12/11/quick-guide-filtering-the-datagrid.page.md", - "fileName": "quick-guide-filtering-the-datagrid.page.md", - "folderPath": "/blog/2023/12/11/", + "/blog/2025/03/20/async-context-menus": { + "filePath": "/blog/2025/03/20/async-context-menus", + "routePath": "/blog/2025/03/20/async-context-menus", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2025/03/20/async-context-menus.page.md", + "fileName": "async-context-menus.page.md", + "folderPath": "/blog/2025/03/20/", "frontmatter": { - "title": "Quick Guide - Filtering the DataGrid", - "author": "admin", - "draft": true, - "date": "2023-12-11T00:00:00.000Z", - "authorData": { - "label": "admin" - } + "title": "Async Context Menus", + "description": "Learn how to use async context menus in Infinite Table.", + "date": "2025-03-20T00:00:00.000Z" }, - "excerpt": "This is the first article in a series of quick guides that will help you get started with the DataGrid, each focusing on a specific feature. In this article, we will learn how to use filtering in the DataGrid.", + "excerpt": "Infinite Table 6.1.0 introduces support for lazy loading context menus. This is useful when you need to load your context menu items conditionally, from the backend, based on the cell's value or other conditions.", "readingTime": "1 min read", - "content": "\nThis is the first article in a series of quick guides that will help you get started with the DataGrid, each focusing on a specific feature. In this article, we will learn how to use filtering in the DataGrid.\n\n### Applying Filters on the DataSource\n\nYou apply filters on the `DataSource` component\n\n```tsx {4-11} title=\"Specifying an initial filter value for the DataSource\"\n\n data={...}\n primaryKey=\"id\"\n defaultFilterValue={[\n field: 'salary',\n filter: {\n operator: 'gt',\n value: 50000,\n type: 'number',\n },\n ]}\n/>\n```\n" + "content": "\nInfinite Table 6.1.0 introduces support for lazy loading context menus. This is useful when you need to load your context menu items conditionally, from the backend, based on the cell's value or other conditions.\n\n## How it works\n\nStarting with version `6.1.0`, the and props can now return a `Promise` that resolves to an array of `MenuItem` objects (or an object with `items` and `columns` properties, if you need to also configure the columns).\n\n\n\n\nRight click any cell in the DataGrid - the context menu will be displayed with a delay of `400ms`.\n\n\n\n\n\n\nThe is called with an object that gives you access to all the info regarding the current right-clicked cell - both the row information and the current column. You can use that to decide whether you want to return a menu immediately or to fetch some data from the server and display the context menu after the server response comes in.\n\n" }, "/blog/2025/05/12/the-first-devtools-for-a-datagrid": { "filePath": "/blog/2025/05/12/the-first-devtools-for-a-datagrid", @@ -3646,21 +3691,6 @@ "readingTime": "3 min read", "content": "\nWe're happy to announce that [Infinite Table DevTools extension](https://chromewebstore.google.com/detail/infinite-table-devtools-e/jpipjljbfffijmgiecljadbogfegejfa) is now live!\n\nInfinite Table is the first DataGrid with a Chrome DevTools extension. Starting with version `7.0.0` of Infinite, you can specify the `debugId` property on the `` instance and it will be picked up by the devtools.\n\n\n\nTo see the extension on a live demo, head to the [chrome webstore](https://chromewebstore.google.com/detail/infinite-table-devtools-e/jpipjljbfffijmgiecljadbogfegejfa) to download the extension.\n\nThen visit [our live demo page](/full-demo) and open your browser devtools - you should see the \"Infinite Table\" devtool tab. Click it and enjoy interacting with the DataGrid!\n\n\n\n```tsx {16}\nconst columns = {\n name: {\n field: 'firstName',\n },\n lastName: {\n field: 'lastName',\n },\n age: {\n field: 'age',\n },\n}\n\nconst App = () => {\n return \n \n \n}\n```\n\n\n\nIf you have multiple instances, each with a unique `debugId` property, they will all show up\n\n\n\"Infinite\n\n\n## Current features\n\nThe Devtools extension was launched with an initial feature-set, which will expand as we grow and as we get user feedbak - so be sure to tell us what you'd like to see in the devtools.\n\nCurrently, it offers the ability to do the following:\n - see the list of all columns and adjust which are visible\n - see timings of the following data operations: sorting, filtering, group/pivot/tree.\n This always show how much the last operation of that type took.\n - interact with the grouping and sorting information in the `` - and revert it to user-values at any time\n - see and clear the logs\n - see various warning messages and performance-related issues.\n\n## Planned features\n\nAs we already mentioned, we're planning to expand the devtools, as we're just getting a taste of what's possible. It took us some time to figure our our best workflow in developing the devtools, and we're now confident we can iterate much faster.\n\nHaving said this, we're looking for feedback from you on what insights you'd like to see in the InfiniteTable DataGrid via the devtools. We have our own list of things we want to work on, but we plan to incorporate user-feedback asap. So here's our wishlist for the devtools:\n\n - ability to see more timings on various operations - including a chart with historical values during the lifetime of a DataGrid instance - something similar to how React DevTools shows render operations and their durations.\n - add the ability to filter logs via channel\n - show more performance tips&tricks that can make your DataGrid faster\n - allow you to interact with many props of the DataGrid - row and cell selection, keyboard navigation, filters, column state, sorting, pivoting, pivot result columns, aggregations, column groups, lazy loading, theming and more.\n - give you a full state of the DataGrid, and the ability to apply and restore it at any time.\n - show you the details of your license key and remind you if it's close to the expiration date.\n\n\n## Your turn\n\nIt's your turn to give us feedback on the Infinite Table DevTools Extension!\n\nLet us know what you think and how you'd like to use it in order to enhance your interaction with the DataGrid. " }, - "/blog/2025/03/20/async-context-menus": { - "filePath": "/blog/2025/03/20/async-context-menus", - "routePath": "/blog/2025/03/20/async-context-menus", - "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2025/03/20/async-context-menus.page.md", - "fileName": "async-context-menus.page.md", - "folderPath": "/blog/2025/03/20/", - "frontmatter": { - "title": "Async Context Menus", - "description": "Learn how to use async context menus in Infinite Table.", - "date": "2025-03-20T00:00:00.000Z" - }, - "excerpt": "Infinite Table 6.1.0 introduces support for lazy loading context menus. This is useful when you need to load your context menu items conditionally, from the backend, based on the cell's value or other conditions.", - "readingTime": "1 min read", - "content": "\nInfinite Table 6.1.0 introduces support for lazy loading context menus. This is useful when you need to load your context menu items conditionally, from the backend, based on the cell's value or other conditions.\n\n## How it works\n\nStarting with version `6.1.0`, the and props can now return a `Promise` that resolves to an array of `MenuItem` objects (or an object with `items` and `columns` properties, if you need to also configure the columns).\n\n\n\n\nRight click any cell in the DataGrid - the context menu will be displayed with a delay of `400ms`.\n\n\n\n\n\n\nThe is called with an object that gives you access to all the info regarding the current right-clicked cell - both the row information and the current column. You can use that to decide whether you want to return a menu immediately or to fetch some data from the server and display the context menu after the server response comes in.\n\n" - }, "/blog/2025/08/16/grouping-toolbar-now-available-in-the-datagrid": { "filePath": "/blog/2025/08/16/grouping-toolbar-now-available-in-the-datagrid", "routePath": "/blog/2025/08/16/grouping-toolbar-now-available-in-the-datagrid", @@ -3680,6 +3710,36 @@ "readingTime": "3 min read", "content": "\n\nWith version `7.2.0`, we added another component to make your interaction with the DataGrid easier - namely the `GroupingToolbar`.\n\nThis toolbar allows users to interact with row grouping very easily, via drag and drop. Drag column headers on the GroupingToolbar component and off you go, grouping is updated. Additionally, you can drag items on the GroupingToolbar in order to change the order of the row grouping.\n\n```tsx {3} title=\"Base structure for using the grouping toolbar\"\n\n \n \n \n \n \n\n```\n\nSimply reference the component via `InfiniteTable.GroupingToolbar` and nest it under ``.\n\n\n\nIn the above and below examples, for simplicity, we're not showing the whole configuration of the `` and `` components - for full code examples, see further below.\n\n\n\nThe good part is that you can very easily add additional elements to your structure and have the grouping toolbar displayed on the side, vertically.\n\n```tsx {8} title=\"Example structure for vertical grouping toolbar\"\n\n \n
\n
\n \n \n
\n \n
\n
\n
\n```\n\n\n\n\n\nIn the example above, try dragging the header of the `hobby` column onto the GroupingToolbar to add grouping by `hobby`.\n\n\n\n\n## Horizontal and vertical layout\n\nAs shown above, you can use the `GroupingToolbar` both horizontally and vertically. This is configured via the `orientation` prop - either `\"horizontal\"` (the default) or `\"vertical\"`.\n\nMake sure you configure this to match your desired layout.\n\n\n\n## Customizing and Extending the GroupingToolbar\n\nWhen building this, we were sure you will want to customize almost everything about the toolbar. So we prepared a simple way to do this, via the `components` prop of the `GroupingToolbar`.\n\nThe following components are available:\n - `Placeholder` - controls the placeholder that's visible when there are no row groups available.\n - `ToolbarItem` - used to replace the toolbar items - corresponding to the row groups.\n - `Host` - the component itself - useful to override when you want to add some other React elements before or after the toolbar items.\n\nIn the example below, we demo how you can display a custom placeholder for the GroupingToolbar.\n\n\n\nWith all these ways to hook into the component, there are no limits to the styling and structure of your layout.\n\nGive it a try and let us know (via github issues or [twitter](https://x.com/get_infinite)) if there's anything you'd like to see improved or have questions about!\n\n## Summary\n\nThe new `GroupingToolbar` component brings an intuitive drag-and-drop interface to row grouping in `InfiniteTable`. Whether you prefer horizontal or vertical layouts, the toolbar provides a seamless way to manage grouping while maintaining the flexibility to customize its appearance and behavior.\n\nWe're excited to see how you'll use this new feature in your applications. Happy coding!\n\n" }, + "/blog/2025/10/09/customizing-your-datagrid-component-with-tailwind": { + "filePath": "/blog/2025/10/09/customizing-your-datagrid-component-with-tailwind", + "routePath": "/blog/2025/10/09/customizing-your-datagrid-component-with-tailwind", + "fileAbsolutePath": "/Users/radubrehar/code/infinite-table/www/content/blog/2025/10/09/customizing-your-datagrid-component-with-tailwind.page.md", + "fileName": "customizing-your-datagrid-component-with-tailwind.page.md", + "folderPath": "/blog/2025/10/09/", + "frontmatter": { + "title": "Customizing your DataGrid component with Tailwind CSS", + "description": "Find out how to customize your DataGrid component to fit your app needs, using Tailwind CSS", + "date": "2025-10-09T00:00:00.000Z", + "author": "radu", + "draft": true, + "tags": [ + "theming", + "customizing" + ], + "thumbnail": "/gen/assets/customizing-your-datagrid-component.png", + "authorData": { + "label": "radu" + }, + "thumbnailDimensions": { + "width": 1536, + "height": 1024, + "type": "png" + } + }, + "excerpt": "We haven't spoken about this very much, but Infinite Table does offer you very powerful ways to customize the structure of your component. After all, we're a React-first DataGrid, so the component and composition patterns it offers should feel at home in a React app.", + "readingTime": "2 min read", + "content": "\nWe haven't spoken about this very much, but Infinite Table does offer you very powerful ways to customize the structure of your component. After all, we're a React-first DataGrid, so the component and composition patterns it offers should feel at home in a React app.\n\n\n```tsx title=\"Default structure of InfiniteTable\"\n\n \n\n```\n\n## Customizing the nesting of the InfiniteTable component\n\nHowever, be aware that you the `` component doesn't have to be a direct child of the `` component. The `` component doesn't actually render anything, but its job is to load, process and prepare the data in a way that `` understands and can display. And actually you can use the DataSource context to gain access to the data yourself.\n\n```tsx {4} title=\"InfiniteTable can be nested anywhere inside the component\"\n\n

Your DataGrid

\n \n \n \n
\n```\n\n\n\n\nInside the `` component you can use the DataSource-provided context via the hook that our component exposes.\n\n\n\n## Choosing what to render\n\nBesides the flexibility of nesting your DataGrid component anywhere in your app, we also offer you the ability to choose what parts of the DataGrid you want to render and where.\n\nLet's suppose you want to show the header after the body of the DataGrid or choose to insert something in between. That should be easy, right? **It is with Infinite!** - but try to do that with the other commercial DataGrids out there!\n\n```tsx live tailwind file=\"./customizing-structure.page.tsx\"\n\n```\n\nAs demoed above, the good part is that you can very easily add additional elements to your structure and have the grouping toolbar displayed on the side, vertically.\n\n```tsx {8} title=\"Example structure for vertical grouping toolbar\"\n\n \n
\n
\n \n \n
\n \n
\n
\n
\n```\n\n\n\nIn the example above, try dragging the header of the `age` column onto the `GroupingToolbar` to add grouping by `age`.\n\n\n\n\n" + }, "/docs/learn/grouping-and-pivoting/pivoting/customizing-pivot-columns": { "filePath": "/docs/learn/grouping-and-pivoting/pivoting/customizing-pivot-columns", "routePath": "/docs/learn/grouping-and-pivoting/pivoting/customizing-pivot-columns", diff --git a/www/src/.gen/routes.d.ts b/www/src/.gen/routes.d.ts index 32bd27cad..9b787dbbf 100644 --- a/www/src/.gen/routes.d.ts +++ b/www/src/.gen/routes.d.ts @@ -1 +1 @@ -export type GeneratedRoute = "/404" | "/community" | "/eula" | "/privacy" | "/blog/ideas" | "/docs/devtools" | "/docs/" | "/comparison/" | "/docs/reference/error-codes" | "/docs/reference/" | "/docs/reference/infinite-table-props" | "/docs/releases/" | "/docs/releases/v1" | "/docs/learn/columns/cell-and-column-styling" | "/docs/learn/columns/column-grouping" | "/docs/learn/columns/column-headers" | "/docs/learn/columns/column-menus" | "/docs/learn/columns/column-order" | "/docs/learn/columns/column-rendering" | "/docs/learn/columns/column-sorting" | "/docs/learn/columns/column-types" | "/docs/learn/columns/fixed-and-flexible-size" | "/docs/learn/columns/" | "/docs/learn/common-issues/" | "/docs/learn/context-menus/using-context-menus" | "/docs/learn/editing/column-editors" | "/docs/learn/editing/custom-editor" | "/docs/learn/editing/excel-like-editing" | "/docs/learn/editing/inline-edit-flow" | "/docs/learn/editing/inline-editing" | "/docs/learn/editing/overview" | "/docs/learn/examples/change-datasource" | "/docs/learn/examples/dynamic-pivoting-example" | "/docs/learn/examples/live-updates-example" | "/docs/learn/examples/performance-many-rows-and-columns" | "/docs/learn/examples/using-sparklines" | "/docs/learn/filtering/extending-existing-filters" | "/docs/learn/filtering/filtering-client-side" | "/docs/learn/filtering/filtering-server-side" | "/docs/learn/filtering/" | "/docs/learn/filtering/providing-a-custom-filter-editor" | "/docs/learn/getting-started/" | "/docs/learn/getting-started/licensing" | "/docs/learn/getting-started/test" | "/docs/learn/getting-started/typescript-types" | "/docs/learn/grouping-and-pivoting/group-aggregations" | "/docs/learn/grouping-and-pivoting/grouping-rows" | "/docs/learn/grouping-and-pivoting/" | "/docs/learn/keyboard-navigation/keyboard-shortcuts" | "/docs/learn/keyboard-navigation/navigating-cells" | "/docs/learn/keyboard-navigation/navigating-rows" | "/docs/learn/rows/disabled-rows" | "/docs/learn/rows/styling-rows" | "/docs/learn/rows/using-row-info" | "/docs/learn/master-detail/caching-detail-datagrid" | "/docs/learn/master-detail/collapsing-and-expanding-rows" | "/docs/learn/master-detail/custom-row-detail-content" | "/docs/learn/master-detail/overview" | "/docs/learn/selection/cell-selection" | "/docs/learn/selection/row-selection" | "/docs/learn/sorting/multiple-sorting" | "/docs/learn/sorting/overview" | "/docs/learn/sorting/single-sorting" | "/docs/learn/theming/css-variables" | "/docs/learn/theming/" | "/docs/learn/tree-grid/overview" | "/docs/learn/tree-grid/tree-column" | "/docs/learn/tree-grid/tree-expand-and-collapse-state" | "/docs/learn/tree-grid/tree-icon-rendering" | "/docs/learn/tree-grid/tree-selection" | "/docs/learn/working-with-data/handling-dates" | "/docs/learn/working-with-data/" | "/docs/learn/working-with-data/lazy-loading" | "/docs/learn/working-with-data/live-pagination" | "/docs/learn/working-with-data/updating-data-in-realtime" | "/docs/reference/api/" | "/docs/reference/cell-selection-api/" | "/docs/reference/column-api/" | "/docs/reference/datasource-api/" | "/docs/reference/datasource-props/" | "/docs/reference/hooks/" | "/docs/reference/row-detail-api/" | "/docs/reference/keyboard-navigation-api/" | "/docs/reference/row-selection-api/" | "/docs/reference/selection-api/" | "/docs/reference/tree-api/" | "/docs/reference/type-definitions/" | "/blog/2021/12/10/infinite-launch" | "/blog/2022/06/15/infinite-launch-beta" | "/blog/2022/06/24/navigating-your-datagrid" | "/blog/2022/08/01/infinite-table-monthly-update-july-2022" | "/blog/2022/11/01/infinite-table-monthly-update-october-2022" | "/blog/2022/09/01/infinite-table-monthly-update-august-2022" | "/blog/2022/11/08/why-another-datagrid" | "/blog/2024/01/23/how-to-customise-datagrid-loading-state" | "/blog/2024/02/02/how-to-configure-default-sorting" | "/blog/2024/02/26/master-detail-now-available-in-react-datagrid" | "/blog/2024/03/06/setting-up-master-detail-datagrid" | "/blog/2024/04/18/the-best-testing-setup-for-frontends-playwright-nextjs" | "/blog/2024/04/22/the-best-testing-strategies-for-frontends" | "/blog/2024/03/08/how-to-select-cells-and-use-cell-selection" | "/blog/2024/05/27/minimalist-theme-for-react-datagrid" | "/blog/2024/06/13/how-to-use-excel-like-editing-in-datagrid" | "/blog/2024/06/05/master-detail-datagrid-with-charts" | "/blog/2024/06/18/how-to-configure-datagrid-to-maximise-screen-real-estate" | "/blog/2024/10/10/new-themes-available" | "/blog/2024/10/15/how-do-i-flash-cells" | "/blog/2024/10/16/shadcn-ui-theme-available" | "/blog/2023/01/26/filtering-data-with-infinite-table-for-react" | "/blog/2023/02/16/using-menus-in-infinite-table" | "/blog/2023/01/16/infinite-table-is-here" | "/blog/2023/07/14/version-2-0-0" | "/blog/2023/10/02/version-3-0-0" | "/blog/2023/10/05/building-a-datagrid-with-the-right-tools" | "/blog/2023/12/11/quick-guide-filtering-the-datagrid" | "/blog/2025/05/12/the-first-devtools-for-a-datagrid" | "/blog/2025/03/20/async-context-menus" | "/blog/2025/08/16/grouping-toolbar-now-available-in-the-datagrid" | "/docs/learn/grouping-and-pivoting/pivoting/customizing-pivot-columns" | "/docs/learn/grouping-and-pivoting/pivoting/overview" \ No newline at end of file +export type GeneratedRoute = "/404" | "/community" | "/eula" | "/privacy" | "/blog/ideas" | "/comparison/" | "/docs/devtools" | "/docs/" | "/docs/reference/error-codes" | "/docs/reference/" | "/docs/reference/infinite-table-props" | "/docs/releases/" | "/docs/releases/v1" | "/docs/learn/common-issues/" | "/docs/learn/columns/cell-and-column-styling" | "/docs/learn/columns/column-grouping" | "/docs/learn/columns/column-headers" | "/docs/learn/columns/column-menus" | "/docs/learn/columns/column-order" | "/docs/learn/columns/column-rendering" | "/docs/learn/columns/column-sorting" | "/docs/learn/columns/column-types" | "/docs/learn/columns/fixed-and-flexible-size" | "/docs/learn/columns/" | "/docs/learn/context-menus/using-context-menus" | "/docs/learn/editing/column-editors" | "/docs/learn/editing/custom-editor" | "/docs/learn/editing/excel-like-editing" | "/docs/learn/editing/inline-edit-flow" | "/docs/learn/editing/inline-editing" | "/docs/learn/editing/overview" | "/docs/learn/examples/change-datasource" | "/docs/learn/examples/dynamic-pivoting-example" | "/docs/learn/examples/live-updates-example" | "/docs/learn/examples/performance-many-rows-and-columns" | "/docs/learn/examples/using-sparklines" | "/docs/learn/filtering/extending-existing-filters" | "/docs/learn/filtering/filtering-client-side" | "/docs/learn/filtering/filtering-server-side" | "/docs/learn/filtering/" | "/docs/learn/filtering/providing-a-custom-filter-editor" | "/docs/learn/getting-started/" | "/docs/learn/getting-started/licensing" | "/docs/learn/getting-started/test" | "/docs/learn/getting-started/typescript-types" | "/docs/learn/keyboard-navigation/keyboard-shortcuts" | "/docs/learn/keyboard-navigation/navigating-cells" | "/docs/learn/keyboard-navigation/navigating-rows" | "/docs/learn/grouping-and-pivoting/group-aggregations" | "/docs/learn/grouping-and-pivoting/grouping-rows" | "/docs/learn/grouping-and-pivoting/" | "/docs/learn/master-detail/caching-detail-datagrid" | "/docs/learn/master-detail/collapsing-and-expanding-rows" | "/docs/learn/master-detail/custom-row-detail-content" | "/docs/learn/master-detail/overview" | "/docs/learn/rows/disabled-rows" | "/docs/learn/rows/styling-rows" | "/docs/learn/rows/using-row-info" | "/docs/learn/selection/cell-selection" | "/docs/learn/selection/row-selection" | "/docs/learn/theming/css-variables" | "/docs/learn/theming/" | "/docs/learn/sorting/multiple-sorting" | "/docs/learn/sorting/overview" | "/docs/learn/sorting/single-sorting" | "/docs/learn/tree-grid/overview" | "/docs/learn/tree-grid/tree-column" | "/docs/learn/tree-grid/tree-expand-and-collapse-state" | "/docs/learn/tree-grid/tree-icon-rendering" | "/docs/learn/tree-grid/tree-selection" | "/docs/learn/working-with-data/handling-dates" | "/docs/learn/working-with-data/" | "/docs/learn/working-with-data/lazy-loading" | "/docs/learn/working-with-data/live-pagination" | "/docs/learn/working-with-data/updating-data-in-realtime" | "/docs/reference/api/" | "/docs/reference/cell-selection-api/" | "/docs/reference/column-api/" | "/docs/reference/datasource-api/" | "/docs/reference/keyboard-navigation-api/" | "/docs/reference/row-detail-api/" | "/docs/reference/hooks/" | "/docs/reference/datasource-props/" | "/docs/reference/row-selection-api/" | "/docs/reference/selection-api/" | "/docs/reference/tree-api/" | "/blog/2021/12/10/infinite-launch" | "/docs/reference/type-definitions/" | "/blog/2022/06/15/infinite-launch-beta" | "/blog/2022/06/24/navigating-your-datagrid" | "/blog/2022/08/01/infinite-table-monthly-update-july-2022" | "/blog/2022/09/01/infinite-table-monthly-update-august-2022" | "/blog/2022/11/01/infinite-table-monthly-update-october-2022" | "/blog/2022/11/08/why-another-datagrid" | "/blog/2023/01/16/infinite-table-is-here" | "/blog/2023/01/26/filtering-data-with-infinite-table-for-react" | "/blog/2023/10/02/version-3-0-0" | "/blog/2023/10/05/building-a-datagrid-with-the-right-tools" | "/blog/2023/07/14/version-2-0-0" | "/blog/2023/02/16/using-menus-in-infinite-table" | "/blog/2024/02/02/how-to-configure-default-sorting" | "/blog/2023/12/11/quick-guide-filtering-the-datagrid" | "/blog/2024/01/23/how-to-customise-datagrid-loading-state" | "/blog/2024/02/26/master-detail-now-available-in-react-datagrid" | "/blog/2024/03/08/how-to-select-cells-and-use-cell-selection" | "/blog/2024/04/18/the-best-testing-setup-for-frontends-playwright-nextjs" | "/blog/2024/03/06/setting-up-master-detail-datagrid" | "/blog/2024/04/22/the-best-testing-strategies-for-frontends" | "/blog/2024/05/27/minimalist-theme-for-react-datagrid" | "/blog/2024/06/13/how-to-use-excel-like-editing-in-datagrid" | "/blog/2024/06/05/master-detail-datagrid-with-charts" | "/blog/2024/06/18/how-to-configure-datagrid-to-maximise-screen-real-estate" | "/blog/2024/10/15/how-do-i-flash-cells" | "/blog/2024/10/10/new-themes-available" | "/blog/2024/10/16/shadcn-ui-theme-available" | "/blog/2025/03/20/async-context-menus" | "/blog/2025/05/12/the-first-devtools-for-a-datagrid" | "/blog/2025/08/16/grouping-toolbar-now-available-in-the-datagrid" | "/blog/2025/10/09/customizing-your-datagrid-component-with-tailwind" | "/docs/learn/grouping-and-pivoting/pivoting/customizing-pivot-columns" | "/docs/learn/grouping-and-pivoting/pivoting/overview" \ No newline at end of file diff --git a/www/src/app/500.tsx b/www/src/app/500.tsx index 01604936c..9dc4a7acf 100644 --- a/www/src/app/500.tsx +++ b/www/src/app/500.tsx @@ -9,7 +9,7 @@ export default function ServerError() { return ( <>

500 - Server Error

-

Something went wrong on the server.

+

Something went wrong on the server!!!

); } diff --git a/www/src/app/blog/RecentPosts.tsx b/www/src/app/blog/RecentPosts.tsx index 62c9d4fce..7c09ec40a 100644 --- a/www/src/app/blog/RecentPosts.tsx +++ b/www/src/app/blog/RecentPosts.tsx @@ -41,8 +41,20 @@ export default function RecentPosts({ posts }: { posts: BlogPost[] }) { return (

- {post.title} + + {post.title} + {post.thumbnail && ( +
+ {post.title} +
+ )} +

+
diff --git a/www/src/app/blog/[...blogpost]/BlogPost.tsx b/www/src/app/blog/[...blogpost]/BlogPost.tsx index c47a0b988..5bea0f48c 100644 --- a/www/src/app/blog/[...blogpost]/BlogPost.tsx +++ b/www/src/app/blog/[...blogpost]/BlogPost.tsx @@ -9,6 +9,7 @@ import { useTwitter } from '@www/components/Layout/useTwitter'; import type { TocHeading } from '@www/utils/getMarkdownHeadings'; import { Toc } from '@www/components/Layout/Toc'; +import { type BlogPost } from '../sortedPosts'; export function BlogPost({ post, @@ -19,15 +20,7 @@ export function BlogPost({ }: { headings: TocHeading[]; children: React.ReactNode; - post: { - body: { - code: string; - }; - title: string; - url: string; - date: string; - author: string; - }; + post: BlogPost; nextRoute?: DocsPageRoute; prevRoute?: DocsPageRoute; }) { @@ -72,6 +65,16 @@ export function BlogPost({

+ {post.thumbnail && ( +
+ {post.title +
+ )} + {children}
@@ -79,7 +82,7 @@ export function BlogPost({

500 - Server Error

-

Something went wrong on the server.

+

Something went wrong on the server...

); } diff --git a/www/src/components/CodeSnippet/index.tsx b/www/src/components/CodeSnippet/index.tsx index 6fc2723f7..1695a3b13 100644 --- a/www/src/components/CodeSnippet/index.tsx +++ b/www/src/components/CodeSnippet/index.tsx @@ -9,6 +9,7 @@ export type CodeSnippetProps = { size?: 'default' | 'md' | 'lg'; title?: string; description?: React.ReactNode | string; + tailwind?: boolean; code?: string; file?: string; lang?: string; @@ -55,6 +56,7 @@ export function CodeSnippet(props: CodeSnippetProps) { title={props.title} description={props.description || descriptions} files={files} + tailwind={props.tailwind} deps={props.importedPackages} > ); diff --git a/www/src/components/Sandpack/Preview.tsx b/www/src/components/Sandpack/Preview.tsx index 381ad73be..2a6657805 100644 --- a/www/src/components/Sandpack/Preview.tsx +++ b/www/src/components/Sandpack/Preview.tsx @@ -1,11 +1,11 @@ /* eslint-disable react-hooks/exhaustive-deps */ -import { useSandpack, LoadingOverlay } from "@codesandbox/sandpack-react"; -import cn from "classnames"; -import * as React from "react"; -import { CSSProperties } from "react"; +import { useSandpack, LoadingOverlay } from '@codesandbox/sandpack-react'; +import cn from 'classnames'; +import * as React from 'react'; +import { CSSProperties } from 'react'; -import { Error } from "./Error"; -import { computeViewportSize, generateRandomId } from "./utils"; +import { Error } from './Error'; +import { computeViewportSize, generateRandomId } from './utils'; type CustomPreviewProps = { className?: string; @@ -49,7 +49,7 @@ export function Preview({ if ( rawError && - rawError.message === "_csbRefreshUtils.prelude is not a function" + rawError.message === '_csbRefreshUtils.prelude is not a function' ) { // Work around a noisy internal error. rawError = null; @@ -72,13 +72,13 @@ export function Preview({ // (window as any).iframeElement = iframeElement; const unsub = listen((message: any) => { - if (message.type === "resize") { + if (message.type === 'resize') { setComputedAutoHeight(message.height); - } else if (message.type === "start") { + } else if (message.type === 'start') { if (message.firstLoad) { setIsReady(false); } - } else if (message.type === "test") { + } else if (message.type === 'test') { // Does it make sense that we're listening to "test" event? // Not really. Does it cause less flicker than "done"? Yes. setIsReady(true); @@ -91,7 +91,7 @@ export function Preview({ }; }, []); - const viewportStyle = computeViewportSize("auto", "portrait"); + const viewportStyle = computeViewportSize('auto', 'portrait'); const overrideStyle = error ? { // Don't collapse errors @@ -107,7 +107,7 @@ export function Preview({ // parent and making them scrollable is confusing. let overflow; if (!isExpanded && !error && isReady) { - overflow = "auto"; + overflow = 'auto'; } // WARNING: @@ -136,23 +136,23 @@ export function Preview({ style.maxHeight = style.height; } return ( -
+