@jielga/tmdatagrid 0.5.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +25 -2
  2. package/dist/index.d.ts +856 -14
  3. package/dist/index.js +3370 -1546
  4. package/dist/index.js.map +1 -1
  5. package/dist/styles.css +1 -1
  6. package/dist/styles.layer.css +1 -0
  7. package/package.json +12 -2
  8. package/skills/columns/SKILL.md +1 -1
  9. package/skills/features/SKILL.md +1 -1
  10. package/skills/getting-started/SKILL.md +1 -1
  11. package/skills/options/SKILL.md +1 -1
  12. package/skills/server-side/SKILL.md +1 -1
  13. package/skills/testing/SKILL.md +198 -0
  14. package/src/tmdatagrid/components/TMDataGrid.module.css +11 -0
  15. package/src/tmdatagrid/components/TMDataGrid.tsx +34 -0
  16. package/src/tmdatagrid/components/TMDataGridCellEditor.tsx +256 -0
  17. package/src/tmdatagrid/components/TMDataGridColumnsPanel.tsx +35 -12
  18. package/src/tmdatagrid/components/TMDataGridDetailsColumn.tsx +3 -0
  19. package/src/tmdatagrid/components/TMDataGridEditActions.tsx +62 -0
  20. package/src/tmdatagrid/components/TMDataGridEditColumn.tsx +237 -0
  21. package/src/tmdatagrid/components/TMDataGridEntryRows.tsx +164 -0
  22. package/src/tmdatagrid/components/TMDataGridFilterPanel.tsx +77 -62
  23. package/src/tmdatagrid/components/TMDataGridFilterPills.tsx +6 -0
  24. package/src/tmdatagrid/components/TMDataGridFooter.tsx +10 -3
  25. package/src/tmdatagrid/components/TMDataGridGroupColumn.tsx +2 -0
  26. package/src/tmdatagrid/components/TMDataGridHeaderCell.module.css +15 -2
  27. package/src/tmdatagrid/components/TMDataGridHeaderCell.tsx +21 -4
  28. package/src/tmdatagrid/components/TMDataGridRowNumberColumn.tsx +44 -0
  29. package/src/tmdatagrid/components/TMDataGridSearch.tsx +2 -0
  30. package/src/tmdatagrid/components/TMDataGridSelectColumn.tsx +3 -0
  31. package/src/tmdatagrid/components/TMDataGridTable.module.css +206 -0
  32. package/src/tmdatagrid/components/TMDataGridTable.tsx +841 -57
  33. package/src/tmdatagrid/components/TMDataGridToolbar.tsx +12 -7
  34. package/src/tmdatagrid/components/editors/TMDataGridBooleanEditor.tsx +29 -0
  35. package/src/tmdatagrid/components/editors/TMDataGridDateEditor.tsx +57 -0
  36. package/src/tmdatagrid/components/editors/TMDataGridMultiSelectEditor.tsx +45 -0
  37. package/src/tmdatagrid/components/editors/TMDataGridNumberEditor.tsx +38 -0
  38. package/src/tmdatagrid/components/editors/TMDataGridSelectEditor.tsx +53 -0
  39. package/src/tmdatagrid/components/editors/TMDataGridStringEditor.tsx +31 -0
  40. package/src/tmdatagrid/components/editors/editorShared.ts +48 -0
  41. package/src/tmdatagrid/components/filters/DgAutocompleteFilter.tsx +46 -0
  42. package/src/tmdatagrid/components/filters/DgDateRangeFilter.tsx +43 -0
  43. package/src/tmdatagrid/components/filters/DgRangeSliderFilter.tsx +52 -0
  44. package/src/tmdatagrid/components/filters/DgTriStateFilter.tsx +38 -0
  45. package/src/tmdatagrid/components/filters/TMDataGridFilterValueInput.tsx +122 -0
  46. package/src/tmdatagrid/components/icons.ts +4 -0
  47. package/src/tmdatagrid/core/autosize.ts +1 -1
  48. package/src/tmdatagrid/core/capabilities.ts +37 -1
  49. package/src/tmdatagrid/core/cellNavigation.ts +41 -0
  50. package/src/tmdatagrid/core/columnUtils.ts +31 -6
  51. package/src/tmdatagrid/core/editEngine.ts +857 -0
  52. package/src/tmdatagrid/core/filterControls.ts +45 -0
  53. package/src/tmdatagrid/core/filterOperators.ts +72 -4
  54. package/src/tmdatagrid/core/labels.ts +68 -1
  55. package/src/tmdatagrid/core/labelsSv.ts +24 -1
  56. package/src/tmdatagrid/core/matchHighlight.ts +96 -0
  57. package/src/tmdatagrid/core/persistence.ts +97 -3
  58. package/src/tmdatagrid/core/quickSearch.ts +144 -0
  59. package/src/tmdatagrid/core/rowPinning.ts +37 -0
  60. package/src/tmdatagrid/core/rowSelection.ts +10 -1
  61. package/src/tmdatagrid/index.ts +51 -0
  62. package/src/tmdatagrid/useTMDataGrid.tsx +456 -15
