@alaarab/ogrid-js 2.0.3 → 2.0.5

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.
package/dist/esm/OGrid.js CHANGED
@@ -341,7 +341,7 @@ export class OGrid {
341
341
  const visibleCols = this.state.visibleColumnDefs;
342
342
  // Compute pinning offsets
343
343
  const columnWidths = this.layoutState.getAllColumnWidths();
344
- const leftOffsets = this.pinningState?.computeLeftOffsets(visibleCols, columnWidths, 120, !!this.rowSelectionState, 40) ?? {};
344
+ const leftOffsets = this.pinningState?.computeLeftOffsets(visibleCols, columnWidths, 120, !!this.rowSelectionState, 40, !!this.options.showRowNumbers) ?? {};
345
345
  const rightOffsets = this.pinningState?.computeRightOffsets(visibleCols, columnWidths, 120) ?? {};
346
346
  this.renderer.setInteractionState({
347
347
  activeCell: this.selectionState.activeCell,
@@ -368,6 +368,8 @@ export class OGrid {
368
368
  },
369
369
  allSelected: this.rowSelectionState?.isAllSelected(items),
370
370
  someSelected: this.rowSelectionState?.isSomeSelected(items),
371
+ // Row numbers
372
+ showRowNumbers: this.options.showRowNumbers,
371
373
  // Column pinning
372
374
  pinnedColumns: this.pinningState?.pinnedColumns,
373
375
  leftOffsets,
@@ -392,15 +394,43 @@ export class OGrid {
392
394
  if (!range)
393
395
  return;
394
396
  const norm = normalizeSelectionRange(range);
397
+ const anchor = this.selectionState.dragAnchor;
398
+ const minR = norm.startRow;
399
+ const maxR = norm.endRow;
400
+ const minC = norm.startCol;
401
+ const maxC = norm.endCol;
395
402
  const cells = wrapper.querySelectorAll('td[data-row-index][data-col-index]');
396
403
  for (const cell of Array.from(cells)) {
397
- const rowIndex = parseInt(cell.getAttribute('data-row-index') ?? '-1', 10);
398
- const colIndex = parseInt(cell.getAttribute('data-col-index') ?? '-1', 10);
404
+ const el = cell;
405
+ const rowIndex = parseInt(el.getAttribute('data-row-index') ?? '-1', 10);
406
+ const colIndex = parseInt(el.getAttribute('data-col-index') ?? '-1', 10);
399
407
  if (isInSelectionRange(norm, rowIndex, colIndex)) {
400
- cell.setAttribute('data-drag-range', 'true');
408
+ el.setAttribute('data-drag-range', 'true');
409
+ // Anchor cell (white background)
410
+ const isAnchor = anchor && rowIndex === anchor.rowIndex && colIndex === anchor.columnIndex;
411
+ if (isAnchor) {
412
+ el.setAttribute('data-drag-anchor', '');
413
+ }
414
+ else {
415
+ el.removeAttribute('data-drag-anchor');
416
+ }
417
+ // Edge borders via inset box-shadow
418
+ const shadows = [];
419
+ if (rowIndex === minR)
420
+ shadows.push('inset 0 2px 0 0 var(--ogrid-selection, #217346)');
421
+ if (rowIndex === maxR)
422
+ shadows.push('inset 0 -2px 0 0 var(--ogrid-selection, #217346)');
423
+ if (colIndex === minC)
424
+ shadows.push('inset 2px 0 0 0 var(--ogrid-selection, #217346)');
425
+ if (colIndex === maxC)
426
+ shadows.push('inset -2px 0 0 0 var(--ogrid-selection, #217346)');
427
+ el.style.boxShadow = shadows.length > 0 ? shadows.join(', ') : '';
401
428
  }
402
429
  else {
403
- cell.removeAttribute('data-drag-range');
430
+ el.removeAttribute('data-drag-range');
431
+ el.removeAttribute('data-drag-anchor');
432
+ if (el.style.boxShadow)
433
+ el.style.boxShadow = '';
404
434
  }
405
435
  }
406
436
  }
@@ -416,6 +446,8 @@ export class OGrid {
416
446
  return;
417
447
  e.preventDefault();
418
448
  this.selectionState.startDrag(rowIndex, colIndex);
449
+ // Apply drag attributes immediately for instant visual feedback on the initial cell
450
+ setTimeout(() => this.updateDragAttributes(), 0);
419
451
  }
420
452
  handleCellContextMenu(rowIndex, colIndex, e) {
421
453
  e.preventDefault();
@@ -201,6 +201,7 @@ export class InlineCellEditor {
201
201
  }
202
202
  this.closeEditor();
203
203
  });
204
+ setTimeout(() => input.select(), 0);
204
205
  return input;
205
206
  }
206
207
  createSelectEditor(value, column) {
@@ -1,4 +1,4 @@
1
- import { getCellValue, buildHeaderRows, isInSelectionRange } from '@alaarab/ogrid-core';
1
+ import { getCellValue, buildHeaderRows, isInSelectionRange, ROW_NUMBER_COLUMN_WIDTH } from '@alaarab/ogrid-core';
2
2
  const CHECKBOX_COL_WIDTH = 40;
3
3
  export class TableRenderer {
4
4
  constructor(container, state) {
@@ -80,9 +80,17 @@ export class TableRenderer {
80
80
  const mode = this.interactionState?.rowSelectionMode;
81
81
  return mode === 'single' || mode === 'multiple';
82
82
  }
83
- /** The column index offset for data columns (1 if checkbox column present, else 0). */
83
+ hasRowNumbersColumn() {
84
+ return !!this.interactionState?.showRowNumbers;
85
+ }
86
+ /** The column index offset for data columns (checkbox + row numbers if present). */
84
87
  getColOffset() {
85
- return this.hasCheckboxColumn() ? 1 : 0;
88
+ let offset = 0;
89
+ if (this.hasCheckboxColumn())
90
+ offset++;
91
+ if (this.hasRowNumbersColumn())
92
+ offset++;
93
+ return offset;
86
94
  }
87
95
  applyPinningStyles(el, columnId, isHeader) {
88
96
  const is = this.interactionState;
@@ -182,6 +190,15 @@ export class TableRenderer {
182
190
  this.appendSelectAllCheckbox(th);
183
191
  tr.appendChild(th);
184
192
  }
193
+ // Row numbers header
194
+ if (this.hasRowNumbersColumn()) {
195
+ const th = document.createElement('th');
196
+ th.className = 'ogrid-header-cell ogrid-row-number-header';
197
+ th.style.width = `${ROW_NUMBER_COLUMN_WIDTH}px`;
198
+ th.style.textAlign = 'center';
199
+ th.textContent = '#';
200
+ tr.appendChild(th);
201
+ }
185
202
  for (let colIdx = 0; colIdx < visibleCols.length; colIdx++) {
186
203
  const col = visibleCols[colIdx];
187
204
  const th = document.createElement('th');
@@ -294,8 +311,11 @@ export class TableRenderer {
294
311
  const visibleCols = this.state.visibleColumnDefs;
295
312
  const { items } = this.state.getProcessedItems();
296
313
  const hasCheckbox = this.hasCheckboxColumn();
314
+ const hasRowNumbers = this.hasRowNumbersColumn();
297
315
  const colOffset = this.getColOffset();
298
316
  const totalColSpan = visibleCols.length + colOffset;
317
+ // Calculate row number offset for pagination
318
+ const rowNumberOffset = hasRowNumbers ? (this.state.page - 1) * this.state.pageSize : 0;
299
319
  if (items.length === 0 && !this.state.isLoading) {
300
320
  const tr = document.createElement('tr');
301
321
  const td = document.createElement('td');
@@ -359,6 +379,17 @@ export class TableRenderer {
359
379
  td.appendChild(checkbox);
360
380
  tr.appendChild(td);
361
381
  }
382
+ // Row numbers column
383
+ if (hasRowNumbers) {
384
+ const td = document.createElement('td');
385
+ td.className = 'ogrid-cell ogrid-row-number-cell';
386
+ td.style.width = `${ROW_NUMBER_COLUMN_WIDTH}px`;
387
+ td.style.textAlign = 'center';
388
+ td.style.color = 'var(--ogrid-fg-muted, #666)';
389
+ td.style.fontSize = '0.9em';
390
+ td.textContent = String(rowNumberOffset + rowIndex + 1);
391
+ tr.appendChild(td);
392
+ }
362
393
  for (let colIndex = 0; colIndex < visibleCols.length; colIndex++) {
363
394
  const col = visibleCols[colIndex];
364
395
  const globalColIndex = colIndex + colOffset;
@@ -1,3 +1,4 @@
1
+ import { ROW_NUMBER_COLUMN_WIDTH } from '@alaarab/ogrid-core';
1
2
  import { EventEmitter } from './EventEmitter';
2
3
  /**
3
4
  * Manages column pinning state — tracks which columns are pinned left/right.
@@ -40,9 +41,13 @@ export class ColumnPinningState {
40
41
  * Compute sticky left offsets for left-pinned columns.
41
42
  * Returns a map of columnId -> left offset in pixels.
42
43
  */
43
- computeLeftOffsets(visibleCols, columnWidths, defaultWidth, hasCheckboxColumn, checkboxColumnWidth) {
44
+ computeLeftOffsets(visibleCols, columnWidths, defaultWidth, hasCheckboxColumn, checkboxColumnWidth, hasRowNumbersColumn) {
44
45
  const offsets = {};
45
- let left = hasCheckboxColumn ? checkboxColumnWidth : 0;
46
+ let left = 0;
47
+ if (hasCheckboxColumn)
48
+ left += checkboxColumnWidth;
49
+ if (hasRowNumbersColumn)
50
+ left += ROW_NUMBER_COLUMN_WIDTH;
46
51
  for (const col of visibleCols) {
47
52
  if (this._pinnedColumns[col.columnId] === 'left') {
48
53
  offsets[col.columnId] = left;
@@ -22,6 +22,9 @@ export class SelectionState {
22
22
  get activeCell() {
23
23
  return this._activeCell;
24
24
  }
25
+ get dragAnchor() {
26
+ return this.dragStartCell;
27
+ }
25
28
  get selectionRange() {
26
29
  return this._selectionRange;
27
30
  }
@@ -328,6 +328,10 @@
328
328
  background: var(--ogrid-bg-range, rgba(33, 115, 70, 0.12)) !important;
329
329
  }
330
330
 
331
+ [data-drag-anchor] {
332
+ background: var(--ogrid-bg, #fff) !important;
333
+ }
334
+
331
335
  /* ── Fill Handle ── */
332
336
 
333
337
  .ogrid-fill-handle {
@@ -25,6 +25,7 @@ export interface TableRendererInteractionState {
25
25
  onSelectAll?: (checked: boolean) => void;
26
26
  allSelected?: boolean;
27
27
  someSelected?: boolean;
28
+ showRowNumbers?: boolean;
28
29
  pinnedColumns?: Record<string, 'left' | 'right'>;
29
30
  leftOffsets?: Record<string, number>;
30
31
  rightOffsets?: Record<string, number>;
@@ -54,7 +55,8 @@ export declare class TableRenderer<T> {
54
55
  /** Re-render body rows and header (after sort/filter/page change). */
55
56
  update(): void;
56
57
  private hasCheckboxColumn;
57
- /** The column index offset for data columns (1 if checkbox column present, else 0). */
58
+ private hasRowNumbersColumn;
59
+ /** The column index offset for data columns (checkbox + row numbers if present). */
58
60
  getColOffset(): number;
59
61
  private applyPinningStyles;
60
62
  private renderHeader;
@@ -22,7 +22,7 @@ export declare class ColumnPinningState {
22
22
  */
23
23
  computeLeftOffsets(visibleCols: {
24
24
  columnId: string;
25
- }[], columnWidths: Record<string, number>, defaultWidth: number, hasCheckboxColumn: boolean, checkboxColumnWidth: number): Record<string, number>;
25
+ }[], columnWidths: Record<string, number>, defaultWidth: number, hasCheckboxColumn: boolean, checkboxColumnWidth: number, hasRowNumbersColumn?: boolean): Record<string, number>;
26
26
  /**
27
27
  * Compute sticky right offsets for right-pinned columns.
28
28
  * Returns a map of columnId -> right offset in pixels.
@@ -18,6 +18,7 @@ export declare class SelectionState {
18
18
  private rafHandle;
19
19
  private pendingRange;
20
20
  get activeCell(): IActiveCell | null;
21
+ get dragAnchor(): IActiveCell | null;
21
22
  get selectionRange(): ISelectionRange | null;
22
23
  get selectedRowIds(): Set<RowId>;
23
24
  get isDragging(): boolean;
@@ -39,6 +39,8 @@ export interface OGridOptions<T> {
39
39
  cellSelection?: boolean;
40
40
  /** Callback fired when a cell value is changed via editing. */
41
41
  onCellValueChanged?: (event: ICellValueChangedEvent<T>) => void;
42
+ /** Show row numbers column. Default: false. */
43
+ showRowNumbers?: boolean;
42
44
  rowSelection?: RowSelectionMode;
43
45
  /** Callback fired when row selection changes. */
44
46
  onSelectionChange?: (event: IRowSelectionChangeEvent<T>) => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alaarab/ogrid-js",
3
- "version": "2.0.3",
3
+ "version": "2.0.5",
4
4
  "description": "OGrid vanilla JS – framework-free data grid with sorting, filtering, pagination, and spreadsheet-style editing.",
5
5
  "main": "dist/esm/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -23,7 +23,7 @@
23
23
  "files": ["dist", "README.md", "LICENSE"],
24
24
  "engines": { "node": ">=18" },
25
25
  "dependencies": {
26
- "@alaarab/ogrid-core": "2.0.3"
26
+ "@alaarab/ogrid-core": "2.0.5"
27
27
  },
28
28
  "publishConfig": { "access": "public" }
29
29
  }