@rufous/ui 0.2.86 → 0.2.88

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
@@ -4308,6 +4308,7 @@ function DataGrid({
4308
4308
  onCellDoubleClick
4309
4309
  }) {
4310
4310
  const sxClass = useSx(sx);
4311
+ const [editingCell, setEditingCell] = (0, import_react23.useState)(null);
4311
4312
  const [columnOverrides, setColumnOverrides] = (0, import_react23.useState)({});
4312
4313
  const resolvedColumns = (0, import_react23.useMemo)(() => {
4313
4314
  return initialColumnsProp.map((col) => {
@@ -4438,8 +4439,8 @@ function DataGrid({
4438
4439
  );
4439
4440
  const evalFilter = (f) => {
4440
4441
  if (!f.value && f.operator !== "is empty" && f.operator !== "is not empty") return true;
4441
- const rawVal = String(item[f.column]);
4442
4442
  const col = resolvedColumns.find((c) => String(c.field) === f.column || String(c.key) === f.column);
4443
+ const rawVal = String(item[f.column]);
4443
4444
  if (col?.type === "number") {
4444
4445
  const itemNum = Number(rawVal);
4445
4446
  const filterNum = Number(f.value);
@@ -4465,23 +4466,30 @@ function DataGrid({
4465
4466
  }
4466
4467
  }
4467
4468
  if (col?.type === "date") {
4469
+ const raw = item[f.column];
4470
+ const resolvedDate = col.valueGetter ? col.valueGetter({ value: raw, row: item, field: f.column }) : raw;
4471
+ const isEmpty = resolvedDate == null || resolvedDate === "" || resolvedDate === "undefined" || resolvedDate === "null";
4472
+ if (f.operator === "is empty") return isEmpty;
4473
+ if (f.operator === "is not empty") return !isEmpty;
4474
+ if (isEmpty) return false;
4475
+ const itemTs = new Date(resolvedDate);
4476
+ itemTs.setHours(0, 0, 0, 0);
4477
+ const filterTs = new Date(f.value);
4478
+ filterTs.setHours(0, 0, 0, 0);
4479
+ if (isNaN(itemTs.getTime()) || isNaN(filterTs.getTime())) return false;
4468
4480
  switch (f.operator) {
4469
4481
  case "is":
4470
- return rawVal === f.value;
4482
+ return itemTs.getTime() === filterTs.getTime();
4471
4483
  case "is not":
4472
- return rawVal !== f.value;
4484
+ return itemTs.getTime() !== filterTs.getTime();
4473
4485
  case "is after":
4474
- return rawVal > f.value;
4486
+ return itemTs.getTime() > filterTs.getTime();
4475
4487
  case "is on or after":
4476
- return rawVal >= f.value;
4488
+ return itemTs.getTime() >= filterTs.getTime();
4477
4489
  case "is before":
4478
- return rawVal < f.value;
4490
+ return itemTs.getTime() < filterTs.getTime();
4479
4491
  case "is on or before":
4480
- return rawVal <= f.value;
4481
- case "is empty":
4482
- return !rawVal;
4483
- case "is not empty":
4484
- return !!rawVal;
4492
+ return itemTs.getTime() <= filterTs.getTime();
4485
4493
  default:
4486
4494
  return true;
4487
4495
  }
@@ -4694,14 +4702,46 @@ function DataGrid({
4694
4702
  "td",
4695
4703
  {
4696
4704
  key: `${item.id}-${colField}`,
4697
- className: `dg-td${col.pinned === "left" ? " pinned-left" : col.pinned === "right" ? " pinned-right" : ""} ${col.cellClassName || ""}`,
4705
+ className: `dg-td${col.pinned === "left" ? " pinned-left" : col.pinned === "right" ? " pinned-right" : ""}${col.editable ? " dg-td--editable" : ""} ${col.cellClassName || ""}`,
4698
4706
  style: { width, minWidth: width, maxWidth: width, left: leftOffset, right: rightOffset, flex: col.flex },
4699
- onDoubleClick: () => onCellDoubleClick?.({ row: item, field: colField, value: item[col.field || ""] })
4707
+ onDoubleClick: () => onCellDoubleClick?.({ row: item, field: colField, value: item[col.field || ""] }),
4708
+ onClick: col.editable ? () => {
4709
+ const field = String(col.field);
4710
+ const rawValue = item[col.field || ""];
4711
+ const value = col.valueGetter ? col.valueGetter({ value: rawValue, row: item, field }) : rawValue;
4712
+ setEditingCell({ rowId: item.id, field, value });
4713
+ } : void 0
4700
4714
  },
4701
4715
  (() => {
4702
4716
  const field = String(col.field);
4703
4717
  const rawValue = item[col.field || ""];
4704
4718
  let value = col.valueGetter ? col.valueGetter({ value: rawValue, row: item, field }) : rawValue;
4719
+ if (col.editable && editingCell?.rowId === item.id && editingCell?.field === field) {
4720
+ const inputType = col.type === "number" ? "number" : col.type === "date" ? "date" : "text";
4721
+ const commit = (finalValue) => {
4722
+ setEditingCell(null);
4723
+ col.onEnter?.({ value: finalValue, row: item, field });
4724
+ };
4725
+ return /* @__PURE__ */ import_react23.default.createElement(
4726
+ "input",
4727
+ {
4728
+ className: "dg-cell-editor",
4729
+ type: inputType,
4730
+ autoFocus: true,
4731
+ defaultValue: editingCell.value ?? "",
4732
+ onClick: (e) => e.stopPropagation(),
4733
+ onKeyDown: (e) => {
4734
+ if (e.key === "Enter") commit(e.target.value);
4735
+ if (e.key === "Escape") setEditingCell(null);
4736
+ },
4737
+ onBlur: (e) => {
4738
+ const finalValue = e.target.value;
4739
+ setEditingCell(null);
4740
+ col.onBlur?.({ value: finalValue, row: item, field });
4741
+ }
4742
+ }
4743
+ );
4744
+ }
4705
4745
  const formattedValue = col.valueFormatter ? col.valueFormatter({ value, row: item, field }) : value;
4706
4746
  if (col.renderCell) {
4707
4747
  return col.renderCell({ value, row: item, field });
@@ -4919,6 +4959,7 @@ var Select = import_react24.default.forwardRef(function Select2(props, ref) {
4919
4959
  const [focusedIdx, setFocusedIdx] = (0, import_react24.useState)(-1);
4920
4960
  const [popupStyle, setPopupStyle] = (0, import_react24.useState)({});
4921
4961
  const containerRef = (0, import_react24.useRef)(null);
4962
+ const popupRef = (0, import_react24.useRef)(null);
4922
4963
  const listRef = (0, import_react24.useRef)(null);
4923
4964
  const inputId = (0, import_react24.useRef)(`rf-sel-${Math.random().toString(36).slice(2, 9)}`).current;
4924
4965
  const sxClass = useSx(sx);
@@ -4957,7 +4998,7 @@ var Select = import_react24.default.forwardRef(function Select2(props, ref) {
4957
4998
  (0, import_react24.useEffect)(() => {
4958
4999
  if (!open) return;
4959
5000
  const handleOutside = (e) => {
4960
- if (containerRef.current && !containerRef.current.contains(e.target)) {
5001
+ if (containerRef.current && !containerRef.current.contains(e.target) && popupRef.current && !popupRef.current.contains(e.target)) {
4961
5002
  closePopup();
4962
5003
  }
4963
5004
  };
@@ -4970,14 +5011,14 @@ var Select = import_react24.default.forwardRef(function Select2(props, ref) {
4970
5011
  window.removeEventListener("resize", calcPopupStyle);
4971
5012
  };
4972
5013
  }, [open, closePopup, calcPopupStyle]);
4973
- const selectOption = (0, import_react24.useCallback)((opt) => {
5014
+ const selectOption = (0, import_react24.useCallback)((opt, event) => {
4974
5015
  if (opt.disabled) return;
4975
5016
  if (multiple) {
4976
5017
  const already = isSelected(opt.value);
4977
5018
  const next = already ? selectedValues.filter((v) => v !== opt.value) : [...selectedValues, opt.value];
4978
- onChange?.(next);
5019
+ onChange?.(event, next);
4979
5020
  } else {
4980
- onChange?.(opt.value);
5021
+ onChange?.(event, opt.value);
4981
5022
  closePopup();
4982
5023
  }
4983
5024
  }, [multiple, isSelected, selectedValues, onChange, closePopup]);
@@ -5013,7 +5054,7 @@ var Select = import_react24.default.forwardRef(function Select2(props, ref) {
5013
5054
  }
5014
5055
  if (focusedIdx >= 0) {
5015
5056
  const opt = selectableOpts[focusedIdx];
5016
- if (opt) selectOption(opt);
5057
+ if (opt) selectOption(opt, e);
5017
5058
  }
5018
5059
  } else if (e.key === "Escape") {
5019
5060
  closePopup();
@@ -5089,7 +5130,7 @@ var Select = import_react24.default.forwardRef(function Select2(props, ref) {
5089
5130
  ),
5090
5131
  helperText && /* @__PURE__ */ import_react24.default.createElement("div", { className: `rf-text-field__helper-text${error ? " rf-text-field__helper-text--error" : ""}` }, helperText),
5091
5132
  open && !disabled && import_react_dom5.default.createPortal(
5092
- /* @__PURE__ */ import_react24.default.createElement("div", { className: "rf-select__popup", role: "presentation", style: popupStyle }, /* @__PURE__ */ import_react24.default.createElement("ul", { ref: listRef, className: "rf-select__listbox", role: "listbox", "aria-multiselectable": multiple }, options.map((opt, idx) => {
5133
+ /* @__PURE__ */ import_react24.default.createElement("div", { ref: popupRef, className: "rf-select__popup", role: "presentation", style: popupStyle }, /* @__PURE__ */ import_react24.default.createElement("ul", { ref: listRef, className: "rf-select__listbox", role: "listbox", "aria-multiselectable": multiple }, options.map((opt, idx) => {
5093
5134
  const selected = isSelected(opt.value);
5094
5135
  const focused = focusedIdx === idx;
5095
5136
  return /* @__PURE__ */ import_react24.default.createElement(
@@ -5109,7 +5150,7 @@ var Select = import_react24.default.forwardRef(function Select2(props, ref) {
5109
5150
  onMouseEnter: () => setFocusedIdx(idx),
5110
5151
  onMouseLeave: () => setFocusedIdx(-1),
5111
5152
  onMouseDown: (e) => e.preventDefault(),
5112
- onClick: () => selectOption(opt)
5153
+ onClick: (e) => selectOption(opt, e)
5113
5154
  },
5114
5155
  /* @__PURE__ */ import_react24.default.createElement("span", { className: "rf-select__option-label" }, opt.label),
5115
5156
  /* @__PURE__ */ import_react24.default.createElement("span", { className: "rf-select__option-check", "aria-hidden": "true" }, /* @__PURE__ */ import_react24.default.createElement(CheckIcon2, null))
package/dist/main.css CHANGED
@@ -531,6 +531,25 @@
531
531
  white-space: nowrap;
532
532
  background: inherit;
533
533
  }
534
+ .dg-td--editable {
535
+ cursor: pointer;
536
+ }
537
+ .dg-td--editable:hover {
538
+ outline: 1px dashed var(--border-color);
539
+ outline-offset: -2px;
540
+ }
541
+ .dg-cell-editor {
542
+ width: 100%;
543
+ padding: 4px 6px;
544
+ font: inherit;
545
+ font-size: 0.875rem;
546
+ color: var(--text-color);
547
+ background: var(--surface-color);
548
+ border: 1.5px solid var(--primary-color, #f15b24);
549
+ border-radius: 4px;
550
+ outline: none;
551
+ box-sizing: border-box;
552
+ }
534
553
  .dg-td.pinned-left {
535
554
  position: sticky;
536
555
  left: 0;
package/dist/main.d.cts CHANGED
@@ -796,6 +796,16 @@ interface Column<T> {
796
796
  type?: 'string' | 'number' | 'date' | 'boolean' | 'actions' | 'status';
797
797
  statusOptions?: string[];
798
798
  editable?: boolean;
799
+ onEnter?: (params: {
800
+ value: any;
801
+ row: T;
802
+ field: string;
803
+ }) => void;
804
+ onBlur?: (params: {
805
+ value: any;
806
+ row: T;
807
+ field: string;
808
+ }) => void;
799
809
  headerClassName?: string;
800
810
  cellClassName?: string;
801
811
  hideable?: boolean;
@@ -838,7 +848,7 @@ type SelectOption = {
838
848
  interface SelectProps {
839
849
  options: SelectOption[] | string[];
840
850
  value?: string | number | null;
841
- onChange?: (value: string | number | null) => void;
851
+ onChange?: (event: React__default.SyntheticEvent, value: string | number | (string | number)[] | null) => void;
842
852
  label?: string;
843
853
  placeholder?: string;
844
854
  variant?: "outlined" | "filled" | "standard";
package/dist/main.d.ts CHANGED
@@ -796,6 +796,16 @@ interface Column<T> {
796
796
  type?: 'string' | 'number' | 'date' | 'boolean' | 'actions' | 'status';
797
797
  statusOptions?: string[];
798
798
  editable?: boolean;
799
+ onEnter?: (params: {
800
+ value: any;
801
+ row: T;
802
+ field: string;
803
+ }) => void;
804
+ onBlur?: (params: {
805
+ value: any;
806
+ row: T;
807
+ field: string;
808
+ }) => void;
799
809
  headerClassName?: string;
800
810
  cellClassName?: string;
801
811
  hideable?: boolean;
@@ -838,7 +848,7 @@ type SelectOption = {
838
848
  interface SelectProps {
839
849
  options: SelectOption[] | string[];
840
850
  value?: string | number | null;
841
- onChange?: (value: string | number | null) => void;
851
+ onChange?: (event: React__default.SyntheticEvent, value: string | number | (string | number)[] | null) => void;
842
852
  label?: string;
843
853
  placeholder?: string;
844
854
  variant?: "outlined" | "filled" | "standard";
package/dist/main.js CHANGED
@@ -4178,6 +4178,7 @@ function DataGrid({
4178
4178
  onCellDoubleClick
4179
4179
  }) {
4180
4180
  const sxClass = useSx(sx);
4181
+ const [editingCell, setEditingCell] = useState9(null);
4181
4182
  const [columnOverrides, setColumnOverrides] = useState9({});
4182
4183
  const resolvedColumns = useMemo2(() => {
4183
4184
  return initialColumnsProp.map((col) => {
@@ -4308,8 +4309,8 @@ function DataGrid({
4308
4309
  );
4309
4310
  const evalFilter = (f) => {
4310
4311
  if (!f.value && f.operator !== "is empty" && f.operator !== "is not empty") return true;
4311
- const rawVal = String(item[f.column]);
4312
4312
  const col = resolvedColumns.find((c) => String(c.field) === f.column || String(c.key) === f.column);
4313
+ const rawVal = String(item[f.column]);
4313
4314
  if (col?.type === "number") {
4314
4315
  const itemNum = Number(rawVal);
4315
4316
  const filterNum = Number(f.value);
@@ -4335,23 +4336,30 @@ function DataGrid({
4335
4336
  }
4336
4337
  }
4337
4338
  if (col?.type === "date") {
4339
+ const raw = item[f.column];
4340
+ const resolvedDate = col.valueGetter ? col.valueGetter({ value: raw, row: item, field: f.column }) : raw;
4341
+ const isEmpty = resolvedDate == null || resolvedDate === "" || resolvedDate === "undefined" || resolvedDate === "null";
4342
+ if (f.operator === "is empty") return isEmpty;
4343
+ if (f.operator === "is not empty") return !isEmpty;
4344
+ if (isEmpty) return false;
4345
+ const itemTs = new Date(resolvedDate);
4346
+ itemTs.setHours(0, 0, 0, 0);
4347
+ const filterTs = new Date(f.value);
4348
+ filterTs.setHours(0, 0, 0, 0);
4349
+ if (isNaN(itemTs.getTime()) || isNaN(filterTs.getTime())) return false;
4338
4350
  switch (f.operator) {
4339
4351
  case "is":
4340
- return rawVal === f.value;
4352
+ return itemTs.getTime() === filterTs.getTime();
4341
4353
  case "is not":
4342
- return rawVal !== f.value;
4354
+ return itemTs.getTime() !== filterTs.getTime();
4343
4355
  case "is after":
4344
- return rawVal > f.value;
4356
+ return itemTs.getTime() > filterTs.getTime();
4345
4357
  case "is on or after":
4346
- return rawVal >= f.value;
4358
+ return itemTs.getTime() >= filterTs.getTime();
4347
4359
  case "is before":
4348
- return rawVal < f.value;
4360
+ return itemTs.getTime() < filterTs.getTime();
4349
4361
  case "is on or before":
4350
- return rawVal <= f.value;
4351
- case "is empty":
4352
- return !rawVal;
4353
- case "is not empty":
4354
- return !!rawVal;
4362
+ return itemTs.getTime() <= filterTs.getTime();
4355
4363
  default:
4356
4364
  return true;
4357
4365
  }
@@ -4564,14 +4572,46 @@ function DataGrid({
4564
4572
  "td",
4565
4573
  {
4566
4574
  key: `${item.id}-${colField}`,
4567
- className: `dg-td${col.pinned === "left" ? " pinned-left" : col.pinned === "right" ? " pinned-right" : ""} ${col.cellClassName || ""}`,
4575
+ className: `dg-td${col.pinned === "left" ? " pinned-left" : col.pinned === "right" ? " pinned-right" : ""}${col.editable ? " dg-td--editable" : ""} ${col.cellClassName || ""}`,
4568
4576
  style: { width, minWidth: width, maxWidth: width, left: leftOffset, right: rightOffset, flex: col.flex },
4569
- onDoubleClick: () => onCellDoubleClick?.({ row: item, field: colField, value: item[col.field || ""] })
4577
+ onDoubleClick: () => onCellDoubleClick?.({ row: item, field: colField, value: item[col.field || ""] }),
4578
+ onClick: col.editable ? () => {
4579
+ const field = String(col.field);
4580
+ const rawValue = item[col.field || ""];
4581
+ const value = col.valueGetter ? col.valueGetter({ value: rawValue, row: item, field }) : rawValue;
4582
+ setEditingCell({ rowId: item.id, field, value });
4583
+ } : void 0
4570
4584
  },
4571
4585
  (() => {
4572
4586
  const field = String(col.field);
4573
4587
  const rawValue = item[col.field || ""];
4574
4588
  let value = col.valueGetter ? col.valueGetter({ value: rawValue, row: item, field }) : rawValue;
4589
+ if (col.editable && editingCell?.rowId === item.id && editingCell?.field === field) {
4590
+ const inputType = col.type === "number" ? "number" : col.type === "date" ? "date" : "text";
4591
+ const commit = (finalValue) => {
4592
+ setEditingCell(null);
4593
+ col.onEnter?.({ value: finalValue, row: item, field });
4594
+ };
4595
+ return /* @__PURE__ */ React75.createElement(
4596
+ "input",
4597
+ {
4598
+ className: "dg-cell-editor",
4599
+ type: inputType,
4600
+ autoFocus: true,
4601
+ defaultValue: editingCell.value ?? "",
4602
+ onClick: (e) => e.stopPropagation(),
4603
+ onKeyDown: (e) => {
4604
+ if (e.key === "Enter") commit(e.target.value);
4605
+ if (e.key === "Escape") setEditingCell(null);
4606
+ },
4607
+ onBlur: (e) => {
4608
+ const finalValue = e.target.value;
4609
+ setEditingCell(null);
4610
+ col.onBlur?.({ value: finalValue, row: item, field });
4611
+ }
4612
+ }
4613
+ );
4614
+ }
4575
4615
  const formattedValue = col.valueFormatter ? col.valueFormatter({ value, row: item, field }) : value;
4576
4616
  if (col.renderCell) {
4577
4617
  return col.renderCell({ value, row: item, field });
@@ -4794,6 +4834,7 @@ var Select = React76.forwardRef(function Select2(props, ref) {
4794
4834
  const [focusedIdx, setFocusedIdx] = useState10(-1);
4795
4835
  const [popupStyle, setPopupStyle] = useState10({});
4796
4836
  const containerRef = useRef10(null);
4837
+ const popupRef = useRef10(null);
4797
4838
  const listRef = useRef10(null);
4798
4839
  const inputId = useRef10(`rf-sel-${Math.random().toString(36).slice(2, 9)}`).current;
4799
4840
  const sxClass = useSx(sx);
@@ -4832,7 +4873,7 @@ var Select = React76.forwardRef(function Select2(props, ref) {
4832
4873
  useEffect10(() => {
4833
4874
  if (!open) return;
4834
4875
  const handleOutside = (e) => {
4835
- if (containerRef.current && !containerRef.current.contains(e.target)) {
4876
+ if (containerRef.current && !containerRef.current.contains(e.target) && popupRef.current && !popupRef.current.contains(e.target)) {
4836
4877
  closePopup();
4837
4878
  }
4838
4879
  };
@@ -4845,14 +4886,14 @@ var Select = React76.forwardRef(function Select2(props, ref) {
4845
4886
  window.removeEventListener("resize", calcPopupStyle);
4846
4887
  };
4847
4888
  }, [open, closePopup, calcPopupStyle]);
4848
- const selectOption = useCallback4((opt) => {
4889
+ const selectOption = useCallback4((opt, event) => {
4849
4890
  if (opt.disabled) return;
4850
4891
  if (multiple) {
4851
4892
  const already = isSelected(opt.value);
4852
4893
  const next = already ? selectedValues.filter((v) => v !== opt.value) : [...selectedValues, opt.value];
4853
- onChange?.(next);
4894
+ onChange?.(event, next);
4854
4895
  } else {
4855
- onChange?.(opt.value);
4896
+ onChange?.(event, opt.value);
4856
4897
  closePopup();
4857
4898
  }
4858
4899
  }, [multiple, isSelected, selectedValues, onChange, closePopup]);
@@ -4888,7 +4929,7 @@ var Select = React76.forwardRef(function Select2(props, ref) {
4888
4929
  }
4889
4930
  if (focusedIdx >= 0) {
4890
4931
  const opt = selectableOpts[focusedIdx];
4891
- if (opt) selectOption(opt);
4932
+ if (opt) selectOption(opt, e);
4892
4933
  }
4893
4934
  } else if (e.key === "Escape") {
4894
4935
  closePopup();
@@ -4964,7 +5005,7 @@ var Select = React76.forwardRef(function Select2(props, ref) {
4964
5005
  ),
4965
5006
  helperText && /* @__PURE__ */ React76.createElement("div", { className: `rf-text-field__helper-text${error ? " rf-text-field__helper-text--error" : ""}` }, helperText),
4966
5007
  open && !disabled && ReactDOM4.createPortal(
4967
- /* @__PURE__ */ React76.createElement("div", { className: "rf-select__popup", role: "presentation", style: popupStyle }, /* @__PURE__ */ React76.createElement("ul", { ref: listRef, className: "rf-select__listbox", role: "listbox", "aria-multiselectable": multiple }, options.map((opt, idx) => {
5008
+ /* @__PURE__ */ React76.createElement("div", { ref: popupRef, className: "rf-select__popup", role: "presentation", style: popupStyle }, /* @__PURE__ */ React76.createElement("ul", { ref: listRef, className: "rf-select__listbox", role: "listbox", "aria-multiselectable": multiple }, options.map((opt, idx) => {
4968
5009
  const selected = isSelected(opt.value);
4969
5010
  const focused = focusedIdx === idx;
4970
5011
  return /* @__PURE__ */ React76.createElement(
@@ -4984,7 +5025,7 @@ var Select = React76.forwardRef(function Select2(props, ref) {
4984
5025
  onMouseEnter: () => setFocusedIdx(idx),
4985
5026
  onMouseLeave: () => setFocusedIdx(-1),
4986
5027
  onMouseDown: (e) => e.preventDefault(),
4987
- onClick: () => selectOption(opt)
5028
+ onClick: (e) => selectOption(opt, e)
4988
5029
  },
4989
5030
  /* @__PURE__ */ React76.createElement("span", { className: "rf-select__option-label" }, opt.label),
4990
5031
  /* @__PURE__ */ React76.createElement("span", { className: "rf-select__option-check", "aria-hidden": "true" }, /* @__PURE__ */ React76.createElement(CheckIcon2, null))
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rufous/ui",
3
3
  "private": false,
4
- "version": "0.2.86",
4
+ "version": "0.2.88",
5
5
  "type": "module",
6
6
  "description": "Experimental: A lightweight React UI component library (Beta)",
7
7
  "style": "./dist/main.css",