@rufous/ui 0.2.95 → 0.2.97

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
@@ -2989,6 +2989,14 @@ var today = () => {
2989
2989
  return new Date(t.getFullYear(), t.getMonth(), t.getDate());
2990
2990
  };
2991
2991
  var isSameDay = (a, b) => a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
2992
+ var normaliseBoundary = (d) => {
2993
+ if (!d) return null;
2994
+ const base = d instanceof Date ? d : isoToDate(typeof d === "string" ? d.split("T")[0] : d);
2995
+ if (!base) return null;
2996
+ return new Date(base.getFullYear(), base.getMonth(), base.getDate());
2997
+ };
2998
+ var isBeforeDay = (a, b) => a.getFullYear() < b.getFullYear() || a.getFullYear() === b.getFullYear() && a.getMonth() < b.getMonth() || a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() < b.getDate();
2999
+ var isAfterDay = (a, b) => a.getFullYear() > b.getFullYear() || a.getFullYear() === b.getFullYear() && a.getMonth() > b.getMonth() || a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() > b.getDate();
2992
3000
  var isDatetimeType = (t) => t !== "date";
2993
3001
  var buildISO = (date, type, hour, minute, ampm) => {
2994
3002
  const y = date.getFullYear();
@@ -3149,15 +3157,13 @@ var CalendarBody = ({
3149
3157
  onPrev,
3150
3158
  onNext,
3151
3159
  onMonthSelect,
3152
- onYearSelect
3160
+ onYearSelect,
3161
+ minDate,
3162
+ maxDate
3153
3163
  }) => {
3154
3164
  const [pickerView, setPickerView] = (0, import_react21.useState)("calendar");
3155
- const handleMonthClick = () => {
3156
- setPickerView(pickerView === "month" ? "calendar" : "month");
3157
- };
3158
- const handleYearClick = () => {
3159
- setPickerView(pickerView === "year" ? "calendar" : "year");
3160
- };
3165
+ const handleMonthClick = () => setPickerView(pickerView === "month" ? "calendar" : "month");
3166
+ const handleYearClick = () => setPickerView(pickerView === "year" ? "calendar" : "year");
3161
3167
  const handleMonthPick = (month) => {
3162
3168
  onMonthSelect(month);
3163
3169
  setPickerView("calendar");
@@ -3169,6 +3175,22 @@ var CalendarBody = ({
3169
3175
  const currentYear = todayDate.getFullYear();
3170
3176
  const yearStart = viewYear - 6;
3171
3177
  const years = Array.from({ length: 16 }, (_, i) => yearStart + i);
3178
+ const prevMonthLastDay = new Date(viewYear, viewMonth, 0);
3179
+ const isPrevDisabled = minDate ? isBeforeDay(prevMonthLastDay, minDate) : false;
3180
+ const nextMonthFirstDay = new Date(viewYear, viewMonth + 1, 1);
3181
+ const isNextDisabled = maxDate ? isAfterDay(nextMonthFirstDay, maxDate) : false;
3182
+ const isMonthDisabled = (idx) => {
3183
+ const lastDayOfMonth = new Date(viewYear, idx + 1, 0);
3184
+ const firstDayOfMonth = new Date(viewYear, idx, 1);
3185
+ if (minDate && isBeforeDay(lastDayOfMonth, minDate)) return true;
3186
+ if (maxDate && isAfterDay(firstDayOfMonth, maxDate)) return true;
3187
+ return false;
3188
+ };
3189
+ const isYearDisabled = (y) => {
3190
+ if (minDate && y < minDate.getFullYear()) return true;
3191
+ if (maxDate && y > maxDate.getFullYear()) return true;
3192
+ return false;
3193
+ };
3172
3194
  return /* @__PURE__ */ import_react21.default.createElement(import_react21.default.Fragment, null, /* @__PURE__ */ import_react21.default.createElement("div", { className: "rf-date-picker__header" }, /* @__PURE__ */ import_react21.default.createElement("div", { className: "rf-date-picker__header-labels" }, /* @__PURE__ */ import_react21.default.createElement(
3173
3195
  "span",
3174
3196
  {
@@ -3183,48 +3205,61 @@ var CalendarBody = ({
3183
3205
  onClick: handleYearClick
3184
3206
  },
3185
3207
  viewYear
3186
- )), /* @__PURE__ */ import_react21.default.createElement("div", { className: "rf-date-picker__nav" }, pickerView === "year" ? /* @__PURE__ */ import_react21.default.createElement(import_react21.default.Fragment, null, /* @__PURE__ */ import_react21.default.createElement("button", { type: "button", className: "rf-date-picker__nav-btn", onClick: () => onYearSelect(viewYear - 16), "aria-label": "Previous years" }, "\u2039"), /* @__PURE__ */ import_react21.default.createElement("button", { type: "button", className: "rf-date-picker__nav-btn", onClick: () => onYearSelect(viewYear + 16), "aria-label": "Next years" }, "\u203A")) : /* @__PURE__ */ import_react21.default.createElement(import_react21.default.Fragment, null, /* @__PURE__ */ import_react21.default.createElement("button", { type: "button", className: "rf-date-picker__nav-btn", onClick: onPrev, "aria-label": "Previous month" }, "\u2039"), /* @__PURE__ */ import_react21.default.createElement("button", { type: "button", className: "rf-date-picker__nav-btn", onClick: onNext, "aria-label": "Next month" }, "\u203A")))), pickerView === "month" && /* @__PURE__ */ import_react21.default.createElement("div", { className: "rf-date-picker__month-grid" }, MONTHS_SHORT.map((m, idx) => /* @__PURE__ */ import_react21.default.createElement(
3187
- "button",
3188
- {
3189
- key: m,
3190
- type: "button",
3191
- className: [
3192
- "rf-date-picker__month-cell",
3193
- idx === viewMonth ? "rf-date-picker__month-cell--selected" : "",
3194
- idx === todayDate.getMonth() && viewYear === currentYear ? "rf-date-picker__month-cell--current" : ""
3195
- ].filter(Boolean).join(" "),
3196
- onClick: () => handleMonthPick(idx)
3197
- },
3198
- m
3199
- ))), pickerView === "year" && /* @__PURE__ */ import_react21.default.createElement("div", { className: "rf-date-picker__year-grid" }, years.map((y) => /* @__PURE__ */ import_react21.default.createElement(
3200
- "button",
3201
- {
3202
- key: y,
3203
- type: "button",
3204
- className: [
3205
- "rf-date-picker__year-cell",
3206
- y === viewYear ? "rf-date-picker__year-cell--selected" : "",
3207
- y === currentYear ? "rf-date-picker__year-cell--current" : ""
3208
- ].filter(Boolean).join(" "),
3209
- onClick: () => handleYearPick(y)
3210
- },
3211
- y
3212
- ))), pickerView === "calendar" && /* @__PURE__ */ import_react21.default.createElement(import_react21.default.Fragment, null, /* @__PURE__ */ import_react21.default.createElement("div", { className: "rf-date-picker__weekdays" }, WEEKDAYS.map((w) => /* @__PURE__ */ import_react21.default.createElement("div", { key: w, className: "rf-date-picker__weekday" }, w))), /* @__PURE__ */ import_react21.default.createElement("div", { className: "rf-date-picker__grid" }, dayCells.map((day, idx) => {
3208
+ )), /* @__PURE__ */ import_react21.default.createElement("div", { className: "rf-date-picker__nav" }, pickerView === "year" ? /* @__PURE__ */ import_react21.default.createElement(import_react21.default.Fragment, null, /* @__PURE__ */ import_react21.default.createElement("button", { type: "button", className: "rf-date-picker__nav-btn", onClick: () => onYearSelect(viewYear - 16), "aria-label": "Previous years" }, "\u2039"), /* @__PURE__ */ import_react21.default.createElement("button", { type: "button", className: "rf-date-picker__nav-btn", onClick: () => onYearSelect(viewYear + 16), "aria-label": "Next years" }, "\u203A")) : /* @__PURE__ */ import_react21.default.createElement(import_react21.default.Fragment, null, /* @__PURE__ */ import_react21.default.createElement("button", { type: "button", className: "rf-date-picker__nav-btn", onClick: onPrev, disabled: isPrevDisabled, "aria-label": "Previous month" }, "\u2039"), /* @__PURE__ */ import_react21.default.createElement("button", { type: "button", className: "rf-date-picker__nav-btn", onClick: onNext, disabled: isNextDisabled, "aria-label": "Next month" }, "\u203A")))), pickerView === "month" && /* @__PURE__ */ import_react21.default.createElement("div", { className: "rf-date-picker__month-grid" }, MONTHS_SHORT.map((m, idx) => {
3209
+ const monthDisabled = isMonthDisabled(idx);
3210
+ return /* @__PURE__ */ import_react21.default.createElement(
3211
+ "button",
3212
+ {
3213
+ key: m,
3214
+ type: "button",
3215
+ disabled: monthDisabled,
3216
+ className: [
3217
+ "rf-date-picker__month-cell",
3218
+ idx === viewMonth ? "rf-date-picker__month-cell--selected" : "",
3219
+ idx === todayDate.getMonth() && viewYear === currentYear ? "rf-date-picker__month-cell--current" : "",
3220
+ monthDisabled ? "rf-date-picker__month-cell--disabled" : ""
3221
+ ].filter(Boolean).join(" "),
3222
+ onClick: () => !monthDisabled && handleMonthPick(idx)
3223
+ },
3224
+ m
3225
+ );
3226
+ })), pickerView === "year" && /* @__PURE__ */ import_react21.default.createElement("div", { className: "rf-date-picker__year-grid" }, years.map((y) => {
3227
+ const yearDisabled = isYearDisabled(y);
3228
+ return /* @__PURE__ */ import_react21.default.createElement(
3229
+ "button",
3230
+ {
3231
+ key: y,
3232
+ type: "button",
3233
+ disabled: yearDisabled,
3234
+ className: [
3235
+ "rf-date-picker__year-cell",
3236
+ y === viewYear ? "rf-date-picker__year-cell--selected" : "",
3237
+ y === currentYear ? "rf-date-picker__year-cell--current" : "",
3238
+ yearDisabled ? "rf-date-picker__year-cell--disabled" : ""
3239
+ ].filter(Boolean).join(" "),
3240
+ onClick: () => !yearDisabled && handleYearPick(y)
3241
+ },
3242
+ y
3243
+ );
3244
+ })), pickerView === "calendar" && /* @__PURE__ */ import_react21.default.createElement(import_react21.default.Fragment, null, /* @__PURE__ */ import_react21.default.createElement("div", { className: "rf-date-picker__weekdays" }, WEEKDAYS.map((w) => /* @__PURE__ */ import_react21.default.createElement("div", { key: w, className: "rf-date-picker__weekday" }, w))), /* @__PURE__ */ import_react21.default.createElement("div", { className: "rf-date-picker__grid" }, dayCells.map((day, idx) => {
3213
3245
  if (day === null) return /* @__PURE__ */ import_react21.default.createElement("div", { key: `e-${idx}`, className: "rf-date-picker__day rf-date-picker__day--empty" });
3214
3246
  const cellDate = new Date(viewYear, viewMonth, day);
3215
3247
  const isSelected = selectedDate ? isSameDay(cellDate, selectedDate) : false;
3216
3248
  const isToday = isSameDay(cellDate, todayDate);
3249
+ const dayDisabled = (minDate ? isBeforeDay(cellDate, minDate) : false) || (maxDate ? isAfterDay(cellDate, maxDate) : false);
3217
3250
  return /* @__PURE__ */ import_react21.default.createElement(
3218
3251
  "button",
3219
3252
  {
3220
3253
  key: day,
3221
3254
  type: "button",
3255
+ disabled: dayDisabled,
3222
3256
  className: [
3223
3257
  "rf-date-picker__day",
3224
3258
  isSelected ? "rf-date-picker__day--selected" : "",
3225
- isToday && !isSelected ? "rf-date-picker__day--today" : ""
3259
+ isToday && !isSelected ? "rf-date-picker__day--today" : "",
3260
+ dayDisabled ? "rf-date-picker__day--disabled" : ""
3226
3261
  ].filter(Boolean).join(" "),
3227
- onClick: () => onDayClick(day)
3262
+ onClick: () => !dayDisabled && onDayClick(day)
3228
3263
  },
3229
3264
  day
3230
3265
  );
@@ -3236,6 +3271,8 @@ var DateField = ({
3236
3271
  onChange,
3237
3272
  type = "date",
3238
3273
  dateFormat = "MM/DD/YYYY",
3274
+ minDate: minDateProp,
3275
+ maxDate: maxDateProp,
3239
3276
  variant = "outlined",
3240
3277
  size = "medium",
3241
3278
  color = "primary",
@@ -3249,6 +3286,8 @@ var DateField = ({
3249
3286
  style,
3250
3287
  sx
3251
3288
  }) => {
3289
+ const minDate = normaliseBoundary(minDateProp);
3290
+ const maxDate = normaliseBoundary(maxDateProp);
3252
3291
  const sxClass = useSx(sx);
3253
3292
  const [open, setOpen] = (0, import_react21.useState)(false);
3254
3293
  const [selectedDate, setSelectedDate] = (0, import_react21.useState)(() => value ? isoToDate(value) : null);
@@ -3348,8 +3387,10 @@ var DateField = ({
3348
3387
  setInputStr(str);
3349
3388
  onChange?.(buildISO(d, type, h, m, ap));
3350
3389
  }, [type, onChange, dateFormat]);
3390
+ const isOutOfRange = (d) => (minDate ? isBeforeDay(d, minDate) : false) || (maxDate ? isAfterDay(d, maxDate) : false);
3351
3391
  const handleDayClick = (day) => {
3352
3392
  const d = new Date(viewYear, viewMonth, day);
3393
+ if (isOutOfRange(d)) return;
3353
3394
  setSelectedDate(d);
3354
3395
  let str = formatDisplay(d, dateFormat);
3355
3396
  if (isDatetimeType(type)) str += " " + formatTimeDisplay(hour, minute, ampm);
@@ -3359,6 +3400,7 @@ var DateField = ({
3359
3400
  };
3360
3401
  const handleToday = () => {
3361
3402
  const t = today();
3403
+ if (isOutOfRange(t)) return;
3362
3404
  setViewYear(t.getFullYear());
3363
3405
  setViewMonth(t.getMonth());
3364
3406
  commitDate(t, hour, minute, ampm);
@@ -3373,7 +3415,7 @@ var DateField = ({
3373
3415
  const datePart = words.slice(0, dateWordCount).join(" ");
3374
3416
  const timeParts = words.slice(dateWordCount);
3375
3417
  const parsed = parseDisplay(datePart, dateFormat);
3376
- if (parsed) {
3418
+ if (parsed && !isOutOfRange(parsed)) {
3377
3419
  setSelectedDate(parsed);
3378
3420
  setViewYear(parsed.getFullYear());
3379
3421
  setViewMonth(parsed.getMonth());
@@ -3567,7 +3609,9 @@ var DateField = ({
3567
3609
  onPrev: prevMonth,
3568
3610
  onNext: nextMonth,
3569
3611
  onMonthSelect: setViewMonth,
3570
- onYearSelect: setViewYear
3612
+ onYearSelect: setViewYear,
3613
+ minDate,
3614
+ maxDate
3571
3615
  }
3572
3616
  ), type === "datetime" && /* @__PURE__ */ import_react21.default.createElement("div", { className: "rf-date-picker__time-section" }, /* @__PURE__ */ import_react21.default.createElement("div", { className: "rf-date-picker__time-label" }, "Time"), /* @__PURE__ */ import_react21.default.createElement(
3573
3617
  SpinnerPanel,
@@ -3581,7 +3625,7 @@ var DateField = ({
3581
3625
  onMinuteInput: handleMinuteInput,
3582
3626
  onAmpmToggle: handleAmpmToggle
3583
3627
  }
3584
- )), /* @__PURE__ */ import_react21.default.createElement("div", { className: "rf-date-picker__divider" }), /* @__PURE__ */ import_react21.default.createElement("div", { className: "rf-date-picker__footer" }, /* @__PURE__ */ import_react21.default.createElement("button", { type: "button", className: "rf-date-picker__footer-btn", onClick: handleToday }, "Today"), /* @__PURE__ */ import_react21.default.createElement("button", { type: "button", className: "rf-date-picker__footer-btn rf-date-picker__footer-btn--clear", onClick: handleClear }, "Clear"))),
3628
+ )), /* @__PURE__ */ import_react21.default.createElement("div", { className: "rf-date-picker__divider" }), /* @__PURE__ */ import_react21.default.createElement("div", { className: "rf-date-picker__footer" }, /* @__PURE__ */ import_react21.default.createElement("button", { type: "button", className: "rf-date-picker__footer-btn", onClick: handleToday, disabled: isOutOfRange(todayDate) }, "Today"), /* @__PURE__ */ import_react21.default.createElement("button", { type: "button", className: "rf-date-picker__footer-btn rf-date-picker__footer-btn--clear", onClick: handleClear }, "Clear"))),
3585
3629
  type === "datetime-side" && /* @__PURE__ */ import_react21.default.createElement("div", { className: "rf-date-picker__side-panel" }, /* @__PURE__ */ import_react21.default.createElement("div", { className: "rf-date-picker__side-label" }, "Time"), /* @__PURE__ */ import_react21.default.createElement("div", { className: "rf-date-picker__side-spinner" }, /* @__PURE__ */ import_react21.default.createElement(
3586
3630
  SpinnerPanel,
3587
3631
  {
@@ -4622,9 +4666,14 @@ function DataGrid({
4622
4666
  const headers = exportableCols.map((c) => c.headerName).join(",");
4623
4667
  const rows = sortedData.map(
4624
4668
  (item) => exportableCols.map((c) => {
4625
- const raw = item[c.field];
4626
- const val = raw === null || raw === void 0 ? "" : String(raw).replace(/"/g, '""');
4627
- return `"${val}"`;
4669
+ const field = String(c.field);
4670
+ const raw = item[field];
4671
+ let val = c.valueGetter ? c.valueGetter({ value: raw, row: item, field }) : raw;
4672
+ if (c.valueFormatter) {
4673
+ val = c.valueFormatter({ value: val, row: item, field });
4674
+ }
4675
+ const str = val === null || val === void 0 ? "" : String(val).replace(/"/g, '""');
4676
+ return `"${str}"`;
4628
4677
  }).join(",")
4629
4678
  );
4630
4679
  const csv = [headers, ...rows].join("\n");
package/dist/main.css CHANGED
@@ -1758,6 +1758,34 @@ pre {
1758
1758
  color: #a41b06;
1759
1759
  font-weight: 600;
1760
1760
  }
1761
+ .rf-date-picker__day--disabled,
1762
+ .rf-date-picker__day:disabled {
1763
+ opacity: 0.3;
1764
+ cursor: not-allowed;
1765
+ pointer-events: none;
1766
+ }
1767
+ .rf-date-picker__month-cell--disabled,
1768
+ .rf-date-picker__month-cell:disabled {
1769
+ opacity: 0.3;
1770
+ cursor: not-allowed;
1771
+ pointer-events: none;
1772
+ }
1773
+ .rf-date-picker__year-cell--disabled,
1774
+ .rf-date-picker__year-cell:disabled {
1775
+ opacity: 0.3;
1776
+ cursor: not-allowed;
1777
+ pointer-events: none;
1778
+ }
1779
+ .rf-date-picker__nav-btn:disabled {
1780
+ opacity: 0.3;
1781
+ cursor: not-allowed;
1782
+ pointer-events: none;
1783
+ }
1784
+ .rf-date-picker__footer-btn:disabled {
1785
+ opacity: 0.3;
1786
+ cursor: not-allowed;
1787
+ pointer-events: none;
1788
+ }
1761
1789
 
1762
1790
  /* lib/styles/date-range-field.css */
1763
1791
  .rf-date-range-field {
package/dist/main.d.cts CHANGED
@@ -627,6 +627,10 @@ interface DateFieldProps {
627
627
  type?: "date" | "datetime" | "datetime-side" | "datetime-scroll";
628
628
  /** Display format for the date in the input field. Default: "MM/DD/YYYY" */
629
629
  dateFormat?: DateFormatString;
630
+ /** Earliest selectable date — ISO string "YYYY-MM-DD" or a Date object */
631
+ minDate?: string | Date;
632
+ /** Latest selectable date — ISO string "YYYY-MM-DD" or a Date object */
633
+ maxDate?: string | Date;
630
634
  variant?: "outlined" | "filled" | "standard" | "compact";
631
635
  size?: "small" | "medium";
632
636
  color?: "primary" | "secondary" | "error" | "success" | "info" | "warning";
package/dist/main.d.ts CHANGED
@@ -627,6 +627,10 @@ interface DateFieldProps {
627
627
  type?: "date" | "datetime" | "datetime-side" | "datetime-scroll";
628
628
  /** Display format for the date in the input field. Default: "MM/DD/YYYY" */
629
629
  dateFormat?: DateFormatString;
630
+ /** Earliest selectable date — ISO string "YYYY-MM-DD" or a Date object */
631
+ minDate?: string | Date;
632
+ /** Latest selectable date — ISO string "YYYY-MM-DD" or a Date object */
633
+ maxDate?: string | Date;
630
634
  variant?: "outlined" | "filled" | "standard" | "compact";
631
635
  size?: "small" | "medium";
632
636
  color?: "primary" | "secondary" | "error" | "success" | "info" | "warning";
package/dist/main.js CHANGED
@@ -2838,6 +2838,14 @@ var today = () => {
2838
2838
  return new Date(t.getFullYear(), t.getMonth(), t.getDate());
2839
2839
  };
2840
2840
  var isSameDay = (a, b) => a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
2841
+ var normaliseBoundary = (d) => {
2842
+ if (!d) return null;
2843
+ const base = d instanceof Date ? d : isoToDate(typeof d === "string" ? d.split("T")[0] : d);
2844
+ if (!base) return null;
2845
+ return new Date(base.getFullYear(), base.getMonth(), base.getDate());
2846
+ };
2847
+ var isBeforeDay = (a, b) => a.getFullYear() < b.getFullYear() || a.getFullYear() === b.getFullYear() && a.getMonth() < b.getMonth() || a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() < b.getDate();
2848
+ var isAfterDay = (a, b) => a.getFullYear() > b.getFullYear() || a.getFullYear() === b.getFullYear() && a.getMonth() > b.getMonth() || a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() > b.getDate();
2841
2849
  var isDatetimeType = (t) => t !== "date";
2842
2850
  var buildISO = (date, type, hour, minute, ampm) => {
2843
2851
  const y = date.getFullYear();
@@ -2998,15 +3006,13 @@ var CalendarBody = ({
2998
3006
  onPrev,
2999
3007
  onNext,
3000
3008
  onMonthSelect,
3001
- onYearSelect
3009
+ onYearSelect,
3010
+ minDate,
3011
+ maxDate
3002
3012
  }) => {
3003
3013
  const [pickerView, setPickerView] = useState7("calendar");
3004
- const handleMonthClick = () => {
3005
- setPickerView(pickerView === "month" ? "calendar" : "month");
3006
- };
3007
- const handleYearClick = () => {
3008
- setPickerView(pickerView === "year" ? "calendar" : "year");
3009
- };
3014
+ const handleMonthClick = () => setPickerView(pickerView === "month" ? "calendar" : "month");
3015
+ const handleYearClick = () => setPickerView(pickerView === "year" ? "calendar" : "year");
3010
3016
  const handleMonthPick = (month) => {
3011
3017
  onMonthSelect(month);
3012
3018
  setPickerView("calendar");
@@ -3018,6 +3024,22 @@ var CalendarBody = ({
3018
3024
  const currentYear = todayDate.getFullYear();
3019
3025
  const yearStart = viewYear - 6;
3020
3026
  const years = Array.from({ length: 16 }, (_, i) => yearStart + i);
3027
+ const prevMonthLastDay = new Date(viewYear, viewMonth, 0);
3028
+ const isPrevDisabled = minDate ? isBeforeDay(prevMonthLastDay, minDate) : false;
3029
+ const nextMonthFirstDay = new Date(viewYear, viewMonth + 1, 1);
3030
+ const isNextDisabled = maxDate ? isAfterDay(nextMonthFirstDay, maxDate) : false;
3031
+ const isMonthDisabled = (idx) => {
3032
+ const lastDayOfMonth = new Date(viewYear, idx + 1, 0);
3033
+ const firstDayOfMonth = new Date(viewYear, idx, 1);
3034
+ if (minDate && isBeforeDay(lastDayOfMonth, minDate)) return true;
3035
+ if (maxDate && isAfterDay(firstDayOfMonth, maxDate)) return true;
3036
+ return false;
3037
+ };
3038
+ const isYearDisabled = (y) => {
3039
+ if (minDate && y < minDate.getFullYear()) return true;
3040
+ if (maxDate && y > maxDate.getFullYear()) return true;
3041
+ return false;
3042
+ };
3021
3043
  return /* @__PURE__ */ React72.createElement(React72.Fragment, null, /* @__PURE__ */ React72.createElement("div", { className: "rf-date-picker__header" }, /* @__PURE__ */ React72.createElement("div", { className: "rf-date-picker__header-labels" }, /* @__PURE__ */ React72.createElement(
3022
3044
  "span",
3023
3045
  {
@@ -3032,48 +3054,61 @@ var CalendarBody = ({
3032
3054
  onClick: handleYearClick
3033
3055
  },
3034
3056
  viewYear
3035
- )), /* @__PURE__ */ React72.createElement("div", { className: "rf-date-picker__nav" }, pickerView === "year" ? /* @__PURE__ */ React72.createElement(React72.Fragment, null, /* @__PURE__ */ React72.createElement("button", { type: "button", className: "rf-date-picker__nav-btn", onClick: () => onYearSelect(viewYear - 16), "aria-label": "Previous years" }, "\u2039"), /* @__PURE__ */ React72.createElement("button", { type: "button", className: "rf-date-picker__nav-btn", onClick: () => onYearSelect(viewYear + 16), "aria-label": "Next years" }, "\u203A")) : /* @__PURE__ */ React72.createElement(React72.Fragment, null, /* @__PURE__ */ React72.createElement("button", { type: "button", className: "rf-date-picker__nav-btn", onClick: onPrev, "aria-label": "Previous month" }, "\u2039"), /* @__PURE__ */ React72.createElement("button", { type: "button", className: "rf-date-picker__nav-btn", onClick: onNext, "aria-label": "Next month" }, "\u203A")))), pickerView === "month" && /* @__PURE__ */ React72.createElement("div", { className: "rf-date-picker__month-grid" }, MONTHS_SHORT.map((m, idx) => /* @__PURE__ */ React72.createElement(
3036
- "button",
3037
- {
3038
- key: m,
3039
- type: "button",
3040
- className: [
3041
- "rf-date-picker__month-cell",
3042
- idx === viewMonth ? "rf-date-picker__month-cell--selected" : "",
3043
- idx === todayDate.getMonth() && viewYear === currentYear ? "rf-date-picker__month-cell--current" : ""
3044
- ].filter(Boolean).join(" "),
3045
- onClick: () => handleMonthPick(idx)
3046
- },
3047
- m
3048
- ))), pickerView === "year" && /* @__PURE__ */ React72.createElement("div", { className: "rf-date-picker__year-grid" }, years.map((y) => /* @__PURE__ */ React72.createElement(
3049
- "button",
3050
- {
3051
- key: y,
3052
- type: "button",
3053
- className: [
3054
- "rf-date-picker__year-cell",
3055
- y === viewYear ? "rf-date-picker__year-cell--selected" : "",
3056
- y === currentYear ? "rf-date-picker__year-cell--current" : ""
3057
- ].filter(Boolean).join(" "),
3058
- onClick: () => handleYearPick(y)
3059
- },
3060
- y
3061
- ))), pickerView === "calendar" && /* @__PURE__ */ React72.createElement(React72.Fragment, null, /* @__PURE__ */ React72.createElement("div", { className: "rf-date-picker__weekdays" }, WEEKDAYS.map((w) => /* @__PURE__ */ React72.createElement("div", { key: w, className: "rf-date-picker__weekday" }, w))), /* @__PURE__ */ React72.createElement("div", { className: "rf-date-picker__grid" }, dayCells.map((day, idx) => {
3057
+ )), /* @__PURE__ */ React72.createElement("div", { className: "rf-date-picker__nav" }, pickerView === "year" ? /* @__PURE__ */ React72.createElement(React72.Fragment, null, /* @__PURE__ */ React72.createElement("button", { type: "button", className: "rf-date-picker__nav-btn", onClick: () => onYearSelect(viewYear - 16), "aria-label": "Previous years" }, "\u2039"), /* @__PURE__ */ React72.createElement("button", { type: "button", className: "rf-date-picker__nav-btn", onClick: () => onYearSelect(viewYear + 16), "aria-label": "Next years" }, "\u203A")) : /* @__PURE__ */ React72.createElement(React72.Fragment, null, /* @__PURE__ */ React72.createElement("button", { type: "button", className: "rf-date-picker__nav-btn", onClick: onPrev, disabled: isPrevDisabled, "aria-label": "Previous month" }, "\u2039"), /* @__PURE__ */ React72.createElement("button", { type: "button", className: "rf-date-picker__nav-btn", onClick: onNext, disabled: isNextDisabled, "aria-label": "Next month" }, "\u203A")))), pickerView === "month" && /* @__PURE__ */ React72.createElement("div", { className: "rf-date-picker__month-grid" }, MONTHS_SHORT.map((m, idx) => {
3058
+ const monthDisabled = isMonthDisabled(idx);
3059
+ return /* @__PURE__ */ React72.createElement(
3060
+ "button",
3061
+ {
3062
+ key: m,
3063
+ type: "button",
3064
+ disabled: monthDisabled,
3065
+ className: [
3066
+ "rf-date-picker__month-cell",
3067
+ idx === viewMonth ? "rf-date-picker__month-cell--selected" : "",
3068
+ idx === todayDate.getMonth() && viewYear === currentYear ? "rf-date-picker__month-cell--current" : "",
3069
+ monthDisabled ? "rf-date-picker__month-cell--disabled" : ""
3070
+ ].filter(Boolean).join(" "),
3071
+ onClick: () => !monthDisabled && handleMonthPick(idx)
3072
+ },
3073
+ m
3074
+ );
3075
+ })), pickerView === "year" && /* @__PURE__ */ React72.createElement("div", { className: "rf-date-picker__year-grid" }, years.map((y) => {
3076
+ const yearDisabled = isYearDisabled(y);
3077
+ return /* @__PURE__ */ React72.createElement(
3078
+ "button",
3079
+ {
3080
+ key: y,
3081
+ type: "button",
3082
+ disabled: yearDisabled,
3083
+ className: [
3084
+ "rf-date-picker__year-cell",
3085
+ y === viewYear ? "rf-date-picker__year-cell--selected" : "",
3086
+ y === currentYear ? "rf-date-picker__year-cell--current" : "",
3087
+ yearDisabled ? "rf-date-picker__year-cell--disabled" : ""
3088
+ ].filter(Boolean).join(" "),
3089
+ onClick: () => !yearDisabled && handleYearPick(y)
3090
+ },
3091
+ y
3092
+ );
3093
+ })), pickerView === "calendar" && /* @__PURE__ */ React72.createElement(React72.Fragment, null, /* @__PURE__ */ React72.createElement("div", { className: "rf-date-picker__weekdays" }, WEEKDAYS.map((w) => /* @__PURE__ */ React72.createElement("div", { key: w, className: "rf-date-picker__weekday" }, w))), /* @__PURE__ */ React72.createElement("div", { className: "rf-date-picker__grid" }, dayCells.map((day, idx) => {
3062
3094
  if (day === null) return /* @__PURE__ */ React72.createElement("div", { key: `e-${idx}`, className: "rf-date-picker__day rf-date-picker__day--empty" });
3063
3095
  const cellDate = new Date(viewYear, viewMonth, day);
3064
3096
  const isSelected = selectedDate ? isSameDay(cellDate, selectedDate) : false;
3065
3097
  const isToday = isSameDay(cellDate, todayDate);
3098
+ const dayDisabled = (minDate ? isBeforeDay(cellDate, minDate) : false) || (maxDate ? isAfterDay(cellDate, maxDate) : false);
3066
3099
  return /* @__PURE__ */ React72.createElement(
3067
3100
  "button",
3068
3101
  {
3069
3102
  key: day,
3070
3103
  type: "button",
3104
+ disabled: dayDisabled,
3071
3105
  className: [
3072
3106
  "rf-date-picker__day",
3073
3107
  isSelected ? "rf-date-picker__day--selected" : "",
3074
- isToday && !isSelected ? "rf-date-picker__day--today" : ""
3108
+ isToday && !isSelected ? "rf-date-picker__day--today" : "",
3109
+ dayDisabled ? "rf-date-picker__day--disabled" : ""
3075
3110
  ].filter(Boolean).join(" "),
3076
- onClick: () => onDayClick(day)
3111
+ onClick: () => !dayDisabled && onDayClick(day)
3077
3112
  },
3078
3113
  day
3079
3114
  );
@@ -3085,6 +3120,8 @@ var DateField = ({
3085
3120
  onChange,
3086
3121
  type = "date",
3087
3122
  dateFormat = "MM/DD/YYYY",
3123
+ minDate: minDateProp,
3124
+ maxDate: maxDateProp,
3088
3125
  variant = "outlined",
3089
3126
  size = "medium",
3090
3127
  color = "primary",
@@ -3098,6 +3135,8 @@ var DateField = ({
3098
3135
  style,
3099
3136
  sx
3100
3137
  }) => {
3138
+ const minDate = normaliseBoundary(minDateProp);
3139
+ const maxDate = normaliseBoundary(maxDateProp);
3101
3140
  const sxClass = useSx(sx);
3102
3141
  const [open, setOpen] = useState7(false);
3103
3142
  const [selectedDate, setSelectedDate] = useState7(() => value ? isoToDate(value) : null);
@@ -3197,8 +3236,10 @@ var DateField = ({
3197
3236
  setInputStr(str);
3198
3237
  onChange?.(buildISO(d, type, h, m, ap));
3199
3238
  }, [type, onChange, dateFormat]);
3239
+ const isOutOfRange = (d) => (minDate ? isBeforeDay(d, minDate) : false) || (maxDate ? isAfterDay(d, maxDate) : false);
3200
3240
  const handleDayClick = (day) => {
3201
3241
  const d = new Date(viewYear, viewMonth, day);
3242
+ if (isOutOfRange(d)) return;
3202
3243
  setSelectedDate(d);
3203
3244
  let str = formatDisplay(d, dateFormat);
3204
3245
  if (isDatetimeType(type)) str += " " + formatTimeDisplay(hour, minute, ampm);
@@ -3208,6 +3249,7 @@ var DateField = ({
3208
3249
  };
3209
3250
  const handleToday = () => {
3210
3251
  const t = today();
3252
+ if (isOutOfRange(t)) return;
3211
3253
  setViewYear(t.getFullYear());
3212
3254
  setViewMonth(t.getMonth());
3213
3255
  commitDate(t, hour, minute, ampm);
@@ -3222,7 +3264,7 @@ var DateField = ({
3222
3264
  const datePart = words.slice(0, dateWordCount).join(" ");
3223
3265
  const timeParts = words.slice(dateWordCount);
3224
3266
  const parsed = parseDisplay(datePart, dateFormat);
3225
- if (parsed) {
3267
+ if (parsed && !isOutOfRange(parsed)) {
3226
3268
  setSelectedDate(parsed);
3227
3269
  setViewYear(parsed.getFullYear());
3228
3270
  setViewMonth(parsed.getMonth());
@@ -3416,7 +3458,9 @@ var DateField = ({
3416
3458
  onPrev: prevMonth,
3417
3459
  onNext: nextMonth,
3418
3460
  onMonthSelect: setViewMonth,
3419
- onYearSelect: setViewYear
3461
+ onYearSelect: setViewYear,
3462
+ minDate,
3463
+ maxDate
3420
3464
  }
3421
3465
  ), type === "datetime" && /* @__PURE__ */ React72.createElement("div", { className: "rf-date-picker__time-section" }, /* @__PURE__ */ React72.createElement("div", { className: "rf-date-picker__time-label" }, "Time"), /* @__PURE__ */ React72.createElement(
3422
3466
  SpinnerPanel,
@@ -3430,7 +3474,7 @@ var DateField = ({
3430
3474
  onMinuteInput: handleMinuteInput,
3431
3475
  onAmpmToggle: handleAmpmToggle
3432
3476
  }
3433
- )), /* @__PURE__ */ React72.createElement("div", { className: "rf-date-picker__divider" }), /* @__PURE__ */ React72.createElement("div", { className: "rf-date-picker__footer" }, /* @__PURE__ */ React72.createElement("button", { type: "button", className: "rf-date-picker__footer-btn", onClick: handleToday }, "Today"), /* @__PURE__ */ React72.createElement("button", { type: "button", className: "rf-date-picker__footer-btn rf-date-picker__footer-btn--clear", onClick: handleClear }, "Clear"))),
3477
+ )), /* @__PURE__ */ React72.createElement("div", { className: "rf-date-picker__divider" }), /* @__PURE__ */ React72.createElement("div", { className: "rf-date-picker__footer" }, /* @__PURE__ */ React72.createElement("button", { type: "button", className: "rf-date-picker__footer-btn", onClick: handleToday, disabled: isOutOfRange(todayDate) }, "Today"), /* @__PURE__ */ React72.createElement("button", { type: "button", className: "rf-date-picker__footer-btn rf-date-picker__footer-btn--clear", onClick: handleClear }, "Clear"))),
3434
3478
  type === "datetime-side" && /* @__PURE__ */ React72.createElement("div", { className: "rf-date-picker__side-panel" }, /* @__PURE__ */ React72.createElement("div", { className: "rf-date-picker__side-label" }, "Time"), /* @__PURE__ */ React72.createElement("div", { className: "rf-date-picker__side-spinner" }, /* @__PURE__ */ React72.createElement(
3435
3479
  SpinnerPanel,
3436
3480
  {
@@ -4492,9 +4536,14 @@ function DataGrid({
4492
4536
  const headers = exportableCols.map((c) => c.headerName).join(",");
4493
4537
  const rows = sortedData.map(
4494
4538
  (item) => exportableCols.map((c) => {
4495
- const raw = item[c.field];
4496
- const val = raw === null || raw === void 0 ? "" : String(raw).replace(/"/g, '""');
4497
- return `"${val}"`;
4539
+ const field = String(c.field);
4540
+ const raw = item[field];
4541
+ let val = c.valueGetter ? c.valueGetter({ value: raw, row: item, field }) : raw;
4542
+ if (c.valueFormatter) {
4543
+ val = c.valueFormatter({ value: val, row: item, field });
4544
+ }
4545
+ const str = val === null || val === void 0 ? "" : String(val).replace(/"/g, '""');
4546
+ return `"${str}"`;
4498
4547
  }).join(",")
4499
4548
  );
4500
4549
  const csv = [headers, ...rows].join("\n");
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rufous/ui",
3
3
  "private": false,
4
- "version": "0.2.95",
4
+ "version": "0.2.97",
5
5
  "type": "module",
6
6
  "description": "Experimental: A lightweight React UI component library (Beta)",
7
7
  "style": "./dist/main.css",