@alaarab/ogrid-vue-vuetify 2.0.6 → 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 +43 -8
- 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 +2 -2
|
@@ -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
|
+
});
|
|
@@ -2,6 +2,7 @@ import { defineComponent, computed, h, Teleport } from 'vue';
|
|
|
2
2
|
import { VCheckbox, VProgressCircular, VBtn } from 'vuetify/components';
|
|
3
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';
|
|
@@ -14,7 +15,7 @@ export const DataGridTable = defineComponent({
|
|
|
14
15
|
},
|
|
15
16
|
setup(props) {
|
|
16
17
|
const propsRef = computed(() => props.gridProps);
|
|
17
|
-
const { wrapperRef, tableContainerRef, tableRef, lastMouseShift, state, columnReorder: { isDragging: isReorderDragging, dropIndicatorX, handleHeaderMouseDown: handleReorderMouseDown }, virtualScroll: { containerRef: vsContainerRef, visibleRange, totalHeight, scrollToRow }, virtualScrollEnabled, columnResize: { handleResizeStart, getColumnWidth }, } = useDataGridTableSetup({ props: propsRef });
|
|
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 });
|
|
18
19
|
return () => {
|
|
19
20
|
/**
|
|
20
21
|
* Vue Performance: Fine-grained reactivity vs React memoization
|
|
@@ -40,15 +41,17 @@ export const DataGridTable = defineComponent({
|
|
|
40
41
|
const interaction = state.interaction.value;
|
|
41
42
|
const ctxMenu = state.contextMenu.value;
|
|
42
43
|
const viewModels = state.viewModels.value;
|
|
43
|
-
const
|
|
44
|
+
const pinning = state.pinning.value;
|
|
45
|
+
const { headerMenu } = pinning;
|
|
46
|
+
const { visibleCols, hasCheckboxCol, hasRowNumbersCol, colOffset: _colOffset, containerWidth, minTableWidth, desiredTableWidth, } = layout;
|
|
44
47
|
const currentPage = props.gridProps.currentPage ?? 1;
|
|
45
48
|
const pageSize = props.gridProps.pageSize ?? 25;
|
|
46
49
|
const rowNumberOffset = hasRowNumbersCol ? (currentPage - 1) * pageSize : 0;
|
|
47
50
|
const { selectedRowIds, handleRowCheckboxChange, handleSelectAll, allSelected, someSelected } = rowSel;
|
|
48
|
-
const { editingCell, setEditingCell, pendingEditorValue, setPendingEditorValue, commitCellEdit, cancelPopoverEdit, popoverAnchorEl, setPopoverAnchorEl } = editing;
|
|
49
|
-
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;
|
|
50
53
|
const { menuPosition, handleCellContextMenu, closeContextMenu } = ctxMenu;
|
|
51
|
-
const { headerFilterInput, cellDescriptorInput, statusBarConfig, showEmptyInGrid, onCellError } = viewModels;
|
|
54
|
+
const { headerFilterInput, cellDescriptorInput, statusBarConfig, showEmptyInGrid, onCellError: _onCellError } = viewModels;
|
|
52
55
|
const items = p.items;
|
|
53
56
|
const getRowId = p.getRowId;
|
|
54
57
|
const layoutMode = p.layoutMode ?? 'fill';
|
|
@@ -77,7 +80,7 @@ export const DataGridTable = defineComponent({
|
|
|
77
80
|
// Render a cell's content
|
|
78
81
|
const renderCellContent = (item, col, rowIndex, colIdx) => {
|
|
79
82
|
const descriptor = getCellRenderDescriptor(item, col, rowIndex, colIdx, cellDescriptorInput);
|
|
80
|
-
const
|
|
83
|
+
const _rowId = getRowId(item);
|
|
81
84
|
if (descriptor.mode === 'editing-inline') {
|
|
82
85
|
const editorProps = buildInlineEditorProps(item, col, descriptor, editCallbacks);
|
|
83
86
|
return h(InlineCellEditor, {
|
|
@@ -112,7 +115,7 @@ export const DataGridTable = defineComponent({
|
|
|
112
115
|
const cellStyle = resolveCellStyle(col, item);
|
|
113
116
|
const interactionProps = getCellInteractionProps(descriptor, col.columnId, interactionHandlers);
|
|
114
117
|
// Compute cell CSS classes based on state
|
|
115
|
-
const
|
|
118
|
+
const _cellClasses = ['ogrid-cell'];
|
|
116
119
|
const cellInlineStyle = {
|
|
117
120
|
width: '100%',
|
|
118
121
|
height: '100%',
|
|
@@ -361,7 +364,27 @@ export const DataGridTable = defineComponent({
|
|
|
361
364
|
},
|
|
362
365
|
onMousedown: (e) => handleReorderMouseDown(col.columnId, e),
|
|
363
366
|
}, [
|
|
364
|
-
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
|
+
]),
|
|
365
388
|
h('div', {
|
|
366
389
|
onMousedown: (e) => { e.stopPropagation(); handleResizeStart(e, col); },
|
|
367
390
|
style: {
|
|
@@ -509,6 +532,18 @@ export const DataGridTable = defineComponent({
|
|
|
509
532
|
onClose: closeContextMenu,
|
|
510
533
|
})),
|
|
511
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
|
+
}),
|
|
512
547
|
// Status bar
|
|
513
548
|
...(statusBarConfig ? [
|
|
514
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",
|