@alaarab/ogrid-vue-vuetify 2.0.14 → 2.0.16

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.
@@ -302,6 +302,23 @@
302
302
  overflow: visible;
303
303
  }
304
304
 
305
+ /* === Editing cell wrapper === */
306
+
307
+ .ogrid-editing-cell {
308
+ width: 100%;
309
+ height: 100%;
310
+ display: flex;
311
+ align-items: center;
312
+ box-sizing: border-box;
313
+ outline: 2px solid var(--ogrid-selection-color, #217346);
314
+ outline-offset: -1px;
315
+ z-index: 2;
316
+ position: relative;
317
+ background: var(--ogrid-bg, #fff);
318
+ overflow: visible;
319
+ padding: 0;
320
+ }
321
+
305
322
  /* === Fill handle === */
306
323
 
307
324
  .ogrid-fill-handle {
@@ -1,5 +1,5 @@
1
1
  import { defineComponent, ref, h, onMounted, nextTick, watch } from 'vue';
2
- import { VCheckbox, VSelect } from 'vuetify/components';
2
+ import { VCheckbox } from 'vuetify/components';
3
3
  const editorWrapperStyle = {
4
4
  width: '100%',
5
5
  height: '100%',
@@ -21,16 +21,102 @@ export const InlineCellEditor = defineComponent({
21
21
  },
22
22
  setup(props) {
23
23
  const inputRef = ref(null);
24
+ const selectWrapperRef = ref(null);
25
+ const selectDropdownRef = ref(null);
24
26
  const localValue = ref(props.value);
27
+ const highlightedIndex = ref(0);
25
28
  // Auto-focus on mount
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
+ };
26
52
  onMounted(() => {
27
53
  nextTick(() => {
54
+ if (selectWrapperRef.value) {
55
+ selectWrapperRef.value.focus();
56
+ positionDropdown();
57
+ return;
58
+ }
28
59
  inputRef.value?.focus();
29
60
  inputRef.value?.select();
30
61
  });
31
62
  });
32
63
  // Sync local value when prop changes
33
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
+ };
34
120
  return () => {
35
121
  if (props.editorType === 'checkbox') {
36
122
  const checked = !!props.value;
@@ -49,22 +135,28 @@ export const InlineCellEditor = defineComponent({
49
135
  }
50
136
  if (props.editorType === 'select') {
51
137
  const values = props.column.cellEditorParams?.values ?? [];
52
- return h('div', { style: editorWrapperStyle }, h(VSelect, {
53
- modelValue: localValue.value != null ? String(localValue.value) : '',
54
- items: values.map((v) => String(v)),
55
- density: 'compact',
56
- hideDetails: true,
57
- variant: 'outlined',
58
- autofocus: true,
59
- style: { minWidth: '0', flex: '1' },
60
- 'onUpdate:modelValue': (v) => props.onCommit(v),
61
- onKeydown: (e) => {
62
- if (e.key === 'Escape') {
63
- e.preventDefault();
64
- props.onCancel();
65
- }
66
- },
67
- }));
138
+ return h('div', {
139
+ ref: (el) => { selectWrapperRef.value = el; },
140
+ tabindex: 0,
141
+ style: { ...editorWrapperStyle, position: 'relative' },
142
+ onKeydown: handleSelectKeyDown,
143
+ }, [
144
+ h('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', width: '100%', cursor: 'pointer', fontSize: '13px', color: 'inherit' } }, [
145
+ h('span', getDisplayText(props.value)),
146
+ h('span', { style: { marginLeft: '4px', fontSize: '10px', opacity: '0.5' } }, '\u25BE'),
147
+ ]),
148
+ h('div', {
149
+ ref: (el) => { selectDropdownRef.value = el; },
150
+ role: 'listbox',
151
+ 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)' },
152
+ }, values.map((v, i) => h('div', {
153
+ key: String(v),
154
+ role: 'option',
155
+ 'aria-selected': i === highlightedIndex.value,
156
+ onClick: () => props.onCommit(v),
157
+ style: { padding: '6px 8px', cursor: 'pointer', color: 'var(--ogrid-fg, #242424)', ...(i === highlightedIndex.value ? { background: 'var(--ogrid-bg-hover, #e8f0fe)' } : {}) },
158
+ }, getDisplayText(v)))),
159
+ ]);
68
160
  }
69
161
  if (props.editorType === 'date') {
70
162
  let dateStr = '';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alaarab/ogrid-vue-vuetify",
3
- "version": "2.0.14",
3
+ "version": "2.0.16",
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",
@@ -37,7 +37,7 @@
37
37
  "node": ">=18"
38
38
  },
39
39
  "dependencies": {
40
- "@alaarab/ogrid-vue": "2.0.14"
40
+ "@alaarab/ogrid-vue": "2.0.15"
41
41
  },
42
42
  "peerDependencies": {
43
43
  "vue": "^3.3.0",