@jielga/tmdatagrid 2.0.0-beta.9 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/README.md +5 -212
  2. package/dist/index.d.ts +1281 -768
  3. package/dist/index.js +4607 -3250
  4. package/dist/index.js.map +1 -1
  5. package/dist/styles.css +1 -1
  6. package/docs/adding-rows.md +132 -0
  7. package/docs/anatomy.md +119 -0
  8. package/docs/card-view.md +108 -0
  9. package/docs/cell-selection.md +194 -0
  10. package/docs/column-layout.md +182 -0
  11. package/docs/column-menu.md +66 -0
  12. package/docs/columns.md +268 -0
  13. package/docs/components.md +311 -0
  14. package/docs/draft-store.md +242 -0
  15. package/docs/editing.md +303 -0
  16. package/docs/editors.md +250 -0
  17. package/docs/export.md +319 -0
  18. package/docs/filtering.md +362 -0
  19. package/docs/getting-started.md +123 -0
  20. package/docs/grouping.md +165 -0
  21. package/docs/loading-and-empty.md +92 -0
  22. package/docs/localization.md +79 -0
  23. package/docs/menu.md +143 -0
  24. package/docs/migrating-to-2.md +163 -0
  25. package/docs/pagination.md +144 -0
  26. package/docs/persistence.md +114 -0
  27. package/docs/portfolio-rebalancer.md +94 -0
  28. package/docs/query-builder.md +179 -0
  29. package/docs/quick-search.md +84 -0
  30. package/docs/row-details.md +115 -0
  31. package/docs/row-interaction.md +149 -0
  32. package/docs/row-pinning.md +132 -0
  33. package/docs/row-selection.md +136 -0
  34. package/docs/row-styling.md +133 -0
  35. package/docs/scrolling.md +112 -0
  36. package/docs/server-query.md +246 -0
  37. package/docs/server-side.md +206 -0
  38. package/docs/sorting.md +101 -0
  39. package/docs/styling.md +126 -0
  40. package/docs/summary-row.md +76 -0
  41. package/docs/testing.md +744 -0
  42. package/docs/toolbar.md +161 -0
  43. package/docs/use-tm-data-grid.md +361 -0
  44. package/package.json +22 -46
  45. package/skills/appearance/SKILL.md +72 -19
  46. package/skills/cell-selection/SKILL.md +46 -47
  47. package/skills/columns/SKILL.md +90 -34
  48. package/skills/data/SKILL.md +86 -16
  49. package/skills/editing/SKILL.md +67 -40
  50. package/skills/editing/references/common-mistakes.md +77 -69
  51. package/skills/editing/references/editing-api.md +22 -19
  52. package/skills/editing/references/editors-and-validation.md +24 -17
  53. package/skills/filtering/SKILL.md +148 -40
  54. package/skills/getting-started/SKILL.md +17 -15
  55. package/skills/grouping/SKILL.md +31 -16
  56. package/skills/options/SKILL.md +7 -7
  57. package/skills/rows/SKILL.md +22 -18
  58. package/skills/server-side/SKILL.md +170 -17
  59. package/skills/testing/SKILL.md +150 -32
  60. package/skills/testing-components/SKILL.md +230 -0
  61. package/skills/testing-editing/SKILL.md +240 -0
  62. package/src/{tmdatagrid/components → components}/TMDataGrid.tsx +38 -21
  63. package/src/{tmdatagrid/components → components}/TMDataGridCellEditor.tsx +54 -8
  64. package/src/{tmdatagrid/components → components}/TMDataGridColumnsPanel.module.css +5 -1
  65. package/src/{tmdatagrid/components → components}/TMDataGridColumnsPanel.tsx +47 -55
  66. package/src/{tmdatagrid/components → components}/TMDataGridDetailsColumn.tsx +5 -51
  67. package/src/{tmdatagrid/components → components}/TMDataGridDraftActions.tsx +41 -24
  68. package/src/{tmdatagrid/components → components}/TMDataGridEditColumn.tsx +12 -59
  69. package/src/{tmdatagrid/components → components}/TMDataGridEntryRows.tsx +164 -92
  70. package/src/components/TMDataGridExportPicker.module.css +77 -0
  71. package/src/components/TMDataGridExportPicker.tsx +234 -0
  72. package/src/components/TMDataGridFilterPanel.module.css +54 -0
  73. package/src/components/TMDataGridFilterPanel.tsx +348 -0
  74. package/src/{tmdatagrid/components → components}/TMDataGridFilterPills.tsx +15 -8
  75. package/src/components/TMDataGridFilterSurface.module.css +54 -0
  76. package/src/components/TMDataGridFilterSurface.tsx +167 -0
  77. package/src/{tmdatagrid/components → components}/TMDataGridFooter.tsx +53 -90
  78. package/src/{tmdatagrid/components → components}/TMDataGridGroupColumn.tsx +5 -69
  79. package/src/{tmdatagrid/components → components}/TMDataGridHeaderCell.module.css +12 -2
  80. package/src/{tmdatagrid/components → components}/TMDataGridHeaderCell.tsx +58 -21
  81. package/src/components/TMDataGridHeaderFilterRow.module.css +51 -0
  82. package/src/components/TMDataGridHeaderFilterRow.tsx +301 -0
  83. package/src/components/TMDataGridMenu.tsx +357 -0
  84. package/src/{tmdatagrid/components → components}/TMDataGridSelectColumn.tsx +11 -48
  85. package/src/{tmdatagrid/components → components}/TMDataGridTable.module.css +69 -56
  86. package/src/{tmdatagrid/components → components}/TMDataGridTable.tsx +240 -138
  87. package/src/{tmdatagrid/components → components}/TMDataGridToolbar.module.css +5 -0
  88. package/src/components/TMDataGridToolbar.tsx +181 -0
  89. package/src/{tmdatagrid/components → components}/editors/TMDataGridBooleanEditor.tsx +3 -3
  90. package/src/{tmdatagrid/components → components}/editors/TMDataGridDateEditor.tsx +3 -3
  91. package/src/{tmdatagrid/components → components}/editors/TMDataGridMultiSelectEditor.tsx +3 -3
  92. package/src/{tmdatagrid/components → components}/editors/TMDataGridNumberEditor.tsx +3 -3
  93. package/src/{tmdatagrid/components → components}/editors/TMDataGridSelectEditor.tsx +3 -3
  94. package/src/{tmdatagrid/components → components}/editors/TMDataGridStringEditor.tsx +3 -3
  95. package/src/{tmdatagrid/components → components}/editors/editorShared.ts +16 -2
  96. package/src/{tmdatagrid/components → components}/filters/DgAutocompleteFilter.tsx +5 -4
  97. package/src/{tmdatagrid/components → components}/filters/DgDateRangeFilter.tsx +22 -5
  98. package/src/{tmdatagrid/components → components}/filters/DgRangeSliderFilter.tsx +5 -1
  99. package/src/{tmdatagrid/components → components}/filters/DgTriStateFilter.tsx +5 -1
  100. package/src/{tmdatagrid/components → components}/filters/TMDataGridFilterValueInput.tsx +44 -28
  101. package/src/components/filters/controlLayout.ts +32 -0
  102. package/src/components/filters/filterControlFor.ts +65 -0
  103. package/src/components/generatedColumns.tsx +187 -0
  104. package/src/{tmdatagrid/components → components}/icons.ts +1 -0
  105. package/src/{tmdatagrid/components → components}/sticky.module.css +44 -0
  106. package/src/components/useHideableColumns.ts +52 -0
  107. package/src/{tmdatagrid/core → core}/autosize.ts +5 -2
  108. package/src/{tmdatagrid/core → core}/columnOptions.ts +60 -8
  109. package/src/{tmdatagrid/core → core}/columnOrdering.ts +33 -14
  110. package/src/{tmdatagrid/core → core}/columnUtils.ts +44 -5
  111. package/src/core/controlledStateSync.ts +108 -0
  112. package/src/core/deletedRows.ts +34 -0
  113. package/src/core/dom.ts +74 -0
  114. package/src/{tmdatagrid/core → core}/editEngine.ts +1107 -460
  115. package/src/core/export.ts +704 -0
  116. package/src/{tmdatagrid/core → core}/filterControls.ts +38 -1
  117. package/src/{tmdatagrid/core → core}/filterOperators.ts +64 -1
  118. package/src/core/filterSurface.ts +99 -0
  119. package/src/{tmdatagrid/core → core}/grouping.ts +21 -0
  120. package/src/{tmdatagrid/core → core}/labels.ts +51 -6
  121. package/src/{tmdatagrid/core → core}/labelsSv.ts +27 -6
  122. package/src/core/pageReset.ts +120 -0
  123. package/src/core/pagination.ts +81 -0
  124. package/src/{tmdatagrid/core → core}/persistence.ts +22 -5
  125. package/src/{tmdatagrid/core → core}/summary.ts +20 -4
  126. package/src/{tmdatagrid/index.ts → index.ts} +69 -35
  127. package/src/{tmdatagrid/useTMDataGrid.tsx → useTMDataGrid.tsx} +428 -109
  128. package/src/useTMDataGridExport.ts +78 -0
  129. package/src/tmdatagrid/components/TMDataGridFilterPanel.module.css +0 -35
  130. package/src/tmdatagrid/components/TMDataGridFilterPanel.tsx +0 -354
  131. package/src/tmdatagrid/components/TMDataGridToolbar.tsx +0 -161
  132. package/src/tmdatagrid/core/cellExport.ts +0 -320
  133. /package/src/{tmdatagrid/TMDataGridContext.ts → TMDataGridContext.ts} +0 -0
  134. /package/src/{tmdatagrid/components → components}/TMDataGrid.module.css +0 -0
  135. /package/src/{tmdatagrid/components → components}/TMDataGridDetailsColumn.module.css +0 -0
  136. /package/src/{tmdatagrid/components → components}/TMDataGridFilterPills.module.css +0 -0
  137. /package/src/{tmdatagrid/components → components}/TMDataGridFooter.module.css +0 -0
  138. /package/src/{tmdatagrid/components → components}/TMDataGridGroupColumn.module.css +0 -0
  139. /package/src/{tmdatagrid/components → components}/TMDataGridRowNumberColumn.tsx +0 -0
  140. /package/src/{tmdatagrid/components → components}/TMDataGridSearch.tsx +0 -0
  141. /package/src/{tmdatagrid/core → core}/capabilities.ts +0 -0
  142. /package/src/{tmdatagrid/core → core}/cellNavigation.ts +0 -0
  143. /package/src/{tmdatagrid/core → core}/cellRange.ts +0 -0
  144. /package/src/{tmdatagrid/core → core}/controlledState.ts +0 -0
  145. /package/src/{tmdatagrid/core → core}/draftCellContext.ts +0 -0
  146. /package/src/{tmdatagrid/core → core}/editorFocus.ts +0 -0
  147. /package/src/{tmdatagrid/core → core}/expanding.ts +0 -0
  148. /package/src/{tmdatagrid/core → core}/matchHighlight.ts +0 -0
  149. /package/src/{tmdatagrid/core → core}/quickSearch.ts +0 -0
  150. /package/src/{tmdatagrid/core → core}/resizePreview.ts +0 -0
  151. /package/src/{tmdatagrid/core → core}/rowPinning.ts +0 -0
  152. /package/src/{tmdatagrid/core → core}/rowSelection.ts +0 -0
  153. /package/src/{tmdatagrid/core → core}/sizes.ts +0 -0
  154. /package/src/{tmdatagrid/core → core}/useSettledTableState.ts +0 -0
