@snksergio/design-system 0.4.0 → 0.5.0

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.
@@ -290,7 +290,7 @@ const Avatar = forwardRef(
290
290
  }
291
291
  );
292
292
  Avatar.displayName = "Avatar";
293
- const DEFAULT_COLUMN_WIDTH = 160;
293
+ const DEFAULT_COLUMN_WIDTH$1 = 160;
294
294
  function useColumnWidths(columns) {
295
295
  return useMemo(() => {
296
296
  const widths = {};
@@ -298,7 +298,7 @@ function useColumnWidths(columns) {
298
298
  let totalPinLeft = 0;
299
299
  let totalPinRight = 0;
300
300
  for (const col of columns) {
301
- widths[col.field] = col.width ?? col.defaultWidth ?? DEFAULT_COLUMN_WIDTH;
301
+ widths[col.field] = col.width ?? col.defaultWidth ?? DEFAULT_COLUMN_WIDTH$1;
302
302
  }
303
303
  let leftSum = 0;
304
304
  for (const col of columns) {
@@ -5326,7 +5326,8 @@ function useDataTableColumns({
5326
5326
  initialWidthOverrides,
5327
5327
  initialPinnedOverrides,
5328
5328
  initialHiddenColumns,
5329
- initialColumnOrder
5329
+ initialColumnOrder,
5330
+ autoWidths
5330
5331
  }) {
5331
5332
  const [widthOverrides, setWidthOverrides] = useState(
5332
5333
  () => initialWidthOverrides ?? {}
@@ -5355,13 +5356,13 @@ function useDataTableColumns({
5355
5356
  const typeDef = col.type ? columnTypeRegistry.get(col.type) : void 0;
5356
5357
  ordered.push({
5357
5358
  ...col,
5358
- width: widthOverrides[field] ?? col.width ?? typeDef?.defaultWidth,
5359
+ width: widthOverrides[field] ?? autoWidths?.[field] ?? col.width ?? typeDef?.defaultWidth,
5359
5360
  sortable: col.sortable ?? typeDef?.defaultSortable,
5360
5361
  pinned: field in pinnedOverrides ? pinnedOverrides[field] : col.pinned
5361
5362
  });
5362
5363
  }
5363
5364
  return ordered;
5364
- }, [columns, columnOrder, hiddenColumns, widthOverrides, pinnedOverrides]);
5365
+ }, [columns, columnOrder, hiddenColumns, widthOverrides, pinnedOverrides, autoWidths]);
5365
5366
  const widthsInput = useMemo(
5366
5367
  () => effectiveColumns.map((c) => ({
5367
5368
  field: String(c.field),
@@ -5429,6 +5430,177 @@ function useDataTableColumns({
5429
5430
  applyColumnState
5430
5431
  };
5431
5432
  }
5433
+ let measureCanvas = null;
5434
+ let measureCtx = null;
5435
+ const DEFAULT_FONT = "13px Geist, system-ui, -apple-system, BlinkMacSystemFont, sans-serif";
5436
+ function ensureCtx(font = DEFAULT_FONT) {
5437
+ if (typeof document === "undefined") return null;
5438
+ if (!measureCanvas) {
5439
+ measureCanvas = document.createElement("canvas");
5440
+ measureCtx = measureCanvas.getContext("2d");
5441
+ }
5442
+ if (measureCtx) measureCtx.font = font;
5443
+ return measureCtx;
5444
+ }
5445
+ function measureTextWidth(text, font) {
5446
+ if (!text) return 0;
5447
+ const ctx = ensureCtx(font);
5448
+ if (!ctx) return 0;
5449
+ return ctx.measureText(text).width;
5450
+ }
5451
+ function getFieldValue$1(row, field) {
5452
+ if (!field.includes(".")) {
5453
+ return row[field];
5454
+ }
5455
+ return field.split(".").reduce(
5456
+ (acc, key) => acc?.[key],
5457
+ row
5458
+ );
5459
+ }
5460
+ function applyValueGetter(row, column2) {
5461
+ if (column2.valueGetter) return column2.valueGetter(row);
5462
+ return getFieldValue$1(row, String(column2.field));
5463
+ }
5464
+ function applyFormatter(row, column2) {
5465
+ const value = applyValueGetter(row, column2);
5466
+ if (column2.valueFormatter) return column2.valueFormatter(value);
5467
+ return value == null ? "" : String(value);
5468
+ }
5469
+ const DEFAULT_COLUMN_WIDTH = 160;
5470
+ const CELL_PADDING_PX = 32;
5471
+ const DEFAULT_SAMPLE_SIZE = 20;
5472
+ function calculateColumnWidths(columns, rows, options) {
5473
+ const { containerWidth, autoFit, sampleSize = DEFAULT_SAMPLE_SIZE } = options;
5474
+ if (containerWidth <= 0) return {};
5475
+ const widths = {};
5476
+ let totalContentWidth = 0;
5477
+ for (const col of columns) {
5478
+ const field = String(col.field);
5479
+ if (col.width !== void 0) {
5480
+ totalContentWidth += col.width;
5481
+ continue;
5482
+ }
5483
+ const typeDef = col.type ? columnTypeRegistry.get(col.type) : void 0;
5484
+ let calculatedWidth = typeDef?.defaultWidth ?? DEFAULT_COLUMN_WIDTH;
5485
+ if (autoFit) {
5486
+ const headerText = String(col.headerName ?? field);
5487
+ let maxWidth = measureTextWidth(headerText) + CELL_PADDING_PX;
5488
+ const sample = rows.slice(0, sampleSize);
5489
+ for (const row of sample) {
5490
+ const text = applyFormatter(row, col);
5491
+ if (!text) continue;
5492
+ const w = measureTextWidth(text) + CELL_PADDING_PX;
5493
+ if (w > maxWidth) maxWidth = w;
5494
+ }
5495
+ if (maxWidth > calculatedWidth) calculatedWidth = maxWidth;
5496
+ }
5497
+ if (col.minWidth !== void 0 && calculatedWidth < col.minWidth) {
5498
+ calculatedWidth = col.minWidth;
5499
+ }
5500
+ if (col.maxWidth !== void 0 && calculatedWidth > col.maxWidth) {
5501
+ calculatedWidth = col.maxWidth;
5502
+ }
5503
+ widths[field] = Math.round(calculatedWidth);
5504
+ totalContentWidth += calculatedWidth;
5505
+ }
5506
+ if (totalContentWidth < containerWidth) {
5507
+ const remainingSpace = containerWidth - totalContentWidth;
5508
+ const flexibleColumns = columns.filter((col) => col.width === void 0);
5509
+ const targets = flexibleColumns.length > 0 ? flexibleColumns : autoFit ? columns : [];
5510
+ if (targets.length > 0) {
5511
+ const extraPerColumn = Math.floor(remainingSpace / targets.length);
5512
+ let distributed = 0;
5513
+ targets.forEach((col, index) => {
5514
+ const field = String(col.field);
5515
+ const isLast = index === targets.length - 1;
5516
+ const extra = isLast ? remainingSpace - distributed : extraPerColumn;
5517
+ const current = widths[field] ?? col.width ?? 0;
5518
+ let next = current + extra;
5519
+ if (col.maxWidth !== void 0 && next > col.maxWidth) {
5520
+ next = col.maxWidth;
5521
+ }
5522
+ widths[field] = Math.round(next);
5523
+ distributed += extra;
5524
+ });
5525
+ }
5526
+ }
5527
+ return widths;
5528
+ }
5529
+ function useColumnAutoWidth(containerRef, columns, rows, options) {
5530
+ const { enabled, sampleSize, reservedWidth = 0 } = options;
5531
+ const [autoWidths, setAutoWidths] = useState({});
5532
+ const rafIdRef = useRef(null);
5533
+ const columnsRef = useRef(columns);
5534
+ const rowsRef = useRef(rows);
5535
+ columnsRef.current = columns;
5536
+ rowsRef.current = rows;
5537
+ useEffect(() => {
5538
+ if (!enabled) {
5539
+ setAutoWidths((prev) => Object.keys(prev).length === 0 ? prev : {});
5540
+ return;
5541
+ }
5542
+ const el = containerRef.current;
5543
+ if (!el) return;
5544
+ if (typeof ResizeObserver === "undefined") return;
5545
+ const recalculate = (containerWidth) => {
5546
+ const effectiveWidth = Math.max(0, containerWidth - reservedWidth);
5547
+ const calcOptions = {
5548
+ containerWidth: effectiveWidth,
5549
+ autoFit: enabled,
5550
+ sampleSize
5551
+ };
5552
+ const next = calculateColumnWidths(
5553
+ columnsRef.current,
5554
+ rowsRef.current,
5555
+ calcOptions
5556
+ );
5557
+ setAutoWidths((prev) => {
5558
+ const prevKeys = Object.keys(prev);
5559
+ const nextKeys = Object.keys(next);
5560
+ if (prevKeys.length === nextKeys.length) {
5561
+ let same = true;
5562
+ for (const k of nextKeys) {
5563
+ if (prev[k] !== next[k]) {
5564
+ same = false;
5565
+ break;
5566
+ }
5567
+ }
5568
+ if (same) return prev;
5569
+ }
5570
+ return next;
5571
+ });
5572
+ };
5573
+ const initialRect = el.getBoundingClientRect();
5574
+ recalculate(initialRect.width);
5575
+ const observer = new ResizeObserver((entries) => {
5576
+ const entry = entries[0];
5577
+ if (!entry) return;
5578
+ if (rafIdRef.current !== null) cancelAnimationFrame(rafIdRef.current);
5579
+ rafIdRef.current = requestAnimationFrame(() => {
5580
+ rafIdRef.current = null;
5581
+ recalculate(entry.contentRect.width);
5582
+ });
5583
+ });
5584
+ observer.observe(el);
5585
+ return () => {
5586
+ observer.disconnect();
5587
+ if (rafIdRef.current !== null) {
5588
+ cancelAnimationFrame(rafIdRef.current);
5589
+ rafIdRef.current = null;
5590
+ }
5591
+ };
5592
+ }, [
5593
+ enabled,
5594
+ containerRef,
5595
+ sampleSize,
5596
+ reservedWidth,
5597
+ columns.length,
5598
+ rows.length,
5599
+ // Inclui campos das columns pra recalcular quando consumer muda a schema
5600
+ columns.map((c) => String(c.field)).join("|")
5601
+ ]);
5602
+ return { autoWidths };
5603
+ }
5432
5604
  function toArray(v) {
5433
5605
  if (v == null) return [];
5434
5606
  if (Array.isArray(v)) return v;
@@ -5483,10 +5655,11 @@ function useDataTablePagination({
5483
5655
  paginationModel: controlledModel,
5484
5656
  onPaginationModelChange,
5485
5657
  initialPageSize = DEFAULT_PAGE_SIZE,
5658
+ initialPage = 1,
5486
5659
  resetTriggers = []
5487
5660
  } = {}) {
5488
5661
  const [uncontrolled, setUncontrolled] = useState({
5489
- page: 1,
5662
+ page: initialPage,
5490
5663
  pageSize: initialPageSize
5491
5664
  });
5492
5665
  const isControlled = controlledModel !== void 0;
@@ -5641,9 +5814,10 @@ const DEFAULT_DEBOUNCE = 500;
5641
5814
  function useDataTableSearch({
5642
5815
  search: controlledSearch,
5643
5816
  onSearchChange,
5644
- debounceMs = DEFAULT_DEBOUNCE
5817
+ debounceMs = DEFAULT_DEBOUNCE,
5818
+ initialSearch
5645
5819
  } = {}) {
5646
- const [uncontrolled, setUncontrolled] = useState("");
5820
+ const [uncontrolled, setUncontrolled] = useState(initialSearch ?? "");
5647
5821
  const isControlled = controlledSearch !== void 0;
5648
5822
  const inputValue = isControlled ? controlledSearch : uncontrolled;
5649
5823
  const [debouncedValue, setDebouncedValue] = useState(inputValue);
@@ -5693,24 +5867,6 @@ function useDataTableFilters({
5693
5867
  }, [setFilterModel]);
5694
5868
  return { filterModel, setFilterModel, clearFilters };
5695
5869
  }
5696
- function getFieldValue$1(row, field) {
5697
- if (!field.includes(".")) {
5698
- return row[field];
5699
- }
5700
- return field.split(".").reduce(
5701
- (acc, key) => acc?.[key],
5702
- row
5703
- );
5704
- }
5705
- function applyValueGetter(row, column2) {
5706
- if (column2.valueGetter) return column2.valueGetter(row);
5707
- return getFieldValue$1(row, String(column2.field));
5708
- }
5709
- function applyFormatter(row, column2) {
5710
- const value = applyValueGetter(row, column2);
5711
- if (column2.valueFormatter) return column2.valueFormatter(value);
5712
- return value == null ? "" : String(value);
5713
- }
5714
5870
  function matchesFilter(value, item, col) {
5715
5871
  const { operator } = item;
5716
5872
  const target = item.value;
@@ -5956,7 +6112,7 @@ function downloadCsv(csv, filename) {
5956
6112
  URL.revokeObjectURL(url);
5957
6113
  }
5958
6114
  const STORAGE_PREFIX$1 = "igreen-datatable:";
5959
- const SCHEMA_VERSION = 3;
6115
+ const SCHEMA_VERSION = 4;
5960
6116
  function storageKey$1(persistId) {
5961
6117
  return `${STORAGE_PREFIX$1}${persistId}`;
5962
6118
  }
@@ -6121,12 +6277,22 @@ function useDataTableController(props, ref) {
6121
6277
  () => loadPersistedState(props.persistId),
6122
6278
  [props.persistId]
6123
6279
  );
6280
+ const scrollContainerRef = useRef(null);
6281
+ const autoFitEnabled = props.autoFit !== false;
6282
+ const selectionReservedWidth = props.selectionConfig?.enabled ? SELECTION_COLUMN_WIDTH : 0;
6283
+ const { autoWidths } = useColumnAutoWidth(
6284
+ scrollContainerRef,
6285
+ props.columns,
6286
+ props.rows ?? [],
6287
+ { enabled: autoFitEnabled, reservedWidth: selectionReservedWidth }
6288
+ );
6124
6289
  const cols = useDataTableColumns({
6125
6290
  columns: props.columns,
6126
6291
  initialWidthOverrides: persistedInitial?.columnWidths,
6127
6292
  initialPinnedOverrides: persistedInitial?.pinnedColumns,
6128
6293
  initialHiddenColumns: persistedInitial?.hiddenColumns,
6129
- initialColumnOrder: persistedInitial?.columnOrder
6294
+ initialColumnOrder: persistedInitial?.columnOrder,
6295
+ autoWidths
6130
6296
  });
6131
6297
  const sort = useDataTableSort({
6132
6298
  sortModel: props.sortModel,
@@ -6135,16 +6301,23 @@ function useDataTableController(props, ref) {
6135
6301
  });
6136
6302
  const filters = useDataTableFilters({
6137
6303
  filterModel: props.filterModel,
6138
- onFilterModelChange: props.onFilterModelChange
6304
+ onFilterModelChange: props.onFilterModelChange,
6305
+ // Hidrata filtros do workspace Default (v4+). Quando uma view custom estava
6306
+ // ativa no último mount, o effect de restore (linha ~410) aplica a view depois.
6307
+ initialFilterModel: persistedInitial?.filterModel
6139
6308
  });
6140
6309
  const search = useDataTableSearch({
6141
6310
  search: props.search,
6142
- onSearchChange: props.onSearchChange
6311
+ onSearchChange: props.onSearchChange,
6312
+ // Hidrata texto de busca do workspace Default (v4+).
6313
+ initialSearch: persistedInitial?.search
6143
6314
  });
6144
6315
  const pagination = useDataTablePagination({
6145
6316
  paginationModel: props.paginationModel,
6146
6317
  onPaginationModelChange: props.onPaginationModelChange,
6147
6318
  initialPageSize: persistedInitial?.pageSize ?? props.paginationConfig?.initialPageSize,
6319
+ // Hidrata página atual do workspace Default (v4+).
6320
+ initialPage: persistedInitial?.currentPage,
6148
6321
  resetTriggers: [filters.filterModel.items.length, search.debouncedValue]
6149
6322
  });
6150
6323
  const density = useDataTableDensity({
@@ -6268,10 +6441,17 @@ function useDataTableController(props, ref) {
6268
6441
  density: density.density,
6269
6442
  sortModel: sort.sortModels,
6270
6443
  pageSize: pagination.paginationModel.pageSize,
6444
+ // v4 — currentPage/filterModel/search persistem como parte do workspace Default.
6445
+ // O defaultSnapshotRef abaixo só atualiza quando currentViewId === null, ou seja
6446
+ // só "captura" esses valores quando a Default está ativa. Aplicar uma view custom
6447
+ // não polui o Default (o save sempre usa defaultSnapshotRef, não persistedSnapshot).
6448
+ currentPage: pagination.paginationModel.page,
6271
6449
  columnWidths: cols.columnWidths,
6272
6450
  pinnedColumns: cols.pinnedColumns,
6273
6451
  hiddenColumns: Array.from(cols.hiddenColumns),
6274
6452
  columnOrder: cols.columnOrder,
6453
+ filterModel: filters.filterModel,
6454
+ search: search.debouncedValue,
6275
6455
  // Fase v3 — features novas também persistem (workspace completo da Default)
6276
6456
  viewMode,
6277
6457
  groupBy,
@@ -6282,10 +6462,13 @@ function useDataTableController(props, ref) {
6282
6462
  density.density,
6283
6463
  sort.sortModels,
6284
6464
  pagination.paginationModel.pageSize,
6465
+ pagination.paginationModel.page,
6285
6466
  cols.columnWidths,
6286
6467
  cols.pinnedColumns,
6287
6468
  cols.hiddenColumns,
6288
6469
  cols.columnOrder,
6470
+ filters.filterModel,
6471
+ search.debouncedValue,
6289
6472
  viewMode,
6290
6473
  groupBy,
6291
6474
  expandedRowIds,
@@ -6298,9 +6481,13 @@ function useDataTableController(props, ref) {
6298
6481
  defaultSnapshotRef.current = rest;
6299
6482
  }
6300
6483
  }, [persistedSnapshot, savedViews.currentViewId]);
6484
+ const persistedSnapshotForSave = useMemo(() => {
6485
+ const base = savedViews.currentViewId === null ? persistedSnapshot : { ...defaultSnapshotRef.current, lastActiveViewId: savedViews.currentViewId };
6486
+ return base;
6487
+ }, [persistedSnapshot, savedViews.currentViewId]);
6301
6488
  useDataTableStatePersistence({
6302
6489
  persistId: props.persistId,
6303
- state: persistedSnapshot
6490
+ state: persistedSnapshotForSave
6304
6491
  });
6305
6492
  const applyViewState = useCallback(
6306
6493
  (id, state) => {
@@ -6412,6 +6599,12 @@ function useDataTableController(props, ref) {
6412
6599
  setViewMode(snapshot.viewMode ?? "table");
6413
6600
  setGroupBy(snapshot.groupBy);
6414
6601
  setExpandedRowIds(snapshot.expandedRowIds ?? []);
6602
+ filters.setFilterModel(snapshot.filterModel ?? { items: [], logicOperator: "AND" });
6603
+ search.setInputValue(snapshot.search ?? "");
6604
+ pagination.setPaginationModel({
6605
+ page: snapshot.currentPage ?? 1,
6606
+ pageSize: snapshot.pageSize ?? pagination.paginationModel.pageSize
6607
+ });
6415
6608
  } else {
6416
6609
  density.setDensity("standard");
6417
6610
  sort.setSortModels([]);
@@ -6424,10 +6617,11 @@ function useDataTableController(props, ref) {
6424
6617
  setViewMode("table");
6425
6618
  setGroupBy(void 0);
6426
6619
  setExpandedRowIds([]);
6620
+ filters.setFilterModel({ items: [], logicOperator: "AND" });
6621
+ search.setInputValue("");
6427
6622
  }
6428
- filters.setFilterModel({ items: [], logicOperator: "AND" });
6429
6623
  savedViews.setCurrentViewId(null);
6430
- }, [props.persistId, props.columns, density, sort, filters, cols, setViewMode, setGroupBy, setExpandedRowIds, savedViews]);
6624
+ }, [props.persistId, props.columns, density, sort, filters, search, pagination, cols, setViewMode, setGroupBy, setExpandedRowIds, savedViews]);
6431
6625
  useImperativeHandle(ref, () => ({
6432
6626
  refresh: () => {
6433
6627
  if (isServerMode) query.refresh();
@@ -6483,6 +6677,8 @@ function useDataTableController(props, ref) {
6483
6677
  isLoading,
6484
6678
  isDataEmpty,
6485
6679
  isNoResults,
6680
+ /** Ref ao scroll container (compartilhado entre Table.scrollRef + useColumnAutoWidth). */
6681
+ scrollContainerRef,
6486
6682
  rowsToRender: effectiveRows,
6487
6683
  /** Todas as rows após filter/search/sort (sem paginate) — usado por totalizers
6488
6684
  * pra agregar sobre tudo, nao só pagina atual. Em server mode = current page. */
@@ -7769,6 +7965,7 @@ function DataTableInternal(props, ref) {
7769
7965
  isLoading,
7770
7966
  isDataEmpty,
7771
7967
  isNoResults,
7968
+ scrollContainerRef,
7772
7969
  rowsToRender,
7773
7970
  rowsAllPagesProcessed,
7774
7971
  totalAfterFilter,
@@ -8163,7 +8360,6 @@ function DataTableInternal(props, ref) {
8163
8360
  rowsToRender,
8164
8361
  contextValue
8165
8362
  ]);
8166
- const scrollContainerRef = useRef(null);
8167
8363
  const defaultRowHeight = density.density === "compact" ? 40 : density.density === "comfortable" ? 64 : 56;
8168
8364
  const estimateRowHeight = props.estimateRowHeight ?? defaultRowHeight;
8169
8365
  const virtualize = props.virtualize === true;
@@ -10002,4 +10198,4 @@ export {
10002
10198
  PanelFooter as y,
10003
10199
  PanelHeader as z
10004
10200
  };
10005
- //# sourceMappingURL=panel-DZ7X1NZV.mjs.map
10201
+ //# sourceMappingURL=panel-CCYNbXJR.mjs.map