@alaarab/ogrid-vue 2.1.0 → 2.1.2

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.
@@ -107,6 +107,7 @@ export function createDataGridTable(ui) {
107
107
  const layoutMode = p.layoutMode ?? 'fill';
108
108
  const rowSelection = p.rowSelection ?? 'none';
109
109
  const suppressHorizontalScroll = p.suppressHorizontalScroll;
110
+ const stickyHeader = p.stickyHeader ?? true;
110
111
  const isLoading = p.isLoading ?? false;
111
112
  const loadingMessage = p.loadingMessage ?? 'Loading\u2026';
112
113
  const ariaLabel = p['aria-label'];
@@ -255,7 +256,7 @@ export function createDataGridTable(ui) {
255
256
  style: { minWidth: `${minTableWidth}px` },
256
257
  }, [
257
258
  // Header
258
- h('thead', { class: 'ogrid-thead' }, headerRows.map((row, rowIdx) => h('tr', { key: rowIdx, class: 'ogrid-header-row' }, [
259
+ h('thead', { class: stickyHeader ? 'ogrid-thead ogrid-sticky-header' : 'ogrid-thead' }, headerRows.map((row, rowIdx) => h('tr', { key: rowIdx, class: 'ogrid-header-row' }, [
259
260
  // Checkbox header cell
260
261
  ...(rowIdx === headerRows.length - 1 && hasCheckboxCol ? [
261
262
  h('th', {
@@ -5,7 +5,7 @@
5
5
  * they only differ in which DataGridTable, ColumnChooser, and PaginationControls
6
6
  * components they use. This factory extracts all shared logic into one place.
7
7
  */
8
- import { defineComponent, h, computed } from 'vue';
8
+ import { defineComponent, h, ref, onMounted, onUnmounted, computed } from 'vue';
9
9
  import { useOGrid, } from '../composables';
10
10
  // --- SideBar constants and styles ---
11
11
  const PANEL_WIDTH = 240;
@@ -245,6 +245,16 @@ export function createOGrid(ui) {
245
245
  const { dataGridProps, pagination, columnChooser, layout, api } = useOGrid(propsRef);
246
246
  // Expose the ref container so parent always gets the latest API value
247
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); });
248
258
  return () => {
249
259
  const sideBar = layout.value.sideBarProps;
250
260
  const hasSideBar = sideBar != null;
@@ -254,6 +264,31 @@ export function createOGrid(ui) {
254
264
  if (layout.value.toolbar) {
255
265
  toolbarChildren.push(layout.value.toolbar);
256
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;
257
292
  // ColumnChooser in toolbar
258
293
  const toolbarEnd = columnChooser.value.placement === 'toolbar'
259
294
  ? h(ui.ColumnChooser, {
@@ -291,49 +326,56 @@ export function createOGrid(ui) {
291
326
  if (hasSideBar && sideBarPosition !== 'left') {
292
327
  mainAreaChildren.push(renderSideBar(sideBar));
293
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;
294
336
  return h('div', {
295
337
  class: layout.value.className,
296
- style: {
297
- display: 'flex',
298
- flexDirection: 'column',
299
- border: '1px solid var(--ogrid-border, rgba(0,0,0,0.12))',
300
- borderRadius: '4px',
301
- overflow: 'hidden',
302
- },
338
+ style: rootStyle,
303
339
  }, [
304
- // Toolbar strip
305
- ...(toolbarChildren.length || toolbarEnd ? [
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)
306
370
  h('div', {
307
371
  style: {
308
372
  display: 'flex',
309
373
  alignItems: 'center',
310
- justifyContent: 'space-between',
311
- padding: '8px 12px',
312
- borderBottom: '1px solid var(--ogrid-border, rgba(0,0,0,0.12))',
313
- gap: '8px',
374
+ padding: '8px 0',
375
+ borderTop: '1px solid var(--ogrid-border, rgba(0,0,0,0.12))',
314
376
  },
315
- }, [
316
- h('div', { style: { display: 'flex', alignItems: 'center', gap: '8px', flex: '1' } }, toolbarChildren),
317
- ...(toolbarEnd ? [toolbarEnd] : []),
318
- ]),
319
- ] : []),
320
- // Below toolbar strip
321
- ...(layout.value.toolbarBelow ? [
322
- h('div', {
323
- style: { padding: '8px 12px', borderBottom: '1px solid var(--ogrid-border, rgba(0,0,0,0.12))' },
324
- }, [layout.value.toolbarBelow]),
325
- ] : []),
326
- // Main content area (sidebar + grid)
327
- h('div', { style: { display: 'flex', flex: '1', minHeight: '0' } }, mainAreaChildren),
328
- // Footer strip (pagination)
329
- h('div', {
330
- style: {
331
- display: 'flex',
332
- alignItems: 'center',
333
- padding: '8px 0',
334
- borderTop: '1px solid var(--ogrid-border, rgba(0,0,0,0.12))',
335
- },
336
- }, [paginationNode]),
377
+ }, [paginationNode]),
378
+ ]),
337
379
  ]);
338
380
  };
339
381
  },
@@ -329,6 +329,7 @@ export function useDataGridState(params) {
329
329
  setColumnSizingOverrides,
330
330
  onColumnResized: props.value.onColumnResized,
331
331
  measuredColumnWidths: measuredColumnWidths.value,
332
+ stickyHeader: props.value.stickyHeader ?? true,
332
333
  }));
333
334
  const rowSelectionState = computed(() => ({
334
335
  selectedRowIds: rowSelectionResult.selectedRowIds.value,
@@ -367,6 +367,7 @@ export function useOGrid(props) {
367
367
  getUserByEmail: ds?.getUserByEmail,
368
368
  layoutMode: p.layoutMode,
369
369
  suppressHorizontalScroll: p.suppressHorizontalScroll,
370
+ stickyHeader: p.stickyHeader ?? true,
370
371
  columnReorder: p.columnReorder,
371
372
  virtualScroll: p.virtualScroll,
372
373
  rowHeight: p.rowHeight,
@@ -402,6 +403,7 @@ export function useOGrid(props) {
402
403
  className: props.value.className,
403
404
  emptyState: props.value.emptyState,
404
405
  sideBarProps: sideBarProps.value,
406
+ fullScreen: props.value.fullScreen,
405
407
  }));
406
408
  const filtersResult = computed(() => ({
407
409
  hasActiveFilters: hasActiveFilters.value,
@@ -77,20 +77,27 @@
77
77
 
78
78
  .ogrid-header-cell {
79
79
  font-weight: 600;
80
- position: sticky;
81
- top: 0;
82
80
  background-color: var(--ogrid-header-bg);
83
81
  z-index: 8;
84
82
  }
85
83
 
84
+ .ogrid-sticky-header .ogrid-header-cell {
85
+ position: sticky;
86
+ top: 0;
87
+ }
88
+
86
89
  .ogrid-header-cell--pinned-left {
87
90
  z-index: 10;
88
91
  will-change: transform;
92
+ border-right: 1px solid var(--ogrid-border, rgba(0, 0, 0, 0.12));
93
+ box-shadow: 2px 0 4px -1px rgba(0, 0, 0, 0.1);
89
94
  }
90
95
 
91
96
  .ogrid-header-cell--pinned-right {
92
97
  z-index: 10;
93
98
  will-change: transform;
99
+ border-left: 1px solid var(--ogrid-border, rgba(0, 0, 0, 0.12));
100
+ box-shadow: -2px 0 4px -1px rgba(0, 0, 0, 0.1);
94
101
  }
95
102
 
96
103
  .ogrid-header-content {
@@ -185,6 +192,8 @@
185
192
  z-index: 6;
186
193
  background-color: var(--ogrid-bg);
187
194
  will-change: transform;
195
+ border-right: 1px solid var(--ogrid-border, rgba(0, 0, 0, 0.12));
196
+ box-shadow: 2px 0 4px -1px rgba(0, 0, 0, 0.1);
188
197
  }
189
198
 
190
199
  .ogrid-data-cell--pinned-right {
@@ -192,6 +201,8 @@
192
201
  z-index: 6;
193
202
  background-color: var(--ogrid-bg);
194
203
  will-change: transform;
204
+ border-left: 1px solid var(--ogrid-border, rgba(0, 0, 0, 0.12));
205
+ box-shadow: -2px 0 4px -1px rgba(0, 0, 0, 0.1);
195
206
  }
196
207
 
197
208
  .ogrid-cell-content {
@@ -28,6 +28,7 @@ export interface DataGridLayoutState<T> {
28
28
  * UI packages use these as a minWidth floor to prevent columns from
29
29
  * shrinking when new data loads (e.g. during server-side pagination). */
30
30
  measuredColumnWidths: Record<string, number>;
31
+ stickyHeader: boolean;
31
32
  }
32
33
  export interface DataGridRowSelectionState {
33
34
  selectedRowIds: Set<RowId>;
@@ -30,6 +30,7 @@ export interface UseOGridLayout {
30
30
  render?: () => unknown;
31
31
  };
32
32
  sideBarProps: SideBarProps | null;
33
+ fullScreen?: boolean;
33
34
  }
34
35
  /** Filter state. */
35
36
  export interface UseOGridFilters {
@@ -63,6 +63,10 @@ interface IOGridBaseProps<T> {
63
63
  layoutMode?: 'content' | 'fill';
64
64
  /** When true, horizontal scrolling is suppressed (overflow-x hidden). */
65
65
  suppressHorizontalScroll?: boolean;
66
+ /** When true (default), header row sticks to the top of the scroll container. */
67
+ stickyHeader?: boolean;
68
+ /** When true, shows a fullscreen toggle button in the toolbar. Default: false. */
69
+ fullScreen?: boolean;
66
70
  /** Side bar configuration. `true` shows default panels (columns + filters). Pass ISideBarDef for options. */
67
71
  sideBar?: boolean | ISideBarDef;
68
72
  /** Page size options shown in the pagination dropdown. Default: [10, 20, 50, 100]. */
@@ -122,6 +126,8 @@ export interface IOGridDataGridProps<T> {
122
126
  layoutMode?: 'content' | 'fill';
123
127
  /** When true, horizontal scrolling is suppressed (overflow-x hidden). */
124
128
  suppressHorizontalScroll?: boolean;
129
+ /** When true (default), header row sticks to the top of the scroll container. */
130
+ stickyHeader?: boolean;
125
131
  isLoading?: boolean;
126
132
  loadingMessage?: string;
127
133
  editable?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alaarab/ogrid-vue",
3
- "version": "2.1.0",
3
+ "version": "2.1.2",
4
4
  "description": "OGrid Vue – Vue 3 composables, headless components, and utilities for OGrid data grids.",
5
5
  "main": "dist/esm/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -36,7 +36,7 @@
36
36
  "node": ">=18"
37
37
  },
38
38
  "dependencies": {
39
- "@alaarab/ogrid-core": "2.1.0"
39
+ "@alaarab/ogrid-core": "2.1.2"
40
40
  },
41
41
  "peerDependencies": {
42
42
  "vue": "^3.3.0"