@alaarab/ogrid-vue 2.1.10 → 2.1.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.
package/dist/esm/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { injectGlobalStyles, Z_INDEX, getStatusBarParts, measureRange, flattenColumns, getMultiSelectFilterFields, deriveFilterOptionsFromData, processClientSideData, validateColumns, validateRowIds, computeRowSelectionState, buildCellIndex, UndoRedoStack, CHECKBOX_COLUMN_WIDTH, DEFAULT_MIN_COLUMN_WIDTH, CELL_PADDING, computeAggregations, getDataGridStatusBarConfig, computeVisibleRange, computeTotalHeight, buildHeaderRows, ROW_NUMBER_COLUMN_WIDTH, getHeaderFilterConfig, getCellRenderDescriptor, buildInlineEditorProps, buildPopoverEditorProps, resolveCellDisplayContent, resolveCellStyle, rangesEqual, normalizeSelectionRange, formatSelectionAsTsv, parseTsvClipboard, applyPastedValues, applyCutClear, measureColumnContentWidth, getPinStateForColumn, parseValue, applyFillValues, applyCellDeletion, computeTabNavigation, computeArrowNavigation, computeNextSortState, mergeFilter, applyRangeRowSelection, getScrollTopForRow, getCellValue, calculateDropTarget, reorderColumnArray, computeAutoScrollSpeed } from '@alaarab/ogrid-core';
1
+ import { injectGlobalStyles, Z_INDEX, getStatusBarParts, measureRange, flattenColumns, getMultiSelectFilterFields, deriveFilterOptionsFromData, processClientSideData, validateColumns, validateRowIds, computeRowSelectionState, buildCellIndex, UndoRedoStack, CHECKBOX_COLUMN_WIDTH, DEFAULT_MIN_COLUMN_WIDTH, CELL_PADDING, computeAggregations, getDataGridStatusBarConfig, validateVirtualScrollConfig, computeVisibleRange, computeTotalHeight, buildHeaderRows, ROW_NUMBER_COLUMN_WIDTH, getHeaderFilterConfig, getCellRenderDescriptor, buildInlineEditorProps, buildPopoverEditorProps, resolveCellDisplayContent, resolveCellStyle, rangesEqual, normalizeSelectionRange, formatSelectionAsTsv, parseTsvClipboard, applyPastedValues, applyCutClear, measureColumnContentWidth, getPinStateForColumn, parseValue, applyFillValues, applyCellDeletion, computeTabNavigation, computeArrowNavigation, computeNextSortState, mergeFilter, applyRangeRowSelection, getScrollTopForRow, getCellValue, calculateDropTarget, reorderColumnArray, computeAutoScrollSpeed } from '@alaarab/ogrid-core';
2
2
  export * from '@alaarab/ogrid-core';
3
3
  export { buildInlineEditorProps, buildPopoverEditorProps, getCellRenderDescriptor, getHeaderFilterConfig, isInSelectionRange, normalizeSelectionRange, resolveCellDisplayContent, resolveCellStyle, toUserLike } from '@alaarab/ogrid-core';
4
4
  import { defineComponent, ref, computed, onMounted, watch, toValue, onUnmounted, h, shallowRef, triggerRef, nextTick, Teleport, isRef, isReadonly, unref, customRef } from 'vue';
