@jielga/tmdatagrid 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +947 -64
- package/dist/index.js +2474 -1423
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +2 -2
- package/skills/columns/SKILL.md +1 -1
- package/skills/features/SKILL.md +61 -11
- package/skills/getting-started/SKILL.md +1 -1
- package/skills/options/SKILL.md +12 -2
- package/skills/server-side/SKILL.md +1 -1
- package/src/tmdatagrid/TMDataGridContext.ts +26 -0
- package/src/tmdatagrid/components/TMDataGrid.module.css +10 -0
- package/src/tmdatagrid/components/TMDataGrid.tsx +23 -1
- package/src/tmdatagrid/components/TMDataGridColumnsPanel.tsx +5 -5
- package/src/tmdatagrid/components/TMDataGridDetailsColumn.module.css +33 -0
- package/src/tmdatagrid/components/TMDataGridDetailsColumn.tsx +160 -0
- package/src/tmdatagrid/components/TMDataGridFilterPanel.tsx +111 -33
- package/src/tmdatagrid/components/TMDataGridFilterPills.tsx +5 -4
- package/src/tmdatagrid/components/TMDataGridFooter.tsx +11 -19
- package/src/tmdatagrid/components/TMDataGridGroupColumn.tsx +23 -11
- package/src/tmdatagrid/components/TMDataGridHeaderCell.module.css +15 -3
- package/src/tmdatagrid/components/TMDataGridHeaderCell.tsx +181 -52
- package/src/tmdatagrid/components/TMDataGridSearch.tsx +122 -0
- package/src/tmdatagrid/components/TMDataGridSelectColumn.tsx +23 -13
- package/src/tmdatagrid/components/TMDataGridTable.module.css +127 -6
- package/src/tmdatagrid/components/TMDataGridTable.tsx +1003 -30
- package/src/tmdatagrid/components/TMDataGridToolbar.tsx +30 -7
- package/src/tmdatagrid/components/icons.ts +1 -0
- package/src/tmdatagrid/components/sticky.module.css +77 -16
- package/src/tmdatagrid/core/autosize.ts +125 -0
- package/src/tmdatagrid/core/capabilities.ts +41 -0
- package/src/tmdatagrid/core/cellExport.ts +321 -0
- package/src/tmdatagrid/core/cellNavigation.ts +103 -0
- package/src/tmdatagrid/core/cellRange.ts +118 -0
- package/src/tmdatagrid/core/columnOptions.ts +123 -0
- package/src/tmdatagrid/core/columnUtils.ts +18 -0
- package/src/tmdatagrid/core/expanding.ts +93 -0
- package/src/tmdatagrid/core/filterOperators.ts +176 -15
- package/src/tmdatagrid/core/labels.ts +211 -0
- package/src/tmdatagrid/core/labelsSv.ts +108 -0
- package/src/tmdatagrid/core/summary.ts +35 -0
- package/src/tmdatagrid/index.ts +70 -0
- package/src/tmdatagrid/useTMDataGrid.tsx +328 -12
package/dist/styles.css
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
._root_13dg1_8[data-size=xs]{--dg-row-height:34px;--dg-header-height:32px;--dg-font-size:var(--mantine-font-size-xs);--dg-padding:6px}._root_13dg1_8[data-size=sm]{--dg-row-height:42px;--dg-header-height:38px;--dg-font-size:var(--mantine-font-size-sm);--dg-padding:8px}._root_13dg1_8[data-size=md]{--dg-row-height:52px;--dg-header-height:44px;--dg-font-size:var(--mantine-font-size-sm);--dg-padding:10px}._root_13dg1_8[data-size=lg]{--dg-row-height:62px;--dg-header-height:52px;--dg-font-size:var(--mantine-font-size-md);--dg-padding:14px}._root_13dg1_8[data-size=xl]{--dg-row-height:72px;--dg-header-height:60px;--dg-font-size:var(--mantine-font-size-lg);--dg-padding:18px}._root_13dg1_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);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}._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_chabz_2{flex-direction:column;flex:1;min-height:0;display:flex;position:relative}._scrollContainer_chabz_10{flex:1;min-height:0;position:relative;overflow:auto}._grid_chabz_18{display:grid}._headerRow_chabz_24{grid-column:1/-1;grid-template-columns:subgrid;z-index:3;display:grid;position:sticky;top:0}._bodyRow_chabz_33{--row-bg:var(--mantine-color-body);grid-column:1/-1;grid-template-columns:subgrid;align-items:stretch;display:grid}._bodyRow_chabz_33:hover{--row-bg:var(--mantine-color-default-hover)}._bodyRow_chabz_33[data-grouped=true]{--row-bg:var(--dg-row-group-bg)}._bodyRow_chabz_33[data-context-menu=true]{--row-bg:var(--mantine-color-default-hover)}._bodyRow_chabz_33[data-selected-bg=true]{--row-bg:var(--dg-row-selected-bg)}._bodyRow_chabz_33[data-highlighted=true]{--row-bg:var(--dg-row-highlight-bg)}._bodyRow_chabz_33[data-selects-on-click=true]{-webkit-user-select:none;user-select:none}._bodyRow_chabz_33:focus-visible{outline:2px solid var(--mantine-primary-color-filled);outline-offset:-2px}._bodyCell_chabz_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_chabz_91[data-select-column=true]{padding:0}._cellContent_chabz_89{white-space:nowrap;text-overflow:ellipsis;min-width:0;overflow:hidden}._bodyCell_chabz_91[data-align=right]{justify-content:flex-end}._bodyCell_chabz_91[data-align=center]{justify-content:center}._bodyCell_chabz_91[data-align=left] ._cellContent_chabz_89{flex:auto}._messageRow_chabz_129{justify-content:center;align-items:center;gap:var(--mantine-spacing-xs);padding:var(--mantine-spacing-xl);flex-direction:column;grid-column:1/-1;display:flex}._stickyLeft_1iqrf_11{border-right:1px solid var(--mantine-color-default-border)}._stickyRight_1iqrf_15{border-left:1px solid var(--mantine-color-default-border)}._stickyLeft_1iqrf_11:after,._stickyRight_1iqrf_15:after{content:"";pointer-events:none;width:4px;position:absolute;top:0;bottom:0}._stickyLeft_1iqrf_11:after{background:linear-gradient(90deg,var(--lightningcss-light,#00000014)var(--lightningcss-dark,#00000047),#0000);left:100%}._stickyRight_1iqrf_15:after{background:linear-gradient(270deg,var(--lightningcss-light,#00000014)var(--lightningcss-dark,#00000047),#0000);right:100%}._headerCell_49xbo_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_49xbo_1[data-align=right]{justify-content:flex-end}._headerCell_49xbo_1[data-align=center]{justify-content:center}._headerCell_49xbo_1[data-select-column=true]{padding:0}._headerCell_49xbo_1[data-active=true] ._headerTitle_49xbo_31{color:var(--mantine-primary-color-filled)}._headerTitle_49xbo_31{text-overflow:ellipsis;white-space:nowrap;flex:auto;min-width:0;overflow:hidden}._headerCell_49xbo_1[data-align=right] ._headerTitle_49xbo_31{text-align:right}._headerCell_49xbo_1[data-align=center] ._headerTitle_49xbo_31{justify-content:center;display:flex}._headerActions_49xbo_54{flex-shrink:0;align-items:center;gap:0;margin-left:auto;display:flex}._headerCell_49xbo_1[data-align=right] ._headerActions_49xbo_54{order:-1;margin-left:0;margin-right:auto}._headerAction_49xbo_54{display:none}._headerCell_49xbo_1:hover ._headerAction_49xbo_54,._headerCell_49xbo_1:focus-within ._headerAction_49xbo_54,._headerAction_49xbo_54[data-pinned-visible=true]{display:flex}._headerCellSortable_49xbo_83{cursor:pointer}._headerCellDragging_49xbo_90{opacity:.4}._headerCellDropBefore_49xbo_94:before,._headerCellDropAfter_49xbo_95:before{content:"";background-color:var(--mantine-primary-color-filled);z-index:5;pointer-events:none;width:2px;position:absolute;top:0;bottom:0}._headerCellDropBefore_49xbo_94:before{left:0}._headerCellDropAfter_49xbo_95:before{right:0}._columnSeparator_49xbo_115{z-index:1;justify-content:center;width:10px;height:50%;display:flex;position:absolute;top:25%;right:0;transform:translate(50%)}._columnSeparatorResizable_49xbo_127{cursor:col-resize;touch-action:none}._columnSeparator_49xbo_115:after{content:"";background-color:#0000;width:1px;height:100%;transition:background-color .12s,width .12s}._headerCell_49xbo_1:hover ._columnSeparator_49xbo_115:after,._headerCell_49xbo_1:focus-within ._columnSeparator_49xbo_115:after{background-color:var(--mantine-color-default-border)}._columnSeparatorResizable_49xbo_127:hover:after,._columnSeparatorActive_49xbo_150:after{background-color:var(--mantine-primary-color-filled);width:2px}._menuItemActive_49xbo_156{background-color:var(--mantine-color-default-hover);font-weight:600}._toolbar_4my8t_1{align-items:center;gap:var(--mantine-spacing-xs);padding:6px var(--mantine-spacing-xs);flex-shrink:0;min-height:42px;display:flex}._toolbarSpacer_4my8t_10{flex:1}
|
|
1
|
+
._root_qequm_8[data-size=xs]{--dg-row-height:34px;--dg-header-height:32px;--dg-font-size:var(--mantine-font-size-xs);--dg-padding:6px}._root_qequm_8[data-size=sm]{--dg-row-height:42px;--dg-header-height:38px;--dg-font-size:var(--mantine-font-size-sm);--dg-padding:8px}._root_qequm_8[data-size=md]{--dg-row-height:52px;--dg-header-height:44px;--dg-font-size:var(--mantine-font-size-sm);--dg-padding:10px}._root_qequm_8[data-size=lg]{--dg-row-height:62px;--dg-header-height:52px;--dg-font-size:var(--mantine-font-size-md);--dg-padding:14px}._root_qequm_8[data-size=xl]{--dg-row-height:72px;--dg-header-height:60px;--dg-font-size:var(--mantine-font-size-lg);--dg-padding:18px}._root_qequm_8{--dg-row-height:52px;--dg-header-height:44px;--dg-font-size:var(--mantine-font-size-sm);--dg-padding:10px;--dg-row-selected-bg:var(--mantine-primary-color-light);--dg-row-highlight-bg:var(--mantine-primary-color-light-hover);--dg-row-group-bg:var(--mantine-color-body);--dg-z-pinned-cell:2;--dg-z-focused-cell:3;--dg-z-summary-row:4;--dg-z-summary-pinned-cell:5;--dg-z-header:6;--dg-z-header-pinned-cell:7;text-align:left;border:1px solid var(--mantine-color-default-border);border-radius:var(--mantine-radius-md);background-color:var(--mantine-color-body);flex-direction:column;min-height:0;display:flex;overflow:hidden}._columnsPanel_1492o_1{flex-direction:column;width:280px;display:flex}._columnsPanelSearch_1492o_7{padding:var(--mantine-spacing-sm)}._columnsPanelList_1492o_11{gap:var(--mantine-spacing-sm);padding:0 var(--mantine-spacing-sm) var(--mantine-spacing-sm);flex-direction:column;display:flex}._columnsPanelFooter_1492o_18{justify-content:space-between;align-items:center;gap:var(--mantine-spacing-sm);padding:var(--mantine-spacing-sm);border-top:1px solid var(--mantine-color-default-border);display:flex}._detailsToggle_n39tz_3{width:100%;height:100%;color:inherit;justify-content:center;align-items:center;display:flex}._detailsToggle_n39tz_3:hover ._chevron_n39tz_12{color:var(--mantine-color-text)}._chevron_n39tz_12{color:var(--mantine-color-dimmed);align-items:center;transition:transform .12s;display:flex}._chevron_n39tz_12[data-expanded=true]{transform:rotate(90deg)}@media (prefers-reduced-motion:reduce){._chevron_n39tz_12{transition:none}}._groupToggle_h06xv_4{width:100%;min-width:0;font-size:var(--dg-font-size);color:inherit;align-items:center;gap:6px;display:flex}._chevron_h06xv_14{color:var(--mantine-color-dimmed);flex:none;align-items:center;transition:transform .12s;display:flex}._chevron_h06xv_14[data-expanded=true]{transform:rotate(90deg)}@media (prefers-reduced-motion:reduce){._chevron_h06xv_14{transition:none}}._groupLabel_h06xv_34{white-space:nowrap;text-overflow:ellipsis;min-width:0;font-weight:600;overflow:hidden}._groupCount_h06xv_44{color:var(--mantine-color-dimmed);font-variant-numeric:tabular-nums;flex:none}._filterPanel_izakx_4{top:calc(var(--dg-header-height) + 2px);left:var(--mantine-spacing-md);z-index:5;padding:var(--mantine-spacing-sm);border-radius:var(--mantine-radius-md);border:1px solid var(--mantine-color-default-border);background-color:var(--mantine-color-body);box-shadow:var(--mantine-shadow-md);position:absolute}._filterRow_izakx_16{align-items:flex-end;gap:var(--mantine-spacing-xs);display:flex}._filterPanelHeader_izakx_22{justify-content:space-between;align-items:center;gap:var(--mantine-spacing-md);padding-bottom:var(--mantine-spacing-xs);display:flex}._filterPanelFooter_izakx_30{justify-content:space-between;align-items:center;gap:var(--mantine-spacing-md);display:flex}._filterPills_1yybb_3{align-items:center;gap:var(--mantine-spacing-xs);flex-wrap:wrap;display:flex}._filterPills_1yybb_3 ._pill_1yybb_15{background-color:var(--lightningcss-light,var(--mantine-color-gray-1))var(--lightningcss-dark,var(--mantine-color-dark-5));color:var(--lightningcss-light,var(--mantine-color-black))var(--lightningcss-dark,var(--mantine-color-dark-0));border:1px solid var(--lightningcss-light,var(--mantine-color-gray-3))var(--lightningcss-dark,var(--mantine-color-dark-3))}._pillLabel_1yybb_27{font:inherit;color:inherit;cursor:pointer}._pillLabel_1yybb_27:hover{text-decoration:underline}._footer_149ca_1{justify-content:flex-end;align-items:center;gap:var(--mantine-spacing-lg);padding:4px var(--mantine-spacing-sm);border-top:1px solid var(--mantine-color-default-border);flex-shrink:0;min-height:48px;display:flex}._footer_149ca_1[data-paging-suspended=true]{opacity:.55}._tableWrapper_1nt60_2{flex-direction:column;flex:1;min-height:0;display:flex;position:relative}._scrollContainer_1nt60_10{flex:1;min-height:0;position:relative;overflow:auto}._grid_1nt60_18{display:grid}._headerRow_1nt60_24{grid-column:1/-1;grid-template-columns:subgrid;z-index:var(--dg-z-header,6);display:grid;position:sticky;top:0}._bodyRow_1nt60_33{--row-bg:var(--mantine-color-body);grid-column:1/-1;grid-template-columns:subgrid;align-items:stretch;display:grid}._bodyRow_1nt60_33:hover{--row-bg:var(--mantine-color-default-hover)}._bodyRow_1nt60_33[data-grouped=true]{--row-bg:var(--dg-row-group-bg)}._bodyRow_1nt60_33[data-context-menu=true]{--row-bg:var(--mantine-color-default-hover)}._bodyRow_1nt60_33[data-selected-bg=true]{--row-bg:var(--dg-row-selected-bg)}._bodyRow_1nt60_33[data-highlighted=true]{--row-bg:var(--dg-row-highlight-bg)}._bodyRow_1nt60_33[data-selects-on-click=true]{-webkit-user-select:none;user-select:none}._bodyRow_1nt60_33:focus-visible{outline:2px solid var(--mantine-primary-color-filled);outline-offset:-2px}._bodyCell_1nt60_91{padding:0 var(--dg-padding);border-bottom:1px solid var(--mantine-color-default-border);background-color:var(--row-bg);font-size:var(--dg-font-size);align-items:center;display:flex}._bodyCell_1nt60_91[data-control-column=true]{padding:0}._grid_1nt60_18[data-range-dragging]{-webkit-user-select:none;user-select:none}._bodyCell_1nt60_91[data-selected=true]{background-color:color-mix(in srgb, var(--mantine-primary-color-filled) 14%, var(--row-bg));--dg-edge-top:transparent;--dg-edge-bottom:transparent;--dg-edge-left:transparent;--dg-edge-right:transparent;box-shadow:inset 0 1px 0 0 var(--dg-edge-top), inset 0 -1px 0 0 var(--dg-edge-bottom), inset 1px 0 0 0 var(--dg-edge-left), inset -1px 0 0 0 var(--dg-edge-right)}._bodyCell_1nt60_91[data-edge-top=true]{--dg-edge-top:var(--mantine-primary-color-filled)}._bodyCell_1nt60_91[data-edge-bottom=true]{--dg-edge-bottom:var(--mantine-primary-color-filled)}._bodyCell_1nt60_91[data-edge-left=true]{--dg-edge-left:var(--mantine-primary-color-filled)}._bodyCell_1nt60_91[data-edge-right=true]{--dg-edge-right:var(--mantine-primary-color-filled)}._bodyCell_1nt60_91[data-focused=true]{outline:2px solid var(--mantine-primary-color-filled);outline-offset:-2px}._cellContent_1nt60_89{white-space:nowrap;text-overflow:ellipsis;min-width:0;overflow:hidden}._bodyCell_1nt60_91[data-align=right]{justify-content:flex-end}._bodyCell_1nt60_91[data-align=center]{justify-content:center}._bodyCell_1nt60_91[data-align=left] ._cellContent_1nt60_89{flex:auto}._detailsCell_1nt60_204{border-bottom:1px solid var(--mantine-color-default-border);background-color:var(--row-bg);padding:var(--dg-padding);-webkit-user-select:text;user-select:text;cursor:auto;grid-column:1/-1}._messageRow_1nt60_215{justify-content:center;align-items:center;gap:var(--mantine-spacing-xs);padding:var(--mantine-spacing-xl);flex-direction:column;grid-column:1/-1;display:flex}._summaryRow_1nt60_228{grid-column:1/-1;grid-template-columns:subgrid;z-index:var(--dg-z-summary-row,4);display:grid;position:sticky;bottom:0}._summaryCell_1nt60_237{padding:0 var(--dg-padding);border-top:1px solid var(--mantine-color-default-border);background-color:var(--mantine-color-body);font-size:var(--dg-font-size);align-items:center;font-weight:600;display:flex}._summaryCell_1nt60_237[data-control-column=true]{padding:0}._summaryCell_1nt60_237[data-align=right]{justify-content:flex-end}._summaryCell_1nt60_237[data-align=center]{justify-content:center}._stickyLeft_6z1iq_16:after,._stickyRight_6z1iq_17:after{content:"";pointer-events:none;width:8px;position:absolute;top:0;bottom:0}._stickyLeft_6z1iq_16:after{background:linear-gradient(90deg,var(--lightningcss-light,#00000012)var(--lightningcss-dark,#00000042),var(--lightningcss-light,#00000006)var(--lightningcss-dark,#00000017) 45%,#0000);left:100%}._stickyRight_6z1iq_17:after{background:linear-gradient(270deg,var(--lightningcss-light,#00000012)var(--lightningcss-dark,#00000042),var(--lightningcss-light,#00000006)var(--lightningcss-dark,#00000017) 45%,#0000);right:100%}@supports (animation-timeline:scroll()){._stickyLeft_6z1iq_16:after,._stickyRight_6z1iq_17:after{--dg-sticky-edge-range:20px;opacity:0;animation-timing-function:linear;animation-fill-mode:both;animation-timeline:scroll(inline)}._stickyLeft_6z1iq_16:after{animation-name:_stickyEdgeIn_6z1iq_1;animation-range:0 var(--dg-sticky-edge-range)}._stickyRight_6z1iq_17:after{animation-name:_stickyEdgeOut_6z1iq_1;animation-range:calc(100% - var(--dg-sticky-edge-range)) 100%}}@keyframes _stickyEdgeIn_6z1iq_1{0%{opacity:0}to{opacity:1}}@keyframes _stickyEdgeOut_6z1iq_1{0%{opacity:1}to{opacity:0}}._headerCell_1b6k8_1{padding:0 var(--dg-padding);min-width:0;min-height:var(--dg-header-height);font-weight:600;font-size:var(--dg-font-size);background-color:var(--mantine-color-body);border-bottom:1px solid var(--mantine-color-default-border);-webkit-user-select:none;user-select:none;align-items:center;gap:2px;display:flex;position:relative}._headerCell_1b6k8_1[data-align=right]{justify-content:flex-end}._headerCell_1b6k8_1[data-align=center]{justify-content:center}._headerCell_1b6k8_1[data-control-column=true]{padding:0}._headerCell_1b6k8_1[data-active=true] ._headerTitle_1b6k8_32{color:var(--mantine-primary-color-filled)}._headerTitle_1b6k8_32{text-overflow:ellipsis;white-space:nowrap;flex:auto;min-width:0;overflow:hidden}._headerCell_1b6k8_1[data-align=right] ._headerTitle_1b6k8_32{text-align:right}._headerCell_1b6k8_1[data-align=center] ._headerTitle_1b6k8_32{justify-content:center;display:flex}._headerActions_1b6k8_55{flex-shrink:0;align-items:center;gap:0;margin-left:auto;display:flex}._headerCell_1b6k8_1[data-align=right] ._headerActions_1b6k8_55{order:-1;margin-left:0;margin-right:auto}._headerAction_1b6k8_55{display:none}._headerCell_1b6k8_1:hover ._headerAction_1b6k8_55,._headerCell_1b6k8_1:focus-within ._headerAction_1b6k8_55,._headerAction_1b6k8_55[data-pinned-visible=true]{display:flex}._headerCellSortable_1b6k8_84{cursor:pointer}._sortIndex_1b6k8_90{color:var(--mantine-color-dimmed);text-align:center;min-width:10px;font-size:10px;font-weight:600;line-height:1}._headerCellDragging_1b6k8_102{opacity:.4}._headerCellDropBefore_1b6k8_106:before,._headerCellDropAfter_1b6k8_107:before{content:"";background-color:var(--mantine-primary-color-filled);z-index:5;pointer-events:none;width:2px;position:absolute;top:0;bottom:0}._headerCellDropBefore_1b6k8_106:before{left:0}._headerCellDropAfter_1b6k8_107:before{right:0}._columnSeparator_1b6k8_127{z-index:1;justify-content:center;width:10px;height:50%;display:flex;position:absolute;top:25%;right:0;transform:translate(50%)}._columnSeparatorResizable_1b6k8_139{cursor:col-resize;touch-action:none}._columnSeparator_1b6k8_127:after{content:"";background-color:#0000;width:1px;height:100%;transition:background-color .12s,width .12s}._headerCell_1b6k8_1:hover ._columnSeparator_1b6k8_127:after,._headerCell_1b6k8_1:focus-within ._columnSeparator_1b6k8_127:after{background-color:var(--mantine-color-default-border)}._columnSeparatorResizable_1b6k8_139:hover:after,._columnSeparatorActive_1b6k8_162:after{background-color:var(--mantine-primary-color-filled);width:2px}._menuItemActive_1b6k8_168{background-color:var(--mantine-color-default-hover);font-weight:600}._toolbar_4my8t_1{align-items:center;gap:var(--mantine-spacing-xs);padding:6px var(--mantine-spacing-xs);flex-shrink:0;min-height:42px;display:flex}._toolbarSpacer_4my8t_10{flex:1}
|
|
2
2
|
/*$vite$:1*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jielga/tmdatagrid",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "A React data grid built on TanStack Table v9 and Mantine — always virtualized, with resizable, reorderable, sortable, filterable, hideable and pinnable columns.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"type": "git",
|
|
12
12
|
"url": "git+https://github.com/Jielga/TMDataGrid.git"
|
|
13
13
|
},
|
|
14
|
-
"homepage": "https://github.
|
|
14
|
+
"homepage": "https://jielga.github.io/TMDataGrid/",
|
|
15
15
|
"bugs": {
|
|
16
16
|
"url": "https://github.com/Jielga/TMDataGrid/issues"
|
|
17
17
|
},
|
package/skills/columns/SKILL.md
CHANGED
package/skills/features/SKILL.md
CHANGED
|
@@ -5,19 +5,20 @@ description: >
|
|
|
5
5
|
table and column options, and reuse the same checks in your own components.
|
|
6
6
|
Covers the enableSorting/enableColumnFilters/enableHiding/enableColumnPinning/
|
|
7
7
|
enableColumnResizing/enableColumnOrdering/enableRowSelection matrix, the
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
selection modes behind rowSelectionMode, the highlightSelectedRows flag and
|
|
9
|
+
the --dg-row-selected-bg colour, the default-off enablePagination switch and
|
|
10
|
+
its three modes (none, client, manual), getGridCapabilities and
|
|
11
|
+
getColumnCapabilities, why the features argument is required for reactivity
|
|
12
|
+
under the React Compiler, column ordering with moveColumn and
|
|
13
|
+
moveColumnByStep, cell selection with arrow-key navigation, drag-selected
|
|
14
|
+
ranges, Ctrl+C copy and Excel-compatible CSV export, and always-on
|
|
14
15
|
virtualization. Load when building a read-only grid, hiding grid chrome,
|
|
15
|
-
enabling pagination, reordering columns from code,
|
|
16
|
-
button.
|
|
16
|
+
enabling pagination, reordering columns from code, adding cell navigation or
|
|
17
|
+
copy/export, or writing a custom toolbar button.
|
|
17
18
|
metadata:
|
|
18
19
|
type: core
|
|
19
20
|
library: '@jielga/tmdatagrid'
|
|
20
|
-
library_version: '0.
|
|
21
|
+
library_version: '0.5.0'
|
|
21
22
|
sources:
|
|
22
23
|
- 'Jielga/TMDataGrid:src/docs/features.md'
|
|
23
24
|
- 'Jielga/TMDataGrid:src/tmdatagrid/core/capabilities.ts'
|
|
@@ -242,10 +243,59 @@ const next = getStepTargetColumn({ table, columnId: "salary", direction: 1 });
|
|
|
242
243
|
Both movers are no-ops for a move that is not allowed, including one across
|
|
243
244
|
regions.
|
|
244
245
|
|
|
246
|
+
## Cell selection
|
|
247
|
+
|
|
248
|
+
Off by default. `cellSelection: "single"` gives one focused cell moved with the
|
|
249
|
+
arrow keys; `"range"` adds a selectable rectangle, Ctrl+C and CSV export.
|
|
250
|
+
|
|
251
|
+
```tsx
|
|
252
|
+
const grid = useTMDataGrid({ data, columns, cellSelection: "range" });
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
On, the body's tab stop moves from the row to a cell (the whole grid is one Tab
|
|
256
|
+
stop), the container reports `role="grid"` with `gridcell` children, and cells
|
|
257
|
+
carry `data-focused` / `data-selected` / `data-edge-*`.
|
|
258
|
+
|
|
259
|
+
| Key | Does |
|
|
260
|
+
| --- | --- |
|
|
261
|
+
| Arrows | one cell, clamped at the edges |
|
|
262
|
+
| Shift+arrows | extends the rectangle from its anchor |
|
|
263
|
+
| PageUp / PageDown | one viewport of rows |
|
|
264
|
+
| Home / End, Ctrl+Home / Ctrl+End | ends of the row, corners of the grid |
|
|
265
|
+
| Enter or F2 | steps into the cell's control; Escape steps back out |
|
|
266
|
+
| Space | selects the row |
|
|
267
|
+
| Ctrl+C | copies the block as tab-separated text |
|
|
268
|
+
|
|
269
|
+
The body is one tab stop: controls inside body cells take `tabindex="-1"` while
|
|
270
|
+
cell selection is on, so Tab leaves the grid instead of walking one control per
|
|
271
|
+
mounted row. Enter/F2 steps in, Escape steps out, Space ticks the row from any
|
|
272
|
+
cell. Custom cell controls should do the same —
|
|
273
|
+
`tabIndex={useCellControlTabIndex()}`. Header controls are untouched.
|
|
274
|
+
|
|
275
|
+
The generated lanes (checkbox, tree, details) are selectable and navigable —
|
|
276
|
+
they take the tint and the outline — but never exported; a block covering only
|
|
277
|
+
those disables Copy and Export.
|
|
278
|
+
|
|
279
|
+
Drag across cells to select a rectangle; Shift+click extends one. The state is
|
|
280
|
+
`ui.state.focusedCell` and `ui.state.cellRange`, both held as `{ rowId,
|
|
281
|
+
columnId }` pairs so sorting and filtering carry the selection with the cells.
|
|
282
|
+
Move them with `ui.actions.setFocusedCell` / `setCellRange`, follow them with
|
|
283
|
+
`onFocusedCellChange`. One rectangle at a time.
|
|
284
|
+
|
|
285
|
+
Right-clicking inside the selection offers Copy, "Export as CSV for Excel" and
|
|
286
|
+
an Include headers toggle; a consumer's `rowContextMenu` items follow a divider.
|
|
287
|
+
The CSV is Nordic-Excel shaped — `sep=;` line, UTF-8 BOM, CRLF, semicolons,
|
|
288
|
+
decimal comma — and `cellExport` on `TMDataGrid.Table` changes any of that.
|
|
289
|
+
Generated lanes (checkbox, tree, details) are never exported, and what is
|
|
290
|
+
written is the cell's value rather than what it renders.
|
|
291
|
+
|
|
245
292
|
## Virtualization
|
|
246
293
|
|
|
247
|
-
Always enabled
|
|
248
|
-
|
|
294
|
+
Always enabled. Only rows within the viewport, plus a small overscan, are
|
|
295
|
+
mounted, so page size does not affect how much is rendered.
|
|
296
|
+
|
|
297
|
+
`overscan` on `useTMDataGrid` (default `6`) sets how many rows are kept mounted
|
|
298
|
+
on each side of the viewport — the one configurable part.
|
|
249
299
|
|
|
250
300
|
Row height comes from `meta.rowHeight`, or from the `size` prop when unset. Rows
|
|
251
301
|
are fixed height; an accurate value keeps scrolling precise.
|
package/skills/options/SKILL.md
CHANGED
|
@@ -13,7 +13,7 @@ description: >
|
|
|
13
13
|
metadata:
|
|
14
14
|
type: core
|
|
15
15
|
library: '@jielga/tmdatagrid'
|
|
16
|
-
library_version: '0.
|
|
16
|
+
library_version: '0.5.0'
|
|
17
17
|
sources:
|
|
18
18
|
- 'Jielga/TMDataGrid:src/docs/use-tm-data-grid.md'
|
|
19
19
|
- 'Jielga/TMDataGrid:src/tmdatagrid/useTMDataGrid.tsx'
|
|
@@ -57,6 +57,9 @@ rather than forwarded to TanStack.
|
|
|
57
57
|
| `enableColumnResizing` | `boolean` | `true` | Enables resizing for the table. |
|
|
58
58
|
| `enableColumnOrdering` | `boolean` | `true` | Enables header dragging and the move menu items. Defined by the grid. |
|
|
59
59
|
| `enablePagination` | `boolean` | `false` | Enables client-side paging and the `Footer` pager. Implied by `manualPagination`. Defined by the grid. |
|
|
60
|
+
| `cellSelection` | `"none" \| "single" \| "range"` | `"none"` | Cell cursor; `"range"` adds a drag-selectable rectangle, Ctrl+C and CSV export. Defined by the grid. |
|
|
61
|
+
| `onFocusedCellChange` | `(cell \| null) => void` | – | Fires whenever the focused cell moves. |
|
|
62
|
+
| `overscan` | `number` | `6` | Rows the virtualizer keeps mounted above and below the viewport. Defined by the grid. |
|
|
60
63
|
| `columnResizeMode` | `"onChange" \| "onEnd"` | `"onChange"` | Resize update strategy. |
|
|
61
64
|
| `initialState` | `Partial<TableState>` | See below | Merged over the grid defaults. |
|
|
62
65
|
| `meta` | `TMDataGridTableMeta` | `{}` | Grid configuration, see below. |
|
|
@@ -168,8 +171,15 @@ const filterPanelOpen = useSelector(grid.ui, (state) => state.filterPanelOpen);
|
|
|
168
171
|
| `toggleColumnsPanel` | `() => void` |
|
|
169
172
|
| `startColumnDrag` | `(columnId: string) => void` |
|
|
170
173
|
| `endColumnDrag` | `() => void` |
|
|
174
|
+
| `setHighlightedRow` | `(rowId: string \| null) => void` |
|
|
175
|
+
| `setSelectionAnchor` | `(rowId: string \| null) => void` |
|
|
176
|
+
| `setFocusedCell` | `(cell: TMDataGridCellPosition \| null) => void` |
|
|
177
|
+
| `setCellRange` | `(range: TMDataGridCellRange \| null) => void` |
|
|
171
178
|
|
|
172
|
-
The last two
|
|
179
|
+
The last two move the cell cursor and the selected rectangle under
|
|
180
|
+
`cellSelection`; DOM focus follows `focusedCell` and scrolls its row into view.
|
|
181
|
+
|
|
182
|
+
`startColumnDrag` / `endColumnDrag` are called by the header cells while a column is being dragged, and
|
|
173
183
|
`ui.draggedColumnId` holds the column being moved — browsers keep `dataTransfer`
|
|
174
184
|
unreadable until the drop.
|
|
175
185
|
|
|
@@ -30,3 +30,29 @@ export function useTMDataGridContext(): TMDataGridContextValue {
|
|
|
30
30
|
}
|
|
31
31
|
return api as TMDataGridContextValue;
|
|
32
32
|
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* What a control inside a *body* cell should put in its `tabIndex`.
|
|
36
|
+
*
|
|
37
|
+
* `-1` once cell selection is on, and this is what makes the promise of one tab
|
|
38
|
+
* stop true. Without it the browser walks Tab into the checkbox of every
|
|
39
|
+
* mounted row — a grid showing twenty rows would be twenty tab stops, and
|
|
40
|
+
* scrolling would change how many. Enter or F2 steps into the cell instead,
|
|
41
|
+
* which reaches a `-1` control perfectly well.
|
|
42
|
+
*
|
|
43
|
+
* Header controls are not covered: the header row is not part of cell
|
|
44
|
+
* navigation, so its sort buttons and menus stay in the tab order, where they
|
|
45
|
+
* are the only way to reach them.
|
|
46
|
+
*
|
|
47
|
+
* A custom cell renderer with a control in it wants the same:
|
|
48
|
+
*
|
|
49
|
+
* ```tsx
|
|
50
|
+
* cell: ({ row }) => (
|
|
51
|
+
* <Button tabIndex={useCellControlTabIndex()} onClick={...}>Open</Button>
|
|
52
|
+
* )
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export function useCellControlTabIndex(): 0 | -1 {
|
|
56
|
+
const { features } = useTMDataGridContext();
|
|
57
|
+
return features.cellSelection ? -1 : 0;
|
|
58
|
+
}
|
|
@@ -60,6 +60,16 @@
|
|
|
60
60
|
band of their own. */
|
|
61
61
|
--dg-row-group-bg: var(--mantine-color-body);
|
|
62
62
|
|
|
63
|
+
/* The stacking ladder, stated once. Body cells sit at auto (0); everything
|
|
64
|
+
sticky stacks over them in this order, and a sticky row crossing a sticky
|
|
65
|
+
column takes the row's slot plus one. */
|
|
66
|
+
--dg-z-pinned-cell: 2;
|
|
67
|
+
--dg-z-focused-cell: 3;
|
|
68
|
+
--dg-z-summary-row: 4;
|
|
69
|
+
--dg-z-summary-pinned-cell: 5;
|
|
70
|
+
--dg-z-header: 6;
|
|
71
|
+
--dg-z-header-pinned-cell: 7;
|
|
72
|
+
|
|
63
73
|
display: flex;
|
|
64
74
|
flex-direction: column;
|
|
65
75
|
min-height: 0;
|
|
@@ -6,10 +6,12 @@ import { TMDataGridContextProvider } from "../TMDataGridContext";
|
|
|
6
6
|
import { TMDataGridFilterPanel } from "./TMDataGridFilterPanel";
|
|
7
7
|
import { TMDataGridFilterPills } from "./TMDataGridFilterPills";
|
|
8
8
|
import { TMDataGridFooter } from "./TMDataGridFooter";
|
|
9
|
+
import { TMDataGridSearch } from "./TMDataGridSearch";
|
|
9
10
|
import { TMDataGridTable } from "./TMDataGridTable";
|
|
10
11
|
import {
|
|
11
12
|
TMDataGridColumnsButton,
|
|
12
13
|
TMDataGridFilterButton,
|
|
14
|
+
TMDataGridLoadingIndicator,
|
|
13
15
|
TMDataGridSummaryCount,
|
|
14
16
|
TMDataGridToolbar,
|
|
15
17
|
TMDataGridToolbarSpacer,
|
|
@@ -60,6 +62,10 @@ function TMDataGridRoot<TData extends RowData>({
|
|
|
60
62
|
table,
|
|
61
63
|
ui,
|
|
62
64
|
features,
|
|
65
|
+
labels,
|
|
66
|
+
renderDetails,
|
|
67
|
+
renderDetailsEstHeight,
|
|
68
|
+
overscan,
|
|
63
69
|
size = DEFAULT_TMDATAGRID_SIZE,
|
|
64
70
|
children,
|
|
65
71
|
className,
|
|
@@ -72,11 +78,25 @@ function TMDataGridRoot<TData extends RowData>({
|
|
|
72
78
|
table,
|
|
73
79
|
ui,
|
|
74
80
|
features,
|
|
81
|
+
labels,
|
|
82
|
+
renderDetails,
|
|
83
|
+
renderDetailsEstHeight,
|
|
84
|
+
overscan,
|
|
75
85
|
size,
|
|
76
86
|
rowHeight,
|
|
77
87
|
controlSize: SIZE_CONTROL_SIZE[size],
|
|
78
88
|
}),
|
|
79
|
-
[
|
|
89
|
+
[
|
|
90
|
+
table,
|
|
91
|
+
ui,
|
|
92
|
+
features,
|
|
93
|
+
labels,
|
|
94
|
+
renderDetails,
|
|
95
|
+
renderDetailsEstHeight,
|
|
96
|
+
overscan,
|
|
97
|
+
size,
|
|
98
|
+
rowHeight,
|
|
99
|
+
],
|
|
80
100
|
);
|
|
81
101
|
|
|
82
102
|
return (
|
|
@@ -96,6 +116,8 @@ export const TMDataGrid = Object.assign(TMDataGridRoot, {
|
|
|
96
116
|
Toolbar: TMDataGridToolbar,
|
|
97
117
|
Spacer: TMDataGridToolbarSpacer,
|
|
98
118
|
SummaryCount: TMDataGridSummaryCount,
|
|
119
|
+
LoadingIndicator: TMDataGridLoadingIndicator,
|
|
120
|
+
Search: TMDataGridSearch,
|
|
99
121
|
ColumnsButton: TMDataGridColumnsButton,
|
|
100
122
|
FilterButton: TMDataGridFilterButton,
|
|
101
123
|
Table: TMDataGridTable,
|
|
@@ -13,7 +13,7 @@ import { SearchIcon } from "./icons";
|
|
|
13
13
|
* `TMDataGrid.ColumnsButton`.
|
|
14
14
|
*/
|
|
15
15
|
export function TMDataGridColumnsPanel() {
|
|
16
|
-
const { table, controlSize } = useTMDataGridContext();
|
|
16
|
+
const { table, labels, controlSize } = useTMDataGridContext();
|
|
17
17
|
const [search, setSearch] = useState("");
|
|
18
18
|
|
|
19
19
|
const columnVisibility = useSelector(
|
|
@@ -45,7 +45,7 @@ export function TMDataGridColumnsPanel() {
|
|
|
45
45
|
<TextInput
|
|
46
46
|
value={search}
|
|
47
47
|
onChange={(event) => setSearch(event.currentTarget.value)}
|
|
48
|
-
placeholder=
|
|
48
|
+
placeholder={labels.columnsSearchPlaceholder}
|
|
49
49
|
leftSection={<SearchIcon size={16} stroke={1.6} />}
|
|
50
50
|
size={controlSize}
|
|
51
51
|
data-autofocus
|
|
@@ -68,7 +68,7 @@ export function TMDataGridColumnsPanel() {
|
|
|
68
68
|
))}
|
|
69
69
|
{visibleInPanel.length === 0 && (
|
|
70
70
|
<Text size={controlSize} c="dimmed">
|
|
71
|
-
|
|
71
|
+
{labels.columnsNoMatch(search)}
|
|
72
72
|
</Text>
|
|
73
73
|
)}
|
|
74
74
|
</div>
|
|
@@ -77,7 +77,7 @@ export function TMDataGridColumnsPanel() {
|
|
|
77
77
|
<div className={classes.columnsPanelFooter}>
|
|
78
78
|
<Checkbox
|
|
79
79
|
size={controlSize}
|
|
80
|
-
label=
|
|
80
|
+
label={labels.columnsShowHideAll}
|
|
81
81
|
checked={shownCount === hideableColumns.length}
|
|
82
82
|
indeterminate={shownCount > 0 && shownCount < hideableColumns.length}
|
|
83
83
|
onChange={(event) =>
|
|
@@ -89,7 +89,7 @@ export function TMDataGridColumnsPanel() {
|
|
|
89
89
|
size="compact-sm"
|
|
90
90
|
onClick={() => table.resetColumnVisibility()}
|
|
91
91
|
>
|
|
92
|
-
|
|
92
|
+
{labels.columnsReset}
|
|
93
93
|
</Button>
|
|
94
94
|
</div>
|
|
95
95
|
</div>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/* The details lane is a fixed track holding one control, so the button fills it
|
|
2
|
+
and the whole cell is the hit target. */
|
|
3
|
+
.detailsToggle {
|
|
4
|
+
display: flex;
|
|
5
|
+
align-items: center;
|
|
6
|
+
justify-content: center;
|
|
7
|
+
width: 100%;
|
|
8
|
+
height: 100%;
|
|
9
|
+
color: inherit;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.detailsToggle:hover .chevron {
|
|
13
|
+
color: var(--mantine-color-text);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.chevron {
|
|
17
|
+
display: flex;
|
|
18
|
+
align-items: center;
|
|
19
|
+
color: var(--mantine-color-dimmed);
|
|
20
|
+
transition: transform 120ms ease;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.chevron[data-expanded="true"] {
|
|
24
|
+
transform: rotate(90deg);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/* One icon rotated rather than two swapped: the turn is what reads as the
|
|
28
|
+
expand, and a swap would jump. */
|
|
29
|
+
@media (prefers-reduced-motion: reduce) {
|
|
30
|
+
.chevron {
|
|
31
|
+
transition: none;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { UnstyledButton } from "@mantine/core";
|
|
2
|
+
import {
|
|
3
|
+
useCellControlTabIndex,
|
|
4
|
+
useTMDataGridContext,
|
|
5
|
+
} from "../TMDataGridContext";
|
|
6
|
+
import type { ColumnDef, Row, RowData } from "@tanstack/react-table";
|
|
7
|
+
import { useSelector } from "@tanstack/react-store";
|
|
8
|
+
import classes from "./TMDataGridDetailsColumn.module.css";
|
|
9
|
+
import { ChevronRightIcon } from "./icons";
|
|
10
|
+
import { areAllRowsExpanded, resolveExpandAll } from "../core/expanding";
|
|
11
|
+
import type { TMDataGridFeatures, TMDataGridTable } from "../useTMDataGrid";
|
|
12
|
+
|
|
13
|
+
export const DETAILS_COLUMN_ID = "__details__";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The chevron that opens a row's detail panel.
|
|
17
|
+
*
|
|
18
|
+
* Expansion is read through a subscription rather than from
|
|
19
|
+
* `row.getIsExpanded()` in the component body: the `row` identity survives an
|
|
20
|
+
* expand, so the React Compiler would cache the call along with it and the
|
|
21
|
+
* chevron would never turn. Same reason the select checkbox subscribes.
|
|
22
|
+
*/
|
|
23
|
+
function DetailsCell<TData extends RowData>({
|
|
24
|
+
row,
|
|
25
|
+
}: {
|
|
26
|
+
row: Row<TMDataGridFeatures, TData>;
|
|
27
|
+
}) {
|
|
28
|
+
const { labels } = useTMDataGridContext();
|
|
29
|
+
const expanded = useSelector(row.table.store, () => row.getIsExpanded());
|
|
30
|
+
// See useCellControlTabIndex: a body control is reached by stepping into its
|
|
31
|
+
// cell, not by Tab, or the grid would have one tab stop per mounted row.
|
|
32
|
+
const tabIndex = useCellControlTabIndex();
|
|
33
|
+
|
|
34
|
+
// Group rows open into their children, not into a panel — see the body's
|
|
35
|
+
// `showsDetails`. Their cells are aggregated, so this lane is already blank
|
|
36
|
+
// on them; the guard is what makes that a decision rather than a side effect.
|
|
37
|
+
if (row.getIsGrouped()) return null;
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<UnstyledButton
|
|
41
|
+
className={classes.detailsToggle}
|
|
42
|
+
tabIndex={tabIndex}
|
|
43
|
+
aria-expanded={expanded}
|
|
44
|
+
aria-label={expanded ? labels.hideDetails : labels.showDetails}
|
|
45
|
+
// The row underneath may select or highlight on click; opening a panel is
|
|
46
|
+
// its own gesture and must not also trigger those.
|
|
47
|
+
onClick={(event) => {
|
|
48
|
+
event.stopPropagation();
|
|
49
|
+
row.toggleExpanded();
|
|
50
|
+
}}
|
|
51
|
+
>
|
|
52
|
+
<span className={classes.chevron} data-expanded={expanded}>
|
|
53
|
+
<ChevronRightIcon size={16} stroke={1.6} />
|
|
54
|
+
</span>
|
|
55
|
+
</UnstyledButton>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Expand-all / collapse-all for the lane, the way the checkbox column's header
|
|
61
|
+
* selects and clears every row.
|
|
62
|
+
*
|
|
63
|
+
* Deliberately not `table.toggleAllRowsExpanded()`: that writes the `expanded`
|
|
64
|
+
* state's whole-table form, and one state holds both the tree and the panels —
|
|
65
|
+
* so it would unfold every group as well. Only the data rows are touched here;
|
|
66
|
+
* whatever the tree was showing, it goes on showing. See resolveExpandAll.
|
|
67
|
+
*/
|
|
68
|
+
function DetailsHeader<TData extends RowData>({
|
|
69
|
+
table,
|
|
70
|
+
}: {
|
|
71
|
+
table: TMDataGridTable<TData>;
|
|
72
|
+
}) {
|
|
73
|
+
const { labels } = useTMDataGridContext();
|
|
74
|
+
// Pre-paginated: expand-all means every row the filters left, not the page
|
|
75
|
+
// that happens to be on screen. Same model `getCanSomeRowsExpand` reads.
|
|
76
|
+
const detailRows = () => table.getPrePaginatedRowModel().flatRows;
|
|
77
|
+
|
|
78
|
+
const allExpanded = useSelector(table.store, (state) =>
|
|
79
|
+
areAllRowsExpanded({
|
|
80
|
+
rows: detailRows(),
|
|
81
|
+
expanded: state.expanded,
|
|
82
|
+
target: "details",
|
|
83
|
+
}),
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<UnstyledButton
|
|
88
|
+
className={classes.detailsToggle}
|
|
89
|
+
aria-expanded={allExpanded}
|
|
90
|
+
aria-label={
|
|
91
|
+
allExpanded ? labels.collapseAllDetails : labels.expandAllDetails
|
|
92
|
+
}
|
|
93
|
+
// Partly expanded opens the rest rather than closing what is already
|
|
94
|
+
// open — the same reading as an indeterminate select-all box.
|
|
95
|
+
onClick={() =>
|
|
96
|
+
table.setExpanded(
|
|
97
|
+
resolveExpandAll({
|
|
98
|
+
rows: detailRows(),
|
|
99
|
+
expanded: table.store.state.expanded,
|
|
100
|
+
target: "details",
|
|
101
|
+
expand: !allExpanded,
|
|
102
|
+
}),
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
>
|
|
106
|
+
<span className={classes.chevron} data-expanded={allExpanded}>
|
|
107
|
+
<ChevronRightIcon size={16} stroke={1.6} />
|
|
108
|
+
</span>
|
|
109
|
+
</UnstyledButton>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* The generated details lane, prepended whenever `renderDetails` is set.
|
|
115
|
+
*
|
|
116
|
+
* Structural, like the checkbox and tree columns: fixed width, pinned to the
|
|
117
|
+
* left after both of them, not hideable, not movable and not resizable. A
|
|
118
|
+
* toggle that wandered off to the right of the grid — or hid itself — would
|
|
119
|
+
* leave rows with panels no one can open.
|
|
120
|
+
*
|
|
121
|
+
* Last of the three because it acts on one record: the checkbox picks rows out
|
|
122
|
+
* and the tree says which group they are in, and only then is there a row to
|
|
123
|
+
* open.
|
|
124
|
+
*
|
|
125
|
+
* Nothing stops a second toggle elsewhere: `row.toggleExpanded()` is the whole
|
|
126
|
+
* interface, and this lane is only the one the grid ships.
|
|
127
|
+
*/
|
|
128
|
+
export function createDetailsColumn<TData extends RowData>(
|
|
129
|
+
label = "Details",
|
|
130
|
+
): ColumnDef<TMDataGridFeatures, TData, unknown> {
|
|
131
|
+
return {
|
|
132
|
+
id: DETAILS_COLUMN_ID,
|
|
133
|
+
meta: {
|
|
134
|
+
label,
|
|
135
|
+
align: "center",
|
|
136
|
+
// Structurally the last of the generated lanes.
|
|
137
|
+
enableOrdering: false,
|
|
138
|
+
},
|
|
139
|
+
// A system lane: as wide as the control it holds and no wider. Fixed at
|
|
140
|
+
// every scale — the control does not grow with the font size, so neither
|
|
141
|
+
// should its track.
|
|
142
|
+
size: 36,
|
|
143
|
+
minSize: 36,
|
|
144
|
+
maxSize: 36,
|
|
145
|
+
enableResizing: false,
|
|
146
|
+
enableSorting: false,
|
|
147
|
+
enableColumnFilter: false,
|
|
148
|
+
enableGlobalFilter: false,
|
|
149
|
+
// Its visibility is not the user's to set: hiding it would strand every
|
|
150
|
+
// panel behind a control that is no longer there.
|
|
151
|
+
enableHiding: false,
|
|
152
|
+
// Structurally pinned to the left; users shouldn't be able to move it.
|
|
153
|
+
enablePinning: false,
|
|
154
|
+
header: ({ table }) => <DetailsHeader table={table} />,
|
|
155
|
+
cell: ({ row }) => <DetailsCell row={row} />,
|
|
156
|
+
// Deliberately no `aggregatedCell`: on a group row every cell outside the
|
|
157
|
+
// grouped column counts as aggregated, and blank is the right answer here —
|
|
158
|
+
// groups expand into their rows, not into a panel.
|
|
159
|
+
};
|
|
160
|
+
}
|