@alaarab/ogrid-core 2.0.22 → 2.1.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.
Files changed (32) hide show
  1. package/dist/esm/constants/index.js +3 -3
  2. package/dist/esm/constants/timing.js +2 -2
  3. package/dist/esm/index.js +54 -10
  4. package/dist/esm/utils/cellValue.js +6 -0
  5. package/dist/esm/utils/clientSideData.js +19 -9
  6. package/dist/esm/utils/clipboardHelpers.js +86 -1
  7. package/dist/esm/utils/columnReorder.js +4 -7
  8. package/dist/esm/utils/dataGridViewModel.js +6 -9
  9. package/dist/esm/utils/exportToCsv.js +21 -10
  10. package/dist/esm/utils/fillHelpers.js +47 -0
  11. package/dist/esm/utils/gridRowComparator.js +3 -3
  12. package/dist/esm/utils/index.js +6 -4
  13. package/dist/esm/utils/keyboardNavigation.js +116 -7
  14. package/dist/esm/utils/ogridHelpers.js +17 -14
  15. package/dist/esm/utils/selectionHelpers.js +48 -0
  16. package/dist/esm/utils/undoRedoStack.js +17 -12
  17. package/dist/esm/utils/validation.js +43 -0
  18. package/dist/esm/utils/virtualScroll.js +2 -2
  19. package/dist/types/constants/index.d.ts +4 -3
  20. package/dist/types/constants/timing.d.ts +2 -2
  21. package/dist/types/index.d.ts +45 -6
  22. package/dist/types/utils/cellValue.d.ts +6 -0
  23. package/dist/types/utils/clipboardHelpers.d.ts +24 -1
  24. package/dist/types/utils/columnReorder.d.ts +21 -10
  25. package/dist/types/utils/exportToCsv.d.ts +9 -0
  26. package/dist/types/utils/fillHelpers.d.ts +18 -0
  27. package/dist/types/utils/index.d.ts +8 -5
  28. package/dist/types/utils/keyboardNavigation.d.ts +50 -3
  29. package/dist/types/utils/ogridHelpers.d.ts +3 -1
  30. package/dist/types/utils/selectionHelpers.d.ts +27 -0
  31. package/dist/types/utils/validation.d.ts +13 -0
  32. package/package.json +9 -2
@@ -1,24 +1,25 @@
1
1
  import { getCellValue } from './cellValue';
2
+ /** Type guard: returns true if val is an IColumnFilterDef (an object with a filter type). */
3
+ export function isFilterConfig(val) {
4
+ return val != null && typeof val === 'object' && 'type' in val;
5
+ }
2
6
  /** Resolve the filter field key for a column (filterField or columnId). */
3
7
  export function getFilterField(col) {
4
- const f = col.filterable && typeof col.filterable === 'object' ? col.filterable : null;
8
+ const f = isFilterConfig(col.filterable) ? col.filterable : null;
5
9
  return (f?.filterField ?? col.columnId);
6
10
  }
7
11
  /** Merge a single filter change into a full IFilters object. Strips empty values automatically. */
8
12
  export function mergeFilter(prev, key, value) {
9
- const next = { ...prev };
10
13
  const isEmpty = value === undefined ||
11
14
  (value.type === 'text' && value.value.trim() === '') ||
12
15
  (value.type === 'multiSelect' && value.value.length === 0) ||
13
16
  (value.type === 'date' && !value.value.from && !value.value.to) ||
14
17
  (value.type === 'people' && !value.value);
15
18
  if (isEmpty) {
16
- delete next[key];
17
- }
18
- else {
19
- next[key] = value;
19
+ const { [key]: _, ...rest } = prev;
20
+ return rest;
20
21
  }
21
- return next;
22
+ return { ...prev, [key]: value };
22
23
  }
23
24
  /** Derive filter options for multiSelect columns from client-side data. */
