@alaarab/ogrid-vue 2.0.23 → 2.1.1

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 (33) hide show
  1. package/dist/esm/components/createDataGridTable.js +5 -1
  2. package/dist/esm/components/createOGrid.js +10 -7
  3. package/dist/esm/composables/useCellSelection.js +101 -61
  4. package/dist/esm/composables/useClipboard.js +15 -55
  5. package/dist/esm/composables/useColumnChooserState.js +4 -7
  6. package/dist/esm/composables/useColumnHeaderFilterState.js +10 -7
  7. package/dist/esm/composables/useColumnHeaderMenuState.js +2 -4
  8. package/dist/esm/composables/useColumnPinning.js +2 -2
  9. package/dist/esm/composables/useColumnReorder.js +8 -1
  10. package/dist/esm/composables/useDataGridState.js +33 -30
  11. package/dist/esm/composables/useDateFilterState.js +1 -1
  12. package/dist/esm/composables/useFillHandle.js +67 -50
  13. package/dist/esm/composables/useKeyboardNavigation.js +25 -109
  14. package/dist/esm/composables/useLatestRef.js +2 -2
  15. package/dist/esm/composables/useMultiSelectFilterState.js +1 -1
  16. package/dist/esm/composables/useOGrid.js +29 -11
  17. package/dist/esm/composables/usePeopleFilterState.js +2 -2
  18. package/dist/esm/composables/useRowSelection.js +13 -16
  19. package/dist/esm/composables/useTableLayout.js +11 -11
  20. package/dist/esm/composables/useTextFilterState.js +1 -1
  21. package/dist/esm/composables/useVirtualScroll.js +20 -17
  22. package/dist/types/composables/index.d.ts +1 -0
  23. package/dist/types/composables/useCellSelection.d.ts +1 -1
  24. package/dist/types/composables/useClipboard.d.ts +1 -1
  25. package/dist/types/composables/useDateFilterState.d.ts +2 -2
  26. package/dist/types/composables/useFillHandle.d.ts +1 -1
  27. package/dist/types/composables/useKeyboardNavigation.d.ts +4 -6
  28. package/dist/types/composables/useLatestRef.d.ts +3 -1
  29. package/dist/types/composables/useMultiSelectFilterState.d.ts +1 -1
  30. package/dist/types/composables/usePeopleFilterState.d.ts +1 -1
  31. package/dist/types/composables/useTextFilterState.d.ts +2 -2
  32. package/dist/types/index.d.ts +1 -1
  33. package/package.json +10 -3
@@ -11,7 +11,7 @@ export function usePeopleFilterState(params) {
11
11
  peopleSearchText.value = v;
12
12
  };
13
13
  // Sync temp state when popover opens
