@opencorestack/opengridx 1.0.6 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -109,8 +109,6 @@ export function ColumnVisibilityPanel<R extends GridRowModel = GridRowModel>(
109
109
  <div className="ogx-column-visibility-panel__search-container">
110
110
  <SearchIcon />
111
111
  <input
112
- id="ogx-column-search"
113
- name="ogx-column-search"
114
112
  autoComplete="off"
115
113
  className="ogx-column-visibility-panel__search-input"
116
114
  placeholder="Search"
@@ -177,6 +177,42 @@
177
177
  overflow: hidden;
178
178
  box-sizing: border-box;
179
179
  border-right: 1px solid var(--ogx-grid-border-color);
180
+ position: relative;
181
+ }
182
+ .ogx__aggregation-cell--pinned-left,
183
+ .ogx__aggregation-cell--pinned-right {
184
+ position: sticky;
185
+ z-index: 3;
186
+ background: var(--ogx-grid-header-background);
187
+ }
188
+ .ogx__aggregation-cell--pinned-right {
189
+ border-left: 1px solid var(--ogx-grid-border-color);
190
+ }
191
+ .ogx__aggregation-cell--pinned-left-last {
192
+ overflow: visible;
193
+ }
194
+ .ogx__aggregation-cell--pinned-left-last::after {
195
+ content: '';
196
+ position: absolute;
197
+ top: 0;
198
+ right: -10px;
199
+ bottom: 0;
200
+ width: 10px;
201
+ pointer-events: none;
202
+ box-shadow: inset 10px 0 8px -8px rgba(0, 0, 0, 0.1);
203
+ }
204
+ .ogx__aggregation-cell--pinned-right-first {
205
+ overflow: visible;
206
+ }
207
+ .ogx__aggregation-cell--pinned-right-first::before {
208
+ content: '';
209
+ position: absolute;
210
+ top: 0;
211
+ left: -10px;
212
+ bottom: 0;
213
+ width: 10px;
214
+ pointer-events: none;
215
+ box-shadow: inset -10px 0 8px -8px rgba(0, 0, 0, 0.1);
180
216
  }
