@diwauhris/ui 1.7.26 → 1.7.28

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/assets/ui.css CHANGED
@@ -4442,6 +4442,12 @@ body {
4442
4442
  animation: datepickerIn 0.2s cubic-bezier(0.16, 1, 0.3, 1);
4443
4443
  overflow: hidden;
4444
4444
  }
4445
+ /* Hidden during initial measurement — no animation until final position is set */
4446
+ .pis-datepicker-popover--measuring {
4447
+ opacity: 0;
4448
+ pointer-events: none;
4449
+ animation: none;
4450
+ }
4445
4451
  @keyframes datepickerIn {
4446
4452
  from { opacity: 0; transform: translateY(6px) scale(0.97); }
4447
4453
  to { opacity: 1; transform: translateY(0) scale(1); }
package/lib/index.cjs CHANGED
@@ -1408,16 +1408,20 @@ function computePopupStyle(rect, placement = "auto", measuredHeight) {
1408
1408
  left: isNarrow ? 8 : Math.max(8, Math.min(rect.left, window.innerWidth - POPOVER_WIDTH - 8)),
1409
1409
  width: isNarrow ? `calc(100vw - 16px)` : void 0
1410
1410
  };
1411
- if (placement === "below" ? true : placement === "above" ? false : spaceBelow >= height || spaceBelow >= spaceAbove) style.top = Math.min(rect.bottom + 6, window.innerHeight - height - 8);
1411
+ const placeBelow = placement === "below" ? true : placement === "above" ? false : spaceBelow >= height || spaceBelow >= spaceAbove;
1412
+ if (placeBelow) style.top = Math.min(rect.bottom + 6, window.innerHeight - height - 8);
1412
1413
  else style.top = Math.max(rect.top - height - 6, 8);
1413
- return style;
1414
+ return {
1415
+ style,
1416
+ placedAbove: !placeBelow
1417
+ };
1414
1418
  }
1415
1419
  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
1420
  const isControlled = open !== void 0;
1417
1421
  const [uncontrolledOpen, setUncontrolledOpen] = (0, react.useState)(defaultOpen);
1418
1422
  const isOpen = isControlled ? open : uncontrolledOpen;
1419
1423
  const [popupStyle, setPopupStyle] = (0, react.useState)({});
1420
- const [positioned, setPositioned] = (0, react.useState)(false);
1424
+ const placedAbove = (0, react.useRef)(false);
1421
1425
  const wrapperRef = (0, react.useRef)(null);
1422
1426
  const popoverRef = (0, react.useRef)(null);
1423
1427
  const inputRef = (0, react.useRef)(null);
@@ -1455,9 +1459,8 @@ function DatePicker({ value, onChange, placeholder, className, disabled, error,
1455
1459
  if (!wrapperRef.current) return;
1456
1460
  requestAnimationFrame(() => {
1457
1461
  if (!wrapperRef.current) return;
1458
- const style = computePopupStyle(wrapperRef.current.getBoundingClientRect(), placement);
1459
- setPopupStyle(style);
1460
- setPositioned(false);
1462
+ const result = computePopupStyle(wrapperRef.current.getBoundingClientRect(), placement);
1463
+ placedAbove.current = result.placedAbove;
1461
1464
  setIsOpen(true);
1462
1465
  });
1463
1466
  }, 50);
@@ -1467,52 +1470,71 @@ function DatePicker({ value, onChange, placeholder, className, disabled, error,
1467
1470
  placement,
1468
1471
  setIsOpen
1469
1472
  ]);
