@alaarab/ogrid-vue-vuetify 2.0.5 → 2.0.7
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/ColumnHeaderFilter/ColumnHeaderFilter.js +0 -1
- package/dist/esm/ColumnHeaderFilter/MultiSelectFilterPopover.js +0 -2
- package/dist/esm/ColumnHeaderFilter/PeopleFilterPopover.js +0 -1
- package/dist/esm/ColumnHeaderFilter/TextFilterPopover.js +0 -2
- package/dist/esm/ColumnHeaderMenu/ColumnHeaderMenu.js +55 -0
- package/dist/esm/DataGridTable/DataGridTable.js +45 -42
- package/dist/esm/OGrid/OGrid.js +239 -6
- package/dist/esm/index.js +1 -0
- package/dist/types/ColumnHeaderMenu/ColumnHeaderMenu.d.ts +91 -0
- package/dist/types/OGrid/OGrid.d.ts +2 -2
- package/dist/types/index.d.ts +2 -0
- package/package.json +6 -5
|
@@ -5,7 +5,6 @@ import { TextFilterPopover } from './TextFilterPopover';
|
|
|
5
5
|
import { MultiSelectFilterPopover } from './MultiSelectFilterPopover';
|
|
6
6
|
import { PeopleFilterPopover } from './PeopleFilterPopover';
|
|
7
7
|
// Vuetify component types don't align with h() overloads; cast to Component
|
|
8
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
9
8
|
const _VBtn = VBtn;
|
|
10
9
|
const _VIcon = VIcon;
|
|
11
10
|
const _VMenu = VMenu;
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { defineComponent, h } from 'vue';
|
|
2
2
|
import { VBtn, VTextField, VCheckbox, VProgressCircular, VDivider } from 'vuetify/components';
|
|
3
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
4
3
|
const _VBtn = VBtn;
|
|
5
4
|
const _VTextField = VTextField;
|
|
6
5
|
const _VCheckbox = VCheckbox;
|
|
7
6
|
const _VProgressCircular = VProgressCircular;
|
|
8
7
|
const _VDivider = VDivider;
|
|
9
|
-
/* eslint-enable @typescript-eslint/no-explicit-any */
|
|
10
8
|
export const MultiSelectFilterPopover = defineComponent({
|
|
11
9
|
name: 'MultiSelectFilterPopover',
|
|
12
10
|
props: {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { defineComponent, h } from 'vue';
|
|
2
2
|
import { VBtn, VTextField, VProgressCircular, VAvatar, VIcon, VDivider } from 'vuetify/components';
|
|
3
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
4
3
|
const _VBtn = VBtn;
|
|
5
4
|
const _VTextField = VTextField;
|
|
6
5
|
const _VProgressCircular = VProgressCircular;
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { defineComponent, h } from 'vue';
|
|
2
2
|
import { VBtn, VTextField } from 'vuetify/components';
|
|
3
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
4
3
|
const _VBtn = VBtn;
|
|
5
4
|
const _VTextField = VTextField;
|
|
6
|
-
/* eslint-enable @typescript-eslint/no-explicit-any */
|
|
7
5
|
export const TextFilterPopover = defineComponent({
|
|
8
6
|
name: 'TextFilterPopover',
|
|
9
7
|
props: {
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { defineComponent, h } from 'vue';
|
|
2
|
+
import { VMenu, VList, VListItem } from 'vuetify/components';
|
|
3
|
+
import { COLUMN_HEADER_MENU_ITEMS } from '@alaarab/ogrid-vue';
|
|
4
|
+
export const ColumnHeaderMenu = defineComponent({
|
|
5
|
+
name: 'ColumnHeaderMenu',
|
|
6
|
+
props: {
|
|
7
|
+
isOpen: { type: Boolean, required: true },
|
|
8
|
+
anchorElement: { type: Object, default: null },
|
|
9
|
+
onClose: { type: Function, required: true },
|
|
10
|
+
onPinLeft: { type: Function, required: true },
|
|
11
|
+
onPinRight: { type: Function, required: true },
|
|
12
|
+
onUnpin: { type: Function, required: true },
|
|
13
|
+
canPinLeft: { type: Boolean, required: true },
|
|
14
|
+
canPinRight: { type: Boolean, required: true },
|
|
15
|
+
canUnpin: { type: Boolean, required: true },
|
|
16
|
+
},
|
|
17
|
+
setup(props) {
|
|
18
|
+
const handleOpenChange = (open) => {
|
|
19
|
+
if (!open) {
|
|
20
|
+
props.onClose();
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
const items = COLUMN_HEADER_MENU_ITEMS;
|
|
24
|
+
const getDisabled = (index) => {
|
|
25
|
+
if (index === 0)
|
|
26
|
+
return !props.canPinLeft;
|
|
27
|
+
if (index === 1)
|
|
28
|
+
return !props.canPinRight;
|
|
29
|
+
if (index === 2)
|
|
30
|
+
return !props.canUnpin;
|
|
31
|
+
return false;
|
|
32
|
+
};
|
|
33
|
+
const getHandler = (index) => {
|
|
34
|
+
if (index === 0)
|
|
35
|
+
return props.onPinLeft;
|
|
36
|
+
if (index === 1)
|
|
37
|
+
return props.onPinRight;
|
|
38
|
+
if (index === 2)
|
|
39
|
+
return props.onUnpin;
|
|
40
|
+
return () => { };
|
|
41
|
+
};
|
|
42
|
+
return () => h(VMenu, {
|
|
43
|
+
modelValue: props.isOpen,
|
|
44
|
+
'onUpdate:modelValue': handleOpenChange,
|
|
45
|
+
activator: props.anchorElement || undefined,
|
|
46
|
+
location: 'bottom start',
|
|
47
|
+
}, {
|
|
48
|
+
default: () => h(VList, { density: 'compact', 'aria-label': 'Column options' }, () => items.map((item, index) => h(VListItem, {
|
|
49
|
+
key: item.id,
|
|
50
|
+
disabled: getDisabled(index),
|
|
51
|
+
onClick: () => { getHandler(index)(); },
|
|
52
|
+
}, () => item.label))),
|
|
53
|
+
});
|
|
54
|
+
},
|
|
55
|
+
});
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { defineComponent,
|
|
1
|
+
import { defineComponent, computed, h, Teleport } from 'vue';
|
|
2
2
|
import { VCheckbox, VProgressCircular, VBtn } from 'vuetify/components';
|
|
3
|
-
import {
|
|
3
|
+
import { useDataGridTableSetup, getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, getCellInteractionProps, buildHeaderRows, CHECKBOX_COLUMN_WIDTH, ROW_NUMBER_COLUMN_WIDTH, DEFAULT_MIN_COLUMN_WIDTH, } from '@alaarab/ogrid-vue';
|
|
4
4
|
import { ColumnHeaderFilter } from '../ColumnHeaderFilter';
|
|
5
|
+
import { ColumnHeaderMenu } from '../ColumnHeaderMenu/ColumnHeaderMenu';
|
|
5
6
|
import { InlineCellEditor } from './InlineCellEditor';
|
|
6
7
|
import { StatusBar } from './StatusBar';
|
|
7
8
|
import { GridContextMenu } from './GridContextMenu';
|
|
@@ -13,36 +14,8 @@ export const DataGridTable = defineComponent({
|
|
|
13
14
|
gridProps: { type: Object, required: true },
|
|
14
15
|
},
|
|
15
16
|
setup(props) {
|
|
16
|
-
const wrapperRef = ref(null);
|
|
17
|
-
const tableContainerRef = ref(null);
|
|
18
|
-
const lastMouseShift = ref(false);
|
|
19
17
|
const propsRef = computed(() => props.gridProps);
|
|
20
|
-
const state =
|
|
21
|
-
// Column reorder — setup scope for lifecycle hooks
|
|
22
|
-
const columnOrderRef = computed(() => {
|
|
23
|
-
const p = props.gridProps;
|
|
24
|
-
if (p.columnOrder)
|
|
25
|
-
return p.columnOrder;
|
|
26
|
-
return flattenColumns(p.columns).filter(c => p.visibleColumns?.has(c.columnId) ?? true).map(c => c.columnId);
|
|
27
|
-
});
|
|
28
|
-
const onColumnOrderChangeRef = computed(() => props.gridProps.onColumnOrderChange);
|
|
29
|
-
const tableRef = ref(null);
|
|
30
|
-
const { isDragging: isReorderDragging, dropIndicatorX, handleHeaderMouseDown: handleReorderMouseDown } = useColumnReorder({
|
|
31
|
-
columnOrder: columnOrderRef,
|
|
32
|
-
onColumnOrderChange: onColumnOrderChangeRef,
|
|
33
|
-
tableRef,
|
|
34
|
-
});
|
|
35
|
-
// Virtual scrolling — setup scope for lifecycle hooks
|
|
36
|
-
const virtualScrollEnabled = computed(() => props.gridProps.virtualScroll?.enabled ?? false);
|
|
37
|
-
const totalRowsRef = computed(() => props.gridProps.items.length);
|
|
38
|
-
const rowHeight = props.gridProps.virtualScroll?.rowHeight ?? 36;
|
|
39
|
-
const overscan = props.gridProps.virtualScroll?.overscan ?? 5;
|
|
40
|
-
const { containerRef: vsContainerRef, visibleRange, totalHeight, scrollToRow } = useVirtualScroll({
|
|
41
|
-
totalRows: totalRowsRef,
|
|
42
|
-
rowHeight,
|
|
43
|
-
enabled: virtualScrollEnabled,
|
|
44
|
-
overscan,
|
|
45
|
-
});
|
|
18
|
+
const { wrapperRef, tableContainerRef, tableRef, lastMouseShift, state, columnReorder: { isDragging: isReorderDragging, dropIndicatorX, handleHeaderMouseDown: handleReorderMouseDown }, virtualScroll: { containerRef: vsContainerRef, visibleRange, totalHeight: _totalHeight, scrollToRow: _scrollToRow }, virtualScrollEnabled, columnResize: { handleResizeStart, getColumnWidth }, } = useDataGridTableSetup({ props: propsRef });
|
|
46
19
|
return () => {
|
|
47
20
|
/**
|
|
48
21
|
* Vue Performance: Fine-grained reactivity vs React memoization
|
|
@@ -68,15 +41,17 @@ export const DataGridTable = defineComponent({
|
|
|
68
41
|
const interaction = state.interaction.value;
|
|
69
42
|
const ctxMenu = state.contextMenu.value;
|
|
70
43
|
const viewModels = state.viewModels.value;
|
|
71
|
-
const
|
|
44
|
+
const pinning = state.pinning.value;
|
|
45
|
+
const { headerMenu } = pinning;
|
|
46
|
+
const { visibleCols, hasCheckboxCol, hasRowNumbersCol, colOffset: _colOffset, containerWidth, minTableWidth, desiredTableWidth, } = layout;
|
|
72
47
|
const currentPage = props.gridProps.currentPage ?? 1;
|
|
73
48
|
const pageSize = props.gridProps.pageSize ?? 25;
|
|
74
49
|
const rowNumberOffset = hasRowNumbersCol ? (currentPage - 1) * pageSize : 0;
|
|
75
50
|
const { selectedRowIds, handleRowCheckboxChange, handleSelectAll, allSelected, someSelected } = rowSel;
|
|
76
|
-
const { editingCell, setEditingCell, pendingEditorValue, setPendingEditorValue, commitCellEdit, cancelPopoverEdit, popoverAnchorEl, setPopoverAnchorEl } = editing;
|
|
77
|
-
const { setActiveCell, handleCellMouseDown, handleSelectAllCells, selectionRange, hasCellSelection, handleGridKeyDown, handleFillHandleMouseDown, handleCopy, handleCut, handlePaste, cutRange, copyRange, canUndo, canRedo, onUndo, onRedo, isDragging, } = interaction;
|
|
51
|
+
const { editingCell: _editingCell, setEditingCell, pendingEditorValue, setPendingEditorValue, commitCellEdit, cancelPopoverEdit, popoverAnchorEl, setPopoverAnchorEl } = editing;
|
|
52
|
+
const { setActiveCell, handleCellMouseDown, handleSelectAllCells, selectionRange, hasCellSelection, handleGridKeyDown, handleFillHandleMouseDown, handleCopy, handleCut, handlePaste, cutRange: _cutRange, copyRange: _copyRange, canUndo, canRedo, onUndo, onRedo, isDragging: _isDragging, } = interaction;
|
|
78
53
|
const { menuPosition, handleCellContextMenu, closeContextMenu } = ctxMenu;
|
|
79
|
-
const { headerFilterInput, cellDescriptorInput, statusBarConfig, showEmptyInGrid, onCellError } = viewModels;
|
|
54
|
+
const { headerFilterInput, cellDescriptorInput, statusBarConfig, showEmptyInGrid, onCellError: _onCellError } = viewModels;
|
|
80
55
|
const items = p.items;
|
|
81
56
|
const getRowId = p.getRowId;
|
|
82
57
|
const layoutMode = p.layoutMode ?? 'fill';
|
|
@@ -91,10 +66,6 @@ export const DataGridTable = defineComponent({
|
|
|
91
66
|
const fitToContent = layoutMode === 'content';
|
|
92
67
|
const allowOverflowX = !suppressHorizontalScroll && containerWidth > 0 && (minTableWidth > containerWidth || desiredTableWidth > containerWidth);
|
|
93
68
|
const headerRows = buildHeaderRows(p.columns, p.visibleColumns);
|
|
94
|
-
const { handleResizeStart, getColumnWidth } = useColumnResize({
|
|
95
|
-
columnSizingOverrides: computed(() => columnSizingOverrides),
|
|
96
|
-
setColumnSizingOverrides,
|
|
97
|
-
});
|
|
98
69
|
const editCallbacks = { commitCellEdit, setEditingCell, setPendingEditorValue, cancelPopoverEdit };
|
|
99
70
|
const interactionHandlers = { handleCellMouseDown, setActiveCell, setEditingCell, handleCellContextMenu };
|
|
100
71
|
const handleSingleRowClick = (e) => {
|
|
@@ -109,7 +80,7 @@ export const DataGridTable = defineComponent({
|
|
|
109
80
|
// Render a cell's content
|
|
110
81
|
const renderCellContent = (item, col, rowIndex, colIdx) => {
|
|
111
82
|
const descriptor = getCellRenderDescriptor(item, col, rowIndex, colIdx, cellDescriptorInput);
|
|
112
|
-
const
|
|
83
|
+
const _rowId = getRowId(item);
|
|
113
84
|
if (descriptor.mode === 'editing-inline') {
|
|
114
85
|
const editorProps = buildInlineEditorProps(item, col, descriptor, editCallbacks);
|
|
115
86
|
return h(InlineCellEditor, {
|
|
@@ -144,7 +115,7 @@ export const DataGridTable = defineComponent({
|
|
|
144
115
|
const cellStyle = resolveCellStyle(col, item);
|
|
145
116
|
const interactionProps = getCellInteractionProps(descriptor, col.columnId, interactionHandlers);
|
|
146
117
|
// Compute cell CSS classes based on state
|
|
147
|
-
const
|
|
118
|
+
const _cellClasses = ['ogrid-cell'];
|
|
148
119
|
const cellInlineStyle = {
|
|
149
120
|
width: '100%',
|
|
150
121
|
height: '100%',
|
|
@@ -393,7 +364,27 @@ export const DataGridTable = defineComponent({
|
|
|
393
364
|
},
|
|
394
365
|
onMousedown: (e) => handleReorderMouseDown(col.columnId, e),
|
|
395
366
|
}, [
|
|
396
|
-
h(
|
|
367
|
+
h('div', { style: { display: 'flex', alignItems: 'center', width: '100%' } }, [
|
|
368
|
+
h(ColumnHeaderFilter, getHeaderFilterConfig(col, headerFilterInput)),
|
|
369
|
+
h('button', {
|
|
370
|
+
onClick: (e) => {
|
|
371
|
+
e.stopPropagation();
|
|
372
|
+
headerMenu.open(col.columnId, e.currentTarget);
|
|
373
|
+
},
|
|
374
|
+
'aria-label': 'Column options',
|
|
375
|
+
title: 'Column options',
|
|
376
|
+
style: {
|
|
377
|
+
background: 'none',
|
|
378
|
+
border: 'none',
|
|
379
|
+
cursor: 'pointer',
|
|
380
|
+
padding: '2px 4px',
|
|
381
|
+
fontSize: '14px',
|
|
382
|
+
color: 'rgba(0,0,0,0.5)',
|
|
383
|
+
lineHeight: '1',
|
|
384
|
+
flexShrink: '0',
|
|
385
|
+
},
|
|
386
|
+
}, '\u22EE'),
|
|
387
|
+
]),
|
|
397
388
|
h('div', {
|
|
398
389
|
onMousedown: (e) => { e.stopPropagation(); handleResizeStart(e, col); },
|
|
399
390
|
style: {
|
|
@@ -541,6 +532,18 @@ export const DataGridTable = defineComponent({
|
|
|
541
532
|
onClose: closeContextMenu,
|
|
542
533
|
})),
|
|
543
534
|
] : []),
|
|
535
|
+
// Column header menu
|
|
536
|
+
h(ColumnHeaderMenu, {
|
|
537
|
+
isOpen: headerMenu.isOpen,
|
|
538
|
+
anchorElement: headerMenu.anchorElement,
|
|
539
|
+
onClose: headerMenu.close,
|
|
540
|
+
onPinLeft: headerMenu.handlePinLeft,
|
|
541
|
+
onPinRight: headerMenu.handlePinRight,
|
|
542
|
+
onUnpin: headerMenu.handleUnpin,
|
|
543
|
+
canPinLeft: headerMenu.canPinLeft,
|
|
544
|
+
canPinRight: headerMenu.canPinRight,
|
|
545
|
+
canUnpin: headerMenu.canUnpin,
|
|
546
|
+
}),
|
|
544
547
|
// Status bar
|
|
545
548
|
...(statusBarConfig ? [
|
|
546
549
|
h(StatusBar, {
|
package/dist/esm/OGrid/OGrid.js
CHANGED
|
@@ -3,6 +3,225 @@ import { useOGrid, } from '@alaarab/ogrid-vue';
|
|
|
3
3
|
import { DataGridTable } from '../DataGridTable/DataGridTable';
|
|
4
4
|
import { ColumnChooser } from '../ColumnChooser/ColumnChooser';
|
|
5
5
|
import { PaginationControls } from '../PaginationControls/PaginationControls';
|
|
6
|
+
// --- SideBar constants and styles ---
|
|
7
|
+
const PANEL_WIDTH = 240;
|
|
8
|
+
const TAB_WIDTH = 36;
|
|
9
|
+
const PANEL_LABELS = {
|
|
10
|
+
columns: 'Columns',
|
|
11
|
+
filters: 'Filters',
|
|
12
|
+
};
|
|
13
|
+
const PANEL_ICONS = {
|
|
14
|
+
columns: '\u2261', // hamburger icon
|
|
15
|
+
filters: '\u2A65', // filter icon
|
|
16
|
+
};
|
|
17
|
+
/** Render the SideBar inline (tab strip + panel content). */
|
|
18
|
+
function renderSideBar(sb) {
|
|
19
|
+
const isOpen = sb.activePanel !== null;
|
|
20
|
+
const position = sb.position ?? 'right';
|
|
21
|
+
const tabStripStyle = {
|
|
22
|
+
display: 'flex',
|
|
23
|
+
flexDirection: 'column',
|
|
24
|
+
width: `${TAB_WIDTH}px`,
|
|
25
|
+
background: 'var(--ogrid-header-bg, #f5f5f5)',
|
|
26
|
+
...(position === 'right'
|
|
27
|
+
? { borderLeft: '1px solid var(--ogrid-border, #e0e0e0)' }
|
|
28
|
+
: { borderRight: '1px solid var(--ogrid-border, #e0e0e0)' }),
|
|
29
|
+
};
|
|
30
|
+
const tabStrip = h('div', { style: tabStripStyle, role: 'tablist', 'aria-label': 'Side bar tabs' }, sb.panels.map((panel) => h('button', {
|
|
31
|
+
key: panel,
|
|
32
|
+
role: 'tab',
|
|
33
|
+
'aria-selected': sb.activePanel === panel,
|
|
34
|
+
'aria-label': PANEL_LABELS[panel],
|
|
35
|
+
title: PANEL_LABELS[panel],
|
|
36
|
+
onClick: () => sb.onPanelChange(sb.activePanel === panel ? null : panel),
|
|
37
|
+
style: {
|
|
38
|
+
width: `${TAB_WIDTH}px`,
|
|
39
|
+
height: `${TAB_WIDTH}px`,
|
|
40
|
+
border: 'none',
|
|
41
|
+
cursor: 'pointer',
|
|
42
|
+
color: 'var(--ogrid-fg, #242424)',
|
|
43
|
+
fontSize: '14px',
|
|
44
|
+
display: 'flex',
|
|
45
|
+
alignItems: 'center',
|
|
46
|
+
justifyContent: 'center',
|
|
47
|
+
background: sb.activePanel === panel ? 'var(--ogrid-bg, #fff)' : 'transparent',
|
|
48
|
+
fontWeight: sb.activePanel === panel ? 'bold' : 'normal',
|
|
49
|
+
},
|
|
50
|
+
}, PANEL_ICONS[panel])));
|
|
51
|
+
let panelContent = null;
|
|
52
|
+
if (isOpen && sb.activePanel) {
|
|
53
|
+
const panelContainerStyle = {
|
|
54
|
+
width: `${PANEL_WIDTH}px`,
|
|
55
|
+
display: 'flex',
|
|
56
|
+
flexDirection: 'column',
|
|
57
|
+
overflow: 'hidden',
|
|
58
|
+
background: 'var(--ogrid-bg, #fff)',
|
|
59
|
+
color: 'var(--ogrid-fg, #242424)',
|
|
60
|
+
...(position === 'right'
|
|
61
|
+
? { borderLeft: '1px solid var(--ogrid-border, #e0e0e0)' }
|
|
62
|
+
: { borderRight: '1px solid var(--ogrid-border, #e0e0e0)' }),
|
|
63
|
+
};
|
|
64
|
+
const panelBodyChildren = [];
|
|
65
|
+
if (sb.activePanel === 'columns') {
|
|
66
|
+
const allVisible = sb.columns.every((c) => sb.visibleColumns.has(c.columnId));
|
|
67
|
+
// Select All / Clear All buttons
|
|
68
|
+
panelBodyChildren.push(h('div', { style: { display: 'flex', gap: '8px', marginBottom: '8px' } }, [
|
|
69
|
+
h('button', {
|
|
70
|
+
disabled: allVisible,
|
|
71
|
+
onClick: () => {
|
|
72
|
+
const next = new Set(sb.visibleColumns);
|
|
73
|
+
sb.columns.forEach((c) => next.add(c.columnId));
|
|
74
|
+
sb.onSetVisibleColumns(next);
|
|
75
|
+
},
|
|
76
|
+
style: { flex: '1', cursor: 'pointer', background: 'var(--ogrid-bg-subtle, #f3f2f1)', color: 'var(--ogrid-fg, #242424)', border: '1px solid var(--ogrid-border, #e0e0e0)', borderRadius: '4px', padding: '4px 8px' },
|
|
77
|
+
}, 'Select All'),
|
|
78
|
+
h('button', {
|
|
79
|
+
onClick: () => {
|
|
80
|
+
const next = new Set();
|
|
81
|
+
sb.columns.forEach((c) => {
|
|
82
|
+
if (c.required && sb.visibleColumns.has(c.columnId))
|
|
83
|
+
next.add(c.columnId);
|
|
84
|
+
});
|
|
85
|
+
sb.onSetVisibleColumns(next);
|
|
86
|
+
},
|
|
87
|
+
style: { flex: '1', cursor: 'pointer', background: 'var(--ogrid-bg-subtle, #f3f2f1)', color: 'var(--ogrid-fg, #242424)', border: '1px solid var(--ogrid-border, #e0e0e0)', borderRadius: '4px', padding: '4px 8px' },
|
|
88
|
+
}, 'Clear All'),
|
|
89
|
+
]));
|
|
90
|
+
// Column checkboxes
|
|
91
|
+
sb.columns.forEach((col) => {
|
|
92
|
+
panelBodyChildren.push(h('label', { key: col.columnId, style: { display: 'flex', alignItems: 'center', gap: '6px', padding: '2px 0', cursor: 'pointer' } }, [
|
|
93
|
+
h('input', {
|
|
94
|
+
type: 'checkbox',
|
|
95
|
+
checked: sb.visibleColumns.has(col.columnId),
|
|
96
|
+
disabled: col.required,
|
|
97
|
+
onChange: (e) => sb.onVisibilityChange(col.columnId, e.target.checked),
|
|
98
|
+
}),
|
|
99
|
+
h('span', null, col.name),
|
|
100
|
+
]));
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
if (sb.activePanel === 'filters') {
|
|
104
|
+
if (sb.filterableColumns.length === 0) {
|
|
105
|
+
panelBodyChildren.push(h('div', { style: { color: 'var(--ogrid-muted, #999)', fontStyle: 'italic' } }, 'No filterable columns'));
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
sb.filterableColumns.forEach((col) => {
|
|
109
|
+
const filterKey = col.filterField;
|
|
110
|
+
const groupChildren = [
|
|
111
|
+
h('div', { style: { fontWeight: '500', marginBottom: '4px', fontSize: '13px' } }, col.name),
|
|
112
|
+
];
|
|
113
|
+
if (col.filterType === 'text') {
|
|
114
|
+
const currentVal = sb.filters[filterKey]?.type === 'text' ? sb.filters[filterKey].value : '';
|
|
115
|
+
groupChildren.push(h('input', {
|
|
116
|
+
type: 'text',
|
|
117
|
+
value: currentVal,
|
|
118
|
+
onInput: (e) => {
|
|
119
|
+
const val = e.target.value;
|
|
120
|
+
sb.onFilterChange(filterKey, val ? { type: 'text', value: val } : undefined);
|
|
121
|
+
},
|
|
122
|
+
placeholder: `Filter ${col.name}...`,
|
|
123
|
+
'aria-label': `Filter ${col.name}`,
|
|
124
|
+
style: { width: '100%', boxSizing: 'border-box', padding: '4px 6px', background: 'var(--ogrid-bg, #fff)', color: 'var(--ogrid-fg, #242424)', border: '1px solid var(--ogrid-border, #e0e0e0)', borderRadius: '4px' },
|
|
125
|
+
}));
|
|
126
|
+
}
|
|
127
|
+
if (col.filterType === 'multiSelect') {
|
|
128
|
+
const options = sb.filterOptions[filterKey] ?? [];
|
|
129
|
+
const msChildren = options.map((opt) => {
|
|
130
|
+
const selected = sb.filters[filterKey]?.type === 'multiSelect' ? sb.filters[filterKey].value.includes(opt) : false;
|
|
131
|
+
return h('label', { key: opt, style: { display: 'flex', alignItems: 'center', gap: '4px', padding: '1px 0', cursor: 'pointer', fontSize: '13px' } }, [
|
|
132
|
+
h('input', {
|
|
133
|
+
type: 'checkbox',
|
|
134
|
+
checked: selected,
|
|
135
|
+
onChange: (e) => {
|
|
136
|
+
const current = sb.filters[filterKey]?.type === 'multiSelect' ? sb.filters[filterKey].value : [];
|
|
137
|
+
const next = e.target.checked
|
|
138
|
+
? [...current, opt]
|
|
139
|
+
: current.filter((v) => v !== opt);
|
|
140
|
+
sb.onFilterChange(filterKey, next.length > 0 ? { type: 'multiSelect', value: next } : undefined);
|
|
141
|
+
},
|
|
142
|
+
}),
|
|
143
|
+
h('span', null, opt),
|
|
144
|
+
]);
|
|
145
|
+
});
|
|
146
|
+
groupChildren.push(h('div', { style: { maxHeight: '120px', overflowY: 'auto' }, role: 'group', 'aria-label': `${col.name} options` }, msChildren));
|
|
147
|
+
}
|
|
148
|
+
if (col.filterType === 'date') {
|
|
149
|
+
const existingValue = sb.filters[filterKey]?.type === 'date' ? sb.filters[filterKey].value : { from: undefined, to: undefined };
|
|
150
|
+
groupChildren.push(h('div', { style: { display: 'flex', flexDirection: 'column', gap: '4px' } }, [
|
|
151
|
+
h('label', { style: { display: 'flex', alignItems: 'center', gap: '4px', fontSize: '12px' } }, [
|
|
152
|
+
'From:',
|
|
153
|
+
h('input', {
|
|
154
|
+
type: 'date',
|
|
155
|
+
value: existingValue.from ?? '',
|
|
156
|
+
onInput: (e) => {
|
|
157
|
+
const from = e.target.value || undefined;
|
|
158
|
+
const to = existingValue.to;
|
|
159
|
+
sb.onFilterChange(filterKey, from || to ? { type: 'date', value: { from, to } } : undefined);
|
|
160
|
+
},
|
|
161
|
+
'aria-label': `${col.name} from date`,
|
|
162
|
+
style: { flex: '1', padding: '2px 4px', background: 'var(--ogrid-bg, #fff)', color: 'var(--ogrid-fg, #242424)', border: '1px solid var(--ogrid-border, #e0e0e0)', borderRadius: '4px' },
|
|
163
|
+
}),
|
|
164
|
+
]),
|
|
165
|
+
h('label', { style: { display: 'flex', alignItems: 'center', gap: '4px', fontSize: '12px' } }, [
|
|
166
|
+
'To:',
|
|
167
|
+
h('input', {
|
|
168
|
+
type: 'date',
|
|
169
|
+
value: existingValue.to ?? '',
|
|
170
|
+
onInput: (e) => {
|
|
171
|
+
const to = e.target.value || undefined;
|
|
172
|
+
const from = existingValue.from;
|
|
173
|
+
sb.onFilterChange(filterKey, from || to ? { type: 'date', value: { from, to } } : undefined);
|
|
174
|
+
},
|
|
175
|
+
'aria-label': `${col.name} to date`,
|
|
176
|
+
style: { flex: '1', padding: '2px 4px', background: 'var(--ogrid-bg, #fff)', color: 'var(--ogrid-fg, #242424)', border: '1px solid var(--ogrid-border, #e0e0e0)', borderRadius: '4px' },
|
|
177
|
+
}),
|
|
178
|
+
]),
|
|
179
|
+
]));
|
|
180
|
+
}
|
|
181
|
+
panelBodyChildren.push(h('div', { key: col.columnId, style: { marginBottom: '12px' } }, groupChildren));
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
panelContent = h('div', { role: 'tabpanel', 'aria-label': PANEL_LABELS[sb.activePanel], style: panelContainerStyle }, [
|
|
186
|
+
// Panel header
|
|
187
|
+
h('div', {
|
|
188
|
+
style: {
|
|
189
|
+
display: 'flex',
|
|
190
|
+
justifyContent: 'space-between',
|
|
191
|
+
alignItems: 'center',
|
|
192
|
+
padding: '8px 12px',
|
|
193
|
+
borderBottom: '1px solid var(--ogrid-border, #e0e0e0)',
|
|
194
|
+
fontWeight: '600',
|
|
195
|
+
},
|
|
196
|
+
}, [
|
|
197
|
+
h('span', null, PANEL_LABELS[sb.activePanel]),
|
|
198
|
+
h('button', {
|
|
199
|
+
onClick: () => sb.onPanelChange(null),
|
|
200
|
+
style: { border: 'none', background: 'transparent', cursor: 'pointer', fontSize: '16px', color: 'var(--ogrid-fg, #242424)' },
|
|
201
|
+
'aria-label': 'Close panel',
|
|
202
|
+
}, '\u00D7'),
|
|
203
|
+
]),
|
|
204
|
+
// Panel body
|
|
205
|
+
h('div', { style: { flex: '1', overflowY: 'auto', padding: '8px 12px' } }, panelBodyChildren),
|
|
206
|
+
]);
|
|
207
|
+
}
|
|
208
|
+
const children = [];
|
|
209
|
+
if (position === 'left') {
|
|
210
|
+
children.push(tabStrip);
|
|
211
|
+
if (panelContent)
|
|
212
|
+
children.push(panelContent);
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
if (panelContent)
|
|
216
|
+
children.push(panelContent);
|
|
217
|
+
children.push(tabStrip);
|
|
218
|
+
}
|
|
219
|
+
return h('div', {
|
|
220
|
+
style: { display: 'flex', flexDirection: 'row', flexShrink: '0' },
|
|
221
|
+
role: 'complementary',
|
|
222
|
+
'aria-label': 'Side bar',
|
|
223
|
+
}, children);
|
|
224
|
+
}
|
|
6
225
|
export const OGrid = defineComponent({
|
|
7
226
|
name: 'OGrid',
|
|
8
227
|
props: {
|
|
@@ -15,6 +234,8 @@ export const OGrid = defineComponent({
|
|
|
15
234
|
expose({ api: api.value });
|
|
16
235
|
return () => {
|
|
17
236
|
const sideBar = layout.value.sideBarProps;
|
|
237
|
+
const hasSideBar = sideBar != null;
|
|
238
|
+
const sideBarPosition = sideBar?.position ?? 'right';
|
|
18
239
|
// Toolbar
|
|
19
240
|
const toolbarChildren = [];
|
|
20
241
|
if (layout.value.toolbar) {
|
|
@@ -41,6 +262,23 @@ export const OGrid = defineComponent({
|
|
|
41
262
|
pageSizeOptions: pagination.value.pageSizeOptions,
|
|
42
263
|
entityLabelPlural: pagination.value.entityLabelPlural,
|
|
43
264
|
});
|
|
265
|
+
// Grid content area
|
|
266
|
+
const gridChild = h('div', {
|
|
267
|
+
style: { flex: '1', minWidth: '0', minHeight: '0', display: 'flex', flexDirection: 'column' },
|
|
268
|
+
}, [
|
|
269
|
+
h(DataGridTable, {
|
|
270
|
+
gridProps: dataGridProps.value,
|
|
271
|
+
}),
|
|
272
|
+
]);
|
|
273
|
+
// Main content area (sidebar + grid)
|
|
274
|
+
const mainAreaChildren = [];
|
|
275
|
+
if (hasSideBar && sideBarPosition === 'left') {
|
|
276
|
+
mainAreaChildren.push(renderSideBar(sideBar));
|
|
277
|
+
}
|
|
278
|
+
mainAreaChildren.push(gridChild);
|
|
279
|
+
if (hasSideBar && sideBarPosition !== 'left') {
|
|
280
|
+
mainAreaChildren.push(renderSideBar(sideBar));
|
|
281
|
+
}
|
|
44
282
|
return h('div', {
|
|
45
283
|
class: layout.value.className,
|
|
46
284
|
style: {
|
|
@@ -74,12 +312,7 @@ export const OGrid = defineComponent({
|
|
|
74
312
|
}, [layout.value.toolbarBelow]),
|
|
75
313
|
] : []),
|
|
76
314
|
// Main content area (sidebar + grid)
|
|
77
|
-
h('div', { style: { display: 'flex', flex: '1', minHeight: '0' } },
|
|
78
|
-
// DataGridTable
|
|
79
|
-
h(DataGridTable, {
|
|
80
|
-
gridProps: dataGridProps.value,
|
|
81
|
-
}),
|
|
82
|
-
]),
|
|
315
|
+
h('div', { style: { display: 'flex', flex: '1', minHeight: '0' } }, mainAreaChildren),
|
|
83
316
|
// Footer strip (pagination)
|
|
84
317
|
h('div', {
|
|
85
318
|
style: {
|
package/dist/esm/index.js
CHANGED
|
@@ -6,3 +6,4 @@ export { DataGridTable } from './DataGridTable/DataGridTable';
|
|
|
6
6
|
export { ColumnHeaderFilter } from './ColumnHeaderFilter/ColumnHeaderFilter';
|
|
7
7
|
export { ColumnChooser } from './ColumnChooser/ColumnChooser';
|
|
8
8
|
export { PaginationControls } from './PaginationControls/PaginationControls';
|
|
9
|
+
export { ColumnHeaderMenu } from './ColumnHeaderMenu/ColumnHeaderMenu';
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { type PropType } from 'vue';
|
|
2
|
+
export interface ColumnHeaderMenuProps {
|
|
3
|
+
isOpen: boolean;
|
|
4
|
+
anchorElement: HTMLElement | null;
|
|
5
|
+
onClose: () => void;
|
|
6
|
+
onPinLeft: () => void;
|
|
7
|
+
onPinRight: () => void;
|
|
8
|
+
onUnpin: () => void;
|
|
9
|
+
canPinLeft: boolean;
|
|
10
|
+
canPinRight: boolean;
|
|
11
|
+
canUnpin: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare const ColumnHeaderMenu: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
14
|
+
isOpen: {
|
|
15
|
+
type: BooleanConstructor;
|
|
16
|
+
required: true;
|
|
17
|
+
};
|
|
18
|
+
anchorElement: {
|
|
19
|
+
type: PropType<HTMLElement | null>;
|
|
20
|
+
default: null;
|
|
21
|
+
};
|
|
22
|
+
onClose: {
|
|
23
|
+
type: PropType<() => void>;
|
|
24
|
+
required: true;
|
|
25
|
+
};
|
|
26
|
+
onPinLeft: {
|
|
27
|
+
type: PropType<() => void>;
|
|
28
|
+
required: true;
|
|
29
|
+
};
|
|
30
|
+
onPinRight: {
|
|
31
|
+
type: PropType<() => void>;
|
|
32
|
+
required: true;
|
|
33
|
+
};
|
|
34
|
+
onUnpin: {
|
|
35
|
+
type: PropType<() => void>;
|
|
36
|
+
required: true;
|
|
37
|
+
};
|
|
38
|
+
canPinLeft: {
|
|
39
|
+
type: BooleanConstructor;
|
|
40
|
+
required: true;
|
|
41
|
+
};
|
|
42
|
+
canPinRight: {
|
|
43
|
+
type: BooleanConstructor;
|
|
44
|
+
required: true;
|
|
45
|
+
};
|
|
46
|
+
canUnpin: {
|
|
47
|
+
type: BooleanConstructor;
|
|
48
|
+
required: true;
|
|
49
|
+
};
|
|
50
|
+
}>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
|
|
51
|
+
[key: string]: any;
|
|
52
|
+
}>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
53
|
+
isOpen: {
|
|
54
|
+
type: BooleanConstructor;
|
|
55
|
+
required: true;
|
|
56
|
+
};
|
|
57
|
+
anchorElement: {
|
|
58
|
+
type: PropType<HTMLElement | null>;
|
|
59
|
+
default: null;
|
|
60
|
+
};
|
|
61
|
+
onClose: {
|
|
62
|
+
type: PropType<() => void>;
|
|
63
|
+
required: true;
|
|
64
|
+
};
|
|
65
|
+
onPinLeft: {
|
|
66
|
+
type: PropType<() => void>;
|
|
67
|
+
required: true;
|
|
68
|
+
};
|
|
69
|
+
onPinRight: {
|
|
70
|
+
type: PropType<() => void>;
|
|
71
|
+
required: true;
|
|
72
|
+
};
|
|
73
|
+
onUnpin: {
|
|
74
|
+
type: PropType<() => void>;
|
|
75
|
+
required: true;
|
|
76
|
+
};
|
|
77
|
+
canPinLeft: {
|
|
78
|
+
type: BooleanConstructor;
|
|
79
|
+
required: true;
|
|
80
|
+
};
|
|
81
|
+
canPinRight: {
|
|
82
|
+
type: BooleanConstructor;
|
|
83
|
+
required: true;
|
|
84
|
+
};
|
|
85
|
+
canUnpin: {
|
|
86
|
+
type: BooleanConstructor;
|
|
87
|
+
required: true;
|
|
88
|
+
};
|
|
89
|
+
}>> & Readonly<{}>, {
|
|
90
|
+
anchorElement: HTMLElement | null;
|
|
91
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { type PropType } from 'vue';
|
|
1
|
+
import { type PropType, type VNode } from 'vue';
|
|
2
2
|
import { type IOGridProps } from '@alaarab/ogrid-vue';
|
|
3
3
|
export declare const OGrid: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
4
4
|
gridProps: {
|
|
5
5
|
type: PropType<IOGridProps<unknown>>;
|
|
6
6
|
required: true;
|
|
7
7
|
};
|
|
8
|
-
}>, () =>
|
|
8
|
+
}>, () => VNode<import("vue").RendererNode, import("vue").RendererElement, {
|
|
9
9
|
[key: string]: any;
|
|
10
10
|
}>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
11
11
|
gridProps: {
|
package/dist/types/index.d.ts
CHANGED
|
@@ -7,3 +7,5 @@ export { ColumnChooser } from './ColumnChooser/ColumnChooser';
|
|
|
7
7
|
export type { IColumnChooserProps } from './ColumnChooser/ColumnChooser';
|
|
8
8
|
export { PaginationControls } from './PaginationControls/PaginationControls';
|
|
9
9
|
export type { IPaginationControlsProps } from './PaginationControls/PaginationControls';
|
|
10
|
+
export { ColumnHeaderMenu } from './ColumnHeaderMenu/ColumnHeaderMenu';
|
|
11
|
+
export type { ColumnHeaderMenuProps } from './ColumnHeaderMenu/ColumnHeaderMenu';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alaarab/ogrid-vue-vuetify",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.7",
|
|
4
4
|
"description": "OGrid Vuetify – Vuetify-based data grid with sorting, filtering, pagination, column chooser, and CSV export.",
|
|
5
5
|
"main": "dist/esm/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"files": ["dist", "README.md", "LICENSE"],
|
|
23
23
|
"engines": { "node": ">=18" },
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@alaarab/ogrid-vue": "2.0.
|
|
25
|
+
"@alaarab/ogrid-vue": "2.0.7"
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
28
|
"vue": "^3.3.0",
|
|
@@ -32,9 +32,10 @@
|
|
|
32
32
|
"vue": "^3.5.28",
|
|
33
33
|
"vuetify": "^3.11.8",
|
|
34
34
|
"@vitejs/plugin-vue": "^6.0.4",
|
|
35
|
-
"vite": "^7.
|
|
36
|
-
"vite-plugin-dts": "^4.5.
|
|
37
|
-
"typescript": "^5.
|
|
35
|
+
"vite": "^7.3.1",
|
|
36
|
+
"vite-plugin-dts": "^4.5.4",
|
|
37
|
+
"typescript": "^5.9.3"
|
|
38
38
|
},
|
|
39
|
+
"sideEffects": false,
|
|
39
40
|
"publishConfig": { "access": "public" }
|
|
40
41
|
}
|