infinity-ui-elements 1.14.3 → 1.14.5
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/components/BottomSheet/BottomSheet.d.ts.map +1 -1
- package/dist/components/SearchableDropdown/SearchableDropdown.d.ts.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.esm.js +78 -23
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +78 -23
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1566,32 +1566,55 @@ const BottomSheet = React__namespace.forwardRef(({ isOpen, onClose, title, descr
|
|
|
1566
1566
|
document.body.style.right = "0";
|
|
1567
1567
|
document.body.style.width = "100%";
|
|
1568
1568
|
}
|
|
1569
|
+
// Track touch position for scroll boundary detection
|
|
1570
|
+
let lastTouchY = 0;
|
|
1571
|
+
const handleTouchStartForScroll = (e) => {
|
|
1572
|
+
lastTouchY = e.touches[0].clientY;
|
|
1573
|
+
};
|
|
1569
1574
|
// Prevent touchmove on overlay to stop iOS rubber-banding
|
|
1575
|
+
// But always allow scrolling inside body content
|
|
1570
1576
|
const preventTouchMove = (e) => {
|
|
1571
1577
|
const target = e.target;
|
|
1572
|
-
|
|
1578
|
+
const currentTouchY = e.touches[0].clientY;
|
|
1579
|
+
const touchDelta = currentTouchY - lastTouchY;
|
|
1580
|
+
lastTouchY = currentTouchY;
|
|
1581
|
+
// Always allow scrolling inside the body content
|
|
1573
1582
|
if (bodyRef.current?.contains(target)) {
|
|
1574
1583
|
const scrollTop = bodyRef.current.scrollTop;
|
|
1575
1584
|
const scrollHeight = bodyRef.current.scrollHeight;
|
|
1576
1585
|
const clientHeight = bodyRef.current.clientHeight;
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
//
|
|
1584
|
-
|
|
1586
|
+
const canScroll = scrollHeight > clientHeight;
|
|
1587
|
+
// If content is scrollable, allow normal scrolling
|
|
1588
|
+
if (canScroll) {
|
|
1589
|
+
const isAtTop = scrollTop <= 0;
|
|
1590
|
+
const isAtBottom = scrollTop + clientHeight >= scrollHeight - 1;
|
|
1591
|
+
const isScrollingDown = touchDelta > 0; // finger moving down = scrolling up
|
|
1592
|
+
const isScrollingUp = touchDelta < 0; // finger moving up = scrolling down
|
|
1593
|
+
// Only prevent at boundaries when trying to scroll beyond
|
|
1594
|
+
if ((isAtTop && isScrollingDown) || (isAtBottom && isScrollingUp)) {
|
|
1595
|
+
// At boundary, prevent overscroll but allow swipe-to-close from drag handle
|
|
1596
|
+
if (isDraggingFromHandle.current) {
|
|
1597
|
+
return; // Allow swipe gesture
|
|
1598
|
+
}
|
|
1585
1599
|
e.preventDefault();
|
|
1586
1600
|
}
|
|
1601
|
+
// Otherwise, allow normal scrolling
|
|
1602
|
+
return;
|
|
1587
1603
|
}
|
|
1604
|
+
// Content not scrollable, allow touch events to pass through
|
|
1605
|
+
return;
|
|
1606
|
+
}
|
|
1607
|
+
// Also allow scrolling in sticky content area
|
|
1608
|
+
if (target.closest('[data-sticky-content]')) {
|
|
1588
1609
|
return;
|
|
1589
1610
|
}
|
|
1590
|
-
// Prevent scroll on overlay and other areas
|
|
1611
|
+
// Prevent scroll on overlay and other areas (to prevent background scroll)
|
|
1591
1612
|
e.preventDefault();
|
|
1592
1613
|
};
|
|
1614
|
+
document.addEventListener("touchstart", handleTouchStartForScroll, { passive: true });
|
|
1593
1615
|
document.addEventListener("touchmove", preventTouchMove, { passive: false });
|
|
1594
1616
|
return () => {
|
|
1617
|
+
document.removeEventListener("touchstart", handleTouchStartForScroll);
|
|
1595
1618
|
document.removeEventListener("touchmove", preventTouchMove);
|
|
1596
1619
|
// Restore original styles
|
|
1597
1620
|
if (originalBodyStyles.current) {
|
|
@@ -1608,11 +1631,24 @@ const BottomSheet = React__namespace.forwardRef(({ isOpen, onClose, title, descr
|
|
|
1608
1631
|
}
|
|
1609
1632
|
};
|
|
1610
1633
|
}, [isOpen]);
|
|
1611
|
-
// Reset closing state when opening
|
|
1634
|
+
// Reset closing state and scroll position when opening
|
|
1612
1635
|
React__namespace.useEffect(() => {
|
|
1613
1636
|
if (isOpen) {
|
|
1614
1637
|
setIsClosing(false);
|
|
1615
1638
|
setKeyboardHeight(0);
|
|
1639
|
+
// Reset scroll to top when opening - use multiple timeouts for iOS reliability
|
|
1640
|
+
const resetScroll = () => {
|
|
1641
|
+
if (bodyRef.current) {
|
|
1642
|
+
bodyRef.current.scrollTop = 0;
|
|
1643
|
+
}
|
|
1644
|
+
};
|
|
1645
|
+
// Immediate reset
|
|
1646
|
+
resetScroll();
|
|
1647
|
+
// After animation starts
|
|
1648
|
+
requestAnimationFrame(resetScroll);
|
|
1649
|
+
// After animation completes
|
|
1650
|
+
const timer = setTimeout(resetScroll, 350);
|
|
1651
|
+
return () => clearTimeout(timer);
|
|
1616
1652
|
}
|
|
1617
1653
|
}, [isOpen]);
|
|
1618
1654
|
// Scroll focused input into view when keyboard opens
|
|
@@ -1650,16 +1686,25 @@ const BottomSheet = React__namespace.forwardRef(({ isOpen, onClose, title, descr
|
|
|
1650
1686
|
return () => document.removeEventListener("focusin", handleFocusIn);
|
|
1651
1687
|
}, [isOpen, scrollToFocusedInput, adjustForKeyboard, viewportHeight, keyboardHeight]);
|
|
1652
1688
|
// Reset scroll to top when content changes (for filtered results)
|
|
1689
|
+
// This ensures empty states and filtered results are always visible
|
|
1653
1690
|
React__namespace.useEffect(() => {
|
|
1654
1691
|
if (!isOpen || !bodyRef.current)
|
|
1655
1692
|
return;
|
|
1656
1693
|
// Use requestAnimationFrame to ensure DOM has updated
|
|
1657
1694
|
requestAnimationFrame(() => {
|
|
1658
1695
|
if (bodyRef.current) {
|
|
1696
|
+
// Always scroll to top to show content (including empty states)
|
|
1659
1697
|
bodyRef.current.scrollTop = 0;
|
|
1698
|
+
// If keyboard is open, ensure content is in visible area
|
|
1699
|
+
if (keyboardHeight > 0) {
|
|
1700
|
+
// Force a re-render of the scroll position
|
|
1701
|
+
bodyRef.current.style.scrollBehavior = 'auto';
|
|
1702
|
+
bodyRef.current.scrollTop = 0;
|
|
1703
|
+
bodyRef.current.style.scrollBehavior = '';
|
|
1704
|
+
}
|
|
1660
1705
|
}
|
|
1661
1706
|
});
|
|
1662
|
-
}, [children, isOpen]);
|
|
1707
|
+
}, [children, isOpen, keyboardHeight]);
|
|
1663
1708
|
// Handle close with animation
|
|
1664
1709
|
const handleClose = React__namespace.useCallback(() => {
|
|
1665
1710
|
if (!onClose)
|
|
@@ -1758,21 +1803,28 @@ const BottomSheet = React__namespace.forwardRef(({ isOpen, onClose, title, descr
|
|
|
1758
1803
|
return null;
|
|
1759
1804
|
const hasHeader = title || description;
|
|
1760
1805
|
// Calculate dynamic max height based on keyboard
|
|
1806
|
+
// When keyboard is open, limit height to fit in visible viewport above keyboard
|
|
1761
1807
|
const dynamicMaxHeight = keyboardHeight > 0
|
|
1762
|
-
?
|
|
1808
|
+
? `${Math.min(viewportHeight - 20, viewportHeight * 0.9)}px`
|
|
1763
1809
|
: maxHeight;
|
|
1764
|
-
return (jsxRuntime.jsxs("div", { className: cn("fixed
|
|
1765
|
-
//
|
|
1766
|
-
|
|
1767
|
-
//
|
|
1810
|
+
return (jsxRuntime.jsxs("div", { className: cn("fixed z-9999 flex items-end justify-center",
|
|
1811
|
+
// Always position at bottom, container height adjusts for keyboard
|
|
1812
|
+
className), style: {
|
|
1813
|
+
// Position container to fill visible viewport (above keyboard)
|
|
1814
|
+
top: 0,
|
|
1815
|
+
left: 0,
|
|
1816
|
+
right: 0,
|
|
1817
|
+
// Use visual viewport height when keyboard is open to stay above keyboard
|
|
1768
1818
|
height: keyboardHeight > 0 ? `${viewportHeight}px` : "100%",
|
|
1819
|
+
// On iOS, also set bottom to ensure proper positioning
|
|
1820
|
+
...(keyboardHeight === 0 && { bottom: 0 }),
|
|
1769
1821
|
}, role: "dialog", "aria-modal": "true", "aria-label": ariaLabel || title, "aria-describedby": ariaDescribedBy, children: [jsxRuntime.jsx("div", { ref: overlayRef, className: cn("absolute inset-0 z-0 bg-black/50 backdrop-blur-sm transition-opacity duration-300", isClosing ? "opacity-0" : "opacity-100", overlayClassName), onClick: handleOverlayClick, "aria-hidden": "true" }), jsxRuntime.jsxs("div", { ref: contentRef, className: cn("relative z-10 w-full transition-transform duration-300", "flex flex-col overflow-hidden", variant === "default" && "bg-white rounded-t-2xl shadow-xl", isClosing ? "animate-slide-out-bottom" : "animate-slide-in-bottom",
|
|
1770
|
-
//
|
|
1771
|
-
|
|
1822
|
+
// Always position at bottom of container
|
|
1823
|
+
"mt-auto", contentClassName), style: {
|
|
1772
1824
|
maxHeight: dynamicMaxHeight,
|
|
1773
1825
|
// Add safe area padding at the bottom for devices with home indicator
|
|
1774
1826
|
paddingBottom: footer ? 0 : `max(env(safe-area-inset-bottom, 0px), ${safeAreaBottom}px)`,
|
|
1775
|
-
}, onTouchStart: handleTouchStart, onTouchMove: handleTouchMove, onTouchEnd: handleTouchEnd, children: [showDragHandle && variant === "default" && (jsxRuntime.jsx("div", { ref: dragHandleRef, className: "flex justify-center pt-3 pb-2 cursor-grab active:cursor-grabbing touch-none", children: jsxRuntime.jsx("div", { className: "w-10 h-1 bg-neutral-300 rounded-full" }) })), hasHeader && (jsxRuntime.jsxs("div", { className: cn("flex items-start justify-between gap-4 shrink-0", variant === "default" && "px-6", variant === "default" && !showDragHandle && "pt-6", variant === "default" && showDragHandle && "pt-2", variant === "default" && !description && "pb-4", variant === "default" && description && "pb-2", headerClassName), children: [jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [title && (jsxRuntime.jsx(Text, { as: "h2", variant: "body", size: "large", weight: "semibold", color: "default", children: title })), description && (jsxRuntime.jsx(Text, { as: "p", variant: "body", size: "small", weight: "regular", color: "subtle", className: "mt-1", children: description }))] }), showCloseButton && onClose && (jsxRuntime.jsx(IconButton, { icon: "close", onClick: handleClose, color: "neutral", size: "small", "aria-label": "Close bottom sheet", className: "shrink-0" }))] })), !hasHeader && showCloseButton && onClose && (jsxRuntime.jsx("div", { className: cn("absolute z-10", variant === "default" && "top-4 right-4"), children: jsxRuntime.jsx(IconButton, { icon: "close", onClick: handleClose, color: "neutral", size: "small", "aria-label": "Close bottom sheet" }) })), stickyContent && (jsxRuntime.jsx("div", { className: cn("shrink-0", variant === "default" && "px-6 pb-4", variant === "default" && !hasHeader && !showDragHandle && "pt-4", variant === "default" && !hasHeader && showDragHandle && "pt-2", stickyContentClassName), children: stickyContent })), jsxRuntime.jsx("div", { ref: bodyRef, className: cn("flex-1 overflow-y-auto overscroll-contain",
|
|
1827
|
+
}, onTouchStart: handleTouchStart, onTouchMove: handleTouchMove, onTouchEnd: handleTouchEnd, children: [showDragHandle && variant === "default" && (jsxRuntime.jsx("div", { ref: dragHandleRef, className: "flex justify-center pt-3 pb-2 cursor-grab active:cursor-grabbing touch-none", children: jsxRuntime.jsx("div", { className: "w-10 h-1 bg-neutral-300 rounded-full" }) })), hasHeader && (jsxRuntime.jsxs("div", { className: cn("flex items-start justify-between gap-4 shrink-0", variant === "default" && "px-6", variant === "default" && !showDragHandle && "pt-6", variant === "default" && showDragHandle && "pt-2", variant === "default" && !description && "pb-4", variant === "default" && description && "pb-2", headerClassName), children: [jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [title && (jsxRuntime.jsx(Text, { as: "h2", variant: "body", size: "large", weight: "semibold", color: "default", children: title })), description && (jsxRuntime.jsx(Text, { as: "p", variant: "body", size: "small", weight: "regular", color: "subtle", className: "mt-1", children: description }))] }), showCloseButton && onClose && (jsxRuntime.jsx(IconButton, { icon: "close", onClick: handleClose, color: "neutral", size: "small", "aria-label": "Close bottom sheet", className: "shrink-0" }))] })), !hasHeader && showCloseButton && onClose && (jsxRuntime.jsx("div", { className: cn("absolute z-10", variant === "default" && "top-4 right-4"), children: jsxRuntime.jsx(IconButton, { icon: "close", onClick: handleClose, color: "neutral", size: "small", "aria-label": "Close bottom sheet" }) })), stickyContent && (jsxRuntime.jsx("div", { className: cn("shrink-0", variant === "default" && "px-6 pb-4", variant === "default" && !hasHeader && !showDragHandle && "pt-4", variant === "default" && !hasHeader && showDragHandle && "pt-2", stickyContentClassName), children: stickyContent })), jsxRuntime.jsx("div", { ref: bodyRef, className: cn("flex-1 min-h-0 overflow-y-auto overscroll-contain",
|
|
1776
1828
|
// Smooth scrolling and momentum scroll for iOS
|
|
1777
1829
|
"-webkit-overflow-scrolling-touch", variant === "default" && "px-6", variant === "default" && hasHeader && !stickyContent && "py-4", variant === "default" && hasHeader && stickyContent && "pb-4", variant === "default" && !hasHeader && !showDragHandle && !stickyContent && "pt-6 pb-4", variant === "default" && !hasHeader && showDragHandle && !stickyContent && "pt-2 pb-4", variant === "default" && stickyContent && "pt-0 pb-4", variant === "default" && !footer && "pb-6", bodyClassName), children: children }), footer && (jsxRuntime.jsxs("div", { className: "flex flex-col shrink-0", style: {
|
|
1778
1830
|
// Add safe area padding to footer
|
|
@@ -4885,11 +4937,14 @@ const SearchableDropdown = React__namespace.forwardRef(({ className, items = [],
|
|
|
4885
4937
|
});
|
|
4886
4938
|
const showDropdown = isOpen && searchValue.length >= minSearchLength;
|
|
4887
4939
|
// Render dropdown menu content
|
|
4888
|
-
const renderDropdownContent = () => (jsxRuntime.jsx(DropdownMenu, { items: itemsWithHandlers, sectionHeading: sectionHeading, isLoading: isLoading, isEmpty: itemsWithAddNew.length === 0 && !showAddNew, emptyTitle: emptyTitle, emptyDescription: emptyDescription, emptyLinkText: emptyLinkText, onEmptyLinkClick: onEmptyLinkClick, primaryButtonText: primaryButtonText, secondaryButtonText: secondaryButtonText, onPrimaryClick: onPrimaryClick, onSecondaryClick: onSecondaryClick, showChevron: showChevron, emptyIcon: emptyIcon, disableFooter: disableFooter, showFooter: (primaryButtonText || secondaryButtonText) && !disableFooter
|
|
4940
|
+
const renderDropdownContent = () => (jsxRuntime.jsx(DropdownMenu, { items: itemsWithHandlers, sectionHeading: isMobile ? undefined : sectionHeading, isLoading: isLoading, isEmpty: itemsWithAddNew.length === 0 && !showAddNew, emptyTitle: emptyTitle, emptyDescription: emptyDescription, emptyLinkText: emptyLinkText, onEmptyLinkClick: onEmptyLinkClick, primaryButtonText: primaryButtonText, secondaryButtonText: secondaryButtonText, onPrimaryClick: onPrimaryClick, onSecondaryClick: onSecondaryClick, showChevron: showChevron, emptyIcon: emptyIcon, disableFooter: disableFooter, showFooter: (primaryButtonText || secondaryButtonText) && !disableFooter
|
|
4889
4941
|
? true
|
|
4890
|
-
: false, footerLayout: footerLayout, onClose: () => setIsOpen(false), focusedIndex: focusedIndex, className: dropdownClassName, width: isMobile ? "full" : (dropdownWidth === "full" ? "full" : "auto"),
|
|
4942
|
+
: false, footerLayout: footerLayout, onClose: () => setIsOpen(false), focusedIndex: focusedIndex, className: dropdownClassName, width: isMobile ? "full" : (dropdownWidth === "full" ? "full" : "auto"),
|
|
4943
|
+
// On mobile, let BottomSheet body handle scrolling; on desktop use calculated maxHeight
|
|
4944
|
+
maxHeight: isMobile ? "none" : `${position.maxHeight}px`, unstyled: isMobile }));
|
|
4891
4945
|
// Search field component for mobile BottomSheet
|
|
4892
|
-
|
|
4946
|
+
// Note: No autoFocus - keyboard only appears when user taps the field
|
|
4947
|
+
const mobileSearchField = (jsxRuntime.jsx(TextField, { value: searchValue, onChange: handleSearchChange, onKeyDown: handleKeyDown, containerClassName: "mb-0", placeholder: textFieldProps.placeholder || "Search...", ...textFieldProps }));
|
|
4893
4948
|
// Mobile: BottomSheet, Desktop: Regular Dropdown
|
|
4894
4949
|
const dropdownMenu = showDropdown && (isMobile ? (jsxRuntime.jsx(BottomSheet, { isOpen: isOpen, onClose: () => setIsOpen(false), title: sectionHeading, variant: "default", showDragHandle: true, closeOnOverlayClick: true, closeOnEscape: true, closeOnSwipeDown: true, adjustForKeyboard: true, scrollToFocusedInput: true, maxHeight: "85vh", stickyContent: mobileSearchField, children: renderDropdownContent() })) : (jsxRuntime.jsx("div", { ref: menuRef, style: {
|
|
4895
4950
|
position: "fixed",
|