@rufous/ui 0.3.24 → 0.3.26

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
@@ -1869,6 +1869,7 @@ var TextField = (0, import_react17.forwardRef)(({
1869
1869
  slotProps,
1870
1870
  InputProps,
1871
1871
  numberVariant,
1872
+ step: stepProp,
1872
1873
  multiline = false,
1873
1874
  rows,
1874
1875
  maxRows,
@@ -2004,7 +2005,7 @@ var TextField = (0, import_react17.forwardRef)(({
2004
2005
  required,
2005
2006
  disabled,
2006
2007
  readOnly,
2007
- step: type === "number" && numberVariant ? STEP_BY_VARIANT[numberVariant] : void 0,
2008
+ step: type === "number" ? stepProp ?? (numberVariant ? STEP_BY_VARIANT[numberVariant] : void 0) : void 0,
2008
2009
  min: type === "number" && numberVariant ? MIN_BY_VARIANT[numberVariant] : void 0,
2009
2010
  ...slotProps?.input,
2010
2011
  ...props,
@@ -5518,7 +5519,7 @@ function DataGrid({
5518
5519
  action.icon
5519
5520
  )))));
5520
5521
  })()));
5521
- }))), filteredData.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"))), customFooter ? /* @__PURE__ */ import_react23.default.createElement("div", { className: "dg-pagination dg-pagination--custom" }, customFooter) : pagination && !tOpts.hideFooter && /* @__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(
5522
+ }))), filteredData.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"))), customFooter ? /* @__PURE__ */ import_react23.default.createElement("div", { className: "dg-pagination dg-pagination--custom" }, customFooter) : pagination && !tOpts.hideFooter && /* @__PURE__ */ import_react23.default.createElement("div", { className: "dg-pagination" }, /* @__PURE__ */ import_react23.default.createElement("div", { className: "dg-page-info" }, !tOpts.hideBottomExport && !tOpts.hideExport && /* @__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(
5522
5523
  FilterSelect,
