@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
package/dist/styles.css CHANGED
@@ -1,2 +1,2 @@
1
- ._root_1kqd2_8[data-size=xs]{--dg-row-height:34px;--dg-header-height:32px;--dg-font-size:var(--mantine-font-size-xs);--dg-padding:6px}._root_1kqd2_8[data-size=sm]{--dg-row-height:42px;--dg-header-height:38px;--dg-font-size:var(--mantine-font-size-sm);--dg-padding:8px}._root_1kqd2_8[data-size=md]{--dg-row-height:52px;--dg-header-height:44px;--dg-font-size:var(--mantine-font-size-sm);--dg-padding:10px}._root_1kqd2_8[data-size=lg]{--dg-row-height:62px;--dg-header-height:52px;--dg-font-size:var(--mantine-font-size-md);--dg-padding:14px}._root_1kqd2_8[data-size=xl]{--dg-row-height:72px;--dg-header-height:60px;--dg-font-size:var(--mantine-font-size-lg);--dg-padding:18px}._root_1kqd2_8{--dg-radius:var(--mantine-radius-md);--dg-row-height:52px;--dg-header-height:44px;--dg-font-size:var(--mantine-font-size-sm);--dg-padding:10px;--dg-row-selected-bg:var(--mantine-primary-color-light);--dg-row-highlight-bg:var(--mantine-primary-color-light-hover);--dg-row-group-bg:var(--mantine-color-body);--dg-row-striped-bg:color-mix(in srgb, var(--mantine-color-text) 3.5%, var(--mantine-color-body));--dg-row-new-bg:color-mix(in srgb, var(--mantine-color-green-6) 8%, var(--mantine-color-body));--dg-z-focused-cell:1;--dg-z-pinned-cell:2;--dg-z-pinned-focused-cell:3;--dg-z-pinned-row:4;--dg-z-pinned-row-pinned-cell:5;--dg-z-summary-row:4;--dg-z-summary-pinned-cell:5;--dg-z-header:6;--dg-z-header-pinned-cell:7;text-align:left;border:1px solid var(--mantine-color-default-border);border-radius:var(--dg-radius);background-color:var(--mantine-color-body);flex-direction:column;min-height:0;display:flex;overflow:hidden}._columnsPanel_1492o_1{flex-direction:column;width:280px;display:flex}._columnsPanelSearch_1492o_7{padding:var(--mantine-spacing-sm)}._columnsPanelList_1492o_11{gap:var(--mantine-spacing-sm);padding:0 var(--mantine-spacing-sm) var(--mantine-spacing-sm);flex-direction:column;display:flex}._columnsPanelFooter_1492o_18{justify-content:space-between;align-items:center;gap:var(--mantine-spacing-sm);padding:var(--mantine-spacing-sm);border-top:1px solid var(--mantine-color-default-border);display:flex}._detailsToggle_n39tz_3{width:100%;height:100%;color:inherit;justify-content:center;align-items:center;display:flex}._detailsToggle_n39tz_3:hover ._chevron_n39tz_12{color:var(--mantine-color-text)}._chevron_n39tz_12{color:var(--mantine-color-dimmed);align-items:center;transition:transform .12s;display:flex}._chevron_n39tz_12[data-expanded=true]{transform:rotate(90deg)}@media (prefers-reduced-motion:reduce){._chevron_n39tz_12{transition:none}}._tableWrapper_11co7_2{flex-direction:column;flex:1;min-height:0;display:flex;position:relative}._scrollContainer_11co7_10{flex:1;min-height:0;position:relative;overflow:auto;container-type:inline-size}._grid_11co7_21{display:grid}._tabGuard_11co7_29{outline:none;width:0;height:0;position:absolute;overflow:hidden}._headerRow_11co7_39{grid-column:1/-1;grid-template-columns:subgrid;z-index:var(--dg-z-header,6);display:grid;position:sticky;top:0}._headerRow_11co7_39[data-dg-header-last]:after{content:"";pointer-events:none;background:linear-gradient(to bottom, var(--dg-header-shadow-color,#00000024), transparent);opacity:0;height:6px;position:absolute;inset:100% 0 auto}@supports (animation-timeline:scroll()){._headerRow_11co7_39[data-dg-header-last]:after{animation:linear both _dg-header-shadow_11co7_1;animation-timeline:scroll();animation-range:0 24px}}@keyframes _dg-header-shadow_11co7_1{0%{opacity:0}to{opacity:1}}._bodyRow_11co7_88{--row-bg:var(--mantine-color-body);grid-column:1/-1;grid-template-columns:subgrid;align-items:stretch;display:grid}._bodyRow_11co7_88[data-striped=true]{--row-bg:var(--dg-row-striped-bg)}._bodyRow_11co7_88[data-new=true]{--row-bg:var(--dg-row-new-bg,var(--mantine-color-body))}._bodyRow_11co7_88:hover{--row-bg:var(--mantine-color-default-hover)}._bodyRow_11co7_88[data-grouped=true]{--row-bg:var(--dg-row-group-bg)}._bodyRow_11co7_88[data-context-menu=true]{--row-bg:var(--mantine-color-default-hover)}._bodyRow_11co7_88[data-selected-bg=true]{--row-bg:var(--dg-row-selected-bg)}._bodyRow_11co7_88[data-highlighted=true]{--row-bg:var(--dg-row-highlight-bg)}._bodyRow_11co7_88[data-selects-on-click=true]{-webkit-user-select:none;user-select:none}._bodyRow_11co7_88:focus-visible{outline:2px solid var(--mantine-primary-color-filled);outline-offset:-2px}._bodyCell_11co7_162{padding:0 var(--dg-padding);border-bottom:1px solid var(--mantine-color-default-border);background-color:var(--row-bg);font-size:var(--dg-font-size);align-items:center;display:flex}._bodyCell_11co7_162[data-control-column=true]{padding:0}._grid_11co7_21[data-range-dragging]{-webkit-user-select:none;user-select:none}._bodyCell_11co7_162[data-selected=true]{background-color:color-mix(in srgb, var(--mantine-primary-color-filled) 14%, var(--row-bg));--dg-edge-top:transparent;--dg-edge-bottom:transparent;--dg-edge-left:transparent;--dg-edge-right:transparent;box-shadow:inset 0 1px 0 0 var(--dg-edge-top), inset 0 -1px 0 0 var(--dg-edge-bottom), inset 1px 0 0 0 var(--dg-edge-left), inset -1px 0 0 0 var(--dg-edge-right)}._bodyCell_11co7_162[data-edge-top=true]{--dg-edge-top:var(--mantine-primary-color-filled)}._bodyCell_11co7_162[data-edge-bottom=true]{--dg-edge-bottom:var(--mantine-primary-color-filled)}._bodyCell_11co7_162[data-edge-left=true]{--dg-edge-left:var(--mantine-primary-color-filled)}._bodyCell_11co7_162[data-edge-right=true]{--dg-edge-right:var(--mantine-primary-color-filled)}._bodyCell_11co7_162[data-focused=true]{outline:2px solid var(--mantine-primary-color-filled);outline-offset:-2px}._cellContent_11co7_160{white-space:nowrap;text-overflow:ellipsis;min-width:0;overflow:hidden}._bodyCell_11co7_162[data-editing]{padding:0 calc(var(--dg-padding) / 2)}._cellEditor_11co7_262{align-items:center;gap:2px;width:100%;min-width:0;display:flex}._cellEditorActions_11co7_271{flex:none;align-items:center;display:flex}._bodyCell_11co7_162[data-dirty],._bodyCell_11co7_162[data-invalid]{position:relative}._bodyCell_11co7_162[data-dirty]:after,._bodyCell_11co7_162[data-invalid]:after{content:"";border:5px solid #0000;border-top-color:var(--mantine-primary-color-filled);border-right-color:var(--mantine-primary-color-filled);position:absolute;top:0;right:0}._bodyCell_11co7_162[data-invalid]:after{border-top-color:var(--mantine-color-error);border-right-color:var(--mantine-color-error)}._bodyCell_11co7_162[data-align=right]{justify-content:flex-end}._bodyCell_11co7_162[data-align=center]{justify-content:center}._bodyCell_11co7_162[data-align=left] ._cellContent_11co7_160{flex:auto}._detailsCell_11co7_322{border-bottom:1px solid var(--mantine-color-default-border);background-color:var(--row-bg);padding:var(--dg-padding);-webkit-user-select:text;user-select:text;cursor:auto;grid-column:1/-1}._messageRow_11co7_15{justify-content:center;justify-self:start;align-items:center;gap:var(--mantine-spacing-xs);width:100cqi;padding:var(--mantine-spacing-xl);flex-direction:column;grid-column:1/-1;display:flex;position:sticky;left:0}._bodyRow_11co7_88[data-deleted=true]{opacity:.55;text-decoration:line-through}._bodyRow_11co7_88[data-deleted=true] ._bodyCell_11co7_162:not([data-control-column=true]){pointer-events:none}._entryBlock_11co7_372{grid-column:1/-1;grid-template-columns:subgrid;top:var(--dg-header-height,0px);z-index:var(--dg-z-pinned-row,4);display:grid;position:sticky}._entryRow_11co7_381{grid-column:1/-1;grid-template-columns:subgrid;display:grid}._pinnedTopBlock_11co7_394{grid-column:1/-1;grid-template-columns:subgrid;top:calc(var(--dg-header-height,0px) + var(--dg-entry-height,0px));z-index:var(--dg-z-pinned-row,4);display:grid;position:sticky}._pinnedTopBlock_11co7_394>:last-child>[role=cell]{border-bottom:2px solid var(--mantine-color-default-border)}._pinnedBottomBlock_11co7_407{grid-column:1/-1;grid-template-columns:subgrid;bottom:var(--dg-summary-height,0px);z-index:var(--dg-z-pinned-row,4);display:grid;position:sticky}._pinnedBottomBlock_11co7_407>:first-child>[role=cell]{border-top:2px solid var(--mantine-color-default-border)}._entryCell_11co7_420{padding:0 calc(var(--dg-padding) / 2);border-bottom:2px solid var(--mantine-primary-color-filled);background-color:var(--mantine-color-body);font-size:var(--dg-font-size);align-items:center;display:flex}._entryCell_11co7_420[data-control-column=true]{justify-content:center;padding:0}._entryRow_11co7_381[data-committed=true] ._entryCell_11co7_420{padding:0 var(--dg-padding);border-bottom:1px solid var(--mantine-color-default-border);background-color:var(--dg-row-new-bg,var(--mantine-color-body))}._entryRow_11co7_381[data-committed=true] ._entryCell_11co7_420[data-control-column=true]{padding:0}._rowStateIndicator_11co7_450{width:22px;height:22px;color:var(--mantine-color-dimmed);flex:none;justify-content:center;align-items:center;display:inline-flex}._rowStateIndicator_11co7_450[data-error]{color:var(--mantine-color-error)}._entryCell_11co7_420[data-align=right]{justify-content:flex-end}._entryCell_11co7_420[data-align=center]{justify-content:center}._matchHighlight_11co7_476{background-color:var(--dg-match-highlight-bg,var(--lightningcss-light,var(--mantine-color-yellow-2))var(--lightningcss-dark,#fab00559));color:inherit;border-radius:2px}._summaryRow_11co7_488{grid-column:1/-1;grid-template-columns:subgrid;z-index:var(--dg-z-summary-row,4);display:grid;position:sticky;bottom:0}._summaryCell_11co7_497{padding:0 var(--dg-padding);border-top:1px solid var(--mantine-color-default-border);background-color:var(--mantine-color-body);font-size:var(--dg-font-size);align-items:center;font-weight:600;display:flex}._summaryCell_11co7_497[data-control-column=true]{padding:0}._summaryCell_11co7_497[data-align=right]{justify-content:flex-end}._summaryCell_11co7_497[data-align=center]{justify-content:center}._groupToggle_1954o_4{width:100%;min-width:0;font-size:var(--dg-font-size);color:inherit;align-items:center;gap:6px;display:flex}._chevron_1954o_14{color:var(--mantine-color-dimmed);flex:none;align-items:center;transition:transform .12s;display:flex}._chevron_1954o_14[data-expanded=true]{transform:rotate(90deg)}@media (prefers-reduced-motion:reduce){._chevron_1954o_14{transition:none}}._groupLabel_1954o_34{white-space:nowrap;text-overflow:ellipsis;min-width:0;font-weight:600;overflow:hidden}._groupCount_1954o_44{color:var(--mantine-color-dimmed);font-variant-numeric:tabular-nums;flex:none}._filterPanel_izakx_4{top:calc(var(--dg-header-height) + 2px);left:var(--mantine-spacing-md);z-index:5;padding:var(--mantine-spacing-sm);border-radius:var(--mantine-radius-md);border:1px solid var(--mantine-color-default-border);background-color:var(--mantine-color-body);box-shadow:var(--mantine-shadow-md);position:absolute}._filterRow_izakx_16{align-items:flex-end;gap:var(--mantine-spacing-xs);display:flex}._filterPanelHeader_izakx_22{justify-content:space-between;align-items:center;gap:var(--mantine-spacing-md);padding-bottom:var(--mantine-spacing-xs);display:flex}._filterPanelFooter_izakx_30{justify-content:space-between;align-items:center;gap:var(--mantine-spacing-md);display:flex}._filterPills_14wl0_3{align-items:center;gap:var(--mantine-spacing-xs);flex-wrap:wrap;display:flex}._filterPills_14wl0_3 ._pill_14wl0_15{background-color:var(--lightningcss-light,var(--mantine-color-gray-1))var(--lightningcss-dark,var(--mantine-color-dark-5));color:var(--lightningcss-light,var(--mantine-color-black))var(--lightningcss-dark,var(--mantine-color-dark-0));border:1px solid var(--lightningcss-light,var(--mantine-color-gray-3))var(--lightningcss-dark,var(--mantine-color-dark-3))}._pillLabel_14wl0_27{font:inherit;color:inherit;cursor:pointer}._pillLabel_14wl0_27:hover{text-decoration:underline}._footer_te0a3_1{justify-content:flex-end;align-items:center;gap:var(--mantine-spacing-lg);padding:4px var(--mantine-spacing-sm);border-top:1px solid var(--mantine-color-default-border);flex-shrink:0;min-height:48px;display:flex}._footer_te0a3_1[data-paging-suspended=true]{opacity:.55}._stickyLeft_1vw51_16:after,._stickyRight_1vw51_17:after{content:"";pointer-events:none;width:8px;position:absolute;top:0;bottom:0}._stickyLeft_1vw51_16:after{background:linear-gradient(90deg,var(--lightningcss-light,#00000012)var(--lightningcss-dark,#00000042),var(--lightningcss-light,#00000006)var(--lightningcss-dark,#00000017) 45%,#0000);left:100%}._stickyRight_1vw51_17:after{background:linear-gradient(270deg,var(--lightningcss-light,#00000012)var(--lightningcss-dark,#00000042),var(--lightningcss-light,#00000006)var(--lightningcss-dark,#00000017) 45%,#0000);right:100%}@supports (animation-timeline:scroll()){._stickyLeft_1vw51_16:after,._stickyRight_1vw51_17:after{--dg-sticky-edge-range:20px;opacity:0;animation-timing-function:linear;animation-fill-mode:both;animation-timeline:scroll(inline)}._stickyLeft_1vw51_16:after{animation-name:_stickyEdgeIn_1vw51_1;animation-range:0 var(--dg-sticky-edge-range)}._stickyRight_1vw51_17:after{animation-name:_stickyEdgeOut_1vw51_1;animation-range:calc(100% - var(--dg-sticky-edge-range)) 100%}}@keyframes _stickyEdgeIn_1vw51_1{0%{opacity:0}to{opacity:1}}@keyframes _stickyEdgeOut_1vw51_1{0%{opacity:1}to{opacity:0}}._headerCell_188ed_1{padding:0 var(--dg-padding);min-width:0;min-height:var(--dg-header-height);font-weight:600;font-size:var(--dg-font-size);background-color:var(--mantine-color-body);border-bottom:1px solid var(--mantine-color-default-border);-webkit-user-select:none;user-select:none;align-items:center;gap:2px;display:flex;position:relative}._headerCell_188ed_1[data-align=right]{justify-content:flex-end}._headerCell_188ed_1[data-align=center]{justify-content:center}._headerCell_188ed_1[data-control-column=true]{padding:0}._headerCellFiltered_188ed_35 ._headerTitle_188ed_35{color:var(--mantine-primary-color-filled)}._headerTitle_188ed_35{text-overflow:ellipsis;white-space:nowrap;flex:auto;min-width:0;overflow:hidden}._headerCell_188ed_1[data-align=right] ._headerTitle_188ed_35{text-align:right}._headerCell_188ed_1[data-align=center] ._headerTitle_188ed_35{justify-content:center;display:flex}._headerActions_188ed_58{flex-shrink:0;align-items:center;gap:0;margin-left:auto;display:flex}._headerCell_188ed_1[data-align=right] ._headerActions_188ed_58{order:-1;margin-left:0;margin-right:auto}._headerAction_188ed_58{display:none}._headerCell_188ed_1:hover ._headerAction_188ed_58,._headerCell_188ed_1:focus-within ._headerAction_188ed_58,._headerAction_188ed_58[data-pinned-visible=true]{display:flex}._headerCellSortable_188ed_87{cursor:pointer}._sortAction_188ed_93[data-sorted=false]{opacity:.55}._sortAction_188ed_93[data-sorted=false]:hover{opacity:1}._sortIndex_188ed_103{color:var(--mantine-color-dimmed);text-align:center;min-width:10px;font-size:10px;font-weight:600;line-height:1}._headerCellDragging_188ed_115{opacity:.4}._headerCellDropBefore_188ed_119:before,._headerCellDropAfter_188ed_120:before{content:"";background-color:var(--mantine-primary-color-filled);z-index:5;pointer-events:none;width:2px;position:absolute;top:0;bottom:0}._headerCellDropBefore_188ed_119:before{left:0}._headerCellDropAfter_188ed_120:before{right:0}._columnSeparator_188ed_140{z-index:1;justify-content:center;width:10px;height:50%;display:flex;position:absolute;top:25%;right:0;transform:translate(50%)}._columnSeparatorResizable_188ed_152{cursor:col-resize;touch-action:none}._columnSeparator_188ed_140:after{content:"";background-color:#0000;width:1px;height:100%;transition:background-color .12s,width .12s}._headerCell_188ed_1:hover ._columnSeparator_188ed_140:after,._headerCell_188ed_1:focus-within ._columnSeparator_188ed_140:after{background-color:var(--mantine-color-default-border)}._columnSeparatorResizable_188ed_152:hover:after,._columnSeparatorActive_188ed_175:after{background-color:var(--mantine-primary-color-filled);width:2px}._menuItemActive_188ed_181{background-color:var(--mantine-color-default-hover);font-weight:600}._toolbar_1459c_1{align-items:center;gap:var(--mantine-spacing-xs);padding:6px var(--mantine-spacing-xs);flex-shrink:0;min-height:42px;display:flex}._toolbarSpacer_1459c_10{flex:1}._summaryCount_1459c_14{white-space:nowrap}
1
+ ._root_1kqd2_8[data-size=xs]{--dg-row-height:34px;--dg-header-height:32px;--dg-font-size:var(--mantine-font-size-xs);--dg-padding:6px}._root_1kqd2_8[data-size=sm]{--dg-row-height:42px;--dg-header-height:38px;--dg-font-size:var(--mantine-font-size-sm);--dg-padding:8px}._root_1kqd2_8[data-size=md]{--dg-row-height:52px;--dg-header-height:44px;--dg-font-size:var(--mantine-font-size-sm);--dg-padding:10px}._root_1kqd2_8[data-size=lg]{--dg-row-height:62px;--dg-header-height:52px;--dg-font-size:var(--mantine-font-size-md);--dg-padding:14px}._root_1kqd2_8[data-size=xl]{--dg-row-height:72px;--dg-header-height:60px;--dg-font-size:var(--mantine-font-size-lg);--dg-padding:18px}._root_1kqd2_8{--dg-radius:var(--mantine-radius-md);--dg-row-height:52px;--dg-header-height:44px;--dg-font-size:var(--mantine-font-size-sm);--dg-padding:10px;--dg-row-selected-bg:var(--mantine-primary-color-light);--dg-row-highlight-bg:var(--mantine-primary-color-light-hover);--dg-row-group-bg:var(--mantine-color-body);--dg-row-striped-bg:color-mix(in srgb, var(--mantine-color-text) 3.5%, var(--mantine-color-body));--dg-row-new-bg:color-mix(in srgb, var(--mantine-color-green-6) 8%, var(--mantine-color-body));--dg-z-focused-cell:1;--dg-z-pinned-cell:2;--dg-z-pinned-focused-cell:3;--dg-z-pinned-row:4;--dg-z-pinned-row-pinned-cell:5;--dg-z-summary-row:4;--dg-z-summary-pinned-cell:5;--dg-z-header:6;--dg-z-header-pinned-cell:7;text-align:left;border:1px solid var(--mantine-color-default-border);border-radius:var(--dg-radius);background-color:var(--mantine-color-body);flex-direction:column;min-height:0;display:flex;overflow:hidden}._columnsPanel_a5do5_1{flex-direction:column;width:280px;display:flex}._columnsPanelSearch_a5do5_7{padding:var(--mantine-spacing-sm)}._columnsPanelList_a5do5_11{gap:var(--mantine-spacing-sm);padding:var(--mantine-spacing-sm);flex-direction:column;display:flex}._columnsPanelSearch_a5do5_7+* ._columnsPanelList_a5do5_11{padding-top:0}._columnsPanelFooter_a5do5_22{justify-content:space-between;align-items:center;gap:var(--mantine-spacing-sm);padding:var(--mantine-spacing-sm);border-top:1px solid var(--mantine-color-default-border);display:flex}._detailsToggle_n39tz_3{width:100%;height:100%;color:inherit;justify-content:center;align-items:center;display:flex}._detailsToggle_n39tz_3:hover ._chevron_n39tz_12{color:var(--mantine-color-text)}._chevron_n39tz_12{color:var(--mantine-color-dimmed);align-items:center;transition:transform .12s;display:flex}._chevron_n39tz_12[data-expanded=true]{transform:rotate(90deg)}@media (prefers-reduced-motion:reduce){._chevron_n39tz_12{transition:none}}._tableWrapper_1qtpn_2{flex-direction:column;flex:1;min-height:0;display:flex;position:relative}._tableWrapper_1qtpn_2[data-dg-filter-sidebar]{flex-direction:row}._scrollContainer_1qtpn_17{flex:1;min-width:0;min-height:0;position:relative;overflow:auto;container-type:inline-size}._grid_1qtpn_29{align-content:start;min-height:100%;display:grid}._tabGuard_1qtpn_44{outline:none;width:0;height:0;position:absolute;overflow:hidden}._headerRow_1qtpn_54{grid-column:1/-1;grid-template-columns:subgrid;z-index:var(--dg-z-header,6);display:grid;position:sticky;top:0}._bodyRow_1qtpn_63{--row-bg:var(--mantine-color-body);grid-column:1/-1;grid-template-columns:subgrid;align-items:stretch;display:grid}._bodyRow_1qtpn_63[data-striped=true]{--row-bg:var(--dg-row-striped-bg)}._bodyRow_1qtpn_63[data-new=true]{--row-bg:var(--dg-row-new-bg,var(--mantine-color-body))}._bodyRow_1qtpn_63:hover{--row-bg:var(--mantine-color-default-hover)}._bodyRow_1qtpn_63[data-grouped=true]{--row-bg:var(--dg-row-group-bg)}._bodyRow_1qtpn_63[data-context-menu=true]{--row-bg:var(--mantine-color-default-hover)}._bodyRow_1qtpn_63[data-selected-bg=true]{--row-bg:var(--dg-row-selected-bg)}._bodyRow_1qtpn_63[data-highlighted=true]{--row-bg:var(--dg-row-highlight-bg)}._bodyRow_1qtpn_63[data-selects-on-click=true]{-webkit-user-select:none;user-select:none}._bodyRow_1qtpn_63:focus-visible{outline:2px solid var(--mantine-primary-color-filled);outline-offset:-2px}._bodyCell_1qtpn_137{padding:0 var(--dg-padding);border-bottom:1px solid var(--mantine-color-default-border);background-color:var(--mantine-color-body);background-image:linear-gradient(var(--row-bg), var(--row-bg));font-size:var(--dg-font-size);align-items:center;display:flex}._bodyCell_1qtpn_137[data-control-column=true]{padding:0}._grid_1qtpn_29[data-range-dragging]{-webkit-user-select:none;user-select:none}._bodyCell_1qtpn_137[data-selected=true]{--dg-range-tint:color-mix(in srgb, var(--mantine-primary-color-filled) 14%, var(--row-bg));background-image:linear-gradient(var(--dg-range-tint), var(--dg-range-tint));--dg-edge-top:transparent;--dg-edge-bottom:transparent;--dg-edge-left:transparent;--dg-edge-right:transparent;box-shadow:inset 0 1px 0 0 var(--dg-edge-top), inset 0 -1px 0 0 var(--dg-edge-bottom), inset 1px 0 0 0 var(--dg-edge-left), inset -1px 0 0 0 var(--dg-edge-right)}._bodyCell_1qtpn_137[data-edge-top=true]{--dg-edge-top:var(--mantine-primary-color-filled)}._bodyCell_1qtpn_137[data-edge-bottom=true]{--dg-edge-bottom:var(--mantine-primary-color-filled)}._bodyCell_1qtpn_137[data-edge-left=true]{--dg-edge-left:var(--mantine-primary-color-filled)}._bodyCell_1qtpn_137[data-edge-right=true]{--dg-edge-right:var(--mantine-primary-color-filled)}._bodyCell_1qtpn_137[data-focused=true]{outline:2px solid var(--mantine-primary-color-filled);outline-offset:-2px}._cellContent_1qtpn_135{white-space:nowrap;text-overflow:ellipsis;min-width:0;overflow:hidden}._bodyCell_1qtpn_137[data-editing]{padding:0 calc(var(--dg-padding) / 2)}._cellEditor_1qtpn_244{align-items:center;gap:2px;width:100%;min-width:0;display:flex}._cellEditorField_1qtpn_254{flex:auto;align-items:center;min-width:0;display:flex}._cellEditorActions_1qtpn_262{flex:none;align-items:center;display:flex}._bodyCell_1qtpn_137[data-dirty],._bodyCell_1qtpn_137[data-invalid],._entryCell_1qtpn_275[data-invalid]{position:relative}._bodyCell_1qtpn_137[data-dirty]:after,._bodyCell_1qtpn_137[data-invalid]:after,._entryCell_1qtpn_275[data-invalid]:after{content:"";border:5px solid #0000;border-top-color:var(--mantine-primary-color-filled);border-right-color:var(--mantine-primary-color-filled);position:absolute;top:0;right:0}._bodyCell_1qtpn_137[data-invalid]:after,._entryCell_1qtpn_275[data-invalid]:after{border-top-color:var(--mantine-color-error);border-right-color:var(--mantine-color-error)}._bodyCell_1qtpn_137[data-align=right]{justify-content:flex-end}._bodyCell_1qtpn_137[data-align=center]{justify-content:center}._bodyCell_1qtpn_137[data-align=left] ._cellContent_1qtpn_135{flex:auto}._detailsCell_1qtpn_317{border-bottom:1px solid var(--mantine-color-default-border);background-color:var(--mantine-color-body);background-image:linear-gradient(var(--row-bg), var(--row-bg));padding:var(--dg-padding);-webkit-user-select:text;user-select:text;cursor:auto;grid-column:1/-1}._messageRow_1qtpn_23{justify-content:center;justify-self:start;align-items:center;gap:var(--mantine-spacing-xs);width:100cqi;padding:var(--mantine-spacing-xl);flex-direction:column;grid-column:1/-1;display:flex;position:sticky;left:0}._bodyRow_1qtpn_63[data-deleted=true]{opacity:.55;text-decoration:line-through}._bodyRow_1qtpn_63[data-deleted=true] ._bodyCell_1qtpn_137:not([data-control-column=true]){pointer-events:none}._entryBlock_1qtpn_369{grid-column:1/-1;grid-template-columns:subgrid;top:var(--dg-header-height,0px);z-index:var(--dg-z-pinned-row,4);display:grid;position:sticky}._entryRow_1qtpn_378{grid-column:1/-1;grid-template-columns:subgrid;display:grid}._pinnedTopBlock_1qtpn_391{grid-column:1/-1;grid-template-columns:subgrid;top:calc(var(--dg-header-height,0px) + var(--dg-entry-height,0px));z-index:var(--dg-z-pinned-row,4);display:grid;position:sticky}._pinnedTopBlock_1qtpn_391>:last-child>[role=cell]{border-bottom:2px solid var(--mantine-color-default-border)}._pinnedBottomBlock_1qtpn_404{grid-column:1/-1;grid-template-columns:subgrid;bottom:var(--dg-summary-height,0px);z-index:var(--dg-z-pinned-row,4);display:grid;position:sticky}._pinnedBottomBlock_1qtpn_404>:first-child>[role=cell]{border-top:2px solid var(--mantine-color-default-border)}._entryCell_1qtpn_275{padding:0 calc(var(--dg-padding) / 2);border-bottom:2px solid var(--mantine-primary-color-filled);background-color:var(--mantine-color-body);font-size:var(--dg-font-size);align-items:center;display:flex}._entryCell_1qtpn_275[data-control-column=true]{justify-content:center;padding:0}._entryRow_1qtpn_378[data-committed=true] ._entryCell_1qtpn_275{padding:0 var(--dg-padding);border-bottom:1px solid var(--mantine-color-default-border);background-image:linear-gradient(var(--dg-row-new-bg,transparent), var(--dg-row-new-bg,transparent))}._entryRow_1qtpn_378[data-committed=true] ._entryCell_1qtpn_275[data-control-column=true]{padding:0}._rowStateIndicator_1qtpn_453{width:22px;height:22px;color:var(--mantine-color-dimmed);flex:none;justify-content:center;align-items:center;display:inline-flex}._rowStateIndicator_1qtpn_453[data-error]{color:var(--mantine-color-error)}._entryCell_1qtpn_275[data-align=right]{justify-content:flex-end}._entryCell_1qtpn_275[data-align=center]{justify-content:center}._matchHighlight_1qtpn_479{background-color:var(--dg-match-highlight-bg,var(--lightningcss-light,var(--mantine-color-yellow-2))var(--lightningcss-dark,#fab00559));color:inherit;border-radius:2px}._summaryRow_1qtpn_500{grid-column:1/-1;grid-template-columns:subgrid;top:calc(100% - var(--dg-summary-height,0px));z-index:var(--dg-z-summary-row,4);display:grid;position:sticky;bottom:0}._summaryCell_1qtpn_510{padding:0 var(--dg-padding);border-top:1px solid var(--mantine-color-default-border);background-color:var(--mantine-color-body);font-size:var(--dg-font-size);align-items:center;font-weight:600;display:flex}._summaryCell_1qtpn_510[data-control-column=true]{padding:0}._summaryCell_1qtpn_510[data-align=right]{justify-content:flex-end}._summaryCell_1qtpn_510[data-align=center]{justify-content:center}._groupToggle_1954o_4{width:100%;min-width:0;font-size:var(--dg-font-size);color:inherit;align-items:center;gap:6px;display:flex}._chevron_1954o_14{color:var(--mantine-color-dimmed);flex:none;align-items:center;transition:transform .12s;display:flex}._chevron_1954o_14[data-expanded=true]{transform:rotate(90deg)}@media (prefers-reduced-motion:reduce){._chevron_1954o_14{transition:none}}._groupLabel_1954o_34{white-space:nowrap;text-overflow:ellipsis;min-width:0;font-weight:600;overflow:hidden}._groupCount_1954o_44{color:var(--mantine-color-dimmed);font-variant-numeric:tabular-nums;flex:none}._filterPanel_wm77f_4{gap:var(--mantine-spacing-xs);flex-direction:column;display:flex}._filterRow_wm77f_12{align-items:flex-end;gap:var(--mantine-spacing-xs);display:flex}._filterRow_wm77f_12[data-layout=stacked]{align-items:end;gap:6px var(--mantine-spacing-xs);padding:var(--mantine-spacing-xs);border:1px solid var(--mantine-color-default-border);border-radius:var(--mantine-radius-sm);grid-template-columns:1fr auto;display:grid}._filterRow_wm77f_12[data-layout=stacked] ._filterRemove_wm77f_32{grid-area:1/2}._filterRow_wm77f_12[data-layout=stacked] ._filterColumnSlot_wm77f_37{grid-area:1/1;min-width:0}._filterRow_wm77f_12[data-layout=stacked] ._filterOperatorSlot_wm77f_43,._filterRow_wm77f_12[data-layout=stacked] ._filterValueSlot_wm77f_44{grid-column:1/-1;min-width:0}._filterPanelFooter_wm77f_49{justify-content:space-between;align-items:center;gap:var(--mantine-spacing-md);display:flex}._filterPills_14wl0_3{align-items:center;gap:var(--mantine-spacing-xs);flex-wrap:wrap;display:flex}._filterPills_14wl0_3 ._pill_14wl0_15{background-color:var(--lightningcss-light,var(--mantine-color-gray-1))var(--lightningcss-dark,var(--mantine-color-dark-5));color:var(--lightningcss-light,var(--mantine-color-black))var(--lightningcss-dark,var(--mantine-color-dark-0));border:1px solid var(--lightningcss-light,var(--mantine-color-gray-3))var(--lightningcss-dark,var(--mantine-color-dark-3))}._pillLabel_14wl0_27{font:inherit;color:inherit;cursor:pointer}._pillLabel_14wl0_27:hover{text-decoration:underline}._footer_te0a3_1{justify-content:flex-end;align-items:center;gap:var(--mantine-spacing-lg);padding:4px var(--mantine-spacing-sm);border-top:1px solid var(--mantine-color-default-border);flex-shrink:0;min-height:48px;display:flex}._footer_te0a3_1[data-paging-suspended=true]{opacity:.55}._stickyLeft_1jwnm_16:after,._stickyRight_1jwnm_17:after{content:"";pointer-events:none;width:8px;position:absolute;top:0;bottom:0}._stickyLeft_1jwnm_16:after{background:linear-gradient(90deg,var(--lightningcss-light,#00000012)var(--lightningcss-dark,#00000042),var(--lightningcss-light,#00000006)var(--lightningcss-dark,#00000017) 45%,#0000);left:100%}._stickyRight_1jwnm_17:after{background:linear-gradient(270deg,var(--lightningcss-light,#00000012)var(--lightningcss-dark,#00000042),var(--lightningcss-light,#00000006)var(--lightningcss-dark,#00000017) 45%,#0000);right:100%}@supports (animation-timeline:scroll()){._stickyLeft_1jwnm_16:after,._stickyRight_1jwnm_17:after{--dg-sticky-edge-range:20px;opacity:0;animation-timing-function:linear;animation-fill-mode:both;animation-timeline:scroll(inline)}._stickyLeft_1jwnm_16:after{animation-name:_stickyEdgeIn_1jwnm_1;animation-range:0 var(--dg-sticky-edge-range)}._stickyRight_1jwnm_17:after{animation-name:_stickyEdgeOut_1jwnm_1;animation-range:calc(100% - var(--dg-sticky-edge-range)) 100%}}@keyframes _stickyEdgeIn_1jwnm_1{0%{opacity:0}to{opacity:1}}@keyframes _stickyEdgeOut_1jwnm_1{0%{opacity:1}to{opacity:0}}._headerBoundary_1jwnm_120:after{content:"";pointer-events:none;background:linear-gradient(to bottom, var(--dg-header-shadow-color,#00000024), transparent);opacity:0;height:6px;position:absolute;inset:100% 0 auto}@supports (animation-timeline:scroll()){._headerBoundary_1jwnm_120:after{animation:linear both _dgHeaderShadow_1jwnm_1;animation-timeline:scroll();animation-range:0 24px}}@keyframes _dgHeaderShadow_1jwnm_1{0%{opacity:0}to{opacity:1}}._surfaceHeader_10bcx_5{justify-content:space-between;align-items:center;gap:var(--mantine-spacing-md);padding-bottom:var(--mantine-spacing-xs);display:flex}._filterPopup_10bcx_18{top:calc(var(--dg-header-stack-height,var(--dg-header-height)) + 2px);left:var(--mantine-spacing-md);z-index:var(--dg-z-filter-popup,20);padding:var(--mantine-spacing-sm);border-radius:var(--mantine-radius-md);border:1px solid var(--mantine-color-default-border);background-color:var(--mantine-color-body);box-shadow:var(--mantine-shadow-md);position:absolute}._filterSidebar_10bcx_33{min-width:0;padding:var(--mantine-spacing-sm);background-color:var(--mantine-color-body);border-left:1px solid var(--mantine-color-default-border);flex-direction:column;flex:none;display:flex}._filterSidebar_10bcx_33[data-side=left]{border-left:none;border-right:1px solid var(--mantine-color-default-border);order:-1}._filterSidebarBody_10bcx_51{flex:1;min-height:0}._headerFilterRow_h1geg_9{grid-column:1/-1;grid-template-columns:subgrid;top:var(--dg-header-height);z-index:var(--dg-z-header,6);display:grid;position:sticky}._headerFilterCell_h1geg_18{min-width:0;padding:calc(var(--dg-padding) / 2);background-color:var(--mantine-color-body);border-bottom:1px solid var(--mantine-color-default-border);align-items:center;gap:2px;display:flex;overflow:hidden}._headerFilterCell_h1geg_18[data-control-column=true]{padding:0}._headerFilterValue_h1geg_39{flex:1;min-width:0}._headerFilterOperator_h1geg_44{flex:none}._operatorItemActive_h1geg_48{color:var(--mantine-primary-color-filled);font-weight:600}._headerCell_31huy_1{padding:0 var(--dg-padding);min-width:0;min-height:var(--dg-header-height);font-weight:600;font-size:var(--dg-font-size);background-color:var(--mantine-color-body);border-bottom:1px solid var(--mantine-color-default-border);-webkit-user-select:none;user-select:none;align-items:center;gap:2px;display:flex;position:relative}._headerCell_31huy_1[data-align=right]{justify-content:flex-end}._headerCell_31huy_1[data-align=center]{justify-content:center}._headerCell_31huy_1[data-control-column=true]{padding:0}._headerCellFiltered_31huy_35 ._headerTitle_31huy_35{color:var(--mantine-primary-color-filled)}._headerTitle_31huy_35{text-overflow:ellipsis;white-space:nowrap;flex:auto;min-width:0;overflow:hidden}._headerCell_31huy_1[data-align=right] ._headerTitle_31huy_35{text-align:right}._headerCell_31huy_1[data-align=center] ._headerTitle_31huy_35{justify-content:center;display:flex}._headerActions_31huy_58{flex-shrink:0;align-items:center;gap:0;margin-left:auto;display:flex}._headerCell_31huy_1[data-align=right] ._headerActions_31huy_58{order:-1;margin-left:0;margin-right:auto}._headerAction_31huy_58{display:none}._headerCell_31huy_1:hover ._headerAction_31huy_58,._headerCell_31huy_1:focus-within ._headerAction_31huy_58,._headerAction_31huy_58[data-pinned-visible=true]{display:flex}._headerCellSortable_31huy_87{cursor:pointer}._sortAction_31huy_93:not([data-sorted]){opacity:.55}._sortAction_31huy_93:not([data-sorted]):hover{opacity:1}._sortIndex_31huy_103{color:var(--mantine-color-dimmed);text-align:center;min-width:10px;font-size:10px;font-weight:600;line-height:1}._headerCellDragging_31huy_115{opacity:.4}._headerCellDropBefore_31huy_119:before,._headerCellDropAfter_31huy_120:before{content:"";background-color:var(--mantine-primary-color-filled);z-index:5;pointer-events:none;width:2px;position:absolute;top:0;bottom:0}._headerCellDropBefore_31huy_119:before{left:0}._headerCellDropAfter_31huy_120:before{right:0}._columnSeparator_31huy_140{z-index:1;justify-content:center;width:10px;height:50%;display:flex;position:absolute;top:25%;right:0;transform:translate(50%)}._headerCell_31huy_1:last-child ._columnSeparator_31huy_140{justify-content:flex-end;transform:none}._columnSeparatorResizable_31huy_162{cursor:col-resize;touch-action:none}._columnSeparator_31huy_140:after{content:"";background-color:#0000;width:1px;height:100%;transition:background-color .12s,width .12s}._headerCell_31huy_1:hover ._columnSeparator_31huy_140:after,._headerCell_31huy_1:focus-within ._columnSeparator_31huy_140:after{background-color:var(--mantine-color-default-border)}._columnSeparatorResizable_31huy_162:hover:after,._columnSeparatorActive_31huy_185:after{background-color:var(--mantine-primary-color-filled);width:2px}._menuItemActive_31huy_191{background-color:var(--mantine-color-default-hover);font-weight:600}._toolbar_gwk05_1{align-items:center;gap:var(--mantine-spacing-xs);padding:6px var(--mantine-spacing-xs);flex-shrink:0;min-height:42px;display:flex}._toolbar_gwk05_1[data-with-bottom-border]{border-bottom:1px solid var(--mantine-color-default-border)}._toolbarSpacer_gwk05_15{flex:1}._summaryCount_gwk05_19{white-space:nowrap}._title_199r2_1{font-weight:500}._list_199r2_5{border:1px solid var(--mantine-color-default-border);border-radius:var(--mantine-radius-default);flex-direction:column;display:flex;overflow:hidden}._listHeader_199r2_13{justify-content:space-between;align-items:center;gap:var(--mantine-spacing-sm);padding:var(--mantine-spacing-xs) var(--mantine-spacing-sm);background-color:var(--mantine-color-default-hover);border-bottom:1px solid var(--mantine-color-default-border);display:flex}._headerLabel_199r2_23{cursor:pointer;font-weight:500}._rows_199r2_28{padding:calc(var(--mantine-spacing-xs) / 2) 0;flex-direction:column;display:flex}._row_199r2_28{padding:0 var(--mantine-spacing-sm)}._row_199r2_28:hover{background-color:var(--mantine-color-default-hover)}._rowBody_199r2_45{align-items:center}._rowLabelWrapper_199r2_49{flex:1;min-width:0}._rowLabel_199r2_49{justify-content:space-between;align-items:center;gap:var(--mantine-spacing-sm);padding-block:calc(var(--mantine-spacing-xs) * .75);cursor:pointer;display:flex}._rowText_199r2_63{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}._rowHint_199r2_69{font-size:var(--mantine-font-size-xs);color:var(--mantine-color-dimmed);flex:none}._empty_199r2_75{padding:var(--mantine-spacing-xs) var(--mantine-spacing-sm)}
2
2
  /*$vite$:1*/
@@ -0,0 +1,132 @@
1
+ # Adding and deleting rows
2
+
3
+ Users add rows in entry rows under the header, and delete them from the edit lane or through `edit.deleteRow`.
4
+ Under `editing.draft` both wait in the [draft store](/docs/draft-store) until Save.
5
+
6
+ ## Adding rows
7
+
8
+ `edit.addRow()` opens an **entry row** in a sticky block under the header, so the row being typed into stays in view.
9
+ Its cells are ordinary editors over a form seeded from `newRowDefaults`.
10
+ A cell the row does not open, a display column or one with `meta.edit.enabled: false`, renders through the column's own `cell` renderer over the row as shown, following the form as it is typed, as it does on a body row.
11
+
12
+ - Enter, or the lane's ✓, commits the row: `onRowAdd` receives it, or under `draft: true` it goes into the draft store and `saveDrafts` reports it in `created`
13
+ - Escape, or ✕, discards it
14
+ - clicking away decides nothing; an entry row is row-shaped in every mode
15
+ - an entry row that was never committed is not part of a save and stays open
16
+
17
+ ```tsx
18
+ useTMDataGrid({
19
+ editing: {
20
+ mode: "row",
21
+ draft: true,
22
+ newRowDefaults: () => ({ id: 0, name: "", hired: today() }),
23
+ onSaveDrafts: async ({ updated, created, deleted }) => {
24
+ await api.saveBatch({ updated, created, deleted });
25
+ },
26
+ },
27
+ });
28
+
29
+ <Button onClick={() => grid.edit.addRow()}>Add row</Button>;
30
+ ```
31
+
32
+ Annotate the return type of `newRowDefaults`: a bare object literal widens a union field to `string`, and `(): Product => ({ ... })` keeps it checked.
33
+
34
+ `addRow(values)` overrides `newRowDefaults` key by key: `addRow()` opens the defaults, `addRow({ department: "Sales" })` opens them with `department` filled in, and passing a whole row duplicates it.
35
+ The seeded values are editable and validate like any other, and nothing reaches `onRowAdd` until the row is committed.
36
+
37
+ A grouped column has no cell on the entry row; under the default `groupedColumnMode: "remove"` it is not in the grid at all.
38
+ Seed it instead: `addRow({ region: "EMEA" })`.
39
+
40
+ ```tsx
41
+ <Button onClick={() => grid.edit.addRow({ department: "Sales", active: true })}>
42
+ Add to Sales
43
+ </Button>;
44
+
45
+ <Button onClick={() => grid.edit.addRow(selected.original)}>Duplicate</Button>;
46
+ ```
47
+
48
+ To limit how many entry rows are open at once, read the entry state from `edit.store` and gate the button:
49
+
50
+ ```tsx
51
+ const hasOpenEntry = useSelector(grid.edit.store, (state) =>
52
+ state.newRows.some((newRow) => !newRow.committed),
53
+ );
54
+
55
+ <Button disabled={hasOpenEntry} onClick={() => grid.edit.addRow()}>
56
+ Add row
57
+ </Button>;
58
+ ```
59
+
60
+ ### Committed new rows
61
+
62
+ Under `draft: true` a committed entry row leaves the entry block and becomes a body row: it carries `data-new` and `data-draft`, is tinted with `--dg-row-new-bg`, and is sorted, filtered and counted with the rest on the values it was entered with.
63
+ Set `newRowsSticky: true` to keep committed rows in the entry block until the save instead, out of the body's sort and out of the row count.
64
+ Double-click, or the lane's pencil, reopens the row into the entry block, which takes it out of the draft store until it is committed again; ✕ removes it.
65
+
66
+ ### Importing rows
67
+
68
+ `edit.addRows(rows)` opens a batch of entry rows in one write, where a loop over `addRow` is one write per row.
69
+ Each row is seeded over `newRowDefaults` like `addRow`.
70
+ `{ commit: true }` submits the rows too: rows that validate are committed, and rows that fail stay open in the entry block with their errors.
71
+ The result, `{ ok, committed, open }`, says which went which way, so the bad rows can be reported before anything is saved.
72
+ Every added row is in exactly one list, and `ok` is `true` when `open` is empty:
73
+
74
+ ```tsx
75
+ const { ok, open } = await grid.edit.addRows(parsedRows, {
76
+ commit: true,
77
+ });
78
+ if (!ok) notify(`${open.length} rows need attention`);
79
+ await grid.edit.saveDrafts();
80
+ ```
81
+
82
+ Column rules apply even though the rows never had an editor on screen: the engine runs `meta.edit.validate` at commit.
83
+ Under `draft: true` the whole import is one publish: the rows are validated together and land in the draft store in the same render that shows them, so ten thousand rows take about a second.
84
+ `saveDrafts` sends them the same way.
85
+ Without `draft: true`, `commit: true` adds each valid row through `onRowAdd`, one call per row, in the order given.
86
+
87
+ ```demo
88
+ file: editing/ImportRows.tsx
89
+ hint: Import parses the pasted rows, commits the valid ones and leaves the rest open with their errors. The second button imports ten thousand generated rows, twenty of them invalid.
90
+ height: 460
91
+ ```
92
+
93
+ ## Deleting rows
94
+
95
+ `edit.deleteRow(rowId)` calls `onRowDelete({ rowId, row })` at once; put any confirmation in that callback.
96
+ Under `draft: true` it marks the row instead: the row renders struck through and inert with `data-deleted`, the lane shows Restore, and `saveDrafts` reports the id in `deleted`.
97
+ The mark goes straight into the draft store; there is nothing to type.
98
+ `edit.restoreRow(rowId)` removes the mark, which is what the lane's Restore calls.
99
+
100
+ `deleteRow` is idempotent: deleting a marked row leaves it marked.
101
+ On an entry row, committed or not, it discards the entry, and an unknown id is a no-op.
102
+ `edit.deleteRows(rowIds)` does the same over a list in one call, so a selection can be passed as it stands: duplicates, marked rows and stale ids included.
103
+
104
+ A marked row is read-only and not selectable until it is restored:
105
+
106
+ - `begin`, `setCellValue`, `setRowValues` and `clearCell` refuse it, and the keyboard cannot open an editor on it
107
+ - an editor open on the row when it is marked is cancelled
108
+ - its checkbox is disabled, select-all skips it, and the mark drops it from `rowSelection`
109
+ - it still sorts, filters, groups, aggregates and counts, and is left out of an export
110
+ - a committed edit stays under the mark: Restore brings the row back as edited, while Save leaves the edit out of `updated`, reports the row in `deleted` only, and forgets the edit once the deletion is saved
111
+
112
+ A row the engine takes out of the table, an entry row that is discarded or saved or a marked row once its deletion is saved, leaves `rowSelection`, `expanded` and `rowPinning` with it, so a saved deletion does not leave the select-all box indeterminate.
113
+
114
+ The grid never modifies `data`: you apply adds and deletes, and the new rows arrive back through `data`.
115
+ The engine's `tempId` (`__new__1`, …) does not need to become a real id; assign one when you create the record.
116
+
117
+ ## Reference
118
+
119
+ | Name | Kind | Type | Default | What it does |
120
+ | ----------------------------- | -------------- | ------------------------------------------------ | ----------------- | ------------------------------------------------------------------------------------------------ |
121
+ | `editing.newRowsSticky` | Member | `boolean` | `false` | `draft: true` only. Keeps committed entry rows in the sticky entry block, out of the body's sort, until the save. |
122
+ | `editing.newRowDefaults` | Member | `TData \| () => TData` | – | Seeds the entry row's form. |
123
+ | `editing.onRowAdd` | Callback | `({ tempId, value }) => void \| Promise` | – | Commits an added row. |
124
+ | `TMDataGridRowAddArgs` | Type | `{ tempId, value }` | – | What `onRowAdd` receives, and one entry of the `created` that `onSaveDrafts` receives. |
125
+ | `editing.onRowDelete` | Callback | `({ rowId, row }) => void \| Promise` | – | Deletes a row. Shows the trash; under `draft: true`, `onSaveDrafts` shows it too. |
126
+ | `TMDataGridRowDeleteArgs` | Type | `{ rowId, row }` | – | What `onRowDelete` receives. |
127
+ | `TMDataGridAddRowsOptions` · `TMDataGridAddRowsResult` | Types | `{ commit? }` · `{ ok, committed, open }` | – | What `edit.addRows` takes as its options, and what it resolves. |
128
+ | `--dg-entry-height` | CSS variable | length | From `size` | Height of the sticky entry block. |
129
+ | `--dg-row-new-bg` | CSS variable | color | Green tint | Background of a committed new row, in the body or the entry block. |
130
+ | `data-deleted` | Data attribute | – | – | On a row marked for deletion under `draft: true`. |
131
+ | `data-new` | Data attribute | – | – | On a body row that is a committed new row, and on an entry row. |
132
+ | `data-committed` | Data attribute | – | – | On an entry row once it is committed, awaiting the save. Seen only under `newRowsSticky`. |
@@ -0,0 +1,119 @@
1
+ # Grid anatomy
2
+
3
+ A grid is a tree of components inside the `TMDataGrid` wrapper. Parts are
4
+ opt-in: a grid containing only `<TMDataGrid.Table />` has no toolbar, footer or
5
+ search. Add the built-in parts you need, configure them, or add components of
6
+ your own.
7
+
8
+ `useTMDataGrid` returns the props `TMDataGrid` takes:
9
+
10
+ ```tsx
11
+ const grid = useTMDataGrid({ data, columns });
12
+
13
+ <TMDataGrid {...grid} style={{ flex: 1, minHeight: 0 }} size="xs">
14
+ <TMDataGrid.Toolbar>
15
+ <TMDataGrid.Search />
16
+ <TMDataGrid.Spacer />
17
+ <TMDataGrid.FilterButton />
18
+ <TMDataGrid.Menu>
19
+ <TMDataGrid.Menu.Columns />
20
+ </TMDataGrid.Menu>
21
+ </TMDataGrid.Toolbar>
22
+ <TMDataGrid.Table />
23
+ <TMDataGrid.Footer />
24
+ </TMDataGrid>
25
+ ```
26
+
27
+ ## The parts
28
+
29
+ Every component below reads the grid from context and must be rendered inside
30
+ `TMDataGrid`.
31
+
32
+ | Component | What it is | Documented on |
33
+ | ----------------------------------------------- | ---------------------------------------------------------------------------- | ----------------------------------------------------- |
34
+ | `TMDataGrid.Table` | The scrollable surface: header, virtualized body, filter panel | This page |
35
+ | `TMDataGrid.Toolbar` | A flex row above the grid | [Toolbar](/docs/toolbar) |
36
+ | `TMDataGrid.Footer` | The pager bar below it | [Pagination](/docs/pagination) |
37
+ | `TMDataGrid.Search` | Quick search input | [Quick search](/docs/quick-search) |
38
+ | `TMDataGrid.FilterButton` · `.FilterPanel` | The filter UI | [Filtering](/docs/filtering) |
39
+ | `TMDataGrid.FilterPills` | Active filters as pills. Also exported as `TMDataGridFilterPills` | [Filtering](/docs/filtering#tmdatagridfilterpills) |
40
+ | `TMDataGrid.Menu` · `.ColumnsPanel` | The burger menu, and the column chooser | [Grid menu](/docs/menu) |
41
+ | `TMDataGrid.Spacer` | Pushes following toolbar items right | [Toolbar](/docs/toolbar) |
42
+ | `TMDataGrid.LoadingIndicator` · `.SummaryCount` | Fetch spinner, and the row count | [Loading and empty states](/docs/loading-and-empty) |
43
+ | `TMDataGrid.DraftActions` | Save and Discard for pending edits. Also exported as `TMDataGridDraftActions` | [Editing](/docs/draft-store) |
44
+
45
+ `FilterPills` is the exception: it takes the grid as an `api` prop and can be
46
+ rendered outside `TMDataGrid`, since an active-filter strip often sits above
47
+ the grid it describes.
48
+
49
+ The parts that can be rendered outside the grid are exported under both
50
+ spellings - `TMDataGrid.FilterPills` and `TMDataGridFilterPills` are the same
51
+ component, and so are the `Search` and `DraftActions` pairs. Every other part
52
+ is reached only through the `TMDataGrid` object.
53
+
54
+ ## `TMDataGrid.Table`
55
+
56
+ The scrollable surface: the header, the virtualized body and the filter panel.
57
+ Pass the row type so the handlers are typed:
58
+
59
+ ```tsx
60
+ <TMDataGrid.Table<Employee> onRowClick={(row) => open(row.original.id)} />
61
+ ```
62
+
63
+ Its props, and those of `TMDataGrid` itself, are listed once, on
64
+ [Components and hooks](/docs/components#tmdatagridtable). This page says what
65
+ each part is and which page documents it; that page says what every prop does.
66
+
67
+ Set `aria-label` on any page holding more than one grid: it is what a screen
68
+ reader announces on entry, and what `getByRole("grid", { name })` matches.
69
+
70
+ ### Which rows it renders
71
+
72
+ With pagination off (the default) rows come from
73
+ `getPrePaginatedRowModel()`: every filtered and sorted row, virtualized. With
74
+ pagination on they come from `getPaginatedRowModel()`, so a
75
+ [`manualPagination`](/docs/server-side) grid renders exactly the page the server
76
+ returned.
77
+ While a column is grouped, paging is suspended and the body holds the group rows and the leaves of open groups.
78
+ Rows [pinned](/docs/row-pinning) to an edge render in their own blocks, outside the body's list.
79
+
80
+ `getDisplayedRows(table, features)` returns the rows the body renders, in render order: the current page when paging is active, every filtered and sorted row otherwise, with pinned rows left out.
81
+ Use it for a renderer of your own, or for the rows that a cell range's `bounds` count into - see [Cell selection](/docs/cell-selection#export-the-range-from-your-own-code).
82
+ In a component, call it inside a selector with a shallow compare, so the rows follow sorting, filtering and paging:
83
+
84
+ ```tsx
85
+ import { useSelector } from "@tanstack/react-store";
86
+ import { shallow } from "@tanstack/store";
87
+
88
+ const { table, features } = useTMDataGridContext();
89
+ const rows = useSelector(table.store, () => getDisplayedRows(table, features), {
90
+ compare: shallow,
91
+ });
92
+ ```
93
+
94
+ ## What the hook returns
95
+
96
+ `useTMDataGrid` returns the table instance, the UI store, the edit engine, the
97
+ resolved labels and the feature flags, along with `resetSettings`,
98
+ `scrollToRow` and the details options it passes through to the Table. The
99
+ [complete list, with types](/docs/components#usetmdatagrid), is on Components
100
+ and hooks; the [options it takes](/docs/use-tm-data-grid) are their own page.
101
+
102
+ Read state through TanStack Store's
103
+ [`useSelector(table.store, …)`](https://tanstack.com/store/latest/docs/framework/react/reference)
104
+ rather than calling methods on `table`. The table identity is stable across
105
+ renders, so the React Compiler caches a bare method call and the component
106
+ stops updating.
107
+
108
+ ## Reference
109
+
110
+ | Name | Kind | Type | Default | What it does |
111
+ | ------------------------------------------------------------------------------------ | --------- | ------------------------------ | ------- | ---------------------------------------------------- |
112
+ | `useTMDataGrid` | Hook | `(options) => TMDataGridApi` | – | Creates the grid's table, UI state and edit engine. |
113
+ | `TMDataGrid` | Component | – | – | The root element. Provides the grid through context. |
114
+ | `TMDataGrid.Table` | Component | – | – | Header, virtualized body and filter panel. |
115
+ | `data-testid` | Prop | `string` | – | Names the grid for tests. |
116
+ | `aria-label` · `aria-labelledby` | Props | `string` | – | The grid's accessible name. |
117
+ | `useTMDataGridContext` | Hook | `() => TMDataGridContextValue` | – | The grid, from inside any child. |
118
+ | `getDisplayedRows` | Export | `(table, features) => Array<Row>` | – | The rows the body renders: the current page when paging is active, pinned rows left out. |
119
+ | `TMDataGridApi` · `TMDataGridTable` · `TMDataGridUiStore` · `TMDataGridFeatureFlags` | Exports | types | – | The hook's result and its parts. |
@@ -0,0 +1,108 @@
1
+ # A card view
2
+
3
+ The same rows as a grid, rendered as cards.
4
+ `useTMDataGrid` and `TMDataGrid` stay; `TMDataGrid.Table` is replaced by a virtualized list of Mantine `Card`s.
5
+ Search, filters, sorting, column visibility and row selection all write the same table state the grid would.
6
+
7
+ ```demo
8
+ file: recipes/CardView.tsx
9
+ hint: Search, sort by Salary, or hide Location in the menu - every card follows. 5 000 rows, a few dozen cards mounted.
10
+ height: 640
11
+ ```
12
+
13
+ ## Keep the root, replace the Table
14
+
15
+ `TMDataGrid` provides the grid through context and renders no rows of its own.
16
+ Every part except `TMDataGrid.Table` works without the Table, so the toolbar stays as it is:
17
+
18
+ ```tsx
19
+ const grid = useTMDataGrid({
20
+ data,
21
+ columns,
22
+ getRowId: (row) => String(row.id),
23
+ filters: { surface: "none" },
24
+ });
25
+
26
+ <TMDataGrid {...grid}>
27
+ <TMDataGrid.Toolbar>
28
+ <TMDataGrid.Search />
29
+ <TMDataGrid.SummaryCount />
30
+ <TMDataGrid.Menu>
31
+ <TMDataGrid.Menu.Columns />
32
+ </TMDataGrid.Menu>
33
+ </TMDataGrid.Toolbar>
34
+ <TMDataGrid.FilterPanel layout="stacked" />
35
+ <CardList table={grid.table} />
36
+ </TMDataGrid>;
37
+ ```
38
+
39
+ The following belong to `TMDataGrid.Table` and are not available without it:
40
+
41
+ - the header, with click-to-sort, resizing, dragging and the column menus
42
+ - the filter popup and sidebar - set `filters.surface` to `"none"` and place [TMDataGrid.FilterPanel](/docs/filtering#tmdatagridfilterpanel) yourself
43
+ - row details, row pinning, cell selection and editing in cells
44
+ - `scrollToRow`, which returns `false` while no Table is mounted
45
+
46
+ The demo replaces the header's sorting with a `Select` that calls `table.setSorting`.
47
+
48
+ ## Read the rows
49
+
50
+ [`getDisplayedRows`](/docs/anatomy#which-rows-it-renders) returns the rows the Table would render: filtered, sorted, and the current page when paging is on.
51
+ Call it inside `useSelector(table.store, …)` with a shallow compare:
52
+
53
+ ```tsx
54
+ const rows = useSelector(table.store, () => getDisplayedRows(table, features), {
55
+ compare: shallow,
56
+ });
57
+ ```
58
+
59
+ The table identity never changes, so the React Compiler caches a bare call and the list stops following filters and sorting.
60
+ The shallow compare re-renders the list only when the rows change.
61
+
62
+ ## Virtualize lines of cards
63
+
64
+ The virtualizer works on lines, not cards: one virtual item is one line of `perLine` cards, and `perLine` follows the width of the scroll container.
65
+
66
+ ```tsx
67
+ const perLine = Math.max(1, Math.floor((width + GAP) / (MIN_CARD_WIDTH + GAP)));
68
+
69
+ const virtualizer = useVirtualizer({
70
+ count: Math.ceil(rows.length / perLine),
71
+ getScrollElement: () => scrollRef.current,
72
+ estimateSize: () => CARD_HEIGHT + GAP,
73
+ });
74
+ ```
75
+
76
+ Each line is a CSS grid of `perLine` columns, positioned at the virtual item's `start`.
77
+ The cards have a fixed height, so `estimateSize` is exact and no line has to be measured.
78
+
79
+ ## Reuse the column definitions
80
+
81
+ A card renders its fields from `row.getVisibleCells()`, through the column's own `cell` renderer:
82
+
83
+ ```tsx
84
+ const cells = useSelector(table.store, () => row.getVisibleCells());
85
+
86
+ cells.map((cell) => (
87
+ <Group key={cell.id} justify="space-between">
88
+ <Text c="dimmed">{getColumnLabel(cell.column)}</Text>
89
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
90
+ </Group>
91
+ ));
92
+ ```
93
+
94
+ - `getColumnLabel` returns the column's `meta.label` or `header`
95
+ - the `cell` renderer is the one the grid uses, so the salary and the status badge look the same in both views
96
+ - hiding a column with `TMDataGrid.Menu.Columns` removes its line from every card
97
+
98
+ Filter out generated columns with `isGeneratedColumn(cell.column.id)`; the checkbox column is in `getVisibleCells()` while row selection is on.
99
+
100
+ ## Select a card
101
+
102
+ Subscribe each card to its own selection, so selecting one card re-renders that card only:
103
+
104
+ ```tsx
105
+ const selected = useSelector(table.store, (state) => state.rowSelection[row.id] === true);
106
+
107
+ <Checkbox checked={selected} onChange={() => row.toggleSelected()} />;
108
+ ```
@@ -0,0 +1,194 @@
1
+ # Cell selection and copy
2
+
3
+ A cell cursor moved with the arrow keys, a rectangle of cells that can be
4
+ dragged out, and Ctrl+C that pastes into Excel as cells rather than as one
5
+ string. Off by default; `cellSelection` turns it on.
6
+
7
+ ```tsx
8
+ const grid = useTMDataGrid({ data, columns, cellSelection: "range" });
9
+ ```
10
+
11
+ | Mode | What it gives |
12
+ | ---- | ------------- |
13
+ | `"none"` | Nothing. The default |
14
+ | `"single"` | One focused cell, moved with the arrow keys |
15
+ | `"range"` | As `"single"`, plus a rectangle of cells, Ctrl+C and the export menu |
16
+
17
+ ```demo
18
+ file: cells/CellSelection.tsx
19
+ hint: Tab into the grid, then arrows to move · Shift+arrows or drag to select a block. However many cells it holds, the whole grid is one tab stop.
20
+ ```
21
+
22
+ Turning it on changes three things about the body:
23
+
24
+ - The tab stop moves from the row to a cell, so the whole grid is **one** Tab
25
+ stop and the arrow keys move within it.
26
+ - The grid reports itself as a `grid` of `gridcell`s rather than a `table` of
27
+ `cell`s, which is what tells a screen reader the arrow keys are live.
28
+ - The focused cell takes `data-focused`, and the selected ones `data-selected`
29
+ and `data-edge-*`, which the stylesheet styles.
30
+
31
+ ## Keys and keyboard shortcuts
32
+
33
+ | Key | Does |
34
+ | --- | --- |
35
+ | Arrows | Moves one cell. Clamped at the edges; no wrapping |
36
+ | Shift+arrows | Extends the rectangle from its anchor (`"range"`) |
37
+ | PageUp / PageDown | Moves one viewport of rows |
38
+ | Home / End | First / last cell of the row |
39
+ | Ctrl+Home / Ctrl+End | First / last cell of the grid |
40
+ | Enter or F2 | Steps into the cell, to its checkbox, link or button |
41
+ | Escape | Steps back out, or drops the rectangle to the focused cell |
42
+ | Space | Selects the row, as a row click does under `selectionMode: "row"` |
43
+ | Ctrl+C | Copies the selection as tab-separated text |
44
+ | Ctrl+V | Not handled. The grid never writes cells from the clipboard |
45
+
46
+ There is no paste and no fill handle: values arrive through a cell editor or
47
+ through [`edit.addRows`](/docs/adding-rows#importing-rows), which is the
48
+ path for importing a block of rows at once.
49
+
50
+ A [cell editor](/docs/editing) takes over Enter and F2 when editing is on.
51
+ Otherwise they focus the first control in the cell, and the arrow keys stop
52
+ moving the cursor while focus is inside it.
53
+
54
+ ## One tab stop
55
+
56
+ The body is one tab stop in each direction.
57
+ Tab from a cell leaves the grid and Shift+Tab leaves it backwards, however many
58
+ rows are mounted and whatever those rows hold.
59
+
60
+ A control inside a body cell needs no `tabIndex`.
61
+ Enter or F2 steps into the cell and onto its first control, Escape steps back
62
+ out to the cell, and Space selects the row from any of its cells without
63
+ stepping in.
64
+
65
+ Once the focus is on a control, Tab walks the rest of that row's controls - its
66
+ open editors, the buttons in its cells, and on an open row the edit lane's save
67
+ and cancel.
68
+ Past the row's last control the cursor moves to the next row's first cell, and
69
+ Shift+Tab before its first control moves to the previous row's last cell;
70
+ neither opens the row it lands on.
71
+ From the last row, Tab leaves the grid.
72
+
73
+ Header controls are unaffected: the header row is not part of cell navigation,
74
+ so Tab is the only way to its sort buttons and menus.
75
+
76
+ ## Where the selection lives
77
+
78
+ `ui.state.focusedCell` is a `{ rowId, columnId }` pair, and `ui.state.cellRange`
79
+ is two of them: the anchor and the moving corner. They hold **ids rather than
80
+ indices**, so sorting, filtering and column reordering move the selection with
81
+ the cells instead of leaving it over whatever took those positions. A range
82
+ whose corner is filtered away is not shown, and returns when the filter is
83
+ cleared.
84
+
85
+ Move either with `ui.actions.setFocusedCell` / `setCellRange`, follow them with
86
+ `onFocusedCellChange`, or read them with TanStack Store's
87
+ [`useSelector`](https://tanstack.com/store/latest/docs/framework/react/reference):
88
+
89
+ ```tsx
90
+ const focusedCell = useSelector(grid.ui, (state) => state.focusedCell);
91
+ ```
92
+
93
+ One rectangle at a time. Ctrl+drag for a second, disjoint block is not
94
+ supported.
95
+
96
+ ### System lanes
97
+
98
+ The generated lanes (checkbox, tree, details) are part of the selection. They
99
+ take the tint and the outline, so the block stays a rectangle and the arrow keys
100
+ still reach the checkbox. They are never exported. A block covering nothing else
101
+ has nothing to copy, and the Copy and Export items are disabled.
102
+
103
+ ## Copy and export
104
+
105
+ Ctrl+C puts the selected block on the clipboard as tab-separated text with CRLF
106
+ between rows, the format Excel, Sheets and Numbers all write themselves, so a
107
+ paste lands in cells rather than in one column. Values only: no header row, the
108
+ same as Excel's own copy.
109
+
110
+ ```demo
111
+ file: cells/CopyAndExport.tsx
112
+ hint: Select a block and press Ctrl+C. It pastes into Excel as cells, not as one string.
113
+ ```
114
+
115
+ Right-clicking inside the selection opens a menu with **Copy**, **Export cells**
116
+ and an **Include headers** toggle. A right-click outside it moves the
117
+ selection there first, as a spreadsheet does. Your own
118
+ [`renderRowContextMenu`](/docs/row-interaction#context-menus) items are appended
119
+ below a divider, so turning cell selection on removes nothing. Those three items are also what that slot's `internalItems` contains.
120
+
121
+ ### The file
122
+
123
+ **Export cells** writes the rectangle in the grid's export format, by default a CSV for a Nordic Excel: a `sep=;` first line, a UTF-8 BOM, CRLF endings, semicolons between fields and a comma as the decimal mark.
124
+ The format, the file name and the header row are the grid's `exportOptions`; the formats, `meta.exportValue` and `meta.enableExport` are on [Export](/docs/export).
125
+
126
+ ```tsx
127
+ const grid = useTMDataGrid({
128
+ data,
129
+ columns,
130
+ cellSelection: "range",
131
+ exportOptions: { format: csvFormat(), fileName: "employees" },
132
+ });
133
+ ```
134
+
135
+ What is written is the cell's **value**, not what it renders, for the file and for Ctrl+C alike.
136
+ Ctrl+C writes numbers with the format's decimal mark, so what is pasted matches what is exported.
137
+
138
+ Exporting every filtered row rather than the rectangle is `TMDataGrid.Menu.Export` and `useTMDataGridExport`, on [Export](/docs/export).
139
+
140
+ ### Export the range from your own code
141
+
142
+ `resolveRangeBounds` turns `ui.state.cellRange` into the `bounds` that `buildExportData` takes: the rectangle as row and column indices, both ends included.
143
+ The row indices count into the rows the body renders, from [`getDisplayedRows`](/docs/anatomy#which-rows-it-renders), and the column indices into the visible leaf columns in render order, start-pinned, center and end-pinned, the generated lanes included.
144
+ It returns `null` when there is no range, and when a corner's row is filtered out or its column hidden.
145
+
146
+ To export the selected range from a control of your own, pass the same rows to `buildExportData` and write the result in any format:
147
+
148
+ ```tsx
149
+ const exportRange = async () => {
150
+ const { table, features, ui } = grid;
151
+ const rows = getDisplayedRows(table, features);
152
+ const columns = [
153
+ ...table.getStartVisibleLeafColumns(),
154
+ ...table.getCenterVisibleLeafColumns(),
155
+ ...table.getEndVisibleLeafColumns(),
156
+ ];
157
+ const bounds = resolveRangeBounds({
158
+ range: ui.state.cellRange,
159
+ rowIndexOf: (rowId) => rows.findIndex((row) => row.id === rowId),
160
+ columnIndexOf: (columnId) =>
161
+ columns.findIndex((column) => column.id === columnId),
162
+ });
163
+ if (bounds === null) return;
164
+
165
+ const data = buildExportData({ table, rows, bounds });
166
+ await writeExportFile(
167
+ data,
168
+ resolveExportOptions({ format: csvFormat(), fileName: "selection" }),
169
+ );
170
+ };
171
+ ```
172
+
173
+ `buildExportData` leaves out the generated lanes and every column with `meta.enableExport: false`, as the Export cells item does.
174
+ Unlike the Export cells item, `resolveRangeBounds` does not fall back to the focused cell when there is no range.
175
+
176
+ ## Reference
177
+
178
+ | Name | Kind | Type | Default | What it does |
179
+ | --- | --- | --- | --- | --- |
180
+ | `cellSelection` | Option | `"none" \| "single" \| "range"` | `"none"` | Turns the cell cursor, and the rectangle, on. |
181
+ | `TMDataGridCellSelectionMode` | Type | – | – | The type of `cellSelection`. |
182
+ | `onFocusedCellChange` | Callback | `(cell \| null) => void` | – | Follows the cursor. |
183
+ | `exportOptions` | Option | `TMDataGridExportOptions` | `DEFAULT_EXPORT_OPTIONS` | Format, file name and header row of the Export cells item. See [Export](/docs/export). |
184
+ | `ui.state.focusedCell` | UI state | `{ rowId, columnId } \| null` | `null` | The cursor. |
185
+ | `ui.state.cellRange` | UI state | `{ anchor, focus } \| null` | `null` | The rectangle's two corners. |
186
+ | `ui.actions.setFocusedCell` · `setCellRange` | UI actions | – | – | Move either from your own code. |
187
+ | `toClipboardText` · `writeClipboardText` | Exports | – | – | The pieces behind Ctrl+C. |
188
+ | `TMDataGridClipboardTextOptions` | Type | `{ decimalComma?, escapeFormulas? }` | – | The options of `toClipboardText`. |
189
+ | `buildExportData` | Export | `({ table, rows, bounds }) => TMDataGridExportData` | – | The rectangle's values, with `bounds`. |
190
+ | `resolveRangeBounds` | Export | `({ range, rowIndexOf, columnIndexOf }) => TMDataGridRangeBounds \| null` | – | The range as row and column indices, the `bounds` that `buildExportData` takes. `null` when a corner is filtered out or hidden. See [Export the range from your own code](#export-the-range-from-your-own-code). |
191
+ | `ResolveRangeBoundsArgs` · `TMDataGridRangeBounds` | Types | – · `{ top, bottom, left, right }` | – | What `resolveRangeBounds` takes, and what it returns. |
192
+ | `labels.copy` · `labels.exportCells` · `labels.includeHeaders` · `labels.cellCount` | Labels | – | – | The menu's strings. |
193
+ | `data-focused` | Data attribute | – | – | On the focused cell. |
194
+ | `data-edge-top` · `-bottom` · `-left` · `-right` | Data attributes | – | – | On cells at the rectangle's border. |