@jielga/tmdatagrid 0.2.0 → 0.5.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 (50) hide show
  1. package/dist/index.d.ts +1337 -94
  2. package/dist/index.js +2812 -1189
  3. package/dist/index.js.map +1 -1
  4. package/dist/styles.css +1 -1
  5. package/package.json +2 -2
  6. package/skills/columns/SKILL.md +1 -1
  7. package/skills/features/SKILL.md +61 -11
  8. package/skills/getting-started/SKILL.md +41 -6
  9. package/skills/options/SKILL.md +12 -2
  10. package/skills/server-side/SKILL.md +1 -1
  11. package/src/tmdatagrid/TMDataGridContext.ts +26 -0
  12. package/src/tmdatagrid/components/TMDataGrid.module.css +21 -1
  13. package/src/tmdatagrid/components/TMDataGrid.tsx +29 -1
  14. package/src/tmdatagrid/components/TMDataGridColumnsPanel.tsx +12 -6
  15. package/src/tmdatagrid/components/TMDataGridDetailsColumn.module.css +33 -0
  16. package/src/tmdatagrid/components/TMDataGridDetailsColumn.tsx +160 -0
  17. package/src/tmdatagrid/components/TMDataGridFilterPanel.module.css +15 -0
  18. package/src/tmdatagrid/components/TMDataGridFilterPanel.tsx +192 -37
  19. package/src/tmdatagrid/components/TMDataGridFilterPills.module.css +35 -0
  20. package/src/tmdatagrid/components/TMDataGridFilterPills.tsx +136 -0
  21. package/src/tmdatagrid/components/TMDataGridFooter.module.css +8 -0
  22. package/src/tmdatagrid/components/TMDataGridFooter.tsx +63 -43
  23. package/src/tmdatagrid/components/TMDataGridGroupColumn.module.css +48 -0
  24. package/src/tmdatagrid/components/TMDataGridGroupColumn.tsx +159 -0
  25. package/src/tmdatagrid/components/TMDataGridHeaderCell.module.css +18 -0
  26. package/src/tmdatagrid/components/TMDataGridHeaderCell.tsx +249 -44
  27. package/src/tmdatagrid/components/TMDataGridSearch.tsx +122 -0
  28. package/src/tmdatagrid/components/TMDataGridSelectColumn.tsx +103 -16
  29. package/src/tmdatagrid/components/TMDataGridTable.module.css +160 -5
  30. package/src/tmdatagrid/components/TMDataGridTable.tsx +1380 -43
  31. package/src/tmdatagrid/components/TMDataGridToolbar.tsx +33 -7
  32. package/src/tmdatagrid/components/icons.ts +6 -0
  33. package/src/tmdatagrid/components/sticky.module.css +77 -16
  34. package/src/tmdatagrid/core/autosize.ts +125 -0
  35. package/src/tmdatagrid/core/capabilities.ts +122 -11
  36. package/src/tmdatagrid/core/cellExport.ts +321 -0
  37. package/src/tmdatagrid/core/cellNavigation.ts +103 -0
  38. package/src/tmdatagrid/core/cellRange.ts +118 -0
  39. package/src/tmdatagrid/core/columnOptions.ts +123 -0
  40. package/src/tmdatagrid/core/columnUtils.ts +18 -0
  41. package/src/tmdatagrid/core/expanding.ts +93 -0
  42. package/src/tmdatagrid/core/filterOperators.ts +198 -12
  43. package/src/tmdatagrid/core/grouping.ts +21 -0
  44. package/src/tmdatagrid/core/labels.ts +211 -0
  45. package/src/tmdatagrid/core/labelsSv.ts +108 -0
  46. package/src/tmdatagrid/core/persistence.ts +12 -0
  47. package/src/tmdatagrid/core/rowSelection.ts +202 -0
  48. package/src/tmdatagrid/core/summary.ts +35 -0
  49. package/src/tmdatagrid/index.ts +96 -2
  50. package/src/tmdatagrid/useTMDataGrid.tsx +558 -37
