@alaarab/ogrid-react 2.0.6 → 2.0.8

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.
@@ -15,6 +15,7 @@ export function useColumnPinning(params) {
15
15
  }
16
16
  }
17
17
  return initial;
18
+ // eslint-disable-next-line react-hooks/exhaustive-deps
18
19
  }, []); // Only on mount
19
20
  const [internalPinnedColumns, setInternalPinnedColumns] = useState(initialPinnedColumns);
20
21
  // Use controlled state if provided, otherwise internal
@@ -273,7 +273,9 @@ export function useOGrid(props, ref) {
273
273
  // No-op at orchestration level — DataGridTable components implement
274
274
  // this via useVirtualScroll.scrollToIndex when virtual scrolling is active.
275
275
  },
276
- }), [
276
+ }),
277
+ // eslint-disable-next-line react-hooks/exhaustive-deps
278
+ [
277
279
  visibleColumns,
278
280
  sort,
279
281
  columnOrder,
@@ -331,7 +333,6 @@ export function useOGrid(props, ref) {
331
333
  const handleColumnPinned = useCallback((columnId, pinned) => {
332
334
  setPinnedOverrides((prev) => {
333
335
  if (pinned === null) {
334
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
335
336
  const { [columnId]: _, ...rest } = prev;
336
337
  return rest;
337
338
  }
@@ -4,7 +4,7 @@
4
4
  * Includes debounced people search effect.
5
5
  */
6
6
  import { useState, useCallback, useEffect, useRef } from 'react';
7
- const PEOPLE_SEARCH_DEBOUNCE_MS = 300;
7
+ import { PEOPLE_SEARCH_DEBOUNCE_MS } from '@alaarab/ogrid-core';
8
8
  export function usePeopleFilterState(params) {
9
9
  const { onUserChange, peopleSearch, isFilterOpen, filterType } = params;
10
10
  const peopleInputRef = useRef(null);
@@ -1,205 +1,39 @@
1
1
  /**
2
- * View model helpers for DataGridTable. Core owns the logic; UI packages only render.
2
+ * View model helpers for DataGridTable.
3
+ * Pure functions are now in @alaarab/ogrid-core. This file re-exports them
4
+ * and adds React-specific helpers / type narrowing.
3
5
  */
4
- import { getCellValue, isInSelectionRange } from '@alaarab/ogrid-core';
5
- /**
6
- * Returns ColumnHeaderFilter props from column def and grid filter/sort state.
7
- * Use in Fluent/Material/Radix DataGridTable instead of createHeaderWithFilter.
8
- */
9
- export function getHeaderFilterConfig(col, input) {
10
- const filterable = col.filterable && typeof col.filterable === 'object' ? col.filterable : null;
11
- const filterType = (filterable?.type ?? 'none');
12
- const filterField = filterable?.filterField ?? col.columnId;
13
- const sortable = col.sortable !== false;
14
- const filterValue = input.filters[filterField];
15
- const base = {
16
- columnKey: col.columnId,
17
- columnName: col.name,
18
- filterType,
19
- isSorted: input.sortBy === col.columnId,
20
- isSortedDescending: input.sortBy === col.columnId && input.sortDirection === 'desc',
21
- onSort: sortable ? () => input.onColumnSort(col.columnId) : undefined,
22
- };
23
- if (filterType === 'text') {
24
- return {
25
- ...base,
26
- textValue: filterValue?.type === 'text' ? filterValue.value : '',
27
- onTextChange: (v) => input.onFilterChange(filterField, v.trim() ? { type: 'text', value: v } : undefined),
28
- };
29
- }
30
- if (filterType === 'people') {
31
- return {
32
- ...base,
33
- selectedUser: filterValue?.type === 'people' ? filterValue.value : undefined,
34
- onUserChange: (u) => input.onFilterChange(filterField, u ? { type: 'people', value: u } : undefined),
35
- peopleSearch: input.peopleSearch,
36
- };
37
- }
38
- if (filterType === 'multiSelect') {
39
- return {
40
- ...base,
41
- options: input.filterOptions[filterField] ?? [],
42
- isLoadingOptions: input.loadingFilterOptions[filterField] ?? false,
43
- selectedValues: filterValue?.type === 'multiSelect' ? filterValue.value : [],
44
- onFilterChange: (values) => input.onFilterChange(filterField, values.length ? { type: 'multiSelect', value: values } : undefined),
45
- };
46
- }
47
- if (filterType === 'date') {
48
- return {
49
- ...base,
50
- dateValue: filterValue?.type === 'date' ? filterValue.value : undefined,
51
- onDateChange: (v) => input.onFilterChange(filterField, v ? { type: 'date', value: v } : undefined),
52
- };
53
- }
54
- return base;
55
- }
56
- /**
57
- * Returns a descriptor for rendering a cell. UI uses this to decide editing-inline vs editing-popover vs display
58
- * and to apply isActive, isInRange, etc. without duplicating the boolean logic.
59
- */
60
- export function getCellRenderDescriptor(item, col, rowIndex, colIdx, input) {
61
- const rowId = input.getRowId(item);
62
- const globalColIndex = colIdx + input.colOffset;
63
- const colEditable = col.editable === true ||
64
- (typeof col.editable === 'function' && col.editable(item));
65
- const canEditInline = input.editable !== false &&
66
- !!colEditable &&
67
- !!input.onCellValueChanged &&
68
- typeof col.cellEditor !== 'function';
69
- const canEditPopup = input.editable !== false &&
70
- !!colEditable &&
71
- !!input.onCellValueChanged &&
72
- typeof col.cellEditor === 'function' &&
73
- col.cellEditorPopup !== false;
74
- const canEditAny = canEditInline || canEditPopup;
75
- const isEditing = input.editingCell?.rowId === rowId &&
76
- input.editingCell?.columnId === col.columnId;
77
- const isActive = input.activeCell?.rowIndex === rowIndex &&
78
- input.activeCell?.columnIndex === globalColIndex;
79
- const isInRange = input.selectionRange != null &&
80
- isInSelectionRange(input.selectionRange, rowIndex, colIdx);
81
- const isInCutRange = input.cutRange != null &&
82
- isInSelectionRange(input.cutRange, rowIndex, colIdx);
83
- const isInCopyRange = input.copyRange != null &&
84
- isInSelectionRange(input.copyRange, rowIndex, colIdx);
85
- const isSelectionEndCell = !input.isDragging &&
86
- input.copyRange == null &&
87
- input.cutRange == null &&
88
- input.selectionRange != null &&
89
- rowIndex === input.selectionRange.endRow &&
90
- colIdx === input.selectionRange.endCol;
91
- const isPinned = col.pinned != null;
92
- const pinnedSide = col.pinned ?? undefined;
93
- let mode = 'display';
94
- let editorType;
95
- let value;
96
- if (isEditing && canEditInline) {
97
- mode = 'editing-inline';
98
- if (col.cellEditor === 'text' ||
99
- col.cellEditor === 'select' ||
100
- col.cellEditor === 'checkbox' ||
101
- col.cellEditor === 'richSelect' ||
102
- col.cellEditor === 'date') {
103
- editorType = col.cellEditor;
104
- }
105
- else if (col.type === 'date') {
106
- editorType = 'date';
107
- }
108
- else if (col.type === 'boolean') {
109
- editorType = 'checkbox';
110
- }
111
- else {
112
- editorType = 'text';
113
- }
114
- value = getCellValue(item, col);
115
- }
116
- else if (isEditing && canEditPopup && typeof col.cellEditor === 'function') {
117
- mode = 'editing-popover';
118
- value = getCellValue(item, col);
119
- }
120
- else {
121
- value = getCellValue(item, col);
122
- }
123
- return {
124
- mode,
125
- editorType,
126
- value,
127
- isActive,
128
- isInRange,
129
- isInCutRange,
130
- isInCopyRange,
131
- isSelectionEndCell,
132
- canEditAny,
133
- isPinned,
134
- pinnedSide,
135
- globalColIndex,
136
- rowId,
137
- rowIndex,
138
- displayValue: value,
139
- };
140
- }
141
- // --- Cell rendering helpers (reduce DataGridTable view-layer duplication) ---
6
+ import { resolveCellDisplayContent as coreResolveCellDisplayContent, resolveCellStyle as coreResolveCellStyle, buildInlineEditorProps as coreBuildInlineEditorProps, buildPopoverEditorProps as coreBuildPopoverEditorProps, } from '@alaarab/ogrid-core';
7
+ // Re-export pure functions from core (no type narrowing needed)
8
+ export { getHeaderFilterConfig, getCellRenderDescriptor, } from '@alaarab/ogrid-core';
9
+ // --- React-typed wrappers for functions that need type narrowing ---
142
10
  /**
143
11
  * Resolves display content for a cell in display mode.
144
- * Handles the renderCell valueFormatter → String() fallback chain.
12
+ * Returns React.ReactNode for JSX compatibility.
145
13
  */
146
14
  export function resolveCellDisplayContent(col, item, displayValue) {
147
- if (col.renderCell)
148
- return col.renderCell(item);
149
- if (col.valueFormatter)
150
- return col.valueFormatter(displayValue, item);
151
- if (displayValue == null)
152
- return null;
153
- if (col.type === 'date') {
154
- const d = new Date(String(displayValue));
155
- if (!Number.isNaN(d.getTime()))
156
- return d.toLocaleDateString();
157
- }
158
- if (col.type === 'boolean') {
159
- return displayValue ? 'True' : 'False';
160
- }
161
- return String(displayValue);
15
+ return coreResolveCellDisplayContent(col, item, displayValue);
162
16
  }
163
17
  /**
164
- * Resolves the cellStyle from a column def, handling both function and static values.
18
+ * Resolves the cellStyle from a column def.
19
+ * Returns React.CSSProperties for JSX compatibility.
165
20
  */
166
21
  export function resolveCellStyle(col, item) {
167
- if (!col.cellStyle)
168
- return undefined;
169
- return typeof col.cellStyle === 'function' ? col.cellStyle(item) : col.cellStyle;
22
+ return coreResolveCellStyle(col, item);
170
23
  }
171
24
  /**
172
- * Builds props for InlineCellEditor. Shared across all UI packages.
25
+ * Builds props for InlineCellEditor with React-specific IColumnDef.
173
26
  */
174
27
  export function buildInlineEditorProps(item, col, descriptor, callbacks) {
175
- return {
176
- value: descriptor.value,
177
- item,
178
- column: col,
179
- rowIndex: descriptor.rowIndex,
180
- editorType: (descriptor.editorType ?? 'text'),
181
- onCommit: (newValue) => callbacks.commitCellEdit(item, col.columnId, descriptor.value, newValue, descriptor.rowIndex, descriptor.globalColIndex),
182
- onCancel: () => callbacks.setEditingCell(null),
183
- };
28
+ const result = coreBuildInlineEditorProps(item, col, descriptor, callbacks);
29
+ return { ...result, column: col };
184
30
  }
185
31
  /**
186
- * Builds ICellEditorProps for custom popover editors. Shared across all UI packages.
32
+ * Builds ICellEditorProps for custom popover editors with React-specific IColumnDef.
187
33
  */
188
34
  export function buildPopoverEditorProps(item, col, descriptor, pendingEditorValue, callbacks) {
189
- const oldValue = descriptor.value;
190
- const displayValue = pendingEditorValue !== undefined ? pendingEditorValue : oldValue;
191
- return {
192
- value: displayValue,
193
- onValueChange: callbacks.setPendingEditorValue,
194
- onCommit: () => {
195
- const newValue = pendingEditorValue !== undefined ? pendingEditorValue : oldValue;
196
- callbacks.commitCellEdit(item, col.columnId, oldValue, newValue, descriptor.rowIndex, descriptor.globalColIndex);
197
- },
198
- onCancel: callbacks.cancelPopoverEdit,
199
- item,
200
- column: col,
201
- cellEditorParams: col.cellEditorParams,
202
- };
35
+ const result = coreBuildPopoverEditorProps(item, col, descriptor, pendingEditorValue, callbacks);
36
+ return { ...result, column: col };
203
37
  }
204
38
  export function getCellInteractionProps(descriptor, columnId, handlers) {
205
39
  return {
@@ -1,5 +1,5 @@
1
1
  // Shared utilities re-exported from core
2
2
  export { escapeCsvValue, buildCsvHeader, buildCsvRows, exportToCsv, triggerCsvDownload, getCellValue, flattenColumns, buildHeaderRows, getFilterField, mergeFilter, deriveFilterOptionsFromData, getMultiSelectFilterFields, getStatusBarParts, getDataGridStatusBarConfig, getPaginationViewModel, PAGE_SIZE_OPTIONS, MAX_PAGE_BUTTONS, GRID_CONTEXT_MENU_ITEMS, getContextMenuHandlers, formatShortcut, parseValue, numberParser, currencyParser, dateParser, emailParser, booleanParser, computeAggregations, processClientSideData, } from '@alaarab/ogrid-core';
3
- // React-specific utilities (not in core)
3
+ // View model utilities (re-exported from core + React-specific getCellInteractionProps)
4
4
  export { getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, getCellInteractionProps, } from './dataGridViewModel';
5
5
  export { areGridRowPropsEqual, isRowInRange } from './gridRowComparator';
@@ -1,115 +1,27 @@
1
1
  /**
2
- * View model helpers for DataGridTable. Core owns the logic; UI packages only render.
2
+ * View model helpers for DataGridTable.
3
+ * Pure functions are now in @alaarab/ogrid-core. This file re-exports them
4
+ * and adds React-specific helpers / type narrowing.
3
5
  */
4
6
  import type * as React from 'react';
5
- import type { ColumnFilterType, ICellEditorProps, IDateFilterValue } from '../types/columnTypes';
6
7
  import type { IColumnDef } from '../types/columnTypes';
7
- import type { RowId, UserLike, IFilters, FilterValue } from '../types/dataGridTypes';
8
- export interface HeaderFilterConfigInput {
9
- sortBy?: string;
10
- sortDirection: 'asc' | 'desc';
11
- onColumnSort: (columnKey: string) => void;
12
- filters: IFilters;
13
- onFilterChange: (key: string, value: FilterValue | undefined) => void;
14
- filterOptions: Record<string, string[]>;
15
- loadingFilterOptions: Record<string, boolean>;
16
- peopleSearch?: (query: string) => Promise<UserLike[]>;
17
- }
18
- /** Props to pass to ColumnHeaderFilter. Matches IColumnHeaderFilterProps. */
19
- export interface HeaderFilterConfig {
20
- columnKey: string;
21
- columnName: string;
22
- filterType: ColumnFilterType;
23
- isSorted?: boolean;
24
- isSortedDescending?: boolean;
25
- onSort?: () => void;
26
- selectedValues?: string[];
27
- onFilterChange?: (values: string[]) => void;
28
- options?: string[];
29
- isLoadingOptions?: boolean;
30
- textValue?: string;
31
- onTextChange?: (value: string) => void;
32
- selectedUser?: UserLike;
33
- onUserChange?: (user: UserLike | undefined) => void;
34
- peopleSearch?: (query: string) => Promise<UserLike[]>;
35
- dateValue?: IDateFilterValue;
36
- onDateChange?: (value: IDateFilterValue | undefined) => void;
37
- }
38
- /**
39
- * Returns ColumnHeaderFilter props from column def and grid filter/sort state.
40
- * Use in Fluent/Material/Radix DataGridTable instead of createHeaderWithFilter.
41
- */
42
- export declare function getHeaderFilterConfig<T>(col: IColumnDef<T>, input: HeaderFilterConfigInput): HeaderFilterConfig;
43
- export type CellRenderMode = 'editing-inline' | 'editing-popover' | 'display';
44
- export interface CellRenderDescriptorInput<T> {
45
- editingCell: {
46
- rowId: RowId;
47
- columnId: string;
48
- } | null;
49
- activeCell: {
50
- rowIndex: number;
51
- columnIndex: number;
52
- } | null;
53
- selectionRange: {
54
- startRow: number;
55
- startCol: number;
56
- endRow: number;
57
- endCol: number;
58
- } | null;
59
- cutRange: {
60
- startRow: number;
61
- startCol: number;
62
- endRow: number;
63
- endCol: number;
64
- } | null;
65
- copyRange: {
66
- startRow: number;
67
- startCol: number;
68
- endRow: number;
69
- endCol: number;
70
- } | null;
71
- colOffset: number;
72
- itemsLength: number;
73
- getRowId: (item: T) => RowId;
74
- editable?: boolean;
75
- onCellValueChanged?: (event: import('../types/columnTypes').ICellValueChangedEvent<T>) => void;
76
- /** True while user is drag-selecting cells — hides fill handle during drag. */
77
- isDragging?: boolean;
78
- }
79
- export interface CellRenderDescriptor {
80
- mode: CellRenderMode;
81
- editorType?: 'text' | 'select' | 'checkbox' | 'richSelect' | 'date';
82
- value?: unknown;
83
- isActive: boolean;
84
- isInRange: boolean;
85
- isInCutRange: boolean;
86
- isInCopyRange: boolean;
87
- isSelectionEndCell: boolean;
88
- canEditAny: boolean;
89
- isPinned: boolean;
90
- pinnedSide?: 'left' | 'right';
91
- globalColIndex: number;
92
- rowId: RowId;
93
- rowIndex: number;
94
- /** Raw value for display (when mode === 'display'). UI uses col.renderCell or col.valueFormatter. */
95
- displayValue?: unknown;
96
- }
97
- /**
98
- * Returns a descriptor for rendering a cell. UI uses this to decide editing-inline vs editing-popover vs display
99
- * and to apply isActive, isInRange, etc. without duplicating the boolean logic.
100
- */
101
- export declare function getCellRenderDescriptor<T>(item: T, col: IColumnDef<T>, rowIndex: number, colIdx: number, input: CellRenderDescriptorInput<T>): CellRenderDescriptor;
8
+ import type { RowId } from '../types/dataGridTypes';
9
+ import type { ICellEditorProps } from '../types/columnTypes';
10
+ import type { CellRenderDescriptor } from '@alaarab/ogrid-core';
11
+ export { getHeaderFilterConfig, getCellRenderDescriptor, } from '@alaarab/ogrid-core';
12
+ export type { HeaderFilterConfigInput, HeaderFilterConfig, CellRenderDescriptorInput, CellRenderDescriptor, CellRenderMode, } from '@alaarab/ogrid-core';
102
13
  /**
103
14
  * Resolves display content for a cell in display mode.
104
- * Handles the renderCell valueFormatter → String() fallback chain.
15
+ * Returns React.ReactNode for JSX compatibility.
105
16
  */
106
17
  export declare function resolveCellDisplayContent<T>(col: IColumnDef<T>, item: T, displayValue: unknown): React.ReactNode;
107
18
  /**
108
- * Resolves the cellStyle from a column def, handling both function and static values.
19
+ * Resolves the cellStyle from a column def.
20
+ * Returns React.CSSProperties for JSX compatibility.
109
21
  */
110
22
  export declare function resolveCellStyle<T>(col: IColumnDef<T>, item: T): React.CSSProperties | undefined;
111
23
  /**
112
- * Builds props for InlineCellEditor. Shared across all UI packages.
24
+ * Builds props for InlineCellEditor with React-specific IColumnDef.
113
25
  */
114
26
  export declare function buildInlineEditorProps<T>(item: T, col: IColumnDef<T>, descriptor: CellRenderDescriptor, callbacks: {
115
27
  commitCellEdit: (item: T, columnId: string, oldValue: unknown, newValue: unknown, rowIndex: number, globalColIndex: number) => void;
@@ -119,23 +31,18 @@ export declare function buildInlineEditorProps<T>(item: T, col: IColumnDef<T>, d
119
31
  item: T;
120
32
  column: IColumnDef<T>;
121
33
  rowIndex: number;
122
- editorType: "text" | "select" | "checkbox" | "richSelect" | "date";
34
+ editorType: 'text' | 'select' | 'checkbox' | 'richSelect' | 'date';
123
35
  onCommit: (newValue: unknown) => void;
124
36
  onCancel: () => void;
125
37
  };
126
38
  /**
127
- * Builds ICellEditorProps for custom popover editors. Shared across all UI packages.
39
+ * Builds ICellEditorProps for custom popover editors with React-specific IColumnDef.
128
40
  */
129
41
  export declare function buildPopoverEditorProps<T>(item: T, col: IColumnDef<T>, descriptor: CellRenderDescriptor, pendingEditorValue: unknown, callbacks: {
130
42
  setPendingEditorValue: (value: unknown) => void;
131
43
  commitCellEdit: (item: T, columnId: string, oldValue: unknown, newValue: unknown, rowIndex: number, globalColIndex: number) => void;
132
44
  cancelPopoverEdit: () => void;
133
45
  }): ICellEditorProps<T>;
134
- /**
135
- * Common interaction props for cell wrapper elements.
136
- * Includes data attributes, event handlers, and accessibility props.
137
- * Spread onto the cell wrapper div/Box in each UI package.
138
- */
139
46
  export interface CellInteractionHandlers {
140
47
  handleCellMouseDown: (e: React.MouseEvent, rowIndex: number, colIndex: number) => void;
141
48
  setActiveCell: (cell: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alaarab/ogrid-react",
3
- "version": "2.0.6",
3
+ "version": "2.0.8",
4
4
  "description": "OGrid React – React hooks, headless components, and utilities for OGrid data grids.",
5
5
  "main": "dist/esm/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -36,12 +36,17 @@
36
36
  "node": ">=18"
37
37
  },
38
38
  "dependencies": {
39
- "@alaarab/ogrid-core": "2.0.6",
40
- "@tanstack/react-virtual": "^3.13.18"
39
+ "@alaarab/ogrid-core": "2.0.7"
41
40
  },
42
41
  "peerDependencies": {
42
+ "@tanstack/react-virtual": "^3.0.0",
43
43
  "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
44
44
  },
45
+ "peerDependenciesMeta": {
46
+ "@tanstack/react-virtual": {
47
+ "optional": true
48
+ }
49
+ },
45
50
  "devDependencies": {
46
51
  "@testing-library/jest-dom": "^6.9.1",
47
52
  "@testing-library/react": "^16.3.2",