@ornery/ui-grid-react 0.1.8 → 0.1.10

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/dist/index.mjs CHANGED
@@ -61,6 +61,7 @@ import {
61
61
  buildGridFocusCellResult,
62
62
  findNextGridCell,
63
63
  isPrintableGridKey,
64
+ isGridNavigationKey,
64
65
  isGridCellPosition,
65
66
  exportCsvRows,
66
67
  buildGridRows,
@@ -186,6 +187,7 @@ function useGridState(options, onRegisterApi) {
186
187
  });
187
188
  const [autoViewportHeight, setAutoViewportHeight] = useState(null);
188
189
  const [pinnedColumns, setPinnedColumns] = useState({});
190
+ const [columnWidthOverrides, setColumnWidthOverrides] = useState({});
189
191
  const gridContainerRef = useRef(null);
190
192
  const initializedGridIdRef = useRef(null);
191
193
  const lastCanvasHeightRef = useRef(0);
@@ -223,6 +225,14 @@ function useGridState(options, onRegisterApi) {
223
225
  currentPageRef.current = currentPage;
224
226
  const pageSizeRef = useRef(pageSize);
225
227
  pageSizeRef.current = pageSize;
228
+ const setEditingCellState = useCallback((nextEditingCell) => {
229
+ editingCellRef.current = nextEditingCell;
230
+ setEditingCell(nextEditingCell);
231
+ }, []);
232
+ const setEditingValueState = useCallback((nextEditingValue) => {
233
+ editingValueRef.current = nextEditingValue;
234
+ setEditingValue(nextEditingValue);
235
+ }, []);
226
236
  const infiniteScrollStateRef = useRef(infiniteScrollState);
227
237
  infiniteScrollStateRef.current = infiniteScrollState;
228
238
  const optionsRef = useRef(options);
@@ -230,9 +240,13 @@ function useGridState(options, onRegisterApi) {
230
240
  const rowSize = options.rowHeight ?? 44;
231
241
  const visibleColumns = useMemo(() => {
232
242
  const orderedColumns = orderVisibleColumns(options.columnDefs, columnOrder);
243
+ const applyWidthOverrides = (columns) => columns.map((col) => {
244
+ const override = columnWidthOverrides[col.name];
245
+ return override == null ? col : { ...col, width: override };
246
+ });
233
247
  const pinnedEntries = Object.entries(pinnedColumns);
234
248
  if (pinnedEntries.length === 0) {
235
- return orderedColumns;
249
+ return applyWidthOverrides(orderedColumns);
236
250
  }
237
251
  const columnByName = new Map(orderedColumns.map((column) => [column.name, column]));
238
252
  const pinnedLeft = pinnedEntries.filter(([, direction]) => direction === "left").map(([columnName]) => columnByName.get(columnName)).filter((column) => column !== void 0);
@@ -240,8 +254,8 @@ function useGridState(options, onRegisterApi) {
240
254
  const centerColumns = orderedColumns.filter(
241
255
  (column) => pinnedColumns[column.name] === void 0
242
256
  );
243
- return [...pinnedLeft, ...centerColumns, ...pinnedRight];
244
- }, [options.columnDefs, columnOrder, pinnedColumns]);
257
+ return applyWidthOverrides([...pinnedLeft, ...centerColumns, ...pinnedRight]);
258
+ }, [options.columnDefs, columnOrder, pinnedColumns, columnWidthOverrides]);
245
259
  const visibleColumnsRef = useRef(visibleColumns);
246
260
  visibleColumnsRef.current = visibleColumns;
247
261
  const pipeline = useMemo(() => {
@@ -378,11 +392,12 @@ function useGridState(options, onRegisterApi) {
378
392
  if (retry) requestAnimationFrame(() => doFocus(false));
379
393
  return;
380
394
  }
381
- target.focus();
395
+ target.focus({ preventScroll: true });
382
396
  if (retry && container.ownerDocument.activeElement !== target) {
383
397
  requestAnimationFrame(() => doFocus(false));
384
398
  }
385
399
  };
400
+ doFocus(true);
386
401
  queueMicrotask(() => doFocus(true));
387
402
  }, []);
