@jielga/tmdatagrid 2.0.0-beta.4 → 2.0.0-beta.7

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 (37) hide show
  1. package/dist/index.d.ts +339 -152
  2. package/dist/index.js +2602 -2450
  3. package/dist/index.js.map +1 -1
  4. package/dist/styles.css +1 -1
  5. package/package.json +1 -1
  6. package/skills/appearance/SKILL.md +1 -1
  7. package/skills/cell-selection/SKILL.md +1 -1
  8. package/skills/columns/SKILL.md +44 -2
  9. package/skills/data/SKILL.md +14 -10
  10. package/skills/editing/SKILL.md +140 -68
  11. package/skills/editing/references/common-mistakes.md +98 -8
  12. package/skills/editing/references/editing-api.md +40 -30
  13. package/skills/editing/references/editors-and-validation.md +65 -14
  14. package/skills/filtering/SKILL.md +1 -1
  15. package/skills/getting-started/SKILL.md +3 -3
  16. package/skills/grouping/SKILL.md +5 -3
  17. package/skills/options/SKILL.md +2 -2
  18. package/skills/rows/SKILL.md +7 -3
  19. package/skills/server-side/SKILL.md +1 -1
  20. package/skills/testing/SKILL.md +1 -1
  21. package/src/tmdatagrid/components/TMDataGrid.tsx +2 -2
  22. package/src/tmdatagrid/components/TMDataGridCellEditor.tsx +33 -34
  23. package/src/tmdatagrid/components/TMDataGridDraftActions.tsx +296 -0
  24. package/src/tmdatagrid/components/TMDataGridEditColumn.tsx +40 -33
  25. package/src/tmdatagrid/components/TMDataGridEntryRows.tsx +7 -7
  26. package/src/tmdatagrid/components/TMDataGridTable.tsx +27 -5
  27. package/src/tmdatagrid/components/TMDataGridToolbar.module.css +4 -0
  28. package/src/tmdatagrid/components/TMDataGridToolbar.tsx +8 -10
  29. package/src/tmdatagrid/components/editors/TMDataGridNumberEditor.tsx +34 -4
  30. package/src/tmdatagrid/components/editors/editorShared.ts +1 -29
  31. package/src/tmdatagrid/core/capabilities.ts +14 -6
  32. package/src/tmdatagrid/core/editEngine.ts +472 -96
  33. package/src/tmdatagrid/core/labels.ts +3 -3
  34. package/src/tmdatagrid/core/summary.ts +10 -2
  35. package/src/tmdatagrid/index.ts +9 -7
  36. package/src/tmdatagrid/useTMDataGrid.tsx +107 -47
  37. package/src/tmdatagrid/components/TMDataGridEditActions.tsx +0 -233