@@ -1254,6 +1254,12 @@ function useKeyboardNavigation(params) {
1254
1254
  const rowSelection = features.rowSelection.value;
1255
1255
  const wrapperRef = features.wrapperRef;
1256
1256
  const scrollToRow = features.scrollToRow;
1257
+ const { fillDown } = features;
1258
+ const onKeyDown = features.onKeyDown?.value;
1259
+ if (onKeyDown) {
1260
+ onKeyDown(e);
1261
+ if (e.defaultPrevented) return;
1262
+ }
1257
1263
  const maxRowIndex = items.length - 1;
1258
1264
  const maxColIndex = visibleColumnCount - 1 + colOffset;
1259
1265
  if (items.length === 0) return;
@@ -1294,6 +1300,15 @@ function useKeyboardNavigation(params) {
1294
1300
  void handlePaste();
1295
1301
  }
1296
1302
  break;
1303
+ case "d":
1304
+ if (e.ctrlKey || e.metaKey) {
1305
+ if (editingCell != null) break;
1306
+ if (editable !== false && fillDown) {
1307
+ e.preventDefault();
1308
+ fillDown();
1309
+ }
1310
+ }
1311
+ break;
1297
1312
  case "ArrowDown":
1298
1313
  case "ArrowUp":
1299
1314
  case "ArrowRight":
@@ -1622,7 +1637,21 @@ function useFillHandle(params) {
1622
1637
  if (!range) return;
1623
1638
  fillDrag.value = { startRow: range.startRow, startCol: range.startCol };
1624
1639
  };
1625
- return { fillDrag, setFillDrag, handleFillHandleMouseDown };
1640
+ const fillDown = () => {
1641
+ const range = selectionRange.value;
1642
+ if (!range || editable.value === false || !onCellValueChanged.value) return;
1643
+ const norm = normalizeSelectionRange(range);
1644
+ const currentItems = items.value;
1645
+ const currentCols = visibleCols.value;
1646
+ const callback = onCellValueChanged.value;
1647
+ const fillEvents = applyFillValues(norm, norm.startRow, norm.startCol, currentItems, currentCols);
1648
+ if (fillEvents.length > 0) {
1649
+ beginBatch?.();
1650
+ for (const evt of fillEvents) callback(evt);
1651
+ endBatch?.();
1652
+ }
1653
+ };
1654
+ return { fillDrag, setFillDrag, handleFillHandleMouseDown, fillDown };
1626
1655
  }
