@optilogic/core 1.3.0 → 1.3.1

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.d.cts CHANGED
@@ -1628,6 +1628,10 @@ interface DataGridProps<T = Record<string, CellValue>> {
1628
1628
  tooltipMinLength?: number;
1629
1629
  /** Show column dividing borders between cells (default: true) */
1630
1630
  showColumnBorders?: boolean;
1631
+ /** When true, columns scale proportionally to fill the container width.
1632
+ * Columns shrink/grow to keep total width = container width.
1633
+ * Horizontal scroll only kicks in if columns hit their minWidth constraints. */
1634
+ fillWidth?: boolean;
1631
1635
  /** Enable internal sorting when in uncontrolled mode (default: true) */
1632
1636
  enableInternalSorting?: boolean;
1633
1637
  /** Enable internal filtering when in uncontrolled mode (default: true) */
@@ -1741,6 +1745,7 @@ interface HeaderCellProps$1<T = Record<string, CellValue>> {
1741
1745
  sorting?: SortConfig;
1742
1746
  filter?: FilterConfig;
1743
1747
  isResizable: boolean;
1748
+ fillWidth?: boolean;
1744
1749
  onSort?: () => void;
1745
1750
  onFilterChange?: (filter: FilterConfig | null) => void;
1746
1751
  onResize?: (width: number) => void;
@@ -1786,7 +1791,7 @@ interface GridCellProps<T = Record<string, CellValue>> {
1786
1791
  /**
1787
1792
  * DataGrid Component
1788
1793
  */
1789
- declare function DataGrid<T = Record<string, CellValue>>({ data, columns, getRowKey, resizableColumns, virtualized, stickyHeader, showTooltips, tooltipMinLength, enableKeyboardNavigation, showColumnBorders, enableInternalSorting, enableInternalFiltering, sorting: controlledSorting, onSortChange, filters: controlledFilters, onFilterChange, columnWidths: controlledColumnWidths, onColumnResize, onColumnResizeStart, onColumnResizeEnd, defaultSorting, defaultFilters, defaultColumnWidths, onStateChange, onRowClick, onRowDoubleClick, selectedRows, onSelectedRowsChange, rowClassName, onCellEdit, onCellEditStart, onCellEditCancel, focusedCell: controlledFocusedCell, onFocusedCellChange, pagination, search, loading, loadingComponent, emptyMessage, emptyComponent, className, tableClassName, infiniteScroll, onLoadMore, hasMore, loadingMore, }: DataGridProps<T>): react_jsx_runtime.JSX.Element;
1794
+ declare function DataGrid<T = Record<string, CellValue>>({ data, columns, getRowKey, resizableColumns, virtualized, stickyHeader, showTooltips, tooltipMinLength, enableKeyboardNavigation, showColumnBorders, fillWidth, enableInternalSorting, enableInternalFiltering, sorting: controlledSorting, onSortChange, filters: controlledFilters, onFilterChange, columnWidths: controlledColumnWidths, onColumnResize, onColumnResizeStart, onColumnResizeEnd, defaultSorting, defaultFilters, defaultColumnWidths, onStateChange, onRowClick, onRowDoubleClick, selectedRows, onSelectedRowsChange, rowClassName, onCellEdit, onCellEditStart, onCellEditCancel, focusedCell: controlledFocusedCell, onFocusedCellChange, pagination, search, loading, loadingComponent, emptyMessage, emptyComponent, className, tableClassName, infiniteScroll, onLoadMore, hasMore, loadingMore, }: DataGridProps<T>): react_jsx_runtime.JSX.Element;
1790
1795
 
1791
1796
  interface HeaderCellProps<T = any> {
1792
1797
  /** Column definition */
@@ -1801,6 +1806,8 @@ interface HeaderCellProps<T = any> {
1801
1806
  filter?: FilterConfig;
1802
1807
  /** Whether this column is resizable */
1803
1808
  isResizable: boolean;
1809
+ /** Whether fillWidth mode is active */
1810
+ fillWidth?: boolean;
1804
1811
  /** Callback when sort is toggled */
1805
1812
  onSort?: () => void;
1806
1813
  /** Callback when filter changes */
@@ -1815,7 +1822,7 @@ interface HeaderCellProps<T = any> {
1815
1822
  /**
1816
1823
  * HeaderCell Component
1817
1824
  */
1818
- declare function HeaderCell<T = any>({ column, columnIndex, width, sorting, filter, isResizable, onSort, onFilterChange, onResizeMouseDown, onResizeDoubleClick, isResizing, }: HeaderCellProps<T>): react_jsx_runtime.JSX.Element;
1825
+ declare function HeaderCell<T = any>({ column, columnIndex, width, sorting, filter, isResizable, fillWidth, onSort, onFilterChange, onResizeMouseDown, onResizeDoubleClick, isResizing, }: HeaderCellProps<T>): react_jsx_runtime.JSX.Element;
1819
1826
 
1820
1827
  interface FilterPopoverProps<T = Record<string, CellValue>> {
1821
1828
  /** Column definition */
@@ -2019,6 +2026,12 @@ interface UseColumnResizeManagerOptions<T = any> {
2019
2026
  onColumnResizeStart?: (columnKey: string) => void;
2020
2027
  /** Callback when resize ends */
2021
2028
  onColumnResizeEnd?: (columnKey: string, width: number) => void;
2029
+ /** Whether fillWidth mode is active */
2030
+ fillWidth?: boolean;
2031
+ /** Measured container width for fillWidth redistribution */
2032
+ containerWidth?: number;
2033
+ /** Effective column widths (after fillWidth scaling) */
2034
+ effectiveColumnWidths?: Record<string, number>;
2022
2035
  }
2023
2036
  interface UseColumnResizeManagerReturn {
2024
2037
  /** Currently resizing column key */
package/dist/index.d.ts CHANGED
@@ -1628,6 +1628,10 @@ interface DataGridProps<T = Record<string, CellValue>> {
1628
1628
  tooltipMinLength?: number;
1629
1629
  /** Show column dividing borders between cells (default: true) */
1630
1630
  showColumnBorders?: boolean;
1631
+ /** When true, columns scale proportionally to fill the container width.
1632
+ * Columns shrink/grow to keep total width = container width.
1633
+ * Horizontal scroll only kicks in if columns hit their minWidth constraints. */
1634
+ fillWidth?: boolean;
1631
1635
  /** Enable internal sorting when in uncontrolled mode (default: true) */
1632
1636
  enableInternalSorting?: boolean;
1633
1637
  /** Enable internal filtering when in uncontrolled mode (default: true) */
@@ -1741,6 +1745,7 @@ interface HeaderCellProps$1<T = Record<string, CellValue>> {
1741
1745
  sorting?: SortConfig;
1742
1746
  filter?: FilterConfig;
1743
1747
  isResizable: boolean;
1748
+ fillWidth?: boolean;
1744
1749
  onSort?: () => void;
1745
1750
  onFilterChange?: (filter: FilterConfig | null) => void;
1746
1751
  onResize?: (width: number) => void;
@@ -1786,7 +1791,7 @@ interface GridCellProps<T = Record<string, CellValue>> {
1786
1791
  /**
1787
1792
  * DataGrid Component
1788
1793
  */
1789
- declare function DataGrid<T = Record<string, CellValue>>({ data, columns, getRowKey, resizableColumns, virtualized, stickyHeader, showTooltips, tooltipMinLength, enableKeyboardNavigation, showColumnBorders, enableInternalSorting, enableInternalFiltering, sorting: controlledSorting, onSortChange, filters: controlledFilters, onFilterChange, columnWidths: controlledColumnWidths, onColumnResize, onColumnResizeStart, onColumnResizeEnd, defaultSorting, defaultFilters, defaultColumnWidths, onStateChange, onRowClick, onRowDoubleClick, selectedRows, onSelectedRowsChange, rowClassName, onCellEdit, onCellEditStart, onCellEditCancel, focusedCell: controlledFocusedCell, onFocusedCellChange, pagination, search, loading, loadingComponent, emptyMessage, emptyComponent, className, tableClassName, infiniteScroll, onLoadMore, hasMore, loadingMore, }: DataGridProps<T>): react_jsx_runtime.JSX.Element;
1794
+ declare function DataGrid<T = Record<string, CellValue>>({ data, columns, getRowKey, resizableColumns, virtualized, stickyHeader, showTooltips, tooltipMinLength, enableKeyboardNavigation, showColumnBorders, fillWidth, enableInternalSorting, enableInternalFiltering, sorting: controlledSorting, onSortChange, filters: controlledFilters, onFilterChange, columnWidths: controlledColumnWidths, onColumnResize, onColumnResizeStart, onColumnResizeEnd, defaultSorting, defaultFilters, defaultColumnWidths, onStateChange, onRowClick, onRowDoubleClick, selectedRows, onSelectedRowsChange, rowClassName, onCellEdit, onCellEditStart, onCellEditCancel, focusedCell: controlledFocusedCell, onFocusedCellChange, pagination, search, loading, loadingComponent, emptyMessage, emptyComponent, className, tableClassName, infiniteScroll, onLoadMore, hasMore, loadingMore, }: DataGridProps<T>): react_jsx_runtime.JSX.Element;
1790
1795
 
1791
1796
  interface HeaderCellProps<T = any> {
1792
1797
  /** Column definition */
@@ -1801,6 +1806,8 @@ interface HeaderCellProps<T = any> {
1801
1806
  filter?: FilterConfig;
1802
1807
  /** Whether this column is resizable */
1803
1808
  isResizable: boolean;
1809
+ /** Whether fillWidth mode is active */
1810
+ fillWidth?: boolean;
1804
1811
  /** Callback when sort is toggled */
1805
1812
  onSort?: () => void;
1806
1813
  /** Callback when filter changes */
@@ -1815,7 +1822,7 @@ interface HeaderCellProps<T = any> {
1815
1822
  /**
1816
1823
  * HeaderCell Component
1817
1824
  */
1818
- declare function HeaderCell<T = any>({ column, columnIndex, width, sorting, filter, isResizable, onSort, onFilterChange, onResizeMouseDown, onResizeDoubleClick, isResizing, }: HeaderCellProps<T>): react_jsx_runtime.JSX.Element;
1825
+ declare function HeaderCell<T = any>({ column, columnIndex, width, sorting, filter, isResizable, fillWidth, onSort, onFilterChange, onResizeMouseDown, onResizeDoubleClick, isResizing, }: HeaderCellProps<T>): react_jsx_runtime.JSX.Element;
1819
1826
 
1820
1827
  interface FilterPopoverProps<T = Record<string, CellValue>> {
1821
1828
  /** Column definition */
@@ -2019,6 +2026,12 @@ interface UseColumnResizeManagerOptions<T = any> {
2019
2026
  onColumnResizeStart?: (columnKey: string) => void;
2020
2027
  /** Callback when resize ends */
2021
2028
  onColumnResizeEnd?: (columnKey: string, width: number) => void;
2029
+ /** Whether fillWidth mode is active */
2030
+ fillWidth?: boolean;
2031
+ /** Measured container width for fillWidth redistribution */
2032
+ containerWidth?: number;
2033
+ /** Effective column widths (after fillWidth scaling) */
2034
+ effectiveColumnWidths?: Record<string, number>;
2022
2035
  }
2023
2036
  interface UseColumnResizeManagerReturn {
2024
2037
  /** Currently resizing column key */
package/dist/index.js CHANGED
@@ -3434,6 +3434,7 @@ function HeaderCell({
3434
3434
  sorting,
3435
3435
  filter,
3436
3436
  isResizable,
3437
+ fillWidth,
3437
3438
  onSort,
3438
3439
  onFilterChange,
3439
3440
  onResizeMouseDown,
@@ -3477,7 +3478,8 @@ function HeaderCell({
3477
3478
  "div",
3478
3479
  {
3479
3480
  className: cn(
3480
- "relative flex-shrink-0 border-r border-border last:border-r-0",
3481
+ "relative border-r border-border last:border-r-0",
3482
+ !fillWidth && "flex-shrink-0",
3481
3483
  "bg-muted select-none",
3482
3484
  isResizing && "bg-accent/20"
3483
3485
  ),
@@ -4417,11 +4419,15 @@ function useColumnResizeManager(options) {
4417
4419
  resizableColumns,
4418
4420
  onColumnResize,
4419
4421
  onColumnResizeStart,
4420
- onColumnResizeEnd
4422
+ onColumnResizeEnd,
4423
+ fillWidth,
4424
+ containerWidth,
4425
+ effectiveColumnWidths
4421
4426
  } = options;
4422
4427
  const [resizingColumn, setResizingColumn] = useState(null);
4423
4428
  const startXRef = useRef(0);
4424
4429
  const startWidthRef = useRef(0);
4430
+ const startEffectiveWidthsRef = useRef({});
4425
4431
  const getColumn = useCallback(
4426
4432
  (columnKey) => {
4427
4433
  return columns.find((c) => c.key === columnKey);
@@ -4445,9 +4451,20 @@ function useColumnResizeManager(options) {
4445
4451
  resizingColumn,
4446
4452
  startWidthRef.current + deltaX
4447
4453
  );
4454
+ if (fillWidth && containerWidth) {
4455
+ const otherCols = columns.filter((c) => c.key !== resizingColumn);
4456
+ const startEffective = startEffectiveWidthsRef.current;
4457
+ const otherTotal = otherCols.reduce((s, c) => s + (startEffective[c.key] || 0), 0);
4458
+ const remaining = containerWidth - newWidth;
4459
+ for (const col of otherCols) {
4460
+ const proportion = (startEffective[col.key] || 0) / otherTotal;
4461
+ const adjusted = clampWidth(col.key, Math.floor(remaining * proportion));
4462
+ onColumnResize(col.key, adjusted);
4463
+ }
4464
+ }
4448
4465
  onColumnResize(resizingColumn, newWidth);
4449
4466
  },
4450
- [resizingColumn, clampWidth, onColumnResize]
4467
+ [resizingColumn, clampWidth, onColumnResize, fillWidth, containerWidth, columns]
4451
4468
  );
4452
4469
  const handleMouseUp = useCallback(() => {
4453
4470
  if (!resizingColumn) return;
@@ -4481,6 +4498,9 @@ function useColumnResizeManager(options) {
4481
4498
  document.body.style.userSelect = "none";
4482
4499
  document.body.style.cursor = "col-resize";
4483
4500
  onColumnResizeStart?.(columnKey);
4501
+ if (fillWidth && effectiveColumnWidths) {
4502
+ startEffectiveWidthsRef.current = { ...effectiveColumnWidths };
4503
+ }
4484
4504
  };
4485
4505
  const handleDoubleClick = (event) => {
4486
4506
  if (!isResizable) return;
@@ -4506,7 +4526,9 @@ function useColumnResizeManager(options) {
4506
4526
  resizingColumn,
4507
4527
  onColumnResize,
4508
4528
  onColumnResizeStart,
4509
- onColumnResizeEnd
4529
+ onColumnResizeEnd,
4530
+ fillWidth,
4531
+ effectiveColumnWidths
4510
4532
  ]
4511
4533
  );
4512
4534
  return {
@@ -4731,6 +4753,7 @@ function DataGrid({
4731
4753
  tooltipMinLength = 30,
4732
4754
  enableKeyboardNavigation = true,
4733
4755
  showColumnBorders = true,
4756
+ fillWidth,
4734
4757
  enableInternalSorting = true,
4735
4758
  enableInternalFiltering = true,
4736
4759
  // Controlled sorting
@@ -4785,6 +4808,16 @@ function DataGrid({
4785
4808
  const headerRef = React20.useRef(null);
4786
4809
  const [headerHeight, setHeaderHeight] = React20.useState(40);
4787
4810
  const [hoveredCell, setHoveredCell] = React20.useState(null);
4811
+ const [containerWidth, setContainerWidth] = React20.useState(0);
4812
+ React20.useLayoutEffect(() => {
4813
+ if (!fillWidth || !parentRef.current) return;
4814
+ const observer = new ResizeObserver((entries) => {
4815
+ const w = entries[0]?.contentRect.width;
4816
+ if (w && w > 0) setContainerWidth(w);
4817
+ });
4818
+ observer.observe(parentRef.current);
4819
+ return () => observer.disconnect();
4820
+ }, [fillWidth]);
4788
4821
  const visibleColumns = React20.useMemo(
4789
4822
  () => columns.filter((col) => !col.hidden),
4790
4823
  [columns]
@@ -4838,14 +4871,6 @@ function DataGrid({
4838
4871
  visibleColumns
4839
4872
  ]);
4840
4873
  processedDataRef.current = processedData;
4841
- const { resizingColumn, getResizeProps } = useColumnResizeManager({
4842
- columns: visibleColumns,
4843
- columnWidths: state.columnWidths,
4844
- resizableColumns,
4845
- onColumnResize: actions.setColumnWidth,
4846
- onColumnResizeStart,
4847
- onColumnResizeEnd
4848
- });
4849
4874
  const shouldVirtualize = virtualized ?? processedData.length > 100;
4850
4875
  React20.useLayoutEffect(() => {
4851
4876
  if (headerRef.current && shouldVirtualize) {
@@ -4859,12 +4884,38 @@ function DataGrid({
4859
4884
  overscan: DEFAULT_OVERSCAN,
4860
4885
  enabled: shouldVirtualize
4861
4886
  });
4862
- const tableWidth = React20.useMemo(() => {
4863
- return visibleColumns.reduce((acc, col) => {
4864
- const width = state.columnWidths[col.key] || col.width || estimateColumnWidth(col);
4865
- return acc + width;
4866
- }, 0);
4867
- }, [visibleColumns, state.columnWidths]);
4887
+ const { tableWidth, effectiveColumnWidths } = React20.useMemo(() => {
4888
+ const rawWidths = {};
4889
+ let rawTotal = 0;
4890
+ for (const col of visibleColumns) {
4891
+ const w = state.columnWidths[col.key] || col.width || estimateColumnWidth(col);
4892
+ rawWidths[col.key] = w;
4893
+ rawTotal += w;
4894
+ }
4895
+ if (!fillWidth || !containerWidth || rawTotal >= containerWidth) {
4896
+ return { tableWidth: rawTotal, effectiveColumnWidths: rawWidths };
4897
+ }
4898
+ const scale = containerWidth / rawTotal;
4899
+ const scaled = {};
4900
+ for (const col of visibleColumns) {
4901
+ let w = Math.floor(rawWidths[col.key] * scale);
4902
+ if (col.minWidth) w = Math.max(w, col.minWidth);
4903
+ if (col.maxWidth) w = Math.min(w, col.maxWidth);
4904
+ scaled[col.key] = w;
4905
+ }
4906
+ return { tableWidth: containerWidth, effectiveColumnWidths: scaled };
4907
+ }, [visibleColumns, state.columnWidths, fillWidth, containerWidth]);
4908
+ const { resizingColumn, getResizeProps } = useColumnResizeManager({
4909
+ columns: visibleColumns,
4910
+ columnWidths: state.columnWidths,
4911
+ resizableColumns,
4912
+ onColumnResize: actions.setColumnWidth,
4913
+ onColumnResizeStart,
4914
+ onColumnResizeEnd,
4915
+ fillWidth,
4916
+ containerWidth,
4917
+ effectiveColumnWidths
4918
+ });
4868
4919
  const visibleRowCount = React20.useMemo(() => {
4869
4920
  if (!parentRef.current) return 10;
4870
4921
  return Math.floor(parentRef.current.clientHeight / DEFAULT_ROW_HEIGHT);
@@ -5096,7 +5147,7 @@ function DataGrid({
5096
5147
  },
5097
5148
  role: "row",
5098
5149
  children: visibleColumns.map((column, colIndex) => {
5099
- const width = state.columnWidths[column.key] || column.width || estimateColumnWidth(column);
5150
+ const width = effectiveColumnWidths[column.key];
5100
5151
  const resizeProps = getResizeProps(column.key);
5101
5152
  return /* @__PURE__ */ jsx(
5102
5153
  HeaderCell,
@@ -5107,6 +5158,7 @@ function DataGrid({
5107
5158
  sorting: getColumnSort(column.key),
5108
5159
  filter: getColumnFilter(column.key),
5109
5160
  isResizable: resizableColumns && column.resizable !== false,
5161
+ fillWidth,
5110
5162
  onSort: () => handleSort(column.key),
5111
5163
  onFilterChange: (filter) => handleFilterChange(column.key, filter),
5112
5164
  onResizeMouseDown: resizeProps.handleMouseDown,
@@ -5155,7 +5207,7 @@ function DataGrid({
5155
5207
  "aria-rowindex": virtualRow.index + 1,
5156
5208
  "aria-selected": isSelected,
5157
5209
  children: visibleColumns.map((column, colIndex) => {
5158
- const width = state.columnWidths[column.key] || column.width || estimateColumnWidth(column);
5210
+ const width = effectiveColumnWidths[column.key];
5159
5211
  const isEditingThisCell = state.editingCell?.rowIndex === virtualRow.index && state.editingCell?.columnKey === column.key;
5160
5212
  const isFocused = state.focusedCell?.rowIndex === virtualRow.index && state.focusedCell?.columnKey === column.key;
5161
5213
  const cellContent = renderCell(
@@ -5170,7 +5222,8 @@ function DataGrid({
5170
5222
  "div",
5171
5223
  {
5172
5224
  className: cn(
5173
- "flex-shrink-0 px-3 py-2 text-sm overflow-hidden",
5225
+ "px-3 py-2 text-sm overflow-hidden",
5226
+ !fillWidth && "flex-shrink-0",
5174
5227
  showColumnBorders && "border-r border-border last:border-r-0",
5175
5228
  isFocused && !isEditingThisCell && "ring-2 ring-inset ring-primary",
5176
5229
  isEditingThisCell && "ring-2 ring-inset ring-primary bg-background",
@@ -5282,7 +5335,7 @@ function DataGrid({
5282
5335
  }
5283
5336
  )
5284
5337
  ] }) }),
5285
- /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-auto min-h-0", children: [
5338
+ /* @__PURE__ */ jsxs("div", { ref: parentRef, className: "flex-1 overflow-auto min-h-0", children: [
5286
5339
  /* @__PURE__ */ jsx(
5287
5340
  "div",
5288
5341
  {
@@ -5293,7 +5346,7 @@ function DataGrid({
5293
5346
  style: { width: tableWidth ? `${tableWidth}px` : "100%" },
5294
5347
  role: "row",
5295
5348
  children: visibleColumns.map((column, colIndex) => {
5296
- const width = state.columnWidths[column.key] || column.width || estimateColumnWidth(column);
5349
+ const width = effectiveColumnWidths[column.key];
5297
5350
  const resizeProps = getResizeProps(column.key);
5298
5351
  return /* @__PURE__ */ jsx(
5299
5352
  HeaderCell,
@@ -5304,6 +5357,7 @@ function DataGrid({
5304
5357
  sorting: getColumnSort(column.key),
5305
5358
  filter: getColumnFilter(column.key),
5306
5359
  isResizable: resizableColumns && column.resizable !== false,
5360
+ fillWidth,
5307
5361
  onSort: () => handleSort(column.key),
5308
5362
  onFilterChange: (filter) => handleFilterChange(column.key, filter),
5309
5363
  onResizeMouseDown: resizeProps.handleMouseDown,
@@ -5333,14 +5387,15 @@ function DataGrid({
5333
5387
  onDoubleClick: () => onRowDoubleClick?.(row, rowIndex),
5334
5388
  role: "row",
5335
5389
  children: visibleColumns.map((column) => {
5336
- const width = state.columnWidths[column.key] || column.width || estimateColumnWidth(column);
5390
+ const width = effectiveColumnWidths[column.key];
5337
5391
  const isEditingThisCell = state.editingCell?.rowIndex === rowIndex && state.editingCell?.columnKey === column.key;
5338
5392
  const isFocused = state.focusedCell?.rowIndex === rowIndex && state.focusedCell?.columnKey === column.key;
5339
5393
  return /* @__PURE__ */ jsx(
5340
5394
  "div",
5341
5395
  {
5342
5396
  className: cn(
5343
- "flex-shrink-0 px-3 py-2 text-sm overflow-hidden",
5397
+ "px-3 py-2 text-sm overflow-hidden",
5398
+ !fillWidth && "flex-shrink-0",
5344
5399
  showColumnBorders && "border-r border-border last:border-r-0",
5345
5400
  isFocused && !isEditingThisCell && "ring-2 ring-inset ring-primary",
5346
5401
  isEditingThisCell && "ring-2 ring-inset ring-primary bg-background",