package/dist/styles.css CHANGED
@@ -1,2 +1,2 @@
1
- ._root_1jc4n_8[data-size=xs]{--dg-row-height:34px;--dg-header-height:32px;--dg-font-size:var(--mantine-font-size-xs);--dg-padding:6px}._root_1jc4n_8[data-size=sm]{--dg-row-height:42px;--dg-header-height:38px;--dg-font-size:var(--mantine-font-size-sm);--dg-padding:8px}._root_1jc4n_8[data-size=md]{--dg-row-height:52px;--dg-header-height:44px;--dg-font-size:var(--mantine-font-size-sm);--dg-padding:10px}._root_1jc4n_8[data-size=lg]{--dg-row-height:62px;--dg-header-height:52px;--dg-font-size:var(--mantine-font-size-md);--dg-padding:14px}._root_1jc4n_8[data-size=xl]{--dg-row-height:72px;--dg-header-height:60px;--dg-font-size:var(--mantine-font-size-lg);--dg-padding:18px}._root_1jc4n_8{--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);text-align:left;border:1px solid var(--mantine-color-default-border);border-radius:var(--mantine-radius-md);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}._filterPanel_1jw69_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_1jw69_16{align-items:flex-end;gap:var(--mantine-spacing-xs);display:flex}._footer_6r35a_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}._tableWrapper_utldp_2{flex-direction:column;flex:1;min-height:0;display:flex;position:relative}._scrollContainer_utldp_10{flex:1;min-height:0;position:relative;overflow:auto}._grid_utldp_18{display:grid}._headerRow_utldp_24{grid-column:1/-1;grid-template-columns:subgrid;z-index:3;display:grid;position:sticky;top:0}._bodyRow_utldp_33{--row-bg:var(--mantine-color-body);grid-column:1/-1;grid-template-columns:subgrid;align-items:stretch;display:grid}._bodyRow_utldp_33:hover{--row-bg:var(--mantine-color-default-hover)}._bodyRow_utldp_33[data-highlighted=true]{--row-bg:var(--dg-row-selected-bg)}._bodyRow_utldp_33[data-selects-on-click=true]{-webkit-user-select:none;user-select:none}._bodyRow_utldp_33:focus-visible{outline:2px solid var(--mantine-primary-color-filled);outline-offset:-2px}._bodyCell_utldp_65{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}._cellContent_utldp_63{white-space:nowrap;text-overflow:ellipsis;min-width:0;overflow:hidden}._bodyCell_utldp_65[data-align=right]{justify-content:flex-end}._bodyCell_utldp_65[data-align=center]{justify-content:center}._bodyCell_utldp_65[data-align=left] ._cellContent_utldp_63{flex:auto}._messageRow_utldp_95{justify-content:center;align-items:center;gap:var(--mantine-spacing-xs);padding:var(--mantine-spacing-xl);flex-direction:column;grid-column:1/-1;display:flex}._stickyLeft_1iqrf_11{border-right:1px solid var(--mantine-color-default-border)}._stickyRight_1iqrf_15{border-left:1px solid var(--mantine-color-default-border)}._stickyLeft_1iqrf_11:after,._stickyRight_1iqrf_15:after{content:"";pointer-events:none;width:4px;position:absolute;top:0;bottom:0}._stickyLeft_1iqrf_11:after{background:linear-gradient(90deg,var(--lightningcss-light,#00000014)var(--lightningcss-dark,#00000047),#0000);left:100%}._stickyRight_1iqrf_15:after{background:linear-gradient(270deg,var(--lightningcss-light,#00000014)var(--lightningcss-dark,#00000047),#0000);right:100%}._headerCell_1oqkm_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_1oqkm_1[data-align=right]{justify-content:flex-end}._headerCell_1oqkm_1[data-align=center]{justify-content:center}._headerCell_1oqkm_1[data-active=true] ._headerTitle_1oqkm_25{color:var(--mantine-primary-color-filled)}._headerTitle_1oqkm_25{text-overflow:ellipsis;white-space:nowrap;flex:auto;min-width:0;overflow:hidden}._headerCell_1oqkm_1[data-align=right] ._headerTitle_1oqkm_25{text-align:right}._headerCell_1oqkm_1[data-align=center] ._headerTitle_1oqkm_25{justify-content:center;display:flex}._headerActions_1oqkm_48{flex-shrink:0;align-items:center;gap:0;margin-left:auto;display:flex}._headerCell_1oqkm_1[data-align=right] ._headerActions_1oqkm_48{order:-1;margin-left:0;margin-right:auto}._headerAction_1oqkm_48{display:none}._headerCell_1oqkm_1:hover ._headerAction_1oqkm_48,._headerCell_1oqkm_1:focus-within ._headerAction_1oqkm_48,._headerAction_1oqkm_48[data-pinned-visible=true]{display:flex}._headerCellSortable_1oqkm_77{cursor:pointer}._headerCellDragging_1oqkm_84{opacity:.4}._headerCellDropBefore_1oqkm_88:before,._headerCellDropAfter_1oqkm_89:before{content:"";background-color:var(--mantine-primary-color-filled);z-index:5;pointer-events:none;width:2px;position:absolute;top:0;bottom:0}._headerCellDropBefore_1oqkm_88:before{left:0}._headerCellDropAfter_1oqkm_89:before{right:0}._columnSeparator_1oqkm_109{z-index:1;justify-content:center;width:10px;height:50%;display:flex;position:absolute;top:25%;right:0;transform:translate(50%)}._columnSeparatorResizable_1oqkm_121{cursor:col-resize;touch-action:none}._columnSeparator_1oqkm_109:after{content:"";background-color:#0000;width:1px;height:100%;transition:background-color .12s,width .12s}._headerCell_1oqkm_1:hover ._columnSeparator_1oqkm_109:after,._headerCell_1oqkm_1:focus-within ._columnSeparator_1oqkm_109:after{background-color:var(--mantine-color-default-border)}._columnSeparatorResizable_1oqkm_121:hover:after,._columnSeparatorActive_1oqkm_144:after{background-color:var(--mantine-primary-color-filled);width:2px}._menuItemActive_1oqkm_150{background-color:var(--mantine-color-default-hover);font-weight:600}._toolbar_4my8t_1{align-items:center;gap:var(--mantine-spacing-xs);padding:6px var(--mantine-spacing-xs);flex-shrink:0;min-height:42px;display:flex}._toolbarSpacer_4my8t_10{flex:1}
1
+ ._root_qequm_8[data-size=xs]{--dg-row-height:34px;--dg-header-height:32px;--dg-font-size:var(--mantine-font-size-xs);--dg-padding:6px}._root_qequm_8[data-size=sm]{--dg-row-height:42px;--dg-header-height:38px;--dg-font-size:var(--mantine-font-size-sm);--dg-padding:8px}._root_qequm_8[data-size=md]{--dg-row-height:52px;--dg-header-height:44px;--dg-font-size:var(--mantine-font-size-sm);--dg-padding:10px}._root_qequm_8[data-size=lg]{--dg-row-height:62px;--dg-header-height:52px;--dg-font-size:var(--mantine-font-size-md);--dg-padding:14px}._root_qequm_8[data-size=xl]{--dg-row-height:72px;--dg-header-height:60px;--dg-font-size:var(--mantine-font-size-lg);--dg-padding:18px}._root_qequm_8{--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-z-pinned-cell:2;--dg-z-focused-cell:3;--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(--mantine-radius-md);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}}._groupToggle_h06xv_4{width:100%;min-width:0;font-size:var(--dg-font-size);color:inherit;align-items:center;gap:6px;display:flex}._chevron_h06xv_14{color:var(--mantine-color-dimmed);flex:none;align-items:center;transition:transform .12s;display:flex}._chevron_h06xv_14[data-expanded=true]{transform:rotate(90deg)}@media (prefers-reduced-motion:reduce){._chevron_h06xv_14{transition:none}}._groupLabel_h06xv_34{white-space:nowrap;text-overflow:ellipsis;min-width:0;font-weight:600;overflow:hidden}._groupCount_h06xv_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_1yybb_3{align-items:center;gap:var(--mantine-spacing-xs);flex-wrap:wrap;display:flex}._filterPills_1yybb_3 ._pill_1yybb_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_1yybb_27{font:inherit;color:inherit;cursor:pointer}._pillLabel_1yybb_27:hover{text-decoration:underline}._footer_149ca_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_149ca_1[data-paging-suspended=true]{opacity:.55}._tableWrapper_1nt60_2{flex-direction:column;flex:1;min-height:0;display:flex;position:relative}._scrollContainer_1nt60_10{flex:1;min-height:0;position:relative;overflow:auto}._grid_1nt60_18{display:grid}._headerRow_1nt60_24{grid-column:1/-1;grid-template-columns:subgrid;z-index:var(--dg-z-header,6);display:grid;position:sticky;top:0}._bodyRow_1nt60_33{--row-bg:var(--mantine-color-body);grid-column:1/-1;grid-template-columns:subgrid;align-items:stretch;display:grid}._bodyRow_1nt60_33:hover{--row-bg:var(--mantine-color-default-hover)}._bodyRow_1nt60_33[data-grouped=true]{--row-bg:var(--dg-row-group-bg)}._bodyRow_1nt60_33[data-context-menu=true]{--row-bg:var(--mantine-color-default-hover)}._bodyRow_1nt60_33[data-selected-bg=true]{--row-bg:var(--dg-row-selected-bg)}._bodyRow_1nt60_33[data-highlighted=true]{--row-bg:var(--dg-row-highlight-bg)}._bodyRow_1nt60_33[data-selects-on-click=true]{-webkit-user-select:none;user-select:none}._bodyRow_1nt60_33:focus-visible{outline:2px solid var(--mantine-primary-color-filled);outline-offset:-2px}._bodyCell_1nt60_91{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_1nt60_91[data-control-column=true]{padding:0}._grid_1nt60_18[data-range-dragging]{-webkit-user-select:none;user-select:none}._bodyCell_1nt60_91[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_1nt60_91[data-edge-top=true]{--dg-edge-top:var(--mantine-primary-color-filled)}._bodyCell_1nt60_91[data-edge-bottom=true]{--dg-edge-bottom:var(--mantine-primary-color-filled)}._bodyCell_1nt60_91[data-edge-left=true]{--dg-edge-left:var(--mantine-primary-color-filled)}._bodyCell_1nt60_91[data-edge-right=true]{--dg-edge-right:var(--mantine-primary-color-filled)}._bodyCell_1nt60_91[data-focused=true]{outline:2px solid var(--mantine-primary-color-filled);outline-offset:-2px}._cellContent_1nt60_89{white-space:nowrap;text-overflow:ellipsis;min-width:0;overflow:hidden}._bodyCell_1nt60_91[data-align=right]{justify-content:flex-end}._bodyCell_1nt60_91[data-align=center]{justify-content:center}._bodyCell_1nt60_91[data-align=left] ._cellContent_1nt60_89{flex:auto}._detailsCell_1nt60_204{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_1nt60_215{justify-content:center;align-items:center;gap:var(--mantine-spacing-xs);padding:var(--mantine-spacing-xl);flex-direction:column;grid-column:1/-1;display:flex}._summaryRow_1nt60_228{grid-column:1/-1;grid-template-columns:subgrid;z-index:var(--dg-z-summary-row,4);display:grid;position:sticky;bottom:0}._summaryCell_1nt60_237{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_1nt60_237[data-control-column=true]{padding:0}._summaryCell_1nt60_237[data-align=right]{justify-content:flex-end}._summaryCell_1nt60_237[data-align=center]{justify-content:center}._stickyLeft_6z1iq_16:after,._stickyRight_6z1iq_17:after{content:"";pointer-events:none;width:8px;position:absolute;top:0;bottom:0}._stickyLeft_6z1iq_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_6z1iq_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_6z1iq_16:after,._stickyRight_6z1iq_17:after{--dg-sticky-edge-range:20px;opacity:0;animation-timing-function:linear;animation-fill-mode:both;animation-timeline:scroll(inline)}._stickyLeft_6z1iq_16:after{animation-name:_stickyEdgeIn_6z1iq_1;animation-range:0 var(--dg-sticky-edge-range)}._stickyRight_6z1iq_17:after{animation-name:_stickyEdgeOut_6z1iq_1;animation-range:calc(100% - var(--dg-sticky-edge-range)) 100%}}@keyframes _stickyEdgeIn_6z1iq_1{0%{opacity:0}to{opacity:1}}@keyframes _stickyEdgeOut_6z1iq_1{0%{opacity:1}to{opacity:0}}._headerCell_1b6k8_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_1b6k8_1[data-align=right]{justify-content:flex-end}._headerCell_1b6k8_1[data-align=center]{justify-content:center}._headerCell_1b6k8_1[data-control-column=true]{padding:0}._headerCell_1b6k8_1[data-active=true] ._headerTitle_1b6k8_32{color:var(--mantine-primary-color-filled)}._headerTitle_1b6k8_32{text-overflow:ellipsis;white-space:nowrap;flex:auto;min-width:0;overflow:hidden}._headerCell_1b6k8_1[data-align=right] ._headerTitle_1b6k8_32{text-align:right}._headerCell_1b6k8_1[data-align=center] ._headerTitle_1b6k8_32{justify-content:center;display:flex}._headerActions_1b6k8_55{flex-shrink:0;align-items:center;gap:0;margin-left:auto;display:flex}._headerCell_1b6k8_1[data-align=right] ._headerActions_1b6k8_55{order:-1;margin-left:0;margin-right:auto}._headerAction_1b6k8_55{display:none}._headerCell_1b6k8_1:hover ._headerAction_1b6k8_55,._headerCell_1b6k8_1:focus-within ._headerAction_1b6k8_55,._headerAction_1b6k8_55[data-pinned-visible=true]{display:flex}._headerCellSortable_1b6k8_84{cursor:pointer}._sortIndex_1b6k8_90{color:var(--mantine-color-dimmed);text-align:center;min-width:10px;font-size:10px;font-weight:600;line-height:1}._headerCellDragging_1b6k8_102{opacity:.4}._headerCellDropBefore_1b6k8_106:before,._headerCellDropAfter_1b6k8_107:before{content:"";background-color:var(--mantine-primary-color-filled);z-index:5;pointer-events:none;width:2px;position:absolute;top:0;bottom:0}._headerCellDropBefore_1b6k8_106:before{left:0}._headerCellDropAfter_1b6k8_107:before{right:0}._columnSeparator_1b6k8_127{z-index:1;justify-content:center;width:10px;height:50%;display:flex;position:absolute;top:25%;right:0;transform:translate(50%)}._columnSeparatorResizable_1b6k8_139{cursor:col-resize;touch-action:none}._columnSeparator_1b6k8_127:after{content:"";background-color:#0000;width:1px;height:100%;transition:background-color .12s,width .12s}._headerCell_1b6k8_1:hover ._columnSeparator_1b6k8_127:after,._headerCell_1b6k8_1:focus-within ._columnSeparator_1b6k8_127:after{background-color:var(--mantine-color-default-border)}._columnSeparatorResizable_1b6k8_139:hover:after,._columnSeparatorActive_1b6k8_162:after{background-color:var(--mantine-primary-color-filled);width:2px}._menuItemActive_1b6k8_168{background-color:var(--mantine-color-default-hover);font-weight:600}._toolbar_4my8t_1{align-items:center;gap:var(--mantine-spacing-xs);padding:6px var(--mantine-spacing-xs);flex-shrink:0;min-height:42px;display:flex}._toolbarSpacer_4my8t_10{flex:1}
2
2
  /*$vite$:1*/
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jielga/tmdatagrid",
3
- "version": "0.2.0",
3
+ "version": "0.5.0",
4
4
  "description": "A React data grid built on TanStack Table v9 and Mantine — always virtualized, with resizable, reorderable, sortable, filterable, hideable and pinnable columns.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -11,7 +11,7 @@
11
11
  "type": "git",
12
12
  "url": "git+https://github.com/Jielga/TMDataGrid.git"
13
13
  },
14
- "homepage": "https://github.com/Jielga/TMDataGrid#readme",
14
+ "homepage": "https://jielga.github.io/TMDataGrid/",
15
15
  "bugs": {
16
16
  "url": "https://github.com/Jielga/TMDataGrid/issues"
17
17
  },
@@ -11,7 +11,7 @@ description: >
11
11
  metadata:
12
12
  type: core
13
13
  library: '@jielga/tmdatagrid'
14
- library_version: '0.2.0'
14
+ library_version: '0.5.0'
15
15
  sources:
16
16
  - 'Jielga/TMDataGrid:src/docs/columns.md'
17
17
  - 'Jielga/TMDataGrid:src/tmdatagrid/core/filterOperators.ts'
@@ -5,19 +5,20 @@ description: >
5
5
  table and column options, and reuse the same checks in your own components.
6
6
  Covers the enableSorting/enableColumnFilters/enableHiding/enableColumnPinning/
7
7
  enableColumnResizing/enableColumnOrdering/enableRowSelection matrix, the
8
- checkbox and row selection modes behind rowSelectionMode, the
9
- highlightSelectedRows flag and the --dg-row-selected-bg colour, the
10
- default-off enablePagination switch and its three modes (none, client,
11
- manual), getGridCapabilities and getColumnCapabilities, why the features
12
- argument is required for reactivity under the React Compiler, column pinning,
13
- column ordering with moveColumn and moveColumnByStep, and always-on
8
+ selection modes behind rowSelectionMode, the highlightSelectedRows flag and
9
+ the --dg-row-selected-bg colour, the default-off enablePagination switch and
10
+ its three modes (none, client, manual), getGridCapabilities and
11
+ getColumnCapabilities, why the features argument is required for reactivity
12
+ under the React Compiler, column ordering with moveColumn and
13
+ moveColumnByStep, cell selection with arrow-key navigation, drag-selected
14
+ ranges, Ctrl+C copy and Excel-compatible CSV export, and always-on
14
15
  virtualization. Load when building a read-only grid, hiding grid chrome,
15
- enabling pagination, reordering columns from code, or writing a custom toolbar
16
- button.
16
+ enabling pagination, reordering columns from code, adding cell navigation or
17
+ copy/export, or writing a custom toolbar button.
17
18
  metadata:
18
19
  type: core
19
20
  library: '@jielga/tmdatagrid'
20
- library_version: '0.2.0'
21
+ library_version: '0.5.0'
21
22
  sources:
22
23
  - 'Jielga/TMDataGrid:src/docs/features.md'
23
24
  - 'Jielga/TMDataGrid:src/tmdatagrid/core/capabilities.ts'
@@ -242,10 +243,59 @@ const next = getStepTargetColumn({ table, columnId: "salary", direction: 1 });
242
243
  Both movers are no-ops for a move that is not allowed, including one across
243
244
  regions.
244
245
 
246
+ ## Cell selection
247
+
248
+ Off by default. `cellSelection: "single"` gives one focused cell moved with the
249
+ arrow keys; `"range"` adds a selectable rectangle, Ctrl+C and CSV export.
250
+
251
+ ```tsx
252
+ const grid = useTMDataGrid({ data, columns, cellSelection: "range" });
253
+ ```
254
+
255
+ On, the body's tab stop moves from the row to a cell (the whole grid is one Tab
256
+ stop), the container reports `role="grid"` with `gridcell` children, and cells
257
+ carry `data-focused` / `data-selected` / `data-edge-*`.
258
+
259
+ | Key | Does |
260
+ | --- | --- |
261
+ | Arrows | one cell, clamped at the edges |
262
+ | Shift+arrows | extends the rectangle from its anchor |
263
+ | PageUp / PageDown | one viewport of rows |
264
+ | Home / End, Ctrl+Home / Ctrl+End | ends of the row, corners of the grid |
265
+ | Enter or F2 | steps into the cell's control; Escape steps back out |
266
+ | Space | selects the row |
267
+ | Ctrl+C | copies the block as tab-separated text |
268
+
269
+ The body is one tab stop: controls inside body cells take `tabindex="-1"` while
270
+ cell selection is on, so Tab leaves the grid instead of walking one control per
271
+ mounted row. Enter/F2 steps in, Escape steps out, Space ticks the row from any
272
+ cell. Custom cell controls should do the same —
273
+ `tabIndex={useCellControlTabIndex()}`. Header controls are untouched.
274
+
275
+ The generated lanes (checkbox, tree, details) are selectable and navigable —
276
+ they take the tint and the outline — but never exported; a block covering only
277
+ those disables Copy and Export.
278
+
279
+ Drag across cells to select a rectangle; Shift+click extends one. The state is
280
+ `ui.state.focusedCell` and `ui.state.cellRange`, both held as `{ rowId,
281
+ columnId }` pairs so sorting and filtering carry the selection with the cells.
282
+ Move them with `ui.actions.setFocusedCell` / `setCellRange`, follow them with
283
+ `onFocusedCellChange`. One rectangle at a time.
284
+
285
+ Right-clicking inside the selection offers Copy, "Export as CSV for Excel" and
286
+ an Include headers toggle; a consumer's `rowContextMenu` items follow a divider.
287
+ The CSV is Nordic-Excel shaped — `sep=;` line, UTF-8 BOM, CRLF, semicolons,
288
+ decimal comma — and `cellExport` on `TMDataGrid.Table` changes any of that.
289
+ Generated lanes (checkbox, tree, details) are never exported, and what is
290
+ written is the cell's value rather than what it renders.
291
+
245
292
  ## Virtualization
246
293
 
247
- Always enabled and not configurable. Only rows within the viewport, plus a small
248
- overscan, are mounted, so page size does not affect how much is rendered.
294
+ Always enabled. Only rows within the viewport, plus a small overscan, are
295
+ mounted, so page size does not affect how much is rendered.
296
+
297
+ `overscan` on `useTMDataGrid` (default `6`) sets how many rows are kept mounted
298
+ on each side of the viewport — the one configurable part.
249
299
 
250
300
  Row height comes from `meta.rowHeight`, or from the `size` prop when unset. Rows
251
301
  are fixed height; an accurate value keeps scrolling precise.
@@ -4,13 +4,13 @@ description: >
4
4
  Set up TMDataGrid, a compound React data grid built on TanStack Table v9 and
5
5
  Mantine. Covers useTMDataGrid, the TMDataGrid root, context, the component
6
6
  catalog (Table, Footer, Toolbar, Spacer, SummaryCount, FilterButton,
7
- ColumnsButton, FilterPanel, ColumnsPanel), the size scale and the
7
+ ColumnsButton, FilterPanel, FilterPills, ColumnsPanel), the size scale and the
8
8
  bounded-height layout requirement. Load when adding a grid, choosing which
9
9
  parts to render, or when rows do not appear.
10
10
  metadata:
11
11
  type: core
12
12
  library: '@jielga/tmdatagrid'
13
- library_version: '0.2.0'
13
+ library_version: '0.5.0'
14
14
  sources:
15
15
  - 'Jielga/TMDataGrid:src/docs/getting-started.md'
16
16
  - 'Jielga/TMDataGrid:src/docs/components.md'
@@ -123,21 +123,24 @@ takes a fixed pixel width once resized or pinned.
123
123
 
124
124
  ## Components
125
125
 
126
- All read the grid from context and must be rendered inside `TMDataGrid`. Order
127
- and presence are up to you — the root is a plain flex column.
126
+ All read the grid from context and must be rendered inside `TMDataGrid`, except
127
+ `TMDataGrid.FilterPills`, which takes the grid as an `api` prop and can live
128
+ anywhere on the page. Order and presence are up to you — the root is a plain
129
+ flex column.
128
130
 
129
131
  | Component | Props | Notes |
130
132
  | --- | --- | --- |
131
133
  | `TMDataGrid` | `table`, `ui`, `features`, `size`, `className`, `style` | Root. Provides context. `style` also takes CSS variables: `--dg-row-selected-bg`, `--dg-row-height`, `--dg-header-height`, `--dg-font-size`, `--dg-padding`. |
132
- | `TMDataGrid.Table` | `onRowClick(row)` | Header, virtualized body, filter panel. `onRowClick` runs in addition to selection under `rowSelectionMode: "row"`. |
134
+ | `TMDataGrid.Table` | `onRowClick(row)`, `rowContextMenu` render prop, `rowContextMenuProps` | Header, virtualized body, filter panel. `onRowClick` runs in addition to selection under `rowSelectionMode: "row"`. |
133
135
  | `TMDataGrid.Footer` | `pageSizeOptions` (default `[10, 25, 50, 100]`), `pagination` render prop | Pagination controls. Renders nothing unless pagination is enabled. |
134
136
  | `TMDataGrid.Toolbar` | `children` | Flex row above the grid. |
135
137
  | `TMDataGrid.Spacer` | — | Pushes later toolbar items right. |
136
138
  | `TMDataGrid.SummaryCount` | `children` | Visible rows out of total. |
137
139
  | `TMDataGrid.FilterButton` | — | Toggles filter panel. Renders nothing if no column is filterable. |
138
140
  | `TMDataGrid.ColumnsButton` | — | Opens column manager. Renders nothing if no column is hideable. |
139
- | `TMDataGrid.FilterPanel` | — | Rendered by `.Table`; exported for custom layouts. |
141
+ | `TMDataGrid.FilterPanel` | — | Rendered by `.Table`; exported for custom layouts. Header close button, Escape, click-away, "Add filter" and "Clear all". |
140
142
  | `TMDataGrid.ColumnsPanel` | — | Rendered by `.ColumnsButton`; exported for custom layouts. |
143
+ | `TMDataGrid.FilterPills` | `api`, `size` (default `"sm"`), `showClearAll` (default `true`), `onPillClick(columnId)`, `className` | One pill per active filter, ✕ to clear it. Takes the api as a prop, so it can be rendered outside the grid. Also exported as `TMDataGridFilterPills`. |
141
144
 
142
145
  Pass the row type so `onRowClick` stays typed:
143
146
 
@@ -145,6 +148,37 @@ Pass the row type so `onRowClick` stays typed:
145
148
  <TMDataGrid.Table<Employee> onRowClick={(row) => open(row.original.id)} />
146
149
  ```
147
150
 
151
+ `rowContextMenu` fills the dropdown of a Mantine `Menu` the grid opens at the
152
+ pointer on a right-click (or a long press). The grid owns the `Menu` — opening,
153
+ positioning, and closing on Escape, outside click, body scroll or an item pick —
154
+ so the render prop only says what goes in it:
155
+
156
+ ```tsx
157
+ <TMDataGrid.Table<Employee>
158
+ rowContextMenu={({ table, row, cell, close }) => (
159
+ <>
160
+ <Menu.Label>{row.original.firstName}</Menu.Label>
161
+ <Menu.Item onClick={() => open(row.original.id)}>Open</Menu.Item>
162
+ <Menu.Item
163
+ onClick={() =>
164
+ navigator.clipboard.writeText(String(cell?.getValue() ?? ""))
165
+ }
166
+ >
167
+ Copy cell value
168
+ </Menu.Item>
169
+ </>
170
+ )}
171
+ />
172
+ ```
173
+
174
+ `cell` is the one that was right-clicked, `table` is there for actions that read
175
+ the selection, and `close` is for dropdown content that is not a `Menu.Item`
176
+ (those close themselves). Return `null` to leave a row without a menu. It is
177
+ called during render, and only for the open row, so it must stay pure — put the
178
+ work in the item handlers. Right-clicking does not change the selection or the
179
+ highlight; the row carries `data-context-menu` while its menu is open. Pass
180
+ `rowContextMenuProps` for `width`, `shadow`, `position` and the rest.
181
+
148
182
  `TMDataGrid.Footer` reads totals from `table.getRowCount()`, which prefers
149
183
  `options.rowCount`. `TMDataGrid.SummaryCount` uses `meta.totalRowCount` when
150
184
  provided, otherwise the pre-filtered row count.
@@ -194,6 +228,7 @@ mirrors them. Set `meta.rowHeight` for a height outside the scale.
194
228
  | `openColumnFilter(api, columnId)` | Adds an empty filter for a column and opens the panel. |
195
229
  | `getColumnLabel(column)` | `meta.label`, a string header, or the column id. |
196
230
  | `getColumnType(column)` | `meta.type`, defaulting to `"string"`. |
231
+ | `formatFilterLabel({ label, type, filter })` | The one-line filter description used on the pills. |
197
232
  | `moveColumn({ table, columnId, targetId, side })` | Moves a column next to another. See the `features` skill. |
198
233
  | `moveColumnByStep({ table, columnId, direction })` | Moves a column one position within its region. |
199
234
  | `SELECT_COLUMN_ID` | Id of the generated checkbox column. |
@@ -13,7 +13,7 @@ description: >
13
13
  metadata:
14
14
  type: core
15
15
  library: '@jielga/tmdatagrid'
16
- library_version: '0.2.0'
16
+ library_version: '0.5.0'
17
17
  sources:
18
18
  - 'Jielga/TMDataGrid:src/docs/use-tm-data-grid.md'
19
19
  - 'Jielga/TMDataGrid:src/tmdatagrid/useTMDataGrid.tsx'
@@ -57,6 +57,9 @@ rather than forwarded to TanStack.
57
57
  | `enableColumnResizing` | `boolean` | `true` | Enables resizing for the table. |
58
58
  | `enableColumnOrdering` | `boolean` | `true` | Enables header dragging and the move menu items. Defined by the grid. |
59
59
  | `enablePagination` | `boolean` | `false` | Enables client-side paging and the `Footer` pager. Implied by `manualPagination`. Defined by the grid. |
60
+ | `cellSelection` | `"none" \| "single" \| "range"` | `"none"` | Cell cursor; `"range"` adds a drag-selectable rectangle, Ctrl+C and CSV export. Defined by the grid. |
61
+ | `onFocusedCellChange` | `(cell \| null) => void` | – | Fires whenever the focused cell moves. |
62
+ | `overscan` | `number` | `6` | Rows the virtualizer keeps mounted above and below the viewport. Defined by the grid. |
60
63
  | `columnResizeMode` | `"onChange" \| "onEnd"` | `"onChange"` | Resize update strategy. |
61
64
  | `initialState` | `Partial<TableState>` | See below | Merged over the grid defaults. |
62
65
  | `meta` | `TMDataGridTableMeta` | `{}` | Grid configuration, see below. |
@@ -168,8 +171,15 @@ const filterPanelOpen = useSelector(grid.ui, (state) => state.filterPanelOpen);
168
171
  | `toggleColumnsPanel` | `() => void` |
169
172
  | `startColumnDrag` | `(columnId: string) => void` |
170
173
  | `endColumnDrag` | `() => void` |
174
+ | `setHighlightedRow` | `(rowId: string \| null) => void` |
175
+ | `setSelectionAnchor` | `(rowId: string \| null) => void` |
176
+ | `setFocusedCell` | `(cell: TMDataGridCellPosition \| null) => void` |
177
+ | `setCellRange` | `(range: TMDataGridCellRange \| null) => void` |
171
178
 
172
- The last two are called by the header cells while a column is being dragged, and
179
+ The last two move the cell cursor and the selected rectangle under
180
+ `cellSelection`; DOM focus follows `focusedCell` and scrolls its row into view.
181
+
182
+ `startColumnDrag` / `endColumnDrag` are called by the header cells while a column is being dragged, and
173
183
  `ui.draggedColumnId` holds the column being moved — browsers keep `dataTransfer`
174
184
  unreadable until the drop.
175
185
 
@@ -10,7 +10,7 @@ description: >
10
10
  metadata:
11
11
  type: core
12
12
  library: '@jielga/tmdatagrid'
13
- library_version: '0.2.0'
13
+ library_version: '0.5.0'
14
14
  sources:
15
15
  - 'Jielga/TMDataGrid:src/docs/server-side.md'
16
16
  - 'Jielga/TMDataGrid:src/tmdatagrid/useTMDataGrid.tsx'
@@ -30,3 +30,29 @@ export function useTMDataGridContext(): TMDataGridContextValue {
30
30
  }
31
31
  return api as TMDataGridContextValue;
32
32
  }
33
+
34
+ /**
35
+ * What a control inside a *body* cell should put in its `tabIndex`.
36
+ *
37
+ * `-1` once cell selection is on, and this is what makes the promise of one tab
38
+ * stop true. Without it the browser walks Tab into the checkbox of every
39
+ * mounted row — a grid showing twenty rows would be twenty tab stops, and
40
+ * scrolling would change how many. Enter or F2 steps into the cell instead,
41
+ * which reaches a `-1` control perfectly well.
42
+ *
43
+ * Header controls are not covered: the header row is not part of cell
44
+ * navigation, so its sort buttons and menus stay in the tab order, where they
45
+ * are the only way to reach them.
46
+ *
47
+ * A custom cell renderer with a control in it wants the same:
48
+ *
49
+ * ```tsx
50
+ * cell: ({ row }) => (
51
+ * <Button tabIndex={useCellControlTabIndex()} onClick={...}>Open</Button>
52
+ * )
53
+ * ```
54
+ */
55
+ export function useCellControlTabIndex(): 0 | -1 {
56
+ const { features } = useTMDataGridContext();
57
+ return features.cellSelection ? -1 : 0;
58
+ }
@@ -46,9 +46,29 @@
46
46
  --dg-header-height: 44px;
47
47
  --dg-font-size: var(--mantine-font-size-sm);
48
48
  --dg-padding: 10px;
49
- /* Background of a highlighted (selected) row. Override it on the grid:
49
+ /* Background of a selected row, when `showSelectedBackground` is on. Override
50
+ it on the grid:
50
51
  <TMDataGrid style={{ "--dg-row-selected-bg": "var(--mantine-color-blue-0)" }} /> */
51
52
  --dg-row-selected-bg: var(--mantine-primary-color-light);
53
+ /* Background of the highlighted row, under a selectionMode that has one.
54
+ Stronger than the selected background by default, since under
55
+ "checkboxAndHighlight" the two show up together. */
56
+ --dg-row-highlight-bg: var(--mantine-primary-color-light-hover);
57
+ /* Background of a group row. Transparent by default — the bold label and the
58
+ chevron are enough, and with the tree collapsed every row on screen is a
59
+ group row, so a tint would grey out the whole grid. Set it to give groups a
60
+ band of their own. */
61
+ --dg-row-group-bg: var(--mantine-color-body);
62
+
63
+ /* The stacking ladder, stated once. Body cells sit at auto (0); everything
64
+ sticky stacks over them in this order, and a sticky row crossing a sticky
65
+ column takes the row's slot plus one. */
66
+ --dg-z-pinned-cell: 2;
67
+ --dg-z-focused-cell: 3;
68
+ --dg-z-summary-row: 4;
69
+ --dg-z-summary-pinned-cell: 5;
70
+ --dg-z-header: 6;
71
+ --dg-z-header-pinned-cell: 7;
52
72
 
53
73
  display: flex;
54
74
  flex-direction: column;
@@ -4,11 +4,14 @@ import classes from "./TMDataGrid.module.css";
4
4
  import { TMDataGridColumnsPanel } from "./TMDataGridColumnsPanel";
5
5
  import { TMDataGridContextProvider } from "../TMDataGridContext";
6
6
  import { TMDataGridFilterPanel } from "./TMDataGridFilterPanel";
7
+ import { TMDataGridFilterPills } from "./TMDataGridFilterPills";
7
8
  import { TMDataGridFooter } from "./TMDataGridFooter";
9
+ import { TMDataGridSearch } from "./TMDataGridSearch";
8
10
  import { TMDataGridTable } from "./TMDataGridTable";
9
11
  import {
10
12
  TMDataGridColumnsButton,
11
13
  TMDataGridFilterButton,
14
+ TMDataGridLoadingIndicator,
12
15
  TMDataGridSummaryCount,
13
16
  TMDataGridToolbar,
14
17
  TMDataGridToolbarSpacer,
@@ -59,6 +62,10 @@ function TMDataGridRoot<TData extends RowData>({
59
62
  table,
60
63
  ui,
61
64
  features,
65
+ labels,
66
+ renderDetails,
67
+ renderDetailsEstHeight,
68
+ overscan,
62
69
  size = DEFAULT_TMDATAGRID_SIZE,
63
70
  children,
64
71
  className,
@@ -71,11 +78,25 @@ function TMDataGridRoot<TData extends RowData>({
71
78
  table,
72
79
  ui,
73
80
  features,
81
+ labels,
82
+ renderDetails,
83
+ renderDetailsEstHeight,
84
+ overscan,
74
85
  size,
75
86
  rowHeight,
76
87
  controlSize: SIZE_CONTROL_SIZE[size],
77
88
  }),
78
- [table, ui, features, size, rowHeight],
89
+ [
90
+ table,
91
+ ui,
92
+ features,
93
+ labels,
94
+ renderDetails,
95
+ renderDetailsEstHeight,
96
+ overscan,
97
+ size,
98
+ rowHeight,
99
+ ],
79
100
  );
80
101
 
81
102
  return (
@@ -95,12 +116,19 @@ export const TMDataGrid = Object.assign(TMDataGridRoot, {
95
116
  Toolbar: TMDataGridToolbar,
96
117
  Spacer: TMDataGridToolbarSpacer,
97
118
  SummaryCount: TMDataGridSummaryCount,
119
+ LoadingIndicator: TMDataGridLoadingIndicator,
120
+ Search: TMDataGridSearch,
98
121
  ColumnsButton: TMDataGridColumnsButton,
99
122
  FilterButton: TMDataGridFilterButton,
100
123
  Table: TMDataGridTable,
101
124
  Footer: TMDataGridFooter,
102
125
  /** Rendered by `TMDataGrid.Table`; exported for custom layouts. */
103
126
  FilterPanel: TMDataGridFilterPanel,
127
+ /**
128
+ * Takes the grid as an `api` prop rather than from context, so it can be
129
+ * rendered outside `<TMDataGrid>` — a page header, for instance.
130
+ */
131
+ FilterPills: TMDataGridFilterPills,
104
132
  /** Rendered by `TMDataGrid.ColumnsButton`; exported for custom layouts. */
105
133
  ColumnsPanel: TMDataGridColumnsPanel,
106
134
  });
@@ -4,6 +4,7 @@ import { useState } from "react";
4
4
  import classes from "./TMDataGridColumnsPanel.module.css";
5
5
  import { useTMDataGridContext } from "../TMDataGridContext";
6
6
  import { getColumnLabel } from "../core/columnUtils";
7
+ import { GROUP_COLUMN_ID } from "./TMDataGridGroupColumn";
7
8
  import { SearchIcon } from "./icons";
8
9
 
9
10
  /**
@@ -12,7 +13,7 @@ import { SearchIcon } from "./icons";
12
13
  * `TMDataGrid.ColumnsButton`.
13
14
  */
14
15
  export function TMDataGridColumnsPanel() {
15
- const { table, controlSize } = useTMDataGridContext();
16
+ const { table, labels, controlSize } = useTMDataGridContext();
16
17
  const [search, setSearch] = useState("");
17
18
 
18
19
  const columnVisibility = useSelector(
@@ -20,7 +21,12 @@ export function TMDataGridColumnsPanel() {
20
21
  (state) => state.columnVisibility,
21
22
  );
22
23
 
23
- const columns = table.getAllLeafColumns();
24
+ // The tree column is left out rather than listed and disabled: its visibility
25
+ // is not a setting at all, it follows the grouping state, so an unchecked box
26
+ // that cannot be ticked would only invite the question.
27
+ const columns = table
28
+ .getAllLeafColumns()
29
+ .filter((column) => column.id !== GROUP_COLUMN_ID);
24
30
  const needle = search.trim().toLowerCase();
25
31
  const visibleInPanel = needle
26
32
  ? columns.filter((column) =>
@@ -39,7 +45,7 @@ export function TMDataGridColumnsPanel() {
39
45
  <TextInput
40
46
  value={search}
41
47
  onChange={(event) => setSearch(event.currentTarget.value)}
42
- placeholder="Search"
48
+ placeholder={labels.columnsSearchPlaceholder}
43
49
  leftSection={<SearchIcon size={16} stroke={1.6} />}
44
50
  size={controlSize}
45
51
  data-autofocus
@@ -62,7 +68,7 @@ export function TMDataGridColumnsPanel() {
62
68
  ))}
63
69
  {visibleInPanel.length === 0 && (
64
70
  <Text size={controlSize} c="dimmed">
65
- No columns match “{search}”
71
+ {labels.columnsNoMatch(search)}
66
72
  </Text>
67
73
  )}
68
74
  </div>
@@ -71,7 +77,7 @@ export function TMDataGridColumnsPanel() {
71
77
  <div className={classes.columnsPanelFooter}>
72
78
  <Checkbox
73
79
  size={controlSize}
74
- label="Show/Hide All"
80
+ label={labels.columnsShowHideAll}
75
81
  checked={shownCount === hideableColumns.length}
76
82
  indeterminate={shownCount > 0 && shownCount < hideableColumns.length}
77
83
  onChange={(event) =>
@@ -83,7 +89,7 @@ export function TMDataGridColumnsPanel() {
83
89
  size="compact-sm"
84
90
  onClick={() => table.resetColumnVisibility()}
85
91
  >
86
- RESET
92
+ {labels.columnsReset}
87
93
  </Button>
88
94
  </div>
89
95
  </div>
@@ -0,0 +1,33 @@
1
+ /* The details lane is a fixed track holding one control, so the button fills it
2
+ and the whole cell is the hit target. */
3
+ .detailsToggle {
4
+ display: flex;
5
+ align-items: center;
6
+ justify-content: center;
7
+ width: 100%;
8
+ height: 100%;
9
+ color: inherit;
10
+ }
11
+
12
+ .detailsToggle:hover .chevron {
13
+ color: var(--mantine-color-text);
14
+ }
15
+
16
+ .chevron {
17
+ display: flex;
18
+ align-items: center;
19
+ color: var(--mantine-color-dimmed);
20
+ transition: transform 120ms ease;
21
+ }
22
+
23
+ .chevron[data-expanded="true"] {
24
+ transform: rotate(90deg);
25
+ }
26
+
27
+ /* One icon rotated rather than two swapped: the turn is what reads as the
28
+ expand, and a swap would jump. */
29
+ @media (prefers-reduced-motion: reduce) {
30
+ .chevron {
31
+ transition: none;
32
+ }
33
+ }