@ornery/ui-grid-react 0.1.6 → 0.1.7-hotfix

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/src/UiGrid.tsx CHANGED
@@ -6,8 +6,8 @@ import type {
6
6
  UiGridApi,
7
7
  GridColumnDef,
8
8
  GridRow,
9
- } from '@ornery/ui-grid';
10
- import type { DisplayItem, RowItem } from '@ornery/ui-grid';
9
+ } from '@ornery/ui-grid-core';
10
+ import type { DisplayItem, RowItem } from '@ornery/ui-grid-core';
11
11
  import { useGridState } from './useGridState';
12
12
  import { useVirtualScroll } from './useVirtualScroll';
13
13
 
@@ -51,30 +51,41 @@ export function UiGrid({
51
51
  expandableFeature,
52
52
  treeViewFeature,
53
53
  csvExportFeature,
54
+ columnMovingFeature,
54
55
  paginationCurrentPage,
55
56
  paginationTotalPages,
56
57
  paginationSelectedPageSize,
57
58
  } = state;
58
59
 
60
+ const headerGridRef = React.useRef<HTMLDivElement | null>(null);
61
+ const filterGridRef = React.useRef<HTMLDivElement | null>(null);
62
+ const [headerStickyHeight, setHeaderStickyHeight] = React.useState(0);
63
+ const [filterStickyHeight, setFilterStickyHeight] = React.useState(0);
64
+ const stickyChromeHeight = headerStickyHeight + filterStickyHeight;
65
+ const bodyViewportHeight = Math.max(
66
+ rowSize,
67
+ (options.viewportHeight ?? 560) - stickyChromeHeight,
68
+ );
69
+
59
70
  const virtualScroll = useVirtualScroll({
60
71
  itemCount: displayItems.length,
61
72
  itemSize: rowSize,
62
- viewportHeight: options.viewportHeight ?? 560,
73
+ viewportHeight: bodyViewportHeight,
63
74
  overscan: 3,
64
75
  });
65
76
 
66
- const headerGridRef = React.useRef<HTMLDivElement | null>(null);
67
- const filterGridRef = React.useRef<HTMLDivElement | null>(null);
68
77
  const [openPinMenuColumn, setOpenPinMenuColumn] = React.useState<string | null>(null);
69
- const [headerStickyHeight, setHeaderStickyHeight] = React.useState(0);
70
- const [filterStickyHeight, setFilterStickyHeight] = React.useState(0);
71
- const stickyChromeHeight = headerStickyHeight + filterStickyHeight;
72
- const scrollContainerHeight = `${(options.viewportHeight ?? 560) + stickyChromeHeight}px`;
78
+ const [draggedColumnName, setDraggedColumnName] = React.useState<string | null>(null);
79
+ const [dropTargetColumnName, setDropTargetColumnName] = React.useState<string | null>(null);
80
+ const scrollContainerHeight = `${options.viewportHeight ?? 560}px`;
73
81
 
74
82
  const eventPathIncludesClass = React.useCallback((event: Event, className: string): boolean => {
75
- const eventPath = typeof event.composedPath === 'function'
76
- ? event.composedPath()
77
- : (event.target ? [event.target] : []);
83
+ const eventPath =
84
+ typeof event.composedPath === 'function'
85
+ ? event.composedPath()
86
+ : event.target
87
+ ? [event.target]
88
+ : [];
78
89
 
79
90
  return eventPath.some((target) => {
80
91
  if (!target || typeof target !== 'object' || !('classList' in target)) {
@@ -119,11 +130,87 @@ export function UiGrid({
119
130
  [state],
120
131
  );
121
132
 
133
+ const handleHeaderDragStart = React.useCallback(
134
+ (column: GridColumnDef, event: React.DragEvent<HTMLDivElement>) => {
135
+ if (!columnMovingFeature) {
136
+ event.preventDefault();
137
+ return;
138
+ }
139
+
140
+ setDraggedColumnName(column.name);
141
+ setDropTargetColumnName(null);
142
+ event.dataTransfer.effectAllowed = 'move';
143
+ event.dataTransfer.setData('text/plain', column.name);
144
+ },
145
+ [columnMovingFeature],
146
+ );
147
+
148
+ const handleHeaderDragOver = React.useCallback(
149
+ (column: GridColumnDef, event: React.DragEvent<HTMLDivElement>) => {
150
+ if (!columnMovingFeature || !draggedColumnName || draggedColumnName === column.name) {
151
+ return;
152
+ }
153
+
154
+ event.preventDefault();
155
+ event.dataTransfer.dropEffect = 'move';
156
+ setDropTargetColumnName(column.name);
157
+ },
158
+ [columnMovingFeature, draggedColumnName],
159
+ );
160
+
161
+ const handleHeaderDrop = React.useCallback(
162
+ (column: GridColumnDef, event: React.DragEvent<HTMLDivElement>) => {
163
+ event.preventDefault();
164
+
165
+ if (!columnMovingFeature) {
166
+ return;
167
+ }
168
+
169
+ const sourceColumnName = draggedColumnName ?? event.dataTransfer.getData('text/plain');
170
+ setDraggedColumnName(null);
171
+ setDropTargetColumnName(null);
172
+
173
+ if (!sourceColumnName || sourceColumnName === column.name) {
174
+ return;
175
+ }
176
+
177
+ state.moveVisibleColumn(sourceColumnName, column.name);
178
+ },
179
+ [columnMovingFeature, draggedColumnName, state],
180
+ );
181
+
182
+ const handleHeaderDragEnd = React.useCallback(() => {
183
+ setDraggedColumnName(null);
184
+ setDropTargetColumnName(null);
185
+ }, []);
186
+
122
187
  React.useLayoutEffect(() => {
123
188
  setHeaderStickyHeight(headerGridRef.current?.offsetHeight ?? 0);
124
189
  setFilterStickyHeight(filterGridRef.current?.offsetHeight ?? 0);
125
190
  }, [visibleColumns, filteringFeature, options.enableFiltering]);
126
191
 
192
+ React.useLayoutEffect(() => {
193
+ const headerElement = headerGridRef.current;
194
+ const filterElement = filterGridRef.current;
195
+ if (typeof ResizeObserver === 'undefined' || (!headerElement && !filterElement)) {
196
+ return;
197
+ }
198
+
199
+ const observer = new ResizeObserver(() => {
200
+ setHeaderStickyHeight(headerGridRef.current?.offsetHeight ?? 0);
201
+ setFilterStickyHeight(filterGridRef.current?.offsetHeight ?? 0);
202
+ });
203
+
204
+ if (headerElement) {
205
+ observer.observe(headerElement);
206
+ }
207
+ if (filterElement) {
208
+ observer.observe(filterElement);
209
+ }
210
+
211
+ return () => observer.disconnect();
212
+ }, []);
213
+
127
214
  React.useEffect(() => {
128
215
  if (!openPinMenuColumn) {
129
216
  return;
@@ -216,81 +303,91 @@ export function UiGrid({
216
303
  const pinned = state.isPinned(column);
217
304
  const pinOffset = pinned ? state.pinnedOffset(column) : null;
218
305
  return (
219
- <div
220
- key={`${rowItem.row.id}-${column.name}`}
221
- className={`${cellClassName(rowItem, column)}${pinned ? ' is-pinned' : ''}`}
222
- data-part="body-cell"
223
- role="gridcell"
224
- tabIndex={0}
225
- data-row-id={rowItem.row.id}
226
- data-col-name={column.name}
227
- onFocus={() => state.focusCell(rowItem.row, column)}
228
- onClick={() => state.focusCell(rowItem.row, column)}
229
- onDoubleClick={(e) => state.handleCellDoubleClick(rowItem.row, column, e)}
230
- onKeyDown={(e) => state.handleCellKeyDown(rowItem.row, column, e)}
231
- style={{
232
- position: pinned ? 'sticky' : undefined,
233
- left: pinOffset?.side === 'left' ? pinOffset.offset : undefined,
234
- right: pinOffset?.side === 'right' ? pinOffset.offset : undefined,
235
- zIndex: pinned ? 2 : undefined,
236
- }}
237
- >
238
306
  <div
239
- className="cell-shell"
240
- style={{ paddingInlineStart: state.cellIndent(rowItem.row, column) }}
307
+ key={`${rowItem.row.id}-${column.name}`}
308
+ className={`${cellClassName(rowItem, column)}${pinned ? ' is-pinned' : ''}`}
309
+ data-part="body-cell"
310
+ role="gridcell"
311
+ tabIndex={0}
312
+ data-row-id={rowItem.row.id}
313
+ data-col-name={column.name}
314
+ onFocus={() => state.focusCell(rowItem.row, column)}
315
+ onClick={() => state.focusCell(rowItem.row, column)}
316
+ onDoubleClick={(e) => state.handleCellDoubleClick(rowItem.row, column, e)}
317
+ onKeyDown={(e) => state.handleCellKeyDown(rowItem.row, column, e)}
318
+ style={{
319
+ position: pinned ? 'sticky' : undefined,
320
+ left: pinOffset?.side === 'left' ? pinOffset.offset : undefined,
321
+ right: pinOffset?.side === 'right' ? pinOffset.offset : undefined,
322
+ zIndex: pinned ? 2 : undefined,
323
+ }}
241
324
  >
242
- {treeViewFeature && state.showTreeToggle(rowItem.row, column) && (
243
- <button
244
- type="button"
245
- className="row-toggle row-toggle-tree"
246
- data-part="tree-toggle"
247
- aria-label={state.treeToggleLabel(rowItem.row)}
248
- aria-expanded={state.isTreeRowExpanded(rowItem.row)}
249
- onClick={(e) => state.toggleTreeRow(rowItem.row, e)}
250
- >
251
- <svg className="toggle-icon" viewBox="0 0 24 24" aria-hidden="true" focusable={false}>
252
- <path
253
- d={state.isTreeRowExpanded(rowItem.row) ? 'M7 10l5 5 5-5z' : 'M10 7l5 5-5 5z'}
254
- />
255
- </svg>
256
- </button>
257
- )}
258
- {expandableFeature && state.showExpandToggle(rowItem.row, column) && (
259
- <button
260
- type="button"
261
- className="row-toggle row-toggle-expand"
262
- data-part="expand-toggle"
263
- aria-label={state.expandToggleLabel(rowItem.row)}
264
- aria-expanded={rowItem.row.expanded}
265
- onClick={(e) => state.toggleRowExpansion(rowItem.row, e)}
266
- >
267
- <svg className="toggle-icon" viewBox="0 0 24 24" aria-hidden="true" focusable={false}>
268
- <path d={rowItem.row.expanded ? 'M7 10l5 5 5-5z' : 'M10 7l5 5-5 5z'} />
269
- </svg>
270
- </button>
271
- )}
272
- <span className="cell-value">
273
- {cellEditFeature && state.isEditingCell(rowItem.row, column) ? (
274
- <input
275
- className="cell-editor"
276
- data-row-id={rowItem.row.id}
277
- data-col-name={column.name}
278
- aria-label={state.headerLabel(column)}
279
- type={state.editorInputType(column)}
280
- defaultValue={editingValue}
281
- onChange={(e) => state.updateEditingValue(e.target.value)}
282
- onKeyDown={(e) => state.handleEditorKeyDown(e)}
283
- onBlur={(e) => state.handleEditorBlur(e)}
284
- />
285
- ) : cellRenderer ? (
286
- (cellRenderer(state.cellContext(rowItem.row, column)) ??
287
- state.displayValue(rowItem.row, column))
288
- ) : (
289
- state.displayValue(rowItem.row, column)
325
+ <div
326
+ className="cell-shell"
327
+ style={{ paddingInlineStart: state.cellIndent(rowItem.row, column) }}
328
+ >
329
+ {treeViewFeature && state.showTreeToggle(rowItem.row, column) && (
330
+ <button
331
+ type="button"
332
+ className="row-toggle row-toggle-tree"
333
+ data-part="tree-toggle"
334
+ aria-label={state.treeToggleLabel(rowItem.row)}
335
+ aria-expanded={state.isTreeRowExpanded(rowItem.row)}
336
+ onClick={(e) => state.toggleTreeRow(rowItem.row, e)}
337
+ >
338
+ <svg
339
+ className="toggle-icon"
340
+ viewBox="0 0 24 24"
341
+ aria-hidden="true"
342
+ focusable={false}
343
+ >
344
+ <path
345
+ d={state.isTreeRowExpanded(rowItem.row) ? 'M7 10l5 5 5-5z' : 'M10 7l5 5-5 5z'}
346
+ />
347
+ </svg>
348
+ </button>
290
349
  )}
291
- </span>
350
+ {expandableFeature && state.showExpandToggle(rowItem.row, column) && (
351
+ <button
352
+ type="button"
353
+ className="row-toggle row-toggle-expand"
354
+ data-part="expand-toggle"
355
+ aria-label={state.expandToggleLabel(rowItem.row)}
356
+ aria-expanded={rowItem.row.expanded}
357
+ onClick={(e) => state.toggleRowExpansion(rowItem.row, e)}
358
+ >
359
+ <svg
360
+ className="toggle-icon"
361
+ viewBox="0 0 24 24"
362
+ aria-hidden="true"
363
+ focusable={false}
364
+ >
365
+ <path d={rowItem.row.expanded ? 'M7 10l5 5 5-5z' : 'M10 7l5 5-5 5z'} />
366
+ </svg>
367
+ </button>
368
+ )}
369
+ <span className="cell-value">
370
+ {cellEditFeature && state.isEditingCell(rowItem.row, column) ? (
371
+ <input
372
+ className="cell-editor"
373
+ data-row-id={rowItem.row.id}
374
+ data-col-name={column.name}
375
+ aria-label={state.headerLabel(column)}
376
+ type={state.editorInputType(column)}
377
+ defaultValue={editingValue}
378
+ onChange={(e) => state.updateEditingValue(e.target.value)}
379
+ onKeyDown={(e) => state.handleEditorKeyDown(e)}
380
+ onBlur={(e) => state.handleEditorBlur(e)}
381
+ />
382
+ ) : cellRenderer ? (
383
+ (cellRenderer(state.cellContext(rowItem.row, column)) ??
384
+ state.displayValue(rowItem.row, column))
385
+ ) : (
386
+ state.displayValue(rowItem.row, column)
387
+ )}
388
+ </span>
389
+ </div>
292
390
  </div>
293
- </div>
294
391
  );
295
392
  });
296
393
  }
@@ -413,7 +510,11 @@ export function UiGrid({
413
510
  <div
414
511
  className="grid-table ui-grid-contents-wrapper"
415
512
  data-part="grid-table"
416
- style={virtualizationEnabled ? { height: scrollContainerHeight, overflowY: 'auto' } : undefined}
513
+ style={
514
+ virtualizationEnabled
515
+ ? { height: scrollContainerHeight, overflowY: 'auto' }
516
+ : undefined
517
+ }
417
518
  onScroll={virtualizationEnabled ? onGridTableScroll : undefined}
418
519
  >
419
520
  {/* Header row */}
@@ -429,119 +530,134 @@ export function UiGrid({
429
530
  const pinOffset = pinned ? state.pinnedOffset(column) : null;
430
531
  const pinMenuOpen = isPinMenuOpen(column);
431
532
  return (
432
- <div
433
- key={column.name}
434
- className={`header-cell ui-grid-header-cell${sortingFeature && state.sortDirection(column) !== 'none' ? ' is-active' : ''}${pinned ? ' is-pinned' : ''}${pinMenuOpen ? ' is-pin-menu-open' : ''}`}
435
- data-part="header-cell"
436
- role="columnheader"
437
- aria-sort={sortingFeature ? (state.sortAriaSort(column) as any) : undefined}
438
- style={{
439
- position: pinned ? 'sticky' : undefined,
440
- left: pinOffset?.side === 'left' ? pinOffset.offset : undefined,
441
- right: pinOffset?.side === 'right' ? pinOffset.offset : undefined,
442
- zIndex: pinMenuOpen ? 8 : pinned ? 2 : undefined,
443
- }}
444
- >
445
- <span className="header-label">{state.headerLabel(column)}</span>
446
-
447
- <div className="header-actions">
448
- {sortingFeature && (
449
- <button
450
- type="button"
451
- className={`header-action${!state.isColumnSortable(column) ? ' header-action-disabled' : ''}`}
452
- disabled={!state.isColumnSortable(column)}
453
- aria-label={state.sortButtonLabel(column)}
454
- title={state.sortButtonLabel(column)}
455
- onClick={() => state.toggleSort(column)}
456
- >
457
- {renderSortIcon(column)}
458
- <span className="sr-only ui-grid-sr-only">
459
- {state.sortButtonLabel(column)}
460
- </span>
461
- </button>
462
- )}
463
-
464
- {groupingFeature &&
465
- state.isGroupingEnabled() &&
466
- column.enableGrouping !== false && (
533
+ <div
534
+ key={column.name}
535
+ className={`header-cell ui-grid-header-cell${sortingFeature && state.sortDirection(column) !== 'none' ? ' is-active' : ''}${pinned ? ' is-pinned' : ''}${pinMenuOpen ? ' is-pin-menu-open' : ''}${draggedColumnName === column.name ? ' is-dragging' : ''}${dropTargetColumnName === column.name ? ' is-drag-target' : ''}`}
536
+ data-part="header-cell"
537
+ role="columnheader"
538
+ aria-sort={sortingFeature ? (state.sortAriaSort(column) as any) : undefined}
539
+ draggable={columnMovingFeature}
540
+ onDragStart={(event) => handleHeaderDragStart(column, event)}
541
+ onDragOver={(event) => handleHeaderDragOver(column, event)}
542
+ onDrop={(event) => handleHeaderDrop(column, event)}
543
+ onDragEnd={handleHeaderDragEnd}
544
+ onDragLeave={() => {
545
+ if (dropTargetColumnName === column.name) {
546
+ setDropTargetColumnName(null);
547
+ }
548
+ }}
549
+ style={{
550
+ position: pinned ? 'sticky' : undefined,
551
+ left: pinOffset?.side === 'left' ? pinOffset.offset : undefined,
552
+ right: pinOffset?.side === 'right' ? pinOffset.offset : undefined,
553
+ zIndex: pinMenuOpen ? 8 : pinned ? 2 : undefined,
554
+ }}
555
+ >
556
+ <span className="header-label">{state.headerLabel(column)}</span>
557
+
558
+ <div className="header-actions">
559
+ {sortingFeature && (
467
560
  <button
468
561
  type="button"
469
- className={`chip-action${state.isGrouped(column) ? ' chip-action-active' : ''}`}
470
- data-part="group-toggle"
471
- aria-label={state.groupingButtonLabel(column)}
472
- title={state.groupingButtonLabel(column)}
473
- onClick={(e) => state.toggleGrouping(column, e)}
562
+ className={`header-action${!state.isColumnSortable(column) ? ' header-action-disabled' : ''}`}
563
+ disabled={!state.isColumnSortable(column)}
564
+ aria-label={state.sortButtonLabel(column)}
565
+ title={state.sortButtonLabel(column)}
566
+ onClick={() => state.toggleSort(column)}
474
567
  >
475
- <svg viewBox="0 0 24 24" aria-hidden="true" focusable={false}>
476
- <path d="M4 6h8v4H4V6Zm0 8h8v4H4v-4Zm10-8h6v4h-6V6Zm0 8h6v4h-6v-4Z" />
477
- </svg>
568
+ {renderSortIcon(column)}
478
569
  <span className="sr-only ui-grid-sr-only">
479
- {state.groupingButtonLabel(column)}
570
+ {state.sortButtonLabel(column)}
480
571
  </span>
481
572
  </button>
482
573
  )}
483
- {state.pinningFeature &&
484
- state.isPinningEnabled() &&
485
- state.isColumnPinnable(column) && (
486
- <div className={`pin-control${pinMenuOpen ? ' pin-control-open' : ''}`} onClick={(event) => event.stopPropagation()}>
574
+
575
+ {groupingFeature &&
576
+ state.isGroupingEnabled() &&
577
+ column.enableGrouping !== false && (
487
578
  <button
488
579
  type="button"
489
- className={`chip-action pin-trigger${pinned || pinMenuOpen ? ' chip-action-active' : ''}`}
490
- data-part="pin-toggle"
491
- aria-label={pinButtonLabel(column)}
492
- title={pinButtonLabel(column)}
493
- aria-haspopup={pinned ? undefined : 'menu'}
494
- aria-expanded={pinned ? undefined : pinMenuOpen}
495
- onClick={(event) => onPinTrigger(column, event)}
580
+ className={`chip-action${state.isGrouped(column) ? ' chip-action-active' : ''}`}
581
+ data-part="group-toggle"
582
+ aria-label={state.groupingButtonLabel(column)}
583
+ title={state.groupingButtonLabel(column)}
584
+ onClick={(e) => state.toggleGrouping(column, e)}
496
585
  >
497
586
  <svg viewBox="0 0 24 24" aria-hidden="true" focusable={false}>
498
- <path d="M16 12V4h1V2H7v2h1v8l-2 2v2h5v6l1 1 1-1v-6h5v-2l-2-2z" />
587
+ <path d="M4 6h8v4H4V6Zm0 8h8v4H4v-4Zm10-8h6v4h-6V6Zm0 8h6v4h-6v-4Z" />
499
588
  </svg>
500
- <span className="sr-only ui-grid-sr-only">{pinButtonLabel(column)}</span>
589
+ <span className="sr-only ui-grid-sr-only">
590
+ {state.groupingButtonLabel(column)}
591
+ </span>
501
592
  </button>
502
-
593
+ )}
594
+ {state.pinningFeature &&
595
+ state.isPinningEnabled() &&
596
+ state.isColumnPinnable(column) && (
503
597
  <div
504
- className="pin-menu"
505
- data-part="pin-menu"
506
- role="menu"
507
- aria-label="Pin options"
508
- aria-hidden={!pinMenuOpen}
598
+ className={`pin-control${pinMenuOpen ? ' pin-control-open' : ''}`}
599
+ onClick={(event) => event.stopPropagation()}
509
600
  >
510
601
  <button
511
602
  type="button"
512
- className="pin-menu-action"
513
- data-part="pin-left-action"
514
- role="menuitem"
515
- aria-label={labels.pinLeft}
516
- title={labels.pinLeft}
517
- tabIndex={pinMenuOpen ? 0 : -1}
518
- onClick={(event) => choosePinDirection(column, 'left', event)}
603
+ className={`chip-action pin-trigger${pinned || pinMenuOpen ? ' chip-action-active' : ''}`}
604
+ data-part="pin-toggle"
605
+ aria-label={pinButtonLabel(column)}
606
+ title={pinButtonLabel(column)}
607
+ aria-haspopup={pinned ? undefined : 'menu'}
608
+ aria-expanded={pinned ? undefined : pinMenuOpen}
609
+ onClick={(event) => onPinTrigger(column, event)}
519
610
  >
520
611
  <svg viewBox="0 0 24 24" aria-hidden="true" focusable={false}>
521
- <path d="M10 6 4 12l6 6v-4h10v-4H10V6z" />
612
+ <path d="M16 12V4h1V2H7v2h1v8l-2 2v2h5v6l1 1 1-1v-6h5v-2l-2-2z" />
522
613
  </svg>
523
- <span className="sr-only ui-grid-sr-only">{labels.pinLeft}</span>
614
+ <span className="sr-only ui-grid-sr-only">
615
+ {pinButtonLabel(column)}
616
+ </span>
524
617
  </button>
525
- <button
526
- type="button"
527
- className="pin-menu-action"
528
- data-part="pin-right-action"
529
- role="menuitem"
530
- aria-label={labels.pinRight}
531
- title={labels.pinRight}
532
- tabIndex={pinMenuOpen ? 0 : -1}
533
- onClick={(event) => choosePinDirection(column, 'right', event)}
618
+
619
+ <div
620
+ className="pin-menu"
621
+ data-part="pin-menu"
622
+ role="menu"
623
+ aria-label="Pin options"
624
+ aria-hidden={!pinMenuOpen}
534
625
  >
535
- <svg viewBox="0 0 24 24" aria-hidden="true" focusable={false}>
536
- <path d="M14 6v4H4v4h10v4l6-6-6-6z" />
537
- </svg>
538
- <span className="sr-only ui-grid-sr-only">{labels.pinRight}</span>
539
- </button>
626
+ <button
627
+ type="button"
628
+ className="pin-menu-action"
629
+ data-part="pin-left-action"
630
+ role="menuitem"
631
+ aria-label={labels.pinLeft}
632
+ title={labels.pinLeft}
633
+ tabIndex={pinMenuOpen ? 0 : -1}
634
+ onClick={(event) => choosePinDirection(column, 'left', event)}
635
+ >
636
+ <svg viewBox="0 0 24 24" aria-hidden="true" focusable={false}>
637
+ <path d="M10 6 4 12l6 6v-4h10v-4H10V6z" />
638
+ </svg>
639
+ <span className="sr-only ui-grid-sr-only">{labels.pinLeft}</span>
640
+ </button>
641
+ <button
642
+ type="button"
643
+ className="pin-menu-action"
644
+ data-part="pin-right-action"
645
+ role="menuitem"
646
+ aria-label={labels.pinRight}
647
+ title={labels.pinRight}
648
+ tabIndex={pinMenuOpen ? 0 : -1}
649
+ onClick={(event) => choosePinDirection(column, 'right', event)}
650
+ >
651
+ <svg viewBox="0 0 24 24" aria-hidden="true" focusable={false}>
652
+ <path d="M14 6v4H4v4h10v4l6-6-6-6z" />
653
+ </svg>
654
+ <span className="sr-only ui-grid-sr-only">{labels.pinRight}</span>
655
+ </button>
656
+ </div>
540
657
  </div>
541
- </div>
542
- )}
658
+ )}
659
+ </div>
543
660
  </div>
544
- </div>
545
661
  );
546
662
  })}
547
663
  </div>
@@ -552,35 +668,38 @@ export function UiGrid({
552
668
  className="filter-grid ui-grid-header"
553
669
  data-part="filters"
554
670
  ref={filterGridRef}
555
- style={{ gridTemplateColumns, ['--ui-grid-header-sticky-top' as string]: `${headerStickyHeight}px` }}
671
+ style={{
672
+ gridTemplateColumns,
673
+ ['--ui-grid-header-sticky-top' as string]: `${headerStickyHeight}px`,
674
+ }}
556
675
  >
557
676
  {visibleColumns.map((column) => {
558
677
  const pinned = state.isPinned(column);
559
678
  const pinOffset = pinned ? state.pinnedOffset(column) : null;
560
679
  return (
561
- <label
562
- key={column.name}
563
- className={`filter-cell ui-grid-filter-container${pinned ? ' is-pinned' : ''}`}
564
- data-part="filter-cell"
565
- style={{
566
- position: pinned ? 'sticky' : undefined,
567
- left: pinOffset?.side === 'left' ? pinOffset.offset : undefined,
568
- right: pinOffset?.side === 'right' ? pinOffset.offset : undefined,
569
- zIndex: pinned ? 2 : undefined,
570
- }}
571
- >
572
- <span className="sr-only ui-grid-sr-only">
573
- {labels.filterColumn} {state.headerLabel(column)}
574
- </span>
575
- <input
576
- className="ui-grid-filter-input"
577
- type="text"
578
- defaultValue={state.filterValue(column.name)}
579
- placeholder={state.filterPlaceholder(column)}
580
- disabled={state.isFilterInputDisabled(column)}
581
- onChange={(e) => state.updateFilter(column.name, e.target.value)}
582
- />
583
- </label>
680
+ <label
681
+ key={column.name}
682
+ className={`filter-cell ui-grid-filter-container${pinned ? ' is-pinned' : ''}`}
683
+ data-part="filter-cell"
684
+ style={{
685
+ position: pinned ? 'sticky' : undefined,
686
+ left: pinOffset?.side === 'left' ? pinOffset.offset : undefined,
687
+ right: pinOffset?.side === 'right' ? pinOffset.offset : undefined,
688
+ zIndex: pinned ? 2 : undefined,
689
+ }}
690
+ >
691
+ <span className="sr-only ui-grid-sr-only">
692
+ {labels.filterColumn} {state.headerLabel(column)}
693
+ </span>
694
+ <input
695
+ className="ui-grid-filter-input"
696
+ type="text"
697
+ defaultValue={state.filterValue(column.name)}
698
+ placeholder={state.filterPlaceholder(column)}
699
+ disabled={state.isFilterInputDisabled(column)}
700
+ onChange={(e) => state.updateFilter(column.name, e.target.value)}
701
+ />
702
+ </label>
584
703
  );
585
704
  })}
586
705
  </div>
@@ -589,7 +708,10 @@ export function UiGrid({
589
708
  {/* Body */}
590
709
  {displayItems.length > 0 ? (
591
710
  virtualizationEnabled ? (
592
- <div className="grid-virtual-spacer" style={{ height: `${virtualScroll.totalHeight}px` }}>
711
+ <div
712
+ className="grid-virtual-spacer"
713
+ style={{ height: `${virtualScroll.totalHeight}px` }}
714
+ >
593
715
  <div
594
716
  className="body-grid ui-grid-canvas grid-virtual-body"
595
717
  data-part="body"