@alaarab/ogrid-core 2.0.9 → 2.0.12

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.
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Column autosize DOM measurement utilities shared across all frameworks.
3
+ */
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;
7
+ /** Maximum column width from autosize. */
8
+ export const AUTOSIZE_MAX_PX = 520;
9
+ /**
10
+ * Measure the ideal width for a column by scanning all DOM cells with
11
+ * `[data-column-id="<columnId>"]` and computing the maximum scrollWidth.
12
+ *
13
+ * Header cells with a `[data-header-label]` child get extra padding for icons.
14
+ *
15
+ * @param columnId - Column to measure
16
+ * @param minWidth - Minimum width (defaults to DEFAULT_MIN_COLUMN_WIDTH)
17
+ * @param container - Optional container element to scope the query (defaults to `document`)
18
+ * @returns The ideal column width in pixels, clamped between minWidth and AUTOSIZE_MAX_PX
19
+ */
20
+ export function measureColumnContentWidth(columnId, minWidth, container) {
21
+ const minW = minWidth ?? DEFAULT_MIN_COLUMN_WIDTH;
22
+ const root = container ?? document;
23
+ const cells = root.querySelectorAll(`[data-column-id="${columnId}"]`);
24
+ if (cells.length === 0)
25
+ return minW;
26
+ let maxWidth = minW;
27
+ cells.forEach((cell) => {
28
+ const el = cell;
29
+ const label = el.querySelector?.('[data-header-label]');
30
+ if (label) {
31
+ maxWidth = Math.max(maxWidth, label.scrollWidth + AUTOSIZE_EXTRA_PX);
32
+ }
33
+ else {
34
+ maxWidth = Math.max(maxWidth, el.scrollWidth);
35
+ }
36
+ });
37
+ return Math.min(AUTOSIZE_MAX_PX, Math.max(minW, Math.ceil(maxWidth)));
38
+ }
@@ -76,7 +76,8 @@ export function getCellRenderDescriptor(item, col, rowIndex, colIdx, input) {
76
76
  const canEditAny = canEditInline || canEditPopup;
77
77
  const isEditing = input.editingCell?.rowId === rowId &&
78
78
  input.editingCell?.columnId === col.columnId;
79
- const isActive = input.activeCell?.rowIndex === rowIndex &&
79
+ const isActive = !input.isDragging &&
80
+ input.activeCell?.rowIndex === rowIndex &&
80
81
  input.activeCell?.columnIndex === globalColIndex;
81
82
  const isInRange = input.selectionRange != null &&
82
83
  isInSelectionRange(input.selectionRange, rowIndex, colIdx);
@@ -0,0 +1,53 @@
1
+ /**
2
+ * DOM utility functions for OGrid components.
3
+ * These utilities are framework-agnostic and can be used across React, Angular, Vue, and vanilla JS implementations.
4
+ */
5
+ /**
6
+ * Measure the bounding rect of a cell range within a container.
7
+ *
8
+ * @param container - The grid container element with position: relative
9
+ * @param range - The selection range to measure
10
+ * @param colOffset - Column offset (1 when checkbox column is present, else 0)
11
+ * @returns Rectangle describing the range position and size, or null if cells not found
12
+ */
13
+ export function measureRange(container, range, colOffset) {
14
+ const startGlobalCol = range.startCol + colOffset;
15
+ const endGlobalCol = range.endCol + colOffset;
16
+ const topLeft = container.querySelector(`[data-row-index="${range.startRow}"][data-col-index="${startGlobalCol}"]`);
17
+ const bottomRight = container.querySelector(`[data-row-index="${range.endRow}"][data-col-index="${endGlobalCol}"]`);
18
+ if (!topLeft || !bottomRight)
19
+ return null;
20
+ const cRect = container.getBoundingClientRect();
21
+ const tlRect = topLeft.getBoundingClientRect();
22
+ const brRect = bottomRight.getBoundingClientRect();
23
+ return {
24
+ top: tlRect.top - cRect.top,
25
+ left: tlRect.left - cRect.left,
26
+ width: brRect.right - tlRect.left,
27
+ height: brRect.bottom - tlRect.top,
28
+ };
29
+ }
30
+ /**
31
+ * Inject a global CSS rule into the document head (once per page, deduplicated by ID).
32
+ *
33
+ * @param id - Unique ID for the style element (prevents duplicates)
34
+ * @param css - CSS content to inject
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * injectGlobalStyles(
39
+ * 'ogrid-marching-ants-keyframes',
40
+ * '@keyframes ogrid-marching-ants{to{stroke-dashoffset:-8}}'
41
+ * );
42
+ * ```
43
+ */
44
+ export function injectGlobalStyles(id, css) {
45
+ if (typeof document === 'undefined')
46
+ return;
47
+ if (document.getElementById(id))
48
+ return;
49
+ const style = document.createElement('style');
50
+ style.id = id;
51
+ style.textContent = css;
52
+ document.head.appendChild(style);
53
+ }
@@ -50,3 +50,31 @@ export const COLUMN_HEADER_MENU_ITEMS = [
50
50
  { id: 'pinRight', label: 'Pin right' },
51
51
  { id: 'unpin', label: 'Unpin' },
52
52
  ];
53
+ /**
54
+ * Builds the complete column header menu items based on current state.
55
+ * Returns pinning, sorting, and sizing options.
56
+ */
57
+ export function getColumnHeaderMenuItems(input) {
58
+ const { canPinLeft, canPinRight, canUnpin, currentSort, isSortable = true, isResizable = true } = input;
59
+ const items = [];
60
+ // Pinning section
61
+ items.push({ id: 'pinLeft', label: 'Pin left', disabled: !canPinLeft }, { id: 'pinRight', label: 'Pin right', disabled: !canPinRight }, { id: 'unpin', label: 'Unpin', disabled: !canUnpin, divider: isSortable || isResizable });
62
+ // Sorting section
63
+ if (isSortable) {
64
+ if (!currentSort) {
65
+ // No sort applied - show both options
66
+ items.push({ id: 'sortAsc', label: 'Sort ascending' }, { id: 'sortDesc', label: 'Sort descending', divider: isResizable });
67
+ }
68
+ else {
69
+ // Sort applied - show opposite + clear
70
+ const oppositeSort = currentSort === 'asc' ? 'desc' : 'asc';
71
+ const oppositeLabel = currentSort === 'asc' ? 'Sort descending' : 'Sort ascending';
72
+ items.push({ id: `sort${oppositeSort === 'asc' ? 'Asc' : 'Desc'}`, label: oppositeLabel }, { id: 'clearSort', label: 'Clear sort', divider: isResizable });
73
+ }
74
+ }
75
+ // Autosize section
76
+ if (isResizable) {
77
+ items.push({ id: 'autosizeThis', label: 'Autosize this column' }, { id: 'autosizeAll', label: 'Autosize all columns' });
78
+ }
79
+ return items;
80
+ }
@@ -5,7 +5,7 @@ export { getFilterField, mergeFilter, deriveFilterOptionsFromData, getMultiSelec
5
5
  export { getStatusBarParts } from './statusBarHelpers';
6
6
  export { getDataGridStatusBarConfig } from './dataGridStatusBar';
7
7
  export { getPaginationViewModel, PAGE_SIZE_OPTIONS, MAX_PAGE_BUTTONS, } from './paginationHelpers';
8
- export { GRID_CONTEXT_MENU_ITEMS, COLUMN_HEADER_MENU_ITEMS, getContextMenuHandlers, formatShortcut } from './gridContextMenuHelpers';
8
+ export { GRID_CONTEXT_MENU_ITEMS, COLUMN_HEADER_MENU_ITEMS, getContextMenuHandlers, getColumnHeaderMenuItems, formatShortcut } from './gridContextMenuHelpers';
9
9
  export { parseValue, numberParser, currencyParser, dateParser, emailParser, booleanParser, } from './valueParsers';
10
10
  export { computeAggregations } from './aggregationUtils';
11
11
  export { processClientSideData } from './clientSideData';
@@ -14,3 +14,6 @@ export { getPinStateForColumn, reorderColumnArray, calculateDropTarget, } from '
14
14
  export { computeVisibleRange, computeTotalHeight, getScrollTopForRow, } from './virtualScroll';
15
15
  export { getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, } from './dataGridViewModel';
16
16
  export { debounce } from './debounce';
17
+ export { measureRange, injectGlobalStyles } from './dom';
18
+ export { computeNextSortState } from './sortHelpers';
19
+ export { measureColumnContentWidth, AUTOSIZE_EXTRA_PX, AUTOSIZE_MAX_PX } from './columnAutosize';
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Sort state computation helpers shared across all frameworks.
3
+ */
4
+ /**
5
+ * Compute the next sort state given the current state and a sort request.
6
+ *
7
+ * @param current - Current sort state
8
+ * @param columnKey - Column being sorted
9
+ * @param direction - Explicit direction, `null` to clear, or `undefined` to toggle
10
+ * @returns New sort state
11
+ */
12
+ export function computeNextSortState(current, columnKey, direction) {
13
+ if (direction === null) {
14
+ // Clear sort
15
+ return { field: '', direction: 'asc' };
16
+ }
17
+ else if (direction) {
18
+ // Explicit direction (from column menu)
19
+ return { field: columnKey, direction };
20
+ }
21
+ else {
22
+ // Toggle (existing behavior for header click)
23
+ return {
24
+ field: columnKey,
25
+ direction: current.field === columnKey && current.direction === 'asc' ? 'desc' : 'asc',
26
+ };
27
+ }
28
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Column autosize DOM measurement utilities shared across all frameworks.
3
+ */
4
+ /** Extra pixels added to header label width to account for sort/filter icons + padding. */
5
+ export declare const AUTOSIZE_EXTRA_PX = 44;
6
+ /** Maximum column width from autosize. */
7
+ export declare const AUTOSIZE_MAX_PX = 520;
8
+ /**
9
+ * Measure the ideal width for a column by scanning all DOM cells with
10
+ * `[data-column-id="<columnId>"]` and computing the maximum scrollWidth.
11
+ *
12
+ * Header cells with a `[data-header-label]` child get extra padding for icons.
13
+ *
14
+ * @param columnId - Column to measure
15
+ * @param minWidth - Minimum width (defaults to DEFAULT_MIN_COLUMN_WIDTH)
16
+ * @param container - Optional container element to scope the query (defaults to `document`)
17
+ * @returns The ideal column width in pixels, clamped between minWidth and AUTOSIZE_MAX_PX
18
+ */
19
+ export declare function measureColumnContentWidth(columnId: string, minWidth?: number, container?: {
20
+ querySelectorAll: (selector: string) => NodeListOf<Element>;
21
+ }): number;
@@ -9,7 +9,7 @@ import type { RowId, UserLike, IFilters, FilterValue } from '../types/dataGridTy
9
9
  export interface HeaderFilterConfigInput {
10
10
  sortBy?: string;
11
11
  sortDirection: 'asc' | 'desc';
12
- onColumnSort: (columnKey: string) => void;
12
+ onColumnSort: (columnKey: string, direction?: 'asc' | 'desc' | null) => void;
13
13
  filters: IFilters;
14
14
  onFilterChange: (key: string, value: FilterValue | undefined) => void;
15
15
  filterOptions: Record<string, string[]>;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * DOM utility functions for OGrid components.
3
+ * These utilities are framework-agnostic and can be used across React, Angular, Vue, and vanilla JS implementations.
4
+ */
5
+ import type { ISelectionRange } from '../types';
6
+ /**
7
+ * Rectangle describing position and dimensions of a DOM element.
8
+ */
9
+ export interface OverlayRect {
10
+ top: number;
11
+ left: number;
12
+ width: number;
13
+ height: number;
14
+ }
15
+ /**
16
+ * Measure the bounding rect of a cell range within a container.
17
+ *
18
+ * @param container - The grid container element with position: relative
19
+ * @param range - The selection range to measure
20
+ * @param colOffset - Column offset (1 when checkbox column is present, else 0)
21
+ * @returns Rectangle describing the range position and size, or null if cells not found
22
+ */
23
+ export declare function measureRange(container: HTMLElement, range: ISelectionRange, colOffset: number): OverlayRect | null;
24
+ /**
25
+ * Inject a global CSS rule into the document head (once per page, deduplicated by ID).
26
+ *
27
+ * @param id - Unique ID for the style element (prevents duplicates)
28
+ * @param css - CSS content to inject
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * injectGlobalStyles(
33
+ * 'ogrid-marching-ants-keyframes',
34
+ * '@keyframes ogrid-marching-ants{to{stroke-dashoffset:-8}}'
35
+ * );
36
+ * ```
37
+ */
38
+ export declare function injectGlobalStyles(id: string, css: string): void;
@@ -34,6 +34,34 @@ export interface IColumnHeaderMenuItem {
34
34
  id: string;
35
35
  label: string;
36
36
  icon?: string;
37
+ disabled?: boolean;
38
+ divider?: boolean;
37
39
  }
38
40
  /** Column header menu items for pin/unpin actions. */
39
41
  export declare const COLUMN_HEADER_MENU_ITEMS: IColumnHeaderMenuItem[];
42
+ /** Input for building column header menu items. */
43
+ export interface ColumnHeaderMenuInput {
44
+ canPinLeft: boolean;
45
+ canPinRight: boolean;
46
+ canUnpin: boolean;
47
+ currentSort?: 'asc' | 'desc' | null;
48
+ isSortable?: boolean;
49
+ isResizable?: boolean;
50
+ }
51
+ /**
52
+ * Builds the complete column header menu items based on current state.
53
+ * Returns pinning, sorting, and sizing options.
54
+ */
55
+ export declare function getColumnHeaderMenuItems(input: ColumnHeaderMenuInput): IColumnHeaderMenuItem[];
56
+ /** Handlers for column header menu actions. */
57
+ export interface ColumnHeaderMenuHandlers {
58
+ onPinLeft: () => void;
59
+ onPinRight: () => void;
60
+ onUnpin: () => void;
61
+ onSortAsc: () => void;
62
+ onSortDesc: () => void;
63
+ onClearSort: () => void;
64
+ onAutosizeThis: () => void;
65
+ onAutosizeAll: () => void;
66
+ onClose: () => void;
67
+ }
@@ -6,10 +6,10 @@ export { getStatusBarParts } from './statusBarHelpers';
6
6
  export { getDataGridStatusBarConfig } from './dataGridStatusBar';
7
7
  export { getPaginationViewModel, PAGE_SIZE_OPTIONS, MAX_PAGE_BUTTONS, } from './paginationHelpers';
8
8
  export type { PaginationViewModel } from './paginationHelpers';
9
- export { GRID_CONTEXT_MENU_ITEMS, COLUMN_HEADER_MENU_ITEMS, getContextMenuHandlers, formatShortcut } from './gridContextMenuHelpers';
9
+ export { GRID_CONTEXT_MENU_ITEMS, COLUMN_HEADER_MENU_ITEMS, getContextMenuHandlers, getColumnHeaderMenuItems, formatShortcut } from './gridContextMenuHelpers';
10
10
  export type { CsvColumn } from './exportToCsv';
11
11
  export type { StatusBarPart, StatusBarPartsInput } from './statusBarHelpers';
12
- export type { GridContextMenuItem, IColumnHeaderMenuItem, GridContextMenuHandlerProps } from './gridContextMenuHelpers';
12
+ export type { GridContextMenuItem, IColumnHeaderMenuItem, GridContextMenuHandlerProps, ColumnHeaderMenuInput, ColumnHeaderMenuHandlers } from './gridContextMenuHelpers';
13
13
  export { parseValue, numberParser, currencyParser, dateParser, emailParser, booleanParser, } from './valueParsers';
14
14
  export type { ParseValueResult } from './valueParsers';
15
15
  export { computeAggregations } from './aggregationUtils';
@@ -24,3 +24,8 @@ export type { IVisibleRange } from './virtualScroll';
24
24
  export { getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, } from './dataGridViewModel';
25
25
  export type { HeaderFilterConfigInput, HeaderFilterConfig, CellRenderDescriptorInput, CellRenderDescriptor, CellRenderMode, } from './dataGridViewModel';
26
26
  export { debounce } from './debounce';
27
+ export { measureRange, injectGlobalStyles } from './dom';
28
+ export type { OverlayRect } from './dom';
29
+ export { computeNextSortState } from './sortHelpers';
30
+ export type { ISortState } from './sortHelpers';
31
+ export { measureColumnContentWidth, AUTOSIZE_EXTRA_PX, AUTOSIZE_MAX_PX } from './columnAutosize';
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Sort state computation helpers shared across all frameworks.
3
+ */
4
+ export interface ISortState {
5
+ field: string;
6
+ direction: 'asc' | 'desc';
7
+ }
8
+ /**
9
+ * Compute the next sort state given the current state and a sort request.
10
+ *
11
+ * @param current - Current sort state
12
+ * @param columnKey - Column being sorted
13
+ * @param direction - Explicit direction, `null` to clear, or `undefined` to toggle
14
+ * @returns New sort state
15
+ */
16
+ export declare function computeNextSortState(current: ISortState, columnKey: string, direction?: 'asc' | 'desc' | null): ISortState;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alaarab/ogrid-core",
3
- "version": "2.0.9",
3
+ "version": "2.0.12",
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",