5523
5524
  {
5524
5525
  placement: "top",
@@ -9332,21 +9333,24 @@ PhoneField.displayName = "PhoneField";
9332
9333
  var import_react49 = __toESM(require("react"), 1);
9333
9334
  var import_react_dom11 = __toESM(require("react-dom"), 1);
9334
9335
  var import_lucide_react3 = require("lucide-react");
9336
+ function nodeId(node) {
9337
+ return node.id ?? node._id;
9338
+ }
9335
9339
  function collectDescendants(node) {
9336
- const ids = [String(node.id)];
9340
+ const ids = [String(nodeId(node))];
9337
9341
  node.children?.forEach((c) => ids.push(...collectDescendants(c)));
9338
9342
  return ids;
9339
9343
  }
9340
9344
  function findNodeById(nodes, id) {
9341
9345
  for (const node of nodes) {
9342
- if (String(node.id) === id) return node;
9346
+ if (String(nodeId(node)) === id) return node;
9343
9347
  const found = findNodeById(node.children ?? [], id);
9344
9348
  if (found) return found;
9345
9349
  }
9346
9350
  return null;
9347
9351
  }
9348
9352
  function getNodeState(node, selected) {
9349
- if (selected.has(String(node.id))) return "checked";
9353
+ if (selected.has(String(nodeId(node)))) return "checked";
9350
9354
  if (!node.children?.length) return "unchecked";
9351
9355
  const states = node.children.map((c) => getNodeState(c, selected));
9352
9356
  if (states.every((s2) => s2 === "checked")) return "checked";
@@ -9368,7 +9372,7 @@ function getTopLevelSelected(nodes, selected) {
9368
9372
  function getAllSelected(nodes, selected) {
9369
9373
  const result = [];
9370
9374
  for (const node of nodes) {
9371
- if (selected.has(String(node.id))) result.push(node);
9375
+ if (selected.has(String(nodeId(node)))) result.push(node);
9372
9376
  if (node.children?.length) result.push(...getAllSelected(node.children, selected));
9373
9377
  }
9374
9378
  return result;
@@ -9406,10 +9410,10 @@ function TreeNodeItem({
9406
9410
  onSelect,
9407
9411
  isFiltering
9408
9412
  }) {
9409
- const nodeId = String(node.id);
9413
+ const nid = String(nodeId(node));
9410
9414
  const hasChildren = !!node.children?.length;
9411
- const isExpanded = isFiltering || expanded.has(nodeId);
9412
- const state = allowChildSelection ? getNodeState(node, selected) : selected.has(nodeId) ? "checked" : "unchecked";
9415
+ const isExpanded = isFiltering || expanded.has(nid);
9416
+ const state = allowChildSelection ? getNodeState(node, selected) : selected.has(nid) ? "checked" : "unchecked";
9413
9417
  return /* @__PURE__ */ import_react49.default.createElement("div", { className: "rf-tsn" }, /* @__PURE__ */ import_react49.default.createElement(
9414
9418
  "div",
9415
9419
  {
@@ -9424,7 +9428,7 @@ function TreeNodeItem({
9424
9428
  className: "rf-tsn__expand",
9425
9429
  onClick: (e) => {
9426
9430
  e.stopPropagation();
9427
- onToggleExpand(nodeId);
9431
+ onToggleExpand(nid);
9428
9432
  }
9429
9433
  },
9430
9434
  isExpanded ? /* @__PURE__ */ import_react49.default.createElement(import_lucide_react3.ChevronDown, { size: 14 }) : /* @__PURE__ */ import_react49.default.createElement(import_lucide_react3.ChevronRight, { size: 14 })
@@ -9573,7 +9577,7 @@ function TreeSelect({
9573
9577
  };
9574
9578
  }, [open, computePosition3]);
9575
9579
  const handleSelect = (node) => {
9576
- const nodeId = String(node.id);
9580
+ const nid = String(nodeId(node));
9577
9581
  if (isMultiple) {
9578
9582
  const newSet = new Set(selectedSet);
9579
9583
  if (allowChildSelection) {
@@ -9587,21 +9591,21 @@ function TreeSelect({
9587
9591
  onNodeSelect?.({ node });
9588
9592
  }
9589
9593
  } else {
9590
- if (newSet.has(nodeId)) {
9591
- newSet.delete(nodeId);
9594
+ if (newSet.has(nid)) {
9595
+ newSet.delete(nid);
9592
9596
  onNodeUnselect?.({ node });
9593
9597
  } else {
9594
- newSet.add(nodeId);
9598
+ newSet.add(nid);
9595
9599
  onNodeSelect?.({ node });
9596
9600
  }
9597
9601
  }
9598
- onChange?.({ value: setToRecord(newSet) });
9602
+ onChange?.({ value: setToRecord(newSet), node });
9599
9603
  } else {
9600
- if (selectedSet.has(nodeId)) {
9601
- onChange?.({ value: null });
9604
+ if (selectedSet.has(nid)) {
9605
+ onChange?.({ value: null, node: null });
9602
9606
  onNodeUnselect?.({ node });
9603
9607
  } else {
9604
- onChange?.({ value: node.id });
9608
+ onChange?.({ value: nodeId(node), node });
9605
9609
  onNodeSelect?.({ node });
9606
9610
  closeDropdown();
9607
9611
  }
@@ -9616,7 +9620,7 @@ function TreeSelect({
9616
9620
  };
9617
9621
  const handleClear = (e) => {
9618
9622
  e.stopPropagation();
9619
- onChange?.({ value: isMultiple ? {} : null });
9623
+ onChange?.({ value: isMultiple ? {} : null, node: null });
9620
9624
  };
9621
9625
  const handleRemoveTag = (e, node) => {
9622
9626
  e.stopPropagation();
@@ -9907,11 +9911,18 @@ function SmartSelect({
9907
9911
  }, [options, getOptionChildren]);
9908
9912
  const flatOptionsList = (0, import_react51.useMemo)(() => flatItems.map((f) => f.option), [flatItems]);
9909
9913
  const displayOptions = (0, import_react51.useMemo)(() => {
9910
- if (!searchResults.length) return flatOptionsList;
9911
- const localKeys = new Set(flatOptionsList.map((o) => getValue(o)));
9912
- const serverOnly = searchResults.filter((o) => !localKeys.has(getValue(o)));
9913
- return [...flatOptionsList, ...serverOnly];
9914
- }, [flatOptionsList, searchResults, getValue]);
9914
+ let base = flatOptionsList;
9915
+ if (searchResults.length) {
9916
+ const localKeys = new Set(flatOptionsList.map((o) => getValue(o)));
9917
+ const serverOnly = searchResults.filter((o) => !localKeys.has(getValue(o)));
9918
+ base = [...flatOptionsList, ...serverOnly];
9919
+ }
9920
+ if (!multiple && value != null) {
9921
+ const key = getValue(value);
9922
+ if (!base.some((o) => getValue(o) === key)) base = [value, ...base];
9923
+ }
9924
+ return base;
9925
+ }, [flatOptionsList, searchResults, getValue, value, multiple]);
9915
9926
  const depthMap = (0, import_react51.useMemo)(() => {
9916
9927
  const map = /* @__PURE__ */ new Map();
9917
9928
  flatItems.forEach(({ option, depth }) => map.set(getValue(option), depth));
@@ -9927,10 +9938,11 @@ function SmartSelect({
9927
9938
  }
9928
9939
  return value != null ? /* @__PURE__ */ new Set([getValue(value)]) : /* @__PURE__ */ new Set();
9929
9940
  }, [multiple, value, getValue]);
9930
- const handleInputChange = (0, import_react51.useCallback)((_, inputValue2) => {
9941
+ const handleInputChange = (0, import_react51.useCallback)((_, inputValue2, reason) => {
9931
9942
  setInputValue(inputValue2);
9932
9943
  if (!onSearchChange) return;
9933
9944
  if (debounceTimer.current) clearTimeout(debounceTimer.current);
9945
+ if (reason === "reset") return;
9934
9946
  if (!inputValue2) {
9935
9947
  onSearchChange("", 0);
9936
9948
  return;
@@ -10041,10 +10053,13 @@ function SmartSelect({
10041
10053
  return [...selected, ...filteredUnselected];
10042
10054
  }
10043
10055
  if (value != null) {
10056
+ const selectedKey = getValue(value);
10044
10057
  const selectedLabel = getOptionLabel(value);
10045
- if (inputValue2 === selectedLabel) {
10046
- const selectedKey = getValue(value);
10058
+ const inOpts = opts.some((o) => getValue(o) === selectedKey);
10059
+ const selectedFallback = inOpts ? [] : [value];
10060
+ if (!inputValue2 || inputValue2 === selectedLabel) {
10047
10061
  return [
10062
+ ...selectedFallback,
10048
10063
  ...opts.filter((o) => getValue(o) === selectedKey),
10049
10064
  ...opts.filter((o) => getValue(o) !== selectedKey)
10050
10065
  ];
package/dist/main.css CHANGED
@@ -6874,21 +6874,24 @@ pre {
6874
6874
  white-space: nowrap;
6875
6875
  color: var(--tf-text-color);
6876
6876
  z-index: 2;
6877
+ flex-shrink: 0;
6878
+ min-width: 36px;
6879
+ box-sizing: border-box;
6877
6880
  }
6878
6881
  .rf-text-field__adornment--start {
6879
- margin-left: 8px;
6880
- margin-right: -6px;
6882
+ padding-left: 10px;
6883
+ padding-right: 4px;
6881
6884
  }
6882
6885
  .rf-text-field__adornment--end {
6883
- margin-right: 8px;
6884
- margin-left: -6px;
6886
+ padding-right: 10px;
6887
+ padding-left: 4px;
6885
6888
  }
6886
6889
  .rf-text-field--standard .rf-text-field__adornment--start {
6887
- margin-left: 0;
6890
+ padding-left: 0;
6888
6891
  margin-top: 16px;
6889
6892
  }
6890
6893
  .rf-text-field--standard .rf-text-field__adornment--end {
6891
- margin-right: 0;
6894
+ padding-right: 0;
6892
6895
  margin-top: 16px;
6893
6896
  }
6894
6897
  .rf-text-field--filled .rf-text-field__adornment--start,
package/dist/main.d.cts CHANGED
@@ -537,6 +537,8 @@ interface TextFieldProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'si
537
537
  * All number variants block `e`, `E`, `+`, `-`.
538
538
  */
539
539
  numberVariant?: NumberVariant;
540
+ /** Custom step for number inputs. Overrides the default step derived from numberVariant. */
541
+ step?: number;
540
542
  /** The color of the component */
541
543
  color?: 'primary' | 'secondary' | 'error' | 'success' | 'info' | 'warning';
542
544
  /** If true, the label is displayed in an error state. */
@@ -921,8 +923,10 @@ interface ToolbarOptions {
921
923
  hideFilter?: boolean;
922
924
  /** Hide the "Manage Columns" button. */
923
925
  hideColumns?: boolean;
924
- /** Hide the "Export CSV" button. */
926
+ /** Hide the "Export CSV" button in the top toolbar. */
925
927
  hideExport?: boolean;
928
+ /** Hide the export icon button in the bottom pagination footer. */
929
+ hideBottomExport?: boolean;
926
930
  /** Hide the title text on the left side. */
927
931
  hideTitle?: boolean;
928
932
  /** Hide the record count below the title. */
@@ -1937,7 +1941,9 @@ interface PhoneFieldProps {
1937
1941
  declare const PhoneField: React__default.ForwardRefExoticComponent<PhoneFieldProps & React__default.RefAttributes<HTMLDivElement>>;
1938
1942
 
1939
1943
  interface TreeNode {
1940
- id: string | number;
1944
+ id?: string | number;
1945
+ /** MongoDB-style id — used as fallback when `id` is absent */
1946
+ _id?: string | number;
1941
1947
  label: string;
1942
1948
  name?: string;
1943
1949
  children?: TreeNode[];
@@ -1950,9 +1956,10 @@ interface TreeSelectProps {
1950
1956
  options: TreeNode[];
1951
1957
  /** Controlled value. Single mode: id or null. Multiple mode: Record<id, boolean>. */
1952
1958
  value?: TreeSelectValue;
1953
- /** Called when selection changes */
1959
+ /** Called when selection changes. In single mode, `node` is also included for convenience. */
1954
1960
  onChange?: (e: {
1955
1961
  value: TreeSelectValue;
1962
+ node?: TreeNode | null;
1956
1963
  }) => void;
1957
1964
  /** Called when a node is selected */
1958
1965
  onNodeSelect?: (e: {
package/dist/main.d.ts CHANGED
@@ -537,6 +537,8 @@ interface TextFieldProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'si
537
537
  * All number variants block `e`, `E`, `+`, `-`.
538
538
  */
539
539
  numberVariant?: NumberVariant;
540
+ /** Custom step for number inputs. Overrides the default step derived from numberVariant. */
541
+ step?: number;
540
542
  /** The color of the component */
541
543
  color?: 'primary' | 'secondary' | 'error' | 'success' | 'info' | 'warning';
542
544
  /** If true, the label is displayed in an error state. */
@@ -921,8 +923,10 @@ interface ToolbarOptions {
921
923
  hideFilter?: boolean;
922
924
  /** Hide the "Manage Columns" button. */
923
925
  hideColumns?: boolean;
924
- /** Hide the "Export CSV" button. */
926
+ /** Hide the "Export CSV" button in the top toolbar. */
925
927
  hideExport?: boolean;
928
+ /** Hide the export icon button in the bottom pagination footer. */
929
+ hideBottomExport?: boolean;
926
930
  /** Hide the title text on the left side. */
927
931
  hideTitle?: boolean;
928
932
  /** Hide the record count below the title. */
@@ -1937,7 +1941,9 @@ interface PhoneFieldProps {
1937
1941
  declare const PhoneField: React__default.ForwardRefExoticComponent<PhoneFieldProps & React__default.RefAttributes<HTMLDivElement>>;
1938
1942
 
1939
1943
  interface TreeNode {
1940
- id: string | number;
1944
+ id?: string | number;
1945
+ /** MongoDB-style id — used as fallback when `id` is absent */
1946
+ _id?: string | number;
1941
1947
  label: string;
1942
1948
  name?: string;
1943
1949
  children?: TreeNode[];
@@ -1950,9 +1956,10 @@ interface TreeSelectProps {
1950
1956
  options: TreeNode[];
1951
1957
  /** Controlled value. Single mode: id or null. Multiple mode: Record<id, boolean>. */
1952
1958
  value?: TreeSelectValue;
1953
- /** Called when selection changes */
1959
+ /** Called when selection changes. In single mode, `node` is also included for convenience. */
1954
1960
  onChange?: (e: {
1955
1961
  value: TreeSelectValue;
1962
+ node?: TreeNode | null;
1956
1963
  }) => void;
1957
1964
  /** Called when a node is selected */
1958
1965
  onNodeSelect?: (e: {
package/dist/main.js CHANGED
@@ -1687,6 +1687,7 @@ var TextField = forwardRef3(({
1687
1687
  slotProps,
1688
1688
  InputProps,
1689
1689
  numberVariant,
1690
+ step: stepProp,
1690
1691
  multiline = false,
1691
1692
  rows,
1692
1693
  maxRows,
@@ -1822,7 +1823,7 @@ var TextField = forwardRef3(({
1822
1823
  required,
1823
1824
  disabled,
1824
1825
  readOnly,
1825
- step: type === "number" && numberVariant ? STEP_BY_VARIANT[numberVariant] : void 0,
1826
+ step: type === "number" ? stepProp ?? (numberVariant ? STEP_BY_VARIANT[numberVariant] : void 0) : void 0,
1826
1827
  min: type === "number" && numberVariant ? MIN_BY_VARIANT[numberVariant] : void 0,
1827
1828
  ...slotProps?.input,
1828
1829
  ...props,
@@ -5374,7 +5375,7 @@ function DataGrid({
5374
5375
  action.icon
5375
5376
  )))));
5376
5377
  })()));
5377
- }))), filteredData.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"))), customFooter ? /* @__PURE__ */ React75.createElement("div", { className: "dg-pagination dg-pagination--custom" }, customFooter) : pagination && !tOpts.hideFooter && /* @__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(
5378
+ }))), filteredData.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"))), customFooter ? /* @__PURE__ */ React75.createElement("div", { className: "dg-pagination dg-pagination--custom" }, customFooter) : pagination && !tOpts.hideFooter && /* @__PURE__ */ React75.createElement("div", { className: "dg-pagination" }, /* @__PURE__ */ React75.createElement("div", { className: "dg-page-info" }, !tOpts.hideBottomExport && !tOpts.hideExport && /* @__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(
5378
5379
  FilterSelect,
5379
5380
  {
5380
5381
  placement: "top",
@@ -9257,21 +9258,24 @@ import React106, {
9257
9258
  } from "react";
9258
9259
  import ReactDOM10 from "react-dom";
9259
9260
  import { ChevronDown as ChevronDown2, ChevronRight as ChevronRight2, X as X3, Search as Search2, Check, Minus } from "lucide-react";
9261
+ function nodeId(node) {
9262
+ return node.id ?? node._id;
9263
+ }
9260
9264
  function collectDescendants(node) {
9261
- const ids = [String(node.id)];
9265
+ const ids = [String(nodeId(node))];
9262
9266
  node.children?.forEach((c) => ids.push(...collectDescendants(c)));
9263
9267
  return ids;
9264
9268
  }
9265
9269
  function findNodeById(nodes, id) {
9266
9270
  for (const node of nodes) {
9267
- if (String(node.id) === id) return node;
9271
+ if (String(nodeId(node)) === id) return node;
9268
9272
  const found = findNodeById(node.children ?? [], id);
9269
9273
  if (found) return found;
9270
9274
  }
9271
9275
  return null;
9272
9276
  }
9273
9277
  function getNodeState(node, selected) {
9274
- if (selected.has(String(node.id))) return "checked";
9278
+ if (selected.has(String(nodeId(node)))) return "checked";
9275
9279
  if (!node.children?.length) return "unchecked";
9276
9280
  const states = node.children.map((c) => getNodeState(c, selected));
9277
9281
  if (states.every((s2) => s2 === "checked")) return "checked";
@@ -9293,7 +9297,7 @@ function getTopLevelSelected(nodes, selected) {
9293
9297
  function getAllSelected(nodes, selected) {
9294
9298
  const result = [];
9295
9299
  for (const node of nodes) {
9296
- if (selected.has(String(node.id))) result.push(node);
9300
+ if (selected.has(String(nodeId(node)))) result.push(node);
9297
9301
  if (node.children?.length) result.push(...getAllSelected(node.children, selected));
9298
9302
  }
9299
9303
  return result;
@@ -9331,10 +9335,10 @@ function TreeNodeItem({
9331
9335
  onSelect,
9332
9336
  isFiltering
9333
9337
  }) {
9334
- const nodeId = String(node.id);
9338
+ const nid = String(nodeId(node));
9335
9339
  const hasChildren = !!node.children?.length;
9336
- const isExpanded = isFiltering || expanded.has(nodeId);
9337
- const state = allowChildSelection ? getNodeState(node, selected) : selected.has(nodeId) ? "checked" : "unchecked";
9340
+ const isExpanded = isFiltering || expanded.has(nid);
9341
+ const state = allowChildSelection ? getNodeState(node, selected) : selected.has(nid) ? "checked" : "unchecked";
9338
9342
  return /* @__PURE__ */ React106.createElement("div", { className: "rf-tsn" }, /* @__PURE__ */ React106.createElement(
9339
9343
  "div",
9340
9344
  {
@@ -9349,7 +9353,7 @@ function TreeNodeItem({
9349
9353
  className: "rf-tsn__expand",
9350
9354
  onClick: (e) => {
9351
9355
  e.stopPropagation();
9352
- onToggleExpand(nodeId);
9356
+ onToggleExpand(nid);
9353
9357
  }
9354
9358
  },
9355
9359
  isExpanded ? /* @__PURE__ */ React106.createElement(ChevronDown2, { size: 14 }) : /* @__PURE__ */ React106.createElement(ChevronRight2, { size: 14 })
@@ -9498,7 +9502,7 @@ function TreeSelect({
9498
9502
  };
9499
9503
  }, [open, computePosition3]);
9500
9504
  const handleSelect = (node) => {
9501
- const nodeId = String(node.id);
9505
+ const nid = String(nodeId(node));
9502
9506
  if (isMultiple) {
9503
9507
  const newSet = new Set(selectedSet);
9504
9508
  if (allowChildSelection) {
@@ -9512,21 +9516,21 @@ function TreeSelect({
9512
9516
  onNodeSelect?.({ node });
9513
9517
  }
9514
9518
  } else {
9515
- if (newSet.has(nodeId)) {
9516
- newSet.delete(nodeId);
9519
+ if (newSet.has(nid)) {
9520
+ newSet.delete(nid);
9517
9521
  onNodeUnselect?.({ node });
9518
9522
  } else {
9519
- newSet.add(nodeId);
9523
+ newSet.add(nid);
9520
9524
  onNodeSelect?.({ node });
9521
9525
  }
9522
9526
  }
9523
- onChange?.({ value: setToRecord(newSet) });
9527
+ onChange?.({ value: setToRecord(newSet), node });
9524
9528
  } else {
9525
- if (selectedSet.has(nodeId)) {
9526
- onChange?.({ value: null });
9529
+ if (selectedSet.has(nid)) {
9530
+ onChange?.({ value: null, node: null });
9527
9531
  onNodeUnselect?.({ node });
9528
9532
  } else {
9529
- onChange?.({ value: node.id });
9533
+ onChange?.({ value: nodeId(node), node });
9530
9534
  onNodeSelect?.({ node });
9531
9535
  closeDropdown();
9532
9536
  }
@@ -9541,7 +9545,7 @@ function TreeSelect({
9541
9545
  };
9542
9546
  const handleClear = (e) => {
9543
9547
  e.stopPropagation();
9544
- onChange?.({ value: isMultiple ? {} : null });
9548
+ onChange?.({ value: isMultiple ? {} : null, node: null });
9545
9549
  };
9546
9550
  const handleRemoveTag = (e, node) => {
9547
9551
  e.stopPropagation();
@@ -9832,11 +9836,18 @@ function SmartSelect({
9832
9836
  }, [options, getOptionChildren]);
9833
9837
  const flatOptionsList = useMemo3(() => flatItems.map((f) => f.option), [flatItems]);
9834
9838
  const displayOptions = useMemo3(() => {
9835
- if (!searchResults.length) return flatOptionsList;
9836
- const localKeys = new Set(flatOptionsList.map((o) => getValue(o)));
9837
- const serverOnly = searchResults.filter((o) => !localKeys.has(getValue(o)));
9838
- return [...flatOptionsList, ...serverOnly];
9839
- }, [flatOptionsList, searchResults, getValue]);
9839
+ let base = flatOptionsList;
9840
+ if (searchResults.length) {
9841
+ const localKeys = new Set(flatOptionsList.map((o) => getValue(o)));
9842
+ const serverOnly = searchResults.filter((o) => !localKeys.has(getValue(o)));
9843
+ base = [...flatOptionsList, ...serverOnly];
9844
+ }
9845
+ if (!multiple && value != null) {
9846
+ const key = getValue(value);
9847
+ if (!base.some((o) => getValue(o) === key)) base = [value, ...base];
9848
+ }
9849
+ return base;
9850
+ }, [flatOptionsList, searchResults, getValue, value, multiple]);
9840
9851
  const depthMap = useMemo3(() => {
9841
9852
  const map = /* @__PURE__ */ new Map();
9842
9853
  flatItems.forEach(({ option, depth }) => map.set(getValue(option), depth));
@@ -9852,10 +9863,11 @@ function SmartSelect({
9852
9863
  }
9853
9864
  return value != null ? /* @__PURE__ */ new Set([getValue(value)]) : /* @__PURE__ */ new Set();
9854
9865
  }, [multiple, value, getValue]);
9855
- const handleInputChange = useCallback11((_, inputValue2) => {
9866
+ const handleInputChange = useCallback11((_, inputValue2, reason) => {
9856
9867
  setInputValue(inputValue2);
9857
9868
  if (!onSearchChange) return;
9858
9869
  if (debounceTimer.current) clearTimeout(debounceTimer.current);
9870
+ if (reason === "reset") return;
9859
9871
  if (!inputValue2) {
9860
9872
  onSearchChange("", 0);
9861
9873
  return;
@@ -9966,10 +9978,13 @@ function SmartSelect({
9966
9978
  return [...selected, ...filteredUnselected];
9967
9979
  }
9968
9980
  if (value != null) {
9981
+ const selectedKey = getValue(value);
9969
9982
  const selectedLabel = getOptionLabel(value);
9970
- if (inputValue2 === selectedLabel) {
9971
- const selectedKey = getValue(value);
9983
+ const inOpts = opts.some((o) => getValue(o) === selectedKey);
9984
+ const selectedFallback = inOpts ? [] : [value];
9985
+ if (!inputValue2 || inputValue2 === selectedLabel) {
9972
9986
  return [
9987
+ ...selectedFallback,
9973
9988
  ...opts.filter((o) => getValue(o) === selectedKey),
9974
9989
  ...opts.filter((o) => getValue(o) !== selectedKey)
9975
9990
  ];
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rufous/ui",
3
3
  "private": false,
4
- "version": "0.3.24",
4
+ "version": "0.3.26",
5
5
  "type": "module",
6
6
  "description": "Experimental: A lightweight React UI component library (Beta)",
7
7
  "style": "./dist/main.css",