@rufous/ui 0.3.17 → 0.3.18

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/main.cjs CHANGED
@@ -4697,7 +4697,11 @@ function DataGrid({
4697
4697
  onRowDoubleClick,
4698
4698
  onCellDoubleClick,
4699
4699
  headerActions,
4700
- toolbarContent
4700
+ toolbarContent,
4701
+ searchableColumns,
4702
+ onSearchChange,
4703
+ onFiltersChange,
4704
+ hideTopExport = false
4701
4705
  }) {
4702
4706
  const sxClass = useSx(sx);
4703
4707
  const [editingCell, setEditingCell] = (0, import_react23.useState)(null);
@@ -4802,6 +4806,9 @@ function DataGrid({
4802
4806
  return next;
4803
4807
  });
4804
4808
  }, [initialColumnsProp]);
4809
+ (0, import_react23.useEffect)(() => {
4810
+ onFiltersChange?.(advancedFilters);
4811
+ }, [advancedFilters, onFiltersChange]);
4805
4812
  const handleSort = (fieldKey, dir) => {
4806
4813
  if (dir !== void 0) {
4807
4814
  setSortField(fieldKey);
@@ -4851,10 +4858,10 @@ function DataGrid({
4851
4858
  };
4852
4859
  const filteredData = (0, import_react23.useMemo)(() => {
4853
4860
  return data.filter((item) => {
4854
- const matchesGlobal = !filterText || resolvedColumns.some((col) => {
4855
- if (col.hidden) return false;
4856
- return getDisplayValue(item, col).toLowerCase().includes(filterText.toLowerCase());
4857
- });
4861
+ const searchCols = searchableColumns ? resolvedColumns.filter((col) => searchableColumns.includes(String(col.field))) : resolvedColumns.filter((col) => !col.hidden);
4862
+ const matchesGlobal = !filterText || searchCols.some(
4863
+ (col) => getDisplayValue(item, col).toLowerCase().includes(filterText.toLowerCase())
4864
+ );
4858
4865
  const evalFilter = (f) => {
4859
4866
  if (!f.value && f.operator !== "is empty" && f.operator !== "is not empty") return true;
4860
4867
  const col = resolvedColumns.find((c) => String(c.field) === f.column || String(c.key) === f.column);
@@ -4962,7 +4969,11 @@ function DataGrid({
4962
4969
  }
4963
4970
  return matchesGlobal && matchesAdvanced;
4964
4971
  });
4965
- }, [data, filterText, advancedFilters, resolvedColumns]);
4972
+ }, [data, filterText, advancedFilters, resolvedColumns, searchableColumns]);
4973
+ (0, import_react23.useEffect)(() => {
4974
+ if (!onSearchChange || !filterText) return;
4975
+ if (filteredData.length === 0) onSearchChange(filterText);
4976
+ }, [filteredData, filterText, onSearchChange]);
4966
4977
  const sortedData = (0, import_react23.useMemo)(() => {
4967
4978
  if (!sortField || !sortDirection) return filteredData;
4968
4979
  const col = resolvedColumns.find((c) => c.field === sortField);
@@ -4993,11 +5004,16 @@ function DataGrid({
4993
5004
  (item) => exportableCols.map((c) => {
4994
5005
  const field = String(c.field);
4995
5006
  const raw = item[field];
4996
- let val = c.valueGetter ? c.valueGetter({ value: raw, row: item, field }) : raw;
4997
- if (c.valueFormatter) {
4998
- val = c.valueFormatter({ value: val, row: item, field });
5007
+ let str;
5008
+ if (c.exportValue) {
5009
+ str = c.exportValue(raw, item).replace(/"/g, '""');
5010
+ } else {
5011
+ let val = c.valueGetter ? c.valueGetter({ value: raw, row: item, field }) : raw;
5012
+ if (c.valueFormatter) {
5013
+ val = c.valueFormatter({ value: val, row: item, field });
5014
+ }
5015
+ str = val === null || val === void 0 ? "" : String(val).replace(/"/g, '""');
4999
5016
  }
5000
- const str = val === null || val === void 0 ? "" : String(val).replace(/"/g, '""');
5001
5017
  return `"${str}"`;
5002
5018
  }).join(",")
5003
5019
  );
@@ -5090,7 +5106,7 @@ function DataGrid({
5090
5106
  onClick: () => setShowManageColumns(true)
5091
5107
  },
5092
5108
  /* @__PURE__ */ import_react23.default.createElement(import_lucide_react2.Columns, { size: 16 })
5093
- )), /* @__PURE__ */ import_react23.default.createElement("button", { className: "dg-action-btn", onClick: handleExport }, /* @__PURE__ */ import_react23.default.createElement(import_lucide_react2.Download, { size: 14 }), " Export CSV"), headerActions && /* @__PURE__ */ import_react23.default.createElement("div", { className: `dg-header-slot ${alignClass(headerActions.align)}` }, headerActions.content))), /* @__PURE__ */ import_react23.default.createElement("div", { className: `dg-toolbar ${alignClass(toolbarContent?.align)}` }, toolbarContent?.content || ""), /* @__PURE__ */ import_react23.default.createElement("div", { className: `dg-table-wrap${paginatedData.length === 0 && !loading ? " dg-table-wrap--empty" : ""}` }, loading && /* @__PURE__ */ import_react23.default.createElement("div", { className: "dg-loading-overlay" }, /* @__PURE__ */ import_react23.default.createElement("div", { className: "dg-loading-spinner" })), /* @__PURE__ */ import_react23.default.createElement("table", { className: "dg-table" }, /* @__PURE__ */ import_react23.default.createElement("thead", null, /* @__PURE__ */ import_react23.default.createElement("tr", null, visibleColumns.map((col, idx) => {
5109
+ )), !hideTopExport && /* @__PURE__ */ import_react23.default.createElement("button", { className: "dg-action-btn", onClick: handleExport }, /* @__PURE__ */ import_react23.default.createElement(import_lucide_react2.Download, { size: 14 }), " Export CSV"), headerActions && /* @__PURE__ */ import_react23.default.createElement("div", { className: `dg-header-slot ${alignClass(headerActions.align)}` }, headerActions.content))), /* @__PURE__ */ import_react23.default.createElement("div", { className: `dg-toolbar ${alignClass(toolbarContent?.align)}` }, toolbarContent?.content || ""), /* @__PURE__ */ import_react23.default.createElement("div", { className: `dg-table-wrap${paginatedData.length === 0 && !loading ? " dg-table-wrap--empty" : ""}` }, loading && /* @__PURE__ */ import_react23.default.createElement("div", { className: "dg-loading-overlay" }, /* @__PURE__ */ import_react23.default.createElement("div", { className: "dg-loading-spinner" })), /* @__PURE__ */ import_react23.default.createElement("table", { className: "dg-table" }, /* @__PURE__ */ import_react23.default.createElement("thead", null, /* @__PURE__ */ import_react23.default.createElement("tr", null, visibleColumns.map((col, idx) => {
5094
5110
  const colField = String(col.field);
5095
5111
  const width = columnWidths[colField] || 200;
5096
5112
  const leftOffset = getLeftOffset(col, idx);
@@ -5111,9 +5127,8 @@ function DataGrid({
5111
5127
  onClick: () => col.sortable !== false && handleSort(col.field || "")
5112
5128
  },
5113
5129
  col.headerName,
5114
- isSorted && sortDirection === "asc" && /* @__PURE__ */ import_react23.default.createElement(import_lucide_react2.ChevronUp, { size: 12 }),
5115
- isSorted && sortDirection === "desc" && /* @__PURE__ */ import_react23.default.createElement(import_lucide_react2.ChevronDown, { size: 12 })
5116
- ), /* @__PURE__ */ import_react23.default.createElement("div", { className: `dg-th-actions${isFiltered ? " dg-th-actions--filtered" : ""}` }, isFiltered && /* @__PURE__ */ import_react23.default.createElement(import_lucide_react2.Filter, { size: 11, style: { color: "var(--primary-color)" } }), !col.disableColumnMenu && /* @__PURE__ */ import_react23.default.createElement(
5130
+ col.sortable !== false && /* @__PURE__ */ import_react23.default.createElement("span", { className: `dg-sort-icon${isSorted ? " dg-sort-icon--active" : ""}` }, isSorted && sortDirection === "asc" && /* @__PURE__ */ import_react23.default.createElement(import_lucide_react2.ChevronUp, { size: 14 }), isSorted && sortDirection === "desc" && /* @__PURE__ */ import_react23.default.createElement(import_lucide_react2.ChevronDown, { size: 14 }), !isSorted && /* @__PURE__ */ import_react23.default.createElement(import_lucide_react2.ChevronsUpDown, { size: 14 }))
5131
+ ), /* @__PURE__ */ import_react23.default.createElement("div", { className: `dg-th-actions${isFiltered ? " dg-th-actions--filtered" : ""}` }, isFiltered && /* @__PURE__ */ import_react23.default.createElement("button", { className: "dg-th-filter-btn", onClick: () => setShowAdvancedFilter(true) }, /* @__PURE__ */ import_react23.default.createElement(import_lucide_react2.Filter, { size: 11 })), !col.disableColumnMenu && /* @__PURE__ */ import_react23.default.createElement(
5117
5132
  "button",
5118
5133
  {
5119
5134
  className: "dg-th-menu-btn",
@@ -5205,7 +5220,7 @@ function DataGrid({
5205
5220
  },
5206
5221
  action.icon
5207
5222
  )))));
5208
- })()))))), paginatedData.length === 0 && /* @__PURE__ */ import_react23.default.createElement("div", { className: "dg-empty-state" }, /* @__PURE__ */ import_react23.default.createElement("svg", { className: "dg-empty-icon", viewBox: "0 0 200 160", fill: "none", xmlns: "http://www.w3.org/2000/svg" }, /* @__PURE__ */ import_react23.default.createElement("rect", { x: "20", y: "30", width: "160", height: "100", rx: "8", fill: "var(--hover-color)", stroke: "var(--border-color)", strokeWidth: "1.5" }), /* @__PURE__ */ import_react23.default.createElement("rect", { x: "20", y: "30", width: "160", height: "28", rx: "8", fill: "var(--border-color)", opacity: "0.5" }), /* @__PURE__ */ import_react23.default.createElement("rect", { x: "20", y: "50", width: "160", height: "8", rx: "0", fill: "var(--border-color)", opacity: "0.5" }), /* @__PURE__ */ import_react23.default.createElement("line", { x1: "72", y1: "30", x2: "72", y2: "130", stroke: "var(--border-color)", strokeWidth: "1" }), /* @__PURE__ */ import_react23.default.createElement("line", { x1: "128", y1: "30", x2: "128", y2: "130", stroke: "var(--border-color)", strokeWidth: "1" }), /* @__PURE__ */ import_react23.default.createElement("line", { x1: "20", y1: "78", x2: "180", y2: "78", stroke: "var(--border-color)", strokeWidth: "1" }), /* @__PURE__ */ import_react23.default.createElement("line", { x1: "20", y1: "104", x2: "180", y2: "104", stroke: "var(--border-color)", strokeWidth: "1" }), /* @__PURE__ */ import_react23.default.createElement("rect", { x: "32", y: "87", width: "28", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.4" }), /* @__PURE__ */ import_react23.default.createElement("rect", { x: "84", y: "87", width: "28", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.4" }), /* @__PURE__ */ import_react23.default.createElement("rect", { x: "140", y: "87", width: "28", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.4" }), /* @__PURE__ */ import_react23.default.createElement("rect", { x: "32", y: "113", width: "20", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.3" }), /* @__PURE__ */ import_react23.default.createElement("rect", { x: "84", y: "113", width: "32", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.3" }), /* @__PURE__ */ import_react23.default.createElement("rect", { x: "140", y: "113", width: "20", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.3" }), /* @__PURE__ */ import_react23.default.createElement("circle", { cx: "148", cy: "108", r: "26", fill: "var(--surface-color)", stroke: "var(--border-color)", strokeWidth: "1.5" }), /* @__PURE__ */ import_react23.default.createElement("circle", { cx: "145", cy: "105", r: "10", stroke: "var(--text-secondary)", strokeWidth: "2.5", opacity: "0.5" }), /* @__PURE__ */ import_react23.default.createElement("line", { x1: "152", y1: "113", x2: "161", y2: "122", stroke: "var(--text-secondary)", strokeWidth: "2.5", strokeLinecap: "round", opacity: "0.5" }), /* @__PURE__ */ import_react23.default.createElement("line", { x1: "141", y1: "101", x2: "149", y2: "109", stroke: "var(--text-secondary)", strokeWidth: "2", strokeLinecap: "round", opacity: "0.5" }), /* @__PURE__ */ import_react23.default.createElement("line", { x1: "149", y1: "101", x2: "141", y2: "109", stroke: "var(--text-secondary)", strokeWidth: "2", strokeLinecap: "round", opacity: "0.5" })), /* @__PURE__ */ import_react23.default.createElement("p", { className: "dg-empty-title" }, "No data found"), /* @__PURE__ */ import_react23.default.createElement("p", { className: "dg-empty-subtitle" }, filterText || hasActiveFilters ? "Try adjusting your search or filters" : "No records to display"))), pagination && /* @__PURE__ */ import_react23.default.createElement("div", { className: "dg-pagination" }, /* @__PURE__ */ import_react23.default.createElement("div", { className: "dg-page-info" }, /* @__PURE__ */ import_react23.default.createElement("div", { className: "dg-per-page" }, /* @__PURE__ */ import_react23.default.createElement("span", null, "Rows per page:"), /* @__PURE__ */ import_react23.default.createElement(
5223
+ })()))))), paginatedData.length === 0 && /* @__PURE__ */ import_react23.default.createElement("div", { className: "dg-empty-state" }, /* @__PURE__ */ import_react23.default.createElement("svg", { className: "dg-empty-icon", viewBox: "0 0 200 160", fill: "none", xmlns: "http://www.w3.org/2000/svg" }, /* @__PURE__ */ import_react23.default.createElement("rect", { x: "20", y: "30", width: "160", height: "100", rx: "8", fill: "var(--hover-color)", stroke: "var(--border-color)", strokeWidth: "1.5" }), /* @__PURE__ */ import_react23.default.createElement("rect", { x: "20", y: "30", width: "160", height: "28", rx: "8", fill: "var(--border-color)", opacity: "0.5" }), /* @__PURE__ */ import_react23.default.createElement("rect", { x: "20", y: "50", width: "160", height: "8", rx: "0", fill: "var(--border-color)", opacity: "0.5" }), /* @__PURE__ */ import_react23.default.createElement("line", { x1: "72", y1: "30", x2: "72", y2: "130", stroke: "var(--border-color)", strokeWidth: "1" }), /* @__PURE__ */ import_react23.default.createElement("line", { x1: "128", y1: "30", x2: "128", y2: "130", stroke: "var(--border-color)", strokeWidth: "1" }), /* @__PURE__ */ import_react23.default.createElement("line", { x1: "20", y1: "78", x2: "180", y2: "78", stroke: "var(--border-color)", strokeWidth: "1" }), /* @__PURE__ */ import_react23.default.createElement("line", { x1: "20", y1: "104", x2: "180", y2: "104", stroke: "var(--border-color)", strokeWidth: "1" }), /* @__PURE__ */ import_react23.default.createElement("rect", { x: "32", y: "87", width: "28", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.4" }), /* @__PURE__ */ import_react23.default.createElement("rect", { x: "84", y: "87", width: "28", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.4" }), /* @__PURE__ */ import_react23.default.createElement("rect", { x: "140", y: "87", width: "28", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.4" }), /* @__PURE__ */ import_react23.default.createElement("rect", { x: "32", y: "113", width: "20", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.3" }), /* @__PURE__ */ import_react23.default.createElement("rect", { x: "84", y: "113", width: "32", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.3" }), /* @__PURE__ */ import_react23.default.createElement("rect", { x: "140", y: "113", width: "20", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.3" }), /* @__PURE__ */ import_react23.default.createElement("circle", { cx: "148", cy: "108", r: "26", fill: "var(--surface-color)", stroke: "var(--border-color)", strokeWidth: "1.5" }), /* @__PURE__ */ import_react23.default.createElement("circle", { cx: "145", cy: "105", r: "10", stroke: "var(--text-secondary)", strokeWidth: "2.5", opacity: "0.5" }), /* @__PURE__ */ import_react23.default.createElement("line", { x1: "152", y1: "113", x2: "161", y2: "122", stroke: "var(--text-secondary)", strokeWidth: "2.5", strokeLinecap: "round", opacity: "0.5" }), /* @__PURE__ */ import_react23.default.createElement("line", { x1: "141", y1: "101", x2: "149", y2: "109", stroke: "var(--text-secondary)", strokeWidth: "2", strokeLinecap: "round", opacity: "0.5" }), /* @__PURE__ */ import_react23.default.createElement("line", { x1: "149", y1: "101", x2: "141", y2: "109", stroke: "var(--text-secondary)", strokeWidth: "2", strokeLinecap: "round", opacity: "0.5" })), /* @__PURE__ */ import_react23.default.createElement("p", { className: "dg-empty-title" }, "No data found"), /* @__PURE__ */ import_react23.default.createElement("p", { className: "dg-empty-subtitle" }, filterText || hasActiveFilters ? "Try adjusting your search or filters" : "No records to display"))), pagination && /* @__PURE__ */ import_react23.default.createElement("div", { className: "dg-pagination" }, /* @__PURE__ */ import_react23.default.createElement("div", { className: "dg-page-info" }, /* @__PURE__ */ import_react23.default.createElement(Tooltip, { title: "Export CSV", placement: "top" }, /* @__PURE__ */ import_react23.default.createElement("button", { className: "dg-icon-btn", onClick: handleExport }, /* @__PURE__ */ import_react23.default.createElement(import_lucide_react2.Download, { size: 14 }))), /* @__PURE__ */ import_react23.default.createElement("div", { className: "dg-per-page" }, /* @__PURE__ */ import_react23.default.createElement("span", null, "Rows per page:"), /* @__PURE__ */ import_react23.default.createElement(
5209
5224
  FilterSelect,
5210
5225
  {
5211
5226
  placement: "top",
@@ -9520,6 +9535,7 @@ function SmartSelect({
9520
9535
  value,
9521
9536
  onChange,
9522
9537
  onSearchChange,
9538
+ debounceMs = 300,
9523
9539
  getOptionLabel,
9524
9540
  getOptionValue,
9525
9541
  getOptionSubLabel,
@@ -9544,6 +9560,10 @@ function SmartSelect({
9544
9560
  style,
9545
9561
  sx
9546
9562
  }) {
9563
+ const debounceTimer = (0, import_react51.useRef)(null);
9564
+ (0, import_react51.useEffect)(() => () => {
9565
+ if (debounceTimer.current) clearTimeout(debounceTimer.current);
9566
+ }, []);
9547
9567
  const getValue = (0, import_react51.useCallback)(
9548
9568
  (o) => getOptionValue ? getOptionValue(o) : String(getOptionLabel(o)),
9549
9569
  [getOptionValue, getOptionLabel]
@@ -9570,6 +9590,7 @@ function SmartSelect({
9570
9590
  }, [multiple, value, getValue]);
9571
9591
  const handleInputChange = (0, import_react51.useCallback)((_, inputValue) => {
9572
9592
  if (!onSearchChange) return;
9593
+ if (debounceTimer.current) clearTimeout(debounceTimer.current);
9573
9594
  if (!inputValue) {
9574
9595
  onSearchChange("");
9575
9596
  return;
@@ -9577,10 +9598,15 @@ function SmartSelect({
9577
9598
  const hasLocalMatch = flatOptionsList.some(
9578
9599
  (opt) => getOptionLabel(opt).toLowerCase().includes(inputValue.toLowerCase()) || (getOptionSubLabel?.(opt) ?? "").toLowerCase().includes(inputValue.toLowerCase())
9579
9600
  );
9580
- if (!hasLocalMatch) {
9601
+ if (hasLocalMatch) return;
9602
+ if (debounceMs <= 0) {
9581
9603
  onSearchChange(inputValue);
9604
+ } else {
9605
+ debounceTimer.current = setTimeout(() => {
9606
+ onSearchChange(inputValue);
9607
+ }, debounceMs);
9582
9608
  }
9583
- }, [onSearchChange, flatOptionsList, getOptionLabel, getOptionSubLabel]);
9609
+ }, [onSearchChange, debounceMs, flatOptionsList, getOptionLabel, getOptionSubLabel]);
9584
9610
  const handleChange = (0, import_react51.useCallback)((_, newValue) => {
9585
9611
  if (!multiple || !allowChildNodesSelection || !getOptionChildren) {
9586
9612
  onChange?.(newValue);
package/dist/main.css CHANGED
@@ -507,6 +507,21 @@
507
507
  .dg-th-label.no-sort {
508
508
  cursor: default;
509
509
  }
510
+ .dg-sort-icon {
511
+ display: flex;
512
+ align-items: center;
513
+ opacity: 0;
514
+ transition: opacity 0.15s;
515
+ flex-shrink: 0;
516
+ color: var(--text-secondary);
517
+ }
518
+ .dg-sort-icon--active {
519
+ opacity: 1;
520
+ color: var(--primary-color);
521
+ }
522
+ .dg-thead-cell:hover .dg-sort-icon {
523
+ opacity: 1;
524
+ }
510
525
  .dg-th-actions {
511
526
  display: flex;
512
527
  align-items: center;
@@ -534,6 +549,20 @@
534
549
  background: var(--border-color);
535
550
  color: var(--text-color);
536
551
  }
552
+ .dg-th-filter-btn {
553
+ background: none;
554
+ border: none;
555
+ cursor: pointer;
556
+ padding: 3px;
557
+ border-radius: 4px;
558
+ color: var(--primary-color);
559
+ display: flex;
560
+ align-items: center;
561
+ transition: background 0.15s;
562
+ }
563
+ .dg-th-filter-btn:hover {
564
+ background: rgba(164, 27, 6, 0.08);
565
+ }
537
566
  .dg-resizer {
538
567
  width: 4px;
539
568
  height: 16px;
package/dist/main.d.cts CHANGED
@@ -850,6 +850,12 @@ interface Column<T> {
850
850
  row: T;
851
851
  field: string;
852
852
  }) => string;
853
+ /**
854
+ * Override the value written to CSV export for this column.
855
+ * Receives the raw field value and the full row item.
856
+ * Takes precedence over valueGetter / valueFormatter for export purposes.
857
+ */
858
+ exportValue?: (value: any, item: T) => string;
853
859
  width?: string | number;
854
860
  minWidth?: string | number;
855
861
  maxWidth?: string | number;
@@ -875,6 +881,12 @@ interface Column<T> {
875
881
  disableColumnMenu?: boolean;
876
882
  isExportable?: boolean;
877
883
  }
884
+ interface FilterState {
885
+ column: string;
886
+ operator: string;
887
+ value: string;
888
+ logic: 'AND' | 'OR';
889
+ }
878
890
  interface Action<T> {
879
891
  label: string;
880
892
  icon: React__default.ReactNode;
@@ -932,11 +944,25 @@ interface DataGridProps<T> {
932
944
  headerActions?: DataGridToolbarSlot;
933
945
  /** Extra content rendered in a second row below the header */
934
946
  toolbarContent?: DataGridToolbarSlot;
947
+ /**
948
+ * Limit global search to these column field names.
949
+ * When omitted, all visible columns are searched.
950
+ */
951
+ searchableColumns?: string[];
952
+ /**
953
+ * Called when the search query finds no local results.
954
+ * Use this to trigger a server-side search.
955
+ */
956
+ onSearchChange?: (query: string) => void;
957
+ /** Called whenever the applied advanced filters change. */
958
+ onFiltersChange?: (filters: FilterState[]) => void;
959
+ /** Hide the "Export CSV" button in the header toolbar. */
960
+ hideTopExport?: boolean;
935
961
  }
936
962
 
937
963
  declare function DataGrid<T extends {
938
964
  id: string | number;
939
- }>({ columns: initialColumnsProp, data, actions, loading, pagination, paginationMode, rowCount, paginationModel, onPaginationModelChange, pageSize: initialPageSize, pageSizeOptions, title, className, sx, onRowDoubleClick, onCellDoubleClick, headerActions, toolbarContent, }: DataGridProps<T>): React__default.JSX.Element;
965
+ }>({ columns: initialColumnsProp, data, actions, loading, pagination, paginationMode, rowCount, paginationModel, onPaginationModelChange, pageSize: initialPageSize, pageSizeOptions, title, className, sx, onRowDoubleClick, onCellDoubleClick, headerActions, toolbarContent, searchableColumns, onSearchChange, onFiltersChange, hideTopExport, }: DataGridProps<T>): React__default.JSX.Element;
940
966
 
941
967
  type SelectOption = {
942
968
  value: string | number;
@@ -1886,6 +1912,11 @@ interface SmartSelectProps<T = any> {
1886
1912
  * use this to trigger an API / server search.
1887
1913
  */
1888
1914
  onSearchChange?: (query: string) => void;
1915
+ /**
1916
+ * Debounce delay in ms before `onSearchChange` fires.
1917
+ * Defaults to 300 ms. Pass 0 to disable debouncing.
1918
+ */
1919
+ debounceMs?: number;
1889
1920
  /** Primary display label for an option (required) */
1890
1921
  getOptionLabel: (option: T) => string;
1891
1922
  /** Unique key for an option — defaults to the label string */
@@ -1939,7 +1970,7 @@ interface SmartSelectProps<T = any> {
1939
1970
  style?: CSSProperties;
1940
1971
  sx?: SxProp;
1941
1972
  }
1942
- declare function SmartSelect<T = any>({ options, value, onChange, onSearchChange, getOptionLabel, getOptionValue, getOptionSubLabel, getOptionChildren, multiple, allowChildNodesSelection, loading, loadingText, filterOptions: filterOptionsProp, renderOption: renderOptionProp, limitTags, label, placeholder, variant, size, disabled, error, helperText, fullWidth, required, className, style, sx, }: SmartSelectProps<T>): React__default.JSX.Element;
1973
+ declare function SmartSelect<T = any>({ options, value, onChange, onSearchChange, debounceMs, getOptionLabel, getOptionValue, getOptionSubLabel, getOptionChildren, multiple, allowChildNodesSelection, loading, loadingText, filterOptions: filterOptionsProp, renderOption: renderOptionProp, limitTags, label, placeholder, variant, size, disabled, error, helperText, fullWidth, required, className, style, sx, }: SmartSelectProps<T>): React__default.JSX.Element;
1943
1974
 
1944
1975
  type ToolbarButton = 'undo' | 'redo' | 'ai' | 'paragraph' | 'fontsize' | 'font' | 'color' | 'bold' | 'italic' | 'strike' | 'link' | 'lineheight' | 'ul' | 'ol' | 'align' | 'indent' | 'outdent' | 'table' | 'image' | 'video' | 'cut' | 'copy' | 'paste' | 'specialchars' | 'code' | 'fullscreen' | 'tts' | 'stt' | 'translate' | 'todo' | '|';
1945
1976
  type EditorVariant = 'default' | 'basic';
package/dist/main.d.ts CHANGED
@@ -850,6 +850,12 @@ interface Column<T> {
850
850
  row: T;
851
851
  field: string;
852
852
  }) => string;
853
+ /**
854
+ * Override the value written to CSV export for this column.
855
+ * Receives the raw field value and the full row item.
856
+ * Takes precedence over valueGetter / valueFormatter for export purposes.
857
+ */
858
+ exportValue?: (value: any, item: T) => string;
853
859
  width?: string | number;
854
860
  minWidth?: string | number;
855
861
  maxWidth?: string | number;
@@ -875,6 +881,12 @@ interface Column<T> {
875
881
  disableColumnMenu?: boolean;
876
882
  isExportable?: boolean;
877
883
  }
884
+ interface FilterState {
885
+ column: string;
886
+ operator: string;
887
+ value: string;
888
+ logic: 'AND' | 'OR';
889
+ }
878
890
  interface Action<T> {
879
891
  label: string;
880
892
  icon: React__default.ReactNode;
@@ -932,11 +944,25 @@ interface DataGridProps<T> {
932
944
  headerActions?: DataGridToolbarSlot;
933
945
  /** Extra content rendered in a second row below the header */
934
946
  toolbarContent?: DataGridToolbarSlot;
947
+ /**
948
+ * Limit global search to these column field names.
949
+ * When omitted, all visible columns are searched.
950
+ */
951
+ searchableColumns?: string[];
952
+ /**
953
+ * Called when the search query finds no local results.
954
+ * Use this to trigger a server-side search.
955
+ */
956
+ onSearchChange?: (query: string) => void;
957
+ /** Called whenever the applied advanced filters change. */
958
+ onFiltersChange?: (filters: FilterState[]) => void;
959
+ /** Hide the "Export CSV" button in the header toolbar. */
960
+ hideTopExport?: boolean;
935
961
  }
936
962
 
937
963
  declare function DataGrid<T extends {
938
964
  id: string | number;
939
- }>({ columns: initialColumnsProp, data, actions, loading, pagination, paginationMode, rowCount, paginationModel, onPaginationModelChange, pageSize: initialPageSize, pageSizeOptions, title, className, sx, onRowDoubleClick, onCellDoubleClick, headerActions, toolbarContent, }: DataGridProps<T>): React__default.JSX.Element;
965
+ }>({ columns: initialColumnsProp, data, actions, loading, pagination, paginationMode, rowCount, paginationModel, onPaginationModelChange, pageSize: initialPageSize, pageSizeOptions, title, className, sx, onRowDoubleClick, onCellDoubleClick, headerActions, toolbarContent, searchableColumns, onSearchChange, onFiltersChange, hideTopExport, }: DataGridProps<T>): React__default.JSX.Element;
940
966
 
941
967
  type SelectOption = {
942
968
  value: string | number;
@@ -1886,6 +1912,11 @@ interface SmartSelectProps<T = any> {
1886
1912
  * use this to trigger an API / server search.
1887
1913
  */
1888
1914
  onSearchChange?: (query: string) => void;
1915
+ /**
1916
+ * Debounce delay in ms before `onSearchChange` fires.
1917
+ * Defaults to 300 ms. Pass 0 to disable debouncing.
1918
+ */
1919
+ debounceMs?: number;
1889
1920
  /** Primary display label for an option (required) */
1890
1921
  getOptionLabel: (option: T) => string;
1891
1922
  /** Unique key for an option — defaults to the label string */
@@ -1939,7 +1970,7 @@ interface SmartSelectProps<T = any> {
1939
1970
  style?: CSSProperties;
1940
1971
  sx?: SxProp;
1941
1972
  }
1942
- declare function SmartSelect<T = any>({ options, value, onChange, onSearchChange, getOptionLabel, getOptionValue, getOptionSubLabel, getOptionChildren, multiple, allowChildNodesSelection, loading, loadingText, filterOptions: filterOptionsProp, renderOption: renderOptionProp, limitTags, label, placeholder, variant, size, disabled, error, helperText, fullWidth, required, className, style, sx, }: SmartSelectProps<T>): React__default.JSX.Element;
1973
+ declare function SmartSelect<T = any>({ options, value, onChange, onSearchChange, debounceMs, getOptionLabel, getOptionValue, getOptionSubLabel, getOptionChildren, multiple, allowChildNodesSelection, loading, loadingText, filterOptions: filterOptionsProp, renderOption: renderOptionProp, limitTags, label, placeholder, variant, size, disabled, error, helperText, fullWidth, required, className, style, sx, }: SmartSelectProps<T>): React__default.JSX.Element;
1943
1974
 
1944
1975
  type ToolbarButton = 'undo' | 'redo' | 'ai' | 'paragraph' | 'fontsize' | 'font' | 'color' | 'bold' | 'italic' | 'strike' | 'link' | 'lineheight' | 'ul' | 'ol' | 'align' | 'indent' | 'outdent' | 'table' | 'image' | 'video' | 'cut' | 'copy' | 'paste' | 'specialchars' | 'code' | 'fullscreen' | 'tts' | 'stt' | 'translate' | 'todo' | '|';
1945
1976
  type EditorVariant = 'default' | 'basic';
package/dist/main.js CHANGED
@@ -4471,7 +4471,8 @@ import {
4471
4471
  ArrowUp,
4472
4472
  ArrowDown,
4473
4473
  Trash2,
4474
- Plus
4474
+ Plus,
4475
+ ChevronsUpDown
4475
4476
  } from "lucide-react";
4476
4477
  function FilterSelect({
4477
4478
  value,
@@ -4566,7 +4567,11 @@ function DataGrid({
4566
4567
  onRowDoubleClick,
4567
4568
  onCellDoubleClick,
4568
4569
  headerActions,
4569
- toolbarContent
4570
+ toolbarContent,
4571
+ searchableColumns,
4572
+ onSearchChange,
4573
+ onFiltersChange,
4574
+ hideTopExport = false
4570
4575
  }) {
4571
4576
  const sxClass = useSx(sx);
4572
4577
  const [editingCell, setEditingCell] = useState9(null);
@@ -4671,6 +4676,9 @@ function DataGrid({
4671
4676
  return next;
4672
4677
  });
4673
4678
  }, [initialColumnsProp]);
4679
+ useEffect9(() => {
4680
+ onFiltersChange?.(advancedFilters);
4681
+ }, [advancedFilters, onFiltersChange]);
4674
4682
  const handleSort = (fieldKey, dir) => {
4675
4683
  if (dir !== void 0) {
4676
4684
  setSortField(fieldKey);
@@ -4720,10 +4728,10 @@ function DataGrid({
4720
4728
  };
4721
4729
  const filteredData = useMemo2(() => {
4722
4730
  return data.filter((item) => {
4723
- const matchesGlobal = !filterText || resolvedColumns.some((col) => {
4724
- if (col.hidden) return false;
4725
- return getDisplayValue(item, col).toLowerCase().includes(filterText.toLowerCase());
4726
- });
4731
+ const searchCols = searchableColumns ? resolvedColumns.filter((col) => searchableColumns.includes(String(col.field))) : resolvedColumns.filter((col) => !col.hidden);
4732
+ const matchesGlobal = !filterText || searchCols.some(
4733
+ (col) => getDisplayValue(item, col).toLowerCase().includes(filterText.toLowerCase())
4734
+ );
4727
4735
  const evalFilter = (f) => {
4728
4736
  if (!f.value && f.operator !== "is empty" && f.operator !== "is not empty") return true;
4729
4737
  const col = resolvedColumns.find((c) => String(c.field) === f.column || String(c.key) === f.column);
@@ -4831,7 +4839,11 @@ function DataGrid({
4831
4839
  }
4832
4840
  return matchesGlobal && matchesAdvanced;
4833
4841
  });
4834
- }, [data, filterText, advancedFilters, resolvedColumns]);
4842
+ }, [data, filterText, advancedFilters, resolvedColumns, searchableColumns]);
4843
+ useEffect9(() => {
4844
+ if (!onSearchChange || !filterText) return;
4845
+ if (filteredData.length === 0) onSearchChange(filterText);
4846
+ }, [filteredData, filterText, onSearchChange]);
4835
4847
  const sortedData = useMemo2(() => {
4836
4848
  if (!sortField || !sortDirection) return filteredData;
4837
4849
  const col = resolvedColumns.find((c) => c.field === sortField);
@@ -4862,11 +4874,16 @@ function DataGrid({
4862
4874
  (item) => exportableCols.map((c) => {
4863
4875
  const field = String(c.field);
4864
4876
  const raw = item[field];
4865
- let val = c.valueGetter ? c.valueGetter({ value: raw, row: item, field }) : raw;
4866
- if (c.valueFormatter) {
4867
- val = c.valueFormatter({ value: val, row: item, field });
4877
+ let str;
4878
+ if (c.exportValue) {
4879
+ str = c.exportValue(raw, item).replace(/"/g, '""');
4880
+ } else {
4881
+ let val = c.valueGetter ? c.valueGetter({ value: raw, row: item, field }) : raw;
4882
+ if (c.valueFormatter) {
4883
+ val = c.valueFormatter({ value: val, row: item, field });
4884
+ }
4885
+ str = val === null || val === void 0 ? "" : String(val).replace(/"/g, '""');
4868
4886
  }
4869
- const str = val === null || val === void 0 ? "" : String(val).replace(/"/g, '""');
4870
4887
  return `"${str}"`;
4871
4888
  }).join(",")
4872
4889
  );
@@ -4959,7 +4976,7 @@ function DataGrid({
4959
4976
  onClick: () => setShowManageColumns(true)
4960
4977
  },
4961
4978
  /* @__PURE__ */ React75.createElement(Columns, { size: 16 })
4962
- )), /* @__PURE__ */ React75.createElement("button", { className: "dg-action-btn", onClick: handleExport }, /* @__PURE__ */ React75.createElement(Download, { size: 14 }), " Export CSV"), headerActions && /* @__PURE__ */ React75.createElement("div", { className: `dg-header-slot ${alignClass(headerActions.align)}` }, headerActions.content))), /* @__PURE__ */ React75.createElement("div", { className: `dg-toolbar ${alignClass(toolbarContent?.align)}` }, toolbarContent?.content || ""), /* @__PURE__ */ React75.createElement("div", { className: `dg-table-wrap${paginatedData.length === 0 && !loading ? " dg-table-wrap--empty" : ""}` }, loading && /* @__PURE__ */ React75.createElement("div", { className: "dg-loading-overlay" }, /* @__PURE__ */ React75.createElement("div", { className: "dg-loading-spinner" })), /* @__PURE__ */ React75.createElement("table", { className: "dg-table" }, /* @__PURE__ */ React75.createElement("thead", null, /* @__PURE__ */ React75.createElement("tr", null, visibleColumns.map((col, idx) => {
4979
+ )), !hideTopExport && /* @__PURE__ */ React75.createElement("button", { className: "dg-action-btn", onClick: handleExport }, /* @__PURE__ */ React75.createElement(Download, { size: 14 }), " Export CSV"), headerActions && /* @__PURE__ */ React75.createElement("div", { className: `dg-header-slot ${alignClass(headerActions.align)}` }, headerActions.content))), /* @__PURE__ */ React75.createElement("div", { className: `dg-toolbar ${alignClass(toolbarContent?.align)}` }, toolbarContent?.content || ""), /* @__PURE__ */ React75.createElement("div", { className: `dg-table-wrap${paginatedData.length === 0 && !loading ? " dg-table-wrap--empty" : ""}` }, loading && /* @__PURE__ */ React75.createElement("div", { className: "dg-loading-overlay" }, /* @__PURE__ */ React75.createElement("div", { className: "dg-loading-spinner" })), /* @__PURE__ */ React75.createElement("table", { className: "dg-table" }, /* @__PURE__ */ React75.createElement("thead", null, /* @__PURE__ */ React75.createElement("tr", null, visibleColumns.map((col, idx) => {
4963
4980
  const colField = String(col.field);
4964
4981
  const width = columnWidths[colField] || 200;
4965
4982
  const leftOffset = getLeftOffset(col, idx);
@@ -4980,9 +4997,8 @@ function DataGrid({
4980
4997
  onClick: () => col.sortable !== false && handleSort(col.field || "")
4981
4998
  },
4982
4999
  col.headerName,
4983
- isSorted && sortDirection === "asc" && /* @__PURE__ */ React75.createElement(ChevronUp, { size: 12 }),
4984
- isSorted && sortDirection === "desc" && /* @__PURE__ */ React75.createElement(ChevronDown, { size: 12 })
4985
- ), /* @__PURE__ */ React75.createElement("div", { className: `dg-th-actions${isFiltered ? " dg-th-actions--filtered" : ""}` }, isFiltered && /* @__PURE__ */ React75.createElement(Filter, { size: 11, style: { color: "var(--primary-color)" } }), !col.disableColumnMenu && /* @__PURE__ */ React75.createElement(
5000
+ col.sortable !== false && /* @__PURE__ */ React75.createElement("span", { className: `dg-sort-icon${isSorted ? " dg-sort-icon--active" : ""}` }, isSorted && sortDirection === "asc" && /* @__PURE__ */ React75.createElement(ChevronUp, { size: 14 }), isSorted && sortDirection === "desc" && /* @__PURE__ */ React75.createElement(ChevronDown, { size: 14 }), !isSorted && /* @__PURE__ */ React75.createElement(ChevronsUpDown, { size: 14 }))
5001
+ ), /* @__PURE__ */ React75.createElement("div", { className: `dg-th-actions${isFiltered ? " dg-th-actions--filtered" : ""}` }, isFiltered && /* @__PURE__ */ React75.createElement("button", { className: "dg-th-filter-btn", onClick: () => setShowAdvancedFilter(true) }, /* @__PURE__ */ React75.createElement(Filter, { size: 11 })), !col.disableColumnMenu && /* @__PURE__ */ React75.createElement(
4986
5002
  "button",
4987
5003
  {
4988
5004
  className: "dg-th-menu-btn",
@@ -5074,7 +5090,7 @@ function DataGrid({
5074
5090
  },
5075
5091
  action.icon
5076
5092
  )))));
5077
- })()))))), paginatedData.length === 0 && /* @__PURE__ */ React75.createElement("div", { className: "dg-empty-state" }, /* @__PURE__ */ React75.createElement("svg", { className: "dg-empty-icon", viewBox: "0 0 200 160", fill: "none", xmlns: "http://www.w3.org/2000/svg" }, /* @__PURE__ */ React75.createElement("rect", { x: "20", y: "30", width: "160", height: "100", rx: "8", fill: "var(--hover-color)", stroke: "var(--border-color)", strokeWidth: "1.5" }), /* @__PURE__ */ React75.createElement("rect", { x: "20", y: "30", width: "160", height: "28", rx: "8", fill: "var(--border-color)", opacity: "0.5" }), /* @__PURE__ */ React75.createElement("rect", { x: "20", y: "50", width: "160", height: "8", rx: "0", fill: "var(--border-color)", opacity: "0.5" }), /* @__PURE__ */ React75.createElement("line", { x1: "72", y1: "30", x2: "72", y2: "130", stroke: "var(--border-color)", strokeWidth: "1" }), /* @__PURE__ */ React75.createElement("line", { x1: "128", y1: "30", x2: "128", y2: "130", stroke: "var(--border-color)", strokeWidth: "1" }), /* @__PURE__ */ React75.createElement("line", { x1: "20", y1: "78", x2: "180", y2: "78", stroke: "var(--border-color)", strokeWidth: "1" }), /* @__PURE__ */ React75.createElement("line", { x1: "20", y1: "104", x2: "180", y2: "104", stroke: "var(--border-color)", strokeWidth: "1" }), /* @__PURE__ */ React75.createElement("rect", { x: "32", y: "87", width: "28", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.4" }), /* @__PURE__ */ React75.createElement("rect", { x: "84", y: "87", width: "28", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.4" }), /* @__PURE__ */ React75.createElement("rect", { x: "140", y: "87", width: "28", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.4" }), /* @__PURE__ */ React75.createElement("rect", { x: "32", y: "113", width: "20", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.3" }), /* @__PURE__ */ React75.createElement("rect", { x: "84", y: "113", width: "32", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.3" }), /* @__PURE__ */ React75.createElement("rect", { x: "140", y: "113", width: "20", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.3" }), /* @__PURE__ */ React75.createElement("circle", { cx: "148", cy: "108", r: "26", fill: "var(--surface-color)", stroke: "var(--border-color)", strokeWidth: "1.5" }), /* @__PURE__ */ React75.createElement("circle", { cx: "145", cy: "105", r: "10", stroke: "var(--text-secondary)", strokeWidth: "2.5", opacity: "0.5" }), /* @__PURE__ */ React75.createElement("line", { x1: "152", y1: "113", x2: "161", y2: "122", stroke: "var(--text-secondary)", strokeWidth: "2.5", strokeLinecap: "round", opacity: "0.5" }), /* @__PURE__ */ React75.createElement("line", { x1: "141", y1: "101", x2: "149", y2: "109", stroke: "var(--text-secondary)", strokeWidth: "2", strokeLinecap: "round", opacity: "0.5" }), /* @__PURE__ */ React75.createElement("line", { x1: "149", y1: "101", x2: "141", y2: "109", stroke: "var(--text-secondary)", strokeWidth: "2", strokeLinecap: "round", opacity: "0.5" })), /* @__PURE__ */ React75.createElement("p", { className: "dg-empty-title" }, "No data found"), /* @__PURE__ */ React75.createElement("p", { className: "dg-empty-subtitle" }, filterText || hasActiveFilters ? "Try adjusting your search or filters" : "No records to display"))), pagination && /* @__PURE__ */ React75.createElement("div", { className: "dg-pagination" }, /* @__PURE__ */ React75.createElement("div", { className: "dg-page-info" }, /* @__PURE__ */ React75.createElement("div", { className: "dg-per-page" }, /* @__PURE__ */ React75.createElement("span", null, "Rows per page:"), /* @__PURE__ */ React75.createElement(
5093
+ })()))))), paginatedData.length === 0 && /* @__PURE__ */ React75.createElement("div", { className: "dg-empty-state" }, /* @__PURE__ */ React75.createElement("svg", { className: "dg-empty-icon", viewBox: "0 0 200 160", fill: "none", xmlns: "http://www.w3.org/2000/svg" }, /* @__PURE__ */ React75.createElement("rect", { x: "20", y: "30", width: "160", height: "100", rx: "8", fill: "var(--hover-color)", stroke: "var(--border-color)", strokeWidth: "1.5" }), /* @__PURE__ */ React75.createElement("rect", { x: "20", y: "30", width: "160", height: "28", rx: "8", fill: "var(--border-color)", opacity: "0.5" }), /* @__PURE__ */ React75.createElement("rect", { x: "20", y: "50", width: "160", height: "8", rx: "0", fill: "var(--border-color)", opacity: "0.5" }), /* @__PURE__ */ React75.createElement("line", { x1: "72", y1: "30", x2: "72", y2: "130", stroke: "var(--border-color)", strokeWidth: "1" }), /* @__PURE__ */ React75.createElement("line", { x1: "128", y1: "30", x2: "128", y2: "130", stroke: "var(--border-color)", strokeWidth: "1" }), /* @__PURE__ */ React75.createElement("line", { x1: "20", y1: "78", x2: "180", y2: "78", stroke: "var(--border-color)", strokeWidth: "1" }), /* @__PURE__ */ React75.createElement("line", { x1: "20", y1: "104", x2: "180", y2: "104", stroke: "var(--border-color)", strokeWidth: "1" }), /* @__PURE__ */ React75.createElement("rect", { x: "32", y: "87", width: "28", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.4" }), /* @__PURE__ */ React75.createElement("rect", { x: "84", y: "87", width: "28", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.4" }), /* @__PURE__ */ React75.createElement("rect", { x: "140", y: "87", width: "28", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.4" }), /* @__PURE__ */ React75.createElement("rect", { x: "32", y: "113", width: "20", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.3" }), /* @__PURE__ */ React75.createElement("rect", { x: "84", y: "113", width: "32", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.3" }), /* @__PURE__ */ React75.createElement("rect", { x: "140", y: "113", width: "20", height: "6", rx: "3", fill: "var(--border-color)", opacity: "0.3" }), /* @__PURE__ */ React75.createElement("circle", { cx: "148", cy: "108", r: "26", fill: "var(--surface-color)", stroke: "var(--border-color)", strokeWidth: "1.5" }), /* @__PURE__ */ React75.createElement("circle", { cx: "145", cy: "105", r: "10", stroke: "var(--text-secondary)", strokeWidth: "2.5", opacity: "0.5" }), /* @__PURE__ */ React75.createElement("line", { x1: "152", y1: "113", x2: "161", y2: "122", stroke: "var(--text-secondary)", strokeWidth: "2.5", strokeLinecap: "round", opacity: "0.5" }), /* @__PURE__ */ React75.createElement("line", { x1: "141", y1: "101", x2: "149", y2: "109", stroke: "var(--text-secondary)", strokeWidth: "2", strokeLinecap: "round", opacity: "0.5" }), /* @__PURE__ */ React75.createElement("line", { x1: "149", y1: "101", x2: "141", y2: "109", stroke: "var(--text-secondary)", strokeWidth: "2", strokeLinecap: "round", opacity: "0.5" })), /* @__PURE__ */ React75.createElement("p", { className: "dg-empty-title" }, "No data found"), /* @__PURE__ */ React75.createElement("p", { className: "dg-empty-subtitle" }, filterText || hasActiveFilters ? "Try adjusting your search or filters" : "No records to display"))), pagination && /* @__PURE__ */ React75.createElement("div", { className: "dg-pagination" }, /* @__PURE__ */ React75.createElement("div", { className: "dg-page-info" }, /* @__PURE__ */ React75.createElement(Tooltip, { title: "Export CSV", placement: "top" }, /* @__PURE__ */ React75.createElement("button", { className: "dg-icon-btn", onClick: handleExport }, /* @__PURE__ */ React75.createElement(Download, { size: 14 }))), /* @__PURE__ */ React75.createElement("div", { className: "dg-per-page" }, /* @__PURE__ */ React75.createElement("span", null, "Rows per page:"), /* @__PURE__ */ React75.createElement(
5078
5094
  FilterSelect,
5079
5095
  {
5080
5096
  placement: "top",
@@ -9433,7 +9449,7 @@ function UserSelectionField({
9433
9449
  }
9434
9450
 
9435
9451
  // lib/SmartSelect/SmartSelect.tsx
9436
- import React108, { useCallback as useCallback11, useMemo as useMemo3 } from "react";
9452
+ import React108, { useCallback as useCallback11, useEffect as useEffect21, useMemo as useMemo3, useRef as useRef25 } from "react";
9437
9453
  var CheckIcon3 = () => /* @__PURE__ */ React108.createElement("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React108.createElement("polyline", { points: "20 6 9 17 4 12" }));
9438
9454
  function flattenTree(options, getChildren, depth = 0) {
9439
9455
  return options.flatMap((opt) => [
@@ -9458,6 +9474,7 @@ function SmartSelect({
9458
9474
  value,
9459
9475
  onChange,
9460
9476
  onSearchChange,
9477
+ debounceMs = 300,
9461
9478
  getOptionLabel,
9462
9479
  getOptionValue,
9463
9480
  getOptionSubLabel,
@@ -9482,6 +9499,10 @@ function SmartSelect({
9482
9499
  style,
9483
9500
  sx
9484
9501
  }) {
9502
+ const debounceTimer = useRef25(null);
9503
+ useEffect21(() => () => {
9504
+ if (debounceTimer.current) clearTimeout(debounceTimer.current);
9505
+ }, []);
9485
9506
  const getValue = useCallback11(
9486
9507
  (o) => getOptionValue ? getOptionValue(o) : String(getOptionLabel(o)),
9487
9508
  [getOptionValue, getOptionLabel]
@@ -9508,6 +9529,7 @@ function SmartSelect({
9508
9529
  }, [multiple, value, getValue]);
9509
9530
  const handleInputChange = useCallback11((_, inputValue) => {
9510
9531
  if (!onSearchChange) return;
9532
+ if (debounceTimer.current) clearTimeout(debounceTimer.current);
9511
9533
  if (!inputValue) {
9512
9534
  onSearchChange("");
9513
9535
  return;
@@ -9515,10 +9537,15 @@ function SmartSelect({
9515
9537
  const hasLocalMatch = flatOptionsList.some(
9516
9538
  (opt) => getOptionLabel(opt).toLowerCase().includes(inputValue.toLowerCase()) || (getOptionSubLabel?.(opt) ?? "").toLowerCase().includes(inputValue.toLowerCase())
9517
9539
  );
9518
- if (!hasLocalMatch) {
9540
+ if (hasLocalMatch) return;
9541
+ if (debounceMs <= 0) {
9519
9542
  onSearchChange(inputValue);
9543
+ } else {
9544
+ debounceTimer.current = setTimeout(() => {
9545
+ onSearchChange(inputValue);
9546
+ }, debounceMs);
9520
9547
  }
9521
- }, [onSearchChange, flatOptionsList, getOptionLabel, getOptionSubLabel]);
9548
+ }, [onSearchChange, debounceMs, flatOptionsList, getOptionLabel, getOptionSubLabel]);
9522
9549
  const handleChange = useCallback11((_, newValue) => {
9523
9550
  if (!multiple || !allowChildNodesSelection || !getOptionChildren) {
9524
9551
  onChange?.(newValue);
@@ -9658,7 +9685,7 @@ function SmartSelect({
9658
9685
  }
9659
9686
 
9660
9687
  // lib/RufousTextEditor/RufousTextEditor.tsx
9661
- import React119, { useMemo as useMemo5, useCallback as useCallback16, useState as useState35, useRef as useRef32, useEffect as useEffect29 } from "react";
9688
+ import React119, { useMemo as useMemo5, useCallback as useCallback16, useState as useState35, useRef as useRef33, useEffect as useEffect30 } from "react";
9662
9689
  import { createPortal as createPortal8 } from "react-dom";
9663
9690
  import { useEditor, EditorContent, EditorContext, FloatingMenu, BubbleMenu } from "@tiptap/react";
9664
9691
  import StarterKit from "@tiptap/starter-kit";
@@ -9684,7 +9711,7 @@ import { ReactRenderer } from "@tiptap/react";
9684
9711
  import tippy from "tippy.js";
9685
9712
 
9686
9713
  // lib/RufousTextEditor/MentionList.tsx
9687
- import React109, { forwardRef as forwardRef11, useEffect as useEffect21, useImperativeHandle, useState as useState26 } from "react";
9714
+ import React109, { forwardRef as forwardRef11, useEffect as useEffect22, useImperativeHandle, useState as useState26 } from "react";
9688
9715
  var MentionList = forwardRef11((props, ref) => {
9689
9716
  const [selectedIndex, setSelectedIndex] = useState26(0);
9690
9717
  const selectItem = (index) => {
@@ -9693,7 +9720,7 @@ var MentionList = forwardRef11((props, ref) => {
9693
9720
  props.command({ id: item.id, label: item.shortName || item.name });
9694
9721
  }
9695
9722
  };
9696
- useEffect21(() => setSelectedIndex(0), [props.items]);
9723
+ useEffect22(() => setSelectedIndex(0), [props.items]);
9697
9724
  useImperativeHandle(ref, () => ({
9698
9725
  onKeyDown: ({ event }) => {
9699
9726
  if (event.key === "ArrowUp") {
@@ -9780,11 +9807,11 @@ function createMentionSuggestion(users) {
9780
9807
  }
9781
9808
 
9782
9809
  // lib/RufousTextEditor/Toolbar.tsx
9783
- import React115, { useState as useState31, useRef as useRef28, useEffect as useEffect25, useCallback as useCallback15 } from "react";
9810
+ import React115, { useState as useState31, useRef as useRef29, useEffect as useEffect26, useCallback as useCallback15 } from "react";
9784
9811
  import { createPortal as createPortal4 } from "react-dom";
9785
9812
 
9786
9813
  // lib/RufousTextEditor/TextToSpeech.tsx
9787
- import React110, { useState as useState27, useEffect as useEffect22, useRef as useRef25, useCallback as useCallback12, forwardRef as forwardRef12, useImperativeHandle as useImperativeHandle2 } from "react";
9814
+ import React110, { useState as useState27, useEffect as useEffect23, useRef as useRef26, useCallback as useCallback12, forwardRef as forwardRef12, useImperativeHandle as useImperativeHandle2 } from "react";
9788
9815
  var TextToSpeech = forwardRef12(({ editor, onTextToSpeech }, ref) => {
9789
9816
  const [speaking, setSpeaking] = useState27(false);
9790
9817
  const [paused, setPaused] = useState27(false);
@@ -9792,9 +9819,9 @@ var TextToSpeech = forwardRef12(({ editor, onTextToSpeech }, ref) => {
9792
9819
  const [selectedVoice, setSelectedVoice] = useState27("");
9793
9820
  const [rate, setRate] = useState27(1);
9794
9821
  const [showPanel, setShowPanel] = useState27(false);
9795
- const utteranceRef = useRef25(null);
9796
- const panelRef = useRef25(null);
9797
- useEffect22(() => {
9822
+ const utteranceRef = useRef26(null);
9823
+ const panelRef = useRef26(null);
9824
+ useEffect23(() => {
9798
9825
  const synth = window.speechSynthesis;
9799
9826
  const loadVoices = () => {
9800
9827
  const available = synth.getVoices();
@@ -9812,7 +9839,7 @@ var TextToSpeech = forwardRef12(({ editor, onTextToSpeech }, ref) => {
9812
9839
  synth.removeEventListener("voiceschanged", loadVoices);
9813
9840
  };
9814
9841
  }, [selectedVoice]);
9815
- useEffect22(() => {
9842
+ useEffect23(() => {
9816
9843
  const handleClick = (e) => {
9817
9844
  if (panelRef.current && !panelRef.current.contains(e.target)) {
9818
9845
  setShowPanel(false);
@@ -9929,18 +9956,18 @@ var TextToSpeech = forwardRef12(({ editor, onTextToSpeech }, ref) => {
9929
9956
  var TextToSpeech_default = TextToSpeech;
9930
9957
 
9931
9958
  // lib/RufousTextEditor/SpeechToText.tsx
9932
- import React111, { useState as useState28, useRef as useRef26, useCallback as useCallback13, useEffect as useEffect23, forwardRef as forwardRef13, useImperativeHandle as useImperativeHandle3 } from "react";
9959
+ import React111, { useState as useState28, useRef as useRef27, useCallback as useCallback13, useEffect as useEffect24, forwardRef as forwardRef13, useImperativeHandle as useImperativeHandle3 } from "react";
9933
9960
  var SpeechToText = forwardRef13(({ editor, onSpeechToText }, ref) => {
9934
9961
  const [listening, setListening] = useState28(false);
9935
9962
  const [showPanel, setShowPanel] = useState28(false);
9936
9963
  const [language, setLanguage] = useState28("en-US");
9937
9964
  const [interim, setInterim] = useState28("");
9938
- const recognitionRef = useRef26(null);
9939
- const panelRef = useRef26(null);
9940
- const isListeningRef = useRef26(false);
9965
+ const recognitionRef = useRef27(null);
9966
+ const panelRef = useRef27(null);
9967
+ const isListeningRef = useRef27(false);
9941
9968
  const SpeechRecognitionAPI = typeof window !== "undefined" ? window.SpeechRecognition || window.webkitSpeechRecognition : null;
9942
9969
  const supported = !!SpeechRecognitionAPI;
9943
- useEffect23(() => {
9970
+ useEffect24(() => {
9944
9971
  const handleClick = (e) => {
9945
9972
  if (panelRef.current && !panelRef.current.contains(e.target)) {
9946
9973
  setShowPanel(false);
@@ -10091,7 +10118,7 @@ var SpeechToText = forwardRef13(({ editor, onSpeechToText }, ref) => {
10091
10118
  var SpeechToText_default = SpeechToText;
10092
10119
 
10093
10120
  // lib/RufousTextEditor/AICommands.tsx
10094
- import React112, { useState as useState29, useRef as useRef27, useEffect as useEffect24, useCallback as useCallback14 } from "react";
10121
+ import React112, { useState as useState29, useRef as useRef28, useEffect as useEffect25, useCallback as useCallback14 } from "react";
10095
10122
  import { createPortal as createPortal2 } from "react-dom";
10096
10123
  var AI_COMMANDS = [
10097
10124
  { id: "improve", label: "Improve writing", prompt: "Improve the following text to make it clearer, more engaging, and well-structured. Return only the improved text, no explanations." },
@@ -10147,8 +10174,8 @@ var AICommands = ({ editor, onAICommand }) => {
10147
10174
  const [originalText, setOriginalText] = useState29("");
10148
10175
  const [selectionRange, setSelectionRange] = useState29(null);
10149
10176
  const [previousResults, setPreviousResults] = useState29([]);
10150
- const panelRef = useRef27(null);
10151
- useEffect24(() => {
10177
+ const panelRef = useRef28(null);
10178
+ useEffect25(() => {
10152
10179
  const handleClick = (e) => {
10153
10180
  if (panelRef.current && !panelRef.current.contains(e.target)) {
10154
10181
  setOpen(false);
@@ -11346,9 +11373,9 @@ var SPECIAL_CHARS = [
11346
11373
  ];
11347
11374
  var Dropdown = ({ trigger, children, className = "", keepOpen = false }) => {
11348
11375
  const [open, setOpen] = useState31(false);
11349
- const ref = useRef28(null);
11350
- const menuRef = useRef28(null);
11351
- useEffect25(() => {
11376
+ const ref = useRef29(null);
11377
+ const menuRef = useRef29(null);
11378
+ useEffect26(() => {
11352
11379
  const handleClick = (e) => {
11353
11380
  const target = e.target;
11354
11381
  const inTrigger = ref.current?.contains(target);
@@ -11360,7 +11387,7 @@ var Dropdown = ({ trigger, children, className = "", keepOpen = false }) => {
11360
11387
  document.addEventListener("mousedown", handleClick);
11361
11388
  return () => document.removeEventListener("mousedown", handleClick);
11362
11389
  }, []);
11363
- useEffect25(() => {
11390
+ useEffect26(() => {
11364
11391
  if (!open || !menuRef.current || !ref.current) return;
11365
11392
  const menu = menuRef.current;
11366
11393
  const trigger2 = ref.current;
@@ -11477,7 +11504,7 @@ var ImagePanel = ({ editor, onClose, onImageUpload }) => {
11477
11504
  const [activeTab, setActiveTab] = useState31("upload");
11478
11505
  const [url, setUrl] = useState31("");
11479
11506
  const [isDragging, setIsDragging] = useState31(false);
11480
- const fileInputRef = useRef28(null);
11507
+ const fileInputRef = useRef29(null);
11481
11508
  const getBase64 = (file) => {
11482
11509
  return new Promise((resolve, reject) => {
11483
11510
  const reader = new FileReader();
@@ -11655,10 +11682,10 @@ var ColorPickerPanel = ({ editor, onClose }) => {
11655
11682
  var Toolbar = ({ editor, setLink, onAICommand, onTranslate, onSpeechToText, onTextToSpeech, onClose, onImageUpload, visibleButtons, isFullscreen, onToggleFullscreen }) => {
11656
11683
  const [, setEditorState] = useState31(0);
11657
11684
  const [todoEnabled, setTodoEnabled] = useState31(false);
11658
- const ttsRef = useRef28(null);
11659
- const sttRef = useRef28(null);
11685
+ const ttsRef = useRef29(null);
11686
+ const sttRef = useRef29(null);
11660
11687
  const show = (id) => !visibleButtons || visibleButtons.has(id);
11661
- useEffect25(() => {
11688
+ useEffect26(() => {
11662
11689
  if (!editor) return;
11663
11690
  const onTransaction = () => setEditorState((n) => n + 1);
11664
11691
  editor.on("transaction", onTransaction);
@@ -12281,7 +12308,7 @@ var Toolbar = ({ editor, setLink, onAICommand, onTranslate, onSpeechToText, onTe
12281
12308
  var Toolbar_default = Toolbar;
12282
12309
 
12283
12310
  // lib/RufousTextEditor/ImageToolbar.tsx
12284
- import React116, { useState as useState32, useEffect as useEffect26, useRef as useRef29 } from "react";
12311
+ import React116, { useState as useState32, useEffect as useEffect27, useRef as useRef30 } from "react";
12285
12312
  import { createPortal as createPortal5 } from "react-dom";
12286
12313
  var ALIGNMENTS = [
12287
12314
  { value: "left", label: "Left", icon: "\u2630" },
@@ -12300,7 +12327,7 @@ var ImagePropertiesModal = ({ editor, node, onClose }) => {
12300
12327
  const [lockRatio, setLockRatio] = useState32(true);
12301
12328
  const [naturalWidth, setNaturalWidth] = useState32(0);
12302
12329
  const [naturalHeight, setNaturalHeight] = useState32(0);
12303
- useEffect26(() => {
12330
+ useEffect27(() => {
12304
12331
  if (src) {
12305
12332
  const img = new window.Image();
12306
12333
  img.onload = () => {
@@ -12423,8 +12450,8 @@ var ImageToolbar = ({ editor }) => {
12423
12450
  const [showVAlign, setShowVAlign] = useState32(false);
12424
12451
  const [copyStatus, setCopyStatus] = useState32("");
12425
12452
  const [pos, setPos] = useState32(null);
12426
- const toolbarRef = useRef29(null);
12427
- useEffect26(() => {
12453
+ const toolbarRef = useRef30(null);
12454
+ useEffect27(() => {
12428
12455
  if (!editor) return;
12429
12456
  const update = () => {
12430
12457
  const { selection } = editor.state;
@@ -12560,7 +12587,7 @@ var ImageToolbar = ({ editor }) => {
12560
12587
  var ImageToolbar_default = ImageToolbar;
12561
12588
 
12562
12589
  // lib/RufousTextEditor/VideoToolbar.tsx
12563
- import React117, { useState as useState33, useEffect as useEffect27, useRef as useRef30 } from "react";
12590
+ import React117, { useState as useState33, useEffect as useEffect28, useRef as useRef31 } from "react";
12564
12591
  import { createPortal as createPortal6 } from "react-dom";
12565
12592
  var ALIGNMENTS2 = [
12566
12593
  { value: "left", label: "Left", icon: "\u2630" },
@@ -12675,8 +12702,8 @@ var VideoToolbar = ({ editor }) => {
12675
12702
  const [showAlign, setShowAlign] = useState33(false);
12676
12703
  const [copyStatus, setCopyStatus] = useState33("");
12677
12704
  const [pos, setPos] = useState33(null);
12678
- const toolbarRef = useRef30(null);
12679
- useEffect27(() => {
12705
+ const toolbarRef = useRef31(null);
12706
+ useEffect28(() => {
12680
12707
  if (!editor) return;
12681
12708
  const update = () => {
12682
12709
  const { selection } = editor.state;
@@ -12800,7 +12827,7 @@ var VideoToolbar = ({ editor }) => {
12800
12827
  var VideoToolbar_default = VideoToolbar;
12801
12828
 
12802
12829
  // lib/RufousTextEditor/TableToolbar.tsx
12803
- import React118, { useState as useState34, useEffect as useEffect28, useRef as useRef31 } from "react";
12830
+ import React118, { useState as useState34, useEffect as useEffect29, useRef as useRef32 } from "react";
12804
12831
  import { createPortal as createPortal7 } from "react-dom";
12805
12832
  var IconAddRowBefore = () => /* @__PURE__ */ React118.createElement("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React118.createElement("path", { d: "M20 3H4c-.55 0-1 .45-1 1v16c0 .55.45 1 1 1h16c.55 0 1-.45 1-1V4c0-.55-.45-1-1-1zm-1 8H5V5h14v6zm0 8H5v-6h14v6z" }), /* @__PURE__ */ React118.createElement("path", { d: "M9 6h2v1.5h1.5v2H11V11H9V9.5H7.5v-2H9z" }));
12806
12833
  var IconAddRowAfter = () => /* @__PURE__ */ React118.createElement("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React118.createElement("path", { d: "M20 3H4c-.55 0-1 .45-1 1v16c0 .55.45 1 1 1h16c.55 0 1-.45 1-1V4c0-.55-.45-1-1-1zm-1 8H5V5h14v6zm0 8H5v-6h14v6z" }), /* @__PURE__ */ React118.createElement("path", { d: "M9 14h2v1.5h1.5v2H11V19H9v-1.5H7.5v-2H9z" }));
@@ -12814,8 +12841,8 @@ var IconSplitCell = () => /* @__PURE__ */ React118.createElement("svg", { width:
12814
12841
  var IconToggleHeader = () => /* @__PURE__ */ React118.createElement("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React118.createElement("path", { d: "M3 3h18v18H3V3zm2 2v4h14V5H5zm0 6v8h14v-8H5z" }), /* @__PURE__ */ React118.createElement("rect", { x: "5", y: "5", width: "14", height: "4", opacity: "0.4" }));
12815
12842
  var TableToolbar = ({ editor }) => {
12816
12843
  const [pos, setPos] = useState34(null);
12817
- const toolbarRef = useRef31(null);
12818
- useEffect28(() => {
12844
+ const toolbarRef = useRef32(null);
12845
+ useEffect29(() => {
12819
12846
  if (!editor) return;
12820
12847
  const update = () => {
12821
12848
  if (!editor.isActive("table")) {
@@ -13054,12 +13081,12 @@ var RufousTextEditor = ({
13054
13081
  return visible;
13055
13082
  }, [buttons, variant, hideButtons]);
13056
13083
  const mentionSuggestion = useMemo5(() => createMentionSuggestion(mentions), [mentions]);
13057
- const onChangeRef = useRef32(onChange);
13058
- const onBlurRef = useRef32(onBlur);
13059
- useEffect29(() => {
13084
+ const onChangeRef = useRef33(onChange);
13085
+ const onBlurRef = useRef33(onBlur);
13086
+ useEffect30(() => {
13060
13087
  onChangeRef.current = onChange;
13061
13088
  }, [onChange]);
13062
- useEffect29(() => {
13089
+ useEffect30(() => {
13063
13090
  onBlurRef.current = onBlur;
13064
13091
  }, [onBlur]);
13065
13092
  const isEditable = editable && !disabled;
@@ -13160,8 +13187,8 @@ var RufousTextEditor = ({
13160
13187
  onChangeRef.current?.(e.getHTML(), e.getJSON());
13161
13188
  }
13162
13189
  });
13163
- const wrapperRef = useRef32(null);
13164
- useEffect29(() => {
13190
+ const wrapperRef = useRef33(null);
13191
+ useEffect30(() => {
13165
13192
  if (!editor) return;
13166
13193
  let blurTimer = null;
13167
13194
  const handler = ({ event }) => {
@@ -13179,7 +13206,7 @@ var RufousTextEditor = ({
13179
13206
  if (blurTimer) clearTimeout(blurTimer);
13180
13207
  };
13181
13208
  }, [editor]);
13182
- const setLinkRef = useRef32(null);
13209
+ const setLinkRef = useRef33(null);
13183
13210
  const [linkModalOpen, setLinkModalOpen] = useState35(false);
13184
13211
  const [linkUrl, setLinkUrl] = useState35("");
13185
13212
  const [linkText, setLinkText] = useState35("");
@@ -13224,10 +13251,10 @@ var RufousTextEditor = ({
13224
13251
  setLinkSelection({ from, to });
13225
13252
  setLinkModalOpen(true);
13226
13253
  }, [editor]);
13227
- useEffect29(() => {
13254
+ useEffect30(() => {
13228
13255
  setLinkRef.current = setLink;
13229
13256
  }, [setLink]);
13230
- useEffect29(() => {
13257
+ useEffect30(() => {
13231
13258
  if (!editor?.view) return;
13232
13259
  const handleKeyDown = (event) => {
13233
13260
  if ((event.ctrlKey || event.metaKey) && event.key === "k") {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rufous/ui",
3
3
  "private": false,
4
- "version": "0.3.17",
4
+ "version": "0.3.18",
5
5
  "type": "module",
6
6
  "description": "Experimental: A lightweight React UI component library (Beta)",
7
7
  "style": "./dist/main.css",