@alaarab/ogrid-vue 2.0.9 → 2.0.11
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/components/MarchingAntsOverlay.js +12 -34
- package/dist/esm/composables/useColumnHeaderMenuState.js +55 -2
- package/dist/esm/composables/useDataGridState.js +14 -0
- package/dist/esm/composables/useOGrid.js +15 -4
- package/dist/types/components/MarchingAntsOverlay.d.ts +46 -0
- package/dist/types/composables/useColumnHeaderMenuState.d.ts +18 -3
- package/dist/types/composables/useDataGridState.d.ts +8 -0
- package/dist/types/types/dataGridTypes.d.ts +4 -0
- package/package.json +2 -2
|
@@ -7,37 +7,7 @@
|
|
|
7
7
|
* Uses SVG rects positioned via cell data-attribute measurements.
|
|
8
8
|
*/
|
|
9
9
|
import { defineComponent, ref, computed, watch, onMounted, onUnmounted, h } from 'vue';
|
|
10
|
-
import { Z_INDEX } from '@alaarab/ogrid-core';
|
|
11
|
-
// Inject the @keyframes rule once into <head> (deduplicates across multiple OGrid instances)
|
|
12
|
-
function ensureKeyframes() {
|
|
13
|
-
if (typeof document === 'undefined')
|
|
14
|
-
return;
|
|
15
|
-
if (document.getElementById('ogrid-marching-ants-keyframes'))
|
|
16
|
-
return;
|
|
17
|
-
const style = document.createElement('style');
|
|
18
|
-
style.id = 'ogrid-marching-ants-keyframes';
|
|
19
|
-
style.textContent =
|
|
20
|
-
'@keyframes ogrid-marching-ants{to{stroke-dashoffset:-8}}';
|
|
21
|
-
document.head.appendChild(style);
|
|
22
|
-
}
|
|
23
|
-
/** Measure the bounding rect of a range within a container. */
|
|
24
|
-
function measureRange(container, range, colOffset) {
|
|
25
|
-
const startGlobalCol = range.startCol + colOffset;
|
|
26
|
-
const endGlobalCol = range.endCol + colOffset;
|
|
27
|
-
const topLeft = container.querySelector(`[data-row-index="${range.startRow}"][data-col-index="${startGlobalCol}"]`);
|
|
28
|
-
const bottomRight = container.querySelector(`[data-row-index="${range.endRow}"][data-col-index="${endGlobalCol}"]`);
|
|
29
|
-
if (!topLeft || !bottomRight)
|
|
30
|
-
return null;
|
|
31
|
-
const cRect = container.getBoundingClientRect();
|
|
32
|
-
const tlRect = topLeft.getBoundingClientRect();
|
|
33
|
-
const brRect = bottomRight.getBoundingClientRect();
|
|
34
|
-
return {
|
|
35
|
-
top: tlRect.top - cRect.top,
|
|
36
|
-
left: tlRect.left - cRect.left,
|
|
37
|
-
width: brRect.right - tlRect.left,
|
|
38
|
-
height: brRect.bottom - tlRect.top,
|
|
39
|
-
};
|
|
40
|
-
}
|
|
10
|
+
import { Z_INDEX, measureRange, injectGlobalStyles } from '@alaarab/ogrid-core';
|
|
41
11
|
export const MarchingAntsOverlay = defineComponent({
|
|
42
12
|
name: 'MarchingAntsOverlay',
|
|
43
13
|
props: {
|
|
@@ -51,6 +21,14 @@ export const MarchingAntsOverlay = defineComponent({
|
|
|
51
21
|
cutRange: { type: Object, default: null },
|
|
52
22
|
/** Column offset — 1 when checkbox column is present, else 0 */
|
|
53
23
|
colOffset: { type: Number, required: true },
|
|
24
|
+
/** Items array — triggers re-measurement when data changes (e.g., sorting) */
|
|
25
|
+
items: { type: Array, required: true },
|
|
26
|
+
/** Visible columns — triggers re-measurement when columns are hidden/shown */
|
|
27
|
+
visibleColumns: { type: Array, default: undefined },
|
|
28
|
+
/** Column sizing overrides — triggers re-measurement when columns are resized */
|
|
29
|
+
columnSizingOverrides: { type: Object, required: true },
|
|
30
|
+
/** Column order — triggers re-measurement when columns are reordered */
|
|
31
|
+
columnOrder: { type: Array, default: undefined },
|
|
54
32
|
},
|
|
55
33
|
setup(props) {
|
|
56
34
|
const selRect = ref(null);
|
|
@@ -70,10 +48,10 @@ export const MarchingAntsOverlay = defineComponent({
|
|
|
70
48
|
};
|
|
71
49
|
// Inject keyframes on mount
|
|
72
50
|
onMounted(() => {
|
|
73
|
-
|
|
51
|
+
injectGlobalStyles('ogrid-marching-ants-keyframes', '@keyframes ogrid-marching-ants{to{stroke-dashoffset:-8}}');
|
|
74
52
|
});
|
|
75
|
-
// Measure when any range changes; re-measure on resize
|
|
76
|
-
watch([() => props.selectionRange, clipRange, () => props.containerRef.value], () => {
|
|
53
|
+
// Measure when any range changes; re-measure on resize, column changes, data changes
|
|
54
|
+
watch([() => props.selectionRange, clipRange, () => props.containerRef.value, () => props.items, () => props.visibleColumns, () => props.columnSizingOverrides, () => props.columnOrder], () => {
|
|
77
55
|
if (!props.selectionRange && !clipRange.value) {
|
|
78
56
|
selRect.value = null;
|
|
79
57
|
clipRect.value = null;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { ref, computed } from 'vue';
|
|
2
2
|
/**
|
|
3
|
-
* Manages state for the column header menu (pin
|
|
3
|
+
* Manages state for the column header menu (pin/unpin, sort, autosize actions).
|
|
4
4
|
* Tracks which column's menu is open, anchor element, and action handlers.
|
|
5
5
|
*/
|
|
6
6
|
export function useColumnHeaderMenuState(params) {
|
|
7
|
-
const { pinnedColumns, onPinColumn, onUnpinColumn } = params;
|
|
7
|
+
const { columns, pinnedColumns, onPinColumn, onUnpinColumn, onSort, onAutosizeColumn, onAutosizeAllColumns, sortBy, sortDirection, } = params;
|
|
8
8
|
const isOpen = ref(false);
|
|
9
9
|
const openForColumn = ref(null);
|
|
10
10
|
const anchorElement = ref(null);
|
|
@@ -18,10 +18,25 @@ export function useColumnHeaderMenuState(params) {
|
|
|
18
18
|
openForColumn.value = null;
|
|
19
19
|
anchorElement.value = null;
|
|
20
20
|
};
|
|
21
|
+
const currentColumn = computed(() => openForColumn.value ? columns.value.find((c) => c.columnId === openForColumn.value) : undefined);
|
|
21
22
|
const currentPinState = computed(() => openForColumn.value ? pinnedColumns.value[openForColumn.value] : undefined);
|
|
22
23
|
const canPinLeft = computed(() => currentPinState.value !== 'left');
|
|
23
24
|
const canPinRight = computed(() => currentPinState.value !== 'right');
|
|
24
25
|
const canUnpin = computed(() => !!currentPinState.value);
|
|
26
|
+
const currentSort = computed(() => {
|
|
27
|
+
if (!openForColumn.value || !sortBy?.value || sortBy.value !== openForColumn.value) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
return sortDirection?.value ?? null;
|
|
31
|
+
});
|
|
32
|
+
const isSortable = computed(() => {
|
|
33
|
+
const col = currentColumn.value;
|
|
34
|
+
return col?.sortable !== false;
|
|
35
|
+
});
|
|
36
|
+
const isResizable = computed(() => {
|
|
37
|
+
// All columns are resizable by default (no per-column resizable flag in core)
|
|
38
|
+
return true;
|
|
39
|
+
});
|
|
25
40
|
const handlePinLeft = () => {
|
|
26
41
|
if (openForColumn.value && canPinLeft.value) {
|
|
27
42
|
onPinColumn(openForColumn.value, 'left');
|
|
@@ -40,6 +55,36 @@ export function useColumnHeaderMenuState(params) {
|
|
|
40
55
|
close();
|
|
41
56
|
}
|
|
42
57
|
};
|
|
58
|
+
const handleSortAsc = () => {
|
|
59
|
+
if (openForColumn.value && onSort) {
|
|
60
|
+
onSort(openForColumn.value, 'asc');
|
|
61
|
+
close();
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
const handleSortDesc = () => {
|
|
65
|
+
if (openForColumn.value && onSort) {
|
|
66
|
+
onSort(openForColumn.value, 'desc');
|
|
67
|
+
close();
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
const handleClearSort = () => {
|
|
71
|
+
if (openForColumn.value && onSort) {
|
|
72
|
+
onSort(openForColumn.value, null);
|
|
73
|
+
close();
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
const handleAutosizeThis = () => {
|
|
77
|
+
if (openForColumn.value && onAutosizeColumn) {
|
|
78
|
+
onAutosizeColumn(openForColumn.value);
|
|
79
|
+
close();
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
const handleAutosizeAll = () => {
|
|
83
|
+
if (onAutosizeAllColumns) {
|
|
84
|
+
onAutosizeAllColumns();
|
|
85
|
+
close();
|
|
86
|
+
}
|
|
87
|
+
};
|
|
43
88
|
return {
|
|
44
89
|
isOpen,
|
|
45
90
|
openForColumn,
|
|
@@ -49,8 +94,16 @@ export function useColumnHeaderMenuState(params) {
|
|
|
49
94
|
handlePinLeft,
|
|
50
95
|
handlePinRight,
|
|
51
96
|
handleUnpin,
|
|
97
|
+
handleSortAsc,
|
|
98
|
+
handleSortDesc,
|
|
99
|
+
handleClearSort,
|
|
100
|
+
handleAutosizeThis,
|
|
101
|
+
handleAutosizeAll,
|
|
52
102
|
canPinLeft,
|
|
53
103
|
canPinRight,
|
|
54
104
|
canUnpin,
|
|
105
|
+
currentSort,
|
|
106
|
+
isSortable,
|
|
107
|
+
isResizable,
|
|
55
108
|
};
|
|
56
109
|
}
|
|
@@ -176,9 +176,15 @@ export function useDataGridState(params) {
|
|
|
176
176
|
onColumnPinned: onColumnPinnedProp.value,
|
|
177
177
|
});
|
|
178
178
|
const headerMenuResult = useColumnHeaderMenuState({
|
|
179
|
+
columns: flatColumns,
|
|
179
180
|
pinnedColumns: pinningResult.pinnedColumns,
|
|
180
181
|
onPinColumn: pinningResult.pinColumn,
|
|
181
182
|
onUnpinColumn: pinningResult.unpinColumn,
|
|
183
|
+
onSort: props.value.onColumnSort,
|
|
184
|
+
onAutosizeColumn: props.value.onAutosizeColumn,
|
|
185
|
+
onAutosizeAllColumns: props.value.onAutosizeAllColumns,
|
|
186
|
+
sortBy: computed(() => props.value.sortBy),
|
|
187
|
+
sortDirection: computed(() => props.value.sortDirection),
|
|
182
188
|
});
|
|
183
189
|
const aggregation = computed(() => computeAggregations(items.value, visibleCols.value, cellSelection.value ? selectionRange.value : null));
|
|
184
190
|
const statusBarConfig = computed(() => {
|
|
@@ -335,9 +341,17 @@ export function useDataGridState(params) {
|
|
|
335
341
|
handlePinLeft: headerMenuResult.handlePinLeft,
|
|
336
342
|
handlePinRight: headerMenuResult.handlePinRight,
|
|
337
343
|
handleUnpin: headerMenuResult.handleUnpin,
|
|
344
|
+
handleSortAsc: headerMenuResult.handleSortAsc,
|
|
345
|
+
handleSortDesc: headerMenuResult.handleSortDesc,
|
|
346
|
+
handleClearSort: headerMenuResult.handleClearSort,
|
|
347
|
+
handleAutosizeThis: headerMenuResult.handleAutosizeThis,
|
|
348
|
+
handleAutosizeAll: headerMenuResult.handleAutosizeAll,
|
|
338
349
|
canPinLeft: headerMenuResult.canPinLeft.value,
|
|
339
350
|
canPinRight: headerMenuResult.canPinRight.value,
|
|
340
351
|
canUnpin: headerMenuResult.canUnpin.value,
|
|
352
|
+
currentSort: headerMenuResult.currentSort.value,
|
|
353
|
+
isSortable: headerMenuResult.isSortable.value,
|
|
354
|
+
isResizable: headerMenuResult.isResizable.value,
|
|
341
355
|
},
|
|
342
356
|
}));
|
|
343
357
|
return {
|
|
@@ -127,10 +127,21 @@ export function useOGrid(props) {
|
|
|
127
127
|
callbacks.value.onVisibleColumnsChange?.(cols);
|
|
128
128
|
};
|
|
129
129
|
const handleSort = (columnKey) => {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
130
|
+
const currentSort = sort.value;
|
|
131
|
+
if (currentSort.field === columnKey) {
|
|
132
|
+
// Cycle: asc → desc → clear
|
|
133
|
+
if (currentSort.direction === 'asc') {
|
|
134
|
+
setSort({ field: columnKey, direction: 'desc' });
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
// Clear sort (empty field means no column is sorted)
|
|
138
|
+
setSort({ field: '', direction: 'asc' });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
// Start new sort
|
|
143
|
+
setSort({ field: columnKey, direction: 'asc' });
|
|
144
|
+
}
|
|
134
145
|
};
|
|
135
146
|
const handleFilterChange = (key, value) => {
|
|
136
147
|
setFilters(mergeFilter(filters.value, key, value));
|
|
@@ -34,6 +34,28 @@ export declare const MarchingAntsOverlay: import("vue").DefineComponent<import("
|
|
|
34
34
|
type: NumberConstructor;
|
|
35
35
|
required: true;
|
|
36
36
|
};
|
|
37
|
+
/** Items array — triggers re-measurement when data changes (e.g., sorting) */
|
|
38
|
+
items: {
|
|
39
|
+
type: PropType<readonly unknown[]>;
|
|
40
|
+
required: true;
|
|
41
|
+
};
|
|
42
|
+
/** Visible columns — triggers re-measurement when columns are hidden/shown */
|
|
43
|
+
visibleColumns: {
|
|
44
|
+
type: PropType<readonly string[] | undefined>;
|
|
45
|
+
default: undefined;
|
|
46
|
+
};
|
|
47
|
+
/** Column sizing overrides — triggers re-measurement when columns are resized */
|
|
48
|
+
columnSizingOverrides: {
|
|
49
|
+
type: PropType<Record<string, {
|
|
50
|
+
widthPx: number;
|
|
51
|
+
}>>;
|
|
52
|
+
required: true;
|
|
53
|
+
};
|
|
54
|
+
/** Column order — triggers re-measurement when columns are reordered */
|
|
55
|
+
columnOrder: {
|
|
56
|
+
type: PropType<readonly string[] | undefined>;
|
|
57
|
+
default: undefined;
|
|
58
|
+
};
|
|
37
59
|
}>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
|
|
38
60
|
[key: string]: any;
|
|
39
61
|
}> | null, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
@@ -62,8 +84,32 @@ export declare const MarchingAntsOverlay: import("vue").DefineComponent<import("
|
|
|
62
84
|
type: NumberConstructor;
|
|
63
85
|
required: true;
|
|
64
86
|
};
|
|
87
|
+
/** Items array — triggers re-measurement when data changes (e.g., sorting) */
|
|
88
|
+
items: {
|
|
89
|
+
type: PropType<readonly unknown[]>;
|
|
90
|
+
required: true;
|
|
91
|
+
};
|
|
92
|
+
/** Visible columns — triggers re-measurement when columns are hidden/shown */
|
|
93
|
+
visibleColumns: {
|
|
94
|
+
type: PropType<readonly string[] | undefined>;
|
|
95
|
+
default: undefined;
|
|
96
|
+
};
|
|
97
|
+
/** Column sizing overrides — triggers re-measurement when columns are resized */
|
|
98
|
+
columnSizingOverrides: {
|
|
99
|
+
type: PropType<Record<string, {
|
|
100
|
+
widthPx: number;
|
|
101
|
+
}>>;
|
|
102
|
+
required: true;
|
|
103
|
+
};
|
|
104
|
+
/** Column order — triggers re-measurement when columns are reordered */
|
|
105
|
+
columnOrder: {
|
|
106
|
+
type: PropType<readonly string[] | undefined>;
|
|
107
|
+
default: undefined;
|
|
108
|
+
};
|
|
65
109
|
}>> & Readonly<{}>, {
|
|
66
110
|
selectionRange: ISelectionRange | null;
|
|
67
111
|
copyRange: ISelectionRange | null;
|
|
68
112
|
cutRange: ISelectionRange | null;
|
|
113
|
+
visibleColumns: readonly string[] | undefined;
|
|
114
|
+
columnOrder: readonly string[] | undefined;
|
|
69
115
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import { type Ref } from 'vue';
|
|
2
|
-
|
|
2
|
+
import type { IColumnDef } from '../types';
|
|
3
|
+
export interface UseColumnHeaderMenuStateParams<T = unknown> {
|
|
4
|
+
columns: Ref<IColumnDef<T>[]>;
|
|
3
5
|
pinnedColumns: Ref<Record<string, 'left' | 'right'>>;
|
|
4
6
|
onPinColumn: (columnId: string, side: 'left' | 'right') => void;
|
|
5
7
|
onUnpinColumn: (columnId: string) => void;
|
|
8
|
+
onSort?: (columnId: string, direction: 'asc' | 'desc' | null) => void;
|
|
9
|
+
onAutosizeColumn?: (columnId: string) => void;
|
|
10
|
+
onAutosizeAllColumns?: () => void;
|
|
11
|
+
sortBy?: Ref<string | undefined>;
|
|
12
|
+
sortDirection?: Ref<'asc' | 'desc' | undefined>;
|
|
6
13
|
}
|
|
7
14
|
export interface UseColumnHeaderMenuStateResult {
|
|
8
15
|
isOpen: Ref<boolean>;
|
|
@@ -13,12 +20,20 @@ export interface UseColumnHeaderMenuStateResult {
|
|
|
13
20
|
handlePinLeft: () => void;
|
|
14
21
|
handlePinRight: () => void;
|
|
15
22
|
handleUnpin: () => void;
|
|
23
|
+
handleSortAsc: () => void;
|
|
24
|
+
handleSortDesc: () => void;
|
|
25
|
+
handleClearSort: () => void;
|
|
26
|
+
handleAutosizeThis: () => void;
|
|
27
|
+
handleAutosizeAll: () => void;
|
|
16
28
|
canPinLeft: Ref<boolean>;
|
|
17
29
|
canPinRight: Ref<boolean>;
|
|
18
30
|
canUnpin: Ref<boolean>;
|
|
31
|
+
currentSort: Ref<'asc' | 'desc' | null>;
|
|
32
|
+
isSortable: Ref<boolean>;
|
|
33
|
+
isResizable: Ref<boolean>;
|
|
19
34
|
}
|
|
20
35
|
/**
|
|
21
|
-
* Manages state for the column header menu (pin
|
|
36
|
+
* Manages state for the column header menu (pin/unpin, sort, autosize actions).
|
|
22
37
|
* Tracks which column's menu is open, anchor element, and action handlers.
|
|
23
38
|
*/
|
|
24
|
-
export declare function useColumnHeaderMenuState(params: UseColumnHeaderMenuStateParams): UseColumnHeaderMenuStateResult;
|
|
39
|
+
export declare function useColumnHeaderMenuState<T = unknown>(params: UseColumnHeaderMenuStateParams<T>): UseColumnHeaderMenuStateResult;
|
|
@@ -136,9 +136,17 @@ export interface DataGridPinningState {
|
|
|
136
136
|
handlePinLeft: () => void;
|
|
137
137
|
handlePinRight: () => void;
|
|
138
138
|
handleUnpin: () => void;
|
|
139
|
+
handleSortAsc: () => void;
|
|
140
|
+
handleSortDesc: () => void;
|
|
141
|
+
handleClearSort: () => void;
|
|
142
|
+
handleAutosizeThis: () => void;
|
|
143
|
+
handleAutosizeAll: () => void;
|
|
139
144
|
canPinLeft: boolean;
|
|
140
145
|
canPinRight: boolean;
|
|
141
146
|
canUnpin: boolean;
|
|
147
|
+
currentSort: 'asc' | 'desc' | null;
|
|
148
|
+
isSortable: boolean;
|
|
149
|
+
isResizable: boolean;
|
|
142
150
|
};
|
|
143
151
|
}
|
|
144
152
|
export interface UseDataGridStateResult<T> {
|
|
@@ -109,6 +109,10 @@ export interface IOGridDataGridProps<T> {
|
|
|
109
109
|
onColumnOrderChange?: (order: string[]) => void;
|
|
110
110
|
/** Called when a column is resized by the user. */
|
|
111
111
|
onColumnResized?: (columnId: string, width: number) => void;
|
|
112
|
+
/** Called when user requests autosize for a single column. */
|
|
113
|
+
onAutosizeColumn?: (columnId: string) => void;
|
|
114
|
+
/** Called when user requests autosize for all columns. */
|
|
115
|
+
onAutosizeAllColumns?: () => void;
|
|
112
116
|
/** Called when a column is pinned or unpinned. */
|
|
113
117
|
onColumnPinned?: (columnId: string, pinned: 'left' | 'right' | null) => void;
|
|
114
118
|
/** Runtime pin overrides (from restored state or programmatic changes). */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alaarab/ogrid-vue",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.11",
|
|
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",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"node": ">=18"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@alaarab/ogrid-core": "2.0.
|
|
38
|
+
"@alaarab/ogrid-core": "2.0.11"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
41
|
"vue": "^3.3.0"
|