package/dist/styles.css CHANGED
@@ -1,2 +1,2 @@
1
- ._root_1wvkg_8[data-size=xs]{--dg-row-height:34px;--dg-header-height:32px;--dg-font-size:var(--mantine-font-size-xs);--dg-padding:6px}._root_1wvkg_8[data-size=sm]{--dg-row-height:42px;--dg-header-height:38px;--dg-font-size:var(--mantine-font-size-sm);--dg-padding:8px}._root_1wvkg_8[data-size=md]{--dg-row-height:52px;--dg-header-height:44px;--dg-font-size:var(--mantine-font-size-sm);--dg-padding:10px}._root_1wvkg_8[data-size=lg]{--dg-row-height:62px;--dg-header-height:52px;--dg-font-size:var(--mantine-font-size-md);--dg-padding:14px}._root_1wvkg_8[data-size=xl]{--dg-row-height:72px;--dg-header-height:60px;--dg-font-size:var(--mantine-font-size-lg);--dg-padding:18px}._root_1wvkg_8{--dg-radius:var(--mantine-radius-md);--dg-row-height:52px;--dg-header-height:44px;--dg-font-size:var(--mantine-font-size-sm);--dg-padding:10px;--dg-row-selected-bg:var(--mantine-primary-color-light);--dg-row-highlight-bg:var(--mantine-primary-color-light-hover);--dg-row-group-bg:var(--mantine-color-body);--dg-row-striped-bg:color-mix(in srgb, var(--mantine-color-text) 3.5%, var(--mantine-color-body));--dg-row-new-bg:color-mix(in srgb, var(--mantine-color-green-6) 8%, var(--mantine-color-body));--dg-z-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(--dg-radius);background-color:var(--mantine-color-body);flex-direction:column;min-height:0;display:flex;overflow:hidden}._columnsPanel_1492o_1{flex-direction:column;width:280px;display:flex}._columnsPanelSearch_1492o_7{padding:var(--mantine-spacing-sm)}._columnsPanelList_1492o_11{gap:var(--mantine-spacing-sm);padding:0 var(--mantine-spacing-sm) var(--mantine-spacing-sm);flex-direction:column;display:flex}._columnsPanelFooter_1492o_18{justify-content:space-between;align-items:center;gap:var(--mantine-spacing-sm);padding:var(--mantine-spacing-sm);border-top:1px solid var(--mantine-color-default-border);display:flex}._detailsToggle_n39tz_3{width:100%;height:100%;color:inherit;justify-content:center;align-items:center;display:flex}._detailsToggle_n39tz_3:hover ._chevron_n39tz_12{color:var(--mantine-color-text)}._chevron_n39tz_12{color:var(--mantine-color-dimmed);align-items:center;transition:transform .12s;display:flex}._chevron_n39tz_12[data-expanded=true]{transform:rotate(90deg)}@media (prefers-reduced-motion:reduce){._chevron_n39tz_12{transition:none}}._tableWrapper_uqiy9_2{flex-direction:column;flex:1;min-height:0;display:flex;position:relative}._scrollContainer_uqiy9_10{flex:1;min-height:0;position:relative;overflow:auto;container-type:inline-size}._grid_uqiy9_21{display:grid}._headerRow_uqiy9_27{grid-column:1/-1;grid-template-columns:subgrid;z-index:var(--dg-z-header,6);display:grid;position:sticky;top:0}._headerRow_uqiy9_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_uqiy9_27[data-dg-header-last]:after{animation:linear both _dg-header-shadow_uqiy9_1;animation-timeline:scroll();animation-range:0 24px}}@keyframes _dg-header-shadow_uqiy9_1{0%{opacity:0}to{opacity:1}}._bodyRow_uqiy9_76{--row-bg:var(--mantine-color-body);grid-column:1/-1;grid-template-columns:subgrid;align-items:stretch;display:grid}._bodyRow_uqiy9_76[data-striped=true]{--row-bg:var(--dg-row-striped-bg)}._bodyRow_uqiy9_76:hover{--row-bg:var(--mantine-color-default-hover)}._bodyRow_uqiy9_76[data-grouped=true]{--row-bg:var(--dg-row-group-bg)}._bodyRow_uqiy9_76[data-context-menu=true]{--row-bg:var(--mantine-color-default-hover)}._bodyRow_uqiy9_76[data-selected-bg=true]{--row-bg:var(--dg-row-selected-bg)}._bodyRow_uqiy9_76[data-highlighted=true]{--row-bg:var(--dg-row-highlight-bg)}._bodyRow_uqiy9_76[data-selects-on-click=true]{-webkit-user-select:none;user-select:none}._bodyRow_uqiy9_76:focus-visible{outline:2px solid var(--mantine-primary-color-filled);outline-offset:-2px}._bodyCell_uqiy9_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_uqiy9_143[data-control-column=true]{padding:0}._grid_uqiy9_21[data-range-dragging]{-webkit-user-select:none;user-select:none}._bodyCell_uqiy9_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_uqiy9_143[data-edge-top=true]{--dg-edge-top:var(--mantine-primary-color-filled)}._bodyCell_uqiy9_143[data-edge-bottom=true]{--dg-edge-bottom:var(--mantine-primary-color-filled)}._bodyCell_uqiy9_143[data-edge-left=true]{--dg-edge-left:var(--mantine-primary-color-filled)}._bodyCell_uqiy9_143[data-edge-right=true]{--dg-edge-right:var(--mantine-primary-color-filled)}._bodyCell_uqiy9_143[data-focused=true]{outline:2px solid var(--mantine-primary-color-filled);outline-offset:-2px}._cellContent_uqiy9_141{white-space:nowrap;text-overflow:ellipsis;min-width:0;overflow:hidden}._bodyCell_uqiy9_143[data-editing]{padding:0 calc(var(--dg-padding) / 2)}._cellEditor_uqiy9_243{align-items:center;gap:2px;width:100%;min-width:0;display:flex}._cellEditorActions_uqiy9_252{flex:none;align-items:center;display:flex}._bodyCell_uqiy9_143[data-dirty],._bodyCell_uqiy9_143[data-invalid]{position:relative}._bodyCell_uqiy9_143[data-dirty]:after,._bodyCell_uqiy9_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_uqiy9_143[data-invalid]:after{border-top-color:var(--mantine-color-error);border-right-color:var(--mantine-color-error)}._bodyCell_uqiy9_143[data-align=right]{justify-content:flex-end}._bodyCell_uqiy9_143[data-align=center]{justify-content:center}._bodyCell_uqiy9_143[data-align=left] ._cellContent_uqiy9_141{flex:auto}._detailsCell_uqiy9_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_uqiy9_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_uqiy9_76[data-deleted=true]{opacity:.55;text-decoration:line-through}._bodyRow_uqiy9_76[data-deleted=true] ._bodyCell_uqiy9_143:not([data-control-column=true]){pointer-events:none}._entryBlock_uqiy9_353{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_uqiy9_362,._entryFlowBlock_uqiy9_371{grid-column:1/-1;grid-template-columns:subgrid;display:grid}._pinnedTopBlock_uqiy9_384{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_uqiy9_384>:last-child>[role=cell]{border-bottom:2px solid var(--mantine-color-default-border)}._pinnedBottomBlock_uqiy9_397{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_uqiy9_397>:first-child>[role=cell]{border-top:2px solid var(--mantine-color-default-border)}._entryCell_uqiy9_410{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_uqiy9_410[data-control-column=true]{justify-content:center;padding:0}._entryRow_uqiy9_362[data-confirmed=true] ._entryCell_uqiy9_410{padding:0 var(--dg-padding);border-bottom:1px solid var(--mantine-color-default-border);background-color:var(--dg-row-new-bg,var(--mantine-color-body))}._entryRow_uqiy9_362[data-confirmed=true] ._entryCell_uqiy9_410[data-control-column=true]{padding:0}._rowStateIndicator_uqiy9_440{width:22px;height:22px;color:var(--mantine-color-dimmed);flex:none;justify-content:center;align-items:center;display:inline-flex}._rowStateIndicator_uqiy9_440[data-error]{color:var(--mantine-color-error)}._entryCell_uqiy9_410[data-align=right]{justify-content:flex-end}._entryCell_uqiy9_410[data-align=center]{justify-content:center}._matchHighlight_uqiy9_466{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_uqiy9_478{grid-column:1/-1;grid-template-columns:subgrid;z-index:var(--dg-z-summary-row,4);display:grid;position:sticky;bottom:0}._summaryCell_uqiy9_487{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_uqiy9_487[data-control-column=true]{padding:0}._summaryCell_uqiy9_487[data-align=right]{justify-content:flex-end}._summaryCell_uqiy9_487[data-align=center]{justify-content:center}._groupToggle_1954o_4{width:100%;min-width:0;font-size:var(--dg-font-size);color:inherit;align-items:center;gap:6px;display:flex}._chevron_1954o_14{color:var(--mantine-color-dimmed);flex:none;align-items:center;transition:transform .12s;display:flex}._chevron_1954o_14[data-expanded=true]{transform:rotate(90deg)}@media (prefers-reduced-motion:reduce){._chevron_1954o_14{transition:none}}._groupLabel_1954o_34{white-space:nowrap;text-overflow:ellipsis;min-width:0;font-weight:600;overflow:hidden}._groupCount_1954o_44{color:var(--mantine-color-dimmed);font-variant-numeric:tabular-nums;flex:none}._filterPanel_izakx_4{top:calc(var(--dg-header-height) + 2px);left:var(--mantine-spacing-md);z-index:5;padding:var(--mantine-spacing-sm);border-radius:var(--mantine-radius-md);border:1px solid var(--mantine-color-default-border);background-color:var(--mantine-color-body);box-shadow:var(--mantine-shadow-md);position:absolute}._filterRow_izakx_16{align-items:flex-end;gap:var(--mantine-spacing-xs);display:flex}._filterPanelHeader_izakx_22{justify-content:space-between;align-items:center;gap:var(--mantine-spacing-md);padding-bottom:var(--mantine-spacing-xs);display:flex}._filterPanelFooter_izakx_30{justify-content:space-between;align-items:center;gap:var(--mantine-spacing-md);display:flex}._filterPills_14wl0_3{align-items:center;gap:var(--mantine-spacing-xs);flex-wrap:wrap;display:flex}._filterPills_14wl0_3 ._pill_14wl0_15{background-color:var(--lightningcss-light,var(--mantine-color-gray-1))var(--lightningcss-dark,var(--mantine-color-dark-5));color:var(--lightningcss-light,var(--mantine-color-black))var(--lightningcss-dark,var(--mantine-color-dark-0));border:1px solid var(--lightningcss-light,var(--mantine-color-gray-3))var(--lightningcss-dark,var(--mantine-color-dark-3))}._pillLabel_14wl0_27{font:inherit;color:inherit;cursor:pointer}._pillLabel_14wl0_27:hover{text-decoration:underline}._footer_te0a3_1{justify-content:flex-end;align-items:center;gap:var(--mantine-spacing-lg);padding:4px var(--mantine-spacing-sm);border-top:1px solid var(--mantine-color-default-border);flex-shrink:0;min-height:48px;display:flex}._footer_te0a3_1[data-paging-suspended=true]{opacity:.55}._stickyLeft_1vw51_16:after,._stickyRight_1vw51_17:after{content:"";pointer-events:none;width:8px;position:absolute;top:0;bottom:0}._stickyLeft_1vw51_16:after{background:linear-gradient(90deg,var(--lightningcss-light,#00000012)var(--lightningcss-dark,#00000042),var(--lightningcss-light,#00000006)var(--lightningcss-dark,#00000017) 45%,#0000);left:100%}._stickyRight_1vw51_17:after{background:linear-gradient(270deg,var(--lightningcss-light,#00000012)var(--lightningcss-dark,#00000042),var(--lightningcss-light,#00000006)var(--lightningcss-dark,#00000017) 45%,#0000);right:100%}@supports (animation-timeline:scroll()){._stickyLeft_1vw51_16:after,._stickyRight_1vw51_17:after{--dg-sticky-edge-range:20px;opacity:0;animation-timing-function:linear;animation-fill-mode:both;animation-timeline:scroll(inline)}._stickyLeft_1vw51_16:after{animation-name:_stickyEdgeIn_1vw51_1;animation-range:0 var(--dg-sticky-edge-range)}._stickyRight_1vw51_17:after{animation-name:_stickyEdgeOut_1vw51_1;animation-range:calc(100% - var(--dg-sticky-edge-range)) 100%}}@keyframes _stickyEdgeIn_1vw51_1{0%{opacity:0}to{opacity:1}}@keyframes _stickyEdgeOut_1vw51_1{0%{opacity:1}to{opacity:0}}._headerCell_188ed_1{padding:0 var(--dg-padding);min-width:0;min-height:var(--dg-header-height);font-weight:600;font-size:var(--dg-font-size);background-color:var(--mantine-color-body);border-bottom:1px solid var(--mantine-color-default-border);-webkit-user-select:none;user-select:none;align-items:center;gap:2px;display:flex;position:relative}._headerCell_188ed_1[data-align=right]{justify-content:flex-end}._headerCell_188ed_1[data-align=center]{justify-content:center}._headerCell_188ed_1[data-control-column=true]{padding:0}._headerCellFiltered_188ed_35 ._headerTitle_188ed_35{color:var(--mantine-primary-color-filled)}._headerTitle_188ed_35{text-overflow:ellipsis;white-space:nowrap;flex:auto;min-width:0;overflow:hidden}._headerCell_188ed_1[data-align=right] ._headerTitle_188ed_35{text-align:right}._headerCell_188ed_1[data-align=center] ._headerTitle_188ed_35{justify-content:center;display:flex}._headerActions_188ed_58{flex-shrink:0;align-items:center;gap:0;margin-left:auto;display:flex}._headerCell_188ed_1[data-align=right] ._headerActions_188ed_58{order:-1;margin-left:0;margin-right:auto}._headerAction_188ed_58{display:none}._headerCell_188ed_1:hover ._headerAction_188ed_58,._headerCell_188ed_1:focus-within ._headerAction_188ed_58,._headerAction_188ed_58[data-pinned-visible=true]{display:flex}._headerCellSortable_188ed_87{cursor:pointer}._sortAction_188ed_93[data-sorted=false]{opacity:.55}._sortAction_188ed_93[data-sorted=false]:hover{opacity:1}._sortIndex_188ed_103{color:var(--mantine-color-dimmed);text-align:center;min-width:10px;font-size:10px;font-weight:600;line-height:1}._headerCellDragging_188ed_115{opacity:.4}._headerCellDropBefore_188ed_119:before,._headerCellDropAfter_188ed_120:before{content:"";background-color:var(--mantine-primary-color-filled);z-index:5;pointer-events:none;width:2px;position:absolute;top:0;bottom:0}._headerCellDropBefore_188ed_119:before{left:0}._headerCellDropAfter_188ed_120:before{right:0}._columnSeparator_188ed_140{z-index:1;justify-content:center;width:10px;height:50%;display:flex;position:absolute;top:25%;right:0;transform:translate(50%)}._columnSeparatorResizable_188ed_152{cursor:col-resize;touch-action:none}._columnSeparator_188ed_140:after{content:"";background-color:#0000;width:1px;height:100%;transition:background-color .12s,width .12s}._headerCell_188ed_1:hover ._columnSeparator_188ed_140:after,._headerCell_188ed_1:focus-within ._columnSeparator_188ed_140:after{background-color:var(--mantine-color-default-border)}._columnSeparatorResizable_188ed_152:hover:after,._columnSeparatorActive_188ed_175:after{background-color:var(--mantine-primary-color-filled);width:2px}._menuItemActive_188ed_181{background-color:var(--mantine-color-default-hover);font-weight:600}._toolbar_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_1wvkg_8[data-size=xs]{--dg-row-height:34px;--dg-header-height:32px;--dg-font-size:var(--mantine-font-size-xs);--dg-padding:6px}._root_1wvkg_8[data-size=sm]{--dg-row-height:42px;--dg-header-height:38px;--dg-font-size:var(--mantine-font-size-sm);--dg-padding:8px}._root_1wvkg_8[data-size=md]{--dg-row-height:52px;--dg-header-height:44px;--dg-font-size:var(--mantine-font-size-sm);--dg-padding:10px}._root_1wvkg_8[data-size=lg]{--dg-row-height:62px;--dg-header-height:52px;--dg-font-size:var(--mantine-font-size-md);--dg-padding:14px}._root_1wvkg_8[data-size=xl]{--dg-row-height:72px;--dg-header-height:60px;--dg-font-size:var(--mantine-font-size-lg);--dg-padding:18px}._root_1wvkg_8{--dg-radius:var(--mantine-radius-md);--dg-row-height:52px;--dg-header-height:44px;--dg-font-size:var(--mantine-font-size-sm);--dg-padding:10px;--dg-row-selected-bg:var(--mantine-primary-color-light);--dg-row-highlight-bg:var(--mantine-primary-color-light-hover);--dg-row-group-bg:var(--mantine-color-body);--dg-row-striped-bg:color-mix(in srgb, var(--mantine-color-text) 3.5%, var(--mantine-color-body));--dg-row-new-bg:color-mix(in srgb, var(--mantine-color-green-6) 8%, var(--mantine-color-body));--dg-z-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(--dg-radius);background-color:var(--mantine-color-body);flex-direction:column;min-height:0;display:flex;overflow:hidden}._columnsPanel_1492o_1{flex-direction:column;width:280px;display:flex}._columnsPanelSearch_1492o_7{padding:var(--mantine-spacing-sm)}._columnsPanelList_1492o_11{gap:var(--mantine-spacing-sm);padding:0 var(--mantine-spacing-sm) var(--mantine-spacing-sm);flex-direction:column;display:flex}._columnsPanelFooter_1492o_18{justify-content:space-between;align-items:center;gap:var(--mantine-spacing-sm);padding:var(--mantine-spacing-sm);border-top:1px solid var(--mantine-color-default-border);display:flex}._detailsToggle_n39tz_3{width:100%;height:100%;color:inherit;justify-content:center;align-items:center;display:flex}._detailsToggle_n39tz_3:hover ._chevron_n39tz_12{color:var(--mantine-color-text)}._chevron_n39tz_12{color:var(--mantine-color-dimmed);align-items:center;transition:transform .12s;display:flex}._chevron_n39tz_12[data-expanded=true]{transform:rotate(90deg)}@media (prefers-reduced-motion:reduce){._chevron_n39tz_12{transition:none}}._tableWrapper_uqiy9_2{flex-direction:column;flex:1;min-height:0;display:flex;position:relative}._scrollContainer_uqiy9_10{flex:1;min-height:0;position:relative;overflow:auto;container-type:inline-size}._grid_uqiy9_21{display:grid}._headerRow_uqiy9_27{grid-column:1/-1;grid-template-columns:subgrid;z-index:var(--dg-z-header,6);display:grid;position:sticky;top:0}._headerRow_uqiy9_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_uqiy9_27[data-dg-header-last]:after{animation:linear both _dg-header-shadow_uqiy9_1;animation-timeline:scroll();animation-range:0 24px}}@keyframes _dg-header-shadow_uqiy9_1{0%{opacity:0}to{opacity:1}}._bodyRow_uqiy9_76{--row-bg:var(--mantine-color-body);grid-column:1/-1;grid-template-columns:subgrid;align-items:stretch;display:grid}._bodyRow_uqiy9_76[data-striped=true]{--row-bg:var(--dg-row-striped-bg)}._bodyRow_uqiy9_76:hover{--row-bg:var(--mantine-color-default-hover)}._bodyRow_uqiy9_76[data-grouped=true]{--row-bg:var(--dg-row-group-bg)}._bodyRow_uqiy9_76[data-context-menu=true]{--row-bg:var(--mantine-color-default-hover)}._bodyRow_uqiy9_76[data-selected-bg=true]{--row-bg:var(--dg-row-selected-bg)}._bodyRow_uqiy9_76[data-highlighted=true]{--row-bg:var(--dg-row-highlight-bg)}._bodyRow_uqiy9_76[data-selects-on-click=true]{-webkit-user-select:none;user-select:none}._bodyRow_uqiy9_76:focus-visible{outline:2px solid var(--mantine-primary-color-filled);outline-offset:-2px}._bodyCell_uqiy9_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_uqiy9_143[data-control-column=true]{padding:0}._grid_uqiy9_21[data-range-dragging]{-webkit-user-select:none;user-select:none}._bodyCell_uqiy9_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_uqiy9_143[data-edge-top=true]{--dg-edge-top:var(--mantine-primary-color-filled)}._bodyCell_uqiy9_143[data-edge-bottom=true]{--dg-edge-bottom:var(--mantine-primary-color-filled)}._bodyCell_uqiy9_143[data-edge-left=true]{--dg-edge-left:var(--mantine-primary-color-filled)}._bodyCell_uqiy9_143[data-edge-right=true]{--dg-edge-right:var(--mantine-primary-color-filled)}._bodyCell_uqiy9_143[data-focused=true]{outline:2px solid var(--mantine-primary-color-filled);outline-offset:-2px}._cellContent_uqiy9_141{white-space:nowrap;text-overflow:ellipsis;min-width:0;overflow:hidden}._bodyCell_uqiy9_143[data-editing]{padding:0 calc(var(--dg-padding) / 2)}._cellEditor_uqiy9_243{align-items:center;gap:2px;width:100%;min-width:0;display:flex}._cellEditorActions_uqiy9_252{flex:none;align-items:center;display:flex}._bodyCell_uqiy9_143[data-dirty],._bodyCell_uqiy9_143[data-invalid]{position:relative}._bodyCell_uqiy9_143[data-dirty]:after,._bodyCell_uqiy9_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_uqiy9_143[data-invalid]:after{border-top-color:var(--mantine-color-error);border-right-color:var(--mantine-color-error)}._bodyCell_uqiy9_143[data-align=right]{justify-content:flex-end}._bodyCell_uqiy9_143[data-align=center]{justify-content:center}._bodyCell_uqiy9_143[data-align=left] ._cellContent_uqiy9_141{flex:auto}._detailsCell_uqiy9_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_uqiy9_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_uqiy9_76[data-deleted=true]{opacity:.55;text-decoration:line-through}._bodyRow_uqiy9_76[data-deleted=true] ._bodyCell_uqiy9_143:not([data-control-column=true]){pointer-events:none}._entryBlock_uqiy9_353{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_uqiy9_362,._entryFlowBlock_uqiy9_371{grid-column:1/-1;grid-template-columns:subgrid;display:grid}._pinnedTopBlock_uqiy9_384{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_uqiy9_384>:last-child>[role=cell]{border-bottom:2px solid var(--mantine-color-default-border)}._pinnedBottomBlock_uqiy9_397{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_uqiy9_397>:first-child>[role=cell]{border-top:2px solid var(--mantine-color-default-border)}._entryCell_uqiy9_410{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_uqiy9_410[data-control-column=true]{justify-content:center;padding:0}._entryRow_uqiy9_362[data-confirmed=true] ._entryCell_uqiy9_410{padding:0 var(--dg-padding);border-bottom:1px solid var(--mantine-color-default-border);background-color:var(--dg-row-new-bg,var(--mantine-color-body))}._entryRow_uqiy9_362[data-confirmed=true] ._entryCell_uqiy9_410[data-control-column=true]{padding:0}._rowStateIndicator_uqiy9_440{width:22px;height:22px;color:var(--mantine-color-dimmed);flex:none;justify-content:center;align-items:center;display:inline-flex}._rowStateIndicator_uqiy9_440[data-error]{color:var(--mantine-color-error)}._entryCell_uqiy9_410[data-align=right]{justify-content:flex-end}._entryCell_uqiy9_410[data-align=center]{justify-content:center}._matchHighlight_uqiy9_466{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_uqiy9_478{grid-column:1/-1;grid-template-columns:subgrid;z-index:var(--dg-z-summary-row,4);display:grid;position:sticky;bottom:0}._summaryCell_uqiy9_487{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_uqiy9_487[data-control-column=true]{padding:0}._summaryCell_uqiy9_487[data-align=right]{justify-content:flex-end}._summaryCell_uqiy9_487[data-align=center]{justify-content:center}._groupToggle_1954o_4{width:100%;min-width:0;font-size:var(--dg-font-size);color:inherit;align-items:center;gap:6px;display:flex}._chevron_1954o_14{color:var(--mantine-color-dimmed);flex:none;align-items:center;transition:transform .12s;display:flex}._chevron_1954o_14[data-expanded=true]{transform:rotate(90deg)}@media (prefers-reduced-motion:reduce){._chevron_1954o_14{transition:none}}._groupLabel_1954o_34{white-space:nowrap;text-overflow:ellipsis;min-width:0;font-weight:600;overflow:hidden}._groupCount_1954o_44{color:var(--mantine-color-dimmed);font-variant-numeric:tabular-nums;flex:none}._filterPanel_izakx_4{top:calc(var(--dg-header-height) + 2px);left:var(--mantine-spacing-md);z-index:5;padding:var(--mantine-spacing-sm);border-radius:var(--mantine-radius-md);border:1px solid var(--mantine-color-default-border);background-color:var(--mantine-color-body);box-shadow:var(--mantine-shadow-md);position:absolute}._filterRow_izakx_16{align-items:flex-end;gap:var(--mantine-spacing-xs);display:flex}._filterPanelHeader_izakx_22{justify-content:space-between;align-items:center;gap:var(--mantine-spacing-md);padding-bottom:var(--mantine-spacing-xs);display:flex}._filterPanelFooter_izakx_30{justify-content:space-between;align-items:center;gap:var(--mantine-spacing-md);display:flex}._filterPills_14wl0_3{align-items:center;gap:var(--mantine-spacing-xs);flex-wrap:wrap;display:flex}._filterPills_14wl0_3 ._pill_14wl0_15{background-color:var(--lightningcss-light,var(--mantine-color-gray-1))var(--lightningcss-dark,var(--mantine-color-dark-5));color:var(--lightningcss-light,var(--mantine-color-black))var(--lightningcss-dark,var(--mantine-color-dark-0));border:1px solid var(--lightningcss-light,var(--mantine-color-gray-3))var(--lightningcss-dark,var(--mantine-color-dark-3))}._pillLabel_14wl0_27{font:inherit;color:inherit;cursor:pointer}._pillLabel_14wl0_27:hover{text-decoration:underline}._footer_te0a3_1{justify-content:flex-end;align-items:center;gap:var(--mantine-spacing-lg);padding:4px var(--mantine-spacing-sm);border-top:1px solid var(--mantine-color-default-border);flex-shrink:0;min-height:48px;display:flex}._footer_te0a3_1[data-paging-suspended=true]{opacity:.55}._stickyLeft_1vw51_16:after,._stickyRight_1vw51_17:after{content:"";pointer-events:none;width:8px;position:absolute;top:0;bottom:0}._stickyLeft_1vw51_16:after{background:linear-gradient(90deg,var(--lightningcss-light,#00000012)var(--lightningcss-dark,#00000042),var(--lightningcss-light,#00000006)var(--lightningcss-dark,#00000017) 45%,#0000);left:100%}._stickyRight_1vw51_17:after{background:linear-gradient(270deg,var(--lightningcss-light,#00000012)var(--lightningcss-dark,#00000042),var(--lightningcss-light,#00000006)var(--lightningcss-dark,#00000017) 45%,#0000);right:100%}@supports (animation-timeline:scroll()){._stickyLeft_1vw51_16:after,._stickyRight_1vw51_17:after{--dg-sticky-edge-range:20px;opacity:0;animation-timing-function:linear;animation-fill-mode:both;animation-timeline:scroll(inline)}._stickyLeft_1vw51_16:after{animation-name:_stickyEdgeIn_1vw51_1;animation-range:0 var(--dg-sticky-edge-range)}._stickyRight_1vw51_17:after{animation-name:_stickyEdgeOut_1vw51_1;animation-range:calc(100% - var(--dg-sticky-edge-range)) 100%}}@keyframes _stickyEdgeIn_1vw51_1{0%{opacity:0}to{opacity:1}}@keyframes _stickyEdgeOut_1vw51_1{0%{opacity:1}to{opacity:0}}._headerCell_188ed_1{padding:0 var(--dg-padding);min-width:0;min-height:var(--dg-header-height);font-weight:600;font-size:var(--dg-font-size);background-color:var(--mantine-color-body);border-bottom:1px solid var(--mantine-color-default-border);-webkit-user-select:none;user-select:none;align-items:center;gap:2px;display:flex;position:relative}._headerCell_188ed_1[data-align=right]{justify-content:flex-end}._headerCell_188ed_1[data-align=center]{justify-content:center}._headerCell_188ed_1[data-control-column=true]{padding:0}._headerCellFiltered_188ed_35 ._headerTitle_188ed_35{color:var(--mantine-primary-color-filled)}._headerTitle_188ed_35{text-overflow:ellipsis;white-space:nowrap;flex:auto;min-width:0;overflow:hidden}._headerCell_188ed_1[data-align=right] ._headerTitle_188ed_35{text-align:right}._headerCell_188ed_1[data-align=center] ._headerTitle_188ed_35{justify-content:center;display:flex}._headerActions_188ed_58{flex-shrink:0;align-items:center;gap:0;margin-left:auto;display:flex}._headerCell_188ed_1[data-align=right] ._headerActions_188ed_58{order:-1;margin-left:0;margin-right:auto}._headerAction_188ed_58{display:none}._headerCell_188ed_1:hover ._headerAction_188ed_58,._headerCell_188ed_1:focus-within ._headerAction_188ed_58,._headerAction_188ed_58[data-pinned-visible=true]{display:flex}._headerCellSortable_188ed_87{cursor:pointer}._sortAction_188ed_93[data-sorted=false]{opacity:.55}._sortAction_188ed_93[data-sorted=false]:hover{opacity:1}._sortIndex_188ed_103{color:var(--mantine-color-dimmed);text-align:center;min-width:10px;font-size:10px;font-weight:600;line-height:1}._headerCellDragging_188ed_115{opacity:.4}._headerCellDropBefore_188ed_119:before,._headerCellDropAfter_188ed_120:before{content:"";background-color:var(--mantine-primary-color-filled);z-index:5;pointer-events:none;width:2px;position:absolute;top:0;bottom:0}._headerCellDropBefore_188ed_119:before{left:0}._headerCellDropAfter_188ed_120:before{right:0}._columnSeparator_188ed_140{z-index:1;justify-content:center;width:10px;height:50%;display:flex;position:absolute;top:25%;right:0;transform:translate(50%)}._columnSeparatorResizable_188ed_152{cursor:col-resize;touch-action:none}._columnSeparator_188ed_140:after{content:"";background-color:#0000;width:1px;height:100%;transition:background-color .12s,width .12s}._headerCell_188ed_1:hover ._columnSeparator_188ed_140:after,._headerCell_188ed_1:focus-within ._columnSeparator_188ed_140:after{background-color:var(--mantine-color-default-border)}._columnSeparatorResizable_188ed_152:hover:after,._columnSeparatorActive_188ed_175:after{background-color:var(--mantine-primary-color-filled);width:2px}._menuItemActive_188ed_181{background-color:var(--mantine-color-default-hover);font-weight:600}._toolbar_1459c_1{align-items:center;gap:var(--mantine-spacing-xs);padding:6px var(--mantine-spacing-xs);flex-shrink:0;min-height:42px;display:flex}._toolbarSpacer_1459c_10{flex:1}._summaryCount_1459c_14{white-space:nowrap}
2
2
  /*$vite$:1*/
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jielga/tmdatagrid",
3
- "version": "2.0.0-beta.4",
3
+ "version": "2.0.0-beta.7",
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",
@@ -16,7 +16,7 @@ description: >
16
16
  metadata:
17
17
  type: core
18
18
  library: '@jielga/tmdatagrid'
19
- library_version: '2.0.0-beta.4'
19
+ library_version: '2.0.0-beta.7'
20
20
  sources:
21
21
  - 'Jielga/TMDataGrid:src/docs/styling.md'
22
22
  - 'Jielga/TMDataGrid:src/docs/toolbar.md'
@@ -14,7 +14,7 @@ description: >
14
14
  metadata:
15
15
  type: core
16
16
  library: '@jielga/tmdatagrid'
17
- library_version: '2.0.0-beta.4'
17
+ library_version: '2.0.0-beta.7'
18
18
  sources:
19
19
  - 'Jielga/TMDataGrid:src/docs/cell-selection.md'
20
20
  - 'Jielga/TMDataGrid:src/tmdatagrid/core/cellNavigation.ts'
@@ -16,7 +16,7 @@ description: >
16
16
  metadata:
17
17
  type: core
18
18
  library: '@jielga/tmdatagrid'
19
- library_version: '2.0.0-beta.4'
19
+ library_version: '2.0.0-beta.7'
20
20
  sources:
21
21
  - 'Jielga/TMDataGrid:src/docs/columns.md'
22
22
  - 'Jielga/TMDataGrid:src/docs/column-layout.md'
@@ -55,6 +55,26 @@ const columns = columnHelper.columns([
55
55
  Define columns at module scope. A new array on every render rebuilds the table's
56
56
  column model.
57
57
 
58
+ A dotted `accessorKey` reads a nested field, and the column id it derives
59
+ replaces the dots: `accessorKey: "address.city"` makes column id
60
+ `address_city`, while the edit path stays `"address.city"`. Anything that
61
+ addresses the column by id - `initialState`, `editing.columns`, `moveColumn`,
62
+ a test selector - takes the underscore form.
63
+
64
+ `columnHelper.group` nests columns under a shared header; `columns` takes
65
+ another `columnHelper.columns` call, not a bare array:
66
+
67
+ ```tsx
68
+ columnHelper.group({
69
+ id: "person",
70
+ header: "Person",
71
+ columns: columnHelper.columns([
72
+ columnHelper.accessor("firstName", { header: "First" }),
73
+ columnHelper.accessor("lastName", { header: "Last" }),
74
+ ]),
75
+ });
76
+ ```
77
+
58
78
  ## meta
59
79
 
60
80
  | Field | Type | Default | Description |
@@ -246,7 +266,7 @@ asks for it.
246
266
  | Checkbox | `SELECT_COLUMN_ID` | Selection is on and the mode has checkboxes |
247
267
  | Tree | `GROUP_COLUMN_ID` | A column is grouped |
248
268
  | Details | `DETAILS_COLUMN_ID` | `renderDetails` is set |
249
- | Edit | `EDIT_COLUMN_ID` | Row mode, draft mode, or `editing.onRowDelete` |
269
+ | Edit | `EDIT_COLUMN_ID` | Row mode, `editing.draft`, or `editing.onRowDelete` |
250
270
 
251
271
  They are structural: fixed width, no column menu, and they cannot be sorted,
252
272
  filtered, resized, re-pinned or moved. The checkbox lane anchors the left pinned
@@ -281,6 +301,28 @@ columnHelper.accessor("email", {
281
301
 
282
302
  Source: `src/docs/column-layout.md` (Sizing).
283
303
 
304
+ ### HIGH Addressing a dotted column by its accessor key
305
+
306
+ A dotted `accessorKey` produces a column id with underscores, so state and
307
+ calls keyed by column id silently match nothing when given the dotted form.
308
+
309
+ Wrong:
310
+
311
+ ```tsx
312
+ initialState: { columnVisibility: { "address.city": false } },
313
+ ```
314
+
315
+ Correct:
316
+
317
+ ```tsx
318
+ initialState: { columnVisibility: { address_city: false } },
319
+ ```
320
+
321
+ The edit path is the exception: `meta.edit.field` and validation issue paths
322
+ stay dotted, because they address the data, not the column.
323
+
324
+ Source: `src/docs/columns.md` (The column helper).
325
+
284
326
  ### CRITICAL A component header without `meta.label`
285
327
 
286
328
  `getColumnLabel(column)` falls back to a *string* header, then to the column id.
@@ -6,16 +6,16 @@ description: >
6
6
  enablePagination, manualPagination), the Footer's renderPagination slot over {
7
7
  state, actions, Controls }, getTMDataGridPaginationApi, isPagingActive versus
8
8
  canPaginate, always-on virtualization with overscan and meta.rowHeight,
9
- scrollToRow and scrollerRef, the edge callbacks onScrollToBottom /
10
- onScrollToRight and why onReachEnd is better for loading more, the header and
11
- pinned-lane depth shadows, and the four empty states in precedence order with
12
- meta.loading, renderEmptyState, hasActiveFilters, TMDataGrid.LoadingIndicator
13
- and TMDataGrid.SummaryCount. Load when adding a pager, tuning scrolling,
14
- scrolling to a row, or deciding what an empty grid should say.
9
+ scrollToRow, the edge callbacks onScrollToBottom / onScrollToRight and why
10
+ onReachEnd is better for loading more, the header and pinned-lane depth
11
+ shadows, and the four empty states in precedence order with meta.loading,
12
+ renderEmptyState, hasActiveFilters, TMDataGrid.LoadingIndicator and
13
+ TMDataGrid.SummaryCount. Load when adding a pager, tuning scrolling, scrolling
14
+ to a row, or deciding what an empty grid should say.
15
15
  metadata:
16
16
  type: core
17
17
  library: '@jielga/tmdatagrid'
18
- library_version: '2.0.0-beta.4'
18
+ library_version: '2.0.0-beta.7'
19
19
  sources:
20
20
  - 'Jielga/TMDataGrid:src/docs/pagination.md'
21
21
  - 'Jielga/TMDataGrid:src/docs/scrolling.md'
@@ -118,8 +118,13 @@ grid.scrollToRow({ rowId: "42", align: "center" });
118
118
 
119
119
  `scrollToRow` exists because the target row may not be mounted, and
120
120
  `element.scrollIntoView()` cannot find an element that is not rendered. `align`
121
- is `"start"`, `"center"`, `"end"` or `"auto"`. `scrollerRef` is the scroll
122
- container itself.
121
+ is `"start"`, `"center"`, `"end"` or `"auto"`. It answers whether the row could
122
+ be reached: `false` when it is filtered out, on another page or an id matching
123
+ no row, and a pinned row answers `true` without scrolling.
124
+
125
+ The hook reaches the virtualizer through `scrollerRef`, which
126
+ `TMDataGrid.Table` fills in. That is internal wiring, not an API - spread the
127
+ whole grid object onto `<TMDataGrid>` or nothing scrolls.
123
128
 
124
129
  `TMDataGrid.Table` reports arrivals at each edge - `onScrollToTop`,
125
130
  `onScrollToBottom`, `onScrollToLeft`, `onScrollToRight` - firing **once** on
@@ -286,7 +291,6 @@ Source: `src/docs/pagination.md` (Grouping suspends it).
286
291
  | `overscan` | Option | `number` | `6` | Rows kept mounted beyond each edge of the viewport. |
287
292
  | `meta.rowHeight` | Option | `number` | From `size` | Row height in pixels. The virtualizer needs a number. |
288
293
  | `scrollToRow` | Hook return | `({ rowId, align? }) => boolean` | `align: "auto"` | Scrolls a row into view, mounted or not. |
289
- | `scrollerRef` | Hook return | `RefObject<HTMLDivElement>` | – | The scroll container. |
290
294
  | `onScrollToTop` · `onScrollToBottom` · `onScrollToLeft` · `onScrollToRight` | Table props | `() => void` | – | Fire once on arriving at that edge. |
291
295
  | `TMDataGridScrollAlign` | Export | `"start" \| "center" \| "end" \| "auto"` | – | The `align` argument. |
292
296
  | `meta.loading` | Option | `boolean` | `false` | A fetch is in flight. Takes precedence over every empty message. |
@@ -1,24 +1,24 @@
1
1
  ---
2
2
  name: editing
3
3
  description: >
4
- Edit cells and rows in TMDataGrid. Covers the editing option with its four
5
- mode policies (cell, cellConfirm, row, draft), the required getRowId,
6
- editing.onCommit and editing.onSaveDrafts, why the grid never mutates data,
7
- per-column gating with meta.edit.enabled and meta.edit.field, the six built-in
8
- editors picked by meta.type, custom editors through meta.edit.editor,
9
- per-keystroke value mapping with meta.edit.mapValue, field validation with
10
- meta.edit.validate and cross-field rules with editing.rowValidators (Standard
11
- Schema and Zod), adding and deleting rows with edit.addRow,
12
- editing.newRowDefaults, editing.onRowAdd and editing.onRowDelete, the
13
- generated edit lane, TMDataGrid.EditActions with its renderActions slot, and
14
- the public edit engine (begin, commit, commitAll, saveDrafts, addRows,
15
- getForm, store). Load when making a grid editable, choosing an edit mode,
16
- wiring a save, writing a cell editor, validating an edit, or when cells will
17
- not open.
4
+ Edit cells and rows in TMDataGrid. Covers the editing option and its two axes
5
+ (mode: cell, cellConfirm, row; draft), the required getRowId, editing.onCommit
6
+ and editing.onSaveDrafts, why the grid never mutates data, gating with
7
+ editing.columns, meta.edit.enabled and meta.edit.field, the built-in editors
8
+ picked by meta.type, custom editors via meta.edit.editor, value mapping with
9
+ meta.edit.mapValue, validation at every level - meta.edit.validate,
10
+ cross-field editing.rowValidators, cross-row editing.tableValidators over the
11
+ draft-overlaid collection - adding and deleting rows (edit.addRow,
12
+ newRowDefaults, onRowAdd, onRowDelete), the edit lane, TMDataGrid.DraftActions
13
+ and renderActions, and the edit engine (begin, commit, commitAll, saveDrafts,
14
+ addRows, setCellValue, setRowValues, getForm, store). Load when making a grid
15
+ editable, choosing an edit mode, wiring a save, writing a cell editor,
16
+ validating an edit, writing cells from a toolbar action or bulk fill, or when
17
+ cells will not open.
18
18
  metadata:
19
19
  type: core
20
20
  library: '@jielga/tmdatagrid'
21
- library_version: '2.0.0-beta.4'
21
+ library_version: '2.0.0-beta.7'
22
22
  sources:
23
23
  - 'Jielga/TMDataGrid:src/docs/editing.md'
24
24
  - 'Jielga/TMDataGrid:src/docs/query-builder.md'
@@ -71,30 +71,32 @@ const grid = useTMDataGrid({
71
71
 
72
72
  Two rules are compile errors, not options that silently do nothing: `editing`
73
73
  requires `getRowId`, and `editing.onSaveDrafts` exists only under
74
- `editing.mode: "draft"`. Draft _without_ `onSaveDrafts` is fine: `saveDrafts`
75
- falls back to the per-row `editing.onCommit` loop.
74
+ `editing.draft: true`. A draft store _without_ `onSaveDrafts` is fine:
75
+ `saveDrafts` falls back to the per-row `editing.onCommit` loop.
76
76
 
77
- ## The four modes
77
+ ## The two axes
78
78
 
79
- All four use the same engine and the same forms. `editing.mode` sets what counts
80
- as a commit, and which controls trigger it.
79
+ `editing.mode` sets what counts as a commit; `editing.draft` sets where that commit goes.
80
+ They are independent, and every pair is legal.
81
81
 
82
82
  | Mode | Commits on | Cancels on | Controls |
83
83
  | --- | --- | --- | --- |
84
- | `"cell"` | Enter, Tab, blur | Escape | none |
85
- | `"cellConfirm"` | ✓ or Enter; blur keeps the draft | ✕ or Escape | ✓ / ✕ beside the input |
86
- | `"row"` | Save in the edit lane, or Ctrl+Enter | Cancel, or Escape | generated edit lane |
87
- | `"draft"` | Enter or the lane's ✓, into the draft store | `edit.cancelAll()`, or per row in the lane | `TMDataGrid.EditActions` + the edit lane |
84
+ | `"cell"` | Enter, Tab, leaving the cell | Escape | none |
85
+ | `"cellConfirm"` | ✓ or Enter; Tab and leaving keep the draft | ✕ or Escape | ✓ / ✕ beside the input |
86
+ | `"row"` | Save in the edit lane, or Enter | Cancel, or Escape | generated edit lane |
88
87
 
89
- Which to pick: `"cell"` for saved-as-you-go spreadsheet feel; `"cellConfirm"`
90
- when a stray click must not fire a request; `"row"` when the row is the unit of
91
- the save or a rule spans two columns; `"draft"` for many edits sent as one
92
- transaction.
88
+ | `editing.draft` | Where a commit goes |
89
+ | --- | --- |
90
+ | `false` (default) | Straight out: `onCommit`, `onRowAdd`, `onRowDelete` |
91
+ | `true` | Into the grid's draft store, until `edit.saveDrafts()` sends the lot |
92
+
93
+ Which to pick: `"cell"` for spreadsheet feel; `"cellConfirm"` when a stray click must not fire a request; `"row"` when the row is the unit of the save or a rule spans two columns.
94
+ Add `draft: true` for many edits sent as one transaction - `{ mode: "row", draft: true }` parks a whole row from the lane's ✓, `{ mode: "cell", draft: true }` parks a row as the caret leaves it.
93
95
 
94
96
  An editor opens on double-click, or with the cell cursor on the cell: Enter, F2,
95
97
  or typing, where the first character replaces the value. Delete or Backspace
96
- clears the value and commits without opening an editor; under `"draft"` the
97
- cleared value is held with the other drafts instead. Editing implies cell
98
+ clears the value and commits without opening an editor; under `draft: true`
99
+ the cleared value is held with the other drafts instead. Editing implies cell
98
100
  selection: `cellSelection` defaults to `"single"` while `editing` is set. The
99
101
  grid places the caret in the cell that was opened, and `edit.addRow()` places it
100
102
  in the new row's first editable cell, so a `meta.edit.editor` receives focus
@@ -105,14 +107,11 @@ the cell clicked, opens every editable cell of the row, and ✓ saves them as on
105
107
  commit. Rows accumulate: opening a second row leaves the first open, and each
106
108
  row's ✓ and ✕ act on that row alone.
107
109
 
108
- Under `"draft"` nothing reaches a callback until `saveDrafts`. Enter commits the
109
- row into the draft store; Tab holds
110
- the draft, Tab moving on to the next editable cell, Escape drops that one draft,
111
- and drafts accumulate across rows, surviving filters, sorts and scrolling.
112
- `edit.commit(rowId)` validates and holds the draft too, so there is no per-row
113
- escape hatch to the consumer. A held draft is displayed: the cell renders the
114
- draft value through the column's own `cell` renderer, with the blue corner
115
- marking it dirty and `data-dirty` on the row.
110
+ Under `draft: true` nothing reaches a callback until `saveDrafts`.
111
+ The mode's own commit gesture parks the row instead of sending it, Escape drops that one draft, and parked rows accumulate, surviving filters, sorts and scrolling.
112
+ `edit.commit(rowId)` parks too, so there is no per-row escape hatch to the consumer.
113
+ A parked row is displayed: the cell renders the draft value through the column's own `cell` renderer, with the blue corner marking it dirty and `data-dirty` on the row.
114
+ An entry row is row-shaped in every mode - every editable cell open at once, the browser's Tab, and the lane's ✓ to enter it.
116
115
 
117
116
  The edit lane is the change indicator and the per-row undo: an edited row shows
118
117
  a pencil icon and Revert, which drops that row's draft; a new row a plus icon, a
@@ -158,6 +157,7 @@ records, so `accessorKey: "address.city"` edits `values.address.city`.
158
157
 
159
158
  | Gate | Effect |
160
159
  | --- | --- |
160
+ | `editing.columns: ["targetPct"]` | Only the named columns edit |
161
161
  | `meta.edit.enabled: false` | The column never edits |
162
162
  | `meta.edit.enabled: (row) => boolean` | Per row, per column |
163
163
  | `meta.edit.field: "lastName"` | The path an `accessorFn` column writes to |
@@ -166,6 +166,11 @@ records, so `accessorKey: "address.city"` edits `values.address.city`.
166
166
  Group rows and the generated lanes (checkbox, row number, details, edit) never
167
167
  edit.
168
168
 
169
+ `editing.columns` lists the column ids that take edits; unset, the default, every column mapping to a data path is editable.
170
+ It gates before `meta.edit`, never past it: a column left out takes no edits whatever its own meta says, and a listed column still answers to its `meta.edit.enabled`.
171
+ The same list decides which cells an entry row opens.
172
+ `edit.isColumnEditable(column)` answers the column's half of the question with no row in hand, for a toolbar or a menu, and `edit.canEditCell(row, column)` asks both halves.
173
+
169
174
  ```tsx
170
175
  // Computed column writing back to a real field, and per-row gating.
171
176
  columnHelper.accessor((row) => `${row.firstName} ${row.lastName}`, {
@@ -184,7 +189,7 @@ columnHelper.accessor("salary", {
184
189
  `meta.type` picks the editor - `"string"` (the default), `"number"`,
185
190
  `"boolean"`, `"date"`, `"select"` and `"multiSelect"` - and `meta.options` feeds
186
191
  the two select editors from the same declaration the filter panel reads. Each
187
- one, with the export that wraps it, is in
192
+ one, with the value it writes into the draft and the export that wraps it, is in
188
193
  [references/editors-and-validation.md](references/editors-and-validation.md#the-built-in-editors).
189
194
 
190
195
  `meta.edit.editor` replaces one, `meta.edit.validate` guards the field, and
@@ -197,7 +202,7 @@ import { z } from "zod";
197
202
  // Field level, on the column. A bare schema means { onChange: schema }.
198
203
  meta: { edit: { validate: z.string().min(2, "At least two characters") } }
199
204
 
200
- // Form level, inside `editing`. Cross-field rules, under "row" or "draft".
205
+ // Form level, inside `editing`. Cross-field rules, under "row".
201
206
  rowValidators: {
202
207
  onSubmit: z
203
208
  .object({ salary: z.number().positive(), status: z.string() })
@@ -210,6 +215,24 @@ rowValidators: {
210
215
  Pathed issues land on the matching cells, pathless ones on the row, and cell
211
216
  corners mark both: blue for a dirty draft, red for a validation error.
212
217
 
218
+ `editing.tableValidators` carries the rules that need the other rows - no
219
+ duplicate keys, no overlapping ranges, shares summing to a total. Its
220
+ `onSubmit` / `onSubmitAsync` receive `{ value, rowId, isNew, rows }`, where
221
+ `rows` is the collection as it would stand if the commit landed: every draft
222
+ overlaid, entry rows appended, deletion-marked rows removed. Same result
223
+ vocabulary as `rowValidators`; errors land on the committing row. The rules
224
+ re-run per parked row during `saveDrafts`, so a draft a later edit
225
+ invalidated blocks the save.
226
+
227
+ ```tsx
228
+ tableValidators: {
229
+ onSubmit: ({ value, rowId, rows }) =>
230
+ rows.some((r) => r.rowId !== rowId && r.value.code === value.code)
231
+ ? { fields: { code: "Codes must be unique" } }
232
+ : undefined,
233
+ }
234
+ ```
235
+
213
236
  `meta.edit.mapValue` rewrites a value instead of rejecting it: uppercase a code,
214
237
  strip spaces from an IBAN, clamp a number. It runs on every write an editor
215
238
  makes, so a text input maps per keystroke, and what it returns is what the
@@ -227,12 +250,12 @@ Detail for all three: [references/editors-and-validation.md](references/editors-
227
250
  from `editing.newRowDefaults`. `edit.addRow(values)` overrides that seed key by
228
251
  key, so `addRow()` opens the `newRowDefaults` row and `addRow(values)` opens it
229
252
  with those fields filled in - pass a whole row to duplicate it. Enter, or the
230
- lane's ✓, commits the add through `editing.onRowAdd` under the immediate modes;
231
- under draft mode it puts the row in the draft store, validated, and
253
+ lane's ✓, commits the add through `editing.onRowAdd`; under `draft: true` it
254
+ parks the row in the draft store, validated, and
232
255
  `saveDrafts` reports it in `added`. Escape, or ✕, discards the entry. An entry
233
256
  row never OK'd is not part of a save - it stays open.
234
257
 
235
- Under `"draft"` an entered row renders as a value row with no inputs, marked
258
+ Under `draft: true` an entered row renders as a value row with no inputs, marked
236
259
  `data-new` and `data-committed` and tinted with `--dg-row-new-bg`. By default it
237
260
  joins the scrolling flow above the body rows; `editing.newRowsSticky: true`
238
261
  keeps committed rows pinned in the entry block until the save. Double-click, or
@@ -246,7 +269,8 @@ const grid = useTMDataGrid({
246
269
  columns,
247
270
  getRowId: (row) => String(row.id),
248
271
  editing: {
249
- mode: "draft",
272
+ mode: "row",
273
+ draft: true,
250
274
  newRowDefaults: () => ({ id: 0, firstName: "", salary: 30_000 }),
251
275
  onSaveDrafts: async ({ updated, created, deleted }) => {
252
276
  await api.saveBatch({ updated, created, deleted });
@@ -260,7 +284,7 @@ const grid = useTMDataGrid({
260
284
  Add senior
261
285
  </Button>
262
286
  <TMDataGrid.Spacer />
263
- <TMDataGrid.EditActions />
287
+ <TMDataGrid.DraftActions />
264
288
  </TMDataGrid.Toolbar>;
265
289
  ```
266
290
 
@@ -277,33 +301,44 @@ if (open.length > 0) notify(`${open.length} rows need attention`);
277
301
  await grid.edit.saveDrafts();
278
302
  ```
279
303
 
280
- `edit.deleteRow(rowId)` calls `editing.onRowDelete({ rowId, row })` immediately
281
- under the immediate modes, so put any confirmation inside that callback. Under
282
- draft mode it toggles a mark instead: the row renders struck through and inert
304
+ `edit.deleteRow(rowId)` calls `editing.onRowDelete({ rowId, row })`
305
+ immediately, so put any confirmation inside that callback. Under `draft: true`
306
+ it toggles a mark instead: the row renders struck through and inert
283
307
  (`data-deleted`), the lane shows Restore, and `saveDrafts` reports the ids in
284
308
  `deleted`. You apply adds and deletes, the same as edits. The engine's `tempId`
285
309
  (`__new__1`, …) does not need to become a real id.
286
310
 
287
311
  ## The built-in controls
288
312
 
289
- The generated edit lane (`EDIT_COLUMN_ID`, pinned right) appears when
290
- `editing.mode` is `"row"` or `"draft"`, or when `editing.onRowDelete` is set.
291
- Nothing else adds it, and `"cell"` mode renders no controls of its own. Under
292
- `"row"` it holds Save and Cancel while a row is open; under `"draft"` it holds
293
- the row-state marker with Revert and Restore, and Save and Cancel never appear.
313
+ The generated edit lane (`EDIT_COLUMN_ID`, pinned right) appears when `editing.mode` is `"row"`, when `editing.draft` is on, or when `editing.onRowDelete` is set.
314
+ Nothing else adds it.
315
+ It holds one thing per axis: the mode's own controls while a row is open - Save and Cancel under `"row"` - and, once a row is parked, the row-state marker with Revert or Restore.
316
+ A parked row never offers a save.
317
+ The trash shows when the deletion has somewhere to report to: `onRowDelete` is set, or under `draft: true`, `onSaveDrafts` is.
318
+ If validation blocks a row, its marker - or the open row's ✓ - turns red with the message in the tooltip, which is where a pathless `rowValidators` message shows.
294
319
  Every control carries a tooltip from the labels.
295
320
 
296
- `TMDataGrid.EditActions` is Save with the pending count plus Discard. It greys
297
- out while nothing is pending, spins while a submit is in flight, renders nothing
321
+ `TMDataGrid.DraftActions` is Save with the draft-store count, Discard, and a
322
+ note counting the rows still open. Save greys out while the store is empty
323
+ however much is being typed, spins while a submit is in flight, renders nothing
298
324
  while editing is off, and works under any mode, not only draft.
299
325
 
300
- `renderActions` replaces the pair and hands over its pieces:
326
+ `renderActions` replaces the set and hands over its pieces:
301
327
 
302
328
  ```tsx
303
- <TMDataGrid.EditActions
304
- renderActions={({ state, Controls }) => (
329
+ <TMDataGrid.DraftActions
330
+ renderActions={({ state, actions, Controls }) => (
305
331
  <Group>
306
- {state.pendingCount > 0 && <Badge>{state.pendingCount}</Badge>}
332
+ {state.draftCount > 0 && <Badge>{state.draftCount}</Badge>}
333
+ <Button
334
+ disabled={state.openCount === 0}
335
+ onClick={() => {
336
+ actions.scrollToFirstOpenRow("center");
337
+ }}
338
+ >
339
+ Go to open row
340
+ </Button>
341
+ <Controls.OpenRowsNote />
307
342
  <Controls.Save />
308
343
  <Controls.Discard />
309
344
  </Group>
@@ -311,23 +346,55 @@ while editing is off, and works under any mode, not only draft.
311
346
  />
312
347
  ```
313
348
 
314
- `state` is `{ pendingCount, isSubmitting }`, `actions` is `{ save, discard }`.
349
+ `state` is
350
+ `{ draftCount, openCount, openRowIds, pendingCount, isSubmitting }` -
351
+ `pendingCount` deprecated, reading as `draftCount + openCount`. `actions` is
352
+ `{ save, commitAll, discard, scrollToRow, scrollToFirstOpenRow }`, and
353
+ `Controls` is `{ Save, Discard, OpenRowsNote }`.
354
+
355
+ The grid is always virtualized, so an open row far down the list has no element
356
+ to scroll to. `actions.scrollToFirstOpenRow(align?)` moves the virtualizer to
357
+ the topmost open row and answers whether it could be reached; `false` means
358
+ every open row is filtered out, on another page or collapsed in a group. An
359
+ open entry row answers `true` without scrolling - the entry block is sticky, so
360
+ it is on screen already.
361
+
362
+ The two orderings differ: `state.openRowIds` is the order the grid opened the
363
+ rows, `scrollToFirstOpenRow` is display order. `openRowIds[0]` need not be the
364
+ row it reaches.
315
365
 
316
366
  ## The engine: `edit`
317
367
 
318
368
  `grid.edit` is public, and everything the built-in controls do goes through it:
319
369
  `begin` and `commit`, `cancel` / `cancelAll`, `commitAll` / `saveDrafts`,
320
- `addRow` / `addRows` /
370
+ `setCellValue` / `setRowValues` / `clearCell`, `addRow` / `addRows` /
321
371
  `deleteRow`, `getForm`, and `store` for `useSelector` (an example is under
322
372
  [Submitting an outer form](#high-submitting-an-outer-form-while-the-grid-holds-a-draft)).
323
- Every member with its signature, the gates, `clearCell`, `deactivate`, and the
324
- `edit.store` shape are in
373
+ `edit.store` publishes each open or parked row's drafted values as
374
+ `rows[rowId].values`, which is what a computed cell or a cross-row check reads
375
+ - `useTMDataGridContext()` reaches the engine from inside a cell renderer.
376
+ Every member with its signature, the gates, `isColumnEditable`, `deactivate`,
377
+ and the `edit.store` shape are in
325
378
  [references/editing-api.md](references/editing-api.md#the-edit-engine).
326
379
 
327
380
  `getForm` exposes the row's form: render it in a drawer and it shares values,
328
381
  dirty state and errors with the inline cells, because it is the same
329
382
  `FormApi`.
330
383
 
384
+ `edit.setCellValue(rowId, columnId, value)` writes one cell and commits its row with no editor open, which is what a toolbar action or a bulk fill wants.
385
+ The row need not be mounted, so a selected row inside a collapsed group takes the write like any other.
386
+ `edit.setRowValues(rowId, values)` does several cells of one row in a single commit, keyed by column id, all or nothing.
387
+
388
+ ```tsx
389
+ for (const row of grid.table.getSelectedRowModel().rows) {
390
+ await grid.edit.setCellValue(row.id, "targetPct", equalWeight(row.original));
391
+ }
392
+ ```
393
+
394
+ Under `draft: true` each row parks in the draft store like any hand-made edit, with the same change markers and the same per-row revert, and the basket leaves through `saveDrafts`.
395
+ `value` is the stored value: no editor runs, so `meta.edit.mapValue` does not run either, while `meta.edit.validate` does.
396
+ Both resolve `false` when the cell takes no edit - no such row or column, `editing.columns` excludes it, `meta.edit.enabled` is off, or the row is not editable - and when validation refuses the value, which leaves the row open carrying its errors.
397
+
331
398
  ## Inside an outer form
332
399
 
333
400
  A `@tanstack/react-form` form can hold the row array, with the grid as a
@@ -338,10 +405,12 @@ approval, map by row id (never index), and assign negative ids to new rows.
338
405
 
339
406
  The validation split follows from what each side can see: **a rule decidable
340
407
  from one row belongs to the grid (`meta.edit.validate`,
341
- `editing.rowValidators`); a rule needing the other rows or the collection ("has
342
- rows", "no duplicates") belongs to the form's field validator.** A row's form
343
- cannot see the array, and `edit.store` publishes field names but not values, so
344
- the form cannot see a draft.
408
+ `editing.rowValidators`), and a rule needing the other rows belongs to
409
+ `editing.tableValidators`, which is handed the collection with every draft
410
+ overlaid.** A collection rule may live in the outer form's field validator
411
+ instead, where the submit gate is the form's own; `edit.store` publishes each
412
+ row's drafted values as `rows[rowId].values` for a rule there that must count
413
+ pending values.
345
414
 
346
415
  ## Common mistakes
347
416
 
@@ -353,10 +422,13 @@ are in [references/common-mistakes.md](references/common-mistakes.md).
353
422
  | CRITICAL | Expecting the grid to write into `data` - without `editing.onCommit` the cell reverts |
354
423
  | CRITICAL | `getRowId` built from the row index - drafts follow the index, not the record |
355
424
  | HIGH | A cell editor defined inside the component - a new type per render unmounts the editor |
356
- | HIGH | A cross-field rule under `mode: "cell"` - `rowValidators` needs `"row"` or `"draft"` |
425
+ | HIGH | A custom editor that binds no error text - a refused commit shows no message; bind `field.state.meta.errors` |
426
+ | HIGH | A cross-field rule under `mode: "cell"` - `rowValidators` needs `"row"` |
357
427
  | HIGH | An `accessorFn` column with no `meta.edit.field` - it maps to nothing and stays read-only |
358
428
  | HIGH | Swallowing the error in `editing.onCommit` - a resolved catch drops the draft |
359
429
  | HIGH | Submitting an outer form while the grid holds a draft - it saves stale rows |
430
+ | HIGH | A bulk write built from `begin` + `getForm` + `commit` - `getForm` can be `undefined`; `edit.setCellValue` is the write |
431
+ | MEDIUM | A computed column frozen while a row is edited - `accessorFn` reads `data`; read the draft from `edit.store`'s `rows[rowId].values` |
360
432
  | MEDIUM | Reading a commit's result as the saved value - it is a `boolean` about the form |
361
433
  | MEDIUM | Expecting `editing.onRowDelete` to fire under draft - the mark waits for `saveDrafts` |
362
434