@@ -0,0 +1,161 @@
1
+ # Toolbar
2
+
3
+ The toolbar is a flex row. Your own buttons are children of it, beside the
4
+ built-in ones; there is no slots API and no `actions` prop.
5
+
6
+ ```tsx
7
+ <TMDataGrid.Toolbar>
8
+ <TMDataGrid.SummaryCount />
9
+ <TMDataGrid.Spacer />
10
+ <TMDataGrid.LoadingIndicator />
11
+ <Button size="xs" variant="light" onClick={exportAll}>
12
+ Export
13
+ </Button>
14
+ <TMDataGrid.FilterButton />
15
+ <TMDataGrid.Menu>
16
+ <TMDataGrid.Menu.Columns />
17
+ </TMDataGrid.Menu>
18
+ </TMDataGrid.Toolbar>
19
+ ```
20
+
21
+ `TMDataGrid.Spacer` pushes everything after it to the right.
22
+ `TMDataGrid.Menu` is the burger; it holds the column chooser here, and anything else you put in it.
23
+ See [Grid menu](/docs/menu).
24
+
25
+ ```demo
26
+ file: customization/ToolbarComposition.tsx
27
+ extraSources: data/employeeColumns.tsx
28
+ ```
29
+
30
+ Only the parts you render exist. A grid with no `Toolbar` has no toolbar; a
31
+ toolbar with only `Search` has only a search box.
32
+
33
+ ## Style props
34
+
35
+ `TMDataGrid.Toolbar` and `TMDataGrid.Spacer` take Mantine's `BoxProps`: the style props (`mb`, `px`, `h`, `hiddenFrom`, …), `className`, `style` and `mod`, set on the element itself.
36
+ `withBottomBorder` draws a 1px line under the toolbar in the theme's default border colour, the same line the header draws under itself.
37
+ It defaults to `false`.
38
+
39
+ ```tsx
40
+ <TMDataGrid.Toolbar withBottomBorder px="sm">
41
+ <TMDataGrid.SummaryCount />
42
+ <TMDataGrid.Spacer hiddenFrom="sm" />
43
+ <TMDataGrid.FilterButton />
44
+ </TMDataGrid.Toolbar>
45
+ ```
46
+
47
+ `TMDataGrid.Footer`, `TMDataGrid.FilterPanel`, `TMDataGrid.FilterPills` and `TMDataGrid.ColumnsPanel` take the same `BoxProps`; see [Components](/docs/components).
48
+
49
+ ## The built-in parts
50
+
51
+ | Component | Shows |
52
+ | --- | --- |
53
+ | `TMDataGrid.Search` | The [quick search](/docs/quick-search) input |
54
+ | `TMDataGrid.FilterButton` | Opens the [filter panel](/docs/filtering), with the active count |
55
+ | `TMDataGrid.Menu` | The burger: a [menu](/docs/menu) you fill; `TMDataGrid.Menu.Columns` is the column chooser as items |
56
+ | `TMDataGrid.SummaryCount` | Visible rows out of total, or the count alone where there is no total to compare it against |
57
+ | `TMDataGrid.LoadingIndicator` | A spinner while `meta.loading` |
58
+ | `TMDataGrid.Spacer` | Pushes what follows to the right |
59
+ | `TMDataGridDraftActions` | Save and Discard for the [draft store](/docs/draft-store) |
60
+
61
+ Each renders nothing when its feature is off, so a read-only grid needs no
62
+ conditionals in the toolbar: `FilterButton` under `enableColumnFilters: false`
63
+ renders nothing at all.
64
+ `TMDataGrid.Menu` is the exception, since it cannot see what its children render; see [Grid menu](/docs/menu).
65
+
66
+ ## Buttons of your own
67
+
68
+ A button that acts on the grid reads it from context:
69
+
70
+ ```tsx
71
+ import { useTMDataGridContext } from "@jielga/tmdatagrid";
72
+
73
+ function ClearFiltersButton() {
74
+ const { table } = useTMDataGridContext();
75
+ return (
76
+ <Button size="xs" onClick={() => table.resetColumnFilters()}>
77
+ Clear filters
78
+ </Button>
79
+ );
80
+ }
81
+ ```
82
+
83
+ Anything rendered inside `TMDataGrid` can call it. It returns
84
+ `{ table, ui, features, filters, exportOptions, labels, controlSize, resetSettings }`.
85
+
86
+ An export button uses `useTMDataGridExport`, which hands back the export as click handlers:
87
+
88
+ ```tsx
89
+ import { useTMDataGridExport } from "@jielga/tmdatagrid";
90
+
91
+ function ExportButton() {
92
+ const { exportAll } = useTMDataGridExport();
93
+ return (
94
+ <Button size="xs" onClick={() => void exportAll()}>
95
+ Export
96
+ </Button>
97
+ );
98
+ }
99
+ ```
100
+
101
+ It writes every filtered row, whatever the cell selection is or whether cell selection is on at all.
102
+ The format, the file name and the selected-rows variant are on [Export](/docs/export).
103
+
104
+ ### Hide a button when its feature is off
105
+
106
+ The built-in parts disappear when their feature is off. Your own can use the
107
+ same checks instead of re-deriving them:
108
+
109
+ ```tsx
110
+ import { getGridCapabilities, useTMDataGridContext } from "@jielga/tmdatagrid";
111
+
112
+ function ExportButton() {
113
+ const { table, features } = useTMDataGridContext();
114
+ const { canFilterAny } = getGridCapabilities(table, features);
115
+
116
+ if (!canFilterAny) return null;
117
+ // …
118
+ }
119
+ ```
120
+
121
+ | Field | True when |
122
+ | --- | --- |
123
+ | `canSortAny` | At least one leaf column can be sorted |
124
+ | `canFilterAny` | At least one leaf column can be filtered |
125
+ | `canHideAny` | At least one leaf column can be hidden |
126
+ | `canPinAny` | At least one leaf column can be pinned |
127
+ | `canReorderAny` | At least one leaf column can be moved |
128
+ | `canGroupAny` | At least one leaf column can be grouped on |
129
+ | `canSelectRows` | `enableRowSelection` is not `false` and the mode is not `"highlight"` |
130
+ | `canPaginate` | Paging is configured. See [`isPagingActive`](/docs/pagination#grouping) for whether it is slicing anything |
131
+ | `canSearch` | At least one leaf column takes part in the quick search |
132
+
133
+ `getColumnCapabilities(column, features)` returns the same for one column, as
134
+ `canSort`, `canFilter`, `canHide`, `canPin`, `canResize`, `canReorder` and
135
+ `canGroup`.
136
+
137
+ ### Reading options reactively
138
+
139
+ `features` is re-derived from the options object on every render, and both
140
+ capability helpers take it alongside the table or column. A bare
141
+ `column.getCanSort()` is memoized against a column identity that survives an
142
+ options change, so a grid whose `enableSorting` turned `false` would keep
143
+ rendering sort indicators; `features` is the value that changes.
144
+
145
+ Read state through TanStack Store's
146
+ [`useSelector(table.store, …)`](https://tanstack.com/store/latest/docs/framework/react/reference)
147
+ and options through `features`, rather than calling methods on a long-lived
148
+ object.
149
+
150
+ ## Reference
151
+
152
+ | Name | Kind | Type | Default | What it does |
153
+ | --- | --- | --- | --- | --- |
154
+ | `TMDataGrid.Toolbar` | Component | `children`, `withBottomBorder`, Mantine `BoxProps` | `withBottomBorder: false` | The flex row above the grid. Style props set on the row. |
155
+ | `TMDataGrid.Spacer` | Component | Mantine `BoxProps` | – | Pushes what follows to the right. |
156
+ | `TMDataGrid.FilterButton` | Component | – | – | Opens the filter panel, with an active count. |
157
+ | `TMDataGrid.Menu` | Component | `children` | – | The burger and its dropdown. See [Grid menu](/docs/menu). |
158
+ | `useTMDataGridContext` | Hook | `() => TMDataGridContextValue` | – | `{ table, ui, features, labels, controlSize, resetSettings }`. |
159
+ | `getGridCapabilities` | Export | `(table, features) => TMDataGridCapabilities` | – | What this grid can do. Reactive to option changes. |
160
+ | `getColumnCapabilities` | Export | `(column, features) => TMDataGridColumnCapabilities` | – | The same for one column. |
161
+ | `readFeatureFlags` | Export | `(options) => TMDataGridFeatureFlags` | – | Derives the flags from an options object. |
@@ -0,0 +1,361 @@
1
+ # useTMDataGrid
2
+
3
+ Creates the table instance and the state used by the grid interface.
4
+
5
+ ```tsx
6
+ const grid = useTMDataGrid<TData>(options);
7
+ // { table, ui, features }
8
+ ```
9
+
10
+ Spread the result onto `TMDataGrid`.
11
+
12
+ ## Options
13
+
14
+ All TanStack `TableOptions` are supported and passed through unchanged,
15
+ including `data`, `columns`, `getRowId`, `state` and the `onXChange` callbacks
16
+ (see [Controlled state](#controlled-state)), the `manual*` flags and `rowCount`.
17
+ The `features` option is supplied internally and cannot be overridden.
18
+
19
+ `persist`, `enableColumnOrdering`, `enablePagination`, `selectionMode`,
20
+ `showSelectedBackground`, `defaultHighlightedRowId`, `onHighlightedRowChange`,
21
+ `renderDetails`, `renderDetailsEstHeight`, `overscan`, `cellSelection`,
22
+ `onFocusedCellChange`, `quickSearchMode`, `labels` and `editing`
23
+ (see [Editing](/docs/editing)) are the grid's own options and are consumed here
24
+ rather than forwarded to TanStack.
25
+
26
+ | Option | Type | Default | Description |
27
+ | --- | --- | --- | --- |
28
+ | `data` | `TData[]` | – | Row data. When the reference changes the table reprocesses the row models, so keep it stable with `useMemo`. |
29
+ | `columns` | `ColumnDef[]` | – | Created with `createTMDataGridColumnHelper`. |
30
+ | `getRowId` | `(row, index) => string` | Row index | Used by row selection and virtualization. |
31
+ | `enableRowSelection` | `boolean \| (row) => boolean` | `true` | `false` removes row selection and its checkbox column. |
32
+ | `selectionMode` | `"checkbox" \| "row" \| "checkboxAndHighlight" \| "highlight"` | `"checkbox"` | What selecting looks like and what a bare row click does. Defined by the grid, see [Row selection](/docs/row-selection). |
33
+ | `showSelectedBackground` | `boolean` | On for `"row"`, off for `"checkbox"` | Determines whether selected rows take a background tint, coloured by `--dg-row-selected-bg`. Defined by the grid, see [Row selection](/docs/row-selection). |
34
+ | `defaultHighlightedRowId` | `string \| null` | – | Row highlighted on mount, under a mode with a highlight. Read once, like `initialState`. |
35
+ | `onHighlightedRowChange` | `(rowId: string \| null) => void` | – | Follows the highlighted row, from clicks and from `ui.actions.setHighlightedRow`. |
36
+ | `enableSorting` | `boolean` | `true` | Enables sorting for the table. |
37
+ | `enableColumnFilters` | `boolean` | `true` | Enables filtering for the table. |
38
+ | `enableHiding` | `boolean` | `true` | Enables column visibility for the table. |
39
+ | `enableColumnPinning` | `boolean` | `true` | Enables pinning for the table. |
40
+ | `enableColumnResizing` | `boolean` | `true` | Enables resizing for the table. |
41
+ | `enableColumnOrdering` | `boolean` | `true` | Enables header dragging and the move menu items. Defined by the grid, see [Column layout](/docs/column-layout#ordering). |
42
+ | `enablePagination` | `boolean` | `false` | Enables client-side paging and the `Footer` pager. Implied by `manualPagination`. Defined by the grid, see [Pagination](/docs/pagination). |
43
+ | `enableRowNumbers` | `boolean` | `false` | The row-number gutter, outermost left. Defined by the grid, see [Row pinning and numbering](/docs/row-pinning). |
44
+ | `enableRowPinning` | `boolean \| (row) => boolean` | `false` | Rows can be pinned to sticky edge blocks with `row.pin()`. See [Row pinning and numbering](/docs/row-pinning). |
45
+ | `quickSearchMode` | `"fuzzy" \| "contains"` | `"fuzzy"` | How `Search` matches. Fuzzy forgives typos and orders unsorted results by match quality; `"contains"` is plain substring matching. Defined by the grid, see [Quick search](/docs/quick-search). |
46
+ | `enableMatchHighlighting` | `boolean` | `false` | Cells mark the matched text while a contains-family filter or the quick search is active. Defined by the grid, see [Quick search](/docs/quick-search#match-highlighting). |
47
+ | `renderDetails` | `({ row, table }) => ReactNode` | – | Panel rendered under an expanded row, spanning every column. Setting it turns row details on and adds the pinned chevron lane. Defined by the grid, see [Row details](/docs/row-details). |
48
+ | `renderDetailsEstHeight` | `number` | `160` | What the virtualizer assumes for a panel it has not measured yet. Panels are measured once mounted, so an approximation is enough. |
49
+ | `cellSelection` | `"none" \| "single" \| "range"` | `"none"` | Cell cursor and, under `"range"`, a selectable rectangle with Ctrl+C and CSV export. Defined by the grid, see [Cell selection](/docs/cell-selection). |
50
+ | `onFocusedCellChange` | `(cell: TMDataGridCellPosition \| null) => void` | – | Called whenever the focused cell moves, by key, click or `setFocusedCell`. |
51
+ | `overscan` | `number` | `6` | Rows the virtualizer keeps mounted above and below the viewport. Raise it if fast scrolling flashes blank rows, lower it when rows are expensive to render. |
52
+ | `columnResizeMode` | `"onChange" \| "onEnd"` | `"onEnd"` | When a resize drag writes `columnSizing`. The grid paints a running drag itself either way; `"onChange"` also publishes a width on every pointer move, which re-renders the grid with each of them. |
53
+ | `initialState` | `Partial<TableState>` | – | Starting state, read once on mount. Merged over the [grid defaults](#default-initial-state). |
54
+ | `state` | `Partial<TableState>` | – | Controlled state. Each slice requires its `onXChange`. See [Controlled state](#controlled-state). |
55
+ | `atoms` | `Partial<Record<slice, Atom>>` | – | External atoms owning state slices. No callback required. Takes precedence over `state`. |
56
+ | `meta` | `TMDataGridTableMeta` | `{}` | Grid configuration. See [meta](#meta). |
57
+ | `filters` | `TMDataGridFiltersOptions` | `{ surface: "popup" }` | Where the filter controls go - a popup, a sidebar, the column headers, or nowhere. See [Filtering](/docs/filtering#the-filters-option). |
58
+ | `persist` | `TMDataGridPersistence` | – | State persistence. See [persist](#persist). |
59
+ | `labels` | `TMDataGridLabelsOverride` | English | Overrides for the grid's strings, see [Localization](#localization). |
60
+
61
+ ### Default initial state
62
+
63
+ | Slice | Default |
64
+ | --- | --- |
65
+ | `pagination` | `{ pageIndex: 0, pageSize: 25 }`. Inert until pagination is enabled |
66
+ | `columnPinning.start` | The checkbox, tree and details columns, followed by any columns you provide |
67
+ | `globalFilterFn` | `"tmDataGridFuzzy"`, the fuzzy matcher behind `quickSearchMode`. `"includesString"` under `quickSearchMode: "contains"` |
68
+
69
+ ### Controlled state
70
+
71
+ `state` makes a slice controlled: the parent owns the value and the grid reads
72
+ it on every render. Each controlled slice requires its `onXChange` callback -
73
+ all writes go through it. Without the callback the slice cannot change; the
74
+ grid logs a console warning in development. For a starting value only, use
75
+ `initialState`.
76
+
77
+ ```tsx
78
+ const [columnVisibility, setColumnVisibility] = useState({ play: false });
79
+
80
+ const grid = useTMDataGrid({
81
+ data,
82
+ columns,
83
+ state: { columnVisibility },
84
+ onColumnVisibilityChange: setColumnVisibility,
85
+ });
86
+ ```
87
+
88
+ - Controllable slices: `columnFilters`, `columnOrder`, `columnPinning`,
89
+ `columnResizing`, `columnSizing`, `columnVisibility`, `expanded`,
90
+ `globalFilter`, `grouping`, `pagination`, `rowPinning`, `rowSelection`,
91
+ `sorting`. Each has a matching `onXChange`.
92
+ - A key set to `undefined` is ignored; the slice is uncontrolled.
93
+ - Slices are compared structurally between renders, so the `state` object can
94
+ be built inline. `Date` values compare by time. `Map`s and class instances
95
+ compare by identity; keep a slice containing one in `useState` or `useMemo`.
96
+ - `atoms` also controls a slice, with no callback: the table writes through the
97
+ atom. An atom takes precedence over `state` for the same slice.
98
+ - `columnVisibility` toggles user-defined columns only. Entries for the
99
+ generated columns (checkbox, details, edit, row number) are ignored; enable
100
+ or disable those through their feature options. The tree column's entry is
101
+ managed by the grid and follows `grouping`.
102
+ - `persist` restores through `initialState` and cannot restore a controlled
103
+ slice. It still writes the slice to storage on change.
104
+
105
+ ## meta
106
+
107
+ Grid configuration, passed through the `meta` option.
108
+
109
+ | Field | Type | Default | Description |
110
+ | --- | --- | --- | --- |
111
+ | `loading` | `boolean` | – | Displays a loader while the grid is empty. Takes precedence over every empty state. |
112
+ | `noResultsLabel` | `string` | `"No rows match your filters"` | The filtered-empty message. A grid with no data and no filters says `labels.noRows` instead; `renderEmptyState` on the Table replaces both. |
113
+ | `rowHeight` | `number` | From `size` | Row height in px. Overrides the size scale. |
114
+ | `totalRowCount` | `number` | – | Unfiltered total used by `SummaryCount`. Required for server-side data. |
115
+
116
+ ## Localization
117
+
118
+ Every string the grid renders - menu items, panels, tooltips, the pager and the
119
+ `aria-label`s - comes from one labels object, English by default. The `labels`
120
+ option takes any subset and merges it over the defaults:
121
+
122
+ ```tsx
123
+ const grid = useTMDataGrid({
124
+ data,
125
+ columns,
126
+ labels: { noResults: "Inga rader matchar dina filter" },
127
+ });
128
+ ```
129
+
130
+ Labels that carry a value are functions, so each language can place the value
131
+ where its grammar requires:
132
+
133
+ ```tsx
134
+ labels: {
135
+ groupBy: (column) => `Gruppera på ${column}`,
136
+ pageRange: ({ from, to, total }) => `${from}–${to} av ${total}`,
137
+ }
138
+ ```
139
+
140
+ Keep the object referentially stable (module scope or `useMemo`); the grid
141
+ re-renders when its identity changes.
142
+
143
+ The full dictionary type is `TMDataGridLabels`; the English defaults are
144
+ exported as `TMDATAGRID_LABELS_EN`, and a complete Swedish dictionary as
145
+ `TMDATAGRID_LABELS_SV`:
146
+
147
+ ```tsx
148
+ import { TMDATAGRID_LABELS_SV } from "@jielga/tmdatagrid";
149
+
150
+ const grid = useTMDataGrid({ data, columns, labels: TMDATAGRID_LABELS_SV });
151
+ ```
152
+
153
+ `meta.noResultsLabel` still works as a per-instance override of
154
+ `labels.noResults`.
155
+
156
+ The resolved dictionary is returned from the hook as `grid.labels`, so custom
157
+ toolbar components can read the same strings as the built-in parts.
158
+
159
+ ## persist
160
+
161
+ Restores table state on mount and writes it back on every change. State is
162
+ split across two keys: `settingsKey` for the column layout, `dataKey` for
163
+ filters, sorting and pagination. Either can be cleared without the other.
164
+
165
+ | Field | Type | Default | Description |
166
+ | --- | --- | --- | --- |
167
+ | `dataKey` | `string \| [string, DataSlice[]]` | – | Storage key for the data group. |
168
+ | `settingsKey` | `string \| [string, SettingsSlice[]]` | – | Storage key for the settings group. |
169
+ | `storageMode` | `"localStorage" \| "sessionStorage"` | `"localStorage"` | Storage area. Use `"sessionStorage"` for per-tab state. |
170
+ | `serialize` | `(value) => string` | `JSON.stringify` | Serializes a payload before storing. |
171
+ | `deserialize` | `(value: string) => unknown` | `JSON.parse` | Parses a stored payload. |
172
+
173
+ Both keys are optional.
174
+
175
+ ```tsx
176
+ // Defined at module scope: the object is a dependency of the write subscription.
177
+ const persist = {
178
+ dataKey: "employees.data",
179
+ settingsKey: "employees.settings",
180
+ } satisfies TMDataGridPersistence;
181
+
182
+ const grid = useTMDataGrid({ data, columns, persist });
183
+ ```
184
+
185
+ ### Selecting slices
186
+
187
+ Passing a key on its own persists every slice in its group. Pass a tuple of
188
+ `[key, slices]` to persist only some of them:
189
+
190
+ ```tsx
191
+ const persist = {
192
+ // Restore filters and sorting, but always start on the first page.
193
+ dataKey: ["employees.data", ["columnFilters", "sorting"]],
194
+ // Restore column layout but not widths.
195
+ settingsKey: ["employees.settings", ["columnVisibility", "columnOrder"]],
196
+ storageMode: "sessionStorage",
197
+ } satisfies TMDataGridPersistence;
198
+ ```
199
+
200
+ | Group | Available slices |
201
+ | --- | --- |
202
+ | `dataKey` | `columnFilters`, `globalFilter`, `sorting`, `pagination`, `expanded` |
203
+ | `settingsKey` | `columnVisibility`, `columnSizing`, `columnOrder`, `columnPinning`, `grouping` |
204
+
205
+ The exported `DATA_STATE_SLICES` and `SETTINGS_STATE_SLICES` arrays contain the
206
+ same values. Slice names are typed per group, so only valid names are accepted.
207
+
208
+ ### Behaviour
209
+
210
+ Restoring happens once on mount through `initialState`. Writing is implemented
211
+ as a subscription to the table store, so state changed directly through the
212
+ table API is persisted as well.
213
+
214
+ Only the selected slices are read back. A payload written before the selection
215
+ was narrowed cannot reintroduce slices you have since opted out of. Unrecognised
216
+ keys are ignored.
217
+
218
+ Payloads carry a version stamp, the exported `PERSIST_PAYLOAD_VERSION`. A
219
+ payload from a different version, including anything written by a 0.x build,
220
+ which had no stamp, is dropped whole rather than migrated.
221
+
222
+ Restored state is realigned against the columns that exist. Entries naming a
223
+ column removed between deploys are dropped: a stale id in the order, a width for
224
+ a column that no longer exists, or a sort or filter that would be active with no
225
+ column to show it. New columns need no handling, since TanStack appends columns
226
+ missing from `columnOrder` in definition order.
227
+
228
+ `resetSettings()` on the returned API puts the settings state back to what a
229
+ first visit with clean storage would have shown - your `initialState` plus the
230
+ structural lanes - and, with persistence configured, writes through to storage
231
+ like any other change. The columns panel's **Reset layout** button calls it.
232
+ TanStack's own `resetColumnX()` family cannot do it on a persisted grid: those
233
+ reset to `initialState`, which the mount built from the restored payload.
234
+
235
+ All storage access is guarded. If storage is unavailable, disabled or full,
236
+ persistence is skipped rather than throwing.
237
+
238
+ Storage keys are not namespaced automatically. Include a tenant or user
239
+ identifier if several users can share a browser profile.
240
+
241
+ ## Return value
242
+
243
+ | Field | Type | Description |
244
+ | --- | --- | --- |
245
+ | `table` | `Table<TMDataGridFeatures, TData>` | The TanStack table instance. |
246
+ | `ui` | `Store<TMDataGridUiState, TMDataGridUiActions>` | State of the filter and column panels. |
247
+ | `filters` | `TMDataGridFiltersSettings` | The `filters` option with its defaults filled in. See [Filtering](/docs/filtering#the-filters-option). |
248
+ | `edit` | `TMDataGridEditApi` | The edit engine, inert until `editing` is set. See [Editing](/docs/editing). |
249
+ | `features` | `TMDataGridFeatureFlags` | Table-level feature switches, re-read from options on each render. See [Toolbar](/docs/toolbar#reading-options-reactively). |
250
+ | `labels` | `TMDataGridLabels` | The resolved label set, overrides merged over English. See [Localization](/docs/localization). |
251
+ | `renderDetails` | `TMDataGridDetailsRenderer<TData> \| undefined` | The detail renderer, passed through for `TMDataGrid.Table` to call. |
252
+ | `renderDetailsEstHeight` | `number` | The estimate, resolved to its default when the option was not set. |
253
+ | `overscan` | `number` | The overscan, resolved to its default when the option was not set. |
254
+ | `resetSettings` | `() => void` | Puts the settings state back to a clean first visit. See [persist](#persist). |
255
+ | `scrollToRow` | `({ rowId, align? }) => boolean` | Scrolls a row into view; see below. |
256
+
257
+ ### scrollToRow
258
+
259
+ The grid is always virtualized, so a row far down the list has no element and
260
+ `scrollIntoView` has nothing to act on. `scrollToRow` moves the virtualizer
261
+ instead:
262
+
263
+ ```ts
264
+ grid.scrollToRow({ rowId: "42" });
265
+ grid.scrollToRow({ rowId: "42", align: "center" });
266
+ ```
267
+
268
+ `align` is TanStack Virtual's: `"auto"` (the default: nearest edge, leaving a
269
+ visible row where it is), `"start"`, `"center"` or `"end"`.
270
+
271
+ It returns whether the row could be reached. `false` means the row is not in the
272
+ current view - filtered out, on another page, or an id matching no row - and
273
+ nothing scrolled. A pinned row returns `true` without scrolling.
274
+
275
+ The identity is stable, so it is safe in a dependency array. Before
276
+ `TMDataGrid.Table` has mounted there is nothing to scroll and it returns
277
+ `false`.
278
+
279
+ Both stores are subscribable, so a parent component can react to grid state
280
+ without holding it:
281
+
282
+ ```tsx
283
+ import { useSelector } from "@tanstack/react-store";
284
+
285
+ const selectedCount = useSelector(
286
+ grid.table.store,
287
+ (state) => Object.keys(state.rowSelection).length,
288
+ );
289
+
290
+ const filterPanelOpen = useSelector(grid.ui, (state) => state.filterPanelOpen);
291
+ ```
292
+
293
+ Read state with `useSelector` rather than `grid.table.store.state`. A direct
294
+ read does not subscribe the component to the store.
295
+
296
+ ### ui actions
297
+
298
+ | Action | Signature |
299
+ | --- | --- |
300
+ | `openFilterPanel` | `(columnId?: string \| null) => void` |
301
+ | `closeFilterPanel` | `() => void` |
302
+ | `focusPanelFilter` | `(columnId: string \| null) => void` |
303
+ | `focusHeaderFilter` | `(columnId: string \| null) => void` |
304
+ | `startColumnDrag` | `(columnId: string) => void` |
305
+ | `endColumnDrag` | `() => void` |
306
+ | `setHighlightedRow` | `(rowId: string \| null) => void` |
307
+ | `setSelectionAnchor` | `(rowId: string \| null) => void` |
308
+ | `setFocusedCell` | `(cell: TMDataGridCellPosition \| null) => void` |
309
+ | `setCellRange` | `(range: TMDataGridCellRange \| null) => void` |
310
+
311
+ `setFocusedCell` and `setCellRange` move the cell cursor and the selected
312
+ rectangle under `cellSelection`. See [Cell selection](/docs/cell-selection). DOM
313
+ focus follows `focusedCell` while the grid holds it, scrolling the row into view
314
+ when it is off screen.
315
+
316
+ `startColumnDrag` and `endColumnDrag` are called by the header cells while a
317
+ column is being dragged. `ui.draggedColumnId` holds the column being moved,
318
+ since `dataTransfer` is unreadable until the drop.
319
+
320
+ `openColumnFilter(grid, columnId)` combines two steps used by the column menu:
321
+ it adds an empty filter row for the column if none exists, then sends the user
322
+ to that column's control. Which control that is follows the `filters` option -
323
+ the panel row, or, under `filters.inHeader`, the column's header control.
324
+
325
+ The two focus actions are the halves of that, and they write separate state
326
+ (`filterPanelColumnId` and `headerFilterColumnId`) so a grid showing both a
327
+ panel and header controls never has the two racing for the caret.
328
+
329
+ ## What each switch removes
330
+
331
+ Almost every control is bound to the TanStack capability check for its feature,
332
+ so setting the standard table or column option removes the corresponding
333
+ interface. Empty menus and inactive buttons are never rendered, except `TMDataGrid.Menu`, which cannot see what its children render; see [Grid menu](/docs/menu).
334
+
335
+ Column ordering and pagination are the two exceptions: TanStack ships state and
336
+ APIs for both but no `enable` option, so the grid defines `enableColumnOrdering`
337
+ (with `meta.enableOrdering`) and `enablePagination` itself. Pagination defaults
338
+ to off; ordering, like the options around it, defaults to on.
339
+
340
+ | Option | Level | Interface removed |
341
+ | --- | --- | --- |
342
+ | `enableSorting: false` | Table, column | Sort indicator, sort menu items, click-to-sort. See [Sorting](/docs/sorting) |
343
+ | `enableColumnFilters: false` | Table | Filter menu item, `FilterButton`, filter panel, header filter row. See [Filtering](/docs/filtering) |
344
+ | `enableColumnFilter: false` | Column | That column's filter menu item, panel entry and header control |
345
+ | `enableGlobalFilter: false` | Table, column | `Search`: the whole input at table level, one column's participation at column level |
346
+ | `enableHiding: false` | Table, column | Hide column, Manage columns, `TMDataGrid.Menu.Columns`. See [Column layout](/docs/column-layout) |
347
+ | `enableColumnPinning: false` | Table | Pin and unpin menu items. See [Column layout](/docs/column-layout) |
348
+ | `enablePinning: false` | Column | That column's pin menu items |
349
+ | `enableColumnResizing: false` | Table | Resize dragging, double-click autosize, the Autosize menu item. The divider remains as a separator |
350
+ | `enableResizing: false` | Column | That column's resize dragging and autosize |
351
+ | `enableColumnOrdering: false` | Table | Header dragging and the move menu items. See [Column layout](/docs/column-layout#ordering) |
352
+ | `meta.enableOrdering: false` | Column | That column's header dragging and move menu items |
353
+ | `enableRowSelection: false` | Table | The checkbox column |
354
+ | `selectionMode: "row"` / `"highlight"` | Table | The checkbox column. The row click selects instead. See [Row selection](/docs/row-selection) |
355
+ | `showSelectedBackground: false` | Table | The highlight background on selected rows. Follows the selection mode by default |
356
+ | `enablePagination: true` | Table | Opt-in: adds paging and the `Footer` pager. Off by default |
357
+ | `enableRowNumbers: true` | Table | Opt-in: the row-number gutter, outermost left. Numbers the current view: sorted, filtered, continuing across pages. Group rows take no number |
358
+ | `enableRowPinning: true` | Table | Opt-in: rows can be pinned to sticky edge blocks with `row.pin()`. Also takes a per-row predicate. See [Row pinning](/docs/row-pinning) |
359
+ | `enableMatchHighlighting: true` | Table | Opt-in: cells mark the matched text while a contains-family filter or the quick search is active. See [Quick search](/docs/quick-search#match-highlighting) |
360
+ | `enableGrouping: false` | Table, column | Group by and Ungroup menu items. See [Grouping](/docs/grouping) |
361
+ | `renderDetails` | Table | Opt-in: adds the details lane, and an expanded row opens a panel underneath it. See [Row details](/docs/row-details) |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jielga/tmdatagrid",
3
- "version": "2.0.0-beta.9",
3
+ "version": "2.0.0",
4
4
  "description": "A React data grid built on TanStack Table v9 and Mantine - always virtualized, with resizable, reorderable, sortable, filterable, hideable and pinnable columns.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -9,7 +9,8 @@
9
9
  ],
10
10
  "repository": {
11
11
  "type": "git",
12
- "url": "git+https://github.com/Jielga/TMDataGrid.git"
12
+ "url": "git+https://github.com/Jielga/TMDataGrid.git",
13
+ "directory": "packages/tmdatagrid"
13
14
  },
14
15
  "homepage": "https://jielga.github.io/TMDataGrid/",
15
16
  "bugs": {
@@ -29,17 +30,13 @@
29
30
  "access": "public"
30
31
  },
31
32
  "intent": {
32
- "docs": "src/docs/",
33
- "skills": [
34
- "@tanstack/react-table",
35
- "@tanstack/table-core",
36
- "@tanstack/router-core"
37
- ]
33
+ "docs": "docs/"
38
34
  },
39
35
  "files": [
40
36
  "dist",
41
- "src/tmdatagrid",
42
- "!src/tmdatagrid/**/*.test.*",
37
+ "src",
38
+ "!src/**/*.test.*",
39
+ "docs",
43
40
  "skills",
44
41
  "!skills/_artifacts"
45
42
  ],
@@ -53,79 +50,58 @@
53
50
  },
54
51
  "./styles.css": "./dist/styles.css",
55
52
  "./styles.layer.css": "./dist/styles.layer.css",
53
+ "./docs/*": "./docs/*",
56
54
  "./package.json": "./package.json"
57
55
  },
58
56
  "scripts": {
59
- "dev": "vite",
60
- "build": "tsc -b && vite build",
61
- "build:lib": "vite build --config vite.lib.config.ts && tsc -p tsconfig.lib.json && rolldown -c rolldown.dts.config.mjs",
62
- "lint": "oxlint",
63
- "check:style": "node scripts/check-house-style.mjs",
64
- "check:skills": "node scripts/intent.mjs validate",
57
+ "build": "vite build && tsc -p tsconfig.build.json && rolldown -c rolldown.dts.config.mjs",
65
58
  "test": "vitest run",
66
- "test:watch": "vitest",
67
- "preview": "vite preview",
68
- "changeset": "changeset",
69
- "version-packages": "changeset version && node scripts/sync-skill-version.mjs",
70
- "release": "changeset publish",
71
- "prepublishOnly": "npm run lint && npm run build:lib",
72
- "prepare": "husky"
59
+ "gallery": "vite --config test/gallery/vite.config.ts",
60
+ "prepublishOnly": "oxlint && bun run build"
73
61
  },
74
62
  "peerDependencies": {
75
63
  "@mantine/core": "^9.4.0",
76
64
  "@tabler/icons-react": "^3.46.0",
77
65
  "@tanstack/react-form": "^1.33.0",
78
- "@tanstack/react-store": "^0.11.0",
79
- "@tanstack/react-table": "^9.0.0-beta.21",
66
+ "@tanstack/react-store": "^0.11.1",
67
+ "@tanstack/react-table": "^9.2.4",
80
68
  "@tanstack/react-virtual": "^3.14.4",
81
- "@tanstack/store": "^0.11.0",
82
- "@tanstack/table-core": "^9.0.0-beta.21",
69
+ "@tanstack/store": "^0.11.1",
70
+ "@tanstack/table-core": "^9.2.4",
83
71
  "react": "^19.1.0",
84
72
  "react-dom": "^19.1.0"
85
73
  },
74
+ "dependencies": {
75
+ "@tanstack/match-sorter-utils": "^8.19.4"
76
+ },
86
77
  "devDependencies": {
87
78
  "@babel/core": "^7.29.7",
88
- "@changesets/changelog-github": "^0.7.0",
89
- "@changesets/cli": "^2.31.1",
90
- "@mantine/code-highlight": "^9.4.1",
91
79
  "@mantine/core": "^9.4.0",
92
- "@mantine/hooks": "^9.4.1",
93
80
  "@rolldown/plugin-babel": "^0.2.3",
94
81
  "@tabler/icons-react": "^3.46.0",
95
82
  "@tanstack/intent": "0.3.6",
96
83
  "@tanstack/react-form": "^1.33.2",
97
- "@tanstack/react-router": "^1.170.17",
98
- "@tanstack/react-router-devtools": "^1.167.0",
99
- "@tanstack/react-store": "^0.11.0",
100
- "@tanstack/react-table": "^9.0.0-beta.21",
84
+ "@tanstack/react-store": "^0.11.1",
85
+ "@tanstack/react-table": "^9.2.4",
101
86
  "@tanstack/react-virtual": "^3.14.4",
102
- "@tanstack/store": "^0.11.0",
103
- "@tanstack/table-core": "^9.0.0-beta.21",
87
+ "@tanstack/store": "^0.11.1",
88
+ "@tanstack/table-core": "^9.2.4",
104
89
  "@testing-library/jest-dom": "^7.0.0",
105
90
  "@testing-library/react": "^16.3.2",
106
91
  "@testing-library/user-event": "^14.6.1",
107
92
  "@types/babel__core": "^7.20.5",
108
- "@types/node": "^24.13.2",
109
93
  "@types/react": "^19.2.17",
110
94
  "@types/react-dom": "^19.2.3",
111
95
  "@vitejs/plugin-react": "^6.0.4",
112
96
  "babel-plugin-react-compiler": "^1.0.0",
113
- "husky": "^9.1.7",
114
97
  "jsdom": "^29.1.1",
115
- "oxlint": "^1.69.0",
116
98
  "react": "^19.2.7",
117
99
  "react-dom": "^19.2.7",
118
- "react-markdown": "^10.1.0",
119
- "remark-gfm": "^4.0.1",
120
100
  "rolldown": "~1.1.5",
121
101
  "rolldown-plugin-dts": "^0.27.14",
122
- "shiki": "^4.4.3",
123
102
  "typescript": "~7.0.2",
124
103
  "vite": "^8.1.5",
125
104
  "vitest": "^4.1.10",
126
105
  "zod": "^4.4.3"
127
- },
128
- "dependencies": {
129
- "@tanstack/match-sorter-utils": "^8.19.4"
130
106
  }
131
107
  }