@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.
- package/README.md +5 -212
- package/dist/index.d.ts +1281 -768
- package/dist/index.js +4607 -3250
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/docs/adding-rows.md +132 -0
- package/docs/anatomy.md +119 -0
- package/docs/card-view.md +108 -0
- package/docs/cell-selection.md +194 -0
- package/docs/column-layout.md +182 -0
- package/docs/column-menu.md +66 -0
- package/docs/columns.md +268 -0
- package/docs/components.md +311 -0
- package/docs/draft-store.md +242 -0
- package/docs/editing.md +303 -0
- package/docs/editors.md +250 -0
- package/docs/export.md +319 -0
- package/docs/filtering.md +362 -0
- package/docs/getting-started.md +123 -0
- package/docs/grouping.md +165 -0
- package/docs/loading-and-empty.md +92 -0
- package/docs/localization.md +79 -0
- package/docs/menu.md +143 -0
- package/docs/migrating-to-2.md +163 -0
- package/docs/pagination.md +144 -0
- package/docs/persistence.md +114 -0
- package/docs/portfolio-rebalancer.md +94 -0
- package/docs/query-builder.md +179 -0
- package/docs/quick-search.md +84 -0
- package/docs/row-details.md +115 -0
- package/docs/row-interaction.md +149 -0
- package/docs/row-pinning.md +132 -0
- package/docs/row-selection.md +136 -0
- package/docs/row-styling.md +133 -0
- package/docs/scrolling.md +112 -0
- package/docs/server-query.md +246 -0
- package/docs/server-side.md +206 -0
- package/docs/sorting.md +101 -0
- package/docs/styling.md +126 -0
- package/docs/summary-row.md +76 -0
- package/docs/testing.md +744 -0
- package/docs/toolbar.md +161 -0
- package/docs/use-tm-data-grid.md +361 -0
- package/package.json +22 -46
- package/skills/appearance/SKILL.md +72 -19
- package/skills/cell-selection/SKILL.md +46 -47
- package/skills/columns/SKILL.md +90 -34
- package/skills/data/SKILL.md +86 -16
- package/skills/editing/SKILL.md +67 -40
- package/skills/editing/references/common-mistakes.md +77 -69
- package/skills/editing/references/editing-api.md +22 -19
- package/skills/editing/references/editors-and-validation.md +24 -17
- package/skills/filtering/SKILL.md +148 -40
- package/skills/getting-started/SKILL.md +17 -15
- package/skills/grouping/SKILL.md +31 -16
- package/skills/options/SKILL.md +7 -7
- package/skills/rows/SKILL.md +22 -18
- package/skills/server-side/SKILL.md +170 -17
- package/skills/testing/SKILL.md +150 -32
- package/skills/testing-components/SKILL.md +230 -0
- package/skills/testing-editing/SKILL.md +240 -0
- package/src/{tmdatagrid/components → components}/TMDataGrid.tsx +38 -21
- package/src/{tmdatagrid/components → components}/TMDataGridCellEditor.tsx +54 -8
- package/src/{tmdatagrid/components → components}/TMDataGridColumnsPanel.module.css +5 -1
- package/src/{tmdatagrid/components → components}/TMDataGridColumnsPanel.tsx +47 -55
- package/src/{tmdatagrid/components → components}/TMDataGridDetailsColumn.tsx +5 -51
- package/src/{tmdatagrid/components → components}/TMDataGridDraftActions.tsx +41 -24
- package/src/{tmdatagrid/components → components}/TMDataGridEditColumn.tsx +12 -59
- package/src/{tmdatagrid/components → components}/TMDataGridEntryRows.tsx +164 -92
- package/src/components/TMDataGridExportPicker.module.css +77 -0
- package/src/components/TMDataGridExportPicker.tsx +234 -0
- package/src/components/TMDataGridFilterPanel.module.css +54 -0
- package/src/components/TMDataGridFilterPanel.tsx +348 -0
- package/src/{tmdatagrid/components → components}/TMDataGridFilterPills.tsx +15 -8
- package/src/components/TMDataGridFilterSurface.module.css +54 -0
- package/src/components/TMDataGridFilterSurface.tsx +167 -0
- package/src/{tmdatagrid/components → components}/TMDataGridFooter.tsx +53 -90
- package/src/{tmdatagrid/components → components}/TMDataGridGroupColumn.tsx +5 -69
- package/src/{tmdatagrid/components → components}/TMDataGridHeaderCell.module.css +12 -2
- package/src/{tmdatagrid/components → components}/TMDataGridHeaderCell.tsx +58 -21
- package/src/components/TMDataGridHeaderFilterRow.module.css +51 -0
- package/src/components/TMDataGridHeaderFilterRow.tsx +301 -0
- package/src/components/TMDataGridMenu.tsx +357 -0
- package/src/{tmdatagrid/components → components}/TMDataGridSelectColumn.tsx +11 -48
- package/src/{tmdatagrid/components → components}/TMDataGridTable.module.css +69 -56
- package/src/{tmdatagrid/components → components}/TMDataGridTable.tsx +240 -138
- package/src/{tmdatagrid/components → components}/TMDataGridToolbar.module.css +5 -0
- package/src/components/TMDataGridToolbar.tsx +181 -0
- package/src/{tmdatagrid/components → components}/editors/TMDataGridBooleanEditor.tsx +3 -3
- package/src/{tmdatagrid/components → components}/editors/TMDataGridDateEditor.tsx +3 -3
- package/src/{tmdatagrid/components → components}/editors/TMDataGridMultiSelectEditor.tsx +3 -3
- package/src/{tmdatagrid/components → components}/editors/TMDataGridNumberEditor.tsx +3 -3
- package/src/{tmdatagrid/components → components}/editors/TMDataGridSelectEditor.tsx +3 -3
- package/src/{tmdatagrid/components → components}/editors/TMDataGridStringEditor.tsx +3 -3
- package/src/{tmdatagrid/components → components}/editors/editorShared.ts +16 -2
- package/src/{tmdatagrid/components → components}/filters/DgAutocompleteFilter.tsx +5 -4
- package/src/{tmdatagrid/components → components}/filters/DgDateRangeFilter.tsx +22 -5
- package/src/{tmdatagrid/components → components}/filters/DgRangeSliderFilter.tsx +5 -1
- package/src/{tmdatagrid/components → components}/filters/DgTriStateFilter.tsx +5 -1
- package/src/{tmdatagrid/components → components}/filters/TMDataGridFilterValueInput.tsx +44 -28
- package/src/components/filters/controlLayout.ts +32 -0
- package/src/components/filters/filterControlFor.ts +65 -0
- package/src/components/generatedColumns.tsx +187 -0
- package/src/{tmdatagrid/components → components}/icons.ts +1 -0
- package/src/{tmdatagrid/components → components}/sticky.module.css +44 -0
- package/src/components/useHideableColumns.ts +52 -0
- package/src/{tmdatagrid/core → core}/autosize.ts +5 -2
- package/src/{tmdatagrid/core → core}/columnOptions.ts +60 -8
- package/src/{tmdatagrid/core → core}/columnOrdering.ts +33 -14
- package/src/{tmdatagrid/core → core}/columnUtils.ts +44 -5
- package/src/core/controlledStateSync.ts +108 -0
- package/src/core/deletedRows.ts +34 -0
- package/src/core/dom.ts +74 -0
- package/src/{tmdatagrid/core → core}/editEngine.ts +1107 -460
- package/src/core/export.ts +704 -0
- package/src/{tmdatagrid/core → core}/filterControls.ts +38 -1
- package/src/{tmdatagrid/core → core}/filterOperators.ts +64 -1
- package/src/core/filterSurface.ts +99 -0
- package/src/{tmdatagrid/core → core}/grouping.ts +21 -0
- package/src/{tmdatagrid/core → core}/labels.ts +51 -6
- package/src/{tmdatagrid/core → core}/labelsSv.ts +27 -6
- package/src/core/pageReset.ts +120 -0
- package/src/core/pagination.ts +81 -0
- package/src/{tmdatagrid/core → core}/persistence.ts +22 -5
- package/src/{tmdatagrid/core → core}/summary.ts +20 -4
- package/src/{tmdatagrid/index.ts → index.ts} +69 -35
- package/src/{tmdatagrid/useTMDataGrid.tsx → useTMDataGrid.tsx} +428 -109
- package/src/useTMDataGridExport.ts +78 -0
- package/src/tmdatagrid/components/TMDataGridFilterPanel.module.css +0 -35
- package/src/tmdatagrid/components/TMDataGridFilterPanel.tsx +0 -354
- package/src/tmdatagrid/components/TMDataGridToolbar.tsx +0 -161
- package/src/tmdatagrid/core/cellExport.ts +0 -320
- /package/src/{tmdatagrid/TMDataGridContext.ts → TMDataGridContext.ts} +0 -0
- /package/src/{tmdatagrid/components → components}/TMDataGrid.module.css +0 -0
- /package/src/{tmdatagrid/components → components}/TMDataGridDetailsColumn.module.css +0 -0
- /package/src/{tmdatagrid/components → components}/TMDataGridFilterPills.module.css +0 -0
- /package/src/{tmdatagrid/components → components}/TMDataGridFooter.module.css +0 -0
- /package/src/{tmdatagrid/components → components}/TMDataGridGroupColumn.module.css +0 -0
- /package/src/{tmdatagrid/components → components}/TMDataGridRowNumberColumn.tsx +0 -0
- /package/src/{tmdatagrid/components → components}/TMDataGridSearch.tsx +0 -0
- /package/src/{tmdatagrid/core → core}/capabilities.ts +0 -0
- /package/src/{tmdatagrid/core → core}/cellNavigation.ts +0 -0
- /package/src/{tmdatagrid/core → core}/cellRange.ts +0 -0
- /package/src/{tmdatagrid/core → core}/controlledState.ts +0 -0
- /package/src/{tmdatagrid/core → core}/draftCellContext.ts +0 -0
- /package/src/{tmdatagrid/core → core}/editorFocus.ts +0 -0
- /package/src/{tmdatagrid/core → core}/expanding.ts +0 -0
- /package/src/{tmdatagrid/core → core}/matchHighlight.ts +0 -0
- /package/src/{tmdatagrid/core → core}/quickSearch.ts +0 -0
- /package/src/{tmdatagrid/core → core}/resizePreview.ts +0 -0
- /package/src/{tmdatagrid/core → core}/rowPinning.ts +0 -0
- /package/src/{tmdatagrid/core → core}/rowSelection.ts +0 -0
- /package/src/{tmdatagrid/core → core}/sizes.ts +0 -0
- /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`. |
|
package/docs/anatomy.md
ADDED
|
@@ -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. |
|