388
403
  const focusEditorInput = useCallback((focusToken) => {
@@ -710,8 +725,8 @@ function useGridState(options, onRegisterApi) {
710
725
  gridApiRef.current,
711
726
  {
712
727
  setFocusedCell: (fc) => setFocusedCell(fc),
713
- setEditingCell: (ec2) => setEditingCell(ec2),
714
- setEditingValue: (ev) => setEditingValue(ev)
728
+ setEditingCell: setEditingCellState,
729
+ setEditingValue: setEditingValueState
715
730
  },
716
731
  row,
717
732
  column,
@@ -723,15 +738,15 @@ function useGridState(options, onRegisterApi) {
723
738
  queueMicrotask(() => focusEditorInput(focusToken));
724
739
  }
725
740
  },
726
- [focusEditorInput]
741
+ [focusEditorInput, setEditingCellState, setEditingValueState]
727
742
  );
728
743
  const commitCellEditFn = useCallback(
729
744
  (direction, restoreFocus = true) => {
730
745
  const result = commitGridCellEditCommand(gridApiRef.current, {
731
746
  getEditingCell: () => editingCellRef.current,
732
747
  getEditingValue: () => editingValueRef.current,
733
- setEditingCell: (ec) => setEditingCell(ec),
734
- setEditingValue: (ev) => setEditingValue(ev),
748
+ setEditingCell: setEditingCellState,
749
+ setEditingValue: setEditingValueState,
735
750
  findRowById: (rowId) => coreFindGridRowById(buildRowsFromData(optionsRef.current.data), rowId),
736
751
  findColumnByName: (columnName) => visibleColumnsRef.current.find((c) => c.name === columnName),
737
752
  parseEditedValue: (column, value, oldValue) => parseGridEditedValue(column, value, oldValue),
@@ -749,25 +764,25 @@ function useGridState(options, onRegisterApi) {
749
764
  focusRenderedCell(result.focusTarget);
750
765
  }
751
766
  },
752
- [buildRowsFromData, focusRenderedCell]
767
+ [buildRowsFromData, focusRenderedCell, setEditingCellState, setEditingValueState]
753
768
  );
754
769
  const cancelCellEditFn = useCallback(() => {
755
770
  const hadEditingCell = editingCellRef.current !== null;
756
771
  const result = cancelGridCellEditCommand(gridApiRef.current, {
757
772
  getEditingCell: () => editingCellRef.current,
758
- setEditingCell: (ec) => setEditingCell(ec),
759
- setEditingValue: (ev) => setEditingValue(ev),
773
+ setEditingCell: setEditingCellState,
774
+ setEditingValue: setEditingValueState,
760
775
  findRowById: (rowId) => coreFindGridRowById(buildRowsFromData(optionsRef.current.data), rowId),
761
776
  findColumnByName: (columnName) => visibleColumnsRef.current.find((c) => c.name === columnName)
762
777
  });
763
778
  if (!hadEditingCell) return;
764
779
  editorFocusTokenRef.current += 1;
765
780
  if (result.focusTarget) focusRenderedCell(result.focusTarget);
766
- }, [buildRowsFromData, focusRenderedCell]);
781
+ }, [buildRowsFromData, focusRenderedCell, setEditingCellState, setEditingValueState]);
767
782
  const moveFocusFn = useCallback(
768
783
  (row, column, direction, triggerEvent) => {
769
784
  const nextCell = findNextGridCell({
770
- rows: pipelineRef.current.visibleRows,
785
+ rows: pipelineRef.current.displayItems.filter((item) => item.kind === "row").map((item) => item.row),
771
786
  columns: visibleColumnsRef.current,
772
787
  rowId: row.id,
773
788
  columnName: column.name,
@@ -844,8 +859,8 @@ function useGridState(options, onRegisterApi) {
844
859
  setHiddenRowReasons({});
845
860
  setCollapsedGroups({});
846
861
  setFocusedCell(null);
847
- setEditingCell(null);
848
- setEditingValue("");
862
+ setEditingCellState(null);
863
+ setEditingValueState("");
849
864
  setExpandedRows({});
850
865
  setExpandedTreeRows({});
851
866
  setColumnOrder(options.columnDefs.map((column) => column.name));
@@ -879,7 +894,7 @@ function useGridState(options, onRegisterApi) {
879
894
  }
880
895
  }, [pipeline, gridApi, rowSize]);
881
896
  useEffect(() => {
882
- if (!FEATURE_AUTO_RESIZE || !options.enableAutoResize) return;
897
+ if (!FEATURE_AUTO_RESIZE) return;
883
898
  const container = gridContainerRef.current;
884
899
  if (!container) return;
885
900
  const observer = observeGridHostSize(container, ({ height: nextHeight, width: nextWidth }) => {
@@ -981,6 +996,9 @@ function useGridState(options, onRegisterApi) {
981
996
  const isFocusedCellFn = useCallback((row, column) => {
982
997
  return isGridCellPosition(focusedCellRef.current, row.id, column.name);
983
998
  }, []);
999
+ const isFocusedRowFn = useCallback((row) => {
1000
+ return focusedCellRef.current?.rowId === row.id || editingCellRef.current?.rowId === row.id;
1001
+ }, []);
984
1002
  const isEditingCellFn = useCallback((row, column) => {
985
1003
  return isGridCellPosition(editingCellRef.current, row.id, column.name);
986
1004
  }, []);
@@ -1104,34 +1122,45 @@ function useGridState(options, onRegisterApi) {
1104
1122
  );
1105
1123
  const handleCellKeyDownFn = useCallback(
1106
1124
  (row, column, event) => {
1107
- focusCellFn(row, column, event.nativeEvent);
1125
+ if (isGridNavigationKey(event.key)) {
1126
+ setFocusedCell({ rowId: row.id, columnName: column.name });
1127
+ } else {
1128
+ focusCellFn(row, column, event.nativeEvent);
1129
+ }
1108
1130
  switch (event.key) {
1109
1131
  case "ArrowLeft":
1110
1132
  event.preventDefault();
1133
+ event.stopPropagation();
1111
1134
  moveFocusFn(row, column, "left", event.nativeEvent);
1112
1135
  return;
1113
1136
  case "ArrowRight":
1114
1137
  event.preventDefault();
1138
+ event.stopPropagation();
1115
1139
  moveFocusFn(row, column, "right", event.nativeEvent);
1116
1140
  return;
1117
1141
  case "ArrowUp":
1118
1142
  event.preventDefault();
1143
+ event.stopPropagation();
1119
1144
  moveFocusFn(row, column, "up", event.nativeEvent);
1120
1145
  return;
1121
1146
  case "ArrowDown":
1122
1147
  event.preventDefault();
1148
+ event.stopPropagation();
1123
1149
  moveFocusFn(row, column, "down", event.nativeEvent);
1124
1150
  return;
1125
1151
  case "Tab":
1126
1152
  event.preventDefault();
1153
+ event.stopPropagation();
1127
1154
  moveFocusFn(row, column, event.shiftKey ? "left" : "right", event.nativeEvent);
1128
1155
  return;
1129
1156
  case "Enter":
1130
1157
  event.preventDefault();
1158
+ event.stopPropagation();
1131
1159
  moveFocusFn(row, column, event.shiftKey ? "up" : "down", event.nativeEvent);
1132
1160
  return;
1133
1161
  case "F2":
1134
1162
  event.preventDefault();
1163
+ event.stopPropagation();
1135
1164
  if (isCellEditable(row, column, event.nativeEvent)) {
1136
1165
  startCellEditFn(row, column, event.nativeEvent);
1137
1166
  }
@@ -1140,6 +1169,7 @@ function useGridState(options, onRegisterApi) {
1140
1169
  case "Delete":
1141
1170
  if (isCellEditable(row, column, event.nativeEvent)) {
1142
1171
  event.preventDefault();
1172
+ event.stopPropagation();
1143
1173
  startCellEditFn(row, column, event.nativeEvent, "");
1144
1174
  }
1145
1175
  return;
@@ -1148,6 +1178,7 @@ function useGridState(options, onRegisterApi) {
1148
1178
  }
1149
1179
  if (isPrintableGridKey(event.key, event.ctrlKey, event.metaKey, event.altKey) && isCellEditable(row, column, event.nativeEvent)) {
1150
1180
  event.preventDefault();
1181
+ event.stopPropagation();
1151
1182
  startCellEditFn(row, column, event.nativeEvent, event.key);
1152
1183
  }
1153
1184
  },
@@ -1163,23 +1194,32 @@ function useGridState(options, onRegisterApi) {
1163
1194
  [focusCellFn, isCellEditable, startCellEditFn]
1164
1195
  );
1165
1196
  const updateEditingValueFn = useCallback((value) => {
1166
- setEditingValue(value);
1167
- }, []);
1197
+ setEditingValueState(value);
1198
+ }, [setEditingValueState]);
1168
1199
  const handleEditorKeyDownFn = useCallback(
1169
1200
  (event) => {
1170
1201
  if (event.key === "Escape") {
1171
1202
  event.preventDefault();
1203
+ event.stopPropagation();
1172
1204
  cancelCellEditFn();
1173
1205
  return;
1174
1206
  }
1175
1207
  if (event.key === "Enter") {
1176
1208
  event.preventDefault();
1209
+ event.stopPropagation();
1177
1210
  commitCellEditFn(event.shiftKey ? "up" : "down");
1178
1211
  return;
1179
1212
  }
1180
1213
  if (event.key === "Tab") {
1181
1214
  event.preventDefault();
1215
+ event.stopPropagation();
1182
1216
  commitCellEditFn(event.shiftKey ? "left" : "right");
1217
+ return;
1218
+ }
1219
+ if (event.key === "ArrowUp" || event.key === "ArrowDown") {
1220
+ event.preventDefault();
1221
+ event.stopPropagation();
1222
+ commitCellEditFn(event.key === "ArrowUp" ? "up" : "down");
1183
1223
  }
1184
1224
  },
1185
1225
  [cancelCellEditFn, commitCellEditFn]
@@ -1241,6 +1281,72 @@ function useGridState(options, onRegisterApi) {
1241
1281
  },
1242
1282
  [setPaginationPageSizeFn]
1243
1283
  );
1284
+ const canResizeColumnsFn = useCallback(() => {
1285
+ return optionsRef.current.enableColumnResizing !== false;
1286
+ }, []);
1287
+ const setColumnWidthOverrideFn = useCallback((columnName, widthPx) => {
1288
+ const nextWidth = `${Math.max(88, Math.round(widthPx))}px`;
1289
+ setColumnWidthOverrides((current) => ({ ...current, [columnName]: nextWidth }));
1290
+ }, []);
1291
+ const measureAutoColumnWidthFn = useCallback((columnName) => {
1292
+ const container = gridContainerRef.current;
1293
+ if (container == null) return 176;
1294
+ const escaped = CSS.escape ? CSS.escape(columnName) : columnName.replace(/([\\".#:[\](){}+~> ])/g, "\\$1");
1295
+ const selectors = [
1296
+ `.header-cell[data-col-name="${escaped}"]`,
1297
+ `.filter-cell[data-col-name="${escaped}"]`,
1298
+ `.body-cell[data-col-name="${escaped}"] .cell-shell`
1299
+ ];
1300
+ let maxWidth = 0;
1301
+ for (const selector of selectors) {
1302
+ const elements = container.querySelectorAll(selector);
1303
+ for (const element of elements) {
1304
+ maxWidth = Math.max(maxWidth, element.scrollWidth);
1305
+ }
1306
+ }
1307
+ return maxWidth + 12;
1308
+ }, []);
1309
+ const handleHeaderResizeMouseDownFn = useCallback(
1310
+ (column, event) => {
1311
+ if (!canResizeColumnsFn()) return;
1312
+ event.preventDefault();
1313
+ event.stopPropagation();
1314
+ const headerCell = event.currentTarget.closest(".header-cell");
1315
+ if (headerCell == null) return;
1316
+ const startX = event.clientX;
1317
+ const startWidth = headerCell.getBoundingClientRect().width;
1318
+ let lastWidth = startWidth;
1319
+ const handleMove = (moveEvent) => {
1320
+ lastWidth = Math.max(88, startWidth + (moveEvent.clientX - startX));
1321
+ const widthStr = `${Math.round(lastWidth)}px`;
1322
+ const newTemplate = buildGridTemplateColumns(
1323
+ visibleColumnsRef.current.map(
1324
+ (c) => c.name === column.name ? { ...c, width: widthStr } : c
1325
+ )
1326
+ );
1327
+ gridContainerRef.current?.querySelectorAll(".header-grid, .filter-grid, .body-grid").forEach((el) => {
1328
+ el.style.gridTemplateColumns = newTemplate;
1329
+ });
1330
+ };
1331
+ const handleUp = () => {
1332
+ window.removeEventListener("mousemove", handleMove);
1333
+ window.removeEventListener("mouseup", handleUp);
1334
+ setColumnWidthOverrideFn(column.name, lastWidth);
1335
+ };
1336
+ window.addEventListener("mousemove", handleMove);
1337
+ window.addEventListener("mouseup", handleUp);
1338
+ },
1339
+ [canResizeColumnsFn, setColumnWidthOverrideFn]
1340
+ );
1341
+ const autoSizeColumnFn = useCallback(
1342
+ (column, event) => {
1343
+ if (!canResizeColumnsFn()) return;
1344
+ event.preventDefault();
1345
+ event.stopPropagation();
1346
+ setColumnWidthOverrideFn(column.name, measureAutoColumnWidthFn(column.name));
1347
+ },
1348
+ [canResizeColumnsFn, setColumnWidthOverrideFn, measureAutoColumnWidthFn]
1349
+ );
1244
1350
  const onViewportScrollFn = useCallback((startIndex) => {
1245
1351
  if (!scrollingRef.current) {
1246
1352
  scrollingRef.current = true;
@@ -1298,6 +1404,7 @@ function useGridState(options, onRegisterApi) {
1298
1404
  paginationSelectedPageSize,
1299
1405
  rowSize,
1300
1406
  viewportHeightPx,
1407
+ autoViewportHeight,
1301
1408
  headerLabel: headerLabelFn,
1302
1409
  isGroupItem: isGroupItemFn,
1303
1410
  isExpandableItem: isExpandableItemFn,
@@ -1313,6 +1420,7 @@ function useGridState(options, onRegisterApi) {
1313
1420
  groupDisclosureLabel: groupDisclosureLabelFn,
1314
1421
  displayValue: displayValueFn,
1315
1422
  isFocusedCell: isFocusedCellFn,
1423
+ isFocusedRow: isFocusedRowFn,
1316
1424
  isEditingCell: isEditingCellFn,
1317
1425
  editorInputType: editorInputTypeFn,
1318
1426
  cellContext: cellContextFn,
@@ -1359,6 +1467,9 @@ function useGridState(options, onRegisterApi) {
1359
1467
  toggleTreeRow: toggleTreeRowFn,
1360
1468
  moveColumn: moveColumnFn,
1361
1469
  moveVisibleColumn: moveVisibleColumnFn,
1470
+ canResizeColumns: canResizeColumnsFn,
1471
+ handleHeaderResizeMouseDown: handleHeaderResizeMouseDownFn,
1472
+ autoSizeColumn: autoSizeColumnFn,
1362
1473
  nextPage: nextPageFn,
1363
1474
  previousPage: previousPageFn,
1364
1475
  onPageSizeChange: onPageSizeChangeFn,
@@ -1446,6 +1557,7 @@ function UiGrid({
1446
1557
  virtualizationEnabled,
1447
1558
  rowSize,
1448
1559
  editingValue,
1560
+ autoViewportHeight,
1449
1561
  sortingFeature,
1450
1562
  filteringFeature,
1451
1563
  groupingFeature,
@@ -1463,10 +1575,8 @@ function UiGrid({
1463
1575
  const [headerStickyHeight, setHeaderStickyHeight] = React.useState(0);
1464
1576
  const [filterStickyHeight, setFilterStickyHeight] = React.useState(0);
1465
1577
  const stickyChromeHeight = headerStickyHeight + filterStickyHeight;
1466
- const bodyViewportHeight = Math.max(
1467
- rowSize,
1468
- (options.viewportHeight ?? 560) - stickyChromeHeight
1469
- );
1578
+ const resolvedViewportHeight = options.viewportHeight ?? (autoViewportHeight && autoViewportHeight > 0 ? autoViewportHeight : 560);
1579
+ const bodyViewportHeight = Math.max(rowSize, resolvedViewportHeight - stickyChromeHeight);
1470
1580
  const virtualScroll = useVirtualScroll({
1471
1581
  itemCount: displayItems.length,
1472
1582
  itemSize: rowSize,
@@ -1476,7 +1586,7 @@ function UiGrid({
1476
1586
  const [openPinMenuColumn, setOpenPinMenuColumn] = React.useState(null);
1477
1587
  const [draggedColumnName, setDraggedColumnName] = React.useState(null);
1478
1588
  const [dropTargetColumnName, setDropTargetColumnName] = React.useState(null);
1479
- const scrollContainerHeight = `${options.viewportHeight ?? 560}px`;
1589
+ const scrollContainerHeight = `${resolvedViewportHeight}px`;
1480
1590
  function renderHeaderContent(column) {
1481
1591
  const value = state.headerLabel(column);
1482
1592
  const context = {
@@ -1782,6 +1892,7 @@ function UiGrid({
1782
1892
  if (column.align === "center") classes.push("align-center");
1783
1893
  if (column.align === "end") classes.push("align-end");
1784
1894
  if (state.isFocusedCell(item.row, column)) classes.push("cell-focused");
1895
+ if (state.isFocusedRow(item.row)) classes.push("row-focused");
1785
1896
  if (cellEditFeature && state.isEditingCell(item.row, column)) classes.push("cell-editing");
1786
1897
  return classes.join(" ");
1787
1898
  }
@@ -1829,7 +1940,7 @@ function UiGrid({
1829
1940
  {
1830
1941
  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" : ""}`,
1831
1942
  "data-part": "header-cell",
1832
- role: "columnheader",
1943
+ "data-col-name": column.name,
1833
1944
  "aria-sort": sortingFeature ? state.sortAriaSort(column) : void 0,
1834
1945
  draggable: columnMovingFeature,
1835
1946
  onDragStart: (event) => handleHeaderDragStart(column, event),
@@ -1952,7 +2063,19 @@ function UiGrid({
1952
2063
  ]
1953
2064
  }
1954
2065
  )
1955
- ] })
2066
+ ] }),
2067
+ state.canResizeColumns() && /* @__PURE__ */ jsx(
2068
+ "button",
2069
+ {
2070
+ type: "button",
2071
+ className: "column-resizer",
2072
+ "data-col-name": column.name,
2073
+ "aria-label": `Resize ${state.headerLabel(column)} column`,
2074
+ title: "Drag to resize, double-click to auto fit",
2075
+ onMouseDown: (event) => state.handleHeaderResizeMouseDown(column, event),
2076
+ onDoubleClick: (event) => state.autoSizeColumn(column, event)
2077
+ }
2078
+ )
1956
2079
  ]
1957
2080
  },
1958
2081
  column.name
package/dist/ui-grid.css CHANGED
@@ -1,53 +1,142 @@
1
1
  .ui-grid-host {
2
- --ui-grid-border-color: var(--app-ui-grid-border-color, #d4d4d8);
3
- --ui-grid-header-background: var(--app-ui-grid-header-background, #f3f4f6);
4
- --ui-grid-row-odd: var(--app-ui-grid-row-odd, #fcfcfd);
5
- --ui-grid-row-even: var(--app-ui-grid-row-even, #f7f7f8);
6
- --ui-grid-row-hover: var(--app-ui-grid-row-hover, #eef4ff);
7
- --ui-grid-cell-color: var(--app-ui-grid-cell-color, #111827);
8
- --ui-grid-muted-color: var(--app-ui-grid-muted-color, #6b7280);
9
- --ui-grid-surface: var(--app-ui-grid-surface, #ffffff);
10
- --ui-grid-radius: var(--app-ui-grid-radius, 4px);
11
- --ui-grid-shadow: var(--app-ui-grid-shadow, 0 10px 24px rgba(15, 23, 42, 0.08));
12
- --ui-grid-header-weight: var(--app-ui-grid-header-weight, 700);
13
- --ui-grid-accent: var(--app-ui-grid-accent, #2563eb);
14
- --ui-grid-group-background: var(--app-ui-grid-group-background, #eceff3);
15
- --ui-grid-status-active-bg: var(--app-ui-grid-status-active-bg, rgba(22, 163, 74, 0.14));
16
- --ui-grid-status-active-color: var(--app-ui-grid-status-active-color, #166534);
17
- --ui-grid-status-expansion-bg: var(--app-ui-grid-status-expansion-bg, rgba(37, 99, 235, 0.14));
18
- --ui-grid-status-expansion-color: var(--app-ui-grid-status-expansion-color, #1d4ed8);
19
- --ui-grid-status-enterprise-bg: var(--app-ui-grid-status-enterprise-bg, rgba(15, 118, 110, 0.14));
20
- --ui-grid-status-enterprise-color: var(--app-ui-grid-status-enterprise-color, #115e59);
21
- --ui-grid-status-pilot-bg: var(--app-ui-grid-status-pilot-bg, rgba(234, 88, 12, 0.14));
22
- --ui-grid-status-pilot-color: var(--app-ui-grid-status-pilot-color, #c2410c);
23
- --ui-grid-pin-menu-open-z-index: var(--app-ui-grid-pin-menu-open-z-index, 8);
24
- --ui-grid-pin-menu-z-index: var(--app-ui-grid-pin-menu-z-index, 20);
25
- --ui-grid-pin-menu-gap: var(--app-ui-grid-pin-menu-gap, 0.25rem);
26
- --ui-grid-pin-menu-padding: var(--app-ui-grid-pin-menu-padding, 0.25rem);
27
- --ui-grid-pin-menu-radius: var(--app-ui-grid-pin-menu-radius, 999px);
28
- --ui-grid-pin-menu-shadow: var(
29
- --app-ui-grid-pin-menu-shadow,
30
- 0 10px 24px color-mix(in srgb, var(--ui-grid-cell-color) 10%, transparent)
2
+ /* Stretch to fill the parent so the autoresize observer can measure a
3
+ deterministic height. Consumers can override these properties to opt
4
+ back into intrinsic sizing. */
5
+ display: flex;
6
+ flex-direction: column;
7
+ min-height: 0;
8
+ height: 100%;
9
+ --_ui-grid-border-color: var(--ui-grid-border-color, var(--app-ui-grid-border-color, #d4d4d8));
10
+ --_ui-grid-header-background: var(
11
+ --ui-grid-header-background,
12
+ var(--app-ui-grid-header-background, #f3f4f6)
13
+ );
14
+ --_ui-grid-row-odd: var(--ui-grid-row-odd, var(--app-ui-grid-row-odd, #fcfcfd));
15
+ --_ui-grid-row-even: var(--ui-grid-row-even, var(--app-ui-grid-row-even, #f7f7f8));
16
+ --_ui-grid-row-hover: var(--ui-grid-row-hover, var(--app-ui-grid-row-hover, #eef4ff));
17
+ --_ui-grid-cell-color: var(--ui-grid-cell-color, var(--app-ui-grid-cell-color, #111827));
18
+ --_ui-grid-muted-color: var(--ui-grid-muted-color, var(--app-ui-grid-muted-color, #6b7280));
19
+ --_ui-grid-surface: var(--ui-grid-surface, var(--app-ui-grid-surface, #ffffff));
20
+ --_ui-grid-radius: var(--ui-grid-radius, var(--app-ui-grid-radius, 4px));
21
+ --_ui-grid-shadow: var(
22
+ --ui-grid-shadow,
23
+ var(--app-ui-grid-shadow, 0 10px 24px rgba(15, 23, 42, 0.08))
24
+ );
25
+ --_ui-grid-header-weight: var(--ui-grid-header-weight, var(--app-ui-grid-header-weight, 700));
26
+ --_ui-grid-accent: var(--ui-grid-accent, var(--app-ui-grid-accent, #2563eb));
27
+ --_ui-grid-group-background: var(
28
+ --ui-grid-group-background,
29
+ var(--app-ui-grid-group-background, #eceff3)
30
+ );
31
+ --_ui-grid-status-active-bg: var(
32
+ --ui-grid-status-active-bg,
33
+ var(--app-ui-grid-status-active-bg, rgba(22, 163, 74, 0.14))
34
+ );
35
+ --_ui-grid-status-active-color: var(
36
+ --ui-grid-status-active-color,
37
+ var(--app-ui-grid-status-active-color, #166534)
38
+ );
39
+ --_ui-grid-status-expansion-bg: var(
40
+ --ui-grid-status-expansion-bg,
41
+ var(--app-ui-grid-status-expansion-bg, rgba(37, 99, 235, 0.14))
42
+ );
43
+ --_ui-grid-status-expansion-color: var(
44
+ --ui-grid-status-expansion-color,
45
+ var(--app-ui-grid-status-expansion-color, #1d4ed8)
46
+ );
47
+ --_ui-grid-status-enterprise-bg: var(
48
+ --ui-grid-status-enterprise-bg,
49
+ var(--app-ui-grid-status-enterprise-bg, rgba(15, 118, 110, 0.14))
50
+ );
51
+ --_ui-grid-status-enterprise-color: var(
52
+ --ui-grid-status-enterprise-color,
53
+ var(--app-ui-grid-status-enterprise-color, #115e59)
31
54
  );
32
- --ui-grid-pin-menu-action-size: var(--app-ui-grid-pin-menu-action-size, 1.75rem);
33
- --ui-grid-pin-control-collapsed-size: var(--app-ui-grid-pin-control-collapsed-size, 1px);
34
- --ui-grid-pin-control-transition-duration: var(
35
- --app-ui-grid-pin-control-transition-duration,
36
- 160ms
55
+ --_ui-grid-status-pilot-bg: var(
56
+ --ui-grid-status-pilot-bg,
57
+ var(--app-ui-grid-status-pilot-bg, rgba(234, 88, 12, 0.14))
37
58
  );
38
- --ui-grid-pin-control-transition-easing: var(
39
- --app-ui-grid-pin-control-transition-easing,
40
- cubic-bezier(0.22, 1, 0.36, 1)
59
+ --_ui-grid-status-pilot-color: var(
60
+ --ui-grid-status-pilot-color,
61
+ var(--app-ui-grid-status-pilot-color, #c2410c)
62
+ );
63
+ --_ui-grid-pin-menu-open-z-index: var(
64
+ --ui-grid-pin-menu-open-z-index,
65
+ var(--app-ui-grid-pin-menu-open-z-index, 8)
66
+ );
67
+ --_ui-grid-pin-menu-z-index: var(--ui-grid-pin-menu-z-index, var(--app-ui-grid-pin-menu-z-index, 20));
68
+ --_ui-grid-pin-menu-gap: var(--ui-grid-pin-menu-gap, var(--app-ui-grid-pin-menu-gap, 0.25rem));
69
+ --_ui-grid-pin-menu-padding: var(
70
+ --ui-grid-pin-menu-padding,
71
+ var(--app-ui-grid-pin-menu-padding, 0.25rem)
72
+ );
73
+ --_ui-grid-pin-menu-radius: var(--ui-grid-pin-menu-radius, var(--app-ui-grid-pin-menu-radius, 999px));
74
+ --_ui-grid-pin-menu-shadow: var(
75
+ --ui-grid-pin-menu-shadow,
76
+ var(
77
+ --app-ui-grid-pin-menu-shadow,
78
+ 0 10px 24px color-mix(in srgb, var(--_ui-grid-cell-color) 10%, transparent)
79
+ )
80
+ );
81
+ --_ui-grid-pin-menu-action-size: var(
82
+ --ui-grid-pin-menu-action-size,
83
+ var(--app-ui-grid-pin-menu-action-size, 1.75rem)
84
+ );
85
+ --_ui-grid-pin-control-collapsed-size: var(
86
+ --ui-grid-pin-control-collapsed-size,
87
+ var(--app-ui-grid-pin-control-collapsed-size, 1px)
88
+ );
89
+ --_ui-grid-pin-control-transition-duration: var(
90
+ --ui-grid-pin-control-transition-duration,
91
+ var(--app-ui-grid-pin-control-transition-duration, 160ms)
92
+ );
93
+ --_ui-grid-pin-control-transition-easing: var(
94
+ --ui-grid-pin-control-transition-easing,
95
+ var(--app-ui-grid-pin-control-transition-easing, cubic-bezier(0.22, 1, 0.36, 1))
96
+ );
97
+ --_ui-grid-pin-menu-scale-closed: var(
98
+ --ui-grid-pin-menu-scale-closed,
99
+ var(--app-ui-grid-pin-menu-scale-closed, 0.72)
41
100
  );
42
- --ui-grid-pin-menu-scale-closed: var(--app-ui-grid-pin-menu-scale-closed, 0.72);
43
101
  display: block;
44
- color: var(--ui-grid-cell-color);
102
+ color: var(--_ui-grid-cell-color);
45
103
  }
46
104
 
47
- .ui-grid-host,
48
105
  .ui-grid-host *,
49
106
  .ui-grid-host *::before,
50
107
  .ui-grid-host *::after {
108
+ --ui-grid-border-color: var(--_ui-grid-border-color);
109
+ --ui-grid-header-background: var(--_ui-grid-header-background);
110
+ --ui-grid-row-odd: var(--_ui-grid-row-odd);
111
+ --ui-grid-row-even: var(--_ui-grid-row-even);
112
+ --ui-grid-row-hover: var(--_ui-grid-row-hover);
113
+ --ui-grid-cell-color: var(--_ui-grid-cell-color);
114
+ --ui-grid-muted-color: var(--_ui-grid-muted-color);
115
+ --ui-grid-surface: var(--_ui-grid-surface);
116
+ --ui-grid-radius: var(--_ui-grid-radius);
117
+ --ui-grid-shadow: var(--_ui-grid-shadow);
118
+ --ui-grid-header-weight: var(--_ui-grid-header-weight);
119
+ --ui-grid-accent: var(--_ui-grid-accent);
120
+ --ui-grid-group-background: var(--_ui-grid-group-background);
121
+ --ui-grid-status-active-bg: var(--_ui-grid-status-active-bg);
122
+ --ui-grid-status-active-color: var(--_ui-grid-status-active-color);
123
+ --ui-grid-status-expansion-bg: var(--_ui-grid-status-expansion-bg);
124
+ --ui-grid-status-expansion-color: var(--_ui-grid-status-expansion-color);
125
+ --ui-grid-status-enterprise-bg: var(--_ui-grid-status-enterprise-bg);
126
+ --ui-grid-status-enterprise-color: var(--_ui-grid-status-enterprise-color);
127
+ --ui-grid-status-pilot-bg: var(--_ui-grid-status-pilot-bg);
128
+ --ui-grid-status-pilot-color: var(--_ui-grid-status-pilot-color);
129
+ --ui-grid-pin-menu-open-z-index: var(--_ui-grid-pin-menu-open-z-index);
130
+ --ui-grid-pin-menu-z-index: var(--_ui-grid-pin-menu-z-index);
131
+ --ui-grid-pin-menu-gap: var(--_ui-grid-pin-menu-gap);
132
+ --ui-grid-pin-menu-padding: var(--_ui-grid-pin-menu-padding);
133
+ --ui-grid-pin-menu-radius: var(--_ui-grid-pin-menu-radius);
134
+ --ui-grid-pin-menu-shadow: var(--_ui-grid-pin-menu-shadow);
135
+ --ui-grid-pin-menu-action-size: var(--_ui-grid-pin-menu-action-size);
136
+ --ui-grid-pin-control-collapsed-size: var(--_ui-grid-pin-control-collapsed-size);
137
+ --ui-grid-pin-control-transition-duration: var(--_ui-grid-pin-control-transition-duration);
138
+ --ui-grid-pin-control-transition-easing: var(--_ui-grid-pin-control-transition-easing);
139
+ --ui-grid-pin-menu-scale-closed: var(--_ui-grid-pin-menu-scale-closed);
51
140
  box-sizing: border-box;
52
141
  }
53
142
 
@@ -139,6 +228,12 @@
139
228
  border: 1px solid var(--ui-grid-border-color);
140
229
  box-shadow: var(--ui-grid-shadow);
141
230
  overflow: hidden;
231
+ /* Allow the frame to grow inside the flexed host so the body grid can fill
232
+ the available height when no explicit viewportHeight is set. */
233
+ display: flex;
234
+ flex-direction: column;
235
+ flex: 1 1 auto;
236
+ min-height: 0;
142
237
  }
143
238
 
144
239
  .metrics-strip {
@@ -231,6 +326,7 @@
231
326
  background: var(--ui-grid-header-background);
232
327
  font-weight: var(--ui-grid-header-weight);
233
328
  align-items: center;
329
+ position: relative;
234
330
  }
235
331
 
236
332
  .header-cell[draggable='true'] {
@@ -255,6 +351,38 @@
255
351
  z-index: var(--ui-grid-pin-menu-open-z-index);
256
352
  }
257
353
 
354
+ .column-resizer {
355
+ position: absolute;
356
+ top: 0;
357
+ right: -4px;
358
+ width: 8px;
359
+ height: 100%;
360
+ padding: 0;
361
+ border: 0;
362
+ background: transparent;
363
+ cursor: col-resize;
364
+ z-index: 5;
365
+ }
366
+
367
+ .column-resizer::after {
368
+ content: '';
369
+ position: absolute;
370
+ top: 20%;
371
+ bottom: 20%;
372
+ left: 50%;
373
+ width: 2px;
374
+ transform: translateX(-50%);
375
+ border-radius: 999px;
376
+ background: color-mix(in srgb, var(--ui-grid-border-color, #888) 80%, transparent);
377
+ opacity: 0;
378
+ transition: opacity 120ms ease;
379
+ }
380
+
381
+ .header-cell:hover .column-resizer::after,
382
+ .column-resizer:focus-visible::after {
383
+ opacity: 1;
384
+ }
385
+
258
386
  .header-label {
259
387
  min-width: 0;
260
388
  display: block;