@alaarab/ogrid-core 2.0.18 → 2.0.21

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.
@@ -9,8 +9,11 @@ export function computeAggregations(items, visibleCols, selectionRange) {
9
9
  if (!selectionRange)
10
10
  return null;
11
11
  const norm = normalizeSelectionRange(selectionRange);
12
- const numericValues = [];
13
12
  let totalCells = 0;
13
+ let sum = 0;
14
+ let min = Infinity;
15
+ let max = -Infinity;
16
+ let count = 0;
14
17
  for (let r = norm.startRow; r <= norm.endRow; r++) {
15
18
  for (let c = norm.startCol; c <= norm.endCol; c++) {
16
19
  if (r >= items.length || c >= visibleCols.length)
@@ -23,29 +26,23 @@ export function computeAggregations(items, visibleCols, selectionRange) {
23
26
  // return NaN instead of partially parsing to 2020
24
27
  const num = typeof raw === 'number' ? raw : Number(raw);
25
28
  if (!isNaN(num) && isFinite(num)) {
26
- numericValues.push(num);
29
+ sum += num;
30
+ if (num < min)
31
+ min = num;
32
+ if (num > max)
33
+ max = num;
34
+ count++;
27
35
  }
28
36
  }
29
37
  }
30
38
  // Need at least 2 cells selected and at least 1 numeric value to show aggregation
31
- if (totalCells < 2 || numericValues.length === 0)
39
+ if (totalCells < 2 || count === 0)
32
40
  return null;
33
- let sum = 0;
34
- let min = numericValues[0];
35
- let max = numericValues[0];
36
- for (let i = 0; i < numericValues.length; i++) {
37
- const v = numericValues[i];
38
- sum += v;
39
- if (v < min)
40
- min = v;
41
- if (v > max)
42
- max = v;
43
- }
44
41
  return {
45
42
  sum,
46
- avg: sum / numericValues.length,
43
+ avg: sum / count,
47
44
  min,
48
45
  max,
49
- count: numericValues.length,
46
+ count,
50
47
  };
51
48
  }
@@ -56,17 +56,19 @@ export function processClientSideData(data, columns, filters, sortBy, sortDirect
56
56
  }
57
57
  case 'date': {
58
58
  const dv = val.value;
59
+ // Pre-compute filter boundary timestamps to avoid repeated Date parsing in the filter loop
60
+ const fromTs = dv.from ? new Date(dv.from + 'T00:00:00').getTime() : NaN;
61
+ const toTs = dv.to ? new Date(dv.to + 'T23:59:59.999').getTime() : NaN;
59
62
  predicates.push((r) => {
60
63
  const cellVal = getCellValue(r, col);
61
64
  if (cellVal == null)
62
65
  return false;
63
- const cellDate = new Date(String(cellVal));
64
- if (Number.isNaN(cellDate.getTime()))
66
+ const cellTs = new Date(String(cellVal)).getTime();
67
+ if (Number.isNaN(cellTs))
65
68
  return false;
66
- const cellDateStr = cellDate.toISOString().split('T')[0];
67
- if (dv.from && cellDateStr < dv.from)
69
+ if (!Number.isNaN(fromTs) && cellTs < fromTs)
68
70
  return false;
69
- if (dv.to && cellDateStr > dv.to)
71
+ if (!Number.isNaN(toTs) && cellTs > toTs)
70
72
  return false;
71
73
  return true;
72
74
  });
@@ -12,7 +12,7 @@ export function formatCellValueForTsv(raw, formatted) {
12
12
  const val = formatted != null && formatted !== '' ? formatted : raw;
13
13
  if (val == null || val === '')
14
14
  return '';
15
- return String(val).replace(/\t/g, ' ').replace(/\n/g, ' ');
15
+ return String(val).replace(/[\t\n]/g, ' ');
16
16
  }
17
17
  /**
18
18
  * Serialize a rectangular cell range to a TSV (tab-separated values) string
@@ -34,7 +34,8 @@ export function formatSelectionAsTsv(items, visibleCols, range) {
34
34
  const item = items[r];
35
35
  const col = visibleCols[c];
36
36
  const raw = getCellValue(item, col);
37
- const formatted = col.valueFormatter ? col.valueFormatter(raw, item) : raw;
37
+ const clipboard = col.clipboardFormatter ? col.clipboardFormatter(raw, item) : null;
38
+ const formatted = clipboard ?? (col.valueFormatter ? col.valueFormatter(raw, item) : raw);
38
39
  cells.push(formatCellValueForTsv(raw, formatted));
39
40
  }
40
41
  rows.push(cells.join('\t'));
@@ -2,8 +2,8 @@
2
2
  * Column autosize DOM measurement utilities shared across all frameworks.
3
3
  */
4
4
  import { DEFAULT_MIN_COLUMN_WIDTH } from '../constants/layout';
5
- /** Extra pixels added to header label width to account for sort/filter icons + padding. */
6
- export const AUTOSIZE_EXTRA_PX = 44;
5
+ /** Extra pixels added to header label width to account for filter icon + padding. */
6
+ export const AUTOSIZE_EXTRA_PX = 28;
7
7
  /** Maximum column width from autosize. */
8
8
  export const AUTOSIZE_MAX_PX = 520;
9
9
  /**
@@ -22,20 +22,34 @@ export function mergeFilter(prev, key, value) {
22
22
  }
23
23
  /** Derive filter options for multiSelect columns from client-side data. */
24
24
  export function deriveFilterOptionsFromData(items, columns) {
25
- const out = {};
26
- columns.forEach((col) => {
25
+ // Collect multiSelect columns upfront
26
+ const filterCols = [];
27
+ for (let i = 0; i < columns.length; i++) {
28
+ const col = columns[i];
27
29
  const f = col.filterable && typeof col.filterable === 'object' ? col.filterable : null;
28
- if (f?.type !== 'multiSelect')
29
- return;
30
- const field = getFilterField(col);
31
- const values = new Set();
32
- items.forEach((item) => {
33
- const v = getCellValue(item, col);
30
+ if (f?.type === 'multiSelect') {
31
+ filterCols.push({ col, field: getFilterField(col) });
32
+ }
33
+ }
34
+ if (filterCols.length === 0)
35
+ return {};
36
+ // Single pass through items, collecting values for all filter columns simultaneously
37
+ const valueSets = new Map();
38
+ for (let i = 0; i < filterCols.length; i++) {
39
+ valueSets.set(filterCols[i].field, new Set());
40
+ }
41
+ for (let i = 0; i < items.length; i++) {
42
+ const item = items[i];
43
+ for (let j = 0; j < filterCols.length; j++) {
44
+ const v = getCellValue(item, filterCols[j].col);
34
45
  if (v != null && v !== '')
35
- values.add(String(v));
36
- });
37
- out[field] = Array.from(values).sort();
38
- });
46
+ valueSets.get(filterCols[j].field).add(String(v));
47
+ }
48
+ }
49
+ const out = {};
50
+ for (let i = 0; i < filterCols.length; i++) {
51
+ out[filterCols[i].field] = Array.from(valueSets.get(filterCols[i].field)).sort();
52
+ }
39
53
  return out;
40
54
  }
41
55
  /** Get list of filter fields that use multiSelect (for useFilterOptions). */
@@ -44,6 +44,8 @@ export interface IColumnDef<T = unknown> extends IColumnMeta {
44
44
  valueGetter?: (item: T) => unknown;
45
45
  /** Format the cell value for display (used when no renderCell). */
46
46
  valueFormatter?: (value: unknown, item: T) => string;
47
+ /** Format the cell value for clipboard copy. When set, overrides valueFormatter for copy/paste. */
48
+ clipboardFormatter?: (value: unknown, item: T) => string;
47
49
  /**
48
50
  * Parse/validate a new value before it is committed to the cell.
49
51
  * Called on paste, inline edit commit, fill handle, and delete.
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Column autosize DOM measurement utilities shared across all frameworks.
3
3
  */
4
- /** Extra pixels added to header label width to account for sort/filter icons + padding. */
5
- export declare const AUTOSIZE_EXTRA_PX = 44;
4
+ /** Extra pixels added to header label width to account for filter icon + padding. */
5
+ export declare const AUTOSIZE_EXTRA_PX = 28;
6
6
  /** Maximum column width from autosize. */
7
7
  export declare const AUTOSIZE_MAX_PX = 520;
8
8
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alaarab/ogrid-core",
3
- "version": "2.0.18",
3
+ "version": "2.0.21",
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",