@diwauhris/ui 1.7.24 → 1.7.25

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/lib/index.cjs CHANGED
@@ -1398,7 +1398,8 @@ function getScrollParent(el) {
1398
1398
  if (["auto", "scroll"].includes(overflowY) || ["auto", "scroll"].includes(overflow)) return el;
1399
1399
  return getScrollParent(el.parentElement);
1400
1400
  }
1401
- function computePopupStyle(rect, placement = "auto") {
1401
+ function computePopupStyle(rect, placement = "auto", measuredHeight) {
1402
+ const height = measuredHeight ?? CALENDAR_HEIGHT;
1402
1403
  const spaceBelow = window.innerHeight - rect.bottom;
1403
1404
  const spaceAbove = rect.top;
1404
1405
  const isNarrow = window.innerWidth < 368;
@@ -1407,23 +1408,29 @@ function computePopupStyle(rect, placement = "auto") {
1407
1408
  left: isNarrow ? 8 : Math.max(8, Math.min(rect.left, window.innerWidth - POPOVER_WIDTH - 8)),
1408
1409
  width: isNarrow ? `calc(100vw - 16px)` : void 0
1409
1410
  };
1410
- if (placement === "below" ? true : placement === "above" ? false : spaceBelow >= CALENDAR_HEIGHT || spaceBelow >= spaceAbove) style.top = Math.min(rect.bottom + 6, window.innerHeight - CALENDAR_HEIGHT - 8);
1411
- else style.top = Math.max(rect.top - CALENDAR_HEIGHT - 6, 8);
1411
+ if (placement === "below" ? true : placement === "above" ? false : spaceBelow >= height || spaceBelow >= spaceAbove) style.top = Math.min(rect.bottom + 6, window.innerHeight - height - 8);
1412
+ else style.top = Math.max(rect.top - height - 6, 8);
1412
1413
  return style;
1413
1414
  }
1414
- function DatePicker({ value, onChange, placeholder, className, disabled, error, minDate, maxDate, id, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, required, dateFormat = "MMM d, yyyy", clearable = false, onOpenChange, placement = "auto", locale, name }) {
1415
- const [isOpen, setIsOpenState] = (0, react.useState)(false);
1415
+ function DatePicker({ value, onChange, placeholder, className, disabled, error, minDate, maxDate, id, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, required, dateFormat = "MMM d, yyyy", clearable = false, onOpenChange, placement = "auto", locale, name, isDateDisabled, readOnly = false, open, defaultOpen = false }) {
1416
+ const isControlled = open !== void 0;
1417
+ const [uncontrolledOpen, setUncontrolledOpen] = (0, react.useState)(defaultOpen);
1418
+ const isOpen = isControlled ? open : uncontrolledOpen;
1416
1419
  const [popupStyle, setPopupStyle] = (0, react.useState)({});
1417
1420
  const wrapperRef = (0, react.useRef)(null);
1418
1421
  const popoverRef = (0, react.useRef)(null);
1419
1422
  const inputRef = (0, react.useRef)(null);
1420
1423
  const internalId = (0, react.useId)();
1421
1424
  const setIsOpen = (0, react.useCallback)((next) => {
1422
- setIsOpenState((prev) => {
1425
+ if (isControlled) {
1426
+ onOpenChange?.(next);
1427
+ return;
1428
+ }
1429
+ setUncontrolledOpen((prev) => {
1423
1430
  if (prev !== next) onOpenChange?.(next);
1424
1431
  return next;
1425
1432
  });
1426
- }, [onOpenChange]);
1433
+ }, [isControlled, onOpenChange]);
1427
1434
  const ctx = useFieldContext();
1428
1435
  const inputId = id ?? ctx?.inputId ?? internalId;
1429
1436
  const resolvedRequired = required ?? ctx?.required ?? false;
@@ -1438,7 +1445,7 @@ function DatePicker({ value, onChange, placeholder, className, disabled, error,
1438
1445
  const displayValue = selectedDate ? (0, date_fns.format)(selectedDate, dateFormat) : "";
1439
1446
  const skipNextOpen = (0, react.useRef)(false);
1440
1447
  const openPopup = (0, react.useCallback)(() => {
1441
- if (disabled) return;
1448
+ if (disabled || readOnly) return;
1442
1449
  if (skipNextOpen.current) {
1443
1450
  skipNextOpen.current = false;
1444
1451
  return;
@@ -1454,9 +1461,16 @@ function DatePicker({ value, onChange, placeholder, className, disabled, error,
1454
1461
  }, 50);
1455
1462
  }, [
1456
1463
  disabled,
1464
+ readOnly,
1457
1465
  placement,
1458
1466
  setIsOpen
1459
1467
  ]);
1468
+ (0, react.useEffect)(() => {
1469
+ if (!isOpen || !popoverRef.current || !wrapperRef.current) return;
1470
+ const measuredHeight = popoverRef.current.offsetHeight;
1471
+ const rect = wrapperRef.current.getBoundingClientRect();
1472
+ setPopupStyle(computePopupStyle(rect, placement, measuredHeight));
1473
+ }, [isOpen, placement]);
1460
1474
  (0, react.useEffect)(() => {
1461
1475
  function handleClickOutside(e) {
1462
1476
  const target = e.target;
@@ -1528,11 +1542,12 @@ function DatePicker({ value, onChange, placeholder, className, disabled, error,
1528
1542
  "aria-required": resolvedRequired ? "true" : void 0,
1529
1543
  "aria-invalid": resolvedInvalid ? "true" : void 0,
1530
1544
  "aria-describedby": describedBy,
1531
- "aria-haspopup": "dialog",
1532
- "aria-expanded": isOpen,
1545
+ "aria-readonly": readOnly ? "true" : void 0,
1546
+ "aria-haspopup": readOnly ? void 0 : "dialog",
1547
+ "aria-expanded": readOnly ? void 0 : isOpen,
1533
1548
  role: "combobox",
1534
1549
  className: inputClasses
1535
- }), clearable && value && !disabled ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1550
+ }), clearable && value && !disabled && !readOnly ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1536
1551
  type: "button",
1537
1552
  "aria-label": "Clear date",
1538
1553
  onMouseDown: (e) => {
@@ -1551,7 +1566,7 @@ function DatePicker({ value, onChange, placeholder, className, disabled, error,
1551
1566
  className: `absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none ${isOpen ? "text-brand-blue" : "text-slate-300"}`,
1552
1567
  "aria-hidden": "true"
1553
1568
  })]
1554
- }), isOpen && !disabled && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1569
+ }), isOpen && !disabled && !readOnly && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1555
1570
  ref: popoverRef,
1556
1571
  className: "pis-datepicker-popover",
1557
1572
  style: popupStyle,
@@ -1571,6 +1586,7 @@ function DatePicker({ value, onChange, placeholder, className, disabled, error,
1571
1586
  defaultActiveStartDate: selectedDate ?? minDate,
1572
1587
  minDate,
1573
1588
  maxDate,
1589
+ tileDisabled: isDateDisabled ? ({ date, view }) => view === "month" && isDateDisabled(date) : void 0,
1574
1590
  locale,
1575
1591
  defaultView: "month",
1576
1592
  showNeighboringDecade: true,
package/lib/index.mjs CHANGED
@@ -1374,7 +1374,8 @@ function getScrollParent(el) {
1374
1374
  if (["auto", "scroll"].includes(overflowY) || ["auto", "scroll"].includes(overflow)) return el;
1375
1375
  return getScrollParent(el.parentElement);
1376
1376
  }
1377
- function computePopupStyle(rect, placement = "auto") {
1377
+ function computePopupStyle(rect, placement = "auto", measuredHeight) {
1378
+ const height = measuredHeight ?? CALENDAR_HEIGHT;
1378
1379
  const spaceBelow = window.innerHeight - rect.bottom;
1379
1380
  const spaceAbove = rect.top;
1380
1381
  const isNarrow = window.innerWidth < 368;
@@ -1383,23 +1384,29 @@ function computePopupStyle(rect, placement = "auto") {
1383
1384
  left: isNarrow ? 8 : Math.max(8, Math.min(rect.left, window.innerWidth - POPOVER_WIDTH - 8)),
1384
1385
  width: isNarrow ? `calc(100vw - 16px)` : void 0
1385
1386
  };
1386
- if (placement === "below" ? true : placement === "above" ? false : spaceBelow >= CALENDAR_HEIGHT || spaceBelow >= spaceAbove) style.top = Math.min(rect.bottom + 6, window.innerHeight - CALENDAR_HEIGHT - 8);
1387
- else style.top = Math.max(rect.top - CALENDAR_HEIGHT - 6, 8);
1387
+ if (placement === "below" ? true : placement === "above" ? false : spaceBelow >= height || spaceBelow >= spaceAbove) style.top = Math.min(rect.bottom + 6, window.innerHeight - height - 8);
1388
+ else style.top = Math.max(rect.top - height - 6, 8);
1388
1389
  return style;
1389
1390
  }
1390
- function DatePicker({ value, onChange, placeholder, className, disabled, error, minDate, maxDate, id, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, required, dateFormat = "MMM d, yyyy", clearable = false, onOpenChange, placement = "auto", locale, name }) {
1391
- const [isOpen, setIsOpenState] = useState(false);
1391
+ function DatePicker({ value, onChange, placeholder, className, disabled, error, minDate, maxDate, id, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, required, dateFormat = "MMM d, yyyy", clearable = false, onOpenChange, placement = "auto", locale, name, isDateDisabled, readOnly = false, open, defaultOpen = false }) {
1392
+ const isControlled = open !== void 0;
1393
+ const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);
1394
+ const isOpen = isControlled ? open : uncontrolledOpen;
1392
1395
  const [popupStyle, setPopupStyle] = useState({});
1393
1396
  const wrapperRef = useRef(null);
1394
1397
  const popoverRef = useRef(null);
1395
1398
  const inputRef = useRef(null);
1396
1399
  const internalId = useId();
1397
1400
  const setIsOpen = useCallback((next) => {
1398
- setIsOpenState((prev) => {
1401
+ if (isControlled) {
1402
+ onOpenChange?.(next);
1403
+ return;
1404
+ }
1405
+ setUncontrolledOpen((prev) => {
1399
1406
  if (prev !== next) onOpenChange?.(next);
1400
1407
  return next;
1401
1408
  });
1402
- }, [onOpenChange]);
1409
+ }, [isControlled, onOpenChange]);
1403
1410
  const ctx = useFieldContext();
1404
1411
  const inputId = id ?? ctx?.inputId ?? internalId;
1405
1412
  const resolvedRequired = required ?? ctx?.required ?? false;
@@ -1414,7 +1421,7 @@ function DatePicker({ value, onChange, placeholder, className, disabled, error,
1414
1421
  const displayValue = selectedDate ? format(selectedDate, dateFormat) : "";
1415
1422
  const skipNextOpen = useRef(false);
1416
1423
  const openPopup = useCallback(() => {
1417
- if (disabled) return;
1424
+ if (disabled || readOnly) return;
1418
1425
  if (skipNextOpen.current) {
1419
1426
  skipNextOpen.current = false;
1420
1427
  return;
@@ -1430,9 +1437,16 @@ function DatePicker({ value, onChange, placeholder, className, disabled, error,
1430
1437
  }, 50);
1431
1438
  }, [
1432
1439
  disabled,
1440
+ readOnly,
1433
1441
  placement,
1434
1442
  setIsOpen
1435
1443
  ]);
1444
+ useEffect(() => {
1445
+ if (!isOpen || !popoverRef.current || !wrapperRef.current) return;
1446
+ const measuredHeight = popoverRef.current.offsetHeight;
1447
+ const rect = wrapperRef.current.getBoundingClientRect();
1448
+ setPopupStyle(computePopupStyle(rect, placement, measuredHeight));
1449
+ }, [isOpen, placement]);
1436
1450
  useEffect(() => {
1437
1451
  function handleClickOutside(e) {
1438
1452
  const target = e.target;
@@ -1504,11 +1518,12 @@ function DatePicker({ value, onChange, placeholder, className, disabled, error,
1504
1518
  "aria-required": resolvedRequired ? "true" : void 0,
1505
1519
  "aria-invalid": resolvedInvalid ? "true" : void 0,
1506
1520
  "aria-describedby": describedBy,
1507
- "aria-haspopup": "dialog",
1508
- "aria-expanded": isOpen,
1521
+ "aria-readonly": readOnly ? "true" : void 0,
1522
+ "aria-haspopup": readOnly ? void 0 : "dialog",
1523
+ "aria-expanded": readOnly ? void 0 : isOpen,
1509
1524
  role: "combobox",
1510
1525
  className: inputClasses
1511
- }), clearable && value && !disabled ? /* @__PURE__ */ jsx("button", {
1526
+ }), clearable && value && !disabled && !readOnly ? /* @__PURE__ */ jsx("button", {
1512
1527
  type: "button",
1513
1528
  "aria-label": "Clear date",
1514
1529
  onMouseDown: (e) => {
@@ -1527,7 +1542,7 @@ function DatePicker({ value, onChange, placeholder, className, disabled, error,
1527
1542
  className: `absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none ${isOpen ? "text-brand-blue" : "text-slate-300"}`,
1528
1543
  "aria-hidden": "true"
1529
1544
  })]
1530
- }), isOpen && !disabled && createPortal(/* @__PURE__ */ jsx("div", {
1545
+ }), isOpen && !disabled && !readOnly && createPortal(/* @__PURE__ */ jsx("div", {
1531
1546
  ref: popoverRef,
1532
1547
  className: "pis-datepicker-popover",
1533
1548
  style: popupStyle,
@@ -1547,6 +1562,7 @@ function DatePicker({ value, onChange, placeholder, className, disabled, error,
1547
1562
  defaultActiveStartDate: selectedDate ?? minDate,
1548
1563
  minDate,
1549
1564
  maxDate,
1565
+ tileDisabled: isDateDisabled ? ({ date, view }) => view === "month" && isDateDisabled(date) : void 0,
1550
1566
  locale,
1551
1567
  defaultView: "month",
1552
1568
  showNeighboringDecade: true,
@@ -32,7 +32,27 @@ interface DatePickerProps {
32
32
  locale?: string;
33
33
  /** Name forwarded to the trigger input for form association. */
34
34
  name?: string;
35
+ /**
36
+ * Per-day predicate to disable individual dates that min/maxDate can't express
37
+ * (weekends, holidays, already-booked days). Return true to disable the date.
38
+ * Only consulted on the day (month) view.
39
+ */
40
+ isDateDisabled?: (date: Date) => boolean;
41
+ /**
42
+ * Read-only: the value is shown but the calendar can't be opened and no date
43
+ * can be picked. Unlike `disabled`, it keeps normal (non-greyed) styling for
44
+ * view/detail screens.
45
+ */
46
+ readOnly?: boolean;
47
+ /**
48
+ * Controlled open state. When provided, the component no longer manages its
49
+ * own open/close — the parent owns it via `onOpenChange`. Leave undefined for
50
+ * the default uncontrolled behavior.
51
+ */
52
+ open?: boolean;
53
+ /** Initial open state when uncontrolled. Ignored when `open` is provided. */
54
+ defaultOpen?: boolean;
35
55
  }
36
- export declare function computePopupStyle(rect: DOMRect, placement?: 'auto' | 'below' | 'above'): React.CSSProperties;
37
- export declare function DatePicker({ value, onChange, placeholder, className, disabled, error, minDate, maxDate, id, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, required, dateFormat, clearable, onOpenChange, placement, locale, name, }: DatePickerProps): import("react").JSX.Element;
56
+ export declare function computePopupStyle(rect: DOMRect, placement?: 'auto' | 'below' | 'above', measuredHeight?: number): React.CSSProperties;
57
+ export declare function DatePicker({ value, onChange, placeholder, className, disabled, error, minDate, maxDate, id, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, required, dateFormat, clearable, onOpenChange, placement, locale, name, isDateDisabled, readOnly, open, defaultOpen, }: DatePickerProps): import("react").JSX.Element;
38
58
  export {};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@diwauhris/ui",
3
3
  "private": false,
4
- "version": "1.7.24",
4
+ "version": "1.7.25",
5
5
  "type": "module",
6
6
  "description": "DIWA UHRIS UI component library — React + Tailwind CSS",
7
7
  "keywords": [