package/dist/styles.css CHANGED
@@ -1,2 +1,2 @@
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}
1
+ ._root_nuit2_8[data-size=xs]{--dg-row-height:34px;--dg-header-height:32px;--dg-font-size:var(--mantine-font-size-xs);--dg-padding:6px}._root_nuit2_8[data-size=sm]{--dg-row-height:42px;--dg-header-height:38px;--dg-font-size:var(--mantine-font-size-sm);--dg-padding:8px}._root_nuit2_8[data-size=md]{--dg-row-height:52px;--dg-header-height:44px;--dg-font-size:var(--mantine-font-size-sm);--dg-padding:10px}._root_nuit2_8[data-size=lg]{--dg-row-height:62px;--dg-header-height:52px;--dg-font-size:var(--mantine-font-size-md);--dg-padding:14px}._root_nuit2_8[data-size=xl]{--dg-row-height:72px;--dg-header-height:60px;--dg-font-size:var(--mantine-font-size-lg);--dg-padding:18px}._root_nuit2_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-row-striped-bg:color-mix(in srgb, var(--mantine-color-text) 3.5%, var(--mantine-color-body));--dg-z-pinned-cell:2;--dg-z-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(--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_18r3a_2{flex-direction:column;flex:1;min-height:0;display:flex;position:relative}._scrollContainer_18r3a_10{flex:1;min-height:0;position:relative;overflow:auto;container-type:inline-size}._grid_18r3a_21{display:grid}._headerRow_18r3a_27{grid-column:1/-1;grid-template-columns:subgrid;z-index:var(--dg-z-header,6);display:grid;position:sticky;top:0}._headerRow_18r3a_27[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_18r3a_27[data-dg-header-last]:after{animation:linear both _dg-header-shadow_18r3a_1;animation-timeline:scroll();animation-range:0 24px}}@keyframes _dg-header-shadow_18r3a_1{0%{opacity:0}to{opacity:1}}._bodyRow_18r3a_76{--row-bg:var(--mantine-color-body);grid-column:1/-1;grid-template-columns:subgrid;align-items:stretch;display:grid}._bodyRow_18r3a_76[data-striped=true]{--row-bg:var(--dg-row-striped-bg)}._bodyRow_18r3a_76:hover{--row-bg:var(--mantine-color-default-hover)}._bodyRow_18r3a_76[data-grouped=true]{--row-bg:var(--dg-row-group-bg)}._bodyRow_18r3a_76[data-context-menu=true]{--row-bg:var(--mantine-color-default-hover)}._bodyRow_18r3a_76[data-selected-bg=true]{--row-bg:var(--dg-row-selected-bg)}._bodyRow_18r3a_76[data-highlighted=true]{--row-bg:var(--dg-row-highlight-bg)}._bodyRow_18r3a_76[data-selects-on-click=true]{-webkit-user-select:none;user-select:none}._bodyRow_18r3a_76:focus-visible{outline:2px solid var(--mantine-primary-color-filled);outline-offset:-2px}._bodyCell_18r3a_143{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_18r3a_143[data-control-column=true]{padding:0}._grid_18r3a_21[data-range-dragging]{-webkit-user-select:none;user-select:none}._bodyCell_18r3a_143[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_18r3a_143[data-edge-top=true]{--dg-edge-top:var(--mantine-primary-color-filled)}._bodyCell_18r3a_143[data-edge-bottom=true]{--dg-edge-bottom:var(--mantine-primary-color-filled)}._bodyCell_18r3a_143[data-edge-left=true]{--dg-edge-left:var(--mantine-primary-color-filled)}._bodyCell_18r3a_143[data-edge-right=true]{--dg-edge-right:var(--mantine-primary-color-filled)}._bodyCell_18r3a_143[data-focused=true]{outline:2px solid var(--mantine-primary-color-filled);outline-offset:-2px}._cellContent_18r3a_141{white-space:nowrap;text-overflow:ellipsis;min-width:0;overflow:hidden}._bodyCell_18r3a_143[data-editing]{padding:0 calc(var(--dg-padding) / 2)}._cellEditor_18r3a_243{align-items:center;gap:2px;width:100%;min-width:0;display:flex}._cellEditorActions_18r3a_252{flex:none;align-items:center;display:flex}._bodyCell_18r3a_143[data-dirty],._bodyCell_18r3a_143[data-invalid]{position:relative}._bodyCell_18r3a_143[data-dirty]:after,._bodyCell_18r3a_143[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_18r3a_143[data-invalid]:after{border-top-color:var(--mantine-color-error);border-right-color:var(--mantine-color-error)}._bodyCell_18r3a_143[data-align=right]{justify-content:flex-end}._bodyCell_18r3a_143[data-align=center]{justify-content:center}._bodyCell_18r3a_143[data-align=left] ._cellContent_18r3a_141{flex:auto}._detailsCell_18r3a_303{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_18r3a_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_18r3a_76[data-deleted=true]{opacity:.55;pointer-events:none;text-decoration:line-through}._entryBlock_18r3a_347{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_18r3a_356{grid-column:1/-1;grid-template-columns:subgrid;display:grid}._pinnedTopBlock_18r3a_369{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_18r3a_369>:last-child>[role=cell]{border-bottom:2px solid var(--mantine-color-default-border)}._pinnedBottomBlock_18r3a_382{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_18r3a_382>:first-child>[role=cell]{border-top:2px solid var(--mantine-color-default-border)}._entryCell_18r3a_395{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_18r3a_395[data-control-column=true]{justify-content:center;padding:0}._entryCell_18r3a_395[data-align=right]{justify-content:flex-end}._entryCell_18r3a_395[data-align=center]{justify-content:center}._matchHighlight_18r3a_422{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_18r3a_434{grid-column:1/-1;grid-template-columns:subgrid;z-index:var(--dg-z-summary-row,4);display:grid;position:sticky;bottom:0}._summaryCell_18r3a_443{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_18r3a_443[data-control-column=true]{padding:0}._summaryCell_18r3a_443[data-align=right]{justify-content:flex-end}._summaryCell_18r3a_443[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_ikfbg_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_ikfbg_1[data-align=right]{justify-content:flex-end}._headerCell_ikfbg_1[data-align=center]{justify-content:center}._headerCell_ikfbg_1[data-control-column=true]{padding:0}._headerCellFiltered_ikfbg_35 ._headerTitle_ikfbg_35{color:var(--mantine-primary-color-filled)}._headerTitle_ikfbg_35{text-overflow:ellipsis;white-space:nowrap;flex:auto;min-width:0;overflow:hidden}._headerCell_ikfbg_1[data-align=right] ._headerTitle_ikfbg_35{text-align:right}._headerCell_ikfbg_1[data-align=center] ._headerTitle_ikfbg_35{justify-content:center;display:flex}._headerActions_ikfbg_58{flex-shrink:0;align-items:center;gap:0;margin-left:auto;display:flex}._headerCell_ikfbg_1[data-align=right] ._headerActions_ikfbg_58{order:-1;margin-left:0;margin-right:auto}._headerAction_ikfbg_58{display:none}._headerCell_ikfbg_1:hover ._headerAction_ikfbg_58,._headerCell_ikfbg_1:focus-within ._headerAction_ikfbg_58,._headerAction_ikfbg_58[data-pinned-visible=true]{display:flex}._headerCellSortable_ikfbg_87{cursor:pointer}._sortAction_ikfbg_93[data-sorted=false]{opacity:.55}._sortAction_ikfbg_93[data-sorted=false]:hover{opacity:1}._sortIndex_ikfbg_103{color:var(--mantine-color-dimmed);text-align:center;min-width:10px;font-size:10px;font-weight:600;line-height:1}._headerCellDragging_ikfbg_115{opacity:.4}._headerCellDropBefore_ikfbg_119:before,._headerCellDropAfter_ikfbg_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_ikfbg_119:before{left:0}._headerCellDropAfter_ikfbg_120:before{right:0}._columnSeparator_ikfbg_140{z-index:1;justify-content:center;width:10px;height:50%;display:flex;position:absolute;top:25%;right:0;transform:translate(50%)}._columnSeparatorResizable_ikfbg_152{cursor:col-resize;touch-action:none}._columnSeparator_ikfbg_140:after{content:"";background-color:#0000;width:1px;height:100%;transition:background-color .12s,width .12s}._headerCell_ikfbg_1:hover ._columnSeparator_ikfbg_140:after,._headerCell_ikfbg_1:focus-within ._columnSeparator_ikfbg_140:after{background-color:var(--mantine-color-default-border)}._columnSeparatorResizable_ikfbg_152:hover:after,._columnSeparatorActive_ikfbg_175:after{background-color:var(--mantine-primary-color-filled);width:2px}._menuItemActive_ikfbg_181{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*/
@@ -0,0 +1 @@
1
+ @import "./styles.css" layer(tmdatagrid);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jielga/tmdatagrid",
3
- "version": "0.5.0",
3
+ "version": "1.0.0",
4
4
  "description": "A React data grid built on TanStack Table v9 and Mantine — always virtualized, with resizable, reorderable, sortable, filterable, hideable and pinnable columns.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -52,6 +52,7 @@
52
52
  "import": "./dist/index.js"
53
53
  },
54
54
  "./styles.css": "./dist/styles.css",
55
+ "./styles.layer.css": "./dist/styles.layer.css",
55
56
  "./package.json": "./package.json"
56
57
  },
57
58
  "scripts": {
@@ -71,6 +72,7 @@
71
72
  "peerDependencies": {
72
73
  "@mantine/core": "^9.4.0",
73
74
  "@tabler/icons-react": "^3.46.0",
75
+ "@tanstack/react-form": "^1.33.0",
74
76
  "@tanstack/react-store": "^0.11.0",
75
77
  "@tanstack/react-table": "^9.0.0-beta.21",
76
78
  "@tanstack/react-virtual": "^3.14.4",
@@ -83,10 +85,13 @@
83
85
  "@babel/core": "^7.29.7",
84
86
  "@changesets/changelog-github": "^0.7.0",
85
87
  "@changesets/cli": "^2.31.1",
88
+ "@mantine/code-highlight": "^9.4.1",
86
89
  "@mantine/core": "^9.4.0",
90
+ "@mantine/hooks": "^9.4.1",
87
91
  "@rolldown/plugin-babel": "^0.2.3",
88
92
  "@tabler/icons-react": "^3.46.0",
89
93
  "@tanstack/intent": "0.3.6",
94
+ "@tanstack/react-form": "^1.33.2",
90
95
  "@tanstack/react-router": "^1.170.17",
91
96
  "@tanstack/react-router-devtools": "^1.167.0",
92
97
  "@tanstack/react-store": "^0.11.0",
@@ -112,8 +117,13 @@
112
117
  "remark-gfm": "^4.0.1",
113
118
  "rolldown": "~1.1.5",
114
119
  "rolldown-plugin-dts": "^0.27.14",
120
+ "shiki": "^4.4.3",
115
121
  "typescript": "~7.0.2",
116
122
  "vite": "^8.1.5",
117
- "vitest": "^4.1.10"
123
+ "vitest": "^4.1.10",
124
+ "zod": "^4.4.3"
125
+ },
126
+ "dependencies": {
127
+ "@tanstack/match-sorter-utils": "^8.19.4"
118
128
  }
119
129
  }
@@ -11,7 +11,7 @@ description: >
11
11
  metadata:
12
12
  type: core
13
13
  library: '@jielga/tmdatagrid'
14
- library_version: '0.5.0'
14
+ library_version: '1.0.0'
15
15
  sources:
16
16
  - 'Jielga/TMDataGrid:src/docs/columns.md'
17
17
  - 'Jielga/TMDataGrid:src/tmdatagrid/core/filterOperators.ts'
@@ -18,7 +18,7 @@ description: >
18
18
  metadata:
19
19
  type: core
20
20
  library: '@jielga/tmdatagrid'
21
- library_version: '0.5.0'
21
+ library_version: '1.0.0'
22
22
  sources:
23
23
  - 'Jielga/TMDataGrid:src/docs/features.md'
24
24
  - 'Jielga/TMDataGrid:src/tmdatagrid/core/capabilities.ts'
@@ -10,7 +10,7 @@ description: >
10
10
  metadata:
11
11
  type: core
12
12
  library: '@jielga/tmdatagrid'
13
- library_version: '0.5.0'
13
+ library_version: '1.0.0'
14
14
  sources:
15
15
  - 'Jielga/TMDataGrid:src/docs/getting-started.md'
16
16
  - 'Jielga/TMDataGrid:src/docs/components.md'
@@ -13,7 +13,7 @@ description: >
13
13
  metadata:
14
14
  type: core
15
15
  library: '@jielga/tmdatagrid'
16
- library_version: '0.5.0'
16
+ library_version: '1.0.0'
17
17
  sources:
18
18
  - 'Jielga/TMDataGrid:src/docs/use-tm-data-grid.md'
19
19
  - 'Jielga/TMDataGrid:src/tmdatagrid/useTMDataGrid.tsx'
@@ -10,7 +10,7 @@ description: >
10
10
  metadata:
11
11
  type: core
12
12
  library: '@jielga/tmdatagrid'
13
- library_version: '0.5.0'
13
+ library_version: '1.0.0'
14
14
  sources:
15
15
  - 'Jielga/TMDataGrid:src/docs/server-side.md'
16
16
  - 'Jielga/TMDataGrid:src/tmdatagrid/useTMDataGrid.tsx'
@@ -0,0 +1,198 @@
1
+ ---
2
+ name: testing
3
+ description: >
4
+ Write tests against TMDataGrid from a consuming app — Playwright or React
5
+ Testing Library. Covers the data-dg-part contract, data-row-id/data-column-id
6
+ coordinates, naming a grid with data-testid, the roles and ARIA the grid
7
+ publishes, the cell/gridcell role flip under cell selection, reaching rows
8
+ past virtualization with data-dg-row-count and scrollToRow, and waiting on
9
+ aria-busy. Load when writing or fixing tests that drive a grid, or when a
10
+ selector for a row, cell or control does not resolve.
11
+ metadata:
12
+ type: core
13
+ library: '@jielga/tmdatagrid'
14
+ library_version: '1.0.0'
15
+ sources:
16
+ - 'Jielga/TMDataGrid:src/docs/testing.md'
17
+ - 'Jielga/TMDataGrid:src/tmdatagrid/components/TMDataGrid.tsx'
18
+ - 'Jielga/TMDataGrid:src/tmdatagrid/components/TMDataGridTable.tsx'
19
+ ---
20
+
21
+ # TMDataGrid — Testing
22
+
23
+ The grid names its own pieces so a suite can be written against structure
24
+ rather than against copy or class names. Three hooks carry it:
25
+
26
+ - **`data-dg-part`** — *what* an element is (`"row"`, `"filter-button"`)
27
+ - **`data-row-id` / `data-column-id`** — *which* one, using your own ids
28
+ - **roles and ARIA** — framework-neutral, and what a screen reader reads
29
+
30
+ Selectors compose: `[data-dg-part="save-row"][data-row-id="42"]`.
31
+
32
+ The grid mints no `data-testid` of its own — that attribute belongs to the app,
33
+ and Playwright's `testIdAttribute` is configurable. `@mantine/core` and
34
+ `@tanstack/*` ship none either.
35
+
36
+ ## Naming a grid
37
+
38
+ Parts repeat across grids on a page. Name the grid, scope through it:
39
+
40
+ ```tsx
41
+ <TMDataGrid {...grid} data-testid="orders">
42
+ <TMDataGrid.Table<Order> aria-label="Orders" />
43
+ </TMDataGrid>
44
+ ```
45
+
46
+ `data-testid` and `id` go on the root (which also carries `data-dg-root`);
47
+ `aria-label` or `aria-labelledby` goes on `TMDataGrid.Table`, because the
48
+ accessible name belongs to the element carrying the `grid` role.
49
+
50
+ ## Roles
51
+
52
+ | Element | Role | Key attributes |
53
+ | --- | --- | --- |
54
+ | Grid | `table`, or `grid` under cell selection | `aria-rowcount`, `aria-colcount`, `aria-busy`, `data-dg-row-count` |
55
+ | Body row | `row` | `data-row-id`, `aria-rowindex`, `data-selected`, `data-highlighted`, `data-grouped`, `data-pinned`, `data-deleted` |
56
+ | Body cell | `cell`, or `gridcell` under cell selection | `data-row-id`, `data-column-id`, `data-editing`, `data-dirty`, `data-invalid`, `data-focused` |
57
+ | Header cell | `columnheader` | `data-column-id`, `aria-sort` |
58
+
59
+ Body cells carry no `data-dg-part` — the coordinate pair already names them.
60
+
61
+ ## Parts
62
+
63
+ **Whole-grid** (unique, no coordinate needed): `toolbar`, `summary-count`,
64
+ `loading`, `search`, `search-clear`, `filter-button`, `filter-panel`,
65
+ `filter-panel-close`, `filter-add`, `filter-clear-all`, `filter-pills`,
66
+ `columns-button`, `columns-panel`, `columns-search`, `columns-toggle-all`,
67
+ `columns-reset`, `footer`, `page-size`, `page-range`, `page-prev`, `page-next`,
68
+ `summary-row`, `pinned-top`, `pinned-bottom`, `select-all`,
69
+ `details-toggle-all`, `save-all`, `discard-all`, `editor-confirm`,
70
+ `editor-cancel`, `editor-input`, `sort-index`.
71
+
72
+ **Keyed by `data-row-id`**: `row`, `entry-row`, `details`, `select-row`,
73
+ `details-toggle`, `group-toggle`, `edit-row`, `delete-row`, `save-row`,
74
+ `cancel-row`, `restore-row`, `confirm-new-row`, `discard-new-row`.
75
+
76
+ **Keyed by `data-column-id`**: `header`, `header-sort`, `header-menu`,
77
+ `header-filter`, `filter-row`, `filter-pill`, `columns-toggle`.
78
+
79
+ **Keyed by both**: `editor`.
80
+
81
+ Inside a `filter-row` the controls are `filter-column`, `filter-operator` and
82
+ `filter-value` — or `filter-value-from` / `filter-value-to` for `between`.
83
+
84
+ A column declaring `meta.filterControl` or `meta.editor` renders your own
85
+ component in that slot, so `filter-value` and `editor-input` cover the built-ins
86
+ only. `filter-row` and `editor` still hold; scope through them.
87
+
88
+ ## A page object
89
+
90
+ ```ts
91
+ import { type Locator, type Page, expect } from "@playwright/test";
92
+
93
+ type PartKey = { rowId?: string; columnId?: string };
94
+
95
+ export class DataGrid {
96
+ readonly root: Locator;
97
+ readonly grid: Locator;
98
+
99
+ constructor(page: Page, testId: string) {
100
+ this.root = page.getByTestId(testId);
101
+ this.grid = this.root.getByRole("table");
102
+ }
103
+
104
+ part(name: string, key: PartKey = {}): Locator {
105
+ return this.root.locator(
106
+ `[data-dg-part="${name}"]` +
107
+ (key.rowId === undefined ? "" : `[data-row-id="${key.rowId}"]`) +
108
+ (key.columnId === undefined ? "" : `[data-column-id="${key.columnId}"]`),
109
+ );
110
+ }
111
+
112
+ cell({ rowId, columnId }: { rowId: string; columnId: string }): Locator {
113
+ return this.root.locator(
114
+ `[data-row-id="${rowId}"][data-column-id="${columnId}"]`,
115
+ );
116
+ }
117
+
118
+ async expectRowCount(count: number): Promise<void> {
119
+ await expect(this.grid).toHaveAttribute("data-dg-row-count", String(count));
120
+ }
121
+
122
+ async expectSettled(): Promise<void> {
123
+ await expect(this.grid).not.toHaveAttribute("aria-busy");
124
+ }
125
+ }
126
+ ```
127
+
128
+ ## Virtualization
129
+
130
+ Only the rows in the viewport plus overscan are in the DOM. A row at index 500
131
+ has no element, and Playwright cannot scroll to what it cannot find.
132
+
133
+ **Count rows off the grid.** `data-dg-row-count` is the body rows the grid is
134
+ showing — the current page under pagination, everything the filters left
135
+ otherwise. (`aria-rowcount` also counts the header and summary rows.)
136
+
137
+ **Reach a row by narrowing to it** — filter or search. Faster, more stable, and
138
+ what a user would do. Where the row must be reached in place,
139
+ `grid.scrollToRow({ rowId, align })` moves the virtualizer and answers whether
140
+ the row was reachable; from Playwright that needs the app to expose the api.
141
+
142
+ ## Waiting
143
+
144
+ `meta.loading` sets `aria-busy` on the grid whether or not the body has rows, so
145
+ a refetch over existing rows is visible to a test. Quick search debounces
146
+ (250 ms by default). Assert on `data-dg-row-count` and let Playwright retry
147
+ rather than adding a timeout.
148
+
149
+ ## Common mistakes
150
+
151
+ ### Selecting chrome by its aria-label
152
+
153
+ Every icon-only control has one, but they come from `labels` and are yours to
154
+ translate. A suite written on `getByRole("button", { name: "Filters" })` breaks
155
+ the day the grid renders in Swedish, and again on any copy change. Use the part.
156
+
157
+ ### Counting row elements
158
+
159
+ `expect(rows).toHaveLength(50)` fails at any real row count, because
160
+ virtualization mounts a couple of dozen. Under jsdom it is worse: there is no
161
+ layout, so the count depends on the stubbed element size. Assert
162
+ `data-dg-row-count`.
163
+
164
+ ### getByRole("cell") on a grid with cell selection
165
+
166
+ `enableCellSelection` turns every `cell` into a `gridcell`, and the grid's
167
+ `table` into a `grid` — a widget with a keyboard cursor is not a table of
168
+ content. Tests written on the role break when the feature is switched on. Query
169
+ cells by `[data-row-id][data-column-id]`, which do not move.
170
+
171
+ ### Expecting a row far down the list to exist
172
+
173
+ `dg-row-450` has no element until it is scrolled to, so the locator times out
174
+ with no useful message. Filter or search down to it first.
175
+
176
+ ### Unscoped parts with two grids on a page
177
+
178
+ `page.locator('[data-dg-part="row"]')` matches both grids and Playwright's
179
+ strict mode fails on the ambiguity. Give each grid a `data-testid` and scope
180
+ every query through it.
181
+
182
+ ### Adding data-testid and expecting the grid to honour it
183
+
184
+ `data-testid` on `<TMDataGrid>` lands on the root element only; it is not a
185
+ prefix that propagates to the parts inside. Scope through the root instead of
186
+ looking for `orders-row-42`.
187
+
188
+ ### Asserting on a column's cells through nth-child
189
+
190
+ Column order changes with pinning, reordering and the generated lanes
191
+ (checkbox, tree, details, row numbers, edit), so a positional index points at a
192
+ different column than it did. Use `[data-column-id]`.
193
+
194
+ ### Waiting on the toolbar spinner
195
+
196
+ `data-dg-part="loading"` only renders where `TMDataGrid.LoadingIndicator` was
197
+ placed, and only while `meta.loading` is true. `aria-busy` on the grid is set
198
+ regardless of whether that component is rendered.
@@ -59,12 +59,23 @@
59
59
  group row, so a tint would grey out the whole grid. Set it to give groups a
60
60
  band of their own. */
61
61
  --dg-row-group-bg: var(--mantine-color-body);
62
+ /* Every second row under `striped`. A whisper of the text colour over the
63
+ body, so one declaration is right in both themes. */
64
+ --dg-row-striped-bg: color-mix(
65
+ in srgb,
66
+ var(--mantine-color-text) 3.5%,
67
+ var(--mantine-color-body)
68
+ );
62
69
 
63
70
  /* The stacking ladder, stated once. Body cells sit at auto (0); everything
64
71
  sticky stacks over them in this order, and a sticky row crossing a sticky
65
72
  column takes the row's slot plus one. */
66
73
  --dg-z-pinned-cell: 2;
67
74
  --dg-z-focused-cell: 3;
75
+ /* The pinned-row slot serves both sticky rows: the summary along the
76
+ bottom and the entry block under the header. */
77
+ --dg-z-pinned-row: 4;
78
+ --dg-z-pinned-row-pinned-cell: 5;
68
79
  --dg-z-summary-row: 4;
69
80
  --dg-z-summary-pinned-cell: 5;
70
81
  --dg-z-header: 6;
@@ -3,6 +3,7 @@ import { type CSSProperties, type ReactNode, useMemo } from "react";
3
3
  import classes from "./TMDataGrid.module.css";
4
4
  import { TMDataGridColumnsPanel } from "./TMDataGridColumnsPanel";
5
5
  import { TMDataGridContextProvider } from "../TMDataGridContext";
6
+ import { TMDataGridEditActions } from "./TMDataGridEditActions";
6
7
  import { TMDataGridFilterPanel } from "./TMDataGridFilterPanel";
7
8
  import { TMDataGridFilterPills } from "./TMDataGridFilterPills";
8
9
  import { TMDataGridFooter } from "./TMDataGridFooter";
@@ -38,6 +39,20 @@ export type TMDataGridProps<TData extends RowData> = TMDataGridApi<TData> & {
38
39
  * background. Plain `CSSProperties` rejects `--*` keys.
39
40
  */
40
41
  style?: CSSProperties & Record<`--${string}`, string | number>;
42
+ /** Set on the root element. */
43
+ id?: string;
44
+ /**
45
+ * Names this grid for tests. The grid names its own pieces with
46
+ * `data-dg-part` rather than minting test ids, and those repeat across
47
+ * grids — scope through this one and they stop colliding:
48
+ *
49
+ * ```ts
50
+ * page.getByTestId("orders").locator('[data-dg-part="row"][data-row-id="42"]')
51
+ * ```
52
+ *
53
+ * See the Testing docs page for the full attribute contract.
54
+ */
55
+ "data-testid"?: string;
41
56
  };
42
57
 
43
58
  /**
@@ -61,15 +76,21 @@ export type TMDataGridProps<TData extends RowData> = TMDataGridApi<TData> & {
61
76
  function TMDataGridRoot<TData extends RowData>({
62
77
  table,
63
78
  ui,
79
+ edit,
64
80
  features,
65
81
  labels,
66
82
  renderDetails,
67
83
  renderDetailsEstHeight,
68
84
  overscan,
85
+ resetSettings,
86
+ scrollToRow,
87
+ scrollerRef,
69
88
  size = DEFAULT_TMDATAGRID_SIZE,
70
89
  children,
71
90
  className,
72
91
  style,
92
+ id,
93
+ "data-testid": testId,
73
94
  }: TMDataGridProps<TData>) {
74
95
  const rowHeight = table.options.meta?.rowHeight ?? SIZE_ROW_HEIGHT[size];
75
96
 
@@ -77,11 +98,15 @@ function TMDataGridRoot<TData extends RowData>({
77
98
  () => ({
78
99
  table,
79
100
  ui,
101
+ edit,
80
102
  features,
81
103
  labels,
82
104
  renderDetails,
83
105
  renderDetailsEstHeight,
84
106
  overscan,
107
+ resetSettings,
108
+ scrollToRow,
109
+ scrollerRef,
85
110
  size,
86
111
  rowHeight,
87
112
  controlSize: SIZE_CONTROL_SIZE[size],
@@ -89,11 +114,15 @@ function TMDataGridRoot<TData extends RowData>({
89
114
  [
90
115
  table,
91
116
  ui,
117
+ edit,
92
118
  features,
93
119
  labels,
94
120
  renderDetails,
95
121
  renderDetailsEstHeight,
96
122
  overscan,
123
+ resetSettings,
124
+ scrollToRow,
125
+ scrollerRef,
97
126
  size,
98
127
  rowHeight,
99
128
  ],
@@ -102,6 +131,10 @@ function TMDataGridRoot<TData extends RowData>({
102
131
  return (
103
132
  <TMDataGridContextProvider value={api}>
104
133
  <div
134
+ id={id}
135
+ data-testid={testId}
136
+ // The handle everything else scopes off — see the `data-testid` prop.
137
+ data-dg-root
105
138
  data-size={size}
106
139
  className={[classes.root, className].filter(Boolean).join(" ")}
107
140
  style={style}
@@ -118,6 +151,7 @@ export const TMDataGrid = Object.assign(TMDataGridRoot, {
118
151
  SummaryCount: TMDataGridSummaryCount,
119
152
  LoadingIndicator: TMDataGridLoadingIndicator,
120
153
  Search: TMDataGridSearch,
154
+ EditActions: TMDataGridEditActions,
121
155
  ColumnsButton: TMDataGridColumnsButton,
122
156
  FilterButton: TMDataGridFilterButton,
123
157
  Table: TMDataGridTable,