@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.
- package/README.md +25 -2
- package/dist/index.d.ts +856 -14
- package/dist/index.js +3370 -1546
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/styles.layer.css +1 -0
- package/package.json +12 -2
- package/skills/columns/SKILL.md +1 -1
- package/skills/features/SKILL.md +1 -1
- package/skills/getting-started/SKILL.md +1 -1
- package/skills/options/SKILL.md +1 -1
- package/skills/server-side/SKILL.md +1 -1
- package/skills/testing/SKILL.md +198 -0
- package/src/tmdatagrid/components/TMDataGrid.module.css +11 -0
- package/src/tmdatagrid/components/TMDataGrid.tsx +34 -0
- package/src/tmdatagrid/components/TMDataGridCellEditor.tsx +256 -0
- package/src/tmdatagrid/components/TMDataGridColumnsPanel.tsx +35 -12
- package/src/tmdatagrid/components/TMDataGridDetailsColumn.tsx +3 -0
- package/src/tmdatagrid/components/TMDataGridEditActions.tsx +62 -0
- package/src/tmdatagrid/components/TMDataGridEditColumn.tsx +237 -0
- package/src/tmdatagrid/components/TMDataGridEntryRows.tsx +164 -0
- package/src/tmdatagrid/components/TMDataGridFilterPanel.tsx +77 -62
- package/src/tmdatagrid/components/TMDataGridFilterPills.tsx +6 -0
- package/src/tmdatagrid/components/TMDataGridFooter.tsx +10 -3
- package/src/tmdatagrid/components/TMDataGridGroupColumn.tsx +2 -0
- package/src/tmdatagrid/components/TMDataGridHeaderCell.module.css +15 -2
- package/src/tmdatagrid/components/TMDataGridHeaderCell.tsx +21 -4
- package/src/tmdatagrid/components/TMDataGridRowNumberColumn.tsx +44 -0
- package/src/tmdatagrid/components/TMDataGridSearch.tsx +2 -0
- package/src/tmdatagrid/components/TMDataGridSelectColumn.tsx +3 -0
- package/src/tmdatagrid/components/TMDataGridTable.module.css +206 -0
- package/src/tmdatagrid/components/TMDataGridTable.tsx +841 -57
- package/src/tmdatagrid/components/TMDataGridToolbar.tsx +12 -7
- package/src/tmdatagrid/components/editors/TMDataGridBooleanEditor.tsx +29 -0
- package/src/tmdatagrid/components/editors/TMDataGridDateEditor.tsx +57 -0
- package/src/tmdatagrid/components/editors/TMDataGridMultiSelectEditor.tsx +45 -0
- package/src/tmdatagrid/components/editors/TMDataGridNumberEditor.tsx +38 -0
- package/src/tmdatagrid/components/editors/TMDataGridSelectEditor.tsx +53 -0
- package/src/tmdatagrid/components/editors/TMDataGridStringEditor.tsx +31 -0
- package/src/tmdatagrid/components/editors/editorShared.ts +48 -0
- package/src/tmdatagrid/components/filters/DgAutocompleteFilter.tsx +46 -0
- package/src/tmdatagrid/components/filters/DgDateRangeFilter.tsx +43 -0
- package/src/tmdatagrid/components/filters/DgRangeSliderFilter.tsx +52 -0
- package/src/tmdatagrid/components/filters/DgTriStateFilter.tsx +38 -0
- package/src/tmdatagrid/components/filters/TMDataGridFilterValueInput.tsx +122 -0
- package/src/tmdatagrid/components/icons.ts +4 -0
- package/src/tmdatagrid/core/autosize.ts +1 -1
- package/src/tmdatagrid/core/capabilities.ts +37 -1
- package/src/tmdatagrid/core/cellNavigation.ts +41 -0
- package/src/tmdatagrid/core/columnUtils.ts +31 -6
- package/src/tmdatagrid/core/editEngine.ts +857 -0
- package/src/tmdatagrid/core/filterControls.ts +45 -0
- package/src/tmdatagrid/core/filterOperators.ts +72 -4
- package/src/tmdatagrid/core/labels.ts +68 -1
- package/src/tmdatagrid/core/labelsSv.ts +24 -1
- package/src/tmdatagrid/core/matchHighlight.ts +96 -0
- package/src/tmdatagrid/core/persistence.ts +97 -3
- package/src/tmdatagrid/core/quickSearch.ts +144 -0
- package/src/tmdatagrid/core/rowPinning.ts +37 -0
- package/src/tmdatagrid/core/rowSelection.ts +10 -1
- package/src/tmdatagrid/index.ts +51 -0
- 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.
|
|
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
|
}
|
package/skills/columns/SKILL.md
CHANGED
package/skills/features/SKILL.md
CHANGED
package/skills/options/SKILL.md
CHANGED
|
@@ -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,
|