@alaarab/ogrid-vue-vuetify 2.0.19 → 2.0.22

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.
@@ -141,23 +141,6 @@ export const ColumnHeaderFilter = defineComponent({
141
141
  })),
142
142
  // Sort + filter buttons
143
143
  h('div', { style: { display: 'flex', alignItems: 'center', marginLeft: '4px', flexShrink: '0' } }, [
144
- // Sort button
145
- ...(props.onSort ? [
146
- h(_VBtn, {
147
- icon: true,
148
- size: 'x-small',
149
- variant: props.isSorted ? 'tonal' : 'text',
150
- color: props.isSorted ? 'primary' : 'default',
151
- 'aria-label': `Sort by ${props.columnName}`,
152
- title: props.isSorted ? (props.isSortedDescending ? 'Sorted descending' : 'Sorted ascending') : 'Sort',
153
- onClick: state.handlers.handleSortClick,
154
- style: {
155
- opacity: props.isSorted ? '1' : '0.7',
156
- },
157
- }, () => h(_VIcon, { size: '16' }, () => props.isSorted
158
- ? (props.isSortedDescending ? 'mdi-arrow-down' : 'mdi-arrow-up')
159
- : 'mdi-swap-vertical')),
160
- ] : []),
161
144
  // Filter icon + menu
162
145
  ...(props.filterType !== 'none' ? [
163
146
  h(_VMenu, {
@@ -1,83 +1,13 @@
1
1
  /* OGrid Vue Vuetify - DataGridTable styles */
2
2
 
3
- /* ─── OGrid Theme Variables ─── */
4
- :root {
5
- --ogrid-bg: #ffffff;
6
- --ogrid-fg: rgba(0, 0, 0, 0.87);
7
- --ogrid-fg-secondary: rgba(0, 0, 0, 0.6);
8
- --ogrid-fg-muted: rgba(0, 0, 0, 0.5);
9
- --ogrid-border: rgba(0, 0, 0, 0.12);
10
- --ogrid-header-bg: rgba(0, 0, 0, 0.04);
11
- --ogrid-hover-bg: rgba(0, 0, 0, 0.04);
12
- --ogrid-selected-row-bg: #e6f0fb;
13
- --ogrid-active-cell-bg: rgba(0, 0, 0, 0.02);
14
- --ogrid-range-bg: rgba(33, 115, 70, 0.12);
15
- --ogrid-accent: #0078d4;
16
- --ogrid-selection-color: #217346;
17
- --ogrid-loading-overlay: rgba(255, 255, 255, 0.7);
18
- }
19
-
20
- @media (prefers-color-scheme: dark) {
21
- :root:not([data-theme="light"]) {
22
- --ogrid-bg: #1e1e1e;
23
- --ogrid-fg: rgba(255, 255, 255, 0.87);
24
- --ogrid-fg-secondary: rgba(255, 255, 255, 0.6);
25
- --ogrid-fg-muted: rgba(255, 255, 255, 0.5);
26
- --ogrid-border: rgba(255, 255, 255, 0.12);
27
- --ogrid-header-bg: rgba(255, 255, 255, 0.06);
28
- --ogrid-hover-bg: rgba(255, 255, 255, 0.08);
29
- --ogrid-selected-row-bg: #1a3a5c;
30
- --ogrid-active-cell-bg: rgba(255, 255, 255, 0.06);
31
- --ogrid-range-bg: rgba(46, 160, 67, 0.15);
32
- --ogrid-accent: #4da6ff;
33
- --ogrid-selection-color: #2ea043;
34
- --ogrid-loading-overlay: rgba(0, 0, 0, 0.7);
35
- }
36
- }
37
-
38
- [data-theme="dark"] {
39
- --ogrid-bg: #1e1e1e;
40
- --ogrid-fg: rgba(255, 255, 255, 0.87);
41
- --ogrid-fg-secondary: rgba(255, 255, 255, 0.6);
42
- --ogrid-fg-muted: rgba(255, 255, 255, 0.5);
43
- --ogrid-border: rgba(255, 255, 255, 0.12);
44
- --ogrid-header-bg: rgba(255, 255, 255, 0.06);
45
- --ogrid-hover-bg: rgba(255, 255, 255, 0.08);
46
- --ogrid-selected-row-bg: #1a3a5c;
47
- --ogrid-active-cell-bg: rgba(255, 255, 255, 0.06);
48
- --ogrid-range-bg: rgba(46, 160, 67, 0.15);
49
- --ogrid-accent: #4da6ff;
50
- --ogrid-selection-color: #2ea043;
51
- --ogrid-loading-overlay: rgba(0, 0, 0, 0.7);
52
- }
53
-
54
- /* Remove focus outline from scrollable wrapper (keyboard nav is handled via cell outlines) */
55
- [role="region"][tabindex="0"] {
56
- outline: none !important;
57
- }
3
+ /* Shared theme variables (light/dark mode, CSS custom properties) */
4
+ @import '@alaarab/ogrid-vue/styles/ogrid-theme.css';
58
5
 
59
- /* Cell selection highlighting - matches React implementation */
60
- .ogrid-cell-in-range {
61
- background: var(--ogrid-bg-range, rgba(33, 115, 70, 0.12)) !important;
62
- }
63
-
64
- /* Cut range highlighting */
65
- .ogrid-cell-cut {
66
- background: var(--ogrid-hover-bg) !important;
67
- opacity: 0.7;
68
- }
6
+ /* Shared layout styles (table structure, header, cells, pinning, fill handle, etc.) */
7
+ @import '@alaarab/ogrid-vue/styles/ogrid-layout.css';
69
8
 
70
- /* Drag-range highlight applied via DOM attributes during drag (bypasses Vue for performance) */
71
- [data-drag-range] {
72
- background: var(--ogrid-range-bg, rgba(33, 115, 70, 0.12)) !important;
73
- }
9
+ /* === Vuetify-specific: Accessibility focus styles === */
74
10
 
75
- /* Anchor cell during drag: white/transparent background (like Excel) */
76
- [data-drag-anchor] {
77
- background: var(--ogrid-bg) !important;
78
- }
79
-
80
- /* Accessibility: Focus visible styles */
81
11
  .v-table th:focus-visible,
82
12
  .v-table td:focus-visible {
83
13
  outline: 2px solid rgb(var(--v-theme-primary));
@@ -100,316 +30,3 @@
100
30
  outline-offset: -2px;
101
31
  z-index: 3;
102
32
  }
103
-
104
- /* === Layout === */
105
-
106
- .ogrid-outer-container {
107
- position: relative;
108
- flex: 1;
109
- min-height: 0;
110
- display: flex;
111
- flex-direction: column;
112
- background-color: var(--ogrid-bg);
113
- color: var(--ogrid-fg);
114
- }
115
-
116
- .ogrid-scroll-wrapper {
117
- display: flex;
118
- flex-direction: column;
119
- min-height: 100%;
120
- }
121
-
122
- .ogrid-table-container {
123
- position: relative;
124
- }
125
-
126
- .ogrid-table-container--loading {
127
- opacity: 0.6;
128
- }
129
-
130
- .ogrid-table {
131
- width: 100%;
132
- border-collapse: collapse;
133
- font-size: 0.875rem;
134
- background-color: var(--ogrid-bg);
135
- color: var(--ogrid-fg);
136
- }
137
-
138
- /* === Header === */
139
-
140
- .ogrid-thead {
141
- z-index: 8;
142
- background-color: var(--ogrid-header-bg);
143
- }
144
-
145
- .ogrid-header-row {
146
- background-color: var(--ogrid-header-bg);
147
- }
148
-
149
- .ogrid-header-cell {
150
- font-weight: 600;
151
- position: sticky;
152
- top: 0;
153
- background-color: var(--ogrid-header-bg);
154
- z-index: 8;
155
- }
156
-
157
- .ogrid-header-cell--pinned-left {
158
- z-index: 10;
159
- will-change: transform;
160
- }
161
-
162
- .ogrid-header-cell--pinned-right {
163
- z-index: 10;
164
- will-change: transform;
165
- }
166
-
167
- .ogrid-header-content {
168
- display: flex;
169
- align-items: center;
170
- width: 100%;
171
- }
172
-
173
- .ogrid-column-group-header {
174
- text-align: center;
175
- font-weight: 600;
176
- border-bottom: 2px solid var(--ogrid-border);
177
- padding: 6px;
178
- }
179
-
180
- .ogrid-column-menu-btn {
181
- background: none;
182
- border: none;
183
- cursor: pointer;
184
- padding: 4px 6px;
185
- font-size: 16px;
186
- color: var(--ogrid-fg-muted);
187
- line-height: 1;
188
- flex-shrink: 0;
189
- border-radius: 4px;
190
- display: inline-flex;
191
- align-items: center;
192
- justify-content: center;
193
- min-width: 24px;
194
- height: 24px;
195
- transition: background-color 0.15s;
196
- }
197
-
198
- .ogrid-column-menu-btn:hover {
199
- background: var(--ogrid-hover-bg, rgba(0, 0, 0, 0.04));
200
- color: var(--ogrid-fg);
201
- }
202
-
203
- /* === Checkbox column === */
204
-
205
- .ogrid-checkbox-header,
206
- .ogrid-checkbox-cell {
207
- text-align: center;
208
- padding: 4px;
209
- }
210
-
211
- .ogrid-checkbox-cell {
212
- padding: 0;
213
- }
214
-
215
- .ogrid-checkbox-wrapper {
216
- display: flex;
217
- align-items: center;
218
- justify-content: center;
219
- }
220
-
221
- .ogrid-checkbox-spacer {
222
- padding: 0;
223
- }
224
-
225
- /* === Row numbers === */
226
-
227
- .ogrid-row-number-header {
228
- text-align: center;
229
- font-weight: 600;
230
- background-color: var(--ogrid-header-bg);
231
- color: var(--ogrid-fg-secondary);
232
- }
233
-
234
- .ogrid-row-number-spacer {
235
- background-color: var(--ogrid-header-bg);
236
- }
237
-
238
- .ogrid-row-number-cell {
239
- text-align: center;
240
- font-weight: 600;
241
- font-variant-numeric: tabular-nums;
242
- background-color: var(--ogrid-header-bg);
243
- color: var(--ogrid-fg-secondary);
244
- }
245
-
246
- /* === Data cells === */
247
-
248
- .ogrid-data-cell {
249
- position: relative;
250
- padding: 0;
251
- height: 1px;
252
- }
253
-
254
- .ogrid-data-cell--pinned-left {
255
- position: sticky;
256
- z-index: 6;
257
- background-color: var(--ogrid-bg);
258
- will-change: transform;
259
- }
260
-
261
- .ogrid-data-cell--pinned-right {
262
- position: sticky;
263
- z-index: 6;
264
- background-color: var(--ogrid-bg);
265
- will-change: transform;
266
- }
267
-
268
- .ogrid-cell-content {
269
- width: 100%;
270
- height: 100%;
271
- display: flex;
272
- align-items: center;
273
- min-width: 0;
274
- padding: 6px 10px;
275
- box-sizing: border-box;
276
- overflow: hidden;
277
- text-overflow: ellipsis;
278
- white-space: nowrap;
279
- user-select: none;
280
- outline: none;
281
- }
282
-
283
- .ogrid-cell-content--numeric {
284
- justify-content: flex-end;
285
- text-align: right;
286
- }
287
-
288
- .ogrid-cell-content--boolean {
289
- justify-content: center;
290
- text-align: center;
291
- }
292
-
293
- .ogrid-cell-content--editable {
294
- cursor: cell;
295
- }
296
-
297
- .ogrid-cell-content--active {
298
- outline: 2px solid var(--ogrid-selection, #217346);
299
- outline-offset: -1px;
300
- z-index: 2;
301
- position: relative;
302
- overflow: visible;
303
- }
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
-
322
- /* === Fill handle === */
323
-
324
- .ogrid-fill-handle {
325
- position: absolute;
326
- right: -3px;
327
- bottom: -3px;
328
- width: 7px;
329
- height: 7px;
330
- background-color: var(--ogrid-selection, #217346);
331
- border: 1px solid var(--ogrid-bg);
332
- border-radius: 1px;
333
- cursor: crosshair;
334
- pointer-events: auto;
335
- z-index: 3;
336
- }
337
-
338
- /* === Resize handle === */
339
-
340
- .ogrid-resize-handle {
341
- position: absolute;
342
- top: 0;
343
- right: -3px;
344
- bottom: 0;
345
- width: 8px;
346
- cursor: col-resize;
347
- user-select: none;
348
- }
349
-
350
- /* === Drop indicator === */
351
-
352
- .ogrid-drop-indicator {
353
- position: absolute;
354
- top: 0;
355
- bottom: 0;
356
- width: 3px;
357
- background: var(--ogrid-primary, #217346);
358
- pointer-events: none;
359
- z-index: 100;
360
- }
361
-
362
- /* === Empty state === */
363
-
364
- .ogrid-empty-state {
365
- padding: 32px 16px;
366
- text-align: center;
367
- border-top: 1px solid var(--ogrid-border);
368
- background-color: var(--ogrid-header-bg);
369
- }
370
-
371
- .ogrid-empty-state-title {
372
- font-size: 1.25rem;
373
- font-weight: 600;
374
- margin-bottom: 8px;
375
- }
376
-
377
- .ogrid-empty-state-message {
378
- font-size: 0.875rem;
379
- color: var(--ogrid-fg-secondary);
380
- }
381
-
382
- /* === Loading overlay === */
383
-
384
- .ogrid-loading-overlay {
385
- position: absolute;
386
- inset: 0;
387
- z-index: 2;
388
- display: flex;
389
- align-items: center;
390
- justify-content: center;
391
- background-color: var(--ogrid-loading-overlay);
392
- }
393
-
394
- .ogrid-loading-inner {
395
- display: flex;
396
- flex-direction: column;
397
- align-items: center;
398
- gap: 8px;
399
- padding: 16px;
400
- background-color: var(--ogrid-bg);
401
- border: 1px solid var(--ogrid-border);
402
- border-radius: 4px;
403
- }
404
-
405
- .ogrid-loading-message {
406
- font-size: 0.875rem;
407
- color: var(--ogrid-fg-secondary);
408
- }
409
-
410
- /* === Popover editor anchor === */
411
-
412
- .ogrid-popover-anchor {
413
- min-height: 100%;
414
- min-width: 40px;
415
- }
@@ -1,216 +1,37 @@
1
- import { defineComponent, ref, h, onMounted, nextTick, watch } from 'vue';
1
+ import { h } from 'vue';
2
2
  import { VCheckbox } from 'vuetify/components';
3
- const editorWrapperStyle = {
4
- width: '100%',
5
- height: '100%',
6
- display: 'flex',
7
- alignItems: 'center',
8
- padding: '0 2px',
9
- boxSizing: 'border-box',
10
- };
11
- export const InlineCellEditor = defineComponent({
12
- name: 'InlineCellEditor',
13
- props: {
14
- value: { default: undefined },
15
- item: { type: Object, required: true },
16
- column: { type: Object, required: true },
17
- rowIndex: { type: Number, required: true },
18
- editorType: { type: String, required: true },
19
- onCommit: { type: Function, required: true },
20
- onCancel: { type: Function, required: true },
21
- },
22
- setup(props) {
23
- const inputRef = ref(null);
24
- const selectWrapperRef = ref(null);
25
- const selectDropdownRef = ref(null);
26
- const localValue = ref(props.value);
27
- const highlightedIndex = ref(0);
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`;
3
+ import { createInlineCellEditor } from '@alaarab/ogrid-vue';
4
+ export const InlineCellEditor = createInlineCellEditor({
5
+ renderCheckbox: ({ checked, onChange, onCancel }) => h(VCheckbox, {
6
+ modelValue: checked,
7
+ hideDetails: true,
8
+ density: 'compact',
9
+ 'onUpdate:modelValue': (c) => onChange(c),
10
+ onKeydown: (e) => {
11
+ if (e.key === 'Escape') {
12
+ e.preventDefault();
13
+ onCancel();
47
14
  }
48
- else {
49
- dropdown.style.top = `${rect.bottom}px`;
15
+ },
16
+ }),
17
+ renderDatePicker: ({ value, onChange, onCancel }) => h('input', {
18
+ type: 'date',
19
+ value,
20
+ style: { width: '100%', height: '100%', border: 'none', outline: 'none', padding: '0 4px', fontSize: 'inherit' },
21
+ onKeydown: (e) => {
22
+ if (e.key === 'Enter') {
23
+ e.preventDefault();
24
+ onChange(e.target.value);
50
25
  }
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;
26
+ if (e.key === 'Escape') {
27
+ e.preventDefault();
28
+ onCancel();
118
29
  }
119
- };
120
- return () => {
121
- if (props.editorType === 'checkbox') {
122
- const checked = !!props.value;
123
- return h('div', { style: { ...editorWrapperStyle, justifyContent: 'center' } }, h(VCheckbox, {
124
- modelValue: checked,
125
- hideDetails: true,
126
- density: 'compact',
127
- 'onUpdate:modelValue': (c) => props.onCommit(c),
128
- onKeydown: (e) => {
129
- if (e.key === 'Escape') {
130
- e.preventDefault();
131
- props.onCancel();
132
- }
133
- },
134
- }));
30
+ if (e.key === 'Tab') {
31
+ e.preventDefault();
32
+ onChange(e.target.value);
135
33
  }
136
- if (props.editorType === 'select') {
137
- const values = props.column.cellEditorParams?.values ?? [];
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
- ]);
160
- }
161
- if (props.editorType === 'date') {
162
- let dateStr = '';
163
- if (localValue.value) {
164
- const d = new Date(String(localValue.value));
165
- if (!Number.isNaN(d.getTime())) {
166
- dateStr = d.toISOString().slice(0, 10);
167
- }
168
- }
169
- return h('div', { style: editorWrapperStyle }, h('input', {
170
- ref: (el) => { inputRef.value = el; },
171
- type: 'date',
172
- value: dateStr,
173
- style: { width: '100%', height: '100%', border: 'none', outline: 'none', padding: '0 4px', fontSize: 'inherit' },
174
- onKeydown: (e) => {
175
- if (e.key === 'Enter') {
176
- e.preventDefault();
177
- props.onCommit(e.target.value);
178
- }
179
- if (e.key === 'Escape') {
180
- e.preventDefault();
181
- props.onCancel();
182
- }
183
- if (e.key === 'Tab') {
184
- e.preventDefault();
185
- props.onCommit(e.target.value);
186
- }
187
- },
188
- onBlur: (e) => props.onCommit(e.target.value),
189
- }));
190
- }
191
- // Default: text editor
192
- return h('div', { style: editorWrapperStyle }, h('input', {
193
- ref: (el) => { inputRef.value = el; },
194
- type: 'text',
195
- value: localValue.value != null ? String(localValue.value) : '',
196
- style: { width: '100%', height: '100%', border: 'none', outline: 'none', padding: '0 4px', fontSize: 'inherit', boxSizing: 'border-box' },
197
- onInput: (e) => { localValue.value = e.target.value; },
198
- onKeydown: (e) => {
199
- if (e.key === 'Enter') {
200
- e.preventDefault();
201
- props.onCommit(localValue.value);
202
- }
203
- if (e.key === 'Escape') {
204
- e.preventDefault();
205
- props.onCancel();
206
- }
207
- if (e.key === 'Tab') {
208
- e.preventDefault();
209
- props.onCommit(localValue.value);
210
- }
211
- },
212
- onBlur: () => props.onCommit(localValue.value),
213
- }));
214
- };
215
- },
34
+ },
35
+ onBlur: (e) => onChange(e.target.value),
36
+ }),
216
37
  });
@@ -1,328 +1,9 @@
1
- import { defineComponent, h, computed } from 'vue';
2
- import { useOGrid, } from '@alaarab/ogrid-vue';
1
+ import { createOGrid } from '@alaarab/ogrid-vue';
3
2
  import { DataGridTable } from '../DataGridTable/DataGridTable';
4
3
  import { ColumnChooser } from '../ColumnChooser/ColumnChooser';
5
4
  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
- }
225
- export const OGrid = defineComponent({
226
- name: 'OGrid',
227
- props: {
228
- gridProps: { type: Object, required: true },
229
- },
230
- setup(props, { expose }) {
231
- const propsRef = computed(() => props.gridProps);
232
- const { dataGridProps, pagination, columnChooser, layout, api } = useOGrid(propsRef);
233
- // Expose the API for parent refs
234
- expose({ api: api.value });
235
- return () => {
236
- const sideBar = layout.value.sideBarProps;
237
- const hasSideBar = sideBar != null;
238
- const sideBarPosition = sideBar?.position ?? 'right';
239
- // Toolbar
240
- const toolbarChildren = [];
241
- if (layout.value.toolbar) {
242
- toolbarChildren.push(layout.value.toolbar);
243
- }
244
- // ColumnChooser in toolbar
245
- const toolbarEnd = columnChooser.value.placement === 'toolbar'
246
- ? h(ColumnChooser, {
247
- columns: columnChooser.value.columns,
248
- visibleColumns: columnChooser.value.visibleColumns,
249
- onVisibilityChange: columnChooser.value.onVisibilityChange,
250
- })
251
- : null;
252
- // Pagination
253
- const paginationNode = h(PaginationControls, {
254
- currentPage: pagination.value.page,
255
- pageSize: pagination.value.pageSize,
256
- totalCount: pagination.value.displayTotalCount,
257
- onPageChange: pagination.value.setPage,
258
- onPageSizeChange: (size) => {
259
- pagination.value.setPageSize(size);
260
- pagination.value.setPage(1);
261
- },
262
- pageSizeOptions: pagination.value.pageSizeOptions,
263
- entityLabelPlural: pagination.value.entityLabelPlural,
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
- }
282
- return h('div', {
283
- class: layout.value.className,
284
- style: {
285
- display: 'flex',
286
- flexDirection: 'column',
287
- border: '1px solid var(--ogrid-border, rgba(0,0,0,0.12))',
288
- borderRadius: '4px',
289
- overflow: 'hidden',
290
- },
291
- }, [
292
- // Toolbar strip
293
- ...(toolbarChildren.length || toolbarEnd ? [
294
- h('div', {
295
- style: {
296
- display: 'flex',
297
- alignItems: 'center',
298
- justifyContent: 'space-between',
299
- padding: '8px 12px',
300
- borderBottom: '1px solid var(--ogrid-border, rgba(0,0,0,0.12))',
301
- gap: '8px',
302
- },
303
- }, [
304
- h('div', { style: { display: 'flex', alignItems: 'center', gap: '8px', flex: '1' } }, toolbarChildren),
305
- ...(toolbarEnd ? [toolbarEnd] : []),
306
- ]),
307
- ] : []),
308
- // Below toolbar strip
309
- ...(layout.value.toolbarBelow ? [
310
- h('div', {
311
- style: { padding: '8px 12px', borderBottom: '1px solid var(--ogrid-border, rgba(0,0,0,0.12))' },
312
- }, [layout.value.toolbarBelow]),
313
- ] : []),
314
- // Main content area (sidebar + grid)
315
- h('div', { style: { display: 'flex', flex: '1', minHeight: '0' } }, mainAreaChildren),
316
- // Footer strip (pagination)
317
- h('div', {
318
- style: {
319
- display: 'flex',
320
- alignItems: 'center',
321
- padding: '8px 0',
322
- borderTop: '1px solid var(--ogrid-border, rgba(0,0,0,0.12))',
323
- },
324
- }, [paginationNode]),
325
- ]);
326
- };
327
- },
5
+ export const OGrid = createOGrid({
6
+ DataGridTable,
7
+ ColumnChooser,
8
+ PaginationControls,
328
9
  });
package/dist/esm/index.js CHANGED
@@ -1,4 +1,6 @@
1
- // Re-export everything from vue adapter
1
+ // Re-export all from base package for consumer convenience.
2
+ // Note: This prevents tree-shaking of unused utilities.
3
+ // Consider explicit named exports in a future major version.
2
4
  export * from '@alaarab/ogrid-vue';
3
5
  // Components
4
6
  export { OGrid } from './OGrid/OGrid';
@@ -1,14 +1,4 @@
1
- import { type PropType } from 'vue';
2
- import type { IColumnDef } from '@alaarab/ogrid-vue';
3
- export interface InlineCellEditorProps<T = unknown> {
4
- value: unknown;
5
- item: T;
6
- column: IColumnDef<T>;
7
- rowIndex: number;
8
- editorType: 'text' | 'select' | 'checkbox' | 'richSelect' | 'date';
9
- onCommit: (value: unknown) => void;
10
- onCancel: () => void;
11
- }
1
+ export type { CreateInlineCellEditorOptions } from '@alaarab/ogrid-vue';
12
2
  export declare const InlineCellEditor: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
13
3
  value: {
14
4
  default: undefined;
@@ -18,7 +8,7 @@ export declare const InlineCellEditor: import("vue").DefineComponent<import("vue
18
8
  required: true;
19
9
  };
20
10
  column: {
21
- type: PropType<IColumnDef>;
11
+ type: import("vue").PropType<import("@alaarab/ogrid-vue").IColumnDef>;
22
12
  required: true;
23
13
  };
24
14
  rowIndex: {
@@ -26,15 +16,15 @@ export declare const InlineCellEditor: import("vue").DefineComponent<import("vue
26
16
  required: true;
27
17
  };
28
18
  editorType: {
29
- type: PropType<"text" | "select" | "checkbox" | "richSelect" | "date">;
19
+ type: import("vue").PropType<"text" | "select" | "checkbox" | "richSelect" | "date">;
30
20
  required: true;
31
21
  };
32
22
  onCommit: {
33
- type: PropType<(value: unknown) => void>;
23
+ type: import("vue").PropType<(value: unknown) => void>;
34
24
  required: true;
35
25
  };
36
26
  onCancel: {
37
- type: PropType<() => void>;
27
+ type: import("vue").PropType<() => void>;
38
28
  required: true;
39
29
  };
40
30
  }>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
@@ -48,7 +38,7 @@ export declare const InlineCellEditor: import("vue").DefineComponent<import("vue
48
38
  required: true;
49
39
  };
50
40
  column: {
51
- type: PropType<IColumnDef>;
41
+ type: import("vue").PropType<import("@alaarab/ogrid-vue").IColumnDef>;
52
42
  required: true;
53
43
  };
54
44
  rowIndex: {
@@ -56,15 +46,15 @@ export declare const InlineCellEditor: import("vue").DefineComponent<import("vue
56
46
  required: true;
57
47
  };
58
48
  editorType: {
59
- type: PropType<"text" | "select" | "checkbox" | "richSelect" | "date">;
49
+ type: import("vue").PropType<"text" | "select" | "checkbox" | "richSelect" | "date">;
60
50
  required: true;
61
51
  };
62
52
  onCommit: {
63
- type: PropType<(value: unknown) => void>;
53
+ type: import("vue").PropType<(value: unknown) => void>;
64
54
  required: true;
65
55
  };
66
56
  onCancel: {
67
- type: PropType<() => void>;
57
+ type: import("vue").PropType<() => void>;
68
58
  required: true;
69
59
  };
70
60
  }>> & Readonly<{}>, {
@@ -1,15 +1,13 @@
1
- import { type PropType, type VNode } from 'vue';
2
- import { type IOGridProps } from '@alaarab/ogrid-vue';
3
1
  export declare const OGrid: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
4
2
  gridProps: {
5
- type: PropType<IOGridProps<unknown>>;
3
+ type: import("vue").PropType<import("@alaarab/ogrid-vue").IOGridProps<unknown>>;
6
4
  required: true;
7
5
  };
8
- }>, () => VNode<import("vue").RendererNode, import("vue").RendererElement, {
6
+ }>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
9
7
  [key: string]: any;
10
8
  }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
11
9
  gridProps: {
12
- type: PropType<IOGridProps<unknown>>;
10
+ type: import("vue").PropType<import("@alaarab/ogrid-vue").IOGridProps<unknown>>;
13
11
  required: true;
14
12
  };
15
13
  }>> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alaarab/ogrid-vue-vuetify",
3
- "version": "2.0.19",
3
+ "version": "2.0.22",
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.19"
40
+ "@alaarab/ogrid-vue": "2.0.22"
41
41
  },
42
42
  "peerDependencies": {
43
43
  "vue": "^3.3.0",