1627
1656
  function useUndoRedo(params) {
1628
1657
  const { onCellValueChanged, maxUndoDepth = 100 } = params;
@@ -2034,6 +2063,19 @@ function useDataGridState(params) {
2034
2063
  clearClipboardRanges();
2035
2064
  handleCellMouseDownBase(e, rowIndex, globalColIndex);
2036
2065
  };
2066
+ const { handleFillHandleMouseDown, fillDown } = useFillHandle({
2067
+ items,
2068
+ visibleCols,
2069
+ editable: editableProp,
2070
+ onCellValueChanged,
2071
+ selectionRange,
2072
+ setSelectionRange,
2073
+ setActiveCell,
2074
+ colOffset,
2075
+ wrapperRef,
2076
+ beginBatch: undoRedo.beginBatch,
2077
+ endBatch: undoRedo.endBatch
2078
+ });
2037
2079
  const { handleGridKeyDown } = useKeyboardNavigation({
2038
2080
  data: { items, visibleCols, colOffset, hasCheckboxCol, visibleColumnCount, getRowId },
2039
2081
  state: { activeCell, selectionRange, editingCell, selectedRowIds: rowSelectionResult.selectedRowIds },
@@ -2054,22 +2096,11 @@ function useDataGridState(params) {
2054
2096
  editable: editableProp,
2055
2097
  onCellValueChanged,
2056
2098
  rowSelection: rowSelectionProp,
2057
- wrapperRef
2099
+ wrapperRef,
2100
+ fillDown,
2101
+ onKeyDown: computed(() => props.value.onKeyDown)
2058
2102
  }
2059
2103
  });
2060
- const { handleFillHandleMouseDown } = useFillHandle({
2061
- items,
2062
- visibleCols,
2063
- editable: editableProp,
2064
- onCellValueChanged,
2065
- selectionRange,
2066
- setSelectionRange,
2067
- setActiveCell,
2068
- colOffset,
2069
- wrapperRef,
2070
- beginBatch: undoRedo.beginBatch,
2071
- endBatch: undoRedo.endBatch
2072
- });
2073
2104
  const {
2074
2105
  containerWidth,
2075
2106
  minTableWidth,
@@ -3050,17 +3081,22 @@ function useColumnReorder(params) {
3050
3081
  };
3051
3082
  return { isDragging, dropIndicatorX, handleHeaderMouseDown };
3052
3083
  }
3084
+ var DEFAULT_PASSTHROUGH_THRESHOLD = 100;
3053
3085
  function useVirtualScroll(params) {
3054
- const { totalRows, rowHeight, enabled, overscan = 5 } = params;
3086
+ const { totalRows, rowHeight, enabled, overscan = 5, threshold = DEFAULT_PASSTHROUGH_THRESHOLD } = params;
3087
+ onMounted(() => {
3088
+ validateVirtualScrollConfig({ enabled: enabled.value, rowHeight });
3089
+ });
3055
3090
  const containerRef = ref(null);
3056
3091
  const scrollTop = ref(0);
3057
3092
  const containerHeight = ref(0);
3058
3093
  let rafId = 0;
3059
3094
  let resizeObserver;
3060
3095
  let prevObservedEl = null;
3096
+ const isActive = computed(() => enabled.value && totalRows.value >= threshold);
3061
3097
  const visibleRange = computed(() => {
3062
- if (!enabled.value) {
3063
- return { startIndex: 0, endIndex: totalRows.value - 1, offsetTop: 0, offsetBottom: 0 };
3098
+ if (!isActive.value) {
3099
+ return { startIndex: 0, endIndex: Math.max(0, totalRows.value - 1), offsetTop: 0, offsetBottom: 0 };
3064
3100
  }
3065
3101
  return computeVisibleRange(
3066
3102
  scrollTop.value,
@@ -3309,7 +3345,7 @@ function createDataGridTable(ui) {
3309
3345
  isDragging: _isDragging
3310
3346
  } = interaction;
3311
3347
  const { menuPosition, handleCellContextMenu, closeContextMenu } = ctxMenu;
3312
- const { headerFilterInput, cellDescriptorInput, statusBarConfig, showEmptyInGrid, onCellError: _onCellError } = viewModels;
3348
+ const { headerFilterInput, cellDescriptorInput, statusBarConfig, showEmptyInGrid, onCellError } = viewModels;
3313
3349
  const items = p.items;
3314
3350
  const getRowId = p.getRowId;
3315
3351
  const layoutMode = p.layoutMode ?? "fill";
@@ -3333,6 +3369,16 @@ function createDataGridTable(ui) {
3333
3369
  rowSel.updateSelection(selectedRowIds.has(rowId) ? /* @__PURE__ */ new Set() : /* @__PURE__ */ new Set([rowId]));
3334
3370
  };
3335
3371
  const renderCellContent = (item, col, rowIndex, colIdx) => {
3372
+ try {
3373
+ return renderCellContentInner(item, col, rowIndex, colIdx);
3374
+ } catch (err) {
3375
+ if (onCellError) {
3376
+ onCellError(err instanceof Error ? err : new Error(String(err)), void 0);
3377
+ }
3378
+ return "";
3379
+ }
3380
+ };
3381
+ const renderCellContentInner = (item, col, rowIndex, colIdx) => {
3336
3382
  const descriptor = getCellRenderDescriptor(item, col, rowIndex, colIdx, cellDescriptorInput);
3337
3383
  if (descriptor.mode === "editing-inline") {
3338
3384
  const editorProps = buildInlineEditorProps(item, col, descriptor, editCallbacks);
@@ -3454,6 +3500,7 @@ function createDataGridTable(ui) {
3454
3500
  tableRef.value = el;
3455
3501
  },
3456
3502
  class: "ogrid-table",
3503
+ role: "grid",
3457
3504
  style: { minWidth: `${minTableWidth}px` }
3458
3505
  }, [
3459
3506
  // Header
@@ -3531,6 +3578,8 @@ function createDataGridTable(ui) {
3531
3578
  if (!cell.columnDef) return null;
3532
3579
  const col = cell.columnDef;
3533
3580
  const { classes: headerClasses, style: headerStyle } = getHeaderClassAndStyle(col);
3581
+ const isSorted = p.sortBy === col.columnId;
3582
+ const ariaSort = isSorted ? p.sortDirection === "asc" ? "ascending" : "descending" : void 0;
3534
3583
  return h("th", {
3535
3584
  key: col.columnId,
3536
3585
  scope: "col",
@@ -3538,6 +3587,7 @@ function createDataGridTable(ui) {
3538
3587
  rowSpan: headerRows.length > 1 ? headerRows.length - rowIdx : void 0,
3539
3588
  class: headerClasses,
3540
3589
  style: headerStyle,
3590
+ "aria-sort": ariaSort,
3541
3591
  onMousedown: (e) => handleReorderMouseDown(col.columnId, e)
3542
3592
  }, [
3543
3593
  h("div", { class: "ogrid-header-content" }, [
@@ -3586,6 +3636,7 @@ function createDataGridTable(ui) {
3586
3636
  rows.push(h("tr", {
3587
3637
  key: rowIdStr,
3588
3638
  "data-row-id": rowIdStr,
3639
+ "aria-selected": isSelected || void 0,
3589
3640
  onClick: handleSingleRowClick,
3590
3641
  style: { cursor: rowSelection === "single" ? "pointer" : void 0 }
3591
3642
  }, [
@@ -1,29 +1,33 @@
1
1
  /* OGrid Shared Layout Styles — consumed by vue-vuetify and vue-primevue */
2
2
 
3
- /* Remove focus outline from scrollable wrapper (keyboard nav is handled via cell outlines) */
3
+ /* Remove focus outline from scrollable wrapper (keyboard nav is handled via cell outlines).
4
+ [role="region"][tabindex] has attribute-selector specificity (0,2,0) which beats any
5
+ framework's single-class or element rule — no !important needed. */
4
6
  [role="region"][tabindex="0"] {
5
- outline: none !important;
7
+ outline: none;
6
8
  }
7
9
 
8
- /* Cell selection highlighting */
9
- .ogrid-cell-in-range {
10
- background: var(--ogrid-bg-range, rgba(33, 115, 70, 0.12)) !important;
10
+ /* Cell selection highlighting.
11
+ Qualify with .ogrid-outer-container (specificity 0,2,0) to beat row-level hover backgrounds. */
12
+ .ogrid-outer-container .ogrid-cell-in-range {
13
+ background: var(--ogrid-bg-range, rgba(33, 115, 70, 0.12));
11
14
  }
12
15
 
13
16
  /* Cut range highlighting */
14
- .ogrid-cell-cut {
15
- background: var(--ogrid-hover-bg) !important;
17
+ .ogrid-outer-container .ogrid-cell-cut {
18
+ background: var(--ogrid-hover-bg);
16
19
  opacity: 0.7;
17
20
  }
18
21
 
19
- /* Drag-range highlight applied via DOM attributes during drag (bypasses Vue for performance) */
20
- [data-drag-range] {
21
- background: var(--ogrid-range-bg, rgba(33, 115, 70, 0.12)) !important;
22
+ /* Drag-range highlight applied via DOM attributes during drag (bypasses Vue for performance).
23
+ Qualifier .ogrid-outer-container raises specificity to 0,2,0, enough to beat row-hover rules. */
24
+ .ogrid-outer-container [data-drag-range] {
25
+ background: var(--ogrid-range-bg, rgba(33, 115, 70, 0.12));
22
26
  }
23
27
 
24
28
  /* Anchor cell during drag: white/transparent background (like Excel) */
25
- [data-drag-anchor] {
26
- background: var(--ogrid-bg) !important;
29
+ .ogrid-outer-container [data-drag-anchor] {
30
+ background: var(--ogrid-bg);
27
31
  }
28
32
 
29
33
  /* === Layout === */
@@ -67,7 +71,7 @@
67
71
  /* === Header === */
68
72
 
69
73
  .ogrid-thead {
70
- z-index: 8;
74
+ z-index: var(--ogrid-z-thead, 8);
71
75
  background-color: var(--ogrid-header-bg);
72
76
  }
73
77
 
@@ -78,7 +82,7 @@
78
82
  .ogrid-header-cell {
79
83
  font-weight: 600;
80
84
  background-color: var(--ogrid-header-bg);
81
- z-index: 8;
85
+ z-index: var(--ogrid-z-thead, 8);
82
86
  }
83
87
 
84
88
  .ogrid-sticky-header .ogrid-header-cell {
@@ -87,14 +91,14 @@
87
91
  }
88
92
 
89
93
  .ogrid-header-cell--pinned-left {
90
- z-index: 10;
94
+ z-index: var(--ogrid-z-pinned-header, 10);
91
95
  will-change: transform;
92
96
  border-right: 1px solid var(--ogrid-border, rgba(0, 0, 0, 0.12));
93
97
  box-shadow: 2px 0 4px -1px rgba(0, 0, 0, 0.1);
94
98
  }
95
99
 
96
100
  .ogrid-header-cell--pinned-right {
97
- z-index: 10;
101
+ z-index: var(--ogrid-z-pinned-header, 10);
98
102
  will-change: transform;
99
103
  border-left: 1px solid var(--ogrid-border, rgba(0, 0, 0, 0.12));
100
104
  box-shadow: -2px 0 4px -1px rgba(0, 0, 0, 0.1);
@@ -189,7 +193,7 @@
189
193
 
190
194
  .ogrid-data-cell--pinned-left {
191
195
  position: sticky;
192
- z-index: 6;
196
+ z-index: var(--ogrid-z-pinned, 6);
193
197
  background-color: var(--ogrid-bg);
194
198
  will-change: transform;
195
199
  border-right: 1px solid var(--ogrid-border, rgba(0, 0, 0, 0.12));
@@ -198,7 +202,7 @@
198
202
 
199
203
  .ogrid-data-cell--pinned-right {
200
204
  position: sticky;
201
- z-index: 6;
205
+ z-index: var(--ogrid-z-pinned, 6);
202
206
  background-color: var(--ogrid-bg);
203
207
  will-change: transform;
204
208
  border-left: 1px solid var(--ogrid-border, rgba(0, 0, 0, 0.12));
@@ -211,7 +215,7 @@
211
215
  display: flex;
212
216
  align-items: center;
213
217
  min-width: 0;
214
- padding: 6px 10px;
218
+ padding: var(--ogrid-cell-padding, 6px 10px);
215
219
  box-sizing: border-box;
216
220
  overflow: hidden;
217
221
  text-overflow: ellipsis;
@@ -237,7 +241,7 @@
237
241
  .ogrid-cell-content--active {
238
242
  outline: 2px solid var(--ogrid-selection, #217346);
239
243
  outline-offset: -1px;
240
- z-index: 2;
244
+ z-index: var(--ogrid-z-active-cell, 2);
241
245
  position: relative;
242
246
  overflow: visible;
243
247
  }
@@ -252,7 +256,7 @@
252
256
  box-sizing: border-box;
253
257
  outline: 2px solid var(--ogrid-selection-color, #217346);
254
258
  outline-offset: -1px;
255
- z-index: 2;
259
+ z-index: var(--ogrid-z-active-cell, 2);
256
260
  position: relative;
257
261
  background: var(--ogrid-bg, #fff);
258
262
  overflow: visible;
@@ -272,7 +276,7 @@
272
276
  border-radius: 1px;
273
277
  cursor: crosshair;
274
278
  pointer-events: auto;
275
- z-index: 3;
279
+ z-index: var(--ogrid-z-fill-handle, 3);
276
280
  }
277
281
 
278
282
  /* === Resize handle === */
@@ -296,7 +300,7 @@
296
300
  width: 3px;
297
301
  background: var(--ogrid-primary, #217346);
298
302
  pointer-events: none;
299
- z-index: 100;
303
+ z-index: var(--ogrid-z-drop-indicator, 100);
300
304
  }
301
305
 
302
306
  /* === Empty state === */
@@ -324,7 +328,7 @@
324
328
  .ogrid-loading-overlay {
325
329
  position: absolute;
326
330
  inset: 0;
327
- z-index: 2;
331
+ z-index: var(--ogrid-z-loading, 2);
328
332
  display: flex;
329
333
  align-items: center;
330
334
  justify-content: center;
@@ -6,6 +6,13 @@
6
6
 
7
7
  /* ─── Light Theme (default) ─── */
8
8
  :where(:root) {
9
+ /* Cell padding — override for row density:
10
+ --ogrid-cell-padding : shorthand (default 6px 10px)
11
+ --ogrid-cell-padding-vertical : vertical only (default 6px)
12
+ --ogrid-cell-padding-horizontal: horizontal only (default 10px) */
13
+ --ogrid-cell-padding: 6px 10px;
14
+ --ogrid-cell-padding-vertical: 6px;
15
+ --ogrid-cell-padding-horizontal: 10px;
9
16
  --ogrid-bg: #ffffff;
10
17
  --ogrid-fg: rgba(0, 0, 0, 0.87);
11
18
  --ogrid-fg-secondary: rgba(0, 0, 0, 0.6);
@@ -25,6 +25,8 @@ export interface UseFillHandleResult {
25
25
  startCol: number;
26
26
  } | null) => void;
27
27
  handleFillHandleMouseDown: (e: MouseEvent) => void;
28
+ /** Fill the current selection down from the top row (Ctrl+D). No-op if no selection or editable=false. */
29
+ fillDown: () => void;
28
30
  }
29
31
  /**
30
32
  * Manages Excel-style fill handle drag-to-fill for cell ranges.
@@ -37,6 +37,8 @@ export interface UseKeyboardNavigationParams<T> {
37
37
  rowSelection: Ref<RowSelectionMode>;
38
38
  wrapperRef: MaybeShallowRef<HTMLElement | null>;
39
39
  scrollToRow?: (index: number, align?: 'start' | 'center' | 'end') => void;
40
+ fillDown?: () => void;
41
+ onKeyDown?: Ref<((event: KeyboardEvent) => void) | undefined>;
40
42
  };
41
43
  }
42
44
  export interface UseKeyboardNavigationResult {
@@ -5,6 +5,11 @@ export interface UseVirtualScrollParams {
5
5
  rowHeight: number;
6
6
  enabled: Ref<boolean>;
7
7
  overscan?: number;
8
+ /**
9
+ * Minimum row count before virtual scrolling activates. Default: 100.
10
+ * When totalRows < threshold, all rows render without virtualization.
11
+ */
12
+ threshold?: number;
8
13
  }
9
14
  export interface UseVirtualScrollResult {
10
15
  containerRef: Ref<HTMLElement | null>;
@@ -171,4 +171,6 @@ export interface IOGridDataGridProps<T> {
171
171
  density?: 'compact' | 'normal' | 'comfortable';
172
172
  'aria-label'?: string;
173
173
  'aria-labelledby'?: string;
174
+ /** Custom keydown handler. Called before grid's built-in handling. Call event.preventDefault() to suppress grid default. */
175
+ onKeyDown?: (event: KeyboardEvent) => void;
174
176
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alaarab/ogrid-vue",
3
- "version": "2.1.10",
3
+ "version": "2.1.12",
4
4
  "description": "OGrid Vue – Vue 3 composables, headless components, and utilities for OGrid data grids.",
5
5
  "main": "dist/esm/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -36,7 +36,7 @@
36
36
  "node": ">=18"
37
37
  },
38
38
  "dependencies": {
39
- "@alaarab/ogrid-core": "2.1.10"
39
+ "@alaarab/ogrid-core": "2.1.12"
40
40
  },
41
41
  "peerDependencies": {
42
42
  "vue": "^3.3.0"