14
- watch(() => params.isFilterOpen(), (open) => {
14
+ watch(params.isFilterOpen, (open) => {
15
15
  if (open) {
16
16
  peopleSearchText.value = '';
17
17
  peopleSuggestions.value = [];
@@ -21,7 +21,7 @@ export function usePeopleFilterState(params) {
21
21
  }
22
22
  });
23
23
  // People search with debounce
24
- watch([peopleSearchText, () => params.peopleSearch, () => params.isFilterOpen()], ([searchText, search, isOpen]) => {
24
+ watch([peopleSearchText, () => params.peopleSearch, params.isFilterOpen], ([searchText, search, isOpen]) => {
25
25
  if (peopleSearchTimeout)
26
26
  clearTimeout(peopleSearchTimeout);
27
27
  if (!search || !isOpen || filterType !== 'people')
@@ -1,4 +1,5 @@
1
- import { shallowRef, computed } from 'vue';
1
+ import { shallowRef, computed, isReadonly } from 'vue';
2
+ import { applyRangeRowSelection, computeRowSelectionState } from '@alaarab/ogrid-core';
2
3
  /**
3
4
  * Manages row selection state for single or multiple selection modes with shift-click range support.
4
5
  */
@@ -17,7 +18,12 @@ export function useRowSelection(params) {
17
18
  });
18
19
  const updateSelection = (newSelectedIds) => {
19
20
  if (controlledSelectedRows.value !== undefined) {
20
- controlledSelectedRows.value = newSelectedIds;
21
+ // In controlled mode: only fire the callback. The parent is responsible for
22
+ // updating its selectedRows prop in response. Only write if the ref is writable
23
+ // (plain ref in tests), not if it's a readonly computed ref from useDataGridState.
24
+ if (!isReadonly(controlledSelectedRows)) {
25
+ controlledSelectedRows.value = newSelectedIds;
26
+ }
21
27
  }
22
28
  else {
23
29
  internalSelectedRows.value = newSelectedIds;
@@ -33,22 +39,13 @@ export function useRowSelection(params) {
33
39
  lastClickedRow = rowIndex;
34
40
  return;
35
41
  }
36
- const next = new Set(selectedRowIds.value);
37
42
  const currentItems = items.value;
43
+ let next;
38
44
  if (shiftKey && lastClickedRow >= 0 && lastClickedRow !== rowIndex) {
39
- const start = Math.min(lastClickedRow, rowIndex);
40
- const end = Math.max(lastClickedRow, rowIndex);
41
- for (let i = start; i <= end; i++) {
42
- if (i < currentItems.length) {
43
- const id = getRowId(currentItems[i]);
44
- if (checked)
45
- next.add(id);
46
- else
47
- next.delete(id);
48
- }
49
- }
45
+ next = applyRangeRowSelection(lastClickedRow, rowIndex, checked, currentItems, getRowId, selectedRowIds.value);
50
46
  }
51
47
  else {
48
+ next = new Set(selectedRowIds.value);
52
49
  if (checked)
53
50
  next.add(rowId);
54
51
  else
@@ -65,8 +62,8 @@ export function useRowSelection(params) {
65
62
  updateSelection(new Set());
66
63
  }
67
64
  };
68
- const allSelected = computed(() => items.value.length > 0 && items.value.every((item) => selectedRowIds.value.has(getRowId(item))));
69
- const someSelected = computed(() => !allSelected.value && items.value.some((item) => selectedRowIds.value.has(getRowId(item))));
65
+ const allSelected = computed(() => computeRowSelectionState(selectedRowIds.value, items.value, getRowId).allSelected);
66
+ const someSelected = computed(() => computeRowSelectionState(selectedRowIds.value, items.value, getRowId).someSelected);
70
67
  return {
71
68
  selectedRowIds,
72
69
  updateSelection,
@@ -22,8 +22,10 @@ export function useTableLayout(params) {
22
22
  onMounted(() => {
23
23
  const el = wrapperRef.value;
24
24
  if (el) {
25
- resizeObserver = new ResizeObserver(measure);
26
- resizeObserver.observe(el);
25
+ if (typeof ResizeObserver !== 'undefined') {
26
+ resizeObserver = new ResizeObserver(measure);
27
+ resizeObserver.observe(el);
28
+ }
27
29
  measure();
28
30
  }
29
31
  });
@@ -52,16 +54,14 @@ export function useTableLayout(params) {
52
54
  watch(flatColumns, (cols) => {
53
55
  const colIds = new Set(cols.map((c) => c.columnId));
54
56
  const prev = columnSizingOverrides.value;
55
- const next = { ...prev };
56
- let changed = false;
57
- for (const id of Object.keys(next)) {
58
- if (!colIds.has(id)) {
59
- delete next[id];
60
- changed = true;
61
- }
62
- }
63
- if (changed)
57
+ const keys = Object.keys(prev);
58
+ const kept = keys.filter((id) => colIds.has(id));
59
+ if (kept.length < keys.length) {
60
+ const next = {};
61
+ for (const id of kept)
62
+ next[id] = prev[id];
64
63
  columnSizingOverrides.value = next;
64
+ }
65
65
  });
66
66
  // Desired table width calculation
67
67
  const desiredTableWidth = computed(() => {
@@ -3,7 +3,7 @@ export function useTextFilterState(params) {
3
3
  const { textValue = '', onTextChange } = params;
4
4
  const tempTextValue = ref(textValue);
5
5
  // Sync temp state when popover opens
6
- watch(() => params.isFilterOpen(), (open) => {
6
+ watch(params.isFilterOpen, (open) => {
7
7
  if (open) {
8
8
  tempTextValue.value = params.textValue ?? '';
9
9
  }
@@ -1,4 +1,4 @@
1
- import { ref, computed, watch, onMounted, onUnmounted } from 'vue';
1
+ import { ref, computed, watch, onUnmounted } from 'vue';
2
2
  import { computeVisibleRange, computeTotalHeight, getScrollTopForRow, } from '@alaarab/ogrid-core';
3
3
  /**
4
4
  * Manages virtual scrolling with RAF-throttled scroll handling and ResizeObserver
@@ -11,6 +11,7 @@ export function useVirtualScroll(params) {
11
11
  const containerHeight = ref(0);
12
12
  let rafId = 0;
13
13
  let resizeObserver;
14
+ let prevObservedEl = null;
14
15
  const visibleRange = computed(() => {
15
16
  if (!enabled.value) {
16
17
  return { startIndex: 0, endIndex: totalRows.value - 1, offsetTop: 0, offsetBottom: 0 };
@@ -39,26 +40,28 @@ export function useVirtualScroll(params) {
39
40
  return;
40
41
  containerHeight.value = el.clientHeight;
41
42
  };
42
- // Watch containerRef to attach/detach scroll listener and ResizeObserver
43
- watch(containerRef, (el, prevEl) => {
44
- if (prevEl) {
45
- prevEl.removeEventListener('scroll', onScroll);
46
- resizeObserver?.disconnect();
43
+ // Watch containerRef to attach/detach scroll listener and ResizeObserver.
44
+ // Track prevObservedEl to avoid re-observing the same element and to
45
+ // properly disconnect old observers before creating new ones.
46
+ watch(containerRef, (el) => {
47
+ if (el === prevObservedEl)
48
+ return; // skip if same element
49
+ // Teardown previous element
50
+ if (prevObservedEl) {
51
+ prevObservedEl.removeEventListener('scroll', onScroll);
47
52
  }
48
- if (el) {
49
- el.addEventListener('scroll', onScroll, { passive: true });
50
- resizeObserver = new ResizeObserver(measure);
51
- resizeObserver.observe(el);
52
- measure();
53
- scrollTop.value = el.scrollTop;
53
+ if (resizeObserver) {
54
+ resizeObserver.disconnect();
55
+ resizeObserver = undefined;
54
56
  }
55
- });
56
- onMounted(() => {
57
- const el = containerRef.value;
57
+ prevObservedEl = el;
58
+ // Setup new element
58
59
  if (el) {
59
60
  el.addEventListener('scroll', onScroll, { passive: true });
60
- resizeObserver = new ResizeObserver(measure);
61
- resizeObserver.observe(el);
61
+ if (typeof ResizeObserver !== 'undefined') {
62
+ resizeObserver = new ResizeObserver(measure);
63
+ resizeObserver.observe(el);
64
+ }
62
65
  measure();
63
66
  scrollTop.value = el.scrollTop;
64
67
  }
@@ -27,6 +27,7 @@ export type { UseFilterOptionsResult } from './useFilterOptions';
27
27
  export { useDebounce, useDebouncedCallback } from './useDebounce';
28
28
  export type { DebouncedFn } from './useDebounce';
29
29
  export { useLatestRef } from './useLatestRef';
30
+ export type { MaybeShallowRef } from './useLatestRef';
30
31
  export { useTableLayout } from './useTableLayout';
31
32
  export type { UseTableLayoutParams, UseTableLayoutResult } from './useTableLayout';
32
33
  export { useColumnHeaderFilterState } from './useColumnHeaderFilterState';
@@ -1,7 +1,7 @@
1
1
  import { type Ref, type ShallowRef } from 'vue';
2
2
  import type { ISelectionRange, IActiveCell } from '../types';
3
3
  export interface UseCellSelectionParams {
4
- colOffset: number;
4
+ colOffset: Ref<number> | number;
5
5
  rowCount: Ref<number>;
6
6
  visibleColCount: Ref<number>;
7
7
  setActiveCell: (cell: IActiveCell | null) => void;
@@ -3,7 +3,7 @@ import type { ISelectionRange, IActiveCell, ICellValueChangedEvent, IColumnDef }
3
3
  export interface UseClipboardParams<T> {
4
4
  items: Ref<T[]>;
5
5
  visibleCols: Ref<IColumnDef<T>[]>;
6
- colOffset: number;
6
+ colOffset: Ref<number> | number;
7
7
  selectionRange: Ref<ISelectionRange | null> | ShallowRef<ISelectionRange | null>;
8
8
  activeCell: Ref<IActiveCell | null> | ShallowRef<IActiveCell | null>;
9
9
  editable: Ref<boolean | undefined>;
@@ -1,9 +1,9 @@
1
- import { ref } from 'vue';
1
+ import { ref, type Ref } from 'vue';
2
2
  import type { IDateFilterValue } from '../types';
3
3
  export interface UseDateFilterStateParams {
4
4
  dateValue?: IDateFilterValue;
5
5
  onDateChange?: (value: IDateFilterValue | undefined) => void;
6
- isFilterOpen: () => boolean;
6
+ isFilterOpen: Ref<boolean>;
7
7
  }
8
8
  export interface UseDateFilterStateResult {
9
9
  tempDateFrom: ReturnType<typeof ref<string>>;
@@ -9,7 +9,7 @@ export interface UseFillHandleParams<T> {
9
9
  selectionRange: Ref<ISelectionRange | null> | ShallowRef<ISelectionRange | null>;
10
10
  setSelectionRange: (range: ISelectionRange | null) => void;
11
11
  setActiveCell: (cell: IActiveCell | null) => void;
12
- colOffset: number;
12
+ colOffset: Ref<number> | number;
13
13
  wrapperRef: Ref<HTMLElement | null> | ShallowRef<HTMLElement | null>;
14
14
  beginBatch?: () => void;
15
15
  endBatch?: () => void;
@@ -1,14 +1,13 @@
1
- import { type Ref, type ShallowRef } from 'vue';
1
+ import { type Ref } from 'vue';
2
2
  import type { RowId, IActiveCell, ISelectionRange, IColumnDef, ICellValueChangedEvent, RowSelectionMode } from '../types';
3
3
  import type { EditingCell } from './useCellEditing';
4
4
  import type { ContextMenuPosition } from './useContextMenu';
5
- /** Accept either Ref or ShallowRef for state fields */
6
- type MaybeShallowRef<T> = Ref<T> | ShallowRef<T>;
5
+ import { type MaybeShallowRef } from './useLatestRef';
7
6
  export interface UseKeyboardNavigationParams<T> {
8
7
  data: {
9
8
  items: Ref<T[]>;
10
9
  visibleCols: Ref<IColumnDef<T>[]>;
11
- colOffset: number;
10
+ colOffset: Ref<number> | number;
12
11
  hasCheckboxCol: Ref<boolean>;
13
12
  visibleColumnCount: Ref<number>;
14
13
  getRowId: (item: T) => RowId;
@@ -36,7 +35,7 @@ export interface UseKeyboardNavigationParams<T> {
36
35
  editable: Ref<boolean | undefined>;
37
36
  onCellValueChanged: Ref<((event: ICellValueChangedEvent<T>) => void) | undefined>;
38
37
  rowSelection: Ref<RowSelectionMode>;
39
- wrapperRef: Ref<HTMLElement | null> | ShallowRef<HTMLElement | null>;
38
+ wrapperRef: MaybeShallowRef<HTMLElement | null>;
40
39
  scrollToRow?: (index: number, align?: 'start' | 'center' | 'end') => void;
41
40
  };
42
41
  }
@@ -47,4 +46,3 @@ export interface UseKeyboardNavigationResult {
47
46
  * Handles all keyboard navigation, shortcuts, and cell editing triggers for the grid.
48
47
  */
49
48
  export declare function useKeyboardNavigation<T>(params: UseKeyboardNavigationParams<T>): UseKeyboardNavigationResult;
50
- export {};
@@ -1,4 +1,6 @@
1
- import { type Ref } from 'vue';
1
+ import { type Ref, type ShallowRef } from 'vue';
2
+ /** Accept either Ref or ShallowRef for state fields */
3
+ export type MaybeShallowRef<T> = Ref<T> | ShallowRef<T>;
2
4
  /**
3
5
  * Returns a ref that always holds the latest value.
4
6
  * Useful for capturing volatile state in stable callbacks
@@ -3,7 +3,7 @@ export interface UseMultiSelectFilterStateParams {
3
3
  selectedValues?: string[];
4
4
  onFilterChange?: (values: string[]) => void;
5
5
  options?: string[];
6
- isFilterOpen: () => boolean;
6
+ isFilterOpen: Ref<boolean>;
7
7
  }
8
8
  export interface UseMultiSelectFilterStateResult {
9
9
  tempSelected: Ref<Set<string>>;
@@ -5,7 +5,7 @@ export interface UsePeopleFilterStateParams {
5
5
  selectedUser?: UserLike;
6
6
  onUserChange?: (user: UserLike | undefined) => void;
7
7
  peopleSearch?: (query: string) => Promise<UserLike[]>;
8
- isFilterOpen: () => boolean;
8
+ isFilterOpen: Ref<boolean>;
9
9
  filterType: ColumnFilterType;
10
10
  }
11
11
  export interface UsePeopleFilterStateResult {
@@ -1,8 +1,8 @@
1
- import { ref } from 'vue';
1
+ import { ref, type Ref } from 'vue';
2
2
  export interface UseTextFilterStateParams {
3
3
  textValue?: string;
4
4
  onTextChange?: (value: string) => void;
5
- isFilterOpen: () => boolean;
5
+ isFilterOpen: Ref<boolean>;
6
6
  }
7
7
  export interface UseTextFilterStateResult {
8
8
  tempTextValue: ReturnType<typeof ref<string>>;
@@ -5,7 +5,7 @@ export { toUserLike, isInSelectionRange, normalizeSelectionRange } from './types
5
5
  export { MarchingAntsOverlay } from './components/MarchingAntsOverlay';
6
6
  export { StatusBar, type StatusBarProps } from './components/StatusBar';
7
7
  export { useOGrid, useDataGridState, useActiveCell, useCellEditing, useCellSelection, useClipboard, useRowSelection, useKeyboardNavigation, useFillHandle, useUndoRedo, useContextMenu, useColumnResize, useColumnReorder, useVirtualScroll, useFilterOptions, useDebounce, useDebouncedCallback, useTableLayout, useColumnHeaderFilterState, useTextFilterState, useMultiSelectFilterState, usePeopleFilterState, useDateFilterState, useColumnChooserState, useInlineCellEditorState, useRichSelectState, useSideBarState, useColumnPinning, useColumnHeaderMenuState, useDataGridTableSetup, } from './composables';
8
- export type { UseOGridResult, UseOGridPagination, UseOGridColumnChooser, UseOGridLayout, UseOGridFilters, ColumnChooserPlacement, UseDataGridStateParams, UseDataGridStateResult, DataGridLayoutState, DataGridRowSelectionState, DataGridEditingState, DataGridCellInteractionState, DataGridContextMenuState, DataGridViewModelState, DataGridPinningState, UseActiveCellResult, EditingCell, UseCellEditingParams, UseCellEditingResult, UseCellSelectionParams, UseCellSelectionResult, UseClipboardParams, UseClipboardResult, UseRowSelectionParams, UseRowSelectionResult, UseKeyboardNavigationParams, UseKeyboardNavigationResult, UseFillHandleParams, UseFillHandleResult, UseUndoRedoParams, UseUndoRedoResult, ContextMenuPosition, UseContextMenuResult, UseColumnResizeParams, UseColumnResizeResult, UseColumnReorderParams, UseColumnReorderResult, UseVirtualScrollParams, UseVirtualScrollResult, UseFilterOptionsResult, UseTableLayoutParams, UseTableLayoutResult, UseColumnHeaderFilterStateParams, UseColumnHeaderFilterStateResult, UseTextFilterStateParams, UseTextFilterStateResult, UseMultiSelectFilterStateParams, UseMultiSelectFilterStateResult, UsePeopleFilterStateParams, UsePeopleFilterStateResult, UseDateFilterStateParams, UseDateFilterStateResult, UseColumnChooserStateParams, UseColumnChooserStateResult, InlineCellEditorType, UseInlineCellEditorStateParams, UseInlineCellEditorStateResult, UseRichSelectStateParams, UseRichSelectStateResult, UseSideBarStateParams, UseSideBarStateResult, DebouncedFn, UseColumnPinningParams, UseColumnPinningResult, UseColumnHeaderMenuStateParams, UseColumnHeaderMenuStateResult, UseDataGridTableSetupParams, UseDataGridTableSetupResult, } from './composables';
8
+ export type { UseOGridResult, UseOGridPagination, UseOGridColumnChooser, UseOGridLayout, UseOGridFilters, ColumnChooserPlacement, UseDataGridStateParams, UseDataGridStateResult, DataGridLayoutState, DataGridRowSelectionState, DataGridEditingState, DataGridCellInteractionState, DataGridContextMenuState, DataGridViewModelState, DataGridPinningState, UseActiveCellResult, EditingCell, UseCellEditingParams, UseCellEditingResult, UseCellSelectionParams, UseCellSelectionResult, UseClipboardParams, UseClipboardResult, UseRowSelectionParams, UseRowSelectionResult, UseKeyboardNavigationParams, UseKeyboardNavigationResult, UseFillHandleParams, UseFillHandleResult, UseUndoRedoParams, UseUndoRedoResult, ContextMenuPosition, UseContextMenuResult, UseColumnResizeParams, UseColumnResizeResult, UseColumnReorderParams, UseColumnReorderResult, UseVirtualScrollParams, UseVirtualScrollResult, UseFilterOptionsResult, UseTableLayoutParams, UseTableLayoutResult, UseColumnHeaderFilterStateParams, UseColumnHeaderFilterStateResult, UseTextFilterStateParams, UseTextFilterStateResult, UseMultiSelectFilterStateParams, UseMultiSelectFilterStateResult, UsePeopleFilterStateParams, UsePeopleFilterStateResult, UseDateFilterStateParams, UseDateFilterStateResult, UseColumnChooserStateParams, UseColumnChooserStateResult, InlineCellEditorType, UseInlineCellEditorStateParams, UseInlineCellEditorStateResult, UseRichSelectStateParams, UseRichSelectStateResult, UseSideBarStateParams, UseSideBarStateResult, DebouncedFn, UseColumnPinningParams, UseColumnPinningResult, UseColumnHeaderMenuStateParams, UseColumnHeaderMenuStateResult, UseDataGridTableSetupParams, UseDataGridTableSetupResult, MaybeShallowRef, } from './composables';
9
9
  export { getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, getCellInteractionProps, } from './utils';
10
10
  export type { HeaderFilterConfigInput, HeaderFilterConfig, CellRenderDescriptorInput, CellRenderDescriptor, CellRenderMode, CellInteractionHandlers, CellInteractionProps, } from './utils';
11
11
  export { createDataGridTable, type IDataGridTableUIBindings } from './components/createDataGridTable';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alaarab/ogrid-vue",
3
- "version": "2.0.23",
3
+ "version": "2.1.1",
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.0.23"
39
+ "@alaarab/ogrid-core": "2.1.1"
40
40
  },
41
41
  "peerDependencies": {
42
42
  "vue": "^3.3.0"
@@ -53,5 +53,12 @@
53
53
  ],
54
54
  "publishConfig": {
55
55
  "access": "public"
56
- }
56
+ },
57
+ "repository": {
58
+ "type": "git",
59
+ "url": "https://github.com/alaarab/ogrid.git",
60
+ "directory": "packages/vue"
61
+ },
62
+ "homepage": "https://ogrid.dev",
63
+ "bugs": "https://github.com/alaarab/ogrid/issues"
57
64
  }