@papernote/ui 2.0.0 → 2.0.1
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/Select.d.ts +3 -3
- package/dist/components/Select.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.esm.js +156 -112
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +156 -112
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/Select.tsx +367 -241
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from
|
|
1
|
+
import React from "react";
|
|
2
2
|
/**
|
|
3
3
|
* Single option in a select dropdown
|
|
4
4
|
*/
|
|
@@ -73,9 +73,9 @@ export interface SelectProps {
|
|
|
73
73
|
/** Height of each option row in pixels (default: 42) */
|
|
74
74
|
virtualItemHeight?: number;
|
|
75
75
|
/** Size of the select trigger - 'lg' provides 44px touch-friendly target */
|
|
76
|
-
size?:
|
|
76
|
+
size?: "sm" | "md" | "lg";
|
|
77
77
|
/** Mobile display mode - 'auto' uses BottomSheet on mobile, 'dropdown' always uses dropdown, 'native' uses native select on mobile */
|
|
78
|
-
mobileMode?:
|
|
78
|
+
mobileMode?: "auto" | "dropdown" | "native";
|
|
79
79
|
/** Render dropdown via portal (default: true). Set to false when overflow clipping is not an issue */
|
|
80
80
|
usePortal?: boolean;
|
|
81
81
|
/** Whether this field is required */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Select.d.ts","sourceRoot":"","sources":["../../src/components/Select.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"Select.d.ts","sourceRoot":"","sources":["../../src/components/Select.tsx"],"names":[],"mappings":"AAAA,OAAO,KAON,MAAM,OAAO,CAAC;AAKf;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,oCAAoC;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,uCAAuC;IACvC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,4CAA4C;IAC5C,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,yBAAyB;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,4BAA4B;IAC5B,OAAO,EAAE,YAAY,EAAE,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,8BAA8B;IAC9B,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,6BAA6B;IAC7B,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,wBAAwB;IACxB,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,yBAAyB;IACzB,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,2BAA2B;IAC3B,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;IACzB,2CAA2C;IAC3C,MAAM,CAAC,EAAE,iBAAiB,EAAE,CAAC;IAC7B,+BAA+B;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,+CAA+C;IAC/C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yCAAyC;IACzC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,yBAAyB;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,8BAA8B;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+BAA+B;IAC/B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oDAAoD;IACpD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2BAA2B;IAC3B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,4DAA4D;IAC5D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,0CAA0C;IAC1C,cAAc,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,mEAAmE;IACnE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,6DAA6D;IAC7D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,wDAAwD;IACxD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,4EAA4E;IAC5E,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1B,sIAAsI;IACtI,UAAU,CAAC,EAAE,MAAM,GAAG,UAAU,GAAG,QAAQ,CAAC;IAC5C,sGAAsG;IACtG,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,qCAAqC;IACrC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAgBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqEG;AACH,QAAA,MAAM,MAAM,kFAizBV,CAAC;AAGH,eAAe,MAAM,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -359,9 +359,9 @@ interface SelectProps {
|
|
|
359
359
|
/** Height of each option row in pixels (default: 42) */
|
|
360
360
|
virtualItemHeight?: number;
|
|
361
361
|
/** Size of the select trigger - 'lg' provides 44px touch-friendly target */
|
|
362
|
-
size?:
|
|
362
|
+
size?: "sm" | "md" | "lg";
|
|
363
363
|
/** Mobile display mode - 'auto' uses BottomSheet on mobile, 'dropdown' always uses dropdown, 'native' uses native select on mobile */
|
|
364
|
-
mobileMode?:
|
|
364
|
+
mobileMode?: "auto" | "dropdown" | "native";
|
|
365
365
|
/** Render dropdown via portal (default: true). Set to false when overflow clipping is not an issue */
|
|
366
366
|
usePortal?: boolean;
|
|
367
367
|
/** Whether this field is required */
|
package/dist/index.esm.js
CHANGED
|
@@ -705,15 +705,15 @@ function usePrefersMobile() {
|
|
|
705
705
|
|
|
706
706
|
// Size classes for trigger button
|
|
707
707
|
const sizeClasses$d = {
|
|
708
|
-
sm:
|
|
709
|
-
md:
|
|
710
|
-
lg:
|
|
708
|
+
sm: "h-8 text-sm py-1",
|
|
709
|
+
md: "h-10 text-base py-2",
|
|
710
|
+
lg: "h-12 text-base py-3 min-h-touch", // 44px touch target
|
|
711
711
|
};
|
|
712
712
|
// Size classes for options
|
|
713
713
|
const optionSizeClasses = {
|
|
714
|
-
sm:
|
|
715
|
-
md:
|
|
716
|
-
lg:
|
|
714
|
+
sm: "py-2 text-sm",
|
|
715
|
+
md: "py-2.5 text-sm",
|
|
716
|
+
lg: "py-3.5 text-base min-h-touch", // 44px touch target for mobile
|
|
717
717
|
};
|
|
718
718
|
/**
|
|
719
719
|
* Select - Dropdown select component with search, groups, virtual scrolling, and mobile support
|
|
@@ -786,9 +786,9 @@ const optionSizeClasses = {
|
|
|
786
786
|
* ```
|
|
787
787
|
*/
|
|
788
788
|
const Select = forwardRef((props, ref) => {
|
|
789
|
-
const { options = [], groups = [], value, onChange, placeholder =
|
|
789
|
+
const { options = [], groups = [], value, onChange, placeholder = "Select an option", searchable = false, disabled = false, label, helperText, error, loading = false, clearable = false, creatable = false, onCreateOption, virtualized = false, virtualHeight = "300px", virtualItemHeight = 42, size = "md", mobileMode = "auto", usePortal = true, required = false, } = props;
|
|
790
790
|
const [isOpen, setIsOpen] = useState(false);
|
|
791
|
-
const [searchQuery, setSearchQuery] = useState(
|
|
791
|
+
const [searchQuery, setSearchQuery] = useState("");
|
|
792
792
|
const [scrollTop, setScrollTop] = useState(0);
|
|
793
793
|
const [activeDescendant] = useState(undefined);
|
|
794
794
|
const [dropdownPosition, setDropdownPosition] = useState(null);
|
|
@@ -801,10 +801,10 @@ const Select = forwardRef((props, ref) => {
|
|
|
801
801
|
const nativeSelectRef = useRef(null);
|
|
802
802
|
// Detect mobile viewport
|
|
803
803
|
const isMobile = useIsMobile();
|
|
804
|
-
const useMobileSheet = mobileMode ===
|
|
805
|
-
const useNativeSelect = mobileMode ===
|
|
804
|
+
const useMobileSheet = mobileMode === "auto" && isMobile;
|
|
805
|
+
const useNativeSelect = mobileMode === "native" && isMobile;
|
|
806
806
|
// Auto-size for mobile
|
|
807
|
-
const effectiveSize = isMobile && size ===
|
|
807
|
+
const effectiveSize = isMobile && size === "md" ? "lg" : size;
|
|
808
808
|
// Generate unique IDs for ARIA
|
|
809
809
|
const labelId = useId();
|
|
810
810
|
const listboxId = useId();
|
|
@@ -818,11 +818,8 @@ const Select = forwardRef((props, ref) => {
|
|
|
818
818
|
close: () => setIsOpen(false),
|
|
819
819
|
}));
|
|
820
820
|
// Flatten all options (from both options and groups)
|
|
821
|
-
const allOptions = [
|
|
822
|
-
|
|
823
|
-
...groups.flatMap(group => group.options)
|
|
824
|
-
];
|
|
825
|
-
const selectedOption = allOptions.find(opt => opt.value === value);
|
|
821
|
+
const allOptions = [...options, ...groups.flatMap((group) => group.options)];
|
|
822
|
+
const selectedOption = allOptions.find((opt) => opt.value === value);
|
|
826
823
|
// Filter options/groups based on search
|
|
827
824
|
const getFilteredData = () => {
|
|
828
825
|
if (!searchable || !searchQuery) {
|
|
@@ -830,41 +827,58 @@ const Select = forwardRef((props, ref) => {
|
|
|
830
827
|
}
|
|
831
828
|
const query = searchQuery.toLowerCase();
|
|
832
829
|
// Filter flat options
|
|
833
|
-
const filteredOptions = options.filter(opt => opt.label.toLowerCase().includes(query));
|
|
830
|
+
const filteredOptions = options.filter((opt) => opt.label.toLowerCase().includes(query));
|
|
834
831
|
// Filter grouped options
|
|
835
832
|
const filteredGroups = groups
|
|
836
|
-
.map(group => ({
|
|
833
|
+
.map((group) => ({
|
|
837
834
|
...group,
|
|
838
|
-
options: group.options.filter(opt => opt.label.toLowerCase().includes(query))
|
|
835
|
+
options: group.options.filter((opt) => opt.label.toLowerCase().includes(query)),
|
|
839
836
|
}))
|
|
840
|
-
.filter(group => group.options.length > 0);
|
|
837
|
+
.filter((group) => group.options.length > 0);
|
|
841
838
|
return { options: filteredOptions, groups: filteredGroups };
|
|
842
839
|
};
|
|
843
840
|
const { options: filteredOptions, groups: filteredGroups } = getFilteredData();
|
|
844
841
|
// Virtual scrolling calculations
|
|
845
|
-
const totalItems = filteredOptions.length + filteredGroups.flatMap(g => g.options).length;
|
|
842
|
+
const totalItems = filteredOptions.length + filteredGroups.flatMap((g) => g.options).length;
|
|
846
843
|
const useVirtualScrolling = virtualized && totalItems > 50;
|
|
847
844
|
const visibleRangeStart = useVirtualScrolling
|
|
848
845
|
? Math.floor(scrollTop / virtualItemHeight)
|
|
849
846
|
: 0;
|
|
850
847
|
const visibleRangeEnd = useVirtualScrolling
|
|
851
|
-
? Math.min(visibleRangeStart +
|
|
848
|
+
? Math.min(visibleRangeStart +
|
|
849
|
+
Math.ceil(parseInt(virtualHeight) / virtualItemHeight) +
|
|
850
|
+
5, totalItems)
|
|
852
851
|
: totalItems;
|
|
853
852
|
// Flatten all filtered items for virtualization
|
|
854
853
|
const allFilteredItems = [
|
|
855
|
-
...filteredOptions.map((opt, idx) => ({
|
|
856
|
-
|
|
854
|
+
...filteredOptions.map((opt, idx) => ({
|
|
855
|
+
type: "option",
|
|
856
|
+
option: opt,
|
|
857
|
+
groupIndex: -1,
|
|
858
|
+
optionIndex: idx,
|
|
859
|
+
})),
|
|
860
|
+
...filteredGroups.flatMap((group, groupIdx) => group.options.map((opt, optIdx) => ({
|
|
861
|
+
type: "grouped",
|
|
862
|
+
option: opt,
|
|
863
|
+
groupIndex: groupIdx,
|
|
864
|
+
optionIndex: optIdx,
|
|
865
|
+
groupLabel: group.label,
|
|
866
|
+
}))),
|
|
857
867
|
];
|
|
858
868
|
const visibleItems = useVirtualScrolling
|
|
859
869
|
? allFilteredItems.slice(visibleRangeStart, visibleRangeEnd)
|
|
860
870
|
: allFilteredItems;
|
|
861
|
-
const offsetY = useVirtualScrolling
|
|
862
|
-
|
|
871
|
+
const offsetY = useVirtualScrolling
|
|
872
|
+
? visibleRangeStart * virtualItemHeight
|
|
873
|
+
: 0;
|
|
874
|
+
const totalHeight = useVirtualScrolling
|
|
875
|
+
? totalItems * virtualItemHeight
|
|
876
|
+
: "auto";
|
|
863
877
|
// Check if we should show "Create" option
|
|
864
878
|
const showCreateOption = creatable &&
|
|
865
|
-
searchQuery.trim() !==
|
|
866
|
-
!filteredOptions.some(opt => opt.label.toLowerCase() === searchQuery.toLowerCase()) &&
|
|
867
|
-
!filteredGroups.some(group => group.options.some(opt => opt.label.toLowerCase() === searchQuery.toLowerCase()));
|
|
879
|
+
searchQuery.trim() !== "" &&
|
|
880
|
+
!filteredOptions.some((opt) => opt.label.toLowerCase() === searchQuery.toLowerCase()) &&
|
|
881
|
+
!filteredGroups.some((group) => group.options.some((opt) => opt.label.toLowerCase() === searchQuery.toLowerCase()));
|
|
868
882
|
// Handle creating new option
|
|
869
883
|
const handleCreateOption = () => {
|
|
870
884
|
if (onCreateOption) {
|
|
@@ -874,7 +888,7 @@ const Select = forwardRef((props, ref) => {
|
|
|
874
888
|
// If no callback, just select the typed value
|
|
875
889
|
onChange?.(searchQuery.trim());
|
|
876
890
|
}
|
|
877
|
-
setSearchQuery(
|
|
891
|
+
setSearchQuery("");
|
|
878
892
|
setIsOpen(false);
|
|
879
893
|
};
|
|
880
894
|
// Handle click outside (desktop dropdown only)
|
|
@@ -888,14 +902,14 @@ const Select = forwardRef((props, ref) => {
|
|
|
888
902
|
const isOutsideDropdown = dropdownRef.current && !dropdownRef.current.contains(target);
|
|
889
903
|
if (isOutsideSelect && isOutsideDropdown) {
|
|
890
904
|
setIsOpen(false);
|
|
891
|
-
setSearchQuery(
|
|
905
|
+
setSearchQuery("");
|
|
892
906
|
}
|
|
893
907
|
};
|
|
894
908
|
if (isOpen) {
|
|
895
|
-
document.addEventListener(
|
|
909
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
896
910
|
}
|
|
897
911
|
return () => {
|
|
898
|
-
document.removeEventListener(
|
|
912
|
+
document.removeEventListener("mousedown", handleClickOutside);
|
|
899
913
|
};
|
|
900
914
|
}, [isOpen, useMobileSheet]);
|
|
901
915
|
// Focus search input when opened
|
|
@@ -929,8 +943,8 @@ const Select = forwardRef((props, ref) => {
|
|
|
929
943
|
const hasSpaceBelow = spaceBelow >= dropdownHeight + gap;
|
|
930
944
|
const hasSpaceAbove = spaceAbove >= dropdownHeight + gap;
|
|
931
945
|
// Prefer bottom placement, flip to top if not enough space below but enough above
|
|
932
|
-
const placement = hasSpaceBelow || !hasSpaceAbove ?
|
|
933
|
-
const top = placement ===
|
|
946
|
+
const placement = hasSpaceBelow || !hasSpaceAbove ? "bottom" : "top";
|
|
947
|
+
const top = placement === "bottom"
|
|
934
948
|
? rect.bottom + gap
|
|
935
949
|
: rect.top - dropdownHeight - gap;
|
|
936
950
|
setDropdownPosition({
|
|
@@ -943,23 +957,23 @@ const Select = forwardRef((props, ref) => {
|
|
|
943
957
|
// Initial position calculation
|
|
944
958
|
updatePosition();
|
|
945
959
|
// Listen for scroll events on all scrollable ancestors
|
|
946
|
-
window.addEventListener(
|
|
947
|
-
window.addEventListener(
|
|
960
|
+
window.addEventListener("scroll", updatePosition, true);
|
|
961
|
+
window.addEventListener("resize", updatePosition);
|
|
948
962
|
return () => {
|
|
949
|
-
window.removeEventListener(
|
|
950
|
-
window.removeEventListener(
|
|
963
|
+
window.removeEventListener("scroll", updatePosition, true);
|
|
964
|
+
window.removeEventListener("resize", updatePosition);
|
|
951
965
|
};
|
|
952
966
|
}, [isOpen, useMobileSheet, usePortal]);
|
|
953
967
|
// Lock body scroll when mobile sheet is open
|
|
954
968
|
useEffect(() => {
|
|
955
969
|
if (useMobileSheet && isOpen) {
|
|
956
|
-
document.body.style.overflow =
|
|
970
|
+
document.body.style.overflow = "hidden";
|
|
957
971
|
}
|
|
958
972
|
else {
|
|
959
|
-
document.body.style.overflow =
|
|
973
|
+
document.body.style.overflow = "";
|
|
960
974
|
}
|
|
961
975
|
return () => {
|
|
962
|
-
document.body.style.overflow =
|
|
976
|
+
document.body.style.overflow = "";
|
|
963
977
|
};
|
|
964
978
|
}, [isOpen, useMobileSheet]);
|
|
965
979
|
// Handle escape key for mobile sheet
|
|
@@ -967,83 +981,105 @@ const Select = forwardRef((props, ref) => {
|
|
|
967
981
|
if (!useMobileSheet || !isOpen)
|
|
968
982
|
return;
|
|
969
983
|
const handleEscape = (e) => {
|
|
970
|
-
if (e.key ===
|
|
984
|
+
if (e.key === "Escape") {
|
|
971
985
|
setIsOpen(false);
|
|
972
|
-
setSearchQuery(
|
|
986
|
+
setSearchQuery("");
|
|
973
987
|
}
|
|
974
988
|
};
|
|
975
|
-
document.addEventListener(
|
|
976
|
-
return () => document.removeEventListener(
|
|
989
|
+
document.addEventListener("keydown", handleEscape);
|
|
990
|
+
return () => document.removeEventListener("keydown", handleEscape);
|
|
977
991
|
}, [isOpen, useMobileSheet]);
|
|
978
992
|
const handleSelect = (optionValue) => {
|
|
979
993
|
onChange?.(optionValue);
|
|
980
994
|
setIsOpen(false);
|
|
981
|
-
setSearchQuery(
|
|
995
|
+
setSearchQuery("");
|
|
982
996
|
};
|
|
983
997
|
const handleClose = () => {
|
|
984
998
|
setIsOpen(false);
|
|
985
|
-
setSearchQuery(
|
|
999
|
+
setSearchQuery("");
|
|
986
1000
|
};
|
|
987
1001
|
// Render option button (shared between desktop and mobile)
|
|
988
1002
|
const renderOption = (option, isSelected, mobile = false) => (jsxs("button", { type: "button", onClick: () => !option.disabled && handleSelect(option.value), disabled: option.disabled, className: `
|
|
989
1003
|
w-full flex items-center justify-between px-4 transition-colors
|
|
990
1004
|
${mobile ? optionSizeClasses.lg : optionSizeClasses[effectiveSize]}
|
|
991
|
-
${isSelected ?
|
|
992
|
-
${option.disabled ?
|
|
993
|
-
`, role: "option", "aria-selected": isSelected, children: [jsxs("span", { className: "flex items-center gap-2", children: [option.icon && jsx("span", { children: option.icon }), option.label] }), isSelected && jsx(Check, { className: `${mobile ?
|
|
1005
|
+
${isSelected ? "bg-accent-50 text-accent-900" : "text-ink-700"}
|
|
1006
|
+
${option.disabled ? "opacity-40 cursor-not-allowed" : "hover:bg-paper-50 active:bg-paper-100 cursor-pointer"}
|
|
1007
|
+
`, role: "option", "aria-selected": isSelected, children: [jsxs("span", { className: "flex items-center gap-2", children: [option.icon && jsx("span", { children: option.icon }), option.label] }), isSelected && (jsx(Check, { className: `${mobile ? "h-5 w-5" : "h-4 w-4"} text-accent-600` }))] }, option.value));
|
|
994
1008
|
// Render options list content (shared between desktop and mobile)
|
|
995
1009
|
const renderOptionsContent = (mobile = false) => {
|
|
996
1010
|
if (loading) {
|
|
997
1011
|
return (jsxs("div", { className: "px-4 py-8 flex items-center justify-center", role: "status", "aria-live": "polite", children: [jsx(Loader2, { className: "h-5 w-5 animate-spin text-ink-500" }), jsx("span", { className: "ml-2 text-sm text-ink-500", children: "Loading..." })] }));
|
|
998
1012
|
}
|
|
999
|
-
if (filteredOptions.length === 0 &&
|
|
1013
|
+
if (filteredOptions.length === 0 &&
|
|
1014
|
+
filteredGroups.length === 0 &&
|
|
1015
|
+
!showCreateOption) {
|
|
1000
1016
|
return (jsx("div", { className: "px-4 py-3 text-sm text-ink-500 text-center", role: "status", "aria-live": "polite", children: "No options found" }));
|
|
1001
1017
|
}
|
|
1002
1018
|
return (jsxs(Fragment, { children: [showCreateOption && (jsx("button", { type: "button", onClick: handleCreateOption, className: `
|
|
1003
1019
|
w-full flex items-center px-4 text-accent-700 hover:bg-accent-50 transition-colors border-b border-paper-200
|
|
1004
|
-
${mobile ?
|
|
1005
|
-
`, children: jsxs("span", { className: "font-medium", children: ["Create \"", searchQuery, "\""] }) })), useVirtualScrolling ? (jsx("div", { style: { height: totalHeight, position:
|
|
1020
|
+
${mobile ? "py-3.5 text-base" : "py-2.5 text-sm"}
|
|
1021
|
+
`, children: jsxs("span", { className: "font-medium", children: ["Create \"", searchQuery, "\""] }) })), useVirtualScrolling ? (jsx("div", { style: { height: totalHeight, position: "relative" }, children: jsx("div", { style: { transform: `translateY(${offsetY}px)` }, children: visibleItems.map((item) => {
|
|
1006
1022
|
const option = item.option;
|
|
1007
1023
|
const isSelected = option.value === value;
|
|
1008
1024
|
const key = `${item.type}-${item.groupIndex}-${item.optionIndex}-${option.value}`;
|
|
1009
|
-
return (jsxs("button", { type: "button", onClick: () => !option.disabled && handleSelect(option.value), disabled: option.disabled, style: {
|
|
1025
|
+
return (jsxs("button", { type: "button", onClick: () => !option.disabled && handleSelect(option.value), disabled: option.disabled, style: {
|
|
1026
|
+
height: mobile ? "56px" : `${virtualItemHeight}px`,
|
|
1027
|
+
}, className: `
|
|
1010
1028
|
w-full flex items-center justify-between px-4 transition-colors
|
|
1011
|
-
${mobile ?
|
|
1012
|
-
${isSelected ?
|
|
1013
|
-
${option.disabled ?
|
|
1014
|
-
`, role: "option", "aria-selected": isSelected, children: [jsxs("span", { className: "flex items-center gap-2", children: [option.icon && jsx("span", { children: option.icon }), option.label] }), isSelected && jsx(Check, { className: `${mobile ?
|
|
1029
|
+
${mobile ? "text-base" : "text-sm"}
|
|
1030
|
+
${isSelected ? "bg-accent-50 text-accent-900" : "text-ink-700"}
|
|
1031
|
+
${option.disabled ? "opacity-40 cursor-not-allowed" : "hover:bg-paper-50 cursor-pointer"}
|
|
1032
|
+
`, role: "option", "aria-selected": isSelected, children: [jsxs("span", { className: "flex items-center gap-2", children: [option.icon && jsx("span", { children: option.icon }), option.label] }), isSelected && (jsx(Check, { className: `${mobile ? "h-5 w-5" : "h-4 w-4"} text-accent-600` }))] }, key));
|
|
1015
1033
|
}) }) })) : (jsxs(Fragment, { children: [filteredOptions.map((option) => renderOption(option, option.value === value, mobile)), filteredGroups.map((group) => (jsxs("div", { children: [jsx("div", { className: `
|
|
1016
1034
|
px-4 font-semibold text-ink-500 uppercase tracking-wider bg-paper-50 border-t border-b border-paper-200
|
|
1017
|
-
${mobile ?
|
|
1035
|
+
${mobile ? "py-2.5 text-xs" : "py-2 text-xs"}
|
|
1018
1036
|
`, children: group.label }), group.options.map((option) => renderOption(option, option.value === value, mobile))] }, group.label)))] }))] }));
|
|
1019
1037
|
};
|
|
1020
1038
|
// Native select for mobile (optional)
|
|
1021
1039
|
if (useNativeSelect) {
|
|
1022
|
-
return (jsxs("div", { className: "w-full", children: [label && (jsxs("label", { id: labelId, className: "label", children: [label, required && jsx("span", { className: "text-error-500 ml-1", children: "*" })] })), jsxs("div", { className: "relative", children: [jsxs("select", { ref: nativeSelectRef, value: value ||
|
|
1040
|
+
return (jsxs("div", { className: "w-full", children: [label && (jsxs("label", { id: labelId, className: "label", children: [label, required && jsx("span", { className: "text-error-500 ml-1", children: "*" })] })), jsxs("div", { className: "relative", children: [jsxs("select", { ref: nativeSelectRef, value: value || "", onChange: (e) => onChange?.(e.target.value), disabled: disabled, className: `
|
|
1023
1041
|
input w-full appearance-none pr-10
|
|
1024
1042
|
${sizeClasses$d[effectiveSize]}
|
|
1025
|
-
${error ?
|
|
1026
|
-
${disabled ?
|
|
1027
|
-
`, "aria-labelledby": label ? labelId : undefined, "aria-invalid": error ?
|
|
1043
|
+
${error ? "border-error-400 focus:border-error-400 focus:ring-error-400" : ""}
|
|
1044
|
+
${disabled ? "opacity-40 cursor-not-allowed" : "cursor-pointer"}
|
|
1045
|
+
`, "aria-labelledby": label ? labelId : undefined, "aria-invalid": error ? "true" : undefined, "aria-describedby": error ? errorId : helperText ? helperTextId : undefined, "aria-required": required, children: [jsx("option", { value: "", disabled: true, children: placeholder }), options.map((opt) => (jsx("option", { value: opt.value, disabled: opt.disabled, children: opt.label }, opt.value))), groups.map((group) => (jsx("optgroup", { label: group.label, children: group.options.map((opt) => (jsx("option", { value: opt.value, disabled: opt.disabled, children: opt.label }, opt.value))) }, group.label)))] }), jsx(ChevronDown, { className: "absolute right-3 top-1/2 -translate-y-1/2 h-5 w-5 text-ink-500 pointer-events-none" })] }), error && (jsx("p", { id: errorId, className: "mt-2 text-xs text-error-600", role: "alert", "aria-live": "assertive", children: error })), helperText && !error && (jsx("p", { id: helperTextId, className: "mt-2 text-xs text-ink-600", children: helperText }))] }));
|
|
1028
1046
|
}
|
|
1029
1047
|
return (jsxs("div", { className: "w-full", children: [label && (jsxs("label", { id: labelId, className: "label", children: [label, required && jsx("span", { className: "text-error-500 ml-1", children: "*" })] })), jsx("div", { ref: selectRef, className: "relative", children: jsxs("button", { ref: buttonRef, type: "button", onClick: () => !disabled && setIsOpen(!isOpen), disabled: disabled, className: `
|
|
1030
1048
|
input w-full flex items-center justify-between px-3
|
|
1031
1049
|
${sizeClasses$d[effectiveSize]}
|
|
1032
|
-
${error ?
|
|
1033
|
-
${disabled ?
|
|
1034
|
-
`, role: "combobox", "aria-haspopup": "listbox", "aria-expanded": isOpen, "aria-controls": listboxId, "aria-labelledby": label ? labelId : undefined, "aria-label": !label ? placeholder : undefined, "aria-activedescendant": activeDescendant, "aria-invalid": error ?
|
|
1050
|
+
${error ? "border-error-400 focus:border-error-400 focus:ring-error-400" : ""}
|
|
1051
|
+
${disabled ? "opacity-40 cursor-not-allowed" : "cursor-pointer"}
|
|
1052
|
+
`, role: "combobox", "aria-haspopup": "listbox", "aria-expanded": isOpen, "aria-controls": listboxId, "aria-labelledby": label ? labelId : undefined, "aria-label": !label ? placeholder : undefined, "aria-activedescendant": activeDescendant, "aria-invalid": error ? "true" : undefined, "aria-describedby": error ? errorId : helperText ? helperTextId : undefined, "aria-disabled": disabled, "aria-required": required, children: [jsxs("span", { className: `flex items-center gap-2 ${selectedOption ? "text-ink-800" : "text-ink-400"}`, children: [loading && (jsx(Loader2, { className: "h-4 w-4 animate-spin text-ink-500" })), !loading && selectedOption?.icon && (jsx("span", { children: selectedOption.icon })), selectedOption ? selectedOption.label : placeholder] }), jsxs("div", { className: "flex items-center gap-1", children: [clearable && value && (jsx("span", { role: "button", tabIndex: -1, onClick: (e) => {
|
|
1035
1053
|
e.stopPropagation();
|
|
1036
|
-
onChange?.(
|
|
1054
|
+
onChange?.("");
|
|
1037
1055
|
setIsOpen(false);
|
|
1038
|
-
},
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1056
|
+
}, onKeyDown: (e) => {
|
|
1057
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
1058
|
+
e.preventDefault();
|
|
1059
|
+
e.stopPropagation();
|
|
1060
|
+
onChange?.("");
|
|
1061
|
+
setIsOpen(false);
|
|
1062
|
+
}
|
|
1063
|
+
}, className: "text-ink-400 hover:text-ink-600 transition-colors p-0.5 cursor-pointer inline-flex", "aria-label": "Clear selection", children: jsx(X, { className: `${effectiveSize === "lg" ? "h-5 w-5" : "h-4 w-4"}` }) })), jsx(ChevronDown, { className: `${effectiveSize === "lg" ? "h-5 w-5" : "h-4 w-4"} text-ink-500 transition-transform ${isOpen ? "rotate-180" : ""}` })] })] }) }), isOpen &&
|
|
1064
|
+
!useMobileSheet &&
|
|
1065
|
+
(usePortal ? dropdownPosition : true) &&
|
|
1066
|
+
(usePortal ? (createPortal(jsxs("div", { ref: dropdownRef, className: `fixed z-[9999] bg-white bg-subtle-grain rounded-lg shadow-lg border border-paper-200 max-h-60 overflow-hidden animate-fade-in ${dropdownPosition?.placement === "top"
|
|
1067
|
+
? "origin-bottom"
|
|
1068
|
+
: "origin-top"}`, style: {
|
|
1069
|
+
top: dropdownPosition.top,
|
|
1070
|
+
left: dropdownPosition.left,
|
|
1071
|
+
width: dropdownPosition.width,
|
|
1072
|
+
}, children: [searchable && (jsx("div", { className: "p-2 border-b border-paper-200", children: jsxs("div", { className: "relative", children: [jsx(Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-ink-400" }), jsx("input", { ref: searchInputRef, type: "text", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), placeholder: "Search...", className: "w-full pl-9 pr-3 py-2 text-sm border border-paper-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-accent-400 focus:border-accent-400", role: "searchbox", "aria-label": "Search options", "aria-autocomplete": "list", "aria-controls": listboxId })] }) })), jsx("div", { ref: listRef, id: listboxId, className: "overflow-y-auto", style: {
|
|
1073
|
+
maxHeight: useVirtualScrolling ? virtualHeight : "12rem",
|
|
1074
|
+
}, onScroll: (e) => useVirtualScrolling && setScrollTop(e.currentTarget.scrollTop), role: "listbox", "aria-label": "Available options", "aria-multiselectable": "false", children: renderOptionsContent(false) })] }), document.body)) : (
|
|
1075
|
+
// Non-portal dropdown (inline, relative positioning)
|
|
1076
|
+
jsxs("div", { ref: dropdownRef, className: "absolute z-50 mt-1 w-full bg-white bg-subtle-grain rounded-lg shadow-lg border border-paper-200 max-h-60 overflow-hidden animate-fade-in", children: [searchable && (jsx("div", { className: "p-2 border-b border-paper-200", children: jsxs("div", { className: "relative", children: [jsx(Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-ink-400" }), jsx("input", { ref: searchInputRef, type: "text", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), placeholder: "Search...", className: "w-full pl-9 pr-3 py-2 text-sm border border-paper-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-accent-400 focus:border-accent-400", role: "searchbox", "aria-label": "Search options", "aria-autocomplete": "list", "aria-controls": listboxId })] }) })), jsx("div", { ref: listRef, id: listboxId, className: "overflow-y-auto", style: {
|
|
1077
|
+
maxHeight: useVirtualScrolling ? virtualHeight : "12rem",
|
|
1078
|
+
}, onScroll: (e) => useVirtualScrolling && setScrollTop(e.currentTarget.scrollTop), role: "listbox", "aria-label": "Available options", "aria-multiselectable": "false", children: renderOptionsContent(false) })] }))), isOpen &&
|
|
1079
|
+
useMobileSheet &&
|
|
1080
|
+
createPortal(jsxs("div", { className: "fixed inset-0 z-50 flex items-end", onClick: (e) => e.target === e.currentTarget && handleClose(), role: "dialog", "aria-modal": "true", "aria-labelledby": label ? `mobile-${labelId}` : undefined, children: [jsx("div", { className: "absolute inset-0 bg-black/50 animate-fade-in" }), jsxs("div", { className: "relative w-full bg-white rounded-t-2xl shadow-2xl animate-slide-up max-h-[85vh] flex flex-col", style: { paddingBottom: "env(safe-area-inset-bottom)" }, children: [jsx("div", { className: "py-3 cursor-grab", children: jsx("div", { className: "w-12 h-1.5 bg-ink-300 rounded-full mx-auto" }) }), jsxs("div", { className: "px-4 pb-3 border-b border-paper-200 flex items-center justify-between", children: [label && (jsx("h2", { id: `mobile-${labelId}`, className: "text-lg font-semibold text-ink-900", children: label })), !label && (jsx("h2", { className: "text-lg font-semibold text-ink-900", children: placeholder })), jsx("button", { onClick: handleClose, className: "text-ink-400 hover:text-ink-600 transition-colors p-2 -mr-2", "aria-label": "Close", children: jsx(X, { className: "h-5 w-5" }) })] }), searchable && (jsx("div", { className: "p-3 border-b border-paper-200", children: jsxs("div", { className: "relative", children: [jsx(Search, { className: "absolute left-4 top-1/2 -translate-y-1/2 h-5 w-5 text-ink-400" }), jsx("input", { ref: mobileSearchInputRef, type: "text", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), placeholder: "Search...", inputMode: "search", enterKeyHint: "search", className: "w-full pl-12 pr-4 py-3 text-base border border-paper-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-accent-400 focus:border-accent-400", role: "searchbox", "aria-label": "Search options" })] }) })), jsx("div", { id: listboxId, className: "overflow-y-auto flex-1", role: "listbox", "aria-label": "Available options", "aria-multiselectable": "false", children: renderOptionsContent(true) })] })] }), document.body), error && (jsx("p", { id: errorId, className: "mt-2 text-xs text-error-600", role: "alert", "aria-live": "assertive", children: error })), helperText && !error && (jsx("p", { id: helperTextId, className: "mt-2 text-xs text-ink-600", children: helperText }))] }));
|
|
1045
1081
|
});
|
|
1046
|
-
Select.displayName =
|
|
1082
|
+
Select.displayName = "Select";
|
|
1047
1083
|
|
|
1048
1084
|
const MultiSelect = forwardRef(({ options, value = [], onChange, placeholder = 'Select options', searchable = false, disabled = false, label, helperText, error, maxHeight = 240, maxSelections, loading = false, 'aria-label': ariaLabel, }, ref) => {
|
|
1049
1085
|
const [isOpen, setIsOpen] = useState(false);
|
|
@@ -15760,44 +15796,52 @@ function getAugmentedNamespace(n) {
|
|
|
15760
15796
|
* (A1, A1:C5, ...)
|
|
15761
15797
|
*/
|
|
15762
15798
|
|
|
15763
|
-
|
|
15799
|
+
var collection;
|
|
15800
|
+
var hasRequiredCollection;
|
|
15801
|
+
|
|
15802
|
+
function requireCollection () {
|
|
15803
|
+
if (hasRequiredCollection) return collection;
|
|
15804
|
+
hasRequiredCollection = 1;
|
|
15805
|
+
class Collection {
|
|
15764
15806
|
|
|
15765
|
-
|
|
15766
|
-
|
|
15767
|
-
|
|
15768
|
-
|
|
15769
|
-
|
|
15770
|
-
|
|
15771
|
-
|
|
15772
|
-
|
|
15773
|
-
|
|
15774
|
-
|
|
15775
|
-
|
|
15807
|
+
constructor(data, refs) {
|
|
15808
|
+
if (data == null && refs == null) {
|
|
15809
|
+
this._data = [];
|
|
15810
|
+
this._refs = [];
|
|
15811
|
+
} else {
|
|
15812
|
+
if (data.length !== refs.length)
|
|
15813
|
+
throw Error('Collection: data length should match references length.');
|
|
15814
|
+
this._data = data;
|
|
15815
|
+
this._refs = refs;
|
|
15816
|
+
}
|
|
15817
|
+
}
|
|
15776
15818
|
|
|
15777
|
-
|
|
15778
|
-
|
|
15779
|
-
|
|
15819
|
+
get data() {
|
|
15820
|
+
return this._data;
|
|
15821
|
+
}
|
|
15780
15822
|
|
|
15781
|
-
|
|
15782
|
-
|
|
15783
|
-
|
|
15823
|
+
get refs() {
|
|
15824
|
+
return this._refs;
|
|
15825
|
+
}
|
|
15784
15826
|
|
|
15785
|
-
|
|
15786
|
-
|
|
15787
|
-
|
|
15827
|
+
get length() {
|
|
15828
|
+
return this._data.length;
|
|
15829
|
+
}
|
|
15788
15830
|
|
|
15789
|
-
|
|
15790
|
-
|
|
15791
|
-
|
|
15792
|
-
|
|
15793
|
-
|
|
15794
|
-
|
|
15795
|
-
|
|
15796
|
-
|
|
15797
|
-
|
|
15798
|
-
}
|
|
15831
|
+
/**
|
|
15832
|
+
* Add data and references to this collection.
|
|
15833
|
+
* @param {{}} obj - data
|
|
15834
|
+
* @param {{}} ref - reference
|
|
15835
|
+
*/
|
|
15836
|
+
add(obj, ref) {
|
|
15837
|
+
this._data.push(obj);
|
|
15838
|
+
this._refs.push(ref);
|
|
15839
|
+
}
|
|
15840
|
+
}
|
|
15799
15841
|
|
|
15800
|
-
|
|
15842
|
+
collection = Collection;
|
|
15843
|
+
return collection;
|
|
15844
|
+
}
|
|
15801
15845
|
|
|
15802
15846
|
var helpers;
|
|
15803
15847
|
var hasRequiredHelpers;
|
|
@@ -15806,7 +15850,7 @@ function requireHelpers () {
|
|
|
15806
15850
|
if (hasRequiredHelpers) return helpers;
|
|
15807
15851
|
hasRequiredHelpers = 1;
|
|
15808
15852
|
const FormulaError = requireError();
|
|
15809
|
-
const Collection =
|
|
15853
|
+
const Collection = requireCollection();
|
|
15810
15854
|
|
|
15811
15855
|
const Types = {
|
|
15812
15856
|
NUMBER: 0,
|
|
@@ -25460,7 +25504,7 @@ var engineering = EngineeringFunctions;
|
|
|
25460
25504
|
|
|
25461
25505
|
const FormulaError$b = requireError();
|
|
25462
25506
|
const {FormulaHelpers: FormulaHelpers$8, Types: Types$6, WildCard, Address: Address$3} = requireHelpers();
|
|
25463
|
-
const Collection$2 =
|
|
25507
|
+
const Collection$2 = requireCollection();
|
|
25464
25508
|
const H$5 = FormulaHelpers$8;
|
|
25465
25509
|
|
|
25466
25510
|
const ReferenceFunctions$1 = {
|
|
@@ -37088,7 +37132,7 @@ var parsing = {
|
|
|
37088
37132
|
const FormulaError$4 = requireError();
|
|
37089
37133
|
const {Address: Address$1} = requireHelpers();
|
|
37090
37134
|
const {Prefix: Prefix$1, Postfix: Postfix$1, Infix: Infix$1, Operators: Operators$1} = operators;
|
|
37091
|
-
const Collection$1 =
|
|
37135
|
+
const Collection$1 = requireCollection();
|
|
37092
37136
|
const MAX_ROW$1 = 1048576, MAX_COLUMN$1 = 16384;
|
|
37093
37137
|
const {NotAllInputParsedException} = require$$4;
|
|
37094
37138
|
|
|
@@ -37850,7 +37894,7 @@ var hooks$1 = {
|
|
|
37850
37894
|
const FormulaError$2 = requireError();
|
|
37851
37895
|
const {FormulaHelpers: FormulaHelpers$1, Types, Address} = requireHelpers();
|
|
37852
37896
|
const {Prefix, Postfix, Infix, Operators} = operators;
|
|
37853
|
-
const Collection =
|
|
37897
|
+
const Collection = requireCollection();
|
|
37854
37898
|
const MAX_ROW = 1048576, MAX_COLUMN = 16384;
|
|
37855
37899
|
|
|
37856
37900
|
let Utils$1 = class Utils {
|