24
25
  export function deriveFilterOptionsFromData(items, columns) {
@@ -26,7 +27,7 @@ export function deriveFilterOptionsFromData(items, columns) {
26
27
  const filterCols = [];
27
28
  for (let i = 0; i < columns.length; i++) {
28
29
  const col = columns[i];
29
- const f = col.filterable && typeof col.filterable === 'object' ? col.filterable : null;
30
+ const f = isFilterConfig(col.filterable) ? col.filterable : null;
30
31
  if (f?.type === 'multiSelect') {
31
32
  filterCols.push({ col, field: getFilterField(col) });
32
33
  }
@@ -42,23 +43,25 @@ export function deriveFilterOptionsFromData(items, columns) {
42
43
  const item = items[i];
43
44
  for (let j = 0; j < filterCols.length; j++) {
44
45
  const v = getCellValue(item, filterCols[j].col);
45
- if (v != null && v !== '')
46
- valueSets.get(filterCols[j].field).add(String(v));
46
+ const set = valueSets.get(filterCols[j].field);
47
+ if (v != null && v !== '' && set)
48
+ set.add(String(v));
47
49
  }
48
50
  }
49
51
  const out = {};
50
52
  for (let i = 0; i < filterCols.length; i++) {
51
- out[filterCols[i].field] = Array.from(valueSets.get(filterCols[i].field)).sort();
53
+ const set = valueSets.get(filterCols[i].field);
54
+ out[filterCols[i].field] = set ? Array.from(set).sort() : [];
52
55
  }
53
56
  return out;
54
57
  }
55
58
  /** Get list of filter fields that use multiSelect (for useFilterOptions). */
56
59
  export function getMultiSelectFilterFields(columns) {
57
60
  const fields = [];
58
- columns.forEach((col) => {
59
- const f = col.filterable && typeof col.filterable === 'object' ? col.filterable : null;
61
+ for (const col of columns) {
62
+ const f = isFilterConfig(col.filterable) ? col.filterable : null;
60
63
  if (f?.type === 'multiSelect')
61
64
  fields.push(getFilterField(col));
62
- });
65
+ }
63
66
  return fields;
64
67
  }
@@ -1,3 +1,7 @@
1
+ // Re-export normalizeSelectionRange from its canonical location for convenience.
2
+ // The original definition lives in dataGridTypes.ts and is preserved there
3
+ // to avoid breaking existing imports.
4
+ export { normalizeSelectionRange } from '../types/dataGridTypes';
1
5
  /**
2
6
  * Compare two selection ranges by value (deep equality).
3
7
  * Returns true if both ranges are equal, including when both are null.
@@ -44,3 +48,47 @@ export function computeAutoScrollSpeed(distance, edgePx = 40, minSpeed = 2, maxS
44
48
  const t = Math.min(distance / edgePx, 1);
45
49
  return minSpeed + t * (maxSpeed - minSpeed);
46
50
  }
51
+ /**
52
+ * Apply a shift-click range selection to a set of row IDs.
53
+ * Used by React `useRowSelection`, Vue `useRowSelection`, and JS `RowSelectionState`.
54
+ *
55
+ * @param start Start index of the range (inclusive).
56
+ * @param end End index of the range (inclusive).
57
+ * @param checked Whether to add (true) or remove (false) the rows.
58
+ * @param items Array of all row data objects.
59
+ * @param getRowId Function to extract a unique row ID from an item.
60
+ * @param currentSelection Current set of selected row IDs (will be shallow-copied).
61
+ * @returns A new Set of selected row IDs after applying the range.
62
+ */
63
+ export function applyRangeRowSelection(start, end, checked, items, getRowId, currentSelection) {
64
+ const next = new Set(currentSelection);
65
+ const lo = Math.min(start, end);
66
+ const hi = Math.max(start, end);
67
+ for (let i = lo; i <= hi; i++) {
68
+ if (i < items.length) {
69
+ const id = getRowId(items[i]);
70
+ if (checked)
71
+ next.add(id);
72
+ else
73
+ next.delete(id);
74
+ }
75
+ }
76
+ return next;
77
+ }
78
+ /**
79
+ * Compute the allSelected / someSelected state from a set of selected row IDs.
80
+ * Used by React `useRowSelection`, Vue `useRowSelection`, and JS `RowSelectionState`.
81
+ *
82
+ * @param selectedIds Current set of selected row IDs.
83
+ * @param items Array of all row data objects.
84
+ * @param getRowId Function to extract a unique row ID from an item.
85
+ * @returns An object with `allSelected` and `someSelected` booleans.
86
+ */
87
+ export function computeRowSelectionState(selectedIds, items, getRowId) {
88
+ if (selectedIds.size === 0 || items.length === 0) {
89
+ return { allSelected: false, someSelected: false };
90
+ }
91
+ const allSelected = items.every((item) => selectedIds.has(getRowId(item)));
92
+ const someSelected = !allSelected && selectedIds.size > 0;
93
+ return { allSelected, someSelected };
94
+ }
@@ -51,8 +51,12 @@ export class UndoRedoStack {
51
51
  this.batch.push(...events);
52
52
  }
53
53
  else {
54
- this.history = [...this.history, events].slice(-this.maxDepth);
55
- this.redoStack = [];
54
+ this.history.push(events);
55
+ if (this.history.length > this.maxDepth) {
56
+ this.history.splice(0, this.history.length - this.maxDepth);
57
+ }
58
+ // Clear redo stack in-place — avoids allocating a new array on every edit
59
+ this.redoStack.length = 0;
56
60
  }
57
61
  }
58
62
  /**
@@ -80,8 +84,11 @@ export class UndoRedoStack {
80
84
  this.batch = null;
81
85
  if (!b || b.length === 0)
82
86
  return;
83
- this.history = [...this.history, b].slice(-this.maxDepth);
84
- this.redoStack = [];
87
+ this.history.push(b);
88
+ if (this.history.length > this.maxDepth) {
89
+ this.history.splice(0, this.history.length - this.maxDepth);
90
+ }
91
+ this.redoStack.length = 0;
85
92
  }
86
93
  /**
87
94
  * Pop the most recent history entry for undo.
@@ -91,11 +98,10 @@ export class UndoRedoStack {
91
98
  * The caller is responsible for applying the events in reverse order.
92
99
  */
93
100
  undo() {
94
- if (this.history.length === 0)
101
+ const lastBatch = this.history.pop();
102
+ if (!lastBatch)
95
103
  return null;
96
- const lastBatch = this.history[this.history.length - 1];
97
- this.history = this.history.slice(0, -1);
98
- this.redoStack = [...this.redoStack, lastBatch];
104
+ this.redoStack.push(lastBatch);
99
105
  return lastBatch;
100
106
  }
101
107
  /**
@@ -104,11 +110,10 @@ export class UndoRedoStack {
104
110
  * or null if there is nothing to redo.
105
111
  */
106
112
  redo() {
107
- if (this.redoStack.length === 0)
113
+ const nextBatch = this.redoStack.pop();
114
+ if (!nextBatch)
108
115
  return null;
109
- const nextBatch = this.redoStack[this.redoStack.length - 1];
110
- this.redoStack = this.redoStack.slice(0, -1);
111
- this.history = [...this.history, nextBatch];
116
+ this.history.push(nextBatch);
112
117
  return nextBatch;
113
118
  }
114
119
  /**
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Validate column definitions at grid initialization.
3
+ * Called once (not per render). Warns on empty/missing/duplicate columnIds.
4
+ */
5
+ export function validateColumns(columns) {
6
+ if (!Array.isArray(columns) || columns.length === 0) {
7
+ console.warn('[OGrid] columns prop is empty or not an array');
8
+ return;
9
+ }
10
+ const ids = new Set();
11
+ for (const col of columns) {
12
+ if (!col.columnId) {
13
+ console.warn('[OGrid] Column missing columnId:', col);
14
+ }
15
+ if (ids.has(col.columnId)) {
16
+ console.warn(`[OGrid] Duplicate columnId: "${col.columnId}"`);
17
+ }
18
+ ids.add(col.columnId);
19
+ }
20
+ }
21
+ /**
22
+ * Validate that getRowId returns unique, non-null values.
23
+ * Dev-only (skipped in production). Samples the first 100 rows.
24
+ * Called once on first data render via a hasValidated flag in the caller.
25
+ */
26
+ export function validateRowIds(items, getRowId) {
27
+ if (typeof process !== 'undefined' && process.env.NODE_ENV === 'production')
28
+ return;
29
+ const ids = new Set();
30
+ const limit = Math.min(items.length, 100);
31
+ for (let i = 0; i < limit; i++) {
32
+ const id = getRowId(items[i]);
33
+ if (id == null) {
34
+ console.warn(`[OGrid] getRowId returned null/undefined for row ${i}`);
35
+ return;
36
+ }
37
+ if (ids.has(id)) {
38
+ console.warn(`[OGrid] Duplicate row ID "${id}" at index ${i}. getRowId must return unique values.`);
39
+ return;
40
+ }
41
+ ids.add(id);
42
+ }
43
+ }
@@ -9,8 +9,8 @@
9
9
  * @returns The visible range with start/end indices and top/bottom spacer offsets
10
10
  */
11
11
  export function computeVisibleRange(scrollTop, rowHeight, containerHeight, totalRows, overscan = 5) {
12
- if (totalRows === 0 || rowHeight <= 0 || containerHeight <= 0) {
13
- return { startIndex: 0, endIndex: -1, offsetTop: 0, offsetBottom: 0 };
12
+ if (totalRows <= 0 || rowHeight <= 0 || containerHeight <= 0) {
13
+ return { startIndex: 0, endIndex: 0, offsetTop: 0, offsetBottom: 0 };
14
14
  }
15
15
  const startIndex = Math.max(0, Math.floor(scrollTop / rowHeight) - overscan);
16
16
  const endIndex = Math.min(totalRows - 1, Math.ceil((scrollTop + containerHeight) / rowHeight) + overscan);
@@ -1,3 +1,4 @@
1
- export * from './layout';
2
- export * from './timing';
3
- export * from './zIndex';
1
+ export { CHECKBOX_COLUMN_WIDTH, ROW_NUMBER_COLUMN_WIDTH, DEFAULT_MIN_COLUMN_WIDTH, CELL_PADDING, GRID_BORDER_RADIUS, } from './layout';
2
+ export { DEFAULT_DEBOUNCE_MS, PEOPLE_SEARCH_DEBOUNCE_MS, SIDEBAR_TRANSITION_MS, } from './timing';
3
+ export { Z_INDEX } from './zIndex';
4
+ export type { ZIndexKey } from './zIndex';
@@ -2,9 +2,9 @@
2
2
  * Timing constants used across OGrid.
3
3
  * Centralizes magic numbers for consistency and easier tuning.
4
4
  */
5
- /** Debounce delay for people/user search inputs (milliseconds) */
6
- export declare const PEOPLE_SEARCH_DEBOUNCE_MS = 300;
7
5
  /** Default debounce delay for generic inputs (milliseconds) */
8
6
  export declare const DEFAULT_DEBOUNCE_MS = 300;
7
+ /** Debounce delay for people/user search inputs (milliseconds) */
8
+ export declare const PEOPLE_SEARCH_DEBOUNCE_MS = 300;
9
9
  /** Sidebar panel transition duration (milliseconds) */
10
10
  export declare const SIDEBAR_TRANSITION_MS = 300;
@@ -1,6 +1,45 @@
1
- export * from './types';
2
- export * from './utils';
3
- export * from './constants';
4
- export { CHECKBOX_COLUMN_WIDTH, ROW_NUMBER_COLUMN_WIDTH, DEFAULT_MIN_COLUMN_WIDTH, CELL_PADDING, GRID_BORDER_RADIUS, } from './constants/layout';
5
- export { PEOPLE_SEARCH_DEBOUNCE_MS, DEFAULT_DEBOUNCE_MS, SIDEBAR_TRANSITION_MS, } from './constants/timing';
6
- export { Z_INDEX } from './constants/zIndex';
1
+ export type { ColumnFilterType, IDateFilterValue, IColumnFilterDef, IColumnMeta, IValueParserParams, IColumnDef, ICellValueChangedEvent, ICellEditorProps, CellEditorParams, IColumnGroupDef, HeaderCell, HeaderRow, IColumnDefinition, } from './types';
2
+ export type { RowId, UserLike, UserLikeInput, FilterValue, IFilters, IFetchParams, IPageResult, IDataSource, IGridColumnState, RowSelectionMode, IRowSelectionChangeEvent, StatusBarPanel, IStatusBarProps, IActiveCell, ISelectionRange, SideBarPanelId, ISideBarDef, IVirtualScrollConfig, IColumnReorderConfig, IOGridApi, } from './types';
3
+ export { toUserLike, isInSelectionRange, normalizeSelectionRange, } from './types';
4
+ export { escapeCsvValue, buildCsvHeader, buildCsvRows, exportToCsv, triggerCsvDownload, } from './utils';
5
+ export type { CsvColumn } from './utils';
6
+ export { getCellValue } from './utils';
7
+ export { flattenColumns, buildHeaderRows } from './utils';
8
+ export { isFilterConfig, getFilterField, mergeFilter, deriveFilterOptionsFromData, getMultiSelectFilterFields, } from './utils';
9
+ export { getStatusBarParts } from './utils';
10
+ export { getDataGridStatusBarConfig } from './utils';
11
+ export type { StatusBarPart, StatusBarPartsInput } from './utils';
12
+ export { getPaginationViewModel, PAGE_SIZE_OPTIONS, MAX_PAGE_BUTTONS, } from './utils';
13
+ export type { PaginationViewModel } from './utils';
14
+ export { GRID_CONTEXT_MENU_ITEMS, COLUMN_HEADER_MENU_ITEMS, getContextMenuHandlers, getColumnHeaderMenuItems, formatShortcut, } from './utils';
15
+ export type { GridContextMenuItem, IColumnHeaderMenuItem, GridContextMenuHandlerProps, ColumnHeaderMenuInput, ColumnHeaderMenuHandlers, } from './utils';
16
+ export { parseValue, numberParser, currencyParser, dateParser, emailParser, booleanParser, } from './utils';
17
+ export type { ParseValueResult } from './utils';
18
+ export { computeAggregations } from './utils';
19
+ export type { AggregationResult } from './utils';
20
+ export { processClientSideData } from './utils';
21
+ export { areGridRowPropsEqual, isRowInRange } from './utils';
22
+ export type { GridRowComparatorProps } from './utils';
23
+ export { getPinStateForColumn, reorderColumnArray, calculateDropTarget, } from './utils';
24
+ export type { ColumnPinState, IDropTarget, ICalculateDropTargetParams } from './utils';
25
+ export { computeVisibleRange, computeTotalHeight, getScrollTopForRow, } from './utils';
26
+ export type { IVisibleRange } from './utils';
27
+ export { getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, } from './utils';
28
+ export type { HeaderFilterConfigInput, HeaderFilterConfig, CellRenderDescriptorInput, CellRenderDescriptor, CellRenderMode, } from './utils';
29
+ export { debounce } from './utils';
30
+ export { measureRange, injectGlobalStyles } from './utils';
31
+ export type { OverlayRect } from './utils';
32
+ export { computeNextSortState } from './utils';
33
+ export type { ISortState } from './utils';
34
+ export { measureColumnContentWidth, AUTOSIZE_EXTRA_PX, AUTOSIZE_MAX_PX } from './utils';
35
+ export { findCtrlArrowTarget, computeTabNavigation, computeArrowNavigation, applyCellDeletion } from './utils';
36
+ export type { ArrowNavigationContext, ArrowNavigationResult } from './utils';
37
+ export { rangesEqual, clampSelectionToBounds, computeAutoScrollSpeed, applyRangeRowSelection, computeRowSelectionState } from './utils';
38
+ export { formatCellValueForTsv, formatSelectionAsTsv, parseTsvClipboard, applyPastedValues, applyCutClear, } from './utils';
39
+ export { applyFillValues } from './utils';
40
+ export { UndoRedoStack } from './utils';
41
+ export { validateColumns, validateRowIds } from './utils';
42
+ export { CHECKBOX_COLUMN_WIDTH, ROW_NUMBER_COLUMN_WIDTH, DEFAULT_MIN_COLUMN_WIDTH, CELL_PADDING, GRID_BORDER_RADIUS, } from './constants';
43
+ export { DEFAULT_DEBOUNCE_MS, PEOPLE_SEARCH_DEBOUNCE_MS, SIDEBAR_TRANSITION_MS, } from './constants';
44
+ export { Z_INDEX } from './constants';
45
+ export type { ZIndexKey } from './constants';
@@ -1,5 +1,11 @@
1
1
  import type { IColumnDef } from '../types/columnTypes';
2
2
  /**
3
3
  * Get the cell value for a row/column, using valueGetter when defined otherwise item[columnId].
4
+ *
5
+ * @param item - The row data object.
6
+ * @param col - Column definition. If `valueGetter` is defined it takes priority;
7
+ * otherwise the value is read via `item[col.columnId]`.
8
+ * Assumes `columnId` is a valid key on the item when no `valueGetter` is provided.
9
+ * @returns The raw cell value (`unknown`). May be `undefined` if the key does not exist on the item.
4
10
  */
5
11
  export declare function getCellValue<T>(item: T, col: IColumnDef<T>): unknown;
@@ -2,7 +2,7 @@
2
2
  * Pure clipboard helpers shared across React, Vue, Angular, and JS.
3
3
  * No framework dependencies — operates on plain values and produces strings.
4
4
  */
5
- import type { IColumnDef } from '../types/columnTypes';
5
+ import type { IColumnDef, ICellValueChangedEvent } from '../types/columnTypes';
6
6
  import type { ISelectionRange } from '../types/dataGridTypes';
7
7
  /**
8
8
  * Format a single cell value for inclusion in a TSV clipboard string.
@@ -31,3 +31,26 @@ export declare function formatSelectionAsTsv<T>(items: T[], visibleCols: IColumn
31
31
  * @returns 2D array: rows of cells. Empty if text is blank.
32
32
  */
33
33
  export declare function parseTsvClipboard(text: string): string[][];
34
+ /**
35
+ * Apply parsed clipboard rows to the grid starting at anchor position.
36
+ * For each cell in the parsed rows, validates editability, parses the value,
37
+ * and produces a cell value changed event.
38
+ *
39
+ * @param parsedRows 2D array of string values (from parseTsvClipboard).
40
+ * @param anchorRow Target starting row index.
41
+ * @param anchorCol Target starting column index (data column, not absolute).
42
+ * @param items Array of all row data objects.
43
+ * @param visibleCols Visible column definitions.
44
+ * @returns Array of cell value changed events to apply.
45
+ */
46
+ export declare function applyPastedValues<T>(parsedRows: string[][], anchorRow: number, anchorCol: number, items: T[], visibleCols: IColumnDef<T>[]): ICellValueChangedEvent<T>[];
47
+ /**
48
+ * Clear cells in a cut range by setting each editable cell to an empty-string-parsed value.
49
+ * Used after pasting cut content to clear the original cells.
50
+ *
51
+ * @param cutRange The normalized range of cells to clear.
52
+ * @param items Array of all row data objects.
53
+ * @param visibleCols Visible column definitions.
54
+ * @returns Array of cell value changed events to apply.
55
+ */
56
+ export declare function applyCutClear<T>(cutRange: ISelectionRange, items: T[], visibleCols: IColumnDef<T>[]): ICellValueChangedEvent<T>[];
@@ -19,6 +19,24 @@ export declare function getPinStateForColumn(columnId: string, pinnedColumns?: {
19
19
  * Returns a new array (does not mutate the input).
20
20
  */
21
21
  export declare function reorderColumnArray(order: string[], columnId: string, targetIndex: number): string[];
22
+ /** Parameters for {@link calculateDropTarget}. */
23
+ export interface ICalculateDropTargetParams {
24
+ /** Current mouse X position (client coordinates). */
25
+ mouseX: number;
26
+ /** Current column display order (array of column ids). */
27
+ columnOrder: string[];
28
+ /** The column being dragged. */
29
+ draggedColumnId: string;
30
+ /** Pin state of the dragged column. */
31
+ draggedPinState: ColumnPinState;
32
+ /** The table (or grid container) DOM element to query headers from. */
33
+ tableElement: Element;
34
+ /** Pinned column configuration. */
35
+ pinnedColumns?: {
36
+ left?: string[];
37
+ right?: string[];
38
+ };
39
+ }
22
40
  /**
23
41
  * Calculate the drop target for a dragged column based on mouse position.
24
42
  *
@@ -26,15 +44,8 @@ export declare function reorderColumnArray(order: string[], columnId: string, ta
26
44
  * finds the midpoint of each header cell, and determines insertion side.
27
45
  * Respects pinning zones: a left-pinned column can only drop among left-pinned, etc.
28
46
  *
29
- * @param mouseX - Current mouse X position (client coordinates)
30
- * @param columnOrder - Current column display order (array of column ids)
31
- * @param draggedColumnId - The column being dragged
32
- * @param draggedPinState - Pin state of the dragged column
33
- * @param tableElement - The table (or grid container) DOM element to query headers from
34
- * @param pinnedColumns - Pinned column configuration
47
+ * @param params - Options object containing mouseX, columnOrder, draggedColumnId,
48
+ * draggedPinState, tableElement, and optional pinnedColumns.
35
49
  * @returns Drop target with insertion index and indicator X, or null if no valid target.
36
50
  */
37
- export declare function calculateDropTarget(mouseX: number, columnOrder: string[], draggedColumnId: string, draggedPinState: ColumnPinState, tableElement: Element, pinnedColumns?: {
38
- left?: string[];
39
- right?: string[];
40
- }): IDropTarget | null;
51
+ export declare function calculateDropTarget(params: ICalculateDropTargetParams): IDropTarget | null;
@@ -6,4 +6,13 @@ export declare function escapeCsvValue(value: unknown): string;
6
6
  export declare function buildCsvHeader(columns: CsvColumn[]): string;
7
7
  export declare function buildCsvRows<T>(items: T[], columns: CsvColumn[], getValue: (item: T, columnId: string) => string): string[];
8
8
  export declare function exportToCsv<T>(items: T[], columns: CsvColumn[], getValue: (item: T, columnId: string) => string, filename?: string): void;
9
+ /**
10
+ * Triggers a browser CSV file download.
11
+ *
12
+ * NOTE: This function uses DOM APIs (document.createElement, document.body) and therefore
13
+ * requires a browser environment. It is intentionally kept in the core package because all
14
+ * framework packages (React, Angular, Vue, JS) need CSV export, and duplicating it would be
15
+ * worse than the DOM dependency. In server-side rendering (SSR) contexts, call exportToCsv
16
+ * only from browser-side code (e.g. event handlers), not during server rendering.
17
+ */
9
18
  export declare function triggerCsvDownload(csvContent: string, filename: string): void;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Pure fill handle helpers shared across React, Vue, Angular, and JS.
3
+ * No framework dependencies — operates on plain arrays and column definitions.
4
+ */
5
+ import type { IColumnDef, ICellValueChangedEvent } from '../types/columnTypes';
6
+ import type { ISelectionRange } from '../types/dataGridTypes';
7
+ /**
8
+ * Apply fill values from a source cell across a normalized selection range.
9
+ * Copies the value from the start cell of the range to every other editable cell.
10
+ *
11
+ * @param range The normalized fill range (startRow/startCol is the source).
12
+ * @param sourceRow The original source row index (skipped during fill).
13
+ * @param sourceCol The original source col index (skipped during fill).
14
+ * @param items Array of all row data objects.
15
+ * @param visibleCols Visible column definitions.
16
+ * @returns Array of cell value changed events to apply. Empty if source cell is out of bounds.
17
+ */
18
+ export declare function applyFillValues<T>(range: ISelectionRange, sourceRow: number, sourceCol: number, items: T[], visibleCols: IColumnDef<T>[]): ICellValueChangedEvent<T>[];
@@ -1,7 +1,7 @@
1
1
  export { escapeCsvValue, buildCsvHeader, buildCsvRows, exportToCsv, triggerCsvDownload, } from './exportToCsv';
2
2
  export { getCellValue } from './cellValue';
3
3
  export { flattenColumns, buildHeaderRows } from './columnUtils';
4
- export { getFilterField, mergeFilter, deriveFilterOptionsFromData, getMultiSelectFilterFields, } from './ogridHelpers';
4
+ export { isFilterConfig, getFilterField, mergeFilter, deriveFilterOptionsFromData, getMultiSelectFilterFields, } from './ogridHelpers';
5
5
  export { getStatusBarParts } from './statusBarHelpers';
6
6
  export { getDataGridStatusBarConfig } from './dataGridStatusBar';
7
7
  export { getPaginationViewModel, PAGE_SIZE_OPTIONS, MAX_PAGE_BUTTONS, } from './paginationHelpers';
@@ -18,7 +18,7 @@ export { processClientSideData } from './clientSideData';
18
18
  export { areGridRowPropsEqual, isRowInRange } from './gridRowComparator';
19
19
  export type { GridRowComparatorProps } from './gridRowComparator';
20
20
  export { getPinStateForColumn, reorderColumnArray, calculateDropTarget, } from './columnReorder';
21
- export type { ColumnPinState, IDropTarget } from './columnReorder';
21
+ export type { ColumnPinState, IDropTarget, ICalculateDropTargetParams } from './columnReorder';
22
22
  export { computeVisibleRange, computeTotalHeight, getScrollTopForRow, } from './virtualScroll';
23
23
  export type { IVisibleRange } from './virtualScroll';
24
24
  export { getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, } from './dataGridViewModel';
@@ -29,7 +29,10 @@ export type { OverlayRect } from './dom';
29
29
  export { computeNextSortState } from './sortHelpers';
30
30
  export type { ISortState } from './sortHelpers';
31
31
  export { measureColumnContentWidth, AUTOSIZE_EXTRA_PX, AUTOSIZE_MAX_PX } from './columnAutosize';
32
- export { findCtrlArrowTarget, computeTabNavigation } from './keyboardNavigation';
33
- export { rangesEqual, clampSelectionToBounds, computeAutoScrollSpeed } from './selectionHelpers';
34
- export { formatCellValueForTsv, formatSelectionAsTsv, parseTsvClipboard, } from './clipboardHelpers';
32
+ export { findCtrlArrowTarget, computeTabNavigation, computeArrowNavigation, applyCellDeletion } from './keyboardNavigation';
33
+ export type { ArrowNavigationContext, ArrowNavigationResult } from './keyboardNavigation';
34
+ export { rangesEqual, clampSelectionToBounds, computeAutoScrollSpeed, applyRangeRowSelection, computeRowSelectionState } from './selectionHelpers';
35
+ export { formatCellValueForTsv, formatSelectionAsTsv, parseTsvClipboard, applyPastedValues, applyCutClear, } from './clipboardHelpers';
36
+ export { applyFillValues } from './fillHelpers';
35
37
  export { UndoRedoStack } from './undoRedoStack';
38
+ export { validateColumns, validateRowIds } from './validation';
@@ -2,6 +2,8 @@
2
2
  * Pure keyboard navigation helpers shared across React, Vue, Angular, and JS.
3
3
  * No framework dependencies — takes plain values, returns plain values.
4
4
  */
5
+ import type { ISelectionRange } from '../types/dataGridTypes';
6
+ import type { IColumnDef, ICellValueChangedEvent } from '../types/columnTypes';
5
7
  /**
6
8
  * Excel-style Ctrl+Arrow: find the target position along a 1D axis.
7
9
  * - Non-empty current + non-empty next → scan through non-empties, stop at last before empty/edge.
@@ -19,13 +21,58 @@ export declare function findCtrlArrowTarget(pos: number, edge: number, step: num
19
21
  *
20
22
  * @param rowIndex Current row index.
21
23
  * @param columnIndex Current absolute column index (includes checkbox offset).
22
- * @param maxRowIndex Maximum row index (items.length - 1).
23
- * @param maxColIndex Maximum absolute column index.
24
+ * @param maxRowIndex Maximum row index (items.length - 1). Must be >= 0.
25
+ * @param maxColIndex Maximum absolute column index. Must be >= 0.
24
26
  * @param colOffset Number of non-data leading columns (checkbox column offset).
25
27
  * @param shiftKey True if Shift is held (backward tab).
26
- * @returns New { rowIndex, columnIndex } after tab.
28
+ * @returns New { rowIndex, columnIndex } after tab. Caller must ensure maxRowIndex and maxColIndex are non-negative.
27
29
  */
28
30
  export declare function computeTabNavigation(rowIndex: number, columnIndex: number, maxRowIndex: number, maxColIndex: number, colOffset: number, shiftKey: boolean): {
29
31
  rowIndex: number;
30
32
  columnIndex: number;
31
33
  };
34
+ /** Input parameters for arrow navigation computation. */
35
+ export interface ArrowNavigationContext {
36
+ direction: 'ArrowDown' | 'ArrowUp' | 'ArrowLeft' | 'ArrowRight';
37
+ rowIndex: number;
38
+ columnIndex: number;
39
+ dataColIndex: number;
40
+ colOffset: number;
41
+ maxRowIndex: number;
42
+ maxColIndex: number;
43
+ visibleColCount: number;
44
+ isCtrl: boolean;
45
+ isShift: boolean;
46
+ selectionRange: ISelectionRange | null;
47
+ isEmptyAt: (r: number, c: number) => boolean;
48
+ }
49
+ /** Result of arrow navigation computation. */
50
+ export interface ArrowNavigationResult {
51
+ newRowIndex: number;
52
+ newColumnIndex: number;
53
+ newDataColIndex: number;
54
+ newRange: ISelectionRange;
55
+ }
56
+ /**
57
+ * Computes the next active cell position and selection range for a single arrow key press.
58
+ * Handles Ctrl+Arrow (jump to edge), Shift+Arrow (extend selection), and plain Arrow (move).
59
+ *
60
+ * Pure function — no framework dependencies.
61
+ *
62
+ * @param ctx Arrow navigation context with current position, direction, modifiers, and grid bounds.
63
+ * @returns The new row/column indices and selection range.
64
+ */
65
+ export declare function computeArrowNavigation(ctx: ArrowNavigationContext): ArrowNavigationResult;
66
+ /**
67
+ * Apply cell deletion (Delete/Backspace key) across a selection range.
68
+ * For each editable cell in the range, parses an empty string as the new value
69
+ * and emits a cell value changed event.
70
+ *
71
+ * Pure function — no framework dependencies.
72
+ *
73
+ * @param range The normalized selection range to clear.
74
+ * @param items Array of all row data objects.
75
+ * @param visibleCols Visible column definitions.
76
+ * @returns Array of cell value changed events to apply.
77
+ */
78
+ export declare function applyCellDeletion<T>(range: ISelectionRange, items: T[], visibleCols: IColumnDef<T>[]): ICellValueChangedEvent<T>[];
@@ -1,4 +1,6 @@
1
- import type { IColumnDef, IFilters, FilterValue } from '../types';
1
+ import type { IColumnDef, IColumnFilterDef, IFilters, FilterValue } from '../types';
2
+ /** Type guard: returns true if val is an IColumnFilterDef (an object with a filter type). */
3
+ export declare function isFilterConfig(val: unknown): val is IColumnFilterDef;
2
4
  /** Resolve the filter field key for a column (filterField or columnId). */
3
5
  export declare function getFilterField<T>(col: IColumnDef<T>): string;
4
6
  /** Merge a single filter change into a full IFilters object. Strips empty values automatically. */
@@ -3,6 +3,7 @@
3
3
  * No framework dependencies — operates only on plain ISelectionRange values.
4
4
  */
5
5
  import type { ISelectionRange } from '../types/dataGridTypes';
6
+ export { normalizeSelectionRange } from '../types/dataGridTypes';
6
7
  /**
7
8
  * Compare two selection ranges by value (deep equality).
8
9
  * Returns true if both ranges are equal, including when both are null.
@@ -28,3 +29,29 @@ export declare function clampSelectionToBounds(range: ISelectionRange, maxRow: n
28
29
  * @returns Scroll speed in pixels per interval tick.
29
30
  */
30
31
  export declare function computeAutoScrollSpeed(distance: number, edgePx?: number, minSpeed?: number, maxSpeed?: number): number;
32
+ /**
33
+ * Apply a shift-click range selection to a set of row IDs.
34
+ * Used by React `useRowSelection`, Vue `useRowSelection`, and JS `RowSelectionState`.
35
+ *
36
+ * @param start Start index of the range (inclusive).
37
+ * @param end End index of the range (inclusive).
38
+ * @param checked Whether to add (true) or remove (false) the rows.
39
+ * @param items Array of all row data objects.
40
+ * @param getRowId Function to extract a unique row ID from an item.
41
+ * @param currentSelection Current set of selected row IDs (will be shallow-copied).
42
+ * @returns A new Set of selected row IDs after applying the range.
43
+ */
44
+ export declare function applyRangeRowSelection<T>(start: number, end: number, checked: boolean, items: T[], getRowId: (item: T) => string | number, currentSelection: Set<string | number>): Set<string | number>;
45
+ /**
46
+ * Compute the allSelected / someSelected state from a set of selected row IDs.
47
+ * Used by React `useRowSelection`, Vue `useRowSelection`, and JS `RowSelectionState`.
48
+ *
49
+ * @param selectedIds Current set of selected row IDs.
50
+ * @param items Array of all row data objects.
51
+ * @param getRowId Function to extract a unique row ID from an item.
52
+ * @returns An object with `allSelected` and `someSelected` booleans.
53
+ */
54
+ export declare function computeRowSelectionState<T>(selectedIds: Set<string | number>, items: T[], getRowId: (item: T) => string | number): {
55
+ allSelected: boolean;
56
+ someSelected: boolean;
57
+ };
@@ -0,0 +1,13 @@
1
+ import type { IColumnDef } from '../types/columnTypes';
2
+ import type { RowId } from '../types/dataGridTypes';
3
+ /**
4
+ * Validate column definitions at grid initialization.
5
+ * Called once (not per render). Warns on empty/missing/duplicate columnIds.
6
+ */
7
+ export declare function validateColumns<T>(columns: IColumnDef<T>[]): void;
8
+ /**
9
+ * Validate that getRowId returns unique, non-null values.
10
+ * Dev-only (skipped in production). Samples the first 100 rows.
11
+ * Called once on first data render via a hasValidated flag in the caller.
12
+ */
13
+ export declare function validateRowIds<T>(items: T[], getRowId: (item: T) => RowId): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alaarab/ogrid-core",
3
- "version": "2.0.22",
3
+ "version": "2.1.0",
4
4
  "description": "OGrid core – framework-agnostic types, algorithms, and utilities for OGrid data grids.",
5
5
  "main": "dist/esm/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -36,5 +36,12 @@
36
36
  "sideEffects": false,
37
37
  "publishConfig": {
38
38
  "access": "public"
39
- }
39
+ },
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "https://github.com/alaarab/ogrid.git",
43
+ "directory": "packages/core"
44
+ },
45
+ "homepage": "https://ogrid.dev",
46
+ "bugs": "https://github.com/alaarab/ogrid/issues"
40
47
  }