1470
- (0, react.useLayoutEffect)(() => {
1473
+ const [popoverReady, setPopoverReady] = (0, react.useState)(false);
1474
+ (0, react.useEffect)(() => {
1471
1475
  if (!isOpen) {
1472
- setPositioned(false);
1476
+ setPopoverReady(false);
1477
+ setPopupStyle({});
1473
1478
  return;
1474
1479
  }
1475
- if (!popoverRef.current || !wrapperRef.current) return;
1476
- const measuredHeight = popoverRef.current.offsetHeight;
1477
- const rect = wrapperRef.current.getBoundingClientRect();
1478
- setPopupStyle(computePopupStyle(rect, placement, measuredHeight));
1479
- setPositioned(true);
1480
- }, [isOpen, placement]);
1480
+ const frameId = requestAnimationFrame(() => {
1481
+ if (!popoverRef.current || !wrapperRef.current) return;
1482
+ const measuredHeight = popoverRef.current.offsetHeight;
1483
+ const result = computePopupStyle(wrapperRef.current.getBoundingClientRect(), placedAbove.current ? "above" : "below", measuredHeight);
1484
+ setPopupStyle(result.style);
1485
+ setPopoverReady(true);
1486
+ });
1487
+ return () => cancelAnimationFrame(frameId);
1488
+ }, [isOpen]);
1481
1489
  (0, react.useEffect)(() => {
1490
+ if (!isOpen) return;
1482
1491
  function handleClickOutside(e) {
1483
1492
  const target = e.target;
1484
- if (wrapperRef.current && !wrapperRef.current.contains(target) && popoverRef.current && !popoverRef.current.contains(target)) setIsOpen(false);
1493
+ if (!popoverRef.current) return;
1494
+ const inWrapper = wrapperRef.current?.contains(target);
1495
+ const inPopover = popoverRef.current.contains(target);
1496
+ if (!inWrapper && !inPopover) setIsOpen(false);
1485
1497
  }
1486
1498
  document.addEventListener("mousedown", handleClickOutside);
1487
1499
  return () => document.removeEventListener("mousedown", handleClickOutside);
1488
- }, []);
1500
+ }, [isOpen, setIsOpen]);
1489
1501
  (0, react.useEffect)(() => {
1490
1502
  if (!isOpen) return;
1503
+ let armed = false;
1504
+ const armTimer = window.setTimeout(() => {
1505
+ armed = true;
1506
+ }, 100);
1491
1507
  const close = () => setIsOpen(false);
1508
+ const onScroll = () => {
1509
+ if (armed) close();
1510
+ };
1492
1511
  const scrollParent = getScrollParent(wrapperRef.current?.parentElement ?? null);
1493
- if (scrollParent) scrollParent.addEventListener("scroll", close, { passive: true });
1494
- window.addEventListener("scroll", close, {
1512
+ if (scrollParent) scrollParent.addEventListener("scroll", onScroll, { passive: true });
1513
+ window.addEventListener("scroll", onScroll, {
1495
1514
  capture: true,
1496
1515
  passive: true
1497
1516
  });
1498
1517
  window.addEventListener("resize", close);
1499
1518
  return () => {
1500
- if (scrollParent) scrollParent.removeEventListener("scroll", close);
1501
- window.removeEventListener("scroll", close, { capture: true });
1519
+ window.clearTimeout(armTimer);
1520
+ if (scrollParent) scrollParent.removeEventListener("scroll", onScroll);
1521
+ window.removeEventListener("scroll", onScroll, { capture: true });
1502
1522
  window.removeEventListener("resize", close);
1503
1523
  };
1504
- }, [isOpen]);
1524
+ }, [isOpen, setIsOpen]);
1505
1525
  (0, react.useEffect)(() => {
1506
1526
  if (!isOpen) return;
1507
1527
  function handleFocusOut(e) {
1508
1528
  if (!e.relatedTarget) return;
1509
1529
  const target = e.relatedTarget;
1510
- if (wrapperRef.current && !wrapperRef.current.contains(target) && popoverRef.current && !popoverRef.current.contains(target)) setIsOpen(false);
1530
+ const inWrapper = wrapperRef.current?.contains(target);
1531
+ const inPopover = popoverRef.current?.contains(target);
1532
+ if (!inWrapper && !inPopover) setIsOpen(false);
1511
1533
  }
1512
1534
  const wrapper = wrapperRef.current;
1513
1535
  wrapper?.addEventListener("focusout", handleFocusOut);
1514
1536
  return () => wrapper?.removeEventListener("focusout", handleFocusOut);
1515
- }, [isOpen]);
1537
+ }, [isOpen, setIsOpen]);
1516
1538
  function handleKeyDown(e) {
1517
1539
  if (e.key === "Escape") {
1518
1540
  e.preventDefault();
@@ -1578,7 +1600,7 @@ function DatePicker({ value, onChange, placeholder, className, disabled, error,
1578
1600
  className: "pis-datepicker-popover",
1579
1601
  style: {
1580
1602
  ...popupStyle,
1581
- visibility: positioned ? void 0 : "hidden"
1603
+ visibility: popoverReady ? "visible" : "hidden"
1582
1604
  },
1583
1605
  role: "dialog",
1584
1606
  "aria-label": ariaLabel ? `${ariaLabel} calendar` : "Date picker",
package/lib/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { clsx } from "clsx";
2
2
  import { twMerge } from "tailwind-merge";
3
3
  import * as React$1 from "react";
4
- import React, { cloneElement, createContext, createElement, isValidElement, use, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
4
+ import React, { cloneElement, createContext, createElement, isValidElement, use, useCallback, useContext, useEffect, useId, useMemo, useRef, useState } from "react";
5
5
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
6
  import { createPortal } from "react-dom";
7
7
  import { AlertCircle, AlertTriangle, Archive, ArchiveRestore, ArrowDown, Ban, CalendarDays, Check, CheckCircle2, ChevronDown, ChevronLeft, ChevronRight, Clock, Command, File, Home, Info, Minus, RotateCcw, Search as Search$1, ShieldCheck, TrendingDown, TrendingUp, UploadCloud, X, ZoomIn, ZoomOut } from "lucide-react";
@@ -1384,16 +1384,20 @@ function computePopupStyle(rect, placement = "auto", measuredHeight) {
1384
1384
  left: isNarrow ? 8 : Math.max(8, Math.min(rect.left, window.innerWidth - POPOVER_WIDTH - 8)),
1385
1385
  width: isNarrow ? `calc(100vw - 16px)` : void 0
1386
1386
  };
1387
- if (placement === "below" ? true : placement === "above" ? false : spaceBelow >= height || spaceBelow >= spaceAbove) style.top = Math.min(rect.bottom + 6, window.innerHeight - height - 8);
1387
+ const placeBelow = placement === "below" ? true : placement === "above" ? false : spaceBelow >= height || spaceBelow >= spaceAbove;
1388
+ if (placeBelow) style.top = Math.min(rect.bottom + 6, window.innerHeight - height - 8);
1388
1389
  else style.top = Math.max(rect.top - height - 6, 8);
1389
- return style;
1390
+ return {
1391
+ style,
1392
+ placedAbove: !placeBelow
1393
+ };
1390
1394
  }
1391
1395
  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
1396
  const isControlled = open !== void 0;
1393
1397
  const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);
1394
1398
  const isOpen = isControlled ? open : uncontrolledOpen;
1395
1399
  const [popupStyle, setPopupStyle] = useState({});
1396
- const [positioned, setPositioned] = useState(false);
1400
+ const placedAbove = useRef(false);
1397
1401
  const wrapperRef = useRef(null);
1398
1402
  const popoverRef = useRef(null);
1399
1403
  const inputRef = useRef(null);
@@ -1431,9 +1435,8 @@ function DatePicker({ value, onChange, placeholder, className, disabled, error,
1431
1435
  if (!wrapperRef.current) return;
1432
1436
  requestAnimationFrame(() => {
1433
1437
  if (!wrapperRef.current) return;
1434
- const style = computePopupStyle(wrapperRef.current.getBoundingClientRect(), placement);
1435
- setPopupStyle(style);
1436
- setPositioned(false);
1438
+ const result = computePopupStyle(wrapperRef.current.getBoundingClientRect(), placement);
1439
+ placedAbove.current = result.placedAbove;
1437
1440
  setIsOpen(true);
1438
1441
  });
1439
1442
  }, 50);
@@ -1443,52 +1446,71 @@ function DatePicker({ value, onChange, placeholder, className, disabled, error,
1443
1446
  placement,
1444
1447
  setIsOpen
1445
1448
  ]);
1446
- useLayoutEffect(() => {
1449
+ const [popoverReady, setPopoverReady] = useState(false);
1450
+ useEffect(() => {
1447
1451
  if (!isOpen) {
1448
- setPositioned(false);
1452
+ setPopoverReady(false);
1453
+ setPopupStyle({});
1449
1454
  return;
1450
1455
  }
1451
- if (!popoverRef.current || !wrapperRef.current) return;
1452
- const measuredHeight = popoverRef.current.offsetHeight;
1453
- const rect = wrapperRef.current.getBoundingClientRect();
1454
- setPopupStyle(computePopupStyle(rect, placement, measuredHeight));
1455
- setPositioned(true);
1456
- }, [isOpen, placement]);
1456
+ const frameId = requestAnimationFrame(() => {
1457
+ if (!popoverRef.current || !wrapperRef.current) return;
1458
+ const measuredHeight = popoverRef.current.offsetHeight;
1459
+ const result = computePopupStyle(wrapperRef.current.getBoundingClientRect(), placedAbove.current ? "above" : "below", measuredHeight);
1460
+ setPopupStyle(result.style);
1461
+ setPopoverReady(true);
1462
+ });
1463
+ return () => cancelAnimationFrame(frameId);
1464
+ }, [isOpen]);
1457
1465
  useEffect(() => {
1466
+ if (!isOpen) return;
1458
1467
  function handleClickOutside(e) {
1459
1468
  const target = e.target;
1460
- if (wrapperRef.current && !wrapperRef.current.contains(target) && popoverRef.current && !popoverRef.current.contains(target)) setIsOpen(false);
1469
+ if (!popoverRef.current) return;
1470
+ const inWrapper = wrapperRef.current?.contains(target);
1471
+ const inPopover = popoverRef.current.contains(target);
1472
+ if (!inWrapper && !inPopover) setIsOpen(false);
1461
1473
  }
1462
1474
  document.addEventListener("mousedown", handleClickOutside);
1463
1475
  return () => document.removeEventListener("mousedown", handleClickOutside);
1464
- }, []);
1476
+ }, [isOpen, setIsOpen]);
1465
1477
  useEffect(() => {
1466
1478
  if (!isOpen) return;
1479
+ let armed = false;
1480
+ const armTimer = window.setTimeout(() => {
1481
+ armed = true;
1482
+ }, 100);
1467
1483
  const close = () => setIsOpen(false);
1484
+ const onScroll = () => {
1485
+ if (armed) close();
1486
+ };
1468
1487
  const scrollParent = getScrollParent(wrapperRef.current?.parentElement ?? null);
1469
- if (scrollParent) scrollParent.addEventListener("scroll", close, { passive: true });
1470
- window.addEventListener("scroll", close, {
1488
+ if (scrollParent) scrollParent.addEventListener("scroll", onScroll, { passive: true });
1489
+ window.addEventListener("scroll", onScroll, {
1471
1490
  capture: true,
1472
1491
  passive: true
1473
1492
  });
1474
1493
  window.addEventListener("resize", close);
1475
1494
  return () => {
1476
- if (scrollParent) scrollParent.removeEventListener("scroll", close);
1477
- window.removeEventListener("scroll", close, { capture: true });
1495
+ window.clearTimeout(armTimer);
1496
+ if (scrollParent) scrollParent.removeEventListener("scroll", onScroll);
1497
+ window.removeEventListener("scroll", onScroll, { capture: true });
1478
1498
  window.removeEventListener("resize", close);
1479
1499
  };
1480
- }, [isOpen]);
1500
+ }, [isOpen, setIsOpen]);
1481
1501
  useEffect(() => {
1482
1502
  if (!isOpen) return;
1483
1503
  function handleFocusOut(e) {
1484
1504
  if (!e.relatedTarget) return;
1485
1505
  const target = e.relatedTarget;
1486
- if (wrapperRef.current && !wrapperRef.current.contains(target) && popoverRef.current && !popoverRef.current.contains(target)) setIsOpen(false);
1506
+ const inWrapper = wrapperRef.current?.contains(target);
1507
+ const inPopover = popoverRef.current?.contains(target);
1508
+ if (!inWrapper && !inPopover) setIsOpen(false);
1487
1509
  }
1488
1510
  const wrapper = wrapperRef.current;
1489
1511
  wrapper?.addEventListener("focusout", handleFocusOut);
1490
1512
  return () => wrapper?.removeEventListener("focusout", handleFocusOut);
1491
- }, [isOpen]);
1513
+ }, [isOpen, setIsOpen]);
1492
1514
  function handleKeyDown(e) {
1493
1515
  if (e.key === "Escape") {
1494
1516
  e.preventDefault();
@@ -1554,7 +1576,7 @@ function DatePicker({ value, onChange, placeholder, className, disabled, error,
1554
1576
  className: "pis-datepicker-popover",
1555
1577
  style: {
1556
1578
  ...popupStyle,
1557
- visibility: positioned ? void 0 : "hidden"
1579
+ visibility: popoverReady ? "visible" : "hidden"
1558
1580
  },
1559
1581
  role: "dialog",
1560
1582
  "aria-label": ariaLabel ? `${ariaLabel} calendar` : "Date picker",
@@ -53,6 +53,9 @@ interface DatePickerProps {
53
53
  /** Initial open state when uncontrolled. Ignored when `open` is provided. */
54
54
  defaultOpen?: boolean;
55
55
  }
56
- export declare function computePopupStyle(rect: DOMRect, placement?: 'auto' | 'below' | 'above', measuredHeight?: number): React.CSSProperties;
56
+ export declare function computePopupStyle(rect: DOMRect, placement?: 'auto' | 'below' | 'above', measuredHeight?: number): {
57
+ style: React.CSSProperties;
58
+ placedAbove: boolean;
59
+ };
57
60
  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;
58
61
  export {};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@diwauhris/ui",
3
3
  "private": false,
4
- "version": "1.7.26",
4
+ "version": "1.7.28",
5
5
  "type": "module",
6
6
  "description": "DIWA UHRIS UI component library — React + Tailwind CSS",
7
7
  "keywords": [