@alaarab/ogrid-vue-vuetify 2.0.9 → 2.0.12

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.
@@ -1,5 +1,5 @@
1
1
  import { defineComponent, h } from 'vue';
2
- import { VBtn, VIcon, VMenu, VTooltip } from 'vuetify/components';
2
+ import { VBtn, VIcon, VMenu, VTooltip, VCard } from 'vuetify/components';
3
3
  import { useColumnHeaderFilterState, } from '@alaarab/ogrid-vue';
4
4
  import { TextFilterPopover } from './TextFilterPopover';
5
5
  import { MultiSelectFilterPopover } from './MultiSelectFilterPopover';
@@ -9,6 +9,7 @@ const _VBtn = VBtn;
9
9
  const _VIcon = VIcon;
10
10
  const _VMenu = VMenu;
11
11
  const _VTooltip = VTooltip;
12
+ const _VCard = VCard;
12
13
  export const ColumnHeaderFilter = defineComponent({
13
14
  name: 'ColumnHeaderFilter',
14
15
  props: {
@@ -145,11 +146,14 @@ export const ColumnHeaderFilter = defineComponent({
145
146
  h(_VBtn, {
146
147
  icon: true,
147
148
  size: 'x-small',
148
- variant: 'text',
149
- color: props.isSorted ? 'primary' : undefined,
149
+ variant: props.isSorted ? 'tonal' : 'text',
150
+ color: props.isSorted ? 'primary' : 'default',
150
151
  'aria-label': `Sort by ${props.columnName}`,
151
152
  title: props.isSorted ? (props.isSortedDescending ? 'Sorted descending' : 'Sorted ascending') : 'Sort',
152
153
  onClick: state.handlers.handleSortClick,
154
+ style: {
155
+ opacity: props.isSorted ? '1' : '0.7',
156
+ },
153
157
  }, () => h(_VIcon, { size: '16' }, () => props.isSorted
154
158
  ? (props.isSortedDescending ? 'mdi-arrow-down' : 'mdi-arrow-up')
155
159
  : 'mdi-swap-vertical')),
@@ -167,10 +171,13 @@ export const ColumnHeaderFilter = defineComponent({
167
171
  ...menuProps,
168
172
  icon: true,
169
173
  size: 'x-small',
170
- variant: 'text',
171
- color: state.hasActiveFilter.value || state.isFilterOpen.value ? 'primary' : undefined,
174
+ variant: (state.hasActiveFilter.value || state.isFilterOpen.value) ? 'tonal' : 'text',
175
+ color: (state.hasActiveFilter.value || state.isFilterOpen.value) ? 'primary' : 'default',
172
176
  'aria-label': `Filter ${props.columnName}`,
173
177
  title: `Filter ${props.columnName}`,
178
+ style: {
179
+ opacity: (state.hasActiveFilter.value || state.isFilterOpen.value) ? '1' : '0.7',
180
+ },
174
181
  }, () => h(_VIcon, { size: '16' }, () => 'mdi-filter-variant')),
175
182
  ...(state.hasActiveFilter.value ? [
176
183
  h('div', {
@@ -182,20 +189,23 @@ export const ColumnHeaderFilter = defineComponent({
182
189
  height: '6px',
183
190
  borderRadius: '50%',
184
191
  backgroundColor: 'rgb(var(--v-theme-primary))',
192
+ zIndex: '1',
185
193
  },
186
194
  }),
187
195
  ] : []),
188
196
  ]),
189
- default: () => h('div', {
197
+ default: () => h(_VCard, {
198
+ elevation: 8,
190
199
  ref: (el) => { state.popoverRef.value = el; },
191
200
  onClick: (e) => e.stopPropagation(),
192
- }, [
201
+ }, () => [
193
202
  h('div', {
194
203
  style: {
195
204
  borderBottom: '1px solid rgba(0,0,0,0.12)',
196
205
  padding: '8px 12px',
197
206
  fontWeight: '600',
198
207
  fontSize: '0.875rem',
208
+ backgroundColor: '#fff',
199
209
  },
200
210
  }, `Filter: ${props.columnName}`),
201
211
  renderPopoverContent(),
@@ -1,6 +1,6 @@
1
- import { defineComponent, h } from 'vue';
2
- import { VMenu, VList, VListItem } from 'vuetify/components';
3
- import { COLUMN_HEADER_MENU_ITEMS } from '@alaarab/ogrid-vue';
1
+ import { defineComponent, h, computed } from 'vue';
2
+ import { VMenu, VList, VListItem, VDivider } from 'vuetify/components';
3
+ import { getColumnHeaderMenuItems } from '@alaarab/ogrid-vue';
4
4
  export const ColumnHeaderMenu = defineComponent({
5
5
  name: 'ColumnHeaderMenu',
6
6
  props: {
@@ -10,9 +10,17 @@ export const ColumnHeaderMenu = defineComponent({
10
10
  onPinLeft: { type: Function, required: true },
11
11
  onPinRight: { type: Function, required: true },
12
12
  onUnpin: { type: Function, required: true },
13
+ onSortAsc: { type: Function, required: true },
14
+ onSortDesc: { type: Function, required: true },
15
+ onClearSort: { type: Function, required: true },
16
+ onAutosizeThis: { type: Function, required: true },
17
+ onAutosizeAll: { type: Function, required: true },
13
18
  canPinLeft: { type: Boolean, required: true },
14
19
  canPinRight: { type: Boolean, required: true },
15
20
  canUnpin: { type: Boolean, required: true },
21
+ currentSort: { type: String, default: null },
22
+ isSortable: { type: Boolean, default: true },
23
+ isResizable: { type: Boolean, default: true },
16
24
  },
17
25
  setup(props) {
18
26
  const handleOpenChange = (open) => {
@@ -20,36 +28,52 @@ export const ColumnHeaderMenu = defineComponent({
20
28
  props.onClose();
21
29
  }
22
30
  };
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;
31
+ const items = computed(() => getColumnHeaderMenuItems({
32
+ canPinLeft: props.canPinLeft,
33
+ canPinRight: props.canPinRight,
34
+ canUnpin: props.canUnpin,
35
+ currentSort: props.currentSort,
36
+ isSortable: props.isSortable,
37
+ isResizable: props.isResizable,
38
+ }));
39
+ const handlers = {
40
+ pinLeft: props.onPinLeft,
41
+ pinRight: props.onPinRight,
42
+ unpin: props.onUnpin,
43
+ sortAsc: props.onSortAsc,
44
+ sortDesc: props.onSortDesc,
45
+ clearSort: props.onClearSort,
46
+ autosizeThis: props.onAutosizeThis,
47
+ autosizeAll: props.onAutosizeAll,
32
48
  };
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 () => { };
49
+ const getHandler = (itemId) => handlers[itemId] || (() => { });
50
+ return () => {
51
+ // If no anchor element or menu is closed, don't render
52
+ if (!props.anchorElement)
53
+ return null;
54
+ return h(VMenu, {
55
+ modelValue: props.isOpen,
56
+ 'onUpdate:modelValue': handleOpenChange,
57
+ location: 'bottom start',
58
+ // Use target prop instead of activator for programmatic positioning
59
+ target: props.anchorElement,
60
+ }, {
61
+ default: () => h(VList, { density: 'compact', 'aria-label': 'Column options' }, () => {
62
+ const children = [];
63
+ items.value.forEach((item) => {
64
+ children.push(h(VListItem, {
65
+ key: item.id,
66
+ disabled: item.disabled,
67
+ onClick: () => { getHandler(item.id)(); },
68
+ }, () => item.label));
69
+ // Add divider after item to separate sections
70
+ if (item.divider) {
71
+ children.push(h(VDivider, { key: `divider-${item.id}` }));
72
+ }
73
+ });
74
+ return children;
75
+ }),
76
+ });
41
77
  };
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
78
  },
55
79
  });
@@ -0,0 +1,398 @@
1
+ /* OGrid Vue Vuetify - DataGridTable styles */
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
+ }
58
+
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
+ }
69
+
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
+ }
74
+
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
+ .v-table th:focus-visible,
82
+ .v-table td:focus-visible {
83
+ outline: 2px solid rgb(var(--v-theme-primary));
84
+ outline-offset: -2px;
85
+ z-index: 11;
86
+ }
87
+
88
+ .v-btn:focus-visible {
89
+ outline: 2px solid rgb(var(--v-theme-primary));
90
+ outline-offset: 2px;
91
+ }
92
+
93
+ .v-checkbox:focus-visible {
94
+ outline: 2px solid rgb(var(--v-theme-primary));
95
+ outline-offset: 2px;
96
+ }
97
+
98
+ .ogrid-cell-content:focus-visible {
99
+ outline: 2px solid rgb(var(--v-theme-primary));
100
+ outline-offset: -2px;
101
+ z-index: 3;
102
+ }
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
+ /* === Fill handle === */
306
+
307
+ .ogrid-fill-handle {
308
+ position: absolute;
309
+ right: -3px;
310
+ bottom: -3px;
311
+ width: 7px;
312
+ height: 7px;
313
+ background-color: var(--ogrid-selection, #217346);
314
+ border: 1px solid var(--ogrid-bg);
315
+ border-radius: 1px;
316
+ cursor: crosshair;
317
+ pointer-events: auto;
318
+ z-index: 3;
319
+ }
320
+
321
+ /* === Resize handle === */
322
+
323
+ .ogrid-resize-handle {
324
+ position: absolute;
325
+ top: 0;
326
+ right: -3px;
327
+ bottom: 0;
328
+ width: 8px;
329
+ cursor: col-resize;
330
+ user-select: none;
331
+ }
332
+
333
+ /* === Drop indicator === */
334
+
335
+ .ogrid-drop-indicator {
336
+ position: absolute;
337
+ top: 0;
338
+ bottom: 0;
339
+ width: 3px;
340
+ background: var(--ogrid-primary, #217346);
341
+ pointer-events: none;
342
+ z-index: 100;
343
+ }
344
+
345
+ /* === Empty state === */
346
+
347
+ .ogrid-empty-state {
348
+ padding: 32px 16px;
349
+ text-align: center;
350
+ border-top: 1px solid var(--ogrid-border);
351
+ background-color: var(--ogrid-header-bg);
352
+ }
353
+
354
+ .ogrid-empty-state-title {
355
+ font-size: 1.25rem;
356
+ font-weight: 600;
357
+ margin-bottom: 8px;
358
+ }
359
+
360
+ .ogrid-empty-state-message {
361
+ font-size: 0.875rem;
362
+ color: var(--ogrid-fg-secondary);
363
+ }
364
+
365
+ /* === Loading overlay === */
366
+
367
+ .ogrid-loading-overlay {
368
+ position: absolute;
369
+ inset: 0;
370
+ z-index: 2;
371
+ display: flex;
372
+ align-items: center;
373
+ justify-content: center;
374
+ background-color: var(--ogrid-loading-overlay);
375
+ }
376
+
377
+ .ogrid-loading-inner {
378
+ display: flex;
379
+ flex-direction: column;
380
+ align-items: center;
381
+ gap: 8px;
382
+ padding: 16px;
383
+ background-color: var(--ogrid-bg);
384
+ border: 1px solid var(--ogrid-border);
385
+ border-radius: 4px;
386
+ }
387
+
388
+ .ogrid-loading-message {
389
+ font-size: 0.875rem;
390
+ color: var(--ogrid-fg-secondary);
391
+ }
392
+
393
+ /* === Popover editor anchor === */
394
+
395
+ .ogrid-popover-anchor {
396
+ min-height: 100%;
397
+ min-width: 40px;
398
+ }