@rufous/ui 0.3.61 → 0.3.64

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
@@ -2424,6 +2424,7 @@ var BaseDialog = ({
2424
2424
  showCloseButton = true,
2425
2425
  buttonAlign = "flex-end",
2426
2426
  showCancelButton = true,
2427
+ showConfirmButton = true,
2427
2428
  formatTitle = true,
2428
2429
  fullWidth = false,
2429
2430
  className,
@@ -2500,7 +2501,7 @@ var BaseDialog = ({
2500
2501
  type: "button"
2501
2502
  },
2502
2503
  cancelText
2503
- ), form ? /* @__PURE__ */ React140.createElement(
2504
+ ), showConfirmButton && (form ? /* @__PURE__ */ React140.createElement(
2504
2505
  "button",
2505
2506
  {
2506
2507
  className: "btn-confirm",
@@ -2532,7 +2533,7 @@ var BaseDialog = ({
2532
2533
  },
2533
2534
  /* @__PURE__ */ React140.createElement("span", { style: { visibility: isButtonLoading ? "hidden" : "visible" } }, confirmText),
2534
2535
  isButtonLoading && /* @__PURE__ */ React140.createElement("span", { style: { position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center" } }, /* @__PURE__ */ React140.createElement(circularProgress_default, { size: 18, color: "#ffffff80" }))
2535
- ));
2536
+ )));
2536
2537
  const containerClass = ["dialog-container", size ? `size-${size}` : "", sxClass, className].filter(Boolean).join(" ");
2537
2538
  const containerStyle = { minWidth, minHeight };
2538
2539
  const dialogInner = /* @__PURE__ */ React140.createElement(React140.Fragment, null, !hideHeader && /* @__PURE__ */ React140.createElement(React140.Fragment, null, customHeader ?? /* @__PURE__ */ React140.createElement("div", { className: "dialog-title" }, /* @__PURE__ */ React140.createElement("h2", null, formatTitle ? title?.charAt(0).toUpperCase() + title?.slice(1) : title), showCloseButton && /* @__PURE__ */ React140.createElement("button", { className: "btn-close", type: "button", onClick: onClose }, /* @__PURE__ */ React140.createElement(
@@ -2761,28 +2762,55 @@ var TextField = (0, import_react11.forwardRef)(({
2761
2762
  className
2762
2763
  ].filter(Boolean).join(" ");
2763
2764
  const internalRef = import_react11.default.useRef(null);
2765
+ const numberMin = type === "number" ? slotProps?.input?.min ?? props.min : void 0;
2766
+ const numberMax = type === "number" ? slotProps?.input?.max ?? props.max : void 0;
2767
+ const setNativeValue = (val) => {
2768
+ if (!internalRef.current) return;
2769
+ const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value")?.set;
2770
+ setter?.call(internalRef.current, val);
2771
+ };
2772
+ const emitChange = (e, numericValue) => {
2773
+ const syntheticEvent = Object.assign({}, e, {
2774
+ target: Object.assign({}, e.target, { value: numericValue }),
2775
+ currentTarget: Object.assign({}, e.currentTarget, { value: numericValue })
2776
+ });
2777
+ onChange?.(syntheticEvent);
2778
+ };
2764
2779
  const handleChange = (e) => {
2765
2780
  if (type === "number") {
2766
- const raw = e.target.value;
2767
- const inputMax = slotProps?.input?.max ?? props.max;
2781
+ let raw = e.target.value;
2768
2782
  const inputMaxLength = slotProps?.input?.maxLength ?? props.maxLength;
2769
2783
  if (inputMaxLength != null) {
2770
2784
  const digits = raw.replace(/[^0-9]/g, "");
2771
2785
  if (digits.length > Number(inputMaxLength)) return;
2772
2786
  }
2773
- if (inputMax != null && raw !== "") {
2774
- if (Number(raw) > Number(inputMax)) return;
2787
+ if (numberMax != null && raw !== "" && Number(raw) > Number(numberMax)) {
2788
+ raw = String(numberMax);
2789
+ setNativeValue(raw);
2775
2790
  }
2776
- const numericValue = raw === "" ? "" : Number(raw);
2777
- const syntheticEvent = Object.assign({}, e, {
2778
- target: Object.assign({}, e.target, { value: numericValue }),
2779
- currentTarget: Object.assign({}, e.currentTarget, { value: numericValue })
2780
- });
2781
- onChange?.(syntheticEvent);
2791
+ emitChange(e, raw === "" ? "" : Number(raw));
2782
2792
  return;
2783
2793
  }
2784
2794
  onChange?.(e);
2785
2795
  };
2796
+ const handleBlur = (e) => {
2797
+ if (type === "number") {
2798
+ const raw = e.target.value;
2799
+ if (raw !== "") {
2800
+ const n = Number(raw);
2801
+ if (!isNaN(n)) {
2802
+ let clamped = n;
2803
+ if (numberMin != null && clamped < Number(numberMin)) clamped = Number(numberMin);
2804
+ if (numberMax != null && clamped > Number(numberMax)) clamped = Number(numberMax);
2805
+ if (clamped !== n) {
2806
+ setNativeValue(String(clamped));
2807
+ emitChange(e, clamped);
2808
+ }
2809
+ }
2810
+ }
2811
+ }
2812
+ onBlur?.(e);
2813
+ };
2786
2814
  const triggerChange = () => {
2787
2815
  if (internalRef.current) {
2788
2816
  const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value")?.set;
@@ -2797,9 +2825,10 @@ var TextField = (0, import_react11.forwardRef)(({
2797
2825
  const stepBy = (delta) => {
2798
2826
  if (!internalRef.current) return;
2799
2827
  const step = Number(stepProp ?? (numberVariant ? STEP_BY_VARIANT[numberVariant] : 1));
2800
- const newVal = (parseFloat(internalRef.current.value) || 0) + delta * step;
2801
- const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value")?.set;
2802
- nativeInputValueSetter?.call(internalRef.current, String(newVal));
2828
+ let newVal = (parseFloat(internalRef.current.value) || 0) + delta * step;
2829
+ if (numberMax != null && newVal > Number(numberMax)) newVal = Number(numberMax);
2830
+ if (numberMin != null && newVal < Number(numberMin)) newVal = Number(numberMin);
2831
+ setNativeValue(String(newVal));
2803
2832
  triggerChange();
2804
2833
  };
2805
2834
  const handleIncrement = (e) => {
@@ -2871,7 +2900,8 @@ var TextField = (0, import_react11.forwardRef)(({
2871
2900
  readOnly,
2872
2901
  ...slotProps?.input,
2873
2902
  ...props,
2874
- onChange: handleChange
2903
+ onChange: handleChange,
2904
+ onBlur: handleBlur
2875
2905
  }
2876
2906
  ), InputProps?.endAdornment && /* @__PURE__ */ import_react11.default.createElement("div", { className: "rf-text-field__adornment rf-text-field__adornment--end" }, InputProps.endAdornment), !isTextarea && type === "number" && !disabled && !readOnly && /* @__PURE__ */ import_react11.default.createElement("div", { className: "rf-text-field__number-controls" }, /* @__PURE__ */ import_react11.default.createElement("button", { type: "button", tabIndex: -1, onClick: handleIncrement, className: "rf-text-field__number-btn" }, /* @__PURE__ */ import_react11.default.createElement("svg", { width: "8", height: "5", viewBox: "0 0 8 5", fill: "currentColor" }, /* @__PURE__ */ import_react11.default.createElement("path", { d: "M4 0L8 5H0L4 0Z" }))), /* @__PURE__ */ import_react11.default.createElement("button", { type: "button", tabIndex: -1, onClick: handleDecrement, className: "rf-text-field__number-btn", style: { marginTop: 2 } }, /* @__PURE__ */ import_react11.default.createElement("svg", { width: "8", height: "5", viewBox: "0 0 8 5", fill: "currentColor" }, /* @__PURE__ */ import_react11.default.createElement("path", { d: "M4 5L0 0H8L4 5Z" })))), hasLabel && /* @__PURE__ */ import_react11.default.createElement("label", { htmlFor: inputId, className: "rf-text-field__label" }, label, " ", required && /* @__PURE__ */ import_react11.default.createElement("span", { className: "rf-text-field__asterisk" }, "*")), variant === "outlined" && /* @__PURE__ */ import_react11.default.createElement("fieldset", { className: "rf-text-field__notch" }, hasLabel ? /* @__PURE__ */ import_react11.default.createElement("legend", { className: "rf-text-field__legend" }, /* @__PURE__ */ import_react11.default.createElement("span", null, label, " ", required ? "*" : "")) : /* @__PURE__ */ import_react11.default.createElement("legend", { className: "rf-text-field__legend--empty" }))), helperText && /* @__PURE__ */ import_react11.default.createElement("div", { className: "rf-text-field__helper-text" }, helperText));
2877
2907
  });
@@ -4713,29 +4743,46 @@ var DateField = ({
4713
4743
  if (!rect) return {};
4714
4744
  const PICKER_H = 420;
4715
4745
  const GAP2 = 6;
4716
- const spaceBelow = window.innerHeight - rect.bottom - GAP2;
4746
+ const MARGIN = 8;
4747
+ const vw = window.innerWidth;
4748
+ const vh = window.innerHeight;
4749
+ const naturalW = isSideVariant ? 470 : 320;
4750
+ const avail = vw - MARGIN * 2;
4751
+ const scale = naturalW > avail ? avail / naturalW : 1;
4752
+ const scaledW = naturalW * scale;
4753
+ let left = rect.left;
4754
+ const maxLeft = vw - scaledW - MARGIN;
4755
+ if (left > maxLeft) left = maxLeft;
4756
+ if (left < MARGIN) left = MARGIN;
4757
+ const spaceBelow = vh - rect.bottom - GAP2;
4717
4758
  const spaceAbove = rect.top - GAP2;
4718
- const useDropUp = spaceBelow < PICKER_H && spaceAbove > spaceBelow;
4759
+ const useDropUp = spaceBelow < PICKER_H * scale && spaceAbove > spaceBelow;
4760
+ const common = {
4761
+ position: "fixed",
4762
+ left,
4763
+ width: naturalW,
4764
+ transform: scale === 1 ? void 0 : `scale(${scale})`,
4765
+ overflowY: "auto",
4766
+ zIndex: 99999
4767
+ };
4719
4768
  if (useDropUp) {
4720
4769
  return {
4721
- position: "fixed",
4722
- left: rect.left,
4770
+ ...common,
4723
4771
  // bottom anchors picker's bottom edge exactly to field top — no gap regardless of actual height
4724
- bottom: window.innerHeight - rect.top + GAP2,
4725
- // prevent going above viewport
4726
- maxHeight: rect.top - GAP2 - 4,
4727
- overflowY: "auto",
4728
- zIndex: 99999,
4729
- animationName: "rf-date-picker-appear-up",
4772
+ bottom: vh - rect.top + GAP2,
4773
+ // unscaled max-height — visual height after scaling fits the gap above
4774
+ maxHeight: (rect.top - GAP2 - MARGIN) / scale,
4775
+ // skip the scale-animating keyframes when we apply a static scale (they'd flash full-size)
4776
+ animationName: scale === 1 ? "rf-date-picker-appear-up" : "none",
4730
4777
  transformOrigin: "bottom left"
4731
4778
  };
4732
4779
  }
4733
4780
  return {
4734
- position: "fixed",
4735
- left: rect.left,
4781
+ ...common,
4736
4782
  top: rect.bottom + GAP2,
4737
- zIndex: 99999,
4738
- animationName: "rf-date-picker-appear",
4783
+ // unscaled max-height — visual height after scaling fits below the field
4784
+ maxHeight: (vh - rect.bottom - GAP2 - MARGIN) / scale,
4785
+ animationName: scale === 1 ? "rf-date-picker-appear" : "none",
4739
4786
  transformOrigin: "top left"
4740
4787
  };
4741
4788
  })(),
@@ -5111,6 +5158,43 @@ var DateRangeField = ({
5111
5158
  const [leftViewMonth, setLeftViewMonth] = (0, import_react16.useState)(() => today2.getMonth());
5112
5159
  const containerRef = (0, import_react16.useRef)(null);
5113
5160
  const inputId = (0, import_react16.useRef)(`rf-dr-${Math.random().toString(36).substr(2, 9)}`).current;
5161
+ const [mobileStyle, setMobileStyle] = (0, import_react16.useState)({});
5162
+ (0, import_react16.useEffect)(() => {
5163
+ if (!open) {
5164
+ setMobileStyle({});
5165
+ return;
5166
+ }
5167
+ const compute = () => {
5168
+ const vw = window.innerWidth;
5169
+ const vh = window.innerHeight;
5170
+ const MARGIN = 8;
5171
+ const GAP2 = 6;
5172
+ const naturalW = pickerType === "panel" ? 520 : 640;
5173
+ const avail = vw - MARGIN * 2;
5174
+ if (naturalW <= avail) {
5175
+ setMobileStyle({});
5176
+ return;
5177
+ }
5178
+ const scale = avail / naturalW;
5179
+ const rect = containerRef.current?.getBoundingClientRect();
5180
+ const top = rect ? rect.bottom + GAP2 : 60;
5181
+ setMobileStyle({
5182
+ position: "fixed",
5183
+ top,
5184
+ left: "50%",
5185
+ transform: `translateX(-50%) scale(${scale})`,
5186
+ transformOrigin: "top center",
5187
+ width: naturalW,
5188
+ maxHeight: (vh - top - MARGIN) / scale,
5189
+ overflowY: "auto",
5190
+ animation: "none",
5191
+ zIndex: 99999
5192
+ });
5193
+ };
5194
+ compute();
5195
+ window.addEventListener("resize", compute);
5196
+ return () => window.removeEventListener("resize", compute);
5197
+ }, [open, pickerType]);
5114
5198
  (0, import_react16.useEffect)(() => {
5115
5199
  const s2 = value?.start ? isoToDate2(value.start) : null;
5116
5200
  const e = value?.end ? isoToDate2(value.end) : null;
@@ -5308,7 +5392,7 @@ var DateRangeField = ({
5308
5392
  variant === "outlined" && /* @__PURE__ */ import_react16.default.createElement("fieldset", { className: "rf-text-field__notch" }, label ? /* @__PURE__ */ import_react16.default.createElement("legend", { className: "rf-text-field__legend" }, /* @__PURE__ */ import_react16.default.createElement("span", null, label, required ? " *" : "")) : /* @__PURE__ */ import_react16.default.createElement("legend", { className: "rf-text-field__legend--empty" }))
5309
5393
  ), open && !disabled && (pickerType === "panel" ? (
5310
5394
  /* ── Panel Mode ── */
5311
- /* @__PURE__ */ import_react16.default.createElement("div", { className: "rf-dr-picker rf-dr-picker--panel", onMouseDown: (e) => e.preventDefault() }, /* @__PURE__ */ import_react16.default.createElement("div", { className: "rf-dr-picker__presets" }, PRESETS.map((p, i) => /* @__PURE__ */ import_react16.default.createElement(import_react16.default.Fragment, { key: p.id }, i > 0 && /* @__PURE__ */ import_react16.default.createElement("div", { className: "rf-dr-picker__preset-sep" }), /* @__PURE__ */ import_react16.default.createElement(
5395
+ /* @__PURE__ */ import_react16.default.createElement("div", { className: "rf-dr-picker rf-dr-picker--panel", style: mobileStyle, onMouseDown: (e) => e.preventDefault() }, /* @__PURE__ */ import_react16.default.createElement("div", { className: "rf-dr-picker__presets" }, PRESETS.map((p, i) => /* @__PURE__ */ import_react16.default.createElement(import_react16.default.Fragment, { key: p.id }, i > 0 && /* @__PURE__ */ import_react16.default.createElement("div", { className: "rf-dr-picker__preset-sep" }), /* @__PURE__ */ import_react16.default.createElement(
5312
5396
  "button",
5313
5397
  {
5314
5398
  type: "button",
@@ -5428,7 +5512,7 @@ var DateRangeField = ({
5428
5512
  ))), /* @__PURE__ */ import_react16.default.createElement("div", { style: { flex: 1 } }), /* @__PURE__ */ import_react16.default.createElement("div", { className: "rf-dr-picker__footer" }, /* @__PURE__ */ import_react16.default.createElement("button", { type: "button", className: "rf-dr-picker__close-btn", onClick: handleClose }, "CLOSE"), /* @__PURE__ */ import_react16.default.createElement("button", { type: "button", className: "rf-dr-picker__apply-btn", onClick: handleApply }, "APPLY"))))
5429
5513
  ) : (
5430
5514
  /* ── Calendar Mode ── */
5431
- /* @__PURE__ */ import_react16.default.createElement("div", { className: "rf-dr-picker rf-dr-picker--calendar", onMouseDown: (e) => e.preventDefault() }, /* @__PURE__ */ import_react16.default.createElement("div", { className: "rf-dr-picker__calendars" }, /* @__PURE__ */ import_react16.default.createElement(
5515
+ /* @__PURE__ */ import_react16.default.createElement("div", { className: "rf-dr-picker rf-dr-picker--calendar", style: mobileStyle, onMouseDown: (e) => e.preventDefault() }, /* @__PURE__ */ import_react16.default.createElement("div", { className: "rf-dr-picker__calendars" }, /* @__PURE__ */ import_react16.default.createElement(
5432
5516
  RangeCalendarBody,
5433
5517
  {
5434
5518
  viewYear: leftViewYear,
@@ -5831,6 +5915,7 @@ function DataGrid({
5831
5915
  const menuRef = (0, import_react17.useRef)(null);
5832
5916
  const [showManageColumns, setShowManageColumns] = (0, import_react17.useState)(false);
5833
5917
  const [showAdvancedFilter, setShowAdvancedFilter] = (0, import_react17.useState)(false);
5918
+ const [mobileToolbarExpanded, setMobileToolbarExpanded] = (0, import_react17.useState)(false);
5834
5919
  const [focusFilterIdx, setFocusFilterIdx] = (0, import_react17.useState)(-1);
5835
5920
  const filterableColumnsProp = initialColumnsProp.filter((c) => c.filterable !== false);
5836
5921
  const initialFilterCol = String(filterableColumnsProp[0]?.field || filterableColumnsProp[0]?.key || "");
@@ -6320,14 +6405,24 @@ function DataGrid({
6320
6405
  onClick: () => setShowAdvancedFilter(true)
6321
6406
  },
6322
6407
  /* @__PURE__ */ import_react17.default.createElement(Funnel, { size: 16 })
6323
- )), showColumnsBtn && /* @__PURE__ */ import_react17.default.createElement(Tooltip, { title: "Manage Columns", placement: "top" }, /* @__PURE__ */ import_react17.default.createElement(
6408
+ )), /* @__PURE__ */ import_react17.default.createElement("div", { className: `dg-actions-overflow${mobileToolbarExpanded ? " dg-actions-overflow--expanded" : ""}` }, showColumnsBtn && /* @__PURE__ */ import_react17.default.createElement(Tooltip, { title: "Manage Columns", placement: "top" }, /* @__PURE__ */ import_react17.default.createElement(
6324
6409
  "button",
6325
6410
  {
6326
6411
  className: "dg-icon-btn",
6327
6412
  onClick: () => setShowManageColumns(true)
6328
6413
  },
6329
6414
  /* @__PURE__ */ import_react17.default.createElement(Columns2, { size: 16 })
6330
- )), showExportBtn && /* @__PURE__ */ import_react17.default.createElement("button", { className: "dg-action-btn", onClick: handleExport, disabled: loading }, /* @__PURE__ */ import_react17.default.createElement(Download, { size: 14 }), " Export CSV"), slotAt("after-actions"));
6415
+ )), showExportBtn && /* @__PURE__ */ import_react17.default.createElement("button", { className: "dg-action-btn", onClick: handleExport, disabled: loading }, /* @__PURE__ */ import_react17.default.createElement(Download, { size: 14 }), " Export CSV"), slotAt("after-actions")), (showColumnsBtn || showExportBtn || slots.some((s2) => s2.position === "after-actions" || !s2.position)) && /* @__PURE__ */ import_react17.default.createElement(
6416
+ "button",
6417
+ {
6418
+ type: "button",
6419
+ className: `dg-toolbar-toggle${mobileToolbarExpanded ? " dg-toolbar-toggle--expanded" : ""}`,
6420
+ onClick: () => setMobileToolbarExpanded((v) => !v),
6421
+ "aria-label": mobileToolbarExpanded ? "Hide toolbar options" : "Show all toolbar options",
6422
+ "aria-expanded": mobileToolbarExpanded
6423
+ },
6424
+ /* @__PURE__ */ import_react17.default.createElement(ChevronDown, { size: 18 })
6425
+ ));
6331
6426
  })())), !tOpts.hideHeader && /* @__PURE__ */ import_react17.default.createElement("div", { className: `dg-toolbar ${alignClass(toolbarContent?.align)}` }, toolbarContent?.content || ""), isGroupingActive && /* @__PURE__ */ import_react17.default.createElement("div", { className: "dg-grouping-bar" }, /* @__PURE__ */ import_react17.default.createElement("span", { className: "dg-grouping-bar-label" }, "Grouped by"), activeGroupingModel.map((gField) => {
6332
6427
  const col = resolvedColumns.find((c) => String(c.field) === gField || String(c.key) === gField);
6333
6428
  return /* @__PURE__ */ import_react17.default.createElement("div", { key: gField, className: "dg-group-chip" }, /* @__PURE__ */ import_react17.default.createElement(Layers, { size: 11 }), /* @__PURE__ */ import_react17.default.createElement("span", null, col?.header ?? col?.headerName ?? gField), !disableRowGrouping && /* @__PURE__ */ import_react17.default.createElement(
package/dist/main.css CHANGED
@@ -97,6 +97,7 @@
97
97
  flex-direction: column;
98
98
  box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.15), 0 10px 10px -5px rgba(0, 0, 0, 0.08);
99
99
  width: 685px !important;
100
+ max-width: 95vw;
100
101
  transition: background-color 0.3s, color 0.3s;
101
102
  }
102
103
  .dialog-container.size-sm {
@@ -495,6 +496,57 @@
495
496
  color: var(--primary-color);
496
497
  background: var(--hover-color);
497
498
  }
499
+ .dg-actions-overflow {
500
+ display: contents;
501
+ }
502
+ .dg-toolbar-toggle {
503
+ display: none;
504
+ align-items: center;
505
+ justify-content: center;
506
+ width: 36px;
507
+ height: 36px;
508
+ padding: 0;
509
+ border: 1px solid var(--border-color);
510
+ border-radius: 8px;
511
+ background: var(--surface-color);
512
+ color: var(--text-secondary);
513
+ cursor: pointer;
514
+ flex-shrink: 0;
515
+ outline: none;
516
+ transition:
517
+ border-color 0.2s,
518
+ color 0.2s,
519
+ background-color 0.2s;
520
+ }
521
+ .dg-toolbar-toggle:hover {
522
+ border-color: var(--primary-color);
523
+ color: var(--primary-color);
524
+ background: var(--hover-color);
525
+ }
526
+ .dg-toolbar-toggle > svg {
527
+ transition: transform 0.2s ease;
528
+ }
529
+ .dg-toolbar-toggle--expanded > svg {
530
+ transform: rotate(180deg);
531
+ }
532
+ @media (max-width: 600px) {
533
+ .dg-toolbar-toggle {
534
+ display: inline-flex;
535
+ margin-left: auto;
536
+ }
537
+ .dg-actions-overflow {
538
+ display: none;
539
+ }
540
+ .dg-actions-overflow--expanded {
541
+ display: flex;
542
+ align-items: center;
543
+ gap: 10px;
544
+ flex-wrap: wrap;
545
+ flex-basis: 100%;
546
+ order: 99;
547
+ margin-top: 4px;
548
+ }
549
+ }
498
550
  .dg-table-wrap {
499
551
  border-top: 2px solid var(--border-color);
500
552
  border-bottom: 1px solid var(--border-color);
package/dist/main.d.cts CHANGED
@@ -907,6 +907,8 @@ interface BaseDialogProps {
907
907
  showCloseButton?: boolean;
908
908
  buttonAlign?: "flex-start" | "flex-end" | "center";
909
909
  showCancelButton?: boolean;
910
+ /** Show/hide the confirm (or submit) button. Defaults to true. */
911
+ showConfirmButton?: boolean;
910
912
  formatTitle?: boolean;
911
913
  fullWidth?: boolean;
912
914
  className?: string;
package/dist/main.d.ts CHANGED
@@ -907,6 +907,8 @@ interface BaseDialogProps {
907
907
  showCloseButton?: boolean;
908
908
  buttonAlign?: "flex-start" | "flex-end" | "center";
909
909
  showCancelButton?: boolean;
910
+ /** Show/hide the confirm (or submit) button. Defaults to true. */
911
+ showConfirmButton?: boolean;
910
912
  formatTitle?: boolean;
911
913
  fullWidth?: boolean;
912
914
  className?: string;
package/dist/main.js CHANGED
@@ -2122,6 +2122,7 @@ var BaseDialog = ({
2122
2122
  showCloseButton = true,
2123
2123
  buttonAlign = "flex-end",
2124
2124
  showCancelButton = true,
2125
+ showConfirmButton = true,
2125
2126
  formatTitle = true,
2126
2127
  fullWidth = false,
2127
2128
  className,
@@ -2198,7 +2199,7 @@ var BaseDialog = ({
2198
2199
  type: "button"
2199
2200
  },
2200
2201
  cancelText
2201
- ), form ? /* @__PURE__ */ React140.createElement(
2202
+ ), showConfirmButton && (form ? /* @__PURE__ */ React140.createElement(
2202
2203
  "button",
2203
2204
  {
2204
2205
  className: "btn-confirm",
@@ -2230,7 +2231,7 @@ var BaseDialog = ({
2230
2231
  },
2231
2232
  /* @__PURE__ */ React140.createElement("span", { style: { visibility: isButtonLoading ? "hidden" : "visible" } }, confirmText),
2232
2233
  isButtonLoading && /* @__PURE__ */ React140.createElement("span", { style: { position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center" } }, /* @__PURE__ */ React140.createElement(circularProgress_default, { size: 18, color: "#ffffff80" }))
2233
- ));
2234
+ )));
2234
2235
  const containerClass = ["dialog-container", size ? `size-${size}` : "", sxClass, className].filter(Boolean).join(" ");
2235
2236
  const containerStyle = { minWidth, minHeight };
2236
2237
  const dialogInner = /* @__PURE__ */ React140.createElement(React140.Fragment, null, !hideHeader && /* @__PURE__ */ React140.createElement(React140.Fragment, null, customHeader ?? /* @__PURE__ */ React140.createElement("div", { className: "dialog-title" }, /* @__PURE__ */ React140.createElement("h2", null, formatTitle ? title?.charAt(0).toUpperCase() + title?.slice(1) : title), showCloseButton && /* @__PURE__ */ React140.createElement("button", { className: "btn-close", type: "button", onClick: onClose }, /* @__PURE__ */ React140.createElement(
@@ -2467,28 +2468,55 @@ var TextField = forwardRef6(({
2467
2468
  className
2468
2469
  ].filter(Boolean).join(" ");
2469
2470
  const internalRef = React143.useRef(null);
2471
+ const numberMin = type === "number" ? slotProps?.input?.min ?? props.min : void 0;
2472
+ const numberMax = type === "number" ? slotProps?.input?.max ?? props.max : void 0;
2473
+ const setNativeValue = (val) => {
2474
+ if (!internalRef.current) return;
2475
+ const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value")?.set;
2476
+ setter?.call(internalRef.current, val);
2477
+ };
2478
+ const emitChange = (e, numericValue) => {
2479
+ const syntheticEvent = Object.assign({}, e, {
2480
+ target: Object.assign({}, e.target, { value: numericValue }),
2481
+ currentTarget: Object.assign({}, e.currentTarget, { value: numericValue })
2482
+ });
2483
+ onChange?.(syntheticEvent);
2484
+ };
2470
2485
  const handleChange = (e) => {
2471
2486
  if (type === "number") {
2472
- const raw = e.target.value;
2473
- const inputMax = slotProps?.input?.max ?? props.max;
2487
+ let raw = e.target.value;
2474
2488
  const inputMaxLength = slotProps?.input?.maxLength ?? props.maxLength;
2475
2489
  if (inputMaxLength != null) {
2476
2490
  const digits = raw.replace(/[^0-9]/g, "");
2477
2491
  if (digits.length > Number(inputMaxLength)) return;
2478
2492
  }
2479
- if (inputMax != null && raw !== "") {
2480
- if (Number(raw) > Number(inputMax)) return;
2493
+ if (numberMax != null && raw !== "" && Number(raw) > Number(numberMax)) {
2494
+ raw = String(numberMax);
2495
+ setNativeValue(raw);
2481
2496
  }
2482
- const numericValue = raw === "" ? "" : Number(raw);
2483
- const syntheticEvent = Object.assign({}, e, {
2484
- target: Object.assign({}, e.target, { value: numericValue }),
2485
- currentTarget: Object.assign({}, e.currentTarget, { value: numericValue })
2486
- });
2487
- onChange?.(syntheticEvent);
2497
+ emitChange(e, raw === "" ? "" : Number(raw));
2488
2498
  return;
2489
2499
  }
2490
2500
  onChange?.(e);
2491
2501
  };
2502
+ const handleBlur = (e) => {
2503
+ if (type === "number") {
2504
+ const raw = e.target.value;
2505
+ if (raw !== "") {
2506
+ const n = Number(raw);
2507
+ if (!isNaN(n)) {
2508
+ let clamped = n;
2509
+ if (numberMin != null && clamped < Number(numberMin)) clamped = Number(numberMin);
2510
+ if (numberMax != null && clamped > Number(numberMax)) clamped = Number(numberMax);
2511
+ if (clamped !== n) {
2512
+ setNativeValue(String(clamped));
2513
+ emitChange(e, clamped);
2514
+ }
2515
+ }
2516
+ }
2517
+ }
2518
+ onBlur?.(e);
2519
+ };
2492
2520
  const triggerChange = () => {
2493
2521
  if (internalRef.current) {
2494
2522
  const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value")?.set;
@@ -2503,9 +2531,10 @@ var TextField = forwardRef6(({
2503
2531
  const stepBy = (delta) => {
2504
2532
  if (!internalRef.current) return;
2505
2533
  const step = Number(stepProp ?? (numberVariant ? STEP_BY_VARIANT[numberVariant] : 1));
2506
- const newVal = (parseFloat(internalRef.current.value) || 0) + delta * step;
2507
- const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value")?.set;
2508
- nativeInputValueSetter?.call(internalRef.current, String(newVal));
2534
+ let newVal = (parseFloat(internalRef.current.value) || 0) + delta * step;
2535
+ if (numberMax != null && newVal > Number(numberMax)) newVal = Number(numberMax);
2536
+ if (numberMin != null && newVal < Number(numberMin)) newVal = Number(numberMin);
2537
+ setNativeValue(String(newVal));
2509
2538
  triggerChange();
2510
2539
  };
2511
2540
  const handleIncrement = (e) => {
@@ -2577,7 +2606,8 @@ var TextField = forwardRef6(({
2577
2606
  readOnly,
2578
2607
  ...slotProps?.input,
2579
2608
  ...props,
2580
- onChange: handleChange
2609
+ onChange: handleChange,
2610
+ onBlur: handleBlur
2581
2611
  }
2582
2612
  ), InputProps?.endAdornment && /* @__PURE__ */ React143.createElement("div", { className: "rf-text-field__adornment rf-text-field__adornment--end" }, InputProps.endAdornment), !isTextarea && type === "number" && !disabled && !readOnly && /* @__PURE__ */ React143.createElement("div", { className: "rf-text-field__number-controls" }, /* @__PURE__ */ React143.createElement("button", { type: "button", tabIndex: -1, onClick: handleIncrement, className: "rf-text-field__number-btn" }, /* @__PURE__ */ React143.createElement("svg", { width: "8", height: "5", viewBox: "0 0 8 5", fill: "currentColor" }, /* @__PURE__ */ React143.createElement("path", { d: "M4 0L8 5H0L4 0Z" }))), /* @__PURE__ */ React143.createElement("button", { type: "button", tabIndex: -1, onClick: handleDecrement, className: "rf-text-field__number-btn", style: { marginTop: 2 } }, /* @__PURE__ */ React143.createElement("svg", { width: "8", height: "5", viewBox: "0 0 8 5", fill: "currentColor" }, /* @__PURE__ */ React143.createElement("path", { d: "M4 5L0 0H8L4 5Z" })))), hasLabel && /* @__PURE__ */ React143.createElement("label", { htmlFor: inputId, className: "rf-text-field__label" }, label, " ", required && /* @__PURE__ */ React143.createElement("span", { className: "rf-text-field__asterisk" }, "*")), variant === "outlined" && /* @__PURE__ */ React143.createElement("fieldset", { className: "rf-text-field__notch" }, hasLabel ? /* @__PURE__ */ React143.createElement("legend", { className: "rf-text-field__legend" }, /* @__PURE__ */ React143.createElement("span", null, label, " ", required ? "*" : "")) : /* @__PURE__ */ React143.createElement("legend", { className: "rf-text-field__legend--empty" }))), helperText && /* @__PURE__ */ React143.createElement("div", { className: "rf-text-field__helper-text" }, helperText));
2583
2613
  });
@@ -4434,29 +4464,46 @@ var DateField = ({
4434
4464
  if (!rect) return {};
4435
4465
  const PICKER_H = 420;
4436
4466
  const GAP2 = 6;
4437
- const spaceBelow = window.innerHeight - rect.bottom - GAP2;
4467
+ const MARGIN = 8;
4468
+ const vw = window.innerWidth;
4469
+ const vh = window.innerHeight;
4470
+ const naturalW = isSideVariant ? 470 : 320;
4471
+ const avail = vw - MARGIN * 2;
4472
+ const scale = naturalW > avail ? avail / naturalW : 1;
4473
+ const scaledW = naturalW * scale;
4474
+ let left = rect.left;
4475
+ const maxLeft = vw - scaledW - MARGIN;
4476
+ if (left > maxLeft) left = maxLeft;
4477
+ if (left < MARGIN) left = MARGIN;
4478
+ const spaceBelow = vh - rect.bottom - GAP2;
4438
4479
  const spaceAbove = rect.top - GAP2;
4439
- const useDropUp = spaceBelow < PICKER_H && spaceAbove > spaceBelow;
4480
+ const useDropUp = spaceBelow < PICKER_H * scale && spaceAbove > spaceBelow;
4481
+ const common = {
4482
+ position: "fixed",
4483
+ left,
4484
+ width: naturalW,
4485
+ transform: scale === 1 ? void 0 : `scale(${scale})`,
4486
+ overflowY: "auto",
4487
+ zIndex: 99999
4488
+ };
4440
4489
  if (useDropUp) {
4441
4490
  return {
4442
- position: "fixed",
4443
- left: rect.left,
4491
+ ...common,
4444
4492
  // bottom anchors picker's bottom edge exactly to field top — no gap regardless of actual height
4445
- bottom: window.innerHeight - rect.top + GAP2,
4446
- // prevent going above viewport
4447
- maxHeight: rect.top - GAP2 - 4,
4448
- overflowY: "auto",
4449
- zIndex: 99999,
4450
- animationName: "rf-date-picker-appear-up",
4493
+ bottom: vh - rect.top + GAP2,
4494
+ // unscaled max-height — visual height after scaling fits the gap above
4495
+ maxHeight: (rect.top - GAP2 - MARGIN) / scale,
4496
+ // skip the scale-animating keyframes when we apply a static scale (they'd flash full-size)
4497
+ animationName: scale === 1 ? "rf-date-picker-appear-up" : "none",
4451
4498
  transformOrigin: "bottom left"
4452
4499
  };
4453
4500
  }
4454
4501
  return {
4455
- position: "fixed",
4456
- left: rect.left,
4502
+ ...common,
4457
4503
  top: rect.bottom + GAP2,
4458
- zIndex: 99999,
4459
- animationName: "rf-date-picker-appear",
4504
+ // unscaled max-height — visual height after scaling fits below the field
4505
+ maxHeight: (vh - rect.bottom - GAP2 - MARGIN) / scale,
4506
+ animationName: scale === 1 ? "rf-date-picker-appear" : "none",
4460
4507
  transformOrigin: "top left"
4461
4508
  };
4462
4509
  })(),
@@ -4836,6 +4883,43 @@ var DateRangeField = ({
4836
4883
  const [leftViewMonth, setLeftViewMonth] = useState9(() => today2.getMonth());
4837
4884
  const containerRef = useRef8(null);
4838
4885
  const inputId = useRef8(`rf-dr-${Math.random().toString(36).substr(2, 9)}`).current;
4886
+ const [mobileStyle, setMobileStyle] = useState9({});
4887
+ useEffect8(() => {
4888
+ if (!open) {
4889
+ setMobileStyle({});
4890
+ return;
4891
+ }
4892
+ const compute = () => {
4893
+ const vw = window.innerWidth;
4894
+ const vh = window.innerHeight;
4895
+ const MARGIN = 8;
4896
+ const GAP2 = 6;
4897
+ const naturalW = pickerType === "panel" ? 520 : 640;
4898
+ const avail = vw - MARGIN * 2;
4899
+ if (naturalW <= avail) {
4900
+ setMobileStyle({});
4901
+ return;
4902
+ }
4903
+ const scale = avail / naturalW;
4904
+ const rect = containerRef.current?.getBoundingClientRect();
4905
+ const top = rect ? rect.bottom + GAP2 : 60;
4906
+ setMobileStyle({
4907
+ position: "fixed",
4908
+ top,
4909
+ left: "50%",
4910
+ transform: `translateX(-50%) scale(${scale})`,
4911
+ transformOrigin: "top center",
4912
+ width: naturalW,
4913
+ maxHeight: (vh - top - MARGIN) / scale,
4914
+ overflowY: "auto",
4915
+ animation: "none",
4916
+ zIndex: 99999
4917
+ });
4918
+ };
4919
+ compute();
4920
+ window.addEventListener("resize", compute);
4921
+ return () => window.removeEventListener("resize", compute);
4922
+ }, [open, pickerType]);
4839
4923
  useEffect8(() => {
4840
4924
  const s2 = value?.start ? isoToDate2(value.start) : null;
4841
4925
  const e = value?.end ? isoToDate2(value.end) : null;
@@ -5033,7 +5117,7 @@ var DateRangeField = ({
5033
5117
  variant === "outlined" && /* @__PURE__ */ React148.createElement("fieldset", { className: "rf-text-field__notch" }, label ? /* @__PURE__ */ React148.createElement("legend", { className: "rf-text-field__legend" }, /* @__PURE__ */ React148.createElement("span", null, label, required ? " *" : "")) : /* @__PURE__ */ React148.createElement("legend", { className: "rf-text-field__legend--empty" }))
5034
5118
  ), open && !disabled && (pickerType === "panel" ? (
5035
5119
  /* ── Panel Mode ── */
5036
- /* @__PURE__ */ React148.createElement("div", { className: "rf-dr-picker rf-dr-picker--panel", onMouseDown: (e) => e.preventDefault() }, /* @__PURE__ */ React148.createElement("div", { className: "rf-dr-picker__presets" }, PRESETS.map((p, i) => /* @__PURE__ */ React148.createElement(React148.Fragment, { key: p.id }, i > 0 && /* @__PURE__ */ React148.createElement("div", { className: "rf-dr-picker__preset-sep" }), /* @__PURE__ */ React148.createElement(
5120
+ /* @__PURE__ */ React148.createElement("div", { className: "rf-dr-picker rf-dr-picker--panel", style: mobileStyle, onMouseDown: (e) => e.preventDefault() }, /* @__PURE__ */ React148.createElement("div", { className: "rf-dr-picker__presets" }, PRESETS.map((p, i) => /* @__PURE__ */ React148.createElement(React148.Fragment, { key: p.id }, i > 0 && /* @__PURE__ */ React148.createElement("div", { className: "rf-dr-picker__preset-sep" }), /* @__PURE__ */ React148.createElement(
5037
5121
  "button",
5038
5122
  {
5039
5123
  type: "button",
@@ -5153,7 +5237,7 @@ var DateRangeField = ({
5153
5237
  ))), /* @__PURE__ */ React148.createElement("div", { style: { flex: 1 } }), /* @__PURE__ */ React148.createElement("div", { className: "rf-dr-picker__footer" }, /* @__PURE__ */ React148.createElement("button", { type: "button", className: "rf-dr-picker__close-btn", onClick: handleClose }, "CLOSE"), /* @__PURE__ */ React148.createElement("button", { type: "button", className: "rf-dr-picker__apply-btn", onClick: handleApply }, "APPLY"))))
5154
5238
  ) : (
5155
5239
  /* ── Calendar Mode ── */
5156
- /* @__PURE__ */ React148.createElement("div", { className: "rf-dr-picker rf-dr-picker--calendar", onMouseDown: (e) => e.preventDefault() }, /* @__PURE__ */ React148.createElement("div", { className: "rf-dr-picker__calendars" }, /* @__PURE__ */ React148.createElement(
5240
+ /* @__PURE__ */ React148.createElement("div", { className: "rf-dr-picker rf-dr-picker--calendar", style: mobileStyle, onMouseDown: (e) => e.preventDefault() }, /* @__PURE__ */ React148.createElement("div", { className: "rf-dr-picker__calendars" }, /* @__PURE__ */ React148.createElement(
5157
5241
  RangeCalendarBody,
5158
5242
  {
5159
5243
  viewYear: leftViewYear,
@@ -5556,6 +5640,7 @@ function DataGrid({
5556
5640
  const menuRef = useRef10(null);
5557
5641
  const [showManageColumns, setShowManageColumns] = useState10(false);
5558
5642
  const [showAdvancedFilter, setShowAdvancedFilter] = useState10(false);
5643
+ const [mobileToolbarExpanded, setMobileToolbarExpanded] = useState10(false);
5559
5644
  const [focusFilterIdx, setFocusFilterIdx] = useState10(-1);
5560
5645
  const filterableColumnsProp = initialColumnsProp.filter((c) => c.filterable !== false);
5561
5646
  const initialFilterCol = String(filterableColumnsProp[0]?.field || filterableColumnsProp[0]?.key || "");
@@ -6045,14 +6130,24 @@ function DataGrid({
6045
6130
  onClick: () => setShowAdvancedFilter(true)
6046
6131
  },
6047
6132
  /* @__PURE__ */ React151.createElement(Funnel, { size: 16 })
6048
- )), showColumnsBtn && /* @__PURE__ */ React151.createElement(Tooltip, { title: "Manage Columns", placement: "top" }, /* @__PURE__ */ React151.createElement(
6133
+ )), /* @__PURE__ */ React151.createElement("div", { className: `dg-actions-overflow${mobileToolbarExpanded ? " dg-actions-overflow--expanded" : ""}` }, showColumnsBtn && /* @__PURE__ */ React151.createElement(Tooltip, { title: "Manage Columns", placement: "top" }, /* @__PURE__ */ React151.createElement(
6049
6134
  "button",
6050
6135
  {
6051
6136
  className: "dg-icon-btn",
6052
6137
  onClick: () => setShowManageColumns(true)
6053
6138
  },
6054
6139
  /* @__PURE__ */ React151.createElement(Columns2, { size: 16 })
6055
- )), showExportBtn && /* @__PURE__ */ React151.createElement("button", { className: "dg-action-btn", onClick: handleExport, disabled: loading }, /* @__PURE__ */ React151.createElement(Download, { size: 14 }), " Export CSV"), slotAt("after-actions"));
6140
+ )), showExportBtn && /* @__PURE__ */ React151.createElement("button", { className: "dg-action-btn", onClick: handleExport, disabled: loading }, /* @__PURE__ */ React151.createElement(Download, { size: 14 }), " Export CSV"), slotAt("after-actions")), (showColumnsBtn || showExportBtn || slots.some((s2) => s2.position === "after-actions" || !s2.position)) && /* @__PURE__ */ React151.createElement(
6141
+ "button",
6142
+ {
6143
+ type: "button",
6144
+ className: `dg-toolbar-toggle${mobileToolbarExpanded ? " dg-toolbar-toggle--expanded" : ""}`,
6145
+ onClick: () => setMobileToolbarExpanded((v) => !v),
6146
+ "aria-label": mobileToolbarExpanded ? "Hide toolbar options" : "Show all toolbar options",
6147
+ "aria-expanded": mobileToolbarExpanded
6148
+ },
6149
+ /* @__PURE__ */ React151.createElement(ChevronDown, { size: 18 })
6150
+ ));
6056
6151
  })())), !tOpts.hideHeader && /* @__PURE__ */ React151.createElement("div", { className: `dg-toolbar ${alignClass(toolbarContent?.align)}` }, toolbarContent?.content || ""), isGroupingActive && /* @__PURE__ */ React151.createElement("div", { className: "dg-grouping-bar" }, /* @__PURE__ */ React151.createElement("span", { className: "dg-grouping-bar-label" }, "Grouped by"), activeGroupingModel.map((gField) => {
6057
6152
  const col = resolvedColumns.find((c) => String(c.field) === gField || String(c.key) === gField);
6058
6153
  return /* @__PURE__ */ React151.createElement("div", { key: gField, className: "dg-group-chip" }, /* @__PURE__ */ React151.createElement(Layers, { size: 11 }), /* @__PURE__ */ React151.createElement("span", null, col?.header ?? col?.headerName ?? gField), !disableRowGrouping && /* @__PURE__ */ React151.createElement(
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rufous/ui",
3
3
  "private": false,
4
- "version": "0.3.61",
4
+ "version": "0.3.64",
5
5
  "type": "module",
6
6
  "description": "Experimental: A lightweight React UI component library (Beta)",
7
7
  "style": "./dist/main.css",