181
217
  .ogx__aggregation-label {
182
218
  font-size: 0.7em;
@@ -685,6 +685,7 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
685
685
  const hasRowSpanning = React.useMemo(() => effectiveColumns.some(c => !!c.rowSpan), [effectiveColumns]);
686
686
  const [columnsPanelOpen, setColumnsPanelOpen] = React.useState(false);
687
687
  const containerRef = React.useRef<HTMLDivElement>(null);
688
+ const standalonePanelRef = React.useRef<HTMLDivElement>(null);
688
689
 
689
690
  const toolbarProps = React.useMemo(() => {
690
691
  if (!slots?.toolbar) return null;
@@ -735,8 +736,7 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
735
736
  useEffect(() => {
736
737
  if (!columnsPanelOpen || slots?.toolbar) return;
737
738
  function handleClickOutside(e: MouseEvent) {
738
- const panel = document.getElementById('ogx-standalone-col-panel');
739
- if (panel && !panel.contains(e.target as Node)) {
739
+ if (standalonePanelRef.current && !standalonePanelRef.current.contains(e.target as Node)) {
740
740
  setColumnsPanelOpen(false);
741
741
  }
742
742
  }
@@ -762,6 +762,7 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
762
762
  <GridStandaloneColumnPanel<R>
763
763
  isOpen={columnsPanelOpen}
764
764
  containerRef={containerRef}
765
+ panelRef={standalonePanelRef}
765
766
  effectiveColumns={effectiveColumns}
766
767
  columnVisibilityModel={columnVisibilityModel}
767
768
  effectiveColumnOrder={effectiveColumnOrder}
@@ -995,6 +996,7 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
995
996
  checkboxSelection={checkboxSelection}
996
997
  hasDetailPanel={hasDetailPanel}
997
998
  rowReordering={rowReordering}
999
+ pinnedColumns={pinnedColumns}
998
1000
  />
999
1001
  )}
1000
1002
 
@@ -1,6 +1,7 @@
1
- import React from 'react';
1
+ import React, { useMemo } from 'react';
2
2
  import { formatAggregationValue } from '../../hooks/features/useAggregation';
3
- import type { GridColDef, GridAggregationModel, GridAggregationResult } from '../../types';
3
+ import type { GridColDef, GridAggregationModel, GridAggregationResult, GridColumnPinning } from '../../types';
4
+ import { calculatePinnedPositions, isColumnPinned } from '../../utils/pinning';
4
5
 
5
6
  interface GridAggregationFooterProps {
6
7
  columns: GridColDef[];
@@ -11,6 +12,7 @@ interface GridAggregationFooterProps {
11
12
  checkboxSelection: boolean;
12
13
  hasDetailPanel: boolean;
13
14
  rowReordering: boolean;
15
+ pinnedColumns?: GridColumnPinning;
14
16
  }
15
17
 
16
18
  export function GridAggregationFooter({
@@ -22,7 +24,28 @@ export function GridAggregationFooter({
22
24
  checkboxSelection,
23
25
  hasDetailPanel,
24
26
  rowReordering,
27
+ pinnedColumns,
25
28
  }: GridAggregationFooterProps) {
29
+ // Compute sticky left/right pixel offsets for each pinned column.
30
+ // The footer always renders checkbox/detail/reorder spacers unconditionally,
31
+ // so treat pinCheckboxColumn and pinExpandColumn as always true.
32
+ const pinnedOffsets = useMemo(
33
+ () => calculatePinnedPositions(
34
+ columns,
35
+ columnWidths,
36
+ pinnedColumns,
37
+ checkboxSelection,
38
+ true,
39
+ hasDetailPanel,
40
+ true,
41
+ rowReordering,
42
+ ),
43
+ [columns, columnWidths, pinnedColumns, checkboxSelection, hasDetailPanel, rowReordering]
44
+ );
45
+
46
+ const lastLeftField = pinnedColumns?.left?.[pinnedColumns.left.length - 1];
47
+ const firstRightField = pinnedColumns?.right?.[0];
48
+
26
49
  return (
27
50
  <div
28
51
  className="ogx__aggregation-footer"
@@ -40,17 +63,32 @@ export function GridAggregationFooter({
40
63
  const rawValue = aggregationResult[col.field];
41
64
  const colWidth = columnWidths[col.field] ?? (typeof col.width === 'number' ? col.width : 120);
42
65
 
66
+ const pinnedPosition = isColumnPinned(col.field, pinnedColumns);
67
+ const pinnedOffset = pinnedPosition ? pinnedOffsets[col.field] : undefined;
68
+
69
+ const className = [
70
+ 'ogx__aggregation-cell',
71
+ pinnedPosition === 'left' && 'ogx__aggregation-cell--pinned-left',
72
+ pinnedPosition === 'right' && 'ogx__aggregation-cell--pinned-right',
73
+ pinnedPosition === 'left' && col.field === lastLeftField && 'ogx__aggregation-cell--pinned-left-last',
74
+ pinnedPosition === 'right' && col.field === firstRightField && 'ogx__aggregation-cell--pinned-right-first',
75
+ ].filter(Boolean).join(' ');
76
+
77
+ const style: React.CSSProperties = {
78
+ width: colWidth,
79
+ minWidth: colWidth,
80
+ maxWidth: colWidth,
81
+ textAlign: (col.align as React.CSSProperties['textAlign']) || 'left',
82
+ };
83
+ if (pinnedPosition === 'left' && pinnedOffset !== undefined) style.left = pinnedOffset;
84
+ if (pinnedPosition === 'right' && pinnedOffset !== undefined) style.right = pinnedOffset;
85
+
43
86
  return (
44
87
  <div
45
88
  key={col.field}
46
- className="ogx__aggregation-cell"
89
+ className={className}
47
90
  role="gridcell"
48
- style={{
49
- width: colWidth,
50
- minWidth: colWidth,
51
- maxWidth: colWidth,
52
- textAlign: (col.align as React.CSSProperties['textAlign']) || 'left',
53
- }}
91
+ style={style}
54
92
  >
55
93
  {fnName ? (
56
94
  <>
@@ -6,6 +6,7 @@ import type { GridColDef, GridRowModel, GridColumnOrderChangeParams } from '../.
6
6
  export interface GridStandaloneColumnPanelProps<R extends GridRowModel> {
7
7
  isOpen: boolean;
8
8
  containerRef: React.RefObject<HTMLDivElement | null>;
9
+ panelRef?: React.RefObject<HTMLDivElement | null>;
9
10
  effectiveColumns: GridColDef<R>[];
10
11
  columnVisibilityModel: Record<string, boolean>;
11
12
  effectiveColumnOrder: string[];
@@ -20,6 +21,7 @@ export interface GridStandaloneColumnPanelProps<R extends GridRowModel> {
20
21
  export function GridStandaloneColumnPanel<R extends GridRowModel>({
21
22
  isOpen,
22
23
  containerRef,
24
+ panelRef,
23
25
  effectiveColumns,
24
26
  columnVisibilityModel,
25
27
  effectiveColumnOrder,
@@ -64,7 +66,7 @@ export function GridStandaloneColumnPanel<R extends GridRowModel>({
64
66
 
65
67
  return ReactDOM.createPortal(
66
68
  <div
67
- id="ogx-standalone-col-panel"
69
+ ref={panelRef}
68
70
  style={{
69
71
  position: 'fixed',
70
72
  top: panelTop,
@@ -1,4 +1,4 @@
1
- import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
1
+ import React, { useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from 'react';
2
2
  import type {
3
3
  GridFilterModel,
4
4
  GridFilterItem,
@@ -25,6 +25,7 @@ const FilterRow: React.FC<{
25
25
  item: GridFilterItem | undefined;
26
26
  onChange: (item: GridFilterItem | null) => void;
27
27
  }> = ({ col, item, onChange }) => {
28
+ const filterId = useId();
28
29
  const operators = getOperatorsForType(col.type);
29
30
  const isActive = !!item;
30
31
  const currentOperator = item?.operator ?? operators[0];
@@ -99,7 +100,7 @@ const FilterRow: React.FC<{
99
100
  <div className="ogx-filter__controls">
100
101
  { }
101
102
  <select
102
- id={`ogx-filter-op-${col.field}`}
103
+ id={`${filterId}-op`}
103
104
  name={`filter-op-${col.field}`}
104
105
  className={`ogx-filter__op-select${isActive ? ' ogx-filter__op-select--active' : ''}`}
105
106
  value={String(currentOperator)}
@@ -114,7 +115,7 @@ const FilterRow: React.FC<{
114
115
  { }
115
116
  {showValue && isBoolean && (
116
117
  <select
117
- id={`ogx-filter-val-${col.field}`}
118
+ id={`${filterId}-val`}
118
119
  name={`filter-val-${col.field}`}
119
120
  className={`ogx-filter__value-input ogx-filter__value-select${isActive ? ' ogx-filter__value-input--active' : ''}`}
120
121
  value={String(item?.value ?? 'true')}
@@ -136,7 +137,7 @@ const FilterRow: React.FC<{
136
137
 
137
138
  {showValue && !isBoolean && (
138
139
  <input
139
- id={`ogx-filter-val-${col.field}`}
140
+ id={`${filterId}-val`}
140
141
  name={`filter-val-${col.field}`}
141
142
  className={`ogx-filter__value-input${isActive ? ' ogx-filter__value-input--active' : ''}`}
142
143
  type="text"
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React, { useId } from 'react';
2
2
  import { Checkbox } from '../ui/Checkbox';
3
3
  import { ColumnResizeHandle } from '../ColumnResizeHandle/ColumnResizeHandle';
4
4
  import type { GridColDef, GridRowModel, GridSortDirection, GridColumnPinning, GridAggregationModel, GridColumnGroup, GridColumnGroupingModel } from '../../types';
@@ -171,6 +171,7 @@ export function Header<R extends GridRowModel = GridRowModel>(props: HeaderProps
171
171
  onManageColumns,
172
172
  } = props;
173
173
 
174
+ const selectAllId = useId();
174
175
  const cellRefs = React.useRef<Record<string, HTMLElement | null>>({});
175
176
  const [menuOpenParams, setMenuOpenParams] = React.useState<{ colDef: GridColDef<R>; anchorEl: HTMLElement } | null>(null);
176
177
 
@@ -340,8 +341,8 @@ export function Header<R extends GridRowModel = GridRowModel>(props: HeaderProps
340
341
  }}
341
342
  >
342
343
  <Checkbox
343
- id="ogx-select-all"
344
- name="ogx-select-all"
344
+ id={selectAllId}
345
+ name={selectAllId}
345
346
  checked={allSelected}
346
347
  indeterminate={someSelected}
347
348
  onChange={(e) => onSelectAll?.(e.target.checked)}
@@ -1,5 +1,5 @@
1
1
 
2
- import React from 'react';
2
+ import React, { useId } from 'react';
3
3
  import { Button } from '../ui/Button';
4
4
 
5
5
  export interface PaginationProps {
@@ -14,6 +14,7 @@ export interface PaginationProps {
14
14
  }
15
15
 
16
16
  export function Pagination(props: PaginationProps) {
17
+ const pageSizeId = useId();
17
18
  const {
18
19
  page,
19
20
  pageSize,
@@ -61,11 +62,11 @@ export function Pagination(props: PaginationProps) {
61
62
  <div className="ogx-pagination" role="navigation" aria-label="Pagination">
62
63
  { }
63
64
  <div className="ogx-pagination__page-size">
64
- <label htmlFor="page-size-select" className="ogx-pagination__label">
65
+ <label htmlFor={pageSizeId} className="ogx-pagination__label">
65
66
  Rows per page:
66
67
  </label>
67
68
  <select
68
- id="page-size-select"
69
+ id={pageSizeId}
69
70
  className="ogx-pagination__select"
70
71
  value={pageSize}
71
72
  onChange={handlePageSizeChange}
@@ -1,5 +1,5 @@
1
1
 
2
- import React from 'react';
2
+ import React, { useId } from 'react';
3
3
  import { Cell } from '../Cell/Cell';
4
4
  import { Checkbox } from '../ui/Checkbox';
5
5
  import { ExpandIcon } from '../ui/ExpandIcon';
@@ -94,6 +94,7 @@ export function Row<R extends GridRowModel = GridRowModel>(props: RowProps<R>) {
94
94
  rowHeight = 52
95
95
  } = props;
96
96
 
97
+ const checkboxId = useId();
97
98
  const expandCellRef = React.useRef<HTMLDivElement>(null);
98
99
  const checkboxCellRef = React.useRef<HTMLDivElement>(null);
99
100
 
@@ -291,7 +292,7 @@ export function Row<R extends GridRowModel = GridRowModel>(props: RowProps<R>) {
291
292
  }}
292
293
  >
293
294
  <Checkbox
294
- id={`ogx-select-row-${row.id}`}
295
+ id={checkboxId}
295
296
  name={`ogx-select-row-${row.id}`}
296
297
  checked={isSelected}
297
298
  onChange={handleCheckboxChange}
@@ -146,8 +146,6 @@ export function GlobalSearch({ value = '', onChange, placeholder = 'Search...',
146
146
  </div>
147
147
  <input
148
148
  ref={inputRef}
149
- id="ogx-global-search-input"
150
- name="ogx-global-search"
151
149
  className="ogx-global-search__input"
152
150
  value={localValue}
153
151
  onChange={(e) => setLocalValue(e.target.value)}
@@ -684,11 +684,9 @@ export function GridToolbar({
684
684
  <GridTooltip title="Pivot">
685
685
  <button
686
686
  ref={pivotButtonRef}
687
- id="ogx-pivot-btn"
688
687
  className={`ogx-toolbar__icon-btn${pivotOpen ? ' ogx-toolbar__icon-btn--active' : ''}`}
689
688
  aria-label="Configure pivot"
690
689
  aria-expanded={pivotOpen}
691
- aria-controls="ogx-pivot-panel"
692
690
  onClick={() => {
693
691
  setPivotOpen(!pivotOpen);
694
692
  setAggOpen(false);
@@ -727,11 +725,9 @@ export function GridToolbar({
727
725
  <GridTooltip title="Summaries">
728
726
  <button
729
727
  ref={aggButtonRef}
730
- id="ogx-aggregation-btn"
731
728
  className={`ogx-toolbar__icon-btn${aggOpen ? ' ogx-toolbar__icon-btn--active' : ''}`}
732
729
  aria-label="Configure summaries"
733
730
  aria-expanded={aggOpen}
734
- aria-controls="ogx-aggregation-panel"
735
731
  onClick={() => {
736
732
  setAggOpen(!aggOpen);
737
733
  setPivotOpen(false);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opencorestack/opengridx",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
4
4
  "description": "OpenGridX: High-performance React data infrastructure. Unlock advanced Row Grouping, Excel Export, and Column Pinning without the usual \"Pro\" gatekeeping. Built for speed, scale, and complete architectural freedom. Fully open, virtualization-ready, and feature-complete.",
5
5
  "type": "module",
6
6
  "main": "./dist/opengridx.umd.js",