@alaarab/ogrid-vue 2.1.2 → 2.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/index.js +4336 -15
- package/package.json +4 -4
- package/dist/esm/components/MarchingAntsOverlay.js +0 -144
- package/dist/esm/components/SideBar.js +0 -1
- package/dist/esm/components/StatusBar.js +0 -49
- package/dist/esm/components/createDataGridTable.js +0 -514
- package/dist/esm/components/createInlineCellEditor.js +0 -194
- package/dist/esm/components/createOGrid.js +0 -383
- package/dist/esm/composables/index.js +0 -33
- package/dist/esm/composables/useActiveCell.js +0 -77
- package/dist/esm/composables/useCellEditing.js +0 -27
- package/dist/esm/composables/useCellSelection.js +0 -359
- package/dist/esm/composables/useClipboard.js +0 -87
- package/dist/esm/composables/useColumnChooserState.js +0 -74
- package/dist/esm/composables/useColumnHeaderFilterState.js +0 -189
- package/dist/esm/composables/useColumnHeaderMenuState.js +0 -113
- package/dist/esm/composables/useColumnPinning.js +0 -64
- package/dist/esm/composables/useColumnReorder.js +0 -110
- package/dist/esm/composables/useColumnResize.js +0 -73
- package/dist/esm/composables/useContextMenu.js +0 -23
- package/dist/esm/composables/useDataGridState.js +0 -425
- package/dist/esm/composables/useDataGridTableSetup.js +0 -66
- package/dist/esm/composables/useDateFilterState.js +0 -36
- package/dist/esm/composables/useDebounce.js +0 -60
- package/dist/esm/composables/useFillHandle.js +0 -205
- package/dist/esm/composables/useFilterOptions.js +0 -39
- package/dist/esm/composables/useInlineCellEditorState.js +0 -42
- package/dist/esm/composables/useKeyboardNavigation.js +0 -232
- package/dist/esm/composables/useLatestRef.js +0 -27
- package/dist/esm/composables/useMultiSelectFilterState.js +0 -59
- package/dist/esm/composables/useOGrid.js +0 -491
- package/dist/esm/composables/usePeopleFilterState.js +0 -66
- package/dist/esm/composables/useRichSelectState.js +0 -59
- package/dist/esm/composables/useRowSelection.js +0 -75
- package/dist/esm/composables/useSideBarState.js +0 -41
- package/dist/esm/composables/useTableLayout.js +0 -85
- package/dist/esm/composables/useTextFilterState.js +0 -26
- package/dist/esm/composables/useUndoRedo.js +0 -65
- package/dist/esm/composables/useVirtualScroll.js +0 -87
- package/dist/esm/types/columnTypes.js +0 -1
- package/dist/esm/types/dataGridTypes.js +0 -1
- package/dist/esm/types/index.js +0 -1
- package/dist/esm/utils/dataGridViewModel.js +0 -23
- package/dist/esm/utils/index.js +0 -1
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
import { defineComponent, ref, h, onMounted, nextTick, watch } from 'vue';
|
|
2
|
-
const editorWrapperStyle = {
|
|
3
|
-
width: '100%',
|
|
4
|
-
height: '100%',
|
|
5
|
-
display: 'flex',
|
|
6
|
-
alignItems: 'center',
|
|
7
|
-
padding: '0 2px',
|
|
8
|
-
boxSizing: 'border-box',
|
|
9
|
-
};
|
|
10
|
-
export function createInlineCellEditor(options) {
|
|
11
|
-
const { renderCheckbox, renderDatePicker } = options;
|
|
12
|
-
return defineComponent({
|
|
13
|
-
name: 'InlineCellEditor',
|
|
14
|
-
props: {
|
|
15
|
-
value: { default: undefined },
|
|
16
|
-
item: { type: Object, required: true },
|
|
17
|
-
column: { type: Object, required: true },
|
|
18
|
-
rowIndex: { type: Number, required: true },
|
|
19
|
-
editorType: { type: String, required: true },
|
|
20
|
-
onCommit: { type: Function, required: true },
|
|
21
|
-
onCancel: { type: Function, required: true },
|
|
22
|
-
},
|
|
23
|
-
setup(props) {
|
|
24
|
-
const inputRef = ref(null);
|
|
25
|
-
const selectWrapperRef = ref(null);
|
|
26
|
-
const selectDropdownRef = ref(null);
|
|
27
|
-
const localValue = ref(props.value);
|
|
28
|
-
const highlightedIndex = ref(0);
|
|
29
|
-
const positionDropdown = () => {
|
|
30
|
-
const wrapper = selectWrapperRef.value;
|
|
31
|
-
const dropdown = selectDropdownRef.value;
|
|
32
|
-
if (!wrapper || !dropdown)
|
|
33
|
-
return;
|
|
34
|
-
const rect = wrapper.getBoundingClientRect();
|
|
35
|
-
const maxH = 200;
|
|
36
|
-
const spaceBelow = window.innerHeight - rect.bottom;
|
|
37
|
-
const flipUp = spaceBelow < maxH && rect.top > spaceBelow;
|
|
38
|
-
dropdown.style.position = 'fixed';
|
|
39
|
-
dropdown.style.left = `${rect.left}px`;
|
|
40
|
-
dropdown.style.width = `${rect.width}px`;
|
|
41
|
-
dropdown.style.maxHeight = `${maxH}px`;
|
|
42
|
-
dropdown.style.zIndex = '9999';
|
|
43
|
-
dropdown.style.right = 'auto';
|
|
44
|
-
if (flipUp) {
|
|
45
|
-
dropdown.style.top = 'auto';
|
|
46
|
-
dropdown.style.bottom = `${window.innerHeight - rect.top}px`;
|
|
47
|
-
}
|
|
48
|
-
else {
|
|
49
|
-
dropdown.style.top = `${rect.bottom}px`;
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
onMounted(() => {
|
|
53
|
-
nextTick(() => {
|
|
54
|
-
if (selectWrapperRef.value) {
|
|
55
|
-
selectWrapperRef.value.focus();
|
|
56
|
-
positionDropdown();
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
inputRef.value?.focus();
|
|
60
|
-
inputRef.value?.select();
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
// Sync local value when prop changes
|
|
64
|
-
watch(() => props.value, (v) => { localValue.value = v; });
|
|
65
|
-
// Initialize highlighted index to current value
|
|
66
|
-
const initHighlightedIndex = () => {
|
|
67
|
-
const values = props.column.cellEditorParams?.values ?? [];
|
|
68
|
-
const idx = values.findIndex((v) => String(v) === String(props.value));
|
|
69
|
-
highlightedIndex.value = Math.max(idx, 0);
|
|
70
|
-
};
|
|
71
|
-
initHighlightedIndex();
|
|
72
|
-
const scrollHighlightedIntoView = () => {
|
|
73
|
-
nextTick(() => {
|
|
74
|
-
const dropdown = selectDropdownRef.value;
|
|
75
|
-
if (!dropdown)
|
|
76
|
-
return;
|
|
77
|
-
const highlighted = dropdown.children[highlightedIndex.value];
|
|
78
|
-
highlighted?.scrollIntoView({ block: 'nearest' });
|
|
79
|
-
});
|
|
80
|
-
};
|
|
81
|
-
const getDisplayText = (value) => {
|
|
82
|
-
const formatValue = props.column.cellEditorParams?.formatValue;
|
|
83
|
-
if (formatValue)
|
|
84
|
-
return formatValue(value);
|
|
85
|
-
return value != null ? String(value) : '';
|
|
86
|
-
};
|
|
87
|
-
const handleSelectKeyDown = (e) => {
|
|
88
|
-
const values = props.column.cellEditorParams?.values ?? [];
|
|
89
|
-
switch (e.key) {
|
|
90
|
-
case 'ArrowDown':
|
|
91
|
-
e.preventDefault();
|
|
92
|
-
highlightedIndex.value = Math.min(highlightedIndex.value + 1, values.length - 1);
|
|
93
|
-
scrollHighlightedIntoView();
|
|
94
|
-
break;
|
|
95
|
-
case 'ArrowUp':
|
|
96
|
-
e.preventDefault();
|
|
97
|
-
highlightedIndex.value = Math.max(highlightedIndex.value - 1, 0);
|
|
98
|
-
scrollHighlightedIntoView();
|
|
99
|
-
break;
|
|
100
|
-
case 'Enter':
|
|
101
|
-
e.preventDefault();
|
|
102
|
-
e.stopPropagation();
|
|
103
|
-
if (values.length > 0 && highlightedIndex.value < values.length) {
|
|
104
|
-
props.onCommit(values[highlightedIndex.value]);
|
|
105
|
-
}
|
|
106
|
-
break;
|
|
107
|
-
case 'Tab':
|
|
108
|
-
e.preventDefault();
|
|
109
|
-
if (values.length > 0 && highlightedIndex.value < values.length) {
|
|
110
|
-
props.onCommit(values[highlightedIndex.value]);
|
|
111
|
-
}
|
|
112
|
-
break;
|
|
113
|
-
case 'Escape':
|
|
114
|
-
e.preventDefault();
|
|
115
|
-
e.stopPropagation();
|
|
116
|
-
props.onCancel();
|
|
117
|
-
break;
|
|
118
|
-
}
|
|
119
|
-
};
|
|
120
|
-
return () => {
|
|
121
|
-
if (props.editorType === 'checkbox') {
|
|
122
|
-
const checked = !!props.value;
|
|
123
|
-
return h('div', { style: { ...editorWrapperStyle, justifyContent: 'center' } }, renderCheckbox({
|
|
124
|
-
checked,
|
|
125
|
-
onChange: (c) => props.onCommit(c),
|
|
126
|
-
onCancel: props.onCancel,
|
|
127
|
-
}));
|
|
128
|
-
}
|
|
129
|
-
if (props.editorType === 'select') {
|
|
130
|
-
const values = props.column.cellEditorParams?.values ?? [];
|
|
131
|
-
return h('div', {
|
|
132
|
-
ref: (el) => { selectWrapperRef.value = el; },
|
|
133
|
-
tabindex: 0,
|
|
134
|
-
style: { ...editorWrapperStyle, position: 'relative' },
|
|
135
|
-
onKeydown: handleSelectKeyDown,
|
|
136
|
-
}, [
|
|
137
|
-
h('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', width: '100%', cursor: 'pointer', fontSize: '13px', color: 'inherit' } }, [
|
|
138
|
-
h('span', getDisplayText(props.value)),
|
|
139
|
-
h('span', { style: { marginLeft: '4px', fontSize: '10px', opacity: '0.5' } }, '\u25BE'),
|
|
140
|
-
]),
|
|
141
|
-
h('div', {
|
|
142
|
-
ref: (el) => { selectDropdownRef.value = el; },
|
|
143
|
-
role: 'listbox',
|
|
144
|
-
style: { position: 'absolute', top: '100%', left: '0', right: '0', maxHeight: '200px', overflowY: 'auto', background: 'var(--ogrid-bg, #fff)', border: '1px solid var(--ogrid-border, rgba(0,0,0,0.12))', zIndex: '10', boxShadow: '0 4px 16px rgba(0,0,0,0.2)' },
|
|
145
|
-
}, values.map((v, i) => h('div', {
|
|
146
|
-
key: String(v),
|
|
147
|
-
role: 'option',
|
|
148
|
-
'aria-selected': i === highlightedIndex.value,
|
|
149
|
-
onClick: () => props.onCommit(v),
|
|
150
|
-
style: { padding: '6px 8px', cursor: 'pointer', color: 'var(--ogrid-fg, #242424)', ...(i === highlightedIndex.value ? { background: 'var(--ogrid-bg-hover, #e8f0fe)' } : {}) },
|
|
151
|
-
}, getDisplayText(v)))),
|
|
152
|
-
]);
|
|
153
|
-
}
|
|
154
|
-
if (props.editorType === 'date') {
|
|
155
|
-
let dateStr = '';
|
|
156
|
-
if (localValue.value) {
|
|
157
|
-
const d = new Date(String(localValue.value));
|
|
158
|
-
if (!Number.isNaN(d.getTime())) {
|
|
159
|
-
dateStr = d.toISOString().slice(0, 10);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
return h('div', { style: editorWrapperStyle }, renderDatePicker({
|
|
163
|
-
value: dateStr,
|
|
164
|
-
onChange: (val) => props.onCommit(val),
|
|
165
|
-
onCancel: props.onCancel,
|
|
166
|
-
}));
|
|
167
|
-
}
|
|
168
|
-
// Default: text editor
|
|
169
|
-
return h('div', { style: editorWrapperStyle }, h('input', {
|
|
170
|
-
ref: (el) => { inputRef.value = el; },
|
|
171
|
-
type: 'text',
|
|
172
|
-
value: localValue.value != null ? String(localValue.value) : '',
|
|
173
|
-
style: { width: '100%', height: '100%', border: 'none', outline: 'none', padding: '0 4px', fontSize: 'inherit', boxSizing: 'border-box' },
|
|
174
|
-
onInput: (e) => { localValue.value = e.target.value; },
|
|
175
|
-
onKeydown: (e) => {
|
|
176
|
-
if (e.key === 'Enter') {
|
|
177
|
-
e.preventDefault();
|
|
178
|
-
props.onCommit(localValue.value);
|
|
179
|
-
}
|
|
180
|
-
if (e.key === 'Escape') {
|
|
181
|
-
e.preventDefault();
|
|
182
|
-
props.onCancel();
|
|
183
|
-
}
|
|
184
|
-
if (e.key === 'Tab') {
|
|
185
|
-
e.preventDefault();
|
|
186
|
-
props.onCommit(localValue.value);
|
|
187
|
-
}
|
|
188
|
-
},
|
|
189
|
-
onBlur: () => props.onCommit(localValue.value),
|
|
190
|
-
}));
|
|
191
|
-
};
|
|
192
|
-
},
|
|
193
|
-
});
|
|
194
|
-
}
|
|
@@ -1,383 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared OGrid factory for Vue UI packages.
|
|
3
|
-
*
|
|
4
|
-
* Both vue-vuetify and vue-primevue OGrid components are 100% identical —
|
|
5
|
-
* they only differ in which DataGridTable, ColumnChooser, and PaginationControls
|
|
6
|
-
* components they use. This factory extracts all shared logic into one place.
|
|
7
|
-
*/
|
|
8
|
-
import { defineComponent, h, ref, onMounted, onUnmounted, computed } from 'vue';
|
|
9
|
-
import { useOGrid, } from '../composables';
|
|
10
|
-
// --- SideBar constants and styles ---
|
|
11
|
-
const PANEL_WIDTH = 240;
|
|
12
|
-
const TAB_WIDTH = 36;
|
|
13
|
-
const PANEL_LABELS = {
|
|
14
|
-
columns: 'Columns',
|
|
15
|
-
filters: 'Filters',
|
|
16
|
-
};
|
|
17
|
-
const PANEL_ICONS = {
|
|
18
|
-
columns: '\u2261', // hamburger icon
|
|
19
|
-
filters: '\u2A65', // filter icon
|
|
20
|
-
};
|
|
21
|
-
/** Render the SideBar inline (tab strip + panel content). */
|
|
22
|
-
function renderSideBar(sb) {
|
|
23
|
-
const isOpen = sb.activePanel !== null;
|
|
24
|
-
const position = sb.position ?? 'right';
|
|
25
|
-
const tabStripStyle = {
|
|
26
|
-
display: 'flex',
|
|
27
|
-
flexDirection: 'column',
|
|
28
|
-
width: `${TAB_WIDTH}px`,
|
|
29
|
-
background: 'var(--ogrid-header-bg, #f5f5f5)',
|
|
30
|
-
...(position === 'right'
|
|
31
|
-
? { borderLeft: '1px solid var(--ogrid-border, #e0e0e0)' }
|
|
32
|
-
: { borderRight: '1px solid var(--ogrid-border, #e0e0e0)' }),
|
|
33
|
-
};
|
|
34
|
-
const tabStrip = h('div', { style: tabStripStyle, role: 'tablist', 'aria-label': 'Side bar tabs' }, sb.panels.map((panel) => h('button', {
|
|
35
|
-
key: panel,
|
|
36
|
-
role: 'tab',
|
|
37
|
-
'aria-selected': sb.activePanel === panel,
|
|
38
|
-
'aria-label': PANEL_LABELS[panel],
|
|
39
|
-
title: PANEL_LABELS[panel],
|
|
40
|
-
onClick: () => sb.onPanelChange(sb.activePanel === panel ? null : panel),
|
|
41
|
-
style: {
|
|
42
|
-
width: `${TAB_WIDTH}px`,
|
|
43
|
-
height: `${TAB_WIDTH}px`,
|
|
44
|
-
border: 'none',
|
|
45
|
-
cursor: 'pointer',
|
|
46
|
-
color: 'var(--ogrid-fg, #242424)',
|
|
47
|
-
fontSize: '14px',
|
|
48
|
-
display: 'flex',
|
|
49
|
-
alignItems: 'center',
|
|
50
|
-
justifyContent: 'center',
|
|
51
|
-
background: sb.activePanel === panel ? 'var(--ogrid-bg, #fff)' : 'transparent',
|
|
52
|
-
fontWeight: sb.activePanel === panel ? 'bold' : 'normal',
|
|
53
|
-
},
|
|
54
|
-
}, PANEL_ICONS[panel])));
|
|
55
|
-
let panelContent = null;
|
|
56
|
-
if (isOpen && sb.activePanel) {
|
|
57
|
-
const panelContainerStyle = {
|
|
58
|
-
width: `${PANEL_WIDTH}px`,
|
|
59
|
-
display: 'flex',
|
|
60
|
-
flexDirection: 'column',
|
|
61
|
-
overflow: 'hidden',
|
|
62
|
-
background: 'var(--ogrid-bg, #fff)',
|
|
63
|
-
color: 'var(--ogrid-fg, #242424)',
|
|
64
|
-
...(position === 'right'
|
|
65
|
-
? { borderLeft: '1px solid var(--ogrid-border, #e0e0e0)' }
|
|
66
|
-
: { borderRight: '1px solid var(--ogrid-border, #e0e0e0)' }),
|
|
67
|
-
};
|
|
68
|
-
const panelBodyChildren = [];
|
|
69
|
-
if (sb.activePanel === 'columns') {
|
|
70
|
-
const allVisible = sb.columns.every((c) => sb.visibleColumns.has(c.columnId));
|
|
71
|
-
// Select All / Clear All buttons
|
|
72
|
-
panelBodyChildren.push(h('div', { style: { display: 'flex', gap: '8px', marginBottom: '8px' } }, [
|
|
73
|
-
h('button', {
|
|
74
|
-
disabled: allVisible,
|
|
75
|
-
onClick: () => {
|
|
76
|
-
const next = new Set(sb.visibleColumns);
|
|
77
|
-
sb.columns.forEach((c) => next.add(c.columnId));
|
|
78
|
-
sb.onSetVisibleColumns(next);
|
|
79
|
-
},
|
|
80
|
-
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' },
|
|
81
|
-
}, 'Select All'),
|
|
82
|
-
h('button', {
|
|
83
|
-
onClick: () => {
|
|
84
|
-
const next = new Set();
|
|
85
|
-
sb.columns.forEach((c) => {
|
|
86
|
-
if (c.required && sb.visibleColumns.has(c.columnId))
|
|
87
|
-
next.add(c.columnId);
|
|
88
|
-
});
|
|
89
|
-
sb.onSetVisibleColumns(next);
|
|
90
|
-
},
|
|
91
|
-
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' },
|
|
92
|
-
}, 'Clear All'),
|
|
93
|
-
]));
|
|
94
|
-
// Column checkboxes
|
|
95
|
-
sb.columns.forEach((col) => {
|
|
96
|
-
panelBodyChildren.push(h('label', { key: col.columnId, style: { display: 'flex', alignItems: 'center', gap: '6px', padding: '2px 0', cursor: 'pointer' } }, [
|
|
97
|
-
h('input', {
|
|
98
|
-
type: 'checkbox',
|
|
99
|
-
checked: sb.visibleColumns.has(col.columnId),
|
|
100
|
-
disabled: col.required,
|
|
101
|
-
onChange: (e) => sb.onVisibilityChange(col.columnId, e.target.checked),
|
|
102
|
-
}),
|
|
103
|
-
h('span', null, col.name),
|
|
104
|
-
]));
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
if (sb.activePanel === 'filters') {
|
|
108
|
-
if (sb.filterableColumns.length === 0) {
|
|
109
|
-
panelBodyChildren.push(h('div', { style: { color: 'var(--ogrid-muted, #999)', fontStyle: 'italic' } }, 'No filterable columns'));
|
|
110
|
-
}
|
|
111
|
-
else {
|
|
112
|
-
sb.filterableColumns.forEach((col) => {
|
|
113
|
-
const filterKey = col.filterField;
|
|
114
|
-
const groupChildren = [
|
|
115
|
-
h('div', { style: { fontWeight: '500', marginBottom: '4px', fontSize: '13px' } }, col.name),
|
|
116
|
-
];
|
|
117
|
-
if (col.filterType === 'text') {
|
|
118
|
-
const filterEntry = sb.filters[filterKey];
|
|
119
|
-
const currentVal = filterEntry?.type === 'text' ? filterEntry.value : '';
|
|
120
|
-
groupChildren.push(h('input', {
|
|
121
|
-
type: 'text',
|
|
122
|
-
value: currentVal,
|
|
123
|
-
onInput: (e) => {
|
|
124
|
-
const val = e.target.value;
|
|
125
|
-
sb.onFilterChange(filterKey, val ? { type: 'text', value: val } : undefined);
|
|
126
|
-
},
|
|
127
|
-
placeholder: `Filter ${col.name}...`,
|
|
128
|
-
'aria-label': `Filter ${col.name}`,
|
|
129
|
-
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' },
|
|
130
|
-
}));
|
|
131
|
-
}
|
|
132
|
-
if (col.filterType === 'multiSelect') {
|
|
133
|
-
const options = sb.filterOptions[filterKey] ?? [];
|
|
134
|
-
const msChildren = options.map((opt) => {
|
|
135
|
-
const msFilter = sb.filters[filterKey];
|
|
136
|
-
const selected = msFilter?.type === 'multiSelect' ? msFilter.value.includes(opt) : false;
|
|
137
|
-
return h('label', { key: opt, style: { display: 'flex', alignItems: 'center', gap: '4px', padding: '1px 0', cursor: 'pointer', fontSize: '13px' } }, [
|
|
138
|
-
h('input', {
|
|
139
|
-
type: 'checkbox',
|
|
140
|
-
checked: selected,
|
|
141
|
-
onChange: (e) => {
|
|
142
|
-
const curFilter = sb.filters[filterKey];
|
|
143
|
-
const current = curFilter?.type === 'multiSelect' ? curFilter.value : [];
|
|
144
|
-
const next = e.target.checked
|
|
145
|
-
? [...current, opt]
|
|
146
|
-
: current.filter((v) => v !== opt);
|
|
147
|
-
sb.onFilterChange(filterKey, next.length > 0 ? { type: 'multiSelect', value: next } : undefined);
|
|
148
|
-
},
|
|
149
|
-
}),
|
|
150
|
-
h('span', null, opt),
|
|
151
|
-
]);
|
|
152
|
-
});
|
|
153
|
-
groupChildren.push(h('div', { style: { maxHeight: '120px', overflowY: 'auto' }, role: 'group', 'aria-label': `${col.name} options` }, msChildren));
|
|
154
|
-
}
|
|
155
|
-
if (col.filterType === 'date') {
|
|
156
|
-
const dateFilter = sb.filters[filterKey];
|
|
157
|
-
const existingValue = dateFilter?.type === 'date' ? dateFilter.value : { from: undefined, to: undefined };
|
|
158
|
-
groupChildren.push(h('div', { style: { display: 'flex', flexDirection: 'column', gap: '4px' } }, [
|
|
159
|
-
h('label', { style: { display: 'flex', alignItems: 'center', gap: '4px', fontSize: '12px' } }, [
|
|
160
|
-
'From:',
|
|
161
|
-
h('input', {
|
|
162
|
-
type: 'date',
|
|
163
|
-
value: existingValue.from ?? '',
|
|
164
|
-
onInput: (e) => {
|
|
165
|
-
const from = e.target.value || undefined;
|
|
166
|
-
const to = existingValue.to;
|
|
167
|
-
sb.onFilterChange(filterKey, from || to ? { type: 'date', value: { from, to } } : undefined);
|
|
168
|
-
},
|
|
169
|
-
'aria-label': `${col.name} from date`,
|
|
170
|
-
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' },
|
|
171
|
-
}),
|
|
172
|
-
]),
|
|
173
|
-
h('label', { style: { display: 'flex', alignItems: 'center', gap: '4px', fontSize: '12px' } }, [
|
|
174
|
-
'To:',
|
|
175
|
-
h('input', {
|
|
176
|
-
type: 'date',
|
|
177
|
-
value: existingValue.to ?? '',
|
|
178
|
-
onInput: (e) => {
|
|
179
|
-
const to = e.target.value || undefined;
|
|
180
|
-
const from = existingValue.from;
|
|
181
|
-
sb.onFilterChange(filterKey, from || to ? { type: 'date', value: { from, to } } : undefined);
|
|
182
|
-
},
|
|
183
|
-
'aria-label': `${col.name} to date`,
|
|
184
|
-
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' },
|
|
185
|
-
}),
|
|
186
|
-
]),
|
|
187
|
-
]));
|
|
188
|
-
}
|
|
189
|
-
panelBodyChildren.push(h('div', { key: col.columnId, style: { marginBottom: '12px' } }, groupChildren));
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
panelContent = h('div', { role: 'tabpanel', 'aria-label': PANEL_LABELS[sb.activePanel], style: panelContainerStyle }, [
|
|
194
|
-
// Panel header
|
|
195
|
-
h('div', {
|
|
196
|
-
style: {
|
|
197
|
-
display: 'flex',
|
|
198
|
-
justifyContent: 'space-between',
|
|
199
|
-
alignItems: 'center',
|
|
200
|
-
padding: '8px 12px',
|
|
201
|
-
borderBottom: '1px solid var(--ogrid-border, #e0e0e0)',
|
|
202
|
-
fontWeight: '600',
|
|
203
|
-
},
|
|
204
|
-
}, [
|
|
205
|
-
h('span', null, PANEL_LABELS[sb.activePanel]),
|
|
206
|
-
h('button', {
|
|
207
|
-
onClick: () => sb.onPanelChange(null),
|
|
208
|
-
style: { border: 'none', background: 'transparent', cursor: 'pointer', fontSize: '16px', color: 'var(--ogrid-fg, #242424)' },
|
|
209
|
-
'aria-label': 'Close panel',
|
|
210
|
-
}, '\u00D7'),
|
|
211
|
-
]),
|
|
212
|
-
// Panel body
|
|
213
|
-
h('div', { style: { flex: '1', overflowY: 'auto', padding: '8px 12px' } }, panelBodyChildren),
|
|
214
|
-
]);
|
|
215
|
-
}
|
|
216
|
-
const children = [];
|
|
217
|
-
if (position === 'left') {
|
|
218
|
-
children.push(tabStrip);
|
|
219
|
-
if (panelContent)
|
|
220
|
-
children.push(panelContent);
|
|
221
|
-
}
|
|
222
|
-
else {
|
|
223
|
-
if (panelContent)
|
|
224
|
-
children.push(panelContent);
|
|
225
|
-
children.push(tabStrip);
|
|
226
|
-
}
|
|
227
|
-
return h('div', {
|
|
228
|
-
style: { display: 'flex', flexDirection: 'row', flexShrink: '0' },
|
|
229
|
-
role: 'complementary',
|
|
230
|
-
'aria-label': 'Side bar',
|
|
231
|
-
}, children);
|
|
232
|
-
}
|
|
233
|
-
/**
|
|
234
|
-
* Creates an OGrid component with framework-specific UI bindings.
|
|
235
|
-
* All orchestration logic, sidebar, toolbar, and layout are shared.
|
|
236
|
-
*/
|
|
237
|
-
export function createOGrid(ui) {
|
|
238
|
-
return defineComponent({
|
|
239
|
-
name: 'OGrid',
|
|
240
|
-
props: {
|
|
241
|
-
gridProps: { type: Object, required: true },
|
|
242
|
-
},
|
|
243
|
-
setup(props, { expose }) {
|
|
244
|
-
const propsRef = computed(() => props.gridProps);
|
|
245
|
-
const { dataGridProps, pagination, columnChooser, layout, api } = useOGrid(propsRef);
|
|
246
|
-
// Expose the ref container so parent always gets the latest API value
|
|
247
|
-
expose({ api });
|
|
248
|
-
// Fullscreen state
|
|
249
|
-
const isFullScreen = ref(false);
|
|
250
|
-
const toggleFullScreen = () => { isFullScreen.value = !isFullScreen.value; };
|
|
251
|
-
// ESC key to exit fullscreen
|
|
252
|
-
const handleEscKey = (e) => {
|
|
253
|
-
if (e.key === 'Escape' && isFullScreen.value)
|
|
254
|
-
isFullScreen.value = false;
|
|
255
|
-
};
|
|
256
|
-
onMounted(() => { document.addEventListener('keydown', handleEscKey); });
|
|
257
|
-
onUnmounted(() => { document.removeEventListener('keydown', handleEscKey); });
|
|
258
|
-
return () => {
|
|
259
|
-
const sideBar = layout.value.sideBarProps;
|
|
260
|
-
const hasSideBar = sideBar != null;
|
|
261
|
-
const sideBarPosition = sideBar?.position ?? 'right';
|
|
262
|
-
// Toolbar
|
|
263
|
-
const toolbarChildren = [];
|
|
264
|
-
if (layout.value.toolbar) {
|
|
265
|
-
toolbarChildren.push(layout.value.toolbar);
|
|
266
|
-
}
|
|
267
|
-
// Fullscreen toggle button
|
|
268
|
-
const showFullScreen = layout.value.fullScreen === true;
|
|
269
|
-
const fullscreenButton = showFullScreen
|
|
270
|
-
? h('button', {
|
|
271
|
-
type: 'button',
|
|
272
|
-
title: isFullScreen.value ? 'Exit fullscreen' : 'Fullscreen',
|
|
273
|
-
'aria-label': isFullScreen.value ? 'Exit fullscreen' : 'Fullscreen',
|
|
274
|
-
onClick: toggleFullScreen,
|
|
275
|
-
style: {
|
|
276
|
-
background: 'none',
|
|
277
|
-
border: '1px solid var(--ogrid-border, rgba(0,0,0,0.12))',
|
|
278
|
-
borderRadius: '4px',
|
|
279
|
-
padding: '4px 6px',
|
|
280
|
-
cursor: 'pointer',
|
|
281
|
-
display: 'flex',
|
|
282
|
-
alignItems: 'center',
|
|
283
|
-
justifyContent: 'center',
|
|
284
|
-
color: 'var(--ogrid-fg, rgba(0,0,0,0.87))',
|
|
285
|
-
},
|
|
286
|
-
}, [
|
|
287
|
-
isFullScreen.value
|
|
288
|
-
? h('svg', { width: 16, height: 16, viewBox: '0 0 16 16', fill: 'none', stroke: 'currentColor', 'stroke-width': '1.5', 'stroke-linecap': 'round', 'stroke-linejoin': 'round', innerHTML: '<polyline points="4 10 0 10 0 14"/><polyline points="12 6 16 6 16 2"/><line x1="0" y1="10" x2="4" y2="6"/><line x1="16" y1="6" x2="12" y2="10"/>' })
|
|
289
|
-
: h('svg', { width: 16, height: 16, viewBox: '0 0 16 16', fill: 'none', stroke: 'currentColor', 'stroke-width': '1.5', 'stroke-linecap': 'round', 'stroke-linejoin': 'round', innerHTML: '<polyline points="10 2 14 2 14 6"/><polyline points="6 14 2 14 2 10"/><line x1="14" y1="2" x2="10" y2="6"/><line x1="2" y1="14" x2="6" y2="10"/>' }),
|
|
290
|
-
])
|
|
291
|
-
: null;
|
|
292
|
-
// ColumnChooser in toolbar
|
|
293
|
-
const toolbarEnd = columnChooser.value.placement === 'toolbar'
|
|
294
|
-
? h(ui.ColumnChooser, {
|
|
295
|
-
columns: columnChooser.value.columns,
|
|
296
|
-
visibleColumns: columnChooser.value.visibleColumns,
|
|
297
|
-
onVisibilityChange: columnChooser.value.onVisibilityChange,
|
|
298
|
-
})
|
|
299
|
-
: null;
|
|
300
|
-
// Pagination
|
|
301
|
-
const paginationNode = h(ui.PaginationControls, {
|
|
302
|
-
currentPage: pagination.value.page,
|
|
303
|
-
pageSize: pagination.value.pageSize,
|
|
304
|
-
totalCount: pagination.value.displayTotalCount,
|
|
305
|
-
onPageChange: pagination.value.setPage,
|
|
306
|
-
onPageSizeChange: (size) => {
|
|
307
|
-
pagination.value.setPageSize(size);
|
|
308
|
-
},
|
|
309
|
-
pageSizeOptions: pagination.value.pageSizeOptions,
|
|
310
|
-
entityLabelPlural: pagination.value.entityLabelPlural,
|
|
311
|
-
});
|
|
312
|
-
// Grid content area
|
|
313
|
-
const gridChild = h('div', {
|
|
314
|
-
style: { flex: '1', minWidth: '0', minHeight: '0', display: 'flex', flexDirection: 'column' },
|
|
315
|
-
}, [
|
|
316
|
-
h(ui.DataGridTable, {
|
|
317
|
-
gridProps: dataGridProps.value,
|
|
318
|
-
}),
|
|
319
|
-
]);
|
|
320
|
-
// Main content area (sidebar + grid)
|
|
321
|
-
const mainAreaChildren = [];
|
|
322
|
-
if (hasSideBar && sideBarPosition === 'left') {
|
|
323
|
-
mainAreaChildren.push(renderSideBar(sideBar));
|
|
324
|
-
}
|
|
325
|
-
mainAreaChildren.push(gridChild);
|
|
326
|
-
if (hasSideBar && sideBarPosition !== 'left') {
|
|
327
|
-
mainAreaChildren.push(renderSideBar(sideBar));
|
|
328
|
-
}
|
|
329
|
-
const hasToolbar = toolbarChildren.length > 0 || toolbarEnd != null || fullscreenButton != null;
|
|
330
|
-
const rootStyle = isFullScreen.value
|
|
331
|
-
? { position: 'fixed', inset: '0', zIndex: 9999, display: 'flex', flexDirection: 'column', background: 'var(--ogrid-bg, #fff)' }
|
|
332
|
-
: { display: 'flex', flexDirection: 'column', border: '1px solid var(--ogrid-border, rgba(0,0,0,0.12))', borderRadius: '4px', overflow: 'hidden' };
|
|
333
|
-
const containerStyle = isFullScreen.value
|
|
334
|
-
? { display: 'flex', flexDirection: 'column', flex: '1', minHeight: '0', overflow: 'hidden', background: 'var(--ogrid-bg, #fff)' }
|
|
335
|
-
: undefined;
|
|
336
|
-
return h('div', {
|
|
337
|
-
class: layout.value.className,
|
|
338
|
-
style: rootStyle,
|
|
339
|
-
}, [
|
|
340
|
-
// Inner container (for fullscreen: no border/radius)
|
|
341
|
-
h('div', { style: containerStyle ?? {} }, [
|
|
342
|
-
// Toolbar strip
|
|
343
|
-
...(hasToolbar ? [
|
|
344
|
-
h('div', {
|
|
345
|
-
style: {
|
|
346
|
-
display: 'flex',
|
|
347
|
-
alignItems: 'center',
|
|
348
|
-
justifyContent: 'space-between',
|
|
349
|
-
padding: '8px 12px',
|
|
350
|
-
borderBottom: '1px solid var(--ogrid-border, rgba(0,0,0,0.12))',
|
|
351
|
-
gap: '8px',
|
|
352
|
-
},
|
|
353
|
-
}, [
|
|
354
|
-
h('div', { style: { display: 'flex', alignItems: 'center', gap: '8px', flex: '1' } }, toolbarChildren),
|
|
355
|
-
h('div', { style: { display: 'flex', alignItems: 'center', gap: '8px' } }, [
|
|
356
|
-
...(toolbarEnd ? [toolbarEnd] : []),
|
|
357
|
-
...(fullscreenButton ? [fullscreenButton] : []),
|
|
358
|
-
]),
|
|
359
|
-
]),
|
|
360
|
-
] : []),
|
|
361
|
-
// Below toolbar strip
|
|
362
|
-
...(layout.value.toolbarBelow ? [
|
|
363
|
-
h('div', {
|
|
364
|
-
style: { padding: '8px 12px', borderBottom: '1px solid var(--ogrid-border, rgba(0,0,0,0.12))' },
|
|
365
|
-
}, [layout.value.toolbarBelow]),
|
|
366
|
-
] : []),
|
|
367
|
-
// Main content area (sidebar + grid)
|
|
368
|
-
h('div', { style: { display: 'flex', flex: '1', minHeight: '0' } }, mainAreaChildren),
|
|
369
|
-
// Footer strip (pagination)
|
|
370
|
-
h('div', {
|
|
371
|
-
style: {
|
|
372
|
-
display: 'flex',
|
|
373
|
-
alignItems: 'center',
|
|
374
|
-
padding: '8px 0',
|
|
375
|
-
borderTop: '1px solid var(--ogrid-border, rgba(0,0,0,0.12))',
|
|
376
|
-
},
|
|
377
|
-
}, [paginationNode]),
|
|
378
|
-
]),
|
|
379
|
-
]);
|
|
380
|
-
};
|
|
381
|
-
},
|
|
382
|
-
});
|
|
383
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
// Main orchestration composables
|
|
2
|
-
export { useOGrid } from './useOGrid';
|
|
3
|
-
export { useDataGridState } from './useDataGridState';
|
|
4
|
-
// Feature composables
|
|
5
|
-
export { useActiveCell } from './useActiveCell';
|
|
6
|
-
export { useCellEditing } from './useCellEditing';
|
|
7
|
-
export { useCellSelection } from './useCellSelection';
|
|
8
|
-
export { useClipboard } from './useClipboard';
|
|
9
|
-
export { useRowSelection } from './useRowSelection';
|
|
10
|
-
export { useKeyboardNavigation } from './useKeyboardNavigation';
|
|
11
|
-
export { useFillHandle } from './useFillHandle';
|
|
12
|
-
export { useUndoRedo } from './useUndoRedo';
|
|
13
|
-
export { useContextMenu } from './useContextMenu';
|
|
14
|
-
export { useColumnResize } from './useColumnResize';
|
|
15
|
-
export { useFilterOptions } from './useFilterOptions';
|
|
16
|
-
export { useDebounce, useDebouncedCallback } from './useDebounce';
|
|
17
|
-
export { useLatestRef } from './useLatestRef';
|
|
18
|
-
export { useTableLayout } from './useTableLayout';
|
|
19
|
-
// Headless state composables
|
|
20
|
-
export { useColumnHeaderFilterState } from './useColumnHeaderFilterState';
|
|
21
|
-
export { useTextFilterState } from './useTextFilterState';
|
|
22
|
-
export { useMultiSelectFilterState } from './useMultiSelectFilterState';
|
|
23
|
-
export { usePeopleFilterState } from './usePeopleFilterState';
|
|
24
|
-
export { useDateFilterState } from './useDateFilterState';
|
|
25
|
-
export { useColumnChooserState } from './useColumnChooserState';
|
|
26
|
-
export { useInlineCellEditorState } from './useInlineCellEditorState';
|
|
27
|
-
export { useRichSelectState } from './useRichSelectState';
|
|
28
|
-
export { useSideBarState } from './useSideBarState';
|
|
29
|
-
export { useColumnReorder } from './useColumnReorder';
|
|
30
|
-
export { useVirtualScroll } from './useVirtualScroll';
|
|
31
|
-
export { useColumnPinning } from './useColumnPinning';
|
|
32
|
-
export { useColumnHeaderMenuState } from './useColumnHeaderMenuState';
|
|
33
|
-
export { useDataGridTableSetup } from './useDataGridTableSetup';
|